WebSocket: C++11/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

Runtime library

All parsing code for C++11/STL generated by Kaitai Struct depends on the C++/STL runtime library. You have to install it before you can parse data.

For C++, the easiest way is to clone the runtime library sources and build them along with your project.

Code

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.bin", 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++11/STL source code to parse WebSocket

websocket.h

#pragma once

// 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 <memory>
#include <vector>

#if KAITAI_STRUCT_VERSION < 9000L
#error "Incompatible Kaitai Struct C++/STL API: version 0.9 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 = nullptr, websocket_t* p__root = nullptr);

private:
    void _read();
    void _clean_up();

public:
    ~websocket_t();

    class frame_header_t : public kaitai::kstruct {

    public:

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

    private:
        void _read();
        void _clean_up();

    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 = nullptr, websocket_t* p__root = nullptr);

    private:
        void _read();
        void _clean_up();

    public:
        ~initial_frame_t();

    private:
        std::unique_ptr<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.get(); }
        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 = nullptr, websocket_t* p__root = nullptr);

    private:
        void _read();
        void _clean_up();

    public:
        ~dataframe_t();

    private:
        std::unique_ptr<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.get(); }
        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:
    std::unique_ptr<initial_frame_t> m_initial_frame;
    std::unique_ptr<std::vector<std::unique_ptr<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.get(); }
    std::vector<std::unique_ptr<dataframe_t>>* trailing_frames() const { return m_trailing_frames.get(); }
    websocket_t* _root() const { return m__root; }
    kaitai::kstruct* _parent() const { return m__parent; }
};

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;
    m_initial_frame = nullptr;
    m_trailing_frames = nullptr;
    _read();
}

void websocket_t::_read() {
    m_initial_frame = std::unique_ptr<initial_frame_t>(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 = std::unique_ptr<std::vector<std::unique_ptr<dataframe_t>>>(new std::vector<std::unique_ptr<dataframe_t>>());
        {
            int i = 0;
            dataframe_t* _;
            do {
                _ = new dataframe_t(m__io, this, m__root);
                m_trailing_frames->push_back(std::move(std::unique_ptr<dataframe_t>(_)));
                i++;
            } while (!(_->header()->finished()));
        }
    }
}

websocket_t::~websocket_t() {
    _clean_up();
}

void websocket_t::_clean_up() {
    if (!n_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_be(1);
    m_reserved = m__io->read_bits_int_be(3);
    m_opcode = static_cast<websocket_t::opcode_t>(m__io->read_bits_int_be(4));
    m_is_masked = m__io->read_bits_int_be(1);
    m_len_payload_primary = m__io->read_bits_int_be(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() {
    _clean_up();
}

void websocket_t::frame_header_t::_clean_up() {
    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;
    m_header = nullptr;
    _read();
}

void websocket_t::initial_frame_t::_read() {
    m_header = std::unique_ptr<frame_header_t>(new frame_header_t(m__io, this, m__root));
    n_payload_bytes = true;
    if (header()->opcode() != websocket_t::OPCODE_TEXT) {
        n_payload_bytes = false;
        m_payload_bytes = m__io->read_bytes(header()->len_payload());
    }
    n_payload_text = true;
    if (header()->opcode() == websocket_t::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() {
    _clean_up();
}

void websocket_t::initial_frame_t::_clean_up() {
    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;
    m_header = nullptr;
    _read();
}

void websocket_t::dataframe_t::_read() {
    m_header = std::unique_ptr<frame_header_t>(new frame_header_t(m__io, this, m__root));
    n_payload_bytes = true;
    if (_root()->initial_frame()->header()->opcode() != websocket_t::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() == websocket_t::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() {
    _clean_up();
}

void websocket_t::dataframe_t::_clean_up() {
    if (!n_payload_bytes) {
    }
    if (!n_payload_text) {
    }
}