src/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
69 / 78
88.5%
Branches:
113 / 163
69.3%
Functions:
8 / 8
100.0%

ProtoEdits.cpp
Line Branch Exec Source
1 #include <stdio.h>
2 #include <string.h>
3 #include "ProtoEdits.h"
4 #include "ProtoParse.h"
5
6 struct edit_op_t {
7 proto_type_t type;
8 model_edit_type_t edit;
9 };
10 static const edit_op_t EDIT_OPS[] = {
11 {proto_type_t::addRoom, model_edit_type_t::add_room},
12 {proto_type_t::addShade, model_edit_type_t::add_shade},
13 {proto_type_t::addGroup, model_edit_type_t::add_group},
14 {proto_type_t::saveRoom, model_edit_type_t::save_room},
15 {proto_type_t::saveShade, model_edit_type_t::save_shade},
16 {proto_type_t::saveGroup, model_edit_type_t::save_group},
17 {proto_type_t::deleteRoom, model_edit_type_t::delete_room},
18 {proto_type_t::deleteShade, model_edit_type_t::delete_shade},
19 {proto_type_t::deleteGroup, model_edit_type_t::delete_group},
20 {proto_type_t::linkRemote, model_edit_type_t::link_remote},
21 {proto_type_t::unlinkRemote, model_edit_type_t::unlink_remote},
22 {proto_type_t::linkToGroup, model_edit_type_t::link_to_group},
23 {proto_type_t::unlinkFromGroup, model_edit_type_t::unlink_from_group},
24 {proto_type_t::linkRepeater, model_edit_type_t::link_repeater},
25 {proto_type_t::unlinkRepeater, model_edit_type_t::unlink_repeater},
26 {proto_type_t::setPaired, model_edit_type_t::set_paired},
27 {proto_type_t::roomSortOrder, model_edit_type_t::room_order},
28 {proto_type_t::shadeSortOrder, model_edit_type_t::shade_order},
29 {proto_type_t::groupSortOrder, model_edit_type_t::group_order}
30 };
31 374 bool protoEditType(uint8_t type, model_edit_type_t &out) {
32
2/2
✓ Branch 0 taken 6744 times.
✓ Branch 1 taken 333 times.
7077 for(const edit_op_t &op : EDIT_OPS) {
33
2/2
✓ Branch 0 taken 6703 times.
✓ Branch 1 taken 41 times.
6744 if(static_cast<uint8_t>(op.type) != type) continue;
34 41 out = op.edit;
35 41 return true;
36 }
37 333 return false;
38 }
39 7 const char *parseReadPayload(uint8_t type, JsonObjectConst d, uint8_t &groupId) {
40 7 groupId = 0;
41
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 4 times.
7 if(type != static_cast<uint8_t>(proto_type_t::groupOptions)) return nullptr;
42
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
4 JsonVariantConst v = d[protoField(proto_field_t::groupId).key];
43
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 3 times.
4 if(v.isNull()) return "groupId is missing";
44
3/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 1 time.
3 if(!v.is<uint8_t>()) return "groupId must be 0-255";
45
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
1 groupId = v.as<uint8_t>();
46 1 return nullptr;
47 }
48
49 // The fields each edit's route reads, so a field it would ignore is refused
50 // rather than dropped without a word. A shade's flags aren't among them:
51 // they hold light, which is set on its own, and the sun and wind state, which
52 // setSensor sets. A group's sunSensor is worked out from its shades.
53 static const proto_field_t ROOM_FIELDS[] = {proto_field_t::name, proto_field_t::sortOrder};
54 static const proto_field_t SHADE_FIELDS[] = {
55 proto_field_t::name, proto_field_t::roomId, proto_field_t::remoteAddress, proto_field_t::shadeType,
56 proto_field_t::tiltType, proto_field_t::bitLength, proto_field_t::proto, proto_field_t::upTime,
57 proto_field_t::downTime, proto_field_t::tiltTime, proto_field_t::stepSize, proto_field_t::flipCommands,
58 proto_field_t::flipPosition, proto_field_t::sunSensor, proto_field_t::light, proto_field_t::simMy,
59 proto_field_t::repeats, proto_field_t::gpioUp, proto_field_t::gpioDown, proto_field_t::gpioMy,
60 proto_field_t::gpioLLTrigger
61 };
62 static const proto_field_t GROUP_FIELDS[] = {
63 proto_field_t::name, proto_field_t::roomId, proto_field_t::remoteAddress, proto_field_t::bitLength,
64 proto_field_t::proto, proto_field_t::flipCommands, proto_field_t::repeats
65 };
66 template<size_t N>
67 32 static bool in(proto_field_t field, const proto_field_t (&fields)[N]) {
68
5/6
bool in<21ul>(proto_field_t, proto_field_t const (&) [21ul]):
✓ Branch 0 taken 340 times.
✓ Branch 1 taken 4 times.
bool in<2ul>(proto_field_t, proto_field_t const (&) [2ul]):
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
bool in<7ul>(proto_field_t, proto_field_t const (&) [7ul]):
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 2 times.
361 for(proto_field_t f : fields)
69
4/6
bool in<21ul>(proto_field_t, proto_field_t const (&) [21ul]):
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 315 times.
bool in<2ul>(proto_field_t, proto_field_t const (&) [2ul]):
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
bool in<7ul>(proto_field_t, proto_field_t const (&) [7ul]):
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
355 if(f == field) return true;
70 6 return false;
71 }
72 59 static bool takes(model_edit_type_t type, proto_field_t f) {
73
9/15
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 34 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 3 times.
✓ Branch 10 taken 3 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 7 times.
✗ Branch 14 not taken.
59 switch(type) {
74 ✗ case model_edit_type_t::add_room: return in(f, ROOM_FIELDS);
75
3/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
4 case model_edit_type_t::save_room: return f == proto_field_t::roomId || in(f, ROOM_FIELDS);
76 1 case model_edit_type_t::add_shade: return in(f, SHADE_FIELDS);
77
4/4
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 6 times.
✓ Branch 3 taken 25 times.
✓ Branch 4 taken 3 times.
34 case model_edit_type_t::save_shade: return f == proto_field_t::shadeId || in(f, SHADE_FIELDS);
78 ✗ case model_edit_type_t::add_group: return in(f, GROUP_FIELDS);
79
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
4 case model_edit_type_t::save_group: return f == proto_field_t::groupId || in(f, GROUP_FIELDS);
80 2 case model_edit_type_t::delete_room: return f == proto_field_t::roomId;
81 1 case model_edit_type_t::delete_shade: return f == proto_field_t::shadeId;
82 ✗ case model_edit_type_t::delete_group: return f == proto_field_t::groupId;
83 3 case model_edit_type_t::link_remote:
84
5/6
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
3 return f == proto_field_t::shadeId || f == proto_field_t::remoteAddress || f == proto_field_t::lastRollingCode;
85
4/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 1 time.
3 case model_edit_type_t::unlink_remote: return f == proto_field_t::shadeId || f == proto_field_t::remoteAddress;
86 ✗ case model_edit_type_t::link_to_group:
87
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ case model_edit_type_t::unlink_from_group: return f == proto_field_t::groupId || f == proto_field_t::shadeId;
88 ✗ case model_edit_type_t::link_repeater:
89 ✗ case model_edit_type_t::unlink_repeater: return f == proto_field_t::remoteAddress;
90
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
7 case model_edit_type_t::set_paired: return f == proto_field_t::shadeId || f == proto_field_t::paired;
91 ✗ default: return false;
92 }
93 }
94 // The name a field has in the edit route's request. Only linkRemote's rolling
95 // code is named apart from the dictionary.
96 51 static const char *routeName(model_edit_type_t type, proto_field_t f) {
97
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 48 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 2 times.
51 if(type == model_edit_type_t::link_remote && f == proto_field_t::lastRollingCode) return "rollingCode";
98 50 return protoField(f).name;
99 }
100 // What the reply's desc points at; every parse runs on async_tcp, one at a
101 // time.
102 static char why[64];
103 26 const char *expandEditPayload(model_edit_type_t type, JsonObjectConst d, JsonDocument &doc) {
104
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 doc.clear();
105
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 21 times.
26 switch(type) {
106 5 case model_edit_type_t::room_order:
107 case model_edit_type_t::shade_order:
108 case model_edit_type_t::group_order: {
109 // The route takes the ids on their own, in their new order.
110
5/8
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 5 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 5 times.
✓ Branch 12 taken 4 times.
9 for(JsonPairConst kv : d) {
111
3/4
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 1 time.
5 if(strcmp(kv.key().c_str(), protoField(proto_field_t::ids).key) == 0) continue;
112 1 return "A sort order takes only ids";
113 }
114
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
4 JsonVariantConst ids = d[protoField(proto_field_t::ids).key];
115
3/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 3 times.
4 if(!ids.is<JsonArrayConst>()) return "ids is missing";
116
5/8
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 9 taken 5 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 7 times.
✓ Branch 13 taken 1 time.
8 for(JsonVariantConst id : ids.as<JsonArrayConst>())
117
3/4
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 5 times.
7 if(!id.is<uint8_t>()) return "ids must be a list of ids";
118
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
1 doc.set(ids);
119
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
1 return doc.overflowed() ? "Too large" : nullptr;
120 }
121 21 default:
122 21 break;
123 }
124
1/2
✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
21 JsonObject obj = doc.to<JsonObject>();
125
5/8
✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 60 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 47 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 60 times.
✓ Branch 12 taken 8 times.
68 for(JsonPairConst kv : d) {
126 proto_field_t field;
127
3/4
✓ Branch 3 taken 60 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 59 times.
60 if(!protoFieldOfKey(kv.key().c_str(), field)) {
128 1 snprintf(why, sizeof(why), "Unknown field %s", kv.key().c_str());
129 13 return why;
130 }
131
1/2
✓ Branch 1 taken 59 times.
✗ Branch 2 not taken.
59 const proto_field_info_t &f = protoField(field);
132
3/4
✓ Branch 1 taken 59 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
✓ Branch 4 taken 51 times.
59 if(!takes(type, field)) {
133 8 snprintf(why, sizeof(why), "%s can't be set by this edit", f.name);
134 8 return why;
135 }
136
6/10
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 51 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 51 times.
✗ Branch 8 not taken.
✓ Branch 11 taken 51 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 4 times.
✓ Branch 14 taken 47 times.
51 if(!protoRouteValue(f, kv.value(), obj[routeName(type, field)].to<JsonVariant>())) {
137
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));
138 4 return why;
139 }
140 }
141 // A left-out field is not given, so it can't stand for unpaired.
142
8/10
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 time.
✓ Branch 9 taken 1 time.
✓ Branch 10 taken 1 time.
✓ Branch 11 taken 7 times.
8 if(type == model_edit_type_t::set_paired && obj["paired"].isNull()) return "paired is missing";
143
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
7 return doc.overflowed() ? "Too large" : nullptr;
144 }
145