summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/examples/peerconnection/server
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/libwebrtc/examples/peerconnection/server
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/examples/peerconnection/server')
-rw-r--r--third_party/libwebrtc/examples/peerconnection/server/data_socket.cc299
-rw-r--r--third_party/libwebrtc/examples/peerconnection/server/data_socket.h152
-rw-r--r--third_party/libwebrtc/examples/peerconnection/server/main.cc193
-rw-r--r--third_party/libwebrtc/examples/peerconnection/server/peer_channel.cc360
-rw-r--r--third_party/libwebrtc/examples/peerconnection/server/peer_channel.h118
-rw-r--r--third_party/libwebrtc/examples/peerconnection/server/server_test.html237
-rw-r--r--third_party/libwebrtc/examples/peerconnection/server/utils.cc25
-rw-r--r--third_party/libwebrtc/examples/peerconnection/server/utils.h25
8 files changed, 1409 insertions, 0 deletions
diff --git a/third_party/libwebrtc/examples/peerconnection/server/data_socket.cc b/third_party/libwebrtc/examples/peerconnection/server/data_socket.cc
new file mode 100644
index 0000000000..855ebd8c0c
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/server/data_socket.cc
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "examples/peerconnection/server/data_socket.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#if defined(WEBRTC_POSIX)
+#include <unistd.h>
+#endif
+
+#include "examples/peerconnection/server/utils.h"
+#include "rtc_base/checks.h"
+
+static const char kHeaderTerminator[] = "\r\n\r\n";
+static const int kHeaderTerminatorLength = sizeof(kHeaderTerminator) - 1;
+
+// static
+const char DataSocket::kCrossOriginAllowHeaders[] =
+ "Access-Control-Allow-Origin: *\r\n"
+ "Access-Control-Allow-Credentials: true\r\n"
+ "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n"
+ "Access-Control-Allow-Headers: Content-Type, "
+ "Content-Length, Connection, Cache-Control\r\n"
+ "Access-Control-Expose-Headers: Content-Length\r\n";
+
+#if defined(WIN32)
+class WinsockInitializer {
+ static WinsockInitializer singleton;
+
+ WinsockInitializer() {
+ WSADATA data;
+ WSAStartup(MAKEWORD(1, 0), &data);
+ }
+
+ public:
+ ~WinsockInitializer() { WSACleanup(); }
+};
+WinsockInitializer WinsockInitializer::singleton;
+#endif
+
+//
+// SocketBase
+//
+
+bool SocketBase::Create() {
+ RTC_DCHECK(!valid());
+ socket_ = ::socket(AF_INET, SOCK_STREAM, 0);
+ return valid();
+}
+
+void SocketBase::Close() {
+ if (socket_ != INVALID_SOCKET) {
+ closesocket(socket_);
+ socket_ = INVALID_SOCKET;
+ }
+}
+
+//
+// DataSocket
+//
+
+std::string DataSocket::request_arguments() const {
+ size_t args = request_path_.find('?');
+ if (args != std::string::npos)
+ return request_path_.substr(args + 1);
+ return "";
+}
+
+bool DataSocket::PathEquals(const char* path) const {
+ RTC_DCHECK(path);
+ size_t args = request_path_.find('?');
+ if (args != std::string::npos)
+ return request_path_.substr(0, args).compare(path) == 0;
+ return request_path_.compare(path) == 0;
+}
+
+bool DataSocket::OnDataAvailable(bool* close_socket) {
+ RTC_DCHECK(valid());
+ char buffer[0xfff] = {0};
+ int bytes = recv(socket_, buffer, sizeof(buffer), 0);
+ if (bytes == SOCKET_ERROR || bytes == 0) {
+ *close_socket = true;
+ return false;
+ }
+
+ *close_socket = false;
+
+ bool ret = true;
+ if (headers_received()) {
+ if (method_ != POST) {
+ // unexpectedly received data.
+ ret = false;
+ } else {
+ data_.append(buffer, bytes);
+ }
+ } else {
+ request_headers_.append(buffer, bytes);
+ size_t found = request_headers_.find(kHeaderTerminator);
+ if (found != std::string::npos) {
+ data_ = request_headers_.substr(found + kHeaderTerminatorLength);
+ request_headers_.resize(found + kHeaderTerminatorLength);
+ ret = ParseHeaders();
+ }
+ }
+ return ret;
+}
+
+bool DataSocket::Send(const std::string& data) const {
+ return send(socket_, data.data(), static_cast<int>(data.length()), 0) !=
+ SOCKET_ERROR;
+}
+
+bool DataSocket::Send(const std::string& status,
+ bool connection_close,
+ const std::string& content_type,
+ const std::string& extra_headers,
+ const std::string& data) const {
+ RTC_DCHECK(valid());
+ RTC_DCHECK(!status.empty());
+ std::string buffer("HTTP/1.1 " + status + "\r\n");
+
+ buffer +=
+ "Server: PeerConnectionTestServer/0.1\r\n"
+ "Cache-Control: no-cache\r\n";
+
+ if (connection_close)
+ buffer += "Connection: close\r\n";
+
+ if (!content_type.empty())
+ buffer += "Content-Type: " + content_type + "\r\n";
+
+ buffer +=
+ "Content-Length: " + int2str(static_cast<int>(data.size())) + "\r\n";
+
+ if (!extra_headers.empty()) {
+ buffer += extra_headers;
+ // Extra headers are assumed to have a separator per header.
+ }
+
+ buffer += kCrossOriginAllowHeaders;
+
+ buffer += "\r\n";
+ buffer += data;
+
+ return Send(buffer);
+}
+
+void DataSocket::Clear() {
+ method_ = INVALID;
+ content_length_ = 0;
+ content_type_.clear();
+ request_path_.clear();
+ request_headers_.clear();
+ data_.clear();
+}
+
+bool DataSocket::ParseHeaders() {
+ RTC_DCHECK(!request_headers_.empty());
+ RTC_DCHECK_EQ(method_, INVALID);
+ size_t i = request_headers_.find("\r\n");
+ if (i == std::string::npos)
+ return false;
+
+ if (!ParseMethodAndPath(request_headers_.data(), i))
+ return false;
+
+ RTC_DCHECK_NE(method_, INVALID);
+ RTC_DCHECK(!request_path_.empty());
+
+ if (method_ == POST) {
+ const char* headers = request_headers_.data() + i + 2;
+ size_t len = request_headers_.length() - i - 2;
+ if (!ParseContentLengthAndType(headers, len))
+ return false;
+ }
+
+ return true;
+}
+
+bool DataSocket::ParseMethodAndPath(const char* begin, size_t len) {
+ struct {
+ const char* method_name;
+ size_t method_name_len;
+ RequestMethod id;
+ } supported_methods[] = {
+ {"GET", 3, GET},
+ {"POST", 4, POST},
+ {"OPTIONS", 7, OPTIONS},
+ };
+
+ const char* path = NULL;
+ for (size_t i = 0; i < ARRAYSIZE(supported_methods); ++i) {
+ if (len > supported_methods[i].method_name_len &&
+ isspace(begin[supported_methods[i].method_name_len]) &&
+ strncmp(begin, supported_methods[i].method_name,
+ supported_methods[i].method_name_len) == 0) {
+ method_ = supported_methods[i].id;
+ path = begin + supported_methods[i].method_name_len;
+ break;
+ }
+ }
+
+ const char* end = begin + len;
+ if (!path || path >= end)
+ return false;
+
+ ++path;
+ begin = path;
+ while (!isspace(*path) && path < end)
+ ++path;
+
+ request_path_.assign(begin, path - begin);
+
+ return true;
+}
+
+bool DataSocket::ParseContentLengthAndType(const char* headers, size_t length) {
+ RTC_DCHECK_EQ(content_length_, 0);
+ RTC_DCHECK(content_type_.empty());
+
+ const char* end = headers + length;
+ while (headers && headers < end) {
+ if (!isspace(headers[0])) {
+ static const char kContentLength[] = "Content-Length:";
+ static const char kContentType[] = "Content-Type:";
+ if ((headers + ARRAYSIZE(kContentLength)) < end &&
+ strncmp(headers, kContentLength, ARRAYSIZE(kContentLength) - 1) ==
+ 0) {
+ headers += ARRAYSIZE(kContentLength) - 1;
+ while (headers[0] == ' ')
+ ++headers;
+ content_length_ = atoi(headers);
+ } else if ((headers + ARRAYSIZE(kContentType)) < end &&
+ strncmp(headers, kContentType, ARRAYSIZE(kContentType) - 1) ==
+ 0) {
+ headers += ARRAYSIZE(kContentType) - 1;
+ while (headers[0] == ' ')
+ ++headers;
+ const char* type_end = strstr(headers, "\r\n");
+ if (type_end == NULL)
+ type_end = end;
+ content_type_.assign(headers, type_end);
+ }
+ } else {
+ ++headers;
+ }
+ headers = strstr(headers, "\r\n");
+ if (headers)
+ headers += 2;
+ }
+
+ return !content_type_.empty() && content_length_ != 0;
+}
+
+//
+// ListeningSocket
+//
+
+bool ListeningSocket::Listen(unsigned short port) {
+ RTC_DCHECK(valid());
+ int enabled = 1;
+ if (setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR,
+ reinterpret_cast<const char*>(&enabled),
+ sizeof(enabled)) != 0) {
+ printf("setsockopt failed\n");
+ return false;
+ }
+ struct sockaddr_in addr = {0};
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ addr.sin_port = htons(port);
+ if (bind(socket_, reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)) ==
+ SOCKET_ERROR) {
+ printf("bind failed\n");
+ return false;
+ }
+ return listen(socket_, 5) != SOCKET_ERROR;
+}
+
+DataSocket* ListeningSocket::Accept() const {
+ RTC_DCHECK(valid());
+ struct sockaddr_in addr = {0};
+ socklen_t size = sizeof(addr);
+ NativeSocket client =
+ accept(socket_, reinterpret_cast<sockaddr*>(&addr), &size);
+ if (client == INVALID_SOCKET)
+ return NULL;
+
+ return new DataSocket(client);
+}
diff --git a/third_party/libwebrtc/examples/peerconnection/server/data_socket.h b/third_party/libwebrtc/examples/peerconnection/server/data_socket.h
new file mode 100644
index 0000000000..57ad5b9aee
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/server/data_socket.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef EXAMPLES_PEERCONNECTION_SERVER_DATA_SOCKET_H_
+#define EXAMPLES_PEERCONNECTION_SERVER_DATA_SOCKET_H_
+
+#ifdef WIN32
+#include <winsock2.h>
+typedef int socklen_t;
+typedef SOCKET NativeSocket;
+#else
+#include <netinet/in.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#define closesocket close
+typedef int NativeSocket;
+
+#ifndef SOCKET_ERROR
+#define SOCKET_ERROR (-1)
+#endif
+
+#ifndef INVALID_SOCKET
+#define INVALID_SOCKET static_cast<NativeSocket>(-1)
+#endif
+#endif
+
+#include <string>
+
+class SocketBase {
+ public:
+ SocketBase() : socket_(INVALID_SOCKET) {}
+ explicit SocketBase(NativeSocket socket) : socket_(socket) {}
+ SocketBase(SocketBase& other) = delete;
+ SocketBase& operator=(const SocketBase& other) = delete;
+ ~SocketBase() { Close(); }
+
+ NativeSocket socket() const { return socket_; }
+ bool valid() const { return socket_ != INVALID_SOCKET; }
+
+ bool Create();
+ void Close();
+
+ protected:
+ NativeSocket socket_;
+};
+
+// Represents an HTTP server socket.
+class DataSocket : public SocketBase {
+ public:
+ enum RequestMethod {
+ INVALID,
+ GET,
+ POST,
+ OPTIONS,
+ };
+
+ explicit DataSocket(NativeSocket socket)
+ : SocketBase(socket), method_(INVALID), content_length_(0) {}
+
+ ~DataSocket() {}
+
+ static const char kCrossOriginAllowHeaders[];
+
+ bool headers_received() const { return method_ != INVALID; }
+
+ RequestMethod method() const { return method_; }
+
+ const std::string& request_path() const { return request_path_; }
+ std::string request_arguments() const;
+
+ const std::string& data() const { return data_; }
+
+ const std::string& content_type() const { return content_type_; }
+
+ size_t content_length() const { return content_length_; }
+
+ bool request_received() const {
+ return headers_received() && (method_ != POST || data_received());
+ }
+
+ bool data_received() const {
+ return method_ != POST || data_.length() >= content_length_;
+ }
+
+ // Checks if the request path (minus arguments) matches a given path.
+ bool PathEquals(const char* path) const;
+
+ // Called when we have received some data from clients.
+ // Returns false if an error occurred.
+ bool OnDataAvailable(bool* close_socket);
+
+ // Send a raw buffer of bytes.
+ bool Send(const std::string& data) const;
+
+ // Send an HTTP response. The `status` should start with a valid HTTP
+ // response code, followed by a string. E.g. "200 OK".
+ // If `connection_close` is set to true, an extra "Connection: close" HTTP
+ // header will be included. `content_type` is the mime content type, not
+ // including the "Content-Type: " string.
+ // `extra_headers` should be either empty or a list of headers where each
+ // header terminates with "\r\n".
+ // `data` is the body of the message. It's length will be specified via
+ // a "Content-Length" header.
+ bool Send(const std::string& status,
+ bool connection_close,
+ const std::string& content_type,
+ const std::string& extra_headers,
+ const std::string& data) const;
+
+ // Clears all held state and prepares the socket for receiving a new request.
+ void Clear();
+
+ protected:
+ // A fairly relaxed HTTP header parser. Parses the method, path and
+ // content length (POST only) of a request.
+ // Returns true if a valid request was received and no errors occurred.
+ bool ParseHeaders();
+
+ // Figures out whether the request is a GET or POST and what path is
+ // being requested.
+ bool ParseMethodAndPath(const char* begin, size_t len);
+
+ // Determines the length of the body and it's mime type.
+ bool ParseContentLengthAndType(const char* headers, size_t length);
+
+ protected:
+ RequestMethod method_;
+ size_t content_length_;
+ std::string content_type_;
+ std::string request_path_;
+ std::string request_headers_;
+ std::string data_;
+};
+
+// The server socket. Accepts connections and generates DataSocket instances
+// for each new connection.
+class ListeningSocket : public SocketBase {
+ public:
+ ListeningSocket() {}
+
+ bool Listen(unsigned short port);
+ DataSocket* Accept() const;
+};
+
+#endif // EXAMPLES_PEERCONNECTION_SERVER_DATA_SOCKET_H_
diff --git a/third_party/libwebrtc/examples/peerconnection/server/main.cc b/third_party/libwebrtc/examples/peerconnection/server/main.cc
new file mode 100644
index 0000000000..50b8c23401
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/server/main.cc
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#if defined(WEBRTC_POSIX)
+#include <sys/select.h>
+#endif
+#include <time.h>
+
+#include <string>
+#include <vector>
+
+#include "absl/flags/flag.h"
+#include "absl/flags/parse.h"
+#include "absl/flags/usage.h"
+#include "examples/peerconnection/server/data_socket.h"
+#include "examples/peerconnection/server/peer_channel.h"
+#include "rtc_base/checks.h"
+#include "system_wrappers/include/field_trial.h"
+#include "test/field_trial.h"
+
+ABSL_FLAG(
+ std::string,
+ force_fieldtrials,
+ "",
+ "Field trials control experimental features. This flag specifies the field "
+ "trials in effect. E.g. running with "
+ "--force_fieldtrials=WebRTC-FooFeature/Enabled/ "
+ "will assign the group Enabled to field trial WebRTC-FooFeature. Multiple "
+ "trials are separated by \"/\"");
+ABSL_FLAG(int, port, 8888, "default: 8888");
+
+static const size_t kMaxConnections = (FD_SETSIZE - 2);
+
+void HandleBrowserRequest(DataSocket* ds, bool* quit) {
+ RTC_DCHECK(ds && ds->valid());
+ RTC_DCHECK(quit);
+
+ const std::string& path = ds->request_path();
+
+ *quit = (path.compare("/quit") == 0);
+
+ if (*quit) {
+ ds->Send("200 OK", true, "text/html", "",
+ "<html><body>Quitting...</body></html>");
+ } else if (ds->method() == DataSocket::OPTIONS) {
+ // We'll get this when a browsers do cross-resource-sharing requests.
+ // The headers to allow cross-origin script support will be set inside
+ // Send.
+ ds->Send("200 OK", true, "", "", "");
+ } else {
+ // Here we could write some useful output back to the browser depending on
+ // the path.
+ printf("Received an invalid request: %s\n", ds->request_path().c_str());
+ ds->Send("500 Sorry", true, "text/html", "",
+ "<html><body>Sorry, not yet implemented</body></html>");
+ }
+}
+
+int main(int argc, char* argv[]) {
+ absl::SetProgramUsageMessage(
+ "Example usage: ./peerconnection_server --port=8888\n");
+ absl::ParseCommandLine(argc, argv);
+
+ // InitFieldTrialsFromString stores the char*, so the char array must outlive
+ // the application.
+ const std::string force_field_trials = absl::GetFlag(FLAGS_force_fieldtrials);
+ webrtc::field_trial::InitFieldTrialsFromString(force_field_trials.c_str());
+
+ int port = absl::GetFlag(FLAGS_port);
+
+ // Abort if the user specifies a port that is outside the allowed
+ // range [1, 65535].
+ if ((port < 1) || (port > 65535)) {
+ printf("Error: %i is not a valid port.\n", port);
+ return -1;
+ }
+
+ ListeningSocket listener;
+ if (!listener.Create()) {
+ printf("Failed to create server socket\n");
+ return -1;
+ } else if (!listener.Listen(port)) {
+ printf("Failed to listen on server socket\n");
+ return -1;
+ }
+
+ printf("Server listening on port %i\n", port);
+
+ PeerChannel clients;
+ typedef std::vector<DataSocket*> SocketArray;
+ SocketArray sockets;
+ bool quit = false;
+ while (!quit) {
+ fd_set socket_set;
+ FD_ZERO(&socket_set);
+ if (listener.valid())
+ FD_SET(listener.socket(), &socket_set);
+
+ for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i)
+ FD_SET((*i)->socket(), &socket_set);
+
+ struct timeval timeout = {10, 0};
+ if (select(FD_SETSIZE, &socket_set, NULL, NULL, &timeout) == SOCKET_ERROR) {
+ printf("select failed\n");
+ break;
+ }
+
+ for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i) {
+ DataSocket* s = *i;
+ bool socket_done = true;
+ if (FD_ISSET(s->socket(), &socket_set)) {
+ if (s->OnDataAvailable(&socket_done) && s->request_received()) {
+ ChannelMember* member = clients.Lookup(s);
+ if (member || PeerChannel::IsPeerConnection(s)) {
+ if (!member) {
+ if (s->PathEquals("/sign_in")) {
+ clients.AddMember(s);
+ } else {
+ printf("No member found for: %s\n", s->request_path().c_str());
+ s->Send("500 Error", true, "text/plain", "",
+ "Peer most likely gone.");
+ }
+ } else if (member->is_wait_request(s)) {
+ // no need to do anything.
+ socket_done = false;
+ } else {
+ ChannelMember* target = clients.IsTargetedRequest(s);
+ if (target) {
+ member->ForwardRequestToPeer(s, target);
+ } else if (s->PathEquals("/sign_out")) {
+ s->Send("200 OK", true, "text/plain", "", "");
+ } else {
+ printf("Couldn't find target for request: %s\n",
+ s->request_path().c_str());
+ s->Send("500 Error", true, "text/plain", "",
+ "Peer most likely gone.");
+ }
+ }
+ } else {
+ HandleBrowserRequest(s, &quit);
+ if (quit) {
+ printf("Quitting...\n");
+ FD_CLR(listener.socket(), &socket_set);
+ listener.Close();
+ clients.CloseAll();
+ }
+ }
+ }
+ } else {
+ socket_done = false;
+ }
+
+ if (socket_done) {
+ printf("Disconnecting socket\n");
+ clients.OnClosing(s);
+ RTC_DCHECK(s->valid()); // Close must not have been called yet.
+ FD_CLR(s->socket(), &socket_set);
+ delete (*i);
+ i = sockets.erase(i);
+ if (i == sockets.end())
+ break;
+ }
+ }
+
+ clients.CheckForTimeout();
+
+ if (FD_ISSET(listener.socket(), &socket_set)) {
+ DataSocket* s = listener.Accept();
+ if (sockets.size() >= kMaxConnections) {
+ delete s; // sorry, that's all we can take.
+ printf("Connection limit reached\n");
+ } else {
+ sockets.push_back(s);
+ printf("New connection...\n");
+ }
+ }
+ }
+
+ for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i)
+ delete (*i);
+ sockets.clear();
+
+ return 0;
+}
diff --git a/third_party/libwebrtc/examples/peerconnection/server/peer_channel.cc b/third_party/libwebrtc/examples/peerconnection/server/peer_channel.cc
new file mode 100644
index 0000000000..f53820cc60
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/server/peer_channel.cc
@@ -0,0 +1,360 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "examples/peerconnection/server/peer_channel.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <algorithm>
+
+#include "examples/peerconnection/server/data_socket.h"
+#include "examples/peerconnection/server/utils.h"
+#include "rtc_base/checks.h"
+
+// Set to the peer id of the originator when messages are being
+// exchanged between peers, but set to the id of the receiving peer
+// itself when notifications are sent from the server about the state
+// of other peers.
+//
+// WORKAROUND: Since support for CORS varies greatly from one browser to the
+// next, we don't use a custom name for our peer-id header (originally it was
+// "X-Peer-Id: "). Instead, we use a "simple header", "Pragma" which should
+// always be exposed to CORS requests. There is a special CORS header devoted
+// to exposing proprietary headers (Access-Control-Expose-Headers), however
+// at this point it is not working correctly in some popular browsers.
+static const char kPeerIdHeader[] = "Pragma: ";
+
+static const char* kRequestPaths[] = {
+ "/wait",
+ "/sign_out",
+ "/message",
+};
+
+enum RequestPathIndex {
+ kWait,
+ kSignOut,
+ kMessage,
+};
+
+const size_t kMaxNameLength = 512;
+
+//
+// ChannelMember
+//
+
+int ChannelMember::s_member_id_ = 0;
+
+ChannelMember::ChannelMember(DataSocket* socket)
+ : waiting_socket_(NULL),
+ id_(++s_member_id_),
+ connected_(true),
+ timestamp_(time(NULL)) {
+ RTC_DCHECK(socket);
+ RTC_DCHECK_EQ(socket->method(), DataSocket::GET);
+ RTC_DCHECK(socket->PathEquals("/sign_in"));
+ name_ = socket->request_arguments();
+ if (name_.empty())
+ name_ = "peer_" + int2str(id_);
+ else if (name_.length() > kMaxNameLength)
+ name_.resize(kMaxNameLength);
+
+ std::replace(name_.begin(), name_.end(), ',', '_');
+}
+
+ChannelMember::~ChannelMember() {}
+
+bool ChannelMember::is_wait_request(DataSocket* ds) const {
+ return ds && ds->PathEquals(kRequestPaths[kWait]);
+}
+
+bool ChannelMember::TimedOut() {
+ return waiting_socket_ == NULL && (time(NULL) - timestamp_) > 30;
+}
+
+std::string ChannelMember::GetPeerIdHeader() const {
+ std::string ret(kPeerIdHeader + int2str(id_) + "\r\n");
+ return ret;
+}
+
+bool ChannelMember::NotifyOfOtherMember(const ChannelMember& other) {
+ RTC_DCHECK_NE(&other, this);
+ QueueResponse("200 OK", "text/plain", GetPeerIdHeader(), other.GetEntry());
+ return true;
+}
+
+// Returns a string in the form "name,id,connected\n".
+std::string ChannelMember::GetEntry() const {
+ RTC_DCHECK(name_.length() <= kMaxNameLength);
+
+ // name, 11-digit int, 1-digit bool, newline, null
+ char entry[kMaxNameLength + 15];
+ snprintf(entry, sizeof(entry), "%s,%d,%d\n",
+ name_.substr(0, kMaxNameLength).c_str(), id_, connected_);
+ return entry;
+}
+
+void ChannelMember::ForwardRequestToPeer(DataSocket* ds, ChannelMember* peer) {
+ RTC_DCHECK(peer);
+ RTC_DCHECK(ds);
+
+ std::string extra_headers(GetPeerIdHeader());
+
+ if (peer == this) {
+ ds->Send("200 OK", true, ds->content_type(), extra_headers, ds->data());
+ } else {
+ printf("Client %s sending to %s\n", name_.c_str(), peer->name().c_str());
+ peer->QueueResponse("200 OK", ds->content_type(), extra_headers,
+ ds->data());
+ ds->Send("200 OK", true, "text/plain", "", "");
+ }
+}
+
+void ChannelMember::OnClosing(DataSocket* ds) {
+ if (ds == waiting_socket_) {
+ waiting_socket_ = NULL;
+ timestamp_ = time(NULL);
+ }
+}
+
+void ChannelMember::QueueResponse(const std::string& status,
+ const std::string& content_type,
+ const std::string& extra_headers,
+ const std::string& data) {
+ if (waiting_socket_) {
+ RTC_DCHECK(queue_.empty());
+ RTC_DCHECK_EQ(waiting_socket_->method(), DataSocket::GET);
+ bool ok =
+ waiting_socket_->Send(status, true, content_type, extra_headers, data);
+ if (!ok) {
+ printf("Failed to deliver data to waiting socket\n");
+ }
+ waiting_socket_ = NULL;
+ timestamp_ = time(NULL);
+ } else {
+ QueuedResponse qr;
+ qr.status = status;
+ qr.content_type = content_type;
+ qr.extra_headers = extra_headers;
+ qr.data = data;
+ queue_.push(qr);
+ }
+}
+
+void ChannelMember::SetWaitingSocket(DataSocket* ds) {
+ RTC_DCHECK_EQ(ds->method(), DataSocket::GET);
+ if (ds && !queue_.empty()) {
+ RTC_DCHECK(!waiting_socket_);
+ const QueuedResponse& response = queue_.front();
+ ds->Send(response.status, true, response.content_type,
+ response.extra_headers, response.data);
+ queue_.pop();
+ } else {
+ waiting_socket_ = ds;
+ }
+}
+
+//
+// PeerChannel
+//
+
+// static
+bool PeerChannel::IsPeerConnection(const DataSocket* ds) {
+ RTC_DCHECK(ds);
+ return (ds->method() == DataSocket::POST && ds->content_length() > 0) ||
+ (ds->method() == DataSocket::GET && ds->PathEquals("/sign_in"));
+}
+
+ChannelMember* PeerChannel::Lookup(DataSocket* ds) const {
+ RTC_DCHECK(ds);
+
+ if (ds->method() != DataSocket::GET && ds->method() != DataSocket::POST)
+ return NULL;
+
+ size_t i = 0;
+ for (; i < ARRAYSIZE(kRequestPaths); ++i) {
+ if (ds->PathEquals(kRequestPaths[i]))
+ break;
+ }
+
+ if (i == ARRAYSIZE(kRequestPaths))
+ return NULL;
+
+ std::string args(ds->request_arguments());
+ static const char kPeerId[] = "peer_id=";
+ size_t found = args.find(kPeerId);
+ if (found == std::string::npos)
+ return NULL;
+
+ int id = atoi(&args[found + ARRAYSIZE(kPeerId) - 1]);
+ Members::const_iterator iter = members_.begin();
+ for (; iter != members_.end(); ++iter) {
+ if (id == (*iter)->id()) {
+ if (i == kWait)
+ (*iter)->SetWaitingSocket(ds);
+ if (i == kSignOut)
+ (*iter)->set_disconnected();
+ return *iter;
+ }
+ }
+
+ return NULL;
+}
+
+ChannelMember* PeerChannel::IsTargetedRequest(const DataSocket* ds) const {
+ RTC_DCHECK(ds);
+ // Regardless of GET or POST, we look for the peer_id parameter
+ // only in the request_path.
+ const std::string& path = ds->request_path();
+ size_t args = path.find('?');
+ if (args == std::string::npos)
+ return NULL;
+ size_t found;
+ const char kTargetPeerIdParam[] = "to=";
+ do {
+ found = path.find(kTargetPeerIdParam, args);
+ if (found == std::string::npos)
+ return NULL;
+ if (found == (args + 1) || path[found - 1] == '&') {
+ found += ARRAYSIZE(kTargetPeerIdParam) - 1;
+ break;
+ }
+ args = found + ARRAYSIZE(kTargetPeerIdParam) - 1;
+ } while (true);
+ int id = atoi(&path[found]);
+ Members::const_iterator i = members_.begin();
+ for (; i != members_.end(); ++i) {
+ if ((*i)->id() == id) {
+ return *i;
+ }
+ }
+ return NULL;
+}
+
+bool PeerChannel::AddMember(DataSocket* ds) {
+ RTC_DCHECK(IsPeerConnection(ds));
+ ChannelMember* new_guy = new ChannelMember(ds);
+ Members failures;
+ BroadcastChangedState(*new_guy, &failures);
+ HandleDeliveryFailures(&failures);
+ members_.push_back(new_guy);
+
+ printf("New member added (total=%s): %s\n",
+ size_t2str(members_.size()).c_str(), new_guy->name().c_str());
+
+ // Let the newly connected peer know about other members of the channel.
+ std::string content_type;
+ std::string response = BuildResponseForNewMember(*new_guy, &content_type);
+ ds->Send("200 Added", true, content_type, new_guy->GetPeerIdHeader(),
+ response);
+ return true;
+}
+
+void PeerChannel::CloseAll() {
+ Members::const_iterator i = members_.begin();
+ for (; i != members_.end(); ++i) {
+ (*i)->QueueResponse("200 OK", "text/plain", "", "Server shutting down");
+ }
+ DeleteAll();
+}
+
+void PeerChannel::OnClosing(DataSocket* ds) {
+ for (Members::iterator i = members_.begin(); i != members_.end(); ++i) {
+ ChannelMember* m = (*i);
+ m->OnClosing(ds);
+ if (!m->connected()) {
+ i = members_.erase(i);
+ Members failures;
+ BroadcastChangedState(*m, &failures);
+ HandleDeliveryFailures(&failures);
+ delete m;
+ if (i == members_.end())
+ break;
+ }
+ }
+ printf("Total connected: %s\n", size_t2str(members_.size()).c_str());
+}
+
+void PeerChannel::CheckForTimeout() {
+ for (Members::iterator i = members_.begin(); i != members_.end(); ++i) {
+ ChannelMember* m = (*i);
+ if (m->TimedOut()) {
+ printf("Timeout: %s\n", m->name().c_str());
+ m->set_disconnected();
+ i = members_.erase(i);
+ Members failures;
+ BroadcastChangedState(*m, &failures);
+ HandleDeliveryFailures(&failures);
+ delete m;
+ if (i == members_.end())
+ break;
+ }
+ }
+}
+
+void PeerChannel::DeleteAll() {
+ for (Members::iterator i = members_.begin(); i != members_.end(); ++i)
+ delete (*i);
+ members_.clear();
+}
+
+void PeerChannel::BroadcastChangedState(const ChannelMember& member,
+ Members* delivery_failures) {
+ // This function should be called prior to DataSocket::Close().
+ RTC_DCHECK(delivery_failures);
+
+ if (!member.connected()) {
+ printf("Member disconnected: %s\n", member.name().c_str());
+ }
+
+ Members::iterator i = members_.begin();
+ for (; i != members_.end(); ++i) {
+ if (&member != (*i)) {
+ if (!(*i)->NotifyOfOtherMember(member)) {
+ (*i)->set_disconnected();
+ delivery_failures->push_back(*i);
+ i = members_.erase(i);
+ if (i == members_.end())
+ break;
+ }
+ }
+ }
+}
+
+void PeerChannel::HandleDeliveryFailures(Members* failures) {
+ RTC_DCHECK(failures);
+
+ while (!failures->empty()) {
+ Members::iterator i = failures->begin();
+ ChannelMember* member = *i;
+ RTC_DCHECK(!member->connected());
+ failures->erase(i);
+ BroadcastChangedState(*member, failures);
+ delete member;
+ }
+}
+
+// Builds a simple list of "name,id\n" entries for each member.
+std::string PeerChannel::BuildResponseForNewMember(const ChannelMember& member,
+ std::string* content_type) {
+ RTC_DCHECK(content_type);
+
+ *content_type = "text/plain";
+ // The peer itself will always be the first entry.
+ std::string response(member.GetEntry());
+ for (Members::iterator i = members_.begin(); i != members_.end(); ++i) {
+ if (member.id() != (*i)->id()) {
+ RTC_DCHECK((*i)->connected());
+ response += (*i)->GetEntry();
+ }
+ }
+
+ return response;
+}
diff --git a/third_party/libwebrtc/examples/peerconnection/server/peer_channel.h b/third_party/libwebrtc/examples/peerconnection/server/peer_channel.h
new file mode 100644
index 0000000000..c3624908ac
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/server/peer_channel.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef EXAMPLES_PEERCONNECTION_SERVER_PEER_CHANNEL_H_
+#define EXAMPLES_PEERCONNECTION_SERVER_PEER_CHANNEL_H_
+
+#include <time.h>
+
+#include <queue>
+#include <string>
+#include <vector>
+
+class DataSocket;
+
+// Represents a single peer connected to the server.
+class ChannelMember {
+ public:
+ explicit ChannelMember(DataSocket* socket);
+ ~ChannelMember();
+
+ bool connected() const { return connected_; }
+ int id() const { return id_; }
+ void set_disconnected() { connected_ = false; }
+ bool is_wait_request(DataSocket* ds) const;
+ const std::string& name() const { return name_; }
+
+ bool TimedOut();
+
+ std::string GetPeerIdHeader() const;
+
+ bool NotifyOfOtherMember(const ChannelMember& other);
+
+ // Returns a string in the form "name,id\n".
+ std::string GetEntry() const;
+
+ void ForwardRequestToPeer(DataSocket* ds, ChannelMember* peer);
+
+ void OnClosing(DataSocket* ds);
+
+ void QueueResponse(const std::string& status,
+ const std::string& content_type,
+ const std::string& extra_headers,
+ const std::string& data);
+
+ void SetWaitingSocket(DataSocket* ds);
+
+ protected:
+ struct QueuedResponse {
+ std::string status, content_type, extra_headers, data;
+ };
+
+ DataSocket* waiting_socket_;
+ int id_;
+ bool connected_;
+ time_t timestamp_;
+ std::string name_;
+ std::queue<QueuedResponse> queue_;
+ static int s_member_id_;
+};
+
+// Manages all currently connected peers.
+class PeerChannel {
+ public:
+ typedef std::vector<ChannelMember*> Members;
+
+ PeerChannel() {}
+
+ ~PeerChannel() { DeleteAll(); }
+
+ const Members& members() const { return members_; }
+
+ // Returns true if the request should be treated as a new ChannelMember
+ // request. Otherwise the request is not peerconnection related.
+ static bool IsPeerConnection(const DataSocket* ds);
+
+ // Finds a connected peer that's associated with the `ds` socket.
+ ChannelMember* Lookup(DataSocket* ds) const;
+
+ // Checks if the request has a "peer_id" parameter and if so, looks up the
+ // peer for which the request is targeted at.
+ ChannelMember* IsTargetedRequest(const DataSocket* ds) const;
+
+ // Adds a new ChannelMember instance to the list of connected peers and
+ // associates it with the socket.
+ bool AddMember(DataSocket* ds);
+
+ // Closes all connections and sends a "shutting down" message to all
+ // connected peers.
+ void CloseAll();
+
+ // Called when a socket was determined to be closing by the peer (or if the
+ // connection went dead).
+ void OnClosing(DataSocket* ds);
+
+ void CheckForTimeout();
+
+ protected:
+ void DeleteAll();
+ void BroadcastChangedState(const ChannelMember& member,
+ Members* delivery_failures);
+ void HandleDeliveryFailures(Members* failures);
+
+ // Builds a simple list of "name,id\n" entries for each member.
+ std::string BuildResponseForNewMember(const ChannelMember& member,
+ std::string* content_type);
+
+ protected:
+ Members members_;
+};
+
+#endif // EXAMPLES_PEERCONNECTION_SERVER_PEER_CHANNEL_H_
diff --git a/third_party/libwebrtc/examples/peerconnection/server/server_test.html b/third_party/libwebrtc/examples/peerconnection/server/server_test.html
new file mode 100644
index 0000000000..0a165f19d5
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/server/server_test.html
@@ -0,0 +1,237 @@
+<html>
+<head>
+<title>PeerConnection server test page</title>
+
+<script>
+var request = null;
+var hangingGet = null;
+var localName;
+var server;
+var my_id = -1;
+var other_peers = {};
+var message_counter = 0;
+
+function trace(txt) {
+ var elem = document.getElementById("debug");
+ elem.innerHTML += txt + "<br>";
+}
+
+function handleServerNotification(data) {
+ trace("Server notification: " + data);
+ var parsed = data.split(',');
+ if (parseInt(parsed[2]) != 0)
+ other_peers[parseInt(parsed[1])] = parsed[0];
+}
+
+function handlePeerMessage(peer_id, data) {
+ ++message_counter;
+ var str = "Message from '" + other_peers[peer_id] + "'&nbsp;";
+ str += "<span id='toggle_" + message_counter + "' onclick='toggleMe(this);' ";
+ str += "style='cursor: pointer'>+</span><br>";
+ str += "<blockquote id='msg_" + message_counter + "' style='display:none'>";
+ str += data + "</blockquote>";
+ trace(str);
+ if (document.getElementById("loopback").checked) {
+ if (data.search("offer") != -1) {
+ // In loopback mode, if DTLS is enabled, notify the client to disable it.
+ // Otherwise replace the offer with an answer.
+ if (data.search("fingerprint") != -1)
+ data = data.replace("offer", "offer-loopback");
+ else
+ data = data.replace("offer", "answer");
+ }
+ sendToPeer(peer_id, data);
+ }
+}
+
+function GetIntHeader(r, name) {
+ var val = r.getResponseHeader(name);
+ return val != null && val.length ? parseInt(val) : -1;
+}
+
+function hangingGetCallback() {
+ try {
+ if (hangingGet.readyState != 4)
+ return;
+ if (hangingGet.status != 200) {
+ trace("server error: " + hangingGet.statusText);
+ disconnect();
+ } else {
+ var peer_id = GetIntHeader(hangingGet, "Pragma");
+ if (peer_id == my_id) {
+ handleServerNotification(hangingGet.responseText);
+ } else {
+ handlePeerMessage(peer_id, hangingGet.responseText);
+ }
+ }
+
+ if (hangingGet) {
+ hangingGet.abort();
+ hangingGet = null;
+ }
+
+ if (my_id != -1)
+ window.setTimeout(startHangingGet, 0);
+ } catch (e) {
+ trace("Hanging get error: " + e.description);
+ }
+}
+
+function startHangingGet() {
+ try {
+ hangingGet = new XMLHttpRequest();
+ hangingGet.onreadystatechange = hangingGetCallback;
+ hangingGet.ontimeout = onHangingGetTimeout;
+ hangingGet.open("GET", server + "/wait?peer_id=" + my_id, true);
+ hangingGet.send();
+ } catch (e) {
+ trace("error" + e.description);
+ }
+}
+
+function onHangingGetTimeout() {
+ trace("hanging get timeout. issuing again.");
+ hangingGet.abort();
+ hangingGet = null;
+ if (my_id != -1)
+ window.setTimeout(startHangingGet, 0);
+}
+
+function signInCallback() {
+ try {
+ if (request.readyState == 4) {
+ if (request.status == 200) {
+ var peers = request.responseText.split("\n");
+ my_id = parseInt(peers[0].split(',')[1]);
+ trace("My id: " + my_id);
+ for (var i = 1; i < peers.length; ++i) {
+ if (peers[i].length > 0) {
+ trace("Peer " + i + ": " + peers[i]);
+ var parsed = peers[i].split(',');
+ other_peers[parseInt(parsed[1])] = parsed[0];
+ }
+ }
+ startHangingGet();
+ request = null;
+ }
+ }
+ } catch (e) {
+ trace("error: " + e.description);
+ }
+}
+
+function signIn() {
+ try {
+ request = new XMLHttpRequest();
+ request.onreadystatechange = signInCallback;
+ request.open("GET", server + "/sign_in?" + localName, true);
+ request.send();
+ } catch (e) {
+ trace("error: " + e.description);
+ }
+}
+
+function sendToPeer(peer_id, data) {
+ if (my_id == -1) {
+ alert("Not connected");
+ return;
+ }
+ if (peer_id == my_id) {
+ alert("Can't send a message to oneself :)");
+ return;
+ }
+ var r = new XMLHttpRequest();
+ r.open("POST", server + "/message?peer_id=" + my_id + "&to=" + peer_id,
+ false);
+ r.setRequestHeader("Content-Type", "text/plain");
+ r.send(data);
+ r = null;
+}
+
+function connect() {
+ localName = document.getElementById("local").value.toLowerCase();
+ server = document.getElementById("server").value.toLowerCase();
+ if (localName.length == 0) {
+ alert("I need a name please.");
+ document.getElementById("local").focus();
+ } else {
+ document.getElementById("connect").disabled = true;
+ document.getElementById("disconnect").disabled = false;
+ document.getElementById("send").disabled = false;
+ signIn();
+ }
+}
+
+function disconnect() {
+ if (request) {
+ request.abort();
+ request = null;
+ }
+
+ if (hangingGet) {
+ hangingGet.abort();
+ hangingGet = null;
+ }
+
+ if (my_id != -1) {
+ request = new XMLHttpRequest();
+ request.open("GET", server + "/sign_out?peer_id=" + my_id, false);
+ request.send();
+ request = null;
+ my_id = -1;
+ }
+
+ document.getElementById("connect").disabled = false;
+ document.getElementById("disconnect").disabled = true;
+ document.getElementById("send").disabled = true;
+}
+
+window.onbeforeunload = disconnect;
+
+function send() {
+ var text = document.getElementById("message").value;
+ var peer_id = parseInt(document.getElementById("peer_id").value);
+ if (!text.length || peer_id == 0) {
+ alert("No text supplied or invalid peer id");
+ } else {
+ sendToPeer(peer_id, text);
+ }
+}
+
+function toggleMe(obj) {
+ var id = obj.id.replace("toggle", "msg");
+ var t = document.getElementById(id);
+ if (obj.innerText == "+") {
+ obj.innerText = "-";
+ t.style.display = "block";
+ } else {
+ obj.innerText = "+";
+ t.style.display = "none";
+ }
+}
+
+</script>
+
+</head>
+<body>
+Server: <input type="text" id="server" value="http://localhost:8888" /><br>
+<input type="checkbox" id="loopback" checked="checked"/> Loopback (just send
+received messages right back)<br>
+Your name: <input type="text" id="local" value="my_name"/>
+<button id="connect" onclick="connect();">Connect</button>
+<button disabled="true" id="disconnect"
+ onclick="disconnect();">Disconnect</button>
+<br>
+<table><tr><td>
+Target peer id: <input type="text" id="peer_id" size="3"/></td><td>
+Message: <input type="text" id="message"/></td><td>
+<button disabled="true" id="send" onclick="send();">Send</button>
+</td></tr></table>
+<button onclick="document.getElementById('debug').innerHTML='';">
+Clear log</button>
+
+<pre id="debug">
+</pre>
+<br><hr>
+</body>
+</html>
diff --git a/third_party/libwebrtc/examples/peerconnection/server/utils.cc b/third_party/libwebrtc/examples/peerconnection/server/utils.cc
new file mode 100644
index 0000000000..5e61e601d9
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/server/utils.cc
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "examples/peerconnection/server/utils.h"
+
+#include <stdio.h>
+
+#include "rtc_base/string_encode.h"
+
+using rtc::ToString;
+
+std::string int2str(int i) {
+ return ToString(i);
+}
+
+std::string size_t2str(size_t i) {
+ return ToString(i);
+}
diff --git a/third_party/libwebrtc/examples/peerconnection/server/utils.h b/third_party/libwebrtc/examples/peerconnection/server/utils.h
new file mode 100644
index 0000000000..85c04a40e9
--- /dev/null
+++ b/third_party/libwebrtc/examples/peerconnection/server/utils.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef EXAMPLES_PEERCONNECTION_SERVER_UTILS_H_
+#define EXAMPLES_PEERCONNECTION_SERVER_UTILS_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0]))
+#endif
+
+std::string int2str(int i);
+std::string size_t2str(size_t i);
+
+#endif // EXAMPLES_PEERCONNECTION_SERVER_UTILS_H_