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