/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import Foundation import CoreFoundation public enum TCType: UInt8 { case stop = 0x00 case boolean_TRUE = 0x01 case boolean_FALSE = 0x02 case i8 = 0x03 case i16 = 0x04 case i32 = 0x05 case i64 = 0x06 case double = 0x07 case binary = 0x08 case list = 0x09 case set = 0x0A case map = 0x0B case `struct` = 0x0C case uuid = 0x0D public static let typeMask: UInt8 = 0xE0 // 1110 0000 public static let typeBits: UInt8 = 0x07 // 0000 0111 public static let typeShiftAmount = 5 } public class TCompactProtocol: TProtocol { public static let protocolID: UInt8 = 0x82 public static let version: UInt8 = 1 public static let versionMask: UInt8 = 0x1F // 0001 1111 public var transport: TTransport var lastField: [UInt8] = [] var lastFieldId: UInt8 = 0 var boolFieldName: String? var boolFieldType: TType? var boolFieldId: Int32? var booleanValue: Bool? var currentMessageName: String? public required init(on transport: TTransport) { self.transport = transport } /// Mark: - TCompactProtocol helpers func writebyteDirect(_ byte: UInt8) throws { let byte = Data([byte]) try ProtocolTransportTry(error: TProtocolError(message: "Transport Write Failed")) { try self.transport.write(data: byte) } } func writeVarint32(_ val: UInt32) throws { var val = val var i32buf = [UInt8](repeating: 0, count: 5) var idx = 0 while true { if (val & ~0x7F) == 0 { i32buf[idx] = UInt8(val) idx += 1 break } else { i32buf[idx] = UInt8((val & 0x7F) | 0x80) idx += 1 val >>= 7 } } try ProtocolTransportTry(error: TProtocolError(message: "Transport Write Failed")) { try self.transport.write(data: Data(i32buf[0..>= 7 } } try ProtocolTransportTry(error: TProtocolError(message: "Transport Write Failed")) { try self.transport.write(data: Data(varint64out[0.. Data { var result = Data() if size != 0 { try ProtocolTransportTry(error: TProtocolError(message: "Transport Read Failed")) { result = try self.transport.readAll(size: size) } } return result } func readVarint32() throws -> UInt32 { var result: UInt32 = 0 var shift: UInt32 = 0 while true { let byte: UInt8 = try read() result |= UInt32(byte & 0x7F) << shift if (byte & 0x80) == 0 { break } shift += 7 } return result } func readVarint64() throws -> UInt64 { var result: UInt64 = 0 var shift: UInt64 = 0 while true { let byte: UInt8 = try read() result |= UInt64(byte & 0x7F) << shift if (byte & 0x80) == 0 { break } shift += 7 } return result } func ttype(_ compactTypeVal: UInt8) throws -> TType { guard let compactType = TCType(rawValue: compactTypeVal) else { throw TProtocolError(message: "Unknown TCType value: \(compactTypeVal)") } switch compactType { case .stop: return .stop; case .boolean_FALSE, .boolean_TRUE: return .bool; case .i8: return .i8; case .i16: return .i16; case .i32: return .i32; case .i64: return .i64; case .double: return .double; case .binary: return .string; case .list: return .list; case .set: return .set; case .map: return .map; case .struct: return .struct; case .uuid: return .uuid; } } func compactType(_ ttype: TType) -> TCType { switch ttype { case .stop: return .stop case .void: return .i8 case .bool: return .boolean_FALSE case .i8: return .i8 case .double: return .double case .i16: return .i16 case .i32: return .i32 case .i64: return .i64 case .string: return .binary case .struct: return .struct case .map: return .map case .set: return .set case .list: return .list case .utf8: return .binary //case .utf16: return .binary case .uuid: return .uuid } } /// ZigZag encoding maps signed integers to unsigned integers so that /// numbers with a small absolute value (for instance, -1) have /// a small varint encoded value too. It does this in a way that /// "zig-zags" back and forth through the positive and negative integers, /// so that -1 is encoded as 1, 1 is encoded as 2, -2 is encoded as 3, and so /// /// - parameter n: number to zigzag /// /// - returns: zigzaged UInt32 func i32ToZigZag(_ n : Int32) -> UInt32 { return UInt32(bitPattern: Int32(n << 1) ^ Int32(n >> 31)) } func i64ToZigZag(_ n : Int64) -> UInt64 { return UInt64(bitPattern: Int64(n << 1) ^ Int64(n >> 63)) } func zigZagToi32(_ n: UInt32) -> Int32 { return Int32(n >> 1) ^ (-Int32(n & 1)) } func zigZagToi64(_ n: UInt64) -> Int64 { return Int64(n >> 1) ^ (-Int64(n & 1)) } /// Mark: - TProtocol public func readMessageBegin() throws -> (String, TMessageType, Int32) { let protocolId: UInt8 = try read() if protocolId != TCompactProtocol.protocolID { let expected = String(format:"%2X", TCompactProtocol.protocolID) let got = String(format:"%2X", protocolId) throw TProtocolError(message: "Wrong Protocol ID \(got)", extendedError: .mismatchedProtocol(expected: expected, got: got)) } let versionAndType: UInt8 = try read() let version: UInt8 = versionAndType & TCompactProtocol.versionMask if version != TCompactProtocol.version { throw TProtocolError(error: .badVersion(expected: "\(TCompactProtocol.version)", got:"\(version)")) } let type = (versionAndType >> UInt8(TCType.typeShiftAmount)) & TCType.typeBits guard let mtype = TMessageType(rawValue: Int32(type)) else { throw TProtocolError(message: "Unknown TMessageType value: \(type)") } let varint = zigZagToi32(try readVarint32()) let sequenceId = Int32(varint) let name: String = try read() return (name, mtype, Int32(sequenceId)) } public func readMessageEnd() throws { } public func readStructBegin() throws -> String { lastField.append(lastFieldId) lastFieldId = 0 return "" } public func readStructEnd() throws { lastFieldId = lastField.last ?? 0 lastField.removeLast() } public func readFieldBegin() throws -> (String, TType, Int32) { let byte: UInt8 = try read() guard let type = TCType(rawValue: byte & 0x0F) else { throw TProtocolError(message: "Unknown TCType \(byte & 0x0F)") } // if it's a stop, then we can return immediately, as the struct is over if type == .stop { return ("", .stop, 0) } var fieldId: Int16 = 0 // mask off the 4MSB of the type header. it could contain a field id delta let modifier = (byte & 0xF0) >> 4 if modifier == 0 { // not a delta. look ahead for the zigzag varint field id fieldId = try read() } else { // has a delta. add the delta to the last Read field id. fieldId = Int16(lastFieldId + modifier) } let fieldType = try ttype(type.rawValue) // if this happens to be a boolean field, the value is encoded in the type if type == .boolean_TRUE || type == .boolean_FALSE { // save the boolean value in a special instance variable booleanValue = type == .boolean_TRUE } // push the new field onto the field stack so we can keep the deltas going lastFieldId = UInt8(fieldId) return ("", fieldType, Int32(fieldId)) } public func readFieldEnd() throws { } public func read() throws -> String { let length = try readVarint32() var result: String if length != 0 { let data = try readBinary(Int(length)) result = String(data: data, encoding: String.Encoding.utf8) ?? "" } else { result = "" } return result } public func read() throws -> Bool { if let val = booleanValue { self.booleanValue = nil return val } else { let result = try read() as UInt8 return TCType(rawValue: result) == .boolean_TRUE } } public func read() throws -> UInt8 { var buff: UInt8 = 0 try ProtocolTransportTry(error: TProtocolError(message: "Transport Read Failed")) { buff = try self.transport.readAll(size: 1)[0] } return buff } public func read() throws -> Int8 { var buff = Data() try ProtocolTransportTry(error: TProtocolError(message: "Transport Read Failed")) { buff = try self.transport.readAll(size: 1) } return buff.withUnsafeBytes { pntr in return pntr.load(as: Int8.self) } } public func read() throws -> Int16 { let v = try readVarint32() return Int16(zigZagToi32(v)) } public func read() throws -> Int32 { let v = try readVarint32() return zigZagToi32(v) } public func read() throws -> Int64 { let v = try readVarint64() return zigZagToi64(v) } public func read() throws -> Double { var buff = Data() try ProtocolTransportTry(error: TProtocolError(message: "Transport Read Failed")) { buff = try self.transport.readAll(size: 8) } let i64: UInt64 = buff.withUnsafeBytes { $0.load(as: UInt64.self) } let bits = CFSwapInt64LittleToHost(i64) return Double(bitPattern: bits) } public func read() throws -> Data { let length = try readVarint32() return try readBinary(Int(length)) } public func read() throws -> UUID { let data = try self.transport.readAll(size: 16) let lsb = data[0.. (TType, TType, Int32) { var keyAndValueType: UInt8 = 8 let size = try readVarint32() if size != 0 { keyAndValueType = try read() } let keyType = try ttype(keyAndValueType >> 4) let valueType = try ttype(keyAndValueType & 0xF) return (keyType, valueType, Int32(size)) } public func readMapEnd() throws { } public func readSetBegin() throws -> (TType, Int32) { return try readListBegin() } public func readSetEnd() throws { } public func readListBegin() throws -> (TType, Int32) { let sizeAndType: UInt8 = try read() var size: UInt32 = UInt32(sizeAndType >> 4) & 0x0f if size == 15 { size = try readVarint32() } let elementType = try ttype(sizeAndType & 0x0F) return (elementType, Int32(size)) } public func readListEnd() throws { } public func writeMessageBegin(name: String, type messageType: TMessageType, sequenceID: Int32) throws { try writebyteDirect(TCompactProtocol.protocolID) let nextByte: UInt8 = (TCompactProtocol.version & TCompactProtocol.versionMask) | (UInt8((UInt32(messageType.rawValue) << UInt32(TCType.typeShiftAmount))) & TCType.typeMask) try writebyteDirect(nextByte) try writeVarint32(i32ToZigZag(sequenceID)) try write(name) currentMessageName = name } public func writeMessageEnd() throws { currentMessageName = nil } public func writeStructBegin(name: String) throws { lastField.append(lastFieldId) lastFieldId = 0 } public func writeStructEnd() throws { lastFieldId = lastField.last ?? 0 lastField.removeLast() } public func writeFieldBegin(name: String, type fieldType: TType, fieldID: Int32) throws { if fieldType == .bool { boolFieldName = name boolFieldType = fieldType boolFieldId = fieldID return } else { try writeFieldBeginInternal(name: name, type: fieldType, fieldID: fieldID, typeOverride: 0xFF) } } func writeFieldBeginInternal(name: String, type fieldType: TType, fieldID: Int32, typeOverride: UInt8) throws { let typeToWrite = typeOverride == 0xFF ? compactType(fieldType).rawValue : typeOverride // check if we can use delta encoding for the field id let diff = UInt8(fieldID) - lastFieldId if (UInt8(fieldID) > lastFieldId) && (diff <= 15) { // Write them together try writebyteDirect((UInt8(fieldID) - lastFieldId) << 4 | typeToWrite) } else { // Write them separate try writebyteDirect(typeToWrite) try write(Int16(fieldID)) } lastFieldId = UInt8(fieldID) } public func writeFieldStop() throws { try writebyteDirect(TCType.stop.rawValue) } public func writeFieldEnd() throws { } public func writeMapBegin(keyType: TType, valueType: TType, size: Int32) throws { if size == 0 { try writebyteDirect(0) } else { try writeVarint32(UInt32(size)) let compactedTypes = compactType(keyType).rawValue << 4 | compactType(valueType).rawValue try writebyteDirect(compactedTypes) } } public func writeMapEnd() throws { } public func writeSetBegin(elementType: TType, size: Int32) throws { try writeCollectionBegin(elementType, size: size) } public func writeSetEnd() throws { } public func writeListBegin(elementType: TType, size: Int32) throws { try writeCollectionBegin(elementType, size: size) } public func writeListEnd() throws { } public func write(_ value: String) throws { try write(value.data(using: String.Encoding.utf8)!) } public func write(_ value: Bool) throws { if let boolFieldId = boolFieldId, let boolFieldType = boolFieldType, let boolFieldName = boolFieldName { // we haven't written the field header yet let compactType: TCType = value ? .boolean_TRUE : .boolean_FALSE try writeFieldBeginInternal(name: boolFieldName, type: boolFieldType, fieldID: boolFieldId, typeOverride: compactType.rawValue) self.boolFieldId = nil self.boolFieldType = nil self.boolFieldName = nil } else { // we're not part of a field, so just write the value. try writebyteDirect(value ? TCType.boolean_TRUE.rawValue : TCType.boolean_FALSE.rawValue) } } public func write(_ value: UInt8) throws { try writebyteDirect(value) } public func write(_ value: Int8) throws { var value = value let buff = Data(bytes: &value, count: MemoryLayout.size(ofValue: value)) try ProtocolTransportTry(error: TProtocolError(message: "Transport write failed")) { try self.transport.write(data: buff) } } public func write(_ value: Int16) throws { try writeVarint32(i32ToZigZag(Int32(value))) } public func write(_ value: Int32) throws { try writeVarint32(i32ToZigZag(value)) } public func write(_ value: Int64) throws { try writeVarint64(i64ToZigZag(value)) } public func write(_ value: Double) throws { var bits = CFSwapInt64HostToLittle(value.bitPattern) let data = withUnsafePointer(to: &bits) { return Data(bytes: UnsafePointer(OpaquePointer($0)), count: MemoryLayout.size) } try ProtocolTransportTry(error: TProtocolError(message: "Transport Write Failed")) { try self.transport.write(data: data) } } public func write(_ data: Data) throws { try writeVarint32(UInt32(data.count)) try ProtocolTransportTry(error: TProtocolError(message: "Transport Write Failed")) { try self.transport.write(data: data) } } public func write(_ value: UUID) throws { let data = withUnsafePointer(to: value.uuid) { Data(bytes: $0, count: MemoryLayout.size(ofValue: value.uuid)) } let msb = data[0..