1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <stdio.h>
6#include <string.h>
7#include <sstream>
8
9#include "echo_server.h"
10
11#include "ppapi/cpp/host_resolver.h"
12#include "ppapi/cpp/instance.h"
13#include "ppapi/cpp/module.h"
14#include "ppapi/cpp/tcp_socket.h"
15#include "ppapi/cpp/udp_socket.h"
16#include "ppapi/cpp/var.h"
17#include "ppapi/utility/completion_callback_factory.h"
18
19#ifdef WIN32
20#undef PostMessage
21// Allow 'this' in initializer list
22#pragma warning(disable : 4355)
23#endif
24
25class ExampleInstance : public pp::Instance {
26 public:
27  explicit ExampleInstance(PP_Instance instance)
28    : pp::Instance(instance),
29      callback_factory_(this),
30      send_outstanding_(false),
31      echo_server_(NULL) {}
32
33  virtual ~ExampleInstance() {
34    delete echo_server_;
35  }
36
37  virtual void HandleMessage(const pp::Var& var_message);
38
39 private:
40  bool IsConnected();
41  bool IsUDP();
42
43  void Connect(const std::string& host, bool tcp);
44  void Close();
45  void Send(const std::string& message);
46  void Receive();
47
48  void OnConnectCompletion(int32_t result);
49  void OnResolveCompletion(int32_t result);
50  void OnReceiveCompletion(int32_t result);
51  void OnReceiveFromCompletion(int32_t result, pp::NetAddress source);
52  void OnSendCompletion(int32_t result);
53
54  pp::CompletionCallbackFactory<ExampleInstance> callback_factory_;
55  pp::TCPSocket tcp_socket_;
56  pp::UDPSocket udp_socket_;
57  pp::HostResolver resolver_;
58  pp::NetAddress remote_host_;
59
60  char receive_buffer_[kBufferSize];
61  bool send_outstanding_;
62  EchoServer* echo_server_;
63};
64
65#define MSG_CREATE_TCP 't'
66#define MSG_CREATE_UDP 'u'
67#define MSG_SEND 's'
68#define MSG_CLOSE 'c'
69#define MSG_LISTEN 'l'
70
71void ExampleInstance::HandleMessage(const pp::Var& var_message) {
72  if (!var_message.is_string())
73    return;
74  std::string message = var_message.AsString();
75  // This message must contain a command character followed by ';' and
76  // arguments like "X;arguments".
77  if (message.length() < 2 || message[1] != ';')
78    return;
79  switch (message[0]) {
80    case MSG_CREATE_UDP:
81      // The command 'b' requests to create a UDP connection the
82      // specified HOST.
83      // HOST is passed as an argument like "t;HOST".
84      Connect(message.substr(2), false);
85      break;
86    case MSG_CREATE_TCP:
87      // The command 'o' requests to connect to the specified HOST.
88      // HOST is passed as an argument like "u;HOST".
89      Connect(message.substr(2), true);
90      break;
91    case MSG_CLOSE:
92      // The command 'c' requests to close without any argument like "c;"
93      Close();
94      break;
95    case MSG_LISTEN:
96      {
97        // The command 'l' starts a listening socket (server).
98        int port = atoi(message.substr(2).c_str());
99        echo_server_ = new EchoServer(this, port);
100        break;
101      }
102    case MSG_SEND:
103      // The command 't' requests to send a message as a text frame. The
104      // message passed as an argument like "t;message".
105      Send(message.substr(2));
106      break;
107    default:
108      std::ostringstream status;
109      status << "Unhandled message from JavaScript: " << message;
110      PostMessage(status.str());
111      break;
112  }
113}
114
115bool ExampleInstance::IsConnected() {
116  if (!tcp_socket_.is_null())
117    return true;
118  if (!udp_socket_.is_null())
119    return true;
120
121  return false;
122}
123
124bool ExampleInstance::IsUDP() {
125  return !udp_socket_.is_null();
126}
127
128void ExampleInstance::Connect(const std::string& host, bool tcp) {
129  if (IsConnected()) {
130    PostMessage("Already connected.");
131    return;
132  }
133
134  if (tcp) {
135    if (!pp::TCPSocket::IsAvailable()) {
136      PostMessage("TCPSocket not available");
137      return;
138    }
139
140    tcp_socket_ = pp::TCPSocket(this);
141    if (tcp_socket_.is_null()) {
142      PostMessage("Error creating TCPSocket.");
143      return;
144    }
145  } else {
146    if (!pp::UDPSocket::IsAvailable()) {
147      PostMessage("UDPSocket not available");
148      return;
149    }
150
151    udp_socket_ = pp::UDPSocket(this);
152    if (udp_socket_.is_null()) {
153      PostMessage("Error creating UDPSocket.");
154      return;
155    }
156  }
157
158  if (!pp::HostResolver::IsAvailable()) {
159    PostMessage("HostResolver not available");
160    return;
161  }
162
163  resolver_ = pp::HostResolver(this);
164  if (resolver_.is_null()) {
165    PostMessage("Error creating HostResolver.");
166    return;
167  }
168
169  int port = 80;
170  std::string hostname = host;
171  size_t pos = host.rfind(':');
172  if (pos != std::string::npos) {
173    hostname = host.substr(0, pos);
174    port = atoi(host.substr(pos+1).c_str());
175  }
176
177  pp::CompletionCallback callback =
178      callback_factory_.NewCallback(&ExampleInstance::OnResolveCompletion);
179  PP_HostResolver_Hint hint = { PP_NETADDRESS_FAMILY_UNSPECIFIED, 0 };
180  resolver_.Resolve(hostname.c_str(), port, hint, callback);
181  PostMessage("Resolving ...");
182}
183
184void ExampleInstance::OnResolveCompletion(int32_t result) {
185  if (result != PP_OK) {
186    PostMessage("Resolve failed.");
187    return;
188  }
189
190  pp::NetAddress addr = resolver_.GetNetAddress(0);
191  PostMessage(std::string("Resolved: ") +
192              addr.DescribeAsString(true).AsString());
193
194  pp::CompletionCallback callback =
195      callback_factory_.NewCallback(&ExampleInstance::OnConnectCompletion);
196
197  if (IsUDP()) {
198    PostMessage("Binding ...");
199    remote_host_ = addr;
200    PP_NetAddress_IPv4 ipv4_addr = { 0, { 0 } };
201    udp_socket_.Bind(pp::NetAddress(this, ipv4_addr), callback);
202  } else {
203    PostMessage("Connecting ...");
204    tcp_socket_.Connect(addr, callback);
205  }
206}
207
208void ExampleInstance::Close() {
209  if (!IsConnected()) {
210    PostMessage("Not connected.");
211    return;
212  }
213
214  if (tcp_socket_.is_null()) {
215    udp_socket_.Close();
216    udp_socket_ = pp::UDPSocket();
217  } else {
218    tcp_socket_.Close();
219    tcp_socket_ = pp::TCPSocket();
220  }
221
222  PostMessage("Closed connection.");
223}
224
225void ExampleInstance::Send(const std::string& message) {
226  if (!IsConnected()) {
227    PostMessage("Not connected.");
228    return;
229  }
230
231  if (send_outstanding_) {
232    PostMessage("Already sending.");
233    return;
234  }
235
236  uint32_t size = message.size();
237  const char* data = message.c_str();
238  pp::CompletionCallback callback =
239      callback_factory_.NewCallback(&ExampleInstance::OnSendCompletion);
240  int32_t result;
241  if (IsUDP())
242     result = udp_socket_.SendTo(data, size, remote_host_, callback);
243  else
244     result = tcp_socket_.Write(data, size, callback);
245  std::ostringstream status;
246  if (result < 0) {
247    if (result == PP_OK_COMPLETIONPENDING) {
248      status << "Sending bytes: " << size;
249      PostMessage(status.str());
250      send_outstanding_ = true;
251    } else {
252      status << "Send returned error: " << result;
253      PostMessage(status.str());
254    }
255  } else {
256    status << "Sent bytes synchronously: " << result;
257    PostMessage(status.str());
258  }
259}
260
261void ExampleInstance::Receive() {
262  memset(receive_buffer_, 0, kBufferSize);
263  if (IsUDP()) {
264    pp::CompletionCallbackWithOutput<pp::NetAddress> callback =
265        callback_factory_.NewCallbackWithOutput(
266            &ExampleInstance::OnReceiveFromCompletion);
267    udp_socket_.RecvFrom(receive_buffer_, kBufferSize, callback);
268  } else {
269    pp::CompletionCallback callback =
270        callback_factory_.NewCallback(&ExampleInstance::OnReceiveCompletion);
271    tcp_socket_.Read(receive_buffer_, kBufferSize, callback);
272  }
273}
274
275void ExampleInstance::OnConnectCompletion(int32_t result) {
276  if (result != PP_OK) {
277    std::ostringstream status;
278    status << "Connection failed: " << result;
279    PostMessage(status.str());
280    return;
281  }
282
283  if (IsUDP()) {
284    pp::NetAddress addr = udp_socket_.GetBoundAddress();
285    PostMessage(std::string("Bound to: ") +
286                addr.DescribeAsString(true).AsString());
287  } else {
288    PostMessage("Connected");
289  }
290
291  Receive();
292}
293
294void ExampleInstance::OnReceiveFromCompletion(int32_t result,
295                                              pp::NetAddress source) {
296  OnReceiveCompletion(result);
297}
298
299void ExampleInstance::OnReceiveCompletion(int32_t result) {
300  if (result < 0) {
301    std::ostringstream status;
302    status << "Receive failed with: " << result;
303    PostMessage(status.str());
304    return;
305  }
306
307  PostMessage(std::string("Received: ") + std::string(receive_buffer_, result));
308  Receive();
309}
310
311void ExampleInstance::OnSendCompletion(int32_t result) {
312  std::ostringstream status;
313  if (result < 0) {
314    status << "Send failed with: " << result;
315  } else {
316    status << "Sent bytes: " << result;
317  }
318  send_outstanding_ = false;
319  PostMessage(status.str());
320}
321
322// The ExampleModule provides an implementation of pp::Module that creates
323// ExampleInstance objects when invoked.
324class ExampleModule : public pp::Module {
325 public:
326  ExampleModule() : pp::Module() {}
327  virtual ~ExampleModule() {}
328
329  virtual pp::Instance* CreateInstance(PP_Instance instance) {
330    return new ExampleInstance(instance);
331  }
332};
333
334// Implement the required pp::CreateModule function that creates our specific
335// kind of Module.
336namespace pp {
337Module* CreateModule() { return new ExampleModule(); }
338}  // namespace pp
339