blob: 812df25c1a0868907c18af2be4c5f176b5cae52a [file] [log] [blame]
Chris Simpsona9b6c702018-04-08 07:11:37 -04001/*
2* Licensed to the Apache Software Foundation (ASF) under one
3* or more contributor license agreements. See the NOTICE file
4* distributed with this work for additional information
5* regarding copyright ownership. The ASF licenses this file
6* to you under the Apache License, Version 2.0 (the
7* "License"); you may not use this file except in compliance
8* with the License. You may obtain a copy of the License at
9*
10* http://www.apache.org/licenses/LICENSE-2.0
11*
12* Unless required by applicable law or agreed to in writing,
13* software distributed under the License is distributed on an
14* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15* KIND, either express or implied. See the License for the
16* specific language governing permissions and limitations
17* under the License.
18*/
19
20import Foundation
21import CoreFoundation
22
23public enum TCType: UInt8 {
24 case stop = 0x00
25 case boolean_TRUE = 0x01
26 case boolean_FALSE = 0x02
27 case i8 = 0x03
28 case i16 = 0x04
29 case i32 = 0x05
30 case i64 = 0x06
31 case double = 0x07
32 case binary = 0x08
33 case list = 0x09
34 case set = 0x0A
35 case map = 0x0B
36 case `struct` = 0x0C
Kino Roya9da9eb2022-10-07 23:13:01 -070037 case uuid = 0x0D
Chris Simpsona9b6c702018-04-08 07:11:37 -040038
39 public static let typeMask: UInt8 = 0xE0 // 1110 0000
40 public static let typeBits: UInt8 = 0x07 // 0000 0111
41 public static let typeShiftAmount = 5
42
43}
44
45
46public class TCompactProtocol: TProtocol {
47 public static let protocolID: UInt8 = 0x82
48 public static let version: UInt8 = 1
49 public static let versionMask: UInt8 = 0x1F // 0001 1111
50
51 public var transport: TTransport
52
53 var lastField: [UInt8] = []
54 var lastFieldId: UInt8 = 0
55
56 var boolFieldName: String?
57 var boolFieldType: TType?
58 var boolFieldId: Int32?
59 var booleanValue: Bool?
60
61 var currentMessageName: String?
62
63 public required init(on transport: TTransport) {
64 self.transport = transport
65 }
66
67
68 /// Mark: - TCompactProtocol helpers
69
70 func writebyteDirect(_ byte: UInt8) throws {
Antoine Cœur08a6eb62019-07-08 18:42:09 +080071 let byte = Data([byte])
Chris Simpsona9b6c702018-04-08 07:11:37 -040072 try ProtocolTransportTry(error: TProtocolError(message: "Transport Write Failed")) {
73 try self.transport.write(data: byte)
74 }
75 }
76
77 func writeVarint32(_ val: UInt32) throws {
78 var val = val
79 var i32buf = [UInt8](repeating: 0, count: 5)
80 var idx = 0
81 while true {
82 if (val & ~0x7F) == 0 {
83 i32buf[idx] = UInt8(val)
84 idx += 1
85 break
86 } else {
87 i32buf[idx] = UInt8((val & 0x7F) | 0x80)
88 idx += 1
89 val >>= 7
90 }
91 }
92
93 try ProtocolTransportTry(error: TProtocolError(message: "Transport Write Failed")) {
Antoine Cœur08a6eb62019-07-08 18:42:09 +080094 try self.transport.write(data: Data(i32buf[0..<idx]))
Chris Simpsona9b6c702018-04-08 07:11:37 -040095 }
96 }
97
98 func writeVarint64(_ val: UInt64) throws {
99 var val = val
100 var varint64out = [UInt8](repeating: 0, count: 10)
101 var idx = 0
102 while true {
103 if (val & ~0x7F) == 0{
104 varint64out[idx] = UInt8(val)
105 idx += 1
106 break
107 } else {
108 varint64out[idx] = UInt8(val & 0x7F) | 0x80
109 idx += 1
110 val >>= 7
111 }
112 }
113
114 try ProtocolTransportTry(error: TProtocolError(message: "Transport Write Failed")) {
Antoine Cœur08a6eb62019-07-08 18:42:09 +0800115 try self.transport.write(data: Data(varint64out[0..<idx]))
Chris Simpsona9b6c702018-04-08 07:11:37 -0400116 }
Chris Simpsona9b6c702018-04-08 07:11:37 -0400117 }
118
119 func writeCollectionBegin(_ elementType: TType, size: Int32) throws {
120 let ctype = compactType(elementType).rawValue
121 if size <= 14 {
122 try writebyteDirect(UInt8(size << 4) | ctype)
123 } else {
124 try writebyteDirect(0xF0 | ctype)
125 try writeVarint32(UInt32(size))
126 }
127 }
128
129 func readBinary(_ size: Int) throws -> Data {
130 var result = Data()
131 if size != 0 {
132 try ProtocolTransportTry(error: TProtocolError(message: "Transport Read Failed")) {
133 result = try self.transport.readAll(size: size)
134 }
135 }
136 return result
137 }
138
139 func readVarint32() throws -> UInt32 {
140 var result: UInt32 = 0
141 var shift: UInt32 = 0
142 while true {
143 let byte: UInt8 = try read()
144
145 result |= UInt32(byte & 0x7F) << shift
146 if (byte & 0x80) == 0 {
147 break
148 }
149
150 shift += 7
151 }
152
153 return result
154 }
155
156 func readVarint64() throws -> UInt64 {
157 var result: UInt64 = 0
158 var shift: UInt64 = 0
159
160 while true {
161 let byte: UInt8 = try read()
162
163 result |= UInt64(byte & 0x7F) << shift
164 if (byte & 0x80) == 0 {
165 break
166 }
167
168 shift += 7
169 }
170 return result
171 }
172
173
174 func ttype(_ compactTypeVal: UInt8) throws -> TType {
175 guard let compactType = TCType(rawValue: compactTypeVal) else {
176 throw TProtocolError(message: "Unknown TCType value: \(compactTypeVal)")
177 }
178
179 switch compactType {
180 case .stop: return .stop;
181 case .boolean_FALSE, .boolean_TRUE: return .bool;
182 case .i8: return .i8;
183 case .i16: return .i16;
184 case .i32: return .i32;
185 case .i64: return .i64;
186 case .double: return .double;
187 case .binary: return .string;
188 case .list: return .list;
189 case .set: return .set;
190 case .map: return .map;
191 case .struct: return .struct;
Kino Roya9da9eb2022-10-07 23:13:01 -0700192 case .uuid: return .uuid;
Chris Simpsona9b6c702018-04-08 07:11:37 -0400193 }
194 }
195
196 func compactType(_ ttype: TType) -> TCType {
197 switch ttype {
198 case .stop: return .stop
199 case .void: return .i8
200 case .bool: return .boolean_FALSE
201 case .i8: return .i8
202 case .double: return .double
203 case .i16: return .i16
204 case .i32: return .i32
205 case .i64: return .i64
206 case .string: return .binary
207 case .struct: return .struct
208 case .map: return .map
209 case .set: return .set
210 case .list: return .list
Kino Roya9da9eb2022-10-07 23:13:01 -0700211 case .uuid: return .uuid
Chris Simpsona9b6c702018-04-08 07:11:37 -0400212 }
213 }
214
215 /// ZigZag encoding maps signed integers to unsigned integers so that
216 /// numbers with a small absolute value (for instance, -1) have
217 /// a small varint encoded value too. It does this in a way that
218 /// "zig-zags" back and forth through the positive and negative integers,
219 /// so that -1 is encoded as 1, 1 is encoded as 2, -2 is encoded as 3, and so
220 ///
221 /// - parameter n: number to zigzag
222 ///
223 /// - returns: zigzaged UInt32
224 func i32ToZigZag(_ n : Int32) -> UInt32 {
Chris Simpson2566ecd2018-08-29 14:40:44 -0400225 return UInt32(bitPattern: Int32(n << 1) ^ Int32(n >> 31))
Chris Simpsona9b6c702018-04-08 07:11:37 -0400226 }
227
228 func i64ToZigZag(_ n : Int64) -> UInt64 {
Chris Simpson2566ecd2018-08-29 14:40:44 -0400229 return UInt64(bitPattern: Int64(n << 1) ^ Int64(n >> 63))
Chris Simpsona9b6c702018-04-08 07:11:37 -0400230 }
231
232 func zigZagToi32(_ n: UInt32) -> Int32 {
233 return Int32(n >> 1) ^ (-Int32(n & 1))
234 }
235
236 func zigZagToi64(_ n: UInt64) -> Int64 {
237 return Int64(n >> 1) ^ (-Int64(n & 1))
238 }
239
240
241
242 /// Mark: - TProtocol
243
244 public func readMessageBegin() throws -> (String, TMessageType, Int32) {
245 let protocolId: UInt8 = try read()
246
247 if protocolId != TCompactProtocol.protocolID {
248 let expected = String(format:"%2X", TCompactProtocol.protocolID)
249 let got = String(format:"%2X", protocolId)
250 throw TProtocolError(message: "Wrong Protocol ID \(got)",
251 extendedError: .mismatchedProtocol(expected: expected, got: got))
Chris Simpsona9b6c702018-04-08 07:11:37 -0400252 }
253
254 let versionAndType: UInt8 = try read()
255 let version: UInt8 = versionAndType & TCompactProtocol.versionMask
256 if version != TCompactProtocol.version {
257 throw TProtocolError(error: .badVersion(expected: "\(TCompactProtocol.version)",
258 got:"\(version)"))
Chris Simpsona9b6c702018-04-08 07:11:37 -0400259 }
260
261 let type = (versionAndType >> UInt8(TCType.typeShiftAmount)) & TCType.typeBits
262 guard let mtype = TMessageType(rawValue: Int32(type)) else {
263 throw TProtocolError(message: "Unknown TMessageType value: \(type)")
264 }
Kino Roya9da9eb2022-10-07 23:13:01 -0700265 let varint = zigZagToi32(try readVarint32())
266 let sequenceId = Int32(varint)
Chris Simpsona9b6c702018-04-08 07:11:37 -0400267 let name: String = try read()
268
269 return (name, mtype, Int32(sequenceId))
270 }
271
272 public func readMessageEnd() throws { }
273
274 public func readStructBegin() throws -> String {
275 lastField.append(lastFieldId)
276 lastFieldId = 0
277 return ""
278 }
279
280 public func readStructEnd() throws {
281 lastFieldId = lastField.last ?? 0
282 lastField.removeLast()
283 }
284
285 public func readFieldBegin() throws -> (String, TType, Int32) {
286 let byte: UInt8 = try read()
287 guard let type = TCType(rawValue: byte & 0x0F) else {
288 throw TProtocolError(message: "Unknown TCType \(byte & 0x0F)")
289 }
290
291 // if it's a stop, then we can return immediately, as the struct is over
292 if type == .stop {
293 return ("", .stop, 0)
294 }
295
296 var fieldId: Int16 = 0
297
298 // mask off the 4MSB of the type header. it could contain a field id delta
299 let modifier = (byte & 0xF0) >> 4
300 if modifier == 0 {
301 // not a delta. look ahead for the zigzag varint field id
302 fieldId = try read()
303 } else {
304 // has a delta. add the delta to the last Read field id.
305 fieldId = Int16(lastFieldId + modifier)
306 }
307
308 let fieldType = try ttype(type.rawValue)
309
310 // if this happens to be a boolean field, the value is encoded in the type
311 if type == .boolean_TRUE || type == .boolean_FALSE {
312 // save the boolean value in a special instance variable
313 booleanValue = type == .boolean_TRUE
314 }
315
316 // push the new field onto the field stack so we can keep the deltas going
317 lastFieldId = UInt8(fieldId)
318 return ("", fieldType, Int32(fieldId))
319 }
320
321 public func readFieldEnd() throws { }
322
323 public func read() throws -> String {
324 let length = try readVarint32()
325
326 var result: String
327
328 if length != 0 {
329 let data = try readBinary(Int(length))
330 result = String(data: data, encoding: String.Encoding.utf8) ?? ""
331 } else {
332 result = ""
333 }
334
335 return result
336 }
337
338 public func read() throws -> Bool {
339 if let val = booleanValue {
340 self.booleanValue = nil
341 return val
342 } else {
343 let result = try read() as UInt8
344 return TCType(rawValue: result) == .boolean_TRUE
345 }
346 }
347
348 public func read() throws -> UInt8 {
349 var buff: UInt8 = 0
350 try ProtocolTransportTry(error: TProtocolError(message: "Transport Read Failed")) {
351 buff = try self.transport.readAll(size: 1)[0]
352 }
353 return buff
354 }
355
Kino Roya9da9eb2022-10-07 23:13:01 -0700356 public func read() throws -> Int8 {
357 var buff = Data()
358 try ProtocolTransportTry(error: TProtocolError(message: "Transport Read Failed")) {
359 buff = try self.transport.readAll(size: 1)
360 }
361 return buff.withUnsafeBytes { pntr in
362 return pntr.load(as: Int8.self)
363 }
364 }
365
Chris Simpsona9b6c702018-04-08 07:11:37 -0400366 public func read() throws -> Int16 {
367 let v = try readVarint32()
368 return Int16(zigZagToi32(v))
369 }
370
371 public func read() throws -> Int32 {
372 let v = try readVarint32()
373 return zigZagToi32(v)
374 }
375
376 public func read() throws -> Int64 {
377 let v = try readVarint64()
378 return zigZagToi64(v)
379 }
380
381 public func read() throws -> Double {
382 var buff = Data()
383 try ProtocolTransportTry(error: TProtocolError(message: "Transport Read Failed")) {
384 buff = try self.transport.readAll(size: 8)
385 }
Alexander Edgeb4711a62020-04-24 14:43:03 +0100386 let i64: UInt64 = buff.withUnsafeBytes { $0.load(as: UInt64.self) }
Chris Simpsona9b6c702018-04-08 07:11:37 -0400387 let bits = CFSwapInt64LittleToHost(i64)
Chris Simpson2566ecd2018-08-29 14:40:44 -0400388 return Double(bitPattern: bits)
Chris Simpsona9b6c702018-04-08 07:11:37 -0400389 }
390
391 public func read() throws -> Data {
392 let length = try readVarint32()
393 return try readBinary(Int(length))
394 }
395
Kino Roya9da9eb2022-10-07 23:13:01 -0700396 public func read() throws -> UUID {
397 let data = try self.transport.readAll(size: 16)
398 let lsb = data[0..<data.count/2]
399 let msb = data[(data.count/2)..<data.count]
400
401 var id = UUID().uuid
402 withUnsafeMutableBytes(of: &id) { pntr in
403 var copyData = msb
404 copyData.append(lsb)
405 copyData.copyBytes(to: pntr)
406 }
407 return UUID(uuid: id)
408 }
409
Chris Simpsona9b6c702018-04-08 07:11:37 -0400410 public func readMapBegin() throws -> (TType, TType, Int32) {
411 var keyAndValueType: UInt8 = 8
412 let size = try readVarint32()
413 if size != 0 {
414 keyAndValueType = try read()
415 }
416
417 let keyType = try ttype(keyAndValueType >> 4)
418 let valueType = try ttype(keyAndValueType & 0xF)
419
420 return (keyType, valueType, Int32(size))
421 }
422
423 public func readMapEnd() throws { }
424
425 public func readSetBegin() throws -> (TType, Int32) {
426 return try readListBegin()
427 }
428
429 public func readSetEnd() throws { }
430
431 public func readListBegin() throws -> (TType, Int32) {
432 let sizeAndType: UInt8 = try read()
433 var size: UInt32 = UInt32(sizeAndType >> 4) & 0x0f
434 if size == 15 {
435 size = try readVarint32()
436 }
437 let elementType = try ttype(sizeAndType & 0x0F)
438
439 return (elementType, Int32(size))
440 }
441
442 public func readListEnd() throws { }
443
444 public func writeMessageBegin(name: String,
445 type messageType: TMessageType,
446 sequenceID: Int32) throws {
447 try writebyteDirect(TCompactProtocol.protocolID)
448 let nextByte: UInt8 = (TCompactProtocol.version & TCompactProtocol.versionMask) |
449 (UInt8((UInt32(messageType.rawValue) << UInt32(TCType.typeShiftAmount))) &
450 TCType.typeMask)
451 try writebyteDirect(nextByte)
Kino Roya9da9eb2022-10-07 23:13:01 -0700452 try writeVarint32(i32ToZigZag(sequenceID))
Chris Simpsona9b6c702018-04-08 07:11:37 -0400453 try write(name)
454
455 currentMessageName = name
456 }
457
458 public func writeMessageEnd() throws {
459 currentMessageName = nil
460 }
461
462 public func writeStructBegin(name: String) throws {
463 lastField.append(lastFieldId)
464 lastFieldId = 0
465 }
466
467 public func writeStructEnd() throws {
468 lastFieldId = lastField.last ?? 0
469 lastField.removeLast()
470 }
471
472 public func writeFieldBegin(name: String,
473 type fieldType: TType,
474 fieldID: Int32) throws {
475 if fieldType == .bool {
476 boolFieldName = name
477 boolFieldType = fieldType
478 boolFieldId = fieldID
479 return
480 } else {
481 try writeFieldBeginInternal(name: name,
482 type: fieldType,
483 fieldID: fieldID,
484 typeOverride: 0xFF)
485 }
486 }
487
488 func writeFieldBeginInternal(name: String,
489 type fieldType: TType,
490 fieldID: Int32,
491 typeOverride: UInt8) throws {
492
493 let typeToWrite = typeOverride == 0xFF ? compactType(fieldType).rawValue : typeOverride
494
495 // check if we can use delta encoding for the field id
496 let diff = UInt8(fieldID) - lastFieldId
497 if (UInt8(fieldID) > lastFieldId) && (diff <= 15) {
498 // Write them together
499 try writebyteDirect((UInt8(fieldID) - lastFieldId) << 4 | typeToWrite)
500
501 } else {
502 // Write them separate
503 try writebyteDirect(typeToWrite)
504 try write(Int16(fieldID))
505 }
506
507 lastFieldId = UInt8(fieldID)
Chris Simpsona9b6c702018-04-08 07:11:37 -0400508 }
509
510 public func writeFieldStop() throws {
511 try writebyteDirect(TCType.stop.rawValue)
512 }
513
514 public func writeFieldEnd() throws { }
515
516 public func writeMapBegin(keyType: TType, valueType: TType, size: Int32) throws {
517 if size == 0 {
518 try writebyteDirect(0)
519 } else {
520 try writeVarint32(UInt32(size))
521
522 let compactedTypes = compactType(keyType).rawValue << 4 | compactType(valueType).rawValue
523 try writebyteDirect(compactedTypes)
524 }
525 }
526
527 public func writeMapEnd() throws { }
528
529 public func writeSetBegin(elementType: TType, size: Int32) throws {
530 try writeCollectionBegin(elementType, size: size)
531 }
532
533 public func writeSetEnd() throws { }
534
535 public func writeListBegin(elementType: TType, size: Int32) throws {
536 try writeCollectionBegin(elementType, size: size)
537 }
538
539 public func writeListEnd() throws { }
540
541 public func write(_ value: String) throws {
542 try write(value.data(using: String.Encoding.utf8)!)
543 }
544
545 public func write(_ value: Bool) throws {
546 if let boolFieldId = boolFieldId, let boolFieldType = boolFieldType,
547 let boolFieldName = boolFieldName {
548
549 // we haven't written the field header yet
550 let compactType: TCType = value ? .boolean_TRUE : .boolean_FALSE
551 try writeFieldBeginInternal(name: boolFieldName, type: boolFieldType, fieldID: boolFieldId,
552 typeOverride: compactType.rawValue)
553 self.boolFieldId = nil
554 self.boolFieldType = nil
555 self.boolFieldName = nil
556 } else {
557 // we're not part of a field, so just write the value.
558 try writebyteDirect(value ? TCType.boolean_TRUE.rawValue : TCType.boolean_FALSE.rawValue)
559 }
560 }
561
562 public func write(_ value: UInt8) throws {
563 try writebyteDirect(value)
564 }
Kino Roya9da9eb2022-10-07 23:13:01 -0700565
566 public func write(_ value: Int8) throws {
567 var value = value
568 let buff = Data(bytes: &value, count: MemoryLayout<Int8>.size(ofValue: value))
569 try ProtocolTransportTry(error: TProtocolError(message: "Transport write failed")) {
570 try self.transport.write(data: buff)
571 }
572 }
573
Chris Simpsona9b6c702018-04-08 07:11:37 -0400574 public func write(_ value: Int16) throws {
575 try writeVarint32(i32ToZigZag(Int32(value)))
576 }
577
578 public func write(_ value: Int32) throws {
579 try writeVarint32(i32ToZigZag(value))
580 }
581
582 public func write(_ value: Int64) throws {
583 try writeVarint64(i64ToZigZag(value))
584 }
585
586 public func write(_ value: Double) throws {
Chris Simpson2566ecd2018-08-29 14:40:44 -0400587 var bits = CFSwapInt64HostToLittle(value.bitPattern)
Chris Simpsona9b6c702018-04-08 07:11:37 -0400588 let data = withUnsafePointer(to: &bits) {
589 return Data(bytes: UnsafePointer<UInt8>(OpaquePointer($0)), count: MemoryLayout<UInt64>.size)
590 }
591 try ProtocolTransportTry(error: TProtocolError(message: "Transport Write Failed")) {
592 try self.transport.write(data: data)
593 }
594 }
595
596 public func write(_ data: Data) throws {
597 try writeVarint32(UInt32(data.count))
598 try ProtocolTransportTry(error: TProtocolError(message: "Transport Write Failed")) {
599 try self.transport.write(data: data)
600 }
601 }
Kino Roya9da9eb2022-10-07 23:13:01 -0700602
603 public func write(_ value: UUID) throws {
604 let data = withUnsafePointer(to: value.uuid) {
605 Data(bytes: $0, count: MemoryLayout.size(ofValue: value.uuid))
606 }
607 let msb = data[0..<data.count/2]
608 let lsb = data[(data.count/2)..<data.count]
609
610 var buff = Data()
611 buff.append(lsb)
612 buff.append(msb)
613
614 try self.transport.write(data: buff)
615 }
Chris Simpsona9b6c702018-04-08 07:11:37 -0400616}