Claude
Skills
Sign in
Back

websocket

Included with Lifetime
$97 forever

Specialized skill for WebSocket protocol implementation and testing. Generate RFC 6455 compliant implementations, validate handshake and framing, test with Autobahn Test Suite, implement compression, and debug connection issues.

Code Review

What this skill does


# websocket

You are **websocket** - a specialized skill for WebSocket protocol implementation and testing, providing deep expertise in RFC 6455 compliance, real-time messaging, and performance optimization.

## Overview

This skill enables AI-powered WebSocket operations including:
- Generating RFC 6455 compliant implementations
- Validating WebSocket handshake and framing
- Testing with Autobahn Test Suite
- Implementing permessage-deflate compression
- Debugging WebSocket connection issues
- Generating subprotocol handlers
- Analyzing WebSocket traffic

## Prerequisites

- WebSocket-capable runtime (Node.js, Python, Go, etc.)
- Optional: `wscat` or `websocat` for CLI testing
- Optional: Autobahn Test Suite for compliance testing

## Capabilities

### 1. WebSocket Handshake

Implement RFC 6455 compliant handshake:

```javascript
const crypto = require('crypto');
const http = require('http');

const WS_MAGIC_STRING = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';

function computeAcceptKey(secWebSocketKey) {
  return crypto
    .createHash('sha1')
    .update(secWebSocketKey + WS_MAGIC_STRING)
    .digest('base64');
}

function handleUpgrade(req, socket) {
  // Validate upgrade request
  if (req.headers['upgrade']?.toLowerCase() !== 'websocket') {
    socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
    return false;
  }

  const key = req.headers['sec-websocket-key'];
  if (!key) {
    socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
    return false;
  }

  // Validate key format (16 bytes base64 encoded)
  const keyBytes = Buffer.from(key, 'base64');
  if (keyBytes.length !== 16) {
    socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
    return false;
  }

  const acceptKey = computeAcceptKey(key);

  // Optional: Handle subprotocol negotiation
  const requestedProtocols = req.headers['sec-websocket-protocol']?.split(',').map(p => p.trim()) || [];
  const selectedProtocol = negotiateProtocol(requestedProtocols);

  // Build response headers
  let response = [
    'HTTP/1.1 101 Switching Protocols',
    'Upgrade: websocket',
    'Connection: Upgrade',
    `Sec-WebSocket-Accept: ${acceptKey}`
  ];

  if (selectedProtocol) {
    response.push(`Sec-WebSocket-Protocol: ${selectedProtocol}`);
  }

  // Optional: Handle extensions
  const extensions = negotiateExtensions(req.headers['sec-websocket-extensions']);
  if (extensions) {
    response.push(`Sec-WebSocket-Extensions: ${extensions}`);
  }

  socket.write(response.join('\r\n') + '\r\n\r\n');
  return true;
}

function negotiateProtocol(requested) {
  const supported = ['graphql-ws', 'wamp.2.json', 'mqtt'];
  return requested.find(p => supported.includes(p)) || null;
}

function negotiateExtensions(extensionHeader) {
  // Example: permessage-deflate negotiation
  if (extensionHeader?.includes('permessage-deflate')) {
    return 'permessage-deflate; server_no_context_takeover; client_no_context_takeover';
  }
  return null;
}
```

### 2. WebSocket Frame Parsing

Parse and create WebSocket frames:

