Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save jboyd01/629f566b0287e479af05e18bd421e123 to your computer and use it in GitHub Desktop.

Select an option

Save jboyd01/629f566b0287e479af05e18bd421e123 to your computer and use it in GitHub Desktop.

Session ID vs Display Name: Protocol Identity Confusion

Status: Architecture Issue Date: 2026-03-06 Impact: Medium - Causes developer confusion, complicates monitoring, blocks clean protocol evolution


Executive Summary

The videocall-rs protocol uses two overlapping identifiers for participants, causing confusion throughout the codebase:

  1. session_id (uint64) - Server-assigned GUID, the true unique identifier
  2. email (string) - Misnamed field that actually contains user-provided display name

This dual-identity system creates maintenance burden, developer confusion, and complicates features like monitoring (vcprobe) and user identity management.


The Problem

Field Naming vs. Actual Content

Protobuf Definition (packet_wrapper.proto):

message PacketWrapper {
  PacketType packet_type = 1;
  string email = 2;           // ❌ MISLEADING NAME
  bytes data = 3;
  uint64 session_id = 4;      // ✅ True unique identifier
}

What email field actually contains:

  • Non-OAuth mode: Whatever user types in "Username" field
    • Examples: "JayAtHome", "Alice123", "bob_work"
  • OAuth mode: Sometimes actual email (alice@company.com), sometimes display name
  • Never validated as email format

The Two Identities

Identifier Type Assigned By Scope Content
session_id uint64 Server on connect Connection lifetime Unique numeric GUID (e.g., 12345)
email string Client/User User choice Display name OR email (e.g., "JayAtHome" or "jay@company.com")

How It Manifests Across Layers

1. UI Layer (yew-ui, dioxus-ui)

Home Page (yew-ui/src/pages/home.rs:156-166):

<label for="username">{"Username"}</label>  // UI says "Username"
<input
    id="username"
    placeholder="Enter your name"           // Placeholder says "name"
    ref={username_ref}
    // ...
/>

// But stored as:
let username = username_ref.cast::<HtmlInputElement>().unwrap().value();
save_username_to_storage(&username);
username_ctx.set(Some(username));  // Called "username" in code

When creating VideoCallClient (yew-ui/src/components/attendants.rs:288):

let opts = VideoCallClientOptions {
    userid: email.clone(),  // Variable named "email" but contains username!
    // ...
};

Inconsistency:

  • UI labels: "Username", "Enter your name"
  • Code variables: email, username, userid
  • Actual content: User-provided display name (not validated as email)

2. Client Layer (videocall-client)

VideoCallClientOptions (src/client/video_call_client.rs:65):

pub struct VideoCallClientOptions {
    pub userid: String,  // Called "userid" but used as display name
    // ...
}

When creating packets:

// connection.rs:250
PacketWrapper {
    email: userid.to_owned(),  // userid → email field
    // ...
}

Health Reporter (src/health_reporter.rs:107-111):

pub fn new(session_id: String, reporting_peer: String, health_interval_ms: u64) -> Self {
    Self {
        session_id,           // Numeric GUID
        reporting_peer,       // Display name (from userid)
        // ...
    }
}

// Called from video_call_client.rs:199-202:
let mut reporter = HealthReporter::new(
    session_id,
    options.userid.clone(),  // Display name goes into reporting_peer
    // ...
);

3. Protocol Layer (Protobuf)

PacketWrapper stamps ALL packets:

message PacketWrapper {
  string email = 2;       // Contains display_name
  uint64 session_id = 4;  // Added by server
}

Server adds session_id (actix-api/src/actors/chat_server.rs:228-230):

if let Ok(mut packet_wrapper) = PacketWrapper::parse_from_bytes(&msg.data) {
    if packet_wrapper.session_id == 0 {
        packet_wrapper.session_id = session;  // Server stamps with session GUID
    }
}

HEALTH packets use BOTH (protobuf/types/health_packet.proto):

message HealthPacket {
  string session_id = 1;          // Session GUID (as string)
  string reporting_peer = 3;      // Display name
  map<string, PeerStats> peer_stats = 7;  // Keys are session_id!
}

4. Backend/Server Layer (actix-api)

Session assignment on connect (src/session_manager.rs:123-132):

