OpenCPN Partial API docs
Loading...
Searching...
No Matches
REST_server.cpp
1/***************************************************************************
2 *
3 * Project: OpenCPN
4 * Purpose: Implement RESTful server.
5 * Author: David Register, Alec Leamas
6 *
7 ***************************************************************************
8 * Copyright (C) 2022 by David Register, Alec Leamas *
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 * This program is distributed in the hope that it will be useful, *
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18 * GNU General Public License for more details. *
19 * *
20 * You should have received a copy of the GNU General Public License *
21 * along with this program; if not, write to the *
22 * Free Software Foundation, Inc., *
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24 **************************************************************************/
25
26#include <mutex>
27#include <vector>
28#include <memory>
29#include <condition_variable>
30#include <thread>
31#include <random>
32
33#include <wx/event.h>
34#include <wx/log.h>
35#include <wx/string.h>
36#include <wx/thread.h>
37#include <wx/utils.h>
38#include <wx/tokenzr.h>
39#include <wx/fileconf.h>
40#include <fstream>
41#include <string>
42
43#include "REST_server.h"
44#include "mongoose.h"
45#include "config_vars.h"
46#include "gui_lib.h"
47#include "REST_server_gui.h"
48#include "pugixml.hpp"
49#include "route.h"
50#include "track.h"
51#include "routeman.h"
52#include "nav_object_database.h"
53
54extern bool g_bportable;
55extern std::vector<Track*> g_TrackList;
56
57Route *GPXLoadRoute1(pugi::xml_node &wpt_node, bool b_fullviz,
58 bool b_layer, bool b_layerviz, int layer_id,
59 bool b_change);
60Track *GPXLoadTrack1(pugi::xml_node &trk_node, bool b_fullviz,
61 bool b_layer, bool b_layerviz, int layer_id);
62RoutePoint *GPXLoadWaypoint1(pugi::xml_node &wpt_node,
63 wxString def_symbol_name, wxString GUID,
64 bool b_fullviz, bool b_layer,
65 bool b_layerviz, int layer_id);
66
67bool InsertRouteA(Route *pTentRoute, NavObjectCollection1* navobj);
68bool InsertTrack(Track *pTentTrack, bool bApplyChanges = false);
69bool InsertWpt(RoutePoint *pWp, bool overwrite);
70
71extern Routeman *g_pRouteMan;
72extern MyFrame *gFrame;
73
74// Some global variables to handle thread syncronization
75int return_status;
76std::condition_variable return_status_condition;
77std::mutex mx;
78
79
80
81class RESTServerThread : public wxThread {
82public:
84
86 void* Entry();
87 void OnExit(void);
88
89private:
90 RESTServer *m_pParent;
91
92};
93
94
95class RESTServerEvent;
96wxDECLARE_EVENT(wxEVT_RESTFUL_SERVER, RESTServerEvent);
97
98class RESTServerEvent : public wxEvent {
99public:
101 wxEventType commandType = wxEVT_RESTFUL_SERVER, int id = 0)
102 : wxEvent(id, commandType){};
104
105 // accessors
106 void SetPayload(std::shared_ptr<std::string> data) {
107 m_payload = data;
108 }
109 void SetSource(std::string source){
110 m_source_peer = source;
111 }
112 void SetAPIKey(std::string key){
113 m_api_key = key;
114 }
115 std::shared_ptr<std::string> GetPayload() { return m_payload; }
116
117 // required for sending with wxPostEvent()
118 wxEvent* Clone() const {
119 RESTServerEvent* newevent =
120 new RESTServerEvent(*this);
121 newevent->m_payload = this->m_payload;
122 newevent->m_source_peer = this->m_source_peer;
123 newevent->m_api_key = this->m_api_key;
124 return newevent;
125 };
126
127 std::shared_ptr<std::string> m_payload;
128 std::string m_source_peer;
129 std::string m_api_key;
130private:
131
132
133};
134
135wxDEFINE_EVENT(wxEVT_RESTFUL_SERVER, RESTServerEvent);
136
137//========================================================================
138/* RESTServer implementation
139 * */
140
141RESTServer::RESTServer()
142 : m_Thread_run_flag(-1)
143{
144
145 m_PINCreateDialog = NULL;
146
147 // Prepare the wxEventHandler to accept events from the actual hardware thread
148 Bind(wxEVT_RESTFUL_SERVER, &RESTServer::HandleServerMessage,
149 this);
150
151}
152
153RESTServer::~RESTServer() { }
154
155bool RESTServer::StartServer(std::string certificate_location) {
156
157 m_certificate_directory = certificate_location;
158 m_cert_file = m_certificate_directory + std::string("cert.pem"); // Certificate PEM file
159 m_key_file = m_certificate_directory + std::string("key.pem"); // The key PEM file
160
161
162 // Load persistent config info
163 LoadConfig();
164
165 // Kick off the Server thread
166 SetSecondaryThread(new RESTServerThread(this));
167 SetThreadRunFlag(1);
168 GetSecondaryThread()->Run();
169
170 return true;
171}
172
173void RESTServer::StopServer() {
174 wxLogMessage(
175 wxString::Format(_T("Stopping REST service")));
176
177 Unbind(wxEVT_RESTFUL_SERVER, &RESTServer::HandleServerMessage,
178 this);
179
180 // Kill off the Secondary RX Thread if alive
181 if (m_pSecondary_Thread) {
182 m_pSecondary_Thread->Delete();
183
184 if (m_bsec_thread_active) // Try to be sure thread object is still alive
185 {
186 wxLogMessage(_T("Stopping Secondary Thread"));
187
188 m_Thread_run_flag = 0;
189
190 int tsec = 10;
191 while ((m_Thread_run_flag >= 0) && (tsec--)) wxSleep(1);
192
193 wxString msg;
194 if (m_Thread_run_flag < 0)
195 msg.Printf(_T("Stopped in %d sec."), 10 - tsec);
196 else
197 msg.Printf(_T("Not Stopped after 10 sec."));
198 wxLogMessage(msg);
199 }
200
201 m_pSecondary_Thread = NULL;
202 m_bsec_thread_active = false;
203 }
204}
205
206bool RESTServer::LoadConfig( void )
207{
208 if( TheBaseConfig() ) {
209 TheBaseConfig()->SetPath("/Settings/RESTServer");
210
211 wxString key_string;
212
213 TheBaseConfig()->Read("ServerKeys", &key_string );
214 wxStringTokenizer st(key_string, _T(";"));
215 while (st.HasMoreTokens()) {
216 wxString s1 = st.GetNextToken();
217 wxString client_name = s1.BeforeFirst(':');
218 wxString client_key = s1.AfterFirst(':');
219
220 m_key_map[client_name.ToStdString()] = client_key.ToStdString();
221 }
222 TheBaseConfig()->Read("ServerOverwriteDuplicates", &m_b_overwrite, 0 );
223
224 }
225 return true;
226}
227
228bool RESTServer::SaveConfig( void )
229{
230 if( TheBaseConfig() ) {
231 TheBaseConfig()->SetPath( _T ( "/Settings/RESTServer" ) );
232
233 wxString key_string;
234 for (auto it : m_key_map){
235 wxString item = it.first.c_str() + wxString(":") + it.second.c_str() + wxString(";");
236 key_string += item;
237 }
238
239 TheBaseConfig()->Write("ServerKeys", key_string );
240
241 TheBaseConfig()->Write("ServerOverwriteDuplicates", m_b_overwrite );
242
243 }
244 return true;
245}
246
247unsigned long long PINtoRandomKey( int dpin) {
248 std::linear_congruential_engine<unsigned long long, 48271, 0, 0xFFFFFFFFFFFFFFFF> engine;
249 engine.seed( dpin );
250 unsigned long long r = engine();
251 return r;
252
253}
254
255std::string PINtoRandomKeyString( int dpin) {
256 unsigned long long pin = PINtoRandomKey(dpin);
257 char buffer[100];
258 snprintf(buffer, sizeof(buffer)-1, "%0llX", pin);
259 return std::string(buffer);
260}
261
262void RESTServer::HandleServerMessage(RESTServerEvent& event) {
263
264 // Cancel existing dialog
265 if(m_PINCreateDialog){
266 m_PINCreateDialog->Close();
267 m_PINCreateDialog->Destroy();
268 m_PINCreateDialog = NULL;
269 }
270
271 auto p = event.GetPayload();
272 std::string *payload = p.get();
273
274 //printf("%s\n", payload->c_str());
275
276 // Server thread is waiting for (return_status >= 0) on notify_one()
277 int return_stat = RESTServerResult::RESULT_GENERIC_ERROR; // generic error
278
279#ifndef CLIAPP
280
281 // Look up the api key in the hash map.
282 std::string api_found;
283 for (auto it : m_key_map){
284 if (it.first == event.m_source_peer && it.second == event.m_api_key){
285 api_found = it.second;
286 break;
287 }
288 }
289
290 if (!api_found.size()){
291 // Need a new PIN confirmation
292 m_dPIN = wxMin(rand() % 10000 + 1, 9999);
293 m_sPIN.Printf("%04d", m_dPIN);
294
295 std::string new_api_key = PINtoRandomKeyString(m_dPIN);
296
297 // Add new PIN to map
298 m_key_map[event.m_source_peer] = new_api_key;
299
300 // And persist it
301 SaveConfig();
302
303
304 m_PINCreateDialog = new PINCreateDialog((wxWindow *)gFrame, wxID_ANY, _("OpenCPN Server Message"),
305 "", wxDefaultPosition, wxDefaultSize, SYMBOL_STG_STYLE );
306
307 wxString hmsg(event.m_source_peer.c_str());
308 hmsg += " ";
309 hmsg += "wants to sent you a new route.\nPlease enter the following PIN number on ";
310 hmsg += wxString(event.m_source_peer.c_str());
311 hmsg += " to pair with this device.\n";
312
313 m_PINCreateDialog->SetMessage(hmsg);
314 m_PINCreateDialog->SetText1Message(m_sPIN);
315
316 m_PINCreateDialog->Show();
317 return_status = RESTServerResult::RESULT_NEW_PIN_REQUESTED;
318
319 std::lock_guard<std::mutex> lock{mx};
320 return_status_condition.notify_one();
321
322 return;
323
324 }
325
326
327
328
329 // GUI dialogs can go here....
330 bool b_cont;
331#if 0
332 AcceptObjectDialog dialog1(NULL, wxID_ANY, _("OpenCPN Server Message"),
333 "", wxDefaultPosition, wxDefaultSize, SYMBOL_STG_STYLE );
334
335 wxString hmsg(event.m_source_peer.c_str());
336 hmsg += " has sent you a new route.\nAccept?";
337
338 dialog1.SetMessage(hmsg);
339 dialog1.SetCheck1Message(_("Always accept objects from this source?"));
340 b_cont = dialog1.ShowModal() == ID_STG_OK;
341#else
342 b_cont = true;
343#endif
344
345 if (b_cont) {\
346 // Load the GPX file
347 pugi::xml_document doc;
348 pugi::xml_parse_result result = doc.load_buffer(payload->c_str(), payload->size());
349 if (result.status == pugi::status_ok){
350 pugi::xml_node objects = doc.child("gpx");
351 for (pugi::xml_node object = objects.first_child(); object;
352 object = object.next_sibling()) {
353 if (!strcmp(object.name(), "rte")) {
354 Route *pRoute = NULL;
355 pRoute = GPXLoadRoute1(object, true, false, false, 0, true);
356 // Check for duplicate GUID
357 if (g_pRouteMan){
358 bool b_add = true;
359 bool b_overwrite_one = false;
360 Route *duplicate = g_pRouteMan->FindRouteByGUID(pRoute->GetGUID());
361 if (duplicate){
362 if (!m_b_overwrite){
363 AcceptObjectDialog dialog2(NULL, wxID_ANY, _("OpenCPN Server Message"),
364 "", wxDefaultPosition, wxDefaultSize, SYMBOL_STG_STYLE );
365
366 dialog2.SetMessage("The received route already exists on this system.\nReplace?");
367 dialog2.SetCheck1Message(_("Always replace objects from this source?"));
368
369 int result = dialog2.ShowModal();
370 bool b_always = dialog2.GetCheck1Value();
371
372 if (result != ID_STG_OK){
373 b_add = false;
374 return_stat = RESTServerResult::RESULT_DUPLICATE_REJECTED;
375 }
376 else{
377 m_b_overwrite = b_always;
378 b_overwrite_one = true;
379 SaveConfig();
380 }
381 }
382
383 if (m_b_overwrite || b_overwrite_one){
384 // Remove the existing duplicate route before adding new route
385 g_pRouteMan->DeleteRoute(duplicate,
386 NavObjectChanges::getInstance());
387 }
388 }
389
390 if (b_add) {
391 // And here is the payoff....
392
393 // Add the route to the global list
395
396 if (InsertRouteA(pRoute, &pSet))
397 return_stat = RESTServerResult::RESULT_NO_ERROR;
398 else
399 return_stat = RESTServerResult::RESULT_ROUTE_INSERT_ERROR;
400 ((wxWindow *)gFrame)->Refresh();
401 }
402 }
403 } else if (!strcmp(object.name(), "trk")) {
404 Track *pRoute = NULL;
405 pRoute = GPXLoadTrack1(object, true, false, false, 0);
406 // Check for duplicate GUID
407 if (g_pRouteMan){
408 bool b_add = true;
409 bool b_overwrite_one = false;
410
411 Track *duplicate = g_pRouteMan->FindTrackByGUID(pRoute->m_GUID);
412 if (duplicate){
413 if (!m_b_overwrite){
414 AcceptObjectDialog dialog2(NULL, wxID_ANY, _("OpenCPN Server Message"),
415 "", wxDefaultPosition, wxDefaultSize, SYMBOL_STG_STYLE );
416
417 dialog2.SetMessage("The received track already exists on this system.\nReplace?");
418 dialog2.SetCheck1Message(_("Always replace objects from this source?"));
419
420 int result = dialog2.ShowModal();
421 bool b_always = dialog2.GetCheck1Value();
422
423 if (result != ID_STG_OK){
424 b_add = false;
425 return_stat = RESTServerResult::RESULT_DUPLICATE_REJECTED;
426 }
427 else{
428 m_b_overwrite = b_always;
429 b_overwrite_one = true;
430 SaveConfig();
431 }
432 }
433
434 if (m_b_overwrite || b_overwrite_one){
435 auto it = std::find(g_TrackList.begin(), g_TrackList.end(), duplicate);
436 if (it != g_TrackList.end()) {
437 g_TrackList.erase(it);
438 }
439 delete duplicate;
440 }
441 }
442
443 if (b_add) {
444 // And here is the payoff....
445
446 // Add the route to the global list
448
449 if (InsertTrack(pRoute, false))
450 return_stat = RESTServerResult::RESULT_NO_ERROR;
451 else
452 return_stat = RESTServerResult::RESULT_ROUTE_INSERT_ERROR;
453 ((wxWindow *)gFrame)->Refresh();
454 }
455 }
456 } else if (!strcmp(object.name(), "wpt")) {
457 RoutePoint *pWp = NULL;
458 pWp = GPXLoadWaypoint1(object, "circle", "", false, false, false, 0);
459 // Check for duplicate GUID
460 if (g_pRouteMan){
461 bool b_add = true;
462 bool b_overwrite_one = false;
463
464 RoutePoint *duplicate = WaypointExists(pWp->GetName(), pWp->m_lat, pWp->m_lon);
465 if (duplicate){
466 if (!m_b_overwrite){
467 AcceptObjectDialog dialog2(NULL, wxID_ANY, _("OpenCPN Server Message"),
468 "", wxDefaultPosition, wxDefaultSize, SYMBOL_STG_STYLE );
469
470 dialog2.SetMessage("The received waypoint already exists on this system.\nReplace?");
471 dialog2.SetCheck1Message(_("Always replace objects from this source?"));
472
473 int result = dialog2.ShowModal();
474 bool b_always = dialog2.GetCheck1Value();
475
476 if (result != ID_STG_OK){
477 b_add = false;
478 return_stat = RESTServerResult::RESULT_DUPLICATE_REJECTED;
479 }
480 else{
481 m_b_overwrite = b_always;
482 b_overwrite_one = true;
483 SaveConfig();
484 }
485 }
486 }
487
488 if (b_add) {
489 // And here is the payoff....
490 if (InsertWpt(pWp, m_b_overwrite || b_overwrite_one))
491 return_stat = RESTServerResult::RESULT_NO_ERROR;
492 else
493 return_stat = RESTServerResult::RESULT_ROUTE_INSERT_ERROR;
494 ((wxWindow *)gFrame)->Refresh();
495 }
496 }
497 }
498 }
499 }
500 }
501 else{
502 return_stat = RESTServerResult::RESULT_OBJECT_REJECTED;
503
504 }
505#else
506 // FIXME (leamas?)
507 // What should the CLI app do here?
508 return_stat = RESTServerResult::RESULT_GENERIC_ERROR;
509#endif
510
511 return_status = return_stat;
512
513 std::lock_guard<std::mutex> lock{mx};
514 return_status_condition.notify_one();
515}
516
517
518 static const char* s_http_addr = "http://0.0.0.0:8000"; // HTTP port
519 static const char* s_https_addr = "https://0.0.0.0:8443"; // HTTPS port
520 // Is this host a portable? Must use another port to avoid equal IP addres conflicts.
521 static const char* s_http_addr_portable = "http://0.0.0.0:8001"; // HTTP port
522 static const char* s_https_addr_portable = "https://0.0.0.0:8444"; // HTTPS port
523
524
525
526// We use the same event handler function for HTTP and HTTPS connections
527// fn_data is NULL for plain HTTP, and non-NULL for HTTPS
528static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
529 RESTServer *parent = static_cast<RESTServer *>(fn_data);
530
531 if (ev == MG_EV_ACCEPT /*&& fn_data != NULL*/) {
532 struct mg_tls_opts opts;
533 memset(&opts, 0, sizeof(mg_tls_opts));
534
535 opts.ca = NULL; //"cert.pem"; // Uncomment to enable two-way SSL
536 opts.cert = parent->m_cert_file.c_str(); // Certificate PEM file
537 opts.certkey = parent->m_key_file.c_str(); // The key PEM file
538 opts.ciphers = NULL;
539 mg_tls_init(c, &opts);
540 } else if (ev == MG_EV_HTTP_MSG) {
541 struct mg_http_message *hm = (struct mg_http_message *) ev_data;
542 if (mg_http_match_uri(hm, "/api/rx_object")) {
543
544 std::string api_key;
545 struct mg_str api_key_parm = mg_http_var(hm->query, mg_str("apikey"));
546 if(api_key_parm.len && api_key_parm.ptr){
547 api_key = std::string(api_key_parm.ptr, api_key_parm.len);
548 }
549
550
551 struct mg_str source = mg_http_var(hm->query, mg_str("source"));
552
553 if(source.len && hm->body.len )
554 {
555 std::string xml_content(hm->body.ptr, hm->body.len);
556 std::string source_peer(source.ptr, source.len);
557 //printf("%s\n", xml_content.c_str());
558
559 //std::ofstream b_stream("bodyfile", std::fstream::out | std::fstream::binary);
560 //b_stream.write(hm->body.ptr, hm->body.len);
561
562 return_status = -1;
563
564 if (parent){
565 RESTServerEvent Nevent(wxEVT_RESTFUL_SERVER, 0);
566 auto buffer = std::make_shared<std::string>(xml_content);
567 Nevent.SetPayload(buffer);
568 Nevent.SetSource(source_peer);
569 Nevent.SetAPIKey(api_key);
570 parent->AddPendingEvent(Nevent);
571 }
572
573 std::unique_lock<std::mutex> lock{mx};
574 while (return_status < 0) { // !predicate
575 std::this_thread::sleep_for (std::chrono::milliseconds(100));
576 return_status_condition.wait(lock);
577 }
578 lock.unlock();
579 }
580
581 mg_http_reply(c, 200, "", "{\"result\": %d}\n", return_status);
582 }
583 }
584 (void) fn_data;
585}
586
587std::string server_ip;
588
589RESTServerThread::RESTServerThread(RESTServer* Launcher) {
590 m_pParent = Launcher; // This thread's immediate "parent"
591
592 server_ip = s_https_addr;
593 // If Portable use another port
594 if (g_bportable) {
595 server_ip = s_https_addr_portable;
596 wxString sip(server_ip);
597 wxLogMessage("Portable REST server IP: Port " + sip);
598 }
599
600 Create();
601}
602
603RESTServerThread::~RESTServerThread(void) {}
604
605void RESTServerThread::OnExit(void) {}
606
607void* RESTServerThread::Entry() {
608 bool not_done = true;
609 m_pParent->SetSecThreadActive(); // I am alive
610
611 struct mg_mgr mgr; // Event manager
612 mg_log_set(MG_LL_DEBUG); // Set log level
613 mg_mgr_init(&mgr); // Initialise event manager
614
615 mg_http_listen(&mgr, server_ip.c_str(), fn, m_pParent); // Create HTTPS listener
616 //mg_http_listen(&mgr, s_https_addr, fn, (void *) 1); // (HTTPS listener)
617
618 for (;;) mg_mgr_poll(&mgr, 1000); // Infinite event loop
619 mg_mgr_free(&mgr);
620
621 m_pParent->SetSecThreadInActive(); // I am dead
622 m_pParent->m_Thread_run_flag = -1;
623
624 return 0;
625}
626
"Accept Object" Dialog Definition
Definition: route.h:70
bool DeleteRoute(Route *pRoute, NavObjectChanges *nav_obj_changes)
Definition: routeman.cpp:726
Definition: track.h:79