gecko-dev/ipc/glue/IPCStreamUtils.cpp

705 lines
20 KiB
C++

/* -*- 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 "IPCStreamUtils.h"
#include "nsIIPCSerializableInputStream.h"
#include "mozilla/Assertions.h"
#include "mozilla/InputStreamLengthHelper.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/File.h"
#include "mozilla/ipc/FileDescriptorSetChild.h"
#include "mozilla/ipc/FileDescriptorSetParent.h"
#include "mozilla/ipc/InputStreamUtils.h"
#include "mozilla/ipc/IPCStreamDestination.h"
#include "mozilla/ipc/IPCStreamSource.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/ipc/PBackgroundParent.h"
#include "mozilla/Unused.h"
#include "nsIAsyncInputStream.h"
#include "nsIAsyncOutputStream.h"
#include "nsIPipe.h"
#include "nsNetCID.h"
#include "nsStreamUtils.h"
using namespace mozilla::dom;
namespace mozilla {
namespace ipc {
namespace {
void
AssertValidValueToTake(const IPCStream& aVal)
{
MOZ_ASSERT(aVal.type() == IPCStream::TIPCRemoteStream ||
aVal.type() == IPCStream::TInputStreamParamsWithFds);
}
void
AssertValidValueToTake(const OptionalIPCStream& aVal)
{
MOZ_ASSERT(aVal.type() == OptionalIPCStream::Tvoid_t ||
aVal.type() == OptionalIPCStream::TIPCStream);
if (aVal.type() == OptionalIPCStream::TIPCStream) {
AssertValidValueToTake(aVal.get_IPCStream());
}
}
// These serialization and cleanup functions could be externally exposed. For
// now, though, keep them private to encourage use of the safer RAII
// AutoIPCStream class.
template<typename M>
bool
SerializeInputStreamWithFdsChild(nsIIPCSerializableInputStream* aStream,
IPCStream& aValue,
M* aManager)
{
MOZ_RELEASE_ASSERT(aStream);
MOZ_ASSERT(aManager);
aValue = InputStreamParamsWithFds();
InputStreamParamsWithFds& streamWithFds =
aValue.get_InputStreamParamsWithFds();
AutoTArray<FileDescriptor, 4> fds;
aStream->Serialize(streamWithFds.stream(), fds);
if (streamWithFds.stream().type() == InputStreamParams::T__None) {
MOZ_CRASH("Serialize failed!");
}
if (fds.IsEmpty()) {
streamWithFds.optionalFds() = void_t();
} else {
PFileDescriptorSetChild* fdSet =
aManager->SendPFileDescriptorSetConstructor(fds[0]);
for (uint32_t i = 1; i < fds.Length(); ++i) {
Unused << fdSet->SendAddFileDescriptor(fds[i]);
}
streamWithFds.optionalFds() = fdSet;
}
return true;
}
template<typename M>
bool
SerializeInputStreamWithFdsParent(nsIIPCSerializableInputStream* aStream,
IPCStream& aValue,
M* aManager)
{
MOZ_RELEASE_ASSERT(aStream);
MOZ_ASSERT(aManager);
aValue = InputStreamParamsWithFds();
InputStreamParamsWithFds& streamWithFds =
aValue.get_InputStreamParamsWithFds();
AutoTArray<FileDescriptor, 4> fds;
aStream->Serialize(streamWithFds.stream(), fds);
if (streamWithFds.stream().type() == InputStreamParams::T__None) {
MOZ_CRASH("Serialize failed!");
}
streamWithFds.optionalFds() = void_t();
if (!fds.IsEmpty()) {
PFileDescriptorSetParent* fdSet =
aManager->SendPFileDescriptorSetConstructor(fds[0]);
for (uint32_t i = 1; i < fds.Length(); ++i) {
if (NS_WARN_IF(!fdSet->SendAddFileDescriptor(fds[i]))) {
Unused << PFileDescriptorSetParent::Send__delete__(fdSet);
fdSet = nullptr;
break;
}
}
if (fdSet) {
streamWithFds.optionalFds() = fdSet;
}
}
return true;
}
template<typename M>
bool
SerializeInputStream(nsIInputStream* aStream, IPCStream& aValue, M* aManager,
bool aDelayedStart)
{
MOZ_ASSERT(aStream);
MOZ_ASSERT(aManager);
// Let's try to take the length using InputStreamLengthHelper. If the length
// cannot be taken synchronously, and its length is needed, the stream needs
// to be fully copied in memory on the deserialization side.
int64_t length;
if (!InputStreamLengthHelper::GetSyncLength(aStream, &length)) {
length = -1;
}
// As a fallback, attempt to stream the data across using a IPCStream
// actor. For blocking streams, create a nonblocking pipe instead,
nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(aStream);
if (!asyncStream) {
const uint32_t kBufferSize = 32768; // matches IPCStream buffer size.
nsCOMPtr<nsIAsyncOutputStream> sink;
nsresult rv = NS_NewPipe2(getter_AddRefs(asyncStream),
getter_AddRefs(sink),
true,
false,
kBufferSize,
UINT32_MAX);
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
nsCOMPtr<nsIEventTarget> target =
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
rv = NS_AsyncCopy(aStream, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS,
kBufferSize);
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
}
MOZ_ASSERT(asyncStream);
IPCRemoteStream remoteStream;
remoteStream.delayedStart() = aDelayedStart;
remoteStream.stream() = IPCStreamSource::Create(asyncStream, aManager);
remoteStream.length() = length;
aValue = remoteStream;
return true;
}
template<typename M>
bool
SerializeInputStreamChild(nsIInputStream* aStream, M* aManager,
IPCStream* aValue,
OptionalIPCStream* aOptionalValue,
bool aDelayedStart)
{
MOZ_ASSERT(aStream);
MOZ_ASSERT(aManager);
MOZ_ASSERT(aValue || aOptionalValue);
// If a stream is known to be larger than 1MB, prefer sending it in chunks.
const uint64_t kTooLargeStream = 1024 * 1024;
nsCOMPtr<nsIIPCSerializableInputStream> serializable =
do_QueryInterface(aStream);
// ExpectedSerializedLength() returns the length of the stream if serialized.
// This is useful to decide if we want to continue using the serialization
// directly, or if it's better to use IPCStream.
uint64_t expectedLength =
serializable ? serializable->ExpectedSerializedLength().valueOr(0) : 0;
if (serializable && expectedLength < kTooLargeStream) {
if (aValue) {
return SerializeInputStreamWithFdsChild(serializable, *aValue, aManager);
}
return SerializeInputStreamWithFdsChild(serializable, *aOptionalValue,
aManager);
}
if (aValue) {
return SerializeInputStream(aStream, *aValue, aManager, aDelayedStart);
}
return SerializeInputStream(aStream, *aOptionalValue, aManager, aDelayedStart);
}
template<typename M>
bool
SerializeInputStreamParent(nsIInputStream* aStream, M* aManager,
IPCStream* aValue,
OptionalIPCStream* aOptionalValue,
bool aDelayedStart)
{
MOZ_ASSERT(aStream);
MOZ_ASSERT(aManager);
MOZ_ASSERT(aValue || aOptionalValue);
// If a stream is known to be larger than 1MB, prefer sending it in chunks.
const uint64_t kTooLargeStream = 1024 * 1024;
nsCOMPtr<nsIIPCSerializableInputStream> serializable =
do_QueryInterface(aStream);
uint64_t expectedLength =
serializable ? serializable->ExpectedSerializedLength().valueOr(0) : 0;
if (serializable && expectedLength < kTooLargeStream) {
if (aValue) {
return SerializeInputStreamWithFdsParent(serializable, *aValue, aManager);
}
return SerializeInputStreamWithFdsParent(serializable, *aOptionalValue,
aManager);
}
if (aValue) {
return SerializeInputStream(aStream, *aValue, aManager, aDelayedStart);
}
return SerializeInputStream(aStream, *aOptionalValue, aManager, aDelayedStart);
}
void
CleanupIPCStream(IPCStream& aValue, bool aConsumedByIPC, bool aDelayedStart)
{
if (aValue.type() == IPCStream::T__None) {
return;
}
if (aValue.type() == IPCStream::TInputStreamParamsWithFds) {
InputStreamParamsWithFds& streamWithFds =
aValue.get_InputStreamParamsWithFds();
// Cleanup file descriptors if necessary
if (streamWithFds.optionalFds().type() ==
OptionalFileDescriptorSet::TPFileDescriptorSetChild) {
AutoTArray<FileDescriptor, 4> fds;
auto fdSetActor = static_cast<FileDescriptorSetChild*>(
streamWithFds.optionalFds().get_PFileDescriptorSetChild());
MOZ_ASSERT(fdSetActor);
// FileDescriptorSet doesn't clear its fds in its ActorDestroy, so we
// unconditionally forget them here. The fds themselves are auto-closed in
// ~FileDescriptor since they originated in this process.
fdSetActor->ForgetFileDescriptors(fds);
if (!aConsumedByIPC) {
Unused << FileDescriptorSetChild::Send__delete__(fdSetActor);
}
} else if (streamWithFds.optionalFds().type() ==
OptionalFileDescriptorSet::TPFileDescriptorSetParent) {
AutoTArray<FileDescriptor, 4> fds;
auto fdSetActor = static_cast<FileDescriptorSetParent*>(
streamWithFds.optionalFds().get_PFileDescriptorSetParent());
MOZ_ASSERT(fdSetActor);
// FileDescriptorSet doesn't clear its fds in its ActorDestroy, so we
// unconditionally forget them here. The fds themselves are auto-closed in
// ~FileDescriptor since they originated in this process.
fdSetActor->ForgetFileDescriptors(fds);
if (!aConsumedByIPC) {
Unused << FileDescriptorSetParent::Send__delete__(fdSetActor);
}
}
return;
}
MOZ_ASSERT(aValue.type() == IPCStream::TIPCRemoteStream);
IPCRemoteStreamType& remoteInputStream =
aValue.get_IPCRemoteStream().stream();
IPCStreamSource* source = nullptr;
if (remoteInputStream.type() == IPCRemoteStreamType::TPChildToParentStreamChild) {
source = IPCStreamSource::Cast(remoteInputStream.get_PChildToParentStreamChild());
} else {
MOZ_ASSERT(remoteInputStream.type() == IPCRemoteStreamType::TPParentToChildStreamParent);
source = IPCStreamSource::Cast(remoteInputStream.get_PParentToChildStreamParent());
}
MOZ_ASSERT(source);
// If the source stream has not been taken to be sent to the other side, we
// can destroy it.
if (!aConsumedByIPC) {
source->StartDestroy();
return;
}
if (!aDelayedStart) {
// If we don't need to do a delayedStart, we start it now. Otherwise, the
// Start() will be called at the first use by the
// IPCStreamDestination::DelayedStartInputStream.
source->Start();
}
}
void
CleanupIPCStream(OptionalIPCStream& aValue, bool aConsumedByIPC, bool aDelayedStart)
{
if (aValue.type() == OptionalIPCStream::Tvoid_t) {
return;
}
CleanupIPCStream(aValue.get_IPCStream(), aConsumedByIPC, aDelayedStart);
}
// Returns false if the serialization should not proceed. This means that the
// inputStream is null.
bool
NormalizeOptionalValue(nsIInputStream* aStream,
IPCStream* aValue,
OptionalIPCStream* aOptionalValue)
{
if (aValue) {
// if aStream is null, we will crash when serializing.
return true;
}
if (!aStream) {
*aOptionalValue = void_t();
return false;
}
*aOptionalValue = IPCStream();
return true;
}
} // anonymous namespace
already_AddRefed<nsIInputStream>
DeserializeIPCStream(const IPCStream& aValue)
{
if (aValue.type() == IPCStream::TIPCRemoteStream) {
const IPCRemoteStream& remoteStream = aValue.get_IPCRemoteStream();
const IPCRemoteStreamType& remoteStreamType = remoteStream.stream();
IPCStreamDestination* destinationStream;
if (remoteStreamType.type() == IPCRemoteStreamType::TPChildToParentStreamParent) {
destinationStream =
IPCStreamDestination::Cast(remoteStreamType.get_PChildToParentStreamParent());
} else {
MOZ_ASSERT(remoteStreamType.type() == IPCRemoteStreamType::TPParentToChildStreamChild);
destinationStream =
IPCStreamDestination::Cast(remoteStreamType.get_PParentToChildStreamChild());
}
destinationStream->SetDelayedStart(remoteStream.delayedStart());
destinationStream->SetLength(remoteStream.length());
return destinationStream->TakeReader();
}
// Note, we explicitly do not support deserializing the PChildToParentStream actor on
// the child side nor the PParentToChildStream actor on the parent side.
MOZ_ASSERT(aValue.type() == IPCStream::TInputStreamParamsWithFds);
const InputStreamParamsWithFds& streamWithFds =
aValue.get_InputStreamParamsWithFds();
AutoTArray<FileDescriptor, 4> fds;
if (streamWithFds.optionalFds().type() ==
OptionalFileDescriptorSet::TPFileDescriptorSetParent) {
auto fdSetActor = static_cast<FileDescriptorSetParent*>(
streamWithFds.optionalFds().get_PFileDescriptorSetParent());
MOZ_ASSERT(fdSetActor);
fdSetActor->ForgetFileDescriptors(fds);
MOZ_ASSERT(!fds.IsEmpty());
if (!FileDescriptorSetParent::Send__delete__(fdSetActor)) {
// child process is gone, warn and allow actor to clean up normally
NS_WARNING("Failed to delete fd set actor.");
}
} else if (streamWithFds.optionalFds().type() ==
OptionalFileDescriptorSet::TPFileDescriptorSetChild) {
auto fdSetActor = static_cast<FileDescriptorSetChild*>(
streamWithFds.optionalFds().get_PFileDescriptorSetChild());
MOZ_ASSERT(fdSetActor);
fdSetActor->ForgetFileDescriptors(fds);
MOZ_ASSERT(!fds.IsEmpty());
Unused << FileDescriptorSetChild::Send__delete__(fdSetActor);
}
return InputStreamHelper::DeserializeInputStream(streamWithFds.stream(), fds);
}
already_AddRefed<nsIInputStream>
DeserializeIPCStream(const OptionalIPCStream& aValue)
{
if (aValue.type() == OptionalIPCStream::Tvoid_t) {
return nullptr;
}
return DeserializeIPCStream(aValue.get_IPCStream());
}
AutoIPCStream::AutoIPCStream(bool aDelayedStart)
: mInlineValue(void_t())
, mValue(nullptr)
, mOptionalValue(&mInlineValue)
, mTaken(false)
, mDelayedStart(aDelayedStart)
{
}
AutoIPCStream::AutoIPCStream(IPCStream& aTarget, bool aDelayedStart)
: mInlineValue(void_t())
, mValue(&aTarget)
, mOptionalValue(nullptr)
, mTaken(false)
, mDelayedStart(aDelayedStart)
{
}
AutoIPCStream::AutoIPCStream(OptionalIPCStream& aTarget, bool aDelayedStart)
: mInlineValue(void_t())
, mValue(nullptr)
, mOptionalValue(&aTarget)
, mTaken(false)
, mDelayedStart(aDelayedStart)
{
*mOptionalValue = void_t();
}
AutoIPCStream::~AutoIPCStream()
{
MOZ_ASSERT(mValue || mOptionalValue);
if (mValue && IsSet()) {
CleanupIPCStream(*mValue, mTaken, mDelayedStart);
} else {
CleanupIPCStream(*mOptionalValue, mTaken, mDelayedStart);
}
}
bool
AutoIPCStream::Serialize(nsIInputStream* aStream, dom::nsIContentChild* aManager)
{
MOZ_ASSERT(aStream || !mValue);
MOZ_ASSERT(aManager);
MOZ_ASSERT(mValue || mOptionalValue);
MOZ_ASSERT(!mTaken);
MOZ_ASSERT(!IsSet());
// If NormalizeOptionalValue returns false, we don't have to proceed.
if (!NormalizeOptionalValue(aStream, mValue, mOptionalValue)) {
return true;
}
if (!SerializeInputStreamChild(aStream, aManager, mValue, mOptionalValue,
mDelayedStart)) {
MOZ_CRASH("IPCStream creation failed!");
}
if (mValue) {
AssertValidValueToTake(*mValue);
} else {
AssertValidValueToTake(*mOptionalValue);
}
return true;
}
bool
AutoIPCStream::Serialize(nsIInputStream* aStream, PBackgroundChild* aManager)
{
MOZ_ASSERT(aStream || !mValue);
MOZ_ASSERT(aManager);
MOZ_ASSERT(mValue || mOptionalValue);
MOZ_ASSERT(!mTaken);
MOZ_ASSERT(!IsSet());
// If NormalizeOptionalValue returns false, we don't have to proceed.
if (!NormalizeOptionalValue(aStream, mValue, mOptionalValue)) {
return true;
}
if (!SerializeInputStreamChild(aStream, aManager, mValue, mOptionalValue,
mDelayedStart)) {
MOZ_CRASH("IPCStream creation failed!");
}
if (mValue) {
AssertValidValueToTake(*mValue);
} else {
AssertValidValueToTake(*mOptionalValue);
}
return true;
}
bool
AutoIPCStream::Serialize(nsIInputStream* aStream,
dom::nsIContentParent* aManager)
{
MOZ_ASSERT(aStream || !mValue);
MOZ_ASSERT(aManager);
MOZ_ASSERT(mValue || mOptionalValue);
MOZ_ASSERT(!mTaken);
MOZ_ASSERT(!IsSet());
// If NormalizeOptionalValue returns false, we don't have to proceed.
if (!NormalizeOptionalValue(aStream, mValue, mOptionalValue)) {
return true;
}
if (!SerializeInputStreamParent(aStream, aManager, mValue, mOptionalValue,
mDelayedStart)) {
return false;
}
if (mValue) {
AssertValidValueToTake(*mValue);
} else {
AssertValidValueToTake(*mOptionalValue);
}
return true;
}
bool
AutoIPCStream::Serialize(nsIInputStream* aStream, PBackgroundParent* aManager)
{
MOZ_ASSERT(aStream || !mValue);
MOZ_ASSERT(aManager);
MOZ_ASSERT(mValue || mOptionalValue);
MOZ_ASSERT(!mTaken);
MOZ_ASSERT(!IsSet());
// If NormalizeOptionalValue returns false, we don't have to proceed.
if (!NormalizeOptionalValue(aStream, mValue, mOptionalValue)) {
return true;
}
if (!SerializeInputStreamParent(aStream, aManager, mValue, mOptionalValue,
mDelayedStart)) {
return false;
}
if (mValue) {
AssertValidValueToTake(*mValue);
} else {
AssertValidValueToTake(*mOptionalValue);
}
return true;
}
bool
AutoIPCStream::IsSet() const
{
MOZ_ASSERT(mValue || mOptionalValue);
if (mValue) {
return mValue->type() != IPCStream::T__None;
} else {
return mOptionalValue->type() != OptionalIPCStream::Tvoid_t &&
mOptionalValue->get_IPCStream().type() != IPCStream::T__None;
}
}
IPCStream&
AutoIPCStream::TakeValue()
{
MOZ_ASSERT(mValue || mOptionalValue);
MOZ_ASSERT(!mTaken);
MOZ_ASSERT(IsSet());
mTaken = true;
if (mValue) {
AssertValidValueToTake(*mValue);
return *mValue;
}
IPCStream& value =
mOptionalValue->get_IPCStream();
AssertValidValueToTake(value);
return value;
}
OptionalIPCStream&
AutoIPCStream::TakeOptionalValue()
{
MOZ_ASSERT(!mTaken);
MOZ_ASSERT(!mValue);
MOZ_ASSERT(mOptionalValue);
mTaken = true;
AssertValidValueToTake(*mOptionalValue);
return *mOptionalValue;
}
void
IPDLParamTraits<nsIInputStream>::Write(IPC::Message* aMsg,
IProtocol* aActor,
nsIInputStream* aParam)
{
mozilla::ipc::AutoIPCStream autoStream;
bool ok = false;
bool found = false;
// We can only serialize our nsIInputStream if it's going to be sent over one
// of the protocols we support, or a protocol which is managed by one of the
// protocols we support.
IProtocol* actor = aActor;
while (!found && actor) {
switch (actor->GetProtocolTypeId()) {
case PContentMsgStart:
if (actor->GetSide() == mozilla::ipc::ParentSide) {
ok = autoStream.Serialize(
aParam, static_cast<mozilla::dom::ContentParent*>(actor));
} else {
MOZ_RELEASE_ASSERT(actor->GetSide() == mozilla::ipc::ChildSide);
ok = autoStream.Serialize(
aParam, static_cast<mozilla::dom::ContentChild*>(actor));
}
found = true;
break;
case PBackgroundMsgStart:
if (actor->GetSide() == mozilla::ipc::ParentSide) {
ok = autoStream.Serialize(
aParam, static_cast<mozilla::ipc::PBackgroundParent*>(actor));
} else {
MOZ_RELEASE_ASSERT(actor->GetSide() == mozilla::ipc::ChildSide);
ok = autoStream.Serialize(
aParam, static_cast<mozilla::ipc::PBackgroundChild*>(actor));
}
found = true;
break;
}
// Try the actor's manager.
actor = actor->Manager();
}
if (!found) {
aActor->FatalError("Attempt to send nsIInputStream over an unsupported ipdl protocol");
}
MOZ_RELEASE_ASSERT(ok, "Failed to serialize nsIInputStream");
WriteIPDLParam(aMsg, aActor, autoStream.TakeOptionalValue());
}
bool
IPDLParamTraits<nsIInputStream>::Read(const IPC::Message* aMsg, PickleIterator* aIter,
IProtocol* aActor, RefPtr<nsIInputStream>* aResult)
{
mozilla::ipc::OptionalIPCStream ipcStream;
if (!ReadIPDLParam(aMsg, aIter, aActor, &ipcStream)) {
return false;
}
*aResult = mozilla::ipc::DeserializeIPCStream(ipcStream);
return true;
}
} // namespace ipc
} // namespace mozilla