Ticket #51: Osc.cs

File Osc.cs, 28.1 KB (added by joreg, 13 months ago)
Line 
1using System.Threading;
2using System.Text;
3using System.Collections;
4using System.IO;
5using System;
6
7/// \mainpage
8/// \section Overview
9/// The .NET Visual C# library for the Make Controller Kit is designed
10/// to make it as simple as possible for developers to integrate the
11/// Make Controller Kit into their desktop applications, offering the
12/// transparency that makes open source software so rewarding to work with. 
13/// You can communicate with the Make Controller Kit from your applications
14/// over either an Ethernet or USB connection, or both.  This library is
15/// supplied both in source form and built, as MakeControllerOsc.dll
16/// This document is a reference for MakeControllerOsc.
17///
18/// \section Communication
19/// Messages to and from the board conform to the OSC (Open Sound Control) protocol. 
20/// OSC is an open, transport-independent standard supported by an increasing
21/// number of environments and devices.
22///
23/// \subsection OSCmessages OSC Messages
24/// OSC messages are represented by the class OscMessage, and consist of two elements:
25/// - An address string for the device on the board you’re dealing with.
26/// - A list of value(s) being sent to or from that device. The list of values is optional.
27///
28/// From the perspective of OSC addresses, the Make Controller Kit is organized into a hierarchy of two or three layers:
29/// - subsystems – classes of device, such as analog inputs, servo controllers, and digital outputs.
30/// - devices – the index of a specific device within a subsystem. 
31/// If there is only one device in a subsystem, the device is not included in the OSC address.
32/// - properties – different devices have different properties, such as the value of an analog input,
33/// the position of a servo motor, or the state of an LED.
34///
35/// OSC messages always begin with a slash, and use a slash to delimit each element in the address,
36/// so an example OSC address string would look like:
37/// \code /subsystem/device/property \endcode
38///
39/// The second part of an OscMessage is a list of values to be sent to the specified address.
40/// The OSC types that are used by the Make Controller Kit for these values are integers,
41/// floats, and strings.  The values in this list are simply separated by spaces, and the
42/// list can be arbitrarily long.  Most devices on the Make Controller Kit expect only one value. 
43/// For example, to set the position of servo 1, you might send a message which
44/// in string form might look like:
45/// \code /servo/1/position 512 \endcode
46///
47/// This addressing scheme allows interactions with the board's various subsystems
48/// and properties, and most importantly, accommodates the possibility of future or
49/// custom devices on the board that have not yet been implemented or imagined. 
50/// If somebody creates, for example, a GPS extension to the board, communicating
51/// with that device from this library is the same as for any other.  More details
52/// about OSC can be found at http://www.opensoundcontrol.org.
53///
54/// \section sendingdata Sending Data
55/// As previously mentioned, the Make Controller Kit can communicate over both
56/// Ethernet and USB.  Messages are sent as packets, both over USB and UDP, and
57/// corresponding structures are used – UsbPacket and UdpPacket.  Once you’ve created
58/// a packet, you can simply call its Send() method, with the OscMessage you’d like to send. 
59/// There are helper methods to create an OscMessage from a string, or you can pass in the OscMessage itself.
60///
61/// For example, you might set up your UsbSend() routine to look something like:
62/// \code public void usbSend(string text)
63/// {
64///     OscMessage oscM = Osc.StringToOscMessage(text);
65///     oscUsb is an Osc object, connected to a UsbPacket object
66///     oscUsb.Send(oscM);
67/// } \endcode
68/// If your data is already in the form of an OscMessage, you can call oscUsb.Send() directly.
69///
70/// \section readingdata Reading Data
71/// The Make Controller Kit must be polled in order to read data from it.  To do this,
72/// send an OscMessage with the address of the device you’d like to read, but omit
73/// the list of values.  When the board receives an OscMessage with no value,
74/// it interprets that as a read request, and sends back an OscMessage with the
75/// current value at the appropriate address.
76///
77/// The .NET Make Controller Kit library conveniently provides handlers that will
78/// call back a given function when an OscMessage with a given address string is received. 
79/// Your implementation could look something like:
80/// \code// Set the handler in the constructor for a particular address
81/// MyConstructor()
82/// {
83///     udpPacket = new UdpPacket();
84///     oscUdp = new Osc(udpPacket);
85///     // A thread is started when the Osc object is created to read
86///     // incoming messages.
87///     oscUdp.SetAddressHandler("/analogin/0/value", Ain0Message);
88/// }
89///
90/// // The method you specified as the handler will be called back when a
91/// // message with a matching address string comes back from the board.
92/// public void AIn0Message(OscMessage oscMessage)
93/// {
94///     // write the message to a console, for example
95///     mct.WriteLine("AIn0 > " + Osc.OscMessageToString(oscMessage));
96/// } \endcode
97/// You could alternatively set a handler for all incoming messages by calling
98/// the SetAllMessageHandler() method in your setup, instead of SetAddressHandler().
99///
100///
101
102
103namespace MakingThings
104{
105  /// <summary>
106  /// The OscMessage class is a data structure that represents
107  /// an OSC address and an arbitrary number of values to be sent to that address.
108  /// </summary>
109  public class OscMessage
110  {
111    public OscMessage()
112    {
113      Values = new ArrayList();
114    }
115     
116   /// <summary>
117   /// The OSC address of the message as a string.
118   /// </summary>
119   public string Address;
120   /// <summary>
121   /// The list of values to be delivered to the Address.
122   /// </summary>
123   public ArrayList Values;
124  }
125
126  public delegate void OscMessageHandler( OscMessage oscM );
127
128  /// <summary>
129  /// The Osc class provides the methods required to send, receive, and manipulate OSC messages.
130  /// Several of the helper methods are static since a running Osc instance is not required for
131  /// their use.
132  ///
133  /// When instanciated, the Osc class opens the PacketIO instance that's handed to it and
134  /// begins to run a reader thread.  The instance is then ready to service Send OscMessage requests
135  /// and to start supplying OscMessages as received back.
136  ///
137  /// The Osc class can be called to Send either individual messages or collections of messages
138  /// in an Osc Bundle.  Receiving is done by delegate.  There are two ways: either submit a method
139  /// to receive all incoming messages or submit a method to handle only one particular address.
140  ///
141  /// Messages can be encoded and decoded from Strings via the static methods on this class, or
142  /// can be hand assembled / disassembled since they're just a string (the address) and a list
143  /// of other parameters in Object form.
144  ///
145  /// </summary>
146  public class Osc
147  {
148    /// <summary>
149    /// Osc Constructor.  Starts the Reader thread and initializes some internal state.
150    /// </summary>
151    /// <param name="oscPacketIO">The PacketIO instance used for packet IO.</param>
152    public Osc(PacketIO oscPacketIO)
153    {
154      // Save the PacketExchage pointer
155      OscPacketIO = oscPacketIO;
156
157      // Create the hashtable for the address lookup mechanism
158      AddressTable = new Hashtable();
159
160      ReadThread = new Thread(Read);
161      ReaderRunning = true;
162      ReadThread.IsBackground = true;
163      ReadThread.Start();
164    }
165
166    /// <summary>
167    /// Make sure the PacketExchange is closed.
168    /// </summary>
169    ~Osc()
170    {
171      if (OscPacketIO.IsOpen())
172        OscPacketIO.Close();
173    }
174
175    /// <summary>
176    /// Read Thread.  Loops waiting for packets.  When a packet is received, it is
177    /// dispatched to any waiting All Message Handler.  Also, the address is looked up and
178    /// any matching handler is called.
179    /// </summary>
180    private void Read()
181    {
182      while (ReaderRunning)
183      {
184        byte[] buffer = new byte[1000];
185        int length = OscPacketIO.ReceivePacket(buffer);
186        if (length > 0)
187        {
188          ArrayList messages = Osc.PacketToOscMessages(buffer, length);
189          foreach (OscMessage om in messages)
190          {
191            if (AllMessageHandler != null)
192              AllMessageHandler(om);
193            OscMessageHandler h = (OscMessageHandler)Hashtable.Synchronized(AddressTable)[om.Address];
194            if (h != null)
195              h(om);
196          }
197        }
198        else
199          Thread.Sleep(500);
200      }
201    }
202
203    /// <summary>
204    /// Send an individual OSC message.  Internally takes the OscMessage object and
205    /// serializes it into a byte[] suitable for sending to the PacketIO.
206    /// </summary>
207    /// <param name="oscMessage">The OSC Message to send.</param>   
208    public void Send( OscMessage oscMessage )
209    {
210      byte[] packet = new byte[1000];
211      int length = Osc.OscMessageToPacket( oscMessage, packet, 1000 );
212      OscPacketIO.SendPacket( packet, length);
213    }
214
215    /// <summary>
216    /// Sends a list of OSC Messages.  Internally takes the OscMessage objects and
217    /// serializes them into a byte[] suitable for sending to the PacketExchange.
218    /// </summary>
219    /// <param name="oms">The OSC Messages (as an ArrayList) to send.</param>   
220    public void Send(ArrayList oms)
221    {
222      byte[] packet = new byte[1000];
223      int length = Osc.OscMessagesToPacket(oms, packet, 1000);
224      OscPacketIO.SendPacket(packet, length);
225    }
226
227    /// <summary>
228    /// Set the method to call back on when any message is received.
229    /// The method needs to have the OscMessageHandler signature - i.e. void amh( OscMessage oscM )
230    /// </summary>
231    /// <param name="amh">The method to call back on.</param>   
232    public void SetAllMessageHandler(OscMessageHandler amh)
233    {
234      AllMessageHandler = amh;
235    }
236
237    /// <summary>
238    /// Set the method to call back on when a message with the specified
239    /// address is received.  The method needs to have the OscMessageHandler signature - i.e.
240    /// void amh( OscMessage oscM )
241    /// </summary>
242    /// <param name="key">Address string to be matched</param>   
243    /// <param name="ah">The method to call back on.</param>   
244    public void SetAddressHandler(string key, OscMessageHandler ah)
245    {
246      Hashtable.Synchronized(AddressTable).Add(key, ah);
247    }
248
249    private PacketIO OscPacketIO;
250    Thread ReadThread;
251    private bool ReaderRunning;
252    private OscMessageHandler AllMessageHandler;
253    Hashtable AddressTable;
254
255    /// <summary>
256    /// General static helper that returns a string suitable for printing representing the supplied
257    /// OscMessage.
258    /// </summary>
259    /// <param name="message">The OscMessage to be stringified.</param>
260    /// <returns>The OscMessage as a string.</returns>
261    public static string OscMessageToString(OscMessage message)
262    {
263      StringBuilder s = new StringBuilder();
264      s.Append(message.Address);
265      foreach( object o in message.Values )
266      {
267        s.Append(" ");
268        if (o is byte[])
269        {
270          byte[] byteArray = (byte[])o;
271          s.Append("<");
272          bool first = true;
273          foreach (byte b in byteArray)
274          {
275            if (!first)
276              s.Append(" ");
277            else
278              first = false;
279            s.Append( String.Format( "{0:X2}",b));
280          }
281          s.Append(">");
282        }
283        else
284          s.Append(o.ToString());
285      }
286      return s.ToString();
287    }
288
289    /// <summary>
290    /// Creates an OscMessage from a string - extracts the address and determines each of the values.
291    /// </summary>
292    /// <param name="message">The string to be turned into an OscMessage</param>
293    /// <returns>The OscMessage.</returns>
294    public static OscMessage StringToOscMessage(string message)
295    {
296      OscMessage oM = new OscMessage();
297      // Console.WriteLine("Splitting " + message);
298      string[] ss = message.Split(new char[] { ' ' });
299      IEnumerator sE = ss.GetEnumerator();
300      if (sE.MoveNext())
301        oM.Address = (string)sE.Current;
302      while ( sE.MoveNext() )
303      {
304        string s = (string)sE.Current;
305        // Console.WriteLine("  <" + s + ">");
306        // Check for a string - as indicated by an open quote
307        if (s.StartsWith("\""))
308        {
309          // Get the whole string
310          StringBuilder quoted = new StringBuilder();
311          bool looped = false;
312          if (s.Length > 1)
313            quoted.Append(s.Substring(1));
314          else
315            looped = true;
316          while (sE.MoveNext())
317          {
318            string a = (string)sE.Current;
319            // Console.WriteLine("    q:<" + a + ">");
320            if (looped)
321              quoted.Append(" ");
322            if (a.EndsWith("\""))
323            {
324              quoted.Append(a.Substring(0, a.Length - 1));
325              break;
326            }
327            else
328            {
329              if (a.Length == 0)
330                quoted.Append(" ");
331              else
332                quoted.Append(a);
333            }
334            looped = true;
335          }
336          oM.Values.Add(quoted.ToString());
337        }
338        else
339        {
340          if ( s.StartsWith( "<" ) )
341          {
342            ArrayList byteList = new ArrayList();
343            if (s.Length > 1)
344              ExtractBytesFromString(byteList, s.Substring(1));
345            while (sE.MoveNext())
346            {
347              string a = (string)sE.Current;
348
349              ExtractBytesFromString(byteList, a);
350
351              if ( a.EndsWith( ">" ) )
352                break;
353            }
354
355            byte[] byteArray = new byte[ byteList.Count ];
356            int index = 0;
357            foreach ( byte b in byteList )
358              byteArray[ index++ ] = (byte)b;
359
360            oM.Values.Add( byteArray );
361          }
362          else
363          {
364            if (s.Length > 0)
365            {
366              try
367              {
368                int i = int.Parse(s);
369                // Console.WriteLine("  i:" + i);
370                oM.Values.Add(i);
371              }
372              catch
373              {
374                try
375                {
376                  float f = float.Parse(s);
377                  // Console.WriteLine("  f:" + f);
378                  oM.Values.Add(f);
379                }
380                catch
381                {
382                  // Console.WriteLine("  s:" + s);
383                  oM.Values.Add(s);
384                }
385              }
386            }
387          }
388        }
389      }
390      return oM;
391    }
392
393    /// <summary>
394    /// Takes a packet (byte[]) and turns it into a list of OscMessages.
395    /// </summary>
396    /// <param name="packet">The packet to be parsed.</param>
397    /// <param name="length">The length of the packet.</param>
398    /// <returns>An ArrayList of OscMessages.</returns>
399    public static ArrayList PacketToOscMessages(byte[] packet, int length)
400    {
401      ArrayList messages = new ArrayList();
402      ExtractMessages(messages, packet, 0, length);
403      return messages;
404    }
405
406    /// <summary>
407    /// Puts an array of OscMessages into a packet (byte[]).
408    /// </summary>
409    /// <param name="messages">An ArrayList of OscMessages.</param>
410    /// <param name="packet">An array of bytes to be populated with the OscMessages.</param>
411    /// <param name="length">The size of the array of bytes.</param>
412    /// <returns>The length of the packet</returns>
413    public static int OscMessagesToPacket(ArrayList messages, byte[] packet, int length)
414    {
415      int index = 0;
416      if (messages.Count == 1)
417        index = OscMessageToPacket((OscMessage)messages[0], packet, 0, length);
418      else
419      {
420        // Write the first bundle bit
421        index = InsertString("#bundle", packet, index, length);
422        // Write a null timestamp (another 8bytes)
423        int c = 8;
424        while (( c-- )>0)
425          packet[index++]++;
426        // Now, put each message preceded by it's length
427        foreach (OscMessage oscM in messages)
428        {
429          int lengthIndex = index;
430          index += 4;
431          int packetStart = index;
432          index = OscMessageToPacket(oscM, packet, index, length);
433          int packetSize = index - packetStart;
434          packet[lengthIndex++] = (byte)((packetSize >> 24) & 0xFF);
435          packet[lengthIndex++] = (byte)((packetSize >> 16) & 0xFF);
436          packet[lengthIndex++] = (byte)((packetSize >> 8) & 0xFF);
437          packet[lengthIndex++] = (byte)((packetSize) & 0xFF);
438        }
439      }
440      return index;
441    }
442
443    /// <summary>
444    /// Creates a packet (an array of bytes) from a single OscMessage.
445    /// </summary>
446    /// <remarks>A convenience method, not requiring a start index.</remarks>
447    /// <param name="oscM">The OscMessage to be returned as a packet.</param>
448    /// <param name="packet">The packet to be populated with the OscMessage.</param>
449    /// <param name="length">The usable size of the array of bytes.</param>
450    /// <returns>The length of the packet</returns>
451    public static int OscMessageToPacket(OscMessage oscM, byte[] packet, int length)
452    {
453      return OscMessageToPacket(oscM, packet, 0, length);
454    }
455
456    /// <summary>
457    /// Creates an array of bytes from a single OscMessage.  Used internally.
458    /// </summary>
459    /// <remarks>Can specify where in the array of bytes the OscMessage should be put.</remarks>
460    /// <param name="oscM">The OscMessage to be turned into an array of bytes.</param>
461    /// <param name="packet">The array of bytes to be populated with the OscMessage.</param>
462    /// <param name="start">The start index in the packet where the OscMessage should be put.</param>
463    /// <param name="length">The length of the array of bytes.</param>
464    /// <returns>The index into the packet after the last OscMessage.</returns>
465    private static int OscMessageToPacket(OscMessage oscM, byte[] packet, int start, int length)
466    {
467      int index = start;
468      index = InsertString(oscM.Address, packet, index, length);
469      //if (oscM.Values.Count > 0)
470      {
471        StringBuilder tag = new StringBuilder();
472        tag.Append(",");
473        int tagIndex = index;
474        index += PadSize(1+oscM.Values.Count);
475
476        foreach (object o in oscM.Values)
477        {
478          if (o is int)
479          {
480            int i = (int)o;
481            tag.Append("i");
482            packet[index++] = (byte)((i >> 24) & 0xFF);
483            packet[index++] = (byte)((i >> 16) & 0xFF);
484            packet[index++] = (byte)((i >> 8) & 0xFF);
485            packet[index++] = (byte)((i) & 0xFF);
486          }
487          else
488          {
489            if (o is float)
490            {
491              float f = (float)o;
492              tag.Append("f");
493              byte[] buffer = new byte[4];
494              MemoryStream ms = new MemoryStream(buffer);
495              BinaryWriter bw = new BinaryWriter(ms);
496              bw.Write(f);
497              packet[index++] = buffer[3];
498              packet[index++] = buffer[2];
499              packet[index++] = buffer[1];
500              packet[index++] = buffer[0];
501            }
502            else
503            {
504              if (o is string)
505              {
506                tag.Append("s");
507                index = InsertString(o.ToString(), packet, index, length);
508              }
509              else
510              {
511                if ( o is byte[] )
512                {
513                  tag.Append("b");
514                  byte[] byteArray = (byte[])o;
515                  int len = byteArray.Length;
516                  packet[index++] = (byte)((len >> 24) & 0xFF);
517                  packet[index++] = (byte)((len >> 16) & 0xFF);
518                  packet[index++] = (byte)((len >> 8) & 0xFF);
519                  packet[index++] = (byte)((len) & 0xFF);
520                  Array.Copy(byteArray, 0, packet, index, len);
521                  index += len;
522                  int pad = len % 4;
523                  if (pad != 0)
524                  {
525                    pad = 4 - pad;
526                    while (pad-- > 0)
527                      packet[index++] = 0;
528                  }
529                }
530                else
531                  tag.Append("?");
532              }
533            }
534          }
535        }
536        InsertString(tag.ToString(), packet, tagIndex, length);
537      }
538      return index;
539    }
540
541    /// <summary>
542    /// Receive a raw packet of bytes and extract OscMessages from it.  Used internally.
543    /// </summary>
544    /// <remarks>The packet may contain a OSC message or a bundle of messages.</remarks>
545    /// <param name="messages">An ArrayList to be populated with the OscMessages.</param>
546    /// <param name="packet">The packet of bytes to be parsed.</param>
547    /// <param name="start">The index of where to start looking in the packet.</param>
548    /// <param name="length">The length of the packet.</param>
549    /// <returns>The index after the last OscMessage read.</returns>
550    private static int ExtractMessages(ArrayList messages, byte[] packet, int start, int length)
551    {
552      int index = start;
553      switch ( (char)packet[ start ] )
554      {
555        case '/':
556          index = ExtractMessage( messages, packet, index, length );
557          break;
558        case '#':
559          string bundleString = ExtractString(packet, start, length);
560          if ( bundleString == "#bundle" )
561          {
562            // skip the "bundle" and the timestamp
563            index+=16;
564            while ( index < length )
565            {
566              int messageSize = ( packet[index++] << 24 ) + ( packet[index++] << 16 ) + ( packet[index++] << 8 ) + packet[index++];
567              int newIndex = ExtractMessages( messages, packet, index, length );
568              index += messageSize;
569            }           
570          }
571          break;
572      }
573      return index;
574    }
575
576    /// <summary>
577    /// Extracts a messages from a packet.
578    /// </summary>
579    /// <param name="messages">An ArrayList to be populated with the OscMessage.</param>
580    /// <param name="packet">The packet of bytes to be parsed.</param>
581    /// <param name="start">The index of where to start looking in the packet.</param>
582    /// <param name="length">The length of the packet.</param>
583    /// <returns>The index after the OscMessage is read.</returns>
584    private static int ExtractMessage(ArrayList messages, byte[] packet, int start, int length)
585    {
586      OscMessage oscM = new OscMessage();
587      oscM.Address = ExtractString(packet, start, length);
588      int index = start + PadSize(oscM.Address.Length);
589      string typeTag = ExtractString(packet, index, length);
590      index += PadSize(typeTag.Length + 1);
591      //oscM.Values.Add(typeTag);
592      foreach (char c in typeTag)
593      {
594        switch (c)
595        {
596          case ',':
597            break;
598          case 's':
599            {
600              string s = ExtractString(packet, index, length);
601              index += PadSize(s.Length+1);
602              oscM.Values.Add(s);
603              break;
604            }
605          case 'b':
606            {
607              byte[] b = ExtractBlob(packet, index, length);
608              index += PadSize(b.Length);
609              oscM.Values.Add(b);
610              break;
611            }
612          case 'i':
613            {
614              int i = ( packet[index++] << 24 ) + ( packet[index++] << 16 ) + ( packet[index++] << 8 ) + packet[index++];
615              oscM.Values.Add(i);
616              break;
617            }
618          case 'f':
619            {
620              byte[] buffer = new byte[4];
621              buffer[3] = packet[index++];
622              buffer[2] = packet[index++];
623              buffer[1] = packet[index++];
624              buffer[0] = packet[index++];
625              MemoryStream ms = new MemoryStream(buffer);
626              BinaryReader br = new BinaryReader(ms);
627              float f = br.ReadSingle();
628              oscM.Values.Add(f);
629              break;
630            }
631        }
632      }
633      messages.Add( oscM );
634      return index;
635    }
636
637    /// <summary>
638    /// Extracts a string from a packet.  Used internally.
639    /// </summary>
640    /// <param name="packet">The packet of bytes to be parsed.</param>
641    /// <param name="start">The index of where to start looking in the packet.</param>
642    /// <param name="length">The length of the packet.</param>
643    /// <returns>The string</returns>
644    private static string ExtractString(byte[] packet, int start, int length)
645    {
646      StringBuilder sb = new StringBuilder();
647      int index = start;
648      while (packet[index] != 0 && index < length)
649        sb.Append((char)packet[index++]);
650      return sb.ToString();
651    }
652
653    /// <summary>
654    /// Extracts a blob from a packet.  Used internally.
655    /// </summary>
656    /// <param name="packet">The packet of bytes to be parsed.</param>
657    /// <param name="start">The index of where to start looking in the packet.</param>
658    /// <param name="length">The length of the packet.</param>
659    /// <returns>The string</returns>
660    private static byte[] ExtractBlob(byte[] packet, int start, int length)
661    {
662      int index = start;
663      int blobSize = ( packet[index++] << 24 ) + ( packet[index++] << 16 ) + ( packet[index++] << 8 ) + packet[index++];
664      byte[] b = new byte[ blobSize ];
665      int blobIndex = 0;
666      while (blobSize-- > 0 && index < length)
667      {
668        b[ blobIndex ] = packet[ index ];
669        blobIndex++;
670        index++;
671      }
672      return b;
673    }
674
675    /// <summary>
676    /// Inserts a string, correctly padded into a packet.  Used internally.
677    /// </summary>
678    /// <param name="string">The string to be inserted</param>
679    /// <param name="packet">The packet of bytes to be parsed.</param>
680    /// <param name="start">The index of where to start looking in the packet.</param>
681    /// <param name="length">The length of the packet.</param>
682    /// <returns>An index to the next byte in the packet after the padded string.</returns>
683    private static int InsertString(string s, byte[] packet, int start, int length)
684    {
685      int index = start;
686      foreach (char c in s)
687      {
688        packet[index++] = (byte)c;
689        if (index == length)
690          return index;
691      }
692      int pad = 4 - (s.Length) % 4;
693      while (pad-- > 0)
694          packet[index++] = 0;
695
696      return index;
697    }
698
699    /// <summary>
700    /// Takes a length and returns what it would be if padded to the nearest 4 bytes.
701    /// </summary>
702    /// <param name="rawSize">Original size</param>
703    /// <returns>padded size</returns>
704    private static int PadSize(int rawSize)
705    {
706      return rawSize + 4 - rawSize % 4;
707    }
708
709    private static void ExtractBytesFromString(ArrayList byteList, string s)
710    {
711      int nibbleCount = 0;
712      int v = 0;
713      // look at each character
714      foreach (char c in s)
715      {
716        // if the character is 0-9, a-f or A-F add it to v left shifting whatever was in there by a nibble
717        if (c >= '0' && c <= '9')
718        {
719          v = ( v << 4 ) + (int)(c - '0');
720          nibbleCount++;
721        }
722        else
723        {
724          if (c >= 'a' && c <= 'f')
725          {
726            v = (v << 4) + (int)(c - 'a') + 10;
727            nibbleCount++;
728          }
729          else
730          {
731            if (c >= 'A' && c <= 'F')
732            {
733              v = (v << 4) + (int)(c - 'A') + 10;
734              nibbleCount++;
735            }
736            else
737            {
738              // if there is a digit in v, and we just got a non-digit then we're done
739              if ( nibbleCount > 0 )
740                nibbleCount++;
741            }
742          }
743        }
744        if (nibbleCount == 2)
745        {
746          byteList.Add((byte)v);
747          v = 0;
748          nibbleCount = 0;
749        }
750      }
751      // if at the end of it all, we were left with a hanging nibble, save it
752      if (nibbleCount > 0)
753        byteList.Add((byte)v);
754    }
755  }
756}