Connection
Connection spans three roles: the device starts and waits for inbound access, your backend issues the connect token, and the client uses that token to connect the target device. This page covers that end-to-end path.
Prepare These Inputs
| Item | Purpose |
|---|---|
device_id | Identifies the target device |
device_secret_key | Device secret used by device startup and backend-side token signing |
access_id | Application credential identifier |
secret_key | Application secret used by your backend |
remote_id | The target identifier used by the client connect call |
sub | The subject identity for this request, such as your user_id |
In the common case, the client-side remote_id is the target device device_id.
Overall Flow
- The device starts the SDK with
device_idanddevice_secret_key. - Your backend checks whether the current subject is allowed to access the target device, then issues the connect
tokenwith the public custom signing algorithm. - The client uses the returned
tokento connect the targetremote_id.
Step 1: Start the Device and Wait for Connections
The device-side C SDK call order is: TiRtcSetOption(...) -> TiRtcInit() -> TiRtcStart().
- If you use a custom service entry, set
TIRTC_OPT_SERVICE_ENDPOINTfirst. TiRtcStart()can take"<device_id>,<device_secret_key>"directly.- If you already set
device_secret_keythroughTIRTC_OPT_SECRET_KEY,TiRtcStart()can also take onlydevice_id.
#include <stdio.h>
#include "tiRTC.h"
static void on_event(int event, const void *data, int len) {
(void)data;
(void)len;
if (event == TiEVENT_SYS_STARTED) {
puts("device started");
}
}
static TIRTCCALLBACKS cbs = {
.on_event = on_event,
};
int start_device(void) {
int code = TiRtcInit();
if (code != 0) {
return code;
}
return TiRtcStart("your-device-id,your-device-secret-key", &cbs);
}A 0 return from TiRtcStart() only means the initial argument check passed. Startup is complete when TiEVENT_SYS_STARTED arrives. After that, the device is waiting for inbound connections.
Step 2: Issue the Token on Your Backend
Before issuing the token, do your own authorization check: can this subject access this target device? If yes, issue the short-lived connect token.
Required Claims
| Field | Meaning |
|---|---|
sub | Subject identity for this request |
scope | Authorized target scope; a common form is connect:<remote_id> |
iss | Your application access_id |
iat | Issued-at timestamp |
exp | Expiration timestamp |
nonce | A fresh random value for each issuance |
Public Custom Signing Algorithm
The final token string sent to the client has this form:
token = "v1.<payload_b64>.<app_sig>"Compute it like this:
payload_json = {
"sub": <subject>,
"scope": "connect:" + <remote_id>,
"iss": <access_id>,
"iat": <issued_at>,
"exp": <expires_at>,
"nonce": <random_nonce>
}
payload_b64 = base64url(payload_json)
device_sig = HMAC_SHA256(device_secret_key, payload_b64)
app_sig = HMAC_SHA256(secret_key, payload_b64 + "." + device_sig)
token = "v1." + payload_b64 + "." + app_sigKeep two boundaries clear:
secret_keyanddevice_secret_keystay in controlled backend or device environments. Do not send them to the client.- The client only consumes the final
token. It does not participate in signing.
Go Example
package tirtc
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"time"
)
type ConnectTokenClaims struct {
Subject string `json:"sub"`
Scope string `json:"scope"`
Issuer string `json:"iss"`
IssuedAt int64 `json:"iat"`
ExpiresAt int64 `json:"exp"`
Nonce string `json:"nonce"`
}
func hmacB64(key, content string) string {
h := hmac.New(sha256.New, []byte(key))
_, _ = h.Write([]byte(content))
return base64.RawURLEncoding.EncodeToString(h.Sum(nil))
}
func randomNonce() (string, error) {
buf := make([]byte, 16)
if _, err := rand.Read(buf); err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(buf), nil
}
func GenerateConnectToken(accessID, secretKey, remoteID, deviceSecretKey, subject string, ttlSeconds int64) (string, error) {
now := time.Now().Unix()
nonce, err := randomNonce()
if err != nil {
return "", err
}
claims := ConnectTokenClaims{
Subject: subject,
Scope: "connect:" + remoteID,
Issuer: accessID,
IssuedAt: now,
ExpiresAt: now + ttlSeconds,
Nonce: nonce,
}
payloadJSON, err := json.Marshal(claims)
if err != nil {
return "", err
}
payloadB64 := base64.RawURLEncoding.EncodeToString(payloadJSON)
deviceSig := hmacB64(deviceSecretKey, payloadB64)
appSig := hmacB64(secretKey, payloadB64+"."+deviceSig)
return "v1." + payloadB64 + "." + appSig, nil
}Step 3: Connect from the Client
The remote_id used here must match the target bound during token issuance. In the common case, this is the target device device_id.
val conn = TiRtcConn()
val code = conn.connect(
remoteId = remoteId,
token = token,
)
if (code != 0) {
Log.e("TiRTC", "connect request failed code=$code")
}let conn = TiRtcConn(delegate: nil)
let code = conn.connect(to: remoteId, token: token)
if code != 0 {
print("connect request failed: \(code)")
}final TiRtcConn conn = TiRtcConn();
final int code = conn.connect(remoteId: remoteId, token: token);
if (code != 0) {
debugPrint('connect request failed: $code');
}Playback, command messaging, and stream messaging all build on this connection.
Alignment Checks
- Treat
TiEVENT_SYS_STARTEDas the device-started signal. - The target bound during backend signing and the client-side
remote_idmust identify the same target. - If your current backend still uses the field name
peer_id, keep it equal to the same target string used asremote_idhere. - Treat
tokenas an opaque string on the client side. Do not assemble, parse, or rewrite it there.