websocks

Types

Close reason codes, that can be used in the close control frame.

pub type CloseReason {
  NormalClosure(data: BitArray)
  GoingAway(data: BitArray)
  ProtocolError(data: BitArray)
  UnsupportedData(data: BitArray)
  InvalidPayloadData(data: BitArray)
  PolicyViolation(data: BitArray)
  MessageTooBig(data: BitArray)
  MandatoryExtension(data: BitArray)
  InternalError(data: BitArray)
  ServiceRestart(data: BitArray)
  TryAgainLater(data: BitArray)
  BadGateway(data: BitArray)
  TLSHandshake(data: BitArray)
  CustomCloseCode(code: Int, data: BitArray)
  NoCloseReason
}

Constructors

  • NormalClosure(data: BitArray)

    The connection successfully completed its purpose and is closing normally.

  • GoingAway(data: BitArray)

    The endpoint is going away, either due to server shutdown or browser navigation.

  • ProtocolError(data: BitArray)

    A WebSocket protocol violation was detected.

  • UnsupportedData(data: BitArray)

    The endpoint received data it cannot accept.

  • InvalidPayloadData(data: BitArray)

    The message data doesn’t match the declared type.

  • PolicyViolation(data: BitArray)

    Generic status for policy violations when no other code applies.

  • MessageTooBig(data: BitArray)

    Message exceeds the maximum size the endpoint can handle.

  • MandatoryExtension(data: BitArray)

    The server encountered an unexpected condition preventing request fulfillment.

  • InternalError(data: BitArray)

    The server encountered unexpected error.

  • ServiceRestart(data: BitArray)

    Server is restarting.

  • TryAgainLater(data: BitArray)

    Temporary server overload.

  • BadGateway(data: BitArray)

    Gateway/proxy received invalid response.

  • TLSHandshake(data: BitArray)

    TLS/SSL handshake failure.

  • CustomCloseCode(code: Int, data: BitArray)

    Custom close codes for application-specific use cases.

  • NoCloseReason

    No close reason.

Context is the internal state of the WebSocket connection. It stores the remaining bytes from the decoding process, fragment accumulation and compression states.

pub opaque type Context

Context takeover settings. Disabling context takeover means the compression context is reset after each message, reducing memory overhead but making compression less effective for small, repetitive payloads.

pub type ContextTakeover {
  ContextTakeover(no_client: Bool, no_server: Bool)
}

Constructors

  • ContextTakeover(no_client: Bool, no_server: Bool)

    Arguments

    no_client

    Indicates that the client does not want to use context takeover.

    no_server

    Indicates that the server does not want to use context takeover.

Control frames that can appear in WebSocket communication.

pub type Control {
  Ping(payload: BitArray)
  Pong(payload: BitArray)
  Close(reason: CloseReason)
}

Constructors

  • Ping(payload: BitArray)

    A ping control frame. Used for keepalive.

  • Pong(payload: BitArray)

    A pong control frame. Response to ping.

  • Close(reason: CloseReason)

    A close control frame. Contains the reason for closing if present.

Errors that can occur during the decoding process.

pub type DecodeError {
  InvalidFrame
  NotEnoughData(data: BitArray)
}

Constructors

  • InvalidFrame

    The frame is invalid.

  • NotEnoughData(data: BitArray)

    The data is not enough to decode the frame.

The result of the decoding process. It contains the decoded complete/incomplete frame with possible compression applied.

pub opaque type DecodedFrame

Each variant corresponds to a type of WebSocket frame, carrying the appropriate payload.

pub type Frame {
  Continuation(payload: BitArray)
  Text(payload: BitArray)
  Binary(payload: BitArray)
  Control(control: Control)
}

Constructors

  • Continuation(payload: BitArray)

    A continuation frame, used for fragmented messages.

  • Text(payload: BitArray)

    A text frame, containing UTF-8 encoded payload.

  • Binary(payload: BitArray)

    A binary frame, containing arbitrary binary data.

  • Control(control: Control)

    A control frame, controlling WebSocket connection.

Errors that can occur during the frame processing in process_incoming_frames.

pub type ProcessError {
  DecodeFailed(reason: DecodeError)
  ResolveFailed(reason: ResolveError)
}

Constructors

  • DecodeFailed(reason: DecodeError)

    Frame decoding failed with the given decode error.

  • ResolveFailed(reason: ResolveError)

    Frame resolution failed with the given resolve error.

Errors that can occur during the resolving fragments process.

pub type ResolveError {
  NotUtf8
  OrphanedContinuation
  ControlFrameFragmented
  FragmentationInterrupted
  ConcurrentFragmentation
  CompressedContinuation
}

Constructors

  • NotUtf8

    The complete text frame payload is not UTF-8.

  • OrphanedContinuation

    Receive continuation frame as the first frame in a fragmentation sequence.

  • ControlFrameFragmented

    The control frame is fragmented.

  • FragmentationInterrupted

    Receive complete text/binary frame when resolving fragmented frame.

  • ConcurrentFragmentation

    Receive incomplete text/binary frame when resolving fragmented frame.

  • CompressedContinuation

    The continuation frame contains compressed flag (RSV1=1).

