move to new cspot

This commit is contained in:
philippe44
2023-03-25 16:48:41 -07:00
parent c712b78931
commit 008c36facf
2983 changed files with 465270 additions and 13569 deletions

View File

@@ -0,0 +1,232 @@
#include "BellHTTPServer.h"
#include <mutex>
#include <regex>
#include "CivetServer.h"
#include "civetweb.h"
using namespace bell;
class WebSocketHandler : public CivetWebSocketHandler {
public:
BellHTTPServer::WSDataHandler dataHandler;
BellHTTPServer::WSStateHandler stateHandler;
WebSocketHandler(BellHTTPServer::WSDataHandler dataHandler,
BellHTTPServer::WSStateHandler stateHandler) {
this->dataHandler = dataHandler;
this->stateHandler = stateHandler;
}
virtual bool handleConnection(CivetServer* server,
struct mg_connection* conn) {
this->stateHandler(conn, BellHTTPServer::WSState::CONNECTED);
return true;
}
virtual void handleReadyState(CivetServer* server,
struct mg_connection* conn) {
this->stateHandler(conn, BellHTTPServer::WSState::READY);
}
virtual bool handleData(CivetServer* server, struct mg_connection* conn,
int bits, char* data, size_t data_len) {
this->dataHandler(conn, data, data_len);
return true;
}
virtual void handleClose(CivetServer* server, struct mg_connection* conn) {
stateHandler(conn, BellHTTPServer::WSState::CLOSED);
}
};
std::vector<std::string> BellHTTPServer::Router::split(
const std::string str, const std::string regex_str) {
std::regex regexz(regex_str);
return {std::sregex_token_iterator(str.begin(), str.end(), regexz, -1),
std::sregex_token_iterator()};
}
void BellHTTPServer::Router::insert(const std::string& route,
HTTPHandler& value) {
auto parts = split(route, "/");
auto currentNode = &root;
for (int index = 0; index < parts.size(); index++) {
auto part = parts[index];
if (part[0] == ':') {
currentNode->isParam = true;
currentNode->paramName = part.substr(1);
part = "";
} else if (part[0] == '*') {
currentNode->isCatchAll = true;
currentNode->value = value;
return;
}
if (!currentNode->children.count(part)) {
currentNode->children[part] = std::make_unique<RouterNode>();
}
currentNode = currentNode->children[part].get();
}
currentNode->value = value;
}
BellHTTPServer::Router::HandlerAndParams BellHTTPServer::Router::find(
const std::string& route) {
auto parts = split(route, "/");
auto currentNode = &root;
std::unordered_map<std::string, std::string> params;
for (int index = 0; index < parts.size(); index++) {
auto part = parts[index];
if (currentNode->children.count(part)) {
currentNode = currentNode->children[part].get();
} else if (currentNode->isParam) {
params[currentNode->paramName] = part;
if (currentNode->children.count("")) {
currentNode = currentNode->children[""].get();
} else {
return {nullptr, Params()};
}
} else if (currentNode->isCatchAll) {
params["**"] = '*';
return {currentNode->value, params};
} else {
return {nullptr, Params()};
}
}
if (currentNode->value != nullptr) {
return {currentNode->value, params};
}
return {nullptr, Params()};
}
bool BellHTTPServer::handleGet(CivetServer* server,
struct mg_connection* conn) {
std::scoped_lock lock(this->responseMutex);
auto requestInfo = mg_get_request_info(conn);
auto handler = getRequestsRouter.find(requestInfo->local_uri);
if (handler.first == nullptr) {
if (this->notFoundHandler != nullptr) {
this->notFoundHandler(conn);
return true;
}
return false;
}
mg_set_user_connection_data(conn, &handler.second);
try {
auto reply = handler.first(conn);
if (reply->body == nullptr) {
return true;
}
mg_printf(
conn,
"HTTP/1.1 %d OK\r\nContent-Type: "
"%s\r\nAccess-Control-Allow-Origin: *\r\nConnection: close\r\n\r\n",
reply->status, reply->headers["Content-Type"].c_str());
mg_write(conn, reply->body, reply->bodySize);
return true;
} catch (std::exception& e) {
BELL_LOG(error, "HttpServer", "Exception occured in handler: %s", e.what());
return false;
}
}
bool BellHTTPServer::handlePost(CivetServer* server,
struct mg_connection* conn) {
std::scoped_lock lock(this->responseMutex);
auto requestInfo = mg_get_request_info(conn);
auto handler = postRequestsRouter.find(requestInfo->local_uri);
if (handler.first == nullptr) {
return false;
}
mg_set_user_connection_data(conn, &handler.second);
try {
auto reply = handler.first(conn);
if (reply->body == nullptr) {
return true;
}
mg_printf(
conn,
"HTTP/1.1 %d OK\r\nContent-Type: "
"%s\r\nAccess-Control-Allow-Origin: *\r\nConnection: close\r\n\r\n",
reply->status, reply->headers["Content-Type"].c_str());
mg_write(conn, reply->body, reply->bodySize);
return true;
} catch (std::exception& e) {
BELL_LOG(error, "HttpServer", "Exception occured in handler: %s", e.what());
return false;
}
}
BellHTTPServer::BellHTTPServer(int serverPort) {
mg_init_library(0);
BELL_LOG(info, "HttpServer", "Server listening on port %d", serverPort);
this->serverPort = serverPort;
auto port = std::to_string(this->serverPort);
const char* options[] = {"listening_ports", port.c_str(), 0};
server = std::make_unique<CivetServer>(options);
}
std::unique_ptr<BellHTTPServer::HTTPResponse> BellHTTPServer::makeJsonResponse(
const std::string& json, int status) {
auto response = std::make_unique<BellHTTPServer::HTTPResponse>();
response->body = (uint8_t*)malloc(json.size());
response->bodySize = json.size();
response->headers["Content-Type"] = "application/json";
response->status = status;
memcpy(response->body, json.c_str(), json.size());
return response;
}
std::unique_ptr<BellHTTPServer::HTTPResponse> BellHTTPServer::makeEmptyResponse() {
auto response = std::make_unique<BellHTTPServer::HTTPResponse>();
return response;
}
void BellHTTPServer::registerGet(const std::string& url,
BellHTTPServer::HTTPHandler handler) {
server->addHandler(url, this);
getRequestsRouter.insert(url, handler);
}
void BellHTTPServer::registerPost(const std::string& url,
BellHTTPServer::HTTPHandler handler) {
server->addHandler(url, this);
postRequestsRouter.insert(url, handler);
}
void BellHTTPServer::registerWS(const std::string& url,
BellHTTPServer::WSDataHandler dataHandler,
BellHTTPServer::WSStateHandler stateHandler) {
server->addWebSocketHandler(url,
new WebSocketHandler(dataHandler, stateHandler));
}
void BellHTTPServer::registerNotFound(HTTPHandler handler) {
this->notFoundHandler = handler;
}
std::unordered_map<std::string, std::string> BellHTTPServer::extractParams(
struct mg_connection* conn) {
std::unordered_map<std::string, std::string>& params =
*(std::unordered_map<std::string, std::string>*)
mg_get_user_connection_data(conn);
return params;
}

View File

@@ -0,0 +1,325 @@
#include "BellTar.h"
#include <sys/stat.h>
#include <fstream>
using namespace bell::BellTar;
#include <cassert>
#include <cstdio> // for sprintf, snprintf and sscanf
#include <cstdlib> // for rand
#include <cstring> // for strlen and memset
#include <ctime> // for time
#ifdef _WIN32
#include <direct.h>
#endif
#ifdef ENABLE_LOGGING
#define LOG printf
#else
#ifdef _WIN32
#define LOG(fmt, ...) ((void)0)
#else
#define LOG(fmt, args...) ((void)0)
#endif
#endif
const char FILL_CHAR = '\0';
const int FILE_NAME_LENGTH = 100;
// From http://en.wikipedia.org/wiki/Tar_(computing)#UStar_format
typedef enum tar_file_type {
tar_file_type_normal = '0',
tar_file_type_hard_link = '1',
tar_file_type_soft_link = '2',
tar_file_type_directory = '5'
} tar_file_type_t;
struct tar_header {
char name[FILE_NAME_LENGTH]; // file name
char mode[8]; // file mode
char uid[8]; // Owner's numeric user ID
char gid[8]; // Group's numeric user ID
char size[12]; // File size in bytes (octal base)
char mtime[12]; // Last modification time in
// numeric Unix time format (octal)
char checksum[8]; // Checksum for header record
char typeflag[1]; // file type, see tar_file_type_t
char linkname[100]; // Name of linked file
char magic[6]; // UStar indicator "ustar"
char version[2]; // UStar version "00"
char uname[32]; // Owner user name
char gname[32]; // Owner group name
char devmajor[8]; // Device major number
char devminor[8]; // Device minor number
char prefix[155]; // Filename prefix
char pad[12]; // padding
};
void header_set_metadata(tar_header* header) {
std::memset(header, 0, sizeof(tar_header));
std::sprintf(header->magic, "ustar");
std::sprintf(header->mtime, "%011lo", (unsigned long) std::time(NULL));
std::sprintf(header->mode, "%07o", 0644);
std::sprintf(header->uname, "unkown"); // ... a bit random
std::sprintf(header->gname, "users");
header->typeflag[0] = 0; // always just a normal file
}
/* From Wikipedia: The checksum is calculated by taking the sum of the
* unsigned byte values of the header record with the eight checksum
* bytes taken to be ascii spaces. */
void header_set_checksum(tar_header* header) {
unsigned int sum = 0;
char* pointer = (char*)header;
char* end = pointer + sizeof(tar_header);
// Iterate over header struct until we are at checksum field.
while (pointer < header->checksum) {
sum += *pointer & 0xff;
pointer++;
}
// ... then add eight 'ascii spaces' ...
sum += ' ' * 8;
pointer += 8;
// ... and go until the end.
while (pointer < end) {
sum += *pointer & 0xff;
pointer++;
}
std::sprintf(header->checksum, "%06o", sum);
}
void header_set_filetype(tar_header* header, tar_file_type_t file_type) {
header->typeflag[0] = file_type;
}
tar_file_type_t header_get_filetype(tar_header* header) {
return tar_file_type_t(header->typeflag[0]);
}
void header_set_filesize(tar_header* header, file_size_t file_size) {
std::sprintf(header->size, "%011llo", file_size);
}
file_size_t header_get_filesize(tar_header* header) {
file_size_t file_size;
std::sscanf(header->size, "%011llo", &file_size);
return file_size;
}
void header_set_filename(tar_header* header, const char* file_name) {
size_t len = std::strlen(file_name);
// len > 0 also ensures that the header does not start with \0
if (len == 0 || len >= FILE_NAME_LENGTH) {
LOG("Invalid file name for tar: %s\n", file_name);
std::sprintf(header->name, "INVALID_%d", std::rand());
} else {
std::sprintf(header->name, "%s", file_name);
}
}
std::string header_get_filename(tar_header* header) {
return std::string(header->name);
}
////////////////////////////////////////
/* Every file in a tar file starts with the tar header */
void _write_header(std::ostream& dst, const char* file_name,
file_size_t file_size,
tar_file_type_t file_type = tar_file_type_normal) {
tar_header header;
header_set_metadata(&header);
header_set_filename(&header, file_name);
header_set_filesize(&header, file_size);
header_set_filetype(&header, file_type);
header_set_checksum(&header);
dst.write((const char*)&header, sizeof(tar_header));
}
void _read_header(std::istream& inp, tar_header* header) {
inp.read((char*)header, sizeof(tar_header));
}
/* The length of the data after the header must be rounded up to a
multiple of 512 bytes, the length of the header. */
void _fill(std::ostream& dst, unsigned long file_size) {
while (file_size % sizeof(tar_header) != 0) {
dst.put(FILL_CHAR);
file_size++;
}
}
bool _check_if_header_is_next(std::istream& inp) {
if (inp.eof() || inp.peek() == EOF) {
LOG("Can not read next file info, istream at EOF.\n");
return false;
}
if (inp.peek() == FILL_CHAR) {
LOG("Can not read next file info, istream is pointing "
"to %d, which a tar header can not start with.\n",
FILL_CHAR);
return false;
}
return true;
}
void _seek_to_next_header(std::istream& inp) {
// Advance to start of next header or to end of file
// Works because
// - header never starts with FILL_CHAR
// - at end of file, peek() returns EOF.
// - FILL_CHAR != EOF
while (inp.peek() == FILL_CHAR)
inp.get();
}
////////////////////////////////////////
// writer Implementation
////////////////////////////////////////
void writer::put(std::string path_in_tar, char const* const data,
const file_size_t data_size) {
_write_header(_dst, path_in_tar.c_str(), data_size);
_dst.write(data, data_size);
_fill(_dst, data_size);
}
void writer::put_directory(std::string path_in_tar) {
_write_header(_dst, path_in_tar.c_str(), 0, tar_file_type_directory);
}
/* The end of an tar is marked by at least two consecutive zero-filled
* records, a record having the size of the header. */
void writer::finish() {
unsigned long i = 0;
while (i < 2 * sizeof(tar_header)) {
_dst.put(FILL_CHAR);
i++;
}
}
////////////////////////////////////////
// reader Implementation
////////////////////////////////////////
bool reader::contains_another_file() {
return _check_if_header_is_next(_inp);
}
void reader::_cache_header() {
if (_cached_header_data_valid)
return;
assert(contains_another_file());
tar_header h;
_read_header(_inp, &h);
_cached_header_data.file_name = header_get_filename(&h);
_cached_header_data.file_size = header_get_filesize(&h);
_cached_header_data.file_type = h.typeflag[0];
_cached_header_data_valid = true;
}
std::string reader::get_next_file_name() {
_cache_header();
return _cached_header_data.file_name;
}
file_size_t reader::get_next_file_size() {
_cache_header();
return _cached_header_data.file_size;
}
void reader::read_next_file(char* const data) {
_inp.read(data, get_next_file_size());
_cached_header_data_valid = false;
_seek_to_next_header(_inp);
}
void reader::skip_next_file() {
_inp.seekg(get_next_file_size(), std::ios::cur);
_cached_header_data_valid = false;
_seek_to_next_header(_inp);
}
char reader::get_next_file_type() {
_cache_header();
return _cached_header_data.file_type;
}
int reader::number_of_files() {
if (_number_of_files == -1) {
std::streampos current_position = _inp.tellg();
_inp.seekg(0, std::ios::beg);
_number_of_files = 0;
while (contains_another_file()) {
_number_of_files++;
skip_next_file();
}
_inp.seekg(current_position);
}
return _number_of_files;
}
void reader::extract_all_files(std::string dest_directory) {
std::vector<uint8_t> scratch_buffer(1024);
while (contains_another_file()) {
char fileType = get_next_file_type();
auto fileName = get_next_file_name();
// 0 is the normal file type, skip apple's ._ files
if (fileType == '0' && !fileName.starts_with("._")) {
std::string path = dest_directory + "/" + fileName;
size_t pos = 0;
while ((pos = path.find('/', pos)) != std::string::npos) {
std::string dir = path.substr(0, pos);
// Create the directory if it doesn't exist
#ifdef _WIN32
mkdir(dir.c_str());
#else
mkdir(dir.c_str(), 0777);
#endif
pos++;
}
std::ofstream out(path, std::ios::binary);
size_t read_size = 0;
size_t file_size = get_next_file_size();
while (read_size < file_size) {
size_t to_read = std::min(file_size - read_size, scratch_buffer.size());
_inp.read((char*)scratch_buffer.data(), to_read);
// Move the read size forward
read_size += _inp.gcount();
// Write the data to the destination file
out.write((char*)scratch_buffer.data(), _inp.gcount());
}
_cached_header_data_valid = false;
_seek_to_next_header(_inp);
} else {
skip_next_file();
}
}
}

