Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "http_client.hpp"
|
||||
#include <fmt/core.h>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace module::main {
|
||||
const char* CONTENT_TYPE_HEADER = "Content-Type: application/json";
|
||||
|
||||
struct payloadInTransit {
|
||||
const std::string& data;
|
||||
size_t position;
|
||||
};
|
||||
|
||||
// Callback for receiving data, saves it into a string
|
||||
static size_t receive_data(char* ptr, size_t size, size_t nmemb, std::string* received_data) {
|
||||
received_data->append(ptr, size * nmemb);
|
||||
return size * nmemb;
|
||||
}
|
||||
|
||||
// Callback for sending data, fetches it from a string
|
||||
static size_t send_data(char* buffer, size_t size, size_t nitems, struct payloadInTransit* payload) {
|
||||
if (payload->position >= payload->data.length()) {
|
||||
// Returning 0 signals to libcurl that we have no more data to send
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Send up to size*nitems bytes of data
|
||||
size_t payload_remaining_bytes = payload->data.length() - payload->position;
|
||||
size_t num_bytes_to_send = std::min(size * nitems, payload_remaining_bytes);
|
||||
std::memcpy(buffer, payload->data.c_str() + payload->position, num_bytes_to_send);
|
||||
payload->position += num_bytes_to_send;
|
||||
return num_bytes_to_send;
|
||||
}
|
||||
|
||||
static HttpClientError client_error(const std::string& host, unsigned int port, const char* method,
|
||||
const std::string& path, const std::string& message) {
|
||||
return HttpClientError(fmt::format("HTTP client error on {} {}:{}{} : {} ", method, host, port, path, message));
|
||||
}
|
||||
|
||||
static void setup_connection(CURL* connection, struct payloadInTransit& request_payload, std::string& response_body,
|
||||
curl_slist*& headers, const int command_timeout_ms) {
|
||||
// Override the Content-Type header
|
||||
headers = curl_slist_append(nullptr, CONTENT_TYPE_HEADER);
|
||||
if (curl_easy_setopt(connection, CURLOPT_HTTPHEADER, headers) != CURLE_OK) {
|
||||
throw std::runtime_error(
|
||||
"libcurl signals that HTTP is unsupported. Your build or linkage might be misconfigured.");
|
||||
}
|
||||
|
||||
// Set up callbacks for reading and writing
|
||||
curl_easy_setopt(connection, CURLOPT_WRITEFUNCTION, receive_data);
|
||||
curl_easy_setopt(connection, CURLOPT_WRITEDATA, &response_body);
|
||||
curl_easy_setopt(connection, CURLOPT_READFUNCTION, send_data);
|
||||
curl_easy_setopt(connection, CURLOPT_READDATA, &request_payload);
|
||||
curl_easy_setopt(connection, CURLOPT_TIMEOUT_MS, command_timeout_ms);
|
||||
|
||||
// Misc. settings come here
|
||||
curl_easy_setopt(connection, CURLOPT_FORBID_REUSE, 1);
|
||||
if (curl_easy_setopt(connection, CURLOPT_FOLLOWLOCATION, 0) != CURLE_OK) {
|
||||
throw std::runtime_error(
|
||||
"libcurl signals that HTTP is unsupported. Your build or linkage might be misconfigured.");
|
||||
}
|
||||
}
|
||||
|
||||
static void setup_libcurl_tls_options_for_connection(CURL* connection, struct curl_blob* dcbm_cert) {
|
||||
// Since the LEM DCBM uses a certificate of only 1024bit, we need to lower the security level
|
||||
curl_easy_setopt(connection, CURLOPT_SSL_CIPHER_LIST, "DEFAULT:@SECLEVEL=1");
|
||||
// However, we still want to enforce TLS 1.2 or higher
|
||||
if (curl_easy_setopt(connection, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_TLSv1_3) !=
|
||||
CURLE_OK) {
|
||||
throw std::runtime_error("Failed to set CURLOPT_SSLVERSION. Is libcurl built with TLS support?");
|
||||
}
|
||||
|
||||
// We do not want to verify the hostname
|
||||
if (curl_easy_setopt(connection, CURLOPT_SSL_VERIFYHOST, 0) != CURLE_OK) {
|
||||
throw std::runtime_error("Failed to set CURLOPT_SSL_VERIFYHOST. Is libcurl built with TLS support?");
|
||||
}
|
||||
// We do want to verify the peer's certificate
|
||||
if (curl_easy_setopt(connection, CURLOPT_SSL_VERIFYPEER, 1) != CURLE_OK) {
|
||||
throw std::runtime_error("Failed to set CURLOPT_SSL_VERIFYPEER. Is libcurl built with TLS support?");
|
||||
}
|
||||
// We do not want to use OCSP
|
||||
// Whether this option is supported or not depends on the SSL backend, so we don't check the error code here.
|
||||
curl_easy_setopt(connection, CURLOPT_SSL_VERIFYSTATUS, 0);
|
||||
|
||||
// Now pass the DCBM certificate to libcurl
|
||||
if (curl_easy_setopt(connection, CURLOPT_CAINFO_BLOB, dcbm_cert) != CURLE_OK) {
|
||||
throw std::runtime_error("Failed to set CURLOPT_CAINFO_BLOB, possibly due to running out of memory.");
|
||||
}
|
||||
}
|
||||
|
||||
// Note: method_name and path are only there for the error message
|
||||
HttpResponse HttpClient::perform_request(CURL* connection, const std::string& request_body, const char* method_name,
|
||||
const std::string& path) const {
|
||||
// give curl a buffer to write its error messages to
|
||||
char curl_error_message[CURL_ERROR_SIZE] = {};
|
||||
curl_easy_setopt(connection, CURLOPT_ERRORBUFFER, curl_error_message);
|
||||
|
||||
// set up the connection options
|
||||
std::string response_body;
|
||||
struct payloadInTransit request_payload {
|
||||
request_body, 0
|
||||
};
|
||||
struct curl_slist* headers;
|
||||
setup_connection(connection, request_payload, response_body, headers, command_timeout_ms);
|
||||
|
||||
// Set up TLS options if TLS is enabled
|
||||
// we define dcbm_cert outside the "if" statement to ensure it outlives curl_easy_perform().
|
||||
struct curl_blob dcbm_cert {
|
||||
(void*)this->dcbm_tls_certificate.c_str(), this->dcbm_tls_certificate.size(),
|
||||
CURL_BLOB_NOCOPY // curl does not need to copy the cert, since it's not in a temporary location
|
||||
};
|
||||
if (this->tls_enabled) {
|
||||
setup_libcurl_tls_options_for_connection(connection, &dcbm_cert);
|
||||
}
|
||||
|
||||
// perform the request
|
||||
CURLcode res = curl_easy_perform(connection);
|
||||
|
||||
// remember to free the headers list...
|
||||
curl_slist_free_all(headers);
|
||||
// check the result of the request and return
|
||||
if (res == CURLE_OK) {
|
||||
long response_code;
|
||||
curl_easy_getinfo(connection, CURLINFO_RESPONSE_CODE, &response_code);
|
||||
return HttpResponse{(unsigned int)response_code, std::move(response_body)};
|
||||
} else {
|
||||
throw client_error(this->host, this->port, method_name, path, std::string(curl_error_message));
|
||||
}
|
||||
}
|
||||
|
||||
CURL* HttpClient::create_curl_handle_and_setup_url(const std::string& path) const {
|
||||
CURL* connection = curl_easy_init();
|
||||
if (!connection) {
|
||||
throw std::runtime_error("Could not create a CURL handle: curl_easy_init() returned null");
|
||||
}
|
||||
const char* protocol = this->tls_enabled ? "https" : "http";
|
||||
if (curl_easy_setopt(connection, CURLOPT_URL,
|
||||
fmt::format("{}://{}:{}{}", protocol, this->host, this->port, path).c_str()) != CURLE_OK) {
|
||||
throw std::runtime_error("Could not set CURLOPT_URL, likely ran out of memory");
|
||||
}
|
||||
if (curl_easy_setopt(connection, CURLOPT_PROTOCOLS_STR, protocol) != CURLE_OK) {
|
||||
throw std::runtime_error(std::string("Could not set supported protocol to ") + protocol +
|
||||
", is it enabled in libcurl?");
|
||||
}
|
||||
if (!this->network_interface.empty()) {
|
||||
if (curl_easy_setopt(connection, CURLOPT_INTERFACE, this->network_interface.c_str()) != CURLE_OK) {
|
||||
throw std::runtime_error("Could not bind to the specified network interface: " + this->network_interface);
|
||||
}
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
HttpResponse HttpClient::get(const std::string& path) const {
|
||||
CURL* connection = this->create_curl_handle_and_setup_url(path);
|
||||
|
||||
if (curl_easy_setopt(connection, CURLOPT_HTTPGET, 1) != CURLE_OK) {
|
||||
curl_easy_cleanup(connection);
|
||||
throw std::runtime_error(
|
||||
"libcurl signals that HTTP is unsupported. Your build or linkage might be misconfigured.");
|
||||
}
|
||||
|
||||
// perform_request() does not cleanup the connection on its own.
|
||||
// We do the cleanup here, and make sure to rethrow any exception that might've occurred.
|
||||
try {
|
||||
HttpResponse response = perform_request(connection, "", "GET", path);
|
||||
curl_easy_cleanup(connection);
|
||||
return response;
|
||||
} catch (std::exception& e) {
|
||||
curl_easy_cleanup(connection);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse HttpClient::put(const std::string& path, const std::string& body) const {
|
||||
CURL* connection = this->create_curl_handle_and_setup_url(path);
|
||||
|
||||
curl_easy_setopt(connection, CURLOPT_UPLOAD, 1);
|
||||
|
||||
// perform_request() does not cleanup the connection on its own.
|
||||
// We do the cleanup here, and make sure to rethrow any exception that might've occurred.
|
||||
try {
|
||||
HttpResponse response = perform_request(connection, body, "PUT", path);
|
||||
curl_easy_cleanup(connection);
|
||||
return response;
|
||||
} catch (std::exception& e) {
|
||||
curl_easy_cleanup(connection);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse HttpClient::post(const std::string& path, const std::string& body) const {
|
||||
CURL* connection = this->create_curl_handle_and_setup_url(path);
|
||||
|
||||
if (curl_easy_setopt(connection, CURLOPT_POST, 1) != CURLE_OK) {
|
||||
curl_easy_cleanup(connection);
|
||||
throw std::runtime_error(
|
||||
"libcurl signals that HTTP is unsupported. Your build or linkage might be misconfigured.");
|
||||
}
|
||||
|
||||
// perform_request() does not cleanup the connection on its own.
|
||||
// We do the cleanup here, and make sure to rethrow any exception that might've occurred.
|
||||
try {
|
||||
HttpResponse response = perform_request(connection, body, "POST", path);
|
||||
curl_easy_cleanup(connection);
|
||||
return response;
|
||||
} catch (std::exception& e) {
|
||||
curl_easy_cleanup(connection);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
} // namespace module::main
|
||||
Reference in New Issue
Block a user