OpenCPN Partial API docs
Loading...
Searching...
No Matches
navmsg_filter.cpp
Go to the documentation of this file.
1/***************************************************************************
2 * Copyright (C) 2025 Alec Leamas *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
16 **************************************************************************/
17
24#include <fstream>
25#include <wx/log.h>
26
27#include "nlohmann/json.hpp"
28
29#include "model/base_platform.h"
30#include "model/navmsg_filter.h"
31
32#include "std_filesystem.h"
33
34using Direction = NavmsgStatus::Direction;
35using State = NavmsgStatus::State;
36using Accepted = NavmsgStatus::Accepted;
37
38static const std::unordered_map<Accepted, std::string> StringByAccepted = {
39 {Accepted::kOk, "Ok"},
40 {Accepted::kFilteredNoOutput, "FilteredNoOutput"},
41 {Accepted::kFilteredDropped, "FilteredDropped"},
42 {Accepted::kNone, "None"}};
43
44std::string NavmsgStatus::AcceptedToString(Accepted a) {
45 try {
46 return StringByAccepted.at(a);
47 } catch (std::out_of_range& e) {
48 std::cout << "Error: " << e.what() << " :" << static_cast<int>(a) << "\n";
49 assert(false && "Bad Accepted state");
50 return ""; // For the compiler
51 }
52}
53
54Accepted NavmsgStatus::StringToAccepted(const std::string& s) {
55 for (auto& kv : StringByAccepted)
56 if (kv.second == s) return kv.first;
57 return Accepted::kNone;
58}
59
60// clang-format: off
61static const std::unordered_map<const char*, Direction> dir_map = {
62 {"input", Direction::kInput},
63 {"handled", Direction::kHandled},
64 {"output", Direction::kOutput},
65 {"internal", Direction::kInternal},
66 {"none", NavmsgStatus::Direction::kNone}};
67
68static const std::unordered_map<const char*, State> statemap = {
69 {"ok", State::kOk},
70 {"checksum-err", State::kChecksumError},
71 {"malformed", State::kMalformed},
72 {"tx-error", State::kTxError}};
73
74static const std::unordered_map<const char*, Accepted> acceptmap = {
75 {"Ok", Accepted::kOk},
76 {"FilteredDropped", Accepted::kFilteredDropped},
77 {"FilteredNoOutput", Accepted::kFilteredNoOutput},
78 {"None", Accepted::kNone}}; // clang-format: on
79
80static NavmsgStatus::Direction StringToDirection(const std::string s) {
81 // Transition from 5.12 beta which used "received" isf "handled"; to
82 // be removed
83 if (s == "received") return NavmsgStatus::Direction::kHandled;
84
85 for (auto kv : dir_map)
86 if (kv.first == s) return kv.second;
87 return NavmsgStatus::Direction::kNone;
88}
89
90static std::string DirectionToString(Direction direction) {
91 for (auto kv : dir_map)
92 if (kv.second == direction) return kv.first;
93 assert(false && "Illegal direction enumeration type (!)");
94 return ""; // for the compiler
95}
96
97static Accepted StringToAccepted(const std::string s) {
98 for (auto kv : acceptmap)
99 if (kv.first == s) return kv.second;
100 return Accepted::kNone;
101}
102
103static std::string AcceptedToString(Accepted accept) {
104 for (auto kv : acceptmap)
105 if (kv.second == accept) return kv.first;
106 assert(false && "Illegal direction enumeration type (!)");
107 return ""; // for the compiler
108}
109
110static State StringToState(const std::string s) {
111 for (auto kv : statemap)
112 if (kv.first == s) return kv.second;
113 return State::kNone;
114}
115
116static std::string StateToString(State state) {
117 for (auto kv : statemap)
118 if (kv.second == state) return kv.first;
119 assert(false && "Illegal direction enumeration type (!)");
120 return ""; // for the compiler
121}
122
123static void ParseBuses(NavmsgFilter& filter, const nlohmann::json& json_val) {
124 for (const auto& value : json_val) {
125 auto str = value.get<std::string>();
126 filter.buses.insert(NavAddr::StringToBus(str));
127 }
128}
129
130static void ParseDirections(NavmsgFilter& filter,
131 const nlohmann::json& json_val) {
132 for (const auto& value : json_val) {
133 auto str = value.get<std::string>();
134 filter.directions.insert(StringToDirection(str));
135 }
136}
137
138static void ParseAccepted(NavmsgFilter& filter, nlohmann::json json_val) {
139 for (const auto& value : json_val) {
140 auto str = value.get<std::string>();
141 filter.accepted.insert(StringToAccepted(str));
142 }
143}
144
145static void ParseStatus(NavmsgFilter& filter, nlohmann::json json_val) {
146 for (const auto& value : json_val) {
147 auto str = value.get<std::string>();
148 filter.status.insert(StringToState(str));
149 }
150}
151
152static void ParseMsgFilter(NavmsgFilter& filter, nlohmann::json json_val) {
153 if (json_val.contains("blockedMsg")) {
154 auto values = json_val["blockedMsg"];
155 for (const auto& val : values)
156 filter.exclude_msg.insert(val.get<std::string>());
157 } else if (json_val.contains("allowedMsg")) {
158 auto values = json_val["allowedMsg"];
159 for (const auto& val : values)
160 filter.include_msg.insert(val.get<std::string>());
161 }
162}
163
164static void ParseInterfaces(NavmsgFilter& filter, nlohmann::json json_val) {
165 for (const auto& value : json_val)
166 filter.interfaces.insert(value.get<std::string>());
167}
168
169static void ParsePgn(NavmsgFilter& filter, nlohmann::json json_val) {
170 try {
171 for (const auto& value : json_val)
172 filter.pgns.insert(std::stoi(value.get<std::string>()));
173 } catch (...) {
174 return;
175 }
176}
177
178static void ParseSource(NavmsgFilter& filter, nlohmann::json json_val) {
179 try {
180 for (const auto& value : json_val)
181 for (unsigned i = 0; i < json_val.size(); i++) {
182 filter.src_pgns.insert(std::stoi(value.get<std::string>()));
183 }
184 } catch (...) {
185 return;
186 }
187}
188
189std::vector<NavmsgFilter> NavmsgFilter::GetFilters(const fs::path& dir) {
190 std::vector<NavmsgFilter> filters;
191 try {
192 for (auto& entry : fs::directory_iterator(dir)) {
193 auto filter = NavmsgFilter::Parse(entry);
194 if (filter.m_is_valid)
195 filters.push_back(filter);
196 else
197 wxLogWarning("Illegal system filter: %s",
198 entry.path().string().c_str());
199 }
200 } catch (...) {
201 wxLogWarning("Bad system filter path: %s", dir.string().c_str());
202 }
203 return filters;
204}
205
206std::vector<NavmsgFilter> NavmsgFilter::GetSystemFilters() {
207 fs::path dirpath(g_BasePlatform->GetSharedDataDir().ToStdString());
208 return NavmsgFilter::GetFilters(dirpath / "filters");
209}
210
211std::vector<NavmsgFilter> GetUserFilters() {
212 fs::path dirpath(g_BasePlatform->GetPrivateDataDir().ToStdString());
213 return NavmsgFilter::GetFilters(dirpath / "filters");
214}
215
216std::vector<NavmsgFilter> NavmsgFilter::GetAllFilters() {
217 std::vector<NavmsgFilter> filters = GetSystemFilters();
218 std::vector user_filters = GetUserFilters();
219 filters.insert(filters.end(), user_filters.begin(), user_filters.end());
220 return filters;
221}
222
224 const std::shared_ptr<const NavMsg>& msg) {
225 if (directions.size() > 0) {
226 if (directions.find(msg_status.direction) == directions.end()) return false;
227 }
228 if (status.size() > 0) {
229 if (status.find(msg_status.status) == status.end()) return false;
230 }
231 if (buses.size() > 0) {
232 if (buses.find(msg->bus) == buses.end()) return false;
233 }
234 if (include_msg.size() > 0) {
235 if (include_msg.find(msg->key()) == include_msg.end()) return false;
236 }
237 if (exclude_msg.size() > 0) {
238 if (exclude_msg.find(msg->key()) != exclude_msg.end()) return false;
239 }
240 if (interfaces.size() > 0) {
241 if (interfaces.find(msg->source->iface) == interfaces.end()) return false;
242 }
243 if (accepted.size() > 0) {
244 if (accepted.find(msg_status.accepted) == accepted.end()) return false;
245 }
246 auto n2k_msg = std::dynamic_pointer_cast<const Nmea2000Msg>(msg);
247 if (n2k_msg) {
248 if (pgns.size() > 0) {
249 if (pgns.find(n2k_msg->PGN) == pgns.end()) return false;
250 }
251 if (src_pgns.size() > 0) {
252 auto src = std::static_pointer_cast<const NavAddr2000>(msg->source);
253 if (src && src_pgns.find(src->name) == src_pgns.end()) return false;
254 }
255 }
256 return true;
257}
258
259NavmsgFilter NavmsgFilter::Parse(const fs::path& path) {
260 std::ifstream stream(path);
261 if (!stream.is_open()) return NavmsgFilter(false);
262 std::stringstream ss;
263 ss << stream.rdbuf();
264 return NavmsgFilter::Parse(ss.str());
265}
266
267NavmsgFilter NavmsgFilter::Parse(const std::string& string) {
268 nlohmann::json root;
269 try {
270 root = nlohmann::json::parse(string);
271 } catch (nlohmann::json::exception& e) {
272 wxLogWarning("Error parsing filter XML: %s", e.what());
273 return NavmsgFilter(false);
274 }
275 NavmsgFilter filter;
276 filter.m_name = root["filter"]["name"].get<std::string>();
277 filter.m_description = root["filter"]["description"].get<std::string>();
278 if (root["filter"].contains("buses"))
279 ParseBuses(filter, root["filter"]["buses"]);
280 if (root["filter"].contains("accepted"))
281 ParseAccepted(filter, root["filter"]["accepted"]);
282 if (root["filter"].contains("status"))
283 ParseStatus(filter, root["filter"]["status"]);
284 if (root["filter"].contains("directions"))
285 ParseDirections(filter, root["filter"]["directions"]);
286 if (root["filter"].contains("msgFilter"))
287 ParseMsgFilter(filter, root["filter"]["msgFilter"]);
288 if (root["filter"].contains("interfaces"))
289 ParseInterfaces(filter, root["filter"]["interfaces"]);
290 if (root["filter"].contains("pgns")) ParsePgn(filter, root["filter"]["pgns"]);
291 if (root["filter"].contains("src_pgns"))
292 ParseSource(filter, root["filter"]["src_pgns"]);
293 return filter;
294}
295
296std::string NavmsgFilter::to_string() const {
297 nlohmann::json root;
298 root["filter"]["name"] = m_name;
299 root["filter"]["description"] = m_description;
300 nlohmann::json& filter = root["filter"];
301
302 if (!buses.empty()) {
303 for (auto b : buses) filter["buses"].push_back(NavAddr::BusToString(b));
304 };
305 if (!directions.empty()) {
306 for (auto d : directions)
307 filter["directions"].push_back(DirectionToString(d));
308 };
309 if (!accepted.empty()) {
310 for (auto a : accepted) filter["accepted"].push_back(AcceptedToString(a));
311 };
312 if (!status.empty()) {
313 for (auto s : status) filter["status"].push_back(StateToString(s));
314 };
315 if (!include_msg.empty()) {
316 for (auto m : include_msg) filter["msgFilter"]["allowedMsg"].push_back(m);
317 } else if (!exclude_msg.empty()) {
318 for (auto m : exclude_msg) filter["msgFilter"]["blockedMsg"].push_back(m);
319 }
320 if (!interfaces.empty()) {
321 for (auto i : interfaces) filter["interfaces"].push_back(i);
322 }
323 if (!pgns.empty()) {
324 for (auto p : pgns) filter["pgns"].push_back(p.to_string());
325 }
326 if (!src_pgns.empty()) {
327 for (auto p : src_pgns) filter["src_pgns"].push_back(p.to_string());
328 }
329 try {
330 return root.dump();
331 } catch (nlohmann::json::exception& e) {
332 wxLogWarning("Error serializing filter %s: %s", m_name.c_str(), e.what());
333 return "";
334 }
335}
BasePlatform * g_BasePlatform
points to g_platform, handles brain-dead MS linker.
Basic platform specific support utilities without GUI deps.
wxString & GetPrivateDataDir()
Return dir path for opencpn.log, etc., respecting -c cli option.
bool Pass(NavmsgStatus status, const std::shared_ptr< const NavMsg > &msg)
Return true if message is not matched by filter.
static std::vector< NavmsgFilter > GetFilters(const fs::path &path)
Return list of pre-defined filters shipped with app, test hook.
static std::vector< NavmsgFilter > GetAllFilters()
Return list of all filters, system + user defined.
std::string to_string() const
Output parsable JSON string representation.
static NavmsgFilter Parse(const std::string &s)
Parse text as created by to_string().
static std::vector< NavmsgFilter > GetSystemFilters()
Return list of pre-defined filters shipped with application.
Representation of message status as determined by the multiplexer.
static Accepted StringToAccepted(const std::string &s)
Return Accepted value corresponding to argument s.
static std::string AcceptedToString(Accepted)
Return string representation of argument.
Data monitor filter definitions.