diff options
Diffstat (limited to 'src/mmo')
-rw-r--r-- | src/mmo/config_parse.cpp | 154 | ||||
-rw-r--r-- | src/mmo/config_parse.hpp | 36 | ||||
-rw-r--r-- | src/mmo/core.cpp | 125 | ||||
-rw-r--r-- | src/mmo/core.hpp | 22 | ||||
-rw-r--r-- | src/mmo/dumb_ptr.cpp | 3 | ||||
-rw-r--r-- | src/mmo/dumb_ptr.hpp | 276 | ||||
-rw-r--r-- | src/mmo/extract.cpp | 62 | ||||
-rw-r--r-- | src/mmo/extract.hpp | 224 | ||||
-rw-r--r-- | src/mmo/extract_test.cpp | 334 | ||||
-rw-r--r-- | src/mmo/human_time_diff.cpp | 3 | ||||
-rw-r--r-- | src/mmo/human_time_diff.hpp | 86 | ||||
-rw-r--r-- | src/mmo/human_time_diff_test.cpp | 83 | ||||
-rw-r--r-- | src/mmo/ip.cpp | 114 | ||||
-rw-r--r-- | src/mmo/ip.hpp | 164 | ||||
-rw-r--r-- | src/mmo/ip.py | 14 | ||||
-rw-r--r-- | src/mmo/ip_test.cpp | 332 | ||||
-rw-r--r-- | src/mmo/md5more.cpp | 128 | ||||
-rw-r--r-- | src/mmo/md5more.hpp | 26 | ||||
-rw-r--r-- | src/mmo/mmo.cpp | 3 | ||||
-rw-r--r-- | src/mmo/mmo.hpp | 377 | ||||
-rw-r--r-- | src/mmo/socket.cpp | 474 | ||||
-rw-r--r-- | src/mmo/socket.hpp | 373 | ||||
-rw-r--r-- | src/mmo/timer.cpp | 201 | ||||
-rw-r--r-- | src/mmo/timer.hpp | 30 | ||||
-rw-r--r-- | src/mmo/timer.t.hpp | 68 | ||||
-rw-r--r-- | src/mmo/utils.cpp | 101 | ||||
-rw-r--r-- | src/mmo/utils.hpp | 116 | ||||
-rw-r--r-- | src/mmo/version.cpp | 55 | ||||
-rw-r--r-- | src/mmo/version.hpp | 69 |
29 files changed, 4053 insertions, 0 deletions
diff --git a/src/mmo/config_parse.cpp b/src/mmo/config_parse.cpp new file mode 100644 index 0000000..d677d8e --- /dev/null +++ b/src/mmo/config_parse.cpp @@ -0,0 +1,154 @@ +#include "config_parse.hpp" +// config_parse.hpp - Framework for per-server config parsers. +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "../strings/xstring.hpp" +#include "../strings/zstring.hpp" + +#include "../io/cxxstdio.hpp" +#include "../io/line.hpp" + +#include "version.hpp" + +#include "../poison.hpp" + +bool is_comment(XString line) +{ + return not line or line.startswith("//"); +} + +template<class ZS> +inline +bool config_split_impl(ZS line, XString *key, ZS *value) +{ + // unconditionally fail if line contains control characters + if (std::find_if(line.begin(), line.end(), + [](unsigned char c) { return c < ' '; } + ) != line.end()) + return false; + // try to find a colon, fail if not found + typename ZS::iterator colon = std::find(line.begin(), line.end(), ':'); + if (colon == line.end()) + return false; + + *key = line.xislice_h(colon).strip(); + // move past the colon and any spaces + ++colon; + *value = line.xislice_t(colon).lstrip(); + return true; +} + +// eventually this should go away +bool config_split(ZString line, XString *key, ZString *value) +{ + return config_split_impl(line, key, value); +} +// and use this instead +bool config_split(XString line, XString *key, XString *value) +{ + return config_split_impl(line, key, value); +} + +/// Master config parser. This handles 'import' and 'version-ge' etc. +/// Then it defers to the inferior parser for a line it does not understand. +bool load_config_file(ZString filename, ConfigItemParser slave) +{ + io::LineReader in(filename); + if (!in.is_open()) + { + PRINTF("Unable to open file: %s\n", filename); + return false; + } + io::Line line; + bool rv = true; + while (in.read_line(line)) + { + if (is_comment(line.text)) + continue; + XString key; + ZString value; + if (!config_split(line.text, &key, &value)) + { + line.error("Bad config line"); + rv = false; + continue; + } + if (key == "import") + { + rv &= load_config_file(value, slave); + continue; + } + else if (key == "version-lt") + { + Version vers; + if (!extract(value, &vers)) + { + rv = false; + continue; + } + if (CURRENT_VERSION < vers) + continue; + break; + } + else if (key == "version-le") + { + Version vers; + if (!extract(value, &vers)) + { + rv = false; + continue; + } + if (CURRENT_VERSION <= vers) + continue; + break; + } + else if (key == "version-gt") + { + Version vers; + if (!extract(value, &vers)) + { + rv = false; + continue; + } + if (CURRENT_VERSION > vers) + continue; + break; + } + else if (key == "version-ge") + { + Version vers; + if (!extract(value, &vers)) + { + rv = false; + continue; + } + if (CURRENT_VERSION >= vers) + continue; + break; + } + else if (!slave(key, value)) + { + line.error("Bad config key or value"); + rv = false; + continue; + } + // nothing to see here, move along + } + return rv; +} diff --git a/src/mmo/config_parse.hpp b/src/mmo/config_parse.hpp new file mode 100644 index 0000000..dd1b79e --- /dev/null +++ b/src/mmo/config_parse.hpp @@ -0,0 +1,36 @@ +#ifndef TMWA_MMO_CONFIG_PARSE_HPP +#define TMWA_MMO_CONFIG_PARSE_HPP +// config_parse.hpp - Framework for per-server config parsers. +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +# include "../sanity.hpp" + +# include "../strings/fwd.hpp" + +typedef bool (*ConfigItemParser)(XString key, ZString value); + +bool is_comment(XString line); +bool config_split(ZString line, XString *key, ZString *value); +bool config_split(XString line, XString *key, XString *value); + +/// Master config parser. This handles 'import' and 'version-ge' etc. +/// Then it defers to the inferior parser for a line it does not understand. +bool load_config_file(ZString filename, ConfigItemParser slave); + +#endif // TMWA_MMO_CONFIG_PARSE_HPP diff --git a/src/mmo/core.cpp b/src/mmo/core.cpp new file mode 100644 index 0000000..1a9a52e --- /dev/null +++ b/src/mmo/core.cpp @@ -0,0 +1,125 @@ +#include "core.hpp" + +#include <sys/wait.h> + +#include <unistd.h> + +#include <csignal> +#include <cstdlib> +#include <ctime> + +#include "../strings/zstring.hpp" + +#include "../generic/random.hpp" + +#include "../io/cxxstdio.hpp" + +#include "socket.hpp" +#include "timer.hpp" + +#include "../poison.hpp" + +// Added by Gabuzomeu +// +// This is an implementation of signal() using sigaction() for portability. +// (sigaction() is POSIX; signal() is not.) Taken from Stevens' _Advanced +// Programming in the UNIX Environment_. +// +typedef void(*sigfunc)(int); +static +sigfunc compat_signal(int signo, sigfunc func) +{ + struct sigaction sact, oact; + + sact.sa_handler = func; + sigfillset(&sact.sa_mask); + sigdelset(&sact.sa_mask, SIGSEGV); + sigdelset(&sact.sa_mask, SIGBUS); + sigdelset(&sact.sa_mask, SIGTRAP); + sigdelset(&sact.sa_mask, SIGILL); + sigdelset(&sact.sa_mask, SIGFPE); + sact.sa_flags = 0; + + if (sigaction(signo, &sact, &oact) < 0) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" + return SIG_ERR; +#pragma GCC diagnostic pop + + return oact.sa_handler; +} + +volatile +bool runflag = true; + +static +void chld_proc(int) +{ + wait(NULL); +} +static +void sig_proc(int) +{ + for (int i = 1; i < 31; ++i) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" + compat_signal(i, SIG_IGN); +#pragma GCC diagnostic pop + runflag = false; +} + +/* + Note about fatal signals: + + Under certain circumstances, + the following signals MUST not be ignored: + SIGFPE, SIGSEGV, SIGILL + Unless you use SA_SIGINFO and *carefully* check the origin, + that means they must be SIG_DFL. + */ +int main(int argc, char **argv) +{ + // ZString args[argc]; is (deliberately!) not supported by clang yet + ZString *args = static_cast<ZString *>(alloca(argc * sizeof(ZString))); + for (int i = 0; i < argc; ++i) + args[i] = ZString(strings::really_construct_from_a_pointer, argv[i], nullptr); + do_init(argc, args); + + if (!runflag) + { + PRINTF("Fatal error during startup; exiting\n"); + return 1; + } + // set up exit handlers *after* the initialization has happened. + // This is because term_func is likely to depend on successful init. + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" + compat_signal(SIGPIPE, SIG_IGN); +#pragma GCC diagnostic pop + compat_signal(SIGTERM, sig_proc); + compat_signal(SIGINT, sig_proc); + compat_signal(SIGCHLD, chld_proc); + + // Signal to create coredumps by system when necessary (crash) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" + compat_signal(SIGSEGV, SIG_DFL); + compat_signal(SIGBUS, SIG_DFL); + compat_signal(SIGTRAP, SIG_DFL); + compat_signal(SIGILL, SIG_DFL); + compat_signal(SIGFPE, SIG_DFL); +#pragma GCC diagnostic pop + + atexit(term_func); + + while (runflag) + { + // TODO - if timers take a long time to run, this + // may wait too long in sendrecv + tick_t now = milli_clock::now(); + interval_t next = do_timer(now); + do_sendrecv(next); + do_parsepacket(); + } +} diff --git a/src/mmo/core.hpp b/src/mmo/core.hpp new file mode 100644 index 0000000..1788ece --- /dev/null +++ b/src/mmo/core.hpp @@ -0,0 +1,22 @@ +#ifndef TMWA_MMO_CORE_HPP +#define TMWA_MMO_CORE_HPP + +# include "../sanity.hpp" + +# include "../strings/fwd.hpp" + +/// core.c contains a server-independent main() function +/// and then runs a do_sendrecv loop + +/// When this is cleared, the server exits gracefully. +extern volatile bool runflag; + +/// This is an external function defined by each server +/// This function must register stuff for the parse loop +extern int do_init(int, ZString *); + +/// Cleanup function called whenever a signal kills us +/// or when if we manage to exit() gracefully. +extern void term_func(void); + +#endif // TMWA_MMO_CORE_HPP diff --git a/src/mmo/dumb_ptr.cpp b/src/mmo/dumb_ptr.cpp new file mode 100644 index 0000000..5f73d27 --- /dev/null +++ b/src/mmo/dumb_ptr.cpp @@ -0,0 +1,3 @@ +#include "dumb_ptr.hpp" + +#include "../poison.hpp" diff --git a/src/mmo/dumb_ptr.hpp b/src/mmo/dumb_ptr.hpp new file mode 100644 index 0000000..98c6308 --- /dev/null +++ b/src/mmo/dumb_ptr.hpp @@ -0,0 +1,276 @@ +#ifndef TMWA_MMO_DUMB_PTR_HPP +#define TMWA_MMO_DUMB_PTR_HPP +// ptr.hpp - temporary new/delete wrappers +// +// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +# include "../sanity.hpp" + +# include <cstring> + +# include <algorithm> + +# include "../strings/astring.hpp" +# include "../strings/zstring.hpp" +# include "../strings/xstring.hpp" + +# include "../generic/const_array.hpp" + +// unmanaged new/delete-able pointer +// should be replaced by std::unique_ptr<T> +template<class T> +class dumb_ptr +{ + template<class U> + friend class dumb_ptr; + T *impl; +public: + explicit + dumb_ptr(T *p=nullptr) + : impl(p) + {} + template<class U> + dumb_ptr(dumb_ptr<U> p) + : impl(p.impl) + {} + dumb_ptr(std::nullptr_t) + : impl(nullptr) + {} + + void delete_() + { + delete impl; + *this = nullptr; + } + template<class... A> + void new_(A&&... a) + { + impl = new T(std::forward<A>(a)...); + } + template<class... A> + static + dumb_ptr<T> make(A&&... a) + { + return dumb_ptr<T>(new T(std::forward<A>(a)...)); + } + dumb_ptr& operator = (std::nullptr_t) + { + impl = nullptr; + return *this; + } + + T& operator *() const + { + return *impl; + } + T *operator->() const + { + return impl; + } + + explicit + operator bool() const + { + return impl; + } + bool operator !() const + { + return !impl; + } + + friend bool operator == (dumb_ptr l, dumb_ptr r) + { + return l.impl == r.impl; + } + friend bool operator != (dumb_ptr l, dumb_ptr r) + { + return !(l == r); + } +}; + +// unmanaged new/delete-able pointer +// should be replaced by std::unique_ptr<T[]> or std::vector<T> +template<class T> +class dumb_ptr<T[]> +{ + T *impl; + size_t sz; +public: + dumb_ptr() : impl(), sz() {} + dumb_ptr(std::nullptr_t) + : impl(nullptr), sz(0) {} + dumb_ptr(T *p, size_t z) + : impl(p) + , sz(z) + {} + + void delete_() + { + delete[] impl; + *this = nullptr; + } + void new_(size_t z) + { + impl = new T[z](); + sz = z; + } + static + dumb_ptr<T[]> make(size_t z) + { + return dumb_ptr<T[]>(new T[z](), z); + } + dumb_ptr& operator = (std::nullptr_t) + { + impl = nullptr; + sz = 0; + return *this; + } + + size_t size() const + { + return sz; + } + void resize(size_t z) + { + if (z == sz) + return; + T *np = new T[z](); + // not exception-safe, but we don't have a dtor anyway + size_t i = std::min(z, sz); + while (i-->0) + np[i] = std::move(impl[i]); + delete[] impl; + impl = np; + sz = z; + } + + T& operator[](size_t i) const + { + return impl[i]; + } + + explicit + operator bool() const + { + return impl; + } + bool operator !() const + { + return !impl; + } + + friend bool operator == (dumb_ptr l, dumb_ptr r) + { + return l.impl == r.impl; + } + friend bool operator != (dumb_ptr l, dumb_ptr r) + { + return !(l == r); + } +}; + +struct dumb_string +{ + dumb_ptr<char[]> impl; + + dumb_string() + : impl() + {} + dumb_string(char *) = delete; + // copy ctor, copy assign, and dtor are all default + + static dumb_string copy(const char *b, const char *e) + { + dumb_string rv; + rv.impl.new_((e - b) + 1); + std::copy(b, e, &rv.impl[0]); + return rv; + } + static dumb_string copy(const char *sz) + { + return dumb_string::copy(sz, sz + strlen(sz)); + } + static dumb_string copys(XString s) + { + return dumb_string::copy(&*s.begin(), &*s.end()); + } + static +# ifndef __clang__ + __attribute__((warning("shouldn't use this - slice instead"))) +# endif + dumb_string copyn(const char *sn, size_t n) + { + return dumb_string::copy(sn, sn + strnlen(sn, n)); + } + + static + dumb_string fake(ZString p) + { + dumb_string rv; + size_t len = p.size(); + rv.impl = dumb_ptr<char[]>(const_cast<char *>(p.c_str()), len); + return rv; + } + + dumb_string dup() const + { + return dumb_string::copy(&impl[0]); + } + void delete_() + { + impl.delete_(); + } + + const char *c_str() const + { + return &impl[0]; + } + + operator ZString() const + { + return ZString(strings::really_construct_from_a_pointer, c_str(), nullptr); + } + + AString str() const + { + return ZString(*this); + } + + char& operator[](size_t i) const + { + return impl[i]; + } + + explicit + operator bool() const + { + return bool(impl); + } + bool operator !() const + { + return !impl; + } +}; + +inline +const char *convert_for_printf(dumb_string ds) +{ + return ds.c_str(); +} + +#endif // TMWA_MMO_DUMB_PTR_HPP diff --git a/src/mmo/extract.cpp b/src/mmo/extract.cpp new file mode 100644 index 0000000..378986d --- /dev/null +++ b/src/mmo/extract.cpp @@ -0,0 +1,62 @@ +#include "extract.hpp" +// extract.cpp - a simple, hierarchical, tokenizer +// +// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "../strings/astring.hpp" +#include "../strings/xstring.hpp" +#include "../strings/vstring.hpp" + +#include "../poison.hpp" + +bool extract(XString str, XString *rv) +{ + *rv = str; + return true; +} + +bool extract(XString str, AString *rv) +{ + *rv = str; + return true; +} + +bool extract(XString str, struct global_reg *var) +{ + return extract(str, + record<','>(&var->str, &var->value)); +} + +bool extract(XString str, struct item *it) +{ + XString ignored; + return extract(str, + record<',', 11>( + &it->id, + &it->nameid, + &it->amount, + &it->equip, + &ignored, + &ignored, + &ignored, + &ignored, + &ignored, + &ignored, + &ignored, + &ignored)); +} diff --git a/src/mmo/extract.hpp b/src/mmo/extract.hpp new file mode 100644 index 0000000..0ea9eb9 --- /dev/null +++ b/src/mmo/extract.hpp @@ -0,0 +1,224 @@ +#ifndef TMWA_MMO_EXTRACT_HPP +#define TMWA_MMO_EXTRACT_HPP +// extract.hpp - a simple, hierarchical, tokenizer +// +// Copyright © 2012-2013 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +# include "../sanity.hpp" + +# include <algorithm> + +# include "../strings/xstring.hpp" + +# include "../generic/const_array.hpp" + +# include "mmo.hpp" +# include "utils.hpp" + +template<class T, typename=typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, char>::value && !std::is_same<T, bool>::value>::type> +bool extract(XString str, T *iv) +{ + if (!str || str.size() > 20) + return false; + if (!((str.front() == '-' && std::is_signed<T>::value) + || ('0' <= str.front() && str.front() <= '9'))) + return false; + // needs a NUL, but can't always be given one. TODO VString? + char buf[20 + 1]; + std::copy(str.begin(), str.end(), buf); + buf[str.size()] = '\0'; + + char *end; + errno = 0; + if (std::is_signed<T>::value) + { + long long v = strtoll(buf, &end, 10); + if (errno || *end) + return false; + *iv = v; + return *iv == v; + } + else + { + unsigned long long v = strtoull(buf, &end, 10); + if (errno || *end) + return false; + *iv = v; + return *iv == v; + } +} + +inline +bool extract(XString str, TimeT *tv) +{ + return extract(str, &tv->value); +} + +// extra typename=void to workaround some duplicate overload rule +template<class T, typename=typename std::enable_if<std::is_enum<T>::value>::type, typename=void> +bool extract(XString str, T *iv) +{ + typedef typename underlying_type<T>::type U; + U v; + // defer to integer version + if (!extract(str, &v)) + return false; + // TODO check bounds using enum min/max as in SSCANF + *iv = static_cast<T>(v); + return true; +} + +bool extract(XString str, XString *rv); + +bool extract(XString str, AString *rv); + +template<uint8_t N> +bool extract(XString str, VString<N> *out) +{ + if (str.size() > N) + return false; + *out = str; + return true; +} + +template<class T> +class LStripper +{ +public: + T impl; +}; + +template<class T> +LStripper<T> lstripping(T v) +{ + return {v}; +} + +template<class T> +bool extract(XString str, LStripper<T> out) +{ + return extract(str.lstrip(), out.impl); +} + +// basically just a std::tuple +// but it provides its data members publically +template<char split, int n, class... T> +class Record; +template<char split, int n> +class Record<split, n> +{ +}; +template<char split, int n, class F, class... R> +class Record<split, n, F, R...> +{ +public: + F frist; + Record<split, n - 1, R...> rest; +public: + Record(F f, R... r) + : frist(f), rest(r...) + {} +}; +template<char split, class... T> +Record<split, sizeof...(T), T...> record(T... t) +{ + return Record<split, sizeof...(T), T...>(t...); +} +template<char split, int n, class... T> +Record<split, n, T...> record(T... t) +{ + static_assert(0 < n && n < sizeof...(T), "don't be silly"); + return Record<split, n, T...>(t...); +} + +template<char split, int n> +bool extract(XString str, Record<split, n>) +{ + return !str; +} + +template<char split, int n, class F, class... R> +bool extract(XString str, Record<split, n, F, R...> rec) +{ + XString::iterator s = std::find(str.begin(), str.end(), split); + XString::iterator s2 = s; + if (s2 != str.end()) + ++s2; + XString head = str.xislice_h(s); + XString tail = str.xislice_t(s2); + if (s == str.end()) + return (extract(head, rec.frist) && n <= 1) + || (!head && n <= 0); + + return (extract(head, rec.frist) || n <= 0) + && extract(tail, rec.rest); +} + +template<char split, class T> +struct VRecord +{ + std::vector<T> *arr; +}; + +template<char split, class T> +VRecord<split, T> vrec(std::vector<T> *arr) +{ + return {arr}; +} + +template<char split, class T> +bool extract(XString str, VRecord<split, T> rec) +{ + if (!str) + return true; + XString::iterator s = std::find(str.begin(), str.end(), split); + rec.arr->emplace_back(); + if (s == str.end()) + return extract(str, &rec.arr->back()); + return extract(str.xislice_h(s), &rec.arr->back()) + && extract(str.xislice_t(s + 1), rec); +} + +bool extract(XString str, struct global_reg *var); + +bool extract(XString str, struct item *it); + +inline +bool extract(XString str, MapName *m) +{ + XString::iterator it = std::find(str.begin(), str.end(), '.'); + str = str.xislice_h(it); + VString<15> tmp; + bool rv = extract(str, &tmp); + *m = tmp; + return rv; +} + +inline +bool extract(XString str, CharName *out) +{ + VString<23> tmp; + if (extract(str, &tmp)) + { + *out = CharName(tmp); + return true; + } + return false; +} + +#endif // TMWA_MMO_EXTRACT_HPP diff --git a/src/mmo/extract_test.cpp b/src/mmo/extract_test.cpp new file mode 100644 index 0000000..3d0610e --- /dev/null +++ b/src/mmo/extract_test.cpp @@ -0,0 +1,334 @@ +#include "extract.hpp" + +#include <gtest/gtest.h> + +#include "../strings/xstring.hpp" + +TEST(extract, record_int) +{ + int x, y, z; + x = y = z = 0; + EXPECT_FALSE(extract("1 2 3 4 ", record<' '>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_FALSE(extract("1 2 3 4", record<' '>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_TRUE(extract("1 2 3 ", record<' '>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_TRUE(extract("1 2 3", record<' '>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_FALSE(extract("1 2 ", record<' '>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_FALSE(extract("1 2", record<' '>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_FALSE(extract("1 ", record<' '>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_FALSE(extract("1", record<' '>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_FALSE(extract(" ", record<' '>(&x, &y, &z))); + EXPECT_EQ(0, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_FALSE(extract("", record<' '>(&x, &y, &z))); + EXPECT_EQ(0, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; + + EXPECT_FALSE(extract("1 2 3 4 ", record<' ', 2>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_FALSE(extract("1 2 3 4", record<' ', 2>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_TRUE(extract("1 2 3 ", record<' ', 2>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_TRUE(extract("1 2 3", record<' ', 2>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_TRUE(extract("1 2 ", record<' ', 2>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_TRUE(extract("1 2", record<' ', 2>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_FALSE(extract("1 ", record<' ', 2>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_FALSE(extract("1", record<' ', 2>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_FALSE(extract(" ", record<' ', 2>(&x, &y, &z))); + EXPECT_EQ(0, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_FALSE(extract("", record<' ', 2>(&x, &y, &z))); + EXPECT_EQ(0, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; + + EXPECT_FALSE(extract("1 2 3 4 ", record<' ', 1>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_FALSE(extract("1 2 3 4", record<' ', 1>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_TRUE(extract("1 2 3 ", record<' ', 1>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_TRUE(extract("1 2 3", record<' ', 1>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_TRUE(extract("1 2 ", record<' ', 1>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_TRUE(extract("1 2", record<' ', 1>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_TRUE(extract("1 ", record<' ', 1>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_TRUE(extract("1", record<' ', 1>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_FALSE(extract(" ", record<' ', 1>(&x, &y, &z))); + EXPECT_EQ(0, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_FALSE(extract("", record<' ', 1>(&x, &y, &z))); + EXPECT_EQ(0, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; +} + +TEST(extract, record_str) +{ + XString x, y, z; + x = y = z = ""; + EXPECT_FALSE(extract("1 2 3 4 ", record<' '>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("2", y); + EXPECT_EQ("3", z); + x = y = z = ""; + EXPECT_FALSE(extract("1 2 3 4", record<' '>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("2", y); + EXPECT_EQ("3", z); + x = y = z = ""; + EXPECT_TRUE(extract("1 2 3 ", record<' '>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("2", y); + EXPECT_EQ("3", z); + x = y = z = ""; + EXPECT_TRUE(extract("1 2 3", record<' '>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("2", y); + EXPECT_EQ("3", z); + x = y = z = ""; + EXPECT_TRUE(extract("1 2 ", record<' '>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("2", y); + EXPECT_EQ("", z); + x = y = z = ""; + EXPECT_FALSE(extract("1 2", record<' '>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("2", y); + EXPECT_EQ("", z); + x = y = z = ""; + EXPECT_FALSE(extract("1 ", record<' '>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("", y); + EXPECT_EQ("", z); + x = y = z = ""; + EXPECT_FALSE(extract("1", record<' '>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("", y); + EXPECT_EQ("", z); + x = y = z = ""; + EXPECT_FALSE(extract(" ", record<' '>(&x, &y, &z))); + EXPECT_EQ("", x); + EXPECT_EQ("", y); + EXPECT_EQ("", z); + x = y = z = ""; + EXPECT_FALSE(extract("", record<' '>(&x, &y, &z))); + EXPECT_EQ("", x); + EXPECT_EQ("", y); + EXPECT_EQ("", z); + x = y = z = ""; + + EXPECT_FALSE(extract("1 2 3 4 ", record<' ', 2>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("2", y); + EXPECT_EQ("3", z); + x = y = z = ""; + EXPECT_FALSE(extract("1 2 3 4", record<' ', 2>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("2", y); + EXPECT_EQ("3", z); + x = y = z = ""; + EXPECT_TRUE(extract("1 2 3 ", record<' ', 2>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("2", y); + EXPECT_EQ("3", z); + x = y = z = ""; + EXPECT_TRUE(extract("1 2 3", record<' ', 2>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("2", y); + EXPECT_EQ("3", z); + x = y = z = ""; + EXPECT_TRUE(extract("1 2 ", record<' ', 2>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("2", y); + EXPECT_EQ("", z); + x = y = z = ""; + EXPECT_TRUE(extract("1 2", record<' ', 2>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("2", y); + EXPECT_EQ("", z); + x = y = z = ""; + EXPECT_TRUE(extract("1 ", record<' ', 2>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("", y); + EXPECT_EQ("", z); + x = y = z = ""; + EXPECT_FALSE(extract("1", record<' ', 2>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("", y); + EXPECT_EQ("", z); + x = y = z = ""; + EXPECT_TRUE(extract(" ", record<' ', 2>(&x, &y, &z))); + EXPECT_EQ("", x); + EXPECT_EQ("", y); + EXPECT_EQ("", z); + x = y = z = ""; + EXPECT_FALSE(extract("", record<' ', 2>(&x, &y, &z))); + EXPECT_EQ("", x); + EXPECT_EQ("", y); + EXPECT_EQ("", z); + x = y = z = ""; + + EXPECT_FALSE(extract("1 2 3 4 ", record<' ', 1>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("2", y); + EXPECT_EQ("3", z); + x = y = z = ""; + EXPECT_FALSE(extract("1 2 3 4", record<' ', 1>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("2", y); + EXPECT_EQ("3", z); + x = y = z = ""; + EXPECT_TRUE(extract("1 2 3 ", record<' ', 1>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("2", y); + EXPECT_EQ("3", z); + x = y = z = ""; + EXPECT_TRUE(extract("1 2 3", record<' ', 1>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("2", y); + EXPECT_EQ("3", z); + x = y = z = ""; + EXPECT_TRUE(extract("1 2 ", record<' ', 1>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("2", y); + EXPECT_EQ("", z); + x = y = z = ""; + EXPECT_TRUE(extract("1 2", record<' ', 1>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("2", y); + EXPECT_EQ("", z); + x = y = z = ""; + EXPECT_TRUE(extract("1 ", record<' ', 1>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("", y); + EXPECT_EQ("", z); + x = y = z = ""; + EXPECT_TRUE(extract("1", record<' ', 1>(&x, &y, &z))); + EXPECT_EQ("1", x); + EXPECT_EQ("", y); + EXPECT_EQ("", z); + x = y = z = ""; + EXPECT_TRUE(extract(" ", record<' ', 1>(&x, &y, &z))); + EXPECT_EQ("", x); + EXPECT_EQ("", y); + EXPECT_EQ("", z); + x = y = z = ""; + EXPECT_TRUE(extract("", record<' ', 1>(&x, &y, &z))); + EXPECT_EQ("", x); + EXPECT_EQ("", y); + EXPECT_EQ("", z); + x = y = z = ""; +} + +TEST(extract, mapname) +{ + MapName map; + EXPECT_TRUE(extract("abc", &map)); + EXPECT_EQ(map, "abc"); + EXPECT_TRUE(extract("abc.gat", &map)); + EXPECT_EQ(map, "abc"); + EXPECT_TRUE(extract("abcdefghijklmno", &map)); + EXPECT_EQ(map, "abcdefghijklmno"); + EXPECT_TRUE(extract("abcdefghijklmno.gat", &map)); + EXPECT_EQ(map, "abcdefghijklmno"); +} diff --git a/src/mmo/human_time_diff.cpp b/src/mmo/human_time_diff.cpp new file mode 100644 index 0000000..93a6d52 --- /dev/null +++ b/src/mmo/human_time_diff.cpp @@ -0,0 +1,3 @@ +#include "human_time_diff.hpp" + +#include "../poison.hpp" diff --git a/src/mmo/human_time_diff.hpp b/src/mmo/human_time_diff.hpp new file mode 100644 index 0000000..689b8d9 --- /dev/null +++ b/src/mmo/human_time_diff.hpp @@ -0,0 +1,86 @@ +#ifndef TMWA_MMO_HUMAN_TIME_DIFF_HPP +#define TMWA_MMO_HUMAN_TIME_DIFF_HPP +// human_time_diff.hpp - broken deltas +// +// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +# include "../sanity.hpp" + +# include "../strings/xstring.hpp" + +# include "extract.hpp" + +struct HumanTimeDiff +{ + short year, month, day, hour, minute, second; + + explicit + operator bool() + { + return year || month || day || hour || minute || second; + } + + bool operator !() + { + return !bool(*this); + } +}; + +inline +bool extract(XString str, HumanTimeDiff *iv) +{ + // str is a sequence of [-+]?[0-9]+([ay]|m|[jd]|h|mn|s) + // there are NO spaces here + // parse by counting the number starts + auto is_num = [](char c) + { return c == '-' || c == '+' || ('0' <= c && c <= '9'); }; + if (!str || !is_num(str.front())) + return false; + *iv = HumanTimeDiff{}; + while (str) + { + auto it = std::find_if_not(str.begin(), str.end(), is_num); + auto it2 = std::find_if(it, str.end(), is_num); + XString number = str.xislice_h(it); + XString suffix = str.xislice(it, it2); + str = str.xislice_t(it2); + + short *ptr = nullptr; + if (suffix == "y" || suffix == "a") + ptr = &iv->year; + else if (suffix == "m") + ptr = &iv->month; + else if (suffix == "j" || suffix == "d") + ptr = &iv->day; + else if (suffix == "h") + ptr = &iv->hour; + else if (suffix == "mn") + ptr = &iv->minute; + else if (suffix == "s") + ptr = &iv->second; + else + return false; + if (number.startswith('+') && !number.startswith("+-")) + number = number.xslice_t(1); + if (*ptr || !extract(number, ptr)) + return false; + } + return true; +} + +#endif // TMWA_MMO_HUMAN_TIME_DIFF_HPP diff --git a/src/mmo/human_time_diff_test.cpp b/src/mmo/human_time_diff_test.cpp new file mode 100644 index 0000000..d3ddad1 --- /dev/null +++ b/src/mmo/human_time_diff_test.cpp @@ -0,0 +1,83 @@ +#include "human_time_diff.hpp" + +#include <gtest/gtest.h> + +// a sequence of [-+]?[0-9]+([ay]|m|[jd]|h|mn|s) + +TEST(humantimediff, single) +{ + HumanTimeDiff diff; + + EXPECT_TRUE(extract("42y", &diff)); + EXPECT_EQ(42, diff.year); + EXPECT_EQ(0, diff.month); + EXPECT_EQ(0, diff.day); + EXPECT_EQ(0, diff.hour); + EXPECT_EQ(0, diff.minute); + EXPECT_EQ(0, diff.second); + + EXPECT_TRUE(extract("42m", &diff)); + EXPECT_EQ(0, diff.year); + EXPECT_EQ(42, diff.month); + EXPECT_EQ(0, diff.day); + EXPECT_EQ(0, diff.hour); + EXPECT_EQ(0, diff.minute); + EXPECT_EQ(0, diff.second); + + EXPECT_TRUE(extract("42d", &diff)); + EXPECT_EQ(0, diff.year); + EXPECT_EQ(0, diff.month); + EXPECT_EQ(42, diff.day); + EXPECT_EQ(0, diff.hour); + EXPECT_EQ(0, diff.minute); + EXPECT_EQ(0, diff.second); + + EXPECT_TRUE(extract("42h", &diff)); + EXPECT_EQ(0, diff.year); + EXPECT_EQ(0, diff.month); + EXPECT_EQ(0, diff.day); + EXPECT_EQ(42, diff.hour); + EXPECT_EQ(0, diff.minute); + EXPECT_EQ(0, diff.second); + + EXPECT_TRUE(extract("42mn", &diff)); + EXPECT_EQ(0, diff.year); + EXPECT_EQ(0, diff.month); + EXPECT_EQ(0, diff.day); + EXPECT_EQ(0, diff.hour); + EXPECT_EQ(42, diff.minute); + EXPECT_EQ(0, diff.second); + + EXPECT_TRUE(extract("42s", &diff)); + EXPECT_EQ(0, diff.year); + EXPECT_EQ(0, diff.month); + EXPECT_EQ(0, diff.day); + EXPECT_EQ(0, diff.hour); + EXPECT_EQ(0, diff.minute); + EXPECT_EQ(42, diff.second); + + EXPECT_TRUE(extract("+42y", &diff)); + EXPECT_EQ(42, diff.year); + EXPECT_TRUE(extract("-42y", &diff)); + EXPECT_EQ(-42, diff.year); + EXPECT_FALSE(extract("++42y", &diff)); + EXPECT_FALSE(extract("+-42y", &diff)); + EXPECT_FALSE(extract("-+42y", &diff)); + EXPECT_FALSE(extract("--42y", &diff)); + EXPECT_FALSE(extract("4+2y", &diff)); + EXPECT_FALSE(extract("42z", &diff)); +} + +TEST(humantimediff, multiple) +{ + HumanTimeDiff diff; + + EXPECT_TRUE(extract("42y23m-2d", &diff)); + EXPECT_EQ(42, diff.year); + EXPECT_EQ(23, diff.month); + EXPECT_EQ(-2, diff.day); + EXPECT_EQ(0, diff.hour); + EXPECT_EQ(0, diff.minute); + EXPECT_EQ(0, diff.second); + EXPECT_FALSE(extract("1y2y", &diff)); +} diff --git a/src/mmo/ip.cpp b/src/mmo/ip.cpp new file mode 100644 index 0000000..146734a --- /dev/null +++ b/src/mmo/ip.cpp @@ -0,0 +1,114 @@ +#include "ip.hpp" +// ip.cpp - Implementation of IP address functions. +// +// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "../strings/xstring.hpp" +#include "../strings/vstring.hpp" + +#include "../io/cxxstdio.hpp" + +#include "../poison.hpp" + +bool extract(XString str, IP4Address *rv) +{ + if (str.endswith('.')) + return false; + uint8_t buf[4]; + if (extract(str, record<'.'>(&buf[0], &buf[1], &buf[2], &buf[3]))) + { + *rv = IP4Address(buf); + return true; + } + return false; +} + +bool extract(XString str, IP4Mask *rv) +{ + IP4Address a, m; + unsigned b; + XString l, r; + if (str.endswith('/')) + return false; + if (extract(str, record<'/'>(&l, &r))) + { + // a.b.c.d/e.f.g.h or a.b.c.d/n + if (!extract(l, &a)) + return false; + if (extract(r, &m)) + { + *rv = IP4Mask(a, m); + return true; + } + if (!extract(r, &b) || b > 32) + return false; + } + else + { + // a.b.c.d or a.b.c.d. or a.b.c. or a.b. or a. + if (extract(str, &a)) + { + *rv = IP4Mask(a, IP4_BROADCAST); + return true; + } + if (!str.endswith('.')) + return false; + uint8_t d[4] {}; + if (extract(str, record<'.'>(&d[0], &d[1], &d[2], &d[3]))) + b = 32; + else if (extract(str, record<'.'>(&d[0], &d[1], &d[2]))) + b = 24; + else if (extract(str, record<'.'>(&d[0], &d[1]))) + b = 16; + else if (extract(str, record<'.'>(&d[0]))) + b = 8; + else + return false; + a = IP4Address(d); + } + // a is set; need to construct m from b + if (b == 0) + m = IP4Address(); + else if (b == 32) + m = IP4_BROADCAST; + else + { + uint32_t s = -1; + s <<= (32 - b); + m = IP4Address({ + static_cast<uint8_t>(s >> 24), + static_cast<uint8_t>(s >> 16), + static_cast<uint8_t>(s >> 8), + static_cast<uint8_t>(s >> 0), + }); + } + *rv = IP4Mask(a, m); + return true; +} + +VString<15> convert_for_printf(IP4Address a_) +{ + const uint8_t *a = a_.bytes(); + return STRNPRINTF(16, "%hhu.%hhu.%hhu.%hhu", a[0], a[1], a[2], a[3]); +} + +VString<31> convert_for_printf(IP4Mask a) +{ + return STRNPRINTF(32, "%s/%s", + a.addr(), a.mask()); +} diff --git a/src/mmo/ip.hpp b/src/mmo/ip.hpp new file mode 100644 index 0000000..a425710 --- /dev/null +++ b/src/mmo/ip.hpp @@ -0,0 +1,164 @@ +#ifndef TMWA_MMO_IP_HPP +#define TMWA_MMO_IP_HPP +// ip.hpp - classes to deal with IP addresses. +// +// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +# include "../sanity.hpp" + +# include <netinet/in.h> + +# include "../strings/fwd.hpp" + +# include "extract.hpp" + +// TODO - in the long run ports belong here also +// and of course, IPv6 stuff. +// But what about unix socket addresses? + +/// Helper function +template<class T, size_t n> +constexpr +bool _ce_a_lt(T (&a)[n], T (&b)[n], size_t i=0) +{ + return (i != n + && (a[i] < b[i] + || (a[i] == b[i] + && _ce_a_lt(a, b, i + 1)))); +} + +/// A 32-bit Ipv4 address. Does not include a port. +/// Guaranteed to be laid out like the network wants. +class IP4Address +{ + uint8_t _addr[4]; +public: + constexpr + IP4Address() + : _addr{} + {} + constexpr explicit + IP4Address(const uint8_t (&a)[4]) + : _addr{a[0], a[1], a[2], a[3]} + {} + explicit + IP4Address(struct in_addr addr) + { + static_assert(sizeof(addr) == sizeof(_addr), "4 bytes"); + *this = IP4Address(reinterpret_cast<const uint8_t (&)[4]>(addr)); + } + explicit + operator struct in_addr() const + { + return reinterpret_cast<const struct in_addr&>(_addr); + } + + constexpr friend + IP4Address operator & (IP4Address l, IP4Address r) + { + return IP4Address({ + static_cast<uint8_t>(l._addr[0] & r._addr[0]), + static_cast<uint8_t>(l._addr[1] & r._addr[1]), + static_cast<uint8_t>(l._addr[2] & r._addr[2]), + static_cast<uint8_t>(l._addr[3] & r._addr[3]), + }); + } + + IP4Address& operator &= (IP4Address m) + { return *this = *this & m; } + + const uint8_t *bytes() const + { return _addr; } + + constexpr friend + bool operator < (IP4Address l, IP4Address r) + { + return _ce_a_lt(l._addr, r._addr); + } + + constexpr friend + bool operator > (IP4Address l, IP4Address r) + { + return _ce_a_lt(r._addr, l._addr); + } + + constexpr friend + bool operator >= (IP4Address l, IP4Address r) + { + return !_ce_a_lt(l._addr, r._addr); + } + + constexpr friend + bool operator <= (IP4Address l, IP4Address r) + { + return !_ce_a_lt(r._addr, l._addr); + } + + constexpr friend + bool operator == (IP4Address l, IP4Address r) + { + return !(l < r || r < l); + } + + constexpr friend + bool operator != (IP4Address l, IP4Address r) + { + return (l < r || r < l); + } +}; + +class IP4Mask +{ + IP4Address _addr, _mask; +public: + constexpr + IP4Mask() : _addr(), _mask() + {} + constexpr + IP4Mask(IP4Address a, IP4Address m) : _addr(a & m), _mask(m) + {} + + constexpr + IP4Address addr() const + { return _addr; } + constexpr + IP4Address mask() const + { return _mask; } + + constexpr + bool covers(IP4Address a) const + { + return (a & _mask) == _addr; + } +}; + + +constexpr +IP4Address IP4_LOCALHOST({127, 0, 0, 1}); +constexpr +IP4Address IP4_BROADCAST({255, 255, 255, 255}); + + +VString<15> convert_for_printf(IP4Address a); +VString<31> convert_for_printf(IP4Mask m); + +bool extract(XString str, IP4Address *iv); + +bool extract(XString str, IP4Mask *iv); + +#endif // TMWA_MMO_IP_HPP diff --git a/src/mmo/ip.py b/src/mmo/ip.py new file mode 100644 index 0000000..e6a8183 --- /dev/null +++ b/src/mmo/ip.py @@ -0,0 +1,14 @@ +class IP4Address(object): + ''' print an IP4Address + ''' + __slots__ = ('_value') + name = 'IP4Address' + enabled = True + + def __init__(self, value): + self._value = value + + def to_string(self): + addr = self._value['_addr'] + addr = tuple(int(addr[i]) for i in range(4)) + return '%d.%d.%d.%d' % addr diff --git a/src/mmo/ip_test.cpp b/src/mmo/ip_test.cpp new file mode 100644 index 0000000..7ef1047 --- /dev/null +++ b/src/mmo/ip_test.cpp @@ -0,0 +1,332 @@ +#include "ip.hpp" + +#include <gtest/gtest.h> + +#include "../io/cxxstdio.hpp" + +#define CB(X) (std::integral_constant<bool, (X)>::value) +TEST(ip4addr, cmp) +{ + constexpr static + IP4Address a = IP4_LOCALHOST; + constexpr static + IP4Address b = IP4_BROADCAST; + + EXPECT_FALSE(CB(a < a)); + EXPECT_TRUE (CB(a < b)); + EXPECT_FALSE(CB(b < a)); + EXPECT_FALSE(CB(b < b)); + + EXPECT_FALSE(CB(a > a)); + EXPECT_FALSE(CB(a > b)); + EXPECT_TRUE (CB(b > a)); + EXPECT_FALSE(CB(b > b)); + + EXPECT_TRUE (CB(a <= a)); + EXPECT_TRUE (CB(a <= b)); + EXPECT_FALSE(CB(b <= a)); + EXPECT_TRUE (CB(b <= b)); + + EXPECT_TRUE (CB(a >= a)); + EXPECT_FALSE(CB(a >= b)); + EXPECT_TRUE (CB(b >= a)); + EXPECT_TRUE (CB(b >= b)); + + EXPECT_TRUE (CB(a == a)); + EXPECT_FALSE(CB(a == b)); + EXPECT_FALSE(CB(b == a)); + EXPECT_TRUE (CB(b == b)); + + EXPECT_FALSE(CB(a != a)); + EXPECT_TRUE (CB(a != b)); + EXPECT_TRUE (CB(b != a)); + EXPECT_FALSE(CB(b != b)); +} + +TEST(ip4addr, str) +{ + IP4Address a; + EXPECT_EQ("0.0.0.0", STRNPRINTF(17, "%s", a)); + EXPECT_EQ("127.0.0.1", STRNPRINTF(17, "%s", IP4_LOCALHOST)); + EXPECT_EQ("255.255.255.255", STRNPRINTF(17, "%s", IP4_BROADCAST)); +} + +TEST(ip4addr, extract) +{ + IP4Address a; + EXPECT_TRUE(extract("0.0.0.0", &a)); + EXPECT_EQ("0.0.0.0", STRNPRINTF(16, "%s", a)); + EXPECT_TRUE(extract("127.0.0.1", &a)); + EXPECT_EQ("127.0.0.1", STRNPRINTF(16, "%s", a)); + EXPECT_TRUE(extract("255.255.255.255", &a)); + EXPECT_EQ("255.255.255.255", STRNPRINTF(16, "%s", a)); + EXPECT_TRUE(extract("1.2.3.4", &a)); + EXPECT_EQ("1.2.3.4", STRNPRINTF(16, "%s", a)); + + EXPECT_FALSE(extract("1.2.3.4.5", &a)); + EXPECT_FALSE(extract("1.2.3.4.", &a)); + EXPECT_FALSE(extract("1.2.3.", &a)); + EXPECT_FALSE(extract("1.2.3", &a)); + EXPECT_FALSE(extract("1.2.", &a)); + EXPECT_FALSE(extract("1.2", &a)); + EXPECT_FALSE(extract("1.", &a)); + EXPECT_FALSE(extract("1", &a)); + EXPECT_FALSE(extract("", &a)); +} + + +TEST(ip4mask, body) +{ + IP4Mask m; + EXPECT_EQ(IP4Address(), m.addr()); + EXPECT_EQ(IP4Address(), m.mask()); + m = IP4Mask(IP4_LOCALHOST, IP4_BROADCAST); + EXPECT_EQ(IP4_LOCALHOST, m.addr()); + EXPECT_EQ(IP4_BROADCAST, m.mask()); +} + +TEST(ip4mask, str) +{ + IP4Mask m; + EXPECT_EQ("0.0.0.0/0.0.0.0", STRNPRINTF(33, "%s", m)); + m = IP4Mask(IP4_LOCALHOST, IP4_BROADCAST); + EXPECT_EQ("127.0.0.1/255.255.255.255", STRNPRINTF(33, "%s", m)); +} + +TEST(ip4mask, extract) +{ + IP4Mask m; + EXPECT_FALSE(extract("9.8.7.6/33", &m)); + EXPECT_FALSE(extract("9.8.7.6.5", &m)); + EXPECT_FALSE(extract("9.8.7.6/", &m)); + EXPECT_FALSE(extract("9.8.7", &m)); + EXPECT_FALSE(extract("9.8", &m)); + EXPECT_FALSE(extract("9", &m)); + + EXPECT_TRUE(extract("127.0.0.1", &m)); + EXPECT_EQ("127.0.0.1/255.255.255.255", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("127.0.0.1.", &m)); + EXPECT_EQ("127.0.0.1/255.255.255.255", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("127.0.0.", &m)); + EXPECT_EQ("127.0.0.0/255.255.255.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("127.0.", &m)); + EXPECT_EQ("127.0.0.0/255.255.0.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("127.", &m)); + EXPECT_EQ("127.0.0.0/255.0.0.0", STRNPRINTF(32, "%s", m)); + + EXPECT_TRUE(extract("1.2.3.4/255.255.255.255", &m)); + EXPECT_EQ("1.2.3.4/255.255.255.255", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("1.2.3.0/255.255.255.0", &m)); + EXPECT_EQ("1.2.3.0/255.255.255.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("1.2.0.4/255.255.0.255", &m)); + EXPECT_EQ("1.2.0.4/255.255.0.255", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("1.2.0.0/255.255.0.0", &m)); + EXPECT_EQ("1.2.0.0/255.255.0.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("1.0.3.4/255.0.255.255", &m)); + EXPECT_EQ("1.0.3.4/255.0.255.255", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("1.0.3.0/255.0.255.0", &m)); + EXPECT_EQ("1.0.3.0/255.0.255.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("1.0.0.4/255.0.0.255", &m)); + EXPECT_EQ("1.0.0.4/255.0.0.255", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("1.0.0.0/255.0.0.0", &m)); + EXPECT_EQ("1.0.0.0/255.0.0.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.2.3.4/0.255.255.255", &m)); + EXPECT_EQ("0.2.3.4/0.255.255.255", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.2.3.0/0.255.255.0", &m)); + EXPECT_EQ("0.2.3.0/0.255.255.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.2.0.4/0.255.0.255", &m)); + EXPECT_EQ("0.2.0.4/0.255.0.255", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.2.0.0/0.255.0.0", &m)); + EXPECT_EQ("0.2.0.0/0.255.0.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.3.4/0.0.255.255", &m)); + EXPECT_EQ("0.0.3.4/0.0.255.255", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.3.0/0.0.255.0", &m)); + EXPECT_EQ("0.0.3.0/0.0.255.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.4/0.0.0.255", &m)); + EXPECT_EQ("0.0.0.4/0.0.0.255", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/0.0.0.0", &m)); + EXPECT_EQ("0.0.0.0/0.0.0.0", STRNPRINTF(32, "%s", m)); + + // please don't do this + EXPECT_TRUE(extract("120.248.200.217/89.57.126.5", &m)); + EXPECT_EQ("88.56.72.1/89.57.126.5", STRNPRINTF(32, "%s", m)); + + EXPECT_TRUE(extract("0.0.0.0/32", &m)); + EXPECT_EQ("0.0.0.0/255.255.255.255", STRNPRINTF(32, "%s", m)); + + EXPECT_TRUE(extract("0.0.0.0/31", &m)); + EXPECT_EQ("0.0.0.0/255.255.255.254", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/30", &m)); + EXPECT_EQ("0.0.0.0/255.255.255.252", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/29", &m)); + EXPECT_EQ("0.0.0.0/255.255.255.248", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/28", &m)); + EXPECT_EQ("0.0.0.0/255.255.255.240", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/27", &m)); + EXPECT_EQ("0.0.0.0/255.255.255.224", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/26", &m)); + EXPECT_EQ("0.0.0.0/255.255.255.192", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/25", &m)); + EXPECT_EQ("0.0.0.0/255.255.255.128", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/24", &m)); + EXPECT_EQ("0.0.0.0/255.255.255.0", STRNPRINTF(32, "%s", m)); + + EXPECT_TRUE(extract("0.0.0.0/23", &m)); + EXPECT_EQ("0.0.0.0/255.255.254.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/22", &m)); + EXPECT_EQ("0.0.0.0/255.255.252.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/21", &m)); + EXPECT_EQ("0.0.0.0/255.255.248.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/20", &m)); + EXPECT_EQ("0.0.0.0/255.255.240.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/19", &m)); + EXPECT_EQ("0.0.0.0/255.255.224.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/18", &m)); + EXPECT_EQ("0.0.0.0/255.255.192.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/17", &m)); + EXPECT_EQ("0.0.0.0/255.255.128.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/16", &m)); + EXPECT_EQ("0.0.0.0/255.255.0.0", STRNPRINTF(32, "%s", m)); + + EXPECT_TRUE(extract("0.0.0.0/15", &m)); + EXPECT_EQ("0.0.0.0/255.254.0.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/14", &m)); + EXPECT_EQ("0.0.0.0/255.252.0.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/13", &m)); + EXPECT_EQ("0.0.0.0/255.248.0.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/12", &m)); + EXPECT_EQ("0.0.0.0/255.240.0.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/11", &m)); + EXPECT_EQ("0.0.0.0/255.224.0.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/10", &m)); + EXPECT_EQ("0.0.0.0/255.192.0.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/9", &m)); + EXPECT_EQ("0.0.0.0/255.128.0.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/8", &m)); + EXPECT_EQ("0.0.0.0/255.0.0.0", STRNPRINTF(32, "%s", m)); + + EXPECT_TRUE(extract("0.0.0.0/7", &m)); + EXPECT_EQ("0.0.0.0/254.0.0.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/6", &m)); + EXPECT_EQ("0.0.0.0/252.0.0.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/5", &m)); + EXPECT_EQ("0.0.0.0/248.0.0.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/4", &m)); + EXPECT_EQ("0.0.0.0/240.0.0.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/3", &m)); + EXPECT_EQ("0.0.0.0/224.0.0.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/2", &m)); + EXPECT_EQ("0.0.0.0/192.0.0.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/1", &m)); + EXPECT_EQ("0.0.0.0/128.0.0.0", STRNPRINTF(32, "%s", m)); + EXPECT_TRUE(extract("0.0.0.0/0", &m)); + EXPECT_EQ("0.0.0.0/0.0.0.0", STRNPRINTF(32, "%s", m)); +} + +TEST(ip4mask, cover) +{ + IP4Address a; + IP4Address b = IP4_BROADCAST; + IP4Address l = IP4_LOCALHOST; + IP4Address h({127, 255, 255, 255}); + IP4Address p24l({10, 0, 0, 0}); + IP4Address p24h({10, 255, 255, 255}); + IP4Address p20l({172, 16, 0, 0}); + IP4Address p20h({172, 31, 255, 255}); + IP4Address p16l({192, 168, 0, 0}); + IP4Address p16h({192, 168, 255, 255}); + IP4Mask m; + EXPECT_TRUE(m.covers(a)); + EXPECT_TRUE(m.covers(b)); + EXPECT_TRUE(m.covers(l)); + EXPECT_TRUE(m.covers(h)); + EXPECT_TRUE(m.covers(p24l)); + EXPECT_TRUE(m.covers(p24h)); + EXPECT_TRUE(m.covers(p20l)); + EXPECT_TRUE(m.covers(p20h)); + EXPECT_TRUE(m.covers(p16l)); + EXPECT_TRUE(m.covers(p16h)); + m = IP4Mask(l, a); + EXPECT_TRUE(m.covers(a)); + EXPECT_TRUE(m.covers(b)); + EXPECT_TRUE(m.covers(l)); + EXPECT_TRUE(m.covers(h)); + EXPECT_TRUE(m.covers(p24l)); + EXPECT_TRUE(m.covers(p24h)); + EXPECT_TRUE(m.covers(p20l)); + EXPECT_TRUE(m.covers(p20h)); + EXPECT_TRUE(m.covers(p16l)); + EXPECT_TRUE(m.covers(p16h)); + m = IP4Mask(l, b); + EXPECT_FALSE(m.covers(a)); + EXPECT_FALSE(m.covers(b)); + EXPECT_TRUE(m.covers(l)); + EXPECT_FALSE(m.covers(h)); + EXPECT_FALSE(m.covers(p24l)); + EXPECT_FALSE(m.covers(p24h)); + EXPECT_FALSE(m.covers(p20l)); + EXPECT_FALSE(m.covers(p20h)); + EXPECT_FALSE(m.covers(p16l)); + EXPECT_FALSE(m.covers(p16h)); + + // but the really useful ones are with partial masks + m = IP4Mask(IP4Address({10, 0, 0, 0}), IP4Address({255, 0, 0, 0})); + EXPECT_FALSE(m.covers(a)); + EXPECT_FALSE(m.covers(b)); + EXPECT_FALSE(m.covers(l)); + EXPECT_FALSE(m.covers(h)); + EXPECT_TRUE(m.covers(p24l)); + EXPECT_TRUE(m.covers(p24h)); + EXPECT_FALSE(m.covers(p20l)); + EXPECT_FALSE(m.covers(p20h)); + EXPECT_FALSE(m.covers(p16l)); + EXPECT_FALSE(m.covers(p16h)); + EXPECT_FALSE(m.covers(IP4Address({9, 255, 255, 255}))); + EXPECT_FALSE(m.covers(IP4Address({11, 0, 0, 0}))); + m = IP4Mask(IP4Address({127, 0, 0, 0}), IP4Address({255, 0, 0, 0})); + EXPECT_FALSE(m.covers(a)); + EXPECT_FALSE(m.covers(b)); + EXPECT_TRUE(m.covers(l)); + EXPECT_TRUE(m.covers(h)); + EXPECT_FALSE(m.covers(p24l)); + EXPECT_FALSE(m.covers(p24h)); + EXPECT_FALSE(m.covers(p20l)); + EXPECT_FALSE(m.covers(p20h)); + EXPECT_FALSE(m.covers(p16l)); + EXPECT_FALSE(m.covers(p16h)); + EXPECT_FALSE(m.covers(IP4Address({126, 255, 255, 255}))); + EXPECT_FALSE(m.covers(IP4Address({128, 0, 0, 0}))); + m = IP4Mask(IP4Address({172, 16, 0, 0}), IP4Address({255, 240, 0, 0})); + EXPECT_FALSE(m.covers(a)); + EXPECT_FALSE(m.covers(b)); + EXPECT_FALSE(m.covers(l)); + EXPECT_FALSE(m.covers(h)); + EXPECT_FALSE(m.covers(p24l)); + EXPECT_FALSE(m.covers(p24h)); + EXPECT_TRUE(m.covers(p20l)); + EXPECT_TRUE(m.covers(p20h)); + EXPECT_FALSE(m.covers(p16l)); + EXPECT_FALSE(m.covers(p16h)); + EXPECT_FALSE(m.covers(IP4Address({172, 15, 255, 255}))); + EXPECT_FALSE(m.covers(IP4Address({172, 32, 0, 0}))); + m = IP4Mask(IP4Address({192, 168, 0, 0}), IP4Address({255, 255, 0, 0})); + EXPECT_FALSE(m.covers(a)); + EXPECT_FALSE(m.covers(b)); + EXPECT_FALSE(m.covers(l)); + EXPECT_FALSE(m.covers(h)); + EXPECT_FALSE(m.covers(p24l)); + EXPECT_FALSE(m.covers(p24h)); + EXPECT_FALSE(m.covers(p20l)); + EXPECT_FALSE(m.covers(p20h)); + EXPECT_TRUE(m.covers(p16l)); + EXPECT_TRUE(m.covers(p16h)); + EXPECT_FALSE(m.covers(IP4Address({192, 167, 255, 255}))); + EXPECT_FALSE(m.covers(IP4Address({192, 169, 0, 0}))); + + // OTOH this is crazy + EXPECT_TRUE(extract("120.248.200.217/89.57.126.5", &m)); + EXPECT_TRUE(m.covers(IP4Address({120, 248, 200, 217}))); + EXPECT_TRUE(m.covers(IP4Address({88, 56, 72, 1}))); + EXPECT_FALSE(m.covers(IP4Address({88, 56, 72, 0}))); + EXPECT_FALSE(m.covers(IP4Address({88, 56, 72, 255}))); +} diff --git a/src/mmo/md5more.cpp b/src/mmo/md5more.cpp new file mode 100644 index 0000000..51ff5c4 --- /dev/null +++ b/src/mmo/md5more.cpp @@ -0,0 +1,128 @@ +#include "md5more.hpp" + +#include "../compat/rawmem.hpp" + +#include "../generic/random.hpp" + +#include "../io/cxxstdio.hpp" + +#include "../poison.hpp" + +#define X block.data + +// TODO - refactor MD5 into a stream, and merge the implementations +// I once implemented an ostream that does it ... +MD5_state MD5_from_FILE(io::ReadFile& in) +{ + uint64_t total_len = 0; + + uint8_t buf[0x40]; + uint8_t block_len = 0; + + MD5_state state; + MD5_init(&state); + + MD5_block block; + + while (true) + { + size_t rv = in.get(sign_cast<char *>(buf + block_len), 0x40 - block_len); + if (!rv) + break; + total_len += 8 * rv; // in bits + block_len += rv; + if (block_len != 0x40) + continue; + for (int i = 0; i < 0x10; i++) + X[i] = buf[4 * i + 0] | buf[4 * i + 1] << 8 | buf[4 * i + 2] << 16 | buf[4 * i + 3] << 24; + MD5_do_block(&state, block); + block_len = 0; + } + // no more input, just pad and append the length + buf[block_len] = 0x80; + really_memset0(buf + block_len + 1, 0x40 - block_len - 1); + if (block_len < 0x38) + { + for (int i = 0; i < 8; i++) + buf[0x38 + i] = total_len >> i * 8; + } + for (int i = 0; i < 0x10; i++) + X[i] = buf[4 * i + 0] | buf[4 * i + 1] << 8 | buf[4 * i + 2] << 16 | buf[4 * i + 3] << 24; + MD5_do_block(&state, block); + if (0x38 <= block_len) + { + really_memset0(buf, 0x38); + for (int i = 0; i < 8; i++) + buf[0x38 + i] = total_len >> i * 8; + for (int i = 0; i < 0x10; i++) + X[i] = buf[4 * i + 0] | buf[4 * i + 1] << 8 | buf[4 * i + 2] << 16 | buf[4 * i + 3] << 24; + MD5_do_block(&state, block); + } + return state; +} + + +// Hash a password with a salt. +// Whoever wrote this FAILS programming +AccountCrypt MD5_saltcrypt(AccountPass key, SaltString salt) +{ + char cbuf[64] {}; + + // hash the key then the salt + // buf ends up as a 64-char NUL-terminated string + md5_string tbuf, tbuf2; + MD5_to_str(MD5_from_string(key), tbuf); + MD5_to_str(MD5_from_string(salt), tbuf2); + const auto it = std::copy(tbuf.begin(), tbuf.end(), std::begin(cbuf)); + auto it2 = std::copy(tbuf2.begin(), tbuf2.end(), it); + assert(it2 == std::end(cbuf)); + + md5_string tbuf3; + MD5_to_str(MD5_from_string(XString(std::begin(cbuf), it2, nullptr)), tbuf3); + + VString<31> obuf; + + // This truncates the string, but we have to keep it like that for compatibility + SNPRINTF(obuf, 32, "!%s$%s", salt, tbuf3); + return stringish<AccountCrypt>(obuf); +} + +SaltString make_salt(void) +{ + char salt[5]; + for (int i = 0; i < 5; i++) + // 126 would probably actually be okay + salt[i] = random_::in(48, 125); + return stringish<SaltString>(XString(salt + 0, salt + 5, nullptr)); +} + +bool pass_ok(AccountPass password, AccountCrypt crypted) +{ + // crypted is like !salt$hash + auto begin = crypted.begin() + 1; + auto end = std::find(begin, crypted.end(), '$'); + SaltString salt = stringish<SaltString>(crypted.xislice(begin, end)); + + return crypted == MD5_saltcrypt(password, salt); +} + +// [M|h]ashes up an IP address and a secret key +// to return a hopefully unique masked IP. +IP4Address MD5_ip(IP4Address ip) +{ + static SaltString secret = make_salt(); + + // MD5sum a secret + the IP address + VString<31> ipbuf; + SNPRINTF(ipbuf, 32, "%s %s", ip, secret); + md5_binary obuf; + MD5_to_bin(MD5_from_string(ipbuf), obuf); + + // Fold the md5sum to 32 bits, pack the bytes to an in_addr + return IP4Address({ + static_cast<uint8_t>(obuf[0] ^ obuf[1] ^ obuf[8] ^ obuf[9]), + static_cast<uint8_t>(obuf[2] ^ obuf[3] ^ obuf[10] ^ obuf[11]), + static_cast<uint8_t>(obuf[4] ^ obuf[5] ^ obuf[12] ^ obuf[13]), + static_cast<uint8_t>(obuf[6] ^ obuf[7] ^ obuf[14] ^ obuf[15]), + }); +} diff --git a/src/mmo/md5more.hpp b/src/mmo/md5more.hpp new file mode 100644 index 0000000..0c50cca --- /dev/null +++ b/src/mmo/md5more.hpp @@ -0,0 +1,26 @@ +#ifndef TMWA_MMO_MD5MORE_HPP +#define TMWA_MMO_MD5MORE_HPP + +# include "../generic/md5.hpp" + +# include "../io/read.hpp" + +# include "ip.hpp" +# include "mmo.hpp" + +MD5_state MD5_from_FILE(io::ReadFile& in); + +// whoever wrote this fails basic understanding of +AccountCrypt MD5_saltcrypt(AccountPass key, SaltString salt); + +/// return some random characters +// Currently, returns a 5-char string +SaltString make_salt(void); + +/// check plaintext password against saved saltcrypt +bool pass_ok(AccountPass password, AccountCrypt crypted); + +/// This returns an IP4Address because it is configurable whether it gets called at all +IP4Address MD5_ip(IP4Address ip); + +#endif // TMWA_MMO_MD5MORE_HPP diff --git a/src/mmo/mmo.cpp b/src/mmo/mmo.cpp new file mode 100644 index 0000000..e9893ee --- /dev/null +++ b/src/mmo/mmo.cpp @@ -0,0 +1,3 @@ +#include "mmo.hpp" + +#include "../poison.hpp" diff --git a/src/mmo/mmo.hpp b/src/mmo/mmo.hpp new file mode 100644 index 0000000..6b3cd53 --- /dev/null +++ b/src/mmo/mmo.hpp @@ -0,0 +1,377 @@ +/// Global structures and defines +#ifndef TMWA_MMO_MMO_HPP +#define TMWA_MMO_MMO_HPP + +# include "../sanity.hpp" + +# include "../compat/memory.hpp" + +# include "../strings/vstring.hpp" + +# include "../generic/enum.hpp" + +# include "timer.t.hpp" + +// affects CharName +# define NAME_IGNORING_CASE 1 + +constexpr int FIFOSIZE_SERVERLINK = 256 * 1024; + +constexpr int MAX_MAP_PER_SERVER = 512; +constexpr int MAX_INVENTORY = 100; +constexpr int MAX_AMOUNT = 30000; +constexpr int MAX_ZENY = 1000000000; // 1G zeny + +enum class SkillID : uint16_t; +constexpr SkillID MAX_SKILL = SkillID(474); // not 450 +constexpr SkillID get_enum_min_value(SkillID) { return SkillID(); } +constexpr SkillID get_enum_max_value(SkillID) { return MAX_SKILL; } + +constexpr int GLOBAL_REG_NUM = 96; +constexpr int ACCOUNT_REG_NUM = 16; +constexpr int ACCOUNT_REG2_NUM = 16; +constexpr interval_t DEFAULT_WALK_SPEED = std::chrono::milliseconds(150); +constexpr interval_t MIN_WALK_SPEED = interval_t::zero(); +constexpr interval_t MAX_WALK_SPEED = std::chrono::seconds(1); +constexpr int MAX_STORAGE = 300; +constexpr int MAX_PARTY = 12; + +# define MIN_HAIR_STYLE battle_config.min_hair_style +# define MAX_HAIR_STYLE battle_config.max_hair_style +# define MIN_HAIR_COLOR battle_config.min_hair_color +# define MAX_HAIR_COLOR battle_config.max_hair_color +# define MIN_CLOTH_COLOR battle_config.min_cloth_color +# define MAX_CLOTH_COLOR battle_config.max_cloth_color + +struct AccountName : VString<23> {}; +struct AccountPass : VString<23> {}; +struct AccountCrypt : VString<39> {}; +struct AccountEmail : VString<39> {}; +struct ServerName : VString<19> {}; +struct PartyName : VString<23> {}; +struct VarName : VString<31> {}; + +# define DEFAULT_EMAIL stringish<AccountEmail>("a@a.com") + +// It is decreed: a mapname shall not contain an extension +class MapName : public strings::_crtp_string<MapName, MapName, strings::ZPair> +{ + VString<15> _impl; +public: + MapName() = default; + MapName(VString<15> v) : _impl(v.xislice_h(std::find(v.begin(), v.end(), '.'))) {} + + iterator begin() const { return &*_impl.begin(); } + iterator end() const { return &*_impl.end(); } + const char *c_str() const { return _impl.c_str(); } + + operator RString() const { return _impl; } + operator AString() const { return _impl; } + operator TString() const { return _impl; } + operator SString() const { return _impl; } + operator ZString() const { return _impl; } + operator XString() const { return _impl; } +}; +template<> +inline +MapName stringish<MapName>(VString<15> iv) +{ + return iv; +} +inline +const char *decay_for_printf(const MapName& vs) { return vs.c_str(); } + +// It is decreed: a charname is sometimes case sensitive +struct CharName +{ +private: + VString<23> _impl; +public: + CharName() = default; + explicit CharName(VString<23> name) + : _impl(name) + {} + + VString<23> to__actual() const + { + return _impl; + } + VString<23> to__lower() const + { + return _impl.to_lower(); + } + VString<23> to__upper() const + { + return _impl.to_upper(); + } + VString<23> to__canonical() const + { +# if NAME_IGNORING_CASE == 0 + return to__actual(); +# endif +# if NAME_IGNORING_CASE == 1 + return to__lower(); +# endif + } + + friend bool operator == (const CharName& l, const CharName& r) + { return l.to__canonical() == r.to__canonical(); } + friend bool operator != (const CharName& l, const CharName& r) + { return l.to__canonical() != r.to__canonical(); } + friend bool operator < (const CharName& l, const CharName& r) + { return l.to__canonical() < r.to__canonical(); } + friend bool operator <= (const CharName& l, const CharName& r) + { return l.to__canonical() <= r.to__canonical(); } + friend bool operator > (const CharName& l, const CharName& r) + { return l.to__canonical() > r.to__canonical(); } + friend bool operator >= (const CharName& l, const CharName& r) + { return l.to__canonical() >= r.to__canonical(); } + + friend + VString<23> convert_for_printf(const CharName& vs) { return vs.to__actual(); } +}; +template<> +inline +CharName stringish<CharName>(VString<23> iv) +{ + return CharName(iv); +} + +namespace e +{ +enum class EPOS : uint16_t +{ + ZERO = 0x0000, + + LEGS = 0x0001, + WEAPON = 0x0002, + GLOVES = 0x0004, + CAPE = 0x0008, + MISC1 = 0x0010, + SHIELD = 0x0020, + SHOES = 0x0040, + MISC2 = 0x0080, + HAT = 0x0100, + TORSO = 0x0200, + + ARROW = 0x8000, +}; +ENUM_BITWISE_OPERATORS(EPOS) + +constexpr EPOS get_enum_min_value(EPOS) { return EPOS(0x0000); } +constexpr EPOS get_enum_max_value(EPOS) { return EPOS(0xffff); } +} +using e::EPOS; + +struct item +{ + int id; + short nameid; + short amount; + EPOS equip; +}; + +struct point +{ + MapName map_; + short x, y; +}; + +namespace e +{ +enum class SkillFlags : uint16_t; +} +using e::SkillFlags; + +struct skill_value +{ + unsigned short lv; + SkillFlags flags; + + friend bool operator == (const skill_value& l, const skill_value& r) + { + return l.lv == r.lv && l.flags == r.flags; + } + friend bool operator != (const skill_value& l, const skill_value& r) + { + return !(l == r); + } +}; + +struct global_reg +{ + VarName str; + int value; +}; + +// Option and Opt1..3 in map.hpp +namespace e +{ +enum class Option : uint16_t; +constexpr Option get_enum_min_value(Option) { return Option(0x0000); } +constexpr Option get_enum_max_value(Option) { return Option(0xffff); } +} +using e::Option; + +enum class ATTR +{ + STR = 0, + AGI = 1, + VIT = 2, + INT = 3, + DEX = 4, + LUK = 5, + + COUNT = 6, +}; + +constexpr ATTR ATTRs[6] = +{ + ATTR::STR, + ATTR::AGI, + ATTR::VIT, + ATTR::INT, + ATTR::DEX, + ATTR::LUK, +}; + +enum class ItemLook : uint16_t +{ + NONE = 0, + BLADE = 1, // or some other common weapons + _2, + SETZER_AND_SCYTHE = 3, + _6, + STAFF = 10, + BOW = 11, + _13 = 13, + _14 = 14, + _16 = 16, + SINGLE_HANDED_COUNT = 17, + + DUAL_BLADE = 0x11, + DUAL_2 = 0x12, + DUAL_6 = 0x13, + DUAL_12 = 0x14, + DUAL_16 = 0x15, + DUAL_26 = 0x16, +}; + +enum class SEX : uint8_t +{ + FEMALE = 0, + MALE = 1, + // For items. This is also used as error, sometime. + NEUTRAL = 2, +}; +inline +char sex_to_char(SEX sex) +{ + switch (sex) + { + case SEX::FEMALE: return 'F'; + case SEX::MALE: return 'M'; + default: return '\0'; + } +} +inline +SEX sex_from_char(char c) +{ + switch (c) + { + case 'F': return SEX::FEMALE; + case 'M': return SEX::MALE; + default: return SEX::NEUTRAL; + } +} + +struct CharKey +{ + CharName name; + int account_id; + int char_id; + unsigned char char_num; +}; + +struct CharData +{ + int partner_id; + + int base_exp, job_exp, zeny; + + short species; + short status_point, skill_point; + int hp, max_hp, sp, max_sp; + Option option; + short karma, manner; + short hair, hair_color, clothes_color; + int party_id; + + ItemLook weapon; + short shield; + short head_top, head_mid, head_bottom; + + unsigned char base_level, job_level; + earray<short, ATTR, ATTR::COUNT> attrs; + SEX sex; + + unsigned long mapip; + unsigned int mapport; + + struct point last_point, save_point; + struct item inventory[MAX_INVENTORY]; + earray<skill_value, SkillID, MAX_SKILL> skill; + int global_reg_num; + struct global_reg global_reg[GLOBAL_REG_NUM]; + int account_reg_num; + struct global_reg account_reg[ACCOUNT_REG_NUM]; + int account_reg2_num; + struct global_reg account_reg2[ACCOUNT_REG2_NUM]; +}; + +struct CharPair +{ + CharKey key; + std::unique_ptr<CharData> data; + + CharPair() + : key{}, data(make_unique<CharData>()) + {} +}; + +struct storage +{ + int dirty; + int account_id; + short storage_status; + short storage_amount; + struct item storage_[MAX_STORAGE]; +}; + +//struct map_session_data; + +struct GM_Account +{ + int account_id; + uint8_t level; +}; + +struct party_member +{ + int account_id; + CharName name; + MapName map; + int leader, online, lv; + struct map_session_data *sd; +}; + +struct party +{ + int party_id; + PartyName name; + int exp; + int item; + struct party_member member[MAX_PARTY]; +}; + +#endif // TMWA_MMO_MMO_HPP diff --git a/src/mmo/socket.cpp b/src/mmo/socket.cpp new file mode 100644 index 0000000..73e32a4 --- /dev/null +++ b/src/mmo/socket.cpp @@ -0,0 +1,474 @@ +#include "socket.hpp" + +#include <arpa/inet.h> +#include <netinet/tcp.h> +#include <sys/socket.h> +//#include <sys/types.h> + +#include <fcntl.h> +#include <unistd.h> + +#include <cstdlib> +#include <cstring> +#include <ctime> + +#include "../io/cxxstdio.hpp" +#include "core.hpp" +#include "timer.hpp" +#include "utils.hpp" + +#include "../poison.hpp" + +static +io::FD_Set readfds; +static +int fd_max; + +static +const uint32_t RFIFO_SIZE = 65536; +static +const uint32_t WFIFO_SIZE = 65536; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +static +std::array<std::unique_ptr<Session>, FD_SETSIZE> session; +#pragma GCC diagnostic pop + +void set_session(io::FD fd, std::unique_ptr<Session> sess) +{ + int f = fd.uncast_dammit(); + assert (0 <= f && f < FD_SETSIZE); + session[f] = std::move(sess); +} +Session *get_session(io::FD fd) +{ + int f = fd.uncast_dammit(); + if (0 <= f && f < FD_SETSIZE) + return session[f].get(); + return nullptr; +} +void reset_session(io::FD fd) +{ + int f = fd.uncast_dammit(); + assert (0 <= f && f < FD_SETSIZE); + session[f] = nullptr; +} +int get_fd_max() { return fd_max; } +IteratorPair<ValueIterator<io::FD, IncrFD>> iter_fds() +{ + return {io::FD::cast_dammit(0), io::FD::cast_dammit(fd_max)}; +} + +/// clean up by discarding handled bytes +inline +void RFIFOFLUSH(Session *s) +{ + really_memmove(&s->rdata[0], &s->rdata[s->rdata_pos], RFIFOREST(s)); + s->rdata_size = RFIFOREST(s); + s->rdata_pos = 0; +} + +/// how much room there is to read more data +inline +size_t RFIFOSPACE(Session *s) +{ + return s->max_rdata - s->rdata_size; +} + + +/// Discard all input +static +void null_parse(Session *s); +/// Default parser for new connections +static +void (*default_func_parse)(Session *) = null_parse; + +void set_defaultparse(void (*defaultparse)(Session *)) +{ + default_func_parse = defaultparse; +} + +/// Read from socket to the queue +static +void recv_to_fifo(Session *s) +{ + if (s->eof) + return; + + ssize_t len = s->fd.read(&s->rdata[s->rdata_size], + RFIFOSPACE(s)); + + if (len > 0) + { + s->rdata_size += len; + s->connected = 1; + } + else + { + s->eof = 1; + } +} + +static +void send_from_fifo(Session *s) +{ + if (s->eof) + return; + + ssize_t len = s->fd.write(&s->wdata[0], s->wdata_size); + + if (len > 0) + { + s->wdata_size -= len; + if (s->wdata_size) + { + really_memmove(&s->wdata[0], &s->wdata[len], + s->wdata_size); + } + s->connected = 1; + } + else + { + s->eof = 1; + } +} + +static +void null_parse(Session *s) +{ + PRINTF("null_parse : %d\n", s); + RFIFOSKIP(s, RFIFOREST(s)); +} + + +static +void connect_client(Session *ls) +{ + struct sockaddr_in client_address; + socklen_t len = sizeof(client_address); + + io::FD fd = ls->fd.accept(reinterpret_cast<struct sockaddr *>(&client_address), &len); + if (fd == io::FD()) + { + perror("accept"); + return; + } + if (fd.uncast_dammit() >= SOFT_LIMIT) + { + FPRINTF(stderr, "softlimit reached, disconnecting : %d\n", fd.uncast_dammit()); + fd.shutdown(SHUT_RDWR); + fd.close(); + return; + } + if (fd_max <= fd.uncast_dammit()) + { + fd_max = fd.uncast_dammit() + 1; + } + + const int yes = 1; + /// Allow to bind() again after the server restarts. + // Since the socket is still in the TIME_WAIT, there's a possibility + // that formerly lost packets might be delivered and confuse the server. + fd.setsockopt(SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes); + /// Send packets as soon as possible + /// even if the kernel thinks there is too little for it to be worth it! + /// Testing shows this is indeed a good idea. + fd.setsockopt(IPPROTO_TCP, TCP_NODELAY, &yes, sizeof yes); + + // Linux-ism: Set socket options to optimize for thin streams + // See http://lwn.net/Articles/308919/ and + // Documentation/networking/tcp-thin.txt .. Kernel 3.2+ +#ifdef TCP_THIN_LINEAR_TIMEOUTS + fd.setsockopt(IPPROTO_TCP, TCP_THIN_LINEAR_TIMEOUTS, &yes, sizeof yes); +#endif +#ifdef TCP_THIN_DUPACK + fd.setsockopt(IPPROTO_TCP, TCP_THIN_DUPACK, &yes, sizeof yes); +#endif + + readfds.set(fd); + + fd.fcntl(F_SETFL, O_NONBLOCK); + + set_session(fd, make_unique<Session>()); + Session *s = get_session(fd); + s->fd = fd; + s->rdata.new_(RFIFO_SIZE); + s->wdata.new_(WFIFO_SIZE); + + s->max_rdata = RFIFO_SIZE; + s->max_wdata = WFIFO_SIZE; + s->func_recv = recv_to_fifo; + s->func_send = send_from_fifo; + s->func_parse = default_func_parse; + s->client_ip = IP4Address(client_address.sin_addr); + s->created = TimeT::now(); + s->connected = 0; +} + +Session *make_listen_port(uint16_t port) +{ + struct sockaddr_in server_address; + io::FD fd = io::FD::socket(AF_INET, SOCK_STREAM, 0); + if (fd == io::FD()) + { + perror("socket"); + return nullptr; + } + if (fd_max <= fd.uncast_dammit()) + fd_max = fd.uncast_dammit() + 1; + + fd.fcntl(F_SETFL, O_NONBLOCK); + + const int yes = 1; + /// Allow to bind() again after the server restarts. + // Since the socket is still in the TIME_WAIT, there's a possibility + // that formerly lost packets might be delivered and confuse the server. + fd.setsockopt(SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes); + /// Send packets as soon as possible + /// even if the kernel thinks there is too little for it to be worth it! + // I'm not convinced this is a good idea; although in minimizes the + // latency for an individual write, it increases traffic in general. + fd.setsockopt(IPPROTO_TCP, TCP_NODELAY, &yes, sizeof yes); + + server_address.sin_family = AF_INET; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#if __GNUC__ > 4 || __GNUC_MINOR__ >= 8 +# pragma GCC diagnostic ignored "-Wuseless-cast" +#endif + server_address.sin_addr.s_addr = htonl(INADDR_ANY); + server_address.sin_port = htons(port); +#pragma GCC diagnostic pop + + if (fd.bind(reinterpret_cast<struct sockaddr *>(&server_address), + sizeof(server_address)) == -1) + { + perror("bind"); + exit(1); + } + if (fd.listen(5) == -1) + { /* error */ + perror("listen"); + exit(1); + } + + readfds.set(fd); + + set_session(fd, make_unique<Session>()); + Session *s = get_session(fd); + s->fd = fd; + + s->func_recv = connect_client; + s->created = TimeT::now(); + s->connected = 1; + + return s; +} + +Session *make_connection(IP4Address ip, uint16_t port) +{ + struct sockaddr_in server_address; + io::FD fd = io::FD::socket(AF_INET, SOCK_STREAM, 0); + if (fd == io::FD()) + { + perror("socket"); + return nullptr; + } + if (fd_max <= fd.uncast_dammit()) + fd_max = fd.uncast_dammit() + 1; + + const int yes = 1; + /// Allow to bind() again after the server restarts. + // Since the socket is still in the TIME_WAIT, there's a possibility + // that formerly lost packets might be delivered and confuse the server. + fd.setsockopt(SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes); + /// Send packets as soon as possible + /// even if the kernel thinks there is too little for it to be worth it! + // I'm not convinced this is a good idea; although in minimizes the + // latency for an individual write, it increases traffic in general. + fd.setsockopt(IPPROTO_TCP, TCP_NODELAY, &yes, sizeof yes); + + server_address.sin_family = AF_INET; + server_address.sin_addr = in_addr(ip); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#if __GNUC__ > 4 || __GNUC_MINOR__ >= 8 +# pragma GCC diagnostic ignored "-Wuseless-cast" +#endif + server_address.sin_port = htons(port); +#pragma GCC diagnostic pop + + fd.fcntl(F_SETFL, O_NONBLOCK); + + /// Errors not caught - we must not block + /// Let the main select() loop detect when we know the state + fd.connect(reinterpret_cast<struct sockaddr *>(&server_address), + sizeof(struct sockaddr_in)); + + readfds.set(fd); + + set_session(fd, make_unique<Session>()); + Session *s = get_session(fd); + s->fd = fd; + s->rdata.new_(RFIFO_SIZE); + s->wdata.new_(WFIFO_SIZE); + + s->max_rdata = RFIFO_SIZE; + s->max_wdata = WFIFO_SIZE; + s->func_recv = recv_to_fifo; + s->func_send = send_from_fifo; + s->func_parse = default_func_parse; + s->created = TimeT::now(); + s->connected = 1; + + return s; +} + +void delete_session(Session *s) +{ + if (!s) + return; + + io::FD fd = s->fd; + // If this was the highest fd, decrease it + // We could add a loop to decrement fd_max further for every null session, + // but this is cheap and good enough for the typical case + if (fd.uncast_dammit() == fd_max - 1) + fd_max--; + readfds.clr(fd); + { + s->rdata.delete_(); + s->wdata.delete_(); + s->session_data.reset(); + reset_session(fd); + } + + // just close() would try to keep sending buffers + fd.shutdown(SHUT_RDWR); + fd.close(); +} + +void realloc_fifo(Session *s, size_t rfifo_size, size_t wfifo_size) +{ + if (s->max_rdata != rfifo_size && s->rdata_size < rfifo_size) + { + s->rdata.resize(rfifo_size); + s->max_rdata = rfifo_size; + } + if (s->max_wdata != wfifo_size && s->wdata_size < wfifo_size) + { + s->wdata.resize(wfifo_size); + s->max_wdata = wfifo_size; + } +} + +void WFIFOSET(Session *s, size_t len) +{ + if (s->wdata_size + len + 16384 > s->max_wdata) + { + realloc_fifo(s, s->max_rdata, s->max_wdata << 1); + PRINTF("socket: %d wdata expanded to %zu bytes.\n", s, s->max_wdata); + } + if (s->wdata_size + len + 2048 < s->max_wdata) + s->wdata_size += len; + else + FPRINTF(stderr, "socket: %d wdata lost !!\n", s), abort(); +} + +void do_sendrecv(interval_t next_ms) +{ + bool any = false; + io::FD_Set rfd = readfds, wfd; + for (io::FD i : iter_fds()) + { + Session *s = get_session(i); + if (s) + { + any = true; + if (s->wdata_size) + wfd.set(i); + } + } + if (!any) + { + if (!has_timers()) + { + PRINTF("Shutting down - nothing to do\n"); + runflag = false; + } + return; + } + struct timeval timeout; + { + std::chrono::seconds next_s = std::chrono::duration_cast<std::chrono::seconds>(next_ms); + std::chrono::microseconds next_us = next_ms - next_s; + timeout.tv_sec = next_s.count(); + timeout.tv_usec = next_us.count(); + } + if (io::FD_Set::select(fd_max, &rfd, &wfd, NULL, &timeout) <= 0) + return; + for (io::FD i : iter_fds()) + { + Session *s = get_session(i); + if (!s) + continue; + if (wfd.isset(i)) + { + if (s->func_send) + //send_from_fifo(i); + s->func_send(s); + } + if (rfd.isset(i)) + { + if (s->func_recv) + //recv_to_fifo(i); + //or connect_client(i); + s->func_recv(s); + } + } +} + +void do_parsepacket(void) +{ + for (io::FD i : iter_fds()) + { + Session *s = get_session(i); + if (!s) + continue; + if (!s->connected + && static_cast<time_t>(TimeT::now()) - static_cast<time_t>(s->created) > CONNECT_TIMEOUT) + { + PRINTF("Session #%d timed out\n", s); + s->eof = 1; + } + if (!s->rdata_size && !s->eof) + continue; + if (s->func_parse) + { + s->func_parse(s); + /// some func_parse may call delete_session + s = get_session(i); + if (s && s->eof) + { + delete_session(s); + s = nullptr; + } + if (!s) + continue; + } + /// Reclaim buffer space for what was read + RFIFOFLUSH(s); + } +} + +void RFIFOSKIP(Session *s, size_t len) +{ + s->rdata_pos += len; + + if (s->rdata_size < s->rdata_pos) + { + FPRINTF(stderr, "too many skip\n"); + abort(); + } +} diff --git a/src/mmo/socket.hpp b/src/mmo/socket.hpp new file mode 100644 index 0000000..a77f512 --- /dev/null +++ b/src/mmo/socket.hpp @@ -0,0 +1,373 @@ +#ifndef TMWA_MMO_SOCKET_HPP +#define TMWA_MMO_SOCKET_HPP + +# include "../sanity.hpp" + +# include <netinet/in.h> + +# include <cstdio> + +# include <array> + +# include "../compat/rawmem.hpp" + +# include "../strings/astring.hpp" +# include "../strings/vstring.hpp" +# include "../strings/xstring.hpp" + +# include "../io/fd.hpp" + +# include "dumb_ptr.hpp" +# include "ip.hpp" +# include "utils.hpp" +# include "timer.t.hpp" + +struct SessionData +{ +}; +struct SessionDeleter +{ + // defined per-server + void operator()(SessionData *sd); +}; + +// Struct declaration + +struct Session +{ + /// Checks whether a newly-connected socket actually does anything + TimeT created; + bool connected; + + /// Flag needed since structure must be freed in a server-dependent manner + bool eof; + + /// Since this is a single-threaded application, it can't block + /// These are the read/write queues + dumb_ptr<uint8_t[]> rdata, wdata; + size_t max_rdata, max_wdata; + /// How much is actually in the queue + size_t rdata_size, wdata_size; + /// How much has already been read from the queue + /// Note that there is no need for a wdata_pos + size_t rdata_pos; + + IP4Address client_ip; + + /// Send or recieve + /// Only called when select() indicates the socket is ready + /// If, after that, nothing is read, it sets eof + // These could probably be hard-coded with a little work + void (*func_recv)(Session *); + void (*func_send)(Session *); + /// This is the important one + /// Set to different functions depending on whether the connection + /// is a player or a server/ladmin + /// Can be set explicitly or via set_defaultparse + void (*func_parse)(Session *); + /// Server-specific data type + std::unique_ptr<SessionData, SessionDeleter> session_data; + + io::FD fd; +}; + +inline +int convert_for_printf(Session *s) +{ + return s->fd.uncast_dammit(); +} + +// save file descriptors for important stuff +constexpr int SOFT_LIMIT = FD_SETSIZE - 50; + +// socket timeout to establish a full connection in seconds +constexpr int CONNECT_TIMEOUT = 15; + + +void set_session(io::FD fd, std::unique_ptr<Session> sess); +Session *get_session(io::FD fd); +void reset_session(io::FD fd); +int get_fd_max(); + +class IncrFD +{ +public: + static + io::FD inced(io::FD v) + { + return io::FD::cast_dammit(v.uncast_dammit() + 1); + } +}; +IteratorPair<ValueIterator<io::FD, IncrFD>> iter_fds(); + + +/// open a socket, bind, and listen. Return an fd, or -1 if socket() fails, +/// but exit if bind() or listen() fails +Session *make_listen_port(uint16_t port); +/// Connect to an address, return a connected socket or -1 +// FIXME - this is IPv4 only! +Session *make_connection(IP4Address ip, uint16_t port); +/// free() the structure and close() the fd +void delete_session(Session *); +/// Make a the internal queues bigger +void realloc_fifo(Session *s, size_t rfifo_size, size_t wfifo_size); +/// Update all sockets that can be read/written from the queues +void do_sendrecv(interval_t next); +/// Call the parser function for every socket that has read data +void do_parsepacket(void); + +/// Change the default parser for newly connected clients +// typically called once per server, but individual clients may identify +// themselves as servers +void set_defaultparse(void(*defaultparse)(Session *)); + +template<class T> +uint8_t *pod_addressof_m(T& structure) +{ + static_assert(is_trivially_copyable<T>::value, "Can only byte-copy POD-ish structs"); + return &reinterpret_cast<uint8_t&>(structure); +} + +template<class T> +const uint8_t *pod_addressof_c(const T& structure) +{ + static_assert(is_trivially_copyable<T>::value, "Can only byte-copy POD-ish structs"); + return &reinterpret_cast<const uint8_t&>(structure); +} + + +/// Check how much can be read +inline +size_t RFIFOREST(Session *s) +{ + return s->rdata_size - s->rdata_pos; +} +/// Read from the queue +inline +const void *RFIFOP(Session *s, size_t pos) +{ + return &s->rdata[s->rdata_pos + pos]; +} +inline +uint8_t RFIFOB(Session *s, size_t pos) +{ + return *static_cast<const uint8_t *>(RFIFOP(s, pos)); +} +inline +uint16_t RFIFOW(Session *s, size_t pos) +{ + return *static_cast<const uint16_t *>(RFIFOP(s, pos)); +} +inline +uint32_t RFIFOL(Session *s, size_t pos) +{ + return *static_cast<const uint32_t *>(RFIFOP(s, pos)); +} +template<class T> +void RFIFO_STRUCT(Session *s, size_t pos, T& structure) +{ + really_memcpy(pod_addressof_m(structure), static_cast<const uint8_t *>(RFIFOP(s, pos)), sizeof(T)); +} +inline +IP4Address RFIFOIP(Session *s, size_t pos) +{ + IP4Address o; + RFIFO_STRUCT(s, pos, o); + return o; +} +template<uint8_t len> +inline +VString<len-1> RFIFO_STRING(Session *s, size_t pos) +{ + const char *const begin = static_cast<const char *>(RFIFOP(s, pos)); + const char *const end = begin + len-1; + const char *const mid = std::find(begin, end, '\0'); + return XString(begin, mid, nullptr); +} +inline +AString RFIFO_STRING(Session *s, size_t pos, size_t len) +{ + const char *const begin = static_cast<const char *>(RFIFOP(s, pos)); + const char *const end = begin + len; + const char *const mid = std::find(begin, end, '\0'); + return XString(begin, mid, nullptr); +} +inline +void RFIFO_BUF_CLONE(Session *s, uint8_t *buf, size_t len) +{ + really_memcpy(buf, static_cast<const uint8_t *>(RFIFOP(s, 0)), len); +} + +/// Done reading +void RFIFOSKIP(Session *s, size_t len); + +/// Read from an arbitrary buffer +inline +const void *RBUFP(const uint8_t *p, size_t pos) +{ + return p + pos; +} +inline +uint8_t RBUFB(const uint8_t *p, size_t pos) +{ + return *static_cast<const uint8_t *>(RBUFP(p, pos)); +} +inline +uint16_t RBUFW(const uint8_t *p, size_t pos) +{ + return *static_cast<const uint16_t *>(RBUFP(p, pos)); +} +inline +uint32_t RBUFL(const uint8_t *p, size_t pos) +{ + return *static_cast<const uint32_t *>(RBUFP(p, pos)); +} +template<class T> +void RBUF_STRUCT(const uint8_t *p, size_t pos, T& structure) +{ + really_memcpy(pod_addressof_m(structure), p + pos, sizeof(T)); +} +inline +IP4Address RBUFIP(const uint8_t *p, size_t pos) +{ + IP4Address o; + RBUF_STRUCT(p, pos, o); + return o; +} +template<uint8_t len> +inline +VString<len-1> RBUF_STRING(const uint8_t *p, size_t pos) +{ + const char *const begin = static_cast<const char *>(RBUFP(p, pos)); + const char *const end = begin + len-1; + const char *const mid = std::find(begin, end, '\0'); + return XString(begin, mid, nullptr); +} +inline +AString RBUF_STRING(const uint8_t *p, size_t pos, size_t len) +{ + const char *const begin = static_cast<const char *>(RBUFP(p, pos)); + const char *const end = begin + len; + const char *const mid = std::find(begin, end, '\0'); + return XString(begin, mid, nullptr); +} + + +/// Unused - check how much data can be written +// the existence of this seems scary +inline +size_t WFIFOSPACE(Session *s) +{ + return s->max_wdata - s->wdata_size; +} +/// Write to the queue +inline +void *WFIFOP(Session *s, size_t pos) +{ + return &s->wdata[s->wdata_size + pos]; +} +inline +uint8_t& WFIFOB(Session *s, size_t pos) +{ + return *static_cast<uint8_t *>(WFIFOP(s, pos)); +} +inline +uint16_t& WFIFOW(Session *s, size_t pos) +{ + return *static_cast<uint16_t *>(WFIFOP(s, pos)); +} +inline +uint32_t& WFIFOL(Session *s, size_t pos) +{ + return *static_cast<uint32_t *>(WFIFOP(s, pos)); +} +template<class T> +void WFIFO_STRUCT(Session *s, size_t pos, T& structure) +{ + really_memcpy(static_cast<uint8_t *>(WFIFOP(s, pos)), pod_addressof_c(structure), sizeof(T)); +} +inline +IP4Address& WFIFOIP(Session *s, size_t pos) +{ + static_assert(is_trivially_copyable<IP4Address>::value, "That was the whole point"); + return *static_cast<IP4Address *>(WFIFOP(s, pos)); +} +inline +void WFIFO_STRING(Session *s, size_t pos, XString str, size_t len) +{ + char *const begin = static_cast<char *>(WFIFOP(s, pos)); + char *const end = begin + len; + char *const mid = std::copy(str.begin(), str.end(), begin); + std::fill(mid, end, '\0'); +} +inline +void WFIFO_ZERO(Session *s, size_t pos, size_t len) +{ + uint8_t *b = static_cast<uint8_t *>(WFIFOP(s, pos)); + uint8_t *e = b + len; + std::fill(b, e, '\0'); +} +inline +void WFIFO_BUF_CLONE(Session *s, const uint8_t *buf, size_t len) +{ + really_memcpy(static_cast<uint8_t *>(WFIFOP(s, 0)), buf, len); +} + +/// Finish writing +void WFIFOSET(Session *s, size_t len); + +/// Write to an arbitrary buffer +inline +void *WBUFP(uint8_t *p, size_t pos) +{ + return p + pos; +} +inline +uint8_t& WBUFB(uint8_t *p, size_t pos) +{ + return *static_cast<uint8_t *>(WBUFP(p, pos)); +} +inline +uint16_t& WBUFW(uint8_t *p, size_t pos) +{ + return *static_cast<uint16_t *>(WBUFP(p, pos)); +} +inline +uint32_t& WBUFL(uint8_t *p, size_t pos) +{ + return *static_cast<uint32_t *>(WBUFP(p, pos)); +} +template<class T> +void WBUF_STRUCT(uint8_t *p, size_t pos, T& structure) +{ + really_memcpy(p + pos, pod_addressof_c(structure), sizeof(T)); +} +inline +IP4Address& WBUFIP(uint8_t *p, size_t pos) +{ + return *static_cast<IP4Address *>(WBUFP(p, pos)); +} +inline +void WBUF_STRING(uint8_t *p, size_t pos, XString s, size_t len) +{ + char *const begin = static_cast<char *>(WBUFP(p, pos)); + char *const end = begin + len; + char *const mid = std::copy(s.begin(), s.end(), begin); + std::fill(mid, end, '\0'); +} +inline +void WBUF_ZERO(uint8_t *p, size_t pos, size_t len) +{ + uint8_t *b = static_cast<uint8_t *>(WBUFP(p, pos)); + uint8_t *e = b + len; + std::fill(b, e, '\0'); +} + +inline +void RFIFO_WFIFO_CLONE(Session *rs, Session *ws, size_t len) +{ + really_memcpy(static_cast<uint8_t *>(WFIFOP(ws, 0)), + static_cast<const uint8_t *>(RFIFOP(rs, 0)), len); +} + +#endif // TMWA_MMO_SOCKET_HPP diff --git a/src/mmo/timer.cpp b/src/mmo/timer.cpp new file mode 100644 index 0000000..b5a2a5e --- /dev/null +++ b/src/mmo/timer.cpp @@ -0,0 +1,201 @@ +#include "timer.hpp" + +#include <sys/stat.h> +#include <sys/time.h> + +#include <cassert> +#include <cstring> + +#include <queue> + +#include "../strings/zstring.hpp" + +#include "../io/cxxstdio.hpp" + +#include "utils.hpp" + +#include "../poison.hpp" + +struct TimerData +{ + /// This will be reset on call, to avoid problems. + Timer *owner; + + /// When it will be triggered + tick_t tick; + /// What will be done + timer_func func; + /// Repeat rate - 0 for oneshot + interval_t interval; + + TimerData(Timer *o, tick_t t, timer_func f, interval_t i) + : owner(o) + , tick(t) + , func(std::move(f)) + , interval(i) + {} +}; + +struct TimerCompare +{ + /// implement "less than" + bool operator() (dumb_ptr<TimerData> l, dumb_ptr<TimerData> r) + { + // C++ provides a max-heap, but we want + // the smallest tick to be the head (a min-heap). + return l->tick > r->tick; + } +}; + +static +std::priority_queue<dumb_ptr<TimerData>, std::vector<dumb_ptr<TimerData>>, TimerCompare> timer_heap; + + +tick_t gettick_cache; + +tick_t milli_clock::now(void) noexcept +{ + struct timeval tval; + // BUG: This will cause strange behavior if the system clock is changed! + // it should be reimplemented in terms of clock_gettime(CLOCK_MONOTONIC, ) + gettimeofday(&tval, NULL); + return gettick_cache = tick_t(std::chrono::seconds(tval.tv_sec) + + std::chrono::duration_cast<std::chrono::milliseconds>( + std::chrono::microseconds(tval.tv_usec))); +} + +static +void do_nothing(TimerData *, tick_t) +{ +} + +void Timer::cancel() +{ + if (!td) + return; + + assert (this == td->owner); + td->owner = nullptr; + td->func = do_nothing; + td->interval = interval_t::zero(); + td = nullptr; +} + +void Timer::detach() +{ + assert (this == td->owner); + td->owner = nullptr; + td = nullptr; +} + +static +void push_timer_heap(dumb_ptr<TimerData> td) +{ + timer_heap.push(td); +} + +static +dumb_ptr<TimerData> top_timer_heap(void) +{ + if (timer_heap.empty()) + return dumb_ptr<TimerData>(); + return timer_heap.top(); +} + +static +void pop_timer_heap(void) +{ + timer_heap.pop(); +} + +Timer::Timer(tick_t tick, timer_func func, interval_t interval) +: td(dumb_ptr<TimerData>::make(this, tick, std::move(func), interval)) +{ + assert (interval >= interval_t::zero()); + + push_timer_heap(td); +} + +Timer::Timer(Timer&& t) +: td(t.td) +{ + t.td = nullptr; + if (td) + { + assert (td->owner == &t); + td->owner = this; + } +} + +Timer& Timer::operator = (Timer&& t) +{ + std::swap(td, t.td); + if (td) + { + assert (td->owner == &t); + td->owner = this; + } + if (t.td) + { + assert (t.td->owner == this); + t.td->owner = &t; + } + return *this; +} + +interval_t do_timer(tick_t tick) +{ + /// Number of milliseconds until it calls this again + // this says to wait 1 sec if all timers get popped + interval_t nextmin = std::chrono::seconds(1); + + while (dumb_ptr<TimerData> td = top_timer_heap()) + { + // while the heap is not empty and + if (td->tick > tick) + { + /// Return the time until the next timer needs to goes off + nextmin = td->tick - tick; + break; + } + pop_timer_heap(); + + // Prevent destroying the object we're in. + // Note: this would be surprising in an interval timer, + // but all interval timers do an immediate explicit detach(). + if (td->owner) + td->owner->detach(); + // If we are too far past the requested tick, call with + // the current tick instead to fix reregistration problems + if (td->tick + std::chrono::seconds(1) < tick) + td->func(td.operator->(), tick); + else + td->func(td.operator->(), td->tick); + + if (td->interval == interval_t::zero()) + { + td.delete_(); + continue; + } + if (td->tick + std::chrono::seconds(1) < tick) + td->tick = tick + td->interval; + else + td->tick += td->interval; + push_timer_heap(td); + } + + return std::max(nextmin, std::chrono::milliseconds(10)); +} + +tick_t file_modified(ZString name) +{ + struct stat buf; + if (stat(name.c_str(), &buf)) + return tick_t(); + return tick_t(std::chrono::seconds(buf.st_mtime)); +} + +bool has_timers() +{ + return !timer_heap.empty(); +} diff --git a/src/mmo/timer.hpp b/src/mmo/timer.hpp new file mode 100644 index 0000000..01b8623 --- /dev/null +++ b/src/mmo/timer.hpp @@ -0,0 +1,30 @@ +#ifndef TMWA_MMO_TIMER_HPP +#define TMWA_MMO_TIMER_HPP + +# include "timer.t.hpp" + +# include "../sanity.hpp" + +# include "../strings/fwd.hpp" + +// updated automatically when using milli_clock::now() +// which is done only by core.cpp +extern tick_t gettick_cache; + +inline +tick_t gettick(void) +{ + return gettick_cache; +} + +/// Do all timers scheduled before tick, and return the number of +/// milliseconds until the next timer happens +interval_t do_timer(tick_t tick); + +/// Stat a file, and return its modification time, truncated to seconds. +tick_t file_modified(ZString name); + +/// Check if there are any events at all scheduled. +bool has_timers(); + +#endif // TMWA_MMO_TIMER_HPP diff --git a/src/mmo/timer.t.hpp b/src/mmo/timer.t.hpp new file mode 100644 index 0000000..dcc88f8 --- /dev/null +++ b/src/mmo/timer.t.hpp @@ -0,0 +1,68 @@ +#ifndef TMWA_MMO_TIMER_T_HPP +#define TMWA_MMO_TIMER_T_HPP + +# include <chrono> +# include <functional> + +# include "dumb_ptr.hpp" + +struct TimerData; + +/// An implementation of the C++ "clock" concept, exposing +/// durations in milliseconds. +class milli_clock +{ +public: + typedef std::chrono::milliseconds duration; + typedef duration::rep rep; + typedef duration::period period; + typedef std::chrono::time_point<milli_clock, duration> time_point; + static const bool is_steady = true; // assumed - not necessarily true + + static time_point now() noexcept; +}; + +/// A point in time. +typedef milli_clock::time_point tick_t; +/// The difference between two points in time. +typedef milli_clock::duration interval_t; +/// (to get additional arguments, use std::bind or a lambda). +typedef std::function<void (TimerData *, tick_t)> timer_func; + +class Timer +{ + friend struct TimerData; + dumb_ptr<TimerData> td; + + Timer(const Timer&) = delete; + Timer& operator = (const Timer&) = delete; +public: + /// Don't own anything yet. + Timer() = default; + /// Schedule a timer for the given tick. + /// If you do not wish to keep track of it, call disconnect(). + /// Otherwise, you may cancel() or replace (operator =) it later. + /// + /// If the interval argument is given, the timer will reschedule + /// itself again forever. Otherwise, it will disconnect() itself + /// just BEFORE it is called. + Timer(tick_t tick, timer_func func, interval_t interval=interval_t::zero()); + + Timer(Timer&& t); + Timer& operator = (Timer&& t); + ~Timer() { cancel(); } + + /// Cancel the delivery of this timer's function, and make it falsy. + /// Implementation note: this doesn't actually remove it, just sets + /// the functor to do_nothing, and waits for the tick before removing. + void cancel(); + /// Make it falsy without cancelling the timer, + void detach(); + + /// Check if there is a timer connected. + explicit operator bool() { return bool(td); } + /// Check if there is no connected timer. + bool operator !() { return !td; } +}; + +#endif // TMWA_MMO_TIMER_T_HPP diff --git a/src/mmo/utils.cpp b/src/mmo/utils.cpp new file mode 100644 index 0000000..0dbf145 --- /dev/null +++ b/src/mmo/utils.cpp @@ -0,0 +1,101 @@ +#include "utils.hpp" + +#include <netinet/in.h> +#include <sys/time.h> + +#include <algorithm> + +#include "../strings/astring.hpp" +#include "../strings/zstring.hpp" +#include "../strings/xstring.hpp" + +#include "../io/cxxstdio.hpp" +#include "../io/write.hpp" + +#include "extract.hpp" + +#include "../poison.hpp" + +//--------------------------------------------------- +// E-mail check: return 0 (not correct) or 1 (valid). +//--------------------------------------------------- +bool e_mail_check(XString email) +{ + // athena limits + if (email.size() < 3 || email.size() > 39) + return 0; + + // part of RFC limits (official reference of e-mail description) + XString::iterator at = std::find(email.begin(), email.end(), '@'); + if (at == email.end()) + return 0; + XString username = email.xislice_h(at); + XString hostname = email.xislice_t(at + 1); + if (!username || !hostname) + return 0; + if (hostname.contains('@')) + return 0; + if (hostname.front() == '.' || hostname.back() == '.') + return 0; + if (hostname.contains_seq("..")) + return 0; + if (email.contains_any(" ;")) + return 0; + return email.is_print(); +} + +//------------------------------------------------- +// Return numerical value of a switch configuration +// on/off, english, français, deutsch, español +//------------------------------------------------- +int config_switch(ZString str) +{ + if (str == "true" || str == "on" || str == "yes" + || str == "oui" || str == "ja" + || str == "si") + return 1; + if (str == "false" || str == "off" || str == "no" + || str == "non" || str == "nein") + return 0; + + int rv; + if (extract(str, &rv)) + return rv; + FPRINTF(stderr, "Fatal: bad option value %s", str); + abort(); +} + +static_assert(sizeof(timestamp_seconds_buffer) == 20, "seconds buffer"); +static_assert(sizeof(timestamp_milliseconds_buffer) == 24, "millis buffer"); + +void stamp_time(timestamp_seconds_buffer& out, const TimeT *t) +{ + struct tm when = t ? *t : TimeT::now(); + char buf[20]; + strftime(buf, 20, "%Y-%m-%d %H:%M:%S", &when); + out = stringish<timestamp_seconds_buffer>(const_(buf)); +} +void stamp_time(timestamp_milliseconds_buffer& out) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + struct tm when = TimeT(tv.tv_sec); + char buf[24]; + strftime(buf, 20, "%Y-%m-%d %H:%M:%S", &when); + sprintf(buf + 19, ".%03d", static_cast<int>(tv.tv_usec / 1000)); + out = stringish<timestamp_milliseconds_buffer>(const_(buf)); +} + +void log_with_timestamp(io::WriteFile& out, XString line) +{ + if (!line) + { + out.put('\n'); + return; + } + timestamp_milliseconds_buffer tmpstr; + stamp_time(tmpstr); + out.really_put(tmpstr.data(), tmpstr.size()); + out.really_put(": ", 2); + out.put_line(line); +} diff --git a/src/mmo/utils.hpp b/src/mmo/utils.hpp new file mode 100644 index 0000000..5e9de26 --- /dev/null +++ b/src/mmo/utils.hpp @@ -0,0 +1,116 @@ +#ifndef TMWA_MMO_UTILS_HPP +#define TMWA_MMO_UTILS_HPP + +# include "../sanity.hpp" + +# include <cstdio> + +# include <type_traits> + +# include "../strings/fwd.hpp" +# include "../strings/vstring.hpp" + +# include "../generic/const_array.hpp" +# include "../generic/operators.hpp" + +# include "../io/fwd.hpp" + +template<class T> +struct is_trivially_copyable +: std::integral_constant<bool, + // come back when GCC actually implements the public traits properly + __has_trivial_copy(T) + && __has_trivial_assign(T) + && __has_trivial_destructor(T)> +{}; + +bool e_mail_check(XString email); +int config_switch (ZString str); + +template<class T> +void really_memzero_this(T *v) +{ + static_assert(is_trivially_copyable<T>::value, "only for mostly-pod types"); + static_assert(std::is_class<T>::value || std::is_union<T>::value, "Only for user-defined structures (for now)"); + memset(v, '\0', sizeof(*v)); +} +template<class T, size_t n> +void really_memzero_this(T (&)[n]) = delete; + +// Exists in place of time_t, to give it a predictable printf-format. +// (on x86 and amd64, time_t == long, but not on x32) +static_assert(sizeof(long long) >= sizeof(time_t), "long long >= time_t"); +struct TimeT : Comparable +{ + long long value; + + // conversion + TimeT(time_t t=0) : value(t) {} + TimeT(struct tm t) : value(timegm(&t)) {} + operator time_t() const { return value; } + operator struct tm() const { time_t v = value; return *gmtime(&v); } + + explicit operator bool() const { return value; } + bool operator !() const { return !value; } + + // prevent surprises + template<class T> + TimeT(T) = delete; + template<class T> + operator T() const = delete; + + static + TimeT now() + { + // poisoned, but this is still in header-land + return time(NULL); + } + + bool error() const + { + return value == -1; + } + bool okay() const + { + return !error(); + } +}; + +inline +long long convert_for_printf(TimeT t) +{ + return t.value; +} + +inline +long long& convert_for_scanf(TimeT& t) +{ + return t.value; +} + +struct timestamp_seconds_buffer : VString<19> {}; +struct timestamp_milliseconds_buffer : VString<23> {}; +void stamp_time(timestamp_seconds_buffer&, const TimeT *t=nullptr); +void stamp_time(timestamp_milliseconds_buffer&); + +void log_with_timestamp(io::WriteFile& out, XString line); + +// TODO VString? +# define TIMESTAMP_DUMMY "YYYY-MM-DD HH:MM:SS" +static_assert(sizeof(TIMESTAMP_DUMMY) == sizeof(timestamp_seconds_buffer), + "timestamp size"); +# define WITH_TIMESTAMP(str) str TIMESTAMP_DUMMY +// str: prefix: YYYY-MM-DD HH:MM:SS +// sizeof: 01234567890123456789012345678 +// str + sizeof: ^ +// -1: ^ +// there's probably a better way to do this now +# define REPLACE_TIMESTAMP(str, t) \ + stamp_time( \ + reinterpret_cast<timestamp_seconds_buffer *>( \ + str + sizeof(str) \ + )[-1], \ + &t \ + ) + +#endif // TMWA_MMO_UTILS_HPP diff --git a/src/mmo/version.cpp b/src/mmo/version.cpp new file mode 100644 index 0000000..dd18fe1 --- /dev/null +++ b/src/mmo/version.cpp @@ -0,0 +1,55 @@ +#include "version.hpp" + +#include "../conf/version.hpp" + +#include "../strings/xstring.hpp" + +#include "extract.hpp" + +#include "../poison.hpp" + +Version CURRENT_VERSION = +{ + VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, + VERSION_DEVEL, + + 0, 0, + VENDOR_VERSION, +}; +Version CURRENT_LOGIN_SERVER_VERSION = +{ + VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, + VERSION_DEVEL, + + 0, TMWA_SERVER_LOGIN, + VENDOR_VERSION, +}; +Version CURRENT_CHAR_SERVER_VERSION = +{ + VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, + VERSION_DEVEL, + + 0, TMWA_SERVER_CHAR | TMWA_SERVER_INTER, + VENDOR_VERSION, +}; +Version CURRENT_MAP_SERVER_VERSION = +{ + VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, + VERSION_DEVEL, + + 0, TMWA_SERVER_MAP, + VENDOR_VERSION, +}; + +#define S2(a) #a +#define S(a) S2(a) + +const char CURRENT_VERSION_STRING[] = "TMWA " + S(VERSION_MAJOR) "." S(VERSION_MINOR) "." S(VERSION_PATCH) + " dev" S(VERSION_DEVEL) " (" VENDOR " " S(VENDOR_VERSION) ")"; + +bool extract(XString str, Version *vers) +{ + *vers = {}; + return extract(str, record<'.'>(&vers->major, &vers->minor, &vers->patch)); +} diff --git a/src/mmo/version.hpp b/src/mmo/version.hpp new file mode 100644 index 0000000..420cbf9 --- /dev/null +++ b/src/mmo/version.hpp @@ -0,0 +1,69 @@ +#ifndef TMWA_MMO_VERSION_HPP +#define TMWA_MMO_VERSION_HPP + +# include <cstdint> + +# include "../strings/fwd.hpp" + +// TODO make these bitwise enums +# define TMWA_FLAG_REGISTRATION 0x01 + +# define TMWA_SERVER_LOGIN 0x01 +# define TMWA_SERVER_CHAR 0x02 +# define TMWA_SERVER_INTER 0x04 +# define TMWA_SERVER_MAP 0x08 + +struct Version +{ + uint8_t major; + uint8_t minor; // flavor1 + uint8_t patch; // flavor2 + uint8_t devel; // flavor3 + + uint8_t flags; + uint8_t which; + uint16_t vend; + // can't add vendor name yet +}; +static_assert(sizeof(Version) == 8, "this is sent over the network, can't change"); + +extern Version CURRENT_VERSION; + +extern Version CURRENT_LOGIN_SERVER_VERSION; +extern Version CURRENT_CHAR_SERVER_VERSION; +extern Version CURRENT_MAP_SERVER_VERSION; + +extern const char CURRENT_VERSION_STRING[]; + +bool extract(XString str, Version *vers); + +constexpr +bool operator < (Version l, Version r) +{ + return (l.major < r.major + || (l.major == r.major + && (l.minor < r.minor + || (l.minor == r.minor + && (l.patch < r.patch + || (l.patch == r.patch + && (l.devel < r.devel + || (l.devel == r.devel + && l.vend < r.vend)))))))); +} +constexpr +bool operator > (Version l, Version r) +{ + return r < l; +} +constexpr +bool operator <= (Version l, Version r) +{ + return !(r < l); +} +constexpr +bool operator >= (Version l, Version r) +{ + return !(l < r); +} + +#endif // TMWA_MMO_VERSION_HPP |