1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "mozilla/AsyncEventDispatcher.h"
9 #include "mozilla/dom/File.h"
10 #include "mozilla/dom/ErrorEvent.h"
11 #include "mozilla/dom/network/UDPSocketChild.h"
12 #include "mozilla/dom/UDPMessageEvent.h"
13 #include "mozilla/dom/UDPSocketBinding.h"
14 #include "mozilla/dom/UnionTypes.h"
15 #include "mozilla/dom/RootedDictionary.h"
16 #include "mozilla/net/DNS.h"
17 #include "nsComponentManagerUtils.h"
18 #include "nsContentUtils.h"
19 #include "nsINetAddr.h"
20 #include "nsStringStream.h"
22 namespace mozilla::dom
{
24 NS_IMPL_ISUPPORTS(UDPSocket::ListenerProxy
, nsIUDPSocketListener
,
27 NS_IMPL_CYCLE_COLLECTION_CLASS(UDPSocket
)
29 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(UDPSocket
,
31 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOpened
)
32 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mClosed
)
33 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
35 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(UDPSocket
, DOMEventTargetHelper
)
36 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOpened
)
37 NS_IMPL_CYCLE_COLLECTION_UNLINK(mClosed
)
38 tmp
->CloseWithReason(NS_OK
);
39 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
41 NS_IMPL_ADDREF_INHERITED(UDPSocket
, DOMEventTargetHelper
)
42 NS_IMPL_RELEASE_INHERITED(UDPSocket
, DOMEventTargetHelper
)
44 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UDPSocket
)
45 NS_INTERFACE_MAP_ENTRY(nsIUDPSocketListener
)
46 NS_INTERFACE_MAP_ENTRY(nsIUDPSocketInternal
)
47 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
50 already_AddRefed
<UDPSocket
> UDPSocket::Constructor(const GlobalObject
& aGlobal
,
51 const UDPOptions
& aOptions
,
53 nsCOMPtr
<nsPIDOMWindowInner
> ownerWindow
=
54 do_QueryInterface(aGlobal
.GetAsSupports());
56 aRv
.Throw(NS_ERROR_FAILURE
);
60 bool addressReuse
= aOptions
.mAddressReuse
;
61 bool loopback
= aOptions
.mLoopback
;
63 nsCString remoteAddress
;
64 if (aOptions
.mRemoteAddress
.WasPassed()) {
65 CopyUTF16toUTF8(aOptions
.mRemoteAddress
.Value(), remoteAddress
);
67 remoteAddress
.SetIsVoid(true);
70 Nullable
<uint16_t> remotePort
;
71 if (aOptions
.mRemotePort
.WasPassed()) {
72 remotePort
.SetValue(aOptions
.mRemotePort
.Value());
74 if (remotePort
.Value() == 0) {
75 aRv
.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR
);
80 nsString localAddress
;
81 if (aOptions
.mLocalAddress
.WasPassed()) {
82 localAddress
= aOptions
.mLocalAddress
.Value();
84 // check if localAddress is a valid IPv4/6 address
85 NS_ConvertUTF16toUTF8
address(localAddress
);
86 if (!net::HostIsIPLiteral(address
)) {
87 aRv
.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR
);
91 SetDOMStringToNull(localAddress
);
94 Nullable
<uint16_t> localPort
;
95 if (aOptions
.mLocalPort
.WasPassed()) {
96 localPort
.SetValue(aOptions
.mLocalPort
.Value());
98 if (localPort
.Value() == 0) {
99 aRv
.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR
);
104 RefPtr
<UDPSocket
> socket
=
105 new UDPSocket(ownerWindow
, remoteAddress
, remotePort
);
106 aRv
= socket
->Init(localAddress
, localPort
, addressReuse
, loopback
);
108 if (NS_WARN_IF(aRv
.Failed())) {
112 return socket
.forget();
115 UDPSocket::UDPSocket(nsPIDOMWindowInner
* aOwner
,
116 const nsCString
& aRemoteAddress
,
117 const Nullable
<uint16_t>& aRemotePort
)
118 : DOMEventTargetHelper(aOwner
),
119 mRemoteAddress(aRemoteAddress
),
120 mRemotePort(aRemotePort
),
121 mAddressReuse(false),
123 mReadyState(SocketReadyState::Opening
) {
126 Document
* aDoc
= aOwner
->GetExtantDoc();
128 aDoc
->DisallowBFCaching();
132 UDPSocket::~UDPSocket() { CloseWithReason(NS_OK
); }
134 JSObject
* UDPSocket::WrapObject(JSContext
* aCx
,
135 JS::Handle
<JSObject
*> aGivenProto
) {
136 return UDPSocket_Binding::Wrap(aCx
, this, aGivenProto
);
139 void UDPSocket::DisconnectFromOwner() {
140 DOMEventTargetHelper::DisconnectFromOwner();
141 CloseWithReason(NS_OK
);
144 already_AddRefed
<Promise
> UDPSocket::Close() {
147 RefPtr
<Promise
> promise
= mClosed
;
149 if (mReadyState
== SocketReadyState::Closed
) {
150 return promise
.forget();
153 CloseWithReason(NS_OK
);
154 return promise
.forget();
157 void UDPSocket::CloseWithReason(nsresult aReason
) {
158 if (mReadyState
== SocketReadyState::Closed
) {
163 if (mReadyState
== SocketReadyState::Opening
) {
164 // reject openedPromise with AbortError if socket is closed without error
165 nsresult openFailedReason
=
166 NS_FAILED(aReason
) ? aReason
: NS_ERROR_DOM_ABORT_ERR
;
167 mOpened
->MaybeReject(openFailedReason
);
171 mReadyState
= SocketReadyState::Closed
;
173 if (mListenerProxy
) {
174 mListenerProxy
->Disconnect();
175 mListenerProxy
= nullptr;
184 mSocketChild
->Close();
185 mSocketChild
= nullptr;
189 if (NS_SUCCEEDED(aReason
)) {
190 mClosed
->MaybeResolveWithUndefined();
192 mClosed
->MaybeReject(aReason
);
196 mPendingMcastCommands
.Clear();
199 void UDPSocket::JoinMulticastGroup(const nsAString
& aMulticastGroupAddress
,
201 if (mReadyState
== SocketReadyState::Closed
) {
202 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
206 if (mReadyState
== SocketReadyState::Opening
) {
207 MulticastCommand
joinCommand(MulticastCommand::Join
,
208 aMulticastGroupAddress
);
209 mPendingMcastCommands
.AppendElement(joinCommand
);
213 MOZ_ASSERT(mSocket
|| mSocketChild
);
215 NS_ConvertUTF16toUTF8
address(aMulticastGroupAddress
);
218 MOZ_ASSERT(!mSocketChild
);
220 aRv
= mSocket
->JoinMulticast(address
, ""_ns
);
221 NS_WARNING_ASSERTION(!aRv
.Failed(), "JoinMulticast failed");
226 MOZ_ASSERT(mSocketChild
);
228 mSocketChild
->JoinMulticast(address
, ""_ns
);
231 void UDPSocket::LeaveMulticastGroup(const nsAString
& aMulticastGroupAddress
,
233 if (mReadyState
== SocketReadyState::Closed
) {
234 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
238 if (mReadyState
== SocketReadyState::Opening
) {
239 MulticastCommand
leaveCommand(MulticastCommand::Leave
,
240 aMulticastGroupAddress
);
241 mPendingMcastCommands
.AppendElement(leaveCommand
);
245 MOZ_ASSERT(mSocket
|| mSocketChild
);
247 nsCString address
= NS_ConvertUTF16toUTF8(aMulticastGroupAddress
);
249 MOZ_ASSERT(!mSocketChild
);
251 aRv
= mSocket
->LeaveMulticast(address
, ""_ns
);
252 NS_WARNING_ASSERTION(!aRv
.Failed(), "mSocket->LeaveMulticast failed");
256 MOZ_ASSERT(mSocketChild
);
258 mSocketChild
->LeaveMulticast(address
, ""_ns
);
261 nsresult
UDPSocket::DoPendingMcastCommand() {
262 MOZ_ASSERT(mReadyState
== SocketReadyState::Open
,
263 "Multicast command can only be executed after socket opened");
265 for (uint32_t i
= 0; i
< mPendingMcastCommands
.Length(); ++i
) {
266 MulticastCommand
& command
= mPendingMcastCommands
[i
];
269 switch (command
.mCommand
) {
270 case MulticastCommand::Join
: {
271 JoinMulticastGroup(command
.mAddress
, rv
);
274 case MulticastCommand::Leave
: {
275 LeaveMulticastGroup(command
.mAddress
, rv
);
280 if (NS_WARN_IF(rv
.Failed())) {
281 return rv
.StealNSResult();
285 mPendingMcastCommands
.Clear();
289 bool UDPSocket::Send(const StringOrBlobOrArrayBufferOrArrayBufferView
& aData
,
290 const Optional
<nsAString
>& aRemoteAddress
,
291 const Optional
<Nullable
<uint16_t>>& aRemotePort
,
293 if (mReadyState
!= SocketReadyState::Open
) {
294 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
298 MOZ_ASSERT(mSocket
|| mSocketChild
);
300 // If the remote address and port were not specified in the constructor or as
301 // arguments, throw InvalidAccessError.
302 nsCString remoteAddress
;
303 if (aRemoteAddress
.WasPassed()) {
304 CopyUTF16toUTF8(aRemoteAddress
.Value(), remoteAddress
);
305 UDPSOCKET_LOG(("%s: Send to %s", __FUNCTION__
, remoteAddress
.get()));
306 } else if (!mRemoteAddress
.IsVoid()) {
307 remoteAddress
= mRemoteAddress
;
308 UDPSOCKET_LOG(("%s: Send to %s", __FUNCTION__
, remoteAddress
.get()));
310 aRv
.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR
);
315 if (aRemotePort
.WasPassed() && !aRemotePort
.Value().IsNull()) {
316 remotePort
= aRemotePort
.Value().Value();
317 } else if (!mRemotePort
.IsNull()) {
318 remotePort
= mRemotePort
.Value();
320 aRv
.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR
);
324 nsCOMPtr
<nsIInputStream
> stream
;
325 if (aData
.IsBlob()) {
326 Blob
& blob
= aData
.GetAsBlob();
328 blob
.CreateInputStream(getter_AddRefs(stream
), aRv
);
329 if (NS_WARN_IF(aRv
.Failed())) {
334 nsCOMPtr
<nsIStringInputStream
> strStream
=
335 do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID
, &rv
);
336 if (NS_WARN_IF(NS_FAILED(rv
))) {
341 if (aData
.IsString()) {
342 NS_ConvertUTF16toUTF8
data(aData
.GetAsString());
343 aRv
= strStream
->SetData(data
.BeginReading(), data
.Length());
346 if (!AppendTypedArrayDataTo(aData
, data
)) {
347 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
350 size_t length
= data
.length();
351 aRv
= strStream
->AdoptData(data
.extractOrCopyRawBuffer(), length
);
354 if (NS_WARN_IF(aRv
.Failed())) {
362 aRv
= mSocket
->SendBinaryStream(remoteAddress
, remotePort
, stream
);
363 } else if (mSocketChild
) {
364 aRv
= mSocketChild
->SendBinaryStream(remoteAddress
, remotePort
, stream
);
367 if (NS_WARN_IF(aRv
.Failed())) {
374 nsresult
UDPSocket::InitLocal(const nsAString
& aLocalAddress
,
375 const uint16_t& aLocalPort
) {
378 nsCOMPtr
<nsIUDPSocket
> sock
=
379 do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv
);
384 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(GetOwner(), &rv
);
389 nsCOMPtr
<nsIPrincipal
> principal
= global
->PrincipalOrNull();
391 return NS_ERROR_FAILURE
;
394 if (aLocalAddress
.IsEmpty()) {
395 rv
= sock
->Init(aLocalPort
, /* loopback = */ false, principal
,
396 mAddressReuse
, /* optionalArgc = */ 1);
399 PR_InitializeNetAddr(PR_IpAddrAny
, aLocalPort
, &prAddr
);
400 PR_StringToNetAddr(NS_ConvertUTF16toUTF8(aLocalAddress
).BeginReading(),
402 UDPSOCKET_LOG(("%s: %s:%u", __FUNCTION__
,
403 NS_ConvertUTF16toUTF8(aLocalAddress
).get(), aLocalPort
));
405 mozilla::net::NetAddr
addr(&prAddr
);
406 rv
= sock
->InitWithAddress(&addr
, principal
, mAddressReuse
,
407 /* optionalArgc = */ 1);
413 rv
= sock
->SetMulticastLoopback(mLoopback
);
420 // Get real local address and local port
421 nsCOMPtr
<nsINetAddr
> localAddr
;
422 rv
= mSocket
->GetLocalAddr(getter_AddRefs(localAddr
));
427 nsCString localAddress
;
428 rv
= localAddr
->GetAddress(localAddress
);
432 CopyUTF8toUTF16(localAddress
, mLocalAddress
);
435 rv
= localAddr
->GetPort(&localPort
);
439 mLocalPort
.SetValue(localPort
);
441 mListenerProxy
= new ListenerProxy(this);
443 rv
= mSocket
->AsyncListen(mListenerProxy
);
448 mReadyState
= SocketReadyState::Open
;
449 rv
= DoPendingMcastCommand();
454 mOpened
->MaybeResolveWithUndefined();
459 nsresult
UDPSocket::InitRemote(const nsAString
& aLocalAddress
,
460 const uint16_t& aLocalPort
) {
463 RefPtr
<UDPSocketChild
> sock
= new UDPSocketChild();
465 mListenerProxy
= new ListenerProxy(this);
467 nsCOMPtr
<nsIGlobalObject
> obj
= do_QueryInterface(GetOwner(), &rv
);
472 nsCOMPtr
<nsIPrincipal
> principal
= obj
->PrincipalOrNull();
474 return NS_ERROR_FAILURE
;
477 rv
= sock
->Bind(mListenerProxy
, principal
,
478 NS_ConvertUTF16toUTF8(aLocalAddress
), aLocalPort
,
479 mAddressReuse
, mLoopback
, 0, 0);
490 nsresult
UDPSocket::Init(const nsString
& aLocalAddress
,
491 const Nullable
<uint16_t>& aLocalPort
,
492 const bool& aAddressReuse
, const bool& aLoopback
) {
493 MOZ_ASSERT(!mSocket
&& !mSocketChild
);
495 mLocalAddress
= aLocalAddress
;
496 mLocalPort
= aLocalPort
;
497 mAddressReuse
= aAddressReuse
;
498 mLoopback
= aLoopback
;
501 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(GetOwner());
503 mOpened
= Promise::Create(global
, rv
);
504 if (NS_WARN_IF(rv
.Failed())) {
505 return rv
.StealNSResult();
508 mClosed
= Promise::Create(global
, rv
);
509 if (NS_WARN_IF(rv
.Failed())) {
510 return rv
.StealNSResult();
513 class OpenSocketRunnable final
: public Runnable
{
515 explicit OpenSocketRunnable(UDPSocket
* aSocket
)
516 : mozilla::Runnable("OpenSocketRunnable"), mSocket(aSocket
) {}
518 NS_IMETHOD
Run() override
{
521 if (mSocket
->mReadyState
!= SocketReadyState::Opening
) {
525 uint16_t localPort
= 0;
526 if (!mSocket
->mLocalPort
.IsNull()) {
527 localPort
= mSocket
->mLocalPort
.Value();
531 if (!XRE_IsParentProcess()) {
532 rv
= mSocket
->InitRemote(mSocket
->mLocalAddress
, localPort
);
534 rv
= mSocket
->InitLocal(mSocket
->mLocalAddress
, localPort
);
537 if (NS_WARN_IF(NS_FAILED(rv
))) {
538 mSocket
->CloseWithReason(NS_ERROR_DOM_NETWORK_ERR
);
545 RefPtr
<UDPSocket
> mSocket
;
548 nsCOMPtr
<nsIRunnable
> runnable
= new OpenSocketRunnable(this);
550 return NS_DispatchToMainThread(runnable
);
553 void UDPSocket::HandleReceivedData(const nsACString
& aRemoteAddress
,
554 const uint16_t& aRemotePort
,
555 const nsTArray
<uint8_t>& aData
) {
556 if (mReadyState
!= SocketReadyState::Open
) {
560 if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
564 if (NS_FAILED(DispatchReceivedData(aRemoteAddress
, aRemotePort
, aData
))) {
565 CloseWithReason(NS_ERROR_UNEXPECTED
);
569 nsresult
UDPSocket::DispatchReceivedData(const nsACString
& aRemoteAddress
,
570 const uint16_t& aRemotePort
,
571 const nsTArray
<uint8_t>& aData
) {
574 if (NS_WARN_IF(!jsapi
.Init(GetOwner()))) {
575 return NS_ERROR_FAILURE
;
578 JSContext
* cx
= jsapi
.cx();
580 // Copy packet data to ArrayBuffer
582 JS::Rooted
<JSObject
*> arrayBuf(cx
, ArrayBuffer::Create(cx
, aData
, error
));
584 if (NS_WARN_IF(error
.Failed())) {
585 return error
.StealNSResult();
588 JS::Rooted
<JS::Value
> jsData(cx
, JS::ObjectValue(*arrayBuf
));
591 RootedDictionary
<UDPMessageEventInit
> init(cx
);
592 CopyUTF8toUTF16(aRemoteAddress
, init
.mRemoteAddress
);
593 init
.mRemotePort
= aRemotePort
;
596 RefPtr
<UDPMessageEvent
> udpEvent
=
597 UDPMessageEvent::Constructor(this, u
"message"_ns
, init
);
599 if (NS_WARN_IF(!udpEvent
)) {
600 return NS_ERROR_FAILURE
;
603 udpEvent
->SetTrusted(true);
605 RefPtr
<AsyncEventDispatcher
> asyncDispatcher
=
606 new AsyncEventDispatcher(this, udpEvent
.forget());
608 return asyncDispatcher
->PostDOMEvent();
611 // nsIUDPSocketListener
614 UDPSocket::OnPacketReceived(nsIUDPSocket
* aSocket
, nsIUDPMessage
* aMessage
) {
615 // nsIUDPSocketListener callbacks should be invoked on main thread.
616 MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
618 // Create appropriate JS object for message
619 FallibleTArray
<uint8_t>& buffer
= aMessage
->GetDataAsTArray();
621 nsCOMPtr
<nsINetAddr
> addr
;
622 if (NS_WARN_IF(NS_FAILED(aMessage
->GetFromAddr(getter_AddRefs(addr
))))) {
626 nsCString remoteAddress
;
627 if (NS_WARN_IF(NS_FAILED(addr
->GetAddress(remoteAddress
)))) {
632 if (NS_WARN_IF(NS_FAILED(addr
->GetPort(&remotePort
)))) {
636 HandleReceivedData(remoteAddress
, remotePort
, buffer
);
641 UDPSocket::OnStopListening(nsIUDPSocket
* aSocket
, nsresult aStatus
) {
642 // nsIUDPSocketListener callbacks should be invoked on main thread.
643 MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
645 CloseWithReason(aStatus
);
650 // nsIUDPSocketInternal
653 UDPSocket::CallListenerError(const nsACString
& aMessage
,
654 const nsACString
& aFilename
,
655 uint32_t aLineNumber
) {
656 CloseWithReason(NS_ERROR_DOM_NETWORK_ERR
);
662 UDPSocket::CallListenerReceivedData(const nsACString
& aRemoteAddress
,
663 uint16_t aRemotePort
,
664 const nsTArray
<uint8_t>& aData
) {
665 HandleReceivedData(aRemoteAddress
, aRemotePort
, aData
);
671 UDPSocket::CallListenerOpened() {
672 if (mReadyState
!= SocketReadyState::Opening
) {
676 MOZ_ASSERT(mSocketChild
);
678 // Get real local address and local port
679 CopyUTF8toUTF16(mSocketChild
->LocalAddress(), mLocalAddress
);
681 mLocalPort
.SetValue(mSocketChild
->LocalPort());
683 mReadyState
= SocketReadyState::Open
;
684 nsresult rv
= DoPendingMcastCommand();
686 if (NS_WARN_IF(NS_FAILED(rv
))) {
691 mOpened
->MaybeResolveWithUndefined();
697 UDPSocket::CallListenerConnected() {
698 // This shouldn't be called here.
705 UDPSocket::CallListenerClosed() {
706 CloseWithReason(NS_OK
);
711 } // namespace mozilla::dom