Files
Steve Karg 1d1cf9bfac Feature/create object initial value backup (#1223)
* Added example Device object and property value backup using create-object list-of-initial-values and writable properties. Initiated using ReinitializeDevice with STARTBACKUP command. Added backup and restore required properties to the Device object.

* Fixed fwrite parameters in bacfile_posix_write_stream_data to return the correct number of bytes written.

* Added Device Management-Backup and Restore-B (DM-BR-B) conversion to Wireshark PCAP format command line utility in apps/dmbrcap which decodes and provides a view of the binary backup file.
2026-02-06 16:54:10 -06:00

357 lines
12 KiB
C

/**
* @file
* @brief Device Management-Backup and Restore tool that generates
* a Wireshark PCAP format file from a CreateObject services encoded file.
* @author Steve Karg <skarg@users.sourceforge.net>
* @date February 2026
* @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
*/
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
/* 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>", 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 <filename>\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;
}