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 MessageWriter
        {
                protected EndianFlag endianness;
                protected MemoryStream stream;

                public Connection connection;

                //a default constructor is a bad idea for now as we want to make sure the header and content-type match
                public MessageWriter () : this (Connection.NativeEndianness) {}

                public MessageWriter (EndianFlag endianness)
                {
                        this.endianness = endianness;
                        stream = new MemoryStream ();
                }

                public byte[] ToArray ()
                {
                        //TODO: mark the writer locked or something here
                        return stream.ToArray ();
                }

                public void CloseWrite ()
                {
                        WritePad (8);
                }

                public void Write (byte val)
                {
                        stream.WriteByte (val);
                }

                public void Write (bool val)
                {
                        Write ((uint) (val ? 1 : 0));
                }

                unsafe protected void MarshalUShort (byte *data)
                {
                        WritePad (2);
                        byte[] dst = new byte[2];

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

                        stream.Write (dst, 0, 2);
                }

                unsafe public void Write (short val)
                {
                        MarshalUShort ((byte*)&val);
                }

                unsafe public void Write (ushort val)
                {
                        MarshalUShort ((byte*)&val);
                }

                unsafe protected void MarshalUInt (byte *data)
                {
                        WritePad (4);
                        byte[] dst = new byte[4];

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

                        stream.Write (dst, 0, 4);
                }

                unsafe public void Write (int val)
                {
                        MarshalUInt ((byte*)&val);
                }

                unsafe public void Write (uint val)
                {
                        MarshalUInt ((byte*)&val);
                }

                unsafe protected void MarshalULong (byte *data)
                {
                        WritePad (8);
                        byte[] dst = new byte[8];

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

                        stream.Write (dst, 0, 8);
                }

                unsafe public void Write (long val)
                {
                        MarshalULong ((byte*)&val);
                }

                unsafe public void Write (ulong val)
                {
                        MarshalULong ((byte*)&val);
                }

#if !DISABLE_SINGLE
                unsafe public void Write (float val)
                {
                        MarshalUInt ((byte*)&val);
                }
#endif

                unsafe public void Write (double val)
                {
                        MarshalULong ((byte*)&val);
                }

                public void Write (string val)
                {
                        byte[] utf8_data = Encoding.UTF8.GetBytes (val);
                        Write ((uint)utf8_data.Length);
                        stream.Write (utf8_data, 0, utf8_data.Length);
                        WriteNull ();
                }

                public void Write (ObjectPath val)
                {
                        Write (val.Value);
                }

                public void Write (Signature val)
                {
                        byte[] ascii_data = val.GetBuffer ();

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

                        Write ((byte)ascii_data.Length);
                        stream.Write (ascii_data, 0, ascii_data.Length);
                        WriteNull ();
                }

                public void WriteComplex (object val, Type type)
                {
                        if (type == typeof (void))
                                return;

                        if (type.IsArray) {
                                WriteArray (val, type.GetElementType ());
                        } else if (type.IsGenericType && (type.GetGenericTypeDefinition () == typeof (IDictionary<,>) || type.GetGenericTypeDefinition () == typeof (Dictionary<,>))) {
                                Type[] genArgs = type.GetGenericArguments ();
                                System.Collections.IDictionary idict = (System.Collections.IDictionary)val;
                                WriteFromDict (genArgs[0], genArgs[1], idict);
                        } else if (Mapper.IsPublic (type)) {
                                WriteObject (type, val);
                        } else if (!type.IsPrimitive && !type.IsEnum) {
                                WriteValueType (val, type);
                                /*
                        } else if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>)) {
                                //is it possible to support nullable types?
                                Type[] genArgs = type.GetGenericArguments ();
                                WriteVariant (genArgs[0], val);
                                */
                        } else {
                                throw new Exception ("Can't write");
                        }
                }

                public void Write (Type type, object val)
                {
                        if (type == typeof (void))
                                return;

                        if (type.IsArray) {
                                WriteArray (val, type.GetElementType ());
                        } else if (type == typeof (ObjectPath)) {
                                Write ((ObjectPath)val);
                        } else if (type == typeof (Signature)) {
                                Write ((Signature)val);
                        } else if (type == typeof (object)) {
                                Write (val);
                        } else if (type == typeof (string)) {
                                Write ((string)val);
                        } else if (type.IsGenericType && (type.GetGenericTypeDefinition () == typeof (IDictionary<,>) || type.GetGenericTypeDefinition () == typeof (Dictionary<,>))) {
                                Type[] genArgs = type.GetGenericArguments ();
                                System.Collections.IDictionary idict = (System.Collections.IDictionary)val;
                                WriteFromDict (genArgs[0], genArgs[1], idict);
                        } else if (Mapper.IsPublic (type)) {
                                WriteObject (type, val);
                        } else if (!type.IsPrimitive && !type.IsEnum) {
                                WriteValueType (val, type);
                        } else {
                                Write (Signature.TypeToDType (type), val);
                        }
                }

                //helper method, should not be used as it boxes needlessly
                public void Write (DType dtype, object val)
                {
                        switch (dtype)
                        {
                                case DType.Byte:
                                {
                                        Write ((byte)val);
                                }
                                break;
                                case DType.Boolean:
                                {
                                        Write ((bool)val);
                                }
                                break;
                                case DType.Int16:
                                {
                                        Write ((short)val);
                                }
                                break;
                                case DType.UInt16:
                                {
                                        Write ((ushort)val);
                                }
                                break;
                                case DType.Int32:
                                {
                                        Write ((int)val);
                                }
                                break;
                                case DType.UInt32:
                                {
                                        Write ((uint)val);
                                }
                                break;
                                case DType.Int64:
                                {
                                        Write ((long)val);
                                }
                                break;
                                case DType.UInt64:
                                {
                                        Write ((ulong)val);
                                }
                                break;
#if !DISABLE_SINGLE
                                case DType.Single:
                                {
                                        Write ((float)val);
                                }
                                break;
#endif
                                case DType.Double:
                                {
                                        Write ((double)val);
                                }
                                break;
                                case DType.String:
                                {
                                        Write ((string)val);
                                }
                                break;
                                case DType.ObjectPath:
                                {
                                        Write ((ObjectPath)val);
                                }
                                break;
                                case DType.Signature:
                                {
                                        Write ((Signature)val);
                                }
                                break;
                                case DType.Variant:
                                {
                                        Write ((object)val);
                                }
                                break;
                                default:
                                throw new Exception ("Unhandled D-Bus type: " + dtype);
                        }
                }

                public void WriteObject (Type type, object val)
                {
                        ObjectPath path;

                        BusObject bobj = val as BusObject;

                        if (bobj == null && val is MarshalByRefObject) {
                                bobj = ((MarshalByRefObject)val).GetLifetimeService () as BusObject;
                        }

                        if (bobj == null)
                                throw new Exception ("No object reference to write");

                        path = bobj.Path;

                        Write (path);
                }

                //variant
                public void Write (object val)
                {
                        //TODO: maybe support sending null variants

                        if (val == null)
                                throw new NotSupportedException ("Cannot send null variant");

                        Type type = val.GetType ();

                        WriteVariant (type, val);
                }

                public void WriteVariant (Type type, object val)
                {
                        Signature sig = Signature.GetSig (type);

                        Write (sig);
                        Write (type, val);
                }

                //this requires a seekable stream for now
                public void WriteArray (object obj, Type elemType)
                {
                        Array val = (Array)obj;

                        //TODO: more fast paths for primitive arrays
                        if (elemType == typeof (byte)) {
                                if (val.Length > Protocol.MaxArrayLength)
                                        throw new Exception ("Array length " + val.Length + " exceeds maximum allowed " + Protocol.MaxArrayLength + " bytes");

                                Write ((uint)val.Length);
                                stream.Write ((byte[])val, 0, val.Length);
                                return;
                        }

                        long origPos = stream.Position;
                        Write ((uint)0);

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

                        long startPos = stream.Position;

                        foreach (object elem in val)
                                Write (elemType, elem);

                        long endPos = stream.Position;
                        uint ln = (uint)(endPos - startPos);
                        stream.Position = origPos;

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

                        Write (ln);
                        stream.Position = endPos;
                }

                public void WriteFromDict (Type keyType, Type valType, System.Collections.IDictionary val)
                {
                        long origPos = stream.Position;
                        Write ((uint)0);

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

                        long startPos = stream.Position;

                        foreach (System.Collections.DictionaryEntry entry in val)
                        {
                                WritePad (8);

                                Write (keyType, entry.Key);
                                Write (valType, entry.Value);
                        }

                        long endPos = stream.Position;
                        uint ln = (uint)(endPos - startPos);
                        stream.Position = origPos;

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

                        Write (ln);
                        stream.Position = endPos;
                }

                public void WriteValueType (object val, Type type)
                {
                        MethodInfo mi = TypeImplementer.GetWriteMethod (type);
                        mi.Invoke (null, new object[] {this, val});
                }

                /*
                public void WriteValueTypeOld (object val, Type type)
                {
                        WritePad (8);

                        if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (KeyValuePair<,>)) {
                                System.Reflection.PropertyInfo key_prop = type.GetProperty ("Key");
                                Write (key_prop.PropertyType, key_prop.GetValue (val, null));

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

                                return;
                        }

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

                        foreach (System.Reflection.FieldInfo fi in fis) {
                                object elem;
                                elem = fi.GetValue (val);
                                Write (fi.FieldType, elem);
                        }
                }
                */

                public void WriteNull ()
                {
                        stream.WriteByte (0);
                }

                public void WritePad (int alignment)
                {
                        int needed = Protocol.PadNeeded ((int)stream.Position, alignment);
                        for (int i = 0 ; i != needed ; i++)
                                stream.WriteByte (0);
                }
        }
}