| /* |
| * 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 |
| |
| /** |
| JSON protocol implementation for thrift. |
| This is a full-feature protocol supporting Write and Read. |
| Please see the C++ class header for a detailed description of the protocol's wire format |
| Adapted from netstd C# version |
| */ |
| public class TJSONProtocol: TProtocol { |
| static let Version: Int = 1 |
| |
| public var transport: TTransport |
| |
| // Temporary buffer used by several methods |
| private var tempBuffer: [UInt8] = [0,0,0,0] |
| private var contextStack: JSONContextStack = JSONContextStack() |
| private var currentContext: JSONBaseContext? |
| private var context: JSONBaseContext { |
| get throws { |
| if (currentContext != nil) { |
| return currentContext! |
| } |
| throw TProtocolError(error: .depthLimit, message: "Current context is nil") |
| } |
| } |
| |
| /** |
| Reader that manages a 1-byte buffer |
| */ |
| private var optionalReader: LookaheadReader? |
| private var reader: LookaheadReader { |
| get throws { |
| if (optionalReader != nil) { |
| return optionalReader! |
| } |
| throw TProtocolError(error: .depthLimit, message: "Lookahead reader is nil") |
| } |
| } |
| |
| // MARK: TJSONProtocol Constructor |
| public required init(on transport: TTransport) { |
| self.transport = transport |
| currentContext = JSONBaseContext(on: self) |
| optionalReader = LookaheadReader(on: self) |
| } |
| |
| // MARK: TJSONProtocol helpers |
| /** |
| Push a new JSON context onto the context stack |
| */ |
| func pushContext(_ context: JSONBaseContext) { |
| contextStack.push(context) |
| currentContext = context |
| } |
| |
| /** |
| Pop current JSON context from the context stack |
| */ |
| func popContext() { |
| _ = contextStack.pop() |
| currentContext = contextStack.isEmpty() ? JSONBaseContext(on: self) : contextStack.peek() |
| } |
| |
| /** |
| Reset context stack to pristine state. Allows for reusal of the protocol even in cases where the protocol instance |
| was in an undefined state due to dangling/stale/obsolete contexts |
| */ |
| func resetContext() { |
| contextStack.clear() |
| currentContext = JSONBaseContext(on: self) |
| } |
| |
| /** |
| Read a byte that must match bytes[0]; otherwise an exception is thrown. |
| - bytes: Input bytes array |
| */ |
| func readJsonSyntaxChar(bytes: [UInt8]) throws { |
| let ch: UInt8 = try reader.read() |
| if (ch != bytes[0]) { |
| throw TProtocolError(error: .invalidData, message: "Unexpected character: \(ch.asCharacter())") |
| } |
| } |
| |
| /** |
| Write the bytes in array buffer as a JSON characters, escaping as needed |
| */ |
| func writeJsonString(bytes: [UInt8]) throws { |
| try context.writeConditionalDelimiter() |
| try transport.writeJSONQuote() |
| |
| let len: Int = bytes.count |
| for i in 0..<len { |
| if (bytes[i] & 0x00FF >= 0x30) { |
| if (bytes[i] == TJSONProtocolConstants.Backslash[0]) { |
| try transport.writeJSONBackslash() |
| try transport.writeJSONBackslash() |
| } else { |
| try transport.write(data: Data(bytes: [bytes[i]], count: 1)) |
| } |
| } else { |
| tempBuffer[0] = TJSONProtocolConstants.JsonCharTable[Int(bytes[i])] |
| if (tempBuffer[0] == 1) { |
| try transport.write(data: Data(bytes: [bytes[i]], count: 1)) |
| } else if (tempBuffer[0] > 1) { |
| try transport.writeJSONBackslash() |
| try transport.write(data: Data(bytes: [tempBuffer[0]], count: 1)) |
| } else { |
| try transport.writeJSONEscSequences() |
| tempBuffer[0] = (bytes[i] >> 4).toHexChar() |
| tempBuffer[1] = (bytes[i]).toHexChar() |
| try transport.write(data: Data(bytes: [tempBuffer[0], tempBuffer[1]], count:2)) |
| } |
| } |
| } |
| try transport.writeJSONQuote() |
| } |
| |
| /** |
| Write out number as a JSON value. If the context dicates so, it will be wrapped in quotes to output as a JSON string. |
| */ |
| func writeJsonInteger(num: Int64) throws { |
| try context.writeConditionalDelimiter() |
| let str: String = String(num) |
| |
| let escapeNum: Bool = try context.escapeNumbers() |
| if (escapeNum) { |
| try transport.write(data: Data(bytes: TJSONProtocolConstants.Quote, count: TJSONProtocolConstants.Quote.count)) |
| } |
| |
| let strData: Data = str.data(using: .utf8)! |
| try transport.write(data: strData) |
| |
| if (escapeNum) { |
| try transport.write(data: Data(bytes: TJSONProtocolConstants.Quote, count: TJSONProtocolConstants.Quote.count)) |
| } |
| } |
| |
| /** |
| Write out a double as a JSON value. If it is Nan or Infinity or if the context dictates escaping, write out as JSON string. |
| */ |
| func writeJsonDouble(num: Double) throws { |
| try context.writeConditionalDelimiter() |
| let str = String(num) |
| var special = false |
| |
| switch(str[0]) { |
| case "N", "I": |
| // Nan or Infinity |
| special = true |
| case "-": |
| if (str[1] == "I") { |
| // -Infinity |
| special = true |
| } |
| default: |
| special = false |
| } |
| |
| let escapeNum = try context.escapeNumbers() |
| let escapeNumOrSpecial = special || escapeNum |
| if (escapeNumOrSpecial) { |
| try transport.writeJSONQuote() |
| } |
| |
| if let strData = str.data(using: .utf8) { |
| try transport.write(data: strData) |
| } else { |
| throw TProtocolError(error: .invalidData, message: "Cannot convert double number to data bytes") |
| } |
| |
| if (escapeNumOrSpecial) { |
| try transport.writeJSONQuote() |
| } |
| } |
| |
| /** |
| Write out contents of byte array as a JSON string with base-64 encoded data |
| */ |
| func writeJsonBase64(bytes: [UInt8]) throws { |
| try context.writeConditionalDelimiter() |
| try transport.writeJSONQuote() |
| |
| var len = bytes.count |
| var off = 0 |
| while (len >= 3) { |
| // Encode 3 bytes at a time |
| TBase64Utils.encode(src: bytes, srcOff: off, len: 3, dst: &tempBuffer, dstOff: 0) |
| try transport.write(data: Data(bytes: tempBuffer, count: 4)) |
| off += 3 |
| len -= 3 |
| } |
| |
| if (len > 0) { |
| // Encode remainder |
| TBase64Utils.encode(src: bytes, srcOff: off, len: len, dst: &tempBuffer, dstOff: 0) |
| try transport.write(data: Data(bytes: tempBuffer, count: len + 1)) |
| } |
| |
| try transport.writeJSONQuote() |
| } |
| |
| func writeJsonObjectStart() throws { |
| try context.writeConditionalDelimiter() |
| try transport.writeJSONLeftBrace() |
| pushContext(JSONPairContext(on: self)) |
| } |
| |
| func writeJsonObjectEnd() throws { |
| popContext() |
| try transport.writeJSONRightBrace() |
| } |
| |
| func writeJsonArrayStart() throws { |
| try context.writeConditionalDelimiter() |
| try transport.writeJSONLeftBracket() |
| pushContext(JSONListContext(on: self)) |
| } |
| |
| func writeJsonArrayEnd() throws { |
| popContext() |
| try transport.writeJSONRightBracket() |
| } |
| |
| /** |
| Read in a JSON string, unescaping as appropriate. Skip reading from the context if skipContext is true. |
| */ |
| func readJsonString(skipContext: Bool) throws -> [UInt8] { |
| var codeunits: [Character] = [] |
| |
| if (!skipContext) { |
| try context.readConditionalDelimiter() |
| } |
| try readJsonSyntaxChar(bytes: TJSONProtocolConstants.Quote) |
| |
| var dataBuffer = Data() |
| while (true) { |
| var ch: UInt8 = try reader.read() |
| if (ch == TJSONProtocolConstants.Quote[0]) { |
| break |
| } |
| |
| // Escaped? |
| if (ch != TJSONProtocolConstants.EscSequences[0]) { |
| dataBuffer.append([ch], count: 1) |
| continue |
| } |
| |
| // distinguish between \uXXXX and \? |
| ch = try reader.read() |
| if (ch != TJSONProtocolConstants.EscSequences[1]) { // control chars like \n |
| guard let off: Int = TJSONProtocolConstants.EscSequences.firstIndex(of: ch) else { |
| throw TProtocolError(error: .invalidData, message: "Expected control char") |
| } |
| ch = TJSONProtocolConstants.EscapeCharValues[off] |
| dataBuffer.append([ch], count: 1) |
| continue |
| } |
| |
| // It's \uXXXX |
| let tempData: Data = try transport.readAll(size: 4) |
| let wch = Int16( ((tempData[0]).toHexChar() << 12) + |
| ((tempData[1]).toHexChar() << 8) + |
| ((tempData[2]).toHexChar() << 4) + |
| ((tempData[3]).toHexChar()) ) |
| guard let wchScalar = UnicodeScalar(Int(wch)) else { |
| throw TProtocolError(error: .invalidData, message: "Expected Unicode character") |
| } |
| |
| if (try wch.magnitude.isHighSurrogate()) { |
| if (codeunits.count > 0) { |
| throw TProtocolError(error: .invalidData, message: "Exptected low surrogate char") |
| } |
| codeunits.append(Character(wchScalar)) |
| } else if (try wch.magnitude.isLowSurrogate()) { |
| if (codeunits.count == 0) { |
| throw TProtocolError(error: .invalidData, message: "Exptected high surrogate char") |
| } |
| codeunits.append(Character(wchScalar)) |
| guard let codeunitsData = String(codeunits).data(using: .utf8) else { |
| throw TProtocolError(error: .invalidData, message: "Codeunits cannot be converted to string bytes") |
| } |
| dataBuffer.append(codeunitsData) |
| codeunits.removeAll() |
| } else { |
| let bytesArray: [UInt8] = withUnsafeBytes(of: wch.bigEndian, Array.init) |
| dataBuffer.append(Data(bytes: bytesArray, count: bytesArray.count)) |
| } |
| } |
| |
| if (codeunits.count > 0) { |
| throw TProtocolError(error: .invalidData, message: "Expected low surrogate char") |
| } |
| |
| let bytesResult: [UInt8] = dataBuffer.map { $0 } |
| return bytesResult |
| } |
| |
| /** |
| Read in a sequence of characters that are all valid in JSON numbers. Does not do a complete regex check |
| to validate that this is actually a number. |
| */ |
| func readJsonNumericChars() throws -> String { |
| var str = "" |
| while(true) { |
| // TODO: Workaround for primitive types with TJSONProtocol: think - how to rewrite into more easy from without exception |
| do { |
| let ch: UInt8 = try reader.peek() |
| if (!ch.isJsonNumeric()) { |
| break |
| } |
| let c = try reader.read() |
| str.append(c.asCharacter()) |
| } catch is TTransportError { |
| break |
| } |
| catch let error { |
| throw error |
| } |
| } |
| return str |
| } |
| |
| /** |
| Read in a JSON number. If the context dictates, read in enclosing quotes. |
| */ |
| func readJsonInteger() throws -> Int64 { |
| try context.readConditionalDelimiter() |
| let escapeNum = try context.escapeNumbers() |
| if (escapeNum) { |
| try readJsonSyntaxChar(bytes: TJSONProtocolConstants.Quote) |
| } |
| |
| let str: String = try readJsonNumericChars() |
| if (escapeNum) { |
| try readJsonSyntaxChar(bytes: TJSONProtocolConstants.Quote) |
| } |
| |
| guard let result = Int64(str) else { throw TProtocolError(error: .invalidData, message: "Cannot convert \(str) to Int64") } |
| return result |
| } |
| |
| /** |
| Read in a JSON double value. Throw if the value is not wrapped in quotes when expected or if wrapped in quotes when not expected. |
| */ |
| func readJsonDouble() throws -> Double { |
| try context.readConditionalDelimiter() |
| |
| let escapeNum = try context.escapeNumbers() |
| if (try reader.peek() == TJSONProtocolConstants.Quote[0]) { |
| let arr: [UInt8] = try readJsonString(skipContext: true) |
| if let str: String = String(data: Data(arr), encoding: .utf8), |
| let dub = Double(str) { |
| if (!escapeNum && !dub.isNaN && !dub.isInfinite) { |
| throw TProtocolError(error: .invalidData, message: "Numeric data unexpectedly quoted") |
| } |
| return dub |
| } else { |
| throw TProtocolError(error: .invalidData, message: "Numeric data convertion to double failed") |
| } |
| } |
| |
| if (escapeNum) { |
| try readJsonSyntaxChar(bytes: TJSONProtocolConstants.Quote) |
| } |
| |
| let str: String = try readJsonNumericChars() |
| if let dub = Double(str) { |
| return dub |
| } else { |
| throw TProtocolError(error: .invalidData, message: "Numeric data convertion to double failed") |
| } |
| } |
| |
| /** |
| Read in a JSON string containing base-64 encoded data and decode it. |
| */ |
| func readJsonBase64() throws -> [UInt8] { |
| var b = try readJsonString(skipContext: false) |
| var len = b.count |
| var off = 0 |
| var size = 0 |
| |
| // Reduce len to ignore fill bytes |
| while( (len > 0) && (b[len - 1] == "=".asciiBytes()[0]) ) { |
| len -= 1 |
| } |
| |
| // Read & decode full byte triplets = 4 source bytes |
| while (len > 4) { |
| // Decode 4 bytes at a time |
| TBase64Utils.decode(src: b, srcOff: off, len: 4, dst: &b, dstOff: size) // Nb: decode in place |
| off += 4 |
| len -= 4 |
| size += 3 |
| } |
| |
| // Don't decode if we hit the end or got a single leftover byte |
| // (invalid base64 but legal for skip of reqular string exType) |
| if (len > 1) { |
| // Decode remainder |
| TBase64Utils.decode(src: b, srcOff: off, len: len, dst: &b, dstOff: size) // NB: decode in place |
| size += len - 1 |
| } |
| |
| let result: [UInt8] = Array(b[0..<size]) |
| return result |
| } |
| |
| func readJsonObjectStart() throws { |
| try context.readConditionalDelimiter() |
| try readJsonSyntaxChar(bytes: TJSONProtocolConstants.LeftBrace) |
| pushContext(JSONPairContext(on: self)) |
| } |
| |
| func readJsonObjectEnd() throws { |
| try readJsonSyntaxChar(bytes: TJSONProtocolConstants.RightBrace) |
| popContext() |
| } |
| |
| func readJsonArrayStart() throws { |
| try context.readConditionalDelimiter() |
| try readJsonSyntaxChar(bytes: TJSONProtocolConstants.LeftBracket) |
| pushContext(JSONListContext(on: self)) |
| } |
| |
| func readJsonArrayEnd() throws { |
| try readJsonSyntaxChar(bytes: TJSONProtocolConstants.RightBracket) |
| popContext() |
| } |
| |
| // MARK: - TProtocol |
| public func readMessageBegin() throws -> (String, TMessageType, Int32) { |
| resetContext() |
| try readJsonArrayStart() |
| |
| let version = try readJsonInteger() |
| if (version != TJSONProtocol.Version) { |
| throw TProtocolError(error: .badVersion(expected: "\(TJSONProtocol.Version)", got: "\(version)"), message: "Bad version") |
| } |
| |
| let buf = try readJsonString(skipContext: false) |
| guard let name = String(bytes: buf, encoding: .utf8) else { |
| throw TProtocolError(error: .invalidData, message: "Invalid message name") |
| } |
| guard let type = TMessageType(rawValue: Int32(try readJsonInteger())) else { |
| throw TProtocolError(error: .invalidData, message: "Invalid message type") |
| } |
| let seqID = try readJsonInteger() |
| |
| return (name, type, Int32(seqID)) |
| } |
| |
| public func readMessageEnd() throws { |
| try readJsonArrayEnd() |
| } |
| |
| public func readStructBegin() throws -> String { |
| try readJsonObjectStart() |
| return "" |
| } |
| |
| public func readStructEnd() throws { |
| try readJsonObjectEnd() |
| } |
| |
| public func readFieldBegin() throws -> (String, TType, Int32) { |
| let ch = try reader.peek() |
| if (ch == TJSONProtocolConstants.RightBrace[0]) { |
| return ("", TType.stop, 0) |
| } |
| |
| let fieldID = try readJsonInteger() |
| try readJsonObjectStart() |
| let fieldName: [UInt8] = try readJsonString(skipContext: false) |
| let fieldType: TType = try TType.getTypeIdForTypeName(fieldName) |
| guard let name = String(bytes: fieldName, encoding: .utf8) else { |
| throw TProtocolError(error: .invalidData, message: "Invalid field name") |
| } |
| return (name, fieldType, Int32(fieldID)) |
| } |
| |
| public func readFieldEnd() throws { |
| try readJsonObjectEnd() |
| } |
| |
| public func readMapBegin() throws -> (TType, TType, Int32) { |
| try readJsonArrayStart() |
| let keyTypeName = try readJsonString(skipContext: false) |
| let keyType = try TType.getTypeIdForTypeName(keyTypeName) |
| |
| let valueTypeName = try readJsonString(skipContext: false) |
| let valueType = try TType.getTypeIdForTypeName(valueTypeName) |
| |
| let count = try readJsonInteger() |
| |
| try checkReadBytesAvailable(keyType: keyType, valueType: valueType, count: Int32(count)) |
| try readJsonObjectStart() |
| return (keyType, valueType, Int32(count)) |
| } |
| |
| public func readMapEnd() throws { |
| try readJsonObjectEnd() |
| try readJsonArrayEnd() |
| } |
| |
| public func readSetBegin() throws -> (TType, Int32) { |
| try readJsonArrayStart() |
| |
| let elementTypeName = try readJsonString(skipContext: false) |
| let elementType = try TType.getTypeIdForTypeName(elementTypeName) |
| |
| let count = try readJsonInteger() |
| |
| try checkReadBytesAvailable(elementType, Int32(count)) |
| |
| return (elementType, Int32(count)) |
| } |
| |
| public func readSetEnd() throws { |
| try readJsonArrayEnd() |
| } |
| |
| public func readListBegin() throws -> (TType, Int32) { |
| try readJsonArrayStart() |
| |
| let elementTypeName = try readJsonString(skipContext: false) |
| let elementType = try TType.getTypeIdForTypeName(elementTypeName) |
| |
| let count = try readJsonInteger() |
| |
| try checkReadBytesAvailable(elementType, Int32(count)) |
| return (elementType, Int32(count)) |
| } |
| |
| public func readListEnd() throws { |
| try readJsonArrayEnd() |
| } |
| |
| public func read() throws -> String { |
| let buf = try readJsonString(skipContext: false) |
| guard let str = String(bytes: buf, encoding: .utf8) else { |
| throw TProtocolError(error: .invalidData, message: "Cannot convert bytes to string") |
| } |
| return str |
| } |
| |
| public func read() throws -> Bool { |
| let intValue = try readJsonInteger() |
| return intValue == 0 ? false : true |
| } |
| |
| public func read() throws -> UInt8 { |
| return UInt8(try readJsonInteger()) |
| } |
| |
| public func read() throws -> Int8 { |
| return Int8(try readJsonInteger()) |
| } |
| |
| public func read() throws -> Int16 { |
| return Int16(try readJsonInteger()) |
| } |
| |
| public func read() throws -> Int32 { |
| return Int32(try readJsonInteger()) |
| } |
| |
| public func read() throws -> Int64 { |
| return try readJsonInteger() |
| } |
| |
| public func read() throws -> Double { |
| return try readJsonDouble() |
| } |
| |
| public func read() throws -> Data { |
| let base64Bytes = try readJsonBase64() |
| return Data(bytes: base64Bytes, count: base64Bytes.count) |
| } |
| |
| public func read() throws -> UUID { |
| let buf = try readJsonString(skipContext: false) |
| guard let id = String(bytes: buf, encoding: .utf8) else { |
| throw TProtocolError(error: .invalidData, message: "Cannot convert bytes to string") |
| } |
| guard let uuid = UUID(uuidString: id) else { |
| throw TProtocolError(error: .invalidData, message: "Cannot convert string to uuid") |
| } |
| return uuid |
| } |
| |
| public func writeMessageBegin(name: String, type messageType: TMessageType, sequenceID: Int32) throws { |
| resetContext() |
| try writeJsonArrayStart() |
| try writeJsonInteger(num: Int64(TJSONProtocol.Version)) |
| |
| guard let nameData = name.data(using: .utf8) else { |
| throw TProtocolError(error: .invalidData, message: "Cannot convert message name to bytes data") |
| } |
| try writeJsonString(bytes: [UInt8] (nameData)) |
| |
| try writeJsonInteger(num: Int64(messageType.rawValue)) |
| try writeJsonInteger(num: Int64(sequenceID)) |
| } |
| |
| public func writeMessageEnd() throws { |
| try writeJsonArrayEnd() |
| } |
| |
| public func writeStructBegin(name: String) throws { |
| try writeJsonObjectStart() |
| } |
| |
| public func writeStructEnd() throws { |
| try writeJsonObjectEnd() |
| } |
| |
| public func writeFieldBegin(name: String, type fieldType: TType, fieldID: Int32) throws { |
| try writeJsonInteger(num: Int64(fieldID)) |
| |
| try writeJsonObjectStart() |
| |
| let fieldTypeName = try fieldType.getTypeNameForTypeId() |
| try writeJsonString(bytes: fieldTypeName) |
| } |
| |
| public func writeFieldStop() throws { |
| // Nop |
| } |
| |
| public func writeFieldEnd() throws { |
| try writeJsonObjectEnd() |
| } |
| |
| public func writeMapBegin(keyType: TType, valueType: TType, size: Int32) throws { |
| try writeJsonArrayStart() |
| |
| let mapKeyTypeName = try keyType.getTypeNameForTypeId() |
| try writeJsonString(bytes: mapKeyTypeName) |
| |
| let mapValueTypeName = try valueType.getTypeNameForTypeId() |
| try writeJsonString(bytes: mapValueTypeName) |
| |
| try writeJsonInteger(num: Int64(size)) |
| |
| try writeJsonObjectStart() |
| } |
| |
| public func writeMapEnd() throws { |
| try writeJsonObjectEnd() |
| try writeJsonArrayEnd() |
| } |
| |
| public func writeSetBegin(elementType: TType, size: Int32) throws { |
| try writeJsonArrayStart() |
| |
| let elementTypeName = try elementType.getTypeNameForTypeId() |
| try writeJsonString(bytes: elementTypeName) |
| |
| try writeJsonInteger(num: Int64(size)) |
| } |
| |
| public func writeSetEnd() throws { |
| try writeJsonArrayEnd() |
| } |
| |
| public func writeListBegin(elementType: TType, size: Int32) throws { |
| try writeJsonArrayStart() |
| |
| let elementTypeName = try elementType.getTypeNameForTypeId() |
| try writeJsonString(bytes: elementTypeName) |
| |
| try writeJsonInteger(num: Int64(size)) |
| } |
| |
| public func writeListEnd() throws { |
| try writeJsonArrayEnd() |
| } |
| |
| public func write(_ value: String) throws { |
| guard let strData = value.data(using: .utf8) else { |
| throw TProtocolError(error: .invalidData, message: "Cannot convert string value to bytes data") |
| } |
| |
| try writeJsonString(bytes: [UInt8](strData)) |
| } |
| |
| public func write(_ value: Bool) throws { |
| try writeJsonInteger(num: value ? 1 : 0) |
| } |
| |
| public func write(_ value: UInt8) throws { |
| try writeJsonInteger(num: Int64(value)) |
| } |
| |
| public func write(_ value: Int8) throws { |
| try writeJsonInteger(num: Int64(value)) |
| } |
| |
| public func write(_ value: Int16) throws { |
| try writeJsonInteger(num: Int64(value)) |
| } |
| |
| public func write(_ value: Int32) throws { |
| try writeJsonInteger(num: Int64(value)) |
| } |
| |
| public func write(_ value: Int64) throws { |
| try writeJsonInteger(num: value) |
| } |
| |
| public func write(_ value: Double) throws { |
| try writeJsonDouble(num: value) |
| } |
| |
| public func write(_ value: Data) throws { |
| try writeJsonBase64(bytes: [UInt8](value)) |
| } |
| |
| public func write(_ value: UUID) throws { |
| guard let strData = value.uuidString.data(using: .utf8) else { |
| throw TProtocolError(error: .invalidData, message: "Cannot convert UUID value to bytes data") |
| } |
| |
| try writeJsonString(bytes: [UInt8](strData)) |
| } |
| |
| // MARK: - Private functions |
| private func checkReadBytesAvailable(keyType: TType, valueType: TType, count: Int32) throws { |
| let elmSize = try getMinSerializedSize(keyType) + getMinSerializedSize(valueType) |
| _ = count * elmSize |
| // TODO: implement checkReadBytesAvailable in TTransport |
| // transport.checkReadBytesAvailable(size: count * elmSize) |
| } |
| |
| private func checkReadBytesAvailable(_ elementType: TType, _ count: Int32) throws { |
| let elmSize = try getMinSerializedSize(elementType) |
| _ = count * elmSize |
| // TODO: implement checkReadBytesAvailable in TTransport |
| // transport.checkReadBytesAvailable(size: count * elmSize) |
| } |
| |
| private func getMinSerializedSize(_ type: TType) throws -> Int32 { |
| switch(type) { |
| case .stop, .void: return 0 |
| case .bool, .i8, .i16, .i32, .i64, .double: return 1 |
| case .string, .struct, .map, .set, .list: return 2 // empty object |
| default: |
| throw TProtocolError(error: .invalidData, message: "Invalid TType") |
| } |
| } |
| |
| // MARK: - TJSONProtocol inner classes |
| /* |
| Base class for tracking JSON contexts that may require |
| inserting/reading additional JSON syntax characters |
| This base context does nothing |
| */ |
| class JSONBaseContext { |
| var proto: TJSONProtocol |
| |
| init(on proto: TJSONProtocol) { |
| self.proto = proto |
| } |
| |
| func writeConditionalDelimiter() throws { |
| } |
| |
| func readConditionalDelimiter() throws { |
| } |
| |
| func escapeNumbers() -> Bool { |
| return false |
| } |
| } |
| |
| /* |
| Context for JSON lists. will insert/read commas before each item except for the first one |
| */ |
| class JSONListContext: JSONBaseContext { |
| private var first: Bool = true |
| |
| override init(on proto: TJSONProtocol) { |
| super.init(on: proto) |
| } |
| |
| override func writeConditionalDelimiter() throws { |
| if (first) { |
| first = false |
| } else { |
| try proto.transport.writeJSONComma() |
| } |
| } |
| |
| override func readConditionalDelimiter() throws { |
| if (first) { |
| first = false |
| } else { |
| try proto.readJsonSyntaxChar(bytes: TJSONProtocolConstants.Comma) |
| } |
| } |
| } |
| |
| /* |
| Context for JSON records. Will insert/read colons before the value portion of each record pair, |
| and commas before each key except the first. In addition, will indicate that numbers in the key position |
| need to be escaped in quotes (since JSON keys must be strings). |
| */ |
| class JSONPairContext : JSONBaseContext { |
| private var colon: Bool = true |
| private var first: Bool = true |
| |
| override init(on proto: TJSONProtocol) { |
| super.init(on: proto) |
| } |
| |
| override func writeConditionalDelimiter() throws { |
| if (first) { |
| first = false |
| colon = true |
| } else { |
| if (colon) { |
| try proto.transport.writeJSONColon() |
| } else { |
| try proto.transport.writeJSONComma() |
| } |
| self.colon = !self.colon |
| } |
| } |
| |
| override func readConditionalDelimiter() throws { |
| if (first) { |
| first = false |
| colon = true |
| } else { |
| try proto.readJsonSyntaxChar(bytes: colon ? TJSONProtocolConstants.Colon : TJSONProtocolConstants.Comma) |
| self.colon = !self.colon |
| } |
| } |
| |
| override func escapeNumbers() -> Bool { |
| return colon |
| } |
| } |
| |
| class JSONContextStack { |
| private var items: [JSONBaseContext] = [] |
| |
| func peek() -> JSONBaseContext { |
| guard let topElement = items.first else { fatalError("This stack is empty.") } |
| return topElement |
| } |
| |
| func pop() -> JSONBaseContext { |
| return items.removeFirst() |
| } |
| |
| func push(_ element: JSONBaseContext) { |
| items.insert(element, at: 0) |
| } |
| |
| func clear() { |
| items.removeAll() |
| } |
| |
| func isEmpty() -> Bool { |
| return items.count == 0 |
| } |
| } |
| |
| class LookaheadReader { |
| private var byteData: UInt8? |
| private var hasData: Bool = false |
| var proto: TJSONProtocol |
| |
| init(on proto: TJSONProtocol) { |
| self.proto = proto |
| } |
| |
| func read() throws -> UInt8 { |
| if (hasData) { |
| hasData = false |
| } else { |
| let data = try proto.transport.readAll(size: 1) |
| byteData = Array(data)[0] |
| } |
| if let byte = byteData { |
| return byte |
| } |
| throw TProtocolError(error: .invalidData, message: "Reader does not have data to read") |
| } |
| |
| func peek() throws -> UInt8 { |
| if (!hasData) { |
| let data = try proto.transport.readAll(size: 1) |
| byteData = Array(data)[0] |
| hasData = true |
| } |
| if let byte = byteData { |
| return byte |
| } |
| throw TProtocolError(error: .invalidData, message: "Reader does not have data to peek") |
| } |
| } |
| } |
| |
| // MARK: TJSONProtocolConstants |
| /** |
| TJSONProtocol Constants properties/fields |
| */ |
| public struct TJSONProtocolConstants { |
| public static let Comma: [UInt8] = ",".asciiBytes() |
| public static let Colon: [UInt8] = ":".asciiBytes() |
| public static let LeftBrace: [UInt8] = "{".asciiBytes() |
| public static let RightBrace: [UInt8] = "}".asciiBytes() |
| public static let LeftBracket: [UInt8] = "[".asciiBytes() |
| public static let RightBracket: [UInt8] = "]".asciiBytes() |
| public static let Quote: [UInt8] = "\"".asciiBytes() |
| public static let Backslash: [UInt8] = "\\".asciiBytes() |
| |
| public static let JsonCharTable: [UInt8] = [ |
| 0, 0, 0, 0, 0, 0, 0, 0, b, t, n, 0, f, r, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 1, 1, qt, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 |
| ] |
| |
| // \b -> \u{0008} |
| // \f -> \u{000C} |
| public static let EscapeChars: [Character] = ["\"", "\\", "/", "\u{0008}", "\u{000C}", "\n", "\r", "\t" ] |
| public static let EscapeCharValues: [UInt8] = "\"\\/\u{0008}\u{000C}\n\r\t".asciiBytes() |
| public static let EscSequences: [UInt8] = "\\u00".asciiBytes() |
| |
| public struct TypeNames { |
| public static let NameBool: [UInt8] = "tf".asciiBytes() |
| public static let NameByte: [UInt8] = "i8".asciiBytes() |
| public static let NameI16: [UInt8] = "i16".asciiBytes() |
| public static let NameI32: [UInt8] = "i32".asciiBytes() |
| public static let NameI64: [UInt8] = "i64".asciiBytes() |
| public static let NameDouble: [UInt8] = "dbl".asciiBytes() |
| public static let NameStruct: [UInt8] = "rec".asciiBytes() |
| public static let NameString: [UInt8] = "str".asciiBytes() |
| public static let NameMap: [UInt8] = "map".asciiBytes() |
| public static let NameList: [UInt8] = "lst".asciiBytes() |
| public static let NameSet: [UInt8] = "set".asciiBytes() |
| } |
| |
| // MARK: private fields helpers |
| private static let b: UInt8 = "b".asciiBytes()[0] |
| private static let t: UInt8 = "t".asciiBytes()[0] |
| private static let n: UInt8 = "n".asciiBytes()[0] |
| private static let f: UInt8 = "f".asciiBytes()[0] |
| private static let r: UInt8 = "r".asciiBytes()[0] |
| private static let qt: UInt8 = "\"".asciiBytes()[0] |
| } |
| |
| // MARK: Extensions |
| extension String { |
| public func asciiBytes() -> [UInt8] { |
| var result: [UInt8] = [] |
| for char in self { |
| result.append(char.asciiValue!) |
| } |
| return result |
| } |
| |
| subscript(offset: Int) -> Character { |
| self[index(startIndex, offsetBy: offset)] |
| } |
| } |
| |
| extension Character { |
| public func asciiByte() -> UInt8 { |
| return self.asciiValue! |
| } |
| } |
| |
| extension UInt8 { |
| /** |
| Convert a byte containing a hex value to its corresponding hex character |
| */ |
| public func toHexChar() -> UInt8 { |
| var value = self & 0x0F |
| if (value < 10) { |
| let zeroChar = Character("0").asciiValue! |
| return value + zeroChar |
| } |
| value -= 10 |
| let aChar = Character("a").asciiValue! |
| return value + aChar |
| } |
| |
| public func isJsonNumeric() -> Bool { |
| let numberBytes = "+-.0123456789Ee".asciiBytes() |
| if (numberBytes.contains(self)) { |
| return true |
| } |
| return false |
| } |
| |
| public func asCharacter() -> Character { |
| let scalar = UnicodeScalar(self) |
| return Character(scalar) |
| } |
| } |
| |
| extension UInt16 { |
| public func isHighSurrogate() throws -> Bool { |
| let wch = self |
| if let d800 = UInt16("D800", radix: 16), |
| let dbff = UInt16("DBFF", radix: 16) { |
| return wch >= d800 && wch <= dbff |
| } else { |
| throw TProtocolError(error: .invalidData, message: "isHighSurrogate failed") |
| } |
| } |
| |
| public func isLowSurrogate() throws -> Bool{ |
| let wch = self |
| if let dc00 = UInt16("DC00", radix: 16), |
| let dfff = UInt16("DFFF", radix: 16) { |
| return wch >= dc00 && wch <= dfff |
| } else { |
| throw TProtocolError(error: .invalidData, message: "isLowSurrogate failed") |
| } |
| } |
| } |
| |
| extension TType { |
| public static func getTypeIdForTypeName(_ name: [UInt8]) throws -> TType { |
| var result = TType.stop |
| if (name.count > 1) { |
| switch(name[0]) { |
| case "t".asciiBytes()[0]: |
| result = TType.bool |
| case "i".asciiBytes()[0]: |
| switch(name[1]) { |
| case "8".asciiBytes()[0]: |
| result = TType.i8 |
| case "1".asciiBytes()[0]: |
| result = TType.i16 |
| case "3".asciiBytes()[0]: |
| result = TType.i32 |
| case "6".asciiBytes()[0]: |
| result = TType.i64 |
| default: |
| result = TType.stop |
| } |
| case "d".asciiBytes()[0]: |
| result = TType.double |
| case "l".asciiBytes()[0]: |
| result = TType.list |
| case "m".asciiBytes()[0]: |
| result = TType.map |
| case "r".asciiBytes()[0]: |
| result = TType.struct |
| case "s".asciiBytes()[0]: |
| if (name[1] == "t".asciiBytes()[0]) { |
| result = TType.string |
| } else if (name[1] == "e".asciiBytes()[0]) { |
| result = TType.set |
| } |
| default: |
| result = TType.stop |
| } |
| } |
| |
| if (result == TType.stop) { |
| throw TProtocolError(error: .notImplemented, message: "Unrecognized exType") |
| } |
| |
| return result |
| } |
| |
| public func getTypeNameForTypeId() throws -> [UInt8] { |
| let typeId = self |
| switch(typeId) { |
| case .bool: |
| return TJSONProtocolConstants.TypeNames.NameBool |
| case .i8: |
| return TJSONProtocolConstants.TypeNames.NameByte |
| case .i16: |
| return TJSONProtocolConstants.TypeNames.NameI16 |
| case .i32: |
| return TJSONProtocolConstants.TypeNames.NameI32 |
| case .i64: |
| return TJSONProtocolConstants.TypeNames.NameI64 |
| case .double: |
| return TJSONProtocolConstants.TypeNames.NameDouble |
| case .string: |
| return TJSONProtocolConstants.TypeNames.NameString |
| case .struct: |
| return TJSONProtocolConstants.TypeNames.NameStruct |
| case .map: |
| return TJSONProtocolConstants.TypeNames.NameMap |
| case .set: |
| return TJSONProtocolConstants.TypeNames.NameSet |
| case .list: |
| return TJSONProtocolConstants.TypeNames.NameList |
| default: |
| throw TProtocolError(error: .invalidData, message: "TypeId: \(typeId) does not have mapping Name") |
| } |
| } |
| } |
| |
| extension TTransport { |
| func writeJSONColon() throws { |
| try self.write(data: Data(bytes: TJSONProtocolConstants.Colon, count: TJSONProtocolConstants.Colon.count)) |
| } |
| |
| func writeJSONComma() throws { |
| try self.write(data: Data(bytes: TJSONProtocolConstants.Comma, count: TJSONProtocolConstants.Comma.count)) |
| } |
| |
| func writeJSONQuote() throws { |
| try self.write(data: Data(bytes: TJSONProtocolConstants.Quote, count: TJSONProtocolConstants.Quote.count)) |
| } |
| |
| func writeJSONBackslash() throws { |
| try self.write(data: Data(bytes: TJSONProtocolConstants.Backslash, count: TJSONProtocolConstants.Backslash.count)) |
| } |
| |
| func writeJSONEscSequences() throws { |
| try self.write(data: Data(bytes: TJSONProtocolConstants.EscSequences, count: TJSONProtocolConstants.EscSequences.count)) |
| } |
| |
| func writeJSONLeftBrace() throws { |
| try self.write(data: Data(bytes: TJSONProtocolConstants.LeftBrace, count: TJSONProtocolConstants.LeftBrace.count)) |
| } |
| |
| func writeJSONRightBrace() throws { |
| try self.write(data: Data(bytes: TJSONProtocolConstants.RightBrace, count: TJSONProtocolConstants.RightBrace.count)) |
| } |
| |
| func writeJSONLeftBracket() throws { |
| try self.write(data: Data(bytes: TJSONProtocolConstants.LeftBracket, count: TJSONProtocolConstants.LeftBracket.count)) |
| } |
| |
| func writeJSONRightBracket() throws { |
| try self.write(data: Data(bytes: TJSONProtocolConstants.RightBracket, count: TJSONProtocolConstants.RightBracket.count)) |
| } |
| } |