forked from mongodb/swift-bson
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathBSONBinary.swift
291 lines (261 loc) · 11.9 KB
/
BSONBinary.swift
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
import ExtrasBase64
import Foundation
import NIO
/// A struct to represent the BSON Binary type.
public struct BSONBinary: Equatable, Hashable {
/// The binary data.
public let data: ByteBuffer
/// The binary subtype for this data.
public let subtype: Subtype
/// Subtypes for BSON Binary values.
public struct Subtype: Equatable, Hashable, RawRepresentable, Codable {
// swiftlint:disable force_unwrapping
/// Generic binary subtype
public static let generic = Subtype(rawValue: 0x00)!
/// A function
public static let function = Subtype(rawValue: 0x01)!
/// Binary (old)
public static let binaryDeprecated = Subtype(rawValue: 0x02)!
/// UUID (old)
public static let uuidDeprecated = Subtype(rawValue: 0x03)!
/// UUID (RFC 4122)
public static let uuid = Subtype(rawValue: 0x04)!
/// MD5
public static let md5 = Subtype(rawValue: 0x05)!
/// Encrypted BSON value
public static let encryptedValue = Subtype(rawValue: 0x06)!
// swiftlint:enable force_unwrapping
/// Subtype indicator value
public let rawValue: UInt8
/// Initializes a `Subtype` with a custom value.
/// Returns nil if rawValue within reserved range [0x07, 0x80).
public init?(rawValue: UInt8) {
guard !(rawValue > 0x06 && rawValue < 0x80) else {
return nil
}
self.rawValue = rawValue
}
/// Initializes a `Subtype` with a custom value. This value must be in the range 0x80-0xFF.
/// - Throws:
/// - `BSONError.InvalidArgumentError` if value passed is outside of the range 0x80-0xFF
public static func userDefined(_ value: Int) throws -> Subtype {
guard let byteValue = UInt8(exactly: value) else {
throw BSONError.InvalidArgumentError(message: "Cannot represent \(value) as UInt8")
}
guard byteValue >= 0x80 else {
throw BSONError.InvalidArgumentError(
message: "userDefined value must be greater than or equal to 0x80 got \(byteValue)"
)
}
guard let subtype = Subtype(rawValue: byteValue) else {
throw BSONError.InvalidArgumentError(message: "Cannot represent \(byteValue) as Subtype")
}
return subtype
}
}
/// Initializes a `BSONBinary` instance from a `UUID`.
/// - Throws:
/// - `BSONError.InvalidArgumentError` if a `BSONBinary` cannot be constructed from this UUID.
public init(from uuid: UUID) throws {
let uuidt = uuid.uuid
let uuidData = Data([
uuidt.0, uuidt.1, uuidt.2, uuidt.3,
uuidt.4, uuidt.5, uuidt.6, uuidt.7,
uuidt.8, uuidt.9, uuidt.10, uuidt.11,
uuidt.12, uuidt.13, uuidt.14, uuidt.15
])
self = try BSONBinary(data: uuidData, subtype: BSONBinary.Subtype.uuid)
}
/// Initializes a `BSONBinary` instance from a `Data` object and a `Subtype` subtype.
/// This will always create a copy of the data.
/// - Throws:
/// - `BSONError.InvalidArgumentError` if the provided data is incompatible with the specified subtype.
public init(data: Data, subtype: Subtype) throws {
var buffer = BSON_ALLOCATOR.buffer(capacity: data.count)
buffer.writeBytes(data)
self = try BSONBinary(buffer: buffer, subtype: subtype)
}
internal init(bytes: [UInt8], subtype: Subtype) throws {
var buffer = BSON_ALLOCATOR.buffer(capacity: bytes.count)
buffer.writeBytes(bytes)
self = try BSONBinary(buffer: buffer, subtype: subtype)
}
internal init(buffer: ByteBuffer, subtype: Subtype) throws {
if [Subtype.uuid, Subtype.uuidDeprecated].contains(subtype) && buffer.readableBytes != 16 {
throw BSONError.InvalidArgumentError(
message:
"Binary data with UUID subtype must be 16 bytes, but buffer has \(buffer.readableBytes) bytes"
)
}
self.subtype = subtype
self.data = buffer
}
/// Initializes a `BSONBinary` instance from a base64 `String` and a `Subtype`.
/// - Throws:
/// - `BSONError.InvalidArgumentError` if the base64 `String` is invalid or if the provided data is
/// incompatible with the specified subtype.
public init(base64: String, subtype: Subtype) throws {
do {
let bytes = try base64.base64decoded()
try self.init(bytes: bytes, subtype: subtype)
} catch let error as ExtrasBase64.DecodingError {
throw BSONError.InvalidArgumentError(
message: "failed to create Data object from invalid base64 string \(base64): \(error)"
)
}
}
/// Converts this `BSONBinary` instance to a `UUID`.
/// - Throws:
/// - `BSONError.InvalidArgumentError` if a non-UUID subtype is set on this `BSONBinary`.
public func toUUID() throws -> UUID {
guard [Subtype.uuid, Subtype.uuidDeprecated].contains(self.subtype) else {
throw BSONError.InvalidArgumentError(
message: "Expected a UUID binary subtype, got subtype \(self.subtype) instead."
)
}
guard let data = self.data.getBytes(at: 0, length: 16) else {
throw BSONError.InternalError(message: "Unable to read 16 bytes from Binary.data")
}
let uuid: uuid_t = (
data[0], data[1], data[2], data[3],
data[4], data[5], data[6], data[7],
data[8], data[9], data[10], data[11],
data[12], data[13], data[14], data[15]
)
return UUID(uuid: uuid)
}
}
extension BSONBinary: BSONValue {
internal static let extJSONTypeWrapperKeys: [String] = ["$binary", "$uuid"]
/*
* Initializes a `Binary` from ExtendedJSON.
*
* Parameters:
* - `json`: a `JSON` representing the canonical or relaxed form of ExtendedJSON for a `Binary`.
* - `keyPath`: an array of `String`s containing the enclosing JSON keys of the current json being passed in.
* This is used for error messages.
*
* Returns:
* - `nil` if the provided value does not conform to the `Binary` syntax.
*
* Throws:
* - `DecodingError` if `json` is a partial match or is malformed.
*/
internal init?(fromExtJSON json: JSON, keyPath: [String]) throws {
if let uuidJSON = try json.value.unwrapObject(withKey: "$uuid", keyPath: keyPath) {
guard let uuidString = uuidJSON.stringValue else {
throw Swift.DecodingError._extendedJSONError(
keyPath: keyPath,
debugDescription: "Expected value for key $uuid \"\(uuidJSON)\" to be a string"
+ " but got some other value"
)
}
guard let uuid = UUID(uuidString: uuidString) else {
throw Swift.DecodingError._extendedJSONError(
keyPath: keyPath,
debugDescription: "Invalid UUID string: \(uuidString)"
)
}
do {
self = try BSONBinary(from: uuid)
return
} catch {
throw Swift.DecodingError._extendedJSONError(
keyPath: keyPath,
debugDescription: error.localizedDescription
)
}
}
// canonical and relaxed extended JSON
guard let binary = try json.value.unwrapObject(withKey: "$binary", keyPath: keyPath) else {
return nil
}
guard
let (base64, subTypeInput) = try binary.unwrapObject(withKeys: "base64", "subType", keyPath: keyPath)
else {
throw Swift.DecodingError._extendedJSONError(
keyPath: keyPath,
debugDescription: "Missing \"base64\" or \"subType\" in \(binary)"
)
}
guard let base64Str = base64.stringValue else {
throw Swift.DecodingError._extendedJSONError(
keyPath: keyPath,
debugDescription: "Could not parse `base64` from \"\(base64)\", " +
"input must be a base64-encoded (with padding as =) payload as a string"
)
}
guard
let subTypeStr = subTypeInput.stringValue,
let subTypeInt = UInt8(subTypeStr, radix: 16),
let subType = Subtype(rawValue: subTypeInt)
else {
throw Swift.DecodingError._extendedJSONError(
keyPath: keyPath,
debugDescription: "Could not parse `SubType` from \"\(subTypeInput)\", " +
"input must be a BSON binary type as a one- or two-character hex string"
)
}
do {
self = try BSONBinary(base64: base64Str, subtype: subType)
} catch {
throw Swift.DecodingError._extendedJSONError(
keyPath: keyPath,
debugDescription: error.localizedDescription
)
}
}
/// Converts this `BSONBinary` to a corresponding `JSON` in relaxed extendedJSON format.
internal func toRelaxedExtendedJSON() -> JSON {
self.toCanonicalExtendedJSON()
}
/// Converts this `BSONBinary` to a corresponding `JSON` in canonical extendedJSON format.
internal func toCanonicalExtendedJSON() -> JSON {
[
"$binary": [
"base64": JSON(.string(Data(self.data.readableBytesView).base64EncodedString())),
"subType": JSON(.string(String(format: "%02x", self.subtype.rawValue)))
]
]
}
internal static var bsonType: BSONType { .binary }
internal var bson: BSON { .binary(self) }
internal static func read(from buffer: inout ByteBuffer) throws -> BSON {
guard let byteLength = buffer.readInteger(endianness: .little, as: Int32.self), byteLength >= 0 else {
throw BSONError.InternalError(message: "Cannot read BSONBinary's byte length")
}
guard let subtypeByte = buffer.readInteger(as: UInt8.self) else {
throw BSONError.InternalError(message: "Cannot read BSONBinary's subtype")
}
guard let subtype = Subtype(rawValue: subtypeByte) else {
throw BSONError.InternalError(message: "Cannot create subtype for 0x\(String(subtypeByte, radix: 16))")
}
guard subtype != .binaryDeprecated else {
guard let oldSize = buffer.readInteger(endianness: .little, as: Int32.self) else {
throw BSONError.InternalError(message: "Cannot read BSONBinary's old byte length")
}
guard oldSize == (byteLength - 4) else {
throw BSONError.InternalError(message: "Invalid size for BSONBinary subtype: \(subtype)")
}
guard let bytes = buffer.readBytes(length: Int(oldSize)) else {
throw BSONError.InternalError(message: "Cannot read \(oldSize) from buffer for BSONBinary")
}
return .binary(try BSONBinary(bytes: bytes, subtype: subtype))
}
guard let bytes = buffer.readBytes(length: Int(byteLength)) else {
throw BSONError.InternalError(message: "Cannot read \(byteLength) from buffer for BSONBinary")
}
return .binary(try BSONBinary(bytes: bytes, subtype: subtype))
}
internal func write(to buffer: inout ByteBuffer) {
if self.subtype == .binaryDeprecated {
buffer.writeInteger(Int32(self.data.readableBytes + 4), endianness: .little, as: Int32.self)
buffer.writeInteger(self.subtype.rawValue, as: UInt8.self)
buffer.writeInteger(Int32(self.data.readableBytes), endianness: .little, as: Int32.self)
} else {
buffer.writeInteger(Int32(self.data.readableBytes), endianness: .little, as: Int32.self)
buffer.writeInteger(self.subtype.rawValue, as: UInt8.self)
}
buffer.writeBytes(self.data.readableBytesView)
}
}