bloop-protocol 1.1.0

Core implementation of the Bloop wire protocol
# Bloop Protocol

Core implementation of the Bloop wire protocol (version 3), the binary
request-response protocol spoken between Bloop clients (e.g. Bloop Boxes) and
Bloop servers over TLS.

This crate is the single source of truth for the wire format. Server and
client frameworks, as well as custom clients, build on it instead of
hand-implementing framing and message layouts. The written specification
lives in the [protocol-spec](https://github.com/bloop-box/protocol-spec)
repository.

## Layers

- **Frames** (`frame`): every message is `message_type: u8` followed by a
  little-endian `u32` payload length and the payload. `read_frame` and
  `write_frame` move `RawMessage` values over async streams, with a
  configurable maximum payload length on the read side.
- **Payload codec** (`codec`): the `Encode` and `Decode` traits map values
  onto the packed payload layout. Implementations exist for the spec's
  primitives (integers, strings, UUIDs, IP addresses, NFC UIDs, data hashes);
  message structs derive both.
- **Message sets** (`set`, `message`): a `MessageSet` dispatches raw frames
  into typed messages by opcode. `ClientMessage` and `ServerMessage` cover
  the standard protocol and take an extension set as a type parameter for
  custom messages.

## Defining custom messages

Extensions own the opcode range `0x80` and above. A custom message is a
derived struct; a message set groups them per wire direction:

```rust
use bloop_protocol::{ClientMessage, Decode, Encode, MessageSet, NfcUid, Payload};

#[derive(Debug, Encode, Decode, Payload)]
#[bloop(opcode = 0x82)]
struct RegisterTag {
    name: String,
    nfc_uid: NfcUid,
}

#[derive(Debug, MessageSet)]
enum CustomRequest {
    RegisterTag(RegisterTag),
}

// Plugs into the standard client-to-server set:
type Request = ClientMessage<CustomRequest>;
```

Fields encode in declaration order. Collections carry their count prefix
width explicitly, matching the spec's two widths:

```rust
#[derive(Debug, Encode, Decode, Payload)]
#[bloop(opcode = 0x81)]
struct TagList {
    #[bloop(count = u8)]
    tags: Vec<TagEntry>,

    #[bloop(count = u32)]
    blob: Vec<u8>,
}
```

The `MessageSet` derive checks at compile time that opcodes are unique and,
for extension sets, outside the reserved standard range. The standard
`ClientMessage`/`ServerMessage` enums are built with the same derive: a
variant holding a generic parameter (their `Custom(Ext)`) acts as a
catch-all that delegates to the plugged-in extension set.

## Reading and writing frames

```rust
use bloop_protocol::{ClientMessage, MessageSet, read_frame, write_frame};

// Client side: encode and send, then await the response.
let frame = message.encode()?;
write_frame(&mut stream, &frame).await?;

// Server side: read and dispatch.
let frame = read_frame(&mut stream, MAX_PAYLOAD_LEN).await?;
let message = ClientMessage::<CustomRequest>::decode(&frame)?;
```

Unknown opcodes and malformed payloads are distinct errors
(`MessageSetError::UnknownOpcode` vs `Malformed`), so a server can answer an
unexpected message gracefully instead of dropping the connection.

## Features

| Feature | Description |
|---------|-------------|
| `hex`   | Hex parsing (`FromHex`) for NFC UIDs and data hashes |
| `md5`   | Conversion from `md5::Digest` into `DataHash` |
| `serde` | Serde support, representing NFC UIDs and data hashes as hex strings (implies `hex`) |

## Workspace

- `bloop-protocol`: the protocol implementation.
- `bloop-protocol-derive`: the `Encode`, `Decode`, `Payload` and `MessageSet`
  derive macros, re-exported by `bloop-protocol`.