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 @@ - + - + @@ -778,7 +808,7 @@ - + - + @@ -823,7 +853,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 }