Bug 1892041 - Part 1: Update test262 features. r=spidermonkey-reviewers,dminor
[gecko.git] / dom / network / UDPSocket.cpp
blob7dfcae36526feaeedbfe185ebbe87bf29415e2bd
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/. */
7 #include "UDPSocket.h"
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,
25 nsIUDPSocketInternal)
27 NS_IMPL_CYCLE_COLLECTION_CLASS(UDPSocket)
29 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(UDPSocket,
30 DOMEventTargetHelper)
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)
49 /* static */
50 already_AddRefed<UDPSocket> UDPSocket::Constructor(const GlobalObject& aGlobal,
51 const UDPOptions& aOptions,
52 ErrorResult& aRv) {
53 nsCOMPtr<nsPIDOMWindowInner> ownerWindow =
54 do_QueryInterface(aGlobal.GetAsSupports());
55 if (!ownerWindow) {
56 aRv.Throw(NS_ERROR_FAILURE);
57 return nullptr;
60 bool addressReuse = aOptions.mAddressReuse;
61 bool loopback = aOptions.mLoopback;
63 nsCString remoteAddress;
64 if (aOptions.mRemoteAddress.WasPassed()) {
65 CopyUTF16toUTF8(aOptions.mRemoteAddress.Value(), remoteAddress);
66 } else {
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);
76 return nullptr;
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);
88 return nullptr;
90 } else {
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);
100 return nullptr;
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())) {
109 return nullptr;
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),
122 mLoopback(false),
123 mReadyState(SocketReadyState::Opening) {
124 MOZ_ASSERT(aOwner);
126 Document* aDoc = aOwner->GetExtantDoc();
127 if (aDoc) {
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() {
145 MOZ_ASSERT(mClosed);
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) {
159 return;
162 if (mOpened) {
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;
178 if (mSocket) {
179 mSocket->Close();
180 mSocket = nullptr;
183 if (mSocketChild) {
184 mSocketChild->Close();
185 mSocketChild = nullptr;
188 if (mClosed) {
189 if (NS_SUCCEEDED(aReason)) {
190 mClosed->MaybeResolveWithUndefined();
191 } else {
192 mClosed->MaybeReject(aReason);
196 mPendingMcastCommands.Clear();
199 void UDPSocket::JoinMulticastGroup(const nsAString& aMulticastGroupAddress,
200 ErrorResult& aRv) {
201 if (mReadyState == SocketReadyState::Closed) {
202 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
203 return;
206 if (mReadyState == SocketReadyState::Opening) {
207 MulticastCommand joinCommand(MulticastCommand::Join,
208 aMulticastGroupAddress);
209 mPendingMcastCommands.AppendElement(joinCommand);
210 return;
213 MOZ_ASSERT(mSocket || mSocketChild);
215 NS_ConvertUTF16toUTF8 address(aMulticastGroupAddress);
217 if (mSocket) {
218 MOZ_ASSERT(!mSocketChild);
220 aRv = mSocket->JoinMulticast(address, ""_ns);
221 NS_WARNING_ASSERTION(!aRv.Failed(), "JoinMulticast failed");
223 return;
226 MOZ_ASSERT(mSocketChild);
228 mSocketChild->JoinMulticast(address, ""_ns);
231 void UDPSocket::LeaveMulticastGroup(const nsAString& aMulticastGroupAddress,
232 ErrorResult& aRv) {
233 if (mReadyState == SocketReadyState::Closed) {
234 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
235 return;
238 if (mReadyState == SocketReadyState::Opening) {
239 MulticastCommand leaveCommand(MulticastCommand::Leave,
240 aMulticastGroupAddress);
241 mPendingMcastCommands.AppendElement(leaveCommand);
242 return;
245 MOZ_ASSERT(mSocket || mSocketChild);
247 nsCString address = NS_ConvertUTF16toUTF8(aMulticastGroupAddress);
248 if (mSocket) {
249 MOZ_ASSERT(!mSocketChild);
251 aRv = mSocket->LeaveMulticast(address, ""_ns);
252 NS_WARNING_ASSERTION(!aRv.Failed(), "mSocket->LeaveMulticast failed");
253 return;
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];
267 ErrorResult rv;
269 switch (command.mCommand) {
270 case MulticastCommand::Join: {
271 JoinMulticastGroup(command.mAddress, rv);
272 break;
274 case MulticastCommand::Leave: {
275 LeaveMulticastGroup(command.mAddress, rv);
276 break;
280 if (NS_WARN_IF(rv.Failed())) {
281 return rv.StealNSResult();
285 mPendingMcastCommands.Clear();
286 return NS_OK;
289 bool UDPSocket::Send(const StringOrBlobOrArrayBufferOrArrayBufferView& aData,
290 const Optional<nsAString>& aRemoteAddress,
291 const Optional<Nullable<uint16_t>>& aRemotePort,
292 ErrorResult& aRv) {
293 if (mReadyState != SocketReadyState::Open) {
294 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
295 return false;
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()));
309 } else {
310 aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
311 return false;
314 uint16_t remotePort;
315 if (aRemotePort.WasPassed() && !aRemotePort.Value().IsNull()) {
316 remotePort = aRemotePort.Value().Value();
317 } else if (!mRemotePort.IsNull()) {
318 remotePort = mRemotePort.Value();
319 } else {
320 aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
321 return false;
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())) {
330 return false;
332 } else {
333 nsresult rv;
334 nsCOMPtr<nsIStringInputStream> strStream =
335 do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
336 if (NS_WARN_IF(NS_FAILED(rv))) {
337 aRv.Throw(rv);
338 return false;
341 if (aData.IsString()) {
342 NS_ConvertUTF16toUTF8 data(aData.GetAsString());
343 aRv = strStream->SetData(data.BeginReading(), data.Length());
344 } else {
345 Vector<char> data;
346 if (!AppendTypedArrayDataTo(aData, data)) {
347 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
348 return false;
350 size_t length = data.length();
351 aRv = strStream->AdoptData(data.extractOrCopyRawBuffer(), length);
354 if (NS_WARN_IF(aRv.Failed())) {
355 return false;
358 stream = strStream;
361 if (mSocket) {
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())) {
368 return false;
371 return true;
374 nsresult UDPSocket::InitLocal(const nsAString& aLocalAddress,
375 const uint16_t& aLocalPort) {
376 nsresult rv;
378 nsCOMPtr<nsIUDPSocket> sock =
379 do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv);
380 if (NS_FAILED(rv)) {
381 return rv;
384 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner(), &rv);
385 if (NS_FAILED(rv)) {
386 return rv;
389 nsCOMPtr<nsIPrincipal> principal = global->PrincipalOrNull();
390 if (!principal) {
391 return NS_ERROR_FAILURE;
394 if (aLocalAddress.IsEmpty()) {
395 rv = sock->Init(aLocalPort, /* loopback = */ false, principal,
396 mAddressReuse, /* optionalArgc = */ 1);
397 } else {
398 PRNetAddr prAddr;
399 PR_InitializeNetAddr(PR_IpAddrAny, aLocalPort, &prAddr);
400 PR_StringToNetAddr(NS_ConvertUTF16toUTF8(aLocalAddress).BeginReading(),
401 &prAddr);
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);
409 if (NS_FAILED(rv)) {
410 return rv;
413 rv = sock->SetMulticastLoopback(mLoopback);
414 if (NS_FAILED(rv)) {
415 return rv;
418 mSocket = sock;
420 // Get real local address and local port
421 nsCOMPtr<nsINetAddr> localAddr;
422 rv = mSocket->GetLocalAddr(getter_AddRefs(localAddr));
423 if (NS_FAILED(rv)) {
424 return rv;
427 nsCString localAddress;
428 rv = localAddr->GetAddress(localAddress);
429 if (NS_FAILED(rv)) {
430 return rv;
432 CopyUTF8toUTF16(localAddress, mLocalAddress);
434 uint16_t localPort;
435 rv = localAddr->GetPort(&localPort);
436 if (NS_FAILED(rv)) {
437 return rv;
439 mLocalPort.SetValue(localPort);
441 mListenerProxy = new ListenerProxy(this);
443 rv = mSocket->AsyncListen(mListenerProxy);
444 if (NS_FAILED(rv)) {
445 return rv;
448 mReadyState = SocketReadyState::Open;
449 rv = DoPendingMcastCommand();
450 if (NS_FAILED(rv)) {
451 return rv;
454 mOpened->MaybeResolveWithUndefined();
456 return NS_OK;
459 nsresult UDPSocket::InitRemote(const nsAString& aLocalAddress,
460 const uint16_t& aLocalPort) {
461 nsresult rv;
463 RefPtr<UDPSocketChild> sock = new UDPSocketChild();
465 mListenerProxy = new ListenerProxy(this);
467 nsCOMPtr<nsIGlobalObject> obj = do_QueryInterface(GetOwner(), &rv);
468 if (NS_FAILED(rv)) {
469 return rv;
472 nsCOMPtr<nsIPrincipal> principal = obj->PrincipalOrNull();
473 if (!principal) {
474 return NS_ERROR_FAILURE;
477 rv = sock->Bind(mListenerProxy, principal,
478 NS_ConvertUTF16toUTF8(aLocalAddress), aLocalPort,
479 mAddressReuse, mLoopback, 0, 0);
481 if (NS_FAILED(rv)) {
482 return rv;
485 mSocketChild = sock;
487 return NS_OK;
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;
500 ErrorResult rv;
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 {
514 public:
515 explicit OpenSocketRunnable(UDPSocket* aSocket)
516 : mozilla::Runnable("OpenSocketRunnable"), mSocket(aSocket) {}
518 NS_IMETHOD Run() override {
519 MOZ_ASSERT(mSocket);
521 if (mSocket->mReadyState != SocketReadyState::Opening) {
522 return NS_OK;
525 uint16_t localPort = 0;
526 if (!mSocket->mLocalPort.IsNull()) {
527 localPort = mSocket->mLocalPort.Value();
530 nsresult rv;
531 if (!XRE_IsParentProcess()) {
532 rv = mSocket->InitRemote(mSocket->mLocalAddress, localPort);
533 } else {
534 rv = mSocket->InitLocal(mSocket->mLocalAddress, localPort);
537 if (NS_WARN_IF(NS_FAILED(rv))) {
538 mSocket->CloseWithReason(NS_ERROR_DOM_NETWORK_ERR);
541 return NS_OK;
544 private:
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) {
557 return;
560 if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
561 return;
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) {
572 AutoJSAPI jsapi;
574 if (NS_WARN_IF(!jsapi.Init(GetOwner()))) {
575 return NS_ERROR_FAILURE;
578 JSContext* cx = jsapi.cx();
580 // Copy packet data to ArrayBuffer
581 ErrorResult error;
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));
590 // Create DOM event
591 RootedDictionary<UDPMessageEventInit> init(cx);
592 CopyUTF8toUTF16(aRemoteAddress, init.mRemoteAddress);
593 init.mRemotePort = aRemotePort;
594 init.mData = jsData;
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
613 NS_IMETHODIMP
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))))) {
623 return NS_OK;
626 nsCString remoteAddress;
627 if (NS_WARN_IF(NS_FAILED(addr->GetAddress(remoteAddress)))) {
628 return NS_OK;
631 uint16_t remotePort;
632 if (NS_WARN_IF(NS_FAILED(addr->GetPort(&remotePort)))) {
633 return NS_OK;
636 HandleReceivedData(remoteAddress, remotePort, buffer);
637 return NS_OK;
640 NS_IMETHODIMP
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);
647 return NS_OK;
650 // nsIUDPSocketInternal
652 NS_IMETHODIMP
653 UDPSocket::CallListenerError(const nsACString& aMessage,
654 const nsACString& aFilename,
655 uint32_t aLineNumber) {
656 CloseWithReason(NS_ERROR_DOM_NETWORK_ERR);
658 return NS_OK;
661 NS_IMETHODIMP
662 UDPSocket::CallListenerReceivedData(const nsACString& aRemoteAddress,
663 uint16_t aRemotePort,
664 const nsTArray<uint8_t>& aData) {
665 HandleReceivedData(aRemoteAddress, aRemotePort, aData);
667 return NS_OK;
670 NS_IMETHODIMP
671 UDPSocket::CallListenerOpened() {
672 if (mReadyState != SocketReadyState::Opening) {
673 return NS_OK;
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))) {
687 CloseWithReason(rv);
688 return NS_OK;
691 mOpened->MaybeResolveWithUndefined();
693 return NS_OK;
696 NS_IMETHODIMP
697 UDPSocket::CallListenerConnected() {
698 // This shouldn't be called here.
699 MOZ_CRASH();
701 return NS_OK;
704 NS_IMETHODIMP
705 UDPSocket::CallListenerClosed() {
706 CloseWithReason(NS_OK);
708 return NS_OK;
711 } // namespace mozilla::dom