* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
* ubs-engine is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
#include "ubse_http_server.h"
#include <grp.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <securec.h>
#include <cerrno>
#include "ubse_common_def.h"
#include "ubse_conf_module.h"
#include "ubse_context.h"
#include "ubse_error.h"
#include "ubse_file_util.h"
#include "ubse_http_common.h"
#include "ubse_logger.h"
#include "ubse_net_util.h"
#include "ubse_security_module.h"
#include "adapter_plugins/mti/ubse_topology_interface.h"
#include "httplib.h"
#include "src/framework/misc/ubse_secure_buffer.h"
#include "src/include/cert/ubse_cert_def.h"
#include "src/include/cert/ubse_cert_validator.h"
namespace ubse::http {
UBSE_DEFINE_THIS_MODULE("ubse");
using namespace ubse::common::def;
using namespace ubse::log;
using namespace ubse::context;
using namespace ubse::config;
using namespace ubse::utils;
using namespace ubse::security;
std::unique_ptr<UbseHttpServer> UbseHttpServer::instance_ = nullptr;
std::once_flag UbseHttpServer::initInstanceFlag_;
constexpr uint32_t START_TIMEOUT = 3000;
constexpr uint32_t SLEEP_TIME = 100;
constexpr mode_t FOLDER_PEUBSEISSION = 0755;
constexpr size_t HTTP_HEADER_SEPARATOR_SIZE = 4;
bool SetSocketFilePermission()
{
std::string udsAddress = UBSE_UBM_UDS_ADDRESS;
uint32_t retryTime = 0;
while (access(udsAddress.c_str(), F_OK) == -1 && retryTime++ < START_TIMEOUT) {
std::this_thread::sleep_for(std::chrono::milliseconds(SLEEP_TIME));
retryTime += SLEEP_TIME;
}
struct group* grp = getgrnam(UBM_GROUP.c_str());
if (!grp) {
UBSE_LOG_ERROR << "Group " << UBM_GROUP << " not found.";
return false;
}
gid_t target_gid = grp->gr_gid;
if (chown(udsAddress.c_str(), -1, target_gid) != 0) {
UBSE_LOG_ERROR << "chown failed: " << strerror(errno);
return false;
}
constexpr mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
if (chmod(udsAddress.c_str(), mode) != 0) {
UBSE_LOG_ERROR << "chmod socket fail: " << strerror(errno);
return false;
}
UBSE_LOG_DEBUG << "Set socket file permission and group success.";
return true;
}
bool UbseHttpServer::Start(bool isTcpServer)
{
try {
if (isTcpServer) {
serverThread_ = std::thread(&UbseHttpServer::TcpRun, this);
} else {
serverThread_ = std::thread(&UbseHttpServer::UdsRun, this);
}
} catch (const std::system_error&) {
UBSE_LOG_ERROR << "Failed to create thread.";
return false;
}
uint32_t retryTime = 0;
while (retryTime <= START_TIMEOUT) {
if (server_ && server_->is_running()) {
return isTcpServer || SetSocketFilePermission();
}
retryTime += SLEEP_TIME;
std::this_thread::sleep_for(std::chrono::milliseconds(SLEEP_TIME));
}
return server_ && server_->is_running();
}
void UbseHttpServer::Stop()
{
if (server_) {
server_->stop();
}
if (serverThread_.joinable()) {
serverThread_.join();
}
std::string udsAddress = UBSE_UBM_UDS_ADDRESS;
if (unlink(udsAddress.c_str()) != 0 && errno != ENOENT) {
UBSE_LOG_ERROR << "Failed to delete UDS socket file: " << strerror(errno);
}
}
static void ProcessRequestHeadersAndParams(const httplib::Request& req, UbseHttpRequest& request)
{
for (const auto& pair : req.headers) {
if (request.headers.find(pair.first) == request.headers.end()) {
request.headers.emplace(pair.first, pair.second);
}
}
for (const auto& pair : req.params) {
if (request.params.find(pair.first) == request.params.end()) {
request.params.emplace(pair.first, pair.second);
}
}
}
std::string UbseHttpServer::GenerateQueryString(const std::multimap<std::string, std::string>& queryParams)
{
std::string queryString;
bool first = true;
for (const auto& param : queryParams) {
if (!first) {
queryString.append("&");
}
first = false;
queryString.append(param.first);
queryString.append("=");
queryString.append(param.second);
}
return queryString;
}
UbseResult UbseHttpServer::ValidateHttpRequest(const httplib::Request& req, UbseHttpRequest& request)
{
if (!req.headers.empty()) {
size_t headerSize = 0;
for (const auto& pair : req.headers) {
headerSize += pair.first.size() + pair.second.size() + HTTP_HEADER_SEPARATOR_SIZE;
}
if (headerSize > httpMaxHeaderSize) {
UBSE_LOG_ERROR << "HttpMsg headers is oversize";
return UBSE_HTTP_ERROR_MSG_OVERSIZE;
}
}
if (!req.params.empty()) {
std::string queryStr = GenerateQueryString(req.params);
if (queryStr.size() > httpMaxQuerySize) {
UBSE_LOG_ERROR << "HttpMsg queryParams is oversize";
return UBSE_HTTP_ERROR_MSG_OVERSIZE;
}
}
if (!req.body.empty() && req.body.size() > httpMaxBodySize) {
UBSE_LOG_ERROR << "HttpRequest BodyLen is oversize";
return UBSE_HTTP_ERROR_MSG_OVERSIZE;
}
request.method = req.method;
if (StringToUbseHttpMethod(request.method) == UbseHttpMethod::UBSE_HTTP_METHOD_INVALID) {
UBSE_LOG_ERROR << "The request method is not support";
return UBSE_ERROR;
}
return UBSE_OK;
}
void UbseHttpServer::HandleRequest(const httplib::Request& req, httplib::Response& res)
{
UBSE_LOG_INFO << "Receive request, uri=" << req.path << ", method=" << req.method;
UbseHttpRequest request{};
if (ValidateHttpRequest(req, request) != UBSE_OK) {
res.status = httplib::BadRequest_400;
res.set_content("The request is invalid.", "text/plain");
return;
}
request.path = req.path;
request.body = req.body;
ProcessRequestHeadersAndParams(req, request);
UbseHttpResponse response{};
std::string routeKey = req.method + req.path;
auto it = routes_.find(routeKey);
if (it == routes_.end()) {
UBSE_LOG_ERROR << "url=" << req.path << "has not been registered in tcp server.";
res.status = httplib::NotFound_404;
res.set_content("Not Found", "text/plain");
return;
}
it->second(request, response);
BuildResponse(res, response);
}
void UbseHttpServer::BuildResponse(httplib::Response& res, const UbseHttpResponse& response)
{
res.status = response.status;
for (const auto& header : response.headers) {
res.set_header(header.first, header.second);
}
res.body = response.body;
}
void UbseHttpServer::RegisterRoute(const std::string& path, const std::string& method, UbseHttpHandlerFunc handler)
{
std::string routeKey = method + path;
std::lock_guard<std::mutex> lock(routesMutex_);
auto it = routes_.find(routeKey);
if (it != routes_.end()) {
UBSE_LOG_WARN << "url=" << path << " has already been registered in server.";
return;
}
routes_[routeKey] = handler;
UBSE_LOG_INFO << "register method= " << method << ", url= " << path << " for http server.";
return;
}
void UbseHttpServer::GetTcpServerPort(uint32_t& port)
{
port = DEFAULT_TCP_SERVER_PORT;
auto module = UbseContext::GetInstance().GetModule<UbseConfModule>();
if (module == nullptr) {
UBSE_LOG_ERROR << "[HTTP] GetTcpServerPort GetModule failed.";
return;
}
UbseResult ret = module->GetConf<uint32_t>(UBSE_UBFM_SECTION, UBSE_HTTP_TCP_SERVER_PORT, port);
if (ret != UBSE_OK) {
UBSE_LOG_ERROR << "[HTTP] GetTcpServerPort get ubse.server.port failed, will use default value: "
<< DEFAULT_TCP_SERVER_PORT;
}
if (!UbseNetUtil::IsPortVaLid(port)) {
UBSE_LOG_ERROR << "ubse.server.port=" << port
<< " is out of range[1024, 65535], will use default value: " << DEFAULT_TCP_SERVER_PORT;
port = DEFAULT_TCP_SERVER_PORT;
}
}
std::unique_ptr<httplib::SSLServer> UbseHttpServer::CreateSslServer()
{
password = cert::UbseSslValidator::LoadPasswordFromFile(UbseSSLConfig::PasswordFile);
if (password.size() == 0) {
UBSE_LOG_ERROR << "Private key password not found.";
return nullptr;
}
auto sslServer = std::make_unique<httplib::SSLServer>(UbseSSLConfig::ServerCertFile, UbseSSLConfig::ServerKeyFile,
UbseSSLConfig::TrustCertFile, nullptr, password.c_str());
if (!sslServer || !sslServer->is_valid()) {
UBSE_LOG_ERROR << "Failed to initialize SSL server.";
return nullptr;
}
SSL_CTX* ctx = static_cast<SSL_CTX*>(sslServer->tls_context());
if (ctx == nullptr) {
UBSE_LOG_ERROR << "Failed to set SSL context for server";
return nullptr;
}
if (SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION) != 1) {
UBSE_LOG_ERROR << "Failed to set min protocol version: TLS1_3_VERSION";
return nullptr;
}
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr);
if (!cert::UbseSslValidator::ConfigureCrlValidation(ctx)) {
UBSE_LOG_ERROR << "Failed to configure CRL validation for server";
return nullptr;
}
sslServer->new_task_queue = []() -> httplib::ThreadPool* {
return new httplib::ThreadPool(NO_1, NO_8);
};
return sslServer;
}
void UbseHttpServer::TcpRun()
{
try {
server_ = CreateSslServer();
if (!server_) {
throw std::runtime_error("Failed to create SSL server.");
}
server_->Get("/.*", [this](const httplib::Request& req, httplib::Response& res) { HandleRequest(req, res); });
server_->Post("/.*", [this](const httplib::Request& req, httplib::Response& res) { HandleRequest(req, res); });
server_->Put("/.*", [this](const httplib::Request& req, httplib::Response& res) { HandleRequest(req, res); });
server_->Delete("/.*",
[this](const httplib::Request& req, httplib::Response& res) { HandleRequest(req, res); });
server_->Patch("/.*", [this](const httplib::Request& req, httplib::Response& res) { HandleRequest(req, res); });
uint32_t port = 0;
GetTcpServerPort(port);
UBSE_LOG_INFO << "TCP server start listening on port=" << port;
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
throw std::runtime_error("Failed to create socket.");
}
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
close(sockfd);
throw std::runtime_error("Port " + std::to_string(port) + " is already in use.");
}
close(sockfd);
std::string eid = "127.0.0.1";
server_->listen(eid, port);
} catch (const std::exception& e) {
UBSE_LOG_ERROR << "Error starting server=" << e.what();
}
}
void UbseHttpServer::UdsRun()
{
try {
server_ = std::make_unique<Server>();
server_->new_task_queue = []() -> httplib::ThreadPool* {
return new httplib::ThreadPool(NO_1, NO_8);
};
if (!server_) {
throw std::runtime_error("Failed to create SSL server.");
}
server_->Get("/.*", [this](const httplib::Request& req, httplib::Response& res) { HandleRequest(req, res); });
server_->Post("/.*", [this](const httplib::Request& req, httplib::Response& res) { HandleRequest(req, res); });
server_->Put("/.*", [this](const httplib::Request& req, httplib::Response& res) { HandleRequest(req, res); });
server_->Delete("/.*",
[this](const httplib::Request& req, httplib::Response& res) { HandleRequest(req, res); });
server_->Patch("/.*", [this](const httplib::Request& req, httplib::Response& res) { HandleRequest(req, res); });
std::string udsAddress = UBSE_UBM_UDS_ADDRESS;
const auto udsParentPath = GetParentDirectory(udsAddress);
std::vector<__u32> caps = {CAP_DAC_OVERRIDE};
UbseSecurityModule::ModifyEffectiveCapabilities(caps, true);
if (UbseFileUtil::CreateAndChmodDirectory(udsParentPath, FOLDER_PEUBSEISSION) != UBSE_OK) {
UBSE_LOG_ERROR << "Uds file directory= " << udsParentPath << " is unavailable.";
UbseSecurityModule::ModifyEffectiveCapabilities(caps, false);
return;
}
UbseSecurityModule::ModifyEffectiveCapabilities(caps, false);
auto res = unlink(udsAddress.c_str());
if (res != 0 && errno != ENOENT) {
UBSE_LOG_ERROR << "unlink uds file fail, errno= " << errno;
return;
}
bool ret = server_->set_address_family(AF_UNIX).listen(udsAddress, 80);
if (!ret) {
UBSE_LOG_ERROR << "start server fail";
return;
}
UBSE_LOG_INFO << "uds server stopped";
} catch (const std::exception& e) {
UBSE_LOG_ERROR << "ERROR starting server= " << e.what();
}
}
std::string UbseHttpServer::GetParentDirectory(const std::string& path)
{
if (path.empty()) {
return "";
}
size_t pos = path.find_last_of('/');
return (pos == std::string::npos) ? "" : path.substr(0, pos);
}
}