
# Struct Frame

[![Tests](https://github.com/mylonics/struct-frame/actions/workflows/test.yml/badge.svg)](https://github.com/mylonics/struct-frame/actions/workflows/test.yml)
[![Coverage](https://codecov.io/gh/mylonics/struct-frame/branch/main/graph/badge.svg)](https://codecov.io/gh/mylonics/struct-frame)
[![Fuzz](https://github.com/mylonics/struct-frame/actions/workflows/fuzz.yml/badge.svg)](https://github.com/mylonics/struct-frame/actions/workflows/fuzz.yml)

[![Python](https://img.shields.io/badge/python-3.9%20%7C%203.11%20%7C%203.13-blue)](https://github.com/mylonics/struct-frame/actions/workflows/test.yml)
[![Node](https://img.shields.io/badge/node-18%20%7C%2020%20%7C%2022-green)](https://github.com/mylonics/struct-frame/actions/workflows/test.yml)
[![.NET](https://img.shields.io/badge/.NET-8%20%7C%2010-512BD4)](https://github.com/mylonics/struct-frame/actions/workflows/test.yml)
[![Rust](https://img.shields.io/badge/rust-stable-orange)](https://github.com/mylonics/struct-frame/actions/workflows/test.yml)

A multi-language code generation framework that converts Protocol Buffer (.proto) files into serialization/deserialization code for C, C++, TypeScript, Python, and GraphQL. It provides framing and parsing utilities for structured message communication.

## Installation

Install from PyPI:

```bash
pip install struct-frame
```

**Note:** The package is named `struct-frame` (with hyphen) on PyPI, but the Python module uses an underscore: `struct_frame`. Use `python -m struct_frame` to run the code generator.

## Quick Start

### Basic Usage
```bash
# Generate code for Python
python -m struct_frame examples/robot.proto --build_py --py_path generated/py

# Generate code for multiple languages
python -m struct_frame examples/robot.proto --build_c --build_cpp --build_ts --build_py

# Get help
python -m struct_frame --help
```

### For Contributors

If you want to contribute or modify the code generator itself, see the [Development Guide](docs/reference/development.md) for instructions on cloning the repository and setting up a development environment.

### Test Suite

The project includes a test suite that validates code generation, compilation, and serialization across all supported languages:

```bash
# Run all tests
python test_all.py

# Run with verbose output
python tests/run_tests.py --verbose

# Skip specific languages
python tests/run_tests.py --skip-lang ts --skip-lang c

# Generate code only (no compilation/execution)
python tests/run_tests.py --only-generate
```

See `tests/README.md` for detailed test documentation.

### Tier D validation workflows

Additional cross-language validation lives under `tests/`:

- Benchmarks: `python tests/benchmarks/run_all.py --quick`, then `python tests/benchmarks/check_regression.py --allow-missing-baseline`.
- Wireshark dissector goldens: `python wireshark/test_dissector.py` (skips when `tshark` is unavailable).
- API stability: `PYTHONPATH=src python tests/api_stability/python/test_api_stability.py`; npm/NuGet/crates scaffolds are checked in.
- Documentation samples: `python tests/docs/extract_samples.py && python tests/docs/run_samples.py --quick`.

### Continuous Integration

The project uses GitHub Actions to automatically run the full test suite on:
- Every push to the `main` branch
- Every pull request targeting the `main` branch

The CI pipeline:
1. Sets up Python 3.11 and Node.js 20
2. Installs system dependencies (GCC, G++)
3. Installs Python dependencies (proto-schema-parser)
4. Installs Node.js dependencies
5. Runs the complete test suite (`python test_all.py`)
6. Uploads test artifacts for debugging

You can view test results in the "Actions" tab of the GitHub repository. Test artifacts (generated code and binary files) are available for download for 5 days after each run.

## Higher-Level SDK

Struct Frame provides high-level SDKs for simplified message communication:

- **TypeScript/JavaScript**: Promise-based with UDP, TCP, WebSocket, and Serial transports
- **Python**: Both sync and async implementations with socket, pyserial, websockets support
- **C++**: Header-only with observer/subscriber pattern, ideal for embedded systems
- **C#**: Async/await-based for .NET Core, Xamarin, and MAUI applications

See the [SDK Overview](https://struct-frame.mylonics.com/extended-features/sdk-overview/) for details.

## Framing System

Struct Frame provides a message framing system for reliable communication over serial links, network sockets, or any byte stream. Framing solves the fundamental problem of determining where messages begin and end in a continuous data stream.

### What is Message Framing?

When sending structured data over a communication channel, you need to:
1. **Identify message boundaries** - Where does one message end and the next begin?
2. **Validate message integrity** - Is the received data complete and uncorrupted?
3. **Route messages by type** - What kind of message is this and how should it be processed?

Struct Frame's framing system addresses these challenges with a cross-platform implementation.

### Framing Architecture

The framing system uses a two-level architecture:

1. **Frame Type** (Framer): Determines the number of start bytes for synchronization
   - **Basic**: 2 start bytes `[0x90] [0x70+PayloadType]`
   - **Tiny**: 1 start byte `[0x70+PayloadType]`
   - **None**: 0 start bytes (relies on external synchronization)

2. **Payload Type**: Defines the header/footer structure after start bytes
   - Minimal, Default, ExtendedMsgIds, ExtendedLength, Extended
   - SysComp, Seq, MultiSystemStream, ExtendedMultiSystemStream

### Basic Default Frame Format (Recommended)

The recommended frame format uses a simple but effective structure:

```
[Start1=0x90] [Start2=0x71] [Length] [Message ID] [Payload Data...] [CRC1] [CRC2]
```

**Frame Components:**
- **Start Bytes (0x90, 0x71)**: Synchronization markers to identify frame boundaries
- **Length**: Payload length (1 byte, up to 255 bytes)
- **Message ID**: Unique identifier (0-255) that maps to specific message types  
- **Payload**: The actual serialized message data (variable length)
- **Fletcher Checksum**: 2-byte error detection using Fletcher-16 algorithm

**Example Frame Breakdown:**
```
Message: vehicle_heartbeat (ID=42) with 4 bytes of data [0x01, 0x02, 0x03, 0x04]
Frame:   [0x90] [0x71] [0x04] [0x2A] [0x01, 0x02, 0x03, 0x04] [0x7F] [0x8A]
          Start1 Start2  Len   ID=42        Payload Data         Checksum
```

### Available Payload Types

| Payload Type | Structure | Overhead | Use Case |
|--------------|-----------|----------|----------|
| Minimal | `[MSG_ID] [PACKET]` | 1 | Fixed-size messages, trusted environments |
| Default | `[LEN] [MSG_ID] [PACKET] [CRC]` | 4 | Standard format (recommended) |
| ExtendedMsgIds | `[PKG_ID] [LEN] [MSG_ID] [PACKET] [CRC]` | 5 | Large systems with many message types |
| ExtendedLength | `[LEN16] [MSG_ID] [PACKET] [CRC]` | 5 | Large payloads (up to 64KB) |
| SysComp | `[SYS_ID] [COMP_ID] [LEN] [MSG_ID] [PACKET] [CRC]` | 6 | Multi-vehicle networks |
| Seq | `[SEQ] [LEN] [MSG_ID] [PACKET] [CRC]` | 5 | Packet loss detection |
| MultiSystemStream | `[SEQ] [SYS] [COMP] [LEN] [MSG_ID] [PACKET] [CRC]` | 7 | Multi-vehicle streaming |

See [Framing Documentation](https://struct-frame.mylonics.com/basic-usage/framing/) for the complete frame format reference.

The frame parser implements a state machine to handle partial data and synchronization recovery:

```mermaid
stateDiagram-v2
    [*] --> LOOKING_FOR_START_BYTE
    LOOKING_FOR_START_BYTE --> GETTING_HEADER: Found 0x90
    GETTING_HEADER --> GETTING_PAYLOAD: Got Message ID
    GETTING_PAYLOAD --> LOOKING_FOR_START_BYTE: Complete Frame
    GETTING_HEADER --> LOOKING_FOR_START_BYTE: Invalid Message ID
    GETTING_PAYLOAD --> LOOKING_FOR_START_BYTE: Checksum Failure
```

**State Descriptions:**
- **LOOKING_FOR_START_BYTE**: Scanning for frame start marker (0x90)
- **GETTING_HEADER**: Processing message ID and calculating expected frame length
- **GETTING_PAYLOAD**: Collecting payload data and checksum bytes

This design handles common real-world issues like:
- **Partial frame reception** (data arrives in chunks)
- **Frame corruption** (invalid start bytes, checksum mismatches)
- **Synchronization loss** (automatic recovery when frames are corrupted)

### Language-Specific Examples

#### Python
```bash
python -m struct_frame examples/myl_vehicle.proto --build_py
# Use generated Python classes directly
```

#### TypeScript
```bash
python -m struct_frame examples/myl_vehicle.proto --build_ts
npx tsc examples/index.ts --outDir generated/
node generated/examples/index.js
```

#### C
```bash
python -m struct_frame examples/myl_vehicle.proto --build_c
gcc examples/main.c -I generated/c -o main
./main
```

#### C++
```bash
python -m struct_frame examples/myl_vehicle.proto --build_cpp
g++ -std=c++20 examples/main.cpp -I generated/cpp -o main
./main
```

#### GraphQL
```bash
python -m struct_frame examples/myl_vehicle.proto --build_gql
# Use generated .graphql schema files
```

## Feature Compatibility Matrix

| Feature | C | C++ | TypeScript | Python | C# | GraphQL | Status |
|---------|---|-----|------------|--------|----|---------|--------|
| **Core Types** | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Stable |
| **String** | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Stable |
| **Enums** | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Stable |
| **Enum Classes** | N/A | ✓ | N/A | N/A | N/A | N/A | Stable |
| **Nested Messages** | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Stable |
| **Message IDs** | ✓ | ✓ | ✓ | ✓ | ✓ | N/A | Stable |
| **Message Serialization** | ✓ | ✓ | ✓ | ✓ | ✓ | N/A | Stable |
| **Flatten** | N/A | N/A | N/A | ✓ | N/A | ✓ | Partial |
| **Arrays** | ✓ | ✓ | Partial | ✓ | ✓ | ✓ | Stable |

**Legend:**
- **✓** - Feature works as documented
- **Partial** - Basic functionality works, some limitations  
- **✗** - Feature not yet available
- **N/A** - Not applicable for this language

## Frame Format and Header Types

### Header Structure Details

The BasicDefault frame format (recommended) provides a robust framing protocol:

#### Header Layout (4 bytes)
```
Byte 0: Start Byte 1 (0x90)
  - Fixed synchronization marker
  - Allows parser to identify frame boundaries
  - Recovery point after frame corruption

Byte 1: Start Byte 2 (0x71 for Default payload type)
  - Second sync byte encodes payload type
  - 0x70 = Minimal, 0x71 = Default, 0x72 = ExtendedMsgIds, etc.
  - Allows different frame formats on same channel

Byte 2: Length (0x00-0xFF)
  - Payload length in bytes
  - Allows receiver to know frame size

Byte 3: Message ID (0x00-0xFF) 
  - Maps to specific message types in proto definitions
  - Used for routing and deserialization
  - Must match `option msgid = X` in proto message
```

#### Footer Layout (2 bytes)
```
Byte N+4: Fletcher Checksum Byte 1
Byte N+5: Fletcher Checksum Byte 2
  - Fletcher-16 checksum algorithm
  - Calculated over Length + Message ID + Payload
  - Provides error detection for corruption
```

### Frame Size Calculation

**Total Frame Size = Header + Payload + Footer**

For BasicDefault (recommended):
- **Header**: 4 bytes (2 start bytes + length + message ID)
- **Payload**: Variable (depends on message content)  
- **Footer**: 2 bytes (Fletcher checksum)

**Example Calculations:**
```proto
message SimpleHeartbeat {
  option msgid = 1;
  uint32 device_id = 1;    // 4 bytes
  bool alive = 2;          // 1 byte
}
// Total: 4 (header) + 5 (payload) + 2 (footer) = 11 bytes
```

### Framing Compatibility Matrix

| Feature | C | C++ | TypeScript | Python | C# | Status | Notes |
|---------|---|-----|------------|--------|----|---------|-------|
| **Frame Encoding** | ✓ | ✓ | ✓ | ✓ | ✓ | Stable | All languages can create frames |
| **Frame Parsing** | ✓ | ✓ | ✓ | ✓ | ✓ | Stable | State machine implementation |
| **Checksum Validation** | ✓ | ✓ | ✓ | ✓ | ✓ | Stable | Fletcher-16 algorithm |
| **Sync Recovery** | ✓ | ✓ | ✓ | ✓ | ✓ | Stable | Auto-recovery from corruption |
| **Partial Frame Handling** | ✓ | ✓ | ✓ | ✓ | ✓ | Stable | Handles chunked data streams |
| **Message ID Routing** | ✓ | ✓ | ✓ | ✓ | ✓ | Stable | Automatic message type detection |
| **Buffer Management** | ✓ | ✓ | ✓ | ✓ | ✓ | Stable | Fixed-size buffers prevent overflow |
| **Cross-Language Compatibility** | ✓ | ✓ | ✓ | ✓ | ✓ | Stable | Frames interoperate between languages |

### Extended Frame Format Options

Struct Frame supports multiple frame types and payload types for different use cases:

**Frame Types:**
- **Basic Frame**: 2 start bytes (0x90, 0x70+PayloadType) - Recommended for most applications
- **Tiny Frame**: 1 start byte (0x70+PayloadType) - Constrained environments
- **None Frame**: 0 start bytes - Trusted point-to-point links

**Payload Types:**
- **Default**: Length + Message ID + Payload + CRC - Recommended
- **Minimal**: Message ID + Payload only - Fixed-size messages
- **Extended**: Package ID + 2-byte length + Message ID + Payload + CRC - Large systems
- **SysComp**: System/Component IDs for multi-vehicle networks (MAVLink-style)
- **MultiSystemStream**: Sequence + SysComp for streaming with loss detection

See [Framing Documentation](https://struct-frame.mylonics.com/basic-usage/framing/) for the complete format reference.

## Frame Format Examples and Usage

### Frame Breakdown Example

Let's trace through a message encoding and parsing example:

**Proto Definition:**
```proto
message VehicleStatus {
  option msgid = 42;
  uint32 vehicle_id = 1;
  float speed = 2;  
  bool engine_on = 3;
}
```

**Message Data:**
```
vehicle_id = 1234 (0x04D2)    -> [0xD2, 0x04, 0x00, 0x00] (little-endian)
speed = 65.5                  -> [0x00, 0x00, 0x83, 0x42] (IEEE 754 float)  
engine_on = true              -> [0x01]
Total payload: 9 bytes
```

**BasicDefault Frame Structure:**
```
Position: [0]  [1]  [2] [3] [4]  [5]  [6]  [7]  [8]  [9]  [10] [11] [12] [13] [14]
Data:      90   71   09  2A  D2   04   00   00   00   00   83   42   01   7E   C9
           │    │    │   │   └─────── Payload (9 bytes) ──────────────┘   │    │
           │    │    │   └─ Message ID (42 = 0x2A)                        │    │
           │    │    └─ Length (9 = 0x09)                                 │    │
           │    └─ Start Byte 2 (0x71 = Default payload type)             │    │
           └─ Start Byte 1 (0x90)                                         └────┘
                                                                        Checksum
```

**Checksum Calculation (Fletcher-16):**
```
Input: [0x09, 0x2A, 0xD2, 0x04, 0x00, 0x00, 0x00, 0x00, 0x83, 0x42, 0x01]
(calculated over length, message ID, and payload)
Result: [0x7E, 0xC9]
```

### Language-Specific Usage Examples

#### Python Frame Handling

```python
import sys
sys.path.insert(0, 'generated/py/')  # py_path

from struct_frame.generated.myl_vehicle import MylVehicleVehicleStatus, get_message_info
from frame_profiles import ProfileStandardWriter, ProfileStandardAccumulatingReader

# Create message
msg = MylVehicleVehicleStatus(vehicle_id=1234, speed=65.5, engine_on=True)

# Encode to frame
writer = ProfileStandardWriter(1024)
writer.write(msg)
frame_bytes = writer.data()
print(f"Frame: {[hex(b) for b in frame_bytes]}")

# Parse frame (byte-by-byte)
reader = ProfileStandardAccumulatingReader(get_message_info=get_message_info)
for byte in frame_bytes:
    result = reader.push_byte(byte)
    if result and result.valid:
        parsed = MylVehicleVehicleStatus.deserialize(result)
        print(f"Parsed: vehicle_id={parsed.vehicle_id}, speed={parsed.speed}")
```

#### TypeScript Frame Handling

```typescript
import { MylVehicleVehicleStatus } from './generated/ts/myl_vehicle.structframe';
import { ProfileStandardWriter, ProfileStandardAccumulatingReader } from './generated/ts/frame-profiles';

// Create and encode message
const msg = new MylVehicleVehicleStatus();
msg.vehicle_id = 1234;
msg.speed = 65.5;
msg.engine_on = true;

const writer = new ProfileStandardWriter(256);
writer.write(msg);
const frameData = writer.data();

// Parse frame
const reader = new ProfileStandardAccumulatingReader();
reader.addData(frameData);
const result = reader.next();
if (result && result.valid) {
    const parsed = MylVehicleVehicleStatus.deserialize(result);
    console.log(`Parsed: vehicle_id=${parsed.vehicle_id}, speed=${parsed.speed}`);
}
```

#### C Frame Handling

```c
#include "myl_vehicle.structframe.h"
#include "frame_profiles.h"

// Create message
MylVehicleVehicleStatus msg = {0};
msg.vehicle_id = 1234;
msg.speed = 65.5f;
msg.engine_on = true;

// Encode to frame
uint8_t frame_buffer[256];
ProfileStandardWriter writer = profile_standard_writer_init(frame_buffer, sizeof(frame_buffer));
profile_standard_writer_write(&writer, MYL_VEHICLE_VEHICLE_STATUS_MSG_ID,
                               (uint8_t*)&msg, sizeof(msg));

// Parse frame byte-by-byte
ProfileStandardAccumulatingReader reader = profile_standard_accumulating_reader_init(get_message_info);
for (size_t i = 0; i < writer.size; i++) {
    FrameMsgInfo info = profile_standard_accumulating_reader_push_byte(&reader, frame_buffer[i]);
    if (info.valid) {
        MylVehicleVehicleStatus* parsed = (MylVehicleVehicleStatus*)info.msg_data;
        printf("Parsed: vehicle_id=%d, speed=%.1f\n", parsed->vehicle_id, parsed->speed);
    }
}
```

#### C++ Frame Handling

```cpp
#include "myl_vehicle.structframe.hpp"
#include "frame_profiles.hpp"

// Create message
MylVehicleVehicleStatus msg{};
msg.vehicle_id = 1234;
msg.speed = 65.5f;
msg.engine_on = true;

// Encode to frame
uint8_t buffer[256];
FrameParsers::ProfileStandardWriter writer(buffer, sizeof(buffer));
writer.write(msg);

// Parse frame byte-by-byte
FrameParsers::ProfileStandardAccumulatingReader reader;
for (size_t i = 0; i < writer.size(); i++) {
    if (auto info = reader.push_byte(buffer[i])) {
        MylVehicleVehicleStatus parsed;
        parsed.deserialize(info);
        std::cout << "Parsed: vehicle_id=" << parsed.vehicle_id
                  << ", speed=" << parsed.speed << "\n";
    }
}
```

### Real-World Integration Patterns

#### Serial Communication
```python
import sys
import serial
sys.path.insert(0, 'generated/py/')

from struct_frame.generated.messages import MyMessage, get_message_info
from frame_profiles import ProfileStandardAccumulatingReader

# Setup serial connection
ser = serial.Serial('/dev/ttyUSB0', 115200)
reader = ProfileStandardAccumulatingReader(get_message_info=get_message_info)

# Continuous parsing loop
while True:
    if ser.in_waiting:
        data = ser.read(ser.in_waiting)
        reader.add_data(data)
        result = reader.next()
        if result and result.valid:
            handle_message(result)
```

#### TCP Socket Communication
```typescript
import * as net from 'net';
import { ProfileStandardAccumulatingReader } from './generated/ts/frame-profiles';
import { MyMessage } from './generated/ts/messages.structframe';

const client = net.createConnection({ port: 8080 }, () => {
  console.log('Connected to server');
});

const reader = new ProfileStandardAccumulatingReader();
client.on('data', (data: Buffer) => {
  reader.addData(data);
  let result;
  while ((result = reader.next()) && result.valid) {
    const msg = MyMessage.deserialize(result);
    handleMessage(msg);
  }
});
```

## Project Structure

- `src/struct_frame/` - Core code generation framework
  - `generate.py` - Main parser and validation logic
  - `*_gen.py` - Language-specific code generators
  - `boilerplate/` - Runtime libraries for each language
- `examples/` - Example .proto files and usage demos
  - `main.c` - C API demonstration (encoding/decoding, parsing)
  - `index.ts` - TypeScript API demonstration (similar functionality)  
  - `*.proto` - Protocol Buffer definitions for examples
- `generated/` - Output directory for generated code (git-ignored)

## Protocol Buffer Schema Reference

### Supported Data Types

| Type | Size (bytes) | Description | Range/Notes |
|------|--------------|-------------|-------------|
| **Integers** |
| `int8` | 1 | Signed 8-bit integer | -128 to 127 |
| `uint8` | 1 | Unsigned 8-bit integer | 0 to 255 |
| `int16` | 2 | Signed 16-bit integer | -32,768 to 32,767 |
| `uint16` | 2 | Unsigned 16-bit integer | 0 to 65,535 |
| `int32` | 4 | Signed 32-bit integer | -2.1B to 2.1B |
| `uint32` | 4 | Unsigned 32-bit integer | 0 to 4.3B |
| `int64` | 8 | Signed 64-bit integer | Large integers |
| `uint64` | 8 | Unsigned 64-bit integer | Large positive integers |
| **Floating Point** |
| `float` | 4 | Single precision (IEEE 754) | 7 decimal digits |
| `double` | 8 | Double precision (IEEE 754) | 15-17 decimal digits |
| **Other** |
| `bool` | 1 | Boolean value | `true` or `false` |
| `string` | Variable | UTF-8 encoded string | Length-prefixed |
| `EnumType` | 1 | Custom enumeration | Defined in .proto |
| `MessageType` | Variable | Nested message | User-defined structure |

> **Note:** All types use little-endian byte order for cross-platform compatibility.

### Array Support

Arrays (repeated fields) support all data types - primitives, enums, and messages across all target languages.

| Array Type | Syntax | Memory Usage | Use Case |
|------------|--------|--------------|----------|
| **Fixed** | `repeated type field = N [size=X];` | `sizeof(type) * X` | Matrices, buffers (always full) |  
| **Bounded** | `repeated type field = N [max_size=X];` | 1 byte (count) + `sizeof(type) * X` | Dynamic lists with limits |
| **String Arrays** | `repeated string field = N [max_size=X, element_size=Y];` | 1 byte (count) + `X * Y` bytes | Text collections with size limits |

```proto
message ArrayExample {
  repeated float matrix = 1 [size=9];                        // 3x3 matrix (always 9 elements)
  repeated string names = 2 [max_size=10, element_size=32];  // Up to 10 strings, each max 32 chars
  repeated int32 values = 3 [max_size=100];                  // Up to 100 integers (variable count)
}
```

**Generated Output** (all languages now supported):
- **Python**: `matrix: list[float]`, `names: list[str]`, `values: list[int]`
- **C**: `float matrix[9]`, `struct { uint8_t count; char data[10][32]; } names`
- **C++**: `float matrix[9]`, `struct { uint8_t count; char data[10][32]; } names` (with enum classes)
- **TypeScript**: `Array('matrix', 'Float32LE', 9)`, `Array('names_data', 'String', 10)`  
- **GraphQL**: `matrix: [Float!]!`, `names: [String!]!`, `values: [Int!]!`

> **Important**: String arrays require both `max_size` (or `size`) AND `element_size` parameters because they are "arrays of arrays" - you need to specify both how many strings AND the maximum size of each individual string. This ensures predictable memory layout and prevents buffer overflows.

### String Type

Strings are a special case of bounded character arrays with built-in UTF-8 encoding and null-termination handling across all target languages.

| String Type | Syntax | Memory Usage | Use Case |
|-------------|--------|--------------|----------|
| **Fixed String** | `string field = N [size=X];` | `X` bytes | Fixed-width text fields |
| **Variable String** | `string field = N [max_size=X];` | 1 byte (length) + `X` bytes | Text with known maximum length |

```proto
message StringExample {
  string device_name = 1 [size=16];              // Exactly 16 characters (pad with nulls)
  string description = 2 [max_size=256];         // Up to 256 characters (length-prefixed)
  string error_msg = 3 [max_size=128];           // Up to 128 characters for error messages
}
```

**String Features:**
- **Simplified Schema**: No need to specify `repeated uint8` for text data
- **Automatic Encoding**: UTF-8 encoding/decoding handled by generators  
- **Null Handling**: Proper null-termination and padding for fixed strings
- **Type Safety**: Clear distinction between binary data and text
- **Cross-Language**: Consistent string handling across C, TypeScript, and Python

### Message Options

**Message ID (`msgid`)** - Required for serializable messages:
```proto
message MyMessage {
  option msgid = 42;  // Must be unique within package (0-65535)
  string content = 1;
}
```

### Field Options

**Flatten (`flatten=true`)** - Merge nested message fields into parent:
```proto
message Position {
  double lat = 1;
  double lon = 2;
}

message Status {
  Position pos = 1 [flatten=true];  // lat, lon become direct fields  
  float battery = 2;
}
```

**Array Options** - Control array behavior:
```proto
message Data {
  repeated int32 fixed_buffer = 1 [size=256];                       // Always 256 integers  
  repeated int32 var_buffer = 2 [max_size=256];                     // Up to 256 integers
  repeated string messages = 3 [max_size=10, element_size=64];      // Up to 10 strings, each max 64 chars
  string device_name = 4 [size=32];                                 // Always 32 characters
  string description = 5 [max_size=256];                            // Up to 256 characters
}
```

## Usage Example

```proto
package sensor_system;

enum SensorType {
  TEMPERATURE = 0;
  HUMIDITY = 1;
  PRESSURE = 2;
}

message Position {
  double lat = 1;
  double lon = 2;
  float alt = 3;
}

message SensorReading {
  option msgid = 1;
  
  uint32 device_id = 1;
  int64 timestamp = 2;
  SensorType type = 3;
  
  // Device name (fixed 16-character string)  
  string device_name = 4 [size=16];
  
  // Sensor location (flattened)
  Position location = 5 [flatten=true];
  
  // Measurement values (up to 8 readings)
  repeated float values = 6 [max_size=8];
  
  // Calibration matrix (always 3x3 = 9 elements)
  repeated float calibration = 7 [size=9];
  
  // Error message (up to 128 characters)
  string error_msg = 8 [max_size=128];
  
  bool valid = 9;
}

message DeviceStatus {
  option msgid = 2;
  
  uint32 device_id = 1;
  repeated SensorReading recent_readings = 2 [max_size=10];
  float battery_level = 3;
}
```

## Schema Validation Rules

- **Message IDs**: Must be unique within package (0-65535)
- **Field numbers**: Must be unique within message  
- **Array requirements**: All `repeated` fields must specify `[size=X]` (fixed) or `[max_size=X]` (bounded)
- **String requirements**: All `string` fields must specify `[size=X]` (fixed) or `[max_size=X]` (variable)
- **String array requirements**: `repeated string` fields must specify both array size AND `[element_size=Y]`
- **Flatten constraints**: No field name collisions after flattening
- **Size limits**: Arrays limited to 255 elements maximum

## Code Generation

```bash
# Generate all languages
python -m struct_frame schema.proto --build_c --build_cpp --build_ts --build_py --build_gql

# Language-specific paths
python -m struct_frame schema.proto --build_py --py_path output/python/
python -m struct_frame schema.proto --build_c --c_path output/c/
python -m struct_frame schema.proto --build_cpp --cpp_path output/cpp/
python -m struct_frame schema.proto --build_ts --ts_path output/typescript/
python -m struct_frame schema.proto --build_gql --gql_path output/graphql/
```

## C++ Implementation

The C++ implementation provides modern C++ features while maintaining compatibility with the same binary message formats used by the C implementation:

### Key Features

- **Enum Classes**: Enums are generated as strongly-typed `enum class` types instead of plain enums
- **Modern C++ Style**: Uses classes, namespaces, templates, and RAII patterns
- **Binary Compatibility**: Generated structs use the same memory layout as C (via `__attribute__((packed))`)
- **Type Safety**: Leverages C++ templates for type-safe message helpers
- **STL Integration**: Uses standard library features like `<cstdint>`, `<functional>`, and `<span>`

### Example

```proto
package robot;

enum RobotStatus : uint8_t {
  IDLE = 0;
  MOVING = 1;
  ERROR = 2;
}

message RobotState {
  option msgid = 10;
  uint32 robot_id = 1;
  RobotStatus status = 2;
  float battery_level = 3;
}
```

**Generated C++ Code:**

```cpp
#pragma pack(push, 1)

// Enum class instead of plain enum
enum class RobotRobotStatus : uint8_t {
    IDLE = 0,
    MOVING = 1,
    ERROR = 2
};

// Struct inheriting from MessageBase<Derived, MSG_ID, MAX_SIZE, MAGIC1, MAGIC2>.
// All template parameters are auto-generated; users do not specify them manually.
struct RobotRobotState : FrameParsers::MessageBase<RobotRobotState, 10, 9, 0xAB, 0xCD> {
    uint32_t robot_id;
    RobotRobotStatus status;
    float battery_level;

    size_t deserialize(const uint8_t* buffer, size_t buffer_size);
    size_t deserialize(const FrameParsers::FrameMsgInfo& frame_info);
    size_t serialize(uint8_t* buffer) const;
};

#pragma pack(pop)

namespace FrameParsers {
inline bool get_message_length(size_t msg_id, size_t* size) {
    switch (msg_id) {
        case RobotRobotState::MSG_ID:
            *size = RobotRobotState::MAX_SIZE;
            return true;
        default: break;
    }
    return false;
}

inline MessageInfo get_message_info(uint16_t msg_id) {
    switch (msg_id) {
        case RobotRobotState::MSG_ID:
            return MessageInfo{RobotRobotState::MAX_SIZE, RobotRobotState::MAGIC1, RobotRobotState::MAGIC2};
        default: break;
    }
    return MessageInfo{};
}
}  // namespace FrameParsers
```

**Usage:**

```cpp
#include "robot.structframe.hpp"
#include "frame_profiles.hpp"

// Create and initialize message
RobotRobotState state{};
state.robot_id = 123;
state.status = RobotRobotStatus::MOVING;  // Type-safe enum class
state.battery_level = 85.5f;

// Encode with frame profile
uint8_t buffer[256];
FrameParsers::ProfileStandardWriter writer(buffer, sizeof(buffer));
writer.write(state);

// Parse byte-by-byte with AccumulatingReader
FrameParsers::ProfileStandardAccumulatingReader reader;
for (size_t i = 0; i < writer.size(); i++) {
    if (auto info = reader.push_byte(buffer[i])) {
        RobotRobotState msg;
        msg.deserialize(info);
        std::cout << "Robot ID: " << msg.robot_id
                  << ", Status: " << static_cast<int>(msg.status) << "\n";
    }
}
```

## Wireshark Dissector (Experimental)

An experimental Wireshark Lua dissector is available for protocol analysis and debugging of struct-frame packets. See [wireshark/README.md](wireshark/README.md) for installation and usage instructions.

## Additional Documentation

- **[Array Implementation Guide](ARRAY_IMPLEMENTATION.md)** - Documentation of array features, syntax, and generated code examples across all languages
