WebSocket: C++/STL parsing library

The WebSocket protocol establishes a two-way communication channel via TCP. Messages are made up of one or more dataframes, and are delineated by frames with the fin bit set.

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of WebSocket using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Using Kaitai Struct in C++/STL usually consists of 3 steps.

  1. We need to create an STL input stream (std::istream). One can open local file for that, or use existing std::string or char* buffer.
    #include <fstream>
    
    std::ifstream is("path/to/local/file.websocket", std::ifstream::binary);
    #include <sstream>
    
    std::istringstream is(str);
    #include <sstream>
    
    const char buf[] = { ... };
    std::string str(buf, sizeof buf);
    std::istringstream is(str);
  2. We need to wrap our input stream into Kaitai stream:
    #include <kaitai/kaitaistream.h>
    
    kaitai::kstream ks(&is);
  3. And finally, we can invoke the parsing:
    websocket_t data(&ks);

After that, one can get various attributes from the structure by invoking getter methods like:

data.initial_frame() // => get initial frame

C++/STL source code to parse WebSocket

websocket.h

#ifndef WEBSOCKET_H_
#define WEBSOCKET_H_

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "kaitai/kaitaistruct.h"

#include <stdint.h>
#include <vector>

#if KAITAI_STRUCT_VERSION < 7000L
#error "Incompatible Kaitai Struct C++/STL API: version 0.7 or later is required"
#endif

/**
 * The WebSocket protocol establishes a two-way communication channel via TCP.
 * Messages are made up of one or more dataframes, and are delineated by
 * frames with the `fin` bit set.
 */

class websocket_t : public kaitai::kstruct {

public:
    class frame_header_t;
    class initial_frame_t;
    class dataframe_t;

    enum opcode_t {
        OPCODE_CONTINUATION = 0,
        OPCODE_TEXT = 1,
        OPCODE_BINARY = 2,
        OPCODE_RESERVED_3 = 3,
        OPCODE_RESERVED_4 = 4,
        OPCODE_RESERVED_5 = 5,
        OPCODE_RESERVED_6 = 6,
        OPCODE_RESERVED_7 = 7,
        OPCODE_CLOSE = 8,
        OPCODE_PING = 9,
        OPCODE_PONG = 10,
        OPCODE_RESERVED_CONTROL_B = 11,
        OPCODE_RESERVED_CONTROL_C = 12,
        OPCODE_RESERVED_CONTROL_D = 13,
        OPCODE_RESERVED_CONTROL_E = 14,
        OPCODE_RESERVED_CONTROL_F = 15
    };

    websocket_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, websocket_t* p__root = 0);

private:
    void _read();

public:
    ~websocket_t();

    class frame_header_t : public kaitai::kstruct {

    public:

        frame_header_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, websocket_t* p__root = 0);

    private:
        void _read();

    public:
        ~frame_header_t();

    private:
        bool f_len_payload;
        int32_t m_len_payload;

    public:
        int32_t len_payload();

    private:
        bool m_finished;
        uint64_t m_reserved;
        opcode_t m_opcode;
        bool m_is_masked;
        uint64_t m_len_payload_primary;
        uint16_t m_len_payload_extended_1;
        bool n_len_payload_extended_1;

    public:
        bool _is_null_len_payload_extended_1() { len_payload_extended_1(); return n_len_payload_extended_1; };

    private:
        uint32_t m_len_payload_extended_2;
        bool n_len_payload_extended_2;

    public:
        bool _is_null_len_payload_extended_2() { len_payload_extended_2(); return n_len_payload_extended_2; };

    private:
        uint32_t m_mask_key;
        bool n_mask_key;

    public:
        bool _is_null_mask_key() { mask_key(); return n_mask_key; };

    private:
        websocket_t* m__root;
        kaitai::kstruct* m__parent;

    public:
        bool finished() const { return m_finished; }
        uint64_t reserved() const { return m_reserved; }
        opcode_t opcode() const { return m_opcode; }
        bool is_masked() const { return m_is_masked; }
        uint64_t len_payload_primary() const { return m_len_payload_primary; }
        uint16_t len_payload_extended_1() const { return m_len_payload_extended_1; }
        uint32_t len_payload_extended_2() const { return m_len_payload_extended_2; }
        uint32_t mask_key() const { return m_mask_key; }
        websocket_t* _root() const { return m__root; }
        kaitai::kstruct* _parent() const { return m__parent; }
    };

    class initial_frame_t : public kaitai::kstruct {

    public:

        initial_frame_t(kaitai::kstream* p__io, websocket_t* p__parent = 0, websocket_t* p__root = 0);

    private:
        void _read();

    public:
        ~initial_frame_t();

    private:
        frame_header_t* m_header;
        std::string m_payload_bytes;
        bool n_payload_bytes;

    public:
        bool _is_null_payload_bytes() { payload_bytes(); return n_payload_bytes; };

    private:
        std::string m_payload_text;
        bool n_payload_text;

    public:
        bool _is_null_payload_text() { payload_text(); return n_payload_text; };

    private:
        websocket_t* m__root;
        websocket_t* m__parent;

    public:
        frame_header_t* header() const { return m_header; }
        std::string payload_bytes() const { return m_payload_bytes; }
        std::string payload_text() const { return m_payload_text; }
        websocket_t* _root() const { return m__root; }
        websocket_t* _parent() const { return m__parent; }
    };

    class dataframe_t : public kaitai::kstruct {

    public:

        dataframe_t(kaitai::kstream* p__io, websocket_t* p__parent = 0, websocket_t* p__root = 0);

    private:
        void _read();

    public:
        ~dataframe_t();

    private:
        frame_header_t* m_header;
        std::string m_payload_bytes;
        bool n_payload_bytes;

    public:
        bool _is_null_payload_bytes() { payload_bytes(); return n_payload_bytes; };

    private:
        std::string m_payload_text;
        bool n_payload_text;

    public:
        bool _is_null_payload_text() { payload_text(); return n_payload_text; };

    private:
        websocket_t* m__root;
        websocket_t* m__parent;

    public:
        frame_header_t* header() const { return m_header; }
        std::string payload_bytes() const { return m_payload_bytes; }
        std::string payload_text() const { return m_payload_text; }
        websocket_t* _root() const { return m__root; }
        websocket_t* _parent() const { return m__parent; }
    };

