commit 6ae40f4243775d67866a474debcd8f9726eb2a64
Author: Henry Wilson <henry@henryandlizzy.uk>
Date: Mon, 28 Aug 2023 13:51:36 +0000
Complete tasks 0-4
Diffstat:
A | .gitignore | | | 2 | ++ |
A | fd.hpp | | | 75 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | inet.hpp | | | 47 | +++++++++++++++++++++++++++++++++++++++++++++++ |
A | makefile | | | 17 | +++++++++++++++++ |
A | protohack-0.cpp | | | 62 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | protohack-1.cpp | | | 399 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | protohack-2.cpp | | | 139 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | protohack-3.cpp | | | 149 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | protohack-4.cpp | | | 58 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
9 files changed, 948 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,2 @@
+protohack-[0-9]
+protohack-[0-9][0-9]
diff --git a/fd.hpp b/fd.hpp
@@ -0,0 +1,75 @@
+#pragma once
+#include <unistd.h>
+
+#include <string>
+
+struct descriptor
+{
+ descriptor()
+ : fd(-1)
+ {}
+
+ explicit descriptor(int d)
+ : fd(d)
+ {}
+
+ ~descriptor()
+ {
+ close();
+ }
+
+ descriptor(descriptor&& old)
+ : fd(old.fd)
+ {
+ old.fd = -1;
+ }
+
+ descriptor& operator =(descriptor&& old)
+ {
+ if (this != &old)
+ {
+ close();
+ fd = old.fd;
+ old.fd = -1;
+ }
+ return *this;
+ }
+
+ descriptor(descriptor const&) = delete;
+ descriptor& operator =(descriptor const&) = delete;
+
+ void close()
+ {
+ if (*this)
+ ::close(fd);
+ fd = -1;
+ }
+
+ explicit operator bool() const
+ {
+ return fd != -1;
+ }
+
+ operator int() const
+ {
+ return fd;
+ }
+
+private:
+ int fd;
+};
+
+int write(descriptor& d, std::string_view s)
+{
+ return ::write(d, s.data(), s.size());
+}
+
+std::string read(descriptor& d)
+{
+ char buf[256];
+ ssize_t n = ::read(d, buf, sizeof buf);
+ if (n > 0)
+ return {buf, (size_t)n};
+ else
+ return {};
+}
diff --git a/inet.hpp b/inet.hpp
@@ -0,0 +1,47 @@
+#pragma once
+
+#include "fd.hpp"
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <err.h>
+
+#include <iostream>
+
+namespace inet {
+
+descriptor listen(int type)
+{
+ struct sockaddr_in addr {sizeof addr, AF_INET, htons(1337), INADDR_ANY};
+ int reuseaddr = 1;
+
+ descriptor incoming{::socket(AF_INET, type, 0)};
+ if (!incoming)
+ err(1, "socket");
+
+ if (::setsockopt(incoming, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof reuseaddr))
+ err(1, "setsockopt");
+ if (::bind(incoming, (sockaddr const*)&addr, sizeof addr))
+ err(1, "bind");
+ if (::listen(incoming, 10))
+ err(1, "listen");
+
+ return incoming;
+}
+
+descriptor accept(descriptor& incoming)
+{
+ struct sockaddr_in addr;
+ socklen_t addr_size = sizeof addr;
+ descriptor conn{::accept(incoming, (sockaddr*)&addr, &addr_size)};
+
+ if (!conn)
+ err(1, "accept");
+
+ std::cout << "Connection " << conn << " from " << inet_ntoa(addr.sin_addr) << std::endl;
+ return conn;
+}
+
+}
diff --git a/makefile b/makefile
@@ -0,0 +1,17 @@
+CXXFLAGS := -std=c++20 -fcolor-diagnostics
+
+ALL := protohack-0 \
+ protohack-1 \
+ protohack-2 \
+ protohack-3 \
+ protohack-4 \
+
+all: $(ALL)
+
+protohack-1: protohack-1.cpp
+ ${LINK.cc} -o protohack-1 protohack-1.cpp -lcrypto
+
+clean:
+ rm -f $(ALL)
+
+.PHONY: all clean
diff --git a/protohack-0.cpp b/protohack-0.cpp
@@ -0,0 +1,62 @@
+#include "inet.hpp"
+
+#include <poll.h>
+
+#include <iostream>
+#include <sstream>
+#include <span>
+#include <map>
+#include <vector>
+
+struct client
+{
+ descriptor socket;
+ std::string buffer;
+};
+
+int main()
+{
+ std::map<int, client> clients;
+ std::vector<pollfd> awaitables;
+ auto incoming = inet::listen(SOCK_STREAM);
+
+ for (;;)
+ {
+ awaitables.clear();
+ awaitables.push_back(pollfd{incoming, POLLIN, 0});
+
+ for (auto const& c : clients)
+ awaitables.push_back(pollfd{c.second.socket, POLLIN, 0});
+
+ if (::poll(awaitables.data(), awaitables.size(), INFTIM) == -1)
+ err(1, "poll");
+
+ if (awaitables.front().revents & POLLIN) // incoming
+ {
+ auto conn = inet::accept(incoming);
+ clients[conn] = {std::move(conn), {}};
+ }
+
+ for (auto& ev : std::span{awaitables}.subspan(1))
+ {
+ if (~ev.revents & POLLIN)
+ continue;
+
+ char buf[0x1000];
+ int n = ::read(ev.fd, buf, sizeof buf);
+ if (not n)
+ {
+ std::cout << "* " << ev.fd << " disconnected\n";
+ clients.erase(ev.fd);
+ continue;
+ }
+ if (n < 0 or n > sizeof buf)
+ err(1, "read");
+
+ std::cout << "* " << ev.fd << ": " << n << " bytes\n";
+ ::write(1, buf, n);
+ //if (n != ::write(ev.fd, buf, n))
+ // err(1, "write");
+ }
+ }
+}
diff --git a/protohack-1.cpp b/protohack-1.cpp
@@ -0,0 +1,399 @@
+#include "inet.hpp"
+#include "fd.hpp"
+
+#include <poll.h>
+
+#include <iostream>
+#include <cmath>
+#include <map>
+#include <vector>
+#include <optional>
+#include <openssl/bn.h>
+
+using namespace std;
+
+struct bignum
+{
+ bignum()
+ : p(BN_new(), BN_free)
+ {
+ BN_init(*this);
+ }
+
+ bignum(BIGNUM* bn)
+ : p(bn, BN_free)
+ {}
+
+ bignum(unsigned long w)
+ : bignum()
+ {
+ BN_set_word(*this, w);
+ }
+
+ bignum(bignum&&) = default;
+ bignum& operator=(bignum&&) = default;
+
+ bignum(bignum const& old)
+ : bignum{BN_dup(old)}
+ {}
+ bignum& operator=(bignum const& old)
+ {
+ if (p)
+ BN_copy(*this, old);
+ else
+ p.reset(BN_dup(old));
+
+ return *this;
+ }
+
+ char const* to_str() const
+ {
+ return BN_bn2dec(*this);
+ }
+
+ bool is_prime() const
+ {
+ return BN_is_prime_fasttest_ex(*this, INT_MAX, nullptr, 1, nullptr);
+ }
+
+ bignum operator +(bignum const& rhs)
+ {
+ bignum r;
+ BN_add(r, *this, rhs);
+ return r;
+ }
+ bignum operator -(bignum const& rhs)
+ {
+ bignum r;
+ BN_sub(r, *this, rhs);
+ return r;
+ }
+
+private:
+ unique_ptr<BIGNUM, void(*)(BIGNUM*)> p;
+
+ operator BIGNUM const*() const
+ {
+ return p.get();
+ }
+ operator BIGNUM*()
+ {
+ return p.get();
+ }
+};
+
+ostream& operator <<(ostream& out, bignum const& rhs)
+{
+ return out << rhs.to_str();
+}
+
+struct json_object
+{
+ std::map<string_view, string_view> nv_pairs;
+ std::map<string_view, bignum> num_pairs;
+};
+
+template <typename T>
+struct parse_t
+{
+ T val;
+ string_view remaining;
+};
+
+std::optional<string_view> parse_char(string_view in, char c)
+{
+ if (in.empty())
+ return {};
+ if (in.front() != c)
+ return {};
+ return in.substr(1);
+}
+
+std::optional<string_view> parse_str(string_view in, string_view str)
+{
+ if (not in.starts_with(str))
+ return {};
+ return in.substr(str.size());
+}
+
+string_view parse_whitespace(string_view in)
+{
+ auto end = in.find_first_not_of(" \t\r\n");
+ return end != string::npos ? in.substr(end) : ""sv;
+}
+
+std::optional<parse_t<bool>> parse_bool(string_view in)
+{
+ if (auto next = parse_str(in, "true"))
+ return parse_t<bool>{true, *next};
+ if (auto next = parse_str(in, "false"))
+ return parse_t<bool>{false, *next};
+ return {};
+}
+
+size_t find_end_quote(string_view str)
+{
+ size_t start = 0;
+ for (;;)
+ {
+ auto pos = str.find_first_of("\"\\", start);
+ if ((pos == string::npos) or (str[pos] == '"'))
+ return pos;
+ start = pos + 2;
+ if (start > str.size())
+ return string::npos;
+ }
+}
+
+std::optional<parse_t<string_view>> parse_string(string_view in)
+{
+ if (auto next = parse_char(in, '"'))
+ in = *next;
+ else
+ return {};
+
+ if (in.empty())
+ return {};
+
+ auto end = find_end_quote(in);
+ auto str = in.substr(0, end);
+ in = end != string::npos ? in.substr(end) : ""sv;
+
+ if (auto next = parse_char(in, '"'))
+ in = *next;
+ else
+ return {};
+
+ return parse_t<string_view>{str, in};
+}
+
+std::optional<parse_t<bignum>> parse_bignum(string_view in)
+{
+ BIGNUM* p = nullptr;
+ auto end = in.find_first_not_of("-0123456789");
+ string str{in.substr(0, end)};
+ int r = BN_dec2bn(&p, str.c_str());
+ if (r == 0)
+ return {};
+ if (r < 0)
+ __builtin_trap();
+
+ in = end != string::npos ? in.substr(end) : ""sv;
+
+ if (auto next = parse_char(in, '.'))
+ {
+ in = *next;
+ }
+ else
+ return parse_t<bignum>{bignum{p}, in};
+
+ end = in.find_first_not_of("0123456789");
+ in = end != string::npos ? in.substr(end) : ""sv;
+ return parse_t<bignum>{bignum{0UL}, in};
+}
+
+std::optional<parse_t<json_object>> parse_object(string_view in)
+{
+ json_object o;
+
+ if (auto next = parse_char(in, '{'))
+ in = *next;
+ else
+ return {};
+
+ in = parse_whitespace(in);
+
+ if (auto next = parse_char(in, '}'))
+ return parse_t<json_object>{std::move(o), *next};
+
+ for (;;)
+ {
+ auto name = parse_string(in);
+ if (not name)
+ return {};
+
+ in = parse_whitespace(name->remaining);
+
+ if (auto next = parse_char(in, ':'))
+ in = parse_whitespace(*next);
+ else
+ return {};
+
+ if (auto value = parse_string(in))
+ {
+ auto& val = o.nv_pairs[name->val];
+ val = value->val;
+ in = value->remaining;
+ }
+ else if (auto value = parse_bignum(in))
+ {
+ auto& val = o.num_pairs[name->val];
+ val = value->val;
+ in = value->remaining;
+ }
+ else if (auto value = parse_bool(in))
+ {
+ //auto& val = o.bool_pairs[name->val];
+ //val = value->val;
+ in = value->remaining;
+ }
+ else if (auto value = parse_object(in))
+ {
+ //auto& val = o.obj_pairs[name->val];
+ //val = value->val;
+ in = value->remaining;
+ }
+ else
+ return {};
+
+ if (auto next = parse_char(in, ','))
+ in = parse_whitespace(*next);
+ else
+ break;
+ }
+ if (auto next = parse_char(in, '}'))
+ return parse_t<json_object>{std::move(o), *next};
+ else
+ return {};
+}
+
+ostream& operator <<(ostream& out, json_object const& rhs)
+{
+ out << '{';
+ for (auto const& nv : rhs.nv_pairs)
+ out << '"' << nv.first << "\": \"" << nv.second << "\",";
+ for (auto const& nv : rhs.num_pairs)
+ out << '"' << nv.first << "\": " << nv.second << ',';
+ return out << '}';
+}
+
+ostream& operator <<(ostream& out, std::optional<parse_t<json_object>> const& rhs)
+{
+ if (not rhs)
+ return out << "<failed parse>";
+ return out << rhs->val << " <" << rhs->remaining << ">";
+}
+
+struct client
+{
+ descriptor d;
+ std::string buf;
+
+ bool do_read()
+ {
+ string data = read(d);
+ if (data.empty())
+ return false;
+
+ buf.append(std::move(data));
+ return true;
+ }
+ optional<string> getline()
+ {
+ if (buf.empty())
+ return {};
+ auto n = buf.find('\n');
+ if (n == string::npos)
+ return {};
+ string line = buf.substr(0, n);
+ buf.erase(0, n+1);
+ return line;
+ }
+};
+
+std::optional<bignum> parse_json_request(string_view line)
+{
+ auto obj = parse_object(line);
+ if (not obj)
+ {
+ cout << "request is invalid JSON\n";
+ return {};
+ }
+ //std::cout << obj << std::flush;
+
+ auto srch = obj->val.nv_pairs.find("method");
+ if ((srch == obj->val.nv_pairs.end()) or (srch->second != "isPrime"))
+ {
+ cout << "'method' != isPrime or is missing\n";
+ return {};
+ }
+ auto srch2 = obj->val.num_pairs.find("number");
+ if (srch2 == obj->val.num_pairs.end())
+ {
+ cout << "'number' is not number type or is missing\n";
+ return {};
+ }
+ return srch2->second;
+}
+
+bool client_ready(client& c)
+{
+ if (not c.do_read())
+ {
+ cout << "* " << c.d << " disconnected\n";
+ return false;
+ }
+
+ while (auto line = c.getline())
+ {
+ if (auto number = parse_json_request(*line))
+ {
+ bool prime = number->is_prime();
+ std::cout << *number << ": " << prime << std::endl;
+
+ if (prime)
+ write(c.d, "{\"method\":\"isPrime\",\"prime\":true}\n"sv);
+ else
+ write(c.d, "{\"method\":\"isPrime\",\"prime\":false}\n"sv);
+ }
+ else
+ {
+ write(c.d, "xxx\n"sv);
+ return false;
+ }
+ }
+ return true;
+}
+
+int main()
+{
+ std::map<int, client> clients;
+ auto incoming = inet::listen(SOCK_STREAM);
+
+ std::cout << std::boolalpha << "Awaiting clients\n";
+
+ for (;;)
+ {
+ std::vector<pollfd> awaitables;
+ awaitables.push_back({incoming, POLLIN, 0});
+ for (auto const& c : clients)
+ awaitables.push_back({c.second.d, POLLIN, 0});
+
+ ::poll(awaitables.data(), awaitables.size(), INFTIM);
+
+ for (auto& ev : awaitables)
+ {
+ if (~ev.revents & POLLIN)
+ continue;
+
+ if (ev.fd == incoming)
+ {
+ auto conn = inet::accept(incoming);
+
+ auto& x = clients[conn];
+ x.d = move(conn);
+ continue;
+ }
+
+ auto it = clients.find(ev.fd);
+ if (it == clients.end())
+ {
+ cerr << "Unknown fd " << ev.fd << endl;
+ return 1;
+ }
+
+ if (not client_ready(it->second))
+ clients.erase(it);
+ }
+ }
+}
diff --git a/protohack-2.cpp b/protohack-2.cpp
@@ -0,0 +1,139 @@
+#include "inet.hpp"
+
+#include <poll.h>
+
+#include <algorithm>
+#include <iostream>
+#include <map>
+#include <vector>
+
+using namespace std;
+
+struct listings : map<int32_t, int32_t>
+{
+ int32_t average(int32_t min, int32_t max)
+ {
+ if (empty())
+ return 0;
+ if (min > max)
+ return 0;
+
+ auto it = lower_bound(min);
+
+ if (it == end())
+ return 0;
+
+ int64_t accum = 0;
+ int32_t n = 0;
+
+ while (it != end() and it->first <= max)
+ {
+ ++n;
+ accum += it++->second;
+ }
+ if (n == 0)
+ return 0;
+
+ return accum / n;
+ }
+};
+
+struct client
+{
+ descriptor d;
+ listings l;
+ std::vector<char> buf;
+};
+
+std::map<int, client> clients;
+
+uint32_t decode(void const* in)
+{
+ auto p = (unsigned char const*)in;
+ return p[3] | p[2] << 8U | p[1] << 16U | p[0] << 24U;
+}
+
+void encode(uint32_t n, void* out)
+{
+ unsigned char* p = (unsigned char*)out;
+ p[0] = (n & 0xFF000000U) >> 24U;
+ p[1] = (n & 0x00FF0000U) >> 16U;
+ p[2] = (n & 0x0000FF00U) >> 8U;
+ p[3] = (n & 0x000000FFU);
+}
+
+int main()
+{
+ auto incoming = inet::listen(SOCK_STREAM);
+
+ for (;;)
+ {
+ std::vector<pollfd> awaitables;
+ awaitables.push_back(pollfd{incoming, POLLIN, 0});
+ for (auto const& c : clients)
+ awaitables.push_back(pollfd{c.second.d, POLLIN, 0});
+
+ ::poll(awaitables.data(), awaitables.size(), INFTIM);
+
+ for (auto& ev : awaitables)
+ {
+ if (~ev.revents & POLLIN)
+ continue;
+
+ if (ev.fd == incoming)
+ {
+ auto conn = inet::accept(incoming);
+ clients[conn].d = std::move(conn);
+ continue;
+ }
+
+ if (auto it = clients.find(ev.fd); it != clients.end())
+ {
+ char buf;
+ int rc = ::read(ev.fd, &buf, sizeof buf);
+ if (not rc)
+ {
+ std::cout << "* " << ev.fd << " disconnected\n";
+ clients.erase(it);
+ continue;
+ }
+ if (rc < 0)
+ err(1, "read");
+
+ auto& buffer = it->second.buf;
+ buffer.push_back(buf);
+ if (buffer.size() < 9)
+ continue;
+
+ auto& l = it->second.l;
+
+ int32_t a = decode(buffer.data() + 1);
+ int32_t b = decode(buffer.data() + 5);
+ char cmd = buffer.front();
+ buffer.erase(buffer.begin(), buffer.begin() + 9);
+ switch (cmd)
+ {
+ case 'I':
+ if (l.count(a))
+ return 1;
+ l[a] = b;
+ continue;
+
+ case 'Q':
+ a = l.average(a, b);
+ char txbuf[4];
+ encode(a, txbuf);
+ ::write(ev.fd, txbuf, 4);
+ continue;
+ }
+
+ std::cout << "* " << ev.fd << " bad command" << cmd << "\n";
+ clients.erase(it);
+ continue;
+ }
+
+ std::cerr << "Unknown fd " << ev.fd << std::endl;
+ return 1;
+ }
+ }
+}
diff --git a/protohack-3.cpp b/protohack-3.cpp
@@ -0,0 +1,149 @@
+#include "inet.hpp"
+
+#include <poll.h>
+
+#include <iostream>
+#include <sstream>
+#include <map>
+#include <vector>
+
+#include "fd.hpp"
+
+using namespace std;
+
+struct client
+{
+ descriptor d;
+ std::string name, buf;
+ stringstream stream;
+
+ bool do_read()
+ {
+ string data = read(d);
+ if (data.empty())
+ return false;
+
+ buf.append(data);
+ return true;
+ }
+ string getline()
+ {
+ auto s = buf.find_first_not_of('\n');
+ auto n = buf.find('\n', s);
+ if (n == string::npos)
+ return {};
+
+ string line = buf.substr(s, n);
+ buf.erase(0, s+n+1);
+ return line;
+ }
+};
+
+std::map<int, client> unnamed, clients;
+
+void broadcast(string msg, int exclude)
+{
+ msg.push_back('\n');
+ std::cout << msg;
+ for (auto& c : clients)
+ if (c.second.d != exclude)
+ write(c.second.d, msg);
+}
+
+int main()
+{
+ auto incoming = inet::listen(SOCK_STREAM);
+
+ for (;;)
+ {
+ std::vector<pollfd> awaitables;
+ awaitables.push_back({incoming, POLLIN, 0});
+ for (auto const& c : unnamed)
+ awaitables.push_back({c.second.d, POLLIN, 0});
+ for (auto const& c : clients)
+ awaitables.push_back({c.second.d, POLLIN, 0});
+
+ ::poll(awaitables.data(), awaitables.size(), INFTIM);
+
+ for (auto& ev : awaitables)
+ {
+ if (~ev.revents & POLLIN)
+ continue;
+
+ if (ev.fd == incoming)
+ {
+ auto conn = inet::accept(incoming);
+
+ write(conn, "Welcome to H&L-chat, what is your name?\n");
+ auto& x = unnamed[conn];
+ x.d = move(conn);
+ continue;
+ }
+
+ auto it = unnamed.find(ev.fd);
+ if (it != unnamed.end())
+ {
+ auto& c = it->second;
+ // c.stream << read(c.d);
+ if (not c.do_read())
+ {
+ unnamed.erase(it);
+ continue;
+ }
+ string line = c.getline();
+ if (line.find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz") != string::npos)
+ {
+ unnamed.erase(it);
+ continue;
+ }
+ // getline(c.stream, line);
+ {
+ ostringstream msg;
+ msg << "* " << line << " joined.";
+ broadcast(msg.str(), c.d);
+ c.name = line;
+ }
+ {
+ ostringstream msg;
+ msg << "* " << clients.size() << " users in chat:";
+ for (auto const& o : clients)
+ msg << ' ' << o.second.name;
+ msg << '\n';
+ write(c.d, msg.str());
+ }
+ clients.insert(unnamed.extract(it));
+ continue;
+ }
+
+ it = clients.find(ev.fd);
+ if (it != clients.end())
+ {
+ auto& c = it->second;
+ // string line = read(c.d);
+ // if (line.empty())
+ if (not c.do_read())
+ {
+ ostringstream msg;
+ msg << "* " << c.name << " has left the room";
+ broadcast(msg.str(), c.d);
+ clients.erase(it);
+ continue;
+ }
+
+ // c.stream << line;
+
+ // getline(c.stream, line);
+ string line = c.getline();
+ if (line.empty())
+ continue;
+ ostringstream msg;
+ msg << "[" << c.name << "] " << line;
+ broadcast(msg.str(), c.d);
+ continue;
+ }
+
+ cerr << "Unknown fd " << ev.fd << endl;
+ return 1;
+ }
+ }
+}
diff --git a/protohack-4.cpp b/protohack-4.cpp
@@ -0,0 +1,58 @@
+#include <poll.h>
+
+#include <iostream>
+#include <sstream>
+#include <map>
+#include <vector>
+
+#include "inet.hpp"
+
+using namespace std;
+
+std::map<string, string> database = {{"version", "m3henry-protohack-4"}};
+
+string recv_udp(descriptor& d, sockaddr_in& a)
+{
+ char buf[1024];
+ socklen_t addr_size = sizeof a;
+
+ ssize_t n = ::recvfrom(d, buf, sizeof buf, 0, (sockaddr*)&a, &addr_size);
+ if (n > 0)
+ return {buf, (size_t)n};
+ else
+ return {};
+}
+
+int send_udp(descriptor& d, sockaddr_in const& a, string const& s)
+{
+ return ::sendto(d, s.data(), s.size(), 0, (sockaddr const*)&a, sizeof a);
+}
+
+int main()
+{
+ auto incoming = inet::listen(SOCK_DGRAM);
+
+ for (;;)
+ {
+ struct sockaddr_in addr;
+ socklen_t addr_size = sizeof addr;
+ char buf[1024];
+
+ auto len = ::recvfrom(incoming, buf, sizeof buf, 0, (sockaddr*)&addr, &addr_size);
+ if (len < 0)
+ return 1;
+ string msg{buf, (size_t)len};
+ string ip = inet_ntoa(addr.sin_addr);
+ cout << ip << ":" << addr.sin_port << " '" << msg << "'\n";
+
+ auto split = msg.find('=');
+ if (split == string::npos)
+ {
+ send_udp(incoming, addr, msg + '=' + database[msg]);
+ continue;
+ }
+ auto key = msg.substr(0, split);
+ if (key != "version")
+ database[key] = msg.substr(split+1);
+ }
+}