1
0
mirror of https://github.com/VCMP-SqMod/SqMod.git synced 2024-11-08 08:47:17 +01:00
SqMod/vendor/DPP/include/dpp/etf.h
Sandu Liviu Catalin cbd8f8b028 Add D++ library.
2023-03-23 20:20:44 +02:00

643 lines
18 KiB
C++

/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Parts of this file inspired by, or outright copied from erlpack:
* https://github.com/discord/erlpack/
*
* Acknowledgements:
*
* sysdep.h:
* Based on work by FURUHASHI Sadayuki in msgpack-python
* (https://github.com/msgpack/msgpack-python)
*
* Copyright (C) 2008-2010 FURUHASHI Sadayuki
* Licensed under the Apache License, Version 2.0 (the "License").
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/json_fwd.h>
namespace dpp {
/** Current ETF format version in use */
const uint8_t FORMAT_VERSION = 131;
/**
* @brief Represents a token which identifies the type of value which follows it
* in the ETF binary structure.
*/
enum etf_token_type : uint8_t {
/// 68 [Distribution header]
ett_distribution = 'D',
/// 70 [Float64:IEEE float]
ett_new_float = 'F',
/// 77 [UInt32:Len, UInt8:Bits, Len:Data]
ett_bit_binary = 'M',
/// 80 [UInt4:UncompressedSize, N:ZlibCompressedData]
ett_compressed = 'P',
/// 97 [UInt8:Int]
ett_smallint = 'a',
/// 98 [Int32:Int]
ett_integer = 'b',
/// 99 [31:Float String] Float in string format (formatted "%.20e", sscanf "%lf"). Superseded by ett_new_float
ett_float = 'c',
/// 100 [UInt16:Len, Len:AtomName] max Len is 255
ett_atom = 'd',
/// 101 [atom:Node, UInt32:ID, UInt8:Creation]
ett_reference = 'e',
/// 102 [atom:Node, UInt32:ID, UInt8:Creation]
ett_port = 'f',
/// 103 [atom:Node, UInt32:ID, UInt32:Serial, UInt8:Creation]
ett_pid = 'g',
/// 104 [UInt8:Arity, N:Elements]
ett_small_tuple = 'h',
/// 105 [UInt32:Arity, N:Elements]
ett_large_tuple = 'i',
/// 106 empty list
ett_nil = 'j',
/// 107 [UInt16:Len, Len:Characters]
ett_string = 'k',
/// 108 [UInt32:Len, Elements, Tail]
ett_list = 'l',
/// 109 [UInt32:Len, Len:Data]
ett_binary = 'm',
/// 110 [UInt8:n, UInt8:Sign, n:nums]
ett_bigint_small = 'n',
/// 111 [UInt32:n, UInt8:Sign, n:nums]
ett_bigint_large = 'o',
/// 112 [UInt32:Size, UInt8:Arity, 16*Uint6-MD5:Uniq, UInt32:Index, UInt32:NumFree, atom:Module, int:OldIndex, int:OldUniq, pid:Pid, NunFree*ext:FreeVars]
ett_new_function = 'p',
/// 113 [atom:Module, atom:Function, smallint:Arity]
ett_export = 'q',
/// 114 [UInt16:Len, atom:Node, UInt8:Creation, Len*UInt32:ID]
ett_new_reference = 'r',
/// 115 [UInt8:Len, Len:AtomName]
ett_atom_small = 's',
/// 116 [UInt32:Airty, N:Pairs]
ett_map = 't',
/// 117 [UInt4:NumFree, pid:Pid, atom:Module, int:Index, int:Uniq, NumFree*ext:FreeVars]
ett_function = 'u',
/// 118 [UInt16:Len, Len:AtomName] max Len is 255 characters (up to 4 bytes per)
ett_atom_utf8 = 'v',
/// 119 [UInt8:Len, Len:AtomName]
ett_atom_utf8_small = 'w'
};
/**
* @brief A horrible structure used within the ETF parser to convert uint64_t to double and back.
* This is horrible, but it is the official way erlang term format does this, so we can't really
* mess with it much.
*/
union type_punner {
/**
* @brief binary integer value
*/
uint64_t ui64;
/**
* @brief double floating point value
*/
double df;
};
/**
* @brief Represents a buffer of bytes being encoded into ETF
*/
struct DPP_EXPORT etf_buffer {
/**
* @brief Raw buffer
*/
std::vector<char> buf;
/**
* @brief Current used length of buffer
* (this is different from buf.size() as it is pre-allocated
* using resize and may not all be in use)
*/
size_t length;
/**
* @brief Construct a new etf buffer object
*
* @param initial initial buffer size to allocate
*/
etf_buffer(size_t initial);
/**
* @brief Destroy the etf buffer object
*/
~etf_buffer();
};
/**
* @brief The etf_parser class can serialise and deserialise ETF (Erlang Term Format)
* into and out of an nlohmann::json object, so that layers above the websocket don't
* have to be any different for handling ETF.
*/
class DPP_EXPORT etf_parser {
/**
* @brief Current size of binary data
*/
size_t size;
/**
* @brief Current offset into binary data
*/
size_t offset;
/**
* @brief Pointer to binary ETF data to be decoded
*/
uint8_t* data;
/**
* @brief Parse a single value, and if that value contains other
* values (e.g. an array or map) then call itself recursively.
*
* @return nlohmann::json JSON value from the ETF
*/
nlohmann::json inner_parse();
/**
* @brief Read 8 bits of data from the buffer
*
* @return uint8_t data retrieved
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
uint8_t read_8_bits();
/**
* @brief Read 16 bits of data from the buffer
*
* @return uint16_t data retrieved
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
uint16_t read_16_bits();
/**
* @brief Read 32 bits of data from the buffer
*
* @return uint32_t data retrieved
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
uint32_t read_32_bits();
/**
* @brief Read 64 bits of data from the buffer
*
* @return uint64_t data retrieved
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
uint64_t read_64_bits();
/**
* @brief Read string data from the buffer
*
* @param length Length of string to retrieve
* @return const char* data retrieved
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
const char* read_string(uint32_t length);
/**
* @brief Process an 'atom' value.
* An atom is a "label" or constant value within the data,
* such as a key name, nullptr, or false.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json process_atom(const char* atom, uint16_t length);
/**
* @brief Decode an 'atom' value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_atom();
/**
* @brief Decode a small 'atom' value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_small_atom();
/**
* @brief Decode a small integer value (0-255).
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_small_integer();
/**
* @brief Decode an integer value (-MAXINT -> MAXINT-1).
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_integer();
/**
* @brief Decode an array of values.
*
* @return nlohmann::json values converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_array(uint32_t length);
/**
* @brief Decode a list of values.
*
* @return nlohmann::json values converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_list();
/**
* @brief Decode a 'tuple' value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_tuple(uint32_t length);
/**
* @brief Decode a nil 'atom' value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_nil();
/**
* @brief Decode a map (object) value.
* Will recurse to evaluate each member variable.
*
* @return nlohmann::json values converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_map();
/**
* @brief Decode a floating point numeric value.
* (depreciated in erlang but still expected to be supported)
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_float();
/**
* @brief Decode a floating type numeric value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_new_float();
/**
* @brief Decode a 'bigint' value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_bigint(uint32_t digits);
/**
* @brief Decode a small 'bigint' value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_bigint_small();
/**
* @brief Decode a large 'bigint' value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_bigint_large();
/**
* @brief Decode a binary value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_binary();
/**
* @brief Decode a string value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_string();
/**
* @brief Decode a string list value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_string_as_list();
/**
* @brief Decode a 'small tuple' value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_tuple_small();
/**
* @brief Decode a 'large tuple' value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_tuple_large();
/**
* @brief Decode a compressed value.
* This is a zlib-compressed binary blob which contains another
* ETF object.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_compressed();
/**
* @brief Decode a 'reference' value.
* Erlang expects this to be supported, in practice Discord doesn't send these right now.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_reference();
/**
* @brief Decode a 'new reference' value.
* Erlang expects this to be supported, in practice Discord doesn't send these right now.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_new_reference();
/**
* @brief Decode a 'port' value.
* Erlang expects this to be supported, in practice Discord doesn't send these right now.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_port();
/**
* @brief Decode a 'PID' value.
* Erlang expects this to be supported, in practice Discord doesn't send these right now.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_pid();
/**
* @brief Decode an 'export' value.
* Erlang expects this to be supported, in practice Discord doesn't send these right now.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_export();
/**
* @brief Write to output buffer for creation of ETF from JSON
*
* @param pk buffer struct
* @param bytes byte buffer to write
* @param l number of bytes to write
* @throw std::exception Buffer cannot be extended
*/
void buffer_write(etf_buffer *pk, const char *bytes, size_t l);
/**
* @brief Append version number to ETF buffer
*
* @param b buffer to append to
* @throw std::exception Buffer cannot be extended
*/
void append_version(etf_buffer *b);
/**
* @brief Append nil value to ETF buffer
*
* @param b buffer to append to
* @throw std::exception Buffer cannot be extended
*/
void append_nil(etf_buffer *b);
/**
* @brief Append false value to ETF buffer
*
* @param b buffer to append to
* @throw std::exception Buffer cannot be extended
*/
void append_false(etf_buffer *b);
/**
* @brief Append true value to ETF buffer
*
* @param b buffer to append to
* @throw std::exception Buffer cannot be extended
*/
void append_true(etf_buffer *b);
/**
* @brief Append small integer value to ETF buffer
*
* @param b buffer to append to
* @param d double to append
* @throw std::exception Buffer cannot be extended
*/
void append_small_integer(etf_buffer *b, unsigned char d);
/**
* @brief Append integer value to ETF buffer
*
* @param b buffer to append to
* @param d integer to append
* @throw std::exception Buffer cannot be extended
*/
void append_integer(etf_buffer *b, int32_t d);
/**
* @brief Append 64 bit integer value to ETF buffer
*
* @param b buffer to append to
* @param d integer to append
* @throw std::exception Buffer cannot be extended
*/
void append_unsigned_long_long(etf_buffer *b, unsigned long long d);
/**
* @brief Append 64 bit integer value to ETF buffer
*
* @param b buffer to append to
* @param d integer to append
* @throw std::exception Buffer cannot be extended
*/
void append_long_long(etf_buffer *b, long long d);
/**
* @brief Append double value to ETF buffer
*
* @param b buffer to append to
* @param f double to append
* @throw std::exception Buffer cannot be extended
*/
void append_double(etf_buffer *b, double f);
/**
* @brief Append atom value to ETF buffer
*
* @param b buffer to append to
* @param bytes pointer to string to append
* @param size size of string to append
* @throw std::exception Buffer cannot be extended
*/
void append_atom(etf_buffer *b, const char *bytes, size_t size);
/**
* @brief Append utf8 atom value to ETF buffer
*
* @param b buffer to append to
* @param bytes pointer to string to append
* @param size size of string to append
* @throw std::exception Buffer cannot be extended
*/
void append_atom_utf8(etf_buffer *b, const char *bytes, size_t size);
/**
* @brief Append binary value to ETF buffer
*
* @param b buffer to append to
* @param bytes pointer to string to append
* @param size size of string to append
* @throw std::exception Buffer cannot be extended
*/
void append_binary(etf_buffer *b, const char *bytes, size_t size);
/**
* @brief Append string value to ETF buffer
*
* @param b buffer to append to
* @param bytes pointer to string to append
* @param size size of string to append
* @throw std::exception Buffer cannot be extended
*/
void append_string(etf_buffer *b, const char *bytes, size_t size);
/**
* @brief Append tuple value to ETF buffer
*
* @param b buffer to append to
* @param size size of value to append
* @throw std::exception Buffer cannot be extended
*/
void append_tuple_header(etf_buffer *b, size_t size);
/**
* @brief Append list terminator to ETF buffer
*
* @param b buffer to append to
* @throw std::exception Buffer cannot be extended
*/
void append_nil_ext(etf_buffer *b);
/**
* @brief Append a list header value to ETF buffer
*
* @param b buffer to append to
* @param size size of values to append
* @throw std::exception Buffer cannot be extended
*/
void append_list_header(etf_buffer *b, size_t size);
/**
* @brief Append a map header value to ETF buffer
*
* @param b buffer to append to
* @param size size of values to append
* @throw std::exception Buffer cannot be extended
*/
void append_map_header(etf_buffer *b, size_t size);
/**
* @brief Build ETF buffer
*
* @param j JSON object to build from
* @param b Buffer to append to
* @throw std::exception Buffer cannot be extended
*/
void inner_build(const nlohmann::json* j, etf_buffer* b);
public:
/**
* @brief Construct a new etf parser object
*/
etf_parser();
/**
* @brief Destroy the etf parser object
*/
~etf_parser();
/**
* @brief Convert ETF binary content to nlohmann::json
*
* @param in Raw binary ETF data (generally from a websocket)
* @return nlohmann::json JSON data for use in the library
* @throw dpp::exception Malformed or otherwise invalid ETF content
*/
nlohmann::json parse(const std::string& in);
/**
* @brief Create ETF binary data from nlohmann::json
*
* @param j JSON value to encode to ETF
* @return std::string raw ETF data. Note that this can
* and probably will contain null values, use std::string::data()
* and std::string::size() to manipulate or send it.
* @throw std::exception Not enough memory, or invalid data types/values
*/
std::string build(const nlohmann::json& j);
};
};