1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
//
// CBOR.swift
// AT-IRC
//
// Minimal CBOR decoder for atirc's Event Stream v0 framing
// (two concatenated CBOR values: header object + body object).
//
import Foundation
indirect enum CBORValue {
case unsigned(UInt64)
case negative(Int64)
case bytes(Data)
case text(String)
case array([CBORValue])
case map([(CBORValue, CBORValue)])
case bool(Bool)
case null
case float(Double)
case tag(UInt64, CBORValue)
case undefined
var string: String? {
if case .text(let value) = self { return value }
return nil
}
var int: Int? {
switch self {
case .unsigned(let value): return Int(value)
case .negative(let value): return Int(value)
default: return nil
}
}
subscript(key: String) -> CBORValue? {
guard case .map(let pairs) = self else { return nil }
for (candidate, value) in pairs where candidate.string == key {
return value
}
return nil
}
/// Convert into JSON-compatible Foundation objects.
func jsonObject() -> Any {
switch self {
case .unsigned(let value): return value
case .negative(let value): return value
case .bytes(let data): return Base64URL.encode(data)
case .text(let value): return value
case .array(let values): return values.map { $0.jsonObject() }
case .map(let pairs):
var result: [String: Any] = [:]
for (key, value) in pairs {
if let name = key.string {
result[name] = value.jsonObject()
}
}
return result
case .bool(let value): return value
case .null, .undefined: return NSNull()
case .float(let value): return value
case .tag(_, let value): return value.jsonObject()
}
}
}
struct CBORDecodingError: Error, CustomStringConvertible {
let description: String
}
struct CBORDecoder {
private let bytes: [UInt8]
private var index = 0
init(_ data: Data) {
bytes = [UInt8](data)
}
var isAtEnd: Bool { index >= bytes.count }
mutating func decode() throws -> CBORValue {
let initial = try readByte()
let major = initial >> 5
let additional = initial & 0x1F
switch major {
case 0:
return .unsigned(try readLength(additional))
case 1:
let magnitude = try readLength(additional)
return .negative(-1 - Int64(magnitude))
case 2:
return .bytes(try readBytes(Int(try readLength(additional))))
case 3:
let data = try readBytes(Int(try readLength(additional)))
return .text(String(decoding: data, as: UTF8.self))
case 4:
let count = try readLength(additional)
var values: [CBORValue] = []
values.reserveCapacity(Int(count))
for _ in 0..<count { values.append(try decode()) }
return .array(values)
case 5:
let count = try readLength(additional)
var pairs: [(CBORValue, CBORValue)] = []
pairs.reserveCapacity(Int(count))
for _ in 0..<count {
let key = try decode()
let value = try decode()
pairs.append((key, value))
}
return .map(pairs)
case 6:
let tag = try readLength(additional)
return .tag(tag, try decode())
case 7:
switch additional {
case 20: return .bool(false)
case 21: return .bool(true)
case 22: return .null
case 23: return .undefined
case 25: return .float(Double(try readUInt16()))
case 26:
let bits = try readUInt32()
return .float(Double(Float(bitPattern: bits)))
case 27:
let bits = try readUInt64()
return .float(Double(bitPattern: bits))
default:
if additional < 24 { return .undefined }
throw CBORDecodingError(description: "unsupported simple value \(additional)")
}
default:
throw CBORDecodingError(description: "unsupported major type \(major)")
}
}
private mutating func readByte() throws -> UInt8 {
guard index < bytes.count else {
throw CBORDecodingError(description: "unexpected end of CBOR input")
}
defer { index += 1 }
return bytes[index]
}
private mutating func readBytes(_ count: Int) throws -> Data {
guard count >= 0, index + count <= bytes.count else {
throw CBORDecodingError(description: "CBOR string length out of bounds")
}
defer { index += count }
return Data(bytes[index..<(index + count)])
}
private mutating func readUInt16() throws -> UInt16 {
let data = try readBytes(2)
return data.reduce(0) { ($0 << 8) | UInt16($1) }
}
private mutating func readUInt32() throws -> UInt32 {
let data = try readBytes(4)
return data.reduce(0) { ($0 << 8) | UInt32($1) }
}
private mutating func readUInt64() throws -> UInt64 {
let data = try readBytes(8)
return data.reduce(0) { ($0 << 8) | UInt64($1) }
}
private mutating func readLength(_ additional: UInt8) throws -> UInt64 {
switch additional {
case 0...23: return UInt64(additional)
case 24: return UInt64(try readByte())
case 25: return UInt64(try readUInt16())
case 26: return UInt64(try readUInt32())
case 27: return try readUInt64()
case 31: throw CBORDecodingError(description: "indefinite-length CBOR is not supported")
default: throw CBORDecodingError(description: "invalid additional info \(additional)")
}
}
}
/// One atirc WebSocket frame: Event Stream v0 header + body.
struct AtircFrame {
let op: Int?
let type: String?
let error: String?
let body: [String: Any]
static func decode(_ data: Data) throws -> AtircFrame {
var decoder = CBORDecoder(data)
let header = try decoder.decode()
let body = try decoder.decode()
let headerObject = header.jsonObject() as? [String: Any] ?? [:]
let bodyObject = body.jsonObject() as? [String: Any] ?? [:]
return AtircFrame(
op: headerObject["op"] as? Int,
type: headerObject["t"] as? String,
error: headerObject["error"] as? String,
body: bodyObject
)
}
}