691d9402a2e045fafe0a55f05c8b8429b125a453
[mono.git] / mcs / class / RabbitMQ.Client / src / client / impl / WireFormatting.cs
1 // This source code is dual-licensed under the Apache License, version
2 // 2.0, and the Mozilla Public License, version 1.1.
3 //
4 // The APL v2.0:
5 //
6 //---------------------------------------------------------------------------
7 //   Copyright (C) 2007, 2008 LShift Ltd., Cohesive Financial
8 //   Technologies LLC., and Rabbit Technologies Ltd.
9 //
10 //   Licensed under the Apache License, Version 2.0 (the "License");
11 //   you may not use this file except in compliance with the License.
12 //   You may obtain a copy of the License at
13 //
14 //       http://www.apache.org/licenses/LICENSE-2.0
15 //
16 //   Unless required by applicable law or agreed to in writing, software
17 //   distributed under the License is distributed on an "AS IS" BASIS,
18 //   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 //   See the License for the specific language governing permissions and
20 //   limitations under the License.
21 //---------------------------------------------------------------------------
22 //
23 // The MPL v1.1:
24 //
25 //---------------------------------------------------------------------------
26 //   The contents of this file are subject to the Mozilla Public License
27 //   Version 1.1 (the "License"); you may not use this file except in
28 //   compliance with the License. You may obtain a copy of the License at
29 //   http://www.rabbitmq.com/mpl.html
30 //
31 //   Software distributed under the License is distributed on an "AS IS"
32 //   basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
33 //   License for the specific language governing rights and limitations
34 //   under the License.
35 //
36 //   The Original Code is The RabbitMQ .NET Client.
37 //
38 //   The Initial Developers of the Original Code are LShift Ltd.,
39 //   Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd.
40 //
41 //   Portions created by LShift Ltd., Cohesive Financial Technologies
42 //   LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007, 2008
43 //   LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit
44 //   Technologies Ltd.;
45 //
46 //   All Rights Reserved.
47 //
48 //   Contributor(s): ______________________________________.
49 //
50 //---------------------------------------------------------------------------
51 using System;
52 using System.IO;
53 using System.Text;
54 using System.Collections;
55 using RabbitMQ.Client;
56 using RabbitMQ.Client.Exceptions;
57 using RabbitMQ.Util;
58
59 namespace RabbitMQ.Client.Impl
60 {
61     public class WireFormatting
62     {
63         public static byte ReadOctet(NetworkBinaryReader reader)
64         {
65             return reader.ReadByte();
66         }
67
68         public static string ReadShortstr(NetworkBinaryReader reader)
69         {
70             int byteCount = reader.ReadByte();
71             return Encoding.UTF8.GetString(reader.ReadBytes(byteCount));
72         }
73
74         public static byte[] ReadLongstr(NetworkBinaryReader reader)
75         {
76             uint byteCount = reader.ReadUInt32();
77             if (byteCount > int.MaxValue)
78             {
79                 throw new SyntaxError("Long string too long; " +
80                                       "byte length=" + byteCount + ", max=" + int.MaxValue);
81             }
82             return reader.ReadBytes((int)byteCount);
83         }
84
85         public static ushort ReadShort(NetworkBinaryReader reader)
86         {
87             return reader.ReadUInt16();
88         }
89
90         public static uint ReadLong(NetworkBinaryReader reader)
91         {
92             return reader.ReadUInt32();
93         }
94
95         public static ulong ReadLonglong(NetworkBinaryReader reader)
96         {
97             return reader.ReadUInt64();
98         }
99
100         public static decimal AmqpToDecimal(byte scale, uint unsignedMantissa)
101         {
102             if (scale > 28)
103             {
104                 throw new SyntaxError("Unrepresentable AMQP decimal table field: " +
105                                       "scale=" + scale);
106             }
107             return new decimal((int)(unsignedMantissa & 0x7FFFFFFF),
108                                0,
109                                0,
110                                ((unsignedMantissa & 0x80000000) == 0) ? false : true,
111                                scale);
112         }
113
114         public static decimal ReadDecimal(NetworkBinaryReader reader)
115         {
116             byte scale = ReadOctet(reader);
117             uint unsignedMantissa = ReadLong(reader);
118             return AmqpToDecimal(scale, unsignedMantissa);
119         }
120
121         ///<summary>Reads an AMQP "table" definition from the reader.</summary>
122         ///<remarks>
123         /// Supports the AMQP 0-8/0-9 standard entry types S, I, D, T
124         /// and F, as well as the QPid-0-8 specific b, d, f, l, s, t,
125         /// x and V types.
126         ///</remarks>
127         public static IDictionary ReadTable(NetworkBinaryReader reader)
128         {
129             Hashtable table = new Hashtable();
130             long tableLength = reader.ReadUInt32();
131
132             Stream backingStream = reader.BaseStream;
133             long startPosition = backingStream.Position;
134             while ((backingStream.Position - startPosition) < tableLength)
135             {
136                 string key = ReadShortstr(reader);
137                 object value = null;
138
139                 byte discriminator = reader.ReadByte();
140                 switch ((char)discriminator)
141                 {
142                   case 'S':
143                       value = ReadLongstr(reader);
144                       break;
145                   case 'I':
146                       value = reader.ReadInt32();
147                       break;
148                   case 'D':
149                       value = ReadDecimal(reader);
150                       break;
151                   case 'T':
152                       value = ReadTimestamp(reader);
153                       break;
154                   case 'F':
155                       value = ReadTable(reader);
156                       break;
157
158                   case 'b':
159                       value = ReadOctet(reader);
160                       break;
161                   case 'd':
162                       value = reader.ReadDouble();
163                       break;
164                   case 'f':
165                       value = reader.ReadSingle();
166                       break;
167                   case 'l':
168                       value = reader.ReadInt64();
169                       break;
170                   case 's':
171                       value = reader.ReadInt16();
172                       break;
173                   case 't':
174                       value = (ReadOctet(reader) != 0);
175                       break;
176                   case 'x':
177                       value = new BinaryTableValue(ReadLongstr(reader));
178                       break;
179                   case 'V':
180                       value = null;
181                       break;
182
183                   default:
184                       throw new SyntaxError("Unrecognised type in table: " +
185                                             (char) discriminator);
186                 }
187
188                 if (!table.ContainsKey(key))
189                 {
190                     table[key] = value;
191                 }
192             }
193
194             return table;
195         }
196
197         public static AmqpTimestamp ReadTimestamp(NetworkBinaryReader reader)
198         {
199             ulong stamp = ReadLonglong(reader);
200             // 0-9 is afaict silent on the signedness of the timestamp.
201             // See also MethodArgumentWriter.WriteTimestamp and AmqpTimestamp itself
202             return new AmqpTimestamp((long)stamp);
203         }
204
205         public static void WriteOctet(NetworkBinaryWriter writer, byte val)
206         {
207             writer.Write((byte)val);
208         }
209
210         public static void WriteShortstr(NetworkBinaryWriter writer, string val)
211         {
212             byte[] bytes = Encoding.UTF8.GetBytes(val);
213             int len = bytes.Length;
214             if (len > 255)
215             {
216                 throw new WireFormattingException("Short string too long; " +
217                                                   "UTF-8 encoded length=" + len + ", max=255");
218             }
219             writer.Write((byte)len);
220             writer.Write(bytes);
221         }
222
223         public static void WriteLongstr(NetworkBinaryWriter writer, byte[] val)
224         {
225             WriteLong(writer, (uint)val.Length);
226             writer.Write(val);
227         }
228
229         public static void WriteShort(NetworkBinaryWriter writer, ushort val)
230         {
231             writer.Write((ushort)val);
232         }
233
234         public static void WriteLong(NetworkBinaryWriter writer, uint val)
235         {
236             writer.Write((uint)val);
237         }
238
239         public static void WriteLonglong(NetworkBinaryWriter writer, ulong val)
240         {
241             writer.Write((ulong)val);
242         }
243
244         public static void DecimalToAmqp(decimal value, out byte scale, out int mantissa)
245         {
246             // According to the documentation :-
247             //  - word 0: low-order "mantissa"
248             //  - word 1, word 2: medium- and high-order "mantissa"
249             //  - word 3: mostly reserved; "exponent" and sign bit
250             // In one way, this is broader than AMQP: the mantissa is larger.
251             // In another way, smaller: the exponent ranges 0-28 inclusive.
252             // We need to be careful about the range of word 0, too: we can
253             // only take 31 bits worth of it, since the sign bit needs to
254             // fit in there too.
255             int[] bitRepresentation = decimal.GetBits(value);
256             if (bitRepresentation[1] != 0 ||    // mantissa extends into middle word
257                 bitRepresentation[2] != 0 ||    // mantissa extends into top word
258                 bitRepresentation[0] < 0)       // mantissa extends beyond 31 bits
259             {
260                 throw new WireFormattingException("Decimal overflow in AMQP encoding", value);
261             }
262             scale = (byte)((((uint)bitRepresentation[3]) >> 16) & 0xFF);
263             mantissa = (int)((((uint)bitRepresentation[3]) & 0x80000000) |
264                               (((uint)bitRepresentation[0]) & 0x7FFFFFFF));
265         }
266
267         public static void WriteDecimal(NetworkBinaryWriter writer, decimal value)
268         {
269             byte scale;
270             int mantissa;
271             DecimalToAmqp(value, out scale, out mantissa);
272             WriteOctet(writer, scale);
273             WriteLong(writer, (uint)mantissa);
274         }
275
276         ///<summary>Writes an AMQP "table" to the writer.</summary>
277         ///<remarks>
278         ///<para>
279         /// In this method, we assume that the stream that backs our
280         /// NetworkBinaryWriter is a positionable stream - which it is
281         /// currently (see Frame.m_accumulator, Frame.GetWriter and
282         /// Command.Transmit).
283         ///</para>
284         ///<para>
285         /// Supports the AMQP 0-8/0-9 standard entry types S, I, D, T
286         /// and F, as well as the QPid-0-8 specific b, d, f, l, s, t
287         /// x and V types.
288         ///</para>
289         ///</remarks>
290         public static void WriteTable(NetworkBinaryWriter writer, IDictionary val)
291         {
292             if (val == null)
293             {
294                 writer.Write((uint)0);
295             }
296             else
297             {
298                 Stream backingStream = writer.BaseStream;
299                 long patchPosition = backingStream.Position;
300                 writer.Write((uint)0); // length of table - will be backpatched
301
302                 foreach (DictionaryEntry entry in val)
303                 {
304                     WriteShortstr(writer, (string)entry.Key);
305                     object value = entry.Value;
306
307                     if (value == null)
308                     {
309                         WriteOctet(writer, (byte)'V');
310                     }
311                     else if (value is string)
312                     {
313                         WriteOctet(writer, (byte)'S');
314                         WriteLongstr(writer, Encoding.UTF8.GetBytes((string)value));
315                     }
316                     else if (value is byte[])
317                     {
318                         WriteOctet(writer, (byte)'S');
319                         WriteLongstr(writer, (byte[])value);
320                     }
321                     else if (value is int)
322                     {
323                         WriteOctet(writer, (byte)'I');
324                         writer.Write((int)value);
325                     }
326                     else if (value is decimal)
327                     {
328                         WriteOctet(writer, (byte)'D');
329                         WriteDecimal(writer, (decimal)value);
330                     }
331                     else if (value is AmqpTimestamp)
332                     {
333                         WriteOctet(writer, (byte)'T');
334                         WriteTimestamp(writer, (AmqpTimestamp)value);
335                     }
336                     else if (value is IDictionary)
337                     {
338                         WriteOctet(writer, (byte)'F');
339                         WriteTable(writer, (IDictionary)value);
340                     }
341                     else if (value is byte)
342                     {
343                         WriteOctet(writer, (byte)'b');
344                         WriteOctet(writer, (byte)value);
345                     }
346                     else if (value is double)
347                     {
348                         WriteOctet(writer, (byte)'d');
349                         writer.Write((double)value);
350                     }
351                     else if (value is float)
352                     {
353                         WriteOctet(writer, (byte)'f');
354                         writer.Write((float)value);
355                     }
356                     else if (value is long)
357                     {
358                         WriteOctet(writer, (byte)'l');
359                         writer.Write((long)value);
360                     }
361                     else if (value is short)
362                     {
363                         WriteOctet(writer, (byte)'s');
364                         writer.Write((short)value);
365                     }
366                     else if (value is bool)
367                     {
368                         WriteOctet(writer, (byte)'t');
369                         WriteOctet(writer, (byte)(((bool)value) ? 1 : 0));
370                     }
371                     else if (value is BinaryTableValue)
372                     {
373                         WriteOctet(writer, (byte)'x');
374                         WriteLongstr(writer, ((BinaryTableValue)value).Bytes);
375                     }
376                     else
377                     {
378                         throw new WireFormattingException("Value cannot appear as table value",
379                                                           value);
380                     }
381                 }
382
383                 // Now, backpatch the table length.
384                 long savedPosition = backingStream.Position;
385                 long tableLength = savedPosition - patchPosition - 4; // offset for length word
386                 backingStream.Seek(patchPosition, SeekOrigin.Begin);
387                 writer.Write((uint)tableLength);
388                 backingStream.Seek(savedPosition, SeekOrigin.Begin);
389             }
390         }
391
392         public static void WriteTimestamp(NetworkBinaryWriter writer, AmqpTimestamp val)
393         {
394             // 0-9 is afaict silent on the signedness of the timestamp.
395             // See also MethodArgumentReader.ReadTimestamp and AmqpTimestamp itself
396             WriteLonglong(writer, (ulong)val.UnixTime);
397         }
398     }
399 }