View File

@@ -0,0 +1,72 @@
#include "BinaryReader.h"
#include <stdlib.h>
bell::BinaryReader::BinaryReader(std::shared_ptr<ByteStream> stream) {
this->stream = stream;
}
size_t bell::BinaryReader::position() {
return stream->position();
}
size_t bell::BinaryReader::size() {
return stream->size();
}
void bell::BinaryReader::close() {
stream->close();
}
void bell::BinaryReader::skip(size_t pos) {
std::vector<uint8_t> b(pos);
stream->read(&b[0], pos);
}
int32_t bell::BinaryReader::readInt() {
uint8_t b[4];
if (stream->read((uint8_t *) b,4) != 4)
return 0;
return static_cast<int32_t>(
(b[3]) |
(b[2] << 8) |
(b[1] << 16)|
(b[0] << 24) );
}
int16_t bell::BinaryReader::readShort() {
uint8_t b[2];
if (stream->read((uint8_t *) b,2) != 2)
return 0;
return static_cast<int16_t>(
(b[1]) |
(b[0] << 8));
}
uint32_t bell::BinaryReader::readUInt() {
return readInt() & 0xffffffffL;
}
uint8_t bell::BinaryReader::readByte() {
uint8_t b[1];
if (stream->read((uint8_t *) b,1) != 1)
return 0;
return b[0];
}
std::vector<uint8_t> bell::BinaryReader::readBytes(size_t size) {
std::vector<uint8_t> data(size);
stream->read(&data[0], size);
return data;
}
long long bell::BinaryReader::readLong() {
long high = readInt();
long low = readInt();
return static_cast<long long>(
((long long) high << 32) | low );
}

View File

@@ -0,0 +1,152 @@
#include <BinaryStream.h>
#include <sstream>
using namespace bell;
BinaryStream::BinaryStream(std::ostream* ostr) {
this->ostr = ostr;
byteOrder = std::endian::native;
}
BinaryStream::BinaryStream(std::istream* istr) {
this->istr = istr;
byteOrder = std::endian::native;
}
void BinaryStream::setByteOrder(std::endian byteOrder) {
this->byteOrder = byteOrder;
flipBytes = byteOrder != std::endian::native;
}
void BinaryStream::ensureReadable() {
if (istr == nullptr)
throw std::runtime_error("No input provided for binary stream");
}
void BinaryStream::ensureWritable() {
if (ostr == nullptr)
throw std::runtime_error("No output provided for binary stream");
}
BinaryStream& BinaryStream::operator>>(int16_t& value) {
ensureReadable();
istr->read((char*)&value, sizeof(value));
if (flipBytes)
value = swap16(value);
return *this;
}
BinaryStream& BinaryStream::operator>>(uint16_t& value) {
ensureReadable();
istr->read((char*)&value, sizeof(value));
if (flipBytes)
swap16(value);
return *this;
}
BinaryStream& BinaryStream::operator>>(int32_t& value) {
ensureReadable();
istr->read((char*)&value, sizeof(value));
if (flipBytes)
value = swap32(value);
return *this;
}
BinaryStream& BinaryStream::operator>>(uint32_t& value) {
ensureReadable();
istr->read((char*)&value, sizeof(value));
if (flipBytes)
value = swap32(value);
return *this;
}
BinaryStream& BinaryStream::operator>>(int64_t& value) {
ensureReadable();
istr->read((char*)&value, sizeof(value));
if (flipBytes)
value = swap64(value);
return *this;
}
BinaryStream& BinaryStream::operator>>(uint64_t& value) {
ensureReadable();
istr->read((char*)&value, sizeof(value));
if (flipBytes)
value = swap64(value);
return *this;
}
BinaryStream& BinaryStream::operator<<(char value) {
ensureWritable();
ostr->write((const char*)&value, sizeof(value));
return *this;
}
BinaryStream& BinaryStream::operator<<(std::byte value) {
ensureWritable();
ostr->write((const char*)&value, sizeof(value));
return *this;
}
BinaryStream& BinaryStream::operator<<(int16_t value) {
ensureWritable();
if (flipBytes)
value = swap16(value);
ostr->write((const char*)&value, sizeof(value));
return *this;
}
BinaryStream& BinaryStream::operator<<(uint16_t value) {
ensureWritable();
if (flipBytes)
value = swap16(value);
ostr->write((const char*)&value, sizeof(value));
return *this;
}
BinaryStream& BinaryStream::operator<<(int32_t value) {
ensureWritable();
if (flipBytes)
value = swap32(value);
ostr->write((const char*)&value, sizeof(value));
return *this;
}
BinaryStream& BinaryStream::operator<<(uint32_t value) {
ensureWritable();
if (flipBytes)
value = swap32(value);
ostr->write((const char*)&value, sizeof(value));
return *this;
}
BinaryStream& BinaryStream::operator<<(int64_t value) {
ensureWritable();
if (flipBytes)
value = swap64(value);
ostr->write((const char*)&value, sizeof(value));
return *this;
}
BinaryStream& BinaryStream::operator<<(uint64_t value) {
ensureWritable();
if (flipBytes)
value = swap64(value);
ostr->write((const char*)&value, sizeof(value));
return *this;
}

View File

@@ -0,0 +1,172 @@
#include "BufferedStream.h"
#include <cstring>
BufferedStream::BufferedStream(
const std::string &taskName,
uint32_t bufferSize,
uint32_t readThreshold,
uint32_t readSize,
uint32_t readyThreshold,
uint32_t notReadyThreshold,
bool waitForReady)
: bell::Task(taskName, 4096, 5, 0) {
this->bufferSize = bufferSize;
this->readAt = bufferSize - readThreshold;
this->readSize = readSize;
this->readyThreshold = readyThreshold;
this->notReadyThreshold = notReadyThreshold;
this->waitForReady = waitForReady;
this->buf = static_cast<uint8_t *>(malloc(bufferSize));
this->bufEnd = buf + bufferSize;
reset();
}
BufferedStream::~BufferedStream() {
this->close();
free(buf);
}
void BufferedStream::close() {
this->terminate = true;
this->readSem.give(); // force a read operation
const std::lock_guard lock(runningMutex);
if (this->source)
this->source->close();
this->source = nullptr;
}
void BufferedStream::reset() {
this->bufReadPtr = this->buf;
this->bufWritePtr = this->buf;
this->readTotal = 0;
this->bufferTotal = 0;
this->readAvailable = 0;
this->terminate = false;
}
bool BufferedStream::open(const std::shared_ptr<bell::ByteStream> &stream) {
if (this->running)
this->close();
reset();
this->source = stream;
startTask();
return source.get();
}
bool BufferedStream::open(const StreamReader &newReader, uint32_t initialOffset) {
if (this->running)
this->close();
reset();
this->reader = newReader;
this->bufferTotal = initialOffset;
startTask();
return source.get();
}
bool BufferedStream::isReady() const {
return readAvailable >= readyThreshold;
}
bool BufferedStream::isNotReady() const {
return readAvailable < notReadyThreshold;
}
size_t BufferedStream::skip(size_t len) {
return read(nullptr, len);
}
size_t BufferedStream::position() {
return readTotal;
}
size_t BufferedStream::size() {
return source->size();
}
uint32_t BufferedStream::lengthBetween(uint8_t *me, uint8_t *other) {
const std::lock_guard lock(readMutex);
if (other <= me) {
// buf .... other ...... me ........ bufEnd
// buf .... me/other ........ bufEnd
return bufEnd - me;
} else {
// buf ........ me ........ other .... bufEnd
return other - me;
}
}
size_t BufferedStream::read(uint8_t *dst, size_t len) {
if (waitForReady && isNotReady()) {
while ((source || reader) && !isReady()) {} // end waiting after termination
}
if (!running && !readAvailable) {
reset();
return 0;
}
uint32_t read = 0;
uint32_t toReadTotal = std::min(readAvailable.load(), static_cast<uint32_t>(len));
while (toReadTotal > 0) {
uint32_t toRead = std::min(toReadTotal, lengthBetween(bufReadPtr, bufWritePtr));
if (dst) {
memcpy(dst, bufReadPtr, toRead);
dst += toRead;
}
readAvailable -= toRead;
bufReadPtr += toRead;
if (bufReadPtr >= bufEnd)
bufReadPtr = buf;
toReadTotal -= toRead;
read += toRead;
readTotal += toRead;
}
this->readSem.give();
return read;
}
void BufferedStream::runTask() {
const std::lock_guard lock(runningMutex);
running = true;
if (!source && reader) {
// get the initial request on the task's thread
source = reader(this->bufferTotal);
}
while (!terminate) {
if (!source)
break;
if (isReady()) {
// buffer ready, wait for any read operations
this->readSem.wait();
}
if (terminate)
break;
if (readAvailable > readAt)
continue;
// here, the buffer needs re-filling
uint32_t len;
bool wasReady = isReady();
do {
uint32_t toRead = std::min(readSize, lengthBetween(bufWritePtr, bufReadPtr));
if (!source) {
len = 0;
break;
}
len = source->read(bufWritePtr, toRead);
readAvailable += len;
bufferTotal += len;
bufWritePtr += len;
if (bufWritePtr >= bufEnd) // TODO is == enough here?
bufWritePtr = buf;
} while (len && readSize < bufferSize - readAvailable); // loop until there's no more free space in the buffer
if (!len && reader)
source = reader(bufferTotal);
else if (!len)
terminate = true;
// signal that buffer is ready for reading
if (!wasReady && isReady()) {
this->readySem.give();
}
}
source = nullptr;
reader = nullptr;
running = false;
}

