/** * @file blf.c * @brief Реализация функций для создания BLF-файлов (разделяемая библиотека). */ #include "blf.h" #include #include #include #include /* ------------------------------------------------------------------------- * Упакованные структуры 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; }