diff --git a/bacnet-stack/demo/handler/s_ts.c b/bacnet-stack/demo/handler/s_ts.c index 8d755964..baadc80b 100644 --- a/bacnet-stack/demo/handler/s_ts.c +++ b/bacnet-stack/demo/handler/s_ts.c @@ -45,13 +45,13 @@ /** @file s_ts.c Send TimeSync requests. */ -void Send_TimeSync( +void Send_TimeSync_Remote( + BACNET_ADDRESS * dest, BACNET_DATE * bdate, BACNET_TIME * btime) { int len = 0; int pdu_len = 0; - BACNET_ADDRESS dest; int bytes_sent = 0; BACNET_NPDU_DATA npdu_data; BACNET_ADDRESS my_address; @@ -59,13 +59,11 @@ void Send_TimeSync( if (!dcc_communication_enabled()) return; - /* we could use unicast or broadcast */ - datalink_get_broadcast_address(&dest); datalink_get_my_address(&my_address); /* encode the NPDU portion of the packet */ npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL); pdu_len = - npdu_encode_pdu(&Handler_Transmit_Buffer[0], &dest, &my_address, + npdu_encode_pdu(&Handler_Transmit_Buffer[0], dest, &my_address, &npdu_data); /* encode the APDU portion of the packet */ @@ -74,7 +72,7 @@ void Send_TimeSync( pdu_len += len; /* send it out the datalink */ bytes_sent = - datalink_send_pdu(&dest, &npdu_data, &Handler_Transmit_Buffer[0], + datalink_send_pdu(dest, &npdu_data, &Handler_Transmit_Buffer[0], pdu_len); #if PRINT_ENABLED if (bytes_sent <= 0) @@ -83,6 +81,16 @@ void Send_TimeSync( #endif } +void Send_TimeSync( + BACNET_DATE * bdate, + BACNET_TIME * btime) +{ + BACNET_ADDRESS dest; + + datalink_get_broadcast_address(&dest); + Send_TimeSync_Remote(&dest, bdate, btime); +} + void Send_TimeSyncUTC( BACNET_DATE * bdate, BACNET_TIME * btime) diff --git a/bacnet-stack/include/client.h b/bacnet-stack/include/client.h index b318bf65..baee6ff4 100644 --- a/bacnet-stack/include/client.h +++ b/bacnet-stack/include/client.h @@ -168,6 +168,10 @@ extern "C" { void Send_TimeSync( BACNET_DATE * bdate, BACNET_TIME * btime); + void Send_TimeSync_Remote( + BACNET_ADDRESS * dest, + BACNET_DATE * bdate, + BACNET_TIME * btime); void Send_TimeSyncUTC( BACNET_DATE * bdate, BACNET_TIME * btime); diff --git a/bacnet-stack/include/handlers.h b/bacnet-stack/include/handlers.h index 5ca772f5..229c8c2c 100644 --- a/bacnet-stack/include/handlers.h +++ b/bacnet-stack/include/handlers.h @@ -176,15 +176,27 @@ extern "C" { uint16_t service_len, BACNET_ADDRESS * src); + /* time synchronization handlers */ void handler_timesync( uint8_t * service_request, uint16_t service_len, BACNET_ADDRESS * src); - void handler_timesync_utc( uint8_t * service_request, uint16_t service_len, BACNET_ADDRESS * src); + /* time sync master features */ + int handler_timesync_encode_recipients( + uint8_t * apdu, + int max_apdu); + void handler_timesync_task(void); + void handler_timesync_init(void); + bool handler_timesync_recipient_write( + BACNET_WRITE_PROPERTY_DATA * wp_data); + bool handler_timesync_interval_set(uint32_t minutes); + bool handler_timesync_recipient_address_set( + unsigned index, + BACNET_ADDRESS *address); void handler_read_property_multiple( uint8_t * service_request, diff --git a/bacnet-stack/include/timesync.h b/bacnet-stack/include/timesync.h index feea60f6..e6684ae5 100644 --- a/bacnet-stack/include/timesync.h +++ b/bacnet-stack/include/timesync.h @@ -28,6 +28,23 @@ #include #include "bacdef.h" +struct BACnet_Recipient_List; +typedef struct BACnet_Recipient_List { + /* + BACnetRecipient ::= CHOICE { + device [0] BACnetObjectIdentifier, + address [1] BACnetAddress + } + */ + uint8_t tag; + union { + BACNET_OBJECT_ID device; + BACNET_ADDRESS address; + } type; + /* simple linked list */ + struct BACnet_Recipient_List *next; +} BACNET_RECIPIENT_LIST; + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ @@ -63,6 +80,15 @@ extern "C" { BACNET_DATE * my_date, BACNET_TIME * my_time); + int timesync_encode_timesync_recipients( + uint8_t * apdu, + unsigned max_apdu, + BACNET_RECIPIENT_LIST *recipient); + int timesync_decode_timesync_recipients( + uint8_t * apdu, + unsigned apdu_len, + BACNET_RECIPIENT_LIST *recipient); + #ifdef TEST #include "ctest.h" void testTimeSync( diff --git a/bacnet-stack/src/timesync.c b/bacnet-stack/src/timesync.c index 04deacd1..c07c0b7a 100644 --- a/bacnet-stack/src/timesync.c +++ b/bacnet-stack/src/timesync.c @@ -113,11 +113,304 @@ int timesync_decode_service_request( return len; } +/** Handle a request to encode the list of timesync recipients. + * + * Invoked by a request to read the Device object's + * PROP_TIME_SYNCHRONIZATION_RECIPIENTS. + * Loops through the list of timesync recipients, and, for each one, + * adds its data to the APDU. + * + * BACnetRecipient ::= CHOICE { + * device [0] BACnetObjectIdentifier, + * address [1] BACnetAddress + * } + * + * BACnetAddress ::= SEQUENCE { + * network-number Unsigned16, -- A value of 0 indicates the local network + * mac-address OCTET STRING -- A string of length 0 indicates a broadcast + * } + * + * @param apdu [out] Buffer in which the APDU contents are built. + * @param max_apdu [in] Max length of the APDU buffer. + * @param recipient [in] BACNET_RECIPIENT_LIST type linked list of recipients. + * + * @return How many bytes were encoded in the buffer, or + * BACNET_STATUS_ABORT if the response would not fit within the buffer. + */ +int timesync_encode_timesync_recipients( + uint8_t * apdu, + unsigned max_apdu, + BACNET_RECIPIENT_LIST *recipient) +{ + int len = 0; + int apdu_len = 0; + BACNET_OCTET_STRING octet_string; + BACNET_RECIPIENT_LIST *pRecipient; + + pRecipient = recipient; + while (pRecipient != NULL) { + if (pRecipient->tag == 0) { + if (max_apdu >= (1+4)) { + /* CHOICE - device [0] BACnetObjectIdentifier */ + len = encode_context_object_id(&apdu[apdu_len], 0, + pRecipient->type.device.type, + pRecipient->type.device.instance); + apdu_len += len; + } else { + return BACNET_STATUS_ABORT; + } + } else if (pRecipient->tag == 1) { + if (pRecipient->type.address.net) { + len = 1 + 3 + 2 + pRecipient->type.address.net + 1; + } else { + len = 1 + 3 + 2 + pRecipient->type.address.mac_len + 1; + } + if (max_apdu >= len) { + /* CHOICE - address [1] BACnetAddress - opening */ + len = encode_opening_tag(&apdu[apdu_len], 1); + apdu_len += len; + /* network-number Unsigned16, */ + /* -- A value of 0 indicates the local network */ + len = + encode_application_unsigned(&apdu[apdu_len], + pRecipient->type.address.net); + apdu_len += len; + /* mac-address OCTET STRING */ + /* -- A string of length 0 indicates a broadcast */ + if (pRecipient->type.address.net == BACNET_BROADCAST_NETWORK) { + octetstring_init(&octet_string, NULL, 0); + } else if (pRecipient->type.address.net) { + octetstring_init(&octet_string, + &pRecipient->type.address.adr[0], + pRecipient->type.address.len); + } else { + octetstring_init(&octet_string, + &pRecipient->type.address.mac[0], + pRecipient->type.address.mac_len); + } + len = encode_application_octet_string(&apdu[apdu_len], + &octet_string); + apdu_len += len; + /* CHOICE - address [1] BACnetAddress - closing */ + len = encode_closing_tag(&apdu[apdu_len], 1); + apdu_len += len; + } else { + /* not a valid tag - don't encode this one */ + } + } + pRecipient = pRecipient->next; + } + + return apdu_len; +} + +/** Handle a request to decode a list of timesync recipients. + * + * Invoked by a request to write the Device object's + * PROP_TIME_SYNCHRONIZATION_RECIPIENTS. + * Loops through the list of timesync recipients, and, for each one, + * adds its data from the APDU. + * + * BACnetRecipient ::= CHOICE { + * device [0] BACnetObjectIdentifier, + * address [1] BACnetAddress + * } + * + * BACnetAddress ::= SEQUENCE { + * network-number Unsigned16, -- A value of 0 indicates the local network + * mac-address OCTET STRING -- A string of length 0 indicates a broadcast + * } + * + * @param apdu [in] Buffer in which the APDU contents are read + * @param max_apdu [in] length of the APDU buffer. + * @param recipient [out] BACNET_RECIPIENT_LIST type linked list of recipients. + * + * @return How many bytes were decoded from the buffer, or + * BACNET_STATUS_ABORT if there was a problem decoding the buffer + */ +int timesync_decode_timesync_recipients( + uint8_t * apdu, + unsigned max_apdu, + BACNET_RECIPIENT_LIST *recipient) +{ + int len = 0; + int apdu_len = 0; + int tag_len = 0; + uint8_t tag_number = 0; + uint32_t len_value_type = 0; + uint32_t unsigned_value = 0; + BACNET_OCTET_STRING octet_string; + BACNET_RECIPIENT_LIST *pRecipient; + + pRecipient = recipient; + while (pRecipient != NULL) { + /* device [0] BACnetObjectIdentifier */ + if (decode_is_context_tag(&apdu[apdu_len], 0)) { + pRecipient->tag = 0; + len = decode_context_object_id(&apdu[apdu_len], 0, + &pRecipient->type.device.type, + &pRecipient->type.device.instance); + if (len < 0) { + return BACNET_STATUS_ABORT; + } + apdu_len += len; + } else if (decode_is_context_tag(&apdu[apdu_len], 1)) { + apdu_len += 1; + pRecipient->tag = 1; + /* network-number Unsigned16 */ + tag_len = + decode_tag_number_and_value(&apdu[apdu_len], &tag_number, + &len_value_type); + apdu_len += tag_len; + if (tag_number != BACNET_APPLICATION_TAG_UNSIGNED_INT) { + return BACNET_STATUS_ABORT; + } + len = decode_unsigned(&apdu[apdu_len], len_value_type, &unsigned_value); + pRecipient->type.address.net = unsigned_value; + apdu_len += len; + /* mac-address OCTET STRING */ + tag_len = + decode_tag_number_and_value(&apdu[apdu_len], &tag_number, + &len_value_type); + apdu_len += tag_len; + if (tag_number != BACNET_APPLICATION_TAG_OCTET_STRING) { + return BACNET_STATUS_ABORT; + } + len = decode_octet_string(&apdu[0], len_value_type, + &octet_string); + apdu_len += len; + if (octetstring_length(&octet_string) == 0) { + /* -- A string of length 0 indicates a broadcast */ + } else if (pRecipient->type.address.net) { + pRecipient->type.address.len = + octetstring_copy_value( + &pRecipient->type.address.adr[0], + sizeof(pRecipient->type.address.adr), + &octet_string); + } else { + pRecipient->type.address.mac_len = + octetstring_copy_value( + &pRecipient->type.address.mac[0], + sizeof(pRecipient->type.address.mac), + &octet_string); + } + } else { + return BACNET_STATUS_ABORT; + } + pRecipient = pRecipient->next; + } + + return apdu_len; +} + + #ifdef TEST #include #include #include "ctest.h" +void testTimeSyncRecipientData( + Test * pTest, + BACNET_RECIPIENT_LIST *recipient1, + BACNET_RECIPIENT_LIST *recipient2) +{ + unsigned i = 0; + + if (recipient1 && recipient2) { + ct_test(pTest, recipient1->tag == recipient2->tag); + if (recipient1->tag == 0) { + ct_test(pTest, + recipient1->type.device.type == + recipient2->type.device.type); + ct_test(pTest, + recipient1->type.device.instance == + recipient2->type.device.instance); + } else if (recipient1->tag == 1) { + ct_test(pTest, + recipient1->type.address.net == + recipient2->type.address.net); + if (recipient1->type.address.net == BACNET_BROADCAST_NETWORK) { + ct_test(pTest, + recipient1->type.address.mac_len == + recipient2->type.address.mac_len); + } else if (recipient1->type.address.net) { + ct_test(pTest, + recipient1->type.address.len == + recipient2->type.address.len); + for (i = 0; i < recipient1->type.address.len; i++) { + ct_test(pTest, + recipient1->type.address.adr[i] == + recipient2->type.address.adr[i]); + } + } else { + ct_test(pTest, + recipient1->type.address.mac_len == + recipient2->type.address.mac_len); + for (i = 0; i < recipient1->type.address.mac_len; i++) { + ct_test(pTest, + recipient1->type.address.mac[i] == + recipient2->type.address.mac[i]); + } + } + } else { + ct_test(pTest, recipient1->tag <= 1); + } + } +} + +void testTimeSyncRecipient( + Test * pTest) +{ + uint8_t apdu[480] = { 0 }; + int len = 0; + BACNET_RECIPIENT_LIST recipient[4]; + BACNET_RECIPIENT_LIST test_recipient[4]; + + /* link the recipient list */ + recipient[0].next = &recipient[1]; + recipient[1].next = &recipient[2]; + recipient[2].next = &recipient[3]; + recipient[3].next = NULL; + /* link the test recipient list */ + test_recipient[0].next = &test_recipient[1]; + test_recipient[1].next = &test_recipient[2]; + test_recipient[2].next = &test_recipient[3]; + test_recipient[3].next = NULL; + /* load the test data - device */ + recipient[0].tag = 0; + recipient[0].type.device.type = OBJECT_DEVICE; + recipient[0].type.device.instance = 1234; + /* load the test data - address */ + /* network = broadcast */ + recipient[1].tag = 1; + recipient[1].type.address.net = BACNET_BROADCAST_NETWORK; + recipient[2].type.address.mac_len = 0; + /* network = non-zero */ + recipient[1].tag = 1; + recipient[2].type.address.net = 4201; + recipient[2].type.address.adr[0] = 127; + recipient[2].type.address.len = 1; + /* network = zero */ + recipient[2].type.address.net = 0; + recipient[2].type.address.mac[0] = 10; + recipient[2].type.address.mac[1] = 1; + recipient[2].type.address.mac[2] = 0; + recipient[2].type.address.mac[3] = 86; + recipient[2].type.address.mac[4] = 0xBA; + recipient[2].type.address.mac[5] = 0xC1; + recipient[2].type.address.mac_len = 6; + /* perform positive test */ + len = timesync_encode_timesync_recipients(&apdu[0], + sizeof(apdu), &recipient[0]); + ct_test(pTest, len != BACNET_STATUS_ABORT); + ct_test(pTest, len > 0); + len = timesync_decode_timesync_recipients(&apdu[0], + sizeof(apdu), &test_recipient[0]); + ct_test(pTest, len != BACNET_STATUS_ABORT); + ct_test(pTest, len > 0); + testTimeSyncRecipientData(pTest, &recipient[0], &test_recipient[0]); +} + int timesync_decode_apdu_service( uint8_t * apdu, BACNET_UNCONFIRMED_SERVICE service,