View File

@@ -0,0 +1,89 @@
#include "CircularBuffer.h"
using namespace bell;
CircularBuffer::CircularBuffer(size_t dataCapacity)
{
this->dataCapacity = dataCapacity;
buffer = std::vector<uint8_t>(dataCapacity);
this->dataSemaphore = std::make_unique<bell::WrappedSemaphore>(5);
};
size_t CircularBuffer::write(const uint8_t *data, size_t bytes)
{
if (bytes == 0)
return 0;
std::lock_guard<std::mutex> guard(bufferMutex);
size_t bytesToWrite = std::min(bytes, dataCapacity - dataSize);
// Write in a single step
if (bytesToWrite <= dataCapacity - endIndex)
{
memcpy(buffer.data() + endIndex, data, bytesToWrite);
endIndex += bytesToWrite;
if (endIndex == dataCapacity)
endIndex = 0;
}
// Write in two steps
else {
size_t firstChunkSize = dataCapacity - endIndex;
memcpy(buffer.data() + endIndex, data, firstChunkSize);
size_t secondChunkSize = bytesToWrite - firstChunkSize;
memcpy(buffer.data(), data + firstChunkSize, secondChunkSize);
endIndex = secondChunkSize;
}
dataSize += bytesToWrite;
// this->dataSemaphore->give();
return bytesToWrite;
}
void CircularBuffer::emptyBuffer() {
std::lock_guard<std::mutex> guard(bufferMutex);
begIndex = 0;
dataSize = 0;
endIndex = 0;
}
void CircularBuffer::emptyExcept(size_t sizeToSet) {
std::lock_guard<std::mutex> guard(bufferMutex);
if (sizeToSet > dataSize)
sizeToSet = dataSize;
dataSize = sizeToSet;
endIndex = begIndex + sizeToSet;
if (endIndex > dataCapacity) {
endIndex -= dataCapacity;
}
}
size_t CircularBuffer::read(uint8_t *data, size_t bytes)
{
if (bytes == 0)
return 0;
std::lock_guard<std::mutex> guard(bufferMutex);
size_t bytesToRead = std::min(bytes, dataSize);
// Read in a single step
if (bytesToRead <= dataCapacity - begIndex)
{
memcpy(data, buffer.data() + begIndex, bytesToRead);
begIndex += bytesToRead;
if (begIndex == dataCapacity)
begIndex = 0;
}
// Read in two steps
else
{
size_t firstChunkSize = dataCapacity - begIndex;
memcpy(data, buffer.data() + begIndex, firstChunkSize);
size_t secondChunkSize = bytesToRead - firstChunkSize;
memcpy(data + firstChunkSize, buffer.data(), secondChunkSize);
begIndex = secondChunkSize;
}
dataSize -= bytesToRead;
return bytesToRead;
}

View File

@@ -0,0 +1,175 @@
#include "EncodedAudioStream.h"
#include <iostream>
using namespace bell;
EncodedAudioStream::EncodedAudioStream() {
bell::decodersInstance->ensureAAC();
bell::decodersInstance->ensureMP3();
inputBuffer = std::vector<uint8_t>(AAC_READBUF_SIZE * 4);
outputBuffer = std::vector<short>(AAC_MAX_NCHANS * AAC_MAX_NSAMPS * 4 * 4);
decodePtr = inputBuffer.data();
}
EncodedAudioStream::~EncodedAudioStream() {
this->innerStream->close();
}
void EncodedAudioStream::openWithStream(
std::unique_ptr<bell::ByteStream> byteStream) {
if (this->innerStream) {
this->innerStream->close();
}
this->innerStream = std::move(byteStream);
this->guessDataFormat();
}
bool EncodedAudioStream::vectorStartsWith(std::vector<uint8_t>& vec,
std::vector<uint8_t>& start) {
if (vec.size() < start.size()) {
return false;
}
for (int i = 0; i < start.size(); i++) {
if (vec[i] != start[i]) {
return false;
}
}
return true;
}
size_t EncodedAudioStream::decodeFrame(uint8_t* dst) {
if (innerStream->size() != 0 &&
innerStream->position() >= innerStream->size()) {
return 0;
}
switch (this->codec) {
case AudioCodec::AAC:
return decodeFrameAAC(dst);
break;
case AudioCodec::MP3:
return decodeFrameMp3(dst);
break;
default:
break;
}
return 0;
}
size_t EncodedAudioStream::decodeFrameMp3(uint8_t* dst) {
size_t writtenBytes = 0;
int bufSize = MP3_READBUF_SIZE;
int readBytes = innerStream->read(inputBuffer.data() + bytesInBuffer,
bufSize - bytesInBuffer);
if (readBytes > 0) {
bytesInBuffer += readBytes;
decodePtr = inputBuffer.data();
offset = MP3FindSyncWord(inputBuffer.data(), bytesInBuffer);
if (offset != -1) {
bytesInBuffer -= offset;
decodePtr += offset;
int decodeStatus =
MP3Decode(bell::decodersInstance->mp3Decoder, &decodePtr,
&bytesInBuffer, outputBuffer.data(), 0);
MP3GetLastFrameInfo(bell::decodersInstance->mp3Decoder, &mp3FrameInfo);
if (decodeStatus == ERR_MP3_NONE) {
decodedSampleRate = mp3FrameInfo.samprate;
writtenBytes =
(mp3FrameInfo.bitsPerSample / 8) * mp3FrameInfo.outputSamps;
memcpy(dst, outputBuffer.data(), writtenBytes);
} else {
BELL_LOG(info, TAG, "Error in frame, moving two bytes %d",
decodeStatus);
decodePtr += 1;
bytesInBuffer -= 1;
}
} else {
BELL_LOG(info, TAG, "Unexpected error in data, skipping a word");
decodePtr += 3800;
bytesInBuffer -= 3800;
}
memmove(inputBuffer.data(), decodePtr, bytesInBuffer);
}
return writtenBytes;
}
bool EncodedAudioStream::isReadable() {
return this->codec != AudioCodec::NONE;
}
size_t EncodedAudioStream::decodeFrameAAC(uint8_t* dst) {
size_t writtenBytes = 0;
auto bufSize = AAC_READBUF_SIZE;
int readBytes = innerStream->read(inputBuffer.data() + bytesInBuffer,
bufSize - bytesInBuffer);
if (readBytes > 0) {
bytesInBuffer += readBytes;
decodePtr = inputBuffer.data();
offset = AACFindSyncWord(inputBuffer.data(), bytesInBuffer);
if (offset != -1) {
bytesInBuffer -= offset;
decodePtr += offset;
int decodeStatus =
AACDecode(bell::decodersInstance->aacDecoder, &decodePtr,
&bytesInBuffer, outputBuffer.data());
AACGetLastFrameInfo(bell::decodersInstance->aacDecoder, &aacFrameInfo);
if (decodeStatus == ERR_AAC_NONE) {
decodedSampleRate = aacFrameInfo.sampRateOut;
writtenBytes =
(aacFrameInfo.bitsPerSample / 8) * aacFrameInfo.outputSamps;
memcpy(dst, outputBuffer.data(), writtenBytes);
} else {
BELL_LOG(info, TAG, "Error in frame, moving two bytes %d",
decodeStatus);
decodePtr += 1;
bytesInBuffer -= 1;
}
} else {
BELL_LOG(info, TAG, "Unexpected error in data, skipping a word");
decodePtr += 3800;
bytesInBuffer -= 3800;
}
memmove(inputBuffer.data(), decodePtr, bytesInBuffer);
}
return writtenBytes;
}
void EncodedAudioStream::guessDataFormat() {
// Read 14 bytes from the stream
this->innerStream->read(inputBuffer.data(), 14);
bytesInBuffer = 14;
BELL_LOG(info, TAG, "No codec set, reading secret bytes");
if (vectorStartsWith(inputBuffer, this->mp3MagicBytesIdc) ||
vectorStartsWith(inputBuffer, this->mp3MagicBytesUntagged)) {
BELL_LOG(info, TAG, "Detected MP3");
codec = AudioCodec::MP3;
} else if (vectorStartsWith(inputBuffer, this->aacMagicBytes) ||
vectorStartsWith(inputBuffer, this->aacMagicBytes4)) {
BELL_LOG(info, TAG, "Detected AAC");
codec = AudioCodec::AAC;
}
if (codec == AudioCodec::NONE) {
throw std::runtime_error("Codec not supported");
}
}
void EncodedAudioStream::readFully(uint8_t* dst, size_t nbytes) {
}

View File

@@ -0,0 +1,70 @@
#include "FileStream.h"
using namespace bell;
FileStream::FileStream(const std::string& path, std::string read)
{
file = fopen(path.c_str(), "rb");
if (file == NULL)
{
throw std::runtime_error("Could not open file: " + path);
}
}
FileStream::~FileStream()
{
close();
}
size_t FileStream::read(uint8_t *buf, size_t nbytes)
{
if (file == NULL)
{
throw std::runtime_error("Stream is closed");
}
return fread(buf, 1, nbytes, file);
}
size_t FileStream::skip(size_t nbytes)
{
if (file == NULL)
{
throw std::runtime_error("Stream is closed");
}
return fseek(file, nbytes, SEEK_CUR);
}
size_t FileStream::position()
{
if (file == NULL)
{
throw std::runtime_error("Stream is closed");
}
return ftell(file);
}
size_t FileStream::size()
{
if (file == NULL)
{
throw std::runtime_error("Stream is closed");
}
size_t pos = ftell(file);
fseek(file, 0, SEEK_END);
size_t size = ftell(file);
fseek(file, pos, SEEK_SET);
return size;
}
void FileStream::close()
{
if (file != NULL)
{
fclose(file);
file = NULL;
}
}

View File

