THRIFT-5591 Add uuid type to IDL and implement reference code (+ improved self-tests)
Client: compiler general, netstd, Delphi
Patch: Jens Geyer
diff --git a/lib/netstd/Thrift/Protocol/Entities/TType.cs b/lib/netstd/Thrift/Protocol/Entities/TType.cs
index 4e922a7..2f3037b 100644
--- a/lib/netstd/Thrift/Protocol/Entities/TType.cs
+++ b/lib/netstd/Thrift/Protocol/Entities/TType.cs
@@ -32,6 +32,7 @@
         Struct = 12,
         Map = 13,
         Set = 14,
-        List = 15
+        List = 15,
+        Uuid = 16
     }
-}
\ No newline at end of file
+}
diff --git a/lib/netstd/Thrift/Protocol/TBinaryProtocol.cs b/lib/netstd/Thrift/Protocol/TBinaryProtocol.cs
index eee137c..ba2a7ab 100644
--- a/lib/netstd/Thrift/Protocol/TBinaryProtocol.cs
+++ b/lib/netstd/Thrift/Protocol/TBinaryProtocol.cs
@@ -21,6 +21,7 @@
 using System.Threading;
 using System.Threading.Tasks;
 using Thrift.Protocol.Entities;
+using Thrift.Protocol.Utilities;
 using Thrift.Transport;
 
 
@@ -209,6 +210,14 @@
             await Trans.WriteAsync(bytes, 0, bytes.Length, cancellationToken);
         }
 
+        public override async Task WriteUuidAsync(Guid uuid, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            var bytes = uuid.SwapByteOrder().ToByteArray();
+            await Trans.WriteAsync(bytes, 0, bytes.Length, cancellationToken);
+        }
+
         public override async ValueTask<TMessage> ReadMessageBeginAsync(CancellationToken cancellationToken)
         {
             cancellationToken.ThrowIfCancellationRequested();
@@ -401,6 +410,16 @@
             return buf;
         }
 
+        public override async ValueTask<Guid> ReadUuidAsync(CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Transport.CheckReadBytesAvailable(16);  // = sizeof(uuid)
+            var buf = new byte[16];
+            await Trans.ReadAllAsync(buf, 0, 16, cancellationToken);
+            return new Guid(buf).SwapByteOrder();
+        }
+
         public override async ValueTask<string> ReadStringAsync(CancellationToken cancellationToken)
         {
             cancellationToken.ThrowIfCancellationRequested();
@@ -443,6 +462,7 @@
                 case TType.Map: return sizeof(int);  // element count
                 case TType.Set: return sizeof(int);  // element count
                 case TType.List: return sizeof(int);  // element count
+                case TType.Uuid: return 16;  // uuid bytes
                 default: throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, "unrecognized type code");
             }
         }
diff --git a/lib/netstd/Thrift/Protocol/TCompactProtocol.cs b/lib/netstd/Thrift/Protocol/TCompactProtocol.cs
index 6893ad4..b899d3d 100644
--- a/lib/netstd/Thrift/Protocol/TCompactProtocol.cs
+++ b/lib/netstd/Thrift/Protocol/TCompactProtocol.cs
@@ -24,6 +24,7 @@
 using System.Threading;
 using System.Threading.Tasks;
 using Thrift.Protocol.Entities;
+using Thrift.Protocol.Utilities;
 using Thrift.Transport;
 
 
@@ -43,8 +44,8 @@
         private const byte NoTypeOverride = 0xFF;
 
         // ReSharper disable once InconsistentNaming
-        private static readonly byte[] TTypeToCompactType = new byte[16];
-        private static readonly TType[] CompactTypeToTType = new TType[13];
+        private static readonly byte[] TTypeToCompactType = new byte[17];
+        private static readonly TType[] CompactTypeToTType = new TType[14];
 
         /// <summary>
         ///     Used to keep track of the last field for the current and previous structs, so we can do the delta stuff.
