Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
128 commits
Select commit Hold shift + click to select a range
6b8216c
ui(chats): inline repeater node ID before name in RepeatRowView
Avi0n Feb 23, 2026
ea34bdd
fix(chat): serialize snapshot applies
Avi0n Feb 23, 2026
ce52810
test(chat): add snapshot deadlock regression
Avi0n Feb 23, 2026
e824c19
fix(chat): extract and fix divider position computation
Avi0n Feb 23, 2026
ae84c18
refactor(tests): inject UserDefaults to make all test suites parallel…
Avi0n Feb 23, 2026
8c5c711
fix(ci): stabilize flaky test runs
Avi0n Feb 23, 2026
5986c53
feat(protocol): add multibyte path hash support
Avi0n Feb 24, 2026
2e5ca59
feat(protocol): add multibyte path hash support
Avi0n Feb 24, 2026
11c3d47
refactor: split PersistenceStore into extension files
Avi0n Feb 24, 2026
dde627c
refactor: split MessageService into extension files
Avi0n Feb 24, 2026
3a9513a
refactor: split BLEStateMachine into extension files
Avi0n Feb 24, 2026
813bdd4
refactor: split ChatViewModel into extension files
Avi0n Feb 24, 2026
4dc55ec
refactor: split SyncCoordinator into extension files
Avi0n Feb 24, 2026
bcb85d5
refactor: split ConnectionManager into extension files
Avi0n Feb 24, 2026
f7c6c11
Merge branch 'refactor/split-persistence-store' into dev
Avi0n Feb 24, 2026
90e9b2c
Merge branch 'refactor/split-message-service' into dev
Avi0n Feb 24, 2026
a8b4a45
Merge branch 'refactor/split-chat-view-model' into dev
Avi0n Feb 24, 2026
2914c6c
Merge branch 'refactor/split-ble-state-machine' into dev
Avi0n Feb 24, 2026
b0619ca
Merge branch 'refactor/split-connection-manager' into dev
Avi0n Feb 24, 2026
b2c5785
Merge branch 'refactor/split-sync-coordinator' into dev
Avi0n Feb 24, 2026
51d0214
fix: address review feedback for PersistenceStore split
Avi0n Feb 24, 2026
a290789
fix: restore private access for channelService in ChatViewModel
Avi0n Feb 24, 2026
30b679e
fix: restore private access for 8 unnecessarily widened ConnectionMan…
Avi0n Feb 24, 2026
1591f7c
test: add mock infrastructure for coverage improvement
Avi0n Feb 24, 2026
dcaaeb6
test: add coverage tests for split extension files
Avi0n Feb 24, 2026
64e748f
refactor: deduplicate validation and error handling in MessageService…
Avi0n Feb 24, 2026
168680d
refactor: extract shared logic from SyncCoordinator message handlers
Avi0n Feb 24, 2026
866a23b
chore: consolidate test helpers and remove unused MockAdvertisementSe…
Avi0n Feb 24, 2026
1c38f29
feat(trace): add rooms to trace path list
Avi0n Feb 24, 2026
858d546
feat(contacts): add discovered node fallback for path name resolution
Avi0n Feb 25, 2026
d777876
feat(trace): add "include discovered" toggle to path pickers
Avi0n Feb 25, 2026
7305121
refactor(trace): unify ContactDTO and DiscoveredNodeDTO resolution
Avi0n Feb 25, 2026
77af6fd
feat(discover): show node ID hash in discovered node rows
Avi0n Feb 25, 2026
acc7b18
fix(chat): clear compose text synchronously before send task
Avi0n Feb 25, 2026
7bcec7e
refactor(telemetry): centralize chart display properties on LPPSensor…
Avi0n Feb 25, 2026
58d88a4
feat(chat): add 1-second cooldown to send button after sending
Avi0n Feb 25, 2026
1143ce6
refactor(chat): remove MentionInputBar wrapper and deduplicate send b…
Avi0n Feb 25, 2026
1beb5ce
fix(settings): update radio preview when advanced settings changed in…
Avi0n Feb 25, 2026
95c9501
refactor: reduce complexity in BLEStateMachine
Avi0n Feb 25, 2026
4752704
refactor: reduce complexity in SyncCoordinator and ConnectionManager
Avi0n Feb 25, 2026
dde5a49
feat(settings): tap-to-connect in device selection sheet
Avi0n Feb 25, 2026
e96909e
refactor(ble): simplify extracted disconnect handler signatures
Avi0n Feb 25, 2026
fa0ea65
Merge branch 'refactor/ble-complexity' into dev
Avi0n Feb 25, 2026
0a3516e
refactor: reduce complexity in AppState
Avi0n Feb 25, 2026
d31571d
refactor(chat): reduce complexity in ChatViewModel
Avi0n Feb 25, 2026
54e6e03
refactor(sync): reuse drainHandlersAndResumeNotifications in performR…
Avi0n Feb 25, 2026
c2a2c77
chore: remove opaque audit reference from comment
Avi0n Feb 25, 2026
b5e5f95
Merge branch 'refactor/sync-complexity' into dev
Avi0n Feb 25, 2026
1534ca1
Merge branch 'refactor/appstate-complexity' into dev
Avi0n Feb 25, 2026
1588b27
Merge branch 'refactor/chatvm-complexity' into dev
Avi0n Feb 25, 2026
698ac76
refactor: clarify naming for sessionStateChangeCount and NoiseFloorQu…
Avi0n Feb 25, 2026
c285b6f
refactor: rename DMCacheKey, FAB components, and snrDB for consistency
Avi0n Feb 25, 2026
5d612b7
feat: add footer text to repeater settings sections
Avi0n Feb 25, 2026
45b8edf
refactor: remove dead code and deduplicate name TextField in repeater…
Avi0n Feb 25, 2026
9e56df7
style: add spacing between map and rows in contact detail location se…
Avi0n Feb 25, 2026
0837db4
fix(contacts): disable action buttons when radio disconnected
Avi0n Feb 25, 2026
0223318
fix(ui): use literal colors for BLE status icon to fix iPad orientati…
Avi0n Feb 25, 2026
848d57f
fix(views): fix state and concurrency bugs in chat and BLE views
Avi0n Feb 26, 2026
5109331
fix(services): fix channel message persistence and callback isolation
Avi0n Feb 26, 2026
2289e92
refactor(views): split multi-type files into single-type files
Avi0n Feb 26, 2026
48e0d8e
refactor(chats): extract computed view properties into View structs
Avi0n Feb 26, 2026
07c107f
refactor(settings): extract computed view properties into View structs
Avi0n Feb 26, 2026
88b098d
fix(quality): replace UIKit with ShareLink, split wireMessageHandlers…
Avi0n Feb 26, 2026
14d0ab5
fix(rxlog): display per-hop SNR for TRACE packets and resolve hop names
Avi0n Feb 26, 2026
10bad99
refactor(rxlog): display key prefix IDs in path rows
Avi0n Feb 26, 2026
4ab8347
Bump version to 0.10.0
Avi0n Feb 26, 2026
9cc1775
feat(contacts): show DiscoveryView in iPad detail pane
Avi0n Feb 26, 2026
bef0891
feat(settings): add language setting with system settings deep link
Avi0n Feb 26, 2026
9d7fb21
fix(ui): work around iPadOS 26 glass ghost on BLE status menu
Avi0n Feb 26, 2026
79916ae
Update CONTRIBUTING.md
Avi0n Feb 26, 2026
982abc2
chore(i18n): translate missing strings across 8 languages
Avi0n Feb 26, 2026
af0fc08
feat(live-activity): add Lock Screen and Dynamic Island Live Activity…
Avi0n Feb 27, 2026
eb63a68
fix(live-activity): use inline TipView and rename to "Live Activity"
Avi0n Feb 27, 2026
7e5b2a8
refactor(settings): simplify SettingsView patterns
Avi0n Feb 27, 2026
144c65c
fix(ci): scope code signing to per-target to preserve widget bundle ID
Avi0n Feb 27, 2026
80dd46c
feat(live-activity): replace last-rx time with packet rate metric
Avi0n Feb 27, 2026
763bd9f
fix(live-activity): remove fixedSize that blew out Dynamic Island width
Avi0n Feb 27, 2026
fd9bbad
fix(live-activity): decay packet rate when packets stop arriving
Avi0n Feb 27, 2026
d52f298
fix(live-activity): use 15s window with projected per-minute rate
Avi0n Feb 27, 2026
8cdb65b
fix(live-activity): use static slate blue background tint
Avi0n Feb 27, 2026
e64ccb7
fix(sync): add diagnostic logging to full sync prune and sync decisio…
Avi0n Feb 27, 2026
d4d90f5
fix(live-activity): restart after system-imposed end, throttle updates
Avi0n Feb 27, 2026
4866bbb
fix(live-activity): improve lock screen text contrast and background …
Avi0n Feb 27, 2026
897362e
fix(live-activity): move battery read after BLE foreground resume
Avi0n Mar 1, 2026
95203c5
fix(live-activity): keep battery polling alive during background
Avi0n Mar 1, 2026
cd55c73
fix(live-activity): prevent ended activity from blocking restart
Avi0n Mar 1, 2026
884095f
fix(settings): live activity toggle updates immediately
Avi0n Mar 1, 2026
124518a
fix(settings): remove closestRepeatPreset fallback and simplify Radio…
Avi0n Mar 1, 2026
b25629e
fix(contacts): remove divider between map and coordinates in contact …
Avi0n Mar 1, 2026
c472653
fix(chats): exclude rooms from new chat contact picker
Avi0n Mar 1, 2026
2d135fa
feat(meshcore): add featureDisabled error and improve importPrivateKey
Avi0n Mar 1, 2026
c540dc4
feat(settings): add device identity management
Avi0n Mar 1, 2026
c8bdd8b
fix(chats): open room info sheet full screen
Avi0n Mar 1, 2026
372ff6f
feat(live-activity): improve background lifecycle and battery refresh
Avi0n Mar 2, 2026
a21b9fc
refactor(broadcaster): cursor-based event delivery
Avi0n Mar 2, 2026
b282564
refactor(chats): move send logic into ChatInputBar
Avi0n Mar 2, 2026
2ddd79f
fix(settings): refresh public key after identity import
Avi0n Mar 2, 2026
42d28bf
fix(los): clamp map region to prevent MKMapView crash
Avi0n Mar 2, 2026
4033859
fix(live-activity): reset packet rate to 0 when widget becomes stale
Avi0n Mar 2, 2026
a1b4528
ui(live-activity): show packet rate in minimal presentation
Avi0n Mar 2, 2026
10924b0
feat(chats): sort @ mention suggestions by most recent sender
Avi0n Mar 2, 2026
dca71c1
fix(live-activity): end activity on user disconnect
Avi0n Mar 2, 2026
7bcecef
feat(repeater): add contact info section to repeater admin settings
Avi0n Mar 2, 2026
dfd10d0
fix(ui): fix error banner alignment in expandable settings sections
Avi0n Mar 2, 2026
83239c9
feat(chats): replace filter menu with inline segmented picker
Avi0n Mar 3, 2026
bd31a0a
fix(settings): change SF Symbol for Live ACtivity toggle
Avi0n Mar 3, 2026
f77e662
fix(chats): prevent partial state updates and stale reloads
Avi0n Mar 3, 2026
3c6d92a
feat(errors): add LocalizedError conformance to all service error types
Avi0n Mar 3, 2026
9675658
fix(live-activity): flush unread count updates immediately
Avi0n Mar 3, 2026
7612e46
feat(node-discovery): add "Add" button to repeater rows
Avi0n Mar 3, 2026
518b991
feat(avatars): show emoji or single initial for single-word names
Avi0n Mar 3, 2026
3da2d3f
fix(notifications): clear suppression immediately after catch-up poll
Avi0n Mar 3, 2026
715ce01
refactor(avatars): consolidate DiscoveredNodeAvatar into ContactAvatar
Avi0n Mar 3, 2026
f00b5d5
fix(ocv): match T1000-E model string with uppercase E
Avi0n Mar 3, 2026
157339e
chore(settings): remove language footer from app settings section
Avi0n Mar 3, 2026
554ef87
fix(rooms): clear stale "Connected" status on disconnect and launch
Avi0n Mar 3, 2026
ab1b062
feat(safety): add malware domain filter for chat links
Avi0n Mar 3, 2026
1818570
fix(tests): use .all filter constant in ConversationFilteringTests
Avi0n Mar 3, 2026
7d626f6
Merge origin/main into dev
Avi0n Mar 3, 2026
dc82fa1
test(chats): fix and migrate ConversationFilteringTests to Swift Testing
Avi0n Mar 4, 2026
f814f0c
test: migrate remaining XCTest files to Swift Testing
Avi0n Mar 4, 2026
7ef433d
refactor(status): move display logic from SyncingPillView to StatusPi…
Avi0n Mar 4, 2026
8ac1714
test: reduce flakiness and improve test quality
Avi0n Mar 4, 2026
8d721c4
fix(test): update CLIResponse edge case expectations for bare prompt …
Avi0n Mar 4, 2026
ed6c4ef
chore(ci): replace Xcode test job with build-only check
Avi0n Mar 4, 2026
adef44f
refactor(ui): simplify MessageActionsSheet and fix iPad detent
Avi0n Mar 4, 2026
e419c36
fix(ui): center emoji row in message actions sheet
Avi0n Mar 4, 2026
5f1dd58
refactor(ui): simplify closure forwarding in ActionsEmojiSection
Avi0n Mar 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
40 changes: 7 additions & 33 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Tests
name: Build