@@ -0,0 +1,155 @@
#include "HTTPClient.h"
using namespace bell;
void HTTPClient::Response::connect(const std::string& url) {
urlParser = bell::URLParser::parse(url);
// Open socket of type
this->socketStream.open(urlParser.host, urlParser.port,
urlParser.schema == "https");
}
HTTPClient::Response::~Response() {
if (this->socketStream.isOpen()) {
this->socketStream.close();
}
}
void HTTPClient::Response::rawRequest(const std::string& url,
const std::string& method,
const std::string& content,
Headers& headers) {
urlParser = bell::URLParser::parse(url);
// Prepare a request
const char* reqEnd = "\r\n";
socketStream << method << " " << urlParser.path << " HTTP/1.1" << reqEnd;
socketStream << "Host: " << urlParser.host << ":" << urlParser.port << reqEnd;
socketStream << "Connection: keep-alive" << reqEnd;
socketStream << "Accept: */*" << reqEnd;
// Write content
if (content.size() > 0) {
socketStream << "Content-Length: " << content.size() << reqEnd;
}
// Write headers
for (auto& header : headers) {
socketStream << header.first << ": " << header.second << reqEnd;
}
socketStream << reqEnd;
socketStream.flush();
// Parse response
readResponseHeaders();
}
void HTTPClient::Response::readResponseHeaders() {
char *method, *path;
const char* msgPointer;
size_t msgLen;
int pret, minorVersion, status;
size_t prevbuflen = 0, numHeaders;
this->httpBufferAvailable = 0;
while (1) {
socketStream.getline((char*)httpBuffer.data() + httpBufferAvailable,
httpBuffer.size() - httpBufferAvailable);
prevbuflen = httpBufferAvailable;
httpBufferAvailable += socketStream.gcount();
// Restore delimiters
memcpy(httpBuffer.data() + httpBufferAvailable - 2, "\r\n", 2);
// Parse the request
numHeaders = sizeof(phResponseHeaders) / sizeof(phResponseHeaders[0]);
pret =
phr_parse_response((const char*)httpBuffer.data(), httpBufferAvailable,
&minorVersion, &status, &msgPointer, &msgLen,
phResponseHeaders, &numHeaders, prevbuflen);
if (pret > 0) {
break; /* successfully parsed the request */
} else if (pret == -1)
throw std::runtime_error("Cannot parse http response");
/* request is incomplete, continue the loop */
assert(pret == -2);
if (httpBufferAvailable == httpBuffer.size())
throw std::runtime_error("Response too large");
}
this->responseHeaders = {};
// Headers have benen read
for (int headerIndex = 0; headerIndex < numHeaders; headerIndex++) {
this->responseHeaders.push_back(
ValueHeader{std::string(phResponseHeaders[headerIndex].name,
phResponseHeaders[headerIndex].name_len),
std::string(phResponseHeaders[headerIndex].value,
phResponseHeaders[headerIndex].value_len)});
}
std::string contentLengthValue = std::string(header("content-length"));
if (contentLengthValue.size() > 0) {
this->hasContentSize = true;
this->contentSize = std::stoi(contentLengthValue);
}
}
void HTTPClient::Response::get(const std::string& url, Headers headers) {
std::string method = "GET";
return this->rawRequest(url, method, "", headers);
}
size_t HTTPClient::Response::contentLength() {
return contentSize;
}
std::string_view HTTPClient::Response::header(const std::string& headerName) {
for (auto& header : this->responseHeaders) {
std::string headerValue = header.first;
std::transform(headerValue.begin(), headerValue.end(), headerValue.begin(),
[](unsigned char c) { return std::tolower(c); });
if (headerName == headerValue) {
return header.second;
}
}
return "";
}
size_t HTTPClient::Response::totalLength() {
auto rangeHeader = header("content-range");
if (rangeHeader.find("/") != std::string::npos) {
return std::stoi(
std::string(rangeHeader.substr(rangeHeader.find("/") + 1)));
}
return this->contentLength();
}
void HTTPClient::Response::readRawBody() {
if (contentSize > 0 && rawBody.size() == 0) {
rawBody = std::vector<uint8_t>(contentSize);
socketStream.read((char*)rawBody.data(), contentSize);
}
}
std::string_view HTTPClient::Response::body() {
readRawBody();
return std::string_view((char*)rawBody.data(), rawBody.size());
}
std::vector<uint8_t> HTTPClient::Response::bytes() {
readRawBody();
return rawBody;
}

View File

@@ -0,0 +1,105 @@
#include "SocketStream.h"
using namespace bell;
int SocketBuffer::open(const std::string& hostname, int port, bool isSSL) {
if (internalSocket != nullptr) { close(); }
if (isSSL) {
internalSocket = std::make_unique<bell::TLSSocket>();
} else {
internalSocket = std::make_unique<bell::TCPSocket>();
}
internalSocket->open(hostname, port);
return 0;
}
int SocketBuffer::close() {
if (internalSocket != nullptr && isOpen()) {
pubsync();
internalSocket->close();
internalSocket = nullptr;
}
return 0;
}
int SocketBuffer::sync() {
ssize_t bw, n = pptr() - pbase();
while (n > 0) {
bw = internalSocket->write(reinterpret_cast<uint8_t*>(pptr() - n), n);
if (bw < 0) {
setp(pptr() - n, obuf + bufLen);
pbump(n);
return -1;
}
n -= bw;
}
setp(obuf, obuf + bufLen);
return 0;
}
SocketBuffer::int_type SocketBuffer::underflow() {
ssize_t br = internalSocket->read(reinterpret_cast<uint8_t*>(ibuf), bufLen);
if (br <= 0) {
setg(NULL, NULL, NULL);
return traits_type::eof();
}
setg(ibuf, ibuf, ibuf + br);
return traits_type::to_int_type(*ibuf);
}
SocketBuffer::int_type SocketBuffer::overflow(int_type c) {
if (sync() < 0)
return traits_type::eof();
if (traits_type::eq_int_type(c, traits_type::eof()))
return traits_type::not_eof(c);
*pptr() = traits_type::to_char_type(c);
pbump(1);
return c;
}
std::streamsize SocketBuffer::xsgetn(char_type* __s, std::streamsize __n) {
const std::streamsize bn = egptr() - gptr();
if (__n <= bn) {
traits_type::copy(__s, gptr(), __n);
gbump(__n);
return __n;
}
traits_type::copy(__s, gptr(), bn);
setg(NULL, NULL, NULL);
std::streamsize remain = __n - bn;
char_type* end = __s + __n;
ssize_t br;
while (remain > 0) {
br = internalSocket->read(reinterpret_cast<uint8_t*>(end - remain), remain);
if (br <= 0)
return (__n - remain);
remain -= br;
}
return __n;
}
std::streamsize SocketBuffer::xsputn(const char_type* __s,
std::streamsize __n) {
if (pptr() + __n <= epptr()) {
traits_type::copy(pptr(), __s, __n);
pbump(__n);
return __n;
}
if (sync() < 0)
return 0;
ssize_t bw;
std::streamsize remain = __n;
const char_type* end = __s + __n;
while (remain > bufLen) {
bw = internalSocket->write((uint8_t*)(end - remain), remain);
if (bw < 0)
return (__n - remain);
remain -= bw;
}
if (remain > 0) {
traits_type::copy(pptr(), end - remain, remain);
pbump(remain);
}
return __n;
}

View File

@@ -0,0 +1,95 @@
#include "TLSSocket.h"
#include "X509Bundle.h"
/**
* Platform TLSSocket implementation for the mbedtls
*/
bell::TLSSocket::TLSSocket() {
this->isClosed = false;
mbedtls_net_init(&server_fd);
mbedtls_ssl_init(&ssl);
mbedtls_ssl_config_init(&conf);
if (bell::X509Bundle::shouldVerify()) {
bell::X509Bundle::attach(&conf);
}
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_entropy_init(&entropy);
const char* pers = "euphonium";
int ret;
if ((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
(const unsigned char*)pers, strlen(pers))) !=
0) {
BELL_LOG(error, "http_tls",
"failed\n ! mbedtls_ctr_drbg_seed returned %d\n", ret);
throw std::runtime_error("mbedtls_ctr_drbg_seed failed");
}
}
void bell::TLSSocket::open(const std::string& hostUrl, uint16_t port) {
int ret;
if ((ret = mbedtls_net_connect(&server_fd, hostUrl.c_str(),
std::to_string(port).c_str(),
MBEDTLS_NET_PROTO_TCP)) != 0) {
BELL_LOG(error, "http_tls", "failed! connect returned %d\n", ret);
}
if ((ret = mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT,
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
BELL_LOG(error, "http_tls", "failed! config returned %d\n", ret);
throw std::runtime_error("mbedtls_ssl_config_defaults failed");
}
// Only verify if the X509 bundle is present
if (bell::X509Bundle::shouldVerify()) {
mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_REQUIRED);
} else {
mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_NONE);
}
mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);
mbedtls_ssl_setup(&ssl, &conf);
if ((ret = mbedtls_ssl_set_hostname(&ssl, hostUrl.c_str())) != 0) {
throw std::runtime_error("mbedtls_ssl_set_hostname failed");
}
mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv,
NULL);
while ((ret = mbedtls_ssl_handshake(&ssl)) != 0) {
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
BELL_LOG(error, "http_tls", "failed! config returned %d\n", ret);
throw std::runtime_error("mbedtls_ssl_handshake error");
}
}
}
size_t bell::TLSSocket::read(uint8_t* buf, size_t len) {
return mbedtls_ssl_read(&ssl, buf, len);
}
size_t bell::TLSSocket::write(uint8_t* buf, size_t len) {
return mbedtls_ssl_write(&ssl, buf, len);
}
size_t bell::TLSSocket::poll() {
return mbedtls_ssl_get_bytes_avail(&ssl);
}
bool bell::TLSSocket::isOpen() {
return !isClosed;
}
void bell::TLSSocket::close() {
if (!isClosed) {
mbedtls_net_free(&server_fd);
mbedtls_ssl_free(&ssl);
mbedtls_ssl_config_free(&conf);
mbedtls_ctr_drbg_free(&ctr_drbg);
mbedtls_entropy_free(&entropy);
this->isClosed = true;
}
}

View File

@@ -0,0 +1 @@
#include "URLParser.h"

View File

@@ -0,0 +1,186 @@
#include "X509Bundle.h"
using namespace bell::X509Bundle;
static mbedtls_x509_crt s_dummy_crt;
static bool s_should_verify_certs = false;
#ifndef MBEDTLS_PRIVATE
#define MBEDTLS_PRIVATE(member) member
#endif
int bell::X509Bundle::crtCheckCertificate(mbedtls_x509_crt* child,
const uint8_t* pub_key_buf,
size_t pub_key_len) {
int ret = 0;
mbedtls_x509_crt parent;
const mbedtls_md_info_t* md_info;
unsigned char hash[MBEDTLS_MD_MAX_SIZE];
mbedtls_x509_crt_init(&parent);
if ((ret = mbedtls_pk_parse_public_key(&parent.pk, pub_key_buf,
pub_key_len)) != 0) {
BELL_LOG(error, TAG, "PK parse failed with error %X", ret);
goto cleanup;
}
// Fast check to avoid expensive computations when not necessary
if (!mbedtls_pk_can_do(&parent.pk, child->MBEDTLS_PRIVATE(sig_pk))) {
BELL_LOG(error, TAG, "Simple compare failed");
ret = -1;
goto cleanup;
}
md_info = mbedtls_md_info_from_type(child->MBEDTLS_PRIVATE(sig_md));
if ((ret = mbedtls_md(md_info, child->tbs.p, child->tbs.len, hash)) != 0) {
BELL_LOG(error, TAG, "Internal mbedTLS error %X", ret);
goto cleanup;
}
if ((ret = mbedtls_pk_verify_ext(
child->MBEDTLS_PRIVATE(sig_pk), child->MBEDTLS_PRIVATE(sig_opts),
&parent.pk, child->MBEDTLS_PRIVATE(sig_md), hash,
mbedtls_md_get_size(md_info), child->MBEDTLS_PRIVATE(sig).p,
child->MBEDTLS_PRIVATE(sig).len)) != 0) {
BELL_LOG(error, TAG, "PK verify failed with error %X", ret);
goto cleanup;
}
cleanup:
mbedtls_x509_crt_free(&parent);
return ret;
}
/* This callback is called for every certificate in the chain. If the chain
* is proper each intermediate certificate is validated through its parent
* in the x509_crt_verify_chain() function. So this callback should
* only verify the first untrusted link in the chain is signed by the
* root certificate in the trusted bundle
*/
int bell::X509Bundle::crtVerifyCallback(void* buf, mbedtls_x509_crt* crt,
int depth, uint32_t* flags) {
mbedtls_x509_crt* child = crt;
/* It's OK for a trusted cert to have a weak signature hash alg.
as we already trust this certificate */
uint32_t flags_filtered = *flags & ~(MBEDTLS_X509_BADCERT_BAD_MD);
if (flags_filtered != MBEDTLS_X509_BADCERT_NOT_TRUSTED) {
return 0;
}
if (s_crt_bundle.crts == NULL) {
BELL_LOG(error, TAG, "No certificates in bundle");
return MBEDTLS_ERR_X509_FATAL_ERROR;
}
BELL_LOG(debug, TAG, "%d certificates in bundle", s_crt_bundle.num_certs);
size_t name_len = 0;
const uint8_t* crt_name;
bool crt_found = false;
int start = 0;
int end = s_crt_bundle.num_certs - 1;
int middle = (end - start) / 2;
/* Look for the certificate using binary search on subject name */
while (start <= end) {
name_len = s_crt_bundle.crts[middle][0] << 8 | s_crt_bundle.crts[middle][1];
crt_name = s_crt_bundle.crts[middle] + CRT_HEADER_OFFSET;
int cmp_res = memcmp(child->issuer_raw.p, crt_name, name_len);
if (cmp_res == 0) {
crt_found = true;
break;
} else if (cmp_res < 0) {
end = middle - 1;
} else {
start = middle + 1;
}
middle = (start + end) / 2;
}
int ret = MBEDTLS_ERR_X509_FATAL_ERROR;
if (crt_found) {
size_t key_len =
s_crt_bundle.crts[middle][2] << 8 | s_crt_bundle.crts[middle][3];
ret = crtCheckCertificate(
child, s_crt_bundle.crts[middle] + CRT_HEADER_OFFSET + name_len,
key_len);
}
if (ret == 0) {
BELL_LOG(info, TAG, "Certificate validated");
*flags = 0;
return 0;
}
BELL_LOG(info, TAG, "Failed to verify certificate");
return MBEDTLS_ERR_X509_FATAL_ERROR;
}
/* Initialize the bundle into an array so we can do binary search for certs,
the bundle generated by the python utility is already presorted by subject name
*/
void bell::X509Bundle::init(const uint8_t* x509_bundle, size_t bundle_size) {
if (bundle_size < BUNDLE_HEADER_OFFSET + CRT_HEADER_OFFSET) {
throw std::runtime_error("Invalid certificate bundle");
}
uint16_t num_certs = (x509_bundle[0] << 8) | x509_bundle[1];
const uint8_t** crts =
(const uint8_t**)calloc(num_certs, sizeof(x509_bundle));
if (crts == NULL) {
throw std::runtime_error("Unable to allocate memory for bundle");
}
const uint8_t* cur_crt;
/* This is the maximum region that is allowed to access */
const uint8_t* bundle_end = x509_bundle + bundle_size;
cur_crt = x509_bundle + BUNDLE_HEADER_OFFSET;
for (int i = 0; i < num_certs; i++) {
crts[i] = cur_crt;
if (cur_crt + CRT_HEADER_OFFSET > bundle_end) {
free(crts);
throw std::runtime_error("Invalid certificate bundle");
}
size_t name_len = cur_crt[0] << 8 | cur_crt[1];
size_t key_len = cur_crt[2] << 8 | cur_crt[3];
cur_crt = cur_crt + CRT_HEADER_OFFSET + name_len + key_len;
}
if (cur_crt > bundle_end) {
free(crts);
throw std::runtime_error("Invalid certificate bundle");
}
/* The previous crt bundle is only updated when initialization of the
* current crt_bundle is successful */
/* Free previous crt_bundle */
free(s_crt_bundle.crts);
s_crt_bundle.num_certs = num_certs;
s_crt_bundle.crts = crts;
// Enable certificate verification
s_should_verify_certs = true;
}
void bell::X509Bundle::attach(mbedtls_ssl_config* conf) {
/* point to a dummy certificate
* This is only required so that the
* cacert_ptr passes non-NULL check during handshake
*/
mbedtls_ssl_config* ssl_conf = (mbedtls_ssl_config*)conf;
mbedtls_x509_crt_init(&s_dummy_crt);
mbedtls_ssl_conf_ca_chain(ssl_conf, &s_dummy_crt, NULL);
mbedtls_ssl_conf_verify(ssl_conf, crtVerifyCallback, NULL);
}
bool bell::X509Bundle::shouldVerify() {
return s_should_verify_certs;
}

