Sources/TwitterApacheThrift/ThriftBinary.swift (141 lines of code) (raw):
// Copyright 2020 Twitter, Inc.
// Licensed under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0
//
// ThriftBinary.swift
// TwitterApacheThrift
//
// Created on 3/26/20.
// Copyright © 2020 Twitter. All rights reserved.
//
import Foundation
/// A class for reading values from thrift data
class ThriftBinary {
/// The buffer for holding the thrift data
let readingBuffer: MemoryBuffer
/// Initialize BinaryProtocol class with data
/// - Parameter data: The thrift data for reading
init(data: Data) {
readingBuffer = MemoryBuffer(buffer: data)
}
/// Moves the read cursor back by one byte for the field type
func moveReadCursorBackAfterType() {
readingBuffer.moveOffset(by: -(UInt8.bitWidth / 8))
}
/// Moves the read cursor back by two bytes for reading the field type and id
func moveReadCursorBackAfterTypeAndFieldID() {
moveReadCursorBackAfterType()
readingBuffer.moveOffset(by: -(Int16.bitWidth / 8))
}
func readThrift(type: ThriftType) throws -> ThriftObject {
return try readValue(index: nil, type: type)
}
func readStruct(index: Int?) throws -> ThriftStruct {
var fields: [Int: ThriftValue] = [:]
var nextField = try readFieldMetadata()
while nextField.type != .stop, let id = nextField.id {
let value = try readValue(index: id, type: nextField.type)
fields[id] = ThriftValue(index: id, type: nextField.type, data: value)
nextField = try readFieldMetadata()
}
return ThriftStruct(index: index, fields: fields)
}
func readValue(index: Int?, type: ThriftType, isCollection: Bool = false) throws -> ThriftObject {
switch type {
case .bool, .byte:
return .data(try Data([readByte()]))
case .double, .int64:
return .data(try readingBuffer.read(size: 8))
case .int16:
return .data(try readingBuffer.read(size: 2))
case .int32:
return .data(try readingBuffer.read(size: 4))
case .string:
return .data(try readBinary())
case .structure:
return .struct(try readStruct(index: index))
case .map:
var values: [ThriftKeyedCollection.Value] = []
let metadata = try readMapMetadata()
for _ in 0..<metadata.size {
let key = try readValue(index: nil, type: metadata.keyType)
let value = try readValue(index: nil, type: metadata.valueType)
values.append(.init(key: key, value: value))
}
return .keyedCollection(ThriftKeyedCollection(index: index,
count: metadata.size,
keyType:metadata.keyType,
elementType: metadata.valueType,
value: values))
case .list, .set:
var values: [ThriftObject] = []
let metadata = try readListMetadata()
for _ in 0..<metadata.size {
let value = try readValue(index: nil, type: metadata.elementType)
values.append(value)
}
return .unkeyedCollection(ThriftUnkeyedCollection(index: index, count: metadata.size, elementType: type, value: values))
default:
return .stop
}
}
/// Reads the next UInt64 from the thrift
/// - Throws: ThriftDecoderError.readBufferOverflow when trying to read outside the range of data
/// - Returns: The value decoded from the thrift
func readUInt64() throws -> UInt64 {
let i64rd = try readingBuffer.read(size: 8)
let byte56 = UInt64(i64rd[0]) << 56
let byte48 = UInt64(i64rd[1]) << 48
let byte40 = UInt64(i64rd[2]) << 40
let byte32 = UInt64(i64rd[3]) << 32
let byte24 = UInt64(i64rd[4]) << 24
let byte16 = UInt64(i64rd[5]) << 16
let byte8 = UInt64(i64rd[6]) << 8
let byte0 = UInt64(i64rd[7]) << 0
return (byte56 | byte48 | byte40 | byte32 | byte24 | byte16 | byte8 | byte0)
}
/// Reads the next Int64 from the thrift
/// - Throws: ThriftDecoderError.readBufferOverflow when trying to read outside the range of data
/// - Returns: The value decoded from the thrift
func readInt64() throws -> Int64 {
let u64 = try readUInt64()
return Int64(bitPattern: u64)
}
/// Reads the next Int32 from the thrift
/// - Throws: ThriftDecoderError.readBufferOverflow when trying to read outside the range of data
/// - Returns: The value decoded from the thrift
func readInt32() throws -> Int32 {
let i32rd = try readingBuffer.read(size: 4)
let byte24 = Int32(i32rd[0]) << 24
let byte16 = Int32(i32rd[1]) << 16
let byte8 = Int32(i32rd[2]) << 8
let byte0 = Int32(i32rd[3]) << 0
return (byte24 | byte16 | byte8 | byte0)
}
/// Reads the next Int16 from the thrift
/// - Throws: ThriftDecoderError.readBufferOverflow when trying to read outside the range of data
/// - Returns: The value decoded from the thrift
func readInt16() throws -> Int16 {
let i16rd = try readingBuffer.read(size: 2)
let byte8 = Int16(i16rd[0]) << 8
let byte0 = Int16(i16rd[1]) << 0
return byte8 | byte0
}
/// Reads the next double from the thrift
/// - Throws: ThriftDecoderError.readBufferOverflow when trying to read outside the range of data
/// - Returns: The value decoded from the thrift
func readDouble() throws -> Double {
let ui64 = try readUInt64()
return Double(bitPattern: ui64)
}
/// Reads the next data from the thrift. The length of data is based on the next 4 bytes.
/// - Throws: ThriftDecoderError.readBufferOverflow when trying to read outside the range of data
/// - Returns: The value decoded from the thrift
func readBinary() throws -> Data {
let size = try readInt32()
return try readingBuffer.read(size: Int(size))
}
/// Reads the next UTF8 string from the thrift. The length of string data is based on the next 4 bytes.
/// - Throws: ThriftDecoderError.readBufferOverflow when trying to read outside the range of data
/// - Throws: ThriftDecoderError.nonUTF8StringData when the string data is not a UTF8 string
/// - Returns: The value decoded from the thrift
func readString() throws -> String {
let stringData = try readBinary()
guard let string = String(bytes: stringData, encoding: .utf8) else {
throw ThriftDecoderError.nonUTF8StringData(stringData)
}
return string
}
/// Reads the next byte from the thrift.
/// - Throws: ThriftDecoderError.readBufferOverflow when trying to read outside the range of data
/// - Returns: Returns true when the next byte is 1, otherwise returns false
func readBool() throws -> Bool {
return try readByte() == 1
}
/// Reads the next byte from the thrift.
/// - Throws: ThriftDecoderError.readBufferOverflow when trying to read outside the range of data
/// - Returns: The value decoded from the thrift
func readByte() throws -> UInt8 {
return try readingBuffer.read(size: 1)[0]
}
/// Reads a map object metadata from the thrift. This type is equivalent to a Dictionary
/// - Throws: ThriftDecoderError.readBufferOverflow when trying to read outside the range of data
/// - Returns: The type of key as a thrift type. The type of values as a thrift type. The amount of elements.
func readMapMetadata() throws -> (keyType: ThriftType, valueType: ThriftType, size: Int) {
let keyType = try readByte()
let valueType = try readByte()
let size = try readInt32()
return (try ThriftType(coreValue: keyType), try ThriftType(coreValue: valueType), Int(size))
}
/// Reads a set object metadata from the thrift.
/// - Throws: ThriftDecoderError.readBufferOverflow when trying to read outside the range of data
/// - Returns: The type of values as a thrift type. The amount of elements.
func readSetMetadata() throws -> (elementType: ThriftType, size: Int) {
let type = try readByte()
let size = try readInt32()
return (try ThriftType(coreValue: type), Int(size))
}
/// Reads a list object metadata from the thrift. This type is equivalent to a Array
/// - Throws: ThriftDecoderError.readBufferOverflow when trying to read outside the range of data
/// - Returns: The type of values as a thrift type. The amount of elements.
func readListMetadata() throws -> (elementType: ThriftType, size: Int) {
let type = try readByte()
let size = try readInt32()
return (try ThriftType(coreValue: type), Int(size))
}
/// Reads a the field metadata from the thrift. This type is equivalent to a Dictionary
/// - Throws: ThriftDecoderError.readBufferOverflow when trying to read outside the range of data
/// - Returns: The field thrift type. The thrift id if it is not a ThriftType.stop.
func readFieldMetadata() throws -> (type: ThriftType, id: Int?) {
let type = try ThriftType(coreValue: try readByte())
if (type != .stop) {
let id = try self.readInt16()
return (type, Int(id))
}
return (type, nil)
}
}