123 lines
3.2 KiB
Swift
123 lines
3.2 KiB
Swift
|
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<T: Sequence>(decodeFrom data: T) where T.Element == UInt8 {
|
||
|
self.time = UInt32(data: data.prefix(MemoryLayout<UInt32>.size))
|
||
|
self.id = UInt32(data: data.dropFirst(MemoryLayout<UInt32>.size))
|
||
|
}
|
||
|
|
||
|
/// The byte length of an encoded message content
|
||
|
static var length: Int {
|
||
|
MemoryLayout<UInt32>.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<T: Sequence>(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)
|
||
|
}
|
||
|
}
|
||
|
}
|