Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

13 Commits

Folders and files

Repository files navigation

Bloop Client Framework

This library handles the connection lifecycle for Bloop protocol clients: DNS lookup, TLS, protocol handshake, authentication, keep-alive pings, and reconnecting after failures. Applications observe the connection through a status watch and perform request-response exchanges through typed operations.

The wire format lives in the bloop-protocol crate.

Quick start

use bloop_client_framework::{BloopClient, ConnectionConfig, ConnectionStatus};

let client = BloopClient::builder()
    .config(ConnectionConfig {
        host: "bloop.example.com".to_string(),
        port: 12345,
        client_id: "client".to_string(),
        client_secret: "secret".to_string(),
    })
    .build()?;

let mut status = client.status();
status.wait_for(|status| matches!(status, ConnectionStatus::Connected { .. })).await?;

let achievements = client.bloop(nfc_uid).await?;
let audio = client.retrieve_audio(achievement_id).await?;

The client can also start without configuration (status Unconfigured) and receive credentials at runtime via configure, e.g. when they arrive on a provisioning tag. Rejected credentials latch the client until new ones arrive; use InvalidCredentialsPolicy::Retry to keep retrying instead.

Custom messages

Extensions define their messages with the bloop-protocol derives (direct dependency required, opcodes 0x80 and above) and pair each request with its response type through the Request trait:

use bloop_client_framework::Request;
use bloop_protocol::{Decode, Encode, Payload};

#[derive(Debug, Encode, Decode, Payload)]
#[bloop(opcode = 0x80)]
struct SubmitScore {
    nfc_uid: NfcUid,
    score: u32,
}

#[derive(Debug, Encode, Decode, Payload)]
#[bloop(opcode = 0x81)]
struct ScoreAccepted {
    rank: u32,
}

impl Request for SubmitScore {
    type Response = ScoreAccepted;
}

// Fully typed request-response:
let accepted = client.custom(SubmitScore { nfc_uid, score }).await?;

Protocol errors, including extension-defined codes, arrive as RequestError::Error(ErrorResponse). For exchanges that fall outside the one-to-one mapping there is request_raw.

On-connect hook

Work that must happen on every (re)connect, such as preloading extension data, runs inside the connection attempt, before the status flips to Connected; a hook failure fails the attempt:

let builder = builder.on_connect(|session| {
    Box::pin(async move {
        let scores = session.custom(FetchHighScores).await?;
        // store the list somewhere shared
        Ok(())
    })
});

All server I/O inside the hook must go through the Session: the hook runs on the client's own connection task, so calling methods on a captured BloopClient handle in the hook deadlocks the client. AudioCache accepts a &mut Session for the same reason.

Audio cache

AudioCache keeps achievement audio on disk, keyed by achievement ID and audio hash, so server-side updates invalidate stale files naturally:

let cache = AudioCache::new("/var/cache/bloop/audio");

// On demand, e.g. when a bloop awards an achievement:
if let Some(path) = cache.ensure(&client, &record).await? {
    play(path);
}

// Or as a preload after a PreloadOutcome::Mismatch:
let skipped = cache.sync(&client, &achievements).await?;

sync returns the IDs of achievements whose audio the server refused to deliver; a non-empty list means the sync was partial, so don't persist the new manifest hash and the next preload check will retry.

NFC reader

The nfc feature provides a channel-backed reader handle with cancel-safe waits, suitable for select! loops, plus NDEF text-record parsing. The nfc-mfrc522 feature adds the built-in backend for MFRC522 modules over SPI (Linux):

use bloop_client_framework::nfc::{NfcReader, Mfrc522Config};

let reader = NfcReader::spawn_mfrc522(Mfrc522Config::default()).await?;

let uid = reader.wait_for_card().await?;
let achievements = client.bloop(uid).await?;
reader.wait_for_removal().await?;

Custom or emulated backends serve the other end of NfcReader::channel() instead, and serve_mfrc522 runs the built-in backend blocking on the calling thread for applications that supervise reader threads themselves.

Features

All features are off by default:

[dependencies]
bloop-client-framework = { version = "1", features = ["audio", "nfc-mfrc522"] }
Feature Description
audio Audio playback for achievement and UI sounds
nfc NFC reader handle, backend channel, and NDEF parsing
nfc-mfrc522 Built-in MFRC522 reader backend (Linux, SPI + GPIO)
tokio-graceful-shutdown Implements IntoSubsystem for the client, quitting cleanly on shutdown

TLS

The server certificate is verified through the operating system's certificate verifier (rustls-platform-verifier). On Linux this reads the system CA bundle once at startup, so locally installed CAs (e.g. for self-signed certificates) require an application restart. RootCertSource::DangerousDisabled skips verification entirely for testing.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages