xref: /btstack/src/mesh/mesh_health_server.c (revision d58a1b5f11ada8ddf896c41fff5a35e7f140c37e)
1 /*
2  * Copyright (C) 2019 BlueKitchen GmbH
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the copyright holders nor the names of
14  *    contributors may be used to endorse or promote products derived
15  *    from this software without specific prior written permission.
16  * 4. Any redistribution, use, or modification is done solely for
17  *    personal benefit and not for any commercial purpose or for
18  *    monetary gain.
19  *
20  * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS
24  * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
27  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
30  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * Please inquire about commercial licensing options at
34  * [email protected]
35  *
36  */
37 
38 #define BTSTACK_FILE__ "mesh_health_server.c"
39 
40 #include <string.h>
41 #include <stdio.h>
42 
43 #include "mesh/mesh_health_server.h"
44 
45 #include "bluetooth_company_id.h"
46 #include "btstack_debug.h"
47 #include "btstack_memory.h"
48 #include "btstack_util.h"
49 
50 #include "mesh/mesh.h"
51 #include "mesh/mesh_access.h"
52 #include "mesh/mesh_node.h"
53 #include "mesh/mesh_foundation.h"
54 #include "mesh/mesh_generic_model.h"
55 #include "mesh/mesh_generic_on_off_server.h"
56 #include "mesh/mesh_keys.h"
57 #include "mesh/mesh_network.h"
58 #include "mesh/mesh_upper_transport.h"
59 
60 static void health_server_send_message(uint16_t src, uint16_t dest, uint16_t netkey_index, uint16_t appkey_index, mesh_pdu_t *pdu){
61     uint8_t ttl = mesh_foundation_default_ttl_get();
62     mesh_upper_transport_setup_access_pdu_header(pdu, netkey_index, appkey_index, ttl, src, dest, 0);
63     mesh_access_send_unacknowledged_pdu(pdu);
64 }
65 
66 static mesh_health_fault_t * mesh_health_server_fault_for_company_id(mesh_model_t *mesh_model, uint16_t company_id){
67     mesh_health_state_t * state = (mesh_health_state_t *) mesh_model->model_data;
68     btstack_linked_list_iterator_t it;
69     btstack_linked_list_iterator_init(&it, &state->faults);
70     while (btstack_linked_list_iterator_has_next(&it)){
71         mesh_health_fault_t * fault = (mesh_health_fault_t *) btstack_linked_list_iterator_next(&it);
72         if (fault->company_id == company_id) return fault;
73     }
74     return NULL;
75 }
76 static mesh_health_fault_t * mesh_health_server_active_fault(mesh_model_t *mesh_model){
77     mesh_health_state_t * state = (mesh_health_state_t *) mesh_model->model_data;
78     btstack_linked_list_iterator_t it;
79     btstack_linked_list_iterator_init(&it, &state->faults);
80     while (btstack_linked_list_iterator_has_next(&it)){
81         mesh_health_fault_t * fault = (mesh_health_fault_t *) btstack_linked_list_iterator_next(&it);
82         if (fault->num_current_faults > 0) return fault;
83     }
84     return NULL;
85 }
86 
87 static void mesh_health_server_update_publication_model_period_divisor(mesh_model_t * mesh_model){
88     if (mesh_model->publication_model == NULL) return;
89     mesh_health_fault_t * fault = mesh_health_server_active_fault(mesh_model);
90     mesh_health_state_t * health_state = (mesh_health_state_t *) mesh_model->model_data;
91     if (fault == NULL){
92         mesh_model->publication_model->period_divisor = health_state->fast_period_divisor;
93     } else {
94         mesh_model->publication_model->period_divisor = 0;
95     }
96 }
97 
98 
99 // Health State
100 const mesh_access_message_t mesh_foundation_health_period_status = {
101         MESH_FOUNDATION_OPERATION_HEALTH_PERIOD_STATUS, "1"
102 };
103 
104 const mesh_access_message_t mesh_foundation_health_attention_status = {
105         MESH_FOUNDATION_OPERATION_HEALTH_ATTENTION_STATUS, "1"
106 };
107 
108 static mesh_pdu_t * health_period_status(mesh_model_t * mesh_model){
109     mesh_health_state_t * state = (mesh_health_state_t *) mesh_model->model_data;
110     // setup message
111     mesh_network_pdu_t * transport_pdu = mesh_access_setup_unsegmented_message(&mesh_foundation_health_period_status, state->fast_period_divisor);
112     return (mesh_pdu_t *) transport_pdu;
113 }
114 
115 static mesh_pdu_t * health_attention_status(void){
116     // setup message
117     mesh_network_pdu_t * transport_pdu = mesh_access_setup_unsegmented_message(&mesh_foundation_health_attention_status, mesh_attention_timer_get());
118     return (mesh_pdu_t *) transport_pdu;
119 }
120 
121 // report fault status - used for both current as well as registered faults, see registered_faults param
122 static mesh_pdu_t * health_fault_status(mesh_model_t * mesh_model, uint32_t opcode, uint16_t company_id, bool registered_faults){
123     mesh_network_pdu_t * transport_pdu = mesh_access_network_init(opcode);
124     if (!transport_pdu) return NULL;
125 
126     mesh_health_fault_t * fault = mesh_health_server_fault_for_company_id(mesh_model, company_id);
127     if (fault == NULL){
128         // no fault state with company_id found
129         mesh_access_network_add_uint8(transport_pdu, 0);
130         mesh_access_network_add_uint16(transport_pdu, company_id);
131     } else {
132         mesh_access_network_add_uint8(transport_pdu, fault->test_id);
133         mesh_access_network_add_uint16(transport_pdu, fault->company_id);
134         int i;
135         if (registered_faults){
136             for (i = 0; i < fault->num_registered_faults; i++){
137                  mesh_access_network_add_uint8(transport_pdu, fault->registered_faults[i]);
138             }
139         }  else {
140             for (i = 0; i < fault->num_current_faults; i++){
141                  mesh_access_network_add_uint8(transport_pdu, fault->current_faults[i]);
142             }
143         }
144     }
145     return (mesh_pdu_t *) transport_pdu;
146 }
147 
148 static void health_fault_get_handler(mesh_model_t *mesh_model, mesh_pdu_t * pdu){
149     mesh_access_parser_state_t parser;
150     mesh_access_parser_init(&parser, (mesh_pdu_t*) pdu);
151     uint16_t company_id = mesh_access_parser_get_u16(&parser);
152 
153     mesh_transport_pdu_t * transport_pdu = (mesh_transport_pdu_t *) health_fault_status(mesh_model, MESH_FOUNDATION_OPERATION_HEALTH_FAULT_STATUS, company_id, true);
154     if (!transport_pdu) return;
155     health_server_send_message(mesh_access_get_element_address(mesh_model), mesh_pdu_src(pdu), mesh_pdu_netkey_index(pdu), mesh_pdu_appkey_index(pdu),(mesh_pdu_t *) transport_pdu);
156     mesh_access_message_processed(pdu);
157 }
158 
159 static uint16_t process_message_fault_clear(mesh_model_t *mesh_model, mesh_pdu_t * pdu){
160     mesh_access_parser_state_t parser;
161     mesh_access_parser_init(&parser, (mesh_pdu_t*) pdu);
162     uint16_t company_id = mesh_access_parser_get_u16(&parser);
163 
164     mesh_health_fault_t * fault = mesh_health_server_fault_for_company_id(mesh_model, company_id);
165     if (fault != NULL){
166         fault->num_registered_faults = 0;
167         memset(fault->registered_faults, 0, sizeof(fault->registered_faults));
168     }
169     return company_id;
170 }
171 
172 static void health_fault_clear_handler(mesh_model_t * mesh_model, mesh_pdu_t * pdu){
173     uint16_t company_id = process_message_fault_clear(mesh_model, pdu);
174 
175     mesh_transport_pdu_t * transport_pdu = (mesh_transport_pdu_t *) health_fault_status(mesh_model, MESH_FOUNDATION_OPERATION_HEALTH_FAULT_STATUS, company_id, true);
176     if (!transport_pdu) return;
177     health_server_send_message(mesh_access_get_element_address(mesh_model), mesh_pdu_src(pdu), mesh_pdu_netkey_index(pdu), mesh_pdu_appkey_index(pdu),(mesh_pdu_t *) transport_pdu);
178     mesh_access_message_processed(pdu);
179 }
180 
181 static void health_fault_clear_unacknowledged_handler(mesh_model_t *mesh_model, mesh_pdu_t * pdu){
182     (void) process_message_fault_clear(mesh_model, pdu);
183     mesh_access_message_processed(pdu);
184 }
185 
186 
187 static void health_fault_test_process_message(mesh_model_t *mesh_model, mesh_pdu_t * pdu, bool acknowledged){
188     mesh_access_parser_state_t parser;
189     mesh_access_parser_init(&parser, (mesh_pdu_t*) pdu);
190     uint8_t  test_id    = mesh_access_parser_get_u8(&parser);
191     uint16_t company_id = mesh_access_parser_get_u16(&parser);
192 
193     uint16_t dest = mesh_pdu_src(pdu);
194     uint16_t netkey_index = mesh_pdu_netkey_index(pdu);
195     uint16_t appkey_index = mesh_pdu_appkey_index(pdu);
196 
197     // check if fault state exists for company id
198     mesh_health_fault_t * fault = mesh_health_server_fault_for_company_id(mesh_model, company_id);
199     if (fault == NULL){
200         return;
201     }
202 
203     // short-cut if not packet handler set, but only for standard test
204     if (mesh_model->model_packet_handler == NULL){
205         if (test_id == 0) {
206             mesh_health_server_report_test_done(dest, netkey_index, appkey_index, test_id, company_id, acknowledged);
207         }
208         return;
209     }
210 
211     uint8_t event[13];
212     int pos = 0;
213     event[pos++] = HCI_EVENT_MESH_META;
214     event[pos++] = sizeof(event) - 2;
215     event[pos++] = MESH_SUBEVENT_HEALTH_PERFORM_TEST;
216 
217     little_endian_store_16(event, pos, dest);
218     pos += 2;
219     little_endian_store_16(event, pos, netkey_index);
220     pos += 2;
221     little_endian_store_16(event, pos, appkey_index);
222     pos += 2;
223     little_endian_store_16(event, pos, company_id);
224     pos += 2;
225 
226     event[pos++] = test_id;
227     event[pos++] = acknowledged;
228 
229     (*mesh_model->model_packet_handler)(HCI_EVENT_PACKET, 0, event, pos);
230 }
231 
232 static void health_fault_test_handler(mesh_model_t *mesh_model, mesh_pdu_t * pdu){
233     health_fault_test_process_message(mesh_model, pdu, true);
234     mesh_access_message_processed(pdu);
235 }
236 
237 static void health_fault_test_unacknowledged_handler(mesh_model_t * mesh_model, mesh_pdu_t * pdu){
238     health_fault_test_process_message(mesh_model, pdu, false);
239     mesh_access_message_processed(pdu);
240 }
241 
242 static void health_period_get_handler(mesh_model_t *mesh_model, mesh_pdu_t * pdu){
243     mesh_transport_pdu_t * transport_pdu = (mesh_transport_pdu_t *) health_period_status(mesh_model);
244     if (!transport_pdu) return;
245     health_server_send_message(mesh_access_get_element_address(mesh_model), mesh_pdu_src(pdu), mesh_pdu_netkey_index(pdu), mesh_pdu_appkey_index(pdu),(mesh_pdu_t *) transport_pdu);
246     mesh_access_message_processed(pdu);
247 }
248 
249 static void process_message_period_set(mesh_model_t *mesh_model, mesh_pdu_t * pdu){
250     mesh_access_parser_state_t parser;
251     mesh_access_parser_init(&parser, (mesh_pdu_t*) pdu);
252     uint8_t fast_period_divisor = mesh_access_parser_get_u8(&parser);
253 
254     mesh_health_state_t * state = (mesh_health_state_t *) mesh_model->model_data;
255     state->fast_period_divisor = fast_period_divisor;
256 
257     mesh_health_server_update_publication_model_period_divisor(mesh_model);
258 }
259 
260 static void health_period_set_handler(mesh_model_t *mesh_model, mesh_pdu_t * pdu){
261     process_message_period_set(mesh_model, pdu);
262 
263     mesh_transport_pdu_t * transport_pdu = (mesh_transport_pdu_t *) health_period_status(mesh_model);
264     if (!transport_pdu) return;
265     health_server_send_message(mesh_access_get_element_address(mesh_model), mesh_pdu_src(pdu), mesh_pdu_netkey_index(pdu), mesh_pdu_appkey_index(pdu),(mesh_pdu_t *) transport_pdu);
266     mesh_access_message_processed(pdu);
267 }
268 
269 static void health_period_set_unacknowledged_handler(mesh_model_t *mesh_model, mesh_pdu_t * pdu){
270     process_message_period_set(mesh_model, pdu);
271     mesh_access_message_processed(pdu);
272 }
273 
274 static void health_attention_get_handler(mesh_model_t *mesh_model, mesh_pdu_t * pdu){
275     mesh_transport_pdu_t * transport_pdu = (mesh_transport_pdu_t *) health_attention_status();
276     if (!transport_pdu) return;
277     health_server_send_message(mesh_access_get_element_address(mesh_model), mesh_pdu_src(pdu), mesh_pdu_netkey_index(pdu), mesh_pdu_appkey_index(pdu),(mesh_pdu_t *) transport_pdu);
278     mesh_access_message_processed(pdu);
279 }
280 
281 static void process_message_attention_set(mesh_model_t *mesh_model, mesh_pdu_t * pdu){
282     mesh_access_parser_state_t parser;
283     mesh_access_parser_init(&parser, (mesh_pdu_t*) pdu);
284     uint8_t timer_s = mesh_access_parser_get_u8(&parser);
285     mesh_attention_timer_set(timer_s);
286 
287     if (mesh_model->model_packet_handler == NULL) return;
288 
289     uint8_t event[4];
290     int pos = 0;
291     event[pos++] = HCI_EVENT_MESH_META;
292     // reserve for size
293     pos++;
294     event[pos++] = MESH_SUBEVENT_HEALTH_ATTENTION_TIMER_CHANGED;
295     // element index
296     event[pos++] = mesh_model->element->element_index;
297     // element index
298     event[1] = pos - 2;
299     (*mesh_model->model_packet_handler)(HCI_EVENT_PACKET, 0, event, pos);
300 }
301 
302 static void health_attention_set_handler(mesh_model_t *mesh_model, mesh_pdu_t * pdu){
303     process_message_attention_set(mesh_model, pdu);
304 
305     mesh_transport_pdu_t * transport_pdu = (mesh_transport_pdu_t *) health_attention_status();
306     if (!transport_pdu) return;
307     health_server_send_message(mesh_access_get_element_address(mesh_model), mesh_pdu_src(pdu), mesh_pdu_netkey_index(pdu), mesh_pdu_appkey_index(pdu),(mesh_pdu_t *) transport_pdu);
308     mesh_access_message_processed(pdu);
309 }
310 
311 static void health_attention_set_unacknowledged_handler(mesh_model_t *mesh_model, mesh_pdu_t * pdu){
312     process_message_attention_set(mesh_model, pdu);
313     mesh_access_message_processed(pdu);
314 }
315 
316 static mesh_pdu_t * mesh_health_server_publish_state_fn(struct mesh_model * mesh_model){
317     uint16_t company_id = mesh_node_get_company_id();
318     mesh_health_fault_t * fault = mesh_health_server_active_fault(mesh_model);
319     if (fault != NULL){
320         company_id = fault->company_id;
321     }
322     // create current status
323     return health_fault_status(mesh_model, MESH_FOUNDATION_OPERATION_HEALTH_CURRENT_STATUS, company_id, false);
324 }
325 
326 // Health Message
327 const static mesh_operation_t mesh_health_model_operations[] = {
328     { MESH_FOUNDATION_OPERATION_HEALTH_FAULT_GET,                                   2, health_fault_get_handler },
329     { MESH_FOUNDATION_OPERATION_HEALTH_FAULT_CLEAR,                                 2, health_fault_clear_handler },
330     { MESH_FOUNDATION_OPERATION_HEALTH_FAULT_CLEAR_UNACKNOWLEDGED,                  2, health_fault_clear_unacknowledged_handler },
331     { MESH_FOUNDATION_OPERATION_HEALTH_FAULT_TEST,                                  3, health_fault_test_handler },
332     { MESH_FOUNDATION_OPERATION_HEALTH_FAULT_TEST_UNACKNOWLEDGED,                   3, health_fault_test_unacknowledged_handler },
333     { MESH_FOUNDATION_OPERATION_HEALTH_PERIOD_GET,                                  0, health_period_get_handler },
334     { MESH_FOUNDATION_OPERATION_HEALTH_PERIOD_SET,                                  1, health_period_set_handler },
335     { MESH_FOUNDATION_OPERATION_HEALTH_PERIOD_SET_UNACKNOWLEDGED,                   1, health_period_set_unacknowledged_handler },
336     { MESH_FOUNDATION_OPERATION_HEALTH_ATTENTION_GET,                               0, health_attention_get_handler },
337     { MESH_FOUNDATION_OPERATION_HEALTH_ATTENTION_SET,                               1, health_attention_set_handler },
338     { MESH_FOUNDATION_OPERATION_HEALTH_ATTENTION_SET_UNACKNOWLEDGED,                1, health_attention_set_unacknowledged_handler },
339     { 0, 0, NULL }
340 };
341 
342 const mesh_operation_t * mesh_health_server_get_operations(void){
343     return mesh_health_model_operations;
344 }
345 
346 void mesh_health_server_register_packet_handler(mesh_model_t *mesh_model, btstack_packet_handler_t events_packet_handler){
347     mesh_model->model_packet_handler = events_packet_handler;
348 }
349 
350 void mesh_health_server_add_fault_state(mesh_model_t *mesh_model, uint16_t company_id, mesh_health_fault_t * fault_state){
351     mesh_health_state_t * state = (mesh_health_state_t *) mesh_model->model_data;
352     mesh_health_fault_t * fault = mesh_health_server_fault_for_company_id(mesh_model, company_id);
353     btstack_assert(fault == NULL);
354     (void) fault;
355     fault_state->company_id = company_id;
356     btstack_linked_list_add(&state->faults, (btstack_linked_item_t *) fault_state);
357 }
358 
359 void mesh_health_server_set_fault(mesh_model_t *mesh_model, uint16_t company_id, uint8_t fault_code){
360     uint16_t i;
361     mesh_health_fault_t * fault = mesh_health_server_fault_for_company_id(mesh_model, company_id);
362     btstack_assert(fault != NULL);
363 
364     // add to registered faults
365     bool add_registered_fault = true;
366     for (i = 0; i < fault->num_registered_faults; i++){
367         if (fault->registered_faults[i] == fault_code){
368             add_registered_fault = false;
369             break;
370         }
371     }
372     if (add_registered_fault && (fault->num_registered_faults < MESH_MAX_NUM_FAULTS)){
373         fault->registered_faults[fault->num_registered_faults] = fault_code;
374         fault->num_registered_faults++;
375     }
376 
377     // add to current faults
378     bool add_current_fault = true;
379     for (i = 0; i < fault->num_current_faults; i++){
380         if (fault->registered_faults[i] == fault_code){
381             add_current_fault = false;
382             break;
383         }
384     }
385     if (add_current_fault && (fault->num_current_faults < MESH_MAX_NUM_FAULTS)){
386         fault->registered_faults[fault->num_current_faults] = fault_code;
387         fault->num_current_faults++;
388     }
389 
390     // update publication model
391     mesh_health_server_update_publication_model_period_divisor(mesh_model);
392 }
393 
394 void mesh_health_server_clear_fault(mesh_model_t *mesh_model, uint16_t company_id, uint8_t fault_code){
395     mesh_health_fault_t * fault = mesh_health_server_fault_for_company_id(mesh_model, company_id);
396     btstack_assert(fault != NULL);
397 
398     // remove from current faults
399     uint16_t i;
400     bool shift_faults = false;
401     for (i = 0; i < fault->num_current_faults; i++){
402         if (!shift_faults){
403             if (fault->registered_faults[i] == fault_code){
404                 shift_faults = true;
405             }
406         }
407         if (i < (MESH_MAX_NUM_FAULTS - 1)){
408             fault->registered_faults[i] = fault->registered_faults[i+1];
409         }
410     }
411 
412     // update count
413     if (shift_faults){
414         fault->num_current_faults--;
415     }
416 
417     // update publication model
418     mesh_health_server_update_publication_model_period_divisor(mesh_model);
419 }
420 
421 void mesh_health_server_set_publication_model(mesh_model_t * mesh_model, mesh_publication_model_t * publication_model){
422     btstack_assert(mesh_model != NULL);
423     btstack_assert(publication_model != NULL);
424     publication_model->publish_state_fn = &mesh_health_server_publish_state_fn;
425     mesh_model->publication_model = publication_model;
426 }
427 
428 void mesh_health_server_report_test_done(uint16_t dest, uint16_t netkey_index, uint16_t appkey_index, uint8_t test_id, uint16_t company_id, bool acknowledged){
429     mesh_model_t * mesh_model = mesh_node_get_health_server();
430     if (mesh_model == NULL) return;
431 
432     mesh_health_fault_t * fault = mesh_health_server_fault_for_company_id(mesh_model, company_id);
433     fault->test_id = test_id;
434 
435     // response for acknowledged health fault test
436     if (acknowledged){
437         mesh_transport_pdu_t * transport_pdu = (mesh_transport_pdu_t *) health_fault_status(mesh_model, MESH_FOUNDATION_OPERATION_HEALTH_FAULT_STATUS, company_id, company_id);
438         if (!transport_pdu) return;
439         health_server_send_message(mesh_node_get_primary_element_address(), dest, netkey_index, appkey_index, (mesh_pdu_t *) transport_pdu);
440     }
441 }
442