/// Build SESSION_ASSIGNED packet: server sends immediately after connect.
pub fn build_session_assigned_packet(session_id: u64) -> Vec<u8> {
    let wrapper = PacketWrapper {
        packet_type: PacketType::SESSION_ASSIGNED.into(),
        email: SYSTEM_USER_EMAIL.to_string(),  // System sender
        session_id,                              // The GUID being assigned
        ..Default::default()
    };
    wrapper.write_to_bytes().unwrap_or_default()
}

Flow:

  1. Client connects
  2. Server generates unique session_id (uint64)
  3. Server sends SESSION_ASSIGNED packet
  4. Client stores session_id, uses it for packet filtering
  5. All subsequent packets stamped with session_id by server

Real-World Impact

Problem 1: Developer Confusion

Example from actual codebase:

// yew-ui/src/pages/meeting.rs:187-193
// Use the user's email as userid so the server can match
// push-notification `target_email` to this observer client.
let email_for_userid = (*current_user_email).clone()
    .unwrap_or_else(|| display_name.clone());

let opts = VideoCallClientOptions {
    userid: email_for_userid,  // Is this email or display_name?
    // ...
};

Questions developers face:

  • Is userid an email address or display name?
  • Should I validate it as email format?
  • Can two users have the same userid?
  • Is session_id or email the unique identifier?

Problem 2: vcprobe Session Mapping

vcprobe needs to display participant names but faces:

  • MEDIA packets have: email (display name) + session_id (GUID)
  • HEALTH peer_stats keys are: session_id as strings
  • Must build mapping: session_id → display name

Code (vcprobe/src/state.rs:99-101, 143-147):

/// Maps session IDs (numbers as strings) to email addresses
/// Built from MEDIA packets to resolve HEALTH packet peer_stats keys
session_to_email: HashMap<String, String>,

// Track session_id → email mapping for HEALTH packet resolution
if pkt.session_id > 0 {
    let session_key = pkt.session_id.to_string();
    self.session_to_email.insert(session_key, email.clone());  // "email" is display_name
}

Reality check: Comment says "email" but it's actually display name. The HashMap is misnamed.


Problem 3: OAuth vs Non-OAuth Divergence

OAuth flow (meeting-api/src/token.rs:36-41):

pub struct SessionTokenClaims {
    pub sub: String,    // ✅ Actual email from OAuth provider
    pub name: String,   // ✅ Display name from OAuth
    // ...
}

OAuth has proper separation, but protocol doesn't:

  • OAuth: sub (email) separate from name (display name)
  • Protocol: email field conflates both concepts

Result:

  • OAuth users: userid might be alice@company.com (real email)
  • Non-OAuth users: userid is AliceAtHome (arbitrary display name)
  • Same field, completely different semantics

Detailed Analysis by Packet Type

MEDIA Packets (AUDIO, VIDEO, SCREEN, HEARTBEAT)

Structure:

message PacketWrapper {
  string email = 2;       // Display name (from userid)
  uint64 session_id = 4;  // Server-assigned GUID
}

message MediaPacket {
  string email = 2;  // Also display name (duplicate!)
  // ...
}

Flow:

  1. Client populates PacketWrapper.email with options.userid (display name)
  2. Client sends with session_id = 0
  3. Server stamps session_id from connection state
  4. Server broadcasts to room

Key point: Both email (display name) and session_id (GUID) transmitted together.


HEALTH Packets

Structure (health_packet.proto:54-65):

message HealthPacket {
  string session_id = 1;          // Numeric session_id as string
  string meeting_id = 2;
  string reporting_peer = 3;      // Display name

  map<string, PeerStats> peer_stats = 7;  // Keys are peer session_ids!
}

Creation (videocall-client/src/health_reporter.rs:521-523):

pb.session_id = session_id.to_string();      // Own session_id
pb.reporting_peer = reporting_peer.to_string();  // Own display name

// peer_stats keys:
for (peer_id, peer_data) in health_map {
    // peer_id is session_id (numeric string) from diagnostics
    pb.peer_stats.insert(peer_id.clone(), peer_stats);
}

Problem: peer_stats map keys are session_ids, but reporting_peer is display name. No consistent identifier.


DIAGNOSTICS Packets

Diagnostics events use display names (videocall-client/src/health_reporter.rs:237-244):

// Diagnostics metrics include:
"from_peer" => {  // Source peer display name
    reporting_peer = Some(s.clone());
}
"to_peer" => {    // Target peer display name
    target_peer = Some(s.clone());
}

But HEALTH packet uses session_ids for peer keys. Requires mapping layer.


Root Causes

