import Foundation import NIOCore /** An authenticated message to or from the device. */ struct Message: Equatable, Hashable { /** The message content without authentication. */ struct Content: Equatable, Hashable { /// The time of message creation, in UNIX time (seconds since 1970) let time: UInt32 /// The counter of the message (for freshness) let id: UInt32 /** Create new message content. - Parameter time: The time of message creation, - Parameter id: The counter of the message */ init(time: UInt32, id: UInt32) { self.time = time self.id = id } /** Decode message content from data. The data consists of two `UInt32` encoded in big endian format (MSB at index 0) - Warning: The sequence must contain at least 8 bytes, or the function will crash. - Parameter data: The sequence containing the bytes. */ init(decodeFrom data: T) where T.Element == UInt8 { self.time = UInt32(data: data.prefix(MemoryLayout.size)) self.id = UInt32(data: data.dropFirst(MemoryLayout.size)) } /// The byte length of an encoded message content static var length: Int { MemoryLayout.size * 2 } /// The message content encoded to data var encoded: Data { time.encoded + id.encoded } /// The message content encoded to bytes var bytes: [UInt8] { time.bytes + id.bytes } } /// The message authentication code for the message (32 bytes) let mac: Data /// The message content let content: Content /** Create an authenticated message - Parameter mac: The message authentication code - Parameter content: The message content */ init(mac: Data, content: Content) { self.mac = mac self.content = content } /** Decode a message from a byte buffer. The buffer must contain at least `Message.length` bytes, or it will return `nil`. - Parameter buffer: The buffer containing the bytes. */ init?(decodeFrom buffer: ByteBuffer) { guard let data = buffer.getBytes(at: 0, length: Message.length) else { return nil } self.init(decodeFrom: data) } /// The message encoded to data var encoded: Data { mac + content.encoded } /// The message encoded to bytes var bytes: [UInt8] { Array(mac) + content.bytes } } extension UInt32 { /** Create a value from a big-endian data representation (MSB first) - Note: The data must contain exactly four bytes. */ init(data: T) where T.Element == UInt8 { self = data .reversed() .enumerated() .map { UInt32($0.element) << ($0.offset * 8) } .reduce(0, +) } /// The value encoded to a big-endian representation var encoded: Data { .init(bytes) } /// The value encoded to a big-endian byte array var bytes: [UInt8] { (0..<4).reversed().map { UInt8((self >> ($0*8)) & 0xFF) } } }