blob: 72ecad676f244916dfbc004f2df3ef8dda6e5db9 [file] [log] [blame]
/*
* BRLTTY - A background process providing access to the console screen (when in
* text mode) for a blind person using a refreshable braille display.
*
* Copyright (C) 1995-2023 by The BRLTTY Developers.
*
* BRLTTY comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed under the terms of the
* GNU Lesser General Public License, as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any
* later version. Please see the file LICENSE-LGPL for details.
*
* Web Page: http://brltty.app/
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "log.h"
#include "strfmt.h"
#include "ihex.h"
#include "file.h"
#include "datafile.h"
#define IHEX_PARSE_VERIFY 0
#define IHEX_RECORD_PREFIX ':'
#define IHEX_COMMENT_PREFIX '#'
#define IHEX_BYTE_WIDTH 8
#define IHEX_BYTE_MASK ((1 << IHEX_BYTE_WIDTH) - 1)
static size_t
ihexByteCount (size_t count) {
return 1 // the number of data bytes
+ 2 // the starting address
+ 1 // the record type
+ count // the data
+ 1 // the checksum
;
}
size_t
ihexRecordLength (size_t count) {
return 1 // the colon prefix
+ (ihexByteCount(count) * 2) // hexadecimal digit pairs
;
}
int
ihexMakeRecord (char *buffer, size_t size, IhexType type, IhexAddress address, const IhexByte *data, IhexCount count) {
IhexByte bytes[ihexByteCount(count)];
IhexByte *end = bytes;
*end++ = count;
*end++ = (address >> IHEX_BYTE_WIDTH) & IHEX_BYTE_MASK;
*end++ = address & IHEX_BYTE_MASK;
*end++ = type;
if (count > 0) end = mempcpy(end, data, count);
{
uint32_t checksum = 0;
{
const IhexByte *byte = bytes;
while (byte < end) checksum += *byte++;
}
checksum ^= IHEX_BYTE_MASK;
checksum += 1;
*end++ = checksum & IHEX_BYTE_MASK;
}
if ((1 + (end - bytes) + 1) > size) return 0;
STR_BEGIN(buffer, size);
STR_PRINTF("%c", IHEX_RECORD_PREFIX);
{
const IhexByte *byte = bytes;
while (byte < end) STR_PRINTF("%02X", *byte++);
}
STR_END;
return 1;
}
int
ihexMakeDataRecord (char *buffer, size_t size, IhexAddress address, const IhexByte *data, IhexCount count) {
return ihexMakeRecord(buffer, size, IHEX_TYPE_DATA, address, data, count);
}
int
ihexMakeEndRecord (char *buffer, size_t size) {
return ihexMakeRecord(buffer, size, IHEX_TYPE_END, 0, NULL, 0);
}
typedef struct {
const char *record;
const char *source;
unsigned int line;
unsigned char error:1;
} IhexRecordProcessingData;
typedef struct {
IhexRecordProcessingData rpd;
IhexRecordHandler *handler;
void *data;
} IhexFileProcessingData;
static void
ihexReportProblem (IhexRecordProcessingData *rpd, const char *message) {
rpd->error = 1;
logMessage(LOG_ERR,
"ihex error: %s: %s[%u]: %s",
message, rpd->source, rpd->line, rpd->record
);
}
static int
ihexCheckDigit (IhexRecordProcessingData *rpd, unsigned char *value, char digit) {
typedef struct {
char first;
char last;
char offset;
} Range;
static const Range ranges[] = {
{ .first='0', .last='9', .offset= 0 },
{ .first='A', .last='F', .offset=10 },
{ .first='a', .last='f', .offset=10 },
};
const Range *range = ranges;
const Range *end = range + ARRAY_COUNT(ranges);
while (range < end) {
if ((digit >= range->first) && (digit <= range->last)) {
*value = (digit - range->first) + range->offset;
return 1;
}
range += 1;
}
ihexReportProblem(rpd, "invalid hexadecimal digit");
return 0;
}
static IhexParsedRecord *
ihexParseRecord (IhexRecordProcessingData *rpd) {
const char *character = rpd->record;
if (!*character || (*character != IHEX_RECORD_PREFIX)) {
ihexReportProblem(rpd, "not an ihex record");
return NULL;
}
size_t length = strlen(++character);
IhexByte bytes[length + 1]; // +1 in case length is 0
IhexByte *end = bytes;
int first = 1;
while (*character) {
unsigned char value;
if (!ihexCheckDigit(rpd, &value, *character)) return NULL;
if (first) {
*end = value << 4;
} else {
*end++ |= value;
}
first = !first;
character += 1;
}
if (!first) {
ihexReportProblem(rpd, "missing hexadecimal digit");
return NULL;
}
{
uint32_t checksum = 0;
const IhexByte *byte = bytes;
while (byte < end) checksum += *byte++;
checksum &= IHEX_BYTE_MASK;
if (checksum) {
ihexReportProblem(rpd, "checksum mismatch");
return NULL;
}
}
const IhexByte *byte = bytes;
size_t actualCount = end - byte;
{
static const char *const messages[] = {
[0] = "missing data byte count",
[1] = "missing address",
[2] = "incomplete address",
[3] = "missing record type",
};
if (actualCount < ARRAY_COUNT(messages)) {
const char *message = messages[actualCount];
if (!message) message = "unknown error";
ihexReportProblem(rpd, message);
return NULL;
}
}
IhexCount count = *byte++;
size_t expectCount = ihexByteCount(count);
if (actualCount < expectCount) {
ihexReportProblem(rpd, "truncated data");
return NULL;
}
if (actualCount > expectCount) {
ihexReportProblem(rpd, "excessive data");
return NULL;
}
IhexParsedRecord *record;
size_t size = sizeof(*record) + count;
record = malloc(size);
if (!record) {
logMallocError();
return NULL;
}
memset(record, 0, size);
record->count = count;
record->address = *byte++ << IHEX_BYTE_WIDTH;
record->address |= *byte++;
record->type = *byte++;
memcpy(record->data, byte, count);
if (IHEX_PARSE_VERIFY) {
const char *expect = rpd->record;
char actual[ihexRecordLength(record->count) + 1];
ihexMakeRecord(
actual, sizeof(actual),
record->type, record->address,
record->data, record->count
);
if (strcmp(actual, expect) != 0) {
ihexReportProblem(rpd, "ihex parse mismatch");
logMessage(LOG_DEBUG, "expect: %s", expect);
logMessage(LOG_DEBUG, "actual: %s", actual);
free(record);
return NULL;
}
}
return record;
}
static int
ihexCallHandler (IhexFileProcessingData *fpd, const IhexParsedRecord *record) {
IhexRecordProcessingData *rpd = &fpd->rpd;
switch (record->type) {
case IHEX_TYPE_DATA:
if (!record->count) return 0;
break;
case IHEX_TYPE_END:
return 0;
default:
ihexReportProblem(rpd, "unsupported record type");
return 0;
}
if (!fpd->handler(record, fpd->data)) {
ihexReportProblem(rpd, "record handler failed");
return 0;
}
return 1;
}
static int
ihexProcessLine (const LineHandlerParameters *parameters) {
IhexFileProcessingData *fpd = parameters->data;
IhexRecordProcessingData *rpd = &fpd->rpd;
rpd->line += 1;
const char *line = parameters->line.text;
while (*line == ' ') line += 1;
if (!*line) return 1;
if (*line == IHEX_COMMENT_PREFIX) return 1;
rpd->record = line;
IhexParsedRecord *record = ihexParseRecord(rpd);
int ok = 0;
if (record) {
if (ihexCallHandler(fpd, record)) {
ok = 1;
}
free(record);
}
return ok;
}
int
ihexProcessFile (const char *path, IhexRecordHandler *handler, void *data) {
IhexFileProcessingData fpd = {
.rpd = {
.source = path,
.line = 0
},
.handler = handler,
.data = data
};
int ok = 0;
FILE *file = openDataFile(path, "r", 0);
if (file) {
if (processLines(file, ihexProcessLine, &fpd)) {
if (!fpd.rpd.error) {
ok = 1;
}
}
fclose(file);
} else if (errno == ENOENT) {
char *url = makePath(PACKAGE_URL, IHEX_FILES_SUBDIRECTORY);
if (url) {
logMessage(LOG_WARNING, "missing firmware blobs can be downloaded from %s", url);
free(url);
}
}
return ok;
}
char *
ihexEnsureExtension (const char *path) {
return ensureFileExtension(path, IHEX_FILE_EXTENSION);
}
char *
ihexMakePath (const char *directory, const char *name) {
char *subdirectory = makePath(directory, IHEX_FILES_SUBDIRECTORY);
if (subdirectory) {
char *file = makeFilePath(subdirectory, name, IHEX_FILE_EXTENSION);
free(subdirectory);
if (file) return file;
}
return NULL;
}