From 093a49e9dd90ec75f5ebcb38a3e43c89cb2bee78 Mon Sep 17 00:00:00 2001
From: Stefan Spettel <stefan.spettel@phine.tech>
Date: Wed, 3 May 2023 19:12:27 +0200
Subject: [PATCH] feat(pcf): Implement HTTP/2 support for SM Policy API

Signed-off-by: Stefan Spettel <stefan.spettel@phine.tech>
---
 src/api-server/api_defs.h                     |   3 +-
 .../sm_policies_collection_api_handler.cpp    |   7 +-
 src/api-server/pcf-api-server.cpp             |   3 +-
 src/api-server/pcf-api-server.hpp             |   8 +-
 src/api-server/pcf-http2-server.cpp           | 171 +++++++++++++++---
 src/api-server/pcf-http2-server.hpp           |  42 ++++-
 src/common/pcf.h                              |   2 -
 src/oai_pcf/main.cpp                          |   1 +
 src/pcf_app/pcf_nrf.cpp                       |   5 +-
 9 files changed, 191 insertions(+), 51 deletions(-)

diff --git a/src/api-server/api_defs.h b/src/api-server/api_defs.h
index f52775c..72858e5 100644
--- a/src/api-server/api_defs.h
+++ b/src/api-server/api_defs.h
@@ -33,7 +33,8 @@
 namespace oai::pcf::api {
 class sm_policies {
  public:
-  static inline const std::string API_BASE     = "/npcf-smpolicycontrol/";
+  static inline const std::string API_NAME     = "npcf-smpolicycontrol";
+  static inline const std::string API_BASE     = "/" + API_NAME + "/";
   static inline const std::string CREATE_ROUTE = "/sm-policies";
 
   static std::string get_route();
diff --git a/src/api-server/handler/sm_policies_collection_api_handler.cpp b/src/api-server/handler/sm_policies_collection_api_handler.cpp
index 9dba21a..b212d1e 100644
--- a/src/api-server/handler/sm_policies_collection_api_handler.cpp
+++ b/src/api-server/handler/sm_policies_collection_api_handler.cpp
@@ -61,8 +61,8 @@ api_response sm_policies_collection_api_handler::create_sm_policy(
 
   switch (res) {
     case status_code::CREATED:
-      http_code    = http_status_code_e::HTTP_STATUS_CODE_201_CREATED;
-      location     = m_address + sm_policies::get_route() + association_id;
+      http_code = http_status_code_e::HTTP_STATUS_CODE_201_CREATED;
+      location  = m_address + sm_policies::get_route() + "/" + association_id;
       content_type = "application/json";
       break;
 
@@ -100,7 +100,8 @@ api_response sm_policies_collection_api_handler::create_sm_policy(
   }
   response.headers.add<Pistache::Http::Header::ContentType>(
       Pistache::Http::Mime::MediaType(content_type));
-  response.body = json_data.dump();
+  response.body        = json_data.dump();
+  response.status_code = http_code;
   return response;
 }
 
diff --git a/src/api-server/pcf-api-server.cpp b/src/api-server/pcf-api-server.cpp
index 809d2af..682b13e 100644
--- a/src/api-server/pcf-api-server.cpp
+++ b/src/api-server/pcf-api-server.cpp
@@ -69,7 +69,7 @@ void setUpUnixSignals(std::vector<int> quitSignals) {
 }
 #endif
 
-// using namespace oai::pcf::api;
+using namespace oai::pcf::api;
 
 void PCFApiServer::init(size_t thr) {
   auto opts = Pistache::Http::Endpoint::options().threads(thr);
@@ -87,6 +87,7 @@ void PCFApiServer::start() {
   m_httpEndpoint->setHandler(m_router->handler());
   m_httpEndpoint->serve();
 }
+
 void PCFApiServer::shutdown() {
   m_httpEndpoint->shutdown();
   m_smPoliciesCollectionApi = nullptr;
diff --git a/src/api-server/pcf-api-server.hpp b/src/api-server/pcf-api-server.hpp
index 88b0a67..5bdd9fb 100644
--- a/src/api-server/pcf-api-server.hpp
+++ b/src/api-server/pcf-api-server.hpp
@@ -31,8 +31,7 @@
  *      contact@openairinterface.org
  */
 
-#ifndef FILE_PCF_API_SERVER_SEEN
-#define FILE_PCF_API_SERVER_SEEN
+#pragma once
 
 //#include "IndividualSMPolicyDocumentApi.h"
 
@@ -50,6 +49,8 @@
 #include "SMPoliciesCollectionApiImpl.h"
 #include "IndividualSMPolicyDocumentApiImpl.h"
 
+namespace oai::pcf::api {
+
 class PCFApiServer {
  public:
   PCFApiServer(
@@ -84,5 +85,4 @@ class PCFApiServer {
   std::shared_ptr<oai::pcf::api::IndividualSMPolicyDocumentApiImpl>
       m_individualSmPolicyDocumentApi;
 };
-
-#endif
+}  // namespace oai::pcf::api
\ No newline at end of file
diff --git a/src/api-server/pcf-http2-server.cpp b/src/api-server/pcf-http2-server.cpp
index 1c7039a..efa3d35 100644
--- a/src/api-server/pcf-http2-server.cpp
+++ b/src/api-server/pcf-http2-server.cpp
@@ -21,9 +21,9 @@
 
 /*! \file pcf_http2-server.h
  \brief
- \author  Rohan Kharade
+ \author  Rohan Kharade, Stefan Spettel
  \company Openairinterface Software Allianse
- \date 2022
+ \date 2023
  \email: rohan.kharade@openairinterface.org
  */
 
@@ -37,13 +37,15 @@
 
 #include "3gpp_29.500.h"
 #include "logger.hpp"
-#include "pcf.h"
 #include "pcf_config.hpp"
+#include "api_defs.h"
+#include "SmPolicyContextData.h"
 
 using namespace nghttp2::asio_http2;
 using namespace nghttp2::asio_http2::server;
 using namespace oai::pcf::model;
 using namespace oai::pcf::config;
+using namespace oai::pcf::api;
 
 extern std::unique_ptr<pcf_config> pcf_cfg;
 
@@ -55,41 +57,152 @@ void pcf_http2_server::start() {
   std::string nfId           = {};
   std::string subscriptionID = {};
 
-  // Get list of supported APIs
+  // SM Policies Collection API
   server.handle(
-      "/", [&](const request& request, const response& /* response */) {
-        request.on_data([&](const uint8_t* /* data */, std::size_t /* len */) {
-          if (request.method().compare("GET") == 0) {
-            // this->get_api_list(response);
+      sm_policies::get_route(),
+      [&](const request& request, const response& response) {
+        if (request.method() != "POST") {
+          handle_method_not_exists(response, request);
+          return;
+        }
+        auto request_body = std::make_shared<std::stringstream>();
+
+        request.on_data(
+            [&, request_body](const uint8_t* data, std::size_t len) {
+              if (len > 0) {
+                std::copy(
+                    data, data + len,
+                    std::ostream_iterator<uint8_t>(*request_body));
+                return;
+              }
+              SmPolicyContextData context;
+              try {
+                nlohmann::json::parse(request_body->str()).get_to(context);
+                context.validate();
+                api_response resp =
+                    m_collection_api_handler->create_sm_policy(context);
+                auto h_map = convert_headers(resp);
+                response.write_head(
+                    static_cast<unsigned int>(resp.status_code), h_map);
+                response.end(resp.body);
+                return;
+              } catch (std::exception& e) {
+                handle_parsing_error(response, e);
+                return;
+              }
+            });
+      });
+
+  // Individual SM Policy
+  // We match for sm-policies/*
+  server.handle(
+      sm_policies::get_route() + "/",
+      [&](const request& request, const response& response) {
+        std::vector<std::string> split_result;
+        boost::split(split_result, request.uri().path, boost::is_any_of("/"));
+        bool is_update = false;
+        bool is_delete = false;
+        bool is_get    = false;
+        std::string policy_id;
+        if (split_result[split_result.size() - 1] == "delete") {
+          is_delete = true;
+          policy_id = split_result[split_result.size() - 2];
+        } else if (split_result[split_result.size() - 1] == "update") {
+          is_update = true;
+          policy_id = split_result[split_result.size() - 2];
+        } else {
+          is_get    = true;
+          policy_id = split_result[split_result.size() - 1];
+        }
+        if ((is_delete || is_update) && request.method() != "POST") {
+          handle_method_not_exists(response, request);
+          return;
+        }
+        if (is_get && request.method() != "GET") {
+          handle_method_not_exists(response, request);
+          return;
+        }
+        auto request_body = std::make_shared<std::stringstream>();
+
+        request.on_data([&, request_body, is_get, is_update, is_delete,
+                         policy_id](const uint8_t* data, std::size_t len) {
+          if (len > 0) {
+            std::copy(
+                data, data + len,
+                std::ostream_iterator<uint8_t>(*request_body));
+            return;
+          }
+          SmPolicyDeleteData delete_data;
+          SmPolicyUpdateContextData update_context_data;
+          api_response resp;
+          try {
+            if (is_update) {
+              nlohmann::json::parse(request_body->str())
+                  .get_to(update_context_data);
+              update_context_data.validate();
+              resp = m_individual_api_handler->update_sm_policy(
+                  policy_id, update_context_data);
+            } else if (is_delete) {
+              nlohmann::json::parse(request_body->str()).get_to(delete_data);
+              delete_data.validate();
+              resp = m_individual_api_handler->delete_sm_policy(
+                  policy_id, delete_data);
+            } else if (is_get) {
+              resp = m_individual_api_handler->get_sm_policy(policy_id);
+            }
+            auto h_map = convert_headers(resp);
+            response.write_head(
+                static_cast<unsigned int>(resp.status_code), h_map);
+            response.end(resp.body);
+            return;
+          } catch (std::exception& e) {
+            handle_parsing_error(response, e);
+            return;
           }
         });
       });
 
+  // Default Route
+  server.handle("/", [&](const request& request, const response& response) {
+    handle_method_not_exists(response, request);
+    return;
+  });
+
   if (server.listen_and_serve(ec, m_address, std::to_string(m_port))) {
-    std::cerr << "HTTP Server error: " << ec.message() << std::endl;
+    Logger::pcf_sbi().error("HTTP Server error: %s", ec.message());
   }
 }
 
-//------------------------------------------------------------------------------
-
-//------------------------------------------------------------------------------
-// void pcf_http2_server::get_api_list(const response &response) {
-//   int http_code = 0;
-//   nlohmann::json json_data = {};
-//   std::string content_type = "application/json";
-//   header_map h;
-//   h.emplace("content-type", header_value{content_type});
-//   if (pcf_cfg.get_api_list(json_data)) {
-//     http_code = HTTP_STATUS_CODE_200_OK;
-//     response.write_head(http_code, h);
-//     response.end(json_data.dump(4).c_str());
-//   } else {
-//     http_code = HTTP_STATUS_CODE_503_SERVICE_UNAVAILABLE;
-//     response.write_head(http_code, h);
-//     response.end();
-//   }
-// }
-//------------------------------------------------------------------------------
 void pcf_http2_server::stop() {
   server.stop();
 }
+
+void pcf_http2_server::handle_method_not_exists(
+    const response& response, const request& request) {
+  Logger::pcf_sbi().warn(
+      "Invalid route/method called: %s : %s", request.method(),
+      request.uri().path);
+  response.write_head(static_cast<unsigned int>(
+      http_status_code_e::HTTP_STATUS_CODE_404_NOT_FOUND));
+  response.end("The requested method does not exist");
+}
+
+void pcf_http2_server::handle_parsing_error(
+    const response& response, const std::exception& ex) {
+  Logger::pcf_sbi().warn("Parsing error: %s", ex.what());
+  response.write_head(static_cast<unsigned int>(
+      http_status_code_e::HTTP_STATUS_CODE_400_BAD_REQUEST));
+  // for security reasons it is better to not give the internal exception to the
+  // user, we can also decide to change that
+  response.end("Could not parse JSON data");
+}
+
+header_map pcf_http2_server::convert_headers(const api_response& response) {
+  header_map h_map;
+  for (const auto& hdr : response.headers.list()) {
+    std::stringstream ss;
+    hdr->write(ss);
+    h_map.emplace(hdr->name(), header_value{ss.str(), false});
+  }
+  return h_map;
+}
diff --git a/src/api-server/pcf-http2-server.hpp b/src/api-server/pcf-http2-server.hpp
index 7350b0b..274d5a8 100644
--- a/src/api-server/pcf-http2-server.hpp
+++ b/src/api-server/pcf-http2-server.hpp
@@ -21,14 +21,13 @@
 
 /*! \file pcf_http2-server.h
  \brief
- \author  Rohan Kharade
+ \author  Rohan Kharade, Stefan Spettel
  \company Openairinterface Software Allianse
  \date 2022
  \email: rohan.kharade@openairinterface.org
  */
 
-#ifndef FILE_PCF_HTTP2_SERVER_SEEN
-#define FILE_PCF_HTTP2_SERVER_SEEN
+#pragma once
 
 #include "conversions.hpp"
 #include "pcf.h"
@@ -38,16 +37,28 @@
 #include <nghttp2/asio_http2_server.h>
 #include "IndividualSMPolicyDocumentApiImpl.h"
 #include "SMPoliciesCollectionApiImpl.h"
+#include "sm_policies_collection_api_handler.h"
+#include "individual_sm_policy_document_api_handler.h"
+
+namespace oai::pcf::api {
 
 class pcf_http2_server {
  public:
   pcf_http2_server(
       const std::string& addr, uint32_t port,
       const std::unique_ptr<oai::pcf::app::pcf_app>& pcf_app_inst)
-      : m_address(addr),
-        m_port(port),
-        server(),
-        smpc_service(pcf_app_inst->get_pcf_smpc_service()){};
+      : m_address(addr), m_port(port), server() {
+    // TODO hardcode http string, how to handle https
+    std::string address = "http://" + addr + ":" + std::to_string(port);
+
+    m_collection_api_handler =
+        std::make_shared<sm_policies_collection_api_handler>(
+            pcf_app_inst->get_pcf_smpc_service(), address);
+
+    m_individual_api_handler =
+        std::make_shared<individual_sm_policy_document_api_handler>(
+            pcf_app_inst->get_pcf_smpc_service());
+  };
 
   void start();
   void init(size_t /* thr */) {}
@@ -63,7 +74,20 @@ class pcf_http2_server {
 
   nghttp2::asio_http2::server::http2 server;
 
-  std::shared_ptr<oai::pcf::app::pcf_smpc> smpc_service;
+  std::shared_ptr<sm_policies_collection_api_handler> m_collection_api_handler;
+  std::shared_ptr<individual_sm_policy_document_api_handler>
+      m_individual_api_handler;
+
+  static void handle_method_not_exists(
+      const nghttp2::asio_http2::server::response& response,
+      const nghttp2::asio_http2::server::request& request);
+
+  static void handle_parsing_error(
+      const nghttp2::asio_http2::server::response& response,
+      const std::exception& ex);
+
+  static nghttp2::asio_http2::header_map convert_headers(
+      const api_response& response);
 };
 
-#endif
+}  // namespace oai::pcf::api
diff --git a/src/common/pcf.h b/src/common/pcf.h
index 2bf2861..caa8722 100644
--- a/src/common/pcf.h
+++ b/src/common/pcf.h
@@ -151,6 +151,4 @@ typedef struct nf_service_s {
 #define AMF_NUMBER_RETRIES 3
 #define UDM_NUMBER_RETRIES 3
 
-#define SM_POLICY_API_NAME "npcf-smpolicycontrol";
-
 #endif
diff --git a/src/oai_pcf/main.cpp b/src/oai_pcf/main.cpp
index 8dbb521..3419024 100644
--- a/src/oai_pcf/main.cpp
+++ b/src/oai_pcf/main.cpp
@@ -32,6 +32,7 @@ using namespace std;
 using namespace oai::pcf::app;
 using namespace oai::pcf::config;
 using namespace oai::utils;
+using namespace oai::pcf::api;
 
 using namespace oai::config;
 
diff --git a/src/pcf_app/pcf_nrf.cpp b/src/pcf_app/pcf_nrf.cpp
index 75f4444..24d861c 100644
--- a/src/pcf_app/pcf_nrf.cpp
+++ b/src/pcf_app/pcf_nrf.cpp
@@ -34,6 +34,7 @@
 #include "pcf_config.hpp"
 #include "pcf_client.hpp"
 #include "Snssai.h"
+#include "api_defs.h"
 
 #include <boost/uuid/random_generator.hpp>
 #include <boost/uuid/uuid_io.hpp>
@@ -80,8 +81,8 @@ void pcf_nrf::generate_pcf_profile() {
 
   // NF services
   nf_service_t nf_service        = {};
-  nf_service.service_instance_id = SM_POLICY_API_NAME;
-  nf_service.service_name        = SM_POLICY_API_NAME;
+  nf_service.service_instance_id = oai::pcf::api::sm_policies::API_NAME;
+  nf_service.service_name        = oai::pcf::api::sm_policies::API_NAME;
   nf_service_version_t version   = {};
   version.api_version_in_uri     = pcf_cfg->sbi.get_api_version();
   version.api_full_version       = "1.0.0";  // TODO: to be updated
-- 
GitLab