Hello libcode-seafire-common

This commit is contained in:
G.H.O.S.T 2024-12-24 22:18:00 +01:00
commit 674b3f7cd8
Signed by: G.H.O.S.T
GPG Key ID: 3BD93EABD1407B82
47 changed files with 2046 additions and 0 deletions

17
.editorconfig Normal file
View File

@ -0,0 +1,17 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
indent_size = 4
max_line_length = off
trim_trailing_whitespace = false
[*.yaml]
indent_size = 2

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto

View File

@ -0,0 +1,24 @@
name: on-push
on: [push]
jobs:
build-and-test:
runs-on: linux
container: code.helloryan.se/infra/buildenv/cxx-amd64-fedora-40:latest
volumes:
- /build
steps:
- name: Clone repository
uses: actions/checkout@v3
- name: Authenticate
run: |
git config unset http.https://code.helloryan.se/.extraheader
echo "${{ secrets.NETRC }}" >> ~/.netrc
- name: Initialize
run: |
bpkg create -d /build cc config.cc.coptions="-Wall -Werror"
bdep init -A /build
- name: Build
run: b
- name: Test
run: b test

32
.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
.bdep/
Doxyfile
# Local default options files.
#
.build2/local/
# Compiler/linker output.
#
*.d
*.t
*.i
*.i.*
*.ii
*.ii.*
*.o
*.obj
*.gcm
*.pcm
*.ifc
*.so
*.dylib
*.dll
*.a
*.lib
*.exp
*.pdb
*.ilk
*.exe
*.exe.dlls/
*.exe.manifest
*.pc

31
LICENSE Normal file
View File

@ -0,0 +1,31 @@
Copyright © 2024 Ryan. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software must
display the following acknowledgement:
This product includes software developed by Ryan, http://helloryan.se/.
4. Neither the name(s) of the copyright holder(s) nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

21
README.md Normal file
View File

