std1.0.0-beta-7 SafeC docs

Source serial/protobuf.h

View in the repository

// SafeC Standard Library — Protocol Buffers wire format (proto3 binary
// encoding, https://protobuf.dev/programming-guides/encoding/).
//
// This is a wire-format *primitive* library, not a `.proto`-file IDL
// compiler (SafeC has no field-name/field-number reflection to drive code
// generation from a schema — see std/serial/value.h's top comment on the
// same limitation for JSON). Making a struct protobuf-serializable means
// writing 'to_proto()'/'from_proto()' methods by hand that call these
// primitives with the field numbers your `.proto` schema assigns:
//
//   // .proto: message Point { int32 x = 1; int32 y = 2; }
//   struct Point { int x; int y; };
//   struct String Point::to_proto() const {
//       struct String out = String::new();
//       pb_write_int64_field(&out, 1, (long long)self.x);
//       pb_write_int64_field(&out, 2, (long long)self.y);
//       return out;
//   }
//   struct Point Point::from_proto(const unsigned char* data, unsigned long len, int* ok) {
//       struct Point p;
//       p.x = 0; p.y = 0;
//       struct PbReader r = PbReader::new(data);
//       int good = 1;
//       while (r.has_more()) {
//           int field; int wireType;
//           if (!r.read_tag(&field, &wireType)) { good = 0; break; }
//           if (field == 1 && wireType == PB_WIRE_VARINT) {
//               long long v; r.read_int64(&v); p.x = (int)v;
//           } else if (field == 2 && wireType == PB_WIRE_VARINT) {
//               long long v; r.read_int64(&v); p.y = (int)v;
//           } else if (!r.skip_field(wireType)) { good = 0; break; }
//       }
//       if (ok != (int*)0) { *ok = good; }
//       return p;
//   }
//
// Interoperates with real protobuf implementations in other languages —
// the wire format itself (varint, tags, zigzag, length-delimited,
// fixed32/fixed64) is exactly what protoc-generated code produces, field
// numbers and wire types are the only thing that has to match by hand
// since there's no shared `.proto` schema driving both sides here.
#pragma once
#include <std/collections/string.h>

namespace std {

// ── Wire types (proto3 encoding spec) ─────────────────────────────────────────
#define PB_WIRE_VARINT  0   // int32/int64/uint32/uint64/sint32/sint64/bool/enum
#define PB_WIRE_FIXED64 1   // fixed64/sfixed64/double
#define PB_WIRE_LEN     2   // string/bytes/embedded messages/packed repeated
#define PB_WIRE_FIXED32 5   // fixed32/sfixed32/float
// Wire types 3 and 4 (deprecated 'group' start/end) are not supported —
// no proto3 schema emits them.

// ── Writer ──────────────────────────────────────────────────────────────────
// Every pb_write_*_field appends its own tag (field_num, wire_type) before
// the value, so a message is built by calling one of these per field, in
// any order, onto the same 'out' buffer.

void pb_write_varint(&String out, unsigned long long v);
void pb_write_tag(&String out, int field_num, int wire_type);

// int32/int64: plain varint of the value's bit pattern (protobuf's own
// quirk: a negative int32 is sign-extended to 64 bits *before* varint
// encoding, so it always takes the full 10 bytes on the wire — this
// matches protoc's own behavior, not a bug).
void pb_write_int64_field(&String out, int field_num, long long v);
void pb_write_uint64_field(&String out, int field_num, unsigned long long v);
// sint32/sint64: zigzag-encoded varint (efficient for small negative
// values, unlike plain int64 above).
void pb_write_sint64_field(&String out, int field_num, long long v);
void pb_write_bool_field(&String out, int field_num, int v);
void pb_write_double_field(&String out, int field_num, double v);
void pb_write_float_field(&String out, int field_num, float v);
// Length-delimited: string, bytes, or an embedded/nested message's own
// already-encoded bytes (encode the submessage with its own to_proto()
// first, then pass the result here).
void pb_write_string_field(&String out, int field_num, const &char s);
void pb_write_bytes_field(&String out, int field_num, []const unsigned char data);

// ── Reader ────────────────────────────────────────────────────────────────
// A zero-copy cursor over an existing byte buffer — read_length_delimited
// returns a slice *of* that same buffer (valid only as long as it is).

struct PbReader {
    []const unsigned char data;
    unsigned long         pos;

    // A reader positioned at the start of data.
    static struct PbReader new([]const unsigned char data);

    int has_more() const;

    // Each returns 1 on success, 0 on a truncated/malformed encoding (ran
    // out of bytes mid-varint, length-delimited field's declared length
    // exceeds the remaining buffer, etc.).
    int read_varint(&unsigned long long out);
    int read_tag(&int field_num, &int wire_type);
    int read_int64(&long long out);
    int read_uint64(&unsigned long long out);
    int read_sint64(&long long out);
    int read_bool(&int out);
    int read_double(&double out);
    int read_float(&float out);
    // The field's bytes, a slice of the reader's own buffer (see the
    // zero-copy note above) — copy it out (e.g. String::from_bytes) if it
    // needs to outlive that buffer. null on a truncated encoding.
    ?[]const unsigned char read_length_delimited();

    // Advances past one field's value without decoding it, given its wire
    // type from a just-read tag — the standard "ignore fields I don't
    // recognize" behavior every protobuf reader needs for forward
    // compatibility with schemas that gained fields since this code was
    // written. Returns 0 for wire type 3/4 (unsupported group markers) or
    // a truncated encoding.
    int skip_field(int wire_type);
};

} // namespace std