diff --git a/.gitignore b/.gitignore
index 72cb90e00..03e4e14da 100755
--- a/.gitignore
+++ b/.gitignore
@@ -18,7 +18,6 @@ profile
DerivedData
*.hmap
*.ipa
-*xcshareddata*
# Bundler
.bundle
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea1dd97af..5d6e94047 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,16 @@
## Changelog
+- **2.2.2**:
+ - Fixed handling IV Index update.
+ - Added a flag to enable IV Index Test Mode.
+ - New method to get next available address from given offset.
+ - 2 methods added to get and set Element's sequence number. Use on your own risk.
+ - A Disconnect button added in the sample app, available with Automatic Connection disabled.
+ - Improvement: Migration to Swift 5.2 and Xcode 11.4.
+ - Improvement: macOs added to podspec (#240).
+ - Improvement: Discovering new proxy connection improved.
+ - Bugfix: Fixed overwriting primary element's name.
+ - Bugfix: Thread synchronization in `ProxyFilter` and `BaseGattBearer`.
+
- **2.2.1**:
- API related to "compatible models" removed, as the client and server models do not need to have model IDs differing by 1 (#225).
diff --git a/Example/Pods/Pods.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/Pods/Pods.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 000000000..18d981003
--- /dev/null
+++ b/Example/Pods/Pods.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/nRFMeshProvision.xcscheme b/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/nRFMeshProvision.xcscheme
new file mode 100644
index 000000000..327b6ec9f
--- /dev/null
+++ b/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/nRFMeshProvision.xcscheme
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Example/Tests/AssigningGroupAddresses.swift b/Example/Tests/AssigningGroupAddresses.swift
index a375c6158..ca4c4686b 100644
--- a/Example/Tests/AssigningGroupAddresses.swift
+++ b/Example/Tests/AssigningGroupAddresses.swift
@@ -100,5 +100,37 @@ class AssigningGroupAddresses: XCTestCase {
XCTAssertNil(address)
}
+
+ func testAssigningGroupAddressRanges_boundaries() {
+ let meshNetwork = MeshNetwork(name: "Test network")
+ let provisioner = Provisioner(name: "P0",
+ allocatedUnicastRange: [AddressRange(0x0001...0x0001)],
+ allocatedGroupRange: [AddressRange(0xC000...0xC000)],
+ allocatedSceneRange: [])
+ XCTAssertNoThrow(try meshNetwork.add(provisioner: provisioner, withAddress: nil))
+
+ let newRangeNil = meshNetwork.nextAvailableGroupAddressRange(ofSize: 0)
+ XCTAssertNil(newRangeNil)
+
+ let newRange0 = meshNetwork.nextAvailableGroupAddressRange(ofSize: 1)
+ XCTAssertNotNil(newRange0)
+ XCTAssertEqual(newRange0?.lowerBound, 0xC001)
+ XCTAssertEqual(newRange0?.upperBound, 0xC001)
+
+ let newRange1 = meshNetwork.nextAvailableGroupAddressRange(ofSize: 2)
+ XCTAssertNotNil(newRange1)
+ XCTAssertEqual(newRange1?.lowerBound, 0xC001)
+ XCTAssertEqual(newRange1?.upperBound, 0xC002)
+
+ let newRange2 = meshNetwork.nextAvailableGroupAddressRange()
+ XCTAssertNotNil(newRange2)
+ XCTAssertEqual(newRange2?.lowerBound, 0xC001)
+ XCTAssertEqual(newRange2?.upperBound, Address.maxGroupAddress)
+
+ let newRange3 = meshNetwork.nextAvailableGroupAddressRange(ofSize: 0xFFFF)
+ XCTAssertNotNil(newRange3)
+ XCTAssertEqual(newRange3?.lowerBound, 0xC001)
+ XCTAssertEqual(newRange3?.upperBound, Address.maxGroupAddress)
+ }
}
diff --git a/Example/Tests/AssigningUnicastAddress.swift b/Example/Tests/AssigningUnicastAddress.swift
index aaa2aa936..c2a9d7c53 100644
--- a/Example/Tests/AssigningUnicastAddress.swift
+++ b/Example/Tests/AssigningUnicastAddress.swift
@@ -73,6 +73,31 @@ class AssigningUnicastAddress: XCTestCase {
XCTAssertEqual(address!, 100)
}
+ func testAssigningUnicastAddress_offset() {
+ let meshNetwork = MeshNetwork(name: "Test network")
+ XCTAssertNoThrow(try meshNetwork.add(node: Node(name: "Node 0", unicastAddress: 1, elements: 9)))
+ XCTAssertNoThrow(try meshNetwork.add(node: Node(name: "Node 1", unicastAddress: 10, elements: 9)))
+ XCTAssertNoThrow(try meshNetwork.add(node: Node(name: "Node 2", unicastAddress: 20, elements: 9)))
+ XCTAssertNoThrow(try meshNetwork.add(node: Node(name: "Node 3", unicastAddress: 30, elements: 9)))
+ XCTAssertNoThrow(try meshNetwork.add(node: Node(name: "Node 4", unicastAddress: 115, elements: 2)))
+
+ let provisioner = Provisioner(name: "Test provisioner",
+ allocatedUnicastRange: [
+ AddressRange(100...200)
+ ],
+ allocatedGroupRange: [], allocatedSceneRange: [])
+
+ let address1 = meshNetwork.nextAvailableUnicastAddress(startingFrom: 110, for: 3, elementsUsing: provisioner)
+
+ XCTAssertNotNil(address1)
+ XCTAssertEqual(address1!, 110)
+
+ let address2 = meshNetwork.nextAvailableUnicastAddress(startingFrom: 110, for: 6, elementsUsing: provisioner)
+
+ XCTAssertNotNil(address2)
+ XCTAssertEqual(address2!, 117)
+ }
+
func testAssigningUnicastAddress_complex() {
let meshNetwork = MeshNetwork(name: "Test network")
XCTAssertNoThrow(try meshNetwork.add(node: Node(name: "Node 0", unicastAddress: 1, elements: 9)))
@@ -116,6 +141,28 @@ class AssigningUnicastAddress: XCTestCase {
XCTAssertEqual(address!, 78)
}
+ func testAssigningUnicastAddress_one() {
+ let meshNetwork = MeshNetwork(name: "Test network")
+ XCTAssertNoThrow(try meshNetwork.add(node: Node(name: "Node 0", unicastAddress: 1, elements: 10)))
+ XCTAssertNoThrow(try meshNetwork.add(node: Node(name: "Node 1", unicastAddress: 12, elements: 18)))
+ XCTAssertNoThrow(try meshNetwork.add(node: Node(name: "Node 2", unicastAddress: 30, elements: 11)))
+ XCTAssertNoThrow(try meshNetwork.add(node: Node(name: "Node 3", unicastAddress: 55, elements: 10)))
+ XCTAssertNoThrow(try meshNetwork.add(node: Node(name: "Node 4", unicastAddress: 65, elements: 5)))
+ XCTAssertNoThrow(try meshNetwork.add(node: Node(name: "Node 5", unicastAddress: 73, elements: 5)))
+
+ let provisioner = Provisioner(name: "Test provisioner",
+ allocatedUnicastRange: [
+ AddressRange(8...38),
+ AddressRange(50...80)
+ ],
+ allocatedGroupRange: [], allocatedSceneRange: [])
+
+ let address = meshNetwork.nextAvailableUnicastAddress(for: 1, elementsUsing: provisioner)
+
+ XCTAssertNotNil(address)
+ XCTAssertEqual(address!, 11)
+ }
+
func testAssigningUnicastAddress_none() {
let meshNetwork = MeshNetwork(name: "Test network")
XCTAssertNoThrow(try meshNetwork.add(node: Node(name: "Node 0", unicastAddress: 1, elements: 10)))
@@ -137,7 +184,7 @@ class AssigningUnicastAddress: XCTestCase {
XCTAssertNil(address)
}
- func testAssigningUnicastAdderessRanges() {
+ func testAssigningUnicastAddressRanges() {
let meshNetwork = MeshNetwork(name: "Test network")
XCTAssertNoThrow(try meshNetwork.add(provisioner:
Provisioner(name: "P0",
@@ -174,7 +221,7 @@ class AssigningUnicastAddress: XCTestCase {
XCTAssertEqual(newRangeMax?.upperBound, 0x5FFF)
}
- func testAssigningUnicastAdderessRanges_none() {
+ func testAssigningUnicastAddressRanges_none() {
let meshNetwork = MeshNetwork(name: "Test network")
XCTAssertNoThrow(try meshNetwork.add(provisioner:
Provisioner(name: "P0",
@@ -185,4 +232,36 @@ class AssigningUnicastAddress: XCTestCase {
let newRange0 = meshNetwork.nextAvailableUnicastAddressRange(ofSize: 1)
XCTAssertNil(newRange0)
}
+
+ func testAssigningUnicastAddressRanges_boundaries() {
+ let meshNetwork = MeshNetwork(name: "Test network")
+ let provisioner = Provisioner(name: "P0",
+ allocatedUnicastRange: [AddressRange(0x0001...0x0001)],
+ allocatedGroupRange: [],
+ allocatedSceneRange: [])
+ XCTAssertNoThrow(try meshNetwork.add(provisioner: provisioner, withAddress: nil))
+
+ let newRangeNil = meshNetwork.nextAvailableUnicastAddressRange(ofSize: 0)
+ XCTAssertNil(newRangeNil)
+
+ let newRange0 = meshNetwork.nextAvailableUnicastAddressRange(ofSize: 1)
+ XCTAssertNotNil(newRange0)
+ XCTAssertEqual(newRange0?.lowerBound, 0x0002)
+ XCTAssertEqual(newRange0?.upperBound, 0x0002)
+
+ let newRange1 = meshNetwork.nextAvailableUnicastAddressRange(ofSize: 2)
+ XCTAssertNotNil(newRange1)
+ XCTAssertEqual(newRange1?.lowerBound, 0x0002)
+ XCTAssertEqual(newRange1?.upperBound, 0x0003)
+
+ let newRange2 = meshNetwork.nextAvailableUnicastAddressRange()
+ XCTAssertNotNil(newRange2)
+ XCTAssertEqual(newRange2?.lowerBound, 0x0002)
+ XCTAssertEqual(newRange2?.upperBound, Address.maxUnicastAddress)
+
+ let newRange3 = meshNetwork.nextAvailableUnicastAddressRange(ofSize: 0xFFFF)
+ XCTAssertNotNil(newRange3)
+ XCTAssertEqual(newRange3?.lowerBound, 0x0002)
+ XCTAssertEqual(newRange3?.upperBound, Address.maxUnicastAddress)
+ }
}
diff --git a/Example/Tests/NetworkPdus.swift b/Example/Tests/NetworkPdus.swift
index 1b644663b..30591f3ca 100644
--- a/Example/Tests/NetworkPdus.swift
+++ b/Example/Tests/NetworkPdus.swift
@@ -42,12 +42,13 @@ class NetworkPdus: XCTestCase {
}
func testDecodingAccessMessage() {
- let networkKey = try! NetworkKey(name: "Test Key", index: 0, key: Data(hex: "7dd7364cd842ad18c17c2b820c84c3d6")!)
- networkKey.ivIndex = IvIndex(index: 0x12345678, updateActive: false)
-
+ let networkKey = try! NetworkKey(name: "Test Key", index: 0,
+ key: Data(hex: "7dd7364cd842ad18c17c2b820c84c3d6")!)
+ let ivIndex = IvIndex(index: 0x12345678, updateActive: false)
let data = Data(hex: "68cab5c5348a230afba8c63d4e686364979deaf4fd40961145939cda0e")!
- let networkPdu = NetworkPdu(decode: data, ofType: .networkPdu, usingNetworkKey: networkKey)
+ let networkPdu = NetworkPdu(decode: data, ofType: .networkPdu,
+ usingNetworkKey: networkKey, andIvIndex: ivIndex)
XCTAssertNotNil(networkPdu)
XCTAssertEqual(networkPdu!.ivi, 0x0)
XCTAssertEqual(networkPdu!.nid, 0x68)
@@ -60,12 +61,13 @@ class NetworkPdus: XCTestCase {
}
func testDecodingControlMessage() {
- let networkKey = try! NetworkKey(name: "Test Key", index: 0, key: Data(hex: "7dd7364cd842ad18c17c2b820c84c3d6")!)
- networkKey.ivIndex = IvIndex(index: 0x12345678, updateActive: false)
-
+ let networkKey = try! NetworkKey(name: "Test Key", index: 0,
+ key: Data(hex: "7dd7364cd842ad18c17c2b820c84c3d6")!)
+ let ivIndex = IvIndex(index: 0x12345678, updateActive: false)
let data = Data(hex: "68eca487516765b5e5bfdacbaf6cb7fb6bff871f035444ce83a670df")!
- let networkPdu = NetworkPdu(decode: data, ofType: .networkPdu, usingNetworkKey: networkKey)
+ let networkPdu = NetworkPdu(decode: data, ofType: .networkPdu,
+ usingNetworkKey: networkKey, andIvIndex: ivIndex)
XCTAssertNotNil(networkPdu)
XCTAssertEqual(networkPdu!.ivi, 0x0)
XCTAssertEqual(networkPdu!.nid, 0x68)
@@ -78,13 +80,14 @@ class NetworkPdus: XCTestCase {
}
func testDecodingControlMessageUsingOldKey() {
- let networkKey = try! NetworkKey(name: "Test Key", index: 0, key: Data(hex: "7dd7364cd842ad18c17c2b820c84c3d6")!)
+ let networkKey = try! NetworkKey(name: "Test Key", index: 0,
+ key: Data(hex: "7dd7364cd842ad18c17c2b820c84c3d6")!)
networkKey.key = Data(hex: "7d01D01D01D01D01D01D01D01D01D01D")!
- networkKey.ivIndex = IvIndex(index: 0x12345678, updateActive: false)
-
+ let ivIndex = IvIndex(index: 0x12345678, updateActive: false)
let data = Data(hex: "68eca487516765b5e5bfdacbaf6cb7fb6bff871f035444ce83a670df")!
- let networkPdu = NetworkPdu(decode: data, ofType: .networkPdu, usingNetworkKey: networkKey)
+ let networkPdu = NetworkPdu(decode: data, ofType: .networkPdu,
+ usingNetworkKey: networkKey, andIvIndex: ivIndex)
XCTAssertNotNil(networkPdu)
XCTAssertEqual(networkPdu!.ivi, 0x0)
XCTAssertEqual(networkPdu!.nid, 0x68)
@@ -96,13 +99,31 @@ class NetworkPdus: XCTestCase {
XCTAssertEqual(networkPdu!.transportPdu, Data(hex: "034b50057e400000010000")!)
}
- func testDecodingControlMessageWithNextIvIndex() {
- let networkKey = try! NetworkKey(name: "Test Key", index: 0, key: Data(hex: "7dd7364cd842ad18c17c2b820c84c3d6")!)
- networkKey.ivIndex = IvIndex(index: 0x12345679, updateActive: true)
+ func testDecodingControlMessageWithIVUpdateActive() {
+ let networkKey = try! NetworkKey(name: "Test Key", index: 0,
+ key: Data(hex: "7dd7364cd842ad18c17c2b820c84c3d6")!)
+ let ivIndex = IvIndex(index: 0x12345679, updateActive: true)
+ let data = Data(hex: "68eca487516765b5e5bfdacbaf6cb7fb6bff871f035444ce83a670df")!
+ let networkPdu = NetworkPdu(decode: data, ofType: .networkPdu, usingNetworkKey: networkKey, andIvIndex: ivIndex)
+ XCTAssertNotNil(networkPdu)
+ XCTAssertEqual(networkPdu!.ivi, 0x0)
+ XCTAssertEqual(networkPdu!.nid, 0x68)
+ XCTAssertEqual(networkPdu!.type, .controlMessage)
+ XCTAssertEqual(networkPdu!.sequence, 1)
+ XCTAssertEqual(networkPdu!.source, 0x1201)
+ XCTAssertEqual(networkPdu!.destination, 0xFFFD)
+ XCTAssertEqual(networkPdu!.transportPdu, Data(hex: "034b50057e400000010000")!)
+ }
+
+ func testDecodingControlMessageWithNextIvIndex() {
+ let networkKey = try! NetworkKey(name: "Test Key", index: 0,
+ key: Data(hex: "7dd7364cd842ad18c17c2b820c84c3d6")!)
+ let ivIndex = IvIndex(index: 0x12345679, updateActive: false)
let data = Data(hex: "68eca487516765b5e5bfdacbaf6cb7fb6bff871f035444ce83a670df")!
- let networkPdu = NetworkPdu(decode: data, ofType: .networkPdu, usingNetworkKey: networkKey)
+ let networkPdu = NetworkPdu(decode: data, ofType: .networkPdu,
+ usingNetworkKey: networkKey, andIvIndex: ivIndex)
XCTAssertNotNil(networkPdu)
XCTAssertEqual(networkPdu!.ivi, 0x0)
XCTAssertEqual(networkPdu!.nid, 0x68)
@@ -113,43 +134,58 @@ class NetworkPdus: XCTestCase {
XCTAssertEqual(networkPdu!.transportPdu, Data(hex: "034b50057e400000010000")!)
}
- func testDecodingControlMessageWithWrongIvIndex() {
- let networkKey = try! NetworkKey(name: "Test Key", index: 0, key: Data(hex: "7dd7364cd842ad18c17c2b820c84c3d6")!)
- networkKey.ivIndex = IvIndex(index: 0x12345679, updateActive: false)
+ func testDecodingControlMessageWithWrongIvIndex_TooLarge() {
+ let networkKey = try! NetworkKey(name: "Test Key", index: 0,
+ key: Data(hex: "7dd7364cd842ad18c17c2b820c84c3d6")!)
+ let ivIndex = IvIndex(index: 0x12345680, updateActive: false)
+ let data = Data(hex: "68eca487516765b5e5bfdacbaf6cb7fb6bff871f035444ce83a670df")!
+ let networkPdu = NetworkPdu(decode: data, ofType: .networkPdu,
+ usingNetworkKey: networkKey, andIvIndex: ivIndex)
+ XCTAssertNil(networkPdu)
+ }
+
+ func testDecodingControlMessageWithWrongIvIndex_TooSmall() {
+ let networkKey = try! NetworkKey(name: "Test Key", index: 0,
+ key: Data(hex: "7dd7364cd842ad18c17c2b820c84c3d6")!)
+ let ivIndex = IvIndex(index: 0x12345677, updateActive: false)
let data = Data(hex: "68eca487516765b5e5bfdacbaf6cb7fb6bff871f035444ce83a670df")!
- let networkPdu = NetworkPdu(decode: data, ofType: .networkPdu, usingNetworkKey: networkKey)
+ let networkPdu = NetworkPdu(decode: data, ofType: .networkPdu,
+ usingNetworkKey: networkKey, andIvIndex: ivIndex)
XCTAssertNil(networkPdu)
}
func testDecodingControlMessageWithWrongKey() {
- let networkKey = try! NetworkKey(name: "Other Key", index: 0, key: Data(hex: "8dd7364cd842ad18c17c2b820c84c3d6")!)
- networkKey.ivIndex = IvIndex(index: 0x12345678, updateActive: false)
-
+ let networkKey = try! NetworkKey(name: "Other Key", index: 0,
+ key: Data(hex: "8dd7364cd842ad18c17c2b820c84c3d6")!)
+ let ivIndex = IvIndex(index: 0x12345678, updateActive: false)
let data = Data(hex: "68eca487516765b5e5bfdacbaf6cb7fb6bff871f035444ce83a670df")!
- let networkPdu = NetworkPdu(decode: data, ofType: .networkPdu, usingNetworkKey: networkKey)
+ let networkPdu = NetworkPdu(decode: data, ofType: .networkPdu,
+ usingNetworkKey: networkKey, andIvIndex: ivIndex)
XCTAssertNil(networkPdu)
}
func testDecodingControlMessageWithWrongKey2() {
- let networkKey = try! NetworkKey(name: "Test Key", index: 0, key: Data(hex: "7dd7364cd842ad18c17c2b820c84c3d6")!)
- networkKey.ivIndex = IvIndex(index: 0x12345678, updateActive: false)
-
+ let networkKey = try! NetworkKey(name: "Test Key", index: 0,
+ key: Data(hex: "7dd7364cd842ad18c17c2b820c84c3d6")!)
+ let ivIndex = IvIndex(index: 0x12345678, updateActive: false)
let otherData = Data(hex: "68eca487516765b5e5bfdacbaf6cb7fb7bff871f035444ce83a670df")!
- let networkPdu = NetworkPdu(decode: otherData, ofType: .networkPdu, usingNetworkKey: networkKey)
+ let networkPdu = NetworkPdu(decode: otherData, ofType: .networkPdu,
+ usingNetworkKey: networkKey, andIvIndex: ivIndex)
XCTAssertNil(networkPdu)
}
func testDecodingControlMessageWithWrongNid() {
- let networkKey = try! NetworkKey(name: "Test Key", index: 0, key: Data(hex: "7dd7364cd842ad18c17c2b820c84c3d6")!)
- networkKey.ivIndex = IvIndex(index: 0x12345678, updateActive: false)
-
+ let networkKey = try! NetworkKey(name: "Test Key", index: 0,
+ key: Data(hex: "7dd7364cd842ad18c17c2b820c84c3d6")!)
+ let ivIndex = IvIndex(index: 0x12345678, updateActive: false)
let data = Data(hex: "69eca487516765b5e5bfdacbaf6cb7fb6bff871f035444ce83a670df")!
- let networkPdu = NetworkPdu(decode: data, ofType: .networkPdu, usingNetworkKey: networkKey)
+ let networkPdu = NetworkPdu(decode: data, ofType: .networkPdu,
+ usingNetworkKey: networkKey, andIvIndex: ivIndex)
XCTAssertNil(networkPdu)
}
diff --git a/Example/Tests/Pdus.swift b/Example/Tests/Pdus.swift
index 56d7af8af..d259d3715 100644
--- a/Example/Tests/Pdus.swift
+++ b/Example/Tests/Pdus.swift
@@ -299,7 +299,7 @@ class Pdus: XCTestCase {
XCTAssertNotNil(manager.meshNetwork)
let meshNetwork = manager.meshNetwork!
let networkKey = meshNetwork.networkKeys.first!
- networkKey.ivIndex = IvIndex(index: 0x12345678, updateActive: false)
+ let ivIndex = IvIndex(index: 0x12345678, updateActive: false)
let source = meshNetwork.localProvisioner?.node?.elements.first
XCTAssertNotNil(source)
let node = meshNetwork.nodes[1]
@@ -323,8 +323,8 @@ class Pdus: XCTestCase {
XCTAssertEqual(accessPdu.opCode, ConfigAppKeyAdd.opCode)
XCTAssertEqual(accessPdu.accessPdu, Data(hex: "0023614563964771734FBD76E3B40519D1D94A48"))
- let pdu = UpperTransportPdu(fromAccessPdu: accessPdu,
- usingKeySet: keySet, sequence: sequence)
+ let pdu = UpperTransportPdu(fromAccessPdu: accessPdu, usingKeySet: keySet,
+ sequence: sequence, andIvIndex: ivIndex)
XCTAssertEqual(pdu.source, source?.unicastAddress)
XCTAssertEqual(pdu.destination, destination.address)
XCTAssertEqual(pdu.sequence, sequence)
@@ -381,7 +381,7 @@ class Pdus: XCTestCase {
XCTAssertNotNil(manager.meshNetwork)
let meshNetwork = manager.meshNetwork!
let networkKey = meshNetwork.networkKeys.first!
- networkKey.ivIndex = IvIndex(index: 0x12345678, updateActive: false)
+ let ivIndex = IvIndex(index: 0x12345678, updateActive: false)
let node = meshNetwork.nodes[1]
let source = node.elements.first
let destination = MeshAddress(meshNetwork.localProvisioner!.unicastAddress!)
@@ -406,7 +406,7 @@ class Pdus: XCTestCase {
XCTAssertEqual(accessPdu.accessPdu, Data(hex: "800300236145"))
let pdu = UpperTransportPdu(fromAccessPdu: accessPdu, usingKeySet: keySet,
- sequence: sequence)
+ sequence: sequence, andIvIndex: ivIndex)
XCTAssertEqual(pdu.source, source?.unicastAddress)
XCTAssertEqual(pdu.destination, destination.address)
XCTAssertEqual(pdu.sequence, sequence)
@@ -442,7 +442,7 @@ class Pdus: XCTestCase {
XCTAssertNotNil(manager.meshNetwork)
let meshNetwork = manager.meshNetwork!
let networkKey = meshNetwork.networkKeys.first!
- networkKey.ivIndex = IvIndex(index: 0x12345678, updateActive: false)
+ let ivIndex = IvIndex(index: 0x12345678, updateActive: false)
let source = meshNetwork.localProvisioner?.unicastAddress
let node = meshNetwork.nodes[1]
let destination = node.unicastAddress
@@ -450,7 +450,8 @@ class Pdus: XCTestCase {
// Test begins here
let networkPdu0 = NetworkPdu(decode: Data(hex: "68CAB5C5348A230AFBA8C63D4E681631C09DEAF4FD409611459A3D6C3E")!,
- ofType: .networkPdu, usingNetworkKey: networkKey)
+ ofType: .networkPdu,
+ usingNetworkKey: networkKey, andIvIndex: ivIndex)
XCTAssertNotNil(networkPdu0)
XCTAssertEqual(networkPdu0?.source, source)
XCTAssertEqual(networkPdu0?.destination, destination)
@@ -462,7 +463,8 @@ class Pdus: XCTestCase {
XCTAssertEqual(networkPdu0?.pdu, Data(hex: "68CAB5C5348A230AFBA8C63D4E681631C09DEAF4FD409611459A3D6C3E"))
let networkPdu1 = NetworkPdu(decode: Data(hex: "681615B5DD4A846CAE0C032BF0746F44F1B8CC8CE502AEF9D2393E5B93")!,
- ofType: .networkPdu, usingNetworkKey: networkKey)
+ ofType: .networkPdu,
+ usingNetworkKey: networkKey, andIvIndex: ivIndex)
XCTAssertNotNil(networkPdu1)
XCTAssertEqual(networkPdu1?.source, source)
XCTAssertEqual(networkPdu1?.destination, destination)
@@ -537,7 +539,7 @@ class Pdus: XCTestCase {
XCTAssertNotNil(manager.meshNetwork)
let meshNetwork = manager.meshNetwork!
let networkKey = meshNetwork.networkKeys.first!
- networkKey.ivIndex = IvIndex(index: 0x12345677, updateActive: false)
+ let ivIndex = IvIndex(index: 0x12345677, updateActive: false)
let node = meshNetwork.nodes[2] // Low Power Node
let applicationKey = meshNetwork.applicationKeys[1]
let source = node.elements.first
@@ -568,7 +570,7 @@ class Pdus: XCTestCase {
XCTAssertEqual(accessPdu.accessPdu, Data(hex: "D50A0048656C6C6F"))
let pdu = UpperTransportPdu(fromAccessPdu: accessPdu, usingKeySet: keySet,
- sequence: sequence)
+ sequence: sequence, andIvIndex: ivIndex)
XCTAssertEqual(pdu.source, source?.unicastAddress)
XCTAssertEqual(pdu.destination, virtualGroup!.address.address)
XCTAssertEqual(pdu.sequence, sequence)
diff --git a/Example/Tests/SecureNetworkBeacons.swift b/Example/Tests/SecureNetworkBeacons.swift
new file mode 100644
index 000000000..887883543
--- /dev/null
+++ b/Example/Tests/SecureNetworkBeacons.swift
@@ -0,0 +1,283 @@
+/*
+* Copyright (c) 2019, Nordic Semiconductor
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without modification,
+* are permitted provided that the following conditions are met:
+*
+* 1. Redistributions of source code must retain the above copyright notice, this
+* list of conditions and the following disclaimer.
+*
+* 2. Redistributions in binary form must reproduce the above copyright notice, this
+* list of conditions and the following disclaimer in the documentation and/or
+* other materials provided with the distribution.
+*
+* 3. Neither the name of the copyright holder nor the names of its contributors may
+* be used to endorse or promote products derived from this software without
+* specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+* POSSIBILITY OF SUCH DAMAGE.
+*/
+
+import XCTest
+@testable import nRFMeshProvision
+
+class SecureNetworkBeacons: XCTestCase {
+ private let networkKey =
+ try! NetworkKey(name: "Primary Network Key", index: 0,
+ key: Data(hex: "8D65C0771C83FAC39E256F697EA3AAE1")!)
+
+ func testDecodingSecureNordicBeacon() throws {
+ let data = Data(hex: "0102EE6C0EFF5298ECFF000000025E5AA7B268B5E044")!
+ let snb = SecureNetworkBeacon(decode: data, usingNetworkKey: networkKey)
+
+ XCTAssertNotNil(snb)
+ XCTAssertEqual(snb?.beaconType, BeaconType.secureNetwork)
+ XCTAssertEqual(snb?.networkId, Data(hex: "EE6C0EFF5298ECFF"))
+ XCTAssertEqual(snb?.networkKey, networkKey)
+ XCTAssertEqual(snb?.ivIndex.index, 2)
+ XCTAssertEqual(snb?.ivIndex.updateActive, true)
+ XCTAssertEqual(snb?.ivIndex.transmitIndex, 1)
+ XCTAssertEqual(snb?.ivIndex.index(for: 0x01), 1)
+ XCTAssertEqual(snb?.ivIndex.index(for: 0x00), 2)
+ XCTAssertEqual(snb?.keyRefreshFlag, false)
+ }
+
+ func testOverwritingWithTheSameIvIndex() {
+ let data = Data(hex: "0102EE6C0EFF5298ECFF000000025E5AA7B268B5E044")!
+ let snb = SecureNetworkBeacon(decode: data, usingNetworkKey: networkKey)
+ let ivIndex = IvIndex(index: 2, updateActive: true)
+
+ let oneHourAgo = Date(hoursAgo: 1)
+ let result = snb?.canOverwrite(ivIndex: ivIndex, updatedAt: oneHourAgo,
+ withIvRecovery: false, testMode: false,
+ andUnlimitedIvRecoveryAllowed: false)
+
+ XCTAssert(result == true)
+ }
+
+ func testOverwritingWithNextIvIndex() {
+ let data = Data(hex: "0102EE6C0EFF5298ECFF000000025E5AA7B268B5E044")!
+ let snb = SecureNetworkBeacon(decode: data, usingNetworkKey: networkKey)
+ let ivIndex = IvIndex(index: 1, updateActive: false)
+
+ let ninetySixHoursAgo = Date(hoursAgo: 96)
+ let almostNinetySixHoursAgo = Date(timeInterval: +10.0, since: ninetySixHoursAgo)
+ let moreThanNinetySixHoursAgo = Date(timeInterval: -10.0, since: ninetySixHoursAgo)
+
+ // Less than 96 hours - test should fail
+ let result0 = snb?.canOverwrite(ivIndex: ivIndex, updatedAt: almostNinetySixHoursAgo,
+ withIvRecovery: false, testMode: false,
+ andUnlimitedIvRecoveryAllowed: false)
+
+ // When previous IV Index was updated using IV Recovery, 96h requiremend
+ // does not apply. Test should pass.
+ let result1 = snb?.canOverwrite(ivIndex: ivIndex, updatedAt: almostNinetySixHoursAgo,
+ withIvRecovery: true, testMode: false,
+ andUnlimitedIvRecoveryAllowed: false)
+
+ // It's ok. 96 hours have passed.
+ let result2 = snb?.canOverwrite(ivIndex: ivIndex, updatedAt: ninetySixHoursAgo,
+ withIvRecovery: false, testMode: false,
+ andUnlimitedIvRecoveryAllowed: false)
+
+ // Now even more time passed, so updating IV Index is ok.
+ let result3 = snb?.canOverwrite(ivIndex: ivIndex, updatedAt: moreThanNinetySixHoursAgo,
+ withIvRecovery: false, testMode: false,
+ andUnlimitedIvRecoveryAllowed: false)
+
+ XCTAssert(result0 == false)
+ XCTAssert(result1 == true)
+ XCTAssert(result2 == true)
+ XCTAssert(result3 == true)
+ }
+
+ func testOverwritingWithNextIvIndexInTestMode() {
+ let data = Data(hex: "0102EE6C0EFF5298ECFF000000025E5AA7B268B5E044")!
+ let snb = SecureNetworkBeacon(decode: data, usingNetworkKey: networkKey)
+ let ivIndex = IvIndex(index: 1, updateActive: false)
+
+ let ninetySixHoursAgo = Date(hoursAgo: 96)
+ let almostNinetySixHoursAgo = Date(timeInterval: +10.0, since: ninetySixHoursAgo)
+ let moreThanNinetySixHoursAgo = Date(timeInterval: -10.0, since: ninetySixHoursAgo)
+
+ // In test mode the 96h requirement does not apply.
+ let result0 = snb?.canOverwrite(ivIndex: ivIndex, updatedAt: almostNinetySixHoursAgo,
+ withIvRecovery: false, testMode: true,
+ andUnlimitedIvRecoveryAllowed: false)
+
+ let result1 = snb?.canOverwrite(ivIndex: ivIndex, updatedAt: ninetySixHoursAgo,
+ withIvRecovery: false, testMode: true,
+ andUnlimitedIvRecoveryAllowed: false)
+
+ let result2 = snb?.canOverwrite(ivIndex: ivIndex, updatedAt: moreThanNinetySixHoursAgo,
+ withIvRecovery: false, testMode: true,
+ andUnlimitedIvRecoveryAllowed: false)
+
+ XCTAssert(result0 == true)
+ XCTAssert(result1 == true)
+ XCTAssert(result2 == true)
+ }
+
+ func testOverwritingWithFarIvIndex() {
+ let data = Data(hex: "0102EE6C0EFF5298ECFF000000025E5AA7B268B5E044")!
+ let snb = SecureNetworkBeacon(decode: data, usingNetworkKey: networkKey)
+ let ivIndex = IvIndex(index: 0, updateActive: false)
+
+ let ninetySixHoursAgo = Date(hoursAgo: 96)
+ let almostNinetySixHoursAgo = Date(timeInterval: +10.0, since: ninetySixHoursAgo)
+ let moreThanNinetySixHoursAgo = Date(timeInterval: -10.0, since: ninetySixHoursAgo)
+ let twoHundredEightyEightHoursAgo = Date(hoursAgo: 288)
+ let almostTwoHundredEightyEightHoursAgo = Date(timeInterval: +10.0, since: twoHundredEightyEightHoursAgo)
+ let moreThanTwoHundredEightyEightHoursAgo = Date(timeInterval: -10.0, since: twoHundredEightyEightHoursAgo)
+
+ // 3 * 96 = 288 hours are required to pass since last IV Index update
+ // for the IV Index to change from 0 (normal operation) to 2 (update active).
+ // The following tests check if SNB cannot be updated before that.
+ let result0 = snb?.canOverwrite(ivIndex: ivIndex, updatedAt: almostNinetySixHoursAgo,
+ withIvRecovery: false, testMode: false,
+ andUnlimitedIvRecoveryAllowed: false)
+
+ let result1 = snb?.canOverwrite(ivIndex: ivIndex, updatedAt: ninetySixHoursAgo,
+ withIvRecovery: false, testMode: false,
+ andUnlimitedIvRecoveryAllowed: false)
+
+ let result2 = snb?.canOverwrite(ivIndex: ivIndex, updatedAt: moreThanNinetySixHoursAgo,
+ withIvRecovery: false, testMode: false,
+ andUnlimitedIvRecoveryAllowed: false)
+
+ let result3 = snb?.canOverwrite(ivIndex: ivIndex,
+ updatedAt: almostTwoHundredEightyEightHoursAgo,
+ withIvRecovery: false, testMode: false,
+ andUnlimitedIvRecoveryAllowed: false)
+
+ // 3 * 96 = 288 hours have passed. IV Index can be updated
+ // from 0 (normal operation) to 2 (update active) using IV Recovery
+ // procedure.
+ let result4 = snb?.canOverwrite(ivIndex: ivIndex,
+ updatedAt: twoHundredEightyEightHoursAgo,
+ withIvRecovery: false, testMode: false,
+ andUnlimitedIvRecoveryAllowed: false)
+
+ // Even more time has passed.
+ let result5 = snb?.canOverwrite(ivIndex: ivIndex,
+ updatedAt: moreThanTwoHundredEightyEightHoursAgo,
+ withIvRecovery: false, testMode: false,
+ andUnlimitedIvRecoveryAllowed: false)
+
+ XCTAssert(result0 == false)
+ XCTAssert(result1 == false)
+ XCTAssert(result2 == false)
+ XCTAssert(result3 == false)
+ XCTAssert(result4 == true)
+ XCTAssert(result5 == true)
+ }
+
+ func testOverwritingWithFarIvIndexInTestMode() {
+ let data = Data(hex: "0102EE6C0EFF5298ECFF000000025E5AA7B268B5E044")!
+ let snb = SecureNetworkBeacon(decode: data, usingNetworkKey: networkKey)
+ let ivIndex = IvIndex(index: 0, updateActive: false)
+
+ let ninetySixHoursAgo = Date(hoursAgo: 96)
+ let almostNinetySixHoursAgo = Date(timeInterval: +10.0, since: ninetySixHoursAgo)
+ let moreThanNinetySixHoursAgo = Date(timeInterval: -10.0, since: ninetySixHoursAgo)
+
+ // Test mode only removes 96h requirements to transition to the next
+ // IV Index. Here we are updating from 0 (normal operation) to
+ // 2 (update active), which is 3 steps. Test mode cannot help.
+ let result0 = snb?.canOverwrite(ivIndex: ivIndex, updatedAt: almostNinetySixHoursAgo,
+ withIvRecovery: false, testMode: true,
+ andUnlimitedIvRecoveryAllowed: false)
+
+ let result1 = snb?.canOverwrite(ivIndex: ivIndex, updatedAt: ninetySixHoursAgo,
+ withIvRecovery: false, testMode: true,
+ andUnlimitedIvRecoveryAllowed: false)
+
+ let result2 = snb?.canOverwrite(ivIndex: ivIndex, updatedAt: moreThanNinetySixHoursAgo,
+ withIvRecovery: false, testMode: true,
+ andUnlimitedIvRecoveryAllowed: false)
+
+ XCTAssert(result0 == false)
+ XCTAssert(result1 == false)
+ XCTAssert(result2 == false)
+ }
+
+ func testOverwritingWithVeryFarIvIndex() {
+ // This Secure Network Beacon has IV Index 52 (update active).
+ let data = Data(hex: "0102EE6C0EFF5298ECFF00000034A53312BF9198C86F")!
+ let snb = SecureNetworkBeacon(decode: data, usingNetworkKey: networkKey)
+ let ivIndex = IvIndex(index: 9, updateActive: false)
+
+ // The IV Index changes from 9 to 52, that is by 43. Also, the update active
+ // flag changes from false to true, which adds one more step.
+ // At least 42 * 192h + additional 96h are required for the IV Index to be
+ // assumed valid. Updating IV by more than 42 is not allowed by the spec.
+ // This library allows, however, to disable this check with a flag.
+ let longTimeAgo = Date(hoursAgo: 42 * 192 + 96)
+ let notThatLongTimeAgo = Date(timeInterval: +10.0, since: longTimeAgo)
+ let longLongTimeAgo = Date(timeInterval: -10.0, since: longTimeAgo)
+
+ // First, with the flag set to false. All should fail.
+ let result0 = snb?.canOverwrite(ivIndex: ivIndex,
+ updatedAt: notThatLongTimeAgo,
+ withIvRecovery: false, testMode: false,
+ andUnlimitedIvRecoveryAllowed: false)
+
+ let result1 = snb?.canOverwrite(ivIndex: ivIndex,
+ updatedAt: longTimeAgo,
+ withIvRecovery: false, testMode: false,
+ andUnlimitedIvRecoveryAllowed: false)
+
+ let result2 = snb?.canOverwrite(ivIndex: ivIndex,
+ updatedAt: longLongTimeAgo,
+ withIvRecovery: false, testMode: false,
+ andUnlimitedIvRecoveryAllowed: false)
+
+ // Now, with the flag set to true.
+ let result3 = snb?.canOverwrite(ivIndex: ivIndex,
+ updatedAt: notThatLongTimeAgo,
+ withIvRecovery: false, testMode: false,
+ andUnlimitedIvRecoveryAllowed: true)
+
+ let result4 = snb?.canOverwrite(ivIndex: ivIndex,
+ updatedAt: longTimeAgo,
+ withIvRecovery: false, testMode: false,
+ andUnlimitedIvRecoveryAllowed: true)
+
+ let result5 = snb?.canOverwrite(ivIndex: ivIndex,
+ updatedAt: longLongTimeAgo,
+ withIvRecovery: false, testMode: false,
+ andUnlimitedIvRecoveryAllowed: true)
+
+ // This test fails for 2 reasons: not enough time and IV Index change
+ // exceeds limit of 42.
+ XCTAssert(result0 == false)
+ // Those tests should fails, as IV Index changed by more than 42.
+ XCTAssert(result1 == false)
+ XCTAssert(result2 == false)
+ // This test returns false, as the time difference is not long enough.
+ XCTAssert(result3 == false)
+ // Those tests pass, as more than 42 * 192h + 96h have passed, and
+ // the IV Index + 42 limit was turned off.
+ XCTAssert(result4 == true)
+ XCTAssert(result5 == true)
+ }
+
+}
+
+private extension Date {
+
+ init(hoursAgo: Double) {
+ self = Date(timeIntervalSinceNow: -hoursAgo * 3600.0)
+ }
+
+}
diff --git a/Example/nRFMeshProvision.xcodeproj/project.pbxproj b/Example/nRFMeshProvision.xcodeproj/project.pbxproj
index 1a9421af0..97ffc1bbe 100755
--- a/Example/nRFMeshProvision.xcodeproj/project.pbxproj
+++ b/Example/nRFMeshProvision.xcodeproj/project.pbxproj
@@ -94,6 +94,8 @@
52A9C1C52313D7D80036792A /* GenericLevelViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A9C1C32313D7D80036792A /* GenericLevelViewCell.swift */; };
52A9C1C62313D7D80036792A /* GenericLevel.xib in Resources */ = {isa = PBXBuildFile; fileRef = 52A9C1C42313D7D80036792A /* GenericLevel.xib */; };
52B7F67D229829F200B0A42F /* Addresses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B7F67C229829F200B0A42F /* Addresses.swift */; };
+ 52BC6F4824507A5400EBEEA4 /* DisconnectCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52BC6F4724507A5400EBEEA4 /* DisconnectCell.swift */; };
+ 52BC6F4A2452C4FB00EBEEA4 /* SecureNetworkBeacons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52BC6F492452C4FB00EBEEA4 /* SecureNetworkBeacons.swift */; };
52CD0A50232FA6CD00A9A181 /* ProxyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52CD0A4F232FA6CD00A9A181 /* ProxyViewController.swift */; };
52CD0A53232FA79300A9A181 /* FilterTypeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52CD0A52232FA79300A9A181 /* FilterTypeCell.swift */; };
52CD0A55232FC11700A9A181 /* AddressCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52CD0A54232FC11700A9A181 /* AddressCell.swift */; };
@@ -226,6 +228,8 @@
52A9C1C32313D7D80036792A /* GenericLevelViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericLevelViewCell.swift; sourceTree = ""; };
52A9C1C42313D7D80036792A /* GenericLevel.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GenericLevel.xib; sourceTree = ""; };
52B7F67C229829F200B0A42F /* Addresses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Addresses.swift; sourceTree = ""; };
+ 52BC6F4724507A5400EBEEA4 /* DisconnectCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisconnectCell.swift; sourceTree = ""; };
+ 52BC6F492452C4FB00EBEEA4 /* SecureNetworkBeacons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureNetworkBeacons.swift; sourceTree = ""; };
52CD0A4F232FA6CD00A9A181 /* ProxyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyViewController.swift; sourceTree = ""; };
52CD0A52232FA79300A9A181 /* FilterTypeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterTypeCell.swift; sourceTree = ""; };
52CD0A54232FC11700A9A181 /* AddressCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressCell.swift; sourceTree = ""; };
@@ -530,6 +534,7 @@
52CD0A612330DCFF00A9A181 /* CustomAddressCell.swift */,
5288C20323704EF300321ED3 /* ConnectionModeCell.swift */,
5288C20723705C8D00321ED3 /* ProxyCell.swift */,
+ 52BC6F4724507A5400EBEEA4 /* DisconnectCell.swift */,
);
path = Cells;
sourceTree = "";
@@ -651,6 +656,7 @@
52F6AD6B22B78F3000F0D7DF /* CompositionData.swift */,
523F776F22C64FD20030EA6A /* ConfigMessages.swift */,
52568D4A22DDFAC700B7B236 /* Groups.swift */,
+ 52BC6F492452C4FB00EBEEA4 /* SecureNetworkBeacons.swift */,
529B75A92248E275008F1CE7 /* nRFMeshProvision_Tests-Bridging-Header.h */,
);
path = Tests;
@@ -879,6 +885,7 @@
528352BC2285B6150097B949 /* OobSelector.swift in Sources */,
52E54CDF23438D2600478F05 /* GenericOnOffServerDelegate.swift in Sources */,
5288C20823705C8D00321ED3 /* ProxyCell.swift in Sources */,
+ 52BC6F4824507A5400EBEEA4 /* DisconnectCell.swift in Sources */,
523F777B22CB86A00030EA6A /* ModelBindAppKeyViewController.swift in Sources */,
52DF5D8F229691BD00C297C1 /* NetworkConnection.swift in Sources */,
52547F27232A3F470002AA7B /* EditRangesViewController.swift in Sources */,
@@ -962,6 +969,7 @@
52B7F67D229829F200B0A42F /* Addresses.swift in Sources */,
5294DADA22B109810041370E /* Pdus.swift in Sources */,
5231400022EEE1770074325A /* AssigningGroupAddresses.swift in Sources */,
+ 52BC6F4A2452C4FB00EBEEA4 /* SecureNetworkBeacons.swift in Sources */,
52568D4B22DDFAC700B7B236 /* Groups.swift in Sources */,
52F6AD6C22B78F3000F0D7DF /* CompositionData.swift in Sources */,
523F777022C64FD20030EA6A /* ConfigMessages.swift in Sources */,
@@ -1179,7 +1187,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherited)";
LINK_WITH_STANDARD_LIBRARIES = YES;
- MARKETING_VERSION = 2.2.1;
+ MARKETING_VERSION = 2.2.2;
MODULE_NAME = ExampleApp;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1213,7 +1221,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherited)";
LINK_WITH_STANDARD_LIBRARIES = YES;
- MARKETING_VERSION = 2.2.1;
+ MARKETING_VERSION = 2.2.2;
MODULE_NAME = ExampleApp;
OTHER_LDFLAGS = (
"$(inherited)",
diff --git a/Example/nRFMeshProvision.xcodeproj/xcshareddata/xcschemes/nRFMeshProvision_Example.xcscheme b/Example/nRFMeshProvision.xcodeproj/xcshareddata/xcschemes/nRFMeshProvision_Example.xcscheme
new file mode 100644
index 000000000..e9c6c969c
--- /dev/null
+++ b/Example/nRFMeshProvision.xcodeproj/xcshareddata/xcschemes/nRFMeshProvision_Example.xcscheme
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Example/nRFMeshProvision.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/nRFMeshProvision.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 000000000..18d981003
--- /dev/null
+++ b/Example/nRFMeshProvision.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/100.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/100.png
new file mode 100644
index 000000000..0c03d71a8
Binary files /dev/null and b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/100.png differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/1024.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/1024.png
new file mode 100644
index 000000000..2eec15e63
Binary files /dev/null and b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/1024.png differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/114.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/114.png
new file mode 100644
index 000000000..8a5bc5eb9
Binary files /dev/null and b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/114.png differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/120.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/120.png
new file mode 100644
index 000000000..b55be67d8
Binary files /dev/null and b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/120.png differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/144.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/144.png
new file mode 100644
index 000000000..8ba7e9b26
Binary files /dev/null and b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/144.png differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/152.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/152.png
new file mode 100644
index 000000000..24f09de3a
Binary files /dev/null and b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/152.png differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/167.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/167.png
new file mode 100644
index 000000000..94c14246d
Binary files /dev/null and b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/167.png differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/180.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/180.png
new file mode 100644
index 000000000..4ed3d63ce
Binary files /dev/null and b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/180.png differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/20.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/20.png
new file mode 100644
index 000000000..02db76158
Binary files /dev/null and b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/20.png differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/29.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/29.png
new file mode 100644
index 000000000..a32bf2b78
Binary files /dev/null and b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/29.png differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/40.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/40.png
new file mode 100644
index 000000000..cf5be03d0
Binary files /dev/null and b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/40.png differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/50.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/50.png
new file mode 100644
index 000000000..b6bee35cd
Binary files /dev/null and b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/50.png differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/57.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/57.png
new file mode 100644
index 000000000..24361b7a9
Binary files /dev/null and b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/57.png differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/58.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/58.png
new file mode 100644
index 000000000..a67e048e7
Binary files /dev/null and b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/58.png differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/60.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/60.png
new file mode 100644
index 000000000..77e588b7b
Binary files /dev/null and b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/60.png differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/72.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/72.png
new file mode 100644
index 000000000..21479ec59
Binary files /dev/null and b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/72.png differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/76.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/76.png
new file mode 100644
index 000000000..c8de47755
Binary files /dev/null and b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/76.png differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/80.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/80.png
new file mode 100644
index 000000000..ac6f537e0
Binary files /dev/null and b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/80.png differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/87.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/87.png
new file mode 100644
index 000000000..72117af3a
Binary files /dev/null and b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/87.png differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Contents.json b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Contents.json
old mode 100755
new mode 100644
index b57f31e6d..65b74d7ef
--- a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Contents.json
+++ b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Contents.json
@@ -1,116 +1 @@
-{
- "images" : [
- {
- "size" : "20x20",
- "idiom": "iphone",
- "filename" : "Icon-App-20@2x.png",
- "scale": "2x"
- },
- {
- "size" : "20x20",
- "idiom": "iphone",
- "filename" : "Icon-App-20@3x.png",
- "scale": "3x"
- },
- {
- "size" : "20x20",
- "idiom": "ipad",
- "filename" : "Icon-App-20.png",
- "scale": "1x"
- },
- {
- "size" : "20x20",
- "idiom": "ipad",
- "filename" : "Icon-App-20@2x.png",
- "scale": "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "Icon-App-40@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "Icon-App-40@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "Icon-App-60@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "Icon-App-60@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "Icon-App-29.png",
- "scale" : "1x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "Icon-App-29@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "ipad",
- "filename" : "Icon-App-40.png",
- "scale" : "1x"
- },
- {
- "size" : "40x40",
- "idiom" : "ipad",
- "filename" : "Icon-App-40@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "Icon-App-76.png",
- "scale" : "1x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "Icon-App-76@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "83.5x83.5",
- "idiom" : "ipad",
- "filename" : "Icon-App-83.5@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "1024x1024",
- "idiom" : "ios-marketing",
- "filename" : "Icon-App-1024.png",
- "scale" : "1x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
+{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"}]}
\ No newline at end of file
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-1024.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-1024.png
deleted file mode 100644
index 28091c030..000000000
Binary files a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-1024.png and /dev/null differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-20.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-20.png
deleted file mode 100644
index 4c21611d8..000000000
Binary files a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-20.png and /dev/null differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-20@2x.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-20@2x.png
deleted file mode 100644
index 28f3707da..000000000
Binary files a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-20@2x.png and /dev/null differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-20@3x.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-20@3x.png
deleted file mode 100644
index 8393588da..000000000
Binary files a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-20@3x.png and /dev/null differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-29.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-29.png
deleted file mode 100644
index 0cbbda85d..000000000
Binary files a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-29.png and /dev/null differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-29@2x.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-29@2x.png
deleted file mode 100644
index ee20c7bed..000000000
Binary files a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-29@2x.png and /dev/null differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-29@3x.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-29@3x.png
deleted file mode 100644
index 10914f30e..000000000
Binary files a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-29@3x.png and /dev/null differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-40.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-40.png
deleted file mode 100644
index 28f3707da..000000000
Binary files a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-40.png and /dev/null differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-40@2x.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-40@2x.png
deleted file mode 100644
index 9c74a090d..000000000
Binary files a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-40@2x.png and /dev/null differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-40@3x.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-40@3x.png
deleted file mode 100644
index acffd6831..000000000
Binary files a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-40@3x.png and /dev/null differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-60@2x.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-60@2x.png
deleted file mode 100644
index acffd6831..000000000
Binary files a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-60@2x.png and /dev/null differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-60@3x.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-60@3x.png
deleted file mode 100644
index 4c9b263e1..000000000
Binary files a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-60@3x.png and /dev/null differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-76.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-76.png
deleted file mode 100644
index 5581fa0a7..000000000
Binary files a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-76.png and /dev/null differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-76@2x.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-76@2x.png
deleted file mode 100644
index b6aea069c..000000000
Binary files a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-76@2x.png and /dev/null differ
diff --git a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-83.5@2x.png b/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-83.5@2x.png
deleted file mode 100644
index 7e0704570..000000000
Binary files a/Example/nRFMeshProvision/Images.xcassets/AppIcon.appiconset/Icon-App-83.5@2x.png and /dev/null differ
diff --git a/Example/nRFMeshProvision/Mesh Network/NetworkConnection.swift b/Example/nRFMeshProvision/Mesh Network/NetworkConnection.swift
index dfd22272a..585515456 100644
--- a/Example/nRFMeshProvision/Mesh Network/NetworkConnection.swift
+++ b/Example/nRFMeshProvision/Mesh Network/NetworkConnection.swift
@@ -124,6 +124,10 @@ class NetworkConnection: NSObject, Bearer {
isStarted = false
}
+ func disconnectCurrent() {
+ proxies.first?.close()
+ }
+
func send(_ data: Data, ofType type: PduType) throws {
guard supports(type) else {
throw BearerError.pduTypeNotSupported
diff --git a/Example/nRFMeshProvision/UI/Base.lproj/Proxy.storyboard b/Example/nRFMeshProvision/UI/Base.lproj/Proxy.storyboard
index 2753b7bd8..40cb0ad66 100644
--- a/Example/nRFMeshProvision/UI/Base.lproj/Proxy.storyboard
+++ b/Example/nRFMeshProvision/UI/Base.lproj/Proxy.storyboard
@@ -1,9 +1,9 @@
-
+
-
+
@@ -67,7 +67,7 @@
-
+
@@ -76,9 +76,38 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -114,7 +143,7 @@
-
+
@@ -135,13 +164,13 @@
-
+
@@ -166,7 +195,7 @@
-
+
@@ -187,7 +216,7 @@
-
+
@@ -227,17 +256,17 @@
-
+
-
+
@@ -275,7 +304,7 @@
-
+
@@ -357,13 +386,13 @@
-
+
@@ -398,7 +427,7 @@
-
+
@@ -514,11 +543,11 @@
+
-
diff --git a/Example/nRFMeshProvision/UI/Base.lproj/Settings.storyboard b/Example/nRFMeshProvision/UI/Base.lproj/Settings.storyboard
index 3c5a0d524..637c67ac4 100644
--- a/Example/nRFMeshProvision/UI/Base.lproj/Settings.storyboard
+++ b/Example/nRFMeshProvision/UI/Base.lproj/Settings.storyboard
@@ -1,9 +1,9 @@
-
+
-
+
@@ -23,7 +23,7 @@
-
+
@@ -51,7 +51,7 @@
-
+
@@ -78,7 +78,7 @@
-
+
@@ -105,7 +105,7 @@
-
+
@@ -128,12 +128,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -166,7 +195,7 @@
-
+
@@ -183,14 +212,14 @@
-
+
-
+
@@ -207,17 +236,17 @@
-
+
-
+
-
+
-
+
-
+
@@ -285,6 +314,7 @@
+
@@ -304,7 +334,7 @@
-
+
@@ -394,11 +424,11 @@
-
+
@@ -483,7 +513,7 @@
-
+
@@ -569,7 +599,7 @@
-
+
@@ -733,7 +763,7 @@
-
+
@@ -743,7 +773,7 @@
-
+
@@ -778,7 +808,7 @@
-
+
@@ -788,7 +818,7 @@
-
+
@@ -823,7 +853,7 @@
-
+
@@ -833,7 +863,7 @@
-
+
@@ -897,13 +927,13 @@
-
+
-
+
@@ -917,7 +947,7 @@
-
+
@@ -931,7 +961,7 @@
-
+
@@ -999,7 +1029,7 @@
-
+
@@ -1053,7 +1083,7 @@
-
+
@@ -1100,7 +1130,7 @@
-
+
@@ -1114,7 +1144,7 @@
-
+
@@ -1128,7 +1158,7 @@
-
+
@@ -1142,7 +1172,7 @@
-
+
@@ -1300,16 +1330,16 @@
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/Example/nRFMeshProvision/View Controllers/Network/Configuration/ConfigurationViewController.swift b/Example/nRFMeshProvision/View Controllers/Network/Configuration/ConfigurationViewController.swift
index c5da04cb0..9937cc91a 100644
--- a/Example/nRFMeshProvision/View Controllers/Network/Configuration/ConfigurationViewController.swift
+++ b/Example/nRFMeshProvision/View Controllers/Network/Configuration/ConfigurationViewController.swift
@@ -49,7 +49,7 @@ class ConfigurationViewController: ProgressViewController {
super.viewWillAppear(animated)
// Element name might have been updated.
- tableView.reloadSections(.keysAndElementsSections, with: .automatic)
+ tableView.reloadSections(.editableSections, with: .automatic)
// Check if the local Provisioner has configuration capabilities.
let localProvisioner = MeshNetworkManager.instance.meshNetwork?.localProvisioner
@@ -291,7 +291,8 @@ class ConfigurationViewController: ProgressViewController {
}
}
- override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
+ override func tableView(_ tableView: UITableView,
+ accessoryButtonTappedForRowWith indexPath: IndexPath) {
switch indexPath.row {
case 0:
presentAlert(title: "Info", message: "Mark a node as configured when you finished setting it up.")
@@ -630,6 +631,6 @@ private extension IndexPath {
private extension IndexSet {
- static let keysAndElementsSections = IndexSet(integersIn: IndexPath.keysSection...IndexPath.elementsSection)
+ static let editableSections = IndexSet(integersIn: IndexPath.keysSection...IndexPath.compositionDataSection)
}
diff --git a/Example/nRFMeshProvision/View Controllers/Network/Provisioning/ScannerTableViewController.swift b/Example/nRFMeshProvision/View Controllers/Network/Provisioning/ScannerTableViewController.swift
index 38dfbdaee..d3d8c1ffe 100644
--- a/Example/nRFMeshProvision/View Controllers/Network/Provisioning/ScannerTableViewController.swift
+++ b/Example/nRFMeshProvision/View Controllers/Network/Provisioning/ScannerTableViewController.swift
@@ -32,6 +32,12 @@ import UIKit
import CoreBluetooth
import nRFMeshProvision
+typealias DiscoveredPeripheral = (
+ device: UnprovisionedDevice,
+ peripheral: CBPeripheral,
+ rssi: Int
+)
+
class ScannerTableViewController: UITableViewController {
// MARK: - Outlets and Actions
@@ -46,8 +52,8 @@ class ScannerTableViewController: UITableViewController {
weak var delegate: ProvisioningViewDelegate?
private var centralManager: CBCentralManager!
- private var discoveredPeripherals = [(device: UnprovisionedDevice, peripheral: CBPeripheral, rssi: Int)]()
-
+ private var discoveredPeripherals: [DiscoveredPeripheral] = []
+
private var alert: UIAlertController?
private var selectedDevice: UnprovisionedDevice?
@@ -139,18 +145,18 @@ extension ScannerTableViewController: CBCentralManagerDelegate {
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral,
advertisementData: [String : Any], rssi RSSI: NSNumber) {
- if !discoveredPeripherals.contains(where: { $0.peripheral == peripheral }) {
+ if let index = discoveredPeripherals.firstIndex(where: { $0.peripheral == peripheral }) {
+ if let cell = tableView.cellForRow(at: IndexPath(row: index, section: 0)) as? DeviceCell {
+ let device = discoveredPeripherals[index].device
+ device.name = advertisementData.localName
+ cell.deviceDidUpdate(device, andRSSI: RSSI.intValue)
+ }
+ } else {
if let unprovisionedDevice = UnprovisionedDevice(advertisementData: advertisementData) {
discoveredPeripherals.append((unprovisionedDevice, peripheral, RSSI.intValue))
tableView.insertRows(at: [IndexPath(row: discoveredPeripherals.count - 1, section: 0)], with: .fade)
tableView.hideEmptyView()
}
- } else {
- if let index = discoveredPeripherals.firstIndex(where: { $0.peripheral == peripheral }) {
- if let cell = tableView.cellForRow(at: IndexPath(row: index, section: 0)) as? DeviceCell {
- cell.deviceDidUpdate(discoveredPeripherals[index].device, andRSSI: RSSI.intValue)
- }
- }
}
}
diff --git a/Example/nRFMeshProvision/View Controllers/Proxy/Cells/DisconnectCell.swift b/Example/nRFMeshProvision/View Controllers/Proxy/Cells/DisconnectCell.swift
new file mode 100644
index 000000000..6fd311432
--- /dev/null
+++ b/Example/nRFMeshProvision/View Controllers/Proxy/Cells/DisconnectCell.swift
@@ -0,0 +1,41 @@
+/*
+* Copyright (c) 2019, Nordic Semiconductor
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without modification,
+* are permitted provided that the following conditions are met:
+*
+* 1. Redistributions of source code must retain the above copyright notice, this
+* list of conditions and the following disclaimer.
+*
+* 2. Redistributions in binary form must reproduce the above copyright notice, this
+* list of conditions and the following disclaimer in the documentation and/or
+* other materials provided with the distribution.
+*
+* 3. Neither the name of the copyright holder nor the names of its contributors may
+* be used to endorse or promote products derived from this software without
+* specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+* POSSIBILITY OF SUCH DAMAGE.
+*/
+
+import UIKit
+import nRFMeshProvision
+
+class DisconnectCell: UITableViewCell {
+
+ @IBOutlet weak var disconnectButton: UIButton!
+
+ @IBAction func disconnectDidTapped(_ sender: UIButton) {
+ MeshNetworkManager.bearer.disconnectCurrent()
+ }
+}
diff --git a/Example/nRFMeshProvision/View Controllers/Proxy/ProxySelectorViewController.swift b/Example/nRFMeshProvision/View Controllers/Proxy/ProxySelectorViewController.swift
index 78d45ca2d..fa8cfd260 100644
--- a/Example/nRFMeshProvision/View Controllers/Proxy/ProxySelectorViewController.swift
+++ b/Example/nRFMeshProvision/View Controllers/Proxy/ProxySelectorViewController.swift
@@ -32,6 +32,8 @@ import UIKit
import CoreBluetooth
import nRFMeshProvision
+typealias DiscoveredProxy = (device: GattBearer, rssi: Int)
+
class ProxySelectorViewController: UITableViewController {
// MARK: - Outlets and Actions
@@ -47,7 +49,7 @@ class ProxySelectorViewController: UITableViewController {
var meshNetwork: MeshNetwork?
private var centralManager: CBCentralManager!
- private var proxies = [(device: GattBearer, rssi: Int)]()
+ private var proxies: [DiscoveredProxy] = []
private var alert: UIAlertController?
private var selectedDevice: GattBearer?
diff --git a/Example/nRFMeshProvision/View Controllers/Proxy/ProxyViewController.swift b/Example/nRFMeshProvision/View Controllers/Proxy/ProxyViewController.swift
index b436c82dd..d0098e98b 100644
--- a/Example/nRFMeshProvision/View Controllers/Proxy/ProxyViewController.swift
+++ b/Example/nRFMeshProvision/View Controllers/Proxy/ProxyViewController.swift
@@ -76,9 +76,9 @@ class ProxyViewController: ProgressViewController, Editable {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
- case 0:
- return 2
- case 1:
+ case IndexPath.statusSection:
+ return 3
+ case IndexPath.proxyTypeSection:
return 1
default:
return MeshNetworkManager.instance.proxyFilter?.addresses.count ?? 0
@@ -122,6 +122,13 @@ class ProxyViewController: ProgressViewController, Editable {
cell.selectionStyle = bearer.isConnectionModeAutomatic ? .none : .default
return cell
}
+ if indexPath == .action {
+ let cell = tableView.dequeueReusableCell(withIdentifier: "disconnect", for: indexPath) as! DisconnectCell
+ let bearer = MeshNetworkManager.bearer!
+ cell.disconnectButton.isEnabled = bearer.isOpen &&
+ !bearer.isConnectionModeAutomatic
+ return cell
+ }
if indexPath == .control {
let cell = tableView.dequeueReusableCell(withIdentifier: "type", for: indexPath) as! FilterTypeCell
cell.delegate = self
@@ -163,12 +170,13 @@ extension ProxyViewController: BearerDelegate {
func bearerDidOpen(_ bearer: Bearer) {
addButton.isEnabled = true
- tableView.reloadRows(at: [.status, .control], with: .automatic)
+ tableView.reloadRows(at: [.status, .action, .control], with: .automatic)
}
func bearer(_ bearer: Bearer, didClose error: Error?) {
addButton.isEnabled = false
MeshNetworkManager.instance.proxyFilter?.clear()
+ tableView.reloadRows(at: [.status, .action, .control], with: .automatic)
}
}
@@ -176,7 +184,7 @@ extension ProxyViewController: BearerDelegate {
extension ProxyViewController: ConnectionModeDelegate {
func connectionModeDidChange(automatic: Bool) {
- tableView.reloadRows(at: [.status], with: .automatic)
+ tableView.reloadRows(at: [.status, .action], with: .automatic)
}
}
@@ -241,6 +249,7 @@ private extension IndexPath {
static let mode = IndexPath(row: 0, section: IndexPath.statusSection)
static let status = IndexPath(row: 1, section: IndexPath.statusSection)
+ static let action = IndexPath(row: 2, section: IndexPath.statusSection)
static let control = IndexPath(row: 0, section: IndexPath.proxyTypeSection)
}
diff --git a/Example/nRFMeshProvision/View Controllers/Settings/SettingsViewController.swift b/Example/nRFMeshProvision/View Controllers/Settings/SettingsViewController.swift
index e23238bf4..b4f8636ad 100644
--- a/Example/nRFMeshProvision/View Controllers/Settings/SettingsViewController.swift
+++ b/Example/nRFMeshProvision/View Controllers/Settings/SettingsViewController.swift
@@ -40,6 +40,10 @@ class SettingsViewController: UITableViewController {
@IBOutlet weak var provisionersLabel: UILabel!
@IBOutlet weak var networkKeysLabel: UILabel!
@IBOutlet weak var appKeysLabel: UILabel!
+ @IBOutlet weak var testModeSwitch: UISwitch!
+ @IBAction func testModeDidChange(_ sender: UISwitch) {
+ MeshNetworkManager.instance.ivUpdateTestMode = sender.isOn
+ }
@IBOutlet weak var resetNetworkButton: UIButton!
@@ -97,6 +101,13 @@ class SettingsViewController: UITableViewController {
}
}
+ override func tableView(_ tableView: UITableView,
+ accessoryButtonTappedForRowWith indexPath: IndexPath) {
+ if indexPath.isIvUpdateTestMode {
+ presentAlert(title: "Info", message: "IV Update test mode allows to transition to the subsequent IV Index without having to wait at least 96 hours. The transition will be done upon receving a valid Secure Network beacon.")
+ }
+ }
+
}
private extension SettingsViewController {
@@ -148,6 +159,8 @@ private extension SettingsViewController {
/// Resets all network settings to default values.
func resetNetwork() {
(UIApplication.shared.delegate as! AppDelegate).createNewMeshNetwork()
+ MeshNetworkManager.instance.ivUpdateTestMode = false
+ testModeSwitch.setOn(false, animated: true)
if MeshNetworkManager.instance.save() {
reload()
@@ -197,6 +210,8 @@ private extension SettingsViewController {
provisionersLabel.text = "\(meshNetwork.provisioners.count)"
networkKeysLabel.text = "\(meshNetwork.networkKeys.count)"
appKeysLabel.text = "\(meshNetwork.applicationKeys.count)"
+ MeshNetworkManager.instance.ivUpdateTestMode = false
+ testModeSwitch.setOn(false, animated: true)
// All tabs should be reset to the root view controller.
parent?.parent?.children.forEach {
@@ -271,22 +286,27 @@ private extension IndexPath {
static let actionsSection = 2
static let aboutSection = 3
- /// Returns whether the IndexPath point to the mesh network name row.
+ /// Returns whether the IndexPath points to the mesh network name row.
var isNetworkName: Bool {
return section == IndexPath.nameSection && row == 0
}
- /// Returns whether the IndexPath point to the network resetting option.
+ /// Returns whether the IndexPath points to the IV Update Test Mode switch row.
+ var isIvUpdateTestMode: Bool {
+ return section == IndexPath.networkSection && row == 3
+ }
+
+ /// Returns whether the IndexPath points to the network resetting option.
var isResetNetwork: Bool {
return section == IndexPath.actionsSection && row == 0
}
- /// Returns whether the IndexPath point to the Source Code link.
+ /// Returns whether the IndexPath points to the Source Code link.
var isLinkToGitHub: Bool {
return section == IndexPath.aboutSection && row == 2
}
- /// Returns whether the IndexPath point to the Issues on GitHub.
+ /// Returns whether the IndexPath points to the Issues on GitHub.
var isLinkToIssues: Bool {
return section == IndexPath.aboutSection && row == 3
}
diff --git a/README.md b/README.md
index 538d910b5..5dfcf0130 100755
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ The nRF Mesh Provision library allows to provision and send messages to Bluetoot
The library is compatible with version 1.0.1 of the Bluetooth Mesh Profile Specification.
-This is the second version of the nRF Mesh Provision library for iOS. All features are tested againt nRF Mesh SDK 3.2 and Zephyr based mesh devices.
+This is the second version of the nRF Mesh Provision library for iOS. All features are tested againt nRF Mesh SDK and Zephyr based mesh devices.
> The first version of this library is no longer maintained. The application available on App Store will eventually be replaced with the new sample application.
@@ -35,11 +35,12 @@ The app and the library are released under BSD-3 license. Feel free to modify th
12. Handling Configuration Server message sent by other Provisioner.
13. Generic OnOff and Vendor model have dedicated controls in sample app.
14. Proxy Filter.
+15. IV Index update (handling updates received in Secure Network beacons).
## NOT (yet) supported features
1. Many SIG defined models, except from supported ones.
-2. Key Refresh Procedure, IV Index update.
+2. Key Refresh Procedure, IV Index update (initiation).
3. Health server messages.
4. Hearbeats.
5. Remote Provisioning.
diff --git a/nRFMeshProvision.podspec b/nRFMeshProvision.podspec
index 1790b05be..bd4514a9d 100755
--- a/nRFMeshProvision.podspec
+++ b/nRFMeshProvision.podspec
@@ -6,9 +6,10 @@
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
#
+
Pod::Spec.new do |s|
s.name = 'nRFMeshProvision'
- s.version = '2.2.1'
+ s.version = '2.2.2'
s.summary = 'A Bluetooth Mesh library'
s.description = <<-DESC
nRF Mesh is a Bluetooth Mesh compliant library that has many features such as provisioning, configuration and control of Bluetooth Mesh compliant nodes.
@@ -18,10 +19,10 @@ Pod::Spec.new do |s|
s.author = { 'Aleksander Nowakowski' => 'aleksander.nowakowski@nordicsemi.no' }
s.source = { :git => 'https://github.com/NordicSemiconductor/IOS-nRF-Mesh-Library.git', :tag => s.version.to_s }
s.social_media_url = 'https://twitter.com/nordictweets'
- s.platform = :ios
+ s.ios.deployment_target = '10.0'
+ s.osx.deployment_target = '10.15'
s.static_framework = true
- s.swift_version = '5.1'
- s.ios.deployment_target = '10.0'
+ s.swift_versions = ['4.2', '5.0', '5.1', '5.2']
s.source_files = 'nRFMeshProvision/Classes/**/*'
s.dependency 'OpenSSL-Universal', '= 1.0.2.19'
s.frameworks = 'CoreBluetooth'
diff --git a/nRFMeshProvision/Classes/Bearer/GATT/BaseGattProxyBearer.swift b/nRFMeshProvision/Classes/Bearer/GATT/BaseGattProxyBearer.swift
index eccc0db45..3dd9ef59f 100644
--- a/nRFMeshProvision/Classes/Bearer/GATT/BaseGattProxyBearer.swift
+++ b/nRFMeshProvision/Classes/Bearer/GATT/BaseGattProxyBearer.swift
@@ -45,6 +45,7 @@ open class BaseGattProxyBearer: NSObject, Bearer, CBCentra
private let centralManager: CBCentralManager
private var basePeripheral: CBPeripheral!
+ private let mutex = DispatchQueue(label: "GattBearer")
/// The protocol used for segmentation and reassembly.
private let protocolHandler: ProxyProtocolHandler
@@ -134,14 +135,20 @@ open class BaseGattProxyBearer: NSObject, Bearer, CBCentra
// to send more data, a `peripheralIsReady(toSendWriteWithoutResponse:)` callback
// will be called, which will send the next packet.
if #available(iOS 11.0, *) {
- let queueWasEmpty = queue.isEmpty
- queue.append(contentsOf: packets)
-
- // Don't look at `basePeripheral.canSendWriteWithoutResponse`. If often returns
- // `false` even when nothing was sent before and no callback is called afterwards.
- // Just assume, that the first packet can always be sent.
- if queueWasEmpty {
- let packet = queue.remove(at: 0)
+ let packet: Data? = mutex.sync {
+ let queueWasEmpty = queue.isEmpty
+ queue.append(contentsOf: packets)
+
+ // Don't look at `basePeripheral.canSendWriteWithoutResponse`. If often returns
+ // `false` even when nothing was sent before and no callback is called afterwards.
+ // Just assume, that the first packet can always be sent.
+ if queueWasEmpty {
+ return queue.remove(at: 0)
+ } else {
+ return nil
+ }
+ }
+ if let packet = packet {
logger?.d(.bearer, "-> 0x\(packet.hex)")
basePeripheral.writeValue(packet, for: dataInCharacteristic, type: .withoutResponse)
}
@@ -329,13 +336,17 @@ open class BaseGattProxyBearer: NSObject, Bearer, CBCentra
// This method is available only on iOS 11+.
open func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) {
- guard !queue.isEmpty else {
- return
+ let packet: Data? = mutex.sync {
+ if queue.isEmpty {
+ return nil
+ } else {
+ return queue.remove(at: 0)
+ }
+ }
+ if let packet = packet {
+ logger?.d(.bearer, "-> 0x\(packet.hex)")
+ peripheral.writeValue(packet, for: dataInCharacteristic!, type: .withoutResponse)
}
-
- let packet = queue.remove(at: 0)
- logger?.d(.bearer, "-> 0x\(packet.hex)")
- peripheral.writeValue(packet, for: dataInCharacteristic!, type: .withoutResponse)
}
}
diff --git a/nRFMeshProvision/Classes/Layers/Lower Transport Layer/AccessMessage.swift b/nRFMeshProvision/Classes/Layers/Lower Transport Layer/AccessMessage.swift
index 8ce9ec59d..c8703a828 100644
--- a/nRFMeshProvision/Classes/Layers/Lower Transport Layer/AccessMessage.swift
+++ b/nRFMeshProvision/Classes/Layers/Lower Transport Layer/AccessMessage.swift
@@ -34,6 +34,7 @@ internal struct AccessMessage: LowerTransportPdu {
let source: Address
let destination: Address
let networkKey: NetworkKey
+ let ivIndex: UInt32
/// 6-bit Application Key identifier. This field is set to `nil`
/// if the message is signed with a Device Key instead.
@@ -77,6 +78,7 @@ internal struct AccessMessage: LowerTransportPdu {
transportMicSize = 4
sequence = networkPdu.sequence
networkKey = networkPdu.networkKey
+ ivIndex = networkPdu.ivIndex
upperTransportPdu = data.advanced(by: 1)
source = networkPdu.source
@@ -95,6 +97,7 @@ internal struct AccessMessage: LowerTransportPdu {
destination = segment.destination
sequence = segment.sequence
networkKey = segment.networkKey
+ ivIndex = segment.ivIndex
// Segments are already sorted by `segmentOffset`.
upperTransportPdu = segments.reduce(Data()) {
@@ -106,7 +109,8 @@ internal struct AccessMessage: LowerTransportPdu {
///
/// - parameter pdu: The Upper Transport PDU.
/// - parameter networkKey: The Network Key to encrypt the PCU with.
- init(fromUnsegmentedUpperTransportPdu pdu: UpperTransportPdu, usingNetworkKey networkKey: NetworkKey) {
+ init(fromUnsegmentedUpperTransportPdu pdu: UpperTransportPdu,
+ usingNetworkKey networkKey: NetworkKey) {
self.aid = pdu.aid
self.upperTransportPdu = pdu.transportPdu
self.transportMicSize = 4
@@ -114,6 +118,7 @@ internal struct AccessMessage: LowerTransportPdu {
self.destination = pdu.destination
self.sequence = pdu.sequence
self.networkKey = networkKey
+ self.ivIndex = pdu.ivIndex
}
}
diff --git a/nRFMeshProvision/Classes/Layers/Lower Transport Layer/ControlMessage.swift b/nRFMeshProvision/Classes/Layers/Lower Transport Layer/ControlMessage.swift
index 148653715..02bf18db9 100644
--- a/nRFMeshProvision/Classes/Layers/Lower Transport Layer/ControlMessage.swift
+++ b/nRFMeshProvision/Classes/Layers/Lower Transport Layer/ControlMessage.swift
@@ -34,6 +34,7 @@ internal struct ControlMessage: LowerTransportPdu {
let source: Address
let destination: Address
let networkKey: NetworkKey
+ let ivIndex: UInt32
/// Message Op Code.
let opCode: UInt8
@@ -64,6 +65,7 @@ internal struct ControlMessage: LowerTransportPdu {
source = networkPdu.source
destination = networkPdu.destination
networkKey = networkPdu.networkKey
+ ivIndex = networkPdu.ivIndex
}
/// Creates a Control Message object from the given list of segments.
@@ -76,6 +78,7 @@ internal struct ControlMessage: LowerTransportPdu {
source = segment.source
destination = segment.destination
networkKey = segment.networkKey
+ ivIndex = segment.ivIndex
// Segments are already sorted by `segmentOffset`.
upperTransportPdu = segments.reduce(Data()) {
@@ -87,17 +90,21 @@ internal struct ControlMessage: LowerTransportPdu {
/// message. The source should be set to the local Node address.
/// The given Network Key should be known to the Proxy Node.
///
- /// - parameter message: The message to be sent.
- /// - parameter source: The address of the local Node.
- /// - parameter networkKey: The Network Key to signe the message with.
- /// The key should be known to the connected
- /// Proxy Node.
+ /// - parameters:
+ /// - message: The message to be sent.
+ /// - source: The address of the local Node.
+ /// - networkKey: The Network Key to signe the message with.
+ /// The key should be known to the connected
+ /// Proxy Node.
+ /// - ivIndex: The current IV Index of the mesh network.
init(fromProxyConfigurationMessage message: ProxyConfigurationMessage,
- sentFrom source: Address, usingNetworkKey networkKey: NetworkKey) {
+ sentFrom source: Address, usingNetworkKey networkKey: NetworkKey,
+ andIvIndex ivIndex: IvIndex) {
self.opCode = message.opCode
self.source = source
self.destination = Address.unassignedAddress
self.networkKey = networkKey
+ self.ivIndex = ivIndex.transmitIndex
self.upperTransportPdu = message.parameters ?? Data()
}
}
diff --git a/nRFMeshProvision/Classes/Layers/Lower Transport Layer/LowerTransportLayer.swift b/nRFMeshProvision/Classes/Layers/Lower Transport Layer/LowerTransportLayer.swift
index 471e27dc8..66e39d10b 100644
--- a/nRFMeshProvision/Classes/Layers/Lower Transport Layer/LowerTransportLayer.swift
+++ b/nRFMeshProvision/Classes/Layers/Lower Transport Layer/LowerTransportLayer.swift
@@ -48,7 +48,7 @@ internal class LowerTransportLayer {
/// The storage for keeping sequence numbers. Each mesh network (with different UUID)
/// has a unique storage, which can be reloaded when the network is imported after it
/// was used before.
- let defaults: UserDefaults
+ private let defaults: UserDefaults
/// The map of incomplete received segments. Every time a Segmented Message is received
/// it is added to the map to an ordered array. When all segments are received
@@ -57,7 +57,7 @@ internal class LowerTransportLayer {
/// The key consists of 16 bits of source address in 2 most significant bytes
/// and `sequenceZero` field in 13 least significant bits.
/// See `UInt32(keyFor:sequenceZero)` below.
- var incompleteSegments: [UInt32 : [SegmentedMessage?]]
+ private var incompleteSegments: [UInt32 : [SegmentedMessage?]]
/// This map contains Segment Acknowlegment Messages of completed messages.
/// It is used when a complete Segmented Message has been received and the
/// ACK has been sent but failed to reach the source Node.
@@ -67,7 +67,7 @@ internal class LowerTransportLayer {
/// acknowledged message, it can immediatelly send the ACK again.
///
/// An item is removed when a next message has been received from the same Node.
- var acknowledgments: [Address : SegmentAcknowledgmentMessage]
+ private var acknowledgments: [Address : SegmentAcknowledgmentMessage]
/// The map of active timers. Every message has `defaultIncompleteTimerInterval`
/// seconds to be completed (timer resets when next segment was received).
/// After that time the segments are discarded.
@@ -75,7 +75,7 @@ internal class LowerTransportLayer {
/// The key consists of 16 bits of source address in 2 most significant bytes
/// and `sequenceZero` field in 13 least significant bits.
/// See `UInt32(keyFor:sequenceZero)` below.
- var incompleteTimers: [UInt32 : BackgroundTimer]
+ private var incompleteTimers: [UInt32 : BackgroundTimer]
/// The map of acknowledgment timers. After receiving a segment targeting
/// any of the Unicast Addresses of one of the Elements of the local Node, a
/// timer is started that will send the Segment Acknowledgment Message for
@@ -85,23 +85,23 @@ internal class LowerTransportLayer {
/// The key consists of 16 bits of source address in 2 most significant bytes
/// and `sequenceZero` field in 13 least significant bits.
/// See `UInt32(keyFor:sequenceZero)` below.
- var acknowledgmentTimers: [UInt32 : BackgroundTimer]
+ private var acknowledgmentTimers: [UInt32 : BackgroundTimer]
/// The map of outgoing segmented messages.
///
/// The key is the `sequenceZero` of the message.
- var outgoingSegments: [UInt16: [SegmentedMessage?]]
+ private var outgoingSegments: [UInt16: [SegmentedMessage?]]
/// The map of segment transmission timers. A segment transmission timer
/// for a Segmented Message with `sequenceZero` is started whenever such
/// message is sent to a Unicast Address. After the timer expires, the
/// layer will resend all non-confirmed segments and reset the timer.
///
/// The key is the `sequenceZero` of the message.
- var segmentTransmissionTimers: [UInt16 : BackgroundTimer]
+ private var segmentTransmissionTimers: [UInt16 : BackgroundTimer]
/// The initial TTL values.
///
/// The key is the `sequenceZero` of the message.
- var segmentTtl: [UInt16 : UInt8]
+ private var segmentTtl: [UInt16 : UInt8]
init(_ networkManager: NetworkManager) {
self.networkManager = networkManager
@@ -299,13 +299,9 @@ private extension LowerTransportLayer {
return true
}
let sequence = networkPdu.messageSequence
+ let receivedSeqAuth = (UInt64(networkPdu.ivIndex) << 24) | UInt64(sequence)
- let newSource = defaults.object(forKey: networkPdu.source.hex) == nil
- if !newSource {
- let lastSequence = defaults.integer(forKey: networkPdu.source.hex)
- let localSeqAuth = (UInt64(networkPdu.networkKey.ivIndex.index) << 24) | UInt64(lastSequence)
- let receivedSeqAuth = (UInt64(networkPdu.networkKey.ivIndex.index) << 24) | UInt64(sequence)
-
+ if let localSeqAuth = defaults.object(forKey: networkPdu.source.hex) as? NSNumber {
// In general, the SeqAuth of the received message must be greater
// than SeqAuth of any previously received message from the same source.
// However, for SAR (Segmentation and Reassembly) sessions, it is
@@ -320,15 +316,15 @@ private extension LowerTransportLayer {
reassemblyInProgress = incompleteSegments[key] != nil ||
acknowledgments[networkPdu.source]?.sequenceZero == sequenceZero
}
- guard receivedSeqAuth > localSeqAuth ||
- (reassemblyInProgress && receivedSeqAuth == localSeqAuth) else {
+ guard receivedSeqAuth > localSeqAuth.uint64Value ||
+ (reassemblyInProgress && receivedSeqAuth == localSeqAuth.uint64Value) else {
// Ignore that message.
logger?.w(.lowerTransport, "Discarding packet (seqAuth: \(receivedSeqAuth), expected > \(localSeqAuth))")
return false
}
}
// SeqAuth is valid, save the new sequence authentication value.
- defaults.set(sequence, forKey: networkPdu.source.hex)
+ defaults.set(NSNumber(value: receivedSeqAuth), forKey: networkPdu.source.hex)
return true
}
diff --git a/nRFMeshProvision/Classes/Layers/Lower Transport Layer/LowerTransportPdu.swift b/nRFMeshProvision/Classes/Layers/Lower Transport Layer/LowerTransportPdu.swift
index 8917a1634..7f3456e13 100644
--- a/nRFMeshProvision/Classes/Layers/Lower Transport Layer/LowerTransportPdu.swift
+++ b/nRFMeshProvision/Classes/Layers/Lower Transport Layer/LowerTransportPdu.swift
@@ -43,6 +43,8 @@ internal protocol LowerTransportPdu {
var destination: Address { get }
/// The Network Key used to decode/encode the PDU.
var networkKey: NetworkKey { get }
+ /// The IV Index used to decode/encode the PDU.
+ var ivIndex: UInt32 { get }
/// Message type.
var type: LowerTransportPduType { get }
/// The raw data of Lower Transport Layer PDU.
diff --git a/nRFMeshProvision/Classes/Layers/Lower Transport Layer/SegmentAcknowledgmentMessage.swift b/nRFMeshProvision/Classes/Layers/Lower Transport Layer/SegmentAcknowledgmentMessage.swift
index 4bbb6514e..44f6c2c22 100644
--- a/nRFMeshProvision/Classes/Layers/Lower Transport Layer/SegmentAcknowledgmentMessage.swift
+++ b/nRFMeshProvision/Classes/Layers/Lower Transport Layer/SegmentAcknowledgmentMessage.swift
@@ -34,6 +34,7 @@ internal struct SegmentAcknowledgmentMessage: LowerTransportPdu {
let source: Address
let destination: Address
let networkKey: NetworkKey
+ let ivIndex: UInt32
/// Message Op Code.
let opCode: UInt8
@@ -78,6 +79,7 @@ internal struct SegmentAcknowledgmentMessage: LowerTransportPdu {
source = networkPdu.source
destination = networkPdu.destination
networkKey = networkPdu.networkKey
+ ivIndex = networkPdu.ivIndex
}
/// Creates the ACK for given array of segments. At least one of
@@ -104,6 +106,7 @@ internal struct SegmentAcknowledgmentMessage: LowerTransportPdu {
source = segment.destination
destination = segment.source
networkKey = segment.networkKey
+ ivIndex = segment.ivIndex
}
/// Returns whether the segment with given index has been received.
diff --git a/nRFMeshProvision/Classes/Layers/Lower Transport Layer/SegmentedAccessMessage.swift b/nRFMeshProvision/Classes/Layers/Lower Transport Layer/SegmentedAccessMessage.swift
index 0ff2cdae8..30b31b45f 100644
--- a/nRFMeshProvision/Classes/Layers/Lower Transport Layer/SegmentedAccessMessage.swift
+++ b/nRFMeshProvision/Classes/Layers/Lower Transport Layer/SegmentedAccessMessage.swift
@@ -37,6 +37,7 @@ internal struct SegmentedAccessMessage: SegmentedMessage {
let source: Address
let destination: Address
let networkKey: NetworkKey
+ let ivIndex: UInt32
/// The Application Key identifier.
/// This field is set to `nil` if the message is signed with a
@@ -99,6 +100,7 @@ internal struct SegmentedAccessMessage: SegmentedMessage {
source = networkPdu.source
destination = networkPdu.destination
networkKey = networkPdu.networkKey
+ ivIndex = networkPdu.ivIndex
message = nil
localElement = nil
userInitiated = false
@@ -117,6 +119,7 @@ internal struct SegmentedAccessMessage: SegmentedMessage {
self.source = pdu.source
self.destination = pdu.destination
self.networkKey = networkKey
+ self.ivIndex = pdu.ivIndex
self.transportMicSize = pdu.transportMicSize
self.sequence = pdu.sequence
self.sequenceZero = UInt16(pdu.sequence & 0x1FFF)
diff --git a/nRFMeshProvision/Classes/Layers/Lower Transport Layer/SegmentedControlMessage.swift b/nRFMeshProvision/Classes/Layers/Lower Transport Layer/SegmentedControlMessage.swift
index bbae694fe..93d45eaf9 100644
--- a/nRFMeshProvision/Classes/Layers/Lower Transport Layer/SegmentedControlMessage.swift
+++ b/nRFMeshProvision/Classes/Layers/Lower Transport Layer/SegmentedControlMessage.swift
@@ -37,6 +37,7 @@ internal struct SegmentedControlMessage: SegmentedMessage {
let source: Address
let destination: Address
let networkKey: NetworkKey
+ let ivIndex: UInt32
/// Message Op Code.
let opCode: UInt8
@@ -83,6 +84,7 @@ internal struct SegmentedControlMessage: SegmentedMessage {
source = networkPdu.source
destination = networkPdu.destination
networkKey = networkPdu.networkKey
+ ivIndex = networkPdu.ivIndex
message = nil
localElement = nil
userInitiated = false
diff --git a/nRFMeshProvision/Classes/Layers/Network Layer/NetworkLayer.swift b/nRFMeshProvision/Classes/Layers/Network Layer/NetworkLayer.swift
index 49036c29c..e7f96859d 100644
--- a/nRFMeshProvision/Classes/Layers/Network Layer/NetworkLayer.swift
+++ b/nRFMeshProvision/Classes/Layers/Network Layer/NetworkLayer.swift
@@ -119,7 +119,7 @@ internal class NetworkLayer {
handle(unprovisionedDeviceBeacon: beaconPdu)
return
}
- logger?.w(.network, "Failed to decrypt Mesh Beacon PDU")
+ logger?.w(.network, "Failed to decrypt mesh beacon PDU")
// else: Invalid or unsupported beacon type.
case .proxyConfiguration:
@@ -133,7 +133,6 @@ internal class NetworkLayer {
default:
return
}
-
}
/// This method tries to send the Lower Transport Message of given type to the
@@ -169,7 +168,14 @@ internal class NetworkLayer {
// If the message was sent locally, don't report Bearer closer error.
try? transmitter.send(networkPdu.pdu, ofType: type)
} else {
- try transmitter.send(networkPdu.pdu, ofType: type)
+ do {
+ try transmitter.send(networkPdu.pdu, ofType: type)
+ } catch {
+ if case BearerError.bearerClosed = error {
+ proxyNetworkKey = nil
+ }
+ throw error
+ }
}
// Unless a GATT Bearer is used, the Network PDUs should be sent multiple times
@@ -196,7 +202,8 @@ internal class NetworkLayer {
func send(proxyConfigurationMessage message: ProxyConfigurationMessage) {
guard let networkKey = proxyNetworkKey else {
// The Proxy Network Key is unknown.
- networkManager.manager.proxyFilter?.managerFailedToDeliverMessage(message, error: BearerError.bearerClosed)
+ networkManager.manager.proxyFilter?
+ .managerFailedToDeliverMessage(message, error: BearerError.bearerClosed)
return
}
@@ -206,12 +213,16 @@ internal class NetworkLayer {
let source = meshNetwork.localProvisioner?.node?.unicastAddress ?? Address.maxUnicastAddress
logger?.i(.proxy, "Sending \(message) from: \(source.hex) to: 0000")
let pdu = ControlMessage(fromProxyConfigurationMessage: message,
- sentFrom: source, usingNetworkKey: networkKey)
+ sentFrom: source, usingNetworkKey: networkKey,
+ andIvIndex: meshNetwork.ivIndex)
logger?.i(.network, "Sending \(pdu)")
do {
try send(lowerTransportPdu: pdu, ofType: .proxyConfiguration, withTtl: 0)
networkManager.manager.proxyFilter?.managerDidDeliverMessage(message)
} catch {
+ if case BearerError.bearerClosed = error {
+ proxyNetworkKey = nil
+ }
networkManager.manager.proxyFilter?.managerFailedToDeliverMessage(message, error: error)
}
}
@@ -219,66 +230,130 @@ internal class NetworkLayer {
private extension NetworkLayer {
- /// This method handles the Unprovisioned Device Beacon.
+ /// This method handles the Unprovisioned Device beacon.
///
/// The curernt implementation does nothing, as remote provisioning is
/// currently not supported.
///
- /// - parameter unprovisionedDeviceBeacon: The Unprovisioned Device Beacon received.
+ /// - parameter unprovisionedDeviceBeacon: The Unprovisioned Device beacon received.
func handle(unprovisionedDeviceBeacon: UnprovisionedDeviceBeacon) {
- // TODO: Handle Unprovisioned Device Beacon.
+ // TODO: Handle Unprovisioned Device beacon.
}
- /// This method handles the Secure Network Beacon.
- /// It will set the proper IV Index and IV Update Active flag for the Network Key
- /// that matches Network ID and change the Key Refresh Phase based on the
- /// key refresh flag specified in the beacon.
+ /// This method handles the Secure Network beacon.
+ /// It will set the IV Index and IV Update Active flag and change the Key Refresh Phase based on the
+ /// information specified in the beacon.
///
- /// - parameter secureNetworkBeacon: The Secure Network Beacon received.
+ /// - parameter secureNetworkBeacon: The Secure Network beacon received.
func handle(secureNetworkBeacon: SecureNetworkBeacon) {
+ /// The Network Key the Secure Network Beacon was encrypted with.
let networkKey = secureNetworkBeacon.networkKey
- // The IV Index in the beacon must be greater or equal to the current one.
- guard secureNetworkBeacon.ivIndex >= networkKey.ivIndex.index else {
- logger?.w(.network, "Discarding beacon (ivIndex: \(secureNetworkBeacon.ivIndex), expected >= \(networkKey.ivIndex.index))")
+ // The library does not retransmit Secure Network Beacon.
+ // If this node is a member of a primary subnet and receives a Secure Network
+ // beacon on a secondary subnet, it will disregard it.
+ if let _ = meshNetwork.networkKeys.primaryKey, networkKey.isSecondary {
+ logger?.w(.network, "Discarding beacon for secondary network (key index: \(networkKey.index))")
return
}
- networkKey.ivIndex = IvIndex(index: secureNetworkBeacon.ivIndex,
- updateActive: secureNetworkBeacon.ivUpdateActive)
- // If the Key Refresh Procedure is in progress, and the new Network Key
- // has already been set, the key erfresh flag indicates switching to phase 2.
- if case .distributingKeys = networkKey.phase, secureNetworkBeacon.keyRefreshFlag {
- networkKey.phase = .finalizing
- }
- // If the Key Refresh Procedure is in phase 2, and the key refresh flag is
- // set to false.
- if case .finalizing = networkKey.phase, !secureNetworkBeacon.keyRefreshFlag {
- networkKey.oldKey = nil // This will set the phase to .normalOperation.
- }
- updateProxyFilter(usingNetworkKey: networkKey)
+ // Get the last IV Index.
+ //
+ // Note: Before version 2.2.2 the last IV Index was not stored.
+ // Instead IV Index was set to 0
+ let map = defaults.object(forKey: IvIndex.indexKey) as? [String : Any]
+ /// The last used IV Index for this mesh network.
+ let lastIVIndex = IvIndex.fromMap(map) ?? IvIndex()
+ /// The date of the last change of IV Index or IV Update Flag.
+ let lastTransitionDate = defaults.object(forKey: IvIndex.timestampKey) as? Date
+ /// A flag whether the IV has recently been updated using IV Recovery procedure.
+ /// The at-least-96h requirement for the duration of the current state will not apply.
+ /// The node shall not execute more than one IV Index Recovery within a period of 192 hours.
+ let isIvRecoveryActive = defaults.bool(forKey: IvIndex.ivRecoveryKey)
+ /// The test mode disables the 96h rule, leaving all other behavior unchanged.
+ let isIvTestModeActive = networkManager.manager.ivUpdateTestMode
+ // Ensure, that the received Secure Network Beacon can overwrite current
+ // IV Index.
+ let flag = networkManager.manager.allowIvIndexRecoveryOver42
+ if secureNetworkBeacon.canOverwrite(ivIndex: lastIVIndex,
+ updatedAt: lastTransitionDate,
+ withIvRecovery: isIvRecoveryActive,
+ testMode: isIvTestModeActive,
+ andUnlimitedIvRecoveryAllowed: flag) {
+ // Update the IV Index based on the information from the Secure Network Beacon.
+ meshNetwork.ivIndex = secureNetworkBeacon.ivIndex
+
+ if meshNetwork.ivIndex > lastIVIndex {
+ logger?.i(.network, "Applying \(meshNetwork.ivIndex)")
+ }
+ // If the IV Index used for transmitting messages effectively increased,
+ // the Node shall reset the sequence number to 0x000000.
+ //
+ // Note: This library keeps seperate sequence numbers for each Element of the
+ // local provisioner (source Unicast Address). All of them need to be reset.
+ if meshNetwork.ivIndex.transmitIndex > lastIVIndex.transmitIndex {
+ logger?.i(.network, "Resetting local sequence numbers to 0")
+ meshNetwork.localProvisioner?.node?.elements.forEach { element in
+ defaults.set(0, forKey: "S\(element.unicastAddress.hex)")
+ }
+ }
+
+ // Store the last IV Index.
+ defaults.set(meshNetwork.ivIndex.asMap, forKey: IvIndex.indexKey)
+ if lastIVIndex != meshNetwork.ivIndex ||
+ defaults.object(forKey: IvIndex.timestampKey) == nil {
+ defaults.set(Date(), forKey: IvIndex.timestampKey)
+
+ let ivRecovery = meshNetwork.ivIndex.index > lastIVIndex.index + 1 &&
+ secureNetworkBeacon.ivIndex.updateActive == false
+ defaults.set(ivRecovery, forKey: IvIndex.ivRecoveryKey)
+ }
+
+ // If the Key Refresh Procedure is in progress, and the new Network Key
+ // has already been set, the key refresh flag indicates switching to phase 2.
+ if case .distributingKeys = networkKey.phase, secureNetworkBeacon.keyRefreshFlag {
+ networkKey.phase = .finalizing
+ }
+ // If the Key Refresh Procedure is in phase 2, and the key refresh flag is
+ // set to false.
+ if case .finalizing = networkKey.phase, !secureNetworkBeacon.keyRefreshFlag {
+ networkKey.oldKey = nil // This will set the phase to .normalOperation.
+ }
+ } else if secureNetworkBeacon.ivIndex != lastIVIndex.previous {
+ var numberOfHoursSinceDate = "unknown time"
+ if let date = lastTransitionDate {
+ numberOfHoursSinceDate = "\(Int(-date.timeIntervalSinceNow / 3600))h"
+ }
+ logger?.w(.network, "Discarding beacon (\(secureNetworkBeacon.ivIndex), last \(lastIVIndex), changed: \(numberOfHoursSinceDate) ago, test mode: \(networkManager.manager.ivUpdateTestMode)))")
+ return
+ } // else,
+ // the Secure Network beacon was sent by a Node with a previous IV Index,
+ // that has not yet transitioned to the one local Node has. Such IV Index
+ // is still valid, at least for some time.
+
+ updateProxyFilter(usingNetworkKey: networkKey,
+ afterIvIndexChanged: lastIVIndex != meshNetwork.ivIndex)
}
/// Updates the information about the Network Key known to the current Proxy Server.
/// The Network Key is required to send Proxy Configuration Messages that can be
/// decoded by the connected Proxy.
///
- /// If the method detects that the Proxy has just been connected, or was reconnected,
- /// it will initiate the Proxy Filter with local Provisioner's Unicast Address and
- /// the `Address.allNodes` group address.
+ /// This method also initiates the Proxy Filter with local Provisioner's Unicast Address and
+ /// the `Address.allNodes` group address if a new connection has been found.
///
- /// - parameter networkKey: The Network Key known to the connected Proxy.
- func updateProxyFilter(usingNetworkKey networkKey: NetworkKey) {
+ /// - parameters:
+ /// - networkKey: The Network Key known to the connected Proxy.
+ /// - ivIndexChanged: True, if IV Index has changed, false otherwise.
+ func updateProxyFilter(usingNetworkKey networkKey: NetworkKey,
+ afterIvIndexChanged ivIndexChanged: Bool) {
let justConnected = proxyNetworkKey == nil
- let reconnected = networkKey == proxyNetworkKey
// Keep the primary Network Key or the most recently received one from the connected
// Proxy Server. This is to make sure (almost) that the Proxy Configuration messages
// are sent encrypted with a key known to this Node.
- if justConnected || networkKey.isPrimary || proxyNetworkKey?.isPrimary == false {
- proxyNetworkKey = networkKey
- }
+ proxyNetworkKey = networkKey
- if justConnected || reconnected {
+ if justConnected || !ivIndexChanged {
networkManager.manager.proxyFilter?.newProxyDidConnect()
}
}
@@ -341,3 +416,8 @@ private extension NetworkLayer {
}
}
+
+private extension IvIndex {
+ static let timestampKey = "IVTimestamp"
+ static let ivRecoveryKey = "IVRecovery"
+}
diff --git a/nRFMeshProvision/Classes/Layers/Network Layer/NetworkPdu.swift b/nRFMeshProvision/Classes/Layers/Network Layer/NetworkPdu.swift
index b83b93825..dc99342f0 100644
--- a/nRFMeshProvision/Classes/Layers/Network Layer/NetworkPdu.swift
+++ b/nRFMeshProvision/Classes/Layers/Network Layer/NetworkPdu.swift
@@ -35,6 +35,8 @@ internal struct NetworkPdu {
let pdu: Data
/// The Network Key used to decode/encode the PDU.
let networkKey: NetworkKey
+ /// The IV Index used to decode/encode the PDU.
+ let ivIndex: UInt32
/// Least significant bit of IV Index.
let ivi: UInt8
@@ -57,12 +59,15 @@ internal struct NetworkPdu {
/// Creates Network PDU object from received PDU. The initiator tries
/// to deobfuscate and decrypt the data using given Network Key and IV Index.
///
- /// - parameter pdu: The data received from mesh network.
- /// - parameter pduType: The type of the PDU: `.networkPdu` of `.proxyConfiguration`.
- /// - parameter networkKey: The Network Key to decrypt the PDU.
+ /// - parameters:
+ /// - pdu: The data received from mesh network.
+ /// - pduType: The type of the PDU: `.networkPdu` of `.proxyConfiguration`.
+ /// - networkKey: The Network Key to decrypt the PDU.
+ /// - ivIndex: The current IV Index.
/// - returns: The deobfuscated and decided Network PDU object, or `nil`,
/// if the key or IV Index don't match.
- init?(decode pdu: Data, ofType pduType: PduType, usingNetworkKey networkKey: NetworkKey) {
+ init?(decode pdu: Data, ofType pduType: PduType,
+ usingNetworkKey networkKey: NetworkKey, andIvIndex ivIndex: IvIndex) {
guard pduType == .networkPdu || pduType == .proxyConfiguration else {
return nil
}
@@ -92,21 +97,16 @@ internal struct NetworkPdu {
}
// IVI should match the LSB bit of current IV Index.
- // If it doesn't, and the IV Update procedure is active, the PDU will be
- // deobfuscated and decoded with IV Index decremented by 1.
- var index = networkKey.ivIndex.index
- if ivi != index & 0x1 {
- if networkKey.ivIndex.updateActive && index > 1 {
- index -= 1
- } else {
- return nil
- }
- }
+ // If it doesn't, the PDU will be deobfuscated and decoded with IV Index
+ // decremented by 1.
+ // See: Bluetooth Mesh Profile 1.0.1 Specification, chapter: 3.10.5.
+ self.ivIndex = ivIndex.index(for: ivi)
let helper = OpenSSLHelper()
for keys in keySets {
// Deobfuscate CTL, TTL, SEQ and SRC.
- let deobfuscatedData = helper.deobfuscate(pdu, ivIndex: index, privacyKey: keys.privacyKey)!
+ let deobfuscatedData = helper.deobfuscate(pdu, ivIndex: self.ivIndex,
+ privacyKey: keys.privacyKey)!
// First validation: Control Messages have NetMIC of size 64 bits.
let ctl = deobfuscatedData[0] >> 7
@@ -117,14 +117,20 @@ internal struct NetworkPdu {
let type = LowerTransportPduType(rawValue: ctl)!
let ttl = deobfuscatedData[0] & 0x7F
// Multiple octet values use Big Endian.
- let sequence = UInt32(deobfuscatedData[1]) << 16 | UInt32(deobfuscatedData[2]) << 8 | UInt32(deobfuscatedData[3])
- let source = Address(deobfuscatedData[4]) << 8 | Address(deobfuscatedData[5])
+ let sequence = UInt32(deobfuscatedData[1]) << 16
+ | UInt32(deobfuscatedData[2]) << 8
+ | UInt32(deobfuscatedData[3])
+ let source = Address(deobfuscatedData[4]) << 8
+ | Address(deobfuscatedData[5])
let micOffset = pdu.count - Int(type.netMicSize)
let destAndTransportPdu = pdu.subdata(in: 7.. NetworkPdu? {
for networkKey in meshNetwork.networkKeys {
- if let networkPdu = NetworkPdu(decode: pdu, ofType: type, usingNetworkKey: networkKey) {
+ if let networkPdu = NetworkPdu(decode: pdu, ofType: type,
+ usingNetworkKey: networkKey, andIvIndex: meshNetwork.ivIndex) {
return networkPdu
}
}
diff --git a/nRFMeshProvision/Classes/Layers/Network Layer/SecureNetworkBeacon.swift b/nRFMeshProvision/Classes/Layers/Network Layer/SecureNetworkBeacon.swift
index a32b56e88..260c3d2e7 100644
--- a/nRFMeshProvision/Classes/Layers/Network Layer/SecureNetworkBeacon.swift
+++ b/nRFMeshProvision/Classes/Layers/Network Layer/SecureNetworkBeacon.swift
@@ -34,7 +34,7 @@ internal struct SecureNetworkBeacon: BeaconPdu {
let pdu: Data
let beaconType: BeaconType = .secureNetwork
- /// The Network Key related to this Secure Network Beacon.
+ /// The Network Key related to this Secure Network beacon.
let networkKey: NetworkKey
/// Key Refresh flag value.
///
@@ -45,14 +45,12 @@ internal struct SecureNetworkBeacon: BeaconPdu {
/// and the new keys, and shall only receive Secure Network beacons
/// secured using the new Network Key.
let keyRefreshFlag: Bool
- /// This flag is set to `true` if IV Update procedure is active.
- let ivUpdateActive: Bool
+ /// The IV Index carried by this Secure Network beacon.
+ let ivIndex: IvIndex
/// Contains the value of the Network ID.
let networkId: Data
- /// Contains the current IV Index.
- let ivIndex: UInt32
- /// Creates USecure Network beacon PDU object from received PDU.
+ /// Creates Secure Network beacon PDU object from received PDU.
///
/// - parameter pdu: The data received from mesh network.
/// - parameter networkKey: The Network Key to validate the beacon.
@@ -63,9 +61,10 @@ internal struct SecureNetworkBeacon: BeaconPdu {
return nil
}
keyRefreshFlag = pdu[1] & 0x01 != 0
- ivUpdateActive = pdu[1] & 0x02 != 0
+ let updateActive = pdu[1] & 0x02 != 0
networkId = pdu.subdata(in: 2..<10)
- ivIndex = CFSwapInt32BigToHost(pdu.read(fromOffset: 10))
+ let index = CFSwapInt32BigToHost(pdu.read(fromOffset: 10))
+ ivIndex = IvIndex(index: index, updateActive: updateActive)
// Authenticate beacon using given Network Key.
let helper = OpenSSLHelper()
@@ -87,6 +86,94 @@ internal struct SecureNetworkBeacon: BeaconPdu {
}
}
+internal extension SecureNetworkBeacon {
+
+ /// This method returns whether the received Secure Network Beacon can override
+ /// the current IV Index.
+ ///
+ /// The following restrictions apply:
+ /// 1. Normal Operation state must last for at least 96 hours.
+ /// 2. IV Update In Progress state must take at least 96 hours and may not be longer than 144h.
+ /// 3. IV Index must not decrease.
+ /// 4. If received Secure Network Beacon has IV Index greater than current IV Index + 1, the
+ /// device will go into IV Index Recovery procedure. In this state, the 96h rule does not apply
+ /// and the IV Index or IV Update Active flag may change before 96 hours.
+ /// 5. If received Secure Network Beacon has IV Index greater than current IV Index + 42, the
+ /// beacon should be ignored (unless a setting in MeshNetworkManager is set to disable this rule).
+ /// 6. The node shall not execute more than one IV Index Recovery within a period of 192 hours.
+ ///
+ /// Note: Library versions before 2.2.2 did not store the last IV Index, so the date and IV Recovery
+ /// flag are optional.
+ ///
+ /// - parameters:
+ /// - target: The IV Index to compare.
+ /// - date: The date of the most recent transition to the current IV Index.
+ /// - ivRecoveryActive: True if the IV Recovery procedure was used to restore
+ /// the IV Index on the previous connection.
+ /// - ivTestMode: True, if IV Update test mode is enabled; false otherwise.
+ /// - ivRecoveryOver42Allowed: Whether the IV Index Recovery procedure should be limited
+ /// to allow maximum increase of IV Index by 42.
+ /// - returns: True, if the Secure Network beacon can be applied; false otherwise.
+ /// - since: 2.2.2
+ /// - seeAlso: Bluetooth Mesh Profile 1.0.1, section 3.10.5.
+ func canOverwrite(ivIndex target: IvIndex, updatedAt date: Date?,
+ withIvRecovery ivRecoveryActive: Bool,
+ testMode: Bool,
+ andUnlimitedIvRecoveryAllowed ivRecoveryOver42Allowed: Bool) -> Bool {
+ // IV Index must increase, or, in case it's equal to the current one,
+ // the IV Update Active flag must change from true to false.
+ // The new index must not be greater than the current one + 42,
+ // unless this rule is disabled.
+ guard (ivIndex.index > target.index &&
+ (ivRecoveryOver42Allowed || ivIndex.index <= target.index + 42)
+ ) ||
+ (ivIndex.index == target.index &&
+ (target.updateActive || !ivIndex.updateActive)
+ ) else {
+ return false
+ }
+ // Staring from version 2.2.2 the date will not be nil.
+ if let date = date {
+ // Let's define a "state" as a pair of IV and IV Update Active flag.
+ // "States" change as follows:
+ // 1. IV = X, IVUA = false (Normal Operation)
+ // 2. IV = X+1, IVUA = true (Update In Progress)
+ // 3. IV = X+1, IVUA = false (Normal Operation)
+ // 4. IV = X+2, IVUA = true (Update In Progress)
+ // 5. ...
+
+ // Calculate number of states between the state defined by the target
+ // IV Index and this Secure Network Beacon.
+ let stateDiff = Int(ivIndex.index - target.index) * 2 - 1
+ + (target.updateActive ? 1 : 0)
+ + (ivIndex.updateActive ? 0 : 1)
+ - (ivRecoveryActive || testMode ? 1 : 0) // this may set stateDiff = -1
+
+ // Each "state" must last for at least 96 hours.
+ // Calculate the minimum number of hours that had to pass since last state
+ // change for the Secure Network Beacon to be assumed valid.
+ // If more has passed, it's also valid, as Normal Operation has no maximum
+ // time duration.
+ let numberOfHoursRequired = stateDiff * 96
+
+ // Get the number of hours since the state changed last time.
+ let numberOfHoursSinceDate = Int(-date.timeIntervalSinceNow / 3600)
+
+ // The node shall not execute more than one IV Index Recovery within a
+ // period of 192 hours.
+ if ivRecoveryActive && stateDiff > 1 && numberOfHoursSinceDate < 192 {
+ return false
+ }
+
+ return numberOfHoursSinceDate >= numberOfHoursRequired
+ }
+ // Before version 2.2.2 the timestamp was not stored. The initial
+ // Secure Network Beacon is assumed to be valid.
+ return true
+ }
+
+}
+
internal extension SecureNetworkBeacon {
/// This method goes over all Network Keys in the mesh network and tries
@@ -118,7 +205,7 @@ internal extension SecureNetworkBeacon {
extension SecureNetworkBeacon: CustomDebugStringConvertible {
var debugDescription: String {
- return "Secure Network Beacon (Network ID: \(networkId.hex), IV Index: \(ivIndex), Key Refresh Flag: \(keyRefreshFlag), IV Update active: \(ivUpdateActive))"
+ return "Secure Network beacon (Network ID: \(networkId.hex), \(ivIndex), Key Refresh Flag: \(keyRefreshFlag))"
}
}
diff --git a/nRFMeshProvision/Classes/Layers/Network Layer/UnprovisionedDeviceBeacon.swift b/nRFMeshProvision/Classes/Layers/Network Layer/UnprovisionedDeviceBeacon.swift
index 91fb3acc7..6e6a3680e 100644
--- a/nRFMeshProvision/Classes/Layers/Network Layer/UnprovisionedDeviceBeacon.swift
+++ b/nRFMeshProvision/Classes/Layers/Network Layer/UnprovisionedDeviceBeacon.swift
@@ -90,7 +90,7 @@ internal extension UnprovisionedDeviceBeacon {
extension UnprovisionedDeviceBeacon: CustomDebugStringConvertible {
var debugDescription: String {
- return "Unprovisioned Device Beacon (UUID: \(deviceUuid.uuidString), OOB Info: \(oob), URI hash: \(uriHash?.hex ?? "None"))"
+ return "Unprovisioned Device beacon (UUID: \(deviceUuid.uuidString), OOB Info: \(oob), URI hash: \(uriHash?.hex ?? "None"))"
}
}
diff --git a/nRFMeshProvision/Classes/Layers/Upper Transport Layer/UpperTransportLayer.swift b/nRFMeshProvision/Classes/Layers/Upper Transport Layer/UpperTransportLayer.swift
index 51bfa5610..6addd6d73 100644
--- a/nRFMeshProvision/Classes/Layers/Upper Transport Layer/UpperTransportLayer.swift
+++ b/nRFMeshProvision/Classes/Layers/Upper Transport Layer/UpperTransportLayer.swift
@@ -100,7 +100,8 @@ internal class UpperTransportLayer {
let networkKey = keySet.networkKey
let pdu = UpperTransportPdu(fromAccessPdu: accessPdu,
- usingKeySet: keySet, sequence: sequence)
+ usingKeySet: keySet, sequence: sequence,
+ andIvIndex: meshNetwork.ivIndex)
logger?.i(.upperTransport, "Sending \(pdu) encrypted using key: \(keySet)")
diff --git a/nRFMeshProvision/Classes/Layers/Upper Transport Layer/UpperTransportPdu.swift b/nRFMeshProvision/Classes/Layers/Upper Transport Layer/UpperTransportPdu.swift
index ee524c432..4630b38f1 100644
--- a/nRFMeshProvision/Classes/Layers/Upper Transport Layer/UpperTransportPdu.swift
+++ b/nRFMeshProvision/Classes/Layers/Upper Transport Layer/UpperTransportPdu.swift
@@ -48,6 +48,8 @@ internal struct UpperTransportPdu {
let aid: UInt8?
/// The sequence number used to encode this message.
let sequence: UInt32
+ /// The IV Index used to encode this message.
+ let ivIndex: UInt32
/// The size of Transport MIC: 4 or 8 bytes.
let transportMicSize: UInt8
/// The Access Layer data.
@@ -73,7 +75,7 @@ internal struct UpperTransportPdu {
let nonce = Data([type, aszmic << 7]) + seq
+ accessMessage.source.bigEndian
+ accessMessage.destination.bigEndian
- + accessMessage.networkKey.ivIndex.index.bigEndian
+ + accessMessage.ivIndex.bigEndian
guard let decryptedData = OpenSSLHelper().calculateDecryptedCCM(encryptedData,
withKey: key, nonce: nonce, andMIC: mic,
@@ -87,19 +89,21 @@ internal struct UpperTransportPdu {
transportPdu = accessMessage.upperTransportPdu
accessPdu = decryptedData
sequence = accessMessage.sequence
+ ivIndex = accessMessage.ivIndex
message = nil
localElement = nil
userInitiated = false
}
- init(fromAccessPdu pdu: AccessPdu,
- usingKeySet keySet: KeySet, sequence: UInt32) {
+ init(fromAccessPdu pdu: AccessPdu, usingKeySet keySet: KeySet,
+ sequence: UInt32, andIvIndex ivIndex: IvIndex) {
self.message = pdu.message
self.localElement = pdu.localElement
self.userInitiated = pdu.userInitiated
self.source = pdu.localElement!.unicastAddress
self.destination = pdu.destination.address
self.sequence = sequence
+ self.ivIndex = ivIndex.transmitIndex
let accessPdu = pdu.accessPdu
self.accessPdu = accessPdu
self.aid = keySet.aid
@@ -114,11 +118,10 @@ internal struct UpperTransportPdu {
// SEQ is 24-bit value, in Big Endian.
let seq = (Data() + sequence.bigEndian).dropFirst()
- let ivIndex = keySet.networkKey.ivIndex
let nonce = Data([type, aszmic << 7]) + seq
- + source.bigEndian
- + destination.bigEndian
- + ivIndex.index.bigEndian
+ + self.source.bigEndian
+ + self.destination.bigEndian
+ + self.ivIndex.bigEndian
self.transportMicSize = aszmic == 0 ? 4 : 8
self.transportPdu = OpenSSLHelper().calculateCCM(accessPdu, withKey: keySet.accessKey, nonce: nonce,
@@ -126,7 +129,7 @@ internal struct UpperTransportPdu {
withAdditionalData: pdu.destination.virtualLabel?.data)
}
- /// This method tries to decode teh Access Message using a matching Application Key
+ /// This method tries to decode the Access Message using a matching Application Key
/// or the node's Device Key, based onthe `aid` field value.
///
/// - parameter accessMessage: The Lower Transport Layer Access Message received.
diff --git a/nRFMeshProvision/Classes/Legacy/MeshState.swift b/nRFMeshProvision/Classes/Legacy/MeshState.swift
index a021ef3b5..caca159d9 100644
--- a/nRFMeshProvision/Classes/Legacy/MeshState.swift
+++ b/nRFMeshProvision/Classes/Legacy/MeshState.swift
@@ -48,7 +48,13 @@ internal struct MeshState: Codable {
private let appKeys : [[String: Data]]
var provisioner: Provisioner {
- return Provisioner(name: UIDevice.current.name)
+ #if os(OSX)
+ let provisionerName = Host.current().localizedName ?? "OSX"
+ #else
+ let provisionerName = UIDevice.current.name
+ #endif
+
+ return Provisioner(name: provisionerName)
}
var provisionerUnicastAddress: Address {
@@ -59,11 +65,14 @@ internal struct MeshState: Codable {
return globalTTL[0]
}
+ var ivIndex: IvIndex {
+ return IvIndex(index: IVIndex.asUInt32,
+ updateActive: flags[0] & 0x40 == 0x40)
+ }
+
var networkKey: NetworkKey {
let index: KeyIndex = keyIndex.asUInt16 & 0x0FFF
let networkKey = try! NetworkKey(name: "Primary Network Key", index: index, key: netKey)
- networkKey.ivIndex.index = IVIndex.asUInt32
- networkKey.ivIndex.updateActive = flags[0] & 0x40 == 0x40
networkKey.phase = flags[0] & 0x80 == 0x80 ? .finalizing : .normalOperation
return networkKey
}
diff --git a/nRFMeshProvision/Classes/Mesh API/MeshNetwork+Address.swift b/nRFMeshProvision/Classes/Mesh API/MeshNetwork+Address.swift
index 86a76db5c..45784b011 100644
--- a/nRFMeshProvision/Classes/Mesh API/MeshNetwork+Address.swift
+++ b/nRFMeshProvision/Classes/Mesh API/MeshNetwork+Address.swift
@@ -57,18 +57,38 @@ public extension MeshNetwork {
!otherNodes.contains { $0.overlapsWithAddress(address, elementsCount: count) }
}
+ /// Returns the next available Unicast Address from the Provisioner's range
+ /// that can be assigned to a new node with 1 element. The element will be
+ /// identified by the returned address.
+ ///
+ /// - parameters:
+ /// - offset: Minimum Unicast Address to be assigned.
+ /// - provisioner: The Provisioner that is creating the node.
+ /// The address will be taken from it's allocated range.
+ /// - returns: The next available Unicast Address that can be assigned to a node,
+ /// or `nil`, if there are no more available addresses in the allocated range.
+ func nextAvailableUnicastAddress(startingFrom offset: Address = Address.minUnicastAddress,
+ using provisioner: Provisioner) -> Address? {
+ return nextAvailableUnicastAddress(startingFrom: offset, for: 1,
+ elementsUsing: provisioner)
+ }
+
/// Returns the next available Unicast Address from the Provisioner's range
/// that can be assigned to a new node with given number of elements.
/// The 0'th element is identified by the node's Unicast Address.
/// Each following element is identified by a subsequent Unicast Address.
///
- /// - parameter elementsCount: The number of node's elements. Each element will be
- /// identified by a subsequent Unicast Address.
- /// - parameter provisioner: The Provisioner that is creating the node.
- /// The address will be taken from it's allocated range.
+ /// - parameters:
+ /// - offset: Minimum Unicast Address to be assigned.
+ /// - elementsCount: The number of Node's elements. Each element will be
+ /// identified by a subsequent Unicast Address.
+ /// - provisioner: The Provisioner that is creating the node.
+ /// The address will be taken from it's allocated range.
/// - returns: The next available Unicast Address that can be assigned to a node,
/// or `nil`, if there are no more available addresses in the allocated range.
- func nextAvailableUnicastAddress(for elementsCount: UInt8, elementsUsing provisioner: Provisioner) -> Address? {
+ func nextAvailableUnicastAddress(startingFrom offset: Address = Address.minUnicastAddress,
+ for elementsCount: UInt8,
+ elementsUsing provisioner: Provisioner) -> Address? {
let sortedNodes = nodes.sorted { $0.unicastAddress < $1.unicastAddress }
// Iterate through all nodes just once, while iterating over ranges.
@@ -77,6 +97,10 @@ public extension MeshNetwork {
// Start from the beginning of the current range.
var address = range.lowAddress
+ if range.contains(offset) && address < offset {
+ address = offset
+ }
+
// Iterate through nodes that weren't checked yet.
let currentIndex = index
for _ in currentIndex.. AddressRange? {
+ func nextAvailableUnicastAddressRange(ofSize size: UInt16 = Address.maxUnicastAddress - Address.minUnicastAddress + 1) -> AddressRange? {
let allRangesSorted: [AddressRange] = provisioners
.reduce([], { ranges, next in ranges + next.allocatedUnicastRange })
.sorted { $0.lowerBound < $1.lowerBound }
@@ -195,7 +195,7 @@ public extension MeshNetwork {
/// - parameter size: The preferred and maximum size of a range to find.
/// - returns: The range of given size, a smaller one if such is not available
/// or `nil` if all addresses are alread allocated.
- func nextAvailableGroupAddressRange(ofSize size: UInt16 = Address.maxGroupAddress) -> AddressRange? {
+ func nextAvailableGroupAddressRange(ofSize size: UInt16 = Address.maxGroupAddress - Address.minGroupAddress + 1) -> AddressRange? {
let allRangesSorted: [AddressRange] = provisioners
.reduce([], { ranges, next in ranges + next.allocatedGroupRange })
.sorted { $0.lowerBound < $1.lowerBound }
@@ -215,7 +215,7 @@ public extension MeshNetwork {
/// - parameter size: The preferred and maximum size of a range to find.
/// - returns: The range of given size, a smaller one if such is not available
/// or `nil` if all scenes are alread allocated.
- func nextAvailableSceneRange(ofSize size: UInt16 = Scene.minScene) -> SceneRange? {
+ func nextAvailableSceneRange(ofSize size: UInt16 = Scene.maxScene - Scene.minScene + 1) -> SceneRange? {
let allRangesSorted: [SceneRange] = provisioners
.reduce([], { ranges, next in ranges + next.allocatedSceneRange })
.sorted { $0.lowerBound < $1.lowerBound }
@@ -239,6 +239,10 @@ public extension MeshNetwork {
/// or `nil` if all addresses are alread allocated.
private func nextAvailableRange(ofSize size: UInt16, in bounds: ClosedRange,
among ranges: [RangeObject]) -> RangeObject? {
+ guard size > 0 else {
+ return nil
+ }
+
var bestRange: RangeObject? = nil
var lastUpperBound: Address = bounds.lowerBound - 1
@@ -250,18 +254,20 @@ public extension MeshNetwork {
}
// If the space exists, but it's not as big as requested, compare
// it with the best range so far and replace if it's bigger.
- if range.lowerBound - lastUpperBound > 1 {
- let newRange = RangeObject(lastUpperBound + 1...range.lowerBound - 1)
+ let availableSize = range.lowerBound - lastUpperBound - 1
+ if availableSize > 0 {
+ let newRange = RangeObject(lastUpperBound + 1...lastUpperBound + availableSize)
if bestRange == nil || newRange.count > bestRange!.count {
bestRange = newRange
}
}
lastUpperBound = range.upperBound
}
-
// If if we didn't return earlier, check after the last range.
- if UInt32(lastUpperBound) + UInt32(size) < bounds.upperBound {
- return RangeObject(lastUpperBound + 1...lastUpperBound + size - 1)
+ let availableSize = bounds.upperBound - lastUpperBound
+ let bestSize = UInt16(bestRange?.count ?? 0)
+ if availableSize > bestSize {
+ return RangeObject(lastUpperBound + 1...lastUpperBound + min(size, availableSize))
}
// The gap of requested size hasn't been found. Return the best found.
diff --git a/nRFMeshProvision/Classes/Mesh API/NetworkKey+MeshNetwork.swift b/nRFMeshProvision/Classes/Mesh API/NetworkKey+MeshNetwork.swift
index e6b11ebb5..faa5f1e4e 100644
--- a/nRFMeshProvision/Classes/Mesh API/NetworkKey+MeshNetwork.swift
+++ b/nRFMeshProvision/Classes/Mesh API/NetworkKey+MeshNetwork.swift
@@ -40,6 +40,12 @@ public extension NetworkKey {
return index == 0
}
+ /// Returns whether the Network Key is a secondary Network Key,
+ /// that is the Key Index is NOT equal to 0.
+ var isSecondary: Bool {
+ return !isPrimary
+ }
+
/// Return whether the Network Key is used in the given mesh network.
///
/// A `true` is returned when the Network Key is added to Network Keys
diff --git a/nRFMeshProvision/Classes/Mesh API/NetworkKeys.swift b/nRFMeshProvision/Classes/Mesh API/NetworkKeys.swift
index 097527f0e..e552a86c0 100644
--- a/nRFMeshProvision/Classes/Mesh API/NetworkKeys.swift
+++ b/nRFMeshProvision/Classes/Mesh API/NetworkKeys.swift
@@ -44,6 +44,12 @@ public extension Array where Element == NetworkKey {
}
}
+ /// The primary Network Key, that is the one with key index 0.
+ /// If the primary Network Key is not known, it's set to `nil`.
+ var primaryKey: NetworkKey? {
+ return self[0]
+ }
+
/// Returns a new list of Network Keys containing all the Network Keys
/// of this list known to the given Node.
///
diff --git a/nRFMeshProvision/Classes/Mesh Messages/Proxy Configuration/AddAddressesToFilter.swift b/nRFMeshProvision/Classes/Mesh Messages/Proxy Configuration/AddAddressesToFilter.swift
index d2b14fd4b..7ead7bee5 100644
--- a/nRFMeshProvision/Classes/Mesh Messages/Proxy Configuration/AddAddressesToFilter.swift
+++ b/nRFMeshProvision/Classes/Mesh Messages/Proxy Configuration/AddAddressesToFilter.swift
@@ -36,7 +36,9 @@ public struct AddAddressesToFilter: StaticAcknowledgedProxyConfigurationMessage
public var parameters: Data? {
var data = Data()
- addresses.forEach { address in
+ // Send addresses sorted. The primary Element will be added as a first one,
+ // so if the Proxy Filter supports only one address, it will be that one.
+ addresses.sorted().forEach { address in
data += address.bigEndian
}
return data
diff --git a/nRFMeshProvision/Classes/Mesh Model/Element.swift b/nRFMeshProvision/Classes/Mesh Model/Element.swift
index e9c592b01..2424c54d0 100644
--- a/nRFMeshProvision/Classes/Mesh Model/Element.swift
+++ b/nRFMeshProvision/Classes/Mesh Model/Element.swift
@@ -134,7 +134,7 @@ public class Element: Codable {
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decodeIfPresent(String.self, forKey: .name)
- index = try container.decode(UInt8.self, forKey: .index)
+ index = try container.decode(UInt8.self, forKey: .index)
let locationAsString = try container.decode(String.self, forKey: .location)
guard let rawValue = UInt16(hex: locationAsString) else {
throw DecodingError.dataCorruptedError(forKey: .location, in: container,
diff --git a/nRFMeshProvision/Classes/Mesh Model/IvIndex.swift b/nRFMeshProvision/Classes/Mesh Model/IvIndex.swift
index 200ec7882..d5e0237e6 100644
--- a/nRFMeshProvision/Classes/Mesh Model/IvIndex.swift
+++ b/nRFMeshProvision/Classes/Mesh Model/IvIndex.swift
@@ -30,7 +30,96 @@
import Foundation
+/// The IV Index received with the last Secure Network Beacon and its
+/// current state.
+///
+/// Bluetooth Mesh Profile Specification 1.0.1, Chapter 3.10.5:
+///
+/// During the Normal Operation state, the IV Update Flag in the Secure Network
+/// beacon and in the Friend Update message shall be set to 0. When this state is
+/// active, a node shall transmit using the current IV Index and shall process
+/// messages from the current IV Index and also the current IV Index - 1.
+///
+/// During the IV Update in Progress state, the IV Update Flag in the Secure Network
+/// beacon and in the Friend Update message shall be set to 1. When this state is
+/// active, a node shall transmit using the current IV Index - 1 and shall process
+/// messages from the current IV Index - 1 and also the current IV Index.
internal struct IvIndex {
var index: UInt32 = 0
var updateActive: Bool = false
+
+ /// The IV Index used for transmitting messages.
+ var transmitIndex: UInt32 {
+ return updateActive && index > 0 ? index - 1 : index
+ }
+
+ /// The IV Index that is to be used for decrypting messages.
+ ///
+ /// - parameter ivi: The IVI bit of the received Network PDU.
+ /// - returns: The IV Index to be used to decrypt the message.
+ func index(for ivi: UInt8) -> UInt32 {
+ return ivi == index & 1 ? index : index - 1
+ }
+}
+
+internal extension IvIndex {
+ static let indexKey = "IVIndex"
+
+ /// Returns the IV Index as dictionary.
+ var asMap: [String : Any] {
+ return ["index": index, "updateActive": updateActive]
+ }
+
+ /// Creates the IV Index from the given dictionary. It must be valid, otherwise `nil` is returned.
+ ///
+ /// - parameter map: The dictionary with IV Index.
+ /// - returns: The IV Index object or `nil`.
+ static func fromMap(_ map: [String: Any]?) -> IvIndex? {
+ if let map = map,
+ let index = map["index"] as? UInt32,
+ let updateActive = map["updateActive"] as? Bool {
+ return IvIndex(index: index, updateActive: updateActive)
+ }
+ return nil
+ }
+
+}
+
+extension IvIndex: Comparable {
+
+ static func < (lhs: IvIndex, rhs: IvIndex) -> Bool {
+ return lhs.index < rhs.index ||
+ (lhs.index == rhs.index && lhs.updateActive && !rhs.updateActive)
+ }
+
+}
+
+extension IvIndex {
+
+ /// The following IV Index, or `nil` if maximum value has been reached.
+ var next: IvIndex? {
+ return updateActive ?
+ IvIndex(index: index, updateActive: false) :
+ index < UInt32.max - 1 ?
+ IvIndex(index: index + 1, updateActive: true) :
+ nil
+ }
+
+ /// The previous IV Index, or `nil` in case of an initial one.
+ var previous: IvIndex? {
+ return !updateActive ?
+ IvIndex(index: index, updateActive: true) :
+ index > 0 ?
+ IvIndex(index: index - 1, updateActive: false) :
+ nil
+ }
+
+}
+
+extension IvIndex: CustomDebugStringConvertible {
+
+ var debugDescription: String {
+ return "IV Index: \(index) (\(updateActive ? "update active" : "normal operation"))"
+ }
+
}
diff --git a/nRFMeshProvision/Classes/Mesh Model/MeshNetwork.swift b/nRFMeshProvision/Classes/Mesh Model/MeshNetwork.swift
index 019a4d4ac..362005cff 100644
--- a/nRFMeshProvision/Classes/Mesh Model/MeshNetwork.swift
+++ b/nRFMeshProvision/Classes/Mesh Model/MeshNetwork.swift
@@ -65,6 +65,9 @@ public class MeshNetwork: Codable {
/// An array of groups in teh network.
public internal(set) var groups: [Group]
+ /// The IV Index of the mesh network.
+ internal var ivIndex: IvIndex
+
/// An array of Elements of the local Provisioner.
private var _localElements: [Element]
/// An array of Elements of the local Provisioner.
@@ -120,6 +123,7 @@ public class MeshNetwork: Codable {
applicationKeys = []
nodes = []
groups = []
+ ivIndex = IvIndex()
_localElements = []
localElements = [ Element(location: .main) ]
}
@@ -154,7 +158,10 @@ public class MeshNetwork: Codable {
applicationKeys = try container.decode([ApplicationKey].self, forKey: .applicationKeys)
nodes = try container.decode([Node].self, forKey: .nodes)
groups = try container.decode([Group].self, forKey: .groups)
-
+ // The IV Index is not a shared in the JSON, as it may change.
+ // The value will be obtained from the Secure Network beacon moment after
+ // connecting to a Proxy node.
+ ivIndex = IvIndex()
_localElements = [.primaryElement]
provisioners.forEach {
diff --git a/nRFMeshProvision/Classes/Mesh Model/NetworkKey.swift b/nRFMeshProvision/Classes/Mesh Model/NetworkKey.swift
index 7a57c953f..cbeadc78f 100644
--- a/nRFMeshProvision/Classes/Mesh Model/NetworkKey.swift
+++ b/nRFMeshProvision/Classes/Mesh Model/NetworkKey.swift
@@ -108,8 +108,6 @@ public class NetworkKey: Key, Codable {
/// The Network ID derived from the old Network Key. This identifier
/// is public information. It is set when `oldKey` is set.
public private(set) var oldNetworkId: Data?
- /// The IV Index for this subnetwork.
- internal var ivIndex: IvIndex
/// Network Key derivaties.
internal private(set) var keys: NetworkKeyDerivaties!
/// Network Key derivaties.
@@ -136,10 +134,6 @@ public class NetworkKey: Key, Codable {
self.minSecurity = .high
self.timestamp = Date()
- // The IV Index is not a shared in the JSON, as it may change.
- // The current value will be obtained from the Security Beacon.
- ivIndex = IvIndex()
-
regenerateKeyDerivaties()
}
@@ -206,10 +200,6 @@ public class NetworkKey: Key, Codable {
minSecurity = try container.decode(Security.self, forKey: .minSecurity)
timestamp = try container.decode(Date.self, forKey: .timestamp)
- // The IV Index is not a shared in the JSON, as it may change.
- // The current value will be obtained from the Security Beacon.
- ivIndex = IvIndex()
-
regenerateKeyDerivaties()
}
diff --git a/nRFMeshProvision/Classes/Mesh Model/Node.swift b/nRFMeshProvision/Classes/Mesh Model/Node.swift
index a5cf638db..744085b2a 100644
--- a/nRFMeshProvision/Classes/Mesh Model/Node.swift
+++ b/nRFMeshProvision/Classes/Mesh Model/Node.swift
@@ -501,7 +501,9 @@ internal extension Node {
newModel.copy(from: oldModel)
// If at least one Model matches, assume the Element didn't
// change much and copy the name of it.
- newElement.name = oldElement.name
+ if let oldName = oldElement.name {
+ newElement.name = oldName
+ }
}
}
}
diff --git a/nRFMeshProvision/Classes/Mesh Model/NodeFeatures.swift b/nRFMeshProvision/Classes/Mesh Model/NodeFeatures.swift
index 17d2aa5d3..40ea85a7a 100644
--- a/nRFMeshProvision/Classes/Mesh Model/NodeFeatures.swift
+++ b/nRFMeshProvision/Classes/Mesh Model/NodeFeatures.swift
@@ -67,7 +67,10 @@ public class NodeFeatures: Codable {
case lowPower
}
- internal init(relay: NodeFeaturesState?, proxy: NodeFeaturesState?, friend: NodeFeaturesState?, lowPower: NodeFeaturesState?) {
+ internal init(relay: NodeFeaturesState?,
+ proxy: NodeFeaturesState?,
+ friend: NodeFeaturesState?,
+ lowPower: NodeFeaturesState?) {
self.relay = relay
self.proxy = proxy
self.friend = friend
diff --git a/nRFMeshProvision/Classes/Mesh Model/RangeObject.swift b/nRFMeshProvision/Classes/Mesh Model/RangeObject.swift
index a37280eca..388b541ae 100644
--- a/nRFMeshProvision/Classes/Mesh Model/RangeObject.swift
+++ b/nRFMeshProvision/Classes/Mesh Model/RangeObject.swift
@@ -34,11 +34,11 @@ public class RangeObject {
public private(set) var range: ClosedRange
- public var lowerBound: Address {
+ public var lowerBound: UInt16 {
return range.lowerBound
}
- public var upperBound: Address {
+ public var upperBound: UInt16 {
return range.upperBound
}
diff --git a/nRFMeshProvision/Classes/MeshNetworkManager.swift b/nRFMeshProvision/Classes/MeshNetworkManager.swift
index 8ade936ad..7a83bc8b4 100644
--- a/nRFMeshProvision/Classes/MeshNetworkManager.swift
+++ b/nRFMeshProvision/Classes/MeshNetworkManager.swift
@@ -111,6 +111,24 @@ public class MeshNetworkManager {
/// 50 * segment count. The TTL and segment count dependent parts are added
/// automatically, and this value shall specify only the constant part.
public var acknowledgmentMessageInterval: TimeInterval = 2.0
+ /// According to Bluetooth Mesh Profile 1.0.1, section 3.10.5, if the IV Index of the mesh
+ /// network increased by more than 42 since the last connection (which can take at least
+ /// 48 weeks), the Node should be reprovisioned. However, as this library can be used to
+ /// provision other Nodes, it should not be blocked from sending messages to the network
+ /// only because the phone wasn't connected to the network for that time. This flag can
+ /// disable this check, effectively allowing such connection.
+ ///
+ /// The same can be achieved by clearing the app data (uninstalling and reinstalling the
+ /// app) and importing the mesh network. With no "previous" IV Index, the library will
+ /// accept any IV Index received in the Secure Network beacon upon connection to the
+ /// GATT Proxy Node.
+ public var allowIvIndexRecoveryOver42: Bool = false
+ /// IV Update Test Mode enables efficient testing of the IV Update procedure.
+ /// The IV Update test mode removes the 96-hour limit; all other behavior of the device
+ /// are unchanged.
+ ///
+ /// - seeAlso: Bluetooth Mesh Profile 1.0.1, section 3.10.5.1.
+ public var ivUpdateTestMode: Bool = false
// MARK: - Computed properties
@@ -636,6 +654,62 @@ extension MeshNetworkManager: BearerDataDelegate {
}
+// MARK: - Managing sequence numbers.
+
+public extension MeshNetworkManager {
+
+ /// This method sets the next outgoing sequence number of the given Element on local Node.
+ /// This 24-bit number will be set in the next message sent by this Element. The sequence
+ /// number is increased by 1 every time the Element sends a message.
+ ///
+ /// Mind, that the sequence number is the least significant 24-bits of a SeqAuth, where
+ /// the 32 most significant bits are called IV Index. The sequence number resets to 0
+ /// when the device re-enters IV Index Normal Operation after 96-144 hours of being
+ /// in IV Index Update In Progress phase. The current IV Index is obtained from the
+ /// Secure Network beacon upon connection to Proxy Node. Setting too low sequence
+ /// number will effectively block the Element from sending messages to the network,
+ /// until it will increase enough not to be discarded by other nodes.
+ ///
+ /// - important: This method should not be used, unless you need to reuse the same
+ /// Provisioner's Unicast Address on another device, or a device where the
+ /// app was uninstalled and reinstalled. Even then the use of it is not recommended.
+ /// The sequence number is an internal parameter of the Element and is
+ /// managed automatically by the library. Instead, each device (phone) should
+ /// use a separate Provisioner with unique set of Unicast Addresses, which
+ /// should not change on export/import.
+ ///
+ /// - parameters:
+ /// - sequence: The new sequence number.
+ /// - element: The Element of a Node associated with the local Provisioner.
+ func setSequenceNumber(_ sequence: UInt32, forLocalElement element: Element) {
+ guard let meshNetwork = meshNetwork,
+ element.parentNode?.isLocalProvisioner == true,
+ let defaults = UserDefaults(suiteName: meshNetwork.uuid.uuidString) else {
+ return
+ }
+ defaults.set(sequence & 0x00FFFFFF, forKey: "S\(element.unicastAddress.hex)")
+ }
+
+ /// Returns the next sequence number that would be used by the given Element on local Node.
+ ///
+ /// - important: The sequence number is an internal parameter of an Element.
+ /// Apps should not use this method unless necessary. It is recommended
+ /// to create a new Provisioner with a unique Unicast Address instead.
+ ///
+ /// - parameter element: The local Element to get sequence number of.
+ /// - returns: The next sequence number, or `nil` if the Element does not belong
+ /// to the local Element or the mesh network does not exist.
+ func getSequenceNumber(ofLocalElement element: Element) -> UInt32? {
+ guard let meshNetwork = meshNetwork,
+ element.parentNode?.isLocalProvisioner == true,
+ let defaults = UserDefaults(suiteName: meshNetwork.uuid.uuidString) else {
+ return nil
+ }
+ return UInt32(defaults.integer(forKey: "S\(element.unicastAddress.hex)"))
+ }
+
+}
+
// MARK: - Save / Load
public extension MeshNetworkManager {
@@ -658,10 +732,17 @@ public extension MeshNetworkManager {
decoder.dateDecodingStrategy = .iso8601
meshData = try decoder.decode(MeshData.self, from: data)
- guard let _ = meshData.meshNetwork else {
+ guard let meshNetwork = meshData.meshNetwork else {
return false
}
+ // Restore the last IV Index. The last IV Index is stored since version 2.2.2.
+ if let defaults = UserDefaults(suiteName: meshNetwork.uuid.uuidString),
+ let map = defaults.object(forKey: IvIndex.indexKey) as? [String : Any],
+ let ivIndex = IvIndex.fromMap(map) {
+ meshNetwork.ivIndex = ivIndex
+ }
+
networkManager = NetworkManager(self)
proxyFilter = ProxyFilter(self)
return true
@@ -673,6 +754,7 @@ public extension MeshNetworkManager {
withAddress: legacyState.provisionerUnicastAddress)
let provisionerNode = network.localProvisioner!.node!
provisionerNode.defaultTTL = legacyState.provisionerDefaultTtl
+ network.ivIndex = legacyState.ivIndex
network.networkKeys.removeAll()
let networkKey = legacyState.networkKey
network.add(networkKey: networkKey)
@@ -751,6 +833,14 @@ public extension MeshNetworkManager {
let meshNetwork = try decoder.decode(MeshNetwork.self, from: data)
meshData.meshNetwork = meshNetwork
+
+ // Restore the last IV Index. The last IV Index is stored since version 2.2.2.
+ if let defaults = UserDefaults(suiteName: meshNetwork.uuid.uuidString),
+ let map = defaults.object(forKey: IvIndex.indexKey) as? [String : Any],
+ let ivIndex = IvIndex.fromMap(map) {
+ meshNetwork.ivIndex = ivIndex
+ }
+
networkManager = NetworkManager(self)
proxyFilter = ProxyFilter(self)
return meshNetwork
diff --git a/nRFMeshProvision/Classes/Provisioning/ProvisioningData.swift b/nRFMeshProvision/Classes/Provisioning/ProvisioningData.swift
index face178de..c2bf0a414 100644
--- a/nRFMeshProvision/Classes/Provisioning/ProvisioningData.swift
+++ b/nRFMeshProvision/Classes/Provisioning/ProvisioningData.swift
@@ -55,7 +55,7 @@ internal class ProvisioningData {
func prepare(for network: MeshNetwork, networkKey: NetworkKey, unicastAddress: Address) {
self.networkKey = networkKey
- self.ivIndex = networkKey.ivIndex
+ self.ivIndex = network.ivIndex
self.unicastAddress = unicastAddress
}
diff --git a/nRFMeshProvision/Classes/ProxyFilter.swift b/nRFMeshProvision/Classes/ProxyFilter.swift
index 838bfc34f..05c071e8a 100644
--- a/nRFMeshProvision/Classes/ProxyFilter.swift
+++ b/nRFMeshProvision/Classes/ProxyFilter.swift
@@ -88,6 +88,7 @@ public class ProxyFilter {
private var counter = 0
private var busy = false
private var buffer: [ProxyConfigurationMessage] = []
+ private let mutex = DispatchQueue(label: "ProxyFilterMutex")
private var logger: LoggerDelegate? {
return manager.logger
@@ -242,6 +243,9 @@ internal extension ProxyFilter {
/// its Unicast Addresses and All Nodes address.
func newProxyDidConnect() {
logger?.i(.proxy, "New Proxy connected")
+ mutex.sync {
+ busy = false
+ }
reset()
if let localProvisioner = manager.meshNetwork?.localProvisioner {
setup(for: localProvisioner)
@@ -254,17 +258,19 @@ internal extension ProxyFilter {
///
/// - parameter message: The message sent.
func managerDidDeliverMessage(_ message: ProxyConfigurationMessage) {
- switch message {
- case let request as AddAddressesToFilter:
- addresses.formUnion(request.addresses)
- case let request as RemoveAddressesFromFilter:
- addresses.subtract(request.addresses)
- case let request as SetFilterType:
- type = request.filterType
- addresses.removeAll()
- default:
- // Ignore.
- break
+ mutex.sync {
+ switch message {
+ case let request as AddAddressesToFilter:
+ addresses.formUnion(request.addresses)
+ case let request as RemoveAddressesFromFilter:
+ addresses.subtract(request.addresses)
+ case let request as SetFilterType:
+ type = request.filterType
+ addresses.removeAll()
+ default:
+ // Ignore.
+ break
+ }
}
// And notify the app.
delegate?.proxyFilterUpdated(type: type, addresses: addresses)
@@ -279,10 +285,12 @@ internal extension ProxyFilter {
/// - parameter message: The message that has not been sent.
/// - parameter error: The error received.
func managerFailedToDeliverMessage(_ message: ProxyConfigurationMessage, error: Error) {
- type = .whitelist
- addresses.removeAll()
- buffer.removeAll()
- busy = false
+ mutex.sync {
+ type = .whitelist
+ addresses.removeAll()
+ buffer.removeAll()
+ busy = false
+ }
// And notify the app.
delegate?.proxyFilterUpdated(type: type, addresses: addresses)
}
@@ -300,12 +308,15 @@ internal extension ProxyFilter {
switch message {
case let status as FilterStatus:
// Handle buffered messages.
- guard buffer.isEmpty else {
- let message = buffer.removeFirst()
+ let bufferEmpty = mutex.sync { buffer.isEmpty }
+ guard bufferEmpty else {
+ let message = mutex.sync { buffer.removeFirst() }
try? manager.send(message)
return
}
- busy = false
+ mutex.sync {
+ busy = false
+ }
// Ensure the current information about the filter is up to date.
guard type == status.filterType && addresses.count == status.listSize else {
@@ -331,7 +342,9 @@ internal extension ProxyFilter {
logger?.w(.proxy, "Limited Proxy Filter detected.")
reset()
if let address = manager.meshNetwork?.localProvisioner?.unicastAddress {
- addresses = [address]
+ mutex.sync {
+ addresses = [address]
+ }
add(addresses: addresses)
}
delegate?.limitedProxyFilterDetected(maxSize: 1)
@@ -362,15 +375,23 @@ private extension ProxyFilter {
///
/// - parameter message: The message to be sent.
func send(_ message: ProxyConfigurationMessage) {
- guard !busy else {
- buffer.append(message)
+ let wasBusy = mutex.sync { return busy }
+ guard !wasBusy else {
+ mutex.sync {
+ buffer.append(message)
+ }
return
}
- busy = true
+ mutex.sync {
+ busy = true
+ }
+
do {
try manager.send(message)
} catch {
- busy = false
+ mutex.sync {
+ busy = false
+ }
}
}
diff --git a/nRFMeshProvision/Classes/Type Extensions/Data.swift b/nRFMeshProvision/Classes/Type Extensions/Data.swift
index c194f3ff1..e05c2f840 100644
--- a/nRFMeshProvision/Classes/Type Extensions/Data.swift
+++ b/nRFMeshProvision/Classes/Type Extensions/Data.swift
@@ -66,7 +66,9 @@ extension DataConvertible {
static func + (lhs: Data, rhs: Self) -> Data {
var value = rhs
- let data = Data(buffer: UnsafeBufferPointer(start: &value, count: 1))
+ let data = withUnsafePointer(to: &value) { pointer -> Data in
+ return Data(buffer: UnsafeBufferPointer(start: pointer, count: 1))
+ }
return lhs + data
}