View File

@@ -0,0 +1,106 @@
#pragma once
#include <BellLogger.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fstream>
#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <utility>
#include <optional>
#include <regex>
#include <sstream>
#include <string>
#include <mutex>
#include <unordered_map>
#include "CivetServer.h"
#include "civetweb.h"
using namespace bell;
namespace bell {
class BellHTTPServer : public CivetHandler {
public:
BellHTTPServer(int serverPort);
enum class WSState { CONNECTED, READY, CLOSED };
struct HTTPResponse {
uint8_t* body;
size_t bodySize;
std::map<std::string, std::string> headers;
int status;
HTTPResponse() {
body = nullptr;
bodySize = 0;
status = 200;
}
~HTTPResponse() {
if (body != nullptr) {
free(body);
body = nullptr;
}
}
};
typedef std::function<std::unique_ptr<HTTPResponse>(struct mg_connection* conn)> HTTPHandler;
typedef std::function<void(struct mg_connection* conn, WSState)>
WSStateHandler;
typedef std::function<void(struct mg_connection* conn, char*, size_t)>
WSDataHandler;
class Router {
public:
struct RouterNode {
std::unordered_map<std::string, std::unique_ptr<RouterNode>> children;
HTTPHandler value = nullptr;
std::string paramName = "";
bool isParam = false;
bool isCatchAll = false;
};
RouterNode root = RouterNode();
typedef std::unordered_map<std::string, std::string> Params;
typedef std::pair<HTTPHandler, Params> HandlerAndParams;
std::vector<std::string> split(const std::string str,
const std::string regex_str);
void insert(const std::string& route, HTTPHandler& value);
HandlerAndParams find(const std::string& route);
};
std::vector<int> getListeningPorts() { return server->getListeningPorts(); };
void close() { server->close(); }
std::unique_ptr<HTTPResponse> makeJsonResponse(const std::string& json, int status = 200);
std::unique_ptr<HTTPResponse> makeEmptyResponse();
void registerNotFound(HTTPHandler handler);
void registerGet(const std::string&, HTTPHandler handler);
void registerPost(const std::string&, HTTPHandler handler);
void registerWS(const std::string&, WSDataHandler dataHandler,
WSStateHandler stateHandler);
static std::unordered_map<std::string, std::string> extractParams(struct mg_connection* conn);
private:
std::unique_ptr<CivetServer> server;
int serverPort = 8080;
Router getRequestsRouter;
Router postRequestsRouter;
std::mutex responseMutex;
HTTPHandler notFoundHandler;
bool handleGet(CivetServer* server, struct mg_connection* conn);
bool handlePost(CivetServer* server, struct mg_connection* conn);
};
} // namespace bell

View File

@@ -0,0 +1,19 @@
#pragma once
#include <string>
namespace bell {
class Socket {
public:
Socket(){};
virtual ~Socket() = default;
virtual void open(const std::string& host, uint16_t port) = 0;
virtual size_t poll() = 0;
virtual size_t write(uint8_t* buf, size_t len) = 0;
virtual size_t read(uint8_t* buf, size_t len) = 0;
virtual bool isOpen() = 0;
virtual void close() = 0;
};
} // namespace bell

View File

@@ -0,0 +1,76 @@
#pragma once
#include <iostream>
#include <string>
#include <vector>
namespace bell::BellTar {
typedef long long unsigned file_size_t;
////////////////////////////////////////
// Writing raw data
////////////////////////////////////////
class writer {
std::ostream& _dst;
public:
writer(std::ostream& dst) : _dst(dst) {}
~writer() { finish(); }
// Append the data specified by |data| and |file_size| into the
// tar file represented by |dst|.
// In the tar file, the data will be available at |path_in_tar|.
void put(std::string path_in_tar, char const* const data,
const file_size_t data_size);
// Write empty folder at |path_in_tar|.
// NOTE: to specify folders for files, just use / in the path
// passed to |put()|.
void put_directory(std::string path_in_tar);
// Call after everything has been added to the tar represented
// by |dst| to make it a valid tar file.
void finish();
};
////////////////////////////////////////
// Reading raw data
////////////////////////////////////////
class reader {
std::istream& _inp;
struct {
std::string file_name;
file_size_t file_size;
char file_type;
} _cached_header_data;
bool _cached_header_data_valid;
void _cache_header();
int _number_of_files;
public:
// Constructor, pass input stream |inp| pointing to a tar file.
reader(std::istream& inp)
: _inp(inp), _cached_header_data_valid(false), _number_of_files(-1) {}
// Returns true iff another file can be read from |inp|.
bool contains_another_file();
// Returns file name of next file in |inp|.
std::string get_next_file_name();
// Returns file size of next file in |inp|. Use to allocate
// memory for the |read_next_file()| call.
file_size_t get_next_file_size();
// Read next file in |inp| to |data|.
void read_next_file(char* const data);
char get_next_file_type();
void extract_all_files(std::string output_dir);
// Skip next file in |inp|.
void skip_next_file();
// Returns number of files in tar at |inp|.
int number_of_files();
};
} // namespace bell::BellTar

View File

@@ -0,0 +1,31 @@
#pragma once
#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <vector>
#include <cstring>
#include <memory>
#include "ByteStream.h"
namespace bell
{
class BinaryReader
{
std::shared_ptr<ByteStream> stream;
size_t currentPos = 0;
public:
BinaryReader(std::shared_ptr<ByteStream> stream);
int32_t readInt();
int16_t readShort();
uint32_t readUInt();
long long readLong();
void close();
uint8_t readByte();
size_t size();
size_t position();
std::vector<uint8_t> readBytes(size_t);
void skip(size_t);
};
}

View File

@@ -0,0 +1,77 @@
#pragma once
#ifndef ESP_PLATFORM
#include <bit>
#endif
#include <iostream>
#include <vector>
namespace bell {
class BinaryStream {
private:
std::endian byteOrder;
std::istream* istr = nullptr;
std::ostream* ostr = nullptr;
void ensureReadable();
void ensureWritable();
bool flipBytes = false;
template <typename T>
T swap16(T value) {
#ifdef _WIN32
return _byteswap_ushort(value);
#else
return __builtin_bswap16(value);
#endif
}
template <typename T>
T swap32(T value) {
#ifdef _WIN32
return _byteswap_ulong(value);
#else
return __builtin_bswap32(value);
#endif
}
template <typename T>
T swap64(T value) {
#ifdef _WIN32
return _byteswap_uint64(value);
#else
return __builtin_bswap64(value);
#endif
}
public:
BinaryStream(std::ostream* ostr);
BinaryStream(std::istream* istr);
/**
* @brief Set byte order used by stream.
*
* @param byteOrder stream's byteorder. Defaults to native.
*/
void setByteOrder(std::endian byteOrder);
// Read operations
BinaryStream& operator>>(char& value);
BinaryStream& operator>>(std::byte& value);
BinaryStream& operator>>(int16_t& value);
BinaryStream& operator>>(uint16_t& value);
BinaryStream& operator>>(int32_t& value);
BinaryStream& operator>>(uint32_t& value);
BinaryStream& operator>>(int64_t& value);
BinaryStream& operator>>(uint64_t& value);
// Write operations
BinaryStream& operator<<(char value);
BinaryStream& operator<<(std::byte value);
BinaryStream& operator<<(int16_t value);
BinaryStream& operator<<(uint16_t value);
BinaryStream& operator<<(int32_t value);
BinaryStream& operator<<(uint32_t value);
BinaryStream& operator<<(int64_t value);
BinaryStream& operator<<(uint64_t value);
};
} // namespace bell

View File