on:
pull_request:
Expand Down Expand Up @@ -27,8 +27,8 @@ jobs:
- name: Test PocketMeshServices
run: swift test --package-path PocketMeshServices

xcode-tests:
name: Xcode Tests
xcode-build:
name: Xcode Build
runs-on: macos-26

steps:
Expand All @@ -54,39 +54,13 @@ jobs:
deriveddata-directory: .derivedData
delete-used-deriveddata-cache: true

- name: Boot simulator
- name: Build
run: |
DEVICE_ID=$(xcrun simctl list devices available -j | python3 -c "
import json, sys
data = json.load(sys.stdin)
for runtime, devices in data['devices'].items():
for d in devices:
if d['name'] == 'iPhone 16e' and d['isAvailable']:
print(d['udid'])
sys.exit(0)
sys.exit(1)
")
xcrun simctl boot "$DEVICE_ID" || true

- name: Build for testing
run: |
xcodebuild build-for-testing \
xcodebuild build \
-scheme PocketMesh \
-destination "platform=iOS Simulator,name=iPhone 16e" \
-destination "generic/platform=iOS Simulator" \
-derivedDataPath .derivedData \
CODE_SIGN_IDENTITY="-" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=NO \
COMPILER_INDEX_STORE_ENABLE=NO \
GCC_GENERATE_DEBUGGING_SYMBOLS=NO

- name: Run tests
run: |
xcodebuild test-without-building \
-scheme PocketMesh \
-destination "platform=iOS Simulator,name=iPhone 16e" \
-derivedDataPath .derivedData \
-resultBundlePath TestResults.xcresult \
-parallel-testing-enabled NO \
-enableCodeCoverage NO \
-collect-test-diagnostics never
COMPILER_INDEX_STORE_ENABLE=NO
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ To make the contribution process smooth and respectful of everyone's time, pleas
### Before Starting Work
- **Discuss your idea first**: Reach out so we can coordinate. This helps avoid duplicate effort, as I might already be working on something similar.
- [MeshCore Discord](https://discord.gg/bSuST8xvet) — look for the PocketMesh forum post.
- Or message me on Matrix at @avion:matrix.org
- Or message me on Matrix @avion:matrix.org

### Pull Request Requirements
When submitting a PR, please:
Expand All @@ -21,7 +21,7 @@ If you're an experienced software engineer and did not rely heavily on AI for yo
If you used AI extensively (which is totally fine, I built this entire project with AI despite not being a SWE and only having basic Python scripting experience), please follow these best practices to ensure high-quality results:

1. **Choose the right model for planning**
If you're not a software engineer and aren't comfortable creating detailed technical plans yourself, stick to the strongest reasoning models: **Claude Opus 4.5** or **GPT 5.2 (high/xhigh)**. These are decent at turning non-technical ideas into solid implementation plans. Other popular models (e.g., GLM 4.7, MiniMax 2.1) perform well when given a detailed plan, but struggle to create one from scratch in a large codebase.
If you're not a software engineer and aren't comfortable creating detailed technical plans yourself, stick to the strongest reasoning models: **Claude Opus** or **GPT Codex (high/xhigh)**. These are decent at turning non-technical ideas into solid implementation plans. Other popular models (e.g., GLM, MiniMax) perform well when given a detailed plan, but struggle to create one from scratch in a large codebase.

2. **Plan thoroughly**
Ask the AI to use research agents/tools to gather context about the relevant parts of the codebase. Think of edge cases. Write the plan to an md file.
Expand All @@ -42,7 +42,7 @@ If you used AI extensively (which is totally fine, I built this entire project w
Manually verify that everything works as expected. Test edge cases.

8. **Submit the PR**
You can ask the AI to draft the PR description. Feel free to use it directly, but adding a bit of your own voice is always appreciated!
In the description, include the reason you are making the changes. You can ask the AI to draft the PR description. Feel free to use it directly, but adding a bit of your own voice is always appreciated!


## Getting Started
Expand Down
7 changes: 6 additions & 1 deletion MeshCore/Sources/MeshCore/Events/MeshEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,7 @@ public struct RadioStats: Sendable, Equatable {
/// - Bytes 12-15: UInt32 - direct_tx
/// - Bytes 16-19: UInt32 - flood_rx
/// - Bytes 20-23: UInt32 - direct_rx
/// - Bytes 24-27: UInt32 - recv_errors (optional, present when frame >= 28 bytes)
public struct PacketStats: Sendable, Equatable {
/// Total packets received.
public let received: UInt32
Expand All @@ -688,6 +689,8 @@ public struct PacketStats: Sendable, Equatable {
public let floodRx: UInt32
/// Total direct packets received.
public let directRx: UInt32
/// Total RadioLib receive errors (CRC failures, malformed packets).
public let receiveErrors: UInt32

/// Initializes a new packet statistics object.
public init(
Expand All @@ -696,14 +699,16 @@ public struct PacketStats: Sendable, Equatable {
floodTx: UInt32,
directTx: UInt32,
floodRx: UInt32,
directRx: UInt32
directRx: UInt32,
receiveErrors: UInt32 = 0
) {
self.received = received
self.sent = sent
self.floodTx = floodTx
self.directTx = directTx
self.floodRx = floodRx
self.directRx = directRx
self.receiveErrors = receiveErrors
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ try await session.updateContact(
publicKey: publicKey,
type: 0,
flags: 0,
outPathLength: -1, // Flood routing
outPathLength: 0xFF, // Flood routing (OUT_PATH_UNKNOWN)
outPath: Data(),
advertisedName: "New Contact",
lastAdvertisement: Date(),
Expand Down
44 changes: 37 additions & 7 deletions MeshCore/Sources/MeshCore/Models/Contact.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ import Foundation
///
/// ## Routing
/// The ``outPath`` and ``outPathLength`` describe the routing path to reach
/// this contact. A path length of -1 indicates flood routing (broadcast to all).
/// this contact. A path length of `0xFF` indicates flood routing (broadcast to all).
///
/// The ``outPathLength`` byte uses bit packing to encode both the hash size and hop count:
/// - Upper 2 bits (6-7): hash size mode (0=1-byte, 1=2-byte, 2=3-byte, 3=reserved)
/// - Lower 6 bits (0-5): hop count (0-63)
///
/// Use ``pathHashSize``, ``pathHopCount``, and ``pathByteLength`` to decode these fields.
///
/// ## Location
/// If the contact shares its location, ``latitude`` and ``longitude``
Expand Down Expand Up @@ -42,8 +48,11 @@ public struct MeshContact: Sendable, Identifiable, Equatable {
/// The operational flags for the contact.
public let flags: ContactFlags

/// The length of the outbound routing path, where -1 indicates flood routing.
public let outPathLength: Int8
/// The encoded outbound path length byte.
///
/// Uses bit packing: upper 2 bits = hash size mode, lower 6 bits = hop count.
/// A value of `0xFF` indicates flood routing (unknown path).
public let outPathLength: UInt8

/// The outbound routing path data.
public let outPath: Data
Expand Down Expand Up @@ -73,9 +82,30 @@ public struct MeshContact: Sendable, Identifiable, Equatable {
/// Indicates whether this contact uses flood (broadcast) routing.
///
/// Flood routing sends messages to all nodes in the network. This is used when
/// no direct path is known.
/// no direct path is known. Represented by `0xFF` on the wire.
public var isFloodPath: Bool {
outPathLength == -1
outPathLength == 0xFF
}

/// The hash size per hop in bytes (1, 2, or 3), derived from the upper 2 bits of ``outPathLength``.
///
/// Only meaningful when ``isFloodPath`` is `false`.
public var pathHashSize: Int {
decodePathLen(outPathLength)?.hashSize ?? 1
}

/// The number of hops in the path, derived from the lower 6 bits of ``outPathLength``.
///
/// Only meaningful when ``isFloodPath`` is `false`.
public var pathHopCount: Int {
decodePathLen(outPathLength)?.hopCount ?? 0
}

/// The total byte length of the path data (`pathHopCount * pathHashSize`).
///
/// Only meaningful when ``isFloodPath`` is `false`.
public var pathByteLength: Int {
decodePathLen(outPathLength)?.byteLength ?? 0
}

/// Initializes a new mesh contact with the specified properties.
Expand All @@ -85,7 +115,7 @@ public struct MeshContact: Sendable, Identifiable, Equatable {
/// - publicKey: The 32-byte public key data.
/// - type: Contact type identifier (chat, repeater, room).
/// - flags: Operational flags (favorite, telemetry permissions).
/// - outPathLength: Length of the outbound path.
/// - outPathLength: Encoded outbound path length byte.
/// - outPath: Outbound path data.
/// - advertisedName: Name advertised by the node.
/// - lastAdvertisement: Date of last advertisement.
Expand All @@ -97,7 +127,7 @@ public struct MeshContact: Sendable, Identifiable, Equatable {
publicKey: Data,
type: ContactType,
flags: ContactFlags,
outPathLength: Int8,
outPathLength: UInt8,
outPath: Data,
advertisedName: String,
lastAdvertisement: Date,
Expand Down
9 changes: 8 additions & 1 deletion MeshCore/Sources/MeshCore/Models/DeviceInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ public struct DeviceCapabilities: Sendable, Equatable {
public let version: String
/// Whether client repeat mode is enabled (v9+ firmware).
public let clientRepeat: Bool
/// The path hash mode (0=1-byte, 1=2-byte, 2=3-byte hashes). Firmware v10+.
public let pathHashMode: UInt8

/// The hash size per hop in bytes (1, 2, or 3), derived from ``pathHashMode``.
public var hashSize: Int { Int(pathHashMode) + 1 }

/// Initializes a new device capabilities structure.
public init(
Expand All @@ -107,7 +112,8 @@ public struct DeviceCapabilities: Sendable, Equatable {
firmwareBuild: String,
model: String,
version: String,
clientRepeat: Bool = false
clientRepeat: Bool = false,
pathHashMode: UInt8 = 0
) {
self.firmwareVersion = firmwareVersion
self.maxContacts = maxContacts
Expand All @@ -117,6 +123,7 @@ public struct DeviceCapabilities: Sendable, Equatable {
self.model = model
self.version = version
self.clientRepeat = clientRepeat
self.pathHashMode = pathHashMode
}
}

Expand Down
17 changes: 15 additions & 2 deletions MeshCore/Sources/MeshCore/Protocol/PacketBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ public enum PacketBuilder: Sendable {
/// - Offset 1 (32 bytes): Public key
/// - Offset 33 (1 byte): Type
/// - Offset 34 (1 byte): Flags
/// - Offset 35 (1 byte): Out path length (signed Int8 as UInt8)
/// - Offset 35 (1 byte): Out path length (encoded: upper 2 bits = hash mode, lower 6 bits = hop count; 0xFF = flood)
/// - Offset 36 (64 bytes): Out path (zero-padded)
/// - Offset 100 (32 bytes): Advertised name (UTF-8, zero-padded)
/// - Offset 132 (4 bytes): Last advert timestamp (UInt32 LE)
Expand All @@ -557,7 +557,7 @@ public enum PacketBuilder: Sendable {
data.append(contact.publicKey.paddedOrTruncated(to: 32)) // 32 bytes
data.append(contact.type.rawValue) // 1 byte
data.append(contact.flags.rawValue) // 1 byte
data.append(UInt8(bitPattern: contact.outPathLength)) // 1 byte
data.append(contact.outPathLength) // 1 byte
data.append(contact.outPath.paddedOrTruncated(to: 64)) // 64 bytes
data.append(contact.advertisedName.utf8PaddedOrTruncated(to: 32)) // 32 bytes

Expand Down Expand Up @@ -780,6 +780,19 @@ public enum PacketBuilder: Sendable {
return data
}

/// Builds a setPathHashMode command to configure the path hash size.
///
/// - Parameter mode: Hash mode (0=1-byte, 1=2-byte, 2=3-byte hashes).
/// - Returns: The command packet data.
///
/// ### Binary Format
/// - Offset 0 (1 byte): Command code `0x3D`
/// - Offset 1 (1 byte): Reserved `0x00`
/// - Offset 2 (1 byte): Mode value (0, 1, or 2)
public static func setPathHashMode(_ mode: UInt8) -> Data {
Data([CommandCode.setPathHashMode.rawValue, 0x00, mode])
}

/// Builds a factoryReset command to wipe all settings and data from the device.
///
/// - Returns: The command packet data.
Expand Down
2 changes: 2 additions & 0 deletions MeshCore/Sources/MeshCore/Protocol/PacketCodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ public enum CommandCode: UInt8, Sendable {
case getAutoAddConfig = 0x3B
/// Gets the allowed frequency ranges for client repeat mode (v9+).
case getRepeatFreq = 0x3C
/// Sets the path hash mode (0=1-byte, 1=2-byte, 2=3-byte hashes).
case setPathHashMode = 0x3D
}

/// Defines the response codes received from the mesh device.
Expand Down
Loading