33#include <wx/filename.h>
39#include "model/config_vars.h"
42#include "model/nav_object_database.h"
44#include "model/pincode.h"
45#include "model/rest_server.h"
46#include "model/routeman.h"
49#include "observable_evt.h"
55using namespace std::chrono_literals;
57static const char*
const kHttpAddr =
"http://0.0.0.0:8000";
58static const char*
const kHttpsAddr =
"http://0.0.0.0:8443";
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@" })--";
67enum { ORS_START_OF_SESSION, ORS_CHUNK_N, ORS_CHUNK_LAST };
70 const enum class Cmd {
90 const std::string& src,
91 const std::string& gpx_data,
bool _force,
93 return RestIoEvtData(Cmd::Object, key, src, gpx_data,
"", _force,
99 const std::string& src) {
100 return {Cmd::Ping, key, src,
"",
""};
105 const std::string& src,
106 const std::string& guid) {
107 return {Cmd::CheckWrite, key, src,
"", guid};
112 const std::string& src) {
113 return {Cmd::ListRoutes, key, src,
"",
""};
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,
""};
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};
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,
""};
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,
144static inline std::string HttpVarToString(
const struct mg_str& query,
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);
153 const std::shared_ptr<RestIoEvtData>& evt_data,
int id) {
155 evt->SetSharedPtr(evt_data);
156 parent->QueueEvent(evt);
157 if (!
dynamic_cast<wxApp*
>(wxAppConsole::GetInstance())) {
158 wxTheApp->ProcessPendingEvents();
162static void HandleRxObject(
struct mg_connection* c,
struct mg_http_message* hm,
164 int MID = ORS_CHUNK_N;
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;
172 xml_content = std::string(hm->chunk.ptr, hm->chunk.len);
174 MID = ORS_CHUNK_LAST;
176 mg_http_delete_chunk(c, hm);
179 if (!source.empty()) {
180 assert(parent &&
"Null parent pointer");
183 api_key, source, xml_content, !force.empty(), !activate.empty()));
184 PostEvent(parent, data_ptr, MID);
186 if (MID == ORS_CHUNK_LAST) {
187 std::unique_lock<std::mutex> lock{parent->
ret_mutex};
191 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
192 mg_http_reply(c, 200,
"",
"{\"result\": %d}\n", parent->
GetReturnStatus());
196static void HandlePing(
struct mg_connection* c,
struct mg_http_message* hm,
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");
203 auto data_ptr = std::make_shared<RestIoEvtData>(
205 PostEvent(parent, data_ptr, ORS_CHUNK_LAST);
206 std::unique_lock<std::mutex> lock{parent->
ret_mutex};
210 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
212 mg_http_reply(c, 200,
"",
"{\"result\": %d, \"version\": \"%s\"}\n",
216static void HandleWritable(
struct mg_connection* c,
struct mg_http_message* hm,
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");
224 auto data_ptr = std::make_shared<RestIoEvtData>(
226 PostEvent(parent, data_ptr, ORS_CHUNK_LAST);
227 std::unique_lock<std::mutex> lock{parent->
ret_mutex};
231 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
233 mg_http_reply(c, 200,
"",
"{\"result\": %d}\n", parent->
GetReturnStatus());
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");
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};
250 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
252 mg_http_reply(c, 200,
"", parent->
m_reply_body.c_str());
255static void HandleActivateRoute(
struct mg_connection* c,
256 struct mg_http_message* hm,
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");
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};
271 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
273 mg_http_reply(c, 200,
"",
"{\"result\": %d}\n", parent->
GetReturnStatus());
276static void HandlePluginMsg(
struct mg_connection* c,
struct mg_http_message* hm,
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;
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");
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};
297 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
298 mg_http_reply(c, 200,
"",
"{\"result\": %d}\n",
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");
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};
319 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
321 mg_http_reply(c, 200,
"",
"{\"result\": %d}\n", parent->
GetReturnStatus());
326static void fn(
struct mg_connection* c,
int ev,
void* ev_data,
void* fn_data) {
327 auto parent =
static_cast<RestServer*
>(fn_data);
329 if (ev == MG_EV_ACCEPT ) {
330 struct mg_tls_opts opts = {0};
334 opts.ciphers =
nullptr;
335 mg_tls_init(c, &opts);
336 }
else if (ev == MG_EV_TLS_HS) {
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);
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);
359 mg_http_reply(c, 404,
"",
"url: not found\n");
367 case RestServerResult::NoError:
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");
384 return s.ToStdString();
390RestServer::IoThread::IoThread(
RestServer& parent, std::string ip)
391 : run_flag(-1), m_parent(parent), m_server_ip(std::move(ip)) {}
393void RestServer::IoThread::Run() {
395 struct mg_mgr mgr = {0};
396 mg_log_set(MG_LL_DEBUG);
400 MESSAGE_LOG <<
"Listening on " << m_server_ip <<
"\n";
401 mg_http_listen(&mgr, m_server_ip.c_str(), fn, &m_parent);
403 while (run_flag > 0) mg_mgr_poll(&mgr, 200);
406 m_parent.m_exit_sem.Post();
409void RestServer::IoThread::Stop() { run_flag = 0; }
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;
416 return r == wxSEMA_NO_ERROR;
419RestServer::Apikeys RestServer::Apikeys::Parse(
const std::string& s) {
422 for (
const auto& ip_key : ip_keys) {
424 if (words.size() != 2)
continue;
425 if (apikeys.find(words[0]) == apikeys.end()) {
426 apikeys[words[0]] = words[1];
432std::string RestServer::Apikeys::ToString()
const {
433 std::stringstream ss;
434 for (
const auto& it : *this) ss << it.first <<
":" << it.second <<
";";
440 std::lock_guard<std::mutex> lock{
ret_mutex};
441 return_status = result;
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),
454 m_io_thread(*this, m_endpoint),
457 Bind(REST_IO_EVT, &RestServer::HandleServerMessage,
this);
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();
469 if (!m_std_thread.joinable()) {
470 m_std_thread = std::thread([&]() { m_io_thread.Run(); });
476 wxLogDebug(
"Stopping REST service");
478 if (m_std_thread.joinable()) {
479 wxLogDebug(
"Stopping io thread");
481 m_io_thread.WaitUntilStopped();
486bool RestServer::LoadConfig() {
487 TheBaseConfig()->SetPath(
"/Settings/RestServer");
489 TheBaseConfig()->Read(
"ServerKeys", &key_string);
490 m_key_map = Apikeys::Parse(key_string.ToStdString());
491 TheBaseConfig()->Read(
"ServerOverwriteDuplicates", &m_overwrite,
false);
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();
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;
510 std::string new_api_key = m_pincode.
Hash();
511 if (evt_data.
api_key.size() < 10)
514 m_key_map[evt_data.
source] = new_api_key;
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();
527void RestServer::HandleServerMessage(
ObservedEvt& event) {
528 auto evt_data = UnpackEvtPointer<RestIoEvtData>(event);
530 switch (event.GetId()) {
531 case ORS_START_OF_SESSION:
533 m_upload_path = wxFileName::CreateTempFileName(
"ocpn_tul").ToStdString();
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();
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());
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();
555 if (!CheckApiKey(*evt_data)) {
560 switch (evt_data->cmd) {
561 case RestIoEvtData::Cmd::Ping:
564 case RestIoEvtData::Cmd::CheckWrite: {
566 auto dup = m_route_ctx.find_route_by_guid(guid);
567 if (!dup || evt_data->
force || m_overwrite) {
574 case RestIoEvtData::Cmd::Object: {
577 if (result.status == pugi::status_ok) {
578 m_upload_path.clear();
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);
596 case RestIoEvtData::Cmd::ListRoutes: {
597 std::stringstream ss;
599 for (
auto& r : *pRouteList) {
600 if (ss.str() !=
"[") ss <<
", ";
601 ss <<
"[ \"" << r->GetGUID() <<
"\", \"" << r->GetName() <<
"\"]";
604 std::string reply(kListRoutesReply);
610 case RestIoEvtData::Cmd::ActivateRoute: {
615 case RestIoEvtData::Cmd::ReverseRoute: {
624 case RestIoEvtData::Cmd::PluginMsg: {
625 std::ifstream f(m_upload_path);
626 m_upload_path.clear();
627 std::stringstream ss;
629 auto msg = std::make_shared<PluginMsg>(
PluginMsg(evt_data->
id, ss.str()));
630 NavMsgBus::GetInstance().
Notify(msg);
638 Route* route = GPXLoadRoute1(
object,
false,
false,
false, 0,
true);
641 bool overwrite_one =
false;
642 Route* duplicate = m_route_ctx.find_route_by_guid(route->GetGUID());
644 if (!m_overwrite && !evt_data.
force) {
646 _(
"The received route already exists on this system.\nReplace?"),
647 _(
"Always replace objects?"));
648 if (result.status != ID_STG_OK) {
652 m_overwrite = result.check1_value;
653 overwrite_one =
true;
659 if (m_overwrite || overwrite_one || evt_data.
force) {
661 m_route_ctx.delete_route(duplicate);
665 if (InsertRouteA(route, &pSet)) {
666 NavObj_dB::GetInstance().InsertRoute(route);
680 Track* track = GPXLoadTrack1(
object,
true,
false,
false, 0);
683 bool overwrite_one =
false;
685 Track* duplicate = m_route_ctx.find_track_by_guid(track->m_GUID);
687 if (!m_overwrite && !evt_data.
force) {
689 _(
"The received track already exists on this system.\nReplace?"),
690 _(
"Always replace objects?"));
692 if (result.status != ID_STG_OK) {
696 m_overwrite = result.check1_value;
697 overwrite_one =
true;
703 if (m_overwrite || overwrite_one || evt_data.
force) {
704 NavObj_dB::GetInstance().DeleteTrack(duplicate);
705 m_route_ctx.delete_track(duplicate);
708 if (InsertTrack(track,
false)) {
709 NavObj_dB::GetInstance().InsertTrack(track);
713 m_dlg_ctx.top_level_refresh();
720 GPXLoadWaypoint1(
object,
"circle",
"",
false,
false,
false, 0);
724 bool overwrite_one =
false;
728 if (!m_overwrite && !evt_data.
force) {
730 _(
"The received waypoint already exists on this system.\nReplace?"),
731 _(
"Always replace objects?"));
732 if (result.status != ID_STG_OK) {
736 m_overwrite = result.check1_value;
737 overwrite_one =
true;
743 if (m_overwrite || overwrite_one || evt_data.
force) {
744 NavObj_dB::GetInstance().DeleteRoutePoint(duplicate);
745 m_route_ctx.delete_waypoint(duplicate);
747 if (InsertWpt(rp, m_overwrite || overwrite_one || evt_data.
force)) {
749 NavObj_dB::GetInstance().InsertRoutePoint(rp);
752 m_dlg_ctx.top_level_refresh();
756RestIoEvtData::RestIoEvtData(Cmd c, std::string key, std::string src,
757 std::string _payload, std::string _id,
bool _force,
760 api_key(std::move(key)),
761 source(std::move(src)),
765 payload(std::move(_payload)) {}
768 : run_pincode_dlg([](const std::string&, const std::string&) -> wxDialog* {
771 update_route_mgr([]() {}),
772 run_accept_object_dlg([](
const wxString&,
const wxString&) {
775 top_level_refresh([]() {}) {}
778 : find_route_by_guid(
779 [](const wxString&) {
return static_cast<Route*
>(
nullptr); }),
781 [](
const wxString&) {
return static_cast<Track*
>(
nullptr); }),
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.
static Pincode Create()
Create a new pincode based on a random value.
std::string Hash() const
Return a hashvalue string.
std::string ToString() const
Return value as string.
std::string CompatHash()
Return a hashvalue as computed on 5.8 hosts.
A plugin to plugin json message over the REST interface.
Callbacks for handling dialogs and RouteManager updates.
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.
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.
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.
EventVar on_routes_update
Notified when list of routes is updated (no data in event)
Represents a track, which is a series of connected track points.
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.
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.