Hello Seafire

This commit is contained in:
R.Y.A.N 2025-03-07 02:25:52 +01:00
commit f858107811
Signed by: R.Y.A.N
GPG Key ID: 3BD93EABD1407B82
47 changed files with 2012 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

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

32
LICENSE.md Normal file
View File

@ -0,0 +1,32 @@
Copyright © 2025 Per Ryan Edin. 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 Seafire, an HTTP/1.1 implementation
> for C++ applications. 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.

10
README.md Normal file
View File

@ -0,0 +1,10 @@
# Seafire
## 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 repository
write access.

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 = 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 seafire/common/
}
export $out_root/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 @@
./: {seafire/ tests/} doc{README.md} legal{LICENSE.md} manifest
# Don't install tests.
#
tests/: install = false

12
manifest Normal file
View File

@ -0,0 +1,12 @@
: 1
name: seafire-common
version: 0.1.0-a.0.z
language: c++
summary: Seafire Commmon C++ library
license: BSD-4-Clause
description-file: README.md
url: https://helloryan.se/
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: 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

Binary file not shown.

9
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 <seafire/common/allocator.hxx>
namespace seafire::common
{
allocator_t::
allocator_t() = default;
allocator_t::allocation_t::
allocation_t() = default;
allocator_t::allocation_t::
~allocation_t() noexcept = default;
} // namespace seafire::common

View File

@ -0,0 +1,66 @@
#ifndef seafire__common__allocator_hxx_
#define seafire__common__allocator_hxx_
#include <memory>
#include <set>
namespace 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 seafire::common
#include <seafire/common/allocator.txx>
#endif

View File

@ -0,0 +1,55 @@
namespace 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 seafire::common

65
seafire/common/buildfile Normal file
View File

