575 lines
16 KiB
Swift
575 lines
16 KiB
Swift
/*
|
|
* 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
|
|
|
|
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(bytes: [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(bytes: i32buf[0..<idx]))
|
|
}
|
|
}
|
|
|
|
func writeVarint64(_ val: UInt64) throws {
|
|
var val = val
|
|
var varint64out = [UInt8](repeating: 0, count: 10)
|
|
var idx = 0
|
|
while true {
|
|
if (val & ~0x7F) == 0{
|
|
varint64out[idx] = UInt8(val)
|
|
idx += 1
|
|
break
|
|
} else {
|
|
varint64out[idx] = UInt8(val & 0x7F) | 0x80
|
|
idx += 1
|
|
val >>= 7
|
|
}
|
|
}
|
|
|
|
try ProtocolTransportTry(error: TProtocolError(message: "Transport Write Failed")) {
|
|
try self.transport.write(data: Data(bytes: varint64out[0..<idx]))
|
|
}
|
|
|
|
}
|
|
|
|
func writeCollectionBegin(_ elementType: TType, size: Int32) throws {
|
|
let ctype = compactType(elementType).rawValue
|
|
if size <= 14 {
|
|
try writebyteDirect(UInt8(size << 4) | ctype)
|
|
} else {
|
|
try writebyteDirect(0xF0 | ctype)
|
|
try writeVarint32(UInt32(size))
|
|
}
|
|
}
|
|
|
|
func readBinary(_ size: Int) throws -> 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;
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
/// 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 sequenceId = try readVarint32()
|
|
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 -> 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 { (ptr: UnsafePointer<UInt8>) -> UInt64 in
|
|
return UnsafePointer<UInt64>(OpaquePointer(ptr)).pointee
|
|
}
|
|
let bits = CFSwapInt64LittleToHost(i64)
|
|
return Double(bitPattern: bits)
|
|
}
|
|
|
|
public func read() throws -> Data {
|
|
let length = try readVarint32()
|
|
return try readBinary(Int(length))
|
|
}
|
|
|
|
public func readMapBegin() throws -> (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(UInt32(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: 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<UInt8>(OpaquePointer($0)), count: MemoryLayout<UInt64>.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)
|
|
}
|
|
}
|
|
}
|