diff --git a/bacnet-stack/Makefile b/bacnet-stack/Makefile index 660199d1..6ea12893 100644 --- a/bacnet-stack/Makefile +++ b/bacnet-stack/Makefile @@ -25,6 +25,9 @@ SRCS = ports/linux/main.c \ device.c \ ai.c \ ao.c \ + bacfile.c \ + arf.c \ + awf.c \ abort.c \ reject.c \ bacerror.c \ diff --git a/bacnet-stack/ai.c b/bacnet-stack/ai.c index d2845e42..fee8627f 100644 --- a/bacnet-stack/ai.c +++ b/bacnet-stack/ai.c @@ -33,7 +33,7 @@ #include "bacenum.h" #include "config.h" // the custom stuff -#define MAX_ANALOG_INPUTS 32 +#define MAX_ANALOG_INPUTS 7 // we simply have 0-n object instances. Yours might be // more complex, and then you need validate that the diff --git a/bacnet-stack/ao.c b/bacnet-stack/ao.c index 2ae6e608..82f61882 100644 --- a/bacnet-stack/ao.c +++ b/bacnet-stack/ao.c @@ -34,7 +34,7 @@ #include "config.h" // the custom stuff #include "wp.h" -#define MAX_ANALOG_OUTPUTS 20 +#define MAX_ANALOG_OUTPUTS 4 // we choose to have a NULL level in our system represented by // a particular value. When the priorities are not in use, they diff --git a/bacnet-stack/apdu.c b/bacnet-stack/apdu.c index 100ce492..70fecaab 100644 --- a/bacnet-stack/apdu.c +++ b/bacnet-stack/apdu.c @@ -42,6 +42,25 @@ #include "tsm.h" #include "iam.h" +#ifdef TEST +void tsm_free_invoke_id(uint8_t invokeID) +{ + // dummy stub for testing + (void)invokeID; +} + +void iam_handler( + uint8_t *service_request, + uint16_t service_len, + BACNET_ADDRESS *src) +{ + // dummy stub for testing + (void)service_request; + (void)service_len; + (void)src; +} +#endif + // Confirmed Function Handlers // If they are not set, they are handled by a reject message static confirmed_function @@ -149,6 +168,35 @@ void apdu_set_confirmed_ack_handler( } } +uint16_t apdu_decode_confirmed_service_request( + uint8_t *apdu, // APDU data + uint16_t apdu_len, + BACNET_CONFIRMED_SERVICE_DATA *service_data, + uint8_t *service_choice, + uint8_t **service_request, + uint16_t *service_request_len) +{ + uint16_t len = 0; // counts where we are in PDU + + service_data->segmented_message = (apdu[0] & BIT3) ? true : false; + service_data->more_follows = (apdu[0] & BIT2) ? true : false; + service_data->segmented_response_accepted = (apdu[0] & BIT1) ? true : false; + service_data->max_segs = decode_max_segs(apdu[1]); + service_data->max_resp = decode_max_apdu(apdu[1]); + service_data->invoke_id = apdu[2]; + len = 3; + if (service_data->segmented_message) + { + service_data->sequence_number = apdu[len++]; + service_data->proposed_window_number = apdu[len++]; + } + *service_choice = apdu[len++]; + *service_request = &apdu[len++]; + *service_request_len = apdu_len - len; + + return len; +} + void apdu_handler( BACNET_ADDRESS *src, // source address bool data_expecting_reply, @@ -170,21 +218,13 @@ void apdu_handler( switch (apdu[0] & 0xF0) { case PDU_TYPE_CONFIRMED_SERVICE_REQUEST: - service_data.segmented_message = (apdu[0] & BIT3) ? true : false; - service_data.more_follows = (apdu[0] & BIT2) ? true : false; - service_data.segmented_response_accepted = (apdu[0] & BIT1) ? true : false; - service_data.max_segs = decode_max_segs(apdu[1]); - service_data.max_resp = decode_max_apdu(apdu[1]); - service_data.invoke_id = apdu[2]; - len = 3; - if (service_data.segmented_message) - { - service_data.sequence_number = apdu[len++]; - service_data.proposed_window_number = apdu[len++]; - } - service_choice = apdu[len++]; - service_request = &apdu[len++]; - service_request_len = apdu_len - len; + len = apdu_decode_confirmed_service_request( + &apdu[0], // APDU data + apdu_len, + &service_data, + &service_choice, + &service_request, + &service_request_len); if (service_choice < MAX_BACNET_CONFIRMED_SERVICE) { if (Confirmed_Function[service_choice]) diff --git a/bacnet-stack/apdu.h b/bacnet-stack/apdu.h index ffd49a5b..a4ae43cb 100644 --- a/bacnet-stack/apdu.h +++ b/bacnet-stack/apdu.h @@ -125,6 +125,14 @@ void apdu_set_unconfirmed_handler( BACNET_UNCONFIRMED_SERVICE service_choice, unconfirmed_function pFunction); +uint16_t apdu_decode_confirmed_service_request( + uint8_t *apdu, // APDU data + uint16_t apdu_len, + BACNET_CONFIRMED_SERVICE_DATA *service_data, + uint8_t *service_choice, + uint8_t **service_request, + uint16_t *service_request_len); + void apdu_handler( BACNET_ADDRESS *src, // source address bool data_expecting_reply, diff --git a/bacnet-stack/bacdcode.c b/bacnet-stack/bacdcode.c index 0dcfee0f..2a4f2306 100644 --- a/bacnet-stack/bacdcode.c +++ b/bacnet-stack/bacdcode.c @@ -1270,7 +1270,7 @@ int decode_bacnet_time(uint8_t * apdu, int *hour, int *min, int *sec, int encode_bacnet_date(uint8_t * apdu, int year, int month, int day, int wday) { - apdu[0] = year; + apdu[0] = year - 1900; apdu[1] = month; apdu[2] = day; apdu[3] = wday; @@ -1299,7 +1299,7 @@ int encode_tagged_date(uint8_t * apdu, int year, int month, int day, // returns the number of apdu bytes consumed int decode_date(uint8_t * apdu, int *year, int *month, int *day, int *wday) { - *year = apdu[0]; + *year = apdu[0] + 1900; *month = apdu[1]; *day = apdu[2]; *wday = apdu[3]; diff --git a/bacnet-stack/device.c b/bacnet-stack/device.c index ac27eac6..ce00e83e 100644 --- a/bacnet-stack/device.c +++ b/bacnet-stack/device.c @@ -31,6 +31,7 @@ #include "config.h" // the custom stuff #include "ai.h" // object list dependency #include "ao.h" // object list dependency +#include "bacfile.h" // object list dependency #include "wp.h" // write property handling static uint32_t Object_Instance_Number = 0; @@ -228,6 +229,7 @@ unsigned Device_Object_List_Count(void) count += Analog_Input_Count(); count += Analog_Output_Count(); + count += bacfile_count(); return count; } @@ -267,7 +269,18 @@ bool Device_Object_List_Identifier(unsigned array_index, status = true; } } - + + if (!status) + { + object_index -= Analog_Output_Count(); + if (object_index < bacfile_count()) + { + *object_type = OBJECT_FILE; + *instance = bacfile_index_to_instance(object_index); + status = true; + } + } + return status; } diff --git a/bacnet-stack/handlers.c b/bacnet-stack/handlers.c index 26861d6f..7625f38f 100644 --- a/bacnet-stack/handlers.c +++ b/bacnet-stack/handlers.c @@ -37,6 +37,8 @@ #include "ao.h" #include "rp.h" #include "wp.h" +#include "arf.h" +#include "bacfile.h" #include "whois.h" #include "iam.h" #include "reject.h" @@ -411,6 +413,39 @@ void ReadPropertyHandler( else error = true; break; + case OBJECT_FILE: + if (bacfile_valid_instance(object_instance)) + { + len = bacfile_encode_property_apdu( + &Temp_Buf[0], + object_instance, + object_property, + array_index, + &error_class, + &error_code); + if (len > 0) + { + // encode the APDU portion of the packet + rp_data.object_type = object_type; + rp_data.object_instance = object_instance; + rp_data.object_property = object_property; + rp_data.array_index = array_index; + rp_data.application_data = &Temp_Buf[0]; + rp_data.application_data_len = len; + // FIXME: probably need a length limitation sent with encode + pdu_len += rp_ack_encode_apdu( + &Tx_Buf[pdu_len], + service_data->invoke_id, + &rp_data); + fprintf(stderr,"Sending Read Property Ack!\n"); + send = true; + } + else + error = true; + } + else + error = true; + break; default: error = true; break; @@ -570,3 +605,175 @@ void WritePropertyHandler( return; } +void AtomicReadFileHandler( + uint8_t *service_request, + uint16_t service_len, + BACNET_ADDRESS *src, + BACNET_CONFIRMED_SERVICE_DATA *service_data) +{ + BACNET_ATOMIC_READ_FILE_DATA data; + int len = 0; + int pdu_len = 0; + BACNET_ADDRESS my_address; + bool send = false; + bool error = false; + int bytes_sent = 0; + BACNET_ERROR_CLASS error_class = ERROR_CLASS_OBJECT; + BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT; + char buffer[MAX_APDU - 16] = ""; // for reply data, less apdu overhead + + fprintf(stderr,"Received Atomic-Read-File Request!\n"); + len = arf_decode_service_request( + service_request, + service_len, + &data); + if (len < 0) + fprintf(stderr,"Unable to decode Atomic-Read-File Request!\n"); + // prepare a reply + datalink_get_my_address(&my_address); + // encode the NPDU portion of the packet + pdu_len = npdu_encode_apdu( + &Tx_Buf[0], + src, + &my_address, + false, // true for confirmed messages + MESSAGE_PRIORITY_NORMAL); + // bad decoding - send an abort + if (len < 0) + { + pdu_len += abort_encode_apdu( + &Tx_Buf[pdu_len], + service_data->invoke_id, + ABORT_REASON_OTHER); + fprintf(stderr,"Sending Abort!\n"); + send = true; + } + else if (service_data->segmented_message) + { + pdu_len += abort_encode_apdu( + &Tx_Buf[pdu_len], + service_data->invoke_id, + ABORT_REASON_SEGMENTATION_NOT_SUPPORTED); + fprintf(stderr,"Sending Abort!\n"); + send = true; + } + else + { + if (data.access == FILE_STREAM_ACCESS) + { + data.fileData = &buffer[0]; + data.fileDataLength = sizeof(buffer); + if (data.type.stream.requestedOctetCount < data.fileDataLength) + { + if (bacfile_read_data(&data)) + { + pdu_len += arf_ack_encode_apdu( + &Tx_Buf[pdu_len], + service_data->invoke_id, + &data); + send = true; + } + else + { + send = true; + error = true; + } + } + else + { + pdu_len += abort_encode_apdu( + &Tx_Buf[pdu_len], + service_data->invoke_id, + ABORT_REASON_SEGMENTATION_NOT_SUPPORTED); + fprintf(stderr,"Sending Abort!\n"); + send = true; + } + } + else + { + error_class = ERROR_CLASS_SERVICES; + error_code = ERROR_CODE_INVALID_FILE_ACCESS_METHOD; + send = true; + error = true; + } + } + if (error) + { + pdu_len += bacerror_encode_apdu( + &Tx_Buf[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_ATOMIC_READ_FILE, + error_class, + error_code); + fprintf(stderr,"Sending Error!\n"); + send = true; + } + if (send) + { + bytes_sent = datalink_send_pdu( + src, // destination address + &Tx_Buf[0], + pdu_len); // number of bytes of data + if (bytes_sent <= 0) + fprintf(stderr,"Failed to send PDU (%s)!\n", strerror(errno)); + } + + return; +} + +// We performed an AtomicReadFile Request, +// and here is the data from the server +// Note: it does not have to be the same file=instance +// that someone can read from us. It is common to +// use the description as the file name. +void AtomicReadFileAckHandler( + uint8_t *service_request, + uint16_t service_len, + BACNET_ADDRESS *src, + BACNET_CONFIRMED_SERVICE_ACK_DATA *service_data) +{ + int len = 0; + BACNET_ATOMIC_READ_FILE_DATA data; + FILE *pFile = NULL; + char *pFilename = NULL; + uint32_t instance = 0; + + (void)src; + // get the file instance from the tsm data before freeing it + instance = bacfile_instance_from_tsm(service_data->invoke_id); + tsm_free_invoke_id(service_data->invoke_id); + len = arf_ack_decode_service_request( + service_request, + service_len, + &data); + fprintf(stderr,"Received Read-File Ack!\n"); + if ((len > 0) && (instance <= BACNET_MAX_INSTANCE)) + { + // write the data received to the file specified + if (data.access == FILE_STREAM_ACCESS) + { + pFilename = bacfile_name(instance); + if (pFilename) + { + pFile = fopen(pFilename, "rb"); + if (pFile) + { + (void)fseek(pFile, + data.type.stream.fileStartPosition, + SEEK_SET); + if (fwrite(data.fileData,data.fileDataLength,1,pFile) != 1) + fprintf(stderr,"Failed to write to %s (%u)!\n", + pFilename, instance); + fclose(pFile); + } + } + } + else if (data.access == FILE_RECORD_ACCESS) + { + // FIXME: add handling for Record Access + } + } +} + + + diff --git a/bacnet-stack/handlers.h b/bacnet-stack/handlers.h index 9fde8944..5be29ec0 100644 --- a/bacnet-stack/handlers.h +++ b/bacnet-stack/handlers.h @@ -76,4 +76,15 @@ bool Send_Read_Property_Request( BACNET_PROPERTY_ID object_property, int32_t array_index); +void AtomicReadFileHandler( + uint8_t *service_request, + uint16_t service_len, + BACNET_ADDRESS *src, + BACNET_CONFIRMED_SERVICE_DATA *service_data); +void AtomicReadFileAckHandler( + uint8_t *service_request, + uint16_t service_len, + BACNET_ADDRESS *src, + BACNET_CONFIRMED_SERVICE_ACK_DATA *service_data); + #endif diff --git a/bacnet-stack/iam.h b/bacnet-stack/iam.h index 6fefb534..7425834d 100644 --- a/bacnet-stack/iam.h +++ b/bacnet-stack/iam.h @@ -66,6 +66,7 @@ void iam_handler( int iam_send(uint8_t *buffer); #ifdef TEST +#include "ctest.h" void testIAm(Test * pTest); #endif diff --git a/bacnet-stack/npdu.c b/bacnet-stack/npdu.c index a0c57063..d8f4eeef 100644 --- a/bacnet-stack/npdu.c +++ b/bacnet-stack/npdu.c @@ -175,7 +175,11 @@ int npdu_decode( BACNET_NPDU_DATA *npdu_data) { int len = 0; // return value - number of octets loaded in this function - int i = 0; // counter + int i = 0; // counter + uint16_t src_net = 0; + uint16_t dest_net = 0; + uint8_t address_len = 0; + uint8_t mac_octet = 0; if (npdu && npdu_data) { @@ -211,30 +215,35 @@ int npdu_decode( // 1 = DNET, DLEN, and Hop Count present // DLEN = 0 denotes broadcast MAC DADR and DADR field is absent // DLEN > 0 specifies length of DADR field - if (dest) + if (npdu[1] & BIT5) { - if (npdu[1] & BIT5) + len += decode_unsigned16(&npdu[len], &dest_net); + // DLEN = 0 denotes broadcast MAC DADR and DADR field is absent + // DLEN > 0 specifies length of DADR field + address_len = npdu[len++]; + if (dest) { - len += decode_unsigned16(&npdu[len], &dest->net); - // DLEN = 0 denotes broadcast MAC DADR and DADR field is absent - // DLEN > 0 specifies length of DADR field - dest->len = npdu[len++]; - if (dest->len) + dest->net = dest_net; + dest->len = address_len; + } + if (address_len) + { + for (i = 0; i < address_len; i++) { - for (i = 0; i < dest->len; i++) - { - dest->adr[i] = npdu[len++]; - } + mac_octet = npdu[len++]; + if (dest) + dest->adr[i] = mac_octet; } } - else + } + // zero out the destination address + else if (dest) + { + dest->net = 0; + dest->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { - dest->net = 0; - dest->len = 0; - for (i = 0; i < MAX_MAC_LEN; i++) - { - dest->adr[i] = 0; - } + dest->adr[i] = 0; } } // Bit 3: Source specifier where: @@ -242,36 +251,40 @@ int npdu_decode( // 1 = SNET, SLEN, and SADR present // SLEN = 0 Invalid // SLEN > 0 specifies length of SADR field - if (src) + if (npdu[1] & BIT3) { - if (npdu[1] & BIT3) + len += decode_unsigned16(&npdu[len], &src_net); + // SLEN = 0 denotes broadcast MAC SADR and SADR field is absent + // SLEN > 0 specifies length of SADR field + address_len = npdu[len++]; + if (src) { - len += decode_unsigned16(&npdu[len], &src->net); - // SLEN = 0 denotes broadcast MAC SADR and SADR field is absent - // SLEN > 0 specifies length of SADR field - src->len = npdu[len++]; - if (src->len) + src->net = src_net; + src->len = address_len; + } + if (address_len) + { + for (i = 0; i < address_len; i++) { - for (i = 0; i < src->len; i++) - { - src->adr[i] = npdu[len++]; - } + mac_octet = npdu[len++]; + if (src) + src->adr[i] = mac_octet; } } - else + } + else if (src) + { + src->net = 0; + src->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { - src->net = 0; - src->len = 0; - for (i = 0; i < MAX_MAC_LEN; i++) - { - src->adr[i] = 0; - } + src->adr[i] = 0; } } // The Hop Count field shall be present only if the message is // destined for a remote network, i.e., if DNET is present. // This is a one-octet field that is initialized to a value of 0xff. - if (dest && dest->net) + if (dest_net) npdu_data->hop_count = npdu[len++]; else npdu_data->hop_count = 0; diff --git a/bacnet-stack/ports/linux/main.c b/bacnet-stack/ports/linux/main.c index cc4c1a4b..984ab245 100644 --- a/bacnet-stack/ports/linux/main.c +++ b/bacnet-stack/ports/linux/main.c @@ -38,6 +38,7 @@ #include "iam.h" #include "tsm.h" #include "device.h" +#include "bacfile.h" #ifdef BACDL_ETHERNET #include "ethernet.h" #endif @@ -236,6 +237,7 @@ static void Init_Service_Handlers(void) // It is required to send the proper reject message... apdu_set_unrecognized_service_handler_handler( UnrecognizedServiceHandler); + // Set the handlers for any confirmed services that we support // we must implement read property - it's required! apdu_set_confirmed_handler( SERVICE_CONFIRMED_READ_PROPERTY, @@ -243,10 +245,16 @@ static void Init_Service_Handlers(void) apdu_set_confirmed_handler( SERVICE_CONFIRMED_WRITE_PROPERTY, WritePropertyHandler); + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_ATOMIC_READ_FILE, + AtomicReadFileHandler); // handle the data coming back from confirmed requests apdu_set_confirmed_ack_handler( SERVICE_CONFIRMED_READ_PROPERTY, ReadPropertyAckHandler); + apdu_set_confirmed_ack_handler( + SERVICE_CONFIRMED_ATOMIC_READ_FILE, + AtomicReadFileAckHandler); } static void print_address_cache(void) diff --git a/bacnet-stack/tsm.c b/bacnet-stack/tsm.c index 2803c63e..25fd2eef 100644 --- a/bacnet-stack/tsm.c +++ b/bacnet-stack/tsm.c @@ -137,7 +137,6 @@ uint8_t tsm_next_free_invokeID(void) return invokeID; } -// returns 0 if there are no free transactions void tsm_set_confirmed_unsegmented_transaction( uint8_t invokeID, BACNET_ADDRESS *dest, @@ -146,7 +145,6 @@ void tsm_set_confirmed_unsegmented_transaction( { uint16_t j = 0; uint8_t index; - if (invokeID) { @@ -171,6 +169,40 @@ void tsm_set_confirmed_unsegmented_transaction( return; } +// used to retrieve the transaction payload +// if we wanted to find out what we sent (i.e. when we get an ack) +bool tsm_get_transaction_pdu( + uint8_t invokeID, + BACNET_ADDRESS *dest, + uint8_t *pdu, + uint16_t *pdu_len) +{ + uint16_t j = 0; + uint8_t index; + bool found = false; + + if (invokeID) + { + index = tsm_find_invokeID_index(invokeID); + // how much checking is needed? state? dest match? just invokeID? + if (index < MAX_TSM_TRANSACTIONS) + { + // FIXME: we may want to free the transaction so it doesn't timeout + // retrieve the transaction + // FIXME: bounds check the pdu_len? + *pdu_len = TSM_List[index].pdu_len; + for (j = 0; j < *pdu_len; j++) + { + pdu[j] = TSM_List[index].pdu[j]; + } + address_copy(dest,&TSM_List[index].dest); + found = true; + } + } + + return found; +} + // called once a millisecond void tsm_timer_milliseconds(uint16_t milliseconds) { diff --git a/bacnet-stack/tsm.h b/bacnet-stack/tsm.h index 07ad07d0..7d4e8c99 100644 --- a/bacnet-stack/tsm.h +++ b/bacnet-stack/tsm.h @@ -97,6 +97,12 @@ void tsm_set_confirmed_unsegmented_transaction( BACNET_ADDRESS *dest, uint8_t *pdu, uint16_t pdu_len); +// returns true if transaction is found +bool tsm_get_transaction_pdu( + uint8_t invokeID, + BACNET_ADDRESS *dest, + uint8_t *pdu, + uint16_t *pdu_len); #endif