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"
54using namespace std::chrono_literals;
56static const char*
const kHttpAddr =
"http://0.0.0.0:8000";
57static const char*
const kHttpsAddr =
"http://0.0.0.0:8443";
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@" })--";
66enum { ORS_START_OF_SESSION, ORS_CHUNK_N, ORS_CHUNK_LAST };
69 const enum class Cmd {
89 const std::string& src,
90 const std::string& gpx_data,
bool _force,
92 return RestIoEvtData(Cmd::Object, key, src, gpx_data,
"", _force,
98 const std::string& src) {
99 return {Cmd::Ping, key, src,
"",
""};
104 const std::string& src,
105 const std::string& guid) {
106 return {Cmd::CheckWrite, key, src,
"", guid};
111 const std::string& src) {
112 return {Cmd::ListRoutes, key, src,
"",
""};
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,
""};
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};
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,
""};
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,
143static inline std::string HttpVarToString(
const struct mg_str& query,
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);
152 const std::shared_ptr<RestIoEvtData>& evt_data,
int id) {
154 evt->SetSharedPtr(evt_data);
155 parent->QueueEvent(evt);
156 if (!
dynamic_cast<wxApp*
>(wxAppConsole::GetInstance())) {
157 wxTheApp->ProcessPendingEvents();
161static void HandleRxObject(
struct mg_connection* c,
struct mg_http_message* hm,
163 int MID = ORS_CHUNK_N;
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;
171 xml_content = std::string(hm->chunk.ptr, hm->chunk.len);
173 MID = ORS_CHUNK_LAST;
175 mg_http_delete_chunk(c, hm);
178 if (!source.empty()) {
179 assert(parent &&
"Null parent pointer");
182 api_key, source, xml_content, !force.empty(), !activate.empty()));
183 PostEvent(parent, data_ptr, MID);
185 if (MID == ORS_CHUNK_LAST) {
186 std::unique_lock<std::mutex> lock{parent->
ret_mutex};
190 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
191 mg_http_reply(c, 200,
"",
"{\"result\": %d}\n", parent->
GetReturnStatus());
195static void HandlePing(
struct mg_connection* c,
struct mg_http_message* hm,
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");
202 auto data_ptr = std::make_shared<RestIoEvtData>(
204 PostEvent(parent, data_ptr, ORS_CHUNK_LAST);
205 std::unique_lock<std::mutex> lock{parent->
ret_mutex};
209 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
211 mg_http_reply(c, 200,
"",
"{\"result\": %d, \"version\": \"%s\"}\n",
215static void HandleWritable(
struct mg_connection* c,
struct mg_http_message* hm,
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");
223 auto data_ptr = std::make_shared<RestIoEvtData>(
225 PostEvent(parent, data_ptr, ORS_CHUNK_LAST);
226 std::unique_lock<std::mutex> lock{parent->
ret_mutex};
230 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
232 mg_http_reply(c, 200,
"",
"{\"result\": %d}\n", parent->
GetReturnStatus());
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");
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};
249 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
251 mg_http_reply(c, 200,
"", parent->
m_reply_body.c_str());
254static void HandleActivateRoute(
struct mg_connection* c,
255 struct mg_http_message* hm,
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");
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};
270 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
272 mg_http_reply(c, 200,
"",
"{\"result\": %d}\n", parent->
GetReturnStatus());
275static void HandlePluginMsg(
struct mg_connection* c,
struct mg_http_message* hm,
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;
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");
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};
296 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
297 mg_http_reply(c, 200,
"",
"{\"result\": %d}\n",
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");
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};
318 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
320 mg_http_reply(c, 200,
"",
"{\"result\": %d}\n", parent->
GetReturnStatus());
325static void fn(
struct mg_connection* c,
int ev,
void* ev_data,
void* fn_data) {
326 auto parent =
static_cast<RestServer*
>(fn_data);
328 if (ev == MG_EV_ACCEPT ) {
329 struct mg_tls_opts opts = {0};
333 opts.ciphers =
nullptr;
334 mg_tls_init(c, &opts);
335 }
else if (ev == MG_EV_TLS_HS) {
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);
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);
358 mg_http_reply(c, 404,
"",
"url: not found\n");
366 case RestServerResult::NoError:
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");
383 return s.ToStdString();
389RestServer::IoThread::IoThread(
RestServer& parent, std::string ip)
390 : run_flag(-1), m_parent(parent), m_server_ip(std::move(ip)) {}
392void RestServer::IoThread::Run() {
394 struct mg_mgr mgr = {0};
395 mg_log_set(MG_LL_DEBUG);
399 MESSAGE_LOG <<
"Listening on " << m_server_ip <<
"\n";
400 mg_http_listen(&mgr, m_server_ip.c_str(), fn, &m_parent);
402 while (run_flag > 0) mg_mgr_poll(&mgr, 200);
405 m_parent.m_exit_sem.Post();
408void RestServer::IoThread::Stop() { run_flag = 0; }
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;
415 return r == wxSEMA_NO_ERROR;
418RestServer::Apikeys RestServer::Apikeys::Parse(
const std::string& s) {
421 for (
const auto& ip_key : ip_keys) {
423 if (words.size() != 2)
continue;
424 if (apikeys.find(words[0]) == apikeys.end()) {
425 apikeys[words[0]] = words[1];
431std::string RestServer::Apikeys::ToString()
const {
432 std::stringstream ss;
433 for (
const auto& it : *this) ss << it.first <<
":" << it.second <<
";";
439 std::lock_guard<std::mutex> lock{
ret_mutex};
440 return_status = result;
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),
453 m_io_thread(*this, m_endpoint),
456 Bind(REST_IO_EVT, &RestServer::HandleServerMessage,
this);
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();
468 if (!m_std_thread.joinable()) {
469 m_std_thread = std::thread([&]() { m_io_thread.Run(); });
475 wxLogDebug(
"Stopping REST service");
477 if (m_std_thread.joinable()) {
478 wxLogDebug(
"Stopping io thread");
480 m_io_thread.WaitUntilStopped();
485bool RestServer::LoadConfig() {
486 TheBaseConfig()->SetPath(
"/Settings/RestServer");
488 TheBaseConfig()->Read(
"ServerKeys", &key_string);
489 m_key_map = Apikeys::Parse(key_string.ToStdString());
490 TheBaseConfig()->Read(
"ServerOverwriteDuplicates", &m_overwrite,
false);
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();
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;
509 std::string new_api_key = m_pincode.
Hash();
510 if (evt_data.
api_key.size() < 10)
513 m_key_map[evt_data.
source] = new_api_key;
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();
526void RestServer::HandleServerMessage(
ObservedEvt& event) {
527 auto evt_data = UnpackEvtPointer<RestIoEvtData>(event);
529 switch (event.GetId()) {
530 case ORS_START_OF_SESSION:
532 m_upload_path = wxFileName::CreateTempFileName(
"ocpn_tul").ToStdString();
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();
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());
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();
554 if (!CheckApiKey(*evt_data)) {
559 switch (evt_data->cmd) {
560 case RestIoEvtData::Cmd::Ping:
563 case RestIoEvtData::Cmd::CheckWrite: {
565 auto dup = m_route_ctx.find_route_by_guid(guid);
566 if (!dup || evt_data->
force || m_overwrite) {
573 case RestIoEvtData::Cmd::Object: {
576 if (result.status == pugi::status_ok) {
577 m_upload_path.clear();
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);
595 case RestIoEvtData::Cmd::ListRoutes: {
596 std::stringstream ss;
598 for (
auto& r : *pRouteList) {
599 if (ss.str() !=
"[") ss <<
", ";
600 ss <<
"[ \"" << r->GetGUID() <<
"\", \"" << r->GetName() <<
"\"]";
603 std::string reply(kListRoutesReply);
609 case RestIoEvtData::Cmd::ActivateRoute: {
614 case RestIoEvtData::Cmd::ReverseRoute: {
623 case RestIoEvtData::Cmd::PluginMsg: {
624 std::ifstream f(m_upload_path);
625 m_upload_path.clear();
626 std::stringstream ss;
628 auto msg = std::make_shared<PluginMsg>(
PluginMsg(evt_data->
id, ss.str()));
629 NavMsgBus::GetInstance().
Notify(msg);
637 Route* route = GPXLoadRoute1(
object,
false,
false,
false, 0,
true);
640 bool overwrite_one =
false;
641 Route* duplicate = m_route_ctx.find_route_by_guid(route->GetGUID());
643 if (!m_overwrite && !evt_data.
force) {
645 _(
"The received route already exists on this system.\nReplace?"),
646 _(
"Always replace objects?"));
647 if (result.status != ID_STG_OK) {
651 m_overwrite = result.check1_value;
652 overwrite_one =
true;
658 if (m_overwrite || overwrite_one || evt_data.
force) {
660 m_route_ctx.delete_route(duplicate);
664 if (InsertRouteA(route, &pSet)) {
678 Track* track = GPXLoadTrack1(
object,
true,
false,
false, 0);
681 bool overwrite_one =
false;
683 Track* duplicate = m_route_ctx.find_track_by_guid(track->m_GUID);
685 if (!m_overwrite && !evt_data.
force) {
687 _(
"The received track already exists on this system.\nReplace?"),
688 _(
"Always replace objects?"));
690 if (result.status != ID_STG_OK) {
694 m_overwrite = result.check1_value;
695 overwrite_one =
true;
701 if (m_overwrite || overwrite_one || evt_data.
force) {
702 m_route_ctx.delete_track(duplicate);
707 if (InsertTrack(track,
false))
711 m_dlg_ctx.top_level_refresh();
718 GPXLoadWaypoint1(
object,
"circle",
"",
false,
false,
false, 0);
722 bool overwrite_one =
false;
724 RoutePoint* duplicate = m_route_ctx.find_wpt_by_guid(rp->m_GUID);
726 if (!m_overwrite && !evt_data.
force) {
728 _(
"The received waypoint already exists on this system.\nReplace?"),
729 _(
"Always replace objects?"));
730 if (result.status != ID_STG_OK) {
734 m_overwrite = result.check1_value;
735 overwrite_one =
true;
741 if (m_overwrite || overwrite_one || evt_data.
force) {
742 m_route_ctx.delete_waypoint(duplicate);
744 if (InsertWpt(rp, m_overwrite || overwrite_one || evt_data.
force))
748 m_dlg_ctx.top_level_refresh();
752RestIoEvtData::RestIoEvtData(Cmd c, std::string key, std::string src,
753 std::string _payload, std::string _id,
bool _force,
756 api_key(std::move(key)),
757 source(std::move(src)),
761 payload(std::move(_payload)) {}
764 : run_pincode_dlg([](const std::string&, const std::string&) -> wxDialog* {
767 update_route_mgr([]() {}),
768 run_accept_object_dlg([](
const wxString&,
const wxString&) {
771 top_level_refresh([]() {}) {}
774 : find_route_by_guid(
775 [](const wxString&) {
return static_cast<Route*
>(
nullptr); }),
777 [](
const wxString&) {
return static_cast<Track*
>(
nullptr); }),
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.
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.
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.