@@ -0,0 +1,125 @@
#pragma once
#include "ByteStream.h"
#include "BellTask.h"
#include "WrappedSemaphore.h"
#include <atomic>
#include <functional>
#include <memory>
#include <mutex>
/**
* This class implements a wrapper around an arbitrary bell::ByteStream,
* providing a circular reading buffer with configurable thresholds.
*
* The BufferedStream runs a bell::Task when it's started, so the caller can
* access the buffer's data asynchronously, whenever needed. The buffer is refilled
* automatically from source stream.
*
* The class implements bell::ByteStream's methods, although for proper functioning,
* the caller code should be modified to check isReady() and isNotReady() flags.
*
* If the actual reading code can't be modified, waitForReady allows to wait for buffer readiness
* during reading. Keep in mind that using the semaphore is probably more resource effective.
*
* The source stream (passed to open() or returned by the reader) should implement the read()
* method correctly, such as that 0 is returned if, and only if the stream ends.
*/
class BufferedStream : public bell::ByteStream, bell::Task {
public:
typedef std::shared_ptr<bell::ByteStream> StreamPtr;
typedef std::function<StreamPtr(uint32_t rangeStart)> StreamReader;
public:
/**
* @param taskName name to use for the reading task
* @param bufferSize total size of the reading buffer
* @param readThreshold how much can be read before refilling the buffer
* @param readSize amount of bytes to read from the source each time
* @param readyThreshold minimum amount of available bytes to report isReady()
* @param notReadyThreshold maximum amount of available bytes to report isNotReady()
* @param waitForReady whether to wait for the buffer to be ready during reading
* @param endWithSource whether to end the streaming as soon as source returns 0 from read()
*/
BufferedStream(
const std::string &taskName,
uint32_t bufferSize,
uint32_t readThreshold,
uint32_t readSize,
uint32_t readyThreshold,
uint32_t notReadyThreshold,
bool waitForReady = false);
~BufferedStream() override;
bool open(const StreamPtr &stream);
bool open(const StreamReader &newReader, uint32_t initialOffset = 0);
void close() override;
// inherited methods
public:
/**
* Read len bytes from the buffer to dst. If waitForReady is enabled
* and readAvailable is lower than notReadyThreshold, the function
* will block until readyThreshold bytes is available.
*
* @returns number of bytes copied to dst (might be lower than len,
* if the buffer does not contain len bytes available), or 0 if the source
* stream is already closed and there is no reader attached.
*/
size_t read(uint8_t *dst, size_t len) override;
size_t skip(size_t len) override;
size_t position() override;
size_t size() override;
// stream status
public:
/**
* Total amount of bytes served to read().
*/
uint32_t readTotal;
/**
* Total amount of bytes read from source.
*/
uint32_t bufferTotal;
/**
* Amount of bytes available to read from the buffer.
*/
std::atomic<uint32_t> readAvailable;
/**
* Whether the caller should start reading the data. This indicates that a safe
* amount (determined by readyThreshold) of data is available in the buffer.
*/
bool isReady() const;
/**
* Whether the caller should stop reading the data. This indicates that the amount of data
* available for reading is decreasing to a non-safe value, as data is being read
* faster than it can be buffered.
*/
bool isNotReady() const;
/**
* Semaphore that is given when the buffer becomes ready (isReady() == true). Caller can
* wait for the semaphore instead of continuously querying isReady().
*/
bell::WrappedSemaphore readySem;
private:
std::mutex runningMutex;
bool running = false;
bool terminate = false;
bell::WrappedSemaphore readSem; // signal to start writing to buffer after reading from it
std::mutex readMutex; // mutex for locking read operations during writing, and vice versa
uint32_t bufferSize;
uint32_t readAt;
uint32_t readSize;
uint32_t readyThreshold;
uint32_t notReadyThreshold;
bool waitForReady;
uint8_t *buf;
uint8_t *bufEnd;
uint8_t *bufReadPtr;
uint8_t *bufWritePtr;
StreamPtr source;
StreamReader reader;
void runTask() override;
void reset();
uint32_t lengthBetween(uint8_t *me, uint8_t *other);
};

View File

@@ -0,0 +1,27 @@
#ifndef BELL_BYTE_READER_H
#define BELL_BYTE_READER_H
#include <stdlib.h>
#include <stdint.h>
/**
* A class for reading bytes from a stream. Further implemented in HTTPStream.h
*/
namespace bell
{
class ByteStream
{
public:
ByteStream(){};
virtual ~ByteStream() = default;
virtual size_t read(uint8_t *buf, size_t nbytes) = 0;
virtual size_t skip(size_t nbytes) = 0;
virtual size_t position() = 0;
virtual size_t size() = 0;
virtual void close() = 0;
};
}
#endif

View File

@@ -0,0 +1,39 @@
#pragma once
#include <algorithm>
#include <memory>
#include <cstring>
#include <mutex>
#include <vector>
#include "WrappedSemaphore.h"
namespace bell {
class CircularBuffer {
public:
CircularBuffer(size_t dataCapacity);
std::unique_ptr<bell::WrappedSemaphore> dataSemaphore;
size_t size() const {
return dataSize;
}
size_t capacity() const {
return dataCapacity;
}
size_t write(const uint8_t *data, size_t bytes);
size_t read(uint8_t *data, size_t bytes);
void emptyBuffer();
void emptyExcept(size_t size);
private:
std::mutex bufferMutex;
size_t begIndex = 0;
size_t endIndex = 0;
size_t dataSize = 0;
size_t dataCapacity = 0;
std::vector<uint8_t> buffer;
};
} // namespace bell

View File

@@ -0,0 +1,55 @@
#pragma once
#include <string>
#include <vector>
#include "BellLogger.h"
#include "ByteStream.h"
#include "DecoderGlobals.h"
#include "aacdec.h"
#include "mp3dec.h"
namespace bell {
class EncodedAudioStream {
public:
EncodedAudioStream();
~EncodedAudioStream();
// Codecs supported by this stream class
enum class AudioCodec { AAC, MP3, OGG, NONE };
void openWithStream(std::unique_ptr<bell::ByteStream> byteStream);
size_t decodeFrame(uint8_t* dst);
bool isReadable();
private:
std::shared_ptr<ByteStream> innerStream;
std::vector<uint8_t> inputBuffer;
std::vector<short> outputBuffer;
std::string TAG = "EncryptedAudioStream";
uint8_t* decodePtr = 0;
int bytesInBuffer = 0;
size_t offset = 0;
size_t decodedSampleRate = 44100;
AudioCodec codec = AudioCodec::NONE;
void guessDataFormat();
void readFully(uint8_t* dst, size_t nbytes);
bool vectorStartsWith(std::vector<uint8_t>&, std::vector<uint8_t>&);
std::vector<uint8_t> aacMagicBytes = {0xFF, 0xF1};
std::vector<uint8_t> aacMagicBytes4 = {0xFF, 0xF9};
std::vector<uint8_t> mp3MagicBytesUntagged = {0xFF, 0xFB};
std::vector<uint8_t> mp3MagicBytesIdc = {0x49, 0x44, 0x33};
AACFrameInfo aacFrameInfo;
MP3FrameInfo mp3FrameInfo;
size_t decodeFrameMp3(uint8_t* dst);
size_t decodeFrameAAC(uint8_t* dst);
};
} // namespace bell

View File

@@ -0,0 +1,47 @@
#pragma once
#include <string>
#include <stdexcept>
#include <BellLogger.h>
#include <ByteStream.h>
#include <stdio.h>
/*
* FileStream
*
* A class for reading and writing to files implementing the ByteStream interface.
*
*/
namespace bell
{
class FileStream : public ByteStream
{
public:
FileStream(const std::string& path, std::string mode);
~FileStream();
FILE* file;
/*
* Reads data from the stream.
*
* @param buf The buffer to read data into.
* @param nbytes The size of the buffer.
* @return The number of bytes read.
* @throws std::runtime_error if the stream is closed.
*/
size_t read(uint8_t *buf, size_t nbytes);
/*
* Skips nbytes bytes in the stream.
*/
size_t skip(size_t nbytes);
size_t position();
size_t size();
// Closes the connection
void close();
};
}

View File

@@ -0,0 +1,99 @@
#pragma once
#include <memory>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <variant>
#include <vector>
#include <cassert>
#include "BellSocket.h"
#include "ByteStream.h"
#include "SocketStream.h"
#include "URLParser.h"
#include "fmt/core.h"
#include "picohttpparser.h"
namespace bell {
class HTTPClient {
public:
// most basic header type, represents by a key-val
typedef std::pair<std::string, std::string> ValueHeader;
typedef std::vector<ValueHeader> Headers;
// Helper over ValueHeader, formatting a HTTP bytes range
struct RangeHeader {
static ValueHeader range(int32_t from, int32_t to) {
return ValueHeader{"Range", fmt::format("bytes={}-{}", from, to)};
}
static ValueHeader last(int32_t nbytes) {
return ValueHeader{"Range", fmt::format("bytes=-{}", nbytes)};
}
};
class Response {
public:
Response(){};
~Response();
/**
* Initializes a connection with a given url.
*/
void connect(const std::string& url);
void rawRequest(const std::string& method, const std::string& url,
const std::string& content, Headers& headers);
void get(const std::string& url, Headers headers = {});
std::string_view body();
std::vector<uint8_t> bytes();
std::string_view header(const std::string& headerName);
bell::SocketStream& stream() { return this->socketStream; }
size_t contentLength();
size_t totalLength();
private:
bell::URLParser urlParser;
bell::SocketStream socketStream;
struct phr_header phResponseHeaders[32];
const size_t HTTP_BUF_SIZE = 1024;
std::vector<uint8_t> httpBuffer = std::vector<uint8_t>(HTTP_BUF_SIZE);
std::vector<uint8_t> rawBody = std::vector<uint8_t>();
size_t httpBufferAvailable;
size_t contentSize = 0;
bool hasContentSize = false;
Headers responseHeaders;
void readResponseHeaders();
void readRawBody();
};
enum class Method : uint8_t { GET = 0, POST = 1 };
struct Request {
std::string url;
Method method;
Headers headers;
};
static std::unique_ptr<Response> get(const std::string& url,
Headers headers = {}) {
auto response = std::make_unique<Response>();
response->connect(url);
response->get(url, headers);
return response;
}
};
} // namespace bell

View File

@@ -0,0 +1,69 @@
#pragma once
#include <iostream>
#include "TCPSocket.h"
#include "TLSSocket.h"
namespace bell {
class SocketBuffer : public std::streambuf {
private:
std::unique_ptr<bell::Socket> internalSocket;
static const int bufLen = 1024;
char ibuf[bufLen], obuf[bufLen];
public:
SocketBuffer() { internalSocket = nullptr; }
SocketBuffer(const std::string& hostname, int port, bool isSSL = false) {
open(hostname, port);
}
int open(const std::string& hostname, int port, bool isSSL = false);
int close();
bool isOpen() {
return internalSocket != nullptr && internalSocket->isOpen();
}
~SocketBuffer() { close(); }
protected:
virtual int sync();
virtual int_type underflow();
virtual int_type overflow(int_type c = traits_type::eof());
virtual std::streamsize xsgetn(char_type* __s, std::streamsize __n);
virtual std::streamsize xsputn(const char_type* __s, std::streamsize __n);
};
class SocketStream : public std::iostream {
private:
SocketBuffer socketBuf;
public:
SocketStream() : std::iostream(&socketBuf) {}
SocketStream(const std::string& hostname, int port, bool isSSL = false)
: std::iostream(&socketBuf) {
open(hostname, port, isSSL);
}
SocketBuffer* rdbuf() { return &socketBuf; }
int open(const std::string& hostname, int port, bool isSSL = false) {
int err = socketBuf.open(hostname, port, isSSL);
if (err)
setstate(std::ios::failbit);
return err;
}
int close() { return socketBuf.close(); }
bool isOpen() { return socketBuf.isOpen(); }
};
} // namespace bell

View File

@@ -0,0 +1,19 @@
#pragma once
#include <cstddef>
#include <istream>
#include <streambuf>
namespace bell {
struct MemoryBuffer : std::streambuf {
MemoryBuffer(std::byte const* base, size_t size) {
std::byte* p(const_cast<std::byte*>(base));
this->setg((char*)p, (char*)p, (char*)p + size);
}
};
struct IMemoryStream : virtual MemoryBuffer, std::istream {
IMemoryStream(std::byte const* base, size_t size)
: MemoryBuffer(base, size),
std::istream(static_cast<std::streambuf*>(this)) {}
};
} // namespace bell

View File

