OpenCPN Partial API docs
Loading...
Searching...
No Matches
rest_server.cpp
Go to the documentation of this file.
1/**************************************************************************
2 * Copyright (C) 2022 David Register *
3 * Copyright (C) 2022-2023 Alec Leamas *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 **************************************************************************/
20
26#include <memory>
27#include <mutex>
28#include <string>
29#include <utility>
30#include <vector>
31
32#include <wx/event.h>
33#include <wx/filename.h>
34#include <wx/log.h>
35#include <wx/string.h>
36
37#include "config.h"
38
39#include "model/config_vars.h"
41#include "model/logger.h"
42#include "model/nav_object_database.h"
43#include "model/ocpn_utils.h"
44#include "model/pincode.h"
45#include "model/rest_server.h"
46#include "model/routeman.h"
47
48#include "mongoose.h"
49#include "observable_evt.h"
50#include "model/navobj_db.h"
51
54
55using namespace std::chrono_literals;
56
57static const char* const kHttpAddr = "http://0.0.0.0:8000";
58static const char* const kHttpsAddr = "http://0.0.0.0:8443";
59
60static const char* const kHttpPortableAddr = "http://0.0.0.0:8001";
61static const char* const kHttpsPortableAddr = "http://0.0.0.0:8444";
62static const char* const kVersionReply = R"--({ "version": "@version@" })--";
63static const char* const kListRoutesReply =
64 R"--( { "version": "@version@", "routes": "@routes@" })--";
65
67enum { ORS_START_OF_SESSION, ORS_CHUNK_N, ORS_CHUNK_LAST };
68
70 const enum class Cmd {
71 Ping,
72 Object,
73 CheckWrite,
74 ListRoutes,
75 ActivateRoute,
76 ReverseRoute,
78 } cmd;
79 const std::string api_key;
80 const std::string source;
81 const std::string id;
82 const bool force;
83 const bool activate;
84
86 const std::string payload;
87
89 static RestIoEvtData CreateCmdData(const std::string& key,
90 const std::string& src,
91 const std::string& gpx_data, bool _force,
92 bool _activate) {
93 return RestIoEvtData(Cmd::Object, key, src, gpx_data, "", _force,
94 _activate);
95 }
96
98 static RestIoEvtData CreatePingData(const std::string& key,
99 const std::string& src) {
100 return {Cmd::Ping, key, src, "", ""};
101 }
102
104 static RestIoEvtData CreateChkWriteData(const std::string& key,
105 const std::string& src,
106 const std::string& guid) {
107 return {Cmd::CheckWrite, key, src, "", guid};
108 }
109
111 static RestIoEvtData CreateListRoutesData(const std::string& key,
112 const std::string& src) {
113 return {Cmd::ListRoutes, key, src, "", ""};
114 }
115
116 static RestIoEvtData CreateActivateRouteData(const std::string& key,
117 const std::string& src,
118 const std::string& guid) {
119 return {Cmd::ActivateRoute, key, src, guid, ""};
120 }
121
122 static RestIoEvtData CreatePluginMsgData(const std::string& key,
123 const std::string& src,
124 const std::string& id,
125 const std::string& msg) {
126 return {Cmd::PluginMsg, key, src, msg, id};
127 }
128
129 static RestIoEvtData CreateReverseRouteData(const std::string& key,
130 const std::string& src,
131 const std::string& guid) {
132 return {Cmd::ReverseRoute, key, src, guid, ""};
133 }
134
135private:
136 RestIoEvtData(Cmd c, std::string key, std::string src, std::string _payload,
137 std::string _id, bool _force, bool _activate);
138 RestIoEvtData(Cmd c, std::string key, std::string src, std::string _payload,
139 std::string id)
140 : RestIoEvtData(c, key, src, _payload, id, false, false) {}
141};
142
144static inline std::string HttpVarToString(const struct mg_str& query,
145 const char* var) {
146 std::string string;
147 struct mg_str mgs = mg_http_var(query, mg_str(var));
148 if (mgs.len && mgs.ptr) string = std::string(mgs.ptr, mgs.len);
149 return string;
150}
151
152static void PostEvent(RestServer* parent,
153 const std::shared_ptr<RestIoEvtData>& evt_data, int id) {
154 auto evt = new ObservedEvt(REST_IO_EVT, id);
155 evt->SetSharedPtr(evt_data);
156 parent->QueueEvent(evt);
157 if (!dynamic_cast<wxApp*>(wxAppConsole::GetInstance())) {
158 wxTheApp->ProcessPendingEvents();
159 }
160}
161
162static void HandleRxObject(struct mg_connection* c, struct mg_http_message* hm,
163 RestServer* parent) {
164 int MID = ORS_CHUNK_N;
165
166 std::string api_key = HttpVarToString(hm->query, "apikey");
167 std::string source = HttpVarToString(hm->query, "source");
168 std::string force = HttpVarToString(hm->query, "force");
169 std::string activate = HttpVarToString(hm->query, "activate");
170 std::string xml_content;
171 if (hm->chunk.len)
172 xml_content = std::string(hm->chunk.ptr, hm->chunk.len);
173 else {
174 MID = ORS_CHUNK_LAST;
175 }
176 mg_http_delete_chunk(c, hm);
177 parent->UpdateReturnStatus(RestServerResult::Void);
178
179 if (!source.empty()) {
180 assert(parent && "Null parent pointer");
181 auto data_ptr =
182 std::make_shared<RestIoEvtData>(RestIoEvtData::CreateCmdData(
183 api_key, source, xml_content, !force.empty(), !activate.empty()));
184 PostEvent(parent, data_ptr, MID);
185 }
186 if (MID == ORS_CHUNK_LAST) {
187 std::unique_lock<std::mutex> lock{parent->ret_mutex};
188 bool r = parent->return_status_cv.wait_for(lock, 10s, [&] {
189 return parent->GetReturnStatus() != RestServerResult::Void;
190 });
191 if (!r) wxLogWarning("Timeout waiting for REST server condition");
192 mg_http_reply(c, 200, "", "{\"result\": %d}\n", parent->GetReturnStatus());
193 }
194}
195
196static void HandlePing(struct mg_connection* c, struct mg_http_message* hm,
197 RestServer* parent) {
198 std::string api_key = HttpVarToString(hm->query, "apikey");
199 std::string source = HttpVarToString(hm->query, "source");
200 if (!source.empty()) {
201 assert(parent && "Null parent pointer");
202 parent->UpdateReturnStatus(RestServerResult::Void);
203 auto data_ptr = std::make_shared<RestIoEvtData>(
204 RestIoEvtData::CreatePingData(api_key, source));
205 PostEvent(parent, data_ptr, ORS_CHUNK_LAST);
206 std::unique_lock<std::mutex> lock{parent->ret_mutex};
207 bool r = parent->return_status_cv.wait_for(lock, 10s, [&] {
208 return parent->GetReturnStatus() != RestServerResult::Void;
209 });
210 if (!r) wxLogWarning("Timeout waiting for REST server condition");
211 }
212 mg_http_reply(c, 200, "", "{\"result\": %d, \"version\": \"%s\"}\n",
213 parent->GetReturnStatus(), VERSION_FULL);
214}
215
216static void HandleWritable(struct mg_connection* c, struct mg_http_message* hm,
217 RestServer* parent) {
218 std::string apikey = HttpVarToString(hm->query, "apikey");
219 std::string source = HttpVarToString(hm->query, "source");
220 std::string guid = HttpVarToString(hm->query, "guid");
221 if (!source.empty()) {
222 assert(parent && "Null parent pointer");
223 parent->UpdateReturnStatus(RestServerResult::Void);
224 auto data_ptr = std::make_shared<RestIoEvtData>(
225 RestIoEvtData::CreateChkWriteData(apikey, source, guid));
226 PostEvent(parent, data_ptr, ORS_CHUNK_LAST);
227 std::unique_lock<std::mutex> lock{parent->ret_mutex};
228 bool r = parent->return_status_cv.wait_for(lock, 10s, [&] {
229 return parent->GetReturnStatus() != RestServerResult::Void;
230 });
231 if (!r) wxLogWarning("Timeout waiting for REST server condition");
232 }
233 mg_http_reply(c, 200, "", "{\"result\": %d}\n", parent->GetReturnStatus());
234}
235
236static void HandleListRoutes(struct mg_connection* c,
237 struct mg_http_message* hm, RestServer* parent) {
238 std::string apikey = HttpVarToString(hm->query, "apikey");
239 std::string source = HttpVarToString(hm->query, "source");
240 if (!source.empty()) {
241 assert(parent && "Null parent pointer");
242 parent->UpdateReturnStatus(RestServerResult::Void);
243 auto data_ptr = std::make_shared<RestIoEvtData>(
245 PostEvent(parent, data_ptr, ORS_CHUNK_LAST);
246 std::unique_lock<std::mutex> lock{parent->ret_mutex};
247 bool r = parent->return_status_cv.wait_for(lock, 10s, [&] {
248 return parent->GetReturnStatus() != RestServerResult::Void;
249 });
250 if (!r) wxLogWarning("Timeout waiting for REST server condition");
251 }
252 mg_http_reply(c, 200, "", parent->m_reply_body.c_str());
253}
254
255static void HandleActivateRoute(struct mg_connection* c,
256 struct mg_http_message* hm,
257 RestServer* parent) {
258 std::string apikey = HttpVarToString(hm->query, "apikey");
259 std::string source = HttpVarToString(hm->query, "source");
260 std::string guid = HttpVarToString(hm->query, "guid");
261 if (!source.empty()) {
262 assert(parent && "Null parent pointer");
263 parent->UpdateReturnStatus(RestServerResult::Void);
264 auto data_ptr = std::make_shared<RestIoEvtData>(
265 RestIoEvtData::CreateActivateRouteData(apikey, source, guid));
266 PostEvent(parent, data_ptr, ORS_CHUNK_LAST);
267 std::unique_lock<std::mutex> lock{parent->ret_mutex};
268 bool r = parent->return_status_cv.wait_for(lock, 10s, [&] {
269 return parent->GetReturnStatus() != RestServerResult::Void;
270 });
271 if (!r) wxLogWarning("Timeout waiting for REST server condition");
272 }
273 mg_http_reply(c, 200, "", "{\"result\": %d}\n", parent->GetReturnStatus());
274}
275
276static void HandlePluginMsg(struct mg_connection* c, struct mg_http_message* hm,
277 RestServer* parent) {
278 std::string apikey = HttpVarToString(hm->query, "apikey");
279 std::string source = HttpVarToString(hm->query, "source");
280 std::string id = HttpVarToString(hm->query, "id");
281 std::string message = HttpVarToString(hm->query, "message");
282 int chunk_status = hm->chunk.len ? ORS_CHUNK_N : ORS_CHUNK_LAST;
283 std::string content;
284 if (hm->chunk.len) content = std::string(hm->chunk.ptr, hm->chunk.len);
285 mg_http_delete_chunk(c, hm);
286 if (!source.empty()) {
287 assert(parent && "Null parent pointer");
288 parent->UpdateReturnStatus(RestServerResult::Void);
289 auto data_ptr = std::make_shared<RestIoEvtData>(
290 RestIoEvtData::CreatePluginMsgData(apikey, source, id, content));
291 PostEvent(parent, data_ptr, chunk_status);
292 if (chunk_status == ORS_CHUNK_LAST) {
293 std::unique_lock<std::mutex> lock{parent->ret_mutex};
294 bool r = parent->return_status_cv.wait_for(lock, 10s, [&] {
295 return parent->GetReturnStatus() != RestServerResult::Void;
296 });
297 if (!r) wxLogWarning("Timeout waiting for REST server condition");
298 mg_http_reply(c, 200, "", "{\"result\": %d}\n",
299 parent->GetReturnStatus());
300 }
301 }
302}
303
304static void HandleReverseRoute(struct mg_connection* c,
305 struct mg_http_message* hm, RestServer* parent) {
306 std::string apikey = HttpVarToString(hm->query, "apikey");
307 std::string source = HttpVarToString(hm->query, "source");
308 std::string guid = HttpVarToString(hm->query, "guid");
309 if (!source.empty()) {
310 assert(parent && "Null parent pointer");
311 parent->UpdateReturnStatus(RestServerResult::Void);
312 auto data_ptr = std::make_shared<RestIoEvtData>(
313 RestIoEvtData::CreateReverseRouteData(apikey, source, guid));
314 PostEvent(parent, data_ptr, ORS_CHUNK_LAST);
315 std::unique_lock<std::mutex> lock{parent->ret_mutex};
316 bool r = parent->return_status_cv.wait_for(lock, 10s, [&] {
317 return parent->GetReturnStatus() != RestServerResult::Void;
318 });
319 if (!r) wxLogWarning("Timeout waiting for REST server condition");
320 }
321 mg_http_reply(c, 200, "", "{\"result\": %d}\n", parent->GetReturnStatus());
322}
323
324// We use the same event handler function for HTTP and HTTPS connections
325// fn_data is NULL for plain HTTP, and non-NULL for HTTPS
326static void fn(struct mg_connection* c, int ev, void* ev_data, void* fn_data) {
327 auto parent = static_cast<RestServer*>(fn_data);
328
329 if (ev == MG_EV_ACCEPT /*&& fn_data != NULL*/) {
330 struct mg_tls_opts opts = {0};
331 opts.ca = nullptr; // "cert.pem" Uncomment to enable two-way SSL
332 opts.cert = parent->m_cert_file.c_str();
333 opts.certkey = parent->m_key_file.c_str();
334 opts.ciphers = nullptr;
335 mg_tls_init(c, &opts);
336 } else if (ev == MG_EV_TLS_HS) { // Think of this as "start of session"
337 PostEvent(parent, nullptr, ORS_START_OF_SESSION);
338 } else if (ev == MG_EV_HTTP_CHUNK) {
339 auto hm = (struct mg_http_message*)ev_data;
340 if (mg_http_match_uri(hm, "/api/ping")) {
341 HandlePing(c, hm, parent);
342 } else if (mg_http_match_uri(hm, "/api/rx_object")) {
343 HandleRxObject(c, hm, parent);
344 } else if (mg_http_match_uri(hm, "/api/writable")) {
345 HandleWritable(c, hm, parent);
346 } else if (mg_http_match_uri(hm, "/api/get-version")) {
347 std::string reply(kVersionReply);
348 ocpn::replace(reply, "@version@", PACKAGE_VERSION);
349 mg_http_reply(c, 200, "", reply.c_str());
350 } else if (mg_http_match_uri(hm, "/api/list-routes")) {
351 HandleListRoutes(c, hm, parent);
352 } else if (mg_http_match_uri(hm, "/api/activate-route")) {
353 HandleActivateRoute(c, hm, parent);
354 } else if (mg_http_match_uri(hm, "/api/reverse-route")) {
355 HandleReverseRoute(c, hm, parent);
356 } else if (mg_http_match_uri(hm, "/api/plugin-msg")) {
357 HandlePluginMsg(c, hm, parent);
358 } else {
359 mg_http_reply(c, 404, "", "url: not found\n");
360 }
361 }
362}
363
364std::string RestResultText(RestServerResult result) {
365 wxString s;
366 switch (result) {
367 case RestServerResult::NoError:
368 s = _("No error");
369 case RestServerResult::GenericError:
370 s = _("Server Generic Error");
371 case RestServerResult::ObjectRejected:
372 s = _("Peer rejected object");
373 case RestServerResult::DuplicateRejected:
374 s = _("Peer rejected duplicate object");
375 case RestServerResult::RouteInsertError:
376 s = _("Peer internal error (insert)");
377 case RestServerResult::NewPinRequested:
378 s = _("Peer requests new pincode");
379 case RestServerResult::ObjectParseError:
380 s = _("XML parse error");
381 case RestServerResult::Void:
382 s = _("Unspecified error");
383 }
384 return s.ToStdString();
385}
386
387//========================================================================
388/* RestServer implementation */
389
390RestServer::IoThread::IoThread(RestServer& parent, std::string ip)
391 : run_flag(-1), m_parent(parent), m_server_ip(std::move(ip)) {}
392
393void RestServer::IoThread::Run() {
394 run_flag = 1;
395 struct mg_mgr mgr = {0}; // Event manager
396 mg_log_set(MG_LL_DEBUG); // Set log level
397 mg_mgr_init(&mgr); // Initialise event manager
398
399 // Create HTTPS listener
400 MESSAGE_LOG << "Listening on " << m_server_ip << "\n";
401 mg_http_listen(&mgr, m_server_ip.c_str(), fn, &m_parent);
402
403 while (run_flag > 0) mg_mgr_poll(&mgr, 200); // Infinite event loop
404 mg_mgr_free(&mgr);
405 run_flag = -1;
406 m_parent.m_exit_sem.Post();
407}
408
409void RestServer::IoThread::Stop() { run_flag = 0; }
410
411bool RestServer::IoThread::WaitUntilStopped() {
412 auto r = m_parent.m_exit_sem.WaitTimeout(10000);
413 if (r != wxSEMA_NO_ERROR) {
414 WARNING_LOG << "Semaphore error: " << r;
415 }
416 return r == wxSEMA_NO_ERROR;
417}
418
419RestServer::Apikeys RestServer::Apikeys::Parse(const std::string& s) {
420 Apikeys apikeys;
421 auto ip_keys = ocpn::split(s.c_str(), ";");
422 for (const auto& ip_key : ip_keys) {
423 auto words = ocpn::split(ip_key.c_str(), ":");
424 if (words.size() != 2) continue;
425 if (apikeys.find(words[0]) == apikeys.end()) {
426 apikeys[words[0]] = words[1];
427 }
428 }
429 return apikeys;
430}
431
432std::string RestServer::Apikeys::ToString() const {
433 std::stringstream ss;
434 for (const auto& it : *this) ss << it.first << ":" << it.second << ";";
435 return ss.str();
436}
437
438void RestServer::UpdateReturnStatus(RestServerResult result) {
439 {
440 std::lock_guard<std::mutex> lock{ret_mutex};
441 return_status = result;
442 }
443 return_status_cv.notify_one();
444}
445
446RestServer::RestServer(RestServerDlgCtx ctx, RouteCtx route_ctx, bool& portable)
447 : m_exit_sem(0, 1),
448 m_endpoint(portable ? kHttpsPortableAddr : kHttpsAddr),
449 m_dlg_ctx(std::move(ctx)),
450 m_route_ctx(std::move(route_ctx)),
451 return_status(RestServerResult::Void),
452 m_pin_dialog(nullptr),
453 m_overwrite(false),
454 m_io_thread(*this, m_endpoint),
455 m_pincode(Pincode::Create()) {
456 // Prepare the wxEventHandler to accept events from the io thread
457 Bind(REST_IO_EVT, &RestServer::HandleServerMessage, this);
458}
459
460RestServer::~RestServer() { StopServer(); }
461
462bool RestServer::StartServer(const fs::path& certificate_location) {
463 m_certificate_directory = certificate_location.string();
464 m_cert_file = (certificate_location / "cert.pem").string();
465 m_key_file = (certificate_location / "key.pem").string();
466
467 // Load persistent config info and kick off the Server thread
468 LoadConfig();
469 if (!m_std_thread.joinable()) {
470 m_std_thread = std::thread([&]() { m_io_thread.Run(); });
471 }
472 return true;
473}
474
476 wxLogDebug("Stopping REST service");
477 // Kill off the IO Thread if alive
478 if (m_std_thread.joinable()) {
479 wxLogDebug("Stopping io thread");
480 m_io_thread.Stop();
481 m_io_thread.WaitUntilStopped();
482 m_std_thread.join();
483 }
484}
485
486bool RestServer::LoadConfig() {
487 TheBaseConfig()->SetPath("/Settings/RestServer");
488 wxString key_string;
489 TheBaseConfig()->Read("ServerKeys", &key_string);
490 m_key_map = Apikeys::Parse(key_string.ToStdString());
491 TheBaseConfig()->Read("ServerOverwriteDuplicates", &m_overwrite, false);
492 return true;
493}
494
495bool RestServer::SaveConfig() {
496 TheBaseConfig()->SetPath("/Settings/RestServer");
497 TheBaseConfig()->Write("ServerKeys", wxString(m_key_map.ToString()));
498 TheBaseConfig()->Write("ServerOverwriteDuplicates", m_overwrite);
499 TheBaseConfig()->Flush();
500 return true;
501}
502
503bool RestServer::CheckApiKey(const RestIoEvtData& evt_data) {
504 // Look up the api key in the hash map. If found, we are done.
505 if (m_key_map.find(evt_data.source) != m_key_map.end()) {
506 if (m_key_map[evt_data.source] == evt_data.api_key) return true;
507 }
508 // Need a new PIN confirmation, add it to map and persist
509 m_pincode = Pincode::Create();
510 std::string new_api_key = m_pincode.Hash();
511 if (evt_data.api_key.size() < 10) // client sends old-style keys
512 new_api_key = m_pincode.CompatHash();
513
514 m_key_map[evt_data.source] = new_api_key;
515 SaveConfig();
516
517 std::stringstream ss;
518 ss << evt_data.source << " " << _("wants to send you new data.") << "\n"
519 << _("Please enter the following PIN number on ") << evt_data.source << " "
520 << _("to pair with this device") << "\n";
521 if (m_pin_dialog) m_pin_dialog->Destroy();
522 m_pin_dialog = m_dlg_ctx.run_pincode_dlg(ss.str(), m_pincode.ToString());
523
524 return false;
525}
526
527void RestServer::HandleServerMessage(ObservedEvt& event) {
528 auto evt_data = UnpackEvtPointer<RestIoEvtData>(event);
529 m_reply_body = "";
530 switch (event.GetId()) {
531 case ORS_START_OF_SESSION:
532 // Prepare a temp file to catch chuncks that might follow
533 m_upload_path = wxFileName::CreateTempFileName("ocpn_tul").ToStdString();
534
535 m_ul_stream.open(m_upload_path.c_str(), std::ios::out | std::ios::trunc);
536 if (!m_ul_stream.is_open()) {
537 wxLogMessage("REST_server: Cannot open %s for write", m_upload_path);
538 m_upload_path.clear(); // reset for next time.
539 return;
540 }
541 return;
542 case ORS_CHUNK_N:
543 // Stream out to temp file
544 if (!m_upload_path.empty() && m_ul_stream.is_open()) {
545 m_ul_stream.write(evt_data->payload.c_str(), evt_data->payload.size());
546 }
547 return;
548 case ORS_CHUNK_LAST:
549 // Cancel existing dialog and close temp file
550 if (m_pin_dialog) wxQueueEvent(m_pin_dialog, new wxCloseEvent);
551 if (!m_upload_path.empty() && m_ul_stream.is_open()) m_ul_stream.close();
552 break;
553 }
554
555 if (!CheckApiKey(*evt_data)) {
556 UpdateReturnStatus(RestServerResult::NewPinRequested);
557 return;
558 }
559
560 switch (evt_data->cmd) {
561 case RestIoEvtData::Cmd::Ping:
562 UpdateReturnStatus(RestServerResult::NoError);
563 return;
564 case RestIoEvtData::Cmd::CheckWrite: {
565 auto guid = evt_data->payload;
566 auto dup = m_route_ctx.find_route_by_guid(guid);
567 if (!dup || evt_data->force || m_overwrite) {
568 UpdateReturnStatus(RestServerResult::NoError);
569 } else {
570 UpdateReturnStatus(RestServerResult::DuplicateRejected);
571 }
572 return;
573 }
574 case RestIoEvtData::Cmd::Object: {
576 pugi::xml_parse_result result = doc.load_file(m_upload_path.c_str());
577 if (result.status == pugi::status_ok) {
578 m_upload_path.clear(); // empty for next time
579
580 pugi::xml_node objects = doc.child("gpx");
581 for (pugi::xml_node object = objects.first_child(); object;
582 object = object.next_sibling()) {
583 if (!strcmp(object.name(), "rte")) {
584 HandleRoute(object, *evt_data);
585 } else if (!strcmp(object.name(), "trk")) {
586 HandleTrack(object, *evt_data);
587 } else if (!strcmp(object.name(), "wpt")) {
588 HandleWaypoint(object, *evt_data);
589 }
590 }
591 } else {
592 UpdateReturnStatus(RestServerResult::ObjectParseError);
593 }
594 break;
595 }
596 case RestIoEvtData::Cmd::ListRoutes: {
597 std::stringstream ss;
598 ss << "[";
599 for (auto& r : *pRouteList) {
600 if (ss.str() != "[") ss << ", ";
601 ss << "[ \"" << r->GetGUID() << "\", \"" << r->GetName() << "\"]";
602 }
603 ss << "]";
604 std::string reply(kListRoutesReply);
605 ocpn::replace(reply, "@version@", PACKAGE_VERSION);
606 ocpn::replace(reply, "@routes@", ss.str());
607 m_reply_body = reply;
608 UpdateReturnStatus(RestServerResult::NoError);
609 } break;
610 case RestIoEvtData::Cmd::ActivateRoute: {
611 auto guid = evt_data->payload;
613 UpdateReturnStatus(RestServerResult::NoError);
614 } break;
615 case RestIoEvtData::Cmd::ReverseRoute: {
616 auto guid = evt_data->payload;
617 if (guid.empty()) {
618 UpdateReturnStatus(RestServerResult::ObjectRejected);
619 } else {
620 reverse_route.Notify(guid);
621 UpdateReturnStatus(RestServerResult::NoError);
622 }
623 } break;
624 case RestIoEvtData::Cmd::PluginMsg: {
625 std::ifstream f(m_upload_path);
626 m_upload_path.clear(); // empty for next time
627 std::stringstream ss;
628 ss << f.rdbuf();
629 auto msg = std::make_shared<PluginMsg>(PluginMsg(evt_data->id, ss.str()));
630 NavMsgBus::GetInstance().Notify(msg);
631 UpdateReturnStatus(RestServerResult::NoError);
632 } break;
633 }
634}
635
636void RestServer::HandleRoute(pugi::xml_node object,
637 const RestIoEvtData& evt_data) {
638 Route* route = GPXLoadRoute1(object, false, false, false, 0, true);
639 // Check for duplicate GUID
640 bool add = true;
641 bool overwrite_one = false;
642 Route* duplicate = m_route_ctx.find_route_by_guid(route->GetGUID());
643 if (duplicate) {
644 if (!m_overwrite && !evt_data.force) {
645 auto result = m_dlg_ctx.run_accept_object_dlg(
646 _("The received route already exists on this system.\nReplace?"),
647 _("Always replace objects?"));
648 if (result.status != ID_STG_OK) {
649 add = false;
650 UpdateReturnStatus(RestServerResult::DuplicateRejected);
651 } else {
652 m_overwrite = result.check1_value;
653 overwrite_one = true;
654 SaveConfig();
655 }
656 }
657 }
658 if (add) {
659 if (m_overwrite || overwrite_one || evt_data.force) {
660 // Remove the existing duplicate route before adding new route
661 m_route_ctx.delete_route(duplicate); // Also handles the navobj.db delete
662 }
663 // Add the route to the global list
665 if (InsertRouteA(route, &pSet)) {
666 NavObj_dB::GetInstance().InsertRoute(route);
667 UpdateReturnStatus(RestServerResult::NoError);
668 if (evt_data.activate)
669 activate_route.Notify(route->GetGUID().ToStdString());
670 if (g_pRouteMan) g_pRouteMan->on_routes_update.Notify();
671 } else {
672 UpdateReturnStatus(RestServerResult::RouteInsertError);
673 }
674 }
676}
677
678void RestServer::HandleTrack(pugi::xml_node object,
679 const RestIoEvtData& evt_data) {
680 Track* track = GPXLoadTrack1(object, true, false, false, 0);
681 // Check for duplicate GUID
682 bool add = true;
683 bool overwrite_one = false;
684
685 Track* duplicate = m_route_ctx.find_track_by_guid(track->m_GUID);
686 if (duplicate) {
687 if (!m_overwrite && !evt_data.force) {
688 auto result = m_dlg_ctx.run_accept_object_dlg(
689 _("The received track already exists on this system.\nReplace?"),
690 _("Always replace objects?"));
691
692 if (result.status != ID_STG_OK) {
693 add = false;
694 UpdateReturnStatus(RestServerResult::DuplicateRejected);
695 } else {
696 m_overwrite = result.check1_value;
697 overwrite_one = true;
698 SaveConfig();
699 }
700 }
701 }
702 if (add) {
703 if (m_overwrite || overwrite_one || evt_data.force) {
704 NavObj_dB::GetInstance().DeleteTrack(duplicate);
705 m_route_ctx.delete_track(duplicate);
706 }
707 // Add the track to the global list
708 if (InsertTrack(track, false)) {
709 NavObj_dB::GetInstance().InsertTrack(track);
710 UpdateReturnStatus(RestServerResult::NoError);
711 } else
712 UpdateReturnStatus(RestServerResult::RouteInsertError);
713 m_dlg_ctx.top_level_refresh();
714 }
715}
716
717void RestServer::HandleWaypoint(pugi::xml_node object,
718 const RestIoEvtData& evt_data) {
719 RoutePoint* rp =
720 GPXLoadWaypoint1(object, "circle", "", false, false, false, 0);
721 rp->m_bIsolatedMark = true; // This is an isolated mark
722 // Check for duplicate GUID
723 bool add = true;
724 bool overwrite_one = false;
725
726 RoutePoint* duplicate = m_route_ctx.find_wpt_by_guid(rp->m_GUID);
727 if (duplicate) {
728 if (!m_overwrite && !evt_data.force) {
729 auto result = m_dlg_ctx.run_accept_object_dlg(
730 _("The received waypoint already exists on this system.\nReplace?"),
731 _("Always replace objects?"));
732 if (result.status != ID_STG_OK) {
733 add = false;
734 UpdateReturnStatus(RestServerResult::DuplicateRejected);
735 } else {
736 m_overwrite = result.check1_value;
737 overwrite_one = true;
738 SaveConfig();
739 }
740 }
741 }
742 if (add) {
743 if (m_overwrite || overwrite_one || evt_data.force) {
744 NavObj_dB::GetInstance().DeleteRoutePoint(duplicate);
745 m_route_ctx.delete_waypoint(duplicate);
746 }
747 if (InsertWpt(rp, m_overwrite || overwrite_one || evt_data.force)) {
748 UpdateReturnStatus(RestServerResult::NoError);
749 NavObj_dB::GetInstance().InsertRoutePoint(rp);
750 } else
751 UpdateReturnStatus(RestServerResult::RouteInsertError);
752 m_dlg_ctx.top_level_refresh();
753 }
754}
755
756RestIoEvtData::RestIoEvtData(Cmd c, std::string key, std::string src,
757 std::string _payload, std::string _id, bool _force,
758 bool _activate)
759 : cmd(c),
760 api_key(std::move(key)),
761 source(std::move(src)),
762 id(_id),
763 force(_force),
764 activate(_activate),
765 payload(std::move(_payload)) {}
766
768 : run_pincode_dlg([](const std::string&, const std::string&) -> wxDialog* {
769 return nullptr;
770 }),
771 update_route_mgr([]() {}),
772 run_accept_object_dlg([](const wxString&, const wxString&) {
773 return AcceptObjectDlgResult();
774 }),
775 top_level_refresh([]() {}) {}
776
778 : find_route_by_guid(
779 [](const wxString&) { return static_cast<Route*>(nullptr); }),
780 find_track_by_guid(
781 [](const wxString&) { return static_cast<Track*>(nullptr); }),
782 find_wpt_by_guid(
783 [](const wxString&) { return static_cast<RoutePoint*>(nullptr); }),
784 delete_route([](Route*) -> void {}),
785 delete_track([](Track*) -> void {}),
786 delete_waypoint([](RoutePoint*) -> void {}) {}
EventVar reverse_route
Notified with a string GUID when user wants to reverse a route.
EventVar activate_route
Notified with a string GUID when user wants to activate a route.
const void Notify()
Notify all listeners, no data supplied.
void Notify(std::shared_ptr< const NavMsg > message)
Accept message received by driver, make it available for upper layers.
Custom event class for OpenCPN's notification system.
A random generated int value with accessors for string and hashcode.
Definition pincode.h:27
static Pincode Create()
Create a new pincode based on a random value.
Definition pincode.cpp:42
std::string Hash() const
Return a hashvalue string.
Definition pincode.cpp:55
std::string ToString() const
Return value as string.
Definition pincode.cpp:49
std::string CompatHash()
Return a hashvalue as computed on 5.8 hosts.
Definition pincode.cpp:31
A plugin to plugin json message over the REST interface.
Callbacks for handling dialogs and RouteManager updates.
Definition rest_server.h:95
RestServerDlgCtx()
All dummy stubs constructor.
std::function< AcceptObjectDlgResult(const wxString &msg, const wxString &check1msg)> run_accept_object_dlg
Run the "Accept Object" dialog, returns value from ShowModal().
std::function< wxDialog *(const std::string &msg, const std::string &text1)> run_pincode_dlg
Run the "Server wants a pincode" dialog.
Definition rest_server.h:99
AbstractRestServer implementation and interface to underlying IO thread.
std::string m_cert_file
Semi-static storage used by IoThread C code.
std::string m_reply_body
IoThread interface: body of return message, if any.
RestServerResult GetReturnStatus()
IoThread interface.
std::condition_variable return_status_cv
IoThread interface: Guards return_status.
std::mutex ret_mutex
IoThread interface: Guards return_status.
void StopServer() override
Stop server thread, blocks until completed.
void UpdateRouteMgr() const
IoThread interface.
void UpdateReturnStatus(RestServerResult r)
IoThread interface.
std::string m_key_file
Semi-static storage used by IoThread C code.
bool StartServer(const fs::path &certificate_location) override
Start the server thread.
Callbacks for handling routes and tracks.
RouteCtx()
Dummy stubs constructor.
Represents a waypoint or mark within the navigation system.
Definition route_point.h:70
wxString m_GUID
Globally Unique Identifier for the waypoint.
bool m_bIsolatedMark
Flag indicating if the waypoint is a standalone mark.
Represents a navigational route in the navigation system.
Definition route.h:98
EventVar on_routes_update
Notified when list of routes is updated (no data in event)
Definition routeman.h:269
Represents a track, which is a series of connected track points.
Definition track.h:111
Raw messages layer, supports sending and recieving navmsg messages.
Enhanced logging interface on top of wx/log.h.
bool replace(std::string &str, const std::string &from, const std::string &to)
Perform in place substitution in str, replacing "from" with "to".
std::vector< std::string > split(const char *token_string, const std::string &delimiter)
Return vector of items in s separated by delimiter.
Class NavObj_dB.
Miscellaneous utilities, many of which string related.
std::string RestResultText(RestServerResult result)
RestServerResult string representation.
wxDEFINE_EVENT(REST_IO_EVT, ObservedEvt)
Event from IO thread to main.
Returned status from RunAcceptObjectDlg.
Definition rest_server.h:83
Definition cm93.h:177
static RestIoEvtData CreateListRoutesData(const std::string &key, const std::string &src)
Create a Cmd::ListRoutes instance.
static RestIoEvtData CreateChkWriteData(const std::string &key, const std::string &src, const std::string &guid)
Create a Cmd::CheckWrite instance.
const bool activate
rest API parameter activate
static RestIoEvtData CreateCmdData(const std::string &key, const std::string &src, const std::string &gpx_data, bool _force, bool _activate)
Create a Cmd::Object instance.
static RestIoEvtData CreatePingData(const std::string &key, const std::string &src)
Create a Cmd::Ping instance:
const std::string payload
GPX data for Cmd::Object, Guid for Cmd::CheckWrite, Activate, Reverse.
const std::string source
Rest API parameter source.
const bool force
rest API parameter force
const std::string api_key
Rest API parameter apikey.
const std::string id
rest API parameter id for PluginMsg.