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