aboutsummaryrefslogtreecommitdiff
path: root/include
diff options
context:
space:
mode:
authorDeterminant <[email protected]>2018-06-26 12:58:54 -0400
committerDeterminant <[email protected]>2018-06-26 12:58:54 -0400
commit5c3b39340d365f5ff37a79424956591e87b44816 (patch)
tree45fc59c19ed95c44bacbbe59396d8bc1b25872dd /include
init
Diffstat (limited to 'include')
-rw-r--r--include/salticidae/conn.h235
-rw-r--r--include/salticidae/crypto.h78
-rw-r--r--include/salticidae/msg.h253
-rw-r--r--include/salticidae/netaddr.h115
-rw-r--r--include/salticidae/network.h552
-rw-r--r--include/salticidae/ref.h202
-rw-r--r--include/salticidae/stream.h274
-rw-r--r--include/salticidae/type.h67
-rw-r--r--include/salticidae/util.h292
9 files changed, 2068 insertions, 0 deletions
diff --git a/include/salticidae/conn.h b/include/salticidae/conn.h
new file mode 100644
index 0000000..40facc9
--- /dev/null
+++ b/include/salticidae/conn.h
@@ -0,0 +1,235 @@
+/**
+ * Copyright (c) 2018 Cornell University.
+ *
+ * Author: Ted Yin <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _SALTICIDAE_CONN_H
+#define _SALTICIDAE_CONN_H
+
+#include <cassert>
+#include <cstdint>
+#include <event2/event.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+
+#include <string>
+#include <unordered_map>
+#include <list>
+#include <algorithm>
+#include <exception>
+
+#include "salticidae/type.h"
+#include "salticidae/ref.h"
+#include "salticidae/util.h"
+#include "salticidae/netaddr.h"
+#include "salticidae/msg.h"
+
+const int MAX_LISTEN_BACKLOG = 10;
+const size_t BUFF_SEG_SIZE = 4096;
+const size_t MAX_MSG_HANDLER = 64;
+const double TRY_CONN_DELAY = 2;
+const double CONN_SERVER_TIMEOUT = 2;
+
+namespace salticidae {
+
+inline double gen_rand_timeout(double base_timeout) {
+ return base_timeout + rand() / (double)RAND_MAX * 0.5 * base_timeout;
+}
+
+class RingBuffer {
+ struct buffer_entry_t {
+ bytearray_t data;
+ bytearray_t::iterator offset;
+ buffer_entry_t(bytearray_t &&_data): data(std::move(_data)) {
+ offset = data.begin();
+ }
+
+ buffer_entry_t(buffer_entry_t &&other) {
+ size_t _offset = other.offset - other.data.begin();
+ data = std::move(other.data);
+ offset = data.begin() + _offset;
+ }
+
+ buffer_entry_t(const buffer_entry_t &other): data(other.data) {
+ offset = data.begin() + (other.offset - other.data.begin());
+ }
+
+ size_t length() const { return data.end() - offset; }
+ };
+ std::list<buffer_entry_t> ring;
+ size_t _size;
+
+ public:
+ RingBuffer(): _size(0) {}
+ ~RingBuffer() { clear(); }
+ RingBuffer &operator=(const RingBuffer &other) = delete;
+ RingBuffer(const RingBuffer &other) = delete;
+ RingBuffer &operator=(RingBuffer &&other) {
+ ring = std::move(other.ring);
+ _size = other._size;
+ other._size = 0;
+ return *this;
+ }
+
+ void push(bytearray_t &&data) {
+ _size += data.size();
+ ring.push_back(buffer_entry_t(std::move(data)));
+ }
+
+ bytearray_t pop(size_t len) {
+ bytearray_t res;
+ auto i = ring.begin();
+ while (len && i != ring.end())
+ {
+ size_t copy_len = std::min(i->length(), len);
+ res.insert(res.end(), i->offset, i->offset + copy_len);
+ i->offset += copy_len;
+ len -= copy_len;
+ if (i->offset == i->data.end())
+ i++;
+ }
+ ring.erase(ring.begin(), i);
+ _size -= res.size();
+ return std::move(res);
+ }
+
+ size_t size() const { return _size; }
+
+ void clear() {
+ ring.clear();
+ _size = 0;
+ }
+};
+
+class ConnPoolError: public SalticidaeError {
+ using SalticidaeError::SalticidaeError;
+};
+
+/** The connection pool. */
+class ConnPool {
+ public:
+ class Conn;
+ using conn_t = RcObj<Conn>;
+ /** The abstraction for a bi-directional connection. */
+ class Conn {
+ public:
+ enum ConnMode {
+ ACTIVE, /**< the connection is established by connect() */
+ PASSIVE, /**< the connection is established by accept() */
+ };
+
+ private:
+ conn_t self_ref;
+ int fd;
+ ConnPool *cpool;
+ ConnMode mode;
+ NetAddr addr;
+
+ RingBuffer send_buffer;
+ RingBuffer recv_buffer;
+
+ Event ev_read;
+ Event ev_write;
+ Event ev_connect;
+ /** does not need to wait if true */
+ bool ready_send;
+
+ void recv_data(evutil_socket_t, short);
+ void send_data(evutil_socket_t, short);
+ void conn_server(evutil_socket_t, short);
+ void try_conn(evutil_socket_t, short);
+
+ public:
+ friend ConnPool;
+ Conn(): self_ref(this) {}
+
+ virtual ~Conn() {
+ SALTICIDAE_LOG_INFO("destroyed connection %s", std::string(*this).c_str());
+ }
+
+ conn_t self() { return self_ref; }
+ operator std::string() const;
+ int get_fd() const { return fd; }
+ const NetAddr &get_addr() const { return addr; }
+ ConnMode get_mode() const { return mode; }
+ RingBuffer &read() { return recv_buffer; }
+
+ void write(bytearray_t &&data) {
+ send_buffer.push(std::move(data));
+ if (ready_send)
+ send_data(fd, EV_WRITE);
+ }
+
+ void move_send_buffer(conn_t other) {
+ send_buffer = std::move(other->send_buffer);
+ }
+
+ void terminate();
+
+ protected:
+ /** close the connection and free all on-going or planned events. */
+ virtual void close() {
+ ev_read.clear();
+ ev_write.clear();
+ ev_connect.clear();
+ ::close(fd);
+ fd = -1;
+ }
+
+ virtual void on_read() = 0;
+ virtual void on_setup() = 0;
+ virtual void on_teardown() = 0;
+ };
+
+ private:
+ std::unordered_map<int, conn_t> pool;
+ int listen_fd;
+ Event ev_listen;
+
+ void accept_client(evutil_socket_t, short);
+ conn_t add_conn(conn_t conn);
+
+ protected:
+ struct event_base *eb;
+ virtual conn_t create_conn() = 0;
+
+ public:
+ friend Conn;
+ ConnPool(struct event_base *eb): eb(eb) {}
+
+ ~ConnPool() {
+ for (auto it: pool)
+ {
+ conn_t conn = it.second;
+ conn->close();
+ }
+ }
+
+ /** create an active mode connection to addr */
+ conn_t create_conn(const NetAddr &addr);
+ /** setup and start listening */
+ void init(NetAddr listen_addr);
+};
+
+}
+
+#endif
diff --git a/include/salticidae/crypto.h b/include/salticidae/crypto.h
new file mode 100644
index 0000000..c329c63
--- /dev/null
+++ b/include/salticidae/crypto.h
@@ -0,0 +1,78 @@
+/**
+ * Copyright (c) 2018 Cornell University.
+ *
+ * Author: Ted Yin <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _SALTICIDAE_CRYPTO_H
+#define _SALTICIDAE_CRYPTO_H
+
+#include "salticidae/type.h"
+#include <openssl/sha.h>
+
+namespace salticidae {
+
+class SHA256 {
+ SHA256_CTX *ctx;
+
+ public:
+ SHA256(): ctx(new SHA256_CTX()) { reset(); }
+ ~SHA256() { delete ctx; }
+
+ void reset() {
+ if (!SHA256_Init(ctx))
+ throw std::runtime_error("openssl SHA256 init error");
+ }
+
+ template<typename T>
+ void update(const T &data) {
+ update(reinterpret_cast<const uint8_t *>(&*data.begin()), data.size());
+ }
+
+ void update(const bytearray_t::const_iterator &it, size_t length) {
+ update(&*it, length);
+ }
+
+ void update(const uint8_t *ptr, size_t length) {
+ if (!SHA256_Update(ctx, ptr, length))
+ throw std::runtime_error("openssl SHA256 update error");
+ }
+
+ void _digest(bytearray_t &md) {
+ if (!SHA256_Final(&*md.begin(), ctx))
+ throw std::runtime_error("openssl SHA256 error");
+ }
+
+ void digest(bytearray_t &md) {
+ md.resize(32);
+ _digest(md);
+ }
+
+ bytearray_t digest() {
+ bytearray_t md(32);
+ _digest(md);
+ return std::move(md);
+ }
+};
+
+}
+
+#endif
diff --git a/include/salticidae/msg.h b/include/salticidae/msg.h
new file mode 100644
index 0000000..62fc33b
--- /dev/null
+++ b/include/salticidae/msg.h
@@ -0,0 +1,253 @@
+/**
+ * Copyright (c) 2018 Cornell University.
+ *
+ * Author: Ted Yin <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _SALTICIDAE_MSG_H
+#define _SALTICIDAE_MSG_H
+
+#include <cstdint>
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include "salticidae/type.h"
+#include "salticidae/stream.h"
+#include "salticidae/netaddr.h"
+
+namespace salticidae {
+
+template<typename OpcodeType = uint8_t,
+ const OpcodeType PING = 0xf0,
+ const OpcodeType PONG = 0xf1>
+class MsgBase {
+ public:
+ using opcode_t = OpcodeType;
+ static const opcode_t OPCODE_PING = PING;
+ static const opcode_t OPCODE_PONG = PONG;
+ static const size_t header_size;
+
+ private:
+ /* header */
+ /* all integers are encoded in little endian in the protocol */
+ uint32_t magic;
+ opcode_t opcode;
+ uint32_t length;
+ uint32_t checksum;
+
+ mutable bytearray_t payload;
+ mutable bool no_payload;
+
+ public:
+ MsgBase(): magic(0x0), no_payload(true) {}
+
+ MsgBase(const MsgBase &other):
+ magic(other.magic),
+ opcode(other.opcode),
+ length(other.length),
+ checksum(other.checksum),
+ payload(other.payload),
+ no_payload(other.no_payload) {}
+
+ MsgBase(MsgBase &&other):
+ magic(other.magic),
+ opcode(std::move(other.opcode)),
+ length(other.length),
+ checksum(other.checksum),
+ payload(std::move(other.payload)),
+ no_payload(other.no_payload) {}
+
+ MsgBase(const uint8_t *raw_header) {
+ uint32_t _magic;
+ opcode_t _opcode;
+ uint32_t _length;
+ uint32_t _checksum;
+ DataStream s(raw_header, raw_header + MsgBase::header_size);
+
+ s >> _magic
+ >> _opcode
+ >> _length
+ >> _checksum;
+ magic = letoh(_magic);
+ opcode = _opcode;
+ length = letoh(_length);
+ checksum = letoh(_checksum);
+ }
+
+ MsgBase &operator=(const MsgBase &other) {
+ magic = other.magic;
+ opcode = other.opcode;
+ length = other.length;
+ checksum = other.checksum;
+ payload = other.payload;
+ no_payload = other.no_payload;
+ return *this;
+ }
+
+ MsgBase &operator=(MsgBase &&other) {
+ magic = other.magic;
+ opcode = std::move(other.opcode);
+ length = other.length;
+ checksum = other.checksum;
+ payload = std::move(other.payload);
+ no_payload = other.no_payload;
+ return *this;
+ }
+
+ ~MsgBase() {}
+
+ size_t get_length() const { return length; }
+
+ const opcode_t &get_opcode() const { return opcode; }
+
+ void set_opcode(const opcode_t &_opcode) {
+ opcode = _opcode;
+ }
+
+ bytearray_t &&get_payload() const {
+#ifndef SALTICIDAE_NOCHECK
+ if (no_payload)
+ throw std::runtime_error("payload not available");
+ no_payload = true;
+#endif
+ return std::move(payload);
+ }
+
+ void set_payload(DataStream &&s) {
+ set_payload(bytearray_t(std::move(s)));
+ }
+
+ void set_payload(bytearray_t &&_payload) {
+ payload = std::move(_payload);
+ length = payload.size();
+ checksum = get_checksum();
+#ifndef SALTICIDAE_NOCHECK
+ no_payload = false;
+#endif
+ }
+
+ operator std::string() const {
+ DataStream s;
+ s << "<"
+ << "magic=" << get_hex(magic) << " "
+ << "opcode=" << get_hex(opcode) << " "
+ << "length=" << get_hex(length) << " "
+ << "checksum=" << get_hex(checksum) << " "
+ << "payload=" << get_hex(payload) << ">";
+
+ //std::string opcode_hex = get_hex(opcode);
+ //char *buff = new char[128 + opcode_hex.size()];
+ //size_t ret = sprintf(buff,
+ // "<magic=%08x opcode=%s length=%08x checksum=%08x payload=",
+ // magic, opcode_hex.c_str(), length, checksum);
+ //buff[ret] = 0;
+ //std::string res = std::string(buff) + bin2hexstr(payload.data(), length) + ">";
+ //delete [] buff;
+ //return std::move(res);
+ return std::string(s);
+ }
+
+ uint32_t get_checksum() const {
+ static class SHA256 sha256;
+ uint32_t res;
+ bytearray_t tmp;
+ sha256.reset();
+ sha256.update(payload);
+ sha256.digest(tmp);
+ sha256.reset();
+ sha256.update(tmp);
+ sha256.digest(tmp);
+ memmove(&res, &*tmp.begin(), 4);
+ return res;
+ }
+
+ bool verify_checksum() const {
+ return checksum == get_checksum();
+ }
+
+ bytearray_t serialize() const {
+ DataStream s;
+ s << htole(magic)
+ << opcode
+ << htole(length)
+ << htole(checksum)
+ << payload;
+ return std::move(s);
+ }
+
+ void gen_ping(uint16_t port) {
+ DataStream s;
+ set_opcode(OPCODE_PING);
+ s << htole(port);
+ set_payload(std::move(s));
+ }
+
+ void parse_ping(uint16_t &port) const {
+ DataStream s(get_payload());
+ s >> port;
+ port = letoh(port);
+ }
+
+ void gen_pong(uint16_t port) {
+ DataStream s;
+ set_opcode(OPCODE_PONG);
+ s << htole(port);
+ set_payload(std::move(s));
+ }
+
+ void parse_pong(uint16_t &port) const {
+ DataStream s(get_payload());
+ s >> port;
+ port = letoh(port);
+ }
+
+ void gen_hash_list(DataStream &s,
+ const std::vector<uint256_t> &hashes) {
+ uint32_t size = htole((uint32_t)hashes.size());
+ s << size;
+ for (const auto &h: hashes) s << h;
+ }
+
+ void parse_hash_list(DataStream &s,
+ std::vector<uint256_t> &hashes) const {
+ uint32_t size;
+ hashes.clear();
+
+ s >> size;
+ size = letoh(size);
+
+ hashes.resize(size);
+ for (auto &hash: hashes) s >> hash;
+ }
+
+};
+
+template<typename OpcodeType,
+ OpcodeType _,
+ OpcodeType __>
+const size_t MsgBase<OpcodeType, _, __>::header_size =
+ sizeof(MsgBase<OpcodeType, _, __>) -
+ sizeof(MsgBase<OpcodeType, _, __>::payload) -
+ sizeof(MsgBase<OpcodeType, _, __>::no_payload);
+}
+
+#endif
diff --git a/include/salticidae/netaddr.h b/include/salticidae/netaddr.h
new file mode 100644
index 0000000..c166c3a
--- /dev/null
+++ b/include/salticidae/netaddr.h
@@ -0,0 +1,115 @@
+/**
+ * Copyright (c) 2018 Cornell University.
+ *
+ * Author: Ted Yin <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _SALTICIDAE_NETADDR_H
+#define _SALTICIDAE_NETADDR_H
+
+#include <string>
+#include <cstring>
+#include <cstdint>
+#include <arpa/inet.h>
+
+#include "salticidae/util.h"
+
+namespace salticidae {
+
+/* TODO: IPv6 support */
+
+struct NetAddr {
+ uint32_t ip;
+ uint16_t port;
+ /* construct from human-readable format */
+ NetAddr(): ip(0), port(0) {}
+
+ NetAddr(uint32_t ip, uint16_t port): ip(ip), port(port) {}
+
+ NetAddr(const std::string &_addr, uint16_t _port) {
+ set_by_ip_port(_addr, _port);
+ }
+
+ void set_by_ip_port(const std::string &_addr, uint16_t _port) {
+ struct hostent *h;
+ if ((h = gethostbyname(_addr.c_str())) == nullptr)
+ throw SalticidaeError("gethostbyname failed");
+ memmove(&ip, h->h_addr_list[0], sizeof(in_addr_t));
+ port = htons(_port);
+ }
+
+ NetAddr(const std::string &ip_port_addr) {
+ size_t pos = ip_port_addr.find(":");
+ if (pos == std::string::npos)
+ throw SalticidaeError("invalid port format");
+ std::string ip_str = ip_port_addr.substr(0, pos);
+ std::string port_str = ip_port_addr.substr(pos + 1);
+ long port;
+ try {
+ port = std::stol(port_str.c_str());
+ } catch (std::logic_error) {
+ throw SalticidaeError("invalid port format");
+ }
+ if (port < 0)
+ throw SalticidaeError("negative port number");
+ if (port > 0xffff)
+ throw SalticidaeError("port number greater than 0xffff");
+ set_by_ip_port(ip_str, (uint16_t)port);
+ }
+ /* construct from unix socket format */
+ NetAddr(const struct sockaddr_in *addr_sock) {
+ ip = addr_sock->sin_addr.s_addr;
+ port = addr_sock->sin_port;
+ }
+
+ bool operator==(const NetAddr &other) const {
+ return ip == other.ip && port == other.port;
+ }
+
+ operator std::string() const {
+ struct in_addr in;
+ in.s_addr = ip;
+ return "<NetAddr " + std::string(inet_ntoa(in)) +
+ ":" + std::to_string(ntohs(port)) + ">";
+ }
+
+ bool is_null() const { return ip == 0 && port == 0; }
+};
+
+}
+
+namespace std {
+ template <>
+ struct hash<salticidae::NetAddr> {
+ size_t operator()(const salticidae::NetAddr &k) const {
+ return k.ip ^ k.port;
+ }
+ };
+
+ template <>
+ struct hash<const salticidae::NetAddr> {
+ size_t operator()(const salticidae::NetAddr &k) const {
+ return k.ip ^ k.port;
+ }
+ };
+}
+
+#endif
diff --git a/include/salticidae/network.h b/include/salticidae/network.h
new file mode 100644
index 0000000..3b82927
--- /dev/null
+++ b/include/salticidae/network.h
@@ -0,0 +1,552 @@
+/**
+ * Copyright (c) 2018 Cornell University.
+ *
+ * Author: Ted Yin <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _SALTICIDAE_NETWORK_H
+#define _SALTICIDAE_NETWORK_H
+
+#include "salticidae/netaddr.h"
+#include "salticidae/msg.h"
+#include "salticidae/conn.h"
+
+namespace salticidae {
+
+/** Network of nodes who can send async messages. */
+template<typename MsgType>
+class MsgNetwork: public ConnPool {
+ public:
+ class Conn: public ConnPool::Conn {
+ enum MsgState {
+ HEADER,
+ PAYLOAD
+ };
+ MsgType msg;
+ MsgState msg_state;
+ MsgNetwork *mn;
+
+ protected:
+ mutable size_t nsent;
+ mutable size_t nrecv;
+
+ public:
+ friend MsgNetwork;
+ Conn(MsgNetwork *mn): msg_state(HEADER), mn(mn), nsent(0), nrecv(0) {}
+ size_t get_nsent() const { return nsent; }
+ size_t get_nrecv() const { return nrecv; }
+ void clear_nsent() const { nsent = 0; }
+ void clear_nrecv() const { nrecv = 0; }
+
+ protected:
+ void on_read() override;
+ void on_setup() override {}
+ void on_teardown() override {}
+ };
+
+ using conn_t = RcObj<Conn>;
+ using msg_callback_t = std::function<void(const MsgType &msg, conn_t conn)>;
+ class msg_stat_by_opcode_t:
+ public std::unordered_map<typename MsgType::opcode_t,
+ std::pair<uint32_t, size_t>> {
+ public:
+ void add(const MsgType &msg) {
+ auto &p = this->operator[](msg.get_opcode());
+ p.first++;
+ p.second += msg.get_length();
+ }
+ };
+
+ private:
+ std::unordered_map<typename MsgType::opcode_t,
+ msg_callback_t> handler_map;
+
+ protected:
+ mutable msg_stat_by_opcode_t sent_by_opcode;
+ mutable msg_stat_by_opcode_t recv_by_opcode;
+ uint16_t listen_port;
+
+ ConnPool::conn_t create_conn() override { return (new Conn(this))->self(); }
+
+ public:
+ MsgNetwork(struct event_base *eb): ConnPool(eb) {}
+ void reg_handler(typename MsgType::opcode_t opcode, msg_callback_t handler);
+ void send_msg(const MsgType &msg, conn_t conn);
+ void init(NetAddr listen_addr);
+ msg_stat_by_opcode_t &get_sent_by_opcode() const {
+ return sent_by_opcode;
+ }
+ msg_stat_by_opcode_t &get_recv_by_opcode() const {
+ return recv_by_opcode;
+ }
+};
+
+/** Simple network that handles client-server requests. */
+template<typename MsgType>
+class ClientNetwork: public MsgNetwork<MsgType> {
+ using MsgNet = MsgNetwork<MsgType>;
+ std::unordered_map<NetAddr, typename MsgNet::conn_t> addr2conn;
+
+ public:
+ class Conn: public MsgNet::Conn {
+ ClientNetwork *cn;
+
+ public:
+ Conn(ClientNetwork *cn):
+ MsgNet::Conn(static_cast<MsgNet *>(cn)),
+ cn(cn) {}
+
+ protected:
+ void on_setup() override;
+ void on_teardown() override;
+ };
+
+ protected:
+ ConnPool::conn_t create_conn() override { return (new Conn(this))->self(); }
+
+ public:
+ ClientNetwork(struct event_base *eb): MsgNet(eb) {}
+ void send_msg(const MsgType &msg, const NetAddr &addr);
+};
+
+class PeerNetworkError: public SalticidaeError {
+ using SalticidaeError::SalticidaeError;
+};
+
+/** Peer-to-peer network where any two nodes could hold a bi-diretional message
+ * channel, established by either side. */
+template<typename MsgType>
+class PeerNetwork: public MsgNetwork<MsgType> {
+ using MsgNet= MsgNetwork<MsgType>;
+ public:
+ enum IdentityMode {
+ IP_BASED,
+ IP_PORT_BASED
+ };
+
+ class Conn: public MsgNet::Conn {
+ NetAddr peer_id;
+ Event ev_timeout;
+ PeerNetwork *pn;
+
+ public:
+ friend PeerNetwork;
+ const NetAddr &get_peer() { return peer_id; }
+ Conn(PeerNetwork *pn):
+ MsgNet::Conn(static_cast<MsgNet *>(pn)),
+ pn(pn) {}
+
+ protected:
+ void close() override {
+ ev_timeout.clear();
+ MsgNet::Conn::close();
+ }
+
+ void on_setup() override;
+ void on_teardown() override;
+ };
+
+ using conn_t = RcObj<Conn>;
+
+ private:
+ struct Peer {
+ /** connection addr, may be different due to passive mode */
+ NetAddr addr;
+ /** the underlying connection, may be invalid when connected = false */
+ conn_t conn;
+ PeerNetwork *pn;
+ Event ev_ping_timer;
+ bool ping_timer_ok;
+ bool pong_msg_ok;
+ bool connected;
+
+ Peer() = delete;
+ Peer(NetAddr addr, conn_t conn, PeerNetwork *pn, struct event_base *eb):
+ addr(addr), conn(conn), pn(pn),
+ ev_ping_timer(
+ Event(eb, -1, 0, std::bind(&Peer::ping_timer, this, _1, _2))),
+ connected(false) {}
+ ~Peer() {}
+ Peer &operator=(const Peer &) = delete;
+ Peer(const Peer &) = delete;
+
+ void ping_timer(evutil_socket_t, short);
+ void reset_ping_timer();
+ void send_ping();
+ void clear_all_events() {
+ if (ev_ping_timer)
+ ev_ping_timer.del();
+ }
+ void reset_conn(conn_t conn);
+ };
+
+ std::unordered_map <NetAddr, Peer *> id2peer;
+ std::vector<NetAddr> peer_list;
+
+ IdentityMode id_mode;
+ double ping_period;
+ double conn_timeout;
+
+ void msg_ping(const MsgType &msg, ConnPool::conn_t conn);
+ void msg_pong(const MsgType &msg, ConnPool::conn_t conn);
+ void reset_conn_timeout(conn_t conn);
+ bool check_new_conn(conn_t conn, uint16_t port);
+ void start_active_conn(const NetAddr &paddr);
+
+ protected:
+ ConnPool::conn_t create_conn() override { return (new Conn(this))->self(); }
+
+ public:
+ PeerNetwork(struct event_base *eb,
+ double ping_period = 30,
+ double conn_timeout = 180,
+ IdentityMode id_mode = IP_PORT_BASED):
+ MsgNet(eb),
+ id_mode(id_mode),
+ ping_period(ping_period),
+ conn_timeout(conn_timeout) {}
+
+ void add_peer(const NetAddr &paddr);
+ const conn_t get_peer_conn(const NetAddr &paddr) const;
+ void send_msg(const MsgType &msg, const Peer *peer);
+ void send_msg(const MsgType &msg, const NetAddr &paddr);
+ void init(NetAddr listen_addr);
+ bool has_peer(const NetAddr &paddr) const;
+ const std::vector<NetAddr> &all_peers() const;
+ using ConnPool::create_conn;
+};
+
+template<typename MsgType>
+void MsgNetwork<MsgType>::Conn::on_read() {
+ auto &recv_buffer = read();
+ auto conn = static_pointer_cast<Conn>(self());
+ while (get_fd() != -1)
+ {
+ if (msg_state == Conn::HEADER)
+ {
+ if (recv_buffer.size() < MsgType::header_size) break;
+ /* new header available */
+ bytearray_t data = recv_buffer.pop(MsgType::header_size);
+ msg = MsgType(data.data());
+ msg_state = Conn::PAYLOAD;
+ }
+ if (msg_state == Conn::PAYLOAD)
+ {
+ size_t len = msg.get_length();
+ if (recv_buffer.size() < len) break;
+ /* new payload available */
+ bytearray_t data = recv_buffer.pop(len);
+ msg.set_payload(std::move(data));
+ msg_state = Conn::HEADER;
+ if (!msg.verify_checksum())
+ {
+ SALTICIDAE_LOG_WARN("checksums do not match, dropping the message");
+ return;
+ }
+ auto it = mn->handler_map.find(msg.get_opcode());
+ if (it == mn->handler_map.end())
+ SALTICIDAE_LOG_WARN("unknown command: %s", get_hex(msg.get_opcode()));
+ else /* call the handler */
+ {
+ SALTICIDAE_LOG_DEBUG("got message %s from %s",
+ std::string(msg).c_str(),
+ std::string(*this).c_str());
+ it->second(msg, conn);
+ nrecv++;
+ mn->recv_by_opcode.add(msg);
+ }
+ }
+ }
+}
+
+template<typename MsgType>
+void PeerNetwork<MsgType>::Peer::reset_conn(conn_t new_conn) {
+ if (conn != new_conn)
+ {
+ if (conn)
+ {
+ SALTICIDAE_LOG_DEBUG("moving send buffer");
+ new_conn->move_send_buffer(conn);
+ SALTICIDAE_LOG_INFO("terminating old connection %s", std::string(*conn).c_str());
+ conn->terminate();
+ }
+ addr = new_conn->get_addr();
+ conn = new_conn;
+ }
+ clear_all_events();
+}
+
+template<typename MsgType>
+void PeerNetwork<MsgType>::Conn::on_setup() {
+ assert(!ev_timeout);
+ ev_timeout = Event(pn->eb, -1, 0, [this](evutil_socket_t, short) {
+ SALTICIDAE_LOG_INFO("peer ping-pong timeout");
+ this->terminate();
+ });
+ /* the initial ping-pong to set up the connection */
+ MsgType ping;
+ ping.gen_ping(pn->listen_port);
+ auto conn = static_pointer_cast<Conn>(this->self());
+ pn->reset_conn_timeout(conn);
+ pn->MsgNet::send_msg(ping, conn);
+}
+
+template<typename MsgType>
+void PeerNetwork<MsgType>::Conn::on_teardown() {
+ auto it = pn->id2peer.find(peer_id);
+ if (it == pn->id2peer.end()) return;
+ Peer *p = it->second;
+ if (this != p->conn) return;
+ p->ev_ping_timer.del();
+ p->connected = false;
+ p->conn = nullptr;
+ SALTICIDAE_LOG_INFO("connection lost %s for %s",
+ std::string(*this).c_str(),
+ std::string(peer_id).c_str());
+ pn->start_active_conn(peer_id);
+}
+
+template<typename MsgType>
+bool PeerNetwork<MsgType>::check_new_conn(conn_t conn, uint16_t port) {
+ if (conn->peer_id.is_null())
+ { /* passive connections can eventually have ids after getting the port
+ number in IP_BASED_PORT mode */
+ assert(id_mode == IP_PORT_BASED);
+ conn->peer_id.ip = conn->get_addr().ip;
+ conn->peer_id.port = port;
+ }
+ Peer *p = id2peer.find(conn->peer_id)->second;
+ if (p->connected)
+ {
+ if (conn != p->conn)
+ {
+ conn->terminate();
+ return true;
+ }
+ return false;
+ }
+ p->reset_conn(conn);
+ p->connected = true;
+ p->reset_ping_timer();
+ p->send_ping();
+ if (p->connected)
+ SALTICIDAE_LOG_INFO("PeerNetwork: established connection with id %s via %s",
+ std::string(conn->peer_id).c_str(), std::string(*conn).c_str());
+ return false;
+}
+
+template<typename MsgType>
+void PeerNetwork<MsgType>::msg_ping(const MsgType &msg, ConnPool::conn_t conn_) {
+ auto conn = static_pointer_cast<Conn>(conn_);
+ uint16_t port;
+ msg.parse_ping(port);
+ SALTICIDAE_LOG_INFO("ping from %s, port %u", std::string(*conn).c_str(), ntohs(port));
+ if (check_new_conn(conn, port)) return;
+ Peer *p = id2peer.find(conn->peer_id)->second;
+ MsgType pong;
+ pong.gen_pong(this->listen_port);
+ send_msg(pong, p);
+}
+
+template<typename MsgType>
+void PeerNetwork<MsgType>::msg_pong(const MsgType &msg, ConnPool::conn_t conn_) {
+ auto conn = static_pointer_cast<Conn>(conn_);
+ auto it = id2peer.find(conn->peer_id);
+ if (it == id2peer.end())
+ {
+ SALTICIDAE_LOG_WARN("pong message discarded");
+ return;
+ }
+ Peer *p = it->second;
+ uint16_t port;
+ msg.parse_pong(port);
+ if (check_new_conn(conn, port)) return;
+ p->pong_msg_ok = true;
+ if (p->ping_timer_ok)
+ {
+ p->reset_ping_timer();
+ p->send_ping();
+ }
+}
+
+template<typename MsgType>
+void MsgNetwork<MsgType>::init(NetAddr listen_addr) {
+ listen_port = listen_addr.port;
+ ConnPool::init(listen_addr);
+}
+
+template<typename MsgType>
+void PeerNetwork<MsgType>::init(NetAddr listen_addr) {
+ MsgNet::init(listen_addr);
+ this->reg_handler(MsgType::OPCODE_PING,
+ std::bind(&PeerNetwork::msg_ping, this, _1, _2));
+ this->reg_handler(MsgType::OPCODE_PONG,
+ std::bind(&PeerNetwork::msg_pong, this, _1, _2));
+}
+
+template<typename MsgType>
+void PeerNetwork<MsgType>::start_active_conn(const NetAddr &addr) {
+ Peer *p = id2peer.find(addr)->second;
+ if (p->connected) return;
+ auto conn = static_pointer_cast<Conn>(create_conn(addr));
+ assert(p->conn == nullptr);
+ p->conn = conn;
+ conn->peer_id = addr;
+ if (id_mode == IP_BASED)
+ conn->peer_id.port = 0;
+}
+
+template<typename MsgType>
+void PeerNetwork<MsgType>::add_peer(const NetAddr &addr) {
+ auto it = id2peer.find(addr);
+ if (it != id2peer.end())
+ throw PeerNetworkError("peer already exists");
+ id2peer.insert(std::make_pair(addr, new Peer(addr, nullptr, this, this->eb)));
+ peer_list.push_back(addr);
+ start_active_conn(addr);
+}
+
+template<typename MsgType>
+const typename PeerNetwork<MsgType>::conn_t
+PeerNetwork<MsgType>::get_peer_conn(const NetAddr &paddr) const {
+ auto it = id2peer.find(paddr);
+ if (it == id2peer.end())
+ throw PeerNetworkError("peer does not exist");
+ return it->second->conn;
+}
+
+template<typename MsgType>
+bool PeerNetwork<MsgType>::has_peer(const NetAddr &paddr) const {
+ return id2peer.count(paddr);
+}
+
+template<typename MsgType>
+void MsgNetwork<MsgType>::reg_handler(typename MsgType::opcode_t opcode,
+ msg_callback_t handler) {
+ handler_map[opcode] = handler;
+}
+
+template<typename MsgType>
+void MsgNetwork<MsgType>::send_msg(const MsgType &msg, conn_t conn) {
+ bytearray_t msg_data = msg.serialize();
+ SALTICIDAE_LOG_DEBUG("wrote message %s to %s",
+ std::string(msg).c_str(),
+ std::string(*conn).c_str());
+ conn->write(std::move(msg_data));
+ conn->nsent++;
+ sent_by_opcode.add(msg);
+}
+
+template<typename MsgType>
+void PeerNetwork<MsgType>::send_msg(const MsgType &msg, const Peer *peer) {
+ bytearray_t msg_data = msg.serialize();
+ SALTICIDAE_LOG_DEBUG("wrote message %s to %s",
+ std::string(msg).c_str(),
+ std::string(peer->addr).c_str());
+ if (peer->connected)
+ {
+ SALTICIDAE_LOG_DEBUG("wrote to ConnPool");
+ peer->conn->write(std::move(msg_data));
+ }
+ else
+ {
+ SALTICIDAE_LOG_DEBUG("dropped");
+ }
+ peer->conn->nsent++;
+ this->sent_by_opcode.add(msg);
+}
+
+template<typename MsgType>
+void PeerNetwork<MsgType>::send_msg(const MsgType &msg, const NetAddr &addr) {
+ auto it = id2peer.find(addr);
+ if (it == id2peer.end())
+ {
+ SALTICIDAE_LOG_ERROR("sending to non-existing peer: %s", std::string(addr).c_str());
+ throw PeerNetworkError("peer does not exist");
+ }
+ send_msg(msg, it->second);
+}
+
+template<typename MsgType>
+void PeerNetwork<MsgType>::Peer::reset_ping_timer() {
+ assert(ev_ping_timer);
+ ev_ping_timer.del();
+ ev_ping_timer.add_with_timeout(gen_rand_timeout(pn->ping_period));
+}
+
+template<typename MsgType>
+void PeerNetwork<MsgType>::reset_conn_timeout(conn_t conn) {
+ assert(conn->ev_timeout);
+ conn->ev_timeout.del();
+ conn->ev_timeout.add_with_timeout(conn_timeout);
+}
+
+template<typename MsgType>
+void PeerNetwork<MsgType>::Peer::send_ping() {
+ ping_timer_ok = false;
+ pong_msg_ok = false;
+ MsgType ping;
+ ping.gen_ping(pn->listen_port);
+ pn->reset_conn_timeout(conn);
+ pn->send_msg(ping, this);
+}
+
+template<typename MsgType>
+void PeerNetwork<MsgType>::Peer::ping_timer(evutil_socket_t, short) {
+ ping_timer_ok = true;
+ if (pong_msg_ok)
+ {
+ reset_ping_timer();
+ send_ping();
+ }
+}
+
+template<typename MsgType>
+const std::vector<NetAddr> &PeerNetwork<MsgType>::all_peers() const {
+ return peer_list;
+}
+
+template<typename MsgType>
+void ClientNetwork<MsgType>::Conn::on_setup() {
+ assert(this->get_mode() == Conn::PASSIVE);
+ const auto &addr = this->get_addr();
+ cn->addr2conn.erase(addr);
+ cn->addr2conn.insert(
+ std::make_pair(addr,
+ static_pointer_cast<Conn>(this->self())));
+}
+
+template<typename MsgType>
+void ClientNetwork<MsgType>::Conn::on_teardown() {
+ assert(this->get_mode() == Conn::PASSIVE);
+ cn->addr2conn.erase(this->get_addr());
+}
+
+template<typename MsgType>
+void ClientNetwork<MsgType>::send_msg(const MsgType &msg, const NetAddr &addr) {
+ auto it = addr2conn.find(addr);
+ if (it == addr2conn.end()) return;
+ MsgNet::send_msg(msg, it->second);
+}
+
+}
+
+#endif
diff --git a/include/salticidae/ref.h b/include/salticidae/ref.h
new file mode 100644
index 0000000..5f54da2
--- /dev/null
+++ b/include/salticidae/ref.h
@@ -0,0 +1,202 @@
+/**
+ * Copyright (c) 2018 Cornell University.
+ *
+ * Author: Ted Yin <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _SALTICIDAE_REF_H
+#define _SALTICIDAE_REF_H
+
+#include <atomic>
+#include <functional>
+
+namespace salticidae {
+
+struct _RCCtl {
+ size_t ref_cnt;
+ size_t weak_cnt;
+ void add_ref() { ref_cnt++; }
+ void add_weak() { weak_cnt++; }
+ bool release_ref() {
+ if (--ref_cnt) return false;
+ if (weak_cnt) return true;
+ delete this;
+ return true;
+ }
+ void release_weak() {
+ if (--weak_cnt) return;
+ if (ref_cnt) return;
+ delete this;
+ }
+ size_t get_cnt() { return ref_cnt; }
+ _RCCtl(): ref_cnt(1), weak_cnt(0) {}
+ ~_RCCtl() {}
+};
+
+struct _ARCCtl {
+ std::atomic_size_t ref_cnt;
+ std::atomic_size_t weak_cnt;
+ std::atomic_uint8_t dcnt;
+ void add_ref() { ref_cnt++; }
+ void add_weak() { weak_cnt++; }
+ bool release_ref() {
+ dcnt++;
+ if (--ref_cnt) { dcnt--; return false; }
+ if (weak_cnt) { dcnt--; return true; }
+ if (!--dcnt) delete this;
+ return true;
+ }
+ void release_weak() {
+ dcnt++;
+ if (--weak_cnt) { dcnt--; return; }
+ if (ref_cnt) { dcnt--; return; }
+ if (!--dcnt) delete this;
+ }
+ size_t get_cnt() { return ref_cnt.load(); }
+ _ARCCtl(): ref_cnt(1), weak_cnt(0), dcnt(0) {}
+ ~_ARCCtl() {}
+};
+
+template<typename T, typename R> class RcObjBase;
+
+template<typename T, typename R>
+class WeakObjBase {
+ R *ctl;
+ void release() { if (ctl) ctl->release_weak(); }
+ public:
+ friend RcObjBase<T, R>;
+ friend std::hash<WeakObjBase<T, R>>;
+ WeakObjBase(): ctl(nullptr) {}
+ WeakObjBase &operator=(const WeakObjBase &other) {
+ release();
+ ctl = other.ctl;
+ ctl->add_weak();
+ return *this;
+ }
+
+ WeakObjBase(const WeakObjBase &other): ctl(other.ctl) {
+ ctl->add_weak();
+ }
+
+ WeakObjBase(WeakObjBase &&other): ctl(other.ctl) {
+ other.ctl = nullptr;
+ }
+
+ WeakObjBase(const RcObjBase<T, R> &other);
+
+ ~WeakObjBase() { release(); }
+};
+
+template<typename T, typename R>
+class RcObjBase {
+ T *obj;
+ R *ctl;
+ void release() {
+ if (ctl && ctl->release_ref())
+ delete obj;
+ }
+ public:
+ friend WeakObjBase<T, R>;
+ friend std::hash<RcObjBase<T, R>>;
+ template<typename T__, typename T_, typename R_>
+ friend RcObjBase<T__, R_> static_pointer_cast(const RcObjBase<T_, R_> &other);
+ template<typename T_, typename R_> friend class RcObjBase;
+
+ operator T*() const { return obj; }
+ T *operator->() const { return obj; }
+ RcObjBase(): obj(nullptr), ctl(nullptr) {}
+ RcObjBase(T *obj): obj(obj), ctl(new R()) {}
+ RcObjBase &operator=(const RcObjBase &other) {
+ release();
+ obj = other.obj;
+ ctl = other.ctl;
+ ctl->add_ref();
+ return *this;
+ }
+
+ RcObjBase(const RcObjBase &other):
+ obj(other.obj), ctl(other.ctl) {
+ ctl->add_ref();
+ }
+
+ template<typename T_>
+ RcObjBase(const RcObjBase<T_, R> &other):
+ obj(other.obj), ctl(other.ctl) {
+ ctl->add_ref();
+ }
+
+ RcObjBase(RcObjBase &&other):
+ obj(other.obj), ctl(other.ctl) {
+ other.ctl = nullptr;
+ }
+
+ RcObjBase(const WeakObjBase<T, R> &other) {
+ if (other.ctl && other.ctl->ref_cnt)
+ {
+ obj = other.obj;
+ ctl = other.ctl;
+ ctl->add_ref();
+ }
+ else
+ {
+ obj = nullptr;
+ ctl = nullptr;
+ }
+ }
+
+ ~RcObjBase() { release(); }
+
+ size_t get_cnt() const { return ctl ? ctl->get_cnt() : 0; }
+};
+
+template<typename T, typename T_, typename R>
+RcObjBase<T, R> static_pointer_cast(const RcObjBase<T_, R> &other) {
+ RcObjBase<T, R> rc{};
+ rc.obj = static_cast<T *>(other.obj);
+ rc.ctl = other.ctl;
+ rc.ctl->add_ref();
+ return std::move(rc);
+}
+
+template<typename T, typename R>
+inline WeakObjBase<T, R>::WeakObjBase(const RcObjBase<T, R> &other):
+ ctl(other.ctl) {
+ ctl->add_weak();
+}
+
+template<typename T> using RcObj = RcObjBase<T, _RCCtl>;
+template<typename T> using WeakObj = WeakObjBase<T, _RCCtl>;
+
+template<typename T> using ArcObj = RcObjBase<T, _ARCCtl>;
+template<typename T> using AweakObj = WeakObjBase<T, _ARCCtl>;
+
+}
+
+namespace std {
+ template<typename T, typename R>
+ struct hash<salticidae::RcObjBase<T, R>> {
+ size_t operator()(const salticidae::RcObjBase<T, R> &k) const {
+ return (size_t)k.obj;
+ }
+ };
+}
+
+#endif
diff --git a/include/salticidae/stream.h b/include/salticidae/stream.h
new file mode 100644
index 0000000..4259773
--- /dev/null
+++ b/include/salticidae/stream.h
@@ -0,0 +1,274 @@
+/**
+ * Copyright (c) 2018 Cornell University.
+ *
+ * Author: Ted Yin <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _SALTICIDAE_STREAM_H
+#define _SALTICIDAE_STREAM_H
+
+#include "salticidae/type.h"
+#include "salticidae/crypto.h"
+
+namespace salticidae {
+
+template<size_t N, typename T> class Blob;
+using uint256_t = Blob<256, uint64_t>;
+
+class DataStream {
+ bytearray_t buffer;
+ size_t offset;
+
+ public:
+ DataStream(): offset(0) {}
+ DataStream(const uint8_t *begin, const uint8_t *end): buffer(begin, end), offset(0) {}
+ DataStream(bytearray_t &&data): buffer(std::move(data)), offset(0) {}
+ DataStream(const bytearray_t &data): buffer(data), offset(0) {}
+
+ DataStream(DataStream &&other):
+ buffer(std::move(other.buffer)),
+ offset(other.offset) {}
+
+ DataStream(const DataStream &other):
+ buffer(other.buffer),
+ offset(other.offset) {}
+
+ DataStream &operator=(const DataStream &other) {
+ buffer = other.buffer;
+ offset = other.offset;
+ return *this;
+ }
+
+ DataStream &operator=(DataStream &&other) {
+ buffer = std::move(other.buffer);
+ offset = std::move(other.offset);
+ return *this;
+ }
+
+ uint8_t *data() { return &buffer[offset]; }
+
+ void clear() {
+ buffer.clear();
+ offset = 0;
+ }
+
+ size_t size() const {
+ return buffer.size() - offset;
+ }
+
+ template<typename T>
+ typename std::enable_if<std::is_integral<T>::value, DataStream &>::type
+ operator<<(T d) {
+ buffer.resize(buffer.size() + sizeof(T));
+ *(reinterpret_cast<T *>(&*buffer.end() - sizeof(T))) = d;
+ return *this;
+ }
+
+ template<typename T>
+ typename std::enable_if<is_ranged<T>::value, DataStream &>::type
+ operator<<(const T &d) {
+ buffer.insert(buffer.end(), d.begin(), d.end());
+ return *this;
+ }
+
+ void put_data(uint8_t *begin, uint8_t *end) {
+ size_t len = end - begin;
+ buffer.resize(buffer.size() + len);
+ memmove(&*buffer.end() - len, begin, len);
+ }
+
+ template<typename T>
+ typename std::enable_if<!is_ranged<T>::value &&
+ !std::is_integral<T>::value, DataStream &>::type
+ operator<<(const T &obj) {
+ obj.serialize(*this);
+ return *this;
+ }
+
+ DataStream &operator<<(const char *cstr) {
+ put_data((uint8_t *)cstr, (uint8_t *)cstr + strlen(cstr));
+ return *this;
+ }
+
+ template<typename T>
+ typename std::enable_if<std::is_integral<T>::value, DataStream &>::type
+ operator>>(T &d) {
+#ifndef SALTICIDAE_NOCHECK
+ if (offset >= buffer.size())
+ throw std::ios_base::failure("insufficient buffer");
+#endif
+ d = *(reinterpret_cast<T *>(&buffer[offset]));
+ offset += sizeof(T);
+ return *this;
+ }
+
+ template<typename T>
+ typename std::enable_if<!std::is_integral<T>::value, DataStream &>::type
+ operator>>(T &obj) {
+ obj.unserialize(*this);
+ return *this;
+ }
+
+ std::string get_hex() const {
+ char buf[3];
+ DataStream s;
+ for (auto it = buffer.begin() + offset; it != buffer.end(); it++)
+ {
+ sprintf(buf, "%02x", *it);
+ s.put_data((uint8_t *)buf, (uint8_t *)buf + 2);
+ }
+ return std::string(s.buffer.begin(), s.buffer.end());
+ }
+
+ void load_hex(const std::string &hexstr) {
+ size_t len = hexstr.size();
+ const char *p;
+ uint8_t *bp;
+ unsigned int tmp;
+ if (len & 1)
+ throw std::runtime_error("not a valid hex string");
+ buffer.resize(len >> 1);
+ offset = 0;
+ for (p = hexstr.data(), bp = &*buffer.begin();
+ p < hexstr.data() + len; p += 2, bp++)
+ {
+ if (sscanf(p, "%02x", &tmp) != 1)
+ throw std::runtime_error("not a valid hex string");
+ *bp = tmp;
+ }
+ }
+
+ operator bytearray_t () const & {
+ return bytearray_t(buffer.begin() + offset, buffer.end());
+ }
+
+ operator bytearray_t () const && {
+ return std::move(buffer);
+ }
+
+ operator std::string () const & {
+ return std::string(buffer.begin() + offset, buffer.end());
+ }
+
+ inline uint256_t get_hash() const;
+};
+
+template<size_t N, typename T = uint64_t>
+class Blob {
+ using _impl_type = T;
+ static const size_t bit_per_datum = sizeof(_impl_type) * 8;
+ static_assert(!(N % bit_per_datum), "N must be divisible by bit_per_datum");
+ static const auto _len = N / bit_per_datum;
+ _impl_type data[_len];
+ bool loaded;
+
+ public:
+
+ Blob(): loaded(false) {}
+ Blob(const bytearray_t &arr) {
+ if (arr.size() != N / 8)
+ throw std::runtime_error("incorrect Blob size");
+ load(&*arr.begin());
+ }
+
+ Blob(const uint8_t *arr) { load(arr); }
+
+ void load(const uint8_t *arr) {
+ arr += N / 8;
+ for (_impl_type *ptr = data + _len; ptr > data;)
+ {
+ _impl_type x = 0;
+ for (unsigned j = 0; j < sizeof(_impl_type); j++)
+ x = (x << 8) | *(--arr);
+ *(--ptr) = x;
+ }
+ loaded = true;
+ }
+
+ bool is_null() const { return !loaded; }
+
+ bool operator==(const Blob<N> &other) const {
+ for (size_t i = 0; i < _len; i++)
+ if (data[i] != other.data[i])
+ return false;
+ return true;
+ }
+
+ bool operator!=(const Blob<N> &other) const {
+ return !(data == other);
+ }
+
+ size_t cheap_hash() const { return *data; }
+
+ void serialize(DataStream &s) const {
+ for (const _impl_type *ptr = data; ptr < data + _len; ptr++)
+ s << htole(*ptr);
+ }
+
+ void unserialize(DataStream &s) {
+ for (_impl_type *ptr = data; ptr < data + _len; ptr++)
+ {
+ _impl_type x;
+ s >> x;
+ *ptr = letoh(x);
+ }
+ }
+};
+
+const size_t ENT_HASH_LENGTH = 256 / 8;
+
+uint256_t DataStream::get_hash() const {
+ class SHA256 d;
+ d.update(buffer.begin() + offset, size());
+ return d.digest();
+}
+
+template<typename T> inline uint256_t get_hash(const T &x) {
+ DataStream s;
+ s << x;
+ return s.get_hash();
+}
+
+template<typename T> inline std::string get_hex(const T &x) {
+ DataStream s;
+ s << x;
+ return s.get_hex();
+}
+
+}
+
+namespace std {
+ template <>
+ struct hash<salticidae::uint256_t> {
+ size_t operator()(const salticidae::uint256_t &k) const {
+ return (size_t)k.cheap_hash();
+ }
+ };
+
+ template <>
+ struct hash<const salticidae::uint256_t> {
+ size_t operator()(const salticidae::uint256_t &k) const {
+ return (size_t)k.cheap_hash();
+ }
+ };
+}
+
+#endif
diff --git a/include/salticidae/type.h b/include/salticidae/type.h
new file mode 100644
index 0000000..edf38a6
--- /dev/null
+++ b/include/salticidae/type.h
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2018 Cornell University.
+ *
+ * Author: Ted Yin <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _SALTICIDAE_TYPE_H
+#define _SALTICIDAE_TYPE_H
+
+#include <vector>
+#include <string>
+#include <cstring>
+#include <cstddef>
+#include <cstdint>
+#include <cstdio>
+#include <ios>
+#include <functional>
+#include <mutex>
+
+namespace salticidae {
+
+const auto _1 = std::placeholders::_1;
+const auto _2 = std::placeholders::_2;
+
+using bytearray_t = std::vector<uint8_t>;
+using mutex_lg_t = std::lock_guard<std::mutex>;
+using mutex_ul_t = std::unique_lock<std::mutex>;
+
+template<typename T> T htole(T) = delete;
+template<> inline uint16_t htole<uint16_t>(uint16_t x) { return htole16(x); }
+template<> inline uint32_t htole<uint32_t>(uint32_t x) { return htole32(x); }
+template<> inline uint64_t htole<uint64_t>(uint64_t x) { return htole64(x); }
+
+template<typename T> T letoh(T) = delete;
+template<> inline uint16_t letoh<uint16_t>(uint16_t x) { return le16toh(x); }
+template<> inline uint32_t letoh<uint32_t>(uint32_t x) { return le32toh(x); }
+template<> inline uint64_t letoh<uint64_t>(uint64_t x) { return le64toh(x); }
+
+template <typename T, typename = void>
+struct is_ranged : std::false_type {};
+
+template <typename T>
+struct is_ranged<T,
+ std::void_t<decltype(std::declval<T>().begin()),
+ decltype(std::declval<T>().end())>> : std::true_type {};
+
+}
+
+#endif
diff --git a/include/salticidae/util.h b/include/salticidae/util.h
new file mode 100644
index 0000000..b3eb991
--- /dev/null
+++ b/include/salticidae/util.h
@@ -0,0 +1,292 @@
+/**
+ * Copyright (c) 2018 Cornell University.
+ *
+ * Author: Ted Yin <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _SALTICIDAE_COMMON_H
+#define _SALTICIDAE_COMMON_H
+
+#include <string>
+#include <exception>
+#include <cstdarg>
+#include <vector>
+#include <unordered_map>
+#include <functional>
+#include <getopt.h>
+#include <event2/event.h>
+
+namespace salticidae {
+
+void sec2tv(double t, struct timeval &tv);
+void event_add_with_timeout(struct event *ev, double timeout);
+
+class SalticidaeError: public std::exception {
+ std::string msg;
+ public:
+ SalticidaeError();
+ SalticidaeError(const std::string &fmt, ...);
+ operator std::string() const;
+};
+
+class Logger {
+ protected:
+ FILE *output;
+ bool opened;
+ void write(const char *tag, const char *fmt, va_list ap);
+
+ public:
+ Logger() : output(stderr), opened(false) {}
+ Logger(FILE *f) : output(f) {}
+ Logger(const char *filename): opened(true) {
+ if ((output = fopen(filename, "w")) == nullptr)
+ throw SalticidaeError("logger cannot open file");
+ }
+
+ ~Logger() {
+ if (opened) fclose(output);
+ }
+
+ void debug(const char *fmt, ...);
+ void info(const char *fmt, ...);
+ void warning(const char *fmt, ...);
+ void error(const char *fmt, ...);
+};
+
+extern Logger logger;
+
+#ifdef SALTICIDAE_DEBUG_LOG
+#define SALTICIDAE_NORMAL_LOG
+#define SALTICIDAE_ENABLE_LOG_DEBUG
+#endif
+
+#ifdef SALTICIDAE_NORMAL_LOG
+#define SALTICIDAE_ENABLE_LOG_INFO
+#define SALTICIDAE_ENABLE_LOG_WARN
+#endif
+
+#ifdef SALTICIDAE_ENABLE_LOG_INFO
+#define SALTICIDAE_LOG_INFO(...) salticidae::logger.info(__VA_ARGS__)
+#else
+#define SALTICIDAE_LOG_INFO(...) ((void)0)
+#endif
+
+#ifdef SALTICIDAE_ENABLE_LOG_DEBUG
+#define SALTICIDAE_LOG_DEBUG(...) salticidae::logger.debug(__VA_ARGS__)
+#else
+#define SALTICIDAE_LOG_DEBUG(...) ((void)0)
+#endif
+
+#ifdef SALTICIDAE_ENABLE_LOG_WARN
+#define SALTICIDAE_LOG_WARN(...) salticidae::logger.warning(__VA_ARGS__)
+#else
+#define SALTICIDAE_LOG_WARN(...) ((void)0)
+#endif
+
+#define SALTICIDAE_LOG_ERROR(...) salticidae::logger.error(__VA_ARGS__)
+
+class ElapsedTime {
+ struct timeval t0;
+ clock_t cpu_t0;
+ public:
+ double elapsed_sec;
+ double cpu_elapsed_sec;
+ void start();
+ void stop(bool show_info = false);
+};
+
+class Config {
+ public:
+ enum Action {
+ SWITCH_ON,
+ SET_VAL,
+ APPEND
+ };
+
+ class OptVal {
+ public:
+ virtual void switch_on() {
+ throw SalticidaeError("undefined OptVal behavior: set_val");
+ }
+
+ virtual void set_val(const std::string &) {
+ throw SalticidaeError("undefined OptVal behavior: set_val");
+ }
+
+ virtual void append(const std::string &) {
+ throw SalticidaeError("undefined OptVal behavior: append");
+ }
+ virtual ~OptVal() = default;
+ };
+
+ class OptValFlag: public OptVal {
+ bool &val;
+ public:
+ OptValFlag(bool &val): val(val) {}
+ void switch_on() override { val = true; }
+ };
+
+ class OptValStr: public OptVal {
+ std::string &val;
+ public:
+ OptValStr(std::string &val): val(val) {}
+ void set_val(const std::string &strval) override {
+ val = strval;
+ }
+ };
+
+ class OptValInt: public OptVal {
+ int &val;
+ public:
+ OptValInt(int &val): val(val) {}
+ void set_val(const std::string &strval) override {
+ size_t idx;
+ try {
+ val = stoi(strval, &idx);
+ } catch (std::invalid_argument) {
+ throw SalticidaeError("invalid integer");
+ }
+ }
+ };
+
+ class OptValDouble: public OptVal {
+ double &val;
+ public:
+ OptValDouble(double &val): val(val) {}
+ void set_val(const std::string &strval) override {
+ size_t idx;
+ try {
+ val = stod(strval, &idx);
+ } catch (std::invalid_argument) {
+ throw SalticidaeError("invalid double");
+ }
+ }
+ };
+
+ class OptValStrVec: public OptVal {
+ std::vector<std::string> &val;
+ public:
+ OptValStrVec(std::vector<std::string> &val): val(val) {}
+ void append(const std::string &strval) override {
+ val.push_back(strval);
+ }
+ };
+
+ private:
+ struct Opt {
+ std::string optname;
+ OptVal *optval;
+ Action action;
+ struct option opt;
+ Opt(const std::string &optname, OptVal *optval, Action action, int idx);
+ Opt(Opt &&other):
+ optname(std::move(other.optname)),
+ optval(other.optval),
+ action(other.action),
+ opt(other.opt) { opt.name = this->optname.c_str(); }
+ };
+
+ std::unordered_map<std::string, Opt> conf;
+ std::vector<Opt *> getopt_order;
+ std::string conf_fname;
+ OptValStr opt_val_conf;
+ int conf_idx;
+ void update(const std::string &optname, const char *optval);
+ void update(Opt &opt, const char *optval);
+
+ public:
+ Config(const std::string &conf_fname):
+ conf_fname(conf_fname),
+ opt_val_conf(this->conf_fname) {
+ conf_idx = getopt_order.size();
+ add_opt("conf", &opt_val_conf, SET_VAL);
+ }
+
+ ~Config() {}
+
+ void add_opt(const std::string &optname, OptVal *optval, Action action);
+ bool load(const std::string &fname);
+ size_t parse(int argc, char **argv);
+};
+
+class Event {
+ public:
+ using callback_t = std::function<void(evutil_socket_t fd, short events)>;
+
+ private:
+ struct event_base *eb;
+ evutil_socket_t fd;
+ short events;
+ struct event *ev;
+ callback_t callback;
+ static inline void _then(evutil_socket_t fd, short events, void *arg) {
+ (static_cast<Event *>(arg))->callback(fd, events);
+ }
+
+ public:
+ Event(): ev(nullptr) {}
+ Event(struct event_base *eb,
+ evutil_socket_t fd,
+ short events,
+ callback_t callback):
+ eb(eb), fd(fd), events(events),
+ ev(event_new(eb, fd, events, Event::_then, this)),
+ callback(callback) {}
+ Event(Event &&other):
+ eb(other.eb), fd(other.fd), events(other.events),
+ callback(std::move(other.callback)) {
+ other.clear();
+ ev = event_new(eb, fd, events, Event::_then, this);
+ }
+ Event &operator=(Event &&other) {
+ clear();
+ other.clear();
+ eb = other.eb;
+ fd = other.fd;
+ events = other.events;
+ ev = event_new(eb, fd, events, Event::_then, this);
+ callback = std::move(other.callback);
+ return *this;
+ }
+
+ ~Event() { clear(); }
+
+ void clear() {
+ if (ev != nullptr)
+ {
+ event_del(ev);
+ event_free(ev);
+ ev = nullptr;
+ }
+ }
+
+ void add() { event_add(ev, nullptr); }
+ void del() { event_del(ev); }
+ void add_with_timeout(double timeout) {
+ event_add_with_timeout(ev, timeout);
+ }
+
+ operator bool() const { return ev != nullptr; }
+};
+
+}
+
+#endif