@@ -86,18 +87,19 @@
         public TCompactProtocol(TTransport trans)
             : base(trans)
         {
-            TTypeToCompactType[(int) TType.Stop] = Types.Stop;
-            TTypeToCompactType[(int) TType.Bool] = Types.BooleanTrue;
-            TTypeToCompactType[(int) TType.Byte] = Types.Byte;
-            TTypeToCompactType[(int) TType.I16] = Types.I16;
-            TTypeToCompactType[(int) TType.I32] = Types.I32;
-            TTypeToCompactType[(int) TType.I64] = Types.I64;
-            TTypeToCompactType[(int) TType.Double] = Types.Double;
-            TTypeToCompactType[(int) TType.String] = Types.Binary;
-            TTypeToCompactType[(int) TType.List] = Types.List;
-            TTypeToCompactType[(int) TType.Set] = Types.Set;
-            TTypeToCompactType[(int) TType.Map] = Types.Map;
-            TTypeToCompactType[(int) TType.Struct] = Types.Struct;
+            TTypeToCompactType[(int)TType.Stop] = Types.Stop;
+            TTypeToCompactType[(int)TType.Bool] = Types.BooleanTrue;
+            TTypeToCompactType[(int)TType.Byte] = Types.Byte;
+            TTypeToCompactType[(int)TType.I16] = Types.I16;
+            TTypeToCompactType[(int)TType.I32] = Types.I32;
+            TTypeToCompactType[(int)TType.I64] = Types.I64;
+            TTypeToCompactType[(int)TType.Double] = Types.Double;
+            TTypeToCompactType[(int)TType.String] = Types.Binary;
+            TTypeToCompactType[(int)TType.List] = Types.List;
+            TTypeToCompactType[(int)TType.Set] = Types.Set;
+            TTypeToCompactType[(int)TType.Map] = Types.Map;
+            TTypeToCompactType[(int)TType.Struct] = Types.Struct;
+            TTypeToCompactType[(int)TType.Uuid] = Types.Uuid;
 
             CompactTypeToTType[Types.Stop] = TType.Stop;
             CompactTypeToTType[Types.BooleanTrue] = TType.Bool;
@@ -112,6 +114,7 @@
             CompactTypeToTType[Types.Set] = TType.Set;
             CompactTypeToTType[Types.Map] = TType.Map;
             CompactTypeToTType[Types.Struct] = TType.Struct;
+            CompactTypeToTType[Types.Uuid] = TType.Uuid;
         }
 
         public void Reset()
@@ -395,6 +398,14 @@
             await Trans.WriteAsync(bytes, 0, bytes.Length, cancellationToken);
         }
 
+        public override async Task WriteUuidAsync(Guid uuid, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            var bytes = uuid.SwapByteOrder().ToByteArray();
+            await Trans.WriteAsync(bytes, 0, bytes.Length, cancellationToken);
+        }
+
         public override async Task WriteMapBeginAsync(TMap map, CancellationToken cancellationToken)
         {
             cancellationToken.ThrowIfCancellationRequested();
@@ -665,6 +676,16 @@
             return buf;
         }
 
+        public override async ValueTask<Guid> ReadUuidAsync(CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Transport.CheckReadBytesAvailable(16);  // = sizeof(uuid)
+            var buf = new byte[16];
+            await Trans.ReadAllAsync(buf, 0, 16, cancellationToken);
+            return new Guid(buf).SwapByteOrder();
+        }
+
         public override async ValueTask<TList> ReadListBeginAsync(CancellationToken cancellationToken)
         {
             cancellationToken.ThrowIfCancellationRequested();
@@ -792,19 +813,20 @@
         {
             switch (type)
             {
-                case TType.Stop:    return 0;
-                case TType.Void:    return 0;
-                case TType.Bool:   return sizeof(byte);
+                case TType.Stop: return 0;
+                case TType.Void: return 0;
+                case TType.Bool: return sizeof(byte);
                 case TType.Double: return 8;  // uses fixedLongToBytes() which always writes 8 bytes
                 case TType.Byte: return sizeof(byte);
-                case TType.I16:     return sizeof(byte);  // zigzag
-                case TType.I32:     return sizeof(byte);  // zigzag
-                case TType.I64:     return sizeof(byte);  // zigzag
+                case TType.I16: return sizeof(byte);  // zigzag
+                case TType.I32: return sizeof(byte);  // zigzag
+                case TType.I64: return sizeof(byte);  // zigzag
                 case TType.String: return sizeof(byte);  // string length
-                case TType.Struct:  return 0;             // empty struct
-                case TType.Map:     return sizeof(byte);  // element count
-                case TType.Set:    return sizeof(byte);  // element count
-                case TType.List:    return sizeof(byte);  // element count
+                case TType.Struct: return 0;             // empty struct
+                case TType.Map: return sizeof(byte);  // element count
+                case TType.Set: return sizeof(byte);  // element count
+                case TType.List: return sizeof(byte);  // element count
+                case TType.Uuid: return 16;  // uuid bytes
                 default: throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, "unrecognized type code");
             }
         }
@@ -835,6 +857,7 @@
             public const byte Set = 0x0A;
             public const byte Map = 0x0B;
             public const byte Struct = 0x0C;
+            public const byte Uuid = 0x0D;
         }
     }
 }
