Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save renssies/9d95c4986d12f0c99ba5aac12e02f8a3 to your computer and use it in GitHub Desktop.
Save renssies/9d95c4986d12f0c99ba5aac12e02f8a3 to your computer and use it in GitHub Desktop.
Delimited Serial Packet Descriptor
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)])
}
}
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