1. Historical: Email-Only Identity

Original assumption:

  • All users authenticated via OAuth
  • email field would always contain actual email
  • Email would serve as unique identifier

Reality:

  • Non-OAuth mode added later
  • Users type arbitrary display names
  • email field retained for backward compatibility

2. Protocol Evolution Without Refactoring

Timeline:

  1. Initial: email field as unique identifier
  2. Added: session_id for server-side session management
  3. Result: Two overlapping identifiers, never reconciled

Current state: Band-aids everywhere:

  • Client uses userid but populates email field
  • Server uses session_id internally
  • vcprobe builds session_to_email mapping
  • Developers confused about which to use

3. Naming Conventions Not Updated

Fields named for original intent, not current usage:

  • email → should be user_identifier or display_name
  • userid → could be email OR display name depending on context
  • reporting_peer → is a display name, not a peer ID

Recommendations

Short-Term: Documentation & Clarification

1. Add inline documentation:

pub struct VideoCallClientOptions {
    /// User identifier for display and packet tagging.
    /// - In OAuth mode: usually the user's email from the OAuth provider
    /// - In non-OAuth mode: arbitrary display name entered by user
    /// This value is sent in PacketWrapper.email field (despite the name).
    pub userid: String,
}

2. Rename variables in new code:

// Instead of:
let email = username_ref.value();

// Use:
let display_name = username_ref.value();
let user_identifier = username_ref.value();

3. Update comments in protobuf:

message PacketWrapper {
  string email = 2 [deprecated = true];  // Actually contains display_name/user_identifier
  uint64 session_id = 4;                 // Server-assigned unique session GUID
}

Medium-Term: Add Proper Display Name Field

Protobuf V2 (backward compatible):

message PacketWrapper {
  PacketType packet_type = 1;
  string email = 2 [deprecated = true];   // Kept for backward compat
  bytes data = 3;
  uint64 session_id = 4;

  // New fields (optional for backward compatibility):
  string user_identifier = 5;  // OAuth email OR non-OAuth username
  string display_name = 6;     // Friendly display name (can differ from identifier)
}

Migration strategy:

  1. Clients populate both email (old) and user_identifier (new)
  2. Server prefers user_identifier if present, falls back to email
  3. After 6-month grace period, deprecate email field
  4. Eventually remove email field in V3

Long-Term: Unified Identity Model

Proper separation:

// User identity (immutable)
message UserIdentity {
  string user_id = 1;        // Email (OAuth) or username (non-OAuth)
  uint64 session_id = 2;     // Server-assigned session GUID
}

// Display metadata (mutable)
message UserProfile {
  string display_name = 1;   // Friendly name shown in UI
  string avatar_url = 2;     // Future: profile picture
}

message PacketWrapper {
  PacketType packet_type = 1;
  UserIdentity identity = 2;
  UserProfile profile = 3;
  bytes data = 4;
}

Benefits:

  • Clear separation: identity vs presentation
  • Session management uses session_id exclusively
  • Display uses display_name exclusively
  • Supports future features (avatars, nicknames)

Migration Path

Phase 1: Documentation (1 week)

Tasks:

  • Add clarifying comments to all email fields
  • Document userid behavior in OAuth vs non-OAuth
  • Update developer onboarding docs
  • Add this document to docs/

Impact: Zero code changes, clarifies existing behavior


Phase 2: Variable Renaming (2-4 weeks)

Tasks:

  • Rename internal variables: emailuser_identifier
  • Update function parameters with clear names
  • Add type aliases: type UserIdentifier = String;
  • Update tests to use clear names

Impact: Internal refactor, no protocol changes


Phase 3: Protocol V2 (1-2 months)

Tasks:

  • Add user_identifier and display_name fields to protobuf
  • Update clients to send both old and new fields
  • Update server to prefer new fields, fall back to old
  • Add feature flag for V2 protocol

Impact: Backward compatible protocol evolution


Phase 4: Deprecate Old Fields (6+ months)

Tasks:

  • Remove email field population from clients
  • Server logs warnings when old fields used
  • Remove backward compatibility shims

Impact: Clean protocol, technical debt eliminated


Testing Strategy

Current Behavior Tests

Add tests documenting current behavior:

#[test]
fn test_userid_can_be_non_email() {
    let opts = VideoCallClientOptions {
        userid: "JayAtHome".to_string(),  // Not an email!
        // ...
    };
    // Should work - userid is actually display_name
}

