Sessions

Session lifecycle and management

Sessions

Reference for OctoMY™'s communication sessions including lifecycle, states, and management.

Pro Tip

Session statistics (rtt, packetLoss, packetsReceived) are invaluable for debugging connectivity issues. High RTT or packet loss often indicates network congestion or NAT timeout issues - consider reducing the keepalive interval if you see frequent disconnects.


Session overview

A CommsSession represents an active communication channel between two nodes:

Session Overview


Session states

State diagram

Session State Diagram

State descriptions

State Description
Closed No active session
Connecting Initiating connection
SYN_SENT SYN sent, waiting for SYN
Handshaking Key exchange in progress
Established Active, encrypted communication
Closing Graceful shutdown in progress

Session lifecycle

Connection establishment

// Session creation flow
CommsSession* session = channel->createSession(associateId);

// State transitions
session->initiate();           // Closed -> Connecting
session->onSynSent();          // Connecting -> SYN_SENT
session->onSynAckReceived();   // SYN_SENT -> Handshaking
session->onAckConfirmed();     // Handshaking -> Established

Active session

// Send data
session->send(courierId, payload);

// Receive data (via signal)
connect(session, &CommsSession::dataReceived,
        [](quint8 courierId, QByteArray data) {
    // Handle incoming data
});

// Check state
if (session->isEstablished()) {
    // Safe to send
}

Session termination

// Graceful close
session->close();              // Established -> Closing

// Immediate close
session->abort();              // Any state -> Closed

// Timeout handling
session->onTimeout();          // Various -> Closed

Session properties

Identity

Property Type Description
sessionId quint64 Unique session identifier
localId quint32 Local node's session ID part
remoteId quint32 Remote node's session ID part
associateId QString Remote Associate ID

Connection info

Property Type Description
remoteAddress CarrierAddress Peer's network address
localAddress CarrierAddress Local bound address
carrier CommsCarrier* Transport layer carrier

Security

Property Type Description
sessionKey QByteArray AES-256 encryption key
localNonce QByteArray Our handshake nonce
remoteNonce QByteArray Peer's handshake nonce
isEncrypted bool Encryption active

Statistics

Property Type Description
rtt quint32 Round-trip time (ms)
packetsReceived quint64 Total packets in
packetsSent quint64 Total packets out
bytesReceived quint64 Total bytes in
bytesSent quint64 Total bytes out
packetLoss float Loss percentage

Session directory

CommsSessionDirectory

Manages all active sessions:

class CommsSessionDirectory : public QObject {
public:
    // Session lookup
    CommsSession* sessionByAssociateId(const QString& id);
    CommsSession* sessionByAddress(const CarrierAddress& addr);
    CommsSession* sessionById(quint64 sessionId);

    // Session creation
    CommsSession* createSession(const QString& associateId);

    // Session enumeration
    QList<CommsSession*> allSessions();
    QList<CommsSession*> establishedSessions();

    // Session removal
    void removeSession(CommsSession* session);

signals:
    void sessionAdded(CommsSession* session);
    void sessionRemoved(CommsSession* session);
    void sessionStateChanged(CommsSession* session, SessionState state);
};

Session lookup

// Find session by Associate ID
auto session = directory->sessionByAssociateId("abc123...");

// Find session by network address
CarrierAddressUDP addr("192.168.1.100", 8124);
auto session = directory->sessionByAddress(addr);

// Iterate all sessions
for (auto session : directory->establishedSessions()) {
    qDebug() << session->remoteAddress();
}

Handshake details

Handshake state machine

Handshake State Machine

Handshake data

struct HandshakeData {
    quint16 protocolVersion;
    quint8 nodeType;           // Agent/Remote/Hub
    quint16 capabilities;      // Feature flags
    QByteArray nonce;          // 32-byte random
    QByteArray publicKey;      // RSA-2048 public key
    QByteArray personalityId;  // 64-byte identity hash
};

Key derivation

// After handshake completes:
QByteArray sharedSecret = deriveSharedSecret(
    localPrivateKey,
    remotePublicKey
);

QByteArray sessionKey = hkdf(
    sharedSecret,
    localNonce + remoteNonce,
    "OctoMY Session Key",
    32  // AES-256 key length
);

Reliability layer

Per-session reliability

Each session maintains reliability state:

struct ReliabilityState {
    quint16 nextSendSeq;       // Next sequence to send
    quint16 nextExpectedSeq;   // Next expected from peer
    quint16 lastAckedSeq;      // Last ACK sent
    quint32 windowSize;        // Current window size

    // Pending reliable packets
    QMap<quint16, PendingPacket> pendingPackets;

    // Receive buffer for reordering
    QMap<quint16, ReceivedPacket> receiveBuffer;
};

