// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "echo_server.h"
#include <stdio.h>
#include <string.h>
#include <sstream>
#include "ppapi/c/pp_errors.h"
#include "ppapi/cpp/var.h"
#include "ppapi/utility/completion_callback_factory.h"
#ifdef WIN32
#undef PostMessage
#endif
// Number of connections to queue up on the listening
// socket before new ones get "Connection Refused"
static const int kBacklog = 10;
// Implement htons locally. Even though this is provided by
// nacl_io we don't want to include nacl_io in this simple
// example.
static uint16_t Htons(uint16_t hostshort) {
uint8_t result_bytes[2];
result_bytes[0] = (uint8_t) ((hostshort >> 8) & 0xFF);
result_bytes[1] = (uint8_t) (hostshort & 0xFF);
uint16_t result;
memcpy(&result, result_bytes, 2);
return result;
}
void EchoServer::Start(uint16_t port) {
if (!pp::TCPSocket::IsAvailable()) {
Log("TCPSocket not available");
return;
}
listening_socket_ = pp::TCPSocket(instance_);
if (listening_socket_.is_null()) {
Log("Error creating TCPSocket.");
return;
}
std::ostringstream status;
status << "Starting server on port: " << port;
Log(status.str().c_str());
// Attempt to listen on all interfaces (0.0.0.0)
// on the given port number.
PP_NetAddress_IPv4 ipv4_addr = { Htons(port), { 0 } };
pp::NetAddress addr(instance_, ipv4_addr);
pp::CompletionCallback callback =
callback_factory_.NewCallback(&EchoServer::OnBindCompletion);
int32_t rtn = listening_socket_.Bind(addr, callback);
if (rtn != PP_OK_COMPLETIONPENDING) {
Log("Error binding listening socket.");
return;
}
}
void EchoServer::OnBindCompletion(int32_t result) {
if (result != PP_OK) {
std::ostringstream status;
status << "server: Bind failed with: " << result;
Log(status.str().c_str());
return;
}
pp::CompletionCallback callback =
callback_factory_.NewCallback(&EchoServer::OnListenCompletion);
int32_t rtn = listening_socket_.Listen(kBacklog, callback);
if (rtn != PP_OK_COMPLETIONPENDING) {
Log("server: Error listening on server socket.");
return;
}
}
void EchoServer::OnListenCompletion(int32_t result) {
std::ostringstream status;
if (result != PP_OK) {
status << "server: Listen failed with: " << result;
Log(status.str().c_str());
return;
}
pp::NetAddress addr = listening_socket_.GetLocalAddress();
status << "server: Listening on: " << addr.DescribeAsString(true).AsString();
Log(status.str().c_str());
ready_ = true;
if (ready_cond_) {
pthread_mutex_lock(ready_lock_);
pthread_cond_signal(ready_cond_);
pthread_mutex_unlock(ready_lock_);
}
TryAccept();
}
void EchoServer::OnAcceptCompletion(int32_t result, pp::TCPSocket socket) {
std::ostringstream status;
if (result != PP_OK) {
status << "server: Accept failed: " << result;
Log(status.str().c_str());
return;
}
pp::NetAddress addr = socket.GetLocalAddress();
status << "server: New connection from: ";
status << addr.DescribeAsString(true).AsString();
Log(status.str().c_str());
incoming_socket_ = socket;
TryRead();
}
void EchoServer::OnReadCompletion(int32_t result) {
std::ostringstream status;
if (result <= 0) {
if (result == 0)
status << "server: client disconnected";
else
status << "server: Read failed: " << result;
Log(status.str().c_str());
// Remove the current incoming socket and try
// to accept the next one.
incoming_socket_.Close();
incoming_socket_ = pp::TCPSocket();
TryAccept();
return;
}
status << "server: Read " << result << " bytes";
Log(status.str().c_str());
// Echo the bytes back to the client
pp::CompletionCallback callback =
callback_factory_.NewCallback(&EchoServer::OnWriteCompletion);
result = incoming_socket_.Write(receive_buffer_, result, callback);
if (result != PP_OK_COMPLETIONPENDING) {
status << "server: Write failed: " << result;
Log(status.str().c_str());
}
}
void EchoServer::OnWriteCompletion(int32_t result) {
std::ostringstream status;
if (result < 0) {
status << "server: Write failed: " << result;
Log(status.str().c_str());
return;
}
status << "server: Wrote " << result << " bytes";
Log(status.str().c_str());
// Try and read more bytes from the client
TryRead();
}
void EchoServer::TryRead() {
pp::CompletionCallback callback =
callback_factory_.NewCallback(&EchoServer::OnReadCompletion);
incoming_socket_.Read(receive_buffer_, kBufferSize, callback);
}
void EchoServer::TryAccept() {
pp::CompletionCallbackWithOutput<pp::TCPSocket> callback =
callback_factory_.NewCallbackWithOutput(
&EchoServer::OnAcceptCompletion);
listening_socket_.Accept(callback);
}