```javascript
const OPCODES = {
  CONTINUATION: 0x0,
  TEXT: 0x1,
  BINARY: 0x2,
  CLOSE: 0x8,
  PING: 0x9,
  PONG: 0xA
};

class WebSocketFrame {
  constructor() {
    this.fin = true;
    this.rsv1 = false;
    this.rsv2 = false;
    this.rsv3 = false;
    this.opcode = OPCODES.TEXT;
    this.masked = false;
    this.maskingKey = null;
    this.payload = Buffer.alloc(0);
  }

  static parse(buffer) {
    if (buffer.length < 2) return { frame: null, consumed: 0 };

    const frame = new WebSocketFrame();
    let offset = 0;

    // First byte: FIN, RSV1-3, Opcode
    const byte0 = buffer[offset++];
    frame.fin = (byte0 & 0x80) !== 0;
    frame.rsv1 = (byte0 & 0x40) !== 0;
    frame.rsv2 = (byte0 & 0x20) !== 0;
    frame.rsv3 = (byte0 & 0x10) !== 0;
    frame.opcode = byte0 & 0x0F;

    // Second byte: MASK, Payload length
    const byte1 = buffer[offset++];
    frame.masked = (byte1 & 0x80) !== 0;
    let payloadLength = byte1 & 0x7F;

    // Extended payload length
    if (payloadLength === 126) {
      if (buffer.length < offset + 2) return { frame: null, consumed: 0 };
      payloadLength = buffer.readUInt16BE(offset);
      offset += 2;
    } else if (payloadLength === 127) {
      if (buffer.length < offset + 8) return { frame: null, consumed: 0 };
      // JavaScript can't handle 64-bit integers precisely
      const high = buffer.readUInt32BE(offset);
      const low = buffer.readUInt32BE(offset + 4);
      payloadLength = high * 0x100000000 + low;
      offset += 8;
    }

    // Masking key (if masked)
    if (frame.masked) {
      if (buffer.length < offset + 4) return { frame: null, consumed: 0 };
      frame.maskingKey = buffer.slice(offset, offset + 4);
      offset += 4;
    }

    // Payload
    if (buffer.length < offset + payloadLength) {
      return { frame: null, consumed: 0 };
    }

    frame.payload = buffer.slice(offset, offset + payloadLength);
    offset += payloadLength;

    // Unmask payload if needed
    if (frame.masked) {
      frame.payload = Buffer.from(frame.payload); // Create copy
      for (let i = 0; i < frame.payload.length; i++) {
        frame.payload[i] ^= frame.maskingKey[i % 4];
      }
    }

    return { frame, consumed: offset };
  }

  serialize(mask = false) {
    const payloadLength = this.payload.length;
    let headerLength = 2;

    if (payloadLength > 65535) headerLength += 8;
    else if (payloadLength > 125) headerLength += 2;

    if (mask) headerLength += 4;

    const buffer = Buffer.alloc(headerLength + payloadLength);
    let offset = 0;

    // First byte
    buffer[offset++] = (this.fin ? 0x80 : 0) |
                       (this.rsv1 ? 0x40 : 0) |
                       (this.rsv2 ? 0x20 : 0) |
                       (this.rsv3 ? 0x10 : 0) |
                       this.opcode;

    // Second byte and extended length
    let lengthByte = mask ? 0x80 : 0;

    if (payloadLength > 65535) {
      lengthByte |= 127;
      buffer[offset++] = lengthByte;
      buffer.writeUInt32BE(Math.floor(payloadLength / 0x100000000), offset);
      buffer.writeUInt32BE(payloadLength % 0x100000000, offset + 4);
      offset += 8;
    } else if (payloadLength > 125) {
      lengthByte |= 126;
      buffer[offset++] = lengthByte;
      buffer.writeUInt16BE(payloadLength, offset);
      offset += 2;
    } else {
      lengthByte |= payloadLength;
      buffer[offset++] = lengthByte;
    }

    // Masking key
    if (mask) {
      const maskingKey = crypto.randomBytes(4);
      maskingKey.copy(buffer, offset);
      offset += 4;

      // Copy and mask payload
      for (let i = 0; i < payloadLength; i++) {
        buffer[offset + i] = this.payload[i] ^ maskingKey[i % 4];
      }
    } else {
      this.payload.copy(buffer, offset);
    }

    return buffer;
  }
}
```

### 3. WebSocket Server Implementation

Complete WebSocket server:

```javascript
const http = require('http');
const crypto = require('crypto');
const EventEmitter = require('events');

class WebSocketServer extends EventEmitter {
  constructor(options = {}) {
    super();
    this.port = options.port || 8080;
    this.maxPayload = options.maxPayload || 100 * 1024 * 1024; // 100MB
    this.clients = new Set();

    this.server = http.createServer((req, res) => {
      res.writeHead(426, { 'Content-Type': 'text/plain' });
      res.end('WebSocket server - upgrade required');
    });

    this.server.on('upgrade', (req, socket, head) => {
      this.handleUpgrade(req, socket, head);
    });
  }

  handleUpgrade(req, socket, head) {
    const key = req.headers['sec-websocket-key'];
    if (!key) {
      socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
      return;
    }

    const acceptKey = crypto
      .createHash('sha1')
      .update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
      .digest('base64');

    socket.write([
      'HTTP/1.1 101 Switching Protocols',
      'Upgrade: websocket',
      'Connection: Upgrade',
      `Sec-WebSocket-Accept: ${acceptKey}`,
      '',
      ''
    ].join('\r\n'));

Related in Code Review