Bug 1338609: Add the ability to serialize and deserialize structs using Microsoft RPC to mscom; r?jimm draft
authorAaron Klotz <aklotz@mozilla.com>
Fri, 17 Feb 2017 14:26:56 -0700
changeset 488204 058775b1102801be51e4ab30be669b7aa4509e4d
parent 488202 4aea23a8ae9f346e9ee162ef247e2bdc023fe464
child 546667 4f10a72aa1b3ae26316d616fe11fc01f1d235cd7
push id46455
push useraklotz@mozilla.com
push dateWed, 22 Feb 2017 19:10:56 +0000
reviewersjimm
bugs1338609
milestone54.0a1
Bug 1338609: Add the ability to serialize and deserialize structs using Microsoft RPC to mscom; r?jimm MozReview-Commit-ID: CSkNYCIQYQe
ipc/mscom/StructStream.cpp
ipc/mscom/StructStream.h
ipc/mscom/moz.build
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/StructStream.cpp
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <malloc.h>
+#include <rpc.h>
+
+/**
+ * These functions need to be defined in order for the types that use
+ * mozilla::mscom::StructToStream and mozilla::mscom::StructFromStream to work.
+ */
+extern "C" {
+
+void __RPC_FAR* __RPC_USER
+midl_user_allocate(size_t aNumBytes)
+{
+  const unsigned long kRpcReqdBufAlignment = 8;
+  return _aligned_malloc(aNumBytes, kRpcReqdBufAlignment);
+}
+
+void __RPC_USER
+midl_user_free(void* aBuffer)
+{
+  _aligned_free(aBuffer);
+}
+
+} // extern "C"
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/StructStream.h
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_mscom_StructStream_h
+#define mozilla_mscom_StructStream_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "nscore.h"
+
+#include <memory.h>
+#include <midles.h>
+#include <objidl.h>
+#include <rpc.h>
+
+/**
+ * This code is used for (de)serializing data structures that have been
+ * declared using midl, thus allowing us to use Microsoft RPC for marshaling
+ * data for our COM handlers that may run in other processes that are not ours.
+ */
+
+namespace mozilla {
+namespace mscom {
+
+namespace detail {
+
+typedef ULONG EncodedLenT;
+
+} // namespace detail
+
+class MOZ_NON_TEMPORARY_CLASS StructToStream
+{
+public:
+  /**
+   * This constructor variant represents an empty/null struct to be serialized.
+   */
+  StructToStream()
+    : mStatus(RPC_S_OK)
+    , mHandle(nullptr)
+    , mBuffer(nullptr)
+    , mEncodedLen(0)
+  {
+  }
+
+  template <typename StructT>
+  StructToStream(StructT& aSrcStruct, void (*aEncodeFnPtr)(handle_t, StructT*))
+    : mStatus(RPC_X_INVALID_BUFFER)
+    , mHandle(nullptr)
+    , mBuffer(nullptr)
+    , mEncodedLen(0)
+  {
+    mStatus = ::MesEncodeDynBufferHandleCreate(&mBuffer, &mEncodedLen,
+                                               &mHandle);
+    if (mStatus != RPC_S_OK) {
+      return;
+    }
+
+    MOZ_SEH_TRY {
+      aEncodeFnPtr(mHandle, &aSrcStruct);
+    } MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+      mStatus = ::RpcExceptionCode();
+      return;
+    }
+
+    if (!mBuffer || !mEncodedLen) {
+      mStatus = RPC_X_NO_MEMORY;
+      return;
+    }
+  }
+
+  ~StructToStream()
+  {
+    if (mHandle) {
+      ::MesHandleFree(mHandle);
+    }
+  }
+
+  static unsigned long GetEmptySize()
+  {
+    return sizeof(detail::EncodedLenT);
+  }
+
+  static HRESULT WriteEmpty(IStream* aDestStream)
+  {
+    StructToStream emptyStruct;
+    return emptyStruct.Write(aDestStream);
+  }
+
+  explicit operator bool() const
+  {
+    return mStatus == RPC_S_OK;
+  }
+
+  bool IsEmpty() const
+  {
+    return mStatus == RPC_S_OK && !mEncodedLen;
+  }
+
+  unsigned long GetSize() const
+  {
+    return sizeof(mEncodedLen) + mEncodedLen;
+  }
+
+  HRESULT Write(IStream* aDestStream)
+  {
+    if (!aDestStream) {
+      return E_INVALIDARG;
+    }
+    if (mStatus != RPC_S_OK) {
+      return E_FAIL;
+    }
+
+    ULONG bytesWritten;
+    HRESULT hr = aDestStream->Write(&mEncodedLen, sizeof(mEncodedLen),
+                                    &bytesWritten);
+    if (FAILED(hr)) {
+      return hr;
+    }
+    if (bytesWritten != sizeof(mEncodedLen)) {
+      return E_UNEXPECTED;
+    }
+
+    if (mBuffer && mEncodedLen) {
+      hr = aDestStream->Write(mBuffer, mEncodedLen, &bytesWritten);
+      if (FAILED(hr)) {
+        return hr;
+      }
+      if (bytesWritten != mEncodedLen) {
+        return E_UNEXPECTED;
+      }
+    }
+
+    return hr;
+  }
+
+  StructToStream(const StructToStream&) = delete;
+  StructToStream(StructToStream&&) = delete;
+  StructToStream& operator=(const StructToStream&) = delete;
+  StructToStream& operator=(StructToStream&&) = delete;
+
+private:
+  RPC_STATUS          mStatus;
+  handle_t            mHandle;
+  char*               mBuffer;
+  detail::EncodedLenT mEncodedLen;
+};
+
+class MOZ_NON_TEMPORARY_CLASS StructFromStream
+{
+  struct AlignedFreeDeleter
+  {
+    void operator()(void* aPtr)
+    {
+      ::_aligned_free(aPtr);
+    }
+  };
+
+  static const detail::EncodedLenT kRpcReqdBufAlignment = 8;
+
+public:
+  explicit StructFromStream(IStream* aStream)
+    : mStatus(RPC_X_INVALID_BUFFER)
+    , mHandle(nullptr)
+  {
+    MOZ_ASSERT(aStream);
+
+    // Read the length of the encoded data first
+    detail::EncodedLenT encodedLen = 0;
+    ULONG bytesRead = 0;
+    HRESULT hr = aStream->Read(&encodedLen, sizeof(encodedLen), &bytesRead);
+    if (FAILED(hr)) {
+      return;
+    }
+
+    // NB: Some implementations of IStream return S_FALSE to indicate EOF,
+    // other implementations return S_OK and set the number of bytes read to 0.
+    // We must handle both.
+    if (hr == S_FALSE || !bytesRead) {
+      mStatus = RPC_S_OBJECT_NOT_FOUND;
+      return;
+    }
+
+    if (bytesRead != sizeof(encodedLen)) {
+      return;
+    }
+
+    if (!encodedLen) {
+      mStatus = RPC_S_OBJECT_NOT_FOUND;
+      return;
+    }
+
+    MOZ_ASSERT(encodedLen % kRpcReqdBufAlignment == 0);
+    if (encodedLen % kRpcReqdBufAlignment) {
+      return;
+    }
+
+    // This memory allocation is fallible
+    mEncodedBuffer.reset(static_cast<char*>(
+          ::_aligned_malloc(encodedLen, kRpcReqdBufAlignment)));
+    if (!mEncodedBuffer) {
+      return;
+    }
+
+    ULONG bytesReadFromStream = 0;
+    hr = aStream->Read(mEncodedBuffer.get(), encodedLen, &bytesReadFromStream);
+    if (FAILED(hr) || bytesReadFromStream != encodedLen) {
+      return;
+    }
+
+    mStatus = ::MesDecodeBufferHandleCreate(mEncodedBuffer.get(), encodedLen,
+                                            &mHandle);
+  }
+
+  ~StructFromStream()
+  {
+    if (mHandle) {
+      ::MesHandleFree(mHandle);
+    }
+  }
+
+  explicit operator bool() const
+  {
+    return mStatus == RPC_S_OK || IsEmpty();
+  }
+
+  bool IsEmpty() const { return mStatus == RPC_S_OBJECT_NOT_FOUND; }
+
+  template <typename StructT>
+  bool Read(StructT* aDestStruct, void (*aDecodeFnPtr)(handle_t, StructT*))
+  {
+    if (!aDestStruct || !aDecodeFnPtr || mStatus != RPC_S_OK) {
+      return false;
+    }
+
+    // NB: Deserialization will fail with BSTRs unless the destination data
+    //     is zeroed out!
+    ZeroMemory(aDestStruct, sizeof(StructT));
+
+    MOZ_SEH_TRY {
+      aDecodeFnPtr(mHandle, aDestStruct);
+    } MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+      mStatus = ::RpcExceptionCode();
+      return false;
+    }
+
+    return true;
+  }
+
+  StructFromStream(const StructFromStream&) = delete;
+  StructFromStream(StructFromStream&&) = delete;
+  StructFromStream& operator=(const StructFromStream&) = delete;
+  StructFromStream& operator=(StructFromStream&&) = delete;
+
+private:
+  RPC_STATUS                          mStatus;
+  handle_t                            mHandle;
+  UniquePtr<char, AlignedFreeDeleter> mEncodedBuffer;
+};
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_StructStream_h
--- a/ipc/mscom/moz.build
+++ b/ipc/mscom/moz.build
@@ -29,31 +29,33 @@ if CONFIG['ACCESSIBILITY']:
     EXPORTS.mozilla.mscom += [
         'ActivationContext.h',
         'DispatchForwarder.h',
         'Interceptor.h',
         'InterceptorLog.h',
         'MainThreadHandoff.h',
         'MainThreadInvoker.h',
         'Registration.h',
+        'StructStream.h',
         'WeakRef.h',
     ]
 
     SOURCES += [
         'Interceptor.cpp',
         'Registration.cpp',
         'WeakRef.cpp',
     ]
 
     UNIFIED_SOURCES += [
         'ActivationContext.cpp',
         'DispatchForwarder.cpp',
         'InterceptorLog.cpp',
         'MainThreadHandoff.cpp',
         'MainThreadInvoker.cpp',
+        'StructStream.cpp',
     ]
 
 LOCAL_INCLUDES += [
     '/xpcom/base',
     '/xpcom/build',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')