@ -0,0 +1,65 @@
intf_libs = # Interface dependencies.
impl_libs = # Implementation dependencies.
import intf_libs =+ libasio%lib{asio}
./: lib{seafire-common}: libul{seafire-common}
libul{seafire-common}: {hxx ixx txx cxx}{** -**.test... -version} \
{hxx }{ version}
libul{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{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{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{seafire-common}: bin.lib.version = "-$version.project_id"
else
lib{seafire-common}: bin.lib.version = "-$version.major.$version.minor"
# Install into the seafire/common/ subdirectory of, say, /usr/include/
# recreating subdirectories.
#
{hxx ixx txx}{*}:
{
install = include/seafire/common/
install.subdirs = true
}

View File

@ -0,0 +1,132 @@
#include <seafire/common/diagnostics.hxx>
#include <iomanip>
namespace 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 seafire::common

View File

@ -0,0 +1,160 @@
#ifndef seafire__common__diagnostics_hxx_
#define seafire__common__diagnostics_hxx_
#include <ostream>
#include <set>
#include <sstream>
#include <string>
namespace 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 seafire::common
#endif

View File

@ -0,0 +1,48 @@
#include <seafire/common/extension-context.hxx>
namespace 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 seafire::common

View File

@ -0,0 +1,91 @@
#ifndef seafire__common__extension_context_hxx_
#define seafire__common__extension_context_hxx_
#include <map>
#include <stdexcept>
#include <typeindex>
namespace 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 seafire::common
#include <seafire/common/extension-context.txx>
#endif

View File

@ -0,0 +1,63 @@
namespace 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 seafire::common

54
seafire/common/invoke.hxx Normal file
View File

@ -0,0 +1,54 @@
#ifndef seafire__common__invoke_hxx_
#define seafire__common__invoke_hxx_
#include <seafire/server/request.hxx>
#include <seafire/server/response.hxx>
namespace 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 seafire::common
#include <seafire/common/invoke.txx>
#endif

62
seafire/common/invoke.txx Normal file
View File

@ -0,0 +1,62 @@
namespace 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 seafire::common

View File

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

View File

@ -0,0 +1,51 @@
#ifndef seafire__common__io__acceptor_hxx_
#define seafire__common__io__acceptor_hxx_
#include <seafire/common/io/stream.hxx>
#include <asio.hpp>
#include <functional>
#include <memory>
namespace 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 seafire::common::io
#endif

View File

@ -0,0 +1,23 @@
#ifndef seafire__common__io__buffer_hxx_
#define seafire__common__io__buffer_hxx_
#include <asio.hpp>
#include <vector>
namespace 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 seafire::common::io
#endif

View File

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

View File

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

View File

@ -0,0 +1,31 @@
#include <seafire/common/io/error.hxx>
namespace 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 seafire::common::io

View File

@ -0,0 +1,37 @@
#ifndef seafire__common__io__error_hxx_
#define seafire__common__io__error_hxx_
#include <system_error>
#include <type_traits>
namespace 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 seafire::common::io
namespace std
{
template<>
struct is_error_code_enum<::seafire::common::io::error_t>
: true_type
{};
} // namespace std
#endif

View File

@ -0,0 +1,131 @@
#include <seafire/common/io/read-until.hxx>
#include <seafire/common/io/error.hxx>
namespace 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 seafire::common::io

View File

@ -0,0 +1,39 @@
#ifndef seafire__common__io__read_until_hxx_
#define seafire__common__io__read_until_hxx_
#include <seafire/common/io/stream.hxx>
#include <functional>
#include <system_error>
#include <asio.hpp>
namespace 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 seafire::common::io
#endif

View File

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

View File

@ -0,0 +1,137 @@
#ifndef seafire__common__io__stream_hxx_
#define seafire__common__io__stream_hxx_
#include <seafire/common/io/buffer.hxx>
#include <asio.hpp>
#include <functional>
#include <system_error>
namespace 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 seafire::common::io
#endif

View File

@ -0,0 +1,36 @@
#include <seafire/common/io/tcp-acceptor.hxx>
#include <seafire/common/io/tcp-socket.hxx>
namespace 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 seafire::common::io

View File

@ -0,0 +1,43 @@
#ifndef seafire__common__io__tcp_acceptor_hxx_
#define seafire__common__io__tcp_acceptor_hxx_
#include <seafire/common/diagnostics.hxx>
#include <seafire/common/io/acceptor.hxx>
#include <asio.hpp>
namespace 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 seafire::common::io
#endif

View File

@ -0,0 +1,149 @@
#include <seafire/common/io/tcp-socket.hxx>
namespace 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 seafire::common::io

View File

@ -0,0 +1,82 @@
#ifndef seafire__common__io__tcp_socket_hxx_
#define seafire__common__io__tcp_socket_hxx_
#include <seafire/common/io/buffer.hxx>
#include <seafire/common/io/stream.hxx>
#include <asio.hpp>
namespace 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 seafire::common::io
#endif

162
seafire/common/traits.hxx Normal file
View File

@ -0,0 +1,162 @@
#ifndef seafire__common__traits_hxx_
#define seafire__common__traits_hxx_
#include <optional>
#include <tuple>
#include <type_traits>
namespace 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 seafire::common::traits
#endif

View File

@ -0,0 +1,37 @@
#ifndef seafire__common__version_hxx_
#define 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 SEAFIRE_COMMON_VERSION $seafire_common.version.project_number$ULL
#define SEAFIRE_COMMON_VERSION_STR "$seafire_common.version.project$"
#define SEAFIRE_COMMON_VERSION_ID "$seafire_common.version.project_id$"
#define SEAFIRE_COMMON_VERSION_FULL "$seafire_common.version$"
#define SEAFIRE_COMMON_VERSION_MAJOR $seafire_common.version.major$
#define SEAFIRE_COMMON_VERSION_MINOR $seafire_common.version.minor$
#define SEAFIRE_COMMON_VERSION_PATCH $seafire_common.version.patch$
#define SEAFIRE_COMMON_PRE_RELEASE $seafire_common.version.pre_release$
#define SEAFIRE_COMMON_SNAPSHOT_SN $seafire_common.version.snapshot_sn$ULL
#define SEAFIRE_COMMON_SNAPSHOT_ID "$seafire_common.version.snapshot_id$"
#endif

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/}