b71f23c4a7823800814fdb865daa69b56497073e
[browser-dbus-bridge.git] / xpcom-dbusservice / DBusMarshaling.cpp
1 /**
2  * Browser D-Bus Bridge, XPCOM version
3  *
4  * Copyright © 2008 Movial Creative Technologies Inc
5  *  Contact: Movial Creative Technologies Inc, <info@movial.com>
6  *  Authors: Lauri Mylläri, <lauri.myllari@movial.fi>
7  *           Kalle Vahlman, <kalle.vahlman@movial.com>
8  *
9  * The contents of this file are subject to the Mozilla Public License
10  * Version 1.1 (the "License"); you may not use this file except in
11  * compliance with the License. You may obtain a copy of the License at
12  * http://www.mozilla.org/MPL/
13  *
14  * Software distributed under the License is distributed on an "AS IS"
15  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
16  * License for the specific language governing rights and limitations
17  * under the License.
18  *
19  * The Original Code is the Browser D-Bus Bridge, XPCOM version.
20  *
21  * The Initial Developer of the Original Code is Movial Creative Technologies
22  * Inc. Portions created by Initial Developer are Copyright (C) 2008
23  * Movial Creative Technologies Inc. All Rights Reserved.
24  *
25  */
26
27 #include <stdio.h>
28
29 #include "nsEmbedString.h"
30 #include "nsComponentManagerUtils.h"
31 #include "nsServiceManagerUtils.h"
32 #include "nsArrayUtils.h"
33 #include "nsTArray.h"
34 #include "nsMemory.h"
35 #include "nsISupportsPrimitives.h"
36 #include "nsIProperties.h"
37 #include "nsIXPConnect.h"
38
39 #include "DBusMarshaling.h"
40
41 #include "bdb-debug.h"
42
43 void addArrayDataToIter(void *data_ptr, PRUint32 start, PRUint32 count, PRUint16 type, DBusMessageIter *aIter, DBusSignatureIter *aSigIter);
44 void addJSValueToIter(JSContext *cx, jsval *aValue, DBusMessageIter *aIter, DBusSignatureIter *aSigIter);
45
46 static void
47 listJSObjectProperties(JSContext *cx, JSObject *js_obj)
48 {
49     JSIdArray *props = JS_Enumerate(cx, js_obj);
50     if (props)
51     {
52         BDBLOG(("  listJSObjectProperties: got JSIdArray with %i props\n", props->length));
53
54         for (int i = 0; i < props->length; i++)
55         {
56             jsval v;
57             const PRUnichar *name;
58             JS_IdToValue( cx, props->vector[i], &v );
59             JSString *prop_string = JS_ValueToString(cx, v);
60             name = reinterpret_cast<const PRUnichar*>(JS_GetStringChars(prop_string));
61             nsDependentString name_s(name);
62             const char *utf8prop = NS_ConvertUTF16toUTF8(name_s).get();
63             BDBLOG(("    property %s\n", utf8prop));
64
65             jsval value;
66             if (JS_LookupUCProperty(cx,
67                                     js_obj,
68                                     JS_GetStringChars(prop_string),
69                                     JS_GetStringLength(prop_string),
70                                     &value) == JS_TRUE)
71             {
72                 JSString *val_string = JS_ValueToString(cx, value);
73                 const PRUnichar *valstr = reinterpret_cast<const PRUnichar*>(JS_GetStringChars(val_string));
74                 nsDependentString val_s(valstr);
75                 const char *utf8val = NS_ConvertUTF16toUTF8(val_s).get();
76                 BDBLOG(("    value %s\n", utf8val));
77             }
78         }
79         JS_DestroyIdArray(cx, props);
80     }
81 }
82
83 void
84 getSignatureFromJSValue(JSContext *cx, jsval *aValue, nsCString &aResult)
85 {
86     aResult.Assign(DBUS_TYPE_INVALID_AS_STRING);
87
88     if (JSVAL_IS_BOOLEAN(*aValue))
89         aResult.Assign(DBUS_TYPE_BOOLEAN_AS_STRING);
90     else if (JSVAL_IS_INT(*aValue))
91         aResult.Assign(DBUS_TYPE_INT32_AS_STRING);
92     else if (JSVAL_IS_DOUBLE(*aValue))
93         aResult.Assign(DBUS_TYPE_DOUBLE_AS_STRING);
94     else if (JSVAL_IS_STRING(*aValue))
95         aResult.Assign(DBUS_TYPE_STRING_AS_STRING);
96     else if (JSVAL_IS_OBJECT(*aValue) && JS_IsArrayObject(cx, JSVAL_TO_OBJECT(*aValue)))
97     {
98         // guess element type from first property value
99
100         JSIdArray *props = JS_Enumerate(cx, JSVAL_TO_OBJECT(*aValue));
101         if (props)
102         {
103             BDBLOG(("    got JSIdArray\n"));
104             aResult.Assign(DBUS_TYPE_ARRAY_AS_STRING);
105
106             // get key signature from first property name
107             jsval propname;
108             nsCAutoString tmpsig;
109             JS_IdToValue(cx, props->vector[0], &propname);
110
111             jsval propvalue;
112             JSString *prop_string = JS_ValueToString(cx, propname);
113             if (JS_LookupUCProperty(cx,
114                                     JSVAL_TO_OBJECT(*aValue),
115                                     JS_GetStringChars(prop_string),
116                                     JS_GetStringLength(prop_string),
117                                     &propvalue) == JS_TRUE)
118             {
119                 getSignatureFromJSValue(cx, &propvalue, tmpsig);
120                 aResult.Append(tmpsig);
121             }
122             else
123             {
124                 // FIXME - could not find property value??
125                 // assume string to keep signature valid
126                 aResult.Append(DBUS_TYPE_STRING_AS_STRING);
127             }
128             JS_DestroyIdArray(cx, props);
129         }
130
131     }
132     else if (JSVAL_IS_OBJECT(*aValue))
133     {
134         nsISupports* supports;
135         JSClass* clazz;
136         JSObject* parent;
137         JSObject* glob = JSVAL_TO_OBJECT(*aValue);
138
139         clazz = JS_GET_CLASS(cx, JS_GetParent(cx, glob));
140
141         if (!clazz ||
142             !(clazz->flags & JSCLASS_HAS_PRIVATE) ||
143             !(clazz->flags & JSCLASS_PRIVATE_IS_NSISUPPORTS) ||
144             !(supports = (nsISupports*) JS_GetPrivate(cx, glob))) {
145
146             BDBLOG(("  getSignatureFromJSValue: could not find nsISupports inside object, assume dictionary\n"));
147
148             // try to enumerate object properties
149             JSIdArray *props = JS_Enumerate(cx, glob);
150             if (props)
151             {
152                 BDBLOG(("    got JSIdArray with %i props\n", props->length));
153                 aResult.Assign(DBUS_TYPE_ARRAY_AS_STRING);
154                 aResult.Append(DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING);
155
156                 // get key signature from first property name
157                 jsval propname;
158                 nsCAutoString tmpsig;
159                 JS_IdToValue(cx, props->vector[0], &propname);
160                 getSignatureFromJSValue(cx, &propname, tmpsig);
161                 aResult.Append(tmpsig);
162
163                 jsval propvalue;
164                 JSString *prop_string = JS_ValueToString(cx, propname);
165                 if (JS_LookupUCProperty(cx,
166                                         glob,
167                                         JS_GetStringChars(prop_string),
168                                         JS_GetStringLength(prop_string),
169                                         &propvalue) == JS_TRUE)
170                 {
171                     getSignatureFromJSValue(cx, &propvalue, tmpsig);
172                     aResult.Append(tmpsig);
173                 }
174                 else
175                 {
176                     // FIXME - could not find property value??
177                     // assume string to keep signature valid
178                     aResult.Append(DBUS_TYPE_STRING_AS_STRING);
179                 }
180                 aResult.Append(DBUS_DICT_ENTRY_END_CHAR_AS_STRING);
181                 JS_DestroyIdArray(cx, props);
182             }
183
184         }
185         else
186         {
187             BDBLOG(("  getSignatureFromJSValue: clazz->name %s\n", clazz->name));
188             // test argument for nsIXPConnectWrappedNative
189             nsCOMPtr<nsIXPConnectWrappedNative> wrappednative = do_QueryInterface(supports);
190             if (wrappednative)
191             {
192                 BDBLOG(("  getSignatureFromJSValue: got nsIXPConnectWrappedNative\n"));
193                 nsCOMPtr<nsIVariant> variant = do_QueryInterface(wrappednative->Native());
194                 if (variant)
195                 {
196                     BDBLOG(("    found wrapped variant\n"));
197                     getSignatureFromVariant(variant, aResult);
198                     return;
199                 }
200             }
201             // use string type as fallback
202             aResult.Assign(DBUS_TYPE_STRING_AS_STRING);
203             return;
204         }
205
206     }
207 }
208
209 void
210 getSignatureFromVariantType(PRUint16 aType, nsCString &aResult)
211 {
212     switch (aType) {
213         case nsIDataType::VTYPE_BOOL:
214             aResult.Assign(DBUS_TYPE_BOOLEAN_AS_STRING);
215             return;
216         case nsIDataType::VTYPE_INT8: /* FIXME - check sign issues;
217                                          dbus supports only unsigned 8bit */
218         case nsIDataType::VTYPE_UINT8:
219             aResult.Assign(DBUS_TYPE_BYTE_AS_STRING);
220             return;
221         case nsIDataType::VTYPE_INT16:
222             aResult.Assign(DBUS_TYPE_INT16_AS_STRING);
223             return;
224         case nsIDataType::VTYPE_UINT16:
225             aResult.Assign(DBUS_TYPE_UINT16_AS_STRING);
226             return;
227         case nsIDataType::VTYPE_INT32:
228             aResult.Assign(DBUS_TYPE_INT32_AS_STRING);
229             return;
230         case nsIDataType::VTYPE_UINT32:
231             aResult.Assign(DBUS_TYPE_UINT32_AS_STRING);
232             return;
233         case nsIDataType::VTYPE_INT64:
234             aResult.Assign(DBUS_TYPE_INT64_AS_STRING);
235             return;
236         case nsIDataType::VTYPE_UINT64:
237             aResult.Assign(DBUS_TYPE_UINT64_AS_STRING);
238             return;
239         case nsIDataType::VTYPE_DOUBLE:
240             aResult.Assign(DBUS_TYPE_DOUBLE_AS_STRING);
241             return;
242         case nsIDataType::VTYPE_VOID:
243             // FIXME - assume that string is the best representation
244         case nsIDataType::VTYPE_WSTRING_SIZE_IS:
245         case nsIDataType::VTYPE_WCHAR_STR:
246             aResult.Assign(DBUS_TYPE_STRING_AS_STRING);
247             return;
248         default:
249             BDBLOG(("  getSignatureFromVariantType: %d not a simple type\n", aType));
250             aResult.Assign(DBUS_TYPE_INVALID_AS_STRING);
251     }
252 }
253
254 void
255 getSignatureFromVariant(nsIVariant *aVariant, nsCString &aResult)
256 {
257     aResult.Assign(DBUS_TYPE_INVALID_AS_STRING);
258
259     PRUint16 dataType;
260     aVariant->GetDataType(&dataType);
261
262     switch (dataType) {
263         case nsIDataType::VTYPE_VOID:
264         case nsIDataType::VTYPE_BOOL:
265         case nsIDataType::VTYPE_INT8:
266         case nsIDataType::VTYPE_UINT8:
267         case nsIDataType::VTYPE_INT16:
268         case nsIDataType::VTYPE_UINT16:
269         case nsIDataType::VTYPE_INT32:
270         case nsIDataType::VTYPE_UINT32:
271         case nsIDataType::VTYPE_INT64:
272         case nsIDataType::VTYPE_UINT64:
273         case nsIDataType::VTYPE_DOUBLE:
274         case nsIDataType::VTYPE_WSTRING_SIZE_IS:
275         case nsIDataType::VTYPE_WCHAR_STR:
276         {
277             PRUint32 val = 0;
278             aVariant->GetAsUint32(&val);
279             BDBLOG(("  getSignatureFromVariant: simple type %i:%i\n", dataType, val));
280             getSignatureFromVariantType(dataType, aResult);
281             break;
282         }
283         case nsIDataType::VTYPE_ARRAY:
284         {
285             BDBLOG(("  getSignatureFromVariant: array\n"));
286
287             // need to recurse into array
288             PRUint16 type = 0;
289             nsIID iid;
290             PRUint32 count = 0;
291             void *data_ptr = nsnull;
292
293             aVariant->GetAsArray(&type,
294                                  &iid,
295                                  &count,
296                                  &data_ptr);
297
298             BDBLOG(("  getSignatureFromVariant: got %d elements of type %d\n", count, type));
299
300             nsCAutoString elementsig;
301
302             if (type == nsIDataType::VTYPE_INTERFACE_IS)
303             {
304                 // get element signature from first element
305                 nsISupports *element = ((nsISupports **)data_ptr)[0];
306                 getSignatureFromISupports(element, elementsig);
307             }
308             else if (type == nsIDataType::VTYPE_ARRAY)
309             {
310                 // FIXME - can this happen?
311                 BDBLOG(("    element type array, don't know how to handle\n"));
312             }
313             else
314             {
315                 getSignatureFromVariantType(type, elementsig);
316             }
317
318             aResult.Assign(DBUS_TYPE_ARRAY_AS_STRING);
319             aResult.Append(elementsig);
320
321             nsMemory::Free(data_ptr);
322             break;
323         }
324         case nsIDataType::VTYPE_INTERFACE_IS:
325         {
326             BDBLOG(("  getSignatureFromVariant: interface\n"));
327             nsCOMPtr<nsISupports> is;
328             nsIID *iid;
329             aVariant->GetAsInterface(&iid, getter_AddRefs(is));
330             getSignatureFromISupports(is, aResult);
331             break;
332         }
333         default:
334         {
335             BDBLOG(("  getSignatureFromVariant: unknown type %d\n", dataType));
336             break;
337         }
338     }
339 }
340
341 void
342 getSignatureFromISupports(nsISupports *aISupports, nsCString &aResult)
343 {
344     aResult.Assign(DBUS_TYPE_INVALID_AS_STRING);
345
346     // test argument for nsIVariant
347     nsCOMPtr<nsIVariant> variant = do_QueryInterface(aISupports);
348     if (variant)
349     {
350         BDBLOG(("  getSignatureFromISupports: nsIVariant\n"));
351         getSignatureFromVariant(variant, aResult);
352         return;
353     }
354
355     // test argument for nsIXPConnectWrappedJS
356     nsCOMPtr<nsIXPConnectWrappedJS> wrapped = do_QueryInterface(aISupports);
357     if (wrapped)
358     {
359         BDBLOG(("  getSignatureFromISupports: nsIXPConnectWrappedJS\n"));
360         JSObject *js_obj = nsnull;
361         if (NS_SUCCEEDED(wrapped->GetJSObject(&js_obj)))
362         {
363             // try to get a JS context (code borrowed from xpcsample1.cpp)
364
365             // get the xpconnect service
366             nsresult rv;
367             nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID(), &rv));
368             if(NS_FAILED(rv))
369                 return;
370             BDBLOG(("    got nsIXPConnect\n"));
371
372             // get the xpconnect native call context
373             nsAXPCNativeCallContext *callContext = nsnull;
374             xpc->GetCurrentNativeCallContext(&callContext);
375             if(!callContext)
376                 return;
377
378             // Get JSContext of current call
379             JSContext* cx;
380             rv = callContext->GetJSContext(&cx);
381             if(NS_FAILED(rv) || !cx)
382                 return;
383             BDBLOG(("    got JSContext\n"));
384
385             jsval obj_as_jsval = OBJECT_TO_JSVAL(js_obj);
386             getSignatureFromJSValue(cx, &obj_as_jsval, aResult);
387         }
388     }
389 }
390
391 PRUint16 getVType(int dType)
392 {
393     switch (dType)
394     {
395        case DBUS_TYPE_BOOLEAN:
396             return nsIDataType::VTYPE_BOOL;
397        case DBUS_TYPE_BYTE:
398             return nsIDataType::VTYPE_INT8;
399        case DBUS_TYPE_INT16:
400             return nsIDataType::VTYPE_INT16;
401        case DBUS_TYPE_UINT16:
402             return nsIDataType::VTYPE_UINT16;
403        case DBUS_TYPE_INT32:
404             return nsIDataType::VTYPE_INT32;
405        case DBUS_TYPE_UINT32:
406             return nsIDataType::VTYPE_UINT32;
407        case DBUS_TYPE_DOUBLE:
408             return nsIDataType::VTYPE_DOUBLE;
409        case DBUS_TYPE_STRING:
410        case DBUS_TYPE_OBJECT_PATH:
411        case DBUS_TYPE_SIGNATURE:
412             return nsIDataType::VTYPE_WCHAR_STR;
413        default:
414             break;
415     }
416
417     return -1;
418 }
419
420 PRBool typesMatch(PRUint16 vType, int dType)
421 {
422     return (vType == getVType(dType));
423 }
424
425 void
426 addVariantToIter(nsIVariant *aVariant, DBusMessageIter *aIter, DBusSignatureIter *aSigIter)
427 {
428     char *element_signature = dbus_signature_iter_get_signature(aSigIter);
429     int element_type = dbus_signature_iter_get_current_type(aSigIter);
430
431     PRUint16 variant_type;
432     aVariant->GetDataType(&variant_type);
433
434     BDBLOG(("addVariantToIter: signature \"%s\", type %c, variant type: %i\n",
435            element_signature,
436            element_type,
437            variant_type));
438
439     if (dbus_type_is_basic(element_type))
440     {
441         BDBLOG(("  add basic type from variant\n"));
442         addBasicTypeToIter(aVariant, aIter, element_type);
443     }
444     else if (element_type == DBUS_TYPE_ARRAY)
445     {
446         if (dbus_signature_iter_get_element_type(aSigIter) == DBUS_TYPE_DICT_ENTRY)
447         {
448             /* TODO: Support for non-JS Dicts */
449             
450             BDBLOG(("  add dict from variant\n"));
451
452             nsCOMPtr<nsISupports> is;
453             nsIID *iid;
454             // Reported by a leak, dunno why?
455             // It's a comptr so it should go away with the end of context afaik
456             aVariant->GetAsInterface(&iid, getter_AddRefs(is));
457
458             // test argument for nsIXPConnectWrappedJS
459             nsCOMPtr<nsIXPConnectWrappedJS> wrapped = do_QueryInterface(is);
460             if (wrapped)
461             {
462                 BDBLOG(("  Found XPConnect object\n"));
463                 JSObject *js_obj = nsnull;
464                 if (NS_SUCCEEDED(wrapped->GetJSObject(&js_obj)))
465                 {
466                     // try to get a JS context (code borrowed from xpcsample1.cpp)
467
468                     // get the xpconnect service
469                     nsresult rv;
470                     nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID(), &rv));
471                     if(NS_FAILED(rv))
472                         return;
473                     BDBLOG(("    got nsIXPConnect\n"));
474
475                     // get the xpconnect native call context
476                     nsAXPCNativeCallContext *callContext = nsnull;
477                     xpc->GetCurrentNativeCallContext(&callContext);
478                     if(!callContext)
479                         return;
480
481                     // Get JSContext of current call
482                     JSContext* cx;
483                     rv = callContext->GetJSContext(&cx);
484                     if(NS_FAILED(rv) || !cx)
485                         return;
486                     BDBLOG(("    got JSContext\n"));
487
488                     jsval obj_as_jsval = OBJECT_TO_JSVAL(js_obj);
489
490                     // try to enumerate object properties
491                     JSIdArray *props = JS_Enumerate(cx, js_obj);
492                     if (props)
493                     {
494                         BDBLOG(("    got JSIdArray with %i props\n", props->length));
495                         
496                         // Start the array container
497                         DBusMessageIter childIter;
498                         DBusSignatureIter childSigIter;
499                         DBusSignatureIter dictSigIter;
500                         dbus_signature_iter_recurse(aSigIter, &childSigIter);
501                         dbus_message_iter_open_container(aIter, DBUS_TYPE_ARRAY,
502                             dbus_signature_iter_get_signature(&childSigIter),
503                             &childIter);
504
505                         // Skip the dict signature iter to the value type
506                         dbus_signature_iter_recurse(&childSigIter, &dictSigIter);
507                         dbus_signature_iter_next(&dictSigIter); // key type
508
509                         for (int p = 0; p < props->length; p++)
510                         {
511                             jsval propname;
512                             nsCAutoString tmpsig;
513                             JS_IdToValue(cx, props->vector[p], &propname);
514
515                             // Start the dict container
516                             DBusMessageIter dictIter;
517                             dbus_message_iter_open_container(&childIter, DBUS_TYPE_DICT_ENTRY,
518                                                              NULL, &dictIter);
519
520                             JSString *prop_string = JS_ValueToString(cx, propname);
521                             const char *cstr = NS_ConvertUTF16toUTF8(JS_GetStringChars(prop_string),
522                                                                JS_GetStringLength(prop_string)).get();
523                             // TODO: we only use strings as keys currently, although
524                             // the spec allows any basic type to be a key and we
525                             // probably *could* use the property index.
526                             dbus_message_iter_append_basic(&dictIter,
527                                                            DBUS_TYPE_STRING,
528                                                            &cstr);
529
530                             jsval propvalue;
531                             if (JS_LookupUCProperty(cx,
532                                                     js_obj,
533                                                     JS_GetStringChars(prop_string),
534                                                     JS_GetStringLength(prop_string),
535                                                     &propvalue) == JS_TRUE)
536                             {
537                                 nsIVariant *var = nsnull;
538                                 nsresult rs = xpc->JSToVariant(cx, propvalue, &var);
539                                 NS_ENSURE_SUCCESS(rs, );
540
541                                 addVariantToIter(var, &dictIter, &dictSigIter);
542                             }
543                             
544                             // Close the dict entry container
545                             dbus_message_iter_close_container(&childIter, &dictIter);
546                         }
547
548                         // Close the array container
549                         dbus_message_iter_close_container(aIter, &childIter);
550
551                         JS_DestroyIdArray(cx, props);
552                     }
553                 }
554             }
555         } else {
556           BDBLOG(("  add array from variant\n"));
557
558             // need to recurse into array
559             PRUint16 type = 0;
560             nsIID iid;
561             PRUint32 count = 0;
562             void *data_ptr = nsnull;
563
564             DBusSignatureIter aChildSigIter;
565             dbus_signature_iter_recurse(aSigIter, &aChildSigIter);
566
567             char *array_signature = dbus_signature_iter_get_signature(&aChildSigIter);
568
569             aVariant->GetAsArray(&type,
570                                  &iid,
571                                  &count,
572                                  &data_ptr);
573
574             BDBLOG(("  %s: got %d elements of type %d\n", __FUNCTION__, count, type));
575             BDBLOG(("  %s: got array signature %s\n", __FUNCTION__, array_signature));
576
577             int dbustype = dbus_signature_iter_get_current_type(&aChildSigIter);
578
579             if (typesMatch(type, dbustype))
580             {
581                 BDBLOG(("*** Yay, types match!\n"));
582
583                 DBusMessageIter arrayIter;
584                 if (dbus_message_iter_open_container(aIter, DBUS_TYPE_ARRAY,
585                                                      array_signature, &arrayIter))
586                 {
587                     addArrayDataToIter(data_ptr, 0, count, type,
588                                        &arrayIter, &aChildSigIter);
589                     dbus_message_iter_close_container(aIter, &arrayIter);
590                 }
591                 nsMemory::Free(data_ptr);
592             } else {
593                 BDBLOG(("*** Bummer, types don't match so we'll need to iterate\n"));
594
595                 DBusMessageIter arrayIter;
596                 dbus_message_iter_open_container(aIter, DBUS_TYPE_ARRAY,
597                                                  array_signature, &arrayIter);
598
599                 /* Seems stupid not to be able to iterate over the values as
600                  * nsIVariants directly, but no API to that effect seems to be
601                  * available...
602                  */
603                 for (int i = 0; i < count; i++)
604                 {
605                     nsCOMPtr<nsIWritableVariant> variant = do_CreateInstance("@mozilla.org/variant;1");
606                     
607                     switch (type)
608                     {
609 #define SET_INT(bits) \
610     case nsIDataType::VTYPE_INT ## bits: \
611         variant->SetAsInt ## bits(*(((PRInt ## bits*)data_ptr)+i)); \
612         break; \
613     case nsIDataType::VTYPE_UINT ## bits: \
614         variant->SetAsUint ## bits(*(((PRUint ## bits*)data_ptr)+i)); \
615         break;
616                         SET_INT(16)
617                         SET_INT(32)
618                         SET_INT(64)
619                         default:
620                             BDBLOG(("*** No conversion from %i to %c\n", type, dbustype));
621                             break;
622                     }
623
624                     addVariantToIter(variant, &arrayIter, &aChildSigIter);
625                 }
626
627                 dbus_message_iter_close_container(aIter, &arrayIter);
628                 nsMemory::Free(data_ptr);
629             }
630
631         }
632     }
633     else if (element_type == DBUS_TYPE_STRUCT)
634     {
635         BDBLOG(("  add struct from variant\n"));
636     }
637     else
638     {
639         BDBLOG(("  unhandled\n"));
640     }
641
642     dbus_free(element_signature);
643 }
644
645 static
646 int is_valid_path (const char *path)
647 {
648   const char *cur = path;
649   const char *prev = cur;
650   
651   if (strlen(path) == 0)
652     return FALSE;
653   
654   /* MUST begin with zero */
655   if (*cur++ != '/')
656     return FALSE;
657   
658   /* The path is guranteed to be null-terminated */
659   while (*cur != '\0')
660   {
661     /* Two slashes can't be together */
662     if (*cur == '/' && *prev == '/')
663     {
664       return FALSE;
665     } else if (!(((*cur) >= '0' && (*cur) <= '9') ||
666                  ((*cur) >= 'A' && (*cur) <= 'Z') ||
667                  ((*cur) >= 'a' && (*cur) <= 'z') ||
668                   (*cur) == '_' || (*cur) == '/')) {
669       return FALSE;
670     }
671     prev = cur;
672     cur++;
673   }
674   
675   return TRUE;
676 }
677
678
679 void addBasicTypeToIter(nsIVariant *aVariant, DBusMessageIter *aIter, int aDBusType)
680 {
681
682     PRUint16 dataType;
683     aVariant->GetDataType(&dataType);
684
685     /* If we got passed an nsISupports, query the variant iface from it and recurse */
686     if (dataType == nsIDataType::VTYPE_INTERFACE_IS)
687     {
688         nsCOMPtr<nsISupports> is;
689         nsIID *iid;
690         if (NS_FAILED(aVariant->GetAsInterface(&iid, getter_AddRefs(is))))
691             return;
692
693         nsCOMPtr<nsIVariant> myVariant = do_QueryInterface(is);
694         if (myVariant)
695             addBasicTypeToIter(myVariant, aIter, aDBusType);
696
697         return;
698     }
699
700     switch (aDBusType)
701     {
702         case DBUS_TYPE_BOOLEAN:
703         {
704             PRBool val;
705             if (NS_FAILED(aVariant->GetAsBool(&val)))
706               return;
707             BDBLOG(("  arg       : BOOLEAN %s\n", val ? "true" : "false"));
708             dbus_message_iter_append_basic(aIter,
709                                            aDBusType,
710                                            &val);
711             break;
712         }
713         case DBUS_TYPE_BYTE:
714         case DBUS_TYPE_INT16:
715         case DBUS_TYPE_UINT16:
716         case DBUS_TYPE_INT32:
717         case DBUS_TYPE_UINT32:
718         {
719             PRUint32 val;
720             if (NS_FAILED(aVariant->GetAsUint32(&val)))
721               return;
722
723             BDBLOG(("  arg       : INT(8|16|32) (%c) %d:%d\n", aDBusType, dataType, val));
724             dbus_message_iter_append_basic(aIter,
725                                            aDBusType,
726                                            &val);
727             break;
728         }
729         case DBUS_TYPE_INT64:
730         {
731             PRInt64 val = 0;
732             if (NS_FAILED(aVariant->GetAsInt64(&val)))
733                 return;
734             BDBLOG(("  arg       : INT64 %lld\n", val));
735             dbus_message_iter_append_basic(aIter,
736                                            aDBusType,
737                                            &val);
738             break;
739         }
740         case DBUS_TYPE_UINT64:
741         {
742             PRUint64 val;
743             if (NS_FAILED(aVariant->GetAsUint64(&val)))
744                 return;
745             BDBLOG(("  arg       : UINT64 %llu\n", val));
746             dbus_message_iter_append_basic(aIter,
747                                            aDBusType,
748                                            &val);
749             break;
750         }
751         case DBUS_TYPE_DOUBLE:
752         {
753             double val;
754             if (NS_FAILED(aVariant->GetAsDouble(&val)))
755                 return;
756             BDBLOG(("  arg       : DOUBLE (%c) %f\n", aDBusType, val));
757             dbus_message_iter_append_basic(aIter,
758                                            aDBusType,
759                                            &val);
760             break;
761         }
762         case DBUS_TYPE_STRING:
763         case DBUS_TYPE_OBJECT_PATH:
764         case DBUS_TYPE_SIGNATURE:
765         {
766             /* FIXME - handle utf-8 conversion */
767             nsCAutoString val;
768             const char *val_ptr;
769             if (NS_FAILED(aVariant->GetAsAUTF8String(val)))
770                 return;
771
772             val_ptr = PromiseFlatCString(val).get();
773             BDBLOG(("  arg       : STRING '%s'\n", val_ptr));
774             if (aDBusType == DBUS_TYPE_OBJECT_PATH
775              && !is_valid_path(val_ptr))
776                 return;
777
778             dbus_message_iter_append_basic(aIter, aDBusType, &val_ptr);
779             break;
780         }
781         default:
782         {
783             BDBLOG(("  addBasicTypeToIter: unhandled DBus type %d!\n", aDBusType));
784             break;
785         }
786     }
787 }
788
789 void addArrayDataToIter(void *data_ptr, PRUint32 start, PRUint32 count, PRUint16 type, DBusMessageIter *aIter, DBusSignatureIter *aSigIter)
790 {
791     int aDBusType = dbus_signature_iter_get_current_type(aSigIter);
792     BDBLOG(("addArrayDataToIter: appending %d elements of type %d '%c'\n", count, type, aDBusType));
793
794     switch (type)
795     {
796 #define ADD_DATA \
797             for (int i = start; i < count; i++) \
798                 dbus_message_iter_append_basic(aIter, aDBusType, data+i)
799         case nsIDataType::VTYPE_INT8:
800         case nsIDataType::VTYPE_UINT8:
801         {
802             char *data = (char *)data_ptr;
803             ADD_DATA;
804             break;
805         }
806         case nsIDataType::VTYPE_INT16:
807         case nsIDataType::VTYPE_UINT16:
808         {
809             PRInt16 *data = (PRInt16 *)data_ptr;
810             ADD_DATA;
811             break;
812         }
813         case nsIDataType::VTYPE_INT32:
814         case nsIDataType::VTYPE_UINT32:
815         {
816             PRInt32 *data = (PRInt32 *)data_ptr;
817             ADD_DATA;
818             break;
819         }
820         case nsIDataType::VTYPE_INT64:
821         case nsIDataType::VTYPE_UINT64:
822         {
823             PRInt64 *data = (PRInt64 *)data_ptr;
824             ADD_DATA;
825             break;
826         }
827         case nsIDataType::VTYPE_DOUBLE:
828         {
829             double *data = (double *)data_ptr;
830             ADD_DATA;
831             break;
832         }
833         case nsIDataType::VTYPE_WCHAR_STR:
834         {
835             PRUnichar **data = (PRUnichar **)data_ptr;
836             for (int i = start; i < count; i++)
837             {
838                 const char *val_ptr;
839                 nsCAutoString val = NS_ConvertUTF16toUTF8(data[i]);
840
841                 val_ptr = PromiseFlatCString(val).get();
842                 BDBLOG(("  arg       : STRING '%s'\n", val_ptr));
843                 if (aDBusType == DBUS_TYPE_OBJECT_PATH
844                  && !is_valid_path(val_ptr))
845                     return;
846
847                 dbus_message_iter_append_basic(aIter, aDBusType, &val_ptr);
848             }
849             break;
850         }
851         case nsIDataType::VTYPE_INTERFACE_IS:
852         {
853             nsISupports **data = (nsISupports **)data_ptr;
854             for (int i = start; i < count; i++)
855             {
856                 nsCOMPtr<nsIVariant> data_variant = do_QueryInterface(data[i]);
857                 if (data_variant)
858                     addVariantToIter(data_variant, aIter, aSigIter);
859                 else
860                     BDBLOG(("  interface not nsIVariant\n"));
861             }
862             break;
863         }
864         default:
865         {
866             BDBLOG(("addArrayDataToIter: unhandled array data type %d\n", type));
867             break;
868         }
869 #undef ADD_DATA
870     }
871 }
872
873 void
874 addJSValueToIter(JSContext *cx, jsval *aValue, DBusMessageIter *aIter, DBusSignatureIter *aSigIter)
875 {
876
877     int dbusType = dbus_signature_iter_get_current_type(aSigIter);
878
879     BDBLOG(("%s(%s, %c, %s)\n", __FUNCTION__,
880             JS_GetTypeName(cx, JS_TypeOfValue(cx, *aValue)),
881             dbusType, dbus_signature_iter_get_signature(aSigIter)));
882
883     // Using the expected type instead of the actual allows autoconversion
884     switch (dbusType)
885     {
886         case DBUS_TYPE_BOOLEAN:
887         {
888             JSBool b = JS_FALSE;
889             if (JS_ValueToBoolean(cx, *aValue, &b))
890             {
891                 dbus_message_iter_append_basic(aIter, DBUS_TYPE_BOOLEAN, &b);
892             }
893             else
894             {
895                 BDBLOG(("%s(): Could not fetch boolean from jsvalue\n", __FUNCTION__));
896             }
897             
898             
899             break;
900         }
901         case DBUS_TYPE_BYTE:
902         case DBUS_TYPE_INT16:
903         case DBUS_TYPE_UINT16:
904         case DBUS_TYPE_INT32:
905         case DBUS_TYPE_UINT32:
906         case DBUS_TYPE_INT64:
907         case DBUS_TYPE_UINT64:
908         case DBUS_TYPE_DOUBLE:
909         {
910             jsdouble d = 0;
911             
912             if (JS_ValueToNumber(cx, *aValue, &d))
913             {
914                 BDBLOG(("%s(%f)\n", __FUNCTION__, d));
915                 dbus_message_iter_append_basic(aIter, dbusType, &d);
916             }
917             else
918             {
919                 BDBLOG(("%s(): Could not fetch number from jsvalue\n", __FUNCTION__));
920             }
921
922             break;
923         }
924         case DBUS_TYPE_STRING:
925         case DBUS_TYPE_OBJECT_PATH:
926         case DBUS_TYPE_SIGNATURE:
927         {
928             JSString *prop_string = JS_ValueToString(cx, *aValue);
929             const char *cstr = NS_ConvertUTF16toUTF8(JS_GetStringChars(prop_string),
930                                                      JS_GetStringLength(prop_string)).get();
931             dbus_message_iter_append_basic(aIter, dbusType, &cstr);
932             break;
933         }
934         case DBUS_TYPE_ARRAY:
935         {
936             if (JSVAL_IS_OBJECT(*aValue) && JS_IsArrayObject(cx, JSVAL_TO_OBJECT(*aValue)))
937             {
938                 // Arrays are simply converted to variants and pushed to the
939                 // variant code path
940                 nsresult rv;
941                 nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID(), &rv));
942                 if(NS_FAILED(rv))
943                     return;
944                 BDBLOG(("    got nsIXPConnect\n"));
945
946                 nsIVariant *var = nsnull;
947                 nsresult rs = xpc->JSToVariant(cx, *aValue, &var);
948                 NS_ENSURE_SUCCESS(rs, );
949
950                 addVariantToIter(var, aIter, aSigIter);
951             }
952             
953             break;
954         }
955         default:
956             BDBLOG(("Don't know how to convert type '%c'\n", dbus_signature_iter_get_current_type(aSigIter)));
957             break;
958    }
959 }
960
961 void getJSValueFromIter(JSContext* cx, DBusMessageIter *aIter, int aDBusType, jsval *v)
962 {
963
964     BDBLOG(("%s(%c)\n", __FUNCTION__, aDBusType));
965
966
967     switch (aDBusType)
968     {
969         case DBUS_TYPE_STRING:
970         case DBUS_TYPE_OBJECT_PATH:
971         case DBUS_TYPE_SIGNATURE:
972         {
973             char *val = nsnull;
974             dbus_message_iter_get_basic(aIter, &val);
975             if (val != nsnull)
976             {
977                 JSString *str = JS_NewStringCopyN(cx, val, strlen(val));
978                 *v = STRING_TO_JSVAL(str);
979             }
980             break;
981         }
982         case DBUS_TYPE_BYTE:
983         case DBUS_TYPE_INT16:
984         case DBUS_TYPE_UINT16:
985         case DBUS_TYPE_INT32:
986         case DBUS_TYPE_UINT32:
987         case DBUS_TYPE_INT64:
988         case DBUS_TYPE_UINT64:
989         {
990             dbus_uint64_t val = 0;
991             dbus_message_iter_get_basic(aIter, &val);
992             if (!JS_NewNumberValue(cx, (jsdouble)val, v))
993             {
994                 BDBLOG(("%s: Number conversion from %c failed\n", __FUNCTION__,
995                        aDBusType));
996             }
997             break;
998         }
999         case DBUS_TYPE_DOUBLE:
1000         {
1001             jsdouble val = 0.0;
1002             dbus_message_iter_get_basic(aIter, &val);
1003             if (!JS_NewNumberValue(cx, val, v))
1004             {
1005                 BDBLOG(("%s: Number conversion from %c failed\n", __FUNCTION__,
1006                        aDBusType));
1007             }
1008             break;
1009         }
1010         case DBUS_TYPE_ARRAY:
1011         {
1012             // FIXME: implement!
1013             // FIXME: Dicts
1014
1015             nsTArray<jsval> elems;
1016             
1017             // recurse to container
1018             DBusMessageIter arrayIter;
1019             dbus_message_iter_recurse(aIter, &arrayIter);
1020             
1021             // iterate over array elements
1022             do
1023             {
1024                 jsval cv;
1025                 BDBLOG(("arg type: %c\n", dbus_message_iter_get_arg_type(&arrayIter)));
1026                 getJSValueFromIter(cx,
1027                                    &arrayIter,
1028                                    dbus_message_iter_get_arg_type(&arrayIter),
1029                                    &cv);
1030                 elems.AppendElement(cv);
1031                 
1032             } while (dbus_message_iter_next(&arrayIter));
1033
1034             // Create an Array object with the elements
1035             JSObject *array = JS_NewArrayObject(cx, elems.Length(), elems.Elements());
1036             *v = OBJECT_TO_JSVAL(array);
1037             break;
1038         }
1039         case DBUS_TYPE_STRUCT:
1040         default:
1041         {
1042             BDBLOG(("%s: Unhandled type %c\n", __FUNCTION__, aDBusType));
1043         }
1044     }
1045
1046     return;
1047 }
1048
1049 already_AddRefed<nsIWritableVariant> getVariantFromIter(DBusMessageIter *aIter, int aDBusType)
1050 {
1051     nsCOMPtr<nsIWritableVariant> variant = do_CreateInstance("@mozilla.org/variant;1");
1052     nsIWritableVariant *retval;
1053
1054     switch (aDBusType)
1055     {
1056         case DBUS_TYPE_BOOLEAN:
1057         {
1058             PRUint32 val = 0;
1059             BDBLOG(("    arg type BOOLEAN: "));
1060             dbus_message_iter_get_basic(aIter, &val);
1061             BDBLOG(("%d\n", val));
1062             variant->SetAsBool(val);
1063             break;
1064         }
1065
1066         case DBUS_TYPE_BYTE:
1067         case DBUS_TYPE_INT16:
1068         case DBUS_TYPE_UINT16:
1069         case DBUS_TYPE_INT32:
1070         case DBUS_TYPE_UINT32:
1071         {
1072             PRUint32 val = 0;
1073             BDBLOG(("    arg type INT: "));
1074             dbus_message_iter_get_basic(aIter, &val);
1075             BDBLOG(("%d\n", val));
1076             variant->SetAsUint32(val);
1077             break;
1078         }
1079         case DBUS_TYPE_INT64:
1080         {
1081             PRInt64 val;
1082             BDBLOG(("    arg type INT64: "));
1083             dbus_message_iter_get_basic(aIter, &val);
1084             BDBLOG(("%lld\n", val));
1085             variant->SetAsInt64(val);
1086             break;
1087         }
1088         case DBUS_TYPE_UINT64:
1089         {
1090             PRUint64 val;
1091             BDBLOG(("    arg type UINT64: "));
1092             dbus_message_iter_get_basic(aIter, &val);
1093             BDBLOG(("%llu\n", val));
1094             variant->SetAsUint64(val);
1095             break;
1096         }
1097         case DBUS_TYPE_DOUBLE:
1098         {
1099             double val;
1100             BDBLOG(("    arg type DOUBLE: "));
1101             dbus_message_iter_get_basic(aIter, &val);
1102             BDBLOG(("%f\n", val));
1103             variant->SetAsDouble(val);
1104             break;
1105         }
1106         case DBUS_TYPE_STRING:
1107         case DBUS_TYPE_OBJECT_PATH:
1108         case DBUS_TYPE_SIGNATURE:
1109         {
1110             const char *tmp;
1111             BDBLOG(("    arg type STRING/OBJECT_PATH/SIGNATURE: "));
1112             dbus_message_iter_get_basic(aIter, &tmp);
1113             nsDependentCString val(tmp);
1114             BDBLOG(("\"%s\"\n", PromiseFlatCString(val).get()));
1115             variant->SetAsAUTF8String(val);
1116             break;
1117         }
1118         case DBUS_TYPE_ARRAY:
1119         {
1120             if (dbus_message_iter_get_element_type(aIter) == DBUS_TYPE_DICT_ENTRY)
1121             {
1122                 BDBLOG(("    arg type ARRAY with DICT_ENTRY\n"));
1123
1124                 // try to get a JS context (code borrowed from xpcsample1.cpp)
1125
1126                 // get the xpconnect service
1127                 nsresult rv;
1128                 nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID(), &rv));
1129                 if(NS_FAILED(rv))
1130                     return nsnull;
1131                 BDBLOG(("    got nsIXPConnect\n"));
1132
1133                 // get the xpconnect native call context
1134                 // FIXME: this doesn't work for signals since there's no
1135                 // active context there...
1136                 nsAXPCNativeCallContext *callContext = nsnull;
1137                 xpc->GetCurrentNativeCallContext(&callContext);
1138                 if(!callContext)
1139                 {
1140                 BDBLOG(("    callContext :(\n"));
1141                     return nsnull;
1142                 }
1143                 // Get JSContext of current call
1144                 JSContext* cx;
1145                 rv = callContext->GetJSContext(&cx);
1146                 if(NS_FAILED(rv) || !cx)
1147                     return nsnull;
1148                 BDBLOG(("    got JSContext\n"));
1149
1150                 // Construct a JS Object
1151                 JSObject *obj = JS_NewObject(cx, nsnull, nsnull, nsnull);
1152                 
1153                 // Set the properties
1154                 DBusMessageIter array_iter;
1155                 dbus_message_iter_recurse(aIter, &array_iter);
1156                 
1157                 do
1158                 {
1159                     DBusMessageIter dict_iter;
1160                     char *key = nsnull;
1161                     dbus_message_iter_recurse(&array_iter, &dict_iter);
1162                     dbus_message_iter_get_basic(&dict_iter, &key);
1163                     BDBLOG(("    found key %s\n", key ? key : "null"));
1164                     dbus_message_iter_next(&dict_iter);
1165                     int value_type = dbus_message_iter_get_arg_type(&dict_iter);
1166                     BDBLOG(("    found value type %c\n", value_type));
1167                     jsval v;
1168                     getJSValueFromIter(cx, &dict_iter, value_type, &v);
1169                     JS_SetProperty(cx, obj, key, &v);
1170                     
1171                 } while (dbus_message_iter_next(&array_iter));
1172                 
1173                 // Convert to variant and return
1174                 nsIVariant *var = nsnull;
1175                 nsresult rs = xpc->JSToVariant(cx, OBJECT_TO_JSVAL(obj), &var);
1176                 variant->SetFromVariant(var);
1177                 var = nsnull;
1178                 NS_ADDREF(retval = variant);
1179                 
1180                 return retval;
1181             }
1182             else
1183             {
1184         
1185                 DBusMessageIter array_iter;
1186                 nsCOMPtr<nsIMutableArray> items;
1187                 PRUint32 item_count;
1188
1189                 BDBLOG(("    arg type ARRAY\n"));
1190                 dbus_message_iter_recurse(aIter, &array_iter);
1191                 items = getArrayFromIter(&array_iter);
1192                 items->GetLength(&item_count);
1193                 BDBLOG(("    array: %d items\n", item_count));
1194
1195                 nsIVariant **item_array = new nsIVariant*[item_count];
1196                 for (int i = 0; i < item_count; i++)
1197                 {
1198                     nsCOMPtr<nsIVariant> item = do_QueryElementAt(items, i);
1199                     item_array[i] = item;
1200                     NS_ADDREF(item);
1201                 }
1202                 variant->SetAsArray(nsIDataType::VTYPE_INTERFACE_IS,
1203                                     &NS_GET_IID(nsIVariant),
1204                                     item_count,
1205                                     item_array);
1206                 for (int i = 0; i < item_count; i++)
1207                     NS_RELEASE(item_array[i]);
1208                 delete[] item_array;
1209             }
1210             break;
1211         }
1212         default:
1213         {
1214             BDBLOG(("    arg type '%c' (%d)\n", aDBusType, aDBusType));
1215             return nsnull;
1216         }
1217     }
1218
1219     NS_ADDREF(retval = variant);
1220     return retval;
1221 }
1222
1223 already_AddRefed<nsIMutableArray> getArrayFromIter(DBusMessageIter *aIter)
1224 {
1225     int current_type;
1226     nsCOMPtr<nsIMutableArray> array = do_CreateInstance("@mozilla.org/array;1");
1227     nsIMutableArray *retval;
1228
1229     BDBLOG(("  ++ enter getArrayFromIter\n"));
1230
1231     while ((current_type = dbus_message_iter_get_arg_type(aIter)) != DBUS_TYPE_INVALID)
1232     {
1233         nsCOMPtr<nsIWritableVariant> variant = getVariantFromIter(aIter, current_type);
1234         if (variant)
1235             array->AppendElement(variant, PR_FALSE);
1236         else
1237             BDBLOG(("    arg type '%c' (%d) not handled\n", current_type, current_type));
1238
1239         dbus_message_iter_next(aIter);
1240     }
1241  
1242     NS_ADDREF(retval = array);
1243     BDBLOG(("  ++ leave getArrayFromIter\n"));
1244     return retval;
1245 }
1246
1247 /* vim: set cindent ts=4 et sw=4: */