1 // Copyright 2006 Alp Toker <alp@atoker.com> 2 // This software is made available under the MIT License 3 // See COPYING for details 4 5 using System; 6 using System.Collections.Generic; 7 using System.IO; 8 using System.Threading; 9 using System.Reflection; 10 11 namespace NDesk.DBus 12 { 13 using Authentication; 14 using Transports; 15 16 public partial class Connection 17 { 18 //TODO: reconsider this field 19 Stream ns = null; 20 21 Transport transport; 22 internal Transport Transport { 23 get { 24 return transport; 25 } set { 26 transport = value; 27 } 28 } 29 Connection()30 protected Connection () {} 31 Connection(Transport transport)32 internal Connection (Transport transport) 33 { 34 this.transport = transport; 35 transport.Connection = this; 36 37 //TODO: clean this bit up 38 ns = transport.Stream; 39 } 40 41 //should this be public? Connection(string address)42 internal Connection (string address) 43 { 44 OpenPrivate (address); 45 Authenticate (); 46 } 47 48 /* 49 bool isConnected = false; 50 public bool IsConnected 51 { 52 get { 53 return isConnected; 54 } 55 } 56 */ 57 58 //should we do connection sharing here? Open(string address)59 public static Connection Open (string address) 60 { 61 Connection conn = new Connection (); 62 conn.OpenPrivate (address); 63 conn.Authenticate (); 64 65 return conn; 66 } 67 OpenPrivate(string address)68 internal void OpenPrivate (string address) 69 { 70 if (address == null) 71 throw new ArgumentNullException ("address"); 72 73 AddressEntry[] entries = Address.Parse (address); 74 if (entries.Length == 0) 75 throw new Exception ("No addresses were found"); 76 77 //TODO: try alternative addresses if needed 78 AddressEntry entry = entries[0]; 79 80 transport = Transport.Create (entry); 81 82 //TODO: clean this bit up 83 ns = transport.Stream; 84 } 85 Authenticate()86 void Authenticate () 87 { 88 if (transport != null) 89 transport.WriteCred (); 90 91 SaslClient auth = new SaslClient (this); 92 auth.Run (); 93 isAuthenticated = true; 94 } 95 96 bool isAuthenticated = false; 97 internal bool IsAuthenticated 98 { 99 get { 100 return isAuthenticated; 101 } 102 } 103 104 //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 105 int serial = 0; GenerateSerial()106 uint GenerateSerial () 107 { 108 //return ++serial; 109 return (uint)Interlocked.Increment (ref serial); 110 } 111 SendWithReplyAndBlock(Message msg)112 internal Message SendWithReplyAndBlock (Message msg) 113 { 114 PendingCall pending = SendWithReply (msg); 115 return pending.Reply; 116 } 117 SendWithReply(Message msg)118 internal PendingCall SendWithReply (Message msg) 119 { 120 msg.ReplyExpected = true; 121 msg.Header.Serial = GenerateSerial (); 122 123 //TODO: throttle the maximum number of concurrent PendingCalls 124 PendingCall pending = new PendingCall (this); 125 pendingCalls[msg.Header.Serial] = pending; 126 127 WriteMessage (msg); 128 129 return pending; 130 } 131 Send(Message msg)132 internal uint Send (Message msg) 133 { 134 msg.Header.Serial = GenerateSerial (); 135 136 WriteMessage (msg); 137 138 //Outbound.Enqueue (msg); 139 //temporary 140 //Flush (); 141 142 return msg.Header.Serial; 143 } 144 145 object writeLock = new object (); WriteMessage(Message msg)146 internal void WriteMessage (Message msg) 147 { 148 byte[] HeaderData = msg.GetHeaderData (); 149 150 long msgLength = HeaderData.Length + (msg.Body != null ? msg.Body.Length : 0); 151 if (msgLength > Protocol.MaxMessageLength) 152 throw new Exception ("Message length " + msgLength + " exceeds maximum allowed " + Protocol.MaxMessageLength + " bytes"); 153 154 lock (writeLock) { 155 ns.Write (HeaderData, 0, HeaderData.Length); 156 if (msg.Body != null && msg.Body.Length != 0) 157 ns.Write (msg.Body, 0, msg.Body.Length); 158 } 159 } 160 161 Queue<Message> Inbound = new Queue<Message> (); 162 /* 163 Queue<Message> Outbound = new Queue<Message> (); 164 165 public void Flush () 166 { 167 //should just iterate the enumerator here 168 while (Outbound.Count != 0) { 169 Message msg = Outbound.Dequeue (); 170 WriteMessage (msg); 171 } 172 } 173 174 public bool ReadWrite (int timeout_milliseconds) 175 { 176 //TODO 177 178 return true; 179 } 180 181 public bool ReadWrite () 182 { 183 return ReadWrite (-1); 184 } 185 186 public bool Dispatch () 187 { 188 //TODO 189 Message msg = Inbound.Dequeue (); 190 //HandleMessage (msg); 191 192 return true; 193 } 194 195 public bool ReadWriteDispatch (int timeout_milliseconds) 196 { 197 //TODO 198 return Dispatch (); 199 } 200 201 public bool ReadWriteDispatch () 202 { 203 return ReadWriteDispatch (-1); 204 } 205 */ 206 ReadMessage()207 internal Message ReadMessage () 208 { 209 byte[] header; 210 byte[] body = null; 211 212 int read; 213 214 //16 bytes is the size of the fixed part of the header 215 byte[] hbuf = new byte[16]; 216 read = ns.Read (hbuf, 0, 16); 217 218 if (read == 0) 219 return null; 220 221 if (read != 16) 222 throw new Exception ("Header read length mismatch: " + read + " of expected " + "16"); 223 224 EndianFlag endianness = (EndianFlag)hbuf[0]; 225 MessageReader reader = new MessageReader (endianness, hbuf); 226 227 //discard the endian byte as we've already read it 228 reader.ReadByte (); 229 230 //discard message type and flags, which we don't care about here 231 reader.ReadByte (); 232 reader.ReadByte (); 233 234 byte version = reader.ReadByte (); 235 236 if (version < Protocol.MinVersion || version > Protocol.MaxVersion) 237 throw new NotSupportedException ("Protocol version '" + version.ToString () + "' is not supported"); 238 239 if (Protocol.Verbose) 240 if (version != Protocol.Version) 241 Console.Error.WriteLine ("Warning: Protocol version '" + version.ToString () + "' is not explicitly supported but may be compatible"); 242 243 uint bodyLength = reader.ReadUInt32 (); 244 //discard serial 245 reader.ReadUInt32 (); 246 uint headerLength = reader.ReadUInt32 (); 247 248 //this check may become relevant if a future version of the protocol allows larger messages 249 /* 250 if (bodyLength > Int32.MaxValue || headerLength > Int32.MaxValue) 251 throw new NotImplementedException ("Long messages are not yet supported"); 252 */ 253 254 int bodyLen = (int)bodyLength; 255 int toRead = (int)headerLength; 256 257 //we fixup to include the padding following the header 258 toRead = Protocol.Padded (toRead, 8); 259 260 long msgLength = toRead + bodyLen; 261 if (msgLength > Protocol.MaxMessageLength) 262 throw new Exception ("Message length " + msgLength + " exceeds maximum allowed " + Protocol.MaxMessageLength + " bytes"); 263 264 header = new byte[16 + toRead]; 265 Array.Copy (hbuf, header, 16); 266 267 read = ns.Read (header, 16, toRead); 268 269 if (read != toRead) 270 throw new Exception ("Message header length mismatch: " + read + " of expected " + toRead); 271 272 //read the body 273 if (bodyLen != 0) { 274 body = new byte[bodyLen]; 275 read = ns.Read (body, 0, bodyLen); 276 277 if (read != bodyLen) 278 throw new Exception ("Message body length mismatch: " + read + " of expected " + bodyLen); 279 } 280 281 Message msg = new Message (); 282 msg.Connection = this; 283 msg.Body = body; 284 msg.SetHeaderData (header); 285 286 return msg; 287 } 288 289 //temporary hack DispatchSignals()290 internal void DispatchSignals () 291 { 292 lock (Inbound) { 293 while (Inbound.Count != 0) { 294 Message msg = Inbound.Dequeue (); 295 HandleSignal (msg); 296 } 297 } 298 } 299 300 internal Thread mainThread = Thread.CurrentThread; 301 302 //temporary hack Iterate()303 public void Iterate () 304 { 305 mainThread = Thread.CurrentThread; 306 307 //Message msg = Inbound.Dequeue (); 308 Message msg = ReadMessage (); 309 HandleMessage (msg); 310 DispatchSignals (); 311 } 312 HandleMessage(Message msg)313 internal void HandleMessage (Message msg) 314 { 315 //TODO: support disconnection situations properly and move this check elsewhere 316 if (msg == null) 317 throw new ArgumentNullException ("msg", "Cannot handle a null message; maybe the bus was disconnected"); 318 319 { 320 object field_value; 321 if (msg.Header.Fields.TryGetValue (FieldCode.ReplySerial, out field_value)) { 322 uint reply_serial = (uint)field_value; 323 PendingCall pending; 324 325 if (pendingCalls.TryGetValue (reply_serial, out pending)) { 326 if (pendingCalls.Remove (reply_serial)) 327 pending.Reply = msg; 328 329 return; 330 } 331 332 //we discard reply messages with no corresponding PendingCall 333 if (Protocol.Verbose) 334 Console.Error.WriteLine ("Unexpected reply message received: MessageType='" + msg.Header.MessageType + "', ReplySerial=" + reply_serial); 335 336 return; 337 } 338 } 339 340 switch (msg.Header.MessageType) { 341 case MessageType.MethodCall: 342 MethodCall method_call = new MethodCall (msg); 343 HandleMethodCall (method_call); 344 break; 345 case MessageType.Signal: 346 //HandleSignal (msg); 347 lock (Inbound) 348 Inbound.Enqueue (msg); 349 break; 350 case MessageType.Error: 351 //TODO: better exception handling 352 Error error = new Error (msg); 353 string errMsg = String.Empty; 354 if (msg.Signature.Value.StartsWith ("s")) { 355 MessageReader reader = new MessageReader (msg); 356 errMsg = reader.ReadString (); 357 } 358 //throw new Exception ("Remote Error: Signature='" + msg.Signature.Value + "' " + error.ErrorName + ": " + errMsg); 359 //if (Protocol.Verbose) 360 Console.Error.WriteLine ("Remote Error: Signature='" + msg.Signature.Value + "' " + error.ErrorName + ": " + errMsg); 361 break; 362 case MessageType.Invalid: 363 default: 364 throw new Exception ("Invalid message received: MessageType='" + msg.Header.MessageType + "'"); 365 } 366 } 367 368 Dictionary<uint,PendingCall> pendingCalls = new Dictionary<uint,PendingCall> (); 369 370 //this might need reworking with MulticastDelegate HandleSignal(Message msg)371 internal void HandleSignal (Message msg) 372 { 373 Signal signal = new Signal (msg); 374 375 //TODO: this is a hack, not necessary when MatchRule is complete 376 MatchRule rule = new MatchRule (); 377 rule.MessageType = MessageType.Signal; 378 rule.Interface = signal.Interface; 379 rule.Member = signal.Member; 380 rule.Path = signal.Path; 381 382 Delegate dlg; 383 if (Handlers.TryGetValue (rule, out dlg)) { 384 //dlg.DynamicInvoke (GetDynamicValues (msg)); 385 386 MethodInfo mi = dlg.Method; 387 //signals have no return value 388 dlg.DynamicInvoke (MessageHelper.GetDynamicValues (msg, mi.GetParameters ())); 389 390 } else { 391 //TODO: how should we handle this condition? sending an Error may not be appropriate in this case 392 if (Protocol.Verbose) 393 Console.Error.WriteLine ("Warning: No signal handler for " + signal.Member); 394 } 395 } 396 397 internal Dictionary<MatchRule,Delegate> Handlers = new Dictionary<MatchRule,Delegate> (); 398 399 //very messy MaybeSendUnknownMethodError(MethodCall method_call)400 internal void MaybeSendUnknownMethodError (MethodCall method_call) 401 { 402 Message msg = MessageHelper.CreateUnknownMethodError (method_call); 403 if (msg != null) 404 Send (msg); 405 } 406 407 //not particularly efficient and needs to be generalized HandleMethodCall(MethodCall method_call)408 internal void HandleMethodCall (MethodCall method_call) 409 { 410 //TODO: Ping and Introspect need to be abstracted and moved somewhere more appropriate once message filter infrastructure is complete 411 412 //FIXME: these special cases are slightly broken for the case where the member but not the interface is specified in the message 413 if (method_call.Interface == "org.freedesktop.DBus.Peer" && method_call.Member == "Ping") { 414 Message reply = MessageHelper.ConstructReply (method_call); 415 Send (reply); 416 return; 417 } 418 419 if (method_call.Interface == "org.freedesktop.DBus.Introspectable" && method_call.Member == "Introspect") { 420 Introspector intro = new Introspector (); 421 intro.root_path = method_call.Path; 422 intro.WriteStart (); 423 424 //FIXME: do this properly 425 //this is messy and inefficient 426 List<string> linkNodes = new List<string> (); 427 int depth = method_call.Path.Decomposed.Length; 428 foreach (ObjectPath pth in RegisteredObjects.Keys) { 429 if (pth.Value == (method_call.Path.Value)) { 430 ExportObject exo = (ExportObject)RegisteredObjects[pth]; 431 intro.WriteType (exo.obj.GetType ()); 432 } else { 433 for (ObjectPath cur = pth ; cur != null ; cur = cur.Parent) { 434 if (cur.Value == method_call.Path.Value) { 435 string linkNode = pth.Decomposed[depth]; 436 if (!linkNodes.Contains (linkNode)) { 437 intro.WriteNode (linkNode); 438 linkNodes.Add (linkNode); 439 } 440 } 441 } 442 } 443 } 444 445 intro.WriteEnd (); 446 447 Message reply = MessageHelper.ConstructReply (method_call, intro.xml); 448 Send (reply); 449 return; 450 } 451 452 BusObject bo; 453 if (RegisteredObjects.TryGetValue (method_call.Path, out bo)) { 454 ExportObject eo = (ExportObject)bo; 455 eo.HandleMethodCall (method_call); 456 } else { 457 MaybeSendUnknownMethodError (method_call); 458 } 459 } 460 461 Dictionary<ObjectPath,BusObject> RegisteredObjects = new Dictionary<ObjectPath,BusObject> (); 462 463 //FIXME: this shouldn't be part of the core API 464 //that also applies to much of the other object mapping code 465 GetObject(Type type, string bus_name, ObjectPath path)466 public object GetObject (Type type, string bus_name, ObjectPath path) 467 { 468 //if (type == null) 469 // return GetObject (bus_name, path); 470 471 //if the requested type is an interface, we can implement it efficiently 472 //otherwise we fall back to using a transparent proxy 473 if (type.IsInterface) { 474 return BusObject.GetObject (this, bus_name, path, type); 475 } else { 476 if (Protocol.Verbose) 477 Console.Error.WriteLine ("Warning: Note that MarshalByRefObject use is not recommended; for best performance, define interfaces"); 478 479 BusObject busObject = new BusObject (this, bus_name, path); 480 DProxy prox = new DProxy (busObject, type); 481 return prox.GetTransparentProxy (); 482 } 483 } 484 GetObject(string bus_name, ObjectPath path)485 public T GetObject<T> (string bus_name, ObjectPath path) 486 { 487 return (T)GetObject (typeof (T), bus_name, path); 488 } 489 490 [Obsolete ("Use the overload of Register() which does not take a bus_name parameter")] Register(string bus_name, ObjectPath path, object obj)491 public void Register (string bus_name, ObjectPath path, object obj) 492 { 493 Register (path, obj); 494 } 495 496 [Obsolete ("Use the overload of Unregister() which does not take a bus_name parameter")] Unregister(string bus_name, ObjectPath path)497 public object Unregister (string bus_name, ObjectPath path) 498 { 499 return Unregister (path); 500 } 501 Register(ObjectPath path, object obj)502 public void Register (ObjectPath path, object obj) 503 { 504 ExportObject eo = new ExportObject (this, path, obj); 505 eo.Registered = true; 506 507 //TODO: 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 508 RegisteredObjects[path] = eo; 509 } 510 Unregister(ObjectPath path)511 public object Unregister (ObjectPath path) 512 { 513 BusObject bo; 514 515 if (!RegisteredObjects.TryGetValue (path, out bo)) 516 throw new Exception ("Cannot unregister " + path + " as it isn't registered"); 517 518 RegisteredObjects.Remove (path); 519 520 ExportObject eo = (ExportObject)bo; 521 eo.Registered = false; 522 523 return eo.obj; 524 } 525 526 //these look out of place, but are useful AddMatch(string rule)527 internal protected virtual void AddMatch (string rule) 528 { 529 } 530 RemoveMatch(string rule)531 internal protected virtual void RemoveMatch (string rule) 532 { 533 } 534 Connection()535 static Connection () 536 { 537 if (BitConverter.IsLittleEndian) 538 NativeEndianness = EndianFlag.Little; 539 else 540 NativeEndianness = EndianFlag.Big; 541 } 542 543 internal static readonly EndianFlag NativeEndianness; 544 } 545 } 546