BLF_DLL/APP/blf.c

648 lines
20 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @file blf.c
* @brief Реализация функций для создания BLF-файлов (разделяемая библиотека).
*/
#include "blf.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
/* -------------------------------------------------------------------------
* Упакованные структуры BLF (повторяем из оригинального заголовка)
* ------------------------------------------------------------------------- */
#if defined(_MSC_VER)
#pragma pack(push, 1)
#else
#define PACKED __attribute__((packed))
#endif
typedef struct PACKED {
uint16_t year;
uint16_t month;
uint16_t dayOfWeek;
uint16_t day;
uint16_t hour;
uint16_t minute;
uint16_t second;
uint16_t milliseconds;
} SYSTEMTIME_PACKED;
typedef struct PACKED {
uint32_t signature;
uint32_t statisticsSize;
uint32_t apiNumber;
uint8_t applicationId;
uint8_t compressionLevel;
uint8_t applicationMajor;
uint8_t applicationMinor;
uint64_t fileSize;
uint64_t uncompressedFileSize;
uint32_t objectCount;
uint32_t applicationBuild;
SYSTEMTIME_PACKED measurementStartTime;
SYSTEMTIME_PACKED lastObjectTime;
uint64_t restorePointsOffset;
uint32_t reservedFileStatistics[16];
} FileHeader;
typedef struct PACKED {
uint32_t mSignature;
uint16_t mHeaderSize;
uint16_t mHeaderVersion;
uint32_t mObjectSize;
uint32_t mObjectType;
} VBLObjectHeaderBase;
typedef struct PACKED {
VBLObjectHeaderBase mBase;
uint32_t mObjectFlags;
uint16_t mClientIndex;
uint16_t mObjectVersion;
uint64_t mObjectTimeStamp;
} VBLObjectHeader;
typedef struct PACKED {
uint16_t mChannel;
uint8_t mFlags;
uint8_t mDLC;
uint32_t mID;
uint8_t mData[8];
} VBLCANMessage;
typedef struct PACKED {
uint16_t mChannel;
uint8_t mID;
uint8_t mDLC;
uint8_t mData[8];
uint8_t mFSMId;
uint8_t mFSMState;
uint8_t mHeaderTime;
uint8_t mFullTime;
uint16_t mCRC;
uint8_t mDir;
uint8_t mReserved;
} VBLLINMessage;
typedef struct PACKED {
uint16_t mChannel;
uint8_t mID;
uint8_t mDLC;
uint8_t mFSMId;
uint8_t mFSMState;
uint8_t mHeaderTime;
uint8_t mFullTime;
} VBLLINSendError;
typedef struct PACKED {
uint16_t compressionMethod;
uint16_t reserved1;
uint32_t reserved2;
uint32_t uncompressedFileSize;
uint32_t reserved3;
} LogContainerData;
typedef struct PACKED {
VBLObjectHeaderBase base;
LogContainerData data;
} ContainerHeader;
#if defined(_MSC_VER)
#pragma pack(pop)
#else
#undef PACKED
#endif
/* -------------------------------------------------------------------------
* Контекст (полное определение)
* ------------------------------------------------------------------------- */
typedef struct {
FILE* fp;
FileHeader header;
long headerPos;
int objectCount;
uint64_t maxTimestamp;
int in_container;
ContainerHeader container_hdr;
long container_hdr_pos;
uint64_t container_timestamp;
} BLFContext;
/* -------------------------------------------------------------------------
* Вспомогательные функции
* ------------------------------------------------------------------------- */
static int systemtime_to_packed(const SYSTEMTIME* src, SYSTEMTIME_PACKED* dst) {
if (!src || !dst) return -1;
dst->year = src->year;
dst->month = src->month;
dst->dayOfWeek = src->dayOfWeek;
dst->day = src->day;
dst->hour = src->hour;
dst->minute = src->minute;
dst->second = src->second;
dst->milliseconds = src->milliseconds;
return 0;
}
static void systemtime_packed_to_unpacked(const SYSTEMTIME_PACKED* src, SYSTEMTIME* dst) {
dst->year = src->year;
dst->month = src->month;
dst->dayOfWeek = src->dayOfWeek;
dst->day = src->day;
dst->hour = src->hour;
dst->minute = src->minute;
dst->second = src->second;
dst->milliseconds = src->milliseconds;
}
static void systemtime_add_ms(SYSTEMTIME_PACKED* st, uint64_t ms) {
uint64_t total_ms = st->milliseconds + ms;
st->milliseconds = (uint16_t)(total_ms % 1000);
uint64_t carry_seconds = total_ms / 1000;
uint64_t total_seconds = st->second + carry_seconds;
st->second = (uint16_t)(total_seconds % 60);
uint64_t carry_minutes = total_seconds / 60;
uint64_t total_minutes = st->minute + carry_minutes;
st->minute = (uint16_t)(total_minutes % 60);
uint64_t carry_hours = total_minutes / 60;
uint64_t total_hours = st->hour + carry_hours;
st->hour = (uint16_t)(total_hours % 24);
/* Дни не меняем считаем, что запись укладывается в один день */
}
static void get_current_systemtime(SYSTEMTIME* st) {
time_t t = time(NULL);
struct tm* tm = localtime(&t);
st->year = tm->tm_year + 1900;
st->month = tm->tm_mon + 1;
st->dayOfWeek = tm->tm_wday;
st->day = tm->tm_mday;
st->hour = tm->tm_hour;
st->minute = tm->tm_min;
st->second = tm->tm_sec;
st->milliseconds = 0; /* точность до секунды */
}
/* -------------------------------------------------------------------------
* Файловые операции (только stdio)
* ------------------------------------------------------------------------- */
static int blf_file_write(FILE* fp, const void* ptr, size_t size) {
if (fwrite(ptr, 1, size, fp) != size) {
fprintf(stderr, "ERROR: fwrite failed (size=%zu)\n", size);
return -1;
}
return 0;
}
static long blf_file_tell(FILE* fp) {
long pos = ftell(fp);
if (pos < 0) {
fprintf(stderr, "ERROR: ftell failed\n");
}
return pos;
}
static int blf_file_seek(FILE* fp, long offset) {
if (fseek(fp, offset, SEEK_SET) != 0) {
fprintf(stderr, "ERROR: fseek to %ld failed\n", offset);
return -1;
}
return 0;
}
static int blf_file_seek_end(FILE* fp) {
if (fseek(fp, 0, SEEK_END) != 0) {
fprintf(stderr, "ERROR: fseek to end failed\n");
return -1;
}
return 0;
}
/* -------------------------------------------------------------------------
* Реализация API
* ------------------------------------------------------------------------- */
BLF_API void* blf_open(const char* filename, const SYSTEMTIME* startTime) {
BLFContext* ctx = (BLFContext*)calloc(1, sizeof(BLFContext));
if (!ctx) {
fprintf(stderr, "ERROR: Failed to allocate context\n");
return NULL;
}
ctx->fp = fopen(filename, "w+b");
if (!ctx->fp) {
fprintf(stderr, "ERROR: Failed to open file %s\n", filename);
free(ctx);
return NULL;
}
/* Инициализация заголовка */
ctx->header.signature = BL_FILE_SIGNATURE;
ctx->header.statisticsSize = sizeof(FileHeader);
ctx->header.apiNumber = 4070100;
ctx->header.applicationId = 0;
ctx->header.compressionLevel = 0;
ctx->header.applicationMajor = 0;
ctx->header.applicationMinor = 0;
ctx->header.applicationBuild = 0;
ctx->header.fileSize = 0;
ctx->header.uncompressedFileSize = 0;
ctx->header.objectCount = 0;
ctx->header.restorePointsOffset = 0;
/* Время начала */
SYSTEMTIME st;
if (startTime) {
st = *startTime;
} else {
get_current_systemtime(&st);
}
if (systemtime_to_packed(&st, &ctx->header.measurementStartTime) != 0) {
fclose(ctx->fp);
free(ctx);
return NULL;
}
/* lastObjectTime пока равно startTime */
ctx->header.lastObjectTime = ctx->header.measurementStartTime;
/* Запись заголовка */
ctx->headerPos = blf_file_tell(ctx->fp);
if (ctx->headerPos < 0) {
fclose(ctx->fp);
free(ctx);
return NULL;
}
if (blf_file_write(ctx->fp, &ctx->header, sizeof(FileHeader)) != 0) {
fclose(ctx->fp);
free(ctx);
return NULL;
}
ctx->objectCount = 0;
ctx->maxTimestamp = 0;
ctx->in_container = 0;
return ctx;
}
BLF_API int blf_set_start_time(void* handle, const SYSTEMTIME* startTime) {
if (!handle || !startTime) return -1;
BLFContext* ctx = (BLFContext*)handle;
return systemtime_to_packed(startTime, &ctx->header.measurementStartTime);
}
BLF_API int blf_start_container(void* handle, uint64_t timestamp) {
if (!handle) return -1;
BLFContext* ctx = (BLFContext*)handle;
if (ctx->in_container) {
fprintf(stderr, "ERROR: Already inside a container\n");
return -1;
}
/* Подготовка заголовка контейнера */
ctx->container_hdr.base.mSignature = BL_OBJ_SIGNATURE;
ctx->container_hdr.base.mHeaderSize = sizeof(VBLObjectHeaderBase);
ctx->container_hdr.base.mHeaderVersion = 1;
ctx->container_hdr.base.mObjectSize = 0; /* будет заполнено позже */
ctx->container_hdr.base.mObjectType = BL_OBJ_TYPE_LOG_CONTAINER;
memset(&ctx->container_hdr.data, 0, sizeof(LogContainerData));
ctx->container_hdr.data.compressionMethod = 0;
/* Запись заголовка контейнера */
ctx->container_hdr_pos = blf_file_tell(ctx->fp);
if (ctx->container_hdr_pos < 0) return -1;
if (blf_file_write(ctx->fp, &ctx->container_hdr, sizeof(ContainerHeader)) != 0) {
fprintf(stderr, "ERROR: Failed to write container header\n");
return -1;
}
ctx->container_timestamp = timestamp;
ctx->in_container = 1;
return 0;
}
BLF_API int blf_end_container(void* handle) {
if (!handle) return -1;
BLFContext* ctx = (BLFContext*)handle;
if (!ctx->in_container) {
fprintf(stderr, "ERROR: No open container to end\n");
return -1;
}
long current_pos = blf_file_tell(ctx->fp);
if (current_pos < 0) return -1;
long data_size = current_pos - (ctx->container_hdr_pos + sizeof(ContainerHeader));
if (data_size < 0) {
fprintf(stderr, "ERROR: Negative container data size\n");
return -1;
}
ctx->container_hdr.base.mObjectSize = sizeof(ContainerHeader) + data_size;
ctx->container_hdr.data.uncompressedFileSize = (uint32_t)data_size;
if (blf_file_seek(ctx->fp, ctx->container_hdr_pos) != 0) {
fprintf(stderr, "ERROR: Failed to seek to container header\n");
return -1;
}
if (blf_file_write(ctx->fp, &ctx->container_hdr, sizeof(ContainerHeader)) != 0) {
fprintf(stderr, "ERROR: Failed to rewrite container header\n");
return -1;
}
if (blf_file_seek_end(ctx->fp) != 0) {
fprintf(stderr, "ERROR: Failed to seek to end of file after container\n");
return -1;
}
ctx->objectCount++;
if (ctx->container_timestamp > ctx->maxTimestamp)
ctx->maxTimestamp = ctx->container_timestamp;
ctx->in_container = 0;
return 0;
}
BLF_API int blf_add_can_message(void* handle,
uint16_t channel,
uint32_t id,
uint8_t flags,
uint8_t dlc,
const uint8_t* data,
uint64_t timestamp) {
if (!handle) return -1;
BLFContext* ctx = (BLFContext*)handle;
if (dlc > 8) {
fprintf(stderr, "ERROR: CAN message DLC > 8\n");
return -1;
}
VBLObjectHeader objHdr;
VBLCANMessage canMsg;
canMsg.mChannel = channel;
canMsg.mFlags = flags;
canMsg.mDLC = dlc;
canMsg.mID = id;
if (data && dlc <= 8) {
memcpy(canMsg.mData, data, dlc);
if (dlc < 8) memset(canMsg.mData + dlc, 0, 8 - dlc);
} else {
memset(canMsg.mData, 0, 8);
}
objHdr.mBase.mSignature = BL_OBJ_SIGNATURE;
objHdr.mBase.mHeaderSize = sizeof(VBLObjectHeader);
objHdr.mBase.mHeaderVersion = 1;
objHdr.mBase.mObjectSize = sizeof(VBLObjectHeader) + sizeof(VBLCANMessage);
objHdr.mBase.mObjectType = BL_OBJ_TYPE_CAN_MESSAGE;
objHdr.mObjectFlags = BL_OBJ_FLAG_TIME_ONE_NANS;
objHdr.mClientIndex = 0;
objHdr.mObjectVersion = 0;
objHdr.mObjectTimeStamp = timestamp;
if (blf_file_write(ctx->fp, &objHdr, sizeof(objHdr)) != 0) {
fprintf(stderr, "ERROR: Failed to write CAN message header\n");
return -1;
}
if (blf_file_write(ctx->fp, &canMsg, sizeof(canMsg)) != 0) {
fprintf(stderr, "ERROR: Failed to write CAN message body\n");
return -1;
}
if (!ctx->in_container) ctx->objectCount++;
if (timestamp > ctx->maxTimestamp) ctx->maxTimestamp = timestamp;
return 0;
}
BLF_API int blf_add_lin_message_obsolete(void* handle,
uint16_t channel,
uint8_t id,
uint8_t dlc,
const uint8_t* data,
uint8_t dir,
uint64_t timestamp,
uint16_t checksum) {
if (!handle) return -1;
BLFContext* ctx = (BLFContext*)handle;
if (dlc > 8) {
fprintf(stderr, "ERROR: LIN message DLC > 8\n");
return -1;
}
VBLObjectHeader objHdr;
VBLLINMessage linMsg;
memset(&linMsg, 0, sizeof(linMsg));
linMsg.mChannel = channel;
linMsg.mID = id & 0x3F;
linMsg.mDLC = dlc;
if (data && dlc > 0) memcpy(linMsg.mData, data, dlc);
uint8_t header_bits = 34;
uint8_t response_bits = (dlc + 1) * 10;
linMsg.mHeaderTime = header_bits;
linMsg.mFullTime = header_bits + response_bits;
linMsg.mCRC = checksum;
linMsg.mDir = dir;
linMsg.mReserved = 0;
objHdr.mBase.mSignature = BL_OBJ_SIGNATURE;
objHdr.mBase.mHeaderSize = sizeof(VBLObjectHeader);
objHdr.mBase.mHeaderVersion = 1;
objHdr.mBase.mObjectSize = sizeof(VBLObjectHeader) + sizeof(VBLLINMessage);
objHdr.mBase.mObjectType = BL_OBJ_TYPE_LIN_MESSAGE;
objHdr.mObjectFlags = BL_OBJ_FLAG_TIME_ONE_NANS;
objHdr.mClientIndex = 0;
objHdr.mObjectVersion = 0;
objHdr.mObjectTimeStamp = timestamp;
if (blf_file_write(ctx->fp, &objHdr, sizeof(objHdr)) != 0) {
fprintf(stderr, "ERROR: Failed to write LIN message header\n");
return -1;
}
if (blf_file_write(ctx->fp, &linMsg, sizeof(linMsg)) != 0) {
fprintf(stderr, "ERROR: Failed to write LIN message body\n");
return -1;
}
if (!ctx->in_container) ctx->objectCount++;
if (timestamp > ctx->maxTimestamp) ctx->maxTimestamp = timestamp;
return 0;
}
BLF_API int blf_add_lin_send_error(void* handle,
uint16_t channel,
uint8_t id,
uint8_t dlc,
uint64_t timestamp) {
if (!handle) return -1;
BLFContext* ctx = (BLFContext*)handle;
VBLObjectHeader objHdr;
VBLLINSendError sendErr;
memset(&sendErr, 0, sizeof(sendErr));
sendErr.mChannel = channel;
sendErr.mID = id & 0x3F;
sendErr.mDLC = dlc;
sendErr.mHeaderTime = 34;
sendErr.mFullTime = 34;
objHdr.mBase.mSignature = BL_OBJ_SIGNATURE;
objHdr.mBase.mHeaderSize = sizeof(VBLObjectHeader);
objHdr.mBase.mHeaderVersion = 1;
objHdr.mBase.mObjectSize = sizeof(VBLObjectHeader) + sizeof(VBLLINSendError);
objHdr.mBase.mObjectType = BL_OBJ_TYPE_LIN_SND_ERROR;
objHdr.mObjectFlags = BL_OBJ_FLAG_TIME_ONE_NANS;
objHdr.mClientIndex = 0;
objHdr.mObjectVersion = 0;
objHdr.mObjectTimeStamp = timestamp;
if (blf_file_write(ctx->fp, &objHdr, sizeof(objHdr)) != 0) {
fprintf(stderr, "ERROR: Failed to write LIN send error header\n");
return -1;
}
if (blf_file_write(ctx->fp, &sendErr, sizeof(sendErr)) != 0) {
fprintf(stderr, "ERROR: Failed to write LIN send error body\n");
return -1;
}
if (!ctx->in_container) ctx->objectCount++;
if (timestamp > ctx->maxTimestamp) ctx->maxTimestamp = timestamp;
return 0;
}
BLF_API int blf_add_env_data(void* handle,
const char* name,
const uint8_t* data,
uint32_t data_len,
uint64_t timestamp) {
if (!handle) return -1;
BLFContext* ctx = (BLFContext*)handle;
if (data == NULL && data_len > 0) {
fprintf(stderr, "ERROR: blf_add_env_data: data is NULL but data_len > 0\n");
return -1;
}
uint32_t name_len = (name != NULL) ? (uint32_t)strlen(name) : 0;
uint32_t object_size = sizeof(VBLObjectHeader) + sizeof(uint32_t)*2 + sizeof(uint64_t) + name_len + data_len;
VBLObjectHeader objHdr;
objHdr.mBase.mSignature = BL_OBJ_SIGNATURE;
objHdr.mBase.mHeaderSize = sizeof(VBLObjectHeader);
objHdr.mBase.mHeaderVersion = 1;
objHdr.mBase.mObjectSize = object_size;
objHdr.mBase.mObjectType = BL_OBJ_TYPE_ENV_DATA;
objHdr.mObjectFlags = BL_OBJ_FLAG_TIME_ONE_NANS;
objHdr.mClientIndex = 0;
objHdr.mObjectVersion = 0;
objHdr.mObjectTimeStamp = timestamp;
if (blf_file_write(ctx->fp, &objHdr, sizeof(objHdr)) != 0) {
fprintf(stderr, "ERROR: Failed to write ENV_DATA header\n");
return -1;
}
uint32_t nameLength = name_len;
uint32_t dataLength = data_len;
uint64_t reserved = 0;
if (blf_file_write(ctx->fp, &nameLength, sizeof(nameLength)) != 0 ||
blf_file_write(ctx->fp, &dataLength, sizeof(dataLength)) != 0 ||
blf_file_write(ctx->fp, &reserved, sizeof(reserved)) != 0) {
fprintf(stderr, "ERROR: Failed to write ENV_DATA fields\n");
return -1;
}
if (name_len > 0 && name != NULL) {
if (blf_file_write(ctx->fp, name, name_len) != 0) {
fprintf(stderr, "ERROR: Failed to write ENV_DATA name\n");
return -1;
}
}
if (data_len > 0 && data != NULL) {
if (blf_file_write(ctx->fp, data, data_len) != 0) {
fprintf(stderr, "ERROR: Failed to write ENV_DATA data\n");
return -1;
}
}
/* Выравнивание до 4 байт */
long pos = blf_file_tell(ctx->fp);
if (pos < 0) return -1;
long pad = (4 - (pos % 4)) % 4;
for (long i = 0; i < pad; i++) {
uint8_t zero = 0;
if (blf_file_write(ctx->fp, &zero, 1) != 0) {
fprintf(stderr, "ERROR: Failed to write ENV_DATA padding\n");
return -1;
}
}
if (!ctx->in_container) ctx->objectCount++;
if (timestamp > ctx->maxTimestamp) ctx->maxTimestamp = timestamp;
return 0;
}
BLF_API int blf_close(void* handle) {
if (!handle) return -1;
BLFContext* ctx = (BLFContext*)handle;
/* Закрываем незавершённый контейнер */
if (ctx->in_container) {
if (blf_end_container(ctx) != 0) {
fprintf(stderr, "WARNING: Failed to close remaining container\n");
}
}
/* Выравнивание файла до 4 байт */
long endPos = blf_file_tell(ctx->fp);
if (endPos < 0) {
fclose(ctx->fp);
free(ctx);
return -1;
}
long padding = (4 - (endPos % 4)) % 4;
for (long i = 0; i < padding; i++) {
uint8_t zero = 0;
if (blf_file_write(ctx->fp, &zero, 1) != 0) {
fclose(ctx->fp);
free(ctx);
return -1;
}
}
endPos += padding;
/* Вычисляем время последнего объекта */
SYSTEMTIME_PACKED lastObj = ctx->header.measurementStartTime;
uint64_t total_ms = ctx->maxTimestamp / 1000000;
systemtime_add_ms(&lastObj, total_ms);
ctx->header.lastObjectTime = lastObj;
/* Обновляем поля заголовка */
ctx->header.fileSize = (uint64_t)endPos;
ctx->header.uncompressedFileSize = (uint64_t)endPos;
ctx->header.objectCount = ctx->objectCount;
/* Перезаписываем заголовок */
if (blf_file_seek(ctx->fp, ctx->headerPos) != 0) {
fprintf(stderr, "ERROR: Failed to seek to file header\n");
fclose(ctx->fp);
free(ctx);
return -1;
}
if (blf_file_write(ctx->fp, &ctx->header, sizeof(FileHeader)) != 0) {
fprintf(stderr, "ERROR: Failed to rewrite file header\n");
fclose(ctx->fp);
free(ctx);
return -1;
}
fclose(ctx->fp);
free(ctx);
return 0;
}