Compare commits

..

9 Commits

Author SHA1 Message Date
Mid
e0716abc81 Handle indefinite elements 2025-10-07 12:27:21 +03:00
Mid
8b0fbfdcf7 Create Makefile.. finally 2025-10-07 11:00:39 +03:00
Mid
75c0ac1997 Bugfix 2025-10-07 10:57:38 +03:00
Mid
cca8f02870 Bug in README.md 2025-09-27 20:56:40 +03:00
Mid
d0ccaff719 C++-friendly headers 2025-09-27 20:56:05 +03:00
Mid
75d2efbe4c Bug fixes 2025-03-30 23:20:47 +03:00
Mid
629a8fdf55 Forgot something 2025-01-04 10:53:47 +02:00
Mid
fb97037243 Writer example 2025-01-04 10:45:25 +02:00
Mid
1476dbdd57 Complete writer interface 2025-01-04 10:44:33 +02:00
7 changed files with 159 additions and 22 deletions

7
Makefile Normal file
View File

@@ -0,0 +1,7 @@
libeebie.so:
$(CC) -shared -g -O2 -o libeebie.so eebie/reader.c eebie/writer.c
install:
install -d /usr/local/include/eebie
install eebie/*.h /usr/local/include/eebie
install libeebie.so /usr/local/lib

View File

@@ -4,6 +4,8 @@ Eebie is a very small C99 library for reading and writing EBML files, namely Mat
The library is meant to be embedded directly into another project.
TODO: signed integer writing
## Reading example
#include"eebie/reader.h"
@@ -39,7 +41,7 @@ The library is meant to be embedded directly into another project.
}
static void on_exit_element(EBMLReader *reader) {
// If needed, the current element ID can be found at `reader->inside.id`.
// If needed, the exitee element ID can be found at `reader->idStack[reader->currentDepth - 1]`.
// Use accumulated buffer here.
@@ -97,7 +99,45 @@ The library is meant to be embedded directly into another project.
## Writing example
TBA
#include"eebie/writer.h"
#include<stdio.h>
#include<stdlib.h>
static void *on_alloc(EBMLWriter *w, void *p, size_t sz) {
return realloc(p, sz);
}
static size_t on_write(EBMLWriter *w, const void *p, size_t sz) {
FILE *fp = w->ud;
return fwrite(p, 1, sz, fp);
}
int main() {
EBMLWriter w;
ebml_writer_init(&w);
FILE *output = fopen("test.ebml", "wb");
w.ud = output;
w.eventWrite = on_write;
w.eventAlloc = on_alloc;
ebml_writer_push(&w, 0x1A45DFA3);
ebml_writer_put(&w, 0x4286, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = 1});
ebml_writer_put(&w, 0x4282, EBML_STRING, (EBMLPrimitive) {.string = "excrement"});
ebml_writer_pop(&w);
// Unknown-sized element must not have corresponding pop
ebml_writer_push_ind(&w, 0x18538067);
ebml_writer_put(&w, 0x4123, EBML_STRING, (EBMLPrimitive) {.string = "yodel"});
while(!ebml_writer_flush(&w));
fclose(output);
}
## `schemagen`

View File

@@ -2,6 +2,7 @@
#define EEBIE_H
#include<stdint.h>
#include<stddef.h>
typedef enum EBMLElementType {
EBML_SIGNED_INTEGER,
@@ -15,13 +16,18 @@ typedef enum EBMLElementType {
EBML_BINARY,
} EBMLElementType;
typedef struct EBMLBinary {
uint8_t *ptr;
size_t length;
} EBMLBinary;
typedef union EBMLPrimitive {
int64_t sInt;
uint64_t uInt;
float flt4;
double flt8;
const char *string;
const uint8_t *binary;
const EBMLBinary binary;
uint64_t date;
} EBMLPrimitive;

View File

@@ -9,6 +9,8 @@
#include"reader.h"
#define is_max(u) ((u) == 0xFFFFFFFFFFFFFFFFUL)
void ebml_reader_init(EBMLReader *this) {
memset(this, 0, sizeof(*this));
this->state = EBMLRS_WAITING_FOR_ELEMENT_ID;
@@ -89,6 +91,10 @@ static int get_varint(const uint8_t *data, size_t length, uint64_t *result) {
if(ret >= 0) {
*result &= ~VARINT_MASKS[ret];
if(*result == (1UL << (ret * 7)) - 1) {
*result = 0xFFFFFFFFFFFFFFFFUL;
}
}
return ret;
@@ -137,7 +143,7 @@ int ebml_reader_feed(EBMLReader *this, const uint8_t *data, size_t length) {
}
this->idStack[this->currentDepth] = this->inside.id;
this->stack[this->currentDepth] = elLength + status;
this->stack[this->currentDepth] = is_max(elLength) ? elLength : elLength + status;
this->currentDepth++;
@@ -156,7 +162,9 @@ int ebml_reader_feed(EBMLReader *this, const uint8_t *data, size_t length) {
}
for(int i = 0; i < this->currentDepth; i++) {
this->stack[i] -= eaten;
if(!is_max(this->stack[i])) {
this->stack[i] -= eaten;
}
}
while(this->currentDepth > 0 && this->stack[this->currentDepth - 1] == 0) {
@@ -173,3 +181,12 @@ int ebml_reader_feed(EBMLReader *this, const uint8_t *data, size_t length) {
return eaten;
}
void ebml_reader_leave(EBMLReader *this, uint64_t id) {
for(int i = 0; i < this->currentDepth; i++) {
if(this->idStack[i] == id) {
this->currentDepth = i;
return;
}
}
}

View File

@@ -5,6 +5,10 @@
#include"ebml.h"
#ifdef __cplusplus
extern "C" {
#endif
#include<stddef.h>
#include<stdint.h>
@@ -42,7 +46,16 @@ typedef struct EBMLReader {
void *ud;
} EBMLReader;
void ebml_reader_init(EBMLReader *this);
int ebml_reader_feed(EBMLReader *this, const uint8_t *data, size_t length);
void ebml_reader_init(EBMLReader*);
int ebml_reader_feed(EBMLReader*, const uint8_t *data, size_t length);
// Because Eebie is schemaless, it must be told when we have left an
// indefinite element, otherwise the stack may grow indefinitely and
// even overflow. The user may call this from eventEnterElement.
void ebml_reader_leave(EBMLReader*, uint64_t id);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -2,9 +2,10 @@
#include<string.h>
#include<endian.h>
#include<stdlib.h>
#include<stddef.h>
#include<assert.h>
static uint64_t VARINT_MASKS[] = {0, 0x80L, 0xC000L, 0xE00000L, 0xF0000000L, 0xF800000000L, 0xFC0000000000L, 0xFE000000000000L, 0L};
static uint64_t VARINT_MASKS[] = {0x0UL, 0x80UL, 0x4000UL, 0x200000UL, 0x10000000UL, 0x0800000000UL, 0x040000000000UL, 0x02000000000000UL, 0x0100000000000000UL};
static int byte_length(uint64_t i) {
if(i == 0) return 1;
@@ -14,6 +15,7 @@ static int byte_length(uint64_t i) {
i >>= 8;
n++;
}
return n;
}
@@ -30,7 +32,8 @@ static int bit_length(uint64_t i) {
}
static uint64_t encode_int(uint64_t i) {
return i | VARINT_MASKS[(bit_length(i) + 6) / 7];
// bit_length(i + 1) over bit_length(i) because 0xFF... lengths are interpreted as indefinite
return i | VARINT_MASKS[(bit_length(i + 1) + 6) / 7];
}
static void advance(EBMLWriter *this, uint64_t amount) {
@@ -41,7 +44,7 @@ static void advance(EBMLWriter *this, uint64_t amount) {
static void add(EBMLWriter *this, const void *data, size_t length) {
if(this->bufferLen + length > this->bufferCapacity) {
this->buffer = realloc(this->buffer, (this->bufferLen + length + 1023) & ~1023);
this->buffer = this->eventAlloc(this, this->buffer, this->bufferCapacity = ((this->bufferLen + length + 1023) & ~1023));
}
memcpy(this->buffer + this->bufferLen, data, length);
@@ -61,19 +64,47 @@ static void add_varint(EBMLWriter *this, uint64_t i) {
void ebml_writer_init(EBMLWriter *this) {
memset(this, 0, sizeof(EBMLWriter));
}
this->buffer = calloc(this->bufferCapacity = 1024, 1);
void ebml_writer_free(EBMLWriter *this) {
this->eventAlloc(this, this->buffer, 0);
}
void ebml_writer_push_ind(EBMLWriter *this, uint64_t id) {
add_varint(this, id);
add(this, &(uint64_t) {htobe64(0x01FFFFFFFFFFFFFFUL)}, sizeof(uint64_t)); // UNKNOWN-SIZED ELEMENT
while(!ebml_writer_flush(this));
}
void ebml_writer_push(EBMLWriter *this, uint64_t id) {
add_varint(this, id);
add(this, &(uint64_t) {htobe64(0x0100000000000000L)}, sizeof(uint64_t)); // LENGTH OF MAX SIZE (BECAUSE UNKNOWN)
add(this, &(uint64_t) {htobe64(0x0100000000000000UL)}, sizeof(uint64_t)); // LENGTH OF MAX SIZE (BECAUSE UNKNOWN)
this->stack[this->currentDepth++] = 0;
}
void ebml_writer_pop(EBMLWriter *this) {
this->currentDepth--;
uint64_t newSize = this->stack[this->currentDepth];
uint64_t *s = (uint64_t*) &this->buffer[this->bufferLen - newSize - 8];
*s = htobe64(0x0100000000000000UL | newSize);
if(this->currentDepth == 0) {
while(!ebml_writer_flush(this));
}
}
bool ebml_writer_flush(EBMLWriter *this) {
size_t written = this->eventWrite(this, this->buffer, this->bufferLen);
memmove(this->buffer, this->buffer + written, this->bufferLen - written);
this->bufferLen -= written;
return this->bufferLen == 0;
}
void ebml_writer_put(EBMLWriter *this, uint64_t id, EBMLElementType type, EBMLPrimitive primitive) {
@@ -89,9 +120,11 @@ void ebml_writer_put(EBMLWriter *this, uint64_t id, EBMLElementType type, EBMLPr
len = 8;
} else if(type == EBML_STRING) {
len = strlen(primitive.string);
} else if(type == EBML_BINARY) {
len = primitive.binary.length;
} else if(type == EBML_DATE) {
len = 8;
} else abort();
} else assert(0);
add_varint(this, encode_int(len));
@@ -100,13 +133,17 @@ void ebml_writer_put(EBMLWriter *this, uint64_t id, EBMLElementType type, EBMLPr
add(this, (uint8_t*) &primitive.uInt + 8 - len, len);
} else if(type == EBML_FLOAT4) {
uint32_t fl = *(uint32_t*) &primitive.flt4;
fl = htobe32(fl);
add(this, &fl, 4);
} else if(type == EBML_FLOAT8) {
uint64_t fl = *(uint64_t*) &primitive.flt8;
fl = htobe64(fl);
add(this, &fl, 8);
} else if(type == EBML_STRING) {
add(this, primitive.string, len);
} else if(type == EBML_BINARY) {
add(this, primitive.binary.ptr, primitive.binary.length);
} else if(type == EBML_DATE) {
add(this, &primitive.date, 8);
} else abort();
} else assert(0);
}

View File

@@ -4,16 +4,25 @@
#include"ebml.h"
#include<stddef.h>
#include<stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#define EBML_WRITE_MAXIMUM_DEPTH 128
typedef size_t(*EBMLWriteCallback)(void *ud, const void *data, size_t length);
struct EBMLWriter;
typedef struct {
typedef size_t(*EBMLEventWrite)(struct EBMLWriter*, const void *data, size_t length);
typedef void*(*EBMLEventAlloc)(struct EBMLWriter*, void *data, size_t length);
typedef struct EBMLWriter {
int currentDepth;
uint64_t stack[EBML_WRITE_MAXIMUM_DEPTH];
EBMLWriteCallback callbackWrite;
EBMLEventWrite eventWrite;
EBMLEventAlloc eventAlloc;
uint8_t *buffer;
size_t bufferLen;
@@ -22,11 +31,19 @@ typedef struct {
void *ud;
} EBMLWriter;
void ebml_writer_init(EBMLWriter *this);
void ebml_writer_init(EBMLWriter*);
void ebml_writer_free(EBMLWriter*);
void ebml_writer_push(EBMLWriter *this, uint64_t id);
void ebml_writer_pop(EBMLWriter *this);
void ebml_writer_push_ind(EBMLWriter*, uint64_t id);
void ebml_writer_push(EBMLWriter*, uint64_t id);
void ebml_writer_pop(EBMLWriter*);
void ebml_writer_put(EBMLWriter *this, uint64_t id, EBMLElementType type, EBMLPrimitive primitive);
bool ebml_writer_flush(EBMLWriter*);
void ebml_writer_put(EBMLWriter*, uint64_t id, EBMLElementType type, EBMLPrimitive primitive);
#ifdef __cplusplus
}
#endif
#endif