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