@@ -0,0 +1,121 @@
#ifndef BELL_BASIC_SOCKET_H
#define BELL_BASIC_SOCKET_H
#include <ctype.h>
#include <stdlib.h>
#include <sys/types.h>
#include <cstring>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include "BellSocket.h"
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include "win32shim.h"
#else
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#ifdef __sun
#include <sys/filio.h>
#endif
#endif
#include <BellLogger.h>
#include <fstream>
#include <sstream>
namespace bell {
class TCPSocket : public bell::Socket {
private:
int sockFd;
bool isClosed = true;
public:
TCPSocket(){};
~TCPSocket() { close(); };
void open(const std::string& host, uint16_t port) {
int err;
int domain = AF_INET;
int socketType = SOCK_STREAM;
struct addrinfo hints {
}, *addr;
//fine-tune hints according to which socket you want to open
hints.ai_family = domain;
hints.ai_socktype = socketType;
hints.ai_protocol =
IPPROTO_IP; // no enum : possible value can be read in /etc/protocols
hints.ai_flags = AI_CANONNAME | AI_ALL | AI_ADDRCONFIG;
// BELL_LOG(info, "http", "%s %d", host.c_str(), port);
char portStr[6];
sprintf(portStr, "%u", port);
err = getaddrinfo(host.c_str(), portStr, &hints, &addr);
if (err != 0) {
throw std::runtime_error("Resolve failed");
}
sockFd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
err = connect(sockFd, addr->ai_addr, addr->ai_addrlen);
if (err < 0) {
close();
BELL_LOG(error, "http", "Could not connect to %s. Error %d", host.c_str(),
errno);
throw std::runtime_error("Resolve failed");
}
int flag = 1;
setsockopt(sockFd, /* socket affected */
IPPROTO_TCP, /* set option at TCP level */
TCP_NODELAY, /* name of option */
(char*)&flag, /* the cast is historical cruft */
sizeof(int)); /* length of option value */
freeaddrinfo(addr);
isClosed = false;
}
size_t read(uint8_t* buf, size_t len) {
return recv(sockFd, (char*)buf, len, 0);
}
size_t write(uint8_t* buf, size_t len) {
return send(sockFd, (char*)buf, len, 0);
}
size_t poll() {
#ifdef _WIN32
unsigned long value;
ioctlsocket(sockFd, FIONREAD, &value);
#else
int value;
ioctl(sockFd, FIONREAD, &value);
#endif
return value;
}
bool isOpen() { return !isClosed; }
void close() {
if (!isClosed) {
#ifdef _WIN32
closesocket(sockFd);
#else
::close(sockFd);
#endif
sockFd = -1;
isClosed = true;
}
}
};
} // namespace bell
#endif

View File

@@ -0,0 +1,60 @@
#ifndef BELL_TLS_SOCKET_H
#define BELL_TLS_SOCKET_H
#include <ctype.h>
#include <cstring>
#include <fstream>
#include <iostream>
#include <memory>
#include "BellLogger.h"
#include "BellSocket.h"
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <unistd.h>
#endif
#include <stdlib.h>
#include <sys/types.h>
#include <sstream>
#include <string>
#include <vector>
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/debug.h"
#include "mbedtls/entropy.h"
#include "mbedtls/net_sockets.h"
#include "mbedtls/ssl.h"
namespace bell {
class TLSSocket : public bell::Socket {
private:
mbedtls_net_context server_fd;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ssl_context ssl;
mbedtls_ssl_config conf;
bool isClosed = true;
public:
TLSSocket();
~TLSSocket() { close(); };
void open(const std::string& host, uint16_t port);
size_t read(uint8_t* buf, size_t len);
size_t write(uint8_t* buf, size_t len);
size_t poll();
bool isOpen();
void close();
};
} // namespace bell
#endif

View File

@@ -0,0 +1,100 @@
#pragma once
#include <iostream>
#include <regex>
#include <stdexcept>
#include <string>
namespace bell {
class URLParser {
public:
static std::string urlEncode(const std::string& value) {
std::string new_str = "";
static auto hex_digt = "0123456789ABCDEF";
std::string result;
result.reserve(value.size() << 1);
for (auto ch : value) {
if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') ||
(ch >= 'a' && ch <= 'z') || ch == '-' || ch == '_' || ch == '!' ||
ch == '\'' || ch == '(' || ch == ')' || ch == '*' || ch == '~' ||
ch == '.') // !'()*-._~
{
result.push_back(ch);
} else {
result += std::string("%") +
hex_digt[static_cast<unsigned char>(ch) >> 4] +
hex_digt[static_cast<unsigned char>(ch) & 15];
}
}
return result;
}
static std::string urlDecode(const std::string& value) {
std::string result;
result.reserve(value.size());
for (std::size_t i = 0; i < value.size(); ++i) {
auto ch = value[i];
if (ch == '%' && (i + 2) < value.size()) {
auto hex = value.substr(i + 1, 2);
auto dec = static_cast<char>(std::strtol(hex.c_str(), nullptr, 16));
result.push_back(dec);
i += 2;
} else if (ch == '+') {
result.push_back(' ');
} else {
result.push_back(ch);
}
}
return result;
}
std::string host;
int port = -1;
std::string schema = "http";
std::string path;
std::regex urlParseRegex = std::regex(
"^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(\\?(?:[^#]*))?(#(?:.*))?");
static URLParser parse(const std::string& url) {
URLParser parser;
// apply parser.urlParseRegex to url
std::cmatch match;
std::regex_match(url.c_str(), match, parser.urlParseRegex);
if (match.size() < 3) {
throw std::invalid_argument("Invalid URL");
}
parser.schema = match[1];
parser.host = match[2];
parser.path = match[3];
if (match[4] != "") {
parser.path += match[4];
}
// check if parser.host contains ':'
if (parser.host.find(':') != std::string::npos) {
auto port = std::stoi(
parser.host.substr(parser.host.find(':') + 1, parser.host.size()));
auto host = parser.host.substr(0, parser.host.find(':'));
parser.port = port;
parser.host = host;
}
if (parser.port == -1) {
parser.port = parser.schema == "http" ? 80 : 443;
}
return parser;
}
};
} // namespace bell

View File

@@ -0,0 +1,40 @@
#pragma once
#include <stdexcept>
#include "BellLogger.h"
#include "mbedtls/ssl.h"
namespace bell::X509Bundle {
typedef struct crt_bundle_t {
const uint8_t** crts;
uint16_t num_certs;
size_t x509_crt_bundle_len;
} crt_bundle_t;
static crt_bundle_t s_crt_bundle;
static constexpr auto TAG = "X509Bundle";
static constexpr auto CRT_HEADER_OFFSET = 4;
static constexpr auto BUNDLE_HEADER_OFFSET = 2;
int crtCheckCertificate(mbedtls_x509_crt* child, const uint8_t* pub_key_buf,
size_t pub_key_len);
/* This callback is called for every certificate in the chain. If the chain
* is proper each intermediate certificate is validated through its parent
* in the x509_crt_verify_chain() function. So this callback should
* only verify the first untrusted link in the chain is signed by the
* root certificate in the trusted bundle
*/
int crtVerifyCallback(void* buf, mbedtls_x509_crt* crt, int depth,
uint32_t* flags);
/* Initialize the bundle into an array so we can do binary search for certs,
the bundle generated by the python utility is already presorted by subject name
*/
void init(const uint8_t* x509_bundle, size_t bundle_size);
void attach(mbedtls_ssl_config* conf);
bool shouldVerify();
}; // namespace bell::X509Bundle

View File

