// // main.swift // OmniKitPacketParser // // Created by Pete Schwamb on 12/19/18. // Copyright © 2018 Pete Schwamb. All rights reserved. // import Foundation let printRepeats = true enum ParsingError: Error { case invalidPacketType(str: String) } extension PacketType { init(rtlomniString: String) throws { switch rtlomniString { case "PTYPE:POD": self = .pod case "PTYPE:PDM": self = .pdm case "PTYPE:CON": self = .con case "PTYPE:ACK": self = .ack default: throw ParsingError.invalidPacketType(str: rtlomniString) } } } extension String { func valPart() -> String { return String(split(separator: ":")[1]) } } extension Int { func nextPacketNumber(_ increment: Int) -> Int { return (self + increment) & 0b11111 } } //from NSHipster - http://nshipster.com/swift-literal-convertible/ struct Regex { let pattern: String let options: NSRegularExpression.Options! private var matcher: NSRegularExpression { return try! NSRegularExpression(pattern: self.pattern, options: self.options) } init(_ pattern: String, options: NSRegularExpression.Options = []) { self.pattern = pattern self.options = options } func match(string: String, options: NSRegularExpression.MatchingOptions = []) -> Bool { return self.matcher.numberOfMatches(in: string, options: options, range: NSMakeRange(0, string.count)) != 0 } } protocol RegularExpressionMatchable { func match(regex: Regex) -> Bool } extension String: RegularExpressionMatchable { func match(regex: Regex) -> Bool { return regex.match(string: self) } } func ~=(pattern: Regex, matchable: T) -> Bool { return matchable.match(regex: pattern) } class LoopIssueReportParser { // * 2018-12-27 01:46:56 +0000 send 1f0e41a6101f1a0e81ed50b102010a0101a000340034170d000208000186a00000000000000111 func parseLine(_ line: String) { let components = line.components(separatedBy: .whitespaces) if components.count == 8, let data = Data(hexadecimalString: components[7]) { let direction = components[6].padding(toLength: 7, withPad: " ", startingAt: 0) guard direction.lowercased() == "send " || direction.lowercased() == "receive" else { return } let date = components[1..<4].joined(separator: " ") do { let message = try Message(encodedData: data) print("\(date) \(direction) \(message)") } catch let error as MessageError { if let packet = try? Packet(encodedData: data) { print("\(date) \(direction) packet \(packet)") } else { switch error { case .notEnoughData: if data.count == 4 { // This is from a packet, not a message, and we don't have the full packet info here print("\(date) \(direction) ack \(components[7])") } else { print("Could not parse \(line)") } default: print("Could not parse \(line): \(error.localizedDescription)") } } } catch let error { print("Could not parse \(line): \(error)") } } } } class RTLOmniLineParser { private var lastPacket: ArraySlice? = nil private var messageDate: String = "" private var lastMessageData = Data() private var messageData = Data() private var messageSource: PacketType = .pdm private var address: String = "" private var packetNumber: Int = 0 private var repeatCount: Int = 0 func parseLine(_ line: String) { let components = line.components(separatedBy: .whitespaces) if components.count > 3, let packetType = try? PacketType(rtlomniString: components[2]) { if lastPacket == components[1...] { return } lastPacket = components[1...] switch packetType { case .pod, .pdm: if components.count != 9 { print("Invalid line:\(line)") return } // 2018-12-19T20:50:48.3d ID1:1f0b3557 PTYPE:POD SEQ:31 ID2:1f0b3557 B9:00 BLEN:205 BODY:02cb510032602138800120478004213c80092045800c203980 CRC:a8 // 2018-05-25T13:03:51.765792 ID1:ffffffff PTYPE:POD SEQ:01 ID2:ffffffff B9:04 BLEN:23 BODY:011502070002070002020000aa6400088cb98f1f16b11e82a5 CRC:72 messageDate = components[0] messageSource = packetType address = String(components[1].valPart()) packetNumber = Int(components[3].valPart())! let messageAddress = String(components[4].valPart()) let b9 = String(components[5].valPart()) if messageData.count > 0 { print("Dropping incomplete message data: \(messageData.hexadecimalString)") } messageData = Data(hexadecimalString: messageAddress + b9)! let messageLen = UInt8(components[6].valPart())! messageData.append(messageLen) let packetData = Data(hexadecimalString: components[7].valPart())! messageData.append(packetData) case .con: // 2018-12-19T05:19:04.3d ID1:1f0b3557 PTYPE:CON SEQ:12 CON:0000000000000126 CRC:60 let packetAddress = String(components[1].valPart()) let nextPacketNumber = Int(components[3].valPart())! if (packetAddress == address) && (nextPacketNumber == packetNumber.nextPacketNumber(2)) { packetNumber = nextPacketNumber let packetData = Data(hexadecimalString: components[4].valPart())! messageData.append(packetData) } else if packetAddress != address { print("mismatched address: \(line)") } else if nextPacketNumber != packetNumber.nextPacketNumber(2) { print("mismatched packet number: \(nextPacketNumber) != \(packetNumber.nextPacketNumber(2)) \(line)") } case .ack: print("Ack: \(line)") } do { let message = try Message(encodedData: messageData) let messageStr = "\(messageDate) \(messageSource) \(message)" if lastMessageData == messageData { repeatCount += 1 if printRepeats { print(messageStr + " repeat:\(repeatCount)") } } else { lastMessageData = messageData repeatCount = 0 print(messageStr) } messageData = Data() } catch MessageError.notEnoughData { return } catch let error { print("Error decoding message: \(error)") } } } } class XcodeLogParser { private var lastPacket: ArraySlice? = nil private var messageDate: String = "" private var sendMessageData = Data() private var recvMessageData = Data() private var messageSource: PacketType = .pdm private var address: String = "" private var packetNumber: Int = 0 private var repeatCount: Int = 0 func parseLine(_ line: String) { let components = line.components(separatedBy: .whitespaces) if let rlCmd = components.last { let direction = components[5].prefix(4) let timeStamp = "\(components[0]) \(components[1])" switch direction { case "Send": let cmdCode = rlCmd.prefix(4).suffix(2) switch(cmdCode) { case "05": // SendAndListen let packetData = Data(hexadecimalString: String(rlCmd.suffix(rlCmd.count - 28)))! do { let packet = try Packet(encodedData: packetData) print("\(timeStamp) \(direction) \(packet)") if packet.packetType == .con { sendMessageData.append(packet.data) } else { sendMessageData = packet.data } if let message = try? Message(encodedData: sendMessageData) { print("\(timeStamp) \(direction) \(message)") sendMessageData = Data() } if packet.packetType == .ack { sendMessageData = Data() } } catch let error { print("Error parsing \(rlCmd): \(error)") } case "06": print("Configure Register: \(cmdCode) \(rlCmd)") default: print("Unhandled command: \(direction) \(cmdCode) \(rlCmd)") } case "Recv": let status = rlCmd.prefix(2) switch(status) { case "dd": if rlCmd.count > 6 { let packetData = Data(hexadecimalString: String(rlCmd.suffix(rlCmd.count - 6)))! do { let packet = try Packet(encodedData: packetData) print("\(timeStamp) \(direction) \(packet)") if packet.packetType == .con { recvMessageData.append(packet.data) } else { recvMessageData = packet.data } do { let message = try Message(encodedData: recvMessageData) print("\(timeStamp) \(direction) \(message)") recvMessageData = Data() } catch MessageError.notEnoughData { break } catch let error { print("Message not parsed from packet data \(recvMessageData.hexadecimalString): \(error)") } } catch let error { print("Error parsing \(rlCmd): \(error)") } } else { //print("\(timeStamp) \(direction) \(rlCmd)") } case "aa": print("\(timeStamp) Receive Timeout") default: print("Unhandled response type: \(direction) \(rlCmd)") } default: break } } } } for filename in CommandLine.arguments[1...] { let rtlOmniParser = RTLOmniLineParser() let loopIssueReportParser = LoopIssueReportParser() let xcodeLogParser = XcodeLogParser() print("Parsing \(filename)") do { let data = try String(contentsOfFile: filename, encoding: .utf8) let lines = data.components(separatedBy: .newlines) for line in lines { switch line { case Regex("ID1:[0-9a-fA-F]+ PTYPE:"): // 2018-12-24T10:58:41.3d ID1:1f0f407e PTYPE:POD SEQ:02 ID2:1f0f407e B9:3c BLEN:24 BODY:0216020d0000000000d23102b103ff02b1000008ab08016e83 CRC:c2 // 2018-05-25T13:03:51.765792 ID1:ffffffff PTYPE:POD SEQ:01 ID2:ffffffff B9:04 BLEN:23 BODY:011502070002070002020000aa6400088cb98f1f16b11e82a5 CRC:72 rtlOmniParser.parseLine(line) case Regex("(send|receive) [0-9a-fA-F]+"): // 2018-12-27 01:46:56 +0000 send 1f0e41a6101f1a0e81ed50b102010a0101a000340034170d000208000186a00000000000000111 loopIssueReportParser.parseLine(line) case Regex("RL (Send|Recv) ?\\(single\\): [0-9a-fA-F]+"): // 2019-02-09 08:23:27.605518-0800 Loop[2978:2294033] [PeripheralManager+RileyLink] RL Send (single): 17050005000000000002580000281f0c27a4591f0c27a447 // 2019-02-09 08:23:28.262888-0800 Loop[2978:2294816] [PeripheralManager+RileyLink] RL Recv(single): dd0c2f1f079e674b1f079e6769 xcodeLogParser.parseLine(line) default: break } } } catch let error { print("Error: \(error)") } }