Packet tracking

struct PendingPacket {
    quint16 sequence;
    QByteArray data;
    quint64 sentTime;
    quint8 retryCount;
    quint32 retryTimeout;
};

Acknowledgment handling

void CommsSession::onAckReceived(quint16 ackNum) {
    // Remove acknowledged packets
    auto it = mPendingPackets.begin();
    while (it != mPendingPackets.end()) {
        if (sequenceLessThan(it.key(), ackNum)) {
            // Update RTT estimate
            updateRTT(now() - it.value().sentTime);
            it = mPendingPackets.erase(it);
        } else {
            ++it;
        }
    }
}

Session events

Signals

class CommsSession : public QObject {
signals:
    // State changes
    void stateChanged(SessionState newState);
    void established();
    void closed();

    // Data
    void dataReceived(quint8 courierId, const QByteArray& data);

    // Reliability
    void packetAcknowledged(quint16 sequence);
    void packetLost(quint16 sequence);

    // Statistics
    void rttUpdated(quint32 rttMs);
    void statsUpdated();
};

Usage example

connect(session, &CommsSession::stateChanged,
        [](SessionState state) {
    switch (state) {
        case SessionState::Established:
            qDebug() << "Connection established!";
            break;
        case SessionState::Closed:
            qDebug() << "Connection closed";
            break;
    }
});

connect(session, &CommsSession::dataReceived,
        [](quint8 courierId, const QByteArray& data) {
    handleCourierData(courierId, data);
});

Session timeouts

Timeout configuration

Parameter Default Description
Connect timeout 10s Handshake time limit
Idle timeout 60s Max time without data
Keepalive interval 5s Ping frequency
Keepalive timeout 15s Disconnect after no pong

Timeout handling

void CommsSession::checkTimeouts() {
    quint64 now = currentTimeMs();

    // Handshake timeout
    if (mState == SessionState::Connecting ||
        mState == SessionState::Handshaking) {
        if (now - mStateEnteredTime > mConnectTimeout) {
            emit connectTimeout();
            close();
            return;
        }
    }

    // Idle timeout
    if (mState == SessionState::Established) {
        if (now - mLastActivityTime > mIdleTimeout) {
            emit idleTimeout();
            close();
            return;
        }
    }

    // Keepalive
    if (now - mLastPingSent > mKeepaliveInterval) {
        sendPing();
    }
}

Session security

Encryption

All established sessions use AES-256-GCM:

QByteArray CommsSession::encrypt(const QByteArray& plaintext) {
    QByteArray iv = generateRandomIV(16);
    QByteArray ciphertext;
    QByteArray tag;

    aes256GcmEncrypt(
        mSessionKey,
        iv,
        plaintext,
        ciphertext,
        tag
    );

    return iv + ciphertext + tag;
}

QByteArray CommsSession::decrypt(const QByteArray& encrypted) {
    QByteArray iv = encrypted.left(16);
    QByteArray tag = encrypted.right(16);
    QByteArray ciphertext = encrypted.mid(16, encrypted.size() - 32);

    QByteArray plaintext;
    if (!aes256GcmDecrypt(mSessionKey, iv, ciphertext, tag, plaintext)) {
        throw DecryptionError();
    }

    return plaintext;
}

Session key rotation

void CommsSession::rotateKey() {
    // Derive new key from current key
    QByteArray newKey = hkdf(
        mSessionKey,
        mLocalNonce + mRemoteNonce + QByteArray::number(mKeyRotation),
        "OctoMY Key Rotation",
        32
    );

    // Send key rotation notification
    sendKeyRotation(mKeyRotation + 1);

    // Update key
    mSessionKey = newKey;
    mKeyRotation++;
}

Multiple sessions

Session per peer

Session Per Peer

Session limits

Setting Default Description
Max sessions 100 Per channel limit
Max pending 10 Incomplete handshakes
Session cleanup 10s Remove stale sessions

Debugging sessions

Session status

void debugSession(CommsSession* session) {
    qDebug() << "Session ID:" << session->sessionId();
    qDebug() << "State:" << session->stateString();
    qDebug() << "Remote:" << session->remoteAddress();
    qDebug() << "RTT:" << session->rtt() << "ms";
    qDebug() << "Packets in:" << session->packetsReceived();
    qDebug() << "Packets out:" << session->packetsSent();
    qDebug() << "Loss:" << session->packetLoss() << "%";
}

Common issues

Issue Cause Solution
Stuck in Connecting Firewall blocking Open UDP port
Frequent disconnects NAT timeout Reduce keepalive
High packet loss Network congestion Reduce send rate
Decryption failures Key mismatch Re-pair nodes

In this section
Topics
reference sessions networking encryption state-machine
See also