blob: b1e41c7455918b58ff4ea58870004ff5f93e0d41 [file] [log] [blame]
HojjatK 💾d67e5c22023-06-05 19:17:26 -07001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20import Foundation
21
22/**
23 JSON protocol implementation for thrift.
24 This is a full-feature protocol supporting Write and Read.
25 Please see the C++ class header for a detailed description of the protocol's wire format
26 Adapted from netstd C# version
27 */
28public class TJSONProtocol: TProtocol {
29 static let Version: Int = 1
30
31 public var transport: TTransport
32
33 // Temporary buffer used by several methods
34 private var tempBuffer: [UInt8] = [0,0,0,0]
35 private var contextStack: JSONContextStack = JSONContextStack()
36 private var currentContext: JSONBaseContext?
37 private var context: JSONBaseContext {
38 get throws {
39 if (currentContext != nil) {
40 return currentContext!
41 }
42 throw TProtocolError(error: .depthLimit, message: "Current context is nil")
43 }
44 }
45
46 /**
47 Reader that manages a 1-byte buffer
48 */
49 private var optionalReader: LookaheadReader?
50 private var reader: LookaheadReader {
51 get throws {
52 if (optionalReader != nil) {
53 return optionalReader!
54 }
55 throw TProtocolError(error: .depthLimit, message: "Lookahead reader is nil")
56 }
57 }
58
59 // MARK: TJSONProtocol Constructor
60 public required init(on transport: TTransport) {
61 self.transport = transport
62 currentContext = JSONBaseContext(on: self)
63 optionalReader = LookaheadReader(on: self)
64 }
65
66 // MARK: TJSONProtocol helpers
67 /**
68 Push a new JSON context onto the context stack
69 */
70 func pushContext(_ context: JSONBaseContext) {
71 contextStack.push(context)
72 currentContext = context
73 }
74
75 /**
76 Pop current JSON context from the context stack
77 */
78 func popContext() {
79 _ = contextStack.pop()
80 currentContext = contextStack.isEmpty() ? JSONBaseContext(on: self) : contextStack.peek()
81 }
82
83 /**
84 Reset context stack to pristine state. Allows for reusal of the protocol even in cases where the protocol instance
85 was in an undefined state due to dangling/stale/obsolete contexts
86 */
87 func resetContext() {
88 contextStack.clear()
89 currentContext = JSONBaseContext(on: self)
90 }
91
92 /**
93 Read a byte that must match bytes[0]; otherwise an exception is thrown.
94 - bytes: Input bytes array
95 */
96 func readJsonSyntaxChar(bytes: [UInt8]) throws {
97 let ch: UInt8 = try reader.read()
98 if (ch != bytes[0]) {
99 throw TProtocolError(error: .invalidData, message: "Unexpected character: \(ch.asCharacter())")
100 }
101 }
102
103 /**
104 Write the bytes in array buffer as a JSON characters, escaping as needed
105 */
106 func writeJsonString(bytes: [UInt8]) throws {
107 try context.writeConditionalDelimiter()
108 try transport.writeJSONQuote()
109
110 let len: Int = bytes.count
111 for i in 0..<len {
112 if (bytes[i] & 0x00FF >= 0x30) {
113 if (bytes[i] == TJSONProtocolConstants.Backslash[0]) {
114 try transport.writeJSONBackslash()
115 try transport.writeJSONBackslash()
116 } else {
117 try transport.write(data: Data(bytes: [bytes[i]], count: 1))
118 }
119 } else {
120 tempBuffer[0] = TJSONProtocolConstants.JsonCharTable[Int(bytes[i])]
121 if (tempBuffer[0] == 1) {
122 try transport.write(data: Data(bytes: [bytes[i]], count: 1))
123 } else if (tempBuffer[0] > 1) {
124 try transport.writeJSONBackslash()
125 try transport.write(data: Data(bytes: [tempBuffer[0]], count: 1))
126 } else {
127 try transport.writeJSONEscSequences()
128 tempBuffer[0] = (bytes[i] >> 4).toHexChar()
129 tempBuffer[1] = (bytes[i]).toHexChar()
130 try transport.write(data: Data(bytes: [tempBuffer[0], tempBuffer[1]], count:2))
131 }
132 }
133 }
134 try transport.writeJSONQuote()
135 }
136
137 /**
138 Write out number as a JSON value. If the context dicates so, it will be wrapped in quotes to output as a JSON string.
139 */
140 func writeJsonInteger(num: Int64) throws {
141 try context.writeConditionalDelimiter()
142 let str: String = String(num)
143
144 let escapeNum: Bool = try context.escapeNumbers()
145 if (escapeNum) {
146 try transport.write(data: Data(bytes: TJSONProtocolConstants.Quote, count: TJSONProtocolConstants.Quote.count))
147 }
148
149 let strData: Data = str.data(using: .utf8)!
150 try transport.write(data: strData)
151
152 if (escapeNum) {
153 try transport.write(data: Data(bytes: TJSONProtocolConstants.Quote, count: TJSONProtocolConstants.Quote.count))
154 }
155 }
156
157 /**
158 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.
159 */
160 func writeJsonDouble(num: Double) throws {
161 try context.writeConditionalDelimiter()
162 let str = String(num)
163 var special = false
164
165 switch(str[0]) {
166 case "N", "I":
167 // Nan or Infinity
168 special = true
169 case "-":
170 if (str[1] == "I") {
171 // -Infinity
172 special = true
173 }
174 default:
175 special = false
176 }
177
178 let escapeNum = try context.escapeNumbers()
179 let escapeNumOrSpecial = special || escapeNum
180 if (escapeNumOrSpecial) {
181 try transport.writeJSONQuote()
182 }
183
184 if let strData = str.data(using: .utf8) {
185 try transport.write(data: strData)
186 } else {
187 throw TProtocolError(error: .invalidData, message: "Cannot convert double number to data bytes")
188 }
189
190 if (escapeNumOrSpecial) {
191 try transport.writeJSONQuote()
192 }
193 }
194
195 /**
196 Write out contents of byte array as a JSON string with base-64 encoded data
197 */
198 func writeJsonBase64(bytes: [UInt8]) throws {
199 try context.writeConditionalDelimiter()
200 try transport.writeJSONQuote()
201
202 var len = bytes.count
203 var off = 0
204 while (len >= 3) {
205 // Encode 3 bytes at a time
206 TBase64Utils.encode(src: bytes, srcOff: off, len: 3, dst: &tempBuffer, dstOff: 0)
207 try transport.write(data: Data(bytes: tempBuffer, count: 4))
208 off += 3
209 len -= 3
210 }
211
212 if (len > 0) {
213 // Encode remainder
214 TBase64Utils.encode(src: bytes, srcOff: off, len: len, dst: &tempBuffer, dstOff: 0)
215 try transport.write(data: Data(bytes: tempBuffer, count: len + 1))
216 }
217
218 try transport.writeJSONQuote()
219 }
220
221 func writeJsonObjectStart() throws {
222 try context.writeConditionalDelimiter()
223 try transport.writeJSONLeftBrace()
224 pushContext(JSONPairContext(on: self))
225 }
226
227 func writeJsonObjectEnd() throws {
228 popContext()
229 try transport.writeJSONRightBrace()
230 }
231
232 func writeJsonArrayStart() throws {
233 try context.writeConditionalDelimiter()
234 try transport.writeJSONLeftBracket()
235 pushContext(JSONListContext(on: self))
236 }
237
238 func writeJsonArrayEnd() throws {
239 popContext()
240 try transport.writeJSONRightBracket()
241 }
242
243 /**
244 Read in a JSON string, unescaping as appropriate. Skip reading from the context if skipContext is true.
245 */
246 func readJsonString(skipContext: Bool) throws -> [UInt8] {
247 var codeunits: [Character] = []
248
249 if (!skipContext) {
250 try context.readConditionalDelimiter()
251 }
252 try readJsonSyntaxChar(bytes: TJSONProtocolConstants.Quote)
253
254 var dataBuffer = Data()
255 while (true) {
256 var ch: UInt8 = try reader.read()
257 if (ch == TJSONProtocolConstants.Quote[0]) {
258 break
259 }
260
261 // Escaped?
262 if (ch != TJSONProtocolConstants.EscSequences[0]) {
263 dataBuffer.append([ch], count: 1)
264 continue
265 }
266
267 // distinguish between \uXXXX and \?
268 ch = try reader.read()
269 if (ch != TJSONProtocolConstants.EscSequences[1]) { // control chars like \n
270 guard let off: Int = TJSONProtocolConstants.EscSequences.firstIndex(of: ch) else {
271 throw TProtocolError(error: .invalidData, message: "Expected control char")
272 }
273 ch = TJSONProtocolConstants.EscapeCharValues[off]
274 dataBuffer.append([ch], count: 1)
275 continue
276 }
277
278 // It's \uXXXX
279 let tempData: Data = try transport.readAll(size: 4)
280 let wch = Int16( ((tempData[0]).toHexChar() << 12) +
281 ((tempData[1]).toHexChar() << 8) +
282 ((tempData[2]).toHexChar() << 4) +
283 ((tempData[3]).toHexChar()) )
284 guard let wchScalar = UnicodeScalar(Int(wch)) else {
285 throw TProtocolError(error: .invalidData, message: "Expected Unicode character")
286 }
287
288 if (try wch.magnitude.isHighSurrogate()) {
289 if (codeunits.count > 0) {
290 throw TProtocolError(error: .invalidData, message: "Exptected low surrogate char")
291 }
292 codeunits.append(Character(wchScalar))
293 } else if (try wch.magnitude.isLowSurrogate()) {
294 if (codeunits.count == 0) {
295 throw TProtocolError(error: .invalidData, message: "Exptected high surrogate char")
296 }
297 codeunits.append(Character(wchScalar))
298 guard let codeunitsData = String(codeunits).data(using: .utf8) else {
299 throw TProtocolError(error: .invalidData, message: "Codeunits cannot be converted to string bytes")
300 }
301 dataBuffer.append(codeunitsData)
302 codeunits.removeAll()
303 } else {
304 let bytesArray: [UInt8] = withUnsafeBytes(of: wch.bigEndian, Array.init)
305 dataBuffer.append(Data(bytes: bytesArray, count: bytesArray.count))
306 }
307 }
308
309 if (codeunits.count > 0) {
310 throw TProtocolError(error: .invalidData, message: "Expected low surrogate char")
311 }
312
313 let bytesResult: [UInt8] = dataBuffer.map { $0 }
314 return bytesResult
315 }
316
317 /**
318 Read in a sequence of characters that are all valid in JSON numbers. Does not do a complete regex check
319 to validate that this is actually a number.
320 */
321 func readJsonNumericChars() throws -> String {
322 var str = ""
323 while(true) {
324 // TODO: Workaround for primitive types with TJSONProtocol: think - how to rewrite into more easy from without exception
325 do {
326 let ch: UInt8 = try reader.peek()
327 if (!ch.isJsonNumeric()) {
328 break
329 }
330 let c = try reader.read()
331 str.append(c.asCharacter())
332 } catch is TTransportError {
333 break
334 }
335 catch let error {
336 throw error
337 }
338 }
339 return str
340 }
341
342 /**
343 Read in a JSON number. If the context dictates, read in enclosing quotes.
344 */
345 func readJsonInteger() throws -> Int64 {
346 try context.readConditionalDelimiter()
347 let escapeNum = try context.escapeNumbers()
348 if (escapeNum) {
349 try readJsonSyntaxChar(bytes: TJSONProtocolConstants.Quote)
350 }
351
352 let str: String = try readJsonNumericChars()
353 if (escapeNum) {
354 try readJsonSyntaxChar(bytes: TJSONProtocolConstants.Quote)
355 }
356
357 guard let result = Int64(str) else { throw TProtocolError(error: .invalidData, message: "Cannot convert \(str) to Int64") }
358 return result
359 }
360
361 /**
362 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.
363 */
364 func readJsonDouble() throws -> Double {
365 try context.readConditionalDelimiter()
366
367 let escapeNum = try context.escapeNumbers()
368 if (try reader.peek() == TJSONProtocolConstants.Quote[0]) {
369 let arr: [UInt8] = try readJsonString(skipContext: true)
370 if let str: String = String(data: Data(arr), encoding: .utf8),
371 let dub = Double(str) {
372 if (!escapeNum && !dub.isNaN && !dub.isInfinite) {
373 throw TProtocolError(error: .invalidData, message: "Numeric data unexpectedly quoted")
374 }
375 return dub
376 } else {
377 throw TProtocolError(error: .invalidData, message: "Numeric data convertion to double failed")
378 }
379 }
380
381 if (escapeNum) {
382 try readJsonSyntaxChar(bytes: TJSONProtocolConstants.Quote)
383 }
384
385 let str: String = try readJsonNumericChars()
386 if let dub = Double(str) {
387 return dub
388 } else {
389 throw TProtocolError(error: .invalidData, message: "Numeric data convertion to double failed")
390 }
391 }
392
393 /**
394 Read in a JSON string containing base-64 encoded data and decode it.
395 */
396 func readJsonBase64() throws -> [UInt8] {
397 var b = try readJsonString(skipContext: false)
398 var len = b.count
399 var off = 0
400 var size = 0
401
402 // Reduce len to ignore fill bytes
403 while( (len > 0) && (b[len - 1] == "=".asciiBytes()[0]) ) {
404 len -= 1
405 }
406
407 // Read & decode full byte triplets = 4 source bytes
408 while (len > 4) {
409 // Decode 4 bytes at a time
410 TBase64Utils.decode(src: b, srcOff: off, len: 4, dst: &b, dstOff: size) // Nb: decode in place
411 off += 4
412 len -= 4
413 size += 3
414 }
415
416 // Don't decode if we hit the end or got a single leftover byte
417 // (invalid base64 but legal for skip of reqular string exType)
418 if (len > 1) {
419 // Decode remainder
420 TBase64Utils.decode(src: b, srcOff: off, len: len, dst: &b, dstOff: size) // NB: decode in place
421 size += len - 1
422 }
423
424 let result: [UInt8] = Array(b[0..<size])
425 return result
426 }
427
428 func readJsonObjectStart() throws {
429 try context.readConditionalDelimiter()
430 try readJsonSyntaxChar(bytes: TJSONProtocolConstants.LeftBrace)
431 pushContext(JSONPairContext(on: self))
432 }
433
434 func readJsonObjectEnd() throws {
435 try readJsonSyntaxChar(bytes: TJSONProtocolConstants.RightBrace)
436 popContext()
437 }
438
439 func readJsonArrayStart() throws {
440 try context.readConditionalDelimiter()
441 try readJsonSyntaxChar(bytes: TJSONProtocolConstants.LeftBracket)
442 pushContext(JSONListContext(on: self))
443 }
444
445 func readJsonArrayEnd() throws {
446 try readJsonSyntaxChar(bytes: TJSONProtocolConstants.RightBracket)
447 popContext()
448 }
449
450 // MARK: - TProtocol
451 public func readMessageBegin() throws -> (String, TMessageType, Int32) {
452 resetContext()
453 try readJsonArrayStart()
454
455 let version = try readJsonInteger()
456 if (version != TJSONProtocol.Version) {
457 throw TProtocolError(error: .badVersion(expected: "\(TJSONProtocol.Version)", got: "\(version)"), message: "Bad version")
458 }
459
460 let buf = try readJsonString(skipContext: false)
461 guard let name = String(bytes: buf, encoding: .utf8) else {
462 throw TProtocolError(error: .invalidData, message: "Invalid message name")
463 }
464 guard let type = TMessageType(rawValue: Int32(try readJsonInteger())) else {
465 throw TProtocolError(error: .invalidData, message: "Invalid message type")
466 }
467 let seqID = try readJsonInteger()
468
469 return (name, type, Int32(seqID))
470 }
471
472 public func readMessageEnd() throws {
473 try readJsonArrayEnd()
474 }
475
476 public func readStructBegin() throws -> String {
477 try readJsonObjectStart()
478 return ""
479 }
480
481 public func readStructEnd() throws {
482 try readJsonObjectEnd()
483 }
484
485 public func readFieldBegin() throws -> (String, TType, Int32) {
486 let ch = try reader.peek()
487 if (ch == TJSONProtocolConstants.RightBrace[0]) {
488 return ("", TType.stop, 0)
489 }
490
491 let fieldID = try readJsonInteger()
492 try readJsonObjectStart()
493 let fieldName: [UInt8] = try readJsonString(skipContext: false)
494 let fieldType: TType = try TType.getTypeIdForTypeName(fieldName)
495 guard let name = String(bytes: fieldName, encoding: .utf8) else {
496 throw TProtocolError(error: .invalidData, message: "Invalid field name")
497 }
498 return (name, fieldType, Int32(fieldID))
499 }
500
501 public func readFieldEnd() throws {
502 try readJsonObjectEnd()
503 }
504
505 public func readMapBegin() throws -> (TType, TType, Int32) {
506 try readJsonArrayStart()
507 let keyTypeName = try readJsonString(skipContext: false)
508 let keyType = try TType.getTypeIdForTypeName(keyTypeName)
509
510 let valueTypeName = try readJsonString(skipContext: false)
511 let valueType = try TType.getTypeIdForTypeName(valueTypeName)
512
513 let count = try readJsonInteger()
514
515 try checkReadBytesAvailable(keyType: keyType, valueType: valueType, count: Int32(count))
516 try readJsonObjectStart()
517 return (keyType, valueType, Int32(count))
518 }
519
520 public func readMapEnd() throws {
521 try readJsonObjectEnd()
522 try readJsonArrayEnd()
523 }
524
525 public func readSetBegin() throws -> (TType, Int32) {
526 try readJsonArrayStart()
527
528 let elementTypeName = try readJsonString(skipContext: false)
529 let elementType = try TType.getTypeIdForTypeName(elementTypeName)
530
531 let count = try readJsonInteger()
532
533 try checkReadBytesAvailable(elementType, Int32(count))
534
535 return (elementType, Int32(count))
536 }
537
538 public func readSetEnd() throws {
539 try readJsonArrayEnd()
540 }
541
542 public func readListBegin() throws -> (TType, Int32) {
543 try readJsonArrayStart()
544
545 let elementTypeName = try readJsonString(skipContext: false)
546 let elementType = try TType.getTypeIdForTypeName(elementTypeName)
547
548 let count = try readJsonInteger()
549
550 try checkReadBytesAvailable(elementType, Int32(count))
551 return (elementType, Int32(count))
552 }
553
554 public func readListEnd() throws {
555 try readJsonArrayEnd()
556 }
557
558 public func read() throws -> String {
559 let buf = try readJsonString(skipContext: false)
560 guard let str = String(bytes: buf, encoding: .utf8) else {
561 throw TProtocolError(error: .invalidData, message: "Cannot convert bytes to string")
562 }
563 return str
564 }
565
566 public func read() throws -> Bool {
567 let intValue = try readJsonInteger()
568 return intValue == 0 ? false : true
569 }
570
571 public func read() throws -> UInt8 {
572 return UInt8(try readJsonInteger())
573 }
574
575 public func read() throws -> Int8 {
576 return Int8(try readJsonInteger())
577 }
578
579 public func read() throws -> Int16 {
580 return Int16(try readJsonInteger())
581 }
582
583 public func read() throws -> Int32 {
584 return Int32(try readJsonInteger())
585 }
586
587 public func read() throws -> Int64 {
588 return try readJsonInteger()
589 }
590
591 public func read() throws -> Double {
592 return try readJsonDouble()
593 }
594
595 public func read() throws -> Data {
596 let base64Bytes = try readJsonBase64()
597 return Data(bytes: base64Bytes, count: base64Bytes.count)
598 }
599
600 public func read() throws -> UUID {
601 let buf = try readJsonString(skipContext: false)
602 guard let id = String(bytes: buf, encoding: .utf8) else {
603 throw TProtocolError(error: .invalidData, message: "Cannot convert bytes to string")
604 }
605 guard let uuid = UUID(uuidString: id) else {
606 throw TProtocolError(error: .invalidData, message: "Cannot convert string to uuid")
607 }
608 return uuid
609 }
610
611 public func writeMessageBegin(name: String, type messageType: TMessageType, sequenceID: Int32) throws {
612 resetContext()
613 try writeJsonArrayStart()
614 try writeJsonInteger(num: Int64(TJSONProtocol.Version))
615
616 guard let nameData = name.data(using: .utf8) else {
617 throw TProtocolError(error: .invalidData, message: "Cannot convert message name to bytes data")
618 }
619 try writeJsonString(bytes: [UInt8] (nameData))
620
621 try writeJsonInteger(num: Int64(messageType.rawValue))
622 try writeJsonInteger(num: Int64(sequenceID))
623 }
624
625 public func writeMessageEnd() throws {
626 try writeJsonArrayEnd()
627 }
628
629 public func writeStructBegin(name: String) throws {
630 try writeJsonObjectStart()
631 }
632
633 public func writeStructEnd() throws {
634 try writeJsonObjectEnd()
635 }
636
637 public func writeFieldBegin(name: String, type fieldType: TType, fieldID: Int32) throws {
638 try writeJsonInteger(num: Int64(fieldID))
639
640 try writeJsonObjectStart()
641
642 let fieldTypeName = try fieldType.getTypeNameForTypeId()
643 try writeJsonString(bytes: fieldTypeName)
644 }
645
646 public func writeFieldStop() throws {
647 // Nop
648 }
649
650 public func writeFieldEnd() throws {
651 try writeJsonObjectEnd()
652 }
653
654 public func writeMapBegin(keyType: TType, valueType: TType, size: Int32) throws {
655 try writeJsonArrayStart()
656
657 let mapKeyTypeName = try keyType.getTypeNameForTypeId()
658 try writeJsonString(bytes: mapKeyTypeName)
659
660 let mapValueTypeName = try valueType.getTypeNameForTypeId()
661 try writeJsonString(bytes: mapValueTypeName)
662
663 try writeJsonInteger(num: Int64(size))
664
665 try writeJsonObjectStart()
666 }
667
668 public func writeMapEnd() throws {
669 try writeJsonObjectEnd()
670 try writeJsonArrayEnd()
671 }
672
673 public func writeSetBegin(elementType: TType, size: Int32) throws {
674 try writeJsonArrayStart()
675
676 let elementTypeName = try elementType.getTypeNameForTypeId()
677 try writeJsonString(bytes: elementTypeName)
678
679 try writeJsonInteger(num: Int64(size))
680 }
681
682 public func writeSetEnd() throws {
683 try writeJsonArrayEnd()
684 }
685
686 public func writeListBegin(elementType: TType, size: Int32) throws {
687 try writeJsonArrayStart()
688
689 let elementTypeName = try elementType.getTypeNameForTypeId()
690 try writeJsonString(bytes: elementTypeName)
691
692 try writeJsonInteger(num: Int64(size))
693 }
694
695 public func writeListEnd() throws {
696 try writeJsonArrayEnd()
697 }
698
699 public func write(_ value: String) throws {
700 guard let strData = value.data(using: .utf8) else {
701 throw TProtocolError(error: .invalidData, message: "Cannot convert string value to bytes data")
702 }
703
704 try writeJsonString(bytes: [UInt8](strData))
705 }
706
707 public func write(_ value: Bool) throws {
708 try writeJsonInteger(num: value ? 1 : 0)
709 }
710
711 public func write(_ value: UInt8) throws {
712 try writeJsonInteger(num: Int64(value))
713 }
714
715 public func write(_ value: Int8) throws {
716 try writeJsonInteger(num: Int64(value))
717 }
718
719 public func write(_ value: Int16) throws {
720 try writeJsonInteger(num: Int64(value))
721 }
722
723 public func write(_ value: Int32) throws {
724 try writeJsonInteger(num: Int64(value))
725 }
726
727 public func write(_ value: Int64) throws {
728 try writeJsonInteger(num: value)
729 }
730
731 public func write(_ value: Double) throws {
732 try writeJsonDouble(num: value)
733 }
734
735 public func write(_ value: Data) throws {
736 try writeJsonBase64(bytes: [UInt8](value))
737 }
738
739 public func write(_ value: UUID) throws {
740 guard let strData = value.uuidString.data(using: .utf8) else {
741 throw TProtocolError(error: .invalidData, message: "Cannot convert UUID value to bytes data")
742 }
743
744 try writeJsonString(bytes: [UInt8](strData))
745 }
746
747 // MARK: - Private functions
748 private func checkReadBytesAvailable(keyType: TType, valueType: TType, count: Int32) throws {
749 let elmSize = try getMinSerializedSize(keyType) + getMinSerializedSize(valueType)
750 _ = count * elmSize
751 // TODO: implement checkReadBytesAvailable in TTransport
752 // transport.checkReadBytesAvailable(size: count * elmSize)
753 }
754
755 private func checkReadBytesAvailable(_ elementType: TType, _ count: Int32) throws {
756 let elmSize = try getMinSerializedSize(elementType)
757 _ = count * elmSize
758 // TODO: implement checkReadBytesAvailable in TTransport
759 // transport.checkReadBytesAvailable(size: count * elmSize)
760 }
761
762 private func getMinSerializedSize(_ type: TType) throws -> Int32 {
763 switch(type) {
764 case .stop, .void: return 0
765 case .bool, .i8, .i16, .i32, .i64, .double: return 1
766 case .string, .struct, .map, .set, .list: return 2 // empty object
767 default:
768 throw TProtocolError(error: .invalidData, message: "Invalid TType")
769 }
770 }
771
772 // MARK: - TJSONProtocol inner classes
773 /*
774 Base class for tracking JSON contexts that may require
775 inserting/reading additional JSON syntax characters
776 This base context does nothing
777 */
778 class JSONBaseContext {
779 var proto: TJSONProtocol
780
781 init(on proto: TJSONProtocol) {
782 self.proto = proto
783 }
784
785 func writeConditionalDelimiter() throws {
786 }
787
788 func readConditionalDelimiter() throws {
789 }
790
791 func escapeNumbers() -> Bool {
792 return false
793 }
794 }
795
796 /*
797 Context for JSON lists. will insert/read commas before each item except for the first one
798 */
799 class JSONListContext: JSONBaseContext {
800 private var first: Bool = true
801
802 override init(on proto: TJSONProtocol) {
803 super.init(on: proto)
804 }
805
806 override func writeConditionalDelimiter() throws {
807 if (first) {
808 first = false
809 } else {
810 try proto.transport.writeJSONComma()
811 }
812 }
813
814 override func readConditionalDelimiter() throws {
815 if (first) {
816 first = false
817 } else {
818 try proto.readJsonSyntaxChar(bytes: TJSONProtocolConstants.Comma)
819 }
820 }
821 }
822
823 /*
824 Context for JSON records. Will insert/read colons before the value portion of each record pair,
825 and commas before each key except the first. In addition, will indicate that numbers in the key position
826 need to be escaped in quotes (since JSON keys must be strings).
827 */
828 class JSONPairContext : JSONBaseContext {
829 private var colon: Bool = true
830 private var first: Bool = true
831
832 override init(on proto: TJSONProtocol) {
833 super.init(on: proto)
834 }
835
836 override func writeConditionalDelimiter() throws {
837 if (first) {
838 first = false
839 colon = true
840 } else {
841 if (colon) {
842 try proto.transport.writeJSONColon()
843 } else {
844 try proto.transport.writeJSONComma()
845 }
846 self.colon = !self.colon
847 }
848 }
849
850 override func readConditionalDelimiter() throws {
851 if (first) {
852 first = false
853 colon = true
854 } else {
855 try proto.readJsonSyntaxChar(bytes: colon ? TJSONProtocolConstants.Colon : TJSONProtocolConstants.Comma)
856 self.colon = !self.colon
857 }
858 }
859
860 override func escapeNumbers() -> Bool {
861 return colon
862 }
863 }
864
865 class JSONContextStack {
866 private var items: [JSONBaseContext] = []
867
868 func peek() -> JSONBaseContext {
869 guard let topElement = items.first else { fatalError("This stack is empty.") }
870 return topElement
871 }
872
873 func pop() -> JSONBaseContext {
874 return items.removeFirst()
875 }
876
877 func push(_ element: JSONBaseContext) {
878 items.insert(element, at: 0)
879 }
880
881 func clear() {
882 items.removeAll()
883 }
884
885 func isEmpty() -> Bool {
886 return items.count == 0
887 }
888 }
889
890 class LookaheadReader {
891 private var byteData: UInt8?
892 private var hasData: Bool = false
893 var proto: TJSONProtocol
894
895 init(on proto: TJSONProtocol) {
896 self.proto = proto
897 }
898
899 func read() throws -> UInt8 {
900 if (hasData) {
901 hasData = false
902 } else {
903 let data = try proto.transport.readAll(size: 1)
904 byteData = Array(data)[0]
905 }
906 if let byte = byteData {
907 return byte
908 }
909 throw TProtocolError(error: .invalidData, message: "Reader does not have data to read")
910 }
911
912 func peek() throws -> UInt8 {
913 if (!hasData) {
914 let data = try proto.transport.readAll(size: 1)
915 byteData = Array(data)[0]
916 hasData = true
917 }
918 if let byte = byteData {
919 return byte
920 }
921 throw TProtocolError(error: .invalidData, message: "Reader does not have data to peek")
922 }
923 }
924}
925
926// MARK: TJSONProtocolConstants
927/**
928 TJSONProtocol Constants properties/fields
929 */
930public struct TJSONProtocolConstants {
931 public static let Comma: [UInt8] = ",".asciiBytes()
932 public static let Colon: [UInt8] = ":".asciiBytes()
933 public static let LeftBrace: [UInt8] = "{".asciiBytes()
934 public static let RightBrace: [UInt8] = "}".asciiBytes()
935 public static let LeftBracket: [UInt8] = "[".asciiBytes()
936 public static let RightBracket: [UInt8] = "]".asciiBytes()
937 public static let Quote: [UInt8] = "\"".asciiBytes()
938 public static let Backslash: [UInt8] = "\\".asciiBytes()
939
940 public static let JsonCharTable: [UInt8] = [
941 0, 0, 0, 0, 0, 0, 0, 0, b, t, n, 0, f, r, 0, 0,
942 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
943 1, 1, qt, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
944 ]
945
946 // \b -> \u{0008}
947 // \f -> \u{000C}
948 public static let EscapeChars: [Character] = ["\"", "\\", "/", "\u{0008}", "\u{000C}", "\n", "\r", "\t" ]
949 public static let EscapeCharValues: [UInt8] = "\"\\/\u{0008}\u{000C}\n\r\t".asciiBytes()
950 public static let EscSequences: [UInt8] = "\\u00".asciiBytes()
951
952 public struct TypeNames {
953 public static let NameBool: [UInt8] = "tf".asciiBytes()
954 public static let NameByte: [UInt8] = "i8".asciiBytes()
955 public static let NameI16: [UInt8] = "i16".asciiBytes()
956 public static let NameI32: [UInt8] = "i32".asciiBytes()
957 public static let NameI64: [UInt8] = "i64".asciiBytes()
958 public static let NameDouble: [UInt8] = "dbl".asciiBytes()
959 public static let NameStruct: [UInt8] = "rec".asciiBytes()
960 public static let NameString: [UInt8] = "str".asciiBytes()
961 public static let NameMap: [UInt8] = "map".asciiBytes()
962 public static let NameList: [UInt8] = "lst".asciiBytes()
963 public static let NameSet: [UInt8] = "set".asciiBytes()
964 }
965
966 // MARK: private fields helpers
967 private static let b: UInt8 = "b".asciiBytes()[0]
968 private static let t: UInt8 = "t".asciiBytes()[0]
969 private static let n: UInt8 = "n".asciiBytes()[0]
970 private static let f: UInt8 = "f".asciiBytes()[0]
971 private static let r: UInt8 = "r".asciiBytes()[0]
972 private static let qt: UInt8 = "\"".asciiBytes()[0]
973}
974
975// MARK: Extensions
976extension String {
977 public func asciiBytes() -> [UInt8] {
978 var result: [UInt8] = []
979 for char in self {
980 result.append(char.asciiValue!)
981 }
982 return result
983 }
984
985 subscript(offset: Int) -> Character {
986 self[index(startIndex, offsetBy: offset)]
987 }
988}
989
990extension Character {
991 public func asciiByte() -> UInt8 {
992 return self.asciiValue!
993 }
994}
995
996extension UInt8 {
997 /**
998 Convert a byte containing a hex value to its corresponding hex character
999 */
1000 public func toHexChar() -> UInt8 {
1001 var value = self & 0x0F
1002 if (value < 10) {
1003 let zeroChar = Character("0").asciiValue!
1004 return value + zeroChar
1005 }
1006 value -= 10
1007 let aChar = Character("a").asciiValue!
1008 return value + aChar
1009 }
1010
1011 public func isJsonNumeric() -> Bool {
1012 let numberBytes = "+-.0123456789Ee".asciiBytes()
1013 if (numberBytes.contains(self)) {
1014 return true
1015 }
1016 return false
1017 }
1018
1019 public func asCharacter() -> Character {
1020 let scalar = UnicodeScalar(self)
1021 return Character(scalar)
1022 }
1023}
1024
1025extension UInt16 {
1026 public func isHighSurrogate() throws -> Bool {
1027 let wch = self
1028 if let d800 = UInt16("D800", radix: 16),
1029 let dbff = UInt16("DBFF", radix: 16) {
1030 return wch >= d800 && wch <= dbff
1031 } else {
1032 throw TProtocolError(error: .invalidData, message: "isHighSurrogate failed")
1033 }
1034 }
1035
1036 public func isLowSurrogate() throws -> Bool{
1037 let wch = self
1038 if let dc00 = UInt16("DC00", radix: 16),
1039 let dfff = UInt16("DFFF", radix: 16) {
1040 return wch >= dc00 && wch <= dfff
1041 } else {
1042 throw TProtocolError(error: .invalidData, message: "isLowSurrogate failed")
1043 }
1044 }
1045}
1046
1047extension TType {
1048 public static func getTypeIdForTypeName(_ name: [UInt8]) throws -> TType {
1049 var result = TType.stop
1050 if (name.count > 1) {
1051 switch(name[0]) {
1052 case "t".asciiBytes()[0]:
1053 result = TType.bool
1054 case "i".asciiBytes()[0]:
1055 switch(name[1]) {
1056 case "8".asciiBytes()[0]:
1057 result = TType.i8
1058 case "1".asciiBytes()[0]:
1059 result = TType.i16
1060 case "3".asciiBytes()[0]:
1061 result = TType.i32
1062 case "6".asciiBytes()[0]:
1063 result = TType.i64
1064 default:
1065 result = TType.stop
1066 }
1067 case "d".asciiBytes()[0]:
1068 result = TType.double
1069 case "l".asciiBytes()[0]:
1070 result = TType.list
1071 case "m".asciiBytes()[0]:
1072 result = TType.map
1073 case "r".asciiBytes()[0]:
1074 result = TType.struct
1075 case "s".asciiBytes()[0]:
1076 if (name[1] == "t".asciiBytes()[0]) {
1077 result = TType.string
1078 } else if (name[1] == "e".asciiBytes()[0]) {
1079 result = TType.set
1080 }
1081 default:
1082 result = TType.stop
1083 }
1084 }
1085
1086 if (result == TType.stop) {
1087 throw TProtocolError(error: .notImplemented, message: "Unrecognized exType")
1088 }
1089
1090 return result
1091 }
1092
1093 public func getTypeNameForTypeId() throws -> [UInt8] {
1094 let typeId = self
1095 switch(typeId) {
1096 case .bool:
1097 return TJSONProtocolConstants.TypeNames.NameBool
1098 case .i8:
1099 return TJSONProtocolConstants.TypeNames.NameByte
1100 case .i16:
1101 return TJSONProtocolConstants.TypeNames.NameI16
1102 case .i32:
1103 return TJSONProtocolConstants.TypeNames.NameI32
1104 case .i64:
1105 return TJSONProtocolConstants.TypeNames.NameI64
1106 case .double:
1107 return TJSONProtocolConstants.TypeNames.NameDouble
1108 case .string:
1109 return TJSONProtocolConstants.TypeNames.NameString
1110 case .struct:
1111 return TJSONProtocolConstants.TypeNames.NameStruct
1112 case .map:
1113 return TJSONProtocolConstants.TypeNames.NameMap
1114 case .set:
1115 return TJSONProtocolConstants.TypeNames.NameSet
1116 case .list:
1117 return TJSONProtocolConstants.TypeNames.NameList
1118 default:
1119 throw TProtocolError(error: .invalidData, message: "TypeId: \(typeId) does not have mapping Name")
1120 }
1121 }
1122}
1123
1124extension TTransport {
1125 func writeJSONColon() throws {
1126 try self.write(data: Data(bytes: TJSONProtocolConstants.Colon, count: TJSONProtocolConstants.Colon.count))
1127 }
1128
1129 func writeJSONComma() throws {
1130 try self.write(data: Data(bytes: TJSONProtocolConstants.Comma, count: TJSONProtocolConstants.Comma.count))
1131 }
1132
1133 func writeJSONQuote() throws {
1134 try self.write(data: Data(bytes: TJSONProtocolConstants.Quote, count: TJSONProtocolConstants.Quote.count))
1135 }
1136
1137 func writeJSONBackslash() throws {
1138 try self.write(data: Data(bytes: TJSONProtocolConstants.Backslash, count: TJSONProtocolConstants.Backslash.count))
1139 }
1140
1141 func writeJSONEscSequences() throws {
1142 try self.write(data: Data(bytes: TJSONProtocolConstants.EscSequences, count: TJSONProtocolConstants.EscSequences.count))
1143 }
1144
1145 func writeJSONLeftBrace() throws {
1146 try self.write(data: Data(bytes: TJSONProtocolConstants.LeftBrace, count: TJSONProtocolConstants.LeftBrace.count))
1147 }
1148
1149 func writeJSONRightBrace() throws {
1150 try self.write(data: Data(bytes: TJSONProtocolConstants.RightBrace, count: TJSONProtocolConstants.RightBrace.count))
1151 }
1152
1153 func writeJSONLeftBracket() throws {
1154 try self.write(data: Data(bytes: TJSONProtocolConstants.LeftBracket, count: TJSONProtocolConstants.LeftBracket.count))
1155 }
1156
1157 func writeJSONRightBracket() throws {
1158 try self.write(data: Data(bytes: TJSONProtocolConstants.RightBracket, count: TJSONProtocolConstants.RightBracket.count))
1159 }
1160}