1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #include "TestCommon.h"
6 #include "gtest/gtest.h"
7 #include "nsISocketTransportService.h"
8 #include "nsISocketTransport.h"
9 #include "nsIServerSocket.h"
10 #include "nsIAsyncInputStream.h"
11 #include "mozilla/net/DNS.h"
12 #include "prerror.h"
13 #include "nsComponentManagerUtils.h"
14 #include "nsServiceManagerUtils.h"
15 
16 using namespace mozilla::net;
17 using namespace mozilla;
18 
19 class ServerListener : public nsIServerSocketListener {
20  public:
21   NS_DECL_ISUPPORTS
22   NS_DECL_NSISERVERSOCKETLISTENER
23 
24   explicit ServerListener(WaitForCondition* waiter);
25 
26   // Port that is got from server side will be store here.
27   uint32_t mClientPort;
28   bool mFailed;
29   RefPtr<WaitForCondition> mWaiter;
30 
31  private:
32   virtual ~ServerListener();
33 };
34 
NS_IMPL_ISUPPORTS(ServerListener,nsIServerSocketListener)35 NS_IMPL_ISUPPORTS(ServerListener, nsIServerSocketListener)
36 
37 ServerListener::ServerListener(WaitForCondition* waiter)
38     : mClientPort(-1), mFailed(false), mWaiter(waiter) {}
39 
40 ServerListener::~ServerListener() = default;
41 
42 NS_IMETHODIMP
OnSocketAccepted(nsIServerSocket * aServ,nsISocketTransport * aTransport)43 ServerListener::OnSocketAccepted(nsIServerSocket* aServ,
44                                  nsISocketTransport* aTransport) {
45   // Run on STS thread.
46   NetAddr peerAddr;
47   nsresult rv = aTransport->GetPeerAddr(&peerAddr);
48   if (NS_FAILED(rv)) {
49     mFailed = true;
50     mWaiter->Notify();
51     return NS_OK;
52   }
53   mClientPort = PR_ntohs(peerAddr.inet.port);
54   mWaiter->Notify();
55   return NS_OK;
56 }
57 
58 NS_IMETHODIMP
OnStopListening(nsIServerSocket * aServ,nsresult aStatus)59 ServerListener::OnStopListening(nsIServerSocket* aServ, nsresult aStatus) {
60   return NS_OK;
61 }
62 
63 class ClientInputCallback : public nsIInputStreamCallback {
64  public:
65   NS_DECL_THREADSAFE_ISUPPORTS
66   NS_DECL_NSIINPUTSTREAMCALLBACK
67 
68   explicit ClientInputCallback(WaitForCondition* waiter);
69 
70   bool mFailed;
71   RefPtr<WaitForCondition> mWaiter;
72 
73  private:
74   virtual ~ClientInputCallback();
75 };
76 
NS_IMPL_ISUPPORTS(ClientInputCallback,nsIInputStreamCallback)77 NS_IMPL_ISUPPORTS(ClientInputCallback, nsIInputStreamCallback)
78 
79 ClientInputCallback::ClientInputCallback(WaitForCondition* waiter)
80     : mFailed(false), mWaiter(waiter) {}
81 
82 ClientInputCallback::~ClientInputCallback() = default;
83 
84 NS_IMETHODIMP
OnInputStreamReady(nsIAsyncInputStream * aStream)85 ClientInputCallback::OnInputStreamReady(nsIAsyncInputStream* aStream) {
86   // Server doesn't send. That means if we are here, we probably have run into
87   // an error.
88   uint64_t avail;
89   nsresult rv = aStream->Available(&avail);
90   if (NS_FAILED(rv)) {
91     mFailed = true;
92   }
93   mWaiter->Notify();
94   return NS_OK;
95 }
96 
TEST(TestBind,MainTest)97 TEST(TestBind, MainTest)
98 {
99   //
100   // Server side.
101   //
102   nsCOMPtr<nsIServerSocket> server =
103       do_CreateInstance("@mozilla.org/network/server-socket;1");
104   ASSERT_TRUE(server);
105 
106   nsresult rv = server->Init(-1, true, -1);
107   ASSERT_TRUE(NS_SUCCEEDED(rv));
108 
109   int32_t serverPort;
110   rv = server->GetPort(&serverPort);
111   ASSERT_TRUE(NS_SUCCEEDED(rv));
112 
113   RefPtr<WaitForCondition> waiter = new WaitForCondition();
114 
115   // Listening.
116   RefPtr<ServerListener> serverListener = new ServerListener(waiter);
117   rv = server->AsyncListen(serverListener);
118   ASSERT_TRUE(NS_SUCCEEDED(rv));
119 
120   //
121   // Client side
122   //
123   uint32_t bindingPort = 20000;
124   nsCOMPtr<nsISocketTransportService> sts =
125       do_GetService("@mozilla.org/network/socket-transport-service;1", &rv);
126   ASSERT_TRUE(NS_SUCCEEDED(rv));
127 
128   nsCOMPtr<nsIInputStream> inputStream;
129   RefPtr<ClientInputCallback> clientCallback;
130 
131   for (int32_t tried = 0; tried < 100; tried++) {
132     nsCOMPtr<nsISocketTransport> client;
133     rv = sts->CreateTransport(nsTArray<nsCString>(), "127.0.0.1"_ns, serverPort,
134                               nullptr, nullptr, getter_AddRefs(client));
135     ASSERT_TRUE(NS_SUCCEEDED(rv));
136 
137     // Bind to a port. It's possible that we are binding to a port that is
138     // currently in use. If we failed to bind, we try next port.
139     NetAddr bindingAddr;
140     bindingAddr.inet.family = AF_INET;
141     bindingAddr.inet.ip = 0;
142     bindingAddr.inet.port = PR_htons(bindingPort);
143     rv = client->Bind(&bindingAddr);
144     ASSERT_TRUE(NS_SUCCEEDED(rv));
145 
146     // Open IO streams, to make client SocketTransport connect to server.
147     clientCallback = new ClientInputCallback(waiter);
148     rv = client->OpenInputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
149                                  getter_AddRefs(inputStream));
150     ASSERT_TRUE(NS_SUCCEEDED(rv));
151 
152     nsCOMPtr<nsIAsyncInputStream> asyncInputStream =
153         do_QueryInterface(inputStream);
154     rv = asyncInputStream->AsyncWait(clientCallback, 0, 0, nullptr);
155 
156     // Wait for server's response or callback of input stream.
157     waiter->Wait(1);
158     if (clientCallback->mFailed) {
159       // if client received error, we likely have bound a port that is in use.
160       // we can try another port.
161       bindingPort++;
162     } else {
163       // We are unlocked by server side, leave the loop and check result.
164       break;
165     }
166   }
167 
168   ASSERT_FALSE(serverListener->mFailed);
169   ASSERT_EQ(serverListener->mClientPort, bindingPort);
170 
171   inputStream->Close();
172   waiter->Wait(1);
173   ASSERT_TRUE(clientCallback->mFailed);
174 
175   server->Close();
176 }
177