diff options
Diffstat (limited to 'src/libs/xpcom18a4/ipc/ipcd/extensions/dconnect/src/ipcDConnectService.cpp')
-rw-r--r-- | src/libs/xpcom18a4/ipc/ipcd/extensions/dconnect/src/ipcDConnectService.cpp | 4210 |
1 files changed, 4210 insertions, 0 deletions
diff --git a/src/libs/xpcom18a4/ipc/ipcd/extensions/dconnect/src/ipcDConnectService.cpp b/src/libs/xpcom18a4/ipc/ipcd/extensions/dconnect/src/ipcDConnectService.cpp new file mode 100644 index 00000000..32565df2 --- /dev/null +++ b/src/libs/xpcom18a4/ipc/ipcd/extensions/dconnect/src/ipcDConnectService.cpp @@ -0,0 +1,4210 @@ +/* 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 <darin@meer.net> + * Dmitry A. Kuminov <dmik@innotek.de> + * + * 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 "ipcDConnectService.h" +#include "ipcMessageWriter.h" +#include "ipcMessageReader.h" +#include "ipcLog.h" + +#include "nsIServiceManagerUtils.h" +#include "nsIInterfaceInfo.h" +#include "nsIInterfaceInfoManager.h" +#include "nsIExceptionService.h" +#include "nsString.h" +#include "nsVoidArray.h" +#include "nsCRT.h" +#include "nsDeque.h" +#include "xptcall.h" + +#ifdef VBOX +# include <map> +# include <list> +# include <iprt/err.h> +# include <iprt/req.h> +# include <iprt/mem.h> +# include <iprt/time.h> +# include <iprt/thread.h> +#endif /* VBOX */ + +#if defined(DCONNECT_MULTITHREADED) + +#if !defined(DCONNECT_WITH_IPRT_REQ_POOL) +#include "nsIThread.h" +#include "nsIRunnable.h" +#endif + +#if defined(DEBUG) && !defined(DCONNECT_STATS) +#define DCONNECT_STATS +#endif + +#if defined(DCONNECT_STATS) +#include <stdio.h> +#endif + +#endif + +// XXX TODO: +// 1. add thread affinity field to SETUP messages + +//----------------------------------------------------------------------------- + +#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 + +//----------------------------------------------------------------------------- + +// +// +--------------------------------------+ +// | major opcode : 1 byte | +// +--------------------------------------+ +// | minor opcode : 1 byte | +// +--------------------------------------+ +// | flags : 2 bytes | +// +--------------------------------------+ +// . . +// . 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 + +// DCON_OP_SETUP_REPLY and DCON_OP_INVOKE_REPLY flags +#define DCON_OP_FLAGS_REPLY_EXCEPTION 0x0001 + +// Within this time all the worker threads must be terminated. +#define VBOX_XPCOM_SHUTDOWN_TIMEOUT_MS (5000) + +#pragma pack(1) + +struct DConnectOp +{ + PRUint8 opcode_major; + PRUint8 opcode_minor; + PRUint16 flags; + PRUint32 request_index; // initialized with NewRequestIndex +}; + +// 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; + // optionally followed by a specially serialized nsIException instance (see + // ipcDConnectService::SerializeException) if DCON_OP_FLAGS_REPLY_EXCEPTION is + // present in flags +}; + +// 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 if NS_SUCCEEDED(result), and + // optionally by a specially serialized nsIException instance (see + // ipcDConnectService::SerializeException) if DCON_OP_FLAGS_REPLY_EXCEPTION is + // present in flags +}; + +#pragma pack() + +//----------------------------------------------------------------------------- + +struct DConAddrPlusPtr +{ + DConAddr addr; + void *p; +}; + +//----------------------------------------------------------------------------- + +ipcDConnectService *ipcDConnectService::mInstance = nsnull; + +//----------------------------------------------------------------------------- + +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; } + PRUint32 Peer() { return mPeer; } + + DConnectInstanceKey::Key GetKey() { + const nsID *iid; + mIInfo->GetIIDShared(&iid); + return DConnectInstanceKey::Key(mPeer, mInstance, iid); + } + + NS_IMETHODIMP_(nsrefcnt) AddRef(void) + { + NS_PRECONDITION(PRInt32(mRefCnt) >= 0, "illegal refcnt"); + nsrefcnt count; + count = PR_AtomicIncrement((PRInt32*)&mRefCnt); + return count; + } + + NS_IMETHODIMP_(nsrefcnt) Release(void) + { + nsrefcnt count; + NS_PRECONDITION(0 != mRefCnt, "dup release"); + count = PR_AtomicDecrement((PRInt32 *)&mRefCnt); + if (0 == count) { + NS_PRECONDITION(PRInt32(mRefCntIPC) == 0, "non-zero IPC refcnt"); + mRefCnt = 1; /* stabilize */ + delete this; + return 0; + } + return count; + } + + // this gets called after calling AddRef() on an instance passed to the + // client over IPC in order to have a count of IPC client-related references + // separately from the overall reference count + NS_IMETHODIMP_(nsrefcnt) AddRefIPC(void) + { + NS_PRECONDITION(PRInt32(mRefCntIPC) >= 0, "illegal refcnt"); + nsrefcnt count = PR_AtomicIncrement((PRInt32*)&mRefCntIPC); + return count; + } + + // this gets called before calling Release() when DCON_OP_RELEASE is + // received from the IPC client and in other cases to balance AddRefIPC() + NS_IMETHODIMP_(nsrefcnt) ReleaseIPC(PRBool locked = PR_FALSE) + { + NS_PRECONDITION(0 != mRefCntIPC, "dup release"); + nsrefcnt count = PR_AtomicDecrement((PRInt32 *)&mRefCntIPC); + if (0 == count) { + // If the last IPC reference is released, remove this instance from the map. + // ipcDConnectService is guaranteed to still exist here + // (DConnectInstance lifetime is bound to ipcDConnectService) + nsRefPtr <ipcDConnectService> dConnect (ipcDConnectService::GetInstance()); + if (dConnect) + dConnect->DeleteInstance(this, locked); + else + NS_NOTREACHED("ipcDConnectService has gone before DConnectInstance"); + } + return count; + } + +private: + nsAutoRefCnt mRefCnt; + nsAutoRefCnt mRefCntIPC; + PRUint32 mPeer; // peer process "owning" this instance + nsCOMPtr<nsIInterfaceInfo> mIInfo; + nsCOMPtr<nsISupports> mInstance; +}; + +void +ipcDConnectService::ReleaseWrappers(nsVoidArray &wrappers, PRUint32 peer) +{ + nsAutoLock lock (mLock); + + for (PRInt32 i=0; i<wrappers.Count(); ++i) + { + DConnectInstance *wrapper = (DConnectInstance *)wrappers[i]; + if (mInstanceSet.Contains(wrapper) && wrapper->Peer() == peer) + { + wrapper->ReleaseIPC(PR_TRUE /* locked */); + wrapper->Release(); + } + } +} + +//----------------------------------------------------------------------------- + +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: + { + AssertReturn(v.val.p, NS_ERROR_INVALID_POINTER); + writer.PutBytes(v.val.p, sizeof(nsID)); + } + break; + + case nsXPTType::T_CHAR_STR: + { + if (v.val.p) + { + int len = strlen((const char *) v.val.p); + writer.PutInt32(len); + writer.PutBytes(v.val.p, len); + } + else + { + // put -1 to indicate null string + writer.PutInt32((PRUint32) -1); + } + } + break; + + case nsXPTType::T_WCHAR_STR: + { + if (v.val.p) + { + int len = 2 * nsCRT::strlen((const PRUnichar *) v.val.p); + writer.PutInt32(len); + writer.PutBytes(v.val.p, len); + } + else + { + // put -1 to indicate null string + writer.PutInt32((PRUint32) -1); + } + } + 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: + // arrays are serialized after all other params outside this routine + break; + + case nsXPTType::T_VOID: + case nsXPTType::T_PSTRING_SIZE_IS: + case nsXPTType::T_PWSTRING_SIZE_IS: + default: + LOG(("unexpected parameter type: %d\n", t.TagPart())); + 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 *) nsMemory::Alloc(sizeof(nsID)); + reader.GetBytes(buf, sizeof(nsID)); + v.val.p = buf; + v.SetValIsAllocated(); + } + break; + + case nsXPTType::T_CHAR_STR: + { + PRUint32 len = reader.GetInt32(); + if (len == (PRUint32) -1) + { + // it's a null string + v.val.p = nsnull; + } + else + { + char *buf = (char *) nsMemory::Alloc(len + 1); + reader.GetBytes(buf, len); + buf[len] = char(0); + + v.val.p = buf; + v.SetValIsAllocated(); + } + } + break; + + case nsXPTType::T_WCHAR_STR: + { + PRUint32 len = reader.GetInt32(); + if (len == (PRUint32) -1) + { + // it's a null string + v.val.p = nsnull; + } + else + { + PRUnichar *buf = (PRUnichar *) nsMemory::Alloc(len + 2); + reader.GetBytes(buf, len); + buf[len / 2] = PRUnichar(0); + + v.val.p = buf; + v.SetValIsAllocated(); + } + } + break; + + case nsXPTType::T_INTERFACE: + case nsXPTType::T_INTERFACE_IS: + { + reader.GetBytes(&v.val.u64, sizeof(DConAddr)); + // stub creation will be handled outside this routine. we only + // deserialize the DConAddr into v.val.u64 temporarily. + } + break; + + case nsXPTType::T_ASTRING: + case nsXPTType::T_DOMSTRING: + { + PRUint32 len = reader.GetInt32(); + + nsString *str = new nsString(); + str->SetLength(len / 2); + PRUnichar *buf = str->BeginWriting(); + reader.GetBytes(buf, len); + + v.val.p = str; + v.SetValIsDOMString(); + } + break; + + case nsXPTType::T_UTF8STRING: + case nsXPTType::T_CSTRING: + { + PRUint32 len = reader.GetInt32(); + + nsCString *str = new nsCString(); + str->SetLength(len); + char *buf = str->BeginWriting(); + reader.GetBytes(buf, len); + + v.val.p = str; + + // this distinction here is pretty pointless + if (t.TagPart() == nsXPTType::T_CSTRING) + v.SetValIsCString(); + else + v.SetValIsUTF8String(); + } + break; + + case nsXPTType::T_ARRAY: + // arrays are deserialized after all other params outside this routine + break; + + 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; + v.flags = 0; + + switch (t.TagPart()) + { + case nsXPTType::T_ASTRING: + case nsXPTType::T_DOMSTRING: + v.val.p = new nsString(); + if (!v.val.p) + return NS_ERROR_OUT_OF_MEMORY; + v.type = t; + v.SetValIsDOMString(); + break; + + case nsXPTType::T_UTF8STRING: + case nsXPTType::T_CSTRING: + v.val.p = new nsCString(); + if (!v.val.p) + return NS_ERROR_OUT_OF_MEMORY; + v.type = t; + v.SetValIsCString(); + break; + + default: + LOG(("unhandled dipper: type=%d\n", t.TagPart())); + return NS_ERROR_UNEXPECTED; + } + } + else if (p.IsOut() || p.IsRetval()) + { + memset(&v.val, 0, sizeof(v.val)); + v.ptr = &v.val; + v.type = t; + v.flags = 0; + v.SetPtrIsData(); + + // the ownership of output nsID, string, wstring, interface pointers and + // arrays is transferred to the receiving party. Therefore, we need to + // instruct FinishParam() to perform a cleanup after serializing them. + switch (t.TagPart()) + { + case nsXPTType::T_IID: + case nsXPTType::T_CHAR_STR: + case nsXPTType::T_WCHAR_STR: + case nsXPTType::T_ARRAY: + v.SetValIsAllocated(); + break; + case nsXPTType::T_INTERFACE: + case nsXPTType::T_INTERFACE_IS: + v.SetValIsInterface(); + break; + default: + break; + } + } + + return NS_OK; +} + +static void +FinishParam(nsXPTCVariant &v) +{ +#ifdef VBOX + /* make valgrind happy */ + if (!v.MustFreeVal()) + return; +#endif + if (!v.val.p) + return; + + if (v.IsValAllocated()) + nsMemory::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(); + if (len == (PRUint32) -1) + { + // it's a null string +#ifdef VBOX + *((char **) v.val.p) = NULL; +#else + v.val.p = 0; +#endif + } + else + { + 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(); + if (len == (PRUint32) -1) + { + // it's a null string +#ifdef VBOX + *((PRUnichar **) v.val.p) = 0; +#else + v.val.p = 0; +#endif + } + else + { + PRUnichar *buf = (PRUnichar *) nsMemory::Alloc(len + 2); + reader.GetBytes(buf, len); + buf[len / 2] = 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 and the original value of v.val.p + // into v.val.p temporarily. needs temporary memory alloc. + DConAddrPlusPtr *buf = (DConAddrPlusPtr *) nsMemory::Alloc(sizeof(DConAddrPlusPtr)); + reader.GetBytes(&buf->addr, sizeof(DConAddr)); + buf->p = v.val.p; + v.val.p = buf; + } + break; + + case nsXPTType::T_ASTRING: + case nsXPTType::T_DOMSTRING: + { + PRUint32 len = reader.GetInt32(); + + nsAString *str = (nsAString *) v.val.p; + + nsAString::iterator begin; + str->SetLength(len / 2); + 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; + + nsACString::iterator begin; + str->SetLength(len); + str->BeginWriting(begin); + + reader.GetBytes(begin.get(), len); + } + break; + + case nsXPTType::T_ARRAY: + // arrays are deserialized after all other params outside this routine + break; + + 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; +} + +//----------------------------------------------------------------------------- +// +// Returns an element from the nsXPTCMiniVariant array by properly casting it to +// nsXPTCVariant when requested +#define GET_PARAM(params, isXPTCVariantArray, idx) \ + (isXPTCVariantArray ? ((nsXPTCVariant *) params) [idx] : params [idx]) + +// isResult is PR_TRUE if the size_is and length_is params are out or retval +// so that nsXPTCMiniVariants contain pointers to their locations instead of the +// values themselves. +static nsresult +GetArrayParamInfo(nsIInterfaceInfo *iinfo, uint16 methodIndex, + const nsXPTMethodInfo &methodInfo, nsXPTCMiniVariant *params, + PRBool isXPTCVariantArray, const nsXPTParamInfo ¶mInfo, + PRBool isResult, PRUint32 &size, PRUint32 &length, + nsXPTType &elemType) +{ + // XXX multidimensional arrays are not supported so dimension is always 0 for + // getting the size_is argument number of the array itself and 1 for getting + // the type of elements stored in the array. + + nsresult rv; + + // get the array size + PRUint8 sizeArg; + rv = iinfo->GetSizeIsArgNumberForParam(methodIndex, ¶mInfo, 0, &sizeArg); + if (NS_FAILED(rv)) + return rv; + + // get the number of valid elements + PRUint8 lenArg; + rv = iinfo->GetLengthIsArgNumberForParam(methodIndex, ¶mInfo, 0, &lenArg); + if (NS_FAILED(rv)) + return rv; + + // according to XPT specs + // (http://www.mozilla.org/scriptable/typelib_file.html), size_is and + // length_is for arrays is always uint32. Check this too. + { + nsXPTParamInfo pi = methodInfo.GetParam (sizeArg); + if (pi.GetType().TagPart() != nsXPTType::T_U32) + { + LOG(("unexpected size_is() parameter type: $d\n", + pi.GetType().TagPart())); + return NS_ERROR_UNEXPECTED; + } + + pi = methodInfo.GetParam (lenArg); + if (pi.GetType().TagPart() != nsXPTType::T_U32) + { + LOG(("unexpected length_is() parameter type: $d\n", + pi.GetType().TagPart())); + return NS_ERROR_UNEXPECTED; + } + } + + if (isResult) + { + length = *((PRUint32 *) GET_PARAM(params,isXPTCVariantArray, lenArg).val.p); + size = *((PRUint32 *) GET_PARAM(params, isXPTCVariantArray, sizeArg).val.p); + } + else + { + length = GET_PARAM(params, isXPTCVariantArray, lenArg).val.u32; + size = GET_PARAM(params, isXPTCVariantArray, sizeArg).val.u32; + } + + if (length > size) + { + NS_WARNING("length_is() value is greater than size_is() value"); + length = size; + } + + // get type of array elements + rv = iinfo->GetTypeForParam(methodIndex, ¶mInfo, 1, &elemType); + if (NS_FAILED(rv)) + return rv; + + if (elemType.IsArithmetic() && + (elemType.IsPointer() || elemType.IsUniquePointer() || + elemType.IsReference())) + { + LOG(("arrays of pointers and references to arithmetic types are " + "not yet supported\n")); + return NS_ERROR_NOT_IMPLEMENTED; + } + + if (elemType.IsArray()) + { + LOG(("multidimensional arrays are not yet supported\n")); + return NS_ERROR_NOT_IMPLEMENTED; + } + + return NS_OK; +} + +static nsresult +GetTypeSize(const nsXPTType &type, PRUint32 &size, PRBool &isSimple) +{ + // get the type size in bytes + size = 0; + isSimple = PR_TRUE; + switch (type.TagPart()) + { + case nsXPTType::T_I8: size = sizeof(PRInt8); break; + case nsXPTType::T_I16: size = sizeof(PRInt16); break; + case nsXPTType::T_I32: size = sizeof(PRInt32); break; + case nsXPTType::T_I64: size = sizeof(PRInt64); break; + case nsXPTType::T_U8: size = sizeof(PRUint8); break; + case nsXPTType::T_U16: size = sizeof(PRUint16); break; + case nsXPTType::T_U32: size = sizeof(PRUint32); break; + case nsXPTType::T_U64: size = sizeof(PRUint64); break; + case nsXPTType::T_FLOAT: size = sizeof(float); break; + case nsXPTType::T_DOUBLE: size = sizeof(double); break; + case nsXPTType::T_BOOL: size = sizeof(PRBool); break; + case nsXPTType::T_CHAR: size = sizeof(char); break; + case nsXPTType::T_WCHAR: size = sizeof(PRUnichar); break; + case nsXPTType::T_IID: /* fall through */ + case nsXPTType::T_CHAR_STR: /* fall through */ + case nsXPTType::T_WCHAR_STR: /* fall through */ + case nsXPTType::T_ASTRING: /* fall through */ + case nsXPTType::T_DOMSTRING: /* fall through */ + case nsXPTType::T_UTF8STRING: /* fall through */ + case nsXPTType::T_CSTRING: /* fall through */ + size = sizeof(void *); + isSimple = PR_FALSE; + break; + case nsXPTType::T_INTERFACE: /* fall through */ + case nsXPTType::T_INTERFACE_IS: /* fall through */ + size = sizeof(DConAddr); + isSimple = PR_FALSE; + break; + default: + LOG(("unexpected parameter type: %d\n", type.TagPart())); + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +static nsresult +SerializeArrayParam(ipcDConnectService *dConnect, + ipcMessageWriter &writer, PRUint32 peerID, + nsIInterfaceInfo *iinfo, uint16 methodIndex, + const nsXPTMethodInfo &methodInfo, + nsXPTCMiniVariant *params, PRBool isXPTCVariantArray, + const nsXPTParamInfo ¶mInfo, + void *array, nsVoidArray &wrappers) +{ + if (!array) + { + // put 0 to indicate null array + writer.PutInt8(0); + return NS_OK; + } + + // put 1 to indicate non-null array + writer.PutInt8(1); + + PRUint32 size = 0; + PRUint32 length = 0; + nsXPTType elemType; + + nsresult rv = GetArrayParamInfo(iinfo, methodIndex, methodInfo, params, + isXPTCVariantArray, paramInfo, PR_FALSE, + size, length, elemType); + if (NS_FAILED (rv)) + return rv; + + PRUint32 elemSize = 0; + PRBool isSimple = PR_TRUE; + rv = GetTypeSize(elemType, elemSize, isSimple); + if (NS_FAILED (rv)) + return rv; + + if (isSimple) + { + // this is a simple arithmetic type, write the whole array at once + writer.PutBytes(array, length * elemSize); + return NS_OK; + } + + // iterate over valid (length_is) elements of the array + // and serialize each of them + nsXPTCMiniVariant v; + for (PRUint32 i = 0; i < length; ++i) + { + v.val.p = ((void **) array) [i]; + + if (elemType.IsInterfacePointer()) + { + nsID iid; + rv = dConnect->GetIIDForMethodParam(iinfo, &methodInfo, paramInfo, elemType, + methodIndex, params, isXPTCVariantArray, + iid); + if (NS_SUCCEEDED(rv)) + rv = dConnect->SerializeInterfaceParam(writer, peerID, iid, + (nsISupports *) v.val.p, + wrappers); + } + else + rv = SerializeParam(writer, elemType, v); + + if (NS_FAILED(rv)) + return rv; + } + + return NS_OK; +} + +// isResult is PR_TRUE if the array param is out or retval +static nsresult +DeserializeArrayParam(ipcDConnectService *dConnect, + ipcMessageReader &reader, PRUint32 peerID, + nsIInterfaceInfo *iinfo, uint16 methodIndex, + const nsXPTMethodInfo &methodInfo, + nsXPTCMiniVariant *params, PRBool isXPTCVariantArray, + const nsXPTParamInfo ¶mInfo, + PRBool isResult, void *&array) +{ + PRUint32 size = 0; + PRUint32 length = 0; + nsXPTType elemType; + + nsresult rv = GetArrayParamInfo(iinfo, methodIndex, methodInfo, params, + isXPTCVariantArray, paramInfo, isResult, + size, length, elemType); + if (NS_FAILED(rv)) + return rv; + + PRUint8 prefix = reader.GetInt8(); + if (prefix == 0) + { + // it's a null array + array = nsnull; + return NS_OK; + } + // sanity + if (prefix != 1) + { + LOG(("unexpected array prefix: %u\n", prefix)); + return NS_ERROR_UNEXPECTED; + } + + PRUint32 elemSize = 0; + PRBool isSimple = PR_TRUE; + rv = GetTypeSize(elemType, elemSize, isSimple); + if (NS_FAILED (rv)) + return rv; + + // Note: for zero-sized arrays, we use the size of 1 because whether + // malloc(0) returns a null pointer or not (which is used in isNull()) + // is implementation-dependent according to the C standard + void *arr = nsMemory::Alloc((size ? size : 1) * elemSize); + if (arr == nsnull) + return NS_ERROR_OUT_OF_MEMORY; + + // initialize the unused space of the array with zeroes + if (length < size) + memset(((PRUint8 *) arr) + length * elemSize, 0, + (size - length) * elemSize); + + if (isSimple) + { + // this is a simple arithmetic type, read the whole array at once + reader.GetBytes(arr, length * elemSize); + + array = arr; + return NS_OK; + } + + // iterate over valid (length_is) elements of the array + // and deserialize each of them individually + nsXPTCVariant v; + for (PRUint32 i = 0; i < length; ++i) + { + rv = DeserializeParam(reader, elemType, v); + + if (NS_SUCCEEDED(rv) && elemType.IsInterfacePointer()) + { + // grab the DConAddr value temporarily stored in the param + PtrBits bits = v.val.u64; + + // DeserializeInterfaceParamBits needs IID only if it's a remote object + nsID iid; + if (bits & PTRBITS_REMOTE_BIT) + rv = dConnect->GetIIDForMethodParam(iinfo, &methodInfo, paramInfo, + elemType, methodIndex, + params, isXPTCVariantArray, iid); + if (NS_SUCCEEDED(rv)) + { + nsISupports *obj = nsnull; + rv = dConnect->DeserializeInterfaceParamBits(bits, peerID, iid, obj); + if (NS_SUCCEEDED(rv)) + v.val.p = obj; + } + } + + if (NS_FAILED(rv)) + break; + + // note that we discard extended param informaton provided by nsXPTCVariant + // and will have to "reconstruct" it from the type tag in FinishArrayParam() + ((void **) arr) [i] = v.val.p; + } + + if (NS_FAILED(rv)) + nsMemory::Free(arr); + else + array = arr; + + return rv; +} + +static void +FinishArrayParam(nsIInterfaceInfo *iinfo, uint16 methodIndex, + const nsXPTMethodInfo &methodInfo, nsXPTCMiniVariant *params, + PRBool isXPTCVariantArray, const nsXPTParamInfo ¶mInfo, + const nsXPTCMiniVariant &arrayVal) +{ + // nothing to do for a null array + void *arr = arrayVal.val.p; + if (!arr) + return; + + PRUint32 size = 0; + PRUint32 length = 0; + nsXPTType elemType; + + // note that FinishArrayParam is called only from OnInvoke to free memory + // after the call has been served. When OnInvoke sets up out and retval + // parameters for the real method, it passes pointers to the nsXPTCMiniVariant + // elements of the params array themselves so that they will eventually + // receive the returned values. For this reason, both in 'in' param and + // 'out/retaval' param cases, size_is and length_is may be read by + // GetArrayParamInfo() by value. Therefore, isResult is always PR_FALSE. + nsresult rv = GetArrayParamInfo(iinfo, methodIndex, methodInfo, params, + isXPTCVariantArray, paramInfo, PR_FALSE, + size, length, elemType); + if (NS_FAILED (rv)) + return; + + nsXPTCVariant v; + v.ptr = nsnull; + v.flags = 0; + + // iterate over valid (length_is) elements of the array + // and free each of them + for (PRUint32 i = 0; i < length; ++i) + { + v.type = elemType.TagPart(); + + switch (elemType.TagPart()) + { + case nsXPTType::T_I8: /* fall through */ + case nsXPTType::T_I16: /* fall through */ + case nsXPTType::T_I32: /* fall through */ + case nsXPTType::T_I64: /* fall through */ + case nsXPTType::T_U8: /* fall through */ + case nsXPTType::T_U16: /* fall through */ + case nsXPTType::T_U32: /* fall through */ + case nsXPTType::T_U64: /* fall through */ + case nsXPTType::T_FLOAT: /* fall through */ + case nsXPTType::T_DOUBLE: /* fall through */ + case nsXPTType::T_BOOL: /* fall through */ + case nsXPTType::T_CHAR: /* fall through */ + case nsXPTType::T_WCHAR: /* fall through */ + // nothing to free for arithmetic types + continue; + case nsXPTType::T_IID: /* fall through */ + case nsXPTType::T_CHAR_STR: /* fall through */ + case nsXPTType::T_WCHAR_STR: /* fall through */ + v.val.p = ((void **) arr) [i]; + v.SetValIsAllocated(); + break; + case nsXPTType::T_INTERFACE: /* fall through */ + case nsXPTType::T_INTERFACE_IS: /* fall through */ + v.val.p = ((void **) arr) [i]; + v.SetValIsInterface(); + break; + case nsXPTType::T_ASTRING: /* fall through */ + case nsXPTType::T_DOMSTRING: /* fall through */ + v.val.p = ((void **) arr) [i]; + v.SetValIsDOMString(); + break; + case nsXPTType::T_UTF8STRING: /* fall through */ + v.val.p = ((void **) arr) [i]; + v.SetValIsUTF8String(); + break; + case nsXPTType::T_CSTRING: /* fall through */ + v.val.p = ((void **) arr) [i]; + v.SetValIsCString(); + break; + default: + LOG(("unexpected parameter type: %d\n", elemType.TagPart())); + return; + } + + FinishParam(v); + } +} + +//----------------------------------------------------------------------------- + +static PRUint32 +NewRequestIndex() +{ + static PRInt32 sRequestIndex; + return (PRUint32) PR_AtomicIncrement(&sRequestIndex); +} + +//----------------------------------------------------------------------------- + +#ifdef VBOX +typedef struct ClientDownInfo +{ + ClientDownInfo(PRUint32 aClient) + { + uClient = aClient; + uTimestamp = PR_IntervalNow(); + } + + PRUint32 uClient; + PRIntervalTime uTimestamp; +} ClientDownInfo; +typedef std::map<PRUint32, ClientDownInfo *> ClientDownMap; +typedef std::list<ClientDownInfo *> ClientDownList; + +#define MAX_CLIENT_DOWN_SIZE 10000 + +/* Protected by the queue monitor. */ +static ClientDownMap g_ClientDownMap; +static ClientDownList g_ClientDownList; + +#endif /* VBOX */ + +class DConnectMsgSelector : public ipcIMessageObserver +{ +public: + DConnectMsgSelector(PRUint32 peer, PRUint8 opCodeMajor, PRUint32 requestIndex) + : mPeer (peer) + , mOpCodeMajor(opCodeMajor) + , 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) + { + // accept special "client dead" messages for a given peer + // (empty target id, zero data and data length) +#ifndef VBOX + if (aSenderID == mPeer && aTarget.Equals(nsID()) && !aData && !aDataLen) + return NS_OK; +#else /* VBOX */ + if (aSenderID != IPC_SENDER_ANY && aTarget.Equals(nsID()) && !aData && !aDataLen) + { + // Insert new client down information. Start by expiring outdated + // entries and free one element if there's still no space (if needed). + PRIntervalTime now = PR_IntervalNow(); + while (!g_ClientDownList.empty()) + { + ClientDownInfo *cInfo = g_ClientDownList.back(); + PRInt64 diff = (PRInt64)now - cInfo->uTimestamp; + if (diff < 0) + diff += (PRInt64)((PRIntervalTime)-1) + 1; + if (diff > PR_SecondsToInterval(15 * 60)) + { + g_ClientDownMap.erase(cInfo->uClient); + g_ClientDownList.pop_back(); + NS_ASSERTION(g_ClientDownMap.size() == g_ClientDownList.size(), + "client down info inconsistency during expiry"); + delete cInfo; + } + else + break; + } + + ClientDownMap::iterator it = g_ClientDownMap.find(aSenderID); + if (it == g_ClientDownMap.end()) + { + /* Getting size of a map is O(1), size of a list can be O(n). */ + while (g_ClientDownMap.size() >= MAX_CLIENT_DOWN_SIZE) + { + ClientDownInfo *cInfo = g_ClientDownList.back(); + g_ClientDownMap.erase(cInfo->uClient); + g_ClientDownList.pop_back(); + NS_ASSERTION(g_ClientDownMap.size() == g_ClientDownList.size(), + "client down info inconsistency during emergency evicting"); + delete cInfo; + } + + ClientDownInfo *cInfo = new ClientDownInfo(aSenderID); + RTMEM_MAY_LEAK(cInfo); /* tstVBoxAPIPerf leaks one allocated during ComPtr<IVirtualBoxClient>::createInprocObject(). */ + g_ClientDownMap[aSenderID] = cInfo; + g_ClientDownList.push_front(cInfo); + NS_ASSERTION(g_ClientDownMap.size() == g_ClientDownList.size(), + "client down info inconsistency after adding entry"); + } + return (aSenderID == mPeer) ? NS_OK : IPC_WAIT_NEXT_MESSAGE; + } + // accept special "client up" messages for a given peer + // (empty target id, zero data and data length=1) + if (aTarget.Equals(nsID()) && !aData && aDataLen == 1) + { + ClientDownMap::iterator it = g_ClientDownMap.find(aSenderID); + if (it != g_ClientDownMap.end()) + { + ClientDownInfo *cInfo = it->second; + g_ClientDownMap.erase(it); + g_ClientDownList.remove(cInfo); + NS_ASSERTION(g_ClientDownMap.size() == g_ClientDownList.size(), + "client down info inconsistency in client up case"); + delete cInfo; + } + return (aSenderID == mPeer) ? NS_OK : IPC_WAIT_NEXT_MESSAGE; + } + // accept special "client check" messages for an anonymous sender + // (invalid sender id, empty target id, zero data and data length + if (aSenderID == IPC_SENDER_ANY && aTarget.Equals(nsID()) && !aData && !aDataLen) + { + LOG(("DConnectMsgSelector::OnMessageAvailable: poll liveness for mPeer=%d\n", + mPeer)); + ClientDownMap::iterator it = g_ClientDownMap.find(mPeer); + return (it == g_ClientDownMap.end()) ? IPC_WAIT_NEXT_MESSAGE : NS_OK; + } +#endif /* VBOX */ + const DConnectOp *op = (const DConnectOp *) aData; + // accept only reply messages with the given peer/opcode/index + // (to prevent eating replies the other thread might be waiting for) + // as well as any non-reply messages (to serve external requests that + // might arrive while we're waiting for the given reply). + if (aDataLen >= sizeof(DConnectOp) && + ((op->opcode_major != DCON_OP_SETUP_REPLY && + op->opcode_major != DCON_OP_INVOKE_REPLY) || + (aSenderID == mPeer && + op->opcode_major == mOpCodeMajor && + op->request_index == mRequestIndex))) + return NS_OK; + else + return IPC_WAIT_NEXT_MESSAGE; + } + + const PRUint32 mPeer; + const PRUint8 mOpCodeMajor; + const PRUint32 mRequestIndex; +}; +NS_IMPL_QUERY_INTERFACE1(DConnectMsgSelector, ipcIMessageObserver) + +class DConnectCompletion : public ipcIMessageObserver +{ +public: + DConnectCompletion(PRUint32 peer, PRUint8 opCodeMajor, PRUint32 requestIndex) + : mSelector(peer, opCodeMajor, 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; + LOG(( + "DConnectCompletion::OnMessageAvailable: " + "senderID=%d, opcode_major=%d, index=%d (waiting for %d)\n", + aSenderID, op->opcode_major, op->request_index, mSelector.mRequestIndex + )); + if (aSenderID == mSelector.mPeer && + op->opcode_major == mSelector.mOpCodeMajor && + op->request_index == mSelector.mRequestIndex) + { + OnResponseAvailable(aSenderID, op, aDataLen); + } + else + { + // ensure ipcDConnectService is not deleted before we finish + nsRefPtr <ipcDConnectService> dConnect (ipcDConnectService::GetInstance()); + if (dConnect) + dConnect->OnMessageAvailable(aSenderID, aTarget, aData, aDataLen); + } + return NS_OK; + } + + virtual void OnResponseAvailable(PRUint32 sender, const DConnectOp *op, PRUint32 opLen) = 0; + + DConnectMsgSelector &GetSelector() + { + return mSelector; + } + +protected: + DConnectMsgSelector mSelector; +}; +NS_IMPL_QUERY_INTERFACE1(DConnectCompletion, ipcIMessageObserver) + +//----------------------------------------------------------------------------- + +class DConnectInvokeCompletion : public DConnectCompletion +{ +public: + DConnectInvokeCompletion(PRUint32 peer, const DConnectInvoke *invoke) + : DConnectCompletion(peer, DCON_OP_INVOKE_REPLY, 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); + 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) + , mInstance(aInstance) + , mPeerID(aPeerID) + , mCachedISupports(0) + , mRefCntLevels(0) + {} + + NS_HIDDEN ~DConnectStub(); + + // 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; } + + DConnectStubKey::Key GetKey() { + return DConnectStubKey::Key(mPeerID, mInstance); + } + + NS_IMETHOD_(nsrefcnt) AddRefIPC(); + +private: + nsCOMPtr<nsIInterfaceInfo> mIInfo; + + // uniquely identifies this object instance between peers. + DConAddr mInstance; + + // the "client id" of our IPC peer. this guy owns the real object. + PRUint32 mPeerID; + + // cached nsISupports stub for this object + DConnectStub *mCachedISupports; + + // stack of reference counter values (protected by + // ipcDConnectService::StubLock()) + nsDeque mRefCntLevels; +}; + +NS_IMETHODIMP_(nsrefcnt) +DConnectStub::AddRefIPC() +{ + // in this special version, we memorize the resulting reference count in the + // associated stack array. This stack is then used by Release() to determine + // when it is necessary to send a RELEASE request to the peer owning the + // object in order to balance AddRef() the peer does on DConnectInstance every + // time it passes an object over IPC. + + // NOTE: this function is to be called from DConnectInstance::CreateStub only! + + nsRefPtr <ipcDConnectService> dConnect (ipcDConnectService::GetInstance()); + NS_ASSERTION(dConnect, "no ipcDConnectService (uninitialized?)"); + if (!dConnect) + return 0; + + // dConnect->StubLock() must be already locked here by + // DConnectInstance::CreateStub + + nsrefcnt count = AddRef(); + mRefCntLevels.Push((void *)(uintptr_t) count); + return count; +} + +nsresult +ipcDConnectService::CreateStub(const nsID &iid, PRUint32 peer, DConAddr instance, + DConnectStub **result) +{ + nsresult rv; + + nsCOMPtr<nsIInterfaceInfo> iinfo; + rv = GetInterfaceInfo(iid, getter_AddRefs(iinfo)); + if (NS_FAILED(rv)) + return rv; + + nsAutoLock lock (mLock); + + if (mDisconnected) + return NS_ERROR_NOT_INITIALIZED; + + // we also need the stub lock which protects DConnectStub::mRefCntLevels and + // ipcDConnectService::mStubs + nsAutoLock stubLock (mStubLock); + + DConnectStub *stub = nsnull; + + // first try to find an existing stub for a given peer and instance + // (we do not care about IID because every DConAddr instance represents + // exactly one interface of the real object on the peer's side) + if (!mStubs.Get(DConnectStubKey::Key(peer, instance), &stub)) + { + stub = new DConnectStub(iinfo, instance, peer); + + if (NS_UNLIKELY(!stub)) + rv = NS_ERROR_OUT_OF_MEMORY; + else + { + rv = StoreStub(stub); + if (NS_FAILED(rv)) + delete stub; + } + } + + if (NS_SUCCEEDED(rv)) + { + stub->AddRefIPC(); + *result = stub; + } + + return rv; +} + +nsresult +ipcDConnectService::SerializeInterfaceParam(ipcMessageWriter &writer, + PRUint32 peer, const nsID &iid, + nsISupports *obj, + nsVoidArray &wrappers) +{ + nsAutoLock lock (mLock); + + if (mDisconnected) + return NS_ERROR_NOT_INITIALIZED; + + // 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. + + // if the interface pointer references an object for which we already have + // an existing wrapper, then we use it instead of creating a new one. this + // is based on the assumption that a valid COM object always returns exactly + // the same pointer value in response to every + // QueryInterface(NS_GET_IID(nsISupports), ...). + + if (!obj) + { + // write null address + DConAddr nullobj = 0; + writer.PutBytes(&nullobj, sizeof(nullobj)); + } + else + { + DConnectStub *stub = nsnull; + nsresult rv = obj->QueryInterface(kDConnectStubID, (void **) &stub); + if (NS_SUCCEEDED(rv) && (stub->PeerID() == peer)) + { + DConAddr p = stub->Instance(); + writer.PutBytes(&p, sizeof(p)); + } + else + { + // create instance wrapper + + nsCOMPtr<nsIInterfaceInfo> iinfo; + rv = GetInterfaceInfo(iid, getter_AddRefs(iinfo)); + if (NS_FAILED(rv)) + return rv; + + DConnectInstance *wrapper = nsnull; + + // first try to find an existing wrapper for the given object + if (!FindInstanceAndAddRef(peer, obj, &iid, &wrapper)) + { + wrapper = new DConnectInstance(peer, iinfo, obj); + if (!wrapper) + return NS_ERROR_OUT_OF_MEMORY; + + rv = StoreInstance(wrapper); + if (NS_FAILED(rv)) + { + delete wrapper; + return rv; + } + + // reference the newly created wrapper + wrapper->AddRef(); + } + + // increase the second, IPC-only, reference counter (mandatory before + // trying wrappers.AppendElement() to make sure ReleaseIPC() will remove + // the wrapper from the instance map on failure) + wrapper->AddRefIPC(); + + if (!wrappers.AppendElement(wrapper)) + { + wrapper->ReleaseIPC(); + wrapper->Release(); + return NS_ERROR_OUT_OF_MEMORY; + } + + // wrapper remains referenced when passing it to the client + // (will be released upon DCON_OP_RELEASE) + + // send address of the instance wrapper, and set the low bit to indicate + // to the remote party that this is a remote instance wrapper. + PtrBits bits = ((PtrBits)(uintptr_t) wrapper); + NS_ASSERTION((bits & PTRBITS_REMOTE_BIT) == 0, "remote bit wrong)"); + bits |= PTRBITS_REMOTE_BIT; + writer.PutBytes(&bits, sizeof(bits)); + } + NS_IF_RELEASE(stub); + } + return NS_OK; +} + +// NOTE: peer and iid are ignored if bits doesn't contain PTRBITS_REMOTE_BIT +nsresult +ipcDConnectService::DeserializeInterfaceParamBits(PtrBits bits, PRUint32 peer, + const nsID &iid, + nsISupports *&obj) +{ + nsresult rv; + + obj = nsnull; + + if (bits & PTRBITS_REMOTE_BIT) + { + // pointer is to a remote object. we need to build a stub. + + bits &= ~PTRBITS_REMOTE_BIT; + + DConnectStub *stub; + rv = CreateStub(iid, peer, (DConAddr) bits, &stub); + if (NS_SUCCEEDED(rv)) + obj = stub; + } + else if (bits) + { + // pointer is to one of our instance wrappers. Replace it with the + // real instance. + + DConnectInstance *wrapper = (DConnectInstance *) bits; + // make sure we've been sent a valid wrapper + if (!CheckInstanceAndAddRef(wrapper, peer)) + { + NS_NOTREACHED("instance wrapper not found"); + return NS_ERROR_INVALID_ARG; + } + obj = wrapper->RealInstance(); + NS_ADDREF(obj); + NS_RELEASE(wrapper); + } + else + { + // obj is alredy nsnull + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- + +#define EXCEPTION_STUB_ID \ +{ /* 70578d68-b25e-4370-a70c-89bbe56e6699 */ \ + 0x70578d68, \ + 0xb25e, \ + 0x4370, \ + {0xa7, 0x0c, 0x89, 0xbb, 0xe5, 0x6e, 0x66, 0x99} \ +} +static NS_DEFINE_IID(kExceptionStubID, EXCEPTION_STUB_ID); + +// ExceptionStub is used to cache all primitive-typed bits of a remote nsIException +// instance (such as the error message or line number) to: +// +// a) reduce the number of IPC calls; +// b) make sure exception information is available to the calling party even if +// the called party terminates immediately after returning an exception. +// To achieve this, all cacheable information is serialized together with +// the instance wrapper itself. + +class ExceptionStub : public nsIException +{ +public: + + NS_DECL_ISUPPORTS + NS_DECL_NSIEXCEPTION + + ExceptionStub(const nsACString &aMessage, nsresult aResult, + const nsACString &aName, const nsACString &aFilename, + PRUint32 aLineNumber, PRUint32 aColumnNumber, + DConnectStub *aXcptStub) + : mMessage(aMessage), mResult(aResult) + , mName(aName), mFilename(aFilename) + , mLineNumber (aLineNumber), mColumnNumber (aColumnNumber) + , mXcptStub (aXcptStub) { NS_ASSERTION(aXcptStub, "NULL"); } + + ~ExceptionStub() {} + + nsIException *Exception() { return (nsIException *)(nsISupports *) mXcptStub; } + DConnectStub *Stub() { return mXcptStub; } + +private: + + nsCString mMessage; + nsresult mResult; + nsCString mName; + nsCString mFilename; + PRUint32 mLineNumber; + PRUint32 mColumnNumber; + nsRefPtr<DConnectStub> mXcptStub; +}; + +NS_IMPL_THREADSAFE_ADDREF(ExceptionStub) +NS_IMPL_THREADSAFE_RELEASE(ExceptionStub) + +NS_IMETHODIMP +ExceptionStub::QueryInterface(const nsID &aIID, void **aInstancePtr) +{ + NS_ASSERTION(aInstancePtr, + "QueryInterface requires a non-NULL destination!"); + + // used to discover if this is an ExceptionStub instance. + if (aIID.Equals(kExceptionStubID)) + { + *aInstancePtr = this; + NS_ADDREF_THIS(); + return NS_OK; + } + + // regular NS_IMPL_QUERY_INTERFACE1 sequence + + nsISupports* foundInterface = 0; + + if (aIID.Equals(NS_GET_IID(nsIException))) + foundInterface = NS_STATIC_CAST(nsIException*, this); + else + if (aIID.Equals(NS_GET_IID(nsISupports))) + foundInterface = NS_STATIC_CAST(nsISupports*, + NS_STATIC_CAST(nsIException *, this)); + else + if (mXcptStub) + { + // ask the real nsIException object + return mXcptStub->QueryInterface(aIID, aInstancePtr); + } + + nsresult status; + if (!foundInterface) + status = NS_NOINTERFACE; + else + { + NS_ADDREF(foundInterface); + status = NS_OK; + } + *aInstancePtr = foundInterface; + return status; +} + +/* readonly attribute string message; */ +NS_IMETHODIMP ExceptionStub::GetMessage(char **aMessage) +{ + AssertReturn(aMessage, NS_ERROR_INVALID_POINTER); + *aMessage = ToNewCString(mMessage); + return NS_OK; +} + +/* readonly attribute nsresult result; */ +NS_IMETHODIMP ExceptionStub::GetResult(nsresult *aResult) +{ + AssertReturn(aResult, NS_ERROR_INVALID_POINTER); + *aResult = mResult; + return NS_OK; +} + +/* readonly attribute string name; */ +NS_IMETHODIMP ExceptionStub::GetName(char **aName) +{ + AssertReturn(aName, NS_ERROR_INVALID_POINTER); + *aName = ToNewCString(mName); + return NS_OK; +} + +/* readonly attribute string filename; */ +NS_IMETHODIMP ExceptionStub::GetFilename(char **aFilename) +{ + AssertReturn(aFilename, NS_ERROR_INVALID_POINTER); + *aFilename = ToNewCString(mFilename); + return NS_OK; +} + +/* readonly attribute PRUint32 lineNumber; */ +NS_IMETHODIMP ExceptionStub::GetLineNumber(PRUint32 *aLineNumber) +{ + AssertReturn(aLineNumber, NS_ERROR_INVALID_POINTER); + *aLineNumber = mLineNumber; + return NS_OK; +} + +/* readonly attribute PRUint32 columnNumber; */ +NS_IMETHODIMP ExceptionStub::GetColumnNumber(PRUint32 *aColumnNumber) +{ + AssertReturn(aColumnNumber, NS_ERROR_INVALID_POINTER); + *aColumnNumber = mColumnNumber; + return NS_OK; +} + +/* readonly attribute nsIStackFrame location; */ +NS_IMETHODIMP ExceptionStub::GetLocation(nsIStackFrame **aLocation) +{ + if (Exception()) + return Exception()->GetLocation (aLocation); + return NS_ERROR_UNEXPECTED; +} + +/* readonly attribute nsIException inner; */ +NS_IMETHODIMP ExceptionStub::GetInner(nsIException **aInner) +{ + if (Exception()) + return Exception()->GetInner (aInner); + return NS_ERROR_UNEXPECTED; +} + +/* readonly attribute nsISupports data; */ +NS_IMETHODIMP ExceptionStub::GetData(nsISupports * *aData) +{ + if (Exception()) + return Exception()->GetData (aData); + return NS_ERROR_UNEXPECTED; +} + +/* string toString (); */ +NS_IMETHODIMP ExceptionStub::ToString(char **_retval) +{ + if (Exception()) + return Exception()->ToString (_retval); + return NS_ERROR_UNEXPECTED; +} + +nsresult +ipcDConnectService::SerializeException(ipcMessageWriter &writer, + PRUint32 peer, nsIException *xcpt, + nsVoidArray &wrappers) +{ + PRBool cache_fields = PR_FALSE; + + // first, seralize the nsIException pointer. The code is merely the same as + // in SerializeInterfaceParam() except that when the exception to serialize + // is an ExceptionStub instance and the real instance it stores as mXcpt + // is a DConnectStub corresponding to an object in the address space of the + // peer, we simply pass that object back instead of creating a new wrapper. + + { + nsAutoLock lock (mLock); + + if (mDisconnected) + return NS_ERROR_NOT_INITIALIZED; + + if (!xcpt) + { + // write null address +#ifdef VBOX + // see ipcDConnectService::DeserializeException()! + PtrBits bits = 0; + writer.PutBytes(&bits, sizeof(bits)); +#else + writer.PutBytes(&xcpt, sizeof(xcpt)); +#endif + } + else + { + ExceptionStub *stub = nsnull; + nsresult rv = xcpt->QueryInterface(kExceptionStubID, (void **) &stub); + if (NS_SUCCEEDED(rv) && (stub->Stub()->PeerID() == peer)) + { + // send the wrapper instance back to the peer + DConAddr p = stub->Stub()->Instance(); + writer.PutBytes(&p, sizeof(p)); + } + else + { + // create instance wrapper + + const nsID &iid = nsIException::GetIID(); + nsCOMPtr<nsIInterfaceInfo> iinfo; + rv = GetInterfaceInfo(iid, getter_AddRefs(iinfo)); + if (NS_FAILED(rv)) + return rv; + + DConnectInstance *wrapper = nsnull; + + // first try to find an existing wrapper for the given object + if (!FindInstanceAndAddRef(peer, xcpt, &iid, &wrapper)) + { + wrapper = new DConnectInstance(peer, iinfo, xcpt); + if (!wrapper) + return NS_ERROR_OUT_OF_MEMORY; + + rv = StoreInstance(wrapper); + if (NS_FAILED(rv)) + { + delete wrapper; + return rv; + } + + // reference the newly created wrapper + wrapper->AddRef(); + } + + // increase the second, IPC-only, reference counter (mandatory before + // trying wrappers.AppendElement() to make sure ReleaseIPC() will remove + // the wrapper from the instance map on failure) + wrapper->AddRefIPC(); + + if (!wrappers.AppendElement(wrapper)) + { + wrapper->ReleaseIPC(); + wrapper->Release(); + return NS_ERROR_OUT_OF_MEMORY; + } + + // wrapper remains referenced when passing it to the client + // (will be released upon DCON_OP_RELEASE) + + // send address of the instance wrapper, and set the low bit to indicate + // to the remote party that this is a remote instance wrapper. + PtrBits bits = ((PtrBits)(uintptr_t) wrapper) | PTRBITS_REMOTE_BIT; + writer.PutBytes(&bits, sizeof(bits)); + + // we want to cache fields to minimize the number of IPC calls when + // accessing exception data on the peer side + cache_fields = PR_TRUE; + } + NS_IF_RELEASE(stub); + } + } + + if (!cache_fields) + return NS_OK; + + nsresult rv; + nsXPIDLCString str; + PRUint32 num; + + // message + rv = xcpt->GetMessage(getter_Copies(str)); + if (NS_SUCCEEDED (rv)) + { + PRUint32 len = str.Length(); + nsACString::const_iterator begin; + const char *data = str.BeginReading(begin).get(); + writer.PutInt32(len); + writer.PutBytes(data, len); + } + else + writer.PutInt32(0); + + // result + nsresult res = 0; + xcpt->GetResult(&res); + writer.PutInt32(res); + + // name + rv = xcpt->GetName(getter_Copies(str)); + if (NS_SUCCEEDED (rv)) + { + PRUint32 len = str.Length(); + nsACString::const_iterator begin; + const char *data = str.BeginReading(begin).get(); + writer.PutInt32(len); + writer.PutBytes(data, len); + } + else + writer.PutInt32(0); + + // filename + rv = xcpt->GetFilename(getter_Copies(str)); + if (NS_SUCCEEDED (rv)) + { + PRUint32 len = str.Length(); + nsACString::const_iterator begin; + const char *data = str.BeginReading(begin).get(); + writer.PutInt32(len); + writer.PutBytes(data, len); + } + else + writer.PutInt32(0); + + // lineNumber + num = 0; + xcpt->GetLineNumber(&num); + writer.PutInt32(num); + + // columnNumber + num = 0; + xcpt->GetColumnNumber(&num); + writer.PutInt32(num); + + return writer.HasError() ? NS_ERROR_OUT_OF_MEMORY : NS_OK; +} + +nsresult +ipcDConnectService::DeserializeException(ipcMessageReader &reader, + PRUint32 peer, + nsIException **xcpt) +{ + NS_ASSERTION (xcpt, "NULL"); + if (!xcpt) + return NS_ERROR_INVALID_POINTER; + + nsresult rv; + PRUint32 len; + + PtrBits bits = 0; + reader.GetBytes(&bits, sizeof(DConAddr)); + if (reader.HasError()) + return NS_ERROR_INVALID_ARG; + + if (bits & PTRBITS_REMOTE_BIT) + { + // pointer is a peer-side exception instance wrapper, + // read cahced exception data and create a stub for it. + + nsCAutoString message; + len = reader.GetInt32(); + if (len) + { + message.SetLength(len); + char *buf = message.BeginWriting(); + reader.GetBytes(buf, len); + } + + nsresult result = reader.GetInt32(); + + nsCAutoString name; + len = reader.GetInt32(); + if (len) + { + name.SetLength(len); + char *buf = name.BeginWriting(); + reader.GetBytes(buf, len); + } + + nsCAutoString filename; + len = reader.GetInt32(); + if (len) + { + filename.SetLength(len); + char *buf = filename.BeginWriting(); + reader.GetBytes(buf, len); + } + + PRUint32 lineNumber = reader.GetInt32(); + PRUint32 columnNumber = reader.GetInt32(); + + if (reader.HasError()) + rv = NS_ERROR_INVALID_ARG; + else + { + DConAddr addr = (DConAddr) (bits & ~PTRBITS_REMOTE_BIT); + nsRefPtr<DConnectStub> stub; + rv = CreateStub(nsIException::GetIID(), peer, addr, + getter_AddRefs(stub)); + if (NS_SUCCEEDED(rv)) + { + // create a special exception "stub" with cached error info + ExceptionStub *xcptStub = + new ExceptionStub (message, result, + name, filename, + lineNumber, columnNumber, + stub); + if (xcptStub) + { + *xcpt = xcptStub; + NS_ADDREF(xcptStub); + } + else + rv = NS_ERROR_OUT_OF_MEMORY; + } + } + } + else if (bits) + { + // pointer is to our instance wrapper for nsIException we've sent before + // (the remote method we've called had called us back and got an exception + // from us that it decided to return as its own result). Replace it with + // the real instance. + DConnectInstance *wrapper = (DConnectInstance *) bits; + if (CheckInstanceAndAddRef(wrapper, peer)) + { + *xcpt = (nsIException *) wrapper->RealInstance(); + NS_ADDREF(wrapper->RealInstance()); + wrapper->Release(); + rv = NS_OK; + } + else + { + NS_NOTREACHED("instance wrapper not found"); + rv = NS_ERROR_INVALID_ARG; + } + } + else + { + // the peer explicitly passed us a NULL exception to indicate that the + // exception on the current thread should be reset + *xcpt = NULL; + return NS_OK; + } + + + return rv; +} + +//----------------------------------------------------------------------------- + +DConnectStub::~DConnectStub() +{ +#ifdef IPC_LOGGING + if (IPC_LOG_ENABLED()) + { + const char *name = NULL; + mIInfo->GetNameShared(&name); + LOG(("{%p} DConnectStub::<dtor>(): peer=%d instance=0x%Lx {%s}\n", + this, mPeerID, mInstance, name)); + } +#endif + + // release the cached nsISupports instance if it's not the same object + if (mCachedISupports != 0 && mCachedISupports != this) + NS_RELEASE(mCachedISupports); +} + +NS_IMETHODIMP_(nsrefcnt) +DConnectStub::AddRef() +{ + nsrefcnt count; + count = PR_AtomicIncrement((PRInt32*)&mRefCnt); + NS_LOG_ADDREF(this, count, "DConnectStub", sizeof(*this)); + return count; +} + +NS_IMETHODIMP_(nsrefcnt) +DConnectStub::Release() +{ + nsrefcnt count; + + nsRefPtr <ipcDConnectService> dConnect (ipcDConnectService::GetInstance()); + if (dConnect) + { + // lock the stub lock on every release to make sure that once the counter + // drops to zero, we delete the stub from the set of stubs before a new + // request to create a stub on other thread tries to find the existing + // stub in the set (wchich could otherwise AddRef the object after it had + // Released to zero and pass it to the client right before its + // destruction). + nsAutoLock stubLock (dConnect->StubLock()); + + count = PR_AtomicDecrement((PRInt32 *)&mRefCnt); + NS_LOG_RELEASE(this, count, "DConnectStub"); + + + #ifdef IPC_LOGGING + if (IPC_LOG_ENABLED()) + { + const char *name; + mIInfo->GetNameShared(&name); + LOG(("{%p} DConnectStub::Release(): peer=%d instance=0x%Lx {%s}, new count=%d\n", + this, mPeerID, mInstance, name, count)); + } + #endif + + // mRefCntLevels may already be empty here (due to the "stabilize" trick below) + if (mRefCntLevels.GetSize() > 0) + { + nsrefcnt top = (nsrefcnt) (long) mRefCntLevels.Peek(); + NS_ASSERTION(top <= count + 1, "refcount is beyond the top level"); + + if (top == count + 1) + { + // refcount dropped to a value stored in ipcDConnectService::CreateStub. + // Send a RELEASE request to the peer (see also AddRefIPC). + + // remove the top refcount value + mRefCntLevels.Pop(); + + if (0 == count) + { + // this is the last reference, remove from the set before we leave + // the lock, to provide atomicity of these two operations + dConnect->DeleteStub (this); + + NS_ASSERTION(mRefCntLevels.GetSize() == 0, "refcnt levels are still left"); + } + + // leave the lock before sending a message + stubLock.unlock(); + + nsresult rv; + + DConnectRelease msg; + msg.opcode_major = DCON_OP_RELEASE; + msg.opcode_minor = 0; + msg.flags = 0; + msg.request_index = 0; // not used, set to some unused value + 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"); + } + } + } + else + { + count = PR_AtomicDecrement((PRInt32 *)&mRefCnt); + NS_LOG_RELEASE(this, count, "DConnectStub"); + } + + if (0 == count) + { + mRefCnt = 1; /* stabilize */ + delete this; + return 0; + } + + return count; +} + +NS_IMETHODIMP +DConnectStub::QueryInterface(const nsID &aIID, void **aInstancePtr) +{ + // used to discover if this is a DConnectStub instance. + if (aIID.Equals(kDConnectStubID)) + { + *aInstancePtr = this; + NS_ADDREF_THIS(); + return NS_OK; + } + + // In order to truely support the COM Identity Rule across processes, + // we need to make the following code work: + // + // IFoo *foo = ... + // nsISupports unk; + // foo->QueryInterface(NS_GET_IID(nsISupports), (void **) &unk); + // unk->Release(); + // nsISupports unk2; + // foo->QueryInterface(NS_GET_IID(nsISupports), (void **) &unk2); + // Assert (unk == unk2); + // + // I.e. querying nsISupports on the same object must always return the same + // pointer, even if the nsISupports object returned for the first time is + // released before it is requested for the second time, as long as the + // original object is kept alive (referenced by the client) between these + // two queries. + // + // This is done by remembering the nsISupports stub returned by the peer + // when nsISupports is queried for the first time. The remembered stub, when + // it is not the same as this object, is strongly referenced in order to + // keep it alive (and therefore have the same pointer value) as long as this + // object is alive. + // + // Besides supporting the Identity Rule, this also reduces the number of IPC + // calls, since an IPC call requesting nsISupports will be done only once + // per every stub object. + + nsRefPtr <ipcDConnectService> dConnect (ipcDConnectService::GetInstance()); + NS_ASSERTION(dConnect, "no ipcDConnectService (uninitialized?)"); + if (!dConnect) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv; + PRBool needISupports = aIID.Equals(NS_GET_IID(nsISupports)); + + if (needISupports) + { + // XXX it would be sufficient to use cmpxchg here to protect access to + // mCachedISupports, but NSPR doesn't provide cross-platform cmpxchg + // functionality, so we have to use a shared lock instead... + PR_Lock(dConnect->StubQILock()); + + // check if we have already got a nsISupports stub for this object + if (mCachedISupports != 0) + { + *aInstancePtr = mCachedISupports; + NS_ADDREF(mCachedISupports); + + PR_Unlock(dConnect->StubQILock()); + return NS_OK; + } + + // check if this object is nsISupports itself + { + nsIID *iid = 0; + rv = mIInfo->GetInterfaceIID(&iid); + NS_ASSERTION(NS_SUCCEEDED(rv) && iid, + "nsIInterfaceInfo::GetInterfaceIID failed"); + if (NS_SUCCEEDED(rv) && iid && + iid->Equals(NS_GET_IID(nsISupports))) + { + nsMemory::Free((void*)iid); + + // nsISupports is queried on nsISupports, return ourselves + *aInstancePtr = this; + NS_ADDREF_THIS(); + // cache ourselves weakly + mCachedISupports = this; + + PR_Unlock(dConnect->StubQILock()); + return NS_OK; + } + if (iid) + nsMemory::Free((void*)iid); + } + + // stub lock remains held until we've queried the peer + } + + // else, we need to query the peer object by making an IPC call + +#ifdef IPC_LOGGING + if (IPC_LOG_ENABLED()) + { + const char *name; + mIInfo->GetNameShared(&name); + const char *nameQ; + nsCOMPtr <nsIInterfaceInfo> iinfoQ; + dConnect->GetInterfaceInfo(aIID, getter_AddRefs(iinfoQ)); + if (iinfoQ) { + iinfoQ->GetNameShared(&nameQ); + LOG(("calling QueryInterface {%s} on peer object " + "(stub=%p, instance=0x%Lx {%s})\n", + nameQ, this, mInstance, name)); + } + } +#endif + + DConnectSetupQueryInterface msg; + msg.opcode_minor = DCON_OP_SETUP_QUERY_INTERFACE; + msg.iid = aIID; + msg.instance = mInstance; + + rv = SetupPeerInstance(mPeerID, &msg, sizeof(msg), aInstancePtr); + + if (needISupports) + { + if (NS_SUCCEEDED(rv)) + { + // cache the nsISupports object (SetupPeerInstance returns DConnectStub) + mCachedISupports = (DConnectStub *) *aInstancePtr; + // use a weak reference if nsISupports is the same object as us + if (this != mCachedISupports) + NS_ADDREF(mCachedISupports); + } + + PR_Unlock(dConnect->StubQILock()); + } + + return rv; +} + +NS_IMETHODIMP +DConnectStub::GetInterfaceInfo(nsIInterfaceInfo **aInfo) +{ + NS_ADDREF(*aInfo = mIInfo); + return NS_OK; +} + +NS_IMETHODIMP +DConnectStub::CallMethod(PRUint16 aMethodIndex, + const nsXPTMethodInfo *aInfo, + nsXPTCMiniVariant *aParams) +{ + LOG(("DConnectStub::CallMethod [methodIndex=%hu]\n", aMethodIndex)); + + nsresult rv; + + // reset the exception early. this is necessary because we may return a + // failure from here without setting an exception (which might be expected + // by the caller to detect the error origin: the interface we are stubbing + // may indicate in some way that it always sets the exception info on + // failure, therefore an "infoless" failure means the origin is RPC). + // besides that, resetting the excetion before every IPC call is exactly the + // same thing as Win32 RPC does, so doing this is useful for getting + // similarity in behaviors. + + nsCOMPtr <nsIExceptionService> es; + es = do_GetService (NS_EXCEPTIONSERVICE_CONTRACTID, &rv); + if (NS_FAILED (rv)) + return rv; + nsCOMPtr <nsIExceptionManager> em; + rv = es->GetCurrentExceptionManager (getter_AddRefs(em)); + if (NS_FAILED (rv)) + return rv; + rv = em->SetCurrentException(NULL); + if (NS_FAILED (rv)) + return rv; + + // ensure ipcDConnectService is not deleted before we finish + nsRefPtr <ipcDConnectService> dConnect (ipcDConnectService::GetInstance()); + if (!dConnect) + return NS_ERROR_FAILURE; + + // dump arguments + + PRUint8 i, paramCount = aInfo->GetParamCount(); + +#ifdef IPC_LOGGING + if (IPC_LOG_ENABLED()) + { + const char *name; + nsCOMPtr<nsIInterfaceInfo> iinfo; + GetInterfaceInfo(getter_AddRefs(iinfo)); + iinfo->GetNameShared(&name); + LOG((" instance=0x%Lx {%s}\n", mInstance, name)); + LOG((" name=%s\n", aInfo->GetName())); + LOG((" param-count=%u\n", (PRUint32) paramCount)); + } +#endif + + + ipcMessageWriter writer(16 * paramCount); + + // INVOKE message header + DConnectInvoke invoke; + invoke.opcode_major = DCON_OP_INVOKE; + invoke.opcode_minor = 0; + invoke.flags = 0; + invoke.request_index = NewRequestIndex(); + invoke.instance = mInstance; + invoke.method_index = aMethodIndex; + + LOG((" request-index=%d\n", (PRUint32) invoke.request_index)); + + 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; i<paramCount; ++i) + { + const nsXPTParamInfo ¶mInfo = aInfo->GetParam(i); + + if (paramInfo.IsIn() && !paramInfo.IsDipper()) + { + const nsXPTType &type = paramInfo.GetType(); + + if (type.IsInterfacePointer()) + { + nsID iid; + rv = dConnect->GetIIDForMethodParam(mIInfo, aInfo, paramInfo, type, + aMethodIndex, aParams, PR_FALSE, iid); + if (NS_SUCCEEDED(rv)) + rv = dConnect->SerializeInterfaceParam(writer, mPeerID, iid, + (nsISupports *) aParams[i].val.p, + wrappers); + } + else + rv = SerializeParam(writer, type, aParams[i]); + + AssertMsgBreak(NS_SUCCEEDED(rv), ("i=%d rv=%#x\n", i, rv)); + } + else if ((paramInfo.IsOut() || paramInfo.IsRetval()) && !aParams[i].val.p) + { + // report error early if NULL pointer is passed as an output parameter + rv = NS_ERROR_NULL_POINTER; + AssertMsgFailedBreak(("i=%d IsOut=%d IsRetval=%d NS_ERROR_NULL_POINTER\n", i, paramInfo.IsOut(), paramInfo.IsRetval())); + break; + } + } + + if (NS_FAILED(rv)) + { + // INVOKE message wasn't sent; clean up wrappers + dConnect->ReleaseWrappers(wrappers, mPeerID); + return rv; + } + + // serialize input array parameters after everything else since the + // deserialization procedure will need to get a size_is value which may be + // stored in any preceeding or following param + for (i=0; i<paramCount; ++i) + { + const nsXPTParamInfo ¶mInfo = aInfo->GetParam(i); + + if (paramInfo.GetType().IsArray() && + paramInfo.IsIn() && !paramInfo.IsDipper()) + { + rv = SerializeArrayParam(dConnect, writer, mPeerID, mIInfo, aMethodIndex, + *aInfo, aParams, PR_FALSE, paramInfo, + aParams[i].val.p, wrappers); + if (NS_FAILED(rv)) + { + // INVOKE message wasn't sent; clean up wrappers + dConnect->ReleaseWrappers(wrappers, mPeerID); + return rv; + } + } + } + + // temporarily disable the DConnect target observer to block normal processing + // of pending messages through the event queue. + IPC_DISABLE_MESSAGE_OBSERVER_FOR_SCOPE(kDConnectTargetID); + + rv = IPC_SendMessage(mPeerID, kDConnectTargetID, + writer.GetBuffer(), + writer.GetSize()); + LOG(("DConnectStub::CallMethod: IPC_SendMessage()=%08X\n", rv)); + if (NS_FAILED(rv)) + { + // INVOKE message wasn't delivered; clean up wrappers + dConnect->ReleaseWrappers(wrappers, mPeerID); + 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(mPeerID, &invoke); + + do + { + rv = IPC_WaitMessage(IPC_SENDER_ANY, kDConnectTargetID, + &completion.GetSelector(), &completion, + DCON_WAIT_TIMEOUT); + LOG(("DConnectStub::CallMethod: IPC_WaitMessage()=%08X\n", rv)); + if (NS_FAILED(rv)) + { + // INVOKE message wasn't received; clean up wrappers + dConnect->ReleaseWrappers(wrappers, mPeerID); + return rv; + } + } + while (completion.IsPending()); + + ipcMessageReader reader(completion.Params(), completion.ParamsLen()); + + rv = completion.GetResult(); + if (NS_SUCCEEDED(rv)) + { + PRUint8 i; + + // handle out-params and retvals: DCON_OP_INVOKE_REPLY has the data + for (i=0; i<paramCount; ++i) + { + const nsXPTParamInfo ¶mInfo = aInfo->GetParam(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! This pass is + // also used to deserialize arrays (array data goes after all other params). + for (i=0; i<paramCount && NS_SUCCEEDED(rv); ++i) + { + const nsXPTParamInfo ¶mInfo = aInfo->GetParam(i); + if ((paramInfo.IsOut() || paramInfo.IsRetval()) && aParams[i].val.p) + { + const nsXPTType &type = paramInfo.GetType(); + if (type.IsInterfacePointer()) + { + // grab the DConAddr value temporarily stored in the param, restore + // the pointer and free the temporarily allocated memory. + DConAddrPlusPtr *dptr = (DConAddrPlusPtr *)aParams[i].val.p; + PtrBits bits = dptr->addr; + aParams[i].val.p = dptr->p; + nsMemory::Free((void *)dptr); + + // DeserializeInterfaceParamBits needs IID only if it's a remote object + nsID iid; + if (bits & PTRBITS_REMOTE_BIT) + rv = dConnect->GetIIDForMethodParam(mIInfo, aInfo, paramInfo, type, + aMethodIndex, aParams, PR_FALSE, + iid); + if (NS_SUCCEEDED(rv)) + { + nsISupports *obj = nsnull; + rv = dConnect->DeserializeInterfaceParamBits(bits, mPeerID, iid, obj); + if (NS_SUCCEEDED(rv)) + *(void **)aParams[i].val.p = obj; + } + } + else if (type.IsArray()) + { + void *array = nsnull; + rv = DeserializeArrayParam(dConnect, reader, mPeerID, mIInfo, + aMethodIndex, *aInfo, aParams, PR_FALSE, + paramInfo, PR_TRUE, array); + if (NS_SUCCEEDED(rv)) + *(void **)aParams[i].val.p = array; + } + } + } + } + + if (completion.Reply()->flags & DCON_OP_FLAGS_REPLY_EXCEPTION) + { + LOG(("got nsIException instance, will create a stub\n")); + + nsIException *xcpt = nsnull; + rv = dConnect->DeserializeException (reader, mPeerID, &xcpt); + if (NS_SUCCEEDED(rv)) + { + rv = em->SetCurrentException(xcpt); + NS_IF_RELEASE(xcpt); + } + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to deserialize/set exception"); + } + + return NS_SUCCEEDED(rv) ? completion.GetResult() : rv; +} + +//----------------------------------------------------------------------------- + +class DConnectSetupCompletion : public DConnectCompletion +{ +public: + DConnectSetupCompletion(PRUint32 peer, const DConnectSetup *setup) + : DConnectCompletion(peer, DCON_OP_SETUP_REPLY, 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; + } + + if (opLen < sizeof(DConnectSetupReply)) + { + NS_NOTREACHED("unexpected response size"); + mStatus = NS_ERROR_UNEXPECTED; + return; + } + + const DConnectSetupReply *reply = (const DConnectSetupReply *) op; + + LOG(("got SETUP_REPLY: status=%x instance=0x%Lx\n", reply->status, reply->instance)); + + mStatus = reply->status; + + if (NS_SUCCEEDED(reply->status)) + { + // ensure ipcDConnectService is not deleted before we finish + nsRefPtr <ipcDConnectService> dConnect (ipcDConnectService::GetInstance()); + nsresult rv; + if (dConnect) + rv = dConnect->CreateStub(mSetup->iid, sender, reply->instance, + getter_AddRefs(mStub)); + else + rv = NS_ERROR_FAILURE; + if (NS_FAILED(rv)) + mStatus = rv; + } + + if (reply->flags & DCON_OP_FLAGS_REPLY_EXCEPTION) + { + const PRUint8 *params = ((const PRUint8 *) op) + sizeof (DConnectSetupReply); + const PRUint32 paramsLen = opLen - sizeof (DConnectSetupReply); + + ipcMessageReader reader(params, paramsLen); + + LOG(("got nsIException instance, will create a stub\n")); + + nsresult rv; + nsCOMPtr <nsIExceptionService> es; + es = do_GetService (NS_EXCEPTIONSERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr <nsIExceptionManager> em; + rv = es->GetCurrentExceptionManager (getter_AddRefs(em)); + if (NS_SUCCEEDED(rv)) + { + // ensure ipcDConnectService is not deleted before we finish + nsRefPtr <ipcDConnectService> dConnect (ipcDConnectService::GetInstance()); + if (dConnect) + { + nsIException *xcpt = nsnull; + rv = dConnect->DeserializeException (reader, sender, &xcpt); + if (NS_SUCCEEDED(rv)) + { + rv = em->SetCurrentException(xcpt); + NS_IF_RELEASE(xcpt); + } + } + else + rv = NS_ERROR_UNEXPECTED; + } + } + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to deserialize/set exception"); + 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<DConnectStub> mStub; +}; + +// static +nsresult +SetupPeerInstance(PRUint32 aPeerID, DConnectSetup *aMsg, PRUint32 aMsgLen, + void **aInstancePtr) +{ + *aInstancePtr = nsnull; + + aMsg->opcode_major = DCON_OP_SETUP; + aMsg->flags = 0; + aMsg->request_index = NewRequestIndex(); + + // temporarily disable the DConnect target observer to block normal processing + // of pending messages through the event queue. + IPC_DISABLE_MESSAGE_OBSERVER_FOR_SCOPE(kDConnectTargetID); + + // send SETUP message, expect SETUP_REPLY + + nsresult rv = IPC_SendMessage(aPeerID, kDConnectTargetID, + (const PRUint8 *) aMsg, aMsgLen); + if (NS_FAILED(rv)) + return rv; + + DConnectSetupCompletion completion(aPeerID, 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(IPC_SENDER_ANY, kDConnectTargetID, + &completion.GetSelector(), &completion, + DCON_WAIT_TIMEOUT); + if (NS_FAILED(rv)) + break; + + rv = completion.GetStub(aInstancePtr); + } + while (NS_SUCCEEDED(rv) && *aInstancePtr == nsnull); + + return rv; +} + +//----------------------------------------------------------------------------- + +#if defined(DCONNECT_MULTITHREADED) && !defined(DCONNECT_WITH_IPRT_REQ_POOL) + +class DConnectWorker : public nsIRunnable +{ +public: + // no reference counting + NS_IMETHOD_(nsrefcnt) AddRef() { return 1; } + NS_IMETHOD_(nsrefcnt) Release() { return 1; } + NS_IMETHOD QueryInterface(const nsIID &aIID, void **aInstancePtr); + + NS_DECL_NSIRUNNABLE + + DConnectWorker(ipcDConnectService *aDConnect) : mDConnect (aDConnect), mIsRunnable (PR_FALSE) {} + NS_HIDDEN_(nsresult) Init(); + NS_HIDDEN_(void) Join() { mThread->Join(); }; + NS_HIDDEN_(bool) IsRunning() { return mIsRunnable; }; + +private: + nsCOMPtr <nsIThread> mThread; + ipcDConnectService *mDConnect; + + // Indicate if thread might be quickly joined on shutdown. + volatile bool mIsRunnable; +}; + +NS_IMPL_QUERY_INTERFACE1(DConnectWorker, nsIRunnable) + +nsresult +DConnectWorker::Init() +{ + return NS_NewThread(getter_AddRefs(mThread), this, 0, PR_JOINABLE_THREAD); +} + +NS_IMETHODIMP +DConnectWorker::Run() +{ + LOG(("DConnect Worker thread started.\n")); + + mIsRunnable = PR_TRUE; + + nsAutoMonitor mon(mDConnect->mPendingMon); + + while (!mDConnect->mDisconnected) + { + DConnectRequest *request = mDConnect->mPendingQ.First(); + if (!request) + { + mDConnect->mWaitingWorkers++; + { + // Note: we attempt to enter mWaitingWorkersMon from under mPendingMon + // here, but it should be safe because it's the only place where it + // happens. We could exit mPendingMon first, but we need to wait on it + // shorltly afterwards, which in turn will require us to enter it again + // just to exit immediately and start waiting. This seems to me a bit + // stupid (exit->enter->exit->wait). + nsAutoMonitor workersMon(mDConnect->mWaitingWorkersMon); + workersMon.NotifyAll(); + } + + nsresult rv = mon.Wait(); + mDConnect->mWaitingWorkers--; + + if (NS_FAILED(rv)) + break; + } + else + { + LOG(("DConnect Worker thread got request.\n")); + + // remove the request from the queue + mDConnect->mPendingQ.RemoveFirst(); + + PRBool pendingQEmpty = mDConnect->mPendingQ.IsEmpty(); + mon.Exit(); + + if (pendingQEmpty) + { + nsAutoMonitor workersMon(mDConnect->mWaitingWorkersMon); + workersMon.NotifyAll(); + } + + // request is processed outside the queue monitor + mDConnect->OnIncomingRequest(request->peer, request->op, request->opLen); + delete request; + + mon.Enter(); + } + } + + mIsRunnable = PR_FALSE; + + LOG(("DConnect Worker thread stopped.\n")); + return NS_OK; +} + +// called only on DConnect message thread +nsresult +ipcDConnectService::CreateWorker() +{ + DConnectWorker *worker = new DConnectWorker(this); + if (!worker) + return NS_ERROR_OUT_OF_MEMORY; + nsresult rv = worker->Init(); + if (NS_SUCCEEDED(rv)) + { + nsAutoLock lock(mLock); + /* tracking an illegal join in Shutdown. */ + NS_ASSERTION(!mDisconnected, "CreateWorker racing Shutdown"); + if (!mWorkers.AppendElement(worker)) + rv = NS_ERROR_OUT_OF_MEMORY; + } + if (NS_FAILED(rv)) + delete worker; + return rv; +} + +#endif // defined(DCONNECT_MULTITHREADED) && !defined(DCONNECT_WITH_IPRT_REQ_POOL) + +//----------------------------------------------------------------------------- + +ipcDConnectService::ipcDConnectService() + : mLock(NULL) + , mStubLock(NULL) + , mDisconnected(PR_TRUE) + , mStubQILock(NULL) +#if defined(DCONNECT_WITH_IPRT_REQ_POOL) + , mhReqPool(NIL_RTREQPOOL) +#endif +{ +} + +PR_STATIC_CALLBACK(PLDHashOperator) +EnumerateInstanceMapAndDelete (const DConnectInstanceKey::Key &aKey, + DConnectInstance *aData, + void *userArg) +{ + // this method is to be called on ipcDConnectService shutdown only + // (after which no DConnectInstances may exist), so forcibly delete them + // disregarding the reference counter + +#ifdef IPC_LOGGING + if (IPC_LOG_ENABLED()) + { + const char *name; + aData->InterfaceInfo()->GetNameShared(&name); + LOG(("ipcDConnectService: WARNING: deleting unreleased " + "instance=%p iface=%p {%s}\n", aData, aData->RealInstance(), name)); + } +#endif + + delete aData; + return PL_DHASH_NEXT; +} + +ipcDConnectService::~ipcDConnectService() +{ + if (!mDisconnected) + Shutdown(); + + mInstance = nsnull; + PR_DestroyLock(mStubQILock); + PR_DestroyLock(mStubLock); + PR_DestroyLock(mLock); +#if defined(DCONNECT_WITH_IPRT_REQ_POOL) + RTReqPoolRelease(mhReqPool); + mhReqPool = NIL_RTREQPOOL; +#endif +} + +//----------------------------------------------------------------------------- + +nsresult +ipcDConnectService::Init() +{ + nsresult rv; + + LOG(("ipcDConnectService::Init.\n")); + + rv = IPC_DefineTarget(kDConnectTargetID, this); + if (NS_FAILED(rv)) + return rv; + + rv = IPC_AddClientObserver(this); + if (NS_FAILED(rv)) + return rv; + + mLock = PR_NewLock(); + if (!mLock) + return NS_ERROR_OUT_OF_MEMORY; + + if (!mInstances.Init()) + return NS_ERROR_OUT_OF_MEMORY; + if (!mInstanceSet.Init()) + return NS_ERROR_OUT_OF_MEMORY; + + mStubLock = PR_NewLock(); + if (!mStubLock) + return NS_ERROR_OUT_OF_MEMORY; + + if (!mStubs.Init()) + return NS_ERROR_OUT_OF_MEMORY; + + mIIM = do_GetService(NS_INTERFACEINFOMANAGER_SERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + mStubQILock = PR_NewLock(); + if (!mStubQILock) + return NS_ERROR_OUT_OF_MEMORY; + +#if defined(DCONNECT_MULTITHREADED) +# if defined(DCONNECT_WITH_IPRT_REQ_POOL) + int vrc = RTReqPoolCreate(1024 /*cMaxThreads*/, 10*RT_MS_1SEC /*cMsMinIdle*/, + 8 /*cThreadsPushBackThreshold */, RT_MS_1SEC /* cMsMaxPushBack */, + "DCon", &mhReqPool); + if (RT_FAILURE(vrc)) + { + mhReqPool = NIL_RTREQPOOL; + return NS_ERROR_FAILURE; + } + + mDisconnected = PR_FALSE; + +# else + + mPendingMon = nsAutoMonitor::NewMonitor("DConnect pendingQ monitor"); + if (!mPendingMon) + return NS_ERROR_OUT_OF_MEMORY; + + mWaitingWorkers = 0; + + mWaitingWorkersMon = nsAutoMonitor::NewMonitor("DConnect waiting workers monitor"); + if (!mWaitingWorkersMon) + return NS_ERROR_OUT_OF_MEMORY; + + /* The DConnectWorker::Run method checks the ipcDConnectService::mDisconnected. + * So mDisconnect must be set here to avoid an immediate exit of the worker thread. + */ + mDisconnected = PR_FALSE; + + // create a single worker thread + rv = CreateWorker(); + if (NS_FAILED(rv)) + { + mDisconnected = PR_TRUE; + return rv; + } + +# endif +#else + + mDisconnected = PR_FALSE; + +#endif + + mInstance = this; + + LOG(("ipcDConnectService::Init NS_OK.\n")); + return NS_OK; +} + +void +ipcDConnectService::Shutdown() +{ + { + // set the disconnected flag to make sensitive public methods + // unavailale from other (non worker) threads. + nsAutoLock lock(mLock); + mDisconnected = PR_TRUE; + } + +#if defined(DCONNECT_MULTITHREADED) +# if defined(DCONNECT_WITH_IPRT_REQ_POOL) + +# if defined(DCONNECT_STATS) + fprintf(stderr, "ipcDConnectService Stats\n"); + fprintf(stderr, + " => number of worker threads: %llu (created %llu)\n" + " => requests processed: %llu\n" + " => avg requests process time: %llu ns\n" + " => avg requests waiting time: %llu ns\n", + RTReqPoolGetStat(mhReqPool, RTREQPOOLSTAT_THREADS), + RTReqPoolGetStat(mhReqPool, RTREQPOOLSTAT_THREADS_CREATED), + RTReqPoolGetStat(mhReqPool, RTREQPOOLSTAT_REQUESTS_PROCESSED), + RTReqPoolGetStat(mhReqPool, RTREQPOOLSTAT_NS_AVERAGE_REQ_PROCESSING), + RTReqPoolGetStat(mhReqPool, RTREQPOOLSTAT_NS_AVERAGE_REQ_QUEUED) + ); +# endif + + RTReqPoolRelease(mhReqPool); + mhReqPool = NIL_RTREQPOOL; + +# else + + { + // remove all pending messages and wake up all workers. + // mDisconnected is true here and they will terminate execution after + // processing the last request. + nsAutoMonitor mon(mPendingMon); + mPendingQ.DeleteAll(); + mon.NotifyAll(); + } + +#if defined(DCONNECT_STATS) + fprintf(stderr, "ipcDConnectService Stats\n"); + fprintf(stderr, " => number of worker threads: %d\n", mWorkers.Count()); + LOG(("ipcDConnectService Stats\n")); + LOG((" => number of worker threads: %d\n", mWorkers.Count())); +#endif + + + // Iterate over currently running worker threads + // during VBOX_XPCOM_SHUTDOWN_TIMEOUT_MS, join() those who + // exited a working loop and abandon ones which have not + // managed to do that when timeout occurred. + LOG(("Worker threads: %d\n", mWorkers.Count())); + uint64_t tsStart = RTTimeMilliTS(); + while ((tsStart + VBOX_XPCOM_SHUTDOWN_TIMEOUT_MS ) > RTTimeMilliTS() && mWorkers.Count() > 0) + { + // Some array elements might be deleted while iterating. Going from the last + // to the first array element (intentionally) in order to do not conflict with + // array indexing once element is deleted. + for (int i = mWorkers.Count() - 1; i >= 0; i--) + { + DConnectWorker *worker = NS_STATIC_CAST(DConnectWorker *, mWorkers[i]); + if (worker->IsRunning() == PR_FALSE) + { + LOG(("Worker %p joined.\n", worker)); + worker->Join(); + delete worker; + mWorkers.RemoveElementAt(i); + } + } + + /* Double-ckeck if we already allowed to quit. */ + if ((tsStart + VBOX_XPCOM_SHUTDOWN_TIMEOUT_MS ) < RTTimeMilliTS() || mWorkers.Count() == 0) + break; + + // Relax a bit before the next round. + RTThreadSleep(10); + } + + LOG(("There are %d thread(s) left.\n", mWorkers.Count())); + + // If there are some running threads left, terminate the process. + if (mWorkers.Count() > 0) + exit(1); + + + nsAutoMonitor::DestroyMonitor(mWaitingWorkersMon); + nsAutoMonitor::DestroyMonitor(mPendingMon); + +# endif +#endif + + // make sure we have released all instances + mInstances.EnumerateRead(EnumerateInstanceMapAndDelete, nsnull); + + mInstanceSet.Clear(); + mInstances.Clear(); + + // clear the stub table + // (this will not release stubs -- it's the client's responsibility) + mStubs.Clear(); +} + +// 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, + nsXPTCMiniVariant *dispatchParams, + PRBool isXPTCVariantArray, + 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 = (nsID *) GET_PARAM(dispatchParams, isXPTCVariantArray, argnum).val.p; + if (!p) + return NS_ERROR_UNEXPECTED; + + result = *p; + } + else + rv = NS_ERROR_UNEXPECTED; + return rv; +} + +nsresult +ipcDConnectService::StoreInstance(DConnectInstance *wrapper) +{ +#ifdef IPC_LOGGING + if (IPC_LOG_ENABLED()) + { + const char *name; + wrapper->InterfaceInfo()->GetNameShared(&name); + LOG(("ipcDConnectService::StoreInstance(): instance=%p iface=%p {%s}\n", + wrapper, wrapper->RealInstance(), name)); + } +#endif + + nsresult rv = mInstanceSet.Put(wrapper); + if (NS_SUCCEEDED(rv)) + { + rv = mInstances.Put(wrapper->GetKey(), wrapper) + ? NS_OK : NS_ERROR_OUT_OF_MEMORY; + if (NS_FAILED(rv)) + mInstanceSet.Remove(wrapper); + } + return rv; +} + +void +ipcDConnectService::DeleteInstance(DConnectInstance *wrapper, + PRBool locked /* = PR_FALSE */) +{ + if (!locked) + PR_Lock(mLock); + +#ifdef IPC_LOGGING + if (IPC_LOG_ENABLED()) + { + const char *name; + wrapper->InterfaceInfo()->GetNameShared(&name); + LOG(("ipcDConnectService::DeleteInstance(): instance=%p iface=%p {%s}\n", + wrapper, wrapper->RealInstance(), name)); + } +#endif + + mInstances.Remove(wrapper->GetKey()); + mInstanceSet.Remove(wrapper); + + if (!locked) + PR_Unlock(mLock); +} + +PRBool +ipcDConnectService::FindInstanceAndAddRef(PRUint32 peer, + const nsISupports *obj, + const nsIID *iid, + DConnectInstance **wrapper) +{ + PRBool result = mInstances.Get(DConnectInstanceKey::Key(peer, obj, iid), wrapper); + if (result) + (*wrapper)->AddRef(); + return result; +} + +PRBool +ipcDConnectService::CheckInstanceAndAddRef(DConnectInstance *wrapper, PRUint32 peer) +{ + nsAutoLock lock (mLock); + + if (mInstanceSet.Contains(wrapper) && wrapper->Peer() == peer) + { + wrapper->AddRef(); + return PR_TRUE; + } + return PR_FALSE; +} + +nsresult +ipcDConnectService::StoreStub(DConnectStub *stub) +{ +#ifdef IPC_LOGGING + if (IPC_LOG_ENABLED()) + { + const char *name; + nsCOMPtr<nsIInterfaceInfo> iinfo; + stub->GetInterfaceInfo(getter_AddRefs(iinfo)); + iinfo->GetNameShared(&name); + LOG(("ipcDConnectService::StoreStub(): stub=%p instance=0x%Lx {%s}\n", + stub, stub->Instance(), name)); + } +#endif + + return mStubs.Put(stub->GetKey(), stub) + ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +void +ipcDConnectService::DeleteStub(DConnectStub *stub) +{ +#ifdef IPC_LOGGING + if (IPC_LOG_ENABLED()) + { + const char *name; + nsCOMPtr<nsIInterfaceInfo> iinfo; + stub->GetInterfaceInfo(getter_AddRefs(iinfo)); + iinfo->GetNameShared(&name); + LOG(("ipcDConnectService::DeleteStub(): stub=%p instance=0x%Lx {%s}\n", + stub, stub->Instance(), name)); + } +#endif + + // this method is intended to be called only from DConnectStub::Release(). + // the stub object is not deleted when removed from the table, because + // DConnectStub pointers are not owned by mStubs. + mStubs.Remove(stub->GetKey()); +} + +// not currently used +#if 0 +PRBool +ipcDConnectService::FindStubAndAddRef(PRUint32 peer, const DConAddr instance, + DConnectStub **stub) +{ + nsAutoLock stubLock (mStubLock); + + PRBool result = mStubs.Get(DConnectStubKey::Key(peer, instance), stub); + if (result) + NS_ADDREF(*stub); + return result; +} +#endif + +NS_IMPL_THREADSAFE_ISUPPORTS3(ipcDConnectService, ipcIDConnectService, + ipcIMessageObserver, + ipcIClientObserver) + +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); + + 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); + + 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) +{ + if (mDisconnected) + return NS_ERROR_NOT_INITIALIZED; + + const DConnectOp *op = (const DConnectOp *) aData; + + LOG (("ipcDConnectService::OnMessageAvailable: " + "senderID=%d, opcode_major=%d, index=%d\n", + aSenderID, op->opcode_major, op->request_index)); + +#if defined(DCONNECT_MULTITHREADED) +# if defined(DCONNECT_WITH_IPRT_REQ_POOL) + + void *pvDataDup = RTMemDup(aData, aDataLen); + if (RT_UNLIKELY(!pvDataDup)) + return NS_ERROR_OUT_OF_MEMORY; + int rc = RTReqPoolCallVoidNoWait(mhReqPool, (PFNRT)ProcessMessageOnWorkerThread, 4, + this, aSenderID, pvDataDup, aDataLen); + if (RT_FAILURE(rc)) + return NS_ERROR_FAILURE; + +# else + + nsAutoMonitor mon(mPendingMon); + mPendingQ.Append(new DConnectRequest(aSenderID, op, aDataLen)); + // notify a worker + mon.Notify(); + mon.Exit(); + + // Yield the cpu so a worker can get a chance to start working without too much fuss. + PR_Sleep(PR_INTERVAL_NO_WAIT); + mon.Enter(); + // examine the queue + if (mPendingQ.Count() > mWaitingWorkers) + { + // wait a little while to let the workers empty the queue. + mon.Exit(); + { + PRUint32 ticks = PR_MillisecondsToInterval(PR_MIN(mWorkers.Count() / 20 + 1, 10)); + nsAutoMonitor workersMon(mWaitingWorkersMon); + workersMon.Wait(ticks); + } + mon.Enter(); + // examine the queue again + if (mPendingQ.Count() > mWaitingWorkers) + { + // we need one more worker + nsresult rv = CreateWorker(); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create one more worker thread"); + rv = rv; + } + } + +# endif +#else + + OnIncomingRequest(aSenderID, op, aDataLen); + +#endif + + return NS_OK; +} + +struct PruneInstanceMapForPeerArgs +{ + ipcDConnectService *that; + PRUint32 clientID; + nsVoidArray &wrappers; +}; + +PR_STATIC_CALLBACK(PLDHashOperator) +PruneInstanceMapForPeer (const DConnectInstanceKey::Key &aKey, + DConnectInstance *aData, + void *userArg) +{ + PruneInstanceMapForPeerArgs *args = (PruneInstanceMapForPeerArgs *)userArg; + NS_ASSERTION(args, "PruneInstanceMapForPeerArgs is NULL"); + + if (args && args->clientID == aData->Peer()) + { + nsrefcnt countIPC = aData->ReleaseIPC(PR_TRUE /* locked */); + + LOG(("ipcDConnectService::PruneInstanceMapForPeer: " + "instance=%p: %d IPC refs to release\n", + aData, countIPC + 1)); + + // release all IPC instances of the "officially dead" client (see + // #OnRelease() to understand why it must be done under the lock). Note + // that due to true multithreading, late OnRelease() requests may still + // happen on other worker threads *after* OnClientStateChange() has been + // called, but it's OK because the instance will be removed from the map + // by the below code alreay and won't be deleted for the second time. + while (countIPC) + { + countIPC = aData->ReleaseIPC(PR_TRUE /* locked */); + aData->Release(); + } + + // collect the instance for the last release + // (we'll do it later outside the lock) + if (!args->wrappers.AppendElement(aData)) + { + NS_NOTREACHED("Not enough memory"); + // bad but what to do + aData->Release(); + } + } + return PL_DHASH_NEXT; +} + +NS_IMETHODIMP +ipcDConnectService::OnClientStateChange(PRUint32 aClientID, + PRUint32 aClientState) +{ + LOG(("ipcDConnectService::OnClientStateChange: aClientID=%d, aClientState=%d\n", + aClientID, aClientState)); + + if (aClientState == ipcIClientObserver::CLIENT_DOWN) + { + if (aClientID == IPC_SENDER_ANY) + { + // a special case: our IPC system is being shutdown, try to safely + // uninitialize everything... + Shutdown(); + } + else + { + LOG(("ipcDConnectService::OnClientStateChange: " + "pruning all instances created for peer %d...\n", aClientID)); + + nsVoidArray wrappers; + + { + nsAutoLock lock (mLock); + + // make sure we have removed all instances from instance maps + PruneInstanceMapForPeerArgs args = { this, aClientID, wrappers }; + mInstances.EnumerateRead(PruneInstanceMapForPeer, (void *)&args); + } + + LOG(("ipcDConnectService::OnClientStateChange: " + "%d lost instances\n", wrappers.Count())); + + // release all pending references left after PruneInstanceMapForPeer(). + // this may call wrapper destructors so it's important to do that + // outside the lock because destructors will release the real objects + // which may need to make asynchronous use our service + for (PRInt32 i = 0; i < wrappers.Count(); ++i) + ((DConnectInstance *) wrappers[i])->Release(); + } + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- + +#if defined(DCONNECT_WITH_IPRT_REQ_POOL) +/** + * Function called by the request thread pool to process a incoming request in + * the context of a worker thread. + */ +/* static */ DECLCALLBACK(void) +ipcDConnectService::ProcessMessageOnWorkerThread(ipcDConnectService *aThis, PRUint32 aSenderID, void *aData, PRUint32 aDataLen) +{ + if (!aThis->mDisconnected) + aThis->OnIncomingRequest(aSenderID, (const DConnectOp *)aData, aDataLen); + RTMemFree(aData); +} +#endif + +void +ipcDConnectService::OnIncomingRequest(PRUint32 peer, const DConnectOp *op, PRUint32 opLen) +{ + switch (op->opcode_major) + { + case DCON_OP_SETUP: + OnSetup(peer, (const DConnectSetup *) op, opLen); + break; + case DCON_OP_RELEASE: + OnRelease(peer, (const DConnectRelease *) op); + break; + case DCON_OP_INVOKE: + OnInvoke(peer, (const DConnectInvoke *) op, opLen); + break; + default: + NS_NOTREACHED("unknown opcode major"); + } +} + +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<nsIComponentManager> 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<nsIComponentManager> 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<nsIServiceManager> 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<nsIServiceManager> 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; + DConnectInstance *QIinstance = (DConnectInstance *)setupQI->instance; + + // make sure we've been sent a valid wrapper + if (!CheckInstanceAndAddRef(QIinstance, peer)) + { + NS_NOTREACHED("instance wrapper not found"); + rv = NS_ERROR_INVALID_ARG; + } + else + { + rv = QIinstance->RealInstance()->QueryInterface(setupQI->iid, (void **) &instance); + QIinstance->Release(); + } + break; + } + + default: + NS_NOTREACHED("unexpected minor opcode"); + rv = NS_ERROR_UNEXPECTED; + break; + } + + nsVoidArray wrappers; + + // 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<nsIInterfaceInfo> iinfo; + rv = GetInterfaceInfo(setup->iid, getter_AddRefs(iinfo)); + if (NS_SUCCEEDED(rv)) + { + nsAutoLock lock (mLock); + + // first try to find an existing wrapper for the given object + if (!FindInstanceAndAddRef(peer, instance, &setup->iid, &wrapper)) + { + wrapper = new DConnectInstance(peer, iinfo, instance); + if (!wrapper) + rv = NS_ERROR_OUT_OF_MEMORY; + else + { + rv = StoreInstance(wrapper); + if (NS_FAILED(rv)) + { + delete wrapper; + wrapper = nsnull; + } + else + { + // reference the newly created wrapper + wrapper->AddRef(); + } + } + } + + if (wrapper) + { + // increase the second, IPC-only, reference counter (mandatory before + // trying wrappers.AppendElement() to make sure ReleaseIPC() will remove + // the wrapper from the instance map on failure) + wrapper->AddRefIPC(); + + if (!wrappers.AppendElement(wrapper)) + { + wrapper->ReleaseIPC(); + wrapper->Release(); + rv = NS_ERROR_OUT_OF_MEMORY; + } + } + + // wrapper remains referenced when passing it to the client + // (will be released upon DCON_OP_RELEASE) + } + } + + NS_IF_RELEASE(instance); + + nsCOMPtr <nsIException> exception; + PRBool got_exception = PR_FALSE; + + if (rv != NS_OK) + { + // try to fetch an nsIException possibly set by one of the setup methods + nsresult rv2; + nsCOMPtr <nsIExceptionService> es; + es = do_GetService(NS_EXCEPTIONSERVICE_CONTRACTID, &rv2); + if (NS_SUCCEEDED(rv2)) + { + nsCOMPtr <nsIExceptionManager> em; + rv2 = es->GetCurrentExceptionManager (getter_AddRefs (em)); + if (NS_SUCCEEDED(rv2)) + { + rv2 = em->GetCurrentException (getter_AddRefs (exception)); + if (NS_SUCCEEDED(rv2)) + { + LOG(("got nsIException instance, will serialize\n")); + got_exception = PR_TRUE; + } + } + } + NS_ASSERTION(NS_SUCCEEDED(rv2), "failed to get/serialize exception"); + if (NS_FAILED(rv2)) + rv = rv2; + } + + ipcMessageWriter writer(64); + + DConnectSetupReply msg; + msg.opcode_major = DCON_OP_SETUP_REPLY; + msg.opcode_minor = 0; + msg.flags = 0; + msg.request_index = setup->request_index; + msg.instance = (DConAddr)(uintptr_t)wrapper; + msg.status = rv; + + if (got_exception) + msg.flags |= DCON_OP_FLAGS_REPLY_EXCEPTION; + + writer.PutBytes(&msg, sizeof(msg)); + + if (got_exception) + { + rv = SerializeException(writer, peer, exception, wrappers); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to get/serialize exception"); + } + + // fire off SETUP_REPLY, don't wait for a response + if (NS_FAILED(rv)) + rv = IPC_SendMessage(peer, kDConnectTargetID, + (const PRUint8 *) &msg, sizeof(msg)); + else + rv = IPC_SendMessage(peer, kDConnectTargetID, + writer.GetBuffer(), writer.GetSize()); + + if (NS_FAILED(rv)) + { + LOG(("unable to send SETUP_REPLY: rv=%x\n", rv)); + ReleaseWrappers(wrappers, peer); + } +} + +void +ipcDConnectService::OnRelease(PRUint32 peer, const DConnectRelease *release) +{ + LOG(("ipcDConnectService::OnRelease [peer=%u instance=0x%Lx]\n", + peer, release->instance)); + + DConnectInstance *wrapper = (DConnectInstance *)release->instance; + + nsAutoLock lock (mLock); + + // make sure we've been sent a valid wrapper from the same peer we created + // this wrapper for + if (mInstanceSet.Contains(wrapper) && wrapper->Peer() == peer) + { + // release the IPC reference from under the lock to ensure atomicity of + // the "check + possible delete" sequence ("delete" is remove this wrapper + // from the instance map when the IPC reference counter drops to zero) + wrapper->ReleaseIPC(PR_TRUE /* locked */); + // leave the lock before Release() because it may call the destructor + // which will release the real object which may need to make asynchronous + // use our service + lock.unlock(); + wrapper->Release(); + } + else + { + // it is possible that the client disconnection event handler has released + // all client instances before the DCON_OP_RELEASE message sent by the + // client gets processed here (because of true multithreading). Just log + // a debug warning + LOG(("ipcDConnectService::OnRelease: WARNING: " + "instance wrapper %p for peer %d not found", wrapper, peer)); + } +} + +void +ipcDConnectService::OnInvoke(PRUint32 peer, const DConnectInvoke *invoke, PRUint32 opLen) +{ + LOG(("ipcDConnectService::OnInvoke [peer=%u instance=0x%Lx method=%u]\n", + peer, invoke->instance, invoke->method_index)); + + DConnectInstance *wrapper = (DConnectInstance *)invoke->instance; + + ipcMessageReader reader((const PRUint8 *) (invoke + 1), opLen - sizeof(*invoke)); + + const nsXPTMethodInfo *methodInfo; + nsXPTCVariant *params = nsnull; + nsCOMPtr<nsIInterfaceInfo> iinfo = nsnull; + PRUint8 i, paramCount = 0, paramUsed = 0; + nsresult rv; + + nsCOMPtr <nsIException> exception; + PRBool got_exception = PR_FALSE; + + // make sure we've been sent a valid wrapper + if (!CheckInstanceAndAddRef(wrapper, peer)) + { + NS_NOTREACHED("instance wrapper not found"); + wrapper = nsnull; + 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(); + + LOG((" iface=%p\n", wrapper->RealInstance())); + LOG((" name=%s\n", methodInfo->GetName())); + LOG((" param-count=%u\n", (PRUint32) paramCount)); + LOG((" request-index=%d\n", (PRUint32) invoke->request_index)); + + params = new nsXPTCVariant[paramCount]; + if (!params) + { + rv = NS_ERROR_OUT_OF_MEMORY; + goto end; + } + + // setup |params| for xptcall + + for (i=0; i<paramCount; ++i, ++paramUsed) + { + const nsXPTParamInfo ¶mInfo = methodInfo->GetParam(i); + + // XXX are inout params an issue? + // yes, we will need to do v.ptr = &v.val for them (DeserializeParam doesn't + // currently do that) to let the callee correctly pick it up and change. + + 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. This pass is also used to deserialize + // arrays (array data goes after all other params). + for (i=0; i<paramCount; ++i) + { + const nsXPTParamInfo ¶mInfo = methodInfo->GetParam(i); + if (paramInfo.IsIn()) + { + const nsXPTType &type = paramInfo.GetType(); + if (type.IsInterfacePointer()) + { + // grab the DConAddr value temporarily stored in the param +#ifdef VBOX + PtrBits bits = params[i].val.u64; +#else + PtrBits bits = (PtrBits)(uintptr_t) params[i].val.p; +#endif + + // DeserializeInterfaceParamBits needs IID only if it's a remote object + nsID iid; + if (bits & PTRBITS_REMOTE_BIT) + { + rv = GetIIDForMethodParam(iinfo, methodInfo, paramInfo, type, + invoke->method_index, params, PR_TRUE, iid); + if (NS_FAILED(rv)) + goto end; + } + + nsISupports *obj = nsnull; + rv = DeserializeInterfaceParamBits(bits, peer, iid, obj); + if (NS_FAILED(rv)) + goto end; + + params[i].val.p = obj; + // mark as interface to let FinishParam() release this param + params[i].SetValIsInterface(); + } + else if (type.IsArray()) + { + void *array = nsnull; + rv = DeserializeArrayParam(this, reader, peer, iinfo, + invoke->method_index, *methodInfo, params, + PR_TRUE, paramInfo, PR_FALSE, array); + if (NS_FAILED(rv)) + goto end; + + params[i].val.p = array; + // mark to let FinishParam() free this param + params[i].SetValIsAllocated(); + } + } + } + + rv = XPTC_InvokeByIndex(wrapper->RealInstance(), + invoke->method_index, + paramCount, + params); + + if (rv != NS_OK) + { + // try to fetch an nsIException possibly set by the method + nsresult rv2; + nsCOMPtr <nsIExceptionService> es; + es = do_GetService(NS_EXCEPTIONSERVICE_CONTRACTID, &rv2); + if (NS_SUCCEEDED(rv2)) + { + nsCOMPtr <nsIExceptionManager> em; + rv2 = es->GetCurrentExceptionManager (getter_AddRefs (em)); + if (NS_SUCCEEDED(rv2)) + { + rv2 = em->GetCurrentException (getter_AddRefs (exception)); + if (NS_SUCCEEDED(rv2)) + { + LOG(("got nsIException instance, will serialize\n")); + got_exception = PR_TRUE; + } + } + } + NS_ASSERTION(NS_SUCCEEDED(rv2), "failed to get/serialize exception"); + if (NS_FAILED(rv2)) + rv = rv2; + } + +end: + LOG(("sending INVOKE_REPLY: rv=%x\n", rv)); + + // balance CheckInstanceAndAddRef() + if (wrapper) + wrapper->Release(); + + ipcMessageWriter writer(64); + + DConnectInvokeReply reply; + reply.opcode_major = DCON_OP_INVOKE_REPLY; + reply.opcode_minor = 0; + reply.flags = 0; + reply.request_index = invoke->request_index; + reply.result = rv; + + if (got_exception) + reply.flags |= DCON_OP_FLAGS_REPLY_EXCEPTION; + + writer.PutBytes(&reply, sizeof(reply)); + + nsVoidArray wrappers; + + if (NS_SUCCEEDED(rv) && params) + { + // serialize out-params and retvals + for (i=0; i<paramCount; ++i) + { + const nsXPTParamInfo paramInfo = methodInfo->GetParam(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, params, PR_TRUE, iid); + if (NS_SUCCEEDED(rv)) + rv = SerializeInterfaceParam(writer, peer, iid, + (nsISupports *) params[i].val.p, wrappers); + } + else + rv = SerializeParam(writer, type, params[i]); + + if (NS_FAILED(rv)) + { + reply.result = rv; + break; + } + } + } + + if (NS_SUCCEEDED(rv)) + { + // serialize output array parameters after everything else since the + // deserialization procedure will need to get a size_is value which may be + // stored in any preceeding or following param + for (i=0; i<paramCount; ++i) + { + const nsXPTParamInfo ¶mInfo = methodInfo->GetParam(i); + + if (paramInfo.GetType().IsArray() && + (paramInfo.IsRetval() || paramInfo.IsOut())) + { + rv = SerializeArrayParam(this, writer, peer, iinfo, invoke->method_index, + *methodInfo, params, PR_TRUE, paramInfo, + params[i].val.p, wrappers); + if (NS_FAILED(rv)) + { + reply.result = rv; + break; + } + } + } + } + } + + if (got_exception) + { + rv = SerializeException(writer, peer, exception, wrappers); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to get/serialize exception"); + } + + 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)); + ReleaseWrappers(wrappers, peer); + } + + if (params) + { + // free individual elements of arrays (note: before freeing arrays + // themselves in FinishParam()) + for (i=0; i<paramUsed; ++i) + { + const nsXPTParamInfo ¶mInfo = methodInfo->GetParam(i); + if (paramInfo.GetType().IsArray()) + FinishArrayParam(iinfo, invoke->method_index, *methodInfo, + params, PR_TRUE, paramInfo, params[i]); + } + + for (i=0; i<paramUsed; ++i) + FinishParam(params[i]); + delete[] params; + } +} |