diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /dom/presentation/PresentationTCPSessionTransport.cpp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/presentation/PresentationTCPSessionTransport.cpp')
-rw-r--r-- | dom/presentation/PresentationTCPSessionTransport.cpp | 589 |
1 files changed, 589 insertions, 0 deletions
diff --git a/dom/presentation/PresentationTCPSessionTransport.cpp b/dom/presentation/PresentationTCPSessionTransport.cpp new file mode 100644 index 000000000..1ccb8b43c --- /dev/null +++ b/dom/presentation/PresentationTCPSessionTransport.cpp @@ -0,0 +1,589 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsArrayUtils.h" +#include "nsIAsyncStreamCopier.h" +#include "nsIInputStreamPump.h" +#include "nsIMultiplexInputStream.h" +#include "nsIMutableArray.h" +#include "nsIOutputStream.h" +#include "nsIPresentationControlChannel.h" +#include "nsIScriptableInputStream.h" +#include "nsISocketTransport.h" +#include "nsISocketTransportService.h" +#include "nsISupportsPrimitives.h" +#include "nsNetUtil.h" +#include "nsQueryObject.h" +#include "nsServiceManagerUtils.h" +#include "nsStreamUtils.h" +#include "nsThreadUtils.h" +#include "PresentationLog.h" +#include "PresentationTCPSessionTransport.h" + +#define BUFFER_SIZE 65536 + +using namespace mozilla; +using namespace mozilla::dom; + +class CopierCallbacks final : public nsIRequestObserver +{ +public: + explicit CopierCallbacks(PresentationTCPSessionTransport* aTransport) + : mOwner(aTransport) + {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER +private: + ~CopierCallbacks() {} + + RefPtr<PresentationTCPSessionTransport> mOwner; +}; + +NS_IMPL_ISUPPORTS(CopierCallbacks, nsIRequestObserver) + +NS_IMETHODIMP +CopierCallbacks::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) +{ + return NS_OK; +} + +NS_IMETHODIMP +CopierCallbacks::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatus) +{ + mOwner->NotifyCopyComplete(aStatus); + return NS_OK; +} + +NS_IMPL_CYCLE_COLLECTION(PresentationTCPSessionTransport, mTransport, + mSocketInputStream, mSocketOutputStream, + mInputStreamPump, mInputStreamScriptable, + mMultiplexStream, mMultiplexStreamCopier, mCallback) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(PresentationTCPSessionTransport) +NS_IMPL_CYCLE_COLLECTING_RELEASE(PresentationTCPSessionTransport) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PresentationTCPSessionTransport) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPresentationSessionTransport) + NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback) + NS_INTERFACE_MAP_ENTRY(nsIPresentationSessionTransport) + NS_INTERFACE_MAP_ENTRY(nsIPresentationSessionTransportBuilder) + NS_INTERFACE_MAP_ENTRY(nsIPresentationTCPSessionTransportBuilder) + NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) + NS_INTERFACE_MAP_ENTRY(nsIStreamListener) + NS_INTERFACE_MAP_ENTRY(nsITransportEventSink) +NS_INTERFACE_MAP_END + +PresentationTCPSessionTransport::PresentationTCPSessionTransport() + : mReadyState(ReadyState::CLOSED) + , mAsyncCopierActive(false) + , mCloseStatus(NS_OK) + , mDataNotificationEnabled(false) +{ +} + +PresentationTCPSessionTransport::~PresentationTCPSessionTransport() +{ +} + +NS_IMETHODIMP +PresentationTCPSessionTransport::BuildTCPSenderTransport(nsISocketTransport* aTransport, + nsIPresentationSessionTransportBuilderListener* aListener) +{ + if (NS_WARN_IF(!aTransport)) { + return NS_ERROR_INVALID_ARG; + } + mTransport = aTransport; + + if (NS_WARN_IF(!aListener)) { + return NS_ERROR_INVALID_ARG; + } + mListener = aListener; + + nsresult rv = CreateStream(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mRole = nsIPresentationService::ROLE_CONTROLLER; + + nsCOMPtr<nsIPresentationSessionTransport> sessionTransport = do_QueryObject(this); + nsCOMPtr<nsIRunnable> onSessionTransportRunnable = + NewRunnableMethod + <nsIPresentationSessionTransport*>(mListener, + &nsIPresentationSessionTransportBuilderListener::OnSessionTransport, + sessionTransport); + + NS_DispatchToCurrentThread(onSessionTransportRunnable.forget()); + + nsCOMPtr<nsIRunnable> setReadyStateRunnable = + NewRunnableMethod<ReadyState>(this, + &PresentationTCPSessionTransport::SetReadyState, + ReadyState::OPEN); + return NS_DispatchToCurrentThread(setReadyStateRunnable.forget()); +} + +NS_IMETHODIMP +PresentationTCPSessionTransport::BuildTCPReceiverTransport(nsIPresentationChannelDescription* aDescription, + nsIPresentationSessionTransportBuilderListener* aListener) +{ + if (NS_WARN_IF(!aDescription)) { + return NS_ERROR_INVALID_ARG; + } + + if (NS_WARN_IF(!aListener)) { + return NS_ERROR_INVALID_ARG; + } + mListener = aListener; + + uint16_t serverPort; + nsresult rv = aDescription->GetTcpPort(&serverPort); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIArray> serverHosts; + rv = aDescription->GetTcpAddress(getter_AddRefs(serverHosts)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // TODO bug 1228504 Take all IP addresses in PresentationChannelDescription + // into account. And at the first stage Presentation API is only exposed on + // Firefox OS where the first IP appears enough for most scenarios. + nsCOMPtr<nsISupportsCString> supportStr = do_QueryElementAt(serverHosts, 0); + if (NS_WARN_IF(!supportStr)) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoCString serverHost; + supportStr->GetData(serverHost); + if (serverHost.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + + PRES_DEBUG("%s:ServerHost[%s],ServerPort[%d]\n", __func__, serverHost.get(), serverPort); + + SetReadyState(ReadyState::CONNECTING); + + nsCOMPtr<nsISocketTransportService> sts = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID); + if (NS_WARN_IF(!sts)) { + return NS_ERROR_NOT_AVAILABLE; + } + rv = sts->CreateTransport(nullptr, 0, serverHost, serverPort, nullptr, + getter_AddRefs(mTransport)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIThread> mainThread; + NS_GetMainThread(getter_AddRefs(mainThread)); + + mTransport->SetEventSink(this, mainThread); + + rv = CreateStream(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mRole = nsIPresentationService::ROLE_RECEIVER; + + nsCOMPtr<nsIPresentationSessionTransport> sessionTransport = do_QueryObject(this); + nsCOMPtr<nsIRunnable> runnable = + NewRunnableMethod + <nsIPresentationSessionTransport*>(mListener, + &nsIPresentationSessionTransportBuilderListener::OnSessionTransport, + sessionTransport); + return NS_DispatchToCurrentThread(runnable.forget()); +} + +nsresult +PresentationTCPSessionTransport::CreateStream() +{ + nsresult rv = mTransport->OpenInputStream(0, 0, 0, getter_AddRefs(mSocketInputStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rv = mTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0, getter_AddRefs(mSocketOutputStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // If the other side is not listening, we will get an |onInputStreamReady| + // callback where |available| raises to indicate the connection was refused. + nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(mSocketInputStream); + if (NS_WARN_IF(!asyncStream)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr<nsIThread> mainThread; + NS_GetMainThread(getter_AddRefs(mainThread)); + + rv = asyncStream->AsyncWait(this, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0, mainThread); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mInputStreamScriptable = do_CreateInstance("@mozilla.org/scriptableinputstream;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rv = mInputStreamScriptable->Init(mSocketInputStream); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mMultiplexStream = do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mMultiplexStreamCopier = do_CreateInstance("@mozilla.org/network/async-stream-copier;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsISocketTransportService> sts = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID); + if (NS_WARN_IF(!sts)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr<nsIEventTarget> target = do_QueryInterface(sts); + rv = mMultiplexStreamCopier->Init(mMultiplexStream, + mSocketOutputStream, + target, + true, /* source buffered */ + false, /* sink buffered */ + BUFFER_SIZE, + false, /* close source */ + false); /* close sink */ + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +PresentationTCPSessionTransport::CreateInputStreamPump() +{ + if (NS_WARN_IF(mInputStreamPump)) { + return NS_OK; + } + + nsresult rv; + mInputStreamPump = do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mInputStreamPump->Init(mSocketInputStream, -1, -1, 0, 0, false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mInputStreamPump->AsyncRead(this, nullptr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +PresentationTCPSessionTransport::EnableDataNotification() +{ + if (NS_WARN_IF(!mCallback)) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + if (mDataNotificationEnabled) { + return NS_OK; + } + + mDataNotificationEnabled = true; + + if (IsReadyToNotifyData()) { + return CreateInputStreamPump(); + } + + return NS_OK; +} + +// nsIPresentationSessionTransportBuilderListener +NS_IMETHODIMP +PresentationTCPSessionTransport::GetCallback(nsIPresentationSessionTransportCallback** aCallback) +{ + nsCOMPtr<nsIPresentationSessionTransportCallback> callback = mCallback; + callback.forget(aCallback); + return NS_OK; +} + +NS_IMETHODIMP +PresentationTCPSessionTransport::SetCallback(nsIPresentationSessionTransportCallback* aCallback) +{ + mCallback = aCallback; + + if (!!mCallback && ReadyState::OPEN == mReadyState) { + // Notify the transport channel is ready. + Unused << NS_WARN_IF(NS_FAILED(mCallback->NotifyTransportReady())); + } + + return NS_OK; +} + +NS_IMETHODIMP +PresentationTCPSessionTransport::GetSelfAddress(nsINetAddr** aSelfAddress) +{ + if (NS_WARN_IF(!mTransport)) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + return mTransport->GetScriptableSelfAddr(aSelfAddress); +} + +void +PresentationTCPSessionTransport::EnsureCopying() +{ + if (mAsyncCopierActive) { + return; + } + + mAsyncCopierActive = true; + RefPtr<CopierCallbacks> callbacks = new CopierCallbacks(this); + Unused << NS_WARN_IF(NS_FAILED(mMultiplexStreamCopier->AsyncCopy(callbacks, nullptr))); +} + +void +PresentationTCPSessionTransport::NotifyCopyComplete(nsresult aStatus) +{ + mAsyncCopierActive = false; + mMultiplexStream->RemoveStream(0); + if (NS_WARN_IF(NS_FAILED(aStatus))) { + if (mReadyState != ReadyState::CLOSED) { + mCloseStatus = aStatus; + SetReadyState(ReadyState::CLOSED); + } + return; + } + + uint32_t count; + nsresult rv = mMultiplexStream->GetCount(&count); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + if (count) { + EnsureCopying(); + return; + } + + if (mReadyState == ReadyState::CLOSING) { + mSocketOutputStream->Close(); + mCloseStatus = NS_OK; + SetReadyState(ReadyState::CLOSED); + } +} + +NS_IMETHODIMP +PresentationTCPSessionTransport::Send(const nsAString& aData) +{ + if (NS_WARN_IF(mReadyState != ReadyState::OPEN)) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + nsresult rv; + nsCOMPtr<nsIStringInputStream> stream = + do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv); + if(NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + NS_ConvertUTF16toUTF8 msgString(aData); + rv = stream->SetData(msgString.BeginReading(), msgString.Length()); + if(NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + mMultiplexStream->AppendStream(stream); + + EnsureCopying(); + + return NS_OK; +} + +NS_IMETHODIMP +PresentationTCPSessionTransport::SendBinaryMsg(const nsACString& aData) +{ + return NS_ERROR_DOM_NOT_SUPPORTED_ERR; +} + +NS_IMETHODIMP +PresentationTCPSessionTransport::SendBlob(nsIDOMBlob* aBlob) +{ + return NS_ERROR_DOM_NOT_SUPPORTED_ERR; +} + +NS_IMETHODIMP +PresentationTCPSessionTransport::Close(nsresult aReason) +{ + PRES_DEBUG("%s:reason[%x]\n", __func__, aReason); + + if (mReadyState == ReadyState::CLOSED || mReadyState == ReadyState::CLOSING) { + return NS_OK; + } + + mCloseStatus = aReason; + SetReadyState(ReadyState::CLOSING); + + uint32_t count = 0; + mMultiplexStream->GetCount(&count); + if (!count) { + mSocketOutputStream->Close(); + } + + mSocketInputStream->Close(); + mDataNotificationEnabled = false; + + mListener = nullptr; + + return NS_OK; +} + +void +PresentationTCPSessionTransport::SetReadyState(ReadyState aReadyState) +{ + mReadyState = aReadyState; + + if (mReadyState == ReadyState::OPEN) { + if (IsReadyToNotifyData()) { + CreateInputStreamPump(); + } + + if (NS_WARN_IF(!mCallback)) { + return; + } + + // Notify the transport channel is ready. + Unused << NS_WARN_IF(NS_FAILED(mCallback->NotifyTransportReady())); + } else if (mReadyState == ReadyState::CLOSED && mCallback) { + if (NS_WARN_IF(!mCallback)) { + return; + } + + // Notify the transport channel has been shut down. + Unused << + NS_WARN_IF(NS_FAILED(mCallback->NotifyTransportClosed(mCloseStatus))); + mCallback = nullptr; + } +} + +// nsITransportEventSink +NS_IMETHODIMP +PresentationTCPSessionTransport::OnTransportStatus(nsITransport* aTransport, + nsresult aStatus, + int64_t aProgress, + int64_t aProgressMax) +{ + PRES_DEBUG("%s:aStatus[%x]\n", __func__, aStatus); + + MOZ_ASSERT(NS_IsMainThread()); + + if (aStatus != NS_NET_STATUS_CONNECTED_TO) { + return NS_OK; + } + + SetReadyState(ReadyState::OPEN); + + return NS_OK; +} + +// nsIInputStreamCallback +NS_IMETHODIMP +PresentationTCPSessionTransport::OnInputStreamReady(nsIAsyncInputStream* aStream) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // Only used for detecting if the connection was refused. + uint64_t dummy; + nsresult rv = aStream->Available(&dummy); + if (NS_WARN_IF(NS_FAILED(rv))) { + if (mReadyState != ReadyState::CLOSED) { + mCloseStatus = NS_ERROR_CONNECTION_REFUSED; + SetReadyState(ReadyState::CLOSED); + } + } + + return NS_OK; +} + +// nsIRequestObserver +NS_IMETHODIMP +PresentationTCPSessionTransport::OnStartRequest(nsIRequest* aRequest, + nsISupports* aContext) +{ + // Do nothing. + return NS_OK; +} + +NS_IMETHODIMP +PresentationTCPSessionTransport::OnStopRequest(nsIRequest* aRequest, + nsISupports* aContext, + nsresult aStatusCode) +{ + PRES_DEBUG("%s:aStatusCode[%x]\n", __func__, aStatusCode); + + MOZ_ASSERT(NS_IsMainThread()); + + uint32_t count; + nsresult rv = mMultiplexStream->GetCount(&count); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mInputStreamPump = nullptr; + + if (count != 0 && NS_SUCCEEDED(aStatusCode)) { + // If we have some buffered output still, and status is not an error, the + // other side has done a half-close, but we don't want to be in the close + // state until we are done sending everything that was buffered. We also + // don't want to call |NotifyTransportClosed| yet. + return NS_OK; + } + + // We call this even if there is no error. + if (mReadyState != ReadyState::CLOSED) { + mCloseStatus = aStatusCode; + SetReadyState(ReadyState::CLOSED); + } + return NS_OK; +} + +// nsIStreamListener +NS_IMETHODIMP +PresentationTCPSessionTransport::OnDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + nsIInputStream* aStream, + uint64_t aOffset, + uint32_t aCount) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (NS_WARN_IF(!mCallback)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCString data; + nsresult rv = mInputStreamScriptable->ReadBytes(aCount, data); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Pass the incoming data to the listener. + return mCallback->NotifyData(data, false); +} |