@ -0,0 +1,21 @@
# libcode-seafire-common
![Build status](https://code.helloryan.se/code/libcode-seafire-common/actions/workflows/on-push.yaml/badge.svg)
## Requirements
None, other than a modern C++-compiler.
## Building
See the wiki, https://code.helloryan.se/code/wiki/wiki/Build-Instructions, for
build instructions.
## Contact
Please report bugs and issues by sending an e-mail to: ryan@helloryan.se.
## Contributing
Please send an e-mail to ryan@helloryan.se to request an account and
write-access to the libcode-seafire-common repository.

4
build/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/config.build
/root/
/bootstrap/
build/

7
build/bootstrap.build Normal file
View File

@ -0,0 +1,7 @@
project = libcode-seafire-common
using version
using config
using test
using install
using dist

6
build/export.build Normal file
View File

@ -0,0 +1,6 @@
$out_root/
{
include code/seafire/common/
}
export $out_root/code/seafire/common/$import.target

16
build/root.build Normal file
View File

@ -0,0 +1,16 @@
# Uncomment to suppress warnings coming from external libraries.
#
#cxx.internal.scope = current
cxx.std = latest
using cxx
hxx{*}: extension = hxx
ixx{*}: extension = ixx
txx{*}: extension = txx
cxx{*}: extension = cxx
# The test target for cross-testing (running tests under Wine, etc).
#
test.target = $cxx.target

5
buildfile Normal file
View File

@ -0,0 +1,5 @@
./: {code/ tests/} doc{README.md} legal{LICENSE} manifest
# Don't install tests.
#
tests/: install = false

9
code/seafire/common/.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
# Generated version header.
#
version.hxx
# Unit test executables and Testscript output directories
# (can be symlinks).
#
*.test
test-*.test

View File

@ -0,0 +1,15 @@
#include <code/seafire/common/allocator.hxx>
namespace code::seafire::common
{
allocator_t::
allocator_t() = default;
allocator_t::allocation_t::
allocation_t() = default;
allocator_t::allocation_t::
~allocation_t() noexcept = default;
} // namespace code::seafire::common

View File

@ -0,0 +1,66 @@
#ifndef code__seafire__common__allocator_hxx_
#define code__seafire__common__allocator_hxx_
#include <memory>
#include <set>
namespace code::seafire::common
{
/// Implements a memory allocator keeping track of allocations.
///
/// All allocations made through an allocator_t are valid for the
/// lifetime of the allocator_t object.
///
class allocator_t
{
public:
allocator_t();
allocator_t(allocator_t const&) = delete;
allocator_t(allocator_t&&) = delete;
template<typename T>
T&
alloc();
template<typename T>
T&
alloc(T const&);
template<typename T>
T&
alloc(T&&);
template<typename T, typename... Args>
T&
alloc_emplace(Args&&...);
allocator_t& operator=(allocator_t const&) = delete;
allocator_t& operator=(allocator_t&&) = delete;
private:
struct allocation_t
{
allocation_t();
allocation_t(allocation_t const&) = delete;
allocation_t(allocation_t&&) = delete;
virtual
~allocation_t() noexcept;
allocation_t& operator=(allocation_t const&) = delete;
allocation_t& operator=(allocation_t&&) = delete;
};
std::set<std::unique_ptr<allocation_t>> allocations_;
};
} // namespace code::seafire::common
#include <code/seafire/common/allocator.txx>
#endif

View File

@ -0,0 +1,55 @@
namespace code::seafire::common
{
template<typename T>
T&
allocator_t::
alloc()
{
return alloc_emplace<T>();
}
template<typename T>
T&
allocator_t::
alloc(T const& object)
{
return alloc_emplace<T>(object);
}
template<typename T>
T&
allocator_t::
alloc(T&& object)
{
return alloc_emplace<T>(object);
}
template<typename T, typename... Args>
T&
allocator_t::
alloc_emplace(Args&&... args)
{
struct object_t
: allocation_t
{
object_t(Args&&... args)
: object{std::forward<Args>(args)...}
{}
T object;
};
std::unique_ptr<object_t> a{
new object_t{std::forward<Args>(args)...}
};
auto& ref = a->object;
allocations_.emplace(std::move(a));
return ref;
}
} // namespace code::seafire::common

View File

@ -0,0 +1,65 @@
intf_libs = # Interface dependencies.
impl_libs = # Implementation dependencies.
import intf_libs =+ libasio%lib{asio}
./: lib{code-seafire-common}: libul{code-seafire-common}
libul{code-seafire-common}: {hxx ixx txx cxx}{** -**.test... -version} \
{hxx }{ version}
libul{code-seafire-common}: $impl_libs $intf_libs
# Unit tests.
#
exe{*.test}:
{
test = true
install = false
}
for t: cxx{**.test...}
{
d = $directory($t)
n = $name($t)...
./: $d/exe{$n}: $t $d/{hxx ixx txx}{+$n} $d/testscript{+$n}
$d/exe{$n}: libul{code-seafire-common}: bin.whole = false
}
hxx{version}: in{version} $src_root/manifest
{
dist = true
clean = ($src_root != $out_root)
}
# Build options.
#
cxx.poptions =+ "-I$out_root" "-I$src_root"
cxx.coptions =+ -fvisibility=hidden
# Export options.
#
lib{code-seafire-common}:
{
cxx.export.poptions = "-I$out_root" "-I$src_root"
cxx.export.libs = $intf_libs
}
# For pre-releases use the complete version to make sure they cannot
# be used in place of another pre-release or the final version. See
# the version module for details on the version.* variable values.
#
if $version.pre_release
lib{code-seafire-common}: bin.lib.version = "-$version.project_id"
else
lib{code-seafire-common}: bin.lib.version = "-$version.major.$version.minor"
# Install into the code/seafire/common/ subdirectory of, say, /usr/include/
# recreating subdirectories.
#
{hxx ixx txx}{*}:
{
install = include/code/seafire/common/
install.subdirs = true
}

View File

@ -0,0 +1,132 @@
#include <code/seafire/common/diagnostics.hxx>
#include <iomanip>
namespace code::seafire::common
{
void
diagnostics_t::
enable(category_t const& category)
{
categories_.emplace(&category);
}
void
diagnostics_t::
disable(category_t const& category)
{
categories_.erase(&category);
}
diagnostics_t::proxy_t
diagnostics_t::
operator<<(category_t const& category)
{
return proxy_t{*this, category};
}
diagnostics_t::
diagnostics_t() = default;
diagnostics_t::
~diagnostics_t() noexcept = default;
bool
diagnostics_t::
is_enabled(category_t const& category)
{
return categories_.count(&category) == 1;
}
void
diagnostics_t::
log(record_t record)
{
if (!is_enabled(record.category)) {
return;
}
std::size_t max{0};
for (auto const& j : categories_) {
if (auto size = j->prefix().size(); size > max) {
max = size;
}
}
std::stringstream message;
for (std::string line; std::getline(record.message, line);) {
message << " -> "
<< std::left
<< std::setw(max)
<< record.category.prefix()
<< ": "
<< line
<< '\n';
}
do_log(message.str());
}
diagnostics_t::category_t::
category_t(std::string prefix)
: prefix_{std::move(prefix)}
{}
diagnostics_t::category_t::
~category_t() noexcept = default;
std::string const&
diagnostics_t::category_t::
prefix() const
{
return prefix_;
}
diagnostics_t::proxy_t::
~proxy_t() noexcept
{
if (diagnostics_) {
diagnostics_->log({category_, std::move(message_)});
}
}
diagnostics_t::proxy_t::
proxy_t(diagnostics_t& diagnostics, category_t const& category)
: diagnostics_{&diagnostics},
category_{category}
{}
diagnostics_t::proxy_t::
proxy_t(proxy_t&& other) noexcept
: category_{other.category_}
{
std::swap(diagnostics_, other.diagnostics_);
}
ostream_diagnostics_t::
ostream_diagnostics_t(std::ostream& output)
: output_{output}
{}
ostream_diagnostics_t::
~ostream_diagnostics_t() noexcept = default;
std::ostream&
ostream_diagnostics_t::
output()
{
return output_;
}
void
ostream_diagnostics_t::
do_log(std::string const& message)
{
output() << message;
}
} // namespace code::seafire::common

View File

@ -0,0 +1,160 @@
#ifndef code__seafire__common__diagnostics_hxx_
#define code__seafire__common__diagnostics_hxx_
#include <ostream>
#include <set>
#include <sstream>
#include <string>
namespace code::seafire::common
{
/// Provides a base for implementing diagnostics.
///
class diagnostics_t
{
public:
class category_t;
class proxy_t;
friend proxy_t;
void
enable(category_t const&);
void
disable(category_t const&);
proxy_t
operator<<(category_t const&);
protected:
struct record_t;
diagnostics_t();
diagnostics_t(diagnostics_t const&) = delete;
diagnostics_t(diagnostics_t&&) = delete;
~diagnostics_t() noexcept;
bool
is_enabled(category_t const&);
void
log(record_t);
virtual
void
do_log(std::string const&) = 0;
diagnostics_t& operator=(diagnostics_t const&) = delete;
diagnostics_t& operator=(diagnostics_t&&) = delete;
private:
std::set<category_t const*> categories_;
};
/// Represents a diagnostic category.
///
class diagnostics_t::category_t
{
public:
explicit
category_t(std::string);
category_t(category_t const&) = delete;
category_t(category_t&&) = delete;
~category_t() noexcept;
std::string const&
prefix() const;
category_t& operator=(category_t const&) = delete;
category_t& operator=(category_t&&) = delete;
private:
std::string prefix_;
};
/// Proxy for writing diagnostics.
///
class diagnostics_t::proxy_t
{
public:
~proxy_t() noexcept;
template<typename T>
proxy_t&
operator<<(T const& other)
{
message_ << other;
return *this;
}
protected:
friend diagnostics_t;
proxy_t(diagnostics_t&, category_t const&);
proxy_t(proxy_t const&) = delete;
proxy_t(proxy_t&&) noexcept;
proxy_t& operator=(proxy_t const&) = delete;
proxy_t& operator=(proxy_t&&) = delete;
private:
diagnostics_t* diagnostics_{};
category_t const& category_;
std::stringstream message_;
};
/// Represents a diagnostic record.
///
struct diagnostics_t::record_t
{
/// The diagnostic category.
///
category_t const& category;
/// The diagnostic message.
///
std::stringstream message;
};
class ostream_diagnostics_t
: public diagnostics_t
{
public:
explicit
ostream_diagnostics_t(std::ostream&);
ostream_diagnostics_t(ostream_diagnostics_t const&) = delete;
ostream_diagnostics_t(ostream_diagnostics_t&&) = delete;
~ostream_diagnostics_t() noexcept;
std::ostream&
output();
ostream_diagnostics_t& operator=(ostream_diagnostics_t const&) = delete;
ostream_diagnostics_t& operator=(ostream_diagnostics_t&&) = delete;
protected:
void
do_log(std::string const&) override;
private:
std::ostream& output_;
};
} // namespace code::seafire::common
#endif

View File

@ -0,0 +1,48 @@
#include <code/seafire/common/extension-context.hxx>
namespace code::seafire::common
{
void*
extension_context_t::
use(std::type_index const& key) const
{
auto it = extensions_.find(key);
if (it == extensions_.end())
throw extension_not_found_t{};
return it->second;
}
void
extension_context_t::
extend(std::type_index const& key, void* ptr)
{
if (0 != extensions_.count(key))
throw duplicate_extension_t{};
if (!ptr)
throw std::invalid_argument{"invalid pointer"};
extensions_.emplace(key, ptr);
}
void
extension_context_t::
erase_extension(std::type_index const& key)
{
extensions_.erase(key);
}
extension_not_found_t::
extension_not_found_t()
: std::runtime_error{"extension not found"}
{}
duplicate_extension_t::
duplicate_extension_t()
: std::runtime_error{"extension already registered"}
{}
} // namespace code::seafire::common

View File

@ -0,0 +1,91 @@
#ifndef code__seafire__common__extension_context_hxx_
#define code__seafire__common__extension_context_hxx_
#include <map>
#include <stdexcept>
#include <typeindex>
namespace code::seafire::common
{
/// Implements a context tracking extensions.
///
/// Ownership of registered extensions is not assumed.
///
class extension_context_t
{
public:
template<typename E>
E&
use();
template<typename E>
E const&
use() const;
template<typename E>
void
extend(E* ptr);
template<typename E>
void
erase();
private:
void*
use(std::type_index const& key) const;
void
extend(std::type_index const& key, void* ptr);
void
erase_extension(std::type_index const& key);
std::map<std::type_index, void*> extensions_;
};
class extension_not_found_t
: public std::runtime_error
{
public:
extension_not_found_t();
};
class duplicate_extension_t
: public std::runtime_error
{
public:
duplicate_extension_t();
};
/// Provides extending an extension context using RAII.
///
template<typename E>
class extend_t
{
public:
using extension_type = typename std::decay_t<E>;
extend_t(extension_context_t&, extension_type&);
extend_t(extend_t const&) = delete;
extend_t(extend_t&&) = delete;
~extend_t() noexcept;
extend_t& operator=(extend_t const&) = delete;
extend_t& operator=(extend_t&&) = delete;
private:
extension_context_t& target_;
};
} // namespace code::seafire::common
#include <code/seafire/common/extension-context.txx>
#endif

View File

@ -0,0 +1,63 @@
namespace code::seafire::common
{
/// Use an extension of type E.
///
template<typename E>
E&
extension_context_t::
use()
{
static std::type_index const key{typeid(E)};
return *(static_cast<E*>(use(key)));
}
/// Use an extension of type E.
///
template<typename E>
E const&
extension_context_t::
use() const
{
static std::type_index const key{typeid(E)};
return *(static_cast<E*>(use(key)));
}
/// Add an extension to this context.
///
template<typename E>
void
extension_context_t::
extend(E* ptr)
{
static std::type_index const key{typeid(E)};
extend(key, ptr);
}
/// Erase an extension from this context.
///
template<typename E>
void
extension_context_t::
erase()
{
static std::type_index const key{typeid(E)};
erase_extension(key);
}
template<typename E>
extend_t<E>::
extend_t(extension_context_t& target, extension_type& ext)
: target_{target}
{
target.extend(&ext);
}
template<typename E>
extend_t<E>::
~extend_t() noexcept
{
target_.erase<extension_type>();
}
} // namespace code::seafire::common

View File

@ -0,0 +1,54 @@
#ifndef code__seafire__common__invoke_hxx_
#define code__seafire__common__invoke_hxx_
#include <code/seafire/server/request.hxx>
#include <code/seafire/server/response.hxx>
namespace code::seafire::common
{
template<typename Ret, typename T, typename... Direct, typename... Params>
Ret
do_invoke(T& target,
server::request_t& req,
Direct const&... direct,
Ret (T::*func)(Direct const&..., Params const&...));
template<typename Ret, typename T, typename... Params, typename... Direct>
Ret
invoke(T& target,
server::request_t& req,
Ret (T::*func)(Params const&...),
Direct const&... direct);
template<typename Ret, typename T, typename... Direct, typename... Params>
Ret
do_invoke(T const& target,
server::request_t& req,
Direct const&... direct,
Ret (T::*func)(Direct const&..., Params const&...) const);
template<typename Ret, typename T, typename... Params, typename... Direct>
Ret
invoke(T const& target,
server::request_t& req,
Ret (T::*func)(Params const&...) const,
Direct const&... direct);
template<typename Ret, typename... Direct, typename... Params>
Ret
do_invoke(server::request_t& req,
Direct&&... direct,
Ret (*func)(Direct..., Params...));
template<typename Ret, typename... Params, typename... Direct>
Ret
invoke(server::request_t& req,
Ret (*func)(Params...),
Direct&&... direct);
} // namespace code::seafire::common
#include <code/seafire/common/invoke.txx>
#endif

View File

@ -0,0 +1,62 @@
namespace code::seafire::common
{
template<typename Ret, typename T, typename... Direct, typename... Params>
Ret
do_invoke(T& target,
server::request_t& req,
Direct const&... direct,
Ret (T::*func)(Direct const&..., Params const&...))
{
return (target.*func)(direct..., std::decay_t<Params>::fetch(req)...);
}
template<typename Ret, typename T, typename... Params, typename... Direct>
Ret
invoke(T& target,
server::request_t& req,
Ret (T::*func)(Params const&...),
Direct const&... direct)
{
return do_invoke<Ret, T, Direct...>(target, req, direct..., func);
}
template<typename Ret, typename T, typename... Direct, typename... Params>
Ret
do_invoke(T const& target,
server::request_t& req,
Direct const&... direct,
Ret (T::*func)(Direct const&..., Params const&...) const)
{
return (target.*func)(direct..., std::decay_t<Params>::fetch(req)...);
}
template<typename Ret, typename T, typename... Params, typename... Direct>
Ret
invoke(T const& target,
server::request_t& req,
Ret (T::*func)(Params const&...) const,
Direct const&... direct)
{
return do_invoke<Ret, T, Direct...>(target, req, direct..., func);
}
template<typename Ret, typename... Direct, typename... Params>
Ret
do_invoke(server::request_t& req,
Direct&&... direct,
Ret (*func)(Direct..., Params...))
{
return (*func)(direct..., std::decay_t<Params>::fetch(req)...);
}
template<typename Ret, typename... Params, typename... Direct>
Ret
invoke(server::request_t& req,
Ret (*func)(Params...),
Direct&&... direct)
{
return do_invoke<Ret, Direct...>(req, direct..., func);
}
} // namespace code::seafire::common

View File

@ -0,0 +1,12 @@
#include <code/seafire/common/io/acceptor.hxx>
namespace code::seafire::common::io
{
acceptor_t::
~acceptor_t() noexcept = default;
acceptor_t::
acceptor_t() = default;
} // namespace code::seafire::common::io

View File

@ -0,0 +1,51 @@
#ifndef code__seafire__common__io__acceptor_hxx_
#define code__seafire__common__io__acceptor_hxx_
#include <code/seafire/common/io/stream.hxx>
#include <asio.hpp>
#include <functional>
#include <memory>
namespace code::seafire::common::io
{
/// Abstract base class for acceptors.
///
class acceptor_t
{
public:
/// Non-blocking accept handler type.
///
using accept_handler_t = std::function<
void(std::error_code, std::unique_ptr<stream_t>)
>;
virtual
~acceptor_t() noexcept;
virtual
asio::any_io_executor const&
get_executor() = 0;
/// Initiate a non-blocking accept of a new stream.
///
virtual
void
async_accept(accept_handler_t) = 0;
protected:
acceptor_t();
acceptor_t(acceptor_t const&) = delete;
acceptor_t(acceptor_t&&) = delete;
acceptor_t& operator=(acceptor_t const&) = delete;
acceptor_t& operator=(acceptor_t&&) = delete;
};
} // namespace code::seafire::common::io
#endif

View File

@ -0,0 +1,23 @@
#ifndef code__seafire__common__io__buffer_hxx_
#define code__seafire__common__io__buffer_hxx_
#include <asio.hpp>
#include <vector>
namespace code::seafire::common::io
{
using const_buffer_t = asio::const_buffer;
using const_buffers_t = std::vector<const_buffer_t>;
using mutable_buffer_t = asio::mutable_buffer;
using mutable_buffers_t = std::vector<mutable_buffer_t>;
using asio::buffer;
using asio::buffer_size;
using asio::buffer_copy;
} // namespace code::seafire::common::io
#endif

View File

@ -0,0 +1,13 @@
#include <code/seafire/common/io/diagnostics.hxx>
namespace code::seafire::common::io
{
common::diagnostics_t::category_t const&
io_category()
{
static common::diagnostics_t::category_t category{"io"};
return category;
}
} // namespace code::seafire::common::io

View File

@ -0,0 +1,14 @@
#ifndef code__seafire__common__io_diagnostics_hxx_
#define code__seafire__common__io_diagnostics_hxx_
#include <code/seafire/common/diagnostics.hxx>
namespace code::seafire::common::io
{
common::diagnostics_t::category_t const&
io_category();
} // namespace code::seafire::common::io
#endif

View File

@ -0,0 +1,31 @@
#include <code/seafire/common/io/error.hxx>
namespace code::seafire::common::io
{
std::error_code
make_error_code(error_t e)
{
static
struct : std::error_category
{
char const* name() const noexcept override
{
return "seafire.common.io";
}
std::string message(int e) const override
{
switch (static_cast<error_t>(e)) {
case error_t::unknown: return "unknown error";
case error_t::read_until_buffer_overflow: return "buffer overflow";
}
return "(unrecognized error)";
}
} category;
return {static_cast<int>(e), category};
}
} // namespace code::seafire::common::io

View File

@ -0,0 +1,37 @@
#ifndef code__seafire__common__io__error_hxx_
#define code__seafire__common__io__error_hxx_
#include <system_error>
#include <type_traits>
namespace code::seafire::common::io
{
/// Common error codes.
///
enum class error_t
{
/// Represents an unknown error.
///
unknown = 1,
read_until_buffer_overflow
};
std::error_code
make_error_code(error_t);
} // namespace code::seafire::common::io
namespace std
{
template<>
struct is_error_code_enum<::code::seafire::common::io::error_t>
: true_type
{};
} // namespace std
#endif

View File

@ -0,0 +1,131 @@
#include <code/seafire/common/io/read-until.hxx>
#include <code/seafire/common/io/error.hxx>
namespace code::seafire::common::io
{
std::size_t
read_until(stream_t& s, asio::streambuf& b, match_condition_t m)
{
std::error_code ec;
auto n = read_until(s, b, m, ec);
if (ec) {
throw std::system_error{ec};
}
return n;
}
std::size_t
read_until(stream_t& s,
asio::streambuf& b,
match_condition_t m,
std::error_code& ec)
{
for (;;) {
// search for a match.
//
auto begin = static_cast<char const*>(b.data().data());
auto result = m(begin, begin + b.data().size(), ec);
if (ec) {
return 0;
}
if (result.second) {
return result.first - begin;
}
// bail out if buffer is already full.
//
if (b.size() == b.max_size()) {
ec = make_error_code(error_t::read_until_buffer_overflow);
return 0;
}
// read more data.
//
auto bytes_to_read = std::min<std::size_t>(
std::max<std::size_t>( 512, b.capacity() - b.size()),
std::min<std::size_t>(65536, b.max_size() - b.size())
);
auto n = s.read(b.prepare(bytes_to_read), ec);
b.commit(n);
if (ec) {
return 0;
}
}
}
void
async_read_until(stream_t& s,
asio::streambuf& b,
match_condition_t m,
read_until_handler_t h)
{
struct operation_t
{
stream_t& s;
asio::streambuf& b;
match_condition_t match;
read_until_handler_t handler;
void
operator()(std::error_code const& ec,
std::size_t n,
bool start = false)
{
if (ec || (!start && n == 0)) {
handler(ec, 0);
return;
}
b.commit(n);
// search for a match.
//
auto begin = static_cast<char const*>(b.data().data());
std::error_code match_ec;
auto result = match(begin, begin + b.data().size(), match_ec);
if (match_ec) {
handler(match_ec, 0);
return;
}
if (result.second) {
handler({}, result.first - begin);
return;
}
if (b.size() == b.max_size()) {
handler(make_error_code(error_t::read_until_buffer_overflow), 0);
return;
}
// read more data.
//
auto bytes_to_read = std::min<std::size_t>(
std::max<std::size_t>( 512, b.capacity() - b.size()),
std::min<std::size_t>(65536, b.max_size() - b.size())
);
s.async_read(b.prepare(bytes_to_read), std::move(*this));
}
};
auto init = [&s, &b, m, h]
{
operation_t{s, b, m, h}({}, 0, true);
};
asio::post(s.get_executor(), init);
}
} // namespace code::seafire::common::io

View File

@ -0,0 +1,39 @@
#ifndef code__seafire__common__io__read_until_hxx_
#define code__seafire__common__io__read_until_hxx_
#include <code/seafire/common/io/stream.hxx>
#include <functional>
#include <system_error>
#include <asio.hpp>
namespace code::seafire::common::io
{
using match_condition_t = std::function<
std::pair<char const*, bool>(char const*, char const*, std::error_code&)
>;
using read_until_handler_t = std::function<
void(std::error_code, std::size_t)
>;
std::size_t
read_until(stream_t&, asio::streambuf&, match_condition_t);
std::size_t
read_until(stream_t&,
asio::streambuf&,
match_condition_t,
std::error_code&);
void
async_read_until(stream_t&,
asio::streambuf&,
match_condition_t,
read_until_handler_t);
} // namespace code::seafire::common::io
#endif

View File

@ -0,0 +1,12 @@
#include <code/seafire/common/io/stream.hxx>
namespace code::seafire::common::io
{
stream_t::
~stream_t() noexcept = default;
stream_t::
stream_t() = default;
} // namespace code::seafire::common::io

View File

@ -0,0 +1,137 @@
#ifndef code__seafire__common__io__stream_hxx_
#define code__seafire__common__io__stream_hxx_
#include <code/seafire/common/io/buffer.hxx>
#include <asio.hpp>
#include <functional>
#include <system_error>
namespace code::seafire::common::io
{
/// Abstract base class for stream.
///
class stream_t
{
public:
/// Non-blocking read handler type.
///
using read_handler_t = std::function<void(std::error_code, std::size_t)>;
/// Non-blocking write handler type.
///
using write_handler_t = std::function<void(std::error_code, std::size_t)>;
/// Non-blocking graceful close handler type.
///
using graceful_close_handler_t = std::function<void()>;
virtual
~stream_t() noexcept;
/// Get the asio executor associated with this stream.
///
virtual
asio::any_io_executor const&
get_executor() = 0;
/// Cancel any pending non-blocking operations.
///
virtual
void
cancel() = 0;
/// Close this stream.
///
virtual
void
close() = 0;
/// Close this stream.
///
virtual
void
close(std::error_code&) = 0;
/// Perform a blocking graceful close of this stream.
///
virtual
void
graceful_close() = 0;
/// Initiate a non-blocking graceful close of this stream.
///
virtual
void
async_graceful_close(graceful_close_handler_t) = 0;
/// Perform a blocking read on this stream.
///
virtual
std::size_t
read(mutable_buffer_t const&) = 0;
/// Perform a blocking read on this stream.
///
virtual
std::size_t
read(mutable_buffer_t const&, std::error_code&) = 0;
/// Initiate a non-blocking read on this stream.
///
virtual
void
async_read(mutable_buffer_t const&, read_handler_t) = 0;
/// Perform a blocking write on this stream.
///
virtual
std::size_t
write(const_buffer_t const&) = 0;
/// Perform a blocking write on this stream.
///
virtual
std::size_t
write(const_buffers_t const&) = 0;
/// Initiate a non-block write on this stream.
///
virtual
std::size_t
write(const_buffer_t const&, std::error_code&) = 0;
/// Initiate a non-block write on this stream.
///
virtual
std::size_t
write(const_buffers_t const&, std::error_code&) = 0;
/// Initiate a non-blocking write on this stream.
///
virtual
void
async_write(const_buffer_t const&, write_handler_t) = 0;
/// Initiate a non-blocking write on this stream.
///
virtual
void
async_write(const_buffers_t const&, write_handler_t) = 0;
protected:
stream_t();
stream_t(stream_t const&) = delete;
stream_t(stream_t&&) = delete;
stream_t& operator=(stream_t const&) = delete;
stream_t& operator=(stream_t&&) = delete;
};
} // namespace code::seafire::common::io
#endif

View File

@ -0,0 +1,36 @@
#include <code/seafire/common/io/tcp-acceptor.hxx>
#include <code/seafire/common/io/tcp-socket.hxx>
namespace code::seafire::common::io
{
tcp_acceptor_t::
tcp_acceptor_t(asio::io_context& io_context,
asio::ip::tcp::endpoint const& endpoint)
: acceptor_{io_context, endpoint}
{}
tcp_acceptor_t::
~tcp_acceptor_t() noexcept = default;
asio::any_io_executor const&
tcp_acceptor_t::
get_executor()
{
return acceptor_.get_executor();
}
void
tcp_acceptor_t::
async_accept(accept_handler_t handler)
{
auto bound = [handler](std::error_code const& ec, asio::ip::tcp::socket socket)
{
handler(ec, std::make_unique<tcp_socket_t>(std::move(socket)));
};
acceptor_.async_accept(bound);
}
} // namespace code::seafire::common::io

View File

@ -0,0 +1,43 @@
#ifndef code__seafire__common__io__tcp_acceptor_hxx_
#define code__seafire__common__io__tcp_acceptor_hxx_
#include <code/seafire/common/diagnostics.hxx>
#include <code/seafire/common/io/acceptor.hxx>
#include <asio.hpp>
namespace code::seafire::common::io
{
/// Implements an tcp acceptor.
///
class tcp_acceptor_t
: public acceptor_t
{
public:
tcp_acceptor_t(asio::io_context&,
asio::ip::tcp::endpoint const&);
tcp_acceptor_t(tcp_acceptor_t const&) = delete;
tcp_acceptor_t(tcp_acceptor_t&&) = delete;
~tcp_acceptor_t() noexcept override;
asio::any_io_executor const&
get_executor() override;
void
async_accept(accept_handler_t) override;
tcp_acceptor_t& operator=(tcp_acceptor_t const&) = delete;
tcp_acceptor_t& operator=(tcp_acceptor_t&&) = delete;
private:
asio::ip::tcp::acceptor acceptor_;
};
} // namespace code::seafire::common::io
#endif

View File

@ -0,0 +1,149 @@
#include <code/seafire/common/io/tcp-socket.hxx>
namespace code::seafire::common::io
{
/// Construct a new TCP/IP socket from an asio socket.
///
tcp_socket_t::
tcp_socket_t(asio::ip::tcp::socket socket)
: socket_{std::move(socket)}
{}
tcp_socket_t::
~tcp_socket_t() noexcept = default;
asio::any_io_executor const&
tcp_socket_t::
get_executor()
{
return socket_.get_executor();
}
void
tcp_socket_t::
cancel()
{
socket_.cancel();
}
void
tcp_socket_t::
close()
{
socket_.close();
}
void
tcp_socket_t::
close(std::error_code& ec)
{
socket_.close(ec);
}
void
tcp_socket_t::
graceful_close()
{
try {
socket_.shutdown(asio::ip::tcp::socket::shutdown_send);
socket_.read_some(buffer(throwaway_));
}
catch (...) {
// Ignore errors during shutdown/close.
//
}
}
void
tcp_socket_t::
async_graceful_close(graceful_close_handler_t handler)
{
std::error_code ec;
socket_.shutdown(asio::ip::tcp::socket::shutdown_send, ec);
if (ec) {
auto bound = [handler]()
{
handler();
};
asio::post(get_executor(), bound);
return;
}
auto bound = [handler](std::error_code const&, std::size_t)
{
// Errors are ignored during graceful shutdown/close, so we
// don't pass anything to the handler.
//
handler();
};
socket_.async_read_some(buffer(throwaway_), bound);
}
std::size_t
tcp_socket_t::
read(mutable_buffer_t const& buffer)
{
return socket_.read_some(buffer);
}
std::size_t
tcp_socket_t::
read(mutable_buffer_t const& buffer, std::error_code& ec)
{
return socket_.read_some(buffer, ec);
}
void
tcp_socket_t::
async_read(mutable_buffer_t const& buffer, read_handler_t handler)
{
return socket_.async_read_some(buffer, handler);
}
std::size_t
tcp_socket_t::
write(const_buffer_t const& buffer)
{
return asio::write(socket_, buffer);
}
std::size_t
tcp_socket_t::
write(const_buffers_t const& buffers)
{
return asio::write(socket_, buffers);
}
std::size_t
tcp_socket_t::
write(const_buffer_t const& buffer, std::error_code& ec)
{
return asio::write(socket_, buffer, ec);
}
std::size_t
tcp_socket_t::
write(const_buffers_t const& buffers, std::error_code& ec)
{
return asio::write(socket_, buffers, ec);
}
void
tcp_socket_t::
async_write(const_buffer_t const& buffer, write_handler_t handler)
{
asio::async_write(socket_, buffer, handler);
}
void
tcp_socket_t::
async_write(const_buffers_t const& buffers, write_handler_t handler)
{
asio::async_write(socket_, buffers, handler);
}
} // namespace code::seafire::common::io

View File

@ -0,0 +1,82 @@
#ifndef code__seafire__common__io__tcp_socket_hxx_
#define code__seafire__common__io__tcp_socket_hxx_
#include <code/seafire/common/io/buffer.hxx>
#include <code/seafire/common/io/stream.hxx>
#include <asio.hpp>
namespace code::seafire::common::io
{
/// Implements a TCP/IP socket.
///
class tcp_socket_t
: public stream_t
{
public:
explicit
tcp_socket_t(asio::ip::tcp::socket);
tcp_socket_t(tcp_socket_t const&) = delete;
tcp_socket_t(tcp_socket_t&&) = delete;
~tcp_socket_t() noexcept override;
asio::any_io_executor const&
get_executor() override;
void
cancel() override;
void
close() override;
void
close(std::error_code&) override;
void
graceful_close() override;
void
async_graceful_close(graceful_close_handler_t) override;
std::size_t
read(mutable_buffer_t const&) override;
std::size_t
read(mutable_buffer_t const&, std::error_code&) override;
void
async_read(mutable_buffer_t const&, read_handler_t) override;
std::size_t
write(const_buffer_t const&) override;
std::size_t
write(const_buffers_t const&) override;
std::size_t
write(const_buffer_t const&, std::error_code&) override;
std::size_t
write(const_buffers_t const&, std::error_code&) override;
void
async_write(const_buffer_t const&, write_handler_t) override;
void
async_write(const_buffers_t const&, write_handler_t) override;
tcp_socket_t& operator=(tcp_socket_t const&) = delete;
tcp_socket_t& operator=(tcp_socket_t&&) = delete;
private:
asio::ip::tcp::socket socket_;
char throwaway_[1];
};
} // namespace code::seafire::common::io
#endif

View File

@ -0,0 +1,162 @@
#ifndef code__seafire__common__traits_hxx_
#define code__seafire__common__traits_hxx_
#include <optional>
#include <tuple>
#include <type_traits>
namespace code::seafire::common::traits
{
// is_optional
//
template<typename T>
struct is_optional
: std::false_type
{};
template<typename T>
struct is_optional<std::optional<T>>
: std::true_type
{};
template<typename T>
constexpr bool is_optional_v{is_optional<T>::value};
// remove_optional
//
template<typename T>
struct remove_optional
{
using type = T;
};
template<typename T>
struct remove_optional<std::optional<T>>
{
using type = T;
};
template<typename T>
using remove_optional_t = remove_optional<T>::type;
// add_optional
//
template<typename T>
std::optional<remove_optional_t<T>>
add_optional(remove_optional<T>&& non_optional)
{
return std::optional<remove_optional<T>>{non_optional};
}
// function_traits
//
template<typename>
struct function_traits;
template<typename Ret, typename... Args>
struct function_traits<Ret(Args...)>
{
static constexpr std::size_t arity = sizeof...(Args);
using return_type = std::decay_t<Ret>;
using argument_tuple = std::tuple<std::decay_t<Args>...>;
};
template<typename Ret, typename Class, typename... Args>
struct function_traits<Ret (Class::*)(Args...)>
{
static constexpr std::size_t arity = sizeof...(Args);
using return_type = std::decay_t<Ret>;
using class_type = Class;
using argument_tuple = std::tuple<std::decay_t<Args>...>;
};
template<typename Ret, typename Class, typename... Args>
struct function_traits<Ret (Class::*)(Args...) const>
{
static constexpr std::size_t arity = sizeof...(Args);
using return_type = std::decay_t<Ret>;
using class_type = Class;
using argument_tuple = std::tuple<std::decay_t<Args>...>;
};
template<typename Ret, typename... Args>
struct function_traits<Ret(*)(Args...)>
{
static constexpr std::size_t arity = sizeof...(Args);
using return_type = std::decay_t<Ret>;
using argument_tuple = std::tuple<std::decay_t<Args>...>;
};
// return_type_t
//
template<typename F>
using return_type_t = typename function_traits<F>::return_type;
// first_arg
//
template<typename F>
struct first_arg
{
using function_traits = traits::function_traits<F>;
using type = std::tuple_element_t<0, typename function_traits::argument_tuple>;
};
template<typename F>
using first_arg_t = first_arg<F>::type;
// function_arg_n
//
template<
typename F,
std::size_t arg,
std::size_t arity = function_traits<F>::arity
>
struct function_arg_n;
template<
typename F,
std::size_t arg
>
struct function_arg_n<F, arg, 0>
{
using function_traits = traits::function_traits<F>;
using type = void;
};
template<
typename F,
std::size_t arg
>
struct function_arg_n<F, arg, 1>
{
using function_traits = traits::function_traits<F>;
using type = std::tuple_element_t<arg, typename function_traits::argument_tuple>;
};
template<
typename F,
std::size_t arg
>
struct function_arg_n<F, arg, 2>
{
using function_traits = traits::function_traits<F>;
using type = std::tuple_element_t<arg, typename function_traits::argument_tuple>;
};
template<
typename F,
std::size_t arg
>
using function_arg_n_t = function_arg_n<F, arg>::type;
} // namespace code::seafire::common::traits
#endif

View File

@ -0,0 +1,37 @@
#ifndef code__seafire__common__version_hxx_
#define code__seafire__common__version_hxx_
// The numeric version format is AAAAABBBBBCCCCCDDDE where:
//
// AAAAA - major version number
// BBBBB - minor version number
// CCCCC - bugfix version number
// DDD - alpha / beta (DDD + 500) version number
// E - final (0) / snapshot (1)
//
// When DDDE is not 0, 1 is subtracted from AAAAABBBBBCCCCC. For example:
//
// Version AAAAABBBBBCCCCCDDDE
//
// 0.1.0 0000000001000000000
// 0.1.2 0000000001000020000
// 1.2.3 0000100002000030000
// 2.2.0-a.1 0000200001999990010
// 3.0.0-b.2 0000299999999995020
// 2.2.0-a.1.z 0000200001999990011
//
#define LIBCODE_SEAFIRE_COMMON_VERSION $libcode_seafire_common.version.project_number$ULL
#define LIBCODE_SEAFIRE_COMMON_VERSION_STR "$libcode_seafire_common.version.project$"
#define LIBCODE_SEAFIRE_COMMON_VERSION_ID "$libcode_seafire_common.version.project_id$"
#define LIBCODE_SEAFIRE_COMMON_VERSION_FULL "$libcode_seafire_common.version$"
#define LIBCODE_SEAFIRE_COMMON_VERSION_MAJOR $libcode_seafire_common.version.major$
#define LIBCODE_SEAFIRE_COMMON_VERSION_MINOR $libcode_seafire_common.version.minor$
#define LIBCODE_SEAFIRE_COMMON_VERSION_PATCH $libcode_seafire_common.version.patch$
#define LIBCODE_SEAFIRE_COMMON_PRE_RELEASE $libcode_seafire_common.version.pre_release$
#define LIBCODE_SEAFIRE_COMMON_SNAPSHOT_SN $libcode_seafire_common.version.snapshot_sn$ULL
#define LIBCODE_SEAFIRE_COMMON_SNAPSHOT_ID "$libcode_seafire_common.version.snapshot_id$"
#endif

12
manifest Normal file
View File

@ -0,0 +1,12 @@
: 1
name: libcode-seafire-common
version: 0.1.0-a.0.z
language: c++
summary: libcode-seafire-common C++ library
license: BSD-4-Clause
description-file: README.md
url: https://helloryan.se/code/
email: ryan@helloryan.se
depends: * build2 >= 0.17.0
depends: * bpkg >= 0.17.0
depends: libasio ^1.29.0

7
repositories.manifest Normal file
View File

@ -0,0 +1,7 @@
: 1
summary: libcode-seafire-common project repository
:
role: prerequisite
location: https://pkg.cppget.org/1/beta
trust: 70:64:FE:E4:E0:F3:60:F1:B4:51:E1:FA:12:5C:E0:B3:DB:DF:96:33:39:B9:2E:E5:C2:68:63:4C:A6:47:39:43

8
tests/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Test executables.
#
driver
# Testscript output directories (can be symlinks).
#
test
test-*

4
tests/build/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/config.build
/root/
/bootstrap/
build/

View File

@ -0,0 +1,5 @@
project = # Unnamed tests subproject.
using config
using test
using dist

16
tests/build/root.build Normal file
View File

@ -0,0 +1,16 @@
cxx.std = latest
using cxx
hxx{*}: extension = hxx
ixx{*}: extension = ixx
txx{*}: extension = txx
cxx{*}: extension = cxx
# Every exe{} in this subproject is by default a test.
#
exe{*}: test = true
# The test target for cross-testing (running tests under Wine, etc).
#
test.target = $cxx.target

1
tests/buildfile Normal file
View File

@ -0,0 +1 @@
./: {*/ -build/}