aboutsummaryrefslogtreecommitdiff
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
init
-rw-r--r--.gitignore16
-rw-r--r--CMakeLists.txt56
-rw-r--r--LICENSE21
-rw-r--r--cmake/Modules/FindLibevent.cmake49
-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
-rw-r--r--src/conn.cpp277
-rw-r--r--src/util.cpp244
-rw-r--r--test/.gitignore1
-rw-r--r--test/CMakeLists.txt24
-rw-r--r--test/Makefile180
-rw-r--r--test/test_msg.cpp74
19 files changed, 3010 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..475a195
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+CMakeFiles/
+cmake_install.cmake
+CMakeDoxygenDefaults.cmake
+CMakeDoxyfile.in
+CMakeCache.txt
+cmake-build-debug/
+libsalticidae.a
+src/*.swo
+src/*.swp
+*.a
+*.o
+*.la
+*.lo
+*.so
+*.gch
+/Makefile
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..5931322
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,56 @@
+# 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.
+
+cmake_minimum_required(VERSION 3.9)
+project(Salticidae)
+set(CMAKE_CXX_STANDARD 14)
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")
+
+find_package(Libevent REQUIRED)
+find_package(OpenSSL REQUIRED)
+
+include_directories(include)
+add_library(salticidae
+ src/util.cpp
+ src/conn.cpp)
+target_link_libraries(salticidae event crypto)
+
+add_subdirectory(test)
+
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+ set(CMAKE_BUILD_TYPE "Release")
+endif()
+
+if(CMAKE_BUILD_TYPE STREQUAL "Debug")
+ add_definitions(-DHOTSTUFF_DEBUG_LOG)
+elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
+ add_definitions(-DHOTSTUFF_NORMAL_LOG)
+endif()
+
+#set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -no-pie -pg")
+set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -W -Wall -Wextra -pedantic")
+
+macro(remove_cxx_flag flag)
+ string(REPLACE "${flag}" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
+endmacro()
+
+remove_cxx_flag("-DNDEBUG")
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..11712af
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+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.
diff --git a/cmake/Modules/FindLibevent.cmake b/cmake/Modules/FindLibevent.cmake
new file mode 100644
index 0000000..e874e67
--- /dev/null
+++ b/cmake/Modules/FindLibevent.cmake
@@ -0,0 +1,49 @@
+# 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.
+
+# This module will define:
+# - Libevent_FOUND - if the library is found
+# - LIBEVENT_INC - include directories
+# - LIBEVENT_LIB - the libraries required to use libevent
+
+set(LIBEVENT_PREFIXES /usr/local /opt/local)
+
+foreach(prefix ${LIBEVENT_PREFIXES})
+ list(APPEND LIBEVENT_LIB_PATH "${prefix}/lib")
+ list(APPEND LIBEVENT_INC_PATH "${prefix}/include")
+endforeach()
+
+find_library(LIBEVENT_LIB NAMES event PATHS ${LIBEVENT_LIB_PATH})
+find_path(LIBEVENT_INC event.h PATHS ${LIBEVENT_INC_PATH})
+
+if (LIBEVENT_LIB AND LIBEVENT_INC)
+ set(Libevent_FOUND TRUE)
+ if (NOT Libevent_FIND_QUIETLY)
+ message(STATUS "Found libevent: ${LIBEVENT_LIB}")
+ endif ()
+else ()
+ set(Libevent_FOUND FALSE)
+ if (Libevent_FIND_REQUIRED)
+ message(FATAL_ERROR "Could NOT find libevent.")
+ endif ()
+ message(STATUS "libevent NOT found.")
+endif ()
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
diff --git a/src/conn.cpp b/src/conn.cpp
new file mode 100644
index 0000000..b323340
--- /dev/null
+++ b/src/conn.cpp
@@ -0,0 +1,277 @@
+/**
+ * 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.
+ */
+
+#include <cstring>
+#include <cassert>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/tcp.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "salticidae/util.h"
+#include "salticidae/conn.h"
+
+namespace salticidae {
+
+ConnPool::Conn::operator std::string() const {
+ return "<Conn fd=" + std::to_string(fd) + " " +
+ "addr=" + std::string(addr).c_str() + " " +
+ "mode=" + ((mode == Conn::ACTIVE) ? "active" : "passive") +
+ ">";
+}
+
+void ConnPool::Conn::send_data(evutil_socket_t fd, short events) {
+ if (!(events & EV_WRITE)) return;
+ auto conn = self(); /* pin the connection */
+ ssize_t ret = BUFF_SEG_SIZE;
+ while (ret == BUFF_SEG_SIZE)
+ {
+ if (!send_buffer.size()) /* nothing to write */
+ break;
+ bytearray_t buff_seg = send_buffer.pop(BUFF_SEG_SIZE);
+ ssize_t size = buff_seg.size();
+ ret = send(fd, buff_seg.data(), size, MSG_NOSIGNAL);
+ SALTICIDAE_LOG_DEBUG("socket sent %d bytes", ret);
+ if (ret < size)
+ {
+ if (ret < 0) /* nothing is sent */
+ {
+ /* rewind the whole buff_seg */
+ send_buffer.push(std::move(buff_seg));
+ if (errno != EWOULDBLOCK)
+ {
+ SALTICIDAE_LOG_INFO("reason: %s", strerror(errno));
+ terminate();
+ return;
+ }
+ }
+ else
+ {
+ /* rewind the leftover */
+ bytearray_t left_over;
+ left_over.resize(size - ret);
+ memmove(left_over.data(), buff_seg.data() + ret, size - ret);
+ send_buffer.push(std::move(left_over));
+ }
+ /* wait for the next write callback */
+ ready_send = false;
+ ev_write.add();
+ return;
+ }
+ }
+ /* consumed the buffer but endpoint still seems to be writable */
+ ready_send = true;
+}
+
+void ConnPool::Conn::recv_data(evutil_socket_t fd, short events) {
+ if (!(events & EV_READ)) return;
+ auto conn = self(); /* pin the connection */
+ ssize_t ret = BUFF_SEG_SIZE;
+ while (ret == BUFF_SEG_SIZE)
+ {
+ bytearray_t buff_seg;
+ buff_seg.resize(BUFF_SEG_SIZE);
+ ret = recv(fd, buff_seg.data(), BUFF_SEG_SIZE, 0);
+ SALTICIDAE_LOG_DEBUG("socket read %d bytes", ret);
+ if (ret <= 0)
+ {
+ if (ret < 0 && errno != EWOULDBLOCK)
+ {
+ SALTICIDAE_LOG_INFO("reason: %s", strerror(errno));
+ /* connection err or half-opened connection */
+ terminate();
+ return;
+ }
+ if (ret == 0)
+ {
+ terminate();
+ return;
+ }
+
+ /* EWOULDBLOCK */
+ break;
+ }
+ buff_seg.resize(ret);
+ recv_buffer.push(std::move(buff_seg));
+ }
+ ev_read.add();
+ on_read();
+}
+
+void ConnPool::accept_client(evutil_socket_t fd, short) {
+ int client_fd;
+ struct sockaddr client_addr;
+ socklen_t addr_size = sizeof(struct sockaddr_in);
+ if ((client_fd = accept(fd, &client_addr, &addr_size)) < 0)
+ SALTICIDAE_LOG_ERROR("error while accepting the connection");
+ else
+ {
+ int one = 1;
+ if (setsockopt(client_fd, SOL_TCP, TCP_NODELAY, (const char *)&one, sizeof(one)) < 0)
+ throw ConnPoolError(std::string("setsockopt failed"));
+ if (fcntl(client_fd, F_SETFL, O_NONBLOCK) == -1)
+ throw ConnPoolError(std::string("unable to set nonblocking socket"));
+
+ NetAddr addr((struct sockaddr_in *)&client_addr);
+ conn_t conn = create_conn();
+ Conn *conn_ptr = conn;
+ conn->fd = client_fd;
+ conn->cpool = this;
+ conn->mode = Conn::PASSIVE;
+ conn->addr = addr;
+ conn->ev_read = Event(eb, client_fd, EV_READ,
+ std::bind(&Conn::recv_data, conn_ptr, _1, _2));
+ conn->ev_write = Event(eb, client_fd, EV_WRITE,
+ std::bind(&Conn::send_data, conn_ptr, _1, _2));
+ conn->ev_read.add();
+ conn->ev_write.add();
+ conn->ready_send = false;
+ add_conn(conn);
+ SALTICIDAE_LOG_INFO("created connection %s", std::string(*conn).c_str());
+ conn->on_setup();
+ }
+ ev_listen.add();
+}
+
+void ConnPool::Conn::conn_server(evutil_socket_t fd, short events) {
+ auto conn = self(); /* pin the connection */
+ if (send(fd, "", 0, MSG_NOSIGNAL) == 0)
+ {
+ ev_read = Event(cpool->eb, fd, EV_READ,
+ std::bind(&Conn::recv_data, this, _1, _2));
+ ev_write = Event(cpool->eb, fd, EV_WRITE,
+ std::bind(&Conn::send_data, this, _1, _2));
+ ev_read.add();
+ ev_write.add();
+ ev_connect.clear();
+ ready_send = false;
+ SALTICIDAE_LOG_INFO("connected to peer %s", std::string(*this).c_str());
+ on_setup();
+ }
+ else
+ {
+ if (events & EV_TIMEOUT)
+ SALTICIDAE_LOG_INFO("%s connect timeout", std::string(*this).c_str());
+ terminate();
+ return;
+ }
+}
+
+void ConnPool::init(NetAddr listen_addr) {
+ int one = 1;
+ if ((listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
+ throw ConnPoolError(std::string("cannot create socket for listening"));
+ if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&one, sizeof(one)) < 0 ||
+ setsockopt(listen_fd, SOL_TCP, TCP_NODELAY, (const char *)&one, sizeof(one)) < 0)
+ throw ConnPoolError(std::string("setsockopt failed"));
+ if (fcntl(listen_fd, F_SETFL, O_NONBLOCK) == -1)
+ throw ConnPoolError(std::string("unable to set nonblocking socket"));
+
+ struct sockaddr_in sockin;
+ memset(&sockin, 0, sizeof(struct sockaddr_in));
+ sockin.sin_family = AF_INET;
+ sockin.sin_addr.s_addr = INADDR_ANY;
+ sockin.sin_port = listen_addr.port;
+
+ if (bind(listen_fd, (struct sockaddr *)&sockin, sizeof(sockin)) < 0)
+ throw ConnPoolError(std::string("binding error"));
+ if (::listen(listen_fd, MAX_LISTEN_BACKLOG) < 0)
+ throw ConnPoolError(std::string("listen error"));
+ ev_listen = Event(eb, listen_fd, EV_READ,
+ std::bind(&ConnPool::accept_client, this, _1, _2));
+ ev_listen.add();
+ SALTICIDAE_LOG_INFO("listening to %u", ntohs(listen_addr.port));
+}
+
+void ConnPool::Conn::terminate() {
+ auto &pool = cpool->pool;
+ auto it = pool.find(fd);
+ if (it != pool.end())
+ {
+ /* temporarily pin the conn before it dies */
+ auto conn = it->second;
+ assert(conn == this);
+ pool.erase(it);
+ close();
+ /* inform the upper layer the connection will be destroyed */
+ on_teardown();
+ conn->self_ref = nullptr; /* remove the self-cycle */
+ }
+}
+
+void ConnPool::Conn::try_conn(evutil_socket_t, short) {
+ auto conn = self(); /* pin the connection */
+ struct sockaddr_in sockin;
+ memset(&sockin, 0, sizeof(struct sockaddr_in));
+ sockin.sin_family = AF_INET;
+ sockin.sin_addr.s_addr = addr.ip;
+ sockin.sin_port = addr.port;
+
+ if (connect(fd, (struct sockaddr *)&sockin,
+ sizeof(struct sockaddr_in)) < 0 && errno != EINPROGRESS)
+ {
+ SALTICIDAE_LOG_INFO("cannot connect to %s", std::string(addr).c_str());
+ terminate();
+ return;
+ }
+ ev_connect = Event(cpool->eb, fd, EV_WRITE,
+ std::bind(&Conn::conn_server, this, _1, _2));
+ ev_connect.add_with_timeout(CONN_SERVER_TIMEOUT);
+}
+
+ConnPool::conn_t ConnPool::create_conn(const NetAddr &addr) {
+ int fd;
+ int one = 1;
+ if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
+ throw ConnPoolError(std::string("cannot create socket for remote"));
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&one, sizeof(one)) < 0 ||
+ setsockopt(fd, SOL_TCP, TCP_NODELAY, (const char *)&one, sizeof(one)) < 0)
+ throw ConnPoolError(std::string("setsockopt failed"));
+ if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1)
+ throw ConnPoolError(std::string("unable to set nonblocking socket"));
+ conn_t conn = create_conn();
+ Conn * conn_ptr = conn;
+ conn->fd = fd;
+ conn->cpool = this;
+ conn->mode = Conn::ACTIVE;
+ conn->addr = addr;
+ conn->ev_connect = Event(eb, -1, 0, std::bind(&Conn::try_conn, conn_ptr, _1, _2));
+ conn->ev_connect.add_with_timeout(gen_rand_timeout(TRY_CONN_DELAY));
+ add_conn(conn);
+ SALTICIDAE_LOG_INFO("created connection %s", std::string(*conn).c_str());
+ return conn;
+}
+
+ConnPool::conn_t ConnPool::add_conn(conn_t conn) {
+ auto it = pool.find(conn->fd);
+ if (it != pool.end())
+ {
+ auto old_conn = it->second;
+ old_conn->terminate();
+ }
+ return pool.insert(std::make_pair(conn->fd, conn)).first->second;
+}
+
+}
diff --git a/src/util.cpp b/src/util.cpp
new file mode 100644
index 0000000..8ce908d
--- /dev/null
+++ b/src/util.cpp
@@ -0,0 +1,244 @@
+/**
+ * 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.
+ */
+
+#include <cstdarg>
+#include <cstring>
+#include <cassert>
+#include <cstdio>
+#include <ctime>
+#include <sys/time.h>
+#include <cmath>
+#include <event2/event.h>
+
+#include "salticidae/util.h"
+
+namespace salticidae {
+
+void sec2tv(double t, struct timeval &tv) {
+ tv.tv_sec = trunc(t);
+ tv.tv_usec = trunc((t - tv.tv_sec) * 1e6);
+}
+
+void event_add_with_timeout(struct event *ev, double timeout) {
+ struct timeval tv;
+ tv.tv_sec = trunc(timeout);
+ tv.tv_usec = trunc((timeout - tv.tv_sec) * 1e6);
+ event_add(ev, &tv);
+}
+
+const std::string get_current_datetime() {
+ /* credit: http://stackoverflow.com/a/41381479/544806 */
+ char fmt[64], buf[64];
+ struct timeval tv;
+ gettimeofday(&tv, nullptr);
+ struct tm *tmp = localtime(&tv.tv_sec);
+ strftime(fmt, sizeof fmt, "%Y-%m-%d %H:%M:%S.%%06u", tmp);
+ snprintf(buf, sizeof buf, fmt, tv.tv_usec);
+ return std::string(buf);
+}
+
+SalticidaeError::SalticidaeError() : msg("unknown") {}
+
+SalticidaeError::SalticidaeError(const std::string &fmt, ...) {
+ size_t guessed_size = 128;
+ std::string buff;
+ va_list ap;
+ for (;;)
+ {
+ buff.resize(guessed_size);
+ va_start(ap, fmt);
+ int nwrote = vsnprintf((char *)buff.data(), guessed_size, fmt.c_str(), ap);
+ if (nwrote < 0 || nwrote == guessed_size)
+ {
+ guessed_size <<= 1;
+ continue;
+ }
+ buff.resize(nwrote);
+ msg = std::move(buff);
+ break;
+ }
+}
+
+SalticidaeError::operator std::string() const {
+ return msg;
+}
+
+void Logger::write(const char *tag, const char *fmt, va_list ap) {
+ fprintf(output, "%s [%s] ", get_current_datetime().c_str(), tag);
+ vfprintf(output, fmt, ap);
+ fprintf(output, "\n");
+}
+
+void Logger::debug(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ write("debug", fmt, ap);
+ va_end(ap);
+}
+
+void Logger::info(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ write("salticidae info", fmt, ap);
+ va_end(ap);
+}
+
+void Logger::warning(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ write("salticidae warn", fmt, ap);
+ va_end(ap);
+}
+void Logger::error(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ write("salticida error", fmt, ap);
+ va_end(ap);
+}
+
+Logger logger;
+
+void ElapsedTime::start() {
+ struct timezone tz;
+ gettimeofday(&t0, &tz);
+ cpu_t0 = clock();
+}
+
+void ElapsedTime::stop(bool show_info) {
+ struct timeval t1;
+ struct timezone tz;
+ gettimeofday(&t1, &tz);
+ cpu_elapsed_sec = (float)clock() / CLOCKS_PER_SEC -
+ (float)cpu_t0 / CLOCKS_PER_SEC;
+ elapsed_sec = (t1.tv_sec + t1.tv_usec * 1e-6) -
+ (t0.tv_sec + t0.tv_usec * 1e-6);
+ if (show_info)
+ SALTICIDAE_LOG_INFO("elapsed: %.3f (wall) %.3f (cpu)",
+ elapsed_sec, cpu_elapsed_sec);
+}
+
+Config::Opt::Opt(const std::string &optname, OptVal *optval, Action action, int idx): \
+ optname(optname), optval(optval), action(action) {
+ opt.name = this->optname.c_str();
+ opt.has_arg = action == SWITCH_ON ? no_argument : required_argument;
+ opt.flag = nullptr;
+ opt.val = idx;
+}
+
+void Config::add_opt(const std::string &optname, OptVal *optval, Action action) {
+ if (conf.count(optname))
+ throw SalticidaeError("option name already exists");
+ auto it = conf.insert(
+ std::make_pair(optname,
+ Opt(optname, optval, action, getopt_order.size()))).first;
+ getopt_order.push_back(&it->second);
+}
+
+std::string trim(const std::string &str,
+ const std::string &space = "\t\r\n ") {
+ const auto new_begin = str.find_first_not_of(space);
+ if (new_begin == std::string::npos)
+ return "";
+ const auto new_end = str.find_last_not_of(space);
+ return str.substr(new_begin, new_end - new_begin + 1);
+}
+
+void Config::update(Opt &p, const char *optval) {
+ switch (p.action)
+ {
+ case SWITCH_ON: p.optval->switch_on(); break;
+ case SET_VAL: p.optval->set_val(optval); break;
+ case APPEND: p.optval->append(optval); break;
+ default:
+ throw SalticidaeError("unknown action");
+ }
+}
+
+void Config::update(const std::string &optname, const char *optval) {
+ assert(conf.count(optname));
+ update(conf.find(optname)->second, optval);
+}
+
+bool Config::load(const std::string &fname) {
+ static const size_t BUFF_SIZE = 1024;
+ FILE *conf_f = fopen(fname.c_str(), "r");
+ char buff[BUFF_SIZE];
+ /* load configuration from file */
+ if (conf_f)
+ {
+ while (fgets(buff, BUFF_SIZE, conf_f))
+ {
+ if (strlen(buff) == BUFF_SIZE - 1)
+ {
+ fclose(conf_f);
+ throw SalticidaeError("configuration file line too long");
+ }
+ std::string line(buff);
+ size_t pos = line.find("=");
+ if (pos == std::string::npos)
+ continue;
+ std::string optname = trim(line.substr(0, pos));
+ std::string optval = trim(line.substr(pos + 1));
+ if (!conf.count(optname))
+ {
+ SALTICIDAE_LOG_WARN("ignoring option name in conf file: %s",
+ optname.c_str());
+ continue;
+ }
+ update(optname, optval.c_str());
+ }
+ return true;
+ }
+ else
+ return false;
+}
+
+size_t Config::parse(int argc, char **argv) {
+ if (load(conf_fname))
+ SALTICIDAE_LOG_INFO("loaded configuration from %s", conf_fname.c_str());
+
+ size_t nopts = getopt_order.size();
+ struct option *longopts = (struct option *)malloc(
+ sizeof(struct option) * (nopts + 1));
+ int ind;
+ for (size_t i = 0; i < nopts; i++)
+ longopts[i] = getopt_order[i]->opt;
+ longopts[nopts] = {0, 0, 0, 0};
+ for (;;)
+ {
+ int id = getopt_long(argc, argv, "", longopts, &ind);
+ if (id == -1 || id == '?') break;
+ update(*getopt_order[id], optarg);
+ if (id == conf_idx)
+ {
+ if (load(conf_fname))
+ SALTICIDAE_LOG_INFO("load configuration from %s", conf_fname.c_str());
+ else
+ SALTICIDAE_LOG_INFO("configuration file %s not found", conf_fname.c_str());
+ }
+ }
+ return optind;
+}
+
+}
diff --git a/test/.gitignore b/test/.gitignore
new file mode 100644
index 0000000..4cdad0d
--- /dev/null
+++ b/test/.gitignore
@@ -0,0 +1 @@
+test_msg
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
new file mode 100644
index 0000000..423229b
--- /dev/null
+++ b/test/CMakeLists.txt
@@ -0,0 +1,24 @@
+# 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.
+
+add_executable(test_msg test_msg.cpp)
+target_link_libraries(test_msg salticidae)
diff --git a/test/Makefile b/test/Makefile
new file mode 100644
index 0000000..173f653
--- /dev/null
+++ b/test/Makefile
@@ -0,0 +1,180 @@
+# CMAKE generated file: DO NOT EDIT!
+# Generated by "Unix Makefiles" Generator, CMake Version 3.11
+
+# Default target executed when no arguments are given to make.
+default_target: all
+
+.PHONY : default_target
+
+# Allow only one "make -f Makefile2" at a time, but pass parallelism.
+.NOTPARALLEL:
+
+
+#=============================================================================
+# Special targets provided by cmake.
+
+# Disable implicit rules so canonical targets will work.
+.SUFFIXES:
+
+
+# Remove some rules from gmake that .SUFFIXES does not remove.
+SUFFIXES =
+
+.SUFFIXES: .hpux_make_needs_suffix_list
+
+
+# Suppress display of executed commands.
+$(VERBOSE).SILENT:
+
+
+# A target that is always out of date.
+cmake_force:
+
+.PHONY : cmake_force
+
+#=============================================================================
+# Set environment variables for the build.
+
+# The shell in which to execute make rules.
+SHELL = /bin/sh
+
+# The CMake executable.
+CMAKE_COMMAND = /usr/bin/cmake
+
+# The command to remove a file.
+RM = /usr/bin/cmake -E remove -f
+
+# Escaping for special characters.
+EQUALS = =
+
+# The top-level source directory on which CMake was run.
+CMAKE_SOURCE_DIR = /home/ymf/work/hot-stuff/code/salticidae
+
+# The top-level build directory on which CMake was run.
+CMAKE_BINARY_DIR = /home/ymf/work/hot-stuff/code/salticidae
+
+#=============================================================================
+# Targets provided globally by CMake.
+
+# Special rule for the target rebuild_cache
+rebuild_cache:
+ @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake to regenerate build system..."
+ /usr/bin/cmake -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR)
+.PHONY : rebuild_cache
+
+# Special rule for the target rebuild_cache
+rebuild_cache/fast: rebuild_cache
+
+.PHONY : rebuild_cache/fast
+
+# Special rule for the target edit_cache
+edit_cache:
+ @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake cache editor..."
+ /usr/bin/ccmake -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR)
+.PHONY : edit_cache
+
+# Special rule for the target edit_cache
+edit_cache/fast: edit_cache
+
+.PHONY : edit_cache/fast
+
+# The main all target
+all: cmake_check_build_system
+ cd /home/ymf/work/hot-stuff/code/salticidae && $(CMAKE_COMMAND) -E cmake_progress_start /home/ymf/work/hot-stuff/code/salticidae/CMakeFiles /home/ymf/work/hot-stuff/code/salticidae/test/CMakeFiles/progress.marks
+ cd /home/ymf/work/hot-stuff/code/salticidae && $(MAKE) -f CMakeFiles/Makefile2 test/all
+ $(CMAKE_COMMAND) -E cmake_progress_start /home/ymf/work/hot-stuff/code/salticidae/CMakeFiles 0
+.PHONY : all
+
+# The main clean target
+clean:
+ cd /home/ymf/work/hot-stuff/code/salticidae && $(MAKE) -f CMakeFiles/Makefile2 test/clean
+.PHONY : clean
+
+# The main clean target
+clean/fast: clean
+
+.PHONY : clean/fast
+
+# Prepare targets for installation.
+preinstall: all
+ cd /home/ymf/work/hot-stuff/code/salticidae && $(MAKE) -f CMakeFiles/Makefile2 test/preinstall
+.PHONY : preinstall
+
+# Prepare targets for installation.
+preinstall/fast:
+ cd /home/ymf/work/hot-stuff/code/salticidae && $(MAKE) -f CMakeFiles/Makefile2 test/preinstall
+.PHONY : preinstall/fast
+
+# clear depends
+depend:
+ cd /home/ymf/work/hot-stuff/code/salticidae && $(CMAKE_COMMAND) -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1
+.PHONY : depend
+
+# Convenience name for target.
+test/CMakeFiles/test_msg.dir/rule:
+ cd /home/ymf/work/hot-stuff/code/salticidae && $(MAKE) -f CMakeFiles/Makefile2 test/CMakeFiles/test_msg.dir/rule
+.PHONY : test/CMakeFiles/test_msg.dir/rule
+
+# Convenience name for target.
+test_msg: test/CMakeFiles/test_msg.dir/rule
+
+.PHONY : test_msg
+
+# fast build rule for target.
+test_msg/fast:
+ cd /home/ymf/work/hot-stuff/code/salticidae && $(MAKE) -f test/CMakeFiles/test_msg.dir/build.make test/CMakeFiles/test_msg.dir/build
+.PHONY : test_msg/fast
+
+test_msg.o: test_msg.cpp.o
+
+.PHONY : test_msg.o
+
+# target to build an object file
+test_msg.cpp.o:
+ cd /home/ymf/work/hot-stuff/code/salticidae && $(MAKE) -f test/CMakeFiles/test_msg.dir/build.make test/CMakeFiles/test_msg.dir/test_msg.cpp.o
+.PHONY : test_msg.cpp.o
+
+test_msg.i: test_msg.cpp.i
+
+.PHONY : test_msg.i
+
+# target to preprocess a source file
+test_msg.cpp.i:
+ cd /home/ymf/work/hot-stuff/code/salticidae && $(MAKE) -f test/CMakeFiles/test_msg.dir/build.make test/CMakeFiles/test_msg.dir/test_msg.cpp.i
+.PHONY : test_msg.cpp.i
+
+test_msg.s: test_msg.cpp.s
+
+.PHONY : test_msg.s
+
+# target to generate assembly for a file
+test_msg.cpp.s:
+ cd /home/ymf/work/hot-stuff/code/salticidae && $(MAKE) -f test/CMakeFiles/test_msg.dir/build.make test/CMakeFiles/test_msg.dir/test_msg.cpp.s
+.PHONY : test_msg.cpp.s
+
+# Help Target
+help:
+ @echo "The following are some of the valid targets for this Makefile:"
+ @echo "... all (the default if no target is provided)"
+ @echo "... clean"
+ @echo "... depend"
+ @echo "... rebuild_cache"
+ @echo "... test_msg"
+ @echo "... edit_cache"
+ @echo "... test_msg.o"
+ @echo "... test_msg.i"
+ @echo "... test_msg.s"
+.PHONY : help
+
+
+
+#=============================================================================
+# Special targets to cleanup operation of make.
+
+# Special rule to run CMake to check the build system integrity.
+# No rule that depends on this can have commands that come from listfiles
+# because they might be regenerated.
+cmake_check_build_system:
+ cd /home/ymf/work/hot-stuff/code/salticidae && $(CMAKE_COMMAND) -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0
+.PHONY : cmake_check_build_system
+
diff --git a/test/test_msg.cpp b/test/test_msg.cpp
new file mode 100644
index 0000000..2d039e4
--- /dev/null
+++ b/test/test_msg.cpp
@@ -0,0 +1,74 @@
+/**
+ * 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.
+ */
+
+#include "salticidae/msg.h"
+
+using salticidae::uint256_t;
+using salticidae::DataStream;
+using salticidae::get_hash;
+using salticidae::get_hex;
+
+struct MsgTest: public salticidae::MsgBase<> {
+ void gen_testhashes(int cnt) {
+ DataStream s;
+ set_opcode(0x01);
+ s << (uint32_t)cnt;
+ for (int i = 0; i < cnt; i++)
+ {
+ uint256_t hash = get_hash(i);
+ printf("adding hash %s\n", get_hex(hash).c_str());
+ s << hash;
+ }
+ set_payload(std::move(s));
+ }
+
+ void parse_testhashes() {
+ DataStream s(get_payload());
+ uint32_t cnt;
+ s >> cnt;
+ printf("got %d hashes\n", cnt);
+ for (int i = 0; i < cnt; i++)
+ {
+ uint256_t hash;
+ s >> hash;
+ printf("got hash %s\n", get_hex(hash).c_str());
+ }
+ }
+};
+
+int main() {
+ MsgTest msg;
+ msg.gen_ping(1234);
+ printf("%s\n", std::string(msg).c_str());
+ msg.gen_testhashes(5);
+ printf("%s\n", std::string(msg).c_str());
+ msg.parse_testhashes();
+ try
+ {
+ msg.parse_testhashes();
+ } catch (std::runtime_error &e) {
+ printf("caught: %s\n", e.what());
+ }
+ return 0;
+}