Minimal implementation of Widevine MediaCAS ECMG.
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=226515998
This commit is contained in:
@@ -20,7 +20,9 @@ PUBLIC_COPTS = ["-fvisibility=default"]
|
||||
|
||||
filegroup(
|
||||
name = "binary_release_files",
|
||||
srcs = glob(["*.h"]),
|
||||
srcs = glob(["*.h"]) + [
|
||||
":wv_ecmg",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
@@ -49,15 +51,6 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "simulcrypt_server",
|
||||
srcs = ["simulcrypt_server.cc"],
|
||||
deps = [
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "wv_cas_ca_descriptor",
|
||||
srcs = ["wv_cas_ca_descriptor.cc"],
|
||||
@@ -97,8 +90,8 @@ cc_library(
|
||||
"@abseil_repo//absl/base:core_headers", # buildcleaner: keep
|
||||
"@abseil_repo//absl/memory", # buildcleaner: keep
|
||||
"@abseil_repo//absl/strings", # buildcleaner: keep
|
||||
"//common:status",
|
||||
"//common:crypto_util",
|
||||
"//common:status",
|
||||
"//example:constants",
|
||||
"//media_cas_packager_sdk/internal:ecm",
|
||||
"//media_cas_packager_sdk/internal:ecm_generator",
|
||||
@@ -134,7 +127,6 @@ cc_library(
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"@abseil_repo//absl/strings",
|
||||
"@curl_repo//:curl",
|
||||
"//common:status",
|
||||
"//common:signature_util",
|
||||
"//media_cas_packager_sdk/internal:key_fetcher",
|
||||
"//protos/public:media_cas_encryption_proto",
|
||||
@@ -153,7 +145,6 @@ cc_test(
|
||||
"//external:protobuf",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//common:status",
|
||||
"//protos/public:media_cas_encryption_proto",
|
||||
],
|
||||
)
|
||||
@@ -175,3 +166,14 @@ cc_test(
|
||||
"//testing:gunit_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "wv_ecmg",
|
||||
srcs = ["wv_ecmg.cc"],
|
||||
deps = [
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//media_cas_packager_sdk/internal:ecmg_client_handler",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 Google LLC.
|
||||
//
|
||||
// This software is licensed under the terms defined in the Widevine Master
|
||||
// License Agreement. For a copy of this agreement, please contact
|
||||
// widevine-licensing@google.com.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Example server that listens on a port for Simulcrypt API messages.
|
||||
|
||||
#include <errno.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include <iostream>
|
||||
|
||||
#include "gflags/gflags.h"
|
||||
#include "glog/logging.h"
|
||||
|
||||
DEFINE_int32(port, 0, "Server port number");
|
||||
|
||||
constexpr uint32_t kBufferSize = 256;
|
||||
constexpr uint32_t kLicenseBacklog = 5;
|
||||
constexpr uint32_t kWriteChunkSize = 18;
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
CHECK(FLAGS_port != 0) << "need --port";
|
||||
|
||||
struct sockaddr_in server_address;
|
||||
bzero(reinterpret_cast<char *>(&server_address), sizeof(server_address));
|
||||
server_address.sin_family = AF_INET;
|
||||
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
server_address.sin_port = htons(FLAGS_port);
|
||||
|
||||
int listen_socket_fd = socket(AF_INET, SOCK_STREAM, /* protocol= */ 0);
|
||||
CHECK(listen_socket_fd >= 0) << "failed to open socket";
|
||||
CHECK(bind(listen_socket_fd, (struct sockaddr *)&server_address,
|
||||
sizeof(server_address)) >= 0)
|
||||
<< "error on binding";
|
||||
std::cout << "Server listening ..." << std::endl << std::flush;
|
||||
int return_val = listen(listen_socket_fd, kLicenseBacklog);
|
||||
switch (return_val) {
|
||||
case EADDRINUSE:
|
||||
LOG(FATAL) << "Another socket is already listening on the same port.";
|
||||
break;
|
||||
case EBADF:
|
||||
LOG(FATAL) << "The argument sockfd is not a valid descriptor.";
|
||||
break;
|
||||
case ENOTSOCK:
|
||||
LOG(FATAL) << "The argument sockfd is not a socket.";
|
||||
break;
|
||||
case EOPNOTSUPP:
|
||||
LOG(FATAL) << "The socket is not of a type that supports the listen() "
|
||||
"operation.";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
struct sockaddr_in client_address;
|
||||
socklen_t clilet_address_size = sizeof(client_address);
|
||||
int client_socket_fd = accept(
|
||||
listen_socket_fd, reinterpret_cast<struct sockaddr *>(&client_address),
|
||||
&clilet_address_size);
|
||||
CHECK(client_socket_fd >= 0) << "error on accept";
|
||||
|
||||
char buffer[kBufferSize];
|
||||
bzero(buffer, kBufferSize);
|
||||
if (read(client_socket_fd, buffer, kBufferSize - 1) < 0) {
|
||||
LOG(FATAL) << "ERROR reading from socket";
|
||||
}
|
||||
printf("Here is the message: %s", buffer);
|
||||
if (write(client_socket_fd, "I got your message", kWriteChunkSize) < 0) {
|
||||
LOG(FATAL) << "ERROR writing to socket";
|
||||
}
|
||||
|
||||
close(client_socket_fd);
|
||||
close(listen_socket_fd);
|
||||
return 0;
|
||||
}
|
||||
@@ -95,7 +95,7 @@ WvCasStatus WvCasCaDescriptor::GenerateCaDescriptor(
|
||||
return INTERNAL;
|
||||
}
|
||||
std::string descriptor;
|
||||
util::Status status =
|
||||
Status status =
|
||||
string_util::BitsetStringToBinaryString(descriptor_bitset, &descriptor);
|
||||
|
||||
*serialized_ca_desc = descriptor;
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
#include <vector>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "common/status.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "common/crypto_util.h"
|
||||
#include "common/status.h"
|
||||
#include "example/constants.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm_generator.h"
|
||||
@@ -154,7 +154,7 @@ WvCasStatus WvCasEcm::GenerateEcm(
|
||||
// TODO(user): When we want to retrieve entitlement key from License Server
|
||||
// we need to figure out a way to provide real 'content_id' and 'provder'
|
||||
// to this function here.
|
||||
util::Status status;
|
||||
Status status;
|
||||
if (!(status = cas_ecm->Initialize(kDefaultContentId, kDefaultProvider,
|
||||
ecm_init_params, &entitlement_request))
|
||||
.ok()) {
|
||||
@@ -265,7 +265,7 @@ WvCasStatus WvCasEcm::GenerateSingleKeyEcm(
|
||||
// TODO(user): When we want to retrieve entitlement key from License Server
|
||||
// we need to figure out a way to provide real 'content_id' and 'provder'
|
||||
// to this function here.
|
||||
util::Status status;
|
||||
Status status;
|
||||
if (!(status = cas_ecm->Initialize(kDefaultContentId, kDefaultProvider,
|
||||
ecm_init_params, &entitlement_request))
|
||||
.ok()) {
|
||||
@@ -319,8 +319,8 @@ WvCasStatus WvCasEcm::GenerateTsPacket(const std::string& ecm, uint16_t pid,
|
||||
uint8_t* continuity_counter,
|
||||
uint8_t* packet) {
|
||||
ssize_t bytes_modified = 0;
|
||||
util::Status status = InsertEcmAsTsPacket(
|
||||
ecm, pid, table_id, continuity_counter, packet, &bytes_modified);
|
||||
Status status = InsertEcmAsTsPacket(ecm, pid, table_id, continuity_counter,
|
||||
packet, &bytes_modified);
|
||||
if (!status.ok() || bytes_modified != kTsPacketSize) {
|
||||
memset(packet, 0, kTsPacketSize);
|
||||
LOG(ERROR) << "Failed to generate TS packet: " << status;
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "curl/curl.h"
|
||||
#include "curl/easy.h"
|
||||
#include "common/status.h"
|
||||
#include "common/signature_util.h"
|
||||
#include "protos/public/media_cas_encryption.pb.h"
|
||||
|
||||
@@ -41,12 +40,12 @@ DEFINE_string(signing_iv, "",
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
util::Status WvCasKeyFetcher::RequestEntitlementKey(
|
||||
const std::string& request_string, std::string* signed_response_string) {
|
||||
Status WvCasKeyFetcher::RequestEntitlementKey(const std::string& request_string,
|
||||
std::string* signed_response_string) {
|
||||
if (FLAGS_signing_provider.empty() || FLAGS_signing_key.empty() ||
|
||||
FLAGS_signing_iv.empty()) {
|
||||
return util::Status(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
return Status(
|
||||
error::INVALID_ARGUMENT,
|
||||
"Flag 'signing_provider', 'signing_key' or 'signing_iv' is empty");
|
||||
}
|
||||
|
||||
@@ -63,8 +62,8 @@ util::Status WvCasKeyFetcher::RequestEntitlementKey(
|
||||
// NOTE: MessageToJsonString will automatically converts 'bytes' type fields
|
||||
// to base64. For example content ID '21140844' becomes 'MjExNDA4NDQ='.
|
||||
if (!MessageToJsonString(request, &request_json, print_options).ok()) {
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"Failed to convert request message to json.");
|
||||
return Status(error::INTERNAL,
|
||||
"Failed to convert request message to json.");
|
||||
}
|
||||
LOG(INFO) << "Json CasEncryptionRequest: " << request_json;
|
||||
|
||||
@@ -76,7 +75,7 @@ util::Status WvCasKeyFetcher::RequestEntitlementKey(
|
||||
request_json, absl::HexStringToBytes(FLAGS_signing_key),
|
||||
absl::HexStringToBytes(FLAGS_signing_iv), &signature)
|
||||
.ok()) {
|
||||
return util::Status(util::error::INTERNAL, "Failed to sign the request.");
|
||||
return Status(error::INTERNAL, "Failed to sign the request.");
|
||||
}
|
||||
signed_request.set_signature(signature);
|
||||
signed_request.set_signer(FLAGS_signing_provider);
|
||||
@@ -85,23 +84,22 @@ util::Status WvCasKeyFetcher::RequestEntitlementKey(
|
||||
// 'signature' fields in SignedCasEncryptionRequest to base64, because they
|
||||
// are of type 'bytes'.
|
||||
if (!MessageToJsonString(signed_request, &signed_request_json).ok()) {
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"Failed to convert signed request message to json.");
|
||||
return Status(error::INTERNAL,
|
||||
"Failed to convert signed request message to json.");
|
||||
}
|
||||
LOG(INFO) << "Json SignedCasEncryptionRequest: " << signed_request_json;
|
||||
|
||||
// Makes HTTP request against License Server.
|
||||
std::string http_response_json;
|
||||
util::Status status =
|
||||
MakeHttpRequest(signed_request_json, &http_response_json);
|
||||
Status status = MakeHttpRequest(signed_request_json, &http_response_json);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
LOG(INFO) << "Json HTTP response: " << http_response_json;
|
||||
HttpResponse http_response;
|
||||
if (!JsonStringToMessage(http_response_json, &http_response).ok()) {
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"Failed to convert http response json to message.");
|
||||
return Status(error::INTERNAL,
|
||||
"Failed to convert http response json to message.");
|
||||
}
|
||||
|
||||
// Processes signed response.
|
||||
@@ -110,13 +108,13 @@ util::Status WvCasKeyFetcher::RequestEntitlementKey(
|
||||
LOG(INFO) << "Json CasEncryptionResponse: " << http_response.response();
|
||||
CasEncryptionResponse response;
|
||||
if (!JsonStringToMessage(http_response.response(), &response).ok()) {
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"Failed to convert response json to message.");
|
||||
return Status(error::INTERNAL,
|
||||
"Failed to convert response json to message.");
|
||||
}
|
||||
SignedCasEncryptionResponse signed_response;
|
||||
signed_response.set_response(response.SerializeAsString());
|
||||
signed_response.SerializeToString(signed_response_string);
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
size_t AppendToString(void* ptr, size_t size, size_t count, std::string* output) {
|
||||
@@ -125,12 +123,11 @@ size_t AppendToString(void* ptr, size_t size, size_t count, std::string* output)
|
||||
return data.size();
|
||||
}
|
||||
|
||||
util::Status WvCasKeyFetcher::MakeHttpRequest(
|
||||
const std::string& signed_request_json, std::string* http_response_json) const {
|
||||
Status WvCasKeyFetcher::MakeHttpRequest(const std::string& signed_request_json,
|
||||
std::string* http_response_json) const {
|
||||
CHECK(http_response_json);
|
||||
if (FLAGS_license_server.empty()) {
|
||||
return util::Status(util::error::INVALID_ARGUMENT,
|
||||
"Flag 'license_server' is empty");
|
||||
return Status(error::INVALID_ARGUMENT, "Flag 'license_server' is empty");
|
||||
}
|
||||
CURL* curl;
|
||||
CURLcode curl_code;
|
||||
@@ -145,15 +142,14 @@ util::Status WvCasKeyFetcher::MakeHttpRequest(
|
||||
(int64_t)strlen(signed_request_json.c_str()));
|
||||
curl_code = curl_easy_perform(curl);
|
||||
if (curl_code != CURLE_OK) {
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"curl_easy_perform() failed: " +
|
||||
std::string(curl_easy_strerror(curl_code)));
|
||||
return Status(error::INTERNAL, "curl_easy_perform() failed: " +
|
||||
std::string(curl_easy_strerror(curl_code)));
|
||||
}
|
||||
curl_easy_cleanup(curl);
|
||||
} else {
|
||||
return util::Status(util::error::INTERNAL, "curl_easy_init() failed");
|
||||
return Status(error::INTERNAL, "curl_easy_init() failed");
|
||||
}
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <string>
|
||||
|
||||
#include "gflags/gflags.h"
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/internal/key_fetcher.h"
|
||||
|
||||
DECLARE_string(license_server);
|
||||
@@ -41,15 +40,15 @@ class WvCasKeyFetcher : public KeyFetcher {
|
||||
// |signed_response_string| a serialized SignedCasEncryptionResponse
|
||||
// message. It should be passed into
|
||||
// widevine::cas::Ecm::ProcessCasEncryptionResponse().
|
||||
virtual util::Status RequestEntitlementKey(const std::string& request_string,
|
||||
std::string* signed_response_string);
|
||||
Status RequestEntitlementKey(const std::string& request_string,
|
||||
std::string* signed_response_string) override;
|
||||
|
||||
protected:
|
||||
// Makes a HTTP request to License Server for entitlement key(s).
|
||||
// Returns the HTTP response in Json format in |http_response_json|.
|
||||
// Protected visibility to support unit testing.
|
||||
virtual util::Status MakeHttpRequest(const std::string& signed_request_json,
|
||||
std::string* http_response_json) const;
|
||||
virtual Status MakeHttpRequest(const std::string& signed_request_json,
|
||||
std::string* http_response_json) const;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "common/status.h"
|
||||
#include "protos/public/media_cas_encryption.pb.h"
|
||||
|
||||
using testing::_;
|
||||
@@ -55,9 +54,8 @@ class MockWvCasKeyFetcher : public WvCasKeyFetcher {
|
||||
public:
|
||||
MockWvCasKeyFetcher() : WvCasKeyFetcher() {}
|
||||
~MockWvCasKeyFetcher() override {}
|
||||
MOCK_CONST_METHOD2(MakeHttpRequest,
|
||||
util::Status(const std::string& signed_request_json,
|
||||
std::string* http_response_json));
|
||||
MOCK_CONST_METHOD2(MakeHttpRequest, Status(const std::string& signed_request_json,
|
||||
std::string* http_response_json));
|
||||
};
|
||||
|
||||
class WvCasKeyFetcherTest : public ::testing::Test {
|
||||
@@ -93,7 +91,7 @@ TEST_F(WvCasKeyFetcherTest, TestRequestEntitlementKey) {
|
||||
EXPECT_CALL(mock_key_fetcher_,
|
||||
MakeHttpRequest(kSignedCasEncryptionRequest, _))
|
||||
.WillOnce(DoAll(SetArgumentPointee<1>(std::string(kHttpResponse)),
|
||||
Return(util::OkStatus())));
|
||||
Return(OkStatus())));
|
||||
|
||||
std::string actual_signed_response;
|
||||
EXPECT_OK(mock_key_fetcher_.RequestEntitlementKey(
|
||||
|
||||
190
media_cas_packager_sdk/public/wv_ecmg.cc
Normal file
190
media_cas_packager_sdk/public/wv_ecmg.cc
Normal file
@@ -0,0 +1,190 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 Google LLC.
|
||||
//
|
||||
// This software is licensed under the terms defined in the Widevine Master
|
||||
// License Agreement. For a copy of this agreement, please contact
|
||||
// widevine-licensing@google.com.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Example server that listens on a port for Simulcrypt API messages.
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include "gflags/gflags.h"
|
||||
#include "glog/logging.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "media_cas_packager_sdk/internal/ecmg_client_handler.h"
|
||||
|
||||
static constexpr int32_t kDefaultDelayStart = 200;
|
||||
static constexpr int32_t kDefaultDelayStop = 200;
|
||||
static constexpr int32_t kDefaultEcmRepPeriod = 100;
|
||||
static constexpr int32_t kDefaultMaxCompTime = 100;
|
||||
static constexpr int32_t kAccessCriteriaTransferMode = 1;
|
||||
|
||||
DEFINE_int32(port, 0, "Server port number");
|
||||
|
||||
// ECMG related flags.
|
||||
// TODO(user): Consider adding flags 'ac_delay_start', 'ac_delay_stop',
|
||||
// 'transition_delay_start', 'transition_delay_stop'.
|
||||
DEFINE_int32(delay_start, kDefaultDelayStart,
|
||||
absl::StrCat("This flag sets the DVB SimulCrypt delay_start "
|
||||
"parameter, in milliseconds. Default: ",
|
||||
kDefaultDelayStart, " ms")
|
||||
.c_str());
|
||||
DEFINE_int32(delay_stop, kDefaultDelayStop,
|
||||
absl::StrCat("This flag sets the DVB SimulCrypt delay_stop "
|
||||
"parameter, in milliseconds. Default: ",
|
||||
kDefaultDelayStop, " ms")
|
||||
.c_str());
|
||||
DEFINE_int32(ecm_rep_period, kDefaultEcmRepPeriod,
|
||||
absl::StrCat("It sets the DVB SimulCrypt parameter "
|
||||
"ECM_rep_period, in milliseconds. Default: ",
|
||||
kDefaultEcmRepPeriod, " ms")
|
||||
.c_str());
|
||||
DEFINE_int32(max_comp_time, kDefaultMaxCompTime,
|
||||
absl::StrCat("It sets the DVB SimulCrypt parameter max_comp_time, "
|
||||
"in milliseconds. Default: ",
|
||||
kDefaultMaxCompTime, " ms")
|
||||
.c_str());
|
||||
DEFINE_int32(access_criteria_transfer_mode, kAccessCriteriaTransferMode,
|
||||
absl::StrCat("It sets the DVB SimulCrypt parameter "
|
||||
"access_criteria_transfer_mode. Default: ",
|
||||
kAccessCriteriaTransferMode)
|
||||
.c_str());
|
||||
|
||||
#define LISTEN_QUEUE_SIZE (20)
|
||||
#define BUFFER_SIZE (1024)
|
||||
|
||||
using widevine::cas::EcmgClientHandler;
|
||||
using widevine::cas::EcmgConfig;
|
||||
|
||||
void BuildEcmgConfig(EcmgConfig* config) {
|
||||
DCHECK(config);
|
||||
config->delay_start = FLAGS_delay_start;
|
||||
config->delay_stop = FLAGS_delay_stop;
|
||||
config->ecm_rep_period = FLAGS_ecm_rep_period;
|
||||
config->max_comp_time = FLAGS_max_comp_time;
|
||||
config->access_criteria_transfer_mode = FLAGS_access_criteria_transfer_mode;
|
||||
}
|
||||
|
||||
void PrintMessage(const std::string& description, const char* const message,
|
||||
size_t length) {
|
||||
LOG(INFO) << description;
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
printf("'\\x%02x', ", static_cast<uint16_t>(*(message + i)));
|
||||
fflush(stdout);
|
||||
}
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void ServeClient(int socket_fd, EcmgClientHandler* ecmg) {
|
||||
DCHECK(ecmg);
|
||||
char request[BUFFER_SIZE];
|
||||
char response[BUFFER_SIZE];
|
||||
while (true) {
|
||||
bzero(request, BUFFER_SIZE);
|
||||
bzero(response, BUFFER_SIZE);
|
||||
size_t response_length = 0;
|
||||
size_t request_length = recv(socket_fd, request, BUFFER_SIZE, 0);
|
||||
if (request_length == 0) {
|
||||
LOG(ERROR) << "No more request from client";
|
||||
return;
|
||||
}
|
||||
if (request_length < 0) {
|
||||
LOG(ERROR) << "Failed to receive request from client";
|
||||
return;
|
||||
}
|
||||
PrintMessage("Request", request, request_length);
|
||||
ecmg->HandleRequest(request, response, &response_length);
|
||||
PrintMessage("Response", response, response_length);
|
||||
if (send(socket_fd, response, response_length, 0) < 0) {
|
||||
LOG(INFO) << "Failed to send response to client";
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
CHECK(FLAGS_port != 0) << "need --port";
|
||||
|
||||
EcmgConfig ecmg_config;
|
||||
BuildEcmgConfig(&ecmg_config);
|
||||
|
||||
// Server address.
|
||||
struct sockaddr_in server_address;
|
||||
bzero(reinterpret_cast<char*>(&server_address), sizeof(server_address));
|
||||
server_address.sin_family = AF_INET;
|
||||
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
server_address.sin_port = htons(FLAGS_port);
|
||||
|
||||
// Create a listening socket.
|
||||
int listen_socket_fd = socket(AF_INET, SOCK_STREAM, /* protocol= */ 0);
|
||||
CHECK(listen_socket_fd >= 0) << "Failed to open listening socket";
|
||||
|
||||
// Set SO_REUSEADDR on a socket to true (1).
|
||||
int optval = 1;
|
||||
setsockopt(listen_socket_fd, SOL_SOCKET, SO_REUSEADDR, &optval,
|
||||
sizeof(optval));
|
||||
|
||||
// Bind address.
|
||||
CHECK(bind(listen_socket_fd, (struct sockaddr*)&server_address,
|
||||
sizeof(server_address)) >= 0)
|
||||
<< "Failed to bind on server socket";
|
||||
|
||||
// Listen for connection from clients.
|
||||
std::cout << "Server listening ..." << std::endl << std::flush;
|
||||
int return_val = listen(listen_socket_fd, LISTEN_QUEUE_SIZE);
|
||||
switch (return_val) {
|
||||
case EADDRINUSE:
|
||||
LOG(FATAL) << "Another socket is already listening on the same port.";
|
||||
break;
|
||||
case EBADF:
|
||||
LOG(FATAL) << "The argument sockfd is not a valid descriptor.";
|
||||
break;
|
||||
case ENOTSOCK:
|
||||
LOG(FATAL) << "The argument sockfd is not a socket.";
|
||||
break;
|
||||
case EOPNOTSUPP:
|
||||
LOG(FATAL) << "The socket is not of a type that supports the listen() "
|
||||
"operation.";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// A single client handler, allow only 1 TCP connection / 1 channel at a time.
|
||||
EcmgClientHandler client_handler(&ecmg_config);
|
||||
|
||||
// While loop to serve different client connections.
|
||||
while (true) {
|
||||
struct sockaddr_in client_address;
|
||||
socklen_t client_address_size = sizeof(client_address);
|
||||
int client_socket_fd = accept(
|
||||
listen_socket_fd, reinterpret_cast<struct sockaddr*>(&client_address),
|
||||
&client_address_size);
|
||||
LOG(INFO) << "\nTCP connection start\n";
|
||||
if (client_socket_fd < 0) {
|
||||
LOG(ERROR) << "Failed to accept connection request from client";
|
||||
} else {
|
||||
// TODO(user): Support multi-threading of serving concurrent clients.
|
||||
// TODO(user): Per jfore@ suggestion, look into using
|
||||
// http://man7.org/linux/man-pages/man7/epoll.7.html
|
||||
ServeClient(client_socket_fd, &client_handler);
|
||||
}
|
||||
LOG(INFO) << "\nTCP connection closed\n";
|
||||
close(client_socket_fd);
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
// Close listening socket.
|
||||
close(listen_socket_fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user