import Foundation /// Common protocol for all MAVLink entities which describes types /// metadata properties. public protocol MAVLinkEntity: CustomStringConvertible, CustomDebugStringConvertible { /// Original MAVLink enum name (from declarations xml) static var typeName: String { get } /// Compact type description static var typeDescription: String { get } /// Verbose type description static var typeDebugDescription: String { get } } // MARK: - Enumeration protocol /// Enumeration protocol description with common for all MAVLink enums /// properties requirements. public protocol Enumeration: RawRepresentable, Equatable, MAVLinkEntity { /// Array with all members of current enum static var allMembers: [Self] { get } // Array with `Name` - `Description` tuples (values from declarations xml file) static var membersDescriptions: [(String, String)] { get } /// `ENUM_END` flag for checking if enum case value is valid static var enumEnd: UInt { get } /// Original MAVLinks enum member name (as declared in definition's xml file) var memberName: String { get } /// Specific member description from definitions xml var memberDescription: String { get } } /// Enumeration protocol default behaviour implementation. extension Enumeration { public static var typeDebugDescription: String { let cases = allMembers.map({ $0.debugDescription }).joined(separator: "\\n\\t") return "Enum \(typeName): \(typeDescription)\\nMembers:\\n\\t\(cases)" } public var description: String { return memberName } public var debugDescription: String { return "\(memberName): \(memberDescription)" } public var memberName: String { return Self.membersDescriptions[Self.allMembers.index(of: self)!].0 } public var memberDescription: String { return Self.membersDescriptions[Self.allMembers.index(of: self)!].1 } } // MARK: - Message protocol /// Message field definition tuple. public typealias FieldDefinition = (name: String, offset: Int, type: String, length: UInt, description: String) /// Message protocol describes all common MAVLink messages properties and /// methods requirements. public protocol Message: MAVLinkEntity { static var id: UInt8 { get } static var payloadLength: UInt8 { get } /// Array of tuples with field definition info static var fieldDefinitions: [FieldDefinition] { get } /// All field's names and values of current Message var allFields: [(String, Any)] { get } /// Initialize Message from received data. /// /// - parameter data: Data to decode. /// /// - throws: Throws `ParseError` or `ParseEnumError` if any parsing errors /// occur. init(data: Data) throws /// Returns `Data` representation of current `Message` struct guided /// by format from `fieldDefinitions`. /// /// - throws: Throws `PackError` if any of message fields do not comply /// format from `fieldDefinitions`. /// /// - returns: Receiver's `Data` representation func pack() throws -> Data } /// Message protocol default behaviour implementation. extension Message { public static var payloadLength: UInt8 { return messageLengths[id] ?? Packet.Constant.maxPayloadLength } public static var typeDebugDescription: String { let fields = fieldDefinitions.map({ "\($0.name): \($0.type): \($0.description)" }).joined(separator: "\n\t") return "Struct \(typeName): \(typeDescription)\nFields:\n\t\(fields)" } public var description: String { let describeField: ((String, Any)) -> String = { (name, value) in let valueString = value is String ? "\"\(value)\"" : value return "\(name): \(valueString)" } let fieldsDescription = allFields.map(describeField).joined(separator: ", ") return "\(type(of: self))(\(fieldsDescription))" } public var debugDescription: String { let describeFieldVerbose: ((String, Any)) -> String = { (name, value) in let valueString = value is String ? "\"\(value)\"" : value let (_, _, _, _, description) = Self.fieldDefinitions.filter { $0.name == name }.first! return "\(name) = \(valueString) : \(description)" } let fieldsDescription = allFields.map(describeFieldVerbose).joined(separator: "\n\t") return "\(Self.typeName): \(Self.typeDescription)\nFields:\n\t\(fieldsDescription)" } public var allFields: [(String, Any)] { var result: [(String, Any)] = [] let mirror = Mirror(reflecting: self) for case let (label?, value) in mirror.children { result.append((label, value)) } return result } } // MARK: - Type aliases public typealias Channel = UInt8 // MARK: - Errors public protocol MAVLinkError: Error, CustomStringConvertible, CustomDebugStringConvertible { } // MARK: Parsing error enumeration /// Parsing errors public enum ParseError: MAVLinkError { /// Size of expected number is larger than receiver's data length. /// - offset: Expected number offset in received data. /// - size: Expected number size in bytes. /// - upperBound: The number of bytes in the data. case valueSizeOutOfBounds(offset: Int, size: Int, upperBound: Int) /// Data contains non ASCII characters. /// - offset: String offset in received data. /// - length: Expected length of string to read. case invalidStringEncoding(offset: Int, length: Int) /// Length check of payload for known `messageId` did fail. /// - messageId: Id of expected `Message` type. /// - receivedLength: Received payload length. /// - properLength: Expected payload length for `Message` type. case invalidPayloadLength(messageId: UInt8, receivedLength: UInt8, expectedLength: UInt8) /// Received `messageId` was not recognized so we can't create appropriate /// `Message`. /// - messageId: Id of the message that was not found in the known message /// list (`messageIdToClass` array). case unknownMessageId(messageId: UInt8) /// Checksum check failed. Message id is known but calculated CRC bytes /// do not match received CRC value. /// - messageId: Id of expected `Message` type. case badCRC(messageId: UInt8) } extension ParseError { /// Textual representation used when written to output stream. public var description: String { switch self { case .valueSizeOutOfBounds: return "ParseError.valueSizeOutOfBounds" case .invalidStringEncoding: return "ParseError.invalidStringEncoding" case .invalidPayloadLength: return "ParseError.invalidPayloadLength" case .unknownMessageId: return "ParseError.unknownMessageId" case .badCRC: return "ParseError.badCRC" } } /// Debug textual representation used when written to output stream, which /// includes all associated values and their labels. public var debugDescription: String { switch self { case let .valueSizeOutOfBounds(offset, size, upperBound): return "ParseError.valueSizeOutOfBounds(offset: \(offset), size: \(size), upperBound: \(upperBound))" case let .invalidStringEncoding(offset, length): return "ParseError.invalidStringEncoding(offset: \(offset), length: \(length))" case let .invalidPayloadLength(messageId, receivedLength, expectedLength): return "ParseError.invalidPayloadLength(messageId: \(messageId), receivedLength: \(receivedLength), expectedLength: \(expectedLength))" case let .unknownMessageId(messageId): return "ParseError.unknownMessageId(messageId: \(messageId))" case let .badCRC(messageId): return "ParseError.badCRC(messageId: \(messageId))" } } } // MARK: Parsing enumeration error /// Special error type for returning Enum parsing errors with details in associated /// values (types of these values are not compatible with `ParseError` enum). public enum ParseEnumError: MAVLinkError { /// Enumeration case with `rawValue` at `valueOffset` was not found in /// `enumType` enumeration. /// - enumType: Type of expected enumeration. /// - rawValue: Raw value that was not found in `enumType`. /// - valueOffset: Value offset in received payload data. case unknownValue(enumType: T.Type, rawValue: T.RawValue, valueOffset: Int) } extension ParseEnumError { /// Textual representation used when written to the output stream. public var description: String { switch self { case .unknownValue: return "ParseEnumError.unknownValue" } } /// Debug textual representation used when written to the output stream, which /// includes all associated values and their labels. public var debugDescription: String { switch self { case let .unknownValue(enumType, rawValue, valueOffset): return "ParseEnumError.unknownValue(enumType: \(enumType), rawValue: \(rawValue), valueOffset: \(valueOffset))" } } } // MARK: Packing errors /// Errors that can occur while packing `Message` for sending. public enum PackError: MAVLinkError { /// Size of received value (together with offset) is out of receiver's length. /// - offset: Expected value offset in payload. /// - size: Provided field value size in bytes. /// - upperBound: Available payload length. case valueSizeOutOfBounds(offset: Int, size: Int, upperBound: Int) /// Length check for provided field value did fail. /// - offset: Expected value offset in payload. /// - providedValueLength: Count of elements (characters) in provided value. /// - allowedLength: Maximum number of elements (characters) allowed in field. case invalidValueLength(offset: Int, providedValueLength: Int, allowedLength: Int) /// String field contains non ASCII characters. /// - offset: Expected value offset in payload. /// - string: Original string. case invalidStringEncoding(offset: Int, string: String) /// CRC extra byte not found for provided `messageId` type. /// - messageId: Id of message type. case crcExtraNotFound(messageId: UInt8) /// Packet finalization process failed due to `message` absence. case messageNotSet } extension PackError { /// Textual representation used when written to the output stream. public var description: String { switch self { case .valueSizeOutOfBounds: return "PackError.valueSizeOutOfBounds" case .invalidValueLength: return "PackError.invalidValueLength" case .invalidStringEncoding: return "PackError.invalidStringEncoding" case .crcExtraNotFound: return "PackError.crcExtraNotFound" case .messageNotSet: return "PackError.messageNotSet" } } /// Debug textual representation used when written to the output stream, which /// includes all associated values and their labels. public var debugDescription: String { switch self { case let .valueSizeOutOfBounds(offset, size, upperBound): return "PackError.valueSizeOutOfBounds(offset: \(offset), size: \(size), upperBound: \(upperBound))" case let .invalidValueLength(offset, providedValueLength, allowedLength): return "PackError.invalidValueLength(offset: \(offset), providedValueLength: \(providedValueLength), allowedLength: \(allowedLength))" case let .invalidStringEncoding(offset, string): return "PackError.invalidStringEncoding(offset: \(offset), string: \(string))" case let .crcExtraNotFound(messageId): return "PackError.crcExtraNotFound(messageId: \(messageId))" case .messageNotSet: return "PackError.messageNotSet" } } } // MARK: - Delegate protocol /// Alternative way to receive parsed Messages, finalized packet's data and all /// errors is to implement this protocol and set as `MAVLink`'s delegate. public protocol MAVLinkDelegate: class { /// Called when MAVLink packet is successfully received, payload length /// and CRC checks are passed. /// /// - parameter packet: Completely received `Packet`. /// - parameter channel: Channel on which `packet` was received. /// - parameter link: `MAVLink` object that handled `packet`. func didReceive(packet: Packet, on channel: Channel, via link: MAVLink) /// Packet receiving failed due to `InvalidPayloadLength` or `BadCRC` error. /// /// - parameter packet: Partially received `Packet`. /// - parameter error: Error that occurred while receiving `data` /// (`InvalidPayloadLength` or `BadCRC` error). /// - parameter channel: Channel on which `packet` was received. /// - parameter link: `MAVLink` object that received `data`. func didFailToReceive(packet: Packet?, with error: MAVLinkError, on channel: Channel, via link: MAVLink) /// Called when received data was successfully parsed into appropriate /// `message` structure. /// /// - parameter message: Successfully parsed `Message`. /// - parameter packet: Completely received `Packet`. /// - parameter channel: Channel on which `message` was received. /// - parameter link: `MAVLink` object that handled `packet`. func didParse(message: Message, from packet: Packet, on channel: Channel, via link: MAVLink) /// Called when `packet` completely received but `MAVLink` was not able to /// finish `Message` processing due to unknown `messageId` or type validation /// errors. /// /// - parameter packet: Completely received `Packet`. /// - parameter error: Error that occurred while parsing `packet`'s /// payload into `Message`. /// - parameter channel: Channel on which `message` was received. /// - parameter link: `MAVLink` object that handled `packet`. func didFailToParseMessage(from packet: Packet, with error: MAVLinkError, on channel: Channel, via link: MAVLink) /// Called when message is finalized and ready for sending to aircraft. /// /// - parameter message: Message to be sent. /// - parameter data: Compiled data that represents `message`. /// - parameter channel: Channel on which `message` should be sent. /// - parameter link: `MAVLink` object that handled `message`. func didFinalize(message: Message, from packet: Packet, to data: Data, on channel: Channel, in link: MAVLink) } // MARK: - Classes implementations /// Main MAVLink class, performs `Packet` receiving, recognition, validation, /// `Message` structure creation and `Message` packing, finalizing for sending. /// Also returns errors through delegation if any errors occurred. /// - warning: Supports only 1.0 version of the MAVlink wire protocol. public class MAVLink { /// States for the parsing state machine. enum ParseState { case uninit case idle case gotStx case gotSequence case gotLength case gotSystemId case gotComponentId case gotMessageId case gotPayload case gotCRC1 case gotBadCRC1 } enum Framing: UInt8 { case incomplete = 0 case ok = 1 case badCRC = 2 } /// Storage for MAVLink parsed packets count, states and errors statistics. class Status { /// Number of received packets var packetReceived: Framing = .incomplete /// Number of parse errors var parseError: UInt8 = 0 /// Parsing state machine var parseState: ParseState = .uninit /// Sequence number of the last received packet var currentRxSeq: UInt8 = 0 /// Sequence number of the last sent packet var currentTxSeq: UInt8 = 0 /// Received packets var packetRxSuccessCount: UInt16 = 0 /// Number of packet drops var packetRxDropCount: UInt16 = 0 } /// MAVLink Packets and States buffers let channelBuffers = (0 ..< Channel.max).map({ _ in Packet() }) let channelStatuses = (0 ..< Channel.max).map({ _ in Status() }) /// Object to pass received packets, messages, errors, finalized data to. public weak var delegate: MAVLinkDelegate? /// Enable this option to check the length of each message. This allows /// invalid messages to be caught much sooner. Use it if the transmission /// medium is prone to missing (or extra) characters (e.g. a radio that /// fades in and out). Use only if the channel will contain message /// types listed in the headers. public var checkMessageLength = true /// Use one extra CRC that is added to the message CRC to detect mismatches /// in the message specifications. This is to prevent that two devices using /// different message versions incorrectly decode a message with the same /// length. Defined as `let` as we support only the latest version (1.0) of /// the MAVLink wire protocol. public let crcExtra = true public init() { } /// This is a convenience function which handles the complete MAVLink /// parsing. The function will parse one byte at a time and return the /// complete packet once it could be successfully decoded. Checksum and /// other failures will be delegated to `delegate`. /// /// - parameter char: The char to parse. /// - parameter channel: Id of the current channel. This allows to parse /// different channels with this function. A channel is not a physical /// message channel like a serial port, but a logic partition of the /// communication streams in this case. /// /// - returns: Returns `nil` if packet could be decoded at the moment, /// the `Packet` structure else. public func parse(char: UInt8, channel: Channel) -> Packet? { /// Function to check if current char is Stx byte. If current char is /// STX, modifies current rxpack and status. func handleSTX(char: UInt8, rxpack: Packet, status: Status) { if char == Packet.Constant.packetStx { rxpack.length = 0 rxpack.channel = channel rxpack.magic = char rxpack.checksum.start() status.parseState = .gotStx } } let rxpack = channelBuffers[Int(channel)] let status = channelStatuses[Int(channel)] status.packetReceived = .incomplete switch status.parseState { case .uninit, .idle: handleSTX(char: char, rxpack: rxpack, status: status) case .gotStx: rxpack.length = char rxpack.payload.count = 0 rxpack.checksum.accumulate(char) status.parseState = .gotLength case .gotLength: rxpack.sequence = char rxpack.checksum.accumulate(char) status.parseState = .gotSequence case .gotSequence: rxpack.systemId = char rxpack.checksum.accumulate(char) status.parseState = .gotSystemId case .gotSystemId: rxpack.componentId = char rxpack.checksum.accumulate(char) status.parseState = .gotComponentId case .gotComponentId: // Check Message length if `checkMessageLength` enabled and // `messageLengths` contains proper id. If `messageLengths` does not // contain info for current messageId, parsing will fail later on CRC check. if checkMessageLength { let messageLength = messageLengths[char] ?? 0 if rxpack.length != messageLength { status.parseError += 1 status.parseState = .idle let error = ParseError.invalidPayloadLength(messageId: char, receivedLength: rxpack.length, expectedLength: messageLength) delegate?.didFailToReceive(packet: nil, with: error, on: channel, via: self) break } } rxpack.messageId = char rxpack.checksum.accumulate(char) if rxpack.length == 0 { status.parseState = .gotPayload } else { status.parseState = .gotMessageId } case .gotMessageId: rxpack.payload.append(char) rxpack.checksum.accumulate(char) if rxpack.payload.count == Int(rxpack.length) { status.parseState = .gotPayload } case .gotPayload: if crcExtra && (messageCRCsExtra[rxpack.messageId] != nil) { rxpack.checksum.accumulate(messageCRCsExtra[rxpack.messageId]!) } rxpack.payload.append(char) if char != rxpack.checksum.lowByte { status.parseState = .gotBadCRC1 fallthrough } else { status.parseState = .gotCRC1 } case .gotCRC1, .gotBadCRC1: if (status.parseState == .gotBadCRC1) || (char != rxpack.checksum.highByte) { status.parseError += 1 status.packetReceived = .badCRC let error = messageIdToClass[rxpack.messageId] == nil ? ParseError.unknownMessageId(messageId: rxpack.messageId) : ParseError.badCRC(messageId: rxpack.messageId) delegate?.didFailToReceive(packet: Packet(packet: rxpack), with: error, on: channel, via: self) handleSTX(char: char, rxpack: rxpack, status: status) } else { // Successfully got message rxpack.payload.append(char) status.packetReceived = .ok } status.parseState = .idle } defer { // Сollect stat here status.parseError = 0 } // If a packet has been sucessfully received guard status.packetReceived == .ok else { return nil } // Copy and delegate received packet let packet = Packet(packet: rxpack) delegate?.didReceive(packet: packet, on: channel, via: self) status.currentRxSeq = rxpack.sequence // Initial condition: If no packet has been received so far, drop count is undefined if status.packetRxSuccessCount == 0 { status.packetRxDropCount = 0 } // Count this packet as received status.packetRxSuccessCount = status.packetRxSuccessCount &+ 1 // Try to create appropriate Message structure, delegate results guard let messageClass = messageIdToClass[packet.messageId] else { let error = ParseError.unknownMessageId(messageId: rxpack.messageId) delegate?.didFailToParseMessage(from: packet, with: error, on: channel, via: self) return packet } do { packet.message = try messageClass.init(data: rxpack.payload) delegate?.didParse(message: packet.message!, from: packet, on: channel, via: self) } catch { delegate?.didFailToParseMessage(from: packet, with: error as! MAVLinkError, on: channel, via: self) return packet } return packet } /// Parse new portion of data, then call `messageHandler` if new message /// is available. /// /// - parameter data: Data to be parsed. /// - parameter channel: Id of the current channel. This allows to /// parse different channels with this function. A channel is not a physical /// message channel like a serial port, but a logic partition of the /// communication streams in this case. /// - parameter messageHandler: The message handler to call when the /// provided data is enough to complete message parsing. Unless you have /// provided a custom delegate, this parameter must not be `nil`, because /// there is no other way to retrieve the parsed message and packet. public func parse(data: Data, channel: Channel, messageHandler: ((Message, Packet) -> Void)? = nil) { data.forEach { byte in if let packet = parse(char: byte, channel: channel), let message = packet.message, let messageHandler = messageHandler { messageHandler(message, packet) } } } /// Prepare `message` bytes for sending, pass to `delegate` for further /// processing and increase sequence counter. /// /// - parameter message: Message to be compiled into bytes and sent. /// - parameter systemId: Id of the sending (this) system. /// - parameter componentId: Id of the sending component. /// - parameter channel: Id of the current channel. /// /// - throws: Throws `PackError`. public func dispatch(message: Message, systemId: UInt8, componentId: UInt8, channel: Channel) throws { let channelStatus = channelStatuses[Int(channel)] let packet = Packet(message: message, systemId: systemId, componentId: componentId, channel: channel) let data = try packet.finalize(sequence: channelStatus.currentTxSeq) delegate?.didFinalize(message: message, from: packet, to: data, on: channel, in: self) channelStatus.currentTxSeq = channelStatus.currentTxSeq &+ 1 } } /// MAVLink Packet structure to store received data that is not full message yet. /// Contains additional to Message info like channel, system id, component id /// and raw payload data, etc. Also used to store and transfer received data of /// unknown or corrupted Messages. /// [More details](http://qgroundcontrol.org/mavlink/start). public class Packet { /// MAVlink Packet constants struct Constant { /// Maximum packets payload length static let maxPayloadLength = UInt8.max static let numberOfChecksumBytes = 2 /// Length of core header (of the comm. layer): message length /// (1 byte) + message sequence (1 byte) + message system id (1 byte) + /// message component id (1 byte) + message type id (1 byte). static let coreHeaderLength = 5 /// Length of all header bytes, including core and checksum static let numberOfHeaderBytes = Constant.numberOfChecksumBytes + Constant.coreHeaderLength + 1 /// Packet start sign. Indicates the start of a new packet. v1.0. static let packetStx: UInt8 = 0xFE } /// Channel on which packet was received public internal(set) var channel: UInt8 = 0 /// Sent at the end of packet public internal(set) var checksum = Checksum() /// Protocol magic marker (PacketStx value) public internal(set) var magic: UInt8 = 0 /// Length of payload public internal(set) var length: UInt8 = 0 /// Sequence of packet public internal(set) var sequence: UInt8 = 0 /// Id of message sender system/aircraft public internal(set) var systemId: UInt8 = 0 /// Id of the message sender component public internal(set) var componentId: UInt8 = 0 /// Id of message type in payload public internal(set) var messageId: UInt8 = 0 /// Message bytes public internal(set) var payload = Data(capacity: Int(Constant.maxPayloadLength) + Constant.numberOfChecksumBytes) /// Received Message structure if available public internal(set) var message: Message? /// Initialize copy of provided Packet. /// /// - parameter packet: Packet to copy init(packet: Packet) { channel = packet.channel checksum = packet.checksum magic = packet.magic length = packet.length sequence = packet.sequence systemId = packet.systemId componentId = packet.componentId messageId = packet.messageId payload = packet.payload message = packet.message } /// Initialize packet with provided `message` for sending. /// /// - parameter message: Message to send. /// - parameter systemId: Id of the sending (this) system. /// - parameter componentId: Id of the sending component. /// - parameter channel: Id of the current channel. init(message: Message, systemId: UInt8, componentId: UInt8, channel: Channel) { self.magic = Constant.packetStx self.systemId = systemId self.componentId = componentId self.messageId = type(of: message).id self.length = type(of: message).payloadLength self.message = message self.channel = channel } init() { } /// Finalize a MAVLink packet with sequence assignment. Returns data that /// could be sent to the aircraft. This function calculates the checksum and /// sets length and aircraft id correctly. It assumes that the packet is /// already correctly initialized with appropriate `message`, `length`, /// `systemId`, `componentId`. /// Could be used to send packets without `MAVLink` object, in this case you /// should take care of `sequence` counter manually. /// /// - parameter sequence: Each channel counts up its send sequence. It allows /// to detect packet loss. /// /// - throws: Throws `PackError`. /// /// - returns: Data public func finalize(sequence: UInt8) throws -> Data { guard let message = message else { throw PackError.messageNotSet } guard let crcExtra = messageCRCsExtra[messageId] else { throw PackError.crcExtraNotFound(messageId: type(of: message).id) } self.sequence = sequence let coreHeader = [length, sequence, systemId, componentId, messageId] let header = [Constant.packetStx] + coreHeader let payload = try message.pack() checksum.start() checksum.accumulate(coreHeader) checksum.accumulate(payload) checksum.accumulate(crcExtra) let checksumBytes = [checksum.lowByte, checksum.highByte] var packetData = Data(capacity: payload.count + Constant.numberOfHeaderBytes) packetData.append(header, count: header.count) packetData.append(payload) packetData.append(checksumBytes, count: checksumBytes.count) return packetData } } /// Struct for storing and calculating checksum. public struct Checksum { struct Constants { static let x25InitCRCValue: UInt16 = 0xFFFF } public var lowByte: UInt8 { return UInt8(truncatingBitPattern: value) } public var highByte: UInt8 { return UInt8(truncatingBitPattern: value >> 8) } public private(set) var value: UInt16 = 0 init() { start() } /// Initialize the buffer for the X.25 CRC. mutating func start() { value = Constants.x25InitCRCValue } /// Accumulate the X.25 CRC by adding one char at a time. The checksum /// function adds the hash of one char at a time to the 16 bit checksum /// `value` (`UInt16`). /// /// - parameter char: New char to hash mutating func accumulate(_ char: UInt8) { var tmp: UInt8 = char ^ UInt8(truncatingBitPattern: value) tmp ^= (tmp << 4) value = (UInt16(value) >> 8) ^ (UInt16(tmp) << 8) ^ (UInt16(tmp) << 3) ^ (UInt16(tmp) >> 4) } /// Accumulate the X.25 CRC by adding `buffer` bytes. /// /// - parameter buffer: Sequence of bytes to hash mutating func accumulate(_ buffer: T) where T.Iterator.Element == UInt8 { buffer.forEach { accumulate($0) } } } // MARK: - CF independent host system byte order determination public enum ByteOrder: UInt32 { case unknown case littleEndian case bigEndian } public func hostByteOrder() -> ByteOrder { var bigAndLittleEndian: UInt32 = (ByteOrder.bigEndian.rawValue << 24) | ByteOrder.littleEndian.rawValue let firstByte: UInt8 = withUnsafePointer(to: &bigAndLittleEndian) { numberPointer in let bufferPointer = numberPointer.withMemoryRebound(to: UInt8.self, capacity: 4) { pointer in return UnsafeBufferPointer(start: pointer, count: 4) } return bufferPointer[0] } return ByteOrder(rawValue: UInt32(firstByte)) ?? .unknown } // MARK: - Data extensions protocol MAVLinkNumber { } extension UInt8: MAVLinkNumber { } extension Int8: MAVLinkNumber { } extension UInt16: MAVLinkNumber { } extension Int16: MAVLinkNumber { } extension UInt32: MAVLinkNumber { } extension Int32: MAVLinkNumber { } extension UInt64: MAVLinkNumber { } extension Int64: MAVLinkNumber { } extension Float: MAVLinkNumber { } extension Double: MAVLinkNumber { } /// Methods for getting properly typed field values from received data. extension Data { /// Returns number value (integer or floating point) from receiver's data. /// /// - parameter offset: Offset in receiver's bytes. /// - parameter byteOrder: Current system endianness. /// /// - throws: Throws `ParseError`. /// /// - returns: Returns `MAVLinkNumber` (UInt8, Int8, UInt16, Int16, UInt32, /// Int32, UInt64, Int64, Float, Double). func number(at offset: Data.Index, byteOrder: ByteOrder = hostByteOrder()) throws -> T { let size = MemoryLayout.stride let range: Range = offset ..< offset + size guard range.upperBound <= count else { throw ParseError.valueSizeOutOfBounds(offset: offset, size: size, upperBound: count) } var bytes = subdata(in: range) if byteOrder != .littleEndian { bytes.reverse() } return bytes.withUnsafeBytes { $0.pointee } } /// Returns typed array from receiver's data. /// /// - parameter offset: Offset in receiver's bytes. /// - parameter capacity: Expected number of elements in array. /// /// - throws: Throws `ParseError`. /// /// - returns: `Array` func array(at offset: Data.Index, capacity: Int) throws -> [T] { var offset = offset var array = [T]() for _ in 0 ..< capacity { array.append(try number(at: offset)) offset += MemoryLayout.stride } return array } /// Returns ASCII String from receiver's data. /// /// - parameter offset: Offset in receiver's bytes. /// - parameter length: Expected length of string to read. /// /// - throws: Throws `ParseError`. /// /// - returns: `String` func string(at offset: Data.Index, length: Int) throws -> String { let range: Range = offset ..< offset + length guard range.upperBound <= count else { throw ParseError.valueSizeOutOfBounds(offset: offset, size: length, upperBound: count) } let bytes = subdata(in: range) let emptySubSequence = Data.SubSequence(base: Data(), bounds: 0 ..< 0) let firstSubSequence = bytes.split(separator: 0x0, maxSplits: 1, omittingEmptySubsequences: false).first ?? emptySubSequence guard let string = String(bytes: firstSubSequence, encoding: .ascii) else { throw ParseError.invalidStringEncoding(offset: offset, length: length) } return string } /// Returns proper typed `Enumeration` subtype value from data or throws /// `ParserEnumError` or `ParseError` error. /// /// - parameter offset: Offset in receiver's bytes. /// /// - throws: Throws `ParserEnumError`, `ParseError`. /// /// - returns: Properly typed `Enumeration` subtype value. func enumeration(at offset: Data.Index) throws -> T where T.RawValue: MAVLinkNumber { let rawValue: T.RawValue = try number(at: offset) guard let enumerationCase = T(rawValue: rawValue) else { throw ParseEnumError.unknownValue(enumType: T.self, rawValue: rawValue, valueOffset: offset) } return enumerationCase } } /// Methods for filling `Data` with properly formatted field values. extension Data { /// Sets properly swapped `number` bytes starting from `offset` in /// receiver's bytes. /// /// - warning: Supports only version 1.0 of MAVLink wire protocol /// (little-endian byte order). /// /// - parameter number: Number value to set. /// - parameter offset: Offset in receiver's bytes. /// - parameter byteOrder: Current system endianness. /// /// - throws: Throws `PackError`. mutating func set(_ number: T, at offset: Data.Index, byteOrder: ByteOrder = hostByteOrder()) throws { let size = MemoryLayout.stride let range = offset ..< offset + size guard range.endIndex <= count else { throw PackError.valueSizeOutOfBounds(offset: offset, size: size, upperBound: count) } var number = number var bytes: Data = withUnsafePointer(to: &number) { numberPointer in let bufferPointer = numberPointer.withMemoryRebound(to: UInt8.self, capacity: size) { pointer in return UnsafeBufferPointer(start: pointer, count: size) } return Data(bufferPointer) } if byteOrder != .littleEndian { bytes.reverse() } replaceSubrange(range, with: bytes) } /// Sets `array` of `MAVLinkNumber` values at `offset` with `capacity` validation. /// /// - parameter array: Array of values to set. /// - parameter offset: Offset in receiver's bytes. /// - parameter capacity: Maximum allowed count of elements in `array`. /// /// - throws: Throws `PackError`. mutating func set(_ array: [T], at offset: Data.Index, capacity: Int) throws { guard array.count <= capacity else { throw PackError.invalidValueLength(offset: offset, providedValueLength: array.count, allowedLength: capacity) } let elementSize = MemoryLayout.stride let arraySize = elementSize * array.count guard offset + arraySize <= count else { throw PackError.valueSizeOutOfBounds(offset: offset, size: arraySize, upperBound: count) } for (index, item) in array.enumerated() { try set(item, at: offset + index * elementSize) } } /// Sets correctly encoded `string` value at `offset` limited to `length` or /// throws `PackError`. /// /// - precondition: `string` value must be ASCII compatible. /// /// - parameter string: Value to set. /// - parameter offset: Offset in receiver's bytes. /// - parameter length: Maximum allowed length of `string`. /// /// - throws: Throws `PackError`. mutating func set(_ string: String, at offset: Data.Index, length: Int) throws { var bytes = string.data(using: .ascii) ?? Data() if bytes.isEmpty && string.unicodeScalars.count > 0 { throw PackError.invalidStringEncoding(offset: offset, string: string) } // Add optional null-termination if provided string is shorter than // expectedlength if bytes.count < length { bytes.append(0x0) } let asciiCharacters = bytes.withUnsafeBytes { Array(UnsafeBufferPointer(start: $0, count: bytes.count)) } try set(asciiCharacters, at: offset, capacity: length) } /// Sets correctly formated `enumeration` raw value at `offset` or throws /// `PackError`. /// /// - parameter enumeration: Value to set. /// - parameter offset: Offset in receiver's bytes. /// /// - throws: Throws `PackError`. mutating func set(_ enumeration: T, at offset: Data.Index) throws where T.RawValue: MAVLinkNumber { try set(enumeration.rawValue, at: offset) } } // MARK: - Additional MAVLink service info