src/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
94 / 94
100.0%
Branches:
133 / 155
85.8%
Functions:
9 / 9
100.0%

ProtoSettings.cpp
Line Branch Exec Source
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include "ProtoSettings.h"
5 #include "ProtoParse.h"
6 #include "EthPhy.h"
7
8 static const char *const SECTIONS[] = {"general", "network", "mqtt", "security", "radio"};
9 16 const char *settingsSectionName(settings_section_t section) { return SECTIONS[static_cast<uint8_t>(section)]; }
10
11 717 static settings_section_t sectionOf(settings_part_t part) {
12
5/5
✓ Branch 0 taken 62 times.
✓ Branch 1 taken 263 times.
✓ Branch 2 taken 125 times.
✓ Branch 3 taken 57 times.
✓ Branch 4 taken 210 times.
717 switch(part) {
13 62 case settings_part_t::general:
14 62 case settings_part_t::ntp: return settings_section_t::general;
15 263 case settings_part_t::conn:
16 case settings_part_t::wifi:
17 case settings_part_t::ethernet:
18 263 case settings_part_t::ip: return settings_section_t::network;
19 125 case settings_part_t::mqtt: return settings_section_t::mqtt;
20 57 case settings_part_t::security: return settings_section_t::security;
21 210 default: return settings_section_t::radio;
22 }
23 }
24
25 // What a save may set. lo..hi is a whole number's range, a number's in
26 // hundredths, and hi a string's longest length: what the setting holds, so the
27 // route's fromJSON neither cuts it short nor wraps it. The has* fields and
28 // radioInit are only ever sent.
29 struct settings_field_t {
30 settings_part_t part;
31 proto_field_t field;
32 const char *route; // the name the route reads; nullptr when it is the field's
33 int32_t lo;
34 int32_t hi;
35 };
36 static const settings_field_t FIELDS[] = {
37 {settings_part_t::general, proto_field_t::hostname, nullptr, 0, 31},
38 {settings_part_t::general, proto_field_t::ssdpBroadcast, nullptr, 0, 0},
39 {settings_part_t::ntp, proto_field_t::ntpServer, nullptr, 0, 64},
40 {settings_part_t::ntp, proto_field_t::posixZone, nullptr, 0, 63},
41 // wifi, ethernet and ethernet with WiFi as the fallback (conn_types_t).
42 {settings_part_t::conn, proto_field_t::connMode, "connType", 1, 3},
43 {settings_part_t::wifi, proto_field_t::ssid, nullptr, 0, 64},
44 {settings_part_t::wifi, proto_field_t::passphrase, nullptr, 0, 64},
45 {settings_part_t::wifi, proto_field_t::roaming, nullptr, 0, 0},
46 {settings_part_t::wifi, proto_field_t::hidden, nullptr, 0, 0},
47 {settings_part_t::ethernet, proto_field_t::boardType, nullptr, 0, 255},
48 {settings_part_t::ethernet, proto_field_t::phyType, nullptr, 0, 255},
49 {settings_part_t::ethernet, proto_field_t::CLKMode, nullptr, 0, ETH_CLOCK_ID_MAX},
50 {settings_part_t::ethernet, proto_field_t::phyAddress, nullptr, -1, 31},
51 // Pins run to GPIO 54, the P4's last.
52 {settings_part_t::ethernet, proto_field_t::PWRPin, nullptr, -1, 54},
53 {settings_part_t::ethernet, proto_field_t::MDCPin, nullptr, 0, 54},
54 {settings_part_t::ethernet, proto_field_t::MDIOPin, nullptr, 0, 54},
55 {settings_part_t::ip, proto_field_t::dhcp, nullptr, 0, 0},
56 {settings_part_t::ip, proto_field_t::ip, nullptr, 0, 15},
57 {settings_part_t::ip, proto_field_t::gateway, nullptr, 0, 15},
58 {settings_part_t::ip, proto_field_t::subnet, nullptr, 0, 15},
59 {settings_part_t::ip, proto_field_t::dns1, nullptr, 0, 15},
60 {settings_part_t::ip, proto_field_t::dns2, nullptr, 0, 15},
61 {settings_part_t::mqtt, proto_field_t::enabled, nullptr, 0, 0},
62 {settings_part_t::mqtt, proto_field_t::pubDisco, nullptr, 0, 0},
63 {settings_part_t::mqtt, proto_field_t::protocol, nullptr, 0, 9},
64 {settings_part_t::mqtt, proto_field_t::hostname, nullptr, 0, 64},
65 {settings_part_t::mqtt, proto_field_t::port, nullptr, 1, 65535},
66 {settings_part_t::mqtt, proto_field_t::username, nullptr, 0, 32},
67 {settings_part_t::mqtt, proto_field_t::password, nullptr, 0, 32},
68 {settings_part_t::mqtt, proto_field_t::rootTopic, nullptr, 0, 64},
69 {settings_part_t::mqtt, proto_field_t::discoTopic, nullptr, 0, 64},
70 // None, PIN or password (security_types).
71 {settings_part_t::security, proto_field_t::securityType, "type", 0, 2},
72 {settings_part_t::security, proto_field_t::username, nullptr, 0, 32},
73 {settings_part_t::security, proto_field_t::password, nullptr, 0, 32},
74 {settings_part_t::security, proto_field_t::pin, nullptr, 0, 4},
75 // 56 or 80, checked on its own.
76 {settings_part_t::radio, proto_field_t::bitLength, "type", 56, 80},
77 // RTS, RTW, RTV, GP Relay or GP Remote (radio_proto), checked on its own.
78 {settings_part_t::radio, proto_field_t::proto, nullptr, 0, 9},
79 {settings_part_t::radio, proto_field_t::enabled, nullptr, 0, 0},
80 {settings_part_t::radio, proto_field_t::TXPin, nullptr, 0, 54},
81 {settings_part_t::radio, proto_field_t::RXPin, nullptr, 0, 54},
82 {settings_part_t::radio, proto_field_t::SCKPin, nullptr, 0, 54},
83 {settings_part_t::radio, proto_field_t::MOSIPin, nullptr, 0, 54},
84 {settings_part_t::radio, proto_field_t::MISOPin, nullptr, 0, 54},
85 {settings_part_t::radio, proto_field_t::CSNPin, nullptr, 0, 54},
86 // What the CC1101 can be set to, in MHz and kHz.
87 {settings_part_t::radio, proto_field_t::frequency, nullptr, 30000, 92800},
88 {settings_part_t::radio, proto_field_t::rxBandwidth, nullptr, 5803, 81250},
89 {settings_part_t::radio, proto_field_t::deviation, nullptr, 158, 38085},
90 {settings_part_t::radio, proto_field_t::txPower, nullptr, -30, 12},
91 {settings_part_t::radio, proto_field_t::noiseDetection, nullptr, 0, 0}
92 };
93
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 230 times.
252 static const char *routeName(const settings_field_t &s) { return s.route ? s.route : protoField(s.field).name; }
94 83 static const settings_field_t *saved(settings_section_t section, proto_field_t field) {
95
2/2
✓ Branch 0 taken 2312 times.
✓ Branch 1 taken 9 times.
2321 for(const settings_field_t &s : FIELDS)
96
6/6
✓ Branch 0 taken 80 times.
✓ Branch 1 taken 2232 times.
✓ Branch 3 taken 74 times.
✓ Branch 4 taken 6 times.
✓ Branch 5 taken 74 times.
✓ Branch 6 taken 2238 times.
2312 if(s.field == field && sectionOf(s.part) == section) return &s;
97 9 return nullptr;
98 }
99
100 79 const char *parseSettingsSection(JsonObjectConst d, settings_section_t &section) {
101 79 const char *name = protoString(d, proto_field_t::section);
102
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 76 times.
79 if(!name) return "section is missing";
103
2/2
✓ Branch 0 taken 249 times.
✓ Branch 1 taken 2 times.
251 for(uint8_t i = 0; i < sizeof(SECTIONS) / sizeof(SECTIONS[0]); i++) {
104
2/2
✓ Branch 0 taken 175 times.
✓ Branch 1 taken 74 times.
249 if(strcmp(SECTIONS[i], name) != 0) continue;
105 74 section = static_cast<settings_section_t>(i);
106 74 return nullptr;
107 }
108 2 return "Unknown section";
109 }
110
111 // Four numbers 0-255 between dots.
112 8 static bool isAddress(const char *s) {
113
2/2
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 4 times.
31 for(int part = 0; part < 4; part++) {
114
6/6
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 18 times.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 26 times.
30 if(part > 0 && *s++ != '.') return false;
115
4/4
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 24 times.
26 if(*s < '0' || *s > '9') return false;
116 char *end;
117 24 const long n = strtol(s, &end, 10);
118
3/4
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 3 taken 23 times.
24 if(n > 255 || end - s > 3) return false;
119 23 s = end;
120 }
121 4 return *s == '\0';
122 }
123
124 // What the reply's desc points at; every parse runs on async_tcp, one at a
125 // time.
126 static char why[64];
127 // A value already of its field's kind: nullptr when it is in range.
128 69 static const char *checkValue(const settings_field_t &s, const proto_field_info_t &f, JsonVariant value) {
129
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 40 times.
69 if(f.kind == 'i') {
130
6/6
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 1 time.
✓ Branch 3 taken 25 times.
✓ Branch 4 taken 3 times.
✓ Branch 6 taken 18 times.
✓ Branch 7 taken 7 times.
29 const bool inRange = value.is<int64_t>() && value.as<int64_t>() >= s.lo && value.as<int64_t>() <= s.hi;
131
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 24 times.
29 if(s.field == proto_field_t::bitLength) {
132
8/8
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 time.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 2 times.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 1 time.
✓ Branch 8 taken 3 times.
✓ Branch 9 taken 2 times.
5 if(inRange && (value.as<int>() == 56 || value.as<int>() == 80)) return nullptr;
133 2 snprintf(why, sizeof(why), "%s must be 56 or 80", f.name);
134 2 return why;
135 }
136
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 20 times.
24 if(s.field == proto_field_t::proto) {
137
8/8
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 1 time.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 1 time.
✓ Branch 8 taken 2 times.
✓ Branch 9 taken 2 times.
4 if(inRange && (value.as<int>() <= 2 || value.as<int>() >= 8)) return nullptr;
138 2 snprintf(why, sizeof(why), "%s must be 0, 1, 2, 8 or 9", f.name);
139 2 return why;
140 }
141
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 9 times.
20 if(inRange) return nullptr;
142
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if(s.lo == s.hi) snprintf(why, sizeof(why), "%s must be %ld", f.name, (long)s.lo);
143 9 else snprintf(why, sizeof(why), "%s must be %ld-%ld", f.name, (long)s.lo, (long)s.hi);
144 9 return why;
145 }
146
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 29 times.
40 if(f.kind == 'n') {
147 // Half a hundredth either way, as 58.03 is a little under it in a float.
148 11 const double hundredths = value.as<double>() * 100;
149
4/4
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 1 time.
11 if(hundredths > s.lo - 0.5 && hundredths < s.hi + 0.5) return nullptr;
150 3 snprintf(why, sizeof(why), "%s must be %ld.%02ld-%ld.%02ld", f.name, (long)(s.lo / 100), (long)(s.lo % 100),
151 3 (long)(s.hi / 100), (long)(s.hi % 100));
152 3 return why;
153 }
154
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 23 times.
29 if(f.kind != 's') return nullptr;
155 23 const char *text = value.as<const char *>();
156
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 20 times.
23 if(strlen(text) > static_cast<size_t>(s.hi)) {
157 3 snprintf(why, sizeof(why), "%s is longer than %ld characters", f.name, (long)s.hi);
158 3 return why;
159 }
160
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 10 times.
20 if(s.part != settings_part_t::ip) return nullptr;
161 // The settings send 0.0.0.0 as "", so "" sets it back.
162
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 8 times.
10 if(!text[0]) value.set("0.0.0.0");
163
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 3 times.
8 else if(!isAddress(text)) {
164 5 snprintf(why, sizeof(why), "%s is not an IP address", f.name);
165 5 return why;
166 }
167 5 return nullptr;
168 }
169
170 70 const char *expandSettingsPayload(JsonObjectConst d, bool hasEthernet, settings_section_t &section, JsonDocument &doc) {
171
1/2
✓ Branch 1 taken 70 times.
✗ Branch 2 not taken.
70 doc.clear();
172
1/2
✓ Branch 1 taken 70 times.
✗ Branch 2 not taken.
70 const char *bad = parseSettingsSection(d, section);
173
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 69 times.
70 if(bad) return bad;
174
1/2
✓ Branch 1 taken 69 times.
✗ Branch 2 not taken.
69 JsonObject obj = doc.to<JsonObject>();
175
5/8
✓ Branch 1 taken 69 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 154 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 114 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 154 times.
✓ Branch 12 taken 29 times.
297 for(JsonPairConst kv : d) {
176 proto_field_t field;
177
3/4
✓ Branch 3 taken 154 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 152 times.
154 if(!protoFieldOfKey(kv.key().c_str(), field)) {
178 2 snprintf(why, sizeof(why), "Unknown field %s", kv.key().c_str());
179 40 return why;
180 }
181
2/2
✓ Branch 0 taken 69 times.
✓ Branch 1 taken 83 times.
152 if(field == proto_field_t::section) continue;
182
1/2
✓ Branch 1 taken 83 times.
✗ Branch 2 not taken.
83 const proto_field_info_t &f = protoField(field);
183 83 const settings_field_t *s = saved(section, field);
184
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 74 times.
83 if(!s) {
185 9 snprintf(why, sizeof(why), "%s can't be saved in %s", f.name, settingsSectionName(section));
186 9 return why;
187 }
188
4/4
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 67 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 6 times.
74 if(s->part == settings_part_t::ethernet && !hasEthernet) return "This device has no Ethernet";
189
3/6
✓ Branch 1 taken 73 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 73 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 73 times.
✗ Branch 8 not taken.
73 JsonVariant value = obj[routeName(*s)].to<JsonVariant>();
190
3/4
✓ Branch 2 taken 73 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 69 times.
73 if(!protoRouteValue(f, kv.value(), value)) {
191
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 snprintf(why, sizeof(why), "%s is not a %s", f.name, protoKindName(f.kind));
192 4 return why;
193 }
194
1/2
✓ Branch 1 taken 69 times.
✗ Branch 2 not taken.
69 bad = checkValue(*s, f, value);
195
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 45 times.
69 if(bad) return bad;
196 }
197
3/4
✓ Branch 1 taken 29 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 28 times.
29 if(obj.size() == 0) return "Nothing to save";
198
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
28 return doc.overflowed() ? "Too large" : nullptr;
199 }
200
201 8 uint16_t settingsParts(settings_section_t section, JsonObjectConst request) {
202 8 uint16_t parts = 0;
203
5/8
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 13 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 13 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 13 times.
✓ Branch 12 taken 8 times.
21 for(JsonPairConst kv : request)
204
2/2
✓ Branch 0 taken 637 times.
✓ Branch 1 taken 13 times.
650 for(const settings_field_t &s : FIELDS)
205
7/8
✓ Branch 1 taken 179 times.
✓ Branch 2 taken 458 times.
✓ Branch 6 taken 179 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 13 times.
✓ Branch 9 taken 166 times.
✓ Branch 10 taken 13 times.
✓ Branch 11 taken 624 times.
637 if(sectionOf(s.part) == section && strcmp(routeName(s), kv.key().c_str()) == 0)
206 13 parts |= 1u << static_cast<uint8_t>(s.part);
207 8 return parts;
208 }
209