/** * @file * @brief Device Management-Backup and Restore tool that generates * a Wireshark PCAP format file from a CreateObject services encoded file. * @author Steve Karg * @date February 2026 * @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0 */ #include #include #include #include #include #include #include /* BACnet Stack defines - first */ #include "bacnet/bacdef.h" #include "bacnet/bacint.h" #include "bacnet/create_object.h" #include "bacnet/datetime.h" #include "bacnet/npdu.h" #include "bacnet/basic/sys/mstimer.h" #include "bacnet/basic/sys/filename.h" /* define our Data Link Type for libPCAP */ #define DLT_CAPTURE_TYPE (1) static uint8_t MTU_Buffer[1501]; static char Capture_Filename[64] = "dmbr_20260209012345.cap"; static FILE *Capture_File_Handle = NULL; /* stream pointer */ static FILE *Backup_File_Handle = NULL; /* stream pointer */ static long Backup_File_Start_Position; static long Backup_File_Packet_Counter; /** * @brief Write data to the capture file. * @param ptr Pointer to the data to be written * @param size Size of each element to write * @param nitems Number of items to write * @return Number of items successfully written to the capture file */ static size_t data_write(const void *ptr, size_t size, size_t nitems) { size_t written = 0; if (Capture_File_Handle) { written = fwrite(ptr, size, nitems, Capture_File_Handle); } return written; } /** * @brief Write header data to the capture file. * @param ptr Pointer to the header data to be written * @param size Size of each element to write * @param nitems Number of items to write * @return Number of items successfully written to the capture file */ static size_t data_write_header(const void *ptr, size_t size, size_t nitems) { size_t written = 0; if (Capture_File_Handle) { written = fwrite(ptr, size, nitems, Capture_File_Handle); } return written; } /** * @brief Create a new capture filename based on the current date and time. * * Closes any existing capture file, generates a new filename using the * current local date and time, and opens a new capture file for writing. */ static void filename_create_new(void) { BACNET_DATE bdate; BACNET_TIME btime; char *filename = &Capture_Filename[0]; size_t filename_size = sizeof(Capture_Filename); if (Capture_File_Handle) { fclose(Capture_File_Handle); } Capture_File_Handle = NULL; datetime_local(&bdate, &btime, NULL, NULL); snprintf( filename, filename_size, "dmbr_%04d%02d%02d%02d%02d%02d.cap", (int)bdate.year, (int)bdate.month, (int)bdate.day, (int)btime.hour, (int)btime.min, (int)btime.sec); Capture_File_Handle = fopen(filename, "wb"); if (Capture_File_Handle) { fprintf(stdout, "dmbrcap: saving capture to %s\n", filename); } else { fprintf( stderr, "dmbrcap: failed to open %s: %s\n", filename, strerror(errno)); } } /** * @brief Write the global header to the capture file in libpcap format. * * Writes the standard libpcap global header including magic number, * version information, timezone, timestamp accuracy, snapshot length, * and data link type. */ static void write_global_header(void) { uint32_t magic_number = 0xa1b2c3d4; /* magic number */ uint16_t version_major = 2; /* major version number */ uint16_t version_minor = 4; /* minor version number */ int32_t thiszone = 0; /* GMT to local correction */ uint32_t sigfigs = 0; /* accuracy of timestamps */ uint32_t snaplen = 65535; /* max length of captured packet, in octets */ uint32_t network = DLT_CAPTURE_TYPE; /* data link type */ /* create a new file. */ (void)data_write_header(&magic_number, sizeof(magic_number), 1); (void)data_write_header(&version_major, sizeof(version_major), 1); (void)data_write_header(&version_minor, sizeof(version_minor), 1); (void)data_write_header(&thiszone, sizeof(thiszone), 1); (void)data_write_header(&sigfigs, sizeof(sigfigs), 1); (void)data_write_header(&snaplen, sizeof(snaplen), 1); (void)data_write_header(&network, sizeof(network), 1); fflush(Capture_File_Handle); } /** * @brief Write a packet record to the capture file in libpcap format. * * Writes a packet record with timestamp and length information followed by * the packet data to the capture file in libpcap format. * @param packet Pointer to the packet data to write * @param packet_len Length of the packet data in bytes */ static void write_received_packet(uint8_t *packet, size_t packet_len) { uint32_t ts_msec = 0; /* timestamp milliseconds */ uint32_t ts_sec = 0; /* timestamp seconds */ uint32_t ts_usec = 0; /* timestamp microseconds */ uint32_t incl_len = 0; /* number of octets of packet saved in file */ uint32_t orig_len = 0; /* actual length of packet */ ts_msec = mstimer_now(); ts_sec = ts_msec / 1000; ts_usec = (ts_msec % 1000) * 1000; (void)data_write(&ts_sec, sizeof(ts_sec), 1); (void)data_write(&ts_usec, sizeof(ts_usec), 1); incl_len = packet_len; (void)data_write(&incl_len, sizeof(incl_len), 1); orig_len = packet_len; (void)data_write(&orig_len, sizeof(orig_len), 1); (void)data_write(packet, packet_len, 1); } /** * @brief Open a backup file for reading. * * Opens an existing backup file for reading and initializes the file * position tracking for packet extraction. * @param filename Path to the backup file to open * @return true if the file was successfully opened, false otherwise */ static bool open_backup_file(const char *filename) { /* open existing file. */ Backup_File_Handle = fopen(filename, "rb"); if (Backup_File_Handle) { fprintf(stdout, "dmbrcap: reading backup from %s\n", filename); Backup_File_Start_Position = 0; return true; } fprintf( stderr, "dmbrcap: failed to open %s: %s\n", filename, strerror(errno)); return false; } /** * @brief Extract and convert the next packet from the backup file. * * Reads the next CreateObject service request from the backup file, * encapsulates it in BACnet NPDU and APDU headers with Ethernet framing, * and writes the complete packet to the capture file. Advances the file * position for the next packet extraction. * @return Length of the packet written, or 0 if no more packets are available */ static size_t backup_file_packet(void) { BACNET_NPDU_DATA npdu_data = { 0 }; size_t len = 0, packet_len = 0, apdu_len = 0; int decoded_len = 0; uint8_t apdu[1500] = { 0 }; /* Ethernet SNAP Encoding*/ /* dest MAC */ MTU_Buffer[0] = 0xFF; MTU_Buffer[1] = 0xFF; MTU_Buffer[2] = 0xFF; MTU_Buffer[3] = 0xFF; MTU_Buffer[4] = 0xFF; MTU_Buffer[5] = 0xFF; /* source MAC */ MTU_Buffer[6] = 0xFF; MTU_Buffer[7] = 0xFF; MTU_Buffer[8] = 0xFF; MTU_Buffer[9] = 0xFF; MTU_Buffer[10] = 0xFF; MTU_Buffer[11] = 0xFF; /* length - 12, 13 */ /* Logical-Link Control SNAP */ /* DSAP for SNAP */ MTU_Buffer[14] = 0x82; /* SSAP for SNAP */ MTU_Buffer[15] = 0x82; /* Control Field for SNAP */ MTU_Buffer[16] = 0x03; /* BACnet NPDU */ len = npdu_encode_pdu(&MTU_Buffer[17], NULL, NULL, &npdu_data); packet_len = 17 + len; /* BACnet APDU header */ MTU_Buffer[packet_len++] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST; MTU_Buffer[packet_len++] = encode_max_segs_max_apdu(0, MAX_APDU); MTU_Buffer[packet_len++] = Backup_File_Packet_Counter % 256; MTU_Buffer[packet_len++] = SERVICE_CONFIRMED_CREATE_OBJECT; /* BACnet APDU service data */ if (Backup_File_Handle) { (void)fseek(Backup_File_Handle, Backup_File_Start_Position, SEEK_SET); apdu_len = fread(apdu, sizeof(uint8_t), sizeof(apdu), Backup_File_Handle); if (apdu_len > 0) { decoded_len = create_object_decode_service_request(apdu, apdu_len, NULL); if (decoded_len > 0) { apdu_len = decoded_len; Backup_File_Start_Position += decoded_len; if ((packet_len + apdu_len) > sizeof(MTU_Buffer)) { apdu_len = sizeof(MTU_Buffer) - packet_len; } memcpy(&MTU_Buffer[packet_len], apdu, apdu_len); packet_len += apdu_len; /* Ethernet length is data only - not address or length bytes */ encode_unsigned16(&MTU_Buffer[12], packet_len - 14); write_received_packet(MTU_Buffer, packet_len); } else { packet_len = 0; } } else { packet_len = 0; } } return packet_len; } /** * @brief Clean up and close all open file handles. * * Flushes and closes the capture file and backup file handles, preparing * the program for exit. This function is registered as an exit handler. */ static void cleanup(void) { if (Capture_File_Handle) { fflush(Capture_File_Handle); fclose(Capture_File_Handle); } Capture_File_Handle = NULL; if (Backup_File_Handle) { fclose(Backup_File_Handle); } Backup_File_Handle = NULL; } /** * @brief Print the command-line usage information. * @param filename The name of the program (typically argv[0]) */ static void print_usage(const char *filename) { printf("Usage: %s ", filename); printf(" [--version][--help]\n"); } /** * @brief Print detailed help information for the program. * @param filename The name of the program (typically argv[0]) */ static void print_help(const char *filename) { printf( "%s \n" "convert a backup file into a capture file.\n", filename); printf("\n"); } /** * @brief Main entry point for the dmbrcap utility. * * Processes command-line arguments, opens the backup file, initializes the * timer system, creates a new capture file with libpcap headers, reads all * packets from the backup file and converts them to libpcap format, then * closes all files and exits. * @param argc Number of command-line arguments * @param argv Array of command-line argument strings * @return 0 on success, 1 if backup file cannot be opened or is not provided */ int main(int argc, char *argv[]) { const char *filename = NULL; int argi = 0; /* decode any command line parameters */ filename = filename_remove_path(argv[0]); for (argi = 1; argi < argc; argi++) { if (strcmp(argv[argi], "--help") == 0) { print_usage(filename); print_help(filename); return 0; } if (strcmp(argv[argi], "--version") == 0) { printf("dmbrcap 1.0.0\n"); printf("Copyright (C) 2026 by Steve Karg\n" "This is free software; see the source for copying " "conditions.\n" "There is NO warranty; not even for MERCHANTABILITY or\n" "FITNESS FOR A PARTICULAR PURPOSE.\n"); return 0; } if (!open_backup_file(argv[argi])) { return 1; } } if (!Backup_File_Handle) { print_usage(filename); return 1; } atexit(cleanup); mstimer_init(); filename_create_new(); write_global_header(); while (backup_file_packet() > 0) { Backup_File_Packet_Counter++; } if (Backup_File_Packet_Counter) { fprintf( stdout, "dmbrcap: wrote %u packets\n", (unsigned)Backup_File_Packet_Counter); } return 0; }