@@ -0,0 +1,87 @@
/*
* Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
* Shigeo Mitsunari
*
* The software is licensed under either the MIT License (below) or the Perl
* license.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef picohttpparser_h
#define picohttpparser_h
#include <sys/types.h>
#if defined(_MSC_VER) && !defined(ssize_t)
#define ssize_t intptr_t
#endif
#ifdef __cplusplus
extern "C" {
#endif
/* contains name and value of a header (name == NULL if is a continuing line
* of a multiline header */
struct phr_header {
const char *name;
size_t name_len;
const char *value;
size_t value_len;
};
/* returns number of bytes consumed if successful, -2 if request is partial,
* -1 if failed */
int phr_parse_request(const char *buf, size_t len, const char **method, size_t *method_len, const char **path, size_t *path_len,
int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len);
/* ditto */
int phr_parse_response(const char *_buf, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len,
struct phr_header *headers, size_t *num_headers, size_t last_len);
/* ditto */
int phr_parse_headers(const char *buf, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len);
/* should be zero-filled before start */
struct phr_chunked_decoder {
size_t bytes_left_in_chunk; /* number of bytes left in current chunk */
char consume_trailer; /* if trailing headers should be consumed */
char _hex_count;
char _state;
};
/* the function rewrites the buffer given as (buf, bufsz) removing the chunked-
* encoding headers. When the function returns without an error, bufsz is
* updated to the length of the decoded data available. Applications should
* repeatedly call the function while it returns -2 (incomplete) every time
* supplying newly arrived data. If the end of the chunked-encoded data is
* found, the function returns a non-negative number indicating the number of
* octets left undecoded, that starts from the offset returned by `*bufsz`.
* Returns -1 on error.
*/
ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *bufsz);
/* returns if the chunked decoder is in middle of chunked data */
int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,665 @@
/*
* Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
* Shigeo Mitsunari
*
* The software is licensed under either the MIT License (below) or the Perl
* license.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <assert.h>
#include <stddef.h>
#include <string.h>
#ifdef __SSE4_2__
#ifdef _MSC_VER
#include <nmmintrin.h>
#else
#include <x86intrin.h>
#endif
#endif
#include "picohttpparser.h"
#if __GNUC__ >= 3
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
#else
#define likely(x) (x)
#define unlikely(x) (x)
#endif
#ifdef _MSC_VER
#define ALIGNED(n) _declspec(align(n))
#else
#define ALIGNED(n) __attribute__((aligned(n)))
#endif
#define IS_PRINTABLE_ASCII(c) ((unsigned char)(c)-040u < 0137u)
#define CHECK_EOF() \
if (buf == buf_end) { \
*ret = -2; \
return NULL; \
}
#define EXPECT_CHAR_NO_CHECK(ch) \
if (*buf++ != ch) { \
*ret = -1; \
return NULL; \
}
#define EXPECT_CHAR(ch) \
CHECK_EOF(); \
EXPECT_CHAR_NO_CHECK(ch);
#define ADVANCE_TOKEN(tok, toklen) \
do { \
const char *tok_start = buf; \
static const char ALIGNED(16) ranges2[16] = "\000\040\177\177"; \
int found2; \
buf = findchar_fast(buf, buf_end, ranges2, 4, &found2); \
if (!found2) { \
CHECK_EOF(); \
} \
while (1) { \
if (*buf == ' ') { \
break; \
} else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \
if ((unsigned char)*buf < '\040' || *buf == '\177') { \
*ret = -1; \
return NULL; \
} \
} \
++buf; \
CHECK_EOF(); \
} \
tok = tok_start; \
toklen = buf - tok_start; \
} while (0)
static const char *token_char_map = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0"
"\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1"
"\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
static const char *findchar_fast(const char *buf, const char *buf_end, const char *ranges, size_t ranges_size, int *found)
{
*found = 0;
#if __SSE4_2__
if (likely(buf_end - buf >= 16)) {
__m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges);
size_t left = (buf_end - buf) & ~15;
do {
__m128i b16 = _mm_loadu_si128((const __m128i *)buf);
int r = _mm_cmpestri(ranges16, ranges_size, b16, 16, _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS);
if (unlikely(r != 16)) {
buf += r;
*found = 1;
break;
}
buf += 16;
left -= 16;
} while (likely(left != 0));
}
#else
/* suppress unused parameter warning */
(void)buf_end;
(void)ranges;
(void)ranges_size;
#endif
return buf;
}
static const char *get_token_to_eol(const char *buf, const char *buf_end, const char **token, size_t *token_len, int *ret)
{
const char *token_start = buf;
#ifdef __SSE4_2__
static const char ALIGNED(16) ranges1[16] = "\0\010" /* allow HT */
"\012\037" /* allow SP and up to but not including DEL */
"\177\177"; /* allow chars w. MSB set */
int found;
buf = findchar_fast(buf, buf_end, ranges1, 6, &found);
if (found)
goto FOUND_CTL;
#else
/* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */
while (likely(buf_end - buf >= 8)) {
#define DOIT() \
do { \
if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \
goto NonPrintable; \
++buf; \
} while (0)
DOIT();
DOIT();
DOIT();
DOIT();
DOIT();
DOIT();
DOIT();
DOIT();
#undef DOIT
continue;
NonPrintable:
if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) {
goto FOUND_CTL;
}
++buf;
}
#endif
for (;; ++buf) {
CHECK_EOF();
if (unlikely(!IS_PRINTABLE_ASCII(*buf))) {
if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) {
goto FOUND_CTL;
}
}
}
FOUND_CTL:
if (likely(*buf == '\015')) {
++buf;
EXPECT_CHAR('\012');
*token_len = buf - 2 - token_start;
} else if (*buf == '\012') {
*token_len = buf - token_start;
++buf;
} else {
*ret = -1;
return NULL;
}
*token = token_start;
return buf;
}
static const char *is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret)
{
int ret_cnt = 0;
buf = last_len < 3 ? buf : buf + last_len - 3;
while (1) {
CHECK_EOF();
if (*buf == '\015') {
++buf;
CHECK_EOF();
EXPECT_CHAR('\012');
++ret_cnt;
} else if (*buf == '\012') {
++buf;
++ret_cnt;
} else {
++buf;
ret_cnt = 0;
}
if (ret_cnt == 2) {
return buf;
}
}
*ret = -2;
return NULL;
}
#define PARSE_INT(valp_, mul_) \
if (*buf < '0' || '9' < *buf) { \
buf++; \
*ret = -1; \
return NULL; \
} \
*(valp_) = (mul_) * (*buf++ - '0');
#define PARSE_INT_3(valp_) \
do { \
int res_ = 0; \
PARSE_INT(&res_, 100) \
*valp_ = res_; \
PARSE_INT(&res_, 10) \
*valp_ += res_; \
PARSE_INT(&res_, 1) \
*valp_ += res_; \
} while (0)
/* returned pointer is always within [buf, buf_end), or null */
static const char *parse_token(const char *buf, const char *buf_end, const char **token, size_t *token_len, char next_char,
int *ret)
{
/* We use pcmpestri to detect non-token characters. This instruction can take no more than eight character ranges (8*2*8=128
* bits that is the size of a SSE register). Due to this restriction, characters `|` and `~` are handled in the slow loop. */
static const char ALIGNED(16) ranges[] = "\x00 " /* control chars and up to SP */
"\"\"" /* 0x22 */
"()" /* 0x28,0x29 */
",," /* 0x2c */
"//" /* 0x2f */
":@" /* 0x3a-0x40 */
"[]" /* 0x5b-0x5d */
"{\xff"; /* 0x7b-0xff */
const char *buf_start = buf;
int found;
buf = findchar_fast(buf, buf_end, ranges, sizeof(ranges) - 1, &found);
if (!found) {
CHECK_EOF();
}
while (1) {
if (*buf == next_char) {
break;
} else if (!token_char_map[(unsigned char)*buf]) {
*ret = -1;
return NULL;
}
++buf;
CHECK_EOF();
}
*token = buf_start;
*token_len = buf - buf_start;
return buf;
}
/* returned pointer is always within [buf, buf_end), or null */
static const char *parse_http_version(const char *buf, const char *buf_end, int *minor_version, int *ret)
{
/* we want at least [HTTP/1.<two chars>] to try to parse */
if (buf_end - buf < 9) {
*ret = -2;
return NULL;
}
EXPECT_CHAR_NO_CHECK('H');
EXPECT_CHAR_NO_CHECK('T');
EXPECT_CHAR_NO_CHECK('T');
EXPECT_CHAR_NO_CHECK('P');
EXPECT_CHAR_NO_CHECK('/');
EXPECT_CHAR_NO_CHECK('1');
EXPECT_CHAR_NO_CHECK('.');
PARSE_INT(minor_version, 1);
return buf;
}
static const char *parse_headers(const char *buf, const char *buf_end, struct phr_header *headers, size_t *num_headers,
size_t max_headers, int *ret)
{
for (;; ++*num_headers) {
CHECK_EOF();
if (*buf == '\015') {
++buf;
EXPECT_CHAR('\012');
break;
} else if (*buf == '\012') {
++buf;
break;
}
if (*num_headers == max_headers) {
*ret = -1;
return NULL;
}
if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) {
/* parsing name, but do not discard SP before colon, see
* http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */
if ((buf = parse_token(buf, buf_end, &headers[*num_headers].name, &headers[*num_headers].name_len, ':', ret)) == NULL) {
return NULL;
}
if (headers[*num_headers].name_len == 0) {
*ret = -1;
return NULL;
}
++buf;
for (;; ++buf) {
CHECK_EOF();
if (!(*buf == ' ' || *buf == '\t')) {
break;
}
}
} else {
headers[*num_headers].name = NULL;
headers[*num_headers].name_len = 0;
}
const char *value;
size_t value_len;
if ((buf = get_token_to_eol(buf, buf_end, &value, &value_len, ret)) == NULL) {
return NULL;
}
/* remove trailing SPs and HTABs */
const char *value_end = value + value_len;
for (; value_end != value; --value_end) {
const char c = *(value_end - 1);
if (!(c == ' ' || c == '\t')) {
break;
}
}
headers[*num_headers].value = value;
headers[*num_headers].value_len = value_end - value;
}
return buf;
}
static const char *parse_request(const char *buf, const char *buf_end, const char **method, size_t *method_len, const char **path,
size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers,
size_t max_headers, int *ret)
{
/* skip first empty line (some clients add CRLF after POST content) */
CHECK_EOF();
if (*buf == '\015') {
++buf;
EXPECT_CHAR('\012');
} else if (*buf == '\012') {
++buf;
}
/* parse request line */
if ((buf = parse_token(buf, buf_end, method, method_len, ' ', ret)) == NULL) {
return NULL;
}
do {
++buf;
CHECK_EOF();
} while (*buf == ' ');
ADVANCE_TOKEN(*path, *path_len);
do {
++buf;
CHECK_EOF();
} while (*buf == ' ');
if (*method_len == 0 || *path_len == 0) {
*ret = -1;
return NULL;
}
if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) {
return NULL;
}
if (*buf == '\015') {
++buf;
EXPECT_CHAR('\012');
} else if (*buf == '\012') {
++buf;
} else {
*ret = -1;
return NULL;
}
return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret);
}
int phr_parse_request(const char *buf_start, size_t len, const char **method, size_t *method_len, const char **path,
size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len)
{
const char *buf = buf_start, *buf_end = buf_start + len;
size_t max_headers = *num_headers;
int r;
*method = NULL;
*method_len = 0;
*path = NULL;
*path_len = 0;
*minor_version = -1;
*num_headers = 0;
/* if last_len != 0, check if the request is complete (a fast countermeasure
againt slowloris */
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
return r;
}
if ((buf = parse_request(buf, buf_end, method, method_len, path, path_len, minor_version, headers, num_headers, max_headers,
&r)) == NULL) {
return r;
}
return (int)(buf - buf_start);
}
static const char *parse_response(const char *buf, const char *buf_end, int *minor_version, int *status, const char **msg,
size_t *msg_len, struct phr_header *headers, size_t *num_headers, size_t max_headers, int *ret)
{
/* parse "HTTP/1.x" */
if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) {
return NULL;
}
/* skip space */
if (*buf != ' ') {
*ret = -1;
return NULL;
}
do {
++buf;
CHECK_EOF();
} while (*buf == ' ');
/* parse status code, we want at least [:digit:][:digit:][:digit:]<other char> to try to parse */
if (buf_end - buf < 4) {
*ret = -2;
return NULL;
}
PARSE_INT_3(status);
/* get message including preceding space */
if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) {
return NULL;
}
if (*msg_len == 0) {
/* ok */
} else if (**msg == ' ') {
/* Remove preceding space. Successful return from `get_token_to_eol` guarantees that we would hit something other than SP
* before running past the end of the given buffer. */
do {
++*msg;
--*msg_len;
} while (**msg == ' ');
} else {
/* garbage found after status code */
*ret = -1;
return NULL;
}
return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret);
}
int phr_parse_response(const char *buf_start, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len,
struct phr_header *headers, size_t *num_headers, size_t last_len)
{
const char *buf = buf_start, *buf_end = buf + len;
size_t max_headers = *num_headers;
int r;
*minor_version = -1;
*status = 0;
*msg = NULL;
*msg_len = 0;
*num_headers = 0;
/* if last_len != 0, check if the response is complete (a fast countermeasure
against slowloris */
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
return r;
}
if ((buf = parse_response(buf, buf_end, minor_version, status, msg, msg_len, headers, num_headers, max_headers, &r)) == NULL) {
return r;
}
return (int)(buf - buf_start);
}
int phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len)
{
const char *buf = buf_start, *buf_end = buf + len;
size_t max_headers = *num_headers;
int r;
*num_headers = 0;
/* if last_len != 0, check if the response is complete (a fast countermeasure
against slowloris */
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
return r;
}
if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, &r)) == NULL) {
return r;
}
return (int)(buf - buf_start);
}
enum {
CHUNKED_IN_CHUNK_SIZE,
CHUNKED_IN_CHUNK_EXT,
CHUNKED_IN_CHUNK_DATA,
CHUNKED_IN_CHUNK_CRLF,
CHUNKED_IN_TRAILERS_LINE_HEAD,
CHUNKED_IN_TRAILERS_LINE_MIDDLE
};
static int decode_hex(int ch)
{
if ('0' <= ch && ch <= '9') {
return ch - '0';
} else if ('A' <= ch && ch <= 'F') {
return ch - 'A' + 0xa;
} else if ('a' <= ch && ch <= 'f') {
return ch - 'a' + 0xa;
} else {
return -1;
}
}
ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *_bufsz)
{
size_t dst = 0, src = 0, bufsz = *_bufsz;
ssize_t ret = -2; /* incomplete */
while (1) {
switch (decoder->_state) {
case CHUNKED_IN_CHUNK_SIZE:
for (;; ++src) {
int v;
if (src == bufsz)
goto Exit;
if ((v = decode_hex(buf[src])) == -1) {
if (decoder->_hex_count == 0) {
ret = -1;
goto Exit;
}
break;
}
if (decoder->_hex_count == sizeof(size_t) * 2) {
ret = -1;
goto Exit;
}
decoder->bytes_left_in_chunk = decoder->bytes_left_in_chunk * 16 + v;
++decoder->_hex_count;
}
decoder->_hex_count = 0;
decoder->_state = CHUNKED_IN_CHUNK_EXT;
/* fallthru */
case CHUNKED_IN_CHUNK_EXT:
/* RFC 7230 A.2 "Line folding in chunk extensions is disallowed" */
for (;; ++src) {
if (src == bufsz)
goto Exit;
if (buf[src] == '\012')
break;
}
++src;
if (decoder->bytes_left_in_chunk == 0) {
if (decoder->consume_trailer) {
decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
break;
} else {
goto Complete;
}
}
decoder->_state = CHUNKED_IN_CHUNK_DATA;
/* fallthru */
case CHUNKED_IN_CHUNK_DATA: {
size_t avail = bufsz - src;
if (avail < decoder->bytes_left_in_chunk) {
if (dst != src)
memmove(buf + dst, buf + src, avail);
src += avail;
dst += avail;
decoder->bytes_left_in_chunk -= avail;
goto Exit;
}
if (dst != src)
memmove(buf + dst, buf + src, decoder->bytes_left_in_chunk);
src += decoder->bytes_left_in_chunk;
dst += decoder->bytes_left_in_chunk;
decoder->bytes_left_in_chunk = 0;
decoder->_state = CHUNKED_IN_CHUNK_CRLF;
}
/* fallthru */
case CHUNKED_IN_CHUNK_CRLF:
for (;; ++src) {
if (src == bufsz)
goto Exit;
if (buf[src] != '\015')
break;
}
if (buf[src] != '\012') {
ret = -1;
goto Exit;
}
++src;
decoder->_state = CHUNKED_IN_CHUNK_SIZE;
break;
case CHUNKED_IN_TRAILERS_LINE_HEAD:
for (;; ++src) {
if (src == bufsz)
goto Exit;
if (buf[src] != '\015')
break;
}
if (buf[src++] == '\012')
goto Complete;
decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE;
/* fallthru */
case CHUNKED_IN_TRAILERS_LINE_MIDDLE:
for (;; ++src) {
if (src == bufsz)
goto Exit;
if (buf[src] == '\012')
break;
}
++src;
decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
break;
default:
assert(!"decoder is corrupt");
}
}
Complete:
ret = bufsz - src;
Exit:
if (dst != src)
memmove(buf + dst, buf + src, bufsz - src);
*_bufsz = dst;
return ret;
}
int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder)
{
return decoder->_state == CHUNKED_IN_CHUNK_DATA;
}
#undef CHECK_EOF
#undef EXPECT_CHAR
#undef ADVANCE_TOKEN