Mono.Zeroconf – Rev 1

Subversion Repositories:
Rev:
// Copyright 2006 Alp Toker <alp@atoker.com>
// This software is made available under the MIT License
// See COPYING for details

using System;
using System.Text;
using System.Collections.Generic;
using System.IO;
using System.Reflection;

namespace NDesk.DBus
{
        class MessageReader
        {
                protected EndianFlag endianness;
                protected byte[] data;
                //TODO: this should be uint or long to handle long messages
                protected int pos = 0;
                protected Message message;

                public MessageReader (EndianFlag endianness, byte[] data)
                {
                        if (data == null)
                                throw new ArgumentNullException ("data");

                        this.endianness = endianness;
                        this.data = data;
                }

                public MessageReader (Message message) : this (message.Header.Endianness, message.Body)
                {
                        if (message == null)
                                throw new ArgumentNullException ("message");

                        this.message = message;
                }

                public object ReadValue (Type type)
                {
                        if (type == typeof (void))
                                return null;

                        if (type.IsArray) {
                                return ReadArray (type.GetElementType ());
                        } else if (type == typeof (ObjectPath)) {
                                return ReadObjectPath ();
                        } else if (type == typeof (Signature)) {
                                return ReadSignature ();
                        } else if (type == typeof (object)) {
                                return ReadVariant ();
                        } else if (type == typeof (string)) {
                                return ReadString ();
                        } else if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (IDictionary<,>)) {
                                Type[] genArgs = type.GetGenericArguments ();
                                //Type dictType = typeof (Dictionary<,>).MakeGenericType (genArgs);
                                //workaround for Mono bug #81035 (memory leak)
                                Type dictType = Mapper.GetGenericType (typeof (Dictionary<,>), genArgs);
                                System.Collections.IDictionary idict = (System.Collections.IDictionary)Activator.CreateInstance(dictType, new object[0]);
                                GetValueToDict (genArgs[0], genArgs[1], idict);
                                return idict;
                        } else if (Mapper.IsPublic (type)) {
                                return GetObject (type);
                        } else if (!type.IsPrimitive && !type.IsEnum) {
                                return ReadStruct (type);
                        } else {
                                object val;
                                DType dtype = Signature.TypeToDType (type);
                                val = ReadValue (dtype);

                                if (type.IsEnum)
                                        val = Enum.ToObject (type, val);

                                return val;
                        }
                }

                //helper method, should not be used generally
                public object ReadValue (DType dtype)
                {
                        switch (dtype)
                        {
                                case DType.Byte:
                                        return ReadByte ();

                                case DType.Boolean:
                                        return ReadBoolean ();

                                case DType.Int16:
                                        return ReadInt16 ();

                                case DType.UInt16:
                                        return ReadUInt16 ();

                                case DType.Int32:
                                        return ReadInt32 ();

                                case DType.UInt32:
                                        return ReadUInt32 ();

                                case DType.Int64:
                                        return ReadInt64 ();

                                case DType.UInt64:
                                        return ReadUInt64 ();

#if !DISABLE_SINGLE
                                case DType.Single:
                                        return ReadSingle ();
#endif

                                case DType.Double:
                                        return ReadDouble ();

                                case DType.String:
                                        return ReadString ();

                                case DType.ObjectPath:
                                        return ReadObjectPath ();

                                case DType.Signature:
                                        return ReadSignature ();

                                case DType.Variant:
                                        return ReadVariant ();

                                default:
                                        throw new Exception ("Unhandled D-Bus type: " + dtype);
                        }
                }

                public object GetObject (Type type)
                {
                        ObjectPath path = ReadObjectPath ();

                        return message.Connection.GetObject (type, (string)message.Header.Fields[FieldCode.Sender], path);
                }

                public byte ReadByte ()
                {
                        return data[pos++];
                }

                public bool ReadBoolean ()
                {
                        uint intval = ReadUInt32 ();

                        switch (intval) {
                                case 0:
                                        return false;
                                case 1:
                                        return true;
                                default:
                                        throw new Exception ("Read value " + intval + " at position " + pos + " while expecting boolean (0/1)");
                        }
                }

                unsafe protected void MarshalUShort (byte *dst)
                {
                        ReadPad (2);

                        if (endianness == Connection.NativeEndianness) {
                                dst[0] = data[pos + 0];
                                dst[1] = data[pos + 1];
                        } else {
                                dst[0] = data[pos + 1];
                                dst[1] = data[pos + 0];
                        }

                        pos += 2;
                }

                unsafe public short ReadInt16 ()
                {
                        short val;

                        MarshalUShort ((byte*)&val);

                        return val;
                }

                unsafe public ushort ReadUInt16 ()
                {
                        ushort val;

                        MarshalUShort ((byte*)&val);

                        return val;
                }

                unsafe protected void MarshalUInt (byte *dst)
                {
                        ReadPad (4);

                        if (endianness == Connection.NativeEndianness) {
                                dst[0] = data[pos + 0];
                                dst[1] = data[pos + 1];
                                dst[2] = data[pos + 2];
                                dst[3] = data[pos + 3];
                        } else {
                                dst[0] = data[pos + 3];
                                dst[1] = data[pos + 2];
                                dst[2] = data[pos + 1];
                                dst[3] = data[pos + 0];
                        }

                        pos += 4;
                }

                unsafe public int ReadInt32 ()
                {
                        int val;

                        MarshalUInt ((byte*)&val);

                        return val;
                }

                unsafe public uint ReadUInt32 ()
                {
                        uint val;

                        MarshalUInt ((byte*)&val);

                        return val;
                }