diff --git a/lib/netstd/Thrift/Protocol/TJSONProtocol.cs b/lib/netstd/Thrift/Protocol/TJSONProtocol.cs
index 8799026..c100d86 100644
--- a/lib/netstd/Thrift/Protocol/TJSONProtocol.cs
+++ b/lib/netstd/Thrift/Protocol/TJSONProtocol.cs
@@ -401,6 +401,10 @@
         {
             await WriteJsonBase64Async(bytes, cancellationToken);
         }
+        public override async Task WriteUuidAsync(Guid uuid, CancellationToken cancellationToken = default)
+        {
+            await WriteStringAsync(uuid.ToString("D"), cancellationToken);  // no curly braces
+        }
 
         /// <summary>
         ///     Read in a JSON string, unescaping as appropriate.. Skip Reading from the
@@ -817,6 +821,11 @@
             return await ReadJsonBase64Async(cancellationToken);
         }
 
+        public override async ValueTask<Guid> ReadUuidAsync(CancellationToken cancellationToken = default)
+        {
+            return new Guid( await ReadStringAsync(cancellationToken));
+        }
+
         // Return the minimum number of bytes a type will consume on the wire
         public override int GetMinSerializedSize(TType type)
         {
@@ -835,6 +844,7 @@
                 case TType.Map: return 2;  // empty map
                 case TType.Set: return 2;  // empty set
                 case TType.List: return 2;  // empty list
+                case TType.Uuid: return 36;  // "E236974D-F0B0-4E05-8F29-0B455D41B1A1"
                 default: throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, "unrecognized type code");
             }
         }
diff --git a/lib/netstd/Thrift/Protocol/TProtocol.cs b/lib/netstd/Thrift/Protocol/TProtocol.cs
index cd93833..f2bec60 100644
--- a/lib/netstd/Thrift/Protocol/TProtocol.cs
+++ b/lib/netstd/Thrift/Protocol/TProtocol.cs
@@ -148,6 +148,7 @@
         }
 
         public abstract Task WriteBinaryAsync(byte[] bytes, CancellationToken cancellationToken = default);
+        public abstract Task WriteUuidAsync(Guid uuid, CancellationToken cancellationToken = default);
 
         public abstract ValueTask<TMessage> ReadMessageBeginAsync(CancellationToken cancellationToken = default);
 
@@ -192,5 +193,7 @@
         }
 
         public abstract ValueTask<byte[]> ReadBinaryAsync(CancellationToken cancellationToken = default);
+
+        public abstract ValueTask<Guid> ReadUuidAsync(CancellationToken cancellationToken = default);
     }
 }
diff --git a/lib/netstd/Thrift/Protocol/TProtocolDecorator.cs b/lib/netstd/Thrift/Protocol/TProtocolDecorator.cs
index b032e83..1ea9fb9 100644
--- a/lib/netstd/Thrift/Protocol/TProtocolDecorator.cs
+++ b/lib/netstd/Thrift/Protocol/TProtocolDecorator.cs
@@ -144,6 +144,11 @@
             await _wrappedProtocol.WriteBinaryAsync(bytes, cancellationToken);
         }
 
+        public override async Task WriteUuidAsync(Guid uuid, CancellationToken cancellationToken)
+        {
+            await _wrappedProtocol.WriteUuidAsync(uuid, cancellationToken);
+        }
+
         public override async ValueTask<TMessage> ReadMessageBeginAsync(CancellationToken cancellationToken)
         {
             return await _wrappedProtocol.ReadMessageBeginAsync(cancellationToken);
@@ -244,6 +249,11 @@
             return await _wrappedProtocol.ReadBinaryAsync(cancellationToken);
         }
 
