27#include <unordered_map>
32#include "nlohmann/json.hpp"
34#include <wx/fileconf.h>
38#include "observable/configvar.h"
41#include "model/nav_object_database.h"
51 memory = (
char*)malloc(1);
57using PeerDlgPair = std::pair<PeerDlgResult, std::string>;
59PeerData::PeerData(obs::EventVar& p)
63 run_status_dlg([](PeerDlg, int) {
return PeerDlgResult::Cancel; }),
64 run_pincode_dlg([] {
return PeerDlgPair(PeerDlgResult::Cancel,
""); }) {}
66static size_t WriteMemoryCallback(
void* contents,
size_t size,
size_t nmemb,
68 size_t realsize = size * nmemb;
71 char* ptr = (
char*)realloc(mem->memory, mem->size + realsize + 1);
74 std::cerr <<
"not enough memory (realloc returned NULL)\n";
79 memcpy(&(mem->memory[mem->size]), contents, realsize);
80 mem->size += realsize;
81 mem->memory[mem->size] = 0;
86static int xfer_callback(
void* clientp, [[maybe_unused]] curl_off_t dltotal,
87 [[maybe_unused]] curl_off_t dlnow, curl_off_t ultotal,
89 auto peer_data =
static_cast<PeerData*
>(clientp);
93 peer_data->progress.Notify(100 * ulnow / ultotal,
"");
97#ifdef CURL_PROGRESSFUNC_CONTINUE
98 return CURL_PROGRESSFUNC_CONTINUE;
108static long ApiPost(
const std::string& url,
const std::string& body,
110 long response_code = -1;
113 CURL* c = curl_easy_init();
115 curl_easy_setopt(c, CURLOPT_ENCODING,
"identity");
116 curl_easy_setopt(c, CURLOPT_URL, url.c_str());
117 curl_easy_setopt(c, CURLOPT_SSL_VERIFYPEER, 0L);
118 curl_easy_setopt(c, CURLOPT_SSL_VERIFYHOST, 0L);
120 curl_easy_setopt(c, CURLOPT_POSTFIELDSIZE, body.size());
121 curl_easy_setopt(c, CURLOPT_COPYPOSTFIELDS, body.c_str());
122 curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
123 curl_easy_setopt(c, CURLOPT_WRITEDATA, (
void*)response);
124 curl_easy_setopt(c, CURLOPT_NOPROGRESS, 0);
125 curl_easy_setopt(c, CURLOPT_XFERINFODATA, &peer_data);
126 curl_easy_setopt(c, CURLOPT_XFERINFOFUNCTION, xfer_callback);
127 curl_easy_setopt(c, CURLOPT_TIMEOUT, 20);
129 curl_easy_setopt(c, CURLOPT_VERBOSE,
130 wxLog::GetLogLevel() >= wxLOG_Debug ? 1 : 0);
132 CURLcode result = curl_easy_perform(c);
134 if (result == CURLE_OK)
135 curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &response_code);
137 curl_easy_cleanup(c);
138 return response_code == -1 ? -
static_cast<long>(result) : response_code;
145static int ApiGet(
const std::string& url,
const MemoryStruct* chunk,
147 long response_code = -1;
149 CURL* c = curl_easy_init();
150 curl_easy_setopt(c, CURLOPT_ENCODING,
"identity");
151 curl_easy_setopt(c, CURLOPT_URL, url.c_str());
152 curl_easy_setopt(c, CURLOPT_SSL_VERIFYPEER, 0L);
153 curl_easy_setopt(c, CURLOPT_SSL_VERIFYHOST, 0L);
154 curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
155 curl_easy_setopt(c, CURLOPT_WRITEDATA, (
void*)chunk);
156 curl_easy_setopt(c, CURLOPT_NOPROGRESS, 1);
157 if (timeout != 0) curl_easy_setopt(c, CURLOPT_TIMEOUT, timeout);
158 CURLcode result = curl_easy_perform(c);
159 if (result == CURLE_OK)
160 curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &response_code);
161 curl_easy_cleanup(c);
162 return response_code == -1 ? -
static_cast<long>(result) : response_code;
165static std::string GetClientKey(std::string& server_name) {
166 obs::ConfigVar<std::string> server_keys(
"/Settings/RESTClient",
"ServerKeys",
168 auto key_string = server_keys.Get(
"");
169 auto entries =
ocpn::split(key_string.c_str(),
";");
170 for (
const auto& entry : entries) {
172 if (server_key.size() != 2)
continue;
173 if (server_key[0] == server_name)
return server_key[1];
178static void SaveClientKey(std::string& server_name, std::string key) {
179 obs::ConfigVar<std::string> server_keys(
"/Settings/RESTClient",
"ServerKeys",
181 auto config_server_keys = server_keys.Get(
"");
183 auto server_keys_list =
ocpn::split(config_server_keys.c_str(),
";");
184 std::unordered_map<std::string, std::string> key_by_server;
185 for (
const auto& item : server_keys_list) {
186 auto server_and_key =
ocpn::split(item.c_str(),
":");
187 if (server_and_key.size() != 2)
continue;
188 key_by_server[server_and_key[0]] = server_and_key[1];
190 key_by_server[server_name] = key;
192 config_server_keys =
"";
193 for (
const auto& it : key_by_server) {
194 config_server_keys += it.first +
":" + it.second +
";";
196 server_keys.Set(config_server_keys);
197 wxLog::FlushActive();
201 std::string body(reply.memory);
204 root = nlohmann::json::parse(body);
205 }
catch (nlohmann::json::exception& e) {
206 wxLogMessage(
"Json server reply parse error: %s", e.what());
209 return RestServerResult::Void;
211 if (root.contains(
"version")) {
212 auto s = root[
"version"].get<std::string>();
215 if (root.contains(
"result")) {
218 return RestServerResult::Void;
222bool CheckKey(
const std::string& key,
PeerData peer_data) {
223 std::stringstream url;
224 url <<
"https://" << peer_data.dest_ip_address <<
"/api/ping"
225 <<
"?source=" << g_hostname <<
"&apikey=" << key;
227 long status = ApiGet(url.str(), &reply, 5);
232 auto result = ParseServerJson(reply, peer_data);
233 return result != RestServerResult::NewPinRequested;
236void GetApiVersion(
PeerData& peer_data) {
238 std::stringstream url;
239 url <<
"https://" << peer_data.dest_ip_address <<
"/api/get-version";
243 long response_code = ApiGet(url.str(), &chunk, 2);
245 if (response_code == 200) {
246 ParseServerJson(chunk, peer_data);
254static bool GetApiKey(
PeerData& peer_data, std::string& key) {
259 api_key = GetClientKey(peer_data.server_name);
261 api_key =
"0123456789abc";
262 std::stringstream url;
263 url <<
"https://" << peer_data.dest_ip_address <<
"/api/ping"
264 <<
"?source=" << g_hostname <<
"&apikey=" << api_key;
266 int status = ApiGet(url.str(), &chunk, 3);
268 auto r = peer_data.
run_status_dlg(PeerDlg::InvalidHttpResponse, status);
269 if (r == PeerDlgResult::Ok)
continue;
272 auto result = ParseServerJson(chunk, peer_data);
274 case RestServerResult::NewPinRequested: {
276 if (pin_result.first == PeerDlgResult::HasPincode) {
277 std::string tentative_pin =
ocpn::trim(pin_result.second);
278 unsigned int_pin = atoi(tentative_pin.c_str());
280 api_key = pincode.Hash();
281 GetApiVersion(peer_data);
283 api_key = pincode.CompatHash();
285 if (!CheckKey(api_key, peer_data)) {
287 if (r == PeerDlgResult::Ok)
continue;
290 SaveClientKey(peer_data.server_name, api_key);
291 }
else if (pin_result.first == PeerDlgResult::Cancel) {
295 static_cast<int>(result));
296 if (r == PeerDlgResult::Ok)
continue;
300 case RestServerResult::GenericError:
303 case RestServerResult::NoError:
307 static_cast<int>(result));
308 if (r == PeerDlgResult::Ok)
continue;
318static std::string PeerDataToXml(
PeerData& peer_data) {
320 std::ostringstream stream;
321 int total = peer_data.routes.size() + peer_data.tracks.size() +
322 peer_data.routepoints.size();
324 for (
auto r : peer_data.routes) {
327 peer_data.
progress.Notify(100 * gpxgen / total,
"");
330 for (
auto r : peer_data.routepoints) {
332 gpx.AddGPXWaypoint(r);
333 peer_data.
progress.Notify(100 * gpxgen / total,
"");
336 for (
auto r : peer_data.tracks) {
339 peer_data.
progress.Notify(100 * gpxgen / total,
"");
342 gpx.save(stream, PUGIXML_TEXT(
" "));
347static void SendObjects(std::string& body,
const std::string& api_key,
351 std::stringstream url;
352 url <<
"https://" << peer_data.dest_ip_address <<
"/api/rx_object"
353 <<
"?source=" << g_hostname <<
"&apikey=" << api_key;
354 if (peer_data.
overwrite) url <<
"&force=1";
355 if (peer_data.
activate) url <<
"&activate=1";
358 long response_code = ApiPost(url.str(), body, peer_data, &chunk);
359 if (response_code == 200) {
360 std::string json(chunk.memory);
363 root = nlohmann::json::parse(json);
364 }
catch (nlohmann::json::exception& e) {
365 wxLogDebug(
"SendObjects, parse errors: %s", e.what());
368 int result = root[
"result"].get<
int>();
376 peer_data.
run_status_dlg(PeerDlg::InvalidHttpResponse, response_code);
383static int CheckChunk(
struct MemoryStruct& chunk,
const std::string& guid) {
384 std::string body(chunk.memory);
387 root = nlohmann::json::parse(body);
388 }
catch (nlohmann::json::exception& e) {
389 wxLogDebug(
"CheckChunk: parsing errors found: %s", e.what());
391 if (!root.contains(
"result"))
return 0;
392 int result = root[
"result"].get<
int>();
394 wxLogDebug(
"Server rejected guid %s, status: %d", guid.c_str(), result);
399static bool CheckObjects(
const std::string& api_key,
PeerData& peer_data) {
400 std::stringstream url;
401 url <<
"https://" << peer_data.dest_ip_address <<
"/api/writable"
402 <<
"?source=" << g_hostname <<
"&apikey=" << api_key <<
"&guid=";
403 for (
const auto& r : peer_data.routes) {
404 std::string guid = r->GetGUID().ToStdString();
405 std::string full_url = url.str() + guid;
407 if (ApiGet(full_url, &chunk) != 200) {
408 wxLogMessage(
"Cannot check /api/writable for route %s", guid.c_str());
411 int result = CheckChunk(chunk, guid);
412 if (result != 0)
return false;
414 for (
const auto& t : peer_data.tracks) {
415 std::string guid = t->m_GUID.ToStdString();
416 std::string full_url = url.str() + guid;
418 if (ApiGet(full_url, &chunk) != 200) {
419 wxLogMessage(
"Cannot check /api/writable for track %s", guid.c_str());
422 int result = CheckChunk(chunk, guid);
423 if (result != 0)
return false;
425 for (
const auto& rp : peer_data.routepoints) {
426 std::string guid = rp->m_GUID.ToStdString();
427 std::string full_url = url.str() + guid;
429 if (ApiGet(full_url, &chunk) != 200) {
430 wxLogMessage(
"Cannot check /api/writable for waypoint %s", guid.c_str());
433 int result = CheckChunk(chunk, guid);
434 if (result != 0)
return false;
440 if (peer_data.routes.empty() && peer_data.routepoints.empty() &&
441 peer_data.tracks.empty()) {
445 bool apikey_ok = GetApiKey(peer_data, api_key);
446 if (!apikey_ok)
return false;
451 std::string body = PeerDataToXml(peer_data);
452 SendObjects(body, api_key, peer_data);
457 if (peer_data.routes.empty() && peer_data.routepoints.empty() &&
458 peer_data.tracks.empty()) {
462 bool apikey_ok = GetApiKey(peer_data, apikey);
463 if (!apikey_ok)
return false;
464 return CheckObjects(apikey, peer_data);
A random generated int value with accessors for string and hashcode.
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.
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.
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, ...)
obs::EventVar & progress
Notified with transfer percent progress (0-100).
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.