Reland Add Netsim Http service
This reverts the revert and fixes the emulator build failure.
Emulator build failure is that `core/server.cc` uses the macro `NETSIM_ANDROID_EMULATOR` but it was not set correctly in `core-lib` target in cmake. Fix the build failure by setting `NETSIM_ANDROID_EMULATOR` in `src/core/CMakeLists.txt`
Bug: 246360358
Test: ninja -C objs netsim
This reverts commit ffac07798f7eadce5065534fbe1c35fb254d8366.
Change-Id: I9a7f9f097b9bfbfa17cfb9df833cd1f70df4fc4b
diff --git a/Android.bp b/Android.bp
index 2cfdb5a..960fff3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -66,6 +66,7 @@
"src/core/server.cc",
"src/fe/cli.cc",
"src/fe/frontend_server.cc",
+ "src/fe/http_server.cc",
"src/hci/fd_startup.cc",
"src/hci/hci_chip_emulator.cc",
"src/hci/hci_debug.cc",
@@ -77,12 +78,16 @@
],
shared_libs: [
"libgrpc++",
+ "libcap",
+ "libcrypto",
+ "libssl",
],
static_libs: [
"libc++fs",
"libjsoncpp",
"libprotobuf-cpp-full",
"libscriptedbeaconpayload-protos-lite",
+ "libwebsockets",
"netsim-proto",
],
whole_static_libs: [
@@ -98,12 +103,16 @@
],
shared_libs: [
"libgrpc++",
+ "libcap",
+ "libcrypto",
+ "libssl",
],
static_libs: [
"libc++fs",
"libjsoncpp",
"libprotobuf-cpp-full",
"libscriptedbeaconpayload-protos-lite",
+ "libwebsockets",
"netsim-proto",
"lib-netsim",
],
diff --git a/src/controller/scene_controller.cc b/src/controller/scene_controller.cc
index a3ca870..c7687e4 100644
--- a/src/controller/scene_controller.cc
+++ b/src/controller/scene_controller.cc
@@ -17,7 +17,6 @@
#include <cmath>
#include "hci/hci_chip_emulator.h"
-#include "util/log.h"
namespace netsim {
namespace controller {
@@ -32,10 +31,14 @@
std::unique_lock<std::mutex> lock(this->mutex_);
scene_.add_devices()->CopyFrom(device);
}
-const netsim::model::Scene &SceneController::Get() const { return scene_; }
+const netsim::model::Scene SceneController::Copy() {
+ std::unique_lock<std::mutex> lock(this->mutex_);
+ return scene_;
+}
bool SceneController::SetPosition(const std::string &device_serial,
const netsim::model::Position &position) {
+ std::unique_lock<std::mutex> lock(this->mutex_);
for (auto &device : *scene_.mutable_devices()) {
if (device.device_serial() == device_serial) {
device.mutable_position()->CopyFrom(position);
diff --git a/src/controller/scene_controller.h b/src/controller/scene_controller.h
index 6a04ade..588e7d8 100644
--- a/src/controller/scene_controller.h
+++ b/src/controller/scene_controller.h
@@ -31,7 +31,7 @@
static SceneController &Singleton();
void Add(netsim::model::Device &device);
- const netsim::model::Scene &Get() const;
+ const netsim::model::Scene Copy();
bool SetPosition(const std::string &device_serial,
const netsim::model::Position &position);
diff --git a/src/controller/scene_controller_test.cc b/src/controller/scene_controller_test.cc
index acef218..840a130 100644
--- a/src/controller/scene_controller_test.cc
+++ b/src/controller/scene_controller_test.cc
@@ -22,7 +22,7 @@
namespace {
TEST(SceneTest, GetTest) {
- const auto &scene = netsim::controller::SceneController::Singleton().Get();
+ const auto &scene = netsim::controller::SceneController::Singleton().Copy();
EXPECT_EQ(scene.devices_size(), 0);
}
@@ -31,7 +31,7 @@
device.set_visible(true);
netsim::controller::SceneController::Singleton().Add(device);
- const auto &scene = netsim::controller::SceneController::Singleton().Get();
+ const auto &scene = netsim::controller::SceneController::Singleton().Copy();
EXPECT_EQ(scene.devices_size(), 1);
EXPECT_EQ(scene.devices(0).visible(), true);
}
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index dd61e2f..e3a747a 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -18,3 +18,5 @@
DEPS frontend-grpc-lib frontend-lib grpc++ protobuf::libprotobuf)
target_include_directories(core-lib PRIVATE ..)
+
+target_compile_definitions(core-lib PUBLIC NETSIM_ANDROID_EMULATOR)
diff --git a/src/core/server.cc b/src/core/server.cc
index 7ef3c44..19c21e8 100644
--- a/src/core/server.cc
+++ b/src/core/server.cc
@@ -16,15 +16,24 @@
#include <memory>
#include <string>
+#include <thread>
#include "fe/frontend_server.h"
#include "hci/fd_startup.h"
+#ifndef NETSIM_ANDROID_EMULATOR
+#include "fe/http_server.h"
+#endif
namespace netsim {
void StartWithFds(const std::string &startup_str, bool debug) {
std::unique_ptr<hci::FdStartup> fds = hci::FdStartup::Create();
fds->Connect(startup_str);
+
+#ifndef NETSIM_ANDROID_EMULATOR
+ std::thread http_server(netsim::http::RunHttpServer);
+#endif
+
// running the frontend server blocks
netsim::RunFrontendServer();
}
diff --git a/src/fe/frontend_server.cc b/src/fe/frontend_server.cc
index ceab739..7d8b0eb 100644
--- a/src/fe/frontend_server.cc
+++ b/src/fe/frontend_server.cc
@@ -46,7 +46,7 @@
grpc::Status GetDevices(grpc::ServerContext *context,
const google::protobuf::Empty *empty,
frontend::GetDevicesResponse *reply) {
- const auto &scene = netsim::controller::SceneController::Singleton().Get();
+ const auto &scene = netsim::controller::SceneController::Singleton().Copy();
for (const auto &device : scene.devices())
reply->add_devices()->CopyFrom(device);
return grpc::Status::OK;
diff --git a/src/fe/frontend_server_test.cc b/src/fe/frontend_server_test.cc
index 3cb00c2..fd8c20d 100644
--- a/src/fe/frontend_server_test.cc
+++ b/src/fe/frontend_server_test.cc
@@ -58,7 +58,7 @@
request.mutable_position()->set_z(3.3);
grpc::Status status = service_.SetPosition(&context_, &request, &response);
ASSERT_TRUE(status.ok());
- const auto &scene = netsim::controller::SceneController::Singleton().Get();
+ const auto &scene = netsim::controller::SceneController::Singleton().Copy();
// NOTE: Singleton won't be reset between tests. Need to either deprecate
// Singleton pattern or provide reset().
bool found = false;
diff --git a/src/fe/http_server.cc b/src/fe/http_server.cc
new file mode 100644
index 0000000..dc323fe
--- /dev/null
+++ b/src/fe/http_server.cc
@@ -0,0 +1,301 @@
+// Copyright 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "fe/http_server.h"
+
+#include <google/protobuf/empty.pb.h>
+#include <google/protobuf/util/json_util.h>
+#include <libwebsockets.h>
+
+#include <string>
+
+#include "controller/scene_controller.h"
+#include "frontend.pb.h"
+
+namespace netsim::http {
+
+namespace {
+
+const unsigned char kCorsHeaderKey[] = "Access-Control-Allow-Origin:";
+const unsigned char kCorsHeaderValue[] = "*";
+
+class Status {
+ public:
+ Status() = default;
+ Status(bool ok) : ok_(ok) { code_ = HTTP_STATUS_OK; }
+ Status(unsigned int code, std::string error_message)
+ : code_(code), error_message_(std::move(error_message)) {
+ ok_ = false;
+ }
+ bool Ok() { return ok_; };
+ unsigned int Code() { return code_; };
+
+ std::string GetJsonString() {
+ return "{\"code\":" + std::to_string(code_) +
+ ", \"error_message\":" + error_message_ + "}";
+ }
+
+ private:
+ bool ok_;
+ unsigned int code_;
+ std::string error_message_;
+};
+
+std::string GetEnv(const std::string &name, const std::string &default_value) {
+ auto val = std::getenv(name.c_str());
+ if (!val) {
+ return default_value;
+ }
+ return val;
+}
+
+Status GetVersion(const std::string &request, std::string &response) {
+ frontend::VersionResponse response_proto;
+ response_proto.set_version("123b");
+ google::protobuf::util::MessageToJsonString(response_proto, &response);
+ return Status(true);
+}
+
+Status SetPosition(const std::string &request, std::string &response) {
+ frontend::SetPositionRequest request_proto;
+ google::protobuf::util::JsonStringToMessage(request, &request_proto);
+ google::protobuf::Empty response_proto;
+
+ auto status = netsim::controller::SceneController::Singleton().SetPosition(
+ request_proto.device_serial(), request_proto.position());
+ if (!status) {
+ return Status(HTTP_STATUS_BAD_REQUEST,
+ "device_serial not found: " + request_proto.device_serial());
+ }
+
+ google::protobuf::util::MessageToJsonString(response_proto, &response);
+ return Status(true);
+}
+
+Status GetDevices(const std::string &request, std::string &response) {
+ frontend::GetDevicesResponse response_proto;
+ const auto &scene = netsim::controller::SceneController::Singleton().Copy();
+ for (const auto &device : scene.devices())
+ response_proto.add_devices()->CopyFrom(device);
+
+ google::protobuf::util::MessageToJsonString(response_proto, &response);
+ return Status(true);
+}
+
+// Per-session user data.
+struct Session {
+ std::string path;
+ bool called_scene_controller = false;
+ bool sent_header = false;
+ bool sent_body = false;
+ Status status;
+ std::string request_body = "";
+ std::string response = "";
+};
+
+int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
+ void *in, size_t len) {
+ auto *session = reinterpret_cast<Session *>(user);
+ uint8_t buf[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE],
+ *start = &buf[LWS_PRE], *p = start,
+ *end = &buf[sizeof(buf) - LWS_PRE - 1];
+
+ switch (reason) {
+ case LWS_CALLBACK_HTTP:
+ session->path = std::string(reinterpret_cast<const char *>(in));
+
+ lws_get_peer_simple(wsi, reinterpret_cast<char *>(buf), sizeof(buf));
+ lwsl_notice("HTTP: connection %s, path %s\n",
+ reinterpret_cast<const char *>(buf), session->path.c_str());
+
+ if (lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI)) // GET
+ // write the body separately
+ lws_callback_on_writable(wsi);
+ return 0;
+
+ case LWS_CALLBACK_HTTP_BODY:
+ session->request_body =
+ std::string(reinterpret_cast<const char *>(in), len);
+ return 0;
+
+ case LWS_CALLBACK_HTTP_BODY_COMPLETION:
+ lws_callback_on_writable(wsi);
+ return 0;
+
+ case LWS_CALLBACK_HTTP_WRITEABLE:
+ if (!session) break;
+
+ if (!session->called_scene_controller) {
+ if (session->path.compare("/get-version") == 0) {
+ session->status =
+ GetVersion(session->request_body, session->response);
+ } else if (session->path.compare("/get-devices") == 0) {
+ session->status =
+ GetDevices(session->request_body, session->response);
+ } else if (session->path.compare("/set-position") == 0) {
+ session->status =
+ SetPosition(session->request_body, session->response);
+ } else {
+ session->status = Status(HTTP_STATUS_NOT_FOUND,
+ "invalid url:" + std::string(session->path));
+ }
+
+ if (!session->status.Ok())
+ session->response = session->status.GetJsonString();
+ session->called_scene_controller = true;
+ }
+
+ if (!session->sent_header) {
+ if (lws_add_http_common_headers(
+ wsi, session->status.Code(), "application/json",
+ LWS_ILLEGAL_HTTP_CONTENT_LEN, /* no content len */
+ &p, end))
+ return 1;
+
+ if (lws_add_http_header_by_name(wsi, kCorsHeaderKey,
+
+ kCorsHeaderValue,
+ sizeof(kCorsHeaderValue), &p, end))
+ return 1;
+ if (lws_finalize_write_http_header(wsi, start, &p, end)) return 1;
+
+ lws_callback_on_writable(wsi);
+ session->sent_header = true;
+ return 0;
+ }
+
+ if (!session->sent_body) {
+ lws_write(wsi, (uint8_t *)session->response.c_str(),
+ session->response.size(), LWS_WRITE_HTTP_FINAL);
+ session->sent_body = true;
+ }
+
+ if (lws_http_transaction_completed(wsi)) return -1;
+
+ return 0;
+
+ default:
+ break;
+ }
+
+ return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static const struct lws_protocols protocol = {
+ "http", callback_http, sizeof(Session), 0, 0, NULL, 0};
+
+static const struct lws_protocols *pprotocols[] = {&protocol, NULL};
+
+// override the default mount for /netsim in the URL space
+
+static const struct lws_http_mount mount_netsim = {
+ /* .mount_next */ NULL, // linked-list "next"
+ /* .mountpoint */ "/netsim", // mountpoint URL
+ /* .origin */ NULL, // protocol
+ /* .def */ NULL,
+ /* .protocol */ "http",
+ /* .cgienv */ NULL,
+ /* .extra_mimetypes */ NULL,
+ /* .interpret */ NULL,
+ /* .cgi_timeout */ 0,
+ /* .cache_max_age */ 0,
+ /* .auth_mask */ 0,
+ /* .cache_reusable */ 0,
+ /* .cache_revalidate */ 0,
+ /* .cache_intermediaries */ 0,
+ /* .origin_protocol */ LWSMPRO_CALLBACK, // dynamic
+ /* .mountpoint_len */ 7, // char count of "/netsim"
+ /* .basic_auth_login_file */ NULL,
+};
+auto origin = GetEnv("HOME", ".") + "/netsim-web";
+static const struct lws_http_mount mount = {
+ /* .mount_next */ &mount_netsim, // linked-list "next"
+ /* .mountpoint */ "/", // mountpoint URL
+ /* .origin */ origin.c_str(), // serve from dir
+ /* .def */ "index.html", // default filename
+ /* .protocol */ NULL,
+ /* .cgienv */ NULL,
+ /* .extra_mimetypes */ NULL,
+ /* .interpret */ NULL,
+ /* .cgi_timeout */ 0,
+ /* .cache_max_age */ 0,
+ /* .auth_mask */ 0,
+ /* .cache_reusable */ 0,
+ /* .cache_revalidate */ 0,
+ /* .cache_intermediaries */ 0,
+ /* .origin_protocol */ LWSMPRO_FILE, // files in a dir
+ /* .mountpoint_len */ 1, // char count
+ /* .basic_auth_login_file */ NULL,
+};
+
+} // namespace
+
+void RunHttpServer() {
+ lws_context_creation_info info;
+ lws_context *context;
+ const char *p;
+ int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
+
+ lws_set_log_level(logs, NULL);
+ lwsl_user("netsim http server is listening on http://localhost:7681\n");
+ lwsl_user("netsim https server is listening on https://localhost:7682\n");
+
+ memset(&info, 0, sizeof info); // otherwise uninitialized garbage
+ info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
+ LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
+ LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+ context = lws_create_context(&info);
+ if (!context) {
+ lwsl_err("lws init failed\n");
+ return;
+ }
+
+ std::string certs_dir = GetEnv("HOME", ".") + "/usr/share/webrtc/certs/";
+ std::string cert_file = certs_dir + "server.crt";
+ std::string key_file = certs_dir + "server.key";
+
+ // Run HTTP service on 7681.
+ info.port = 7681;
+ info.pprotocols = pprotocols;
+ info.mounts = &mount;
+ info.vhost_name = "http";
+
+ if (!lws_create_vhost(context, &info)) {
+ lwsl_err("Failed to create tls vhost\n");
+ goto bail;
+ }
+
+ // Run HTTP service on 7682.
+ info.port = 7682;
+#if defined(LWS_WITH_TLS)
+ info.ssl_cert_filepath = cert_file.c_str();
+ info.ssl_private_key_filepath = key_file.c_str();
+#endif
+ info.vhost_name = "https";
+
+ if (!lws_create_vhost(context, &info)) {
+ lwsl_err("Failed to create tls vhost\n");
+ goto bail;
+ }
+
+ while (n >= 0) n = lws_service(context, 0);
+
+bail:
+ lws_context_destroy(context);
+
+ return;
+}
+
+} // namespace netsim::http
diff --git a/src/fe/http_server.h b/src/fe/http_server.h
new file mode 100644
index 0000000..d4d7e17
--- /dev/null
+++ b/src/fe/http_server.h
@@ -0,0 +1,21 @@
+// Copyright 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+namespace netsim::http {
+
+void RunHttpServer();
+
+} // namespace netsim::http
\ No newline at end of file