Skip to content

bloop-box/protocol-spec

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 

Repository files navigation

Bloop Protocol Specification

Version: 3.0 (2025-07-06)

Overview

The Bloop Protocol is a binary, synchronous request-response protocol layered over a TLS stream. It is used to communicate between clients (e.g., Bloop Boxes) and the server, supporting features like version negotiation, authentication, NFC-based achievements, audio retrieval and connection management.

Any minor version upgrades should be backward compatible, which means no change to existing messages or message flow. This means that only new messages might be added, which would be determined by the server capability flags.

Any changes to the existing flow will create a new major version. A client should be able to support several major versions if possible. Due to the lack of version negotiation in version 2, support for that must be manually configured if supported.

Changelog

Version 3.0 (Current):

  • Added version negotiation and capabilities bitmask
  • Achievement responses include optionally audio hashes
  • Added preload (audio manifest) hash mechanism
  • Unified message framing and error handling
  • Custom extension space reserved for users (opcodes ≥ 0x80)
  • Message types have been made unique between server and client messages

Version 2:

  • Authentication message format adjusted and local IP address added
  • Achievement IDs changed to UUIDs
  • Quit message added

Conventions and Data Types

The message format descriptions in this section use Rust-like struct definitions to describe their layout. The structs are packed, i.e., there are never any alignment gaps.

The following data types are used in the descriptions:

Type Description
uint8 8-bit unsigned integer
uint32 32-bit unsigned integer, little endian
uint64 64-bit unsigned integer, little endian
uuid Fixed 16-byte array, equivalent to [u8; 16]
nfc_uid A uint8 length (must be 4, 7, or 10), followed by the give number of bytes
ip_addr A uint8 indicating IP version (4 or 6) followed by 4 or 16 bytes for IPv4/IPv6 addresses
hash A byte string prefixed with its length as uint8
string A UTF-8 encoded text string prefixed with its byte length as uint8
bytes A byte string prefixed with its length as uint32

Structs

Achievement

Achievement conveys the unique identifier of an achievement and, optionally, the hash of the associated audio file. Using the bytes type for the audio hash allows signaling the absence of audio (zero-length) or including the actual hash.

struct Achievement {
    // Unique ID of the achievement.
    id: uuid,

    // Audio file hash. If the length is zero, no audio is available.
    audio_hash: hash,
}

Message Format

All messages in the Bloop protocol have the following format:

struct Message {
    // Message type indicating the message purpose.
    message_type: uint8,
    
    // Total payload length in bytes, excluding message_type and payload_length.
    payload_length: uint32,
    
    // Message payload. The content depends on the message type.
    payload: [u8; payload_length],
}

The server and the client must not fragment messages, i.e., a complete message must be sent before continuing. Most messages come in pairs (request and response), in which case the client must wait for the response before sending another message.

Errors

At any point the server may send an ErrorResponse indicating an error condition. This is implied in the message flow documentation, and only successful paths are explicitly documented. The handling of the ErrorResponse message depends on the connection phase, as well as the severity of the error.

If the server is not able to recover from an error, the connection is closed immediately after an ErrorResponse message is sent.

Message Flow

There are two main phases in the lifetime of a Bloop connection: the connection phase and the command phase.

The connection phase is responsible for negotiating the protocol and connection parameters, including authentication. The command phase is the regular operation phase where the server is processing queries sent by the client.

Connection Phase

To begin a session, a client opens a connection to the server and sends the ClientHandshake. The server responds in one of two ways:

  1. ServerHandshake, which also declares the supported server capabilities.
  2. ErrorResponse, which indicates an unsupported version range.

A server must only respond with a major version within the requested version range. If the returned version is outside the requested range, the client must terminate the connection.

Clients must ignore unknown capability flags but may log them for diagnostics.

Authentication

The client must now authenticate by sending an Authentication message. The server will either respond with an AuthenticationAccepted message or an ErrorResponse.

Command Phase

In the command phase, the server expects the client to send one of the following messages:

Instructs the server to verify an NFC UID. On success, the server responds with a BloopAccepted message. If the NFC UID is unknown or has been throttled, i.e., due to too many bloops at the same client, the server will instead respond with an ErrorResponse message.

Ask the server for sending audio data for a given achievement. If audio data exists for the achievement ID, the server responds with an AudioData message. If either, the achievement doesn't exist or it has no audio data attached, the server responds with an ErrorResponse message.

Acts as a keep-alive signal to the server, to which the server will always respond with a Pong message. A server implementation should typically have a connection timeout. Ideally, a client should send a ping message every couple of seconds. This also helps the client to determine if it is still connected, i.e., when the Wi-Fi connection drops.

Recommended: Clients should send a Ping every 5 seconds. Servers should disconnect after 30 seconds of inactivity.

Only available if the sever capability flags contain 0x01.

The Preload Check allows the client to verify if its cached audio assets are up to date by sending its stored global manifest hash to the server. The server compares this hash against its current audio manifest hash and responds accordingly.

