29 #include <unordered_map>
32 #include <curl/curl.h>
34 #include <wx/fileconf.h>
35 #include <wx/json_defs.h>
36 #include <wx/jsonreader.h>
38 #include <wx/string.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);
58 using PeerDlgPair = std::pair<PeerDlgResult, std::string>;
64 run_status_dlg([](PeerDlg, int) {
return PeerDlgResult::Cancel; }),
65 run_pincode_dlg([] {
return PeerDlgPair(PeerDlgResult::Cancel,
""); }) {}
67 static 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;
87 static 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;
109 static 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;
146 static 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;
166 static 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];
179 static 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();
200 static 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;
215 if (root.HasMember(
"version")) {
216 auto s = root[
"version"].AsString().ToStdString();
219 if (root.HasMember(
"result")) {
220 return static_cast<RestServerResult
>(root[
"result"].AsInt());
222 return RestServerResult::Void;
226 bool 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;
240 void 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);
258 static 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;
322 static 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(
" "));
351 static 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);
387 static 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);
403 static 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;
443 bool 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);
460 bool 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.
bool activate
API parameter, activate route after transfer.
EventVar & progress
Notified with transfer percent progress (0-100).
SemanticVersion api_version
server API version
std::function< PeerDlgResult(PeerDlg, int)> run_status_dlg
Dialog displaying status (good, bad, ...)
std::function< std::pair< PeerDlgResult, std::string >)> run_pincode_dlg
Pin confirm dialog, returns new {0, user_pin} or {error_code, error msg)
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.