tlv
#
A small C23 library for serializing and deserializing typed values over sockets using the Type-Length-Value wire format.
Each message looks like this:
packet title TLV Message +1: "Type" +4: "Length" +8: "Value (could be more than 8 bytes)"
Multiple frames can be concatenated into a single stream and sent in one write().
Supported types#
| Type tag | C type | Description |
|---|---|---|
T_U08 |
uint8_t |
Unsigned 8-bit integer |
T_U16 |
uint16_t |
Unsigned 16-bit integer |
T_U32 |
uint32_t |
Unsigned 32-bit integer |
T_U64 |
uint64_t |
Unsigned 64-bit integer |
T_S08 |
int8_t |
Signed 8-bit integer |
T_S16 |
int16_t |
Signed 16-bit integer |
T_S32 |
int32_t |
Signed 32-bit integer |
T_S64 |
int64_t |
Signed 64-bit integer |
T_F32 |
float |
IEEE-754 32-bit float |
T_F64 |
double |
IEEE-754 64-bit float |
T_BOL |
bool |
Boolean |
T_STR |
String |
UTF-8 string (not null-terminated) |
T_BIN |
Buffer |
Raw binary data |
T_ERR |
ErrString |
UTF-8 error string |
T_TIM |
Timestamp |
Unix timestamp (int64_t) |
T_CHR |
Codepoint |
Unicode codepoint (UTF-32, uint32_t) |
Usage#
Sending#
Steps:
- serialize values
- concatenate them into a stream
- write to socket
#include "tlv.h"
Buffer stream = serialize((uint32_t)file_size);
stream = buf_append(stream, serialize_cstr(filename));
stream = buf_append(stream, serialize_bin(file_data));
write(fd, stream.bytes, stream.len);
free(stream.bytes);
Receiving#
Steps:
- read from socket
- deserialize
- access by type
Buffer incoming = { .bytes = recv_buf, .len = n };
Message *msgs = deserialize(incoming);
uint32_t size = message_u32(msgs[0]);
char *filename = message_cstr(msgs[1]); // heap-allocated, call free()
Buffer data = message_bin(msgs[2]);
// do your thing
free(filename);
for (size_t i = 0; i < da_length(msgs); i++)
message_free(&msgs[i]);
da_free(msgs);
Build#
Bootstrap once:
cc nob.c -o nob
Then:
./nob # build .build/libtlv.a and .build/libtlv.so
./nob test # compile and run tests (ASan + UBSan enabled)
./nob static # static library only
./nob dynamic # dynamic library only
./nob docs # generate HTML docs in .build/docs/html/index.html
./nob valgrind # run tests under Valgrind
./nob clean # remove .build/
The nob binary recompiles itself automatically when nob.c changes.
Ownership rules#
Remember this !!!
serialize*()andbuf_append()return an ownedBuffer- call
free(buf.bytes)when done.
- call
deserialize()returns an ownedMessage*array- call
message_free()on each element, thenda_free().
- call
message_cstr()returns a heap-allocatedchar*- call
free()when done.
- call