blob: 482178e8ebaa3e9b2ba0f1218eec15754959401b [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
211 case .utf8: return .binary
Kino Roya9da9eb2022-10-07 23:13:01 -0700212 //case .utf16: return .binary
213 case .uuid: return .uuid
Chris Simpsona9b6c702018-04-08 07:11:37 -0400214 }
215 }
216
217 /// ZigZag encoding maps signed integers to unsigned integers so that
218 /// numbers with a small absolute value (for instance, -1) have
219 /// a small varint encoded value too. It does this in a way that
220 /// "zig-zags" back and forth through the positive and negative integers,
221 /// so that -1 is encoded as 1, 1 is encoded as 2, -2 is encoded as 3, and so
222 ///
223 /// - parameter n: number to zigzag
224 ///
225 /// - returns: zigzaged UInt32
226 func i32ToZigZag(_ n : Int32) -> UInt32 {
Chris Simpson2566ecd2018-08-29 14:40:44 -0400227 return UInt32(bitPattern: Int32(n << 1) ^ Int32(n >> 31))
Chris Simpsona9b6c702018-04-08 07:11:37 -0400228 }
229
230 func i64ToZigZag(_ n : Int64) -> UInt64 {
Chris Simpson2566ecd2018-08-29 14:40:44 -0400231 return UInt64(bitPattern: Int64(n << 1) ^ Int64(n >> 63))
Chris Simpsona9b6c702018-04-08 07:11:37 -0400232 }
233
234 func zigZagToi32(_ n: UInt32) -> Int32 {
235 return Int32(n >> 1) ^ (-Int32(n & 1))
236 }
237
238 func zigZagToi64(_ n: UInt64) -> Int64 {
239 return Int64(n >> 1) ^ (-Int64(n & 1))
240 }
241
242
243
244 /// Mark: - TProtocol
245
246 public func readMessageBegin() throws -> (String, TMessageType, Int32) {
247 let protocolId: UInt8 = try read()
248
249 if protocolId != TCompactProtocol.protocolID {
250 let expected = String(format:"%2X", TCompactProtocol.protocolID)
251 let got = String(format:"%2X", protocolId)
252 throw TProtocolError(message: "Wrong Protocol ID \(got)",
253 extendedError: .mismatchedProtocol(expected: expected, got: got))
Chris Simpsona9b6c702018-04-08 07:11:37 -0400254 }
255
256 let versionAndType: UInt8 = try read()
257 let version: UInt8 = versionAndType & TCompactProtocol.versionMask
258 if version != TCompactProtocol.version {
259 throw TProtocolError(error: .badVersion(expected: "\(TCompactProtocol.version)",
260 got:"\(version)"))
Chris Simpsona9b6c702018-04-08 07:11:37 -0400261 }
262
263 let type = (versionAndType >> UInt8(TCType.typeShiftAmount)) & TCType.typeBits
264 guard let mtype = TMessageType(rawValue: Int32(type)) else {
265 throw TProtocolError(message: "Unknown TMessageType value: \(type)")
266 }
Kino Roya9da9eb2022-10-07 23:13:01 -0700267 let varint = zigZagToi32(try readVarint32())
268 let sequenceId = Int32(varint)
Chris Simpsona9b6c702018-04-08 07:11:37 -0400269 let name: String = try read()
270
271 return (name, mtype, Int32(sequenceId))
272 }
273
274 public func readMessageEnd() throws { }
275
276 public func readStructBegin() throws -> String {
277 lastField.append(lastFieldId)
278 lastFieldId = 0
279 return ""
280 }
281
282 public func readStructEnd() throws {
283 lastFieldId = lastField.last ?? 0
284 lastField.removeLast()
285 }
286
287 public func readFieldBegin() throws -> (String, TType, Int32) {
288 let byte: UInt8 = try read()
289 guard let type = TCType(rawValue: byte & 0x0F) else {
290 throw TProtocolError(message: "Unknown TCType \(byte & 0x0F)")
291 }
292
293 // if it's a stop, then we can return immediately, as the struct is over
294 if type == .stop {
295 return ("", .stop, 0)
296 }
297
298 var fieldId: Int16 = 0
299
300 // mask off the 4MSB of the type header. it could contain a field id delta
301 let modifier = (byte & 0xF0) >> 4
302 if modifier == 0 {
303 // not a delta. look ahead for the zigzag varint field id
304 fieldId = try read()
305 } else {
306 // has a delta. add the delta to the last Read field id.
307 fieldId = Int16(lastFieldId + modifier)
308 }
309
310 let fieldType = try ttype(type.rawValue)
311
312 // if this happens to be a boolean field, the value is encoded in the type
313 if type == .boolean_TRUE || type == .boolean_FALSE {
314 // save the boolean value in a special instance variable
315 booleanValue = type == .boolean_TRUE
316 }
317
318 // push the new field onto the field stack so we can keep the deltas going
319 lastFieldId = UInt8(fieldId)
320 return ("", fieldType, Int32(fieldId))
321 }
322
323 public func readFieldEnd() throws { }
324
325 public func read() throws -> String {
326 let length = try readVarint32()
327
328 var result: String
329
330 if length != 0 {
331 let data = try readBinary(Int(length))
332 result = String(data: data, encoding: String.Encoding.utf8) ?? ""
333 } else {
334 result = ""
335 }
336
337 return result
338 }
339
340 public func read() throws -> Bool {
341 if let val = booleanValue {
342 self.booleanValue = nil
343 return val
344 } else {
345 let result = try read() as UInt8
346 return TCType(rawValue: result) == .boolean_TRUE
347 }
348 }
349
350 public func read() throws -> UInt8 {
351 var buff: UInt8 = 0
352 try ProtocolTransportTry(error: TProtocolError(message: "Transport Read Failed")) {
353 buff = try self.transport.readAll(size: 1)[0]
354 }
355 return buff
356 }
357
Kino Roya9da9eb2022-10-07 23:13:01 -0700358 public func read() throws -> Int8 {
359 var buff = Data()
360 try ProtocolTransportTry(error: TProtocolError(message: "Transport Read Failed")) {
361 buff = try self.transport.readAll(size: 1)
362 }
363 return buff.withUnsafeBytes { pntr in
364 return pntr.load(as: Int8.self)
365 }
366 }
367
Chris Simpsona9b6c702018-04-08 07:11:37 -0400368 public func read() throws -> Int16 {
369 let v = try readVarint32()
370 return Int16(zigZagToi32(v))
371 }
372
373 public func read() throws -> Int32 {
374 let v = try readVarint32()
375 return zigZagToi32(v)
376 }
377
378 public func read() throws -> Int64 {
379 let v = try readVarint64()
380 return zigZagToi64(v)
381 }
382
383 public func read() throws -> Double {
384 var buff = Data()
385 try ProtocolTransportTry(error: TProtocolError(message: "Transport Read Failed")) {
386 buff = try self.transport.readAll(size: 8)
387 }
Alexander Edgeb4711a62020-04-24 14:43:03 +0100388 let i64: UInt64 = buff.withUnsafeBytes { $0.load(as: UInt64.self) }
Chris Simpsona9b6c702018-04-08 07:11:37 -0400389 let bits = CFSwapInt64LittleToHost(i64)
Chris Simpson2566ecd2018-08-29 14:40:44 -0400390 return Double(bitPattern: bits)
Chris Simpsona9b6c702018-04-08 07:11:37 -0400391 }
392
393 public func read() throws -> Data {
394 let length = try readVarint32()
395 return try readBinary(Int(length))
396 }
397
Kino Roya9da9eb2022-10-07 23:13:01 -0700398 public func read() throws -> UUID {
399 let data = try self.transport.readAll(size: 16)
400 let lsb = data[0..<data.count/2]
401 let msb = data[(data.count/2)..<data.count]
402
403 var id = UUID().uuid
404 withUnsafeMutableBytes(of: &id) { pntr in
405 var copyData = msb
406 copyData.append(lsb)
407 copyData.copyBytes(to: pntr)
408 }
409 return UUID(uuid: id)
410 }
411
Chris Simpsona9b6c702018-04-08 07:11:37 -0400412 public func readMapBegin() throws -> (TType, TType, Int32) {
413 var keyAndValueType: UInt8 = 8
414 let size = try readVarint32()
415 if size != 0 {
416 keyAndValueType = try read()
417 }
418
419 let keyType = try ttype(keyAndValueType >> 4)
420 let valueType = try ttype(keyAndValueType & 0xF)
421
422 return (keyType, valueType, Int32(size))
423 }
424
425 public func readMapEnd() throws { }
426
427 public func readSetBegin() throws -> (TType, Int32) {
428 return try readListBegin()
429 }
430
431 public func readSetEnd() throws { }
432
433 public func readListBegin() throws -> (TType, Int32) {
434 let sizeAndType: UInt8 = try read()
435 var size: UInt32 = UInt32(sizeAndType >> 4) & 0x0f
436 if size == 15 {
437 size = try readVarint32()
438 }
439 let elementType = try ttype(sizeAndType & 0x0F)
440
441 return (elementType, Int32(size))
442 }
443
444 public func readListEnd() throws { }
445
446 public func writeMessageBegin(name: String,
447 type messageType: TMessageType,
448 sequenceID: Int32) throws {
449 try writebyteDirect(TCompactProtocol.protocolID)
450 let nextByte: UInt8 = (TCompactProtocol.version & TCompactProtocol.versionMask) |
451 (UInt8((UInt32(messageType.rawValue) << UInt32(TCType.typeShiftAmount))) &
452 TCType.typeMask)
453 try writebyteDirect(nextByte)
Kino Roya9da9eb2022-10-07 23:13:01 -0700454 try writeVarint32(i32ToZigZag(sequenceID))
Chris Simpsona9b6c702018-04-08 07:11:37 -0400455 try write(name)
456
457 currentMessageName = name
458 }
459
460 public func writeMessageEnd() throws {
461 currentMessageName = nil
462 }
463
464 public func writeStructBegin(name: String) throws {
465 lastField.append(lastFieldId)
466 lastFieldId = 0
467 }
468
469 public func writeStructEnd() throws {
470 lastFieldId = lastField.last ?? 0
471 lastField.removeLast()
472 }
473
474 public func writeFieldBegin(name: String,
475 type fieldType: TType,
476 fieldID: Int32) throws {
477 if fieldType == .bool {
478 boolFieldName = name
479 boolFieldType = fieldType
480 boolFieldId = fieldID
481 return
482 } else {
483 try writeFieldBeginInternal(name: name,
484 type: fieldType,
485 fieldID: fieldID,
486 typeOverride: 0xFF)
487 }
488 }
489
490 func writeFieldBeginInternal(name: String,
491 type fieldType: TType,
492 fieldID: Int32,
493 typeOverride: UInt8) throws {
494
495 let typeToWrite = typeOverride == 0xFF ? compactType(fieldType).rawValue : typeOverride
496
497 // check if we can use delta encoding for the field id
498 let diff = UInt8(fieldID) - lastFieldId
499 if (UInt8(fieldID) > lastFieldId) && (diff <= 15) {
500 // Write them together
501 try writebyteDirect((UInt8(fieldID) - lastFieldId) << 4 | typeToWrite)
502
503 } else {
504 // Write them separate
505 try writebyteDirect(typeToWrite)
506 try write(Int16(fieldID))
507 }
508
509 lastFieldId = UInt8(fieldID)
Chris Simpsona9b6c702018-04-08 07:11:37 -0400510 }
511
512 public func writeFieldStop() throws {
513 try writebyteDirect(TCType.stop.rawValue)
514 }
515
516 public func writeFieldEnd() throws { }
517
518 public func writeMapBegin(keyType: TType, valueType: TType, size: Int32) throws {
519 if size == 0 {
520 try writebyteDirect(0)
521 } else {
522 try writeVarint32(UInt32(size))
523
524 let compactedTypes = compactType(keyType).rawValue << 4 | compactType(valueType).rawValue
525 try writebyteDirect(compactedTypes)
526 }
527 }
528
529 public func writeMapEnd() throws { }
530
531 public func writeSetBegin(elementType: TType, size: Int32) throws {
532 try writeCollectionBegin(elementType, size: size)
533 }
534
535 public func writeSetEnd() throws { }
536
537 public func writeListBegin(elementType: TType, size: Int32) throws {
538 try writeCollectionBegin(elementType, size: size)
539 }
540
541 public func writeListEnd() throws { }
542
543 public func write(_ value: String) throws {
544 try write(value.data(using: String.Encoding.utf8)!)
545 }
546
547 public func write(_ value: Bool) throws {
548 if let boolFieldId = boolFieldId, let boolFieldType = boolFieldType,
549 let boolFieldName = boolFieldName {
550
551 // we haven't written the field header yet
552 let compactType: TCType = value ? .boolean_TRUE : .boolean_FALSE
553 try writeFieldBeginInternal(name: boolFieldName, type: boolFieldType, fieldID: boolFieldId,
554 typeOverride: compactType.rawValue)
555 self.boolFieldId = nil
556 self.boolFieldType = nil
557 self.boolFieldName = nil
558 } else {
559 // we're not part of a field, so just write the value.
560 try writebyteDirect(value ? TCType.boolean_TRUE.rawValue : TCType.boolean_FALSE.rawValue)
561 }
562 }
563
564 public func write(_ value: UInt8) throws {
565 try writebyteDirect(value)
566 }
Kino Roya9da9eb2022-10-07 23:13:01 -0700567
568 public func write(_ value: Int8) throws {
569 var value = value
570 let buff = Data(bytes: &value, count: MemoryLayout<Int8>.size(ofValue: value))
571 try ProtocolTransportTry(error: TProtocolError(message: "Transport write failed")) {
572 try self.transport.write(data: buff)
573 }
574 }
575
Chris Simpsona9b6c702018-04-08 07:11:37 -0400576 public func write(_ value: Int16) throws {
577 try writeVarint32(i32ToZigZag(Int32(value)))
578 }
579
580 public func write(_ value: Int32) throws {
581 try writeVarint32(i32ToZigZag(value))
582 }
583
584 public func write(_ value: Int64) throws {
585 try writeVarint64(i64ToZigZag(value))
586 }
587
588 public func write(_ value: Double) throws {
Chris Simpson2566ecd2018-08-29 14:40:44 -0400589 var bits = CFSwapInt64HostToLittle(value.bitPattern)
Chris Simpsona9b6c702018-04-08 07:11:37 -0400590 let data = withUnsafePointer(to: &bits) {
591 return Data(bytes: UnsafePointer<UInt8>(OpaquePointer($0)), count: MemoryLayout<UInt64>.size)
592 }
593 try ProtocolTransportTry(error: TProtocolError(message: "Transport Write Failed")) {
594 try self.transport.write(data: data)
595 }
596 }
597
598 public func write(_ data: Data) throws {
599 try writeVarint32(UInt32(data.count))
600 try ProtocolTransportTry(error: TProtocolError(message: "Transport Write Failed")) {
601 try self.transport.write(data: data)
602 }
603 }
Kino Roya9da9eb2022-10-07 23:13:01 -0700604
605 public func write(_ value: UUID) throws {
606 let data = withUnsafePointer(to: value.uuid) {
607 Data(bytes: $0, count: MemoryLayout.size(ofValue: value.uuid))
608 }
609 let msb = data[0..<data.count/2]
610 let lsb = data[(data.count/2)..<data.count]
611
612 var buff = Data()
613 buff.append(lsb)
614 buff.append(msb)
615
616 try self.transport.write(data: buff)
617 }
Chris Simpsona9b6c702018-04-08 07:11:37 -0400618}