Skip to content

Commit

Permalink
Merge pull request #312 from NordicSemiconductor/improvement/defaults
Browse files Browse the repository at this point in the history
Sequence number related improvements
  • Loading branch information
philips77 authored Jan 25, 2021
2 parents 4af9269 + ccc9667 commit 0cb5931
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 32 deletions.
4 changes: 4 additions & 0 deletions Example/Pods/Pods.xcodeproj/project.pbxproj

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions Example/Tests/FastSending.swift
Original file line number Diff line number Diff line change
Expand Up @@ -363,9 +363,8 @@ class FastSending: XCTestCase, MeshNetworkDelegate {
}
if let network = manager.meshNetwork,
let defaults = UserDefaults(suiteName: network.uuid.uuidString) {
defaults.removeObject(forKey: "S0001") // Remove sender's Sequence number
defaults.removeObject(forKey: "P0001") // Remove receivers's previous Sequence number
defaults.removeObject(forKey: "0001") // Remove receivers's Sequence number
defaults.removeSequenceNumber(for: Address(0001))
defaults.removeSeqAuthValues(of: Address(0001))
}
} catch {
print(error)
Expand Down
25 changes: 21 additions & 4 deletions Example/Tests/ManagingProvisioners.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class ManagingProvisioners: XCTestCase {
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: "New provisioner",
let provisioner = Provisioner(name: "Main provisioner",
allocatedUnicastRange: [
AddressRange(8...38),
AddressRange(50...80)
Expand All @@ -67,9 +67,26 @@ class ManagingProvisioners: XCTestCase {
XCTAssertEqual(meshNetwork.nodes[6].unicastAddress, 11)
XCTAssertEqual(meshNetwork.nodes[6].lastUnicastAddress, 11)

meshNetwork.remove(provisioner: provisioner)
XCTAssertEqual(meshNetwork.provisioners.count, 0)
XCTAssertEqual(meshNetwork.nodes.count, 6)
// This will throw, as it's not possible to remove the last Provisioner object.
XCTAssertThrowsError(try meshNetwork.remove(provisioner: provisioner))
XCTAssertEqual(meshNetwork.provisioners.count, 1)
XCTAssertEqual(meshNetwork.nodes.count, 7)

let otherProvisioner = Provisioner(name: "New provisioner",
allocatedUnicastRange: [
AddressRange(100...200)
],
allocatedGroupRange: [],
allocatedSceneRange: [])
XCTAssertNoThrow(try meshNetwork.add(provisioner: otherProvisioner))
XCTAssertEqual(meshNetwork.provisioners.count, 2)
XCTAssertEqual(meshNetwork.nodes.count, 8)
XCTAssertEqual(meshNetwork.nodes[7].unicastAddress, 100)
XCTAssertEqual(meshNetwork.nodes[7].lastUnicastAddress, 100)

XCTAssertNoThrow(try meshNetwork.remove(provisioner: otherProvisioner))
XCTAssertEqual(meshNetwork.provisioners.count, 1)
XCTAssertEqual(meshNetwork.nodes.count, 7)
}

func testAddProvisioner_missingRanges() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ private extension LowerTransportLayer {
let sequence = networkPdu.messageSequence
let receivedSeqAuth = (UInt64(networkPdu.ivIndex) << 24) | UInt64(sequence)

if let localSeqAuth = defaults.object(forKey: networkPdu.source.hex) as? NSNumber {
if let localSeqAuth = defaults.lastSeqAuthValue(for: networkPdu.source) {
// 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
Expand Down Expand Up @@ -351,30 +351,30 @@ private extension LowerTransportLayer {
// received in the correct order. As a workaround, the queue may be set to
// a serial one in MeshNetworkManager initializer.
var missed = false
if let previousSeqAuth = defaults.object(forKey: "P\(networkPdu.source.hex)") as? NSNumber {
missed = receivedSeqAuth < localSeqAuth.uint64Value &&
receivedSeqAuth > previousSeqAuth.uint64Value
if let previousSeqAuth = defaults.previousSeqAuthValue(for: networkPdu.source) {
missed = receivedSeqAuth < localSeqAuth &&
receivedSeqAuth > previousSeqAuth
}

// Validate.
guard receivedSeqAuth > localSeqAuth.uint64Value || missed ||
(reassemblyInProgress && receivedSeqAuth == localSeqAuth.uint64Value) else {
guard receivedSeqAuth > localSeqAuth || missed ||
(reassemblyInProgress && receivedSeqAuth == localSeqAuth) else {
// Ignore that message.
logger?.w(.lowerTransport, "Discarding packet (seqAuth: \(receivedSeqAuth), expected > \(localSeqAuth))")
return false
}

// The message is valid. Remember the previous SeqAuth.
let newPreviousSeqAuth = min(receivedSeqAuth, localSeqAuth.uint64Value)
defaults.set(newPreviousSeqAuth, forKey: "P\(networkPdu.source.hex)")
let newPreviousSeqAuth = min(receivedSeqAuth, localSeqAuth)
defaults.storePreviousSeqAuthValue(newPreviousSeqAuth, for: networkPdu.source)

// If the message was processed after its successor, don't overwrite the last SeqAuth.
if missed {
return true
}
}
// SeqAuth is valid, save the new sequence authentication value.
defaults.set(NSNumber(value: receivedSeqAuth), forKey: networkPdu.source.hex)
defaults.storeLastSeqAuthValue(receivedSeqAuth, for: networkPdu.source)
return true
}

Expand Down
15 changes: 5 additions & 10 deletions nRFMeshProvision/Classes/Layers/Network Layer/NetworkLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,7 @@ internal class NetworkLayer {
/// - returns: The Sequence number a message can be sent with.
func nextSequenceNumber(for source: Address) -> UInt32 {
return mutex.sync {
// Get the current sequence number for local Provisioner's source address.
let sequence = UInt32(defaults.integer(forKey: "S\(source.hex)"))
// As the sequnce number was just used, it has to be incremented.
defaults.set(sequence + 1, forKey: "S\(source.hex)")
return sequence
defaults.nextSequenceNumber(for: source)
}
}
}
Expand Down Expand Up @@ -281,7 +277,7 @@ private extension NetworkLayer {
// 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
// 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()
Expand Down Expand Up @@ -311,11 +307,10 @@ private extension NetworkLayer {
//
// 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 {
if let localNode = meshNetwork.localProvisioner?.node,
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)")
}
defaults.resetSequenceNumbers(of: localNode)
}

// Store the last IV Index.
Expand Down
13 changes: 7 additions & 6 deletions nRFMeshProvision/Classes/Mesh Model/MeshNetwork.swift
Original file line number Diff line number Diff line change
Expand Up @@ -467,13 +467,15 @@ extension MeshNetwork {
node.meshNetwork = nil
timestamp = Date()

// The stored SeqAuth value cannot be removed, as that could
// lead to accepting repeated messages.
/*
// Forget the last sequence number for the device.
let meshUuid = self.uuid
if let defauts = UserDefaults(suiteName: meshUuid.uuidString) {
for element in node.elements {
defauts.removeObject(forKey: element.unicastAddress.hex)
}
if let defaults = UserDefaults(suiteName: meshUuid.uuidString) {
defaults.removeSeqAuthValues(of: node)
}
*/
}
}

Expand Down Expand Up @@ -505,8 +507,7 @@ extension MeshNetwork {
/// If the mesh network already contains a Scene with the same number,
/// this method throws an error.
///
/// - parameters
/// - scene: The Scene to be added.
/// - parameter scene: The Scene to be added.
func add(scene: Scene) {
scene.meshNetwork = self
scenes.append(scene)
Expand Down
141 changes: 141 additions & 0 deletions nRFMeshProvision/Classes/Type Extensions/UserDefaults+SeqAuth.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Copyright (c) 2021, 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 Foundation

// This extension contains helper methods for handling Sequence Numbers
// of outgoing messages from the local Node. Each message must contain
// a unique 24-bit Sequence Number, which together with 32-bit IV Index
// ensure that replay attacks are not possible.
internal extension UserDefaults {

/// Returns the next SEQ number to be used to send a message from
/// the given Unicast Address.
///
/// Each time this method is called returned value is incremented by 1.
///
/// Size of SEQ is 24 bits.
///
/// - parameter source: The Unicast Address of local Element.
/// - returns: The next SEQ number to be used.
func nextSequenceNumber(for source: Address) -> UInt32 {
// Get the current sequence number source address.
let sequence = UInt32(integer(forKey: "S\(source.hex)"))
// As the sequnce number was just used, it has to be incremented.
set(sequence + 1, forKey: "S\(source.hex)")
return sequence
}

/// Resets the SEQ associated with all Elements of the given Node to 0.
///
/// This method should be called when the IV Index is incremented and SEQ
/// number should be reset.
///
/// - parameter node: The local Node.
func resetSequenceNumbers(of node: Node) {
node.elements.forEach { element in
set(0, forKey: "S\(element.unicastAddress.hex)")
}
}

/// Removes the SEQ number associated with the given address.
///
/// - parameter source: The address to be forgotten.
func removeSequenceNumber(for source: Address) {
removeObject(forKey: "S\(source.hex)")
}

}

// This extension contains helper methods for handling SeqAuth values
// of received messages. The local Node must remember the last SeqAuth value
// received from all Nodes it is communicating with and discard messeges
// having lower or equal SeqAuth value, as potential replies.
internal extension UserDefaults {

/// Returns the last SeqAuth value stored for the given source address, or nil,
/// if no message has ever been received from that address.
///
/// The SeqAuth value ensures uniqueness of each message. Each message from
/// the same source address must be sent with unique value of SeqAuth.
///
/// - parameter source: The source Unicast Address.
/// - returns: The 32+24 bit SeqAuth value, or nil.
func lastSeqAuthValue(for source: Address) -> UInt64? {
return (object(forKey: source.hex) as? NSNumber)?.uint64Value
}

/// Stores the last received SeqAuth value in User Defaults.
///
/// - parameters:
/// - value: The SeqAuth value of received message.
/// - source: The source Unicast Address.
func storeLastSeqAuthValue(_ value: UInt64, for source: Address) {
set(NSNumber(value: value), forKey: source.hex)
}

/// Returns the previousl last SeqAuth value for the given source address, or nil,
/// if no more than 1 message has ever been received from that address.
///
/// - parameter source: The source Unicast Address.
/// - returns: The 32+24 bit SeqAuth value, or nil.
func previousSeqAuthValue(for source: Address) -> UInt64? {
return (object(forKey: "P\(source.hex)") as? NSNumber)?.uint64Value
}

/// Stores the previously received SeqAuth value in User Defaults.
///
/// - parameters:
/// - value: The previously received SeqAuth value.
/// - source: The source Unicast Address.
func storePreviousSeqAuthValue(_ value: UInt64, for source: Address) {
set(NSNumber(value: value), forKey: "P\(source.hex)")
}

/// Removes all known SeqAuth values associated with any of the Elements
/// of the given remote Node.
///
/// - parameter node: The remote Node.
func removeSeqAuthValues(of node: Node) {
node.elements.forEach { element in
removeSeqAuthValues(of: element.unicastAddress)
}
}

/// Removes last known SeqAuth value associated with the givem address
/// of a remote Node.
///
/// - parameter source: The forgotten Address.
func removeSeqAuthValues(of source: Address) {
removeObject(forKey: source.hex)
removeObject(forKey: "P\(source.hex)")
}

}

0 comments on commit 0cb5931

Please sign in to comment.