Forked from nathanntg/DelimitedSerialPacketDescriptor.swift
Last active
April 10, 2024 07:15
-
-
Save renssies/9d95c4986d12f0c99ba5aac12e02f8a3 to your computer and use it in GitHub Desktop.
Delimited Serial Packet Descriptor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ORSSerial | |
/// An example of an extension of the ORSSerialPacketDescriptor that enables identifying delimited packets. | |
public class DelimitedSerialPacketDescriptor: ORSSerialPacketDescriptor { | |
private(set) public var delimiter: Data? | |
convenience public init(delimiter: Data, maximumPacketLength maxPacketLength: UInt, userInfo: AnyObject?, responseEvaluator: @escaping ORSSerialPacketEvaluator) { | |
self.init(maximumPacketLength: maxPacketLength, userInfo: userInfo, responseEvaluator: responseEvaluator) | |
// set delimiter | |
self.delimiter = delimiter | |
} | |
convenience public init(delimiterString: String, maximumPacketLength maxPacketLength: UInt, userInfo: AnyObject?, responseEvaluator: @escaping ORSSerialPacketEvaluator) { | |
self.init(maximumPacketLength: maxPacketLength, userInfo: userInfo, responseEvaluator: responseEvaluator) | |
// set delimiter | |
self.delimiter = delimiterString.data(using: .utf8) | |
} | |
private func packetMatchingExcludingFinalDelimiter(buffer: Data) -> Data? { | |
// only use log if delimiter is provided (should only be called if delimiter exists) | |
guard let delimiter = delimiter else { | |
return nil | |
} | |
// empty buffer? potentially valid | |
if buffer.count == 0 { | |
if dataIsValidPacket(buffer) { | |
return buffer | |
} | |
return nil | |
} | |
// work back from the end of the buffer | |
for i in 0...buffer.count { | |
// check for delimiter if not reading from the beginning of the buffer | |
if i < buffer.count { | |
// not enough space for the delimiter | |
if i + delimiter.count > buffer.count { | |
continue | |
} | |
// check for proceeding delimiter | |
// (could be more lenient and just check for the end of the delimiter) | |
let startIndex = buffer.endIndex.advanced(by: (-1 * i) - delimiter.count) | |
let windowDel = buffer[startIndex..<startIndex.advanced(by: delimiter.count)] | |
// does not match? continue | |
if windowDel != delimiter { | |
continue | |
} | |
} | |
// make window | |
let window = buffer[buffer.endIndex.advanced(by: i * -1)..<buffer.endIndex] | |
if dataIsValidPacket(window) { | |
return window | |
} | |
} | |
return nil | |
} | |
override public func packetMatching(atEndOfBuffer buffer: Data?) -> Data? { | |
// only use log if delimiter is provided | |
guard let delimiter = delimiter else { | |
// otherwise inherit normal behavior | |
return super.packetMatching(atEndOfBuffer: buffer) | |
} | |
// unwrap buffer | |
guard let buffer = buffer else { return nil } | |
// space for delimiter | |
if buffer.count < delimiter.count { | |
return nil | |
} | |
// ensure buffer ends with delimiter | |
let windowFinalDel = buffer[buffer.endIndex.advanced(by: -1 * delimiter.count)..<buffer.endIndex] | |
if windowFinalDel != delimiter { | |
return nil | |
} | |
return packetMatchingExcludingFinalDelimiter(buffer: buffer[buffer.startIndex..<buffer.endIndex.advanced(by: -1 * delimiter.count)]) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import XCTest | |
class DelimitedSerialPacketDescriptorTests: XCTestCase { | |
func testPacketDescriptorNonEmpty() { | |
let desc = DelimitedSerialPacketDescriptor(delimiter: "\r\n".data(using: .ascii)!, maximumPacketLength: 32, userInfo: nil, responseEvaluator: { data -> Bool in | |
guard let data else { | |
return false | |
} | |
return data.count > 0 | |
}) | |
let toTest: [(String, String?)] = [ | |
("No\r\nFinal", nil), | |
("\r\nTesting\r\n", "Testing"), | |
("T\r", nil), | |
("\r\nMultiple\r\nStrings\r\nIn\r\nRow\r\n", "Row"), | |
("\r\n", nil), | |
("\r\n\r\n", "\r\n"), | |
("Blah\r\n", "Blah") | |
] | |
for (stringIn, stringOut) in toTest { | |
let dataIn = stringIn.data(using: .ascii)! | |
print(stringIn) | |
let dataOut: Data? = stringOut?.data(using: .ascii)! ?? nil | |
if let stringOut { | |
print(stringOut) | |
} else { | |
print("<nil>") | |
} | |
print("---") | |
XCTAssertEqual(dataOut, desc.packetMatching(atEndOfBuffer: dataIn)) | |
} | |
} | |
func testPacketDescriptorWithDelimiterStringNonEmpty() { | |
let desc = DelimitedSerialPacketDescriptor(delimiterString: "\r\n", maximumPacketLength: 32, userInfo: nil, responseEvaluator: { data -> Bool in | |
guard let data else { | |
return false | |
} | |
return data.count > 0 | |
}) | |
let toTest: [(String, String?)] = [ | |
("No\r\nFinal", nil), | |
("\r\nTesting\r\n", "Testing"), | |
("T\r", nil), | |
("\r\nMultiple\r\nStrings\r\nIn\r\nRow\r\n", "Row"), | |
("\r\n", nil), | |
("\r\n\r\n", "\r\n"), | |
("Blah\r\n", "Blah") | |
] | |
for (stringIn, stringOut) in toTest { | |
let dataIn = stringIn.data(using: .ascii)! | |
print(stringIn) | |
let dataOut: Data? = stringOut?.data(using: .ascii)! ?? nil | |
if let stringOut { | |
print(stringOut) | |
} else { | |
print("<nil>") | |
} | |
print("---") | |
XCTAssertEqual(dataOut, desc.packetMatching(atEndOfBuffer: dataIn)) | |
} | |
} | |
func testPacketDescriptorEmpty() { | |
let desc = DelimitedSerialPacketDescriptor(delimiter: "\r\n".data(using: .ascii)!, maximumPacketLength: 16, userInfo: nil, responseEvaluator: { data -> Bool in | |
return data != nil | |
}) | |
let toTest: [(String, String?)] = [ | |
("\r\n\r", nil), | |
("\r\n", ""), | |
("\r\n\r\n", "") | |
] | |
for (stringIn, stringOut) in toTest { | |
let dataIn = stringIn.data(using: .ascii)! | |
let dataOut: Data? = stringOut?.data(using: .ascii)! ?? nil | |
XCTAssertEqual(dataOut, desc.packetMatching(atEndOfBuffer: dataIn)) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment