1
// Copyright 2006 Alp Toker <alp@atoker.com>
2
// This software is made available under the MIT License
3
// See COPYING for details
6
using System.Diagnostics;
7
using System.Collections.Generic;
10
using System.Threading;
12
using System.Reflection;
14
//using Console = System.Diagnostics.Trace;
21
public class Connection
23
//TODO: reduce/correct visibility of these when appropriate
24
public Stream ns = null;
25
public long SocketHandle;
27
protected Transport transport;
28
public Transport Transport {
36
//TODO: reduce visibility when test-server no longer needs this
37
//protected Connection ()
42
public Connection (string address)
44
OpenPrivate (address);
48
protected bool isConnected = false;
49
public bool IsConnected
56
public static Connection Open (string address)
58
Connection conn = new Connection ();
59
conn.OpenPrivate (address);
65
//TODO: reduce visibility when test-server no longer needs this
66
//protected void OpenPrivate (string address)
67
public void OpenPrivate (string address)
73
throw new ArgumentNullException ("address");
75
if (!Address.Parse (address, out path, out abstr))
76
throw new ArgumentException ("Invalid D-Bus address: '" + address + "'", "address");
83
void Open (string path, bool abstr)
85
//transport = new UnixMonoTransport (path, abstr);
86
transport = new UnixNativeTransport (path, abstr);
87
ns = transport.Stream;
88
SocketHandle = transport.SocketHandle;
91
public void Authenticate ()
93
SaslClient auth = new SaslClient (this);
95
isAuthenticated = true;
98
protected bool isAuthenticated = false;
99
public bool IsAuthenticated
102
return isAuthenticated;
106
protected string unique_name = null;
107
public virtual string UniqueName
112
if (unique_name != null)
113
throw new Exception ("Unique name of a Connection can only be set once");
118
//Interlocked.Increment() handles the overflow condition for uint correctly, so it's ok to store the value as an int but cast it to uint
119
protected int serial = 0;
120
protected uint GenerateSerial ()
123
return (uint)Interlocked.Increment (ref serial);
129
public Message SendWithReplyAndBlock (Message msg)
131
uint id = SendWithReply (msg);
133
Message retMsg = WaitForReplyTo (id);
139
public uint SendWithReply (Message msg)
141
msg.ReplyExpected = true;
145
public uint Send (Message msg)
147
msg.Header.Serial = GenerateSerial ();
153
//Outbound.Enqueue (msg);
157
return msg.Header.Serial;
161
protected void WriteMessage (Message msg)
163
//Monitor.Enter (ns);
165
//ns.Write (msg.HeaderData, 0, msg.HeaderSize);
166
//Console.WriteLine ("headerSize: " + msg.HeaderSize);
167
//Console.WriteLine ("headerLength: " + msg.HeaderData.Length);
168
//Console.WriteLine ();
169
ns.Write (msg.HeaderData, 0, msg.HeaderData.Length);
170
if (msg.Body != null) {
171
ns.Write (msg.Body, 0, msg.Body.Length);
172
//msg.Body.WriteTo (ns);
178
protected Queue<Message> Inbound = new Queue<Message> ();
180
protected Queue<Message> Outbound = new Queue<Message> ();
184
//should just iterate the enumerator here
185
while (Outbound.Count != 0) {
186
Message msg = Outbound.Dequeue ();
191
public bool ReadWrite (int timeout_milliseconds)
198
public bool ReadWrite ()
200
return ReadWrite (-1);
203
public bool Dispatch ()
206
Message msg = Inbound.Dequeue ();
207
//HandleMessage (msg);
212
public bool ReadWriteDispatch (int timeout_milliseconds)
218
public bool ReadWriteDispatch ()
220
return ReadWriteDispatch (-1);
224
public Message ReadMessage ()
226
//Monitor.Enter (ns);
228
//FIXME: fix reading algorithm to work in one step
229
//this code is a bit silly and inefficient
230
//hopefully it's at least correct and avoids polls for now
234
byte[] buf = new byte[16];
235
read = ns.Read (buf, 0, 16);
238
throw new Exception ("Header read length mismatch: " + read + " of expected " + "16");
240
MemoryStream ms = new MemoryStream ();
242
ms.Write (buf, 0, 16);
244
EndianFlag endianness = (EndianFlag)buf[0];
245
MessageReader reader = new MessageReader (endianness, buf);
247
//discard the endian byte as we've already read it
249
reader.GetValue (out tmp);
251
//discard message type and flags, which we don't care about here
252
reader.GetValue (out tmp);
253
reader.GetValue (out tmp);
256
reader.GetValue (out version);
258
if (version < Protocol.MinVersion || version > Protocol.MaxVersion)
259
throw new NotSupportedException ("Protocol version '" + version.ToString () + "' is not supported");
261
if (Protocol.Verbose)
262
if (version != Protocol.Version)
263
Console.Error.WriteLine ("Warning: Protocol version '" + version.ToString () + "' is not explicitly supported but may be compatible");
265
uint bodyLength, serial, headerLength;
266
reader.GetValue (out bodyLength);
267
reader.GetValue (out serial);
268
reader.GetValue (out headerLength);
270
//TODO: remove this limitation
271
if (bodyLength > Int32.MaxValue || headerLength > Int32.MaxValue)
272
throw new NotImplementedException ("Long messages are not yet supported");
274
int bodyLen = (int)bodyLength;
275
int toRead = (int)headerLength;
277
toRead = Protocol.Padded ((int)toRead, 8);
279
buf = new byte[toRead];
281
read = ns.Read (buf, 0, toRead);
284
throw new Exception ("Read length mismatch: " + read + " of expected " + toRead);
286
ms.Write (buf, 0, buf.Length);
288
Message msg = new Message ();
289
msg.HeaderData = ms.ToArray ();
294
//msg.Body = new byte[(int)msg.Header->Length];
295
byte[] body = new byte[bodyLen];
297
//int len = ns.Read (msg.Body, 0, msg.Body.Length);
298
int len = ns.Read (body, 0, bodyLen);
300
//if (len != msg.Body.Length)
302
throw new Exception ("Message body size mismatch");
304
//msg.Body = new MemoryStream (body);
310
//this needn't be done here
316
//needs to be done properly
317
public Message WaitForReplyTo (uint id)
319
//Message msg = Inbound.Dequeue ();
322
while ((msg = ReadMessage ()) != null) {
323
if (msg.Header.Fields.ContainsKey (FieldCode.ReplySerial))
324
if ((uint)msg.Header.Fields[FieldCode.ReplySerial] == id)
335
protected void DispatchSignals ()
338
while (Inbound.Count != 0) {
339
Message msg = Inbound.Dequeue ();
346
public void Iterate ()
348
//Message msg = Inbound.Dequeue ();
349
Message msg = ReadMessage ();
354
protected void HandleMessage (Message msg)
356
switch (msg.Header.MessageType) {
357
case MessageType.MethodCall:
358
MethodCall method_call = new MethodCall (msg);
359
HandleMethodCall (method_call);
361
case MessageType.MethodReturn:
362
MethodReturn method_return = new MethodReturn (msg);
363
if (PendingCalls.ContainsKey (method_return.ReplySerial)) {
364
//TODO: pending calls
367
//if the signature is empty, it's just a token return message
368
if (msg.Signature != Signature.Empty)
369
Console.Error.WriteLine ("Warning: Couldn't handle async MethodReturn message for request id " + method_return.ReplySerial + " with signature '" + msg.Signature + "'");
371
case MessageType.Error:
372
//TODO: better exception handling
373
Error error = new Error (msg);
375
if (msg.Signature.Value == "s") {
376
MessageReader reader = new MessageReader (msg);
377
reader.GetValue (out errMsg);
379
//throw new Exception ("Remote Error: Signature='" + msg.Signature.Value + "' " + error.ErrorName + ": " + errMsg);
380
//if (Protocol.Verbose)
381
Console.Error.WriteLine ("Remote Error: Signature='" + msg.Signature.Value + "' " + error.ErrorName + ": " + errMsg);
383
case MessageType.Signal:
384
//HandleSignal (msg);
386
Inbound.Enqueue (msg);
388
case MessageType.Invalid:
390
throw new Exception ("Invalid message received: MessageType='" + msg.Header.MessageType + "'");
394
protected Dictionary<uint,Message> PendingCalls = new Dictionary<uint,Message> ();
397
//this might need reworking with MulticastDelegate
398
protected void HandleSignal (Message msg)
400
Signal signal = new Signal (msg);
402
string matchRule = MessageFilter.CreateMatchRule (MessageType.Signal, signal.Path, signal.Interface, signal.Member);
404
if (Handlers.ContainsKey (matchRule)) {
405
Delegate dlg = Handlers[matchRule];
406
//dlg.DynamicInvoke (GetDynamicValues (msg));
408
MethodInfo mi = dlg.Method;
409
//signals have no return value
410
dlg.DynamicInvoke (MessageHelper.GetDynamicValues (msg, mi.GetParameters ()));
413
//TODO: how should we handle this condition? sending an Error may not be appropriate in this case
414
if (Protocol.Verbose)
415
Console.Error.WriteLine ("Warning: No signal handler for " + signal.Member);
419
public Dictionary<string,Delegate> Handlers = new Dictionary<string,Delegate> ();
421
//not particularly efficient and needs to be generalized
422
protected void HandleMethodCall (MethodCall method_call)
424
//Console.Error.WriteLine ("method_call destination: " + method_call.Destination);
425
//Console.Error.WriteLine ("method_call path: " + method_call.Path);
427
//TODO: Ping and Introspect need to be abstracted and moved somewhere more appropriate once message filter infrastructure is complete
429
if (method_call.Interface == "org.freedesktop.DBus.Peer" && method_call.Member == "Ping") {
430
object[] pingRet = new object[0];
431
Message reply = MessageHelper.ConstructReplyFor (method_call, pingRet);
436
if (method_call.Interface == "org.freedesktop.DBus.Introspectable" && method_call.Member == "Introspect") {
437
Introspector intro = new Introspector ();
438
intro.root_path = method_call.Path;
439
//FIXME: do this properly
440
foreach (ObjectPath pth in RegisteredObjects.Keys) {
441
if (pth.Value.StartsWith (method_call.Path.Value)) {
442
intro.target_path = pth;
443
intro.target_type = RegisteredObjects[pth].GetType ();
446
intro.HandleIntrospect ();
447
//Console.Error.WriteLine (intro.xml);
449
object[] introRet = new object[1];
450
introRet[0] = intro.xml;
451
Message reply = MessageHelper.ConstructReplyFor (method_call, introRet);
456
if (RegisteredObjects.ContainsKey (method_call.Path)) {
457
object obj = RegisteredObjects[method_call.Path];
458
Type type = obj.GetType ();
459
//object retObj = type.InvokeMember (msg.Member, BindingFlags.InvokeMethod, null, obj, MessageHelper.GetDynamicValues (msg));
461
string methodName = method_call.Member;
463
//map property accessors
464
//FIXME: this needs to be done properly, not with simple String.Replace
465
//special case for Notifications left as a reminder that this is broken
466
if (method_call.Interface == "org.freedesktop.Notifications") {
467
methodName = methodName.Replace ("Get", "get_");
468
methodName = methodName.Replace ("Set", "set_");
471
//FIXME: breaks for overloaded methods
472
MethodInfo mi = type.GetMethod (methodName, BindingFlags.Public | BindingFlags.Instance);
474
//TODO: send errors instead of passing up local exceptions for these
477
throw new Exception ("The requested method could not be resolved");
479
//FIXME: such a simple approach won't work unfortunately
480
//if (!Mapper.IsPublic (mi))
481
// throw new Exception ("The resolved method is not marked as being public on this bus");
483
object retObj = null;
485
object[] inArgs = MessageHelper.GetDynamicValues (method_call.message, mi.GetParameters ());
486
retObj = mi.Invoke (obj, inArgs);
487
} catch (TargetInvocationException e) {
488
//TODO: consider whether it's correct to send an error for calls that don't expect a reply
490
//TODO: complete exception sending support
491
//TODO: method not found etc. exceptions
492
Exception ie = e.InnerException;
493
if (Protocol.Verbose) {
494
Console.Error.WriteLine ();
495
Console.Error.WriteLine (ie);
496
Console.Error.WriteLine ();
499
if (!method_call.message.ReplyExpected) {
500
Console.Error.WriteLine ();
501
Console.Error.WriteLine ("Warning: Not sending Error message (" + ie.GetType ().Name + ") as reply because no reply was expected by call to '" + (method_call.Interface + "." + method_call.Member) + "'");
502
Console.Error.WriteLine ();
506
Error error = new Error (Mapper.GetInterfaceName (ie.GetType ()), method_call.message.Header.Serial);
507
error.message.Signature = new Signature (DType.String);
509
MessageWriter writer = new MessageWriter ();
510
writer.Write (ie.Message);
511
error.message.Body = writer.ToArray ();
513
//TODO: we should be more strict here, but this fallback was added as a quick fix for p2p
514
if (method_call.Sender != null)
515
error.message.Header.Fields[FieldCode.Destination] = method_call.Sender;
517
error.message.Header.Fields[FieldCode.Interface] = method_call.Interface;
518
error.message.Header.Fields[FieldCode.Member] = method_call.Member;
520
Send (error.message);
524
if (method_call.message.ReplyExpected) {
528
if (retObj == null) {
529
retObjs = new object[0];
531
retObjs = new object[1];
535
Message reply = ConstructReplyFor (method_call, retObjs);
537
Message reply = MessageHelper.ConstructReplyFor (method_call, mi.ReturnType, retObj);
541
//FIXME: send the appropriate Error message
542
Console.Error.WriteLine ("Warning: No method handler for " + method_call.Member);
546
protected Dictionary<ObjectPath,object> RegisteredObjects = new Dictionary<ObjectPath,object> ();
548
//FIXME: this shouldn't be part of the core API
549
//that also applies to much of the other object mapping code
550
//it should cache proxies and objects, really
552
//inspired by System.Activator
553
public object GetObject (Type type, string bus_name, ObjectPath path)
555
BusObject busObject = new BusObject (this, bus_name, path);
556
DProxy prox = new DProxy (busObject, type);
557
return prox.GetTransparentProxy ();
561
public object GetObject (Type type, string bus_name, ObjectPath path)
563
return BusObject.GetObject (this, bus_name, path, type);
567
public T GetObject<T> (string bus_name, ObjectPath path)
569
return (T)GetObject (typeof (T), bus_name, path);
572
public void Register (string bus_name, ObjectPath path, object obj)
574
Type type = obj.GetType ();
576
BusObject busObject = new BusObject (this, bus_name, path);
578
foreach (EventInfo ei in type.GetEvents (BindingFlags.Public | BindingFlags.Instance)) {
579
//hook up only events that are public to the bus
580
if (!Mapper.IsPublic (ei))
583
Delegate dlg = busObject.GetHookupDelegate (ei);
584
ei.AddEventHandler (obj, dlg);
587
//FIXME: implement some kind of tree data structure or internal object hierarchy. right now we are ignoring the name and putting all object paths in one namespace, which is bad
588
RegisteredObjects[path] = obj;
591
public object Unregister (string bus_name, ObjectPath path)
593
//TODO: make use of bus_name
595
if (!RegisteredObjects.ContainsKey (path))
596
throw new Exception ("Cannot unmarshal " + path + " as it isn't marshaled");
597
object obj = RegisteredObjects[path];
599
RegisteredObjects.Remove (path);
601
//FIXME: complete unmarshaling including the handlers we added etc.
606
//these look out of place, but are useful
607
public virtual void AddMatch (string rule)
611
public virtual void RemoveMatch (string rule)
617
if (BitConverter.IsLittleEndian)
618
NativeEndianness = EndianFlag.Little;
620
NativeEndianness = EndianFlag.Big;
623
public static readonly EndianFlag NativeEndianness;