A C library that implements the Type-Length-Value wire format.
1

Configure Feed

Select the types of activity you want to include in your feed.

C 99.3%
Nix 0.7%
Shell 0.1%
Other 0.1%
11 1 0

Clone this repository

https://tangled.org/stau.space/tlv https://tangled.org/did:plc:d244ywxr3ba6gck2iesd6fjn
git@tangled.org:stau.space/tlv git@tangled.org:did:plc:d244ywxr3ba6gck2iesd6fjn

For self-hosted knots, clone URLs may differ based on your setup.



README.md

tlv CI#

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:

  1. serialize values
  2. concatenate them into a stream
  3. 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:

  1. read from socket
  2. deserialize
  3. 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*() and buf_append() return an owned Buffer
    • call free(buf.bytes) when done.
  • deserialize() returns an owned Message* array
    • call message_free() on each element, then da_free().
  • message_cstr() returns a heap-allocated char*
    • call free() when done.