#[test]
fn test_session_id_is_unique_identifier() {
    // Verify two users with same display_name get different session_ids
}

#[test]
fn test_health_packet_peer_stats_keys_are_session_ids() {
    // Verify peer_stats keys are numeric session_ids, not display names
}

Migration Tests

Ensure backward compatibility:

#[test]
fn test_v2_packet_readable_by_v1_server() {
    // V2 client sends user_identifier + email (old)
    // V1 server should read email field
}

#[test]
fn test_v1_packet_readable_by_v2_server() {
    // V1 client sends only email field
    // V2 server should fall back to email
}

Discussion Questions for Team

Architecture

  1. Should userid be validated?

    • Require email format?
    • Allow arbitrary strings?
    • Different rules for OAuth vs non-OAuth?
  2. Is session_id the source of truth for uniqueness?

    • Can two users have same display name?
    • Should server enforce unique display names?
  3. HEALTH packet keys: session_id or display_name?

    • Current: session_id (requires mapping)
    • Alternative: Use display_name (simpler but less robust)

User Experience

  1. What does "Username" mean to users?

    • Is it their identity (like email)?
    • Is it a nickname/display name?
    • Should we rename the UI label?
  2. OAuth users: show email or display name?

    • In participant list: alice@company.com or Alice Smith?
    • In vcprobe: which identifier?

Implementation

  1. Renaming priority?

    • Start with UI layer (most visible)?
    • Start with protocol (most foundational)?
    • Incremental across all layers?
  2. Backward compatibility window?

    • How long to support V1 protocol?
    • Can we force upgrade for security reasons?
  3. vcprobe fix: temporary or permanent?

    • Keep session_to_email mapping as-is?
    • Wait for protocol V2?
    • Rename to session_to_display_name now?

Appendix: Code References

Key Files

Protocol:

  • protobuf/types/packet_wrapper.proto - PacketWrapper definition
  • protobuf/types/media_packet.proto - MediaPacket definition
  • protobuf/types/health_packet.proto - HealthPacket definition

Client:

  • videocall-client/src/client/video_call_client.rs:65 - VideoCallClientOptions
  • videocall-client/src/health_reporter.rs:107 - HealthReporter::new
  • videocall-client/src/connection/connection.rs:250 - Packet email field population

UI:

  • yew-ui/src/pages/home.rs:156 - Username input field
  • yew-ui/src/components/attendants.rs:288 - VideoCallClient creation
  • yew-ui/src/pages/meeting.rs:189 - OAuth vs non-OAuth userid

Backend:

  • actix-api/src/actors/chat_server.rs:228 - Session ID stamping
  • actix-api/src/session_manager.rs:125 - SESSION_ASSIGNED packet

Monitoring:

  • vcprobe/src/state.rs:99 - session_to_email mapping
  • vcprobe/src/state.rs:143 - Mapping construction

Summary

The session_id vs email (actually display_name) confusion is a technical debt from protocol evolution. It causes:

  • Developer confusion
  • Complicated monitoring code
  • Inconsistent identity semantics
  • Blocked protocol improvements

Recommended fix: Incremental migration to explicit user_identifier and display_name fields, deprecating the misleading email field.

Timeline: 1 week (docs) → 1 month (renaming) → 2 months (protocol V2) → 6 months (deprecation)

Priority: Medium - Not blocking current functionality, but impedes future work and onboarding.

@testradav
Copy link

testradav commented Mar 6, 2026

my 2cents:

session_id

  • session_id is the ultimate source of truth, assigned to an attendee instance by the server upon joining the meeting, whether user is authenticated or unauthenticated
  • packet wrapper should have session_id

display_name

  • display name to be taken from the UI/localStorage
image image
  • not sent with ALL packet wrappers, but sent with a session join packet, or rename packet (eg. the clients need to know everyones display name at one point)
  • change internal usage named as username to displayName (or display_name, depending on case style)

email, and sometimes username as described above

  • change to be called uuid (user unique id) throughout to avoid confusion

  • if authenticated,

    • value provided by the auth identity provider
  • if not authenticated,

    • value is null
  • stored in session cookie (part of jwt token)

  • should packet wrapper also contain uuid ?

    • yes:
      • allows client to identify multiple instances (sessions) of same user authed
      • useful for setting host of meeting? TBD
      • extra payload with every packet (perf hit)
    • no:
      • client would need to call server to get all sessions for a given uuid to determine matching

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment