Skip to content
CryoCryo home
Stdlibnet::ws

frame

import std::net::ws::frame; · source

RFC 6455 section 5. A frame is a 2-byte header, an extended length (0, 2, or 8 bytes), an optional 4-byte masking key, then the payload.

Frame

type struct Frame {
    fin:     boolean;
    opcode:  OpCode;
    masked:  boolean;
    payload: Array<u8>;

    drop(mut &this) -> void;
}
const MAX_PAYLOAD: u64 = 67108864;

A decoded frame owns its payload; masked records whether the wire frame set the mask bit, so the connection layer — which knows whether it is the client or the server — can enforce the mask-direction rule.

OpCode

type enum OpCode : u8 {
    Continuation = 0x0;
    Text = 0x1;
    Binary = 0x2;
    Close = 0x8;
    Ping = 0x9;
    Pong = 0xA;
}

The four-bit opcode. Text, Binary, and Continuation carry application data across a possibly fragmented message; Close, Ping, and Pong are control frames, never fragmented and at most 125 bytes.

Reading and writing frames

implement struct BufStream<S> {
    queue_frame(mut &this, fin: boolean, opcode: OpCode, payload: Slice<u8>, mask: boolean) -> Result<(), IoError>;
    async read_frame(mut &this) -> Result<Frame, IoError>
    where S: AsyncTransport;
}

Both directions are methods on BufStream<S>, beside HTTP/1.1's read_request and for the same reasons. Reading is a method because a parser written as a static taking mut &BufStream<S> cannot be called from an async caller holding the connection as a local; only the receiver is re-supplied on every poll. Queuing is synchronous: a whole frame — header, extended length, key, masked payload — is built into the connection's pending buffer with no suspension point inside it, and the caller suspends exactly once, in flush. The bytes are masked in place where they already sit, so no staging array is needed.

queue_frame applies a fresh masking key from the OS CSPRNG when the caller is a client and none when it is a server. read_frame rejects a reserved bit, an unknown opcode, or a payload beyond MAX_PAYLOAD (64 MiB) with InvalidData; no buffered() slice is held across an await, which is what makes the parse safe against a fill that grows and moves the connection's buffer.

Written once for BufStream<S> over any async transport, so ws:// over TcpStream and wss:// over TlsStream are served by the same code. You drive this through WebSocket; it is public so a different message layer could be built on the same framing.