Onboarding and Sessions
In QAROS you never "create" or "join" a session directly — every session is obtained through onboarding. This page covers the four situations an integrated application encounters and the teardown rules. The security machinery behind these calls is described in Security and Onboarding; from the API's perspective it is completely hidden.
The canonical startup flow
Persist exactly one thing: the onboarding ID (a 16-byte UUID) returned by your first successful onboard. Then, on every start:
saved id? ──yes──> qar_runtime_rejoin ──ok──> session ✓
│ │
no SESSION_NOT_FOUND
│ │
└────────> show code-entry UI ──> qar_runtime_onboard
│
persist new id ──> session ✓
static QarResult obtain_session(
QarRuntime* runtime,
QarOnboardingId* io_saved_id, /* loaded from your app's settings */
const char* code_from_user, /* NULL until you prompted the user */
QarSession** out_session)
{
QarPeerPresentation who = qar_peer_presentation_default();
who.display_name = "My CAD Tool";
who.app_version = "2.4.0";
if (not qar_onboarding_id_is_zero(io_saved_id))
{
QarRejoinInit rejoin = qar_rejoin_init_default();
rejoin.onboarding_id = *io_saved_id;
rejoin.presentation = who;
QarResult r = qar_runtime_rejoin(
runtime, &rejoin, NULL, NULL, NULL, out_session);
if (qar_result_is_success(r))
return r; /* silent reconnect — the normal path */
if (not qar_result_has_code(r, QAR_STATUS_ONBOARDING_SESSION_NOT_FOUND))
return r; /* real error (hub unreachable, ...) */
/* no persisted slot -> fall through to onboarding */
}
QarOnboardInit onboard = qar_onboard_init_default();
QarOnboardCodeExt onboard_code = qar_onboard_code_ext_default();
onboard.presentation = who;
onboard_code.code = code_from_user; /* the short code shown by the hub */
onboard.header.next = &onboard_code.header;
QarResult r = qar_runtime_onboard(
runtime, &onboard, NULL, NULL, NULL, io_saved_id, out_session);
if (qar_result_has_code(r, QAR_STATUS_PAKE_ERROR))
{
/* wrong or expired code — re-prompt the user and call again */
}
/* on success: persist *io_saved_id before returning */
return r;
}
Key points:
qar_runtime_rejoinpops no UI and is silent — call it optimistically on every start.QAR_STATUS_ONBOARDING_SESSION_NOT_FOUNDis expected control flow: it means "this identity has no persisted slot here — onboard instead".QAR_STATUS_PAKE_ERRORmeans the code was wrong or expired (codes live ~10 seconds) — just re-prompt.qar_runtime_onboardtargets the local Hub by default when you chainQarOnboardCodeExt. To reach a remote Hub, chain aQarOnboardHostExtafter the code extension (see API Conventions).- Both calls have
_asyncvariants whose result callback delivers the same(onboarding_id, session)pair exactly once — use those to keep your UI thread responsive, and pass aQarCancelTokenso the user can abort.
Sibling onboarding: one app, several API instances
If your application hosts a second QAROS API instance — a helper process, a plugin sandbox, or a C# binding living next to native code — that instance must not pair with a code again. Instead, the already-onboarded instance mints an invite and hands it over.
The complete pattern is:
- Instance A starts with the normal rejoin-or-onboard flow and gets a
QarSession*. - Instance A calls
qar_session_request_onboarding_invite. - Instance A serializes the returned
QarOnboardingInviteand sends the bytes over your IPC boundary. - Instance B creates its own
QarRuntimewith its own storage root. - Instance B deserializes the invite and calls
qar_runtime_onboardwithQarOnboardInviteExt. - Instance B persists its own returned
QarOnboardingIdfor future rejoins.
The primary instance side looks like this:
/* In the onboarded instance: */
QarRequestInviteInit req = qar_request_invite_init_default();
QarOnboardingInvite* invite = NULL;
qar_session_request_onboarding_invite(session, &req, NULL, NULL, NULL, &invite);
size_t size = 0;
qar_onboarding_invite_serialized_size(invite, &size);
uint8_t* blob = malloc(size);
size_t written = 0;
qar_onboarding_invite_serialize(invite, blob, size, &written);
/* ship `blob` over your IPC of choice, then: */
qar_onboarding_invite_handle_destroy(invite);
/* In the sibling instance: */
QarOnboardingInvite* invite = NULL;
qar_onboarding_invite_deserialize(blob, blob_size, &invite);
QarOnboardInit init = qar_onboard_init_default();
QarOnboardInviteExt invite_mode = qar_onboard_invite_ext_default();
init.presentation = who;
invite_mode.invite = invite;
init.header.next = &invite_mode.header;
QarOnboardingId my_id = qar_onboarding_id_default();
QarSession* session = NULL;
qar_runtime_onboard(
runtime, &init, NULL, NULL, NULL, &my_id, &session);
qar_onboarding_invite_handle_destroy(invite); /* caller keeps ownership */
Two constraints matter here:
- Give each runtime instance its own
storage_folder_pathor its own persisted onboarding-id file. The tutorial example usesonboarding_and_rejoin.primary-stateandonboarding_and_rejoin.sibling-state. - Invites expire (
qar_onboarding_invite_get_expires_unix) — mint them on demand, move them immediately, and do not stockpile them.
The compiled example in this repo now demonstrates the full two-instance path end to end:
- Source:
qaros/qar-streaming-c/examples/onboarding_and_rejoin.c - Example walkthrough: Onboarding and Rejoin
Getting devices into the session
There is no host-side "invite a device by address" call. Every peer reaches the session the same way: it runs its own rejoin-or-onboard flow (this page) against the hub, either directly with a pairing code or via a serialized invite from an already-onboarded instance (see Sibling onboarding above). Once a peer has joined, it becomes visible through peer enumeration, and it is the peer itself that requests a render stream from your application — see Accepting render stream requests.
Teardown: destroy vs. forget
Teardown is two separate decisions:
| Call | Effect | When |
|---|---|---|
qar_session_handle_destroy(session) | Tears down the active session but keeps the persisted identity, so the next start can rejoin silently. | Every normal shutdown |
qar_runtime_forget(runtime, &forget_init) | Erases the persisted identity slot (certificate + state). Next start requires a fresh pairing code. Rejected with QAR_STATUS_ONBOARDING_SESSION_STILL_ACTIVE while the session is active — destroy the session handle first. | "Sign out" / "unpair this device" |
qar_session_handle_destroy(session);
/* only for an explicit un-pair: */
QarForgetInit forget = qar_forget_init_default();
forget.onboarding_id = saved_id;
qar_result_log_if_error(qar_runtime_forget(runtime, &forget));
Compiled tutorial
This page is backed by the compiled example onboarding_and_rejoin.c, including the sibling-instance onboarding flow. Use it as the reference implementation when wiring two QAROS API instances into the same application:
- Onboarding and Rejoin
- Dynamic Loading tutorial for the DLL bootstrap used by the example