/* vim:set ts=2 sw=2 et cindent: */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla IPC. * * The Initial Developer of the Original Code is IBM Corporation. * Portions created by the Initial Developer are Copyright (C) 2004 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Darin Fisher * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include #include "ipcDConnectService.h" #include "ipcMessageWriter.h" #include "ipcMessageReader.h" #include "ipcLog.h" #include "nsServiceManagerUtils.h" #include "nsIInterfaceInfo.h" #include "nsIInterfaceInfoManager.h" #include "nsAutoPtr.h" #include "nsString.h" #include "nsVoidArray.h" #include "nsCRT.h" #include "xptcall.h" // XXX TODO: // 1. add a client observer and prune mInstances when a peer disconnects // 2. add thread affinity field to SETUP messages // 3. support array parameters //----------------------------------------------------------------------------- #define DCONNECT_IPC_TARGETID \ { /* 43ca47ef-ebc8-47a2-9679-a4703218089f */ \ 0x43ca47ef, \ 0xebc8, \ 0x47a2, \ {0x96, 0x79, 0xa4, 0x70, 0x32, 0x18, 0x08, 0x9f} \ } static const nsID kDConnectTargetID = DCONNECT_IPC_TARGETID; //----------------------------------------------------------------------------- #define DCON_WAIT_TIMEOUT PR_INTERVAL_NO_TIMEOUT // used elsewhere like nsAtomTable to safely represent the integral value // of an address. typedef unsigned long PtrBits; //----------------------------------------------------------------------------- // // +--------------------------------+ // | opcode : 1 byte | // +--------------------------------+ // | flags : 1 byte | // +--------------------------------+ // . . // . variable payload . // . . // +--------------------------------+ // // dconnect major opcodes #define DCON_OP_SETUP 1 #define DCON_OP_RELEASE 2 #define DCON_OP_INVOKE 3 #define DCON_OP_SETUP_REPLY 4 #define DCON_OP_INVOKE_REPLY 5 // dconnect minor opcodes for DCON_OP_SETUP #define DCON_OP_SETUP_NEW_INST_CLASSID 1 #define DCON_OP_SETUP_NEW_INST_CONTRACTID 2 #define DCON_OP_SETUP_GET_SERV_CLASSID 3 #define DCON_OP_SETUP_GET_SERV_CONTRACTID 4 #define DCON_OP_SETUP_QUERY_INTERFACE 5 // dconnect minor opcodes for RELEASE // dconnect minor opcodes for INVOKE struct DConnectOp { PRUint8 opcode_major; PRUint8 opcode_minor; PRUint32 request_index; // initialized with NewRequestIndex }; typedef class DConnectInstance* DConAddr; // SETUP structs struct DConnectSetup : DConnectOp { nsID iid; }; struct DConnectSetupClassID : DConnectSetup { nsID classid; }; struct DConnectSetupContractID : DConnectSetup { char contractid[1]; // variable length }; struct DConnectSetupQueryInterface : DConnectSetup { DConAddr instance; }; // SETUP_REPLY struct struct DConnectSetupReply : DConnectOp { DConAddr instance; nsresult status; }; // RELEASE struct struct DConnectRelease : DConnectOp { DConAddr instance; }; // INVOKE struct struct DConnectInvoke : DConnectOp { DConAddr instance; PRUint16 method_index; // followed by an array of in-param blobs }; // INVOKE_REPLY struct struct DConnectInvokeReply : DConnectOp { nsresult result; // followed by an array of out-param blobs }; //----------------------------------------------------------------------------- static ipcDConnectService *gDConnect; //----------------------------------------------------------------------------- static nsresult SetupPeerInstance(PRUint32 aPeerID, DConnectSetup *aMsg, PRUint32 aMsgLen, void **aInstancePtr); //----------------------------------------------------------------------------- // A wrapper class holding an instance to an in-process XPCOM object. class DConnectInstance { public: DConnectInstance(PRUint32 peer, nsIInterfaceInfo *iinfo, nsISupports *instance) : mPeer(peer) , mIInfo(iinfo) , mInstance(instance) {} nsISupports *RealInstance() { return mInstance; } nsIInterfaceInfo *InterfaceInfo() { return mIInfo; } private: PRUint32 mPeer; // peer process "owning" this instance nsCOMPtr mIInfo; nsCOMPtr mInstance; }; static void DeleteWrappers(nsVoidArray &wrappers) { for (PRInt32 i=0; iDeleteInstance((DConnectInstance *) wrappers[i]); } //----------------------------------------------------------------------------- static nsresult SerializeParam(ipcMessageWriter &writer, const nsXPTType &t, const nsXPTCMiniVariant &v) { switch (t.TagPart()) { case nsXPTType::T_I8: case nsXPTType::T_U8: writer.PutInt8(v.val.u8); break; case nsXPTType::T_I16: case nsXPTType::T_U16: writer.PutInt16(v.val.u16); break; case nsXPTType::T_I32: case nsXPTType::T_U32: writer.PutInt32(v.val.u32); break; case nsXPTType::T_I64: case nsXPTType::T_U64: writer.PutBytes(&v.val.u64, sizeof(PRUint64)); break; case nsXPTType::T_FLOAT: writer.PutBytes(&v.val.f, sizeof(float)); break; case nsXPTType::T_DOUBLE: writer.PutBytes(&v.val.d, sizeof(double)); break; case nsXPTType::T_BOOL: writer.PutBytes(&v.val.b, sizeof(PRBool)); break; case nsXPTType::T_CHAR: writer.PutBytes(&v.val.c, sizeof(char)); break; case nsXPTType::T_WCHAR: writer.PutBytes(&v.val.wc, sizeof(PRUnichar)); break; case nsXPTType::T_IID: writer.PutBytes(v.val.p, sizeof(nsID)); break; case nsXPTType::T_CHAR_STR: { int len = strlen((const char *) v.val.p); writer.PutInt32(len); writer.PutBytes(v.val.p, len); } break; case nsXPTType::T_WCHAR_STR: { int len = 2 * nsCRT::strlen((const PRUnichar *) v.val.p); writer.PutInt32(len); writer.PutBytes(v.val.p, len); } break; case nsXPTType::T_INTERFACE: case nsXPTType::T_INTERFACE_IS: NS_NOTREACHED("this should be handled elsewhere"); return NS_ERROR_UNEXPECTED; case nsXPTType::T_ASTRING: case nsXPTType::T_DOMSTRING: { const nsAString *str = (const nsAString *) v.val.p; PRUint32 len = 2 * str->Length(); nsAString::const_iterator begin; const PRUnichar *data = str->BeginReading(begin).get(); writer.PutInt32(len); writer.PutBytes(data, len); } break; case nsXPTType::T_UTF8STRING: case nsXPTType::T_CSTRING: { const nsACString *str = (const nsACString *) v.val.p; PRUint32 len = str->Length(); nsACString::const_iterator begin; const char *data = str->BeginReading(begin).get(); writer.PutInt32(len); writer.PutBytes(data, len); } break; case nsXPTType::T_ARRAY: LOG(("array types are not yet supported\n")); return NS_ERROR_NOT_IMPLEMENTED; case nsXPTType::T_VOID: case nsXPTType::T_PSTRING_SIZE_IS: case nsXPTType::T_PWSTRING_SIZE_IS: default: LOG(("unexpected parameter type\n")); return NS_ERROR_UNEXPECTED; } return NS_OK; } static nsresult DeserializeParam(ipcMessageReader &reader, const nsXPTType &t, nsXPTCVariant &v) { // defaults v.ptr = nsnull; v.type = t; v.flags = 0; switch (t.TagPart()) { case nsXPTType::T_I8: case nsXPTType::T_U8: v.val.u8 = reader.GetInt8(); break; case nsXPTType::T_I16: case nsXPTType::T_U16: v.val.u16 = reader.GetInt16(); break; case nsXPTType::T_I32: case nsXPTType::T_U32: v.val.u32 = reader.GetInt32(); break; case nsXPTType::T_I64: case nsXPTType::T_U64: reader.GetBytes(&v.val.u64, sizeof(v.val.u64)); break; case nsXPTType::T_FLOAT: reader.GetBytes(&v.val.f, sizeof(v.val.f)); break; case nsXPTType::T_DOUBLE: reader.GetBytes(&v.val.d, sizeof(v.val.d)); break; case nsXPTType::T_BOOL: reader.GetBytes(&v.val.b, sizeof(v.val.b)); break; case nsXPTType::T_CHAR: reader.GetBytes(&v.val.c, sizeof(v.val.c)); break; case nsXPTType::T_WCHAR: reader.GetBytes(&v.val.wc, sizeof(v.val.wc)); break; case nsXPTType::T_IID: { nsID *buf = (nsID *) malloc(sizeof(nsID)); NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY); reader.GetBytes(buf, sizeof(nsID)); v.val.p = v.ptr = buf; v.flags = nsXPTCVariant::PTR_IS_DATA | nsXPTCVariant::VAL_IS_ALLOCD; } break; case nsXPTType::T_CHAR_STR: { PRUint32 len = reader.GetInt32(); char *buf = (char *) malloc(len + 1); NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY); reader.GetBytes(buf, len); buf[len] = char(0); v.val.p = v.ptr = buf; v.flags = nsXPTCVariant::PTR_IS_DATA | nsXPTCVariant::VAL_IS_ALLOCD; } break; case nsXPTType::T_WCHAR_STR: { PRUint32 len = reader.GetInt32(); PRUnichar *buf = (PRUnichar *) malloc(len + 2); NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY); reader.GetBytes(buf, len); buf[len] = PRUnichar(0); v.val.p = v.ptr = buf; v.flags = nsXPTCVariant::PTR_IS_DATA | nsXPTCVariant::VAL_IS_ALLOCD; } break; case nsXPTType::T_INTERFACE: case nsXPTType::T_INTERFACE_IS: { reader.GetBytes(&v.ptr, sizeof(void *)); v.val.p = nsnull; v.flags = nsXPTCVariant::PTR_IS_DATA; } break; case nsXPTType::T_ASTRING: case nsXPTType::T_DOMSTRING: { PRUint32 len = reader.GetInt32(); nsString *str = new nsString(); if (!str || !(EnsureStringLength(*str, len/2))) return NS_ERROR_OUT_OF_MEMORY; PRUnichar *buf = str->BeginWriting(); reader.GetBytes(buf, len); v.val.p = v.ptr = str; v.flags = nsXPTCVariant::PTR_IS_DATA | nsXPTCVariant::VAL_IS_DOMSTR; } break; case nsXPTType::T_UTF8STRING: case nsXPTType::T_CSTRING: { PRUint32 len = reader.GetInt32(); nsCString *str = new nsCString(); if (!str || !(EnsureStringLength(*str, len))) return NS_ERROR_OUT_OF_MEMORY; char *buf = str->BeginWriting(); reader.GetBytes(buf, len); v.val.p = v.ptr = str; v.flags = nsXPTCVariant::PTR_IS_DATA; // this distinction here is pretty pointless if (t.TagPart() == nsXPTType::T_CSTRING) v.flags |= nsXPTCVariant::VAL_IS_CSTR; else v.flags |= nsXPTCVariant::VAL_IS_UTF8STR; } break; case nsXPTType::T_ARRAY: LOG(("array types are not yet supported\n")); return NS_ERROR_NOT_IMPLEMENTED; case nsXPTType::T_VOID: case nsXPTType::T_PSTRING_SIZE_IS: case nsXPTType::T_PWSTRING_SIZE_IS: default: LOG(("unexpected parameter type\n")); return NS_ERROR_UNEXPECTED; } return NS_OK; } static nsresult SetupParam(const nsXPTParamInfo &p, nsXPTCVariant &v) { const nsXPTType &t = p.GetType(); if (p.IsIn() && p.IsDipper()) { v.ptr = nsnull; switch (t.TagPart()) { case nsXPTType::T_ASTRING: case nsXPTType::T_DOMSTRING: v.ptr = new nsString(); if (!v.ptr) return NS_ERROR_OUT_OF_MEMORY; v.val.p = v.ptr; v.type = t; v.flags = nsXPTCVariant::PTR_IS_DATA | nsXPTCVariant::VAL_IS_DOMSTR; break; case nsXPTType::T_UTF8STRING: case nsXPTType::T_CSTRING: v.ptr = new nsCString(); if (!v.ptr) return NS_ERROR_OUT_OF_MEMORY; v.val.p = v.ptr; v.type = t; v.flags = nsXPTCVariant::PTR_IS_DATA | nsXPTCVariant::VAL_IS_CSTR; break; default: LOG(("unhandled dipper: type=%d\n", t.TagPart())); return NS_ERROR_UNEXPECTED; } } else if (p.IsOut()) { v.ptr = &v.val; v.type = t; v.flags = nsXPTCVariant::PTR_IS_DATA; } return NS_OK; } static void FinishParam(nsXPTCVariant &v) { if (!v.val.p) return; if (v.IsValAllocated()) free(v.val.p); else if (v.IsValInterface()) ((nsISupports *) v.val.p)->Release(); else if (v.IsValDOMString()) delete (nsAString *) v.val.p; else if (v.IsValUTF8String() || v.IsValCString()) delete (nsACString *) v.val.p; } static nsresult DeserializeResult(ipcMessageReader &reader, const nsXPTType &t, nsXPTCMiniVariant &v) { if (v.val.p == nsnull) return NS_OK; switch (t.TagPart()) { case nsXPTType::T_I8: case nsXPTType::T_U8: *((PRUint8 *) v.val.p) = reader.GetInt8(); break; case nsXPTType::T_I16: case nsXPTType::T_U16: *((PRUint16 *) v.val.p) = reader.GetInt16(); break; case nsXPTType::T_I32: case nsXPTType::T_U32: *((PRUint32 *) v.val.p) = reader.GetInt32(); break; case nsXPTType::T_I64: case nsXPTType::T_U64: reader.GetBytes(v.val.p, sizeof(PRUint64)); break; case nsXPTType::T_FLOAT: reader.GetBytes(v.val.p, sizeof(float)); break; case nsXPTType::T_DOUBLE: reader.GetBytes(v.val.p, sizeof(double)); break; case nsXPTType::T_BOOL: reader.GetBytes(v.val.p, sizeof(PRBool)); break; case nsXPTType::T_CHAR: reader.GetBytes(v.val.p, sizeof(char)); break; case nsXPTType::T_WCHAR: reader.GetBytes(v.val.p, sizeof(PRUnichar)); break; case nsXPTType::T_IID: { nsID *buf = (nsID *) nsMemory::Alloc(sizeof(nsID)); reader.GetBytes(buf, sizeof(nsID)); *((nsID **) v.val.p) = buf; } break; case nsXPTType::T_CHAR_STR: { PRUint32 len = reader.GetInt32(); char *buf = (char *) nsMemory::Alloc(len + 1); reader.GetBytes(buf, len); buf[len] = char(0); *((char **) v.val.p) = buf; } break; case nsXPTType::T_WCHAR_STR: { PRUint32 len = reader.GetInt32(); PRUnichar *buf = (PRUnichar *) nsMemory::Alloc(len + 2); reader.GetBytes(buf, len); buf[len] = PRUnichar(0); *((PRUnichar **) v.val.p) = buf; } break; case nsXPTType::T_INTERFACE: case nsXPTType::T_INTERFACE_IS: { // stub creation will be handled outside this routine. we only // deserialize the DConAddr into v.val.p temporarily. void *ptr; reader.GetBytes(&ptr, sizeof(void *)); *((void **) v.val.p) = ptr; } break; case nsXPTType::T_ASTRING: case nsXPTType::T_DOMSTRING: { PRUint32 len = reader.GetInt32(); nsAString *str = (nsAString *) v.val.p; if (!str || !(EnsureStringLength(*str, len/2))) return NS_ERROR_OUT_OF_MEMORY; nsAString::iterator begin; str->BeginWriting(begin); reader.GetBytes(begin.get(), len); } break; case nsXPTType::T_UTF8STRING: case nsXPTType::T_CSTRING: { PRUint32 len = reader.GetInt32(); nsACString *str = (nsACString *) v.val.p; if (!str || !(EnsureStringLength(*str, len))) return NS_ERROR_OUT_OF_MEMORY; nsACString::iterator begin; str->BeginWriting(begin); reader.GetBytes(begin.get(), len); } break; case nsXPTType::T_ARRAY: LOG(("array types are not yet supported\n")); return NS_ERROR_NOT_IMPLEMENTED; case nsXPTType::T_VOID: case nsXPTType::T_PSTRING_SIZE_IS: case nsXPTType::T_PWSTRING_SIZE_IS: default: LOG(("unexpected parameter type\n")); return NS_ERROR_UNEXPECTED; } return NS_OK; } //----------------------------------------------------------------------------- static PRUint32 NewRequestIndex() { static PRUint32 sRequestIndex; return ++sRequestIndex; } //----------------------------------------------------------------------------- class DConnectCompletion : public ipcIMessageObserver { public: DConnectCompletion(PRUint32 requestIndex) : mRequestIndex(requestIndex) {} // stack based only NS_IMETHOD_(nsrefcnt) AddRef() { return 1; } NS_IMETHOD_(nsrefcnt) Release() { return 1; } NS_IMETHOD QueryInterface(const nsIID &aIID, void **aInstancePtr); NS_IMETHOD OnMessageAvailable(PRUint32 aSenderID, const nsID &aTarget, const PRUint8 *aData, PRUint32 aDataLen) { const DConnectOp *op = (const DConnectOp *) aData; if ((aDataLen >= sizeof(DConnectOp)) && (op->request_index == mRequestIndex)) OnResponseAvailable(aSenderID, op, aDataLen); else gDConnect->OnMessageAvailable(aSenderID, aTarget, aData, aDataLen); return NS_OK; } virtual void OnResponseAvailable(PRUint32 sender, const DConnectOp *op, PRUint32 opLen) = 0; protected: PRUint32 mRequestIndex; }; NS_IMPL_QUERY_INTERFACE1(DConnectCompletion, ipcIMessageObserver) //----------------------------------------------------------------------------- class DConnectInvokeCompletion : public DConnectCompletion { public: DConnectInvokeCompletion(const DConnectInvoke *invoke) : DConnectCompletion(invoke->request_index) , mReply(nsnull) , mParamsLen(0) {} ~DConnectInvokeCompletion() { if (mReply) free(mReply); } void OnResponseAvailable(PRUint32 sender, const DConnectOp *op, PRUint32 opLen) { mReply = (DConnectInvokeReply *) malloc(opLen); if (!mReply) return; // out of memory memcpy(mReply, op, opLen); // the length in bytes of the parameter blob mParamsLen = opLen - sizeof(*mReply); } PRBool IsPending() const { return mReply == nsnull; } nsresult GetResult() const { return mReply->result; } const PRUint8 *Params() const { return (const PRUint8 *) (mReply + 1); } PRUint32 ParamsLen() const { return mParamsLen; } const DConnectInvokeReply *Reply() const { return mReply; } private: DConnectInvokeReply *mReply; PRUint32 mParamsLen; }; //----------------------------------------------------------------------------- #define DCONNECT_STUB_ID \ { /* 132c1f14-5442-49cb-8fe6-e60214bbf1db */ \ 0x132c1f14, \ 0x5442, \ 0x49cb, \ {0x8f, 0xe6, 0xe6, 0x02, 0x14, 0xbb, 0xf1, 0xdb} \ } static NS_DEFINE_IID(kDConnectStubID, DCONNECT_STUB_ID); // this class represents the non-local object instance. class DConnectStub : public nsXPTCStubBase { public: NS_DECL_ISUPPORTS DConnectStub(nsIInterfaceInfo *aIInfo, DConAddr aInstance, PRUint32 aPeerID) : mIInfo(aIInfo) , mMaster(nsnull) , mInstance(aInstance) , mPeerID(aPeerID) {} // return a refcounted pointer to the InterfaceInfo for this object // NOTE: on some platforms this MUST not fail or we crash! NS_IMETHOD GetInterfaceInfo(nsIInterfaceInfo **aInfo); // call this method and return result NS_IMETHOD CallMethod(PRUint16 aMethodIndex, const nsXPTMethodInfo *aInfo, nsXPTCMiniVariant *aParams); DConAddr Instance() { return mInstance; } PRUint32 PeerID() { return mPeerID; } private: NS_HIDDEN ~DConnectStub(); // returns a weak reference to a child supporting the specified interface NS_HIDDEN_(DConnectStub *) FindStubSupportingIID(const nsID &aIID); // returns true if this stub supports the specified interface NS_HIDDEN_(PRBool) SupportsIID(const nsID &aIID); private: nsCOMPtr mIInfo; nsVoidArray mChildren; // weak references (cleared by the children) DConnectStub *mMaster; // strong reference // uniquely identifies this object instance between peers. DConAddr mInstance; // the "client id" of our IPC peer. this guy owns the real object. PRUint32 mPeerID; }; static nsresult CreateStub(const nsID &iid, PRUint32 peer, DConAddr instance, DConnectStub **result) { nsresult rv; nsCOMPtr iinfo; rv = gDConnect->GetInterfaceInfo(iid, getter_AddRefs(iinfo)); if (NS_FAILED(rv)) return rv; DConnectStub *stub = new DConnectStub(iinfo, instance, peer); if (NS_UNLIKELY(!stub)) rv = NS_ERROR_OUT_OF_MEMORY; else { NS_ADDREF(stub); *result = stub; } return rv; } static nsresult SerializeInterfaceParam(ipcMessageWriter &writer, PRUint32 peer, const nsID &iid, const nsXPTType &type, const nsXPTCMiniVariant &v, nsVoidArray &wrappers) { // we create an instance wrapper, and assume that the other side will send a // RELEASE message when it no longer needs the instance wrapper. that will // usually happen after the call returns. // // XXX a lazy scheme might be better, but for now simplicity wins. // if the interface pointer references a DConnectStub corresponding // to an object in the address space of the peer, then no need to // create a new wrapper. nsISupports *obj = (nsISupports *) v.val.p; if (!obj) { // write null address writer.PutBytes(&obj, sizeof(obj)); } else { DConnectStub *stub = nsnull; nsresult rv = obj->QueryInterface(kDConnectStubID, (void **) &stub); if (NS_SUCCEEDED(rv) && (stub->PeerID() == peer)) { void *p = stub->Instance(); writer.PutBytes(&p, sizeof(p)); } else { // create instance wrapper nsCOMPtr iinfo; rv = gDConnect->GetInterfaceInfo(iid, getter_AddRefs(iinfo)); if (NS_FAILED(rv)) return rv; DConnectInstance *wrapper = nsnull; wrapper = new DConnectInstance(peer, iinfo, obj); if (!wrapper) return NS_ERROR_OUT_OF_MEMORY; if (!wrappers.AppendElement(wrapper)) { delete wrapper; return NS_ERROR_OUT_OF_MEMORY; } rv = gDConnect->StoreInstance(wrapper); if (NS_FAILED(rv)) { wrappers.RemoveElement(wrapper); delete wrapper; return rv; } // send address of the instance wrapper, and set the low bit // to indicate that this is an instance wrapper. PtrBits bits = ((PtrBits) wrapper) | 0x1; writer.PutBytes(&bits, sizeof(bits)); } NS_IF_RELEASE(stub); } return NS_OK; } DConnectStub::~DConnectStub() { // destroying stub, notify peer that we have released our reference nsresult rv; DConnectRelease msg; msg.opcode_major = DCON_OP_RELEASE; msg.opcode_minor = 0; msg.instance = mInstance; // fire off asynchronously... we don't expect any response to this message. rv = IPC_SendMessage(mPeerID, kDConnectTargetID, (const PRUint8 *) &msg, sizeof(msg)); if (NS_FAILED(rv)) NS_WARNING("failed to send RELEASE event"); if (!mMaster) { // delete each child stub for (PRInt32 i=0; i iter = mIInfo; do { if (NS_SUCCEEDED(iter->IsIID(&iid, &match)) && match) return PR_TRUE; nsCOMPtr parent; iter->GetParent(getter_AddRefs(parent)); iter = parent; } while (iter != nsnull); return PR_FALSE; } DConnectStub * DConnectStub::FindStubSupportingIID(const nsID &iid) { NS_ASSERTION(mMaster == nsnull, "this is not a master stub"); if (SupportsIID(iid)) return this; for (PRInt32 i=0; iSupportsIID(iid)) return child; } return nsnull; } NS_IMETHODIMP_(nsrefcnt) DConnectStub::AddRef() { if (mMaster) return mMaster->AddRef(); NS_ASSERT_OWNINGTHREAD("DConnectStub"); ++mRefCnt; NS_LOG_ADDREF(this, mRefCnt, "DConnectStub", sizeof(*this)); return mRefCnt; } NS_IMETHODIMP_(nsrefcnt) DConnectStub::Release() { if (mMaster) return mMaster->Release(); NS_ASSERT_OWNINGTHREAD("DConnectStub"); --mRefCnt; NS_LOG_RELEASE(this, mRefCnt, "DConnectStub"); if (mRefCnt == 0) { mRefCnt = 1; /* stabilize */ delete this; return 0; } \ return mRefCnt; } NS_IMETHODIMP DConnectStub::QueryInterface(const nsID &aIID, void **aInstancePtr) { DConnectStub *master = mMaster ? mMaster : this; // always return the master stub for nsISupports if (aIID.Equals(NS_GET_IID(nsISupports))) { *aInstancePtr = master; NS_ADDREF(master); return NS_OK; } // used to discover if this is a DConnectStub instance. if (aIID.Equals(kDConnectStubID)) { *aInstancePtr = this; NS_ADDREF_THIS(); return NS_OK; } // it might seem attractive to check this stub first for parent // interfaces, but we should instead always defer to the master // since that ensures that the result of QI is independent of // the stub on which it is called. #if 0 if (SupportsIID(aIID)) { *aInstancePtr = this; NS_ADDREF(this); return NS_OK; } #endif // does any existing stub support the requested IID? DConnectStub *stub = master->FindStubSupportingIID(aIID); if (stub) { *aInstancePtr = stub; NS_ADDREF(stub); return NS_OK; } // else, we need to query the peer object LOG(("calling QueryInterface on peer object\n")); DConnectSetupQueryInterface msg; msg.opcode_minor = DCON_OP_SETUP_QUERY_INTERFACE; msg.iid = aIID; msg.instance = mInstance; void *result; nsresult rv = SetupPeerInstance(mPeerID, &msg, sizeof(msg), &result); if (NS_FAILED(rv)) return rv; stub = (DConnectStub *) result; // add stub to the master's list of children, so we can preserve // symmetry in future QI calls. the master will delete each child // when it is destroyed. the refcount of each child is bound to // the refcount of the master. this is done to deal with code // like this: // // nsCOMPtr bar = ...; // nsIFoo *foo; // { // nsCOMPtr temp = do_QueryInterface(bar); // foo = temp; // } // foo->DoStuff(); // // while this code is not valid XPCOM (since it is using |foo| // after having called Release on it), such code is unfortunately // very common in the mozilla codebase. the assumption this code // is making is that so long as |bar| is alive, it should be valid // to access |foo| even if the code doesn't own a strong reference // to |foo|! clearly wrong, but we need to support it anyways. stub->mMaster = master; master->mChildren.AppendElement(stub); *aInstancePtr = stub; NS_ADDREF(stub); return NS_OK; } NS_IMETHODIMP DConnectStub::GetInterfaceInfo(nsIInterfaceInfo **aInfo) { NS_ADDREF(*aInfo = mIInfo); return NS_OK; } NS_IMETHODIMP DConnectStub::CallMethod(PRUint16 aMethodIndex, const nsXPTMethodInfo *aInfo, nsXPTCMiniVariant *aParams) { nsresult rv; LOG(("DConnectStub::CallMethod [methodIndex=%hu]\n", aMethodIndex)); // dump arguments PRUint8 i, paramCount = aInfo->GetParamCount(); LOG((" name=%s\n", aInfo->GetName())); LOG((" param-count=%u\n", (PRUint32) paramCount)); ipcMessageWriter writer(16 * paramCount); // INVOKE message header DConnectInvoke invoke; invoke.opcode_major = DCON_OP_INVOKE; invoke.opcode_minor = 0; invoke.request_index = NewRequestIndex(); invoke.instance = mInstance; invoke.method_index = aMethodIndex; writer.PutBytes(&invoke, sizeof(invoke)); // list of wrappers that get created during parameter serialization. if we // are unable to send the INVOKE message, then we'll clean these up. nsVoidArray wrappers; for (i=0; iGetParam(i); if (paramInfo.IsIn() && !paramInfo.IsDipper()) { const nsXPTType &type = paramInfo.GetType(); if (type.IsInterfacePointer()) { nsID iid; rv = gDConnect->GetIIDForMethodParam(mIInfo, aInfo, paramInfo, type, aMethodIndex, i, aParams, PR_FALSE, iid); if (NS_SUCCEEDED(rv)) rv = SerializeInterfaceParam(writer, mPeerID, iid, type, aParams[i], wrappers); } else rv = SerializeParam(writer, type, aParams[i]); if (NS_FAILED(rv)) return rv; } } rv = IPC_SendMessage(mPeerID, kDConnectTargetID, writer.GetBuffer(), writer.GetSize()); if (NS_FAILED(rv)) { // INVOKE message wasn't delivered; clean up wrappers DeleteWrappers(wrappers); return rv; } // now, we wait for the method call to complete. during that time, it's // possible that we'll receive other method call requests. we'll process // those while waiting for out method call to complete. it's critical that // we do so since those other method calls might need to complete before // out method call can complete! DConnectInvokeCompletion completion(&invoke); do { rv = IPC_WaitMessage(mPeerID, kDConnectTargetID, &completion, DCON_WAIT_TIMEOUT); if (NS_FAILED(rv)) { // INVOKE message wasn't received; clean up wrappers DeleteWrappers(wrappers); return rv; } } while (completion.IsPending()); rv = completion.GetResult(); if (NS_SUCCEEDED(rv) && completion.ParamsLen() > 0) { ipcMessageReader reader(completion.Params(), completion.ParamsLen()); PRUint8 i; // handle out-params and retvals: DCON_OP_INVOKE_REPLY has the data for (i=0; iGetParam(i); if (paramInfo.IsOut() || paramInfo.IsRetval()) DeserializeResult(reader, paramInfo.GetType(), aParams[i]); } // fixup any interface pointers using a second pass so we can properly // handle INTERFACE_IS referencing an IID that is an out param! for (i=0; iGetParam(i); if (aParams[i].val.p && (paramInfo.IsOut() || paramInfo.IsRetval())) { const nsXPTType &type = paramInfo.GetType(); if (type.IsInterfacePointer()) { PtrBits bits = (PtrBits) *((void **) aParams[i].val.p); if (bits & 0x1) { *((void **) aParams[i].val.p) = (void *) (bits & ~0x1); nsID iid; rv = gDConnect->GetIIDForMethodParam(mIInfo, aInfo, paramInfo, type, aMethodIndex, i, aParams, PR_FALSE, iid); if (NS_SUCCEEDED(rv)) { DConnectStub *stub; void **pptr = (void **) aParams[i].val.p; rv = CreateStub(iid, mPeerID, (DConAddr) *pptr, &stub); if (NS_SUCCEEDED(rv)) *((nsISupports **) aParams[i].val.p) = stub; } } else if (bits) { // pointer is to one of our instance wrappers. DConnectInstance *wrapper = (DConnectInstance *) aParams[i].val.p; *((void **) aParams[i].val.p) = wrapper->RealInstance(); } else { *((void **) aParams[i].val.p) = nsnull; } } } } } return rv; } //----------------------------------------------------------------------------- class DConnectSetupCompletion : public DConnectCompletion { public: DConnectSetupCompletion(const DConnectSetup *setup) : DConnectCompletion(setup->request_index) , mSetup(setup) , mStatus(NS_OK) {} void OnResponseAvailable(PRUint32 sender, const DConnectOp *op, PRUint32 opLen) { if (op->opcode_major != DCON_OP_SETUP_REPLY) { NS_NOTREACHED("unexpected response"); mStatus = NS_ERROR_UNEXPECTED; return; } const DConnectSetupReply *reply = (const DConnectSetupReply *) op; LOG(("got SETUP_REPLY: status=%x instance=%p\n", reply->status, reply->instance)); if (NS_FAILED(reply->status)) { NS_ASSERTION(!reply->instance, "non-null instance on failure"); mStatus = reply->status; } else { nsresult rv = CreateStub(mSetup->iid, sender, reply->instance, getter_AddRefs(mStub)); if (NS_FAILED(rv)) mStatus = rv; } } nsresult GetStub(void **aInstancePtr) { if (NS_FAILED(mStatus)) return mStatus; DConnectStub *stub = mStub; NS_IF_ADDREF(stub); *aInstancePtr = stub; return NS_OK; } private: const DConnectSetup *mSetup; nsresult mStatus; nsRefPtr mStub; }; // static nsresult SetupPeerInstance(PRUint32 aPeerID, DConnectSetup *aMsg, PRUint32 aMsgLen, void **aInstancePtr) { *aInstancePtr = nsnull; aMsg->opcode_major = DCON_OP_SETUP; aMsg->request_index = NewRequestIndex(); // send SETUP message, expect SETUP_REPLY nsresult rv = IPC_SendMessage(aPeerID, kDConnectTargetID, (const PRUint8 *) aMsg, aMsgLen); if (NS_FAILED(rv)) return rv; DConnectSetupCompletion completion(aMsg); // need to allow messages from other clients to be processed immediately // to avoid distributed dead locks. the completion's OnMessageAvailable // will call our default OnMessageAvailable if it receives any message // other than the one for which it is waiting. do { rv = IPC_WaitMessage(aPeerID, kDConnectTargetID, &completion, DCON_WAIT_TIMEOUT); if (NS_FAILED(rv)) break; rv = completion.GetStub(aInstancePtr); } while (NS_SUCCEEDED(rv) && *aInstancePtr == nsnull); return rv; } //----------------------------------------------------------------------------- PR_STATIC_CALLBACK(PLDHashOperator) DestroyDConnectInstance(const void *key, DConnectInstance *wrapper, void *userArg) { delete wrapper; return PL_DHASH_NEXT; } //----------------------------------------------------------------------------- ipcDConnectService::~ipcDConnectService() { // make sure we have released all instances mInstances.EnumerateRead(DestroyDConnectInstance, nsnull); mInstances.Clear(); gDConnect = nsnull; } nsresult ipcDConnectService::Init() { nsresult rv; rv = IPC_DefineTarget(kDConnectTargetID, this); if (NS_FAILED(rv)) return rv; if (!mInstances.Init()) return NS_ERROR_OUT_OF_MEMORY; mIIM = do_GetService(NS_INTERFACEINFOMANAGER_SERVICE_CONTRACTID, &rv); if (NS_FAILED(rv)) return rv; gDConnect = this; return NS_OK; } // this should be inlined nsresult ipcDConnectService::GetInterfaceInfo(const nsID &iid, nsIInterfaceInfo **result) { return mIIM->GetInfoForIID(&iid, result); } // this is adapted from the version in xpcwrappednative.cpp nsresult ipcDConnectService::GetIIDForMethodParam(nsIInterfaceInfo *iinfo, const nsXPTMethodInfo *methodInfo, const nsXPTParamInfo ¶mInfo, const nsXPTType &type, PRUint16 methodIndex, PRUint8 paramIndex, nsXPTCMiniVariant *dispatchParams, PRBool isFullVariantArray, nsID &result) { PRUint8 argnum, tag = type.TagPart(); nsresult rv; if (tag == nsXPTType::T_INTERFACE) { rv = iinfo->GetIIDForParamNoAlloc(methodIndex, ¶mInfo, &result); } else if (tag == nsXPTType::T_INTERFACE_IS) { rv = iinfo->GetInterfaceIsArgNumberForParam(methodIndex, ¶mInfo, &argnum); if (NS_FAILED(rv)) return rv; const nsXPTParamInfo& arg_param = methodInfo->GetParam(argnum); const nsXPTType& arg_type = arg_param.GetType(); // The xpidl compiler ensures this. We reaffirm it for safety. if (!arg_type.IsPointer() || arg_type.TagPart() != nsXPTType::T_IID) return NS_ERROR_UNEXPECTED; nsID *p; if (isFullVariantArray) p = (nsID *) ((nsXPTCVariant *) dispatchParams)[argnum].val.p; else p = (nsID *) dispatchParams[argnum].val.p; if (!p) return NS_ERROR_UNEXPECTED; result = *p; } else rv = NS_ERROR_UNEXPECTED; return rv; } nsresult ipcDConnectService::StoreInstance(DConnectInstance *wrapper) { return mInstances.Put(nsVoidPtrHashKey(wrapper).GetKey(), wrapper) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; } void ipcDConnectService::DeleteInstance(DConnectInstance *wrapper) { // this calls |operator delete| mInstances.Remove(nsVoidPtrHashKey(wrapper).GetKey()); } NS_IMPL_ISUPPORTS2(ipcDConnectService, ipcIDConnectService, ipcIMessageObserver) NS_IMETHODIMP ipcDConnectService::CreateInstance(PRUint32 aPeerID, const nsID &aCID, const nsID &aIID, void **aInstancePtr) { DConnectSetupClassID msg; msg.opcode_minor = DCON_OP_SETUP_NEW_INST_CLASSID; msg.iid = aIID; msg.classid = aCID; return SetupPeerInstance(aPeerID, &msg, sizeof(msg), aInstancePtr); } NS_IMETHODIMP ipcDConnectService::CreateInstanceByContractID(PRUint32 aPeerID, const char *aContractID, const nsID &aIID, void **aInstancePtr) { size_t slen = strlen(aContractID); size_t size = sizeof(DConnectSetupContractID) + slen; DConnectSetupContractID *msg = (DConnectSetupContractID *) malloc(size); NS_ENSURE_TRUE(msg, NS_ERROR_OUT_OF_MEMORY); msg->opcode_minor = DCON_OP_SETUP_NEW_INST_CONTRACTID; msg->iid = aIID; memcpy(&msg->contractid, aContractID, slen + 1); nsresult rv = SetupPeerInstance(aPeerID, msg, size, aInstancePtr); free(msg); return rv; } NS_IMETHODIMP ipcDConnectService::GetService(PRUint32 aPeerID, const nsID &aCID, const nsID &aIID, void **aInstancePtr) { DConnectSetupClassID msg; msg.opcode_minor = DCON_OP_SETUP_GET_SERV_CLASSID; msg.iid = aIID; msg.classid = aCID; return SetupPeerInstance(aPeerID, &msg, sizeof(msg), aInstancePtr); } NS_IMETHODIMP ipcDConnectService::GetServiceByContractID(PRUint32 aPeerID, const char *aContractID, const nsID &aIID, void **aInstancePtr) { size_t slen = strlen(aContractID); size_t size = sizeof(DConnectSetupContractID) + slen; DConnectSetupContractID *msg = (DConnectSetupContractID *) malloc(size); NS_ENSURE_TRUE(msg, NS_ERROR_OUT_OF_MEMORY); msg->opcode_minor = DCON_OP_SETUP_GET_SERV_CONTRACTID; msg->iid = aIID; memcpy(&msg->contractid, aContractID, slen + 1); nsresult rv = SetupPeerInstance(aPeerID, msg, size, aInstancePtr); free(msg); return rv; } //----------------------------------------------------------------------------- NS_IMETHODIMP ipcDConnectService::OnMessageAvailable(PRUint32 aSenderID, const nsID &aTarget, const PRUint8 *aData, PRUint32 aDataLen) { const DConnectOp *op = (const DConnectOp *) aData; switch (op->opcode_major) { case DCON_OP_SETUP: OnSetup(aSenderID, (const DConnectSetup *) aData, aDataLen); break; case DCON_OP_RELEASE: OnRelease(aSenderID, (const DConnectRelease *) aData); break; case DCON_OP_INVOKE: OnInvoke(aSenderID, (const DConnectInvoke *) aData, aDataLen); break; default: NS_NOTREACHED("unknown opcode major"); } return NS_OK; } //----------------------------------------------------------------------------- void ipcDConnectService::OnSetup(PRUint32 peer, const DConnectSetup *setup, PRUint32 opLen) { nsISupports *instance = nsnull; nsresult rv = NS_ERROR_FAILURE; switch (setup->opcode_minor) { // CreateInstance case DCON_OP_SETUP_NEW_INST_CLASSID: { const DConnectSetupClassID *setupCI = (const DConnectSetupClassID *) setup; nsCOMPtr compMgr; rv = NS_GetComponentManager(getter_AddRefs(compMgr)); if (NS_SUCCEEDED(rv)) rv = compMgr->CreateInstance(setupCI->classid, nsnull, setupCI->iid, (void **) &instance); break; } // CreateInstanceByContractID case DCON_OP_SETUP_NEW_INST_CONTRACTID: { const DConnectSetupContractID *setupCI = (const DConnectSetupContractID *) setup; nsCOMPtr compMgr; rv = NS_GetComponentManager(getter_AddRefs(compMgr)); if (NS_SUCCEEDED(rv)) rv = compMgr->CreateInstanceByContractID(setupCI->contractid, nsnull, setupCI->iid, (void **) &instance); break; } // GetService case DCON_OP_SETUP_GET_SERV_CLASSID: { const DConnectSetupClassID *setupCI = (const DConnectSetupClassID *) setup; nsCOMPtr svcMgr; rv = NS_GetServiceManager(getter_AddRefs(svcMgr)); if (NS_SUCCEEDED(rv)) rv = svcMgr->GetService(setupCI->classid, setupCI->iid, (void **) &instance); break; } // GetServiceByContractID case DCON_OP_SETUP_GET_SERV_CONTRACTID: { const DConnectSetupContractID *setupCI = (const DConnectSetupContractID *) setup; nsCOMPtr svcMgr; rv = NS_GetServiceManager(getter_AddRefs(svcMgr)); if (NS_SUCCEEDED(rv)) rv = svcMgr->GetServiceByContractID(setupCI->contractid, setupCI->iid, (void **) &instance); break; } // QueryInterface case DCON_OP_SETUP_QUERY_INTERFACE: { const DConnectSetupQueryInterface *setupQI = (const DConnectSetupQueryInterface *) setup; // make sure we've been sent a valid wrapper if (!mInstances.Get(nsVoidPtrHashKey(setupQI->instance).GetKey(), nsnull)) { NS_NOTREACHED("instance wrapper not found"); rv = NS_ERROR_INVALID_ARG; } else rv = setupQI->instance->RealInstance()->QueryInterface(setupQI->iid, (void **) &instance); break; } default: NS_NOTREACHED("unexpected minor opcode"); rv = NS_ERROR_UNEXPECTED; break; } // now, create instance wrapper, and store it in our instances set. // this allows us to keep track of object references held on behalf of a // particular peer. we can use this information to cleanup after a peer // that disconnects without sending RELEASE messages for its objects. DConnectInstance *wrapper = nsnull; if (NS_SUCCEEDED(rv)) { nsCOMPtr iinfo; rv = gDConnect->GetInterfaceInfo(setup->iid, getter_AddRefs(iinfo)); if (NS_SUCCEEDED(rv)) { wrapper = new DConnectInstance(peer, iinfo, instance); if (!wrapper) rv = NS_ERROR_OUT_OF_MEMORY; else rv = StoreInstance(wrapper); } } if (NS_FAILED(rv) && instance) NS_RELEASE(instance); DConnectSetupReply msg; msg.opcode_major = DCON_OP_SETUP_REPLY; msg.opcode_minor = 0; msg.request_index = setup->request_index; msg.instance = wrapper; msg.status = rv; // fire off SETUP_REPLY, don't wait for a response IPC_SendMessage(peer, kDConnectTargetID, (const PRUint8 *) &msg, sizeof(msg)); } void ipcDConnectService::OnRelease(PRUint32 peer, const DConnectRelease *release) { LOG(("ipcDConnectService::OnRelease [peer=%u instance=%p]\n", peer, release->instance)); #ifdef DEBUG // make sure we've been sent a valid wrapper if (!mInstances.Get(nsVoidPtrHashKey(release->instance).GetKey(), nsnull)) NS_NOTREACHED("instance wrapper not found"); #endif DeleteInstance(release->instance); } void ipcDConnectService::OnInvoke(PRUint32 peer, const DConnectInvoke *invoke, PRUint32 opLen) { LOG(("ipcDConnectService::OnInvoke [peer=%u instance=%p method=%u]\n", peer, invoke->instance, invoke->method_index)); DConnectInstance *wrapper = invoke->instance; ipcMessageReader reader((const PRUint8 *) (invoke + 1), opLen - sizeof(*invoke)); const nsXPTMethodInfo *methodInfo; nsXPTCVariant *params = nsnull; nsIInterfaceInfo *iinfo = nsnull; PRUint8 i, paramCount = 0, paramUsed = 0; nsresult rv; // make sure we've been sent a valid wrapper if (!mInstances.Get(nsVoidPtrHashKey(wrapper).GetKey(), nsnull)) { NS_NOTREACHED("instance wrapper not found"); rv = NS_ERROR_INVALID_ARG; goto end; } iinfo = wrapper->InterfaceInfo(); rv = iinfo->GetMethodInfo(invoke->method_index, &methodInfo); if (NS_FAILED(rv)) goto end; paramCount = methodInfo->GetParamCount(); params = new nsXPTCVariant[paramCount]; if (!params) goto end; // setup |params| for xptcall for (i=0; iGetParam(i); // XXX are inout params an issue? if (paramInfo.IsIn() && !paramInfo.IsDipper()) rv = DeserializeParam(reader, paramInfo.GetType(), params[i]); else rv = SetupParam(paramInfo, params[i]); if (NS_FAILED(rv)) goto end; } // fixup any interface pointers. we do this with a second pass so that // we can properly handle INTERFACE_IS. for (i=0; iGetParam(i); const nsXPTType &type = paramInfo.GetType(); if (paramInfo.IsIn() && type.IsInterfacePointer()) { PtrBits bits = (PtrBits) params[i].ptr; if (bits & 0x1) { // pointer is to a remote object. we need to build a stub. params[i].ptr = (void *) (bits & ~0x1); nsID iid; rv = GetIIDForMethodParam(iinfo, methodInfo, paramInfo, type, invoke->method_index, i, params, PR_TRUE, iid); if (NS_SUCCEEDED(rv)) { DConnectStub *stub; rv = CreateStub(iid, peer, (DConAddr) params[i].ptr, &stub); if (NS_SUCCEEDED(rv)) { params[i].val.p = params[i].ptr = stub; params[i].SetValIsInterface(); } } } else if (bits) { // pointer is to one of our instance wrappers. DConnectInstance *wrapper = (DConnectInstance *) params[i].ptr; params[i].val.p = params[i].ptr = wrapper->RealInstance(); params[i].SetValIsInterface(); } else { params[i].val.p = params[i].ptr = nsnull; params[i].SetValIsInterface(); } } } rv = NS_InvokeByIndex(wrapper->RealInstance(), invoke->method_index, paramCount, params); end: LOG(("sending INVOKE_REPLY: rv=%x\n", rv)); ipcMessageWriter writer(64); DConnectInvokeReply reply; reply.opcode_major = DCON_OP_INVOKE_REPLY; reply.opcode_minor = 0; reply.request_index = invoke->request_index; reply.result = rv; writer.PutBytes(&reply, sizeof(reply)); nsVoidArray wrappers; if (NS_SUCCEEDED(rv) && params) { // serialize out-params and retvals for (i=0; iGetParam(i); if (paramInfo.IsRetval() || paramInfo.IsOut()) { const nsXPTType &type = paramInfo.GetType(); if (type.IsInterfacePointer()) { nsID iid; rv = GetIIDForMethodParam(iinfo, methodInfo, paramInfo, type, invoke->method_index, i, params, PR_TRUE, iid); if (NS_SUCCEEDED(rv)) rv = SerializeInterfaceParam(writer, peer, iid, type, params[i], wrappers); } else rv = SerializeParam(writer, type, params[i]); if (NS_FAILED(rv)) { reply.result = rv; break; } } } } if (NS_FAILED(rv)) rv = IPC_SendMessage(peer, kDConnectTargetID, (const PRUint8 *) &reply, sizeof(reply)); else rv = IPC_SendMessage(peer, kDConnectTargetID, writer.GetBuffer(), writer.GetSize()); if (NS_FAILED(rv)) { LOG(("unable to send INVOKE_REPLY: rv=%x\n", rv)); DeleteWrappers(wrappers); } if (params) { for (i=0; i