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