29#include <unordered_map>
34#include <wx/fileconf.h>
35#include <wx/json_defs.h>
36#include <wx/jsonreader.h>
40#include "model/config_vars.h"
41#include "model/nav_object_database.h"
42#include "model/peer_client.h"
43#include "model/ocpn_utils.h"
44#include "model/rest_server.h"
45#include "model/semantic_vers.h"
46#include "observable_confvar.h"
52 memory = (
char*)malloc(1);
58using PeerDlgPair = std::pair<PeerDlgResult, std::string>;
64 run_status_dlg([](PeerDlg, int) {
return PeerDlgResult::Cancel; }),
65 run_pincode_dlg([] {
return PeerDlgPair(PeerDlgResult::Cancel,
""); }) {}
67static size_t WriteMemoryCallback(
void* contents,
size_t size,
size_t nmemb,
69 size_t realsize = size * nmemb;
72 char* ptr = (
char*)realloc(mem->memory, mem->size + realsize + 1);
75 std::cerr <<
"not enough memory (realloc returned NULL)\n";
80 memcpy(&(mem->memory[mem->size]), contents, realsize);
81 mem->size += realsize;
82 mem->memory[mem->size] = 0;
87static int xfer_callback(
void* clientp, [[maybe_unused]] curl_off_t dltotal,
88 [[maybe_unused]] curl_off_t dlnow, curl_off_t ultotal,
90 auto peer_data =
static_cast<PeerData*
>(clientp);
94 peer_data->progress.Notify(100 * ulnow / ultotal,
"");
98#ifdef CURL_PROGRESSFUNC_CONTINUE
99 return CURL_PROGRESSFUNC_CONTINUE;
109static long ApiPost(
const std::string& url,
const std::string& body,
111 long response_code = -1;
114 CURL* c = curl_easy_init();
116 curl_easy_setopt(c, CURLOPT_ENCODING,
"identity");
117 curl_easy_setopt(c, CURLOPT_URL, url.c_str());
118 curl_easy_setopt(c, CURLOPT_SSL_VERIFYPEER, 0L);
119 curl_easy_setopt(c, CURLOPT_SSL_VERIFYHOST, 0L);
121 curl_easy_setopt(c, CURLOPT_POSTFIELDSIZE, body.size());
122 curl_easy_setopt(c, CURLOPT_COPYPOSTFIELDS, body.c_str());
123 curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
124 curl_easy_setopt(c, CURLOPT_WRITEDATA, (
void*)response);
125 curl_easy_setopt(c, CURLOPT_NOPROGRESS, 0);
126 curl_easy_setopt(c, CURLOPT_XFERINFODATA, &peer_data);
127 curl_easy_setopt(c, CURLOPT_XFERINFOFUNCTION, xfer_callback);
128 curl_easy_setopt(c, CURLOPT_TIMEOUT, 20);
130 curl_easy_setopt(c, CURLOPT_VERBOSE,
131 wxLog::GetLogLevel() >= wxLOG_Debug ? 1 : 0);
133 CURLcode result = curl_easy_perform(c);
135 if (result == CURLE_OK)
136 curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &response_code);
138 curl_easy_cleanup(c);
139 return response_code == -1 ? -
static_cast<long>(result) : response_code;
146static int ApiGet(
const std::string& url,
const MemoryStruct* chunk,
148 long response_code = -1;
150 CURL* c = curl_easy_init();
151 curl_easy_setopt(c, CURLOPT_ENCODING,
"identity");
152 curl_easy_setopt(c, CURLOPT_URL, url.c_str());
153 curl_easy_setopt(c, CURLOPT_SSL_VERIFYPEER, 0L);
154 curl_easy_setopt(c, CURLOPT_SSL_VERIFYHOST, 0L);
155 curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
156 curl_easy_setopt(c, CURLOPT_WRITEDATA, (
void*)chunk);
157 curl_easy_setopt(c, CURLOPT_NOPROGRESS, 1);
158 if (timeout != 0) curl_easy_setopt(c, CURLOPT_TIMEOUT, timeout);
159 CURLcode result = curl_easy_perform(c);
160 if (result == CURLE_OK)
161 curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &response_code);
162 curl_easy_cleanup(c);
163 return response_code == -1 ? -
static_cast<long>(result) : response_code;
166static std::string GetClientKey(std::string& server_name) {
169 auto key_string = server_keys.Get(
"");
170 auto entries = ocpn::split(key_string.c_str(),
";");
171 for (
const auto& entry : entries) {
172 auto server_key = ocpn::split(entry.c_str(),
":");
173 if (server_key.size() != 2)
continue;
174 if (server_key[0] == server_name)
return server_key[1];
179static void SaveClientKey(std::string& server_name, std::string key) {
182 auto config_server_keys = server_keys.Get(
"");
184 auto server_keys_list = ocpn::split(config_server_keys.c_str(),
";");
185 std::unordered_map<std::string, std::string> key_by_server;
186 for (
const auto& item : server_keys_list) {
187 auto server_and_key = ocpn::split(item.c_str(),
":");
188 if (server_and_key.size() != 2)
continue;
189 key_by_server[server_and_key[0]] = server_and_key[1];
191 key_by_server[server_name] = key;
193 config_server_keys =
"";
194 for (
const auto& it : key_by_server) {
195 config_server_keys += it.first +
":" + it.second +
";";
197 server_keys.Set(config_server_keys);
198 wxLog::FlushActive();
200static RestServerResult ParseServerJson(
const MemoryStruct& reply,
202 wxString body(reply.memory);
205 int num_errors = reader.
Parse(body, &root);
206 if (num_errors != 0) {
207 for (
const auto& error : reader.GetErrors()) {
208 wxLogMessage(
"Json server reply parse error: %s",
209 error.ToStdString().c_str());
213 return RestServerResult::Void;
216 auto s = root[
"version"].
AsString().ToStdString();
220 return static_cast<RestServerResult
>(root[
"result"].
AsInt());
222 return RestServerResult::Void;
226bool CheckKey(
const std::string& key,
PeerData peer_data) {
227 std::stringstream url;
228 url <<
"https://" << peer_data.dest_ip_address <<
"/api/ping"
229 <<
"?source=" << g_hostname <<
"&apikey=" << key;
231 long status = ApiGet(url.str(), &reply, 5);
236 auto result = ParseServerJson(reply, peer_data);
237 return result != RestServerResult::NewPinRequested;
240void GetApiVersion(
PeerData& peer_data) {
242 std::stringstream url;
243 url <<
"https://" << peer_data.dest_ip_address <<
"/api/get-version";
247 long response_code = ApiGet(url.str(), &chunk, 2);
249 if (response_code == 200) {
250 ParseServerJson(chunk, peer_data);
258static bool GetApiKey(
PeerData& peer_data, std::string& key) {
263 api_key = GetClientKey(peer_data.server_name);
265 api_key =
"0123456789abc";
266 std::stringstream url;
267 url <<
"https://" << peer_data.dest_ip_address <<
"/api/ping"
268 <<
"?source=" << g_hostname <<
"&apikey=" << api_key;
270 int status = ApiGet(url.str(), &chunk, 3);
272 auto r = peer_data.
run_status_dlg(PeerDlg::InvalidHttpResponse, status);
273 if (r == PeerDlgResult::Ok)
continue;
276 auto result = ParseServerJson(chunk, peer_data);
278 case RestServerResult::NewPinRequested: {
280 if (pin_result.first == PeerDlgResult::HasPincode) {
281 std::string tentative_pin = ocpn::trim(pin_result.second);
282 unsigned int_pin = atoi(tentative_pin.c_str());
284 api_key = pincode.Hash();
285 GetApiVersion(peer_data);
287 api_key = pincode.CompatHash();
289 if (!CheckKey(api_key, peer_data)) {
291 if (r == PeerDlgResult::Ok)
continue;
294 SaveClientKey(peer_data.server_name, api_key);
295 }
else if (pin_result.first == PeerDlgResult::Cancel) {
299 static_cast<int>(result));
300 if (r == PeerDlgResult::Ok)
continue;
304 case RestServerResult::GenericError:
307 case RestServerResult::NoError:
311 static_cast<int>(result));
312 if (r == PeerDlgResult::Ok)
continue;
322static std::string PeerDataToXml(
PeerData& peer_data) {
324 std::ostringstream stream;
325 int total = peer_data.routes.size() + peer_data.tracks.size() +
326 peer_data.routepoints.size();
328 for (
auto r : peer_data.routes) {
334 for (
auto r : peer_data.routepoints) {
336 gpx.AddGPXWaypoint(r);
340 for (
auto r : peer_data.tracks) {
346 gpx.save(stream, PUGIXML_TEXT(
" "));
351static void SendObjects(std::string& body,
const std::string& api_key,
355 std::stringstream url;
356 url <<
"https://" << peer_data.dest_ip_address <<
"/api/rx_object"
357 <<
"?source=" << g_hostname <<
"&apikey=" << api_key;
358 if (peer_data.
overwrite) url <<
"&force=1";
359 if (peer_data.
activate) url <<
"&activate=1";
362 long response_code = ApiPost(url.str(), body, peer_data, &chunk);
363 if (response_code == 200) {
364 wxString json(chunk.memory);
368 int num_errors = reader.
Parse(json, &root);
370 wxLogDebug(
"SendObjects, parse errors: %d", num_errors);
372 int result = root[
"result"].
AsInt();
380 peer_data.
run_status_dlg(PeerDlg::InvalidHttpResponse, response_code);
387static int CheckChunk(
struct MemoryStruct& chunk,
const std::string& guid) {
388 wxString body(chunk.memory);
391 int num_errors = reader.
Parse(body, &root);
393 wxLogDebug(
"CheckChunk: parsing errors found: %d", num_errors);
394 int result = root[
"result"].
AsInt();
396 wxLogDebug(
"Server rejected guid %s, status: %d", guid.c_str(), result);
403static bool CheckObjects(
const std::string& api_key,
PeerData& peer_data) {
404 std::stringstream url;
405 url <<
"https://" << peer_data.dest_ip_address <<
"/api/writable"
406 <<
"?source=" << g_hostname <<
"&apikey=" << api_key <<
"&guid=";
407 for (
const auto& r : peer_data.routes) {
408 std::string guid = r->GetGUID().ToStdString();
409 std::string full_url = url.str() + guid;
411 if (ApiGet(full_url, &chunk) != 200) {
412 wxLogMessage(
"Cannot check /api/writable for route %s", guid.c_str());
415 int result = CheckChunk(chunk, guid);
416 if (result != 0)
return false;
418 for (
const auto& t : peer_data.tracks) {
419 std::string guid = t->m_GUID.ToStdString();
420 std::string full_url = url.str() + guid;
422 if (ApiGet(full_url, &chunk) != 200) {
423 wxLogMessage(
"Cannot check /api/writable for track %s", guid.c_str());
426 int result = CheckChunk(chunk, guid);
427 if (result != 0)
return false;
429 for (
const auto& rp : peer_data.routepoints) {
430 std::string guid = rp->m_GUID.ToStdString();
431 std::string full_url = url.str() + guid;
433 if (ApiGet(full_url, &chunk) != 200) {
434 wxLogMessage(
"Cannot check /api/writable for waypoint %s", guid.c_str());
437 int result = CheckChunk(chunk, guid);
438 if (result != 0)
return false;
443bool SendNavobjects(
PeerData& peer_data) {
444 if (peer_data.routes.empty() && peer_data.routepoints.empty() &&
445 peer_data.tracks.empty()) {
449 bool apikey_ok = GetApiKey(peer_data, api_key);
450 if (!apikey_ok)
return false;
455 std::string body = PeerDataToXml(peer_data);
456 SendObjects(body, api_key, peer_data);
460bool CheckNavObjects(
PeerData& peer_data) {
461 if (peer_data.routes.empty() && peer_data.routepoints.empty() &&
462 peer_data.tracks.empty()) {
466 bool apikey_ok = GetApiKey(peer_data, apikey);
467 if (!apikey_ok)
return false;
468 return CheckObjects(apikey, peer_data);
Wrapper for configuration variables which lives in a wxBaseConfig object.
Generic event handling between MVC Model and Controller based on a shared EventVar variable.
const void Notify()
Notify all listeners, no data supplied.
A random generated int value with accessors for string and hashcode.
int Parse(const wxString &doc, wxJSONValue *val)
Parse the JSON document.
The JSON value class implementation.
bool HasMember(unsigned index) const
Return TRUE if the object contains an element at the specified index.
wxString AsString() const
Return the stored value as a wxWidget's string.
int AsInt() const
Return the stored value as an integer.
bool activate
API parameter, activate route after transfer.
EventVar & progress
Notified with transfer percent progress (0-100).
std::function< std::pair< PeerDlgResult, std::string >()> run_pincode_dlg
Pin confirm dialog, returns new {0, user_pin} or {error_code, error msg)
SemanticVersion api_version
server API version
std::function< PeerDlgResult(PeerDlg, int)> run_status_dlg
Dialog displaying status (good, bad, ...)
bool overwrite
API parameter, force overwrite w/o server dialogs.
Versions uses a modified semantic versioning scheme: major.minor.revision.post-tag+build.
static SemanticVersion parse(std::string s)
Parse a version string, sets major == -1 on errors.