+        public override async ValueTask<Guid> ReadUuidAsync(CancellationToken cancellationToken)
+        {
+            return await _wrappedProtocol.ReadUuidAsync(cancellationToken);
+        }
+
         // Returns the minimum amount of bytes needed to store the smallest possible instance of TType.
         public override int GetMinSerializedSize(TType type)
         {
diff --git a/lib/netstd/Thrift/Protocol/Utilities/TGuidExtensions.cs b/lib/netstd/Thrift/Protocol/Utilities/TGuidExtensions.cs
new file mode 100644
index 0000000..190ddbb
--- /dev/null
+++ b/lib/netstd/Thrift/Protocol/Utilities/TGuidExtensions.cs
@@ -0,0 +1,82 @@
+// 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.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+
+namespace Thrift.Protocol.Utilities
+{
+    public static class TGuidExtensions
+    {
+        public static Guid SwapByteOrder(this Guid self)
+        {
+            var bytes = self.ToByteArray();
+
+            // already network order on BigEndian machines
+            if (BitConverter.IsLittleEndian)
+            {
+                SwapBytes(ref bytes[0], ref bytes[3]);
+                SwapBytes(ref bytes[1], ref bytes[2]);
+                SwapBytes(ref bytes[4], ref bytes[5]);
+                SwapBytes(ref bytes[6], ref bytes[7]);
+            }
+
+            return new Guid(bytes);
+        }
+
+        private static void SwapBytes(ref byte one, ref byte two)
+        {
+            var tmp = one;
+            one = two;
+            two = tmp;
+        }
+
+        #region SelfTest
+#if DEBUG
+        static private readonly Guid TEST_GUID = new Guid("{00112233-4455-6677-8899-aabbccddeeff}");
+
+        static TGuidExtensions()
+        {
+            SelfTest();
+        }
+
+        private static void SelfTest()
+        {
+            // host to network
+            var guid = TEST_GUID;
+            guid = guid.SwapByteOrder();
+
+            // validate network order
+            var bytes = guid.ToByteArray();
+            for (var i = 0; i < 10; ++i)
+            {
+                var expected = i * 0x11;
+                Debug.Assert( bytes[i] == expected);
+            }
+
+            // network to host and final validation
+            guid = guid.SwapByteOrder();
+            Debug.Assert(guid.Equals(TEST_GUID));
+        }
+
+#endif
+        #endregion
+
+    }
+}
diff --git a/lib/netstd/Thrift/Protocol/Utilities/TJsonProtocolConstants.cs b/lib/netstd/Thrift/Protocol/Utilities/TJsonProtocolConstants.cs
index 6cc1302..f8c261a 100644
--- a/lib/netstd/Thrift/Protocol/Utilities/TJsonProtocolConstants.cs
+++ b/lib/netstd/Thrift/Protocol/Utilities/TJsonProtocolConstants.cs
@@ -1,4 +1,4 @@
-// Licensed to the Apache Software Foundation(ASF) under one
+// 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
@@ -56,6 +56,7 @@
             public static readonly byte[] NameMap = { (byte)'m', (byte)'a', (byte)'p' };
             public static readonly byte[] NameList = { (byte)'l', (byte)'s', (byte)'t' };
             public static readonly byte[] NameSet = { (byte)'s', (byte)'e', (byte)'t' };
+            public static readonly byte[] NameUuid = { (byte)'u', (byte)'i', (byte)'d' };
         }
     }
-}
\ No newline at end of file
+}
diff --git a/lib/netstd/Thrift/Protocol/Utilities/TJsonProtocolHelper.cs b/lib/netstd/Thrift/Protocol/Utilities/TJsonProtocolHelper.cs
index ff49ebe..67c7bc0 100644
--- a/lib/netstd/Thrift/Protocol/Utilities/TJsonProtocolHelper.cs
+++ b/lib/netstd/Thrift/Protocol/Utilities/TJsonProtocolHelper.cs
@@ -1,4 +1,4 @@
-// Licensed to the Apache Software Foundation(ASF) under one
+// 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
@@ -48,6 +48,8 @@
                     return TJSONProtocolConstants.TypeNames.NameSet;
                 case TType.List:
                     return TJSONProtocolConstants.TypeNames.NameList;
+                case TType.Uuid:
+                    return TJSONProtocolConstants.TypeNames.NameUuid;
                 default:
                     throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, "Unrecognized exType");
             }
@@ -102,6 +104,9 @@
                     case (byte) 't':
                         result = TType.Bool;
                         break;
+                    case (byte)'u':
+                        result = TType.Uuid;
+                        break;
                 }
             }
             if (result == TType.Stop)
@@ -173,4 +178,4 @@
             return (byte)((char)val + 'a');
         }
     }
-}
\ No newline at end of file
+}
diff --git a/lib/netstd/Thrift/Protocol/Utilities/TProtocolUtil.cs b/lib/netstd/Thrift/Protocol/Utilities/TProtocolUtil.cs
index 832e46e..3c8b37a 100644
--- a/lib/netstd/Thrift/Protocol/Utilities/TProtocolUtil.cs
+++ b/lib/netstd/Thrift/Protocol/Utilities/TProtocolUtil.cs
@@ -55,6 +55,9 @@
                         // Don't try to decode the string, just skip it.
                         await protocol.ReadBinaryAsync(cancellationToken);
                         break;
+                    case TType.Uuid:
+                        await protocol.ReadUuidAsync(cancellationToken);
+                        break;
                     case TType.Struct:
                         await protocol.ReadStructBeginAsync(cancellationToken);
                         while (true)