If the manifest hash matches, the server responds with a PreloadMatch message and the client can resume as normal. In case of a mismatch, the server responds with a PreloadMismatch message. In this case, the client can check all returned achievements and download any missing audio files.

While the protocol makes no assumptions about the hash type, it is recommended to use MD5 for its short hash length. This is enough for simple integrity checks.

To calculate the audio manifest hash, the server should select all achievements with audio hashes, sort them by their ID and create a compound hash over all IDs and audio hashes. This guarantees a stable hash which should only change when new achievements are added or audio data have been updated.

Termination

The normal termination procedure is that the client sends a Quit message and immediately closes the connection. On receipt of this message, the server cleans up the connection resources and closes the connection.

In some cases the server might disconnect without a client request to do so. In such cases the server will attempt to send an ErrorResponse message to indicate the reason for the disconnection.

Messages

All messages have a uint8 message type, of which the range of 0x00 to 0x7f is reserved. Any message types above that can be used for custom extensions.

ErrorResponse

Sent by: server.

Format:

struct ErrorResponse {
    // Message type.
    message_type: uint8 = 0x00,

    // Error code specifying the type of error.
    error_code: uint8,
}

Error codes in the range of 0x00 to 0x7f are reserved, any codes above that can be used for custom extensions.

The following error codes are currently defined:

Code Severity Description
0x00 FATAL Unexpected message sent by client
0x01 FATAL Malformed message sent by client
0x02 FATAL Requested version range not supported
0x03 FATAL Invalid credentials send in authentication
0x04 ERROR Unknown NFC UID
0x05 ERROR NFC UID recognized but throttled
0x06 ERROR Audio file unavailable for the given achievement

Any fatal error will cause the connection to be closed, while the client should handle error gracefully.

ClientHandshake

Sent by: client

Format:

struct ClientHandshake {
    // Message type.
    message_type: uint8 = 0x01,

    // Minimum supported major version.
    min_version: uint8,

    // Maximum supported major version.
    max_version: uint8,
}

ServerHandshake

Sent by: server

Format:

struct ServerHandshake {
    // Message type.
    message_type: uint8 = 0x02,

    // The accepted version based on the requested version range.
    accepted_version: uint8,

    // Bitmask of capabilities supported by the server.
    capabilities: uint64,
}

The following capability flags are currently supported:

Hex code Description
0x01 Audio file preload

Authentication

Sent by: client

Format:

struct Authentication {
    // Message type.
    message_type: uint8 = 0x03,

    // Client ID.
    client_id: string,

    // Client secreted.
    client_secret: string,

    // Local IP address of the client, either IPv4 or IPv6.
    ip_address: ip_addr,
}

AuthenticationAccepted

Sent by: server

Format:

struct AuthenticationAccepted {
    // Message type.
    message_type: uint8 = 0x04,
}

Ping

Sent by: client

Format:

struct Ping {
    // Message type.
    message_type: uint8 = 0x05,
}

Pong

Sent by: server

Format:

struct Pong {
    // Message type.
    message_type: uint8 = 0x06,
}

Quit

Sent by: client

Format:

struct Quit {
    // Message type.
    message_type: uint8 = 0x07,
}

Bloop

Sent by: client

Format:

struct Bloop {
    // Message type.
    message_type: uint8 = 0x08,

    // NFC UID scanned on the client.
    nfc_uid: nfc_uid,
}

BloopAccepted

Sent by: server

Format:

struct BloopAccepted {
    // Message type.
    message_type: uint8 = 0x09,

    // Number of awarded achievements.
    num_achievements: uint8,

    // List of all awarded achievements.
    achievements: [achievement; num_achievements],
}

RetrieveAudio

Sent by: client

Format:

struct RetrieveAudio {
    // Message type.
    message_type: uint8 = 0x0a,

    // Unique ID of the achievement.
    achievement_id: uuid,
}

AudioData

Sent by: server

Format:

struct AudioData {
    // Message type.
    message_type: uint8 = 0x0b,

    // MP3 audio data.
    audio_data: bytes,
}

PreloadCheck

Sent by: client

Format:

struct PreloadCheck {
    // Message type.
    message_type: uint8 = 0x0c,

    // Stored audio manifest hash. Zero-length indicates no stored hash.
    audio_manifest_hash: hash,
}

PreloadMatch

Sent by: server

Format:

struct PreloadCheck {
    // Message type.
    message_type: uint8 = 0x0d,
}

PreloadMismatch

Sent by: server

Format:

struct PreloadMismatch {
    // Message type.
    message_type: uint8 = 0x0e,

    // New audio manifest hash.
    audio_manifest_hash: hash,

    // Number of achievements with audio data.
    num_achievements: uint32,

    // List of all achievements with audio data.
    achievements: [achievement; num_achievements],
}

About

Protocol specifications for client-server communication

Resources

Stars

Watchers

Forks