                unsafe protected void MarshalULong (byte *dst)
                {
                        ReadPad (8);

                        if (endianness == Connection.NativeEndianness) {
                                for (int i = 0; i < 8; ++i)
                                        dst[i] = data[pos + i];
                        } else {
                                for (int i = 0; i < 8; ++i)
                                        dst[i] = data[pos + (7 - i)];
                        }

                        pos += 8;
                }

                unsafe public long ReadInt64 ()
                {
                        long val;

                        MarshalULong ((byte*)&val);

                        return val;
                }

                unsafe public ulong ReadUInt64 ()
                {
                        ulong val;

                        MarshalULong ((byte*)&val);

                        return val;
                }

#if !DISABLE_SINGLE
                unsafe public float ReadSingle ()
                {
                        float val;

                        MarshalUInt ((byte*)&val);

                        return val;
                }
#endif

                unsafe public double ReadDouble ()
                {
                        double val;

                        MarshalULong ((byte*)&val);

                        return val;
                }

                public string ReadString ()
                {
                        uint ln = ReadUInt32 ();

                        string val = Encoding.UTF8.GetString (data, pos, (int)ln);
                        pos += (int)ln;
                        ReadNull ();

                        return val;
                }

                public ObjectPath ReadObjectPath ()
                {
                        //exactly the same as string
                        return new ObjectPath (ReadString ());
                }

                public Signature ReadSignature ()
                {
                        byte ln = ReadByte ();

                        if (ln > Protocol.MaxSignatureLength)
                                throw new Exception ("Signature length " + ln + " exceeds maximum allowed " + Protocol.MaxSignatureLength + " bytes");

                        byte[] sigData = new byte[ln];
                        Array.Copy (data, pos, sigData, 0, (int)ln);
                        pos += (int)ln;
                        ReadNull ();

                        return new Signature (sigData);
                }

                public object ReadVariant ()
                {
                        return ReadVariant (ReadSignature ());
                }

                object ReadVariant (Signature sig)
                {
                        return ReadValue (sig.ToType ());
                }

                //not pretty or efficient but works
                public void GetValueToDict (Type keyType, Type valType, System.Collections.IDictionary val)
                {
                        uint ln = ReadUInt32 ();

                        if (ln > Protocol.MaxArrayLength)
                                throw new Exception ("Dict length " + ln + " exceeds maximum allowed " + Protocol.MaxArrayLength + " bytes");

                        //advance to the alignment of the element
                        //ReadPad (Protocol.GetAlignment (Signature.TypeToDType (type)));
                        ReadPad (8);

                        int endPos = pos + (int)ln;

                        //while (stream.Position != endPos)
                        while (pos < endPos)
                        {
                                ReadPad (8);

                                val.Add (ReadValue (keyType), ReadValue (valType));
                        }

                        if (pos != endPos)
                                throw new Exception ("Read pos " + pos + " != ep " + endPos);
                }

                //this could be made generic to avoid boxing
                public Array ReadArray (Type elemType)
                {
                        uint ln = ReadUInt32 ();

                        if (ln > Protocol.MaxArrayLength)
                                throw new Exception ("Array length " + ln + " exceeds maximum allowed " + Protocol.MaxArrayLength + " bytes");

                        //TODO: more fast paths for primitive arrays
                        if (elemType == typeof (byte)) {
                                byte[] valb = new byte[ln];
                                Array.Copy (data, pos, valb, 0, (int)ln);
                                pos += (int)ln;
                                return valb;
                        }

                        //advance to the alignment of the element
                        ReadPad (Protocol.GetAlignment (Signature.TypeToDType (elemType)));

                        int endPos = pos + (int)ln;

                        //List<T> vals = new List<T> ();
                        System.Collections.ArrayList vals = new System.Collections.ArrayList ();

                        //while (stream.Position != endPos)
                        while (pos < endPos)
                                vals.Add (ReadValue (elemType));

                        if (pos != endPos)
                                throw new Exception ("Read pos " + pos + " != ep " + endPos);

                        return vals.ToArray (elemType);
                }

                //struct
                //probably the wrong place for this
                //there might be more elegant solutions
                public object ReadStruct (Type type)
                {
                        ReadPad (8);

                        object val = Activator.CreateInstance (type);

                        /*
                        if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (KeyValuePair<,>)) {
                                object elem;

                                System.Reflection.PropertyInfo key_prop = type.GetProperty ("Key");
                                GetValue (key_prop.PropertyType, out elem);
                                key_prop.SetValue (val, elem, null);

                                System.Reflection.PropertyInfo val_prop = type.GetProperty ("Value");
                                GetValue (val_prop.PropertyType, out elem);
                                val_prop.SetValue (val, elem, null);

                                return;
                        }
                        */

                        FieldInfo[] fis = type.GetFields (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

                        foreach (System.Reflection.FieldInfo fi in fis)
                                fi.SetValue (val, ReadValue (fi.FieldType));

                        return val;
                }

                public void ReadNull ()
                {
                        if (data[pos] != 0)
                                throw new Exception ("Read non-zero byte at position " + pos + " while expecting null terminator");
                        pos++;
                }

                /*
                public void ReadPad (int alignment)
                {
                        pos = Protocol.Padded (pos, alignment);
                }
                */

                public void ReadPad (int alignment)
                {
                        for (int endPos = Protocol.Padded (pos, alignment) ; pos != endPos ; pos++)
                                if (data[pos] != 0)
                                        throw new Exception ("Read non-zero byte at position " + pos + " while expecting padding");
                }
        }
}