diff options
Diffstat (limited to 'lib/csharp')
-rw-r--r-- | lib/csharp/Makefile.am | 2 | ||||
-rw-r--r-- | lib/csharp/src/Protocol/TBase64Utils.cs | 100 | ||||
-rw-r--r-- | lib/csharp/src/Protocol/TJSONProtocol.cs | 1070 | ||||
-rw-r--r-- | lib/csharp/src/Protocol/TProtocol.cs | 4 | ||||
-rw-r--r-- | lib/csharp/src/Protocol/TProtocolException.cs | 1 | ||||
-rw-r--r-- | lib/csharp/src/Thrift.csproj | 2 | ||||
-rw-r--r-- | lib/csharp/src/Transport/TTransport.cs | 5 |
7 files changed, 1182 insertions, 2 deletions
diff --git a/lib/csharp/Makefile.am b/lib/csharp/Makefile.am index d8a007be9..8110319cb 100644 --- a/lib/csharp/Makefile.am +++ b/lib/csharp/Makefile.am @@ -20,6 +20,8 @@ THRIFTCODE= \ src/Collections/THashSet.cs \ src/Protocol/TBase.cs \ + src/Protocol/TBase64Utils.cs \ + src/Protocol/TJSONProtocol.cs \ src/Protocol/TProtocolException.cs \ src/Protocol/TProtocolFactory.cs \ src/Protocol/TList.cs \ diff --git a/lib/csharp/src/Protocol/TBase64Utils.cs b/lib/csharp/src/Protocol/TBase64Utils.cs new file mode 100644 index 000000000..f857d6fce --- /dev/null +++ b/lib/csharp/src/Protocol/TBase64Utils.cs @@ -0,0 +1,100 @@ +/** + * 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. + */ + +using System; + +namespace Thrift.Protocol +{ + internal static class TBase64Utils + { + internal const string ENCODE_TABLE = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + internal static void encode(byte[] src, int srcOff, int len, byte[] dst, + int dstOff) + { + dst[dstOff] = (byte)ENCODE_TABLE[(src[srcOff] >> 2) & 0x3F]; + if (len == 3) + { + dst[dstOff + 1] = + (byte)ENCODE_TABLE[ + ((src[srcOff] << 4) + (src[srcOff + 1] >> 4)) & 0x3F]; + dst[dstOff + 2] = + (byte)ENCODE_TABLE[ + ((src[srcOff + 1] << 2) + (src[srcOff + 2] >> 6)) & 0x3F]; + dst[dstOff + 3] = + (byte)ENCODE_TABLE[src[srcOff + 2] & 0x3F]; + } + else if (len == 2) + { + dst[dstOff + 1] = + (byte)ENCODE_TABLE[ + ((src[srcOff] << 4) + (src[srcOff + 1] >> 4)) & 0x3F]; + dst[dstOff + 2] = + (byte)ENCODE_TABLE[(src[srcOff + 1] << 2) & 0x3F]; + + } + else + { // len == 1) { + dst[dstOff + 1] = + (byte)ENCODE_TABLE[(src[srcOff] << 4) & 0x3F]; + } + } + + private static int[] DECODE_TABLE = { + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, + 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, + 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, + -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, + 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + }; + + internal static void decode(byte[] src, int srcOff, int len, byte[] dst, + int dstOff) + { + dst[dstOff] = (byte) + ((DECODE_TABLE[src[srcOff] & 0x0FF] << 2) | + (DECODE_TABLE[src[srcOff + 1] & 0x0FF] >> 4)); + if (len > 2) + { + dst[dstOff + 1] = (byte) + (((DECODE_TABLE[src[srcOff + 1] & 0x0FF] << 4) & 0xF0) | + (DECODE_TABLE[src[srcOff + 2] & 0x0FF] >> 2)); + if (len > 3) + { + dst[dstOff + 2] = (byte) + (((DECODE_TABLE[src[srcOff + 2] & 0x0FF] << 6) & 0xC0) | + DECODE_TABLE[src[srcOff + 3] & 0x0FF]); + } + } + } + + } +} diff --git a/lib/csharp/src/Protocol/TJSONProtocol.cs b/lib/csharp/src/Protocol/TJSONProtocol.cs new file mode 100644 index 000000000..65cab4f5b --- /dev/null +++ b/lib/csharp/src/Protocol/TJSONProtocol.cs @@ -0,0 +1,1070 @@ +/** + * 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. + */ + +using System; +using System.IO; +using System.Text; +using System.Collections.Generic; + +using Thrift.Transport; + +namespace Thrift.Protocol +{ + /// <summary> + /// JSON protocol implementation for thrift. + /// + /// This is a full-featured protocol supporting Write and Read. + /// + /// Please see the C++ class header for a detailed description of the + /// protocol's wire format. + /// + /// Adapted from the Java version. + /// </summary> + public class TJSONProtocol : TProtocol + { + /// <summary> + /// Factory for JSON protocol objects + /// </summary> + public class Factory : TProtocolFactory + { + public TProtocol GetProtocol(TTransport trans) + { + return new TJSONProtocol(trans); + } + } + + private static byte[] COMMA = new byte[] { (byte)',' }; + private static byte[] COLON = new byte[] { (byte)':' }; + private static byte[] LBRACE = new byte[] { (byte)'{' }; + private static byte[] RBRACE = new byte[] { (byte)'}' }; + private static byte[] LBRACKET = new byte[] { (byte)'[' }; + private static byte[] RBRACKET = new byte[] { (byte)']' }; + private static byte[] QUOTE = new byte[] { (byte)'"' }; + private static byte[] BACKSLASH = new byte[] { (byte)'\\' }; + private static byte[] ZERO = new byte[] { (byte)'0' }; + + private byte[] ESCSEQ = new byte[] { (byte)'\\', (byte)'u', (byte)'0', (byte)'0' }; + + private const long VERSION = 1; + private byte[] JSON_CHAR_TABLE = { + 0, 0, 0, 0, 0, 0, 0, 0,(byte)'b',(byte)'t',(byte)'n', 0,(byte)'f',(byte)'r', 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1,(byte)'"', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + }; + + private char[] ESCAPE_CHARS = "\"\\bfnrt".ToCharArray(); + + private byte[] ESCAPE_CHAR_VALS = { + (byte)'"', (byte)'\\', (byte)'\b', (byte)'\f', (byte)'\n', (byte)'\r', (byte)'\t', + }; + + private const int DEF_STRING_SIZE = 16; + + private static byte[] NAME_BOOL = new byte[] { (byte)'t', (byte)'f' }; + private static byte[] NAME_BYTE = new byte[] { (byte)'i', (byte)'8' }; + private static byte[] NAME_I16 = new byte[] { (byte)'i', (byte)'1', (byte)'6' }; + private static byte[] NAME_I32 = new byte[] { (byte)'i', (byte)'3', (byte)'2' }; + private static byte[] NAME_I64 = new byte[] { (byte)'i', (byte)'6', (byte)'4' }; + private static byte[] NAME_DOUBLE = new byte[] { (byte)'d', (byte)'b', (byte)'l' }; + private static byte[] NAME_STRUCT = new byte[] { (byte)'r', (byte)'e', (byte)'c' }; + private static byte[] NAME_STRING = new byte[] { (byte)'s', (byte)'t', (byte)'r' }; + private static byte[] NAME_MAP = new byte[] { (byte)'m', (byte)'a', (byte)'p' }; + private static byte[] NAME_LIST = new byte[] { (byte)'l', (byte)'s', (byte)'t' }; + private static byte[] NAME_SET = new byte[] { (byte)'s', (byte)'e', (byte)'t' }; + + private static byte[] GetTypeNameForTypeID(TType typeID) + { + switch (typeID) + { + case TType.Bool: + return NAME_BOOL; + case TType.Byte: + return NAME_BYTE; + case TType.I16: + return NAME_I16; + case TType.I32: + return NAME_I32; + case TType.I64: + return NAME_I64; + case TType.Double: + return NAME_DOUBLE; + case TType.String: + return NAME_STRING; + case TType.Struct: + return NAME_STRUCT; + case TType.Map: + return NAME_MAP; + case TType.Set: + return NAME_SET; + case TType.List: + return NAME_LIST; + default: + throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, + "Unrecognized type"); + } + } + + private static TType GetTypeIDForTypeName(byte[] name) + { + TType result = TType.Stop; + if (name.Length > 1) + { + switch (name[0]) + { + case (byte)'d': + result = TType.Double; + break; + case (byte)'i': + switch (name[1]) + { + case (byte)'8': + result = TType.Byte; + break; + case (byte)'1': + result = TType.I16; + break; + case (byte)'3': + result = TType.I32; + break; + case (byte)'6': + result = TType.I64; + break; + } + break; + case (byte)'l': + result = TType.List; + break; + case (byte)'m': + result = TType.Map; + break; + case (byte)'r': + result = TType.Struct; + break; + case (byte)'s': + if (name[1] == (byte)'t') + { + result = TType.String; + } + else if (name[1] == (byte)'e') + { + result = TType.Set; + } + break; + case (byte)'t': + result = TType.Bool; + break; + } + } + if (result == TType.Stop) + { + throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, + "Unrecognized type"); + } + return result; + } + + ///<summary> + /// Base class for tracking JSON contexts that may require + /// inserting/Reading additional JSON syntax characters + /// This base context does nothing. + ///</summary> + protected class JSONBaseContext + { + protected TJSONProtocol proto; + + public JSONBaseContext(TJSONProtocol proto) + { + this.proto = proto; + } + + public virtual void Write() { } + + public virtual void Read() { } + + public virtual bool EscapeNumbers() { return false; } + } + + ///<summary> + /// Context for JSON lists. Will insert/Read commas before each item except + /// for the first one + ///</summary> + protected class JSONListContext : JSONBaseContext + { + public JSONListContext(TJSONProtocol protocol) + : base(protocol) + { + + } + + private bool first = true; + + public override void Write() + { + if (first) + { + first = false; + } + else + { + proto.trans.Write(COMMA); + } + } + + public override void Read() + { + if (first) + { + first = false; + } + else + { + proto.ReadJSONSyntaxChar(COMMA); + } + } + } + + ///<summary> + /// 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). + ///</summary> + protected class JSONPairContext : JSONBaseContext + { + public JSONPairContext(TJSONProtocol proto) + : base(proto) + { + + } + + private bool first = true; + private bool colon = true; + + public override void Write() + { + if (first) + { + first = false; + colon = true; + } + else + { + proto.trans.Write(colon ? COLON : COMMA); + colon = !colon; + } + } + + public override void Read() + { + if (first) + { + first = false; + colon = true; + } + else + { + proto.ReadJSONSyntaxChar(colon ? COLON : COMMA); + colon = !colon; + } + } + + public override bool EscapeNumbers() + { + return colon; + } + } + + ///<summary> + /// Holds up to one byte from the transport + ///</summary> + protected class LookaheadReader + { + protected TJSONProtocol proto; + + public LookaheadReader(TJSONProtocol proto) + { + this.proto = proto; + } + + private bool hasData; + private byte[] data = new byte[1]; + + ///<summary> + /// Return and consume the next byte to be Read, either taking it from the + /// data buffer if present or getting it from the transport otherwise. + ///</summary> + public byte Read() + { + if (hasData) + { + hasData = false; + } + else + { + proto.trans.ReadAll(data, 0, 1); + } + return data[0]; + } + + ///<summary> + /// Return the next byte to be Read without consuming, filling the data + /// buffer if it has not been filled alReady. + ///</summary> + public byte Peek() + { + if (!hasData) + { + proto.trans.ReadAll(data, 0, 1); + } + hasData = true; + return data[0]; + } + } + + // Default encoding + protected Encoding utf8Encoding = UTF8Encoding.UTF8; + + // Stack of nested contexts that we may be in + protected Stack<JSONBaseContext> contextStack = new Stack<JSONBaseContext>(); + + // Current context that we are in + protected JSONBaseContext context; + + // Reader that manages a 1-byte buffer + protected LookaheadReader reader; + + ///<summary> + /// Push a new JSON context onto the stack. + ///</summary> + protected void PushContext(JSONBaseContext c) + { + contextStack.Push(context); + context = c; + } + + ///<summary> + /// Pop the last JSON context off the stack + ///</summary> + protected void PopContext() + { + context = contextStack.Pop(); + } + + ///<summary> + /// TJSONProtocol Constructor + ///</summary> + public TJSONProtocol(TTransport trans) + : base(trans) + { + context = new JSONBaseContext(this); + reader = new LookaheadReader(this); + } + + // Temporary buffer used by several methods + private byte[] tempBuffer = new byte[4]; + + ///<summary> + /// Read a byte that must match b[0]; otherwise an excpetion is thrown. + /// Marked protected to avoid synthetic accessor in JSONListContext.Read + /// and JSONPairContext.Read + ///</summary> + protected void ReadJSONSyntaxChar(byte[] b) + { + byte ch = reader.Read(); + if (ch != b[0]) + { + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Unexpected character:" + (char)ch); + } + } + + ///<summary> + /// Convert a byte containing a hex char ('0'-'9' or 'a'-'f') into its + /// corresponding hex value + ///</summary> + private static byte HexVal(byte ch) + { + if ((ch >= '0') && (ch <= '9')) + { + return (byte)((char)ch - '0'); + } + else if ((ch >= 'a') && (ch <= 'f')) + { + return (byte)((char)ch - 'a'); + } + else + { + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Expected hex character"); + } + } + + ///<summary> + /// Convert a byte containing a hex value to its corresponding hex character + ///</summary> + private static byte HexChar(byte val) + { + val &= 0x0F; + if (val < 10) + { + return (byte)((char)val + '0'); + } + else + { + return (byte)((char)val + 'a'); + } + } + + ///<summary> + /// Write the bytes in array buf as a JSON characters, escaping as needed + ///</summary> + private void WriteJSONString(byte[] b) + { + context.Write(); + trans.Write(QUOTE); + int len = b.Length; + for (int i = 0; i < len; i++) + { + if ((b[i] & 0x00FF) >= 0x30) + { + if (b[i] == BACKSLASH[0]) + { + trans.Write(BACKSLASH); + trans.Write(BACKSLASH); + } + else + { + trans.Write(b, i, 1); + } + } + else + { + tempBuffer[0] = JSON_CHAR_TABLE[b[i]]; + if (tempBuffer[0] == 1) + { + trans.Write(b, i, 1); + } + else if (tempBuffer[0] > 1) + { + trans.Write(BACKSLASH); + trans.Write(tempBuffer, 0, 1); + } + else + { + trans.Write(ESCSEQ); + tempBuffer[0] = HexChar((byte)(b[i] >> 4)); + tempBuffer[1] = HexChar(b[i]); + trans.Write(tempBuffer, 0, 2); + } + } + } + trans.Write(QUOTE); + } + + ///<summary> + /// Write out number as a JSON value. If the context dictates so, it will be + /// wrapped in quotes to output as a JSON string. + ///</summary> + private void WriteJSONInteger(long num) + { + context.Write(); + String str = num.ToString(); + + bool escapeNum = context.EscapeNumbers(); + if (escapeNum) + trans.Write(QUOTE); + + trans.Write(utf8Encoding.GetBytes(str)); + + if (escapeNum) + trans.Write(QUOTE); + } + + ///<summary> + /// 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. + ///</summary> + private void WriteJSONDouble(double num) + { + context.Write(); + String str = num.ToString(); + bool special = false; + + switch (str[0]) + { + case 'N': // NaN + case 'I': // Infinity + special = true; + break; + case '-': + if (str[1] == 'I') + { // -Infinity + special = true; + } + break; + } + + bool escapeNum = special || context.EscapeNumbers(); + + if (escapeNum) + trans.Write(QUOTE); + + trans.Write(utf8Encoding.GetBytes(str)); + + if (escapeNum) + trans.Write(QUOTE); + } + ///<summary> + /// Write out contents of byte array b as a JSON string with base-64 encoded + /// data + ///</summary> + private void WriteJSONBase64(byte[] b) + { + context.Write(); + trans.Write(QUOTE); + + int len = b.Length; + int off = 0; + + while (len >= 3) + { + // Encode 3 bytes at a time + TBase64Utils.encode(b, off, 3, tempBuffer, 0); + trans.Write(tempBuffer, 0, 4); + off += 3; + len -= 3; + } + if (len > 0) + { + // Encode remainder + TBase64Utils.encode(b, off, len, tempBuffer, 0); + trans.Write(tempBuffer, 0, len + 1); + } + + trans.Write(QUOTE); + } + + private void WriteJSONObjectStart() + { + context.Write(); + trans.Write(LBRACE); + PushContext(new JSONPairContext(this)); + } + + private void WriteJSONObjectEnd() + { + PopContext(); + trans.Write(RBRACE); + } + + private void WriteJSONArrayStart() + { + context.Write(); + trans.Write(LBRACKET); + PushContext(new JSONListContext(this)); + } + + private void WriteJSONArrayEnd() + { + PopContext(); + trans.Write(RBRACKET); + } + + public override void WriteMessageBegin(TMessage message) + { + WriteJSONArrayStart(); + WriteJSONInteger(VERSION); + + byte[] b = utf8Encoding.GetBytes(message.Name); + WriteJSONString(b); + + WriteJSONInteger((long)message.Type); + WriteJSONInteger(message.SeqID); + } + + public override void WriteMessageEnd() + { + WriteJSONArrayEnd(); + } + + public override void WriteStructBegin(TStruct str) + { + WriteJSONObjectStart(); + } + + public override void WriteStructEnd() + { + WriteJSONObjectEnd(); + } + + public override void WriteFieldBegin(TField field) + { + WriteJSONInteger(field.ID); + WriteJSONObjectStart(); + WriteJSONString(GetTypeNameForTypeID(field.Type)); + } + + public override void WriteFieldEnd() + { + WriteJSONObjectEnd(); + } + + public override void WriteFieldStop() { } + + public override void WriteMapBegin(TMap map) + { + WriteJSONArrayStart(); + WriteJSONString(GetTypeNameForTypeID(map.KeyType)); + WriteJSONString(GetTypeNameForTypeID(map.ValueType)); + WriteJSONInteger(map.Count); + WriteJSONObjectStart(); + } + + public override void WriteMapEnd() + { + WriteJSONObjectEnd(); + WriteJSONArrayEnd(); + } + + public override void WriteListBegin(TList list) + { + WriteJSONArrayStart(); + WriteJSONString(GetTypeNameForTypeID(list.ElementType)); + WriteJSONInteger(list.Count); + } + + public override void WriteListEnd() + { + WriteJSONArrayEnd(); + } + + public override void WriteSetBegin(TSet set) + { + WriteJSONArrayStart(); + WriteJSONString(GetTypeNameForTypeID(set.ElementType)); + WriteJSONInteger(set.Count); + } + + public override void WriteSetEnd() + { + WriteJSONArrayEnd(); + } + + public override void WriteBool(bool b) + { + WriteJSONInteger(b ? (long)1 : (long)0); + } + + public override void WriteByte(byte b) + { + WriteJSONInteger((long)b); + } + + public override void WriteI16(short i16) + { + WriteJSONInteger((long)i16); + } + + public override void WriteI32(int i32) + { + WriteJSONInteger((long)i32); + } + + public override void WriteI64(long i64) + { + WriteJSONInteger(i64); + } + + public override void WriteDouble(double dub) + { + WriteJSONDouble(dub); + } + + public override void WriteString(String str) + { + byte[] b = utf8Encoding.GetBytes(str); + WriteJSONString(b); + } + + public override void WriteBinary(byte[] bin) + { + WriteJSONBase64(bin); + } + + /** + * Reading methods. + */ + + ///<summary> + /// Read in a JSON string, unescaping as appropriate.. Skip Reading from the + /// context if skipContext is true. + ///</summary> + private byte[] ReadJSONString(bool skipContext) + { + MemoryStream buffer = new MemoryStream(); + + + if (!skipContext) + { + context.Read(); + } + ReadJSONSyntaxChar(QUOTE); + while (true) + { + byte ch = reader.Read(); + if (ch == QUOTE[0]) + { + break; + } + if (ch == ESCSEQ[0]) + { + ch = reader.Read(); + if (ch == ESCSEQ[1]) + { + ReadJSONSyntaxChar(ZERO); + ReadJSONSyntaxChar(ZERO); + trans.ReadAll(tempBuffer, 0, 2); + ch = (byte)((HexVal((byte)tempBuffer[0]) << 4) + HexVal(tempBuffer[1])); + } + else + { + int off = Array.IndexOf(ESCAPE_CHARS, ch); + if (off == -1) + { + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Expected control char"); + } + ch = ESCAPE_CHAR_VALS[off]; + } + } + buffer.Write(new byte[] { (byte)ch }, 0, 1); + } + return buffer.ToArray(); + } + + ///<summary> + /// Return true if the given byte could be a valid part of a JSON number. + ///</summary> + private bool IsJSONNumeric(byte b) + { + switch (b) + { + case (byte)'+': + case (byte)'-': + case (byte)'.': + case (byte)'0': + case (byte)'1': + case (byte)'2': + case (byte)'3': + case (byte)'4': + case (byte)'5': + case (byte)'6': + case (byte)'7': + case (byte)'8': + case (byte)'9': + case (byte)'E': + case (byte)'e': + return true; + } + return false; + } + + ///<summary> + /// 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. + ////</summary> + private String ReadJSONNumericChars() + { + StringBuilder strbld = new StringBuilder(); + while (true) + { + byte ch = reader.Peek(); + if (!IsJSONNumeric(ch)) + { + break; + } + strbld.Append((char)reader.Read()); + } + return strbld.ToString(); + } + + ///<summary> + /// Read in a JSON number. If the context dictates, Read in enclosing quotes. + ///</summary> + private long ReadJSONInteger() + { + context.Read(); + if (context.EscapeNumbers()) + { + ReadJSONSyntaxChar(QUOTE); + } + String str = ReadJSONNumericChars(); + if (context.EscapeNumbers()) + { + ReadJSONSyntaxChar(QUOTE); + } + try + { + return Int64.Parse(str); + } + catch (FormatException) + { + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Bad data encounted in numeric data"); + } + } + + ///<summary> + /// 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. + ///</summary> + private double ReadJSONDouble() + { + context.Read(); + if (reader.Peek() == QUOTE[0]) + { + byte[] arr = ReadJSONString(true); + double dub = Double.Parse(utf8Encoding.GetString(arr)); + + if (!context.EscapeNumbers() && !Double.IsNaN(dub) && + !Double.IsInfinity(dub)) + { + // Throw exception -- we should not be in a string in this case + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Numeric data unexpectedly quoted"); + } + return dub; + } + else + { + if (context.EscapeNumbers()) + { + // This will throw - we should have had a quote if escapeNum == true + ReadJSONSyntaxChar(QUOTE); + } + try + { + return Double.Parse(ReadJSONNumericChars()); + } + catch (FormatException) + { + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Bad data encounted in numeric data"); + } + } + } + + //<summary> + /// Read in a JSON string containing base-64 encoded data and decode it. + ///</summary> + private byte[] ReadJSONBase64() + { + byte[] b = ReadJSONString(false); + int len = b.Length; + int off = 0; + int size = 0; + while (len >= 4) + { + // Decode 4 bytes at a time + TBase64Utils.decode(b, off, 4, b, size); // NB: decoded 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 regular string type) + if (len > 1) + { + // Decode remainder + TBase64Utils.decode(b, off, len, b, size); // NB: decoded in place + size += len - 1; + } + // Sadly we must copy the byte[] (any way around this?) + byte[] result = new byte[size]; + Array.Copy(b, 0, result, 0, size); + return result; + } + + private void ReadJSONObjectStart() + { + context.Read(); + ReadJSONSyntaxChar(LBRACE); + PushContext(new JSONPairContext(this)); + } + + private void ReadJSONObjectEnd() + { + ReadJSONSyntaxChar(RBRACE); + PopContext(); + } + + private void ReadJSONArrayStart() + { + context.Read(); + ReadJSONSyntaxChar(LBRACKET); + PushContext(new JSONListContext(this)); + } + + private void ReadJSONArrayEnd() + { + ReadJSONSyntaxChar(RBRACKET); + PopContext(); + } + + public override TMessage ReadMessageBegin() + { + TMessage message = new TMessage(); + ReadJSONArrayStart(); + if (ReadJSONInteger() != VERSION) + { + throw new TProtocolException(TProtocolException.BAD_VERSION, + "Message contained bad version."); + } + + message.Name = utf8Encoding.GetString(ReadJSONString(false)); + message.Type = (TMessageType)ReadJSONInteger(); + message.SeqID = (int)ReadJSONInteger(); + return message; + } + + public override void ReadMessageEnd() + { + ReadJSONArrayEnd(); + } + + public override TStruct ReadStructBegin() + { + ReadJSONObjectStart(); + return new TStruct(); + } + + public override void ReadStructEnd() + { + ReadJSONObjectEnd(); + } + + public override TField ReadFieldBegin() + { + TField field = new TField(); + byte ch = reader.Peek(); + if (ch == RBRACE[0]) + { + field.Type = TType.Stop; + } + else + { + field.ID = (short)ReadJSONInteger(); + ReadJSONObjectStart(); + field.Type = GetTypeIDForTypeName(ReadJSONString(false)); + } + return field; + } + + public override void ReadFieldEnd() + { + ReadJSONObjectEnd(); + } + + public override TMap ReadMapBegin() + { + TMap map = new TMap(); + ReadJSONArrayStart(); + map.KeyType = GetTypeIDForTypeName(ReadJSONString(false)); + map.ValueType = GetTypeIDForTypeName(ReadJSONString(false)); + map.Count = (int)ReadJSONInteger(); + ReadJSONObjectStart(); + return map; + } + + public override void ReadMapEnd() + { + ReadJSONObjectEnd(); + ReadJSONArrayEnd(); + } + + public override TList ReadListBegin() + { + TList list = new TList(); + ReadJSONArrayStart(); + list.ElementType = GetTypeIDForTypeName(ReadJSONString(false)); + list.Count = (int)ReadJSONInteger(); + return list; + } + + public override void ReadListEnd() + { + ReadJSONArrayEnd(); + } + + public override TSet ReadSetBegin() + { + TSet set = new TSet(); + ReadJSONArrayStart(); + set.ElementType = GetTypeIDForTypeName(ReadJSONString(false)); + set.Count = (int)ReadJSONInteger(); + return set; + } + + public override void ReadSetEnd() + { + ReadJSONArrayEnd(); + } + + public override bool ReadBool() + { + return (ReadJSONInteger() == 0 ? false : true); + } + + public override byte ReadByte() + { + return (byte)ReadJSONInteger(); + } + + public override short ReadI16() + { + return (short)ReadJSONInteger(); + } + + public override int ReadI32() + { + return (int)ReadJSONInteger(); + } + + public override long ReadI64() + { + return (long)ReadJSONInteger(); + } + + public override double ReadDouble() + { + return ReadJSONDouble(); + } + + public override String ReadString() + { + return ReadJSONString(false).ToString(); + } + + public override byte[] ReadBinary() + { + return ReadJSONBase64(); + } + + } +} diff --git a/lib/csharp/src/Protocol/TProtocol.cs b/lib/csharp/src/Protocol/TProtocol.cs index 27c7dc11b..4f723dddd 100644 --- a/lib/csharp/src/Protocol/TProtocol.cs +++ b/lib/csharp/src/Protocol/TProtocol.cs @@ -60,7 +60,7 @@ namespace Thrift.Protocol public abstract void WriteI32(int i32); public abstract void WriteI64(long i64); public abstract void WriteDouble(double d); - public void WriteString(string s) { + public virtual void WriteString(string s) { WriteBinary(Encoding.UTF8.GetBytes(s)); } public abstract void WriteBinary(byte[] b); @@ -83,7 +83,7 @@ namespace Thrift.Protocol public abstract int ReadI32(); public abstract long ReadI64(); public abstract double ReadDouble(); - public string ReadString() { + public virtual string ReadString() { return Encoding.UTF8.GetString(ReadBinary()); } public abstract byte[] ReadBinary(); diff --git a/lib/csharp/src/Protocol/TProtocolException.cs b/lib/csharp/src/Protocol/TProtocolException.cs index d570bb823..207f5e919 100644 --- a/lib/csharp/src/Protocol/TProtocolException.cs +++ b/lib/csharp/src/Protocol/TProtocolException.cs @@ -32,6 +32,7 @@ namespace Thrift.Protocol public const int NEGATIVE_SIZE = 2; public const int SIZE_LIMIT = 3; public const int BAD_VERSION = 4; + public const int NOT_IMPLEMENTED= 5; protected int type_ = UNKNOWN; diff --git a/lib/csharp/src/Thrift.csproj b/lib/csharp/src/Thrift.csproj index f382f407b..f154841c7 100644 --- a/lib/csharp/src/Thrift.csproj +++ b/lib/csharp/src/Thrift.csproj @@ -56,8 +56,10 @@ <ItemGroup> <Compile Include="Collections\THashSet.cs" /> <Compile Include="Protocol\TBase.cs" /> + <Compile Include="Protocol\TBase64Utils.cs" /> <Compile Include="Protocol\TBinaryProtocol.cs" /> <Compile Include="Protocol\TField.cs" /> + <Compile Include="Protocol\TJSONProtocol.cs" /> <Compile Include="Protocol\TList.cs" /> <Compile Include="Protocol\TMap.cs" /> <Compile Include="Protocol\TMessage.cs" /> diff --git a/lib/csharp/src/Transport/TTransport.cs b/lib/csharp/src/Transport/TTransport.cs index d2391c204..cecde8723 100644 --- a/lib/csharp/src/Transport/TTransport.cs +++ b/lib/csharp/src/Transport/TTransport.cs @@ -61,6 +61,11 @@ namespace Thrift.Transport return got; } + public virtual void Write(byte[] buf) + { + Write (buf, 0, buf.Length); + } + public abstract void Write(byte[] buf, int off, int len); public virtual void Flush() |