Initial Commit
This commit is contained in:
parent
f6d785e0f5
commit
6859e27368
43
.devcontainer/devcontainer.json
Normal file
43
.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
// Displayed name
|
||||
"name": "Message-Utils",
|
||||
|
||||
// Image to be used
|
||||
"image": "swift",
|
||||
|
||||
// Customization
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"sswg.swift-lang",
|
||||
"fabiospampinato.vscode-highlight",
|
||||
"fabiospampinato.vscode-todo-plus"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// Env in container
|
||||
"containerEnv": {
|
||||
|
||||
},
|
||||
|
||||
// Mounts in container
|
||||
"mounts": [
|
||||
{
|
||||
"source": "${localWorkspaceFolder}",
|
||||
"target": "/workspace",
|
||||
"type": "bind"
|
||||
}
|
||||
],
|
||||
|
||||
// The WorkspaceFolder inside container
|
||||
"workspaceFolder": "/workspace",
|
||||
|
||||
// RunArgs
|
||||
"runArgs": [
|
||||
"--name",
|
||||
"Swift-MessageUtils"
|
||||
]
|
||||
|
||||
|
||||
}
|
||||
24
Package.swift
Normal file
24
Package.swift
Normal file
@ -0,0 +1,24 @@
|
||||
// swift-tools-version: 6.0
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "MessageUtils",
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, making them visible to other packages.
|
||||
.library(
|
||||
name: "MessageUtils",
|
||||
targets: ["MessageUtils"]),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package, defining a module or a test suite.
|
||||
// Targets can depend on other targets in this package and products from dependencies.
|
||||
.target(
|
||||
name: "MessageUtils"),
|
||||
.testTarget(
|
||||
name: "MessageUtilsTests",
|
||||
dependencies: ["MessageUtils"]
|
||||
),
|
||||
]
|
||||
)
|
||||
4
Sources/MessageUtils/Enums/DevType.swift
Normal file
4
Sources/MessageUtils/Enums/DevType.swift
Normal file
@ -0,0 +1,4 @@
|
||||
public enum DeviceType: UInt8 {
|
||||
case EDGE_SENSOR = 0
|
||||
case SCANNER_SENSOR = 1
|
||||
}
|
||||
8
Sources/MessageUtils/Enums/MessageType.swift
Normal file
8
Sources/MessageUtils/Enums/MessageType.swift
Normal file
@ -0,0 +1,8 @@
|
||||
public enum MessageType: UInt8 {
|
||||
case KEEPALIVE = 0
|
||||
case DATA = 1
|
||||
case INFO = 2
|
||||
case WARNING = 50
|
||||
case ERROR = 100
|
||||
case CRITICAL = 255
|
||||
}
|
||||
3
Sources/MessageUtils/Enums/SignType.swift
Normal file
3
Sources/MessageUtils/Enums/SignType.swift
Normal file
@ -0,0 +1,3 @@
|
||||
public enum SignType: UInt32 {
|
||||
case P521 = 10
|
||||
}
|
||||
13
Sources/MessageUtils/Errors/UtilsErrors.swift
Normal file
13
Sources/MessageUtils/Errors/UtilsErrors.swift
Normal file
@ -0,0 +1,13 @@
|
||||
public enum SerializationError : Error {
|
||||
|
||||
case SIGNATURE_NOT_SUPPORTED
|
||||
|
||||
}
|
||||
|
||||
public enum CommonError : Error {
|
||||
case SIGNATURE_NOT_SUPPORTED
|
||||
}
|
||||
|
||||
public enum DeserializationError: Error {
|
||||
case UNMATCHING_SIGNATURE_TYPE
|
||||
}
|
||||
203
Sources/MessageUtils/MessageUtils.swift
Normal file
203
Sources/MessageUtils/MessageUtils.swift
Normal file
@ -0,0 +1,203 @@
|
||||
// The Swift Programming Language
|
||||
// https://docs.swift.org/swift-book
|
||||
import Foundation
|
||||
|
||||
public func serializeV1(msg: Message) throws -> Data {
|
||||
|
||||
let MESSAGE_CAPACITY: Int = try countBytes(msg: msg)
|
||||
|
||||
var serializedData: Data = Data(count: MESSAGE_CAPACITY)
|
||||
|
||||
// Serialize Header
|
||||
|
||||
// UGLY: extrapolate in functions
|
||||
serializedData[0] = msg.version
|
||||
serializedData[1] = msg.messageType.rawValue
|
||||
serializedData[2] = msg.devType.rawValue
|
||||
serializedData[3] = msg.RESERVED
|
||||
serializedData[4...7] = msg.signType.rawValue.data
|
||||
// First 8 bytes
|
||||
|
||||
serializedData[8...15] = msg.timestamp.data
|
||||
// 8 Bytes
|
||||
|
||||
serializedData[16...31] = msg.devID.data
|
||||
// 16 bytes
|
||||
|
||||
serializedData[32...39] = msg.location.x.data
|
||||
// 8 Bytes
|
||||
|
||||
serializedData[40...47] = msg.location.y.data
|
||||
// 8 Bytes
|
||||
|
||||
serializedData[48...55] = msg.location.z.data
|
||||
// 8 Bytes
|
||||
|
||||
var index = 56
|
||||
|
||||
for field in msg.fields {
|
||||
serializedData[index...(index + 3)] = UInt32(field.key.count).data
|
||||
serializedData[(index + 4)...(index + 7)] = UInt32(field.key.count).data
|
||||
|
||||
index += 8
|
||||
|
||||
serializedData[index..<(index + field.key.count)] = Data(field.key)
|
||||
|
||||
index += field.key.count
|
||||
|
||||
serializedData[index..<(index + field.value.count)] = Data(field.value)
|
||||
|
||||
index += field.value.count
|
||||
}
|
||||
|
||||
let paddingBytes = (8 - (index % 8)) % 8
|
||||
|
||||
serializedData[index..<(index + paddingBytes)] = Data(count: paddingBytes)
|
||||
index += paddingBytes
|
||||
|
||||
// Add STOP FIELDS
|
||||
serializedData[index..<(index + 8)] = UInt64(0).data
|
||||
print(UInt64(0).data.base64EncodedString())
|
||||
index += 8
|
||||
serializedData[index..<serializedData.count] = Data(msg.signature)
|
||||
|
||||
if serializedData.count % 8 != 0 {
|
||||
print(serializedData.count % 8)
|
||||
print(MESSAGE_CAPACITY % 8)
|
||||
}
|
||||
|
||||
return serializedData
|
||||
}
|
||||
|
||||
public func derializeV1(serializedData: Data) throws -> Message {
|
||||
|
||||
// Serialize Header
|
||||
|
||||
// UGLY: extrapolate in functions
|
||||
let version: UInt8 = serializedData[0]
|
||||
let messageType: MessageType = MessageType(rawValue: serializedData[1])!
|
||||
let devType: DeviceType = DeviceType(rawValue: serializedData[2])!
|
||||
let RESERVED: UInt8 = serializedData[3]
|
||||
let signType: SignType = SignType(rawValue: serializedData[4...7].uint32)!
|
||||
// First 8 bytes
|
||||
|
||||
let signBytes = try signatureBytes(signature: signType)
|
||||
|
||||
let timestamp = serializedData[8...15].timestamp
|
||||
// 8 Bytes
|
||||
|
||||
let devID = serializedData[16...31].uint128
|
||||
// 16 bytes
|
||||
|
||||
let locationX = serializedData[32...39].uint64
|
||||
// 8 Bytes
|
||||
|
||||
let locationY = serializedData[40...47].uint64
|
||||
// 8 Bytes
|
||||
|
||||
let locationZ = serializedData[48...55].uint64
|
||||
// 8 Bytes
|
||||
|
||||
var index = 56
|
||||
|
||||
// Deserializing Fields
|
||||
var MORE_FIELDS = true
|
||||
var fields: [Field] = []
|
||||
|
||||
while MORE_FIELDS {
|
||||
|
||||
let nextChunk = serializedData[index..<(index + 8)]
|
||||
let fieldKeyCount = serializedData[index...(index + 4)].uint32
|
||||
let fieldValueCount = serializedData[(index + 4)..<(index + 8)].uint32
|
||||
|
||||
index += 8
|
||||
|
||||
let a = nextChunk.uint64
|
||||
|
||||
if nextChunk.uint64 == 0 {
|
||||
MORE_FIELDS = false
|
||||
continue
|
||||
}
|
||||
|
||||
let key = serializedData[index..<(index + Int(fieldKeyCount))].map { value in
|
||||
return value
|
||||
}
|
||||
index += Int(fieldKeyCount)
|
||||
let value = serializedData[index..<(index + Int(fieldValueCount))].map { value in
|
||||
return value
|
||||
}
|
||||
index += Int(fieldValueCount)
|
||||
|
||||
fields.append(Field(key: key, value: value))
|
||||
|
||||
}
|
||||
|
||||
let paddingBytes = (8 - (index % 8)) % 8
|
||||
|
||||
// Skip padding bytes
|
||||
index += paddingBytes
|
||||
|
||||
let signature = serializedData[index..<serializedData.count].map { value in
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Sanity check signature with signatureType
|
||||
if signature.count != signBytes {
|
||||
throw DeserializationError.UNMATCHING_SIGNATURE_TYPE
|
||||
}
|
||||
|
||||
return Message(
|
||||
version: version,
|
||||
messageType: messageType,
|
||||
devType: devType,
|
||||
RESERVED: RESERVED,
|
||||
signType: signType,
|
||||
timestamp: timestamp,
|
||||
devID: devID,
|
||||
location: Location(x: locationX, y: locationY, z: locationZ),
|
||||
fields: fields,
|
||||
signature: signature
|
||||
)
|
||||
}
|
||||
|
||||
public func countBytes(msg: Message) throws -> Int {
|
||||
|
||||
///
|
||||
/// Author: Christian Risi
|
||||
///
|
||||
/// This is computed as all the bits for the fixed fields
|
||||
///
|
||||
/// In this case we have:
|
||||
/// - 4 B --> Generic Info
|
||||
/// - 4 B --> Timestamp
|
||||
/// - 8 B --> Device ID
|
||||
/// - 12 B --> Device Location
|
||||
/// - 8 B --> FIELDS STOP
|
||||
let INITIAL_CAPACITY_BYTES: Int = 36
|
||||
let FIELD_HEADER_CAPACITY_BYTES: Int = 4
|
||||
|
||||
var fieldReveservedCapacity: Int = 0
|
||||
|
||||
for field in msg.fields {
|
||||
fieldReveservedCapacity += FIELD_HEADER_CAPACITY_BYTES + field.key.count + field.value.count
|
||||
}
|
||||
|
||||
// UGLY: We are assuming P521 signature
|
||||
let SIGNATURE_CAPACITY_BYTES: Int = try signatureBytes(signature: msg.signType)
|
||||
|
||||
return (INITIAL_CAPACITY_BYTES + fieldReveservedCapacity + SIGNATURE_CAPACITY_BYTES)
|
||||
|
||||
}
|
||||
|
||||
public func signatureBytes(signature: SignType) throws -> Int {
|
||||
switch signature {
|
||||
|
||||
case .P521:
|
||||
return 132
|
||||
|
||||
default:
|
||||
throw CommonError.SIGNATURE_NOT_SUPPORTED
|
||||
}
|
||||
}
|
||||
8
Sources/MessageUtils/Structs/Field.swift
Normal file
8
Sources/MessageUtils/Structs/Field.swift
Normal file
@ -0,0 +1,8 @@
|
||||
import Foundation
|
||||
|
||||
public struct Field {
|
||||
|
||||
public let key: [UInt8]
|
||||
public let value: [UInt8]
|
||||
|
||||
}
|
||||
7
Sources/MessageUtils/Structs/Location.swift
Normal file
7
Sources/MessageUtils/Structs/Location.swift
Normal file
@ -0,0 +1,7 @@
|
||||
public struct Location {
|
||||
|
||||
public let x: UInt64
|
||||
public let y: UInt64
|
||||
public let z: UInt64
|
||||
|
||||
}
|
||||
37
Sources/MessageUtils/Structs/Message.swift
Normal file
37
Sources/MessageUtils/Structs/Message.swift
Normal file
@ -0,0 +1,37 @@
|
||||
import Foundation
|
||||
|
||||
public struct Message{
|
||||
|
||||
public let version: UInt8
|
||||
public let messageType: MessageType
|
||||
public let devType: DeviceType
|
||||
public let RESERVED: UInt8
|
||||
public let signType: SignType
|
||||
|
||||
public let timestamp : Date
|
||||
public let devID : UInt128
|
||||
public let location: Location
|
||||
public let fields: [Field]
|
||||
public let signature: [UInt8]
|
||||
|
||||
|
||||
public func toString() -> String{
|
||||
var description = ""
|
||||
description += "MESSAGE -------\n"
|
||||
description += "V: \t\(self.version)\n"
|
||||
description += "Message Type: \t\(self.messageType.rawValue)\n"
|
||||
description += "Device Type: \t\(self.devType.rawValue)\n"
|
||||
description += "RESERVED: \t\(self.RESERVED)\n"
|
||||
description += "Signature Type: \t\(self.signType.rawValue)\n"
|
||||
description += "Timestamp: \t\(self.timestamp)\n"
|
||||
description += "Device ID: \t\(self.devID)\n"
|
||||
description += "Location: \tX: \(self.location.x)\tY: \(self.location.y)\tZ: \(self.location.z)\n"
|
||||
description += "Fields: \n"
|
||||
for field in self.fields {
|
||||
description += "\t\(String(data: Data(field.key), encoding: .ascii) ?? "UNABLE TO DECODE"): \(String(data: Data(field.value), encoding: .ascii) ?? "UNABLE TO DECODE")"
|
||||
}
|
||||
description += "Signature: \t\(self.signature)\n"
|
||||
|
||||
return description
|
||||
}
|
||||
}
|
||||
5
Sources/MessageUtils/Utils/DataCompatibleP.swift
Normal file
5
Sources/MessageUtils/Utils/DataCompatibleP.swift
Normal file
@ -0,0 +1,5 @@
|
||||
import Foundation
|
||||
|
||||
public protocol DataCompatibleP {
|
||||
var data : Data {get}
|
||||
}
|
||||
32
Sources/MessageUtils/Utils/DataToUtils.swift
Normal file
32
Sources/MessageUtils/Utils/DataToUtils.swift
Normal file
@ -0,0 +1,32 @@
|
||||
import Foundation
|
||||
|
||||
public extension Data {
|
||||
|
||||
var uint8 : UInt8 {
|
||||
return self.withUnsafeBytes{ $0.bindMemory(to: UInt8.self) }[0]
|
||||
}
|
||||
|
||||
var uint16: UInt16 {
|
||||
return self.withUnsafeBytes{ $0.bindMemory(to: UInt16.self) }[0]
|
||||
}
|
||||
|
||||
var uint32: UInt32 {
|
||||
return self.withUnsafeBytes{ $0.bindMemory(to: UInt32.self) }[0]
|
||||
}
|
||||
|
||||
var uint64: UInt64 {
|
||||
return self.withUnsafeBytes{ $0.bindMemory(to: UInt64.self) }[0]
|
||||
}
|
||||
|
||||
var uint128: UInt128 {
|
||||
return self.withUnsafeBytes{ $0.bindMemory(to: UInt128.self) }[0]
|
||||
}
|
||||
|
||||
var uint: UInt {
|
||||
return self.withUnsafeBytes{ $0.bindMemory(to: UInt.self) }[0]
|
||||
}
|
||||
|
||||
var timestamp: Date {
|
||||
return self.withUnsafeBytes{ $0.bindMemory(to: Date.self) }[0]
|
||||
}
|
||||
}
|
||||
52
Sources/MessageUtils/Utils/MessageBytes.md
Normal file
52
Sources/MessageUtils/Utils/MessageBytes.md
Normal file
@ -0,0 +1,52 @@
|
||||
# Message Structure
|
||||
```
|
||||
+-+ 64 - bits---+----------------+-------------+----------------+-----------------------+
|
||||
|0| Version 7 | Message Type 8 | Dev Type 8 | RESERVED 16 | Sign Type 32 |
|
||||
+-+-------------+----------------+-------------+----------------+-----------------------+
|
||||
| Timestamp 64 |
|
||||
+---------------------------------------------------------------------------------------+
|
||||
| DevID 128 bits |
|
||||
| |
|
||||
+---------------------------------------------------------------------------------------+
|
||||
| Location 192 bits |
|
||||
| |
|
||||
| |
|
||||
+---------------------------------------------------------------------------------------+
|
||||
\ /
|
||||
| Fields -----------------------------------------------------------------------------|
|
||||
/ \
|
||||
+---------------------------------------------------------------------------------------+
|
||||
| 0 Padding 64 - n |
|
||||
+---------------------------------------------------------------------------------------+
|
||||
| Signature up to-512 bits |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
+---------------------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
# Fields
|
||||
each field can be at most 8 * 2^32 bits of length:
|
||||
- 34359738368 bits
|
||||
- 4294967296 Bytes
|
||||
|
||||
## Key
|
||||
**MUST** be a `String`
|
||||
|
||||
## Value
|
||||
It's up to the Application to decide whether the value is
|
||||
a string or another type of datum
|
||||
|
||||
```
|
||||
+-- 64 - bits--------------------------+----------------------------------------+
|
||||
| Key-Length 32 | Value-Length 32 |
|
||||
+--------------------------------------+----------------------------------------+
|
||||
\ Key /
|
||||
|-----------------------------------------------------------------------------|
|
||||
/ Value \
|
||||
+-------------------------------------------------------------------------------+
|
||||
```
|
||||
43
Sources/MessageUtils/Utils/ToDataUtils.swift
Normal file
43
Sources/MessageUtils/Utils/ToDataUtils.swift
Normal file
@ -0,0 +1,43 @@
|
||||
import Foundation
|
||||
|
||||
extension UInt8: DataCompatibleP {
|
||||
public var data: Data {
|
||||
var obj = self
|
||||
return Data(bytes: &obj, count: MemoryLayout<Self>.stride)
|
||||
}
|
||||
}
|
||||
|
||||
extension UInt16: DataCompatibleP {
|
||||
public var data: Data {
|
||||
var obj = self
|
||||
return Data(bytes: &obj, count: MemoryLayout<Self>.stride)
|
||||
}
|
||||
}
|
||||
|
||||
extension UInt32: DataCompatibleP {
|
||||
public var data: Data {
|
||||
var obj = self
|
||||
return Data(bytes: &obj, count: MemoryLayout<Self>.stride)
|
||||
}
|
||||
}
|
||||
|
||||
extension UInt64: DataCompatibleP {
|
||||
public var data: Data {
|
||||
var obj = self
|
||||
return Data(bytes: &obj, count: MemoryLayout<Self>.stride)
|
||||
}
|
||||
}
|
||||
|
||||
extension UInt128: DataCompatibleP {
|
||||
public var data: Data {
|
||||
var obj = self
|
||||
return Data(bytes: &obj, count: MemoryLayout<Self>.stride)
|
||||
}
|
||||
}
|
||||
|
||||
extension Date: DataCompatibleP {
|
||||
public var data: Data {
|
||||
var obj = self
|
||||
return Data(bytes: &obj, count: MemoryLayout<Self>.stride)
|
||||
}
|
||||
}
|
||||
87
Tests/MessageUtilsTests/MessageUtilsTests.swift
Normal file
87
Tests/MessageUtilsTests/MessageUtilsTests.swift
Normal file
@ -0,0 +1,87 @@
|
||||
import Foundation
|
||||
|
||||
import Testing
|
||||
@testable import MessageUtils
|
||||
|
||||
|
||||
|
||||
@Test func serializeMessage() async throws {
|
||||
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
|
||||
let msg = Message(
|
||||
version: 1,
|
||||
messageType: .KEEPALIVE,
|
||||
devType: .EDGE_SENSOR,
|
||||
RESERVED: 0,
|
||||
signType: .P521,
|
||||
timestamp: Date(),
|
||||
devID: 1,
|
||||
location: Location(x: 10,y: 20,z: 1),
|
||||
fields: [],
|
||||
signature: Array(repeating: 255, count: 132)
|
||||
)
|
||||
|
||||
let data = try serializeV1(msg: msg)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test func serializeMessageWithField() async throws {
|
||||
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
|
||||
let msg = Message(
|
||||
version: 1,
|
||||
messageType: .KEEPALIVE,
|
||||
devType: .EDGE_SENSOR,
|
||||
RESERVED: 0,
|
||||
signType: .P521,
|
||||
timestamp: Date(),
|
||||
devID: 1,
|
||||
location: Location(x: 10,y: 20,z: 1),
|
||||
fields: [
|
||||
Field(key: Array("valueOfLife".utf8), value: Array("42".utf8)),
|
||||
Field(key: Array("valueOfLife".utf8), value: Array("42".utf8))
|
||||
],
|
||||
signature: Array(repeating: 255, count: 132)
|
||||
)
|
||||
|
||||
let data = try serializeV1(msg: msg)
|
||||
|
||||
}
|
||||
|
||||
@Test func serializeDeserializeMessage() async throws {
|
||||
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
|
||||
let msg = Message(
|
||||
version: 1,
|
||||
messageType: .KEEPALIVE,
|
||||
devType: .EDGE_SENSOR,
|
||||
RESERVED: 0,
|
||||
signType: .P521,
|
||||
timestamp: Date(),
|
||||
devID: 1,
|
||||
location: Location(x: 10,y: 20,z: 1),
|
||||
fields: [
|
||||
Field(key: Array("valueOfLife".utf8), value: Array("42".utf8)),
|
||||
Field(key: Array("valueOfLife".utf8), value: Array("42".utf8))
|
||||
],
|
||||
signature: Array(repeating: 255, count: 132)
|
||||
)
|
||||
|
||||
print(msg.toString())
|
||||
|
||||
let data = try serializeV1(msg: msg)
|
||||
|
||||
let bytes = data.map { value in
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
|
||||
print(bytes.count % 8 == 0)
|
||||
|
||||
|
||||
let msg2 = try derializeV1(serializedData: data)
|
||||
print(msg2.toString())
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user