Last active
September 5, 2025 13:25
-
-
Save peterhellberg/65346cabe20318a61fc4e6cb817fe342 to your computer and use it in GitHub Desktop.
Tiny example in Go of using CBOR tags to distinguish between two different types of messages using github.com/fxamacker/cbor/v2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"log" | |
"github.com/fxamacker/cbor/v2" | |
) | |
const ( | |
tagChatMessage = 10001 | |
tagChatPresence = 10002 | |
) | |
type ChatEvent interface { | |
ChatEventType() string | |
} | |
type chatMessage struct { | |
User string `cbor:"user"` | |
Message string `cbor:"message"` | |
} | |
func (c chatMessage) ChatEventType() string { | |
return "chatMessage" | |
} | |
type chatPresence struct { | |
User string `cbor:"user"` | |
Status string `cbor:"status"` | |
} | |
func (c chatPresence) ChatEventType() string { | |
return "chatPresence" | |
} | |
func marshal(tagNum uint64, v any) ([]byte, error) { | |
payloadBytes, err := cbor.Marshal(v) | |
if err != nil { | |
return nil, err | |
} | |
return cbor.Marshal(cbor.RawTag{ | |
Number: tagNum, | |
Content: payloadBytes, | |
}) | |
} | |
func unmarshal(data []byte) (ChatEvent, error) { | |
var tag cbor.RawTag | |
if err := cbor.Unmarshal(data, &tag); err != nil { | |
return nil, fmt.Errorf("failed to unmarshal tag: %w", err) | |
} | |
switch tag.Number { | |
case tagChatMessage: | |
var msg chatMessage | |
return msg, cbor.Unmarshal(tag.Content, &msg) | |
case tagChatPresence: | |
var pres chatPresence | |
return pres, cbor.Unmarshal(tag.Content, &pres) | |
default: | |
return nil, fmt.Errorf("unknown tag: %d", tag.Number) | |
} | |
} | |
func main() { | |
var ( | |
msg = chatMessage{User: "alice", Message: "hello!"} | |
pres = chatPresence{User: "bob", Status: "away"} | |
) | |
msgBytes, err := marshal(tagChatMessage, msg) | |
if err != nil { | |
log.Fatal(err) | |
} | |
presBytes, err := marshal(tagChatPresence, pres) | |
if err != nil { | |
log.Fatal(err) | |
} | |
for _, data := range [][]byte{ | |
msgBytes, | |
presBytes, | |
} { | |
event, err := unmarshal(data) | |
if err != nil { | |
log.Fatal(err) | |
} else { | |
fmt.Printf("Decoded data: %#v\n", event) | |
} | |
switch e := event.(type) { | |
case chatMessage: | |
fmt.Println("Message from:", e.User) | |
case chatPresence: | |
fmt.Println("Presence update:", e.User, e.Status) | |
} | |
} | |
} |
You could encode the messages wrapped in a self describing CBOR tag:
func marshal(tagNum uint64, v any) ([]byte, error) {
return cbor.Marshal(cbor.Tag{
Number: tagSelfDescribe,
Content: cbor.Tag{
Number: tagNum,
Content: v,
},
})
}
Where tagSelfDescribe = 55799
https://www.rfc-editor.org/rfc/rfc8949.html#name-self-described-cbor
package main
import (
"fmt"
"log"
"github.com/fxamacker/cbor/v2"
)
const (
tagChatMessage uint64 = 10001
tagChatPresence = 10002
tagSelfDescribe = 55799
)
type ChatEvent interface {
ChatEventType() string
}
type chatMessage struct {
User string `cbor:"user"`
Message string `cbor:"message"`
}
func (c chatMessage) ChatEventType() string {
return "chatMessage"
}
type chatPresence struct {
User string `cbor:"user"`
Status string `cbor:"status"`
}
func (c chatPresence) ChatEventType() string {
return "chatPresence"
}
func marshal(number uint64, content any) ([]byte, error) {
return cbor.Marshal(cbor.Tag{
Number: tagSelfDescribe,
Content: cbor.Tag{
Number: number,
Content: content,
},
})
}
func unmarshal(data []byte) (ChatEvent, error) {
var raw cbor.RawTag
if err := cbor.Unmarshal(data, &raw); err != nil {
return nil, fmt.Errorf("failed to unmarshal tag: %w", err)
}
switch raw.Number {
case tagChatMessage:
var msg chatMessage
return msg, cbor.Unmarshal(raw.Content, &msg)
case tagChatPresence:
var pres chatPresence
return pres, cbor.Unmarshal(raw.Content, &pres)
default:
return nil, fmt.Errorf("unknown tag: %d", raw.Number)
}
}
func main() {
var (
msg = chatMessage{User: "alice", Message: "hello!"}
pres = chatPresence{User: "bob", Status: "away"}
)
msgBytes, err := marshal(tagChatMessage, msg)
if err != nil {
log.Fatal(err)
}
presBytes, err := marshal(tagChatPresence, pres)
if err != nil {
log.Fatal(err)
}
for _, data := range [][]byte{
msgBytes,
presBytes,
} {
event, err := unmarshal(data)
if err != nil {
log.Fatal(err)
} else {
fmt.Printf("Decoded data: %#v\n", event)
}
switch e := event.(type) {
case chatMessage:
fmt.Println("Message from:", e.User)
case chatPresence:
fmt.Println("Presence update:", e.User, e.Status)
}
}
}
TinyCBOR version in C (compiled using zig cc
)
Download and unpack tinycbor v0.6.1 into tinycbor-0.6.1/
curl -s -L https://github.com/intel/tinycbor/archive/refs/tags/v0.6.1.tar.gz | tar xvz
c-cbor-tagged-messages.c
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include "cbor.h"
#define TAG_SELF_DESCRIBE 55799
#define TAG_CHAT_MESSAGE 10001
#define TAG_CHAT_PRESENCE 10002
typedef struct {
char user[64];
char message[256];
bool priority;
} ChatMessage;
typedef struct {
char user[64];
char status[64];
int level;
} ChatPresence;
// ---------------- ENCODING ----------------
typedef void (*EncodeContentFn)(CborEncoder *map, void *ctx);
size_t encode_tagged_message(uint8_t *buffer, size_t buf_size, CborTag outer_tag,
CborTag inner_tag, EncodeContentFn encode_fn, void *ctx) {
CborEncoder enc, map;
cbor_encoder_init(&enc, buffer, buf_size, 0);
cbor_encode_tag(&enc, outer_tag);
cbor_encode_tag(&enc, inner_tag);
cbor_encoder_create_map(&enc, &map, CborIndefiniteLength);
encode_fn(&map, ctx);
cbor_encoder_close_container(&enc, &map);
return cbor_encoder_get_buffer_size(&enc, buffer);
}
void encode_chat_message_content(CborEncoder *map, void *ctx) {
ChatMessage *msg = (ChatMessage *)ctx;
cbor_encode_text_stringz(map, "user");
cbor_encode_text_stringz(map, msg->user);
cbor_encode_text_stringz(map, "message");
cbor_encode_text_stringz(map, msg->message);
cbor_encode_text_stringz(map, "priority");
cbor_encode_boolean(map, msg->priority);
}
void encode_chat_presence_content(CborEncoder *map, void *ctx) {
ChatPresence *pres = (ChatPresence *)ctx;
cbor_encode_text_stringz(map, "user");
cbor_encode_text_stringz(map, pres->user);
cbor_encode_text_stringz(map, "status");
cbor_encode_text_stringz(map, pres->status);
cbor_encode_text_stringz(map, "level");
cbor_encode_int(map, pres->level);
}
size_t encode_chat_message(uint8_t *buffer, size_t buf_size, ChatMessage *msg) {
return encode_tagged_message(buffer, buf_size, TAG_SELF_DESCRIBE, TAG_CHAT_MESSAGE,
encode_chat_message_content, msg);
}
size_t encode_chat_presence(uint8_t *buffer, size_t buf_size, ChatPresence *pres) {
return encode_tagged_message(buffer, buf_size, TAG_SELF_DESCRIBE, TAG_CHAT_PRESENCE,
encode_chat_presence_content, pres);
}
// ---------------- DECODING ----------------
typedef void (*MapEntryHandler)(const char *key, CborValue *val, void *ctx);
void decode_map_values(CborValue *map, MapEntryHandler handle_entry, void *ctx) {
for (CborValue item = *map; !cbor_value_at_end(&item);) {
char key[64];
size_t key_len = sizeof(key);
if (cbor_value_copy_text_string(&item, key, &key_len, NULL) != CborNoError) {
printf("Error reading key\n");
return;
}
cbor_value_advance(&item);
handle_entry(key, &item, ctx);
cbor_value_advance(&item);
}
}
void handle_chat_message_entry(const char *key, CborValue *val, void *ctx) {
ChatMessage *msg = (ChatMessage *)ctx;
if (strcmp(key, "user") == 0 && cbor_value_is_text_string(val)) {
size_t len = sizeof(msg->user);
cbor_value_copy_text_string(val, msg->user, &len, NULL);
} else if (strcmp(key, "message") == 0 && cbor_value_is_text_string(val)) {
size_t len = sizeof(msg->message);
cbor_value_copy_text_string(val, msg->message, &len, NULL);
} else if (strcmp(key, "priority") == 0 && cbor_value_is_boolean(val)) {
bool b;
cbor_value_get_boolean(val, &b);
msg->priority = b;
}
}
void handle_chat_presence_entry(const char *key, CborValue *val, void *ctx) {
ChatPresence *pres = (ChatPresence *)ctx;
if (strcmp(key, "user") == 0 && cbor_value_is_text_string(val)) {
size_t len = sizeof(pres->user);
cbor_value_copy_text_string(val, pres->user, &len, NULL);
} else if (strcmp(key, "status") == 0 && cbor_value_is_text_string(val)) {
size_t len = sizeof(pres->status);
cbor_value_copy_text_string(val, pres->status, &len, NULL);
} else if (strcmp(key, "level") == 0 && cbor_value_is_integer(val)) {
int64_t level;
cbor_value_get_int64(val, &level);
pres->level = (int)level;
}
}
void decode_message(uint8_t *data, size_t len) {
CborParser parser;
CborValue value, inner;
CborTag tag;
CborError err;
err = cbor_parser_init(data, len, 0, &parser, &value);
if (err != CborNoError) { printf("Parser init error\n"); return; }
if (!cbor_value_is_tag(&value)) { printf("Expected self-describe tag\n"); return; }
cbor_value_get_tag(&value, &tag);
if (tag != TAG_SELF_DESCRIBE) { printf("Expected self-describe tag, got %lu\n", tag); return; }
cbor_value_advance(&value);
if (!cbor_value_is_tag(&value)) { printf("Expected inner type tag\n"); return; }
cbor_value_get_tag(&value, &tag);
cbor_value_advance(&value);
cbor_value_enter_container(&value, &inner);
if (tag == TAG_CHAT_MESSAGE) {
ChatMessage msg = {0};
decode_map_values(&inner, handle_chat_message_entry, &msg);
printf("Decoded ChatMessage: user=%s, message=%s, priority=%s\n",
msg.user, msg.message, msg.priority?"true":"false");
} else if (tag == TAG_CHAT_PRESENCE) {
ChatPresence pres = {0};
decode_map_values(&inner, handle_chat_presence_entry, &pres);
printf("Decoded ChatPresence: user=%s, status=%s, level=%d\n",
pres.user, pres.status, pres.level);
} else {
printf("Unknown tag: %lu\n", tag);
}
}
void print_and_decode(const char *label, uint8_t *buffer, size_t len) {
printf("%s (%zu bytes): ", label, len);
for (size_t i = 0; i < len; i++) printf("%02x", buffer[i]);
printf("\n");
decode_message(buffer, len);
}
// ---------------- MAIN ----------------
int main() {
uint8_t buffer[512];
size_t len;
ChatMessage msg = {.user="alice", .message="hello!", .priority=true};
len = encode_chat_message(buffer, sizeof(buffer), &msg);
print_and_decode("Encoded ChatMessage", buffer, len);
ChatPresence pres = {.user="bob", .status="away", .level=2};
len = encode_chat_presence(buffer, sizeof(buffer), &pres);
print_and_decode("Encoded ChatPresence", buffer, len);
return 0;
}
Makefile
CC = zig cc
CFLAGS = -Wall -O2 -I./tinycbor-0.6.1/src
SRCS = c-cbor-tagged-messages.c \
tinycbor-0.6.1/src/cborencoder.c \
tinycbor-0.6.1/src/cborparser.c
OBJS = c-cbor-tagged-messages.o \
tinycbor-0.6.1/src/cborencoder.o \
tinycbor-0.6.1/src/cborparser.o
TARGET = c-cbor-tagged-messages
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
main.o: main.c
$(CC) $(CFLAGS) -c $< -o $@
tinycbor-0.6.1/src/cborencoder.o: tinycbor-0.6.1/src/cborencoder.c
$(CC) $(CFLAGS) -c $< -o $@
tinycbor-0.6.1/src/cborparser.o: tinycbor-0.6.1/src/cborparser.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET)
.PHONY: download
download:
curl -s -L https://github.com/intel/tinycbor/archive/refs/tags/v0.6.1.tar.gz | tar xvz
Result
$ make && ./c-cbor-tagged-messages
zig cc -Wall -O2 -I./tinycbor-0.6.1/src -c -o c-cbor-tagged-messages.o c-cbor-tagged-messages.c
zig cc -Wall -O2 -I./tinycbor-0.6.1/src -c tinycbor-0.6.1/src/cborencoder.c -o tinycbor-0.6.1/src/cborencoder.o
zig cc -Wall -O2 -I./tinycbor-0.6.1/src -c tinycbor-0.6.1/src/cborparser.c -o tinycbor-0.6.1/src/cborparser.o
zig cc -Wall -O2 -I./tinycbor-0.6.1/src -o c-cbor-tagged-messages c-cbor-tagged-messages.o tinycbor-0.6.1/src/cborencoder.o tinycbor-0.6.1/src/cborparser.o
Encoded ChatMessage (44 bytes): d9d9f7d92711bf647573657265616c696365676d6573736167656668656c6c6f21687072696f72697479f5ff
Decoded ChatMessage: user=alice, message=hello!
Encoded ChatPresence (36 bytes): d9d9f7d92712bf647573657263626f62667374617475736461776179656c6576656c02ff
Decoded ChatPresence: user=bob, status=away
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://pkg.go.dev/github.com/fxamacker/cbor/v2