Represents an instruction, indicating whether to continue processing more frames or stop. Used by the frame handler in process_incoming_frames to control the processing flow.

pub type ResolveNext(state) {
  Continue(state: state)
  Stop(state: state)
}

Constructors

  • Continue(state: state)

    Continue processing more frames with the updated state.

  • Stop(state: state)

    Stop processing frames and return the updated state. Remaining data will be stored in the context buffer for the next processing call.

Values

pub fn close_context(context: Context) -> Nil

Frees the compression resources. Should be called when the context is no longer needed.

Example

websocks.close_context(context)
// => Nil
pub fn compute_accept(key: String) -> String

Computes the value of the Sec-WebSocket-Accept header during the handshake. Requires Sec-WebSocket-Key header value to be present.

Example

websocks.compute_accept("dGhlIHNhbXBsZSBub25jZQ==")
// => "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="
pub fn create_context(
  compression: option.Option(ContextTakeover),
) -> Context

Creates a new context with the optional compression settings.

Example

websocks.create_context(
  Some(websocks.ContextTakeover(no_client: True, no_server: False)),
)
// => Context
pub fn decode_frame(
  data: BitArray,
  context: Context,
) -> Result(#(DecodedFrame, BitArray), DecodeError)

Decodes a single frame from the given data. For decoding multiple frames it is recommended to use decode_many_frames instead.

Example

// 0x81 : fin=1, rsv1-3=0, opcode=1
// 0x05 : mask=0, payload length=5
let frame = <<0x81, 0x05>>
// 0x48 0x65 0x6c 0x6c 0x6f : "Hello"
let payload = <<0x48, 0x65, 0x6c, 0x6c, 0x6f>>

// Let's assume we have this buffer:
let buffer = <<frame:bits, payload:bits, frame:bits>>

// The first frame is decoded, and the remaining binary is returned.
let decoded = websocks.decode_frame(buffer)
// => Ok(#(DecodedFrame, <<129, 5>>))

let assert Ok(#(_decoded_frame, rest)) = decoded

// Remaining binary is not enough to decode the frame.
websocks.decode_frame(rest)
// => Error(NotEnoughData(<<129, 5>>))

// If we add the remaining payload to the buffer, the frame is decoded.
websocks.decode_frame(<<rest:bits, payload:bits>>)
// => Ok(#(DecodedFrame, <<>>))
pub fn decode_many_frames(
  data: BitArray,
  context: Context,
) -> Result(#(List(DecodedFrame), Context), Nil)

Decodes multiple frames from the given data until the buffer is empty or an error occurs. Returns the provided context with updated buffer.

Example

// 0x81 : fin=1, rsv1-3=0, opcode=1
// 0x04 : mask=0, payload length=4
let frame = <<0x81, 0x04>>
// 0x48 0x65 0x6c 0x6c : "Hell"
let payload1 = <<0x48, 0x65, 0x6c, 0x6c>>
// 0x6f 0x20 0x57 0x6f : "o Wo"
let payload2 = <<0x6f, 0x20, 0x57, 0x6f>>
// 0x72 0x6c 0x64 0x21 : "rld!"
let payload3 = <<0x72, 0x6c, 0x64, 0x21>>

// Let's assume we have this buffer:
let frames = <<
  frame:bits,
  payload1:bits,
  frame:bits,
  payload2:bits,
  frame:bits,
>>

// The context is used to store the remaining bytes after decoding.
let context = websocks.create_context(None)

let decoded = websocks.decode_many_frames(frames, context)
// => Ok(#([DecodedFrame, DecodedFrame], Context: <<0x81, 0x04>>))

let assert Ok(#(_decoded_frames, updated_context)) = decoded

// We can try to decode the remaining frames using the updated context.
websocks.decode_many_frames(payload3, updated_context)
// => Ok(#([DecodedFrame], Context: <<>>))
pub fn encode_binary_frame(
  payload payload: BitArray,
  context context: Context,
  masking masking: option.Option(BitArray),
) -> BitArray

Encodes a binary frame with the given payload, context and mask. If the context has compression enabled, the payload will be compressed.

pub fn encode_close_frame(
  reason reason: CloseReason,
  masking masking: option.Option(BitArray),
) -> BitArray

Encodes a close frame with the given reason and mask.

pub fn encode_ping_frame(
  payload payload: BitArray,
  masking masking: option.Option(BitArray),
) -> BitArray

Encodes a ping frame with the given payload and mask.

pub fn encode_pong_frame(
  payload payload: BitArray,
  masking masking: option.Option(BitArray),
) -> BitArray

Encodes a pong frame with the given payload and mask.

pub fn encode_text_frame(
  payload payload: BitArray,
  context context: Context,
  masking masking: option.Option(BitArray),
) -> BitArray

Encodes a text frame with the given payload, context and masking. If the context has compression enabled, the payload will be compressed.

pub fn get_context_takeovers(
  extensions: List(String),
) -> ContextTakeover

Extracts the client and server context takeover settings from the list of extensions.

Example

let extensions =
   request.get_header(req, "sec-websocket-extensions")
   |> result.map(string.split(_, ";"))
   |> result.unwrap([])
// => ["permessage-deflate", "client_no_context_takeover"]

websocks.get_context_takeovers(extensions)
// => ContextTakeover(no_client: True, no_server: False)
pub fn has_deflate(extensions: List(String)) -> Bool

Checks if the permessage-deflate extension is present in the list of extensions.

Example

let extensions =
   request.get_header(req, "sec-websocket-extensions")
   |> result.map(string.split(_, ";"))
   |> result.unwrap([])
// => ["permessage-deflate", "client_no_context_takeover"]

websocks.has_deflate(extensions)
// => True
pub const magic_string: String

Sequence of characters that is used to compute the Sec-WebSocket-Accept header during the handshake.

pub fn mask(payload: BitArray, mask: BitArray) -> BitArray

Masks the payload of any length using the provided mask.

Example

let payload = bit_array.from_string("Hello")
// Original: H(0x48) e(0x65) l(0x6c) l(0x6c) o(0x6f)
// Mask:     0x37     0xfa    0x21    0x3d    0x37
// Masked:   0x7f     0x9f    0x4d    0x51    0x58
websocks.mask(payload, <<0x37, 0xfa, 0x21, 0x3d>>)
// => <<0x7f, 0x9f, 0x4d, 0x51, 0x58>>
pub fn process_incoming_frames(
  data: BitArray,
  context: Context,
  state: state,
  handler: fn(state, Context, Frame) -> ResolveNext(state),
) -> Result(#(state, Context), ProcessError)

Processes incoming WebSocket frames from the given data. This function combines the context buffer with the new data, decodes frames, resolves fragments, and calls the handler function for each resolved frame. The handler returns ResolveNext to control the processing flow. If there’s not enough data to decode a complete frame, the remaining data is stored in the context buffer for the next call.

Example

// 0x81 : fin=1, rsv1-3=0, opcode=1
// 0x05 : mask=0, payload length=5
let frame = <<0x81, 0x05>>
// 0x48 0x65 0x6c 0x6c 0x6f : "Hello"
let payload = <<0x48, 0x65, 0x6c, 0x6c, 0x6f>>

// let's assume we have this buffer:
let data = <<frame:bits, payload:bits, frame:bits>>

let context = websocks.create_context(None)
let initial_state = 0

// Handler that collects all text frames
let handler = fn(state, _context, frame) {
  case frame {
    websocks.Continuation(payload) ->
      echo #("received continuation frame", payload)
    websocks.Text(payload) -> echo #("received text frame", payload)
    websocks.Binary(payload) -> echo #("received binary frame", payload)
    websocks.Control(websocks.Ping(payload)) ->
      echo #("received ping frame", payload)
    websocks.Control(websocks.Pong(payload)) ->
      echo #("received pong frame", payload)
    websocks.Control(websocks.Close(reason)) ->
      echo #(
        "received close frame",
        bit_array.from_string(string.inspect(reason)),
      )
  }

  websocks.Continue(state + 1)
}

let processed =
  websocks.process_incoming_frames(data, context, initial_state, handler)
  // => #("received text frame", "Hello")

echo processed
// => Ok(#(1, context: <<0x81, 0x05>>))
pub fn resolve_fragments(
  decoded_frames: List(DecodedFrame),
  context: Context,
) -> Result(#(List(Frame), Context), ResolveError)

Resolves a list of decoded frames into a list of frames. If the context has compression enabled, the payload will be decompressed. Incomplete frames are stored in the updated context until the fragmentation is complete. If returns an error, the protocol is violated, and the implementation should consider closing the WebSocket connection.

Example

// Incomplete text frame:
// 0x01 : fin=0, rsv1-3=0, opcode=1
// 0x04 : mask=0, payload length=4
let text = <<0x01, 0x04>>
// 0x00 : fin=0, rsv1-3=0, opcode=0
// 0x04 : mask=0, payload length=4
let continuation_complete = <<0x80, 0x04>>
// Complete continuation frame:
// 0x80 : fin=1, rsv1-3=0, opcode=0
// 0x04 : mask=0, payload length=4
let continuation_incomplete = <<0x00, 0x04>>
// 0x48 0x65 0x6c 0x6c : "Hell"
let payload1 = <<0x48, 0x65, 0x6c, 0x6c>>
// 0x6f 0x20 0x57 0x6f : "o Wo"
let payload2 = <<0x6f, 0x20, 0x57, 0x6f>>
// 0x72 0x6c 0x64 0x21 : "rld!"
let payload3 = <<0x72, 0x6c, 0x64, 0x21>>

// Let's assume we have this buffer:
let frames = <<
  text:bits,
  payload1:bits,
  continuation_incomplete:bits,
  payload2:bits,
  continuation_complete:bits,
  payload3:bits,
>>

let context = websocks.create_context(None)

// We assume that the frames have been successfully decoded.
let assert Ok(#(decoded_frames, context)) =
  websocks.decode_many_frames(frames, context)

// Resolve the decoded frames.
websocks.resolve_fragments(decoded_frames, context)
// => Ok(#([Text("Hello World!")], Context))
Search Document