27#include <unordered_map>
32#include <wx/fileconf.h>
33#include <wx/json_defs.h>
34#include <wx/jsonreader.h>
39#include "model/nav_object_database.h"
50 memory = (
char*)malloc(1);
56using PeerDlgPair = std::pair<PeerDlgResult, std::string>;
62 run_status_dlg([](PeerDlg, int) {
return PeerDlgResult::Cancel; }),
63 run_pincode_dlg([] {
return PeerDlgPair(PeerDlgResult::Cancel,
""); }) {}
65static size_t WriteMemoryCallback(
void* contents,
size_t size,
size_t nmemb,
67 size_t realsize = size * nmemb;
70 char* ptr = (
char*)realloc(mem->memory, mem->size + realsize + 1);
73 std::cerr <<
"not enough memory (realloc returned NULL)\n";
78 memcpy(&(mem->memory[mem->size]), contents, realsize);
79 mem->size += realsize;
80 mem->memory[mem->size] = 0;
85static int xfer_callback(
void* clientp, [[maybe_unused]] curl_off_t dltotal,
86 [[maybe_unused]] curl_off_t dlnow, curl_off_t ultotal,
88 auto peer_data =
static_cast<PeerData*
>(clientp);
92 peer_data->progress.Notify(100 * ulnow / ultotal,
"");
96#ifdef CURL_PROGRESSFUNC_CONTINUE
97 return CURL_PROGRESSFUNC_CONTINUE;
107static long ApiPost(
const std::string& url,
const std::string& body,
109 long response_code = -1;
112 CURL* c = curl_easy_init();
114 curl_easy_setopt(c, CURLOPT_ENCODING,
"identity");
115 curl_easy_setopt(c, CURLOPT_URL, url.c_str());
116 curl_easy_setopt(c, CURLOPT_SSL_VERIFYPEER, 0L);
117 curl_easy_setopt(c, CURLOPT_SSL_VERIFYHOST, 0L);
119 curl_easy_setopt(c, CURLOPT_POSTFIELDSIZE, body.size());
120 curl_easy_setopt(c, CURLOPT_COPYPOSTFIELDS, body.c_str());
121 curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
122 curl_easy_setopt(c, CURLOPT_WRITEDATA, (
void*)response);
123 curl_easy_setopt(c, CURLOPT_NOPROGRESS, 0);
124 curl_easy_setopt(c, CURLOPT_XFERINFODATA, &peer_data);
125 curl_easy_setopt(c, CURLOPT_XFERINFOFUNCTION, xfer_callback);
126 curl_easy_setopt(c, CURLOPT_TIMEOUT, 20);
128 curl_easy_setopt(c, CURLOPT_VERBOSE,
129 wxLog::GetLogLevel() >= wxLOG_Debug ? 1 : 0);
131 CURLcode result = curl_easy_perform(c);
133 if (result == CURLE_OK)
134 curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &response_code);
136 curl_easy_cleanup(c);
137 return response_code == -1 ? -
static_cast<long>(result) : response_code;
144static int ApiGet(
const std::string& url,
const MemoryStruct* chunk,
146 long response_code = -1;
148 CURL* c = curl_easy_init();
149 curl_easy_setopt(c, CURLOPT_ENCODING,
"identity");
150 curl_easy_setopt(c, CURLOPT_URL, url.c_str());
151 curl_easy_setopt(c, CURLOPT_SSL_VERIFYPEER, 0L);
152 curl_easy_setopt(c, CURLOPT_SSL_VERIFYHOST, 0L);
153 curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
154 curl_easy_setopt(c, CURLOPT_WRITEDATA, (
void*)chunk);
155 curl_easy_setopt(c, CURLOPT_NOPROGRESS, 1);
156 if (timeout != 0) curl_easy_setopt(c, CURLOPT_TIMEOUT, timeout);
157 CURLcode result = curl_easy_perform(c);
158 if (result == CURLE_OK)
159 curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &response_code);
160 curl_easy_cleanup(c);
161 return response_code == -1 ? -
static_cast<long>(result) : response_code;
164static std::string GetClientKey(std::string& server_name) {
167 auto key_string = server_keys.Get(
"");
168 auto entries =
ocpn::split(key_string.c_str(),
";");
169 for (
const auto& entry : entries) {
171 if (server_key.size() != 2)
continue;
172 if (server_key[0] == server_name)
return server_key[1];
177static void SaveClientKey(std::string& server_name, std::string key) {
180 auto config_server_keys = server_keys.Get(
"");
182 auto server_keys_list =
ocpn::split(config_server_keys.c_str(),
";");
183 std::unordered_map<std::string, std::string> key_by_server;
184 for (
const auto& item : server_keys_list) {
185 auto server_and_key =
ocpn::split(item.c_str(),
":");
186 if (server_and_key.size() != 2)
continue;
187 key_by_server[server_and_key[0]] = server_and_key[1];
189 key_by_server[server_name] = key;
191 config_server_keys =
"";
192 for (
const auto& it : key_by_server) {
193 config_server_keys += it.first +
":" + it.second +
";";
195 server_keys.Set(config_server_keys);
196 wxLog::FlushActive();
200 wxString body(reply.memory);
203 int num_errors = reader.
Parse(body, &root);
204 if (num_errors != 0) {
205 for (
const auto& error : reader.GetErrors()) {
206 wxLogMessage(
"Json server reply parse error: %s",
207 error.ToStdString().c_str());
211 return RestServerResult::Void;
214 auto s = root[
"version"].
AsString().ToStdString();
220 return RestServerResult::Void;
224bool CheckKey(
const std::string& key,
PeerData peer_data) {
225 std::stringstream url;
226 url <<
"https://" << peer_data.dest_ip_address <<
"/api/ping"
227 <<
"?source=" << g_hostname <<
"&apikey=" << key;
229 long status = ApiGet(url.str(), &reply, 5);
234 auto result = ParseServerJson(reply, peer_data);
235 return result != RestServerResult::NewPinRequested;
238void GetApiVersion(
PeerData& peer_data) {
240 std::stringstream url;
241 url <<
"https://" << peer_data.dest_ip_address <<
"/api/get-version";
245 long response_code = ApiGet(url.str(), &chunk, 2);
247 if (response_code == 200) {
248 ParseServerJson(chunk, peer_data);
256static bool GetApiKey(
PeerData& peer_data, std::string& key) {
261 api_key = GetClientKey(peer_data.server_name);
263 api_key =
"0123456789abc";
264 std::stringstream url;
265 url <<
"https://" << peer_data.dest_ip_address <<
"/api/ping"
266 <<
"?source=" << g_hostname <<
"&apikey=" << api_key;
268 int status = ApiGet(url.str(), &chunk, 3);
270 auto r = peer_data.
run_status_dlg(PeerDlg::InvalidHttpResponse, status);
271 if (r == PeerDlgResult::Ok)
continue;
274 auto result = ParseServerJson(chunk, peer_data);
276 case RestServerResult::NewPinRequested: {
278 if (pin_result.first == PeerDlgResult::HasPincode) {
279 std::string tentative_pin =
ocpn::trim(pin_result.second);
280 unsigned int_pin = atoi(tentative_pin.c_str());
282 api_key = pincode.Hash();
283 GetApiVersion(peer_data);
285 api_key = pincode.CompatHash();
287 if (!CheckKey(api_key, peer_data)) {
289 if (r == PeerDlgResult::Ok)
continue;
292 SaveClientKey(peer_data.server_name, api_key);
293 }
else if (pin_result.first == PeerDlgResult::Cancel) {
297 static_cast<int>(result));
298 if (r == PeerDlgResult::Ok)
continue;
302 case RestServerResult::GenericError:
305 case RestServerResult::NoError:
309 static_cast<int>(result));
310 if (r == PeerDlgResult::Ok)
continue;
320static std::string PeerDataToXml(
PeerData& peer_data) {
322 std::ostringstream stream;
323 int total = peer_data.routes.size() + peer_data.tracks.size() +
324 peer_data.routepoints.size();
326 for (
auto r : peer_data.routes) {
332 for (
auto r : peer_data.routepoints) {
334 gpx.AddGPXWaypoint(r);
338 for (
auto r : peer_data.tracks) {
344 gpx.save(stream, PUGIXML_TEXT(
" "));
349static void SendObjects(std::string& body,
const std::string& api_key,
353 std::stringstream url;
354 url <<
"https://" << peer_data.dest_ip_address <<
"/api/rx_object"
355 <<
"?source=" << g_hostname <<
"&apikey=" << api_key;
356 if (peer_data.
overwrite) url <<
"&force=1";
357 if (peer_data.
activate) url <<
"&activate=1";
360 long response_code = ApiPost(url.str(), body, peer_data, &chunk);
361 if (response_code == 200) {
362 wxString json(chunk.memory);
366 int num_errors = reader.
Parse(json, &root);
368 wxLogDebug(
"SendObjects, parse errors: %d", num_errors);
370 int result = root[
"result"].
AsInt();
378 peer_data.
run_status_dlg(PeerDlg::InvalidHttpResponse, response_code);
385static int CheckChunk(
struct MemoryStruct& chunk,
const std::string& guid) {
386 wxString body(chunk.memory);
389 int num_errors = reader.
Parse(body, &root);
391 wxLogDebug(
"CheckChunk: parsing errors found: %d", num_errors);
392 int result = root[
"result"].
AsInt();
394 wxLogDebug(
"Server rejected guid %s, status: %d", guid.c_str(), result);
401static bool CheckObjects(
const std::string& api_key,
PeerData& peer_data) {
402 std::stringstream url;
403 url <<
"https://" << peer_data.dest_ip_address <<
"/api/writable"
404 <<
"?source=" << g_hostname <<
"&apikey=" << api_key <<
"&guid=";
405 for (
const auto& r : peer_data.routes) {
406 std::string guid = r->GetGUID().ToStdString();
407 std::string full_url = url.str() + guid;
409 if (ApiGet(full_url, &chunk) != 200) {
410 wxLogMessage(
"Cannot check /api/writable for route %s", guid.c_str());
413 int result = CheckChunk(chunk, guid);
414 if (result != 0)
return false;
416 for (
const auto& t : peer_data.tracks) {
417 std::string guid = t->m_GUID.ToStdString();
418 std::string full_url = url.str() + guid;
420 if (ApiGet(full_url, &chunk) != 200) {
421 wxLogMessage(
"Cannot check /api/writable for track %s", guid.c_str());
424 int result = CheckChunk(chunk, guid);
425 if (result != 0)
return false;
427 for (
const auto& rp : peer_data.routepoints) {
428 std::string guid = rp->m_GUID.ToStdString();
429 std::string full_url = url.str() + guid;
431 if (ApiGet(full_url, &chunk) != 200) {
432 wxLogMessage(
"Cannot check /api/writable for waypoint %s", guid.c_str());
435 int result = CheckChunk(chunk, guid);
436 if (result != 0)
return false;
442 if (peer_data.routes.empty() && peer_data.routepoints.empty() &&
443 peer_data.tracks.empty()) {
447 bool apikey_ok = GetApiKey(peer_data, api_key);
448 if (!apikey_ok)
return false;
453 std::string body = PeerDataToXml(peer_data);
454 SendObjects(body, api_key, peer_data);
459 if (peer_data.routes.empty() && peer_data.routepoints.empty() &&
460 peer_data.tracks.empty()) {
464 bool apikey_ok = GetApiKey(peer_data, apikey);
465 if (!apikey_ok)
return false;
466 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.
void Notify() override
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.
Global variables stored in configuration file.
std::vector< std::string > split(const char *token_string, const std::string &delimiter)
Return vector of items in s separated by delimiter.
std::string trim(std::string s)
Strip possibly trailing and/or leading space characters in s.
Notify()/Listen() configuration variable wrapper.
Miscellaneous utilities, many of which string related.
bool SendNavobjects(PeerData &peer_data)
Send data to server peer.
bool CheckNavObjects(PeerData &peer_data)
Check if server peer deems that writing these objects can be accepted i.
Peer client non-gui abstraction.
RestServerResult
Return codes from HandleServerMessage and eventually in the http response.
Semantic version encode/decode object.
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.