Skip to content

Commit 67615d8

Browse files
authored
Enable TLS for HTTP API and add endpoints (#32)
* add security policies for public access to API * load certificates from .pem files * add HTTP API extension * add memory endpoint * fix /memory/reset status code * update dependencies
1 parent a1368ed commit 67615d8

File tree

8 files changed

+309
-38
lines changed

8 files changed

+309
-38
lines changed

src/api.cpp

Lines changed: 194 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,21 @@
33
// GPL-3.0 License
44

55
#include "api.h"
6+
#include "mongoose.h"
67

78
#include <MicroOcpp/Debug.h>
9+
#include <MicroOcpp/Core/Memory.h>
10+
#include <MicroOcpp/Model/ConnectorBase/EvseId.h>
11+
#include <MicroOcpp/Model/Authorization/IdToken.h>
812

913
#include "evse.h"
1014

1115
//simple matching function; takes * as a wildcard
1216
bool str_match(const char *query, const char *pattern) {
1317
size_t qi = 0, pi = 0;
1418

15-
while (query[qi] && pattern[pi]) {
16-
if (query[qi] == pattern[pi]) {
19+
while (pattern[pi]) {
20+
if (query[qi] && query[qi] == pattern[pi]) {
1721
qi++;
1822
pi++;
1923
} else if (pattern[pi] == '*') {
@@ -155,3 +159,191 @@ int mocpp_api_call(const char *endpoint, MicroOcpp::Method method, const char *b
155159

156160
return status;
157161
}
162+
163+
int mocpp_api2_call(const char *uri_raw, size_t uri_raw_len, MicroOcpp::Method method, const char *query_raw, size_t query_raw_len, char *resp_body, size_t resp_body_size) {
164+
165+
snprintf(resp_body, resp_body_size, "%s", "");
166+
167+
struct mg_str uri = mg_str_n(uri_raw, uri_raw_len);
168+
struct mg_str query = mg_str_n(query_raw, query_raw_len);
169+
170+
int evse_id = -1;
171+
int connector_id = -1;
172+
173+
unsigned int num;
174+
struct mg_str evse_id_str = mg_http_var(query, mg_str("evse_id"));
175+
if (evse_id_str.buf) {
176+
if (!mg_str_to_num(evse_id_str, 10, &num, sizeof(num)) || num < 1 || num >= MO_NUM_EVSEID) {
177+
snprintf(resp_body, resp_body_size, "invalid connector_id");
178+
return 400;
179+
}
180+
evse_id = (int)num;
181+
}
182+
183+
struct mg_str connector_id_str = mg_http_var(query, mg_str("connector_id"));
184+
if (connector_id_str.buf) {
185+
if (!mg_str_to_num(connector_id_str, 10, &num, sizeof(num)) || num != 1) {
186+
snprintf(resp_body, resp_body_size, "invalid connector_id");
187+
return 400;
188+
}
189+
connector_id = (int)num;
190+
}
191+
192+
if (mg_match(uri, mg_str("/plugin"), NULL)) {
193+
if (method != MicroOcpp::Method::POST) {
194+
return 405;
195+
}
196+
if (evse_id < 0) {
197+
snprintf(resp_body, resp_body_size, "no action taken");
198+
return 200;
199+
} else {
200+
snprintf(resp_body, resp_body_size, "%s", connectors[evse_id-1].getEvPlugged() ? "EV already plugged" : "plugged in EV");
201+
connectors[evse_id-1].setEvPlugged(true);
202+
connectors[evse_id-1].setEvReady(true);
203+
connectors[evse_id-1].setEvseReady(true);
204+
return 200;
205+
}
206+
} else if (mg_match(uri, mg_str("/plugout"), NULL)) {
207+
if (method != MicroOcpp::Method::POST) {
208+
return 405;
209+
}
210+
if (evse_id < 0) {
211+
snprintf(resp_body, resp_body_size, "no action taken");
212+
return 200;
213+
} else {
214+
snprintf(resp_body, resp_body_size, "%s", connectors[evse_id-1].getEvPlugged() ? "EV already unplugged" : "unplug EV");
215+
connectors[evse_id-1].setEvPlugged(false);
216+
connectors[evse_id-1].setEvReady(false);
217+
connectors[evse_id-1].setEvseReady(false);
218+
return 200;
219+
}
220+
} else if (mg_match(uri, mg_str("/end"), NULL)) {
221+
if (method != MicroOcpp::Method::POST) {
222+
return 405;
223+
}
224+
bool trackEvReady = false;
225+
for (size_t i = 0; i < connectors.size(); i++) {
226+
trackEvReady |= connectors[i].getEvReady();
227+
connectors[i].setEvReady(false);
228+
}
229+
snprintf(resp_body, resp_body_size, "%s", trackEvReady ? "suspended EV" : "EV already suspended");
230+
return 200;
231+
} else if (mg_match(uri, mg_str("/state"), NULL)) {
232+
if (method != MicroOcpp::Method::POST) {
233+
return 405;
234+
}
235+
struct mg_str ready_str = mg_http_var(query, mg_str("ready"));
236+
bool ready = true;
237+
if (ready_str.buf) {
238+
if (mg_match(ready_str, mg_str("true"), NULL)) {
239+
ready = true;
240+
} else if (mg_match(ready_str, mg_str("false"), NULL)) {
241+
ready = false;
242+
} else {
243+
snprintf(resp_body, resp_body_size, "invalid ready");
244+
return 400;
245+
}
246+
}
247+
bool trackEvReady = false;
248+
for (size_t i = 0; i < connectors.size(); i++) {
249+
if (connectors[i].getEvPlugged()) {
250+
bool trackEvReady = connectors[i].getEvReady();
251+
connectors[i].setEvReady(ready);
252+
snprintf(resp_body, resp_body_size, "%s, %s", ready ? "EV suspended" : "EV not suspended", trackEvReady ? "suspended before" : "not suspended before");
253+
return 200;
254+
}
255+
}
256+
snprintf(resp_body, resp_body_size, "no action taken - EV not plugged");
257+
return 200;
258+
} else if (mg_match(uri, mg_str("/authorize"), NULL)) {
259+
if (method != MicroOcpp::Method::POST) {
260+
return 405;
261+
}
262+
struct mg_str id = mg_http_var(query, mg_str("id"));
263+
if (!id.buf) {
264+
snprintf(resp_body, resp_body_size, "missing id");
265+
return 400;
266+
}
267+
struct mg_str type = mg_http_var(query, mg_str("type"));
268+
if (!id.buf) {
269+
snprintf(resp_body, resp_body_size, "missing type");
270+
return 400;
271+
}
272+
273+
int ret;
274+
char id_buf [MO_IDTOKEN_LEN_MAX + 1];
275+
ret = snprintf(id_buf, sizeof(id_buf), "%.*s", (int)id.len, id.buf);
276+
if (ret < 0 || ret >= sizeof(id_buf)) {
277+
snprintf(resp_body, resp_body_size, "invalid id");
278+
return 400;
279+
}
280+
char type_buf [128];
281+
ret = snprintf(type_buf, sizeof(type_buf), "%.*s", (int)type.len, type.buf);
282+
if (ret < 0 || ret >= sizeof(type_buf)) {
283+
snprintf(resp_body, resp_body_size, "invalid type");
284+
return 400;
285+
}
286+
287+
if (evse_id <= 0) {
288+
snprintf(resp_body, resp_body_size, "invalid evse_id");
289+
return 400;
290+
}
291+
292+
bool trackAuthActive = connectors[evse_id-1].getSessionIdTag();
293+
294+
if (!connectors[evse_id-1].presentNfcTag(id_buf, type_buf)) {
295+
snprintf(resp_body, resp_body_size, "invalid id and / or type");
296+
return 400;
297+
}
298+
299+
bool authActive = connectors[evse_id-1].getSessionIdTag();
300+
301+
snprintf(resp_body, resp_body_size, "%s",
302+
!trackAuthActive && authActive ? "authorize in progress" :
303+
trackAuthActive && !authActive ? "unauthorize in progress" :
304+
trackAuthActive && authActive ? "no action taken (EVSE still authorized)" :
305+
"no action taken (EVSE not authorized)");
306+
307+
return 200;
308+
} else if (mg_match(uri, mg_str("/memory/info"), NULL)) {
309+
#if MO_OVERRIDE_ALLOCATION && MO_ENABLE_HEAP_PROFILER
310+
{
311+
if (method != MicroOcpp::Method::GET) {
312+
return 405;
313+
}
314+
315+
int ret = mo_mem_write_stats_json(resp_body, resp_body_size);
316+
if (ret < 0 || ret >= resp_body_size) {
317+
snprintf(resp_body, resp_body_size, "internal error");
318+
return 500;
319+
}
320+
321+
return 200;
322+
}
323+
#else
324+
{
325+
snprintf(resp_body, resp_body_size, "memory profiler disabled");
326+
return 404;
327+
}
328+
#endif
329+
} else if (mg_match(uri, mg_str("/memory/reset"), NULL)) {
330+
#if MO_OVERRIDE_ALLOCATION && MO_ENABLE_HEAP_PROFILER
331+
{
332+
if (method != MicroOcpp::Method::POST) {
333+
return 405;
334+
}
335+
336+
MO_MEM_RESET();
337+
return 200;
338+
}
339+
#else
340+
{
341+
snprintf(resp_body, resp_body_size, "memory profiler disabled");
342+
return 404;
343+
}
344+
#endif
345+
346+
}
347+
348+
return 404;
349+
}

src/api.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@ enum class Method {
1919

2020
int mocpp_api_call(const char *endpoint, MicroOcpp::Method method, const char *body, char *resp_body, size_t resp_body_size);
2121

22+
int mocpp_api2_call(const char *endpoint, size_t endpoint_len, MicroOcpp::Method method, const char *query, size_t query_len, char *resp_body, size_t resp_body_size);
23+
2224
#endif

src/evse.cpp

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <MicroOcpp/Model/Transactions/Transaction.h>
1010
#include <MicroOcpp/Model/Transactions/TransactionService.h>
1111
#include <MicroOcpp/Model/Variables/VariableService.h>
12+
#include <MicroOcpp/Model/Authorization/IdToken.h>
1213
#include <MicroOcpp/Operations/StatusNotification.h>
1314
#include <MicroOcpp/Version.h>
1415
#include <MicroOcpp/Debug.h>
@@ -146,41 +147,54 @@ void Evse::loop() {
146147

147148
}
148149

149-
void Evse::presentNfcTag(const char *uid_cstr) {
150-
if (!uid_cstr) {
150+
void Evse::presentNfcTag(const char *uid) {
151+
if (!uid) {
151152
MO_DBG_ERR("invalid argument");
152153
return;
153154
}
154-
std::string uid = uid_cstr;
155155

156156
#if MO_ENABLE_V201
157157
if (auto context = getOcppContext()) {
158158
if (context->getVersion().major == 2) {
159-
if (auto txService = context->getModel().getTransactionService()) {
160-
if (auto evse = txService->getEvse(connectorId)) {
161-
if (evse->getTransaction() && evse->getTransaction()->isAuthorizationActive) {
162-
evse->endAuthorization(MicroOcpp::IdToken(uid_cstr, MicroOcpp::IdToken::Type::KeyCode));
163-
} else {
164-
evse->beginAuthorization(MicroOcpp::IdToken(uid_cstr, MicroOcpp::IdToken::Type::KeyCode));
165-
}
166-
return;
167-
}
168-
}
159+
presentNfcTag(uid, "ISO14443");
160+
return;
169161
}
170162
}
171163
#endif
172164

173165
if (isTransactionActive(connectorId)) {
174-
if (!uid.compare(getTransactionIdTag(connectorId))) {
175-
endTransaction(uid.c_str(), "Local", connectorId);
166+
if (!strcmp(uid, getTransactionIdTag(connectorId))) {
167+
endTransaction(uid, "Local", connectorId);
176168
} else {
177169
MO_DBG_INFO("RFID card denied");
178170
}
179171
} else {
180-
beginTransaction(uid.c_str(), connectorId);
172+
beginTransaction(uid, connectorId);
181173
}
182174
}
183175

176+
#if MO_ENABLE_V201
177+
bool Evse::presentNfcTag(const char *uid, const char *type) {
178+
179+
MicroOcpp::IdToken idToken {nullptr, MicroOcpp::IdToken::Type::UNDEFINED, "Simulator"};
180+
if (!idToken.parseCstr(uid, type)) {
181+
return false;
182+
}
183+
184+
if (auto txService = getOcppContext()->getModel().getTransactionService()) {
185+
if (auto evse = txService->getEvse(connectorId)) {
186+
if (evse->getTransaction() && evse->getTransaction()->isAuthorizationActive) {
187+
evse->endAuthorization(idToken);
188+
} else {
189+
evse->beginAuthorization(idToken);
190+
}
191+
return true;
192+
}
193+
}
194+
return false;
195+
}
196+
#endif
197+
184198
void Evse::setEvPlugged(bool plugged) {
185199
if (!trackEvPluggedBool) return;
186200
trackEvPluggedBool->setBool(plugged);

src/evse.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <array>
99
#include <string>
1010
#include <MicroOcpp/Core/Configuration.h>
11+
#include <MicroOcpp/Version.h>
1112

1213
#define SIMULATOR_FN MO_FILENAME_PREFIX "simulator.jsn"
1314

@@ -41,6 +42,10 @@ class Evse {
4142

4243
void presentNfcTag(const char *uid);
4344

45+
#if MO_ENABLE_V201
46+
bool presentNfcTag(const char *uid, const char *type);
47+
#endif //MO_ENABLE_V201
48+
4449
void setEvPlugged(bool plugged);
4550

4651
bool getEvPlugged();

src/main.cpp

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include <MicroOcpp.h>
1111
#include <MicroOcpp/Core/Context.h>
12+
#include <MicroOcpp/Core/FilesystemUtils.h>
1213
#include "evse.h"
1314
#include "api.h"
1415

@@ -128,6 +129,10 @@ void app_loop() {
128129

129130
#if MO_NETLIB == MO_NETLIB_MONGOOSE
130131

132+
#ifndef MO_SIM_ENDPOINT_URL
133+
#define MO_SIM_ENDPOINT_URL "http://0.0.0.0:8000" //URL to forward to mg_http_listen(). Will be ignored if the URL field exists in api.jsn
134+
#endif
135+
131136
int main() {
132137

133138
#if MBEDTLS_PLATFORM_MEMORY
@@ -143,12 +148,23 @@ int main() {
143148
mg_log_set(MG_LL_INFO);
144149
mg_mgr_init(&mgr);
145150

146-
mg_http_listen(&mgr, "0.0.0.0:8000", http_serve, NULL); // Create listening connection
147-
148151
auto filesystem = MicroOcpp::makeDefaultFilesystemAdapter(MicroOcpp::FilesystemOpt::Use_Mount_FormatOnFail);
149152

150153
load_ocpp_version(filesystem);
151154

155+
struct mg_str api_cert = mg_file_read(&mg_fs_posix, MO_FILENAME_PREFIX "api_cert.pem");
156+
struct mg_str api_key = mg_file_read(&mg_fs_posix, MO_FILENAME_PREFIX "api_key.pem");
157+
158+
auto api_settings_doc = MicroOcpp::FilesystemUtils::loadJson(filesystem, MO_FILENAME_PREFIX "api.jsn", "Simulator");
159+
if (!api_settings_doc) {
160+
api_settings_doc = MicroOcpp::makeJsonDoc("Simulator", 0);
161+
}
162+
JsonObject api_settings = api_settings_doc->as<JsonObject>();
163+
164+
const char *api_url = api_settings["url"] | MO_SIM_ENDPOINT_URL;
165+
166+
mg_http_listen(&mgr, api_url, http_serve, (void*)api_url); // Create listening connection
167+
152168
osock = new MicroOcpp::MOcppMongooseClient(&mgr,
153169
"ws://echo.websocket.events",
154170
"charger-01",
@@ -160,7 +176,7 @@ int main() {
160176
MicroOcpp::ProtocolVersion{1,6}
161177
);
162178

163-
server_initialize(osock);
179+
server_initialize(osock, api_cert.buf ? api_cert.buf : "", api_key.buf ? api_key.buf : "", api_settings["user"] | "", api_settings["pass"] | "");
164180
app_setup(*osock, filesystem);
165181

166182
setOnResetExecute([] (bool isHard) {
@@ -191,6 +207,8 @@ int main() {
191207

192208
delete osock;
193209
mg_mgr_free(&mgr);
210+
free(api_cert.buf);
211+
free(api_key.buf);
194212
return 0;
195213
}
196214

0 commit comments

Comments
 (0)