private:
    initial_frame_t* m_initial_frame;
    std::vector<dataframe_t*>* m_trailing_frames;
    bool n_trailing_frames;

public:
    bool _is_null_trailing_frames() { trailing_frames(); return n_trailing_frames; };

private:
    websocket_t* m__root;
    kaitai::kstruct* m__parent;

public:
    initial_frame_t* initial_frame() const { return m_initial_frame; }
    std::vector<dataframe_t*>* trailing_frames() const { return m_trailing_frames; }
    websocket_t* _root() const { return m__root; }
    kaitai::kstruct* _parent() const { return m__parent; }
};

#endif  // WEBSOCKET_H_

websocket.cpp

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "websocket.h"



websocket_t::websocket_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, websocket_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = this;
    _read();
}

void websocket_t::_read() {
    m_initial_frame = new initial_frame_t(m__io, this, m__root);
    n_trailing_frames = true;
    if (initial_frame()->header()->finished() != true) {
        n_trailing_frames = false;
        m_trailing_frames = new std::vector<dataframe_t*>();
        {
            int i = 0;
            dataframe_t* _;
            do {
                _ = new dataframe_t(m__io, this, m__root);
                m_trailing_frames->push_back(_);
                i++;
            } while (!(_->header()->finished()));
        }
    }
}

websocket_t::~websocket_t() {
    delete m_initial_frame;
    if (!n_trailing_frames) {
        for (std::vector<dataframe_t*>::iterator it = m_trailing_frames->begin(); it != m_trailing_frames->end(); ++it) {
            delete *it;
        }
        delete m_trailing_frames;
    }
}

websocket_t::frame_header_t::frame_header_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, websocket_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    f_len_payload = false;
    _read();
}

void websocket_t::frame_header_t::_read() {
    m_finished = m__io->read_bits_int(1);
    m_reserved = m__io->read_bits_int(3);
    m_opcode = static_cast<websocket_t::opcode_t>(m__io->read_bits_int(4));
    m_is_masked = m__io->read_bits_int(1);
    m_len_payload_primary = m__io->read_bits_int(7);
    m__io->align_to_byte();
    n_len_payload_extended_1 = true;
    if (len_payload_primary() == 126) {
        n_len_payload_extended_1 = false;
        m_len_payload_extended_1 = m__io->read_u2be();
    }
    n_len_payload_extended_2 = true;
    if (len_payload_primary() == 127) {
        n_len_payload_extended_2 = false;
        m_len_payload_extended_2 = m__io->read_u4be();
    }
    n_mask_key = true;
    if (is_masked()) {
        n_mask_key = false;
        m_mask_key = m__io->read_u4be();
    }
}

websocket_t::frame_header_t::~frame_header_t() {
    if (!n_len_payload_extended_1) {
    }
    if (!n_len_payload_extended_2) {
    }
    if (!n_mask_key) {
    }
}

int32_t websocket_t::frame_header_t::len_payload() {
    if (f_len_payload)
        return m_len_payload;
    m_len_payload = ((len_payload_primary() <= 125) ? (len_payload_primary()) : (((len_payload_primary() == 126) ? (len_payload_extended_1()) : (len_payload_extended_2()))));
    f_len_payload = true;
    return m_len_payload;
}

websocket_t::initial_frame_t::initial_frame_t(kaitai::kstream* p__io, websocket_t* p__parent, websocket_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void websocket_t::initial_frame_t::_read() {
    m_header = new frame_header_t(m__io, this, m__root);
    n_payload_bytes = true;
    if (header()->opcode() != OPCODE_TEXT) {
        n_payload_bytes = false;
        m_payload_bytes = m__io->read_bytes(header()->len_payload());
    }
    n_payload_text = true;
    if (header()->opcode() == OPCODE_TEXT) {
        n_payload_text = false;
        m_payload_text = kaitai::kstream::bytes_to_str(m__io->read_bytes(header()->len_payload()), std::string("UTF-8"));
    }
}

websocket_t::initial_frame_t::~initial_frame_t() {
    delete m_header;
    if (!n_payload_bytes) {
    }
    if (!n_payload_text) {
    }
}

websocket_t::dataframe_t::dataframe_t(kaitai::kstream* p__io, websocket_t* p__parent, websocket_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void websocket_t::dataframe_t::_read() {
    m_header = new frame_header_t(m__io, this, m__root);
    n_payload_bytes = true;
    if (_root()->initial_frame()->header()->opcode() != OPCODE_TEXT) {
        n_payload_bytes = false;
        m_payload_bytes = m__io->read_bytes(header()->len_payload());
    }
    n_payload_text = true;
    if (_root()->initial_frame()->header()->opcode() == OPCODE_TEXT) {
        n_payload_text = false;
        m_payload_text = kaitai::kstream::bytes_to_str(m__io->read_bytes(header()->len_payload()), std::string("UTF-8"));
    }
}

websocket_t::dataframe_t::~dataframe_t() {
    delete m_header;
    if (!n_payload_bytes) {
    }
    if (!n_payload_text) {
    }
}