blob: 59773c34af3909fe0f40a15f27aafae09260f218 [file] [log] [blame]
/*
* 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)
}
}
}