From 9d23fe58052b3ef971305699a00ea6dbd1111744 Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Sat, 12 Jun 2021 15:50:29 +0200 Subject: [PATCH 1/1] Get the full history from the main meshlink repository. --- .astylerc | 10 + .gitignore | 36 + .gitmodules | 0 AUTHORS | 1 + COPYING | 288 + COPYING.README | 116 + Doxyfile | 14 + Makefile.am | 30 + NEWS | 3 + README | 42 + README.android | 15 + README.git | 21 + README.meshlink | 6 + TODO | 41 + configure.ac | 169 + doc/.gitignore | 6 + doc/Configuration.md | 97 + doc/SPTPS | 170 + examples/.gitignore | 8 + examples/Makefile.am | 28 + examples/channels.c | 336 ++ examples/chat.c | 279 + examples/chatpp.cc | 242 + examples/fixlib.txt | 3 + examples/groupchat.c | 492 ++ examples/manynodes.c | 504 ++ examples/meshlinkapp.c | 60 + examples/monitor.c | 248 + m4/.gitignore | 5 + m4/attribute.m4 | 25 + m4/ax_check_compile_flag.m4 | 72 + m4/ax_check_link_flag.m4 | 71 + m4/ax_prog_doxygen.m4 | 586 ++ m4/ax_pthread.m4 | 485 ++ src/.gitignore | 1 + src/Makefile.am | 99 + src/adns.c | 232 + src/adns.h | 32 + src/buffer.c | 122 + src/buffer.h | 37 + src/chacha-poly1305/chacha-poly1305.c | 181 + src/chacha-poly1305/chacha-poly1305.h | 19 + src/chacha-poly1305/chacha.c | 223 + src/chacha-poly1305/chacha.h | 25 + src/chacha-poly1305/poly1305.c | 197 + src/chacha-poly1305/poly1305.h | 16 + src/conf.c | 1115 ++++ src/conf.h | 62 + src/connection.c | 91 + src/connection.h | 90 + src/crypto.c | 99 + src/crypto.h | 27 + src/devtools.c | 383 ++ src/devtools.h | 212 + src/discovery.c | 1063 ++++ src/discovery.h | 30 + src/dropin.c | 59 + src/dropin.h | 40 + src/ecdh.h | 34 + src/ecdsa.h | 41 + src/ecdsagen.h | 29 + src/ed25519/add_scalar.c | 56 + src/ed25519/ecdh.c | 51 + src/ed25519/ecdsa.c | 130 + src/ed25519/ecdsagen.c | 56 + src/ed25519/ed25519.h | 38 + src/ed25519/fe.c | 1491 +++++ src/ed25519/fe.h | 41 + src/ed25519/fixedint.h | 70 + src/ed25519/ge.c | 467 ++ src/ed25519/ge.h | 74 + src/ed25519/key_exchange.c | 79 + src/ed25519/keypair.c | 16 + src/ed25519/precomp_data.h | 1391 +++++ src/ed25519/sc.c | 809 +++ src/ed25519/sc.h | 12 + src/ed25519/seed.c | 44 + src/ed25519/sha512.c | 282 + src/ed25519/sha512.h | 20 + src/ed25519/sign.c | 31 + src/ed25519/verify.c | 77 + src/edge.c | 115 + src/edge.h | 50 + src/event.c | 484 ++ src/event.h | 105 + src/graph.c | 231 + src/graph.h | 25 + src/hash.c | 121 + src/hash.h | 41 + src/have.h | 133 + src/list.c | 176 + src/list.h | 78 + src/logger.c | 56 + src/logger.h | 27 + src/mdns.c | 441 ++ src/mdns.h | 14 + src/meshlink++.h | 1378 +++++ src/meshlink.c | 4939 +++++++++++++++++ src/meshlink.h | 1868 +++++++ src/meshlink.sym | 113 + src/meshlink_internal.h | 262 + src/meshlink_queue.h | 120 + src/meta.c | 175 + src/meta.h | 33 + src/net.c | 746 +++ src/net.h | 120 + src/net_packet.c | 637 +++ src/net_setup.c | 671 +++ src/net_socket.c | 605 ++ src/netutl.c | 360 ++ src/netutl.h | 41 + src/node.c | 175 + src/node.h | 113 + src/packmsg.h | 2044 +++++++ src/prf.c | 132 + src/prf.h | 25 + src/protocol.c | 259 + src/protocol.h | 116 + src/protocol_auth.c | 454 ++ src/protocol_edge.c | 384 ++ src/protocol_key.c | 494 ++ src/protocol_misc.c | 147 + src/route.c | 92 + src/route.h | 28 + src/sockaddr.h | 28 + src/splay_tree.c | 633 +++ src/splay_tree.h | 103 + src/sptps.c | 759 +++ src/sptps.h | 100 + src/submesh.c | 138 + src/submesh.h | 41 + src/system.h | 35 + src/utcp-test.c | 410 ++ src/utcp.c | 2574 +++++++++ src/utcp.h | 128 + src/utcp_priv.h | 202 + src/utils.c | 206 + src/utils.h | 50 + src/xalloc.h | 96 + src/xoshiro.c | 42 + src/xoshiro.h | 6 + test/.gitignore | 18 + test/Makefile.am | 160 + test/api_set_node_status_cb.c | 58 + test/basic.c | 155 + test/basicpp.cpp | 77 + test/blackbox/.gitignore | 5 + test/blackbox/Makefile.am | 25 + test/blackbox/common/common_handlers.c | 189 + test/blackbox/common/common_handlers.h | 53 + test/blackbox/common/common_types.h | 58 + test/blackbox/common/containers.c | 1134 ++++ test/blackbox/common/containers.h | 84 + test/blackbox/common/mesh_event_handler.c | 290 + test/blackbox/common/mesh_event_handler.h | 156 + .../common/network_namespace_framework.c | 569 ++ .../common/network_namespace_framework.h | 123 + test/blackbox/common/tcpdump.c | 68 + test/blackbox/common/tcpdump.h | 29 + test/blackbox/common/test_step.c | 140 + test/blackbox/common/test_step.h | 33 + test/blackbox/run_blackbox_tests/.gitignore | 1 + test/blackbox/run_blackbox_tests/Makefile.am | 73 + .../run_blackbox_tests/execute_tests.c | 130 + .../run_blackbox_tests/execute_tests.h | 70 + .../run_blackbox_tests/run_blackbox_tests.c | 169 + test/blackbox/run_blackbox_tests/test_cases.c | 551 ++ test/blackbox/run_blackbox_tests/test_cases.h | 29 + .../run_blackbox_tests/test_cases_add_addr.c | 172 + .../run_blackbox_tests/test_cases_add_addr.h | 29 + .../test_cases_add_ex_addr.c | 134 + .../test_cases_add_ex_addr.h | 29 + .../test_cases_autoconnect.c | 160 + .../test_cases_autoconnect.h | 28 + .../run_blackbox_tests/test_cases_blacklist.c | 233 + .../run_blackbox_tests/test_cases_blacklist.h | 28 + .../test_cases_channel_blacklist.c | 210 + .../test_cases_channel_blacklist.h | 43 + .../test_cases_channel_close.c | 147 + .../test_cases_channel_close.h | 28 + .../test_cases_channel_conn.c | 802 +++ .../test_cases_channel_conn.h | 29 + .../test_cases_channel_ex.c | 664 +++ .../test_cases_channel_ex.h | 28 + .../test_cases_channel_get_flags.c | 202 + .../test_cases_channel_get_flags.h | 28 + .../test_cases_channel_open.c | 259 + .../test_cases_channel_open.h | 28 + .../test_cases_channel_send.c | 365 ++ .../test_cases_channel_send.h | 31 + .../test_cases_channel_set_accept_cb.c | 271 + .../test_cases_channel_set_accept_cb.h | 28 + .../test_cases_channel_set_poll_cb.c | 519 ++ .../test_cases_channel_set_poll_cb.h | 28 + .../test_cases_channel_set_receive_cb.c | 261 + .../test_cases_channel_set_receive_cb.h | 28 + .../test_cases_channel_shutdown.c | 348 ++ .../test_cases_channel_shutdown.h | 28 + .../test_cases_default_blacklist.c | 207 + .../test_cases_default_blacklist.h | 28 + .../run_blackbox_tests/test_cases_destroy.c | 154 + .../run_blackbox_tests/test_cases_destroy.h | 28 + .../run_blackbox_tests/test_cases_export.c | 118 + .../run_blackbox_tests/test_cases_export.h | 28 + .../test_cases_get_all_nodes.c | 182 + .../test_cases_get_all_nodes.h | 28 + .../test_cases_get_all_nodes_by_dev_class.c | 344 ++ .../test_cases_get_all_nodes_by_dev_class.h | 29 + .../test_cases_get_ex_addr.c | 147 + .../test_cases_get_ex_addr.h | 28 + .../test_cases_get_fingerprint.c | 180 + .../test_cases_get_fingerprint.h | 28 + .../run_blackbox_tests/test_cases_get_node.c | 209 + .../run_blackbox_tests/test_cases_get_node.h | 28 + .../test_cases_get_node_reachability.c | 960 ++++ .../test_cases_get_node_reachability.h | 26 + .../run_blackbox_tests/test_cases_get_port.c | 110 + .../run_blackbox_tests/test_cases_get_port.h | 28 + .../run_blackbox_tests/test_cases_get_self.c | 115 + .../run_blackbox_tests/test_cases_get_self.h | 28 + .../test_cases_hint_address.c | 142 + .../test_cases_hint_address.h | 28 + .../run_blackbox_tests/test_cases_import.c | 317 ++ .../run_blackbox_tests/test_cases_import.h | 28 + .../run_blackbox_tests/test_cases_invite.c | 287 + .../run_blackbox_tests/test_cases_invite.h | 28 + .../run_blackbox_tests/test_cases_join.c | 788 +++ .../run_blackbox_tests/test_cases_join.h | 29 + .../test_cases_key_rotation.c | 503 ++ .../test_cases_key_rotation.h | 26 + .../run_blackbox_tests/test_cases_open.c | 355 ++ .../run_blackbox_tests/test_cases_open.h | 28 + .../run_blackbox_tests/test_cases_pmtu.c | 159 + .../run_blackbox_tests/test_cases_pmtu.h | 28 + .../test_cases_random_port_bindings01.c | 302 + .../test_cases_random_port_bindings01.h | 28 + .../test_cases_random_port_bindings02.c | 435 ++ .../test_cases_random_port_bindings02.h | 28 + .../run_blackbox_tests/test_cases_rec_cb.c | 212 + .../run_blackbox_tests/test_cases_rec_cb.h | 28 + .../run_blackbox_tests/test_cases_send.c | 179 + .../run_blackbox_tests/test_cases_send.h | 29 + .../test_cases_set_connection_try_cb.c | 152 + .../test_cases_set_connection_try_cb.h | 26 + .../test_cases_set_log_cb.c | 147 + .../test_cases_set_log_cb.h | 28 + .../run_blackbox_tests/test_cases_set_port.c | 305 + .../run_blackbox_tests/test_cases_set_port.h | 28 + .../run_blackbox_tests/test_cases_sign.c | 404 ++ .../run_blackbox_tests/test_cases_sign.h | 28 + .../run_blackbox_tests/test_cases_start.c | 131 + .../run_blackbox_tests/test_cases_start.h | 30 + .../run_blackbox_tests/test_cases_status_cb.c | 179 + .../run_blackbox_tests/test_cases_status_cb.h | 28 + .../test_cases_stop_close.c | 94 + .../test_cases_stop_close.h | 29 + .../run_blackbox_tests/test_cases_submesh01.c | 186 + .../run_blackbox_tests/test_cases_submesh01.h | 30 + .../run_blackbox_tests/test_cases_submesh02.c | 189 + .../run_blackbox_tests/test_cases_submesh02.h | 30 + .../run_blackbox_tests/test_cases_submesh03.c | 184 + .../run_blackbox_tests/test_cases_submesh03.h | 30 + .../run_blackbox_tests/test_cases_submesh04.c | 163 + .../run_blackbox_tests/test_cases_submesh04.h | 30 + .../run_blackbox_tests/test_cases_verify.c | 384 ++ .../run_blackbox_tests/test_cases_verify.h | 29 + .../run_blackbox_tests/test_cases_whitelist.c | 332 ++ .../run_blackbox_tests/test_cases_whitelist.h | 28 + .../run_blackbox_tests/test_optimal_pmtu.c | 651 +++ .../run_blackbox_tests/test_optimal_pmtu.h | 62 + .../node_sim_nut_01.c | 224 + .../node_sim_nut_01.h | 31 + .../node_sim_peer_01.c | 109 + .../node_sim_relay_01.c | 49 + .../test_case_channel_conn_01/Makefile.am | 9 + .../test_case_channel_conn_01/node_sim_nut.c | 168 + .../test_case_channel_conn_01/node_sim_peer.c | 127 + .../test_case_channel_conn_02/Makefile.am | 9 + .../test_case_channel_conn_02/node_sim_nut.c | 170 + .../test_case_channel_conn_02/node_sim_peer.c | 113 + .../test_case_channel_conn_03/Makefile.am | 9 + .../test_case_channel_conn_03/node_sim_nut.c | 174 + .../test_case_channel_conn_03/node_sim_peer.c | 115 + .../test_case_channel_conn_04/Makefile.am | 9 + .../test_case_channel_conn_04/node_sim_nut.c | 167 + .../test_case_channel_conn_04/node_sim_peer.c | 137 + .../test_case_channel_conn_05/Makefile.am | 13 + .../test_case_channel_conn_05/node_sim_nut.c | 168 + .../test_case_channel_conn_05/node_sim_peer.c | 127 + .../node_sim_relay.c | 59 + .../test_case_channel_conn_06/Makefile.am | 13 + .../test_case_channel_conn_06/node_sim_nut.c | 174 + .../test_case_channel_conn_06/node_sim_peer.c | 115 + .../node_sim_relay.c | 59 + .../test_case_channel_conn_07/Makefile.am | 13 + .../test_case_channel_conn_07/node_sim_nut.c | 180 + .../test_case_channel_conn_07/node_sim_peer.c | 115 + .../node_sim_relay.c | 59 + .../test_case_channel_conn_08/Makefile.am | 13 + .../test_case_channel_conn_08/node_sim_nut.c | 167 + .../test_case_channel_conn_08/node_sim_peer.c | 137 + .../node_sim_relay.c | 59 + .../test_case_meta_conn_01/Makefile.am | 13 + .../test_case_meta_conn_01/node_sim_nut.c | 133 + .../test_case_meta_conn_01/node_sim_peer.c | 64 + .../test_case_meta_conn_01/node_sim_relay.c | 60 + .../test_case_meta_conn_01/test/node_step.sh | 25 + .../test_case_meta_conn_02/Makefile.am | 13 + .../test_case_meta_conn_02/node_sim_nut.c | 118 + .../test_case_meta_conn_02/node_sim_peer.c | 70 + .../test_case_meta_conn_02/node_sim_relay.c | 66 + .../test_case_meta_conn_03/Makefile.am | 13 + .../test_case_meta_conn_03/node_sim_nut.c | 131 + .../test_case_meta_conn_03/node_sim_peer.c | 70 + .../test_case_meta_conn_03/node_sim_relay.c | 66 + .../test_case_meta_conn_04/Makefile.am | 13 + .../test_case_meta_conn_04/node_sim_nut.c | 130 + .../test_case_meta_conn_04/node_sim_peer.c | 70 + .../test_case_meta_conn_04/node_sim_relay.c | 63 + .../test_case_meta_conn_05/Makefile.am | 13 + .../test_case_meta_conn_05/node_sim_nut.c | 147 + .../test_case_meta_conn_05/node_sim_peer.c | 70 + .../test_case_meta_conn_05/node_sim_relay.c | 63 + .../test_case_optimal_pmtu_01/node_sim_nut.c | 304 + .../test_case_optimal_pmtu_01/node_sim_peer.c | 133 + .../node_sim_relay.c | 64 + .../test_case_optimal_pmtu.h | 22 + .../test_case_optimal_pmtu_02/Makefile.am | 13 + .../test_case_optimal_pmtu_03/Makefile.am | 13 + .../test_case_optimal_pmtu_04/Makefile.am | 13 + .../test_case_optimal_pmtu_05/Makefile.am | 13 + .../test_case_optimal_pmtu_06/Makefile.am | 13 + .../test_case_optimal_pmtu_07/Makefile.am | 13 + .../test_case_optimal_pmtu_07/node_sim_nut.c | 330 ++ .../test_case_optimal_pmtu_07/node_sim_peer.c | 168 + .../node_sim_relay.c | 59 + .../blackbox/test_cases_submesh01/Makefile.am | 25 + .../test_cases_submesh01/node_sim_app1node1.c | 231 + .../test_cases_submesh01/node_sim_app1node2.c | 255 + .../test_cases_submesh01/node_sim_app2node1.c | 230 + .../test_cases_submesh01/node_sim_app2node2.c | 255 + .../test_cases_submesh01/node_sim_corenode1.c | 190 + .../test_cases_submesh01/node_sim_corenode2.c | 212 + .../blackbox/test_cases_submesh02/Makefile.am | 25 + .../test_cases_submesh02/node_sim_app1node1.c | 231 + .../test_cases_submesh02/node_sim_app1node2.c | 298 + .../test_cases_submesh02/node_sim_app2node1.c | 230 + .../test_cases_submesh02/node_sim_app2node2.c | 301 + .../test_cases_submesh02/node_sim_corenode1.c | 190 + .../test_cases_submesh02/node_sim_corenode2.c | 212 + .../blackbox/test_cases_submesh03/Makefile.am | 13 + .../test_cases_submesh03/node_sim_app1node1.c | 231 + .../test_cases_submesh03/node_sim_app1node2.c | 267 + .../test_cases_submesh03/node_sim_corenode1.c | 190 + .../blackbox/test_cases_submesh04/Makefile.am | 13 + .../test_cases_submesh04/node_sim_app1node1.c | 231 + .../test_cases_submesh04/node_sim_app1node2.c | 299 + .../test_cases_submesh04/node_sim_corenode1.c | 190 + test/blackbox/util/build_container.sh | 97 + test/blackbox/util/gen_invite.c | 51 + test/blackbox/util/install_node_sim_copy.sh | 3 + test/blackbox/util/install_packages.sh | 55 + test/blackbox/util/lxc_copy_dir.sh | 27 + test/blackbox/util/lxc_copy_file.sh | 25 + test/blackbox/util/lxc_rename.sh | 29 + test/blackbox/util/lxc_run.sh | 26 + test/blackbox/util/nat.sh | 130 + test/blackbox/util/nat_destroy.sh | 16 + test/blackbox/util/node_step.sh | 25 + test/blacklist.c | 257 + test/channels-aio-abort.c | 125 + test/channels-aio-cornercases.c | 201 + test/channels-aio-fd.c | 179 + test/channels-aio.c | 200 + test/channels-buffer-storage.c | 165 + test/channels-cornercases.c | 152 + test/channels-failure.c | 157 + test/channels-fork.c | 199 + test/channels-no-partial.c | 92 + test/channels-udp-cornercases.c | 174 + test/channels-udp.c | 188 + test/channels.c | 102 + test/duplicate.c | 78 + test/echo-fork.c | 189 + test/encrypted.c | 55 + test/ephemeral.c | 69 + test/get-all-nodes.c | 220 + test/import-export.c | 126 + test/invite-join.c | 429 ++ test/meta-connections.c | 89 + test/netns_utils.c | 148 + test/netns_utils.h | 17 + test/run_blackbox_tests.sh | 11 + test/sign-verify.c | 51 + test/storage-policy.c | 211 + test/stream.c | 157 + test/trio.c | 184 + test/trio2.c | 149 + test/utcp-benchmark | 80 + test/utcp-benchmark-stream | 85 + test/utils.c | 192 + test/utils.h | 62 + 402 files changed, 74330 insertions(+) create mode 100644 .astylerc create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 COPYING.README create mode 100644 Doxyfile create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 README.android create mode 100644 README.git create mode 100644 README.meshlink create mode 100644 TODO create mode 100644 configure.ac create mode 100644 doc/.gitignore create mode 100644 doc/Configuration.md create mode 100644 doc/SPTPS create mode 100644 examples/.gitignore create mode 100644 examples/Makefile.am create mode 100644 examples/channels.c create mode 100644 examples/chat.c create mode 100644 examples/chatpp.cc create mode 100644 examples/fixlib.txt create mode 100644 examples/groupchat.c create mode 100644 examples/manynodes.c create mode 100644 examples/meshlinkapp.c create mode 100644 examples/monitor.c create mode 100644 m4/.gitignore create mode 100644 m4/attribute.m4 create mode 100644 m4/ax_check_compile_flag.m4 create mode 100644 m4/ax_check_link_flag.m4 create mode 100644 m4/ax_prog_doxygen.m4 create mode 100644 m4/ax_pthread.m4 create mode 100644 src/.gitignore create mode 100644 src/Makefile.am create mode 100644 src/adns.c create mode 100644 src/adns.h create mode 100644 src/buffer.c create mode 100644 src/buffer.h create mode 100644 src/chacha-poly1305/chacha-poly1305.c create mode 100644 src/chacha-poly1305/chacha-poly1305.h create mode 100644 src/chacha-poly1305/chacha.c create mode 100644 src/chacha-poly1305/chacha.h create mode 100644 src/chacha-poly1305/poly1305.c create mode 100644 src/chacha-poly1305/poly1305.h create mode 100644 src/conf.c create mode 100644 src/conf.h create mode 100644 src/connection.c create mode 100644 src/connection.h create mode 100644 src/crypto.c create mode 100644 src/crypto.h create mode 100644 src/devtools.c create mode 100644 src/devtools.h create mode 100644 src/discovery.c create mode 100644 src/discovery.h create mode 100644 src/dropin.c create mode 100644 src/dropin.h create mode 100644 src/ecdh.h create mode 100644 src/ecdsa.h create mode 100644 src/ecdsagen.h create mode 100644 src/ed25519/add_scalar.c create mode 100644 src/ed25519/ecdh.c create mode 100644 src/ed25519/ecdsa.c create mode 100644 src/ed25519/ecdsagen.c create mode 100644 src/ed25519/ed25519.h create mode 100644 src/ed25519/fe.c create mode 100644 src/ed25519/fe.h create mode 100644 src/ed25519/fixedint.h create mode 100644 src/ed25519/ge.c create mode 100644 src/ed25519/ge.h create mode 100644 src/ed25519/key_exchange.c create mode 100644 src/ed25519/keypair.c create mode 100644 src/ed25519/precomp_data.h create mode 100644 src/ed25519/sc.c create mode 100644 src/ed25519/sc.h create mode 100644 src/ed25519/seed.c create mode 100644 src/ed25519/sha512.c create mode 100644 src/ed25519/sha512.h create mode 100644 src/ed25519/sign.c create mode 100644 src/ed25519/verify.c create mode 100644 src/edge.c create mode 100644 src/edge.h create mode 100644 src/event.c create mode 100644 src/event.h create mode 100644 src/graph.c create mode 100644 src/graph.h create mode 100644 src/hash.c create mode 100644 src/hash.h create mode 100644 src/have.h create mode 100644 src/list.c create mode 100644 src/list.h create mode 100644 src/logger.c create mode 100644 src/logger.h create mode 100644 src/mdns.c create mode 100644 src/mdns.h create mode 100644 src/meshlink++.h create mode 100644 src/meshlink.c create mode 100644 src/meshlink.h create mode 100644 src/meshlink.sym create mode 100644 src/meshlink_internal.h create mode 100644 src/meshlink_queue.h create mode 100644 src/meta.c create mode 100644 src/meta.h create mode 100644 src/net.c create mode 100644 src/net.h create mode 100644 src/net_packet.c create mode 100644 src/net_setup.c create mode 100644 src/net_socket.c create mode 100644 src/netutl.c create mode 100644 src/netutl.h create mode 100644 src/node.c create mode 100644 src/node.h create mode 100644 src/packmsg.h create mode 100644 src/prf.c create mode 100644 src/prf.h create mode 100644 src/protocol.c create mode 100644 src/protocol.h create mode 100644 src/protocol_auth.c create mode 100644 src/protocol_edge.c create mode 100644 src/protocol_key.c create mode 100644 src/protocol_misc.c create mode 100644 src/route.c create mode 100644 src/route.h create mode 100644 src/sockaddr.h create mode 100644 src/splay_tree.c create mode 100644 src/splay_tree.h create mode 100644 src/sptps.c create mode 100644 src/sptps.h create mode 100644 src/submesh.c create mode 100644 src/submesh.h create mode 100644 src/system.h create mode 100644 src/utcp-test.c create mode 100644 src/utcp.c create mode 100644 src/utcp.h create mode 100644 src/utcp_priv.h create mode 100644 src/utils.c create mode 100644 src/utils.h create mode 100644 src/xalloc.h create mode 100644 src/xoshiro.c create mode 100644 src/xoshiro.h create mode 100644 test/.gitignore create mode 100644 test/Makefile.am create mode 100644 test/api_set_node_status_cb.c create mode 100644 test/basic.c create mode 100644 test/basicpp.cpp create mode 100644 test/blackbox/.gitignore create mode 100644 test/blackbox/Makefile.am create mode 100644 test/blackbox/common/common_handlers.c create mode 100644 test/blackbox/common/common_handlers.h create mode 100644 test/blackbox/common/common_types.h create mode 100644 test/blackbox/common/containers.c create mode 100644 test/blackbox/common/containers.h create mode 100644 test/blackbox/common/mesh_event_handler.c create mode 100644 test/blackbox/common/mesh_event_handler.h create mode 100644 test/blackbox/common/network_namespace_framework.c create mode 100644 test/blackbox/common/network_namespace_framework.h create mode 100644 test/blackbox/common/tcpdump.c create mode 100644 test/blackbox/common/tcpdump.h create mode 100644 test/blackbox/common/test_step.c create mode 100644 test/blackbox/common/test_step.h create mode 100644 test/blackbox/run_blackbox_tests/.gitignore create mode 100644 test/blackbox/run_blackbox_tests/Makefile.am create mode 100644 test/blackbox/run_blackbox_tests/execute_tests.c create mode 100644 test/blackbox/run_blackbox_tests/execute_tests.h create mode 100644 test/blackbox/run_blackbox_tests/run_blackbox_tests.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_add_addr.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_add_addr.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_add_ex_addr.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_add_ex_addr.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_autoconnect.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_autoconnect.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_blacklist.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_blacklist.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_channel_blacklist.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_channel_blacklist.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_channel_close.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_channel_close.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_channel_conn.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_channel_conn.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_channel_ex.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_channel_ex.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_channel_get_flags.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_channel_get_flags.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_channel_open.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_channel_open.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_channel_send.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_channel_send.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_channel_set_accept_cb.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_channel_set_accept_cb.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_channel_set_poll_cb.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_channel_set_poll_cb.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_channel_set_receive_cb.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_channel_set_receive_cb.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_channel_shutdown.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_channel_shutdown.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_default_blacklist.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_default_blacklist.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_destroy.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_destroy.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_export.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_export.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_get_all_nodes.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_get_all_nodes.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_get_all_nodes_by_dev_class.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_get_all_nodes_by_dev_class.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_get_ex_addr.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_get_ex_addr.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_get_fingerprint.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_get_fingerprint.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_get_node.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_get_node.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_get_node_reachability.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_get_node_reachability.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_get_port.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_get_port.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_get_self.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_get_self.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_hint_address.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_hint_address.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_import.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_import.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_invite.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_invite.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_join.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_join.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_key_rotation.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_key_rotation.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_open.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_open.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_pmtu.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_pmtu.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_random_port_bindings01.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_random_port_bindings01.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_random_port_bindings02.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_random_port_bindings02.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_rec_cb.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_rec_cb.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_send.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_send.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_set_connection_try_cb.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_set_connection_try_cb.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_set_log_cb.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_set_log_cb.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_set_port.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_set_port.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_sign.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_sign.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_start.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_start.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_status_cb.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_status_cb.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_stop_close.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_stop_close.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_submesh01.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_submesh01.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_submesh02.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_submesh02.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_submesh03.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_submesh03.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_submesh04.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_submesh04.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_verify.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_verify.h create mode 100644 test/blackbox/run_blackbox_tests/test_cases_whitelist.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_whitelist.h create mode 100644 test/blackbox/run_blackbox_tests/test_optimal_pmtu.c create mode 100644 test/blackbox/run_blackbox_tests/test_optimal_pmtu.h create mode 100644 test/blackbox/test_case_channel_blacklist_01/node_sim_nut_01.c create mode 100644 test/blackbox/test_case_channel_blacklist_01/node_sim_nut_01.h create mode 100644 test/blackbox/test_case_channel_blacklist_01/node_sim_peer_01.c create mode 100644 test/blackbox/test_case_channel_blacklist_01/node_sim_relay_01.c create mode 100644 test/blackbox/test_case_channel_conn_01/Makefile.am create mode 100644 test/blackbox/test_case_channel_conn_01/node_sim_nut.c create mode 100644 test/blackbox/test_case_channel_conn_01/node_sim_peer.c create mode 100644 test/blackbox/test_case_channel_conn_02/Makefile.am create mode 100644 test/blackbox/test_case_channel_conn_02/node_sim_nut.c create mode 100644 test/blackbox/test_case_channel_conn_02/node_sim_peer.c create mode 100644 test/blackbox/test_case_channel_conn_03/Makefile.am create mode 100644 test/blackbox/test_case_channel_conn_03/node_sim_nut.c create mode 100644 test/blackbox/test_case_channel_conn_03/node_sim_peer.c create mode 100644 test/blackbox/test_case_channel_conn_04/Makefile.am create mode 100644 test/blackbox/test_case_channel_conn_04/node_sim_nut.c create mode 100644 test/blackbox/test_case_channel_conn_04/node_sim_peer.c create mode 100644 test/blackbox/test_case_channel_conn_05/Makefile.am create mode 100644 test/blackbox/test_case_channel_conn_05/node_sim_nut.c create mode 100644 test/blackbox/test_case_channel_conn_05/node_sim_peer.c create mode 100644 test/blackbox/test_case_channel_conn_05/node_sim_relay.c create mode 100644 test/blackbox/test_case_channel_conn_06/Makefile.am create mode 100644 test/blackbox/test_case_channel_conn_06/node_sim_nut.c create mode 100644 test/blackbox/test_case_channel_conn_06/node_sim_peer.c create mode 100644 test/blackbox/test_case_channel_conn_06/node_sim_relay.c create mode 100644 test/blackbox/test_case_channel_conn_07/Makefile.am create mode 100644 test/blackbox/test_case_channel_conn_07/node_sim_nut.c create mode 100644 test/blackbox/test_case_channel_conn_07/node_sim_peer.c create mode 100644 test/blackbox/test_case_channel_conn_07/node_sim_relay.c create mode 100644 test/blackbox/test_case_channel_conn_08/Makefile.am create mode 100644 test/blackbox/test_case_channel_conn_08/node_sim_nut.c create mode 100644 test/blackbox/test_case_channel_conn_08/node_sim_peer.c create mode 100644 test/blackbox/test_case_channel_conn_08/node_sim_relay.c create mode 100644 test/blackbox/test_case_meta_conn_01/Makefile.am create mode 100644 test/blackbox/test_case_meta_conn_01/node_sim_nut.c create mode 100644 test/blackbox/test_case_meta_conn_01/node_sim_peer.c create mode 100644 test/blackbox/test_case_meta_conn_01/node_sim_relay.c create mode 100755 test/blackbox/test_case_meta_conn_01/test/node_step.sh create mode 100644 test/blackbox/test_case_meta_conn_02/Makefile.am create mode 100644 test/blackbox/test_case_meta_conn_02/node_sim_nut.c create mode 100644 test/blackbox/test_case_meta_conn_02/node_sim_peer.c create mode 100644 test/blackbox/test_case_meta_conn_02/node_sim_relay.c create mode 100644 test/blackbox/test_case_meta_conn_03/Makefile.am create mode 100644 test/blackbox/test_case_meta_conn_03/node_sim_nut.c create mode 100644 test/blackbox/test_case_meta_conn_03/node_sim_peer.c create mode 100644 test/blackbox/test_case_meta_conn_03/node_sim_relay.c create mode 100644 test/blackbox/test_case_meta_conn_04/Makefile.am create mode 100644 test/blackbox/test_case_meta_conn_04/node_sim_nut.c create mode 100644 test/blackbox/test_case_meta_conn_04/node_sim_peer.c create mode 100644 test/blackbox/test_case_meta_conn_04/node_sim_relay.c create mode 100644 test/blackbox/test_case_meta_conn_05/Makefile.am create mode 100644 test/blackbox/test_case_meta_conn_05/node_sim_nut.c create mode 100644 test/blackbox/test_case_meta_conn_05/node_sim_peer.c create mode 100644 test/blackbox/test_case_meta_conn_05/node_sim_relay.c create mode 100644 test/blackbox/test_case_optimal_pmtu_01/node_sim_nut.c create mode 100644 test/blackbox/test_case_optimal_pmtu_01/node_sim_peer.c create mode 100644 test/blackbox/test_case_optimal_pmtu_01/node_sim_relay.c create mode 100644 test/blackbox/test_case_optimal_pmtu_01/test_case_optimal_pmtu.h create mode 100644 test/blackbox/test_case_optimal_pmtu_02/Makefile.am create mode 100644 test/blackbox/test_case_optimal_pmtu_03/Makefile.am create mode 100644 test/blackbox/test_case_optimal_pmtu_04/Makefile.am create mode 100644 test/blackbox/test_case_optimal_pmtu_05/Makefile.am create mode 100644 test/blackbox/test_case_optimal_pmtu_06/Makefile.am create mode 100644 test/blackbox/test_case_optimal_pmtu_07/Makefile.am create mode 100644 test/blackbox/test_case_optimal_pmtu_07/node_sim_nut.c create mode 100644 test/blackbox/test_case_optimal_pmtu_07/node_sim_peer.c create mode 100644 test/blackbox/test_case_optimal_pmtu_07/node_sim_relay.c create mode 100644 test/blackbox/test_cases_submesh01/Makefile.am create mode 100644 test/blackbox/test_cases_submesh01/node_sim_app1node1.c create mode 100644 test/blackbox/test_cases_submesh01/node_sim_app1node2.c create mode 100644 test/blackbox/test_cases_submesh01/node_sim_app2node1.c create mode 100644 test/blackbox/test_cases_submesh01/node_sim_app2node2.c create mode 100644 test/blackbox/test_cases_submesh01/node_sim_corenode1.c create mode 100644 test/blackbox/test_cases_submesh01/node_sim_corenode2.c create mode 100644 test/blackbox/test_cases_submesh02/Makefile.am create mode 100644 test/blackbox/test_cases_submesh02/node_sim_app1node1.c create mode 100644 test/blackbox/test_cases_submesh02/node_sim_app1node2.c create mode 100644 test/blackbox/test_cases_submesh02/node_sim_app2node1.c create mode 100644 test/blackbox/test_cases_submesh02/node_sim_app2node2.c create mode 100644 test/blackbox/test_cases_submesh02/node_sim_corenode1.c create mode 100644 test/blackbox/test_cases_submesh02/node_sim_corenode2.c create mode 100644 test/blackbox/test_cases_submesh03/Makefile.am create mode 100644 test/blackbox/test_cases_submesh03/node_sim_app1node1.c create mode 100644 test/blackbox/test_cases_submesh03/node_sim_app1node2.c create mode 100644 test/blackbox/test_cases_submesh03/node_sim_corenode1.c create mode 100644 test/blackbox/test_cases_submesh04/Makefile.am create mode 100644 test/blackbox/test_cases_submesh04/node_sim_app1node1.c create mode 100644 test/blackbox/test_cases_submesh04/node_sim_app1node2.c create mode 100644 test/blackbox/test_cases_submesh04/node_sim_corenode1.c create mode 100755 test/blackbox/util/build_container.sh create mode 100644 test/blackbox/util/gen_invite.c create mode 100755 test/blackbox/util/install_node_sim_copy.sh create mode 100755 test/blackbox/util/install_packages.sh create mode 100755 test/blackbox/util/lxc_copy_dir.sh create mode 100755 test/blackbox/util/lxc_copy_file.sh create mode 100755 test/blackbox/util/lxc_rename.sh create mode 100755 test/blackbox/util/lxc_run.sh create mode 100755 test/blackbox/util/nat.sh create mode 100755 test/blackbox/util/nat_destroy.sh create mode 100755 test/blackbox/util/node_step.sh create mode 100644 test/blacklist.c create mode 100644 test/channels-aio-abort.c create mode 100644 test/channels-aio-cornercases.c create mode 100644 test/channels-aio-fd.c create mode 100644 test/channels-aio.c create mode 100644 test/channels-buffer-storage.c create mode 100644 test/channels-cornercases.c create mode 100644 test/channels-failure.c create mode 100644 test/channels-fork.c create mode 100644 test/channels-no-partial.c create mode 100644 test/channels-udp-cornercases.c create mode 100644 test/channels-udp.c create mode 100644 test/channels.c create mode 100644 test/duplicate.c create mode 100644 test/echo-fork.c create mode 100644 test/encrypted.c create mode 100644 test/ephemeral.c create mode 100644 test/get-all-nodes.c create mode 100644 test/import-export.c create mode 100644 test/invite-join.c create mode 100644 test/meta-connections.c create mode 100644 test/netns_utils.c create mode 100644 test/netns_utils.h create mode 100755 test/run_blackbox_tests.sh create mode 100644 test/sign-verify.c create mode 100644 test/storage-policy.c create mode 100644 test/stream.c create mode 100644 test/trio.c create mode 100644 test/trio2.c create mode 100755 test/utcp-benchmark create mode 100755 test/utcp-benchmark-stream create mode 100644 test/utils.c create mode 100644 test/utils.h diff --git a/.astylerc b/.astylerc new file mode 100644 index 0000000..ba38946 --- /dev/null +++ b/.astylerc @@ -0,0 +1,10 @@ +--indent=tab=8 +--convert-tabs +-j +-f +-A2 +-U +-p +-xg +-k3 +-w diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..85d49b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +Makefile +Makefile.in +*.o +*.a +*.lo +*.la +*.orig +*~ +.* +!.gitignore +!.gitmodules +!.astylerc +core +core.* +vgcore.* +tags +/config.* +/autom4te.cache +/aclocal.m4 +/aminclude.am +/ar-lib +/build +/compile +/configure +/depcomp +/doxygen-doc +/install-sh +/missing +/libtool +/ltmain.sh +/py-compile +/test-driver +/stamp-h1 +/INSTALL +/meshlink-* +/ChangeLog diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e69de29 diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..565957b --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Guus Sliepen diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..819b40f --- /dev/null +++ b/COPYING @@ -0,0 +1,288 @@ +Copyright (C) 2014-2018 Guus Sliepen + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/COPYING.README b/COPYING.README new file mode 100644 index 0000000..214af75 --- /dev/null +++ b/COPYING.README @@ -0,0 +1,116 @@ +# License obligations + +The source code of the MeshLink library itself is licensed under the GNU +General Public License version 2 or later (see the file COPYING for the +complete license). However, the MeshLink library depends on other libraries as +well, which each have their own license. This document summarizes your +obligations when you are releasing binaries that link with the MeshLink library +and any dependent libraries, for both open source and closed source software. + +Please note that this document is not to be interpreted as a license itself. + +## List of libraries that MeshLink depends on + +* Catta (LGPL version 2.1) +* Ed25519 (zlib license) +* Chacha20-Poly1305 (public domain) + +## Obligations for closed source software + +With closed source software, we mean any software that is distributed only in +binary form. Note that there is no distinction between software that is sold, +rented out, or is gratis. You are not allowed to distribute closed source +software linked to the MeshLink library unless you have obtained a commercial +license for the MeshLink library. If you do distribute closed source software +linked to MeshLink without a commercial license, you are violating +international copyright law. In this case, please contact the MeshLink +author(s) as soon as possible in order to remedy this situation. + +If you do have a commercial license for the MeshLink library, then apart from +the obligations set out in the commercial license, you are also obligated to +follow the rules set out in the licenses of the libraries MeshLink depends on. +In short, these are: + +* Unless you have compiled MeshLink without support for Catta, you must make + the source code of Catta, with any modifications that you made to the Catta + library itself, available on request to anyone who has received a copy of + your closed source software, under the terms of the GNU Lesser General Public + License version 2.1 or later. +* Unless you have compiled MeshLink without support for Catta, you must give + prominent notice with each copy of your software that your software is using + Catta, and that the Catta library itself is covered under the terms of the + GNU Lesser General Public License version 2.1. + +Apart from these obligations, you are strongly recommended to mention the other +libraries that MeshLink is depending on, as well as mention the MeshLink +library itself: + +* Attribute Orson Peters as the author of the Ed25519 library. +* Attribute Daniel J. Bernstein as the author of the Chacha20-Poly1305 library. +* Attribute Guus Sliepen as the author of the MeshLink library. + +## Obligations for open source software + +With open source software, we mean any software that is distributed in source +form, or is distributed in binary form with the exact source code used to +compile the binary form made publicly available, or with this source code +made available on request. Note that there is no distinction between software +that is sold, rented out, or is gratis. + +If your software itself does not contain any copies or excerpts of the source +code of MeshLink, you are free to distribute that software under any license +you want. However, as soon as you distribute any binaries that are linked with +MeshLink, then the resulting binaries can only be distributed under the terms +of the GNU General Public License version 2 or later. In practice, this is not +a problem since if you make the source code available publicly, you are +fulfilling the obligations of the GPL, unless your software has a software +license that is incompatible with the GPL. Please check the following website +to find out if your open source software is compatible with MeshLink: + +https://www.gnu.org/licenses/license-list.en.html#SoftwareLicenses + +Apart from the obligations for the MeshLink library, set out in the GPL, you +are also obligated to follow the rules set out in the licenses of the libraries +MeshLink depends on. In short, these are: + +* Unless you have compiled MeshLink without support for Catta, you must make + the source code of Catta, with any modifications that you made to the Catta + library itself, available on request to anyone who has received a copy of + your closed source software, under the terms of the GNU Lesser General Public + License version 2.1 or later. +* Unless you have compiled MeshLink without support for Catta, you must give + prominent notice with each copy of your software that your software is using + Catta, and that the Catta library itself is covered under the terms of the + GNU Lesser General Public License version 2.1. + +Apart from these obligations, you are strongly recommended to mention the other +libraries that MeshLink is depending on, as well as mention the MeshLink +library itself: + +* Attribute Orson Peters as the author of the Ed25519 library. +* Attribute Daniel J. Bernstein as the author of the Chacha20-Poly1305 library. +* Attribute Guus Sliepen as the author of the MeshLink library. + +## Recommended attribution text + +Attribution for the libraries your software depends on should be added to any +written, printed or electronic documentation that comes with your software, as +well as displayed in any about dialogs, splash screens, and any other forms of +display that contain credits, license information and/or attribution. The +recommended text to display is: + + This software makes use of the following libraries: + + - MeshLink, copyright © 2014-2018 Guus Sliepen, + licensed under the GPL version 2 or later¹ + - Catta, copyright © 2004-2018 the Avahi Developers and others, + licensed under the LGPL version 2.1 or later² + - Chacha20-Poly1305, by Daniel J. Bernstein, public domain + - Ed25519, copyright © 2015 Orson Peters, licensed under the zlib license + +¹) omit the "licensed under" text if you have a commercial license +²) omit this is you compiled MeshLink without support for Catta + +The full text of the licenses of all libraries used should be included in the +documentation, and should be included in any forms or displays that contain +license information. diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..33ae06c --- /dev/null +++ b/Doxyfile @@ -0,0 +1,14 @@ +PROJECT_NAME = $(PROJECT) +PROJECT_NUMBER = $(VERSION) +PROJECT_BRIEF = "Mesh networking library" +OUTPUT_DIRECTORY = $(DOCDIR) +INPUT = $(SRCDIR)/src/meshlink.h \ + $(SRCDIR)/src/meshlink++.h +LATEX_BATCHMODE = YES +GENERATE_MAN = NO +EXTRACT_ALL = YES +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = YES +WARN_AS_ERROR = YES diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..1dc085c --- /dev/null +++ b/Makefile.am @@ -0,0 +1,30 @@ +## Process this file with automake to get Makefile.in + +AUTOMAKE_OPTIONS = gnu + +SUBDIRS = src test examples + +DIST_SUBDIRS = $(SUBDIRS) + +ACLOCAL_AMFLAGS = -I m4 + +EXTRA_DIST = \ + COPYING.README \ + README.android \ + $(DX_CONFIG) + +@DX_RULES@ + +MOSTLYCLEANFILES = $(DX_CLEANFILES) + +ChangeLog: + git log > ChangeLog + +astyle: + @astyle --version | grep -q "Version 3" || (echo 'ERROR: astyle version 3 required!' 1>&2 && exit 1) + astyle --options=.astylerc -nQ \ + src/*.[ch] \ + src/ed25519/e*.[ch] \ + examples/*.[ch] \ + examples/*.cc \ + `find test -name '*.[ch]'` diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..81da889 --- /dev/null +++ b/NEWS @@ -0,0 +1,3 @@ +Version 0.1 + + * Initial version of the MeshLink library. diff --git a/README b/README new file mode 100644 index 0000000..59d9927 --- /dev/null +++ b/README @@ -0,0 +1,42 @@ +This is the README file for the MeshLink library. Installation instructions may +be found in the INSTALL file. + +MeshLink is Copyright (C) 2014-2018 Guus Sliepen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or (at +your option) any later version. See the file COPYING for more details. + +To obtain a license to use this library in commercial software, please contact +sales@meshlink.io. + + +This is not a finished version +------------------------------ + +Please do not use this library yet. + + +Requirements +------------ + +In order to compile MeshLink, you will need a GNU C compiler environment. + + +Features +-------- + +MeshLink is a library that allows applications to connect to other instances of +itself, and exchange messages in a secure way. MeshLink provides end-to-end +encryption and authentication of messages with perfect forward secrecy. The +MeshLink library starts its own thread which handles all network +communications. The application only needs to register callbacks to get +notified of incoming messages and other important events. + +Other noteworthy features are: + +- IPv6 support +- NAT traversal (requires at least one node that is not behind a NAT) +- Ed25519 keys (TBD) +- ChaCha-Poly1305 encryption and message authentication diff --git a/README.android b/README.android new file mode 100644 index 0000000..20cd69a --- /dev/null +++ b/README.android @@ -0,0 +1,15 @@ +# Quick how-to cross compile MeshLink for Android + +- Download the *latest* Android NDK for your OS from https://developer.android.com/ndk/downloads +- Unzip the NDK in a suitable location, for example `/usr/local` +- Create a standalone toolchain using the NDK, like so: + + /usr/local/android-ndk-rXXX/build/toold/make-standalone-toolchain.sh --install-dir=/tmp/my-android-toolchain + +- Clone and cross-compile MeshLink: + + git clone git://meshlink.io/meshlink + cd meshlink + autoreconf -fsi + CC=/tmp/my-android-toolchain/bin/arm-linux-androideabi-gcc CXX=/tmp/my-android-toolchain/bin/arm-linux-androideabi-g++ ./configure --host=arm-linux-androideabi + make -j$(nproc) diff --git a/README.git b/README.git new file mode 100644 index 0000000..f28b01f --- /dev/null +++ b/README.git @@ -0,0 +1,21 @@ +Before you can start compiling MeshLink from a fresh git clone, you have +to install the very latest versions of the following packages: + +- GCC +- automake +- autoconf + +Then you have to let the autotools create all the autogenerated files, using +this command: + +autoreconf -fsi + +If you change configure.in or any Makefile.am file, you will have to rerun +autoreconf. After this, you can run configure and make as usual. To create a +tarball suitable for release, run: + +make dist + +To clean up your working copy so that no autogenerated files remain, run: + +git clean -f diff --git a/README.meshlink b/README.meshlink new file mode 100644 index 0000000..5c92312 --- /dev/null +++ b/README.meshlink @@ -0,0 +1,6 @@ + +The libmeshlink.so library is compiled and you will find it in ./src/.libs/ + +You can make your tests using this command + +export LD_LIBRARY_PATH='/home/saverio/SORGENTI/meshlink/src/.libs/' diff --git a/TODO b/TODO new file mode 100644 index 0000000..6a51375 --- /dev/null +++ b/TODO @@ -0,0 +1,41 @@ +TODO list for MeshLink +---------------------- + +* meshlink_join(): + - add checks that we only join another mesh if we did not connect to any other node before. + - only allow meshlink_join() when the library thread is not running. + - remove old host config file if the invitation gave the node a different name. + - how to handle nodes that are part of one mesh and want to join another? + +* meshlink_leave()? + - leaving a mesh is basically starting all over again, with new private keys? + +* Allow meshlink_open() to be called with a NULL name, in anticipation of meshlink_join(). + - Do not allow meshlink_start() if no Name is set. + +* Add autoconf/automake stuff to call Doxygen. + +* Write a manual + - Add introduction and walkthrough how to use MeshLink in an application to + the doxygen manual. + - Explain what MeshLink does and how it should be used in the application + - Import examples into the manual? + - List possible ways of synchronisation between application and library threads + - simple polling + - pthread_cond? + - pipe() to signal an event loop of the application's choice + - whatever equivalent(s) there are on Windows + +Not finished but being worked on: +--------------------------------- + +* Provide thread-safety to functions that return pointers to meshlink_node_t. + - The mesh->nodes tree can be updated by the library thread at any time, + so it must be protected by a mutex or rwlock. + - Individial node_t's must never be freed or moved except in meshlink_close(). + - Check all public API functions. + +* Write a test suite for the library. + +* Add a "channel" library to MeshLink. + - Support multiple TCP- and UDP-like streams between two nodes. diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..022bba7 --- /dev/null +++ b/configure.ac @@ -0,0 +1,169 @@ +dnl Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.69) +AC_INIT([MeshLink], [0.1]) +DX_PS_FEATURE(OFF) +DX_INIT_DOXYGEN([MeshLink], [$(top_srcdir)/Doxyfile]) +AM_INIT_AUTOMAKE([std-options subdir-objects nostdinc silent-rules -Wall]) +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_MACRO_DIR([m4]) +AM_SILENT_RULES([yes]) + +# Enable GNU extensions. +# Define this here, not in acconfig's @TOP@ section, since definitions +# in the latter don't make it into the configure-time tests. +AC_USE_SYSTEM_EXTENSIONS +AC_DEFINE([__USE_BSD], 1, [Enable BSD extensions]) +AC_DEFINE([_POSIX_C_SOURCE], 200809L, [Enable POSIX features]) +AC_DEFINE([_DARWIN_C_SOURCE], 1, [Enable Darwin features]) + +dnl Checks for programs. +AM_PROG_CC_C_O +AM_PROG_AR +AC_PROG_CC +AC_PROG_CC_C99 +AC_PROG_CXX +AC_PROG_CPP +AC_PROG_INSTALL +AC_PROG_LN_S +LT_INIT +AX_PTHREAD + +dnl Check and set OS + +AC_CANONICAL_HOST + +case $host_os in + *linux*) + linux=true + AC_DEFINE(HAVE_LINUX, 1, [Linux]) + ;; + *mingw*) + mingw=true + AC_DEFINE(HAVE_MINGW, 1, [MinGW]) + LIBS="$LIBS -lws2_32 -lgdi32 -lcrypt32" + ;; + *darwin* | *ios*) + LIBS="$LIBS -framework Foundation -framework SystemConfiguration" + ;; +esac + +AM_CONDITIONAL(LINUX, test "$linux" = true) +AM_CONDITIONAL(MINGW, test "$mingw" = true) + +AC_CACHE_SAVE + +if test -d /sw/include ; then + CPPFLAGS="$CPPFLAGS -I/sw/include" +fi +if test -d /sw/lib ; then + LIBS="$LIBS -L/sw/lib" +fi + +AX_CHECK_COMPILE_FLAG([-std=c11], [CFLAGS="$CFLAGS -std=c11"]) + +dnl Compiler hardening flags +dnl No -fstack-protector-all because it doesn't work on all platforms or architectures. + +AC_ARG_ENABLE([hardening], AS_HELP_STRING([--disable-hardening], [disable compiler and linker hardening flags])) +AS_IF([test "x$enable_hardening" != "xno"], + [CPPFLAGS="$CPPFLAGS -Wall -W -pedantic" + AX_CHECK_COMPILE_FLAG([-DFORTIFY_SOURCE=2], [CPPFLAGS="$CPPFLAGS -DFORITFY_SOURCE=2"]) + AX_CHECK_COMPILE_FLAG([-fwrapv], [CPPFLAGS="$CPPFLAGS -fwrapv"], + [AX_CHECK_COMPILE_FLAG([-fno-strict-overflow], [CPPFLAGS="$CPPFLAGS -fno-strict-overflow"])] + ) + case $host_os in + *mingw*) + AX_CHECK_LINK_FLAG([-Wl,--dynamicbase], [LDFLAGS="$LDFLAGS -Wl,--dynamicbase"]) + AX_CHECK_LINK_FLAG([-Wl,--nxcompat], [LDFLAGS="$LDFLAGS -Wl,--nxcompat"]) + ;; + *) + AX_CHECK_COMPILE_FLAG([-fPIC], [CPPFLAGS="$CPPFLAGS -fPIC"]) + ;; + esac + AX_CHECK_LINK_FLAG([-Wl,-z,relro], [LDFLAGS="$LDFLAGS -Wl,-z,relro"]) + AX_CHECK_LINK_FLAG([-Wl,-z,now], [LDFLAGS="$LDFLAGS -Wl,-z,now"]) + AX_CHECK_COMPILE_FLAG([-Wextra -pedantic -Wreturn-type -Wold-style-definition -Wmissing-declarations -Wmissing-prototypes -Wstrict-prototypes -Wredundant-decls -Wshadow -Wbad-function-cast -Wwrite-strings -fdiagnostics-show-option -fstrict-aliasing -Wmissing-noreturn], [CPPFLAGS="$CPPFLAGS -Wextra -pedantic -Wreturn-type -Wold-style-definition -Wmissing-declarations -Wmissing-prototypes -Wstrict-prototypes -Wredundant-decls -Wshadow -Wbad-function-cast -Wwrite-strings -fdiagnostics-show-option -fstrict-aliasing -Wmissing-noreturn"]) + ] +); + +dnl UTCP debug flags +AC_ARG_ENABLE([utcp_debug], AS_HELP_STRING([--enable-utcp-debug], [compile utcp with debug output])) +AS_IF([test "x$enable_utcp_debug" = "xyes"], + [AX_CHECK_COMPILE_FLAG([-DUTCP_DEBUG], [CPPFLAGS="$CPPFLAGS -DUTCP_DEBUG"]) + ] +); + +dnl Blackbox test suite +PKG_CHECK_MODULES([CMOCKA], [cmocka >= 1.1.0], [cmocka=true], [cmocka=false]) +PKG_CHECK_MODULES([LXC], [lxc >= 2.0.0], [lxc=true], [lxc=false]) +AM_CONDITIONAL(BLACKBOX_TESTS, test "$cmocka" = true -a "$lxc" = true) + + +dnl Additional example code +PKG_CHECK_MODULES([NCURSES], [ncurses >= 5], [curses=true], [curses=false]) +AC_ARG_ENABLE([monitor_code], + [AS_HELP_STRING([--enable-monitor-code], [Add monitor example code to the build])], + [AS_IF([test "x$enable_monitor_code" = "xyes"], [monitor_code=true], [monitor_code=false])], + [monitor_code=false] +) +AM_CONDITIONAL(MONITOR, test "$monitor_code" = true) + +dnl Install test binaries +AC_ARG_ENABLE([install_tests], + [AS_HELP_STRING([--enable-install-tests], [include test binaries in installation])], + [AS_IF([test "x$enable_install_tests" = "xyes"], [install_tests=true], [install_tests=false])], + [install_tests=false] +) + +AM_CONDITIONAL(INSTALL_TESTS, test "$install_tests" = true) + +dnl Checks for header files. +dnl We do this in multiple stages, because unlike Linux all the other operating systems really suck and don't include their own dependencies. + +AC_CHECK_HEADERS([syslog.h sys/file.h sys/param.h sys/resource.h sys/socket.h sys/time.h sys/un.h sys/wait.h netdb.h arpa/inet.h dirent.h curses.h ifaddrs.h stdatomic.h]) + +dnl Checks for typedefs, structures, and compiler characteristics. +MeshLink_ATTRIBUTE(__malloc__) +MeshLink_ATTRIBUTE(__warn_unused_result__) + +dnl Checks for library functions. +AC_CHECK_FUNCS([asprintf fchmod fork gettimeofday random pselect select setns strdup usleep getifaddrs freeifaddrs], + [], [], [#include "$srcdir/src/have.h"] +) + +dnl Support for SunOS + +AC_CHECK_FUNC(socket, [], [ + AC_CHECK_LIB(socket, connect) +]) + +AC_CACHE_SAVE + +AC_CONFIG_FILES([ + Makefile + src/Makefile + test/Makefile + test/blackbox/Makefile + test/blackbox/run_blackbox_tests/Makefile + test/blackbox/test_case_channel_conn_01/Makefile + test/blackbox/test_case_channel_conn_02/Makefile + test/blackbox/test_case_channel_conn_03/Makefile + test/blackbox/test_case_channel_conn_04/Makefile + test/blackbox/test_case_channel_conn_05/Makefile + test/blackbox/test_case_channel_conn_06/Makefile + test/blackbox/test_case_channel_conn_07/Makefile + test/blackbox/test_case_channel_conn_08/Makefile + test/blackbox/test_case_meta_conn_01/Makefile + test/blackbox/test_case_meta_conn_02/Makefile + test/blackbox/test_case_meta_conn_03/Makefile + test/blackbox/test_case_meta_conn_04/Makefile + test/blackbox/test_case_meta_conn_05/Makefile + test/blackbox/test_cases_submesh01/Makefile + test/blackbox/test_cases_submesh02/Makefile + test/blackbox/test_cases_submesh03/Makefile + test/blackbox/test_cases_submesh04/Makefile + examples/Makefile +]) + +AC_OUTPUT diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 0000000..1af3cfd --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1,6 @@ +html/ +latex/ +man/ +include.texi +*.info +*.tex diff --git a/doc/Configuration.md b/doc/Configuration.md new file mode 100644 index 0000000..6b76457 --- /dev/null +++ b/doc/Configuration.md @@ -0,0 +1,97 @@ +# Configuration files + +There currently are three different types of configuration files in MeshLink: + +- the main configuration file +- the host config files +- pending invitation files + +All configuration files are in PackMessage format. The contents will be +described below. + +There are three different ways envisioned to store configuration data: + +- unencrypted files +- encrypted files +- ephemeral, in-memory storage + +To keep things as simple and flexible as possible, there are some restrictions: + +- Configuration files are read from and written to memory in one go. + When a configuration files is changed, it is not modified in place, + but has to be written from scratch. +- When in-memory, functions from `packmsg.h` can be used to parse and generate the configuration data. +- Configuration files are read and written only via functions declared in `conf.h`. + This also includes creating and destroying the configuration directory. + +## Main configuration file + +When stored on disk, it's in `meshlink.conf`. The contents are: + +- uint32: configuration format version +- str: name of the local node +- bin: private Ed25519 key +- bin: private Ed25519 key for invitations +- uint16: port number of the local node + +More information about the local node is stored in its host config file. + +## Host configuration files + +When stored on disk, there is one file per node in the mesh, inside the directory `hosts/`. +The contents of a host configuration are: + +- uint32: configuration format version +- str: name of the node +- str: name of the submesh this node is part of, or "." if it's in the core mesh +- int32: device class +- bool: blacklisted +- bin: public Ed25519 key, or zero-length if no key is known +- str: canonical address (may be zero length if unset) +- arr[ext]: recent addresses + +## Invitation files + +When stored on disk, there is one file per pending invitation, inside the directory `invitations/`. +The contents of an invitation file are: + +- uint32: invitation format version +- str: name of the invitee +- str: name of the submesh this node will be part of, or "." if it's in the core mesh +- int32: device class of the invitee (may be unused) +- arr[bin]: one or more host config files + +## Encryption + +When encryption is enabled, each file is individually encrypted using Chacha20-Poly1305. +A unique counter stored at the start of the file. A (master) key must be provided by the application. + +## Exporting configuration + +Calling `meshlink_export()` will return an array of host config files: + +- arr[bin]: one or more host config files + +## Loading, saving and unloading configuration data to/from memory + +### Main configuration file + +Created during first setup. +Loaded at start. +Never unloaded. +Might have to be saved again when port number or private key for invitations changes. + +### Host configuration files + +Can be loaded partially into memory: +- devclass+blacklist status only, always required in memory, so loaded in `load_all_nodes()`. +- public key, needed whenever SPTPS is required, or when the application wants a fingerprint or verify signed data from that node. +- canonical and recent addresses, needed whenever we want to make outgoing connections to this node. + +Furthermore, in order to properly merge new data, we need to load the whole config file into memory when: +- updating recent addresses +- exporting this node's information (no API for this yet) + +Whenever a node's information is updated, we mark it dirty. It is written out at a convenient time. + + diff --git a/doc/SPTPS b/doc/SPTPS new file mode 100644 index 0000000..2d8fee5 --- /dev/null +++ b/doc/SPTPS @@ -0,0 +1,170 @@ +Simple Peer-to-Peer Security +---------------------------- + +SPTPS is a protocol that, like TLS, aims to provide a secure transport layer +for applications. However, it is specifically aimed at peer-to-peer +applications. Specifically, peers have each other's credentials beforehand, +they need not negotiate certificates. Also, the security parameters of the +application is also known beforehand, so they need not negotiate cipher suites. +Only one cipher suite is available, and only one authentication method is used. +This not only greatly simplifies the protocol, it also gets rid of an entire +class of attacks and possible programming mistakes. + +SPTPS can be used both on top of reliable stream protocols such as TCP or on +top of datagram protocols such as UDP. + +Stream record layer +------------------- + +A record consists of these fields: + +- uint32_t seqno (network byte order) +- uint16_t length (network byte order) +- uint8_t type +- opaque data[length] +- opaque hmac[HMAC_SIZE] (HMAC over all preceding fields) + +Remarks: + +- The seqno field is never sent to the peer, but is included in the calculation + of the HMAC. +- At the start of the session, the HMAC field does not appear until after the + SIGnature records have been exchanged. +- After the authentication phase, the type and data fields are encrypted before + the HMAC is calculated. + +Message type: + +- 0..127 represent application records. The meaning of the value is application + specific. +- 128 is a handshake record. +- 129..255 are reserved and never to be used for application records. + +Datagram record layer +--------------------- + +A record consists of these fields: + +- uint16_t length (network byte order) +- uint32_t seqno (network byte order) +- uint8_t type +- opaque data[length] +- opaque hmac[HMAC_SIZE] (HMAC over all preceding fields) + +Remarks: + +- The length field is never sent to the peer, but is included in the calculation + of the HMAC. +- The rest is the same as the stream record layer. + +Authentication protocol +----------------------- + +The authentication consists of an exchange of Key EXchange, SIGnature and +ACKnowledge messages, transmitted using type 128 records. + +Overview: + +Initiator Responder +--------------------- +KEX -> + <- KEX +SIG -> + <- SIG + +...encrypt and HMAC using session keys from now on... + +App -> + <- App +... + ... + +...key renegotiation starts here... + +KEX -> + <- KEX +SIG -> + <- SIG +ACK -> + <- ACK + +...encrypt and HMAC using new session keys from now on... + +App -> + <- App +... + ... +--------------------- + +Note that the responder does not need to wait before it receives the first KEX +message, it can immediately send its own once it has accepted an incoming +connection. + +Key EXchange message: + +- uint8_t kex_version (always 0 in this version of SPTPS) +- opaque nonce[32] (random number) +- opaque ecdh_key[ECDH_SIZE] + +SIGnature message: + +- opaque ecdsa_signature[ECDSA_SIZE] + +ACKnowledge message: + +- empty (only sent after key renegotiation) + +Remarks: + +- At the start, both peers generate a random nonce and an Elliptic Curve public + key and send it to the other in the KEX message. +- After receiving the other's KEX message, both KEX messages are concatenated + (see below), and the result is signed using ECDSA. The result is sent to the + other. +- After receiving the other's SIG message, the signature is verified. If it is + correct, the shared secret is calculated from the public keys exchanged in the + KEX message using the Elliptic Curve Diffie-Helman algorithm. +- The shared secret key is expanded using a PRF. Both nonces and the application + specific label are also used as input for the PRF. +- An ACK message is sent only when doing key renegotiation, and is sent using + the old encryption keys. +- The expanded key is used to key the encryption and HMAC algorithms. + +The signature is calculated over this string: + +- uint8_t initiator (0 = local peer, 1 = remote peer is initiator) +- opaque remote_kex_message[1 + 32 + ECDH_SIZE] +- opaque local_kex_message[1 + 32 + ECDH_SIZE] +- opaque label[label_length] + +The PRF is calculated as follows: + +- A HMAC using SHA512 is used, the shared secret is used as the key. +- For each block of 64 bytes, a HMAC is calculated. For block n: hmac[n] = + HMAC_SHA512(hmac[n - 1] + seed) +- For the first block (n = 1), hmac[0] is given by HMAC_SHA512(zeroes + seed), + where zeroes is a block of 64 zero bytes. + +The seed is as follows: + +- const char[13] "key expansion" +- opaque responder_nonce[32] +- opaque initiator_nonce[32] +- opaque label[label_length] + +The expanded key is used as follows: + +- opaque responder_cipher_key[CIPHER_KEYSIZE] +- opaque responder_digest_key[DIGEST_KEYSIZE] +- opaque initiator_cipher_key[CIPHER_KEYSIZE] +- opaque initiator_digest_key[DIGEST_KEYSIZE] + +Where initiator_cipher_key is the key used by session initiator to encrypt +messages sent to the responder. + +TODO: +----- + +- Document format of ECDH public key, ECDSA signature +- Document how CTR mode is used +- Refer to TLS RFCs where appropriate diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..3c0d410 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,8 @@ +*/ +channels +chat +chatpp +manynodes +meshlinkapp +monitor +groupchat diff --git a/examples/Makefile.am b/examples/Makefile.am new file mode 100644 index 0000000..ee64fa8 --- /dev/null +++ b/examples/Makefile.am @@ -0,0 +1,28 @@ +EXTRA_PROGRAMS = meshlinkapp chat chatpp manynodes channels groupchat + +AM_CPPFLAGS = $(PTHREAD_CFLAGS) -I${top_srcdir}/src -iquote. -Wall +AM_LDFLAGS = $(PTHREAD_LIBS) + +meshlinkapp_SOURCES = meshlinkapp.c +meshlinkapp_LDADD = ${top_builddir}/src/libmeshlink.la + +chat_SOURCES = chat.c +chat_LDADD = ${top_builddir}/src/libmeshlink.la + +chatpp_SOURCES = chatpp.cc +chatpp_LDADD = ${top_builddir}/src/libmeshlink.la + +manynodes_SOURCES = manynodes.c +manynodes_LDADD = ${top_builddir}/src/libmeshlink.la + +channels_SOURCES = channels.c +channels_LDADD = ${top_builddir}/src/libmeshlink.la + +if MONITOR +EXTRA_PROGRAMS += monitor +monitor_SOURCES = monitor.c +monitor_LDADD = ${top_builddir}/src/libmeshlink.la $(NCURSES_LIBS) -lm +endif + +groupchat_SOURCES = groupchat.c +groupchat_LDADD = ${top_builddir}/src/libmeshlink.la diff --git a/examples/channels.c b/examples/channels.c new file mode 100644 index 0000000..0f23de8 --- /dev/null +++ b/examples/channels.c @@ -0,0 +1,336 @@ +#include +#include +#include +#include +#include "../src/meshlink.h" + +#define CHAT_PORT 531 + +static void log_message(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) { + (void) mesh; + + static const char *levelstr[] = { + [MESHLINK_DEBUG] = "\x1b[34mDEBUG", + [MESHLINK_INFO] = "\x1b[32mINFO", + [MESHLINK_WARNING] = "\x1b[33mWARNING", + [MESHLINK_ERROR] = "\x1b[31mERROR", + [MESHLINK_CRITICAL] = "\x1b[31mCRITICAL", + }; + fprintf(stderr, "%s:\x1b[0m %s\n", levelstr[level], text); +} + +static void channel_receive(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) { + if(!len) { + if(meshlink_errno) { + fprintf(stderr, "Error while reading data from %s: %s\n", channel->node->name, meshlink_strerror(meshlink_errno)); + } else { + fprintf(stderr, "Chat connection closed by %s\n", channel->node->name); + } + + channel->node->priv = NULL; + meshlink_channel_close(mesh, channel); + return; + } + + // TODO: we now have TCP semantics, don't expect exactly one message per receive call. + + printf("%s says: ", channel->node->name); + fwrite(data, len, 1, stdout); + fputc('\n', stdout); +} + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + (void)data; + (void)len; + + // Only accept connections to the chat port + if(port != CHAT_PORT) { + fprintf(stderr, "Rejected incoming channel from '%s' to port %u\n", channel->node->name, port); + return false; + } + + fprintf(stderr, "Accepted incoming channel from '%s'\n", channel->node->name); + + // Remember the channel + channel->node->priv = channel; + + // Set the receive callback + meshlink_set_channel_receive_cb(mesh, channel, channel_receive); + + // Accept this channel + return true; +} + +static void channel_poll(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + + fprintf(stderr, "Channel to '%s' connected\n", channel->node->name); + meshlink_set_channel_poll_cb(mesh, channel, NULL); +} + +static void node_status(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(reachable) { + printf("%s joined.\n", node->name); + } else { + printf("%s left.\n", node->name); + } +} + +static meshlink_node_t **nodes; +static size_t nnodes; + +static void parse_command(meshlink_handle_t *mesh, char *buf) { + char *arg = strchr(buf, ' '); + + if(arg) { + *arg++ = 0; + } + + if(!strcasecmp(buf, "invite")) { + char *invitation; + + if(!arg) { + fprintf(stderr, "/invite requires an argument!\n"); + return; + } + + invitation = meshlink_invite(mesh, NULL, arg); + + if(!invitation) { + fprintf(stderr, "Could not invite '%s': %s\n", arg, meshlink_strerror(meshlink_errno)); + return; + } + + printf("Invitation for %s: %s\n", arg, invitation); + free(invitation); + } else if(!strcasecmp(buf, "join")) { + if(!arg) { + fprintf(stderr, "/join requires an argument!\n"); + return; + } + + meshlink_stop(mesh); + + if(!meshlink_join(mesh, arg)) { + fprintf(stderr, "Could not join using invitation: %s\n", meshlink_strerror(meshlink_errno)); + } else { + fprintf(stderr, "Invitation accepted!\n"); + } + + if(!meshlink_start(mesh)) { + fprintf(stderr, "Could not restart MeshLink: %s\n", meshlink_strerror(meshlink_errno)); + exit(1); + } + } else if(!strcasecmp(buf, "kick")) { + if(!arg) { + fprintf(stderr, "/kick requires an argument!\n"); + return; + } + + meshlink_node_t *node = meshlink_get_node(mesh, arg); + + if(!node) { + fprintf(stderr, "Error looking up '%s': %s\n", arg, meshlink_strerror(meshlink_errno)); + return; + } + + if(!meshlink_blacklist(mesh, node)) { + fprintf(stderr, "Error blacklising '%s': %s", arg, meshlink_strerror(meshlink_errno)); + return; + } + + printf("Node '%s' blacklisted.\n", arg); + } else if(!strcasecmp(buf, "whitelist")) { + if(!arg) { + fprintf(stderr, "/whitelist requires an argument!\n"); + return; + } + + meshlink_node_t *node = meshlink_get_node(mesh, arg); + + if(!node) { + fprintf(stderr, "Error looking up '%s': %s\n", arg, meshlink_strerror(meshlink_errno)); + return; + } + + if(!meshlink_whitelist(mesh, node)) { + fprintf(stderr, "Error whitelising '%s': %s", arg, meshlink_strerror(meshlink_errno)); + return; + } + + printf("Node '%s' whitelisted.\n", arg); + } else if(!strcasecmp(buf, "who")) { + if(!arg) { + nodes = meshlink_get_all_nodes(mesh, nodes, &nnodes); + + if(!nnodes) { + fprintf(stderr, "Could not get list of nodes: %s\n", meshlink_strerror(meshlink_errno)); + } else { + printf("%zu known nodes:", nnodes); + + for(size_t i = 0; i < nnodes; i++) { + printf(" %s", nodes[i]->name); + } + + printf("\n"); + } + } else { + meshlink_node_t *node = meshlink_get_node(mesh, arg); + + if(!node) { + fprintf(stderr, "Error looking up '%s': %s\n", arg, meshlink_strerror(meshlink_errno)); + } else { + printf("Node %s found\n", arg); + } + } + } else if(!strcasecmp(buf, "quit")) { + printf("Bye!\n"); + fclose(stdin); + } else if(!strcasecmp(buf, "help")) { + printf( + ": Send a message to the given node.\n" + " Subsequent messages don't need the : prefix.\n" + "/invite Create an invitation for a new node.\n" + "/join Join an existing mesh using an invitation.\n" + "/kick Blacklist the given node.\n" + "/who [] List all nodes or show information about the given node.\n" + "/quit Exit this program.\n" + ); + } else { + fprintf(stderr, "Unknown command '/%s'\n", buf); + } +} + +static void parse_input(meshlink_handle_t *mesh, char *buf) { + static meshlink_node_t *destination; + size_t len; + + if(!buf) { + return; + } + + // Remove newline. + + len = strlen(buf); + + if(len && buf[len - 1] == '\n') { + buf[--len] = 0; + } + + if(len && buf[len - 1] == '\r') { + buf[--len] = 0; + } + + // Ignore empty lines. + + if(!len) { + return; + } + + // Commands start with '/' + + if(*buf == '/') { + parse_command(mesh, buf + 1); + return; + } + + // Lines in the form "name: message..." set the destination node. + + char *msg = buf; + char *colon = strchr(buf, ':'); + + if(colon) { + *colon = 0; + msg = colon + 1; + + if(*msg == ' ') { + msg++; + } + + destination = meshlink_get_node(mesh, buf); + + if(!destination) { + fprintf(stderr, "Error looking up '%s': %s\n", buf, meshlink_strerror(meshlink_errno)); + return; + } + } + + if(!destination) { + fprintf(stderr, "Who are you talking to? Write 'name: message...'\n"); + return; + } + + // We want to have one channel per node. + // We keep the pointer to the meshlink_channel_t in the priv field of that node. + meshlink_channel_t *channel = destination->priv; + + if(!channel) { + fprintf(stderr, "Opening chat channel to '%s'\n", destination->name); + channel = meshlink_channel_open(mesh, destination, CHAT_PORT, channel_receive, NULL, 0); + + if(!channel) { + fprintf(stderr, "Could not create channel to '%s': %s\n", destination->name, meshlink_strerror(meshlink_errno)); + return; + } + + destination->priv = channel; + meshlink_set_channel_poll_cb(mesh, channel, channel_poll); + } + + if(!meshlink_channel_send(mesh, channel, msg, strlen(msg))) { + fprintf(stderr, "Could not send message to '%s': %s\n", destination->name, meshlink_strerror(meshlink_errno)); + return; + } + + printf("Message sent to '%s'.\n", destination->name); +} + +int main(int argc, char *argv[]) { + const char *confbase = ".chat"; + const char *nick = NULL; + char buf[1024]; + + if(argc > 1) { + confbase = argv[1]; + } + + if(argc > 2) { + nick = argv[2]; + } + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_message); + + meshlink_handle_t *mesh = meshlink_open(confbase, nick, "chat", DEV_CLASS_STATIONARY); + + if(!mesh) { + fprintf(stderr, "Could not open MeshLink: %s\n", meshlink_strerror(meshlink_errno)); + return 1; + } + + meshlink_set_node_status_cb(mesh, node_status); + meshlink_set_log_cb(mesh, MESHLINK_INFO, log_message); + + // Set the channel accept callback. This implicitly turns on channels for all nodes. + // This replaces the call to meshlink_set_receive_cb(). + meshlink_set_channel_accept_cb(mesh, channel_accept); + + if(!meshlink_start(mesh)) { + fprintf(stderr, "Could not start MeshLink: %s\n", meshlink_strerror(meshlink_errno)); + return 1; + } + + printf("Chat started.\nType /help for a list of commands.\n"); + + while(fgets(buf, sizeof(buf), stdin)) { + parse_input(mesh, buf); + } + + printf("Chat stopping.\n"); + + meshlink_stop(mesh); + meshlink_close(mesh); + + return 0; +} diff --git a/examples/chat.c b/examples/chat.c new file mode 100644 index 0000000..88fe127 --- /dev/null +++ b/examples/chat.c @@ -0,0 +1,279 @@ +#include +#include +#include +#include +#include "../src/meshlink.h" + +static void log_message(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) { + (void)mesh; + + static const char *levelstr[] = { + [MESHLINK_DEBUG] = "\x1b[34mDEBUG", + [MESHLINK_INFO] = "\x1b[32mINFO", + [MESHLINK_WARNING] = "\x1b[33mWARNING", + [MESHLINK_ERROR] = "\x1b[31mERROR", + [MESHLINK_CRITICAL] = "\x1b[31mCRITICAL", + }; + + fprintf(stderr, "%s:\x1b[0m %s\n", levelstr[level], text); +} + +static void receive(meshlink_handle_t *mesh, meshlink_node_t *source, const void *data, size_t len) { + (void)mesh; + + const char *msg = data; + + if(!len || msg[len - 1]) { + fprintf(stderr, "Received invalid data from %s\n", source->name); + return; + } + + printf("%s says: %s\n", source->name, msg); +} + +static void node_status(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(reachable) { + printf("%s joined.\n", node->name); + } else { + printf("%s left.\n", node->name); + } +} + +static meshlink_node_t **nodes; +static size_t nnodes; + +static void parse_command(meshlink_handle_t *mesh, char *buf) { + char *arg = strchr(buf, ' '); + + if(arg) { + *arg++ = 0; + } + + if(!strcasecmp(buf, "invite")) { + char *invitation; + + if(!arg) { + fprintf(stderr, "/invite requires an argument!\n"); + return; + } + + invitation = meshlink_invite(mesh, NULL, arg); + + if(!invitation) { + fprintf(stderr, "Could not invite '%s': %s\n", arg, meshlink_strerror(meshlink_errno)); + return; + } + + printf("Invitation for %s: %s\n", arg, invitation); + free(invitation); + } else if(!strcasecmp(buf, "join")) { + if(!arg) { + fprintf(stderr, "/join requires an argument!\n"); + return; + } + + meshlink_stop(mesh); + + if(!meshlink_join(mesh, arg)) { + fprintf(stderr, "Could not join using invitation: %s\n", meshlink_strerror(meshlink_errno)); + } else { + fprintf(stderr, "Invitation accepted!\n"); + } + + if(!meshlink_start(mesh)) { + fprintf(stderr, "Could not restart MeshLink: %s\n", meshlink_strerror(meshlink_errno)); + exit(1); + } + } else if(!strcasecmp(buf, "kick")) { + if(!arg) { + fprintf(stderr, "/kick requires an argument!\n"); + return; + } + + meshlink_node_t *node = meshlink_get_node(mesh, arg); + + if(!node) { + fprintf(stderr, "Error looking up '%s': %s\n", arg, meshlink_strerror(meshlink_errno)); + return; + } + + if(!meshlink_blacklist(mesh, node)) { + fprintf(stderr, "Error blacklising '%s': %s", arg, meshlink_strerror(meshlink_errno)); + return; + } + + printf("Node '%s' blacklisted.\n", arg); + } else if(!strcasecmp(buf, "whitelist")) { + if(!arg) { + fprintf(stderr, "/whitelist requires an argument!\n"); + return; + } + + meshlink_node_t *node = meshlink_get_node(mesh, arg); + + if(!node) { + fprintf(stderr, "Error looking up '%s': %s\n", arg, meshlink_strerror(meshlink_errno)); + return; + } + + if(!meshlink_whitelist(mesh, node)) { + fprintf(stderr, "Error whitelising '%s': %s", arg, meshlink_strerror(meshlink_errno)); + return; + } + + printf("Node '%s' whitelisted.\n", arg); + } else if(!strcasecmp(buf, "who")) { + if(!arg) { + nodes = meshlink_get_all_nodes(mesh, nodes, &nnodes); + + if(!nnodes) { + fprintf(stderr, "Could not get list of nodes: %s\n", meshlink_strerror(meshlink_errno)); + } else { + printf("%zu known nodes:", nnodes); + + for(size_t i = 0; i < nnodes; i++) { + printf(" %s", nodes[i]->name); + } + + printf("\n"); + } + } else { + meshlink_node_t *node = meshlink_get_node(mesh, arg); + + if(!node) { + fprintf(stderr, "Error looking up '%s': %s\n", arg, meshlink_strerror(meshlink_errno)); + } else { + printf("Node %s found\n", arg); + } + } + } else if(!strcasecmp(buf, "quit")) { + printf("Bye!\n"); + fclose(stdin); + } else if(!strcasecmp(buf, "help")) { + printf( + ": Send a message to the given node.\n" + " Subsequent messages don't need the : prefix.\n" + "/invite Create an invitation for a new node.\n" + "/join Join an existing mesh using an invitation.\n" + "/kick Blacklist the given node.\n" + "/who [] List all nodes or show information about the given node.\n" + "/quit Exit this program.\n" + ); + } else { + fprintf(stderr, "Unknown command '/%s'\n", buf); + } +} + +static void parse_input(meshlink_handle_t *mesh, char *buf) { + static meshlink_node_t *destination; + size_t len; + + if(!buf) { + return; + } + + // Remove newline. + + len = strlen(buf); + + if(len && buf[len - 1] == '\n') { + buf[--len] = 0; + } + + if(len && buf[len - 1] == '\r') { + buf[--len] = 0; + } + + // Ignore empty lines. + + if(!len) { + return; + } + + // Commands start with '/' + + if(*buf == '/') { + parse_command(mesh, buf + 1); + return; + } + + // Lines in the form "name: message..." set the destination node. + + char *msg = buf; + char *colon = strchr(buf, ':'); + + if(colon) { + *colon = 0; + msg = colon + 1; + + if(*msg == ' ') { + msg++; + } + + destination = meshlink_get_node(mesh, buf); + + if(!destination) { + fprintf(stderr, "Error looking up '%s': %s\n", buf, meshlink_strerror(meshlink_errno)); + return; + } + } + + if(!destination) { + fprintf(stderr, "Who are you talking to? Write 'name: message...'\n"); + return; + } + + if(!meshlink_send(mesh, destination, msg, strlen(msg) + 1)) { + fprintf(stderr, "Could not send message to '%s': %s\n", destination->name, meshlink_strerror(meshlink_errno)); + return; + } + + printf("Message sent to '%s'.\n", destination->name); +} + +int main(int argc, char *argv[]) { + const char *confbase = ".chat"; + const char *nick = NULL; + char buf[1024]; + + if(argc > 1) { + confbase = argv[1]; + } + + if(argc > 2) { + nick = argv[2]; + } + + meshlink_set_log_cb(NULL, MESHLINK_INFO, log_message); + + meshlink_handle_t *mesh = meshlink_open(confbase, nick, "chat", DEV_CLASS_STATIONARY); + + if(!mesh) { + fprintf(stderr, "Could not open MeshLink: %s\n", meshlink_strerror(meshlink_errno)); + return 1; + } + + meshlink_set_receive_cb(mesh, receive); + meshlink_set_node_status_cb(mesh, node_status); + meshlink_set_log_cb(mesh, MESHLINK_INFO, log_message); + + if(!meshlink_start(mesh)) { + fprintf(stderr, "Could not start MeshLink: %s\n", meshlink_strerror(meshlink_errno)); + return 1; + } + + printf("Chat started.\nType /help for a list of commands.\n"); + + while(fgets(buf, sizeof(buf), stdin)) { + parse_input(mesh, buf); + } + + printf("Chat stopping.\n"); + + meshlink_stop(mesh); + meshlink_close(mesh); + + return 0; +} diff --git a/examples/chatpp.cc b/examples/chatpp.cc new file mode 100644 index 0000000..294ca86 --- /dev/null +++ b/examples/chatpp.cc @@ -0,0 +1,242 @@ +#include +#include +#include +#include +#include "../src/meshlink++.h" + +class ChatMesh : public meshlink::mesh { +public: + void log(meshlink::log_level_t level, const char *text) { + const char *levelstr[] = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}; + fprintf(stderr, "%s: %s\n", levelstr[level], text); + } + + void receive(meshlink::node *source, const void *data, size_t len) { + const char *msg = (const char *)data; + + if(!len || msg[len - 1]) { + fprintf(stderr, "Received invalid data from %s\n", source->name); + return; + } + + printf("%s says: %s\n", source->name, msg); + } + + void node_status(meshlink::node *node, bool reachable) { + if(reachable) { + printf("%s joined.\n", node->name); + } else { + printf("%s left.\n", node->name); + } + } +}; + +static meshlink::node **nodes; +static size_t nnodes; + +static void parse_command(meshlink::mesh *mesh, char *buf) { + char *arg = strchr(buf, ' '); + + if(arg) { + *arg++ = 0; + } + + if(!strcasecmp(buf, "invite")) { + char *invitation; + + if(!arg) { + fprintf(stderr, "/invite requires an argument!\n"); + return; + } + + invitation = mesh->invite(NULL, arg); + + if(!invitation) { + fprintf(stderr, "Could not invite '%s': %s\n", arg, meshlink::strerror()); + return; + } + + printf("Invitation for %s: %s\n", arg, invitation); + free(invitation); + } else if(!strcasecmp(buf, "join")) { + if(!arg) { + fprintf(stderr, "/join requires an argument!\n"); + return; + } + + mesh->stop(); + + if(!mesh->join(arg)) { + fprintf(stderr, "Could not join using invitation: %s\n", meshlink::strerror()); + } else { + fprintf(stderr, "Invitation accepted!\n"); + } + + if(!mesh->start()) { + fprintf(stderr, "Could not restart MeshLink: %s\n", meshlink::strerror()); + exit(1); + } + } else if(!strcasecmp(buf, "kick")) { + if(!arg) { + fprintf(stderr, "/kick requires an argument!\n"); + return; + } + + meshlink::node *node = mesh->get_node(arg); + + if(!node) { + fprintf(stderr, "Error looking up '%s': %s\n", arg, meshlink::strerror()); + return; + } + + mesh->blacklist(node); + + printf("Node '%s' blacklisted.\n", arg); + } else if(!strcasecmp(buf, "who")) { + if(!arg) { + nodes = mesh->get_all_nodes(nodes, &nnodes); + + if(!nodes) { + fprintf(stderr, "Could not get list of nodes: %s\n", meshlink::strerror()); + } else { + printf("%lu known nodes:", (unsigned long)nnodes); + + for(size_t i = 0; i < nnodes; i++) { + printf(" %s", nodes[i]->name); + } + + printf("\n"); + } + } else { + meshlink::node *node = mesh->get_node(arg); + + if(!node) { + fprintf(stderr, "Error looking up '%s': %s\n", arg, meshlink::strerror()); + } else { + printf("Node %s found\n", arg); + } + } + } else if(!strcasecmp(buf, "quit")) { + printf("Bye!\n"); + fclose(stdin); + } else if(!strcasecmp(buf, "help")) { + printf( + ": Send a message to the given node.\n" + " Subsequent messages don't need the : prefix.\n" + "/invite Create an invitation for a new node.\n" + "/join Join an existing mesh using an invitation.\n" + "/kick Blacklist the given node.\n" + "/who [] List all nodes or show information about the given node.\n" + "/quit Exit this program.\n" + ); + } else { + fprintf(stderr, "Unknown command '/%s'\n", buf); + } +} + +static void parse_input(meshlink::mesh *mesh, char *buf) { + static meshlink::node *destination; + size_t len; + + if(!buf) { + return; + } + + // Remove newline. + + len = strlen(buf); + + if(len && buf[len - 1] == '\n') { + buf[--len] = 0; + } + + if(len && buf[len - 1] == '\r') { + buf[--len] = 0; + } + + // Ignore empty lines. + + if(!len) { + return; + } + + // Commands start with '/' + + if(*buf == '/') { + return parse_command(mesh, buf + 1); + } + + // Lines in the form "name: message..." set the destination node. + + char *msg = buf; + char *colon = strchr(buf, ':'); + + if(colon) { + *colon = 0; + msg = colon + 1; + + if(*msg == ' ') { + msg++; + } + + destination = mesh->get_node(buf); + + if(!destination) { + fprintf(stderr, "Error looking up '%s': %s\n", buf, meshlink::strerror()); + return; + } + } + + if(!destination) { + fprintf(stderr, "Who are you talking to? Write 'name: message...'\n"); + return; + } + + if(!mesh->send(destination, msg, strlen(msg) + 1)) { + fprintf(stderr, "Could not send message to '%s': %s\n", destination->name, meshlink::strerror()); + return; + } + + printf("Message sent to '%s'.\n", destination->name); +} + + +int main(int argc, char *argv[]) { + const char *confbase = ".chat"; + const char *nick = NULL; + char buf[1024]; + + if(argc > 1) { + confbase = argv[1]; + } + + if(argc > 2) { + nick = argv[2]; + } + + ChatMesh mesh; + mesh.open(confbase, nick, "chatpp", DEV_CLASS_STATIONARY); + + if(!mesh.isOpen()) { + fprintf(stderr, "Could not open MeshLink: %s\n", meshlink::strerror()); + return 1; + } + + if(!mesh.start()) { + fprintf(stderr, "Could not start MeshLink: %s\n", meshlink::strerror()); + return 1; + } + + printf("Chat started.\nType /help for a list of commands.\n"); + + while(fgets(buf, sizeof(buf), stdin)) { + parse_input(&mesh, buf); + } + + printf("Chat stopping.\n"); + + mesh.stop(); + mesh.close(); + + return 0; +} diff --git a/examples/fixlib.txt b/examples/fixlib.txt new file mode 100644 index 0000000..e230dcd --- /dev/null +++ b/examples/fixlib.txt @@ -0,0 +1,3 @@ +MYPATH=`pwd` +echo $MYPATH +export LD_LIBRARY_PATH=$MYPATH'/../src/.libs/' diff --git a/examples/groupchat.c b/examples/groupchat.c new file mode 100644 index 0000000..18e6f61 --- /dev/null +++ b/examples/groupchat.c @@ -0,0 +1,492 @@ +#include +#include +#include +#include + +#include "../src/meshlink.h" +#include "../src/devtools.h" + +#define CHAT_PORT 531 + +static void log_message(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) { + (void) mesh; + + static const char *levelstr[] = { + [MESHLINK_DEBUG] = "\x1b[34mDEBUG", + [MESHLINK_INFO] = "\x1b[32mINFO", + [MESHLINK_WARNING] = "\x1b[33mWARNING", + [MESHLINK_ERROR] = "\x1b[31mERROR", + [MESHLINK_CRITICAL] = "\x1b[31mCRITICAL", + }; + fprintf(stderr, "%s:\x1b[0m %s\n", levelstr[level], text); +} + +static void channel_receive(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) { + if(!len) { + if(meshlink_errno) { + fprintf(stderr, "Error while reading data from %s: %s\n", channel->node->name, meshlink_strerror(meshlink_errno)); + } else { + fprintf(stderr, "Chat connection closed by %s\n", channel->node->name); + } + + channel->node->priv = NULL; + meshlink_channel_close(mesh, channel); + return; + } + + // TODO: we now have TCP semantics, don't expect exactly one message per receive call. + + fprintf(stderr, "%s says: ", channel->node->name); + fwrite(data, len, 1, stderr); + fputc('\n', stderr); +} + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + (void)data; + (void)len; + + // Only accept connections to the chat port + if(port != CHAT_PORT) { + fprintf(stderr, "Rejected incoming channel from '%s' to port %u\n", channel->node->name, port); + return false; + } + + fprintf(stderr, "Accepted incoming channel from '%s'\n", channel->node->name); + + // Remember the channel + channel->node->priv = channel; + + // Set the receive callback + meshlink_set_channel_receive_cb(mesh, channel, channel_receive); + + // Accept this channel + return true; +} + +static void channel_poll(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + + fprintf(stderr, "Channel to '%s' connected\n", channel->node->name); + meshlink_set_channel_poll_cb(mesh, channel, NULL); +} + +static void node_status(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(reachable) { + fprintf(stderr, "%s joined.\n", node->name); + } else { + fprintf(stderr, "%s left.\n", node->name); + } +} + +static void parse_command(meshlink_handle_t *mesh, char *buf) { + char *arg = strchr(buf, ' '); + char *arg1 = NULL; + + if(arg) { + *arg++ = 0; + + arg1 = strchr(arg, ' '); + + if(arg1) { + *arg1++ = 0; + } + + } + + if(!strcasecmp(buf, "invite")) { + if(!arg) { + fprintf(stderr, "/invite requires an argument!\n"); + return; + } + + meshlink_submesh_t *s = NULL; + + if(arg1) { + size_t nmemb; + meshlink_submesh_t **submeshes = devtool_get_all_submeshes(mesh, NULL, &nmemb); + + if(!submeshes || !nmemb) { + fprintf(stderr, "Group does not exist!\n"); + return; + } + + for(size_t i = 0; i < nmemb; i++) { + if(!strcmp(arg1, submeshes[i]->name)) { + s = submeshes[i]; + break; + } + } + + free(submeshes); + + if(!s) { + fprintf(stderr, "Group is not yet created!\n"); + return; + } + } + + char *invitation = meshlink_invite(mesh, s, arg); + + if(!invitation) { + fprintf(stderr, "Could not invite '%s': %s\n", arg, meshlink_strerror(meshlink_errno)); + return; + } + + fprintf(stderr, "Invitation for %s: %s\n", arg, invitation); + free(invitation); + } else if(!strcasecmp(buf, "canonical")) { + bool set; + char *host = NULL, *port = NULL; + + if(!arg) { + fprintf(stderr, "/canonical requires an argument!\n"); + return; + } + + if((0 == strncasecmp(arg, "-h", 2)) && (strlen(arg) > 2)) { + host = arg + 2; + } else if((0 == strncasecmp(arg, "-p", 2)) && (strlen(arg) > 2)) { + port = arg + 2; + } else { + fprintf(stderr, "Unknown argument: %s!\n", arg); + return; + } + + if(arg1) { + if((0 == strncasecmp(arg1, "-h", 2)) && (strlen(arg1) > 2)) { + host = arg1 + 2; + } else if((0 == strncasecmp(arg1, "-p", 2)) && (strlen(arg1) > 2)) { + port = arg1 + 2; + } else { + fprintf(stderr, "Unknown argument: %s!\n", arg1); + return; + } + } + + if(!host && !port) { + fprintf(stderr, "Unable to set Canonical address because no valid arguments are found!\n"); + return; + } + + set = meshlink_set_canonical_address(mesh, meshlink_get_self(mesh), host, port); + + if(!set) { + fprintf(stderr, "Could not set canonical address '%s:%s': %s\n", host, port, meshlink_strerror(meshlink_errno)); + return; + } + + fprintf(stderr, "Canonical address set as '%s:%s'\n", host, port); + } else if(!strcasecmp(buf, "group")) { + if(!arg) { + fprintf(stderr, "/group requires an argument!\n"); + return; + } + + meshlink_submesh_t *s = meshlink_submesh_open(mesh, arg); + + if(!s) { + fprintf(stderr, "Could not create group: %s\n", meshlink_strerror(meshlink_errno)); + } else { + fprintf(stderr, "Group '%s' created!\n", s->name); + } + } else if(!strcasecmp(buf, "join")) { + if(!arg) { + fprintf(stderr, "/join requires an argument!\n"); + return; + } + + meshlink_stop(mesh); + + if(!meshlink_join(mesh, arg)) { + fprintf(stderr, "Could not join using invitation: %s\n", meshlink_strerror(meshlink_errno)); + } else { + fprintf(stderr, "Invitation accepted!\n"); + } + + if(!meshlink_start(mesh)) { + fprintf(stderr, "Could not restart MeshLink: %s\n", meshlink_strerror(meshlink_errno)); + exit(1); + } + } else if(!strcasecmp(buf, "kick")) { + if(!arg) { + fprintf(stderr, "/kick requires an argument!\n"); + return; + } + + meshlink_node_t *node = meshlink_get_node(mesh, arg); + + if(!node) { + fprintf(stderr, "Error looking up '%s': %s\n", arg, meshlink_strerror(meshlink_errno)); + return; + } + + if(!meshlink_blacklist(mesh, node)) { + fprintf(stderr, "Error blacklising '%s': %s", arg, meshlink_strerror(meshlink_errno)); + return; + } + + fprintf(stderr, "Node '%s' blacklisted.\n", arg); + } else if(!strcasecmp(buf, "whitelist")) { + if(!arg) { + fprintf(stderr, "/whitelist requires an argument!\n"); + return; + } + + meshlink_node_t *node = meshlink_get_node(mesh, arg); + + if(!node) { + fprintf(stderr, "Error looking up '%s': %s\n", arg, meshlink_strerror(meshlink_errno)); + return; + } + + if(!meshlink_whitelist(mesh, node)) { + fprintf(stderr, "Error whitelising '%s': %s", arg, meshlink_strerror(meshlink_errno)); + return; + } + + fprintf(stderr, "Node '%s' whitelisted.\n", arg); + } else if(!strcasecmp(buf, "who")) { + meshlink_submesh_t *node_group = NULL; + + if(!arg) { + size_t nnodes; + meshlink_node_t **nodes = meshlink_get_all_nodes(mesh, NULL, &nnodes); + + if(!nnodes) { + fprintf(stderr, "Could not get list of nodes: %s\n", meshlink_strerror(meshlink_errno)); + return; + } + + fprintf(stderr, "%lu known nodes:\n", (unsigned long)nnodes); + + for(size_t i = 0; i < nnodes; i++) { + fprintf(stderr, " %lu. %s", (unsigned long)i, nodes[i]->name); + + if((node_group = meshlink_get_node_submesh(mesh, nodes[i]))) { + fprintf(stderr, "\t%s", node_group->name); + } + + fprintf(stderr, "\n"); + } + + fprintf(stderr, "\n"); + + free(nodes); + } else { + meshlink_node_t *node = meshlink_get_node(mesh, arg); + + if(!node) { + fprintf(stderr, "Error looking up '%s': %s\n", arg, meshlink_strerror(meshlink_errno)); + } else { + fprintf(stderr, "Node %s found", arg); + + if((node_group = meshlink_get_node_submesh(mesh, node))) { + fprintf(stderr, " in group %s", node_group->name); + } + + fprintf(stderr, "\n"); + } + } + } else if(!strcasecmp(buf, "listgroup")) { + if(!arg) { + fprintf(stderr, "/listgroup requires an argument!\n"); + return; + } + + size_t nmemb; + meshlink_submesh_t **submeshes = devtool_get_all_submeshes(mesh, NULL, &nmemb); + + if(!submeshes || !nmemb) { + fprintf(stderr, "Group does not exist!\n"); + return; + } + + meshlink_submesh_t *s = NULL; + + for(size_t i = 0; i < nmemb; i++) { + if(!strcmp(arg, submeshes[i]->name)) { + s = submeshes[i]; + break; + } + } + + free(submeshes); + + if(!s) { + fprintf(stderr, "Group %s does not exist!\n", arg); + return; + } + + size_t nnodes; + meshlink_node_t **nodes = meshlink_get_all_nodes_by_submesh(mesh, s, NULL, &nnodes); + + if(!nodes || !nnodes) { + fprintf(stderr, "Group %s does not contain any nodes!\n", arg); + return; + } + + fprintf(stderr, "%zu known nodes in group %s:", nnodes, arg); + + for(size_t i = 0; i < nnodes; i++) { + fprintf(stderr, " %s", nodes[i]->name); + } + + fprintf(stderr, "\n"); + + free(nodes); + } else if(!strcasecmp(buf, "quit")) { + fprintf(stderr, "Bye!\n"); + fclose(stdin); + } else if(!strcasecmp(buf, "help")) { + fprintf(stderr, + ": Send a message to the given node.\n" + " Subsequent messages don't need the : prefix.\n" + "/group Create a new group" + "/invite [submesh] Create an invitation for a new node.\n" + " Node joins either coremesh or submesh depending on submesh parameter.\n" + "/join Join an existing mesh using an invitation.\n" + "/kick Blacklist the given node.\n" + "/who [] List all nodes or show information about the given node.\n" + "/listgroup List all nodes in a given group.\n" + "/canonical -h -p Set Canonical address to be present in invitation.\n" + " Any one of two options an be specified. At least one option must be present\n" + "/quit Exit this program.\n" + ); + } else { + fprintf(stderr, "Unknown command '/%s'\n", buf); + } +} + +static void parse_input(meshlink_handle_t *mesh, char *buf) { + static meshlink_node_t *destination; + size_t len; + + if(!buf) { + return; + } + + // Remove newline. + + len = strlen(buf); + + if(len && buf[len - 1] == '\n') { + buf[--len] = 0; + } + + if(len && buf[len - 1] == '\r') { + buf[--len] = 0; + } + + // Ignore empty lines. + + if(!len) { + return; + } + + // Commands start with '/' + + if(*buf == '/') { + parse_command(mesh, buf + 1); + return; + } + + // Lines in the form "name: message..." set the destination node. + + char *msg = buf; + char *colon = strchr(buf, ':'); + + if(colon) { + *colon = 0; + msg = colon + 1; + + if(*msg == ' ') { + msg++; + } + + destination = meshlink_get_node(mesh, buf); + + if(!destination) { + fprintf(stderr, "Error looking up '%s': %s\n", buf, meshlink_strerror(meshlink_errno)); + return; + } + } + + if(!destination) { + fprintf(stderr, "Who are you talking to? Write 'name: message...'\n"); + return; + } + + // We want to have one channel per node. + // We keep the pointer to the meshlink_channel_t in the priv field of that node. + meshlink_channel_t *channel = destination->priv; + + if(!channel) { + fprintf(stderr, "Opening chat channel to '%s'\n", destination->name); + channel = meshlink_channel_open(mesh, destination, CHAT_PORT, channel_receive, NULL, 0); + + if(!channel) { + fprintf(stderr, "Could not create channel to '%s': %s\n", destination->name, meshlink_strerror(meshlink_errno)); + return; + } + + destination->priv = channel; + meshlink_set_channel_poll_cb(mesh, channel, channel_poll); + } + + if(!meshlink_channel_send(mesh, channel, msg, strlen(msg))) { + fprintf(stderr, "Could not send message to '%s': %s\n", destination->name, meshlink_strerror(meshlink_errno)); + return; + } + + fprintf(stderr, "Message sent to '%s'.\n", destination->name); +} + +int main(int argc, char *argv[]) { + const char *confbase = ".chat"; + const char *nick = NULL; + char buf[1024]; + + if(argc > 1) { + confbase = argv[1]; + } + + if(argc > 2) { + nick = argv[2]; + } + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_message); + + meshlink_handle_t *mesh = meshlink_open(confbase, nick, "chat", DEV_CLASS_STATIONARY); + + if(!mesh) { + fprintf(stderr, "Could not open MeshLink: %s\n", meshlink_strerror(meshlink_errno)); + return 1; + } + + meshlink_set_node_status_cb(mesh, node_status); + meshlink_set_log_cb(mesh, MESHLINK_INFO, log_message); + + // Set the channel accept callback. This implicitly turns on channels for all nodes. + // This replaces the call to meshlink_set_receive_cb(). + meshlink_set_channel_accept_cb(mesh, channel_accept); + + if(!meshlink_start(mesh)) { + fprintf(stderr, "Could not start MeshLink: %s\n", meshlink_strerror(meshlink_errno)); + return 1; + } + + fprintf(stderr, "Chat started.\nType /help for a list of commands.\n"); + + while(fgets(buf, sizeof(buf), stdin)) { + parse_input(mesh, buf); + } + + fprintf(stderr, "Chat stopping.\n"); + + meshlink_stop(mesh); + meshlink_close(mesh); + + return 0; +} diff --git a/examples/manynodes.c b/examples/manynodes.c new file mode 100644 index 0000000..64ccd9c --- /dev/null +++ b/examples/manynodes.c @@ -0,0 +1,504 @@ +#include +#include +#include +#include +#include +#include + +#if !defined(_WIN32) && !defined(__APPLE__) +#include +#elif defined(__APPLE__) +#include +#endif + +#include "../src/meshlink.h" +#include "../src/devtools.h" + +#include +#include +#include + +#include +#include +#include + +static int n = 10; +static meshlink_handle_t **meshes; +static const char *namesprefix = "machine1"; +static int nodeindex = 0; + +static meshlink_node_t **nodes; +static size_t nnodes; + +static void log_message(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) { + const char *levelstr[] = { + [MESHLINK_DEBUG] = "\x1b[34mDEBUG", + [MESHLINK_INFO] = "\x1b[32mINFO", + [MESHLINK_WARNING] = "\x1b[33mWARNING", + [MESHLINK_ERROR] = "\x1b[31mERROR", + [MESHLINK_CRITICAL] = "\x1b[31mCRITICAL", + }; + fprintf(stderr, "%s\t%s:\x1b[0m %s\n", mesh ? mesh->name : "global", levelstr[level], text); +} + +//Test mesh sending data +static void testmesh(void) { + for(int nindex = 0; nindex < n; nindex++) { + + nodes = meshlink_get_all_nodes(meshes[nindex], nodes, &nnodes); + + if(!nodes) { + fprintf(stderr, "Could not get list of nodes: %s\n", meshlink_strerror(meshlink_errno)); + } else { + printf("%zu known nodes:\n", nnodes); + + for(size_t i = 0; i < nnodes; i++) { + //printf(" %s\n", nodes[i]->name); + if(!meshlink_send(meshes[nindex], nodes[i], "magic", strlen("magic") + 1)) { + fprintf(stderr, "Could not send message to '%s': %s\n", nodes[i]->name, meshlink_strerror(meshlink_errno)); + } + } + + } + + } +} +// Make all nodes know about each other by importing each others public keys and addresses. +static void linkmesh(void) { + for(int i = 0; i < n; i++) { + char *datai = meshlink_export(meshes[i]); + + for(int j = i + 1; j < n; j++) { + char *dataj = meshlink_export(meshes[j]); + + if(!meshlink_import(meshes[i], dataj) || !meshlink_import(meshes[j], datai)) { + fprintf(stderr, "Could not exchange keys between %s and %s: %s\n", meshes[i]->name, meshes[j]->name, meshlink_strerror(meshlink_errno)); + } + + free(dataj); + } + + free(datai); + } +} + +static bool exportmeshgraph(const char *path) { + assert(path); + + struct stat ps; + int psr = stat(path, &ps); + + if(psr == 0 || errno != ENOENT) { + if(psr == -1) { + perror("stat"); + } else { + fprintf(stderr, "%s exists already\n", path); + } + + return false; + } + + FILE *stream = fopen(path, "w"); + + if(!stream) { + perror("stream"); + return false; + } + + if(!devtool_export_json_all_edges_state(meshes[0], stream)) { + fclose(stream); + fprintf(stderr, "could not export graph\n"); + return false; + } + + fclose(stream); + return true; +} + + +static void exportmeshgraph_timer(int signum) { + (void)signum; + + struct timeval ts; + gettimeofday(&ts, NULL); + + char name[1024]; + snprintf(name, sizeof(name), "%sgraph_%ld_%03ld.json", namesprefix, ts.tv_sec, ts.tv_usec / 1000L); + + exportmeshgraph(name); +} + +#ifdef ITIMER_REAL +static bool exportmeshgraph_started = false; + +static bool exportmeshgraph_end(void) { + if(!exportmeshgraph_started) { + return false; + } + + struct itimerval zero_timer; + + setitimer(ITIMER_REAL, &zero_timer, NULL); + + exportmeshgraph_started = false; + + return true; +} + +static bool exportmeshgraph_begin(const char *timeout_str) { + if(!timeout_str) { + return false; + } + + if(exportmeshgraph_started) { + if(!exportmeshgraph_end()) { + return false; + } + } + + // get timeout + int timeout = atoi(timeout_str); + + if(timeout < 100) { + timeout = 100; + } + + int timeout_sec = timeout / 1000; + int timeout_msec = timeout % 1000; + + /* Install timer_handler as the signal handler for SIGALRM. */ + signal(SIGALRM, exportmeshgraph_timer); + + /* Configure the timer to expire immediately... */ + struct itimerval timer; + timer.it_value.tv_sec = 0; + timer.it_value.tv_usec = 1000; + + /* ... and every X msec after that. */ + timer.it_interval.tv_sec = timeout_sec; + timer.it_interval.tv_usec = timeout_msec * 1000; + + /* Start a real timer. */ + setitimer(ITIMER_REAL, &timer, NULL); + + exportmeshgraph_started = true; + + return true; +} +#else +static bool exportmeshgraph_end(void) { + return false; +} + +static bool exportmeshgraph_begin(const char *timeout_str) { + return false; +} +#endif + +static void parse_command(char *buf) { + char *arg = strchr(buf, ' '); + + if(arg) { + *arg++ = 0; + } + + if(!strcasecmp(buf, "invite")) { + char *invitation; + + if(!arg) { + fprintf(stderr, "/invite requires an argument!\n"); + return; + } + + invitation = meshlink_invite(meshes[nodeindex], NULL, arg); + + if(!invitation) { + fprintf(stderr, "Could not invite '%s': %s\n", arg, meshlink_strerror(meshlink_errno)); + return; + } + + printf("Invitation for %s: %s\n", arg, invitation); + free(invitation); + } else if(!strcasecmp(buf, "join")) { + if(!arg) { + fprintf(stderr, "/join requires an argument!\n"); + return; + } + + meshlink_stop(meshes[nodeindex]); + + if(!meshlink_join(meshes[nodeindex], arg)) { + fprintf(stderr, "Could not join using invitation: %s\n", meshlink_strerror(meshlink_errno)); + } else { + fprintf(stderr, "Invitation accepted!\n"); + } + + if(!meshlink_start(meshes[nodeindex])) { + fprintf(stderr, "Could not restart MeshLink: %s\n", meshlink_strerror(meshlink_errno)); + exit(1); + } + } else if(!strcasecmp(buf, "kick")) { + if(!arg) { + fprintf(stderr, "/kick requires an argument!\n"); + return; + } + + meshlink_node_t *node = meshlink_get_node(meshes[nodeindex], arg); + + if(!node) { + fprintf(stderr, "Unknown node '%s'\n", arg); + return; + } + + if(!meshlink_blacklist(meshes[nodeindex], node)) { + fprintf(stderr, "Error blacklising '%s': %s", arg, meshlink_strerror(meshlink_errno)); + return; + } + + printf("Node '%s' blacklisted.\n", arg); + } else if(!strcasecmp(buf, "whitelist")) { + if(!arg) { + fprintf(stderr, "/whitelist requires an argument!\n"); + return; + } + + meshlink_node_t *node = meshlink_get_node(meshes[nodeindex], arg); + + if(!node) { + fprintf(stderr, "Error looking up '%s': %s\n", arg, meshlink_strerror(meshlink_errno)); + return; + } + + if(!meshlink_whitelist(meshes[nodeindex], node)) { + fprintf(stderr, "Error whitelising '%s': %s", arg, meshlink_strerror(meshlink_errno)); + return; + } + + printf("Node '%s' whitelisted.\n", arg); + } else if(!strcasecmp(buf, "who")) { + if(!arg) { + nodes = meshlink_get_all_nodes(meshes[nodeindex], nodes, &nnodes); + + if(!nodes) { + fprintf(stderr, "Could not get list of nodes: %s\n", meshlink_strerror(meshlink_errno)); + } else { + printf("%zu known nodes:", nnodes); + + for(size_t i = 0; i < nnodes; i++) { + printf(" %s", nodes[i]->name); + } + + printf("\n"); + } + } else { + meshlink_node_t *node = meshlink_get_node(meshes[nodeindex], arg); + + if(!node) { + fprintf(stderr, "Unknown node '%s'\n", arg); + } else { + printf("Node %s found, pmtu %ld\n", arg, (long int)meshlink_get_pmtu(meshes[nodeindex], node)); + } + } + } else if(!strcasecmp(buf, "link")) { + linkmesh(); + } else if(!strcasecmp(buf, "eg")) { + exportmeshgraph(arg); + } else if(!strcasecmp(buf, "egb")) { + exportmeshgraph_begin(arg); + } else if(!strcasecmp(buf, "ege")) { + exportmeshgraph_end(); + } else if(!strcasecmp(buf, "test")) { + testmesh(); + } else if(!strcasecmp(buf, "select")) { + if(!arg) { + fprintf(stderr, "/select requires an argument!\n"); + return; + } + + nodeindex = atoi(arg); + printf("Index is now %d\n", nodeindex); + } else if(!strcasecmp(buf, "stop")) { + meshlink_stop(meshes[nodeindex]); + } else if(!strcasecmp(buf, "quit")) { + printf("Bye!\n"); + fclose(stdin); + } else if(!strcasecmp(buf, "help")) { + printf( + ": Send a message to the given node.\n" + " Subsequent messages don't need the : prefix.\n" + "/invite Create an invitation for a new node.\n" + "/join Join an existing mesh using an invitation.\n" + "/kick Blacklist the given node.\n" + "/who [] List all nodes or show information about the given node.\n" + "/link Link all nodes together.\n" + "/eg Export graph as json file.\n" + "/test Test functionality sending some data to all nodes\n" + "/select Select the active node running the user commands\n" + "/stop Call meshlink_stop, use /select first to select which node to stop\n" + "/quit Exit this program.\n" + ); + } else { + fprintf(stderr, "Unknown command '/%s'\n", buf); + } +} + +static void parse_input(char *buf) { + static meshlink_node_t *destination; + size_t len; + + if(!buf) { + return; + } + + // Remove newline. + + len = strlen(buf); + + if(len && buf[len - 1] == '\n') { + buf[--len] = 0; + } + + if(len && buf[len - 1] == '\r') { + buf[--len] = 0; + } + + // Ignore empty lines. + + if(!len) { + return; + } + + // Commands start with '/' + + if(*buf == '/') { + parse_command(buf + 1); + return; + } + + // Lines in the form "name: message..." set the destination node. + + char *msg = buf; + char *colon = strchr(buf, ':'); + + if(colon) { + *colon = 0; + msg = colon + 1; + + if(*msg == ' ') { + msg++; + } + + destination = meshlink_get_node(meshes[nodeindex], buf); + + if(!destination) { + fprintf(stderr, "Unknown node '%s'\n", buf); + return; + } + } + + if(!destination) { + fprintf(stderr, "Who are you talking to? Write 'name: message...'\n"); + return; + } + + if(!meshlink_send(meshes[nodeindex], destination, msg, strlen(msg) + 1)) { + fprintf(stderr, "Could not send message to '%s': %s\n", destination->name, meshlink_strerror(meshlink_errno)); + return; + } + + printf("Message sent to '%s'.\n", destination->name); +} + +int main(int argc, char *argv[]) { + const char *basebase = ".manynodes"; + const char *graphexporttimeout = NULL; + char buf[1024]; + + if(argc > 1) { + n = atoi(argv[1]); + } + + if(n < 1) { + fprintf(stderr, "Usage: %s [number of local nodes] [confbase] [prefixnodenames] [graphexport timeout]\n", argv[0]); + return 1; + } + + if(argc > 2) { + basebase = argv[2]; + } + + if(argc > 3) { + namesprefix = argv[3]; + } + + if(argc > 4) { + graphexporttimeout = argv[4]; + } + + meshes = calloc(n, sizeof(*meshes)); + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_message); +#ifndef _WIN32 + mkdir(basebase, 0750); +#else + mkdir(basebase); +#endif + + char filename[PATH_MAX]; + char nodename[100]; + + for(int i = 0; i < n; i++) { + snprintf(nodename, sizeof(nodename), "%snode%d", namesprefix, i); + snprintf(filename, sizeof(filename), "%s/%s", basebase, nodename); + + if(n / (i + 1) > n / 4) { + meshes[i] = meshlink_open(filename, nodename, "manynodes", DEV_CLASS_BACKBONE); + } else { + meshes[i] = meshlink_open(filename, nodename, "manynodes", DEV_CLASS_PORTABLE); + } + + meshlink_set_log_cb(meshes[i], MESHLINK_DEBUG, log_message); + + if(!meshes[i]) { + fprintf(stderr, "errno is: %d\n", meshlink_errno); + fprintf(stderr, "Could not open %s: %s\n", filename, meshlink_strerror(meshlink_errno)); + return 1; + } + } + + int started = 0; + + for(int i = 0; i < n; i++) { + if(!meshlink_start(meshes[i])) { + fprintf(stderr, "Could not start node %d: %s\n", i, meshlink_strerror(meshlink_errno)); + } else { + started++; + } + } + + if(!started) { + fprintf(stderr, "Could not start any node!\n"); + return 1; + } + + if(graphexporttimeout) { + exportmeshgraph_begin(graphexporttimeout); + } + + printf("%d nodes started.\nType /help for a list of commands.\n", started); + + // handle input + while(fgets(buf, sizeof(buf), stdin)) { + parse_input(buf); + } + + exportmeshgraph_end(); + + printf("Nodes stopping.\n"); + + for(int i = 0; i < n; i++) { + meshlink_close(meshes[i]); + } + + return 0; +} diff --git a/examples/meshlinkapp.c b/examples/meshlinkapp.c new file mode 100644 index 0000000..67f996a --- /dev/null +++ b/examples/meshlinkapp.c @@ -0,0 +1,60 @@ +#include "../src/logger.h" +#include "../src/system.h" +#include "../src/meshlink.h" + +static void handle_recv_data(meshlink_handle_t *mesh, meshlink_node_t *source, void *data, size_t len) { + (void)mesh; + + printf("Received %zu bytes from %s: %s\n", len, source->name, (char *)data); +} + +int main(int argc, char **argv) { + const char *confbase = argc > 1 ? argv[1] : "/tmp/meshlink/"; + const char *name = argc > 2 ? argv[2] : "foo"; + const char *remotename = argc > 3 ? argv[3] : "bar"; + + meshlink_handle_t *myhandle; + + myhandle = meshlink_open(confbase, name, "meshlinkapp", DEV_CLASS_STATIONARY); + + if(!myhandle) { + fprintf(stderr, "Could not open MeshLink: %s", meshlink_strerror(meshlink_errno)); + return 1; + } + + //Register callback function for incoming data + meshlink_set_receive_cb(myhandle, (meshlink_receive_cb_t)handle_recv_data); + + if(!meshlink_start(myhandle)) { + fprintf(stderr, "Could not start MeshLink: %s", meshlink_strerror(meshlink_errno)); + return 1; + } + + while(1) { + sleep(10); + + meshlink_node_t *remotenode = meshlink_get_node(myhandle, remotename); + + if(!remotenode) { + fprintf(stderr, "Node %s not known yet.\n", remotename); + continue; + } + + //sample data to send out + char mydata[200]; + memset(mydata, 0, 200); + strcpy(mydata, "Hello World!"); + + //send out data + if(!meshlink_send(myhandle, remotenode, mydata, sizeof(mydata))) { + fprintf(stderr, "Error sending data: %s", meshlink_strerror(meshlink_errno)); + return 1; + } + } + + meshlink_stop(myhandle); + meshlink_close(myhandle); + + return 0; +} + diff --git a/examples/monitor.c b/examples/monitor.c new file mode 100644 index 0000000..218cb67 --- /dev/null +++ b/examples/monitor.c @@ -0,0 +1,248 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../src/meshlink.h" +#include "../src/devtools.h" + +static WINDOW *topwin; +static WINDOW *nodewin; +static WINDOW *splitwin; +static WINDOW *logwin; +static WINDOW *statuswin; +static float splitpoint = 0.5; + +static meshlink_handle_t *mesh; +static meshlink_node_t **nodes; +static size_t nnodes; + +static void log_message(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) { + (void)mesh; + + wattron(logwin, COLOR_PAIR(level)); + wprintw(logwin, "%s\n", text); + wattroff(logwin, COLOR_PAIR(level)); + wrefresh(logwin); +} + +static void do_resize() { + const int nodelines = lrintf((LINES - 3) * splitpoint); + const int loglines = (LINES - 3) - nodelines; + assert(nodelines > 1); + assert(loglines > 1); + assert(COLS > 1); + + mvwin(topwin, 0, 0); + wresize(topwin, 1, COLS); + + mvwin(nodewin, 1, 0); + wresize(nodewin, nodelines, COLS); + + mvwin(splitwin, 1 + nodelines, 0); + wresize(splitwin, 1, COLS); + + mvwin(logwin, 2 + nodelines, 0); + wresize(logwin, loglines, COLS); + + mvwin(statuswin, LINES - 1, 0); + wresize(statuswin, 1, COLS); +} + + +static void do_redraw_nodes() { + werase(nodewin); + nodes = meshlink_get_all_nodes(mesh, nodes, &nnodes); + + for(size_t i = 0; i < nnodes; i++) { + devtool_node_status_t status; + devtool_get_node_status(mesh, nodes[i], &status); + char host[NI_MAXHOST] = ""; + char serv[NI_MAXSERV] = ""; + getnameinfo((struct sockaddr *)&status.address, sizeof status.address, host, sizeof host, serv, sizeof serv, NI_NUMERICHOST | NI_NUMERICSERV); + const char *desc; + + switch(status.udp_status) { + case DEVTOOL_UDP_FAILED: + desc = "UDP failed"; + break; + + case DEVTOOL_UDP_IMPOSSIBLE: + desc = "unreachable"; + break; + + case DEVTOOL_UDP_TRYING: + desc = "probing"; + break; + + case DEVTOOL_UDP_WORKING: + desc = "UDP working"; + break; + + case DEVTOOL_UDP_UNKNOWN: + default: + desc = "unknown"; + break; + }; + + if(!strcmp(nodes[i]->name, mesh->name)) { + desc = "myself"; + } + + char mtustate = ' '; + + if(status.minmtu) { + if(status.minmtu != status.maxmtu) { + mtustate = '~'; + } + }; + + mvwprintw(nodewin, i, 0, "%-16s %-12s %-32s %5s %c%5d", nodes[i]->name, desc, host, serv, mtustate, status.maxmtu); + } + + wnoutrefresh(nodewin); +} + +static void do_redraw() { + // Draw top line + werase(topwin); + mvwprintw(topwin, 0, 0, "%-16s %-12s %-32s %5s %6s", "Node:", "Status:", "UDP address:", "Port:", "MTU:"); + wnoutrefresh(topwin); + + // Draw middle line + werase(splitwin); + mvwprintw(splitwin, 0, 0, "Log output:"); + wnoutrefresh(splitwin); + + // Draw bottom line + werase(statuswin); + mvwprintw(statuswin, 0, 0, "Status bar"); + wnoutrefresh(statuswin); + + wnoutrefresh(logwin); + + do_redraw_nodes(); +} + +static void node_status(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + (void)node; + (void)reachable; + do_redraw_nodes(); + doupdate(); +} + +int main(int argc, char *argv[]) { + const char *confbase = ".monitor"; + const char *id = NULL; + + if(argc > 1) { + confbase = argv[1]; + } + + if(argc > 2) { + id = argv[2]; + } + + initscr(); + start_color(); + curs_set(false); + noecho(); + + topwin = newwin(1, COLS, 0, 0); + nodewin = newwin(1, COLS, 1, 0); + splitwin = newwin(1, COLS, 2, 0); + logwin = newwin(1, COLS, 3, 0); + statuswin = newwin(1, COLS, 4, 0); + + leaveok(topwin, true); + leaveok(nodewin, true); + leaveok(splitwin, true); + leaveok(logwin, true); + leaveok(statuswin, true); + + wattrset(topwin, A_REVERSE); + wattrset(splitwin, A_REVERSE); + wattrset(statuswin, A_REVERSE); + + wbkgdset(topwin, ' ' | A_REVERSE); + wbkgdset(splitwin, ' ' | A_REVERSE); + wbkgdset(statuswin, ' ' | A_REVERSE); + + init_pair(1, COLOR_GREEN, -1); + init_pair(2, COLOR_YELLOW, -1); + init_pair(3, COLOR_RED, -1); + init_pair(4, COLOR_RED, -1); + + scrollok(logwin, true); + + do_resize(); + do_redraw(); + doupdate(); + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_message); + + mesh = meshlink_open(confbase, id, "monitor", DEV_CLASS_STATIONARY); + + if(!mesh) { + endwin(); + fprintf(stderr, "Could not open MeshLink: %s\n", meshlink_strerror(meshlink_errno)); + return 1; + } + + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_message); + meshlink_set_node_status_cb(mesh, node_status); + + if(!meshlink_start(mesh)) { + endwin(); + fprintf(stderr, "Could not start MeshLink: %s\n", meshlink_strerror(meshlink_errno)); + return 1; + } + + bool running = true; + timeout(500); + wtimeout(topwin, 500); + + do_redraw(); + doupdate(); + + while(running) { + int key = wgetch(topwin); + + switch(key) { + case 'q': + case 27: + case KEY_BREAK: + running = false; + break; + + case KEY_RESIZE: + do_resize(); + break; + + case 'r': + case KEY_REFRESH: + clearok(topwin, true); + clearok(nodewin, true); + clearok(splitwin, true); + clearok(logwin, true); + clearok(statuswin, true); + break; + } + + do_redraw(); + doupdate(); + } + + meshlink_stop(mesh); + meshlink_close(mesh); + + endwin(); + + return 0; +} diff --git a/m4/.gitignore b/m4/.gitignore new file mode 100644 index 0000000..94e6f26 --- /dev/null +++ b/m4/.gitignore @@ -0,0 +1,5 @@ +/libtool.m4 +/ltoptions.m4 +/ltsugar.m4 +/ltversion.m4 +/lt~obsolete.m4 diff --git a/m4/attribute.m4 b/m4/attribute.m4 new file mode 100644 index 0000000..0678485 --- /dev/null +++ b/m4/attribute.m4 @@ -0,0 +1,25 @@ +dnl Check to find out whether function attributes are supported. +dnl If they are not, #define them to be nothing. + +AC_DEFUN([MeshLink_ATTRIBUTE], +[ + AC_CACHE_CHECK([for working $1 attribute], MeshLink_cv_attribute_$1, + [ + tempcflags="$CFLAGS" + CFLAGS="$CFLAGS -Wall -Werror" + AC_COMPILE_IFELSE( + [AC_LANG_SOURCE( + [void *test(void *arg) __attribute__ (($1)); + void *test(void *arg) { return arg; } + ], + )], + [MeshLink_cv_attribute_$1=yes], + [MeshLink_cv_attribute_$1=no] + ) + CFLAGS="$tempcflags" + ]) + + if test ${MeshLink_cv_attribute_$1} = no; then + AC_DEFINE([$1], [], [Defined if the $1 attribute is not supported.]) + fi +]) diff --git a/m4/ax_check_compile_flag.m4 b/m4/ax_check_compile_flag.m4 new file mode 100644 index 0000000..c3a8d69 --- /dev/null +++ b/m4/ax_check_compile_flag.m4 @@ -0,0 +1,72 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS]) +# +# DESCRIPTION +# +# Check whether the given FLAG works with the current language's compiler +# or gives an error. (Warnings, however, are ignored) +# +# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on +# success/failure. +# +# If EXTRA-FLAGS is defined, it is added to the current language's default +# flags (e.g. CFLAGS) when the check is done. The check is thus made with +# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to +# force the compiler to issue an error when a bad flag is given. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this +# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim +# Copyright (c) 2011 Maarten Bosmans +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 2 + +AC_DEFUN([AX_CHECK_COMPILE_FLAG], +[AC_PREREQ(2.59)dnl for _AC_LANG_PREFIX +AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl +AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ + ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS + _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" + AC_COMPILE_IFELSE([AC_LANG_PROGRAM()], + [AS_VAR_SET(CACHEVAR,[yes])], + [AS_VAR_SET(CACHEVAR,[no])]) + _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) +AS_IF([test x"AS_VAR_GET(CACHEVAR)" = xyes], + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_COMPILE_FLAGS diff --git a/m4/ax_check_link_flag.m4 b/m4/ax_check_link_flag.m4 new file mode 100644 index 0000000..e2d0d36 --- /dev/null +++ b/m4/ax_check_link_flag.m4 @@ -0,0 +1,71 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_check_link_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_LINK_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS]) +# +# DESCRIPTION +# +# Check whether the given FLAG works with the linker or gives an error. +# (Warnings, however, are ignored) +# +# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on +# success/failure. +# +# If EXTRA-FLAGS is defined, it is added to the linker's default flags +# when the check is done. The check is thus made with the flags: "LDFLAGS +# EXTRA-FLAGS FLAG". This can for example be used to force the linker to +# issue an error when a bad flag is given. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this +# macro in sync with AX_CHECK_{PREPROC,COMPILE}_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim +# Copyright (c) 2011 Maarten Bosmans +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 2 + +AC_DEFUN([AX_CHECK_LINK_FLAG], +[AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_ldflags_$4_$1])dnl +AC_CACHE_CHECK([whether the linker accepts $1], CACHEVAR, [ + ax_check_save_flags=$LDFLAGS + LDFLAGS="$LDFLAGS $4 $1" + AC_LINK_IFELSE([AC_LANG_PROGRAM()], + [AS_VAR_SET(CACHEVAR,[yes])], + [AS_VAR_SET(CACHEVAR,[no])]) + LDFLAGS=$ax_check_save_flags]) +AS_IF([test x"AS_VAR_GET(CACHEVAR)" = xyes], + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_LINK_FLAGS diff --git a/m4/ax_prog_doxygen.m4 b/m4/ax_prog_doxygen.m4 new file mode 100644 index 0000000..ed1dc83 --- /dev/null +++ b/m4/ax_prog_doxygen.m4 @@ -0,0 +1,586 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_prog_doxygen.html +# =========================================================================== +# +# SYNOPSIS +# +# DX_INIT_DOXYGEN(PROJECT-NAME, [DOXYFILE-PATH], [OUTPUT-DIR], ...) +# DX_DOXYGEN_FEATURE(ON|OFF) +# DX_DOT_FEATURE(ON|OFF) +# DX_HTML_FEATURE(ON|OFF) +# DX_CHM_FEATURE(ON|OFF) +# DX_CHI_FEATURE(ON|OFF) +# DX_MAN_FEATURE(ON|OFF) +# DX_RTF_FEATURE(ON|OFF) +# DX_XML_FEATURE(ON|OFF) +# DX_PDF_FEATURE(ON|OFF) +# DX_PS_FEATURE(ON|OFF) +# +# DESCRIPTION +# +# The DX_*_FEATURE macros control the default setting for the given +# Doxygen feature. Supported features are 'DOXYGEN' itself, 'DOT' for +# generating graphics, 'HTML' for plain HTML, 'CHM' for compressed HTML +# help (for MS users), 'CHI' for generating a separate .chi file by the +# .chm file, and 'MAN', 'RTF', 'XML', 'PDF' and 'PS' for the appropriate +# output formats. The environment variable DOXYGEN_PAPER_SIZE may be +# specified to override the default 'a4wide' paper size. +# +# By default, HTML, PDF and PS documentation is generated as this seems to +# be the most popular and portable combination. MAN pages created by +# Doxygen are usually problematic, though by picking an appropriate subset +# and doing some massaging they might be better than nothing. CHM and RTF +# are specific for MS (note that you can't generate both HTML and CHM at +# the same time). The XML is rather useless unless you apply specialized +# post-processing to it. +# +# The macros mainly control the default state of the feature. The use can +# override the default by specifying --enable or --disable. The macros +# ensure that contradictory flags are not given (e.g., +# --enable-doxygen-html and --enable-doxygen-chm, +# --enable-doxygen-anything with --disable-doxygen, etc.) Finally, each +# feature will be automatically disabled (with a warning) if the required +# programs are missing. +# +# Once all the feature defaults have been specified, call DX_INIT_DOXYGEN +# with the following parameters: a one-word name for the project for use +# as a filename base etc., an optional configuration file name (the +# default is '$(srcdir)/Doxyfile', the same as Doxygen's default), and an +# optional output directory name (the default is 'doxygen-doc'). To run +# doxygen multiple times for different configuration files and output +# directories provide more parameters: the second, forth, sixth, etc +# parameter are configuration file names and the third, fifth, seventh, +# etc parameter are output directories. No checking is done to catch +# duplicates. +# +# Automake Support +# +# The DX_RULES substitution can be used to add all needed rules to the +# Makefile. Note that this is a substitution without being a variable: +# only the @DX_RULES@ syntax will work. +# +# The provided targets are: +# +# doxygen-doc: Generate all doxygen documentation. +# +# doxygen-run: Run doxygen, which will generate some of the +# documentation (HTML, CHM, CHI, MAN, RTF, XML) +# but will not do the post processing required +# for the rest of it (PS, PDF). +# +# doxygen-ps: Generate doxygen PostScript documentation. +# +# doxygen-pdf: Generate doxygen PDF documentation. +# +# Note that by default these are not integrated into the automake targets. +# If doxygen is used to generate man pages, you can achieve this +# integration by setting man3_MANS to the list of man pages generated and +# then adding the dependency: +# +# $(man3_MANS): doxygen-doc +# +# This will cause make to run doxygen and generate all the documentation. +# +# The following variable is intended for use in Makefile.am: +# +# DX_CLEANFILES = everything to clean. +# +# Then add this variable to MOSTLYCLEANFILES. +# +# LICENSE +# +# Copyright (c) 2009 Oren Ben-Kiki +# Copyright (c) 2015 Olaf Mandel +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 24 + +## ----------## +## Defaults. ## +## ----------## + +DX_ENV="" +AC_DEFUN([DX_FEATURE_doc], ON) +AC_DEFUN([DX_FEATURE_dot], OFF) +AC_DEFUN([DX_FEATURE_man], OFF) +AC_DEFUN([DX_FEATURE_html], ON) +AC_DEFUN([DX_FEATURE_chm], OFF) +AC_DEFUN([DX_FEATURE_chi], OFF) +AC_DEFUN([DX_FEATURE_rtf], OFF) +AC_DEFUN([DX_FEATURE_xml], OFF) +AC_DEFUN([DX_FEATURE_pdf], ON) +AC_DEFUN([DX_FEATURE_ps], ON) + +## --------------- ## +## Private macros. ## +## --------------- ## + +# DX_ENV_APPEND(VARIABLE, VALUE) +# ------------------------------ +# Append VARIABLE="VALUE" to DX_ENV for invoking doxygen and add it +# as a substitution (but not a Makefile variable). The substitution +# is skipped if the variable name is VERSION. +AC_DEFUN([DX_ENV_APPEND], +[AC_SUBST([DX_ENV], ["$DX_ENV $1='$2'"])dnl +m4_if([$1], [VERSION], [], [AC_SUBST([$1], [$2])dnl +AM_SUBST_NOTMAKE([$1])])dnl +]) + +# DX_DIRNAME_EXPR +# --------------- +# Expand into a shell expression prints the directory part of a path. +AC_DEFUN([DX_DIRNAME_EXPR], + [[expr ".$1" : '\(\.\)[^/]*$' \| "x$1" : 'x\(.*\)/[^/]*$']]) + +# DX_IF_FEATURE(FEATURE, IF-ON, IF-OFF) +# ------------------------------------- +# Expands according to the M4 (static) status of the feature. +AC_DEFUN([DX_IF_FEATURE], [ifelse(DX_FEATURE_$1, ON, [$2], [$3])]) + +# DX_REQUIRE_PROG(VARIABLE, PROGRAM) +# ---------------------------------- +# Require the specified program to be found for the DX_CURRENT_FEATURE to work. +AC_DEFUN([DX_REQUIRE_PROG], [ +AC_PATH_TOOL([$1], [$2]) +if test "$DX_FLAG_[]DX_CURRENT_FEATURE$$1" = 1; then + AC_MSG_WARN([$2 not found - will not DX_CURRENT_DESCRIPTION]) + AC_SUBST(DX_FLAG_[]DX_CURRENT_FEATURE, 0) +fi +]) + +# DX_TEST_FEATURE(FEATURE) +# ------------------------ +# Expand to a shell expression testing whether the feature is active. +AC_DEFUN([DX_TEST_FEATURE], [test "$DX_FLAG_$1" = 1]) + +# DX_CHECK_DEPEND(REQUIRED_FEATURE, REQUIRED_STATE) +# ------------------------------------------------- +# Verify that a required features has the right state before trying to turn on +# the DX_CURRENT_FEATURE. +AC_DEFUN([DX_CHECK_DEPEND], [ +test "$DX_FLAG_$1" = "$2" \ +|| AC_MSG_ERROR([doxygen-DX_CURRENT_FEATURE ifelse([$2], 1, + requires, contradicts) doxygen-$1]) +]) + +# DX_CLEAR_DEPEND(FEATURE, REQUIRED_FEATURE, REQUIRED_STATE) +# ---------------------------------------------------------- +# Turn off the DX_CURRENT_FEATURE if the required feature is off. +AC_DEFUN([DX_CLEAR_DEPEND], [ +test "$DX_FLAG_$1" = "$2" || AC_SUBST(DX_FLAG_[]DX_CURRENT_FEATURE, 0) +]) + +# DX_FEATURE_ARG(FEATURE, DESCRIPTION, +# CHECK_DEPEND, CLEAR_DEPEND, +# REQUIRE, DO-IF-ON, DO-IF-OFF) +# -------------------------------------------- +# Parse the command-line option controlling a feature. CHECK_DEPEND is called +# if the user explicitly turns the feature on (and invokes DX_CHECK_DEPEND), +# otherwise CLEAR_DEPEND is called to turn off the default state if a required +# feature is disabled (using DX_CLEAR_DEPEND). REQUIRE performs additional +# requirement tests (DX_REQUIRE_PROG). Finally, an automake flag is set and +# DO-IF-ON or DO-IF-OFF are called according to the final state of the feature. +AC_DEFUN([DX_ARG_ABLE], [ + AC_DEFUN([DX_CURRENT_FEATURE], [$1]) + AC_DEFUN([DX_CURRENT_DESCRIPTION], [$2]) + AC_ARG_ENABLE(doxygen-$1, + [AS_HELP_STRING(DX_IF_FEATURE([$1], [--disable-doxygen-$1], + [--enable-doxygen-$1]), + DX_IF_FEATURE([$1], [don't $2], [$2]))], + [ +case "$enableval" in +#( +y|Y|yes|Yes|YES) + AC_SUBST([DX_FLAG_$1], 1) + $3 +;; #( +n|N|no|No|NO) + AC_SUBST([DX_FLAG_$1], 0) +;; #( +*) + AC_MSG_ERROR([invalid value '$enableval' given to doxygen-$1]) +;; +esac +], [ +AC_SUBST([DX_FLAG_$1], [DX_IF_FEATURE([$1], 1, 0)]) +$4 +]) +if DX_TEST_FEATURE([$1]); then + $5 + : +fi +if DX_TEST_FEATURE([$1]); then + $6 + : +else + $7 + : +fi +]) + +## -------------- ## +## Public macros. ## +## -------------- ## + +# DX_XXX_FEATURE(DEFAULT_STATE) +# ----------------------------- +AC_DEFUN([DX_DOXYGEN_FEATURE], [AC_DEFUN([DX_FEATURE_doc], [$1])]) +AC_DEFUN([DX_DOT_FEATURE], [AC_DEFUN([DX_FEATURE_dot], [$1])]) +AC_DEFUN([DX_MAN_FEATURE], [AC_DEFUN([DX_FEATURE_man], [$1])]) +AC_DEFUN([DX_HTML_FEATURE], [AC_DEFUN([DX_FEATURE_html], [$1])]) +AC_DEFUN([DX_CHM_FEATURE], [AC_DEFUN([DX_FEATURE_chm], [$1])]) +AC_DEFUN([DX_CHI_FEATURE], [AC_DEFUN([DX_FEATURE_chi], [$1])]) +AC_DEFUN([DX_RTF_FEATURE], [AC_DEFUN([DX_FEATURE_rtf], [$1])]) +AC_DEFUN([DX_XML_FEATURE], [AC_DEFUN([DX_FEATURE_xml], [$1])]) +AC_DEFUN([DX_XML_FEATURE], [AC_DEFUN([DX_FEATURE_xml], [$1])]) +AC_DEFUN([DX_PDF_FEATURE], [AC_DEFUN([DX_FEATURE_pdf], [$1])]) +AC_DEFUN([DX_PS_FEATURE], [AC_DEFUN([DX_FEATURE_ps], [$1])]) + +# DX_INIT_DOXYGEN(PROJECT, [CONFIG-FILE], [OUTPUT-DOC-DIR], ...) +# -------------------------------------------------------------- +# PROJECT also serves as the base name for the documentation files. +# The default CONFIG-FILE is "$(srcdir)/Doxyfile" and OUTPUT-DOC-DIR is +# "doxygen-doc". +# More arguments are interpreted as interleaved CONFIG-FILE and +# OUTPUT-DOC-DIR values. +AC_DEFUN([DX_INIT_DOXYGEN], [ + +# Files: +AC_SUBST([DX_PROJECT], [$1]) +AC_SUBST([DX_CONFIG], ['ifelse([$2], [], [$(srcdir)/Doxyfile], [$2])']) +AC_SUBST([DX_DOCDIR], ['ifelse([$3], [], [doxygen-doc], [$3])']) +m4_if(m4_eval(3 < m4_count($@)), 1, [m4_for([DX_i], 4, m4_count($@), 2, + [AC_SUBST([DX_CONFIG]m4_eval(DX_i[/2]), + 'm4_default_nblank_quoted(m4_argn(DX_i, $@), + [$(srcdir)/Doxyfile])')])])dnl +m4_if(m4_eval(3 < m4_count($@)), 1, [m4_for([DX_i], 5, m4_count($@,), 2, + [AC_SUBST([DX_DOCDIR]m4_eval([(]DX_i[-1)/2]), + 'm4_default_nblank_quoted(m4_argn(DX_i, $@), + [doxygen-doc])')])])dnl +m4_define([DX_loop], m4_dquote(m4_if(m4_eval(3 < m4_count($@)), 1, + [m4_for([DX_i], 4, m4_count($@), 2, [, m4_eval(DX_i[/2])])], + [])))dnl + +# Environment variables used inside doxygen.cfg: +DX_ENV_APPEND(SRCDIR, $srcdir) +DX_ENV_APPEND(PROJECT, $DX_PROJECT) +DX_ENV_APPEND(VERSION, $PACKAGE_VERSION) + +# Doxygen itself: +DX_ARG_ABLE(doc, [generate any doxygen documentation], + [], + [], + [DX_REQUIRE_PROG([DX_DOXYGEN], doxygen) + DX_REQUIRE_PROG([DX_PERL], perl)], + [DX_ENV_APPEND(PERL_PATH, $DX_PERL)]) + +# Dot for graphics: +DX_ARG_ABLE(dot, [generate graphics for doxygen documentation], + [DX_CHECK_DEPEND(doc, 1)], + [DX_CLEAR_DEPEND(doc, 1)], + [DX_REQUIRE_PROG([DX_DOT], dot)], + [DX_ENV_APPEND(HAVE_DOT, YES) + DX_ENV_APPEND(DOT_PATH, [`DX_DIRNAME_EXPR($DX_DOT)`])], + [DX_ENV_APPEND(HAVE_DOT, NO)]) + +# Man pages generation: +DX_ARG_ABLE(man, [generate doxygen manual pages], + [DX_CHECK_DEPEND(doc, 1)], + [DX_CLEAR_DEPEND(doc, 1)], + [], + [DX_ENV_APPEND(GENERATE_MAN, YES)], + [DX_ENV_APPEND(GENERATE_MAN, NO)]) + +# RTF file generation: +DX_ARG_ABLE(rtf, [generate doxygen RTF documentation], + [DX_CHECK_DEPEND(doc, 1)], + [DX_CLEAR_DEPEND(doc, 1)], + [], + [DX_ENV_APPEND(GENERATE_RTF, YES)], + [DX_ENV_APPEND(GENERATE_RTF, NO)]) + +# XML file generation: +DX_ARG_ABLE(xml, [generate doxygen XML documentation], + [DX_CHECK_DEPEND(doc, 1)], + [DX_CLEAR_DEPEND(doc, 1)], + [], + [DX_ENV_APPEND(GENERATE_XML, YES)], + [DX_ENV_APPEND(GENERATE_XML, NO)]) + +# (Compressed) HTML help generation: +DX_ARG_ABLE(chm, [generate doxygen compressed HTML help documentation], + [DX_CHECK_DEPEND(doc, 1)], + [DX_CLEAR_DEPEND(doc, 1)], + [DX_REQUIRE_PROG([DX_HHC], hhc)], + [DX_ENV_APPEND(HHC_PATH, $DX_HHC) + DX_ENV_APPEND(GENERATE_HTML, YES) + DX_ENV_APPEND(GENERATE_HTMLHELP, YES)], + [DX_ENV_APPEND(GENERATE_HTMLHELP, NO)]) + +# Separate CHI file generation. +DX_ARG_ABLE(chi, [generate doxygen separate compressed HTML help index file], + [DX_CHECK_DEPEND(chm, 1)], + [DX_CLEAR_DEPEND(chm, 1)], + [], + [DX_ENV_APPEND(GENERATE_CHI, YES)], + [DX_ENV_APPEND(GENERATE_CHI, NO)]) + +# Plain HTML pages generation: +DX_ARG_ABLE(html, [generate doxygen plain HTML documentation], + [DX_CHECK_DEPEND(doc, 1) DX_CHECK_DEPEND(chm, 0)], + [DX_CLEAR_DEPEND(doc, 1) DX_CLEAR_DEPEND(chm, 0)], + [], + [DX_ENV_APPEND(GENERATE_HTML, YES)], + [DX_TEST_FEATURE(chm) || DX_ENV_APPEND(GENERATE_HTML, NO)]) + +# PostScript file generation: +DX_ARG_ABLE(ps, [generate doxygen PostScript documentation], + [DX_CHECK_DEPEND(doc, 1)], + [DX_CLEAR_DEPEND(doc, 1)], + [DX_REQUIRE_PROG([DX_LATEX], latex) + DX_REQUIRE_PROG([DX_MAKEINDEX], makeindex) + DX_REQUIRE_PROG([DX_DVIPS], dvips) + DX_REQUIRE_PROG([DX_EGREP], egrep)]) + +# PDF file generation: +DX_ARG_ABLE(pdf, [generate doxygen PDF documentation], + [DX_CHECK_DEPEND(doc, 1)], + [DX_CLEAR_DEPEND(doc, 1)], + [DX_REQUIRE_PROG([DX_PDFLATEX], pdflatex) + DX_REQUIRE_PROG([DX_MAKEINDEX], makeindex) + DX_REQUIRE_PROG([DX_EGREP], egrep)]) + +# LaTeX generation for PS and/or PDF: +if DX_TEST_FEATURE(ps) || DX_TEST_FEATURE(pdf); then + DX_ENV_APPEND(GENERATE_LATEX, YES) +else + DX_ENV_APPEND(GENERATE_LATEX, NO) +fi + +# Paper size for PS and/or PDF: +AC_ARG_VAR(DOXYGEN_PAPER_SIZE, + [a4wide (default), a4, letter, legal or executive]) +case "$DOXYGEN_PAPER_SIZE" in +#( +"") + AC_SUBST(DOXYGEN_PAPER_SIZE, "") +;; #( +a4wide|a4|letter|legal|executive) + DX_ENV_APPEND(PAPER_SIZE, $DOXYGEN_PAPER_SIZE) +;; #( +*) + AC_MSG_ERROR([unknown DOXYGEN_PAPER_SIZE='$DOXYGEN_PAPER_SIZE']) +;; +esac + +# Rules: +AS_IF([[test $DX_FLAG_html -eq 1]], +[[DX_SNIPPET_html="## ------------------------------- ## +## Rules specific for HTML output. ## +## ------------------------------- ## + +DX_CLEAN_HTML = \$(DX_DOCDIR)/html]dnl +m4_foreach([DX_i], [m4_shift(DX_loop)], [[\\ + \$(DX_DOCDIR]DX_i[)/html]])[ + +"]], +[[DX_SNIPPET_html=""]]) +AS_IF([[test $DX_FLAG_chi -eq 1]], +[[DX_SNIPPET_chi=" +DX_CLEAN_CHI = \$(DX_DOCDIR)/\$(PACKAGE).chi]dnl +m4_foreach([DX_i], [m4_shift(DX_loop)], [[\\ + \$(DX_DOCDIR]DX_i[)/\$(PACKAGE).chi]])["]], +[[DX_SNIPPET_chi=""]]) +AS_IF([[test $DX_FLAG_chm -eq 1]], +[[DX_SNIPPET_chm="## ------------------------------ ## +## Rules specific for CHM output. ## +## ------------------------------ ## + +DX_CLEAN_CHM = \$(DX_DOCDIR)/chm]dnl +m4_foreach([DX_i], [m4_shift(DX_loop)], [[\\ + \$(DX_DOCDIR]DX_i[)/chm]])[\ +${DX_SNIPPET_chi} + +"]], +[[DX_SNIPPET_chm=""]]) +AS_IF([[test $DX_FLAG_man -eq 1]], +[[DX_SNIPPET_man="## ------------------------------ ## +## Rules specific for MAN output. ## +## ------------------------------ ## + +DX_CLEAN_MAN = \$(DX_DOCDIR)/man]dnl +m4_foreach([DX_i], [m4_shift(DX_loop)], [[\\ + \$(DX_DOCDIR]DX_i[)/man]])[ + +"]], +[[DX_SNIPPET_man=""]]) +AS_IF([[test $DX_FLAG_rtf -eq 1]], +[[DX_SNIPPET_rtf="## ------------------------------ ## +## Rules specific for RTF output. ## +## ------------------------------ ## + +DX_CLEAN_RTF = \$(DX_DOCDIR)/rtf]dnl +m4_foreach([DX_i], [m4_shift(DX_loop)], [[\\ + \$(DX_DOCDIR]DX_i[)/rtf]])[ + +"]], +[[DX_SNIPPET_rtf=""]]) +AS_IF([[test $DX_FLAG_xml -eq 1]], +[[DX_SNIPPET_xml="## ------------------------------ ## +## Rules specific for XML output. ## +## ------------------------------ ## + +DX_CLEAN_XML = \$(DX_DOCDIR)/xml]dnl +m4_foreach([DX_i], [m4_shift(DX_loop)], [[\\ + \$(DX_DOCDIR]DX_i[)/xml]])[ + +"]], +[[DX_SNIPPET_xml=""]]) +AS_IF([[test $DX_FLAG_ps -eq 1]], +[[DX_SNIPPET_ps="## ----------------------------- ## +## Rules specific for PS output. ## +## ----------------------------- ## + +DX_CLEAN_PS = \$(DX_DOCDIR)/\$(PACKAGE).ps]dnl +m4_foreach([DX_i], [m4_shift(DX_loop)], [[\\ + \$(DX_DOCDIR]DX_i[)/\$(PACKAGE).ps]])[ + +DX_PS_GOAL = doxygen-ps + +doxygen-ps: \$(DX_CLEAN_PS) + +]m4_foreach([DX_i], [DX_loop], +[[\$(DX_DOCDIR]DX_i[)/\$(PACKAGE).ps: \$(DX_DOCDIR]DX_i[)/\$(PACKAGE).tag + \$(DX_V_LATEX)cd \$(DX_DOCDIR]DX_i[)/latex; \\ + rm -f *.aux *.toc *.idx *.ind *.ilg *.log *.out; \\ + \$(DX_LATEX) refman.tex; \\ + \$(DX_MAKEINDEX) refman.idx; \\ + \$(DX_LATEX) refman.tex; \\ + countdown=5; \\ + while \$(DX_EGREP) 'Rerun (LaTeX|to get cross-references right)' \\ + refman.log > /dev/null 2>&1 \\ + && test \$\$countdown -gt 0; do \\ + \$(DX_LATEX) refman.tex; \\ + countdown=\`expr \$\$countdown - 1\`; \\ + done; \\ + \$(DX_DVIPS) -o ../\$(PACKAGE).ps refman.dvi + +]])["]], +[[DX_SNIPPET_ps=""]]) +AS_IF([[test $DX_FLAG_pdf -eq 1]], +[[DX_SNIPPET_pdf="## ------------------------------ ## +## Rules specific for PDF output. ## +## ------------------------------ ## + +DX_CLEAN_PDF = \$(DX_DOCDIR)/\$(PACKAGE).pdf]dnl +m4_foreach([DX_i], [m4_shift(DX_loop)], [[\\ + \$(DX_DOCDIR]DX_i[)/\$(PACKAGE).pdf]])[ + +DX_PDF_GOAL = doxygen-pdf + +doxygen-pdf: \$(DX_CLEAN_PDF) + +]m4_foreach([DX_i], [DX_loop], +[[\$(DX_DOCDIR]DX_i[)/\$(PACKAGE).pdf: \$(DX_DOCDIR]DX_i[)/\$(PACKAGE).tag + \$(DX_V_LATEX)cd \$(DX_DOCDIR]DX_i[)/latex; \\ + rm -f *.aux *.toc *.idx *.ind *.ilg *.log *.out; \\ + \$(DX_PDFLATEX) refman.tex; \\ + \$(DX_MAKEINDEX) refman.idx; \\ + \$(DX_PDFLATEX) refman.tex; \\ + countdown=5; \\ + while \$(DX_EGREP) 'Rerun (LaTeX|to get cross-references right)' \\ + refman.log > /dev/null 2>&1 \\ + && test \$\$countdown -gt 0; do \\ + \$(DX_PDFLATEX) refman.tex; \\ + countdown=\`expr \$\$countdown - 1\`; \\ + done; \\ + mv refman.pdf ../\$(PACKAGE).pdf + +]])["]], +[[DX_SNIPPET_pdf=""]]) +AS_IF([[test $DX_FLAG_ps -eq 1 -o $DX_FLAG_pdf -eq 1]], +[[DX_SNIPPET_latex="## ------------------------------------------------- ## +## Rules specific for LaTeX (shared for PS and PDF). ## +## ------------------------------------------------- ## + +DX_V_LATEX = \$(_DX_v_LATEX_\$(V)) +_DX_v_LATEX_ = \$(_DX_v_LATEX_\$(AM_DEFAULT_VERBOSITY)) +_DX_v_LATEX_0 = @echo \" LATEX \" \$][@; + +DX_CLEAN_LATEX = \$(DX_DOCDIR)/latex]dnl +m4_foreach([DX_i], [m4_shift(DX_loop)], [[\\ + \$(DX_DOCDIR]DX_i[)/latex]])[ + +"]], +[[DX_SNIPPET_latex=""]]) + +AS_IF([[test $DX_FLAG_doc -eq 1]], +[[DX_SNIPPET_doc="## --------------------------------- ## +## Format-independent Doxygen rules. ## +## --------------------------------- ## + +${DX_SNIPPET_html}\ +${DX_SNIPPET_chm}\ +${DX_SNIPPET_man}\ +${DX_SNIPPET_rtf}\ +${DX_SNIPPET_xml}\ +${DX_SNIPPET_ps}\ +${DX_SNIPPET_pdf}\ +${DX_SNIPPET_latex}\ +DX_V_DXGEN = \$(_DX_v_DXGEN_\$(V)) +_DX_v_DXGEN_ = \$(_DX_v_DXGEN_\$(AM_DEFAULT_VERBOSITY)) +_DX_v_DXGEN_0 = @echo \" DXGEN \" \$<; + +.PHONY: doxygen-run doxygen-doc \$(DX_PS_GOAL) \$(DX_PDF_GOAL) + +.INTERMEDIATE: doxygen-run \$(DX_PS_GOAL) \$(DX_PDF_GOAL) + +doxygen-run:]m4_foreach([DX_i], [DX_loop], + [[ \$(DX_DOCDIR]DX_i[)/\$(PACKAGE).tag]])[ + +doxygen-doc: doxygen-run \$(DX_PS_GOAL) \$(DX_PDF_GOAL) + +]m4_foreach([DX_i], [DX_loop], +[[\$(DX_DOCDIR]DX_i[)/\$(PACKAGE).tag: \$(DX_CONFIG]DX_i[) \$(pkginclude_HEADERS) + \$(A""M_V_at)rm -rf \$(DX_DOCDIR]DX_i[) + \$(DX_V_DXGEN)\$(DX_ENV) DOCDIR=\$(DX_DOCDIR]DX_i[) \$(DX_DOXYGEN) \$(DX_CONFIG]DX_i[) + \$(A""M_V_at)echo Timestamp >\$][@ + +]])dnl +[DX_CLEANFILES = \\] +m4_foreach([DX_i], [DX_loop], +[[ \$(DX_DOCDIR]DX_i[)/doxygen_sqlite3.db \\ + \$(DX_DOCDIR]DX_i[)/\$(PACKAGE).tag \\ +]])dnl +[ -r \\ + \$(DX_CLEAN_HTML) \\ + \$(DX_CLEAN_CHM) \\ + \$(DX_CLEAN_CHI) \\ + \$(DX_CLEAN_MAN) \\ + \$(DX_CLEAN_RTF) \\ + \$(DX_CLEAN_XML) \\ + \$(DX_CLEAN_PS) \\ + \$(DX_CLEAN_PDF) \\ + \$(DX_CLEAN_LATEX)"]], +[[DX_SNIPPET_doc=""]]) +AC_SUBST([DX_RULES], +["${DX_SNIPPET_doc}"])dnl +AM_SUBST_NOTMAKE([DX_RULES]) + +#For debugging: +#echo DX_FLAG_doc=$DX_FLAG_doc +#echo DX_FLAG_dot=$DX_FLAG_dot +#echo DX_FLAG_man=$DX_FLAG_man +#echo DX_FLAG_html=$DX_FLAG_html +#echo DX_FLAG_chm=$DX_FLAG_chm +#echo DX_FLAG_chi=$DX_FLAG_chi +#echo DX_FLAG_rtf=$DX_FLAG_rtf +#echo DX_FLAG_xml=$DX_FLAG_xml +#echo DX_FLAG_pdf=$DX_FLAG_pdf +#echo DX_FLAG_ps=$DX_FLAG_ps +#echo DX_ENV=$DX_ENV +]) diff --git a/m4/ax_pthread.m4 b/m4/ax_pthread.m4 new file mode 100644 index 0000000..5fbf9fe --- /dev/null +++ b/m4/ax_pthread.m4 @@ -0,0 +1,485 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_pthread.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) +# +# DESCRIPTION +# +# This macro figures out how to build C programs using POSIX threads. It +# sets the PTHREAD_LIBS output variable to the threads library and linker +# flags, and the PTHREAD_CFLAGS output variable to any special C compiler +# flags that are needed. (The user can also force certain compiler +# flags/libs to be tested by setting these environment variables.) +# +# Also sets PTHREAD_CC to any special C compiler that is needed for +# multi-threaded programs (defaults to the value of CC otherwise). (This +# is necessary on AIX to use the special cc_r compiler alias.) +# +# NOTE: You are assumed to not only compile your program with these flags, +# but also to link with them as well. For example, you might link with +# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS +# +# If you are only building threaded programs, you may wish to use these +# variables in your default LIBS, CFLAGS, and CC: +# +# LIBS="$PTHREAD_LIBS $LIBS" +# CFLAGS="$CFLAGS $PTHREAD_CFLAGS" +# CC="$PTHREAD_CC" +# +# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant +# has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to +# that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX). +# +# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the +# PTHREAD_PRIO_INHERIT symbol is defined when compiling with +# PTHREAD_CFLAGS. +# +# ACTION-IF-FOUND is a list of shell commands to run if a threads library +# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it +# is not found. If ACTION-IF-FOUND is not specified, the default action +# will define HAVE_PTHREAD. +# +# Please let the authors know if this macro fails on any platform, or if +# you have any other suggestions or comments. This macro was based on work +# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help +# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by +# Alejandro Forero Cuervo to the autoconf macro repository. We are also +# grateful for the helpful feedback of numerous users. +# +# Updated for Autoconf 2.68 by Daniel Richard G. +# +# LICENSE +# +# Copyright (c) 2008 Steven G. Johnson +# Copyright (c) 2011 Daniel Richard G. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 24 + +AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) +AC_DEFUN([AX_PTHREAD], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_REQUIRE([AC_PROG_CC]) +AC_REQUIRE([AC_PROG_SED]) +AC_LANG_PUSH([C]) +ax_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on Tru64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then + ax_pthread_save_CC="$CC" + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"]) + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS]) + AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes]) + AC_MSG_RESULT([$ax_pthread_ok]) + if test "x$ax_pthread_ok" = "xno"; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + CC="$ax_pthread_save_CC" + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items starting with a "-" are +# C compiler flags, and other items are library names, except for "none" +# which indicates that we try without any flags at all, and "pthread-config" +# which is a program returning the flags for the Pth emulation library. + +ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64 +# (Note: HP C rejects this with "bad form for `-t' option") +# -pthreads: Solaris/gcc (Note: HP C also rejects) +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads and +# -D_REENTRANT too), HP C (must be checked before -lpthread, which +# is present but should not be used directly; and before -mthreads, +# because the compiler interprets this as "-mt" + "-hreads") +# -mthreads: Mingw32/gcc, Lynx/gcc +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case $host_os in + + freebsd*) + + # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) + # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) + + ax_pthread_flags="-kthread lthread $ax_pthread_flags" + ;; + + hpux*) + + # From the cc(1) man page: "[-mt] Sets various -D flags to enable + # multi-threading and also sets -lpthread." + + ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags" + ;; + + openedition*) + + # IBM z/OS requires a feature-test macro to be defined in order to + # enable POSIX threads at all, so give the user a hint if this is + # not set. (We don't define these ourselves, as they can affect + # other portions of the system API in unpredictable ways.) + + AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING], + [ +# if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS) + AX_PTHREAD_ZOS_MISSING +# endif + ], + [AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])]) + ;; + + solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (N.B.: The stubs are missing + # pthread_cleanup_push, or rather a function called by this macro, + # so we could check for that, but who knows whether they'll stub + # that too in a future libc.) So we'll check first for the + # standard Solaris way of linking pthreads (-mt -lpthread). + + ax_pthread_flags="-mt,pthread pthread $ax_pthread_flags" + ;; +esac + +# GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC) + +AS_IF([test "x$GCC" = "xyes"], + [ax_pthread_flags="-pthread -pthreads $ax_pthread_flags"]) + +# The presence of a feature test macro requesting re-entrant function +# definitions is, on some systems, a strong hint that pthreads support is +# correctly enabled + +case $host_os in + darwin* | hpux* | linux* | osf* | solaris*) + ax_pthread_check_macro="_REENTRANT" + ;; + + aix*) + ax_pthread_check_macro="_THREAD_SAFE" + ;; + + *) + ax_pthread_check_macro="--" + ;; +esac +AS_IF([test "x$ax_pthread_check_macro" = "x--"], + [ax_pthread_check_cond=0], + [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"]) + +# Are we compiling with Clang? + +AC_CACHE_CHECK([whether $CC is Clang], + [ax_cv_PTHREAD_CLANG], + [ax_cv_PTHREAD_CLANG=no + # Note that Autoconf sets GCC=yes for Clang as well as GCC + if test "x$GCC" = "xyes"; then + AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG], + [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */ +# if defined(__clang__) && defined(__llvm__) + AX_PTHREAD_CC_IS_CLANG +# endif + ], + [ax_cv_PTHREAD_CLANG=yes]) + fi + ]) +ax_pthread_clang="$ax_cv_PTHREAD_CLANG" + +ax_pthread_clang_warning=no + +# Clang needs special handling, because older versions handle the -pthread +# option in a rather... idiosyncratic way + +if test "x$ax_pthread_clang" = "xyes"; then + + # Clang takes -pthread; it has never supported any other flag + + # (Note 1: This will need to be revisited if a system that Clang + # supports has POSIX threads in a separate library. This tends not + # to be the way of modern systems, but it's conceivable.) + + # (Note 2: On some systems, notably Darwin, -pthread is not needed + # to get POSIX threads support; the API is always present and + # active. We could reasonably leave PTHREAD_CFLAGS empty. But + # -pthread does define _REENTRANT, and while the Darwin headers + # ignore this macro, third-party headers might not.) + + PTHREAD_CFLAGS="-pthread" + PTHREAD_LIBS= + + ax_pthread_ok=yes + + # However, older versions of Clang make a point of warning the user + # that, in an invocation where only linking and no compilation is + # taking place, the -pthread option has no effect ("argument unused + # during compilation"). They expect -pthread to be passed in only + # when source code is being compiled. + # + # Problem is, this is at odds with the way Automake and most other + # C build frameworks function, which is that the same flags used in + # compilation (CFLAGS) are also used in linking. Many systems + # supported by AX_PTHREAD require exactly this for POSIX threads + # support, and in fact it is often not straightforward to specify a + # flag that is used only in the compilation phase and not in + # linking. Such a scenario is extremely rare in practice. + # + # Even though use of the -pthread flag in linking would only print + # a warning, this can be a nuisance for well-run software projects + # that build with -Werror. So if the active version of Clang has + # this misfeature, we search for an option to squash it. + + AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread], + [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG], + [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown + # Create an alternate version of $ac_link that compiles and + # links in two steps (.c -> .o, .o -> exe) instead of one + # (.c -> exe), because the warning occurs only in the second + # step + ax_pthread_save_ac_link="$ac_link" + ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g' + ax_pthread_link_step=`$as_echo "$ac_link" | sed "$ax_pthread_sed"` + ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)" + ax_pthread_save_CFLAGS="$CFLAGS" + for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do + AS_IF([test "x$ax_pthread_try" = "xunknown"], [break]) + CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS" + ac_link="$ax_pthread_save_ac_link" + AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], + [ac_link="$ax_pthread_2step_ac_link" + AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], + [break]) + ]) + done + ac_link="$ax_pthread_save_ac_link" + CFLAGS="$ax_pthread_save_CFLAGS" + AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no]) + ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try" + ]) + + case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in + no | unknown) ;; + *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;; + esac + +fi # $ax_pthread_clang = yes + +if test "x$ax_pthread_ok" = "xno"; then +for ax_pthread_try_flag in $ax_pthread_flags; do + + case $ax_pthread_try_flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + -mt,pthread) + AC_MSG_CHECKING([whether pthreads work with -mt -lpthread]) + PTHREAD_CFLAGS="-mt" + PTHREAD_LIBS="-lpthread" + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag]) + PTHREAD_CFLAGS="$ax_pthread_try_flag" + ;; + + pthread-config) + AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) + AS_IF([test "x$ax_pthread_config" = "xno"], [continue]) + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag]) + PTHREAD_LIBS="-l$ax_pthread_try_flag" + ;; + esac + + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include +# if $ax_pthread_check_cond +# error "$ax_pthread_check_macro must be defined" +# endif + static void routine(void *a) { a = 0; } + static void *start_routine(void *a) { return a; }], + [pthread_t th; pthread_attr_t attr; + pthread_create(&th, 0, start_routine, 0); + pthread_join(th, 0); + pthread_attr_init(&attr); + pthread_cleanup_push(routine, 0); + pthread_cleanup_pop(0) /* ; */])], + [ax_pthread_ok=yes], + []) + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + AC_MSG_RESULT([$ax_pthread_ok]) + AS_IF([test "x$ax_pthread_ok" = "xyes"], [break]) + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + +# Various other checks: +if test "x$ax_pthread_ok" = "xyes"; then + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. + AC_CACHE_CHECK([for joinable pthread attribute], + [ax_cv_PTHREAD_JOINABLE_ATTR], + [ax_cv_PTHREAD_JOINABLE_ATTR=unknown + for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], + [int attr = $ax_pthread_attr; return attr /* ; */])], + [ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break], + []) + done + ]) + AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \ + test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \ + test "x$ax_pthread_joinable_attr_defined" != "xyes"], + [AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], + [$ax_cv_PTHREAD_JOINABLE_ATTR], + [Define to necessary symbol if this constant + uses a non-standard name on your system.]) + ax_pthread_joinable_attr_defined=yes + ]) + + AC_CACHE_CHECK([whether more special flags are required for pthreads], + [ax_cv_PTHREAD_SPECIAL_FLAGS], + [ax_cv_PTHREAD_SPECIAL_FLAGS=no + case $host_os in + solaris*) + ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS" + ;; + esac + ]) + AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \ + test "x$ax_pthread_special_flags_added" != "xyes"], + [PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS" + ax_pthread_special_flags_added=yes]) + + AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], + [ax_cv_PTHREAD_PRIO_INHERIT], + [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[int i = PTHREAD_PRIO_INHERIT;]])], + [ax_cv_PTHREAD_PRIO_INHERIT=yes], + [ax_cv_PTHREAD_PRIO_INHERIT=no]) + ]) + AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \ + test "x$ax_pthread_prio_inherit_defined" != "xyes"], + [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.]) + ax_pthread_prio_inherit_defined=yes + ]) + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + # More AIX lossage: compile with *_r variant + if test "x$GCC" != "xyes"; then + case $host_os in + aix*) + AS_CASE(["x/$CC"], + [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], + [#handle absolute path differently from PATH based program lookup + AS_CASE(["x$CC"], + [x/*], + [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])], + [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])]) + ;; + esac + fi +fi + +test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" + +AC_SUBST([PTHREAD_LIBS]) +AC_SUBST([PTHREAD_CFLAGS]) +AC_SUBST([PTHREAD_CC]) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test "x$ax_pthread_ok" = "xyes"; then + ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) + : +else + ax_pthread_ok=no + $2 +fi +AC_LANG_POP +])dnl AX_PTHREAD diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..ae2b191 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1 @@ +utcp-test diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..fad47bf --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,99 @@ +## Produce this file with automake to get Makefile.in + +AM_CPPFLAGS = -Wall + +ed25519_SOURCES = \ + ed25519/add_scalar.c \ + ed25519/ecdh.c \ + ed25519/ecdsa.c \ + ed25519/ecdsagen.c \ + ed25519/ed25519.h \ + ed25519/fe.c ed25519/fe.h \ + ed25519/fixedint.h \ + ed25519/ge.c ed25519/ge.h \ + ed25519/key_exchange.c \ + ed25519/keypair.c \ + ed25519/precomp_data.h \ + ed25519/sc.c ed25519/sc.h \ + ed25519/seed.c \ + ed25519/sha512.c ed25519/sha512.h \ + ed25519/sign.c \ + ed25519/verify.c + +chacha_poly1305_SOURCES = \ + chacha-poly1305/chacha.c chacha-poly1305/chacha.h \ + chacha-poly1305/chacha-poly1305.c chacha-poly1305/chacha-poly1305.h \ + chacha-poly1305/poly1305.c chacha-poly1305/poly1305.h + +utcp_SOURCES = \ + utcp.c utcp.h \ + utcp_priv.h + +lib_LTLIBRARIES = libmeshlink.la +EXTRA_PROGRAMS = utcp-test + +pkginclude_HEADERS = meshlink++.h meshlink.h + +libmeshlink_la_LDFLAGS = -export-symbols $(srcdir)/meshlink.sym + +libmeshlink_la_SOURCES = \ + adns.c adns.h \ + buffer.c buffer.h \ + conf.c conf.h \ + connection.c connection.h \ + crypto.c crypto.h \ + discovery.c discovery.h \ + dropin.c dropin.h \ + ecdh.h \ + ecdsa.h \ + ecdsagen.h \ + edge.c edge.h \ + event.c event.h \ + graph.c graph.h \ + hash.c hash.h \ + have.h \ + list.c list.h \ + logger.c logger.h \ + mdns.c mdns.h \ + meshlink.c meshlink.h meshlink.sym \ + meshlink_internal.h \ + meshlink_queue.h \ + meta.c meta.h \ + net.c net.h \ + net_packet.c \ + net_setup.c \ + net_socket.c \ + netutl.c netutl.h \ + node.c node.h \ + submesh.c submesh.h \ + packmsg.h \ + prf.c prf.h \ + protocol.c protocol.h \ + protocol_auth.c \ + protocol_edge.c \ + protocol_key.c \ + protocol_misc.c \ + route.c route.h \ + sockaddr.h \ + splay_tree.c splay_tree.h \ + sptps.c sptps.h \ + system.h \ + utils.c utils.h \ + xalloc.h \ + xoshiro.c xoshiro.h \ + devtools.c devtools.h \ + $(ed25519_SOURCES) \ + $(chacha_poly1305_SOURCES) \ + $(utcp_SOURCES) + +utcp_test_SOURCES = \ + utcp-test.c \ + $(utcp_SOURCES) + +EXTRA_libmeshlink_la_DEPENDENCIES = $(srcdir)/meshlink.sym + +libmeshlink_la_CFLAGS = $(PTHREAD_CFLAGS) -fPIC -iquote. +libmeshlink_la_LDFLAGS += $(PTHREAD_LIBS) + +utcp_test_CFLAGS = $(PTHREAD_CFLAGS) -iquote. +utcp_test_LDFLAGS = $(PTHREAD_LIBS) diff --git a/src/adns.c b/src/adns.c new file mode 100644 index 0000000..758c080 --- /dev/null +++ b/src/adns.c @@ -0,0 +1,232 @@ +/* + dns.c -- hostname resolving functions + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include + +#include "adns.h" +#include "devtools.h" +#include "logger.h" +#include "xalloc.h" + +typedef struct adns_item { + adns_cb_t cb; + void *data; + time_t deadline; + struct addrinfo *ai; + int err; + char *host; + char *serv; +} adns_item_t; + +static void *adns_loop(void *data) { + meshlink_handle_t *mesh = data; + + while(true) { + adns_item_t *item = meshlink_queue_pop_cond(&mesh->adns_queue, &mesh->adns_cond); + + if(!item) { + break; + } + + if(time(NULL) < item->deadline) { + logger(mesh, MESHLINK_DEBUG, "Resolving %s port %s", item->host, item->serv); + devtool_adns_resolve_probe(); + int result = getaddrinfo(item->host, item->serv, NULL, &item->ai); + + if(result) { + item->ai = NULL; + item->err = errno; + } + } else { + logger(mesh, MESHLINK_WARNING, "Deadline passed for DNS request %s port %s", item->host, item->serv); + item->ai = NULL; + item->err = ETIMEDOUT; + } + + if(meshlink_queue_push(&mesh->adns_done_queue, item)) { + signal_trigger(&mesh->loop, &mesh->adns_signal); + } else { + free(item->host); + free(item->serv); + free(item); + } + } + + return NULL; +} + +static void adns_cb_handler(event_loop_t *loop, void *data) { + (void)loop; + meshlink_handle_t *mesh = data; + + for(adns_item_t *item; (item = meshlink_queue_pop(&mesh->adns_done_queue));) { + item->cb(mesh, item->host, item->serv, item->data, item->ai, item->err); + free(item); + } +} + +void init_adns(meshlink_handle_t *mesh) { + meshlink_queue_init(&mesh->adns_queue); + meshlink_queue_init(&mesh->adns_done_queue); + signal_add(&mesh->loop, &mesh->adns_signal, adns_cb_handler, mesh, 1); + pthread_create(&mesh->adns_thread, NULL, adns_loop, mesh); +} + +void exit_adns(meshlink_handle_t *mesh) { + if(!mesh->adns_signal.cb) { + return; + } + + /* Drain the queue of any pending ADNS requests */ + for(adns_item_t *item; (item = meshlink_queue_pop(&mesh->adns_queue));) { + free(item->host); + free(item->serv); + free(item); + } + + /* Signal the ADNS thread to stop */ + if(!meshlink_queue_push(&mesh->adns_queue, NULL)) { + abort(); + } + + pthread_cond_signal(&mesh->adns_cond); + + pthread_join(mesh->adns_thread, NULL); + meshlink_queue_exit(&mesh->adns_queue); + signal_del(&mesh->loop, &mesh->adns_signal); +} + +void adns_queue(meshlink_handle_t *mesh, char *host, char *serv, adns_cb_t cb, void *data, int timeout) { + adns_item_t *item = xmalloc(sizeof(*item)); + item->cb = cb; + item->data = data; + item->deadline = time(NULL) + timeout; + item->host = host; + item->serv = serv; + + logger(mesh, MESHLINK_DEBUG, "Enqueueing DNS request for %s port %s", item->host, item->serv); + + if(!meshlink_queue_push(&mesh->adns_queue, item)) { + abort(); + } + + pthread_cond_signal(&mesh->adns_cond); +} + +struct adns_blocking_info { + meshlink_handle_t *mesh; + pthread_mutex_t mutex; + pthread_cond_t cond; + char *host; + char *serv; + struct addrinfo *ai; + int socktype; + bool done; +}; + +static void *adns_blocking_handler(void *data) { + struct adns_blocking_info *info = data; + + logger(info->mesh, MESHLINK_DEBUG, "Resolving %s port %s", info->host, info->serv); + devtool_adns_resolve_probe(); + + struct addrinfo hint = { + .ai_family = AF_UNSPEC, + .ai_socktype = info->socktype, + }; + + if(getaddrinfo(info->host, info->serv, &hint, &info->ai)) { + info->ai = NULL; + } + + if(pthread_mutex_lock(&info->mutex) != 0) { + abort(); + } + + bool cleanup = info->done; + + if(!info->done) { + info->done = true; + pthread_cond_signal(&info->cond); + } + + pthread_mutex_unlock(&info->mutex); + + if(cleanup) { + free(info->host); + free(info->serv); + free(info); + } + + return NULL; +} + +struct addrinfo *adns_blocking_request(meshlink_handle_t *mesh, char *host, char *serv, int socktype, int timeout) { + struct adns_blocking_info *info = xzalloc(sizeof(*info)); + + info->mesh = mesh; + info->host = host; + info->serv = serv; + info->socktype = socktype; + pthread_mutex_init(&info->mutex, NULL); + pthread_cond_init(&info->cond, NULL); + + struct timespec deadline; + clock_gettime(CLOCK_REALTIME, &deadline); + deadline.tv_sec += timeout; + + pthread_t thread; + + if(pthread_create(&thread, NULL, adns_blocking_handler, info)) { + free(info->host); + free(info->serv); + free(info); + return NULL; + } else { + pthread_detach(thread); + } + + if(pthread_mutex_lock(&info->mutex) != 0) { + abort(); + } + + pthread_cond_timedwait(&info->cond, &info->mutex, &deadline); + + struct addrinfo *result = NULL; + bool cleanup = info->done; + + if(info->done) { + result = info->ai; + } else { + logger(mesh, MESHLINK_WARNING, "Deadline passed for DNS request %s port %s", host, serv); + info->done = true; + } + + pthread_mutex_unlock(&info->mutex); + + if(cleanup) { + free(info->host); + free(info->serv); + free(info); + } + + return result; +} diff --git a/src/adns.h b/src/adns.h new file mode 100644 index 0000000..ff3166d --- /dev/null +++ b/src/adns.h @@ -0,0 +1,32 @@ +#ifndef MESHLINK_ADNS_H +#define MESHLINK_ADNS_H + +/* + adns.h -- header file for adns.c + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "meshlink_internal.h" + +typedef void (*adns_cb_t)(meshlink_handle_t *mesh, char *host, char *serv, void *data, struct addrinfo *ai, int err); + +void init_adns(meshlink_handle_t *mesh); +void exit_adns(meshlink_handle_t *mesh); +void adns_queue(meshlink_handle_t *mesh, char *host, char *serv, adns_cb_t cb, void *data, int timeout); +struct addrinfo *adns_blocking_request(meshlink_handle_t *mesh, char *host, char *serv, int socktype, int timeout); + +#endif diff --git a/src/buffer.c b/src/buffer.c new file mode 100644 index 0000000..c6c3495 --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,122 @@ +/* + buffer.c -- buffer management + Copyright (C) 2014-2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "buffer.h" +#include "xalloc.h" + +void buffer_compact(buffer_t *buffer, size_t maxsize) { + if(buffer->len >= maxsize || buffer->offset / 7 > buffer->len / 8) { + memmove(buffer->data, buffer->data + buffer->offset, buffer->len - buffer->offset); + buffer->len -= buffer->offset; + buffer->offset = 0; + } +} + +// Make sure we can add size bytes to the buffer, and return a pointer to the start of those bytes. + +char *buffer_prepare(buffer_t *buffer, size_t size) { + if(!buffer->data) { + assert(!buffer->maxlen); + + buffer->maxlen = size; + buffer->data = xmalloc(size); + } else { + if(buffer->offset && buffer->len + size > buffer->maxlen) { + memmove(buffer->data, buffer->data + buffer->offset, buffer->len - buffer->offset); + buffer->len -= buffer->offset; + buffer->offset = 0; + } + + if(buffer->len + size > buffer->maxlen) { + buffer->maxlen = buffer->len + size; + buffer->data = xrealloc(buffer->data, buffer->maxlen); + } + } + + char *start = buffer->data + buffer->len; + + buffer->len += size; + + return start; +} + +// Copy data into the buffer. + +void buffer_add(buffer_t *buffer, const char *data, size_t size) { + assert(data); + assert(size); + + memcpy(buffer_prepare(buffer, size), data, size); +} + +// Remove given number of bytes from the buffer, return a pointer to the start of them. + +static char *buffer_consume(buffer_t *buffer, size_t size) { + assert(size); + assert(buffer->len - buffer->offset >= size); + + char *start = buffer->data + buffer->offset; + + buffer->offset += size; + + if(buffer->offset >= buffer->len) { + buffer->offset = 0; + buffer->len = 0; + } + + return start; +} + +// Check if there is a complete line in the buffer, and if so, return it NULL-terminated. + +char *buffer_readline(buffer_t *buffer) { + char *newline = memchr(buffer->data + buffer->offset, '\n', buffer->len - buffer->offset); + + if(!newline) { + return NULL; + } + + size_t len = newline + 1 - (buffer->data + buffer->offset); + *newline = 0; + return buffer_consume(buffer, len); +} + +// Check if we have enough bytes in the buffer, and if so, return a pointer to the start of them. + +char *buffer_read(buffer_t *buffer, size_t size) { + assert(size); + + if(buffer->len - buffer->offset < size) { + return NULL; + } + + return buffer_consume(buffer, size); +} + +void buffer_clear(buffer_t *buffer) { + assert(!buffer->data == !buffer->maxlen); + + free(buffer->data); + buffer->data = NULL; + buffer->maxlen = 0; + buffer->len = 0; + buffer->offset = 0; +} diff --git a/src/buffer.h b/src/buffer.h new file mode 100644 index 0000000..26c0dc9 --- /dev/null +++ b/src/buffer.h @@ -0,0 +1,37 @@ +#ifndef MESHLINK_BUFFER_H +#define MESHLINK_BUFFER_H + +/* + conf.h -- header for conf.c + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +typedef struct buffer_t { + char *data; + size_t maxlen; + size_t len; + size_t offset; +} buffer_t; + +void buffer_compact(buffer_t *buffer, size_t maxsize); +char *buffer_prepare(buffer_t *buffer, size_t size); +void buffer_add(buffer_t *buffer, const char *data, size_t size); +char *buffer_readline(buffer_t *buffer); +char *buffer_read(buffer_t *buffer, size_t size); +void buffer_clear(buffer_t *buffer); + +#endif diff --git a/src/chacha-poly1305/chacha-poly1305.c b/src/chacha-poly1305/chacha-poly1305.c new file mode 100644 index 0000000..3b89b74 --- /dev/null +++ b/src/chacha-poly1305/chacha-poly1305.c @@ -0,0 +1,181 @@ +#include "../system.h" + +#include "../xalloc.h" + +#include "chacha.h" +#include "chacha-poly1305.h" +#include "poly1305.h" + +struct chacha_poly1305_ctx { + struct chacha_ctx main_ctx, header_ctx; +}; + +chacha_poly1305_ctx_t *chacha_poly1305_init(void) +{ + chacha_poly1305_ctx_t *ctx = xzalloc(sizeof *ctx); + return ctx; +} + +void chacha_poly1305_exit(chacha_poly1305_ctx_t *ctx) +{ + free(ctx); +} + +bool chacha_poly1305_set_key(chacha_poly1305_ctx_t *ctx, const void *key) +{ + chacha_keysetup(&ctx->main_ctx, key, 256); + chacha_keysetup(&ctx->header_ctx, (uint8_t *)key + 32, 256); + return true; +} + +static void put_u64(void *vp, uint64_t v) +{ + uint8_t *p = (uint8_t *) vp; + + p[0] = (uint8_t) (v >> 56) & 0xff; + p[1] = (uint8_t) (v >> 48) & 0xff; + p[2] = (uint8_t) (v >> 40) & 0xff; + p[3] = (uint8_t) (v >> 32) & 0xff; + p[4] = (uint8_t) (v >> 24) & 0xff; + p[5] = (uint8_t) (v >> 16) & 0xff; + p[6] = (uint8_t) (v >> 8) & 0xff; + p[7] = (uint8_t) v & 0xff; +} + +bool chacha_poly1305_encrypt(chacha_poly1305_ctx_t *ctx, uint64_t seqnr, const void *indata, size_t inlen, void *outdata, size_t *outlen) { + uint8_t seqbuf[8]; + const uint8_t one[8] = { 1, 0, 0, 0, 0, 0, 0, 0 }; /* NB little-endian */ + uint8_t poly_key[POLY1305_KEYLEN]; + + /* + * Run ChaCha20 once to generate the Poly1305 key. The IV is the + * packet sequence number. + */ + memset(poly_key, 0, sizeof(poly_key)); + put_u64(seqbuf, seqnr); + chacha_ivsetup(&ctx->main_ctx, seqbuf, NULL); + chacha_encrypt_bytes(&ctx->main_ctx, poly_key, poly_key, sizeof(poly_key)); + + /* Set Chacha's block counter to 1 */ + chacha_ivsetup(&ctx->main_ctx, seqbuf, one); + + chacha_encrypt_bytes(&ctx->main_ctx, indata, outdata, inlen); + poly1305_auth((uint8_t *)outdata + inlen, outdata, inlen, poly_key); + + if (outlen) + *outlen = inlen + POLY1305_TAGLEN; + + return true; +} + +bool chacha_poly1305_verify(chacha_poly1305_ctx_t *ctx, uint64_t seqnr, const void *indata, size_t inlen) { + uint8_t seqbuf[8]; + uint8_t expected_tag[POLY1305_TAGLEN], poly_key[POLY1305_KEYLEN]; + + /* + * Run ChaCha20 once to generate the Poly1305 key. The IV is the + * packet sequence number. + */ + memset(poly_key, 0, sizeof(poly_key)); + put_u64(seqbuf, seqnr); + chacha_ivsetup(&ctx->main_ctx, seqbuf, NULL); + chacha_encrypt_bytes(&ctx->main_ctx, poly_key, poly_key, sizeof(poly_key)); + + /* Check tag before anything else */ + inlen -= POLY1305_TAGLEN; + const uint8_t *tag = (const uint8_t *)indata + inlen; + + poly1305_auth(expected_tag, indata, inlen, poly_key); + if (memcmp(expected_tag, tag, POLY1305_TAGLEN)) + return false; + + return true; +} + +bool chacha_poly1305_decrypt(chacha_poly1305_ctx_t *ctx, uint64_t seqnr, const void *indata, size_t inlen, void *outdata, size_t *outlen) { + uint8_t seqbuf[8]; + const uint8_t one[8] = { 1, 0, 0, 0, 0, 0, 0, 0 }; /* NB little-endian */ + uint8_t expected_tag[POLY1305_TAGLEN], poly_key[POLY1305_KEYLEN]; + + /* + * Run ChaCha20 once to generate the Poly1305 key. The IV is the + * packet sequence number. + */ + memset(poly_key, 0, sizeof(poly_key)); + put_u64(seqbuf, seqnr); + chacha_ivsetup(&ctx->main_ctx, seqbuf, NULL); + chacha_encrypt_bytes(&ctx->main_ctx, poly_key, poly_key, sizeof(poly_key)); + + /* Set Chacha's block counter to 1 */ + chacha_ivsetup(&ctx->main_ctx, seqbuf, one); + + /* Check tag before anything else */ + inlen -= POLY1305_TAGLEN; + const uint8_t *tag = (const uint8_t *)indata + inlen; + + poly1305_auth(expected_tag, indata, inlen, poly_key); + if (memcmp(expected_tag, tag, POLY1305_TAGLEN)) + return false; + + chacha_encrypt_bytes(&ctx->main_ctx, indata, outdata, inlen); + + if (outlen) + *outlen = inlen; + + return true; +} + +bool chacha_poly1305_encrypt_iv96(chacha_poly1305_ctx_t *ctx, const uint8_t *seqbuf, const void *indata, size_t inlen, void *outdata, size_t *outlen) { + const uint8_t one[4] = { 1, 0, 0, 0 }; /* NB little-endian */ + uint8_t poly_key[POLY1305_KEYLEN]; + + /* + * Run ChaCha20 once to generate the Poly1305 key. The IV is the + * packet sequence number. + */ + memset(poly_key, 0, sizeof(poly_key)); + chacha_ivsetup_96(&ctx->main_ctx, seqbuf, NULL); + chacha_encrypt_bytes(&ctx->main_ctx, poly_key, poly_key, sizeof(poly_key)); + + /* Set Chacha's block counter to 1 */ + chacha_ivsetup_96(&ctx->main_ctx, seqbuf, one); + + chacha_encrypt_bytes(&ctx->main_ctx, indata, outdata, inlen); + poly1305_auth((uint8_t *)outdata + inlen, outdata, inlen, poly_key); + + if (outlen) + *outlen = inlen + POLY1305_TAGLEN; + + return true; +} + +bool chacha_poly1305_decrypt_iv96(chacha_poly1305_ctx_t *ctx, const uint8_t *seqbuf, const void *indata, size_t inlen, void *outdata, size_t *outlen) { + const uint8_t one[4] = { 1, 0, 0, 0 }; /* NB little-endian */ + uint8_t expected_tag[POLY1305_TAGLEN], poly_key[POLY1305_KEYLEN]; + + /* + * Run ChaCha20 once to generate the Poly1305 key. The IV is the + * packet sequence number. + */ + memset(poly_key, 0, sizeof(poly_key)); + chacha_ivsetup_96(&ctx->main_ctx, seqbuf, NULL); + chacha_encrypt_bytes(&ctx->main_ctx, poly_key, poly_key, sizeof(poly_key)); + + /* Set Chacha's block counter to 1 */ + chacha_ivsetup_96(&ctx->main_ctx, seqbuf, one); + + /* Check tag before anything else */ + inlen -= POLY1305_TAGLEN; + const uint8_t *tag = (const uint8_t *)indata + inlen; + + poly1305_auth(expected_tag, indata, inlen, poly_key); + if (memcmp(expected_tag, tag, POLY1305_TAGLEN)) + return false; + + chacha_encrypt_bytes(&ctx->main_ctx, indata, outdata, inlen); + + if (outlen) + *outlen = inlen; + + return true; +} diff --git a/src/chacha-poly1305/chacha-poly1305.h b/src/chacha-poly1305/chacha-poly1305.h new file mode 100644 index 0000000..7c6c30a --- /dev/null +++ b/src/chacha-poly1305/chacha-poly1305.h @@ -0,0 +1,19 @@ +#ifndef CHACHA_POLY1305_H +#define CHACHA_POLY1305_H + +#define CHACHA_POLY1305_KEYLEN 64 + +typedef struct chacha_poly1305_ctx chacha_poly1305_ctx_t; + +extern chacha_poly1305_ctx_t *chacha_poly1305_init(void); +extern void chacha_poly1305_exit(chacha_poly1305_ctx_t *); +extern bool chacha_poly1305_set_key(chacha_poly1305_ctx_t *ctx, const void *key); + +extern bool chacha_poly1305_encrypt(chacha_poly1305_ctx_t *ctx, uint64_t seqnr, const void *indata, size_t inlen, void *outdata, size_t *outlen); +extern bool chacha_poly1305_verify(chacha_poly1305_ctx_t *ctx, uint64_t seqnr, const void *indata, size_t inlen); +extern bool chacha_poly1305_decrypt(chacha_poly1305_ctx_t *ctx, uint64_t seqnr, const void *indata, size_t inlen, void *outdata, size_t *outlen); + +extern bool chacha_poly1305_encrypt_iv96(chacha_poly1305_ctx_t *ctx, const uint8_t *seqbuf, const void *indata, size_t inlen, void *outdata, size_t *outlen); +extern bool chacha_poly1305_decrypt_iv96(chacha_poly1305_ctx_t *ctx, const uint8_t *seqbuf, const void *indata, size_t inlen, void *outdata, size_t *outlen); + +#endif //CHACHA_POLY1305_H diff --git a/src/chacha-poly1305/chacha.c b/src/chacha-poly1305/chacha.c new file mode 100644 index 0000000..a158de5 --- /dev/null +++ b/src/chacha-poly1305/chacha.c @@ -0,0 +1,223 @@ +/* +chacha-merged.c version 20080118 +D. J. Bernstein +Public domain. +*/ + +#include "../system.h" + +#include "chacha.h" + +typedef struct chacha_ctx chacha_ctx; + +#define U8C(v) (v##U) +#define U32C(v) (v##U) + +#define U8V(v) ((uint8_t)(v) & U8C(0xFF)) +#define U32V(v) ((uint32_t)(v) & U32C(0xFFFFFFFF)) + +#define ROTL32(v, n) \ + (U32V((v) << (n)) | ((v) >> (32 - (n)))) + +#define U8TO32_LITTLE(p) \ + (((uint32_t)((p)[0]) ) | \ + ((uint32_t)((p)[1]) << 8) | \ + ((uint32_t)((p)[2]) << 16) | \ + ((uint32_t)((p)[3]) << 24)) + +#define U32TO8_LITTLE(p, v) \ + do { \ + (p)[0] = U8V((v) ); \ + (p)[1] = U8V((v) >> 8); \ + (p)[2] = U8V((v) >> 16); \ + (p)[3] = U8V((v) >> 24); \ + } while (0) + +#define ROTATE(v,c) (ROTL32(v,c)) +#define XOR(v,w) ((v) ^ (w)) +#define PLUS(v,w) (U32V((v) + (w))) +#define PLUSONE(v) (PLUS((v),1)) + +#define QUARTERROUND(a,b,c,d) \ + a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \ + a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c), 7); + +static const char sigma[16] = "expand 32-byte k"; +static const char tau[16] = "expand 16-byte k"; + +void chacha_keysetup(chacha_ctx *x, const uint8_t *k, uint32_t kbits) +{ + const char *constants; + + x->input[4] = U8TO32_LITTLE(k + 0); + x->input[5] = U8TO32_LITTLE(k + 4); + x->input[6] = U8TO32_LITTLE(k + 8); + x->input[7] = U8TO32_LITTLE(k + 12); + if (kbits == 256) { /* recommended */ + k += 16; + constants = sigma; + } else { /* kbits == 128 */ + constants = tau; + } + x->input[8] = U8TO32_LITTLE(k + 0); + x->input[9] = U8TO32_LITTLE(k + 4); + x->input[10] = U8TO32_LITTLE(k + 8); + x->input[11] = U8TO32_LITTLE(k + 12); + x->input[0] = U8TO32_LITTLE(constants + 0); + x->input[1] = U8TO32_LITTLE(constants + 4); + x->input[2] = U8TO32_LITTLE(constants + 8); + x->input[3] = U8TO32_LITTLE(constants + 12); +} + +void chacha_ivsetup(chacha_ctx *x, const uint8_t *iv, const uint8_t *counter) +{ + x->input[12] = counter == NULL ? 0 : U8TO32_LITTLE(counter + 0); + x->input[13] = counter == NULL ? 0 : U8TO32_LITTLE(counter + 4); + x->input[14] = U8TO32_LITTLE(iv + 0); + x->input[15] = U8TO32_LITTLE(iv + 4); +} + +void chacha_ivsetup_96(chacha_ctx *x, const uint8_t *iv, const uint8_t *counter) +{ + x->input[12] = counter == NULL ? 0 : U8TO32_LITTLE(counter + 0); + x->input[13] = U8TO32_LITTLE(iv + 0); + x->input[14] = U8TO32_LITTLE(iv + 4); + x->input[15] = U8TO32_LITTLE(iv + 8); +} + +void +chacha_encrypt_bytes(chacha_ctx *x, const uint8_t *m, uint8_t *c, uint32_t bytes) +{ + uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; + uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; + uint8_t *ctarget = NULL; + uint8_t tmp[64]; + uint32_t i; + + if (!bytes) + return; + + j0 = x->input[0]; + j1 = x->input[1]; + j2 = x->input[2]; + j3 = x->input[3]; + j4 = x->input[4]; + j5 = x->input[5]; + j6 = x->input[6]; + j7 = x->input[7]; + j8 = x->input[8]; + j9 = x->input[9]; + j10 = x->input[10]; + j11 = x->input[11]; + j12 = x->input[12]; + j13 = x->input[13]; + j14 = x->input[14]; + j15 = x->input[15]; + + for (;;) { + if (bytes < 64) { + for (i = 0; i < bytes; ++i) + tmp[i] = m[i]; + m = tmp; + ctarget = c; + c = tmp; + } + x0 = j0; + x1 = j1; + x2 = j2; + x3 = j3; + x4 = j4; + x5 = j5; + x6 = j6; + x7 = j7; + x8 = j8; + x9 = j9; + x10 = j10; + x11 = j11; + x12 = j12; + x13 = j13; + x14 = j14; + x15 = j15; + for (i = 20; i > 0; i -= 2) { + QUARTERROUND(x0, x4, x8, x12) + QUARTERROUND(x1, x5, x9, x13) + QUARTERROUND(x2, x6, x10, x14) + QUARTERROUND(x3, x7, x11, x15) + QUARTERROUND(x0, x5, x10, x15) + QUARTERROUND(x1, x6, x11, x12) + QUARTERROUND(x2, x7, x8, x13) + QUARTERROUND(x3, x4, x9, x14) + } + x0 = PLUS(x0, j0); + x1 = PLUS(x1, j1); + x2 = PLUS(x2, j2); + x3 = PLUS(x3, j3); + x4 = PLUS(x4, j4); + x5 = PLUS(x5, j5); + x6 = PLUS(x6, j6); + x7 = PLUS(x7, j7); + x8 = PLUS(x8, j8); + x9 = PLUS(x9, j9); + x10 = PLUS(x10, j10); + x11 = PLUS(x11, j11); + x12 = PLUS(x12, j12); + x13 = PLUS(x13, j13); + x14 = PLUS(x14, j14); + x15 = PLUS(x15, j15); + + x0 = XOR(x0, U8TO32_LITTLE(m + 0)); + x1 = XOR(x1, U8TO32_LITTLE(m + 4)); + x2 = XOR(x2, U8TO32_LITTLE(m + 8)); + x3 = XOR(x3, U8TO32_LITTLE(m + 12)); + x4 = XOR(x4, U8TO32_LITTLE(m + 16)); + x5 = XOR(x5, U8TO32_LITTLE(m + 20)); + x6 = XOR(x6, U8TO32_LITTLE(m + 24)); + x7 = XOR(x7, U8TO32_LITTLE(m + 28)); + x8 = XOR(x8, U8TO32_LITTLE(m + 32)); + x9 = XOR(x9, U8TO32_LITTLE(m + 36)); + x10 = XOR(x10, U8TO32_LITTLE(m + 40)); + x11 = XOR(x11, U8TO32_LITTLE(m + 44)); + x12 = XOR(x12, U8TO32_LITTLE(m + 48)); + x13 = XOR(x13, U8TO32_LITTLE(m + 52)); + x14 = XOR(x14, U8TO32_LITTLE(m + 56)); + x15 = XOR(x15, U8TO32_LITTLE(m + 60)); + + j12 = PLUSONE(j12); + if (!j12) { + j13 = PLUSONE(j13); + /* stopping at 2^70 bytes per nonce is user's responsibility */ + } + + U32TO8_LITTLE(c + 0, x0); + U32TO8_LITTLE(c + 4, x1); + U32TO8_LITTLE(c + 8, x2); + U32TO8_LITTLE(c + 12, x3); + U32TO8_LITTLE(c + 16, x4); + U32TO8_LITTLE(c + 20, x5); + U32TO8_LITTLE(c + 24, x6); + U32TO8_LITTLE(c + 28, x7); + U32TO8_LITTLE(c + 32, x8); + U32TO8_LITTLE(c + 36, x9); + U32TO8_LITTLE(c + 40, x10); + U32TO8_LITTLE(c + 44, x11); + U32TO8_LITTLE(c + 48, x12); + U32TO8_LITTLE(c + 52, x13); + U32TO8_LITTLE(c + 56, x14); + U32TO8_LITTLE(c + 60, x15); + + if (bytes <= 64) { + if (bytes < 64) { + for (i = 0; i < bytes; ++i) + ctarget[i] = c[i]; + } + x->input[12] = j12; + x->input[13] = j13; + return; + } + bytes -= 64; + c += 64; + m += 64; + } +} diff --git a/src/chacha-poly1305/chacha.h b/src/chacha-poly1305/chacha.h new file mode 100644 index 0000000..3874605 --- /dev/null +++ b/src/chacha-poly1305/chacha.h @@ -0,0 +1,25 @@ +/* +chacha-merged.c version 20080118 +D. J. Bernstein +Public domain. +*/ + +#ifndef CHACHA_H +#define CHACHA_H + +struct chacha_ctx { + uint32_t input[16]; +}; + +#define CHACHA_MINKEYLEN 16 +#define CHACHA_NONCELEN 8 +#define CHACHA_CTRLEN 8 +#define CHACHA_STATELEN (CHACHA_NONCELEN+CHACHA_CTRLEN) +#define CHACHA_BLOCKLEN 64 + +void chacha_keysetup(struct chacha_ctx *x, const uint8_t *k, uint32_t kbits); +void chacha_ivsetup(struct chacha_ctx *x, const uint8_t *iv, const uint8_t *ctr); +void chacha_ivsetup_96(struct chacha_ctx *x, const uint8_t *iv, const uint8_t *ctr); +void chacha_encrypt_bytes(struct chacha_ctx *x, const uint8_t *m, uint8_t * c, uint32_t bytes); + +#endif /* CHACHA_H */ diff --git a/src/chacha-poly1305/poly1305.c b/src/chacha-poly1305/poly1305.c new file mode 100644 index 0000000..f1ddf2d --- /dev/null +++ b/src/chacha-poly1305/poly1305.c @@ -0,0 +1,197 @@ +/* + * Public Domain poly1305 from Andrew Moon + * poly1305-donna-unrolled.c from https://github.com/floodyberry/poly1305-donna + */ + +#include "../system.h" + +#include "poly1305.h" + +#define mul32x32_64(a,b) ((uint64_t)(a) * (b)) + +#define U8TO32_LE(p) \ + (((uint32_t)((p)[0])) | \ + ((uint32_t)((p)[1]) << 8) | \ + ((uint32_t)((p)[2]) << 16) | \ + ((uint32_t)((p)[3]) << 24)) + +#define U32TO8_LE(p, v) \ + do { \ + (p)[0] = (uint8_t)((v)); \ + (p)[1] = (uint8_t)((v) >> 8); \ + (p)[2] = (uint8_t)((v) >> 16); \ + (p)[3] = (uint8_t)((v) >> 24); \ + } while (0) + +void +poly1305_auth(unsigned char out[POLY1305_TAGLEN], const unsigned char *m, size_t inlen, const unsigned char key[POLY1305_KEYLEN]) +{ + uint32_t t0, t1, t2, t3; + uint32_t h0, h1, h2, h3, h4; + uint32_t r0, r1, r2, r3, r4; + uint32_t s1, s2, s3, s4; + uint32_t b, nb; + size_t j; + uint64_t t[5]; + uint64_t f0, f1, f2, f3; + uint32_t g0, g1, g2, g3, g4; + uint64_t c; + unsigned char mp[16]; + + /* clamp key */ + t0 = U8TO32_LE(key + 0); + t1 = U8TO32_LE(key + 4); + t2 = U8TO32_LE(key + 8); + t3 = U8TO32_LE(key + 12); + + /* precompute multipliers */ + r0 = t0 & 0x3ffffff; + t0 >>= 26; + t0 |= t1 << 6; + r1 = t0 & 0x3ffff03; + t1 >>= 20; + t1 |= t2 << 12; + r2 = t1 & 0x3ffc0ff; + t2 >>= 14; + t2 |= t3 << 18; + r3 = t2 & 0x3f03fff; + t3 >>= 8; + r4 = t3 & 0x00fffff; + + s1 = r1 * 5; + s2 = r2 * 5; + s3 = r3 * 5; + s4 = r4 * 5; + + /* init state */ + h0 = 0; + h1 = 0; + h2 = 0; + h3 = 0; + h4 = 0; + + /* full blocks */ + if (inlen < 16) + goto poly1305_donna_atmost15bytes; + + poly1305_donna_16bytes: + m += 16; + inlen -= 16; + + t0 = U8TO32_LE(m - 16); + t1 = U8TO32_LE(m - 12); + t2 = U8TO32_LE(m - 8); + t3 = U8TO32_LE(m - 4); + + h0 += t0 & 0x3ffffff; + h1 += ((((uint64_t) t1 << 32) | t0) >> 26) & 0x3ffffff; + h2 += ((((uint64_t) t2 << 32) | t1) >> 20) & 0x3ffffff; + h3 += ((((uint64_t) t3 << 32) | t2) >> 14) & 0x3ffffff; + h4 += (t3 >> 8) | (1 << 24); + + poly1305_donna_mul: + t[0] = mul32x32_64(h0, r0) + mul32x32_64(h1, s4) + mul32x32_64(h2, s3) + mul32x32_64(h3, s2) + mul32x32_64(h4, s1); + t[1] = mul32x32_64(h0, r1) + mul32x32_64(h1, r0) + mul32x32_64(h2, s4) + mul32x32_64(h3, s3) + mul32x32_64(h4, s2); + t[2] = mul32x32_64(h0, r2) + mul32x32_64(h1, r1) + mul32x32_64(h2, r0) + mul32x32_64(h3, s4) + mul32x32_64(h4, s3); + t[3] = mul32x32_64(h0, r3) + mul32x32_64(h1, r2) + mul32x32_64(h2, r1) + mul32x32_64(h3, r0) + mul32x32_64(h4, s4); + t[4] = mul32x32_64(h0, r4) + mul32x32_64(h1, r3) + mul32x32_64(h2, r2) + mul32x32_64(h3, r1) + mul32x32_64(h4, r0); + + h0 = (uint32_t) t[0] & 0x3ffffff; + c = (t[0] >> 26); + t[1] += c; + h1 = (uint32_t) t[1] & 0x3ffffff; + b = (uint32_t) (t[1] >> 26); + t[2] += b; + h2 = (uint32_t) t[2] & 0x3ffffff; + b = (uint32_t) (t[2] >> 26); + t[3] += b; + h3 = (uint32_t) t[3] & 0x3ffffff; + b = (uint32_t) (t[3] >> 26); + t[4] += b; + h4 = (uint32_t) t[4] & 0x3ffffff; + b = (uint32_t) (t[4] >> 26); + h0 += b * 5; + + if (inlen >= 16) + goto poly1305_donna_16bytes; + + /* final bytes */ + poly1305_donna_atmost15bytes: + if (!inlen) + goto poly1305_donna_finish; + + for (j = 0; j < inlen; j++) + mp[j] = m[j]; + mp[j++] = 1; + for (; j < 16; j++) + mp[j] = 0; + inlen = 0; + + t0 = U8TO32_LE(mp + 0); + t1 = U8TO32_LE(mp + 4); + t2 = U8TO32_LE(mp + 8); + t3 = U8TO32_LE(mp + 12); + + h0 += t0 & 0x3ffffff; + h1 += ((((uint64_t) t1 << 32) | t0) >> 26) & 0x3ffffff; + h2 += ((((uint64_t) t2 << 32) | t1) >> 20) & 0x3ffffff; + h3 += ((((uint64_t) t3 << 32) | t2) >> 14) & 0x3ffffff; + h4 += (t3 >> 8); + + goto poly1305_donna_mul; + + poly1305_donna_finish: + b = h0 >> 26; + h0 = h0 & 0x3ffffff; + h1 += b; + b = h1 >> 26; + h1 = h1 & 0x3ffffff; + h2 += b; + b = h2 >> 26; + h2 = h2 & 0x3ffffff; + h3 += b; + b = h3 >> 26; + h3 = h3 & 0x3ffffff; + h4 += b; + b = h4 >> 26; + h4 = h4 & 0x3ffffff; + h0 += b * 5; + b = h0 >> 26; + h0 = h0 & 0x3ffffff; + h1 += b; + + g0 = h0 + 5; + b = g0 >> 26; + g0 &= 0x3ffffff; + g1 = h1 + b; + b = g1 >> 26; + g1 &= 0x3ffffff; + g2 = h2 + b; + b = g2 >> 26; + g2 &= 0x3ffffff; + g3 = h3 + b; + b = g3 >> 26; + g3 &= 0x3ffffff; + g4 = h4 + b - (1 << 26); + + b = (g4 >> 31) - 1; + nb = ~b; + h0 = (h0 & nb) | (g0 & b); + h1 = (h1 & nb) | (g1 & b); + h2 = (h2 & nb) | (g2 & b); + h3 = (h3 & nb) | (g3 & b); + h4 = (h4 & nb) | (g4 & b); + + f0 = ((h0) | (h1 << 26)) + (uint64_t) U8TO32_LE(&key[16]); + f1 = ((h1 >> 6) | (h2 << 20)) + (uint64_t) U8TO32_LE(&key[20]); + f2 = ((h2 >> 12) | (h3 << 14)) + (uint64_t) U8TO32_LE(&key[24]); + f3 = ((h3 >> 18) | (h4 << 8)) + (uint64_t) U8TO32_LE(&key[28]); + + U32TO8_LE(&out[0], f0); + f1 += (f0 >> 32); + U32TO8_LE(&out[4], f1); + f2 += (f1 >> 32); + U32TO8_LE(&out[8], f2); + f3 += (f2 >> 32); + U32TO8_LE(&out[12], f3); +} diff --git a/src/chacha-poly1305/poly1305.h b/src/chacha-poly1305/poly1305.h new file mode 100644 index 0000000..9a64015 --- /dev/null +++ b/src/chacha-poly1305/poly1305.h @@ -0,0 +1,16 @@ +/* $OpenBSD: poly1305.h,v 1.2 2013/12/19 22:57:13 djm Exp $ */ + +/* + * Public Domain poly1305 from Andrew Moon + * poly1305-donna-unrolled.c from https://github.com/floodyberry/poly1305-donna + */ + +#ifndef POLY1305_H +#define POLY1305_H + +#define POLY1305_KEYLEN 32 +#define POLY1305_TAGLEN 16 + +void poly1305_auth(uint8_t out[POLY1305_TAGLEN], const uint8_t *m, size_t inlen, const uint8_t key[POLY1305_KEYLEN]); + +#endif /* POLY1305_H */ diff --git a/src/conf.c b/src/conf.c new file mode 100644 index 0000000..db91d5f --- /dev/null +++ b/src/conf.c @@ -0,0 +1,1115 @@ +/* + econf.c -- configuration code + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" +#include +#include +#include + +#include "conf.h" +#include "crypto.h" +#include "logger.h" +#include "meshlink_internal.h" +#include "xalloc.h" +#include "packmsg.h" + +/// Generate a path to the main configuration file. +static void make_main_path(meshlink_handle_t *mesh, const char *conf_subdir, char *path, size_t len) { + assert(conf_subdir); + assert(path); + assert(len); + + snprintf(path, len, "%s" SLASH "%s" SLASH "meshlink.conf", mesh->confbase, conf_subdir); +} + +/// Generate a path to a host configuration file. +static void make_host_path(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, char *path, size_t len) { + assert(conf_subdir); + assert(name); + assert(path); + assert(len); + + snprintf(path, len, "%s" SLASH "%s" SLASH "hosts" SLASH "%s", mesh->confbase, conf_subdir, name); +} + +/// Generate a path to an unused invitation file. +static void make_invitation_path(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, char *path, size_t len) { + assert(conf_subdir); + assert(name); + assert(path); + assert(len); + + snprintf(path, len, "%s" SLASH "%s" SLASH "invitations" SLASH "%s", mesh->confbase, conf_subdir, name); +} + +/// Generate a path to a used invitation file. +static void make_used_invitation_path(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, char *path, size_t len) { + assert(conf_subdir); + assert(name); + assert(path); + assert(len); + + snprintf(path, len, "%s" SLASH "%s" SLASH "invitations" SLASH "%s.used", mesh->confbase, conf_subdir, name); +} + +/// Remove a directory recursively +static bool deltree(const char *dirname) { + assert(dirname); + + DIR *d = opendir(dirname); + + if(d) { + struct dirent *ent; + + while((ent = readdir(d))) { + if(ent->d_name[0] == '.') { + if(!ent->d_name[1] || (ent->d_name[1] == '.' && !ent->d_name[2])) { + continue; + } + } + + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s" SLASH "%s", dirname, ent->d_name); + + if(unlink(filename)) { + if(!deltree(filename)) { + return false; + } + } + } + + closedir(d); + } else { + return errno == ENOENT; + } + + return rmdir(dirname) == 0; +} + +bool sync_path(const char *pathname) { + assert(pathname); + + int fd = open(pathname, O_RDONLY); + + if(fd < 0) { + logger(NULL, MESHLINK_ERROR, "Failed to open %s: %s\n", pathname, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + if(fsync(fd)) { + logger(NULL, MESHLINK_ERROR, "Failed to sync %s: %s\n", pathname, strerror(errno)); + close(fd); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + if(close(fd)) { + logger(NULL, MESHLINK_ERROR, "Failed to close %s: %s\n", pathname, strerror(errno)); + close(fd); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + return true; +} + +/// Try decrypting the main configuration file from the given sub-directory. +static bool main_config_decrypt(meshlink_handle_t *mesh, const char *conf_subdir) { + assert(mesh->config_key); + assert(mesh->confbase); + assert(conf_subdir); + + config_t config; + + if(!main_config_read(mesh, conf_subdir, &config, mesh->config_key)) { + logger(mesh, MESHLINK_ERROR, "Could not read main configuration file"); + return false; + } + + packmsg_input_t in = {config.buf, config.len}; + + uint32_t version = packmsg_get_uint32(&in); + config_free(&config); + + return version == MESHLINK_CONFIG_VERSION; +} + +/// Create a fresh configuration directory +bool config_init(meshlink_handle_t *mesh, const char *conf_subdir) { + assert(conf_subdir); + + if(!mesh->confbase) { + return true; + } + + char path[PATH_MAX]; + + // Create "current" sub-directory in the confbase + snprintf(path, sizeof(path), "%s" SLASH "%s", mesh->confbase, conf_subdir); + + if(!deltree(path)) { + logger(mesh, MESHLINK_DEBUG, "Could not delete directory %s: %s\n", path, strerror(errno)); + return false; + } + + if(mkdir(path, 0700)) { + logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", path, strerror(errno)); + return false; + } + + make_host_path(mesh, conf_subdir, "", path, sizeof(path)); + + if(mkdir(path, 0700)) { + logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", path, strerror(errno)); + return false; + } + + make_invitation_path(mesh, conf_subdir, "", path, sizeof(path)); + + if(mkdir(path, 0700)) { + logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", path, strerror(errno)); + return false; + } + + return true; +} + +/// Wipe an existing configuration directory +bool config_destroy(const char *confbase, const char *conf_subdir) { + assert(conf_subdir); + + if(!confbase) { + return true; + } + + struct stat st; + + char path[PATH_MAX]; + + // Check the presence of configuration base sub directory. + snprintf(path, sizeof(path), "%s" SLASH "%s", confbase, conf_subdir); + + if(stat(path, &st)) { + if(errno == ENOENT) { + return true; + } else { + logger(NULL, MESHLINK_ERROR, "Cannot stat %s: %s\n", path, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + } + + // Remove meshlink.conf + snprintf(path, sizeof(path), "%s" SLASH "%s" SLASH "meshlink.conf", confbase, conf_subdir); + + if(unlink(path)) { + if(errno != ENOENT) { + logger(NULL, MESHLINK_ERROR, "Cannot delete %s: %s\n", path, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + } + + snprintf(path, sizeof(path), "%s" SLASH "%s", confbase, conf_subdir); + + if(!deltree(path)) { + logger(NULL, MESHLINK_ERROR, "Cannot delete %s: %s\n", path, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + return sync_path(confbase); +} + +static bool copytree(const char *src_dir_name, const void *src_key, const char *dst_dir_name, const void *dst_key) { + assert(src_dir_name); + assert(dst_dir_name); + + char src_filename[PATH_MAX]; + char dst_filename[PATH_MAX]; + struct dirent *ent; + + DIR *src_dir = opendir(src_dir_name); + + if(!src_dir) { + logger(NULL, MESHLINK_ERROR, "Could not open directory file %s\n", src_dir_name); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + // Delete if already exists and create a new destination directory + if(!deltree(dst_dir_name)) { + logger(NULL, MESHLINK_ERROR, "Cannot delete %s: %s\n", dst_dir_name, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + if(mkdir(dst_dir_name, 0700)) { + logger(NULL, MESHLINK_ERROR, "Could not create directory %s\n", dst_filename); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + while((ent = readdir(src_dir))) { + if(ent->d_name[0] == '.') { + continue; + } + + snprintf(dst_filename, sizeof(dst_filename), "%s" SLASH "%s", dst_dir_name, ent->d_name); + snprintf(src_filename, sizeof(src_filename), "%s" SLASH "%s", src_dir_name, ent->d_name); + + if(ent->d_type == DT_DIR) { + if(!copytree(src_filename, src_key, dst_filename, dst_key)) { + logger(NULL, MESHLINK_ERROR, "Copying %s to %s failed\n", src_filename, dst_filename); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + if(!sync_path(dst_filename)) { + return false; + } + } else if(ent->d_type == DT_REG) { + struct stat st; + config_t config; + + if(stat(src_filename, &st)) { + logger(NULL, MESHLINK_ERROR, "Could not stat file `%s': %s\n", src_filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + FILE *f = fopen(src_filename, "r"); + + if(!f) { + logger(NULL, MESHLINK_ERROR, "Failed to open `%s': %s\n", src_filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + if(!config_read_file(NULL, f, &config, src_key)) { + logger(NULL, MESHLINK_ERROR, "Failed to read `%s': %s\n", src_filename, strerror(errno)); + fclose(f); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + if(fclose(f)) { + logger(NULL, MESHLINK_ERROR, "Failed to close `%s': %s\n", src_filename, strerror(errno)); + config_free(&config); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + f = fopen(dst_filename, "w"); + + if(!f) { + logger(NULL, MESHLINK_ERROR, "Failed to open `%s': %s", dst_filename, strerror(errno)); + config_free(&config); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + if(!config_write_file(NULL, f, &config, dst_key)) { + logger(NULL, MESHLINK_ERROR, "Failed to write `%s': %s", dst_filename, strerror(errno)); + config_free(&config); + fclose(f); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + if(fclose(f)) { + logger(NULL, MESHLINK_ERROR, "Failed to close `%s': %s", dst_filename, strerror(errno)); + config_free(&config); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + config_free(&config); + + struct utimbuf times; + times.modtime = st.st_mtime; + times.actime = st.st_atime; + + if(utime(dst_filename, ×)) { + logger(NULL, MESHLINK_ERROR, "Failed to utime `%s': %s", dst_filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + } + } + + closedir(src_dir); + return true; +} + +bool config_copy(meshlink_handle_t *mesh, const char *src_dir_name, const void *src_key, const char *dst_dir_name, const void *dst_key) { + assert(src_dir_name); + assert(dst_dir_name); + + char src_filename[PATH_MAX]; + char dst_filename[PATH_MAX]; + + snprintf(dst_filename, sizeof(dst_filename), "%s" SLASH "%s", mesh->confbase, dst_dir_name); + snprintf(src_filename, sizeof(src_filename), "%s" SLASH "%s", mesh->confbase, src_dir_name); + + return copytree(src_filename, src_key, dst_filename, dst_key); +} + +/// Check the presence of the main configuration file. +bool main_config_exists(meshlink_handle_t *mesh, const char *conf_subdir) { + assert(conf_subdir); + + if(!mesh->confbase) { + return false; + } + + char path[PATH_MAX]; + make_main_path(mesh, conf_subdir, path, sizeof(path)); + return access(path, F_OK) == 0; +} + +bool config_rename(meshlink_handle_t *mesh, const char *old_conf_subdir, const char *new_conf_subdir) { + assert(old_conf_subdir); + assert(new_conf_subdir); + + if(!mesh->confbase) { + return false; + } + + char old_path[PATH_MAX]; + char new_path[PATH_MAX]; + + snprintf(old_path, sizeof(old_path), "%s" SLASH "%s", mesh->confbase, old_conf_subdir); + snprintf(new_path, sizeof(new_path), "%s" SLASH "%s", mesh->confbase, new_conf_subdir); + + return rename(old_path, new_path) == 0 && sync_path(mesh->confbase); +} + +bool config_sync(meshlink_handle_t *mesh, const char *conf_subdir) { + assert(conf_subdir); + + if(!mesh->confbase || mesh->storage_policy == MESHLINK_STORAGE_DISABLED) { + return true; + } + + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s" SLASH "%s" SLASH "hosts", mesh->confbase, conf_subdir); + + if(!sync_path(path)) { + return false; + } + + snprintf(path, sizeof(path), "%s" SLASH "%s", mesh->confbase, conf_subdir); + + if(!sync_path(path)) { + return false; + } + + return true; +} + +bool meshlink_confbase_exists(meshlink_handle_t *mesh) { + if(!mesh->confbase) { + return false; + } + + bool confbase_exists = false; + bool confbase_decryptable = false; + + if(main_config_exists(mesh, "current")) { + confbase_exists = true; + + if(mesh->config_key && main_config_decrypt(mesh, "current")) { + confbase_decryptable = true; + } + } + + if(mesh->config_key && !confbase_decryptable && main_config_exists(mesh, "new")) { + confbase_exists = true; + + if(main_config_decrypt(mesh, "new")) { + if(!config_destroy(mesh->confbase, "current")) { + return false; + } + + if(!config_rename(mesh, "new", "current")) { + return false; + } + + confbase_decryptable = true; + } + } + + if(mesh->config_key && !confbase_decryptable && main_config_exists(mesh, "old")) { + confbase_exists = true; + + if(main_config_decrypt(mesh, "old")) { + if(!config_destroy(mesh->confbase, "current")) { + return false; + } + + if(!config_rename(mesh, "old", "current")) { + return false; + } + + confbase_decryptable = true; + } + } + + // Cleanup if current is existing with old and new + if(confbase_exists && confbase_decryptable) { + if(!config_destroy(mesh->confbase, "old") || !config_destroy(mesh->confbase, "new")) { + return false; + } + } + + return confbase_exists; +} + +/// Lock the main configuration file. Creates confbase if necessary. +bool main_config_lock(meshlink_handle_t *mesh, const char *lock_filename) { + if(!mesh->confbase) { + return true; + } + + assert(lock_filename); + + if(mkdir(mesh->confbase, 0700) && errno != EEXIST) { + logger(NULL, MESHLINK_ERROR, "Cannot create configuration directory %s: %s", mesh->confbase, strerror(errno)); + meshlink_close(mesh); + meshlink_errno = MESHLINK_ESTORAGE; + return NULL; + } + + mesh->lockfile = fopen(lock_filename, "w+"); + + if(!mesh->lockfile) { + logger(NULL, MESHLINK_ERROR, "Cannot not open %s: %s\n", lock_filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + +#ifdef FD_CLOEXEC + fcntl(fileno(mesh->lockfile), F_SETFD, FD_CLOEXEC); +#endif + +#ifdef HAVE_MINGW + // TODO: use _locking()? +#else + + if(flock(fileno(mesh->lockfile), LOCK_EX | LOCK_NB) != 0) { + logger(NULL, MESHLINK_ERROR, "Cannot lock %s: %s\n", lock_filename, strerror(errno)); + fclose(mesh->lockfile); + mesh->lockfile = NULL; + meshlink_errno = MESHLINK_EBUSY; + return false; + } + +#endif + + return true; +} + +/// Unlock the main configuration file. +void main_config_unlock(meshlink_handle_t *mesh) { + if(mesh->lockfile) { + fclose(mesh->lockfile); + mesh->lockfile = NULL; + } +} + +/// Read a configuration file from a FILE handle. +bool config_read_file(meshlink_handle_t *mesh, FILE *f, config_t *config, const void *key) { + assert(f); + + long len; + + if(fseek(f, 0, SEEK_END) || !(len = ftell(f)) || fseek(f, 0, SEEK_SET)) { + logger(mesh, MESHLINK_ERROR, "Cannot get config file size: %s\n", strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + uint8_t *buf = xmalloc(len); + + if(fread(buf, len, 1, f) != 1) { + logger(mesh, MESHLINK_ERROR, "Cannot read config file: %s\n", strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + if(key) { + uint8_t *decrypted = xmalloc(len); + size_t decrypted_len = len; + chacha_poly1305_ctx_t *ctx = chacha_poly1305_init(); + chacha_poly1305_set_key(ctx, key); + + if(len > 12 && chacha_poly1305_decrypt_iv96(ctx, buf, buf + 12, len - 12, decrypted, &decrypted_len)) { + chacha_poly1305_exit(ctx); + free(buf); + config->buf = decrypted; + config->len = decrypted_len; + return true; + } else { + logger(mesh, MESHLINK_ERROR, "Cannot decrypt config file\n"); + meshlink_errno = MESHLINK_ESTORAGE; + chacha_poly1305_exit(ctx); + free(decrypted); + free(buf); + return false; + } + } + + config->buf = buf; + config->len = len; + + return true; +} + +/// Write a configuration file to a FILE handle. +bool config_write_file(meshlink_handle_t *mesh, FILE *f, const config_t *config, const void *key) { + assert(f); + + if(key) { + uint8_t buf[config->len + 16]; + size_t len = sizeof(buf); + uint8_t seqbuf[12]; + randomize(&seqbuf, sizeof(seqbuf)); + chacha_poly1305_ctx_t *ctx = chacha_poly1305_init(); + chacha_poly1305_set_key(ctx, key); + bool success = false; + + if(chacha_poly1305_encrypt_iv96(ctx, seqbuf, config->buf, config->len, buf, &len)) { + success = fwrite(seqbuf, sizeof(seqbuf), 1, f) == 1 && fwrite(buf, len, 1, f) == 1; + + if(!success) { + logger(mesh, MESHLINK_ERROR, "Cannot write config file: %s", strerror(errno)); + } + + meshlink_errno = MESHLINK_ESTORAGE; + } else { + logger(mesh, MESHLINK_ERROR, "Cannot encrypt config file\n"); + meshlink_errno = MESHLINK_ESTORAGE; + } + + chacha_poly1305_exit(ctx); + return success; + } + + if(fwrite(config->buf, config->len, 1, f) != 1) { + logger(mesh, MESHLINK_ERROR, "Cannot write config file: %s", strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + if(fflush(f)) { + logger(mesh, MESHLINK_ERROR, "Failed to flush file: %s", strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + if(fsync(fileno(f))) { + logger(mesh, MESHLINK_ERROR, "Failed to sync file: %s\n", strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + return true; +} + +/// Free resources of a loaded configuration file. +void config_free(config_t *config) { + assert(!config->len || config->buf); + + free((uint8_t *)config->buf); + config->buf = NULL; + config->len = 0; +} + +/// Check the presence of a host configuration file. +bool config_exists(meshlink_handle_t *mesh, const char *conf_subdir, const char *name) { + assert(conf_subdir); + + if(!mesh->confbase) { + return false; + } + + char path[PATH_MAX]; + make_host_path(mesh, conf_subdir, name, path, sizeof(path)); + + return access(path, F_OK) == 0; +} + +/// Read a host configuration file. +bool config_read(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, config_t *config, void *key) { + assert(conf_subdir); + + if(!mesh->confbase) { + return false; + } + + char path[PATH_MAX]; + make_host_path(mesh, conf_subdir, name, path, sizeof(path)); + + FILE *f = fopen(path, "r"); + + if(!f) { + logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno)); + return false; + } + + if(!config_read_file(mesh, f, config, key)) { + logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno)); + fclose(f); + return false; + } + + fclose(f); + + return true; +} + +bool config_scan_all(meshlink_handle_t *mesh, const char *conf_subdir, const char *conf_type, config_scan_action_t action, void *arg) { + assert(conf_subdir); + assert(conf_type); + + if(!mesh->confbase) { + return true; + } + + DIR *dir; + struct dirent *ent; + char dname[PATH_MAX]; + snprintf(dname, sizeof(dname), "%s" SLASH "%s" SLASH "%s", mesh->confbase, conf_subdir, conf_type); + + dir = opendir(dname); + + if(!dir) { + logger(mesh, MESHLINK_ERROR, "Could not open %s: %s", dname, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + while((ent = readdir(dir))) { + if(ent->d_name[0] == '.') { + continue; + } + + if(!action(mesh, ent->d_name, arg)) { + closedir(dir); + return false; + } + } + + closedir(dir); + return true; +} + +/// Write a host configuration file. +bool config_write(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, const config_t *config, void *key) { + assert(conf_subdir); + assert(name); + assert(config); + + if(!mesh->confbase) { + return true; + } + + char path[PATH_MAX]; + char tmp_path[PATH_MAX + 4]; + make_host_path(mesh, conf_subdir, name, path, sizeof(path)); + snprintf(tmp_path, sizeof(tmp_path), "%s.tmp", path); + + FILE *f = fopen(tmp_path, "w"); + + if(!f) { + logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", tmp_path, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + if(!config_write_file(mesh, f, config, key)) { + logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", tmp_path, strerror(errno)); + fclose(f); + return false; + } + + if(fclose(f)) { + logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", tmp_path, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + if(rename(tmp_path, path)) { + logger(mesh, MESHLINK_ERROR, "Failed to rename `%s' to `%s': %s", tmp_path, path, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + return true; +} + +/// Delete a host configuration file. +bool config_delete(meshlink_handle_t *mesh, const char *conf_subdir, const char *name) { + assert(conf_subdir); + assert(name); + + if(!mesh->confbase) { + return true; + } + + char path[PATH_MAX]; + make_host_path(mesh, conf_subdir, name, path, sizeof(path)); + + if(unlink(path) && errno != ENOENT) { + logger(mesh, MESHLINK_ERROR, "Failed to unlink `%s': %s", path, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + return true; +} + +/// Read the main configuration file. +bool main_config_read(meshlink_handle_t *mesh, const char *conf_subdir, config_t *config, void *key) { + assert(conf_subdir); + assert(config); + + if(!mesh->confbase) { + return false; + } + + char path[PATH_MAX]; + make_main_path(mesh, conf_subdir, path, sizeof(path)); + + FILE *f = fopen(path, "r"); + + if(!f) { + logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno)); + return false; + } + + if(!config_read_file(mesh, f, config, key)) { + logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno)); + fclose(f); + return false; + } + + fclose(f); + + return true; +} + +/// Write the main configuration file. +bool main_config_write(meshlink_handle_t *mesh, const char *conf_subdir, const config_t *config, void *key) { + assert(conf_subdir); + assert(config); + + if(!mesh->confbase) { + return true; + } + + char path[PATH_MAX]; + char tmp_path[PATH_MAX + 4]; + make_main_path(mesh, conf_subdir, path, sizeof(path)); + snprintf(tmp_path, sizeof(tmp_path), "%s.tmp", path); + + FILE *f = fopen(tmp_path, "w"); + + if(!f) { + logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", tmp_path, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + if(!config_write_file(mesh, f, config, key)) { + logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", tmp_path, strerror(errno)); + fclose(f); + return false; + } + + if(rename(tmp_path, path)) { + logger(mesh, MESHLINK_ERROR, "Failed to rename `%s' to `%s': %s", tmp_path, path, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + fclose(f); + return false; + } + + if(fclose(f)) { + logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", tmp_path, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + return true; +} + +/// Read an invitation file from the confbase sub-directory, and immediately delete it. +bool invitation_read(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, config_t *config, void *key) { + assert(conf_subdir); + assert(name); + assert(config); + + if(!mesh->confbase) { + return false; + } + + char path[PATH_MAX]; + char used_path[PATH_MAX]; + make_invitation_path(mesh, conf_subdir, name, path, sizeof(path)); + make_used_invitation_path(mesh, conf_subdir, name, used_path, sizeof(used_path)); + + // Atomically rename the invitation file + if(rename(path, used_path)) { + if(errno == ENOENT) { + logger(mesh, MESHLINK_ERROR, "Peer tried to use non-existing invitation %s\n", name); + } else { + logger(mesh, MESHLINK_ERROR, "Error trying to rename invitation %s\n", name); + } + + return false; + } + + FILE *f = fopen(used_path, "r"); + + if(!f) { + logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno)); + return false; + } + + // Check the timestamp + struct stat st; + + if(fstat(fileno(f), &st)) { + logger(mesh, MESHLINK_ERROR, "Could not stat invitation file %s\n", name); + fclose(f); + unlink(used_path); + return false; + } + + if(time(NULL) >= st.st_mtime + mesh->invitation_timeout) { + logger(mesh, MESHLINK_ERROR, "Peer tried to use an outdated invitation file %s\n", name); + fclose(f); + unlink(used_path); + return false; + } + + if(!config_read_file(mesh, f, config, key)) { + logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno)); + fclose(f); + unlink(used_path); + return false; + } + + fclose(f); + + if(unlink(used_path)) { + logger(mesh, MESHLINK_ERROR, "Failed to unlink `%s': %s", path, strerror(errno)); + return false; + } + + snprintf(path, sizeof(path), "%s" SLASH "%s" SLASH "invitations", mesh->confbase, conf_subdir); + + if(!sync_path(path)) { + logger(mesh, MESHLINK_ERROR, "Failed to sync `%s': %s", path, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + return true; +} + +/// Write an invitation file. +bool invitation_write(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, const config_t *config, void *key) { + assert(conf_subdir); + assert(name); + assert(config); + + if(!mesh->confbase) { + return false; + } + + char path[PATH_MAX]; + make_invitation_path(mesh, conf_subdir, name, path, sizeof(path)); + + FILE *f = fopen(path, "w"); + + if(!f) { + logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + if(!config_write_file(mesh, f, config, key)) { + logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", path, strerror(errno)); + fclose(f); + return false; + } + + if(fclose(f)) { + logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", path, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + snprintf(path, sizeof(path), "%s" SLASH "%s" SLASH "invitations", mesh->confbase, conf_subdir); + + if(!sync_path(path)) { + logger(mesh, MESHLINK_ERROR, "Failed to sync `%s': %s", path, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + return true; +} + +/// Purge old invitation files +size_t invitation_purge_old(meshlink_handle_t *mesh, time_t deadline) { + if(!mesh->confbase) { + return true; + } + + char path[PATH_MAX]; + make_invitation_path(mesh, "current", "", path, sizeof(path)); + + DIR *dir = opendir(path); + + if(!dir) { + logger(mesh, MESHLINK_DEBUG, "Could not read directory %s: %s\n", path, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return 0; + } + + errno = 0; + size_t count = 0; + struct dirent *ent; + + while((ent = readdir(dir))) { + if(strlen(ent->d_name) != 24) { + continue; + } + + char invname[PATH_MAX]; + struct stat st; + + if(snprintf(invname, sizeof(invname), "%s" SLASH "%s", path, ent->d_name) >= PATH_MAX) { + logger(mesh, MESHLINK_DEBUG, "Filename too long: %s" SLASH "%s", path, ent->d_name); + continue; + } + + if(!stat(invname, &st)) { + if(mesh->invitation_key && deadline < st.st_mtime) { + count++; + } else { + unlink(invname); + } + } else { + logger(mesh, MESHLINK_DEBUG, "Could not stat %s: %s\n", invname, strerror(errno)); + errno = 0; + } + } + + if(errno) { + logger(mesh, MESHLINK_DEBUG, "Error while reading directory %s: %s\n", path, strerror(errno)); + closedir(dir); + meshlink_errno = MESHLINK_ESTORAGE; + return 0; + } + + closedir(dir); + + return count; +} + +/// Purge invitations for the given node +size_t invitation_purge_node(meshlink_handle_t *mesh, const char *node_name) { + if(!mesh->confbase) { + return true; + } + + char path[PATH_MAX]; + make_invitation_path(mesh, "current", "", path, sizeof(path)); + + DIR *dir = opendir(path); + + if(!dir) { + logger(mesh, MESHLINK_DEBUG, "Could not read directory %s: %s\n", path, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return 0; + } + + errno = 0; + size_t count = 0; + struct dirent *ent; + + while((ent = readdir(dir))) { + if(strlen(ent->d_name) != 24) { + continue; + } + + char invname[PATH_MAX]; + + if(snprintf(invname, sizeof(invname), "%s" SLASH "%s", path, ent->d_name) >= PATH_MAX) { + logger(mesh, MESHLINK_DEBUG, "Filename too long: %s" SLASH "%s", path, ent->d_name); + continue; + } + + FILE *f = fopen(invname, "r"); + + if(!f) { + errno = 0; + continue; + } + + config_t config; + + if(!config_read_file(mesh, f, &config, mesh->config_key)) { + logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", invname, strerror(errno)); + config_free(&config); + fclose(f); + errno = 0; + continue; + } + + packmsg_input_t in = {config.buf, config.len}; + packmsg_get_uint32(&in); // skip version + char *name = packmsg_get_str_dup(&in); + + if(name && !strcmp(name, node_name)) { + logger(mesh, MESHLINK_DEBUG, "Removing invitation for %s", node_name); + unlink(invname); + } + + free(name); + config_free(&config); + fclose(f); + } + + if(errno) { + logger(mesh, MESHLINK_DEBUG, "Error while reading directory %s: %s\n", path, strerror(errno)); + closedir(dir); + meshlink_errno = MESHLINK_ESTORAGE; + return 0; + } + + closedir(dir); + + return count; +} diff --git a/src/conf.h b/src/conf.h new file mode 100644 index 0000000..656a0ac --- /dev/null +++ b/src/conf.h @@ -0,0 +1,62 @@ +#ifndef MESHLINK_CONF_H +#define MESHLINK_CONF_H + +/* + econf.h -- header for econf.c + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +struct meshlink_handle; + +typedef struct config_t { + const uint8_t *buf; + size_t len; +} config_t; + +typedef bool (*config_scan_action_t)(struct meshlink_handle *mesh, const char *name, void *arg); + +bool config_read_file(struct meshlink_handle *mesh, FILE *f, struct config_t *, const void *key) __attribute__((__warn_unused_result__)); +bool config_write_file(struct meshlink_handle *mesh, FILE *f, const struct config_t *, const void *key) __attribute__((__warn_unused_result__)); +void config_free(struct config_t *config); + +bool meshlink_confbase_exists(struct meshlink_handle *mesh) __attribute__((__warn_unused_result__)); + +bool config_init(struct meshlink_handle *mesh, const char *conf_subdir) __attribute__((__warn_unused_result__)); +bool config_destroy(const char *confbase, const char *conf_subdir) __attribute__((__warn_unused_result__)); +bool config_copy(struct meshlink_handle *mesh, const char *src_dir_name, const void *src_key, const char *dst_dir_name, const void *dst_key) __attribute__((__warn_unused_result__)); +bool config_rename(struct meshlink_handle *mesh, const char *old_conf_subdir, const char *new_conf_subdir) __attribute__((__warn_unused_result__)); +bool config_sync(struct meshlink_handle *mesh, const char *conf_subdir) __attribute__((__warn_unused_result__)); +bool sync_path(const char *path) __attribute__((__warn_unused_result__)); + +bool main_config_exists(struct meshlink_handle *mesh, const char *conf_subdir) __attribute__((__warn_unused_result__)); +bool main_config_lock(struct meshlink_handle *mesh, const char *lock_filename) __attribute__((__warn_unused_result__)); +void main_config_unlock(struct meshlink_handle *mesh); +bool main_config_read(struct meshlink_handle *mesh, const char *conf_subdir, struct config_t *, void *key) __attribute__((__warn_unused_result__)); +bool main_config_write(struct meshlink_handle *mesh, const char *conf_subdir, const struct config_t *, void *key) __attribute__((__warn_unused_result__)); + +bool config_exists(struct meshlink_handle *mesh, const char *conf_subdir, const char *name) __attribute__((__warn_unused_result__)); +bool config_read(struct meshlink_handle *mesh, const char *conf_subdir, const char *name, struct config_t *, void *key) __attribute__((__warn_unused_result__)); +bool config_write(struct meshlink_handle *mesh, const char *conf_subdir, const char *name, const struct config_t *, void *key) __attribute__((__warn_unused_result__)); +bool config_delete(struct meshlink_handle *mesh, const char *conf_subdir, const char *name) __attribute__((__warn_unused_result__)); +bool config_scan_all(struct meshlink_handle *mesh, const char *conf_subdir, const char *conf_type, config_scan_action_t action, void *arg) __attribute__((__warn_unused_result__)); + +bool invitation_read(struct meshlink_handle *mesh, const char *conf_subdir, const char *name, struct config_t *, void *key) __attribute__((__warn_unused_result__)); +bool invitation_write(struct meshlink_handle *mesh, const char *conf_subdir, const char *name, const struct config_t *, void *key) __attribute__((__warn_unused_result__)); +size_t invitation_purge_old(struct meshlink_handle *mesh, time_t deadline); +size_t invitation_purge_node(struct meshlink_handle *mesh, const char *node_name); + +#endif diff --git a/src/connection.c b/src/connection.c new file mode 100644 index 0000000..9cc6491 --- /dev/null +++ b/src/connection.c @@ -0,0 +1,91 @@ +/* + connection.c -- connection list management + Copyright (C) 2000-2013 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "list.h" +#include "conf.h" +#include "connection.h" +#include "list.h" +#include "logger.h" +#include "meshlink_internal.h" +#include "utils.h" +#include "xalloc.h" + +void init_connections(meshlink_handle_t *mesh) { + assert(!mesh->connections); + assert(!mesh->everyone); + + mesh->connections = list_alloc((list_action_t) free_connection); + mesh->everyone = new_connection(); + mesh->everyone->name = xstrdup("mesh->everyone"); +} + +void exit_connections(meshlink_handle_t *mesh) { + if(mesh->connections) { + list_delete_list(mesh->connections); + } + + if(mesh->everyone) { + free_connection(mesh->everyone); + } + + mesh->connections = NULL; + mesh->everyone = NULL; +} + +connection_t *new_connection(void) { + return xzalloc(sizeof(connection_t)); +} + +void free_connection(connection_t *c) { + assert(c); + + sptps_stop(&c->sptps); + ecdsa_free(c->ecdsa); + + buffer_clear(&c->inbuf); + buffer_clear(&c->outbuf); + + if(c->io.cb) { + abort(); + } + + if(c->socket > 0) { + closesocket(c->socket); + } + + free(c->name); + + free(c); +} + +void connection_add(meshlink_handle_t *mesh, connection_t *c) { + assert(c); + + c->mesh = mesh; + list_insert_tail(mesh->connections, c); +} + +void connection_del(meshlink_handle_t *mesh, connection_t *c) { + assert(c); + + io_del(&mesh->loop, &c->io); + list_delete(mesh->connections, c); +} diff --git a/src/connection.h b/src/connection.h new file mode 100644 index 0000000..b5ccaed --- /dev/null +++ b/src/connection.h @@ -0,0 +1,90 @@ +#ifndef MESHLINK_CONNECTION_H +#define MESHLINK_CONNECTION_H + +/* + connection.h -- header for connection.c + Copyright (C) 2000-2013, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "buffer.h" +#include "list.h" +#include "sptps.h" + +#define OPTION_INDIRECT 0x0001 +#define OPTION_TCPONLY 0x0002 +#define OPTION_PMTU_DISCOVERY 0x0004 +#define OPTION_CLAMP_MSS 0x0008 +#define OPTION_VERSION(x) ((x) >> 24) /* Top 8 bits are for protocol minor version */ + +typedef struct connection_status_t { + uint16_t pinged: 1; /* sent ping */ + uint16_t active: 1; /* 1 if active.. */ + uint16_t connecting: 1; /* 1 if we are waiting for a non-blocking connect() to finish */ + uint16_t unused: 1; + uint16_t control: 1; /* 1 if this is a control connection */ + uint16_t pcap: 1; /* 1 if this is a control connection requesting packet capture */ + uint16_t log: 1; /* 1 if this is a control connection requesting log dump */ + uint16_t invitation: 1; /* 1 if this is an invitation */ + uint16_t invitation_used: 1; /* 1 if the invitation has been consumed */ + uint16_t initiator: 1; /* 1 if we initiated this connection */ +} connection_status_t; + +#include "ecdsa.h" +#include "edge.h" +#include "net.h" +#include "node.h" +#include "submesh.h" + +typedef struct connection_t { + char *name; /* name he claims to have */ + struct node_t *node; /* node associated with the other end */ + + connection_status_t status; /* status info */ + int socket; /* socket used for this connection */ + union sockaddr_t address; /* his real (internet) ip */ + + struct meshlink_handle *mesh; /* the mesh this connection belongs to */ + + // I/O + sptps_t sptps; + struct buffer_t inbuf; + struct buffer_t outbuf; + io_t io; /* input/output event on this metadata connection */ + int tcplen; /* length of incoming TCPpacket */ + int allow_request; /* defined if there's only one request possible */ + time_t last_ping_time; /* last time we saw some activity from the other end or pinged them */ + time_t last_key_renewal; /* last time we renewed the SPTPS key */ + + struct outgoing_t *outgoing; /* used to keep track of outgoing connections */ + + struct edge_t *edge; /* edge associated with this connection */ + struct submesh_t *submesh; /* his submesh handle if available in invitation file */ + + // Only used during authentication + ecdsa_t *ecdsa; /* his public ECDSA key */ + int protocol_major; /* used protocol */ + int protocol_minor; /* used protocol */ +} connection_t; + +void init_connections(struct meshlink_handle *mesh); +void exit_connections(struct meshlink_handle *mesh); +connection_t *new_connection(void) __attribute__((__malloc__)); +void free_connection(connection_t *); +void connection_add(struct meshlink_handle *mesh, connection_t *); +void connection_del(struct meshlink_handle *mesh, connection_t *); + +#endif diff --git a/src/crypto.c b/src/crypto.c new file mode 100644 index 0000000..3244a0b --- /dev/null +++ b/src/crypto.c @@ -0,0 +1,99 @@ +/* + crypto.c -- Cryptographic miscellaneous functions and initialisation + Copyright (C) 2014 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "crypto.h" + +//TODO: use a strict random source once to seed a PRNG? + +#ifndef HAVE_MINGW + +static int random_fd = -1; + +void crypto_init(void) { + assert(random_fd == -1); + + random_fd = open("/dev/urandom", O_RDONLY); + + if(random_fd < 0) { + random_fd = open("/dev/random", O_RDONLY); + } + + if(random_fd < 0) { + fprintf(stderr, "Could not open source of random numbers: %s\n", strerror(errno)); + abort(); + } +} + +void crypto_exit(void) { + assert(random_fd != -1); + + close(random_fd); + random_fd = -1; +} + +void randomize(void *out, size_t outlen) { + assert(outlen); + + char *ptr = out; + + while(outlen) { + size_t len = read(random_fd, ptr, outlen); + + if(len <= 0) { + if(errno == EAGAIN || errno == EINTR) { + continue; + } + + fprintf(stderr, "Could not read random numbers: %s\n", strerror(errno)); + abort(); + } + + ptr += len; + outlen -= len; + } +} + +#else + +#include +HCRYPTPROV prov; + +void crypto_init(void) { + if(!CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + fprintf(stderr, "CryptAcquireContext() failed!\n"); + abort(); + } +} + +void crypto_exit(void) { + CryptReleaseContext(prov, 0); +} + +void randomize(void *out, size_t outlen) { + assert(outlen); + + if(!CryptGenRandom(prov, outlen, out)) { + fprintf(stderr, "CryptGenRandom() failed\n"); + abort(); + } +} + +#endif diff --git a/src/crypto.h b/src/crypto.h new file mode 100644 index 0000000..9e8a19b --- /dev/null +++ b/src/crypto.h @@ -0,0 +1,27 @@ +#ifndef MESHLINK_CRYPTO_H +#define MESHLINK_CRYPTO_H + +/* + crypto.h -- header for crypto.c + Copyright (C) 2014-2020 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +void crypto_init(void); +void crypto_exit(void); +void randomize(void *buf, size_t len); + +#endif diff --git a/src/devtools.c b/src/devtools.c new file mode 100644 index 0000000..2a98e59 --- /dev/null +++ b/src/devtools.c @@ -0,0 +1,383 @@ +/* + devtools.c -- Debugging and quality control functions. + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" +#include + +#include "logger.h" +#include "meshlink_internal.h" +#include "node.h" +#include "submesh.h" +#include "splay_tree.h" +#include "netutl.h" +#include "xalloc.h" + +#include "devtools.h" + +static void nop_probe(void) { + return; +} + +static void keyrotate_nop_probe(int stage) { + (void)stage; + return; +} + +static void inviter_commits_first_nop_probe(bool stage) { + (void)stage; + return; +} + +static void sptps_renewal_nop_probe(meshlink_node_t *node) { + (void)node; + return; +} + +void (*devtool_trybind_probe)(void) = nop_probe; +void (*devtool_keyrotate_probe)(int stage) = keyrotate_nop_probe; +void (*devtool_set_inviter_commits_first)(bool inviter_commited_first) = inviter_commits_first_nop_probe; +void (*devtool_adns_resolve_probe)(void) = nop_probe; +void (*devtool_sptps_renewal_probe)(meshlink_node_t *node) = sptps_renewal_nop_probe; + +/* Return an array of edges in the current network graph. + * Data captures the current state and will not be updated. + * Caller must deallocate data when done. + */ +devtool_edge_t *devtool_get_all_edges(meshlink_handle_t *mesh, devtool_edge_t *edges, size_t *nmemb) { + if(!mesh || !nmemb || (*nmemb && !edges)) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + devtool_edge_t *result = NULL; + unsigned int result_size = 0; + + result_size = mesh->edges->count / 2; + + // if result is smaller than edges, we have to dealloc all the excess devtool_edge_t + if((size_t)result_size > *nmemb) { + result = xrealloc(edges, result_size * sizeof(*result)); + } else { + result = edges; + } + + if(result) { + devtool_edge_t *p = result; + unsigned int n = 0; + + for splay_each(edge_t, e, mesh->edges) { + // skip edges that do not represent a two-directional connection + if(!e->reverse || e->reverse->to != e->from) { + continue; + } + + // don't count edges twice + if(e->to < e->from) { + continue; + } + + assert(n < result_size); + + p->from = (meshlink_node_t *)e->from; + p->to = (meshlink_node_t *)e->to; + p->address = e->address.storage; + p->weight = e->weight; + + n++; + p++; + } + + // shrink result to the actual amount of memory used + result = xrealloc(result, n * sizeof(*result)); + *nmemb = n; + } else { + *nmemb = 0; + meshlink_errno = MESHLINK_ENOMEM; + } + + pthread_mutex_unlock(&mesh->mutex); + + return result; +} + +static bool fstrwrite(const char *str, FILE *stream) { + assert(stream); + + size_t len = strlen(str); + + if(fwrite((void *)str, 1, len, stream) != len) { + return false; + } + + return true; +} + +bool devtool_export_json_all_edges_state(meshlink_handle_t *mesh, FILE *stream) { + assert(stream); + + bool result = true; + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + // export edges and nodes + size_t node_count = 0; + size_t edge_count = 0; + + meshlink_node_t **nodes = meshlink_get_all_nodes(mesh, NULL, &node_count); + devtool_edge_t *edges = devtool_get_all_edges(mesh, NULL, &edge_count); + + if((!nodes && node_count != 0) || (!edges && edge_count != 0)) { + goto fail; + } + + // export begin + if(!fstrwrite("{\n", stream)) { + goto fail; + } + + // export nodes + if(!fstrwrite("\t\"nodes\": {\n", stream)) { + goto fail; + } + + char buf[16]; + + for(size_t i = 0; i < node_count; ++i) { + if(!fstrwrite("\t\t\"", stream) || !fstrwrite(((node_t *)nodes[i])->name, stream) || !fstrwrite("\": {\n", stream)) { + goto fail; + } + + if(!fstrwrite("\t\t\t\"name\": \"", stream) || !fstrwrite(((node_t *)nodes[i])->name, stream) || !fstrwrite("\",\n", stream)) { + goto fail; + } + + snprintf(buf, sizeof(buf), "%d", ((node_t *)nodes[i])->devclass); + + if(!fstrwrite("\t\t\t\"devclass\": ", stream) || !fstrwrite(buf, stream) || !fstrwrite("\n", stream)) { + goto fail; + } + + if(!fstrwrite((i + 1) != node_count ? "\t\t},\n" : "\t\t}\n", stream)) { + goto fail; + } + } + + if(!fstrwrite("\t},\n", stream)) { + goto fail; + } + + // export edges + + if(!fstrwrite("\t\"edges\": {\n", stream)) { + goto fail; + } + + for(size_t i = 0; i < edge_count; ++i) { + if(!fstrwrite("\t\t\"", stream) || !fstrwrite(edges[i].from->name, stream) || !fstrwrite("_to_", stream) || !fstrwrite(edges[i].to->name, stream) || !fstrwrite("\": {\n", stream)) { + goto fail; + } + + if(!fstrwrite("\t\t\t\"from\": \"", stream) || !fstrwrite(edges[i].from->name, stream) || !fstrwrite("\",\n", stream)) { + goto fail; + } + + if(!fstrwrite("\t\t\t\"to\": \"", stream) || !fstrwrite(edges[i].to->name, stream) || !fstrwrite("\",\n", stream)) { + goto fail; + } + + char *host = NULL, *port = NULL, *address = NULL; + sockaddr2str((const sockaddr_t *)&edges[i].address, &host, &port); + + if(host && port) { + xasprintf(&address, "{ \"host\": \"%s\", \"port\": %s }", host, port); + } + + free(host); + free(port); + + if(!fstrwrite("\t\t\t\"address\": ", stream) || !fstrwrite(address ? address : "null", stream) || !fstrwrite(",\n", stream)) { + free(address); + goto fail; + } + + free(address); + + snprintf(buf, sizeof(buf), "%d", edges[i].weight); + + if(!fstrwrite("\t\t\t\"weight\": ", stream) || !fstrwrite(buf, stream) || !fstrwrite("\n", stream)) { + goto fail; + } + + if(!fstrwrite((i + 1) != edge_count ? "\t\t},\n" : "\t\t}\n", stream)) { + goto fail; + } + } + + if(!fstrwrite("\t}\n", stream)) { + goto fail; + } + + // DONE! + + if(!fstrwrite("}", stream)) { + goto fail; + } + + goto done; + +fail: + result = false; + +done: + free(nodes); + free(edges); + + pthread_mutex_unlock(&mesh->mutex); + + return result; +} + +void devtool_get_node_status(meshlink_handle_t *mesh, meshlink_node_t *node, devtool_node_status_t *status) { + if(!mesh || !node || !status) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + node_t *internal = (node_t *)node; + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + memcpy(&status->status, &internal->status, sizeof status->status); + memcpy(&status->address, &internal->address, sizeof status->address); + status->mtu = internal->mtu; + status->minmtu = internal->minmtu; + status->maxmtu = internal->maxmtu; + status->mtuprobes = internal->mtuprobes; + status->in_packets = internal->in_packets; + status->in_bytes = internal->in_bytes; + status->out_packets = internal->out_packets; + status->out_bytes = internal->out_bytes; + + // Derive UDP connection status + if(internal == mesh->self) { + status->udp_status = DEVTOOL_UDP_WORKING; + } else if(!internal->status.reachable) { + status->udp_status = DEVTOOL_UDP_IMPOSSIBLE; + } else if(!internal->status.validkey) { + status->udp_status = DEVTOOL_UDP_UNKNOWN; + } else if(internal->status.udp_confirmed) { + status->udp_status = DEVTOOL_UDP_WORKING; + } else if(internal->mtuprobes > 30) { + status->udp_status = DEVTOOL_UDP_FAILED; + } else if(internal->mtuprobes > 0) { + status->udp_status = DEVTOOL_UDP_TRYING; + } else { + status->udp_status = DEVTOOL_UDP_UNKNOWN; + } + + pthread_mutex_unlock(&mesh->mutex); +} + +meshlink_submesh_t **devtool_get_all_submeshes(meshlink_handle_t *mesh, meshlink_submesh_t **submeshes, size_t *nmemb) { + if(!mesh || !nmemb || (*nmemb && !submeshes)) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + meshlink_submesh_t **result; + + //lock mesh->nodes + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + *nmemb = mesh->submeshes->count; + result = realloc(submeshes, *nmemb * sizeof(*submeshes)); + + if(result) { + meshlink_submesh_t **p = result; + + for list_each(submesh_t, s, mesh->submeshes) { + *p++ = (meshlink_submesh_t *)s; + } + } else { + *nmemb = 0; + free(submeshes); + meshlink_errno = MESHLINK_ENOMEM; + } + + pthread_mutex_unlock(&mesh->mutex); + + return result; +} + +meshlink_handle_t *devtool_open_in_netns(const char *confbase, const char *name, const char *appname, dev_class_t devclass, int netns) { + meshlink_open_params_t *params = meshlink_open_params_init(confbase, name, appname, devclass); + params->netns = dup(netns); + meshlink_handle_t *handle; + + if(params->netns == -1) { + handle = NULL; + meshlink_errno = MESHLINK_EINVAL; + } else { + handle = meshlink_open_ex(params); + } + + meshlink_open_params_free(params); + + return handle; +} + +void devtool_force_sptps_renewal(meshlink_handle_t *mesh, meshlink_node_t *node) { + if(!mesh || !node) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + node_t *n = (node_t *)node; + connection_t *c = n->connection; + + n->last_req_key = -3600; + + if(c) { + c->last_key_renewal = -3600; + } +} + +void devtool_set_meta_status_cb(meshlink_handle_t *mesh, meshlink_node_status_cb_t cb) { + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + mesh->meta_status_cb = cb; + pthread_mutex_unlock(&mesh->mutex); +} diff --git a/src/devtools.h b/src/devtools.h new file mode 100644 index 0000000..786e290 --- /dev/null +++ b/src/devtools.h @@ -0,0 +1,212 @@ +#ifndef MESHLINK_DEVTOOLS_H +#define MESHLINK_DEVTOOLS_H + +/* + devtools.h -- header for devtools.h + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +/// \file devtools.h +/** This header files declares functions that are only intended for debugging and quality control. + * They are not necessary for the normal operation of MeshLink. + * Applications should not depend on any of these functions for their normal operation. + */ + +/// An edge in the MeshLink network. +typedef struct devtool_edge devtool_edge_t; + +/// An edge in the MeshLink network. +struct devtool_edge { + struct meshlink_node *from; ///< Pointer to a node. Node memory is + // owned by meshlink and should not be + // deallocated. Node contents may be + // changed by meshlink. + struct meshlink_node *to; ///< Pointer to a node. Node memory is + // owned by meshlink and should not be + // deallocated. Node contents may be + // changed by meshlink. + struct sockaddr_storage address;///< The address information associated + // with this edge. + int weight; ///< Weight assigned to this edge. +}; + +/// Get a list of edges. +/** This function returns an array with copies of all known bidirectional edges. + * The edges are copied to capture the mesh state at call time, since edges + * mutate frequently. The nodes pointed to within the devtool_edge_t type + * are not copies; these are the same pointers that one would get from a call + * to meshlink_get_all_nodes(). + * + * @param mesh A handle which represents an instance of MeshLink. + * @param edges A pointer to a previously allocated array of + * devtool_edge_t, or NULL in which case MeshLink will + * allocate a new array. + * The application is allowed to call free() on the array whenever it wishes. + * The pointers in the devtool_edge_t elements are valid until + * meshlink_close() is called. + * @param nmemb A pointer to a variable holding the number of elements that + * are stored in the array. In case the @a edges @a + * argument is not NULL, MeshLink might call realloc() + * on the array to change its size. + * The contents of this variable will be changed to reflect + * the new size of the array. + * @return A pointer to an array containing devtool_edge_t elements, + * or NULL in case of an error. + * If the @a edges @a argument was not NULL, then the + * return value can be either the same value or a different + * value. If the new values is NULL, then the old array + * will have been freed by Meshlink. + */ +devtool_edge_t *devtool_get_all_edges(meshlink_handle_t *mesh, devtool_edge_t *edges, size_t *nmemb); + +/// Export a list of edges to a file in JSON format. +/* @param mesh A handle which represents an instance of MeshLink. + * @param FILE An open file descriptor to which a JSON representation of the edges will be written. + * + * @return True in case of success, false otherwise. + */ +bool devtool_export_json_all_edges_state(meshlink_handle_t *mesh, FILE *stream); + +/// The status of a node. +typedef struct devtool_node_status devtool_node_status_t; + +/// The status of a node. +struct devtool_node_status { + uint32_t status; + struct sockaddr_storage address; + uint16_t mtu; + uint16_t minmtu; + uint16_t maxmtu; + int mtuprobes; + enum { + DEVTOOL_UDP_FAILED = -2, /// UDP tried but failed + DEVTOOL_UDP_IMPOSSIBLE = -1, /// UDP not possible (node unreachable) + DEVTOOL_UDP_UNKNOWN = 0, /// UDP status not known (never tried to communicate with the node) + DEVTOOL_UDP_TRYING, /// UDP detection in progress + DEVTOOL_UDP_WORKING, /// UDP communication established + } udp_status; + uint64_t in_packets; + uint64_t in_bytes; + uint64_t out_packets; + uint64_t out_bytes; +}; + +/// Get the status of a node. +/** This function returns a struct containing extra information about a node. + * The information is a snapshot taken at call time. + * + * @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a meshlink_node_t. + * @param status A pointer to a devtools_node_status_t variable that has + * to be provided by the caller. + * The contents of this variable will be changed to reflect + * the current status of the node. + */ +void devtool_get_node_status(meshlink_handle_t *mesh, meshlink_node_t *node, devtool_node_status_t *status); + +/// Get the list of all submeshes of a meshlink instance. +/** This function returns an array of submesh handles. + * These pointers are the same pointers that are present in the submeshes list + * in mesh handle. + * + * @param mesh A handle which represents an instance of MeshLink. + * @param submeshes A pointer to an array of submesh handles if any allocated previously. + * @param nmemb A pointer to a size_t variable that has + * to be provided by the caller. + * The contents of this variable will be changed to indicate + * the number if array elements. + */ +meshlink_submesh_t **devtool_get_all_submeshes(meshlink_handle_t *mesh, meshlink_submesh_t **submeshes, size_t *nmemb); + +/// Open a MeshLink instance in a given network namespace. +/** This function opens MeshLink in the given network namespace. + * + * @param confbase The directory in which MeshLink will store its configuration files. + * After the function returns, the application is free to overwrite or free @a confbase @a. + * @param name The name which this instance of the application will use in the mesh. + * After the function returns, the application is free to overwrite or free @a name @a. + * @param appname The application name which will be used in the mesh. + * After the function returns, the application is free to overwrite or free @a name @a. + * @param devclass The device class which will be used in the mesh. + * @param netns A filedescriptor that represents the network namespace. + * + * @return A pointer to a meshlink_handle_t which represents this instance of MeshLink, or NULL in case of an error. + * The pointer is valid until meshlink_close() is called. + */ +meshlink_handle_t *devtool_open_in_netns(const char *confbase, const char *name, const char *appname, dev_class_t devclass, int netns); + +/// Debug function pointer variable for set port API +/** This function pointer variable is a userspace tracepoint or debugger callback for + * set port function @a meshlink_set_port @a. + * On assigning a debug function variable invokes callback when try_bind() succeeds in meshlink_set_port API. + * + */ +extern void (*devtool_trybind_probe)(void); + +/// Debug function pointer variable for encrypted key rotate API +/** This function pointer variable is a userspace tracepoint or debugger callback for + * encrypted key rotation function @a meshlink_encrypted_key_rotate @a. + * On assigning a debug function variable invokes callback for each stage from the key rotate API. + * + * @param stage Debug stage number. + */ +extern void (*devtool_keyrotate_probe)(int stage); + +/// Debug function pointer variable for asynchronous DNS resolving +extern void (*devtool_adns_resolve_probe)(void); + +/// Debug function pointer variable for SPTPS key renewal +/** This function pointer variable is a userspace tracepoint or debugger callback for + * SPTPS key renewal. + * + * @param node The node whose SPTPS key(s) are being renewed + */ +extern void (*devtool_sptps_renewal_probe)(meshlink_node_t *node); + +/// Force renewal of SPTPS sessions with the given node. +/** This causes the SPTPS sessions for both the UDP and TCP connections to renew their keys. + * + * @param mesh A handle which represents an instance of MeshLink. + * @param node The node whose SPTPS key(s) should be renewed + */ +void devtool_force_sptps_renewal(meshlink_handle_t *mesh, meshlink_node_t *node); + +/// Debug function pointer variable for asserting inviter/invitee committing sequence +/** This function pointer variable is a userspace tracepoint or debugger callback which + * invokes either after inviter writing invitees host file into the disk + * or after invitee writing it's main config file and host config files that inviter sent into + * the disk. + * + * @param inviter_commited_first true if inviter committed first else false if invitee committed first the other host file into the disk. + */ +extern void (*devtool_set_inviter_commits_first)(bool inviter_commited_first); + +/// Set the meta-connection status callback. +/** This functions sets the callback that is called whenever a meta-connection is made or closed. + * The callback is run in MeshLink's own thread. + * It is therefore important that the callback uses apprioriate methods (queues, pipes, locking, etc.) + * to hand the data over to the application's thread. + * The callback should also not block itself and return as quickly as possible. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param cb A pointer to the function which will be called when a node's meta-connection status changes. + * If a NULL pointer is given, the callback will be disabled. + */ +void devtool_set_meta_status_cb(struct meshlink_handle *mesh, meshlink_node_status_cb_t cb); + +#endif diff --git a/src/discovery.c b/src/discovery.c new file mode 100644 index 0000000..032b559 --- /dev/null +++ b/src/discovery.c @@ -0,0 +1,1063 @@ +/* + discovery.c -- local network discovery + Copyright (C) 2014-2021 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#define __APPLE_USE_RFC_3542 +#include "system.h" + +#if defined(__APPLE__) +#include +#include +#include +#include +#include +#include +#elif defined(__unix) && !defined(__linux) +#include +#include +#include +#elif defined(__linux) +#include +#include +#include +#include +#include +#endif + +#include "mdns.h" +#include "meshlink_internal.h" +#include "event.h" +#include "discovery.h" +#include "sockaddr.h" +#include "logger.h" +#include "netutl.h" +#include "node.h" +#include "connection.h" +#include "utils.h" +#include "xalloc.h" + +#define MESHLINK_MDNS_SERVICE_TYPE "_%s._tcp" +#define MESHLINK_MDNS_NAME_KEY "name" +#define MESHLINK_MDNS_FINGERPRINT_KEY "fingerprint" + +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + +#ifndef IPV6_ADD_MEMBERSHIP +#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP +#endif + +#ifndef IPV6_DROP_MEMBERSHIP +#define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP +#endif + +static const sockaddr_t mdns_address_ipv4 = { + .in.sin_family = AF_INET, + .in.sin_addr.s_addr = 0xfb0000e0, + .in.sin_port = 0xe914, +}; + +static const sockaddr_t mdns_address_ipv6 = { + .in6.sin6_family = AF_INET6, + .in6.sin6_addr.s6_addr[0x0] = 0xff, + .in6.sin6_addr.s6_addr[0x1] = 0x02, + .in6.sin6_addr.s6_addr[0xf] = 0xfb, + .in6.sin6_port = 0xe914, +}; + +typedef struct discovery_address { + int index; + bool up; + sockaddr_t address; + time_t last_response_sent; +} discovery_address_t; + +static int iface_compare(const void *va, const void *vb) { + const int *a = va; + const int *b = vb; + return *a - *b; +} + +static int address_compare(const void *va, const void *vb) { + const discovery_address_t *a = va; + const discovery_address_t *b = vb; + + if(a->index != b->index) { + return a->index - b->index; + } + + return sockaddrcmp_noport(&a->address, &b->address); +} + +static void send_mdns_packet_ipv4(meshlink_handle_t *mesh, int fd, int index, const sockaddr_t *src, const sockaddr_t *dest, void *data, size_t len) { +#ifdef IP_MULTICAST_IF + struct ip_mreqn mreq = { + .imr_address = src->in.sin_addr, + .imr_ifindex = index, + }; + + if(setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &mreq, sizeof(mreq)) != 0) { + logger(mesh, MESHLINK_ERROR, "Could not set outgoing multicast interface on IPv4 socket"); + return; + } + +#endif + +#ifdef IP_PKTINFO + struct iovec iov = { + .iov_base = data, + .iov_len = len, + }; + + struct in_pktinfo pkti = { + .ipi_ifindex = index, + .ipi_addr = src->in.sin_addr, + }; + + union { + char buf[CMSG_SPACE(sizeof(pkti))]; + struct cmsghdr align; + } u; + + memset(&u, 0, sizeof(u)); + + struct msghdr msg = { + .msg_name = (struct sockaddr *) &dest->sa, + .msg_namelen = SALEN(dest->sa), + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = u.buf, + .msg_controllen = sizeof(u.buf), + }; + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(pkti)); + memcpy(CMSG_DATA(cmsg), &pkti, sizeof(pkti)); + + // Send the packet + ssize_t result = sendmsg(fd, &msg, MSG_DONTWAIT | MSG_NOSIGNAL); +#else + (void)index; + (void)src; + + // Send the packet + ssize_t result = sendto(fd, data, len, MSG_DONTWAIT | MSG_NOSIGNAL, &dest->sa, SALEN(dest->sa)); +#endif + + if(result <= 0) { + logger(mesh, MESHLINK_ERROR, "Error sending multicast packet: %s", strerror(errno)); + } +} + +static void send_mdns_packet_ipv6(meshlink_handle_t *mesh, int fd, int index, const sockaddr_t *src, const sockaddr_t *dest, void *data, size_t len) { +#ifdef IPV6_MULTICAST_IF + + if(setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &index, sizeof(index)) != 0) { + logger(mesh, MESHLINK_ERROR, "Could not set outgoing multicast interface on IPv6 socket"); + return; + } + +#endif + +#ifdef IPV6_PKTINFO + struct iovec iov = { + .iov_base = data, + .iov_len = len, + }; + + struct in6_pktinfo pkti = { + .ipi6_ifindex = index, + .ipi6_addr = src->in6.sin6_addr, + }; + + union { + char buf[CMSG_SPACE(sizeof(pkti))]; + struct cmsghdr align; + } u; + + memset(&u, 0, sizeof u); + + struct msghdr msg = { + .msg_name = (struct sockaddr *) &dest->sa, + .msg_namelen = SALEN(dest->sa), + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = u.buf, + .msg_controllen = CMSG_LEN(sizeof(pkti)), + }; + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(pkti)); + memcpy(CMSG_DATA(cmsg), &pkti, sizeof(pkti)); + + // Send the packet + ssize_t result = sendmsg(fd, &msg, MSG_DONTWAIT | MSG_NOSIGNAL); +#else + (void)index; + (void)src; + + // Send the packet + ssize_t result = sendto(fd, data, len, MSG_DONTWAIT | MSG_NOSIGNAL, &dest->sa, SALEN(dest->sa)); +#endif + + if(result <= 0) { + logger(mesh, MESHLINK_ERROR, "Error sending multicast packet: %s", strerror(errno)); + } +} + +static void send_mdns_packet(meshlink_handle_t *mesh, discovery_address_t *addr, bool response) { + // Configure the socket to send the packet to the right interface + uint8_t msg[1024]; + size_t msg_size; + + if(response) { + char *fingerprint = meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self); + static const char *keys[] = {MESHLINK_MDNS_NAME_KEY, MESHLINK_MDNS_FINGERPRINT_KEY}; + const char *values[] = {mesh->name, fingerprint}; + msg_size = prepare_response(msg, sizeof msg, fingerprint, mesh->appname, "tcp", atoi(mesh->myport), 2, keys, values); + free(fingerprint); + addr->last_response_sent = mesh->loop.now.tv_sec; + } else { + msg_size = prepare_request(msg, sizeof msg, mesh->appname, "tcp"); + } + + switch(addr->address.sa.sa_family) { + case AF_INET: + send_mdns_packet_ipv4(mesh, mesh->discovery.sockets[0].fd, addr->index, &addr->address, &mdns_address_ipv4, msg, msg_size); + break; + + case AF_INET6: + send_mdns_packet_ipv6(mesh, mesh->discovery.sockets[1].fd, addr->index, &addr->address, &mdns_address_ipv6, msg, msg_size); + break; + + default: + break; + } +} + +static void mdns_io_handler(event_loop_t *loop, void *data, int flags) { + (void)flags; + meshlink_handle_t *mesh = loop->data; + io_t *io = data; + uint8_t buf[1024]; + sockaddr_t sa; + socklen_t sl = sizeof(sa); + +#ifdef IP_PKTINFO + struct iovec iov = { + .iov_base = buf, + .iov_len = sizeof(buf), + }; + + union { + char buf[1024]; + struct cmsghdr align; + } u; + + struct msghdr msg = { + .msg_name = &sa, + .msg_namelen = sl, + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = u.buf, + .msg_controllen = sizeof(u.buf), + }; + + ssize_t len = recvmsg(io->fd, &msg, MSG_DONTWAIT | MSG_NOSIGNAL); + + sl = msg.msg_namelen; +#else + ssize_t len = recvfrom(io->fd, buf, sizeof(buf), MSG_DONTWAIT, &sa.sa, &sl); +#endif + + if(len == -1) { + if(!sockwouldblock(errno)) { + logger(mesh, MESHLINK_ERROR, "Error reading from mDNS discovery socket: %s", strerror(errno)); + io_set(loop, io, 0); + } + + return; + } + + char *name = NULL; + uint16_t port = 0; + const char *keys[2] = {MESHLINK_MDNS_NAME_KEY, MESHLINK_MDNS_FINGERPRINT_KEY}; + char *values[2] = {NULL, NULL}; + + if(parse_response(buf, len, &name, mesh->appname, "tcp", &port, 2, keys, values)) { + node_t *n = (node_t *)meshlink_get_node(mesh, values[0]); + + if(n) { + if(n != mesh->self) { + logger(mesh, MESHLINK_INFO, "Node %s discovered on the local network.\n", n->name); + } + + switch(sa.sa.sa_family) { + case AF_INET: + sa.in.sin_port = htons(port); + break; + + case AF_INET6: + sa.in6.sin6_port = htons(port); + break; + + default: + logger(mesh, MESHLINK_WARNING, "Could not resolve node %s to a known address family type.\n", n->name); + sa.sa.sa_family = AF_UNKNOWN; + break; + } + + if(sa.sa.sa_family != AF_UNKNOWN) { + n->catta_address = sa; + n->last_connect_try = 0; + node_add_recent_address(mesh, n, &sa); + + if(n->connection) { + n->connection->last_ping_time = -3600; + } + + for list_each(outgoing_t, outgoing, mesh->outgoings) { + if(outgoing->node != n) { + continue; + } + + outgoing->timeout = 0; + + if(outgoing->ev.cb) { + timeout_set(&mesh->loop, &outgoing->ev, &(struct timespec) { + 0, 0 + }); + } + } + } + } + } else if(parse_request(buf, len, mesh->appname, "tcp")) { + char *fingerprint = meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self); + const char *response_values[] = {mesh->name, fingerprint}; + size_t size = prepare_response(buf, sizeof(buf), fingerprint, mesh->appname, "tcp", atoi(mesh->myport), 2, keys, response_values); + + int index = -1; + int family = AF_UNSPEC; + + for(struct cmsghdr *cmptr = CMSG_FIRSTHDR(&msg); cmptr != NULL; cmptr = CMSG_NXTHDR(&msg, cmptr)) { +#ifdef IP_PKTINFO + + if(cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO) { + struct in_pktinfo *pktinfo = (struct in_pktinfo *) CMSG_DATA(cmptr); + index = pktinfo->ipi_ifindex; + family = AF_INET; + break; + } else +#endif +#ifdef IPV6_PKTINFO + if(cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == IPV6_PKTINFO) { + struct in6_pktinfo *pktinfo = (struct in6_pktinfo *) CMSG_DATA(cmptr); + index = pktinfo->ipi6_ifindex; + family = AF_INET6; + break; + } + +#endif + } + + if(index) { + // Send a multicast response back using all addresses matching the interface + for(int i = 0; i < mesh->discovery.address_count; i++) { + discovery_address_t *p = &mesh->discovery.addresses[i]; + + if(p->index == index && p->address.sa.sa_family == family) { + send_mdns_packet(mesh, p, true); + } + } + } else { + // Send a unicast response back + sendto(io->fd, buf, size, MSG_DONTWAIT | MSG_NOSIGNAL, &sa.sa, sl); + } + + free(fingerprint); + } + + free(name); + + for(int i = 0; i < 2; i++) { + free(values[i]); + } +} + +static void iface_up(meshlink_handle_t *mesh, int index) { + int *p = bsearch(&index, mesh->discovery.ifaces, mesh->discovery.iface_count, sizeof(*p), iface_compare); + + if(p) { + return; + } + + mesh->discovery.ifaces = xrealloc(mesh->discovery.ifaces, ++mesh->discovery.iface_count * sizeof(*p)); + mesh->discovery.ifaces[mesh->discovery.iface_count - 1] = index; + qsort(mesh->discovery.ifaces, mesh->discovery.iface_count, sizeof(*p), iface_compare); + + // Add multicast membership + struct ip_mreqn mreq4 = { + .imr_multiaddr = mdns_address_ipv4.in.sin_addr, + .imr_ifindex = index, + }; + + setsockopt(mesh->discovery.sockets[0].fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq4, sizeof(mreq4)); + setsockopt(mesh->discovery.sockets[0].fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq4, sizeof(mreq4)); + + struct ipv6_mreq mreq6 = { + .ipv6mr_multiaddr = mdns_address_ipv6.in6.sin6_addr, + .ipv6mr_interface = index, + }; + + setsockopt(mesh->discovery.sockets[1].fd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, &mreq6, sizeof(mreq6)); + setsockopt(mesh->discovery.sockets[1].fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq6, sizeof(mreq6)); + + // Send an announcement for all addresses associated with this interface + for(int i = 0; i < mesh->discovery.address_count; i++) { + if(mesh->discovery.addresses[i].index == index) { + send_mdns_packet(mesh, &mesh->discovery.addresses[i], false); + } + } + + handle_network_change(mesh, true); +} + +static void iface_down(meshlink_handle_t *mesh, int index) { + int *p = bsearch(&index, mesh->discovery.ifaces, mesh->discovery.iface_count, sizeof(*p), iface_compare); + + if(!p) { + return; + } + + // Drop multicast membership + struct ip_mreqn mreq4 = { + .imr_multiaddr = mdns_address_ipv4.in.sin_addr, + .imr_ifindex = index, + }; + setsockopt(mesh->discovery.sockets[0].fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq4, sizeof(mreq4)); + + struct ipv6_mreq mreq6 = { + .ipv6mr_multiaddr = mdns_address_ipv6.in6.sin6_addr, + .ipv6mr_interface = index, + }; + setsockopt(mesh->discovery.sockets[1].fd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, &mreq6, sizeof(mreq6)); + + memmove(p, p + 1, (mesh->discovery.ifaces + --mesh->discovery.iface_count - p) * sizeof(*p)); + + handle_network_change(mesh, mesh->discovery.iface_count); +} + +static void addr_add(meshlink_handle_t *mesh, const discovery_address_t *addr) { + discovery_address_t *p = bsearch(addr, mesh->discovery.addresses, mesh->discovery.address_count, sizeof(*p), address_compare); + + if(p) { + return; + } + + bool up = bsearch(&addr->index, mesh->discovery.ifaces, mesh->discovery.iface_count, sizeof(int), iface_compare); + + mesh->discovery.addresses = xrealloc(mesh->discovery.addresses, ++mesh->discovery.address_count * sizeof(*p)); + mesh->discovery.addresses[mesh->discovery.address_count - 1] = *addr; + mesh->discovery.addresses[mesh->discovery.address_count - 1].up = up; + + if(up) { + send_mdns_packet(mesh, &mesh->discovery.addresses[mesh->discovery.address_count - 1], false); + } + + qsort(mesh->discovery.addresses, mesh->discovery.address_count, sizeof(*p), address_compare); +} + +static void addr_del(meshlink_handle_t *mesh, const discovery_address_t *addr) { + discovery_address_t *p = bsearch(addr, mesh->discovery.addresses, mesh->discovery.address_count, sizeof(*p), address_compare); + + if(!p) { + return; + } + + memmove(p, p + 1, (mesh->discovery.addresses + --mesh->discovery.address_count - p) * sizeof(*p)); +} + +void scan_ifaddrs(meshlink_handle_t *mesh) { +#ifdef HAVE_GETIFADDRS + struct ifaddrs *ifa = NULL; + + if(getifaddrs(&ifa) == -1) { + logger(mesh, MESHLINK_ERROR, "Could not get list of interface addresses: %s", strerror(errno)); + return; + } + + // Check for interfaces being removed + for(int i = 0; i < mesh->discovery.iface_count;) { + bool found = false; + + for(struct ifaddrs *ifap = ifa; ifap; ifap = ifap->ifa_next) { + if(!ifap->ifa_name) { + continue; + } + + int index = if_nametoindex(ifap->ifa_name); + + if(mesh->discovery.ifaces[i] == index) { + found = true; + break; + } + } + + if(!found) { + iface_down(mesh, mesh->discovery.ifaces[i]); + } else { + i++; + } + } + + // Check for addresses being removed + for(int i = 0; i < mesh->discovery.address_count;) { + discovery_address_t *p = &mesh->discovery.addresses[i]; + bool found = false; + + for(struct ifaddrs *ifap = ifa; ifap; ifap = ifap->ifa_next) { + if(!ifap->ifa_name || !ifap->ifa_addr) { + continue; + } + + int index = if_nametoindex(ifap->ifa_name); + + if(p->index == index && sockaddrcmp_noport(&p->address, (sockaddr_t *)ifap->ifa_addr) == 0) { + found = true; + break; + } + } + + if(!found) { + (void)addr_del; + memmove(p, p + 1, (mesh->discovery.addresses + --mesh->discovery.address_count - p) * sizeof(*p)); + } else { + i++; + } + } + + // Check for interfaces state changes and addresses going up + for(struct ifaddrs *ifap = ifa; ifap; ifap = ifap->ifa_next) { + if(!ifap->ifa_name) { + continue; + } + + int index = if_nametoindex(ifap->ifa_name); + + if(ifap->ifa_flags & IFF_UP && ifap->ifa_flags & IFF_MULTICAST && !(ifap->ifa_flags & IFF_LOOPBACK)) { + iface_up(mesh, index); + } else { + iface_down(mesh, index); + } + + if(!ifap->ifa_addr) { + continue; + } + + discovery_address_t addr = { + .index = index, + }; + + sockaddr_t *sa = (sockaddr_t *)ifap->ifa_addr; + + if(sa->sa.sa_family == AF_INET) { + memcpy(&addr.address.in, &sa->in, sizeof(sa->in)); + addr.address.in.sin_port = ntohs(5353); + } else if(sa->sa.sa_family == AF_INET6) { + memcpy(&addr.address.in6, &sa->in6, sizeof(sa->in6)); + addr.address.in6.sin6_port = ntohs(5353); + } else { + addr.address.sa.sa_family = AF_UNKNOWN; + } + + if(addr.address.sa.sa_family != AF_UNKNOWN) { + addr_add(mesh, &addr); + } + } + + freeifaddrs(ifa); +#else + (void)mesh; +#endif +} + +#if defined(__linux) +static void netlink_getlink(int fd) { + static const struct { + struct nlmsghdr nlm; + struct ifinfomsg ifi; + } msg = { + .nlm.nlmsg_len = NLMSG_LENGTH(sizeof(msg.ifi)), + .nlm.nlmsg_type = RTM_GETLINK, + .nlm.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, + .nlm.nlmsg_seq = 1, + .ifi.ifi_family = AF_UNSPEC, + }; + send(fd, &msg, msg.nlm.nlmsg_len, 0); +} + +static void netlink_getaddr(int fd) { + static const struct { + struct nlmsghdr nlm; + struct ifaddrmsg ifa; + } msg = { + .nlm.nlmsg_len = NLMSG_LENGTH(sizeof(msg.ifa)), + .nlm.nlmsg_type = RTM_GETADDR, + .nlm.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, + .nlm.nlmsg_seq = 2, + .ifa.ifa_family = AF_UNSPEC, + }; + send(fd, &msg, msg.nlm.nlmsg_len, 0); +} + +static void netlink_parse_link(meshlink_handle_t *mesh, const struct nlmsghdr *nlm) { + const struct ifinfomsg *ifi = (const struct ifinfomsg *)(nlm + 1); + + if(ifi->ifi_flags & IFF_UP && ifi->ifi_flags & IFF_MULTICAST) { + iface_up(mesh, ifi->ifi_index); + } else { + iface_down(mesh, ifi->ifi_index); + } +} + +static void netlink_parse_addr(meshlink_handle_t *mesh, const struct nlmsghdr *nlm) { + const struct ifaddrmsg *ifa = (const struct ifaddrmsg *)(nlm + 1); + const uint8_t *ptr = (const uint8_t *)(ifa + 1); + size_t len = nlm->nlmsg_len - (ptr - (const uint8_t *)nlm); + + while(len >= sizeof(struct rtattr)) { + const struct rtattr *rta = (const struct rtattr *)ptr; + + if(rta->rta_len <= 0 || rta->rta_len > len) { + break; + } + + if(rta->rta_type == IFA_ADDRESS) { + discovery_address_t addr = { + .index = ifa->ifa_index, + }; + + if(rta->rta_len == 8) { + addr.address.sa.sa_family = AF_INET; + memcpy(&addr.address.in.sin_addr, ptr + 4, 4); + addr.address.in.sin_port = ntohs(5353); + } else if(rta->rta_len == 20) { + addr.address.sa.sa_family = AF_INET6; + memcpy(&addr.address.in6.sin6_addr, ptr + 4, 16); + addr.address.in6.sin6_port = ntohs(5353); + addr.address.in6.sin6_scope_id = ifa->ifa_index; + } else { + addr.address.sa.sa_family = AF_UNKNOWN; + } + + if(addr.address.sa.sa_family != AF_UNKNOWN) { + if(nlm->nlmsg_type == RTM_NEWADDR) { + addr_add(mesh, &addr); + } else { + addr_del(mesh, &addr); + } + } + } + + unsigned short rta_len = (rta->rta_len + 3) & ~3; + ptr += rta_len; + len -= rta_len; + } +} + +static void netlink_parse(meshlink_handle_t *mesh, const void *data, size_t len) { + const uint8_t *ptr = data; + + while(len >= sizeof(struct nlmsghdr)) { + const struct nlmsghdr *nlm = (const struct nlmsghdr *)ptr; + + if(nlm->nlmsg_len > len) { + break; + } + + switch(nlm->nlmsg_type) { + case RTM_NEWLINK: + case RTM_DELLINK: + netlink_parse_link(mesh, nlm); + break; + + case RTM_NEWADDR: + case RTM_DELADDR: + netlink_parse_addr(mesh, nlm); + } + + ptr += nlm->nlmsg_len; + len -= nlm->nlmsg_len; + } +} + +static void netlink_io_handler(event_loop_t *loop, void *data, int flags) { + (void)flags; + (void)data; + meshlink_handle_t *mesh = loop->data; + + struct { + struct nlmsghdr nlm; + char data[16384]; + } msg; + + while(true) { + ssize_t result = recv(mesh->discovery.pfroute_io.fd, &msg, sizeof(msg), MSG_DONTWAIT); + + if(result <= 0) { + if(result == 0 || errno == EAGAIN || errno == EINTR) { + break; + } + + logger(mesh, MESHLINK_ERROR, "Reading from Netlink socket failed: %s\n", strerror(errno)); + io_set(loop, &mesh->discovery.pfroute_io, 0); + } + + if((size_t)result < sizeof(msg.nlm)) { + logger(mesh, MESHLINK_ERROR, "Invalid Netlink message\n"); + break; + } + + if(msg.nlm.nlmsg_type == NLMSG_DONE) { + if(msg.nlm.nlmsg_seq == 1) { + // We just got the result of GETLINK, now send GETADDR. + netlink_getaddr(mesh->discovery.pfroute_io.fd); + } + } else { + netlink_parse(mesh, &msg, result); + + if(loop->now.tv_sec > mesh->discovery.last_update + 5) { + mesh->discovery.last_update = loop->now.tv_sec; + handle_network_change(mesh, true); + } + } + } +} +#elif defined(__APPLE__) +static void reachability_change_callback(SCNetworkReachabilityRef reachability, SCNetworkReachabilityFlags flags, void *info) { + (void)reachability; + (void)flags; + + meshlink_handle_t *mesh = info; + + pthread_mutex_lock(&mesh->mutex); + + scan_ifaddrs(mesh); + + if(mesh->loop.now.tv_sec > mesh->discovery.last_update + 5) { + logger(mesh, MESHLINK_INFO, "Network change detected"); + mesh->discovery.last_update = mesh->loop.now.tv_sec; + handle_network_change(mesh, true); + } + + pthread_mutex_unlock(&mesh->mutex); +} + +static void *network_change_handler(void *arg) { + meshlink_handle_t *mesh = arg; + + mesh->discovery.runloop = CFRunLoopGetCurrent(); + + SCNetworkReachabilityRef reach_v4 = SCNetworkReachabilityCreateWithName(NULL, "93.184.216.34"); + SCNetworkReachabilityRef reach_v6 = SCNetworkReachabilityCreateWithName(NULL, "2606:2800:220:1:248:1893:25c8:1946"); + + SCNetworkReachabilityContext context; + memset(&context, 0, sizeof(context)); + context.info = mesh; + + if(reach_v4) { + SCNetworkReachabilitySetCallback(reach_v4, reachability_change_callback, &context); + SCNetworkReachabilityScheduleWithRunLoop(reach_v4, mesh->discovery.runloop, kCFRunLoopDefaultMode); + } else { + logger(mesh, MESHLINK_ERROR, "Could not create IPv4 network reachability watcher"); + } + + if(reach_v6) { + SCNetworkReachabilitySetCallback(reach_v6, reachability_change_callback, &context); + SCNetworkReachabilityScheduleWithRunLoop(reach_v6, mesh->discovery.runloop, kCFRunLoopDefaultMode); + } else { + logger(mesh, MESHLINK_ERROR, "Could not create IPv6 network reachability watcher"); + } + + CFRunLoopRun(); + + mesh->discovery.runloop = NULL; + + if(reach_v4) { + SCNetworkReachabilityUnscheduleFromRunLoop(reach_v4, mesh->discovery.runloop, kCFRunLoopDefaultMode); + CFRelease(reach_v4); + } + + if(reach_v6) { + SCNetworkReachabilityUnscheduleFromRunLoop(reach_v6, mesh->discovery.runloop, kCFRunLoopDefaultMode); + CFRelease(reach_v6); + } + + return NULL; +} +#elif defined(RTM_NEWADDR) +static void pfroute_parse_iface(meshlink_handle_t *mesh, const struct rt_msghdr *rtm) { + const struct if_msghdr *ifm = (const struct if_msghdr *)rtm; + + if(ifm->ifm_flags & IFF_UP && ifm->ifm_flags & IFF_MULTICAST && !(ifm->ifm_flags & IFF_LOOPBACK)) { + iface_up(mesh, ifm->ifm_index); + } else { + iface_down(mesh, ifm->ifm_index); + } +} + +static void pfroute_parse_addr(meshlink_handle_t *mesh, const struct rt_msghdr *rtm) { + const struct ifa_msghdr *ifam = (const struct ifa_msghdr *)rtm; + const char *p = (const char *)(ifam + 1); + + for(unsigned int i = 1; i; i <<= 1) { + if(!(ifam->ifam_addrs & i)) { + continue; + } + + const sockaddr_t *sa = (const sockaddr_t *)p; + + if(i == RTA_IFA) { + discovery_address_t addr = { + .index = ifam->ifam_index, + }; + + if(sa->sa.sa_family == AF_INET) { + addr.address.in = sa->in; + addr.address.in.sin_port = ntohs(5353); + } else if(sa->sa.sa_family == AF_INET6) { + addr.address.in6 = sa->in6; + addr.address.in6.sin6_port = ntohs(5353); + } else { + addr.address.sa.sa_family = AF_UNKNOWN; + } + + if(addr.address.sa.sa_family != AF_UNKNOWN) { + if(ifam->ifam_type == RTM_NEWADDR) { + addr_add(mesh, &addr); + } else { + addr_del(mesh, &addr); + } + } + + break; + } + + size_t len = (sa->sa.sa_len + 3) & ~3; + p += len; + } +} + +static void pfroute_io_handler(event_loop_t *loop, void *data, int flags) { + (void)flags; + (void)data; + meshlink_handle_t *mesh = loop->data; + + struct { + struct rt_msghdr rtm; + char data[2048]; + } msg; + + while(true) { + msg.rtm.rtm_version = 0; + ssize_t result = recv(mesh->discovery.pfroute_io.fd, &msg, sizeof(msg), MSG_DONTWAIT); + + if(result <= 0) { + if(result == 0 || errno == EAGAIN || errno == EINTR) { + break; + } + + logger(mesh, MESHLINK_ERROR, "Reading from PFROUTE socket failed: %s\n", strerror(errno)); + io_set(loop, &mesh->discovery.pfroute_io, 0); + } + + if(msg.rtm.rtm_version != RTM_VERSION) { + logger(mesh, MESHLINK_ERROR, "Invalid PFROUTE message version\n"); + break; + } + + switch(msg.rtm.rtm_type) { + case RTM_IFINFO: + pfroute_parse_iface(mesh, &msg.rtm); + break; + + case RTM_NEWADDR: + case RTM_DELADDR: + pfroute_parse_addr(mesh, &msg.rtm); + break; + + default: + break; + } + } +} +#endif + +bool discovery_start(meshlink_handle_t *mesh) { + logger(mesh, MESHLINK_DEBUG, "discovery_start called\n"); + + assert(mesh); + + // Set up multicast sockets for mDNS + static const int one = 1; + static const int ttl = 255; + static const uint8_t one8 = 1; + static const uint8_t ttl8 = 255; + + int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + if(fd == -1) { + logger(mesh, MESHLINK_ERROR, "Error creating IPv4 socket: %s", strerror(errno)); + } else { + + sockaddr_t sa4 = { + .in.sin_family = AF_INET, + .in.sin_port = ntohs(5353), + }; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); +#ifdef SO_REUSEPORT + setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); +#endif +#ifdef IP_PKTINFO + setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); +#endif + setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one8, sizeof(one8)); + setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl8, sizeof(ttl8)); + + if(bind(fd, &sa4.sa, SALEN(sa4.sa)) == -1) { + logger(mesh, MESHLINK_ERROR, "Error binding to IPv4 multicast socket: %s", strerror(errno)); + } else { + io_add(&mesh->loop, &mesh->discovery.sockets[0], mdns_io_handler, &mesh->discovery.sockets[0], fd, IO_READ); + } + } + + fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + + if(fd == -1) { + logger(mesh, MESHLINK_ERROR, "Error creating IPv6 socket: %s", strerror(errno)); + } else { + sockaddr_t sa6 = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = ntohs(5353), + }; + + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); +#ifdef SO_REUSEPORT + setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); +#endif +#ifdef IPV6_RECVPKTINFO + setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); +#endif + setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); + setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one)); + setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); + setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); + + if(bind(fd, &sa6.sa, SALEN(sa6.sa)) == -1) { + logger(mesh, MESHLINK_ERROR, "Error binding to IPv6 multicast socket: %s", strerror(errno)); + } else { + io_add(&mesh->loop, &mesh->discovery.sockets[1], mdns_io_handler, &mesh->discovery.sockets[1], fd, IO_READ); + } + } + +#if defined(__linux) + int sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE); + + if(sock != -1) { + struct sockaddr_nl sa; + memset(&sa, 0, sizeof(sa)); + sa.nl_family = AF_NETLINK; + sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR; + + if(bind(sock, (struct sockaddr *)&sa, sizeof(sa)) != -1) { + io_add(&mesh->loop, &mesh->discovery.pfroute_io, netlink_io_handler, NULL, sock, IO_READ); + netlink_getlink(sock); + } else { + logger(mesh, MESHLINK_WARNING, "Could not bind AF_NETLINK socket: %s", strerror(errno)); + scan_ifaddrs(mesh); + } + } else { + logger(mesh, MESHLINK_WARNING, "Could not open AF_NETLINK socket: %s", strerror(errno)); + scan_ifaddrs(mesh); + } + +#elif defined(__APPLE__) + pthread_create(&mesh->discovery.thread, NULL, network_change_handler, mesh); + // TODO: Do we need to wait for the thread to start succesfully? + scan_ifaddrs(mesh); +#elif defined(RTM_NEWADDR) + int sock = socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC); + + if(sock != -1) { + io_add(&mesh->loop, &mesh->discovery.pfroute_io, pfroute_io_handler, NULL, sock, IO_READ); + } else { + logger(mesh, MESHLINK_WARNING, "Could not open PF_ROUTE socket: %s", strerror(errno)); + } + + scan_ifaddrs(mesh); +#endif + + return true; +} + +void discovery_stop(meshlink_handle_t *mesh) { + logger(mesh, MESHLINK_DEBUG, "discovery_stop called\n"); + + assert(mesh); + + free(mesh->discovery.ifaces); + free(mesh->discovery.addresses); + mesh->discovery.ifaces = NULL; + mesh->discovery.addresses = NULL; + mesh->discovery.iface_count = 0; + mesh->discovery.address_count = 0; + +#if defined(__APPLE__) + + if(mesh->discovery.runloop) { + CFRunLoopStop(mesh->discovery.runloop); + pthread_join(mesh->discovery.thread, NULL); + } + +#endif + + if(mesh->discovery.pfroute_io.cb) { + close(mesh->discovery.pfroute_io.fd); + io_del(&mesh->loop, &mesh->discovery.pfroute_io); + } + + for(int i = 0; i < 2; i++) { + if(mesh->discovery.sockets[i].cb) { + close(mesh->discovery.sockets[i].fd); + io_del(&mesh->loop, &mesh->discovery.sockets[i]); + } + } +} + +void discovery_refresh(meshlink_handle_t *mesh) { + for(int i = 0; i < mesh->discovery.address_count; i++) { + if(mesh->discovery.addresses[i].up) { + send_mdns_packet(mesh, &mesh->discovery.addresses[i], false); + } + } +} diff --git a/src/discovery.h b/src/discovery.h new file mode 100644 index 0000000..03c4312 --- /dev/null +++ b/src/discovery.h @@ -0,0 +1,30 @@ +#ifndef MESHLINK_DISCOVERY_H +#define MESHLINK_DISCOVERY_H + +/* + discovery.h -- header for discovery.c + Copyright (C) 2014-2021 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +bool discovery_start(meshlink_handle_t *mesh); +void discovery_stop(meshlink_handle_t *mesh); +void discovery_refresh(meshlink_handle_t *mesh); +void scan_ifaddrs(meshlink_handle_t *mesh); + +#endif diff --git a/src/dropin.c b/src/dropin.c new file mode 100644 index 0000000..552998b --- /dev/null +++ b/src/dropin.c @@ -0,0 +1,59 @@ +/* + dropin.c -- a set of drop-in replacements for libc functions + Copyright (C) 2014 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "xalloc.h" + +#ifndef HAVE_ASPRINTF +int asprintf(char **buf, const char *fmt, ...) { + int result; + va_list ap; + va_start(ap, fmt); + result = vasprintf(buf, fmt, ap); + va_end(ap); + return result; +} + +int vasprintf(char **buf, const char *fmt, va_list ap) { + int status; + va_list aq; + int len; + + len = 4096; + *buf = xmalloc(len); + + va_copy(aq, ap); + status = vsnprintf(*buf, len, fmt, aq); + va_end(aq); + + if(status >= 0) { + *buf = xrealloc(*buf, status + 1); + } + + if(status > len - 1) { + len = status; + va_copy(aq, ap); + status = vsnprintf(*buf, len, fmt, aq); + va_end(aq); + } + + return status; +} +#endif diff --git a/src/dropin.h b/src/dropin.h new file mode 100644 index 0000000..603a28d --- /dev/null +++ b/src/dropin.h @@ -0,0 +1,40 @@ +#ifndef MESHLINK_DROPIN_H +#define MESHLINK_DROPIN_H + +/* + dropin.h -- header file for dropin.c + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef HAVE_ASPRINTF +int asprintf(char **, const char *, ...); +int vasprintf(char **, const char *, va_list ap); +#endif + +#ifdef HAVE_MINGW +#define mkdir(a, b) mkdir(a) + +#ifndef SHUT_RDWR +#define SHUT_RDWR SD_BOTH +#endif + +#ifndef EAI_SYSTEM +#define EAI_SYSTEM 0 +#endif +#endif + +#endif diff --git a/src/ecdh.h b/src/ecdh.h new file mode 100644 index 0000000..ea4a841 --- /dev/null +++ b/src/ecdh.h @@ -0,0 +1,34 @@ +#ifndef MESHLINK_ECDH_H +#define MESHLINK_ECDH_H + +/* + ecdh.h -- header file for ecdh.c + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#define ECDH_SIZE 32 +#define ECDH_SHARED_SIZE 32 + +#ifndef __MESHLINK_ECDH_INTERNAL__ +typedef struct ecdh ecdh_t; +#endif + +ecdh_t *ecdh_generate_public(void *pubkey) __attribute__((__malloc__)); +bool ecdh_compute_shared(ecdh_t *ecdh, const void *pubkey, void *shared) __attribute__((__warn_unused_result__)); +void ecdh_free(ecdh_t *ecdh); + +#endif diff --git a/src/ecdsa.h b/src/ecdsa.h new file mode 100644 index 0000000..64b329f --- /dev/null +++ b/src/ecdsa.h @@ -0,0 +1,41 @@ +#ifndef MESHLINK_ECDSA_H +#define MESHLINK_ECDSA_H + +/* + ecdsa.h -- ECDSA key handling + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef __MESHLINK_ECDSA_INTERNAL__ +typedef struct ecdsa ecdsa_t; +#endif + +ecdsa_t *ecdsa_set_private_key(const void *p) __attribute__((__malloc__)); +ecdsa_t *ecdsa_set_base64_public_key(const char *p) __attribute__((__malloc__)); +ecdsa_t *ecdsa_set_public_key(const void *p) __attribute__((__malloc__)); +char *ecdsa_get_base64_public_key(ecdsa_t *ecdsa); +const void *ecdsa_get_public_key(ecdsa_t *ecdsa) __attribute__((__malloc__)); +const void *ecdsa_get_private_key(ecdsa_t *ecdsa) __attribute__((__malloc__)); +ecdsa_t *ecdsa_read_pem_public_key(FILE *fp) __attribute__((__malloc__)); +ecdsa_t *ecdsa_read_pem_private_key(FILE *fp) __attribute__((__malloc__)); +size_t ecdsa_size(ecdsa_t *ecdsa); +bool ecdsa_sign(ecdsa_t *ecdsa, const void *in, size_t inlen, void *out) __attribute__((__warn_unused_result__)); +bool ecdsa_verify(ecdsa_t *ecdsa, const void *in, size_t inlen, const void *out) __attribute__((__warn_unused_result__)); +bool ecdsa_active(ecdsa_t *ecdsa); +void ecdsa_free(ecdsa_t *ecdsa); + +#endif diff --git a/src/ecdsagen.h b/src/ecdsagen.h new file mode 100644 index 0000000..2639067 --- /dev/null +++ b/src/ecdsagen.h @@ -0,0 +1,29 @@ +#ifndef MESHLINK_ECDSAGEN_H +#define MESHLINK_ECDSAGEN_H + +/* + ecdsagen.h -- ECDSA key generation and export + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "ecdsa.h" + +ecdsa_t *ecdsa_generate(void) __attribute__((__malloc__)); +bool ecdsa_write_pem_public_key(ecdsa_t *ecdsa, FILE *fp) __attribute__((__warn_unused_result__)); +bool ecdsa_write_pem_private_key(ecdsa_t *ecdsa, FILE *fp) __attribute__((__warn_unused_result__)); + +#endif diff --git a/src/ed25519/add_scalar.c b/src/ed25519/add_scalar.c new file mode 100644 index 0000000..37fed7b --- /dev/null +++ b/src/ed25519/add_scalar.c @@ -0,0 +1,56 @@ +#include "ed25519.h" +#include "ge.h" +#include "sc.h" + + +/* see http://crypto.stackexchange.com/a/6215/4697 */ +void ed25519_add_scalar(unsigned char *public_key, unsigned char *private_key, const unsigned char *scalar) { + const unsigned char SC_1[32] = {1}; /* scalar with value 1 */ + + unsigned char n[32]; + ge_p3 nB; + ge_p1p1 A_p1p1; + ge_p3 A; + ge_p3 public_key_unpacked; + ge_cached T; + + int i; + + /* copy the scalar and clear highest bit */ + for (i = 0; i < 31; ++i) { + n[i] = scalar[i]; + } + n[31] = scalar[31] & 127; + + /* private key: a = n + t */ + if (private_key) { + sc_muladd(private_key, SC_1, n, private_key); + } + + /* public key: A = nB + T */ + if (public_key) { + /* if we know the private key we don't need a point addition, which is faster */ + /* using a "timing attack" you could find out whether or not we know the private + key, but this information seems rather useless - if this is important pass + public_key and private_key separately in 2 function calls */ + if (private_key) { + ge_scalarmult_base(&A, private_key); + } else { + /* unpack public key into T */ + ge_frombytes_negate_vartime(&public_key_unpacked, public_key); + fe_neg(public_key_unpacked.X, public_key_unpacked.X); // undo negate + fe_neg(public_key_unpacked.T, public_key_unpacked.T); // undo negate + ge_p3_to_cached(&T, &public_key_unpacked); + + /* calculate n*B */ + ge_scalarmult_base(&nB, n); + + /* A = n*B + T */ + ge_add(&A_p1p1, &nB, &T); + ge_p1p1_to_p3(&A, &A_p1p1); + } + + /* pack public key */ + ge_p3_tobytes(public_key, &A); + } +} diff --git a/src/ed25519/ecdh.c b/src/ed25519/ecdh.c new file mode 100644 index 0000000..844b0fc --- /dev/null +++ b/src/ed25519/ecdh.c @@ -0,0 +1,51 @@ +/* + ecdh.c -- Diffie-Hellman key exchange handling + Copyright (C) 2014 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "../system.h" + +#include "ed25519.h" + +#define __MESHLINK_ECDH_INTERNAL__ +typedef struct ecdh_t { + uint8_t private[64]; +} ecdh_t; + +#include "../crypto.h" +#include "../ecdh.h" +#include "../xalloc.h" + +ecdh_t *ecdh_generate_public(void *pubkey) { + ecdh_t *ecdh = xzalloc(sizeof * ecdh); + + uint8_t seed[32]; + randomize(seed, sizeof seed); + ed25519_create_keypair(pubkey, ecdh->private, seed); + + return ecdh; +} + +bool ecdh_compute_shared(ecdh_t *ecdh, const void *pubkey, void *shared) { + ed25519_key_exchange(shared, pubkey, ecdh->private); + free(ecdh); + return true; +} + +void ecdh_free(ecdh_t *ecdh) { + free(ecdh); +} diff --git a/src/ed25519/ecdsa.c b/src/ed25519/ecdsa.c new file mode 100644 index 0000000..7cab5f2 --- /dev/null +++ b/src/ed25519/ecdsa.c @@ -0,0 +1,130 @@ +/* + ecdsa.c -- ECDSA key handling + Copyright (C) 2014-2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "../system.h" + +#include "ed25519.h" + +#define __MESHLINK_ECDSA_INTERNAL__ +typedef struct { + uint8_t private[64]; + uint8_t public[32]; +} ecdsa_t; + +#include "../logger.h" +#include "../ecdsa.h" +#include "../utils.h" +#include "../xalloc.h" + +// Get and set ECDSA keys +// +ecdsa_t *ecdsa_set_base64_public_key(const char *p) { + int len = strlen(p); + + if(len != 43) { + logger(NULL, MESHLINK_ERROR, "Invalid size %d for public key!", len); + return 0; + } + + ecdsa_t *ecdsa = xzalloc(sizeof * ecdsa); + len = b64decode(p, ecdsa->public, len); + + if(len != 32) { + logger(NULL, MESHLINK_ERROR, "Invalid format of public key! len = %d", len); + free(ecdsa); + return 0; + } + + return ecdsa; +} + +ecdsa_t *ecdsa_set_public_key(const void *p) { + ecdsa_t *ecdsa = xzalloc(sizeof(*ecdsa)); + memcpy(ecdsa->public, p, sizeof ecdsa->public); + return ecdsa; +} + +char *ecdsa_get_base64_public_key(ecdsa_t *ecdsa) { + char *base64 = xmalloc(44); + b64encode(ecdsa->public, base64, sizeof ecdsa->public); + + return base64; +} + +const void *ecdsa_get_public_key(ecdsa_t *ecdsa) { + return ecdsa->public; +} + +ecdsa_t *ecdsa_set_private_key(const void *p) { + ecdsa_t *ecdsa = xzalloc(sizeof(*ecdsa)); + memcpy(ecdsa->private, p, sizeof(*ecdsa)); + return ecdsa; +} + +const void *ecdsa_get_private_key(ecdsa_t *ecdsa) { + return ecdsa->private; +} + +// Read PEM ECDSA keys + +ecdsa_t *ecdsa_read_pem_public_key(FILE *fp) { + ecdsa_t *ecdsa = xzalloc(sizeof(*ecdsa)); + + if(fread(ecdsa->public, sizeof ecdsa->public, 1, fp) == 1) { + return ecdsa; + } + + free(ecdsa); + return 0; +} + +ecdsa_t *ecdsa_read_pem_private_key(FILE *fp) { + ecdsa_t *ecdsa = xmalloc(sizeof * ecdsa); + + if(fread(ecdsa, sizeof * ecdsa, 1, fp) == 1) { + return ecdsa; + } + + free(ecdsa); + return 0; +} + +size_t ecdsa_size(ecdsa_t *ecdsa) { + (void)ecdsa; + return 64; +} + +// TODO: standardise output format? + +bool ecdsa_sign(ecdsa_t *ecdsa, const void *in, size_t len, void *sig) { + ed25519_sign(sig, in, len, ecdsa->public, ecdsa->private); + return true; +} + +bool ecdsa_verify(ecdsa_t *ecdsa, const void *in, size_t len, const void *sig) { + return ed25519_verify(sig, in, len, ecdsa->public); +} + +bool ecdsa_active(ecdsa_t *ecdsa) { + return ecdsa; +} + +void ecdsa_free(ecdsa_t *ecdsa) { + free(ecdsa); +} diff --git a/src/ed25519/ecdsagen.c b/src/ed25519/ecdsagen.c new file mode 100644 index 0000000..4238bf5 --- /dev/null +++ b/src/ed25519/ecdsagen.c @@ -0,0 +1,56 @@ +/* + ecdsagen.c -- ECDSA key generation and export + Copyright (C) 2014 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "../system.h" + +#include "../crypto.h" +#include "ed25519.h" + +#define __MESHLINK_ECDSA_INTERNAL__ +typedef struct { + uint8_t private[64]; + uint8_t public[32]; +} ecdsa_t; + +#include "../crypto.h" +#include "../ecdsagen.h" +#include "../utils.h" +#include "../xalloc.h" + +// Generate ECDSA key + +ecdsa_t *ecdsa_generate(void) { + ecdsa_t *ecdsa = xzalloc(sizeof * ecdsa); + + uint8_t seed[32]; + randomize(seed, sizeof seed); + ed25519_create_keypair(ecdsa->public, ecdsa->private, seed); + + return ecdsa; +} + +// Write PEM ECDSA keys + +bool ecdsa_write_pem_public_key(ecdsa_t *ecdsa, FILE *fp) { + return fwrite(ecdsa->public, sizeof ecdsa->public, 1, fp) == 1; +} + +bool ecdsa_write_pem_private_key(ecdsa_t *ecdsa, FILE *fp) { + return fwrite(ecdsa, sizeof * ecdsa, 1, fp) == 1; +} diff --git a/src/ed25519/ed25519.h b/src/ed25519/ed25519.h new file mode 100644 index 0000000..0e6e57c --- /dev/null +++ b/src/ed25519/ed25519.h @@ -0,0 +1,38 @@ +#ifndef ED25519_H +#define ED25519_H + +#include + +#if defined(_WIN32) +#if defined(ED25519_BUILD_DLL) +#define ED25519_DECLSPEC __declspec(dllexport) +#elif defined(ED25519_DLL) +#define ED25519_DECLSPEC __declspec(dllimport) +#else +#define ED25519_DECLSPEC +#endif +#else +#define ED25519_DECLSPEC +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef ED25519_NO_SEED +int ED25519_DECLSPEC ed25519_create_seed(unsigned char *seed); +#endif + +void ED25519_DECLSPEC ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed); +void ED25519_DECLSPEC ed25519_sign(unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key, const unsigned char *private_key); +int ED25519_DECLSPEC ed25519_verify(const unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *private_key); +void ED25519_DECLSPEC ed25519_add_scalar(unsigned char *public_key, unsigned char *private_key, const unsigned char *scalar); +void ED25519_DECLSPEC ed25519_key_exchange(unsigned char *shared_secret, const unsigned char *public_key, const unsigned char *private_key); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/ed25519/fe.c b/src/ed25519/fe.c new file mode 100644 index 0000000..448e3e9 --- /dev/null +++ b/src/ed25519/fe.c @@ -0,0 +1,1491 @@ +#include "fixedint.h" +#include "fe.h" + + +/* + helper functions +*/ +static uint64_t load_3(const unsigned char *in) { + uint64_t result; + + result = (uint64_t) in[0]; + result |= ((uint64_t) in[1]) << 8; + result |= ((uint64_t) in[2]) << 16; + + return result; +} + +static uint64_t load_4(const unsigned char *in) { + uint64_t result; + + result = (uint64_t) in[0]; + result |= ((uint64_t) in[1]) << 8; + result |= ((uint64_t) in[2]) << 16; + result |= ((uint64_t) in[3]) << 24; + + return result; +} + + + +/* + h = 0 +*/ + +void fe_0(fe h) { + h[0] = 0; + h[1] = 0; + h[2] = 0; + h[3] = 0; + h[4] = 0; + h[5] = 0; + h[6] = 0; + h[7] = 0; + h[8] = 0; + h[9] = 0; +} + + + +/* + h = 1 +*/ + +void fe_1(fe h) { + h[0] = 1; + h[1] = 0; + h[2] = 0; + h[3] = 0; + h[4] = 0; + h[5] = 0; + h[6] = 0; + h[7] = 0; + h[8] = 0; + h[9] = 0; +} + + + +/* + h = f + g + Can overlap h with f or g. + + Preconditions: + |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + + Postconditions: + |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +*/ + +void fe_add(fe h, const fe f, const fe g) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t g0 = g[0]; + int32_t g1 = g[1]; + int32_t g2 = g[2]; + int32_t g3 = g[3]; + int32_t g4 = g[4]; + int32_t g5 = g[5]; + int32_t g6 = g[6]; + int32_t g7 = g[7]; + int32_t g8 = g[8]; + int32_t g9 = g[9]; + int32_t h0 = f0 + g0; + int32_t h1 = f1 + g1; + int32_t h2 = f2 + g2; + int32_t h3 = f3 + g3; + int32_t h4 = f4 + g4; + int32_t h5 = f5 + g5; + int32_t h6 = f6 + g6; + int32_t h7 = f7 + g7; + int32_t h8 = f8 + g8; + int32_t h9 = f9 + g9; + + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + + + +/* + Replace (f,g) with (g,g) if b == 1; + replace (f,g) with (f,g) if b == 0. + + Preconditions: b in {0,1}. +*/ + +void fe_cmov(fe f, const fe g, unsigned int b) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t g0 = g[0]; + int32_t g1 = g[1]; + int32_t g2 = g[2]; + int32_t g3 = g[3]; + int32_t g4 = g[4]; + int32_t g5 = g[5]; + int32_t g6 = g[6]; + int32_t g7 = g[7]; + int32_t g8 = g[8]; + int32_t g9 = g[9]; + int32_t x0 = f0 ^ g0; + int32_t x1 = f1 ^ g1; + int32_t x2 = f2 ^ g2; + int32_t x3 = f3 ^ g3; + int32_t x4 = f4 ^ g4; + int32_t x5 = f5 ^ g5; + int32_t x6 = f6 ^ g6; + int32_t x7 = f7 ^ g7; + int32_t x8 = f8 ^ g8; + int32_t x9 = f9 ^ g9; + + b = (unsigned int) (- (int) b); /* silence warning */ + x0 &= b; + x1 &= b; + x2 &= b; + x3 &= b; + x4 &= b; + x5 &= b; + x6 &= b; + x7 &= b; + x8 &= b; + x9 &= b; + + f[0] = f0 ^ x0; + f[1] = f1 ^ x1; + f[2] = f2 ^ x2; + f[3] = f3 ^ x3; + f[4] = f4 ^ x4; + f[5] = f5 ^ x5; + f[6] = f6 ^ x6; + f[7] = f7 ^ x7; + f[8] = f8 ^ x8; + f[9] = f9 ^ x9; +} + +/* + Replace (f,g) with (g,f) if b == 1; + replace (f,g) with (f,g) if b == 0. + + Preconditions: b in {0,1}. +*/ + +void fe_cswap(fe f,fe g,unsigned int b) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t g0 = g[0]; + int32_t g1 = g[1]; + int32_t g2 = g[2]; + int32_t g3 = g[3]; + int32_t g4 = g[4]; + int32_t g5 = g[5]; + int32_t g6 = g[6]; + int32_t g7 = g[7]; + int32_t g8 = g[8]; + int32_t g9 = g[9]; + int32_t x0 = f0 ^ g0; + int32_t x1 = f1 ^ g1; + int32_t x2 = f2 ^ g2; + int32_t x3 = f3 ^ g3; + int32_t x4 = f4 ^ g4; + int32_t x5 = f5 ^ g5; + int32_t x6 = f6 ^ g6; + int32_t x7 = f7 ^ g7; + int32_t x8 = f8 ^ g8; + int32_t x9 = f9 ^ g9; + b = -b; + x0 &= b; + x1 &= b; + x2 &= b; + x3 &= b; + x4 &= b; + x5 &= b; + x6 &= b; + x7 &= b; + x8 &= b; + x9 &= b; + f[0] = f0 ^ x0; + f[1] = f1 ^ x1; + f[2] = f2 ^ x2; + f[3] = f3 ^ x3; + f[4] = f4 ^ x4; + f[5] = f5 ^ x5; + f[6] = f6 ^ x6; + f[7] = f7 ^ x7; + f[8] = f8 ^ x8; + f[9] = f9 ^ x9; + g[0] = g0 ^ x0; + g[1] = g1 ^ x1; + g[2] = g2 ^ x2; + g[3] = g3 ^ x3; + g[4] = g4 ^ x4; + g[5] = g5 ^ x5; + g[6] = g6 ^ x6; + g[7] = g7 ^ x7; + g[8] = g8 ^ x8; + g[9] = g9 ^ x9; +} + + + +/* + h = f +*/ + +void fe_copy(fe h, const fe f) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + + h[0] = f0; + h[1] = f1; + h[2] = f2; + h[3] = f3; + h[4] = f4; + h[5] = f5; + h[6] = f6; + h[7] = f7; + h[8] = f8; + h[9] = f9; +} + + + +/* + Ignores top bit of h. +*/ + +void fe_frombytes(fe h, const unsigned char *s) { + int64_t h0 = load_4(s); + int64_t h1 = load_3(s + 4) << 6; + int64_t h2 = load_3(s + 7) << 5; + int64_t h3 = load_3(s + 10) << 3; + int64_t h4 = load_3(s + 13) << 2; + int64_t h5 = load_4(s + 16); + int64_t h6 = load_3(s + 20) << 7; + int64_t h7 = load_3(s + 23) << 5; + int64_t h8 = load_3(s + 26) << 4; + int64_t h9 = (load_3(s + 29) & 8388607) << 2; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + + carry9 = (h9 + (int64_t) (1 << 24)) >> 25; + h0 += carry9 * 19; + h9 -= carry9 << 25; + carry1 = (h1 + (int64_t) (1 << 24)) >> 25; + h2 += carry1; + h1 -= carry1 << 25; + carry3 = (h3 + (int64_t) (1 << 24)) >> 25; + h4 += carry3; + h3 -= carry3 << 25; + carry5 = (h5 + (int64_t) (1 << 24)) >> 25; + h6 += carry5; + h5 -= carry5 << 25; + carry7 = (h7 + (int64_t) (1 << 24)) >> 25; + h8 += carry7; + h7 -= carry7 << 25; + carry0 = (h0 + (int64_t) (1 << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + carry2 = (h2 + (int64_t) (1 << 25)) >> 26; + h3 += carry2; + h2 -= carry2 << 26; + carry4 = (h4 + (int64_t) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry6 = (h6 + (int64_t) (1 << 25)) >> 26; + h7 += carry6; + h6 -= carry6 << 26; + carry8 = (h8 + (int64_t) (1 << 25)) >> 26; + h9 += carry8; + h8 -= carry8 << 26; + + h[0] = (int32_t) h0; + h[1] = (int32_t) h1; + h[2] = (int32_t) h2; + h[3] = (int32_t) h3; + h[4] = (int32_t) h4; + h[5] = (int32_t) h5; + h[6] = (int32_t) h6; + h[7] = (int32_t) h7; + h[8] = (int32_t) h8; + h[9] = (int32_t) h9; +} + + + +void fe_invert(fe out, const fe z) { + fe t0; + fe t1; + fe t2; + fe t3; + int i; + + fe_sq(t0, z); + + for (i = 1; i < 1; ++i) { + fe_sq(t0, t0); + } + + fe_sq(t1, t0); + + for (i = 1; i < 2; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t1, z, t1); + fe_mul(t0, t0, t1); + fe_sq(t2, t0); + + for (i = 1; i < 1; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t1, t2); + fe_sq(t2, t1); + + for (i = 1; i < 5; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t2, t1); + fe_sq(t2, t1); + + for (i = 1; i < 10; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t2, t2, t1); + fe_sq(t3, t2); + + for (i = 1; i < 20; ++i) { + fe_sq(t3, t3); + } + + fe_mul(t2, t3, t2); + fe_sq(t2, t2); + + for (i = 1; i < 10; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t2, t1); + fe_sq(t2, t1); + + for (i = 1; i < 50; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t2, t2, t1); + fe_sq(t3, t2); + + for (i = 1; i < 100; ++i) { + fe_sq(t3, t3); + } + + fe_mul(t2, t3, t2); + fe_sq(t2, t2); + + for (i = 1; i < 50; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t2, t1); + fe_sq(t1, t1); + + for (i = 1; i < 5; ++i) { + fe_sq(t1, t1); + } + + fe_mul(out, t1, t0); +} + + + +/* + return 1 if f is in {1,3,5,...,q-2} + return 0 if f is in {0,2,4,...,q-1} + + Preconditions: + |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +*/ + +int fe_isnegative(const fe f) { + unsigned char s[32]; + + fe_tobytes(s, f); + + return s[0] & 1; +} + + + +/* + return 1 if f == 0 + return 0 if f != 0 + + Preconditions: + |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +*/ + +int fe_isnonzero(const fe f) { + unsigned char s[32]; + unsigned char r; + + fe_tobytes(s, f); + + r = s[0]; + #define F(i) r |= s[i] + F(1); + F(2); + F(3); + F(4); + F(5); + F(6); + F(7); + F(8); + F(9); + F(10); + F(11); + F(12); + F(13); + F(14); + F(15); + F(16); + F(17); + F(18); + F(19); + F(20); + F(21); + F(22); + F(23); + F(24); + F(25); + F(26); + F(27); + F(28); + F(29); + F(30); + F(31); + #undef F + + return r != 0; +} + + + +/* + h = f * g + Can overlap h with f or g. + + Preconditions: + |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + |g| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + + Postconditions: + |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. + */ + + /* + Notes on implementation strategy: + + Using schoolbook multiplication. + Karatsuba would save a little in some cost models. + + Most multiplications by 2 and 19 are 32-bit precomputations; + cheaper than 64-bit postcomputations. + + There is one remaining multiplication by 19 in the carry chain; + one *19 precomputation can be merged into this, + but the resulting data flow is considerably less clean. + + There are 12 carries below. + 10 of them are 2-way parallelizable and vectorizable. + Can get away with 11 carries, but then data flow is much deeper. + + With tighter constraints on inputs can squeeze carries into int32. +*/ + +void fe_mul(fe h, const fe f, const fe g) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t g0 = g[0]; + int32_t g1 = g[1]; + int32_t g2 = g[2]; + int32_t g3 = g[3]; + int32_t g4 = g[4]; + int32_t g5 = g[5]; + int32_t g6 = g[6]; + int32_t g7 = g[7]; + int32_t g8 = g[8]; + int32_t g9 = g[9]; + int32_t g1_19 = 19 * g1; /* 1.959375*2^29 */ + int32_t g2_19 = 19 * g2; /* 1.959375*2^30; still ok */ + int32_t g3_19 = 19 * g3; + int32_t g4_19 = 19 * g4; + int32_t g5_19 = 19 * g5; + int32_t g6_19 = 19 * g6; + int32_t g7_19 = 19 * g7; + int32_t g8_19 = 19 * g8; + int32_t g9_19 = 19 * g9; + int32_t f1_2 = 2 * f1; + int32_t f3_2 = 2 * f3; + int32_t f5_2 = 2 * f5; + int32_t f7_2 = 2 * f7; + int32_t f9_2 = 2 * f9; + int64_t f0g0 = f0 * (int64_t) g0; + int64_t f0g1 = f0 * (int64_t) g1; + int64_t f0g2 = f0 * (int64_t) g2; + int64_t f0g3 = f0 * (int64_t) g3; + int64_t f0g4 = f0 * (int64_t) g4; + int64_t f0g5 = f0 * (int64_t) g5; + int64_t f0g6 = f0 * (int64_t) g6; + int64_t f0g7 = f0 * (int64_t) g7; + int64_t f0g8 = f0 * (int64_t) g8; + int64_t f0g9 = f0 * (int64_t) g9; + int64_t f1g0 = f1 * (int64_t) g0; + int64_t f1g1_2 = f1_2 * (int64_t) g1; + int64_t f1g2 = f1 * (int64_t) g2; + int64_t f1g3_2 = f1_2 * (int64_t) g3; + int64_t f1g4 = f1 * (int64_t) g4; + int64_t f1g5_2 = f1_2 * (int64_t) g5; + int64_t f1g6 = f1 * (int64_t) g6; + int64_t f1g7_2 = f1_2 * (int64_t) g7; + int64_t f1g8 = f1 * (int64_t) g8; + int64_t f1g9_38 = f1_2 * (int64_t) g9_19; + int64_t f2g0 = f2 * (int64_t) g0; + int64_t f2g1 = f2 * (int64_t) g1; + int64_t f2g2 = f2 * (int64_t) g2; + int64_t f2g3 = f2 * (int64_t) g3; + int64_t f2g4 = f2 * (int64_t) g4; + int64_t f2g5 = f2 * (int64_t) g5; + int64_t f2g6 = f2 * (int64_t) g6; + int64_t f2g7 = f2 * (int64_t) g7; + int64_t f2g8_19 = f2 * (int64_t) g8_19; + int64_t f2g9_19 = f2 * (int64_t) g9_19; + int64_t f3g0 = f3 * (int64_t) g0; + int64_t f3g1_2 = f3_2 * (int64_t) g1; + int64_t f3g2 = f3 * (int64_t) g2; + int64_t f3g3_2 = f3_2 * (int64_t) g3; + int64_t f3g4 = f3 * (int64_t) g4; + int64_t f3g5_2 = f3_2 * (int64_t) g5; + int64_t f3g6 = f3 * (int64_t) g6; + int64_t f3g7_38 = f3_2 * (int64_t) g7_19; + int64_t f3g8_19 = f3 * (int64_t) g8_19; + int64_t f3g9_38 = f3_2 * (int64_t) g9_19; + int64_t f4g0 = f4 * (int64_t) g0; + int64_t f4g1 = f4 * (int64_t) g1; + int64_t f4g2 = f4 * (int64_t) g2; + int64_t f4g3 = f4 * (int64_t) g3; + int64_t f4g4 = f4 * (int64_t) g4; + int64_t f4g5 = f4 * (int64_t) g5; + int64_t f4g6_19 = f4 * (int64_t) g6_19; + int64_t f4g7_19 = f4 * (int64_t) g7_19; + int64_t f4g8_19 = f4 * (int64_t) g8_19; + int64_t f4g9_19 = f4 * (int64_t) g9_19; + int64_t f5g0 = f5 * (int64_t) g0; + int64_t f5g1_2 = f5_2 * (int64_t) g1; + int64_t f5g2 = f5 * (int64_t) g2; + int64_t f5g3_2 = f5_2 * (int64_t) g3; + int64_t f5g4 = f5 * (int64_t) g4; + int64_t f5g5_38 = f5_2 * (int64_t) g5_19; + int64_t f5g6_19 = f5 * (int64_t) g6_19; + int64_t f5g7_38 = f5_2 * (int64_t) g7_19; + int64_t f5g8_19 = f5 * (int64_t) g8_19; + int64_t f5g9_38 = f5_2 * (int64_t) g9_19; + int64_t f6g0 = f6 * (int64_t) g0; + int64_t f6g1 = f6 * (int64_t) g1; + int64_t f6g2 = f6 * (int64_t) g2; + int64_t f6g3 = f6 * (int64_t) g3; + int64_t f6g4_19 = f6 * (int64_t) g4_19; + int64_t f6g5_19 = f6 * (int64_t) g5_19; + int64_t f6g6_19 = f6 * (int64_t) g6_19; + int64_t f6g7_19 = f6 * (int64_t) g7_19; + int64_t f6g8_19 = f6 * (int64_t) g8_19; + int64_t f6g9_19 = f6 * (int64_t) g9_19; + int64_t f7g0 = f7 * (int64_t) g0; + int64_t f7g1_2 = f7_2 * (int64_t) g1; + int64_t f7g2 = f7 * (int64_t) g2; + int64_t f7g3_38 = f7_2 * (int64_t) g3_19; + int64_t f7g4_19 = f7 * (int64_t) g4_19; + int64_t f7g5_38 = f7_2 * (int64_t) g5_19; + int64_t f7g6_19 = f7 * (int64_t) g6_19; + int64_t f7g7_38 = f7_2 * (int64_t) g7_19; + int64_t f7g8_19 = f7 * (int64_t) g8_19; + int64_t f7g9_38 = f7_2 * (int64_t) g9_19; + int64_t f8g0 = f8 * (int64_t) g0; + int64_t f8g1 = f8 * (int64_t) g1; + int64_t f8g2_19 = f8 * (int64_t) g2_19; + int64_t f8g3_19 = f8 * (int64_t) g3_19; + int64_t f8g4_19 = f8 * (int64_t) g4_19; + int64_t f8g5_19 = f8 * (int64_t) g5_19; + int64_t f8g6_19 = f8 * (int64_t) g6_19; + int64_t f8g7_19 = f8 * (int64_t) g7_19; + int64_t f8g8_19 = f8 * (int64_t) g8_19; + int64_t f8g9_19 = f8 * (int64_t) g9_19; + int64_t f9g0 = f9 * (int64_t) g0; + int64_t f9g1_38 = f9_2 * (int64_t) g1_19; + int64_t f9g2_19 = f9 * (int64_t) g2_19; + int64_t f9g3_38 = f9_2 * (int64_t) g3_19; + int64_t f9g4_19 = f9 * (int64_t) g4_19; + int64_t f9g5_38 = f9_2 * (int64_t) g5_19; + int64_t f9g6_19 = f9 * (int64_t) g6_19; + int64_t f9g7_38 = f9_2 * (int64_t) g7_19; + int64_t f9g8_19 = f9 * (int64_t) g8_19; + int64_t f9g9_38 = f9_2 * (int64_t) g9_19; + int64_t h0 = f0g0 + f1g9_38 + f2g8_19 + f3g7_38 + f4g6_19 + f5g5_38 + f6g4_19 + f7g3_38 + f8g2_19 + f9g1_38; + int64_t h1 = f0g1 + f1g0 + f2g9_19 + f3g8_19 + f4g7_19 + f5g6_19 + f6g5_19 + f7g4_19 + f8g3_19 + f9g2_19; + int64_t h2 = f0g2 + f1g1_2 + f2g0 + f3g9_38 + f4g8_19 + f5g7_38 + f6g6_19 + f7g5_38 + f8g4_19 + f9g3_38; + int64_t h3 = f0g3 + f1g2 + f2g1 + f3g0 + f4g9_19 + f5g8_19 + f6g7_19 + f7g6_19 + f8g5_19 + f9g4_19; + int64_t h4 = f0g4 + f1g3_2 + f2g2 + f3g1_2 + f4g0 + f5g9_38 + f6g8_19 + f7g7_38 + f8g6_19 + f9g5_38; + int64_t h5 = f0g5 + f1g4 + f2g3 + f3g2 + f4g1 + f5g0 + f6g9_19 + f7g8_19 + f8g7_19 + f9g6_19; + int64_t h6 = f0g6 + f1g5_2 + f2g4 + f3g3_2 + f4g2 + f5g1_2 + f6g0 + f7g9_38 + f8g8_19 + f9g7_38; + int64_t h7 = f0g7 + f1g6 + f2g5 + f3g4 + f4g3 + f5g2 + f6g1 + f7g0 + f8g9_19 + f9g8_19; + int64_t h8 = f0g8 + f1g7_2 + f2g6 + f3g5_2 + f4g4 + f5g3_2 + f6g2 + f7g1_2 + f8g0 + f9g9_38; + int64_t h9 = f0g9 + f1g8 + f2g7 + f3g6 + f4g5 + f5g4 + f6g3 + f7g2 + f8g1 + f9g0 ; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + + carry0 = (h0 + (int64_t) (1 << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + carry4 = (h4 + (int64_t) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + + carry1 = (h1 + (int64_t) (1 << 24)) >> 25; + h2 += carry1; + h1 -= carry1 << 25; + carry5 = (h5 + (int64_t) (1 << 24)) >> 25; + h6 += carry5; + h5 -= carry5 << 25; + + carry2 = (h2 + (int64_t) (1 << 25)) >> 26; + h3 += carry2; + h2 -= carry2 << 26; + carry6 = (h6 + (int64_t) (1 << 25)) >> 26; + h7 += carry6; + h6 -= carry6 << 26; + + carry3 = (h3 + (int64_t) (1 << 24)) >> 25; + h4 += carry3; + h3 -= carry3 << 25; + carry7 = (h7 + (int64_t) (1 << 24)) >> 25; + h8 += carry7; + h7 -= carry7 << 25; + + carry4 = (h4 + (int64_t) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry8 = (h8 + (int64_t) (1 << 25)) >> 26; + h9 += carry8; + h8 -= carry8 << 26; + + carry9 = (h9 + (int64_t) (1 << 24)) >> 25; + h0 += carry9 * 19; + h9 -= carry9 << 25; + + carry0 = (h0 + (int64_t) (1 << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + + h[0] = (int32_t) h0; + h[1] = (int32_t) h1; + h[2] = (int32_t) h2; + h[3] = (int32_t) h3; + h[4] = (int32_t) h4; + h[5] = (int32_t) h5; + h[6] = (int32_t) h6; + h[7] = (int32_t) h7; + h[8] = (int32_t) h8; + h[9] = (int32_t) h9; +} + + +/* +h = f * 121666 +Can overlap h with f. + +Preconditions: + |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. + +Postconditions: + |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. +*/ + +void fe_mul121666(fe h, fe f) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int64_t h0 = f0 * (int64_t) 121666; + int64_t h1 = f1 * (int64_t) 121666; + int64_t h2 = f2 * (int64_t) 121666; + int64_t h3 = f3 * (int64_t) 121666; + int64_t h4 = f4 * (int64_t) 121666; + int64_t h5 = f5 * (int64_t) 121666; + int64_t h6 = f6 * (int64_t) 121666; + int64_t h7 = f7 * (int64_t) 121666; + int64_t h8 = f8 * (int64_t) 121666; + int64_t h9 = f9 * (int64_t) 121666; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + + carry9 = (h9 + (int64_t) (1<<24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25; + carry1 = (h1 + (int64_t) (1<<24)) >> 25; h2 += carry1; h1 -= carry1 << 25; + carry3 = (h3 + (int64_t) (1<<24)) >> 25; h4 += carry3; h3 -= carry3 << 25; + carry5 = (h5 + (int64_t) (1<<24)) >> 25; h6 += carry5; h5 -= carry5 << 25; + carry7 = (h7 + (int64_t) (1<<24)) >> 25; h8 += carry7; h7 -= carry7 << 25; + + carry0 = (h0 + (int64_t) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26; + carry2 = (h2 + (int64_t) (1<<25)) >> 26; h3 += carry2; h2 -= carry2 << 26; + carry4 = (h4 + (int64_t) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26; + carry6 = (h6 + (int64_t) (1<<25)) >> 26; h7 += carry6; h6 -= carry6 << 26; + carry8 = (h8 + (int64_t) (1<<25)) >> 26; h9 += carry8; h8 -= carry8 << 26; + + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + + +/* +h = -f + +Preconditions: + |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + +Postconditions: + |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. +*/ + +void fe_neg(fe h, const fe f) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t h0 = -f0; + int32_t h1 = -f1; + int32_t h2 = -f2; + int32_t h3 = -f3; + int32_t h4 = -f4; + int32_t h5 = -f5; + int32_t h6 = -f6; + int32_t h7 = -f7; + int32_t h8 = -f8; + int32_t h9 = -f9; + + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + + +void fe_pow22523(fe out, const fe z) { + fe t0; + fe t1; + fe t2; + int i; + fe_sq(t0, z); + + for (i = 1; i < 1; ++i) { + fe_sq(t0, t0); + } + + fe_sq(t1, t0); + + for (i = 1; i < 2; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t1, z, t1); + fe_mul(t0, t0, t1); + fe_sq(t0, t0); + + for (i = 1; i < 1; ++i) { + fe_sq(t0, t0); + } + + fe_mul(t0, t1, t0); + fe_sq(t1, t0); + + for (i = 1; i < 5; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t0, t1, t0); + fe_sq(t1, t0); + + for (i = 1; i < 10; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t1, t1, t0); + fe_sq(t2, t1); + + for (i = 1; i < 20; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t2, t1); + fe_sq(t1, t1); + + for (i = 1; i < 10; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t0, t1, t0); + fe_sq(t1, t0); + + for (i = 1; i < 50; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t1, t1, t0); + fe_sq(t2, t1); + + for (i = 1; i < 100; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t2, t1); + fe_sq(t1, t1); + + for (i = 1; i < 50; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t0, t1, t0); + fe_sq(t0, t0); + + for (i = 1; i < 2; ++i) { + fe_sq(t0, t0); + } + + fe_mul(out, t0, z); + return; +} + + +/* +h = f * f +Can overlap h with f. + +Preconditions: + |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + +Postconditions: + |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. +*/ + +/* +See fe_mul.c for discussion of implementation strategy. +*/ + +void fe_sq(fe h, const fe f) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t f0_2 = 2 * f0; + int32_t f1_2 = 2 * f1; + int32_t f2_2 = 2 * f2; + int32_t f3_2 = 2 * f3; + int32_t f4_2 = 2 * f4; + int32_t f5_2 = 2 * f5; + int32_t f6_2 = 2 * f6; + int32_t f7_2 = 2 * f7; + int32_t f5_38 = 38 * f5; /* 1.959375*2^30 */ + int32_t f6_19 = 19 * f6; /* 1.959375*2^30 */ + int32_t f7_38 = 38 * f7; /* 1.959375*2^30 */ + int32_t f8_19 = 19 * f8; /* 1.959375*2^30 */ + int32_t f9_38 = 38 * f9; /* 1.959375*2^30 */ + int64_t f0f0 = f0 * (int64_t) f0; + int64_t f0f1_2 = f0_2 * (int64_t) f1; + int64_t f0f2_2 = f0_2 * (int64_t) f2; + int64_t f0f3_2 = f0_2 * (int64_t) f3; + int64_t f0f4_2 = f0_2 * (int64_t) f4; + int64_t f0f5_2 = f0_2 * (int64_t) f5; + int64_t f0f6_2 = f0_2 * (int64_t) f6; + int64_t f0f7_2 = f0_2 * (int64_t) f7; + int64_t f0f8_2 = f0_2 * (int64_t) f8; + int64_t f0f9_2 = f0_2 * (int64_t) f9; + int64_t f1f1_2 = f1_2 * (int64_t) f1; + int64_t f1f2_2 = f1_2 * (int64_t) f2; + int64_t f1f3_4 = f1_2 * (int64_t) f3_2; + int64_t f1f4_2 = f1_2 * (int64_t) f4; + int64_t f1f5_4 = f1_2 * (int64_t) f5_2; + int64_t f1f6_2 = f1_2 * (int64_t) f6; + int64_t f1f7_4 = f1_2 * (int64_t) f7_2; + int64_t f1f8_2 = f1_2 * (int64_t) f8; + int64_t f1f9_76 = f1_2 * (int64_t) f9_38; + int64_t f2f2 = f2 * (int64_t) f2; + int64_t f2f3_2 = f2_2 * (int64_t) f3; + int64_t f2f4_2 = f2_2 * (int64_t) f4; + int64_t f2f5_2 = f2_2 * (int64_t) f5; + int64_t f2f6_2 = f2_2 * (int64_t) f6; + int64_t f2f7_2 = f2_2 * (int64_t) f7; + int64_t f2f8_38 = f2_2 * (int64_t) f8_19; + int64_t f2f9_38 = f2 * (int64_t) f9_38; + int64_t f3f3_2 = f3_2 * (int64_t) f3; + int64_t f3f4_2 = f3_2 * (int64_t) f4; + int64_t f3f5_4 = f3_2 * (int64_t) f5_2; + int64_t f3f6_2 = f3_2 * (int64_t) f6; + int64_t f3f7_76 = f3_2 * (int64_t) f7_38; + int64_t f3f8_38 = f3_2 * (int64_t) f8_19; + int64_t f3f9_76 = f3_2 * (int64_t) f9_38; + int64_t f4f4 = f4 * (int64_t) f4; + int64_t f4f5_2 = f4_2 * (int64_t) f5; + int64_t f4f6_38 = f4_2 * (int64_t) f6_19; + int64_t f4f7_38 = f4 * (int64_t) f7_38; + int64_t f4f8_38 = f4_2 * (int64_t) f8_19; + int64_t f4f9_38 = f4 * (int64_t) f9_38; + int64_t f5f5_38 = f5 * (int64_t) f5_38; + int64_t f5f6_38 = f5_2 * (int64_t) f6_19; + int64_t f5f7_76 = f5_2 * (int64_t) f7_38; + int64_t f5f8_38 = f5_2 * (int64_t) f8_19; + int64_t f5f9_76 = f5_2 * (int64_t) f9_38; + int64_t f6f6_19 = f6 * (int64_t) f6_19; + int64_t f6f7_38 = f6 * (int64_t) f7_38; + int64_t f6f8_38 = f6_2 * (int64_t) f8_19; + int64_t f6f9_38 = f6 * (int64_t) f9_38; + int64_t f7f7_38 = f7 * (int64_t) f7_38; + int64_t f7f8_38 = f7_2 * (int64_t) f8_19; + int64_t f7f9_76 = f7_2 * (int64_t) f9_38; + int64_t f8f8_19 = f8 * (int64_t) f8_19; + int64_t f8f9_38 = f8 * (int64_t) f9_38; + int64_t f9f9_38 = f9 * (int64_t) f9_38; + int64_t h0 = f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38; + int64_t h1 = f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38; + int64_t h2 = f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19; + int64_t h3 = f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38; + int64_t h4 = f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38; + int64_t h5 = f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38; + int64_t h6 = f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19; + int64_t h7 = f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38; + int64_t h8 = f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38; + int64_t h9 = f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + carry0 = (h0 + (int64_t) (1 << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + carry4 = (h4 + (int64_t) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry1 = (h1 + (int64_t) (1 << 24)) >> 25; + h2 += carry1; + h1 -= carry1 << 25; + carry5 = (h5 + (int64_t) (1 << 24)) >> 25; + h6 += carry5; + h5 -= carry5 << 25; + carry2 = (h2 + (int64_t) (1 << 25)) >> 26; + h3 += carry2; + h2 -= carry2 << 26; + carry6 = (h6 + (int64_t) (1 << 25)) >> 26; + h7 += carry6; + h6 -= carry6 << 26; + carry3 = (h3 + (int64_t) (1 << 24)) >> 25; + h4 += carry3; + h3 -= carry3 << 25; + carry7 = (h7 + (int64_t) (1 << 24)) >> 25; + h8 += carry7; + h7 -= carry7 << 25; + carry4 = (h4 + (int64_t) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry8 = (h8 + (int64_t) (1 << 25)) >> 26; + h9 += carry8; + h8 -= carry8 << 26; + carry9 = (h9 + (int64_t) (1 << 24)) >> 25; + h0 += carry9 * 19; + h9 -= carry9 << 25; + carry0 = (h0 + (int64_t) (1 << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + h[0] = (int32_t) h0; + h[1] = (int32_t) h1; + h[2] = (int32_t) h2; + h[3] = (int32_t) h3; + h[4] = (int32_t) h4; + h[5] = (int32_t) h5; + h[6] = (int32_t) h6; + h[7] = (int32_t) h7; + h[8] = (int32_t) h8; + h[9] = (int32_t) h9; +} + + +/* +h = 2 * f * f +Can overlap h with f. + +Preconditions: + |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + +Postconditions: + |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. +*/ + +/* +See fe_mul.c for discussion of implementation strategy. +*/ + +void fe_sq2(fe h, const fe f) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t f0_2 = 2 * f0; + int32_t f1_2 = 2 * f1; + int32_t f2_2 = 2 * f2; + int32_t f3_2 = 2 * f3; + int32_t f4_2 = 2 * f4; + int32_t f5_2 = 2 * f5; + int32_t f6_2 = 2 * f6; + int32_t f7_2 = 2 * f7; + int32_t f5_38 = 38 * f5; /* 1.959375*2^30 */ + int32_t f6_19 = 19 * f6; /* 1.959375*2^30 */ + int32_t f7_38 = 38 * f7; /* 1.959375*2^30 */ + int32_t f8_19 = 19 * f8; /* 1.959375*2^30 */ + int32_t f9_38 = 38 * f9; /* 1.959375*2^30 */ + int64_t f0f0 = f0 * (int64_t) f0; + int64_t f0f1_2 = f0_2 * (int64_t) f1; + int64_t f0f2_2 = f0_2 * (int64_t) f2; + int64_t f0f3_2 = f0_2 * (int64_t) f3; + int64_t f0f4_2 = f0_2 * (int64_t) f4; + int64_t f0f5_2 = f0_2 * (int64_t) f5; + int64_t f0f6_2 = f0_2 * (int64_t) f6; + int64_t f0f7_2 = f0_2 * (int64_t) f7; + int64_t f0f8_2 = f0_2 * (int64_t) f8; + int64_t f0f9_2 = f0_2 * (int64_t) f9; + int64_t f1f1_2 = f1_2 * (int64_t) f1; + int64_t f1f2_2 = f1_2 * (int64_t) f2; + int64_t f1f3_4 = f1_2 * (int64_t) f3_2; + int64_t f1f4_2 = f1_2 * (int64_t) f4; + int64_t f1f5_4 = f1_2 * (int64_t) f5_2; + int64_t f1f6_2 = f1_2 * (int64_t) f6; + int64_t f1f7_4 = f1_2 * (int64_t) f7_2; + int64_t f1f8_2 = f1_2 * (int64_t) f8; + int64_t f1f9_76 = f1_2 * (int64_t) f9_38; + int64_t f2f2 = f2 * (int64_t) f2; + int64_t f2f3_2 = f2_2 * (int64_t) f3; + int64_t f2f4_2 = f2_2 * (int64_t) f4; + int64_t f2f5_2 = f2_2 * (int64_t) f5; + int64_t f2f6_2 = f2_2 * (int64_t) f6; + int64_t f2f7_2 = f2_2 * (int64_t) f7; + int64_t f2f8_38 = f2_2 * (int64_t) f8_19; + int64_t f2f9_38 = f2 * (int64_t) f9_38; + int64_t f3f3_2 = f3_2 * (int64_t) f3; + int64_t f3f4_2 = f3_2 * (int64_t) f4; + int64_t f3f5_4 = f3_2 * (int64_t) f5_2; + int64_t f3f6_2 = f3_2 * (int64_t) f6; + int64_t f3f7_76 = f3_2 * (int64_t) f7_38; + int64_t f3f8_38 = f3_2 * (int64_t) f8_19; + int64_t f3f9_76 = f3_2 * (int64_t) f9_38; + int64_t f4f4 = f4 * (int64_t) f4; + int64_t f4f5_2 = f4_2 * (int64_t) f5; + int64_t f4f6_38 = f4_2 * (int64_t) f6_19; + int64_t f4f7_38 = f4 * (int64_t) f7_38; + int64_t f4f8_38 = f4_2 * (int64_t) f8_19; + int64_t f4f9_38 = f4 * (int64_t) f9_38; + int64_t f5f5_38 = f5 * (int64_t) f5_38; + int64_t f5f6_38 = f5_2 * (int64_t) f6_19; + int64_t f5f7_76 = f5_2 * (int64_t) f7_38; + int64_t f5f8_38 = f5_2 * (int64_t) f8_19; + int64_t f5f9_76 = f5_2 * (int64_t) f9_38; + int64_t f6f6_19 = f6 * (int64_t) f6_19; + int64_t f6f7_38 = f6 * (int64_t) f7_38; + int64_t f6f8_38 = f6_2 * (int64_t) f8_19; + int64_t f6f9_38 = f6 * (int64_t) f9_38; + int64_t f7f7_38 = f7 * (int64_t) f7_38; + int64_t f7f8_38 = f7_2 * (int64_t) f8_19; + int64_t f7f9_76 = f7_2 * (int64_t) f9_38; + int64_t f8f8_19 = f8 * (int64_t) f8_19; + int64_t f8f9_38 = f8 * (int64_t) f9_38; + int64_t f9f9_38 = f9 * (int64_t) f9_38; + int64_t h0 = f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38; + int64_t h1 = f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38; + int64_t h2 = f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19; + int64_t h3 = f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38; + int64_t h4 = f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38; + int64_t h5 = f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38; + int64_t h6 = f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19; + int64_t h7 = f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38; + int64_t h8 = f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38; + int64_t h9 = f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + h0 += h0; + h1 += h1; + h2 += h2; + h3 += h3; + h4 += h4; + h5 += h5; + h6 += h6; + h7 += h7; + h8 += h8; + h9 += h9; + carry0 = (h0 + (int64_t) (1 << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + carry4 = (h4 + (int64_t) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry1 = (h1 + (int64_t) (1 << 24)) >> 25; + h2 += carry1; + h1 -= carry1 << 25; + carry5 = (h5 + (int64_t) (1 << 24)) >> 25; + h6 += carry5; + h5 -= carry5 << 25; + carry2 = (h2 + (int64_t) (1 << 25)) >> 26; + h3 += carry2; + h2 -= carry2 << 26; + carry6 = (h6 + (int64_t) (1 << 25)) >> 26; + h7 += carry6; + h6 -= carry6 << 26; + carry3 = (h3 + (int64_t) (1 << 24)) >> 25; + h4 += carry3; + h3 -= carry3 << 25; + carry7 = (h7 + (int64_t) (1 << 24)) >> 25; + h8 += carry7; + h7 -= carry7 << 25; + carry4 = (h4 + (int64_t) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry8 = (h8 + (int64_t) (1 << 25)) >> 26; + h9 += carry8; + h8 -= carry8 << 26; + carry9 = (h9 + (int64_t) (1 << 24)) >> 25; + h0 += carry9 * 19; + h9 -= carry9 << 25; + carry0 = (h0 + (int64_t) (1 << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + h[0] = (int32_t) h0; + h[1] = (int32_t) h1; + h[2] = (int32_t) h2; + h[3] = (int32_t) h3; + h[4] = (int32_t) h4; + h[5] = (int32_t) h5; + h[6] = (int32_t) h6; + h[7] = (int32_t) h7; + h[8] = (int32_t) h8; + h[9] = (int32_t) h9; +} + + +/* +h = f - g +Can overlap h with f or g. + +Preconditions: + |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + +Postconditions: + |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +*/ + +void fe_sub(fe h, const fe f, const fe g) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t g0 = g[0]; + int32_t g1 = g[1]; + int32_t g2 = g[2]; + int32_t g3 = g[3]; + int32_t g4 = g[4]; + int32_t g5 = g[5]; + int32_t g6 = g[6]; + int32_t g7 = g[7]; + int32_t g8 = g[8]; + int32_t g9 = g[9]; + int32_t h0 = f0 - g0; + int32_t h1 = f1 - g1; + int32_t h2 = f2 - g2; + int32_t h3 = f3 - g3; + int32_t h4 = f4 - g4; + int32_t h5 = f5 - g5; + int32_t h6 = f6 - g6; + int32_t h7 = f7 - g7; + int32_t h8 = f8 - g8; + int32_t h9 = f9 - g9; + + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + + + +/* +Preconditions: + |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. + +Write p=2^255-19; q=floor(h/p). +Basic claim: q = floor(2^(-255)(h + 19 2^(-25)h9 + 2^(-1))). + +Proof: + Have |h|<=p so |q|<=1 so |19^2 2^(-255) q|<1/4. + Also have |h-2^230 h9|<2^231 so |19 2^(-255)(h-2^230 h9)|<1/4. + + Write y=2^(-1)-19^2 2^(-255)q-19 2^(-255)(h-2^230 h9). + Then 0> 25; + q = (h0 + q) >> 26; + q = (h1 + q) >> 25; + q = (h2 + q) >> 26; + q = (h3 + q) >> 25; + q = (h4 + q) >> 26; + q = (h5 + q) >> 25; + q = (h6 + q) >> 26; + q = (h7 + q) >> 25; + q = (h8 + q) >> 26; + q = (h9 + q) >> 25; + /* Goal: Output h-(2^255-19)q, which is between 0 and 2^255-20. */ + h0 += 19 * q; + /* Goal: Output h-2^255 q, which is between 0 and 2^255-20. */ + carry0 = h0 >> 26; + h1 += carry0; + h0 -= carry0 << 26; + carry1 = h1 >> 25; + h2 += carry1; + h1 -= carry1 << 25; + carry2 = h2 >> 26; + h3 += carry2; + h2 -= carry2 << 26; + carry3 = h3 >> 25; + h4 += carry3; + h3 -= carry3 << 25; + carry4 = h4 >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry5 = h5 >> 25; + h6 += carry5; + h5 -= carry5 << 25; + carry6 = h6 >> 26; + h7 += carry6; + h6 -= carry6 << 26; + carry7 = h7 >> 25; + h8 += carry7; + h7 -= carry7 << 25; + carry8 = h8 >> 26; + h9 += carry8; + h8 -= carry8 << 26; + carry9 = h9 >> 25; + h9 -= carry9 << 25; + + /* h10 = carry9 */ + /* + Goal: Output h0+...+2^255 h10-2^255 q, which is between 0 and 2^255-20. + Have h0+...+2^230 h9 between 0 and 2^255-1; + evidently 2^255 h10-2^255 q = 0. + Goal: Output h0+...+2^230 h9. + */ + s[0] = (unsigned char) (h0 >> 0); + s[1] = (unsigned char) (h0 >> 8); + s[2] = (unsigned char) (h0 >> 16); + s[3] = (unsigned char) ((h0 >> 24) | (h1 << 2)); + s[4] = (unsigned char) (h1 >> 6); + s[5] = (unsigned char) (h1 >> 14); + s[6] = (unsigned char) ((h1 >> 22) | (h2 << 3)); + s[7] = (unsigned char) (h2 >> 5); + s[8] = (unsigned char) (h2 >> 13); + s[9] = (unsigned char) ((h2 >> 21) | (h3 << 5)); + s[10] = (unsigned char) (h3 >> 3); + s[11] = (unsigned char) (h3 >> 11); + s[12] = (unsigned char) ((h3 >> 19) | (h4 << 6)); + s[13] = (unsigned char) (h4 >> 2); + s[14] = (unsigned char) (h4 >> 10); + s[15] = (unsigned char) (h4 >> 18); + s[16] = (unsigned char) (h5 >> 0); + s[17] = (unsigned char) (h5 >> 8); + s[18] = (unsigned char) (h5 >> 16); + s[19] = (unsigned char) ((h5 >> 24) | (h6 << 1)); + s[20] = (unsigned char) (h6 >> 7); + s[21] = (unsigned char) (h6 >> 15); + s[22] = (unsigned char) ((h6 >> 23) | (h7 << 3)); + s[23] = (unsigned char) (h7 >> 5); + s[24] = (unsigned char) (h7 >> 13); + s[25] = (unsigned char) ((h7 >> 21) | (h8 << 4)); + s[26] = (unsigned char) (h8 >> 4); + s[27] = (unsigned char) (h8 >> 12); + s[28] = (unsigned char) ((h8 >> 20) | (h9 << 6)); + s[29] = (unsigned char) (h9 >> 2); + s[30] = (unsigned char) (h9 >> 10); + s[31] = (unsigned char) (h9 >> 18); +} diff --git a/src/ed25519/fe.h b/src/ed25519/fe.h new file mode 100644 index 0000000..b4b62d2 --- /dev/null +++ b/src/ed25519/fe.h @@ -0,0 +1,41 @@ +#ifndef FE_H +#define FE_H + +#include "fixedint.h" + + +/* + fe means field element. + Here the field is \Z/(2^255-19). + An element t, entries t[0]...t[9], represents the integer + t[0]+2^26 t[1]+2^51 t[2]+2^77 t[3]+2^102 t[4]+...+2^230 t[9]. + Bounds on each t[i] vary depending on context. +*/ + + +typedef int32_t fe[10]; + + +void fe_0(fe h); +void fe_1(fe h); + +void fe_frombytes(fe h, const unsigned char *s); +void fe_tobytes(unsigned char *s, const fe h); + +void fe_copy(fe h, const fe f); +int fe_isnegative(const fe f); +int fe_isnonzero(const fe f); +void fe_cmov(fe f, const fe g, unsigned int b); +void fe_cswap(fe f, fe g, unsigned int b); + +void fe_neg(fe h, const fe f); +void fe_add(fe h, const fe f, const fe g); +void fe_invert(fe out, const fe z); +void fe_sq(fe h, const fe f); +void fe_sq2(fe h, const fe f); +void fe_mul(fe h, const fe f, const fe g); +void fe_mul121666(fe h, fe f); +void fe_pow22523(fe out, const fe z); +void fe_sub(fe h, const fe f, const fe g); + +#endif diff --git a/src/ed25519/fixedint.h b/src/ed25519/fixedint.h new file mode 100644 index 0000000..d03e4bd --- /dev/null +++ b/src/ed25519/fixedint.h @@ -0,0 +1,70 @@ +/* + Portable header to provide the 32 and 64 bits type. + + Not a compatible replacement for , do not blindly use it as such. +*/ + +#if ((defined(__STDC__) && __STDC__ && __STDC_VERSION__ >= 199901L) || (defined(__WATCOMC__) && (defined(_STDINT_H_INCLUDED) || __WATCOMC__ >= 1250)) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_) || defined(__UINT_FAST64_TYPE__)) )) && !defined(FIXEDINT_H_INCLUDED) + #include + #define FIXEDINT_H_INCLUDED + + #if defined(__WATCOMC__) && __WATCOMC__ >= 1250 && !defined(UINT64_C) + #include + #define UINT64_C(x) (x + (UINT64_MAX - UINT64_MAX)) + #endif +#endif + + +#ifndef FIXEDINT_H_INCLUDED + #define FIXEDINT_H_INCLUDED + + /* (u)int32_t */ + #ifndef uint32_t + #if (ULONG_MAX == 0xffffffffUL) + typedef unsigned long uint32_t; + #elif (UINT_MAX == 0xffffffffUL) + typedef unsigned int uint32_t; + #elif (USHRT_MAX == 0xffffffffUL) + typedef unsigned short uint32_t; + #endif + #endif + + + #ifndef int32_t + #if (LONG_MAX == 0x7fffffffL) + typedef signed long int32_t; + #elif (INT_MAX == 0x7fffffffL) + typedef signed int int32_t; + #elif (SHRT_MAX == 0x7fffffffL) + typedef signed short int32_t; + #endif + #endif + + + /* (u)int64_t */ + #if (defined(__STDC__) && defined(__STDC_VERSION__) && __STDC__ && __STDC_VERSION__ >= 199901L) + typedef long long int64_t; + typedef unsigned long long uint64_t; + + #define UINT64_C(v) v ##ULL + #define INT64_C(v) v ##LL + #elif defined(__GNUC__) + __extension__ typedef long long int64_t; + __extension__ typedef unsigned long long uint64_t; + + #define UINT64_C(v) v ##ULL + #define INT64_C(v) v ##LL + #elif defined(__MWERKS__) || defined(__SUNPRO_C) || defined(__SUNPRO_CC) || defined(__APPLE_CC__) || defined(_LONG_LONG) || defined(_CRAYC) + typedef long long int64_t; + typedef unsigned long long uint64_t; + + #define UINT64_C(v) v ##ULL + #define INT64_C(v) v ##LL + #elif (defined(__WATCOMC__) && defined(__WATCOM_INT64__)) || (defined(_MSC_VER) && _INTEGRAL_MAX_BITS >= 64) || (defined(__BORLANDC__) && __BORLANDC__ > 0x460) || defined(__alpha) || defined(__DECC) + typedef __int64 int64_t; + typedef unsigned __int64 uint64_t; + + #define UINT64_C(v) v ##UI64 + #define INT64_C(v) v ##I64 + #endif +#endif diff --git a/src/ed25519/ge.c b/src/ed25519/ge.c new file mode 100644 index 0000000..3c342b1 --- /dev/null +++ b/src/ed25519/ge.c @@ -0,0 +1,467 @@ +#include "ge.h" +#include "precomp_data.h" + + +/* +r = p + q +*/ + +void ge_add(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) { + fe t0; + fe_add(r->X, p->Y, p->X); + fe_sub(r->Y, p->Y, p->X); + fe_mul(r->Z, r->X, q->YplusX); + fe_mul(r->Y, r->Y, q->YminusX); + fe_mul(r->T, q->T2d, p->T); + fe_mul(r->X, p->Z, q->Z); + fe_add(t0, r->X, r->X); + fe_sub(r->X, r->Z, r->Y); + fe_add(r->Y, r->Z, r->Y); + fe_add(r->Z, t0, r->T); + fe_sub(r->T, t0, r->T); +} + + +static void slide(signed char *r, const unsigned char *a) { + int i; + int b; + int k; + + for (i = 0; i < 256; ++i) { + r[i] = 1 & (a[i >> 3] >> (i & 7)); + } + + for (i = 0; i < 256; ++i) + if (r[i]) { + for (b = 1; b <= 6 && i + b < 256; ++b) { + if (r[i + b]) { + if (r[i] + (r[i + b] << b) <= 15) { + r[i] += r[i + b] << b; + r[i + b] = 0; + } else if (r[i] - (r[i + b] << b) >= -15) { + r[i] -= r[i + b] << b; + + for (k = i + b; k < 256; ++k) { + if (!r[k]) { + r[k] = 1; + break; + } + + r[k] = 0; + } + } else { + break; + } + } + } + } +} + +/* +r = a * A + b * B +where a = a[0]+256*a[1]+...+256^31 a[31]. +and b = b[0]+256*b[1]+...+256^31 b[31]. +B is the Ed25519 base point (x,4/5) with x positive. +*/ + +void ge_double_scalarmult_vartime(ge_p2 *r, const unsigned char *a, const ge_p3 *A, const unsigned char *b) { + signed char aslide[256]; + signed char bslide[256]; + ge_cached Ai[8]; /* A,3A,5A,7A,9A,11A,13A,15A */ + ge_p1p1 t; + ge_p3 u; + ge_p3 A2; + int i; + slide(aslide, a); + slide(bslide, b); + ge_p3_to_cached(&Ai[0], A); + ge_p3_dbl(&t, A); + ge_p1p1_to_p3(&A2, &t); + ge_add(&t, &A2, &Ai[0]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[1], &u); + ge_add(&t, &A2, &Ai[1]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[2], &u); + ge_add(&t, &A2, &Ai[2]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[3], &u); + ge_add(&t, &A2, &Ai[3]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[4], &u); + ge_add(&t, &A2, &Ai[4]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[5], &u); + ge_add(&t, &A2, &Ai[5]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[6], &u); + ge_add(&t, &A2, &Ai[6]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[7], &u); + ge_p2_0(r); + + for (i = 255; i >= 0; --i) { + if (aslide[i] || bslide[i]) { + break; + } + } + + for (; i >= 0; --i) { + ge_p2_dbl(&t, r); + + if (aslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_add(&t, &u, &Ai[aslide[i] / 2]); + } else if (aslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_sub(&t, &u, &Ai[(-aslide[i]) / 2]); + } + + if (bslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_madd(&t, &u, &Bi[bslide[i] / 2]); + } else if (bslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_msub(&t, &u, &Bi[(-bslide[i]) / 2]); + } + + ge_p1p1_to_p2(r, &t); + } +} + + +static const fe d = { + -10913610, 13857413, -15372611, 6949391, 114729, -8787816, -6275908, -3247719, -18696448, -12055116 +}; + +static const fe sqrtm1 = { + -32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482 +}; + +int ge_frombytes_negate_vartime(ge_p3 *h, const unsigned char *s) { + fe u; + fe v; + fe v3; + fe vxx; + fe check; + fe_frombytes(h->Y, s); + fe_1(h->Z); + fe_sq(u, h->Y); + fe_mul(v, u, d); + fe_sub(u, u, h->Z); /* u = y^2-1 */ + fe_add(v, v, h->Z); /* v = dy^2+1 */ + fe_sq(v3, v); + fe_mul(v3, v3, v); /* v3 = v^3 */ + fe_sq(h->X, v3); + fe_mul(h->X, h->X, v); + fe_mul(h->X, h->X, u); /* x = uv^7 */ + fe_pow22523(h->X, h->X); /* x = (uv^7)^((q-5)/8) */ + fe_mul(h->X, h->X, v3); + fe_mul(h->X, h->X, u); /* x = uv^3(uv^7)^((q-5)/8) */ + fe_sq(vxx, h->X); + fe_mul(vxx, vxx, v); + fe_sub(check, vxx, u); /* vx^2-u */ + + if (fe_isnonzero(check)) { + fe_add(check, vxx, u); /* vx^2+u */ + + if (fe_isnonzero(check)) { + return -1; + } + + fe_mul(h->X, h->X, sqrtm1); + } + + if (fe_isnegative(h->X) == (s[31] >> 7)) { + fe_neg(h->X, h->X); + } + + fe_mul(h->T, h->X, h->Y); + return 0; +} + + +/* +r = p + q +*/ + +void ge_madd(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q) { + fe t0; + fe_add(r->X, p->Y, p->X); + fe_sub(r->Y, p->Y, p->X); + fe_mul(r->Z, r->X, q->yplusx); + fe_mul(r->Y, r->Y, q->yminusx); + fe_mul(r->T, q->xy2d, p->T); + fe_add(t0, p->Z, p->Z); + fe_sub(r->X, r->Z, r->Y); + fe_add(r->Y, r->Z, r->Y); + fe_add(r->Z, t0, r->T); + fe_sub(r->T, t0, r->T); +} + + +/* +r = p - q +*/ + +void ge_msub(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q) { + fe t0; + + fe_add(r->X, p->Y, p->X); + fe_sub(r->Y, p->Y, p->X); + fe_mul(r->Z, r->X, q->yminusx); + fe_mul(r->Y, r->Y, q->yplusx); + fe_mul(r->T, q->xy2d, p->T); + fe_add(t0, p->Z, p->Z); + fe_sub(r->X, r->Z, r->Y); + fe_add(r->Y, r->Z, r->Y); + fe_sub(r->Z, t0, r->T); + fe_add(r->T, t0, r->T); +} + + +/* +r = p +*/ + +void ge_p1p1_to_p2(ge_p2 *r, const ge_p1p1 *p) { + fe_mul(r->X, p->X, p->T); + fe_mul(r->Y, p->Y, p->Z); + fe_mul(r->Z, p->Z, p->T); +} + + + +/* +r = p +*/ + +void ge_p1p1_to_p3(ge_p3 *r, const ge_p1p1 *p) { + fe_mul(r->X, p->X, p->T); + fe_mul(r->Y, p->Y, p->Z); + fe_mul(r->Z, p->Z, p->T); + fe_mul(r->T, p->X, p->Y); +} + + +void ge_p2_0(ge_p2 *h) { + fe_0(h->X); + fe_1(h->Y); + fe_1(h->Z); +} + + + +/* +r = 2 * p +*/ + +void ge_p2_dbl(ge_p1p1 *r, const ge_p2 *p) { + fe t0; + + fe_sq(r->X, p->X); + fe_sq(r->Z, p->Y); + fe_sq2(r->T, p->Z); + fe_add(r->Y, p->X, p->Y); + fe_sq(t0, r->Y); + fe_add(r->Y, r->Z, r->X); + fe_sub(r->Z, r->Z, r->X); + fe_sub(r->X, t0, r->Y); + fe_sub(r->T, r->T, r->Z); +} + + +void ge_p3_0(ge_p3 *h) { + fe_0(h->X); + fe_1(h->Y); + fe_1(h->Z); + fe_0(h->T); +} + + +/* +r = 2 * p +*/ + +void ge_p3_dbl(ge_p1p1 *r, const ge_p3 *p) { + ge_p2 q; + ge_p3_to_p2(&q, p); + ge_p2_dbl(r, &q); +} + + + +/* +r = p +*/ + +static const fe d2 = { + -21827239, -5839606, -30745221, 13898782, 229458, 15978800, -12551817, -6495438, 29715968, 9444199 +}; + +void ge_p3_to_cached(ge_cached *r, const ge_p3 *p) { + fe_add(r->YplusX, p->Y, p->X); + fe_sub(r->YminusX, p->Y, p->X); + fe_copy(r->Z, p->Z); + fe_mul(r->T2d, p->T, d2); +} + + +/* +r = p +*/ + +void ge_p3_to_p2(ge_p2 *r, const ge_p3 *p) { + fe_copy(r->X, p->X); + fe_copy(r->Y, p->Y); + fe_copy(r->Z, p->Z); +} + + +void ge_p3_tobytes(unsigned char *s, const ge_p3 *h) { + fe recip; + fe x; + fe y; + fe_invert(recip, h->Z); + fe_mul(x, h->X, recip); + fe_mul(y, h->Y, recip); + fe_tobytes(s, y); + s[31] ^= fe_isnegative(x) << 7; +} + + +static unsigned char equal(signed char b, signed char c) { + unsigned char ub = b; + unsigned char uc = c; + unsigned char x = ub ^ uc; /* 0: yes; 1..255: no */ + uint64_t y = x; /* 0: yes; 1..255: no */ + y -= 1; /* large: yes; 0..254: no */ + y >>= 63; /* 1: yes; 0: no */ + return (unsigned char) y; +} + +static unsigned char negative(signed char b) { + uint64_t x = b; /* 18446744073709551361..18446744073709551615: yes; 0..255: no */ + x >>= 63; /* 1: yes; 0: no */ + return (unsigned char) x; +} + +static void cmov(ge_precomp *t, ge_precomp *u, unsigned char b) { + fe_cmov(t->yplusx, u->yplusx, b); + fe_cmov(t->yminusx, u->yminusx, b); + fe_cmov(t->xy2d, u->xy2d, b); +} + + +static void select(ge_precomp *t, int pos, signed char b) { + ge_precomp minust; + unsigned char bnegative = negative(b); + unsigned char babs = b - (((-bnegative) & b) << 1); + fe_1(t->yplusx); + fe_1(t->yminusx); + fe_0(t->xy2d); + cmov(t, &base[pos][0], equal(babs, 1)); + cmov(t, &base[pos][1], equal(babs, 2)); + cmov(t, &base[pos][2], equal(babs, 3)); + cmov(t, &base[pos][3], equal(babs, 4)); + cmov(t, &base[pos][4], equal(babs, 5)); + cmov(t, &base[pos][5], equal(babs, 6)); + cmov(t, &base[pos][6], equal(babs, 7)); + cmov(t, &base[pos][7], equal(babs, 8)); + fe_copy(minust.yplusx, t->yminusx); + fe_copy(minust.yminusx, t->yplusx); + fe_neg(minust.xy2d, t->xy2d); + cmov(t, &minust, bnegative); +} + +/* +h = a * B +where a = a[0]+256*a[1]+...+256^31 a[31] +B is the Ed25519 base point (x,4/5) with x positive. + +Preconditions: + a[31] <= 127 +*/ + +void ge_scalarmult_base(ge_p3 *h, const unsigned char *a) { + signed char e[64]; + signed char carry; + ge_p1p1 r; + ge_p2 s; + ge_precomp t; + int i; + + for (i = 0; i < 32; ++i) { + e[2 * i + 0] = (a[i] >> 0) & 15; + e[2 * i + 1] = (a[i] >> 4) & 15; + } + + /* each e[i] is between 0 and 15 */ + /* e[63] is between 0 and 7 */ + carry = 0; + + for (i = 0; i < 63; ++i) { + e[i] += carry; + carry = e[i] + 8; + carry >>= 4; + e[i] -= carry << 4; + } + + e[63] += carry; + /* each e[i] is between -8 and 8 */ + ge_p3_0(h); + + for (i = 1; i < 64; i += 2) { + select(&t, i / 2, e[i]); + ge_madd(&r, h, &t); + ge_p1p1_to_p3(h, &r); + } + + ge_p3_dbl(&r, h); + ge_p1p1_to_p2(&s, &r); + ge_p2_dbl(&r, &s); + ge_p1p1_to_p2(&s, &r); + ge_p2_dbl(&r, &s); + ge_p1p1_to_p2(&s, &r); + ge_p2_dbl(&r, &s); + ge_p1p1_to_p3(h, &r); + + for (i = 0; i < 64; i += 2) { + select(&t, i / 2, e[i]); + ge_madd(&r, h, &t); + ge_p1p1_to_p3(h, &r); + } +} + + +/* +r = p - q +*/ + +void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) { + fe t0; + + fe_add(r->X, p->Y, p->X); + fe_sub(r->Y, p->Y, p->X); + fe_mul(r->Z, r->X, q->YminusX); + fe_mul(r->Y, r->Y, q->YplusX); + fe_mul(r->T, q->T2d, p->T); + fe_mul(r->X, p->Z, q->Z); + fe_add(t0, r->X, r->X); + fe_sub(r->X, r->Z, r->Y); + fe_add(r->Y, r->Z, r->Y); + fe_sub(r->Z, t0, r->T); + fe_add(r->T, t0, r->T); +} + + +void ge_tobytes(unsigned char *s, const ge_p2 *h) { + fe recip; + fe x; + fe y; + fe_invert(recip, h->Z); + fe_mul(x, h->X, recip); + fe_mul(y, h->Y, recip); + fe_tobytes(s, y); + s[31] ^= fe_isnegative(x) << 7; +} diff --git a/src/ed25519/ge.h b/src/ed25519/ge.h new file mode 100644 index 0000000..17fde2d --- /dev/null +++ b/src/ed25519/ge.h @@ -0,0 +1,74 @@ +#ifndef GE_H +#define GE_H + +#include "fe.h" + + +/* +ge means group element. + +Here the group is the set of pairs (x,y) of field elements (see fe.h) +satisfying -x^2 + y^2 = 1 + d x^2y^2 +where d = -121665/121666. + +Representations: + ge_p2 (projective): (X:Y:Z) satisfying x=X/Z, y=Y/Z + ge_p3 (extended): (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT + ge_p1p1 (completed): ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T + ge_precomp (Duif): (y+x,y-x,2dxy) +*/ + +typedef struct { + fe X; + fe Y; + fe Z; +} ge_p2; + +typedef struct { + fe X; + fe Y; + fe Z; + fe T; +} ge_p3; + +typedef struct { + fe X; + fe Y; + fe Z; + fe T; +} ge_p1p1; + +typedef struct { + fe yplusx; + fe yminusx; + fe xy2d; +} ge_precomp; + +typedef struct { + fe YplusX; + fe YminusX; + fe Z; + fe T2d; +} ge_cached; + +void ge_p3_tobytes(unsigned char *s, const ge_p3 *h); +void ge_tobytes(unsigned char *s, const ge_p2 *h); +int ge_frombytes_negate_vartime(ge_p3 *h, const unsigned char *s); + +void ge_add(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q); +void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q); +void ge_double_scalarmult_vartime(ge_p2 *r, const unsigned char *a, const ge_p3 *A, const unsigned char *b); +void ge_madd(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q); +void ge_msub(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q); +void ge_scalarmult_base(ge_p3 *h, const unsigned char *a); + +void ge_p1p1_to_p2(ge_p2 *r, const ge_p1p1 *p); +void ge_p1p1_to_p3(ge_p3 *r, const ge_p1p1 *p); +void ge_p2_0(ge_p2 *h); +void ge_p2_dbl(ge_p1p1 *r, const ge_p2 *p); +void ge_p3_0(ge_p3 *h); +void ge_p3_dbl(ge_p1p1 *r, const ge_p3 *p); +void ge_p3_to_cached(ge_cached *r, const ge_p3 *p); +void ge_p3_to_p2(ge_p2 *r, const ge_p3 *p); + +#endif diff --git a/src/ed25519/key_exchange.c b/src/ed25519/key_exchange.c new file mode 100644 index 0000000..abd75da --- /dev/null +++ b/src/ed25519/key_exchange.c @@ -0,0 +1,79 @@ +#include "ed25519.h" +#include "fe.h" + +void ed25519_key_exchange(unsigned char *shared_secret, const unsigned char *public_key, const unsigned char *private_key) { + unsigned char e[32]; + unsigned int i; + + fe x1; + fe x2; + fe z2; + fe x3; + fe z3; + fe tmp0; + fe tmp1; + + int pos; + unsigned int swap; + unsigned int b; + + /* copy the private key and make sure it's valid */ + for (i = 0; i < 32; ++i) { + e[i] = private_key[i]; + } + + e[0] &= 248; + e[31] &= 63; + e[31] |= 64; + + /* unpack the public key and convert edwards to montgomery */ + /* due to CodesInChaos: montgomeryX = (edwardsY + 1)*inverse(1 - edwardsY) mod p */ + fe_frombytes(x1, public_key); + fe_1(tmp1); + fe_add(tmp0, x1, tmp1); + fe_sub(tmp1, tmp1, x1); + fe_invert(tmp1, tmp1); + fe_mul(x1, tmp0, tmp1); + + fe_1(x2); + fe_0(z2); + fe_copy(x3, x1); + fe_1(z3); + + swap = 0; + for (pos = 254; pos >= 0; --pos) { + b = e[pos / 8] >> (pos & 7); + b &= 1; + swap ^= b; + fe_cswap(x2, x3, swap); + fe_cswap(z2, z3, swap); + swap = b; + + /* from montgomery.h */ + fe_sub(tmp0, x3, z3); + fe_sub(tmp1, x2, z2); + fe_add(x2, x2, z2); + fe_add(z2, x3, z3); + fe_mul(z3, tmp0, x2); + fe_mul(z2, z2, tmp1); + fe_sq(tmp0, tmp1); + fe_sq(tmp1, x2); + fe_add(x3, z3, z2); + fe_sub(z2, z3, z2); + fe_mul(x2, tmp1, tmp0); + fe_sub(tmp1, tmp1, tmp0); + fe_sq(z2, z2); + fe_mul121666(z3, tmp1); + fe_sq(x3, x3); + fe_add(tmp0, tmp0, z3); + fe_mul(z3, x1, z2); + fe_mul(z2, tmp1, tmp0); + } + + fe_cswap(x2, x3, swap); + fe_cswap(z2, z3, swap); + + fe_invert(z2, z2); + fe_mul(x2, x2, z2); + fe_tobytes(shared_secret, x2); +} diff --git a/src/ed25519/keypair.c b/src/ed25519/keypair.c new file mode 100644 index 0000000..dc1b8ec --- /dev/null +++ b/src/ed25519/keypair.c @@ -0,0 +1,16 @@ +#include "ed25519.h" +#include "sha512.h" +#include "ge.h" + + +void ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed) { + ge_p3 A; + + sha512(seed, 32, private_key); + private_key[0] &= 248; + private_key[31] &= 63; + private_key[31] |= 64; + + ge_scalarmult_base(&A, private_key); + ge_p3_tobytes(public_key, &A); +} diff --git a/src/ed25519/precomp_data.h b/src/ed25519/precomp_data.h new file mode 100644 index 0000000..ce59788 --- /dev/null +++ b/src/ed25519/precomp_data.h @@ -0,0 +1,1391 @@ +static ge_precomp Bi[8] = { + { + { 25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605 }, + { -12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378 }, + { -8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546 }, + }, + { + { 15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024 }, + { 16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574 }, + { 30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357 }, + }, + { + { 10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380 }, + { 4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306 }, + { 19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942 }, + }, + { + { 5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766 }, + { -30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701 }, + { 28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300 }, + }, + { + { -22518993, -6692182, 14201702, -8745502, -23510406, 8844726, 18474211, -1361450, -13062696, 13821877 }, + { -6455177, -7839871, 3374702, -4740862, -27098617, -10571707, 31655028, -7212327, 18853322, -14220951 }, + { 4566830, -12963868, -28974889, -12240689, -7602672, -2830569, -8514358, -10431137, 2207753, -3209784 }, + }, + { + { -25154831, -4185821, 29681144, 7868801, -6854661, -9423865, -12437364, -663000, -31111463, -16132436 }, + { 25576264, -2703214, 7349804, -11814844, 16472782, 9300885, 3844789, 15725684, 171356, 6466918 }, + { 23103977, 13316479, 9739013, -16149481, 817875, -15038942, 8965339, -14088058, -30714912, 16193877 }, + }, + { + { -33521811, 3180713, -2394130, 14003687, -16903474, -16270840, 17238398, 4729455, -18074513, 9256800 }, + { -25182317, -4174131, 32336398, 5036987, -21236817, 11360617, 22616405, 9761698, -19827198, 630305 }, + { -13720693, 2639453, -24237460, -7406481, 9494427, -5774029, -6554551, -15960994, -2449256, -14291300 }, + }, + { + { -3151181, -5046075, 9282714, 6866145, -31907062, -863023, -18940575, 15033784, 25105118, -7894876 }, + { -24326370, 15950226, -31801215, -14592823, -11662737, -5090925, 1573892, -2625887, 2198790, -15804619 }, + { -3099351, 10324967, -2241613, 7453183, -5446979, -2735503, -13812022, -16236442, -32461234, -12290683 }, + }, +}; + + +/* base[i][j] = (j+1)*256^i*B */ +static ge_precomp base[32][8] = { + { + { + { 25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605 }, + { -12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378 }, + { -8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546 }, + }, + { + { -12815894, -12976347, -21581243, 11784320, -25355658, -2750717, -11717903, -3814571, -358445, -10211303 }, + { -21703237, 6903825, 27185491, 6451973, -29577724, -9554005, -15616551, 11189268, -26829678, -5319081 }, + { 26966642, 11152617, 32442495, 15396054, 14353839, -12752335, -3128826, -9541118, -15472047, -4166697 }, + }, + { + { 15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024 }, + { 16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574 }, + { 30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357 }, + }, + { + { -17036878, 13921892, 10945806, -6033431, 27105052, -16084379, -28926210, 15006023, 3284568, -6276540 }, + { 23599295, -8306047, -11193664, -7687416, 13236774, 10506355, 7464579, 9656445, 13059162, 10374397 }, + { 7798556, 16710257, 3033922, 2874086, 28997861, 2835604, 32406664, -3839045, -641708, -101325 }, + }, + { + { 10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380 }, + { 4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306 }, + { 19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942 }, + }, + { + { -15371964, -12862754, 32573250, 4720197, -26436522, 5875511, -19188627, -15224819, -9818940, -12085777 }, + { -8549212, 109983, 15149363, 2178705, 22900618, 4543417, 3044240, -15689887, 1762328, 14866737 }, + { -18199695, -15951423, -10473290, 1707278, -17185920, 3916101, -28236412, 3959421, 27914454, 4383652 }, + }, + { + { 5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766 }, + { -30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701 }, + { 28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300 }, + }, + { + { 14499471, -2729599, -33191113, -4254652, 28494862, 14271267, 30290735, 10876454, -33154098, 2381726 }, + { -7195431, -2655363, -14730155, 462251, -27724326, 3941372, -6236617, 3696005, -32300832, 15351955 }, + { 27431194, 8222322, 16448760, -3907995, -18707002, 11938355, -32961401, -2970515, 29551813, 10109425 }, + }, + }, + { + { + { -13657040, -13155431, -31283750, 11777098, 21447386, 6519384, -2378284, -1627556, 10092783, -4764171 }, + { 27939166, 14210322, 4677035, 16277044, -22964462, -12398139, -32508754, 12005538, -17810127, 12803510 }, + { 17228999, -15661624, -1233527, 300140, -1224870, -11714777, 30364213, -9038194, 18016357, 4397660 }, + }, + { + { -10958843, -7690207, 4776341, -14954238, 27850028, -15602212, -26619106, 14544525, -17477504, 982639 }, + { 29253598, 15796703, -2863982, -9908884, 10057023, 3163536, 7332899, -4120128, -21047696, 9934963 }, + { 5793303, 16271923, -24131614, -10116404, 29188560, 1206517, -14747930, 4559895, -30123922, -10897950 }, + }, + { + { -27643952, -11493006, 16282657, -11036493, 28414021, -15012264, 24191034, 4541697, -13338309, 5500568 }, + { 12650548, -1497113, 9052871, 11355358, -17680037, -8400164, -17430592, 12264343, 10874051, 13524335 }, + { 25556948, -3045990, 714651, 2510400, 23394682, -10415330, 33119038, 5080568, -22528059, 5376628 }, + }, + { + { -26088264, -4011052, -17013699, -3537628, -6726793, 1920897, -22321305, -9447443, 4535768, 1569007 }, + { -2255422, 14606630, -21692440, -8039818, 28430649, 8775819, -30494562, 3044290, 31848280, 12543772 }, + { -22028579, 2943893, -31857513, 6777306, 13784462, -4292203, -27377195, -2062731, 7718482, 14474653 }, + }, + { + { 2385315, 2454213, -22631320, 46603, -4437935, -15680415, 656965, -7236665, 24316168, -5253567 }, + { 13741529, 10911568, -33233417, -8603737, -20177830, -1033297, 33040651, -13424532, -20729456, 8321686 }, + { 21060490, -2212744, 15712757, -4336099, 1639040, 10656336, 23845965, -11874838, -9984458, 608372 }, + }, + { + { -13672732, -15087586, -10889693, -7557059, -6036909, 11305547, 1123968, -6780577, 27229399, 23887 }, + { -23244140, -294205, -11744728, 14712571, -29465699, -2029617, 12797024, -6440308, -1633405, 16678954 }, + { -29500620, 4770662, -16054387, 14001338, 7830047, 9564805, -1508144, -4795045, -17169265, 4904953 }, + }, + { + { 24059557, 14617003, 19037157, -15039908, 19766093, -14906429, 5169211, 16191880, 2128236, -4326833 }, + { -16981152, 4124966, -8540610, -10653797, 30336522, -14105247, -29806336, 916033, -6882542, -2986532 }, + { -22630907, 12419372, -7134229, -7473371, -16478904, 16739175, 285431, 2763829, 15736322, 4143876 }, + }, + { + { 2379352, 11839345, -4110402, -5988665, 11274298, 794957, 212801, -14594663, 23527084, -16458268 }, + { 33431127, -11130478, -17838966, -15626900, 8909499, 8376530, -32625340, 4087881, -15188911, -14416214 }, + { 1767683, 7197987, -13205226, -2022635, -13091350, 448826, 5799055, 4357868, -4774191, -16323038 }, + }, + }, + { + { + { 6721966, 13833823, -23523388, -1551314, 26354293, -11863321, 23365147, -3949732, 7390890, 2759800 }, + { 4409041, 2052381, 23373853, 10530217, 7676779, -12885954, 21302353, -4264057, 1244380, -12919645 }, + { -4421239, 7169619, 4982368, -2957590, 30256825, -2777540, 14086413, 9208236, 15886429, 16489664 }, + }, + { + { 1996075, 10375649, 14346367, 13311202, -6874135, -16438411, -13693198, 398369, -30606455, -712933 }, + { -25307465, 9795880, -2777414, 14878809, -33531835, 14780363, 13348553, 12076947, -30836462, 5113182 }, + { -17770784, 11797796, 31950843, 13929123, -25888302, 12288344, -30341101, -7336386, 13847711, 5387222 }, + }, + { + { -18582163, -3416217, 17824843, -2340966, 22744343, -10442611, 8763061, 3617786, -19600662, 10370991 }, + { 20246567, -14369378, 22358229, -543712, 18507283, -10413996, 14554437, -8746092, 32232924, 16763880 }, + { 9648505, 10094563, 26416693, 14745928, -30374318, -6472621, 11094161, 15689506, 3140038, -16510092 }, + }, + { + { -16160072, 5472695, 31895588, 4744994, 8823515, 10365685, -27224800, 9448613, -28774454, 366295 }, + { 19153450, 11523972, -11096490, -6503142, -24647631, 5420647, 28344573, 8041113, 719605, 11671788 }, + { 8678025, 2694440, -6808014, 2517372, 4964326, 11152271, -15432916, -15266516, 27000813, -10195553 }, + }, + { + { -15157904, 7134312, 8639287, -2814877, -7235688, 10421742, 564065, 5336097, 6750977, -14521026 }, + { 11836410, -3979488, 26297894, 16080799, 23455045, 15735944, 1695823, -8819122, 8169720, 16220347 }, + { -18115838, 8653647, 17578566, -6092619, -8025777, -16012763, -11144307, -2627664, -5990708, -14166033 }, + }, + { + { -23308498, -10968312, 15213228, -10081214, -30853605, -11050004, 27884329, 2847284, 2655861, 1738395 }, + { -27537433, -14253021, -25336301, -8002780, -9370762, 8129821, 21651608, -3239336, -19087449, -11005278 }, + { 1533110, 3437855, 23735889, 459276, 29970501, 11335377, 26030092, 5821408, 10478196, 8544890 }, + }, + { + { 32173121, -16129311, 24896207, 3921497, 22579056, -3410854, 19270449, 12217473, 17789017, -3395995 }, + { -30552961, -2228401, -15578829, -10147201, 13243889, 517024, 15479401, -3853233, 30460520, 1052596 }, + { -11614875, 13323618, 32618793, 8175907, -15230173, 12596687, 27491595, -4612359, 3179268, -9478891 }, + }, + { + { 31947069, -14366651, -4640583, -15339921, -15125977, -6039709, -14756777, -16411740, 19072640, -9511060 }, + { 11685058, 11822410, 3158003, -13952594, 33402194, -4165066, 5977896, -5215017, 473099, 5040608 }, + { -20290863, 8198642, -27410132, 11602123, 1290375, -2799760, 28326862, 1721092, -19558642, -3131606 }, + }, + }, + { + { + { 7881532, 10687937, 7578723, 7738378, -18951012, -2553952, 21820786, 8076149, -27868496, 11538389 }, + { -19935666, 3899861, 18283497, -6801568, -15728660, -11249211, 8754525, 7446702, -5676054, 5797016 }, + { -11295600, -3793569, -15782110, -7964573, 12708869, -8456199, 2014099, -9050574, -2369172, -5877341 }, + }, + { + { -22472376, -11568741, -27682020, 1146375, 18956691, 16640559, 1192730, -3714199, 15123619, 10811505 }, + { 14352098, -3419715, -18942044, 10822655, 32750596, 4699007, -70363, 15776356, -28886779, -11974553 }, + { -28241164, -8072475, -4978962, -5315317, 29416931, 1847569, -20654173, -16484855, 4714547, -9600655 }, + }, + { + { 15200332, 8368572, 19679101, 15970074, -31872674, 1959451, 24611599, -4543832, -11745876, 12340220 }, + { 12876937, -10480056, 33134381, 6590940, -6307776, 14872440, 9613953, 8241152, 15370987, 9608631 }, + { -4143277, -12014408, 8446281, -391603, 4407738, 13629032, -7724868, 15866074, -28210621, -8814099 }, + }, + { + { 26660628, -15677655, 8393734, 358047, -7401291, 992988, -23904233, 858697, 20571223, 8420556 }, + { 14620715, 13067227, -15447274, 8264467, 14106269, 15080814, 33531827, 12516406, -21574435, -12476749 }, + { 236881, 10476226, 57258, -14677024, 6472998, 2466984, 17258519, 7256740, 8791136, 15069930 }, + }, + { + { 1276410, -9371918, 22949635, -16322807, -23493039, -5702186, 14711875, 4874229, -30663140, -2331391 }, + { 5855666, 4990204, -13711848, 7294284, -7804282, 1924647, -1423175, -7912378, -33069337, 9234253 }, + { 20590503, -9018988, 31529744, -7352666, -2706834, 10650548, 31559055, -11609587, 18979186, 13396066 }, + }, + { + { 24474287, 4968103, 22267082, 4407354, 24063882, -8325180, -18816887, 13594782, 33514650, 7021958 }, + { -11566906, -6565505, -21365085, 15928892, -26158305, 4315421, -25948728, -3916677, -21480480, 12868082 }, + { -28635013, 13504661, 19988037, -2132761, 21078225, 6443208, -21446107, 2244500, -12455797, -8089383 }, + }, + { + { -30595528, 13793479, -5852820, 319136, -25723172, -6263899, 33086546, 8957937, -15233648, 5540521 }, + { -11630176, -11503902, -8119500, -7643073, 2620056, 1022908, -23710744, -1568984, -16128528, -14962807 }, + { 23152971, 775386, 27395463, 14006635, -9701118, 4649512, 1689819, 892185, -11513277, -15205948 }, + }, + { + { 9770129, 9586738, 26496094, 4324120, 1556511, -3550024, 27453819, 4763127, -19179614, 5867134 }, + { -32765025, 1927590, 31726409, -4753295, 23962434, -16019500, 27846559, 5931263, -29749703, -16108455 }, + { 27461885, -2977536, 22380810, 1815854, -23033753, -3031938, 7283490, -15148073, -19526700, 7734629 }, + }, + }, + { + { + { -8010264, -9590817, -11120403, 6196038, 29344158, -13430885, 7585295, -3176626, 18549497, 15302069 }, + { -32658337, -6171222, -7672793, -11051681, 6258878, 13504381, 10458790, -6418461, -8872242, 8424746 }, + { 24687205, 8613276, -30667046, -3233545, 1863892, -1830544, 19206234, 7134917, -11284482, -828919 }, + }, + { + { 11334899, -9218022, 8025293, 12707519, 17523892, -10476071, 10243738, -14685461, -5066034, 16498837 }, + { 8911542, 6887158, -9584260, -6958590, 11145641, -9543680, 17303925, -14124238, 6536641, 10543906 }, + { -28946384, 15479763, -17466835, 568876, -1497683, 11223454, -2669190, -16625574, -27235709, 8876771 }, + }, + { + { -25742899, -12566864, -15649966, -846607, -33026686, -796288, -33481822, 15824474, -604426, -9039817 }, + { 10330056, 70051, 7957388, -9002667, 9764902, 15609756, 27698697, -4890037, 1657394, 3084098 }, + { 10477963, -7470260, 12119566, -13250805, 29016247, -5365589, 31280319, 14396151, -30233575, 15272409 }, + }, + { + { -12288309, 3169463, 28813183, 16658753, 25116432, -5630466, -25173957, -12636138, -25014757, 1950504 }, + { -26180358, 9489187, 11053416, -14746161, -31053720, 5825630, -8384306, -8767532, 15341279, 8373727 }, + { 28685821, 7759505, -14378516, -12002860, -31971820, 4079242, 298136, -10232602, -2878207, 15190420 }, + }, + { + { -32932876, 13806336, -14337485, -15794431, -24004620, 10940928, 8669718, 2742393, -26033313, -6875003 }, + { -1580388, -11729417, -25979658, -11445023, -17411874, -10912854, 9291594, -16247779, -12154742, 6048605 }, + { -30305315, 14843444, 1539301, 11864366, 20201677, 1900163, 13934231, 5128323, 11213262, 9168384 }, + }, + { + { -26280513, 11007847, 19408960, -940758, -18592965, -4328580, -5088060, -11105150, 20470157, -16398701 }, + { -23136053, 9282192, 14855179, -15390078, -7362815, -14408560, -22783952, 14461608, 14042978, 5230683 }, + { 29969567, -2741594, -16711867, -8552442, 9175486, -2468974, 21556951, 3506042, -5933891, -12449708 }, + }, + { + { -3144746, 8744661, 19704003, 4581278, -20430686, 6830683, -21284170, 8971513, -28539189, 15326563 }, + { -19464629, 10110288, -17262528, -3503892, -23500387, 1355669, -15523050, 15300988, -20514118, 9168260 }, + { -5353335, 4488613, -23803248, 16314347, 7780487, -15638939, -28948358, 9601605, 33087103, -9011387 }, + }, + { + { -19443170, -15512900, -20797467, -12445323, -29824447, 10229461, -27444329, -15000531, -5996870, 15664672 }, + { 23294591, -16632613, -22650781, -8470978, 27844204, 11461195, 13099750, -2460356, 18151676, 13417686 }, + { -24722913, -4176517, -31150679, 5988919, -26858785, 6685065, 1661597, -12551441, 15271676, -15452665 }, + }, + }, + { + { + { 11433042, -13228665, 8239631, -5279517, -1985436, -725718, -18698764, 2167544, -6921301, -13440182 }, + { -31436171, 15575146, 30436815, 12192228, -22463353, 9395379, -9917708, -8638997, 12215110, 12028277 }, + { 14098400, 6555944, 23007258, 5757252, -15427832, -12950502, 30123440, 4617780, -16900089, -655628 }, + }, + { + { -4026201, -15240835, 11893168, 13718664, -14809462, 1847385, -15819999, 10154009, 23973261, -12684474 }, + { -26531820, -3695990, -1908898, 2534301, -31870557, -16550355, 18341390, -11419951, 32013174, -10103539 }, + { -25479301, 10876443, -11771086, -14625140, -12369567, 1838104, 21911214, 6354752, 4425632, -837822 }, + }, + { + { -10433389, -14612966, 22229858, -3091047, -13191166, 776729, -17415375, -12020462, 4725005, 14044970 }, + { 19268650, -7304421, 1555349, 8692754, -21474059, -9910664, 6347390, -1411784, -19522291, -16109756 }, + { -24864089, 12986008, -10898878, -5558584, -11312371, -148526, 19541418, 8180106, 9282262, 10282508 }, + }, + { + { -26205082, 4428547, -8661196, -13194263, 4098402, -14165257, 15522535, 8372215, 5542595, -10702683 }, + { -10562541, 14895633, 26814552, -16673850, -17480754, -2489360, -2781891, 6993761, -18093885, 10114655 }, + { -20107055, -929418, 31422704, 10427861, -7110749, 6150669, -29091755, -11529146, 25953725, -106158 }, + }, + { + { -4234397, -8039292, -9119125, 3046000, 2101609, -12607294, 19390020, 6094296, -3315279, 12831125 }, + { -15998678, 7578152, 5310217, 14408357, -33548620, -224739, 31575954, 6326196, 7381791, -2421839 }, + { -20902779, 3296811, 24736065, -16328389, 18374254, 7318640, 6295303, 8082724, -15362489, 12339664 }, + }, + { + { 27724736, 2291157, 6088201, -14184798, 1792727, 5857634, 13848414, 15768922, 25091167, 14856294 }, + { -18866652, 8331043, 24373479, 8541013, -701998, -9269457, 12927300, -12695493, -22182473, -9012899 }, + { -11423429, -5421590, 11632845, 3405020, 30536730, -11674039, -27260765, 13866390, 30146206, 9142070 }, + }, + { + { 3924129, -15307516, -13817122, -10054960, 12291820, -668366, -27702774, 9326384, -8237858, 4171294 }, + { -15921940, 16037937, 6713787, 16606682, -21612135, 2790944, 26396185, 3731949, 345228, -5462949 }, + { -21327538, 13448259, 25284571, 1143661, 20614966, -8849387, 2031539, -12391231, -16253183, -13582083 }, + }, + { + { 31016211, -16722429, 26371392, -14451233, -5027349, 14854137, 17477601, 3842657, 28012650, -16405420 }, + { -5075835, 9368966, -8562079, -4600902, -15249953, 6970560, -9189873, 16292057, -8867157, 3507940 }, + { 29439664, 3537914, 23333589, 6997794, -17555561, -11018068, -15209202, -15051267, -9164929, 6580396 }, + }, + }, + { + { + { -12185861, -7679788, 16438269, 10826160, -8696817, -6235611, 17860444, -9273846, -2095802, 9304567 }, + { 20714564, -4336911, 29088195, 7406487, 11426967, -5095705, 14792667, -14608617, 5289421, -477127 }, + { -16665533, -10650790, -6160345, -13305760, 9192020, -1802462, 17271490, 12349094, 26939669, -3752294 }, + }, + { + { -12889898, 9373458, 31595848, 16374215, 21471720, 13221525, -27283495, -12348559, -3698806, 117887 }, + { 22263325, -6560050, 3984570, -11174646, -15114008, -566785, 28311253, 5358056, -23319780, 541964 }, + { 16259219, 3261970, 2309254, -15534474, -16885711, -4581916, 24134070, -16705829, -13337066, -13552195 }, + }, + { + { 9378160, -13140186, -22845982, -12745264, 28198281, -7244098, -2399684, -717351, 690426, 14876244 }, + { 24977353, -314384, -8223969, -13465086, 28432343, -1176353, -13068804, -12297348, -22380984, 6618999 }, + { -1538174, 11685646, 12944378, 13682314, -24389511, -14413193, 8044829, -13817328, 32239829, -5652762 }, + }, + { + { -18603066, 4762990, -926250, 8885304, -28412480, -3187315, 9781647, -10350059, 32779359, 5095274 }, + { -33008130, -5214506, -32264887, -3685216, 9460461, -9327423, -24601656, 14506724, 21639561, -2630236 }, + { -16400943, -13112215, 25239338, 15531969, 3987758, -4499318, -1289502, -6863535, 17874574, 558605 }, + }, + { + { -13600129, 10240081, 9171883, 16131053, -20869254, 9599700, 33499487, 5080151, 2085892, 5119761 }, + { -22205145, -2519528, -16381601, 414691, -25019550, 2170430, 30634760, -8363614, -31999993, -5759884 }, + { -6845704, 15791202, 8550074, -1312654, 29928809, -12092256, 27534430, -7192145, -22351378, 12961482 }, + }, + { + { -24492060, -9570771, 10368194, 11582341, -23397293, -2245287, 16533930, 8206996, -30194652, -5159638 }, + { -11121496, -3382234, 2307366, 6362031, -135455, 8868177, -16835630, 7031275, 7589640, 8945490 }, + { -32152748, 8917967, 6661220, -11677616, -1192060, -15793393, 7251489, -11182180, 24099109, -14456170 }, + }, + { + { 5019558, -7907470, 4244127, -14714356, -26933272, 6453165, -19118182, -13289025, -6231896, -10280736 }, + { 10853594, 10721687, 26480089, 5861829, -22995819, 1972175, -1866647, -10557898, -3363451, -6441124 }, + { -17002408, 5906790, 221599, -6563147, 7828208, -13248918, 24362661, -2008168, -13866408, 7421392 }, + }, + { + { 8139927, -6546497, 32257646, -5890546, 30375719, 1886181, -21175108, 15441252, 28826358, -4123029 }, + { 6267086, 9695052, 7709135, -16603597, -32869068, -1886135, 14795160, -7840124, 13746021, -1742048 }, + { 28584902, 7787108, -6732942, -15050729, 22846041, -7571236, -3181936, -363524, 4771362, -8419958 }, + }, + }, + { + { + { 24949256, 6376279, -27466481, -8174608, -18646154, -9930606, 33543569, -12141695, 3569627, 11342593 }, + { 26514989, 4740088, 27912651, 3697550, 19331575, -11472339, 6809886, 4608608, 7325975, -14801071 }, + { -11618399, -14554430, -24321212, 7655128, -1369274, 5214312, -27400540, 10258390, -17646694, -8186692 }, + }, + { + { 11431204, 15823007, 26570245, 14329124, 18029990, 4796082, -31446179, 15580664, 9280358, -3973687 }, + { -160783, -10326257, -22855316, -4304997, -20861367, -13621002, -32810901, -11181622, -15545091, 4387441 }, + { -20799378, 12194512, 3937617, -5805892, -27154820, 9340370, -24513992, 8548137, 20617071, -7482001 }, + }, + { + { -938825, -3930586, -8714311, 16124718, 24603125, -6225393, -13775352, -11875822, 24345683, 10325460 }, + { -19855277, -1568885, -22202708, 8714034, 14007766, 6928528, 16318175, -1010689, 4766743, 3552007 }, + { -21751364, -16730916, 1351763, -803421, -4009670, 3950935, 3217514, 14481909, 10988822, -3994762 }, + }, + { + { 15564307, -14311570, 3101243, 5684148, 30446780, -8051356, 12677127, -6505343, -8295852, 13296005 }, + { -9442290, 6624296, -30298964, -11913677, -4670981, -2057379, 31521204, 9614054, -30000824, 12074674 }, + { 4771191, -135239, 14290749, -13089852, 27992298, 14998318, -1413936, -1556716, 29832613, -16391035 }, + }, + { + { 7064884, -7541174, -19161962, -5067537, -18891269, -2912736, 25825242, 5293297, -27122660, 13101590 }, + { -2298563, 2439670, -7466610, 1719965, -27267541, -16328445, 32512469, -5317593, -30356070, -4190957 }, + { -30006540, 10162316, -33180176, 3981723, -16482138, -13070044, 14413974, 9515896, 19568978, 9628812 }, + }, + { + { 33053803, 199357, 15894591, 1583059, 27380243, -4580435, -17838894, -6106839, -6291786, 3437740 }, + { -18978877, 3884493, 19469877, 12726490, 15913552, 13614290, -22961733, 70104, 7463304, 4176122 }, + { -27124001, 10659917, 11482427, -16070381, 12771467, -6635117, -32719404, -5322751, 24216882, 5944158 }, + }, + { + { 8894125, 7450974, -2664149, -9765752, -28080517, -12389115, 19345746, 14680796, 11632993, 5847885 }, + { 26942781, -2315317, 9129564, -4906607, 26024105, 11769399, -11518837, 6367194, -9727230, 4782140 }, + { 19916461, -4828410, -22910704, -11414391, 25606324, -5972441, 33253853, 8220911, 6358847, -1873857 }, + }, + { + { 801428, -2081702, 16569428, 11065167, 29875704, 96627, 7908388, -4480480, -13538503, 1387155 }, + { 19646058, 5720633, -11416706, 12814209, 11607948, 12749789, 14147075, 15156355, -21866831, 11835260 }, + { 19299512, 1155910, 28703737, 14890794, 2925026, 7269399, 26121523, 15467869, -26560550, 5052483 }, + }, + }, + { + { + { -3017432, 10058206, 1980837, 3964243, 22160966, 12322533, -6431123, -12618185, 12228557, -7003677 }, + { 32944382, 14922211, -22844894, 5188528, 21913450, -8719943, 4001465, 13238564, -6114803, 8653815 }, + { 22865569, -4652735, 27603668, -12545395, 14348958, 8234005, 24808405, 5719875, 28483275, 2841751 }, + }, + { + { -16420968, -1113305, -327719, -12107856, 21886282, -15552774, -1887966, -315658, 19932058, -12739203 }, + { -11656086, 10087521, -8864888, -5536143, -19278573, -3055912, 3999228, 13239134, -4777469, -13910208 }, + { 1382174, -11694719, 17266790, 9194690, -13324356, 9720081, 20403944, 11284705, -14013818, 3093230 }, + }, + { + { 16650921, -11037932, -1064178, 1570629, -8329746, 7352753, -302424, 16271225, -24049421, -6691850 }, + { -21911077, -5927941, -4611316, -5560156, -31744103, -10785293, 24123614, 15193618, -21652117, -16739389 }, + { -9935934, -4289447, -25279823, 4372842, 2087473, 10399484, 31870908, 14690798, 17361620, 11864968 }, + }, + { + { -11307610, 6210372, 13206574, 5806320, -29017692, -13967200, -12331205, -7486601, -25578460, -16240689 }, + { 14668462, -12270235, 26039039, 15305210, 25515617, 4542480, 10453892, 6577524, 9145645, -6443880 }, + { 5974874, 3053895, -9433049, -10385191, -31865124, 3225009, -7972642, 3936128, -5652273, -3050304 }, + }, + { + { 30625386, -4729400, -25555961, -12792866, -20484575, 7695099, 17097188, -16303496, -27999779, 1803632 }, + { -3553091, 9865099, -5228566, 4272701, -5673832, -16689700, 14911344, 12196514, -21405489, 7047412 }, + { 20093277, 9920966, -11138194, -5343857, 13161587, 12044805, -32856851, 4124601, -32343828, -10257566 }, + }, + { + { -20788824, 14084654, -13531713, 7842147, 19119038, -13822605, 4752377, -8714640, -21679658, 2288038 }, + { -26819236, -3283715, 29965059, 3039786, -14473765, 2540457, 29457502, 14625692, -24819617, 12570232 }, + { -1063558, -11551823, 16920318, 12494842, 1278292, -5869109, -21159943, -3498680, -11974704, 4724943 }, + }, + { + { 17960970, -11775534, -4140968, -9702530, -8876562, -1410617, -12907383, -8659932, -29576300, 1903856 }, + { 23134274, -14279132, -10681997, -1611936, 20684485, 15770816, -12989750, 3190296, 26955097, 14109738 }, + { 15308788, 5320727, -30113809, -14318877, 22902008, 7767164, 29425325, -11277562, 31960942, 11934971 }, + }, + { + { -27395711, 8435796, 4109644, 12222639, -24627868, 14818669, 20638173, 4875028, 10491392, 1379718 }, + { -13159415, 9197841, 3875503, -8936108, -1383712, -5879801, 33518459, 16176658, 21432314, 12180697 }, + { -11787308, 11500838, 13787581, -13832590, -22430679, 10140205, 1465425, 12689540, -10301319, -13872883 }, + }, + }, + { + { + { 5414091, -15386041, -21007664, 9643570, 12834970, 1186149, -2622916, -1342231, 26128231, 6032912 }, + { -26337395, -13766162, 32496025, -13653919, 17847801, -12669156, 3604025, 8316894, -25875034, -10437358 }, + { 3296484, 6223048, 24680646, -12246460, -23052020, 5903205, -8862297, -4639164, 12376617, 3188849 }, + }, + { + { 29190488, -14659046, 27549113, -1183516, 3520066, -10697301, 32049515, -7309113, -16109234, -9852307 }, + { -14744486, -9309156, 735818, -598978, -20407687, -5057904, 25246078, -15795669, 18640741, -960977 }, + { -6928835, -16430795, 10361374, 5642961, 4910474, 12345252, -31638386, -494430, 10530747, 1053335 }, + }, + { + { -29265967, -14186805, -13538216, -12117373, -19457059, -10655384, -31462369, -2948985, 24018831, 15026644 }, + { -22592535, -3145277, -2289276, 5953843, -13440189, 9425631, 25310643, 13003497, -2314791, -15145616 }, + { -27419985, -603321, -8043984, -1669117, -26092265, 13987819, -27297622, 187899, -23166419, -2531735 }, + }, + { + { -21744398, -13810475, 1844840, 5021428, -10434399, -15911473, 9716667, 16266922, -5070217, 726099 }, + { 29370922, -6053998, 7334071, -15342259, 9385287, 2247707, -13661962, -4839461, 30007388, -15823341 }, + { -936379, 16086691, 23751945, -543318, -1167538, -5189036, 9137109, 730663, 9835848, 4555336 }, + }, + { + { -23376435, 1410446, -22253753, -12899614, 30867635, 15826977, 17693930, 544696, -11985298, 12422646 }, + { 31117226, -12215734, -13502838, 6561947, -9876867, -12757670, -5118685, -4096706, 29120153, 13924425 }, + { -17400879, -14233209, 19675799, -2734756, -11006962, -5858820, -9383939, -11317700, 7240931, -237388 }, + }, + { + { -31361739, -11346780, -15007447, -5856218, -22453340, -12152771, 1222336, 4389483, 3293637, -15551743 }, + { -16684801, -14444245, 11038544, 11054958, -13801175, -3338533, -24319580, 7733547, 12796905, -6335822 }, + { -8759414, -10817836, -25418864, 10783769, -30615557, -9746811, -28253339, 3647836, 3222231, -11160462 }, + }, + { + { 18606113, 1693100, -25448386, -15170272, 4112353, 10045021, 23603893, -2048234, -7550776, 2484985 }, + { 9255317, -3131197, -12156162, -1004256, 13098013, -9214866, 16377220, -2102812, -19802075, -3034702 }, + { -22729289, 7496160, -5742199, 11329249, 19991973, -3347502, -31718148, 9936966, -30097688, -10618797 }, + }, + { + { 21878590, -5001297, 4338336, 13643897, -3036865, 13160960, 19708896, 5415497, -7360503, -4109293 }, + { 27736861, 10103576, 12500508, 8502413, -3413016, -9633558, 10436918, -1550276, -23659143, -8132100 }, + { 19492550, -12104365, -29681976, -852630, -3208171, 12403437, 30066266, 8367329, 13243957, 8709688 }, + }, + }, + { + { + { 12015105, 2801261, 28198131, 10151021, 24818120, -4743133, -11194191, -5645734, 5150968, 7274186 }, + { 2831366, -12492146, 1478975, 6122054, 23825128, -12733586, 31097299, 6083058, 31021603, -9793610 }, + { -2529932, -2229646, 445613, 10720828, -13849527, -11505937, -23507731, 16354465, 15067285, -14147707 }, + }, + { + { 7840942, 14037873, -33364863, 15934016, -728213, -3642706, 21403988, 1057586, -19379462, -12403220 }, + { 915865, -16469274, 15608285, -8789130, -24357026, 6060030, -17371319, 8410997, -7220461, 16527025 }, + { 32922597, -556987, 20336074, -16184568, 10903705, -5384487, 16957574, 52992, 23834301, 6588044 }, + }, + { + { 32752030, 11232950, 3381995, -8714866, 22652988, -10744103, 17159699, 16689107, -20314580, -1305992 }, + { -4689649, 9166776, -25710296, -10847306, 11576752, 12733943, 7924251, -2752281, 1976123, -7249027 }, + { 21251222, 16309901, -2983015, -6783122, 30810597, 12967303, 156041, -3371252, 12331345, -8237197 }, + }, + { + { 8651614, -4477032, -16085636, -4996994, 13002507, 2950805, 29054427, -5106970, 10008136, -4667901 }, + { 31486080, 15114593, -14261250, 12951354, 14369431, -7387845, 16347321, -13662089, 8684155, -10532952 }, + { 19443825, 11385320, 24468943, -9659068, -23919258, 2187569, -26263207, -6086921, 31316348, 14219878 }, + }, + { + { -28594490, 1193785, 32245219, 11392485, 31092169, 15722801, 27146014, 6992409, 29126555, 9207390 }, + { 32382935, 1110093, 18477781, 11028262, -27411763, -7548111, -4980517, 10843782, -7957600, -14435730 }, + { 2814918, 7836403, 27519878, -7868156, -20894015, -11553689, -21494559, 8550130, 28346258, 1994730 }, + }, + { + { -19578299, 8085545, -14000519, -3948622, 2785838, -16231307, -19516951, 7174894, 22628102, 8115180 }, + { -30405132, 955511, -11133838, -15078069, -32447087, -13278079, -25651578, 3317160, -9943017, 930272 }, + { -15303681, -6833769, 28856490, 1357446, 23421993, 1057177, 24091212, -1388970, -22765376, -10650715 }, + }, + { + { -22751231, -5303997, -12907607, -12768866, -15811511, -7797053, -14839018, -16554220, -1867018, 8398970 }, + { -31969310, 2106403, -4736360, 1362501, 12813763, 16200670, 22981545, -6291273, 18009408, -15772772 }, + { -17220923, -9545221, -27784654, 14166835, 29815394, 7444469, 29551787, -3727419, 19288549, 1325865 }, + }, + { + { 15100157, -15835752, -23923978, -1005098, -26450192, 15509408, 12376730, -3479146, 33166107, -8042750 }, + { 20909231, 13023121, -9209752, 16251778, -5778415, -8094914, 12412151, 10018715, 2213263, -13878373 }, + { 32529814, -11074689, 30361439, -16689753, -9135940, 1513226, 22922121, 6382134, -5766928, 8371348 }, + }, + }, + { + { + { 9923462, 11271500, 12616794, 3544722, -29998368, -1721626, 12891687, -8193132, -26442943, 10486144 }, + { -22597207, -7012665, 8587003, -8257861, 4084309, -12970062, 361726, 2610596, -23921530, -11455195 }, + { 5408411, -1136691, -4969122, 10561668, 24145918, 14240566, 31319731, -4235541, 19985175, -3436086 }, + }, + { + { -13994457, 16616821, 14549246, 3341099, 32155958, 13648976, -17577068, 8849297, 65030, 8370684 }, + { -8320926, -12049626, 31204563, 5839400, -20627288, -1057277, -19442942, 6922164, 12743482, -9800518 }, + { -2361371, 12678785, 28815050, 4759974, -23893047, 4884717, 23783145, 11038569, 18800704, 255233 }, + }, + { + { -5269658, -1773886, 13957886, 7990715, 23132995, 728773, 13393847, 9066957, 19258688, -14753793 }, + { -2936654, -10827535, -10432089, 14516793, -3640786, 4372541, -31934921, 2209390, -1524053, 2055794 }, + { 580882, 16705327, 5468415, -2683018, -30926419, -14696000, -7203346, -8994389, -30021019, 7394435 }, + }, + { + { 23838809, 1822728, -15738443, 15242727, 8318092, -3733104, -21672180, -3492205, -4821741, 14799921 }, + { 13345610, 9759151, 3371034, -16137791, 16353039, 8577942, 31129804, 13496856, -9056018, 7402518 }, + { 2286874, -4435931, -20042458, -2008336, -13696227, 5038122, 11006906, -15760352, 8205061, 1607563 }, + }, + { + { 14414086, -8002132, 3331830, -3208217, 22249151, -5594188, 18364661, -2906958, 30019587, -9029278 }, + { -27688051, 1585953, -10775053, 931069, -29120221, -11002319, -14410829, 12029093, 9944378, 8024 }, + { 4368715, -3709630, 29874200, -15022983, -20230386, -11410704, -16114594, -999085, -8142388, 5640030 }, + }, + { + { 10299610, 13746483, 11661824, 16234854, 7630238, 5998374, 9809887, -16694564, 15219798, -14327783 }, + { 27425505, -5719081, 3055006, 10660664, 23458024, 595578, -15398605, -1173195, -18342183, 9742717 }, + { 6744077, 2427284, 26042789, 2720740, -847906, 1118974, 32324614, 7406442, 12420155, 1994844 }, + }, + { + { 14012521, -5024720, -18384453, -9578469, -26485342, -3936439, -13033478, -10909803, 24319929, -6446333 }, + { 16412690, -4507367, 10772641, 15929391, -17068788, -4658621, 10555945, -10484049, -30102368, -4739048 }, + { 22397382, -7767684, -9293161, -12792868, 17166287, -9755136, -27333065, 6199366, 21880021, -12250760 }, + }, + { + { -4283307, 5368523, -31117018, 8163389, -30323063, 3209128, 16557151, 8890729, 8840445, 4957760 }, + { -15447727, 709327, -6919446, -10870178, -29777922, 6522332, -21720181, 12130072, -14796503, 5005757 }, + { -2114751, -14308128, 23019042, 15765735, -25269683, 6002752, 10183197, -13239326, -16395286, -2176112 }, + }, + }, + { + { + { -19025756, 1632005, 13466291, -7995100, -23640451, 16573537, -32013908, -3057104, 22208662, 2000468 }, + { 3065073, -1412761, -25598674, -361432, -17683065, -5703415, -8164212, 11248527, -3691214, -7414184 }, + { 10379208, -6045554, 8877319, 1473647, -29291284, -12507580, 16690915, 2553332, -3132688, 16400289 }, + }, + { + { 15716668, 1254266, -18472690, 7446274, -8448918, 6344164, -22097271, -7285580, 26894937, 9132066 }, + { 24158887, 12938817, 11085297, -8177598, -28063478, -4457083, -30576463, 64452, -6817084, -2692882 }, + { 13488534, 7794716, 22236231, 5989356, 25426474, -12578208, 2350710, -3418511, -4688006, 2364226 }, + }, + { + { 16335052, 9132434, 25640582, 6678888, 1725628, 8517937, -11807024, -11697457, 15445875, -7798101 }, + { 29004207, -7867081, 28661402, -640412, -12794003, -7943086, 31863255, -4135540, -278050, -15759279 }, + { -6122061, -14866665, -28614905, 14569919, -10857999, -3591829, 10343412, -6976290, -29828287, -10815811 }, + }, + { + { 27081650, 3463984, 14099042, -4517604, 1616303, -6205604, 29542636, 15372179, 17293797, 960709 }, + { 20263915, 11434237, -5765435, 11236810, 13505955, -10857102, -16111345, 6493122, -19384511, 7639714 }, + { -2830798, -14839232, 25403038, -8215196, -8317012, -16173699, 18006287, -16043750, 29994677, -15808121 }, + }, + { + { 9769828, 5202651, -24157398, -13631392, -28051003, -11561624, -24613141, -13860782, -31184575, 709464 }, + { 12286395, 13076066, -21775189, -1176622, -25003198, 4057652, -32018128, -8890874, 16102007, 13205847 }, + { 13733362, 5599946, 10557076, 3195751, -5557991, 8536970, -25540170, 8525972, 10151379, 10394400 }, + }, + { + { 4024660, -16137551, 22436262, 12276534, -9099015, -2686099, 19698229, 11743039, -33302334, 8934414 }, + { -15879800, -4525240, -8580747, -2934061, 14634845, -698278, -9449077, 3137094, -11536886, 11721158 }, + { 17555939, -5013938, 8268606, 2331751, -22738815, 9761013, 9319229, 8835153, -9205489, -1280045 }, + }, + { + { -461409, -7830014, 20614118, 16688288, -7514766, -4807119, 22300304, 505429, 6108462, -6183415 }, + { -5070281, 12367917, -30663534, 3234473, 32617080, -8422642, 29880583, -13483331, -26898490, -7867459 }, + { -31975283, 5726539, 26934134, 10237677, -3173717, -605053, 24199304, 3795095, 7592688, -14992079 }, + }, + { + { 21594432, -14964228, 17466408, -4077222, 32537084, 2739898, 6407723, 12018833, -28256052, 4298412 }, + { -20650503, -11961496, -27236275, 570498, 3767144, -1717540, 13891942, -1569194, 13717174, 10805743 }, + { -14676630, -15644296, 15287174, 11927123, 24177847, -8175568, -796431, 14860609, -26938930, -5863836 }, + }, + }, + { + { + { 12962541, 5311799, -10060768, 11658280, 18855286, -7954201, 13286263, -12808704, -4381056, 9882022 }, + { 18512079, 11319350, -20123124, 15090309, 18818594, 5271736, -22727904, 3666879, -23967430, -3299429 }, + { -6789020, -3146043, 16192429, 13241070, 15898607, -14206114, -10084880, -6661110, -2403099, 5276065 }, + }, + { + { 30169808, -5317648, 26306206, -11750859, 27814964, 7069267, 7152851, 3684982, 1449224, 13082861 }, + { 10342826, 3098505, 2119311, 193222, 25702612, 12233820, 23697382, 15056736, -21016438, -8202000 }, + { -33150110, 3261608, 22745853, 7948688, 19370557, -15177665, -26171976, 6482814, -10300080, -11060101 }, + }, + { + { 32869458, -5408545, 25609743, 15678670, -10687769, -15471071, 26112421, 2521008, -22664288, 6904815 }, + { 29506923, 4457497, 3377935, -9796444, -30510046, 12935080, 1561737, 3841096, -29003639, -6657642 }, + { 10340844, -6630377, -18656632, -2278430, 12621151, -13339055, 30878497, -11824370, -25584551, 5181966 }, + }, + { + { 25940115, -12658025, 17324188, -10307374, -8671468, 15029094, 24396252, -16450922, -2322852, -12388574 }, + { -21765684, 9916823, -1300409, 4079498, -1028346, 11909559, 1782390, 12641087, 20603771, -6561742 }, + { -18882287, -11673380, 24849422, 11501709, 13161720, -4768874, 1925523, 11914390, 4662781, 7820689 }, + }, + { + { 12241050, -425982, 8132691, 9393934, 32846760, -1599620, 29749456, 12172924, 16136752, 15264020 }, + { -10349955, -14680563, -8211979, 2330220, -17662549, -14545780, 10658213, 6671822, 19012087, 3772772 }, + { 3753511, -3421066, 10617074, 2028709, 14841030, -6721664, 28718732, -15762884, 20527771, 12988982 }, + }, + { + { -14822485, -5797269, -3707987, 12689773, -898983, -10914866, -24183046, -10564943, 3299665, -12424953 }, + { -16777703, -15253301, -9642417, 4978983, 3308785, 8755439, 6943197, 6461331, -25583147, 8991218 }, + { -17226263, 1816362, -1673288, -6086439, 31783888, -8175991, -32948145, 7417950, -30242287, 1507265 }, + }, + { + { 29692663, 6829891, -10498800, 4334896, 20945975, -11906496, -28887608, 8209391, 14606362, -10647073 }, + { -3481570, 8707081, 32188102, 5672294, 22096700, 1711240, -33020695, 9761487, 4170404, -2085325 }, + { -11587470, 14855945, -4127778, -1531857, -26649089, 15084046, 22186522, 16002000, -14276837, -8400798 }, + }, + { + { -4811456, 13761029, -31703877, -2483919, -3312471, 7869047, -7113572, -9620092, 13240845, 10965870 }, + { -7742563, -8256762, -14768334, -13656260, -23232383, 12387166, 4498947, 14147411, 29514390, 4302863 }, + { -13413405, -12407859, 20757302, -13801832, 14785143, 8976368, -5061276, -2144373, 17846988, -13971927 }, + }, + }, + { + { + { -2244452, -754728, -4597030, -1066309, -6247172, 1455299, -21647728, -9214789, -5222701, 12650267 }, + { -9906797, -16070310, 21134160, 12198166, -27064575, 708126, 387813, 13770293, -19134326, 10958663 }, + { 22470984, 12369526, 23446014, -5441109, -21520802, -9698723, -11772496, -11574455, -25083830, 4271862 }, + }, + { + { -25169565, -10053642, -19909332, 15361595, -5984358, 2159192, 75375, -4278529, -32526221, 8469673 }, + { 15854970, 4148314, -8893890, 7259002, 11666551, 13824734, -30531198, 2697372, 24154791, -9460943 }, + { 15446137, -15806644, 29759747, 14019369, 30811221, -9610191, -31582008, 12840104, 24913809, 9815020 }, + }, + { + { -4709286, -5614269, -31841498, -12288893, -14443537, 10799414, -9103676, 13438769, 18735128, 9466238 }, + { 11933045, 9281483, 5081055, -5183824, -2628162, -4905629, -7727821, -10896103, -22728655, 16199064 }, + { 14576810, 379472, -26786533, -8317236, -29426508, -10812974, -102766, 1876699, 30801119, 2164795 }, + }, + { + { 15995086, 3199873, 13672555, 13712240, -19378835, -4647646, -13081610, -15496269, -13492807, 1268052 }, + { -10290614, -3659039, -3286592, 10948818, 23037027, 3794475, -3470338, -12600221, -17055369, 3565904 }, + { 29210088, -9419337, -5919792, -4952785, 10834811, -13327726, -16512102, -10820713, -27162222, -14030531 }, + }, + { + { -13161890, 15508588, 16663704, -8156150, -28349942, 9019123, -29183421, -3769423, 2244111, -14001979 }, + { -5152875, -3800936, -9306475, -6071583, 16243069, 14684434, -25673088, -16180800, 13491506, 4641841 }, + { 10813417, 643330, -19188515, -728916, 30292062, -16600078, 27548447, -7721242, 14476989, -12767431 }, + }, + { + { 10292079, 9984945, 6481436, 8279905, -7251514, 7032743, 27282937, -1644259, -27912810, 12651324 }, + { -31185513, -813383, 22271204, 11835308, 10201545, 15351028, 17099662, 3988035, 21721536, -3148940 }, + { 10202177, -6545839, -31373232, -9574638, -32150642, -8119683, -12906320, 3852694, 13216206, 14842320 }, + }, + { + { -15815640, -10601066, -6538952, -7258995, -6984659, -6581778, -31500847, 13765824, -27434397, 9900184 }, + { 14465505, -13833331, -32133984, -14738873, -27443187, 12990492, 33046193, 15796406, -7051866, -8040114 }, + { 30924417, -8279620, 6359016, -12816335, 16508377, 9071735, -25488601, 15413635, 9524356, -7018878 }, + }, + { + { 12274201, -13175547, 32627641, -1785326, 6736625, 13267305, 5237659, -5109483, 15663516, 4035784 }, + { -2951309, 8903985, 17349946, 601635, -16432815, -4612556, -13732739, -15889334, -22258478, 4659091 }, + { -16916263, -4952973, -30393711, -15158821, 20774812, 15897498, 5736189, 15026997, -2178256, -13455585 }, + }, + }, + { + { + { -8858980, -2219056, 28571666, -10155518, -474467, -10105698, -3801496, 278095, 23440562, -290208 }, + { 10226241, -5928702, 15139956, 120818, -14867693, 5218603, 32937275, 11551483, -16571960, -7442864 }, + { 17932739, -12437276, -24039557, 10749060, 11316803, 7535897, 22503767, 5561594, -3646624, 3898661 }, + }, + { + { 7749907, -969567, -16339731, -16464, -25018111, 15122143, -1573531, 7152530, 21831162, 1245233 }, + { 26958459, -14658026, 4314586, 8346991, -5677764, 11960072, -32589295, -620035, -30402091, -16716212 }, + { -12165896, 9166947, 33491384, 13673479, 29787085, 13096535, 6280834, 14587357, -22338025, 13987525 }, + }, + { + { -24349909, 7778775, 21116000, 15572597, -4833266, -5357778, -4300898, -5124639, -7469781, -2858068 }, + { 9681908, -6737123, -31951644, 13591838, -6883821, 386950, 31622781, 6439245, -14581012, 4091397 }, + { -8426427, 1470727, -28109679, -1596990, 3978627, -5123623, -19622683, 12092163, 29077877, -14741988 }, + }, + { + { 5269168, -6859726, -13230211, -8020715, 25932563, 1763552, -5606110, -5505881, -20017847, 2357889 }, + { 32264008, -15407652, -5387735, -1160093, -2091322, -3946900, 23104804, -12869908, 5727338, 189038 }, + { 14609123, -8954470, -6000566, -16622781, -14577387, -7743898, -26745169, 10942115, -25888931, -14884697 }, + }, + { + { 20513500, 5557931, -15604613, 7829531, 26413943, -2019404, -21378968, 7471781, 13913677, -5137875 }, + { -25574376, 11967826, 29233242, 12948236, -6754465, 4713227, -8940970, 14059180, 12878652, 8511905 }, + { -25656801, 3393631, -2955415, -7075526, -2250709, 9366908, -30223418, 6812974, 5568676, -3127656 }, + }, + { + { 11630004, 12144454, 2116339, 13606037, 27378885, 15676917, -17408753, -13504373, -14395196, 8070818 }, + { 27117696, -10007378, -31282771, -5570088, 1127282, 12772488, -29845906, 10483306, -11552749, -1028714 }, + { 10637467, -5688064, 5674781, 1072708, -26343588, -6982302, -1683975, 9177853, -27493162, 15431203 }, + }, + { + { 20525145, 10892566, -12742472, 12779443, -29493034, 16150075, -28240519, 14943142, -15056790, -7935931 }, + { -30024462, 5626926, -551567, -9981087, 753598, 11981191, 25244767, -3239766, -3356550, 9594024 }, + { -23752644, 2636870, -5163910, -10103818, 585134, 7877383, 11345683, -6492290, 13352335, -10977084 }, + }, + { + { -1931799, -5407458, 3304649, -12884869, 17015806, -4877091, -29783850, -7752482, -13215537, -319204 }, + { 20239939, 6607058, 6203985, 3483793, -18386976, -779229, -20723742, 15077870, -22750759, 14523817 }, + { 27406042, -6041657, 27423596, -4497394, 4996214, 10002360, -28842031, -4545494, -30172742, -4805667 }, + }, + }, + { + { + { 11374242, 12660715, 17861383, -12540833, 10935568, 1099227, -13886076, -9091740, -27727044, 11358504 }, + { -12730809, 10311867, 1510375, 10778093, -2119455, -9145702, 32676003, 11149336, -26123651, 4985768 }, + { -19096303, 341147, -6197485, -239033, 15756973, -8796662, -983043, 13794114, -19414307, -15621255 }, + }, + { + { 6490081, 11940286, 25495923, -7726360, 8668373, -8751316, 3367603, 6970005, -1691065, -9004790 }, + { 1656497, 13457317, 15370807, 6364910, 13605745, 8362338, -19174622, -5475723, -16796596, -5031438 }, + { -22273315, -13524424, -64685, -4334223, -18605636, -10921968, -20571065, -7007978, -99853, -10237333 }, + }, + { + { 17747465, 10039260, 19368299, -4050591, -20630635, -16041286, 31992683, -15857976, -29260363, -5511971 }, + { 31932027, -4986141, -19612382, 16366580, 22023614, 88450, 11371999, -3744247, 4882242, -10626905 }, + { 29796507, 37186, 19818052, 10115756, -11829032, 3352736, 18551198, 3272828, -5190932, -4162409 }, + }, + { + { 12501286, 4044383, -8612957, -13392385, -32430052, 5136599, -19230378, -3529697, 330070, -3659409 }, + { 6384877, 2899513, 17807477, 7663917, -2358888, 12363165, 25366522, -8573892, -271295, 12071499 }, + { -8365515, -4042521, 25133448, -4517355, -6211027, 2265927, -32769618, 1936675, -5159697, 3829363 }, + }, + { + { 28425966, -5835433, -577090, -4697198, -14217555, 6870930, 7921550, -6567787, 26333140, 14267664 }, + { -11067219, 11871231, 27385719, -10559544, -4585914, -11189312, 10004786, -8709488, -21761224, 8930324 }, + { -21197785, -16396035, 25654216, -1725397, 12282012, 11008919, 1541940, 4757911, -26491501, -16408940 }, + }, + { + { 13537262, -7759490, -20604840, 10961927, -5922820, -13218065, -13156584, 6217254, -15943699, 13814990 }, + { -17422573, 15157790, 18705543, 29619, 24409717, -260476, 27361681, 9257833, -1956526, -1776914 }, + { -25045300, -10191966, 15366585, 15166509, -13105086, 8423556, -29171540, 12361135, -18685978, 4578290 }, + }, + { + { 24579768, 3711570, 1342322, -11180126, -27005135, 14124956, -22544529, 14074919, 21964432, 8235257 }, + { -6528613, -2411497, 9442966, -5925588, 12025640, -1487420, -2981514, -1669206, 13006806, 2355433 }, + { -16304899, -13605259, -6632427, -5142349, 16974359, -10911083, 27202044, 1719366, 1141648, -12796236 }, + }, + { + { -12863944, -13219986, -8318266, -11018091, -6810145, -4843894, 13475066, -3133972, 32674895, 13715045 }, + { 11423335, -5468059, 32344216, 8962751, 24989809, 9241752, -13265253, 16086212, -28740881, -15642093 }, + { -1409668, 12530728, -6368726, 10847387, 19531186, -14132160, -11709148, 7791794, -27245943, 4383347 }, + }, + }, + { + { + { -28970898, 5271447, -1266009, -9736989, -12455236, 16732599, -4862407, -4906449, 27193557, 6245191 }, + { -15193956, 5362278, -1783893, 2695834, 4960227, 12840725, 23061898, 3260492, 22510453, 8577507 }, + { -12632451, 11257346, -32692994, 13548177, -721004, 10879011, 31168030, 13952092, -29571492, -3635906 }, + }, + { + { 3877321, -9572739, 32416692, 5405324, -11004407, -13656635, 3759769, 11935320, 5611860, 8164018 }, + { -16275802, 14667797, 15906460, 12155291, -22111149, -9039718, 32003002, -8832289, 5773085, -8422109 }, + { -23788118, -8254300, 1950875, 8937633, 18686727, 16459170, -905725, 12376320, 31632953, 190926 }, + }, + { + { -24593607, -16138885, -8423991, 13378746, 14162407, 6901328, -8288749, 4508564, -25341555, -3627528 }, + { 8884438, -5884009, 6023974, 10104341, -6881569, -4941533, 18722941, -14786005, -1672488, 827625 }, + { -32720583, -16289296, -32503547, 7101210, 13354605, 2659080, -1800575, -14108036, -24878478, 1541286 }, + }, + { + { 2901347, -1117687, 3880376, -10059388, -17620940, -3612781, -21802117, -3567481, 20456845, -1885033 }, + { 27019610, 12299467, -13658288, -1603234, -12861660, -4861471, -19540150, -5016058, 29439641, 15138866 }, + { 21536104, -6626420, -32447818, -10690208, -22408077, 5175814, -5420040, -16361163, 7779328, 109896 }, + }, + { + { 30279744, 14648750, -8044871, 6425558, 13639621, -743509, 28698390, 12180118, 23177719, -554075 }, + { 26572847, 3405927, -31701700, 12890905, -19265668, 5335866, -6493768, 2378492, 4439158, -13279347 }, + { -22716706, 3489070, -9225266, -332753, 18875722, -1140095, 14819434, -12731527, -17717757, -5461437 }, + }, + { + { -5056483, 16566551, 15953661, 3767752, -10436499, 15627060, -820954, 2177225, 8550082, -15114165 }, + { -18473302, 16596775, -381660, 15663611, 22860960, 15585581, -27844109, -3582739, -23260460, -8428588 }, + { -32480551, 15707275, -8205912, -5652081, 29464558, 2713815, -22725137, 15860482, -21902570, 1494193 }, + }, + { + { -19562091, -14087393, -25583872, -9299552, 13127842, 759709, 21923482, 16529112, 8742704, 12967017 }, + { -28464899, 1553205, 32536856, -10473729, -24691605, -406174, -8914625, -2933896, -29903758, 15553883 }, + { 21877909, 3230008, 9881174, 10539357, -4797115, 2841332, 11543572, 14513274, 19375923, -12647961 }, + }, + { + { 8832269, -14495485, 13253511, 5137575, 5037871, 4078777, 24880818, -6222716, 2862653, 9455043 }, + { 29306751, 5123106, 20245049, -14149889, 9592566, 8447059, -2077124, -2990080, 15511449, 4789663 }, + { -20679756, 7004547, 8824831, -9434977, -4045704, -3750736, -5754762, 108893, 23513200, 16652362 }, + }, + }, + { + { + { -33256173, 4144782, -4476029, -6579123, 10770039, -7155542, -6650416, -12936300, -18319198, 10212860 }, + { 2756081, 8598110, 7383731, -6859892, 22312759, -1105012, 21179801, 2600940, -9988298, -12506466 }, + { -24645692, 13317462, -30449259, -15653928, 21365574, -10869657, 11344424, 864440, -2499677, -16710063 }, + }, + { + { -26432803, 6148329, -17184412, -14474154, 18782929, -275997, -22561534, 211300, 2719757, 4940997 }, + { -1323882, 3911313, -6948744, 14759765, -30027150, 7851207, 21690126, 8518463, 26699843, 5276295 }, + { -13149873, -6429067, 9396249, 365013, 24703301, -10488939, 1321586, 149635, -15452774, 7159369 }, + }, + { + { 9987780, -3404759, 17507962, 9505530, 9731535, -2165514, 22356009, 8312176, 22477218, -8403385 }, + { 18155857, -16504990, 19744716, 9006923, 15154154, -10538976, 24256460, -4864995, -22548173, 9334109 }, + { 2986088, -4911893, 10776628, -3473844, 10620590, -7083203, -21413845, 14253545, -22587149, 536906 }, + }, + { + { 4377756, 8115836, 24567078, 15495314, 11625074, 13064599, 7390551, 10589625, 10838060, -15420424 }, + { -19342404, 867880, 9277171, -3218459, -14431572, -1986443, 19295826, -15796950, 6378260, 699185 }, + { 7895026, 4057113, -7081772, -13077756, -17886831, -323126, -716039, 15693155, -5045064, -13373962 }, + }, + { + { -7737563, -5869402, -14566319, -7406919, 11385654, 13201616, 31730678, -10962840, -3918636, -9669325 }, + { 10188286, -15770834, -7336361, 13427543, 22223443, 14896287, 30743455, 7116568, -21786507, 5427593 }, + { 696102, 13206899, 27047647, -10632082, 15285305, -9853179, 10798490, -4578720, 19236243, 12477404 }, + }, + { + { -11229439, 11243796, -17054270, -8040865, -788228, -8167967, -3897669, 11180504, -23169516, 7733644 }, + { 17800790, -14036179, -27000429, -11766671, 23887827, 3149671, 23466177, -10538171, 10322027, 15313801 }, + { 26246234, 11968874, 32263343, -5468728, 6830755, -13323031, -15794704, -101982, -24449242, 10890804 }, + }, + { + { -31365647, 10271363, -12660625, -6267268, 16690207, -13062544, -14982212, 16484931, 25180797, -5334884 }, + { -586574, 10376444, -32586414, -11286356, 19801893, 10997610, 2276632, 9482883, 316878, 13820577 }, + { -9882808, -4510367, -2115506, 16457136, -11100081, 11674996, 30756178, -7515054, 30696930, -3712849 }, + }, + { + { 32988917, -9603412, 12499366, 7910787, -10617257, -11931514, -7342816, -9985397, -32349517, 7392473 }, + { -8855661, 15927861, 9866406, -3649411, -2396914, -16655781, -30409476, -9134995, 25112947, -2926644 }, + { -2504044, -436966, 25621774, -5678772, 15085042, -5479877, -24884878, -13526194, 5537438, -13914319 }, + }, + }, + { + { + { -11225584, 2320285, -9584280, 10149187, -33444663, 5808648, -14876251, -1729667, 31234590, 6090599 }, + { -9633316, 116426, 26083934, 2897444, -6364437, -2688086, 609721, 15878753, -6970405, -9034768 }, + { -27757857, 247744, -15194774, -9002551, 23288161, -10011936, -23869595, 6503646, 20650474, 1804084 }, + }, + { + { -27589786, 15456424, 8972517, 8469608, 15640622, 4439847, 3121995, -10329713, 27842616, -202328 }, + { -15306973, 2839644, 22530074, 10026331, 4602058, 5048462, 28248656, 5031932, -11375082, 12714369 }, + { 20807691, -7270825, 29286141, 11421711, -27876523, -13868230, -21227475, 1035546, -19733229, 12796920 }, + }, + { + { 12076899, -14301286, -8785001, -11848922, -25012791, 16400684, -17591495, -12899438, 3480665, -15182815 }, + { -32361549, 5457597, 28548107, 7833186, 7303070, -11953545, -24363064, -15921875, -33374054, 2771025 }, + { -21389266, 421932, 26597266, 6860826, 22486084, -6737172, -17137485, -4210226, -24552282, 15673397 }, + }, + { + { -20184622, 2338216, 19788685, -9620956, -4001265, -8740893, -20271184, 4733254, 3727144, -12934448 }, + { 6120119, 814863, -11794402, -622716, 6812205, -15747771, 2019594, 7975683, 31123697, -10958981 }, + { 30069250, -11435332, 30434654, 2958439, 18399564, -976289, 12296869, 9204260, -16432438, 9648165 }, + }, + { + { 32705432, -1550977, 30705658, 7451065, -11805606, 9631813, 3305266, 5248604, -26008332, -11377501 }, + { 17219865, 2375039, -31570947, -5575615, -19459679, 9219903, 294711, 15298639, 2662509, -16297073 }, + { -1172927, -7558695, -4366770, -4287744, -21346413, -8434326, 32087529, -1222777, 32247248, -14389861 }, + }, + { + { 14312628, 1221556, 17395390, -8700143, -4945741, -8684635, -28197744, -9637817, -16027623, -13378845 }, + { -1428825, -9678990, -9235681, 6549687, -7383069, -468664, 23046502, 9803137, 17597934, 2346211 }, + { 18510800, 15337574, 26171504, 981392, -22241552, 7827556, -23491134, -11323352, 3059833, -11782870 }, + }, + { + { 10141598, 6082907, 17829293, -1947643, 9830092, 13613136, -25556636, -5544586, -33502212, 3592096 }, + { 33114168, -15889352, -26525686, -13343397, 33076705, 8716171, 1151462, 1521897, -982665, -6837803 }, + { -32939165, -4255815, 23947181, -324178, -33072974, -12305637, -16637686, 3891704, 26353178, 693168 }, + }, + { + { 30374239, 1595580, -16884039, 13186931, 4600344, 406904, 9585294, -400668, 31375464, 14369965 }, + { -14370654, -7772529, 1510301, 6434173, -18784789, -6262728, 32732230, -13108839, 17901441, 16011505 }, + { 18171223, -11934626, -12500402, 15197122, -11038147, -15230035, -19172240, -16046376, 8764035, 12309598 }, + }, + }, + { + { + { 5975908, -5243188, -19459362, -9681747, -11541277, 14015782, -23665757, 1228319, 17544096, -10593782 }, + { 5811932, -1715293, 3442887, -2269310, -18367348, -8359541, -18044043, -15410127, -5565381, 12348900 }, + { -31399660, 11407555, 25755363, 6891399, -3256938, 14872274, -24849353, 8141295, -10632534, -585479 }, + }, + { + { -12675304, 694026, -5076145, 13300344, 14015258, -14451394, -9698672, -11329050, 30944593, 1130208 }, + { 8247766, -6710942, -26562381, -7709309, -14401939, -14648910, 4652152, 2488540, 23550156, -271232 }, + { 17294316, -3788438, 7026748, 15626851, 22990044, 113481, 2267737, -5908146, -408818, -137719 }, + }, + { + { 16091085, -16253926, 18599252, 7340678, 2137637, -1221657, -3364161, 14550936, 3260525, -7166271 }, + { -4910104, -13332887, 18550887, 10864893, -16459325, -7291596, -23028869, -13204905, -12748722, 2701326 }, + { -8574695, 16099415, 4629974, -16340524, -20786213, -6005432, -10018363, 9276971, 11329923, 1862132 }, + }, + { + { 14763076, -15903608, -30918270, 3689867, 3511892, 10313526, -21951088, 12219231, -9037963, -940300 }, + { 8894987, -3446094, 6150753, 3013931, 301220, 15693451, -31981216, -2909717, -15438168, 11595570 }, + { 15214962, 3537601, -26238722, -14058872, 4418657, -15230761, 13947276, 10730794, -13489462, -4363670 }, + }, + { + { -2538306, 7682793, 32759013, 263109, -29984731, -7955452, -22332124, -10188635, 977108, 699994 }, + { -12466472, 4195084, -9211532, 550904, -15565337, 12917920, 19118110, -439841, -30534533, -14337913 }, + { 31788461, -14507657, 4799989, 7372237, 8808585, -14747943, 9408237, -10051775, 12493932, -5409317 }, + }, + { + { -25680606, 5260744, -19235809, -6284470, -3695942, 16566087, 27218280, 2607121, 29375955, 6024730 }, + { 842132, -2794693, -4763381, -8722815, 26332018, -12405641, 11831880, 6985184, -9940361, 2854096 }, + { -4847262, -7969331, 2516242, -5847713, 9695691, -7221186, 16512645, 960770, 12121869, 16648078 }, + }, + { + { -15218652, 14667096, -13336229, 2013717, 30598287, -464137, -31504922, -7882064, 20237806, 2838411 }, + { -19288047, 4453152, 15298546, -16178388, 22115043, -15972604, 12544294, -13470457, 1068881, -12499905 }, + { -9558883, -16518835, 33238498, 13506958, 30505848, -1114596, -8486907, -2630053, 12521378, 4845654 }, + }, + { + { -28198521, 10744108, -2958380, 10199664, 7759311, -13088600, 3409348, -873400, -6482306, -12885870 }, + { -23561822, 6230156, -20382013, 10655314, -24040585, -11621172, 10477734, -1240216, -3113227, 13974498 }, + { 12966261, 15550616, -32038948, -1615346, 21025980, -629444, 5642325, 7188737, 18895762, 12629579 }, + }, + }, + { + { + { 14741879, -14946887, 22177208, -11721237, 1279741, 8058600, 11758140, 789443, 32195181, 3895677 }, + { 10758205, 15755439, -4509950, 9243698, -4879422, 6879879, -2204575, -3566119, -8982069, 4429647 }, + { -2453894, 15725973, -20436342, -10410672, -5803908, -11040220, -7135870, -11642895, 18047436, -15281743 }, + }, + { + { -25173001, -11307165, 29759956, 11776784, -22262383, -15820455, 10993114, -12850837, -17620701, -9408468 }, + { 21987233, 700364, -24505048, 14972008, -7774265, -5718395, 32155026, 2581431, -29958985, 8773375 }, + { -25568350, 454463, -13211935, 16126715, 25240068, 8594567, 20656846, 12017935, -7874389, -13920155 }, + }, + { + { 6028182, 6263078, -31011806, -11301710, -818919, 2461772, -31841174, -5468042, -1721788, -2776725 }, + { -12278994, 16624277, 987579, -5922598, 32908203, 1248608, 7719845, -4166698, 28408820, 6816612 }, + { -10358094, -8237829, 19549651, -12169222, 22082623, 16147817, 20613181, 13982702, -10339570, 5067943 }, + }, + { + { -30505967, -3821767, 12074681, 13582412, -19877972, 2443951, -19719286, 12746132, 5331210, -10105944 }, + { 30528811, 3601899, -1957090, 4619785, -27361822, -15436388, 24180793, -12570394, 27679908, -1648928 }, + { 9402404, -13957065, 32834043, 10838634, -26580150, -13237195, 26653274, -8685565, 22611444, -12715406 }, + }, + { + { 22190590, 1118029, 22736441, 15130463, -30460692, -5991321, 19189625, -4648942, 4854859, 6622139 }, + { -8310738, -2953450, -8262579, -3388049, -10401731, -271929, 13424426, -3567227, 26404409, 13001963 }, + { -31241838, -15415700, -2994250, 8939346, 11562230, -12840670, -26064365, -11621720, -15405155, 11020693 }, + }, + { + { 1866042, -7949489, -7898649, -10301010, 12483315, 13477547, 3175636, -12424163, 28761762, 1406734 }, + { -448555, -1777666, 13018551, 3194501, -9580420, -11161737, 24760585, -4347088, 25577411, -13378680 }, + { -24290378, 4759345, -690653, -1852816, 2066747, 10693769, -29595790, 9884936, -9368926, 4745410 }, + }, + { + { -9141284, 6049714, -19531061, -4341411, -31260798, 9944276, -15462008, -11311852, 10931924, -11931931 }, + { -16561513, 14112680, -8012645, 4817318, -8040464, -11414606, -22853429, 10856641, -20470770, 13434654 }, + { 22759489, -10073434, -16766264, -1871422, 13637442, -10168091, 1765144, -12654326, 28445307, -5364710 }, + }, + { + { 29875063, 12493613, 2795536, -3786330, 1710620, 15181182, -10195717, -8788675, 9074234, 1167180 }, + { -26205683, 11014233, -9842651, -2635485, -26908120, 7532294, -18716888, -9535498, 3843903, 9367684 }, + { -10969595, -6403711, 9591134, 9582310, 11349256, 108879, 16235123, 8601684, -139197, 4242895 }, + }, + }, + { + { + { 22092954, -13191123, -2042793, -11968512, 32186753, -11517388, -6574341, 2470660, -27417366, 16625501 }, + { -11057722, 3042016, 13770083, -9257922, 584236, -544855, -7770857, 2602725, -27351616, 14247413 }, + { 6314175, -10264892, -32772502, 15957557, -10157730, 168750, -8618807, 14290061, 27108877, -1180880 }, + }, + { + { -8586597, -7170966, 13241782, 10960156, -32991015, -13794596, 33547976, -11058889, -27148451, 981874 }, + { 22833440, 9293594, -32649448, -13618667, -9136966, 14756819, -22928859, -13970780, -10479804, -16197962 }, + { -7768587, 3326786, -28111797, 10783824, 19178761, 14905060, 22680049, 13906969, -15933690, 3797899 }, + }, + { + { 21721356, -4212746, -12206123, 9310182, -3882239, -13653110, 23740224, -2709232, 20491983, -8042152 }, + { 9209270, -15135055, -13256557, -6167798, -731016, 15289673, 25947805, 15286587, 30997318, -6703063 }, + { 7392032, 16618386, 23946583, -8039892, -13265164, -1533858, -14197445, -2321576, 17649998, -250080 }, + }, + { + { -9301088, -14193827, 30609526, -3049543, -25175069, -1283752, -15241566, -9525724, -2233253, 7662146 }, + { -17558673, 1763594, -33114336, 15908610, -30040870, -12174295, 7335080, -8472199, -3174674, 3440183 }, + { -19889700, -5977008, -24111293, -9688870, 10799743, -16571957, 40450, -4431835, 4862400, 1133 }, + }, + { + { -32856209, -7873957, -5422389, 14860950, -16319031, 7956142, 7258061, 311861, -30594991, -7379421 }, + { -3773428, -1565936, 28985340, 7499440, 24445838, 9325937, 29727763, 16527196, 18278453, 15405622 }, + { -4381906, 8508652, -19898366, -3674424, -5984453, 15149970, -13313598, 843523, -21875062, 13626197 }, + }, + { + { 2281448, -13487055, -10915418, -2609910, 1879358, 16164207, -10783882, 3953792, 13340839, 15928663 }, + { 31727126, -7179855, -18437503, -8283652, 2875793, -16390330, -25269894, -7014826, -23452306, 5964753 }, + { 4100420, -5959452, -17179337, 6017714, -18705837, 12227141, -26684835, 11344144, 2538215, -7570755 }, + }, + { + { -9433605, 6123113, 11159803, -2156608, 30016280, 14966241, -20474983, 1485421, -629256, -15958862 }, + { -26804558, 4260919, 11851389, 9658551, -32017107, 16367492, -20205425, -13191288, 11659922, -11115118 }, + { 26180396, 10015009, -30844224, -8581293, 5418197, 9480663, 2231568, -10170080, 33100372, -1306171 }, + }, + { + { 15121113, -5201871, -10389905, 15427821, -27509937, -15992507, 21670947, 4486675, -5931810, -14466380 }, + { 16166486, -9483733, -11104130, 6023908, -31926798, -1364923, 2340060, -16254968, -10735770, -10039824 }, + { 28042865, -3557089, -12126526, 12259706, -3717498, -6945899, 6766453, -8689599, 18036436, 5803270 }, + }, + }, + { + { + { -817581, 6763912, 11803561, 1585585, 10958447, -2671165, 23855391, 4598332, -6159431, -14117438 }, + { -31031306, -14256194, 17332029, -2383520, 31312682, -5967183, 696309, 50292, -20095739, 11763584 }, + { -594563, -2514283, -32234153, 12643980, 12650761, 14811489, 665117, -12613632, -19773211, -10713562 }, + }, + { + { 30464590, -11262872, -4127476, -12734478, 19835327, -7105613, -24396175, 2075773, -17020157, 992471 }, + { 18357185, -6994433, 7766382, 16342475, -29324918, 411174, 14578841, 8080033, -11574335, -10601610 }, + { 19598397, 10334610, 12555054, 2555664, 18821899, -10339780, 21873263, 16014234, 26224780, 16452269 }, + }, + { + { -30223925, 5145196, 5944548, 16385966, 3976735, 2009897, -11377804, -7618186, -20533829, 3698650 }, + { 14187449, 3448569, -10636236, -10810935, -22663880, -3433596, 7268410, -10890444, 27394301, 12015369 }, + { 19695761, 16087646, 28032085, 12999827, 6817792, 11427614, 20244189, -1312777, -13259127, -3402461 }, + }, + { + { 30860103, 12735208, -1888245, -4699734, -16974906, 2256940, -8166013, 12298312, -8550524, -10393462 }, + { -5719826, -11245325, -1910649, 15569035, 26642876, -7587760, -5789354, -15118654, -4976164, 12651793 }, + { -2848395, 9953421, 11531313, -5282879, 26895123, -12697089, -13118820, -16517902, 9768698, -2533218 }, + }, + { + { -24719459, 1894651, -287698, -4704085, 15348719, -8156530, 32767513, 12765450, 4940095, 10678226 }, + { 18860224, 15980149, -18987240, -1562570, -26233012, -11071856, -7843882, 13944024, -24372348, 16582019 }, + { -15504260, 4970268, -29893044, 4175593, -20993212, -2199756, -11704054, 15444560, -11003761, 7989037 }, + }, + { + { 31490452, 5568061, -2412803, 2182383, -32336847, 4531686, -32078269, 6200206, -19686113, -14800171 }, + { -17308668, -15879940, -31522777, -2831, -32887382, 16375549, 8680158, -16371713, 28550068, -6857132 }, + { -28126887, -5688091, 16837845, -1820458, -6850681, 12700016, -30039981, 4364038, 1155602, 5988841 }, + }, + { + { 21890435, -13272907, -12624011, 12154349, -7831873, 15300496, 23148983, -4470481, 24618407, 8283181 }, + { -33136107, -10512751, 9975416, 6841041, -31559793, 16356536, 3070187, -7025928, 1466169, 10740210 }, + { -1509399, -15488185, -13503385, -10655916, 32799044, 909394, -13938903, -5779719, -32164649, -15327040 }, + }, + { + { 3960823, -14267803, -28026090, -15918051, -19404858, 13146868, 15567327, 951507, -3260321, -573935 }, + { 24740841, 5052253, -30094131, 8961361, 25877428, 6165135, -24368180, 14397372, -7380369, -6144105 }, + { -28888365, 3510803, -28103278, -1158478, -11238128, -10631454, -15441463, -14453128, -1625486, -6494814 }, + }, + }, + { + { + { 793299, -9230478, 8836302, -6235707, -27360908, -2369593, 33152843, -4885251, -9906200, -621852 }, + { 5666233, 525582, 20782575, -8038419, -24538499, 14657740, 16099374, 1468826, -6171428, -15186581 }, + { -4859255, -3779343, -2917758, -6748019, 7778750, 11688288, -30404353, -9871238, -1558923, -9863646 }, + }, + { + { 10896332, -7719704, 824275, 472601, -19460308, 3009587, 25248958, 14783338, -30581476, -15757844 }, + { 10566929, 12612572, -31944212, 11118703, -12633376, 12362879, 21752402, 8822496, 24003793, 14264025 }, + { 27713862, -7355973, -11008240, 9227530, 27050101, 2504721, 23886875, -13117525, 13958495, -5732453 }, + }, + { + { -23481610, 4867226, -27247128, 3900521, 29838369, -8212291, -31889399, -10041781, 7340521, -15410068 }, + { 4646514, -8011124, -22766023, -11532654, 23184553, 8566613, 31366726, -1381061, -15066784, -10375192 }, + { -17270517, 12723032, -16993061, 14878794, 21619651, -6197576, 27584817, 3093888, -8843694, 3849921 }, + }, + { + { -9064912, 2103172, 25561640, -15125738, -5239824, 9582958, 32477045, -9017955, 5002294, -15550259 }, + { -12057553, -11177906, 21115585, -13365155, 8808712, -12030708, 16489530, 13378448, -25845716, 12741426 }, + { -5946367, 10645103, -30911586, 15390284, -3286982, -7118677, 24306472, 15852464, 28834118, -7646072 }, + }, + { + { -17335748, -9107057, -24531279, 9434953, -8472084, -583362, -13090771, 455841, 20461858, 5491305 }, + { 13669248, -16095482, -12481974, -10203039, -14569770, -11893198, -24995986, 11293807, -28588204, -9421832 }, + { 28497928, 6272777, -33022994, 14470570, 8906179, -1225630, 18504674, -14165166, 29867745, -8795943 }, + }, + { + { -16207023, 13517196, -27799630, -13697798, 24009064, -6373891, -6367600, -13175392, 22853429, -4012011 }, + { 24191378, 16712145, -13931797, 15217831, 14542237, 1646131, 18603514, -11037887, 12876623, -2112447 }, + { 17902668, 4518229, -411702, -2829247, 26878217, 5258055, -12860753, 608397, 16031844, 3723494 }, + }, + { + { -28632773, 12763728, -20446446, 7577504, 33001348, -13017745, 17558842, -7872890, 23896954, -4314245 }, + { -20005381, -12011952, 31520464, 605201, 2543521, 5991821, -2945064, 7229064, -9919646, -8826859 }, + { 28816045, 298879, -28165016, -15920938, 19000928, -1665890, -12680833, -2949325, -18051778, -2082915 }, + }, + { + { 16000882, -344896, 3493092, -11447198, -29504595, -13159789, 12577740, 16041268, -19715240, 7847707 }, + { 10151868, 10572098, 27312476, 7922682, 14825339, 4723128, -32855931, -6519018, -10020567, 3852848 }, + { -11430470, 15697596, -21121557, -4420647, 5386314, 15063598, 16514493, -15932110, 29330899, -15076224 }, + }, + }, + { + { + { -25499735, -4378794, -15222908, -6901211, 16615731, 2051784, 3303702, 15490, -27548796, 12314391 }, + { 15683520, -6003043, 18109120, -9980648, 15337968, -5997823, -16717435, 15921866, 16103996, -3731215 }, + { -23169824, -10781249, 13588192, -1628807, -3798557, -1074929, -19273607, 5402699, -29815713, -9841101 }, + }, + { + { 23190676, 2384583, -32714340, 3462154, -29903655, -1529132, -11266856, 8911517, -25205859, 2739713 }, + { 21374101, -3554250, -33524649, 9874411, 15377179, 11831242, -33529904, 6134907, 4931255, 11987849 }, + { -7732, -2978858, -16223486, 7277597, 105524, -322051, -31480539, 13861388, -30076310, 10117930 }, + }, + { + { -29501170, -10744872, -26163768, 13051539, -25625564, 5089643, -6325503, 6704079, 12890019, 15728940 }, + { -21972360, -11771379, -951059, -4418840, 14704840, 2695116, 903376, -10428139, 12885167, 8311031 }, + { -17516482, 5352194, 10384213, -13811658, 7506451, 13453191, 26423267, 4384730, 1888765, -5435404 }, + }, + { + { -25817338, -3107312, -13494599, -3182506, 30896459, -13921729, -32251644, -12707869, -19464434, -3340243 }, + { -23607977, -2665774, -526091, 4651136, 5765089, 4618330, 6092245, 14845197, 17151279, -9854116 }, + { -24830458, -12733720, -15165978, 10367250, -29530908, -265356, 22825805, -7087279, -16866484, 16176525 }, + }, + { + { -23583256, 6564961, 20063689, 3798228, -4740178, 7359225, 2006182, -10363426, -28746253, -10197509 }, + { -10626600, -4486402, -13320562, -5125317, 3432136, -6393229, 23632037, -1940610, 32808310, 1099883 }, + { 15030977, 5768825, -27451236, -2887299, -6427378, -15361371, -15277896, -6809350, 2051441, -15225865 }, + }, + { + { -3362323, -7239372, 7517890, 9824992, 23555850, 295369, 5148398, -14154188, -22686354, 16633660 }, + { 4577086, -16752288, 13249841, -15304328, 19958763, -14537274, 18559670, -10759549, 8402478, -9864273 }, + { -28406330, -1051581, -26790155, -907698, -17212414, -11030789, 9453451, -14980072, 17983010, 9967138 }, + }, + { + { -25762494, 6524722, 26585488, 9969270, 24709298, 1220360, -1677990, 7806337, 17507396, 3651560 }, + { -10420457, -4118111, 14584639, 15971087, -15768321, 8861010, 26556809, -5574557, -18553322, -11357135 }, + { 2839101, 14284142, 4029895, 3472686, 14402957, 12689363, -26642121, 8459447, -5605463, -7621941 }, + }, + { + { -4839289, -3535444, 9744961, 2871048, 25113978, 3187018, -25110813, -849066, 17258084, -7977739 }, + { 18164541, -10595176, -17154882, -1542417, 19237078, -9745295, 23357533, -15217008, 26908270, 12150756 }, + { -30264870, -7647865, 5112249, -7036672, -1499807, -6974257, 43168, -5537701, -32302074, 16215819 }, + }, + }, + { + { + { -6898905, 9824394, -12304779, -4401089, -31397141, -6276835, 32574489, 12532905, -7503072, -8675347 }, + { -27343522, -16515468, -27151524, -10722951, 946346, 16291093, 254968, 7168080, 21676107, -1943028 }, + { 21260961, -8424752, -16831886, -11920822, -23677961, 3968121, -3651949, -6215466, -3556191, -7913075 }, + }, + { + { 16544754, 13250366, -16804428, 15546242, -4583003, 12757258, -2462308, -8680336, -18907032, -9662799 }, + { -2415239, -15577728, 18312303, 4964443, -15272530, -12653564, 26820651, 16690659, 25459437, -4564609 }, + { -25144690, 11425020, 28423002, -11020557, -6144921, -15826224, 9142795, -2391602, -6432418, -1644817 }, + }, + { + { -23104652, 6253476, 16964147, -3768872, -25113972, -12296437, -27457225, -16344658, 6335692, 7249989 }, + { -30333227, 13979675, 7503222, -12368314, -11956721, -4621693, -30272269, 2682242, 25993170, -12478523 }, + { 4364628, 5930691, 32304656, -10044554, -8054781, 15091131, 22857016, -10598955, 31820368, 15075278 }, + }, + { + { 31879134, -8918693, 17258761, 90626, -8041836, -4917709, 24162788, -9650886, -17970238, 12833045 }, + { 19073683, 14851414, -24403169, -11860168, 7625278, 11091125, -19619190, 2074449, -9413939, 14905377 }, + { 24483667, -11935567, -2518866, -11547418, -1553130, 15355506, -25282080, 9253129, 27628530, -7555480 }, + }, + { + { 17597607, 8340603, 19355617, 552187, 26198470, -3176583, 4593324, -9157582, -14110875, 15297016 }, + { 510886, 14337390, -31785257, 16638632, 6328095, 2713355, -20217417, -11864220, 8683221, 2921426 }, + { 18606791, 11874196, 27155355, -5281482, -24031742, 6265446, -25178240, -1278924, 4674690, 13890525 }, + }, + { + { 13609624, 13069022, -27372361, -13055908, 24360586, 9592974, 14977157, 9835105, 4389687, 288396 }, + { 9922506, -519394, 13613107, 5883594, -18758345, -434263, -12304062, 8317628, 23388070, 16052080 }, + { 12720016, 11937594, -31970060, -5028689, 26900120, 8561328, -20155687, -11632979, -14754271, -10812892 }, + }, + { + { 15961858, 14150409, 26716931, -665832, -22794328, 13603569, 11829573, 7467844, -28822128, 929275 }, + { 11038231, -11582396, -27310482, -7316562, -10498527, -16307831, -23479533, -9371869, -21393143, 2465074 }, + { 20017163, -4323226, 27915242, 1529148, 12396362, 15675764, 13817261, -9658066, 2463391, -4622140 }, + }, + { + { -16358878, -12663911, -12065183, 4996454, -1256422, 1073572, 9583558, 12851107, 4003896, 12673717 }, + { -1731589, -15155870, -3262930, 16143082, 19294135, 13385325, 14741514, -9103726, 7903886, 2348101 }, + { 24536016, -16515207, 12715592, -3862155, 1511293, 10047386, -3842346, -7129159, -28377538, 10048127 }, + }, + }, + { + { + { -12622226, -6204820, 30718825, 2591312, -10617028, 12192840, 18873298, -7297090, -32297756, 15221632 }, + { -26478122, -11103864, 11546244, -1852483, 9180880, 7656409, -21343950, 2095755, 29769758, 6593415 }, + { -31994208, -2907461, 4176912, 3264766, 12538965, -868111, 26312345, -6118678, 30958054, 8292160 }, + }, + { + { 31429822, -13959116, 29173532, 15632448, 12174511, -2760094, 32808831, 3977186, 26143136, -3148876 }, + { 22648901, 1402143, -22799984, 13746059, 7936347, 365344, -8668633, -1674433, -3758243, -2304625 }, + { -15491917, 8012313, -2514730, -12702462, -23965846, -10254029, -1612713, -1535569, -16664475, 8194478 }, + }, + { + { 27338066, -7507420, -7414224, 10140405, -19026427, -6589889, 27277191, 8855376, 28572286, 3005164 }, + { 26287124, 4821776, 25476601, -4145903, -3764513, -15788984, -18008582, 1182479, -26094821, -13079595 }, + { -7171154, 3178080, 23970071, 6201893, -17195577, -4489192, -21876275, -13982627, 32208683, -1198248 }, + }, + { + { -16657702, 2817643, -10286362, 14811298, 6024667, 13349505, -27315504, -10497842, -27672585, -11539858 }, + { 15941029, -9405932, -21367050, 8062055, 31876073, -238629, -15278393, -1444429, 15397331, -4130193 }, + { 8934485, -13485467, -23286397, -13423241, -32446090, 14047986, 31170398, -1441021, -27505566, 15087184 }, + }, + { + { -18357243, -2156491, 24524913, -16677868, 15520427, -6360776, -15502406, 11461896, 16788528, -5868942 }, + { -1947386, 16013773, 21750665, 3714552, -17401782, -16055433, -3770287, -10323320, 31322514, -11615635 }, + { 21426655, -5650218, -13648287, -5347537, -28812189, -4920970, -18275391, -14621414, 13040862, -12112948 }, + }, + { + { 11293895, 12478086, -27136401, 15083750, -29307421, 14748872, 14555558, -13417103, 1613711, 4896935 }, + { -25894883, 15323294, -8489791, -8057900, 25967126, -13425460, 2825960, -4897045, -23971776, -11267415 }, + { -15924766, -5229880, -17443532, 6410664, 3622847, 10243618, 20615400, 12405433, -23753030, -8436416 }, + }, + { + { -7091295, 12556208, -20191352, 9025187, -17072479, 4333801, 4378436, 2432030, 23097949, -566018 }, + { 4565804, -16025654, 20084412, -7842817, 1724999, 189254, 24767264, 10103221, -18512313, 2424778 }, + { 366633, -11976806, 8173090, -6890119, 30788634, 5745705, -7168678, 1344109, -3642553, 12412659 }, + }, + { + { -24001791, 7690286, 14929416, -168257, -32210835, -13412986, 24162697, -15326504, -3141501, 11179385 }, + { 18289522, -14724954, 8056945, 16430056, -21729724, 7842514, -6001441, -1486897, -18684645, -11443503 }, + { 476239, 6601091, -6152790, -9723375, 17503545, -4863900, 27672959, 13403813, 11052904, 5219329 }, + }, + }, + { + { + { 20678546, -8375738, -32671898, 8849123, -5009758, 14574752, 31186971, -3973730, 9014762, -8579056 }, + { -13644050, -10350239, -15962508, 5075808, -1514661, -11534600, -33102500, 9160280, 8473550, -3256838 }, + { 24900749, 14435722, 17209120, -15292541, -22592275, 9878983, -7689309, -16335821, -24568481, 11788948 }, + }, + { + { -3118155, -11395194, -13802089, 14797441, 9652448, -6845904, -20037437, 10410733, -24568470, -1458691 }, + { -15659161, 16736706, -22467150, 10215878, -9097177, 7563911, 11871841, -12505194, -18513325, 8464118 }, + { -23400612, 8348507, -14585951, -861714, -3950205, -6373419, 14325289, 8628612, 33313881, -8370517 }, + }, + { + { -20186973, -4967935, 22367356, 5271547, -1097117, -4788838, -24805667, -10236854, -8940735, -5818269 }, + { -6948785, -1795212, -32625683, -16021179, 32635414, -7374245, 15989197, -12838188, 28358192, -4253904 }, + { -23561781, -2799059, -32351682, -1661963, -9147719, 10429267, -16637684, 4072016, -5351664, 5596589 }, + }, + { + { -28236598, -3390048, 12312896, 6213178, 3117142, 16078565, 29266239, 2557221, 1768301, 15373193 }, + { -7243358, -3246960, -4593467, -7553353, -127927, -912245, -1090902, -4504991, -24660491, 3442910 }, + { -30210571, 5124043, 14181784, 8197961, 18964734, -11939093, 22597931, 7176455, -18585478, 13365930 }, + }, + { + { -7877390, -1499958, 8324673, 4690079, 6261860, 890446, 24538107, -8570186, -9689599, -3031667 }, + { 25008904, -10771599, -4305031, -9638010, 16265036, 15721635, 683793, -11823784, 15723479, -15163481 }, + { -9660625, 12374379, -27006999, -7026148, -7724114, -12314514, 11879682, 5400171, 519526, -1235876 }, + }, + { + { 22258397, -16332233, -7869817, 14613016, -22520255, -2950923, -20353881, 7315967, 16648397, 7605640 }, + { -8081308, -8464597, -8223311, 9719710, 19259459, -15348212, 23994942, -5281555, -9468848, 4763278 }, + { -21699244, 9220969, -15730624, 1084137, -25476107, -2852390, 31088447, -7764523, -11356529, 728112 }, + }, + { + { 26047220, -11751471, -6900323, -16521798, 24092068, 9158119, -4273545, -12555558, -29365436, -5498272 }, + { 17510331, -322857, 5854289, 8403524, 17133918, -3112612, -28111007, 12327945, 10750447, 10014012 }, + { -10312768, 3936952, 9156313, -8897683, 16498692, -994647, -27481051, -666732, 3424691, 7540221 }, + }, + { + { 30322361, -6964110, 11361005, -4143317, 7433304, 4989748, -7071422, -16317219, -9244265, 15258046 }, + { 13054562, -2779497, 19155474, 469045, -12482797, 4566042, 5631406, 2711395, 1062915, -5136345 }, + { -19240248, -11254599, -29509029, -7499965, -5835763, 13005411, -6066489, 12194497, 32960380, 1459310 }, + }, + }, + { + { + { 19852034, 7027924, 23669353, 10020366, 8586503, -6657907, 394197, -6101885, 18638003, -11174937 }, + { 31395534, 15098109, 26581030, 8030562, -16527914, -5007134, 9012486, -7584354, -6643087, -5442636 }, + { -9192165, -2347377, -1997099, 4529534, 25766844, 607986, -13222, 9677543, -32294889, -6456008 }, + }, + { + { -2444496, -149937, 29348902, 8186665, 1873760, 12489863, -30934579, -7839692, -7852844, -8138429 }, + { -15236356, -15433509, 7766470, 746860, 26346930, -10221762, -27333451, 10754588, -9431476, 5203576 }, + { 31834314, 14135496, -770007, 5159118, 20917671, -16768096, -7467973, -7337524, 31809243, 7347066 }, + }, + { + { -9606723, -11874240, 20414459, 13033986, 13716524, -11691881, 19797970, -12211255, 15192876, -2087490 }, + { -12663563, -2181719, 1168162, -3804809, 26747877, -14138091, 10609330, 12694420, 33473243, -13382104 }, + { 33184999, 11180355, 15832085, -11385430, -1633671, 225884, 15089336, -11023903, -6135662, 14480053 }, + }, + { + { 31308717, -5619998, 31030840, -1897099, 15674547, -6582883, 5496208, 13685227, 27595050, 8737275 }, + { -20318852, -15150239, 10933843, -16178022, 8335352, -7546022, -31008351, -12610604, 26498114, 66511 }, + { 22644454, -8761729, -16671776, 4884562, -3105614, -13559366, 30540766, -4286747, -13327787, -7515095 }, + }, + { + { -28017847, 9834845, 18617207, -2681312, -3401956, -13307506, 8205540, 13585437, -17127465, 15115439 }, + { 23711543, -672915, 31206561, -8362711, 6164647, -9709987, -33535882, -1426096, 8236921, 16492939 }, + { -23910559, -13515526, -26299483, -4503841, 25005590, -7687270, 19574902, 10071562, 6708380, -6222424 }, + }, + { + { 2101391, -4930054, 19702731, 2367575, -15427167, 1047675, 5301017, 9328700, 29955601, -11678310 }, + { 3096359, 9271816, -21620864, -15521844, -14847996, -7592937, -25892142, -12635595, -9917575, 6216608 }, + { -32615849, 338663, -25195611, 2510422, -29213566, -13820213, 24822830, -6146567, -26767480, 7525079 }, + }, + { + { -23066649, -13985623, 16133487, -7896178, -3389565, 778788, -910336, -2782495, -19386633, 11994101 }, + { 21691500, -13624626, -641331, -14367021, 3285881, -3483596, -25064666, 9718258, -7477437, 13381418 }, + { 18445390, -4202236, 14979846, 11622458, -1727110, -3582980, 23111648, -6375247, 28535282, 15779576 }, + }, + { + { 30098053, 3089662, -9234387, 16662135, -21306940, 11308411, -14068454, 12021730, 9955285, -16303356 }, + { 9734894, -14576830, -7473633, -9138735, 2060392, 11313496, -18426029, 9924399, 20194861, 13380996 }, + { -26378102, -7965207, -22167821, 15789297, -18055342, -6168792, -1984914, 15707771, 26342023, 10146099 }, + }, + }, + { + { + { -26016874, -219943, 21339191, -41388, 19745256, -2878700, -29637280, 2227040, 21612326, -545728 }, + { -13077387, 1184228, 23562814, -5970442, -20351244, -6348714, 25764461, 12243797, -20856566, 11649658 }, + { -10031494, 11262626, 27384172, 2271902, 26947504, -15997771, 39944, 6114064, 33514190, 2333242 }, + }, + { + { -21433588, -12421821, 8119782, 7219913, -21830522, -9016134, -6679750, -12670638, 24350578, -13450001 }, + { -4116307, -11271533, -23886186, 4843615, -30088339, 690623, -31536088, -10406836, 8317860, 12352766 }, + { 18200138, -14475911, -33087759, -2696619, -23702521, -9102511, -23552096, -2287550, 20712163, 6719373 }, + }, + { + { 26656208, 6075253, -7858556, 1886072, -28344043, 4262326, 11117530, -3763210, 26224235, -3297458 }, + { -17168938, -14854097, -3395676, -16369877, -19954045, 14050420, 21728352, 9493610, 18620611, -16428628 }, + { -13323321, 13325349, 11432106, 5964811, 18609221, 6062965, -5269471, -9725556, -30701573, -16479657 }, + }, + { + { -23860538, -11233159, 26961357, 1640861, -32413112, -16737940, 12248509, -5240639, 13735342, 1934062 }, + { 25089769, 6742589, 17081145, -13406266, 21909293, -16067981, -15136294, -3765346, -21277997, 5473616 }, + { 31883677, -7961101, 1083432, -11572403, 22828471, 13290673, -7125085, 12469656, 29111212, -5451014 }, + }, + { + { 24244947, -15050407, -26262976, 2791540, -14997599, 16666678, 24367466, 6388839, -10295587, 452383 }, + { -25640782, -3417841, 5217916, 16224624, 19987036, -4082269, -24236251, -5915248, 15766062, 8407814 }, + { -20406999, 13990231, 15495425, 16395525, 5377168, 15166495, -8917023, -4388953, -8067909, 2276718 }, + }, + { + { 30157918, 12924066, -17712050, 9245753, 19895028, 3368142, -23827587, 5096219, 22740376, -7303417 }, + { 2041139, -14256350, 7783687, 13876377, -25946985, -13352459, 24051124, 13742383, -15637599, 13295222 }, + { 33338237, -8505733, 12532113, 7977527, 9106186, -1715251, -17720195, -4612972, -4451357, -14669444 }, + }, + { + { -20045281, 5454097, -14346548, 6447146, 28862071, 1883651, -2469266, -4141880, 7770569, 9620597 }, + { 23208068, 7979712, 33071466, 8149229, 1758231, -10834995, 30945528, -1694323, -33502340, -14767970 }, + { 1439958, -16270480, -1079989, -793782, 4625402, 10647766, -5043801, 1220118, 30494170, -11440799 }, + }, + { + { -5037580, -13028295, -2970559, -3061767, 15640974, -6701666, -26739026, 926050, -1684339, -13333647 }, + { 13908495, -3549272, 30919928, -6273825, -21521863, 7989039, 9021034, 9078865, 3353509, 4033511 }, + { -29663431, -15113610, 32259991, -344482, 24295849, -12912123, 23161163, 8839127, 27485041, 7356032 }, + }, + }, + { + { + { 9661027, 705443, 11980065, -5370154, -1628543, 14661173, -6346142, 2625015, 28431036, -16771834 }, + { -23839233, -8311415, -25945511, 7480958, -17681669, -8354183, -22545972, 14150565, 15970762, 4099461 }, + { 29262576, 16756590, 26350592, -8793563, 8529671, -11208050, 13617293, -9937143, 11465739, 8317062 }, + }, + { + { -25493081, -6962928, 32500200, -9419051, -23038724, -2302222, 14898637, 3848455, 20969334, -5157516 }, + { -20384450, -14347713, -18336405, 13884722, -33039454, 2842114, -21610826, -3649888, 11177095, 14989547 }, + { -24496721, -11716016, 16959896, 2278463, 12066309, 10137771, 13515641, 2581286, -28487508, 9930240 }, + }, + { + { -17751622, -2097826, 16544300, -13009300, -15914807, -14949081, 18345767, -13403753, 16291481, -5314038 }, + { -33229194, 2553288, 32678213, 9875984, 8534129, 6889387, -9676774, 6957617, 4368891, 9788741 }, + { 16660756, 7281060, -10830758, 12911820, 20108584, -8101676, -21722536, -8613148, 16250552, -11111103 }, + }, + { + { -19765507, 2390526, -16551031, 14161980, 1905286, 6414907, 4689584, 10604807, -30190403, 4782747 }, + { -1354539, 14736941, -7367442, -13292886, 7710542, -14155590, -9981571, 4383045, 22546403, 437323 }, + { 31665577, -12180464, -16186830, 1491339, -18368625, 3294682, 27343084, 2786261, -30633590, -14097016 }, + }, + { + { -14467279, -683715, -33374107, 7448552, 19294360, 14334329, -19690631, 2355319, -19284671, -6114373 }, + { 15121312, -15796162, 6377020, -6031361, -10798111, -12957845, 18952177, 15496498, -29380133, 11754228 }, + { -2637277, -13483075, 8488727, -14303896, 12728761, -1622493, 7141596, 11724556, 22761615, -10134141 }, + }, + { + { 16918416, 11729663, -18083579, 3022987, -31015732, -13339659, -28741185, -12227393, 32851222, 11717399 }, + { 11166634, 7338049, -6722523, 4531520, -29468672, -7302055, 31474879, 3483633, -1193175, -4030831 }, + { -185635, 9921305, 31456609, -13536438, -12013818, 13348923, 33142652, 6546660, -19985279, -3948376 }, + }, + { + { -32460596, 11266712, -11197107, -7899103, 31703694, 3855903, -8537131, -12833048, -30772034, -15486313 }, + { -18006477, 12709068, 3991746, -6479188, -21491523, -10550425, -31135347, -16049879, 10928917, 3011958 }, + { -6957757, -15594337, 31696059, 334240, 29576716, 14796075, -30831056, -12805180, 18008031, 10258577 }, + }, + { + { -22448644, 15655569, 7018479, -4410003, -30314266, -1201591, -1853465, 1367120, 25127874, 6671743 }, + { 29701166, -14373934, -10878120, 9279288, -17568, 13127210, 21382910, 11042292, 25838796, 4642684 }, + { -20430234, 14955537, -24126347, 8124619, -5369288, -5990470, 30468147, -13900640, 18423289, 4177476 }, + }, + }, +}; diff --git a/src/ed25519/sc.c b/src/ed25519/sc.c new file mode 100644 index 0000000..ca5bad2 --- /dev/null +++ b/src/ed25519/sc.c @@ -0,0 +1,809 @@ +#include "fixedint.h" +#include "sc.h" + +static uint64_t load_3(const unsigned char *in) { + uint64_t result; + + result = (uint64_t) in[0]; + result |= ((uint64_t) in[1]) << 8; + result |= ((uint64_t) in[2]) << 16; + + return result; +} + +static uint64_t load_4(const unsigned char *in) { + uint64_t result; + + result = (uint64_t) in[0]; + result |= ((uint64_t) in[1]) << 8; + result |= ((uint64_t) in[2]) << 16; + result |= ((uint64_t) in[3]) << 24; + + return result; +} + +/* +Input: + s[0]+256*s[1]+...+256^63*s[63] = s + +Output: + s[0]+256*s[1]+...+256^31*s[31] = s mod l + where l = 2^252 + 27742317777372353535851937790883648493. + Overwrites s in place. +*/ + +void sc_reduce(unsigned char *s) { + int64_t s0 = 2097151 & load_3(s); + int64_t s1 = 2097151 & (load_4(s + 2) >> 5); + int64_t s2 = 2097151 & (load_3(s + 5) >> 2); + int64_t s3 = 2097151 & (load_4(s + 7) >> 7); + int64_t s4 = 2097151 & (load_4(s + 10) >> 4); + int64_t s5 = 2097151 & (load_3(s + 13) >> 1); + int64_t s6 = 2097151 & (load_4(s + 15) >> 6); + int64_t s7 = 2097151 & (load_3(s + 18) >> 3); + int64_t s8 = 2097151 & load_3(s + 21); + int64_t s9 = 2097151 & (load_4(s + 23) >> 5); + int64_t s10 = 2097151 & (load_3(s + 26) >> 2); + int64_t s11 = 2097151 & (load_4(s + 28) >> 7); + int64_t s12 = 2097151 & (load_4(s + 31) >> 4); + int64_t s13 = 2097151 & (load_3(s + 34) >> 1); + int64_t s14 = 2097151 & (load_4(s + 36) >> 6); + int64_t s15 = 2097151 & (load_3(s + 39) >> 3); + int64_t s16 = 2097151 & load_3(s + 42); + int64_t s17 = 2097151 & (load_4(s + 44) >> 5); + int64_t s18 = 2097151 & (load_3(s + 47) >> 2); + int64_t s19 = 2097151 & (load_4(s + 49) >> 7); + int64_t s20 = 2097151 & (load_4(s + 52) >> 4); + int64_t s21 = 2097151 & (load_3(s + 55) >> 1); + int64_t s22 = 2097151 & (load_4(s + 57) >> 6); + int64_t s23 = (load_4(s + 60) >> 3); + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + int64_t carry10; + int64_t carry11; + int64_t carry12; + int64_t carry13; + int64_t carry14; + int64_t carry15; + int64_t carry16; + + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + s23 = 0; + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + s22 = 0; + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + s21 = 0; + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + s20 = 0; + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + s19 = 0; + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + s18 = 0; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry12 = (s12 + (1 << 20)) >> 21; + s13 += carry12; + s12 -= carry12 << 21; + carry14 = (s14 + (1 << 20)) >> 21; + s15 += carry14; + s14 -= carry14 << 21; + carry16 = (s16 + (1 << 20)) >> 21; + s17 += carry16; + s16 -= carry16 << 21; + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + carry13 = (s13 + (1 << 20)) >> 21; + s14 += carry13; + s13 -= carry13 << 21; + carry15 = (s15 + (1 << 20)) >> 21; + s16 += carry15; + s15 -= carry15 << 21; + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + s17 = 0; + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + s16 = 0; + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + s15 = 0; + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + s14 = 0; + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + s13 = 0; + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + carry0 = (s0 + (1 << 20)) >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry2 = (s2 + (1 << 20)) >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry4 = (s4 + (1 << 20)) >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry1 = (s1 + (1 << 20)) >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry3 = (s3 + (1 << 20)) >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry5 = (s5 + (1 << 20)) >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + carry0 = s0 >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry1 = s1 >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry2 = s2 >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry3 = s3 >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry4 = s4 >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry5 = s5 >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry6 = s6 >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry7 = s7 >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry8 = s8 >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry9 = s9 >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry10 = s10 >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry11 = s11 >> 21; + s12 += carry11; + s11 -= carry11 << 21; + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + carry0 = s0 >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry1 = s1 >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry2 = s2 >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry3 = s3 >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry4 = s4 >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry5 = s5 >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry6 = s6 >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry7 = s7 >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry8 = s8 >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry9 = s9 >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry10 = s10 >> 21; + s11 += carry10; + s10 -= carry10 << 21; + + s[0] = (unsigned char) (s0 >> 0); + s[1] = (unsigned char) (s0 >> 8); + s[2] = (unsigned char) ((s0 >> 16) | (s1 << 5)); + s[3] = (unsigned char) (s1 >> 3); + s[4] = (unsigned char) (s1 >> 11); + s[5] = (unsigned char) ((s1 >> 19) | (s2 << 2)); + s[6] = (unsigned char) (s2 >> 6); + s[7] = (unsigned char) ((s2 >> 14) | (s3 << 7)); + s[8] = (unsigned char) (s3 >> 1); + s[9] = (unsigned char) (s3 >> 9); + s[10] = (unsigned char) ((s3 >> 17) | (s4 << 4)); + s[11] = (unsigned char) (s4 >> 4); + s[12] = (unsigned char) (s4 >> 12); + s[13] = (unsigned char) ((s4 >> 20) | (s5 << 1)); + s[14] = (unsigned char) (s5 >> 7); + s[15] = (unsigned char) ((s5 >> 15) | (s6 << 6)); + s[16] = (unsigned char) (s6 >> 2); + s[17] = (unsigned char) (s6 >> 10); + s[18] = (unsigned char) ((s6 >> 18) | (s7 << 3)); + s[19] = (unsigned char) (s7 >> 5); + s[20] = (unsigned char) (s7 >> 13); + s[21] = (unsigned char) (s8 >> 0); + s[22] = (unsigned char) (s8 >> 8); + s[23] = (unsigned char) ((s8 >> 16) | (s9 << 5)); + s[24] = (unsigned char) (s9 >> 3); + s[25] = (unsigned char) (s9 >> 11); + s[26] = (unsigned char) ((s9 >> 19) | (s10 << 2)); + s[27] = (unsigned char) (s10 >> 6); + s[28] = (unsigned char) ((s10 >> 14) | (s11 << 7)); + s[29] = (unsigned char) (s11 >> 1); + s[30] = (unsigned char) (s11 >> 9); + s[31] = (unsigned char) (s11 >> 17); +} + + + +/* +Input: + a[0]+256*a[1]+...+256^31*a[31] = a + b[0]+256*b[1]+...+256^31*b[31] = b + c[0]+256*c[1]+...+256^31*c[31] = c + +Output: + s[0]+256*s[1]+...+256^31*s[31] = (ab+c) mod l + where l = 2^252 + 27742317777372353535851937790883648493. +*/ + +void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, const unsigned char *c) { + int64_t a0 = 2097151 & load_3(a); + int64_t a1 = 2097151 & (load_4(a + 2) >> 5); + int64_t a2 = 2097151 & (load_3(a + 5) >> 2); + int64_t a3 = 2097151 & (load_4(a + 7) >> 7); + int64_t a4 = 2097151 & (load_4(a + 10) >> 4); + int64_t a5 = 2097151 & (load_3(a + 13) >> 1); + int64_t a6 = 2097151 & (load_4(a + 15) >> 6); + int64_t a7 = 2097151 & (load_3(a + 18) >> 3); + int64_t a8 = 2097151 & load_3(a + 21); + int64_t a9 = 2097151 & (load_4(a + 23) >> 5); + int64_t a10 = 2097151 & (load_3(a + 26) >> 2); + int64_t a11 = (load_4(a + 28) >> 7); + int64_t b0 = 2097151 & load_3(b); + int64_t b1 = 2097151 & (load_4(b + 2) >> 5); + int64_t b2 = 2097151 & (load_3(b + 5) >> 2); + int64_t b3 = 2097151 & (load_4(b + 7) >> 7); + int64_t b4 = 2097151 & (load_4(b + 10) >> 4); + int64_t b5 = 2097151 & (load_3(b + 13) >> 1); + int64_t b6 = 2097151 & (load_4(b + 15) >> 6); + int64_t b7 = 2097151 & (load_3(b + 18) >> 3); + int64_t b8 = 2097151 & load_3(b + 21); + int64_t b9 = 2097151 & (load_4(b + 23) >> 5); + int64_t b10 = 2097151 & (load_3(b + 26) >> 2); + int64_t b11 = (load_4(b + 28) >> 7); + int64_t c0 = 2097151 & load_3(c); + int64_t c1 = 2097151 & (load_4(c + 2) >> 5); + int64_t c2 = 2097151 & (load_3(c + 5) >> 2); + int64_t c3 = 2097151 & (load_4(c + 7) >> 7); + int64_t c4 = 2097151 & (load_4(c + 10) >> 4); + int64_t c5 = 2097151 & (load_3(c + 13) >> 1); + int64_t c6 = 2097151 & (load_4(c + 15) >> 6); + int64_t c7 = 2097151 & (load_3(c + 18) >> 3); + int64_t c8 = 2097151 & load_3(c + 21); + int64_t c9 = 2097151 & (load_4(c + 23) >> 5); + int64_t c10 = 2097151 & (load_3(c + 26) >> 2); + int64_t c11 = (load_4(c + 28) >> 7); + int64_t s0; + int64_t s1; + int64_t s2; + int64_t s3; + int64_t s4; + int64_t s5; + int64_t s6; + int64_t s7; + int64_t s8; + int64_t s9; + int64_t s10; + int64_t s11; + int64_t s12; + int64_t s13; + int64_t s14; + int64_t s15; + int64_t s16; + int64_t s17; + int64_t s18; + int64_t s19; + int64_t s20; + int64_t s21; + int64_t s22; + int64_t s23; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + int64_t carry10; + int64_t carry11; + int64_t carry12; + int64_t carry13; + int64_t carry14; + int64_t carry15; + int64_t carry16; + int64_t carry17; + int64_t carry18; + int64_t carry19; + int64_t carry20; + int64_t carry21; + int64_t carry22; + + s0 = c0 + a0 * b0; + s1 = c1 + a0 * b1 + a1 * b0; + s2 = c2 + a0 * b2 + a1 * b1 + a2 * b0; + s3 = c3 + a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0; + s4 = c4 + a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0; + s5 = c5 + a0 * b5 + a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1 + a5 * b0; + s6 = c6 + a0 * b6 + a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1 + a6 * b0; + s7 = c7 + a0 * b7 + a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 + a6 * b1 + a7 * b0; + s8 = c8 + a0 * b8 + a1 * b7 + a2 * b6 + a3 * b5 + a4 * b4 + a5 * b3 + a6 * b2 + a7 * b1 + a8 * b0; + s9 = c9 + a0 * b9 + a1 * b8 + a2 * b7 + a3 * b6 + a4 * b5 + a5 * b4 + a6 * b3 + a7 * b2 + a8 * b1 + a9 * b0; + s10 = c10 + a0 * b10 + a1 * b9 + a2 * b8 + a3 * b7 + a4 * b6 + a5 * b5 + a6 * b4 + a7 * b3 + a8 * b2 + a9 * b1 + a10 * b0; + s11 = c11 + a0 * b11 + a1 * b10 + a2 * b9 + a3 * b8 + a4 * b7 + a5 * b6 + a6 * b5 + a7 * b4 + a8 * b3 + a9 * b2 + a10 * b1 + a11 * b0; + s12 = a1 * b11 + a2 * b10 + a3 * b9 + a4 * b8 + a5 * b7 + a6 * b6 + a7 * b5 + a8 * b4 + a9 * b3 + a10 * b2 + a11 * b1; + s13 = a2 * b11 + a3 * b10 + a4 * b9 + a5 * b8 + a6 * b7 + a7 * b6 + a8 * b5 + a9 * b4 + a10 * b3 + a11 * b2; + s14 = a3 * b11 + a4 * b10 + a5 * b9 + a6 * b8 + a7 * b7 + a8 * b6 + a9 * b5 + a10 * b4 + a11 * b3; + s15 = a4 * b11 + a5 * b10 + a6 * b9 + a7 * b8 + a8 * b7 + a9 * b6 + a10 * b5 + a11 * b4; + s16 = a5 * b11 + a6 * b10 + a7 * b9 + a8 * b8 + a9 * b7 + a10 * b6 + a11 * b5; + s17 = a6 * b11 + a7 * b10 + a8 * b9 + a9 * b8 + a10 * b7 + a11 * b6; + s18 = a7 * b11 + a8 * b10 + a9 * b9 + a10 * b8 + a11 * b7; + s19 = a8 * b11 + a9 * b10 + a10 * b9 + a11 * b8; + s20 = a9 * b11 + a10 * b10 + a11 * b9; + s21 = a10 * b11 + a11 * b10; + s22 = a11 * b11; + s23 = 0; + carry0 = (s0 + (1 << 20)) >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry2 = (s2 + (1 << 20)) >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry4 = (s4 + (1 << 20)) >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry12 = (s12 + (1 << 20)) >> 21; + s13 += carry12; + s12 -= carry12 << 21; + carry14 = (s14 + (1 << 20)) >> 21; + s15 += carry14; + s14 -= carry14 << 21; + carry16 = (s16 + (1 << 20)) >> 21; + s17 += carry16; + s16 -= carry16 << 21; + carry18 = (s18 + (1 << 20)) >> 21; + s19 += carry18; + s18 -= carry18 << 21; + carry20 = (s20 + (1 << 20)) >> 21; + s21 += carry20; + s20 -= carry20 << 21; + carry22 = (s22 + (1 << 20)) >> 21; + s23 += carry22; + s22 -= carry22 << 21; + carry1 = (s1 + (1 << 20)) >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry3 = (s3 + (1 << 20)) >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry5 = (s5 + (1 << 20)) >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + carry13 = (s13 + (1 << 20)) >> 21; + s14 += carry13; + s13 -= carry13 << 21; + carry15 = (s15 + (1 << 20)) >> 21; + s16 += carry15; + s15 -= carry15 << 21; + carry17 = (s17 + (1 << 20)) >> 21; + s18 += carry17; + s17 -= carry17 << 21; + carry19 = (s19 + (1 << 20)) >> 21; + s20 += carry19; + s19 -= carry19 << 21; + carry21 = (s21 + (1 << 20)) >> 21; + s22 += carry21; + s21 -= carry21 << 21; + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + s23 = 0; + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + s22 = 0; + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + s21 = 0; + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + s20 = 0; + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + s19 = 0; + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + s18 = 0; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry12 = (s12 + (1 << 20)) >> 21; + s13 += carry12; + s12 -= carry12 << 21; + carry14 = (s14 + (1 << 20)) >> 21; + s15 += carry14; + s14 -= carry14 << 21; + carry16 = (s16 + (1 << 20)) >> 21; + s17 += carry16; + s16 -= carry16 << 21; + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + carry13 = (s13 + (1 << 20)) >> 21; + s14 += carry13; + s13 -= carry13 << 21; + carry15 = (s15 + (1 << 20)) >> 21; + s16 += carry15; + s15 -= carry15 << 21; + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + s17 = 0; + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + s16 = 0; + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + s15 = 0; + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + s14 = 0; + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + s13 = 0; + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + carry0 = (s0 + (1 << 20)) >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry2 = (s2 + (1 << 20)) >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry4 = (s4 + (1 << 20)) >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry1 = (s1 + (1 << 20)) >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry3 = (s3 + (1 << 20)) >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry5 = (s5 + (1 << 20)) >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + carry0 = s0 >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry1 = s1 >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry2 = s2 >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry3 = s3 >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry4 = s4 >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry5 = s5 >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry6 = s6 >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry7 = s7 >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry8 = s8 >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry9 = s9 >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry10 = s10 >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry11 = s11 >> 21; + s12 += carry11; + s11 -= carry11 << 21; + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + carry0 = s0 >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry1 = s1 >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry2 = s2 >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry3 = s3 >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry4 = s4 >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry5 = s5 >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry6 = s6 >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry7 = s7 >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry8 = s8 >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry9 = s9 >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry10 = s10 >> 21; + s11 += carry10; + s10 -= carry10 << 21; + + s[0] = (unsigned char) (s0 >> 0); + s[1] = (unsigned char) (s0 >> 8); + s[2] = (unsigned char) ((s0 >> 16) | (s1 << 5)); + s[3] = (unsigned char) (s1 >> 3); + s[4] = (unsigned char) (s1 >> 11); + s[5] = (unsigned char) ((s1 >> 19) | (s2 << 2)); + s[6] = (unsigned char) (s2 >> 6); + s[7] = (unsigned char) ((s2 >> 14) | (s3 << 7)); + s[8] = (unsigned char) (s3 >> 1); + s[9] = (unsigned char) (s3 >> 9); + s[10] = (unsigned char) ((s3 >> 17) | (s4 << 4)); + s[11] = (unsigned char) (s4 >> 4); + s[12] = (unsigned char) (s4 >> 12); + s[13] = (unsigned char) ((s4 >> 20) | (s5 << 1)); + s[14] = (unsigned char) (s5 >> 7); + s[15] = (unsigned char) ((s5 >> 15) | (s6 << 6)); + s[16] = (unsigned char) (s6 >> 2); + s[17] = (unsigned char) (s6 >> 10); + s[18] = (unsigned char) ((s6 >> 18) | (s7 << 3)); + s[19] = (unsigned char) (s7 >> 5); + s[20] = (unsigned char) (s7 >> 13); + s[21] = (unsigned char) (s8 >> 0); + s[22] = (unsigned char) (s8 >> 8); + s[23] = (unsigned char) ((s8 >> 16) | (s9 << 5)); + s[24] = (unsigned char) (s9 >> 3); + s[25] = (unsigned char) (s9 >> 11); + s[26] = (unsigned char) ((s9 >> 19) | (s10 << 2)); + s[27] = (unsigned char) (s10 >> 6); + s[28] = (unsigned char) ((s10 >> 14) | (s11 << 7)); + s[29] = (unsigned char) (s11 >> 1); + s[30] = (unsigned char) (s11 >> 9); + s[31] = (unsigned char) (s11 >> 17); +} diff --git a/src/ed25519/sc.h b/src/ed25519/sc.h new file mode 100644 index 0000000..e29e7fa --- /dev/null +++ b/src/ed25519/sc.h @@ -0,0 +1,12 @@ +#ifndef SC_H +#define SC_H + +/* +The set of scalars is \Z/l +where l = 2^252 + 27742317777372353535851937790883648493. +*/ + +void sc_reduce(unsigned char *s); +void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, const unsigned char *c); + +#endif diff --git a/src/ed25519/seed.c b/src/ed25519/seed.c new file mode 100644 index 0000000..15f41c0 --- /dev/null +++ b/src/ed25519/seed.c @@ -0,0 +1,44 @@ +#include "ed25519.h" + +#ifndef ED25519_NO_SEED + +#ifdef _WIN32 +#include +#include +#else +#include +#endif + +int ed25519_create_seed(unsigned char *seed) { +#ifdef _WIN32 + HCRYPTPROV prov; + + if (!CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + return 1; + } + + if (!CryptGenRandom(prov, 32, seed)) { + CryptReleaseContext(prov, 0); + return 1; + } + + CryptReleaseContext(prov, 0); +#else + FILE *f = fopen("/dev/urandom", "rb"); + + if (f == NULL) { + return 1; + } + + if(fread(seed, 32, 1, f) != 1) { + fclose(f); + return 1; + } + + fclose(f); +#endif + + return 0; +} + +#endif diff --git a/src/ed25519/sha512.c b/src/ed25519/sha512.c new file mode 100644 index 0000000..45cbf9c --- /dev/null +++ b/src/ed25519/sha512.c @@ -0,0 +1,282 @@ +/* LibTomCrypt, modular cryptographic library -- Tom St Denis + * + * LibTomCrypt is a library that provides various cryptographic + * algorithms in a highly modular and flexible manner. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tomstdenis@gmail.com, http://libtom.org + */ + +#include "fixedint.h" +#include "sha512.h" + +/* the K array */ +static const uint64_t K[80] = { + UINT64_C(0x428a2f98d728ae22), UINT64_C(0x7137449123ef65cd), + UINT64_C(0xb5c0fbcfec4d3b2f), UINT64_C(0xe9b5dba58189dbbc), + UINT64_C(0x3956c25bf348b538), UINT64_C(0x59f111f1b605d019), + UINT64_C(0x923f82a4af194f9b), UINT64_C(0xab1c5ed5da6d8118), + UINT64_C(0xd807aa98a3030242), UINT64_C(0x12835b0145706fbe), + UINT64_C(0x243185be4ee4b28c), UINT64_C(0x550c7dc3d5ffb4e2), + UINT64_C(0x72be5d74f27b896f), UINT64_C(0x80deb1fe3b1696b1), + UINT64_C(0x9bdc06a725c71235), UINT64_C(0xc19bf174cf692694), + UINT64_C(0xe49b69c19ef14ad2), UINT64_C(0xefbe4786384f25e3), + UINT64_C(0x0fc19dc68b8cd5b5), UINT64_C(0x240ca1cc77ac9c65), + UINT64_C(0x2de92c6f592b0275), UINT64_C(0x4a7484aa6ea6e483), + UINT64_C(0x5cb0a9dcbd41fbd4), UINT64_C(0x76f988da831153b5), + UINT64_C(0x983e5152ee66dfab), UINT64_C(0xa831c66d2db43210), + UINT64_C(0xb00327c898fb213f), UINT64_C(0xbf597fc7beef0ee4), + UINT64_C(0xc6e00bf33da88fc2), UINT64_C(0xd5a79147930aa725), + UINT64_C(0x06ca6351e003826f), UINT64_C(0x142929670a0e6e70), + UINT64_C(0x27b70a8546d22ffc), UINT64_C(0x2e1b21385c26c926), + UINT64_C(0x4d2c6dfc5ac42aed), UINT64_C(0x53380d139d95b3df), + UINT64_C(0x650a73548baf63de), UINT64_C(0x766a0abb3c77b2a8), + UINT64_C(0x81c2c92e47edaee6), UINT64_C(0x92722c851482353b), + UINT64_C(0xa2bfe8a14cf10364), UINT64_C(0xa81a664bbc423001), + UINT64_C(0xc24b8b70d0f89791), UINT64_C(0xc76c51a30654be30), + UINT64_C(0xd192e819d6ef5218), UINT64_C(0xd69906245565a910), + UINT64_C(0xf40e35855771202a), UINT64_C(0x106aa07032bbd1b8), + UINT64_C(0x19a4c116b8d2d0c8), UINT64_C(0x1e376c085141ab53), + UINT64_C(0x2748774cdf8eeb99), UINT64_C(0x34b0bcb5e19b48a8), + UINT64_C(0x391c0cb3c5c95a63), UINT64_C(0x4ed8aa4ae3418acb), + UINT64_C(0x5b9cca4f7763e373), UINT64_C(0x682e6ff3d6b2b8a3), + UINT64_C(0x748f82ee5defb2fc), UINT64_C(0x78a5636f43172f60), + UINT64_C(0x84c87814a1f0ab72), UINT64_C(0x8cc702081a6439ec), + UINT64_C(0x90befffa23631e28), UINT64_C(0xa4506cebde82bde9), + UINT64_C(0xbef9a3f7b2c67915), UINT64_C(0xc67178f2e372532b), + UINT64_C(0xca273eceea26619c), UINT64_C(0xd186b8c721c0c207), + UINT64_C(0xeada7dd6cde0eb1e), UINT64_C(0xf57d4f7fee6ed178), + UINT64_C(0x06f067aa72176fba), UINT64_C(0x0a637dc5a2c898a6), + UINT64_C(0x113f9804bef90dae), UINT64_C(0x1b710b35131c471b), + UINT64_C(0x28db77f523047d84), UINT64_C(0x32caab7b40c72493), + UINT64_C(0x3c9ebe0a15c9bebc), UINT64_C(0x431d67c49c100d4c), + UINT64_C(0x4cc5d4becb3e42b6), UINT64_C(0x597f299cfc657e2a), + UINT64_C(0x5fcb6fab3ad6faec), UINT64_C(0x6c44198c4a475817) +}; + +/* Various logical functions */ + +#define ROR64c(x, y) \ + ( ((((x)&UINT64_C(0xFFFFFFFFFFFFFFFF))>>((uint64_t)(y)&UINT64_C(63))) | \ + ((x)<<((uint64_t)(64-((y)&UINT64_C(63)))))) & UINT64_C(0xFFFFFFFFFFFFFFFF)) + +#define STORE64H(x, y) \ + { (y)[0] = (unsigned char)(((x)>>56)&255); (y)[1] = (unsigned char)(((x)>>48)&255); \ + (y)[2] = (unsigned char)(((x)>>40)&255); (y)[3] = (unsigned char)(((x)>>32)&255); \ + (y)[4] = (unsigned char)(((x)>>24)&255); (y)[5] = (unsigned char)(((x)>>16)&255); \ + (y)[6] = (unsigned char)(((x)>>8)&255); (y)[7] = (unsigned char)((x)&255); } + +#define LOAD64H(x, y) \ + { x = (((uint64_t)((y)[0] & 255))<<56)|(((uint64_t)((y)[1] & 255))<<48) | \ + (((uint64_t)((y)[2] & 255))<<40)|(((uint64_t)((y)[3] & 255))<<32) | \ + (((uint64_t)((y)[4] & 255))<<24)|(((uint64_t)((y)[5] & 255))<<16) | \ + (((uint64_t)((y)[6] & 255))<<8)|(((uint64_t)((y)[7] & 255))); } + +#define Ch(x,y,z) (z ^ (x & (y ^ z))) +#define Maj(x,y,z) (((x | y) & z) | (x & y)) +#define S(x, n) ROR64c(x, n) +#define R(x, n) (((x) &UINT64_C(0xFFFFFFFFFFFFFFFF))>>((uint64_t)n)) +#define Sigma0(x) (S(x, 28) ^ S(x, 34) ^ S(x, 39)) +#define Sigma1(x) (S(x, 14) ^ S(x, 18) ^ S(x, 41)) +#define Gamma0(x) (S(x, 1) ^ S(x, 8) ^ R(x, 7)) +#define Gamma1(x) (S(x, 19) ^ S(x, 61) ^ R(x, 6)) +#ifndef MIN +#define MIN(x, y) ( ((x)<(y))?(x):(y) ) +#endif + +/* compress 1024-bits */ +static int sha512_compress(sha512_context *md, const unsigned char *buf) +{ + uint64_t S[8], W[80], t0, t1; + int i; + + /* copy state into S */ + for (i = 0; i < 8; i++) { + S[i] = md->state[i]; + } + + /* copy the state into 1024-bits into W[0..15] */ + for (i = 0; i < 16; i++) { + LOAD64H(W[i], buf + (8 * i)); + } + + /* fill W[16..79] */ + for (i = 16; i < 80; i++) { + W[i] = + Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16]; + } + +/* Compress */ +#define RND(a,b,c,d,e,f,g,h,i) \ + t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; \ + t1 = Sigma0(a) + Maj(a, b, c);\ + d += t0; \ + h = t0 + t1; + + for (i = 0; i < 80; i += 8) { + RND(S[0], S[1], S[2], S[3], S[4], S[5], S[6], S[7], i + 0); + RND(S[7], S[0], S[1], S[2], S[3], S[4], S[5], S[6], i + 1); + RND(S[6], S[7], S[0], S[1], S[2], S[3], S[4], S[5], i + 2); + RND(S[5], S[6], S[7], S[0], S[1], S[2], S[3], S[4], i + 3); + RND(S[4], S[5], S[6], S[7], S[0], S[1], S[2], S[3], i + 4); + RND(S[3], S[4], S[5], S[6], S[7], S[0], S[1], S[2], i + 5); + RND(S[2], S[3], S[4], S[5], S[6], S[7], S[0], S[1], i + 6); + RND(S[1], S[2], S[3], S[4], S[5], S[6], S[7], S[0], i + 7); + } + +#undef RND + + /* feedback */ + for (i = 0; i < 8; i++) { + md->state[i] = md->state[i] + S[i]; + } + + return 0; +} + +/** + Initialize the hash state + @param md The hash state you wish to initialize + @return 0 if successful +*/ +int sha512_init(sha512_context *md) +{ + if (md == NULL) + return 1; + + md->curlen = 0; + md->length = 0; + md->state[0] = UINT64_C(0x6a09e667f3bcc908); + md->state[1] = UINT64_C(0xbb67ae8584caa73b); + md->state[2] = UINT64_C(0x3c6ef372fe94f82b); + md->state[3] = UINT64_C(0xa54ff53a5f1d36f1); + md->state[4] = UINT64_C(0x510e527fade682d1); + md->state[5] = UINT64_C(0x9b05688c2b3e6c1f); + md->state[6] = UINT64_C(0x1f83d9abfb41bd6b); + md->state[7] = UINT64_C(0x5be0cd19137e2179); + + return 0; +} + +/** + Process a block of memory though the hash + @param md The hash state + @param in The data to hash + @param inlen The length of the data (octets) + @return 0 if successful +*/ +int sha512_update(sha512_context *md, const void *vin, size_t inlen) +{ + const unsigned char *in = vin; + size_t n; + size_t i; + int err; + if (md == NULL) + return 1; + if (in == NULL) + return 1; + if (md->curlen > sizeof(md->buf)) { + return 1; + } + while (inlen > 0) { + if (md->curlen == 0 && inlen >= 128) { + if ((err = sha512_compress(md, in)) != 0) { + return err; + } + md->length += 128 * 8; + in += 128; + inlen -= 128; + } else { + n = MIN(inlen, (128 - md->curlen)); + + for (i = 0; i < n; i++) { + md->buf[i + md->curlen] = in[i]; + } + + md->curlen += n; + in += n; + inlen -= n; + if (md->curlen == 128) { + if ((err = sha512_compress(md, md->buf)) != 0) { + return err; + } + md->length += 8 * 128; + md->curlen = 0; + } + } + } + return 0; +} + +/** + Terminate the hash to get the digest + @param md The hash state + @param out [out] The destination of the hash (64 bytes) + @return 0 if successful +*/ +int sha512_final(sha512_context *md, void *vout) +{ + unsigned char *out = vout; + int i; + + if (md == NULL) + return 1; + if (out == NULL) + return 1; + + if (md->curlen >= sizeof(md->buf)) { + return 1; + } + + /* increase the length of the message */ + md->length += md->curlen * UINT64_C(8); + + /* append the '1' bit */ + md->buf[md->curlen++] = 0x80; + + /* if the length is currently above 112 bytes we append zeros + * then compress. Then we can fall back to padding zeros and length + * encoding like normal. + */ + if (md->curlen > 112) { + while (md->curlen < 128) { + md->buf[md->curlen++] = 0; + } + sha512_compress(md, md->buf); + md->curlen = 0; + } + + /* pad up to 120 bytes of zeroes + * note: that from 112 to 120 is the 64 MSB of the length. We assume that you won't hash + * > 2^64 bits of data... :-) + */ + while (md->curlen < 120) { + md->buf[md->curlen++] = 0; + } + + /* store length */ + STORE64H(md->length, md->buf + 120); + sha512_compress(md, md->buf); + + /* copy output */ + for (i = 0; i < 8; i++) { + STORE64H(md->state[i], out + (8 * i)); + } + + return 0; +} + +int sha512(const void *message, size_t message_len, void *out) +{ + sha512_context ctx; + int ret; + if ((ret = sha512_init(&ctx))) + return ret; + if ((ret = sha512_update(&ctx, message, message_len))) + return ret; + if ((ret = sha512_final(&ctx, out))) + return ret; + return 0; +} diff --git a/src/ed25519/sha512.h b/src/ed25519/sha512.h new file mode 100644 index 0000000..2676021 --- /dev/null +++ b/src/ed25519/sha512.h @@ -0,0 +1,20 @@ +#ifndef SHA512_H +#define SHA512_H + +#include + +#include "fixedint.h" + +/* state */ +typedef struct sha512_context_ { + uint64_t length, state[8]; + size_t curlen; + unsigned char buf[128]; +} sha512_context; + +int sha512_init(sha512_context *md); +int sha512_final(sha512_context *md, void *out); +int sha512_update(sha512_context *md, const void *in, size_t inlen); +int sha512(const void *message, size_t message_len, void *out); + +#endif diff --git a/src/ed25519/sign.c b/src/ed25519/sign.c new file mode 100644 index 0000000..199a839 --- /dev/null +++ b/src/ed25519/sign.c @@ -0,0 +1,31 @@ +#include "ed25519.h" +#include "sha512.h" +#include "ge.h" +#include "sc.h" + + +void ed25519_sign(unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key, const unsigned char *private_key) { + sha512_context hash; + unsigned char hram[64]; + unsigned char r[64]; + ge_p3 R; + + + sha512_init(&hash); + sha512_update(&hash, private_key + 32, 32); + sha512_update(&hash, message, message_len); + sha512_final(&hash, r); + + sc_reduce(r); + ge_scalarmult_base(&R, r); + ge_p3_tobytes(signature, &R); + + sha512_init(&hash); + sha512_update(&hash, signature, 32); + sha512_update(&hash, public_key, 32); + sha512_update(&hash, message, message_len); + sha512_final(&hash, hram); + + sc_reduce(hram); + sc_muladd(signature + 32, hram, private_key, r); +} diff --git a/src/ed25519/verify.c b/src/ed25519/verify.c new file mode 100644 index 0000000..32f988e --- /dev/null +++ b/src/ed25519/verify.c @@ -0,0 +1,77 @@ +#include "ed25519.h" +#include "sha512.h" +#include "ge.h" +#include "sc.h" + +static int consttime_equal(const unsigned char *x, const unsigned char *y) { + unsigned char r = 0; + + r = x[0] ^ y[0]; + #define F(i) r |= x[i] ^ y[i] + F(1); + F(2); + F(3); + F(4); + F(5); + F(6); + F(7); + F(8); + F(9); + F(10); + F(11); + F(12); + F(13); + F(14); + F(15); + F(16); + F(17); + F(18); + F(19); + F(20); + F(21); + F(22); + F(23); + F(24); + F(25); + F(26); + F(27); + F(28); + F(29); + F(30); + F(31); + #undef F + + return !r; +} + +int ed25519_verify(const unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key) { + unsigned char h[64]; + unsigned char checker[32]; + sha512_context hash; + ge_p3 A; + ge_p2 R; + + if (signature[63] & 224) { + return 0; + } + + if (ge_frombytes_negate_vartime(&A, public_key) != 0) { + return 0; + } + + sha512_init(&hash); + sha512_update(&hash, signature, 32); + sha512_update(&hash, public_key, 32); + sha512_update(&hash, message, message_len); + sha512_final(&hash, h); + + sc_reduce(h); + ge_double_scalarmult_vartime(&R, h, &A, signature + 32); + ge_tobytes(checker, &R); + + if (!consttime_equal(checker, signature)) { + return 0; + } + + return 1; +} diff --git a/src/edge.c b/src/edge.c new file mode 100644 index 0000000..afe9cfe --- /dev/null +++ b/src/edge.c @@ -0,0 +1,115 @@ +/* + edge.c -- edge tree management + Copyright (C) 2014 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "splay_tree.h" +#include "edge.h" +#include "logger.h" +#include "meshlink_internal.h" +#include "netutl.h" +#include "node.h" +#include "utils.h" +#include "xalloc.h" + +static int edge_compare(const edge_t *a, const edge_t *b) { + return strcmp(a->to->name, b->to->name); +} + +static int edge_weight_compare(const edge_t *a, const edge_t *b) { + int result; + + result = a->weight - b->weight; + + if(result) { + return result; + } + + result = strcmp(a->from->name, b->from->name); + + if(result) { + return result; + } + + return strcmp(a->to->name, b->to->name); +} + +void init_edges(meshlink_handle_t *mesh) { + mesh->edges = splay_alloc_tree((splay_compare_t) edge_weight_compare, NULL); +} + +splay_tree_t *new_edge_tree(void) { + return splay_alloc_tree((splay_compare_t) edge_compare, (splay_action_t) free_edge); +} + +void free_edge_tree(splay_tree_t *edge_tree) { + splay_delete_tree(edge_tree); +} + +void exit_edges(meshlink_handle_t *mesh) { + if(mesh->edges) { + splay_delete_tree(mesh->edges); + } + + mesh->edges = NULL; +} + +/* Creation and deletion of connection elements */ + +edge_t *new_edge(void) { + return xzalloc(sizeof(edge_t)); +} + +void free_edge(edge_t *e) { + sockaddrfree(&e->address); + + free(e); +} + +void edge_add(meshlink_handle_t *mesh, edge_t *e) { + splay_insert(mesh->edges, e); + splay_insert(e->from->edge_tree, e); + + e->reverse = lookup_edge(e->to, e->from); + + if(e->reverse) { + e->reverse->reverse = e; + } +} + +void edge_del(meshlink_handle_t *mesh, edge_t *e) { + if(e->reverse) { + e->reverse->reverse = NULL; + } + + splay_delete(mesh->edges, e); + splay_delete(e->from->edge_tree, e); +} + +edge_t *lookup_edge(node_t *from, node_t *to) { + assert(from); + assert(to); + + edge_t v; + + v.from = from; + v.to = to; + + return splay_search(from->edge_tree, &v); +} diff --git a/src/edge.h b/src/edge.h new file mode 100644 index 0000000..d5ec117 --- /dev/null +++ b/src/edge.h @@ -0,0 +1,50 @@ +#ifndef MESHLINK_EDGE_H +#define MESHLINK_EDGE_H + +/* + edge.h -- header for edge.c + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "splay_tree.h" +#include "connection.h" +#include "net.h" +#include "node.h" + +typedef struct edge_t { + struct node_t *from; + struct node_t *to; + sockaddr_t address; + + struct connection_t *connection; /* connection associated with this edge, if available */ + struct edge_t *reverse; /* edge in the opposite direction, if available */ + + int weight; /* weight of this edge */ + uint32_t session_id; /* the session_id of the from node */ +} edge_t; + +void init_edges(struct meshlink_handle *mesh); +void exit_edges(struct meshlink_handle *mesh); +edge_t *new_edge(void) __attribute__((__malloc__)); +void free_edge(edge_t *); +struct splay_tree_t *new_edge_tree(void) __attribute__((__malloc__)); +void free_edge_tree(struct splay_tree_t *); +void edge_add(struct meshlink_handle *mesh, edge_t *); +void edge_del(struct meshlink_handle *mesh, edge_t *); +edge_t *lookup_edge(struct node_t *, struct node_t *) __attribute__((__warn_unused_result__)); + +#endif diff --git a/src/event.c b/src/event.c new file mode 100644 index 0000000..f8ebe8b --- /dev/null +++ b/src/event.c @@ -0,0 +1,484 @@ +/* + event.c -- I/O, timeout and signal event handling + Copyright (C) 2014-2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "dropin.h" +#include "event.h" +#include "logger.h" +#include "meshlink.h" +#include "net.h" +#include "splay_tree.h" +#include "utils.h" +#include "xalloc.h" + +#ifndef EVENT_CLOCK +#if defined(CLOCK_MONOTONIC_RAW) && defined(__x86_64__) +#define EVENT_CLOCK CLOCK_MONOTONIC_RAW +#else +#define EVENT_CLOCK CLOCK_MONOTONIC +#endif +#endif + +static void timespec_add(const struct timespec *a, const struct timespec *b, struct timespec *r) { + r->tv_sec = a->tv_sec + b->tv_sec; + r->tv_nsec = a->tv_nsec + b->tv_nsec; + + if(r->tv_nsec > 1000000000) { + r->tv_sec++, r->tv_nsec -= 1000000000; + } +} + +static void timespec_sub(const struct timespec *a, const struct timespec *b, struct timespec *r) { + r->tv_sec = a->tv_sec - b->tv_sec; + r->tv_nsec = a->tv_nsec - b->tv_nsec; + + if(r->tv_nsec < 0) { + r->tv_sec--, r->tv_nsec += 1000000000; + } +} + +static bool timespec_lt(const struct timespec *a, const struct timespec *b) { + if(a->tv_sec == b->tv_sec) { + return a->tv_nsec < b->tv_nsec; + } else { + return a->tv_sec < b->tv_sec; + } +} + +static void timespec_clear(struct timespec *a) { + a->tv_sec = 0; +} + +static int io_compare(const io_t *a, const io_t *b) { + return a->fd - b->fd; +} + +static int timeout_compare(const timeout_t *a, const timeout_t *b) { + if(a->tv.tv_sec < b->tv.tv_sec) { + return -1; + } else if(a->tv.tv_sec > b->tv.tv_sec) { + return 1; + } else if(a->tv.tv_nsec < b->tv.tv_nsec) { + return -1; + } else if(a->tv.tv_nsec > b->tv.tv_nsec) { + return 1; + } else if(a < b) { + return -1; + } else if(a > b) { + return 1; + } else { + return 0; + } +} + +void io_add(event_loop_t *loop, io_t *io, io_cb_t cb, void *data, int fd, int flags) { + assert(!io->cb); + + io->fd = fd; + io->cb = cb; + io->data = data; + io->node.data = io; + + io_set(loop, io, flags); + + splay_node_t *node = splay_insert_node(&loop->ios, &io->node); + assert(node); + (void)node; +} + +void io_set(event_loop_t *loop, io_t *io, int flags) { + assert(io->cb); + + io->flags = flags; + + if(flags & IO_READ) { + FD_SET(io->fd, &loop->readfds); + } else { + FD_CLR(io->fd, &loop->readfds); + } + + if(flags & IO_WRITE) { + FD_SET(io->fd, &loop->writefds); + } else { + FD_CLR(io->fd, &loop->writefds); + } +} + +void io_del(event_loop_t *loop, io_t *io) { + assert(io->cb); + + loop->deletion = true; + + io_set(loop, io, 0); + + splay_unlink_node(&loop->ios, &io->node); + io->cb = NULL; +} + +void timeout_add(event_loop_t *loop, timeout_t *timeout, timeout_cb_t cb, void *data, struct timespec *tv) { + timeout->cb = cb; + timeout->data = data; + + timeout_set(loop, timeout, tv); +} + +void timeout_set(event_loop_t *loop, timeout_t *timeout, struct timespec *tv) { + assert(timeout->cb); + + if(timeout->node.data) { + splay_unlink_node(&loop->timeouts, &timeout->node); + } else { + timeout->node.data = timeout; + } + + if(!loop->now.tv_sec) { + clock_gettime(EVENT_CLOCK, &loop->now); + } + + timespec_add(&loop->now, tv, &timeout->tv); + + if(!splay_insert_node(&loop->timeouts, &timeout->node)) { + abort(); + } + + loop->deletion = true; +} + +static void timeout_disable(event_loop_t *loop, timeout_t *timeout) { + if(timeout->node.data) { + splay_unlink_node(&loop->timeouts, &timeout->node); + timeout->node.data = NULL; + } + + timespec_clear(&timeout->tv); +} + +void timeout_del(event_loop_t *loop, timeout_t *timeout) { + if(!timeout->cb) { + return; + } + + if(timeout->node.data) { + timeout_disable(loop, timeout); + } + + timeout->cb = NULL; + loop->deletion = true; +} + +static int signal_compare(const signal_t *a, const signal_t *b) { + return (int)a->signum - (int)b->signum; +} + +static void signalio_handler(event_loop_t *loop, void *data, int flags) { + (void)data; + (void)flags; + unsigned char signum; + + if(read(loop->pipefd[0], &signum, 1) != 1) { + return; + } + + signal_t *sig = splay_search(&loop->signals, &(signal_t) { + .signum = signum + }); + + if(sig) { +#ifdef HAVE_STDATOMIC_H + atomic_flag_clear(&sig->set); +#endif + sig->cb(loop, sig->data); + } +} + +static void pipe_init(event_loop_t *loop) { + int result = pipe(loop->pipefd); + assert(result == 0); + + if(result == 0) { +#ifdef O_NONBLOCK + fcntl(loop->pipefd[0], F_SETFL, O_NONBLOCK); + fcntl(loop->pipefd[1], F_SETFL, O_NONBLOCK); +#endif + io_add(loop, &loop->signalio, signalio_handler, NULL, loop->pipefd[0], IO_READ); + } +} + +static void pipe_exit(event_loop_t *loop) { + io_del(loop, &loop->signalio); + + close(loop->pipefd[0]); + close(loop->pipefd[1]); + + loop->pipefd[0] = -1; + loop->pipefd[1] = -1; +} + +void signal_trigger(event_loop_t *loop, signal_t *sig) { +#ifdef HAVE_STDATOMIC_H + + if(atomic_flag_test_and_set(&sig->set)) { + return; + } + +#endif + + uint8_t signum = sig->signum; + write(loop->pipefd[1], &signum, 1); + return; +} + +void signal_add(event_loop_t *loop, signal_t *sig, signal_cb_t cb, void *data, uint8_t signum) { + assert(!sig->cb); + + sig->cb = cb; + sig->data = data; + sig->signum = signum; + sig->node.data = sig; + +#ifdef HAVE_STDATOMIC_H + atomic_flag_clear(&sig->set); +#endif + + if(loop->pipefd[0] == -1) { + pipe_init(loop); + } + + if(!splay_insert_node(&loop->signals, &sig->node)) { + abort(); + } +} + +void signal_del(event_loop_t *loop, signal_t *sig) { + assert(sig->cb); + + loop->deletion = true; + + splay_unlink_node(&loop->signals, &sig->node); + sig->cb = NULL; + + if(!loop->signals.count && loop->pipefd[0] != -1) { + pipe_exit(loop); + } +} + +void idle_set(event_loop_t *loop, idle_cb_t cb, void *data) { + loop->idle_cb = cb; + loop->idle_data = data; +} + +static void check_bad_fds(event_loop_t *loop, meshlink_handle_t *mesh) { + // Just call all registered callbacks and have them check their fds + + do { + loop->deletion = false; + + for splay_each(io_t, io, &loop->ios) { + if(io->flags & IO_WRITE) { + io->cb(loop, io->data, IO_WRITE); + } + + if(loop->deletion) { + break; + } + + if(io->flags & IO_READ) { + io->cb(loop, io->data, IO_READ); + } + + if(loop->deletion) { + break; + } + } + } while(loop->deletion); + + // Rebuild the fdsets + + fd_set old_readfds; + fd_set old_writefds; + memcpy(&old_readfds, &loop->readfds, sizeof(old_readfds)); + memcpy(&old_writefds, &loop->writefds, sizeof(old_writefds)); + + memset(&loop->readfds, 0, sizeof(loop->readfds)); + memset(&loop->writefds, 0, sizeof(loop->writefds)); + + for splay_each(io_t, io, &loop->ios) { + if(io->flags & IO_READ) { + FD_SET(io->fd, &loop->readfds); + io->cb(loop, io->data, IO_READ); + } + + if(io->flags & IO_WRITE) { + FD_SET(io->fd, &loop->writefds); + io->cb(loop, io->data, IO_WRITE); + } + } + + if(memcmp(&old_readfds, &loop->readfds, sizeof(old_readfds))) { + logger(mesh, MESHLINK_WARNING, "Incorrect readfds fixed"); + } + + if(memcmp(&old_writefds, &loop->writefds, sizeof(old_writefds))) { + logger(mesh, MESHLINK_WARNING, "Incorrect writefds fixed"); + } +} + +bool event_loop_run(event_loop_t *loop, meshlink_handle_t *mesh) { + assert(mesh); + + fd_set readable; + fd_set writable; + int errors = 0; + + while(loop->running) { + clock_gettime(EVENT_CLOCK, &loop->now); + struct timespec it, ts = {3600, 0}; + + while(loop->timeouts.head) { + timeout_t *timeout = loop->timeouts.head->data; + + if(timespec_lt(&timeout->tv, &loop->now)) { + timeout_disable(loop, timeout); + timeout->cb(loop, timeout->data); + } else { + timespec_sub(&timeout->tv, &loop->now, &ts); + break; + } + } + + if(loop->idle_cb) { + it = loop->idle_cb(loop, loop->idle_data); + + if(it.tv_sec >= 0 && timespec_lt(&it, &ts)) { + ts = it; + } + } + + memcpy(&readable, &loop->readfds, sizeof(readable)); + memcpy(&writable, &loop->writefds, sizeof(writable)); + + int fds = 0; + + if(loop->ios.tail) { + io_t *last = loop->ios.tail->data; + fds = last->fd + 1; + } + + // release mesh mutex during select + pthread_mutex_unlock(&mesh->mutex); + +#ifdef HAVE_PSELECT + int n = pselect(fds, &readable, &writable, NULL, &ts, NULL); +#else + struct timeval tv = {ts.tv_sec, ts.tv_nsec / 1000}; + int n = select(fds, &readable, &writable, NULL, (struct timeval *)&tv); +#endif + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + clock_gettime(EVENT_CLOCK, &loop->now); + + if(n < 0) { + if(sockwouldblock(errno)) { + continue; + } else { + errors++; + + if(errors > 10) { + logger(mesh, MESHLINK_ERROR, "Unrecoverable error from select(): %s", strerror(errno)); + return false; + } + + logger(mesh, MESHLINK_WARNING, "Error from select(), checking for bad fds: %s", strerror(errno)); + check_bad_fds(loop, mesh); + continue; + } + } + + errors = 0; + + if(!n) { + continue; + } + + // Normally, splay_each allows the current node to be deleted. However, + // it can be that one io callback triggers the deletion of another io, + // so we have to detect this and break the loop. + + loop->deletion = false; + + for splay_each(io_t, io, &loop->ios) { + if(FD_ISSET(io->fd, &writable) && io->cb) { + io->cb(loop, io->data, IO_WRITE); + } + + if(loop->deletion) { + break; + } + + if(FD_ISSET(io->fd, &readable) && io->cb) { + io->cb(loop, io->data, IO_READ); + } + + if(loop->deletion) { + break; + } + } + } + + return true; +} + +void event_loop_start(event_loop_t *loop) { + loop->running = true; +} + +void event_loop_stop(event_loop_t *loop) { + loop->running = false; +} + +void event_loop_init(event_loop_t *loop) { + loop->ios.compare = (splay_compare_t)io_compare; + loop->timeouts.compare = (splay_compare_t)timeout_compare; + loop->signals.compare = (splay_compare_t)signal_compare; + loop->pipefd[0] = -1; + loop->pipefd[1] = -1; + clock_gettime(EVENT_CLOCK, &loop->now); +} + +void event_loop_exit(event_loop_t *loop) { + assert(!loop->ios.count); + assert(!loop->timeouts.count); + assert(!loop->signals.count); + + for splay_each(io_t, io, &loop->ios) { + splay_unlink_node(&loop->ios, splay_node); + } + + for splay_each(timeout_t, timeout, &loop->timeouts) { + splay_unlink_node(&loop->timeouts, splay_node); + } + + for splay_each(signal_t, signal, &loop->signals) { + splay_unlink_node(&loop->signals, splay_node); + } +} diff --git a/src/event.h b/src/event.h new file mode 100644 index 0000000..6b53d91 --- /dev/null +++ b/src/event.h @@ -0,0 +1,105 @@ +#ifndef MESHLINK_EVENT_H +#define MESHLINK_EVENT_H + +/* + event.h -- I/O, timeout and signal event handling + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "splay_tree.h" +#include "system.h" +#include + +#define IO_READ 1 +#define IO_WRITE 2 + +typedef struct event_loop_t event_loop_t; +struct meshlink_handle; + +typedef void (*io_cb_t)(event_loop_t *loop, void *data, int flags); +typedef void (*timeout_cb_t)(event_loop_t *loop, void *data); +typedef void (*signal_cb_t)(event_loop_t *loop, void *data); +typedef struct timespec(*idle_cb_t)(event_loop_t *loop, void *data); + +typedef struct io_t { + struct splay_node_t node; + int fd; + int flags; + io_cb_t cb; + void *data; +} io_t; + +typedef struct timeout_t { + struct splay_node_t node; + struct timespec tv; + timeout_cb_t cb; + void *data; +} timeout_t; + +typedef struct signal_t { + struct splay_node_t node; + int signum; +#ifdef HAVE_STDATOMIC_H + volatile atomic_flag set; +#endif + signal_cb_t cb; + void *data; +} signal_t; + +struct event_loop_t { + void *data; + + volatile bool running; + bool deletion; + + struct timespec now; + + splay_tree_t timeouts; + idle_cb_t idle_cb; + void *idle_data; + splay_tree_t ios; + splay_tree_t signals; + + fd_set readfds; + fd_set writefds; + + io_t signalio; + int pipefd[2]; +}; + +void io_add(event_loop_t *loop, io_t *io, io_cb_t cb, void *data, int fd, int flags); +void io_del(event_loop_t *loop, io_t *io); +void io_set(event_loop_t *loop, io_t *io, int flags); + +void timeout_add(event_loop_t *loop, timeout_t *timeout, timeout_cb_t cb, void *data, struct timespec *tv); +void timeout_del(event_loop_t *loop, timeout_t *timeout); +void timeout_set(event_loop_t *loop, timeout_t *timeout, struct timespec *tv); + +void signal_add(event_loop_t *loop, signal_t *sig, signal_cb_t cb, void *data, uint8_t signum); +void signal_trigger(event_loop_t *loop, signal_t *sig); +void signal_del(event_loop_t *loop, signal_t *sig); + +void idle_set(event_loop_t *loop, idle_cb_t cb, void *data); + +void event_loop_init(event_loop_t *loop); +void event_loop_exit(event_loop_t *loop); +bool event_loop_run(event_loop_t *loop, struct meshlink_handle *mesh) __attribute__((__warn_unused_result__)); +void event_loop_flush_output(event_loop_t *loop); +void event_loop_start(event_loop_t *loop); +void event_loop_stop(event_loop_t *loop); + +#endif diff --git a/src/graph.c b/src/graph.c new file mode 100644 index 0000000..9a2bfb1 --- /dev/null +++ b/src/graph.c @@ -0,0 +1,231 @@ +/* + graph.c -- graph algorithms + Copyright (C) 2014 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +/* We need to generate two trees from the graph: + + 1. A minimum spanning tree for broadcasts, + 2. A single-source shortest path tree for unicasts. + + Actually, the first one alone would suffice but would make unicast packets + take longer routes than necessary. + + For the MST algorithm we can choose from Prim's or Kruskal's. I personally + favour Kruskal's, because we make an extra AVL tree of edges sorted on + weights (metric). That tree only has to be updated when an edge is added or + removed, and during the MST algorithm we just have go linearly through that + tree, adding safe edges until #edges = #nodes - 1. The implementation here + however is not so fast, because I tried to avoid having to make a forest and + merge trees. + + For the SSSP algorithm Dijkstra's seems to be a nice choice. Currently a + simple breadth-first search is presented here. + + The SSSP algorithm will also be used to determine whether nodes are + reachable from the source. It will also set the correct destination address + and port of a node if possible. +*/ + +#include "system.h" + +#include "connection.h" +#include "edge.h" +#include "graph.h" +#include "list.h" +#include "logger.h" +#include "meshlink_internal.h" +#include "netutl.h" +#include "node.h" +#include "protocol.h" +#include "utils.h" +#include "xalloc.h" +#include "graph.h" + +/* Implementation of a simple breadth-first search algorithm. + Running time: O(E) +*/ + +static void sssp_bfs(meshlink_handle_t *mesh) { + list_t *todo_list = list_alloc(NULL); + + /* Clear visited status on nodes */ + + for splay_each(node_t, n, mesh->nodes) { + n->status.visited = false; + n->distance = -1; + } + + /* Begin with mesh->self */ + + mesh->self->status.visited = mesh->threadstarted; + mesh->self->nexthop = mesh->self; + mesh->self->prevedge = NULL; + mesh->self->distance = 0; + list_insert_head(todo_list, mesh->self); + + /* Loop while todo_list is filled */ + + for list_each(node_t, n, todo_list) { /* "n" is the node from which we start */ + logger(mesh, MESHLINK_DEBUG, " Examining edges from %s", n->name); + + if(n->distance < 0) { + abort(); + } + + for splay_each(edge_t, e, n->edge_tree) { /* "e" is the edge connected to "from" */ + if(!e->reverse) { + continue; + } + + /* Situation: + + / + / + ----->(n)---e-->(e->to) + \ + \ + + Where e is an edge, (n) and (e->to) are nodes. + n->address is set to the e->address of the edge left of n to n. + We are currently examining the edge e right of n from n: + + - If edge e provides for better reachability of e->to, update + e->to and (re)add it to the todo_list to (re)examine the reachability + of nodes behind it. + */ + + if(e->to->status.visited + && (e->to->distance != n->distance + 1 || e->weight >= e->to->prevedge->weight)) { + continue; + } + + e->to->status.visited = true; + e->to->nexthop = (n->nexthop == mesh->self) ? e->to : n->nexthop; + e->to->prevedge = e; + e->to->distance = n->distance + 1; + + if(!e->to->status.reachable || (e->to->address.sa.sa_family == AF_UNSPEC && e->address.sa.sa_family != AF_UNKNOWN)) { + update_node_udp(mesh, e->to, &e->address); + } + + list_insert_tail(todo_list, e->to); + } + + list_next = list_node->next; /* Because the list_insert_tail() above could have added something extra for us! */ + list_delete_node(todo_list, list_node); + } + + list_free(todo_list); +} + +static void check_reachability(meshlink_handle_t *mesh) { + /* Check reachability status. */ + + int reachable = -1; /* Don't count ourself */ + + for splay_each(node_t, n, mesh->nodes) { + if(n->status.visited) { + reachable++; + } + + /* Check for nodes that have changed session_id */ + if(n->status.visited && n->prevedge && n->prevedge->reverse->session_id != n->session_id) { + logger(mesh, MESHLINK_DEBUG, "Node %s has a new session ID", n->name); + + n->session_id = n->prevedge->reverse->session_id; + + if(n->utcp) { + utcp_reset_all_connections(n->utcp); + } + + n->status.validkey = false; + sptps_stop(&n->sptps); + n->status.waitingforkey = false; + n->last_req_key = -3600; + + n->status.udp_confirmed = false; + n->maxmtu = MTU; + n->minmtu = 0; + n->mtuprobes = 0; + + timeout_del(&mesh->loop, &n->mtutimeout); + } + + if(n->status.visited != n->status.reachable) { + n->status.reachable = !n->status.reachable; + n->status.dirty = true; + + if(!n->status.blacklisted) { + if(n->status.reachable) { + logger(mesh, MESHLINK_DEBUG, "Node %s became reachable", n->name); + bool first_time_reachable = !n->last_reachable; + n->last_reachable = time(NULL); + + if(first_time_reachable) { + if(!node_write_config(mesh, n, false)) { + logger(mesh, MESHLINK_WARNING, "Could not write host config file for node %s!\n", n->name); + + } + } + } else { + logger(mesh, MESHLINK_DEBUG, "Node %s became unreachable", n->name); + n->last_unreachable = time(NULL); + } + } + + n->status.udp_confirmed = false; + n->maxmtu = MTU; + n->minmtu = 0; + n->mtuprobes = 0; + + timeout_del(&mesh->loop, &n->mtutimeout); + + if(!n->status.blacklisted) { + update_node_status(mesh, n); + } + + if(!n->status.reachable) { + update_node_udp(mesh, n, NULL); + n->status.broadcast = false; + } + + if(n->utcp) { + utcp_offline(n->utcp, !n->status.reachable); + } + } + } + + if(mesh->reachable != reachable) { + if(!reachable) { + mesh->last_unreachable = mesh->loop.now.tv_sec; + + if(mesh->threadstarted && mesh->periodictimer.cb) { + timeout_set(&mesh->loop, &mesh->periodictimer, &(struct timespec) { + 0, prng(mesh, TIMER_FUDGE) + }); + } + } + + mesh->reachable = reachable; + } +} + +void graph(meshlink_handle_t *mesh) { + sssp_bfs(mesh); + check_reachability(mesh); +} diff --git a/src/graph.h b/src/graph.h new file mode 100644 index 0000000..1775732 --- /dev/null +++ b/src/graph.h @@ -0,0 +1,25 @@ +#ifndef MESHLINK_GRAPH_H +#define MESHLINK_GRAPH_H + +/* + graph.h -- header for graph.c + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +void graph(struct meshlink_handle *mesh); + +#endif diff --git a/src/hash.c b/src/hash.c new file mode 100644 index 0000000..a796442 --- /dev/null +++ b/src/hash.c @@ -0,0 +1,121 @@ +/* + hash.c -- hash table management + Copyright (C) 2014 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "hash.h" +#include "xalloc.h" + +/* Generic hash function */ + +static uint32_t hash_function(const void *p, size_t len) { + const uint8_t *q = p; + uint32_t hash = 0; + + while(true) { + for(int i = len > 4 ? 4 : len; --i;) { + hash += q[len - i] << (8 * i); + } + + hash *= 0x9e370001UL; // Golden ratio prime. + + if(len <= 4) { + break; + } + + len -= 4; + } + + return hash; +} + +/* Map 32 bits int onto 0..n-1, without throwing away too many bits if n is 2^8 or 2^16 */ + +static uint32_t modulo(uint32_t hash, size_t n) { + if(n == 0x100) { + return (hash >> 24) ^ ((hash >> 16) & 0xff) ^ ((hash >> 8) & 0xff) ^ (hash & 0xff); + } else if(n == 0x10000) { + return (hash >> 16) ^ (hash & 0xffff); + } else { + return hash % n; + } +} + +/* (De)allocation */ + +hash_t *hash_alloc(size_t n, size_t size) { + hash_t *hash = xzalloc(sizeof(*hash)); + hash->n = n; + hash->size = size; + hash->keys = xzalloc(hash->n * hash->size); + hash->values = xzalloc(hash->n * sizeof(*hash->values)); + return hash; +} + +void hash_free(hash_t *hash) { + free(hash->keys); + free(hash->values); + free(hash); +} + +/* Searching and inserting */ + +void hash_insert(hash_t *hash, const void *key, const void *value) { + uint32_t i = modulo(hash_function(key, hash->size), hash->n); + memcpy(hash->keys + i * hash->size, key, hash->size); + hash->values[i] = value; +} + +void *hash_search(const hash_t *hash, const void *key) { + uint32_t i = modulo(hash_function(key, hash->size), hash->n); + + if(hash->values[i] && !memcmp(key, hash->keys + i * hash->size, hash->size)) { + return (void *)hash->values[i]; + } + + return NULL; +} + +void *hash_search_or_insert(hash_t *hash, const void *key, const void *value) { + uint32_t i = modulo(hash_function(key, hash->size), hash->n); + + if(hash->values[i] && !memcmp(key, hash->keys + i * hash->size, hash->size)) { + return (void *)hash->values[i]; + } + + memcpy(hash->keys + i * hash->size, key, hash->size); + hash->values[i] = value; + return NULL; +} + +/* Utility functions */ + +void hash_clear(hash_t *hash) { + memset(hash->values, 0, hash->n * sizeof(*hash->values)); +} + +void hash_resize(hash_t *hash, size_t n) { + hash->keys = xrealloc(hash->keys, n * hash->size); + hash->values = xrealloc(hash->values, n * sizeof(*hash->values)); + + if(n > hash->n) { + memset(hash->keys + hash->n * hash->size, 0, (n - hash->n) * hash->size); + memset(hash->values + hash->n, 0, (n - hash->n) * sizeof(*hash->values)); + } +} diff --git a/src/hash.h b/src/hash.h new file mode 100644 index 0000000..b28dc97 --- /dev/null +++ b/src/hash.h @@ -0,0 +1,41 @@ +#ifndef MESHLINK_HASH_H +#define MESHLINK_HASH_H + +/* + hash.h -- header file for hash.c + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +typedef struct hash_t { + size_t n; + size_t size; + char *keys; + const void **values; +} hash_t; + +hash_t *hash_alloc(size_t n, size_t size) __attribute__((__malloc__)); +void hash_free(hash_t *); + +void hash_insert(hash_t *, const void *key, const void *value); + +void *hash_search(const hash_t *, const void *key); +void *hash_search_or_insert(hash_t *, const void *key, const void *value); + +void hash_clear(hash_t *); +void hash_resize(hash_t *, size_t n); + +#endif diff --git a/src/have.h b/src/have.h new file mode 100644 index 0000000..e7da316 --- /dev/null +++ b/src/have.h @@ -0,0 +1,133 @@ +#ifndef MESHLINK_HAVE_H +#define MESHLINK_HAVE_H + +/* + have.h -- include headers which are known to exist + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef HAVE_MINGW +#ifdef WITH_WINDOWS2000 +#define WINVER Windows2000 +#else +#define WINVER WindowsXP +#endif +#define WIN32_LEAN_AND_MEAN +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_STDATOMIC_H +#include +#endif + +#ifdef HAVE_MINGW +#include +#include +#include +#include +#endif + +#ifdef HAVE_TERMIOS_H +#include +#endif + +/* Include system specific headers */ + +#ifdef HAVE_SYSLOG_H +#include +#endif + +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#ifdef HAVE_TIME_H +#include +#endif + +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#ifdef HAVE_SYS_STAT_H +#include +#endif + +#ifdef HAVE_SYS_FILE_H +#include +#endif + +#ifdef HAVE_SYS_WAIT_H +#include +#endif + +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#ifdef HAVE_SYS_RESOURCE_H +#include +#endif + +#ifdef HAVE_SYS_UN_H +#include +#endif + +#ifdef HAVE_DIRENT_H +#include +#endif + +/* SunOS really wants sys/socket.h BEFORE net/if.h, + and FreeBSD wants these lines below the rest. */ + +#ifdef HAVE_NETDB_H +#include +#endif + +#ifdef HAVE_SYS_SOCKET_H +#include +#endif + +#ifdef HAVE_ARPA_INET_H +#include +#endif + +#ifdef HAVE_IFADDRS_H +#include +#endif + +#ifdef HAVE_MINGW +#define SLASH "\\" +#else +#define SLASH "/" +#endif + +#endif diff --git a/src/list.c b/src/list.c new file mode 100644 index 0000000..b93ebfc --- /dev/null +++ b/src/list.c @@ -0,0 +1,176 @@ +/* + list.c -- functions to deal with double linked lists + Copyright (C) 2014 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "list.h" +#include "xalloc.h" + +/* (De)constructors */ + +list_t *list_alloc(list_action_t delete) { + list_t *list = xzalloc(sizeof(list_t)); + list->delete = delete; + + return list; +} + +void list_free(list_t *list) { + free(list); +} + +static list_node_t *list_alloc_node(void) { + return xzalloc(sizeof(list_node_t)); +} + +static void list_free_node(list_t *list, list_node_t *node) { + if(node->data && list->delete) { + list->delete(node->data); + } + + free(node); +} + +/* Insertion and deletion */ + +list_node_t *list_insert_head(list_t *list, void *data) { + list_node_t *node = list_alloc_node(); + + node->data = data; + node->prev = NULL; + node->next = list->head; + list->head = node; + + if(node->next) { + node->next->prev = node; + } else { + list->tail = node; + } + + list->count++; + + return node; +} + +list_node_t *list_insert_tail(list_t *list, void *data) { + list_node_t *node = list_alloc_node(); + + node->data = data; + node->next = NULL; + node->prev = list->tail; + list->tail = node; + + if(node->prev) { + node->prev->next = node; + } else { + list->head = node; + } + + list->count++; + + return node; +} + +static void list_unlink_node(list_t *list, list_node_t *node) { + assert(list->count); + assert(node->prev || list->head == node); + assert(node->next || list->tail == node); + + if(node->prev) { + node->prev->next = node->next; + } else { + list->head = node->next; + } + + if(node->next) { + node->next->prev = node->prev; + } else { + list->tail = node->prev; + } + + list->count--; +} + +void list_delete_node(list_t *list, list_node_t *node) { + list_unlink_node(list, node); + list_free_node(list, node); +} + +void list_delete_head(list_t *list) { + list_delete_node(list, list->head); +} + +void list_delete_tail(list_t *list) { + list_delete_node(list, list->tail); +} + +void list_delete(list_t *list, const void *data) { + for(list_node_t *node = list->head, *next; next = node ? node->next : NULL, node; node = next) { + if(node->data == data) { + list_delete_node(list, node); + } + } +} + +/* Head/tail lookup */ + +void *list_get_head(list_t *list) { + if(list->head) { + return list->head->data; + } else { + return NULL; + } +} + +void *list_get_tail(list_t *list) { + if(list->tail) { + return list->tail->data; + } else { + return NULL; + } +} + +/* Fast list deletion */ + +void list_delete_list(list_t *list) { + for(list_node_t *node = list->head, *next; next = node ? node->next : NULL, node; node = next) { + list_free_node(list, node); + list->count--; + } + + assert(!list->count); + + list_free(list); +} + +/* Traversing */ + +void list_foreach_node(list_t *list, list_action_node_t action) { + for(list_node_t *node = list->head, *next; next = node ? node->next : NULL, node; node = next) { + action(node); + } +} + +void list_foreach(list_t *list, list_action_t action) { + for(list_node_t *node = list->head, *next; next = node ? node->next : NULL, node; node = next) { + if(node->data) { + action(node->data); + } + } +} diff --git a/src/list.h b/src/list.h new file mode 100644 index 0000000..bdce156 --- /dev/null +++ b/src/list.h @@ -0,0 +1,78 @@ +#ifndef MESHLINK_LIST_H +#define MESHLINK_LIST_H + +/* + list.h -- header file for list.c + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +typedef struct list_node_t { + struct list_node_t *prev; + struct list_node_t *next; + + /* Payload */ + + void *data; +} list_node_t; + +typedef void (*list_action_t)(const void *); +typedef void (*list_action_node_t)(const list_node_t *); + +typedef struct list_t { + list_node_t *head; + list_node_t *tail; + unsigned int count; + + /* Callbacks */ + + list_action_t delete; +} list_t; + +/* (De)constructors */ + +list_t *list_alloc(list_action_t) __attribute__((__malloc__)); +void list_free(list_t *); + +/* Insertion and deletion */ + +list_node_t *list_insert_head(list_t *, void *); +list_node_t *list_insert_tail(list_t *, void *); + +void list_delete(list_t *, const void *); + +void list_delete_node(list_t *, list_node_t *); + +void list_delete_head(list_t *); +void list_delete_tail(list_t *); + +/* Head/tail lookup */ + +void *list_get_head(list_t *); +void *list_get_tail(list_t *); + +/* Fast list deletion */ + +void list_delete_list(list_t *); + +/* Traversing */ + +void list_foreach(list_t *, list_action_t); +void list_foreach_node(list_t *, list_action_node_t); + +#define list_each(type, item, list) (type *item = (type *)1; item; item = NULL) for(list_node_t *list_node = (list)->head, *list_next; item = list_node ? list_node->data : NULL, list_next = list_node ? list_node->next : NULL, list_node; list_node = list_next) + +#endif diff --git a/src/logger.c b/src/logger.c new file mode 100644 index 0000000..78d629e --- /dev/null +++ b/src/logger.c @@ -0,0 +1,56 @@ +/* + logger.c -- logging code + Copyright (C) 2014-2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "logger.h" +#include "meshlink_internal.h" +#include "sptps.h" + +// TODO: refactor logging code to use a meshlink_handle_t *. +void logger(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *format, ...) { + assert(format); + + if(mesh) { + if(level < mesh->log_level || !mesh->log_cb) { + return; + } + } else { + if(level < global_log_level || !global_log_cb) { + return; + } + } + + va_list ap; + char message[1024] = ""; + + va_start(ap, format); + int len = vsnprintf(message, sizeof(message), format, ap); + va_end(ap); + + if(len > 0 && (size_t)len < sizeof(message) && message[len - 1] == '\n') { + message[len - 1] = 0; + } + + if(mesh) { + mesh->log_cb(mesh, level, message); + } else { + global_log_cb(NULL, level, message); + } +} diff --git a/src/logger.h b/src/logger.h new file mode 100644 index 0000000..b9269f0 --- /dev/null +++ b/src/logger.h @@ -0,0 +1,27 @@ +#ifndef MESHLINK_LOGGER_H +#define MESHLINK_LOGGER_H + +/* + logger.h -- header file for logger.c + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "meshlink_internal.h" + +void logger(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *format, ...) __attribute__((__format__(printf, 3, 4))); + +#endif diff --git a/src/mdns.c b/src/mdns.c new file mode 100644 index 0000000..e92a833 --- /dev/null +++ b/src/mdns.c @@ -0,0 +1,441 @@ +// SPDX-FileCopyrightText: 2020 Guus Sliepen +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "system.h" + +#include + +#include "mdns.h" +#include "xalloc.h" + +// Creating a buffer + +typedef struct { + uint8_t *ptr; + ptrdiff_t len; +} buf_t; + +static void buf_add(buf_t *buf, const void *data, uint32_t len) { + if(buf->len >= len) { + memcpy(buf->ptr, data, len); + buf->ptr += len; + buf->len -= len; + } else { + buf->len = -1; + } +} + +static void buf_add_uint8(buf_t *buf, uint8_t val) { + if(buf->len >= 1) { + buf->ptr[0] = val; + buf->ptr++; + buf->len--; + } else { + buf->len = -1; + } +} + +static void buf_add_uint16(buf_t *buf, uint16_t val) { + uint16_t nval = htons(val); + buf_add(buf, &nval, sizeof(nval)); +} + +static void buf_add_uint32(buf_t *buf, uint32_t val) { + uint32_t nval = htonl(val); + buf_add(buf, &nval, sizeof(nval)); +} + +static void buf_add_label(buf_t *buf, const char *str) { + size_t len = strlen(str); + + if(len < 256) { + buf_add_uint8(buf, len); + buf_add(buf, str, len); + } else { + buf->len = -1; + } +} + +static void buf_add_ulabel(buf_t *buf, const char *str) { + size_t len = strlen(str); + + if(len + 1 < 256) { + buf_add_uint8(buf, len + 1); + buf_add_uint8(buf, '_'); + buf_add(buf, str, len); + } else { + buf->len = -1; + } +} + +static void buf_add_kvp(buf_t *buf, const char *key, const char *val) { + size_t key_len = strlen(key); + size_t val_len = strlen(val); + + if(key_len + val_len + 1 < 256) { + buf_add_uint8(buf, key_len + val_len + 1); + buf_add(buf, key, key_len); + buf_add_uint8(buf, '='); + buf_add(buf, val, val_len); + } else { + buf->len = -1; + } +} + +static uint8_t *buf_len_start(buf_t *buf) { + if(buf->len < 2) { + buf->len = -1; + return NULL; + } else { + uint8_t *ptr = buf->ptr; + buf->ptr += 2; + buf->len -= 2; + return ptr; + } +} + +static void buf_len_end(buf_t *buf, uint8_t *ptr) { + if(buf->len < 0) { + return; + } + + uint16_t len = htons(buf->ptr - ptr - 2); + memcpy(ptr, &len, sizeof(len)); +} + +// Functions reading a buffer + +typedef struct { + const uint8_t *ptr; + ptrdiff_t len; +} cbuf_t; + +static void buf_check(cbuf_t *buf, const void *data, uint32_t len) { + if(buf->len >= len && !memcmp(buf->ptr, data, len)) { + buf->ptr += len; + buf->len -= len; + } else { + buf->len = -1; + } +} + +static void buf_check_uint8(cbuf_t *buf, uint8_t val) { + if(buf->len >= 1 && buf->ptr[0] == val) { + buf->ptr++; + buf->len--; + } else { + buf->len = -1; + } +} + +static void buf_check_uint16(cbuf_t *buf, uint16_t val) { + uint16_t nval = htons(val); + buf_check(buf, &nval, sizeof(nval)); +} + +static uint16_t buf_get_uint16(cbuf_t *buf) { + uint16_t nval; + + if(buf->len >= 2) { + memcpy(&nval, buf->ptr, 2); + buf->ptr += 2; + buf->len -= 2; + return ntohs(nval); + } else { + buf->len = -1; + return 0; + } +} + +static void buf_check_uint32(cbuf_t *buf, uint32_t val) { + uint32_t nval = htonl(val); + buf_check(buf, &nval, sizeof(nval)); +} + +static void buf_check_label(cbuf_t *buf, const char *str) { + size_t len = strlen(str); + + if(len < 256) { + buf_check_uint8(buf, len); + buf_check(buf, str, len); + } else { + buf->len = -1; + } +} + +static char *buf_get_label(cbuf_t *buf) { + if(buf->len < 1) { + buf->len = -1; + return NULL; + } + + uint8_t len = buf->ptr[0]; + buf->ptr++; + buf->len--; + + if(buf->len < len) { + buf->len = -1; + return NULL; + } + + char *label = xmalloc(len + 1); + memcpy(label, buf->ptr, len); + label[len] = 0; + buf->ptr += len; + buf->len -= len; + return label; +} + +static void buf_check_ulabel(cbuf_t *buf, const char *str) { + size_t len = strlen(str); + + if(len + 1 < 256) { + buf_check_uint8(buf, len + 1); + buf_check_uint8(buf, '_'); + buf_check(buf, str, len); + } else { + buf->len = -1; + } +} + +static void buf_get_kvp(cbuf_t *buf, const char *key, char **val) { + char *kvp = buf_get_label(buf); + + if(buf->len == -1) { + return; + } + + char *split = strchr(kvp, '='); + + if(!split) { + buf->len = -1; + return; + } + + *split++ = 0; + + if(strcmp(kvp, key)) { + buf->len = -1; + return; + } + + memmove(kvp, split, strlen(split) + 1); + *val = kvp; +} + +static const uint8_t *buf_check_len_start(cbuf_t *buf) { + if(buf->len < 2) { + buf->len = -1; + return NULL; + } else { + const uint8_t *ptr = buf->ptr; + buf->ptr += 2; + buf->len -= 2; + return ptr; + } +} + +static void buf_check_len_end(cbuf_t *buf, const uint8_t *ptr) { + if(buf->len < 0) { + return; + } + + uint16_t len = htons(buf->ptr - ptr - 2); + + if(memcmp(ptr, &len, sizeof(len))) { + buf->len = -1; + } +} + +size_t prepare_request(void *vdata, size_t size, const char *protocol, const char *transport) { + uint8_t *data = vdata; + buf_t buf = {data, size}; + + // Header + buf_add_uint16(&buf, 0); // TX ID + buf_add_uint16(&buf, 0); // flags + buf_add_uint16(&buf, 1); // 1 question + buf_add_uint16(&buf, 0); // 0 answer RR + buf_add_uint16(&buf, 0); // 0 authority RRs + buf_add_uint16(&buf, 0); // 0 additional RR + + // Question section: _protocol._transport.local PTR IN + buf_add_ulabel(&buf, protocol); + buf_add_ulabel(&buf, transport); + buf_add_label(&buf, "local"); + buf_add_uint8(&buf, 0); + buf_add_uint16(&buf, 0xc); // PTR + buf_add_uint16(&buf, 0x1); // IN + + // Done. + if(buf.len < 0) { + return 0; + } else { + return buf.ptr - data; + } +} + +bool parse_request(const void *vdata, size_t size, const char *protocol, const char *transport) { + const uint8_t *data = vdata; + cbuf_t buf = {data, size}; + + // Header + buf_get_uint16(&buf); // TX ID + buf_check_uint16(&buf, 0); // flags + buf_check_uint16(&buf, 1); // 1 question + buf_get_uint16(&buf); // ? answer RR + buf_get_uint16(&buf); // ? authority RRs + buf_get_uint16(&buf); // ? additional RR + + if(buf.len == -1) { + return false; + } + + // Question section: _protocol._transport.local PTR IN + buf_check_ulabel(&buf, protocol); + buf_check_ulabel(&buf, transport); + buf_check_label(&buf, "local"); + buf_check_uint8(&buf, 0); + buf_check_uint16(&buf, 0xc); // PTR + buf_check_uint16(&buf, 0x1); // IN + + if(buf.len == -1) { + return false; + } + + // Done. + return buf.len != -1; +} + +size_t prepare_response(void *vdata, size_t size, const char *name, const char *protocol, const char *transport, uint16_t port, int nkeys, const char **keys, const char **values) { + uint8_t *data = vdata; + buf_t buf = {data, size}; + + // Header + buf_add_uint16(&buf, 0); // TX ID + buf_add_uint16(&buf, 0x8400); // flags + buf_add_uint16(&buf, 0); // 1 question + buf_add_uint16(&buf, 3); // 1 answer RR + buf_add_uint16(&buf, 0); // 0 authority RRs + buf_add_uint16(&buf, 0); // 1 additional RR + + // Add the TXT record: _protocol._transport local TXT IN 3600 name._protocol._transport key=value... + uint16_t full_name = buf.ptr - data; // remember start of full name + buf_add_label(&buf, name); + uint16_t protocol_offset = buf.ptr - data; // remember start of _protocol + buf_add_ulabel(&buf, protocol); + buf_add_ulabel(&buf, transport); + uint16_t local_offset = buf.ptr - data; // remember start of local + buf_add_label(&buf, "local"); + buf_add_uint8(&buf, 0); + buf_add_uint16(&buf, 0x10); // TXT + buf_add_uint16(&buf, 0x1); // IN + buf_add_uint32(&buf, 3600); // TTL + + uint8_t *len_ptr = buf_len_start(&buf); + + for(int i = 0; i < nkeys; i++) { + buf_add_kvp(&buf, keys[i], values[i]); + } + + buf_len_end(&buf, len_ptr); + + // Add the PTR record: _protocol._transport.local PTR IN 3600 name._protocol._transport.local + buf_add_uint16(&buf, 0xc000 | protocol_offset); + buf_add_uint16(&buf, 0xc); // PTR + buf_add_uint16(&buf, 0x8001); // IN (flush) + buf_add_uint32(&buf, 3600); // TTL + len_ptr = buf_len_start(&buf); + buf_add_uint16(&buf, 0xc000 | full_name); + buf_len_end(&buf, len_ptr); + + // Add the SRV record: name._protocol._transport.local SRV IN 120 0 0 port name.local + buf_add_uint16(&buf, 0xc000 | full_name); + buf_add_uint16(&buf, 0x21); // SRV + buf_add_uint16(&buf, 0x8001); // IN (flush) + buf_add_uint32(&buf, 120); // TTL + len_ptr = buf_len_start(&buf); + buf_add_uint16(&buf, 0); // priority + buf_add_uint16(&buf, 0); // weight + buf_add_uint16(&buf, port); // port + buf_add_label(&buf, name); + buf_add_uint16(&buf, 0xc000 | local_offset); + buf_len_end(&buf, len_ptr); + + // Done. + if(buf.len < 0) { + return 0; + } else { + return buf.ptr - data; + } +} + +bool parse_response(const void *vdata, size_t size, char **name, const char *protocol, const char *transport, uint16_t *port, int nkeys, const char **keys, char **values) { + const uint8_t *data = vdata; + cbuf_t buf = {data, size}; + + // Header + buf_check_uint16(&buf, 0); // TX ID + buf_check_uint16(&buf, 0x8400); // flags + buf_check_uint16(&buf, 0); // 0 question + buf_check_uint16(&buf, 3); // 1 answer RR + buf_check_uint16(&buf, 0); // 0 authority RRs + buf_check_uint16(&buf, 0); // 0 checkitional RR + + if(buf.len == -1) { + return false; + } + + // Check the TXT record: _protocol._transport local TXT IN 3600 name._protocol._transport key=value... + uint16_t full_name = buf.ptr - data; // remember start of full name + *name = buf_get_label(&buf); + uint16_t protocol_offset = buf.ptr - data; // remember start of _protocol + buf_check_ulabel(&buf, protocol); + buf_check_ulabel(&buf, transport); + uint16_t local_offset = buf.ptr - data; // remember start of local + buf_check_label(&buf, "local"); + buf_check_uint8(&buf, 0); + buf_check_uint16(&buf, 0x10); // TXT + buf_check_uint16(&buf, 0x1); // IN + buf_check_uint32(&buf, 3600); // TTL + const uint8_t *len_ptr = buf_check_len_start(&buf); + + for(int i = 0; i < nkeys; i++) { + buf_get_kvp(&buf, keys[i], &values[i]); + } + + buf_check_len_end(&buf, len_ptr); + + if(buf.len == -1) { + return false; + } + + // Check the PTR record: _protocol._transport.local PTR IN 3600 name._protocol._transport.local + buf_check_uint16(&buf, 0xc000 | protocol_offset); + buf_check_uint16(&buf, 0xc); // PTR + buf_check_uint16(&buf, 0x8001); // IN (flush) + buf_check_uint32(&buf, 3600); // TTL + len_ptr = buf_check_len_start(&buf); + buf_check_uint16(&buf, 0xc000 | full_name); + buf_check_len_end(&buf, len_ptr); + + if(buf.len == -1) { + return false; + } + + // Check the SRV record: name._protocol._transport.local SRV IN 120 0 0 port name.local + buf_check_uint16(&buf, 0xc000 | full_name); + buf_check_uint16(&buf, 0x21); // SRV + buf_check_uint16(&buf, 0x8001); // IN (flush) + buf_check_uint32(&buf, 120); // TTL + len_ptr = buf_check_len_start(&buf); + buf_check_uint16(&buf, 0); // priority + buf_check_uint16(&buf, 0); // weight + *port = buf_get_uint16(&buf); // port + buf_check_label(&buf, *name); + buf_check_uint16(&buf, 0xc000 | local_offset); + buf_check_len_end(&buf, len_ptr); + + // Done. + return buf.len == 0; +} diff --git a/src/mdns.h b/src/mdns.h new file mode 100644 index 0000000..6029158 --- /dev/null +++ b/src/mdns.h @@ -0,0 +1,14 @@ +#pragma once + +// SPDX-FileCopyrightText: 2020 Guus Sliepen +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +size_t prepare_packet(void *buf, size_t size, const char *name, const char *protocol, const char *transport, uint16_t port, int nkeys, const char **keys, const char **values, bool response); +size_t prepare_request(void *buf, size_t size, const char *protocol, const char *transport); +size_t prepare_response(void *buf, size_t size, const char *name, const char *protocol, const char *transport, uint16_t port, int nkeys, const char **keys, const char **values); +bool parse_response(const void *buf, size_t size, char **name, const char *protocol, const char *transport, uint16_t *port, int nkeys, const char **keys, char **values); +bool parse_request(const void *buf, size_t size, const char *protocol, const char *transport); + diff --git a/src/meshlink++.h b/src/meshlink++.h new file mode 100644 index 0000000..25fd34f --- /dev/null +++ b/src/meshlink++.h @@ -0,0 +1,1378 @@ +#ifndef MESHLINKPP_H +#define MESHLINKPP_H + +/* + meshlink++.h -- MeshLink C++ API + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include +#include // for 'placement new' + +namespace meshlink { +class mesh; +class node; +class channel; +class submesh; + +/// Severity of log messages generated by MeshLink. +typedef meshlink_log_level_t log_level_t; + +/// Code of most recent error encountered. +typedef meshlink_errno_t errno_t; + +/// A callback for receiving data from the mesh. +/** @param mesh A handle which represents an instance of MeshLink. + * @param source A pointer to a meshlink::node describing the source of the data. + * @param data A pointer to a buffer containing the data sent by the source. + * @param len The length of the received data. + */ +typedef void (*receive_cb_t)(mesh *mesh, node *source, const void *data, size_t len); + +/// A callback reporting the meta-connection attempt made by the host node to an another node. +/** @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a meshlink_node_t describing the node to whom meta-connection is being tried. + * This pointer is valid until meshlink_close() is called. + */ +typedef void (*connection_try_cb_t)(mesh *mesh, node *node); + +/// A callback reporting node status changes. +/** @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a meshlink::node describing the node whose status changed. + * @param reachable True if the node is reachable, false otherwise. + */ +typedef void (*node_status_cb_t)(mesh *mesh, node *node, bool reachable); + +/// A callback reporting node path MTU changes. +/** @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a meshlink_node_t describing the node whose status changed. + * This pointer is valid until meshlink_close() is called. + * @param pmtu The current path MTU to the node, or 0 if UDP communication is not (yet) possible. + */ +typedef void (*node_pmtu_cb_t)(mesh *mesh, node *node, uint16_t pmtu); + +/// A callback reporting duplicate node detection. +/** @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a meshlink_node_t describing the node which is duplicate. + * This pointer is valid until meshlink_close() is called. + */ +typedef void (*duplicate_cb_t)(mesh *mesh, node *node); + +/// A callback for receiving log messages generated by MeshLink. +/** @param mesh A handle which represents an instance of MeshLink. + * @param level An enum describing the severity level of the message. + * @param text A pointer to a string containing the textual log message. + */ +typedef void (*log_cb_t)(mesh *mesh, log_level_t level, const char *text); + +/// A callback for listening for incoming channels. +/** @param mesh A handle which represents an instance of MeshLink. + * @param node A handle for the node that wants to open a channel. + * @param port The port number the peer wishes to connect to. + * + * @return This function should return true if the application listens for the incoming channel, false otherwise. + */ +typedef bool (*meshlink_channel_listen_cb_t)(struct meshlink_handle *mesh, struct meshlink_node *node, uint16_t port); + +/// A callback for accepting incoming channels. +/** @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the incoming channel. + * @param port The port number the peer wishes to connect to. + * @param data A pointer to a buffer containing data already received. (Not yet used.) + * @param len The length of the data. (Not yet used.) + * + * @return This function should return true if the application accepts the incoming channel, false otherwise. + * If returning false, the channel is invalid and may not be used anymore. + */ +typedef bool (*channel_accept_cb_t)(mesh *mesh, channel *channel, uint16_t port, const void *data, size_t len); + +/// A callback for receiving data from a channel. +/** @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + * @param data A pointer to a buffer containing data sent by the source. + * @param len The length of the data. + */ +typedef void (*channel_receive_cb_t)(mesh *mesh, channel *channel, const void *data, size_t len); + +/// A callback that is called when data can be send on a channel. +/** @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + * @param len The maximum length of data that is guaranteed to be accepted by a call to channel_send(). + */ +typedef void (*channel_poll_cb_t)(mesh *mesh, channel *channel, size_t len); + +/// A callback for cleaning up buffers submitted for asynchronous I/O. +/** This callbacks signals that MeshLink has finished using this buffer. + * The ownership of the buffer is now back into the application's hands. + * + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel which used this buffer. + * @param data A pointer to a buffer containing the enqueued data. + * @param len The length of the buffer. + * @param priv A private pointer which was set by the application when submitting the buffer. + */ +typedef void (*aio_cb_t)(mesh *mesh, channel *channel, const void *data, size_t len, void *priv); + +/// A callback for asynchronous I/O to and from filedescriptors. +/** This callbacks signals that MeshLink has finished using this filedescriptor. + * + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel which used this filedescriptor. + * @param fd The filedescriptor that was used. + * @param len The length of the data that was successfully sent or received. + * @param priv A private pointer which was set by the application when submitting the buffer. + */ +typedef void (*aio_fd_cb_t)(mesh *mesh, channel *channel, int fd, size_t len, void *priv); + +/// A class describing a MeshLink node. +class node: public meshlink_node_t { +}; + +/// A class describing a MeshLink Sub-Mesh. +class submesh: public meshlink_submesh_t { +}; + +/// A class describing a MeshLink channel. +class channel: public meshlink_channel_t { +public: + static const uint32_t RELIABLE = MESHLINK_CHANNEL_RELIABLE; + static const uint32_t ORDERED = MESHLINK_CHANNEL_ORDERED; + static const uint32_t FRAMED = MESHLINK_CHANNEL_FRAMED; + static const uint32_t DROP_LATE = MESHLINK_CHANNEL_DROP_LATE; + static const uint32_t NO_PARTIAL = MESHLINK_CHANNEL_NO_PARTIAL; + static const uint32_t TCP = MESHLINK_CHANNEL_TCP; + static const uint32_t UDP = MESHLINK_CHANNEL_UDP; +}; + +/// A class describing a MeshLink mesh. +class mesh { +public: + mesh() : handle(0) {} + + virtual ~mesh() { + this->close(); + } + + bool isOpen() const { + return (handle != 0); + } + +// TODO: please enable C++11 in autoconf to enable "move constructors": +// mesh(mesh&& other) +// : handle(other.handle) +// { +// if(handle) +// handle->priv = this; +// other.handle = 0; +// } + + /// Initialize MeshLink's configuration directory. + /** This function causes MeshLink to initialize its configuration directory, + * if it hasn't already been initialized. + * It only has to be run the first time the application starts, + * but it is not a problem if it is run more than once, as long as + * the arguments given are the same. + * + * This function does not start any network I/O yet. The application should + * first set callbacks, and then call meshlink_start(). + * + * @param confbase The directory in which MeshLink will store its configuration files. + * @param name The name which this instance of the application will use in the mesh. + * @param appname The application name which will be used in the mesh. + * @param devclass The device class which will be used in the mesh. + * + * @return This function will return a pointer to a meshlink::mesh if MeshLink has successfully set up its configuration files, NULL otherwise. + */ + bool open(const char *confbase, const char *name, const char *appname, dev_class_t devclass) { + handle = meshlink_open(confbase, name, appname, devclass); + + if(handle) { + handle->priv = this; + } + + return isOpen(); + } + + mesh(const char *confbase, const char *name, const char *appname, dev_class_t devclass) { + open(confbase, name, appname, devclass); + } + + /// Close the MeshLink handle. + /** This function calls meshlink_stop() if necessary, + * and frees all memory allocated by MeshLink. + * Afterwards, the handle and any pointers to a struct meshlink_node are invalid. + */ + void close() { + if(handle) { + handle->priv = 0; + meshlink_close(handle); + } + + handle = 0; + } + + /** instead of registerin callbacks you derive your own class and overwrite the following abstract member functions. + * These functions are run in MeshLink's own thread. + * It is therefore important that these functions use apprioriate methods (queues, pipes, locking, etc.) + * to hand the data over to the application's thread. + * These functions should also not block itself and return as quickly as possible. + * The default member functions are no-ops, so you are not required to overwrite all these member functions + */ + + /// This function is called whenever another node sends data to the local node. + virtual void receive(node *source, const void *data, size_t length) { + /* do nothing */ + (void)source; + (void)data; + (void) length; + } + + /// This functions is called whenever another node's status changed. + virtual void node_status(node *peer, bool reachable) { + /* do nothing */ + (void)peer; + (void)reachable; + } + + /// This functions is called whenever another node's path MTU changes. + virtual void node_pmtu(node *peer, uint16_t pmtu) { + /* do nothing */ + (void)peer; + (void)pmtu; + } + + /// This functions is called whenever a duplicate node is detected. + virtual void node_duplicate(node *peer) { + /* do nothing */ + (void)peer; + } + + /// This functions is called whenever MeshLink has some information to log. + virtual void log(log_level_t level, const char *message) { + /* do nothing */ + (void)level; + (void)message; + } + + /// This functions is called whenever MeshLink has encountered a serious error. + virtual void error(meshlink_errno_t meshlink_errno) { + /* do nothing */ + (void)meshlink_errno; + } + + /// This functions is called whenever MeshLink is blacklisted by another node. + virtual void blacklisted(node *peer) { + /* do nothing */ + (void)peer; + } + + /// This functions is called whenever MeshLink a meta-connection attempt is made. + virtual void connection_try(node *peer) { + /* do nothing */ + (void)peer; + } + + /// This functions is called to determine if we are listening for incoming channels. + /** + * The function is run in MeshLink's own thread. + * It is therefore important that the callback uses apprioriate methods (queues, pipes, locking, etc.) + * to pass data to or from the application's thread. + * The callback should also not block itself and return as quickly as possible. + * + * @param node A handle for the node that wants to open a channel. + * @param port The port number the peer wishes to connect to. + * + * @return This function should return true if the application accepts the incoming channel, false otherwise. + */ + virtual bool channel_listen(node *node, uint16_t port) { + /* by default accept all channels */ + (void)node; + (void)port; + return true; + } + + /// This functions is called whenever another node attempts to open a channel to the local node. + /** + * If the channel is accepted, the poll_callback will be set to channel_poll and can be + * changed using set_channel_poll_cb(). Likewise, the receive callback is set to + * channel_receive(). + * + * The function is run in MeshLink's own thread. + * It is therefore important that the callback uses apprioriate methods (queues, pipes, locking, etc.) + * to pass data to or from the application's thread. + * The callback should also not block itself and return as quickly as possible. + * + * @param channel A handle for the incoming channel. + * @param port The port number the peer wishes to connect to. + * @param data A pointer to a buffer containing data already received. (Not yet used.) + * @param len The length of the data. (Not yet used.) + * + * @return This function should return true if the application accepts the incoming channel, false otherwise. + * If returning false, the channel is invalid and may not be used anymore. + */ + virtual bool channel_accept(channel *channel, uint16_t port, const void *data, size_t len) { + /* by default reject all channels */ + (void)channel; + (void)port; + (void)data; + (void)len; + return false; + } + + /// This function is called by Meshlink for receiving data from a channel. + /** + * The function is run in MeshLink's own thread. + * It is therefore important that the callback uses apprioriate methods (queues, pipes, locking, etc.) + * to pass data to or from the application's thread. + * The callback should also not block itself and return as quickly as possible. + * + * @param channel A handle for the channel. + * @param data A pointer to a buffer containing data sent by the source. + * @param len The length of the data. + */ + virtual void channel_receive(channel *channel, const void *data, size_t len) { + /* do nothing */ + (void)channel; + (void)data; + (void)len; + } + + /// This function is called by Meshlink when data can be send on a channel. + /** + * The function is run in MeshLink's own thread. + * It is therefore important that the callback uses apprioriate methods (queues, pipes, locking, etc.) + * to pass data to or from the application's thread. + * + * The callback should also not block itself and return as quickly as possible. + * @param channel A handle for the channel. + * @param len The maximum length of data that is guaranteed to be accepted by a call to channel_send(). + */ + virtual void channel_poll(channel *channel, size_t len) { + /* do nothing */ + (void)channel; + (void)len; + } + + /// Start MeshLink. + /** This function causes MeshLink to open network sockets, make outgoing connections, and + * create a new thread, which will handle all network I/O. + * + * @return This function will return true if MeshLink has successfully started its thread, false otherwise. + */ + bool start() { + meshlink_set_receive_cb(handle, &receive_trampoline); + meshlink_set_node_status_cb(handle, &node_status_trampoline); + meshlink_set_node_pmtu_cb(handle, &node_pmtu_trampoline); + meshlink_set_node_duplicate_cb(handle, &node_duplicate_trampoline); + meshlink_set_log_cb(handle, MESHLINK_DEBUG, &log_trampoline); + meshlink_set_error_cb(handle, &error_trampoline); + meshlink_set_blacklisted_cb(handle, &blacklisted_trampoline); + meshlink_set_channel_listen_cb(handle, &channel_listen_trampoline); + meshlink_set_channel_accept_cb(handle, &channel_accept_trampoline); + meshlink_set_connection_try_cb(handle, &connection_try_trampoline); + return meshlink_start(handle); + } + + /// Stop MeshLink. + /** This function causes MeshLink to disconnect from all other nodes, + * close all sockets, and shut down its own thread. + */ + void stop() { + meshlink_stop(handle); + } + + /// Send data to another node. + /** This functions sends one packet of data to another node in the mesh. + * The packet is sent using UDP semantics, which means that + * the packet is sent as one unit and is received as one unit, + * and that there is no guarantee that the packet will arrive at the destination. + * The application should take care of getting an acknowledgement and retransmission if necessary. + * + * @param destination A pointer to a meshlink::node describing the destination for the data. + * @param data A pointer to a buffer containing the data to be sent to the source. + * @param len The length of the data. + * @return This function will return true if MeshLink has queued the message for transmission, and false otherwise. + * A return value of true does not guarantee that the message will actually arrive at the destination. + */ + bool send(node *destination, const void *data, unsigned int len) { + return meshlink_send(handle, destination, data, len); + } + + /// Get a handle for a specific node. + /** This function returns a handle for the node with the given name. + * + * @param name The name of the node for which a handle is requested. + * + * @return A pointer to a meshlink::node which represents the requested node, + * or NULL if the requested node does not exist. + */ + node *get_node(const char *name) { + return (node *)meshlink_get_node(handle, name); + } + + /// Get a node's reachability status. + /** This function returns the current reachability of a given node, and the times of the last state changes. + * If a given state change never happened, the time returned will be 0. + * + * @param node A pointer to a meshlink::node describing the node. + * @param last_reachable A pointer to a time_t variable that will be filled in with the last time the node became reachable. + * @param last_unreachable A pointer to a time_t variable that will be filled in with the last time the node became unreachable. + * + * @return This function returns true if the node is currently reachable, false otherwise. + */ + bool get_node_reachability(node *node, time_t *last_reachable = NULL, time_t *last_unreachable = NULL) { + return meshlink_get_node_reachability(handle, node, last_reachable, last_unreachable); + } + + /// Get a node's blacklist status. + /** This function returns the current blacklist status of a given node. + * + * @param node A pointer to a meshlink::node describing the node. + * + * @return This function returns true if the node is currently blacklisted, false otherwise. + */ + bool get_node_blacklisted(node *node) { + return meshlink_get_node_blacklisted(handle, node); + } + + /// Get a handle for a specific submesh. + /** This function returns a handle for the submesh with the given name. + * + * @param name The name of the submesh for which a handle is requested. + * + * @return A pointer to a meshlink::submesh which represents the requested submesh, + * or NULL if the requested submesh does not exist. + */ + submesh *get_submesh(const char *name) { + return (submesh *)meshlink_get_submesh(handle, name); + } + + /// Get a handle for our own node. + /** This function returns a handle for the local node. + * + * @return A pointer to a meshlink::node which represents the local node. + */ + node *get_self() { + return (node *)meshlink_get_self(handle); + } + + /// Get a list of all nodes. + /** This function returns a list with handles for all known nodes. + * + * @param nodes A pointer to an array of pointers to meshlink::node, which should be allocated by the application. + * @param nmemb The maximum number of pointers that can be stored in the nodes array. + * + * @return The number of known nodes, or -1 in case of an error. + * This can be larger than nmemb, in which case not all nodes were stored in the nodes array. + */ + node **get_all_nodes(node **nodes, size_t *nmemb) { + return (node **)meshlink_get_all_nodes(handle, (meshlink_node_t **)nodes, nmemb); + } + + /// Get a list of all nodes by blacklist status. + /** This function returns a list with handles for all the nodes who were either blacklisted or whitelisted. + * + * @param blacklisted If true, a list of blacklisted nodes will be returned, otherwise whitelisted nodes. + * @param nodes A pointer to an array of pointers to meshlink::node, which should be allocated by the application. + * @param nmemb The maximum number of pointers that can be stored in the nodes array. + * + * @return A pointer to an array containing pointers to all known nodes with the given blacklist status. + * If the @a nodes argument was not NULL, then the return value can either be the same value or a different value. + * If it is a new value, the old value of @a nodes should not be used anymore. + * If the new value is NULL, then the old array will have been freed by MeshLink. + */ + node **get_all_nodes_by_blacklisted(bool blacklisted, node **nodes, size_t *nmemb) { + return (node **)meshlink_get_all_nodes_by_blacklisted(handle, blacklisted, (meshlink_node_t **)nodes, nmemb); + } + + /// Sign data using the local node's MeshLink key. + /** This function signs data using the local node's MeshLink key. + * The generated signature can be securely verified by other nodes. + * + * @param data A pointer to a buffer containing the data to be signed. + * @param len The length of the data to be signed. + * @param signature A pointer to a buffer where the signature will be stored. + * @param siglen The size of the signature buffer. Will be changed after the call to match the size of the signature itself. + * + * @return This function returns true if the signature is valid, false otherwise. + */ + bool sign(const void *data, size_t len, void *signature, size_t *siglen) { + return meshlink_sign(handle, data, len, signature, siglen); + } + + /// Verify the signature generated by another node of a piece of data. + /** This function verifies the signature that another node generated for a piece of data. + * + * @param source A pointer to a meshlink_node_t describing the source of the signature. + * @param data A pointer to a buffer containing the data to be verified. + * @param len The length of the data to be verified. + * @param signature A pointer to a string containing the signature. + * @param siglen The size of the signature. + * + * @return This function returns true if the signature is valid, false otherwise. + */ + bool verify(node *source, const void *data, size_t len, const void *signature, size_t siglen) { + return meshlink_verify(handle, source, data, len, signature, siglen); + } + + /// Set the canonical Address for a node. + /** This function sets the canonical Address for a node. + * This address is stored permanently until it is changed by another call to this function, + * unlike other addresses associated with a node, + * such as those added with meshlink_hint_address() or addresses discovered at runtime. + * + * If a canonical Address is set for the local node, + * it will be used for the hostname part of generated invitation URLs. + * + * @param node A pointer to a meshlink_node_t describing the node. + * @param address A nul-terminated C string containing the address, which can be either in numeric format or a hostname. + * @param port A nul-terminated C string containing the port, which can be either in numeric or symbolic format. + * If it is NULL, the listening port's number will be used. + * + * @return This function returns true if the address was added, false otherwise. + */ + bool set_canonical_address(node *node, const char *address, const char *port = NULL) { + return meshlink_set_canonical_address(handle, node, address, port); + } + + /// Clear the canonical Address for a node. + /** This function clears the canonical Address for a node. + * + * @param node A pointer to a struct meshlink_node describing the node. + * + * @return This function returns true if the address was removed, false otherwise. + */ + bool clear_canonical_address(node *node) { + return meshlink_clear_canonical_address(handle, node); + } + + /// Add an invitation address for the local node. + /** This function adds an address for the local node, which will be used only for invitation URLs. + * This address is not stored permanently. + * Multiple addresses can be added using multiple calls to this function. + * + * @param address A nul-terminated C string containing the address, which can be either in numeric format or a hostname. + * @param port A nul-terminated C string containing the port, which can be either in numeric or symbolic format. + * If it is NULL, the listening port's number will be used. + * + * @return This function returns true if the address was added, false otherwise. + */ + bool add_invitation_address(const char *address, const char *port) { + return meshlink_add_invitation_address(handle, address, port); + } + + /// Clears all invitation address for the local node. + /** This function removes all addresses added with meshlink_add_invitation_address(). + */ + void clear_invitation_addresses() { + return meshlink_clear_invitation_addresses(handle); + } + + /// Add an Address for the local node. + /** This function adds an Address for the local node, which will be used for invitation URLs. + * @deprecated This function is deprecated, use set_canonical_address() and/or add_invitation_address(). + * + * @param address A string containing the address, which can be either in numeric format or a hostname. + * + * @return This function returns true if the address was added, false otherwise. + */ + bool add_address(const char *address) __attribute__((__deprecated__("use set_canonical_address() and/or add_invitation_address() instead"))) { + return meshlink_set_canonical_address(handle, get_self(), address, NULL); + } + + /** This function performs tries to discover the local node's external address + * by contacting the meshlink.io server. If a reverse lookup of the address works, + * the FQDN associated with the address will be returned. + * + * Please note that this is function only returns a single address, + * even if the local node might have more than one external address. + * In that case, there is no control over which address will be selected. + * Also note that if you have a dynamic IP address, or are behind carrier-grade NAT, + * there is no guarantee that the external address will be valid for an extended period of time. + * + * This function is blocking. It can take several seconds before it returns. + * There is no guarantee it will be able to resolve the external address. + * Failures might be because by temporary network outages. + * + * @param family The address family to check, for example AF_INET or AF_INET6. If AF_UNSPEC is given, + * this might return the external address for any working address family. + * + * @return This function returns a pointer to a C string containing the discovered external address, + * or NULL if there was an error looking up the address. + * After get_external_address() returns, the application is free to overwrite or free this string. + */ + bool get_external_address(int family = AF_UNSPEC) { + return meshlink_get_external_address_for_family(handle, family); + } + + /** This function performs tries to discover the address of the local interface used for outgoing connection. + * + * Please note that this is function only returns a single address, + * even if the local node might have more than one external address. + * In that case, there is no control over which address will be selected. + * Also note that if you have a dynamic IP address, or are behind carrier-grade NAT, + * there is no guarantee that the external address will be valid for an extended period of time. + * + * This function will fail if it couldn't find a local address for the given address family. + * If hostname resolving is requested, this function may block for a few seconds. + * + * @param family The address family to check, for example AF_INET or AF_INET6. If AF_UNSPEC is given, + * this might return the external address for any working address family. + * + * @return This function returns a pointer to a C string containing the discovered external address, + * or NULL if there was an error looking up the address. + * After get_external_address() returns, the application is free to overwrite or free this string. + */ + bool get_local_address(int family = AF_UNSPEC) { + return meshlink_get_local_address_for_family(handle, family); + } + + /// Try to discover the external address for the local node, and add it to its list of addresses. + /** This function is equivalent to: + * + * mesh->add_address(mesh->get_external_address()); + * + * Read the description of get_external_address() for the limitations of this function. + * + * @return This function returns true if the address was added, false otherwise. + */ + bool add_external_address() { + return meshlink_add_external_address(handle); + } + + /// Get the network port used by the local node. + /** This function returns the network port that the local node is listening on. + * + * @return This function returns the port number, or -1 in case of an error. + */ + int get_port() { + return meshlink_get_port(handle); + } + + /// Set the network port used by the local node. + /** This function sets the network port that the local node is listening on. + * It may only be called when the mesh is not running. + * If unsure, call stop() before calling this function. + * Also note that if your node is already part of a mesh with other nodes, + * that the other nodes may no longer be able to initiate connections to the local node, + * since they will try to connect to the previously configured port. + * + * @param port The port number to listen on. This must be between 0 and 65535. + * If the port is set to 0, then MeshLink will listen on a port + * that is randomly assigned by the operating system every time open() is called. + * + * @return This function returns true if the port was successfully changed + * to the desired port, false otherwise. If it returns false, there + * is no guarantee that MeshLink is listening on the old port. + */ + bool set_port(int port) { + return meshlink_set_port(handle, port); + } + + /// Set the timeout for invitations. + /** This function sets the timeout for invitations. + * The timeout is retroactively applied to all outstanding invitations. + * + * @param timeout The timeout for invitations in seconds. + */ + void set_invitation_timeout(int timeout) { + meshlink_set_invitation_timeout(handle, timeout); + } + + /// Set the scheduling granularity of the application + /** This should be set to the effective scheduling granularity for the application. + * This depends on the scheduling granularity of the operating system, the application's + * process priority and whether it is running as realtime or not. + * The default value is 10000 (10 milliseconds). + * + * @param granularity The scheduling granularity of the application in microseconds. + */ + void set_granularity(long granularity) { + meshlink_set_scheduling_granularity(handle, granularity); + } + + /// Sets the storage policy used by MeshLink + /** This sets the policy MeshLink uses when it has new information about nodes. + * By default, all udpates will be stored to disk (unless an ephemeral instance has been opened). + * Setting the policy to MESHLINK_STORAGE_KEYS_ONLY, only updates that contain new keys for nodes + * are stored, as well as blacklist/whitelist settings. + * By setting the policy to MESHLINK_STORAGE_DISABLED, no updates will be stored. + * + * @param policy The storage policy to use. + */ + void set_storage_policy(meshlink_storage_policy_t policy) { + meshlink_set_storage_policy(handle, policy); + } + + /// Invite another node into the mesh. + /** This function generates an invitation that can be used by another node to join the same mesh as the local node. + * The generated invitation is a string containing a URL. + * This URL should be passed by the application to the invitee in a way that no eavesdroppers can see the URL. + * The URL can only be used once, after the user has joined the mesh the URL is no longer valid. + * + * @param submesh A handle to the submesh to put the invitee in. + * @param name The name that the invitee will use in the mesh. + * @param flags A bitwise-or'd combination of flags that controls how the URL is generated. + * + * @return This function returns a string that contains the invitation URL. + * The application should call free() after it has finished using the URL. + */ + char *invite(submesh *submesh, const char *name, uint32_t flags = 0) { + return meshlink_invite_ex(handle, submesh, name, flags); + } + + /// Use an invitation to join a mesh. + /** This function allows the local node to join an existing mesh using an invitation URL generated by another node. + * An invitation can only be used if the local node has never connected to other nodes before. + * After a successfully accepted invitation, the name of the local node may have changed. + * + * This function may only be called on a mesh that has not been started yet and which is not already part of an existing mesh. + * + * This function is blocking. It can take several seconds before it returns. + * There is no guarantee it will perform a successful join. + * Failures might be caused by temporary network outages, or by the invitation having expired. + * + * @param invitation A string containing the invitation URL. + * + * @return This function returns true if the local node joined the mesh it was invited to, false otherwise. + */ + bool join(const char *invitation) { + return meshlink_join(handle, invitation); + } + + /// Export the local node's key and addresses. + /** This function generates a string that contains the local node's public key and one or more IP addresses. + * The application can pass it in some way to another node, which can then import it, + * granting the local node access to the other node's mesh. + * + * @return This function returns a string that contains the exported key and addresses. + * The application should call free() after it has finished using this string. + */ + char *export_key() { + return meshlink_export(handle); + } + + /// Import another node's key and addresses. + /** This function accepts a string containing the exported public key and addresses of another node. + * By importing this data, the local node grants the other node access to its mesh. + * + * @param data A string containing the other node's exported key and addresses. + * + * @return This function returns true if the data was valid and the other node has been granted access to the mesh, false otherwise. + */ + bool import_key(const char *data) { + return meshlink_import(handle, data); + } + + /// Forget any information about a node. + /** This function allows the local node to forget any information it has about a node, + * and if possible will remove any data it has stored on disk about the node. + * + * Any open channels to this node must be closed before calling this function. + * + * After this call returns, the node handle is invalid and may no longer be used, regardless + * of the return value of this call. + * + * Note that this function does not prevent MeshLink from actually forgetting about a node, + * or re-learning information about a node at a later point in time. It is merely a hint that + * the application does not care about this node anymore and that any resources kept could be + * cleaned up. + * + * \memberof meshlink_node + * @param node A pointer to a struct meshlink_node describing the node to be forgotten. + * + * @return This function returns true if all currently known data about the node has been forgotten, false otherwise. + */ + bool forget_node(node *node) { + return meshlink_forget_node(handle, node); + } + + /// Blacklist a node from the mesh. + /** This function causes the local node to blacklist another node. + * The local node will drop any existing connections to that node, + * and will not send data to it nor accept any data received from it any more. + * + * @param node A pointer to a meshlink::node describing the node to be blacklisted. + * + * @return This function returns true if the node has been whitelisted, false otherwise. + */ + bool blacklist(node *node) { + return meshlink_blacklist(handle, node); + } + + /// Blacklist a node from the mesh by name. + /** This function causes the local node to blacklist another node by name. + * The local node will drop any existing connections to that node, + * and will not send data to it nor accept any data received from it any more. + * + * If no node by the given name is known, it is created. + * + * @param name The name of the node to blacklist. + * + * @return This function returns true if the node has been blacklisted, false otherwise. + */ + bool blacklist_by_name(const char *name) { + return meshlink_blacklist_by_name(handle, name); + } + + /// Whitelist a node on the mesh. + /** This function causes the local node to whitelist another node. + * The local node will allow connections to and from that node, + * and will send data to it and accept any data received from it. + * + * @param node A pointer to a meshlink::node describing the node to be whitelisted. + * + * @return This function returns true if the node has been whitelisted, false otherwise. + */ + bool whitelist(node *node) { + return meshlink_whitelist(handle, node); + } + + /// Whitelist a node on the mesh by name. + /** This function causes the local node to whitelist a node by name. + * The local node will allow connections to and from that node, + * and will send data to it and accept any data received from it. + * + * If no node by the given name is known, it is created. + * This is useful if new nodes are blacklisted by default. + * + * \memberof meshlink_node + * @param name The name of the node to whitelist. + * + * @return This function returns true if the node has been whitelisted, false otherwise. + */ + bool whitelist_by_name(const char *name) { + return meshlink_whitelist_by_name(handle, name); + } + + /// Set the poll callback. + /** This functions sets the callback that is called whenever data can be sent to another node. + * The callback is run in MeshLink's own thread. + * It is therefore important that the callback uses apprioriate methods (queues, pipes, locking, etc.) + * to pass data to or from the application's thread. + * The callback should also not block itself and return as quickly as possible. + * + * @param channel A handle for the channel. + * @param cb A pointer to the function which will be called when data can be sent to another node. + * If a NULL pointer is given, the callback will be disabled. + */ + void set_channel_poll_cb(channel *channel, channel_poll_cb_t cb) { + meshlink_set_channel_poll_cb(handle, channel, (meshlink_channel_poll_cb_t)cb); + } + + /// Set the send buffer size of a channel. + /** This function sets the desired size of the send buffer. + * The default size is 128 kB. + * + * @param channel A handle for the channel. + * @param size The desired size for the send buffer. + * If a NULL pointer is given, the callback will be disabled. + */ + void set_channel_sndbuf(channel *channel, size_t size) { + meshlink_set_channel_sndbuf(handle, channel, size); + } + + /// Set the receive buffer size of a channel. + /** This function sets the desired size of the receive buffer. + * The default size is 128 kB. + * + * @param channel A handle for the channel. + * @param size The desired size for the send buffer. + * If a NULL pointer is given, the callback will be disabled. + */ + void set_channel_rcvbuf(channel *channel, size_t size) { + meshlink_set_channel_rcvbuf(handle, channel, size); + } + + /// Set the flags of a channel. + /** This function allows changing some of the channel flags. + * Currently only MESHLINK_CHANNEL_NO_PARTIAL and MESHLINK_CHANNEL_DROP_LATE are supported, other flags are ignored. + * These flags only affect the local side of the channel with the peer. + * The changes take effect immediately. + * + * @param channel A handle for the channel. + * @param flags A bitwise-or'd combination of flags that set the semantics for this channel. + */ + void set_channel_flags(channel *channel, uint32_t flags) { + meshlink_set_channel_flags(handle, channel, flags); + } + + /// Set the send buffer storage of a channel. + /** This function provides MeshLink with a send buffer allocated by the application. + * + * @param channel A handle for the channel. + * @param buf A pointer to the start of the buffer. + * If a NULL pointer is given, MeshLink will use its own internal buffer again. + * @param size The size of the buffer. + */ + void set_channel_sndbuf_storage(channel *channel, void *buf, size_t size) { + meshlink_set_channel_sndbuf_storage(handle, channel, buf, size); + } + + /// Set the receive buffer storage of a channel. + /** This function provides MeshLink with a receive buffer allocated by the application. + * + * @param channel A handle for the channel. + * @param buf A pointer to the start of the buffer. + * If a NULL pointer is given, MeshLink will use its own internal buffer again. + * @param size The size of the buffer. + */ + void set_channel_rcvbuf_storage(channel *channel, void *buf, size_t size) { + meshlink_set_channel_rcvbuf_storage(handle, channel, buf, size); + } + + /// Set the connection timeout used for channels to the given node. + /** This sets the timeout after which unresponsive channels will be reported as closed. + * The timeout is set for all current and future channels to the given node. + * + * @param node The node to set the channel timeout for. + * @param timeout The timeout in seconds after which unresponsive channels will be reported as closed. + * The default is 60 seconds. + */ + void set_node_channel_timeout(node *node, int timeout) { + meshlink_set_node_channel_timeout(handle, node, timeout); + } + + /// Open a reliable stream channel to another node. + /** This function is called whenever a remote node wants to open a channel to the local node. + * The application then has to decide whether to accept or reject this channel. + * + * This function sets the channel poll callback to channel_poll_trampoline, which in turn + * calls channel_poll. To set a different, channel-specific poll callback, use set_channel_poll_cb. + * + * @param node The node to which this channel is being initiated. + * @param port The port number the peer wishes to connect to. + * @param cb A pointer to the function which will be called when the remote node sends data to the local node. + * @param data A pointer to a buffer containing data to already queue for sending. + * @param len The length of the data. + * If len is 0, the data pointer is copied into the channel's priv member. + * @param flags A bitwise-or'd combination of flags that set the semantics for this channel. + * + * @return A handle for the channel, or NULL in case of an error. + */ + channel *channel_open(node *node, uint16_t port, channel_receive_cb_t cb, const void *data, size_t len, uint32_t flags = channel::TCP) { + channel *ch = (channel *)meshlink_channel_open_ex(handle, node, port, (meshlink_channel_receive_cb_t)cb, data, len, flags); + meshlink_set_channel_poll_cb(handle, ch, &channel_poll_trampoline); + return ch; + } + + /// Open a reliable stream channel to another node. + /** This function is called whenever a remote node wants to open a channel to the local node. + * The application then has to decide whether to accept or reject this channel. + * + * This function sets the channel receive callback to channel_receive_trampoline, + * which in turn calls channel_receive. + * + * This function sets the channel poll callback to channel_poll_trampoline, which in turn + * calls channel_poll. To set a different, channel-specific poll callback, use set_channel_poll_cb. + * + * @param node The node to which this channel is being initiated. + * @param port The port number the peer wishes to connect to. + * @param data A pointer to a buffer containing data to already queue for sending. + * @param len The length of the data. + * If len is 0, the data pointer is copied into the channel's priv member. + * @param flags A bitwise-or'd combination of flags that set the semantics for this channel. + * + * @return A handle for the channel, or NULL in case of an error. + */ + channel *channel_open(node *node, uint16_t port, const void *data, size_t len, uint32_t flags = channel::TCP) { + channel *ch = (channel *)meshlink_channel_open_ex(handle, node, port, &channel_receive_trampoline, data, len, flags); + meshlink_set_channel_poll_cb(handle, ch, &channel_poll_trampoline); + return ch; + } + + /// Partially close a reliable stream channel. + /** This shuts down the read or write side of a channel, or both, without closing the handle. + * It can be used to inform the remote node that the local node has finished sending all data on the channel, + * but still allows waiting for incoming data from the remote node. + * + * @param channel A handle for the channel. + * @param direction Must be one of SHUT_RD, SHUT_WR or SHUT_RDWR. + */ + void channel_shutdown(channel *channel, int direction) { + return meshlink_channel_shutdown(handle, channel, direction); + } + + /// Close a reliable stream channel. + /** This informs the remote node that the local node has finished sending all data on the channel. + * It also causes the local node to stop accepting incoming data from the remote node. + * Afterwards, the channel handle is invalid and must not be used any more. + * + * It is allowed to call this function at any time on a valid handle, even inside callback functions. + * If called with a valid handle, this function always succeeds, otherwise the result is undefined. + * + * @param channel A handle for the channel. + */ + void channel_close(meshlink_channel_t *channel) { + return meshlink_channel_close(handle, channel); + } + + /// Abort a reliable stream channel. + /** This aborts a channel. + * Data that was in the send and receive buffers is dropped, so potentially there is some data that + * was sent on this channel that will not be received by the peer. + * Afterwards, the channel handle is invalid and must not be used any more. + * + * It is allowed to call this function at any time on a valid handle, even inside callback functions. + * If called with a valid handle, this function always succeeds, otherwise the result is undefined. + * + * @param channel A handle for the channel. + */ + void channel_abort(meshlink_channel_t *channel) { + return meshlink_channel_abort(handle, channel); + } + + /// Transmit data on a channel + /** This queues data to send to the remote node. + * + * @param channel A handle for the channel. + * @param data A pointer to a buffer containing data sent by the source. + * @param len The length of the data. + * + * @return The amount of data that was queued, which can be less than len, or a negative value in case of an error. + * If MESHLINK_CHANNEL_NO_PARTIAL is set, then the result will either be len, + * 0 if the buffer is currently too full, or -1 if len is too big even for an empty buffer. + */ + ssize_t channel_send(channel *channel, void *data, size_t len) { + return meshlink_channel_send(handle, channel, data, len); + } + + /// Transmit data on a channel asynchronously + /** This registers a buffer that will be used to send data to the remote node. + * Multiple buffers can be registered, in which case data will be sent in the order the buffers were registered. + * While there are still buffers with unsent data, the poll callback will not be called. + * + * @param channel A handle for the channel. + * @param data A pointer to a buffer containing data sent by the source, or NULL if there is no data to send. + * After meshlink_channel_aio_send() returns, the buffer may not be modified or freed by the application + * until the callback routine is called. + * @param len The length of the data, or 0 if there is no data to send. + * @param cb A pointer to the function which will be called when MeshLink has finished using the buffer. + * @param priv A private pointer which was set by the application when submitting the buffer. + * + * @return True if the buffer was enqueued, false otherwise. + */ + bool channel_aio_send(channel *channel, const void *data, size_t len, meshlink_aio_cb_t cb, void *priv) { + return meshlink_channel_aio_send(handle, channel, data, len, cb, priv); + } + + /// Transmit data on a channel asynchronously from a filedescriptor + /** This will read up to the specified length number of bytes from the given filedescriptor, and send it over the channel. + * The callback may be returned early if there is an error reading from the filedescriptor. + * While there is still with unsent data, the poll callback will not be called. + * + * @param channel A handle for the channel. + * @param fd A file descriptor from which data will be read. + * @param len The length of the data, or 0 if there is no data to send. + * @param cb A pointer to the function which will be called when MeshLink has finished using the filedescriptor. + * @param priv A private pointer which was set by the application when submitting the buffer. + * + * @return True if the buffer was enqueued, false otherwise. + */ + bool channel_aio_fd_send(channel *channel, int fd, size_t len, meshlink_aio_fd_cb_t cb, void *priv) { + return meshlink_channel_aio_fd_send(handle, channel, fd, len, cb, priv); + } + + /// Receive data on a channel asynchronously + /** This registers a buffer that will be filled with incoming channel data. + * Multiple buffers can be registered, in which case data will be received in the order the buffers were registered. + * While there are still buffers that have not been filled, the receive callback will not be called. + * + * @param channel A handle for the channel. + * @param data A pointer to a buffer that will be filled with incoming data. + * After meshlink_channel_aio_receive() returns, the buffer may not be modified or freed by the application + * until the callback routine is called. + * @param len The length of the data. + * @param cb A pointer to the function which will be called when MeshLink has finished using the buffer. + * @param priv A private pointer which was set by the application when submitting the buffer. + * + * @return True if the buffer was enqueued, false otherwise. + */ + bool channel_aio_receive(channel *channel, const void *data, size_t len, meshlink_aio_cb_t cb, void *priv) { + return meshlink_channel_aio_receive(handle, channel, data, len, cb, priv); + } + + /// Receive data on a channel asynchronously and send it to a filedescriptor + /** This will read up to the specified length number of bytes from the channel, and send it to the filedescriptor. + * The callback may be returned early if there is an error writing to the filedescriptor. + * While there is still unread data, the receive callback will not be called. + * + * @param channel A handle for the channel. + * @param fd A file descriptor to which data will be written. + * @param len The length of the data. + * @param cb A pointer to the function which will be called when MeshLink has finished using the filedescriptor. + * @param priv A private pointer which was set by the application when submitting the buffer. + * + * @return True if the buffer was enqueued, false otherwise. + */ + bool channel_aio_fd_receive(channel *channel, int fd, size_t len, meshlink_aio_fd_cb_t cb, void *priv) { + return meshlink_channel_aio_fd_receive(handle, channel, fd, len, cb, priv); + } + + /// Get the amount of bytes in the send buffer. + /** This returns the amount of bytes in the send buffer. + * These bytes have not been received by the peer yet. + * + * @param channel A handle for the channel. + * + * @return The amount of un-ACKed bytes in the send buffer. + */ + size_t channel_get_sendq(channel *channel) { + return meshlink_channel_get_sendq(handle, channel); + } + + /// Get the amount of bytes in the receive buffer. + /** This returns the amount of bytes in the receive buffer. + * These bytes have not been processed by the application yet. + * + * @param channel A handle for the channel. + * + * @return The amount of bytes in the receive buffer. + */ + size_t channel_get_recvq(channel *channel) { + return meshlink_channel_get_recvq(handle, channel); + } + + /// Get the maximum segment size of a channel. + /** This returns the amount of bytes that can be sent at once for channels with UDP semantics. + * + * @param channel A handle for the channel. + * + * @return The amount of bytes in the receive buffer. + */ + size_t channel_get_mss(channel *channel) { + return meshlink_channel_get_mss(handle, channel); + }; + + /// Enable or disable zeroconf discovery of local peers + /** This controls whether zeroconf discovery using the Catta library will be + * enabled to search for peers on the local network. By default, it is enabled. + * + * @param enable Set to true to enable discovery, false to disable. + */ + void enable_discovery(bool enable = true) { + meshlink_enable_discovery(handle, enable); + } + + /// Inform MeshLink that the local network configuration might have changed + /** This is intended to be used when there is no way for MeshLink to get notifications of local network changes. + * It forces MeshLink to scan all network interfaces for changes in up/down status and new/removed addresses, + * and will immediately check if all connections to other nodes are still alive. + */ + void hint_network_change() { + meshlink_hint_network_change(handle); + } + + /// Set device class timeouts + /** This sets the ping interval and timeout for a given device class. + * + * @param devclass The device class to update + * @param pinginterval The interval between keepalive packets, in seconds. The default is 60. + * @param pingtimeout The required time within which a peer should respond, in seconds. The default is 5. + * The timeout must be smaller than the interval. + */ + void set_dev_class_timeouts(dev_class_t devclass, int pinginterval, int pingtimeout) { + meshlink_set_dev_class_timeouts(handle, devclass, pinginterval, pingtimeout); + } + + /// Set device class fast retry period + /** This sets the fast retry period for a given device class. + * During this period after the last time the mesh becomes unreachable, connections are tried once a second. + * + * @param devclass The device class to update + * @param fast_retry_period The period during which fast connection retries are done. The default is 0. + */ + void set_dev_class_fast_retry_period(dev_class_t devclass, int fast_retry_period) { + meshlink_set_dev_class_fast_retry_period(handle, devclass, fast_retry_period); + } + + /// Set device class maximum timeout + /** This sets the maximum timeout for outgoing connection retries for a given device class. + * + * @param devclass The device class to update + * @param maxtimeout The maximum timeout between reconnection attempts, in seconds. The default is 900. + */ + void set_dev_class_maxtimeout(dev_class_t devclass, int maxtimeout) { + meshlink_set_dev_class_maxtimeout(handle, devclass, maxtimeout); + } + + /// Set which order invitations are committed + /** This determines in which order configuration files are written to disk during an invitation. + * By default, the invitee saves the configuration to disk first, then the inviter. + * By calling this function with @a inviter_commits_first set to true, the order is reversed. + * + * @param inviter_commits_first If true, then the node that invited a peer will commit data to disk first. + */ + void set_inviter_commits_first(bool inviter_commits_first) { + meshlink_set_inviter_commits_first(handle, inviter_commits_first); + } + + /// Set the URL used to discover the host's external address + /** For generating invitation URLs, MeshLink can look up the externally visible address of the local node. + * It does so by querying an external service. By default, this is http://meshlink.io/host.cgi. + * Only URLs starting with http:// are supported. + * + * @param url The URL to use for external address queries, or NULL to revert back to the default URL. + */ + void set_external_address_discovery_url(const char *url) { + meshlink_set_external_address_discovery_url(handle, url); + } + +private: + // non-copyable: + mesh(const mesh &) /* TODO: C++11: = delete */; + void operator=(const mesh &) /* TODO: C++11: = delete */; + + /// static callback trampolines: + static void receive_trampoline(meshlink_handle_t *handle, meshlink_node_t *source, const void *data, size_t length) { + if(!(handle->priv)) { + return; + } + + meshlink::mesh *that = static_cast(handle->priv); + that->receive(static_cast(source), data, length); + } + + static void node_status_trampoline(meshlink_handle_t *handle, meshlink_node_t *peer, bool reachable) { + if(!(handle->priv)) { + return; + } + + meshlink::mesh *that = static_cast(handle->priv); + that->node_status(static_cast(peer), reachable); + } + + static void node_pmtu_trampoline(meshlink_handle_t *handle, meshlink_node_t *peer, uint16_t pmtu) { + if(!(handle->priv)) { + return; + } + + meshlink::mesh *that = static_cast(handle->priv); + that->node_pmtu(static_cast(peer), pmtu); + } + + static void node_duplicate_trampoline(meshlink_handle_t *handle, meshlink_node_t *peer) { + if(!(handle->priv)) { + return; + } + + meshlink::mesh *that = static_cast(handle->priv); + that->node_duplicate(static_cast(peer)); + } + + static void log_trampoline(meshlink_handle_t *handle, log_level_t level, const char *message) { + if(!(handle->priv)) { + return; + } + + meshlink::mesh *that = static_cast(handle->priv); + that->log(level, message); + } + + static void error_trampoline(meshlink_handle_t *handle, meshlink_errno_t meshlink_errno) { + if(!(handle->priv)) { + return; + } + + meshlink::mesh *that = static_cast(handle->priv); + that->error(meshlink_errno); + } + + static void blacklisted_trampoline(meshlink_handle_t *handle, meshlink_node_t *peer) { + if(!(handle->priv)) { + return; + } + + meshlink::mesh *that = static_cast(handle->priv); + that->blacklisted(static_cast(peer)); + } + + static void connection_try_trampoline(meshlink_handle_t *handle, meshlink_node_t *peer) { + if(!(handle->priv)) { + return; + } + + meshlink::mesh *that = static_cast(handle->priv); + that->connection_try(static_cast(peer)); + } + + static bool channel_listen_trampoline(meshlink_handle_t *handle, meshlink_node_t *node, uint16_t port) { + if(!(handle->priv)) { + return false; + } + + meshlink::mesh *that = static_cast(handle->priv); + return that->channel_listen(static_cast(node), port); + } + + static bool channel_accept_trampoline(meshlink_handle_t *handle, meshlink_channel *channel, uint16_t port, const void *data, size_t len) { + if(!(handle->priv)) { + return false; + } + + meshlink::mesh *that = static_cast(handle->priv); + bool accepted = that->channel_accept(static_cast(channel), port, data, len); + + if(accepted) { + meshlink_set_channel_receive_cb(handle, channel, &channel_receive_trampoline); + meshlink_set_channel_poll_cb(handle, channel, &channel_poll_trampoline); + } + + return accepted; + } + + static void channel_receive_trampoline(meshlink_handle_t *handle, meshlink_channel *channel, const void *data, size_t len) { + if(!(handle->priv)) { + return; + } + + meshlink::mesh *that = static_cast(handle->priv); + that->channel_receive(static_cast(channel), data, len); + } + + static void channel_poll_trampoline(meshlink_handle_t *handle, meshlink_channel *channel, size_t len) { + if(!(handle->priv)) { + return; + } + + meshlink::mesh *that = static_cast(handle->priv); + that->channel_poll(static_cast(channel), len); + } + + meshlink_handle_t *handle; +}; + +static inline const char *strerror(errno_t err = meshlink_errno) { + return meshlink_strerror(err); +} + +/// Destroy a MeshLink instance. +/** This function remove all configuration files of a MeshLink instance. It should only be called when the application + * does not have an open handle to this instance. Afterwards, a call to meshlink_open() will create a completely + * new instance. + * + * @param confbase The directory in which MeshLink stores its configuration files. + * After the function returns, the application is free to overwrite or free @a confbase @a. + * + * @return This function will return true if the MeshLink instance was successfully destroyed, false otherwise. + */ +static inline bool destroy(const char *confbase) { + return meshlink_destroy(confbase); +} +} + +#endif diff --git a/src/meshlink.c b/src/meshlink.c new file mode 100644 index 0000000..4cc285d --- /dev/null +++ b/src/meshlink.c @@ -0,0 +1,4939 @@ +/* + meshlink.c -- Implementation of the MeshLink API. + Copyright (C) 2014-2021 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" +#include + +#include "adns.h" +#include "crypto.h" +#include "ecdsagen.h" +#include "logger.h" +#include "meshlink_internal.h" +#include "net.h" +#include "netutl.h" +#include "node.h" +#include "submesh.h" +#include "packmsg.h" +#include "prf.h" +#include "protocol.h" +#include "route.h" +#include "sockaddr.h" +#include "utils.h" +#include "xalloc.h" +#include "ed25519/sha512.h" +#include "discovery.h" +#include "devtools.h" +#include "graph.h" + +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif +__thread meshlink_errno_t meshlink_errno; +meshlink_log_cb_t global_log_cb; +meshlink_log_level_t global_log_level; + +typedef bool (*search_node_by_condition_t)(const node_t *, const void *); + +static int rstrip(char *value) { + int len = strlen(value); + + while(len && strchr("\t\r\n ", value[len - 1])) { + value[--len] = 0; + } + + return len; +} + +static void get_canonical_address(node_t *n, char **hostname, char **port) { + if(!n->canonical_address) { + return; + } + + *hostname = xstrdup(n->canonical_address); + char *space = strchr(*hostname, ' '); + + if(space) { + *space++ = 0; + *port = xstrdup(space); + } +} + +static bool is_valid_hostname(const char *hostname) { + if(!*hostname) { + return false; + } + + for(const char *p = hostname; *p; p++) { + if(!(isalnum(*p) || *p == '-' || *p == '.' || *p == ':')) { + return false; + } + } + + return true; +} + +static bool is_valid_port(const char *port) { + if(!*port) { + return false; + } + + if(isdigit(*port)) { + char *end; + unsigned long int result = strtoul(port, &end, 10); + return result && result < 65536 && !*end; + } + + for(const char *p = port; *p; p++) { + if(!(isalnum(*p) || *p == '-')) { + return false; + } + } + + return true; +} + +static void set_timeout(int sock, int timeout) { +#ifdef _WIN32 + DWORD tv = timeout; +#else + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout - tv.tv_sec * 1000) * 1000; +#endif + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); +} + +struct socket_in_netns_params { + int domain; + int type; + int protocol; + int netns; + int fd; +}; + +#ifdef HAVE_SETNS +static void *socket_in_netns_thread(void *arg) { + struct socket_in_netns_params *params = arg; + + if(setns(params->netns, CLONE_NEWNET) == -1) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + params->fd = socket(params->domain, params->type, params->protocol); + + return NULL; +} +#endif // HAVE_SETNS + +static int socket_in_netns(int domain, int type, int protocol, int netns) { + if(netns == -1) { + return socket(domain, type, protocol); + } + +#ifdef HAVE_SETNS + struct socket_in_netns_params params = {domain, type, protocol, netns, -1}; + + pthread_t thr; + + if(pthread_create(&thr, NULL, socket_in_netns_thread, ¶ms) == 0) { + if(pthread_join(thr, NULL) != 0) { + abort(); + } + } + + return params.fd; +#else + return -1; +#endif // HAVE_SETNS + +} + +// Find out what local address a socket would use if we connect to the given address. +// We do this using connect() on a UDP socket, so the kernel has to resolve the address +// of both endpoints, but this will actually not send any UDP packet. +static bool getlocaladdr(const char *destaddr, sockaddr_t *sa, socklen_t *salen, int netns) { + struct addrinfo *rai = NULL; + const struct addrinfo hint = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_DGRAM, + .ai_protocol = IPPROTO_UDP, + .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV, + }; + + if(getaddrinfo(destaddr, "80", &hint, &rai) || !rai) { + return false; + } + + int sock = socket_in_netns(rai->ai_family, rai->ai_socktype, rai->ai_protocol, netns); + + if(sock == -1) { + freeaddrinfo(rai); + return false; + } + + if(connect(sock, rai->ai_addr, rai->ai_addrlen) && !sockwouldblock(errno)) { + closesocket(sock); + freeaddrinfo(rai); + return false; + } + + freeaddrinfo(rai); + + if(getsockname(sock, &sa->sa, salen)) { + closesocket(sock); + return false; + } + + closesocket(sock); + return true; +} + +static bool getlocaladdrname(const char *destaddr, char *host, socklen_t hostlen, int netns) { + sockaddr_t sa; + socklen_t salen = sizeof(sa); + + if(!getlocaladdr(destaddr, &sa, &salen, netns)) { + return false; + } + + if(getnameinfo(&sa.sa, salen, host, hostlen, NULL, 0, NI_NUMERICHOST | NI_NUMERICSERV)) { + return false; + } + + return true; +} + +char *meshlink_get_external_address(meshlink_handle_t *mesh) { + return meshlink_get_external_address_for_family(mesh, AF_UNSPEC); +} + +char *meshlink_get_external_address_for_family(meshlink_handle_t *mesh, int family) { + logger(mesh, MESHLINK_DEBUG, "meshlink_get_external_address_for_family(%d)", family); + const char *url = mesh->external_address_url; + + if(!url) { + url = "http://meshlink.io/host.cgi"; + } + + /* Find the hostname part between the slashes */ + if(strncmp(url, "http://", 7)) { + abort(); + meshlink_errno = MESHLINK_EINTERNAL; + return NULL; + } + + const char *begin = url + 7; + + const char *end = strchr(begin, '/'); + + if(!end) { + end = begin + strlen(begin); + } + + /* Make a copy */ + char host[end - begin + 1]; + strncpy(host, begin, end - begin); + host[end - begin] = 0; + + char *port = strchr(host, ':'); + + if(port) { + *port++ = 0; + } + + logger(mesh, MESHLINK_DEBUG, "Trying to discover externally visible hostname...\n"); + struct addrinfo *ai = adns_blocking_request(mesh, xstrdup(host), xstrdup(port ? port : "80"), SOCK_STREAM, 5); + char line[256]; + char *hostname = NULL; + + for(struct addrinfo *aip = ai; aip; aip = aip->ai_next) { + if(family != AF_UNSPEC && aip->ai_family != family) { + continue; + } + + int s = socket_in_netns(aip->ai_family, aip->ai_socktype, aip->ai_protocol, mesh->netns); + +#ifdef SO_NOSIGPIPE + int nosigpipe = 1; + setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); +#endif + + if(s >= 0) { + set_timeout(s, 5000); + + if(connect(s, aip->ai_addr, aip->ai_addrlen)) { + closesocket(s); + s = -1; + } + } + + if(s >= 0) { + send(s, "GET ", 4, 0); + send(s, url, strlen(url), 0); + send(s, " HTTP/1.0\r\n\r\n", 13, 0); + int len = recv(s, line, sizeof(line) - 1, MSG_WAITALL); + + if(len > 0) { + line[len] = 0; + + if(line[len - 1] == '\n') { + line[--len] = 0; + } + + char *p = strrchr(line, '\n'); + + if(p && p[1]) { + hostname = xstrdup(p + 1); + } + } + + closesocket(s); + + if(hostname) { + break; + } + } + } + + if(ai) { + freeaddrinfo(ai); + } + + // Check that the hostname is reasonable + if(hostname && !is_valid_hostname(hostname)) { + free(hostname); + hostname = NULL; + } + + if(!hostname) { + meshlink_errno = MESHLINK_ERESOLV; + } + + return hostname; +} + +static bool is_localaddr(sockaddr_t *sa) { + switch(sa->sa.sa_family) { + case AF_INET: + return *(uint8_t *)(&sa->in.sin_addr.s_addr) == 127; + + case AF_INET6: { + uint16_t first = sa->in6.sin6_addr.s6_addr[0] << 8 | sa->in6.sin6_addr.s6_addr[1]; + return first == 0 || (first & 0xffc0) == 0xfe80; + } + + default: + return false; + } +} + +#ifdef HAVE_GETIFADDRS +struct getifaddrs_in_netns_params { + struct ifaddrs **ifa; + int netns; +}; + +#ifdef HAVE_SETNS +static void *getifaddrs_in_netns_thread(void *arg) { + struct getifaddrs_in_netns_params *params = arg; + + if(setns(params->netns, CLONE_NEWNET) == -1) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(getifaddrs(params->ifa) != 0) { + *params->ifa = NULL; + } + + return NULL; +} +#endif // HAVE_SETNS + +static int getifaddrs_in_netns(struct ifaddrs **ifa, int netns) { + if(netns == -1) { + return getifaddrs(ifa); + } + +#ifdef HAVE_SETNS + struct getifaddrs_in_netns_params params = {ifa, netns}; + pthread_t thr; + + if(pthread_create(&thr, NULL, getifaddrs_in_netns_thread, ¶ms) == 0) { + if(pthread_join(thr, NULL) != 0) { + abort(); + } + } + + return *params.ifa ? 0 : -1; +#else + return -1; +#endif // HAVE_SETNS + +} +#endif + +char *meshlink_get_local_address_for_family(meshlink_handle_t *mesh, int family) { + logger(mesh, MESHLINK_DEBUG, "meshlink_get_local_address_for_family(%d)", family); + + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + // Determine address of the local interface used for outgoing connections. + char localaddr[NI_MAXHOST]; + bool success = false; + + if(family == AF_INET) { + success = getlocaladdrname("93.184.216.34", localaddr, sizeof(localaddr), mesh->netns); + } else if(family == AF_INET6) { + success = getlocaladdrname("2606:2800:220:1:248:1893:25c8:1946", localaddr, sizeof(localaddr), mesh->netns); + } + +#ifdef HAVE_GETIFADDRS + + if(!success) { + struct ifaddrs *ifa = NULL; + getifaddrs_in_netns(&ifa, mesh->netns); + + for(struct ifaddrs *ifap = ifa; ifap; ifap = ifap->ifa_next) { + sockaddr_t *sa = (sockaddr_t *)ifap->ifa_addr; + + if(!sa || sa->sa.sa_family != family) { + continue; + } + + if(is_localaddr(sa)) { + continue; + } + + if(!getnameinfo(&sa->sa, SALEN(sa->sa), localaddr, sizeof(localaddr), NULL, 0, NI_NUMERICHOST | NI_NUMERICSERV)) { + success = true; + break; + } + } + + freeifaddrs(ifa); + } + +#endif + + if(!success) { + meshlink_errno = MESHLINK_ENETWORK; + return NULL; + } + + return xstrdup(localaddr); +} + +static void remove_duplicate_hostnames(char *host[], char *port[], int n) { + for(int i = 0; i < n; i++) { + if(!host[i]) { + continue; + } + + // Ignore duplicate hostnames + bool found = false; + + for(int j = 0; j < i; j++) { + if(!host[j]) { + continue; + } + + if(strcmp(host[i], host[j])) { + continue; + } + + if(strcmp(port[i], port[j])) { + continue; + } + + found = true; + break; + } + + if(found || !is_valid_hostname(host[i])) { + free(host[i]); + free(port[i]); + host[i] = NULL; + port[i] = NULL; + continue; + } + } +} + +// This gets the hostname part for use in invitation URLs +static char *get_my_hostname(meshlink_handle_t *mesh, uint32_t flags) { + int count = 4 + (mesh->invitation_addresses ? mesh->invitation_addresses->count : 0); + int n = 0; + char *hostname[count]; + char *port[count]; + char *hostport = NULL; + + memset(hostname, 0, sizeof(hostname)); + memset(port, 0, sizeof(port)); + + if(!(flags & (MESHLINK_INVITE_LOCAL | MESHLINK_INVITE_PUBLIC))) { + flags |= MESHLINK_INVITE_LOCAL | MESHLINK_INVITE_PUBLIC; + } + + if(!(flags & (MESHLINK_INVITE_IPV4 | MESHLINK_INVITE_IPV6))) { + flags |= MESHLINK_INVITE_IPV4 | MESHLINK_INVITE_IPV6; + } + + // Add all explicitly set invitation addresses + if(mesh->invitation_addresses) { + for list_each(char, combo, mesh->invitation_addresses) { + hostname[n] = xstrdup(combo); + char *slash = strrchr(hostname[n], '/'); + + if(slash) { + *slash = 0; + port[n] = xstrdup(slash + 1); + } + + n++; + } + } + + // Add local addresses if requested + if(flags & MESHLINK_INVITE_LOCAL) { + if(flags & MESHLINK_INVITE_IPV4) { + hostname[n++] = meshlink_get_local_address_for_family(mesh, AF_INET); + } + + if(flags & MESHLINK_INVITE_IPV6) { + hostname[n++] = meshlink_get_local_address_for_family(mesh, AF_INET6); + } + } + + // Add public/canonical addresses if requested + if(flags & MESHLINK_INVITE_PUBLIC) { + // Try the CanonicalAddress first + get_canonical_address(mesh->self, &hostname[n], &port[n]); + + if(!hostname[n] && count == 4) { + if(flags & MESHLINK_INVITE_IPV4) { + hostname[n++] = meshlink_get_external_address_for_family(mesh, AF_INET); + } + + if(flags & MESHLINK_INVITE_IPV6) { + hostname[n++] = meshlink_get_external_address_for_family(mesh, AF_INET6); + } + } else { + n++; + } + } + + for(int i = 0; i < n; i++) { + // Ensure we always have a port number + if(hostname[i] && !port[i]) { + port[i] = xstrdup(mesh->myport); + } + } + + remove_duplicate_hostnames(hostname, port, n); + + // Resolve the hostnames + for(int i = 0; i < n; i++) { + if(!hostname[i]) { + continue; + } + + // Convert what we have to a sockaddr + struct addrinfo *ai_in = adns_blocking_request(mesh, xstrdup(hostname[i]), xstrdup(port[i]), SOCK_STREAM, 5); + + if(!ai_in) { + continue; + } + + // Remember the address(es) + for(struct addrinfo *aip = ai_in; aip; aip = aip->ai_next) { + node_add_recent_address(mesh, mesh->self, (sockaddr_t *)aip->ai_addr); + } + + freeaddrinfo(ai_in); + continue; + } + + // Remove duplicates again, since IPv4 and IPv6 addresses might map to the same hostname + remove_duplicate_hostnames(hostname, port, n); + + // Concatenate all unique address to the hostport string + for(int i = 0; i < n; i++) { + if(!hostname[i]) { + continue; + } + + // Append the address to the hostport string + char *newhostport; + xasprintf(&newhostport, (strchr(hostname[i], ':') ? "%s%s[%s]:%s" : "%s%s%s:%s"), hostport ? hostport : "", hostport ? "," : "", hostname[i], port[i]); + free(hostport); + hostport = newhostport; + + free(hostname[i]); + free(port[i]); + } + + return hostport; +} + +static bool try_bind(meshlink_handle_t *mesh, int port) { + struct addrinfo *ai = NULL; + struct addrinfo hint = { + .ai_flags = AI_PASSIVE, + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP, + }; + + char portstr[16]; + snprintf(portstr, sizeof(portstr), "%d", port); + + if(getaddrinfo(NULL, portstr, &hint, &ai) || !ai) { + return false; + } + + bool success = false; + + for(struct addrinfo *aip = ai; aip; aip = aip->ai_next) { + /* Try to bind to TCP. */ + + int tcp_fd = setup_tcp_listen_socket(mesh, aip); + + if(tcp_fd == -1) { + if(errno == EADDRINUSE) { + /* If this port is in use for any address family, avoid it. */ + success = false; + break; + } else { + continue; + } + } + + /* If TCP worked, then we require that UDP works as well. */ + + int udp_fd = setup_udp_listen_socket(mesh, aip); + + if(udp_fd == -1) { + closesocket(tcp_fd); + success = false; + break; + } + + closesocket(tcp_fd); + closesocket(udp_fd); + success = true; + } + + freeaddrinfo(ai); + return success; +} + +int check_port(meshlink_handle_t *mesh) { + for(int i = 0; i < 1000; i++) { + int port = 0x1000 + prng(mesh, 0x8000); + + if(try_bind(mesh, port)) { + free(mesh->myport); + xasprintf(&mesh->myport, "%d", port); + return port; + } + } + + meshlink_errno = MESHLINK_ENETWORK; + logger(mesh, MESHLINK_DEBUG, "Could not find any available network port.\n"); + return 0; +} + +static bool write_main_config_files(meshlink_handle_t *mesh) { + if(!mesh->confbase) { + return true; + } + + uint8_t buf[4096]; + + /* Write the main config file */ + packmsg_output_t out = {buf, sizeof buf}; + + packmsg_add_uint32(&out, MESHLINK_CONFIG_VERSION); + packmsg_add_str(&out, mesh->name); + packmsg_add_bin(&out, ecdsa_get_private_key(mesh->private_key), 96); + packmsg_add_bin(&out, ecdsa_get_private_key(mesh->invitation_key), 96); + packmsg_add_uint16(&out, atoi(mesh->myport)); + + if(!packmsg_output_ok(&out)) { + return false; + } + + config_t config = {buf, packmsg_output_size(&out, buf)}; + + if(!main_config_write(mesh, "current", &config, mesh->config_key)) { + return false; + } + + /* Write our own host config file */ + if(!node_write_config(mesh, mesh->self, true)) { + return false; + } + + return true; +} + +typedef struct { + meshlink_handle_t *mesh; + int sock; + char cookie[18 + 32]; + char hash[18]; + bool success; + sptps_t sptps; + char *data; + size_t thedatalen; + size_t blen; + char line[4096]; + char buffer[4096]; +} join_state_t; + +static bool finalize_join(join_state_t *state, const void *buf, uint16_t len) { + meshlink_handle_t *mesh = state->mesh; + packmsg_input_t in = {buf, len}; + uint32_t version = packmsg_get_uint32(&in); + + if(version != MESHLINK_INVITATION_VERSION) { + logger(mesh, MESHLINK_ERROR, "Invalid invitation version!\n"); + return false; + } + + char *name = packmsg_get_str_dup(&in); + char *submesh_name = packmsg_get_str_dup(&in); + dev_class_t devclass = packmsg_get_int32(&in); + uint32_t count = packmsg_get_array(&in); + + if(!name || !check_id(name)) { + logger(mesh, MESHLINK_DEBUG, "No valid Name found in invitation!\n"); + free(name); + free(submesh_name); + return false; + } + + if(!submesh_name || (strcmp(submesh_name, CORE_MESH) && !check_id(submesh_name))) { + logger(mesh, MESHLINK_DEBUG, "No valid Submesh found in invitation!\n"); + free(name); + free(submesh_name); + return false; + } + + if(!count) { + logger(mesh, MESHLINK_ERROR, "Incomplete invitation file!\n"); + free(name); + free(submesh_name); + return false; + } + + free(mesh->name); + free(mesh->self->name); + mesh->name = name; + mesh->self->name = xstrdup(name); + mesh->self->submesh = strcmp(submesh_name, CORE_MESH) ? lookup_or_create_submesh(mesh, submesh_name) : NULL; + free(submesh_name); + mesh->self->devclass = devclass == DEV_CLASS_UNKNOWN ? mesh->devclass : devclass; + + // Initialize configuration directory + if(!config_init(mesh, "current")) { + return false; + } + + if(!write_main_config_files(mesh)) { + return false; + } + + // Write host config files + for(uint32_t i = 0; i < count; i++) { + const void *data; + uint32_t data_len = packmsg_get_bin_raw(&in, &data); + + if(!data_len) { + logger(mesh, MESHLINK_ERROR, "Incomplete invitation file!\n"); + return false; + } + + packmsg_input_t in2 = {data, data_len}; + uint32_t version2 = packmsg_get_uint32(&in2); + char *name2 = packmsg_get_str_dup(&in2); + + if(!packmsg_input_ok(&in2) || version2 != MESHLINK_CONFIG_VERSION || !check_id(name2)) { + free(name2); + packmsg_input_invalidate(&in); + break; + } + + if(!check_id(name2)) { + free(name2); + break; + } + + if(!strcmp(name2, mesh->name)) { + logger(mesh, MESHLINK_ERROR, "Secondary chunk would overwrite our own host config file.\n"); + free(name2); + meshlink_errno = MESHLINK_EPEER; + return false; + } + + node_t *n = new_node(); + n->name = name2; + + config_t config = {data, data_len}; + + if(!node_read_from_config(mesh, n, &config)) { + free_node(n); + logger(mesh, MESHLINK_ERROR, "Invalid host config file in invitation file!\n"); + meshlink_errno = MESHLINK_EPEER; + return false; + } + + if(i == 0) { + /* The first host config file is of the inviter itself; + * remember the address we are currently using for the invitation connection. + */ + sockaddr_t sa; + socklen_t salen = sizeof(sa); + + if(getpeername(state->sock, &sa.sa, &salen) == 0) { + node_add_recent_address(mesh, n, &sa); + } + } + + /* Clear the reachability times, since we ourself have never seen these nodes yet */ + n->last_reachable = 0; + n->last_unreachable = 0; + + if(!node_write_config(mesh, n, true)) { + free_node(n); + return false; + } + + node_add(mesh, n); + } + + /* Ensure the configuration directory metadata is on disk */ + if(!config_sync(mesh, "current") || (mesh->confbase && !sync_path(mesh->confbase))) { + return false; + } + + if(!mesh->inviter_commits_first) { + devtool_set_inviter_commits_first(false); + } + + sptps_send_record(&state->sptps, 1, ecdsa_get_public_key(mesh->private_key), 32); + + logger(mesh, MESHLINK_DEBUG, "Configuration stored in: %s\n", mesh->confbase); + + return true; +} + +static bool invitation_send(void *handle, uint8_t type, const void *data, size_t len) { + (void)type; + join_state_t *state = handle; + const char *ptr = data; + + while(len) { + int result = send(state->sock, ptr, len, 0); + + if(result == -1 && errno == EINTR) { + continue; + } else if(result <= 0) { + return false; + } + + ptr += result; + len -= result; + } + + return true; +} + +static bool invitation_receive(void *handle, uint8_t type, const void *msg, uint16_t len) { + join_state_t *state = handle; + meshlink_handle_t *mesh = state->mesh; + + if(mesh->inviter_commits_first) { + switch(type) { + case SPTPS_HANDSHAKE: + return sptps_send_record(&state->sptps, 2, state->cookie, 18 + 32); + + case 1: + break; + + case 0: + if(!finalize_join(state, msg, len)) { + return false; + } + + logger(mesh, MESHLINK_DEBUG, "Invitation successfully accepted.\n"); + shutdown(state->sock, SHUT_RDWR); + state->success = true; + break; + + default: + return false; + } + } else { + switch(type) { + case SPTPS_HANDSHAKE: + return sptps_send_record(&state->sptps, 0, state->cookie, 18); + + case 0: + return finalize_join(state, msg, len); + + case 1: + logger(mesh, MESHLINK_DEBUG, "Invitation successfully accepted.\n"); + shutdown(state->sock, SHUT_RDWR); + state->success = true; + break; + + default: + return false; + } + } + + return true; +} + +static bool recvline(join_state_t *state) { + char *newline = NULL; + + while(!(newline = memchr(state->buffer, '\n', state->blen))) { + int result = recv(state->sock, state->buffer + state->blen, sizeof(state)->buffer - state->blen, 0); + + if(result == -1 && errno == EINTR) { + continue; + } else if(result <= 0) { + return false; + } + + state->blen += result; + } + + if((size_t)(newline - state->buffer) >= sizeof(state->line)) { + return false; + } + + size_t len = newline - state->buffer; + + memcpy(state->line, state->buffer, len); + state->line[len] = 0; + memmove(state->buffer, newline + 1, state->blen - len - 1); + state->blen -= len + 1; + + return true; +} + +static bool sendline(int fd, const char *format, ...) { + char buffer[4096]; + char *p = buffer; + int blen = 0; + va_list ap; + + va_start(ap, format); + blen = vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + if(blen < 1 || (size_t)blen >= sizeof(buffer)) { + return false; + } + + buffer[blen] = '\n'; + blen++; + + while(blen) { + int result = send(fd, p, blen, MSG_NOSIGNAL); + + if(result == -1 && errno == EINTR) { + continue; + } else if(result <= 0) { + return false; + } + + p += result; + blen -= result; + } + + return true; +} + +static const char *errstr[] = { + [MESHLINK_OK] = "No error", + [MESHLINK_EINVAL] = "Invalid argument", + [MESHLINK_ENOMEM] = "Out of memory", + [MESHLINK_ENOENT] = "No such node", + [MESHLINK_EEXIST] = "Node already exists", + [MESHLINK_EINTERNAL] = "Internal error", + [MESHLINK_ERESOLV] = "Could not resolve hostname", + [MESHLINK_ESTORAGE] = "Storage error", + [MESHLINK_ENETWORK] = "Network error", + [MESHLINK_EPEER] = "Error communicating with peer", + [MESHLINK_ENOTSUP] = "Operation not supported", + [MESHLINK_EBUSY] = "MeshLink instance already in use", + [MESHLINK_EBLACKLISTED] = "Node is blacklisted", +}; + +const char *meshlink_strerror(meshlink_errno_t err) { + if((int)err < 0 || err >= sizeof(errstr) / sizeof(*errstr)) { + return "Invalid error code"; + } + + return errstr[err]; +} + +static bool ecdsa_keygen(meshlink_handle_t *mesh) { + logger(mesh, MESHLINK_DEBUG, "Generating ECDSA keypairs:\n"); + + mesh->private_key = ecdsa_generate(); + mesh->invitation_key = ecdsa_generate(); + + if(!mesh->private_key || !mesh->invitation_key) { + logger(mesh, MESHLINK_ERROR, "Error during key generation!\n"); + meshlink_errno = MESHLINK_EINTERNAL; + return false; + } + + logger(mesh, MESHLINK_DEBUG, "Done.\n"); + + return true; +} + +static bool timespec_lt(const struct timespec *a, const struct timespec *b) { + if(a->tv_sec == b->tv_sec) { + return a->tv_nsec < b->tv_nsec; + } else { + return a->tv_sec < b->tv_sec; + } +} + +static struct timespec idle(event_loop_t *loop, void *data) { + (void)loop; + meshlink_handle_t *mesh = data; + struct timespec t, tmin = {3600, 0}; + + for splay_each(node_t, n, mesh->nodes) { + if(!n->utcp) { + continue; + } + + t = utcp_timeout(n->utcp); + + if(timespec_lt(&t, &tmin)) { + tmin = t; + } + } + + return tmin; +} + +// Get our local address(es) by simulating connecting to an Internet host. +static void add_local_addresses(meshlink_handle_t *mesh) { + sockaddr_t sa; + sa.storage.ss_family = AF_UNKNOWN; + socklen_t salen = sizeof(sa); + + // IPv4 example.org + + if(getlocaladdr("93.184.216.34", &sa, &salen, mesh->netns)) { + sa.in.sin_port = ntohs(atoi(mesh->myport)); + node_add_recent_address(mesh, mesh->self, &sa); + } + + // IPv6 example.org + + salen = sizeof(sa); + + if(getlocaladdr("2606:2800:220:1:248:1893:25c8:1946", &sa, &salen, mesh->netns)) { + sa.in6.sin6_port = ntohs(atoi(mesh->myport)); + node_add_recent_address(mesh, mesh->self, &sa); + } +} + +static bool meshlink_setup(meshlink_handle_t *mesh) { + if(!config_destroy(mesh->confbase, "new")) { + logger(mesh, MESHLINK_ERROR, "Could not delete configuration in %s/new: %s\n", mesh->confbase, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + if(!config_destroy(mesh->confbase, "old")) { + logger(mesh, MESHLINK_ERROR, "Could not delete configuration in %s/old: %s\n", mesh->confbase, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + if(!config_init(mesh, "current")) { + logger(mesh, MESHLINK_ERROR, "Could not set up configuration in %s/current: %s\n", mesh->confbase, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + if(!ecdsa_keygen(mesh)) { + meshlink_errno = MESHLINK_EINTERNAL; + return false; + } + + if(check_port(mesh) == 0) { + meshlink_errno = MESHLINK_ENETWORK; + return false; + } + + /* Create a node for ourself */ + + mesh->self = new_node(); + mesh->self->name = xstrdup(mesh->name); + mesh->self->devclass = mesh->devclass; + mesh->self->ecdsa = ecdsa_set_public_key(ecdsa_get_public_key(mesh->private_key)); + mesh->self->session_id = mesh->session_id; + + if(!write_main_config_files(mesh)) { + logger(mesh, MESHLINK_ERROR, "Could not write main config files into %s/current: %s\n", mesh->confbase, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + /* Ensure the configuration directory metadata is on disk */ + if(!config_sync(mesh, "current")) { + return false; + } + + return true; +} + +static bool meshlink_read_config(meshlink_handle_t *mesh) { + config_t config; + + if(!main_config_read(mesh, "current", &config, mesh->config_key)) { + logger(NULL, MESHLINK_ERROR, "Could not read main configuration file!"); + return false; + } + + packmsg_input_t in = {config.buf, config.len}; + const void *private_key; + const void *invitation_key; + + uint32_t version = packmsg_get_uint32(&in); + char *name = packmsg_get_str_dup(&in); + uint32_t private_key_len = packmsg_get_bin_raw(&in, &private_key); + uint32_t invitation_key_len = packmsg_get_bin_raw(&in, &invitation_key); + uint16_t myport = packmsg_get_uint16(&in); + + if(!packmsg_done(&in) || version != MESHLINK_CONFIG_VERSION || private_key_len != 96 || invitation_key_len != 96) { + logger(NULL, MESHLINK_ERROR, "Error parsing main configuration file!"); + free(name); + config_free(&config); + return false; + } + + if(mesh->name && strcmp(mesh->name, name)) { + logger(NULL, MESHLINK_ERROR, "Configuration is for a different name (%s)!", name); + meshlink_errno = MESHLINK_ESTORAGE; + free(name); + config_free(&config); + return false; + } + + free(mesh->name); + mesh->name = name; + xasprintf(&mesh->myport, "%u", myport); + mesh->private_key = ecdsa_set_private_key(private_key); + mesh->invitation_key = ecdsa_set_private_key(invitation_key); + config_free(&config); + + /* Create a node for ourself and read our host configuration file */ + + mesh->self = new_node(); + mesh->self->name = xstrdup(name); + mesh->self->devclass = mesh->devclass; + mesh->self->session_id = mesh->session_id; + + if(!node_read_public_key(mesh, mesh->self)) { + logger(NULL, MESHLINK_ERROR, "Could not read our host configuration file!"); + meshlink_errno = MESHLINK_ESTORAGE; + free_node(mesh->self); + mesh->self = NULL; + return false; + } + + return true; +} + +#ifdef HAVE_SETNS +static void *setup_network_in_netns_thread(void *arg) { + meshlink_handle_t *mesh = arg; + + if(setns(mesh->netns, CLONE_NEWNET) != 0) { + return NULL; + } + + bool success = setup_network(mesh); + return success ? arg : NULL; +} +#endif // HAVE_SETNS + +meshlink_open_params_t *meshlink_open_params_init(const char *confbase, const char *name, const char *appname, dev_class_t devclass) { + logger(NULL, MESHLINK_DEBUG, "meshlink_open_params_init(%s, %s, %s, %d)", confbase, name, appname, devclass); + + if(!confbase || !*confbase) { + logger(NULL, MESHLINK_ERROR, "No confbase given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(!appname || !*appname) { + logger(NULL, MESHLINK_ERROR, "No appname given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(strchr(appname, ' ')) { + logger(NULL, MESHLINK_ERROR, "Invalid appname given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(name && !check_id(name)) { + logger(NULL, MESHLINK_ERROR, "Invalid name given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(devclass < 0 || devclass >= DEV_CLASS_COUNT) { + logger(NULL, MESHLINK_ERROR, "Invalid devclass given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + meshlink_open_params_t *params = xzalloc(sizeof * params); + + params->confbase = xstrdup(confbase); + params->name = name ? xstrdup(name) : NULL; + params->appname = xstrdup(appname); + params->devclass = devclass; + params->netns = -1; + + xasprintf(¶ms->lock_filename, "%s" SLASH "meshlink.lock", confbase); + + return params; +} + +bool meshlink_open_params_set_netns(meshlink_open_params_t *params, int netns) { + logger(NULL, MESHLINK_DEBUG, "meshlink_open_params_set_netnst(%d)", netns); + + if(!params) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + params->netns = netns; + + return true; +} + +bool meshlink_open_params_set_storage_key(meshlink_open_params_t *params, const void *key, size_t keylen) { + logger(NULL, MESHLINK_DEBUG, "meshlink_open_params_set_storage_key(%p, %zu)", key, keylen); + + if(!params) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if((!key && keylen) || (key && !keylen)) { + logger(NULL, MESHLINK_ERROR, "Invalid key length!\n"); + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + params->key = key; + params->keylen = keylen; + + return true; +} + +bool meshlink_open_params_set_storage_policy(meshlink_open_params_t *params, meshlink_storage_policy_t policy) { + logger(NULL, MESHLINK_DEBUG, "meshlink_open_params_set_storage_policy(%d)", policy); + + if(!params) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + params->storage_policy = policy; + + return true; +} + +bool meshlink_open_params_set_lock_filename(meshlink_open_params_t *params, const char *filename) { + logger(NULL, MESHLINK_DEBUG, "meshlink_open_params_set_lock_filename(%s)", filename); + + if(!params || !filename) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + free(params->lock_filename); + params->lock_filename = xstrdup(filename); + + return true; +} + +bool meshlink_encrypted_key_rotate(meshlink_handle_t *mesh, const void *new_key, size_t new_keylen) { + logger(NULL, MESHLINK_DEBUG, "meshlink_encrypted_key_rotate(%p, %zu)", new_key, new_keylen); + + if(!mesh || !new_key || !new_keylen) { + logger(mesh, MESHLINK_ERROR, "Invalid arguments given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + // Create hash for the new key + void *new_config_key; + new_config_key = xmalloc(CHACHA_POLY1305_KEYLEN); + + if(!prf(new_key, new_keylen, "MeshLink configuration key", 26, new_config_key, CHACHA_POLY1305_KEYLEN)) { + logger(mesh, MESHLINK_ERROR, "Error creating new configuration key!\n"); + meshlink_errno = MESHLINK_EINTERNAL; + pthread_mutex_unlock(&mesh->mutex); + return false; + } + + // Copy contents of the "current" confbase sub-directory to "new" confbase sub-directory with the new key + + if(!config_copy(mesh, "current", mesh->config_key, "new", new_config_key)) { + logger(mesh, MESHLINK_ERROR, "Could not set up configuration in %s/old: %s\n", mesh->confbase, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + pthread_mutex_unlock(&mesh->mutex); + return false; + } + + devtool_keyrotate_probe(1); + + // Rename confbase/current/ to confbase/old + + if(!config_rename(mesh, "current", "old")) { + logger(mesh, MESHLINK_ERROR, "Cannot rename %s/current to %s/old\n", mesh->confbase, mesh->confbase); + meshlink_errno = MESHLINK_ESTORAGE; + pthread_mutex_unlock(&mesh->mutex); + return false; + } + + devtool_keyrotate_probe(2); + + // Rename confbase/new/ to confbase/current + + if(!config_rename(mesh, "new", "current")) { + logger(mesh, MESHLINK_ERROR, "Cannot rename %s/new to %s/current\n", mesh->confbase, mesh->confbase); + meshlink_errno = MESHLINK_ESTORAGE; + pthread_mutex_unlock(&mesh->mutex); + return false; + } + + devtool_keyrotate_probe(3); + + // Cleanup the "old" confbase sub-directory + + if(!config_destroy(mesh->confbase, "old")) { + pthread_mutex_unlock(&mesh->mutex); + return false; + } + + // Change the mesh handle key with new key + + free(mesh->config_key); + mesh->config_key = new_config_key; + + pthread_mutex_unlock(&mesh->mutex); + + return true; +} + +void meshlink_open_params_free(meshlink_open_params_t *params) { + logger(NULL, MESHLINK_DEBUG, "meshlink_open_params_free()"); + + if(!params) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + free(params->confbase); + free(params->name); + free(params->appname); + free(params->lock_filename); + + free(params); +} + +/// Device class traits +static const dev_class_traits_t default_class_traits[DEV_CLASS_COUNT] = { + { .pingtimeout = 5, .pinginterval = 60, .maxtimeout = 900, .min_connects = 3, .max_connects = 10000, .edge_weight = 1 }, // DEV_CLASS_BACKBONE + { .pingtimeout = 5, .pinginterval = 60, .maxtimeout = 900, .min_connects = 3, .max_connects = 100, .edge_weight = 3 }, // DEV_CLASS_STATIONARY + { .pingtimeout = 5, .pinginterval = 60, .maxtimeout = 900, .min_connects = 3, .max_connects = 3, .edge_weight = 6 }, // DEV_CLASS_PORTABLE + { .pingtimeout = 5, .pinginterval = 60, .maxtimeout = 900, .min_connects = 1, .max_connects = 1, .edge_weight = 9 }, // DEV_CLASS_UNKNOWN +}; + +meshlink_handle_t *meshlink_open(const char *confbase, const char *name, const char *appname, dev_class_t devclass) { + logger(NULL, MESHLINK_DEBUG, "meshlink_open(%s, %s, %s, %d)", confbase, name, appname, devclass); + + if(!confbase || !*confbase) { + logger(NULL, MESHLINK_ERROR, "No confbase given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + char lock_filename[PATH_MAX]; + snprintf(lock_filename, sizeof(lock_filename), "%s" SLASH "meshlink.lock", confbase); + + /* Create a temporary struct on the stack, to avoid allocating and freeing one. */ + meshlink_open_params_t params = { + .confbase = (char *)confbase, + .lock_filename = lock_filename, + .name = (char *)name, + .appname = (char *)appname, + .devclass = devclass, + .netns = -1, + }; + + return meshlink_open_ex(¶ms); +} + +meshlink_handle_t *meshlink_open_encrypted(const char *confbase, const char *name, const char *appname, dev_class_t devclass, const void *key, size_t keylen) { + logger(NULL, MESHLINK_DEBUG, "meshlink_open_encrypted(%s, %s, %s, %d, %p, %zu)", confbase, name, appname, devclass, key, keylen); + + if(!confbase || !*confbase) { + logger(NULL, MESHLINK_ERROR, "No confbase given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + char lock_filename[PATH_MAX]; + snprintf(lock_filename, sizeof(lock_filename), "%s" SLASH "meshlink.lock", confbase); + + /* Create a temporary struct on the stack, to avoid allocating and freeing one. */ + meshlink_open_params_t params = { + .confbase = (char *)confbase, + .lock_filename = lock_filename, + .name = (char *)name, + .appname = (char *)appname, + .devclass = devclass, + .netns = -1, + }; + + if(!meshlink_open_params_set_storage_key(¶ms, key, keylen)) { + return false; + } + + return meshlink_open_ex(¶ms); +} + +meshlink_handle_t *meshlink_open_ephemeral(const char *name, const char *appname, dev_class_t devclass) { + logger(NULL, MESHLINK_DEBUG, "meshlink_open_ephemeral(%s, %s, %d)", name, appname, devclass); + + if(!name) { + logger(NULL, MESHLINK_ERROR, "No name given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(!check_id(name)) { + logger(NULL, MESHLINK_ERROR, "Invalid name given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(!appname || !*appname) { + logger(NULL, MESHLINK_ERROR, "No appname given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(strchr(appname, ' ')) { + logger(NULL, MESHLINK_ERROR, "Invalid appname given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(devclass < 0 || devclass >= DEV_CLASS_COUNT) { + logger(NULL, MESHLINK_ERROR, "Invalid devclass given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + /* Create a temporary struct on the stack, to avoid allocating and freeing one. */ + meshlink_open_params_t params = { + .name = (char *)name, + .appname = (char *)appname, + .devclass = devclass, + .netns = -1, + }; + + return meshlink_open_ex(¶ms); +} + +meshlink_handle_t *meshlink_open_ex(const meshlink_open_params_t *params) { + logger(NULL, MESHLINK_DEBUG, "meshlink_open_ex()"); + + // Validate arguments provided by the application + if(!params->appname || !*params->appname) { + logger(NULL, MESHLINK_ERROR, "No appname given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(strchr(params->appname, ' ')) { + logger(NULL, MESHLINK_ERROR, "Invalid appname given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(params->name && !check_id(params->name)) { + logger(NULL, MESHLINK_ERROR, "Invalid name given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(params->devclass < 0 || params->devclass >= DEV_CLASS_COUNT) { + logger(NULL, MESHLINK_ERROR, "Invalid devclass given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if((params->key && !params->keylen) || (!params->key && params->keylen)) { + logger(NULL, MESHLINK_ERROR, "Invalid key length!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + meshlink_handle_t *mesh = xzalloc(sizeof(meshlink_handle_t)); + + if(params->confbase) { + mesh->confbase = xstrdup(params->confbase); + } + + mesh->appname = xstrdup(params->appname); + mesh->devclass = params->devclass; + mesh->discovery.enabled = true; + mesh->invitation_timeout = 604800; // 1 week + mesh->netns = params->netns; + mesh->submeshes = NULL; + mesh->log_cb = global_log_cb; + mesh->log_level = global_log_level; + mesh->packet = xmalloc(sizeof(vpn_packet_t)); + + randomize(&mesh->prng_state, sizeof(mesh->prng_state)); + + do { + randomize(&mesh->session_id, sizeof(mesh->session_id)); + } while(mesh->session_id == 0); + + memcpy(mesh->dev_class_traits, default_class_traits, sizeof(default_class_traits)); + + mesh->name = params->name ? xstrdup(params->name) : NULL; + + // Hash the key + if(params->key) { + mesh->config_key = xmalloc(CHACHA_POLY1305_KEYLEN); + + if(!prf(params->key, params->keylen, "MeshLink configuration key", 26, mesh->config_key, CHACHA_POLY1305_KEYLEN)) { + logger(NULL, MESHLINK_ERROR, "Error creating configuration key!\n"); + meshlink_close(mesh); + meshlink_errno = MESHLINK_EINTERNAL; + return NULL; + } + } + + // initialize mutexes and conds + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + + if(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0) { + abort(); + } + + pthread_mutex_init(&mesh->mutex, &attr); + pthread_cond_init(&mesh->cond, NULL); + + pthread_cond_init(&mesh->adns_cond, NULL); + + mesh->threadstarted = false; + event_loop_init(&mesh->loop); + mesh->loop.data = mesh; + + meshlink_queue_init(&mesh->outpacketqueue); + + // Atomically lock the configuration directory. + if(!main_config_lock(mesh, params->lock_filename)) { + meshlink_close(mesh); + return NULL; + } + + // If no configuration exists yet, create it. + + bool new_configuration = false; + + if(!meshlink_confbase_exists(mesh)) { + if(!mesh->name) { + logger(NULL, MESHLINK_ERROR, "No configuration files found!\n"); + meshlink_close(mesh); + meshlink_errno = MESHLINK_ESTORAGE; + return NULL; + } + + if(!meshlink_setup(mesh)) { + logger(NULL, MESHLINK_ERROR, "Cannot create initial configuration\n"); + meshlink_close(mesh); + return NULL; + } + + new_configuration = true; + } else { + if(!meshlink_read_config(mesh)) { + logger(NULL, MESHLINK_ERROR, "Cannot read main configuration\n"); + meshlink_close(mesh); + return NULL; + } + } + + mesh->storage_policy = params->storage_policy; + +#ifdef HAVE_MINGW + struct WSAData wsa_state; + WSAStartup(MAKEWORD(2, 2), &wsa_state); +#endif + + // Setup up everything + // TODO: we should not open listening sockets yet + + bool success = false; + + if(mesh->netns != -1) { +#ifdef HAVE_SETNS + pthread_t thr; + + if(pthread_create(&thr, NULL, setup_network_in_netns_thread, mesh) == 0) { + void *retval = NULL; + success = pthread_join(thr, &retval) == 0 && retval; + } + +#else + meshlink_errno = MESHLINK_EINTERNAL; + return NULL; + +#endif // HAVE_SETNS + } else { + success = setup_network(mesh); + } + + if(!success) { + meshlink_close(mesh); + meshlink_errno = MESHLINK_ENETWORK; + return NULL; + } + + add_local_addresses(mesh); + + if(!node_write_config(mesh, mesh->self, new_configuration)) { + logger(NULL, MESHLINK_ERROR, "Cannot update configuration\n"); + return NULL; + } + + idle_set(&mesh->loop, idle, mesh); + + logger(NULL, MESHLINK_DEBUG, "meshlink_open returning\n"); + return mesh; +} + +meshlink_submesh_t *meshlink_submesh_open(meshlink_handle_t *mesh, const char *submesh) { + logger(NULL, MESHLINK_DEBUG, "meshlink_submesh_open(%s)", submesh); + + meshlink_submesh_t *s = NULL; + + if(!mesh) { + logger(NULL, MESHLINK_ERROR, "No mesh handle given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(!submesh || !*submesh) { + logger(NULL, MESHLINK_ERROR, "No submesh name given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + //lock mesh->nodes + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + s = (meshlink_submesh_t *)create_submesh(mesh, submesh); + + pthread_mutex_unlock(&mesh->mutex); + + return s; +} + +static void *meshlink_main_loop(void *arg) { + meshlink_handle_t *mesh = arg; + + if(mesh->netns != -1) { +#ifdef HAVE_SETNS + + if(setns(mesh->netns, CLONE_NEWNET) != 0) { + pthread_cond_signal(&mesh->cond); + return NULL; + } + +#else + pthread_cond_signal(&mesh->cond); + return NULL; +#endif // HAVE_SETNS + } + + if(mesh->discovery.enabled) { + discovery_start(mesh); + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + logger(mesh, MESHLINK_DEBUG, "Starting main_loop...\n"); + pthread_cond_broadcast(&mesh->cond); + main_loop(mesh); + logger(mesh, MESHLINK_DEBUG, "main_loop returned.\n"); + + pthread_mutex_unlock(&mesh->mutex); + + // Stop discovery + if(mesh->discovery.enabled) { + discovery_stop(mesh); + } + + return NULL; +} + +bool meshlink_start(meshlink_handle_t *mesh) { + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + logger(mesh, MESHLINK_DEBUG, "meshlink_start called\n"); + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + assert(mesh->self); + assert(mesh->private_key); + assert(mesh->self->ecdsa); + assert(!memcmp((uint8_t *)mesh->self->ecdsa + 64, (uint8_t *)mesh->private_key + 64, 32)); + + if(mesh->threadstarted) { + logger(mesh, MESHLINK_DEBUG, "thread was already running\n"); + pthread_mutex_unlock(&mesh->mutex); + return true; + } + + if(mesh->listen_socket[0].tcp.fd < 0) { + logger(mesh, MESHLINK_ERROR, "Listening socket not open\n"); + meshlink_errno = MESHLINK_ENETWORK; + return false; + } + + // Reset node connection timers + for splay_each(node_t, n, mesh->nodes) { + n->last_connect_try = 0; + } + + // TODO: open listening sockets first + + //Check that a valid name is set + if(!mesh->name) { + logger(mesh, MESHLINK_ERROR, "No name given!\n"); + meshlink_errno = MESHLINK_EINVAL; + pthread_mutex_unlock(&mesh->mutex); + return false; + } + + init_outgoings(mesh); + init_adns(mesh); + + // Start the main thread + + event_loop_start(&mesh->loop); + + // Ensure we have a decent amount of stack space. Musl's default of 80 kB is too small. + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, 1024 * 1024); + + if(pthread_create(&mesh->thread, &attr, meshlink_main_loop, mesh) != 0) { + logger(mesh, MESHLINK_ERROR, "Could not start thread: %s\n", strerror(errno)); + memset(&mesh->thread, 0, sizeof(mesh)->thread); + meshlink_errno = MESHLINK_EINTERNAL; + event_loop_stop(&mesh->loop); + pthread_mutex_unlock(&mesh->mutex); + return false; + } + + pthread_cond_wait(&mesh->cond, &mesh->mutex); + mesh->threadstarted = true; + + // Ensure we are considered reachable + graph(mesh); + + pthread_mutex_unlock(&mesh->mutex); + return true; +} + +void meshlink_stop(meshlink_handle_t *mesh) { + logger(mesh, MESHLINK_DEBUG, "meshlink_stop()\n"); + + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + // Shut down the main thread + event_loop_stop(&mesh->loop); + + // Send ourselves a UDP packet to kick the event loop + for(int i = 0; i < mesh->listen_sockets; i++) { + sockaddr_t sa; + socklen_t salen = sizeof(sa); + + if(getsockname(mesh->listen_socket[i].udp.fd, &sa.sa, &salen) == -1) { + logger(mesh, MESHLINK_ERROR, "System call `%s' failed: %s", "getsockname", sockstrerror(sockerrno)); + continue; + } + + if(sendto(mesh->listen_socket[i].udp.fd, "", 1, MSG_NOSIGNAL, &sa.sa, salen) == -1) { + logger(mesh, MESHLINK_ERROR, "Could not send a UDP packet to ourself: %s", sockstrerror(sockerrno)); + } + } + + if(mesh->threadstarted) { + // Wait for the main thread to finish + pthread_mutex_unlock(&mesh->mutex); + + if(pthread_join(mesh->thread, NULL) != 0) { + abort(); + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + mesh->threadstarted = false; + } + + // Close all metaconnections + if(mesh->connections) { + for(list_node_t *node = mesh->connections->head, *next; node; node = next) { + next = node->next; + connection_t *c = node->data; + c->outgoing = NULL; + terminate_connection(mesh, c, false); + } + } + + exit_adns(mesh); + exit_outgoings(mesh); + + // Ensure we are considered unreachable + if(mesh->nodes) { + graph(mesh); + } + + // Try to write out any changed node config files, ignore errors at this point. + if(mesh->nodes) { + for splay_each(node_t, n, mesh->nodes) { + if(n->status.dirty) { + if(!node_write_config(mesh, n, false)) { + // ignore + } + } + } + } + + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_close(meshlink_handle_t *mesh) { + logger(mesh, MESHLINK_DEBUG, "meshlink_close()\n"); + + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + // stop can be called even if mesh has not been started + meshlink_stop(mesh); + + // lock is not released after this + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + // Close and free all resources used. + + close_network_connections(mesh); + + logger(mesh, MESHLINK_INFO, "Terminating"); + + event_loop_exit(&mesh->loop); + +#ifdef HAVE_MINGW + + if(mesh->confbase) { + WSACleanup(); + } + +#endif + + ecdsa_free(mesh->invitation_key); + + if(mesh->netns != -1) { + close(mesh->netns); + } + + for(vpn_packet_t *packet; (packet = meshlink_queue_pop(&mesh->outpacketqueue));) { + free(packet); + } + + meshlink_queue_exit(&mesh->outpacketqueue); + + free(mesh->name); + free(mesh->appname); + free(mesh->confbase); + free(mesh->config_key); + free(mesh->external_address_url); + free(mesh->packet); + ecdsa_free(mesh->private_key); + + if(mesh->invitation_addresses) { + list_delete_list(mesh->invitation_addresses); + } + + main_config_unlock(mesh); + + pthread_mutex_unlock(&mesh->mutex); + pthread_mutex_destroy(&mesh->mutex); + + memset(mesh, 0, sizeof(*mesh)); + + free(mesh); +} + +bool meshlink_destroy_ex(const meshlink_open_params_t *params) { + logger(NULL, MESHLINK_DEBUG, "meshlink_destroy_ex()\n"); + + if(!params) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(!params->confbase) { + /* Ephemeral instances */ + return true; + } + + /* Exit early if the confbase directory itself doesn't exist */ + if(access(params->confbase, F_OK) && errno == ENOENT) { + return true; + } + + /* Take the lock the same way meshlink_open() would. */ + FILE *lockfile = fopen(params->lock_filename, "w+"); + + if(!lockfile) { + logger(NULL, MESHLINK_ERROR, "Could not open lock file %s: %s", params->lock_filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + +#ifdef FD_CLOEXEC + fcntl(fileno(lockfile), F_SETFD, FD_CLOEXEC); +#endif + +#ifdef HAVE_MINGW + // TODO: use _locking()? +#else + + if(flock(fileno(lockfile), LOCK_EX | LOCK_NB) != 0) { + logger(NULL, MESHLINK_ERROR, "Configuration directory %s still in use\n", params->lock_filename); + fclose(lockfile); + meshlink_errno = MESHLINK_EBUSY; + return false; + } + +#endif + + if(!config_destroy(params->confbase, "current") || !config_destroy(params->confbase, "new") || !config_destroy(params->confbase, "old")) { + logger(NULL, MESHLINK_ERROR, "Cannot remove sub-directories in %s: %s\n", params->confbase, strerror(errno)); + return false; + } + + if(unlink(params->lock_filename)) { + logger(NULL, MESHLINK_ERROR, "Cannot remove lock file %s: %s\n", params->lock_filename, strerror(errno)); + fclose(lockfile); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + fclose(lockfile); + + if(!sync_path(params->confbase)) { + logger(NULL, MESHLINK_ERROR, "Cannot sync directory %s: %s\n", params->confbase, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; + } + + return true; +} + +bool meshlink_destroy(const char *confbase) { + logger(NULL, MESHLINK_DEBUG, "meshlink_destroy(%s)", confbase); + + char lock_filename[PATH_MAX]; + snprintf(lock_filename, sizeof(lock_filename), "%s" SLASH "meshlink.lock", confbase); + + meshlink_open_params_t params = { + .confbase = (char *)confbase, + .lock_filename = lock_filename, + }; + + return meshlink_destroy_ex(¶ms); +} + +void meshlink_set_receive_cb(meshlink_handle_t *mesh, meshlink_receive_cb_t cb) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_receive_cb(%p)", (void *)(intptr_t)cb); + + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + mesh->receive_cb = cb; + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_set_connection_try_cb(meshlink_handle_t *mesh, meshlink_connection_try_cb_t cb) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_connection_try_cb(%p)", (void *)(intptr_t)cb); + + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + mesh->connection_try_cb = cb; + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_set_node_status_cb(meshlink_handle_t *mesh, meshlink_node_status_cb_t cb) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_node_status_cb(%p)", (void *)(intptr_t)cb); + + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + mesh->node_status_cb = cb; + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_set_node_pmtu_cb(meshlink_handle_t *mesh, meshlink_node_pmtu_cb_t cb) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_node_pmtu_cb(%p)", (void *)(intptr_t)cb); + + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + mesh->node_pmtu_cb = cb; + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_set_node_duplicate_cb(meshlink_handle_t *mesh, meshlink_node_duplicate_cb_t cb) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_node_duplicate_cb(%p)", (void *)(intptr_t)cb); + + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + mesh->node_duplicate_cb = cb; + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_set_log_cb(meshlink_handle_t *mesh, meshlink_log_level_t level, meshlink_log_cb_t cb) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_log_cb(%p)", (void *)(intptr_t)cb); + + if(mesh) { + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + mesh->log_cb = cb; + mesh->log_level = cb ? level : 0; + pthread_mutex_unlock(&mesh->mutex); + } else { + global_log_cb = cb; + global_log_level = cb ? level : 0; + } +} + +void meshlink_set_error_cb(struct meshlink_handle *mesh, meshlink_error_cb_t cb) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_error_cb(%p)", (void *)(intptr_t)cb); + + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + mesh->error_cb = cb; + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_set_blacklisted_cb(struct meshlink_handle *mesh, meshlink_blacklisted_cb_t cb) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_blacklisted_cb(%p)", (void *)(intptr_t)cb); + + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + mesh->blacklisted_cb = cb; + pthread_mutex_unlock(&mesh->mutex); +} + +static bool prepare_packet(meshlink_handle_t *mesh, meshlink_node_t *destination, const void *data, size_t len, vpn_packet_t *packet) { + meshlink_packethdr_t *hdr; + + if(len > MAXSIZE - sizeof(*hdr)) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + node_t *n = (node_t *)destination; + + if(n->status.blacklisted) { + logger(mesh, MESHLINK_ERROR, "Node %s blacklisted, dropping packet\n", n->name); + meshlink_errno = MESHLINK_EBLACKLISTED; + return false; + } + + // Prepare the packet + packet->probe = false; + packet->tcp = false; + packet->len = len + sizeof(*hdr); + + hdr = (meshlink_packethdr_t *)packet->data; + memset(hdr, 0, sizeof(*hdr)); + // leave the last byte as 0 to make sure strings are always + // null-terminated if they are longer than the buffer + strncpy((char *)hdr->destination, destination->name, sizeof(hdr->destination) - 1); + strncpy((char *)hdr->source, mesh->self->name, sizeof(hdr->source) - 1); + + memcpy(packet->data + sizeof(*hdr), data, len); + + return true; +} + +static bool meshlink_send_immediate(meshlink_handle_t *mesh, meshlink_node_t *destination, const void *data, size_t len) { + assert(mesh); + assert(destination); + assert(data); + assert(len); + + // Prepare the packet + if(!prepare_packet(mesh, destination, data, len, mesh->packet)) { + return false; + } + + // Send it immediately + route(mesh, mesh->self, mesh->packet); + + return true; +} + +bool meshlink_send(meshlink_handle_t *mesh, meshlink_node_t *destination, const void *data, size_t len) { + logger(mesh, MESHLINK_DEBUG, "meshlink_send(%s, %p, %zu)", destination ? destination->name : "(null)", data, len); + + // Validate arguments + if(!mesh || !destination) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(!len) { + return true; + } + + if(!data) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + // Prepare the packet + vpn_packet_t *packet = malloc(sizeof(*packet)); + + if(!packet) { + meshlink_errno = MESHLINK_ENOMEM; + return false; + } + + if(!prepare_packet(mesh, destination, data, len, packet)) { + free(packet); + return false; + } + + // Queue it + if(!meshlink_queue_push(&mesh->outpacketqueue, packet)) { + free(packet); + meshlink_errno = MESHLINK_ENOMEM; + return false; + } + + logger(mesh, MESHLINK_DEBUG, "Adding packet of %zu bytes to packet queue", len); + + // Notify event loop + signal_trigger(&mesh->loop, &mesh->datafromapp); + + return true; +} + +void meshlink_send_from_queue(event_loop_t *loop, void *data) { + (void)loop; + meshlink_handle_t *mesh = data; + + logger(mesh, MESHLINK_DEBUG, "Flushing the packet queue"); + + for(vpn_packet_t *packet; (packet = meshlink_queue_pop(&mesh->outpacketqueue));) { + logger(mesh, MESHLINK_DEBUG, "Removing packet of %d bytes from packet queue", packet->len); + mesh->self->in_packets++; + mesh->self->in_bytes += packet->len; + route(mesh, mesh->self, packet); + free(packet); + } +} + +ssize_t meshlink_get_pmtu(meshlink_handle_t *mesh, meshlink_node_t *destination) { + if(!mesh || !destination) { + meshlink_errno = MESHLINK_EINVAL; + return -1; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + node_t *n = (node_t *)destination; + + if(!n->status.reachable) { + pthread_mutex_unlock(&mesh->mutex); + return 0; + + } else if(n->mtuprobes > 30 && n->minmtu) { + pthread_mutex_unlock(&mesh->mutex); + return n->minmtu; + } else { + pthread_mutex_unlock(&mesh->mutex); + return MTU; + } +} + +char *meshlink_get_fingerprint(meshlink_handle_t *mesh, meshlink_node_t *node) { + if(!mesh || !node) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + node_t *n = (node_t *)node; + + if(!node_read_public_key(mesh, n) || !n->ecdsa) { + meshlink_errno = MESHLINK_EINTERNAL; + pthread_mutex_unlock(&mesh->mutex); + return false; + } + + char *fingerprint = ecdsa_get_base64_public_key(n->ecdsa); + + if(!fingerprint) { + meshlink_errno = MESHLINK_EINTERNAL; + } + + pthread_mutex_unlock(&mesh->mutex); + return fingerprint; +} + +meshlink_node_t *meshlink_get_self(meshlink_handle_t *mesh) { + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + return (meshlink_node_t *)mesh->self; +} + +meshlink_node_t *meshlink_get_node(meshlink_handle_t *mesh, const char *name) { + if(!mesh || !name) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + node_t *n = NULL; + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + n = lookup_node(mesh, (char *)name); // TODO: make lookup_node() use const + pthread_mutex_unlock(&mesh->mutex); + + if(!n) { + meshlink_errno = MESHLINK_ENOENT; + } + + return (meshlink_node_t *)n; +} + +meshlink_submesh_t *meshlink_get_submesh(meshlink_handle_t *mesh, const char *name) { + if(!mesh || !name) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + meshlink_submesh_t *submesh = NULL; + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + submesh = (meshlink_submesh_t *)lookup_submesh(mesh, name); + pthread_mutex_unlock(&mesh->mutex); + + if(!submesh) { + meshlink_errno = MESHLINK_ENOENT; + } + + return submesh; +} + +meshlink_node_t **meshlink_get_all_nodes(meshlink_handle_t *mesh, meshlink_node_t **nodes, size_t *nmemb) { + if(!mesh || !nmemb || (*nmemb && !nodes)) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + meshlink_node_t **result; + + //lock mesh->nodes + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + *nmemb = mesh->nodes->count; + result = realloc(nodes, *nmemb * sizeof(*nodes)); + + if(result) { + meshlink_node_t **p = result; + + for splay_each(node_t, n, mesh->nodes) { + *p++ = (meshlink_node_t *)n; + } + } else { + *nmemb = 0; + free(nodes); + meshlink_errno = MESHLINK_ENOMEM; + } + + pthread_mutex_unlock(&mesh->mutex); + + return result; +} + +static meshlink_node_t **meshlink_get_all_nodes_by_condition(meshlink_handle_t *mesh, const void *condition, meshlink_node_t **nodes, size_t *nmemb, search_node_by_condition_t search_node) { + meshlink_node_t **result; + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + *nmemb = 0; + + for splay_each(node_t, n, mesh->nodes) { + if(search_node(n, condition)) { + ++*nmemb; + } + } + + if(*nmemb == 0) { + free(nodes); + pthread_mutex_unlock(&mesh->mutex); + return NULL; + } + + result = realloc(nodes, *nmemb * sizeof(*nodes)); + + if(result) { + meshlink_node_t **p = result; + + for splay_each(node_t, n, mesh->nodes) { + if(search_node(n, condition)) { + *p++ = (meshlink_node_t *)n; + } + } + } else { + *nmemb = 0; + free(nodes); + meshlink_errno = MESHLINK_ENOMEM; + } + + pthread_mutex_unlock(&mesh->mutex); + + return result; +} + +static bool search_node_by_dev_class(const node_t *node, const void *condition) { + dev_class_t *devclass = (dev_class_t *)condition; + + if(*devclass == (dev_class_t)node->devclass) { + return true; + } + + return false; +} + +static bool search_node_by_blacklisted(const node_t *node, const void *condition) { + return *(bool *)condition == node->status.blacklisted; +} + +static bool search_node_by_submesh(const node_t *node, const void *condition) { + if(condition == node->submesh) { + return true; + } + + return false; +} + +struct time_range { + time_t start; + time_t end; +}; + +static bool search_node_by_last_reachable(const node_t *node, const void *condition) { + const struct time_range *range = condition; + time_t start = node->last_reachable; + time_t end = node->last_unreachable; + + if(end < start) { + end = time(NULL); + + if(end < start) { + start = end; + } + } + + if(range->end >= range->start) { + return start <= range->end && end >= range->start; + } else { + return start > range->start || end < range->end; + } +} + +meshlink_node_t **meshlink_get_all_nodes_by_dev_class(meshlink_handle_t *mesh, dev_class_t devclass, meshlink_node_t **nodes, size_t *nmemb) { + if(!mesh || devclass < 0 || devclass >= DEV_CLASS_COUNT || !nmemb) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + return meshlink_get_all_nodes_by_condition(mesh, &devclass, nodes, nmemb, search_node_by_dev_class); +} + +meshlink_node_t **meshlink_get_all_nodes_by_submesh(meshlink_handle_t *mesh, meshlink_submesh_t *submesh, meshlink_node_t **nodes, size_t *nmemb) { + if(!mesh || !submesh || !nmemb) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + return meshlink_get_all_nodes_by_condition(mesh, submesh, nodes, nmemb, search_node_by_submesh); +} + +meshlink_node_t **meshlink_get_all_nodes_by_last_reachable(meshlink_handle_t *mesh, time_t start, time_t end, meshlink_node_t **nodes, size_t *nmemb) { + if(!mesh || !nmemb) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + struct time_range range = {start, end}; + + return meshlink_get_all_nodes_by_condition(mesh, &range, nodes, nmemb, search_node_by_last_reachable); +} + +meshlink_node_t **meshlink_get_all_nodes_by_blacklisted(meshlink_handle_t *mesh, bool blacklisted, meshlink_node_t **nodes, size_t *nmemb) { + if(!mesh || !nmemb) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + return meshlink_get_all_nodes_by_condition(mesh, &blacklisted, nodes, nmemb, search_node_by_blacklisted); +} + +dev_class_t meshlink_get_node_dev_class(meshlink_handle_t *mesh, meshlink_node_t *node) { + if(!mesh || !node) { + meshlink_errno = MESHLINK_EINVAL; + return -1; + } + + dev_class_t devclass; + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + devclass = ((node_t *)node)->devclass; + + pthread_mutex_unlock(&mesh->mutex); + + return devclass; +} + +bool meshlink_get_node_blacklisted(meshlink_handle_t *mesh, meshlink_node_t *node) { + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + } + + if(!node) { + return mesh->default_blacklist; + } + + bool blacklisted; + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + blacklisted = ((node_t *)node)->status.blacklisted; + + pthread_mutex_unlock(&mesh->mutex); + + return blacklisted; +} + +meshlink_submesh_t *meshlink_get_node_submesh(meshlink_handle_t *mesh, meshlink_node_t *node) { + if(!mesh || !node) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + node_t *n = (node_t *)node; + + meshlink_submesh_t *s; + + s = (meshlink_submesh_t *)n->submesh; + + return s; +} + +bool meshlink_get_node_reachability(struct meshlink_handle *mesh, struct meshlink_node *node, time_t *last_reachable, time_t *last_unreachable) { + if(!mesh || !node) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + node_t *n = (node_t *)node; + bool reachable; + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + reachable = n->status.reachable && !n->status.blacklisted; + + if(last_reachable) { + *last_reachable = n->last_reachable; + } + + if(last_unreachable) { + *last_unreachable = n->last_unreachable; + } + + pthread_mutex_unlock(&mesh->mutex); + + return reachable; +} + +bool meshlink_sign(meshlink_handle_t *mesh, const void *data, size_t len, void *signature, size_t *siglen) { + logger(mesh, MESHLINK_DEBUG, "meshlink_sign(%p, %zu, %p, %p)", data, len, signature, (void *)siglen); + + if(!mesh || !data || !len || !signature || !siglen) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(*siglen < MESHLINK_SIGLEN) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + if(!ecdsa_sign(mesh->private_key, data, len, signature)) { + meshlink_errno = MESHLINK_EINTERNAL; + pthread_mutex_unlock(&mesh->mutex); + return false; + } + + *siglen = MESHLINK_SIGLEN; + pthread_mutex_unlock(&mesh->mutex); + return true; +} + +bool meshlink_verify(meshlink_handle_t *mesh, meshlink_node_t *source, const void *data, size_t len, const void *signature, size_t siglen) { + logger(mesh, MESHLINK_DEBUG, "meshlink_verify(%p, %zu, %p, %zu)", data, len, signature, siglen); + + if(!mesh || !source || !data || !len || !signature) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(siglen != MESHLINK_SIGLEN) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + bool rval = false; + + struct node_t *n = (struct node_t *)source; + + if(!node_read_public_key(mesh, n)) { + meshlink_errno = MESHLINK_EINTERNAL; + rval = false; + } else { + rval = ecdsa_verify(((struct node_t *)source)->ecdsa, data, len, signature); + } + + pthread_mutex_unlock(&mesh->mutex); + return rval; +} + +static bool refresh_invitation_key(meshlink_handle_t *mesh) { + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + size_t count = invitation_purge_old(mesh, time(NULL) - mesh->invitation_timeout); + + if(!count) { + // TODO: Update invitation key if necessary? + } + + pthread_mutex_unlock(&mesh->mutex); + + return mesh->invitation_key; +} + +bool meshlink_set_canonical_address(meshlink_handle_t *mesh, meshlink_node_t *node, const char *address, const char *port) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_canonical_address(%s, %s, %s)", node ? node->name : "(null)", address ? address : "(null)", port ? port : "(null)"); + + if(!mesh || !node || !address) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(!is_valid_hostname(address)) { + logger(mesh, MESHLINK_ERROR, "Invalid character in address: %s", address); + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if((node_t *)node != mesh->self && !port) { + logger(mesh, MESHLINK_ERROR, "Missing port number!"); + meshlink_errno = MESHLINK_EINVAL; + return false; + + } + + if(port && !is_valid_port(port)) { + logger(mesh, MESHLINK_ERROR, "Invalid character in port: %s", address); + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + char *canonical_address; + + xasprintf(&canonical_address, "%s %s", address, port ? port : mesh->myport); + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + node_t *n = (node_t *)node; + free(n->canonical_address); + n->canonical_address = canonical_address; + + if(!node_write_config(mesh, n, false)) { + pthread_mutex_unlock(&mesh->mutex); + return false; + } + + pthread_mutex_unlock(&mesh->mutex); + + return config_sync(mesh, "current"); +} + +bool meshlink_clear_canonical_address(meshlink_handle_t *mesh, meshlink_node_t *node) { + logger(mesh, MESHLINK_DEBUG, "meshlink_clear_canonical_address(%s)", node ? node->name : "(null)"); + + if(!mesh || !node) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + node_t *n = (node_t *)node; + free(n->canonical_address); + n->canonical_address = NULL; + + if(!node_write_config(mesh, n, false)) { + pthread_mutex_unlock(&mesh->mutex); + return false; + } + + pthread_mutex_unlock(&mesh->mutex); + + return config_sync(mesh, "current"); +} + +bool meshlink_add_invitation_address(struct meshlink_handle *mesh, const char *address, const char *port) { + logger(mesh, MESHLINK_DEBUG, "meshlink_add_invitation_address(%s, %s)", address ? address : "(null)", port ? port : "(null)"); + + if(!mesh || !address) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(!is_valid_hostname(address)) { + logger(mesh, MESHLINK_ERROR, "Invalid character in address: %s\n", address); + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(port && !is_valid_port(port)) { + logger(mesh, MESHLINK_ERROR, "Invalid character in port: %s\n", address); + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + char *combo; + + if(port) { + xasprintf(&combo, "%s/%s", address, port); + } else { + combo = xstrdup(address); + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + if(!mesh->invitation_addresses) { + mesh->invitation_addresses = list_alloc((list_action_t)free); + } + + list_insert_tail(mesh->invitation_addresses, combo); + pthread_mutex_unlock(&mesh->mutex); + + return true; +} + +void meshlink_clear_invitation_addresses(struct meshlink_handle *mesh) { + logger(mesh, MESHLINK_DEBUG, "meshlink_clear_invitation_addresses()"); + + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + if(mesh->invitation_addresses) { + list_delete_list(mesh->invitation_addresses); + mesh->invitation_addresses = NULL; + } + + pthread_mutex_unlock(&mesh->mutex); +} + +bool meshlink_add_address(meshlink_handle_t *mesh, const char *address) { + logger(mesh, MESHLINK_DEBUG, "meshlink_add_address(%s)", address ? address : "(null)"); + + return meshlink_set_canonical_address(mesh, (meshlink_node_t *)mesh->self, address, NULL); +} + +bool meshlink_add_external_address(meshlink_handle_t *mesh) { + logger(mesh, MESHLINK_DEBUG, "meshlink_add_external_address()"); + + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + char *address = meshlink_get_external_address(mesh); + + if(!address) { + return false; + } + + bool rval = meshlink_set_canonical_address(mesh, (meshlink_node_t *)mesh->self, address, NULL); + free(address); + + return rval; +} + +int meshlink_get_port(meshlink_handle_t *mesh) { + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return -1; + } + + if(!mesh->myport) { + meshlink_errno = MESHLINK_EINTERNAL; + return -1; + } + + int port; + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + port = atoi(mesh->myport); + pthread_mutex_unlock(&mesh->mutex); + + return port; +} + +bool meshlink_set_port(meshlink_handle_t *mesh, int port) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_port(%d)", port); + + if(!mesh || port < 0 || port >= 65536 || mesh->threadstarted) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(mesh->myport && port == atoi(mesh->myport)) { + return true; + } + + if(!try_bind(mesh, port)) { + meshlink_errno = MESHLINK_ENETWORK; + return false; + } + + devtool_trybind_probe(); + + bool rval = false; + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + if(mesh->threadstarted) { + meshlink_errno = MESHLINK_EINVAL; + goto done; + } + + free(mesh->myport); + xasprintf(&mesh->myport, "%d", port); + + /* Close down the network. This also deletes mesh->self. */ + close_network_connections(mesh); + + /* Recreate mesh->self. */ + mesh->self = new_node(); + mesh->self->name = xstrdup(mesh->name); + mesh->self->devclass = mesh->devclass; + mesh->self->session_id = mesh->session_id; + xasprintf(&mesh->myport, "%d", port); + + if(!node_read_public_key(mesh, mesh->self)) { + logger(NULL, MESHLINK_ERROR, "Could not read our host configuration file!"); + meshlink_errno = MESHLINK_ESTORAGE; + free_node(mesh->self); + mesh->self = NULL; + goto done; + } else if(!setup_network(mesh)) { + meshlink_errno = MESHLINK_ENETWORK; + goto done; + } + + /* Rebuild our own list of recent addresses */ + memset(mesh->self->recent, 0, sizeof(mesh->self->recent)); + add_local_addresses(mesh); + + /* Write meshlink.conf with the updated port number */ + write_main_config_files(mesh); + + rval = config_sync(mesh, "current"); + +done: + pthread_mutex_unlock(&mesh->mutex); + + return rval && meshlink_get_port(mesh) == port; +} + +void meshlink_set_invitation_timeout(meshlink_handle_t *mesh, int timeout) { + logger(mesh, MESHLINK_DEBUG, "meshlink_invitation_timeout(%d)", timeout); + + mesh->invitation_timeout = timeout; +} + +char *meshlink_invite_ex(meshlink_handle_t *mesh, meshlink_submesh_t *submesh, const char *name, uint32_t flags) { + logger(mesh, MESHLINK_DEBUG, "meshlink_invite_ex(%s, %s, %u)", submesh ? submesh->name : "(null)", name ? name : "(null)", flags); + + meshlink_submesh_t *s = NULL; + + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(submesh) { + s = (meshlink_submesh_t *)lookup_submesh(mesh, submesh->name); + + if(s != submesh) { + logger(mesh, MESHLINK_ERROR, "Invalid submesh handle.\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + } else { + s = (meshlink_submesh_t *)mesh->self->submesh; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + // Check validity of the new node's name + if(!check_id(name)) { + logger(mesh, MESHLINK_ERROR, "Invalid name for node.\n"); + meshlink_errno = MESHLINK_EINVAL; + pthread_mutex_unlock(&mesh->mutex); + return NULL; + } + + // Ensure no host configuration file with that name exists + if(config_exists(mesh, "current", name)) { + logger(mesh, MESHLINK_ERROR, "A host config file for %s already exists!\n", name); + meshlink_errno = MESHLINK_EEXIST; + pthread_mutex_unlock(&mesh->mutex); + return NULL; + } + + // Ensure no other nodes know about this name + if(lookup_node(mesh, name)) { + logger(mesh, MESHLINK_ERROR, "A node with name %s is already known!\n", name); + meshlink_errno = MESHLINK_EEXIST; + pthread_mutex_unlock(&mesh->mutex); + return NULL; + } + + // Get the local address + char *address = get_my_hostname(mesh, flags); + + if(!address) { + logger(mesh, MESHLINK_ERROR, "No Address known for ourselves!\n"); + meshlink_errno = MESHLINK_ERESOLV; + pthread_mutex_unlock(&mesh->mutex); + return NULL; + } + + if(!refresh_invitation_key(mesh)) { + meshlink_errno = MESHLINK_EINTERNAL; + pthread_mutex_unlock(&mesh->mutex); + return NULL; + } + + // If we changed our own host config file, write it out now + if(mesh->self->status.dirty) { + if(!node_write_config(mesh, mesh->self, false)) { + logger(mesh, MESHLINK_ERROR, "Could not write our own host config file!\n"); + pthread_mutex_unlock(&mesh->mutex); + return NULL; + } + } + + char hash[64]; + + // Create a hash of the key. + char *fingerprint = ecdsa_get_base64_public_key(mesh->invitation_key); + sha512(fingerprint, strlen(fingerprint), hash); + b64encode_urlsafe(hash, hash, 18); + + // Create a random cookie for this invitation. + char cookie[25]; + randomize(cookie, 18); + + // Create a filename that doesn't reveal the cookie itself + char buf[18 + strlen(fingerprint)]; + char cookiehash[64]; + memcpy(buf, cookie, 18); + memcpy(buf + 18, fingerprint, sizeof(buf) - 18); + sha512(buf, sizeof(buf), cookiehash); + b64encode_urlsafe(cookiehash, cookiehash, 18); + + b64encode_urlsafe(cookie, cookie, 18); + + free(fingerprint); + + /* Construct the invitation file */ + uint8_t outbuf[4096]; + packmsg_output_t inv = {outbuf, sizeof(outbuf)}; + + packmsg_add_uint32(&inv, MESHLINK_INVITATION_VERSION); + packmsg_add_str(&inv, name); + packmsg_add_str(&inv, s ? s->name : CORE_MESH); + packmsg_add_int32(&inv, DEV_CLASS_UNKNOWN); /* TODO: allow this to be set by inviter? */ + + /* TODO: Add several host config files to bootstrap connections. + * Note: make sure we only add config files of nodes that are in the core mesh or the same submesh, + * and are not blacklisted. + */ + config_t configs[5]; + memset(configs, 0, sizeof(configs)); + int count = 0; + + if(config_read(mesh, "current", mesh->self->name, &configs[count], mesh->config_key)) { + count++; + } + + /* Append host config files to the invitation file */ + packmsg_add_array(&inv, count); + + for(int i = 0; i < count; i++) { + packmsg_add_bin(&inv, configs[i].buf, configs[i].len); + config_free(&configs[i]); + } + + config_t config = {outbuf, packmsg_output_size(&inv, outbuf)}; + + if(!invitation_write(mesh, "current", cookiehash, &config, mesh->config_key)) { + logger(mesh, MESHLINK_DEBUG, "Could not create invitation file %s: %s\n", cookiehash, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + pthread_mutex_unlock(&mesh->mutex); + return NULL; + } + + // Create an URL from the local address, key hash and cookie + char *url; + xasprintf(&url, "%s/%s%s", address, hash, cookie); + free(address); + + pthread_mutex_unlock(&mesh->mutex); + return url; +} + +char *meshlink_invite(meshlink_handle_t *mesh, meshlink_submesh_t *submesh, const char *name) { + logger(mesh, MESHLINK_DEBUG, "meshlink_invite_ex(%s, %s)", submesh ? submesh->name : "(null)", name ? name : "(null)"); + + return meshlink_invite_ex(mesh, submesh, name, 0); +} + +bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) { + logger(mesh, MESHLINK_DEBUG, "meshlink_join(%s)", invitation ? invitation : "(null)"); + + if(!mesh || !invitation) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(mesh->storage_policy == MESHLINK_STORAGE_DISABLED) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + join_state_t state = { + .mesh = mesh, + .sock = -1, + }; + + ecdsa_t *key = NULL; + ecdsa_t *hiskey = NULL; + + //TODO: think of a better name for this variable, or of a different way to tokenize the invitation URL. + char copy[strlen(invitation) + 1]; + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + //Before doing meshlink_join make sure we are not connected to another mesh + if(mesh->threadstarted) { + logger(mesh, MESHLINK_ERROR, "Cannot join while started\n"); + meshlink_errno = MESHLINK_EINVAL; + goto exit; + } + + // Refuse to join a mesh if we are already part of one. We are part of one if we know at least one other node. + if(mesh->nodes->count > 1) { + logger(mesh, MESHLINK_ERROR, "Already part of an existing mesh\n"); + meshlink_errno = MESHLINK_EINVAL; + goto exit; + } + + strcpy(copy, invitation); + + // Split the invitation URL into a list of hostname/port tuples, a key hash and a cookie. + + char *slash = strchr(copy, '/'); + + if(!slash) { + goto invalid; + } + + *slash++ = 0; + + if(strlen(slash) != 48) { + goto invalid; + } + + char *address = copy; + char *port = NULL; + + if(!b64decode(slash, state.hash, 18) || !b64decode(slash + 24, state.cookie, 18)) { + goto invalid; + } + + if(mesh->inviter_commits_first) { + memcpy(state.cookie + 18, ecdsa_get_public_key(mesh->private_key), 32); + } + + // Generate a throw-away key for the invitation. + key = ecdsa_generate(); + + if(!key) { + meshlink_errno = MESHLINK_EINTERNAL; + goto exit; + } + + char *b64key = ecdsa_get_base64_public_key(key); + char *comma; + + while(address && *address) { + // We allow commas in the address part to support multiple addresses in one invitation URL. + comma = strchr(address, ','); + + if(comma) { + *comma++ = 0; + } + + // Split of the port + port = strrchr(address, ':'); + + if(!port) { + goto invalid; + } + + *port++ = 0; + + // IPv6 address are enclosed in brackets, per RFC 3986 + if(*address == '[') { + address++; + char *bracket = strchr(address, ']'); + + if(!bracket) { + goto invalid; + } + + *bracket++ = 0; + + if(*bracket) { + goto invalid; + } + } + + // Connect to the meshlink daemon mentioned in the URL. + struct addrinfo *ai = adns_blocking_request(mesh, xstrdup(address), xstrdup(port), SOCK_STREAM, 30); + + if(ai) { + for(struct addrinfo *aip = ai; aip; aip = aip->ai_next) { + state.sock = socket_in_netns(aip->ai_family, SOCK_STREAM, IPPROTO_TCP, mesh->netns); + + if(state.sock == -1) { + logger(mesh, MESHLINK_DEBUG, "Could not open socket: %s\n", strerror(errno)); + meshlink_errno = MESHLINK_ENETWORK; + continue; + } + +#ifdef SO_NOSIGPIPE + int nosigpipe = 1; + setsockopt(state.sock, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); +#endif + + set_timeout(state.sock, 5000); + + if(connect(state.sock, aip->ai_addr, aip->ai_addrlen)) { + logger(mesh, MESHLINK_DEBUG, "Could not connect to %s port %s: %s\n", address, port, strerror(errno)); + meshlink_errno = MESHLINK_ENETWORK; + closesocket(state.sock); + state.sock = -1; + continue; + } + + break; + } + + freeaddrinfo(ai); + } else { + meshlink_errno = MESHLINK_ERESOLV; + } + + if(state.sock != -1 || !comma) { + break; + } + + address = comma; + } + + if(state.sock == -1) { + goto exit; + } + + logger(mesh, MESHLINK_DEBUG, "Connected to %s port %s...\n", address, port); + + // Tell him we have an invitation, and give him our throw-away key. + + state.blen = 0; + + if(!sendline(state.sock, "0 ?%s %d.%d %s", b64key, PROT_MAJOR, PROT_MINOR, mesh->appname)) { + logger(mesh, MESHLINK_ERROR, "Error sending request to %s port %s: %s\n", address, port, strerror(errno)); + meshlink_errno = MESHLINK_ENETWORK; + goto exit; + } + + free(b64key); + + char hisname[4096] = ""; + int code, hismajor, hisminor = 0; + + if(!recvline(&state) || sscanf(state.line, "%d %s %d.%d", &code, hisname, &hismajor, &hisminor) < 3 || code != 0 || hismajor != PROT_MAJOR || !check_id(hisname) || !recvline(&state) || !rstrip(state.line) || sscanf(state.line, "%d ", &code) != 1 || code != ACK || strlen(state.line) < 3) { + logger(mesh, MESHLINK_ERROR, "Cannot read greeting from peer\n"); + meshlink_errno = MESHLINK_ENETWORK; + goto exit; + } + + // Check if the hash of the key he gave us matches the hash in the URL. + char *fingerprint = state.line + 2; + char hishash[64]; + + if(sha512(fingerprint, strlen(fingerprint), hishash)) { + logger(mesh, MESHLINK_ERROR, "Could not create hash\n%s\n", state.line + 2); + meshlink_errno = MESHLINK_EINTERNAL; + goto exit; + } + + if(memcmp(hishash, state.hash, 18)) { + logger(mesh, MESHLINK_ERROR, "Peer has an invalid key!\n%s\n", state.line + 2); + meshlink_errno = MESHLINK_EPEER; + goto exit; + } + + hiskey = ecdsa_set_base64_public_key(fingerprint); + + if(!hiskey) { + meshlink_errno = MESHLINK_EINTERNAL; + goto exit; + } + + // Start an SPTPS session + if(!sptps_start(&state.sptps, &state, true, false, key, hiskey, meshlink_invitation_label, sizeof(meshlink_invitation_label), invitation_send, invitation_receive)) { + meshlink_errno = MESHLINK_EINTERNAL; + goto exit; + } + + // Feed rest of input buffer to SPTPS + if(!sptps_receive_data(&state.sptps, state.buffer, state.blen)) { + meshlink_errno = MESHLINK_EPEER; + goto exit; + } + + ssize_t len; + logger(mesh, MESHLINK_DEBUG, "Starting invitation recv loop: %d %zu\n", state.sock, sizeof(state.line)); + + while((len = recv(state.sock, state.line, sizeof(state.line), 0))) { + if(len < 0) { + if(errno == EINTR) { + continue; + } + + logger(mesh, MESHLINK_ERROR, "Error reading data from %s port %s: %s\n", address, port, strerror(errno)); + meshlink_errno = MESHLINK_ENETWORK; + goto exit; + } + + if(!sptps_receive_data(&state.sptps, state.line, len)) { + meshlink_errno = MESHLINK_EPEER; + goto exit; + } + } + + if(!state.success) { + logger(mesh, MESHLINK_ERROR, "Connection closed by peer, invitation cancelled.\n"); + meshlink_errno = MESHLINK_EPEER; + goto exit; + } + + sptps_stop(&state.sptps); + ecdsa_free(hiskey); + ecdsa_free(key); + closesocket(state.sock); + + pthread_mutex_unlock(&mesh->mutex); + return true; + +invalid: + logger(mesh, MESHLINK_ERROR, "Invalid invitation URL\n"); + meshlink_errno = MESHLINK_EINVAL; +exit: + sptps_stop(&state.sptps); + ecdsa_free(hiskey); + ecdsa_free(key); + + if(state.sock != -1) { + closesocket(state.sock); + } + + pthread_mutex_unlock(&mesh->mutex); + return false; +} + +char *meshlink_export(meshlink_handle_t *mesh) { + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + // Create a config file on the fly. + + uint8_t buf[4096]; + packmsg_output_t out = {buf, sizeof(buf)}; + packmsg_add_uint32(&out, MESHLINK_CONFIG_VERSION); + packmsg_add_str(&out, mesh->name); + packmsg_add_str(&out, CORE_MESH); + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + packmsg_add_int32(&out, mesh->self->devclass); + packmsg_add_bool(&out, mesh->self->status.blacklisted); + packmsg_add_bin(&out, ecdsa_get_public_key(mesh->private_key), 32); + + if(mesh->self->canonical_address && !strchr(mesh->self->canonical_address, ' ')) { + char *canonical_address = NULL; + xasprintf(&canonical_address, "%s %s", mesh->self->canonical_address, mesh->myport); + packmsg_add_str(&out, canonical_address); + free(canonical_address); + } else { + packmsg_add_str(&out, mesh->self->canonical_address ? mesh->self->canonical_address : ""); + } + + uint32_t count = 0; + + for(uint32_t i = 0; i < MAX_RECENT; i++) { + if(mesh->self->recent[i].sa.sa_family) { + count++; + } else { + break; + } + } + + packmsg_add_array(&out, count); + + for(uint32_t i = 0; i < count; i++) { + packmsg_add_sockaddr(&out, &mesh->self->recent[i]); + } + + packmsg_add_int64(&out, 0); + packmsg_add_int64(&out, 0); + + pthread_mutex_unlock(&mesh->mutex); + + if(!packmsg_output_ok(&out)) { + logger(mesh, MESHLINK_ERROR, "Error creating export data\n"); + meshlink_errno = MESHLINK_EINTERNAL; + return NULL; + } + + // Prepare a base64-encoded packmsg array containing our config file + + uint32_t len = packmsg_output_size(&out, buf); + uint32_t len2 = ((len + 4) * 4) / 3 + 4; + uint8_t *buf2 = xmalloc(len2); + packmsg_output_t out2 = {buf2, len2}; + packmsg_add_array(&out2, 1); + packmsg_add_bin(&out2, buf, packmsg_output_size(&out, buf)); + + if(!packmsg_output_ok(&out2)) { + logger(mesh, MESHLINK_ERROR, "Error creating export data\n"); + meshlink_errno = MESHLINK_EINTERNAL; + free(buf2); + return NULL; + } + + b64encode_urlsafe(buf2, (char *)buf2, packmsg_output_size(&out2, buf2)); + + return (char *)buf2; +} + +bool meshlink_import(meshlink_handle_t *mesh, const char *data) { + logger(mesh, MESHLINK_DEBUG, "meshlink_import(%p)", (const void *)data); + + if(!mesh || !data) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + size_t datalen = strlen(data); + uint8_t *buf = xmalloc(datalen); + int buflen = b64decode(data, buf, datalen); + + if(!buflen) { + logger(mesh, MESHLINK_ERROR, "Invalid data\n"); + free(buf); + meshlink_errno = MESHLINK_EPEER; + return false; + } + + packmsg_input_t in = {buf, buflen}; + uint32_t count = packmsg_get_array(&in); + + if(!count) { + logger(mesh, MESHLINK_ERROR, "Invalid data\n"); + free(buf); + meshlink_errno = MESHLINK_EPEER; + return false; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + while(count--) { + const void *data2; + uint32_t len2 = packmsg_get_bin_raw(&in, &data2); + + if(!len2) { + break; + } + + packmsg_input_t in2 = {data2, len2}; + uint32_t version = packmsg_get_uint32(&in2); + char *name = packmsg_get_str_dup(&in2); + + if(!packmsg_input_ok(&in2) || version != MESHLINK_CONFIG_VERSION || !check_id(name)) { + free(name); + packmsg_input_invalidate(&in); + break; + } + + if(!check_id(name)) { + free(name); + break; + } + + node_t *n = lookup_node(mesh, name); + + if(n) { + logger(mesh, MESHLINK_DEBUG, "Node %s already exists, not importing\n", name); + free(name); + continue; + } + + n = new_node(); + n->name = name; + + config_t config = {data2, len2}; + + if(!node_read_from_config(mesh, n, &config)) { + free_node(n); + packmsg_input_invalidate(&in); + break; + } + + /* Clear the reachability times, since we ourself have never seen these nodes yet */ + n->last_reachable = 0; + n->last_unreachable = 0; + + if(!node_write_config(mesh, n, true)) { + free_node(n); + free(buf); + return false; + } + + node_add(mesh, n); + } + + pthread_mutex_unlock(&mesh->mutex); + + free(buf); + + if(!packmsg_done(&in)) { + logger(mesh, MESHLINK_ERROR, "Invalid data\n"); + meshlink_errno = MESHLINK_EPEER; + return false; + } + + if(!config_sync(mesh, "current")) { + return false; + } + + return true; +} + +static bool blacklist(meshlink_handle_t *mesh, node_t *n) { + if(n == mesh->self) { + logger(mesh, MESHLINK_ERROR, "%s blacklisting itself?\n", n->name); + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(n->status.blacklisted) { + logger(mesh, MESHLINK_DEBUG, "Node %s already blacklisted\n", n->name); + return true; + } + + n->status.blacklisted = true; + + /* Immediately shut down any connections we have with the blacklisted node. + * We can't call terminate_connection(), because we might be called from a callback function. + */ + for list_each(connection_t, c, mesh->connections) { + if(c->node == n) { + if(c->status.active) { + send_error(mesh, c, BLACKLISTED, "blacklisted"); + } + + shutdown(c->socket, SHUT_RDWR); + } + } + + utcp_reset_all_connections(n->utcp); + + n->mtu = 0; + n->minmtu = 0; + n->maxmtu = MTU; + n->mtuprobes = 0; + n->status.udp_confirmed = false; + + if(n->status.reachable) { + n->last_unreachable = time(NULL); + } + + /* Graph updates will suppress status updates for blacklisted nodes, so we need to + * manually call the status callback if necessary. + */ + if(n->status.reachable && mesh->node_status_cb) { + mesh->node_status_cb(mesh, (meshlink_node_t *)n, false); + } + + /* Remove any outstanding invitations */ + invitation_purge_node(mesh, n->name); + + return node_write_config(mesh, n, true) && config_sync(mesh, "current"); +} + +bool meshlink_blacklist(meshlink_handle_t *mesh, meshlink_node_t *node) { + logger(mesh, MESHLINK_DEBUG, "meshlink_blacklist(%s)", node ? node->name : "(null)"); + + if(!mesh || !node) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + if(!blacklist(mesh, (node_t *)node)) { + pthread_mutex_unlock(&mesh->mutex); + return false; + } + + pthread_mutex_unlock(&mesh->mutex); + + logger(mesh, MESHLINK_DEBUG, "Blacklisted %s.\n", node->name); + return true; +} + +bool meshlink_blacklist_by_name(meshlink_handle_t *mesh, const char *name) { + logger(mesh, MESHLINK_DEBUG, "meshlink_blacklist_by_name(%s)", name ? name : "(null)"); + + if(!mesh || !name) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + node_t *n = lookup_node(mesh, (char *)name); + + if(!n) { + n = new_node(); + n->name = xstrdup(name); + node_add(mesh, n); + } + + if(!blacklist(mesh, (node_t *)n)) { + pthread_mutex_unlock(&mesh->mutex); + return false; + } + + pthread_mutex_unlock(&mesh->mutex); + + logger(mesh, MESHLINK_DEBUG, "Blacklisted %s.\n", name); + return true; +} + +static bool whitelist(meshlink_handle_t *mesh, node_t *n) { + if(n == mesh->self) { + logger(mesh, MESHLINK_ERROR, "%s whitelisting itself?\n", n->name); + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(!n->status.blacklisted) { + logger(mesh, MESHLINK_DEBUG, "Node %s was already whitelisted\n", n->name); + return true; + } + + n->status.blacklisted = false; + + if(n->status.reachable) { + n->last_reachable = time(NULL); + update_node_status(mesh, n); + } + + return node_write_config(mesh, n, true) && config_sync(mesh, "current"); +} + +bool meshlink_whitelist(meshlink_handle_t *mesh, meshlink_node_t *node) { + logger(mesh, MESHLINK_DEBUG, "meshlink_whitelist(%s)", node ? node->name : "(null)"); + + if(!mesh || !node) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + if(!whitelist(mesh, (node_t *)node)) { + pthread_mutex_unlock(&mesh->mutex); + return false; + } + + pthread_mutex_unlock(&mesh->mutex); + + logger(mesh, MESHLINK_DEBUG, "Whitelisted %s.\n", node->name); + return true; +} + +bool meshlink_whitelist_by_name(meshlink_handle_t *mesh, const char *name) { + logger(mesh, MESHLINK_DEBUG, "meshlink_whitelist_by_name(%s)", name ? name : "(null)"); + + if(!mesh || !name) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + node_t *n = lookup_node(mesh, (char *)name); + + if(!n) { + n = new_node(); + n->name = xstrdup(name); + node_add(mesh, n); + } + + if(!whitelist(mesh, (node_t *)n)) { + pthread_mutex_unlock(&mesh->mutex); + return false; + } + + pthread_mutex_unlock(&mesh->mutex); + + logger(mesh, MESHLINK_DEBUG, "Whitelisted %s.\n", name); + return true; +} + +void meshlink_set_default_blacklist(meshlink_handle_t *mesh, bool blacklist) { + mesh->default_blacklist = blacklist; +} + +bool meshlink_forget_node(meshlink_handle_t *mesh, meshlink_node_t *node) { + logger(mesh, MESHLINK_DEBUG, "meshlink_forget_node(%s)", node ? node->name : "(null)"); + + if(!mesh || !node) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + node_t *n = (node_t *)node; + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + /* Check that the node is not reachable */ + if(n->status.reachable || n->connection) { + pthread_mutex_unlock(&mesh->mutex); + logger(mesh, MESHLINK_WARNING, "Could not forget %s: still reachable", n->name); + return false; + } + + /* Check that we don't have any active UTCP connections */ + if(n->utcp && utcp_is_active(n->utcp)) { + pthread_mutex_unlock(&mesh->mutex); + logger(mesh, MESHLINK_WARNING, "Could not forget %s: active UTCP connections", n->name); + return false; + } + + /* Check that we have no active connections to this node */ + for list_each(connection_t, c, mesh->connections) { + if(c->node == n) { + pthread_mutex_unlock(&mesh->mutex); + logger(mesh, MESHLINK_WARNING, "Could not forget %s: active connection", n->name); + return false; + } + } + + /* Remove any pending outgoings to this node */ + if(mesh->outgoings) { + for list_each(outgoing_t, outgoing, mesh->outgoings) { + if(outgoing->node == n) { + list_delete_node(mesh->outgoings, list_node); + } + } + } + + /* Delete the config file for this node */ + if(!config_delete(mesh, "current", n->name)) { + pthread_mutex_unlock(&mesh->mutex); + return false; + } + + /* Delete any pending invitations */ + invitation_purge_node(mesh, n->name); + + /* Delete the node struct and any remaining edges referencing this node */ + node_del(mesh, n); + + pthread_mutex_unlock(&mesh->mutex); + + return config_sync(mesh, "current"); +} + +/* Hint that a hostname may be found at an address + * See header file for detailed comment. + */ +void meshlink_hint_address(meshlink_handle_t *mesh, meshlink_node_t *node, const struct sockaddr *addr) { + logger(mesh, MESHLINK_DEBUG, "meshlink_hint_address(%s, %p)", node ? node->name : "(null)", (const void *)addr); + + if(!mesh || !node || !addr) { + meshlink_errno = EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + node_t *n = (node_t *)node; + + if(node_add_recent_address(mesh, n, (sockaddr_t *)addr)) { + if(!node_write_config(mesh, n, false)) { + logger(mesh, MESHLINK_DEBUG, "Could not update %s\n", n->name); + } + } + + pthread_mutex_unlock(&mesh->mutex); + // @TODO do we want to fire off a connection attempt right away? +} + +static bool channel_pre_accept(struct utcp *utcp, uint16_t port) { + (void)port; + node_t *n = utcp->priv; + meshlink_handle_t *mesh = n->mesh; + + if(mesh->channel_accept_cb && mesh->channel_listen_cb) { + return mesh->channel_listen_cb(mesh, (meshlink_node_t *)n, port); + } else { + return mesh->channel_accept_cb; + } +} + +/* Finish one AIO buffer, return true if the channel is still open. */ +static bool aio_finish_one(meshlink_handle_t *mesh, meshlink_channel_t *channel, meshlink_aio_buffer_t **head) { + meshlink_aio_buffer_t *aio = *head; + *head = aio->next; + + if(channel->c) { + channel->in_callback = true; + + if(aio->data) { + if(aio->cb.buffer) { + aio->cb.buffer(mesh, channel, aio->data, aio->done, aio->priv); + } + } else { + if(aio->cb.fd) { + aio->cb.fd(mesh, channel, aio->fd, aio->done, aio->priv); + } + } + + channel->in_callback = false; + + if(!channel->c) { + free(aio); + free(channel); + return false; + } + } + + free(aio); + return true; +} + +/* Finish all AIO buffers, return true if the channel is still open. */ +static bool aio_abort(meshlink_handle_t *mesh, meshlink_channel_t *channel, meshlink_aio_buffer_t **head) { + while(*head) { + if(!aio_finish_one(mesh, channel, head)) { + return false; + } + } + + return true; +} + +static ssize_t channel_recv(struct utcp_connection *connection, const void *data, size_t len) { + meshlink_channel_t *channel = connection->priv; + + if(!channel) { + abort(); + } + + node_t *n = channel->node; + meshlink_handle_t *mesh = n->mesh; + + if(n->status.destroyed) { + meshlink_channel_close(mesh, channel); + return len; + } + + const char *p = data; + size_t left = len; + + while(channel->aio_receive) { + if(!len) { + /* This receive callback signalled an error, abort all outstanding AIO buffers. */ + if(!aio_abort(mesh, channel, &channel->aio_receive)) { + return len; + } + + break; + } + + meshlink_aio_buffer_t *aio = channel->aio_receive; + size_t todo = aio->len - aio->done; + + if(todo > left) { + todo = left; + } + + if(aio->data) { + memcpy((char *)aio->data + aio->done, p, todo); + } else { + ssize_t result = write(aio->fd, p, todo); + + if(result <= 0) { + if(result < 0 && errno == EINTR) { + continue; + } + + /* Writing to fd failed, cancel just this AIO buffer. */ + logger(mesh, MESHLINK_ERROR, "Writing to AIO fd %d failed: %s", aio->fd, strerror(errno)); + + if(!aio_finish_one(mesh, channel, &channel->aio_receive)) { + return len; + } + + continue; + } + + todo = result; + } + + aio->done += todo; + p += todo; + left -= todo; + + if(aio->done == aio->len) { + if(!aio_finish_one(mesh, channel, &channel->aio_receive)) { + return len; + } + } + + if(!left) { + return len; + } + } + + if(channel->receive_cb) { + channel->receive_cb(mesh, channel, p, left); + } + + return len; +} + +static void channel_accept(struct utcp_connection *utcp_connection, uint16_t port) { + node_t *n = utcp_connection->utcp->priv; + + if(!n) { + abort(); + } + + meshlink_handle_t *mesh = n->mesh; + + if(!mesh->channel_accept_cb) { + return; + } + + meshlink_channel_t *channel = xzalloc(sizeof(*channel)); + channel->node = n; + channel->c = utcp_connection; + + if(mesh->channel_accept_cb(mesh, channel, port, NULL, 0)) { + utcp_accept(utcp_connection, channel_recv, channel); + } else { + free(channel); + } +} + +static void channel_retransmit(struct utcp_connection *utcp_connection) { + node_t *n = utcp_connection->utcp->priv; + meshlink_handle_t *mesh = n->mesh; + + if(n->mtuprobes == 31 && n->mtutimeout.cb) { + timeout_set(&mesh->loop, &n->mtutimeout, &(struct timespec) { + 0, 0 + }); + } +} + +static ssize_t channel_send(struct utcp *utcp, const void *data, size_t len) { + node_t *n = utcp->priv; + + if(n->status.destroyed) { + return -1; + } + + meshlink_handle_t *mesh = n->mesh; + return meshlink_send_immediate(mesh, (meshlink_node_t *)n, data, len) ? (ssize_t)len : -1; +} + +void meshlink_set_channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, meshlink_channel_receive_cb_t cb) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_channel_receive_cb(%p, %p)", (void *)channel, (void *)(intptr_t)cb); + + if(!mesh || !channel) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + channel->receive_cb = cb; +} + +static void channel_receive(meshlink_handle_t *mesh, meshlink_node_t *source, const void *data, size_t len) { + (void)mesh; + node_t *n = (node_t *)source; + + if(!n->utcp) { + abort(); + } + + utcp_recv(n->utcp, data, len); +} + +static void channel_poll(struct utcp_connection *connection, size_t len) { + meshlink_channel_t *channel = connection->priv; + + if(!channel) { + abort(); + } + + node_t *n = channel->node; + meshlink_handle_t *mesh = n->mesh; + + while(channel->aio_send) { + if(!len) { + /* This poll callback signalled an error, abort all outstanding AIO buffers. */ + if(!aio_abort(mesh, channel, &channel->aio_send)) { + return; + } + + break; + } + + /* We have at least one AIO buffer. Send as much as possible from the buffers. */ + meshlink_aio_buffer_t *aio = channel->aio_send; + size_t todo = aio->len - aio->done; + ssize_t sent; + + if(todo > len) { + todo = len; + } + + if(aio->data) { + sent = utcp_send(connection, (char *)aio->data + aio->done, todo); + } else { + /* Limit the amount we read at once to avoid stack overflows */ + if(todo > 65536) { + todo = 65536; + } + + char buf[todo]; + ssize_t result = read(aio->fd, buf, todo); + + if(result > 0) { + todo = result; + sent = utcp_send(connection, buf, todo); + } else { + if(result < 0 && errno == EINTR) { + continue; + } + + /* Reading from fd failed, cancel just this AIO buffer. */ + if(result != 0) { + logger(mesh, MESHLINK_ERROR, "Reading from AIO fd %d failed: %s", aio->fd, strerror(errno)); + } + + if(!aio_finish_one(mesh, channel, &channel->aio_send)) { + return; + } + + continue; + } + } + + if(sent != (ssize_t)todo) { + /* Sending failed, abort all outstanding AIO buffers and send a poll callback. */ + if(!aio_abort(mesh, channel, &channel->aio_send)) { + return; + } + + len = 0; + break; + } + + aio->done += sent; + len -= sent; + + /* If we didn't finish this buffer, exit early. */ + if(aio->done < aio->len) { + return; + } + + /* Signal completion of this buffer, and go to the next one. */ + if(!aio_finish_one(mesh, channel, &channel->aio_send)) { + return; + } + + if(!len) { + return; + } + } + + if(channel->poll_cb) { + channel->poll_cb(mesh, channel, len); + } else { + utcp_set_poll_cb(connection, NULL); + } +} + +void meshlink_set_channel_poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, meshlink_channel_poll_cb_t cb) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_channel_poll_cb(%p, %p)", (void *)channel, (void *)(intptr_t)cb); + + if(!mesh || !channel) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + channel->poll_cb = cb; + utcp_set_poll_cb(channel->c, (cb || channel->aio_send) ? channel_poll : NULL); + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_set_channel_listen_cb(meshlink_handle_t *mesh, meshlink_channel_listen_cb_t cb) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_channel_listen_cb(%p)", (void *)(intptr_t)cb); + + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + mesh->channel_listen_cb = cb; + + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_set_channel_accept_cb(meshlink_handle_t *mesh, meshlink_channel_accept_cb_t cb) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_channel_accept_cb(%p)", (void *)(intptr_t)cb); + + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + mesh->channel_accept_cb = cb; + mesh->receive_cb = channel_receive; + + for splay_each(node_t, n, mesh->nodes) { + if(!n->utcp && n != mesh->self) { + n->utcp = utcp_init(channel_accept, channel_pre_accept, channel_send, n); + utcp_set_mtu(n->utcp, n->mtu - sizeof(meshlink_packethdr_t)); + utcp_set_retransmit_cb(n->utcp, channel_retransmit); + } + } + + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_set_channel_sndbuf(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t size) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_channel_sndbuf(%p, %zu)", (void *)channel, size); + + meshlink_set_channel_sndbuf_storage(mesh, channel, NULL, size); +} + +void meshlink_set_channel_rcvbuf(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t size) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_channel_rcvbuf(%p, %zu)", (void *)channel, size); + + meshlink_set_channel_rcvbuf_storage(mesh, channel, NULL, size); +} + +void meshlink_set_channel_sndbuf_storage(meshlink_handle_t *mesh, meshlink_channel_t *channel, void *buf, size_t size) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_channel_sndbuf_storage(%p, %p, %zu)", (void *)channel, buf, size); + + if(!mesh || !channel) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + utcp_set_sndbuf(channel->c, buf, size); + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_set_channel_rcvbuf_storage(meshlink_handle_t *mesh, meshlink_channel_t *channel, void *buf, size_t size) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_channel_rcvbuf_storage(%p, %p, %zu)", (void *)channel, buf, size); + + if(!mesh || !channel) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + utcp_set_rcvbuf(channel->c, buf, size); + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_set_channel_flags(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint32_t flags) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_channel_flags(%p, %u)", (void *)channel, flags); + + if(!mesh || !channel) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + utcp_set_flags(channel->c, flags); + pthread_mutex_unlock(&mesh->mutex); +} + +meshlink_channel_t *meshlink_channel_open_ex(meshlink_handle_t *mesh, meshlink_node_t *node, uint16_t port, meshlink_channel_receive_cb_t cb, const void *data, size_t len, uint32_t flags) { + logger(mesh, MESHLINK_DEBUG, "meshlink_channel_open_ex(%s, %u, %p, %p, %zu, %u)", node ? node->name : "(null)", port, (void *)(intptr_t)cb, data, len, flags); + + if(data && len) { + abort(); // TODO: handle non-NULL data + } + + if(!mesh || !node) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + node_t *n = (node_t *)node; + + if(!n->utcp) { + n->utcp = utcp_init(channel_accept, channel_pre_accept, channel_send, n); + utcp_set_mtu(n->utcp, n->mtu - sizeof(meshlink_packethdr_t)); + utcp_set_retransmit_cb(n->utcp, channel_retransmit); + mesh->receive_cb = channel_receive; + + if(!n->utcp) { + meshlink_errno = errno == ENOMEM ? MESHLINK_ENOMEM : MESHLINK_EINTERNAL; + pthread_mutex_unlock(&mesh->mutex); + return NULL; + } + } + + if(n->status.blacklisted) { + logger(mesh, MESHLINK_ERROR, "Cannot open a channel with blacklisted node\n"); + meshlink_errno = MESHLINK_EBLACKLISTED; + pthread_mutex_unlock(&mesh->mutex); + return NULL; + } + + meshlink_channel_t *channel = xzalloc(sizeof(*channel)); + channel->node = n; + channel->receive_cb = cb; + + if(data && !len) { + channel->priv = (void *)data; + } + + channel->c = utcp_connect_ex(n->utcp, port, channel_recv, channel, flags); + + pthread_mutex_unlock(&mesh->mutex); + + if(!channel->c) { + meshlink_errno = errno == ENOMEM ? MESHLINK_ENOMEM : MESHLINK_EINTERNAL; + free(channel); + return NULL; + } + + return channel; +} + +meshlink_channel_t *meshlink_channel_open(meshlink_handle_t *mesh, meshlink_node_t *node, uint16_t port, meshlink_channel_receive_cb_t cb, const void *data, size_t len) { + logger(mesh, MESHLINK_DEBUG, "meshlink_channel_open_ex(%s, %u, %p, %p, %zu)", node ? node->name : "(null)", port, (void *)(intptr_t)cb, data, len); + + return meshlink_channel_open_ex(mesh, node, port, cb, data, len, MESHLINK_CHANNEL_TCP); +} + +void meshlink_channel_shutdown(meshlink_handle_t *mesh, meshlink_channel_t *channel, int direction) { + logger(mesh, MESHLINK_DEBUG, "meshlink_channel_shutdown(%p, %d)", (void *)channel, direction); + + if(!mesh || !channel) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + utcp_shutdown(channel->c, direction); + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_channel_close(meshlink_handle_t *mesh, meshlink_channel_t *channel) { + logger(mesh, MESHLINK_DEBUG, "meshlink_channel_close(%p)", (void *)channel); + + if(!mesh || !channel) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + if(channel->c) { + utcp_close(channel->c); + channel->c = NULL; + + /* Clean up any outstanding AIO buffers. */ + aio_abort(mesh, channel, &channel->aio_send); + aio_abort(mesh, channel, &channel->aio_receive); + } + + if(!channel->in_callback) { + free(channel); + } + + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_channel_abort(meshlink_handle_t *mesh, meshlink_channel_t *channel) { + logger(mesh, MESHLINK_DEBUG, "meshlink_channel_abort(%p)", (void *)channel); + + if(!mesh || !channel) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + if(channel->c) { + utcp_abort(channel->c); + channel->c = NULL; + + /* Clean up any outstanding AIO buffers. */ + aio_abort(mesh, channel, &channel->aio_send); + aio_abort(mesh, channel, &channel->aio_receive); + } + + if(!channel->in_callback) { + free(channel); + } + + pthread_mutex_unlock(&mesh->mutex); +} + +ssize_t meshlink_channel_send(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) { + logger(mesh, MESHLINK_DEBUG, "meshlink_channel_send(%p, %p, %zu)", (void *)channel, data, len); + + if(!mesh || !channel) { + meshlink_errno = MESHLINK_EINVAL; + return -1; + } + + if(!len) { + return 0; + } + + if(!data) { + meshlink_errno = MESHLINK_EINVAL; + return -1; + } + + // TODO: more finegrained locking. + // Ideally we want to put the data into the UTCP connection's send buffer. + // Then, preferably only if there is room in the receiver window, + // kick the meshlink thread to go send packets. + + ssize_t retval; + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + /* Disallow direct calls to utcp_send() while we still have AIO active. */ + if(channel->aio_send) { + retval = 0; + } else { + retval = utcp_send(channel->c, data, len); + } + + pthread_mutex_unlock(&mesh->mutex); + + if(retval < 0) { + meshlink_errno = MESHLINK_ENETWORK; + } + + return retval; +} + +bool meshlink_channel_aio_send(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len, meshlink_aio_cb_t cb, void *priv) { + logger(mesh, MESHLINK_DEBUG, "meshlink_channel_aio_send(%p, %p, %zu, %p, %p)", (void *)channel, data, len, (void *)(intptr_t)cb, priv); + + if(!mesh || !channel) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(!len || !data) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + meshlink_aio_buffer_t *aio = xzalloc(sizeof(*aio)); + aio->data = data; + aio->len = len; + aio->cb.buffer = cb; + aio->priv = priv; + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + /* Append the AIO buffer descriptor to the end of the chain */ + meshlink_aio_buffer_t **p = &channel->aio_send; + + while(*p) { + p = &(*p)->next; + } + + *p = aio; + + /* Ensure the poll callback is set, and call it right now to push data if possible */ + utcp_set_poll_cb(channel->c, channel_poll); + size_t todo = MIN(len, utcp_get_rcvbuf_free(channel->c)); + + if(todo) { + channel_poll(channel->c, todo); + } + + pthread_mutex_unlock(&mesh->mutex); + + return true; +} + +bool meshlink_channel_aio_fd_send(meshlink_handle_t *mesh, meshlink_channel_t *channel, int fd, size_t len, meshlink_aio_fd_cb_t cb, void *priv) { + logger(mesh, MESHLINK_DEBUG, "meshlink_channel_aio_fd_send(%p, %d, %zu, %p, %p)", (void *)channel, fd, len, (void *)(intptr_t)cb, priv); + + if(!mesh || !channel) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(!len || fd == -1) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + meshlink_aio_buffer_t *aio = xzalloc(sizeof(*aio)); + aio->fd = fd; + aio->len = len; + aio->cb.fd = cb; + aio->priv = priv; + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + /* Append the AIO buffer descriptor to the end of the chain */ + meshlink_aio_buffer_t **p = &channel->aio_send; + + while(*p) { + p = &(*p)->next; + } + + *p = aio; + + /* Ensure the poll callback is set, and call it right now to push data if possible */ + utcp_set_poll_cb(channel->c, channel_poll); + size_t left = utcp_get_rcvbuf_free(channel->c); + + if(left) { + channel_poll(channel->c, left); + } + + pthread_mutex_unlock(&mesh->mutex); + + return true; +} + +bool meshlink_channel_aio_receive(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len, meshlink_aio_cb_t cb, void *priv) { + logger(mesh, MESHLINK_DEBUG, "meshlink_channel_aio_receive(%p, %p, %zu, %p, %p)", (void *)channel, data, len, (void *)(intptr_t)cb, priv); + + if(!mesh || !channel) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(!len || !data) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + meshlink_aio_buffer_t *aio = xzalloc(sizeof(*aio)); + aio->data = data; + aio->len = len; + aio->cb.buffer = cb; + aio->priv = priv; + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + /* Append the AIO buffer descriptor to the end of the chain */ + meshlink_aio_buffer_t **p = &channel->aio_receive; + + while(*p) { + p = &(*p)->next; + } + + *p = aio; + + pthread_mutex_unlock(&mesh->mutex); + + return true; +} + +bool meshlink_channel_aio_fd_receive(meshlink_handle_t *mesh, meshlink_channel_t *channel, int fd, size_t len, meshlink_aio_fd_cb_t cb, void *priv) { + logger(mesh, MESHLINK_DEBUG, "meshlink_channel_aio_fd_receive(%p, %d, %zu, %p, %p)", (void *)channel, fd, len, (void *)(intptr_t)cb, priv); + + if(!mesh || !channel) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(!len || fd == -1) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + meshlink_aio_buffer_t *aio = xzalloc(sizeof(*aio)); + aio->fd = fd; + aio->len = len; + aio->cb.fd = cb; + aio->priv = priv; + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + /* Append the AIO buffer descriptor to the end of the chain */ + meshlink_aio_buffer_t **p = &channel->aio_receive; + + while(*p) { + p = &(*p)->next; + } + + *p = aio; + + pthread_mutex_unlock(&mesh->mutex); + + return true; +} + +uint32_t meshlink_channel_get_flags(meshlink_handle_t *mesh, meshlink_channel_t *channel) { + if(!mesh || !channel) { + meshlink_errno = MESHLINK_EINVAL; + return -1; + } + + return channel->c->flags; +} + +size_t meshlink_channel_get_sendq(meshlink_handle_t *mesh, meshlink_channel_t *channel) { + if(!mesh || !channel) { + meshlink_errno = MESHLINK_EINVAL; + return -1; + } + + return utcp_get_sendq(channel->c); +} + +size_t meshlink_channel_get_recvq(meshlink_handle_t *mesh, meshlink_channel_t *channel) { + if(!mesh || !channel) { + meshlink_errno = MESHLINK_EINVAL; + return -1; + } + + return utcp_get_recvq(channel->c); +} + +size_t meshlink_channel_get_mss(meshlink_handle_t *mesh, meshlink_channel_t *channel) { + if(!mesh || !channel) { + meshlink_errno = MESHLINK_EINVAL; + return -1; + } + + return utcp_get_mss(channel->node->utcp); +} + +void meshlink_set_node_channel_timeout(meshlink_handle_t *mesh, meshlink_node_t *node, int timeout) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_node_channel_timeout(%s, %d)", node ? node->name : "(null)", timeout); + + if(!mesh || !node) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + node_t *n = (node_t *)node; + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + if(!n->utcp) { + n->utcp = utcp_init(channel_accept, channel_pre_accept, channel_send, n); + utcp_set_mtu(n->utcp, n->mtu - sizeof(meshlink_packethdr_t)); + utcp_set_retransmit_cb(n->utcp, channel_retransmit); + } + + utcp_set_user_timeout(n->utcp, timeout); + + pthread_mutex_unlock(&mesh->mutex); +} + +void update_node_status(meshlink_handle_t *mesh, node_t *n) { + if(n->status.reachable && mesh->channel_accept_cb && !n->utcp) { + n->utcp = utcp_init(channel_accept, channel_pre_accept, channel_send, n); + utcp_set_mtu(n->utcp, n->mtu - sizeof(meshlink_packethdr_t)); + utcp_set_retransmit_cb(n->utcp, channel_retransmit); + } + + if(mesh->node_status_cb) { + mesh->node_status_cb(mesh, (meshlink_node_t *)n, n->status.reachable && !n->status.blacklisted); + } + + if(mesh->node_pmtu_cb) { + mesh->node_pmtu_cb(mesh, (meshlink_node_t *)n, n->minmtu); + } +} + +void update_node_pmtu(meshlink_handle_t *mesh, node_t *n) { + utcp_set_mtu(n->utcp, (n->minmtu > MINMTU ? n->minmtu : MINMTU) - sizeof(meshlink_packethdr_t)); + + if(mesh->node_pmtu_cb && !n->status.blacklisted) { + mesh->node_pmtu_cb(mesh, (meshlink_node_t *)n, n->minmtu); + } +} + +void handle_duplicate_node(meshlink_handle_t *mesh, node_t *n) { + if(!mesh->node_duplicate_cb || n->status.duplicate) { + return; + } + + n->status.duplicate = true; + mesh->node_duplicate_cb(mesh, (meshlink_node_t *)n); +} + +void meshlink_enable_discovery(meshlink_handle_t *mesh, bool enable) { + logger(mesh, MESHLINK_DEBUG, "meshlink_enable_discovery(%d)", enable); + + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + if(mesh->discovery.enabled == enable) { + goto end; + } + + if(mesh->threadstarted) { + if(enable) { + discovery_start(mesh); + } else { + discovery_stop(mesh); + } + } + + mesh->discovery.enabled = enable; + +end: + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_hint_network_change(struct meshlink_handle *mesh) { + logger(mesh, MESHLINK_DEBUG, "meshlink_hint_network_change()"); + + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + if(mesh->discovery.enabled) { + scan_ifaddrs(mesh); + } + + if(mesh->loop.now.tv_sec > mesh->discovery.last_update + 5) { + mesh->discovery.last_update = mesh->loop.now.tv_sec; + handle_network_change(mesh, 1); + } + + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_set_dev_class_timeouts(meshlink_handle_t *mesh, dev_class_t devclass, int pinginterval, int pingtimeout) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_dev_class_timeouts(%d, %d, %d)", devclass, pinginterval, pingtimeout); + + if(!mesh || devclass < 0 || devclass >= DEV_CLASS_COUNT) { + meshlink_errno = EINVAL; + return; + } + + if(pinginterval < 1 || pingtimeout < 1 || pingtimeout > pinginterval) { + meshlink_errno = EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + mesh->dev_class_traits[devclass].pinginterval = pinginterval; + mesh->dev_class_traits[devclass].pingtimeout = pingtimeout; + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_set_dev_class_fast_retry_period(meshlink_handle_t *mesh, dev_class_t devclass, int fast_retry_period) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_dev_class_fast_retry_period(%d, %d)", devclass, fast_retry_period); + + if(!mesh || devclass < 0 || devclass >= DEV_CLASS_COUNT) { + meshlink_errno = EINVAL; + return; + } + + if(fast_retry_period < 0) { + meshlink_errno = EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + mesh->dev_class_traits[devclass].fast_retry_period = fast_retry_period; + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_set_dev_class_maxtimeout(struct meshlink_handle *mesh, dev_class_t devclass, int maxtimeout) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_dev_class_fast_maxtimeout(%d, %d)", devclass, maxtimeout); + + if(!mesh || devclass < 0 || devclass >= DEV_CLASS_COUNT) { + meshlink_errno = EINVAL; + return; + } + + if(maxtimeout < 0) { + meshlink_errno = EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + mesh->dev_class_traits[devclass].maxtimeout = maxtimeout; + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_reset_timers(struct meshlink_handle *mesh) { + logger(mesh, MESHLINK_DEBUG, "meshlink_reset_timers()"); + + if(!mesh) { + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + handle_network_change(mesh, true); + + if(mesh->discovery.enabled) { + discovery_refresh(mesh); + } + + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_set_inviter_commits_first(struct meshlink_handle *mesh, bool inviter_commits_first) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_inviter_commits_first(%d)", inviter_commits_first); + + if(!mesh) { + meshlink_errno = EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + mesh->inviter_commits_first = inviter_commits_first; + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_set_external_address_discovery_url(struct meshlink_handle *mesh, const char *url) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_external_address_discovery_url(%s)", url ? url : "(null)"); + + if(!mesh) { + meshlink_errno = EINVAL; + return; + } + + if(url && (strncmp(url, "http://", 7) || strchr(url, ' '))) { + meshlink_errno = EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + free(mesh->external_address_url); + mesh->external_address_url = url ? xstrdup(url) : NULL; + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_set_scheduling_granularity(struct meshlink_handle *mesh, long granularity) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_scheduling_granularity(%ld)", granularity); + + if(!mesh || granularity < 0) { + meshlink_errno = EINVAL; + return; + } + + utcp_set_clock_granularity(granularity); +} + +void meshlink_set_storage_policy(struct meshlink_handle *mesh, meshlink_storage_policy_t policy) { + logger(mesh, MESHLINK_DEBUG, "meshlink_set_storage_policy(%d)", policy); + + if(!mesh) { + meshlink_errno = EINVAL; + return; + } + + if(pthread_mutex_lock(&mesh->mutex) != 0) { + abort(); + } + + mesh->storage_policy = policy; + pthread_mutex_unlock(&mesh->mutex); +} + +void handle_network_change(meshlink_handle_t *mesh, bool online) { + (void)online; + + if(!mesh->connections || !mesh->loop.running) { + return; + } + + retry(mesh); + signal_trigger(&mesh->loop, &mesh->datafromapp); +} + +void call_error_cb(meshlink_handle_t *mesh, meshlink_errno_t cb_errno) { + // We should only call the callback function if we are in the background thread. + if(!mesh->error_cb) { + return; + } + + if(!mesh->threadstarted) { + return; + } + + if(mesh->thread == pthread_self()) { + mesh->error_cb(mesh, cb_errno); + } +} + +static void __attribute__((constructor)) meshlink_init(void) { + crypto_init(); + utcp_set_clock_granularity(10000); +} + +static void __attribute__((destructor)) meshlink_exit(void) { + crypto_exit(); +} diff --git a/src/meshlink.h b/src/meshlink.h new file mode 100644 index 0000000..a6c5b4b --- /dev/null +++ b/src/meshlink.h @@ -0,0 +1,1868 @@ +#ifndef MESHLINK_H +#define MESHLINK_H + +/* + meshlink.h -- MeshLink API + Copyright (C) 2014-2021 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include +#include +#include +#include + +#if defined(_WIN32) +#include +#else +#include +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/// The length in bytes of a signature made with meshlink_sign() +#define MESHLINK_SIGLEN (64ul) + +// The maximum length of fingerprints +#define MESHLINK_FINGERPRINTLEN (64ul) + +/// A handle for an instance of MeshLink. +typedef struct meshlink_handle meshlink_handle_t; + +/// A handle for a MeshLink node. +typedef struct meshlink_node meshlink_node_t; + +/// A handle for a MeshLink channel. +typedef struct meshlink_channel meshlink_channel_t; + +/// A struct containing all parameters used for opening a mesh. +typedef struct meshlink_open_params meshlink_open_params_t; + +/// A handle for a MeshLink sub-mesh. +typedef struct meshlink_submesh meshlink_submesh_t; + +/// Code of most recent error encountered. +typedef enum { + MESHLINK_OK, ///< Everything is fine + MESHLINK_EINVAL, ///< Invalid parameter(s) to function call + MESHLINK_ENOMEM, ///< Out of memory + MESHLINK_ENOENT, ///< Node is not known + MESHLINK_EEXIST, ///< Node already exists + MESHLINK_EINTERNAL, ///< MeshLink internal error + MESHLINK_ERESOLV, ///< MeshLink could not resolve a hostname + MESHLINK_ESTORAGE, ///< MeshLink could not load or write data from/to disk + MESHLINK_ENETWORK, ///< MeshLink encountered a network error + MESHLINK_EPEER, ///< A peer caused an error + MESHLINK_ENOTSUP, ///< The operation is not supported in the current configuration of MeshLink + MESHLINK_EBUSY, ///< The MeshLink instance is already in use by another process + MESHLINK_EBLACKLISTED ///< The operation is not allowed because the node is blacklisted +} meshlink_errno_t; + +/// Device class +typedef enum { + DEV_CLASS_BACKBONE = 0, + DEV_CLASS_STATIONARY = 1, + DEV_CLASS_PORTABLE = 2, + DEV_CLASS_UNKNOWN = 3, + DEV_CLASS_COUNT +} dev_class_t; + +/// Storage policy +typedef enum { + MESHLINK_STORAGE_ENABLED, ///< Store all updates. + MESHLINK_STORAGE_DISABLED, ///< Don't store any updates. + MESHLINK_STORAGE_KEYS_ONLY ///< Only store updates when a node's key has changed. +} meshlink_storage_policy_t; + +/// Invitation flags +static const uint32_t MESHLINK_INVITE_LOCAL = 1; // Only use local addresses in the URL +static const uint32_t MESHLINK_INVITE_PUBLIC = 2; // Only use public or canonical addresses in the URL +static const uint32_t MESHLINK_INVITE_IPV4 = 4; // Only use IPv4 addresses in the URL +static const uint32_t MESHLINK_INVITE_IPV6 = 8; // Only use IPv6 addresses in the URL +static const uint32_t MESHLINK_INVITE_NUMERIC = 16; // Don't look up hostnames + +/// Channel flags +static const uint32_t MESHLINK_CHANNEL_RELIABLE = 1; // Data is retransmitted when packets are lost. +static const uint32_t MESHLINK_CHANNEL_ORDERED = 2; // Data is delivered in-order to the application. +static const uint32_t MESHLINK_CHANNEL_FRAMED = 4; // Data is delivered in chunks of the same length as data was originally sent. +static const uint32_t MESHLINK_CHANNEL_DROP_LATE = 8; // When packets are reordered, late packets are ignored. +static const uint32_t MESHLINK_CHANNEL_NO_PARTIAL = 16; // Calls to meshlink_channel_send() will either send all data or nothing. +static const uint32_t MESHLINK_CHANNEL_TCP = 3; // Select TCP semantics. +static const uint32_t MESHLINK_CHANNEL_UDP = 0; // Select UDP semantics. + +/// A variable holding the last encountered error from MeshLink. +/** This is a thread local variable that contains the error code of the most recent error + * encountered by a MeshLink API function called in the current thread. + * The variable is only updated when an error is encountered, and is not reset to MESHLINK_OK + * if a function returned successfully. + */ +extern __thread meshlink_errno_t meshlink_errno; + +#ifndef MESHLINK_INTERNAL_H + +struct meshlink_handle { + const char *const name; ///< Textual name of ourself. It is stored in a nul-terminated C string, which is allocated by MeshLink. + void *priv; ///< Private pointer which may be set freely by the application, and is never used or modified by MeshLink. +}; + +struct meshlink_node { + const char *const name; ///< Textual name of this node. It is stored in a nul-terminated C string, which is allocated by MeshLink. + void *priv; ///< Private pointer which may be set freely by the application, and is never used or modified by MeshLink. +}; + +struct meshlink_submesh { + const char *const name; ///< Textual name of this Sub-Mesh. It is stored in a nul-terminated C string, which is allocated by MeshLink. + void *priv; ///< Private pointer which may be set freely by the application, and is never used or modified by MeshLink. +}; + +struct meshlink_channel { + struct meshlink_node *const node; ///< Pointer to the peer of this channel. + void *priv; ///< Private pointer which may be set freely by the application, and is never used or modified by MeshLink. +}; + +#endif // MESHLINK_INTERNAL_H + +/// Get the text for the given MeshLink error code. +/** This function returns a pointer to the string containing the description of the given error code. + * + * @param err An error code returned by MeshLink. + * + * @return A pointer to a string containing the description of the error code. + * The pointer is to static storage that is valid for the lifetime of the application. + * This function will always return a valid pointer, even if an invalid error code has been passed. + */ +const char *meshlink_strerror(meshlink_errno_t err) __attribute__((__warn_unused_result__)); + +/// Create a new meshlink_open_params_t struct. +/** This function allocates and initializes a new meshlink_open_params_t struct that can be passed to meshlink_open_ex(). + * The resulting struct may be reused for multiple calls to meshlink_open_ex(). + * After the last use, the application must free this struct using meshlink_open_params_free(). + * + * @param confbase The directory in which MeshLink will store its configuration files. + * After the function returns, the application is free to overwrite or free @a confbase. + * @param name The name which this instance of the application will use in the mesh. + * After the function returns, the application is free to overwrite or free @a name. + * If NULL is passed as the name, the name used last time the MeshLink instance was initialized is used. + * @param appname The application name which will be used in the mesh. + * After the function returns, the application is free to overwrite or free @a name. + * @param devclass The device class which will be used in the mesh. + * + * @return A pointer to a meshlink_open_params_t which can be passed to meshlink_open_ex(), or NULL in case of an error. + * The pointer is valid until meshlink_open_params_free() is called. + */ +meshlink_open_params_t *meshlink_open_params_init(const char *confbase, const char *name, const char *appname, dev_class_t devclass) __attribute__((__warn_unused_result__)); + +/// Free a meshlink_open_params_t struct. +/** This function frees a meshlink_open_params_t struct and all resources associated with it. + * + * @param params A pointer to a meshlink_open_params_t which must have been created earlier with meshlink_open_params_init(). + */ +void meshlink_open_params_free(meshlink_open_params_t *params); + +/// Set the network namespace MeshLink should use. +/** This function changes the open parameters to use the given netns filedescriptor. + * + * @param params A pointer to a meshlink_open_params_t which must have been created earlier with meshlink_open_params_init(). + * @param netns A filedescriptor that must point to a valid network namespace, or -1 to have MeshLink use the same namespace as the calling thread. + * + * @return This function will return true if the open parameters have been successfully updated, false otherwise. + */ +bool meshlink_open_params_set_netns(meshlink_open_params_t *params, int netns) __attribute__((__warn_unused_result__)); + +/// Set the encryption key MeshLink should use for local storage. +/** This function changes the open parameters to use the given key for encrypting MeshLink's own configuration files. + * + * @param params A pointer to a meshlink_open_params_t which must have been created earlier with meshlink_open_params_init(). + * @param key A pointer to a key, or NULL in case no encryption should be used. + * @param keylen The length of the given key, or 0 in case no encryption should be used. + * + * @return This function will return true if the open parameters have been successfully updated, false otherwise. + */ +bool meshlink_open_params_set_storage_key(meshlink_open_params_t *params, const void *key, size_t keylen) __attribute__((__warn_unused_result__)); + +/// Set the encryption key MeshLink should use for local storage. +/** This function changes the open parameters to use the given storage policy. + * + * @param params A pointer to a meshlink_open_params_t which must have been created earlier with meshlink_open_params_init(). + * @param policy The storage policy to use. + * + * @return This function will return true if the open parameters have been successfully updated, false otherwise. + */ +bool meshlink_open_params_set_storage_policy(meshlink_open_params_t *params, meshlink_storage_policy_t policy) __attribute__((__warn_unused_result__)); + +/// Set the filename of the lockfile. +/** This function changes the path of the lockfile used to ensure only one instance of MeshLink can be open at the same time. + * If an application changes this, it must always set it to the same location. + * + * @param params A pointer to a meshlink_open_params_t which must have been created earlier with meshlink_open_params_init(). + * @param filename The filename of the lockfile. + * + * @return This function will return true if the open parameters have been successfully updated, false otherwise. + */ +bool meshlink_open_params_set_lock_filename(meshlink_open_params_t *params, const char *filename) __attribute__((__warn_unused_result__)); + +/// Open or create a MeshLink instance. +/** This function opens or creates a MeshLink instance. + * All parameters needed by MeshLink are passed via a meshlink_open_params_t struct, + * which must have been initialized earlier by the application. + * + * This function returns a pointer to a struct meshlink_handle that will be allocated by MeshLink. + * When the application does no longer need to use this handle, it must call meshlink_close() to + * free its resources. + * + * This function does not start any network I/O yet. The application should + * first set callbacks, and then call meshlink_start(). + * + * @param params A pointer to a meshlink_open_params_t which must be filled in by the application. + * After the function returns, the application is free to reuse or free @a params. + * + * @return A pointer to a struct meshlink_handle which represents this instance of MeshLink, or NULL in case of an error. + * The pointer is valid until meshlink_close() is called. + */ +struct meshlink_handle *meshlink_open_ex(const meshlink_open_params_t *params) __attribute__((__warn_unused_result__)); + +/// Open or create a MeshLink instance. +/** This function opens or creates a MeshLink instance. + * The state is stored in the configuration directory passed in the variable @a confbase. + * If the configuration directory does not exist yet, for example when it is the first time + * this instance is opened, the configuration directory will be automatically created and initialized. + * However, the parent directory should already exist, otherwise an error will be returned. + * + * The name given should be a unique identifier for this instance. + * + * This function returns a pointer to a struct meshlink_handle that will be allocated by MeshLink. + * When the application does no longer need to use this handle, it must call meshlink_close() to + * free its resources. + * + * This function does not start any network I/O yet. The application should + * first set callbacks, and then call meshlink_start(). + * + * @param confbase The directory in which MeshLink will store its configuration files. + * After the function returns, the application is free to overwrite or free @a confbase. + * @param name The name which this instance of the application will use in the mesh. + * After the function returns, the application is free to overwrite or free @a name. + * If NULL is passed as the name, the name used last time the MeshLink instance was initialized is used. + * @param appname The application name which will be used in the mesh. + * After the function returns, the application is free to overwrite or free @a name. + * @param devclass The device class which will be used in the mesh. + * + * @return A pointer to a struct meshlink_handle which represents this instance of MeshLink, or NULL in case of an error. + * The pointer is valid until meshlink_close() is called. + */ +struct meshlink_handle *meshlink_open(const char *confbase, const char *name, const char *appname, dev_class_t devclass) __attribute__((__warn_unused_result__)); + +/// Open or create a MeshLink instance that uses encrypted storage. +/** This function opens or creates a MeshLink instance. + * The state is stored in the configuration directory passed in the variable @a confbase. + * If the configuration directory does not exist yet, for example when it is the first time + * this instance is opened, the configuration directory will be automatically created and initialized. + * However, the parent directory should already exist, otherwise an error will be returned. + * + * The name given should be a unique identifier for this instance. + * + * This function returns a pointer to a struct meshlink_handle that will be allocated by MeshLink. + * When the application does no longer need to use this handle, it must call meshlink_close() to + * free its resources. + * + * This function does not start any network I/O yet. The application should + * first set callbacks, and then call meshlink_start(). + * + * @param confbase The directory in which MeshLink will store its configuration files. + * After the function returns, the application is free to overwrite or free @a confbase. + * @param name The name which this instance of the application will use in the mesh. + * After the function returns, the application is free to overwrite or free @a name. + * If NULL is passed as the name, the name used last time the MeshLink instance was initialized is used. + * @param appname The application name which will be used in the mesh. + * After the function returns, the application is free to overwrite or free @a name. + * @param devclass The device class which will be used in the mesh. + * @param key A pointer to a key used to encrypt storage. + * @param keylen The length of the key in bytes. + * + * @return A pointer to a struct meshlink_handle which represents this instance of MeshLink, or NULL in case of an error. + * The pointer is valid until meshlink_close() is called. + */ +struct meshlink_handle *meshlink_open_encrypted(const char *confbase, const char *name, const char *appname, dev_class_t devclass, const void *key, size_t keylen) __attribute__((__warn_unused_result__)); + +/// Create an ephemeral MeshLink instance that does not store any state. +/** This function creates a MeshLink instance. + * No state is ever saved, so once this instance is closed, all its state is gone. + * + * The name given should be a unique identifier for this instance. + * + * This function returns a pointer to a struct meshlink_handle that will be allocated by MeshLink. + * When the application does no longer need to use this handle, it must call meshlink_close() to + * free its resources. + * + * This function does not start any network I/O yet. The application should + * first set callbacks, and then call meshlink_start(). + * + * @param name The name which this instance of the application will use in the mesh. + * After the function returns, the application is free to overwrite or free @a name. + * @param appname The application name which will be used in the mesh. + * After the function returns, the application is free to overwrite or free @a name. + * @param devclass The device class which will be used in the mesh. + * + * @return A pointer to a struct meshlink_handle which represents this instance of MeshLink, or NULL in case of an error. + * The pointer is valid until meshlink_close() is called. + */ +struct meshlink_handle *meshlink_open_ephemeral(const char *name, const char *appname, dev_class_t devclass) __attribute__((__warn_unused_result__)); + +/// Create Sub-Mesh. +/** This function causes MeshLink to open a new Sub-Mesh network + * create a new thread, which will handle all network I/O. + * + * It is allowed to call this function even if MeshLink is already started, in which case it will return true. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * + * @param submesh Name of the new Sub-Mesh to create. + * + * @return A pointer to a struct meshlink_submesh which represents this instance of SubMesh, or NULL in case of an error. + * The pointer is valid until meshlink_close() is called. + */ +struct meshlink_submesh *meshlink_submesh_open(struct meshlink_handle *mesh, const char *submesh) __attribute__((__warn_unused_result__)); + +/// Start MeshLink. +/** This function causes MeshLink to open network sockets, make outgoing connections, and + * create a new thread, which will handle all network I/O. + * + * It is allowed to call this function even if MeshLink is already started, in which case it will return true. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * + * @return This function will return true if MeshLink has successfully started, false otherwise. + */ +bool meshlink_start(struct meshlink_handle *mesh) __attribute__((__warn_unused_result__)); + +/// Stop MeshLink. +/** This function causes MeshLink to disconnect from all other nodes, + * close all sockets, and shut down its own thread. + * + * This function always succeeds. It is allowed to call meshlink_stop() even if MeshLink is already stopped or has never been started. + * Channels that are still open will remain valid, but any communication via channels will stop as well. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + */ +void meshlink_stop(struct meshlink_handle *mesh); + +/// Close the MeshLink handle. +/** This function calls meshlink_stop() if necessary, + * and frees the struct meshlink_handle and all associacted memory allocated by MeshLink, including all channels. + * Afterwards, the handle and any pointers to a struct meshlink_node or struct meshlink_channel are invalid. + * + * It is allowed to call this function at any time on a valid handle, except inside callback functions. + * If called at a proper time with a valid handle, this function always succeeds. + * If called within a callback or with an invalid handle, the result is undefined. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + */ +void meshlink_close(struct meshlink_handle *mesh); + +/// Destroy a MeshLink instance. +/** This function remove all configuration files of a MeshLink instance. It should only be called when the application + * does not have an open handle to this instance. Afterwards, a call to meshlink_open() will create a completely + * new instance. + * + * @param confbase The directory in which MeshLink stores its configuration files. + * After the function returns, the application is free to overwrite or free @a confbase. + * + * @return This function will return true if the MeshLink instance was successfully destroyed, false otherwise. + */ +bool meshlink_destroy(const char *confbase) __attribute__((__warn_unused_result__)); + +/// Destroy a MeshLink instance using open parameters. +/** This function remove all configuration files of a MeshLink instance. It should only be called when the application + * does not have an open handle to this instance. Afterwards, a call to meshlink_open() will create a completely + * new instance. + * + * This version expects a pointer to meshlink_open_params_t, + * and will use exactly the same settings used for opening a handle to destroy it. + * + * @param params A pointer to a meshlink_open_params_t which must be filled in by the application. + * After the function returns, the application is free to reuse or free @a params. + * + * @return This function will return true if the MeshLink instance was successfully destroyed, false otherwise. + */ +bool meshlink_destroy_ex(const meshlink_open_params_t *params) __attribute__((__warn_unused_result__)); + +/// A callback for receiving data from the mesh. +/** @param mesh A handle which represents an instance of MeshLink. + * @param source A pointer to a struct meshlink_node describing the source of the data. + * @param data A pointer to a buffer containing the data sent by the source, or NULL in case there is no data (an empty packet was received). + * The pointer is only valid during the lifetime of the callback. + * The callback should mempcy() the data if it needs to be available outside the callback. + * @param len The length of the received data, or 0 in case there is no data. + */ +typedef void (*meshlink_receive_cb_t)(struct meshlink_handle *mesh, struct meshlink_node *source, const void *data, size_t len); + +/// Set the receive callback. +/** This functions sets the callback that is called whenever another node sends data to the local node. + * The callback is run in MeshLink's own thread. + * It is therefore important that the callback uses apprioriate methods (queues, pipes, locking, etc.) + * to hand the data over to the application's thread. + * The callback should also not block itself and return as quickly as possible. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param cb A pointer to the function which will be called when another node sends data to the local node. + * If a NULL pointer is given, the callback will be disabled. + */ +void meshlink_set_receive_cb(struct meshlink_handle *mesh, meshlink_receive_cb_t cb); + +/// A callback reporting the meta-connection attempt made by the host node to an another node. +/** @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a struct meshlink_node describing the node to whom meta-connection is being tried. + * This pointer is valid until meshlink_close() is called. + */ +typedef void (*meshlink_connection_try_cb_t)(struct meshlink_handle *mesh, struct meshlink_node *node); + +/// Set the meta-connection try callback. +/** This functions sets the callback that is called whenever a connection attempt is happened to another node. + * The callback is run in MeshLink's own thread. + * It is therefore important that the callback uses apprioriate methods (queues, pipes, locking, etc.) + * to hand the data over to the application's thread. + * The callback should also not block itself and return as quickly as possible. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param cb A pointer to the function which will be called when host node attempts to make + * the connection to another node. If a NULL pointer is given, the callback will be disabled. + */ +void meshlink_set_connection_try_cb(struct meshlink_handle *mesh, meshlink_connection_try_cb_t cb); + +/// A callback reporting node status changes. +/** @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a struct meshlink_node describing the node whose status changed. + * This pointer is valid until meshlink_close() is called. + * @param reachable True if the node is reachable, false otherwise. + */ +typedef void (*meshlink_node_status_cb_t)(struct meshlink_handle *mesh, struct meshlink_node *node, bool reachable); + +/// Set the node status callback. +/** This functions sets the callback that is called whenever another node's status changed. + * The callback is run in MeshLink's own thread. + * It is therefore important that the callback uses apprioriate methods (queues, pipes, locking, etc.) + * to hand the data over to the application's thread. + * The callback should also not block itself and return as quickly as possible. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param cb A pointer to the function which will be called when another node's status changes. + * If a NULL pointer is given, the callback will be disabled. + */ +void meshlink_set_node_status_cb(struct meshlink_handle *mesh, meshlink_node_status_cb_t cb); + +/// A callback reporting node path MTU changes. +/** @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a struct meshlink_node describing the node whose status changed. + * This pointer is valid until meshlink_close() is called. + * @param pmtu The current path MTU to the node, or 0 if UDP communication is not (yet) possible. + */ +typedef void (*meshlink_node_pmtu_cb_t)(struct meshlink_handle *mesh, struct meshlink_node *node, uint16_t pmtu); + +/// Set the node extended status callback. +/** This functions sets the callback that is called whenever certain connectivity parameters for a node change. + * The callback is run in MeshLink's own thread. + * It is therefore important that the callback uses apprioriate methods (queues, pipes, locking, etc.) + * to hand the data over to the application's thread. + * The callback should also not block itself and return as quickly as possible. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param cb A pointer to the function which will be called when another node's extended status changes. + * If a NULL pointer is given, the callback will be disabled. + */ +void meshlink_set_node_pmtu_cb(struct meshlink_handle *mesh, meshlink_node_pmtu_cb_t cb); + +/// A callback reporting duplicate node detection. +/** @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a struct meshlink_node describing the node which is duplicate. + * This pointer is valid until meshlink_close() is called. + */ +typedef void (*meshlink_node_duplicate_cb_t)(struct meshlink_handle *mesh, struct meshlink_node *node); + +/// Set the node duplicate callback. +/** This functions sets the callback that is called whenever a duplicate node is detected. + * The callback is run in MeshLink's own thread. + * It is therefore important that the callback uses apprioriate methods (queues, pipes, locking, etc.) + * to hand the data over to the application's thread. + * The callback should also not block itself and return as quickly as possible. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param cb A pointer to the function which will be called when a duplicate node is detected. + * If a NULL pointer is given, the callback will be disabled. + */ +void meshlink_set_node_duplicate_cb(struct meshlink_handle *mesh, meshlink_node_duplicate_cb_t cb); + +/// Severity of log messages generated by MeshLink. +typedef enum { + MESHLINK_DEBUG, ///< Internal debugging messages. Only useful during application development. + MESHLINK_INFO, ///< Informational messages. + MESHLINK_WARNING, ///< Warnings which might indicate problems, but which are not real errors. + MESHLINK_ERROR, ///< Errors which hamper correct functioning of MeshLink, without causing it to fail completely. + MESHLINK_CRITICAL ///< Critical errors which cause MeshLink to fail completely. +} meshlink_log_level_t; + +/// A callback for receiving log messages generated by MeshLink. +/** @param mesh A handle which represents an instance of MeshLink, or NULL. + * @param level An enum describing the severity level of the message. + * @param text A pointer to a nul-terminated C string containing the textual log message. + * This pointer is only valid for the duration of the callback. + * The application must not free() this pointer. + * The application should strdup() the text if it has to be available outside the callback. + */ +typedef void (*meshlink_log_cb_t)(struct meshlink_handle *mesh, meshlink_log_level_t level, const char *text); + +/// Set the log callback. +/** This functions sets the callback that is called whenever MeshLink has some information to log. + * + * The @a mesh parameter can either be a valid MeshLink handle, or NULL. + * In case it is NULL, the callback will be called for errors that happen outside the context of a valid mesh instance. + * Otherwise, it will be called for errors that happen in the context of the given mesh instance. + * + * If @a mesh is not NULL, then the callback is run in MeshLink's own thread. + * It is important that the callback uses apprioriate methods (queues, pipes, locking, etc.) + * to hand the data over to the application's thread. + * The callback should also not block itself and return as quickly as possible. + * + * The @a mesh parameter can either be a valid MeshLink handle, or NULL. + * In case it is NULL, the callback will be called for errors that happen outside the context of a valid mesh instance. + * Otherwise, it will be called for errors that happen in the context of the given mesh instance. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink, or NULL. + * @param level An enum describing the minimum severity level. Debugging information with a lower level will not trigger the callback. + * @param cb A pointer to the function which will be called when another node sends data to the local node. + * If a NULL pointer is given, the callback will be disabled. + */ +void meshlink_set_log_cb(struct meshlink_handle *mesh, meshlink_log_level_t level, meshlink_log_cb_t cb); + +/// A callback for receiving error conditions encountered by the MeshLink thread. +/** @param mesh A handle which represents an instance of MeshLink, or NULL. + * @param errno The error code describing what kind of error occurred. + */ +typedef void (*meshlink_error_cb_t)(struct meshlink_handle *mesh, meshlink_errno_t meshlink_errno); + +/// Set the error callback. +/** This functions sets the callback that is called whenever the MeshLink thread encounters a serious error. + * + * While most API functions report an error directly to the caller in case something went wrong, + * MeshLink also runs a background thread which can encounter error conditions. + * Most of them will be dealt with automatically, however there can be errors that will prevent MeshLink from + * working correctly. When the callback is called, it means that MeshLink is no longer functioning + * as expected. The application should then present an error message and shut down, or perform any other + * action it deems appropriate. + * + * The callback is run in MeshLink's own thread. + * It is important that the callback uses apprioriate methods (queues, pipes, locking, etc.) + * to hand the data over to the application's thread. + * The callback should also not block itself and return as quickly as possible. + * + * Even though the callback signals a serious error inside MeshLink, all open handles are still valid, + * and the application should close handles in exactly the same it would have to do if the callback + * was not called. This must not be done inside the callback itself. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink, or NULL. + * @param cb A pointer to the function which will be called when a serious error is encountered. + * If a NULL pointer is given, the callback will be disabled. + */ +void meshlink_set_error_cb(struct meshlink_handle *mesh, meshlink_error_cb_t cb); + +/// A callback for receiving blacklisted conditions encountered by the MeshLink thread. +/** @param mesh A handle which represents an instance of MeshLink, or NULL. + * @param node The node that blacklisted the local node. + */ +typedef void (*meshlink_blacklisted_cb_t)(struct meshlink_handle *mesh, struct meshlink_node *node); + +/// Set the blacklisted callback. +/** This functions sets the callback that is called whenever MeshLink detects that it is blacklisted by another node. + * + * The callback is run in MeshLink's own thread. + * It is important that the callback uses apprioriate methods (queues, pipes, locking, etc.) + * to hand the data over to the application's thread. + * The callback should also not block itself and return as quickly as possible. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink, or NULL. + * @param cb A pointer to the function which will be called when a serious error is encountered. + * If a NULL pointer is given, the callback will be disabled. + */ +void meshlink_set_blacklisted_cb(struct meshlink_handle *mesh, meshlink_blacklisted_cb_t cb); + +/// Send data to another node. +/** This functions sends one packet of data to another node in the mesh. + * The packet is sent using UDP semantics, which means that + * the packet is sent as one unit and is received as one unit, + * and that there is no guarantee that the packet will arrive at the destination. + * Packets that are too big to be sent over the network as one unit might be dropped, and this function may return an error if this situation can be detected beforehand. + * The application should not send packets that are larger than the path MTU, which can be queried with meshlink_get_pmtu(). + * The application should take care of getting an acknowledgement and retransmission if necessary. + * + * \memberof meshlink_node + * @param mesh A handle which represents an instance of MeshLink. + * @param destination A pointer to a struct meshlink_node describing the destination for the data. + * @param data A pointer to a buffer containing the data to be sent to the source. + * After meshlink_send() returns, the application is free to overwrite or free this buffer. + * It is valid to specify a NULL pointer, but only if @a len is also 0. + * @param len The length of the data. + * @return This function will return true if MeshLink has queued the message for transmission, and false otherwise. + * A return value of true does not guarantee that the message will actually arrive at the destination. + */ +bool meshlink_send(struct meshlink_handle *mesh, struct meshlink_node *destination, const void *data, size_t len) __attribute__((__warn_unused_result__)); + +/// Query the maximum packet size that can be sent to a node. +/** This functions returns the maximum size of packets (path MTU) that can be sent to a specific node with meshlink_send(). + * The path MTU is a property of the path packets will take to the destination node over the Internet. + * It can be different for different destination nodes. + * and the path MTU can change at any point in time due to changes in the Internet. + * Therefore, although this should only occur rarely, it can still happen that packets that do not exceed this size get dropped. + * + * \memberof meshlink_node + * @param mesh A handle which represents an instance of MeshLink. + * @param destination A pointer to a struct meshlink_node describing the destination for the data. + * + * @return The recommended maximum size of packets that are to be sent to the destination node, 0 if the node is unreachable, + * or a negative value in case of an error. + */ +ssize_t meshlink_get_pmtu(struct meshlink_handle *mesh, struct meshlink_node *destination) __attribute__((__warn_unused_result__)); + +/// Get a handle for our own node. +/** This function returns a handle for the local node. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * + * @return A pointer to a struct meshlink_node which represents the local node. + * The pointer is guaranteed to be valid until meshlink_close() is called. + */ +struct meshlink_node *meshlink_get_self(struct meshlink_handle *mesh) __attribute__((__warn_unused_result__)); + +/// Get a handle for a specific node. +/** This function returns a handle for the node with the given name. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param name The name of the node for which a handle is requested. + * After this function returns, the application is free to overwrite or free @a name. + * + * @return A pointer to a struct meshlink_node which represents the requested node, + * or NULL if the requested node does not exist. + * The pointer is guaranteed to be valid until meshlink_close() is called. + */ +struct meshlink_node *meshlink_get_node(struct meshlink_handle *mesh, const char *name) __attribute__((__warn_unused_result__)); + +/// Get a handle for a specific submesh. +/** This function returns a handle for the submesh with the given name. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param name The name of the submesh for which a handle is requested. + * After this function returns, the application is free to overwrite or free @a name. + * + * @return A pointer to a struct meshlink_submesh which represents the requested submesh, + * or NULL if the requested submesh does not exist. + * The pointer is guaranteed to be valid until meshlink_close() is called. + */ +struct meshlink_submesh *meshlink_get_submesh(struct meshlink_handle *mesh, const char *name) __attribute__((__warn_unused_result__)); + +/// Get the fingerprint of a node's public key. +/** This function returns a fingerprint of the node's public key. + * It should be treated as an opaque blob. + * + * \memberof meshlink_node + * @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a struct meshlink_node describing the node. + * + * @return A nul-terminated C string containing the fingerprint of the node's public key in a printable ASCII format. + * The application should call free() after it is done using this string. + */ +char *meshlink_get_fingerprint(struct meshlink_handle *mesh, struct meshlink_node *node) __attribute__((__warn_unused_result__)); + +/// Get a list of all nodes. +/** This function returns a list with handles for all known nodes. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param nodes A pointer to a previously allocated array of pointers to struct meshlink_node, or NULL in which case MeshLink will allocate a new array. + * The application can supply an array it allocated itself with malloc, or the return value from the previous call to this function (which is the preferred way). + * The application is allowed to call free() on the array whenever it wishes. + * The pointers in the array are valid until meshlink_close() is called. + * @param nmemb A pointer to a variable holding the number of nodes that are stored in the array. + * In case the @a nodes argument is not NULL, MeshLink might call realloc() on the array to change its size. + * The contents of this variable will be changed to reflect the new size of the array. + * + * @return A pointer to an array containing pointers to all known nodes, or NULL in case of an error. + * If the @a nodes argument was not NULL, then the return value can either be the same value or a different value. + * If it is a new value, the old value of @a nodes should not be used anymore. + * If the new value is NULL, then the old array will have been freed by MeshLink. + */ +struct meshlink_node **meshlink_get_all_nodes(struct meshlink_handle *mesh, struct meshlink_node **nodes, size_t *nmemb) __attribute__((__warn_unused_result__)); + +/// Sign data using the local node's MeshLink key. +/** This function signs data using the local node's MeshLink key. + * The generated signature can be securely verified by other nodes. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param data A pointer to a buffer containing the data to be signed. + * @param len The length of the data to be signed. + * @param signature A pointer to a buffer where the signature will be stored. + * The buffer must be allocated by the application, and should be at least MESHLINK_SIGLEN bytes big. + * The signature is a binary blob, and is not nul-terminated. + * @param siglen The size of the signature buffer. Will be changed after the call to match the size of the signature itself. + * + * @return This function returns true if the signature was correctly generated, false otherwise. + */ +bool meshlink_sign(struct meshlink_handle *mesh, const void *data, size_t len, void *signature, size_t *siglen) __attribute__((__warn_unused_result__)); + +/// Get the list of all nodes by device class. +/** This function returns a list with handles for all the nodes that matches with the given @a devclass. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param devclass Device class of the nodes for which the list has to be obtained. + * @param nodes A pointer to a previously allocated array of pointers to struct meshlink_node, or NULL in which case MeshLink will allocate a new array. + * The application can supply an array it allocated itself with malloc, or the return value from the previous call to this function (which is the preferred way). + * The application is allowed to call free() on the array whenever it wishes. + * The pointers in the array are valid until meshlink_close() is called. + * @param nmemb A pointer to a variable holding the number of nodes with the same @a device class that are stored in the array. + * In case the @a nodes argument is not NULL, MeshLink might call realloc() on the array to change its size. + * The contents of this variable will be changed to reflect the new size of the array. + * + * @return A pointer to an array containing pointers to all known nodes of the given device class, or NULL in case of an error. + * If the @a nodes argument was not NULL, then the return value can either be the same value or a different value. + * If it is a new value, the old value of @a nodes should not be used anymore. + * If the new value is NULL, then the old array will have been freed by MeshLink. + */ +struct meshlink_node **meshlink_get_all_nodes_by_dev_class(struct meshlink_handle *mesh, dev_class_t devclass, struct meshlink_node **nodes, size_t *nmemb) __attribute__((__warn_unused_result__)); + +/// Get the list of all nodes by Submesh. +/** This function returns a list with handles for all the nodes that matches with the given @a Submesh. + * + * \memberof meshlink_submesh + * @param mesh A handle which represents an instance of MeshLink. + * @param submesh Submesh handle of the nodes for which the list has to be obtained. + * @param nodes A pointer to a previously allocated array of pointers to struct meshlink_node, or NULL in which case MeshLink will allocate a new array. + * The application can supply an array it allocated itself with malloc, or the return value from the previous call to this function (which is the preferred way). + * The application is allowed to call free() on the array whenever it wishes. + * The pointers in the array are valid until meshlink_close() is called. + * @param nmemb A pointer to a variable holding the number of nodes with the same @a device class that are stored in the array. + * In case the @a nodes argument is not NULL, MeshLink might call realloc() on the array to change its size. + * The contents of this variable will be changed to reflect the new size of the array. + * + * @return A pointer to an array containing pointers to all known nodes of the given Submesh, or NULL in case of an error. + * If the @a nodes argument was not NULL, then the return value can either be the same value or a different value. + * If it is a new value, the old value of @a nodes should not be used anymore. + * If the new value is NULL, then the old array will have been freed by MeshLink. + */ +struct meshlink_node **meshlink_get_all_nodes_by_submesh(struct meshlink_handle *mesh, struct meshlink_submesh *submesh, struct meshlink_node **nodes, size_t *nmemb) __attribute__((__warn_unused_result__)); + +/// Get the list of all nodes by time they were last reachable. +/** This function returns a list with handles for all the nodes whose last known reachability time overlaps with the given time range. + * If the range includes 0, it will count nodes that were never online. + * If start is bigger than end, the result will be inverted. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param start Start time. + * @param end End time. + * @param nodes A pointer to a previously allocated array of pointers to struct meshlink_node, or NULL in which case MeshLink will allocate a new array. + * The application can supply an array it allocated itself with malloc, or the return value from the previous call to this function (which is the preferred way). + * The application is allowed to call free() on the array whenever it wishes. + * The pointers in the array are valid until meshlink_close() is called. + * @param nmemb A pointer to a variable holding the number of nodes that were reachable within the period given by @a start and @a end. + * In case the @a nodes argument is not NULL, MeshLink might call realloc() on the array to change its size. + * The contents of this variable will be changed to reflect the new size of the array. + * + * @return A pointer to an array containing pointers to all known nodes that were reachable within the period given by @a start and @a end. + * If the @a nodes argument was not NULL, then the return value can either be the same value or a different value. + * If it is a new value, the old value of @a nodes should not be used anymore. + * If the new value is NULL, then the old array will have been freed by MeshLink. + */ +struct meshlink_node **meshlink_get_all_nodes_by_last_reachable(struct meshlink_handle *mesh, time_t start, time_t end, struct meshlink_node **nodes, size_t *nmemb) __attribute__((__warn_unused_result__)); + +/// Get the list of all nodes by blacklist status. +/** This function returns a list with handles for all the nodes who were either blacklisted or whitelisted. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param blacklisted If true, a list of blacklisted nodes will be returned, otherwise whitelisted nodes. + * @param nodes A pointer to a previously allocated array of pointers to struct meshlink_node, or NULL in which case MeshLink will allocate a new array. + * The application can supply an array it allocated itself with malloc, or the return value from the previous call to this function (which is the preferred way). + * The application is allowed to call free() on the array whenever it wishes. + * The pointers in the array are valid until meshlink_close() is called. + * @param nmemb A pointer to a variable holding the number of nodes that were reachable within the period given by @a start and @a end. + * In case the @a nodes argument is not NULL, MeshLink might call realloc() on the array to change its size. + * The contents of this variable will be changed to reflect the new size of the array. + * + * @return A pointer to an array containing pointers to all known nodes with the given blacklist status. + * If the @a nodes argument was not NULL, then the return value can either be the same value or a different value. + * If it is a new value, the old value of @a nodes should not be used anymore. + * If the new value is NULL, then the old array will have been freed by MeshLink. + */ +struct meshlink_node **meshlink_get_all_nodes_by_blacklisted(struct meshlink_handle *mesh, bool blacklisted, struct meshlink_node **nodes, size_t *nmemb) __attribute__((__warn_unused_result__)); + +/// Get the node's device class. +/** This function returns the device class of the given node. + * + * \memberof meshlink_node + * @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a struct meshlink_node describing the node. + * + * @return This function returns the device class of the @a node, or -1 in case of an error. + */ +dev_class_t meshlink_get_node_dev_class(struct meshlink_handle *mesh, struct meshlink_node *node) __attribute__((__warn_unused_result__)); + +/// Get the node's blacklist status. +/** This function returns the given node is blacklisted. + * + * \memberof meshlink_node + * @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a struct meshlink_node describing the node. + * + * @return This function returns true if the node is blacklisted, false otherwise. + */ +bool meshlink_get_node_blacklisted(struct meshlink_handle *mesh, struct meshlink_node *node) __attribute__((__warn_unused_result__)); + +/// Get the node's submesh handle. +/** This function returns the submesh handle of the given node. + * + * \memberof meshlink_node + * @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a struct meshlink_node describing the node. + * + * @return This function returns the submesh handle of the @a node, or NULL in case of an error. + */ +struct meshlink_submesh *meshlink_get_node_submesh(struct meshlink_handle *mesh, struct meshlink_node *node) __attribute__((__warn_unused_result__)); + +/// Get a node's reachability status. +/** This function returns the current reachability of a given node, and the times of the last state changes. + * If a given state change never happened, the time returned will be 0. + * + * \memberof meshlink_node + * @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a struct meshlink_node describing the node. + * @param last_reachable A pointer to a time_t variable that will be filled in with the last time the node became reachable. + * Pass NULL to not have anything written. + * @param last_unreachable A pointer to a time_t variable that will be filled in with the last time the node became unreachable. + * Pass NULL to not have anything written. + * + * @return This function returns true if the node is currently reachable, false otherwise. + */ +bool meshlink_get_node_reachability(struct meshlink_handle *mesh, struct meshlink_node *node, time_t *last_reachable, time_t *last_unreachable); + +/// Verify the signature generated by another node of a piece of data. +/** This function verifies the signature that another node generated for a piece of data. + * + * \memberof meshlink_node + * @param mesh A handle which represents an instance of MeshLink. + * @param source A pointer to a struct meshlink_node describing the source of the signature. + * @param data A pointer to a buffer containing the data to be verified. + * @param len The length of the data to be verified. + * @param signature A pointer to a buffer where the signature is stored. + * @param siglen A pointer to a variable holding the size of the signature buffer. + * The contents of the variable will be changed by meshlink_sign() to reflect the actual size of the signature. + * + * @return This function returns true if the signature is valid, false otherwise. + */ +bool meshlink_verify(struct meshlink_handle *mesh, struct meshlink_node *source, const void *data, size_t len, const void *signature, size_t siglen) __attribute__((__warn_unused_result__)); + +/// Set the canonical Address for a node. +/** This function sets the canonical Address for a node. + * This address is stored permanently until it is changed by another call to this function, + * unlike other addresses associated with a node, + * such as those added with meshlink_hint_address() or addresses discovered at runtime. + * + * If a canonical Address is set for the local node, + * it will be used for the hostname part of generated invitation URLs. + * If a canonical Address is set for a remote node, + * it is used exclusively for creating outgoing connections to that node. + * + * \memberof meshlink_node + * @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a struct meshlink_node describing the node. + * @param address A nul-terminated C string containing the address, which can be either in numeric format or a hostname. + * @param port A nul-terminated C string containing the port, which can be either in numeric or symbolic format. + * If it is NULL, the listening port's number will be used. + * + * @return This function returns true if the address was added, false otherwise. + */ +bool meshlink_set_canonical_address(struct meshlink_handle *mesh, struct meshlink_node *node, const char *address, const char *port) __attribute__((__warn_unused_result__)); + +/// Clear the canonical Address for a node. +/** This function clears the canonical Address for a node. + * + * \memberof meshlink_node + * @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a struct meshlink_node describing the node. + * + * @return This function returns true if the address was removed, false otherwise. + */ +bool meshlink_clear_canonical_address(struct meshlink_handle *mesh, struct meshlink_node *node) __attribute__((__warn_unused_result__)); + +/// Add an invitation address for the local node. +/** This function adds an address for the local node, which will be used only for invitation URLs. + * This address is not stored permanently. + * Multiple addresses can be added using multiple calls to this function. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param address A nul-terminated C string containing the address, which can be either in numeric format or a hostname. + * @param port A nul-terminated C string containing the port, which can be either in numeric or symbolic format. + * If it is NULL, the current listening port's number will be used. + * + * @return This function returns true if the address was added, false otherwise. + */ +bool meshlink_add_invitation_address(struct meshlink_handle *mesh, const char *address, const char *port) __attribute__((__warn_unused_result__)); + +/// Clears all invitation address for the local node. +/** This function removes all addresses added with meshlink_add_invitation_address(). + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + */ +void meshlink_clear_invitation_addresses(struct meshlink_handle *mesh); + +/// Add an Address for the local node. +/** This function adds an Address for the local node, which will be used for invitation URLs. + * @deprecated This function is deprecated, use meshlink_set_canonical_address() and/or meshlink_add_invitation_address(). + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param address A nul-terminated C string containing the address, which can be either in numeric format or a hostname. + * + * @return This function returns true if the address was added, false otherwise. + */ +bool meshlink_add_address(struct meshlink_handle *mesh, const char *address) __attribute__((__warn_unused_result__, __deprecated__("use meshlink_set_canonical_address() and/or meshlink_add_invitation_address() instead"))); + +/// Try to discover the external address for the local node. +/** This function performs tries to discover the local node's external address + * by contacting the meshlink.io server. If a reverse lookup of the address works, + * the FQDN associated with the address will be returned. + * + * Please note that this is function only returns a single address, + * even if the local node might have more than one external address. + * In that case, there is no control over which address will be selected. + * Also note that if you have a dynamic IP address, or are behind carrier-grade NAT, + * there is no guarantee that the external address will be valid for an extended period of time. + * + * This function is blocking. It can take several seconds before it returns. + * There is no guarantee it will be able to resolve the external address. + * Failures might be because by temporary network outages. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * + * @return This function returns a pointer to a C string containing the discovered external address, + * or NULL if there was an error looking up the address. + * After meshlink_get_external_address() returns, the application is free to overwrite or free this string. + */ +char *meshlink_get_external_address(struct meshlink_handle *mesh) __attribute__((__warn_unused_result__)); + +/// Try to discover the external address for the local node. +/** This function performs tries to discover the local node's external address + * by contacting the meshlink.io server. If a reverse lookup of the address works, + * the FQDN associated with the address will be returned. + * + * Please note that this is function only returns a single address, + * even if the local node might have more than one external address. + * In that case, there is no control over which address will be selected. + * Also note that if you have a dynamic IP address, or are behind carrier-grade NAT, + * there is no guarantee that the external address will be valid for an extended period of time. + * + * This function is blocking. It can take several seconds before it returns. + * There is no guarantee it will be able to resolve the external address. + * Failures might be because by temporary network outages. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param address_family The address family to check, for example AF_INET or AF_INET6. If AF_UNSPEC is given, + * this might return the external address for any working address family. + * + * @return This function returns a pointer to a C string containing the discovered external address, + * or NULL if there was an error looking up the address. + * After meshlink_get_external_address_for_family() returns, the application is free to overwrite or free this string. + */ +char *meshlink_get_external_address_for_family(struct meshlink_handle *mesh, int address_family) __attribute__((__warn_unused_result__)); + +/// Try to discover the local address for the local node. +/** This function performs tries to discover the address of the local interface used for outgoing connection. + * + * Please note that this is function only returns a single address, + * even if the interface might have more than one address. + * In that case, there is no control over which address will be selected. + * Also note that if you have a dynamic IP address, + * there is no guarantee that the local address will be valid for an extended period of time. + * + * This function will fail if it couldn't find a local address for the given address family. + * If hostname resolving is requested, this function may block for a few seconds. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param address_family The address family to check, for example AF_INET or AF_INET6. If AF_UNSPEC is given, + * this might return the local address for any working address family. + * + * @return This function returns a pointer to a C string containing the discovered local address, + * or NULL if there was an error looking up the address. + * After meshlink_get_local_address_for_family() returns, the application is free to overwrite or free this string. + */ +char *meshlink_get_local_address_for_family(struct meshlink_handle *mesh, int address_family) __attribute__((__warn_unused_result__)); + +/// Try to discover the external address for the local node, and add it to its list of addresses. +/** This function is equivalent to: + * + * meshlink_set_canonical_address(mesh, meshlink_get_self(mesh), meshlink_get_external_address(mesh), NULL); + * + * Read the description of meshlink_get_external_address() for the limitations of this function. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * + * @return This function returns true if the address was added, false otherwise. + */ +bool meshlink_add_external_address(struct meshlink_handle *mesh) __attribute__((__warn_unused_result__)); + +/// Get the network port used by the local node. +/** This function returns the network port that the local node is listening on. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * + * @return This function returns the port number, or -1 in case of an error. + */ +int meshlink_get_port(struct meshlink_handle *mesh) __attribute__((__warn_unused_result__)); + +/// Set the network port used by the local node. +/** This function sets the network port that the local node is listening on. + * It may only be called when the mesh is not running. + * If unsure, call meshlink_stop() before calling this function. + * Also note that if your node is already part of a mesh with other nodes, + * that the other nodes may no longer be able to initiate connections to the local node, + * since they will try to connect to the previously configured port. + * + * Note that if a canonical address has been set for the local node, + * you might need to call meshlink_set_canonical_address() again to ensure it includes the new port number. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param port The port number to listen on. This must be between 0 and 65535. + * If the port is set to 0, then MeshLink will listen on a port + * that is randomly assigned by the operating system every time meshlink_open() is called. + * + * @return This function returns true if the port was successfully changed + * to the desired port, false otherwise. If it returns false, there + * is no guarantee that MeshLink is listening on the old port. + */ + +bool meshlink_set_port(struct meshlink_handle *mesh, int port) __attribute__((__warn_unused_result__)); + +/// Set the timeout for invitations. +/** This function sets the timeout for invitations. + * Note that timeouts are only checked at the time a node tries to join using an invitation. + * The default timeout for invitations is 1 week. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param timeout The timeout for invitations in seconds. + */ +void meshlink_set_invitation_timeout(struct meshlink_handle *mesh, int timeout); + +/// Invite another node into the mesh. +/** This function generates an invitation that can be used by another node to join the same mesh as the local node. + * The generated invitation is a string containing a URL. + * This URL should be passed by the application to the invitee in a way that no eavesdroppers can see the URL. + * The URL can only be used once, after the user has joined the mesh the URL is no longer valid. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param submesh A handle which represents an instance of SubMesh. + * @param name A nul-terminated C string containing the name that the invitee will be allowed to use in the mesh. + * After this function returns, the application is free to overwrite or free @a name. + * @param flags A bitwise-or'd combination of flags that controls how the URL is generated. + * + * @return This function returns a nul-terminated C string that contains the invitation URL, or NULL in case of an error. + * The application should call free() after it has finished using the URL. + */ +char *meshlink_invite_ex(struct meshlink_handle *mesh, struct meshlink_submesh *submesh, const char *name, uint32_t flags) __attribute__((__warn_unused_result__)); + +/// Invite another node into the mesh. +/** This function generates an invitation that can be used by another node to join the same mesh as the local node. + * The generated invitation is a string containing a URL. + * This URL should be passed by the application to the invitee in a way that no eavesdroppers can see the URL. + * The URL can only be used once, after the user has joined the mesh the URL is no longer valid. + * + * Calling this function is equal to callen meshlink_invite_ex() with flags set to 0. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param submesh A handle which represents an instance of SubMesh. + * @param name A nul-terminated C string containing the name that the invitee will be allowed to use in the mesh. + * After this function returns, the application is free to overwrite or free @a name. + * + * @return This function returns a nul-terminated C string that contains the invitation URL, or NULL in case of an error. + * The application should call free() after it has finished using the URL. + */ +char *meshlink_invite(struct meshlink_handle *mesh, struct meshlink_submesh *submesh, const char *name) __attribute__((__warn_unused_result__)); + +/// Use an invitation to join a mesh. +/** This function allows the local node to join an existing mesh using an invitation URL generated by another node. + * An invitation can only be used if the local node has never connected to other nodes before. + * After a successfully accepted invitation, the name of the local node may have changed. + * + * This function may only be called on a mesh that has not been started yet and which is not already part of an existing mesh. + * It is not valid to call this function when the storage policy set to MESHLINK_STORAGE_DISABLED. + * + * This function is blocking. It can take several seconds before it returns. + * There is no guarantee it will perform a successful join. + * Failures might be caused by temporary network outages, or by the invitation having expired. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param invitation A nul-terminated C string containing the invitation URL. + * After this function returns, the application is free to overwrite or free @a invitation. + * + * @return This function returns true if the local node joined the mesh it was invited to, false otherwise. + */ +bool meshlink_join(struct meshlink_handle *mesh, const char *invitation) __attribute__((__warn_unused_result__)); + +/// Export the local node's key and addresses. +/** This function generates a string that contains the local node's public key and one or more IP addresses. + * The application can pass it in some way to another node, which can then import it, + * granting the local node access to the other node's mesh. + * The exported data does not contain any secret keys, it is therefore safe to transmit this data unencrypted over public networks. + * + * Note that to create a working connection between two nodes, both must call meshink_export() and both must meshlink_import() each other's data. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * + * @return This function returns a nul-terminated C string that contains the exported key and addresses, or NULL in case of an error. + * The application should call free() after it has finished using this string. + */ +char *meshlink_export(struct meshlink_handle *mesh) __attribute__((__warn_unused_result__)); + +/// Import another node's key and addresses. +/** This function accepts a string containing the exported public key and addresses of another node. + * By importing this data, the local node grants the other node access to its mesh. + * The application should make sure that the data it imports is really coming from the node it wants to import, + * + * Note that to create a working connection between two nodes, both must call meshink_export() and both must meshlink_import() each other's data. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param data A nul-terminated C string containing the other node's exported key and addresses. + * After this function returns, the application is free to overwrite or free @a data. + * + * @return This function returns true if the data was valid and the other node has been granted access to the mesh, false otherwise. + */ +bool meshlink_import(struct meshlink_handle *mesh, const char *data) __attribute__((__warn_unused_result__)); + +/// Forget any information about a node. +/** This function allows the local node to forget any information it has about a node, + * and if possible will remove any data it has stored on disk about the node. + * + * Any open channels to this node must be closed before calling this function. + * + * After this call returns, the node handle is invalid and may no longer be used, regardless + * of the return value of this call. + * + * Note that this function does not prevent MeshLink from actually forgetting about a node, + * or re-learning information about a node at a later point in time. It is merely a hint that + * the application does not care about this node anymore and that any resources kept could be + * cleaned up. + * + * \memberof meshlink_node + * @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a struct meshlink_node describing the node to be forgotten. + * + * @return This function returns true if all currently known data about the node has been forgotten, false otherwise. + */ +bool meshlink_forget_node(struct meshlink_handle *mesh, struct meshlink_node *node); + +/// Blacklist a node from the mesh. +/** This function causes the local node to blacklist another node. + * The local node will drop any existing connections to that node, + * and will not send data to it nor accept any data received from it any more. + * + * \memberof meshlink_node + * @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a struct meshlink_node describing the node to be blacklisted. + * + * @return This function returns true if the node has been blacklisted, false otherwise. + */ +bool meshlink_blacklist(struct meshlink_handle *mesh, struct meshlink_node *node) __attribute__((__warn_unused_result__)); + +/// Blacklist a node from the mesh by name. +/** This function causes the local node to blacklist another node by name. + * The local node will drop any existing connections to that node, + * and will not send data to it nor accept any data received from it any more. + * + * If no node by the given name is known, it is created. + * + * \memberof meshlink_node + * @param mesh A handle which represents an instance of MeshLink. + * @param name The name of the node to blacklist. + * + * @return This function returns true if the node has been blacklisted, false otherwise. + */ +bool meshlink_blacklist_by_name(struct meshlink_handle *mesh, const char *name) __attribute__((__warn_unused_result__)); + +/// Whitelist a node on the mesh. +/** This function causes the local node to whitelist a previously blacklisted node. + * The local node will allow connections to and from that node, + * and will send data to it and accept any data received from it. + * + * \memberof meshlink_node + * @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a struct meshlink_node describing the node to be whitelisted. + * + * @return This function returns true if the node has been whitelisted, false otherwise. + */ +bool meshlink_whitelist(struct meshlink_handle *mesh, struct meshlink_node *node) __attribute__((__warn_unused_result__)); + +/// Whitelist a node on the mesh by name. +/** This function causes the local node to whitelist a node by name. + * The local node will allow connections to and from that node, + * and will send data to it and accept any data received from it. + * + * If no node by the given name is known, it is created. + * This is useful if new nodes are blacklisted by default. + * + * \memberof meshlink_node + * @param mesh A handle which represents an instance of MeshLink. + * @param name The name of the node to whitelist. + * + * @return This function returns true if the node has been whitelisted, false otherwise. + */ +bool meshlink_whitelist_by_name(struct meshlink_handle *mesh, const char *name) __attribute__((__warn_unused_result__)); + +/// Set whether new nodes are blacklisted by default. +/** This function sets the blacklist behaviour for newly discovered nodes. + * If set to true, new nodes will be automatically blacklisted. + * If set to false, which is the default, new nodes are automatically whitelisted. + * The whitelist/blacklist status of a node may be changed afterwards with the + * meshlink_whitelist() and meshlink_blacklist() functions. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param blacklist True if new nodes are to be blacklisted, false if whitelisted. + */ +void meshlink_set_default_blacklist(struct meshlink_handle *mesh, bool blacklist); + +/// A callback for listening for incoming channels. +/** This function is called whenever a remote node wants to open a channel to the local node. + * This callback should only make a decision whether to accept or reject this channel. + * The accept callback should be set to get a handle to the actual channel. + * + * The callback is run in MeshLink's own thread. + * It is therefore important that the callback return quickly and uses apprioriate methods (queues, pipes, locking, etc.) + * to hand any data over to the application's thread. + * + * @param mesh A handle which represents an instance of MeshLink. + * @param node A handle for the node that wants to open a channel. + * @param port The port number the peer wishes to connect to. + * + * @return This function should return true if the application accepts the incoming channel, false otherwise. + */ +typedef bool (*meshlink_channel_listen_cb_t)(struct meshlink_handle *mesh, struct meshlink_node *node, uint16_t port); + +/// A callback for accepting incoming channels. +/** This function is called whenever a remote node has opened a channel to the local node. + * + * The callback is run in MeshLink's own thread. + * It is therefore important that the callback return quickly and uses apprioriate methods (queues, pipes, locking, etc.) + * to hand any data over to the application's thread. + * + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the incoming channel. + * If the application accepts the incoming channel by returning true, + * then this handle is valid until meshlink_channel_close() is called on it. + * If the application rejects the incoming channel by returning false, + * then this handle is invalid after the callback returns + * (the callback does not need to call meshlink_channel_close() itself in this case). + * @param port The port number the peer wishes to connect to. + * @param data A pointer to a buffer containing data already received, or NULL in case no data has been received yet. (Not yet used.) + * The pointer is only valid during the lifetime of the callback. + * The callback should mempcy() the data if it needs to be available outside the callback. + * @param len The length of the data, or 0 in case no data has been received yet. (Not yet used.) + * + * @return This function should return true if the application accepts the incoming channel, false otherwise. + * If returning false, the channel is invalid and may not be used anymore. + */ +typedef bool (*meshlink_channel_accept_cb_t)(struct meshlink_handle *mesh, struct meshlink_channel *channel, uint16_t port, const void *data, size_t len); + +/// A callback for receiving data from a channel. +/** This function is called whenever data is received from a remote node on a channel. + * + * This function is also called in case the channel has been closed by the remote node, or when the channel is terminated abnormally. + * In both cases, @a data will be NULL and @a len will be 0, and meshlink_errno will be set. + * In any case, the @a channel handle will still be valid until the application calls meshlink_close(). + * + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + * @param data A pointer to a buffer containing data sent by the source, or NULL in case of an error. + * The pointer is only valid during the lifetime of the callback. + * The callback should mempcy() the data if it needs to be available outside the callback. + * @param len The length of the data, or 0 in case of an error. + */ +typedef void (*meshlink_channel_receive_cb_t)(struct meshlink_handle *mesh, struct meshlink_channel *channel, const void *data, size_t len); + +/// A callback informing the application when data can be sent on a channel. +/** This function is called whenever there is enough free buffer space so a call to meshlink_channel_send() will succeed. + * + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + * @param len The maximum amount of data that is guaranteed to be accepted by meshlink_channel_send(), + * or 0 in case of an error. + */ +typedef void (*meshlink_channel_poll_cb_t)(struct meshlink_handle *mesh, struct meshlink_channel *channel, size_t len); + +/// Set the listen callback. +/** This functions sets the callback that is called whenever another node wants to open a channel to the local node. + * The callback is run in MeshLink's own thread. + * It is therefore important that the callback uses apprioriate methods (queues, pipes, locking, etc.) + * to hand the data over to the application's thread. + * The callback should also not block itself and return as quickly as possible. + * + * If no listen or accept callbacks are set, incoming channels are rejected. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param cb A pointer to the function which will be called when another node want to open a channel. + * If a NULL pointer is given, the callback will be disabled. + */ +void meshlink_set_channel_listen_cb(struct meshlink_handle *mesh, meshlink_channel_listen_cb_t cb); + +/// Set the accept callback. +/** This functions sets the callback that is called whenever a remote node has opened a channel to the local node. + * The callback is run in MeshLink's own thread. + * It is therefore important that the callback uses apprioriate methods (queues, pipes, locking, etc.) + * to hand the data over to the application's thread. + * The callback should also not block itself and return as quickly as possible. + * + * If no listen or accept callbacks are set, incoming channels are rejected. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param cb A pointer to the function which will be called when a new channel has been opened by a remote node. + * If a NULL pointer is given, the callback will be disabled. + */ +void meshlink_set_channel_accept_cb(struct meshlink_handle *mesh, meshlink_channel_accept_cb_t cb); + +/// Set the receive callback. +/** This functions sets the callback that is called whenever another node sends data to the local node. + * The callback is run in MeshLink's own thread. + * It is therefore important that the callback uses apprioriate methods (queues, pipes, locking, etc.) + * to hand the data over to the application's thread. + * The callback should also not block itself and return as quickly as possible. + * + * \memberof meshlink_channel + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + * @param cb A pointer to the function which will be called when another node sends data to the local node. + * If a NULL pointer is given, the callback will be disabled and incoming data is ignored. + */ +void meshlink_set_channel_receive_cb(struct meshlink_handle *mesh, struct meshlink_channel *channel, meshlink_channel_receive_cb_t cb); + +/// Set the poll callback. +/** This functions sets the callback that is called whenever data can be sent to another node. + * The callback is run in MeshLink's own thread. + * It is therefore important that the callback uses apprioriate methods (queues, pipes, locking, etc.) + * to pass data to or from the application's thread. + * The callback should also not block itself and return as quickly as possible. + * + * \memberof meshlink_channel + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + * @param cb A pointer to the function which will be called when data can be sent to another node. + * If a NULL pointer is given, the callback will be disabled. + */ +void meshlink_set_channel_poll_cb(struct meshlink_handle *mesh, struct meshlink_channel *channel, meshlink_channel_poll_cb_t cb); + +/// Set the send buffer size of a channel. +/** This function sets the desired size of the send buffer. + * The default size is 128 kB. + * + * \memberof meshlink_channel + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + * @param size The desired size for the send buffer. + */ +void meshlink_set_channel_sndbuf(struct meshlink_handle *mesh, struct meshlink_channel *channel, size_t size); + +/// Set the receive buffer size of a channel. +/** This function sets the desired size of the receive buffer. + * The default size is 128 kB. + * + * \memberof meshlink_channel + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + * @param size The desired size for the send buffer. + */ +void meshlink_set_channel_rcvbuf(struct meshlink_handle *mesh, struct meshlink_channel *channel, size_t size); + +/// Set the send buffer storage of a channel. +/** This function provides MeshLink with a send buffer allocated by the application. + * The buffer must be valid until the channel is closed or until this function is called again with a NULL pointer for @a buf. + * + * \memberof meshlink_channel + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + * @param buf A pointer to the start of the buffer. + * If a NULL pointer is given, MeshLink will use its own internal buffer again. + * @param size The size of the buffer. + */ +void meshlink_set_channel_sndbuf_storage(struct meshlink_handle *mesh, struct meshlink_channel *channel, void *buf, size_t size); + +/// Set the receive buffer storage of a channel. +/** This function provides MeshLink with a receive buffer allocated by the application. + * The buffer must be valid until the channel is closed or until this function is called again with a NULL pointer for @a buf. + * + * \memberof meshlink_channel + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + * @param buf A pointer to the start of the buffer. + * If a NULL pointer is given, MeshLink will use its own internal buffer again. + * @param size The size of the buffer. + */ +void meshlink_set_channel_rcvbuf_storage(struct meshlink_handle *mesh, struct meshlink_channel *channel, void *buf, size_t size); + +/// Set the flags of a channel. +/** This function allows changing some of the channel flags. + * Currently only MESHLINK_CHANNEL_NO_PARTIAL and MESHLINK_CHANNEL_DROP_LATE are supported, other flags are ignored. + * These flags only affect the local side of the channel with the peer. + * The changes take effect immediately. + * + * \memberof meshlink_channel + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + * @param flags A bitwise-or'd combination of flags that set the semantics for this channel. + */ +void meshlink_set_channel_flags(struct meshlink_handle *mesh, struct meshlink_channel *channel, uint32_t flags); + +/// Open a reliable stream channel to another node. +/** This function is called whenever a remote node wants to open a channel to the local node. + * The application then has to decide whether to accept or reject this channel. + * + * This function returns a pointer to a struct meshlink_channel that will be allocated by MeshLink. + * When the application does no longer need to use this channel, it must call meshlink_close() + * to free its resources. + * + * \memberof meshlink_node + * @param mesh A handle which represents an instance of MeshLink. + * @param node The node to which this channel is being initiated. + * @param port The port number the peer wishes to connect to. + * @param cb A pointer to the function which will be called when the remote node sends data to the local node. + * The pointer may be NULL, in which case incoming data is ignored. + * @param data A pointer to a buffer containing data to already queue for sending, or NULL if there is no data to send. + * After meshlink_send() returns, the application is free to overwrite or free this buffer. + * If len is 0, the data pointer is copied into the channel's priv member. + * @param len The length of the data, or 0 if there is no data to send. + * @param flags A bitwise-or'd combination of flags that set the semantics for this channel. + * + * @return A handle for the channel, or NULL in case of an error. + * The handle is valid until meshlink_channel_close() is called. + */ +struct meshlink_channel *meshlink_channel_open_ex(struct meshlink_handle *mesh, struct meshlink_node *node, uint16_t port, meshlink_channel_receive_cb_t cb, const void *data, size_t len, uint32_t flags) __attribute__((__warn_unused_result__)); + +/// Open a reliable stream channel to another node. +/** This function is called whenever a remote node wants to open a channel to the local node. + * The application then has to decide whether to accept or reject this channel. + * + * This function returns a pointer to a struct meshlink_channel that will be allocated by MeshLink. + * When the application does no longer need to use this channel, it must call meshlink_close() + * to free its resources. + * + * Calling this function is equivalent to calling meshlink_channel_open_ex() + * with the flags set to MESHLINK_CHANNEL_TCP. + * + * \memberof meshlink_node + * @param mesh A handle which represents an instance of MeshLink. + * @param node The node to which this channel is being initiated. + * @param port The port number the peer wishes to connect to. + * @param cb A pointer to the function which will be called when the remote node sends data to the local node. + * The pointer may be NULL, in which case incoming data is ignored. + * @param data A pointer to a buffer containing data to already queue for sending, or NULL if there is no data to send. + * After meshlink_send() returns, the application is free to overwrite or free this buffer. + * @param len The length of the data, or 0 if there is no data to send. + * If len is 0, the data pointer is copied into the channel's priv member. + * + * @return A handle for the channel, or NULL in case of an error. + * The handle is valid until meshlink_channel_close() is called. + */ +struct meshlink_channel *meshlink_channel_open(struct meshlink_handle *mesh, struct meshlink_node *node, uint16_t port, meshlink_channel_receive_cb_t cb, const void *data, size_t len) __attribute__((__warn_unused_result__)); + +/// Partially close a reliable stream channel. +/** This shuts down the read or write side of a channel, or both, without closing the handle. + * It can be used to inform the remote node that the local node has finished sending all data on the channel, + * but still allows waiting for incoming data from the remote node. + * + * Shutting down the receive direction is also possible, and is equivalent to setting the receive callback to NULL. + * + * \memberof meshlink_channel + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + * @param direction Must be one of SHUT_RD, SHUT_WR or SHUT_RDWR, otherwise this call will not have any affect. + */ +void meshlink_channel_shutdown(struct meshlink_handle *mesh, struct meshlink_channel *channel, int direction); + +/// Close a reliable stream channel. +/** This informs the remote node that the local node has finished sending all data on the channel. + * It also causes the local node to stop accepting incoming data from the remote node. + * Afterwards, the channel handle is invalid and must not be used any more. + * + * It is allowed to call this function at any time on a valid handle, even inside callback functions. + * If called with a valid handle, this function always succeeds, otherwise the result is undefined. + * + * \memberof meshlink_channel + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + */ +void meshlink_channel_close(struct meshlink_handle *mesh, struct meshlink_channel *channel); + +/// Abort a reliable stream channel. +/** This aborts a channel. + * Data that was in the send and receive buffers is dropped, so potentially there is some data that + * was sent on this channel that will not be received by the peer. + * Afterwards, the channel handle is invalid and must not be used any more. + * + * It is allowed to call this function at any time on a valid handle, even inside callback functions. + * If called with a valid handle, this function always succeeds, otherwise the result is undefined. + * + * \memberof meshlink_channel + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + */ +void meshlink_channel_abort(struct meshlink_handle *mesh, struct meshlink_channel *channel); + +/// Transmit data on a channel +/** This queues data to send to the remote node. + * + * \memberof meshlink_channel + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + * @param data A pointer to a buffer containing data sent by the source, or NULL if there is no data to send. + * After meshlink_send() returns, the application is free to overwrite or free this buffer. + * @param len The length of the data, or 0 if there is no data to send. + * + * @return The amount of data that was queued, which can be less than len, or a negative value in case of an error. + * If MESHLINK_CHANNEL_NO_PARTIAL is set, then the result will either be len, + * 0 if the buffer is currently too full, or -1 if len is too big even for an empty buffer. + */ +ssize_t meshlink_channel_send(struct meshlink_handle *mesh, struct meshlink_channel *channel, const void *data, size_t len) __attribute__((__warn_unused_result__)); + +/// A callback for cleaning up buffers submitted for asynchronous I/O. +/** This callbacks signals that MeshLink has finished using this buffer. + * The ownership of the buffer is now back into the application's hands. + * + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel which used this buffer. + * @param data A pointer to a buffer containing the enqueued data. + * @param len The length of the buffer. + * @param priv A private pointer which was set by the application when submitting the buffer. + */ +typedef void (*meshlink_aio_cb_t)(struct meshlink_handle *mesh, struct meshlink_channel *channel, const void *data, size_t len, void *priv); + +/// A callback for asynchronous I/O to and from filedescriptors. +/** This callbacks signals that MeshLink has finished using this filedescriptor. + * + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel which used this filedescriptor. + * @param fd The filedescriptor that was used. + * @param len The length of the data that was successfully sent or received. + * @param priv A private pointer which was set by the application when submitting the buffer. + */ +typedef void (*meshlink_aio_fd_cb_t)(struct meshlink_handle *mesh, struct meshlink_channel *channel, int fd, size_t len, void *priv); + +/// Transmit data on a channel asynchronously +/** This registers a buffer that will be used to send data to the remote node. + * Multiple buffers can be registered, in which case data will be sent in the order the buffers were registered. + * While there are still buffers with unsent data, the poll callback will not be called. + * + * \memberof meshlink_channel + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + * @param data A pointer to a buffer containing data sent by the source, or NULL if there is no data to send. + * After meshlink_channel_aio_send() returns, the buffer may not be modified or freed by the application + * until the callback routine is called. + * @param len The length of the data, or 0 if there is no data to send. + * @param cb A pointer to the function which will be called when MeshLink has finished using the buffer. + * @param priv A private pointer which is passed unchanged to the callback. + * + * @return True if the buffer was enqueued, false otherwise. + */ +bool meshlink_channel_aio_send(struct meshlink_handle *mesh, struct meshlink_channel *channel, const void *data, size_t len, meshlink_aio_cb_t cb, void *priv) __attribute__((__warn_unused_result__)); + +/// Transmit data on a channel asynchronously from a filedescriptor +/** This will read up to the specified length number of bytes from the given filedescriptor, and send it over the channel. + * The callback may be returned early if there is an error reading from the filedescriptor. + * While there is still with unsent data, the poll callback will not be called. + * + * \memberof meshlink_channel + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + * @param fd A file descriptor from which data will be read. + * @param len The length of the data, or 0 if there is no data to send. + * @param cb A pointer to the function which will be called when MeshLink has finished using the filedescriptor. + * @param priv A private pointer which is passed unchanged to the callback. + * + * @return True if the buffer was enqueued, false otherwise. + */ +bool meshlink_channel_aio_fd_send(struct meshlink_handle *mesh, struct meshlink_channel *channel, int fd, size_t len, meshlink_aio_fd_cb_t cb, void *priv) __attribute__((__warn_unused_result__)); + +/// Receive data on a channel asynchronously +/** This registers a buffer that will be filled with incoming channel data. + * Multiple buffers can be registered, in which case data will be received in the order the buffers were registered. + * While there are still buffers that have not been filled, the receive callback will not be called. + * + * \memberof meshlink_channel + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + * @param data A pointer to a buffer that will be filled with incoming data. + * After meshlink_channel_aio_receive() returns, the buffer may not be modified or freed by the application + * until the callback routine is called. + * @param len The length of the data. + * @param cb A pointer to the function which will be called when MeshLink has finished using the buffer. + * @param priv A private pointer which is passed unchanged to the callback. + * + * @return True if the buffer was enqueued, false otherwise. + */ +bool meshlink_channel_aio_receive(struct meshlink_handle *mesh, struct meshlink_channel *channel, const void *data, size_t len, meshlink_aio_cb_t cb, void *priv) __attribute__((__warn_unused_result__)); + +/// Receive data on a channel asynchronously and send it to a filedescriptor +/** This will read up to the specified length number of bytes from the channel, and send it to the filedescriptor. + * The callback may be returned early if there is an error writing to the filedescriptor. + * While there is still unread data, the receive callback will not be called. + * + * \memberof meshlink_channel + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + * @param fd A file descriptor to which data will be written. + * @param len The length of the data. + * @param cb A pointer to the function which will be called when MeshLink has finished using the filedescriptor. + * @param priv A private pointer which was set by the application when submitting the buffer. + * + * @return True if the buffer was enqueued, false otherwise. + */ +bool meshlink_channel_aio_fd_receive(struct meshlink_handle *mesh, struct meshlink_channel *channel, int fd, size_t len, meshlink_aio_fd_cb_t cb, void *priv) __attribute__((__warn_unused_result__)); + +/// Get channel flags. +/** This returns the flags used when opening this channel. + * + * \memberof meshlink_channel + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + * + * @return The flags set for this channel. + */ +uint32_t meshlink_channel_get_flags(struct meshlink_handle *mesh, struct meshlink_channel *channel) __attribute__((__warn_unused_result__)); + +/// Get the amount of bytes in the send buffer. +/** This returns the amount of bytes in the send buffer. + * These bytes have not been received by the peer yet. + * + * \memberof meshlink_channel + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + * + * @return The amount of un-ACKed bytes in the send buffer. + */ +size_t meshlink_channel_get_sendq(struct meshlink_handle *mesh, struct meshlink_channel *channel) __attribute__((__warn_unused_result__)); + +/// Get the amount of bytes in the receive buffer. +/** This returns the amount of bytes in the receive buffer. + * These bytes have not been processed by the application yet. + * + * \memberof meshlink_channel + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + * + * @return The amount of bytes in the receive buffer. + */ +size_t meshlink_channel_get_recvq(struct meshlink_handle *mesh, struct meshlink_channel *channel) __attribute__((__warn_unused_result__)); + +/// Get the maximum segment size of a channel. +/** This returns the amount of bytes that can be sent at once for channels with UDP semantics. + * + * \memberof meshlink_channel + * @param mesh A handle which represents an instance of MeshLink. + * @param channel A handle for the channel. + * + * @return The amount of bytes in the receive buffer. + */ +size_t meshlink_channel_get_mss(struct meshlink_handle *mesh, struct meshlink_channel *channel) __attribute__((__warn_unused_result__)); + +/// Set the connection timeout used for channels to the given node. +/** This sets the timeout after which unresponsive channels will be reported as closed. + * The timeout is set for all current and future channels to the given node. + * + * \memberof meshlink_node + * @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a struct meshlink_node describing the node to set the channel connection timeout for. + * @param timeout The timeout in seconds after which unresponsive channels will be reported as closed. + * The default is 60 seconds. + */ +void meshlink_set_node_channel_timeout(struct meshlink_handle *mesh, struct meshlink_node *node, int timeout); + +/// Hint that a hostname may be found at an address +/** This function indicates to meshlink that the given hostname is likely found + * at the given IP address and port. + * + * \memberof meshlink_node + * @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a struct meshlink_node describing the node to add the address hint for. + * @param addr The IP address and port which should be tried for the + * given hostname. The caller is free to overwrite or free + * this memory once meshlink returns. + */ +void meshlink_hint_address(struct meshlink_handle *mesh, struct meshlink_node *node, const struct sockaddr *addr); + +/// Enable or disable zeroconf discovery of local peers +/** This controls whether zeroconf discovery using the Catta library will be + * enabled to search for peers on the local network. By default, it is enabled. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param enable Set to true to enable discovery, false to disable. + */ +void meshlink_enable_discovery(struct meshlink_handle *mesh, bool enable); + +/// Inform MeshLink that the local network configuration might have changed +/** This is intended to be used when there is no way for MeshLink to get notifications of local network changes. + * It forces MeshLink to scan all network interfaces for changes in up/down status and new/removed addresses, + * and will immediately check if all connections to other nodes are still alive. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + */ +void meshlink_hint_network_change(struct meshlink_handle *mesh); + +/// Performs key rotation for an encrypted storage +/** This rotates the (master) key for an encrypted storage and discards the old key + * if the call succeeded. This is an atomic call. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param key A pointer to the new key used to encrypt storage. + * @param keylen The length of the new key in bytes. + * + * @return This function returns true if the key rotation for the encrypted storage succeeds, false otherwise. + */ +bool meshlink_encrypted_key_rotate(struct meshlink_handle *mesh, const void *key, size_t keylen) __attribute__((__warn_unused_result__)); + +/// Set device class timeouts +/** This sets the ping interval and timeout for a given device class. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param devclass The device class to update + * @param pinginterval The interval between keepalive packets, in seconds. The default is 60. + * @param pingtimeout The required time within which a peer should respond, in seconds. The default is 5. + * The timeout must be smaller than the interval. + */ +void meshlink_set_dev_class_timeouts(struct meshlink_handle *mesh, dev_class_t devclass, int pinginterval, int pingtimeout); + +/// Set device class fast retry period +/** This sets the fast retry period for a given device class. + * During this period after the last time the mesh becomes unreachable, connections are tried once a second. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param devclass The device class to update + * @param fast_retry_period The period during which fast connection retries are done. The default is 0. + */ +void meshlink_set_dev_class_fast_retry_period(struct meshlink_handle *mesh, dev_class_t devclass, int fast_retry_period); + +/// Set device class maximum timeout +/** This sets the maximum timeout for outgoing connection retries for a given device class. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param devclass The device class to update + * @param maxtimeout The maximum timeout between reconnection attempts, in seconds. The default is 900. + */ +void meshlink_set_dev_class_maxtimeout(struct meshlink_handle *mesh, dev_class_t devclass, int maxtimeout); + +/// Reset all connection timers +/** This resets all timers related to connections, causing pending outgoing connections to be retried immediately. + * It also sends keepalive packets on all active connections immediately. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + */ +void meshlink_reset_timers(struct meshlink_handle *mesh); + +/// Set which order invitations are committed +/** This determines in which order configuration files are written to disk during an invitation. + * By default, the invitee saves the configuration to disk first, then the inviter. + * By calling this function with @a inviter_commits_first set to true, the order is reversed. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param inviter_commits_first If true, then the node that invited a peer will commit data to disk first. + */ +void meshlink_set_inviter_commits_first(struct meshlink_handle *mesh, bool inviter_commits_first); + +/// Set the URL used to discover the host's external address +/** For generating invitation URLs, MeshLink can look up the externally visible address of the local node. + * It does so by querying an external service. By default, this is http://meshlink.io/host.cgi. + * Only URLs starting with http:// are supported. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param url The URL to use for external address queries, or NULL to revert back to the default URL. + */ +void meshlink_set_external_address_discovery_url(struct meshlink_handle *mesh, const char *url); + +/// Set the scheduling granularity of the application +/** This should be set to the effective scheduling granularity for the application. + * This depends on the scheduling granularity of the operating system, the application's + * process priority and whether it is running as realtime or not. + * The default value is 10000 (10 milliseconds). + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param granularity The scheduling granularity of the application in microseconds. + */ +void meshlink_set_scheduling_granularity(struct meshlink_handle *mesh, long granularity); + +/// Sets the storage policy used by MeshLink +/** This sets the policy MeshLink uses when it has new information about nodes. + * By default, all udpates will be stored to disk (unless an ephemeral instance has been opened). + * Setting the policy to MESHLINK_STORAGE_KEYS_ONLY, only updates that contain new keys for nodes + * are stored, as well as blacklist/whitelist settings. + * By setting the policy to MESHLINK_STORAGE_DISABLED, no updates will be stored. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param policy The storage policy to use. + */ +void meshlink_set_storage_policy(struct meshlink_handle *mesh, meshlink_storage_policy_t policy); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/meshlink.sym b/src/meshlink.sym new file mode 100644 index 0000000..7c4de62 --- /dev/null +++ b/src/meshlink.sym @@ -0,0 +1,113 @@ +__emutls_v.meshlink_errno +devtool_export_json_all_edges_state +devtool_get_all_edges +devtool_get_all_submeshes +devtool_get_node_status +devtool_keyrotate_probe +devtool_open_in_netns +devtool_set_meta_status_cb +devtool_set_inviter_commits_first +devtool_trybind_probe +meshlink_add_address +meshlink_add_external_address +meshlink_add_invitation_address +meshlink_blacklist +meshlink_blacklist_by_name +meshlink_channel_abort +meshlink_channel_aio_fd_receive +meshlink_channel_aio_fd_send +meshlink_channel_aio_receive +meshlink_channel_aio_send +meshlink_channel_close +meshlink_channel_get_flags +meshlink_channel_get_mss +meshlink_channel_get_recvq +meshlink_channel_get_sendq +meshlink_channel_open +meshlink_channel_open_ex +meshlink_channel_send +meshlink_channel_shutdown +meshlink_clear_canonical_address +meshlink_clear_invitation_addresses +meshlink_close +meshlink_destroy +meshlink_destroy_ex +meshlink_enable_discovery +meshlink_encrypted_key_rotate +meshlink_errno +meshlink_export +meshlink_forget_node +meshlink_get_all_nodes +meshlink_get_all_nodes_by_blacklisted +meshlink_get_all_nodes_by_dev_class +meshlink_get_all_nodes_by_last_reachable +meshlink_get_all_nodes_by_submesh +meshlink_get_external_address +meshlink_get_external_address_for_family +meshlink_get_fingerprint +meshlink_get_local_address_for_family +meshlink_get_node +meshlink_get_node_blacklisted +meshlink_get_node_dev_class +meshlink_get_node_reachability +meshlink_get_node_submesh +meshlink_get_pmtu +meshlink_get_port +meshlink_get_self +meshlink_get_submesh +meshlink_hint_address +meshlink_hint_network_change +meshlink_import +meshlink_invite +meshlink_invite_ex +meshlink_join +meshlink_main_loop +meshlink_open +meshlink_open_encrypted +meshlink_open_ephemeral +meshlink_open_ex +meshlink_open_params_free +meshlink_open_params_init +meshlink_open_params_set_lock_filename +meshlink_open_params_set_netns +meshlink_open_params_set_storage_key +meshlink_open_params_set_storage_policy +meshlink_reset_timers +meshlink_send +meshlink_set_blacklisted_cb +meshlink_set_canonical_address +meshlink_set_channel_accept_cb +meshlink_set_channel_flags +meshlink_set_channel_listen_cb +meshlink_set_channel_poll_cb +meshlink_set_channel_rcvbuf +meshlink_set_channel_rcvbuf_storage +meshlink_set_channel_receive_cb +meshlink_set_channel_sndbuf +meshlink_set_channel_sndbuf_storage +meshlink_set_connection_try_cb +meshlink_set_default_blacklist +meshlink_set_dev_class_fast_retry_period +meshlink_set_dev_class_maxtimeout +meshlink_set_dev_class_timeouts +meshlink_set_error_cb +meshlink_set_external_address_discovery_url +meshlink_set_invitation_timeout +meshlink_set_inviter_commits_first +meshlink_set_log_cb +meshlink_set_node_channel_timeout +meshlink_set_node_duplicate_cb +meshlink_set_node_pmtu_cb +meshlink_set_node_status_cb +meshlink_set_port +meshlink_set_receive_cb +meshlink_set_scheduling_granularity +meshlink_sign +meshlink_start +meshlink_stop +meshlink_set_storage_policy +meshlink_strerror +meshlink_submesh_open +meshlink_verify +meshlink_whitelist +meshlink_whitelist_by_name diff --git a/src/meshlink_internal.h b/src/meshlink_internal.h new file mode 100644 index 0000000..2618601 --- /dev/null +++ b/src/meshlink_internal.h @@ -0,0 +1,262 @@ +#ifndef MESHLINK_INTERNAL_H +#define MESHLINK_INTERNAL_H + +/* + meshlink_internal.h -- Internal parts of the public API. + Copyright (C) 2014-2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef MESHLINK_H +#error You must not include both meshlink.h and meshlink_internal.h! +#endif + +#include "system.h" + +#include "event.h" +#include "hash.h" +#include "meshlink.h" +#include "meshlink_queue.h" +#include "sockaddr.h" +#include "sptps.h" +#include "xoshiro.h" + +#include + +#define MAXSOCKETS 4 /* Probably overkill... */ + +static const char meshlink_invitation_label[] = "MeshLink invitation"; +static const char meshlink_tcp_label[] = "MeshLink TCP"; +static const char meshlink_udp_label[] = "MeshLink UDP"; + +#define MESHLINK_CONFIG_VERSION 2 +#define MESHLINK_INVITATION_VERSION 2 + +typedef struct listen_socket_t { + struct io_t tcp; + struct io_t udp; + sockaddr_t sa; + sockaddr_t broadcast_sa; +} listen_socket_t; + +struct meshlink_open_params { + char *confbase; + char *lock_filename; + char *appname; + char *name; + dev_class_t devclass; + + int netns; + + const void *key; + size_t keylen; + meshlink_storage_policy_t storage_policy; +}; + +/// Device class traits +typedef struct { + int pinginterval; + int pingtimeout; + int fast_retry_period; + int maxtimeout; + unsigned int min_connects; + unsigned int max_connects; + int edge_weight; +} dev_class_traits_t; + +/// A handle for an instance of MeshLink. +struct meshlink_handle { + // public members + char *name; + void *priv; + + // private members + pthread_mutex_t mutex; + event_loop_t loop; + struct node_t *self; + meshlink_log_cb_t log_cb; + meshlink_log_level_t log_level; + void *packet; + + // The most important network-related members come first + int reachable; + int listen_sockets; + listen_socket_t listen_socket[MAXSOCKETS]; + + meshlink_receive_cb_t receive_cb; + meshlink_queue_t outpacketqueue; + signal_t datafromapp; + + hash_t *node_udp_cache; + + struct splay_tree_t *nodes; + struct splay_tree_t *edges; + + struct list_t *connections; + struct list_t *outgoings; + struct list_t *submeshes; + + // Meta-connection-related members + struct splay_tree_t *past_request_tree; + timeout_t past_request_timeout; + + int connection_burst; + int contradicting_add_edge; + int contradicting_del_edge; + int sleeptime; + time_t connection_burst_time; + time_t last_hard_try; + time_t last_unreachable; + timeout_t pingtimer; + timeout_t periodictimer; + + struct connection_t *everyone; + uint64_t prng_state[4]; + uint32_t session_id; + + int next_pit; + int pits[10]; + + // Infrequently used callbacks + meshlink_node_status_cb_t node_status_cb; + meshlink_node_status_cb_t meta_status_cb; + meshlink_node_pmtu_cb_t node_pmtu_cb; + meshlink_channel_listen_cb_t channel_listen_cb; + meshlink_channel_accept_cb_t channel_accept_cb; + meshlink_node_duplicate_cb_t node_duplicate_cb; + meshlink_connection_try_cb_t connection_try_cb; + meshlink_error_cb_t error_cb; + meshlink_blacklisted_cb_t blacklisted_cb; + + // Mesh parameters + char *appname; + char *myport; + + struct ecdsa *private_key; + struct ecdsa *invitation_key; + + dev_class_t devclass; + + int invitation_timeout; + int udp_choice; + + dev_class_traits_t dev_class_traits[DEV_CLASS_COUNT]; + + int netns; + + bool default_blacklist; + bool inviter_commits_first; + + // Configuration + char *confbase; + FILE *lockfile; + void *config_key; + char *external_address_url; + struct list_t *invitation_addresses; + meshlink_storage_policy_t storage_policy; + + // Thread management + pthread_t thread; + pthread_cond_t cond; + bool threadstarted; + + // mDNS discovery + struct { + bool enabled; + io_t pfroute_io; + int *ifaces; + struct discovery_address *addresses; + int iface_count; + int address_count; + io_t sockets[2]; + time_t last_update; +#ifdef __APPLE__ + pthread_t thread; + void *runloop; +#endif + } discovery; + + // ADNS + pthread_t adns_thread; + pthread_cond_t adns_cond; + meshlink_queue_t adns_queue; + meshlink_queue_t adns_done_queue; + signal_t adns_signal; +}; + +/// A handle for a MeshLink node. +struct meshlink_node { + const char *name; + void *priv; +}; + +/// A handle for a node Sub-Mesh. +struct meshlink_submesh { + const char *name; + void *priv; +}; + +/// An AIO buffer. +typedef struct meshlink_aio_buffer { + const void *data; + int fd; + size_t len; + size_t done; + union { + meshlink_aio_cb_t buffer; + meshlink_aio_fd_cb_t fd; + } cb; + void *priv; + struct meshlink_aio_buffer *next; +} meshlink_aio_buffer_t; + +/// A channel. +struct meshlink_channel { + struct node_t *node; + void *priv; + bool in_callback; + + struct utcp_connection *c; + meshlink_aio_buffer_t *aio_send; + meshlink_aio_buffer_t *aio_receive; + meshlink_channel_receive_cb_t receive_cb; + meshlink_channel_poll_cb_t poll_cb; +}; + +/// Header for data packets routed between nodes +typedef struct meshlink_packethdr { + uint8_t destination[16]; + uint8_t source[16]; +} __attribute__((__packed__)) meshlink_packethdr_t; + +void meshlink_send_from_queue(event_loop_t *loop, void *mesh); +void update_node_status(meshlink_handle_t *mesh, struct node_t *n); +void update_node_pmtu(meshlink_handle_t *mesh, struct node_t *n); +extern meshlink_log_level_t global_log_level; +extern meshlink_log_cb_t global_log_cb; +void handle_duplicate_node(meshlink_handle_t *mesh, struct node_t *n); +void handle_network_change(meshlink_handle_t *mesh, bool online); +void call_error_cb(meshlink_handle_t *mesh, meshlink_errno_t meshlink_errno); + +/// Per-instance PRNG +static inline int prng(meshlink_handle_t *mesh, uint64_t max) { + return xoshiro(mesh->prng_state) % max; +} + +/// Fudge value of ~0.1 seconds, in microseconds. +static const unsigned int TIMER_FUDGE = 0x8000000; + +#endif diff --git a/src/meshlink_queue.h b/src/meshlink_queue.h new file mode 100644 index 0000000..665c4d6 --- /dev/null +++ b/src/meshlink_queue.h @@ -0,0 +1,120 @@ +#ifndef MESHLINK_QUEUE_H +#define MESHLINK_QUEUE_H + +/* + queue.h -- Thread-safe queue + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include +#include +#include +#include + +typedef struct meshlink_queue { + struct meshlink_queue_item *head; + struct meshlink_queue_item *tail; + pthread_mutex_t mutex; +} meshlink_queue_t; + +typedef struct meshlink_queue_item { + void *data; + struct meshlink_queue_item *next; +} meshlink_queue_item_t; + +static inline void meshlink_queue_init(meshlink_queue_t *queue) { + queue->head = NULL; + queue->tail = NULL; + pthread_mutex_init(&queue->mutex, NULL); +} + +static inline void meshlink_queue_exit(meshlink_queue_t *queue) { + pthread_mutex_destroy(&queue->mutex); +} + +static inline __attribute__((__warn_unused_result__)) bool meshlink_queue_push(meshlink_queue_t *queue, void *data) { + meshlink_queue_item_t *item = malloc(sizeof(*item)); + + if(!item) { + return false; + } + + item->data = data; + item->next = NULL; + + if(pthread_mutex_lock(&queue->mutex) != 0) { + abort(); + } + + if(!queue->tail) { + queue->head = queue->tail = item; + } else { + queue->tail = queue->tail->next = item; + } + + pthread_mutex_unlock(&queue->mutex); + return true; +} + +static inline __attribute__((__warn_unused_result__)) void *meshlink_queue_pop(meshlink_queue_t *queue) { + meshlink_queue_item_t *item; + + if(pthread_mutex_lock(&queue->mutex) != 0) { + abort(); + } + + if((item = queue->head)) { + queue->head = item->next; + + if(!queue->head) { + queue->tail = NULL; + } + } + + pthread_mutex_unlock(&queue->mutex); + + void *data = item ? item->data : NULL; + free(item); + return data; +} + +static inline __attribute__((__warn_unused_result__)) void *meshlink_queue_pop_cond(meshlink_queue_t *queue, pthread_cond_t *cond) { + meshlink_queue_item_t *item; + + if(pthread_mutex_lock(&queue->mutex) != 0) { + abort(); + } + + while(!queue->head) { + pthread_cond_wait(cond, &queue->mutex); + } + + item = queue->head; + queue->head = item->next; + + if(!queue->head) { + queue->tail = NULL; + } + + pthread_mutex_unlock(&queue->mutex); + + void *data = item->data; + free(item); + return data; +} + +#endif diff --git a/src/meta.c b/src/meta.c new file mode 100644 index 0000000..94aab98 --- /dev/null +++ b/src/meta.c @@ -0,0 +1,175 @@ +/* + meta.c -- handle the meta communication + Copyright (C) 2014-2017 Guus Sliepen , + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "connection.h" +#include "logger.h" +#include "meshlink_internal.h" +#include "meta.h" +#include "net.h" +#include "protocol.h" +#include "utils.h" +#include "xalloc.h" + +bool send_meta_sptps(void *handle, uint8_t type, const void *buffer, size_t length) { + (void)type; + + assert(handle); + assert(buffer); + assert(length); + + connection_t *c = handle; + meshlink_handle_t *mesh = c->mesh; + + buffer_add(&c->outbuf, (const char *)buffer, length); + io_set(&mesh->loop, &c->io, IO_READ | IO_WRITE); + + return true; +} + +bool send_meta(meshlink_handle_t *mesh, connection_t *c, const char *buffer, int length) { + assert(c); + assert(buffer); + assert(length); + + logger(mesh, MESHLINK_DEBUG, "Sending %d bytes of metadata to %s", length, c->name); + + if(c->allow_request == ID) { + buffer_add(&c->outbuf, buffer, length); + io_set(&mesh->loop, &c->io, IO_READ | IO_WRITE); + return true; + } + + return sptps_send_record(&c->sptps, 0, buffer, length); +} + +void broadcast_meta(meshlink_handle_t *mesh, connection_t *from, const char *buffer, int length) { + assert(buffer); + assert(length); + + for list_each(connection_t, c, mesh->connections) + if(c != from && c->status.active) { + send_meta(mesh, c, buffer, length); + } +} + +void broadcast_submesh_meta(meshlink_handle_t *mesh, connection_t *from, const submesh_t *s, const char *buffer, int length) { + assert(buffer); + assert(length); + + for list_each(connection_t, c, mesh->connections) + if(c != from && c->status.active) { + if(c->node && submesh_allows_node(s, c->node)) { + send_meta(mesh, c, buffer, length); + } + } +} + +bool receive_meta_sptps(void *handle, uint8_t type, const void *data, uint16_t length) { + assert(handle); + assert(!length || data); + + connection_t *c = handle; + meshlink_handle_t *mesh = c->mesh; + char *request = (char *)data; + + if(!c) { + logger(mesh, MESHLINK_ERROR, "receive_meta_sptps() called with NULL pointer!"); + abort(); + } + + if(type == SPTPS_HANDSHAKE) { + if(c->allow_request == ACK) { + return send_ack(mesh, c); + } else { + return true; + } + } + + if(!request) { + return true; + } + + /* Are we receiving a TCPpacket? */ + + if(c->tcplen) { + abort(); // TODO: get rid of tcplen altogether + } + + /* Change newline to null byte, just like non-SPTPS requests */ + + if(request[length - 1] == '\n') { + request[length - 1] = 0; + } + + /* Otherwise we are waiting for a request */ + + return receive_request(mesh, c, request); +} + +bool receive_meta(meshlink_handle_t *mesh, connection_t *c) { + int inlen; + char inbuf[MAXBUFSIZE]; + + inlen = recv(c->socket, inbuf, sizeof(inbuf), 0); + + if(inlen <= 0) { + if(!inlen || !errno) { + logger(mesh, MESHLINK_INFO, "Connection closed by %s", c->name); + } else if(sockwouldblock(sockerrno)) { + return true; + } else { + logger(mesh, MESHLINK_ERROR, "Metadata socket read error for %s: %s", c->name, sockstrerror(sockerrno)); + } + + return false; + } + + logger(mesh, MESHLINK_DEBUG, "Received %d bytes of metadata from %s", inlen, c->name); + + if(c->allow_request == ID) { + buffer_add(&c->inbuf, inbuf, inlen); + + char *request = buffer_readline(&c->inbuf); + + if(request) { + if(!receive_request(mesh, c, request) || c->allow_request == ID) { + return false; + } + + int left = c->inbuf.len - c->inbuf.offset; + + if(left > 0) { + return sptps_receive_data(&c->sptps, buffer_read(&c->inbuf, left), left); + } else { + return true; + } + } + + if(c->inbuf.len >= sizeof(inbuf)) { + logger(mesh, MESHLINK_ERROR, "Input buffer full for %s", c->name); + return false; + } else { + return true; + } + } + + return sptps_receive_data(&c->sptps, inbuf, inlen); +} diff --git a/src/meta.h b/src/meta.h new file mode 100644 index 0000000..039fa34 --- /dev/null +++ b/src/meta.h @@ -0,0 +1,33 @@ +#ifndef MESHLINK_META_H +#define MESHLINK_META_H + +/* + meta.h -- header for meta.c + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "connection.h" + +bool send_meta(struct meshlink_handle *mesh, struct connection_t *, const char *, int); +bool send_meta_sptps(void *, uint8_t, const void *, size_t); +bool receive_meta_sptps(void *, uint8_t, const void *, uint16_t); +void broadcast_meta(struct meshlink_handle *mesh, struct connection_t *, const char *, int); +extern void broadcast_submesh_meta(struct meshlink_handle *mesh, connection_t *from, const submesh_t *s, + const char *buffer, int length); +bool receive_meta(struct meshlink_handle *mesh, struct connection_t *) __attribute__((__warn_unused_result__)); + +#endif diff --git a/src/net.c b/src/net.c new file mode 100644 index 0000000..35cfc65 --- /dev/null +++ b/src/net.c @@ -0,0 +1,746 @@ +/* + net.c -- most of the network code + Copyright (C) 2014-2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "utils.h" +#include "conf.h" +#include "connection.h" +#include "devtools.h" +#include "graph.h" +#include "logger.h" +#include "meshlink_internal.h" +#include "meta.h" +#include "net.h" +#include "netutl.h" +#include "protocol.h" +#include "sptps.h" +#include "xalloc.h" + +#include + +#if !defined(min) +static inline int min(int a, int b) { + return a < b ? a : b; +} +#endif + +static const int default_timeout = 5; +static const int default_interval = 60; + +/* + Terminate a connection: + - Mark it as inactive + - Remove the edge representing this connection + - Kill it with fire + - Check if we need to retry making an outgoing connection +*/ +void terminate_connection(meshlink_handle_t *mesh, connection_t *c, bool report) { + if(c->status.active) { + logger(mesh, MESHLINK_INFO, "Closing connection with %s", c->name); + } + + if(c->node && c->node->connection == c) { + if(c->status.active && mesh->meta_status_cb) { + mesh->meta_status_cb(mesh, (meshlink_node_t *)c->node, false); + } + + c->node->connection = NULL; + } + + c->status.active = false; + + if(c->edge) { + if(report) { + send_del_edge(mesh, mesh->everyone, c->edge, 0); + } + + edge_del(mesh, c->edge); + c->edge = NULL; + + /* Run MST and SSSP algorithms */ + + graph(mesh); + + /* If the node is not reachable anymore but we remember it had an edge to us, clean it up */ + + if(report && c->node && !c->node->status.reachable) { + edge_t *e; + e = lookup_edge(c->node, mesh->self); + + if(e) { + send_del_edge(mesh, mesh->everyone, e, 0); + edge_del(mesh, e); + } + } + } + + outgoing_t *outgoing = c->outgoing; + connection_del(mesh, c); + + /* Check if this was our outgoing connection */ + + if(outgoing) { + do_outgoing_connection(mesh, outgoing); + } +} + +/* + Check if the other end is active. + If we have sent packets, but didn't receive any, + then possibly the other end is dead. We send a + PING request over the meta connection. If the other + end does not reply in time, we consider them dead + and close the connection. +*/ +static void timeout_handler(event_loop_t *loop, void *data) { + assert(data); + + meshlink_handle_t *mesh = loop->data; + logger(mesh, MESHLINK_DEBUG, "timeout_handler()"); + + for list_each(connection_t, c, mesh->connections) { + int pingtimeout = c->node ? mesh->dev_class_traits[c->node->devclass].pingtimeout : default_timeout; + int pinginterval = c->node ? mesh->dev_class_traits[c->node->devclass].pinginterval : default_interval; + + if(c->outgoing && !c->status.active && c->outgoing->timeout < 5) { + pingtimeout = 1; + } + + // Also make sure that if outstanding key requests for the UDP counterpart of a connection has timed out, we restart it. + if(c->node) { + if(c->node->status.waitingforkey && c->node->last_req_key + pingtimeout < mesh->loop.now.tv_sec) { + send_req_key(mesh, c->node); + } + } + + if(c->status.active && c->last_key_renewal + 3600 < mesh->loop.now.tv_sec) { + devtool_sptps_renewal_probe((meshlink_node_t *)c->node); + + if(!sptps_force_kex(&c->sptps)) { + logger(mesh, MESHLINK_ERROR, "SPTPS key renewal for connection with %s failed", c->name); + terminate_connection(mesh, c, true); + continue; + } else { + c->last_key_renewal = mesh->loop.now.tv_sec; + } + } + + if(c->last_ping_time + pingtimeout < mesh->loop.now.tv_sec) { + if(c->status.active) { + if(c->status.pinged) { + logger(mesh, MESHLINK_INFO, "%s didn't respond to PING in %ld seconds", c->name, (long)mesh->loop.now.tv_sec - c->last_ping_time); + } else if(c->last_ping_time + pinginterval <= mesh->loop.now.tv_sec) { + send_ping(mesh, c); + continue; + } else { + continue; + } + } else { + if(c->status.connecting) { + logger(mesh, MESHLINK_WARNING, "Timeout while connecting to %s", c->name); + } else { + logger(mesh, MESHLINK_WARNING, "Timeout from %s during authentication", c->name); + } + } + + terminate_connection(mesh, c, c->status.active); + } + } + + timeout_set(&mesh->loop, data, &(struct timespec) { + 1, prng(mesh, TIMER_FUDGE) + }); +} + +// devclass asc, last_successfull_connection desc +static int node_compare_devclass_asc_lsc_desc(const void *a, const void *b) { + const node_t *na = a, *nb = b; + + if(na->devclass < nb->devclass) { + return -1; + } + + if(na->devclass > nb->devclass) { + return 1; + } + + if(na->last_successfull_connection == nb->last_successfull_connection) { + return 0; + } + + if(na->last_successfull_connection == 0 || na->last_successfull_connection > nb->last_successfull_connection) { + return -1; + } + + if(nb->last_successfull_connection == 0 || na->last_successfull_connection < nb->last_successfull_connection) { + return 1; + } + + if(na < nb) { + return -1; + } + + if(na > nb) { + return 1; + } + + return 0; +} + +// last_successfull_connection desc +static int node_compare_lsc_desc(const void *a, const void *b) { + const node_t *na = a, *nb = b; + + if(na->last_successfull_connection == nb->last_successfull_connection) { + return 0; + } + + if(na->last_successfull_connection == 0 || na->last_successfull_connection > nb->last_successfull_connection) { + return -1; + } + + if(nb->last_successfull_connection == 0 || na->last_successfull_connection < nb->last_successfull_connection) { + return 1; + } + + if(na < nb) { + return -1; + } + + if(na > nb) { + return 1; + } + + return 0; +} + +// devclass desc +static int node_compare_devclass_desc(const void *a, const void *b) { + const node_t *na = a, *nb = b; + + if(na->devclass < nb->devclass) { + return -1; + } + + if(na->devclass > nb->devclass) { + return 1; + } + + if(na < nb) { + return -1; + } + + if(na > nb) { + return 1; + } + + return 0; +} + + +/* + +autoconnect() +{ + timeout = 5 + + // find the best one for initial connect + + if cur < min + newcon = + first from nodes + where dclass <= my.dclass and !connection and (timestamp - last_retry) > retry_timeout + order by dclass asc, last_connection desc + if newcon + timeout = 0 + goto connect + + + // find better nodes to connect to: in case we have less than min connections within [BACKBONE, i] and there are nodes which we are not connected to within the range + + if min <= cur < max + j = 0 + for i = BACKBONE to my.dclass + j += count(from connections where node.dclass = i) + if j < min + newcon = + first from nodes + where dclass = i and !connection and (timestamp - last_retry) > retry_timeout + order by last_connection desc + if newcon + goto connect + else + break + + + // heal partitions + + if min <= cur < max + newcon = + first from nodes + where dclass <= my.dclass and !reachable and (timestamp - last_retry) > retry_timeout + order by dclass asc, last_connection desc + if newcon + goto connect + + + // connect + +connect: + if newcon + connect newcon + + + // disconnect outgoing connections in case we have more than min connections within [BACKBONE, i] and there are nodes which we are connected to within the range [i, PORTABLE] + + if min < cur <= max + j = 0 + for i = BACKBONE to my.dclass + j += count(from connections where node.dclass = i) + if min < j + delcon = + first from nodes + where dclass >= i and outgoing_connection + order by dclass desc + if disconnect + goto disconnect + else + break + + + // disconnect connections in case we have more than enough connections + + if max < cur + delcon = + first from nodes + where outgoing_connection + order by dclass desc + goto disconnect + + // disconnect + +disconnect + if delcon + disconnect delcon + + + // next iteration + next (timeout, autoconnect) + +} + +*/ + + +static void periodic_handler(event_loop_t *loop, void *data) { + meshlink_handle_t *mesh = loop->data; + + /* Check if there are too many contradicting ADD_EDGE and DEL_EDGE messages. + This usually only happens when another node has the same Name as this node. + If so, sleep for a short while to prevent a storm of contradicting messages. + */ + + if(mesh->contradicting_del_edge > 100 && mesh->contradicting_add_edge > 100) { + logger(mesh, MESHLINK_WARNING, "Possible node with same Name as us! Sleeping %d seconds.", mesh->sleeptime); + struct timespec ts = {mesh->sleeptime, 0}; + nanosleep(&ts, NULL); + mesh->sleeptime *= 2; + + if(mesh->sleeptime < 0) { + mesh->sleeptime = 3600; + } + } else { + mesh->sleeptime /= 2; + + if(mesh->sleeptime < 10) { + mesh->sleeptime = 10; + } + } + + mesh->contradicting_add_edge = 0; + mesh->contradicting_del_edge = 0; + + int timeout = default_timeout; + + /* Check if we need to make or break connections. */ + + if(mesh->nodes->count > 1) { + + logger(mesh, MESHLINK_DEBUG, "--- autoconnect begin ---"); + + int retry_timeout = min(mesh->nodes->count * default_timeout, 60); + + logger(mesh, MESHLINK_DEBUG, "* devclass = %d", mesh->devclass); + logger(mesh, MESHLINK_DEBUG, "* nodes = %d", mesh->nodes->count); + logger(mesh, MESHLINK_DEBUG, "* retry_timeout = %d", retry_timeout); + + + // connect disconnect nodes + + node_t *connect_to = NULL; + node_t *disconnect_from = NULL; + + + // get cur_connects + + unsigned int cur_connects = 0; + + for list_each(connection_t, c, mesh->connections) { + if(c->status.active) { + cur_connects += 1; + } + } + + logger(mesh, MESHLINK_DEBUG, "* cur_connects = %d", cur_connects); + logger(mesh, MESHLINK_DEBUG, "* outgoings = %d", mesh->outgoings->count); + + // get min_connects and max_connects + + unsigned int min_connects = mesh->dev_class_traits[mesh->devclass].min_connects; + unsigned int max_connects = mesh->dev_class_traits[mesh->devclass].max_connects; + + logger(mesh, MESHLINK_DEBUG, "* min_connects = %d", min_connects); + logger(mesh, MESHLINK_DEBUG, "* max_connects = %d", max_connects); + + // find the best one for initial connect + + if(cur_connects < min_connects) { + splay_tree_t *nodes = splay_alloc_tree(node_compare_devclass_asc_lsc_desc, NULL); + + for splay_each(node_t, n, mesh->nodes) { + logger(mesh, MESHLINK_DEBUG, "* %s->devclass = %d", n->name, n->devclass); + + if(n != mesh->self && n->devclass <= mesh->devclass && !n->connection && !n->status.blacklisted && (n->last_connect_try == 0 || (mesh->loop.now.tv_sec - n->last_connect_try) > retry_timeout)) { + splay_insert(nodes, n); + } + } + + if(nodes->head) { + //timeout = 0; + connect_to = (node_t *)nodes->head->data; + + logger(mesh, MESHLINK_DEBUG, "* found best one for initial connect: %s", connect_to->name); + } else { + logger(mesh, MESHLINK_DEBUG, "* could not find node for initial connect"); + } + + splay_delete_tree(nodes); + } + + + // find better nodes to connect to + + if(!connect_to && min_connects <= cur_connects && cur_connects < max_connects) { + unsigned int connects = 0; + + for(dev_class_t devclass = 0; devclass <= mesh->devclass; ++devclass) { + for list_each(connection_t, c, mesh->connections) { + if(c->status.active && c->node && c->node->devclass == devclass) { + connects += 1; + } + } + + if(connects < min_connects) { + splay_tree_t *nodes = splay_alloc_tree(node_compare_lsc_desc, NULL); + + for splay_each(node_t, n, mesh->nodes) { + if(n != mesh->self && n->devclass == devclass && !n->connection && !n->status.blacklisted && (n->last_connect_try == 0 || (mesh->loop.now.tv_sec - n->last_connect_try) > retry_timeout)) { + splay_insert(nodes, n); + } + } + + if(nodes->head) { + logger(mesh, MESHLINK_DEBUG, "* found better node"); + connect_to = (node_t *)nodes->head->data; + + splay_delete_tree(nodes); + break; + } + + splay_delete_tree(nodes); + } else { + break; + } + } + + if(!connect_to) { + logger(mesh, MESHLINK_DEBUG, "* could not find better nodes"); + } + } + + + // heal partitions + + if(!connect_to && min_connects <= cur_connects && cur_connects < max_connects) { + splay_tree_t *nodes = splay_alloc_tree(node_compare_devclass_asc_lsc_desc, NULL); + + for splay_each(node_t, n, mesh->nodes) { + if(n != mesh->self && n->devclass <= mesh->devclass && !n->status.reachable && !n->status.blacklisted && (n->last_connect_try == 0 || (mesh->loop.now.tv_sec - n->last_connect_try) > retry_timeout)) { + splay_insert(nodes, n); + } + } + + if(nodes->head) { + logger(mesh, MESHLINK_DEBUG, "* try to heal partition"); + connect_to = (node_t *)nodes->head->data; + } else { + logger(mesh, MESHLINK_DEBUG, "* could not find nodes for partition healing"); + } + + splay_delete_tree(nodes); + } + + + // perform connect + + if(connect_to && !connect_to->connection) { + connect_to->last_connect_try = mesh->loop.now.tv_sec; + logger(mesh, MESHLINK_DEBUG, "Autoconnect trying to connect to %s", connect_to->name); + + /* check if there is already a connection attempt to this node */ + bool skip = false; + + for list_each(outgoing_t, outgoing, mesh->outgoings) { + if(outgoing->node == connect_to) { + logger(mesh, MESHLINK_DEBUG, "* skip autoconnect since it is an outgoing connection already"); + skip = true; + break; + } + } + + if(!connect_to->status.reachable && !node_read_public_key(mesh, connect_to)) { + logger(mesh, MESHLINK_DEBUG, "* skip autoconnect since we don't know this node's public key"); + skip = true; + } + + if(!skip) { + logger(mesh, MESHLINK_DEBUG, "Autoconnecting to %s", connect_to->name); + outgoing_t *outgoing = xzalloc(sizeof(outgoing_t)); + outgoing->node = connect_to; + list_insert_tail(mesh->outgoings, outgoing); + setup_outgoing_connection(mesh, outgoing); + } + } + + + // disconnect suboptimal outgoing connections + + if(min_connects < cur_connects /*&& cur_connects <= max_connects*/) { + unsigned int connects = 0; + + for(dev_class_t devclass = 0; devclass <= mesh->devclass; ++devclass) { + for list_each(connection_t, c, mesh->connections) { + if(c->status.active && c->node && c->node->devclass == devclass) { + connects += 1; + } + } + + if(min_connects < connects) { + splay_tree_t *nodes = splay_alloc_tree(node_compare_devclass_desc, NULL); + + for list_each(connection_t, c, mesh->connections) { + if(c->outgoing && c->node && c->node->devclass >= devclass) { + splay_insert(nodes, c->node); + } + } + + if(nodes->head) { + logger(mesh, MESHLINK_DEBUG, "* disconnect suboptimal outgoing connection"); + disconnect_from = (node_t *)nodes->head->data; + } + + splay_delete_tree(nodes); + break; + } + } + + if(!disconnect_from) { + logger(mesh, MESHLINK_DEBUG, "* no suboptimal outgoing connections"); + } + } + + + // disconnect connections (too many connections) + + if(!disconnect_from && max_connects < cur_connects) { + splay_tree_t *nodes = splay_alloc_tree(node_compare_devclass_desc, NULL); + + for list_each(connection_t, c, mesh->connections) { + if(c->status.active && c->node) { + splay_insert(nodes, c->node); + } + } + + if(nodes->head) { + logger(mesh, MESHLINK_DEBUG, "* disconnect connection (too many connections)"); + + //timeout = 0; + disconnect_from = (node_t *)nodes->head->data; + } else { + logger(mesh, MESHLINK_DEBUG, "* no node we want to disconnect, even though we have too many connections"); + } + + splay_delete_tree(nodes); + } + + + // perform disconnect + + if(disconnect_from && disconnect_from->connection) { + logger(mesh, MESHLINK_DEBUG, "Autodisconnecting from %s", disconnect_from->connection->name); + list_delete(mesh->outgoings, disconnect_from->connection->outgoing); + disconnect_from->connection->outgoing = NULL; + terminate_connection(mesh, disconnect_from->connection, disconnect_from->connection->status.active); + } + + // reduce timeout if we don't have enough connections + outgoings + if(cur_connects + mesh->outgoings->count < 3) { + timeout = 1; + } + + // done! + + logger(mesh, MESHLINK_DEBUG, "--- autoconnect end ---"); + } + + for splay_each(node_t, n, mesh->nodes) { + if(n->status.dirty) { + if(!node_write_config(mesh, n, false)) { + logger(mesh, MESHLINK_DEBUG, "Could not update %s", n->name); + } + } + + if(n->status.reachable && n->status.validkey && n->last_req_key + 3600 < mesh->loop.now.tv_sec) { + logger(mesh, MESHLINK_DEBUG, "SPTPS key renewal for node %s", n->name); + devtool_sptps_renewal_probe((meshlink_node_t *)n); + + if(!sptps_force_kex(&n->sptps)) { + logger(mesh, MESHLINK_ERROR, "SPTPS key renewal for node %s failed", n->name); + n->status.validkey = false; + sptps_stop(&n->sptps); + n->status.waitingforkey = false; + n->last_req_key = -3600; + } else { + n->last_req_key = mesh->loop.now.tv_sec; + } + } + } + + timeout_set(&mesh->loop, data, &(struct timespec) { + timeout, prng(mesh, TIMER_FUDGE) + }); +} + +void handle_meta_connection_data(meshlink_handle_t *mesh, connection_t *c) { + if(!receive_meta(mesh, c)) { + terminate_connection(mesh, c, c->status.active); + return; + } +} + +void retry(meshlink_handle_t *mesh) { + /* Reset the reconnection timers for all outgoing connections */ + for list_each(outgoing_t, outgoing, mesh->outgoings) { + outgoing->timeout = 0; + + if(outgoing->ev.cb) { + timeout_set(&mesh->loop, &outgoing->ev, &(struct timespec) { + 0, 0 + }); + } + } + + /* For active connections, check if their addresses are still valid. + * If yes, reset their ping timers, otherwise terminate them. */ + for list_each(connection_t, c, mesh->connections) { + if(!c->status.active) { + continue; + } + + if(!c->status.pinged) { + c->last_ping_time = -3600; + } + + sockaddr_t sa; + socklen_t salen = sizeof(sa); + + if(getsockname(c->socket, &sa.sa, &salen)) { + continue; + } + + switch(sa.sa.sa_family) { + case AF_INET: + sa.in.sin_port = 0; + break; + + case AF_INET6: + sa.in6.sin6_port = 0; + break; + + default: + continue; + } + + int sock = socket(sa.sa.sa_family, SOCK_STREAM, IPPROTO_TCP); + + if(sock == -1) { + continue; + } + + if(bind(sock, &sa.sa, salen) && errno == EADDRNOTAVAIL) { + logger(mesh, MESHLINK_DEBUG, "Local address for connection to %s no longer valid, terminating", c->name); + terminate_connection(mesh, c, c->status.active); + } + + closesocket(sock); + } + + /* Kick the ping timeout handler */ + if(mesh->pingtimer.cb) { + timeout_set(&mesh->loop, &mesh->pingtimer, &(struct timespec) { + 0, 0 + }); + } +} + +/* + this is where it all happens... +*/ +void main_loop(meshlink_handle_t *mesh) { + timeout_add(&mesh->loop, &mesh->pingtimer, timeout_handler, &mesh->pingtimer, &(struct timespec) { + 1, prng(mesh, TIMER_FUDGE) + }); + timeout_add(&mesh->loop, &mesh->periodictimer, periodic_handler, &mesh->periodictimer, &(struct timespec) { + 0, 0 + }); + + //Add signal handler + mesh->datafromapp.signum = 0; + signal_add(&mesh->loop, &mesh->datafromapp, meshlink_send_from_queue, mesh, mesh->datafromapp.signum); + + if(!event_loop_run(&mesh->loop, mesh)) { + logger(mesh, MESHLINK_ERROR, "Error while waiting for input: %s", strerror(errno)); + call_error_cb(mesh, MESHLINK_ENETWORK); + } + + signal_del(&mesh->loop, &mesh->datafromapp); + timeout_del(&mesh->loop, &mesh->periodictimer); + timeout_del(&mesh->loop, &mesh->pingtimer); +} diff --git a/src/net.h b/src/net.h new file mode 100644 index 0000000..9994fac --- /dev/null +++ b/src/net.h @@ -0,0 +1,120 @@ +#ifndef MESHLINK_NET_H +#define MESHLINK_NET_H + +/* + net.h -- header for net.c + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "event.h" +#include "sockaddr.h" + +/* Maximum size of SPTPS payload */ +#ifdef ENABLE_JUMBOGRAMS +#define MTU 8951 /* 9000 bytes payload - 28 bytes IP+UDP header - 21 bytes SPTPS header+MAC */ +#else +#define MTU 1451 /* 1500 bytes payload - 28 bytes IP+UDP - 21 bytes SPTPS header+MAC */ +#endif + +#define MINMTU 527 /* 576 minimum recommended Internet MTU - 28 bytes IP+UDP - 21 bytes SPTPS header+MAC */ + +/* MAXSIZE is the maximum size of an encapsulated packet */ +#define MAXSIZE (MTU + 64) + +/* MAXBUFSIZE is the maximum size of a request: enough for a base64 encoded MAXSIZEd packet plus request header */ +#define MAXBUFSIZE ((MAXSIZE * 8) / 6 + 128) + +typedef struct vpn_packet_t { + uint16_t probe: 1; + int16_t tcp: 1; + uint16_t len; /* the actual number of bytes in the `data' field */ + uint8_t data[MAXSIZE]; +} vpn_packet_t; + +/* Packet types when using SPTPS */ + +#define PKT_COMPRESSED 1 +#define PKT_PROBE 4 + +typedef enum packet_type_t { + PACKET_NORMAL, + PACKET_COMPRESSED, + PACKET_PROBE +} packet_type_t; + +#include "conf.h" +#include "list.h" + +typedef struct outgoing_t { + struct node_t *node; + enum { + OUTGOING_START, + OUTGOING_CANONICAL_RESOLVE, + OUTGOING_CANONICAL, + OUTGOING_RECENT, + OUTGOING_KNOWN, + OUTGOING_END, + OUTGOING_NO_KNOWN_ADDRESSES, + } state; + int timeout; + timeout_t ev; + struct addrinfo *ai; + struct addrinfo *aip; +} outgoing_t; + +/* Yes, very strange placement indeed, but otherwise the typedefs get all tangled up */ +#include "connection.h" +#include "node.h" + +void init_outgoings(struct meshlink_handle *mesh); +void exit_outgoings(struct meshlink_handle *mesh); + +void retry_outgoing(struct meshlink_handle *mesh, outgoing_t *); +void handle_incoming_vpn_data(struct event_loop_t *loop, void *, int); +void finish_connecting(struct meshlink_handle *mesh, struct connection_t *); +void do_outgoing_connection(struct meshlink_handle *mesh, struct outgoing_t *); +void handle_new_meta_connection(struct event_loop_t *loop, void *, int); +int setup_tcp_listen_socket(struct meshlink_handle *mesh, const struct addrinfo *aip) __attribute__((__warn_unused_result__)); +int setup_udp_listen_socket(struct meshlink_handle *mesh, const struct addrinfo *aip) __attribute__((__warn_unused_result__)); +bool send_sptps_data(void *handle, uint8_t type, const void *data, size_t len); +bool receive_sptps_record(void *handle, uint8_t type, const void *data, uint16_t len) __attribute__((__warn_unused_result__)); +void send_packet(struct meshlink_handle *mesh, struct node_t *, struct vpn_packet_t *); +char *get_name(struct meshlink_handle *mesh) __attribute__((__warn_unused_result__)); +void load_all_nodes(struct meshlink_handle *mesh); +bool setup_myself_reloadable(struct meshlink_handle *mesh) __attribute__((__warn_unused_result__)); +bool setup_network(struct meshlink_handle *mesh) __attribute__((__warn_unused_result__)); +void reset_outgoing(struct outgoing_t *); +void setup_outgoing_connection(struct meshlink_handle *mesh, struct outgoing_t *); +void close_network_connections(struct meshlink_handle *mesh); +void main_loop(struct meshlink_handle *mesh); +void terminate_connection(struct meshlink_handle *mesh, struct connection_t *, bool); +bool node_read_public_key(struct meshlink_handle *mesh, struct node_t *) __attribute__((__warn_unused_result__)); +bool node_read_from_config(struct meshlink_handle *mesh, struct node_t *, const config_t *config) __attribute__((__warn_unused_result__)); +bool read_ecdsa_public_key(struct meshlink_handle *mesh, struct connection_t *) __attribute__((__warn_unused_result__)); +bool read_ecdsa_private_key(struct meshlink_handle *mesh) __attribute__((__warn_unused_result__)); +bool node_write_config(struct meshlink_handle *mesh, struct node_t *, bool new_key) __attribute__((__warn_unused_result__)); +void send_mtu_probe(struct meshlink_handle *mesh, struct node_t *); +void handle_meta_connection_data(struct meshlink_handle *mesh, struct connection_t *); +void retry(struct meshlink_handle *mesh); +int check_port(struct meshlink_handle *mesh); +void flush_meta(struct meshlink_handle *mesh, struct connection_t *); + +#ifndef HAVE_MINGW +#define closesocket(s) close(s) +#endif + +#endif diff --git a/src/net_packet.c b/src/net_packet.c new file mode 100644 index 0000000..02a617d --- /dev/null +++ b/src/net_packet.c @@ -0,0 +1,637 @@ +/* + net_packet.c -- Handles in- and outgoing VPN packets + Copyright (C) 2014-2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "conf.h" +#include "connection.h" +#include "crypto.h" +#include "graph.h" +#include "logger.h" +#include "meshlink_internal.h" +#include "net.h" +#include "netutl.h" +#include "protocol.h" +#include "route.h" +#include "utils.h" +#include "xalloc.h" + +int keylifetime = 0; + +static void send_udppacket(meshlink_handle_t *mesh, node_t *, vpn_packet_t *); + +#define MAX_SEQNO 1073741824 + +/* mtuprobes == 1..30: initial discovery, send bursts with 1 second interval + mtuprobes == 31: sleep pinginterval seconds + mtuprobes == 32: send 1 burst, sleep pingtimeout second + mtuprobes == 33: no response from other side, restart PMTU discovery process + + Probes are sent in batches of at least three, with random sizes between the + lower and upper boundaries for the MTU thus far discovered. + + After the initial discovery, a fourth packet is added to each batch with a + size larger than the currently known PMTU, to test if the PMTU has increased. + + In case local discovery is enabled, another packet is added to each batch, + which will be broadcast to the local network. + +*/ + +static void send_mtu_probe_handler(event_loop_t *loop, void *data) { + meshlink_handle_t *mesh = loop->data; + node_t *n = data; + int timeout = 1; + + n->mtuprobes++; + + if(!n->status.reachable || !n->status.validkey) { + logger(mesh, MESHLINK_INFO, "Trying to send MTU probe to unreachable or rekeying node %s", n->name); + n->mtuprobes = 0; + return; + } + + if(n->mtuprobes > 32) { + if(!n->minmtu) { + n->mtuprobes = 31; + timeout = mesh->dev_class_traits[n->devclass].pinginterval; + goto end; + } + + logger(mesh, MESHLINK_INFO, "%s did not respond to UDP ping, restarting PMTU discovery", n->name); + n->status.udp_confirmed = false; + n->mtuprobes = 1; + n->minmtu = 0; + n->maxmtu = MTU; + + update_node_pmtu(mesh, n); + } + + if(n->mtuprobes >= 10 && n->mtuprobes < 32 && !n->minmtu) { + logger(mesh, MESHLINK_INFO, "No response to MTU probes from %s", n->name); + n->mtuprobes = 31; + } + + if(n->mtuprobes == 30 || (n->mtuprobes < 30 && n->minmtu >= n->maxmtu)) { + if(n->minmtu > n->maxmtu) { + n->minmtu = n->maxmtu; + update_node_pmtu(mesh, n); + } else { + n->maxmtu = n->minmtu; + } + + n->mtu = n->minmtu; + logger(mesh, MESHLINK_INFO, "Fixing MTU of %s to %d after %d probes", n->name, n->mtu, n->mtuprobes); + n->mtuprobes = 31; + } + + if(n->mtuprobes == 31) { + if(!n->minmtu && n->status.want_udp && n->nexthop && n->nexthop->connection) { + /* Send a dummy ANS_KEY to try to update the reflexive UDP address */ + send_request(mesh, n->nexthop->connection, NULL, "%d %s %s . -1 -1 -1 0", ANS_KEY, mesh->self->name, n->name); + n->status.want_udp = false; + } + + timeout = mesh->dev_class_traits[n->devclass].pinginterval; + goto end; + } else if(n->mtuprobes == 32) { + timeout = mesh->dev_class_traits[n->devclass].pingtimeout; + } + + for(int i = 0; i < 5; i++) { + int len; + + if(i == 0) { + if(n->mtuprobes < 30 || n->maxmtu + 8 >= MTU) { + continue; + } + + len = n->maxmtu + 8; + } else if(n->maxmtu <= n->minmtu) { + len = n->maxmtu; + } else { + len = n->minmtu + 1 + prng(mesh, n->maxmtu - n->minmtu); + } + + if(len < 64) { + len = 64; + } + + vpn_packet_t packet; + packet.probe = true; + memset(packet.data, 0, 14); + randomize(packet.data + 14, len - 14); + packet.len = len; + n->status.broadcast = i >= 4 && n->mtuprobes <= 10 && n->prevedge; + + logger(mesh, MESHLINK_DEBUG, "Sending MTU probe length %d to %s", len, n->name); + + send_udppacket(mesh, n, &packet); + } + + n->status.broadcast = false; + +end: + timeout_set(&mesh->loop, &n->mtutimeout, &(struct timespec) { + timeout, prng(mesh, TIMER_FUDGE) + }); +} + +void send_mtu_probe(meshlink_handle_t *mesh, node_t *n) { + timeout_add(&mesh->loop, &n->mtutimeout, send_mtu_probe_handler, n, &(struct timespec) { + 1, 0 + }); + send_mtu_probe_handler(&mesh->loop, n); +} + +static void mtu_probe_h(meshlink_handle_t *mesh, node_t *n, vpn_packet_t *packet, uint16_t len) { + if(len < 64) { + logger(mesh, MESHLINK_WARNING, "Got too short MTU probe length %d from %s", packet->len, n->name); + return; + } + + logger(mesh, MESHLINK_DEBUG, "Got MTU probe length %d from %s", packet->len, n->name); + + if(!packet->data[0]) { + /* It's a probe request, send back a reply */ + + packet->data[0] = 1; + + /* Temporarily set udp_confirmed, so that the reply is sent + back exactly the way it came in. */ + + bool udp_confirmed = n->status.udp_confirmed; + n->status.udp_confirmed = true; + send_udppacket(mesh, n, packet); + n->status.udp_confirmed = udp_confirmed; + } else { + /* It's a valid reply: now we know bidirectional communication + is possible using the address and socket that the reply + packet used. */ + + if(!n->status.udp_confirmed) { + char *address, *port; + sockaddr2str(&n->address, &address, &port); + + if(n->nexthop && n->nexthop->connection) { + send_request(mesh, n->nexthop->connection, NULL, "%d %s %s . -1 -1 -1 0 %s %s", ANS_KEY, n->name, n->name, address, port); + } else { + logger(mesh, MESHLINK_WARNING, "Cannot send reflexive address to %s via %s", n->name, n->nexthop ? n->nexthop->name : n->name); + } + + free(address); + free(port); + n->status.udp_confirmed = true; + } + + /* If we haven't established the PMTU yet, restart the discovery process. */ + + if(n->mtuprobes > 30) { + if(len == n->maxmtu + 8) { + logger(mesh, MESHLINK_INFO, "Increase in PMTU to %s detected, restarting PMTU discovery", n->name); + n->maxmtu = MTU; + n->mtuprobes = 10; + return; + } + + if(n->minmtu) { + n->mtuprobes = 30; + } else { + n->mtuprobes = 1; + } + } + + /* If applicable, raise the minimum supported MTU */ + + if(len > n->maxmtu) { + len = n->maxmtu; + } + + if(n->minmtu < len) { + n->minmtu = len; + update_node_pmtu(mesh, n); + } + } +} + +/* VPN packet I/O */ + +static void receive_packet(meshlink_handle_t *mesh, node_t *n, vpn_packet_t *packet) { + logger(mesh, MESHLINK_DEBUG, "Received packet of %d bytes from %s", packet->len, n->name); + + if(n->status.blacklisted) { + logger(mesh, MESHLINK_WARNING, "Dropping packet from blacklisted node %s", n->name); + } else { + n->in_packets++; + n->in_bytes += packet->len; + + route(mesh, n, packet); + } +} + +static bool try_mac(meshlink_handle_t *mesh, node_t *n, const vpn_packet_t *inpkt) { + (void)mesh; + return sptps_verify_datagram(&n->sptps, inpkt->data, inpkt->len); +} + +static void receive_udppacket(meshlink_handle_t *mesh, node_t *n, vpn_packet_t *inpkt) { + if(!n->status.reachable) { + logger(mesh, MESHLINK_ERROR, "Got SPTPS data from unreachable node %s", n->name); + return; + } + + if(!n->sptps.state) { + if(!n->status.waitingforkey) { + logger(mesh, MESHLINK_DEBUG, "Got packet from %s but we haven't exchanged keys yet", n->name); + send_req_key(mesh, n); + } else { + logger(mesh, MESHLINK_DEBUG, "Got packet from %s but he hasn't got our key yet", n->name); + } + + return; + } + + if(!sptps_receive_data(&n->sptps, inpkt->data, inpkt->len)) { + logger(mesh, MESHLINK_ERROR, "Could not process SPTPS data from %s: %s", n->name, strerror(errno)); + } +} + +static void send_sptps_packet(meshlink_handle_t *mesh, node_t *n, vpn_packet_t *origpkt) { + if(!n->status.reachable) { + logger(mesh, MESHLINK_ERROR, "Trying to send SPTPS data to unreachable node %s", n->name); + return; + } + + if(!n->status.validkey) { + logger(mesh, MESHLINK_INFO, "No valid key known yet for %s", n->name); + + if(!n->status.waitingforkey) { + send_req_key(mesh, n); + } else if(n->last_req_key + 10 < mesh->loop.now.tv_sec) { + logger(mesh, MESHLINK_DEBUG, "No key from %s after 10 seconds, restarting SPTPS", n->name); + sptps_stop(&n->sptps); + n->status.waitingforkey = false; + send_req_key(mesh, n); + } + + return; + } + + uint8_t type = 0; + + // If it's a probe, send it immediately without trying to compress it. + if(origpkt->probe) { + sptps_send_record(&n->sptps, PKT_PROBE, origpkt->data, origpkt->len); + return; + } + + sptps_send_record(&n->sptps, type, origpkt->data, origpkt->len); + return; +} + +static void choose_udp_address(meshlink_handle_t *mesh, const node_t *n, const sockaddr_t **sa, int *sock, sockaddr_t *sa_buf) { + /* Latest guess */ + *sa = &n->address; + *sock = n->sock; + + /* If the UDP address is confirmed, use it. */ + if(n->status.udp_confirmed) { + return; + } + + /* Send every third packet to n->address; that could be set + to the node's reflexive UDP address discovered during key + exchange. */ + + if(++mesh->udp_choice >= 3) { + mesh->udp_choice = 0; + return; + } + + /* If we have learned an address via Catta, try this once every batch */ + if(mesh->udp_choice == 1 && n->catta_address.sa.sa_family != AF_UNSPEC) { + *sa = &n->catta_address; + goto check_socket; + } + + /* Else, if we have a canonical address, try this once every batch */ + if(mesh->udp_choice == 1 && n->canonical_address) { + char *host = xstrdup(n->canonical_address); + char *port = strchr(host, ' '); + + if(port) { + *port++ = 0; + *sa_buf = str2sockaddr_random(mesh, host, port); + *sa = sa_buf; + + if(sa_buf->sa.sa_family != AF_UNKNOWN) { + free(host); + goto check_socket; + } + } + + free(host); + } + + /* Otherwise, address are found in edges to this node. + So we pick a random edge and a random socket. */ + + edge_t *candidate = NULL; + + { + int i = 0; + int j = prng(mesh, n->edge_tree->count); + + for splay_each(edge_t, e, n->edge_tree) { + if(i++ == j) { + candidate = e->reverse; + break; + } + } + } + + if(candidate) { + *sa = &candidate->address; + *sock = prng(mesh, mesh->listen_sockets); + } + +check_socket: + + /* Make sure we have a suitable socket for the chosen address */ + if(mesh->listen_socket[*sock].sa.sa.sa_family != (*sa)->sa.sa_family) { + for(int i = 0; i < mesh->listen_sockets; i++) { + if(mesh->listen_socket[i].sa.sa.sa_family == (*sa)->sa.sa_family) { + *sock = i; + break; + } + } + } +} + +static void choose_broadcast_address(meshlink_handle_t *mesh, const node_t *n, const sockaddr_t **sa, int *sock) { + *sock = prng(mesh, mesh->listen_sockets); + sockaddr_t *broadcast_sa = &mesh->listen_socket[*sock].broadcast_sa; + + if(broadcast_sa->sa.sa_family == AF_INET6) { + broadcast_sa->in6.sin6_port = n->prevedge->address.in.sin_port; + } else { + broadcast_sa->in.sin_port = n->prevedge->address.in.sin_port; + } + + *sa = broadcast_sa; +} + +static void send_udppacket(meshlink_handle_t *mesh, node_t *n, vpn_packet_t *origpkt) { + if(!n->status.reachable) { + logger(mesh, MESHLINK_INFO, "Trying to send UDP packet to unreachable node %s", n->name); + return; + } + + send_sptps_packet(mesh, n, origpkt); +} + +bool send_sptps_data(void *handle, uint8_t type, const void *data, size_t len) { + assert(handle); + assert(data); + assert(len); + + node_t *to = handle; + meshlink_handle_t *mesh = to->mesh; + + if(!to->status.reachable) { + logger(mesh, MESHLINK_ERROR, "Trying to send SPTPS data to unreachable node %s", to->name); + return false; + } + + /* Send it via TCP if it is a handshake packet, TCPOnly is in use, or this packet is larger than the MTU. */ + + if(type >= SPTPS_HANDSHAKE || (type != PKT_PROBE && (len - 21) > to->minmtu)) { + char buf[len * 4 / 3 + 5]; + b64encode(data, buf, len); + + if(!to->nexthop || !to->nexthop->connection) { + logger(mesh, MESHLINK_WARNING, "Unable to forward SPTPS packet to %s via %s", to->name, to->nexthop ? to->nexthop->name : to->name); + return false; + } + + /* If no valid key is known yet, send the packets using ANS_KEY requests, + to ensure we get to learn the reflexive UDP address. */ + if(!to->status.validkey) { + return send_request(mesh, to->nexthop->connection, NULL, "%d %s %s %s -1 -1 -1 %d", ANS_KEY, mesh->self->name, to->name, buf, 0); + } else { + return send_request(mesh, to->nexthop->connection, NULL, "%d %s %s %d %s", REQ_KEY, mesh->self->name, to->name, REQ_SPTPS, buf); + } + } + + /* Otherwise, send the packet via UDP */ + + sockaddr_t sa_buf; + const sockaddr_t *sa; + int sock; + + if(to->status.broadcast) { + choose_broadcast_address(mesh, to, &sa, &sock); + } else { + choose_udp_address(mesh, to, &sa, &sock, &sa_buf); + } + + if(sendto(mesh->listen_socket[sock].udp.fd, data, len, 0, &sa->sa, SALEN(sa->sa)) < 0 && !sockwouldblock(sockerrno)) { + if(sockmsgsize(sockerrno)) { + if(to->maxmtu >= len) { + to->maxmtu = len - 1; + } + + if(to->mtu >= len) { + to->mtu = len - 1; + } + } else { + logger(mesh, MESHLINK_WARNING, "Error sending UDP SPTPS packet to %s: %s", to->name, sockstrerror(sockerrno)); + return false; + } + } + + return true; +} + +bool receive_sptps_record(void *handle, uint8_t type, const void *data, uint16_t len) { + assert(handle); + assert(!data || len); + + node_t *from = handle; + meshlink_handle_t *mesh = from->mesh; + + if(type == SPTPS_HANDSHAKE) { + if(!from->status.validkey) { + logger(mesh, MESHLINK_INFO, "SPTPS key exchange with %s successful", from->name); + from->status.validkey = true; + from->status.waitingforkey = false; + + if(from->utcp) { + utcp_reset_timers(from->utcp); + } + } + + return true; + } + + if(len > MAXSIZE) { + logger(mesh, MESHLINK_ERROR, "Packet from %s larger than maximum supported size (%d > %d)", from->name, len, MAXSIZE); + return false; + } + + vpn_packet_t inpkt; + + if(type == PKT_PROBE) { + inpkt.len = len; + inpkt.probe = true; + memcpy(inpkt.data, data, len); + mtu_probe_h(mesh, from, &inpkt, len); + return true; + } else { + inpkt.probe = false; + } + + if(type & ~(PKT_COMPRESSED)) { + logger(mesh, MESHLINK_ERROR, "Unexpected SPTPS record type %d len %d from %s", type, len, from->name); + return false; + } + + if(type & PKT_COMPRESSED) { + logger(mesh, MESHLINK_ERROR, "Error while decompressing packet from %s", from->name); + return false; + } + + memcpy(inpkt.data, data, len); // TODO: get rid of memcpy + inpkt.len = len; + + receive_packet(mesh, from, &inpkt); + return true; +} + +/* + send a packet to the given vpn ip. +*/ +void send_packet(meshlink_handle_t *mesh, node_t *n, vpn_packet_t *packet) { + if(n == mesh->self) { + n->out_packets++; + n->out_bytes += packet->len; + // TODO: send to application + return; + } + + logger(mesh, MESHLINK_DEBUG, "Sending packet of %d bytes to %s", packet->len, n->name); + + if(!n->status.reachable) { + logger(mesh, MESHLINK_WARNING, "Node %s is not reachable", n->name); + return; + } + + n->out_packets++; + n->out_bytes += packet->len; + n->status.want_udp = true; + + send_sptps_packet(mesh, n, packet); + return; +} + +static node_t *try_harder(meshlink_handle_t *mesh, const sockaddr_t *from, const vpn_packet_t *pkt) { + node_t *n = NULL; + bool hard = false; + + for splay_each(edge_t, e, mesh->edges) { + if(!e->to->status.reachable || e->to == mesh->self) { + continue; + } + + if(sockaddrcmp_noport(from, &e->address)) { + if(mesh->last_hard_try == mesh->loop.now.tv_sec) { + continue; + } + + hard = true; + } + + if(!try_mac(mesh, e->to, pkt)) { + continue; + } + + n = e->to; + break; + } + + if(hard) { + mesh->last_hard_try = mesh->loop.now.tv_sec; + } + + return n; +} + +void handle_incoming_vpn_data(event_loop_t *loop, void *data, int flags) { + (void)flags; + meshlink_handle_t *mesh = loop->data; + listen_socket_t *ls = data; + vpn_packet_t pkt; + char *hostname; + sockaddr_t from; + socklen_t fromlen = sizeof(from); + node_t *n; + int len; + + memset(&from, 0, sizeof(from)); + + len = recvfrom(ls->udp.fd, pkt.data, MAXSIZE, 0, &from.sa, &fromlen); + + if(len <= 0 || len > MAXSIZE) { + if(!sockwouldblock(sockerrno)) { + logger(mesh, MESHLINK_ERROR, "Receiving packet failed: %s", sockstrerror(sockerrno)); + } + + return; + } + + pkt.len = len; + + sockaddrunmap(&from); /* Some braindead IPv6 implementations do stupid things. */ + + n = lookup_node_udp(mesh, &from); + + if(!n) { + n = try_harder(mesh, &from, &pkt); + + if(n) { + update_node_udp(mesh, n, &from); + } else if(mesh->log_level <= MESHLINK_WARNING) { + hostname = sockaddr2hostname(&from); + logger(mesh, MESHLINK_WARNING, "Received UDP packet from unknown source %s", hostname); + free(hostname); + return; + } else { + return; + } + } + + if(n->status.blacklisted) { + logger(mesh, MESHLINK_WARNING, "Dropping packet from blacklisted node %s", n->name); + return; + } + + n->sock = ls - mesh->listen_socket; + + receive_udppacket(mesh, n, &pkt); +} diff --git a/src/net_setup.c b/src/net_setup.c new file mode 100644 index 0000000..a7a538f --- /dev/null +++ b/src/net_setup.c @@ -0,0 +1,671 @@ +/* + net_setup.c -- Setup. + Copyright (C) 2014-2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "conf.h" +#include "connection.h" +#include "ecdsa.h" +#include "graph.h" +#include "logger.h" +#include "meshlink_internal.h" +#include "net.h" +#include "netutl.h" +#include "packmsg.h" +#include "protocol.h" +#include "route.h" +#include "utils.h" +#include "xalloc.h" +#include "submesh.h" + +/// Helper function to start parsing a host config file +static bool node_get_config(meshlink_handle_t *mesh, node_t *n, config_t *config, packmsg_input_t *in) { + if(!config_read(mesh, "current", n->name, config, mesh->config_key)) { + return false; + } + + in->ptr = config->buf; + in->len = config->len; + + uint32_t version = packmsg_get_uint32(in); + + if(version != MESHLINK_CONFIG_VERSION) { + logger(mesh, MESHLINK_ERROR, "Invalid config file for node %s", n->name); + config_free(config); + return false; + } + + const char *name; + uint32_t len = packmsg_get_str_raw(in, &name); + + if(len != strlen(n->name) || !name || strncmp(name, n->name, len)) { + logger(mesh, MESHLINK_ERROR, "Invalid config file for node %s", n->name); + config_free(config); + return false; + } + + return true; +} + +/// Read the public key from a host config file. Used whenever we need to start an SPTPS session. +bool node_read_public_key(meshlink_handle_t *mesh, node_t *n) { + if(ecdsa_active(n->ecdsa)) { + return true; + } + + config_t config; + packmsg_input_t in; + + if(!node_get_config(mesh, n, &config, &in)) { + return false; + } + + packmsg_skip_element(&in); /* submesh */ + packmsg_get_int32(&in); /* devclass */ + packmsg_get_bool(&in); /* blacklisted */ + + const void *key; + uint32_t len = packmsg_get_bin_raw(&in, &key); + + if(len != 32) { + config_free(&config); + return false; + } + + n->ecdsa = ecdsa_set_public_key(key); + + // While we are at it, read known address information + if(!n->canonical_address) { + n->canonical_address = packmsg_get_str_dup(&in); + + if(!*n->canonical_address) { + free(n->canonical_address); + n->canonical_address = NULL; + } + } else { + packmsg_skip_element(&in); + } + + // Append any known addresses in the config file to the list we currently have + uint32_t known_count = 0; + + for(uint32_t i = 0; i < MAX_RECENT; i++) { + if(n->recent[i].sa.sa_family) { + known_count++; + } + } + + uint32_t count = packmsg_get_array(&in); + + for(uint32_t i = 0; i < count; i++) { + if(i < MAX_RECENT - known_count) { + n->recent[i + known_count] = packmsg_get_sockaddr(&in); + } else { + packmsg_skip_element(&in); + } + } + + time_t last_reachable = packmsg_get_int64(&in); + time_t last_unreachable = packmsg_get_int64(&in); + + if(!n->last_reachable) { + n->last_reachable = last_reachable; + } + + if(!n->last_unreachable) { + n->last_unreachable = last_unreachable; + } + + config_free(&config); + return true; +} + +/// Fill in node details from a config blob. +bool node_read_from_config(meshlink_handle_t *mesh, node_t *n, const config_t *config) { + if(n->canonical_address) { + return true; + } + + packmsg_input_t in = {config->buf, config->len}; + uint32_t version = packmsg_get_uint32(&in); + + if(version != MESHLINK_CONFIG_VERSION) { + return false; + } + + char *name = packmsg_get_str_dup(&in); + + if(!name) { + return false; + } + + if(n->name) { + if(strcmp(n->name, name)) { + free(name); + return false; + } + + free(name); + } else { + n->name = name; + } + + char *submesh_name = packmsg_get_str_dup(&in); + + if(!strcmp(submesh_name, CORE_MESH)) { + free(submesh_name); + n->submesh = NULL; + } else { + n->submesh = lookup_or_create_submesh(mesh, submesh_name); + free(submesh_name); + + if(!n->submesh) { + return false; + } + } + + n->devclass = packmsg_get_int32(&in); + n->status.blacklisted = packmsg_get_bool(&in); + const void *key; + uint32_t len = packmsg_get_bin_raw(&in, &key); + + if(len) { + if(len != 32) { + return false; + } + + if(!ecdsa_active(n->ecdsa)) { + n->ecdsa = ecdsa_set_public_key(key); + } + } + + n->canonical_address = packmsg_get_str_dup(&in); + + if(!*n->canonical_address) { + free(n->canonical_address); + n->canonical_address = NULL; + } + + uint32_t count = packmsg_get_array(&in); + + for(uint32_t i = 0; i < count; i++) { + if(i < MAX_RECENT) { + n->recent[i] = packmsg_get_sockaddr(&in); + } else { + packmsg_skip_element(&in); + } + } + + n->last_reachable = packmsg_get_int64(&in); + n->last_unreachable = packmsg_get_int64(&in); + + return packmsg_done(&in); +} + +bool node_write_config(meshlink_handle_t *mesh, node_t *n, bool new_key) { + if(!mesh->confbase) { + return true; + } + + switch(mesh->storage_policy) { + case MESHLINK_STORAGE_KEYS_ONLY: + if(!new_key) { + return true; + } + + break; + + case MESHLINK_STORAGE_DISABLED: + return true; + + default: + break; + } + + uint8_t buf[4096]; + packmsg_output_t out = {buf, sizeof(buf)}; + + packmsg_add_uint32(&out, MESHLINK_CONFIG_VERSION); + packmsg_add_str(&out, n->name); + packmsg_add_str(&out, n->submesh ? n->submesh->name : CORE_MESH); + packmsg_add_int32(&out, n->devclass); + packmsg_add_bool(&out, n->status.blacklisted); + + if(ecdsa_active(n->ecdsa)) { + packmsg_add_bin(&out, ecdsa_get_public_key(n->ecdsa), 32); + } else { + packmsg_add_bin(&out, "", 0); + } + + packmsg_add_str(&out, n->canonical_address ? n->canonical_address : ""); + + uint32_t count = 0; + + for(uint32_t i = 0; i < MAX_RECENT; i++) { + if(n->recent[i].sa.sa_family) { + count++; + } else { + break; + } + } + + packmsg_add_array(&out, count); + + for(uint32_t i = 0; i < count; i++) { + packmsg_add_sockaddr(&out, &n->recent[i]); + } + + packmsg_add_int64(&out, n->last_reachable); + packmsg_add_int64(&out, n->last_unreachable); + + if(!packmsg_output_ok(&out)) { + meshlink_errno = MESHLINK_EINTERNAL; + return false; + } + + config_t config = {buf, packmsg_output_size(&out, buf)}; + + if(!config_write(mesh, "current", n->name, &config, mesh->config_key)) { + call_error_cb(mesh, MESHLINK_ESTORAGE); + return false; + } + + n->status.dirty = false; + return true; +} + +static bool load_node(meshlink_handle_t *mesh, const char *name, void *priv) { + (void)priv; + + if(!check_id(name)) { + // Check if this is a temporary file, if so remove it + const char *suffix = strstr(name, ".tmp"); + + if(suffix && !suffix[4]) { + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s" SLASH "current" SLASH "hosts", mesh->confbase); + unlink(filename); + } + + return true; + } + + node_t *n = lookup_node(mesh, name); + + if(n) { + return true; + } + + n = new_node(); + n->name = xstrdup(name); + + config_t config; + packmsg_input_t in; + + if(!node_get_config(mesh, n, &config, &in)) { + free_node(n); + return false; + } + + if(!node_read_from_config(mesh, n, &config)) { + logger(mesh, MESHLINK_ERROR, "Invalid config file for node %s", n->name); + config_free(&config); + free_node(n); + return false; + } + + config_free(&config); + + node_add(mesh, n); + + return true; +} + +int setup_tcp_listen_socket(meshlink_handle_t *mesh, const struct addrinfo *aip) { + int nfd = socket(aip->ai_family, SOCK_STREAM, IPPROTO_TCP); + + if(nfd == -1) { + return -1; + } + +#ifdef FD_CLOEXEC + fcntl(nfd, F_SETFD, FD_CLOEXEC); +#endif + +#ifdef O_NONBLOCK + int flags = fcntl(nfd, F_GETFL); + + if(fcntl(nfd, F_SETFL, flags | O_NONBLOCK) < 0) { + closesocket(nfd); + logger(mesh, MESHLINK_ERROR, "System call `%s' failed: %s", "fcntl", strerror(errno)); + return -1; + } + +#elif defined(WIN32) + unsigned long arg = 1; + + if(ioctlsocket(nfd, FIONBIO, &arg) != 0) { + closesocket(nfd); + logger(mesh, MESHLINK_ERROR, "Call to `%s' failed: %s", "ioctlsocket", sockstrerror(sockerrno)); + return -1; + } + +#endif + int option = 1; + setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, (void *)&option, sizeof(option)); + +#if defined(IPV6_V6ONLY) + + if(aip->ai_family == AF_INET6) { + setsockopt(nfd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&option, sizeof(option)); + } + +#else +#warning IPV6_V6ONLY not defined +#endif + + if(bind(nfd, aip->ai_addr, aip->ai_addrlen)) { + closesocket(nfd); + return -1; + } + + if(listen(nfd, 3)) { + logger(mesh, MESHLINK_ERROR, "System call `%s' failed: %s", "listen", sockstrerror(sockerrno)); + closesocket(nfd); + return -1; + } + + return nfd; +} + +int setup_udp_listen_socket(meshlink_handle_t *mesh, const struct addrinfo *aip) { + int nfd = socket(aip->ai_family, SOCK_DGRAM, IPPROTO_UDP); + + if(nfd == -1) { + return -1; + } + +#ifdef FD_CLOEXEC + fcntl(nfd, F_SETFD, FD_CLOEXEC); +#endif + +#ifdef O_NONBLOCK + int flags = fcntl(nfd, F_GETFL); + + if(fcntl(nfd, F_SETFL, flags | O_NONBLOCK) < 0) { + closesocket(nfd); + logger(mesh, MESHLINK_ERROR, "System call `%s' failed: %s", "fcntl", strerror(errno)); + return -1; + } + +#elif defined(WIN32) + unsigned long arg = 1; + + if(ioctlsocket(nfd, FIONBIO, &arg) != 0) { + closesocket(nfd); + logger(mesh, MESHLINK_ERROR, "Call to `%s' failed: %s", "ioctlsocket", sockstrerror(sockerrno)); + return -1; + } + +#endif + + int option = 1; + setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, (void *)&option, sizeof(option)); + setsockopt(nfd, SOL_SOCKET, SO_BROADCAST, (void *)&option, sizeof(option)); + +#if defined(IPV6_V6ONLY) + + if(aip->ai_family == AF_INET6) { + setsockopt(nfd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&option, sizeof(option)); + } + +#endif + +#if defined(IP_DONTFRAG) && !defined(IP_DONTFRAGMENT) +#define IP_DONTFRAGMENT IP_DONTFRAG +#endif + +#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DO) + option = IP_PMTUDISC_DO; + setsockopt(nfd, IPPROTO_IP, IP_MTU_DISCOVER, (void *)&option, sizeof(option)); +#elif defined(IP_DONTFRAGMENT) + option = 1; + setsockopt(nfd, IPPROTO_IP, IP_DONTFRAGMENT, (void *)&option, sizeof(option)); +#endif + + if(aip->ai_family == AF_INET6) { +#if defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_DO) + option = IPV6_PMTUDISC_DO; + setsockopt(nfd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, (void *)&option, sizeof(option)); +#elif defined(IPV6_DONTFRAG) + option = 1; + setsockopt(nfd, IPPROTO_IPV6, IPV6_DONTFRAG, (void *)&option, sizeof(option)); +#endif + } + + if(bind(nfd, aip->ai_addr, aip->ai_addrlen)) { + closesocket(nfd); + return -1; + } + + return nfd; +} + +/* + Add listening sockets. +*/ +static bool add_listen_sockets(meshlink_handle_t *mesh) { + struct addrinfo *ai; + + struct addrinfo hint = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP, + .ai_flags = AI_PASSIVE | AI_NUMERICSERV, + }; + + int err = getaddrinfo(NULL, mesh->myport, &hint, &ai); + + if(err || !ai) { + logger(mesh, MESHLINK_ERROR, "System call `%s' failed: %s", "getaddrinfo", err == EAI_SYSTEM ? strerror(err) : gai_strerror(err)); + return false; + } + + bool success = false; + + for(struct addrinfo *aip = ai; aip; aip = aip->ai_next) { + // Ignore duplicate addresses + bool found = false; + + for(int i = 0; i < mesh->listen_sockets; i++) { + if(!memcmp(&mesh->listen_socket[i].sa, aip->ai_addr, aip->ai_addrlen)) { + found = true; + break; + } + } + + if(found) { + continue; + } + + if(mesh->listen_sockets >= MAXSOCKETS) { + logger(mesh, MESHLINK_ERROR, "Too many listening sockets"); + return false; + } + + /* Try to bind to TCP */ + + int tcp_fd = setup_tcp_listen_socket(mesh, aip); + + if(tcp_fd == -1) { + if(errno == EADDRINUSE) { + /* If this port is in use for any address family, avoid it. */ + success = false; + break; + } else { + continue; + } + } + + /* If TCP worked, then we require that UDP works as well. */ + + int udp_fd = setup_udp_listen_socket(mesh, aip); + + if(udp_fd == -1) { + closesocket(tcp_fd); + success = false; + break; + } + + io_add(&mesh->loop, &mesh->listen_socket[mesh->listen_sockets].tcp, handle_new_meta_connection, &mesh->listen_socket[mesh->listen_sockets], tcp_fd, IO_READ); + io_add(&mesh->loop, &mesh->listen_socket[mesh->listen_sockets].udp, handle_incoming_vpn_data, &mesh->listen_socket[mesh->listen_sockets], udp_fd, IO_READ); + + if(mesh->log_level <= MESHLINK_INFO) { + char *hostname = sockaddr2hostname((sockaddr_t *) aip->ai_addr); + logger(mesh, MESHLINK_INFO, "Listening on %s", hostname); + free(hostname); + } + + memcpy(&mesh->listen_socket[mesh->listen_sockets].sa, aip->ai_addr, aip->ai_addrlen); + memcpy(&mesh->listen_socket[mesh->listen_sockets].broadcast_sa, aip->ai_addr, aip->ai_addrlen); + + if(aip->ai_family == AF_INET6) { + mesh->listen_socket[mesh->listen_sockets].broadcast_sa.in6.sin6_addr.s6_addr[0x0] = 0xff; + mesh->listen_socket[mesh->listen_sockets].broadcast_sa.in6.sin6_addr.s6_addr[0x1] = 0x02; + mesh->listen_socket[mesh->listen_sockets].broadcast_sa.in6.sin6_addr.s6_addr[0xf] = 0x01; + } else { + mesh->listen_socket[mesh->listen_sockets].broadcast_sa.in.sin_addr.s_addr = 0xffffffff; + } + + mesh->listen_sockets++; + success = true; + } + + freeaddrinfo(ai); + + if(!success) { + for(int i = 0; i < mesh->listen_sockets; i++) { + io_del(&mesh->loop, &mesh->listen_socket[i].tcp); + io_del(&mesh->loop, &mesh->listen_socket[i].udp); + closesocket(mesh->listen_socket[i].tcp.fd); + closesocket(mesh->listen_socket[i].udp.fd); + } + + mesh->listen_sockets = 0; + } + + return success; +} + +/* + Configure node_t mesh->self and set up the local sockets (listen only) +*/ +static bool setup_myself(meshlink_handle_t *mesh) { + mesh->self->nexthop = mesh->self; + + node_add(mesh, mesh->self); + + if(!config_scan_all(mesh, "current", "hosts", load_node, NULL)) { + logger(mesh, MESHLINK_WARNING, "Could not scan all host config files"); + } + + /* Open sockets */ + + mesh->listen_sockets = 0; + + if(!add_listen_sockets(mesh)) { + if(strcmp(mesh->myport, "0")) { + logger(mesh, MESHLINK_WARNING, "Could not bind to port %s, trying to find an alternative port", mesh->myport); + + if(!check_port(mesh)) { + logger(mesh, MESHLINK_WARNING, "Could not bind to any port, trying to bind to port 0"); + free(mesh->myport); + mesh->myport = xstrdup("0"); + } + + if(!add_listen_sockets(mesh)) { + return false; + } + } else { + return false; + } + } + + if(!mesh->listen_sockets) { + logger(mesh, MESHLINK_ERROR, "Unable to create any listening socket!"); + return false; + } + + /* Done. */ + + mesh->last_unreachable = mesh->loop.now.tv_sec; + + return true; +} + +/* + initialize network +*/ +bool setup_network(meshlink_handle_t *mesh) { + init_connections(mesh); + init_submeshes(mesh); + init_nodes(mesh); + init_edges(mesh); + init_requests(mesh); + + if(!setup_myself(mesh)) { + return false; + } + + return true; +} + +/* + close all open network connections +*/ +void close_network_connections(meshlink_handle_t *mesh) { + if(mesh->connections) { + for(list_node_t *node = mesh->connections->head, *next; node; node = next) { + next = node->next; + connection_t *c = node->data; + c->outgoing = NULL; + terminate_connection(mesh, c, false); + } + } + + for(int i = 0; i < mesh->listen_sockets; i++) { + io_del(&mesh->loop, &mesh->listen_socket[i].tcp); + io_del(&mesh->loop, &mesh->listen_socket[i].udp); + closesocket(mesh->listen_socket[i].tcp.fd); + closesocket(mesh->listen_socket[i].udp.fd); + } + + exit_requests(mesh); + exit_edges(mesh); + exit_nodes(mesh); + exit_submeshes(mesh); + exit_connections(mesh); + + free(mesh->myport); + mesh->myport = NULL; + + mesh->self = NULL; + + return; +} diff --git a/src/net_socket.c b/src/net_socket.c new file mode 100644 index 0000000..4989b6c --- /dev/null +++ b/src/net_socket.c @@ -0,0 +1,605 @@ +/* + net_socket.c -- Handle various kinds of sockets. + Copyright (C) 2014-2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "adns.h" +#include "conf.h" +#include "connection.h" +#include "list.h" +#include "logger.h" +#include "meshlink_internal.h" +#include "meta.h" +#include "net.h" +#include "netutl.h" +#include "protocol.h" +#include "utils.h" +#include "xalloc.h" + +/* Needed on Mac OS/X */ +#ifndef SOL_TCP +#define SOL_TCP IPPROTO_TCP +#endif + +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + +static const int max_connection_burst = 100; + +/* Setup sockets */ + +static void configure_tcp(connection_t *c) { +#ifdef O_NONBLOCK + int flags = fcntl(c->socket, F_GETFL); + + if(fcntl(c->socket, F_SETFL, flags | O_NONBLOCK) < 0) { + logger(c->mesh, MESHLINK_ERROR, "System call `%s' failed: %s", "fcntl", strerror(errno)); + } + +#elif defined(WIN32) + unsigned long arg = 1; + + if(ioctlsocket(c->socket, FIONBIO, &arg) != 0) { + logger(c->mesh, MESHLINK_ERROR, "System call `%s' failed: %s", "ioctlsocket", sockstrerror(sockerrno)); + } + +#endif + +#if defined(SOL_TCP) && defined(TCP_NODELAY) + int nodelay = 1; + setsockopt(c->socket, SOL_TCP, TCP_NODELAY, (void *)&nodelay, sizeof(nodelay)); +#endif + +#if defined(IP_TOS) && defined(IPTOS_LOWDELAY) + int lowdelay = IPTOS_LOWDELAY; + setsockopt(c->socket, IPPROTO_IP, IP_TOS, (void *)&lowdelay, sizeof(lowdelay)); +#endif + +#if defined(SO_NOSIGPIPE) + int nosigpipe = 1; + setsockopt(c->socket, SOL_SOCKET, SO_NOSIGPIPE, (void *)&nosigpipe, sizeof(nosigpipe)); +#endif +} + +static void retry_outgoing_handler(event_loop_t *loop, void *data) { + assert(data); + + meshlink_handle_t *mesh = loop->data; + outgoing_t *outgoing = data; + setup_outgoing_connection(mesh, outgoing); +} + +void retry_outgoing(meshlink_handle_t *mesh, outgoing_t *outgoing) { + if(!mesh->reachable && mesh->loop.now.tv_sec < mesh->last_unreachable + mesh->dev_class_traits[outgoing->node->devclass].fast_retry_period) { + outgoing->timeout = 1; + } else { + outgoing->timeout += 5; + } + + int maxtimeout = mesh->dev_class_traits[outgoing->node->devclass].maxtimeout; + + if(outgoing->timeout > maxtimeout) { + outgoing->timeout = maxtimeout; + } + + timeout_add(&mesh->loop, &outgoing->ev, retry_outgoing_handler, outgoing, &(struct timespec) { + outgoing->timeout, prng(mesh, TIMER_FUDGE) + }); + + logger(mesh, MESHLINK_INFO, "Trying to re-establish outgoing connection in %d seconds", outgoing->timeout); +} + +void finish_connecting(meshlink_handle_t *mesh, connection_t *c) { + logger(mesh, MESHLINK_INFO, "Connected to %s", c->name); + + c->last_ping_time = mesh->loop.now.tv_sec; + c->status.connecting = false; + + send_id(mesh, c); +} + +static void handle_meta_write(meshlink_handle_t *mesh, connection_t *c) { + if(c->outbuf.len <= c->outbuf.offset) { + return; + } + + ssize_t outlen = send(c->socket, c->outbuf.data + c->outbuf.offset, c->outbuf.len - c->outbuf.offset, MSG_NOSIGNAL); + + if(outlen <= 0) { + if(!errno || errno == EPIPE) { + logger(mesh, MESHLINK_INFO, "Connection closed by %s", c->name); + } else if(sockwouldblock(sockerrno)) { + logger(mesh, MESHLINK_DEBUG, "Sending %lu bytes to %s would block", (unsigned long)(c->outbuf.len - c->outbuf.offset), c->name); + return; + } else { + logger(mesh, MESHLINK_ERROR, "Could not send %lu bytes of data to %s: %s", (unsigned long)(c->outbuf.len - c->outbuf.offset), c->name, strerror(errno)); + } + + terminate_connection(mesh, c, c->status.active); + return; + } + + buffer_read(&c->outbuf, outlen); + + if(!c->outbuf.len) { + io_set(&mesh->loop, &c->io, IO_READ); + } +} + +void flush_meta(meshlink_handle_t *mesh, connection_t *c) { + handle_meta_write(mesh, c); +} + +static void handle_meta_io(event_loop_t *loop, void *data, int flags) { + meshlink_handle_t *mesh = loop->data; + connection_t *c = data; + + if(c->status.connecting) { + c->status.connecting = false; + + int result; + socklen_t len = sizeof(result); + getsockopt(c->socket, SOL_SOCKET, SO_ERROR, (void *)&result, &len); + + if(!result) { + finish_connecting(mesh, c); + } else { + logger(mesh, MESHLINK_ERROR, "Error while connecting to %s: %s", c->name, sockstrerror(result)); + terminate_connection(mesh, c, false); + return; + } + } + + if(flags & IO_WRITE) { + handle_meta_write(mesh, c); + } else { + handle_meta_connection_data(mesh, c); + } +} + +// Find edges pointing to this node, and use them to build a list of unique, known addresses. +static struct addrinfo *get_known_addresses(node_t *n) { + struct addrinfo *ai = NULL; + + for splay_each(edge_t, e, n->edge_tree) { + if(!e->reverse) { + continue; + } + + bool found = false; + + for(struct addrinfo *aip = ai; aip; aip = aip->ai_next) { + if(!sockaddrcmp(&e->reverse->address, (sockaddr_t *)aip->ai_addr)) { + found = true; + break; + } + } + + if(found) { + continue; + } + + // Create a new struct addrinfo, and put it at the head of the list. + struct addrinfo *nai = xzalloc(sizeof(*nai) + SALEN(e->reverse->address.sa)); + nai->ai_next = ai; + ai = nai; + + ai->ai_family = e->reverse->address.sa.sa_family; + ai->ai_socktype = SOCK_STREAM; + ai->ai_protocol = IPPROTO_TCP; + ai->ai_addrlen = SALEN(e->reverse->address.sa); + ai->ai_addr = (struct sockaddr *)(nai + 1); + memcpy(ai->ai_addr, &e->reverse->address, ai->ai_addrlen); + } + + return ai; +} + +// Build a list of recently seen addresses. +static struct addrinfo *get_recent_addresses(node_t *n) { + struct addrinfo *ai = NULL; + struct addrinfo *aip; + + for(int i = 0; i < 5; i++) { + if(!n->recent[i].sa.sa_family) { + break; + } + + // Create a new struct addrinfo, and put it at the end of the list. + struct addrinfo *nai = xzalloc(sizeof(*nai) + SALEN(n->recent[i].sa)); + + if(!ai) { + ai = nai; + } else { + aip->ai_next = nai; + } + + aip = nai; + + nai->ai_family = n->recent[i].sa.sa_family; + nai->ai_socktype = SOCK_STREAM; + nai->ai_protocol = IPPROTO_TCP; + nai->ai_addrlen = SALEN(n->recent[i].sa); + nai->ai_addr = (struct sockaddr *)(nai + 1); + memcpy(nai->ai_addr, &n->recent[i], nai->ai_addrlen); + } + + return ai; +} + +// Free struct addrinfo list from get_known_addresses(). +static void free_known_addresses(struct addrinfo *ai) { + for(struct addrinfo *aip = ai, *next; aip; aip = next) { + next = aip->ai_next; + free(aip); + } +} + +static void canonical_resolve_cb(meshlink_handle_t *mesh, char *host, char *serv, void *data, struct addrinfo *ai, int err) { + (void)serv; + (void)err; + node_t *n = data; + + free(host); + free(serv); + + for list_each(outgoing_t, outgoing, mesh->outgoings) { + if(outgoing->node == n) { + if(outgoing->state == OUTGOING_CANONICAL_RESOLVE) { + outgoing->ai = ai; + outgoing->aip = NULL; + outgoing->state = OUTGOING_CANONICAL; + do_outgoing_connection(mesh, outgoing); + } + + return; + } + } +} + +static bool get_next_outgoing_address(meshlink_handle_t *mesh, outgoing_t *outgoing) { + (void)mesh; + + bool start = false; + + if(outgoing->state == OUTGOING_START) { + start = true; + outgoing->state = OUTGOING_CANONICAL_RESOLVE; + } + + if(outgoing->state == OUTGOING_CANONICAL_RESOLVE) { + node_t *n = outgoing->node; + + if(n->canonical_address) { + char *address = xstrdup(n->canonical_address); + char *port = strchr(address, ' '); + + if(port) { + *port++ = 0; + port = xstrdup(port); + adns_queue(mesh, address, port, canonical_resolve_cb, outgoing->node, 2); + return false; + } else { + logger(mesh, MESHLINK_ERROR, "Canonical address for %s is missing port number", n->name); + free(address); + outgoing->state = OUTGOING_RECENT; + } + + } else { + outgoing->state = OUTGOING_RECENT; + } + } + + if(outgoing->state == OUTGOING_CANONICAL) { + if(!outgoing->aip) { + outgoing->aip = outgoing->ai; + } else { + outgoing->aip = outgoing->aip->ai_next; + } + + if(outgoing->aip) { + return true; + } + + if(outgoing->ai) { + freeaddrinfo(outgoing->ai); + } + + outgoing->ai = NULL; + outgoing->aip = NULL; + outgoing->state = OUTGOING_END; + } + + if(outgoing->state == OUTGOING_RECENT) { + if(!outgoing->aip) { + outgoing->ai = get_recent_addresses(outgoing->node); + outgoing->aip = outgoing->ai; + } else { + outgoing->aip = outgoing->aip->ai_next; + } + + if(outgoing->aip) { + return true; + } + + free_known_addresses(outgoing->ai); + outgoing->ai = NULL; + outgoing->aip = NULL; + outgoing->state = OUTGOING_KNOWN; + } + + if(outgoing->state == OUTGOING_KNOWN) { + if(!outgoing->aip) { + outgoing->ai = get_known_addresses(outgoing->node); + outgoing->aip = outgoing->ai; + } else { + outgoing->aip = outgoing->aip->ai_next; + } + + if(outgoing->aip) { + return true; + } + + free_known_addresses(outgoing->ai); + outgoing->ai = NULL; + outgoing->aip = NULL; + outgoing->state = OUTGOING_END; + } + + if(start) { + outgoing->state = OUTGOING_NO_KNOWN_ADDRESSES; + } + + return false; +} + +void do_outgoing_connection(meshlink_handle_t *mesh, outgoing_t *outgoing) { +begin: + + if(!get_next_outgoing_address(mesh, outgoing)) { + if(outgoing->state == OUTGOING_CANONICAL_RESOLVE) { + /* We are waiting for a callback from the ADNS thread */ + } else if(outgoing->state == OUTGOING_NO_KNOWN_ADDRESSES) { + logger(mesh, MESHLINK_ERROR, "No known addresses for %s", outgoing->node->name); + list_delete(mesh->outgoings, outgoing); + } else { + logger(mesh, MESHLINK_ERROR, "Could not set up a meta connection to %s", outgoing->node->name); + retry_outgoing(mesh, outgoing); + } + + return; + } + + connection_t *c = new_connection(); + c->outgoing = outgoing; + + memcpy(&c->address, outgoing->aip->ai_addr, outgoing->aip->ai_addrlen); + + if(mesh->log_level <= MESHLINK_INFO) { + char *hostname = sockaddr2hostname(&c->address); + logger(mesh, MESHLINK_INFO, "Trying to connect to %s at %s", outgoing->node->name, hostname); + free(hostname); + } + + c->socket = socket(c->address.sa.sa_family, SOCK_STREAM, IPPROTO_TCP); + + if(c->socket == -1) { + if(mesh->log_level <= MESHLINK_ERROR) { + char *hostname = sockaddr2hostname(&c->address); + logger(mesh, MESHLINK_ERROR, "Creating socket for %s at %s failed: %s", c->name, hostname, sockstrerror(sockerrno)); + free(hostname); + } + + free_connection(c); + goto begin; + } + + configure_tcp(c); + +#ifdef FD_CLOEXEC + fcntl(c->socket, F_SETFD, FD_CLOEXEC); +#endif + +#if defined(IPV6_V6ONLY) + + if(c->address.sa.sa_family == AF_INET6) { + static const int option = 1; + setsockopt(c->socket, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&option, sizeof(option)); + } + +#endif + + /* Connect */ + + int result = connect(c->socket, &c->address.sa, SALEN(c->address.sa)); + + if(result == -1 && !sockinprogress(sockerrno)) { + if(mesh->log_level <= MESHLINK_ERROR) { + char *hostname = sockaddr2hostname(&c->address); + logger(mesh, MESHLINK_ERROR, "Could not connect to %s: %s", outgoing->node->name, sockstrerror(sockerrno)); + free(hostname); + } + + free_connection(c); + goto begin; + } + + /* Now that there is a working socket, fill in the rest and register this connection. */ + + c->status.connecting = true; + c->status.initiator = true; + c->name = xstrdup(outgoing->node->name); + c->last_ping_time = mesh->loop.now.tv_sec; + + connection_add(mesh, c); + + io_add(&mesh->loop, &c->io, handle_meta_io, c, c->socket, IO_READ | IO_WRITE); +} + +void reset_outgoing(outgoing_t *outgoing) { + if(outgoing->ai) { + if(outgoing->state == OUTGOING_RECENT || outgoing->state == OUTGOING_KNOWN) { + free_known_addresses(outgoing->ai); + } else { + freeaddrinfo(outgoing->ai); + } + } + + outgoing->ai = NULL; + outgoing->aip = NULL; + outgoing->state = OUTGOING_START; +} + +void setup_outgoing_connection(meshlink_handle_t *mesh, outgoing_t *outgoing) { + timeout_del(&mesh->loop, &outgoing->ev); + + if(outgoing->node->connection) { + logger(mesh, MESHLINK_INFO, "Already connected to %s", outgoing->node->name); + + outgoing->node->connection->outgoing = outgoing; + return; + } + + reset_outgoing(outgoing); + + if(outgoing->node->status.blacklisted) { + return; + } + + if(mesh->connection_try_cb) { + mesh->connection_try_cb(mesh, (meshlink_node_t *)outgoing->node); + } + + do_outgoing_connection(mesh, outgoing); +} + +/// Delayed close of a filedescriptor. +static void tarpit(meshlink_handle_t *mesh, int fd) { + if(!fd) { + return; + } + + if(mesh->pits[mesh->next_pit]) { + closesocket(mesh->pits[mesh->next_pit]); + } + + mesh->pits[mesh->next_pit++] = fd; + + if(mesh->next_pit >= (int)(sizeof mesh->pits / sizeof mesh->pits[0])) { + mesh->next_pit = 0; + } +} + +/* + accept a new tcp connect and create a + new connection +*/ +void handle_new_meta_connection(event_loop_t *loop, void *data, int flags) { + (void)flags; + meshlink_handle_t *mesh = loop->data; + listen_socket_t *l = data; + connection_t *c; + sockaddr_t sa; + int fd; + socklen_t len = sizeof(sa); + + memset(&sa, 0, sizeof(sa)); + + fd = accept(l->tcp.fd, &sa.sa, &len); + + if(fd < 0) { + if(sockwouldblock(errno)) { + return; + } + + if(errno == EINVAL) { // TODO: check if Windows agrees + event_loop_stop(loop); + return; + } + + logger(mesh, MESHLINK_ERROR, "Accepting a new connection failed: %s", sockstrerror(sockerrno)); + return; + } + + sockaddrunmap(&sa); + + /* Rate limit incoming connections to max_connection_burst/second. */ + + if(mesh->loop.now.tv_sec != mesh->connection_burst_time) { + mesh->connection_burst_time = mesh->loop.now.tv_sec; + mesh->connection_burst = 0; + } + + if(mesh->connection_burst >= max_connection_burst) { + tarpit(mesh, fd); + return; + } + + mesh->connection_burst++; + + // Accept the new connection + + c = new_connection(); + c->name = xstrdup(""); + + c->address = sa; + c->socket = fd; + c->last_ping_time = mesh->loop.now.tv_sec; + + char *hostname = sockaddr2hostname(&sa); + logger(mesh, MESHLINK_INFO, "Connection from %s", hostname); + free(hostname); + + io_add(&mesh->loop, &c->io, handle_meta_io, c, c->socket, IO_READ); + + configure_tcp(c); + + connection_add(mesh, c); + + c->allow_request = ID; + send_id(mesh, c); +} + +static void free_outgoing(outgoing_t *outgoing) { + meshlink_handle_t *mesh = outgoing->node->mesh; + + timeout_del(&mesh->loop, &outgoing->ev); + + if(outgoing->ai) { + if(outgoing->state == OUTGOING_RECENT || outgoing->state == OUTGOING_KNOWN) { + free_known_addresses(outgoing->ai); + } else { + freeaddrinfo(outgoing->ai); + } + } + + free(outgoing); +} + +void init_outgoings(meshlink_handle_t *mesh) { + mesh->outgoings = list_alloc((list_action_t)free_outgoing); +} + +void exit_outgoings(meshlink_handle_t *mesh) { + if(mesh->outgoings) { + list_delete_list(mesh->outgoings); + mesh->outgoings = NULL; + } +} diff --git a/src/netutl.c b/src/netutl.c new file mode 100644 index 0000000..79dadea --- /dev/null +++ b/src/netutl.c @@ -0,0 +1,360 @@ +/* + netutl.c -- some supporting network utility code + Copyright (C) 2014-2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "meshlink_internal.h" +#include "net.h" +#include "netutl.h" +#include "logger.h" +#include "utils.h" +#include "xalloc.h" + +/* + Turn a string into a struct addrinfo. + Return NULL on failure. +*/ +struct addrinfo *str2addrinfo(const char *address, const char *service, int socktype) { + struct addrinfo *ai; + int err; + + struct addrinfo hint = { + .ai_family = AF_UNSPEC, + .ai_socktype = socktype, + }; + + err = getaddrinfo(address, service, &hint, &ai); + + if(err) { + logger(NULL, MESHLINK_WARNING, "Error looking up %s port %s: %s", address, service, err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); + return NULL; + } + + return ai; +} + +sockaddr_t str2sockaddr(const char *address, const char *port) { + struct addrinfo *ai; + sockaddr_t result; + int err; + + memset(&result, 0, sizeof(result)); + + struct addrinfo hint = { + .ai_family = AF_UNSPEC, + .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV, + .ai_socktype = SOCK_STREAM, + }; + + err = getaddrinfo(address, port, &hint, &ai); + + if(err || !ai) { + logger(NULL, MESHLINK_DEBUG, "Unknown type address %s port %s", address, port); + result.sa.sa_family = AF_UNKNOWN; + result.unknown.address = xstrdup(address); + result.unknown.port = xstrdup(port); + return result; + } + + memcpy(&result, ai->ai_addr, ai->ai_addrlen); + freeaddrinfo(ai); + + return result; +} + +sockaddr_t str2sockaddr_random(struct meshlink_handle *mesh, const char *address, const char *port) { + struct addrinfo *ai; + sockaddr_t result; + int err; + + memset(&result, 0, sizeof(result)); + + struct addrinfo hint = { + .ai_family = AF_UNSPEC, + .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV, + .ai_socktype = SOCK_STREAM, + }; + + err = getaddrinfo(address, port, &hint, &ai); + + if(err || !ai) { + result.sa.sa_family = AF_UNKNOWN; + result.unknown.address = NULL; + result.unknown.port = NULL; + return result; + } + + int count = 0; + + for(struct addrinfo *aip = ai; aip; aip = aip->ai_next) { + count++; + } + + struct addrinfo *aip = ai; + + for(count = prng(mesh, count); count--; aip = aip->ai_next); + + memcpy(&result, aip->ai_addr, aip->ai_addrlen); + freeaddrinfo(ai); + + return result; +} + +void sockaddr2str(const sockaddr_t *sa, char **addrstr, char **portstr) { + char address[NI_MAXHOST]; + char port[NI_MAXSERV]; + char *scopeid; + int err; + + if(sa->sa.sa_family == AF_UNKNOWN) { + if(addrstr) { + *addrstr = xstrdup(sa->unknown.address); + } + + if(portstr) { + *portstr = xstrdup(sa->unknown.port); + } + + return; + } + + err = getnameinfo(&sa->sa, SALEN(sa->sa), address, sizeof(address), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV); + + if(err) { + logger(NULL, MESHLINK_ERROR, "Error while translating addresses: %s", err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); + abort(); + } + + scopeid = strchr(address, '%'); + + if(scopeid) { + *scopeid = '\0'; /* Descope. */ + } + + if(addrstr) { + *addrstr = xstrdup(address); + } + + if(portstr) { + *portstr = xstrdup(port); + } +} + +char *sockaddr2hostname(const sockaddr_t *sa) { + char *str; + char address[NI_MAXHOST] = "unknown"; + char port[NI_MAXSERV] = "unknown"; + int err; + + if(sa->sa.sa_family == AF_UNKNOWN) { + xasprintf(&str, "%s port %s", sa->unknown.address, sa->unknown.port); + return str; + } + + err = getnameinfo(&sa->sa, SALEN(sa->sa), address, sizeof(address), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV); + + if(err) { + logger(NULL, MESHLINK_ERROR, "Error while looking up hostname: %s", err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); + abort(); + } + + xasprintf(&str, "%s port %s", address, port); + + return str; +} + +int sockaddrcmp_noport(const sockaddr_t *a, const sockaddr_t *b) { + int result; + + result = a->sa.sa_family - b->sa.sa_family; + + if(result) { + return result; + } + + switch(a->sa.sa_family) { + case AF_UNSPEC: + return 0; + + case AF_UNKNOWN: + return strcmp(a->unknown.address, b->unknown.address); + + case AF_INET: + return memcmp(&a->in.sin_addr, &b->in.sin_addr, sizeof(a->in.sin_addr)); + + case AF_INET6: + return memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof(a->in6.sin6_addr)); + + default: + logger(NULL, MESHLINK_ERROR, "sockaddrcmp() was called with unknown address family %d, exitting!", + a->sa.sa_family); + abort(); + } +} + +int sockaddrcmp(const sockaddr_t *a, const sockaddr_t *b) { + int result; + + result = a->sa.sa_family - b->sa.sa_family; + + if(result) { + return result; + } + + switch(a->sa.sa_family) { + case AF_UNSPEC: + return 0; + + case AF_UNKNOWN: + result = strcmp(a->unknown.address, b->unknown.address); + + if(result) { + return result; + } + + return strcmp(a->unknown.port, b->unknown.port); + + case AF_INET: + result = memcmp(&a->in.sin_addr, &b->in.sin_addr, sizeof(a)->in.sin_addr); + + if(result) { + return result; + } + + return memcmp(&a->in.sin_port, &b->in.sin_port, sizeof(a)->in.sin_port); + + case AF_INET6: + result = memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof(a)->in6.sin6_addr); + + if(result) { + return result; + } + + return memcmp(&a->in6.sin6_port, &b->in6.sin6_port, sizeof(a)->in6.sin6_port); + + default: + logger(NULL, MESHLINK_ERROR, "sockaddrcmp() was called with unknown address family %d, exitting!", + a->sa.sa_family); + abort(); + } +} + +void sockaddrcpy(sockaddr_t *a, const sockaddr_t *b) { + if(b->sa.sa_family != AF_UNKNOWN) { + *a = *b; + } else { + a->unknown.family = AF_UNKNOWN; + a->unknown.address = xstrdup(b->unknown.address); + a->unknown.port = xstrdup(b->unknown.port); + } +} + +void sockaddrcpy_setport(sockaddr_t *a, const sockaddr_t *b, uint16_t port) { + sockaddrcpy(a, b); + + switch(b->sa.sa_family) { + case AF_INET: + a->in.sin_port = htons(port); + break; + + case AF_INET6: + a->in6.sin6_port = htons(port); + break; + + default: + break; + } +} + +void sockaddrfree(sockaddr_t *a) { + if(a->sa.sa_family == AF_UNKNOWN) { + free(a->unknown.address); + free(a->unknown.port); + } +} + +void sockaddrunmap(sockaddr_t *sa) { + if(sa->sa.sa_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&sa->in6.sin6_addr)) { + sa->in.sin_addr.s_addr = ((uint32_t *) & sa->in6.sin6_addr)[3]; + sa->in.sin_family = AF_INET; + } +} + +void packmsg_add_sockaddr(packmsg_output_t *out, const sockaddr_t *sa) { + switch(sa->sa.sa_family) { + case AF_INET: { + uint8_t buf[6]; + memcpy(buf + 0, &sa->in.sin_port, 2); + memcpy(buf + 2, &sa->in.sin_addr, 4); + packmsg_add_ext(out, 4, buf, sizeof(buf)); + break; + } + + case AF_INET6: { + uint8_t buf[18]; + memcpy(buf + 0, &sa->in6.sin6_port, 2); + memcpy(buf + 2, &sa->in6.sin6_addr, 16); + packmsg_add_ext(out, 6, buf, sizeof(buf)); + break; + } + + default: + packmsg_output_invalidate(out); + break; + } +} + +sockaddr_t packmsg_get_sockaddr(packmsg_input_t *in) { + sockaddr_t sa; + memset(&sa, 0, sizeof sa); + + int8_t type; + const void *data; + uint32_t len = packmsg_get_ext_raw(in, &type, &data); + + switch(type) { + case 4: + if(len != 6) { + packmsg_input_invalidate(in); + return sa; + } + + sa.sa.sa_family = AF_INET; + memcpy(&sa.in.sin_port, (uint8_t *)data + 0, 2); + memcpy(&sa.in.sin_addr, (uint8_t *)data + 2, 4); + break; + + case 6: + if(len != 18) { + packmsg_input_invalidate(in); + return sa; + } + + sa.sa.sa_family = AF_INET6; + memcpy(&sa.in6.sin6_port, (uint8_t *)data + 0, 2); + memcpy(&sa.in6.sin6_addr, (uint8_t *)data + 2, 16); + break; + + default: + packmsg_input_invalidate(in); + return sa; + } + + return sa; +} diff --git a/src/netutl.h b/src/netutl.h new file mode 100644 index 0000000..69346bc --- /dev/null +++ b/src/netutl.h @@ -0,0 +1,41 @@ +#ifndef MESHLINK_NETUTL_H +#define MESHLINK_NETUTL_H + +/* + netutl.h -- header file for netutl.c + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "net.h" +#include "packmsg.h" + +struct addrinfo *str2addrinfo(const char *, const char *, int) __attribute__((__malloc__)); +sockaddr_t str2sockaddr(const char *, const char *); +sockaddr_t str2sockaddr_random(struct meshlink_handle *mesh, const char *, const char *); +void sockaddr2str(const sockaddr_t *, char **, char **); +char *sockaddr2hostname(const sockaddr_t *) __attribute__((__malloc__)); +int sockaddrcmp(const sockaddr_t *, const sockaddr_t *) __attribute__((__warn_unused_result__)); +int sockaddrcmp_noport(const sockaddr_t *, const sockaddr_t *) __attribute__((__warn_unused_result__)); +void sockaddrunmap(sockaddr_t *); +void sockaddrfree(sockaddr_t *); +void sockaddrcpy(sockaddr_t *, const sockaddr_t *); +void sockaddrcpy_setport(sockaddr_t *, const sockaddr_t *, uint16_t port); + +void packmsg_add_sockaddr(struct packmsg_output *out, const sockaddr_t *); +sockaddr_t packmsg_get_sockaddr(struct packmsg_input *in) __attribute__((__warn_unused_result__)); + +#endif diff --git a/src/node.c b/src/node.c new file mode 100644 index 0000000..b8caed6 --- /dev/null +++ b/src/node.c @@ -0,0 +1,175 @@ +/* + node.c -- node tree management + Copyright (C) 2014 Guus Sliepen , + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "hash.h" +#include "logger.h" +#include "meshlink_internal.h" +#include "net.h" +#include "netutl.h" +#include "node.h" +#include "splay_tree.h" +#include "utils.h" +#include "xalloc.h" + +static int node_compare(const node_t *a, const node_t *b) { + return strcmp(a->name, b->name); +} + +void init_nodes(meshlink_handle_t *mesh) { + mesh->nodes = splay_alloc_tree((splay_compare_t) node_compare, (splay_action_t) free_node); + mesh->node_udp_cache = hash_alloc(0x100, sizeof(sockaddr_t)); +} + +void exit_nodes(meshlink_handle_t *mesh) { + if(mesh->node_udp_cache) { + hash_free(mesh->node_udp_cache); + } + + if(mesh->nodes) { + splay_delete_tree(mesh->nodes); + } + + mesh->node_udp_cache = NULL; + mesh->nodes = NULL; +} + +node_t *new_node(void) { + node_t *n = xzalloc(sizeof(*n)); + + n->edge_tree = new_edge_tree(); + n->mtu = MTU; + n->maxmtu = MTU; + n->devclass = DEV_CLASS_UNKNOWN; + + return n; +} + +void free_node(node_t *n) { + n->status.destroyed = true; + + utcp_exit(n->utcp); + + if(n->edge_tree) { + free_edge_tree(n->edge_tree); + } + + sockaddrfree(&n->address); + + ecdsa_free(n->ecdsa); + sptps_stop(&n->sptps); + + if(n->mtutimeout.cb) { + abort(); + } + + free(n->name); + free(n->canonical_address); + + free(n); +} + +void node_add(meshlink_handle_t *mesh, node_t *n) { + n->mesh = mesh; + splay_insert(mesh->nodes, n); +} + +void node_del(meshlink_handle_t *mesh, node_t *n) { + timeout_del(&mesh->loop, &n->mtutimeout); + + for splay_each(edge_t, e, n->edge_tree) { + edge_del(mesh, e); + } + + splay_delete(mesh->nodes, n); +} + +node_t *lookup_node(meshlink_handle_t *mesh, const char *name) { + const node_t n = {.name = (char *)name}; + node_t *result; + + result = splay_search(mesh->nodes, &n); + + return result; +} + +node_t *lookup_node_udp(meshlink_handle_t *mesh, const sockaddr_t *sa) { + return hash_search(mesh->node_udp_cache, sa); +} + +void update_node_udp(meshlink_handle_t *mesh, node_t *n, const sockaddr_t *sa) { + if(n == mesh->self) { + logger(mesh, MESHLINK_WARNING, "Trying to update UDP address of mesh->self!"); + return; + } + + hash_insert(mesh->node_udp_cache, &n->address, NULL); + + if(sa) { + n->address = *sa; + n->sock = 0; + + for(int i = 0; i < mesh->listen_sockets; i++) { + if(mesh->listen_socket[i].sa.sa.sa_family == sa->sa.sa_family) { + n->sock = i; + break; + } + } + + hash_insert(mesh->node_udp_cache, sa, n); + + node_add_recent_address(mesh, n, sa); + + if(mesh->log_level <= MESHLINK_DEBUG) { + char *hostname = sockaddr2hostname(&n->address); + logger(mesh, MESHLINK_DEBUG, "UDP address of %s set to %s", n->name, hostname); + free(hostname); + } + } +} + +bool node_add_recent_address(meshlink_handle_t *mesh, node_t *n, const sockaddr_t *sa) { + (void)mesh; + bool found = false; + int i; + + /* Check if we already know this address */ + for(i = 0; i < MAX_RECENT && n->recent[i].sa.sa_family; i++) { + if(!sockaddrcmp(&n->recent[i], sa)) { + found = true; + break; + } + } + + if(found && i == 0) { + /* It's already the most recent address, nothing to do. */ + return false; + } + + if(i >= MAX_RECENT) { + i = MAX_RECENT - 1; + } + + memmove(n->recent + 1, n->recent, i * sizeof(*n->recent)); + memcpy(n->recent, sa, SALEN(sa->sa)); + + n->status.dirty = true; + return !found; +} diff --git a/src/node.h b/src/node.h new file mode 100644 index 0000000..63f3c2c --- /dev/null +++ b/src/node.h @@ -0,0 +1,113 @@ +#ifndef MESHLINK_NODE_H +#define MESHLINK_NODE_H + +/* + node.h -- header for node.c + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "event.h" +#include "sockaddr.h" +#include "sptps.h" +#include "utcp.h" +#include "submesh.h" + +typedef struct node_status_t { + uint16_t validkey: 1; /* 1 if we currently have a valid key for him */ + uint16_t waitingforkey: 1; /* 1 if we already sent out a request */ + uint16_t visited: 1; /* 1 if this node has been visited by one of the graph algorithms */ + uint16_t reachable: 1; /* 1 if this node is reachable in the graph */ + uint16_t udp_confirmed: 1; /* 1 if the address is one that we received UDP traffic on */ + uint16_t broadcast: 1; /* 1 if the next UDP packet should be broadcast to the local network */ + uint16_t blacklisted: 1; /* 1 if the node is blacklist so we never want to speak with him anymore */ + uint16_t destroyed: 1; /* 1 if the node is being destroyed, deallocate channels when any callback is triggered */ + uint16_t duplicate: 1; /* 1 if the node is duplicate, ie. multiple nodes using the same Name are online */ + uint16_t dirty: 1; /* 1 if the configuration of the node is dirty and needs to be written out */ + uint16_t want_udp: 1; /* 1 if we want working UDP because we have data to send */ +} node_status_t; + +#define MAX_RECENT 5 + +typedef struct node_t { + // Public member variables + char *name; /* name of this node */ + void *priv; + + // Private member variables + node_status_t status; + uint16_t minmtu; /* Probed minimum MTU */ + dev_class_t devclass; + + // Used for packet I/O + int sock; /* Socket to use for outgoing UDP packets */ + uint32_t session_id; /* Unique ID for this node's currently running process */ + sptps_t sptps; + sockaddr_t address; /* his real (internet) ip to send UDP packets to */ + + struct utcp *utcp; + + // Traffic counters + uint64_t in_packets; + uint64_t in_bytes; + uint64_t out_packets; + uint64_t out_bytes; + + // MTU probes + timeout_t mtutimeout; /* Probe event */ + int mtuprobes; /* Number of probes */ + uint16_t mtu; /* Maximum size of packets to send to this node */ + uint16_t maxmtu; /* Probed maximum MTU */ + + // Used for meta-connection I/O, timeouts + struct meshlink_handle *mesh; /* The mesh this node belongs to */ + struct submesh_t *submesh; /* Nodes Sub-Mesh Handle*/ + + time_t last_req_key; + + struct ecdsa *ecdsa; /* His public ECDSA key */ + + struct connection_t *connection; /* Connection associated with this node (if a direct connection exists) */ + time_t last_connect_try; + time_t last_successfull_connection; + + char *canonical_address; /* The canonical address of this node, if known */ + sockaddr_t recent[MAX_RECENT]; /* Recently seen addresses */ + sockaddr_t catta_address; /* Latest address seen by Catta */ + + // Graph-related member variables + time_t last_reachable; + time_t last_unreachable; + + int distance; + struct node_t *nexthop; /* nearest node from us to him */ + struct edge_t *prevedge; /* nearest node from him to us */ + + struct splay_tree_t *edge_tree; /* Edges with this node as one of the endpoints */ +} node_t; + +void init_nodes(struct meshlink_handle *mesh); +void exit_nodes(struct meshlink_handle *mesh); +node_t *new_node(void) __attribute__((__malloc__)); +void free_node(node_t *n); +void node_add(struct meshlink_handle *mesh, node_t *n); +void node_del(struct meshlink_handle *mesh, node_t *n); +node_t *lookup_node(struct meshlink_handle *mesh, const char *name) __attribute__((__warn_unused_result__)); +node_t *lookup_node_udp(struct meshlink_handle *mesh, const sockaddr_t *sa) __attribute__((__warn_unused_result__)); +void update_node_udp(struct meshlink_handle *mesh, node_t *n, const sockaddr_t *sa); +bool node_add_recent_address(struct meshlink_handle *mesh, node_t *n, const sockaddr_t *addr); + +#endif diff --git a/src/packmsg.h b/src/packmsg.h new file mode 100644 index 0000000..675011e --- /dev/null +++ b/src/packmsg.h @@ -0,0 +1,2044 @@ +#pragma once + +/* + SPDX-License-Identifier: BSD-3-Clause + + packmsg.h -- Little-endian MessagePack implementation, optimized for speed + Copyright (C) 2018 Guus Sliepen + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + DAMAGE. +*/ + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define packmsg_likely(x) __builtin_expect(!!(x), 1) +#define packmsg_unlikely(x) __builtin_expect(!!(x), 0) + +/** \mainpage PackMessage, a safe and fast header-only C library for little-endian MessagePack encoding and decoding. + * + * This library can encode and decode MessagePack objects, however it differs in one important point + * from the official MessagePack specification: PackMessage stores all values in little-endian format. + * PackMessage offers a simple streaming API for encoding and decoding. + * + * PackMessage is *safe*: + * + * * Reads from and writes to buffers are always bounds checked. + * * String, binary and extension data can be read into buffers allocated by PackMessage using simple API calls. + * * Any error will result in null values and pointers being returned, and/or application-allocated buffers for strings will be zero-terminated, so there is no undefined state. + * * Once an encoding/decoding error occurs, all subsequent operations on the same buffer will also fail. + * * The API is designed to follow the principle of least surprise, and makes it hard to use in a wrong way. + * + * PackMessage is *fast*: + * + * * Values are stored in little-endian format, since virtually all mainstream processors are little-endian, or they can switch between endianness and are probably running an operating system that has configured it to be little-endian. This saves the overhead of converting to and from big-endian format. + * * No memory allocation is done unless requested. + * * The application can get const pointers to string, binary and extension data pointing into the input buffer if desired, avoiding copies. + * * The application does not have to check for errors after for every operation; it can be done once after encoding/decoding a buffer if desired. + * * The library is header-only, allowing the compiler to inline all functions and better optimize your application. + * + * ## API overview + * + * For encoding, a packmsg_output_t variable must be initialized + * with a pointer to the start of an output buffer, and its size. + * Elements can then be encoded using packmsg_add_*() functions. + * When all desired elements have been added, the length of the encoded message + * can be retrieved using the packmsg_output_size() function. + * + * For decoding, a packmsg_input_t variable must be initialized + * with a const pointer to the start of an input buffer, and its size. + * Elements can then be decoded using packmsg_get_*() functions. + * If the type of elements in a message is not known up front, then + * the type of the next element can be queried using packmsg_get_type() + * or packmsg_is_*() functions. To check that the complete message has been decoded + * correctly, the function packmsg_done() can be called. + * + * ## Example code + * + * @ref example.c + * + * \example example.c + * + * This is an example of how to encode and decode the equivalent of the JSON object `{"compact": true, "schema": 0}` + * using PackMessage. + */ + +/* Buffer iterators + * ================ + */ + +/** \brief Iterator for PackMessage output. + * + * This is an iterator that has to be initialized with a pointer to + * an output buffer that is allocated by the application, + * and the length of that buffer. A pointer to it is passed to all + * packmsg_add_*() functions. + */ +typedef struct packmsg_output { + uint8_t *ptr; /**< A pointer into a buffer. */ + ptrdiff_t len; /**< The remaining length of the buffer, or -1 in case of errors. */ +} packmsg_output_t; + +/** \brief Iterator for PackMessage input. + * + * This is an iterator that has to be initialized with a pointer to + * an input buffer that is allocated by the application, + * and the length of that buffer. A pointer to it is passed to all + * packmsg_get_*() functions. + */ +typedef struct packmsg_input { + const uint8_t *ptr; /**< A pointer into a buffer. */ + ptrdiff_t len; /**< The remaining length of the buffer, or -1 in case of errors. */ +} packmsg_input_t; + +/* Checks + * ====== + */ + +/** \brief Check if the PackMessage output buffer is in a valid state. + * \memberof packmsg_output + * + * This function checks if all operations performed on the output buffer so far + * have all completed successfully, and the buffer contains a valid PackMessage message. + * + * \param buf A pointer to an output buffer iterator. + * + * \return True if all write operations performed on the output buffer so far have completed successfully, + * false if any error has occurred. + */ +static inline bool packmsg_output_ok(const packmsg_output_t *buf) { + assert(buf); + + return packmsg_likely(buf->len >= 0); +} + +/** \brief Calculate the amount of bytes written to the output buffer. + * \memberof packmsg_output + * + * This function calculates the amount of bytes written to the output buffer + * based on the current position of the output iterator, and a pointer to the start of the buffer. + * + * \param buf A pointer to an output buffer iterator. + * \param start A pointer to the start of the output buffer. + * + * \return The total amount of bytes written to the output buffer, + * or 0 if any error has occurred. + */ +static inline size_t packmsg_output_size(const packmsg_output_t *buf, const uint8_t *start) { + if(packmsg_likely(packmsg_output_ok(buf))) { + return buf->ptr - start; + } else { + return 0; + } +} + +/** \brief Check if the PackMessage input buffer is in a valid state. + * \memberof packmsg_input + * + * This function checks if all operations performed on the input buffer so far + * have all completed successfully, and the buffer contains a valid PackMessage message. + * + * \param buf A pointer to an input buffer iterator. + * + * \return True if all read operations performed on the input buffer so far have completed successfully, + * false if any error has occurred. + */ +static inline __attribute__((__warn_unused_result__)) bool packmsg_input_ok(const packmsg_input_t *buf) { + assert(buf); + + return packmsg_likely(buf->len >= 0); +} + +/** \brief Check if the PackMessage input buffer has been read completely. + * \memberof packmsg_input + * + * This function checks if all data in the input buffer has been consumed + * by input operations. This function should always be called after the last + * input operation, when one expects the whole buffer to have been read. + * + * \param buf A pointer to an input buffer iterator. + * + * \return True if the whole input buffer has been read successfully, + * false if there is still data remaining in the input buffer, + * or if any error has occurred. + */ +static inline __attribute__((__warn_unused_result__)) bool packmsg_done(const packmsg_input_t *buf) { + assert(buf); + + return buf->len == 0; +} + +/* Invalidation functions + * ====================== + */ + +/** \brief Invalidate an output iterator. + * \memberof packmsg_output + * + * This function invalidates an output iterator. This signals that an error occurred, + * and prevents further output to be written. + * + * \param buf A pointer to an output buffer iterator. + */ +static inline void packmsg_output_invalidate(packmsg_output_t *buf) { + buf->len = -1; +} + +/** \brief Invalidate an input iterator. + * \memberof packmsg_input + * + * This function invalidates an input iterator. This signals that an error occurred, + * and prevents further input to be read. + * + * \param buf A pointer to an input buffer iterator. + */ +static inline void packmsg_input_invalidate(packmsg_input_t *buf) { + buf->len = -1; +} + +/* Encoding functions + * ================== + */ + +/** \brief Internal function, do not use. */ +static inline void packmsg_write_hdr_(packmsg_output_t *buf, uint8_t hdr) { + assert(buf); + assert(buf->ptr); + + if(packmsg_likely(buf->len > 0)) { + *buf->ptr = hdr; + buf->ptr++; + buf->len--; + } else { + packmsg_output_invalidate(buf); + } +} + +/** \brief Internal function, do not use. */ +static inline void packmsg_write_data_(packmsg_output_t *buf, const void *data, uint32_t dlen) { + assert(buf); + assert(buf->ptr); + assert(data); + + if(packmsg_likely(buf->len >= 0 && (uint32_t)buf->len >= dlen)) { + memcpy(buf->ptr, data, dlen); + buf->ptr += dlen; + buf->len -= dlen; + } else { + packmsg_output_invalidate(buf); + } +} + +/** \brief Internal function, do not use. */ +static inline void packmsg_write_hdrdata_(packmsg_output_t *buf, uint8_t hdr, const void *data, uint32_t dlen) { + assert(buf); + assert(buf->ptr); + assert(data); + + if(packmsg_likely(buf->len > 0 && (uint32_t)buf->len > dlen)) { + *buf->ptr = hdr; + buf->ptr++; + buf->len--; + + memcpy(buf->ptr, data, dlen); + buf->ptr += dlen; + buf->len -= dlen; + } else { + packmsg_output_invalidate(buf); + } +} + +/** \brief Internal function, do not use. */ +static inline void *packmsg_reserve_(packmsg_output_t *buf, uint32_t len) { + assert(buf); + assert(buf->ptr); + + if(packmsg_likely(buf->len >= 0 && (uint32_t)buf->len >= len)) { + void *ptr = buf->ptr; + buf->ptr += len; + buf->len -= len; + return ptr; + } else { + packmsg_output_invalidate(buf); + return NULL; + } +} + +/** \brief Add a NIL to the output. + * \memberof packmsg_output + * + * \param buf A pointer to an output buffer iterator. + */ +static inline void packmsg_add_nil(packmsg_output_t *buf) { + packmsg_write_hdr_(buf, 0xc0); +} + +/** \brief Add a boolean value to the output. + * \memberof packmsg_output + * + * \param buf A pointer to an output buffer iterator. + * \param val The value to add. + */ +static inline void packmsg_add_bool(packmsg_output_t *buf, bool val) { + packmsg_write_hdr_(buf, val ? 0xc3 : 0xc2); +} + +/** \brief Add an int8 value to the output. + * \memberof packmsg_output + * + * \param buf A pointer to an output buffer iterator. + * \param val The value to add. + */ +static inline void packmsg_add_int8(packmsg_output_t *buf, int8_t val) { + if(val >= -32) { // fixint + packmsg_write_hdr_(buf, val); + } else { // TODO: negative fixint + packmsg_write_hdrdata_(buf, 0xd0, &val, 1); + } +} + +/** \brief Add an int16 value to the output. + * \memberof packmsg_output + * + * \param buf A pointer to an output buffer iterator. + * \param val The value to add. + */ +static inline void packmsg_add_int16(packmsg_output_t *buf, int16_t val) { + if((int8_t) val != val) { + packmsg_write_hdrdata_(buf, 0xd1, &val, 2); + } else { + packmsg_add_int8(buf, val); + } +} + +/** \brief Add an int32 value to the output. + * \memberof packmsg_output + * + * \param buf A pointer to an output buffer iterator. + * \param val The value to add. + */ +static inline void packmsg_add_int32(packmsg_output_t *buf, int32_t val) { + if((int16_t) val != val) { + packmsg_write_hdrdata_(buf, 0xd2, &val, 4); + } else { + packmsg_add_int16(buf, val); + } +} + +/** \brief Add an int64 value to the output. + * \memberof packmsg_output + * + * \param buf A pointer to an output buffer iterator. + * \param val The value to add. + */ +static inline void packmsg_add_int64(packmsg_output_t *buf, int64_t val) { + if((int32_t) val != val) { + packmsg_write_hdrdata_(buf, 0xd3, &val, 8); + } else { + packmsg_add_int32(buf, val); + } +} + +/** \brief Add a uint8 value to the output. + * \memberof packmsg_output + * + * \param buf A pointer to an output buffer iterator. + * \param val The value to add. + */ +static inline void packmsg_add_uint8(packmsg_output_t *buf, uint8_t val) { + if(val < 0x80) { // fixint + packmsg_write_hdr_(buf, val); + } else { + packmsg_write_hdrdata_(buf, 0xcc, &val, 1); + } +} + +/** \brief Add a uint16 value to the output. + * \memberof packmsg_output + * + * \param buf A pointer to an output buffer iterator. + * \param val The value to add. + */ +static inline void packmsg_add_uint16(packmsg_output_t *buf, uint16_t val) { + if(val & 0xff00) { + packmsg_write_hdrdata_(buf, 0xcd, &val, 2); + } else { + packmsg_add_uint8(buf, val); + } +} + +/** \brief Add a int32 value to the output. + * \memberof packmsg_output + * + * \param buf A pointer to an output buffer iterator. + * \param val The value to add. + */ +static inline void packmsg_add_uint32(packmsg_output_t *buf, uint32_t val) { + if(val & 0xffff0000) { + packmsg_write_hdrdata_(buf, 0xce, &val, 4); + } else { + packmsg_add_uint16(buf, val); + } +} + +/** \brief Add a int64 value to the output. + * \memberof packmsg_output + * + * \param buf A pointer to an output buffer iterator. + * \param val The value to add. + */ +static inline void packmsg_add_uint64(packmsg_output_t *buf, uint64_t val) { + if(val & 0xffffffff00000000) { + packmsg_write_hdrdata_(buf, 0xcf, &val, 8); + } else { + packmsg_add_uint32(buf, val); + } +} + +/** \brief Add a float value to the output. + * \memberof packmsg_output + * + * \param buf A pointer to an output buffer iterator. + * \param val The value to add. + */ +static inline void packmsg_add_float(packmsg_output_t *buf, float val) { + packmsg_write_hdrdata_(buf, 0xca, &val, 4); +} + +/** \brief Add a double value to the output. + * \memberof packmsg_output + * + * \param buf A pointer to an output buffer iterator. + * \param val The value to add. + */ +static inline void packmsg_add_double(packmsg_output_t *buf, double val) { + packmsg_write_hdrdata_(buf, 0xcb, &val, 8); +} + +/** \brief Add a string with a given length to the output. + * \memberof packmsg_output + * + * The string must be at least as long as the given length. + * Any NUL-bytes within the given length range will be included. + * + * \param buf A pointer to an output buffer iterator. + * \param str The string to add. + * \param len The length of the string in bytes. + */ +static inline void packmsg_add_str_raw(packmsg_output_t *buf, const char *str, uint32_t len) { + if(len < 32) { + packmsg_write_hdr_(buf, 0xa0 | (uint8_t) len); + } else if(len <= 0xff) { + packmsg_write_hdrdata_(buf, 0xd9, &len, 1); + } else if(len <= 0xffff) { + packmsg_write_hdrdata_(buf, 0xda, &len, 2); + } else { + packmsg_write_hdrdata_(buf, 0xdb, &len, 4); + } + + packmsg_write_data_(buf, str, len); +} + +/** \brief Add a string to the output. + * \memberof packmsg_output + * + * \param buf A pointer to an output buffer iterator. + * \param str The string to add. This must be a NUL-terminated string. + */ +static inline void packmsg_add_str(packmsg_output_t *buf, const char *str) { + size_t len = strlen(str); + + if(packmsg_likely(len <= 0xffffffff)) { + packmsg_add_str_raw(buf, str, len); + } else { + packmsg_output_invalidate(buf); + } +} + +/** \brief Reserve space for a string with a given length in the output. + * \memberof packmsg_output + * + * This writes a header for a string with the given length to the output, + * and reserves space for that string. + * The caller must fill in that space. + * + * \param buf A pointer to an output buffer iterator. + * \param len The length of the string in bytes. + * + * \return A pointer to the reserved space for the string, + * or NULL in case of an error. + */ +static inline char *packmsg_add_str_reserve(packmsg_output_t *buf, uint32_t len) { + if(len < 32) { + packmsg_write_hdr_(buf, 0xa0 | (uint8_t) len); + } else if(len <= 0xff) { + packmsg_write_hdrdata_(buf, 0xd9, &len, 1); + } else if(len <= 0xffff) { + packmsg_write_hdrdata_(buf, 0xda, &len, 2); + } else { + packmsg_write_hdrdata_(buf, 0xdb, &len, 4); + } + + return (char *)packmsg_reserve_(buf, len); +} + +/** \brief Add binary data to the output. + * \memberof packmsg_output + * + * \param buf A pointer to an output buffer iterator. + * \param data A pointer to the data to add. + * \param dlen The length of the data in bytes. + */ +static inline void packmsg_add_bin(packmsg_output_t *buf, const void *data, uint32_t dlen) { + if(dlen <= 0xff) { + packmsg_write_hdrdata_(buf, 0xc4, &dlen, 1); + } else if(dlen <= 0xffff) { + packmsg_write_hdrdata_(buf, 0xc5, &dlen, 2); + } else { + packmsg_write_hdrdata_(buf, 0xc6, &dlen, 4); + } + + packmsg_write_data_(buf, data, dlen); +} + +/** \brief Reserve space for binary data in the output. + * \memberof packmsg_output + * + * This writes a header for a block of data with the given length to the output, + * and reserves space for that data. + * The caller must fill in that space. + * + * \param buf A pointer to an output buffer iterator. + * \param dlen The length of the data in bytes. + * + * \return A pointer to the reserved space for the data, + * or NULL in case of an error. + */ +static inline void *packmsg_add_bin_reserve(packmsg_output_t *buf, uint32_t dlen) { + if(dlen <= 0xff) { + packmsg_write_hdrdata_(buf, 0xc4, &dlen, 1); + } else if(dlen <= 0xffff) { + packmsg_write_hdrdata_(buf, 0xc5, &dlen, 2); + } else { + packmsg_write_hdrdata_(buf, 0xc6, &dlen, 4); + } + + return packmsg_reserve_(buf, dlen); +} + +/** \brief Add extension data to the output. + * \memberof packmsg_output + * + * \param buf A pointer to an output buffer iterator. + * \param type The extension type. Values between 0 and 127 are application specific, + * values between -1 and -128 are reserved for future extensions. + * \param data A pointer to the data to add. + * \param dlen The length of the data in bytes. + */ +static inline void packmsg_add_ext(packmsg_output_t *buf, int8_t type, const void *data, uint32_t dlen) { + if(dlen <= 0xff) { + if(dlen == 16) { + packmsg_write_hdrdata_(buf, 0xd8, &type, 1); + } else if(dlen == 8) { + packmsg_write_hdrdata_(buf, 0xd7, &type, 1); + } else if(dlen == 4) { + packmsg_write_hdrdata_(buf, 0xd6, &type, 1); + } else if(dlen == 2) { + packmsg_write_hdrdata_(buf, 0xd5, &type, 1); + } else if(dlen == 1) { + packmsg_write_hdrdata_(buf, 0xd4, &type, 1); + } else { + packmsg_write_hdrdata_(buf, 0xc7, &dlen, 1); + packmsg_write_data_(buf, &type, 1); + } + } else if(dlen <= 0xffff) { + packmsg_write_hdrdata_(buf, 0xc8, &dlen, 2); + packmsg_write_data_(buf, &type, 1); + } else if(dlen <= 0xffffffff) { + packmsg_write_hdrdata_(buf, 0xc9, &dlen, 4); + packmsg_write_data_(buf, &type, 1); + } else { + packmsg_output_invalidate(buf); + return; + } + + packmsg_write_data_(buf, data, dlen); +} + +/** \brief Reserve space for extension data in the output. + * \memberof packmsg_output + * + * This writes a header for extension data with the given type + * and length to the output, + * and reserves space for that extension data. + * The caller must fill in that space. + * + * \param buf A pointer to an output buffer iterator. + * \param type The extension type. Values between 0 and 127 are application specific, + * values between -1 and -128 are reserved for future extensions. + * \param dlen The length of the data in bytes. + * + * \return A pointer to the reserved space for the extension data, + * or NULL in case of an error. + */ +static inline void *packmsg_add_ext_reserve(packmsg_output_t *buf, int8_t type, uint32_t dlen) { + if(dlen <= 0xff) { + if(dlen == 16) { + packmsg_write_hdrdata_(buf, 0xd8, &type, 1); + } else if(dlen == 8) { + packmsg_write_hdrdata_(buf, 0xd7, &type, 1); + } else if(dlen == 4) { + packmsg_write_hdrdata_(buf, 0xd6, &type, 1); + } else if(dlen == 2) { + packmsg_write_hdrdata_(buf, 0xd5, &type, 1); + } else if(dlen == 1) { + packmsg_write_hdrdata_(buf, 0xd4, &type, 1); + } else { + packmsg_write_hdrdata_(buf, 0xc7, &dlen, 1); + packmsg_write_data_(buf, &type, 1); + } + } else if(dlen <= 0xffff) { + packmsg_write_hdrdata_(buf, 0xc8, &dlen, 2); + packmsg_write_data_(buf, &type, 1); + } else { + packmsg_write_hdrdata_(buf, 0xc9, &dlen, 4); + packmsg_write_data_(buf, &type, 1); + } + + return packmsg_reserve_(buf, dlen); +} + +/** \brief Add a map header to the output. + * \memberof packmsg_output + * + * This function only adds an an indicator that the next 2 * count elements + * are a sequence of key-value pairs that make up the contents of the map. + * These key-value pairs have to be added by the application using regular + * packmsg_add_*() calls. + * + * \param buf A pointer to an output buffer iterator. + * \param count The number of elements in the map. + */ +static inline void packmsg_add_map(packmsg_output_t *buf, uint32_t count) { + if(count <= 0xf) { + packmsg_write_hdr_(buf, 0x80 | (uint8_t) count); + } else if(count <= 0xffff) { + packmsg_write_hdrdata_(buf, 0xde, &count, 2); + } else { + packmsg_write_hdrdata_(buf, 0xdf, &count, 4); + } +} + +/** \brief Add an array header to the output. + * \memberof packmsg_output + * + * This function only adds an an indicator that the next count elements + * are a sequence of elements that make up the contents of the array. + * These elements have to be added by the application using regular + * packmsg_add_*() calls. + * + * \param buf A pointer to an output buffer iterator. + * \param count The number of elements in the array. + */ +static inline void packmsg_add_array(packmsg_output_t *buf, uint32_t count) { + if(count <= 0xf) { + packmsg_write_hdr_(buf, 0x90 | (uint8_t) count); + } else if(count <= 0xffff) { + packmsg_write_hdrdata_(buf, 0xdc, &count, 2); + } else { + packmsg_write_hdrdata_(buf, 0xdd, &count, 4); + } +} + +/* Decoding functions + * ================== + */ + +/** \brief Internal function, do not use. */ +static inline uint8_t packmsg_read_hdr_(packmsg_input_t *buf) { + assert(buf); + assert(buf->ptr); + + if(packmsg_likely(buf->len > 0)) { + uint8_t hdr = *buf->ptr; + buf->ptr++; + buf->len--; + return hdr; + } else { + packmsg_input_invalidate(buf); + return 0xc1; + } +} + +/** \brief Internal function, do not use. */ +static inline void packmsg_read_data_(packmsg_input_t *buf, void *data, uint32_t dlen) { + assert(buf); + assert(buf->ptr); + assert(data); + + if(packmsg_likely(buf->len >= 0 && (uint32_t)buf->len >= dlen)) { + memcpy(data, buf->ptr, dlen); + buf->ptr += dlen; + buf->len -= dlen; + } else { + packmsg_input_invalidate(buf); + } +} + +/** \brief Internal function, do not use. */ +static inline uint8_t packmsg_peek_hdr_(const packmsg_input_t *buf) { + assert(buf); + assert(buf->ptr); + + if(packmsg_likely(buf->len > 0)) { + return *buf->ptr; + } else { + return 0xc1; + } +} + +/** \brief Get a NIL from the input. + * \memberof packmsg_input + * + * This function does not return anything, but will invalidate the input iterator + * if no NIL was successfully consumed from the input. + * + * \param buf A pointer to an input buffer iterator. + */ +static inline void packmsg_get_nil(packmsg_input_t *buf) { + if(packmsg_read_hdr_(buf) != 0xc0) { + packmsg_input_invalidate(buf); + } +} + + +/** \brief Get a boolean value from the input. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * \return The boolean value that was read from the input, + * or false in case of an error. + */ +static inline bool packmsg_get_bool(packmsg_input_t *buf) { + uint8_t hdr = packmsg_read_hdr_(buf); + + if(hdr == 0xc2) { + return false; + } else if(hdr == 0xc3) { + return true; + } else { + packmsg_input_invalidate(buf); + return false; + } +} + +/** \brief Get an int8 value from the input. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * \return The int8 value that was read from the input, + * or 0 in case of an error. + */ +static inline int8_t packmsg_get_int8(packmsg_input_t *buf) { + uint8_t hdr = packmsg_read_hdr_(buf); + + if(hdr < 0x80 || hdr >= 0xe0) { + return (int8_t)hdr; + } else if(hdr == 0xd0) { + return packmsg_read_hdr_(buf); + } else { + packmsg_input_invalidate(buf); + return 0; + } +} + +/** \brief Get an int16 value from the input. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * \return The int16 value that was read from the input, + * or 0 in case of an error. + */ +static inline int16_t packmsg_get_int16(packmsg_input_t *buf) { + uint8_t hdr = packmsg_read_hdr_(buf); + + if(hdr < 0x80 || hdr >= 0xe0) { + return (int8_t)hdr; + } else if(hdr == 0xd0) { + return (int8_t) packmsg_read_hdr_(buf); + } else if(hdr == 0xd1) { + int16_t val = 0; + packmsg_read_data_(buf, &val, 2); + return val; + } else { + packmsg_input_invalidate(buf); + return 0; + } +} + +/** \brief Get an int32 value from the input. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * \return The int32 value that was read from the input, + * or 0 in case of an error. + */ +static inline int32_t packmsg_get_int32(packmsg_input_t *buf) { + uint8_t hdr = packmsg_read_hdr_(buf); + + if(hdr < 0x80 || hdr >= 0xe0) { + return (int8_t)hdr; + } else if(hdr == 0xd0) { + return (int8_t) packmsg_read_hdr_(buf); + } else if(hdr == 0xd1) { + int16_t val = 0; + packmsg_read_data_(buf, &val, 2); + return val; + } else if(hdr == 0xd2) { + int32_t val = 0; + packmsg_read_data_(buf, &val, 4); + return val; + } else { + packmsg_input_invalidate(buf); + return 0; + } +} + +/** \brief Get an int64 value from the input. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * \return The int64 value that was read from the input, + * or 0 in case of an error. + */ +static inline int64_t packmsg_get_int64(packmsg_input_t *buf) { + uint8_t hdr = packmsg_read_hdr_(buf); + + if(hdr < 0x80 || hdr >= 0xe0) { + return (int8_t)hdr; + } else if(hdr == 0xd0) { + return (int8_t) packmsg_read_hdr_(buf); + } else if(hdr == 0xd1) { + int16_t val = 0; + packmsg_read_data_(buf, &val, 2); + return val; + } else if(hdr == 0xd2) { + int32_t val = 0; + packmsg_read_data_(buf, &val, 4); + return val; + } else if(hdr == 0xd3) { + int64_t val = 0; + packmsg_read_data_(buf, &val, 8); + return val; + } else { + packmsg_input_invalidate(buf); + return 0; + } +} + +/** \brief Get an uint8 value from the input. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * \return The uint8 value that was read from the input, + * or 0 in case of an error. + */ +static inline uint8_t packmsg_get_uint8(packmsg_input_t *buf) { + uint8_t hdr = packmsg_read_hdr_(buf); + + if(hdr < 0x80) { + return hdr; + } else if(hdr == 0xcc) { + return packmsg_read_hdr_(buf); + } else { + packmsg_input_invalidate(buf); + return 0; + } +} + +/** \brief Get an uint16 value from the input. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * \return The uint16 value that was read from the input, + * or 0 in case of an error. + */ +static inline uint16_t packmsg_get_uint16(packmsg_input_t *buf) { + uint8_t hdr = packmsg_read_hdr_(buf); + + if(hdr < 0x80) { + return hdr; + } else if(hdr == 0xcc) { + return packmsg_read_hdr_(buf); + } else if(hdr == 0xcd) { + uint16_t val = 0; + packmsg_read_data_(buf, &val, 2); + return val; + } else { + packmsg_input_invalidate(buf); + return 0; + } +} + +/** \brief Get an uint32 value from the input. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * \return The uint32 value that was read from the input, + * or 0 in case of an error. + */ +static inline uint32_t packmsg_get_uint32(packmsg_input_t *buf) { + uint8_t hdr = packmsg_read_hdr_(buf); + + if(hdr < 0x80) { + return hdr; + } else if(hdr == 0xcc) { + return packmsg_read_hdr_(buf); + } else if(hdr == 0xcd) { + uint16_t val = 0; + packmsg_read_data_(buf, &val, 2); + return val; + } else if(hdr == 0xce) { + uint32_t val = 0; + packmsg_read_data_(buf, &val, 4); + return val; + } else { + packmsg_input_invalidate(buf); + return 0; + } +} + +/** \brief Get an uint64 value from the input. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * \return The uint64 value that was read from the input, + * or 0 in case of an error. + */ +static inline uint64_t packmsg_get_uint64(packmsg_input_t *buf) { + uint8_t hdr = packmsg_read_hdr_(buf); + + if(hdr < 0x80) { + return hdr; + } else if(hdr == 0xcc) { + return packmsg_read_hdr_(buf); + } else if(hdr == 0xcd) { + uint16_t val = 0; + packmsg_read_data_(buf, &val, 2); + return val; + } else if(hdr == 0xce) { + uint32_t val = 0; + packmsg_read_data_(buf, &val, 4); + return val; + } else if(hdr == 0xcf) { + uint64_t val = 0; + packmsg_read_data_(buf, &val, 8); + return val; + } else { + packmsg_input_invalidate(buf); + return 0; + } +} + +/** \brief Get a float value from the input. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * \return The float value that was read from the input, + * or 0 in case of an error. + */ +static inline float packmsg_get_float(packmsg_input_t *buf) { + uint8_t hdr = packmsg_read_hdr_(buf); + + if(hdr == 0xca) { + float val; + packmsg_read_data_(buf, &val, 4); + return val; + } else { + packmsg_input_invalidate(buf); + return 0; + } +} + +/** \brief Get a double value from the input. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * \return The float value that was read from the input, + * or 0 in case of an error. + */ +static inline double packmsg_get_double(packmsg_input_t *buf) { + uint8_t hdr = packmsg_read_hdr_(buf); + + if(hdr == 0xcb) { + double val; + packmsg_read_data_(buf, &val, 8); + return val; + } else if(hdr == 0xca) { + float val; + packmsg_read_data_(buf, &val, 4); + return val; + } else { + packmsg_input_invalidate(buf); + return 0; + } +} + +/** \brief Get a raw pointer to a string from the input. + * \memberof packmsg_input + * + * This function returns the size of a string and a pointer into the input buffer itself, + * to a string that is *not NUL-terminated!* This function avoids making a copy of the string, + * but the application must take care to not read more than the returned number of bytes. + * + * \param buf A pointer to an input buffer iterator. + * \param[out] str A pointer to a const char pointer that will be set to the start of the string, + * or will be set to NULL in case of an error. + * \return The size of the string in bytes, + * or 0 in case of an error. + */ +static inline uint32_t packmsg_get_str_raw(packmsg_input_t *buf, const char **str) { + assert(str); + + uint8_t hdr = packmsg_read_hdr_(buf); + uint32_t slen = 0; + + if((hdr & 0xe0) == 0xa0) { + slen = hdr & 0x1f; + } else if(hdr == 0xd9) { + packmsg_read_data_(buf, &slen, 1); + } else if(hdr == 0xda) { + packmsg_read_data_(buf, &slen, 2); + } else if(hdr == 0xdb) { + packmsg_read_data_(buf, &slen, 4); + } else { + packmsg_input_invalidate(buf); + *str = NULL; + return 0; + } + + if(packmsg_likely(buf->len >= 0 && (uint32_t)buf->len >= slen)) { + *str = (const char *)buf->ptr; + buf->ptr += slen; + buf->len -= slen; + return slen; + } else { + packmsg_input_invalidate(buf); + *str = NULL; + return 0; + } +} + +/** \brief Copy a string from the input into a newly allocated buffer. + * \memberof packmsg_input + * + * This function copies a string from the input into a buffer allocated by the library + * using malloc(). The copy will be NUL-terminated. + * The application is responsible for freeing the memory of the buffer using free(). + * + * \param buf A pointer to an input buffer iterator. + * + * \return A pointer to the newly allocated buffer containing a NUL-terminated string, + * or NULL in case of an error. + */ +static inline char *packmsg_get_str_dup(packmsg_input_t *buf) { + const char *str; + uint32_t slen = packmsg_get_str_raw(buf, &str); + + if(packmsg_likely(packmsg_input_ok(buf))) { + char *dup = (char *)malloc((size_t) slen + 1); + + if(packmsg_likely(dup)) { + memcpy(dup, str, slen); + dup[slen] = 0; + return dup; + } else { + packmsg_input_invalidate(buf); + return NULL; + } + } else { + return NULL; + } +} + +/** \brief Copy a string from the input into another buffer. + * \memberof packmsg_input + * + * This function copies a string from the input another buffer provided by the application. + * The buffer must be long enough to hold the complete string plus a terminating NUL-byte. + * If the buffer is not long enough, or another error occurred, + * a single NUL-byte will be written to the start of the buffer (if its size is at least one byte). + * + * \param buf A pointer to an input buffer iterator. + * \param data A pointer to a buffer allocated by the application. + * \param dlen The size of the buffer pointed to by data. + * + * \return The size of the string in bytes, + * or 0 in case of an error. + */ +static inline uint32_t packmsg_get_str_copy(packmsg_input_t *buf, void *data, uint32_t dlen) { + assert(data); + + const char *str; + uint32_t slen = packmsg_get_str_raw(buf, &str); + + if(packmsg_likely(packmsg_input_ok(buf))) { + if(packmsg_likely(slen < dlen)) { + memcpy(data, str, slen); + ((char *)data)[slen] = 0; + return slen; + } else { + if(dlen) { + *(char *)data = 0; + } + + packmsg_input_invalidate(buf); + return 0; + } + } else { + if(dlen) { + *(char *)data = 0; + } + + return 0; + } +} + +/** \brief Get a raw pointer to binary data from the input. + * \memberof packmsg_input + * + * This function returns the size of the binary data and a pointer into the input buffer itself. + * This function avoids making a copy of the binary data, + * but the application must take care to not read more than the returned number of bytes. + * + * \param buf A pointer to an input buffer iterator. + * \param[out] data A pointer to a const void pointer that will be set to the start of the data, + * or will be set to NULL in case of an error. + * \return The size of the data in bytes, + * or 0 in case of an error. + */ +static inline uint32_t packmsg_get_bin_raw(packmsg_input_t *buf, const void **data) { + assert(data); + + uint8_t hdr = packmsg_read_hdr_(buf); + uint32_t dlen = 0; + + if(hdr == 0xc4) { + packmsg_read_data_(buf, &dlen, 1); + } else if(hdr == 0xc5) { + packmsg_read_data_(buf, &dlen, 2); + } else if(hdr == 0xc6) { + packmsg_read_data_(buf, &dlen, 4); + } else { + packmsg_input_invalidate(buf); + *data = NULL; + return 0; + } + + if(packmsg_likely(buf->len >= 0 && (uint32_t)buf->len >= dlen)) { + *data = buf->ptr; + buf->ptr += dlen; + buf->len -= dlen; + return dlen; + } else { + packmsg_input_invalidate(buf); + *data = NULL; + return 0; + } +} + +/** \brief Copy binary data from the input into a newly allocated buffer. + * \memberof packmsg_input + * + * This function copies binary data from the input into a buffer allocated by the library + * using malloc(). + * The application is responsible for freeing the memory of the buffer using free(). + * + * \param buf A pointer to an input buffer iterator. + * \param[out] dlen A pointer to an uint32_t that will be set to the size of the binary data. + * + * \return A pointer to the newly allocated buffer containing the binary data, + * or NULL in case of an error. + */ +static inline void *packmsg_get_bin_dup(packmsg_input_t *buf, uint32_t *dlen) { + const void *data; + *dlen = packmsg_get_bin_raw(buf, &data); + + if(packmsg_likely(packmsg_input_ok(buf))) { + char *dup = (char *)malloc(*dlen); + + if(packmsg_likely(dup)) { + memcpy(dup, data, *dlen); + return dup; + } else { + *dlen = 0; + packmsg_input_invalidate(buf); + return NULL; + } + } else { + return NULL; + } +} + +/** \brief Copy binary data from the input into another buffer. + * \memberof packmsg_input + * + * This function copies binary data from the input another buffer provided by the application. + * The buffer must be long enough to hold all the binary data. + * + * \param buf A pointer to an input buffer iterator. + * \param rawbuf A pointer to a buffer allocated by the application. + * \param rlen The size of the buffer pointed to by data. + * + * \return The size of the binary data in bytes, + * or 0 in case of an error. + */ +static inline uint32_t packmsg_get_bin_copy(packmsg_input_t *buf, void *rawbuf, uint32_t rlen) { + assert(rawbuf); + + const void *data; + uint32_t dlen = packmsg_get_bin_raw(buf, &data); + + if(packmsg_likely(packmsg_input_ok(buf))) { + if(packmsg_likely(dlen <= rlen)) { + memcpy(rawbuf, data, dlen); + return dlen; + } else { + packmsg_input_invalidate(buf); + return 0; + } + } else { + return 0; + } +} + +/** \brief Get a raw pointer to extension data from the input. + * \memberof packmsg_input + * + * This function returns the type of the extension, the size of the data + * and a pointer into the input buffer itself. + * This function avoids making a copy of the binary data, + * but the application must take care to not read more than the returned number of bytes. + * + * \param buf A pointer to an input buffer iterator. + * \param[out] type A pointer to an int8_t that will be set to the type of the extension. + * or will be set to 0 in case of an error. + * \param[out] data A pointer to a const void pointer that will be set to the start of the data, + * or will be set to NULL in case of an error. + * + * \return The size of the data in bytes, + * or 0 in case of an error. + */ +static inline uint32_t packmsg_get_ext_raw(packmsg_input_t *buf, int8_t *type, const void **data) { + assert(type); + assert(data); + + uint8_t hdr = packmsg_read_hdr_(buf); + uint32_t dlen = 0; + + if(hdr == 0xc7) { + packmsg_read_data_(buf, &dlen, 1); + } else if(hdr == 0xc8) { + packmsg_read_data_(buf, &dlen, 2); + } else if(hdr == 0xc9) { + packmsg_read_data_(buf, &dlen, 4); + } else if(hdr >= 0xd4 && hdr <= 0xd8) { + dlen = 1 << (hdr - 0xd4); + } else { + packmsg_input_invalidate(buf); + *type = 0; + *data = NULL; + return 0; + } + + *type = packmsg_read_hdr_(buf); + + if(packmsg_likely(buf->len >= 0 && (uint32_t)buf->len >= dlen)) { + *data = buf->ptr; + buf->ptr += dlen; + buf->len -= dlen; + return dlen; + } else { + packmsg_input_invalidate(buf); + *type = 0; + *data = NULL; + return 0; + } +} + +/** \brief Copy extension data from the input into a newly allocated buffer. + * \memberof packmsg_input + * + * This function copies extension data from the input into a buffer allocated by the library + * using malloc(). + * The application is responsible for freeing the memory of the buffer using free(). + * + * \param buf A pointer to an input buffer iterator. + * \param[out] type A pointer to an int8_t that will be set to the type of the extension. + * or will be set to 0 in case of an error. + * \param[out] dlen A pointer to an uint32_t that will be set to the size of the extension data, + * or will be set to 0 in case of an error. + * + * \return A pointer to the newly allocated buffer containing the extension data, + * or NULL in case of an error. + */ +static inline void *packmsg_get_ext_dup(packmsg_input_t *buf, int8_t *type, uint32_t *dlen) { + assert(type); + + const void *data; + *dlen = packmsg_get_ext_raw(buf, type, &data); + + if(packmsg_likely(packmsg_input_ok(buf))) { + char *dup = (char *)malloc(*dlen); + + if(packmsg_likely(dup)) { + memcpy(dup, data, *dlen); + return dup; + } else { + *type = 0; + *dlen = 0; + packmsg_input_invalidate(buf); + return NULL; + } + } else { + *type = 0; + *dlen = 0; + return NULL; + } +} + +/** \brief Copy extension data from the input into another buffer. + * \memberof packmsg_input + * + * This function copies extension data from the input another buffer provided by the application. + * The buffer must be long enough to hold all the extension data. + * + * \param buf A pointer to an input buffer iterator. + * \param[out] type A pointer to an int8_t that will be set to the type of the extension. + * or will be set to 0 in case of an error. + * \param rawbuf A pointer to a buffer allocated by the application. + * \param rlen The size of the buffer pointed to by data. + * + * \return The size of the extension data in bytes, + * or 0 in case of an error. + */ +static inline uint32_t packmsg_get_ext_copy(packmsg_input_t *buf, int8_t *type, void *rawbuf, uint32_t rlen) { + assert(type); + assert(rawbuf); + + const void *data; + uint32_t dlen = packmsg_get_ext_raw(buf, type, &data); + + if(packmsg_likely(packmsg_input_ok(buf))) { + if(packmsg_likely(dlen <= rlen)) { + memcpy(rawbuf, data, dlen); + return dlen; + } else { + *type = 0; + packmsg_input_invalidate(buf); + return 0; + } + } else { + *type = 0; + return 0; + } +} + +/** \brief Get a map header from the output. + * \memberof packmsg_input + * + * This function only reads a map header, and returns the number of key-value + * pairs in the map. + * These key-value pairs have to be read by the application using regular + * packmsg_get_*() calls. + * + * \param buf A pointer to an input buffer iterator. + * + * \return The number of key-value pairs in the map. + */ +static inline uint32_t packmsg_get_map(packmsg_input_t *buf) { + uint8_t hdr = packmsg_read_hdr_(buf); + + if((hdr & 0xf0) == 0x80) { + return hdr & 0xf; + } else if(hdr == 0xde) { + uint32_t dlen = 0; + packmsg_read_data_(buf, &dlen, 2); + return dlen; + } else if(hdr == 0xdf) { + uint32_t dlen = 0; + packmsg_read_data_(buf, &dlen, 4); + return dlen; + } else { + packmsg_input_invalidate(buf); + return 0; + } +} + +/** \brief Get an array header from the output. + * \memberof packmsg_input + * + * This function only reads an array header, and returns the number of elements + * in the array. + * These elements have to be read by the application using regular + * packmsg_get_*() calls. + * + * \param buf A pointer to an input buffer iterator. + * + * \return The number of elements in the array. + */ +static inline uint32_t packmsg_get_array(packmsg_input_t *buf) { + uint8_t hdr = packmsg_read_hdr_(buf); + + if((hdr & 0xf0) == 0x90) { + return hdr & 0xf; + } else if(hdr == 0xdc) { + uint32_t dlen = 0; + packmsg_read_data_(buf, &dlen, 2); + return dlen; + } else if(hdr == 0xdd) { + uint32_t dlen = 0; + packmsg_read_data_(buf, &dlen, 4); + return dlen; + } else { + packmsg_input_invalidate(buf); + return 0; + } +} + +/* Type checking + * ============= + */ + +/** \brief An enum describing the type of an element in a PackMessage message. + * + * This enum describes the type of an element in a PackMessage message. + * In case of integers and floating point values, the type normally represents + * the smallest type that can successfully hold the value of the element; + * i.e. an element of type PACKMSG_INT32 can only successfully be read by + * packmsg_get_int32() or packmsg_get_int64(). However, the converse it not true; + * for an element of type PACKMSG_INT32, there is no guarantee + * that the value is larger than would fit into an int16_t. + * + * PackMessage makes a clear distinction between signed and unsigned integers, + * except in the case of positive fixints (values between 0 and 127 inclusive), + * which can be read as both signed and unsigned. + */ +enum packmsg_type { + PACKMSG_ERROR, /**< An invalid element was found or the input buffer is in an invalid state. */ + PACKMSG_NIL, /**< The next element is a NIL. */ + PACKMSG_BOOL, /**< The next element is a boolean. */ + PACKMSG_POSITIVE_FIXINT, /**< The next element is an integer between 0 and 127 inclusive. */ + PACKMSG_INT8, /**< The next element is a signed integer that fits in an int8_t. */ + PACKMSG_INT16, /**< The next element is a signed integer that fits in an int16_t. */ + PACKMSG_INT32, /**< The next element is a signed integer that fits in an int32_t. */ + PACKMSG_INT64, /**< The next element is a signed integer that fits in an int64_t. */ + PACKMSG_UINT8, /**< The next element is an unsigned integer that fits in an uint8_t. */ + PACKMSG_UINT16, /**< The next element is an unsigned integer that fits in an uint16_t. */ + PACKMSG_UINT32, /**< The next element is an unsigned integer that fits in an uint32_t. */ + PACKMSG_UINT64, /**< The next element is an unsigned integer that fits in an uint64_t. */ + PACKMSG_FLOAT, /**< The next element is a single precision floating point value. */ + PACKMSG_DOUBLE, /**< The next element is a double precision floating point value. */ + PACKMSG_STR, /**< The next element is a string. */ + PACKMSG_BIN, /**< The next element is binary data. */ + PACKMSG_EXT, /**< The next element is extension data. */ + PACKMSG_MAP, /**< The next element is a map header. */ + PACKMSG_ARRAY, /**< The next element is an array header. */ + PACKMSG_DONE, /**< There are no more elements in the input buffer. */ +}; + +/** \brief Checks if the next element is a NIL. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * + * \return True if the next element can be read by packmsg_get_nil(), + * false if not or if any other error occurred. + */ +static inline bool packmsg_is_nil(const packmsg_input_t *buf) { + return packmsg_peek_hdr_(buf) == 0xc0; +} + +/** \brief Checks if the next element is a bool. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * + * \return True if the next element can be read by packmsg_get_nil(), + * false if not or if any other error occurred. + */ +static inline bool packmsg_is_bool(const packmsg_input_t *buf) { + return (packmsg_peek_hdr_(buf) & 0xfe) == 0xc2; +} + +/** \brief Checks if the next element is a signed integer that fits in an int8_t. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * + * \return True if the next element can be read by packmsg_get_int8(), + * false if not or if any other error occurred. + */ +static inline bool packmsg_is_int8(const packmsg_input_t *buf) { + uint8_t hdr = packmsg_peek_hdr_(buf); + return hdr < 0x80 || hdr == 0xd0; +} + +/** \brief Checks if the next element is a signed integer that fits in an int16_t. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * + * \return True if the next element can be read by packmsg_get_int16(), + * false if not or if any other error occurred. + */ +static inline bool packmsg_is_int16(const packmsg_input_t *buf) { + uint8_t hdr = packmsg_peek_hdr_(buf); + return hdr < 0x80 || hdr == 0xd0 || hdr == 0xd1; +} + +/** \brief Checks if the next element is a signed integer that fits in an int32_t. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * + * \return True if the next element can be read by packmsg_get_int32(), + * false if not or if any other error occurred. + */ +static inline bool packmsg_is_int32(const packmsg_input_t *buf) { + uint8_t hdr = packmsg_peek_hdr_(buf); + return hdr < 0x80 || hdr == 0xd0 || hdr == 0xd1 || hdr == 0xd2; +} + +/** \brief Checks if the next element is a signed integer that fits in an int64_t. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * + * \return True if the next element can be read by packmsg_get_int64(), + * false if not or if any other error occurred. + */ +static inline bool packmsg_is_int64(const packmsg_input_t *buf) { + uint8_t hdr = packmsg_peek_hdr_(buf); + return hdr < 0x80 || hdr == 0xd0 || hdr == 0xd1 || hdr == 0xd2 || hdr == 0xd3; +} + +/** \brief Checks if the next element is an unsigned integer that fits in an uint8_t. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * + * \return True if the next element can be read by packmsg_get_uint8(), + * false if not or if any other error occurred. + */ +static inline bool packmsg_is_uint8(const packmsg_input_t *buf) { + uint8_t hdr = packmsg_peek_hdr_(buf); + return hdr < 0x80 || hdr == 0xcc; +} + +/** \brief Checks if the next element is an unsigned integer that fits in an uint16_t. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * + * \return True if the next element can be read by packmsg_get_uint16(), + * false if not or if any other error occurred. + */ +static inline bool packmsg_is_uint16(const packmsg_input_t *buf) { + uint8_t hdr = packmsg_peek_hdr_(buf); + return hdr < 0x80 || hdr == 0xcc || hdr == 0xcd; +} + +/** \brief Checks if the next element is an unsigned integer that fits in an uint32_t. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * + * \return True if the next element can be read by packmsg_get_uint32(), + * false if not or if any other error occurred. + */ +static inline bool packmsg_is_uint32(const packmsg_input_t *buf) { + uint8_t hdr = packmsg_peek_hdr_(buf); + return hdr < 0x80 || hdr == 0xcc || hdr == 0xcd || hdr == 0xce; +} + +/** \brief Checks if the next element is an unsigned integer that fits in an uint64_t. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * + * \return True if the next element can be read by packmsg_get_uint64(), + * false if not or if any other error occurred. + */ +static inline bool packmsg_is_uint64(const packmsg_input_t *buf) { + uint8_t hdr = packmsg_peek_hdr_(buf); + return hdr < 0x80 || hdr == 0xcc || hdr == 0xcd || hdr == 0xce || hdr == 0xcf; +} + +/** \brief Checks if the next element is a single precision floating point value. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * + * \return True if the next element can be read by packmsg_get_float(), + * false if not or if any other error occurred. + */ +static inline bool packmsg_is_float(const packmsg_input_t *buf) { + return packmsg_peek_hdr_(buf) == 0xca; +} + +/** \brief Checks if the next element is a single or double precision floating point value. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * + * \return True if the next element can be read by packmsg_get_double(), + * false if not or if any other error occurred. + */ +static inline bool packmsg_is_double(const packmsg_input_t *buf) { + uint8_t hdr = packmsg_peek_hdr_(buf); + return hdr == 0xcb || hdr == 0xca; +} + +/** \brief Checks if the next element is a string. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * + * \return True if the next element can be read by packmsg_get_str_*(), + * false if not or if any other error occurred. + */ +static inline bool packmsg_is_str(const packmsg_input_t *buf) { + uint8_t hdr = packmsg_peek_hdr_(buf); + return (hdr & 0xe0) == 0xa0 || hdr == 0xd9 || hdr == 0xda || hdr == 0xdb; +} + +/** \brief Checks if the next element is binary data. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * + * \return True if the next element can be read by packmsg_get_bin_*(), + * false if not or if any other error occurred. + */ +static inline bool packmsg_is_bin(const packmsg_input_t *buf) { + return (packmsg_peek_hdr_(buf) & 0xfc) == 0xc4; +} + +/** \brief Checks if the next element is extension data. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * + * \return True if the next element can be read by packmsg_get_ext_*(), + * false if not or if any other error occurred. + */ +static inline bool packmsg_is_ext(const packmsg_input_t *buf) { + uint8_t hdr = packmsg_peek_hdr_(buf); + return (hdr >= 0xc7 && hdr <= 0xc9) || (hdr >= 0xd4 && hdr <= 0xd8); +} + +/** \brief Checks if the next element is a map header. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * + * \return True if the next element can be read by packmsg_get_map(), + * false if not or if any other error occurred. + */ +static inline bool packmsg_is_map(const packmsg_input_t *buf) { + uint8_t hdr = packmsg_peek_hdr_(buf); + return (hdr & 0xf0) == 0x80 || hdr == 0xde || hdr == 0xdf; +} + +/** \brief Checks if the next element is an array header. + * \memberof packmsg_input + * + * \param buf A pointer to an input buffer iterator. + * + * \return True if the next element can be read by packmsg_get_array(), + * false if not or if any other error occurred. + */ +static inline bool packmsg_is_array(const packmsg_input_t *buf) { + uint8_t hdr = packmsg_peek_hdr_(buf); + return (hdr & 0xf0) == 0x90 || hdr == 0xdc || hdr == 0xdd; +} + +/** \brief Checks the type of the next element. + * \memberof packmsg_input + * + * This function checks the next element and returns an enum packmsg_type + * that describes the type of the element. If the input buffer was fully consumed + * and there are no more elements left, this function will return PACKMSG_DONE. + * + * \param buf A pointer to an output buffer iterator. + * + * \return The type of the next element, or PACKMSG_DONE if no more elements + * are present in the input buffer, or PACKMSG_ERROR if the next element + * is invalid, or if any other error occurred. + */ +static inline enum packmsg_type packmsg_get_type(const packmsg_input_t *buf) { + if(packmsg_unlikely(packmsg_done(buf))) { + return PACKMSG_DONE; + } + + uint8_t hdr = packmsg_peek_hdr_(buf); + + switch(hdr >> 4) { + case 0x0: + case 0x1: + case 0x2: + case 0x3: + case 0x4: + case 0x5: + case 0x6: + case 0x7: + return PACKMSG_POSITIVE_FIXINT; + + case 0x8: + return PACKMSG_MAP; + + case 0x9: + return PACKMSG_ARRAY; + + case 0xa: + case 0xb: + return PACKMSG_STR; + + case 0xc: + switch(hdr & 0xf) { + case 0x0: + return PACKMSG_NIL; + + case 0x1: + return PACKMSG_ERROR; + + case 0x2: + case 0x3: + return PACKMSG_BOOL; + + case 0x4: + case 0x5: + case 0x6: + return PACKMSG_BIN; + + case 0x7: + case 0x8: + case 0x9: + return PACKMSG_EXT; + + case 0xa: + return PACKMSG_FLOAT; + + case 0xb: + return PACKMSG_DOUBLE; + + case 0xc: + return PACKMSG_UINT8; + + case 0xd: + return PACKMSG_UINT16; + + case 0xe: + return PACKMSG_UINT32; + + case 0xf: + return PACKMSG_UINT64; + + default: + return PACKMSG_ERROR; + } + + case 0xd: + switch(hdr & 0xf) { + case 0x0: + return PACKMSG_INT8; + + case 0x1: + return PACKMSG_INT16; + + case 0x2: + return PACKMSG_INT32; + + case 0x3: + return PACKMSG_INT64; + + case 0x4: + case 0x5: + case 0x6: + case 0x7: + case 0x8: + return PACKMSG_EXT; + + case 0x9: + case 0xa: + case 0xb: + return PACKMSG_STR; + + case 0xc: + case 0xd: + return PACKMSG_ARRAY; + + case 0xe: + case 0xf: + return PACKMSG_MAP; + + default: + return PACKMSG_ERROR; + } + + case 0xe: + case 0xf: + return PACKMSG_INT8; + + default: + return PACKMSG_ERROR; + } +} + +/** \brief Skip one element in the input + * \memberof packmsg_input + * + * This function skips the next element in the input. + * If the element is a map or an array, only the map or array header is skipped, + * but not the contents of the map or array. + * + * \param buf A pointer to an output buffer iterator. + */ +static inline void packmsg_skip_element(packmsg_input_t *buf) { + uint8_t hdr = packmsg_read_hdr_(buf); + int32_t skip = 0; + + switch(hdr >> 4) { + case 0x0: + case 0x1: + case 0x2: + case 0x3: + case 0x4: + case 0x5: + case 0x6: + case 0x7: + case 0x8: + case 0x9: + return; + + case 0xa: + case 0xb: + skip = hdr & 0x1f; + break; + + case 0xc: + switch(hdr & 0xf) { + case 0x0: + case 0x1: + case 0x2: + case 0x3: + return; + + case 0x4: + skip = -1; + break; + + case 0x5: + skip = -2; + break; + + case 0x6: + skip = -4; + break; + + case 0x7: + skip = -1; + break; + + case 0x8: + skip = -2; + break; + + case 0x9: + skip = -4; + break; + + case 0xa: + skip = 4; + break; + + case 0xb: + skip = 8; + break; + + case 0xc: + skip = 1; + break; + + case 0xd: + skip = 2; + break; + + case 0xe: + skip = 4; + break; + + case 0xf: + skip = 8; + break; + } + + break; + + case 0xd: + switch(hdr & 0xf) { + case 0x0: + skip = 1; + break; + + case 0x1: + skip = 2; + break; + + case 0x2: + skip = 4; + break; + + case 0x3: + skip = 8; + break; + + case 0x4: + skip = 2; + break; + + case 0x5: + skip = 3; + break; + + case 0x6: + skip = 5; + break; + + case 0x7: + skip = 9; + break; + + case 0x8: + skip = 17; + break; + + case 0x9: + skip = -1; + break; + + case 0xa: + skip = -2; + break; + + case 0xb: + skip = -4; + break; + + case 0xc: + skip = 2; + break; + + case 0xd: + skip = 4; + break; + + case 0xe: + skip = 2; + break; + + case 0xf: + skip = 4; + break; + } + + break; + + case 0xe: + case 0xf: + return; + } + + uint32_t dlen = 0; + + if(skip < 0) { + packmsg_read_data_(buf, &dlen, -skip); + + if(hdr >= 0xc7 && hdr <= 0xc9) { + dlen++; + } + } else { + dlen = skip; + } + + if(packmsg_likely(buf->len >= 0 && (uint32_t)buf->len >= dlen)) { + buf->ptr += dlen; + buf->len -= dlen; + } else { + packmsg_input_invalidate(buf); + } +} + +/** \brief Skip one object in the input + * \memberof packmsg_input + * + * This function checks the type of the next element. + * In case it is a scalar value (for example, an int or a string), + * it skips just that scalar. If the next element is a map or an array, + * it will recursively skip as many objects as there are in that map or array. + * + * \param buf A pointer to an output buffer iterator. + */ +static inline void packmsg_skip_object(packmsg_input_t *buf) { + if(packmsg_is_array(buf)) { + uint32_t count = packmsg_get_array(buf); + + while(count-- && buf->len >= 0) { + packmsg_skip_object(buf); + } + } else if(packmsg_is_map(buf)) { + uint32_t count = packmsg_get_map(buf); + + while(count-- && buf->len >= 0) { + packmsg_skip_object(buf); + packmsg_skip_object(buf); + } + } else { + packmsg_skip_element(buf); + } +} + +#ifdef __cplusplus +} +#endif diff --git a/src/prf.c b/src/prf.c new file mode 100644 index 0000000..e6fdcb6 --- /dev/null +++ b/src/prf.c @@ -0,0 +1,132 @@ +/* + prf.c -- Pseudo-Random Function for key material generation + Copyright (C) 2014 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "prf.h" +#include "ed25519/sha512.h" + +static void memxor(char *buf, char c, size_t len) { + assert(buf); + assert(len); + + for(size_t i = 0; i < len; i++) { + buf[i] ^= c; + } +} + +#define mdlen 64 + +// TODO: separate key setup from hmac_sha512 + +static bool hmac_sha512(const char *key, size_t keylen, const char *msg, size_t msglen, char *out) { + assert(msg); + assert(msglen); + + char tmp[2 * mdlen]; + sha512_context md; + + if(keylen <= mdlen) { + memcpy(tmp, key, keylen); + memset(tmp + keylen, 0, mdlen - keylen); + } else { + if(sha512(key, keylen, tmp) != 0) { + return false; + } + } + + if(sha512_init(&md) != 0) { + return false; + } + + // ipad + memxor(tmp, 0x36, mdlen); + + if(sha512_update(&md, tmp, mdlen) != 0) { + return false; + } + + // message + if(sha512_update(&md, msg, msglen) != 0) { + return false; + } + + if(sha512_final(&md, tmp + mdlen) != 0) { + return false; + } + + // opad + memxor(tmp, 0x36 ^ 0x5c, mdlen); + + if(sha512(tmp, sizeof(tmp), out) != 0) { + return false; + } + + return true; +} + +/* Generate key material from a master secret and a seed, based on RFC 4346 section 5. + We use SHA512 instead of MD5 and SHA1. + */ + +bool prf(const char *secret, size_t secretlen, const char *seed, size_t seedlen, char *out, size_t outlen) { + assert(secret); + assert(secretlen); + assert(seed); + assert(seedlen); + assert(out); + assert(outlen); + + /* Data is what the "inner" HMAC function processes. + It consists of the previous HMAC result plus the seed. + */ + + char data[mdlen + seedlen]; + memset(data, 0, mdlen); + memcpy(data + mdlen, seed, seedlen); + + char hash[mdlen]; + + while(outlen > 0) { + /* Inner HMAC */ + if(!hmac_sha512(data, sizeof(data), secret, secretlen, data)) { + return false; + } + + /* Outer HMAC */ + if(outlen >= mdlen) { + if(!hmac_sha512(data, sizeof(data), secret, secretlen, out)) { + return false; + } + + out += mdlen; + outlen -= mdlen; + } else { + if(!hmac_sha512(data, sizeof(data), secret, secretlen, hash)) { + return false; + } + + memcpy(out, hash, outlen); + out += outlen; + outlen = 0; + } + } + + return true; +} diff --git a/src/prf.h b/src/prf.h new file mode 100644 index 0000000..1494eca --- /dev/null +++ b/src/prf.h @@ -0,0 +1,25 @@ +#ifndef MESHLINK_PRF_H +#define MESHLINK_PRF_H + +/* + prf.h -- header file for prf.c + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +bool prf(const char *secret, size_t secretlen, const char *seed, size_t seedlen, char *out, size_t outlen) __attribute__((__warn_unused_result__)); + +#endif diff --git a/src/protocol.c b/src/protocol.c new file mode 100644 index 0000000..9d1dff9 --- /dev/null +++ b/src/protocol.c @@ -0,0 +1,259 @@ +/* + protocol.c -- handle the meta-protocol, basic functions + Copyright (C) 2014-2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "conf.h" +#include "connection.h" +#include "logger.h" +#include "meshlink_internal.h" +#include "meta.h" +#include "protocol.h" +#include "utils.h" +#include "xalloc.h" +#include "submesh.h" + +/* Jumptable for the request handlers */ + +static bool (*request_handlers[NUM_REQUESTS])(meshlink_handle_t *, connection_t *, const char *) = { + [ID] = id_h, + [ACK] = ack_h, + [STATUS] = status_h, + [ERROR] = error_h, + [TERMREQ] = termreq_h, + [PING] = ping_h, + [PONG] = pong_h, + [ADD_EDGE] = add_edge_h, + [DEL_EDGE] = del_edge_h, + [KEY_CHANGED] = key_changed_h, + [REQ_KEY] = req_key_h, + [ANS_KEY] = ans_key_h, +}; + +/* Request names */ + +static const char *request_name[NUM_REQUESTS] = { + [ID] = "ID", + [ACK] = "ACK", + [STATUS] = "STATUS", + [ERROR] = "ERROR", + [TERMREQ] = "TERMREQ", + [PING] = "PING", + [PONG] = "PONG", + [ADD_EDGE] = "ADD_EDGE", + [DEL_EDGE] = "DEL_EDGE", + [KEY_CHANGED] = "KEY_CHANGED", + [REQ_KEY] = "REQ_KEY", + [ANS_KEY] = "ANS_KEY", +}; + +bool check_id(const char *id) { + if(!id || !*id) { + return false; + } + + for(; *id; id++) + if(!isalnum(*id) && *id != '_' && *id != '-') { + return false; + } + + return true; +} + +/* Generic request routines - takes care of logging and error + detection as well */ + +bool send_request(meshlink_handle_t *mesh, connection_t *c, const submesh_t *s, const char *format, ...) { + assert(format); + assert(*format); + + if(!c) { + logger(mesh, MESHLINK_ERROR, "Trying to send request to non-existing connection"); + return false; + } + + va_list args; + char request[MAXBUFSIZE]; + int len; + + /* Use vsnprintf instead of vxasprintf: faster, no memory + fragmentation, cleanup is automatic, and there is a limit on the + input buffer anyway */ + + va_start(args, format); + len = vsnprintf(request, MAXBUFSIZE, format, args); + va_end(args); + + if(len < 0 || len > MAXBUFSIZE - 1) { + logger(mesh, MESHLINK_ERROR, "Output buffer overflow while sending request to %s", c->name); + return false; + } + + logger(mesh, MESHLINK_DEBUG, "Sending %s to %s: %s", request_name[atoi(request)], c->name, request); + + request[len++] = '\n'; + + if(c == mesh->everyone) { + + if(s) { + broadcast_submesh_meta(mesh, NULL, s, request, len); + } else { + broadcast_meta(mesh, NULL, request, len); + } + + return true; + } else { + return send_meta(mesh, c, request, len); + } +} + +void forward_request(meshlink_handle_t *mesh, connection_t *from, const submesh_t *s, const char *request) { + assert(from); + assert(request); + assert(*request); + + logger(mesh, MESHLINK_DEBUG, "Forwarding %s from %s: %s", request_name[atoi(request)], from->name, request); + + // Create a temporary newline-terminated copy of the request + int len = strlen(request); + char tmp[len + 1]; + + memcpy(tmp, request, len); + tmp[len] = '\n'; + + if(s) { + broadcast_submesh_meta(mesh, from, s, tmp, sizeof(tmp)); + } else { + broadcast_meta(mesh, from, tmp, sizeof(tmp)); + } +} + +bool receive_request(meshlink_handle_t *mesh, connection_t *c, const char *request) { + assert(request); + + int reqno = atoi(request); + + if(reqno || *request == '0') { + if((reqno < 0) || (reqno >= NUM_REQUESTS) || !request_handlers[reqno]) { + logger(mesh, MESHLINK_DEBUG, "Unknown request from %s: %s", c->name, request); + return false; + } else { + logger(mesh, MESHLINK_DEBUG, "Got %s from %s: %s", request_name[reqno], c->name, request); + } + + if((c->allow_request != ALL) && (c->allow_request != reqno) && (reqno != ERROR)) { + logger(mesh, MESHLINK_ERROR, "Unauthorized request from %s", c->name); + return false; + } + + if(!request_handlers[reqno](mesh, c, request)) { + /* Something went wrong. Probably scriptkiddies. Terminate. */ + + logger(mesh, MESHLINK_ERROR, "Error while processing %s from %s", request_name[reqno], c->name); + return false; + } + } else { + logger(mesh, MESHLINK_ERROR, "Bogus data received from %s", c->name); + return false; + } + + return true; +} + +static int past_request_compare(const past_request_t *a, const past_request_t *b) { + return strcmp(a->request, b->request); +} + +static void free_past_request(past_request_t *r) { + if(r->request) { + free((void *)r->request); + } + + free(r); +} + +static const int request_timeout = 60; + +static void age_past_requests(event_loop_t *loop, void *data) { + (void)data; + meshlink_handle_t *mesh = loop->data; + int left = 0, deleted = 0; + + for splay_each(past_request_t, p, mesh->past_request_tree) { + if(p->firstseen + request_timeout <= mesh->loop.now.tv_sec) { + splay_delete_node(mesh->past_request_tree, splay_node), deleted++; + } else { + left++; + } + } + + if(left || deleted) { + logger(mesh, MESHLINK_DEBUG, "Aging past requests: deleted %d, left %d", deleted, left); + } + + if(left) { + timeout_set(&mesh->loop, &mesh->past_request_timeout, &(struct timespec) { + 10, prng(mesh, TIMER_FUDGE) + }); + } +} + +bool seen_request(meshlink_handle_t *mesh, const char *request) { + assert(request); + assert(*request); + + past_request_t *new, p = {.request = request}; + + if(splay_search(mesh->past_request_tree, &p)) { + logger(mesh, MESHLINK_DEBUG, "Already seen request"); + return true; + } else { + new = xmalloc(sizeof(*new)); + new->request = xstrdup(request); + new->firstseen = mesh->loop.now.tv_sec; + + if(!mesh->past_request_tree->head && mesh->past_request_timeout.cb) { + timeout_set(&mesh->loop, &mesh->past_request_timeout, &(struct timespec) { + 10, prng(mesh, TIMER_FUDGE) + }); + } + + splay_insert(mesh->past_request_tree, new); + return false; + } +} + +void init_requests(meshlink_handle_t *mesh) { + assert(!mesh->past_request_tree); + + mesh->past_request_tree = splay_alloc_tree((splay_compare_t) past_request_compare, (splay_action_t) free_past_request); + timeout_add(&mesh->loop, &mesh->past_request_timeout, age_past_requests, NULL, &(struct timespec) { + 0, 0 + }); +} + +void exit_requests(meshlink_handle_t *mesh) { + if(mesh->past_request_tree) { + splay_delete_tree(mesh->past_request_tree); + } + + mesh->past_request_tree = NULL; + + timeout_del(&mesh->loop, &mesh->past_request_timeout); +} diff --git a/src/protocol.h b/src/protocol.h new file mode 100644 index 0000000..fb1e109 --- /dev/null +++ b/src/protocol.h @@ -0,0 +1,116 @@ +#ifndef MESHLINK_PROTOCOL_H +#define MESHLINK_PROTOCOL_H + +/* + protocol.h -- header for protocol.c + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "ecdsa.h" + +/* Protocol version. Different major versions are incompatible. */ + +#define PROT_MAJOR 17 +#define PROT_MINOR 3 /* Should not exceed 255! */ + +/* Silly Windows */ + +#ifdef ERROR +#undef ERROR +#endif + +/* Request numbers */ + +typedef enum request_t { + ALL = -1, /* Guardian for allow_request */ + ID = 0, METAKEY, CHALLENGE, CHAL_REPLY, ACK, + STATUS, ERROR, TERMREQ, + PING, PONG, + ADD_SUBNET, DEL_SUBNET, + ADD_EDGE, DEL_EDGE, + KEY_CHANGED, REQ_KEY, ANS_KEY, + PACKET, + /* Extended requests */ + CONTROL, + REQ_PUBKEY, ANS_PUBKEY, + REQ_SPTPS, + REQ_CANONICAL, + NUM_REQUESTS +} request_t; + +typedef enum request_error_t { + NONE = 0, + BLACKLISTED = 1, +} request_error_t; + +typedef struct past_request_t { + const char *request; + time_t firstseen; +} past_request_t; + +/* Maximum size of strings in a request. + * scanf terminates %2048s with a NUL character, + * but the NUL character can be written after the 2048th non-NUL character. + */ + +#define MAX_STRING_SIZE 2049 +#define MAX_STRING "%2048s" + +#include "edge.h" +#include "net.h" +#include "node.h" + +/* Basic functions */ + +bool send_request(struct meshlink_handle *mesh, struct connection_t *, const struct submesh_t *s, const char *, ...) __attribute__((__format__(printf, 4, 5))); +void forward_request(struct meshlink_handle *mesh, struct connection_t *, const struct submesh_t *, const char *); +bool receive_request(struct meshlink_handle *mesh, struct connection_t *, const char *); +bool check_id(const char *); + +void init_requests(struct meshlink_handle *mesh); +void exit_requests(struct meshlink_handle *mesh); +bool seen_request(struct meshlink_handle *mesh, const char *); + +/* Requests */ + +bool send_id(struct meshlink_handle *mesh, struct connection_t *); +bool send_ack(struct meshlink_handle *mesh, struct connection_t *); +bool send_error(struct meshlink_handle *mesh, struct connection_t *, request_error_t, const char *); +bool send_ping(struct meshlink_handle *mesh, struct connection_t *); +bool send_pong(struct meshlink_handle *mesh, struct connection_t *); +bool send_add_edge(struct meshlink_handle *mesh, struct connection_t *, const struct edge_t *, int contradictions); +bool send_del_edge(struct meshlink_handle *mesh, struct connection_t *, const struct edge_t *, int contradictions); +bool send_req_key(struct meshlink_handle *mesh, struct node_t *); +bool send_canonical_address(struct meshlink_handle *mesh, struct node_t *); + +/* Request handlers */ + +bool id_h(struct meshlink_handle *mesh, struct connection_t *, const char *); +bool ack_h(struct meshlink_handle *mesh, struct connection_t *, const char *); +bool status_h(struct meshlink_handle *mesh, struct connection_t *, const char *); +bool error_h(struct meshlink_handle *mesh, struct connection_t *, const char *); +bool termreq_h(struct meshlink_handle *mesh, struct connection_t *, const char *); +bool ping_h(struct meshlink_handle *mesh, struct connection_t *, const char *); +bool pong_h(struct meshlink_handle *mesh, struct connection_t *, const char *); +bool add_edge_h(struct meshlink_handle *mesh, struct connection_t *, const char *); +bool del_edge_h(struct meshlink_handle *mesh, struct connection_t *, const char *); +bool key_changed_h(struct meshlink_handle *mesh, struct connection_t *, const char *); +bool req_key_h(struct meshlink_handle *mesh, struct connection_t *, const char *); +bool ans_key_h(struct meshlink_handle *mesh, struct connection_t *, const char *); +bool tcppacket_h(struct meshlink_handle *mesh, struct connection_t *, const char *); + +#endif diff --git a/src/protocol_auth.c b/src/protocol_auth.c new file mode 100644 index 0000000..a0d350d --- /dev/null +++ b/src/protocol_auth.c @@ -0,0 +1,454 @@ +/* + protocol_auth.c -- handle the meta-protocol, authentication + Copyright (C) 2014-2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "conf.h" +#include "connection.h" +#include "devtools.h" +#include "ecdsa.h" +#include "edge.h" +#include "graph.h" +#include "logger.h" +#include "meshlink_internal.h" +#include "meta.h" +#include "net.h" +#include "netutl.h" +#include "node.h" +#include "packmsg.h" +#include "prf.h" +#include "protocol.h" +#include "sptps.h" +#include "utils.h" +#include "xalloc.h" +#include "ed25519/sha512.h" + +#include + +extern bool node_write_devclass(meshlink_handle_t *mesh, node_t *n); + +bool send_id(meshlink_handle_t *mesh, connection_t *c) { + return send_request(mesh, c, NULL, "%d %s %d.%d %s", ID, mesh->self->name, PROT_MAJOR, PROT_MINOR, mesh->appname); +} + +static bool commit_invitation(meshlink_handle_t *mesh, connection_t *c, const void *data) { + // Check if the node is known + node_t *n = lookup_node(mesh, c->name); + + if(n) { + if(n->status.blacklisted) { + logger(mesh, MESHLINK_ERROR, "Invitee %s is blacklisted", c->name); + } else { + logger(mesh, MESHLINK_ERROR, "Invitee %s already known", c->name); + } + + return false; + } + + // Create a new node + n = new_node(); + n->name = xstrdup(c->name); + n->devclass = DEV_CLASS_UNKNOWN; + n->ecdsa = ecdsa_set_public_key(data); + n->submesh = c->submesh; + + // Remember its current address + node_add_recent_address(mesh, n, &c->address); + + if(!node_write_config(mesh, n, true) || !config_sync(mesh, "current")) { + logger(mesh, MESHLINK_ERROR, "Error writing configuration file for invited node %s!\n", c->name); + free_node(n); + return false; + + } + + node_add(mesh, n); + + logger(mesh, MESHLINK_INFO, "Key successfully received from %s", c->name); + + //TODO: callback to application to inform of an accepted invitation + + sptps_send_record(&c->sptps, 1, "", 0); + + return true; +} + +static bool process_invitation(meshlink_handle_t *mesh, connection_t *c, const void *data) { + // Recover the filename from the cookie and the key + char *fingerprint = ecdsa_get_base64_public_key(mesh->invitation_key); + char hash[64]; + char hashbuf[18 + strlen(fingerprint)]; + char cookie[25]; + memcpy(hashbuf, data, 18); + memcpy(hashbuf + 18, fingerprint, sizeof(hashbuf) - 18); + sha512(hashbuf, sizeof(hashbuf), hash); + b64encode_urlsafe(hash, cookie, 18); + free(fingerprint); + + config_t config; + + if(!invitation_read(mesh, "current", cookie, &config, mesh->config_key)) { + logger(mesh, MESHLINK_ERROR, "Error while trying to read invitation file\n"); + return false; + } + + // Read the new node's Name from the file + packmsg_input_t in = {config.buf, config.len}; + packmsg_get_uint32(&in); // skip version + free(c->name); + c->name = packmsg_get_str_dup(&in); + + // Check if the file contains Sub-Mesh information + char *submesh_name = packmsg_get_str_dup(&in); + + if(!strcmp(submesh_name, CORE_MESH)) { + free(submesh_name); + c->submesh = NULL; + } else { + if(!check_id(submesh_name)) { + logger(mesh, MESHLINK_ERROR, "Invalid invitation file %s\n", cookie); + free(submesh_name); + return false; + } + + c->submesh = lookup_or_create_submesh(mesh, submesh_name); + free(submesh_name); + + if(!c->submesh) { + logger(mesh, MESHLINK_ERROR, "Unknown submesh in invitation file %s\n", cookie); + return false; + } + } + + if(mesh->inviter_commits_first && !commit_invitation(mesh, c, (const char *)data + 18)) { + return false; + } + + if(mesh->inviter_commits_first) { + devtool_set_inviter_commits_first(true); + } + + // Send the node the contents of the invitation file + sptps_send_record(&c->sptps, 0, config.buf, config.len); + + config_free(&config); + + c->status.invitation_used = true; + + logger(mesh, MESHLINK_INFO, "Invitation %s successfully sent to %s", cookie, c->name); + return true; +} + +static bool receive_invitation_sptps(void *handle, uint8_t type, const void *data, uint16_t len) { + connection_t *c = handle; + meshlink_handle_t *mesh = c->mesh; + + // Extend the time for the invitation exchange upon receiving a valid message + c->last_ping_time = mesh->loop.now.tv_sec; + + if(type == SPTPS_HANDSHAKE) { + // The peer should send its cookie first. + return true; + } + + if(mesh->inviter_commits_first) { + if(type == 2 && len == 18 + 32 && !c->status.invitation_used) { + return process_invitation(mesh, c, data); + } + } else { + if(type == 0 && len == 18 && !c->status.invitation_used) { + return process_invitation(mesh, c, data); + } else if(type == 1 && len == 32 && c->status.invitation_used) { + return commit_invitation(mesh, c, data); + } + } + + return false; +} + +bool id_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { + assert(request); + assert(*request); + + char name[MAX_STRING_SIZE]; + + if(sscanf(request, "%*d " MAX_STRING " %d.%d", name, &c->protocol_major, &c->protocol_minor) < 2) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s", "ID", c->name); + return false; + } + + /* Check if this is an invitation */ + + if(name[0] == '?') { + if(!mesh->invitation_key) { + logger(mesh, MESHLINK_ERROR, "Got invitation from %s but we don't have an invitation key", c->name); + return false; + } + + c->ecdsa = ecdsa_set_base64_public_key(name + 1); + + if(!c->ecdsa) { + logger(mesh, MESHLINK_ERROR, "Got bad invitation from %s", c->name); + return false; + } + + c->status.invitation = true; + char *mykey = ecdsa_get_base64_public_key(mesh->invitation_key); + + if(!mykey) { + return false; + } + + if(!send_request(mesh, c, NULL, "%d %s", ACK, mykey)) { + return false; + } + + free(mykey); + + c->protocol_minor = 2; + c->allow_request = 1; + c->last_ping_time = mesh->loop.now.tv_sec; + + return sptps_start(&c->sptps, c, false, false, mesh->invitation_key, c->ecdsa, meshlink_invitation_label, sizeof(meshlink_invitation_label), send_meta_sptps, receive_invitation_sptps); + } + + /* Check if identity is a valid name */ + + if(!check_id(name)) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s: %s", "ID", c->name, "invalid name"); + return false; + } + + /* If this is an outgoing connection, make sure we are connected to the right host */ + + if(c->outgoing) { + if(strcmp(c->name, name)) { + logger(mesh, MESHLINK_ERROR, "Peer is %s instead of %s", name, c->name); + return false; + } + } else { + if(c->name) { + free(c->name); + } + + c->name = xstrdup(name); + } + + /* Check if version matches */ + + if(c->protocol_major != PROT_MAJOR) { + logger(mesh, MESHLINK_ERROR, "Peer %s uses incompatible version %d.%d", + c->name, c->protocol_major, c->protocol_minor); + return false; + } + + /* Check if we know this node */ + + node_t *n = lookup_node(mesh, c->name); + + if(!n) { + logger(mesh, MESHLINK_ERROR, "Peer %s has unknown identity", c->name); + return false; + } + + if(!node_read_public_key(mesh, n)) { + logger(mesh, MESHLINK_ERROR, "No key known for peer %s", c->name); + + if(n->status.reachable && !n->status.waitingforkey) { + logger(mesh, MESHLINK_INFO, "Requesting key from peer %s", c->name); + send_req_key(mesh, n); + } + + return false; + } + + /* Forbid version rollback for nodes whose ECDSA key we know */ + + if(ecdsa_active(c->ecdsa) && c->protocol_minor < 2) { + logger(mesh, MESHLINK_ERROR, "Peer %s tries to roll back protocol version to %d.%d", + c->name, c->protocol_major, c->protocol_minor); + return false; + } + + c->allow_request = ACK; + c->last_ping_time = mesh->loop.now.tv_sec; + char label[sizeof(meshlink_tcp_label) + strlen(mesh->self->name) + strlen(c->name) + 2]; + + if(c->outgoing) { + snprintf(label, sizeof(label), "%s %s %s", meshlink_tcp_label, mesh->self->name, c->name); + } else { + snprintf(label, sizeof(label), "%s %s %s", meshlink_tcp_label, c->name, mesh->self->name); + } + + if(mesh->log_level <= MESHLINK_DEBUG) { + char buf1[1024], buf2[1024]; + bin2hex((uint8_t *)mesh->private_key + 64, buf1, 32); + bin2hex((uint8_t *)n->ecdsa + 64, buf2, 32); + logger(mesh, MESHLINK_DEBUG, "Connection to %s mykey %s hiskey %s", c->name, buf1, buf2); + } + + return sptps_start(&c->sptps, c, c->outgoing, false, mesh->private_key, n->ecdsa, label, sizeof(label) - 1, send_meta_sptps, receive_meta_sptps); +} + +bool send_ack(meshlink_handle_t *mesh, connection_t *c) { + node_t *n = lookup_node(mesh, c->name); + + if(n && n->status.blacklisted) { + logger(mesh, MESHLINK_WARNING, "Peer %s is blacklisted", c->name); + return send_error(mesh, c, BLACKLISTED, "blacklisted"); + } + + c->last_ping_time = mesh->loop.now.tv_sec; + return send_request(mesh, c, NULL, "%d %s %d %x", ACK, mesh->myport, mesh->devclass, OPTION_PMTU_DISCOVERY | (PROT_MINOR << 24)); +} + +static void send_everything(meshlink_handle_t *mesh, connection_t *c) { + /* Send all known subnets and edges */ + + for splay_each(node_t, n, mesh->nodes) { + for inner_splay_each(edge_t, e, n->edge_tree) { + send_add_edge(mesh, c, e, 0); + } + } +} + +bool ack_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { + assert(request); + assert(*request); + + char hisport[MAX_STRING_SIZE]; + int devclass; + uint32_t options; + node_t *n; + + if(sscanf(request, "%*d " MAX_STRING " %d %x", hisport, &devclass, &options) != 3) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s", "ACK", c->name); + return false; + } + + if(devclass < 0 || devclass >= DEV_CLASS_COUNT) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s: %s", "ACK", c->name, "devclass invalid"); + return false; + } + + /* Check if we already have a node_t for him */ + + n = lookup_node(mesh, c->name); + + if(!n) { + n = new_node(); + n->name = xstrdup(c->name); + node_add(mesh, n); + } else { + if(n->connection) { + /* Oh dear, we already have a connection to this node. */ + logger(mesh, MESHLINK_INFO, "Established a second connection with %s, closing old connection", n->connection->name); + + if(n->connection->outgoing) { + if(c->outgoing) { + logger(mesh, MESHLINK_WARNING, "Two outgoing connections to the same node!"); + } else { + c->outgoing = n->connection->outgoing; + } + + n->connection->outgoing = NULL; + } + + /* Remove the edge before terminating the connection, to prevent a graph update. */ + edge_del(mesh, n->connection->edge); + n->connection->edge = NULL; + + terminate_connection(mesh, n->connection, false); + } + } + + n->devclass = devclass; + n->status.dirty = true; + + n->last_successfull_connection = mesh->loop.now.tv_sec; + + n->connection = c; + n->nexthop = n; + c->node = n; + + /* Activate this connection */ + + c->allow_request = ALL; + c->last_key_renewal = mesh->loop.now.tv_sec; + c->status.active = true; + + logger(mesh, MESHLINK_INFO, "Connection with %s activated", c->name); + + if(mesh->meta_status_cb) { + mesh->meta_status_cb(mesh, (meshlink_node_t *)n, true); + } + + /* Terminate any connections to this node that are not activated yet */ + + for list_each(connection_t, other, mesh->connections) { + if(!other->status.active && !strcmp(other->name, c->name)) { + if(other->outgoing) { + if(c->outgoing) { + logger(mesh, MESHLINK_WARNING, "Two outgoing connections to the same node!"); + } else { + c->outgoing = other->outgoing; + } + + other->outgoing = NULL; + } + + logger(mesh, MESHLINK_DEBUG, "Terminating pending second connection with %s", n->name); + terminate_connection(mesh, other, false); + } + } + + /* Send him everything we know */ + + send_everything(mesh, c); + + /* Create an edge_t for this connection */ + + assert(devclass >= 0 && devclass < DEV_CLASS_COUNT); + + c->edge = new_edge(); + c->edge->from = mesh->self; + c->edge->to = n; + sockaddrcpy_setport(&c->edge->address, &c->address, atoi(hisport)); + c->edge->weight = mesh->dev_class_traits[devclass].edge_weight; + c->edge->connection = c; + + node_add_recent_address(mesh, n, &c->address); + edge_add(mesh, c->edge); + + /* Notify everyone of the new edge */ + + send_add_edge(mesh, mesh->everyone, c->edge, 0); + + /* Run MST and SSSP algorithms */ + + graph(mesh); + + /* Request a session key to jump start UDP traffic */ + + if(c->status.initiator) { + send_req_key(mesh, n); + } + + return true; +} diff --git a/src/protocol_edge.c b/src/protocol_edge.c new file mode 100644 index 0000000..a2ab267 --- /dev/null +++ b/src/protocol_edge.c @@ -0,0 +1,384 @@ +/* + protocol_edge.c -- handle the meta-protocol, edges + Copyright (C) 2014 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "conf.h" +#include "connection.h" +#include "edge.h" +#include "graph.h" +#include "logger.h" +#include "meshlink_internal.h" +#include "meta.h" +#include "net.h" +#include "netutl.h" +#include "node.h" +#include "protocol.h" +#include "utils.h" +#include "xalloc.h" +#include "submesh.h" + +bool send_add_edge(meshlink_handle_t *mesh, connection_t *c, const edge_t *e, int contradictions) { + bool x; + char *address, *port; + const char *from_submesh, *to_submesh; + const submesh_t *s = NULL; + + if(c->node && c->node->submesh) { + if(!submesh_allows_node(e->from->submesh, c->node)) { + return true; + } + + if(!submesh_allows_node(e->to->submesh, c->node)) { + return true; + } + } + + if(e->from->submesh && e->to->submesh && (e->from->submesh != e->to->submesh)) { + return true; + } + + sockaddr2str(&e->address, &address, &port); + + if(e->from->submesh) { + from_submesh = e->from->submesh->name; + } else { + from_submesh = CORE_MESH; + } + + if(e->to->submesh) { + to_submesh = e->to->submesh->name; + } else { + to_submesh = CORE_MESH; + } + + if(e->from->submesh) { + s = e->from->submesh; + } else { + s = e->to->submesh; + } + + x = send_request(mesh, c, s, "%d %x %s %d %s %s %s %s %d %s %x %d %d %x", ADD_EDGE, prng(mesh, UINT_MAX), + e->from->name, e->from->devclass, from_submesh, e->to->name, address, port, + e->to->devclass, to_submesh, OPTION_PMTU_DISCOVERY, e->weight, contradictions, e->from->session_id); + free(address); + free(port); + + return x; +} + +bool add_edge_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { + assert(request); + assert(*request); + + edge_t *e; + node_t *from, *to; + char from_name[MAX_STRING_SIZE]; + int from_devclass; + char from_submesh_name[MAX_STRING_SIZE] = ""; + char to_name[MAX_STRING_SIZE]; + char to_address[MAX_STRING_SIZE]; + char to_port[MAX_STRING_SIZE]; + int to_devclass; + char to_submesh_name[MAX_STRING_SIZE] = ""; + sockaddr_t address; + int weight; + int contradictions = 0; + uint32_t session_id = 0; + submesh_t *s = NULL; + + if(sscanf(request, "%*d %*x "MAX_STRING" %d "MAX_STRING" "MAX_STRING" "MAX_STRING" "MAX_STRING" %d "MAX_STRING" %*x %d %d %x", + from_name, &from_devclass, from_submesh_name, to_name, to_address, to_port, &to_devclass, to_submesh_name, + &weight, &contradictions, &session_id) < 9) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s", "ADD_EDGE", c->name); + return false; + } + + // Check if devclasses are valid + + if(from_devclass < 0 || from_devclass >= DEV_CLASS_COUNT) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s: %s", "ADD_EDGE", c->name, "from devclass invalid"); + return false; + } + + if(to_devclass < 0 || to_devclass >= DEV_CLASS_COUNT) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s: %s", "ADD_EDGE", c->name, "to devclass invalid"); + return false; + } + + if(0 == strcmp(from_submesh_name, "")) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s: %s", "ADD_EDGE", c->name, "invalid submesh id"); + return false; + } + + if(0 == strcmp(to_submesh_name, "")) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s: %s", "ADD_EDGE", c->name, "invalid submesh id"); + return false; + } + + if(seen_request(mesh, request)) { + return true; + } + + /* Lookup nodes */ + + from = lookup_node(mesh, from_name); + to = lookup_node(mesh, to_name); + + if(!from) { + from = new_node(); + from->status.dirty = true; + from->status.blacklisted = mesh->default_blacklist; + from->name = xstrdup(from_name); + from->devclass = from_devclass; + + from->submesh = NULL; + + if(0 != strcmp(from_submesh_name, CORE_MESH)) { + if(!(from->submesh = lookup_or_create_submesh(mesh, from_submesh_name))) { + return false; + } + } + + node_add(mesh, from); + } + + if(contradictions > 50) { + handle_duplicate_node(mesh, from); + } + + from->devclass = from_devclass; + + if(!from->session_id) { + from->session_id = session_id; + } + + if(!to) { + to = new_node(); + to->status.dirty = true; + to->status.blacklisted = mesh->default_blacklist; + to->name = xstrdup(to_name); + to->devclass = to_devclass; + + to->submesh = NULL; + + if(0 != strcmp(to_submesh_name, CORE_MESH)) { + if(!(to->submesh = lookup_or_create_submesh(mesh, to_submesh_name))) { + return false; + + } + } + + node_add(mesh, to); + } + + to->devclass = to_devclass; + + /* Convert addresses */ + + address = str2sockaddr(to_address, to_port); + + /* Check if edge already exists */ + + e = lookup_edge(from, to); + + if(e) { + if(e->weight != weight || e->session_id != session_id || sockaddrcmp(&e->address, &address)) { + if(from == mesh->self) { + /* The sender has outdated information, we own this edge to send a correction back */ + logger(mesh, MESHLINK_DEBUG, "Got %s from %s for ourself which does not match existing entry", "ADD_EDGE", c->name); + send_add_edge(mesh, c, e, 0); + return true; + } else if(to == mesh->self && from != c->node && from->status.reachable) { + /* The sender has outdated information, someone else owns this node so they will correct */ + logger(mesh, MESHLINK_DEBUG, "Got %s from %s which does not match existing entry, ignoring", "ADD_EDGE", c->name); + return true; + } else { + /* Might be outdated, but update our information, another node will send a correction if necessary */ + logger(mesh, MESHLINK_DEBUG, "Got %s from %s which does not match existing entry", "ADD_EDGE", c->name); + edge_del(mesh, e); + } + } else { + return true; + } + } else if(from == mesh->self) { + logger(mesh, MESHLINK_WARNING, "Got %s from %s for ourself which does not exist", "ADD_EDGE", c->name); + mesh->contradicting_add_edge++; + e = new_edge(); + e->from = from; + e->to = to; + e->session_id = session_id; + send_del_edge(mesh, c, e, mesh->contradicting_add_edge); + free_edge(e); + return true; + } + + e = new_edge(); + e->from = from; + e->to = to; + e->address = address; + e->weight = weight; + e->session_id = session_id; + edge_add(mesh, e); + + /* Run MST before or after we tell the rest? */ + + graph(mesh); + + if(e->from->submesh && e->to->submesh && (e->from->submesh != e->to->submesh)) { + logger(mesh, MESHLINK_ERROR, "Dropping add edge ( %s to %s )", e->from->submesh->name, e->to->submesh->name); + return false; + } + + if(e->from->submesh) { + s = e->from->submesh; + } else { + s = e->to->submesh; + } + + /* Tell the rest about the new edge */ + + forward_request(mesh, c, s, request); + + return true; +} + +bool send_del_edge(meshlink_handle_t *mesh, connection_t *c, const edge_t *e, int contradictions) { + submesh_t *s = NULL; + + if(c->node && c->node->submesh) { + if(!submesh_allows_node(e->from->submesh, c->node)) { + return true; + } + + if(!submesh_allows_node(e->to->submesh, c->node)) { + return true; + } + } + + if(e->from->submesh && e->to->submesh && (e->from->submesh != e->to->submesh)) { + return true; + } + + + if(e->from->submesh) { + s = e->from->submesh; + } else { + s = e->to->submesh; + } + + return send_request(mesh, c, s, "%d %x %s %s %d %x", DEL_EDGE, prng(mesh, UINT_MAX), + e->from->name, e->to->name, contradictions, e->session_id); +} + +bool del_edge_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { + assert(request); + assert(*request); + + edge_t *e; + char from_name[MAX_STRING_SIZE]; + char to_name[MAX_STRING_SIZE]; + node_t *from, *to; + int contradictions = 0; + uint32_t session_id = 0; + submesh_t *s = NULL; + + if(sscanf(request, "%*d %*x "MAX_STRING" "MAX_STRING" %d %x", from_name, to_name, &contradictions, &session_id) < 2) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s", "DEL_EDGE", c->name); + return false; + } + + if(seen_request(mesh, request)) { + return true; + } + + /* Lookup nodes */ + + from = lookup_node(mesh, from_name); + to = lookup_node(mesh, to_name); + + if(!from) { + logger(mesh, MESHLINK_WARNING, "Got %s from %s which does not appear in the edge tree", "DEL_EDGE", c->name); + return true; + } + + if(!to) { + logger(mesh, MESHLINK_WARNING, "Got %s from %s which does not appear in the edge tree", "DEL_EDGE", c->name); + return true; + } + + if(contradictions > 50) { + handle_duplicate_node(mesh, from); + } + + /* Check if edge exists */ + + e = lookup_edge(from, to); + + if(!e) { + logger(mesh, MESHLINK_WARNING, "Got %s from %s which does not appear in the edge tree", "DEL_EDGE", c->name); + return true; + } + + if(e->from == mesh->self) { + logger(mesh, MESHLINK_WARNING, "Got %s from %s for ourself", "DEL_EDGE", c->name); + mesh->contradicting_del_edge++; + send_add_edge(mesh, c, e, mesh->contradicting_del_edge); /* Send back a correction */ + return true; + } + + /* Tell the rest about the deleted edge */ + + + if(!e->from->submesh || !e->to->submesh || (e->from->submesh == e->to->submesh)) { + if(e->from->submesh) { + s = e->from->submesh; + } else { + s = e->to->submesh; + } + + /* Tell the rest about the deleted edge */ + forward_request(mesh, c, s, request); + + } else { + logger(mesh, MESHLINK_ERROR, "Dropping del edge ( %s to %s )", e->from->submesh->name, e->to->submesh->name); + return false; + } + + /* Delete the edge */ + + edge_del(mesh, e); + + /* Run MST before or after we tell the rest? */ + + graph(mesh); + + /* If the node is not reachable anymore but we remember it had an edge to us, clean it up */ + + if(!to->status.reachable) { + e = lookup_edge(to, mesh->self); + + if(e) { + send_del_edge(mesh, mesh->everyone, e, 0); + edge_del(mesh, e); + } + } + + return true; +} diff --git a/src/protocol_key.c b/src/protocol_key.c new file mode 100644 index 0000000..25dbc55 --- /dev/null +++ b/src/protocol_key.c @@ -0,0 +1,494 @@ +/* + protocol_key.c -- handle the meta-protocol, key exchange + Copyright (C) 2014-2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "connection.h" +#include "logger.h" +#include "meshlink_internal.h" +#include "net.h" +#include "netutl.h" +#include "node.h" +#include "prf.h" +#include "protocol.h" +#include "sptps.h" +#include "utils.h" +#include "xalloc.h" + +static const int req_key_timeout = 2; + +bool key_changed_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { + assert(request); + assert(*request); + + char name[MAX_STRING_SIZE]; + node_t *n; + + if(sscanf(request, "%*d %*x " MAX_STRING, name) != 1) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s", "KEY_CHANGED", c->name); + return false; + } + + if(seen_request(mesh, request)) { + return true; + } + + n = lookup_node(mesh, name); + + if(!n) { + logger(mesh, MESHLINK_ERROR, "Got %s from %s origin %s which does not exist", "KEY_CHANGED", c->name, name); + return true; + } + + /* Tell the others */ + + forward_request(mesh, c, NULL, request); + + return true; +} + +static bool send_initial_sptps_data(void *handle, uint8_t type, const void *data, size_t len) { + (void)type; + + assert(data); + assert(len); + + node_t *to = handle; + meshlink_handle_t *mesh = to->mesh; + + if(!to->nexthop || !to->nexthop->connection) { + logger(mesh, MESHLINK_WARNING, "Cannot send SPTPS data to %s via %s", to->name, to->nexthop ? to->nexthop->name : to->name); + return false; + } + + to->sptps.send_data = send_sptps_data; + char buf[len * 4 / 3 + 5]; + b64encode(data, buf, len); + return send_request(mesh, to->nexthop->connection, NULL, "%d %s %s %d %s", REQ_KEY, mesh->self->name, to->name, REQ_KEY, buf); +} + +bool send_canonical_address(meshlink_handle_t *mesh, node_t *to) { + if(!mesh->self->canonical_address) { + return true; + } + + return send_request(mesh, to->nexthop->connection, NULL, "%d %s %s %d %s", REQ_KEY, mesh->self->name, to->name, REQ_CANONICAL, mesh->self->canonical_address); +} + +bool send_req_key(meshlink_handle_t *mesh, node_t *to) { + if(!node_read_public_key(mesh, to)) { + logger(mesh, MESHLINK_DEBUG, "No ECDSA key known for %s", to->name); + + if(!to->nexthop || !to->nexthop->connection) { + logger(mesh, MESHLINK_WARNING, "Cannot send REQ_PUBKEY to %s via %s", to->name, to->nexthop ? to->nexthop->name : to->name); + return true; + } + + char *pubkey = ecdsa_get_base64_public_key(mesh->private_key); + send_request(mesh, to->nexthop->connection, NULL, "%d %s %s %d %s", REQ_KEY, mesh->self->name, to->name, REQ_PUBKEY, pubkey); + free(pubkey); + return true; + } + + if(to->sptps.label) { + logger(mesh, MESHLINK_DEBUG, "send_req_key(%s) called while sptps->label != NULL!", to->name); + } + + /* Send our canonical address to help with UDP hole punching */ + send_canonical_address(mesh, to); + + char label[sizeof(meshlink_udp_label) + strlen(mesh->self->name) + strlen(to->name) + 2]; + snprintf(label, sizeof(label), "%s %s %s", meshlink_udp_label, mesh->self->name, to->name); + sptps_stop(&to->sptps); + to->status.validkey = false; + to->status.waitingforkey = true; + to->last_req_key = mesh->loop.now.tv_sec; + return sptps_start(&to->sptps, to, true, true, mesh->private_key, to->ecdsa, label, sizeof(label) - 1, send_initial_sptps_data, receive_sptps_record); +} + +/* REQ_KEY is overloaded to allow arbitrary requests to be routed between two nodes. */ + +static bool req_key_ext_h(meshlink_handle_t *mesh, connection_t *c, const char *request, node_t *from, int reqno) { + (void)c; + + if(!from->nexthop || !from->nexthop->connection) { + logger(mesh, MESHLINK_WARNING, "Cannot answer REQ_KEY from %s via %s", from->name, from->nexthop ? from->nexthop->name : from->name); + return true; + } + + switch(reqno) { + case REQ_PUBKEY: { + char *pubkey = ecdsa_get_base64_public_key(mesh->private_key); + + if(!node_read_public_key(mesh, from)) { + char hiskey[MAX_STRING_SIZE]; + + if(sscanf(request, "%*d %*s %*s %*d " MAX_STRING, hiskey) == 1) { + from->ecdsa = ecdsa_set_base64_public_key(hiskey); + + if(!from->ecdsa) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s: %s", "REQ_PUBKEY", from->name, "invalid pubkey"); + return true; + } + + logger(mesh, MESHLINK_INFO, "Learned ECDSA public key from %s", from->name); + from->status.dirty = true; + + if(!node_write_config(mesh, from, true)) { + // ignore + } + } + } + + send_request(mesh, from->nexthop->connection, NULL, "%d %s %s %d %s", REQ_KEY, mesh->self->name, from->name, ANS_PUBKEY, pubkey); + free(pubkey); + return true; + } + + case ANS_PUBKEY: { + if(node_read_public_key(mesh, from)) { + logger(mesh, MESHLINK_WARNING, "Got ANS_PUBKEY from %s even though we already have his pubkey", from->name); + return true; + } + + char pubkey[MAX_STRING_SIZE]; + + if(sscanf(request, "%*d %*s %*s %*d " MAX_STRING, pubkey) != 1 || !(from->ecdsa = ecdsa_set_base64_public_key(pubkey))) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s: %s", "ANS_PUBKEY", from->name, "invalid pubkey"); + return true; + } + + logger(mesh, MESHLINK_INFO, "Learned ECDSA public key from %s", from->name); + from->status.dirty = true; + + if(!node_write_config(mesh, from, true)) { + // ignore + } + + /* If we are trying to form an outgoing connection to this node, retry immediately */ + for list_each(outgoing_t, outgoing, mesh->outgoings) { + if(outgoing->node == from && outgoing->ev.cb) { + outgoing->timeout = 0; + timeout_set(&mesh->loop, &outgoing->ev, &(struct timespec) { + 0, 0 + }); + } + } + + /* Also reset any UTCP timers */ + utcp_reset_timers(from->utcp); + + return true; + } + + case REQ_KEY: { + if(!node_read_public_key(mesh, from)) { + logger(mesh, MESHLINK_DEBUG, "No ECDSA key known for %s", from->name); + send_request(mesh, from->nexthop->connection, NULL, "%d %s %s %d", REQ_KEY, mesh->self->name, from->name, REQ_PUBKEY); + return true; + } + + if(from->sptps.label) { + logger(mesh, MESHLINK_DEBUG, "Got REQ_KEY from %s while we already started a SPTPS session!", from->name); + + if(mesh->loop.now.tv_sec < from->last_req_key + req_key_timeout && strcmp(mesh->self->name, from->name) < 0) { + logger(mesh, MESHLINK_DEBUG, "Ignoring REQ_KEY from %s.", from->name); + return true; + } + } + + char buf[MAX_STRING_SIZE]; + int len; + + if(sscanf(request, "%*d %*s %*s %*d " MAX_STRING, buf) != 1 || !(len = b64decode(buf, buf, strlen(buf)))) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s: %s", "REQ_SPTPS_START", from->name, "invalid SPTPS data"); + return true; + } + + char label[sizeof(meshlink_udp_label) + strlen(from->name) + strlen(mesh->self->name) + 2]; + snprintf(label, sizeof(label), "%s %s %s", meshlink_udp_label, from->name, mesh->self->name); + sptps_stop(&from->sptps); + from->status.validkey = false; + from->status.waitingforkey = true; + from->last_req_key = mesh->loop.now.tv_sec; + + /* Send our canonical address to help with UDP hole punching */ + send_canonical_address(mesh, from); + + if(!sptps_start(&from->sptps, from, false, true, mesh->private_key, from->ecdsa, label, sizeof(label) - 1, send_sptps_data, receive_sptps_record)) { + logger(mesh, MESHLINK_ERROR, "Could not start SPTPS session with %s: %s", from->name, strerror(errno)); + return true; + } + + if(!sptps_receive_data(&from->sptps, buf, len)) { + logger(mesh, MESHLINK_ERROR, "Could not process SPTPS data from %s: %s", from->name, strerror(errno)); + return true; + } + + return true; + } + + case REQ_SPTPS: { + if(!from->status.validkey) { + logger(mesh, MESHLINK_ERROR, "Got REQ_SPTPS from %s but we don't have a valid key yet", from->name); + return true; + } + + char buf[MAX_STRING_SIZE]; + int len; + + if(sscanf(request, "%*d %*s %*s %*d " MAX_STRING, buf) != 1 || !(len = b64decode(buf, buf, strlen(buf)))) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s: %s", "REQ_SPTPS", from->name, "invalid SPTPS data"); + return true; + } + + if(!sptps_receive_data(&from->sptps, buf, len)) { + logger(mesh, MESHLINK_ERROR, "Could not process SPTPS data from %s: %s", from->name, strerror(errno)); + return true; + } + + return true; + } + + case REQ_CANONICAL: { + char host[MAX_STRING_SIZE]; + char port[MAX_STRING_SIZE]; + + if(sscanf(request, "%*d %*s %*s %*d " MAX_STRING " " MAX_STRING, host, port) != 2) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s: %s", "REQ_CANONICAL", from->name, "invalid canonical address"); + return true; + } + + char *canonical_address; + xasprintf(&canonical_address, "%s %s", host, port); + + if(mesh->log_level <= MESHLINK_DEBUG && (!from->canonical_address || strcmp(from->canonical_address, canonical_address))) { + logger(mesh, MESHLINK_DEBUG, "Updating canonical address of %s to %s", from->name, canonical_address); + } + + free(from->canonical_address); + from->canonical_address = canonical_address; + return true; + } + + default: + logger(mesh, MESHLINK_ERROR, "Unknown extended REQ_KEY request from %s: %s", from->name, request); + return true; + } +} + +bool req_key_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { + assert(request); + assert(*request); + + char from_name[MAX_STRING_SIZE]; + char to_name[MAX_STRING_SIZE]; + node_t *from, *to; + int reqno = 0; + + if(sscanf(request, "%*d " MAX_STRING " " MAX_STRING " %d", from_name, to_name, &reqno) < 2) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s", "REQ_KEY", c->name); + return false; + } + + if(!check_id(from_name) || !check_id(to_name)) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s: %s", "REQ_KEY", c->name, "invalid name"); + return false; + } + + from = lookup_node(mesh, from_name); + + if(!from) { + logger(mesh, MESHLINK_ERROR, "Got %s from %s origin %s which does not exist in our connection list", + "REQ_KEY", c->name, from_name); + return true; + } + + to = lookup_node(mesh, to_name); + + if(!to) { + logger(mesh, MESHLINK_ERROR, "Got %s from %s destination %s which does not exist in our connection list", + "REQ_KEY", c->name, to_name); + return true; + } + + /* Check if this key request is for us */ + + if(to == mesh->self) { /* Yes */ + /* Is this an extended REQ_KEY message? */ + if(reqno) { + return req_key_ext_h(mesh, c, request, from, reqno); + } + + /* This should never happen. Ignore it, unless it came directly from the connected peer, in which case we disconnect. */ + return from->connection != c; + } else { + if(!to->status.reachable || !to->nexthop || !to->nexthop->connection) { + logger(mesh, MESHLINK_WARNING, "Got %s from %s destination %s which is not reachable", + "REQ_KEY", c->name, to_name); + return true; + } + + send_request(mesh, to->nexthop->connection, NULL, "%s", request); + } + + return true; +} + +bool ans_key_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { + assert(request); + assert(*request); + + char from_name[MAX_STRING_SIZE]; + char to_name[MAX_STRING_SIZE]; + char key[MAX_STRING_SIZE]; + char address[MAX_STRING_SIZE] = ""; + char port[MAX_STRING_SIZE] = ""; + int cipher, digest, maclength, compression; + node_t *from, *to; + + if(sscanf(request, "%*d "MAX_STRING" "MAX_STRING" "MAX_STRING" %d %d %d %d "MAX_STRING" "MAX_STRING, + from_name, to_name, key, &cipher, &digest, &maclength, + &compression, address, port) < 7) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s", "ANS_KEY", c->name); + return false; + } + + if(!check_id(from_name) || !check_id(to_name)) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s: %s", "ANS_KEY", c->name, "invalid name"); + return false; + } + + from = lookup_node(mesh, from_name); + + if(!from) { + logger(mesh, MESHLINK_ERROR, "Got %s from %s origin %s which does not exist in our connection list", + "ANS_KEY", c->name, from_name); + return true; + } + + to = lookup_node(mesh, to_name); + + if(!to) { + logger(mesh, MESHLINK_ERROR, "Got %s from %s destination %s which does not exist in our connection list", + "ANS_KEY", c->name, to_name); + return true; + } + + /* Forward it if necessary */ + + if(to != mesh->self) { + if(!to->status.reachable) { + logger(mesh, MESHLINK_WARNING, "Got %s from %s destination %s which is not reachable", + "ANS_KEY", c->name, to_name); + return true; + } + + if(from == to) { + logger(mesh, MESHLINK_WARNING, "Got %s from %s from %s to %s", + "ANS_KEY", c->name, from_name, to_name); + return true; + } + + if(!to->nexthop || !to->nexthop->connection) { + logger(mesh, MESHLINK_WARNING, "Cannot forward ANS_KEY to %s via %s", to->name, to->nexthop ? to->nexthop->name : to->name); + return false; + } + + /* Append the known UDP address of the from node, if we have a confirmed one */ + if(!*address && from->status.udp_confirmed && from->address.sa.sa_family != AF_UNSPEC) { + char *reflexive_address, *reflexive_port; + logger(mesh, MESHLINK_DEBUG, "Appending reflexive UDP address to ANS_KEY from %s to %s", from->name, to->name); + sockaddr2str(&from->address, &reflexive_address, &reflexive_port); + send_request(mesh, to->nexthop->connection, NULL, "%s %s %s", request, reflexive_address, reflexive_port); + free(reflexive_address); + free(reflexive_port); + return true; + } + + return send_request(mesh, to->nexthop->connection, NULL, "%s", request); + } + + /* Is this an ANS_KEY informing us of our own reflexive UDP address? */ + + if(from == mesh->self) { + if(*key == '.' && *address && *port) { + logger(mesh, MESHLINK_DEBUG, "Learned our own reflexive UDP address from %s: %s port %s", c->name, address, port); + + /* Inform all other nodes we want to communicate with and which are reachable via this connection */ + for splay_each(node_t, n, mesh->nodes) { + if(n->nexthop != c->node) { + continue; + } + + if(n->status.udp_confirmed) { + continue; + } + + if(!n->status.waitingforkey && !n->status.validkey) { + continue; + } + + if(!n->nexthop->connection) { + continue; + } + + logger(mesh, MESHLINK_DEBUG, "Forwarding our own reflexive UDP address to %s", n->name); + send_request(mesh, c, NULL, "%d %s %s . -1 -1 -1 0 %s %s", ANS_KEY, mesh->self->name, n->name, address, port); + } + } else { + logger(mesh, MESHLINK_WARNING, "Got %s from %s from %s to %s", + "ANS_KEY", c->name, from_name, to_name); + } + + return true; + } + + /* Process SPTPS data if present */ + + if(*key != '.') { + /* Don't use key material until every check has passed. */ + from->status.validkey = false; + + /* Compression is not supported. */ + if(compression != 0) { + logger(mesh, MESHLINK_ERROR, "Node %s uses bogus compression level!", from->name); + return true; + } + + char buf[strlen(key)]; + int len = b64decode(key, buf, strlen(key)); + + if(!len || !sptps_receive_data(&from->sptps, buf, len)) { + logger(mesh, MESHLINK_ERROR, "Error processing SPTPS data from %s", from->name); + } + } + + if(from->status.validkey) { + if(*address && *port) { + logger(mesh, MESHLINK_DEBUG, "Using reflexive UDP address from %s: %s port %s", from->name, address, port); + sockaddr_t sa = str2sockaddr(address, port); + update_node_udp(mesh, from, &sa); + } + + send_mtu_probe(mesh, from); + } + + return true; +} diff --git a/src/protocol_misc.c b/src/protocol_misc.c new file mode 100644 index 0000000..7410ad1 --- /dev/null +++ b/src/protocol_misc.c @@ -0,0 +1,147 @@ +/* + protocol_misc.c -- handle the meta-protocol, miscellaneous functions + Copyright (C) 2014-2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "conf.h" +#include "connection.h" +#include "logger.h" +#include "meshlink_internal.h" +#include "meta.h" +#include "net.h" +#include "netutl.h" +#include "protocol.h" +#include "utils.h" + +int maxoutbufsize = 0; + +/* Status and error notification routines */ + +bool status_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { + assert(request); + assert(*request); + + int statusno; + char statusstring[MAX_STRING_SIZE]; + + if(sscanf(request, "%*d %d " MAX_STRING, &statusno, statusstring) != 2) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s", "STATUS", c->name); + return false; + } + + logger(mesh, MESHLINK_INFO, "Status message from %s: %d: %s", c->name, statusno, statusstring); + + return true; +} + +bool send_error(meshlink_handle_t *mesh, connection_t *c, request_error_t err, const char *message) { + send_request(mesh, c, NULL, "%d %d %s", ERROR, err, message); + flush_meta(mesh, c); + return false; +} + +bool error_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { + assert(request); + assert(*request); + + int err; + char errorstring[MAX_STRING_SIZE]; + + if(sscanf(request, "%*d %d " MAX_STRING, &err, errorstring) != 2) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s", "ERROR", c->name); + return false; + } + + logger(mesh, MESHLINK_INFO, "Error message from %s: %d: %s", c->name, err, errorstring); + + switch(err) { + case BLACKLISTED: + if(mesh->blacklisted_cb) { + mesh->blacklisted_cb(mesh, (meshlink_node_t *)lookup_node(mesh, c->name)); + } + } + + return false; +} + +bool termreq_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { + (void)mesh; + (void)c; + (void)request; + + assert(request); + assert(*request); + + return false; +} + +bool send_ping(meshlink_handle_t *mesh, connection_t *c) { + c->status.pinged = true; + c->last_ping_time = mesh->loop.now.tv_sec; + + return send_request(mesh, c, NULL, "%d", PING); +} + +bool ping_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { + (void)request; + + assert(request); + assert(*request); + + return send_pong(mesh, c); +} + +bool send_pong(meshlink_handle_t *mesh, connection_t *c) { + return send_request(mesh, c, NULL, "%d", PONG); +} + +bool pong_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { + (void)mesh; + (void)request; + + assert(request); + assert(*request); + + c->status.pinged = false; + + /* Successful connection, reset timeout if this is an outgoing connection. */ + + if(c->outgoing) { + reset_outgoing(c->outgoing); + } + + return true; +} + +/* Sending and receiving packets via TCP */ + +bool tcppacket_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { + assert(request); + assert(*request); + + short int len; + + if(sscanf(request, "%*d %hd", &len) != 1) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s", "PACKET", c->name); + return false; + } + + // This should never happen with MeshLink. + return false; +} diff --git a/src/route.c b/src/route.c new file mode 100644 index 0000000..ee36a9b --- /dev/null +++ b/src/route.c @@ -0,0 +1,92 @@ +/* + route.c -- routing + Copyright (C) 2014 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "logger.h" +#include "meshlink_internal.h" +#include "net.h" +#include "route.h" +#include "utils.h" + +static bool checklength(node_t *source, vpn_packet_t *packet, uint16_t length) { + assert(length); + + if(packet->len < length) { + logger(source->mesh, MESHLINK_WARNING, "Got too short packet from %s", source->name); + return false; + } else { + return true; + } +} + +void route(meshlink_handle_t *mesh, node_t *source, vpn_packet_t *packet) { + assert(source); + + // TODO: route on name or key + + meshlink_packethdr_t *hdr = (meshlink_packethdr_t *) packet->data; + node_t *dest = lookup_node(mesh, (char *)hdr->destination); + logger(mesh, MESHLINK_DEBUG, "Routing packet from \"%s\" to \"%s\"\n", hdr->source, hdr->destination); + + //Check Length + if(!checklength(source, packet, sizeof(*hdr))) { + return; + } + + if(dest == NULL) { + //Lookup failed + logger(mesh, MESHLINK_WARNING, "Can't lookup the destination of a packet in the route() function. This should never happen!\n"); + logger(mesh, MESHLINK_WARNING, "Destination was: %s\n", hdr->destination); + return; + } + + if(dest == mesh->self) { + const void *payload = packet->data + sizeof(*hdr); + size_t len = packet->len - sizeof(*hdr); + + char hex[len * 2 + 1]; + + if(mesh->log_level <= MESHLINK_DEBUG) { + bin2hex(payload, hex, len); // don't do this unless it's going to be logged + } + + logger(mesh, MESHLINK_DEBUG, "I received a packet for me with payload: %s\n", hex); + + if(mesh->receive_cb) { + mesh->receive_cb(mesh, (meshlink_node_t *)source, payload, len); + } + + return; + } + + if(!dest->status.reachable) { + //TODO: check what to do here, not just print a warning + logger(mesh, MESHLINK_WARNING, "The destination of a packet in the route() function is unreachable. Dropping packet.\n"); + return; + } + + if(dest == source) { + logger(mesh, MESHLINK_ERROR, "Routing loop for packet from %s!", source->name); + return; + } + + send_packet(mesh, dest, packet); + return; +} diff --git a/src/route.h b/src/route.h new file mode 100644 index 0000000..92e9fe0 --- /dev/null +++ b/src/route.h @@ -0,0 +1,28 @@ +#ifndef MESHLINK_ROUTE_H +#define MESHLINK_ROUTE_H + +/* + route.h -- header file for route.c + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "net.h" +#include "node.h" + +void route(struct meshlink_handle *mesh, struct node_t *, struct vpn_packet_t *); + +#endif diff --git a/src/sockaddr.h b/src/sockaddr.h new file mode 100644 index 0000000..4a823aa --- /dev/null +++ b/src/sockaddr.h @@ -0,0 +1,28 @@ +#ifndef MESHLINK_SOCKADDR_H +#define MESHLINK_SOCKADDR_H + +#define AF_UNKNOWN 255 + +#ifdef SA_LEN +#define SALEN(s) SA_LEN(&(s)) +#else +#define SALEN(s) ((s).sa_family==AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)) +#endif + +struct sockaddr_unknown { + uint16_t family; + uint16_t pad1; + uint32_t pad2; + char *address; + char *port; +}; + +typedef union sockaddr_t { + struct sockaddr sa; + struct sockaddr_in in; + struct sockaddr_in6 in6; + struct sockaddr_unknown unknown; + struct sockaddr_storage storage; +} sockaddr_t; + +#endif diff --git a/src/splay_tree.c b/src/splay_tree.c new file mode 100644 index 0000000..2d69471 --- /dev/null +++ b/src/splay_tree.c @@ -0,0 +1,633 @@ +/* + splay_tree.c -- splay tree and linked list convenience + Copyright (C) 2014-2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "splay_tree.h" +#include "xalloc.h" + +/* Splay operation */ + +static splay_node_t *splay_top_down(splay_tree_t *tree, const void *data, int *result) { + splay_node_t left, right; + splay_node_t *leftbottom = &left, *rightbottom = &right, *child, *grandchild; + splay_node_t *root = tree->root; + int c; + + memset(&left, 0, sizeof(left)); + memset(&right, 0, sizeof(right)); + + if(!root) { + if(result) { + *result = 0; + } + + return NULL; + } + + while((c = tree->compare(data, root->data))) { + if(c < 0 && (child = root->left)) { + c = tree->compare(data, child->data); + + if(c < 0 && (grandchild = child->left)) { + rightbottom->left = child; + child->parent = rightbottom; + rightbottom = child; + + if((root->left = child->right)) { + child->right->parent = root; + } + + child->right = root; + root->parent = child; + + child->left = NULL; + grandchild->parent = NULL; + + root = grandchild; + } else if(c > 0 && (grandchild = child->right)) { + leftbottom->right = child; + child->parent = leftbottom; + leftbottom = child; + + child->right = NULL; + grandchild->parent = NULL; + + rightbottom->left = root; + root->parent = rightbottom; + rightbottom = root; + + root->left = NULL; + + root = grandchild; + } else { + rightbottom->left = root; + root->parent = rightbottom; + rightbottom = root; + + root->left = NULL; + child->parent = NULL; + + root = child; + break; + } + } else if(c > 0 && (child = root->right)) { + c = tree->compare(data, child->data); + + if(c > 0 && (grandchild = child->right)) { + leftbottom->right = child; + child->parent = leftbottom; + leftbottom = child; + + if((root->right = child->left)) { + child->left->parent = root; + } + + child->left = root; + root->parent = child; + + child->right = NULL; + grandchild->parent = NULL; + + root = grandchild; + } else if(c < 0 && (grandchild = child->left)) { + rightbottom->left = child; + child->parent = rightbottom; + rightbottom = child; + + child->left = NULL; + grandchild->parent = NULL; + + leftbottom->right = root; + root->parent = leftbottom; + leftbottom = root; + + root->right = NULL; + + root = grandchild; + } else { + leftbottom->right = root; + root->parent = leftbottom; + leftbottom = root; + + root->right = NULL; + child->parent = NULL; + + root = child; + break; + } + } else { + break; + } + } + + /* Merge trees */ + + if(left.right) { + if(root->left) { + leftbottom->right = root->left; + root->left->parent = leftbottom; + } + + root->left = left.right; + left.right->parent = root; + } + + if(right.left) { + if(root->right) { + rightbottom->left = root->right; + root->right->parent = rightbottom; + } + + root->right = right.left; + right.left->parent = root; + } + + /* Return result */ + + tree->root = root; + + if(result) { + *result = c; + } + + return tree->root; +} + +static void splay_bottom_up(splay_tree_t *tree, splay_node_t *node) { + splay_node_t *parent, *grandparent, *greatgrandparent; + + while((parent = node->parent)) { + if(!(grandparent = parent->parent)) { /* zig */ + if(node == parent->left) { + if((parent->left = node->right)) { + parent->left->parent = parent; + } + + node->right = parent; + } else { + if((parent->right = node->left)) { + parent->right->parent = parent; + } + + node->left = parent; + } + + parent->parent = node; + node->parent = NULL; + } else { + greatgrandparent = grandparent->parent; + + if(node == parent->left && parent == grandparent->left) { /* left zig-zig */ + if((grandparent->left = parent->right)) { + grandparent->left->parent = grandparent; + } + + parent->right = grandparent; + grandparent->parent = parent; + + if((parent->left = node->right)) { + parent->left->parent = parent; + } + + node->right = parent; + parent->parent = node; + } else if(node == parent->right && parent == grandparent->right) { /* right zig-zig */ + if((grandparent->right = parent->left)) { + grandparent->right->parent = grandparent; + } + + parent->left = grandparent; + grandparent->parent = parent; + + if((parent->right = node->left)) { + parent->right->parent = parent; + } + + node->left = parent; + parent->parent = node; + } else if(node == parent->right && parent == grandparent->left) { /* left-right zig-zag */ + if((parent->right = node->left)) { + parent->right->parent = parent; + } + + node->left = parent; + parent->parent = node; + + if((grandparent->left = node->right)) { + grandparent->left->parent = grandparent; + } + + node->right = grandparent; + grandparent->parent = node; + } else { /* right-left zig-zag */ + if((parent->left = node->right)) { + parent->left->parent = parent; + } + + node->right = parent; + parent->parent = node; + + if((grandparent->right = node->left)) { + grandparent->right->parent = grandparent; + } + + node->left = grandparent; + grandparent->parent = node; + } + + if((node->parent = greatgrandparent)) { + if(grandparent == greatgrandparent->left) { + greatgrandparent->left = node; + } else { + greatgrandparent->right = node; + } + } + } + } + + tree->root = node; +} + +/* (De)constructors */ + +splay_tree_t *splay_alloc_tree(splay_compare_t compare, splay_action_t delete) { + splay_tree_t *tree; + + tree = xzalloc(sizeof(splay_tree_t)); + tree->compare = compare; + tree->delete = delete; + + return tree; +} + +splay_node_t *splay_alloc_node(void) { + return xzalloc(sizeof(splay_node_t)); +} + +void splay_free_node(splay_tree_t *tree, splay_node_t *node) { + if(node->data && tree->delete) { + tree->delete(node->data); + } + + free(node); +} + +/* Searching */ + +void *splay_search(splay_tree_t *tree, const void *data) { + splay_node_t *node; + + node = splay_search_node(tree, data); + + return node ? node->data : NULL; +} + +void *splay_search_closest(splay_tree_t *tree, const void *data, int *result) { + splay_node_t *node; + + node = splay_search_closest_node(tree, data, result); + + return node ? node->data : NULL; +} + +void *splay_search_closest_smaller(splay_tree_t *tree, const void *data) { + splay_node_t *node; + + node = splay_search_closest_smaller_node(tree, data); + + return node ? node->data : NULL; +} + +void *splay_search_closest_greater(splay_tree_t *tree, const void *data) { + splay_node_t *node; + + node = splay_search_closest_greater_node(tree, data); + + return node ? node->data : NULL; +} + +splay_node_t *splay_search_node(splay_tree_t *tree, const void *data) { + splay_node_t *node; + int result; + + node = splay_search_closest_node(tree, data, &result); + + return result ? NULL : node; +} + +splay_node_t *splay_search_closest_node_nosplay(const splay_tree_t *tree, const void *data, int *result) { + splay_node_t *node; + int c; + + node = tree->root; + + if(!node) { + if(result) { + *result = 0; + } + + return NULL; + } + + for(;;) { + c = tree->compare(data, node->data); + + if(c < 0) { + if(node->left) { + node = node->left; + } else { + break; + } + } else if(c > 0) { + if(node->right) { + node = node->right; + } else { + break; + } + } else { + break; + } + } + + if(result) { + *result = c; + } + + return node; +} + +splay_node_t *splay_search_closest_node(splay_tree_t *tree, const void *data, int *result) { + return splay_top_down(tree, data, result); +} + +splay_node_t *splay_search_closest_smaller_node(splay_tree_t *tree, const void *data) { + splay_node_t *node; + int result; + + node = splay_search_closest_node(tree, data, &result); + + if(result < 0) { + node = node->prev; + } + + return node; +} + +splay_node_t *splay_search_closest_greater_node(splay_tree_t *tree, const void *data) { + splay_node_t *node; + int result; + + node = splay_search_closest_node(tree, data, &result); + + if(result > 0) { + node = node->next; + } + + return node; +} + +/* Insertion and deletion */ + +static void splay_insert_top(splay_tree_t *tree, splay_node_t *node) { + node->prev = node->next = node->left = node->right = node->parent = NULL; + tree->head = tree->tail = tree->root = node; + tree->count++; +} + +static void splay_insert_after(splay_tree_t *tree, splay_node_t *after, splay_node_t *node); + +static void splay_insert_before(splay_tree_t *tree, splay_node_t *before, splay_node_t *node) { + if(!before) { + if(tree->tail) { + splay_insert_after(tree, tree->tail, node); + } else { + splay_insert_top(tree, node); + } + + return; + } + + node->next = before; + + if((node->prev = before->prev)) { + before->prev->next = node; + } else { + tree->head = node; + } + + before->prev = node; + + splay_bottom_up(tree, before); + + node->right = before; + before->parent = node; + + if((node->left = before->left)) { + before->left->parent = node; + } + + before->left = NULL; + + node->parent = NULL; + tree->root = node; + tree->count++; +} + +static void splay_insert_after(splay_tree_t *tree, splay_node_t *after, splay_node_t *node) { + if(!after) { + if(tree->head) { + splay_insert_before(tree, tree->head, node); + } else { + splay_insert_top(tree, node); + } + + return; + } + + node->prev = after; + + if((node->next = after->next)) { + after->next->prev = node; + } else { + tree->tail = node; + } + + after->next = node; + + splay_bottom_up(tree, after); + + node->left = after; + after->parent = node; + + if((node->right = after->right)) { + after->right->parent = node; + } + + after->right = NULL; + + node->parent = NULL; + tree->root = node; + tree->count++; +} + +splay_node_t *splay_insert(splay_tree_t *tree, void *data) { + splay_node_t *closest, *new; + int result; + + if(!tree->root) { + new = splay_alloc_node(); + new->data = data; + splay_insert_top(tree, new); + } else { + closest = splay_search_closest_node(tree, data, &result); + + if(!result) { + return NULL; + } + + new = splay_alloc_node(); + new->data = data; + + if(result < 0) { + splay_insert_before(tree, closest, new); + } else { + splay_insert_after(tree, closest, new); + } + } + + return new; +} + +splay_node_t *splay_insert_node(splay_tree_t *tree, splay_node_t *node) { + splay_node_t *closest; + int result; + + node->left = node->right = node->parent = node->next = node->prev = NULL; + + if(!tree->root) { + splay_insert_top(tree, node); + } else { + closest = splay_search_closest_node(tree, node->data, &result); + + if(!result) { + return NULL; + } + + if(result < 0) { + splay_insert_before(tree, closest, node); + } else { + splay_insert_after(tree, closest, node); + } + } + + return node; +} + +splay_node_t *splay_unlink(splay_tree_t *tree, void *data) { + splay_node_t *node; + + node = splay_search_node(tree, data); + + if(node) { + splay_unlink_node(tree, node); + } + + return node; +} + +void splay_unlink_node(splay_tree_t *tree, splay_node_t *node) { + assert(tree->count); + assert(node->prev || tree->head == node); + assert(node->next || tree->tail == node); + + if(node->prev) { + node->prev->next = node->next; + } else { + tree->head = node->next; + } + + if(node->next) { + node->next->prev = node->prev; + } else { + tree->tail = node->prev; + } + + splay_bottom_up(tree, node); + + if(node->prev) { + node->left->parent = NULL; + tree->root = node->left; + + if((node->prev->right = node->right)) { + node->right->parent = node->prev; + } + } else if(node->next) { + tree->root = node->right; + node->right->parent = NULL; + } else { + tree->root = NULL; + } + + tree->count--; +} + +void splay_delete_node(splay_tree_t *tree, splay_node_t *node) { + splay_unlink_node(tree, node); + splay_free_node(tree, node); +} + +void splay_delete(splay_tree_t *tree, void *data) { + splay_node_t *node; + + node = splay_search_node(tree, data); + + if(node) { + splay_delete_node(tree, node); + } +} + +/* Fast tree cleanup */ + +void splay_delete_tree(splay_tree_t *tree) { + for(splay_node_t *node = tree->head, *next; node; node = next) { + next = node->next; + splay_free_node(tree, node); + tree->count--; + } + + assert(!tree->count); + free(tree); +} + +/* Tree walking */ + +void splay_foreach(const splay_tree_t *tree, splay_action_t action) { + for(splay_node_t *node = tree->head, *next; node; node = next) { + next = node->next; + action(node->data); + } +} + +void splay_foreach_node(const splay_tree_t *tree, splay_action_t action) { + for(splay_node_t *node = tree->head, *next; node; node = next) { + next = node->next; + action(node); + } +} diff --git a/src/splay_tree.h b/src/splay_tree.h new file mode 100644 index 0000000..de19ab7 --- /dev/null +++ b/src/splay_tree.h @@ -0,0 +1,103 @@ +#ifndef MESHLINK_SPLAY_TREE_H +#define MESHLINK_SPLAY_TREE_H + +/* + splay_tree.h -- header file for splay_tree.c + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +typedef struct splay_node_t { + + /* Linked list part */ + + struct splay_node_t *next; + struct splay_node_t *prev; + + /* Tree part */ + + struct splay_node_t *parent; + struct splay_node_t *left; + struct splay_node_t *right; + + /* Payload */ + + void *data; + +} splay_node_t; + +typedef int (*splay_compare_t)(const void *, const void *); +typedef void (*splay_action_t)(const void *); +typedef void (*splay_action_node_t)(const splay_node_t *); + +typedef struct splay_tree_t { + + /* Linked list part */ + + splay_node_t *head; + splay_node_t *tail; + + /* Tree part */ + + splay_node_t *root; + + splay_compare_t compare; + splay_action_t delete; + + unsigned int count; + +} splay_tree_t; + +/* (De)constructors */ + +splay_tree_t *splay_alloc_tree(splay_compare_t, splay_action_t) __attribute__((__malloc__)); +void splay_delete_tree(splay_tree_t *); + +splay_node_t *splay_alloc_node(void) __attribute__((__malloc__)); +void splay_free_node(splay_tree_t *tree, splay_node_t *); + +/* Insertion and deletion */ + +splay_node_t *splay_insert(splay_tree_t *, void *); +splay_node_t *splay_insert_node(splay_tree_t *, splay_node_t *); + +splay_node_t *splay_unlink(splay_tree_t *, void *); +void splay_unlink_node(splay_tree_t *tree, splay_node_t *); +void splay_delete(splay_tree_t *, void *); +void splay_delete_node(splay_tree_t *, splay_node_t *); + +/* Searching */ + +void *splay_search(splay_tree_t *, const void *); +void *splay_search_closest(splay_tree_t *, const void *, int *); +void *splay_search_closest_smaller(splay_tree_t *, const void *); +void *splay_search_closest_greater(splay_tree_t *, const void *); + +splay_node_t *splay_search_node(splay_tree_t *, const void *); +splay_node_t *splay_search_closest_node(splay_tree_t *, const void *, int *); +splay_node_t *splay_search_closest_node_nosplay(const splay_tree_t *, const void *, int *); +splay_node_t *splay_search_closest_smaller_node(splay_tree_t *, const void *); +splay_node_t *splay_search_closest_greater_node(splay_tree_t *, const void *); + +/* Tree walking */ + +void splay_foreach(const splay_tree_t *, splay_action_t); +void splay_foreach_node(const splay_tree_t *, splay_action_t); + +#define splay_each(type, item, tree) (type *item = (type *)1; item; item = NULL) for(splay_node_t *splay_node = (tree)->head, *splay_next; item = splay_node ? splay_node->data : NULL, splay_next = splay_node ? splay_node->next : NULL, splay_node; splay_node = splay_next) +#define inner_splay_each(type, item, tree) (type *item = (type *)1; item; item = NULL) for(splay_node_t *inner_splay_node = (tree)->head, *inner_splay_next; item = inner_splay_node ? inner_splay_node->data : NULL, inner_splay_next = inner_splay_node ? inner_splay_node->next : NULL, inner_splay_node; inner_splay_node = inner_splay_next) + +#endif diff --git a/src/sptps.c b/src/sptps.c new file mode 100644 index 0000000..fab2e9d --- /dev/null +++ b/src/sptps.c @@ -0,0 +1,759 @@ +/* + sptps.c -- Simple Peer-to-Peer Security + Copyright (C) 2014-2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "chacha-poly1305/chacha-poly1305.h" +#include "crypto.h" +#include "ecdh.h" +#include "ecdsa.h" +#include "logger.h" +#include "prf.h" +#include "sptps.h" + +/* + Nonce MUST be exchanged first (done) + Signatures MUST be done over both nonces, to guarantee the signature is fresh + Otherwise: if ECDHE key of one side is compromised, it can be reused! + + Add explicit tag to beginning of structure to distinguish the client and server when signing. (done) + + Sign all handshake messages up to ECDHE kex with long-term public keys. (done) + + HMACed KEX finished message to prevent downgrade attacks and prove you have the right key material (done by virtue of ECDSA over the whole ECDHE exchange?) + + Explicit close message needs to be added. + + Maybe do add some alert messages to give helpful error messages? Not more than TLS sends. + + Use counter mode instead of OFB. (done) + + Make sure ECC operations are fixed time (aka prevent side-channel attacks). +*/ + +void sptps_log_quiet(sptps_t *s, int s_errno, const char *format, va_list ap) { + (void)s; + (void)s_errno; + (void)format; + (void)ap; + + assert(format); +} + +void sptps_log_stderr(sptps_t *s, int s_errno, const char *format, va_list ap) { + (void)s; + (void)s_errno; + + assert(format); + + vfprintf(stderr, format, ap); + fputc('\n', stderr); +} + +void (*sptps_log)(sptps_t *s, int s_errno, const char *format, va_list ap) = sptps_log_quiet; + +// Log an error message. +static bool error(sptps_t *s, int s_errno, const char *format, ...) { + assert(s_errno); + assert(format); + + if(format) { + va_list ap; + va_start(ap, format); + sptps_log(s, s_errno, format, ap); + va_end(ap); + } + + errno = s_errno; + return false; +} + +static void warning(sptps_t *s, const char *format, ...) { + assert(format); + + va_list ap; + va_start(ap, format); + sptps_log(s, 0, format, ap); + va_end(ap); +} + +// Send a record (datagram version, accepts all record types, handles encryption and authentication). +static bool send_record_priv_datagram(sptps_t *s, uint8_t type, const void *data, uint16_t len) { + char buffer[len + 21UL]; + + // Create header with sequence number, length and record type + uint32_t seqno = s->outseqno++; + uint32_t netseqno = ntohl(seqno); + + memcpy(buffer, &netseqno, 4); + buffer[4] = type; + memcpy(buffer + 5, data, len); + + if(s->outstate) { + // If first handshake has finished, encrypt and HMAC + chacha_poly1305_encrypt(s->outcipher, seqno, buffer + 4, len + 1, buffer + 4, NULL); + return s->send_data(s->handle, type, buffer, len + 21UL); + } else { + // Otherwise send as plaintext + return s->send_data(s->handle, type, buffer, len + 5UL); + } +} +// Send a record (private version, accepts all record types, handles encryption and authentication). +static bool send_record_priv(sptps_t *s, uint8_t type, const void *data, uint16_t len) { + if(s->datagram) { + return send_record_priv_datagram(s, type, data, len); + } + + char buffer[len + 19UL]; + + // Create header with sequence number, length and record type + uint32_t seqno = s->outseqno++; + uint16_t netlen = htons(len); + + memcpy(buffer, &netlen, 2); + buffer[2] = type; + memcpy(buffer + 3, data, len); + + if(s->outstate) { + // If first handshake has finished, encrypt and HMAC + chacha_poly1305_encrypt(s->outcipher, seqno, buffer + 2, len + 1, buffer + 2, NULL); + return s->send_data(s->handle, type, buffer, len + 19UL); + } else { + // Otherwise send as plaintext + return s->send_data(s->handle, type, buffer, len + 3UL); + } +} + +// Send an application record. +bool sptps_send_record(sptps_t *s, uint8_t type, const void *data, uint16_t len) { + assert(!len || data); + + // Sanity checks: application cannot send data before handshake is finished, + // and only record types 0..127 are allowed. + if(!s->outstate) { + return error(s, EINVAL, "Handshake phase not finished yet"); + } + + if(type >= SPTPS_HANDSHAKE) { + return error(s, EINVAL, "Invalid application record type"); + } + + return send_record_priv(s, type, data, len); +} + +// Send a Key EXchange record, containing a random nonce and an ECDHE public key. +static bool send_kex(sptps_t *s) { + size_t keylen = ECDH_SIZE; + + // Make room for our KEX message, which we will keep around since send_sig() needs it. + if(s->mykex) { + return false; + } + + s->mykex = realloc(s->mykex, 1 + 32 + keylen); + + if(!s->mykex) { + return error(s, errno, strerror(errno)); + } + + // Set version byte to zero. + s->mykex[0] = SPTPS_VERSION; + + // Create a random nonce. + randomize(s->mykex + 1, 32); + + // Create a new ECDH public key. + if(!(s->ecdh = ecdh_generate_public(s->mykex + 1 + 32))) { + return error(s, EINVAL, "Failed to generate ECDH public key"); + } + + return send_record_priv(s, SPTPS_HANDSHAKE, s->mykex, 1 + 32 + keylen); +} + +// Send a SIGnature record, containing an ECDSA signature over both KEX records. +static bool send_sig(sptps_t *s) { + size_t keylen = ECDH_SIZE; + size_t siglen = ecdsa_size(s->mykey); + + // Concatenate both KEX messages, plus tag indicating if it is from the connection originator, plus label + char msg[(1 + 32 + keylen) * 2 + 1 + s->labellen]; + char sig[siglen]; + + msg[0] = s->initiator; + memcpy(msg + 1, s->mykex, 1 + 32 + keylen); + memcpy(msg + 1 + 33 + keylen, s->hiskex, 1 + 32 + keylen); + memcpy(msg + 1 + 2 * (33 + keylen), s->label, s->labellen); + + // Sign the result. + if(!ecdsa_sign(s->mykey, msg, sizeof(msg), sig)) { + return error(s, EINVAL, "Failed to sign SIG record"); + } + + // Send the SIG exchange record. + return send_record_priv(s, SPTPS_HANDSHAKE, sig, sizeof(sig)); +} + +// Generate key material from the shared secret created from the ECDHE key exchange. +static bool generate_key_material(sptps_t *s, const char *shared, size_t len) { + assert(shared); + assert(len); + + // Initialise cipher and digest structures if necessary + if(!s->outstate) { + s->incipher = chacha_poly1305_init(); + s->outcipher = chacha_poly1305_init(); + + if(!s->incipher || !s->outcipher) { + return error(s, EINVAL, "Failed to open cipher"); + } + } + + // Allocate memory for key material + size_t keylen = 2 * CHACHA_POLY1305_KEYLEN; + + s->key = realloc(s->key, keylen); + + if(!s->key) { + return error(s, errno, strerror(errno)); + } + + // Create the HMAC seed, which is "key expansion" + session label + server nonce + client nonce + char seed[s->labellen + 64 + 13]; + strcpy(seed, "key expansion"); + + if(s->initiator) { + memcpy(seed + 13, s->mykex + 1, 32); + memcpy(seed + 45, s->hiskex + 1, 32); + } else { + memcpy(seed + 13, s->hiskex + 1, 32); + memcpy(seed + 45, s->mykex + 1, 32); + } + + memcpy(seed + 77, s->label, s->labellen); + + // Use PRF to generate the key material + if(!prf(shared, len, seed, s->labellen + 64 + 13, s->key, keylen)) { + return error(s, EINVAL, "Failed to generate key material"); + } + + return true; +} + +// Send an ACKnowledgement record. +static bool send_ack(sptps_t *s) { + return send_record_priv(s, SPTPS_HANDSHAKE, "", 0); +} + +// Receive an ACKnowledgement record. +static bool receive_ack(sptps_t *s, const char *data, uint16_t len) { + (void)data; + + if(len) { + return error(s, EIO, "Invalid ACK record length"); + } + + if(s->initiator) { + if(!chacha_poly1305_set_key(s->incipher, s->key)) { + return error(s, EINVAL, "Failed to set counter"); + } + } else { + if(!chacha_poly1305_set_key(s->incipher, s->key + CHACHA_POLY1305_KEYLEN)) { + return error(s, EINVAL, "Failed to set counter"); + } + } + + free(s->key); + s->key = NULL; + s->instate = true; + + return true; +} + +// Receive a Key EXchange record, respond by sending a SIG record. +static bool receive_kex(sptps_t *s, const char *data, uint16_t len) { + // Verify length of the HELLO record + if(len != 1 + 32 + ECDH_SIZE) { + return error(s, EIO, "Invalid KEX record length"); + } + + // Ignore version number for now. + + // Make a copy of the KEX message, send_sig() and receive_sig() need it + if(s->hiskex) { + return error(s, EINVAL, "Received a second KEX message before first has been processed"); + } + + s->hiskex = realloc(s->hiskex, len); + + if(!s->hiskex) { + return error(s, errno, strerror(errno)); + } + + memcpy(s->hiskex, data, len); + + return send_sig(s); +} + +// Receive a SIGnature record, verify it, if it passed, compute the shared secret and calculate the session keys. +static bool receive_sig(sptps_t *s, const char *data, uint16_t len) { + size_t keylen = ECDH_SIZE; + size_t siglen = ecdsa_size(s->hiskey); + + // Verify length of KEX record. + if(len != siglen) { + return error(s, EIO, "Invalid KEX record length"); + } + + // Concatenate both KEX messages, plus tag indicating if it is from the connection originator + char msg[(1 + 32 + keylen) * 2 + 1 + s->labellen]; + + msg[0] = !s->initiator; + memcpy(msg + 1, s->hiskex, 1 + 32 + keylen); + memcpy(msg + 1 + 33 + keylen, s->mykex, 1 + 32 + keylen); + memcpy(msg + 1 + 2 * (33 + keylen), s->label, s->labellen); + + // Verify signature. + if(!ecdsa_verify(s->hiskey, msg, sizeof(msg), data)) { + return error(s, EIO, "Failed to verify SIG record"); + } + + // Compute shared secret. + char shared[ECDH_SHARED_SIZE]; + + if(!ecdh_compute_shared(s->ecdh, s->hiskex + 1 + 32, shared)) { + return error(s, EINVAL, "Failed to compute ECDH shared secret"); + } + + s->ecdh = NULL; + + // Generate key material from shared secret. + if(!generate_key_material(s, shared, sizeof(shared))) { + return false; + } + + free(s->mykex); + free(s->hiskex); + + s->mykex = NULL; + s->hiskex = NULL; + + // Send cipher change record + if(s->outstate && !send_ack(s)) { + return false; + } + + // TODO: only set new keys after ACK has been set/received + if(s->initiator) { + if(!chacha_poly1305_set_key(s->outcipher, s->key + CHACHA_POLY1305_KEYLEN)) { + return error(s, EINVAL, "Failed to set key"); + } + } else { + if(!chacha_poly1305_set_key(s->outcipher, s->key)) { + return error(s, EINVAL, "Failed to set key"); + } + } + + return true; +} + +// Force another Key EXchange (for testing purposes). +bool sptps_force_kex(sptps_t *s) { + if(!s->outstate) { + return error(s, EINVAL, "Cannot force KEX in current state"); + } + + if(s->state != SPTPS_SECONDARY_KEX) { + // We are already in the middle of a key exchange + return true; + } + + s->state = SPTPS_KEX; + return send_kex(s); +} + +// Receive a handshake record. +static bool receive_handshake(sptps_t *s, const char *data, uint16_t len) { + // Only a few states to deal with handshaking. + switch(s->state) { + case SPTPS_SECONDARY_KEX: + + // We receive a secondary KEX request, first respond by sending our own. + if(!send_kex(s)) { + return false; + } + + // fallthrough + case SPTPS_KEX: + + // We have sent our KEX request, we expect our peer to sent one as well. + if(!receive_kex(s, data, len)) { + return false; + } + + s->state = SPTPS_SIG; + return true; + + case SPTPS_SIG: + + // If we already sent our secondary public ECDH key, we expect the peer to send his. + if(!receive_sig(s, data, len)) { + return false; + } + + if(s->outstate) { + s->state = SPTPS_ACK; + } else { + s->outstate = true; + + if(!receive_ack(s, NULL, 0)) { + return false; + } + + s->receive_record(s->handle, SPTPS_HANDSHAKE, NULL, 0); + s->state = SPTPS_SECONDARY_KEX; + } + + return true; + + case SPTPS_ACK: + + // We expect a handshake message to indicate transition to the new keys. + if(!receive_ack(s, data, len)) { + return false; + } + + s->receive_record(s->handle, SPTPS_HANDSHAKE, NULL, 0); + s->state = SPTPS_SECONDARY_KEX; + return true; + + // TODO: split ACK into a VERify and ACK? + default: + return error(s, EIO, "Invalid session state %d", s->state); + } +} + +// Check datagram for valid HMAC +bool sptps_verify_datagram(sptps_t *s, const void *data, size_t len) { + if(!s->instate) { + return error(s, EIO, "SPTPS state not ready to verify this datagram"); + } + + if(len < 21) { + return error(s, EIO, "Received short packet in sptps_verify_datagram"); + } + + uint32_t seqno; + memcpy(&seqno, data, 4); + seqno = ntohl(seqno); + // TODO: check whether seqno makes sense, to avoid CPU intensive decrypt + + return chacha_poly1305_verify(s->incipher, seqno, (const char *)data + 4, len - 4); +} + +// Receive incoming data, datagram version. +static bool sptps_receive_data_datagram(sptps_t *s, const void *vdata, size_t len) { + const char *data = vdata; + + if(len < (s->instate ? 21 : 5)) { + return error(s, EIO, "Received short packet in sptps_receive_data_datagram"); + } + + uint32_t seqno; + memcpy(&seqno, data, 4); + seqno = ntohl(seqno); + + if(!s->instate) { + if(seqno != s->inseqno) { + return error(s, EIO, "Invalid packet seqno: %d != %d", seqno, s->inseqno); + } + + s->inseqno = seqno + 1; + + uint8_t type = data[4]; + + if(type != SPTPS_HANDSHAKE) { + return error(s, EIO, "Application record received before handshake finished"); + } + + return receive_handshake(s, data + 5, len - 5); + } + + // Decrypt + + if(len > s->decrypted_buffer_len) { + s->decrypted_buffer_len *= 2; + char *new_buffer = realloc(s->decrypted_buffer, s->decrypted_buffer_len); + + if(!new_buffer) { + return error(s, errno, strerror(errno)); + } + + s->decrypted_buffer = new_buffer; + } + + size_t outlen; + + if(!chacha_poly1305_decrypt(s->incipher, seqno, data + 4, len - 4, s->decrypted_buffer, &outlen)) { + return error(s, EIO, "Failed to decrypt and verify packet"); + } + + // Replay protection using a sliding window of configurable size. + // s->inseqno is expected sequence number + // seqno is received sequence number + // s->late[] is a circular buffer, a 1 bit means a packet has not been received yet + // The circular buffer contains bits for sequence numbers from s->inseqno - s->replaywin * 8 to (but excluding) s->inseqno. + if(s->replaywin) { + if(seqno != s->inseqno) { + if(seqno >= s->inseqno + s->replaywin * 8) { + // TODO: Prevent packets that jump far ahead of the queue from causing many others to be dropped. + warning(s, "Lost %d packets\n", seqno - s->inseqno); + // Mark all packets in the replay window as being late. + memset(s->late, 255, s->replaywin); + } else if(seqno < s->inseqno) { + // If the sequence number is farther in the past than the bitmap goes, or if the packet was already received, drop it. + if((s->inseqno >= s->replaywin * 8 && seqno < s->inseqno - s->replaywin * 8) || !(s->late[(seqno / 8) % s->replaywin] & (1 << seqno % 8))) { + return error(s, EIO, "Received late or replayed packet, seqno %d, last received %d\n", seqno, s->inseqno); + } + } else { + // We missed some packets. Mark them in the bitmap as being late. + for(uint32_t i = s->inseqno; i < seqno; i++) { + s->late[(i / 8) % s->replaywin] |= 1 << i % 8; + } + } + } + + // Mark the current packet as not being late. + s->late[(seqno / 8) % s->replaywin] &= ~(1 << seqno % 8); + } + + if(seqno >= s->inseqno) { + s->inseqno = seqno + 1; + } + + if(!s->inseqno) { + s->received = 0; + } else { + s->received++; + } + + // Append a NULL byte for safety. + s->decrypted_buffer[len - 20] = 0; + + uint8_t type = s->decrypted_buffer[0]; + + if(type < SPTPS_HANDSHAKE) { + if(!s->instate) { + return error(s, EIO, "Application record received before handshake finished"); + } + + if(!s->receive_record(s->handle, type, s->decrypted_buffer + 1, len - 21)) { + abort(); + } + } else if(type == SPTPS_HANDSHAKE) { + if(!receive_handshake(s, s->decrypted_buffer + 1, len - 21)) { + abort(); + } + } else { + return error(s, EIO, "Invalid record type %d", type); + } + + return true; +} + +// Receive incoming data. Check if it contains a complete record, if so, handle it. +bool sptps_receive_data(sptps_t *s, const void *data, size_t len) { + if(!s->state) { + return error(s, EIO, "Invalid session state zero"); + } + + if(s->datagram) { + return sptps_receive_data_datagram(s, data, len); + } + + const char *ptr = data; + + while(len) { + // First read the 2 length bytes. + if(s->buflen < 2) { + size_t toread = 2 - s->buflen; + + if(toread > len) { + toread = len; + } + + memcpy(s->inbuf + s->buflen, ptr, toread); + + s->buflen += toread; + len -= toread; + ptr += toread; + + // Exit early if we don't have the full length. + if(s->buflen < 2) { + return true; + } + + // Get the length bytes + + memcpy(&s->reclen, s->inbuf, 2); + s->reclen = ntohs(s->reclen); + + // If we have the length bytes, ensure our buffer can hold the whole request. + s->inbuf = realloc(s->inbuf, s->reclen + 19UL); + + if(!s->inbuf) { + return error(s, errno, strerror(errno)); + } + + // Exit early if we have no more data to process. + if(!len) { + return true; + } + } + + // Read up to the end of the record. + size_t toread = s->reclen + (s->instate ? 19UL : 3UL) - s->buflen; + + if(toread > len) { + toread = len; + } + + memcpy(s->inbuf + s->buflen, ptr, toread); + s->buflen += toread; + len -= toread; + ptr += toread; + + // If we don't have a whole record, exit. + if(s->buflen < s->reclen + (s->instate ? 19UL : 3UL)) { + return true; + } + + // Update sequence number. + + uint32_t seqno = s->inseqno++; + + // Check HMAC and decrypt. + if(s->instate) { + if(!chacha_poly1305_decrypt(s->incipher, seqno, s->inbuf + 2UL, s->reclen + 17UL, s->inbuf + 2UL, NULL)) { + return error(s, EINVAL, "Failed to decrypt and verify record"); + } + } + + // Append a NULL byte for safety. + s->inbuf[s->reclen + 3UL] = 0; + + uint8_t type = s->inbuf[2]; + + if(type < SPTPS_HANDSHAKE) { + if(!s->instate) { + return error(s, EIO, "Application record received before handshake finished"); + } + + if(!s->receive_record(s->handle, type, s->inbuf + 3, s->reclen)) { + return false; + } + } else if(type == SPTPS_HANDSHAKE) { + if(!receive_handshake(s, s->inbuf + 3, s->reclen)) { + return false; + } + } else { + return error(s, EIO, "Invalid record type %d", type); + } + + s->buflen = 0; + } + + return true; +} + +// Start a SPTPS session. +bool sptps_start(sptps_t *s, void *handle, bool initiator, bool datagram, ecdsa_t *mykey, ecdsa_t *hiskey, const char *label, size_t labellen, send_data_t send_data, receive_record_t receive_record) { + if(!s || !mykey || !hiskey || !label || !labellen || !send_data || !receive_record) { + return error(s, EINVAL, "Invalid argument to sptps_start()"); + } + + // Initialise struct sptps + memset(s, 0, sizeof(*s)); + + s->handle = handle; + s->initiator = initiator; + s->datagram = datagram; + s->mykey = mykey; + s->hiskey = hiskey; + s->replaywin = 32; + s->decrypted_buffer_len = 1024; + s->decrypted_buffer = malloc(s->decrypted_buffer_len); + + if(!s->decrypted_buffer) { + return error(s, errno, strerror(errno)); + } + + if(s->replaywin) { + s->late = malloc(s->replaywin); + + if(!s->late) { + return error(s, errno, strerror(errno)); + } + + memset(s->late, 0, s->replaywin); + } + + s->label = malloc(labellen); + + if(!s->label) { + return error(s, errno, strerror(errno)); + } + + if(!datagram) { + s->inbuf = malloc(7); + + if(!s->inbuf) { + return error(s, errno, strerror(errno)); + } + + s->buflen = 0; + } + + memcpy(s->label, label, labellen); + s->labellen = labellen; + + s->send_data = send_data; + s->receive_record = receive_record; + + // Do first KEX immediately + s->state = SPTPS_KEX; + return send_kex(s); +} + +// Stop a SPTPS session. +bool sptps_stop(sptps_t *s) { + // Clean up any resources. + chacha_poly1305_exit(s->incipher); + chacha_poly1305_exit(s->outcipher); + ecdh_free(s->ecdh); + free(s->inbuf); + free(s->mykex); + free(s->hiskex); + free(s->key); + free(s->label); + free(s->late); + memset(s->decrypted_buffer, 0, s->decrypted_buffer_len); + free(s->decrypted_buffer); + memset(s, 0, sizeof(*s)); + return true; +} diff --git a/src/sptps.h b/src/sptps.h new file mode 100644 index 0000000..ba6bcd5 --- /dev/null +++ b/src/sptps.h @@ -0,0 +1,100 @@ +#ifndef MESHLINK_SPTPS_H +#define MESHLINK_SPTPS_H + +/* + sptps.h -- Simple Peer-to-Peer Security + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "chacha-poly1305/chacha-poly1305.h" +#include "ecdh.h" +#include "ecdsa.h" + +#define SPTPS_VERSION 0 + +// Record types +#define SPTPS_HANDSHAKE 128 // Key exchange and authentication +#define SPTPS_ALERT 129 // Warning or error messages +#define SPTPS_CLOSE 130 // Application closed the connection + +// Key exchange states +#define SPTPS_KEX 1 // Waiting for the first Key EXchange record +#define SPTPS_SECONDARY_KEX 2 // Ready to receive a secondary Key EXchange record +#define SPTPS_SIG 3 // Waiting for a SIGnature record +#define SPTPS_ACK 4 // Waiting for an ACKnowledgement record + +typedef bool (*send_data_t)(void *handle, uint8_t type, const void *data, size_t len); +typedef bool (*receive_record_t)(void *handle, uint8_t type, const void *data, uint16_t len); + +typedef struct sptps { + // State + bool initiator; + bool datagram; + bool instate; + bool outstate; + + int state; + + // Main member variables + char *inbuf; + size_t buflen; + + chacha_poly1305_ctx_t *incipher; + uint32_t replaywin; + uint32_t inseqno; + uint32_t received; + uint16_t reclen; + + chacha_poly1305_ctx_t *outcipher; + uint32_t outseqno; + + char *late; + + char *decrypted_buffer; + size_t decrypted_buffer_len; + + // Callbacks + void *handle; + send_data_t send_data; + receive_record_t receive_record; + + // Variables used for the authentication phase + ecdsa_t *mykey; + ecdsa_t *hiskey; + ecdh_t *ecdh; + + char *mykex; + char *hiskex; + char *key; + char *label; + size_t labellen; + +} sptps_t; + +void sptps_log_quiet(sptps_t *s, int s_errno, const char *format, va_list ap); +void sptps_log_stderr(sptps_t *s, int s_errno, const char *format, va_list ap); +extern void (*sptps_log)(sptps_t *s, int s_errno, const char *format, va_list ap); +bool sptps_start(sptps_t *s, void *handle, bool initiator, bool datagram, ecdsa_t *mykey, ecdsa_t *hiskey, const char *label, size_t labellen, send_data_t send_data, receive_record_t receive_record) __attribute__((__warn_unused_result__)); +bool sptps_stop(sptps_t *s); +bool sptps_send_record(sptps_t *s, uint8_t type, const void *data, uint16_t len); +bool sptps_receive_data(sptps_t *s, const void *data, size_t len) __attribute__((__warn_unused_result__)); +bool sptps_force_kex(sptps_t *s) __attribute__((__warn_unused_result__)); +bool sptps_verify_datagram(sptps_t *s, const void *data, size_t len) __attribute__((__warn_unused_result__)); + +#endif diff --git a/src/submesh.c b/src/submesh.c new file mode 100644 index 0000000..4af2be0 --- /dev/null +++ b/src/submesh.c @@ -0,0 +1,138 @@ +/* + submesh.c -- submesh management + Copyright (C) 2019 Guus Sliepen , + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "hash.h" +#include "logger.h" +#include "meshlink_internal.h" +#include "net.h" +#include "netutl.h" +#include "submesh.h" +#include "splay_tree.h" +#include "utils.h" +#include "xalloc.h" +#include "protocol.h" + +static submesh_t *new_submesh(void) { + return xzalloc(sizeof(submesh_t)); +} + +static void free_submesh(submesh_t *s) { + free(s->name); + free(s); +} + +void init_submeshes(meshlink_handle_t *mesh) { + assert(!mesh->submeshes); + mesh->submeshes = list_alloc((list_action_t)free_submesh); +} + +void exit_submeshes(meshlink_handle_t *mesh) { + if(mesh->submeshes) { + list_delete_list(mesh->submeshes); + } + + mesh->submeshes = NULL; +} + +static submesh_t *submesh_add(meshlink_handle_t *mesh, const char *submesh) { + assert(submesh); + + submesh_t *s = new_submesh(); + s->name = xstrdup(submesh); + list_insert_tail(mesh->submeshes, (void *)s); + return s; +} + +submesh_t *create_submesh(meshlink_handle_t *mesh, const char *submesh) { + assert(submesh); + + if(0 == strcmp(submesh, CORE_MESH)) { + logger(NULL, MESHLINK_ERROR, "Cannot create submesh handle for core mesh!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(!check_id(submesh)) { + logger(NULL, MESHLINK_ERROR, "Invalid SubMesh Id!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(lookup_submesh(mesh, submesh)) { + logger(NULL, MESHLINK_ERROR, "SubMesh Already exists!\n"); + meshlink_errno = MESHLINK_EEXIST; + return NULL; + } + + return submesh_add(mesh, submesh); +} + +submesh_t *lookup_or_create_submesh(meshlink_handle_t *mesh, const char *submesh) { + assert(submesh); + + if(0 == strcmp(submesh, CORE_MESH)) { + logger(NULL, MESHLINK_ERROR, "Cannot create submesh handle for core mesh!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(!check_id(submesh)) { + logger(NULL, MESHLINK_ERROR, "Invalid SubMesh Id!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + submesh_t *s = lookup_submesh(mesh, submesh); + + if(s) { + meshlink_errno = MESHLINK_OK; + return s; + } + + return submesh_add(mesh, submesh); +} + +submesh_t *lookup_submesh(struct meshlink_handle *mesh, const char *submesh_name) { + assert(submesh_name); + + submesh_t *submesh = NULL; + + if(!mesh->submeshes) { + return NULL; + } + + for list_each(submesh_t, s, mesh->submeshes) { + if(!strcmp(submesh_name, s->name)) { + submesh = s; + break; + } + } + + return submesh; +} + +bool submesh_allows_node(const submesh_t *submesh, const node_t *node) { + if(!node->submesh || !submesh || submesh == node->submesh) { + return true; + } else { + return false; + } +} diff --git a/src/submesh.h b/src/submesh.h new file mode 100644 index 0000000..9366b05 --- /dev/null +++ b/src/submesh.h @@ -0,0 +1,41 @@ +#ifndef MESHLINK_SUBMESH_H +#define MESHLINK_SUBMESH_H + +/* + submesh.h -- header for submesh.c + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "meshlink_internal.h" + +#define CORE_MESH "." + +typedef struct submesh_t { + char *name; /* name of this Sub-Mesh */ + void *priv; + + struct meshlink_handle *mesh; /* the mesh this submesh belongs to */ +} submesh_t; + +void init_submeshes(struct meshlink_handle *mesh); +void exit_submeshes(struct meshlink_handle *mesh); +submesh_t *create_submesh(struct meshlink_handle *mesh, const char *) __attribute__((__warn_unused_result__)); +submesh_t *lookup_submesh(struct meshlink_handle *mesh, const char *) __attribute__((__warn_unused_result__)); +submesh_t *lookup_or_create_submesh(struct meshlink_handle *mesh, const char *) __attribute__((__warn_unused_result__)); +bool submesh_allows_node(const submesh_t *submesh, const struct node_t *node) __attribute__((__warn_unused_result__)); + +#endif diff --git a/src/system.h b/src/system.h new file mode 100644 index 0000000..a31ba7a --- /dev/null +++ b/src/system.h @@ -0,0 +1,35 @@ +#ifndef MESHLINK_SYSTEM_H +#define MESHLINK_SYSTEM_H + +/* + system.h -- system headers + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "../config.h" + +#include "have.h" + +#ifndef HAVE_STRSIGNAL +# define strsignal(p) "" +#endif + +/* Other functions */ + +#include "dropin.h" + +#endif diff --git a/src/utcp-test.c b/src/utcp-test.c new file mode 100644 index 0000000..a7c0534 --- /dev/null +++ b/src/utcp-test.c @@ -0,0 +1,410 @@ +/* + utcp-test.c -- Test application for UTCP + Copyright (C) 2014-2020 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" +#include +#include + +#include "utcp.h" + +#define DIR_READ 1 +#define DIR_WRITE 2 + +static struct utcp_connection *c; +static int dir = DIR_READ | DIR_WRITE; +static long inpktno; +static long outpktno; +static long dropfrom; +static long dropto; +static double reorder; +static long reorder_dist = 10; +static double dropin; +static double dropout; +static long total_out; +static long total_in; +static FILE *reference; +static long mtu; +static long bufsize; + +static char *reorder_data; +static size_t reorder_len; +static int reorder_countdown; + +#if UTCP_DEBUG +static void debug(const char *format, ...) { + struct timespec tv; + char buf[1024]; + int len; + + clock_gettime(CLOCK_REALTIME, &tv); + len = snprintf(buf, sizeof(buf), "%ld.%06lu ", (long)tv.tv_sec, tv.tv_nsec / 1000); + va_list ap; + va_start(ap, format); + len += vsnprintf(buf + len, sizeof(buf) - len, format, ap); + va_end(ap); + + if(len > 0 && (size_t)len < sizeof(buf)) { + fwrite(buf, len, 1, stderr); + } +} +#else +#define debug(...) do {} while(0) +#endif + +static ssize_t do_recv(struct utcp_connection *rc, const void *data, size_t len) { + (void)rc; + + if(!data || !len) { + if(errno) { + debug("Error: %s\n", strerror(errno)); + dir = 0; + } else { + dir &= ~DIR_WRITE; + debug("Connection closed by peer\n"); + } + + return -1; + } + + if(reference) { + char buf[len]; + + if(fread(buf, len, 1, reference) != 1) { + debug("Error reading reference\n"); + abort(); + } + + if(memcmp(buf, data, len)) { + debug("Received data differs from reference\n"); + abort(); + } + } + + return write(1, data, len); +} + +static void do_accept(struct utcp_connection *nc, uint16_t port) { + (void)port; + utcp_accept(nc, do_recv, NULL); + c = nc; + + if(bufsize) { + utcp_set_sndbuf(c, NULL, bufsize); + utcp_set_rcvbuf(c, NULL, bufsize); + } + + utcp_set_accept_cb(c->utcp, NULL, NULL); +} + +static ssize_t do_send(struct utcp *utcp, const void *data, size_t len) { + int s = *(int *)utcp->priv; + outpktno++; + + if(outpktno >= dropfrom && outpktno < dropto) { + if(drand48() < dropout) { + debug("Dropped outgoing packet\n"); + return len; + } + + if(!reorder_data && drand48() < reorder) { + reorder_data = malloc(len); + + if(!reorder_data) { + debug("Out of memory\n"); + return len; + } + + reorder_len = len; + memcpy(reorder_data, data, len); + reorder_countdown = 1 + drand48() * reorder_dist; + return len; + } + } + + if(reorder_data) { + if(--reorder_countdown < 0) { + total_out += reorder_len; + send(s, reorder_data, reorder_len, MSG_DONTWAIT); + free(reorder_data); + reorder_data = NULL; + } + } + + total_out += len; + ssize_t result = send(s, data, len, MSG_DONTWAIT); + + if(result <= 0) { + debug("Error sending UDP packet: %s\n", strerror(errno)); + } + + return result; +} + +static void set_mtu(struct utcp *u, int s) { +#ifdef IP_MTU + + if(!mtu) { + socklen_t optlen = sizeof(mtu); + getsockopt(s, IPPROTO_IP, IP_MTU, &mtu, &optlen); + } + +#else + (void)s; +#endif + + if(!mtu || mtu == 65535) { + mtu = 1500; + } + + debug("Using MTU %lu\n", mtu); + + utcp_set_mtu(u, mtu ? mtu - 28 : 1300); +} + +int main(int argc, char *argv[]) { + srand(time(NULL)); + srand48(time(NULL)); + + if(argc < 2 || argc > 3) { + return 1; + } + + bool server = argc == 2; + bool connected = false; + uint32_t flags = UTCP_TCP; + size_t read_size = 102400; + + if(getenv("DROPIN")) { + dropin = atof(getenv("DROPIN")); + } + + if(getenv("DROPOUT")) { + dropout = atof(getenv("DROPOUT")); + } + + if(getenv("DROPFROM")) { + dropfrom = atoi(getenv("DROPFROM")); + } + + if(getenv("DROPTO")) { + dropto = atoi(getenv("DROPTO")); + } + + if(getenv("REORDER")) { + reorder = atof(getenv("REORDER")); + } + + if(getenv("REORDER_DIST")) { + reorder_dist = atoi(getenv("REORDER_DIST")); + } + + if(getenv("FLAGS")) { + flags = atoi(getenv("FLAGS")); + } + + if(getenv("READ_SIZE")) { + read_size = atoi(getenv("READ_SIZE")); + } + + if(getenv("MTU")) { + mtu = atoi(getenv("MTU")); + } + + if(getenv("BUFSIZE")) { + bufsize = atoi(getenv("BUFSIZE")); + } + + char *reference_filename = getenv("REFERENCE"); + + if(reference_filename) { + reference = fopen(reference_filename, "r"); + } + + if(dropto < dropfrom) { + dropto = 1 << 30; + } + + struct addrinfo *ai; + + struct addrinfo hint = { + .ai_flags = server ? AI_PASSIVE : 0, + .ai_socktype = SOCK_DGRAM, + }; + + getaddrinfo(server ? NULL : argv[1], server ? argv[1] : argv[2], &hint, &ai); + + if(!ai) { + debug("Could not lookup address: %s\n", strerror(errno)); + return 1; + } + + int s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + + if(s == -1) { + debug("Could not create socket: %s\n", strerror(errno)); + return 1; + } + + static const int one = 1; + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one); + + if(server) { + if(bind(s, ai->ai_addr, ai->ai_addrlen)) { + debug("Could not bind: %s\n", strerror(errno)); + return 1; + } + } else { +#ifdef SO_NOSIGPIPE + int nosigpipe = 1; + setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); +#endif + + if(connect(s, ai->ai_addr, ai->ai_addrlen)) { + debug("Could not connect: %s\n", strerror(errno)); + return 1; + } + + connected = true; + } + + freeaddrinfo(ai); + + struct utcp *u = utcp_init(server ? do_accept : NULL, NULL, do_send, &s); + + if(!u) { + debug("Could not initialize UTCP\n"); + return 1; + } + + utcp_set_user_timeout(u, 10); + + if(!server) { + set_mtu(u, s); + c = utcp_connect_ex(u, 1, do_recv, NULL, flags); + + if(bufsize) { + utcp_set_sndbuf(c, NULL, bufsize); + utcp_set_rcvbuf(c, NULL, bufsize); + } + } + + struct pollfd fds[2] = { + {.fd = 0, .events = POLLIN | POLLERR | POLLHUP}, + {.fd = s, .events = POLLIN | POLLERR | POLLHUP}, + }; + + char buf[102400]; + + struct timespec timeout = utcp_timeout(u); + + while(!connected || utcp_is_active(u)) { + size_t max = c ? utcp_get_sndbuf_free(c) : 0; + + if(max > sizeof(buf)) { + max = sizeof(buf); + } + + if(max > read_size) { + max = read_size; + } + + int timeout_ms = timeout.tv_sec * 1000 + timeout.tv_nsec / 1000000 + 1; + + debug("polling, dir = %d, timeout = %d\n", dir, timeout_ms); + + if((dir & DIR_READ) && max) { + poll(fds, 2, timeout_ms); + } else { + poll(fds + 1, 1, timeout_ms); + } + + if(fds[0].revents) { + fds[0].revents = 0; + ssize_t len = read(0, buf, max); + debug("stdin %zd\n", len); + + if(len <= 0) { + fds[0].fd = -1; + dir &= ~DIR_READ; + + if(c) { + utcp_shutdown(c, SHUT_WR); + } + + if(len == -1) { + break; + } else { + continue; + } + } + + if(c) { + ssize_t sent = utcp_send(c, buf, len); + + if(sent != len) { + debug("Short send: %zd != %zd\n", sent, len); + } + } + } + + if(fds[1].revents) { + fds[1].revents = 0; + struct sockaddr_storage ss; + socklen_t sl = sizeof(ss); + int len = recvfrom(s, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr *)&ss, &sl); + debug("netin %zu\n", len); + + if(len <= 0) { + debug("Error receiving UDP packet: %s\n", strerror(errno)); + break; + } + + if(!connected) { + if(!connect(s, (struct sockaddr *)&ss, sl)) { + connected = true; + set_mtu(u, s); + } + } + + inpktno++; + + if(inpktno >= dropto || inpktno < dropfrom || drand48() >= dropin) { + total_in += len; + + if(utcp_recv(u, buf, len) == -1) { + debug("Error receiving UTCP packet: %s\n", strerror(errno)); + } + } else { + debug("Dropped incoming packet\n"); + } + } + + timeout = utcp_timeout(u); + }; + + utcp_close(c); + + utcp_exit(u); + + free(reorder_data); + + debug("Total bytes in: %ld, out: %ld\n", total_in, total_out); + + return 0; +} diff --git a/src/utcp.c b/src/utcp.c new file mode 100644 index 0000000..ca91bee --- /dev/null +++ b/src/utcp.c @@ -0,0 +1,2574 @@ +/* + utcp.c -- Userspace TCP + Copyright (C) 2014-2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" +#include + +#include "utcp_priv.h" + +#ifndef EBADMSG +#define EBADMSG 104 +#endif + +#ifndef SHUT_RDWR +#define SHUT_RDWR 2 +#endif + +#ifdef poll +#undef poll +#endif + +#ifndef UTCP_CLOCK +#if defined(CLOCK_MONOTONIC_RAW) && defined(__x86_64__) +#define UTCP_CLOCK CLOCK_MONOTONIC_RAW +#else +#define UTCP_CLOCK CLOCK_MONOTONIC +#endif +#endif + +static void timespec_sub(const struct timespec *a, const struct timespec *b, struct timespec *r) { + r->tv_sec = a->tv_sec - b->tv_sec; + r->tv_nsec = a->tv_nsec - b->tv_nsec; + + if(r->tv_nsec < 0) { + r->tv_sec--, r->tv_nsec += NSEC_PER_SEC; + } +} + +static int32_t timespec_diff_usec(const struct timespec *a, const struct timespec *b) { + return (a->tv_sec - b->tv_sec) * 1000000 + (a->tv_nsec - b->tv_nsec) / 1000; +} + +static bool timespec_lt(const struct timespec *a, const struct timespec *b) { + if(a->tv_sec == b->tv_sec) { + return a->tv_nsec < b->tv_nsec; + } else { + return a->tv_sec < b->tv_sec; + } +} + +static void timespec_clear(struct timespec *a) { + a->tv_sec = 0; + a->tv_nsec = 0; +} + +static bool timespec_isset(const struct timespec *a) { + return a->tv_sec; +} + +static long CLOCK_GRANULARITY; // usec + +static inline size_t min(size_t a, size_t b) { + return a < b ? a : b; +} + +static inline size_t max(size_t a, size_t b) { + return a > b ? a : b; +} + +#ifdef UTCP_DEBUG +#include + +#ifndef UTCP_DEBUG_DATALEN +#define UTCP_DEBUG_DATALEN 20 +#endif + +static void debug(struct utcp_connection *c, const char *format, ...) { + struct timespec tv; + char buf[1024]; + int len; + + clock_gettime(CLOCK_REALTIME, &tv); + len = snprintf(buf, sizeof(buf), "%ld.%06lu %u:%u ", (long)tv.tv_sec, tv.tv_nsec / 1000, c ? c->src : 0, c ? c->dst : 0); + va_list ap; + va_start(ap, format); + len += vsnprintf(buf + len, sizeof(buf) - len, format, ap); + va_end(ap); + + if(len > 0 && (size_t)len < sizeof(buf)) { + fwrite(buf, len, 1, stderr); + } +} + +static void print_packet(struct utcp_connection *c, const char *dir, const void *pkt, size_t len) { + struct hdr hdr; + + if(len < sizeof(hdr)) { + debug(c, "%s: short packet (%lu bytes)\n", dir, (unsigned long)len); + return; + } + + memcpy(&hdr, pkt, sizeof(hdr)); + + uint32_t datalen; + + if(len > sizeof(hdr)) { + datalen = min(len - sizeof(hdr), UTCP_DEBUG_DATALEN); + } else { + datalen = 0; + } + + + const uint8_t *data = (uint8_t *)pkt + sizeof(hdr); + char str[datalen * 2 + 1]; + char *p = str; + + for(uint32_t i = 0; i < datalen; i++) { + *p++ = "0123456789ABCDEF"[data[i] >> 4]; + *p++ = "0123456789ABCDEF"[data[i] & 15]; + } + + *p = 0; + + debug(c, "%s: len %lu src %u dst %u seq %u ack %u wnd %u aux %x ctl %s%s%s%s%s data %s\n", + dir, (unsigned long)len, hdr.src, hdr.dst, hdr.seq, hdr.ack, hdr.wnd, hdr.aux, + hdr.ctl & SYN ? "SYN" : "", + hdr.ctl & RST ? "RST" : "", + hdr.ctl & FIN ? "FIN" : "", + hdr.ctl & ACK ? "ACK" : "", + hdr.ctl & MF ? "MF" : "", + str + ); +} + +static void debug_cwnd(struct utcp_connection *c) { + debug(c, "snd.cwnd %u snd.ssthresh %u\n", c->snd.cwnd, ~c->snd.ssthresh ? c->snd.ssthresh : 0); +} +#else +#define debug(...) do {} while(0) +#define print_packet(...) do {} while(0) +#define debug_cwnd(...) do {} while(0) +#endif + +static void set_state(struct utcp_connection *c, enum state state) { + c->state = state; + + if(state == ESTABLISHED) { + timespec_clear(&c->conn_timeout); + } + + debug(c, "state %s\n", strstate[state]); +} + +static bool fin_wanted(struct utcp_connection *c, uint32_t seq) { + if(seq != c->snd.last) { + return false; + } + + switch(c->state) { + case FIN_WAIT_1: + case CLOSING: + case LAST_ACK: + return true; + + default: + return false; + } +} + +static bool is_reliable(struct utcp_connection *c) { + return c->flags & UTCP_RELIABLE; +} + +static int32_t seqdiff(uint32_t a, uint32_t b) { + return a - b; +} + +// Buffer functions +static bool buffer_wraps(struct buffer *buf) { + return buf->size - buf->offset < buf->used; +} + +static bool buffer_resize(struct buffer *buf, uint32_t newsize) { + assert(!buf->external); + + if(!newsize) { + free(buf->data); + buf->data = NULL; + buf->size = 0; + buf->offset = 0; + return true; + } + + char *newdata = realloc(buf->data, newsize); + + if(!newdata) { + return false; + } + + buf->data = newdata; + + if(buffer_wraps(buf)) { + // Shift the right part of the buffer until it hits the end of the new buffer. + // Old situation: + // [345......012] + // New situation: + // [345.........|........012] + uint32_t tailsize = buf->size - buf->offset; + uint32_t newoffset = newsize - tailsize; + memmove(buf->data + newoffset, buf->data + buf->offset, tailsize); + buf->offset = newoffset; + } + + buf->size = newsize; + return true; +} + +// Store data into the buffer +static ssize_t buffer_put_at(struct buffer *buf, size_t offset, const void *data, size_t len) { + debug(NULL, "buffer_put_at %lu %lu %lu\n", (unsigned long)buf->used, (unsigned long)offset, (unsigned long)len); + + // Ensure we don't store more than maxsize bytes in total + size_t required = offset + len; + + if(required > buf->maxsize) { + if(offset >= buf->maxsize) { + return 0; + } + + len = buf->maxsize - offset; + required = buf->maxsize; + } + + // Check if we need to resize the buffer + if(required > buf->size) { + size_t newsize = buf->size; + + if(!newsize) { + newsize = 4096; + } + + do { + newsize *= 2; + } while(newsize < required); + + if(newsize > buf->maxsize) { + newsize = buf->maxsize; + } + + if(!buffer_resize(buf, newsize)) { + return -1; + } + } + + uint32_t realoffset = buf->offset + offset; + + if(buf->size - buf->offset <= offset) { + // The offset wrapped + realoffset -= buf->size; + } + + if(buf->size - realoffset < len) { + // The new chunk of data must be wrapped + memcpy(buf->data + realoffset, data, buf->size - realoffset); + memcpy(buf->data, (char *)data + buf->size - realoffset, len - (buf->size - realoffset)); + } else { + memcpy(buf->data + realoffset, data, len); + } + + if(required > buf->used) { + buf->used = required; + } + + return len; +} + +static ssize_t buffer_put(struct buffer *buf, const void *data, size_t len) { + return buffer_put_at(buf, buf->used, data, len); +} + +// Copy data from the buffer without removing it. +static ssize_t buffer_copy(struct buffer *buf, void *data, size_t offset, size_t len) { + // Ensure we don't copy more than is actually stored in the buffer + if(offset >= buf->used) { + return 0; + } + + if(buf->used - offset < len) { + len = buf->used - offset; + } + + uint32_t realoffset = buf->offset + offset; + + if(buf->size - buf->offset <= offset) { + // The offset wrapped + realoffset -= buf->size; + } + + if(buf->size - realoffset < len) { + // The data is wrapped + memcpy(data, buf->data + realoffset, buf->size - realoffset); + memcpy((char *)data + buf->size - realoffset, buf->data, len - (buf->size - realoffset)); + } else { + memcpy(data, buf->data + realoffset, len); + } + + return len; +} + +// Copy data from the buffer without removing it. +static ssize_t buffer_call(struct utcp_connection *c, struct buffer *buf, size_t offset, size_t len) { + if(!c->recv) { + return len; + } + + // Ensure we don't copy more than is actually stored in the buffer + if(offset >= buf->used) { + return 0; + } + + if(buf->used - offset < len) { + len = buf->used - offset; + } + + uint32_t realoffset = buf->offset + offset; + + if(buf->size - buf->offset <= offset) { + // The offset wrapped + realoffset -= buf->size; + } + + if(buf->size - realoffset < len) { + // The data is wrapped + ssize_t rx1 = c->recv(c, buf->data + realoffset, buf->size - realoffset); + + if(rx1 < buf->size - realoffset) { + return rx1; + } + + // The channel might have been closed by the previous callback + if(!c->recv) { + return len; + } + + ssize_t rx2 = c->recv(c, buf->data, len - (buf->size - realoffset)); + + if(rx2 < 0) { + return rx2; + } else { + return rx1 + rx2; + } + } else { + return c->recv(c, buf->data + realoffset, len); + } +} + +// Discard data from the buffer. +static ssize_t buffer_discard(struct buffer *buf, size_t len) { + if(buf->used < len) { + len = buf->used; + } + + if(buf->size - buf->offset <= len) { + buf->offset -= buf->size; + } + + if(buf->used == len) { + buf->offset = 0; + } else { + buf->offset += len; + } + + buf->used -= len; + + return len; +} + +static void buffer_clear(struct buffer *buf) { + buf->used = 0; + buf->offset = 0; +} + +static bool buffer_set_size(struct buffer *buf, uint32_t minsize, uint32_t maxsize) { + if(maxsize < minsize) { + maxsize = minsize; + } + + buf->maxsize = maxsize; + + return buf->size >= minsize || buffer_resize(buf, minsize); +} + +static void buffer_transfer(struct buffer *buf, char *newdata, size_t newsize) { + if(buffer_wraps(buf)) { + // Old situation: + // [345......012] + // New situation: + // [012345......] + uint32_t tailsize = buf->size - buf->offset; + memcpy(newdata, buf->data + buf->offset, tailsize); + memcpy(newdata + tailsize, buf->data, buf->used - tailsize); + } else { + // Old situation: + // [....012345..] + // New situation: + // [012345......] + memcpy(newdata, buf->data + buf->offset, buf->used); + } + + buf->offset = 0; + buf->size = newsize; +} + +static void set_buffer_storage(struct buffer *buf, char *data, size_t size) { + if(size > UINT32_MAX) { + size = UINT32_MAX; + } + + buf->maxsize = size; + + if(data) { + if(buf->external) { + // Don't allow resizing an external buffer + abort(); + } + + if(size < buf->used) { + // Ignore requests for an external buffer if we are already using more than it can store + return; + } + + // Transition from internal to external buffer + buffer_transfer(buf, data, size); + free(buf->data); + buf->data = data; + buf->external = true; + } else if(buf->external) { + // Transition from external to internal buf + size_t minsize = buf->used <= DEFAULT_SNDBUFSIZE ? DEFAULT_SNDBUFSIZE : buf->used; + + if(minsize) { + data = malloc(minsize); + + if(!data) { + // Cannot handle this + abort(); + } + + buffer_transfer(buf, data, minsize); + buf->data = data; + } else { + buf->data = NULL; + buf->size = 0; + } + + buf->external = false; + } else { + // Don't do anything if the buffer wraps + if(buffer_wraps(buf)) { + return; + } + + // Realloc internal storage + size_t minsize = max(DEFAULT_SNDBUFSIZE, buf->offset + buf->used); + + if(minsize) { + data = realloc(buf->data, minsize); + + if(data) { + buf->data = data; + buf->size = minsize; + } + } else { + free(buf->data); + buf->data = NULL; + buf->size = 0; + } + } +} + +static void buffer_exit(struct buffer *buf) { + if(!buf->external) { + free(buf->data); + } + + memset(buf, 0, sizeof(*buf)); +} + +static uint32_t buffer_free(const struct buffer *buf) { + return buf->maxsize > buf->used ? buf->maxsize - buf->used : 0; +} + +// Connections are stored in a sorted list. +// This gives O(log(N)) lookup time, O(N log(N)) insertion time and O(N) deletion time. + +static int compare(const void *va, const void *vb) { + assert(va && vb); + + const struct utcp_connection *a = *(struct utcp_connection **)va; + const struct utcp_connection *b = *(struct utcp_connection **)vb; + + assert(a && b); + + int c = (int)a->src - (int)b->src; + + if(c) { + return c; + } + + c = (int)a->dst - (int)b->dst; + return c; +} + +static struct utcp_connection *find_connection(const struct utcp *utcp, uint16_t src, uint16_t dst) { + if(!utcp->nconnections) { + return NULL; + } + + struct utcp_connection key = { + .src = src, + .dst = dst, + }, *keyp = &key; + struct utcp_connection **match = bsearch(&keyp, utcp->connections, utcp->nconnections, sizeof(*utcp->connections), compare); + return match ? *match : NULL; +} + +static void free_connection(struct utcp_connection *c) { + struct utcp *utcp = c->utcp; + struct utcp_connection **cp = bsearch(&c, utcp->connections, utcp->nconnections, sizeof(*utcp->connections), compare); + + assert(cp); + + int i = cp - utcp->connections; + memmove(cp, cp + 1, (utcp->nconnections - i - 1) * sizeof(*cp)); + utcp->nconnections--; + + buffer_exit(&c->rcvbuf); + buffer_exit(&c->sndbuf); + free(c); +} + +static struct utcp_connection *allocate_connection(struct utcp *utcp, uint16_t src, uint16_t dst) { + // Check whether this combination of src and dst is free + + if(src) { + if(find_connection(utcp, src, dst)) { + errno = EADDRINUSE; + return NULL; + } + } else { // If src == 0, generate a random port number with the high bit set + if(utcp->nconnections >= 32767) { + errno = ENOMEM; + return NULL; + } + + src = rand() | 0x8000; + + while(find_connection(utcp, src, dst)) { + src++; + } + } + + // Allocate memory for the new connection + + if(utcp->nconnections >= utcp->nallocated) { + if(!utcp->nallocated) { + utcp->nallocated = 4; + } else { + utcp->nallocated *= 2; + } + + struct utcp_connection **new_array = realloc(utcp->connections, utcp->nallocated * sizeof(*utcp->connections)); + + if(!new_array) { + return NULL; + } + + utcp->connections = new_array; + } + + struct utcp_connection *c = calloc(1, sizeof(*c)); + + if(!c) { + return NULL; + } + + if(!buffer_set_size(&c->sndbuf, DEFAULT_SNDBUFSIZE, DEFAULT_MAXSNDBUFSIZE)) { + free(c); + return NULL; + } + + if(!buffer_set_size(&c->rcvbuf, DEFAULT_RCVBUFSIZE, DEFAULT_MAXRCVBUFSIZE)) { + buffer_exit(&c->sndbuf); + free(c); + return NULL; + } + + // Fill in the details + + c->src = src; + c->dst = dst; +#ifdef UTCP_DEBUG + c->snd.iss = 0; +#else + c->snd.iss = rand(); +#endif + c->snd.una = c->snd.iss; + c->snd.nxt = c->snd.iss + 1; + c->snd.last = c->snd.nxt; + c->snd.cwnd = (utcp->mss > 2190 ? 2 : utcp->mss > 1095 ? 3 : 4) * utcp->mss; + c->snd.ssthresh = ~0; + debug_cwnd(c); + c->srtt = 0; + c->rttvar = 0; + c->rto = START_RTO; + c->utcp = utcp; + + // Add it to the sorted list of connections + + utcp->connections[utcp->nconnections++] = c; + qsort(utcp->connections, utcp->nconnections, sizeof(*utcp->connections), compare); + + return c; +} + +static inline uint32_t absdiff(uint32_t a, uint32_t b) { + if(a > b) { + return a - b; + } else { + return b - a; + } +} + +// Update RTT variables. See RFC 6298. +static void update_rtt(struct utcp_connection *c, uint32_t rtt) { + if(!rtt) { + debug(c, "invalid rtt\n"); + return; + } + + if(!c->srtt) { + c->srtt = rtt; + c->rttvar = rtt / 2; + } else { + c->rttvar = (c->rttvar * 3 + absdiff(c->srtt, rtt)) / 4; + c->srtt = (c->srtt * 7 + rtt) / 8; + } + + c->rto = c->srtt + max(4 * c->rttvar, CLOCK_GRANULARITY); + + if(c->rto > MAX_RTO) { + c->rto = MAX_RTO; + } + + debug(c, "rtt %u srtt %u rttvar %u rto %u\n", rtt, c->srtt, c->rttvar, c->rto); +} + +static void start_retransmit_timer(struct utcp_connection *c) { + clock_gettime(UTCP_CLOCK, &c->rtrx_timeout); + + uint32_t rto = c->rto; + + while(rto > USEC_PER_SEC) { + c->rtrx_timeout.tv_sec++; + rto -= USEC_PER_SEC; + } + + c->rtrx_timeout.tv_nsec += rto * 1000; + + if(c->rtrx_timeout.tv_nsec >= NSEC_PER_SEC) { + c->rtrx_timeout.tv_nsec -= NSEC_PER_SEC; + c->rtrx_timeout.tv_sec++; + } + + debug(c, "rtrx_timeout %ld.%06lu\n", c->rtrx_timeout.tv_sec, c->rtrx_timeout.tv_nsec); +} + +static void stop_retransmit_timer(struct utcp_connection *c) { + timespec_clear(&c->rtrx_timeout); + debug(c, "rtrx_timeout cleared\n"); +} + +struct utcp_connection *utcp_connect_ex(struct utcp *utcp, uint16_t dst, utcp_recv_t recv, void *priv, uint32_t flags) { + struct utcp_connection *c = allocate_connection(utcp, 0, dst); + + if(!c) { + return NULL; + } + + assert((flags & ~0x1f) == 0); + + c->flags = flags; + c->recv = recv; + c->priv = priv; + + struct { + struct hdr hdr; + uint8_t init[4]; + } pkt; + + pkt.hdr.src = c->src; + pkt.hdr.dst = c->dst; + pkt.hdr.seq = c->snd.iss; + pkt.hdr.ack = 0; + pkt.hdr.wnd = c->rcvbuf.maxsize; + pkt.hdr.ctl = SYN; + pkt.hdr.aux = 0x0101; + pkt.init[0] = 1; + pkt.init[1] = 0; + pkt.init[2] = 0; + pkt.init[3] = flags & 0x7; + + set_state(c, SYN_SENT); + + print_packet(c, "send", &pkt, sizeof(pkt)); + utcp->send(utcp, &pkt, sizeof(pkt)); + + clock_gettime(UTCP_CLOCK, &c->conn_timeout); + c->conn_timeout.tv_sec += utcp->timeout; + + start_retransmit_timer(c); + + return c; +} + +struct utcp_connection *utcp_connect(struct utcp *utcp, uint16_t dst, utcp_recv_t recv, void *priv) { + return utcp_connect_ex(utcp, dst, recv, priv, UTCP_TCP); +} + +void utcp_accept(struct utcp_connection *c, utcp_recv_t recv, void *priv) { + if(c->reapable || c->state != SYN_RECEIVED) { + debug(c, "accept() called on invalid connection in state %s\n", c, strstate[c->state]); + return; + } + + debug(c, "accepted %p %p\n", c, recv, priv); + c->recv = recv; + c->priv = priv; + c->do_poll = true; + set_state(c, ESTABLISHED); +} + +static void ack(struct utcp_connection *c, bool sendatleastone) { + int32_t left = seqdiff(c->snd.last, c->snd.nxt); + int32_t cwndleft = is_reliable(c) ? min(c->snd.cwnd, c->snd.wnd) - seqdiff(c->snd.nxt, c->snd.una) : MAX_UNRELIABLE_SIZE; + + assert(left >= 0); + + if(cwndleft <= 0) { + left = 0; + } else if(cwndleft < left) { + left = cwndleft; + + if(!sendatleastone || cwndleft > c->utcp->mss) { + left -= left % c->utcp->mss; + } + } + + debug(c, "cwndleft %d left %d\n", cwndleft, left); + + if(!left && !sendatleastone) { + return; + } + + struct { + struct hdr hdr; + uint8_t data[]; + } *pkt = c->utcp->pkt; + + pkt->hdr.src = c->src; + pkt->hdr.dst = c->dst; + pkt->hdr.ack = c->rcv.nxt; + pkt->hdr.wnd = is_reliable(c) ? c->rcvbuf.maxsize : 0; + pkt->hdr.ctl = ACK; + pkt->hdr.aux = 0; + + do { + uint32_t seglen = left > c->utcp->mss ? c->utcp->mss : left; + pkt->hdr.seq = c->snd.nxt; + + buffer_copy(&c->sndbuf, pkt->data, seqdiff(c->snd.nxt, c->snd.una), seglen); + + c->snd.nxt += seglen; + left -= seglen; + + if(!is_reliable(c)) { + if(left) { + pkt->hdr.ctl |= MF; + } else { + pkt->hdr.ctl &= ~MF; + } + } + + if(seglen && fin_wanted(c, c->snd.nxt)) { + seglen--; + pkt->hdr.ctl |= FIN; + } + + if(!c->rtt_start.tv_sec) { + // Start RTT measurement + clock_gettime(UTCP_CLOCK, &c->rtt_start); + c->rtt_seq = pkt->hdr.seq + seglen; + debug(c, "starting RTT measurement, expecting ack %u\n", c->rtt_seq); + } + + print_packet(c, "send", pkt, sizeof(pkt->hdr) + seglen); + c->utcp->send(c->utcp, pkt, sizeof(pkt->hdr) + seglen); + + if(left && !is_reliable(c)) { + pkt->hdr.wnd += seglen; + } + } while(left); +} + +ssize_t utcp_send(struct utcp_connection *c, const void *data, size_t len) { + if(c->reapable) { + debug(c, "send() called on closed connection\n"); + errno = EBADF; + return -1; + } + + switch(c->state) { + case CLOSED: + case LISTEN: + debug(c, "send() called on unconnected connection\n"); + errno = ENOTCONN; + return -1; + + case SYN_SENT: + case SYN_RECEIVED: + case ESTABLISHED: + case CLOSE_WAIT: + break; + + case FIN_WAIT_1: + case FIN_WAIT_2: + case CLOSING: + case LAST_ACK: + case TIME_WAIT: + debug(c, "send() called on closed connection\n"); + errno = EPIPE; + return -1; + } + + // Exit early if we have nothing to send. + + if(!len) { + return 0; + } + + if(!data) { + errno = EFAULT; + return -1; + } + + // Check if we need to be able to buffer all data + + if(c->flags & UTCP_NO_PARTIAL) { + if(len > buffer_free(&c->sndbuf)) { + if(len > c->sndbuf.maxsize) { + errno = EMSGSIZE; + return -1; + } else { + errno = EWOULDBLOCK; + return 0; + } + } + } + + // Add data to send buffer. + + if(is_reliable(c)) { + len = buffer_put(&c->sndbuf, data, len); + } else if(c->state != SYN_SENT && c->state != SYN_RECEIVED) { + if(len > MAX_UNRELIABLE_SIZE || buffer_put(&c->sndbuf, data, len) != (ssize_t)len) { + errno = EMSGSIZE; + return -1; + } + } else { + return 0; + } + + if(len <= 0) { + if(is_reliable(c)) { + errno = EWOULDBLOCK; + return 0; + } else { + return len; + } + } + + c->snd.last += len; + + // Don't send anything yet if the connection has not fully established yet + + if(c->state == SYN_SENT || c->state == SYN_RECEIVED) { + return len; + } + + ack(c, false); + + if(!is_reliable(c)) { + c->snd.una = c->snd.nxt = c->snd.last; + buffer_discard(&c->sndbuf, c->sndbuf.used); + } + + if(is_reliable(c) && !timespec_isset(&c->rtrx_timeout)) { + start_retransmit_timer(c); + } + + if(is_reliable(c) && !timespec_isset(&c->conn_timeout)) { + clock_gettime(UTCP_CLOCK, &c->conn_timeout); + c->conn_timeout.tv_sec += c->utcp->timeout; + } + + return len; +} + +static void swap_ports(struct hdr *hdr) { + uint16_t tmp = hdr->src; + hdr->src = hdr->dst; + hdr->dst = tmp; +} + +static void fast_retransmit(struct utcp_connection *c) { + if(c->state == CLOSED || c->snd.last == c->snd.una) { + debug(c, "fast_retransmit() called but nothing to retransmit!\n"); + return; + } + + struct utcp *utcp = c->utcp; + + struct { + struct hdr hdr; + uint8_t data[]; + } *pkt = c->utcp->pkt; + + pkt->hdr.src = c->src; + pkt->hdr.dst = c->dst; + pkt->hdr.wnd = c->rcvbuf.maxsize; + pkt->hdr.aux = 0; + + switch(c->state) { + case ESTABLISHED: + case FIN_WAIT_1: + case CLOSE_WAIT: + case CLOSING: + case LAST_ACK: + // Send unacked data again. + pkt->hdr.seq = c->snd.una; + pkt->hdr.ack = c->rcv.nxt; + pkt->hdr.ctl = ACK; + uint32_t len = min(seqdiff(c->snd.last, c->snd.una), utcp->mss); + + if(fin_wanted(c, c->snd.una + len)) { + len--; + pkt->hdr.ctl |= FIN; + } + + buffer_copy(&c->sndbuf, pkt->data, 0, len); + print_packet(c, "rtrx", pkt, sizeof(pkt->hdr) + len); + utcp->send(utcp, pkt, sizeof(pkt->hdr) + len); + break; + + default: + break; + } +} + +static void retransmit(struct utcp_connection *c) { + if(c->state == CLOSED || c->snd.last == c->snd.una) { + debug(c, "retransmit() called but nothing to retransmit!\n"); + stop_retransmit_timer(c); + return; + } + + struct utcp *utcp = c->utcp; + + if(utcp->retransmit) { + utcp->retransmit(c); + } + + struct { + struct hdr hdr; + uint8_t data[]; + } *pkt = c->utcp->pkt; + + pkt->hdr.src = c->src; + pkt->hdr.dst = c->dst; + pkt->hdr.wnd = c->rcvbuf.maxsize; + pkt->hdr.aux = 0; + + switch(c->state) { + case SYN_SENT: + // Send our SYN again + pkt->hdr.seq = c->snd.iss; + pkt->hdr.ack = 0; + pkt->hdr.ctl = SYN; + pkt->hdr.aux = 0x0101; + pkt->data[0] = 1; + pkt->data[1] = 0; + pkt->data[2] = 0; + pkt->data[3] = c->flags & 0x7; + print_packet(c, "rtrx", pkt, sizeof(pkt->hdr) + 4); + utcp->send(utcp, pkt, sizeof(pkt->hdr) + 4); + break; + + case SYN_RECEIVED: + // Send SYNACK again + pkt->hdr.seq = c->snd.nxt; + pkt->hdr.ack = c->rcv.nxt; + pkt->hdr.ctl = SYN | ACK; + print_packet(c, "rtrx", pkt, sizeof(pkt->hdr)); + utcp->send(utcp, pkt, sizeof(pkt->hdr)); + break; + + case ESTABLISHED: + case FIN_WAIT_1: + case CLOSE_WAIT: + case CLOSING: + case LAST_ACK: + // Send unacked data again. + pkt->hdr.seq = c->snd.una; + pkt->hdr.ack = c->rcv.nxt; + pkt->hdr.ctl = ACK; + uint32_t len = min(seqdiff(c->snd.last, c->snd.una), utcp->mss); + + if(fin_wanted(c, c->snd.una + len)) { + len--; + pkt->hdr.ctl |= FIN; + } + + // RFC 5681 slow start after timeout + uint32_t flightsize = seqdiff(c->snd.nxt, c->snd.una); + c->snd.ssthresh = max(flightsize / 2, utcp->mss * 2); // eq. 4 + c->snd.cwnd = utcp->mss; + debug_cwnd(c); + + buffer_copy(&c->sndbuf, pkt->data, 0, len); + print_packet(c, "rtrx", pkt, sizeof(pkt->hdr) + len); + utcp->send(utcp, pkt, sizeof(pkt->hdr) + len); + + c->snd.nxt = c->snd.una + len; + break; + + case CLOSED: + case LISTEN: + case TIME_WAIT: + case FIN_WAIT_2: + // We shouldn't need to retransmit anything in this state. +#ifdef UTCP_DEBUG + abort(); +#endif + stop_retransmit_timer(c); + goto cleanup; + } + + start_retransmit_timer(c); + c->rto *= 2; + + if(c->rto > MAX_RTO) { + c->rto = MAX_RTO; + } + + c->rtt_start.tv_sec = 0; // invalidate RTT timer + c->dupack = 0; // cancel any ongoing fast recovery + +cleanup: + return; +} + +/* Update receive buffer and SACK entries after consuming data. + * + * Situation: + * + * |.....0000..1111111111.....22222......3333| + * |---------------^ + * + * 0..3 represent the SACK entries. The ^ indicates up to which point we want + * to remove data from the receive buffer. The idea is to substract "len" + * from the offset of all the SACK entries, and then remove/cut down entries + * that are shifted to before the start of the receive buffer. + * + * There are three cases: + * - the SACK entry is after ^, in that case just change the offset. + * - the SACK entry starts before and ends after ^, so we have to + * change both its offset and size. + * - the SACK entry is completely before ^, in that case delete it. + */ +static void sack_consume(struct utcp_connection *c, size_t len) { + debug(c, "sack_consume %lu\n", (unsigned long)len); + + if(len > c->rcvbuf.used) { + debug(c, "all SACK entries consumed\n"); + c->sacks[0].len = 0; + return; + } + + buffer_discard(&c->rcvbuf, len); + + for(int i = 0; i < NSACKS && c->sacks[i].len;) { + if(len < c->sacks[i].offset) { + c->sacks[i].offset -= len; + i++; + } else if(len < c->sacks[i].offset + c->sacks[i].len) { + c->sacks[i].len -= len - c->sacks[i].offset; + c->sacks[i].offset = 0; + i++; + } else { + if(i < NSACKS - 1) { + memmove(&c->sacks[i], &c->sacks[i + 1], (NSACKS - 1 - i) * sizeof(c->sacks)[i]); + c->sacks[NSACKS - 1].len = 0; + } else { + c->sacks[i].len = 0; + break; + } + } + } + + for(int i = 0; i < NSACKS && c->sacks[i].len; i++) { + debug(c, "SACK[%d] offset %u len %u\n", i, c->sacks[i].offset, c->sacks[i].len); + } +} + +static void handle_out_of_order(struct utcp_connection *c, uint32_t offset, const void *data, size_t len) { + debug(c, "out of order packet, offset %u\n", offset); + // Packet loss or reordering occured. Store the data in the buffer. + ssize_t rxd = buffer_put_at(&c->rcvbuf, offset, data, len); + + if(rxd <= 0) { + debug(c, "packet outside receive buffer, dropping\n"); + return; + } + + if((size_t)rxd < len) { + debug(c, "packet partially outside receive buffer\n"); + len = rxd; + } + + // Make note of where we put it. + for(int i = 0; i < NSACKS; i++) { + if(!c->sacks[i].len) { // nothing to merge, add new entry + debug(c, "new SACK entry %d\n", i); + c->sacks[i].offset = offset; + c->sacks[i].len = rxd; + break; + } else if(offset < c->sacks[i].offset) { + if(offset + rxd < c->sacks[i].offset) { // insert before + if(!c->sacks[NSACKS - 1].len) { // only if room left + debug(c, "insert SACK entry at %d\n", i); + memmove(&c->sacks[i + 1], &c->sacks[i], (NSACKS - i - 1) * sizeof(c->sacks)[i]); + c->sacks[i].offset = offset; + c->sacks[i].len = rxd; + } else { + debug(c, "SACK entries full, dropping packet\n"); + } + + break; + } else { // merge + debug(c, "merge with start of SACK entry at %d\n", i); + c->sacks[i].offset = offset; + break; + } + } else if(offset <= c->sacks[i].offset + c->sacks[i].len) { + if(offset + rxd > c->sacks[i].offset + c->sacks[i].len) { // merge + debug(c, "merge with end of SACK entry at %d\n", i); + c->sacks[i].len = offset + rxd - c->sacks[i].offset; + // TODO: handle potential merge with next entry + } + + break; + } + } + + for(int i = 0; i < NSACKS && c->sacks[i].len; i++) { + debug(c, "SACK[%d] offset %u len %u\n", i, c->sacks[i].offset, c->sacks[i].len); + } +} + +static void handle_in_order(struct utcp_connection *c, const void *data, size_t len) { + if(c->recv) { + ssize_t rxd = c->recv(c, data, len); + + if(rxd != (ssize_t)len) { + // TODO: handle the application not accepting all data. + abort(); + } + } + + // Check if we can process out-of-order data now. + if(c->sacks[0].len && len >= c->sacks[0].offset) { + debug(c, "incoming packet len %lu connected with SACK at %u\n", (unsigned long)len, c->sacks[0].offset); + + if(len < c->sacks[0].offset + c->sacks[0].len) { + size_t offset = len; + len = c->sacks[0].offset + c->sacks[0].len; + size_t remainder = len - offset; + + ssize_t rxd = buffer_call(c, &c->rcvbuf, offset, remainder); + + if(rxd != (ssize_t)remainder) { + // TODO: handle the application not accepting all data. + abort(); + } + } + } + + if(c->rcvbuf.used) { + sack_consume(c, len); + } + + c->rcv.nxt += len; +} + +static void handle_unreliable(struct utcp_connection *c, const struct hdr *hdr, const void *data, size_t len) { + // Fast path for unfragmented packets + if(!hdr->wnd && !(hdr->ctl & MF)) { + if(c->recv) { + c->recv(c, data, len); + } + + c->rcv.nxt = hdr->seq + len; + return; + } + + // Ensure reassembled packet are not larger than 64 kiB + if(hdr->wnd >= MAX_UNRELIABLE_SIZE || hdr->wnd + len > MAX_UNRELIABLE_SIZE) { + return; + } + + // Don't accept out of order fragments + if(hdr->wnd && hdr->seq != c->rcv.nxt) { + return; + } + + // Reset the receive buffer for the first fragment + if(!hdr->wnd) { + buffer_clear(&c->rcvbuf); + } + + ssize_t rxd = buffer_put_at(&c->rcvbuf, hdr->wnd, data, len); + + if(rxd != (ssize_t)len) { + return; + } + + // Send the packet if it's the final fragment + if(!(hdr->ctl & MF)) { + buffer_call(c, &c->rcvbuf, 0, hdr->wnd + len); + } + + c->rcv.nxt = hdr->seq + len; +} + +static void handle_incoming_data(struct utcp_connection *c, const struct hdr *hdr, const void *data, size_t len) { + if(!is_reliable(c)) { + handle_unreliable(c, hdr, data, len); + return; + } + + uint32_t offset = seqdiff(hdr->seq, c->rcv.nxt); + + if(offset) { + handle_out_of_order(c, offset, data, len); + } else { + handle_in_order(c, data, len); + } +} + + +ssize_t utcp_recv(struct utcp *utcp, const void *data, size_t len) { + const uint8_t *ptr = data; + + if(!utcp) { + errno = EFAULT; + return -1; + } + + if(!len) { + return 0; + } + + if(!data) { + errno = EFAULT; + return -1; + } + + // Drop packets smaller than the header + + struct hdr hdr; + + if(len < sizeof(hdr)) { + print_packet(NULL, "recv", data, len); + errno = EBADMSG; + return -1; + } + + // Make a copy from the potentially unaligned data to a struct hdr + + memcpy(&hdr, ptr, sizeof(hdr)); + + // Try to match the packet to an existing connection + + struct utcp_connection *c = find_connection(utcp, hdr.dst, hdr.src); + print_packet(c, "recv", data, len); + + // Process the header + + ptr += sizeof(hdr); + len -= sizeof(hdr); + + // Drop packets with an unknown CTL flag + + if(hdr.ctl & ~(SYN | ACK | RST | FIN | MF)) { + print_packet(NULL, "recv", data, len); + errno = EBADMSG; + return -1; + } + + // Check for auxiliary headers + + const uint8_t *init = NULL; + + uint16_t aux = hdr.aux; + + while(aux) { + size_t auxlen = 4 * (aux >> 8) & 0xf; + uint8_t auxtype = aux & 0xff; + + if(len < auxlen) { + errno = EBADMSG; + return -1; + } + + switch(auxtype) { + case AUX_INIT: + if(!(hdr.ctl & SYN) || auxlen != 4) { + errno = EBADMSG; + return -1; + } + + init = ptr; + break; + + default: + errno = EBADMSG; + return -1; + } + + len -= auxlen; + ptr += auxlen; + + if(!(aux & 0x800)) { + break; + } + + if(len < 2) { + errno = EBADMSG; + return -1; + } + + memcpy(&aux, ptr, 2); + len -= 2; + ptr += 2; + } + + bool has_data = len || (hdr.ctl & (SYN | FIN)); + + // Is it for a new connection? + + if(!c) { + // Ignore RST packets + + if(hdr.ctl & RST) { + return 0; + } + + // Is it a SYN packet and are we LISTENing? + + if(hdr.ctl & SYN && !(hdr.ctl & ACK) && utcp->accept) { + // If we don't want to accept it, send a RST back + if((utcp->listen && !utcp->listen(utcp, hdr.dst))) { + len = 1; + goto reset; + } + + // Try to allocate memory, otherwise send a RST back + c = allocate_connection(utcp, hdr.dst, hdr.src); + + if(!c) { + len = 1; + goto reset; + } + + // Parse auxilliary information + if(init) { + if(init[0] < 1) { + len = 1; + goto reset; + } + + c->flags = init[3] & 0x7; + } else { + c->flags = UTCP_TCP; + } + +synack: + // Return SYN+ACK, go to SYN_RECEIVED state + c->snd.wnd = hdr.wnd; + c->rcv.irs = hdr.seq; + c->rcv.nxt = c->rcv.irs + 1; + set_state(c, SYN_RECEIVED); + + struct { + struct hdr hdr; + uint8_t data[4]; + } pkt; + + pkt.hdr.src = c->src; + pkt.hdr.dst = c->dst; + pkt.hdr.ack = c->rcv.irs + 1; + pkt.hdr.seq = c->snd.iss; + pkt.hdr.wnd = c->rcvbuf.maxsize; + pkt.hdr.ctl = SYN | ACK; + + if(init) { + pkt.hdr.aux = 0x0101; + pkt.data[0] = 1; + pkt.data[1] = 0; + pkt.data[2] = 0; + pkt.data[3] = c->flags & 0x7; + print_packet(c, "send", &pkt, sizeof(hdr) + 4); + utcp->send(utcp, &pkt, sizeof(hdr) + 4); + } else { + pkt.hdr.aux = 0; + print_packet(c, "send", &pkt, sizeof(hdr)); + utcp->send(utcp, &pkt, sizeof(hdr)); + } + + start_retransmit_timer(c); + } else { + // No, we don't want your packets, send a RST back + len = 1; + goto reset; + } + + return 0; + } + + debug(c, "state %s\n", strstate[c->state]); + + // In case this is for a CLOSED connection, ignore the packet. + // TODO: make it so incoming packets can never match a CLOSED connection. + + if(c->state == CLOSED) { + debug(c, "got packet for closed connection\n"); + goto reset; + } + + // It is for an existing connection. + + // 1. Drop invalid packets. + + // 1a. Drop packets that should not happen in our current state. + + switch(c->state) { + case SYN_SENT: + case SYN_RECEIVED: + case ESTABLISHED: + case FIN_WAIT_1: + case FIN_WAIT_2: + case CLOSE_WAIT: + case CLOSING: + case LAST_ACK: + case TIME_WAIT: + break; + + default: +#ifdef UTCP_DEBUG + abort(); +#endif + break; + } + + // 1b. Discard data that is not in our receive window. + + if(is_reliable(c)) { + bool acceptable; + + if(c->state == SYN_SENT) { + acceptable = true; + } else if(len == 0) { + acceptable = seqdiff(hdr.seq, c->rcv.nxt) >= 0; + } else { + int32_t rcv_offset = seqdiff(hdr.seq, c->rcv.nxt); + + // cut already accepted front overlapping + if(rcv_offset < 0) { + acceptable = len > (size_t) - rcv_offset; + + if(acceptable) { + ptr -= rcv_offset; + len += rcv_offset; + hdr.seq -= rcv_offset; + } + } else { + acceptable = seqdiff(hdr.seq, c->rcv.nxt) >= 0 && seqdiff(hdr.seq, c->rcv.nxt) + len <= c->rcvbuf.maxsize; + } + } + + if(!acceptable) { + debug(c, "packet not acceptable, %u <= %u + %lu < %u\n", c->rcv.nxt, hdr.seq, (unsigned long)len, c->rcv.nxt + c->rcvbuf.maxsize); + + // Ignore unacceptable RST packets. + if(hdr.ctl & RST) { + return 0; + } + + // Otherwise, continue processing. + len = 0; + } + } else { +#if UTCP_DEBUG + int32_t rcv_offset = seqdiff(hdr.seq, c->rcv.nxt); + + if(rcv_offset) { + debug(c, "packet out of order, offset %u bytes", rcv_offset); + } + +#endif + } + + c->snd.wnd = hdr.wnd; // TODO: move below + + // 1c. Drop packets with an invalid ACK. + // ackno should not roll back, and it should also not be bigger than what we ever could have sent + // (= snd.una + c->sndbuf.used). + + if(!is_reliable(c)) { + if(hdr.ack != c->snd.last && c->state >= ESTABLISHED) { + hdr.ack = c->snd.una; + } + } + + // 2. Handle RST packets + + if(hdr.ctl & RST) { + switch(c->state) { + case SYN_SENT: + if(!(hdr.ctl & ACK)) { + return 0; + } + + // The peer has refused our connection. + set_state(c, CLOSED); + errno = ECONNREFUSED; + + if(c->recv) { + c->recv(c, NULL, 0); + } + + if(c->poll && !c->reapable) { + c->poll(c, 0); + } + + return 0; + + case SYN_RECEIVED: + if(hdr.ctl & ACK) { + return 0; + } + + // We haven't told the application about this connection yet. Silently delete. + free_connection(c); + return 0; + + case ESTABLISHED: + case FIN_WAIT_1: + case FIN_WAIT_2: + case CLOSE_WAIT: + if(hdr.ctl & ACK) { + return 0; + } + + // The peer has aborted our connection. + set_state(c, CLOSED); + errno = ECONNRESET; + buffer_clear(&c->sndbuf); + buffer_clear(&c->rcvbuf); + + if(c->recv) { + c->recv(c, NULL, 0); + } + + if(c->poll && !c->reapable) { + c->poll(c, 0); + } + + return 0; + + case CLOSING: + case LAST_ACK: + case TIME_WAIT: + if(hdr.ctl & ACK) { + return 0; + } + + // As far as the application is concerned, the connection has already been closed. + // If it has called utcp_close() already, we can immediately free this connection. + if(c->reapable) { + free_connection(c); + return 0; + } + + // Otherwise, immediately move to the CLOSED state. + set_state(c, CLOSED); + return 0; + + default: +#ifdef UTCP_DEBUG + abort(); +#endif + break; + } + } + + uint32_t advanced; + + if(!(hdr.ctl & ACK)) { + advanced = 0; + goto skip_ack; + } + + // 3. Advance snd.una + + if(seqdiff(hdr.ack, c->snd.last) > 0 || seqdiff(hdr.ack, c->snd.una) < 0) { + debug(c, "packet ack seqno out of range, %u <= %u < %u\n", c->snd.una, hdr.ack, c->snd.una + c->sndbuf.used); + goto reset; + } + + advanced = seqdiff(hdr.ack, c->snd.una); + + if(advanced) { + // RTT measurement + if(c->rtt_start.tv_sec) { + if(c->rtt_seq == hdr.ack) { + struct timespec now; + clock_gettime(UTCP_CLOCK, &now); + int32_t diff = timespec_diff_usec(&now, &c->rtt_start); + update_rtt(c, diff); + c->rtt_start.tv_sec = 0; + } else if(c->rtt_seq < hdr.ack) { + debug(c, "cancelling RTT measurement: %u < %u\n", c->rtt_seq, hdr.ack); + c->rtt_start.tv_sec = 0; + } + } + + int32_t data_acked = advanced; + + switch(c->state) { + case SYN_SENT: + case SYN_RECEIVED: + data_acked--; + break; + + // TODO: handle FIN as well. + default: + break; + } + + assert(data_acked >= 0); + +#ifndef NDEBUG + int32_t bufused = seqdiff(c->snd.last, c->snd.una); + assert(data_acked <= bufused); +#endif + + if(data_acked) { + buffer_discard(&c->sndbuf, data_acked); + + if(is_reliable(c)) { + c->do_poll = true; + } + } + + // Also advance snd.nxt if possible + if(seqdiff(c->snd.nxt, hdr.ack) < 0) { + c->snd.nxt = hdr.ack; + } + + c->snd.una = hdr.ack; + + if(c->dupack) { + if(c->dupack >= 3) { + debug(c, "fast recovery ended\n"); + c->snd.cwnd = c->snd.ssthresh; + } + + c->dupack = 0; + } + + // Increase the congestion window according to RFC 5681 + if(c->snd.cwnd < c->snd.ssthresh) { + c->snd.cwnd += min(advanced, utcp->mss); // eq. 2 + } else { + c->snd.cwnd += max(1, (utcp->mss * utcp->mss) / c->snd.cwnd); // eq. 3 + } + + if(c->snd.cwnd > c->sndbuf.maxsize) { + c->snd.cwnd = c->sndbuf.maxsize; + } + + debug_cwnd(c); + + // Check if we have sent a FIN that is now ACKed. + switch(c->state) { + case FIN_WAIT_1: + if(c->snd.una == c->snd.last) { + set_state(c, FIN_WAIT_2); + } + + break; + + case CLOSING: + if(c->snd.una == c->snd.last) { + clock_gettime(UTCP_CLOCK, &c->conn_timeout); + c->conn_timeout.tv_sec += utcp->timeout; + set_state(c, TIME_WAIT); + } + + break; + + default: + break; + } + } else { + if(!len && is_reliable(c) && c->snd.una != c->snd.last) { + c->dupack++; + debug(c, "duplicate ACK %d\n", c->dupack); + + if(c->dupack == 3) { + // RFC 5681 fast recovery + debug(c, "fast recovery started\n", c->dupack); + uint32_t flightsize = seqdiff(c->snd.nxt, c->snd.una); + c->snd.ssthresh = max(flightsize / 2, utcp->mss * 2); // eq. 4 + c->snd.cwnd = min(c->snd.ssthresh + 3 * utcp->mss, c->sndbuf.maxsize); + + if(c->snd.cwnd > c->sndbuf.maxsize) { + c->snd.cwnd = c->sndbuf.maxsize; + } + + debug_cwnd(c); + + fast_retransmit(c); + } else if(c->dupack > 3) { + c->snd.cwnd += utcp->mss; + + if(c->snd.cwnd > c->sndbuf.maxsize) { + c->snd.cwnd = c->sndbuf.maxsize; + } + + debug_cwnd(c); + } + + // We got an ACK which indicates the other side did get one of our packets. + // Reset the retransmission timer to avoid going to slow start, + // but don't touch the connection timeout. + start_retransmit_timer(c); + } + } + + // 4. Update timers + + if(advanced) { + if(c->snd.una == c->snd.last) { + stop_retransmit_timer(c); + timespec_clear(&c->conn_timeout); + } else if(is_reliable(c)) { + start_retransmit_timer(c); + clock_gettime(UTCP_CLOCK, &c->conn_timeout); + c->conn_timeout.tv_sec += utcp->timeout; + } + } + +skip_ack: + // 5. Process SYN stuff + + if(hdr.ctl & SYN) { + switch(c->state) { + case SYN_SENT: + + // This is a SYNACK. It should always have ACKed the SYN. + if(!advanced) { + goto reset; + } + + c->rcv.irs = hdr.seq; + c->rcv.nxt = hdr.seq + 1; + + if(c->shut_wr) { + c->snd.last++; + set_state(c, FIN_WAIT_1); + } else { + c->do_poll = true; + set_state(c, ESTABLISHED); + } + + break; + + case SYN_RECEIVED: + // This is a retransmit of a SYN, send back the SYNACK. + goto synack; + + case ESTABLISHED: + case FIN_WAIT_1: + case FIN_WAIT_2: + case CLOSE_WAIT: + case CLOSING: + case LAST_ACK: + case TIME_WAIT: + // This could be a retransmission. Ignore the SYN flag, but send an ACK back. + break; + + default: +#ifdef UTCP_DEBUG + abort(); +#endif + return 0; + } + } + + // 6. Process new data + + if(c->state == SYN_RECEIVED) { + // This is the ACK after the SYNACK. It should always have ACKed the SYNACK. + if(!advanced) { + goto reset; + } + + // Are we still LISTENing? + if(utcp->accept) { + utcp->accept(c, c->src); + } + + if(c->state != ESTABLISHED) { + set_state(c, CLOSED); + c->reapable = true; + goto reset; + } + } + + if(len) { + switch(c->state) { + case SYN_SENT: + case SYN_RECEIVED: + // This should never happen. +#ifdef UTCP_DEBUG + abort(); +#endif + return 0; + + case ESTABLISHED: + break; + + case FIN_WAIT_1: + case FIN_WAIT_2: + if(c->reapable) { + // We already closed the connection and are not interested in more data. + goto reset; + } + + break; + + case CLOSE_WAIT: + case CLOSING: + case LAST_ACK: + case TIME_WAIT: + // Ehm no, We should never receive more data after a FIN. + goto reset; + + default: +#ifdef UTCP_DEBUG + abort(); +#endif + return 0; + } + + handle_incoming_data(c, &hdr, ptr, len); + } + + // 7. Process FIN stuff + + if((hdr.ctl & FIN) && (!is_reliable(c) || hdr.seq + len == c->rcv.nxt)) { + switch(c->state) { + case SYN_SENT: + case SYN_RECEIVED: + // This should never happen. +#ifdef UTCP_DEBUG + abort(); +#endif + break; + + case ESTABLISHED: + set_state(c, CLOSE_WAIT); + break; + + case FIN_WAIT_1: + set_state(c, CLOSING); + break; + + case FIN_WAIT_2: + clock_gettime(UTCP_CLOCK, &c->conn_timeout); + c->conn_timeout.tv_sec += utcp->timeout; + set_state(c, TIME_WAIT); + break; + + case CLOSE_WAIT: + case CLOSING: + case LAST_ACK: + case TIME_WAIT: + // Ehm, no. We should never receive a second FIN. + goto reset; + + default: +#ifdef UTCP_DEBUG + abort(); +#endif + break; + } + + // FIN counts as one sequence number + c->rcv.nxt++; + len++; + + // Inform the application that the peer closed its end of the connection. + if(c->recv) { + errno = 0; + c->recv(c, NULL, 0); + } + } + + // Now we send something back if: + // - we received data, so we have to send back an ACK + // -> sendatleastone = true + // - or we got an ack, so we should maybe send a bit more data + // -> sendatleastone = false + + if(is_reliable(c) || hdr.ctl & SYN || hdr.ctl & FIN) { + ack(c, has_data); + } + + return 0; + +reset: + swap_ports(&hdr); + hdr.wnd = 0; + hdr.aux = 0; + + if(hdr.ctl & ACK) { + hdr.seq = hdr.ack; + hdr.ctl = RST; + } else { + hdr.ack = hdr.seq + len; + hdr.seq = 0; + hdr.ctl = RST | ACK; + } + + print_packet(c, "send", &hdr, sizeof(hdr)); + utcp->send(utcp, &hdr, sizeof(hdr)); + return 0; + +} + +int utcp_shutdown(struct utcp_connection *c, int dir) { + debug(c, "shutdown %d at %u\n", dir, c ? c->snd.last : 0); + + if(!c) { + errno = EFAULT; + return -1; + } + + if(c->reapable) { + debug(c, "shutdown() called on closed connection\n"); + errno = EBADF; + return -1; + } + + if(!(dir == UTCP_SHUT_RD || dir == UTCP_SHUT_WR || dir == UTCP_SHUT_RDWR)) { + errno = EINVAL; + return -1; + } + + // TCP does not have a provision for stopping incoming packets. + // The best we can do is to just ignore them. + if(dir == UTCP_SHUT_RD || dir == UTCP_SHUT_RDWR) { + c->recv = NULL; + } + + // The rest of the code deals with shutting down writes. + if(dir == UTCP_SHUT_RD) { + return 0; + } + + // Only process shutting down writes once. + if(c->shut_wr) { + return 0; + } + + c->shut_wr = true; + + switch(c->state) { + case CLOSED: + case LISTEN: + errno = ENOTCONN; + return -1; + + case SYN_SENT: + return 0; + + case SYN_RECEIVED: + case ESTABLISHED: + set_state(c, FIN_WAIT_1); + break; + + case FIN_WAIT_1: + case FIN_WAIT_2: + return 0; + + case CLOSE_WAIT: + set_state(c, CLOSING); + break; + + case CLOSING: + case LAST_ACK: + case TIME_WAIT: + return 0; + } + + c->snd.last++; + + ack(c, false); + + if(!timespec_isset(&c->rtrx_timeout)) { + start_retransmit_timer(c); + } + + return 0; +} + +static bool reset_connection(struct utcp_connection *c) { + if(!c) { + errno = EFAULT; + return false; + } + + if(c->reapable) { + debug(c, "abort() called on closed connection\n"); + errno = EBADF; + return false; + } + + buffer_clear(&c->sndbuf); + buffer_clear(&c->rcvbuf); + + switch(c->state) { + case CLOSED: + return true; + + case LISTEN: + case SYN_SENT: + case CLOSING: + case LAST_ACK: + case TIME_WAIT: + set_state(c, CLOSED); + return true; + + case SYN_RECEIVED: + case ESTABLISHED: + case FIN_WAIT_1: + case FIN_WAIT_2: + case CLOSE_WAIT: + set_state(c, CLOSED); + break; + } + + // Send RST + + struct hdr hdr; + + hdr.src = c->src; + hdr.dst = c->dst; + hdr.seq = c->snd.nxt; + hdr.ack = c->rcv.nxt; + hdr.wnd = 0; + hdr.ctl = RST; + hdr.aux = 0; + + print_packet(c, "send", &hdr, sizeof(hdr)); + c->utcp->send(c->utcp, &hdr, sizeof(hdr)); + return true; +} + +static void set_reapable(struct utcp_connection *c) { + set_buffer_storage(&c->sndbuf, NULL, min(c->sndbuf.maxsize, DEFAULT_MAXSNDBUFSIZE)); + set_buffer_storage(&c->rcvbuf, NULL, min(c->rcvbuf.maxsize, DEFAULT_MAXRCVBUFSIZE)); + + c->recv = NULL; + c->poll = NULL; + c->reapable = true; +} + +// Resets all connections, but does not invalidate connection handles +void utcp_reset_all_connections(struct utcp *utcp) { + if(!utcp) { + errno = EINVAL; + return; + } + + for(int i = 0; i < utcp->nconnections; i++) { + struct utcp_connection *c = utcp->connections[i]; + + if(c->reapable || c->state == CLOSED) { + continue; + } + + reset_connection(c); + + if(c->recv) { + errno = 0; + c->recv(c, NULL, 0); + } + + if(c->poll && !c->reapable) { + errno = 0; + c->poll(c, 0); + } + } + + return; +} + +int utcp_close(struct utcp_connection *c) { + if(utcp_shutdown(c, SHUT_RDWR) && errno != ENOTCONN) { + return -1; + } + + set_reapable(c); + return 0; +} + +int utcp_abort(struct utcp_connection *c) { + if(!reset_connection(c)) { + return -1; + } + + set_reapable(c); + return 0; +} + +/* Handle timeouts. + * One call to this function will loop through all connections, + * checking if something needs to be resent or not. + * The return value is the time to the next timeout in milliseconds, + * or maybe a negative value if the timeout is infinite. + */ +struct timespec utcp_timeout(struct utcp *utcp) { + struct timespec now; + clock_gettime(UTCP_CLOCK, &now); + struct timespec next = {now.tv_sec + 3600, now.tv_nsec}; + + for(int i = 0; i < utcp->nconnections; i++) { + struct utcp_connection *c = utcp->connections[i]; + + if(!c) { + continue; + } + + // delete connections that have been utcp_close()d. + if(c->state == CLOSED) { + if(c->reapable) { + debug(c, "reaping\n"); + free_connection(c); + i--; + } + + continue; + } + + if(timespec_isset(&c->conn_timeout) && timespec_lt(&c->conn_timeout, &now)) { + errno = ETIMEDOUT; + c->state = CLOSED; + buffer_clear(&c->sndbuf); + buffer_clear(&c->rcvbuf); + + if(c->recv) { + c->recv(c, NULL, 0); + } + + if(c->poll && !c->reapable) { + c->poll(c, 0); + } + + continue; + } + + if(timespec_isset(&c->rtrx_timeout) && timespec_lt(&c->rtrx_timeout, &now)) { + debug(c, "retransmitting after timeout\n"); + retransmit(c); + } + + if(c->poll) { + if((c->state == ESTABLISHED || c->state == CLOSE_WAIT) && c->do_poll) { + c->do_poll = false; + uint32_t len = buffer_free(&c->sndbuf); + + if(len) { + c->poll(c, len); + } + } else if(c->state == CLOSED) { + c->poll(c, 0); + } + } + + if(timespec_isset(&c->conn_timeout) && timespec_lt(&c->conn_timeout, &next)) { + next = c->conn_timeout; + } + + if(timespec_isset(&c->rtrx_timeout) && timespec_lt(&c->rtrx_timeout, &next)) { + next = c->rtrx_timeout; + } + } + + struct timespec diff; + + timespec_sub(&next, &now, &diff); + + return diff; +} + +bool utcp_is_active(struct utcp *utcp) { + if(!utcp) { + return false; + } + + for(int i = 0; i < utcp->nconnections; i++) + if(utcp->connections[i]->state != CLOSED && utcp->connections[i]->state != TIME_WAIT) { + return true; + } + + return false; +} + +struct utcp *utcp_init(utcp_accept_t accept, utcp_listen_t listen, utcp_send_t send, void *priv) { + if(!send) { + errno = EFAULT; + return NULL; + } + + struct utcp *utcp = calloc(1, sizeof(*utcp)); + + if(!utcp) { + return NULL; + } + + utcp_set_mtu(utcp, DEFAULT_MTU); + + if(!utcp->pkt) { + free(utcp); + return NULL; + } + + if(!CLOCK_GRANULARITY) { + struct timespec res; + clock_getres(UTCP_CLOCK, &res); + CLOCK_GRANULARITY = res.tv_sec * USEC_PER_SEC + res.tv_nsec / 1000; + } + + utcp->accept = accept; + utcp->listen = listen; + utcp->send = send; + utcp->priv = priv; + utcp->timeout = DEFAULT_USER_TIMEOUT; // sec + + return utcp; +} + +void utcp_exit(struct utcp *utcp) { + if(!utcp) { + return; + } + + for(int i = 0; i < utcp->nconnections; i++) { + struct utcp_connection *c = utcp->connections[i]; + + if(!c->reapable) { + buffer_clear(&c->sndbuf); + buffer_clear(&c->rcvbuf); + + if(c->recv) { + c->recv(c, NULL, 0); + } + + if(c->poll && !c->reapable) { + c->poll(c, 0); + } + } + + buffer_exit(&c->rcvbuf); + buffer_exit(&c->sndbuf); + free(c); + } + + free(utcp->connections); + free(utcp->pkt); + free(utcp); +} + +uint16_t utcp_get_mtu(struct utcp *utcp) { + return utcp ? utcp->mtu : 0; +} + +uint16_t utcp_get_mss(struct utcp *utcp) { + return utcp ? utcp->mss : 0; +} + +void utcp_set_mtu(struct utcp *utcp, uint16_t mtu) { + if(!utcp) { + return; + } + + if(mtu <= sizeof(struct hdr)) { + return; + } + + if(mtu > utcp->mtu) { + char *new = realloc(utcp->pkt, mtu + sizeof(struct hdr)); + + if(!new) { + return; + } + + utcp->pkt = new; + } + + utcp->mtu = mtu; + utcp->mss = mtu - sizeof(struct hdr); +} + +void utcp_reset_timers(struct utcp *utcp) { + if(!utcp) { + return; + } + + struct timespec now, then; + + clock_gettime(UTCP_CLOCK, &now); + + then = now; + + then.tv_sec += utcp->timeout; + + for(int i = 0; i < utcp->nconnections; i++) { + struct utcp_connection *c = utcp->connections[i]; + + if(c->reapable) { + continue; + } + + if(timespec_isset(&c->rtrx_timeout)) { + c->rtrx_timeout = now; + } + + if(timespec_isset(&c->conn_timeout)) { + c->conn_timeout = then; + } + + c->rtt_start.tv_sec = 0; + + if(c->rto > START_RTO) { + c->rto = START_RTO; + } + } +} + +int utcp_get_user_timeout(struct utcp *u) { + return u ? u->timeout : 0; +} + +void utcp_set_user_timeout(struct utcp *u, int timeout) { + if(u) { + u->timeout = timeout; + } +} + +size_t utcp_get_sndbuf(struct utcp_connection *c) { + return c ? c->sndbuf.maxsize : 0; +} + +size_t utcp_get_sndbuf_free(struct utcp_connection *c) { + if(!c) { + return 0; + } + + switch(c->state) { + case SYN_SENT: + case SYN_RECEIVED: + case ESTABLISHED: + case CLOSE_WAIT: + return buffer_free(&c->sndbuf); + + default: + return 0; + } +} + +void utcp_set_sndbuf(struct utcp_connection *c, void *data, size_t size) { + if(!c) { + return; + } + + set_buffer_storage(&c->sndbuf, data, size); + + c->do_poll = is_reliable(c) && buffer_free(&c->sndbuf); +} + +size_t utcp_get_rcvbuf(struct utcp_connection *c) { + return c ? c->rcvbuf.maxsize : 0; +} + +size_t utcp_get_rcvbuf_free(struct utcp_connection *c) { + if(c && (c->state == ESTABLISHED || c->state == CLOSE_WAIT)) { + return buffer_free(&c->rcvbuf); + } else { + return 0; + } +} + +void utcp_set_rcvbuf(struct utcp_connection *c, void *data, size_t size) { + if(!c) { + return; + } + + set_buffer_storage(&c->rcvbuf, data, size); +} + +size_t utcp_get_sendq(struct utcp_connection *c) { + return c->sndbuf.used; +} + +size_t utcp_get_recvq(struct utcp_connection *c) { + return c->rcvbuf.used; +} + +bool utcp_get_nodelay(struct utcp_connection *c) { + return c ? c->nodelay : false; +} + +void utcp_set_nodelay(struct utcp_connection *c, bool nodelay) { + if(c) { + c->nodelay = nodelay; + } +} + +bool utcp_get_keepalive(struct utcp_connection *c) { + return c ? c->keepalive : false; +} + +void utcp_set_keepalive(struct utcp_connection *c, bool keepalive) { + if(c) { + c->keepalive = keepalive; + } +} + +size_t utcp_get_outq(struct utcp_connection *c) { + return c ? seqdiff(c->snd.nxt, c->snd.una) : 0; +} + +void utcp_set_recv_cb(struct utcp_connection *c, utcp_recv_t recv) { + if(c) { + c->recv = recv; + } +} + +void utcp_set_poll_cb(struct utcp_connection *c, utcp_poll_t poll) { + if(c) { + c->poll = poll; + c->do_poll = is_reliable(c) && buffer_free(&c->sndbuf); + } +} + +void utcp_set_accept_cb(struct utcp *utcp, utcp_accept_t accept, utcp_listen_t listen) { + if(utcp) { + utcp->accept = accept; + utcp->listen = listen; + } +} + +void utcp_expect_data(struct utcp_connection *c, bool expect) { + if(!c || c->reapable) { + return; + } + + if(!(c->state == ESTABLISHED || c->state == FIN_WAIT_1 || c->state == FIN_WAIT_2)) { + return; + } + + if(expect) { + // If we expect data, start the connection timer. + if(!timespec_isset(&c->conn_timeout)) { + clock_gettime(UTCP_CLOCK, &c->conn_timeout); + c->conn_timeout.tv_sec += c->utcp->timeout; + } + } else { + // If we want to cancel expecting data, only clear the timer when there is no unACKed data. + if(c->snd.una == c->snd.last) { + timespec_clear(&c->conn_timeout); + } + } +} + +void utcp_set_flags(struct utcp_connection *c, uint32_t flags) { + c->flags &= ~UTCP_CHANGEABLE_FLAGS; + c->flags |= flags & UTCP_CHANGEABLE_FLAGS; +} + +void utcp_offline(struct utcp *utcp, bool offline) { + struct timespec now; + clock_gettime(UTCP_CLOCK, &now); + + for(int i = 0; i < utcp->nconnections; i++) { + struct utcp_connection *c = utcp->connections[i]; + + if(c->reapable) { + continue; + } + + utcp_expect_data(c, offline); + + if(!offline) { + if(timespec_isset(&c->rtrx_timeout)) { + c->rtrx_timeout = now; + } + + utcp->connections[i]->rtt_start.tv_sec = 0; + + if(c->rto > START_RTO) { + c->rto = START_RTO; + } + } + } +} + +void utcp_set_retransmit_cb(struct utcp *utcp, utcp_retransmit_t cb) { + utcp->retransmit = cb; +} + +void utcp_set_clock_granularity(long granularity) { + CLOCK_GRANULARITY = granularity; +} diff --git a/src/utcp.h b/src/utcp.h new file mode 100644 index 0000000..5de9364 --- /dev/null +++ b/src/utcp.h @@ -0,0 +1,128 @@ +/* + utcp.h -- Userspace TCP + Copyright (C) 2014 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef UTCP_H +#define UTCP_H + +#include +#include +#include +// TODO: Windows +#include + +#ifndef UTCP_INTERNAL +struct utcp { + void *priv; +}; + +struct utcp_connection { + void *priv; + struct utcp *const utcp; + const uint32_t flags; +}; +#else +struct utcp; +struct utcp_connection; +#endif + +#define UTCP_SHUT_RD 0 +#define UTCP_SHUT_WR 1 +#define UTCP_SHUT_RDWR 2 + +#define UTCP_ORDERED 1 +#define UTCP_RELIABLE 2 +#define UTCP_FRAMED 4 +#define UTCP_DROP_LATE 8 +#define UTCP_NO_PARTIAL 16 + +#define UTCP_TCP 3 +#define UTCP_UDP 0 +#define UTCP_CHANGEABLE_FLAGS 0x18U + +typedef bool (*utcp_listen_t)(struct utcp *utcp, uint16_t port); +typedef void (*utcp_accept_t)(struct utcp_connection *utcp_connection, uint16_t port); +typedef void (*utcp_retransmit_t)(struct utcp_connection *connection); + +typedef ssize_t (*utcp_send_t)(struct utcp *utcp, const void *data, size_t len); +typedef ssize_t (*utcp_recv_t)(struct utcp_connection *connection, const void *data, size_t len); + +typedef void (*utcp_poll_t)(struct utcp_connection *connection, size_t len); + +struct utcp *utcp_init(utcp_accept_t accept, utcp_listen_t listen, utcp_send_t send, void *priv); +void utcp_exit(struct utcp *utcp); + +struct utcp_connection *utcp_connect_ex(struct utcp *utcp, uint16_t port, utcp_recv_t recv, void *priv, uint32_t flags); +struct utcp_connection *utcp_connect(struct utcp *utcp, uint16_t port, utcp_recv_t recv, void *priv); +void utcp_accept(struct utcp_connection *utcp, utcp_recv_t recv, void *priv); +ssize_t utcp_send(struct utcp_connection *connection, const void *data, size_t len); +ssize_t utcp_recv(struct utcp *utcp, const void *data, size_t len); +int utcp_close(struct utcp_connection *connection); +int utcp_abort(struct utcp_connection *connection); +int utcp_shutdown(struct utcp_connection *connection, int how); +struct timespec utcp_timeout(struct utcp *utcp); +void utcp_set_recv_cb(struct utcp_connection *connection, utcp_recv_t recv); +void utcp_set_poll_cb(struct utcp_connection *connection, utcp_poll_t poll); +void utcp_set_accept_cb(struct utcp *utcp, utcp_accept_t accept, utcp_listen_t listen); +bool utcp_is_active(struct utcp *utcp); +void utcp_reset_all_connections(struct utcp *utcp); + +// Global socket options + +int utcp_get_user_timeout(struct utcp *utcp); +void utcp_set_user_timeout(struct utcp *utcp, int seconds); + +uint16_t utcp_get_mtu(struct utcp *utcp); +uint16_t utcp_get_mss(struct utcp *utcp); +void utcp_set_mtu(struct utcp *utcp, uint16_t mtu); + +void utcp_reset_timers(struct utcp *utcp); + +void utcp_offline(struct utcp *utcp, bool offline); +void utcp_set_retransmit_cb(struct utcp *utcp, utcp_retransmit_t retransmit); + +// Per-socket options + +size_t utcp_get_sndbuf(struct utcp_connection *connection); +void utcp_set_sndbuf(struct utcp_connection *connection, void *buf, size_t size); +size_t utcp_get_sndbuf_free(struct utcp_connection *connection); + +size_t utcp_get_rcvbuf(struct utcp_connection *connection); +void utcp_set_rcvbuf(struct utcp_connection *connection, void *buf, size_t size); +size_t utcp_get_rcvbuf_free(struct utcp_connection *connection); + +size_t utcp_get_sendq(struct utcp_connection *connection); +size_t utcp_get_recvq(struct utcp_connection *connection); + +bool utcp_get_nodelay(struct utcp_connection *connection); +void utcp_set_nodelay(struct utcp_connection *connection, bool nodelay); + +bool utcp_get_keepalive(struct utcp_connection *connection); +void utcp_set_keepalive(struct utcp_connection *connection, bool keepalive); + +size_t utcp_get_outq(struct utcp_connection *connection); + +void utcp_expect_data(struct utcp_connection *connection, bool expect); + +void utcp_set_flags(struct utcp_connection *connection, uint32_t flags); + +// Completely global options + +void utcp_set_clock_granularity(long granularity); + +#endif diff --git a/src/utcp_priv.h b/src/utcp_priv.h new file mode 100644 index 0000000..f96b92b --- /dev/null +++ b/src/utcp_priv.h @@ -0,0 +1,202 @@ +/* + utcp.h -- Userspace TCP + Copyright (C) 2014 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef UTCP_PRIV_H +#define UTCP_PRIV_H + +#define UTCP_INTERNAL +#include "utcp.h" + +#define PREP(l) char pkt[(l) + sizeof struct hdr]; struct hdr *hdr = &pkt; + +#define SYN 1 +#define ACK 2 +#define FIN 4 +#define RST 8 +#define MF 16 + +#define AUX_INIT 1 +#define AUX_FRAME 2 +#define AUX_SAK 3 +#define AUX_TIMESTAMP 4 + +#define NSACKS 4 +#define DEFAULT_SNDBUFSIZE 0 +#define DEFAULT_MAXSNDBUFSIZE 131072 +#define DEFAULT_RCVBUFSIZE 0 +#define DEFAULT_MAXRCVBUFSIZE 131072 + +#define MAX_UNRELIABLE_SIZE 16777215 +#define DEFAULT_MTU 1000 + +#define USEC_PER_SEC 1000000L +#define NSEC_PER_SEC 1000000000L +#define DEFAULT_USER_TIMEOUT 60 +#define START_RTO (1 * USEC_PER_SEC) +#define MAX_RTO (3 * USEC_PER_SEC) + +struct hdr { + uint16_t src; // Source port + uint16_t dst; // Destination port + uint32_t seq; // Sequence number + uint32_t ack; // Acknowledgement number + uint32_t wnd; // Window size + uint16_t ctl; // Flags (SYN, ACK, FIN, RST) + uint16_t aux; // other stuff +}; + +enum state { + CLOSED, + LISTEN, + SYN_SENT, + SYN_RECEIVED, + ESTABLISHED, + FIN_WAIT_1, + FIN_WAIT_2, + CLOSE_WAIT, + CLOSING, + LAST_ACK, + TIME_WAIT +}; + +static const char *strstate[] __attribute__((unused)) = { + [CLOSED] = "CLOSED", + [LISTEN] = "LISTEN", + [SYN_SENT] = "SYN_SENT", + [SYN_RECEIVED] = "SYN_RECEIVED", + [ESTABLISHED] = "ESTABLISHED", + [FIN_WAIT_1] = "FIN_WAIT_1", + [FIN_WAIT_2] = "FIN_WAIT_2", + [CLOSE_WAIT] = "CLOSE_WAIT", + [CLOSING] = "CLOSING", + [LAST_ACK] = "LAST_ACK", + [TIME_WAIT] = "TIME_WAIT" +}; + +struct buffer { + char *data; + uint32_t offset; + uint32_t used; + uint32_t size; + uint32_t maxsize; + bool external; +}; + +struct sack { + uint32_t offset; + uint32_t len; +}; + +struct utcp_connection { + void *priv; + struct utcp *utcp; + uint32_t flags; + + bool reapable; + bool do_poll; + + // Callbacks + + utcp_recv_t recv; + utcp_poll_t poll; + + // TCP State + + uint16_t src; + uint16_t dst; + enum state state; + + struct { + uint32_t una; + uint32_t nxt; + uint32_t wnd; + uint32_t iss; + + uint32_t last; + uint32_t cwnd; + uint32_t ssthresh; + } snd; + + struct { + uint32_t nxt; + uint32_t irs; + } rcv; + + int dupack; + + // Timers + + struct timespec conn_timeout; + struct timespec rtrx_timeout; + struct timespec rtt_start; + uint32_t rtt_seq; + + // RTT variables + + uint32_t srtt; // usec + uint32_t rttvar; // usec + uint32_t rto; // usec + + // Buffers + + uint32_t prev_free; + struct buffer sndbuf; + struct buffer rcvbuf; + struct sack sacks[NSACKS]; + + // Per-socket options + + bool nodelay; + bool keepalive; + bool shut_wr; + + // Congestion avoidance state + + struct timespec tlast; + uint64_t bandwidth; +}; + +struct utcp { + void *priv; + + // Callbacks + + utcp_accept_t accept; + utcp_listen_t listen; + utcp_retransmit_t retransmit; + utcp_send_t send; + + // Packet buffer + + void *pkt; + + // Global socket options + + uint16_t mtu; // The maximum size of a UTCP packet, including headers. + uint16_t mss; // The maximum size of the payload of a UTCP packet. + int timeout; // sec + + // Connection management + + struct utcp_connection **connections; + int nconnections; + int nallocated; +}; + +#endif diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..6e1c4cc --- /dev/null +++ b/src/utils.c @@ -0,0 +1,206 @@ +/* + utils.c -- gathering of some stupid small functions + Copyright (C) 2014 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "../src/logger.h" +#include "utils.h" + +static const char hexadecimals[] = "0123456789ABCDEF"; +static const char base64_original[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char base64_urlsafe[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; +static const char base64_decode[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + +static int charhex2bin(char c) { + if(isdigit(c)) { + return c - '0'; + } else { + return toupper(c) - 'A' + 10; + } +} + +int hex2bin(const char *src, void *vdst, int length) { + uint8_t *dst = vdst; + int i; + + for(i = 0; i < length && isxdigit(src[i * 2]) && isxdigit(src[i * 2 + 1]); i++) { + dst[i] = charhex2bin(src[i * 2]) * 16 + charhex2bin(src[i * 2 + 1]); + } + + return i; +} + +int bin2hex(const void *vsrc, char *dst, int length) { + const uint8_t *src = vsrc; + + for(int i = length - 1; i >= 0; i--) { + dst[i * 2 + 1] = hexadecimals[(unsigned char) src[i] & 15]; + dst[i * 2] = hexadecimals[(unsigned char) src[i] >> 4]; + } + + dst[length * 2] = 0; + return length * 2; +} + +int b64decode(const char *src, void *dst, int length) { + int i; + uint32_t triplet = 0; + unsigned char *udst = dst; + + for(i = 0; i < length / 3 * 4 && src[i]; i++) { + triplet |= base64_decode[src[i] & 0xff] << (6 * (i & 3)); + + if((i & 3) == 3) { + if(triplet & 0xff000000U) { + return 0; + } + + udst[0] = triplet & 0xff; + triplet >>= 8; + udst[1] = triplet & 0xff; + triplet >>= 8; + udst[2] = triplet; + triplet = 0; + udst += 3; + } + } + + if(triplet & 0xff000000U) { + return 0; + } + + if((i & 3) == 3) { + udst[0] = triplet & 0xff; + triplet >>= 8; + udst[1] = triplet & 0xff; + return i / 4 * 3 + 2; + } else if((i & 3) == 2) { + udst[0] = triplet & 0xff; + return i / 4 * 3 + 1; + } else { + return i / 4 * 3; + } +} + +static int b64encode_internal(const void *src, char *dst, int length, const char *alphabet) { + uint32_t triplet; + const unsigned char *usrc = src; + int si = length / 3 * 3; + int di = length / 3 * 4; + + switch(length % 3) { + case 2: + triplet = usrc[si] | usrc[si + 1] << 8; + dst[di] = alphabet[triplet & 63]; + triplet >>= 6; + dst[di + 1] = alphabet[triplet & 63]; + triplet >>= 6; + dst[di + 2] = alphabet[triplet]; + dst[di + 3] = 0; + length = di + 2; + break; + + case 1: + triplet = usrc[si]; + dst[di] = alphabet[triplet & 63]; + triplet >>= 6; + dst[di + 1] = alphabet[triplet]; + dst[di + 2] = 0; + length = di + 1; + break; + + default: + dst[di] = 0; + length = di; + break; + } + + while(si > 0) { + di -= 4; + si -= 3; + triplet = usrc[si] | usrc[si + 1] << 8 | usrc[si + 2] << 16; + dst[di] = alphabet[triplet & 63]; + triplet >>= 6; + dst[di + 1] = alphabet[triplet & 63]; + triplet >>= 6; + dst[di + 2] = alphabet[triplet & 63]; + triplet >>= 6; + dst[di + 3] = alphabet[triplet]; + } + + return length; +} + +int b64encode(const void *src, char *dst, int length) { + return b64encode_internal(src, dst, length, base64_original); +} + +int b64encode_urlsafe(const void *src, char *dst, int length) { + return b64encode_internal(src, dst, length, base64_urlsafe); +} + +#if defined(HAVE_MINGW) || defined(HAVE_CYGWIN) +#ifdef HAVE_CYGWIN +#include +#endif + +const char *winerror(int err) { + static char buf[1024], *ptr; + + ptr = buf + sprintf(buf, "(%d) ", err); + + if(!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), ptr, sizeof(buf) - (ptr - buf), NULL)) { + strncpy(buf, "(unable to format errormessage)", sizeof(buf)); + } + + if((ptr = strchr(buf, '\r'))) { + *ptr = '\0'; + } + + return buf; +} +#endif + +unsigned int bitfield_to_int(const void *bitfield, size_t size) { + unsigned int value = 0; + + if(size > sizeof(value)) { + size = sizeof(value); + } + + memcpy(&value, bitfield, size); + return value; +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..dfbb205 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,50 @@ +#ifndef MESHLINK_UTILS_H +#define MESHLINK_UTILS_H + +/* + utils.h -- header file for utils.c + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +int hex2bin(const char *src, void *dst, int length); +int bin2hex(const void *src, char *dst, int length); + +int b64encode(const void *src, char *dst, int length); +int b64encode_urlsafe(const void *src, char *dst, int length); +int b64decode(const char *src, void *dst, int length); + +#ifdef HAVE_MINGW +const char *winerror(int); +#define strerror(x) ((x)>0?strerror(x):winerror(GetLastError())) +#define sockerrno WSAGetLastError() +#define sockstrerror(x) winerror(x) +#define sockwouldblock(x) ((x) == WSAEWOULDBLOCK || (x) == WSAEINTR) +#define sockmsgsize(x) ((x) == WSAEMSGSIZE) +#define sockinprogress(x) ((x) == WSAEINPROGRESS || (x) == WSAEWOULDBLOCK) +#define sockinuse(x) ((x) == WSAEADDRINUSE) +#else +#define sockerrno errno +#define sockstrerror(x) strerror(x) +#define sockwouldblock(x) ((x) == EWOULDBLOCK || (x) == EINTR) +#define sockmsgsize(x) ((x) == EMSGSIZE) +#define sockinprogress(x) ((x) == EINPROGRESS) +#define sockinuse(x) ((x) == EADDRINUSE) +#endif + +unsigned int bitfield_to_int(const void *bitfield, size_t size) __attribute__((__warn_unused_result__)); + +#endif diff --git a/src/xalloc.h b/src/xalloc.h new file mode 100644 index 0000000..948723e --- /dev/null +++ b/src/xalloc.h @@ -0,0 +1,96 @@ +#ifndef MESHLINK_XALLOC_H +#define MESHLINK_XALLOC_H + +/* + xalloc.h -- malloc and related functions with out of memory checking + Copyright (C) 2014, 2017 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., Foundation, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +static inline void *xmalloc(size_t n) __attribute__((__malloc__)); +static inline void *xmalloc(size_t n) { + void *p = malloc(n); + + if(!p) { + abort(); + } + + return p; +} + +static inline void *xzalloc(size_t n) __attribute__((__malloc__)); +static inline void *xzalloc(size_t n) { + void *p = calloc(1, n); + + if(!p) { + abort(); + } + + return p; +} + +static inline void *xrealloc(void *p, size_t n) { + p = realloc(p, n); + + if(!p) { + abort(); + } + + return p; +} + +static inline char *xstrdup(const char *s) __attribute__((__malloc__)); +static inline char *xstrdup(const char *s) { + char *p = strdup(s); + + if(!p) { + abort(); + } + + return p; +} + +static inline int xvasprintf(char **strp, const char *fmt, va_list ap) { +#ifdef HAVE_MINGW + char buf[1024]; + int result = vsnprintf(buf, sizeof(buf), fmt, ap); + + if(result < 0) { + abort(); + } + + *strp = xstrdup(buf); +#else + int result = vasprintf(strp, fmt, ap); + + if(result < 0) { + abort(); + } + +#endif + return result; +} + +static inline int xasprintf(char **strp, const char *fmt, ...) __attribute__((__format__(printf, 2, 3))); +static inline int xasprintf(char **strp, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int result = xvasprintf(strp, fmt, ap); + va_end(ap); + return result; +} + +#endif diff --git a/src/xoshiro.c b/src/xoshiro.c new file mode 100644 index 0000000..c05e940 --- /dev/null +++ b/src/xoshiro.c @@ -0,0 +1,42 @@ +/* Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) + +To the extent possible under law, the author has dedicated all copyright +and related and neighboring rights to this software to the public domain +worldwide. This software is distributed without any warranty. + +See . */ + +#include +#include "xoshiro.h" + +/* This is xoshiro256** 1.0, one of our all-purpose, rock-solid + generators. It has excellent (sub-ns) speed, a state (256 bits) that is + large enough for any parallel application, and it passes all tests we + are aware of. + + For generating just floating-point numbers, xoshiro256+ is even faster. + + The state must be seeded so that it is not everywhere zero. If you have + a 64-bit seed, we suggest to seed a splitmix64 generator and use its + output to fill s. */ + +static inline uint64_t rotl(const uint64_t x, int k) { + return (x << k) | (x >> (64 - k)); +} + +uint64_t xoshiro(uint64_t s[4]) { + const uint64_t result = rotl(s[1] * 5, 7) * 9; + + const uint64_t t = s[1] << 17; + + s[2] ^= s[0]; + s[3] ^= s[1]; + s[1] ^= s[2]; + s[0] ^= s[3]; + + s[2] ^= t; + + s[3] = rotl(s[3], 45); + + return result; +} diff --git a/src/xoshiro.h b/src/xoshiro.h new file mode 100644 index 0000000..c771c7b --- /dev/null +++ b/src/xoshiro.h @@ -0,0 +1,6 @@ +#ifndef MESHLINK_XOSHIRO_H +#define MESHLINK_XOSHIRO_H + +uint64_t xoshiro(uint64_t s[4]) __attribute__((__warn_unused_result__)); + +#endif diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..b1dd28e --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,18 @@ +*.log +*.trs +/basic +/basicpp +/channels +/channels-cornercases +/channels-fork +/duplicate +/echo-fork +/encrypted +/ephemeral +/import-export +/invite-join +/sign-verify +/trio +/*.[0123456789] +/channels_aio_fd.in +/channels_aio_fd.out* diff --git a/test/Makefile.am b/test/Makefile.am new file mode 100644 index 0000000..65834bb --- /dev/null +++ b/test/Makefile.am @@ -0,0 +1,160 @@ +TESTS = \ + basic \ + basicpp \ + blacklist \ + channels \ + channels-aio \ + channels-aio-abort \ + channels-aio-cornercases \ + channels-aio-fd \ + channels-buffer-storage \ + channels-cornercases \ + channels-failure \ + channels-fork \ + channels-no-partial \ + channels-udp \ + channels-udp-cornercases \ + duplicate \ + encrypted \ + ephemeral \ + get-all-nodes \ + import-export \ + invite-join \ + meta-connections \ + sign-verify \ + storage-policy \ + trio \ + trio2 \ + utcp-benchmark \ + utcp-benchmark-stream + +TESTS += \ + api_set_node_status_cb + +if BLACKBOX_TESTS +SUBDIRS = blackbox +endif + +dist_check_SCRIPTS = $(TESTS) + +AM_CPPFLAGS = $(PTHREAD_CFLAGS) -I${top_srcdir}/src -iquote. -Wall +AM_LDFLAGS = $(PTHREAD_LIBS) + +check_PROGRAMS = \ + api_set_node_status_cb \ + basic \ + basicpp \ + blacklist \ + channels \ + channels-aio \ + channels-aio-abort \ + channels-aio-cornercases \ + channels-aio-fd \ + channels-buffer-storage \ + channels-cornercases \ + channels-failure \ + channels-fork \ + channels-no-partial \ + channels-udp \ + channels-udp-cornercases \ + duplicate \ + echo-fork \ + encrypted \ + ephemeral \ + get-all-nodes \ + import-export \ + invite-join \ + meta-connections \ + sign-verify \ + storage-policy \ + stream \ + trio \ + trio2 + +if INSTALL_TESTS +bin_PROGRAMS = $(check_PROGRAMS) +endif + +api_set_node_status_cb_SOURCES = api_set_node_status_cb.c utils.c utils.h +api_set_node_status_cb_LDADD = $(top_builddir)/src/libmeshlink.la + +basic_SOURCES = basic.c utils.c utils.h +basic_LDADD = $(top_builddir)/src/libmeshlink.la + +basicpp_SOURCES = basicpp.cpp utils.c utils.h +basicpp_LDADD = $(top_builddir)/src/libmeshlink.la + +blacklist_SOURCES = blacklist.c utils.c utils.h +blacklist_LDADD = $(top_builddir)/src/libmeshlink.la + +channels_SOURCES = channels.c utils.c utils.h +channels_LDADD = $(top_builddir)/src/libmeshlink.la + +channels_aio_SOURCES = channels-aio.c utils.c utils.h +channels_aio_LDADD = $(top_builddir)/src/libmeshlink.la + +channels_aio_abort_SOURCES = channels-aio-abort.c utils.c utils.h +channels_aio_abort_LDADD = $(top_builddir)/src/libmeshlink.la + +channels_aio_cornercases_SOURCES = channels-aio-cornercases.c utils.c utils.h +channels_aio_cornercases_LDADD = $(top_builddir)/src/libmeshlink.la + +channels_aio_fd_SOURCES = channels-aio-fd.c utils.c utils.h +channels_aio_fd_LDADD = $(top_builddir)/src/libmeshlink.la + +channels_buffer_storage_SOURCES = channels-buffer-storage.c utils.c utils.h +channels_buffer_storage_LDADD = $(top_builddir)/src/libmeshlink.la + +channels_no_partial_SOURCES = channels-no-partial.c utils.c utils.h +channels_no_partial_LDADD = $(top_builddir)/src/libmeshlink.la + +channels_failure_SOURCES = channels-failure.c utils.c utils.h +channels_failure_LDADD = $(top_builddir)/src/libmeshlink.la + +channels_fork_SOURCES = channels-fork.c utils.c utils.h +channels_fork_LDADD = $(top_builddir)/src/libmeshlink.la + +channels_cornercases_SOURCES = channels-cornercases.c utils.c utils.h +channels_cornercases_LDADD = $(top_builddir)/src/libmeshlink.la + +channels_udp_SOURCES = channels-udp.c utils.c utils.h +channels_udp_LDADD = $(top_builddir)/src/libmeshlink.la + +channels_udp_cornercases_SOURCES = channels-udp-cornercases.c utils.c utils.h +channels_udp_cornercases_LDADD = $(top_builddir)/src/libmeshlink.la + +duplicate_SOURCES = duplicate.c utils.c utils.h +duplicate_LDADD = $(top_builddir)/src/libmeshlink.la + +echo_fork_SOURCES = echo-fork.c utils.c utils.h +echo_fork_LDADD = $(top_builddir)/src/libmeshlink.la + +encrypted_SOURCES = encrypted.c utils.c utils.h +encrypted_LDADD = $(top_builddir)/src/libmeshlink.la + +ephemeral_SOURCES = ephemeral.c utils.c utils.h +ephemeral_LDADD = $(top_builddir)/src/libmeshlink.la + +get_all_nodes_SOURCES = get-all-nodes.c utils.c utils.h +get_all_nodes_LDADD = $(top_builddir)/src/libmeshlink.la + +import_export_SOURCES = import-export.c utils.c utils.h +import_export_LDADD = $(top_builddir)/src/libmeshlink.la + +invite_join_SOURCES = invite-join.c utils.c utils.h +invite_join_LDADD = $(top_builddir)/src/libmeshlink.la + +meta_connections_SOURCES = meta-connections.c netns_utils.c netns_utils.h utils.c utils.h +meta_connections_LDADD = $(top_builddir)/src/libmeshlink.la + +sign_verify_SOURCES = sign-verify.c utils.c utils.h +sign_verify_LDADD = $(top_builddir)/src/libmeshlink.la + +storage_policy_SOURCES = storage-policy.c utils.c utils.h +storage_policy_LDADD = $(top_builddir)/src/libmeshlink.la + +trio_SOURCES = trio.c utils.c utils.h +trio_LDADD = $(top_builddir)/src/libmeshlink.la + +trio2_SOURCES = trio2.c utils.c utils.h +trio2_LDADD = $(top_builddir)/src/libmeshlink.la diff --git a/test/api_set_node_status_cb.c b/test/api_set_node_status_cb.c new file mode 100644 index 0000000..7e5da19 --- /dev/null +++ b/test/api_set_node_status_cb.c @@ -0,0 +1,58 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include + +#include "meshlink.h" +#include "utils.h" + +static struct sync_flag a_reachable; +static struct sync_flag b_reachable; + +static void status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!reachable) { + return; + } + + if(!strcmp(node->name, "a")) { + set_sync_flag(&a_reachable, true); + } else if(!strcmp(node->name, "b")) { + set_sync_flag(&b_reachable, true); + } +} + +int main(void) { + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + init_sync_flag(&a_reachable); + init_sync_flag(&b_reachable); + + meshlink_handle_t *mesh1, *mesh2; + open_meshlink_pair_ephemeral(&mesh1, &mesh2, "api_set_node_status_cb"); + + // Test case #1: check that setting a valid status callback will cause it to be called + // when the node itself is started or stopped + + meshlink_set_node_status_cb(mesh1, status_cb); + assert(meshlink_start(mesh1)); + assert(wait_sync_flag(&a_reachable, 5)); + + // Test case #2: check that the status callback will be called when another peer is started + + assert(meshlink_start(mesh2)); + assert(wait_sync_flag(&b_reachable, 5)); + + // Test case #3: check that passing a NULL pointer for the mesh returns an error + + meshlink_errno = MESHLINK_OK; + meshlink_set_node_status_cb(NULL, status_cb); + assert(meshlink_errno == MESHLINK_EINVAL); + + // Done. + + close_meshlink_pair(mesh1, mesh2); +} diff --git a/test/basic.c b/test/basic.c new file mode 100644 index 0000000..004e718 --- /dev/null +++ b/test/basic.c @@ -0,0 +1,155 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "meshlink.h" +#include "utils.h" + +int main(void) { + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Check that the first time we need to supply a name + + assert(meshlink_destroy("basic_conf")); + + meshlink_handle_t *mesh = meshlink_open("basic_conf", NULL, "basic", DEV_CLASS_BACKBONE); + assert(!mesh); + + // Open a new meshlink instance. + + mesh = meshlink_open("basic_conf", "foo", "basic", DEV_CLASS_BACKBONE); + assert(mesh); + + // Check that we can't open a second instance of the same node. + + meshlink_handle_t *mesh2 = meshlink_open("basic_conf", "foo", "basic", DEV_CLASS_BACKBONE); + assert(!mesh2); + + // Check that we cannot destroy an instance that is in use. + + assert(!meshlink_destroy("basic_conf")); + + // Check that our own node exists. + + meshlink_node_t *self = meshlink_get_self(mesh); + assert(self); + assert(!strcmp(self->name, "foo")); + + // Check that we are not reachable. + + time_t last_reachable; + time_t last_unreachable; + assert(!meshlink_get_node_reachability(mesh, self, &last_reachable, &last_unreachable)); + assert(!last_reachable); + assert(!last_unreachable); + + // Start and stop the mesh. + + assert(meshlink_start(mesh)); + + // Check that we are now reachable + + assert(meshlink_get_node_reachability(mesh, self, &last_reachable, &last_unreachable)); + assert(last_reachable); + assert(!last_unreachable); + + meshlink_stop(mesh); + + // Check that we are no longer reachable. + + assert(!meshlink_get_node_reachability(mesh, self, &last_reachable, &last_unreachable)); + assert(last_reachable); + assert(last_unreachable); + + // Make sure we can start and stop the mesh again. + + assert(meshlink_start(mesh)); + assert(meshlink_start(mesh)); + meshlink_stop(mesh); + meshlink_stop(mesh); + + // Close the mesh and open it again, now with a different name parameter. + + meshlink_close(mesh); + mesh = meshlink_open("basic_conf", "bar", "basic", DEV_CLASS_BACKBONE); + assert(!mesh); + + // Open it without providing a name + + mesh = meshlink_open("basic_conf", NULL, "basic", DEV_CLASS_BACKBONE); + assert(mesh); + + self = meshlink_get_self(mesh); + assert(self); + assert(!strcmp(mesh->name, "foo")); + assert(!strcmp(self->name, "foo")); + + // Check that we remembered we were reachable + + assert(!meshlink_get_node_reachability(mesh, self, &last_reachable, &last_unreachable)); + assert(last_reachable); + assert(last_unreachable); + + // Check that the name is ignored now, and that we still are "foo". + + assert(!meshlink_get_node(mesh, "bar")); + self = meshlink_get_self(mesh); + assert(self); + assert(!strcmp(self->name, "foo")); + + // Start and stop the mesh. + + assert(meshlink_start(mesh)); + meshlink_stop(mesh); + meshlink_close(mesh); + + // Check that messing with the config directory will create a new instance. + + assert(unlink("basic_conf/current/meshlink.conf") == 0); + mesh = meshlink_open("basic_conf", "bar", "basic", DEV_CLASS_BACKBONE); + assert(mesh); + assert(!meshlink_get_node(mesh, "foo")); + self = meshlink_get_self(mesh); + assert(self); + assert(!strcmp(self->name, "bar")); + assert(access("basic_conf/new", X_OK) == -1 && errno == ENOENT); + meshlink_close(mesh); + + assert(rename("basic_conf/current", "basic_conf/new") == 0); + mesh = meshlink_open("basic_conf", "baz", "basic", DEV_CLASS_BACKBONE); + assert(mesh); + assert(!meshlink_get_node(mesh, "bar")); + self = meshlink_get_self(mesh); + assert(self); + assert(!strcmp(self->name, "baz")); + assert(access("basic_conf/new", X_OK) == -1 && errno == ENOENT); + meshlink_close(mesh); + + // Destroy the mesh. + + assert(meshlink_destroy("basic_conf")); + + // Check that the configuration directory is completely empty. + + DIR *dir = opendir("basic_conf"); + assert(dir); + struct dirent *ent; + + while((ent = readdir(dir))) { + assert(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")); + } + + closedir(dir); + + // Check that we can destroy it again. + + assert(meshlink_destroy("basic_conf")); +} diff --git a/test/basicpp.cpp b/test/basicpp.cpp new file mode 100644 index 0000000..c0550ec --- /dev/null +++ b/test/basicpp.cpp @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include +#include + +#include "meshlink++.h" + +using namespace std; + +int main(void) { + assert(meshlink::destroy("basicpp_conf")); + + // Open a new meshlink instance. + + { + meshlink::mesh mesh("basicpp_conf", "foo", "basicpp", DEV_CLASS_BACKBONE); + assert(mesh.isOpen()); + + // Check that our own node exists. + + meshlink::node *self = mesh.get_self(); + assert(self); + assert(!strcmp(self->name, "foo")); + + // Disable local discovery. + + mesh.enable_discovery(false); + + // Start and stop the mesh. + + assert(mesh.start()); + mesh.stop(); + + // Make sure we can start and stop the mesh again. + + assert(mesh.start()); + mesh.stop(); + + // Close the mesh and open it again, now with a different name parameter. + + mesh.close(); + assert(!mesh.open("basicpp_conf", "bar", "basicpp", DEV_CLASS_BACKBONE)); + + // Open it without giving a name. + + assert(mesh.open("basicpp_conf", nullptr, "basicpp", DEV_CLASS_BACKBONE)); + + // Check that the name is ignored now, and that we still are "foo". + + self = mesh.get_self(); + assert(self); + assert(!strcmp(self->name, "foo")); + + // Start and stop the mesh. + + mesh.enable_discovery(false); + + assert(mesh.start()); + mesh.stop(); + } + + // Destroy the mesh. + + assert(meshlink::destroy("basicpp_conf")); + + DIR *dir = opendir("basicpp_conf"); + assert(dir); + struct dirent *ent; + while((ent = readdir(dir))) { + assert(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")); + } + closedir(dir); + + return 0; +} diff --git a/test/blackbox/.gitignore b/test/blackbox/.gitignore new file mode 100644 index 0000000..4190b42 --- /dev/null +++ b/test/blackbox/.gitignore @@ -0,0 +1,5 @@ +gen_invite +node_sim_peer +node_sim_nut +node_sim_relay +interfaces diff --git a/test/blackbox/Makefile.am b/test/blackbox/Makefile.am new file mode 100644 index 0000000..df5792b --- /dev/null +++ b/test/blackbox/Makefile.am @@ -0,0 +1,25 @@ +check_PROGRAMS = gen_invite +SUBDIRS = \ + run_blackbox_tests \ + test_case_channel_conn_01 \ + test_case_channel_conn_02 \ + test_case_channel_conn_03 \ + test_case_channel_conn_04 \ + test_case_channel_conn_05 \ + test_case_channel_conn_06 \ + test_case_channel_conn_07 \ + test_case_channel_conn_08 \ + test_case_meta_conn_01 \ + test_case_meta_conn_02 \ + test_case_meta_conn_03 \ + test_case_meta_conn_04 \ + test_case_meta_conn_05 \ + test_cases_submesh01 \ + test_cases_submesh02 \ + test_cases_submesh03 \ + test_cases_submesh04 + + +gen_invite_SOURCES = util/gen_invite.c common/common_handlers.c common/test_step.c common/mesh_event_handler.c +gen_invite_LDADD = ../../src/libmeshlink.la +gen_invite_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/common/common_handlers.c b/test/blackbox/common/common_handlers.c new file mode 100644 index 0000000..e728f43 --- /dev/null +++ b/test/blackbox/common/common_handlers.c @@ -0,0 +1,189 @@ +/* + common_handlers.c -- Implementation of common callback handling and signal handling + functions for black box tests + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "test_step.h" +#include "common_handlers.h" + +char *lxc_bridge = NULL; +black_box_state_t *state_ptr = NULL; + +bool meta_conn_status[10]; + +bool test_running; + +static int meshlink_get_node_in_container(const char *name) { + int i; + + for(i = 0; i < state_ptr->num_nodes; i++) { + if(!strcasecmp(state_ptr->node_names[i], name)) { + return i; + break; + } + } + + return -1; +} + +void mesh_close_signal_handler(int signum) { + (void)signum; + test_running = false; + + exit(EXIT_SUCCESS); +} + +void setup_signals(void) { + test_running = true; + signal(SIGTERM, mesh_close_signal_handler); +} + +/* Return the IP Address of the Interface 'if_name' + The caller is responsible for freeing the dynamically allocated string that is returned */ +char *get_ip(const char *if_name) { + struct ifaddrs *ifaddr, *ifa; + char *ip; + int family; + + ip = malloc(NI_MAXHOST); + assert(ip); + assert(getifaddrs(&ifaddr) != -1); + + for(ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if(ifa->ifa_addr == NULL) { + continue; + } + + family = ifa->ifa_addr->sa_family; + + if(family == AF_INET && !strcmp(ifa->ifa_name, if_name)) { + assert(!getnameinfo(ifa->ifa_addr, (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6), ip, NI_MAXHOST, NULL, 0, NI_NUMERICHOST)); + break; + } + } + + return ip; +} + +/* Return the IP Address of the Interface 'if_name' + The caller is responsible for freeing the dynamically allocated string that is returned */ +char *get_netmask(const char *if_name) { + struct ifaddrs *ifaddr, *ifa; + char *ip; + int family; + + ip = malloc(NI_MAXHOST); + assert(ip); + assert(getifaddrs(&ifaddr) != -1); + + for(ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if(ifa->ifa_addr == NULL) { + continue; + } + + family = ifa->ifa_addr->sa_family; + + if(family == AF_INET && !strcmp(ifa->ifa_name, if_name)) { + assert(!getnameinfo(ifa->ifa_netmask, (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6), ip, NI_MAXHOST, NULL, 0, NI_NUMERICHOST)); + break; + } + } + + return ip; +} + +/* Change the IP Address of an interface */ +void set_ip(const char *if_name, const char *new_ip) { + char set_ip_cmd[100]; + assert(snprintf(set_ip_cmd, sizeof(set_ip_cmd), "ifconfig %s %s", if_name, new_ip) >= 0); + assert(system(set_ip_cmd) == 0); +} + +/* Change the Netmask of an interface */ +void set_netmask(const char *if_name, const char *new_netmask) { + char set_mask_cmd[100]; + assert(snprintf(set_mask_cmd, sizeof(set_mask_cmd), "ifconfig %s netmask %s", if_name, new_netmask) >= 0); + assert(system(set_mask_cmd) == 0); +} + +/* Bring a network interface down (before making changes such as the IP Address) */ +void stop_nw_intf(const char *if_name) { + char nw_down_cmd[100]; + assert(snprintf(nw_down_cmd, sizeof(nw_down_cmd), "ifconfig %s down", if_name) >= 0); + assert(system(nw_down_cmd) == 0); +} + +/* Bring a network interface up (after bringing it down and making changes such as + the IP Address) */ +void start_nw_intf(const char *if_name) { + char nw_up_cmd[100]; + assert(snprintf(nw_up_cmd, sizeof(nw_up_cmd), "ifconfig %s up", if_name) >= 0); + assert(system(nw_up_cmd) == 0); +} + +void meshlink_callback_node_status(meshlink_handle_t *mesh, meshlink_node_t *node, + bool reachable) { + (void)mesh; + fprintf(stderr, "Node %s became %s\n", node->name, (reachable) ? "reachable" : "unreachable"); +} + +void meshlink_callback_logger(meshlink_handle_t *mesh, meshlink_log_level_t level, + const char *text) { + (void)mesh; + (void)level; + + fprintf(stderr, "meshlink>> %s\n", text); + + if(state_ptr) { + bool status; + char name[100]; + + if(sscanf(text, "Connection with %s activated", name) == 1) { + status = true; + } else if(sscanf(text, "Already connected to %s", name) == 1) { + status = true; + } else if(sscanf(text, "Connection closed by %s", name) == 1) { + status = false; + } else if(sscanf(text, "Closing connection with %s", name) == 1) { + status = false; + } else { + return; + } + + int i = meshlink_get_node_in_container(name); + assert(i != -1); + meta_conn_status[i] = status; + } +} diff --git a/test/blackbox/common/common_handlers.h b/test/blackbox/common/common_handlers.h new file mode 100644 index 0000000..7c5fbd2 --- /dev/null +++ b/test/blackbox/common/common_handlers.h @@ -0,0 +1,53 @@ +/* + common_handlers.h -- Declarations of common callback handlers and signal handlers for + black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef COMMON_HANDLERS_H +#define COMMON_HANDLERS_H + +#include "common_types.h" + +#define PRINT_TEST_CASE_HEADER() if(state_ptr) \ + fprintf(stderr, "[ %s ]\n", state_ptr->test_case_name) +#define PRINT_TEST_CASE_MSG(...) if(state_ptr) \ + do { \ + fprintf(stderr, "[ %s ] ", \ + state_ptr->test_case_name); \ + fprintf(stderr, __VA_ARGS__); \ + } while(0) + +extern bool meta_conn_status[]; +extern bool node_reachable_status[]; +extern bool test_running; + +char *get_ip(const char *if_name); +char *get_netmask(const char *if_name); +void stop_nw_intf(const char *if_name); +void start_nw_intf(const char *if_name); +void set_ip(const char *if_name, const char *new_ip); +void set_netmask(const char *if_name, const char *new_ip); +void mesh_close_signal_handler(int a); +void mesh_stop_start_signal_handler(int a); +void setup_signals(void); +void meshlink_callback_node_status(meshlink_handle_t *mesh, meshlink_node_t *node, + bool reachable); +void meshlink_callback_logger(meshlink_handle_t *mesh, meshlink_log_level_t level, + const char *text); + +#endif // COMMON_HANDLERS_H diff --git a/test/blackbox/common/common_types.h b/test/blackbox/common/common_types.h new file mode 100644 index 0000000..edea650 --- /dev/null +++ b/test/blackbox/common/common_types.h @@ -0,0 +1,58 @@ +/* + common_types.h -- Declarations of common types used in Black Box Testing + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef COMMON_TYPES_H +#define COMMON_TYPES_H + +#include +#include "../../../src/meshlink.h" + +#define NUT_NODE_NAME "nut" + +#define LXC_UTIL_REL_PATH "test/blackbox/util" +#define LXC_RENAME_SCRIPT "lxc_rename.sh" +#define LXC_RUN_SCRIPT "lxc_run.sh" +#define LXC_COPY_SCRIPT "lxc_copy_file.sh" +#define LXC_BUILD_SCRIPT "build_container.sh" +#define LXC_NAT_BUILD "nat.sh" +#define LXC_NAT_FULL_CONE "full_cone.sh" +#define LXC_NAT_DESTROY "nat_destroy.sh" + +typedef struct black_box_state { + char *test_case_name; + char **node_names; + int num_nodes; + bool test_result; +} black_box_state_t; + +extern char *lxc_bridge; + +extern char *eth_if_name; + +extern black_box_state_t *state_ptr; + +extern char *meshlink_root_path; + +/* Meshlink Mesh Handle */ +extern meshlink_handle_t *mesh_handle; + +/* Flag to indicate if Mesh is running */ +extern bool mesh_started; + +#endif // COMMON_TYPES_H diff --git a/test/blackbox/common/containers.c b/test/blackbox/common/containers.c new file mode 100644 index 0000000..4f3f881 --- /dev/null +++ b/test/blackbox/common/containers.c @@ -0,0 +1,1134 @@ +/* + containers.h -- Container Management API + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include "containers.h" +#include "common_handlers.h" + +char *lxc_path = NULL; +extern char *choose_arch; +static char container_ips[10][100]; + +/* Return the handle to an existing container after finding it by container name */ +struct lxc_container *find_container(const char *name) { + struct lxc_container **test_containers; + char **container_names; + int num_containers, i; + + assert((num_containers = list_all_containers(lxc_path, &container_names, + &test_containers)) != -1); + + for(i = 0; i < num_containers; i++) { + if(strcmp(container_names[i], name) == 0) { + return test_containers[i]; + } + } + + return NULL; +} + +/* Rename a Container */ +void rename_container(const char *old_name, const char *new_name) { + char rename_command[200]; + int rename_status; + struct lxc_container *old_container; + + /* Stop the old container if its still running */ + assert((old_container = find_container(old_name))); + old_container->shutdown(old_container, CONTAINER_SHUTDOWN_TIMEOUT); + /* Call stop() in case shutdown() fails - one of these two will always succeed */ + old_container->stop(old_container); + /* Rename the Container */ + /* TO DO: Perform this operation using the LXC API - currently does not work via the API, + need to investigate and determine why it doesn't work, and make it work */ + assert(snprintf(rename_command, sizeof(rename_command), + "%s/" LXC_UTIL_REL_PATH "/" LXC_RENAME_SCRIPT " %s %s %s", meshlink_root_path, lxc_path, + old_name, new_name) >= 0); + rename_status = system(rename_command); + PRINT_TEST_CASE_MSG("Container '%s' rename status: %d\n", old_name, rename_status); + assert(rename_status == 0); +} + +char *run_in_container(const char *cmd, const char *container_name, bool daemonize) { + char container_find_name[1000]; + struct lxc_container *container; + + assert(cmd); + assert(container_name); + assert(snprintf(container_find_name, sizeof(container_find_name), "%s_%s", + state_ptr->test_case_name, container_name) >= 0); + assert((container = find_container(container_find_name))); + + return run_in_container_ex(cmd, container, daemonize); +} + +char *execute_in_container(const char *cmd, const char *container_name, bool daemonize) { + struct lxc_container *container; + + assert(cmd); + assert(container_name); + assert((container = find_container(container_name))); + + return run_in_container_ex(cmd, container, daemonize); +} + +/* Run 'cmd' inside the Container created for 'node' and return the first line of the output + or NULL if there is no output - useful when, for example, a meshlink invite is generated + by a node running inside a Container + 'cmd' is run as a daemon if 'daemonize' is true - this mode is useful for running node + simulations in Containers + The caller is responsible for freeing the returned string */ +char *run_in_container_ex(const char *cmd, struct lxc_container *container, bool daemonize) { + char *output = NULL; + size_t output_len = 0; + + /* Run the command within the Container, either as a daemon or foreground process */ + /* TO DO: Perform this operation using the LXC API - currently does not work using the API + Need to determine why it doesn't work, and make it work */ + if(daemonize) { + char run_script_path[100]; + char *attach_argv[4]; + + assert(snprintf(run_script_path, sizeof(run_script_path), "%s/" LXC_UTIL_REL_PATH "/" LXC_RUN_SCRIPT, + meshlink_root_path) >= 0); + attach_argv[0] = run_script_path; + attach_argv[1] = (char *)cmd; + attach_argv[2] = container->name; + attach_argv[3] = NULL; + + /* To daemonize, create a child process and detach it from its parent (this program) */ + if(fork() == 0) { + assert(daemon(1, 0) != -1); // Detach from the parent process + assert(execv(attach_argv[0], attach_argv) != -1); // Run exec() in the child process + } + } else { + char *attach_command; + size_t attach_command_len; + int i; + attach_command_len = strlen(meshlink_root_path) + strlen(LXC_UTIL_REL_PATH) + strlen(LXC_RUN_SCRIPT) + strlen(cmd) + strlen(container->name) + 10; + attach_command = malloc(attach_command_len); + assert(attach_command); + + assert(snprintf(attach_command, attach_command_len, + "%s/" LXC_UTIL_REL_PATH "/" LXC_RUN_SCRIPT " \"%s\" %s", meshlink_root_path, cmd, + container->name) >= 0); + FILE *attach_fp; + assert((attach_fp = popen(attach_command, "r"))); + free(attach_command); + /* If the command has an output, strip out any trailing carriage returns or newlines and + return it, otherwise return NULL */ + output = NULL; + output_len = 0; + + if(getline(&output, &output_len, attach_fp) != -1) { + i = strlen(output) - 1; + + while(output[i] == '\n' || output[i] == '\r') { + i--; + } + + output[i + 1] = '\0'; + } else { + free(output); + output = NULL; + } + + assert(pclose(attach_fp) != -1); + } + + return output; +} + +/* Wait for a starting Container to obtain an IP Address, then save that IP for future use */ +void container_wait_ip(int node) { + char container_name[100]; + char *ip; + + assert(snprintf(container_name, sizeof(container_name), "%s_%s", state_ptr->test_case_name, + state_ptr->node_names[node]) >= 0); + ip = container_wait_ip_ex(container_name); + + strncpy(container_ips[node], ip, sizeof(container_ips[node])); // Save the IP for future use + PRINT_TEST_CASE_MSG("Node '%s' has IP Address %s\n", state_ptr->node_names[node], + container_ips[node]); + free(ip); +} + +char *container_wait_ip_ex(const char *container_name) { + struct lxc_container *test_container; + char lxcls_command[200]; + char *ip; + size_t ip_len; + bool ip_found; + int i; + int timeout; + FILE *lxcls_fp; + + assert((test_container = find_container(container_name))); + assert(snprintf(lxcls_command, sizeof(lxcls_command), + "lxc-ls -f | grep %s | tr -s ' ' | cut -d ' ' -f 5", test_container->name) >= 0); + PRINT_TEST_CASE_MSG("Waiting for Container '%s' to acquire IP\n", test_container->name); + assert((ip = malloc(20))); + ip_len = sizeof(ip); + ip_found = false; + timeout = 60; + + while(!ip_found && timeout) { + assert((lxcls_fp = popen(lxcls_command, "r"))); // Run command + assert(getline((char **)&ip, &ip_len, lxcls_fp) != -1); // Read its output + /* Strip newlines and carriage returns from output */ + i = strlen(ip) - 1; + + while(ip[i] == '\n' || ip[i] == '\r') { + i--; + } + + ip[i + 1] = '\0'; + ip_found = (strcmp(ip, "-") != 0); // If the output is not "-", IP has been acquired + assert(pclose(lxcls_fp) != -1); + sleep(1); + timeout--; + } + + // Fail if IP cannot be read + assert(timeout); + + return ip; +} + +/* Create all required test containers */ +void create_containers(const char *node_names[], int num_nodes) { + int i; + char container_name[100]; + int create_status, snapshot_status, snap_restore_status; + struct lxc_container *first_container = NULL; + + for(i = 0; i < num_nodes; i++) { + assert(snprintf(container_name, sizeof(container_name), "run_%s", node_names[i]) >= 0); + + /* If this is the first Container, create it otherwise restore the snapshot saved + for the first Container to create an additional Container */ + if(i == 0) { + assert((first_container = lxc_container_new(container_name, NULL))); + assert(!first_container->is_defined(first_container)); + create_status = first_container->createl(first_container, "download", NULL, NULL, + LXC_CREATE_QUIET, "-d", "ubuntu", "-r", "trusty", "-a", choose_arch, NULL); + fprintf(stderr, "Container '%s' create status: %d - %s\n", container_name, + first_container->error_num, first_container->error_string); + assert(create_status); + snapshot_status = first_container->snapshot(first_container, NULL); + fprintf(stderr, "Container '%s' snapshot status: %d - %s\n", container_name, + first_container->error_num, first_container->error_string); + assert(snapshot_status != -1); + } else { + assert(first_container); + snap_restore_status = first_container->snapshot_restore(first_container, "snap0", + container_name); + fprintf(stderr, "Snapshot restore to Container '%s' status: %d - %s\n", container_name, + first_container->error_num, first_container->error_string); + assert(snap_restore_status); + } + } +} + +/* Setup Containers required for a test + This function should always be invoked in a CMocka context + after setting the state of the test case to an instance of black_box_state_t */ +void setup_containers(void **state) { + black_box_state_t *test_state = (black_box_state_t *)(*state); + int i; + char build_command[200]; + struct lxc_container *test_container, *new_container; + char container_find_name[100]; + char container_new_name[100]; + int create_status, build_status; + + PRINT_TEST_CASE_HEADER(); + + for(i = 0; i < test_state->num_nodes; i++) { + /* Find the run_ Container or create it if it doesn't exist */ + assert(snprintf(container_find_name, sizeof(container_find_name), "run_%s", + test_state->node_names[i]) >= 0); + + if(!(test_container = find_container(container_find_name))) { + assert((test_container = lxc_container_new(container_find_name, NULL))); + assert(!test_container->is_defined(test_container)); + create_status = test_container->createl(test_container, "download", NULL, NULL, + LXC_CREATE_QUIET, "-d", "ubuntu", "-r", "trusty", "-a", choose_arch, NULL); + PRINT_TEST_CASE_MSG("Container '%s' create status: %d - %s\n", container_find_name, + test_container->error_num, test_container->error_string); + assert(create_status); + } + + /* Stop the Container if it's running */ + test_container->shutdown(test_container, CONTAINER_SHUTDOWN_TIMEOUT); + /* Call stop() in case shutdown() fails + One of these two calls will always succeed */ + test_container->stop(test_container); + /* Rename the Container to make it specific to this test case, + if a Container with the target name already exists, skip this step */ + assert(snprintf(container_new_name, sizeof(container_new_name), "%s_%s", + test_state->test_case_name, test_state->node_names[i]) >= 0); + + if(!(new_container = find_container(container_new_name))) { + rename_container(test_container->name, container_new_name); + assert((new_container = find_container(container_new_name))); + } + + /* Start the Container */ + assert(new_container->start(new_container, 0, NULL)); + /* Build the Container by copying required files into it */ + assert(snprintf(build_command, sizeof(build_command), + "%s/" LXC_UTIL_REL_PATH "/" LXC_BUILD_SCRIPT " %s %s %s +x >/dev/null", + meshlink_root_path, test_state->test_case_name, test_state->node_names[i], + meshlink_root_path) >= 0); + build_status = system(build_command); + PRINT_TEST_CASE_MSG("Container '%s' build Status: %d\n", new_container->name, + build_status); + assert(build_status == 0); + /* Restart the Container after building it and wait for it to acquire an IP */ + new_container->shutdown(new_container, CONTAINER_SHUTDOWN_TIMEOUT); + new_container->stop(new_container); + new_container->start(new_container, 0, NULL); + container_wait_ip(i); + } +} + +/* Destroy all Containers with names containing 'run_' - Containers saved for debugging will + have names beginning with test_case_ ; 'run_' is reserved for temporary Containers + intended to be re-used for the next test */ +void destroy_containers(void) { + struct lxc_container **test_containers; + char **container_names; + int num_containers, i; + + assert((num_containers = list_all_containers(lxc_path, &container_names, + &test_containers)) != -1); + + for(i = 0; i < num_containers; i++) { + if(strstr(container_names[i], "run_")) { + fprintf(stderr, "Destroying Container '%s'\n", container_names[i]); + /* Stop the Container - it cannot be destroyed till it is stopped */ + test_containers[i]->shutdown(test_containers[i], CONTAINER_SHUTDOWN_TIMEOUT); + /* Call stop() in case shutdown() fails + One of these two calls will always succeed */ + test_containers[i]->stop(test_containers[i]); + /* Destroy the Container */ + test_containers[i]->destroy(test_containers[i]); + /* Call destroy_with_snapshots() in case destroy() fails + one of these two calls will always succeed */ + test_containers[i]->destroy_with_snapshots(test_containers[i]); + } + } +} + +/* Restart all the Containers being used in the current test case i.e. Containers with + names beginning with _ */ +void restart_all_containers(void) { + char container_name[100]; + struct lxc_container *test_container; + int i; + + for(i = 0; i < state_ptr->num_nodes; i++) { + /* Shutdown, then start the Container, then wait for it to acquire an IP Address */ + assert(snprintf(container_name, sizeof(container_name), "%s_%s", state_ptr->test_case_name, + state_ptr->node_names[i]) >= 0); + assert((test_container = find_container(container_name))); + test_container->shutdown(test_container, CONTAINER_SHUTDOWN_TIMEOUT); + test_container->stop(test_container); + test_container->start(test_container, 0, NULL); + container_wait_ip(i); + } +} + +/* Run the gen_invite command inside the 'inviter' container to generate an invite + for 'invitee', and return the generated invite which is output on the terminal */ +char *invite_in_container(const char *inviter, const char *invitee) { + char invite_command[200]; + char *invite_url; + + assert(snprintf(invite_command, sizeof(invite_command), + "LD_LIBRARY_PATH=/home/ubuntu/test/.libs /home/ubuntu/test/gen_invite %s %s " + "2> gen_invite.log", inviter, invitee) >= 0); + assert((invite_url = run_in_container(invite_command, inviter, false))); + PRINT_TEST_CASE_MSG("Invite Generated from '%s' to '%s': %s\n", inviter, + invitee, invite_url); + + return invite_url; +} + +/* Run the gen_invite command inside the 'inviter' container to generate an invite + for 'invitee' belonging to pparticular submesh, and return the generated invite + which is output on the terminal */ +char *submesh_invite_in_container(const char *inviter, const char *invitee, const char *submesh) { + char invite_command[200]; + char *invite_url; + + assert(snprintf(invite_command, sizeof(invite_command), + "LD_LIBRARY_PATH=/home/ubuntu/test/.libs /home/ubuntu/test/gen_invite %s %s %s " + "2> gen_invite.log", inviter, invitee, submesh) >= 0); + assert((invite_url = run_in_container(invite_command, inviter, false))); + PRINT_TEST_CASE_MSG("Invite Generated from '%s' to '%s': %s\n", inviter, + invitee, invite_url); + + return invite_url; +} + +/* Run the node_sim_ program inside the 'node''s container */ +void node_sim_in_container(const char *node, const char *device_class, const char *invite_url) { + char *node_sim_command; + size_t node_sim_command_len; + + node_sim_command_len = 500 + (invite_url ? strlen(invite_url) : 0); + node_sim_command = calloc(1, node_sim_command_len); + assert(node_sim_command); + assert(snprintf(node_sim_command, node_sim_command_len, + "LD_LIBRARY_PATH=/home/ubuntu/test/.libs /home/ubuntu/test/node_sim_%s %s %s %s " + "1>&2 2>> node_sim_%s.log", node, node, device_class, + (invite_url) ? invite_url : "", node) >= 0); + run_in_container(node_sim_command, node, true); + PRINT_TEST_CASE_MSG("node_sim_%s started in Container\n", node); + + free(node_sim_command); +} + +/* Run the node_sim_ program inside the 'node''s container with event handling capable */ +void node_sim_in_container_event(const char *node, const char *device_class, + const char *invite_url, const char *clientId, const char *import) { + char *node_sim_command; + size_t node_sim_command_len; + + assert(node && device_class && clientId && import); + node_sim_command_len = 500 + (invite_url ? strlen(invite_url) : 0); + node_sim_command = calloc(1, node_sim_command_len); + assert(node_sim_command); + assert(snprintf(node_sim_command, node_sim_command_len, + "LD_LIBRARY_PATH=/home/ubuntu/test/.libs /home/ubuntu/test/node_sim_%s %s %s %s %s %s " + "1>&2 2>> node_sim_%s.log", node, node, device_class, + clientId, import, (invite_url) ? invite_url : "", node) >= 0); + run_in_container(node_sim_command, node, true); + PRINT_TEST_CASE_MSG("node_sim_%s(Client Id :%s) started in Container with event handling\n%s\n", + node, clientId, node_sim_command); + + free(node_sim_command); +} + +/* Run the node_step.sh script inside the 'node''s container to send the 'sig' signal to the + node_sim program in the container */ +void node_step_in_container(const char *node, const char *sig) { + char node_step_command[1000]; + + assert(snprintf(node_step_command, sizeof(node_step_command), + "/home/ubuntu/test/node_step.sh lt-node_sim_%s %s 1>&2 2> node_step.log", + node, sig) >= 0); + run_in_container(node_step_command, node, false); + PRINT_TEST_CASE_MSG("Signal %s sent to node_sim_%s\n", sig, node); +} + +/* Change the IP Address of the Container running 'node' + Changes begin from X.X.X.254 and continue iteratively till an available address is found */ +void change_ip(int node) { + char *gateway_addr; + char new_ip[20] = ""; + char *netmask; + char *last_dot_in_ip; + int last_ip_byte = 254; + FILE *if_fp; + char copy_command[200]; + char container_name[100]; + struct lxc_container *container; + int copy_file_stat; + + /* Get IP Address of LXC Bridge Interface - this will be set up as the Gateway Address + of the Static IP assigned to the Container */ + assert((gateway_addr = get_ip(lxc_bridge))); + /* Get Netmask of LXC Brdige Interface */ + assert((netmask = get_netmask(lxc_bridge))); + + /* Replace last byte of Container's IP with 254 to form the new Container IP */ + assert(container_ips[node]); + strncpy(new_ip, container_ips[node], sizeof(new_ip) - 1); + assert((last_dot_in_ip = strrchr(new_ip, '.'))); + assert(snprintf(last_dot_in_ip + 1, 4, "%d", last_ip_byte) >= 0); + + /* Check that the new IP does not match the Container's existing IP + if it does, iterate till it doesn't */ + /* TO DO: Make sure the IP does not conflict with any other running Container */ + while(strcmp(new_ip, container_ips[node]) == 0) { + last_ip_byte--; + assert(snprintf(last_dot_in_ip + 1, 4, "%d", last_ip_byte) >= 0); + } + + /* Create new 'interfaces' file for Container */ + assert((if_fp = fopen("interfaces", "w"))); + fprintf(if_fp, "auto lo\n"); + fprintf(if_fp, "iface lo inet loopback\n"); + fprintf(if_fp, "\n"); + fprintf(if_fp, "auto eth0\n"); + fprintf(if_fp, "iface eth0 inet static\n"); + fprintf(if_fp, "\taddress %s\n", new_ip); + fprintf(if_fp, "\tnetmask %s\n", netmask); + fprintf(if_fp, "\tgateway %s\n", gateway_addr); + assert(fclose(if_fp) != EOF); + + /* Copy 'interfaces' file into Container's /etc/network path */ + assert(snprintf(copy_command, sizeof(copy_command), + "%s/" LXC_UTIL_REL_PATH "/" LXC_COPY_SCRIPT " interfaces %s_%s /etc/network/interfaces", + meshlink_root_path, state_ptr->test_case_name, state_ptr->node_names[node]) >= 0); + copy_file_stat = system(copy_command); + PRINT_TEST_CASE_MSG("Container '%s_%s' 'interfaces' file copy status: %d\n", + state_ptr->test_case_name, state_ptr->node_names[node], copy_file_stat); + assert(copy_file_stat == 0); + + /* Restart Container to apply new IP Address */ + assert(snprintf(container_name, sizeof(container_name), "%s_%s", state_ptr->test_case_name, + state_ptr->node_names[node]) >= 0); + assert((container = find_container(container_name))); + container->shutdown(container, CONTAINER_SHUTDOWN_TIMEOUT); + /* Call stop() in case shutdown() fails + One of these two calls with always succeed */ + container->stop(container); + assert(container->start(container, 0, NULL)); + + strncpy(container_ips[node], new_ip, sizeof(container_ips[node])); // Save the new IP Address + PRINT_TEST_CASE_MSG("Node '%s' IP Address changed to %s\n", state_ptr->node_names[node], + container_ips[node]); +} + +char **get_container_interface_ips(const char *container_name, const char *interface_name) { + char **ips; + struct lxc_container *container = find_container(container_name); + assert(container); + + char **interfaces = container->get_interfaces(container); + assert(interfaces); + + int i; + ips = NULL; + + for(i = 0; interfaces[i]; i++) { + if(!strcasecmp(interfaces[i], interface_name)) { + ips = container->get_ips(container, interface_name, "inet", 0); + assert(ips); + break; + } + } + + free(interfaces); + + return ips; +} + +/* Install an app in a container */ +void install_in_container(const char *node, const char *app) { + char install_cmd[100]; + + assert(snprintf(install_cmd, sizeof(install_cmd), + "apt-get install %s -y >> /dev/null", app) >= 0); + run_in_container(install_cmd, node, false); + // TODO: Check in container whether app has installed or not with a timeout + sleep(10); +} + +/* Return container's IP address */ +char *get_container_ip(const char *node_name) { + int node = -1, i; + + for(i = 0; i < state_ptr->num_nodes; i++) { + if(!strcasecmp(state_ptr->node_names[i], node_name)) { + node = i; + break; + } + } + + if(i == state_ptr->num_nodes) { + return NULL; + } + + char *ip = strdup(container_ips[node]); + assert(ip); + + return ip; +} + +/* Simulate a network failure by adding NAT rule in the container with it's IP address */ +void block_node_ip(const char *node) { + char block_cmd[100]; + char *node_ip; + + node_ip = get_container_ip(node); + assert(node_ip); + assert(snprintf(block_cmd, sizeof(block_cmd), "iptables -A OUTPUT -p all -s %s -j DROP", node_ip) >= 0); + run_in_container(block_cmd, node, false); + + assert(snprintf(block_cmd, sizeof(block_cmd), "iptables -A INPUT -p all -s %s -j DROP", node_ip) >= 0); + run_in_container(block_cmd, node, false); + + assert(snprintf(block_cmd, sizeof(block_cmd), "iptables -A FORWARD -p all -s %s -j DROP", node_ip) >= 0); + run_in_container(block_cmd, node, false); + + free(node_ip); +} + +void accept_port_rule(const char *node, const char *chain, const char *protocol, int port) { + char block_cmd[100]; + + assert(port >= 0 && port < 65536); + assert(!strcmp(chain, "INPUT") || !strcmp(chain, "FORWARD") || !strcmp(chain, "OUTPUT")); + assert(!strcmp(protocol, "all") || !strcmp(protocol, "tcp") || !strcmp(protocol, "udp") || !strcmp(protocol, "icmp")); + assert(snprintf(block_cmd, sizeof(block_cmd), "iptables -A %s -p %s --dport %d -j ACCEPT", chain, protocol, port) >= 0); + run_in_container(block_cmd, node, false); +} + +/* Restore the network that was blocked before by the NAT rule with it's own IP address */ +void unblock_node_ip(const char *node) { + char unblock_cmd[100]; + char *node_ip; + + node_ip = get_container_ip(node); + assert(node_ip); + assert(snprintf(unblock_cmd, sizeof(unblock_cmd), "iptables -D OUTPUT -p all -s %s -j DROP", node_ip) >= 0); + run_in_container(unblock_cmd, node, false); + + assert(snprintf(unblock_cmd, sizeof(unblock_cmd), "iptables -D INPUT -p all -s %s -j DROP", node_ip) >= 0); + run_in_container(unblock_cmd, node, false); + + assert(snprintf(unblock_cmd, sizeof(unblock_cmd), "iptables -D FORWARD -p all -s %s -j DROP", node_ip) >= 0); + run_in_container(unblock_cmd, node, false); +} + +char *block_icmp(const char *container_name) { + char block_cmd[500]; + assert(container_name); + assert(snprintf(block_cmd, sizeof(block_cmd), "iptables -A FORWARD -p icmp -j DROP") >= 0); + return execute_in_container(block_cmd, container_name, false); +} + +char *unblock_icmp(const char *container_name) { + char block_cmd[500]; + assert(container_name); + assert(snprintf(block_cmd, sizeof(block_cmd), "iptables -D FORWARD -p icmp -j DROP") >= 0); + return execute_in_container(block_cmd, container_name, false); +} + +char *change_container_mtu(const char *container_name, const char *interface_name, int mtu) { + char cmd[500]; + assert(container_name); + assert(snprintf(cmd, sizeof(cmd), "ifconfig %s mtu %d", interface_name, mtu) >= 0); + return execute_in_container(cmd, container_name, false); +} + +char *flush_conntrack(const char *container_name) { + assert(container_name); + + return execute_in_container("conntrack -F", container_name, false); +} + +void flush_nat_rules(const char *container_name, const char *chain) { + char *ret; + char flush_cmd[500]; + + assert(container_name); + assert(snprintf(flush_cmd, sizeof(flush_cmd), "iptables -F %s", chain ? chain : "") >= 0); + ret = execute_in_container("iptables -F", container_name, false); + assert(ret == NULL); +} + +void add_full_cone_nat_rules(const char *container_name, const char *pub_interface, const char *priv_interface_listen_address) { + char nat_cmd[500]; + + char **pub_interface_ips = get_container_interface_ips(container_name, pub_interface); + assert(pub_interface_ips[0]); + char *pub_interface_ip = pub_interface_ips[0]; + + assert(snprintf(nat_cmd, sizeof(nat_cmd), + "%s/" LXC_UTIL_REL_PATH "/" LXC_NAT_FULL_CONE " %s %s %s %s >/dev/null", + meshlink_root_path, container_name, pub_interface, pub_interface_ip, priv_interface_listen_address) >= 0); + assert(system(nat_cmd) == 0); + free(pub_interface_ips); +} + +/* Create a NAT and a bridge, bridge connected to NAT and containers to be NATed can be switched + to the NAT bridge from lxcbr0 */ +void nat_create(const char *nat_name, const char *nat_bridge, int nat_type) { + (void)nat_type; + + char build_command[200]; + assert(snprintf(build_command, sizeof(build_command), + "%s/" LXC_UTIL_REL_PATH "/" LXC_NAT_BUILD " %s %s %s >/dev/stderr", + meshlink_root_path, nat_name, nat_bridge, meshlink_root_path) >= 0); + assert(system(build_command) == 0); +} + +void nat_destroy(const char *nat_name) { + char build_command[200]; + assert(snprintf(build_command, sizeof(build_command), + "%s/" LXC_UTIL_REL_PATH "/" LXC_NAT_DESTROY " %s +x >/dev/null", + meshlink_root_path, nat_name) >= 0); + assert(system(build_command) == 0); +} + +/* Switches a container from current bridge to a new bridge */ +void container_switch_bridge(const char *container_name, char *lxc_conf_path, const char *current_bridge, const char *new_bridge) { + char config_path[500]; + char buffer[500]; + struct lxc_container *container; + char *lxc_path_temp; + char *ip; + + PRINT_TEST_CASE_MSG("Switiching container %s from bridge '%s' to bridge '%s'", container_name, current_bridge, new_bridge); + lxc_path_temp = lxc_path; + lxc_path = lxc_conf_path; + container = find_container(container_name); + assert(container); + lxc_path = lxc_path_temp; + assert(snprintf(config_path, sizeof(config_path), "%s/%s/config", lxc_conf_path, container_name) >= 0); + FILE *fp = fopen(config_path, "r"); + assert(fp); + FILE *fp_temp = fopen(".temp_file", "w"); + assert(fp_temp); + + int net_no; + + while((fgets(buffer, sizeof(buffer), fp)) != NULL) { + if(sscanf(buffer, "lxc.net.%d.link", &net_no) == 1) { + char *ptr; + int len; + + if((ptr = strstr(buffer, current_bridge)) != NULL) { + len = strlen(current_bridge); + + if(((*(ptr - 1) == ' ') || (*(ptr - 1) == '\t') || (*(ptr - 1) == '=')) && ((ptr[len] == '\n') || (ptr[len] == '\t') || (ptr[len] == '\0') || (ptr[len] == ' '))) { + sprintf(buffer, "lxc.net.%d.link = %s\n", net_no, new_bridge); + } + } + } + + fputs(buffer, fp_temp); + } + + fclose(fp_temp); + fclose(fp); + remove(config_path); + rename(".temp_file", config_path); + + /* Restart the Container after building it and wait for it to acquire an IP */ + char cmd[200]; + int sys_ret; + assert(snprintf(cmd, sizeof(cmd), "lxc-stop %s", container_name) >= 0); + sys_ret = system(cmd); + assert(snprintf(cmd, sizeof(cmd), "lxc-start %s", container_name) >= 0); + sys_ret = system(cmd); + assert(sys_ret == 0); + ip = container_wait_ip_ex(container_name); + PRINT_TEST_CASE_MSG("Obtained IP address: %s for container %s after switching bridge", ip, container_name); + free(ip); +} + +/* Takes bridgeName as input parameter and creates a bridge */ +void create_bridge(const char *bridgeName) { + char command[100] = "brctl addbr "; + strcat(command, bridgeName); + int create_bridge_status = system(command); + assert(create_bridge_status == 0); + PRINT_TEST_CASE_MSG("%s bridge created\n", bridgeName); +} + +/* Add interface for the bridge created */ +void add_interface(const char *bridgeName, const char *interfaceName) { + char command[100] = "brctl addif "; + char cmd[100] = "dhclient "; + + strcat(command, bridgeName); + strcat(command, " "); + strcat(command, interfaceName); + int addif_status = system(command); + assert(addif_status == 0); + strcat(cmd, bridgeName); + int dhclient_status = system(cmd); + assert(dhclient_status == 0); + PRINT_TEST_CASE_MSG("Added interface for %s\n", bridgeName); +} + +/* Create a veth pair and bring them up */ +void add_veth_pair(const char *vethName1, const char *vethName2) { + char command[100] = "ip link add "; + char upCommand1[100] = "ip link set "; + char upCommand2[100] = "ip link set "; + + strcat(command, vethName1); + strcat(command, " type veth peer name "); + strcat(command, vethName2); + int link_add_status = system(command); + assert(link_add_status == 0); + strcat(upCommand1, vethName1); + strcat(upCommand1, " up"); + int link_set_veth1_status = system(upCommand1); + assert(link_set_veth1_status == 0); + strcat(upCommand2, vethName2); + strcat(upCommand2, " up"); + int link_set_veth2_status = system(upCommand2); + assert(link_set_veth2_status == 0); + PRINT_TEST_CASE_MSG("Added veth pairs %s and %s\n", vethName1, vethName2); +} + +/* Bring the interface up for the bridge created */ +void bring_if_up(const char *bridgeName) { + char command[300] = "ifconfig "; + strcat(command, bridgeName); + strcat(command, " up"); + int if_up_status = system(command); + assert(if_up_status == 0); + sleep(2); + PRINT_TEST_CASE_MSG("Interface brought up for %s created\n", bridgeName); +} + +/** + * Replace all occurrences of a given a word in string. + */ +void replaceAll(char *str, const char *oldWord, const char *newWord) { + char *pos, temp[BUFSIZ]; + int index = 0; + int owlen; + owlen = strlen(oldWord); + + while((pos = strstr(str, oldWord)) != NULL) { + strcpy(temp, str); + index = pos - str; + str[index] = '\0'; + strcat(str, newWord); + strcat(str, temp + index + owlen); + } +} + +/* Switches the bridge for a given container */ +void switch_bridge(const char *containerName, const char *currentBridge, const char *newBridge) { + char command[100] = "lxc-stop -n "; + char command_start[100] = "lxc-start -n "; + PRINT_TEST_CASE_MSG("Switching %s container to %s\n", containerName, newBridge); + strcat(command, containerName); + strcat(command_start, containerName); + int container_stop_status = system(command); + assert(container_stop_status == 0); + sleep(2); + FILE *fPtr; + FILE *fTemp; + char path[300] = "/var/lib/lxc/"; + strcat(path, containerName); + strcat(path, "/config"); + + char buffer[BUFSIZ]; + /* Open all required files */ + fPtr = fopen(path, "r"); + fTemp = fopen("replace.tmp", "w"); + + if(fPtr == NULL || fTemp == NULL) { + PRINT_TEST_CASE_MSG("\nUnable to open file.\n"); + PRINT_TEST_CASE_MSG("Please check whether file exists and you have read/write privilege.\n"); + exit(EXIT_SUCCESS); + } + + while((fgets(buffer, BUFSIZ, fPtr)) != NULL) { + replaceAll(buffer, currentBridge, newBridge); + fputs(buffer, fTemp); + } + + fclose(fPtr); + fclose(fTemp); + remove(path); + rename("replace.tmp", path); + PRINT_TEST_CASE_MSG("Switching procedure done successfully\n"); + int container_start_status = system(command_start); + assert(container_start_status == 0); + sleep(2); +} + +/* Bring the interface down for the bridge created */ +void bring_if_down(const char *bridgeName) { + char command[300] = "ip link set dev "; + strcat(command, bridgeName); + strcat(command, " down"); + int if_down_status = system(command); + assert(if_down_status == 0); + PRINT_TEST_CASE_MSG("Interface brought down for %s created\n", bridgeName); +} + +/* Delete interface for the bridge created */ +void del_interface(const char *bridgeName, const char *interfaceName) { + char command[300] = "brctl delif "; + strcat(command, bridgeName); + strcat(command, interfaceName); + int if_delete_status = system(command); + assert(if_delete_status == 0); + PRINT_TEST_CASE_MSG("Deleted interface for %s\n", bridgeName); +} + +/* Takes bridgeName as input parameter and deletes a bridge */ +void delete_bridge(const char *bridgeName) { + bring_if_down(bridgeName); + char command[300] = "brctl delbr "; + strcat(command, bridgeName); + int bridge_delete = system(command); + assert(bridge_delete == 0); + PRINT_TEST_CASE_MSG("%s bridge deleted\n", bridgeName); + sleep(2); +} + +/* Creates container on a specified bridge with added interface */ +void create_container_on_bridge(const char *containerName, const char *bridgeName, const char *ifName) { + char command[100] = "lxc-create -t download -n "; + char cmd[100] = " -- -d ubuntu -r trusty -a "; + char start[100] = "lxc-start -n "; + FILE *fPtr; + char path[300] = "/var/lib/lxc/"; + strcat(path, containerName); + strcat(path, "/config"); + strcat(command, containerName); + strcat(command, cmd); + strcat(command, choose_arch); + int container_create_status = system(command); + assert(container_create_status == 0); + sleep(3); + assert((fPtr = fopen(path, "a+"))); + fprintf(fPtr, "lxc.net.0.name = eth0\n"); + fprintf(fPtr, "\n"); + fprintf(fPtr, "lxc.net.1.type = veth\n"); + fprintf(fPtr, "lxc.net.1.flags = up\n"); + fprintf(fPtr, "lxc.net.1.link = %s\n", bridgeName); + fprintf(fPtr, "lxc.net.1.name = %s\n", ifName); + fprintf(fPtr, "lxc.net.1.hwaddr = 00:16:3e:ab:xx:xx\n"); + fclose(fPtr); + strcat(start, containerName); + int container_start_status = system(start); + assert(container_start_status == 0); + sleep(3); + PRINT_TEST_CASE_MSG("Created %s on %s with interface name %s\n", containerName, bridgeName, ifName); +} + +/* Configures dnsmasq and iptables for the specified container with inputs of listen address and dhcp range */ +void config_dnsmasq(const char *containerName, const char *ifName, const char *listenAddress, const char *dhcpRange) { + char command[500] = "echo \"apt-get install dnsmasq iptables -y\" | lxc-attach -n "; + strcat(command, containerName); + strcat(command, " --"); + int iptables_install_status = system(command); + assert(iptables_install_status == 0); + sleep(5); + char com1[300] = "echo \"echo \"interface=eth1\" >> /etc/dnsmasq.conf\" | lxc-attach -n "; + strcat(com1, containerName); + strcat(com1, " --"); + int dnsmasq_status = system(com1); + assert(dnsmasq_status == 0); + sleep(5); + char com2[300] = "echo \"echo \"bind-interfaces\" >> /etc/dnsmasq.conf\" | lxc-attach -n "; + strcat(com2, containerName); + strcat(com2, " --"); + dnsmasq_status = system(com2); + assert(dnsmasq_status == 0); + sleep(5); + char com3[300] = "echo \"echo \"listen-address="; + strcat(com3, listenAddress); + strcat(com3, "\" >> /etc/dnsmasq.conf\" | lxc-attach -n "); + strcat(com3, containerName); + strcat(com3, " --"); + dnsmasq_status = system(com3); + assert(dnsmasq_status == 0); + sleep(5); + char com4[300] = "echo \"echo \"dhcp-range="; + strcat(com4, dhcpRange); + strcat(com4, "\" >> /etc/dnsmasq.conf\" | lxc-attach -n "); + strcat(com4, containerName); + strcat(com4, " --"); + dnsmasq_status = system(com4); + assert(dnsmasq_status == 0); + sleep(5); + char cmd[300] = "echo \"ifconfig "; + strcat(cmd, ifName); + strcat(cmd, " "); + strcat(cmd, listenAddress); + strcat(cmd, " netmask 255.255.255.0 up\" | lxc-attach -n "); + strcat(cmd, containerName); + strcat(cmd, " --"); + dnsmasq_status = system(cmd); + assert(dnsmasq_status == 0); + sleep(2); + char com[500] = "echo \"service dnsmasq restart >> /dev/null\" | lxc-attach -n "; + strcat(com, containerName); + strcat(com, " --"); + dnsmasq_status = system(com); + assert(dnsmasq_status == 0); + sleep(2); + PRINT_TEST_CASE_MSG("Configured dnsmasq in %s with interface name %s, listen-address = %s, dhcp-range = %s\n", containerName, ifName, listenAddress, dhcpRange); +} + +/* Configure the NAT rules inside the container */ +void config_nat(const char *containerName, const char *listenAddress) { + char *last_dot_in_ip; + int last_ip_byte = 0; + char new_ip[300] = {0}; + strncpy(new_ip, listenAddress, sizeof(new_ip) - 1); + assert((last_dot_in_ip = strrchr(new_ip, '.'))); + assert(snprintf(last_dot_in_ip + 1, 4, "%d", last_ip_byte) >= 0); + char comd[300] = "echo \"iptables -t nat -A POSTROUTING -s "; + strcat(comd, new_ip); + strcat(comd, "/24 ! -d "); + strcat(comd, new_ip); + strcat(comd, "/24 -j MASQUERADE\" | lxc-attach -n "); + strcat(comd, containerName); + strcat(comd, " --"); + int conf_nat_status = system(comd); + assert(conf_nat_status == 0); + sleep(2); + PRINT_TEST_CASE_MSG("Configured NAT on %s\n", containerName); +} + +/* Creates a NAT layer on a specified bridge with certain dhcp range to allocate ips for nodes */ +void create_nat_layer(const char *containerName, const char *bridgeName, const char *ifName, const char *listenAddress, char *dhcpRange) { + create_bridge(bridgeName); + bring_if_up(bridgeName); + create_container_on_bridge(containerName, bridgeName, ifName); + config_dnsmasq(containerName, ifName, listenAddress, dhcpRange); + config_nat(containerName, listenAddress); + PRINT_TEST_CASE_MSG("NAT layer created with %s\n", containerName); +} + +/* Destroys the NAT layer created */ +void destroy_nat_layer(const char *containerName, const char *bridgeName) { + bring_if_down(bridgeName); + delete_bridge(bridgeName); + char command[100] = "lxc-stop -n "; + strcat(command, containerName); + int container_stop_status = system(command); + assert(container_stop_status == 0); + char destroy[100] = "lxc-destroy -n "; + strcat(destroy, containerName); + strcat(destroy, " -s"); + int container_destroy_status = system(destroy); + assert(container_destroy_status == 0); + PRINT_TEST_CASE_MSG("NAT layer destroyed with %s\n", containerName); +} + +/* Add incoming firewall rules for ipv4 addresses with packet type and port number */ +void incoming_firewall_ipv4(const char *packetType, int portNumber) { + char buf[5]; + snprintf(buf, sizeof(buf), "%d", portNumber); + assert(system("iptables -F") == 0); + assert(system("iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT") == 0); + assert(system("iptables -A INPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT") == 0); + char command[100] = "iptables -A INPUT -p "; + strcat(command, packetType); + strcat(command, " --dport "); + strcat(command, buf); + strcat(command, " -j ACCEPT"); + assert(system(command) == 0); + sleep(2); + assert(system("iptables -A INPUT -j DROP") == 0); + PRINT_TEST_CASE_MSG("Firewall for incoming requests added on IPv4"); + assert(system("iptables -L") == 0); +} + +/* Add incoming firewall rules for ipv6 addresses with packet type and port number */ +void incoming_firewall_ipv6(const char *packetType, int portNumber) { + char buf[5]; + snprintf(buf, sizeof(buf), "%d", portNumber); + assert(system("ip6tables -F") == 0); + assert(system("ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT") == 0); + assert(system("ip6tables -A INPUT -s ::1 -d ::1 -j ACCEPT") == 0); + char command[100] = "ip6tables -A INPUT -p "; + strcat(command, packetType); + strcat(command, " --dport "); + strcat(command, buf); + strcat(command, " -j ACCEPT"); + assert(system(command) == 0); + sleep(2); + assert(system("ip6tables -A INPUT -j DROP") == 0); + PRINT_TEST_CASE_MSG("Firewall for incoming requests added on IPv6"); + assert(system("ip6tables -L") == 0); +} + +/* Add outgoing firewall rules for ipv4 addresses with packet type and port number */ +void outgoing_firewall_ipv4(const char *packetType, int portNumber) { + char buf[5]; + snprintf(buf, sizeof(buf), "%d", portNumber); + assert(system("iptables -F") == 0); + assert(system("iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT") == 0); + assert(system("iptables -A OUTPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT") == 0); + char command[100] = "iptables -A OUTPUT -p "; + strcat(command, packetType); + strcat(command, " --dport "); + strcat(command, buf); + strcat(command, " -j ACCEPT"); + assert(system(command) == 0); + sleep(2); + assert(system("iptables -A OUTPUT -j DROP") == 0); + PRINT_TEST_CASE_MSG("Firewall for outgoing requests added on IPv4"); + assert(system("iptables -L") == 0); +} + +/* Add outgoing firewall rules for ipv6 addresses with packet type and port number */ +void outgoing_firewall_ipv6(const char *packetType, int portNumber) { + char buf[5]; + snprintf(buf, sizeof(buf), "%d", portNumber); + assert(system("ip6tables -F") == 0); + assert(system("ip6tables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT") == 0); + assert(system("ip6tables -A OUTPUT -s ::1 -d ::1 -j ACCEPT") == 0); + char command[100] = "ip6tables -A OUTPUT -p "; + strcat(command, packetType); + strcat(command, " --dport "); + strcat(command, buf); + strcat(command, " -j ACCEPT"); + assert(system(command) == 0); + sleep(2); + assert(system("ip6tables -A OUTPUT -j DROP") == 0); + PRINT_TEST_CASE_MSG("Firewall for outgoing requests added on IPv6"); + assert(system("ip6tables -L") == 0); +} + +void bridge_add(const char *bridge_name) { + char cmd[500]; + + assert(bridge_name); + assert(snprintf(cmd, sizeof(cmd), "brctl addbr %s", bridge_name) >= 0); + assert(system(cmd) == 0); + assert(snprintf(cmd, sizeof(cmd), "ifconfig %s up", bridge_name) >= 0); + assert(system(cmd) == 0); +} + +void bridge_delete(const char *bridge_name) { + char cmd[500]; + + assert(bridge_name); + assert(snprintf(cmd, sizeof(cmd), "brctl delbr %s", bridge_name) >= 0); + assert(system(cmd) == 0); +} + +void bridge_add_interface(const char *bridge_name, const char *interface_name) { + char cmd[500]; + + assert(bridge_name || interface_name); + assert(snprintf(cmd, sizeof(cmd), "brctl addif %s %s", bridge_name, interface_name) >= 0); + assert(system(cmd) == 0); +} diff --git a/test/blackbox/common/containers.h b/test/blackbox/common/containers.h new file mode 100644 index 0000000..c807bbf --- /dev/null +++ b/test/blackbox/common/containers.h @@ -0,0 +1,84 @@ +/* + containers.h -- Declarations for Container Management API + Copyright (C) 2018 Guus Sliepen + Manav Kumar Mehta + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef CONTAINERS_H +#define CONTAINERS_H + +#include + +#define DAEMON_ARGV_LEN 2000 +#define CONTAINER_SHUTDOWN_TIMEOUT 5 + +#define DHCP_RANGE "172.16.0.2,172.16.255.254,12h" +#define PUB_INTERFACE "eth0" +#define PRIV_INTERFACE "eth1" +#define LISTEN_ADDRESS "172.16.0.1" +#define NET_MASK "255.255.255.0" +#define SUBNET_MASK "172.16.0.0/24" + +#define FULLCONE_NAT 1 +#define ADDRESS_RESTRICTED_NAT 2 +#define PORT_RESTRICTED_NAT 3 +#define SYMMERTIC_NAT 4 + +extern char *lxc_path; + +extern struct lxc_container *find_container(const char *name); +extern void rename_container(const char *old_name, const char *new_name); +extern char *run_in_container(const char *cmd, const char *node, bool daemonize); +extern void container_wait_ip(int node); +extern void create_containers(const char *node_names[], int num_nodes); +extern void setup_containers(void **state); +extern void destroy_containers(void); +extern void restart_all_containers(void); +extern char *invite_in_container(const char *inviter, const char *invitee); +extern char *submesh_invite_in_container(const char *inviter, const char *invitee, const char *submesh); +extern void node_sim_in_container(const char *node, const char *device_class, const char *invite_url); +extern void node_sim_in_container_event(const char *node, const char *device_class, + const char *invite_url, const char *clientId, const char *import); +extern void node_step_in_container(const char *node, const char *sig); +extern void change_ip(int node); + +extern char *get_container_ip(const char *node_name); +extern void install_in_container(const char *node, const char *app); +extern void unblock_node_ip(const char *node); +extern void block_node_ip(const char *node); +extern void accept_port_rule(const char *node, const char *chain, const char *protocol, int port); +extern void nat_create(const char *nat_name, const char *nat_bridge, int nat_type); +extern void container_switch_bridge(const char *container_name, char *lxc_conf_path, const char *current_bridge, const char *new_bridge); +extern void bridge_add(const char *bridge_name); +extern void bridge_delete(const char *bridge_name); +extern void bridge_add_interface(const char *bridge_name, const char *interface_name); + +extern void nat_destroy(const char *nat_name); +extern char *run_in_container_ex(const char *cmd, struct lxc_container *container, bool daemonize); +extern char *execute_in_container(const char *cmd, const char *container_name, bool daemonize); +extern char *block_icmp(const char *container_name); +extern char *unblock_icmp(const char *container_name); +extern char *change_container_mtu(const char *container_name, const char *interface_name, int mtu); +extern char *flush_conntrack(const char *container_name); + +extern char **get_container_interface_ips(const char *container_name, const char *interface_name); +extern void flush_nat_rules(const char *container_name, const char *chain); +extern void add_full_cone_nat_rules(const char *container_name, const char *pub_interface, const char *priv_interface_listen_address); +extern void add_port_rest_nat_rules(const char *container_name, const char *pub_interface); +extern char *container_wait_ip_ex(const char *container_name); + +#endif // CONTAINERS_H diff --git a/test/blackbox/common/mesh_event_handler.c b/test/blackbox/common/mesh_event_handler.c new file mode 100644 index 0000000..e00f7d3 --- /dev/null +++ b/test/blackbox/common/mesh_event_handler.c @@ -0,0 +1,290 @@ +/* + mesh_event_handler.c -- handling of mesh events API + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../../src/meshlink_queue.h" +#include "../../utils.h" +#include "mesh_event_handler.h" + +#define SERVER_LISTEN_PORT "9000" /* Port number that is binded with mesh event server socket */ +#define UDP_BUFF_MAX 2000 + +const char *event_status[] = { + [NODE_STARTED] = "Node Started", + [NODE_JOINED] = "Node Joined", + [ERR_NETWORK] = "Network Error", + [CHANNEL_OPENED] = "Channel Opened", + [CHANNEL_DATA_RECIEVED] = "Channel Data Received", + [SIG_ABORT] = "SIG_ABORT Received", + [MESH_EVENT_COMPLETED] = "MESH_EVENT_COMPLETED Received" +}; + +// TODO: Implement mesh event handling with reentrancy . +static struct sockaddr_in server_addr; +static int client_fd = -1; +static int server_fd = -1; +static pthread_t event_receive_thread, event_handle_thread; +static meshlink_queue_t event_queue; +static bool event_receive_thread_running, event_handle_thread_running; +static struct cond_flag sync_event = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; + +static void set_cond_flag(struct cond_flag *s, bool flag) { + pthread_mutex_lock(&s->mutex); + s->flag = flag; + pthread_cond_broadcast(&s->cond); + pthread_mutex_unlock(&s->mutex); +} + +static bool wait_cond_flag(struct cond_flag *s, int seconds) { + struct timespec timeout; + clock_gettime(CLOCK_REALTIME, &timeout); + timeout.tv_sec += seconds; + + pthread_mutex_lock(&s->mutex); + + while(!s->flag) + if(!pthread_cond_timedwait(&s->cond, &s->mutex, &timeout) || errno != EINTR) { + break; + } + + pthread_mutex_unlock(&s->mutex); + + return s->flag; +} + +// event_receive_handler running in a separate thread queues all the events received from the UDP port +static void *event_receive_handler(void *arg) { + (void)arg; + size_t recv_ret; + char udp_buff[UDP_BUFF_MAX]; + struct sockaddr client; + socklen_t soc_len; + + while(event_receive_thread_running) { + recv_ret = recvfrom(server_fd, udp_buff, sizeof(udp_buff), 0, &client, &soc_len); + assert(recv_ret >= sizeof(mesh_event_payload_t)); + + // Push received mesh event data into the event_queue + mesh_event_payload_t *data = malloc(sizeof(mesh_event_payload_t)); + assert(data); + memcpy(data, udp_buff, sizeof(mesh_event_payload_t)); + + // Also receive if there is any payload + if(data->payload_length) { + void *payload_data = malloc(data->payload_length); + assert(payload_data); + memcpy(payload_data, udp_buff + (int)sizeof(mesh_event_payload_t), data->payload_length); + data->payload = payload_data; + } else { + data->payload = NULL; + } + + // Push the event into the event queue + assert(meshlink_queue_push(&event_queue, data)); + } + + return NULL; +} + +// `event_handler' runs in a separate thread which invokes the event handle callback with +// event packet as argument returns from the thread when the callback returns `true' or timeout +static void *event_handler(void *argv) { + bool callback_return = false; + void *data; + mesh_event_payload_t mesh_event_rec_packet; + mesh_event_callback_t callback = *(mesh_event_callback_t *)argv; + + while(event_handle_thread_running) { + + // Pops the event if found in the event queue + while((data = meshlink_queue_pop(&event_queue)) != NULL) { + memcpy(&mesh_event_rec_packet, data, sizeof(mesh_event_payload_t)); + free(data); + + // Invokes the callback with the popped event packet + callback_return = callback(mesh_event_rec_packet); + + if(mesh_event_rec_packet.payload_length) { + free(mesh_event_rec_packet.payload); + } + + // Return or Close the event handle thread if callback returns true + if(callback_return) { + set_cond_flag(&sync_event, true); + event_handle_thread_running = false; + break; + } + } + } + + return NULL; +} + +char *mesh_event_sock_create(const char *if_name) { + struct sockaddr_in server = {0}; + char *ip; + struct ifreq req_if = {0}; + struct sockaddr_in *resp_if_addr; + + assert(if_name); + assert(!event_receive_thread_running); + + server_fd = socket(AF_INET, SOCK_DGRAM, 0); + assert(server_fd >= 0); + + int reuse = 1; + assert(setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) != -1); + + req_if.ifr_addr.sa_family = AF_INET; + strncpy(req_if.ifr_name, if_name, IFNAMSIZ - 1); + assert(ioctl(server_fd, SIOCGIFADDR, &req_if) != -1); + resp_if_addr = (struct sockaddr_in *) & (req_if.ifr_addr); + + server.sin_family = AF_INET; + server.sin_addr = resp_if_addr->sin_addr; + server.sin_port = htons(atoi(SERVER_LISTEN_PORT)); + assert(bind(server_fd, (struct sockaddr *) &server, sizeof(struct sockaddr)) != -1); + + assert((ip = malloc(30))); + strncpy(ip, inet_ntoa(resp_if_addr->sin_addr), 20); + strcat(ip, ":"); + strcat(ip, SERVER_LISTEN_PORT); + + meshlink_queue_init(&event_queue); + event_receive_thread_running = true; + assert(!pthread_create(&event_receive_thread, NULL, event_receive_handler, NULL)); + + return ip; +} + +void mesh_event_sock_connect(const char *import) { + assert(import); + + char *ip = strdup(import); + assert(ip); + char *port = strchr(ip, ':'); + assert(port); + *port = '\0'; + port++; + + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = inet_addr(ip); + server_addr.sin_port = htons(atoi(port)); + client_fd = socket(AF_INET, SOCK_DGRAM, 0); + free(ip); + assert(client_fd >= 0); +} + +bool mesh_event_sock_send(int client_id, mesh_event_t event, const void *payload, size_t payload_length) { + if(client_fd < 0) { + fprintf(stderr, "mesh_event_sock_send called without calling mesh_event_sock_connect\n"); + return false; + } + + if(client_id < 0 || event < 0 || event >= MAX_EVENT || (payload == NULL && payload_length)) { + fprintf(stderr, "Invalid parameters\n"); + return false; + } + + ssize_t send_size = sizeof(mesh_event_payload_t) + payload_length; + char *send_packet = malloc(send_size); + assert(send_packet); + mesh_event_payload_t mesh_event_send_packet; + + mesh_event_send_packet.client_id = client_id; + mesh_event_send_packet.mesh_event = event; + mesh_event_send_packet.payload_length = payload_length; + mesh_event_send_packet.payload = NULL; + memcpy(send_packet, &mesh_event_send_packet, sizeof(mesh_event_send_packet)); + + if(payload_length) { + memcpy(send_packet + sizeof(mesh_event_send_packet), payload, payload_length); + } + + ssize_t send_ret = sendto(client_fd, send_packet, send_size, 0, (const struct sockaddr *) &server_addr, sizeof(server_addr)); + free(send_packet); + + if(send_ret < 0) { + perror("sendto status"); + return false; + } else { + return true; + } +} + +bool wait_for_event(mesh_event_callback_t callback, int seconds) { + if(callback == NULL || seconds == 0) { + fprintf(stderr, "Invalid parameters\n"); + return false; + } + + if(event_handle_thread_running) { + fprintf(stderr, "Event handle thread is already running\n"); + return false; + } else { + event_handle_thread_running = true; + } + + set_cond_flag(&sync_event, false); + assert(!pthread_create(&event_handle_thread, NULL, event_handler, (void *)&callback)); + bool wait_ret = wait_cond_flag(&sync_event, seconds); + event_handle_thread_running = false; + pthread_cancel(event_handle_thread); + + return wait_ret; +} + +void mesh_events_flush(void) { + mesh_event_payload_t *data; + + while((data = meshlink_queue_pop(&event_queue)) != NULL) { + if(data->payload_length) { + free(data->payload); + } + + free(data); + } +} + +void mesh_event_destroy(void) { + mesh_events_flush(); + event_receive_thread_running = false; + pthread_cancel(event_receive_thread); +} diff --git a/test/blackbox/common/mesh_event_handler.h b/test/blackbox/common/mesh_event_handler.h new file mode 100644 index 0000000..609b1cf --- /dev/null +++ b/test/blackbox/common/mesh_event_handler.h @@ -0,0 +1,156 @@ +/* + mesh_event_handler.h + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef _MESH_EVENT_HANDLER_H_ +#define _MESH_EVENT_HANDLER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/// mesh events +// TODO: Add more mesh event if required. +typedef enum { + NO_PREFERENCE = 0, + META_CONN_SUCCESSFUL, + META_CONN, + META_DISCONN, + META_CONN_CLOSED, + NODE_INVITATION, + CHANGED_IP_ADDRESS, + NODE_UNREACHABLE, + NODE_REACHABLE, + META_RECONN_SUCCESSFUL, + META_RECONN_FAILURE, + MESH_DATA_RECEIVED, + NODE_STARTED, + NODE_LEFT, + NODE_RESTARTED, + NODE_JOINED, + NODE_JOINED1, + NODE_JOINED2, + NODE_JOINED3, + PORT_NO, + OPTIMAL_PMTU_PEER, + OPTIMAL_PMTU_RELAY, + ERR_NETWORK, + SIG_ABORT, + MESH_DATA_VERIFED, + CHANNEL_OPENED, + CHANNEL_REQ_RECIEVED, + CHANNEL_CONNECTED, + CHANNEL_DATA_RECIEVED, + MESH_NODE_DISCOVERED, + INCOMING_META_CONN, + OUTGOING_META_CONN, + AUTO_DISCONN, + MESH_EVENT_COMPLETED, + MAX_EVENT // Maximum event enum +} mesh_event_t; + +extern const char *event_status[]; + +/// mesh event UDP packet +typedef struct mesh_event_payload { + void *payload; + mesh_event_t mesh_event; + uint16_t client_id; + uint32_t payload_length; +} mesh_event_payload_t; + +struct cond_flag { + pthread_mutex_t mutex; + pthread_cond_t cond; + bool flag; +}; + +/// callback for handling the mesh event +/** mesh event callback called from wait_for_event() if the mesh event UDP server gets a mesh event. + * + * @param mesh_event_packet packet containing client-id, mesh event & payload (if any). + */ +typedef bool (*mesh_event_callback_t)(mesh_event_payload_t mesh_event_packet); + +/// Creates an UDP server for listening mesh events. +/** This function creates an UDP socket, binds it with given interface address and returns a NULL + * terminated string containing server's IP address & port number. + * + * @param ifname Name of the network interface to which the socket has to be created. + * + * @return This function returns a NULL terminated string which has IP address and + * port number of the server socket. The application should call free() after + * it has finished using the exported string. + */ +extern char *mesh_event_sock_create(const char *ifname); + +/// Waits for the mesh event for about the given timeout. +/** This function waits for the mesh event that's expected to occur for the given timeout. If a mesh event + * is received then the given callback will be invoked. + * + * @param callback callback which handles the mesh event packet. + * @param timeout timeout for which the the function has to wait for the event. + * + * @return This function returns true if a mesh event occurred else false if timeout exceeded. + */ +extern bool wait_for_event(mesh_event_callback_t callback, int timeout); + +/// Sends the mesh event to server. +/** This function sends the mesh event to the server. At the server end it's expected to wait_for_event() + * otherwise the packet will be dropped. + * + * @param client_id Client id by which server can identify the client/node. + * @param event An enum describing the mesh event. + * @param payload Payload can also be attached along with the mesh event if any, else NULL can + * can be specified. + * @param payload_length Length of the payload if specified else 0 can be specified. + * the maximum payload size can be up to PAYLOAD_MAX_SIZE and if the + * PAYLOAD_MAX_SIZE macro is changed it should not exceed the UDP datagram size. + * + * @return This function returns true on success else returns false. + */ +extern bool mesh_event_sock_send(int client_id, mesh_event_t event, const void *payload, size_t payload_length); + +/// Imports the server address, saves it and opens an UDP client socket. +/** This function creates an UDP socket, binds it with given interface address and returns a NULL + * terminated string containing server's IP address & port number. + * + * @param server_address NULL terminated string that's exported by mesh_event_sock_create() which + * which contains IP address and port number of the mesh event server. + * + * @return void + */ +extern void mesh_event_sock_connect(const char *server_address); + +extern void mesh_event_destroy(void); + +extern void mesh_events_flush(void); + +#endif // _MESH_EVENT_HANDLER_H_ diff --git a/test/blackbox/common/network_namespace_framework.c b/test/blackbox/common/network_namespace_framework.c new file mode 100644 index 0000000..8f65296 --- /dev/null +++ b/test/blackbox/common/network_namespace_framework.c @@ -0,0 +1,569 @@ +/* + test_optimal_pmtu.c -- Execution of specific meshlink black box test cases + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include "network_namespace_framework.h" + +#define DEFAULT_PUB_NET_ADDR "203.0.113.0/24" +#define DEFAULT_GATEWAY_NET_ADDR "203.0.113.254" +#define NS_PEER0 " ns_peer0 " +#define NS_ETH0 " ns_eth0 " +#define PEER_INDEX i ? 0 : 1 +#define get_namespace_handle_by_index(state_ptr, index) index < state_ptr->num_namespaces ? &(state_ptr->namespaces[index]) : NULL +#define get_interface_handle_by_index(namespace, index) index < namespace->interfaces_no ? &((namespace->interfaces)[index]) : NULL + +static int ipv4_str_check_cidr(const char *ip_addr) { + int cidr = 0; + sscanf(ip_addr, "%*d.%*d.%*d.%*d/%d", &cidr); + return cidr; +} + +static char *ipv4_str_remove_cidr(const char *ipv4_addr) { + char *ptr = strdup(ipv4_addr); + assert(ptr); + + if(ipv4_str_check_cidr(ptr)) { + char *end = strchr(ptr, '/'); + *end = '\0'; + } + + return ptr; +} + +namespace_t *find_namespace(netns_state_t *state, const char *namespace_name) { + int i; + + for(i = 0; i < state->num_namespaces; i++) { + if(!strcmp((state->namespaces[i]).name, namespace_name)) { + return &(state->namespaces[i]); + } + } + + return NULL; +} + +static int netns_delete_namespace(namespace_t *namespace_handle) { + char cmd[200]; + + if(namespace_handle->type != BRIDGE) { + assert(sprintf(cmd, "ip netns del %s 2>/dev/null", namespace_handle->name) >= 0); + } else { + assert(sprintf(cmd, "ip link del %s 2>/dev/null", namespace_handle->name) >= 0); + } + + return system(cmd); +} + +/* Create new network namespace using namespace handle */ +static void netns_create_namespace(netns_state_t *test_state, namespace_t *namespace_handle) { + (void)test_state; + + char cmd[200]; + + // Add the network namespace + + sprintf(cmd, "ip netns add %s", namespace_handle->name); + assert(system(cmd) == 0); + + sprintf(cmd, "ip netns exec %s ip link set dev lo up", namespace_handle->name); + assert(system(cmd) == 0); +} + +static void netns_create_bridge(netns_state_t *test_state, namespace_t *namespace_handle) { + (void)test_state; + + char cmd[200]; + + sprintf(cmd, "ip link add name %s type bridge", namespace_handle->name); + assert(system(cmd) == 0); + + sprintf(cmd, "ip link set %s up", namespace_handle->name); + assert(system(cmd) == 0); +} + +interface_t *get_peer_interface_handle(netns_state_t *test_state, namespace_t *namespace, namespace_t *peer_namespace) { + (void)test_state; + + int i; + interface_t *interfaces = namespace->interfaces; + int if_no = namespace->interfaces_no; + char *peer_name = peer_namespace->name; + + for(i = 0; i < if_no; i++) { + if(!strcasecmp(interfaces[i].if_peer, peer_name)) { + return &interfaces[i]; + } + } + + return NULL; +} + +interface_t *get_interface_handle_by_name(netns_state_t *test_state, namespace_t *namespace, const char *peer_name) { + namespace_t *peer_ns; + peer_ns = find_namespace(test_state, peer_name); + assert(peer_ns); + + return get_peer_interface_handle(test_state, namespace, peer_ns); +} + +bool check_interfaces_visited(netns_state_t *test_state, namespace_t *ns1, namespace_t *ns2) { + interface_t *iface, *peer_iface; + iface = get_peer_interface_handle(test_state, ns1, ns2); + peer_iface = get_peer_interface_handle(test_state, ns2, ns1); + assert(iface && peer_iface); + + return iface->priv || peer_iface->priv; +} + +void netns_connect_namespaces(netns_state_t *test_state, namespace_t *ns1, namespace_t *ns2) { + char buff[20], cmd[200]; + int i; + char eth_pairs[2][20]; + namespace_t *ns[2] = { ns1, ns2 }; + interface_t *interface; + char *set = "set"; + + // Check if visited already + if(check_interfaces_visited(test_state, ns1, ns2)) { + return; + } + + assert(sprintf(eth_pairs[0], "%.9s_eth0", ns2->name) >= 0); + assert(sprintf(eth_pairs[1], "%.9s_peer0", ns1->name) >= 0); + + // Delete veth pair if already exists + for(i = 0; i < 2; i++) { + assert(sprintf(cmd, "ip link del %s 2>/dev/null", eth_pairs[i]) >= 0); + system(cmd); + } + + // Create veth pair + assert(sprintf(cmd, "ip link add %s type veth peer name %s", eth_pairs[0], eth_pairs[1]) >= 0); + assert(system(cmd) == 0); + + for(i = 0; i < 2; i++) { + + // Find interface handle that with it's peer interface + interface = get_peer_interface_handle(test_state, ns[i], ns[PEER_INDEX]); + assert(interface); + + if(ns[i]->type != BRIDGE) { + + // Define interface name + char *if_name; + + if(interface->if_name) { + if_name = interface->if_name; + } else { + assert(sprintf(buff, "eth_%s", interface->if_peer) >= 0); + if_name = buff; + } + + interface->if_name = strdup(if_name); + + assert(interface->if_name); + + // Connect one end of the the veth pair to the namespace's interface + assert(sprintf(cmd, "ip link set %s netns %s name %s", eth_pairs[i], ns[i]->name, interface->if_name) >= 0); + + assert(system(cmd) == 0); + } else { + + // Connect one end of the the veth pair to the bridge + assert(sprintf(cmd, "ip link set %s master %s up\n", eth_pairs[i], ns[i]->name) >= 0); + assert(system(cmd) == 0); + } + + // Mark interfaces as connected + interface->priv = set; + interface = get_peer_interface_handle(test_state, ns[PEER_INDEX], ns[i]); + assert(interface); + interface->priv = set; + } +} + +void netns_configure_ip_address(netns_state_t *test_state) { + int i, if_no; + namespace_t *namespace; + interface_t *if_handle; + char cmd[200]; + + for(i = 0; i < test_state->num_namespaces; i++) { + namespace = get_namespace_handle_by_index(test_state, i); + + for(if_no = 0; if_no < namespace->interfaces_no; if_no++) { + if_handle = get_interface_handle_by_index(namespace, if_no); + assert(if_handle); + + if(if_handle->if_addr && namespace->type != BRIDGE) { + assert(sprintf(cmd, "ip netns exec %s ip addr add %s dev %s", namespace->name, if_handle->if_addr, if_handle->if_name) >= 0); + assert(system(cmd) == 0); + assert(sprintf(cmd, "ip netns exec %s ip link set dev %s up", namespace->name, if_handle->if_name) >= 0); + assert(system(cmd) == 0); + + if(if_handle->if_default_route_ip) { + char *route_ip = ipv4_str_remove_cidr(if_handle->if_default_route_ip); + assert(sprintf(cmd, "ip netns exec %s ip route add default via %s", namespace->name, route_ip) >= 0); + assert(system(cmd) == 0); + free(route_ip); + } + } + } + } +} + +void netns_enable_all_nats(netns_state_t *test_state) { + int i, j; + namespace_t *namespace, *peer_namespace; + interface_t *interface_handle; + char cmd[200]; + char *ip_addr; + + for(i = 0; i < test_state->num_namespaces; i++) { + namespace = get_namespace_handle_by_index(test_state, i); + + if(namespace->type == FULL_CONE) { + assert(namespace->nat_arg); + netns_fullcone_handle_t **nat_rules = namespace->nat_arg; + char *eth0; + + for(j = 0; nat_rules[j]; j++) { + assert(nat_rules[j]->snat_to_source && nat_rules[j]->dnat_to_destination); + + interface_handle = get_interface_handle_by_name(test_state, namespace, nat_rules[j]->snat_to_source); + assert(interface_handle); + eth0 = interface_handle->if_name; + ip_addr = ipv4_str_remove_cidr(interface_handle->if_addr); + assert(sprintf(cmd, "ip netns exec %s iptables -t nat -A POSTROUTING -o %s -j SNAT --to-source %s", namespace->name, eth0, ip_addr) >= 0); + assert(system(cmd) == 0); + free(ip_addr); + + peer_namespace = find_namespace(test_state, nat_rules[j]->dnat_to_destination); + interface_handle = get_interface_handle_by_name(test_state, peer_namespace, namespace->name); + assert(interface_handle); + + ip_addr = ipv4_str_remove_cidr(interface_handle->if_addr); + assert(sprintf(cmd, "ip netns exec %s iptables -t nat -A PREROUTING -i %s -j DNAT --to-destination %s", namespace->name, eth0, ip_addr) >= 0); + assert(system(cmd) == 0); + free(ip_addr); + } + } + } +} + +void netns_create_all_namespaces(netns_state_t *test_state) { + int i; + namespace_t *namespace; + + for(i = 0; i < test_state->num_namespaces; i++) { + namespace = get_namespace_handle_by_index(test_state, i); + + // Delete the namespace if already exists + netns_delete_namespace(namespace); + + // Create namespace + + if(namespace->type != BRIDGE) { + netns_create_namespace(test_state, namespace); + } else { + netns_create_bridge(test_state, namespace); + } + } +} + +void netns_connect_all_namespaces(netns_state_t *test_state) { + int i, j; + namespace_t *namespace, *peer_namespace; + interface_t *interfaces; + interface_t *interface_handle; + + for(i = 0; i < test_state->num_namespaces; i++) { + namespace = get_namespace_handle_by_index(test_state, i); + assert(namespace->interfaces); + interfaces = namespace->interfaces; + + for(j = 0; j < namespace->interfaces_no; j++) { + peer_namespace = find_namespace(test_state, interfaces[j].if_peer); + assert(peer_namespace); + netns_connect_namespaces(test_state, namespace, peer_namespace); + } + } + + // Reset all priv members of the interfaces + + for(i = 0; i < test_state->num_namespaces; i++) { + namespace = get_namespace_handle_by_index(test_state, i); + assert(namespace->interfaces); + + for(j = 0; j < namespace->interfaces_no; j++) { + interface_handle = get_interface_handle_by_index(namespace, j); + assert(interface_handle); + interface_handle->priv = NULL; + } + } +} + +void increment_ipv4_str(char *ip_addr, int ip_addr_size) { + uint32_t addr_int_n, addr_int_h; + + assert(inet_pton(AF_INET, ip_addr, &addr_int_n) > 0); + addr_int_h = ntohl(addr_int_n); + addr_int_h = addr_int_h + 1; + addr_int_n = htonl(addr_int_h); + assert(inet_ntop(AF_INET, &addr_int_n, ip_addr, ip_addr_size)); +} + +void increment_ipv4_cidr_str(char *ip) { + int subnet; + assert(sscanf(ip, "%*d.%*d.%*d.%*d/%d", &subnet) >= 0); + char *ptr = strchr(ip, '/'); + *ptr = '\0'; + increment_ipv4_str(ip, INET6_ADDRSTRLEN); + sprintf(ip + strlen(ip), "/%d", subnet); +} + +interface_t *netns_get_priv_addr(netns_state_t *test_state, const char *namespace_name) { + namespace_t *namespace_handle; + interface_t *interface_handle; + int if_no; + + namespace_handle = find_namespace(test_state, namespace_name); + assert(namespace_handle); + + for(if_no = 0; if_no < namespace_handle->interfaces_no; if_no++) { + interface_handle = get_interface_handle_by_index(namespace_handle, if_no); + + if(!strcmp(namespace_handle->name, interface_handle->fetch_ip_netns_name)) { + return interface_handle; + } + } + + return NULL; +} + +void netns_add_default_route_addr(netns_state_t *test_state) { + int ns, if_no; + namespace_t *namespace_handle; + interface_t *interface_handle, *peer_interface_handle; + + for(ns = 0; ns < test_state->num_namespaces; ns++) { + namespace_handle = get_namespace_handle_by_index(test_state, ns); + assert(namespace_handle); + + if(namespace_handle->type != HOST) { + continue; + } + + for(if_no = 0; if_no < namespace_handle->interfaces_no; if_no++) { + interface_handle = get_interface_handle_by_index(namespace_handle, if_no); + + if(interface_handle->if_default_route_ip == NULL) { + peer_interface_handle = netns_get_priv_addr(test_state, interface_handle->fetch_ip_netns_name); + assert(peer_interface_handle); + interface_handle->if_default_route_ip = ipv4_str_remove_cidr(peer_interface_handle->if_addr); + } else { + char *dup = strdup(interface_handle->if_default_route_ip); + assert(dup); + interface_handle->if_default_route_ip = dup; + } + } + } +} + +void netns_assign_ip_addresses(netns_state_t *test_state) { + int ns, j; + namespace_t *namespace_handle; + interface_t *interface_handle; + + char *addr = malloc(INET6_ADDRSTRLEN); + assert(addr); + + if(test_state->public_net_addr) { + assert(strncpy(addr, test_state->public_net_addr, INET6_ADDRSTRLEN)); + } else { + assert(strncpy(addr, DEFAULT_PUB_NET_ADDR, INET6_ADDRSTRLEN)); + } + + test_state->public_net_addr = addr; + + for(ns = 0; ns < test_state->num_namespaces; ns++) { + namespace_handle = get_namespace_handle_by_index(test_state, ns); + assert(namespace_handle); + + if(namespace_handle->type == BRIDGE) { + continue; + } + + for(j = 0; j < namespace_handle->interfaces_no; j++) { + interface_handle = get_interface_handle_by_index(namespace_handle, j); + assert(interface_handle); + + if(interface_handle->if_addr) { + continue; + } + + // If fetch ip net namespace name is given get IP address from it, else get a public IP address + + if(interface_handle->fetch_ip_netns_name) { + namespace_t *gw_netns_handle = find_namespace(test_state, interface_handle->fetch_ip_netns_name); + assert(gw_netns_handle); + assert(gw_netns_handle->static_config_net_addr); + + increment_ipv4_cidr_str(gw_netns_handle->static_config_net_addr); + interface_handle->if_addr = strdup(gw_netns_handle->static_config_net_addr); + } else { + increment_ipv4_cidr_str(test_state->public_net_addr); + interface_handle->if_addr = strdup(test_state->public_net_addr); + + if(namespace_handle->type == HOST) { + if(interface_handle->if_default_route_ip) { + char *dup = strdup(interface_handle->if_default_route_ip); + assert(dup); + interface_handle->if_default_route_ip = dup; + } else { + interface_handle->if_default_route_ip = strdup(DEFAULT_GATEWAY_NET_ADDR); + } + } + } + } + } + + netns_add_default_route_addr(test_state); +} + +static void netns_namespace_init_pids(netns_state_t *test_state) { + int if_no; + namespace_t *namespace_handle; + + for(if_no = 0; if_no < test_state->num_namespaces; if_no++) { + namespace_handle = get_namespace_handle_by_index(test_state, if_no); + assert(namespace_handle); + namespace_handle->pid_nos = 0; + namespace_handle->pids = NULL; + } +} + +pid_t run_cmd_in_netns(netns_state_t *test_state, char *namespace_name, char *cmd_str) { + pid_t pid; + namespace_t *namespace_handle; + char cmd[1000]; + + assert(namespace_name && cmd_str); + namespace_handle = find_namespace(test_state, namespace_name); + assert(namespace_handle); + + if((pid = fork()) == 0) { + assert(daemon(1, 0) != -1); + assert(sprintf(cmd, "ip netns exec %s %s", namespace_name, cmd_str) >= 0); + assert(system(cmd) == 0); + exit(0); + } + + pid_t *pid_ptr; + pid_ptr = realloc(namespace_handle->pids, (namespace_handle->pid_nos + 1) * sizeof(pid_t)); + assert(pid_ptr); + namespace_handle->pids = pid_ptr; + (namespace_handle->pids)[namespace_handle->pid_nos] = pid; + namespace_handle->pid_nos = namespace_handle->pid_nos + 1; + + return pid; +} + +static void *pthread_fun(void *arg) { + netns_thread_t *netns_arg = (netns_thread_t *)arg; + char namespace_path[100]; + void *ret; + assert(sprintf(namespace_path, "/var/run/netns/%s", netns_arg->namespace_name) >= 0); + int fd = open(namespace_path, O_RDONLY); + assert(fd != -1); + assert(setns(fd, CLONE_NEWNET) != -1); + + ret = (netns_arg->netns_thread)(netns_arg->arg); + pthread_detach(netns_arg->thread_handle); + pthread_exit(ret); +} + +void run_node_in_namespace_thread(netns_thread_t *netns_arg) { + assert(netns_arg->namespace_name && netns_arg->netns_thread); + assert(!pthread_create(&(netns_arg->thread_handle), NULL, pthread_fun, netns_arg)); +} + +void netns_destroy_topology(netns_state_t *test_state) { + namespace_t *namespace_handle; + interface_t *interface_handle; + int if_no, j, i; + pid_t pid, pid_ret; + + for(if_no = 0; if_no < test_state->num_namespaces; if_no++) { + namespace_handle = get_namespace_handle_by_index(test_state, if_no); + assert(namespace_handle->interfaces); + + for(i = 0; i < namespace_handle->pid_nos; i++) { + pid = (namespace_handle->pids)[i]; + kill(pid, SIGINT); + pid_ret = waitpid(pid, NULL, WNOHANG); + assert(pid_ret != -1); + + if(pid_ret == 0) { + fprintf(stderr, "pid: %d, is still running\n", pid); + } + } + + // Free interface name, interface address, interface default address etc., + // which are dynamically allocated and set the values to NULL + + for(j = 0; j < namespace_handle->interfaces_no; j++) { + interface_handle = get_interface_handle_by_index(namespace_handle, j); + assert(interface_handle); + + free(interface_handle->if_name); + interface_handle->if_name = NULL; + free(interface_handle->if_addr); + interface_handle->if_addr = NULL; + free(interface_handle->if_default_route_ip); + interface_handle->if_default_route_ip = NULL; + } + + // Delete namespace + assert(netns_delete_namespace(namespace_handle) == 0); + } + + free(test_state->public_net_addr); + test_state->public_net_addr = NULL; +} + +bool netns_create_topology(netns_state_t *test_state) { + + // (Re)create name-spaces and bridges + netns_create_all_namespaces(test_state); + + // Connect namespaces and bridges(if any) with their interfaces + netns_connect_all_namespaces(test_state); + + // Assign IP addresses for the interfaces in namespaces + netns_assign_ip_addresses(test_state); + + // Configure assigned IP addresses with the interfaces in netns + netns_configure_ip_address(test_state); + + // Enable all NATs + netns_enable_all_nats(test_state); + + netns_namespace_init_pids(test_state); + + return true; +} diff --git a/test/blackbox/common/network_namespace_framework.h b/test/blackbox/common/network_namespace_framework.h new file mode 100644 index 0000000..9e6005e --- /dev/null +++ b/test/blackbox/common/network_namespace_framework.h @@ -0,0 +1,123 @@ +/* + network_namespace_framework.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PUB_IF 0 +#define PRIV_IF 1 + +typedef enum { + HOST, + FULL_CONE, + PORT_REST, + ADDR_REST, + SYMMERTRIC, + BRIDGE, +} namespace_type_t; + +typedef void *pthread_fun_ptr_t(void *arg); + +typedef struct { + char *if_name; + int if_type; + char *if_peer; + char *if_addr; + char *if_route; + char *addr_host; + char *fetch_ip_netns_name; + char *if_default_route_ip; + void *priv; +} interface_t; + +typedef struct { + char *snat_to_source; + char *dnat_to_destination; +} netns_fullcone_handle_t; + +typedef struct { + char *name; + namespace_type_t type; + void *nat_arg; + char static_config_net_addr[INET6_ADDRSTRLEN]; // Buffer should be of length INET_ADDRSTRLEN or INET6_ADDRSTRLEN + interface_t *interfaces; + int interfaces_no; + pid_t *pids; + int pid_nos; + void *priv; +} namespace_t; + +typedef struct { + char *test_case_name; + namespace_t *namespaces; + int num_namespaces; + char *public_net_addr; + pthread_t **threads; + bool test_result; +} netns_state_t; + +typedef struct { + char *namespace_name; + pthread_fun_ptr_t *netns_thread; + pthread_t thread_handle; + void *arg; +} netns_thread_t; + +typedef struct { + char *node_name; + char *confbase; + char *app_name; + int dev_class; + char *join_invitation; +} mesh_arg_t; + +typedef struct { + mesh_arg_t *mesh_arg; + char *invitee_name; + char *invite_str; +} mesh_invite_arg_t; + +extern bool netns_create_topology(netns_state_t *state); +extern void netns_destroy_topology(netns_state_t *test_state); +extern void run_node_in_namespace_thread(netns_thread_t *netns_arg); +extern pid_t run_cmd_in_netns(netns_state_t *test_state, char *namespace_name, char *cmd_str); diff --git a/test/blackbox/common/tcpdump.c b/test/blackbox/common/tcpdump.c new file mode 100644 index 0000000..1d9b23f --- /dev/null +++ b/test/blackbox/common/tcpdump.c @@ -0,0 +1,68 @@ +/* + tcpdump.c -- Implementation of Black Box Test Execution for meshlink + + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include "common_handlers.h" +#include "tcpdump.h" + +pid_t tcpdump_start(char *interface) { + char *argv[] = { "tcpdump", "-i", interface, NULL }; + // child process have a pipe to the parent process when parent process terminates SIGPIPE kills the tcpdump + int pipes[2]; + assert(pipe(pipes) != -1); + PRINT_TEST_CASE_MSG("\x1b[32mLaunching TCP Dump ..\x1b[0m\n"); + + pid_t tcpdump_pid = fork(); + + if(tcpdump_pid == 0) { + prctl(PR_SET_PDEATHSIG, SIGHUP); + close(pipes[1]); + // Open log file for TCP Dump + int fd = open(TCPDUMP_LOG_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644); + assert(fd != -1); + close(STDOUT_FILENO); + assert(dup2(fd, STDOUT_FILENO) != -1); + + // Launch TCPDump with port numbers of sleepy, gateway & relay + execvp("/usr/sbin/tcpdump", argv); + perror("execvp "); + exit(1); + } else { + close(pipes[0]); + } + + return tcpdump_pid; +} + +void tcpdump_stop(pid_t tcpdump_pid) { + PRINT_TEST_CASE_MSG("\n\x1b[32mStopping TCP Dump.\x1b[0m\n"); + assert(!kill(tcpdump_pid, SIGTERM)); +} diff --git a/test/blackbox/common/tcpdump.h b/test/blackbox/common/tcpdump.h new file mode 100644 index 0000000..37e92b8 --- /dev/null +++ b/test/blackbox/common/tcpdump.h @@ -0,0 +1,29 @@ +/* + tcpdump.h -- Declarations of common callback handlers and signal handlers for + black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef TCPDUMP_H +#define TCPDUMP_H + +#define TCPDUMP_LOG_FILE "tcpdump.log" + +extern pid_t tcpdump_start(char *); +extern void tcpdump_stop(pid_t tcpdump_pid); + +#endif // TCPDUMP_H diff --git a/test/blackbox/common/test_step.c b/test/blackbox/common/test_step.c new file mode 100644 index 0000000..b772b93 --- /dev/null +++ b/test/blackbox/common/test_step.c @@ -0,0 +1,140 @@ +/* + test_step.c -- Handlers for executing test steps during node simulation + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +ŝ + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include "../../../src/meshlink.h" +#include "test_step.h" +#include "common_handlers.h" + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +meshlink_handle_t *mesh_handle = NULL; +bool mesh_started = false; +char *eth_if_name = NULL; + +meshlink_handle_t *execute_open(char *node_name, char *dev_class) { + /* Set up logging for Meshlink */ + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + mesh_handle = meshlink_open("testconf", node_name, "node_sim", atoi(dev_class)); + fprintf(stderr, "meshlink_open status: %s\n", meshlink_strerror(meshlink_errno)); + meshlink_enable_discovery(mesh_handle, false); + PRINT_TEST_CASE_MSG("meshlink_open status: %s\n", meshlink_strerror(meshlink_errno)); + assert(mesh_handle); + + /* Set up logging for Meshlink with the newly acquired Mesh Handle */ + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + /* Set up callback for node status (reachable / unreachable) */ + meshlink_set_node_status_cb(mesh_handle, meshlink_callback_node_status); + + return mesh_handle; +} + +char *execute_invite(char *invitee, meshlink_submesh_t *submesh) { + char *invite_url = meshlink_invite_ex(mesh_handle, submesh, invitee, MESHLINK_INVITE_LOCAL | MESHLINK_INVITE_NUMERIC); + + PRINT_TEST_CASE_MSG("meshlink_invite status: %s\n", meshlink_strerror(meshlink_errno)); + assert(invite_url); + + return invite_url; +} + +void execute_join(char *invite_url) { + bool join_status; + + join_status = meshlink_join(mesh_handle, invite_url); + assert(join_status); +} + +void execute_start(void) { + bool start_init_status = meshlink_start(mesh_handle); + + PRINT_TEST_CASE_MSG("meshlink_start status: %s\n", meshlink_strerror(meshlink_errno)); + assert(start_init_status); + mesh_started = true; +} + +void execute_stop(void) { + assert(mesh_handle); + meshlink_stop(mesh_handle); + mesh_started = false; +} + +void execute_close(void) { + assert(mesh_handle); + meshlink_close(mesh_handle); +} + +void execute_change_ip(void) { + char *eth_if_ip; + int last_byte; + char new_ip[20] = "", gateway_ip[20] = ""; + char *last_dot_in_ip; + char *eth_if_netmask; + + /* Get existing IP Address of Ethernet Bridge Interface */ + assert((eth_if_ip = get_ip(eth_if_name))); + + /* Set new IP Address by replacing the last byte with last byte + 1 */ + strncpy(new_ip, eth_if_ip, sizeof(new_ip) - 1); + assert((last_dot_in_ip = strrchr(new_ip, '.'))); + last_byte = atoi(last_dot_in_ip + 1); + assert(snprintf(last_dot_in_ip + 1, 4, "%d", (last_byte > 253) ? 2 : (last_byte + 1)) >= 0); + + /* TO DO: Check for IP conflicts with other interfaces and existing Containers */ + /* Bring the network interface down before making changes */ + stop_nw_intf(eth_if_name); + /* Save the netmask first, then restore it after setting the new IP Address */ + assert((eth_if_netmask = get_netmask(eth_if_name))); + set_ip(eth_if_name, new_ip); + set_netmask(eth_if_name, eth_if_netmask); + /* Bring the network interface back up again to apply changes */ + start_nw_intf(eth_if_name); + + /* Get Gateway's IP Address, by replacing the last byte with 1 in the current IP Address */ + /* TO DO: Obtain the actual Gateway IP Address */ + strncpy(gateway_ip, eth_if_ip, sizeof(gateway_ip) - 1); + assert((last_dot_in_ip = strrchr(gateway_ip, '.'))); + assert(snprintf(last_dot_in_ip + 1, 4, "%d", 1) >= 0); + + /* Add the default route back again, which would have been deleted when the + network interface was brought down */ + /* TO DO: Perform this action using ioctl with SIOCADDRT */ + /*assert(snprintf(route_chg_command, sizeof(route_chg_command), "route add default gw %s", + gateway_ip) >= 0); + route_chg_status = system(route_chg_command); + PRINT_TEST_CASE_MSG("Default Route Add status = %d\n", route_chg_status); + assert(route_chg_status == 0); */ + // Not necessary for ubuntu versions of 16.04 and 18.04 + + PRINT_TEST_CASE_MSG("Node '%s' IP Address changed to %s\n", NUT_NODE_NAME, new_ip); + + free(eth_if_ip); + free(eth_if_netmask); +} + diff --git a/test/blackbox/common/test_step.h b/test/blackbox/common/test_step.h new file mode 100644 index 0000000..1704d34 --- /dev/null +++ b/test/blackbox/common/test_step.h @@ -0,0 +1,33 @@ +/* + test_step.h -- Handlers for executing test steps during node simulation + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef TEST_STEP_H +#define TEST_STEP_H + +#include "../../../src/meshlink.h" + +meshlink_handle_t *execute_open(char *node_name, char *dev_class); +char *execute_invite(char *invitee, meshlink_submesh_t *submesh); +void execute_join(char *invite_url); +void execute_start(void); +void execute_stop(void); +void execute_close(void); +void execute_change_ip(void); + +#endif // TEST_STEP_H diff --git a/test/blackbox/run_blackbox_tests/.gitignore b/test/blackbox/run_blackbox_tests/.gitignore new file mode 100644 index 0000000..076f4db --- /dev/null +++ b/test/blackbox/run_blackbox_tests/.gitignore @@ -0,0 +1 @@ +/run_blackbox_tests diff --git a/test/blackbox/run_blackbox_tests/Makefile.am b/test/blackbox/run_blackbox_tests/Makefile.am new file mode 100644 index 0000000..e4251a6 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/Makefile.am @@ -0,0 +1,73 @@ +check_PROGRAMS = run_blackbox_tests + +run_blackbox_tests_SOURCES = \ + run_blackbox_tests.c \ + test_cases.c execute_tests.c \ + ../common/mesh_event_handler.c \ + ../common/containers.c \ + ../common/tcpdump.c \ + ../common/common_handlers.c \ + ../common/test_step.c \ + ../common/network_namespace_framework.c \ + ../../utils.c \ + test_cases_destroy.c \ + test_cases_export.c \ + test_cases_get_all_nodes.c \ + test_cases_get_fingerprint.c \ + test_cases_invite.c \ + test_cases_rec_cb.c \ + test_cases_set_port.c \ + test_cases_sign.c \ + test_cases_verify.c \ + test_cases_channel_ex.c \ + test_cases_channel_get_flags.c \ + test_cases_status_cb.c \ + test_cases_set_log_cb.c \ + test_cases_join.c \ + test_cases_import.c \ + test_cases_channel_set_accept_cb.c \ + test_cases_channel_set_poll_cb.c \ + test_cases_hint_address.c \ + test_cases_channel_set_receive_cb.c \ + test_cases_open.c \ + test_cases_start.c \ + test_cases_stop_close.c \ + test_cases_pmtu.c \ + test_cases_get_self.c \ + test_cases_send.c \ + test_cases_get_node.c \ + test_cases_add_addr.c \ + test_cases_get_ex_addr.c \ + test_cases_add_ex_addr.c \ + test_cases_get_port.c \ + test_cases_blacklist.c \ + test_cases_whitelist.c \ + test_cases_default_blacklist.c \ + test_cases_channel_open.c \ + test_cases_channel_close.c \ + test_cases_channel_send.c \ + test_cases_channel_shutdown.c \ + test_cases_channel_conn.c \ + test_cases_get_all_nodes_by_dev_class.c \ + ../test_case_optimal_pmtu_01/node_sim_nut.c \ + ../test_case_optimal_pmtu_01/node_sim_relay.c \ + ../test_case_optimal_pmtu_01/node_sim_peer.c \ + test_optimal_pmtu.c \ + ../test_case_channel_blacklist_01/node_sim_nut_01.c \ + ../test_case_channel_blacklist_01/node_sim_peer_01.c \ + ../test_case_channel_blacklist_01/node_sim_relay_01.c \ + test_cases_channel_blacklist.c \ + test_cases_submesh01.c \ + test_cases_submesh02.c \ + test_cases_submesh03.c \ + test_cases_submesh04.c \ + test_cases_autoconnect.c \ + test_cases_set_connection_try_cb.c \ + test_cases_random_port_bindings01.c \ + test_cases_random_port_bindings02.c \ + test_cases_key_rotation.c \ + test_cases_get_node_reachability.c + +run_blackbox_tests_LDADD = ../../../src/libmeshlink.la $(LXC_LIBS) $(CMOCKA_LIBS) +run_blackbox_tests_CFLAGS = -D_GNU_SOURCE $(LXC_CFLAGS) $(CMOCKA_CFLAGS) + diff --git a/test/blackbox/run_blackbox_tests/execute_tests.c b/test/blackbox/run_blackbox_tests/execute_tests.c new file mode 100644 index 0000000..6b534ee --- /dev/null +++ b/test/blackbox/run_blackbox_tests/execute_tests.c @@ -0,0 +1,130 @@ +/* + execute_tests.c -- Utility functions for black box test execution + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include "execute_tests.h" +#include "../common/common_handlers.h" +#include "../common/containers.h" +#include "../common/test_step.h" + +int setup_test(void **state) { + int i; + + fprintf(stderr, "Setting up Containers\n"); + state_ptr = (black_box_state_t *)(*state); + + for(i = 0; i < state_ptr->num_nodes; i++) { + meta_conn_status[i] = false; + } + + setup_containers(state); + + return EXIT_SUCCESS; +} + +void execute_test(test_step_func_t step_func, void **state) { + black_box_state_t *test_state = (black_box_state_t *)(*state); + + fprintf(stderr, "\n\x1b[32mRunning Test\x1b[0m : \x1b[34m%s\x1b[0m\n", test_state->test_case_name); + test_state->test_result = step_func(); + + if(!test_state->test_result) { + fail(); + } +} + +int teardown_test(void **state) { + black_box_state_t *test_state = (black_box_state_t *)(*state); + char container_old_name[100], container_new_name[100]; + int i; + + if(test_state->test_result) { + PRINT_TEST_CASE_MSG("Test successful! Shutting down nodes.\n"); + + for(i = 0; i < test_state->num_nodes; i++) { + /* Shut down node */ + node_step_in_container(test_state->node_names[i], "SIGTERM"); + /* Rename Container to run_ - this allows it to be re-used for the + next test, otherwise it will be ignored assuming that it has been saved + for debugging */ + assert(snprintf(container_old_name, sizeof(container_old_name), "%s_%s", + test_state->test_case_name, test_state->node_names[i]) >= 0); + assert(snprintf(container_new_name, sizeof(container_new_name), "run_%s", + test_state->node_names[i]) >= 0); + rename_container(container_old_name, container_new_name); + } + } + + state_ptr = NULL; + + return EXIT_SUCCESS; +} + +bool change_state(node_status_t *status, mesh_event_t currentEv) { + + if(status->current_index == status->max_events) { + return false; + } + + if(currentEv == status->expected_events[status->current_index]) { + status->current_index = status->current_index + 1; + + return true; + } else { + return false; + } +} + +void signal_node_start(node_status_t *node_status, int start, int end, char *node_ids[]) { + int i, index; + + for(i = start; i <= end; i++) { + index = node_status[i].current_index; + + if(index < 1 || NODE_JOINED != node_status[i].expected_events[index - 1]) { + return; + } + } + + + for(i = start; i <= end; i++) { + fprintf(stderr, "\tSending signals to '%s'\n", node_ids[i]); + node_step_in_container(node_ids[i], "SIGIO"); + } + + return; +} + +bool check_nodes_finished(node_status_t *node_status, int length) { + for(int i = 0; i < length; i++) { + if(node_status[i].current_index != node_status[i].max_events) { + return false; + } + } + + return true; +} diff --git a/test/blackbox/run_blackbox_tests/execute_tests.h b/test/blackbox/run_blackbox_tests/execute_tests.h new file mode 100644 index 0000000..cafc83f --- /dev/null +++ b/test/blackbox/run_blackbox_tests/execute_tests.h @@ -0,0 +1,70 @@ +#ifndef EXECUTE_TESTS_H +#define EXECUTE_TESTS_H + +/* + execute_tests.h -- header file for execute_tests.c + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include +#include "../common/mesh_event_handler.h" + +typedef struct { + const mesh_event_t *expected_events; + int current_index; + int max_events; +} node_status_t; + +typedef bool (*test_step_func_t)(void); + +int setup_test(void **state); +void execute_test(test_step_func_t step_func, void **state); +int teardown_test(void **state); + +/// Changes the state of the node state machine. +/** This function changes the current state of the node + * + * @param status Pointer to status handle of that node. + * @param currentEv Current event triggered by the node. + * + * @return This function returns true if state change is successful else returns false + */ +extern bool change_state(node_status_t *status, mesh_event_t currentEv); + +/// Sends SIGIO signal to all the nodes in the container. +/** This function Triggers SIGIO signal to all the target applications running inside the container + * + * @param status Pointer to array of status handles of target nodes. + * @param start Starting index from which to start in the array. + * @param end Ending index of the array + * @param node_ids Pointer to array of node id strings + * + * @return Void + */ +extern void signal_node_start(node_status_t *node_status, int start, int end, char *node_ids[]); + +/// Checks for the completion of nodes state machines. +/** This function checks whether the nodes state machines have reached their maximum state indexes + * + * @param status Pointer to array of status handles of target nodes. + * @param length Number of nodes to check. + * + * @return This function returns true if all the nodes reached their max states + */ +extern bool check_nodes_finished(node_status_t *node_status, int length); + +#endif // TEST_STEP_H diff --git a/test/blackbox/run_blackbox_tests/run_blackbox_tests.c b/test/blackbox/run_blackbox_tests/run_blackbox_tests.c new file mode 100644 index 0000000..fe6f9a9 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/run_blackbox_tests.c @@ -0,0 +1,169 @@ +/* + run_blackbox_tests.c -- Implementation of Black Box Test Execution for meshlink + + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include "execute_tests.h" +#include "test_cases.h" +#include "test_cases_open.h" +#include "test_cases_start.h" +#include "test_cases_stop_close.h" +#include "test_cases_send.h" +#include "test_cases_pmtu.h" +#include "test_cases_get_self.h" +#include "test_cases_get_node.h" +#include "test_cases_add_addr.h" +#include "test_cases_get_ex_addr.h" +#include "test_cases_add_ex_addr.h" +#include "test_cases_get_port.h" +#include "test_cases_blacklist.h" +#include "test_cases_default_blacklist.h" +#include "test_cases_whitelist.h" +#include "test_cases_channel_open.h" +#include "test_cases_channel_close.h" +#include "test_cases_channel_send.h" +#include "test_cases_channel_shutdown.h" + +#include "test_cases_destroy.h" +#include "test_cases_get_all_nodes.h" +#include "test_cases_get_fingerprint.h" +#include "test_cases_rec_cb.h" +#include "test_cases_sign.h" +#include "test_cases_set_port.h" +#include "test_cases_verify.h" +#include "test_cases_invite.h" +#include "test_cases_export.h" +#include "test_cases_channel_ex.h" +#include "test_cases_channel_get_flags.h" +#include "test_cases_status_cb.h" +#include "test_cases_set_log_cb.h" +#include "test_cases_join.h" +#include "test_cases_import.h" +#include "test_cases_channel_set_accept_cb.h" +#include "test_cases_channel_set_poll_cb.h" +#include "test_cases_channel_set_receive_cb.h" +#include "test_cases_hint_address.h" +#include "test_optimal_pmtu.h" +#include "test_cases_key_rotation.h" + +#include "test_cases_channel_conn.h" +#include "test_cases_get_all_nodes_by_dev_class.h" +#include "test_cases_submesh01.h" +#include "test_cases_submesh02.h" +#include "test_cases_submesh03.h" +#include "test_cases_submesh04.h" +#include "test_cases_autoconnect.h" +#include "test_cases_set_connection_try_cb.h" + +#include "test_cases_random_port_bindings01.h" +#include "test_cases_random_port_bindings02.h" + +#include "test_cases_get_node_reachability.h" + +#include "../common/containers.h" +#include "../common/common_handlers.h" + +char *meshlink_root_path = NULL; +char *choose_arch = NULL; +int total_tests; + +int main(int argc, char *argv[]) { + /* Set configuration */ + assert(argc > 5); + meshlink_root_path = argv[1]; + lxc_path = argv[2]; + lxc_bridge = argv[3]; + eth_if_name = argv[4]; + choose_arch = argv[5]; + + int failed_tests = 0; + + failed_tests += test_meta_conn(); + failed_tests += test_meshlink_set_status_cb(); + failed_tests += test_meshlink_join(); + failed_tests += test_meshlink_set_channel_poll_cb(); + failed_tests += test_meshlink_channel_open_ex(); + failed_tests += test_meshlink_channel_get_flags(); + failed_tests += test_meshlink_set_channel_accept_cb(); + failed_tests += test_meshlink_destroy(); + failed_tests += test_meshlink_export(); + failed_tests += test_meshlink_get_fingerprint(); + failed_tests += test_meshlink_get_all_nodes(); + failed_tests += test_meshlink_get_all_node_by_device_class(); + failed_tests += test_meshlink_set_port(); + failed_tests += test_meshlink_sign(); + failed_tests += test_meshlink_verify(); + failed_tests += test_meshlink_import(); + failed_tests += test_meshlink_invite(); + failed_tests += test_meshlink_set_receive_cb(); + failed_tests += test_meshlink_set_log_cb(); + failed_tests += test_meshlink_set_channel_receive_cb(); + failed_tests += test_meshlink_hint_address(); + + failed_tests += test_meshlink_open(); + failed_tests += test_meshlink_start(); + failed_tests += test_meshlink_stop_close(); + failed_tests += test_meshlink_send(); + failed_tests += test_meshlink_channel_send(); + failed_tests += test_meshlink_channel_shutdown(); + failed_tests += test_meshlink_pmtu(); + failed_tests += test_meshlink_get_self(); + failed_tests += test_meshlink_get_node(); + failed_tests += test_meshlink_add_address(); + failed_tests += test_meshlink_get_external_address(); + failed_tests += test_meshlink_add_external_address(); + failed_tests += test_meshlink_get_port(); + failed_tests += test_meshlink_blacklist(); + failed_tests += test_meshlink_whitelist(); + failed_tests += test_meshlink_default_blacklist(); + failed_tests += test_meshlink_channel_open(); + failed_tests += test_meshlink_channel_close(); + + failed_tests += test_meshlink_channel_conn(); + failed_tests += test_optimal_pmtu(); + + failed_tests += test_cases_submesh01(); + failed_tests += test_cases_submesh02(); + failed_tests += test_cases_submesh03(); + failed_tests += test_cases_submesh04(); + + failed_tests += test_meshlink_autoconnect(); + failed_tests += test_cases_connection_try(); + + failed_tests += test_optimal_pmtu(); + failed_tests += test_meshlink_encrypted_key_rotation(); + + failed_tests += test_meshlink_random_port_bindings01(); + failed_tests += test_meshlink_random_port_bindings02(); + + failed_tests += test_get_node_reachability(); + + printf("[ PASSED ] %d test(s).\n", total_tests - failed_tests); + printf("[ FAILED ] %d test(s).\n", failed_tests); + + return failed_tests; +} diff --git a/test/blackbox/run_blackbox_tests/test_cases.c b/test/blackbox/run_blackbox_tests/test_cases.c new file mode 100644 index 0000000..c07edec --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases.c @@ -0,0 +1,551 @@ +/* + test_cases.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include "execute_tests.h" +#include "test_cases.h" +#include "pthread.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "../common/mesh_event_handler.h" + +#define RELAY_ID "0" +#define PEER_ID "1" +#define NUT_ID "2" + +static void test_case_meta_conn_01(void **state); +static bool test_steps_meta_conn_01(void); +static void test_case_meta_conn_02(void **state); +static bool test_steps_meta_conn_02(void); +static void test_case_meta_conn_03(void **state); +static bool test_steps_meta_conn_03(void); +static void test_case_meta_conn_04(void **state); +static bool test_steps_meta_conn_04(void); +static void test_case_meta_conn_05(void **state); +static bool test_steps_meta_conn_05(void); + +/* State structure for Meta-connections Test Case #1 */ +static char *test_meta_conn_1_nodes[] = { "relay", "peer", "nut" }; +static black_box_state_t test_meta_conn_1_state = { + .test_case_name = "test_case_meta_conn_01", + .node_names = test_meta_conn_1_nodes, + .num_nodes = 3, +}; + +/* State structure for Meta-connections Test Case #2 */ +static char *test_meta_conn_2_nodes[] = { "relay", "peer", "nut" }; +static black_box_state_t test_meta_conn_2_state = { + .test_case_name = "test_case_meta_conn_02", + .node_names = test_meta_conn_2_nodes, + .num_nodes = 3, +}; + +/* State structure for Meta-connections Test Case #3 */ +static char *test_meta_conn_3_nodes[] = { "relay", "peer", "nut" }; +static black_box_state_t test_meta_conn_3_state = { + .test_case_name = "test_case_meta_conn_03", + .node_names = test_meta_conn_3_nodes, + .num_nodes = 3, +}; + +/* State structure for Meta-connections Test Case #4 */ +static char *test_meta_conn_4_nodes[] = { "peer", "nut" }; +static black_box_state_t test_meta_conn_4_state = { + .test_case_name = "test_case_meta_conn_04", + .node_names = test_meta_conn_4_nodes, + .num_nodes = 2, +}; + +/* State structure for Meta-connections Test Case #5 */ +static char *test_meta_conn_5_nodes[] = { "peer", "nut" }; +static black_box_state_t test_meta_conn_5_state = { + .test_case_name = "test_case_meta_conn_05", + .node_names = test_meta_conn_5_nodes, + .num_nodes = 2, +}; + +int black_box_group0_setup(void **state) { + (void)state; + + const char *nodes[] = { "peer", "relay", "nut"}; + int num_nodes = sizeof(nodes) / sizeof(nodes[0]); + + PRINT_TEST_CASE_MSG("Creating Containers\n"); + destroy_containers(); + create_containers(nodes, num_nodes); + + return 0; +} + +int black_box_group0_teardown(void **state) { + (void)state; + + PRINT_TEST_CASE_MSG("Destroying Containers\n"); + destroy_containers(); + + return 0; +} + +int black_box_all_nodes_setup(void **state) { + (void)state; + + const char *nodes[] = { "peer" }; + int num_nodes = sizeof(nodes) / sizeof(nodes[0]); + + PRINT_TEST_CASE_MSG("Creating Containers\n"); + destroy_containers(); + create_containers(nodes, num_nodes); + PRINT_TEST_CASE_MSG("Created Containers\n"); + return 0; +} + +static bool meta_conn01_conn; +static bool meta_conn01_closed; +static bool meta_conn01_reconn; + +static bool meta_conn01_cb(mesh_event_payload_t payload) { + char event_node_name[][10] = {"RELAY", "PEER", "NUT"}; + fprintf(stderr, "%s : ", event_node_name[payload.client_id]); + + switch(payload.mesh_event) { + case META_CONN_SUCCESSFUL : + meta_conn01_conn = true; + break; + + case NODE_STARTED : + fprintf(stderr, "Node started\n"); + break; + + case META_CONN_CLOSED : + meta_conn01_closed = true; + break; + + case META_RECONN_SUCCESSFUL : + meta_conn01_reconn = true; + break; + + default: + break; + } + + return true; +} + +/* Execute Meta-connections Test Case # 1 - re-connection to peer after disconnection when + connected via a third node */ +static void test_case_meta_conn_01(void **state) { + execute_test(test_steps_meta_conn_01, state); +} + +/* Test Steps for Meta-connections Test Case # 1 - re-connection to peer after disconnection when + connected via a third (relay) node + + Test Steps: + 1. Run NUT, relay and peer nodes with relay inviting the other two nodes + 2. After connection to peer, terminate the peer node's running instance + 3. After peer becomes unreachable, wait 60 seconds then re-start the peer node's instance + + Expected Result: + NUT is re-connected to peer +*/ +static bool test_steps_meta_conn_01(void) { + char *invite_peer, *invite_nut; + char *import; + + import = mesh_event_sock_create(eth_if_name); + invite_peer = invite_in_container("relay", "peer"); + invite_nut = invite_in_container("relay", NUT_NODE_NAME); + node_sim_in_container_event("relay", "1", NULL, RELAY_ID, import); + wait_for_event(meta_conn01_cb, 5); + node_sim_in_container_event("peer", "1", invite_peer, PEER_ID, import); + wait_for_event(meta_conn01_cb, 5); + node_sim_in_container_event("nut", "1", invite_nut, NUT_ID, import); + wait_for_event(meta_conn01_cb, 5); + + PRINT_TEST_CASE_MSG("Waiting for peer to be connected with NUT\n"); + assert(wait_for_event(meta_conn01_cb, 60)); + assert(meta_conn01_conn); + + PRINT_TEST_CASE_MSG("Sending SIGTERM to peer\n"); + node_step_in_container("peer", "SIGTERM"); + PRINT_TEST_CASE_MSG("Waiting for peer to become unreachable\n"); + assert(wait_for_event(meta_conn01_cb, 60)); + assert(meta_conn01_closed); + + node_sim_in_container_event("peer", "1", NULL, PEER_ID, import); + wait_for_event(meta_conn01_cb, 5); + PRINT_TEST_CASE_MSG("Waiting for peer to be re-connected\n"); + wait_for_event(meta_conn01_cb, 60); + + mesh_event_destroy(); + free(invite_peer); + free(invite_nut); + + assert_int_equal(meta_conn01_reconn, true); + + return true; +} + + +static bool meta_conn02_conn; + +static bool meta_conn02_cb(mesh_event_payload_t payload) { + char event_node_name[][10] = {"RELAY", "PEER", "NUT"}; + fprintf(stderr, "%s : ", event_node_name[payload.client_id]); + + switch(payload.mesh_event) { + case META_CONN_SUCCESSFUL : + fprintf(stderr, "Meta Connection Successful\n"); + meta_conn02_conn = true; + break; + + case NODE_STARTED : + fprintf(stderr, "Node started\n"); + break; + + default: + break; + } + + return true; +} +/* Execute Meta-connections Test Case # 2 - re-connection to peer via third node + after changing IP of NUT and peer */ +static void test_case_meta_conn_02(void **state) { + execute_test(test_steps_meta_conn_02, state); +} +/* Test Steps for Meta-connections Test Case # 2 - re-connection to peer via third node + after changing IP of NUT and peer + + Test Steps: + 1. Run NUT, relay and peer nodes with relay inviting the other two nodes + 2. After connection to peer, change the NUT's IP Address and the peer node's IP Address + + Expected Result: + NUT is first disconnected from peer then automatically re-connected to peer +*/ +static bool test_steps_meta_conn_02(void) { + char *invite_peer, *invite_nut; + char *import; + + import = mesh_event_sock_create(eth_if_name); + invite_peer = invite_in_container("relay", "peer"); + invite_nut = invite_in_container("relay", NUT_NODE_NAME); + node_sim_in_container_event("relay", "1", NULL, RELAY_ID, import); + wait_for_event(meta_conn02_cb, 5); + node_sim_in_container_event("peer", "1", invite_peer, PEER_ID, import); + wait_for_event(meta_conn02_cb, 5); + node_sim_in_container_event("nut", "1", invite_nut, NUT_ID, import); + wait_for_event(meta_conn02_cb, 5); + + PRINT_TEST_CASE_MSG("Waiting for peer to be connected with NUT\n"); + assert(wait_for_event(meta_conn02_cb, 60)); + assert(meta_conn02_conn); + + meta_conn02_conn = false; + node_sim_in_container_event("peer", "1", NULL, PEER_ID, import); + wait_for_event(meta_conn02_cb, 5); + node_sim_in_container_event("nut", "1", NULL, NUT_ID, import); + wait_for_event(meta_conn02_cb, 5); + + PRINT_TEST_CASE_MSG("Waiting for peer to be connected with NUT\n"); + wait_for_event(meta_conn02_cb, 60); + + mesh_event_destroy(); + free(invite_peer); + free(invite_nut); + + assert_int_equal(meta_conn02_conn, true); + + return true; +} + +static bool meta_conn03_result; +static bool meta_conn03_conn; + +static bool meta_conn03_cb(mesh_event_payload_t payload) { + char event_node_name[][10] = {"RELAY", "PEER", "NUT"}; + fprintf(stderr, "%s : ", event_node_name[payload.client_id]); + + switch(payload.mesh_event) { + case META_CONN_SUCCESSFUL : + fprintf(stderr, "Meta Connection Successful\n"); + meta_conn03_conn = true; + break; + + case NODE_STARTED : + fprintf(stderr, "Node started\n"); + break; + + case META_RECONN_FAILURE : + fprintf(stderr, "Failed to reconnect with"); + meta_conn03_result = false; + break; + + case META_RECONN_SUCCESSFUL : + fprintf(stderr, "Reconnected\n"); + meta_conn03_result = true; + break; + + default: + break; + } + + return true; +} +/* Execute Meta-connections Test Case # 3 - re-connection to peer via third node + after changing IP of peer */ +static void test_case_meta_conn_03(void **state) { + execute_test(test_steps_meta_conn_03, state); +} +/* Test Steps for Meta-connections Test Case # 3 - re-connection to peer via third node + after changing IP of peer + + Test Steps: + 1. Run NUT, relay and peer nodes with relay inviting the other two nodes + 2. After connection to peer, change the peer node's IP Address + + Expected Result: + NUT is first disconnected from peer then automatically re-connected to peer +*/ +static bool test_steps_meta_conn_03(void) { + char *invite_peer, *invite_nut; + char *import; + + import = mesh_event_sock_create(eth_if_name); + invite_peer = invite_in_container("relay", "peer"); + invite_nut = invite_in_container("relay", NUT_NODE_NAME); + node_sim_in_container_event("relay", "1", NULL, RELAY_ID, import); + wait_for_event(meta_conn03_cb, 5); + node_sim_in_container_event("peer", "1", invite_peer, PEER_ID, import); + wait_for_event(meta_conn03_cb, 5); + node_sim_in_container_event("nut", "1", invite_nut, NUT_ID, import); + wait_for_event(meta_conn03_cb, 5); + + PRINT_TEST_CASE_MSG("Waiting for peer to be connected with NUT\n"); + assert(wait_for_event(meta_conn03_cb, 60)); + assert(meta_conn03_conn); + + PRINT_TEST_CASE_MSG("Changing IP address of PEER container\n"); + change_ip(1); + sleep(3); + node_sim_in_container_event("peer", "1", NULL, PEER_ID, import); + wait_for_event(meta_conn03_cb, 5); + PRINT_TEST_CASE_MSG("Waiting for peer to be re-connected\n"); + wait_for_event(meta_conn03_cb, 5); + + mesh_event_destroy(); + free(invite_peer); + free(invite_nut); + + assert_int_equal(meta_conn03_result, true); + + return true; +} + +static char *invite_peer = NULL; +static bool meta_conn04 = false; + +static bool meta_conn04_cb(mesh_event_payload_t payload) { + char event_node_name[][10] = {"PEER", "NUT"}; + fprintf(stderr, "%s : ", event_node_name[payload.client_id]); + + switch(payload.mesh_event) { + case META_CONN_SUCCESSFUL : + fprintf(stderr, "Meta Connection Successful\n"); + meta_conn04 = true; + break; + + case NODE_INVITATION : + fprintf(stderr, "Invitation generated\n"); + invite_peer = malloc(payload.payload_length); + strcpy(invite_peer, (char *)payload.payload); + break; + + case NODE_STARTED : + fprintf(stderr, "Node started\n"); + break; + + default : + fprintf(stderr, "Undefined mesh event\n"); + break; + } + + return true; +} + +/* Execute Meta-connections Test Case # 4 - re-connection to peer after changing IP of + NUT and peer */ +static void test_case_meta_conn_04(void **state) { + execute_test(test_steps_meta_conn_04, state); +} + +/* Execute Meta-connections Test Case # 4 - re-connection to peer after changing IP of + NUT and peer + + Test Steps: + 1. Run NUT and peer nodes with NUT inviting the peer node + 2. After connection to peer, change the NUT's IP Address and the peer node's IP Address + + Expected Result: + NUT is first disconnected from peer then automatically re-connected to peer +*/ +static bool test_steps_meta_conn_04(void) { + char *import; + + import = mesh_event_sock_create(eth_if_name); + node_sim_in_container_event("nut", "1", NULL, "1", import); + wait_for_event(meta_conn04_cb, 5); + + PRINT_TEST_CASE_MSG("Waiting for NUT to generate invitation to PEER\n"); + wait_for_event(meta_conn04_cb, 5); + + assert(invite_peer); + + PRINT_TEST_CASE_MSG("Running PEER node in the container\n"); + fprintf(stderr, "inv: %s\n", invite_peer); + node_sim_in_container_event("peer", "1", invite_peer, "0", import); + wait_for_event(meta_conn04_cb, 5); + PRINT_TEST_CASE_MSG("Waiting for peer to be connected with NUT\n"); + + assert(wait_for_event(meta_conn04_cb, 60)); + + PRINT_TEST_CASE_MSG("Changing IP address of NUT container\n"); + change_ip(1); + + node_sim_in_container_event("nut", "1", "restart", "1", import); + wait_for_event(meta_conn04_cb, 5); + PRINT_TEST_CASE_MSG("Changing IP address of PEER container\n"); + change_ip(0); + node_sim_in_container_event("peer", "1", NULL, "0", import); + wait_for_event(meta_conn04_cb, 5); + + PRINT_TEST_CASE_MSG("Waiting for peer to be re-connected\n"); + wait_for_event(meta_conn04_cb, 5); + + mesh_event_destroy(); + free(invite_peer); + free(import); + + assert_int_equal(meta_conn04, true); + + return true; +} + +static char *invitation = NULL; + +static bool meta_conn05 = false; + +static bool meta_conn05_cb(mesh_event_payload_t payload) { + char event_node_name[][10] = {"PEER", "NUT"}; + fprintf(stderr, "%s : ", event_node_name[payload.client_id]); + + switch(payload.mesh_event) { + case META_CONN_SUCCESSFUL : + meta_conn05 = true; + break; + + case NODE_INVITATION : + invitation = malloc(payload.payload_length); + strcpy(invitation, (char *)payload.payload); + break; + + case NODE_STARTED : + fprintf(stderr, "Node started\n"); + break; + + default: + break; + } + + return true; +} + +/* Execute Meta-connections Test Case # 5 - re-connection to peer after changing IP of peer */ +static void test_case_meta_conn_05(void **state) { + execute_test(test_steps_meta_conn_05, state); +} + +/* Execute Meta-connections Test Case # 5 - re-connection to peer after changing IP of peer + + Test Steps: + 1. Run NUT and peer nodes with NUT inviting the peer node + 2. After connection to peer, change the peer node's IP Address + + Expected Result: + NUT is first disconnected from peer then automatically re-connected to peer +*/ +static bool test_steps_meta_conn_05(void) { + char *import; + + import = mesh_event_sock_create(eth_if_name); + node_sim_in_container_event("nut", "1", NULL, "1", import); + wait_for_event(meta_conn05_cb, 5); + + wait_for_event(meta_conn05_cb, 5); + + assert(invitation); + + node_sim_in_container_event("peer", "1", invitation, "0", import); + wait_for_event(meta_conn05_cb, 5); + + assert(wait_for_event(meta_conn05_cb, 5)); + + change_ip(0); + meta_conn05 = false; + node_sim_in_container_event("peer", "1", NULL, "0", import); + wait_for_event(meta_conn05_cb, 5); + PRINT_TEST_CASE_MSG("Waiting for peer to be re-connected\n"); + wait_for_event(meta_conn05_cb, 5); + + mesh_event_destroy(); + free(invitation); + free(import); + + assert_int_equal(meta_conn05, true); + + return true; +} + +int test_meta_conn(void) { + const struct CMUnitTest blackbox_group0_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_meta_conn_01, setup_test, teardown_test, + (void *)&test_meta_conn_1_state), + cmocka_unit_test_prestate_setup_teardown(test_case_meta_conn_02, setup_test, teardown_test, + (void *)&test_meta_conn_2_state), + cmocka_unit_test_prestate_setup_teardown(test_case_meta_conn_03, setup_test, teardown_test, + (void *)&test_meta_conn_3_state), + cmocka_unit_test_prestate_setup_teardown(test_case_meta_conn_04, setup_test, teardown_test, + (void *)&test_meta_conn_4_state), + cmocka_unit_test_prestate_setup_teardown(test_case_meta_conn_05, setup_test, teardown_test, + (void *)&test_meta_conn_5_state) + }; + total_tests += sizeof(blackbox_group0_tests) / sizeof(blackbox_group0_tests[0]); + + return cmocka_run_group_tests(blackbox_group0_tests, black_box_group0_setup, black_box_group0_teardown); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases.h b/test/blackbox/run_blackbox_tests/test_cases.h new file mode 100644 index 0000000..9260d01 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases.h @@ -0,0 +1,29 @@ +#ifndef TEST_CASES_H +#define TEST_CASES_H + +/* + test_cases.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + + +#include + +extern int total_tests; +extern int test_meta_conn(void); + +#endif // TEST_STEP_H diff --git a/test/blackbox/run_blackbox_tests/test_cases_add_addr.c b/test/blackbox/run_blackbox_tests/test_cases_add_addr.c new file mode 100644 index 0000000..02453ef --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_add_addr.c @@ -0,0 +1,172 @@ +/* + test_cases_add_addr.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_add_addr.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include + +static void test_case_mesh_add_address_01(void **state); +static bool test_steps_mesh_add_address_01(void); +static void test_case_mesh_add_address_02(void **state); +static bool test_steps_mesh_add_address_02(void); +static void test_case_mesh_add_address_03(void **state); +static bool test_steps_mesh_add_address_03(void); + +/* State structure for meshlink_add_address Test Case #1 */ +static black_box_state_t test_mesh_add_address_01_state = { + .test_case_name = "test_case_mesh_add_address_01", +}; + +/* State structure for meshlink_add_address Test Case #2 */ +static black_box_state_t test_mesh_add_address_02_state = { + .test_case_name = "test_case_mesh_add_address_02", +}; + +/* State structure for meshlink_add_address Test Case #3 */ +static black_box_state_t test_mesh_add_address_03_state = { + .test_case_name = "test_case_mesh_add_address_03", +}; + +/* Execute meshlink_add_address Test Case # 1 */ +static void test_case_mesh_add_address_01(void **state) { + execute_test(test_steps_mesh_add_address_01, state); +} + +/* Test Steps for meshlink_add_address Test Case # 1 + + Test Steps: + 1. Create node instance + 2. Add an address to the host node + 2. Open host file from confbase & verify address being added + + Expected Result: + meshlink_add_address API adds the new address given to it's confbase +*/ +static bool test_steps_mesh_add_address_01(void) { + char *node = "foo"; + assert(meshlink_destroy("add_conf.1")); + + // Create node instance + meshlink_handle_t *mesh = meshlink_open("add_conf.1", node, "chat", DEV_CLASS_STATIONARY); + assert(mesh != NULL); + + char *hostname = "localhost"; + bool ret = meshlink_add_address(mesh, hostname); + assert_int_equal(ret, true); + + // Open the foo host file from confbase to verify address being added + bool found = false; + FILE *fp = fopen("./add_conf.1/hosts/foo", "r"); + assert(fp); + char line[100]; + + while(fgets(line, 100, fp) != NULL) { + if(strcasestr(line, "Address") && strcasestr(line, hostname)) { + found = true; + } + } + + assert(!fclose(fp)); + + assert_int_equal(found, true); + + // Clean up + meshlink_close(mesh); + assert(meshlink_destroy("add_conf.1")); + return true; +} + +/* Execute meshlink_add_address Test Case # 2 */ +static void test_case_mesh_add_address_02(void **state) { + execute_test(test_steps_mesh_add_address_02, state); +} + +/* Test Steps for meshlink_add_address Test Case # 2 + + Test Steps: + 1. Create node instance + 2. Call meshlink_add_address API using NULL as mesh handle argument + + Expected Result: + meshlink_add_address API returns false by reporting error successfully. +*/ +static bool test_steps_mesh_add_address_02(void) { + // Passing NULL as mesh handle argument to meshlink_add_address API + bool result = meshlink_add_address(NULL, "localhost"); + assert_int_equal(result, false); + + return true; +} + +/* Execute meshlink_add_address Test Case # 3 */ +static void test_case_mesh_add_address_03(void **state) { + execute_test(test_steps_mesh_add_address_03, state); +} + +/* Test Steps for meshlink_add_address Test Case # 3 + + Test Steps: + 1. Create node instance + 2. Call meshlink_add_address API using NULL as address argument + + Expected Result: + meshlink_add_address API returns false by reporting error successfully. +*/ +static bool test_steps_mesh_add_address_03(void) { + assert(meshlink_destroy("add_conf.3")); + + // Create node instance + meshlink_handle_t *mesh = meshlink_open("add_conf.3", "foo", "chat", DEV_CLASS_STATIONARY); + assert(mesh != NULL); + + bool result = meshlink_add_address(mesh, NULL); + assert_int_equal(result, false); + + meshlink_close(mesh); + assert(meshlink_destroy("add_conf.3")); + return true; +} + +int test_meshlink_add_address(void) { + const struct CMUnitTest blackbox_add_addr_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_add_address_01, NULL, NULL, + (void *)&test_mesh_add_address_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_add_address_02, NULL, NULL, + (void *)&test_mesh_add_address_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_add_address_03, NULL, NULL, + (void *)&test_mesh_add_address_03_state) + }; + + total_tests += sizeof(blackbox_add_addr_tests) / sizeof(blackbox_add_addr_tests[0]); + + return cmocka_run_group_tests(blackbox_add_addr_tests, NULL, NULL); +} + diff --git a/test/blackbox/run_blackbox_tests/test_cases_add_addr.h b/test/blackbox/run_blackbox_tests/test_cases_add_addr.h new file mode 100644 index 0000000..bfa3c05 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_add_addr.h @@ -0,0 +1,29 @@ +#ifndef TEST_CASES_ADD_ADDR_H +#define TEST_CASES_ADD_ADDR_H + +/* + test_cases_add_addr.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + + +#include + +extern int test_meshlink_add_address(void); +extern int total_tests; + +#endif //TEST_CASES_ADD_ADDR_H_INCLUDED diff --git a/test/blackbox/run_blackbox_tests/test_cases_add_ex_addr.c b/test/blackbox/run_blackbox_tests/test_cases_add_ex_addr.c new file mode 100644 index 0000000..894d3d3 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_add_ex_addr.c @@ -0,0 +1,134 @@ +/* + test_cases_add_ex_addr.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_add_ex_addr.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include + +static void test_case_mesh_add_ex_address_01(void **state); +static bool test_steps_mesh_add_ex_address_01(void); +static void test_case_mesh_add_ex_address_02(void **state); +static bool test_steps_mesh_add_ex_address_02(void); + +/* State structure for meshlink_add_external_address Test Case #1 */ +static black_box_state_t test_mesh_add_ex_address_01_state = { + .test_case_name = "test_case_mesh_add_ex_address_01", +}; + +/* State structure for meshlink_add_external_address Test Case #2 */ +static black_box_state_t test_mesh_add_ex_address_02_state = { + .test_case_name = "test_case_mesh_add_ex_address_01", +}; + +/* Execute meshlink_add_external_address Test Case # 1 */ +void test_case_mesh_add_ex_address_01(void **state) { + execute_test(test_steps_mesh_add_ex_address_01, state); +} + +/* Test Steps for meshlink_add_external_address Test Case # 1 + + Test Steps: + 1. Create node instance + 2. Get mesh's external address + 3. Add external address using meshlink_add_external_address API + 4. Open nodes confbase and read the external address from the list if addresses + + Expected Result: + meshlink_add_external_address API adds the new address given to it's confbase +*/ +bool test_steps_mesh_add_ex_address_01(void) { + assert(meshlink_destroy("addex_conf.1")); + + // Create node instance + meshlink_handle_t *mesh = meshlink_open("addex_conf.1", "foo", "test", DEV_CLASS_STATIONARY); + assert(mesh != NULL); + + char *external_address = meshlink_get_external_address(mesh); + assert(external_address); + + bool ret = meshlink_add_external_address(mesh); + assert_int_equal(ret, true); + + // Open the foo host file from confbase to verify address being added + bool found = false; + FILE *fp = fopen("./addex_conf.1/hosts/foo", "r"); + assert(fp); + char line[100]; + + while(fgets(line, 100, fp) != NULL) { + if(strcasestr(line, "Address") && strcasestr(line, external_address)) { + found = true; + } + } + + assert(!fclose(fp)); + + assert_int_equal(found, true); + + meshlink_close(mesh); + assert(meshlink_destroy("addex_conf.1")); + return true; +} + +/* Execute meshlink_add_external_address Test Case # 2 */ +void test_case_mesh_add_ex_address_02(void **state) { + execute_test(test_steps_mesh_add_ex_address_02, state); +} + +/* Test Steps for meshlink_add_external_address Test Case # 2 + + Test Steps: + 1. Create node instance + 2. Call meshlink_add_external_address API using NULL as mesh handle argument + + Expected Result: + meshlink_add_external_address API returns false by reporting error successfully. +*/ +bool test_steps_mesh_add_ex_address_02(void) { + bool result = meshlink_add_external_address(NULL); + assert_int_equal(result, false); + + return true; +} + +int test_meshlink_add_external_address(void) { + const struct CMUnitTest blackbox_add_ex_addr_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_add_ex_address_01, NULL, NULL, + (void *)&test_mesh_add_ex_address_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_add_ex_address_02, NULL, NULL, + (void *)&test_mesh_add_ex_address_02_state) + }; + + total_tests += sizeof(blackbox_add_ex_addr_tests) / sizeof(blackbox_add_ex_addr_tests[0]); + + return cmocka_run_group_tests(blackbox_add_ex_addr_tests, NULL, NULL); +} + diff --git a/test/blackbox/run_blackbox_tests/test_cases_add_ex_addr.h b/test/blackbox/run_blackbox_tests/test_cases_add_ex_addr.h new file mode 100644 index 0000000..c5eca66 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_add_ex_addr.h @@ -0,0 +1,29 @@ +#ifndef TEST_CASES_ADD_EX_ADDR_H +#define TEST_CASES_ADD_EX_ADDR_H + +/* + test_cases_add_ex_addr.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + + +#include + +extern int test_meshlink_add_external_address(void); +extern int total_tests; + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_autoconnect.c b/test/blackbox/run_blackbox_tests/test_cases_autoconnect.c new file mode 100644 index 0000000..f9c2771 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_autoconnect.c @@ -0,0 +1,160 @@ +/* + test_cases_blacklist.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +#include "execute_tests.h" +#include "test_cases_autoconnect.h" +#include +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "../../utils.h" +#include +#include +#include +#include +#include +#include + +static void test_case_autoconnect(void **state); +static bool test_steps_mesh_autoconnect(void); +static meshlink_handle_t *mesh1, *mesh2; + +/* State structure for meshlink_blacklist Test Case #1 */ +static black_box_state_t test_mesh_autoconnect_state = { + .test_case_name = "test_case_mesh_autoconnect", +}; +struct sync_flag test_autoconnect_m1n1_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +struct sync_flag test_autoconnect_blacklisted = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +struct sync_flag test_autoconnect_successful = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; + +/* Execute meshlink_blacklist Test Case # 1*/ +void test_case_autoconnect(void **state) { + execute_test(test_steps_mesh_autoconnect, state); +} + +void callback_logger(meshlink_handle_t *mesh, meshlink_log_level_t level, + const char *text) { + (void)level; + + fprintf(stderr, "%s: {%s}\n", mesh->name, text); + + if((check_sync_flag(&test_autoconnect_blacklisted) == true) && (strcmp("m1n2", mesh->name) == 0) && (strcmp("* could not find node for initial connect", text) == 0)) { + fprintf(stderr, "Test case successful\n"); + set_sync_flag(&test_autoconnect_successful, true); + } else if((check_sync_flag(&test_autoconnect_blacklisted) == true) && (strcmp("m1n2", mesh->name) == 0)) { + assert(strcmp(text, "Autoconnect trying to connect to m1n1") != 0); + } + +} + +static void receive(meshlink_handle_t *mesh, meshlink_node_t *src, const void *data, size_t len) { + (void)mesh; + (void)src; + (void)data; + assert(len); +} + +static void status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + fprintf(stderr, "Status of node {%s} is %d\n", node->name, reachable); + + if(!strcmp(node->name, "m1n1") && reachable) { + set_sync_flag(&test_autoconnect_m1n1_reachable, true); + } +} + + +/* Test Steps for meshlink_blacklist Test Case # 1 + + Test Steps: + 1. Open both the node instances + 2. Join bar node with foo and Send & Receive data + 3. Blacklist bar and Send & Receive data + + Expected Result: + When default blacklist is disabled, foo node should receive data from bar + but when enabled foo node should not receive data +*/ +bool test_steps_mesh_autoconnect(void) { + char *invite = NULL; + meshlink_node_t *node = NULL; + + assert(meshlink_destroy("m1n1")); + assert(meshlink_destroy("m1n2")); + + // Open two new meshlink instance. + mesh1 = meshlink_open("m1n1", "m1n1", "autoconnect", DEV_CLASS_BACKBONE); + assert(mesh1 != NULL); + meshlink_set_log_cb(mesh1, TEST_MESHLINK_LOG_LEVEL, callback_logger); + + mesh2 = meshlink_open("m1n2", "m1n2", "autoconnect", DEV_CLASS_STATIONARY); + assert(mesh2 != NULL); + meshlink_set_log_cb(mesh2, TEST_MESHLINK_LOG_LEVEL, callback_logger); + meshlink_set_receive_cb(mesh1, receive); + + // Start both instances + meshlink_set_node_status_cb(mesh1, status_cb); + assert(meshlink_start(mesh1)); + + invite = meshlink_invite(mesh1, NULL, "m1n2"); + assert(invite); + + assert(meshlink_join(mesh2, invite)); + + meshlink_set_node_status_cb(mesh2, status_cb); + assert(meshlink_start(mesh2)); + + assert(wait_sync_flag(&test_autoconnect_m1n1_reachable, 30)); + + node = meshlink_get_node(mesh2, "m1n1"); + assert(meshlink_blacklist(mesh2, node)); + set_sync_flag(&test_autoconnect_blacklisted, true); + + assert(wait_sync_flag(&test_autoconnect_successful, 60)); + + // Clean up. + meshlink_close(mesh1); + fprintf(stderr, "Meshlink node1 closed\n"); + meshlink_close(mesh2); + fprintf(stderr, "Meshlink node2 closed\n"); + + assert(meshlink_destroy("m1n1")); + assert(meshlink_destroy("m1n2")); + fprintf(stderr, "Meshlink nodes destroyed\n"); + + return true; +} + +int test_meshlink_autoconnect(void) { + const struct CMUnitTest blackbox_blacklist_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_autoconnect, NULL, NULL, + (void *)&test_mesh_autoconnect_state) + }; + + total_tests += sizeof(blackbox_blacklist_tests) / sizeof(blackbox_blacklist_tests[0]); + + return cmocka_run_group_tests(blackbox_blacklist_tests, NULL, NULL); +} + diff --git a/test/blackbox/run_blackbox_tests/test_cases_autoconnect.h b/test/blackbox/run_blackbox_tests/test_cases_autoconnect.h new file mode 100644 index 0000000..10b4911 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_autoconnect.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_AUTOCONNECT_H +#define TEST_CASES_AUTOCONNECT_H + +/* + test_cases_autoconnect.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_autoconnect(void); +extern int total_tests; + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_blacklist.c b/test/blackbox/run_blackbox_tests/test_cases_blacklist.c new file mode 100644 index 0000000..8f9a118 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_blacklist.c @@ -0,0 +1,233 @@ +/* + test_cases_blacklist.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +#include "execute_tests.h" +#include "test_cases_blacklist.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include + +static void test_case_mesh_blacklist_01(void **state); +static bool test_steps_mesh_blacklist_01(void); +static void test_case_mesh_blacklist_02(void **state); +static bool test_steps_mesh_blacklist_02(void); +static void test_case_mesh_blacklist_03(void **state); +static bool test_steps_mesh_blacklist_03(void); + +/* State structure for meshlink_blacklist Test Case #1 */ +static black_box_state_t test_mesh_blacklist_01_state = { + .test_case_name = "test_case_mesh_blacklist_01", +}; + +/* State structure for meshlink_blacklist Test Case #2 */ +static black_box_state_t test_mesh_blacklist_02_state = { + .test_case_name = "test_case_mesh_blacklist_02", +}; + +/* State structure for meshlink_blacklist Test Case #3 */ +static black_box_state_t test_mesh_blacklist_03_state = { + .test_case_name = "test_case_mesh_blacklist_03", +}; + +/* Execute meshlink_blacklist Test Case # 1*/ +void test_case_mesh_blacklist_01(void **state) { + execute_test(test_steps_mesh_blacklist_01, state); +} + +static bool received; + +static void receive(meshlink_handle_t *mesh, meshlink_node_t *src, const void *data, size_t len) { + (void)mesh; + + const char *msg = data; + assert(len); + + if(!strcmp(src->name, "bar") && len == 5 && !strcmp(msg, "test")) { + received = true; + } +} + +static bool bar_reachable; + +static void status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcmp(node->name, "bar") && reachable) { + bar_reachable = true; + } +} + + +/* Test Steps for meshlink_blacklist Test Case # 1 + + Test Steps: + 1. Open both the node instances + 2. Join bar node with foo and Send & Receive data + 3. Blacklist bar and Send & Receive data + + Expected Result: + When default blacklist is disabled, foo node should receive data from bar + but when enabled foo node should not receive data +*/ +bool test_steps_mesh_blacklist_01(void) { + assert(meshlink_destroy("blacklist_conf.1")); + assert(meshlink_destroy("blacklist_conf.2")); + + // Open two new meshlink instance. + meshlink_handle_t *mesh1 = meshlink_open("blacklist_conf.1", "foo", "blacklist", DEV_CLASS_BACKBONE); + assert(mesh1 != NULL); + meshlink_set_log_cb(mesh1, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_handle_t *mesh2 = meshlink_open("blacklist_conf.2", "bar", "blacklist", DEV_CLASS_BACKBONE); + assert(mesh2 != NULL); + meshlink_set_log_cb(mesh2, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_set_receive_cb(mesh1, receive); + + // Start both instances + bar_reachable = false; + meshlink_set_node_status_cb(mesh1, status_cb); + assert(meshlink_start(mesh1)); + assert(meshlink_start(mesh2)); + sleep(1); + + char *foo_export = meshlink_export(mesh1); + assert(foo_export != NULL); + assert(meshlink_import(mesh2, foo_export)); + char *bar_export = meshlink_export(mesh2); + assert(meshlink_import(mesh1, bar_export)); + sleep(5); + assert_int_equal(bar_reachable, true); + + meshlink_node_t *bar = meshlink_get_node(mesh1, "bar"); + assert(bar); + meshlink_node_t *foo = meshlink_get_node(mesh2, "foo"); + assert(foo); + + received = false; + assert(meshlink_send(mesh2, foo, "test", 5)); + sleep(1); + assert(received); + + assert(meshlink_blacklist(mesh1, bar)); + + received = false; + assert(meshlink_send(mesh2, foo, "test", 5)); + sleep(1); + assert_int_equal(received, false); + + // Clean up. + meshlink_close(mesh2); + meshlink_close(mesh1); + assert(meshlink_destroy("blacklist_conf.1")); + assert(meshlink_destroy("blacklist_conf.2")); + return true; +} + +/* Execute meshlink_blacklist Test Case # 2*/ +void test_case_mesh_blacklist_02(void **state) { + execute_test(test_steps_mesh_blacklist_02, state); +} + + +/* Test Steps for meshlink_blacklist Test Case # 2 + + Test Steps: + 1. Calling meshlink_blacklist with NULL as mesh handle argument. + + Expected Result: + meshlink_blacklist API handles the invalid parameter when called by giving proper error number. +*/ +bool test_steps_mesh_blacklist_02(void) { + assert(meshlink_destroy("blacklist_conf.3")); + + // Open two new meshlink instance. + meshlink_handle_t *mesh = meshlink_open("blacklist_conf.3", "foo", "blacklist", DEV_CLASS_BACKBONE); + assert(mesh != NULL); + + meshlink_node_t *node = meshlink_get_self(mesh); + assert(node); + + // Passing NULL as mesh handle and node handle being some valid node handle + assert(!meshlink_blacklist(NULL, node)); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + // Clean up. + meshlink_close(mesh); + assert(meshlink_destroy("blacklist_conf.3")); + return true; +} + +/* Execute meshlink_blacklist Test Case # 3*/ +void test_case_mesh_blacklist_03(void **state) { + execute_test(test_steps_mesh_blacklist_03, state); +} + +/* Test Steps for meshlink_blacklist Test Case # 3 + + Test Steps: + 1. Create node instance + 2. Calling meshlink_blacklist with NULL as node handle argument. + + Expected Result: + meshlink_blacklist API handles the invalid parameter when called by giving proper error number. +*/ +bool test_steps_mesh_blacklist_03(void) { + assert(meshlink_destroy("blacklist_conf.4")); + + // Open two new meshlink instance. + meshlink_handle_t *mesh = meshlink_open("blacklist_conf.4", "foo", "blacklist", DEV_CLASS_BACKBONE); + assert(mesh != NULL); + + // Passing NULL as node handle and mesh handle being some valid mesh handle value + assert(!meshlink_blacklist(mesh, NULL)); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + // Clean up. + meshlink_close(mesh); + assert(meshlink_destroy("blacklist_conf.4")); + return true; +} + +int test_meshlink_blacklist(void) { + const struct CMUnitTest blackbox_blacklist_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_blacklist_01, NULL, NULL, + (void *)&test_mesh_blacklist_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_blacklist_02, NULL, NULL, + (void *)&test_mesh_blacklist_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_blacklist_03, NULL, NULL, + (void *)&test_mesh_blacklist_03_state) + }; + + total_tests += sizeof(blackbox_blacklist_tests) / sizeof(blackbox_blacklist_tests[0]); + + return cmocka_run_group_tests(blackbox_blacklist_tests, NULL, NULL); +} + diff --git a/test/blackbox/run_blackbox_tests/test_cases_blacklist.h b/test/blackbox/run_blackbox_tests/test_cases_blacklist.h new file mode 100644 index 0000000..b591d02 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_blacklist.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_BLACKLIST_H +#define TEST_CASES_BLACKLIST_H + +/* + test_cases_blacklist.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_blacklist(void); +extern int total_tests; + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_channel_blacklist.c b/test/blackbox/run_blackbox_tests/test_cases_channel_blacklist.c new file mode 100644 index 0000000..568a475 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_channel_blacklist.c @@ -0,0 +1,210 @@ +/* + test_optimal_pmtu.c -- Execution of specific meshlink black box test cases + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "../common/network_namespace_framework.h" +#include "../../utils.h" +#include "test_cases_channel_blacklist.h" +#include "../test_case_channel_blacklist_01/node_sim_nut_01.h" + +typedef bool (*test_step_func_t)(void); +extern int total_tests; + +static bool test_steps_channel_blacklist_01(void); +static void test_case_channel_blacklist_01(void **state); + +static int setup_test(void **state); +static int teardown_test(void **state); +static void *gen_inv(void *arg); + +netns_state_t *test_channel_disconnection_state; + +static mesh_arg_t relay_arg = {.node_name = "relay", .confbase = "relay", .app_name = "relay", .dev_class = 0 }; +static mesh_arg_t peer_arg = {.node_name = "peer", .confbase = "peer", .app_name = "peer", .dev_class = 1 }; +static mesh_arg_t nut_arg = {.node_name = "nut", .confbase = "nut", .app_name = "nut", .dev_class = 1 }; +static mesh_invite_arg_t relay_nut_invite_arg = {.mesh_arg = &relay_arg, .invitee_name = "nut" }; +static mesh_invite_arg_t relay_peer_invite_arg = {.mesh_arg = &relay_arg, .invitee_name = "peer" }; +static netns_thread_t netns_relay_nut_invite = {.namespace_name = "relay", .netns_thread = gen_inv, .arg = &relay_nut_invite_arg}; +static netns_thread_t netns_relay_peer_invite = {.namespace_name = "relay", .netns_thread = gen_inv, .arg = &relay_peer_invite_arg}; +static netns_thread_t netns_relay_handle = {.namespace_name = "relay", .netns_thread = test_channel_blacklist_disonnection_relay_01, .arg = &relay_arg}; +static netns_thread_t netns_peer_handle = {.namespace_name = "peer", .netns_thread = test_channel_blacklist_disonnection_peer_01, .arg = &peer_arg}; +static netns_thread_t netns_nut_handle = {.namespace_name = "nut", .netns_thread = test_channel_blacklist_disonnection_nut_01, .arg = &nut_arg}; + +struct sync_flag test_channel_discon_nut_close = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; + +static int setup_test(void **state) { + (void)state; + + netns_create_topology(test_channel_disconnection_state); + fprintf(stderr, "\nCreated topology\n"); + + assert(meshlink_destroy("nut")); + assert(meshlink_destroy("peer")); + assert(meshlink_destroy("relay")); + channel_discon_case_ping = false; + channel_discon_network_failure_01 = false; + channel_discon_network_failure_02 = false; + test_channel_restart_01 = false; + set_sync_flag(&test_channel_discon_nut_close, false); + + return EXIT_SUCCESS; +} + +static int teardown_test(void **state) { + (void)state; + + assert(meshlink_destroy("nut")); + assert(meshlink_destroy("peer")); + assert(meshlink_destroy("relay")); + netns_destroy_topology(test_channel_disconnection_state); + + return EXIT_SUCCESS; +} + +static void execute_test(test_step_func_t step_func, void **state) { + (void)state; + + + fprintf(stderr, "\n\x1b[32mRunning Test\x1b[0m\n"); + bool test_result = step_func(); + + if(!test_result) { + fail(); + } +} + +static void *gen_inv(void *arg) { + mesh_invite_arg_t *mesh_invite_arg = (mesh_invite_arg_t *)arg; + meshlink_handle_t *mesh; + mesh = meshlink_open(mesh_invite_arg->mesh_arg->node_name, mesh_invite_arg->mesh_arg->confbase, mesh_invite_arg->mesh_arg->app_name, mesh_invite_arg->mesh_arg->dev_class); + assert(mesh); + + char *invitation = meshlink_invite(mesh, NULL, mesh_invite_arg->invitee_name); + assert(invitation); + mesh_invite_arg->invite_str = invitation; + meshlink_close(mesh); + + return NULL; +} + +static void launch_3_nodes(void) { + run_node_in_namespace_thread(&netns_relay_nut_invite); + sleep(1); + assert(relay_nut_invite_arg.invite_str); + nut_arg.join_invitation = relay_nut_invite_arg.invite_str; + + run_node_in_namespace_thread(&netns_relay_peer_invite); + sleep(1); + assert(relay_peer_invite_arg.invite_str); + peer_arg.join_invitation = relay_peer_invite_arg.invite_str; + + relay_arg.join_invitation = NULL; + + run_node_in_namespace_thread(&netns_relay_handle); + sleep(1); + + run_node_in_namespace_thread(&netns_peer_handle); + sleep(1); + + run_node_in_namespace_thread(&netns_nut_handle); +} + +static void test_case_channel_blacklist_01(void **state) { + execute_test(test_steps_channel_blacklist_01, state); + return; +} + +static bool test_steps_channel_blacklist_01(void) { + + launch_3_nodes(); + + wait_sync_flag(&test_channel_discon_nut_close, 240); + + test_channel_blacklist_disonnection_peer_01_running = false; + test_channel_blacklist_disonnection_relay_01_running = false; + assert_int_equal(total_reachable_callbacks_01, 1); + assert_int_equal(total_unreachable_callbacks_01, 1); + assert_int_equal(total_channel_closure_callbacks_01, 2); + + return true; +} + +int test_meshlink_channel_blacklist(void) { + + interface_t relay_ifs[] = { { .if_peer = "wan_bridge" } }; + namespace_t relay = { + .name = "relay", + .type = HOST, + .interfaces = relay_ifs, + .interfaces_no = 1, + }; + + interface_t peer_ifs[] = { { .if_peer = "wan_bridge" } }; + namespace_t peer = { + .name = "peer", + .type = HOST, + .interfaces = peer_ifs, + .interfaces_no = 1, + }; + + interface_t nut_ifs[] = { { .if_peer = "wan_bridge" } }; + namespace_t nut = { + .name = "nut", + .type = HOST, + .interfaces = nut_ifs, + .interfaces_no = 1, + }; + + interface_t wan_ifs[] = { { .if_peer = "peer" }, { .if_peer = "nut" }, { .if_peer = "relay" } }; + namespace_t wan_bridge = { + .name = "wan_bridge", + .type = BRIDGE, + .interfaces = wan_ifs, + .interfaces_no = 3, + }; + + namespace_t test_channel_nodes[] = { relay, wan_bridge, nut, peer }; + + netns_state_t test_channels_nodes = { + .test_case_name = "test_case_channel", + .namespaces = test_channel_nodes, + .num_namespaces = 4, + }; + test_channel_disconnection_state = &test_channels_nodes; + + const struct CMUnitTest blackbox_group0_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_channel_blacklist_01, setup_test, teardown_test, + (void *)NULL), + }; + total_tests += sizeof(blackbox_group0_tests) / sizeof(blackbox_group0_tests[0]); + + return cmocka_run_group_tests(blackbox_group0_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_channel_blacklist.h b/test/blackbox/run_blackbox_tests/test_cases_channel_blacklist.h new file mode 100644 index 0000000..5d9445f --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_channel_blacklist.h @@ -0,0 +1,43 @@ +#ifndef TEST_CASES_CHANNEL_CONN_H +#define TEST_CASES_CHANNEL_CONN_H + +/* + test_cases_channel_blacklist.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + + +#include + +extern int test_meshlink_channel_blacklist(void); + +extern void *test_channel_blacklist_disonnection_nut_01(void *arg); +extern void *test_channel_blacklist_disonnection_peer_01(void *arg); +extern void *test_channel_blacklist_disonnection_relay_01(void *arg); + +extern int total_reachable_callbacks_01; +extern int total_unreachable_callbacks_01; +extern int total_channel_closure_callbacks_01; +extern bool channel_discon_case_ping; +extern bool channel_discon_network_failure_01; +extern bool channel_discon_network_failure_02; +extern bool test_channel_blacklist_disonnection_peer_01_running; +extern bool test_channel_blacklist_disonnection_relay_01_running; +extern bool test_blacklist_whitelist_01; +extern bool test_channel_restart_01; + +#endif // TEST_CASES_CHANNEL_CONN_H diff --git a/test/blackbox/run_blackbox_tests/test_cases_channel_close.c b/test/blackbox/run_blackbox_tests/test_cases_channel_close.c new file mode 100644 index 0000000..9f2c9c9 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_channel_close.c @@ -0,0 +1,147 @@ +/* + test_cases_channel_close.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_channel_close.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include + +static void test_case_mesh_channel_close_01(void **state); +static bool test_steps_mesh_channel_close_01(void); +static void test_case_mesh_channel_close_02(void **state); +static bool test_steps_mesh_channel_close_02(void); + +/* State structure for meshlink_channel_close Test Case #1 */ +static black_box_state_t test_mesh_channel_close_01_state = { + .test_case_name = "test_case_mesh_channel_close_01", +}; + +/* State structure for meshlink_channel_close Test Case #2 */ +static black_box_state_t test_mesh_channel_close_02_state = { + .test_case_name = "test_case_mesh_channel_close_02", +}; + +/* Execute meshlink_channel_close Test Case # 1*/ +static void test_case_mesh_channel_close_01(void **state) { + execute_test(test_steps_mesh_channel_close_01, state); + return; +} + +/* Test Steps for meshlink_channel_close Test Case # 1*/ +static bool test_steps_mesh_channel_close_01(void) { + assert(meshlink_destroy("chan_close_conf.3")); + assert(meshlink_destroy("chan_close_conf.4")); + + // Open two new meshlink instance. + meshlink_handle_t *mesh1 = meshlink_open("chan_close_conf.3", "foo", "channels", DEV_CLASS_BACKBONE); + assert(mesh1 != NULL); + + meshlink_handle_t *mesh2 = meshlink_open("chan_close_conf.4", "bar", "channels", DEV_CLASS_BACKBONE); + assert(mesh2 != NULL); + + if(!mesh2) { + fprintf(stderr, "Could not initialize configuration for bar\n"); + return false; + } + + char *exp = meshlink_export(mesh1); + assert(exp != NULL); + assert(meshlink_import(mesh2, exp)); + free(exp); + exp = meshlink_export(mesh2); + assert(exp != NULL); + assert(meshlink_import(mesh1, exp)); + free(exp); + + // Start both instances + assert(meshlink_start(mesh1)); + assert(meshlink_start(mesh2)); + sleep(2); + + // Open a channel from foo to bar. + + meshlink_node_t *bar = meshlink_get_node(mesh1, "bar"); + assert(bar != NULL); + meshlink_channel_t *channel = meshlink_channel_open(mesh1, bar, 7, NULL, NULL, 0); + assert(channel != NULL); + + meshlink_channel_close(NULL, channel); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + + // Clean up. + + meshlink_close(mesh2); + meshlink_close(mesh1); + assert(meshlink_destroy("chan_close_conf.3")); + assert(meshlink_destroy("chan_close_conf.4")); + return true; +} + +/* Execute meshlink_channel_close Test Case # 2*/ +static void test_case_mesh_channel_close_02(void **state) { + execute_test(test_steps_mesh_channel_close_02, state); + return; +} + +/* Test Steps for meshlink_channel_close Test Case # 2*/ +static bool test_steps_mesh_channel_close_02(void) { + assert(meshlink_destroy("chan_close_conf.5")); + // Open two new meshlink instance. + + meshlink_handle_t *mesh = meshlink_open("chan_close_conf.5", "foo", "channels", DEV_CLASS_BACKBONE); + assert(mesh != NULL); + + // Start both instances + assert(meshlink_start(mesh)); + + // Pass NULL as mesh handle + meshlink_channel_close(mesh, NULL); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + // Clean up. + + meshlink_close(mesh); + assert(meshlink_destroy("chan_close_conf.5")); + return true; +} + +int test_meshlink_channel_close(void) { + const struct CMUnitTest blackbox_channel_close_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_channel_close_01, NULL, NULL, + (void *)&test_mesh_channel_close_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_channel_close_02, NULL, NULL, + (void *)&test_mesh_channel_close_02_state) + }; + + total_tests += sizeof(blackbox_channel_close_tests) / sizeof(blackbox_channel_close_tests[0]); + + return cmocka_run_group_tests(blackbox_channel_close_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_channel_close.h b/test/blackbox/run_blackbox_tests/test_cases_channel_close.h new file mode 100644 index 0000000..7203a19 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_channel_close.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_CHANNEL_CLOSE_H +#define TEST_CASES_CHANNEL_CLOSE_H + +/* + test_cases_channel_close.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_channel_close(void); +extern int total_tests; + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_channel_conn.c b/test/blackbox/run_blackbox_tests/test_cases_channel_conn.c new file mode 100644 index 0000000..9bed91b --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_channel_conn.c @@ -0,0 +1,802 @@ +/* + test_cases_channel_conn.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "execute_tests.h" +#include "test_cases_channel_conn.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "../common/mesh_event_handler.h" + +#define PEER_ID "0" +#define NUT_ID "1" +#define RELAY_ID "2" + +static void test_case_channel_conn_01(void **state); +static bool test_steps_channel_conn_01(void); +static void test_case_channel_conn_02(void **state); +static bool test_steps_channel_conn_02(void); +static void test_case_channel_conn_03(void **state); +static bool test_steps_channel_conn_03(void); +static void test_case_channel_conn_04(void **state); +static bool test_steps_channel_conn_04(void); +static void test_case_channel_conn_05(void **state); +static bool test_steps_channel_conn_05(void); +static void test_case_channel_conn_06(void **state); +static bool test_steps_channel_conn_06(void); +static void test_case_channel_conn_07(void **state); +static bool test_steps_channel_conn_07(void); +static void test_case_channel_conn_08(void **state); +static bool test_steps_channel_conn_08(void); + +static char *test_channel_conn_2_nodes[] = { "peer", "nut" }; +static char *test_channel_conn_3_nodes[] = { "peer", "nut", "relay" }; + +static black_box_state_t test_case_channel_conn_01_state = { + .test_case_name = "test_case_channel_conn_01", + .node_names = test_channel_conn_2_nodes, + .num_nodes = 2, +}; +static black_box_state_t test_case_channel_conn_02_state = { + .test_case_name = "test_case_channel_conn_02", + .node_names = test_channel_conn_2_nodes, + .num_nodes = 2, +}; +static black_box_state_t test_case_channel_conn_03_state = { + .test_case_name = "test_case_channel_conn_03", + .node_names = test_channel_conn_2_nodes, + .num_nodes = 2, +}; +static black_box_state_t test_case_channel_conn_04_state = { + .test_case_name = "test_case_channel_conn_04", + .node_names = test_channel_conn_2_nodes, + .num_nodes = 2, +}; +static black_box_state_t test_case_channel_conn_05_state = { + .test_case_name = "test_case_channel_conn_05", + .node_names = test_channel_conn_3_nodes, + .num_nodes = 3, +}; +static black_box_state_t test_case_channel_conn_06_state = { + .test_case_name = "test_case_channel_conn_06", + .node_names = test_channel_conn_3_nodes, + .num_nodes = 3, +}; +static black_box_state_t test_case_channel_conn_07_state = { + .test_case_name = "test_case_channel_conn_07", + .node_names = test_channel_conn_3_nodes, + .num_nodes = 3, +}; +static black_box_state_t test_case_channel_conn_08_state = { + .test_case_name = "test_case_channel_conn_08", + .node_names = test_channel_conn_3_nodes, + .num_nodes = 3, +}; + +static bool joined; +static bool channel_opened; +static bool node_restarted; +static bool received_error; +static bool channel_received; +static bool node_reachable; +static bool node_unreachable; + +/* Callback function for handling channel connection test cases mesh events */ +static bool channel_conn_cb(mesh_event_payload_t payload) { + switch(payload.mesh_event) { + case NODE_JOINED : + joined = true; + break; + + case CHANNEL_OPENED : + channel_opened = true; + break; + + case NODE_RESTARTED : + node_restarted = true; + break; + + case ERR_NETWORK : + received_error = true; + break; + + case CHANNEL_DATA_RECIEVED : + channel_received = true; + break; + + case NODE_UNREACHABLE : + node_unreachable = true; + break; + + case NODE_REACHABLE : + node_reachable = true; + break; + + default : + PRINT_TEST_CASE_MSG("Undefined event occurred\n"); + } + + return true; +} + +/* Execute channel connections Test Case # 1 - simulate a temporary network + failure of about 30 seconds, messages sent while the network was down + should be received by the other side after the network comes up again. */ +static void test_case_channel_conn_01(void **state) { + execute_test(test_steps_channel_conn_01, state); + return; +} + +/* Test Steps for channel connections Test Case # 1 + + Test Steps: + 1. Run NUT & peer node instances and open a channel between them + 2. Simulate a network failure in NUT's container for about 30 secs, + meanwhile send data via channel from NUT to peer. + 3. After restoring network, peer node receive's data via channel. + + Expected Result: + Peer node receives data via channel without any error after restoring network. +*/ +static bool test_steps_channel_conn_01(void) { + char *invite_nut; + char *import; + + joined = false; + channel_opened = false; + channel_received = false; + + // Setup Containers + + install_in_container("nut", "iptables"); + accept_port_rule("nut", "OUTPUT", "udp", 9000); + import = mesh_event_sock_create(eth_if_name); + invite_nut = invite_in_container("peer", "nut"); + assert(invite_nut); + + // Run node instances in containers & open a channel + + node_sim_in_container_event("peer", "1", NULL, PEER_ID, import); + node_sim_in_container_event("nut", "1", invite_nut, NUT_ID, import); + + wait_for_event(channel_conn_cb, 30); + assert_int_equal(joined, true); + + wait_for_event(channel_conn_cb, 30); + assert_int_equal(channel_opened, true); + + // Simulate network failure in NUT's LXC container with it's IP address as NAT rule + + block_node_ip("nut"); + sleep(2); + + // Sending SIGUSR1 signal to node-under-test indicating the network failure + + node_step_in_container("nut", "SIGUSR1"); + sleep(30); + + // Restore NUT's network + + unblock_node_ip("nut"); + + // Wait for peer node to receive data via channel from NUT + + wait_for_event(channel_conn_cb, 60); + + mesh_event_destroy(); + free(invite_nut); + free(import); + + assert_int_equal(channel_received, true); + + return true; +} + +/* Execute channel connections Test Case # 2 - a simulated network failure + of more than 1 minute, and sending messages over the channel during the + failure. Then after about 1 minute, the channel should receive an error */ +static void test_case_channel_conn_02(void **state) { + execute_test(test_steps_channel_conn_02, state); + return; +} + +/* Test Steps for channel connections Test Case # 2 + + Test Steps: + 1. Run NUT and peer node instances in containers and open a channel between them. + 2. Create a network failure for about 90 secs in NUT container + and signal NUT node about the network failure. + 3. Meanwhile NUT sends data to peer via channel and restore the network after + 90 secs. + + Expected Result: + Peer node should receive error closing the channel after channel timeout(60 secs). +*/ +static bool test_steps_channel_conn_02(void) { + char *invite_nut; + char *import; + + joined = false; + channel_opened = false; + received_error = false; + + // Setup containers + + install_in_container("nut", "iptables"); + accept_port_rule("nut", "OUTPUT", "udp", 9000); + import = mesh_event_sock_create(eth_if_name); + invite_nut = invite_in_container("peer", "nut"); + assert(invite_nut); + + // Run NUT and peer node instances in containers & open a channel + + node_sim_in_container_event("peer", "1", NULL, PEER_ID, import); + node_sim_in_container_event("nut", "1", invite_nut, NUT_ID, import); + + wait_for_event(channel_conn_cb, 30); + assert_int_equal(joined, true); + + wait_for_event(channel_conn_cb, 10); + assert_int_equal(channel_opened, true); + + // Simulate network failure in NUT's LXC container with it's IP address as NAT rule + + block_node_ip("nut"); + + // Sending SIGUSR1 signal to node-under-test indicating the network failure + + node_step_in_container("nut", "SIGUSR1"); + sleep(90); + + // Restore NUT containers network after 90 secs + + unblock_node_ip("nut"); + + // Wait for peer node to send the event about the channel error occurred with length = 0 + + wait_for_event(channel_conn_cb, 90); + + mesh_event_destroy(); + free(invite_nut); + free(import); + + assert_int_equal(received_error, true); + + return true; +} + +/* Execute channel connections Test Case # 3 - a simulated network failure + once node instance is made offline restore the network and send data via + channel */ +static void test_case_channel_conn_03(void **state) { + execute_test(test_steps_channel_conn_03, state); + return; +} + +/* Test Steps for channel connections Test Case # 3 + + Test Steps: + 1. Run NUT and peer node instances and open a channel between them. + 2. Create a network failure in NUT container, bring NUT node offline + and receive the status at test driver and restore the network + 3. After peer node instance is reachable to NUT node send data via channel + + Expected Result: + Peer node should receive data from NUT without any error. +*/ +static bool test_steps_channel_conn_03(void) { + char *invite_nut; + char *import; + + joined = false; + channel_opened = false; + node_unreachable = false; + node_reachable = false; + channel_received = false; + + // Setup containers + + install_in_container("nut", "iptables"); + accept_port_rule("nut", "OUTPUT", "udp", 9000); + import = mesh_event_sock_create(eth_if_name); + invite_nut = invite_in_container("peer", "nut"); + assert(invite_nut); + + // Run NUT and peer node instances in containers & open a channel + + node_sim_in_container_event("peer", "1", NULL, PEER_ID, import); + node_sim_in_container_event("nut", "1", invite_nut, NUT_ID, import); + + wait_for_event(channel_conn_cb, 30); + assert_int_equal(joined, true); + + wait_for_event(channel_conn_cb, 10); + assert_int_equal(channel_opened, true); + + // Simulate network failure in NUT's LXC container with it's IP address as NAT rule + + node_reachable = false; + block_node_ip("nut"); + + // Sending SIGUSR1 signal to node-under-test indicating the network failure + + node_step_in_container("nut", "SIGUSR1"); + + // Wait for the node status to become unreachable + + wait_for_event(channel_conn_cb, 100); + assert_int_equal(node_unreachable, true); + + // Restore NUT container's network + + unblock_node_ip("nut"); + + // Wait for the node status to become reachable + + wait_for_event(channel_conn_cb, 100); + assert_int_equal(node_reachable, true); + + // Wait for data to be received at peer via channel from NUT after restoring n/w + + wait_for_event(channel_conn_cb, 90); + + mesh_event_destroy(); + free(invite_nut); + free(import); + + assert_int_equal(channel_received, true); + + return true; +} + +/* Execute channel connections Test Case # 4 - receiving an error when node-under-test + tries to send data on channel to peer node after peer node stops and starts the + node instance */ +static void test_case_channel_conn_04(void **state) { + execute_test(test_steps_channel_conn_04, state); + return; +} + +/* Test Steps for Meta-connections Test Case # 4 + + Test Steps: + 1. Run peer and NUT node instances in containers and open a channel between them. + 2. Stop and start the NUT node instance and wait for about > 60 secs. + 3. Send data via channel from Peer node and wait for event in test driver. + + Expected Result: + Peer node should receive error(as length = 0) in receive callback of peer node's instance. +*/ +static bool test_steps_channel_conn_04(void) { + char *invite_nut; + char *import; + + joined = false; + channel_opened = false; + node_restarted = false; + received_error = false; + import = mesh_event_sock_create(eth_if_name); + invite_nut = invite_in_container("peer", "nut"); + assert(invite_nut); + + // Run NUT and peer node instances in containers and open a channel + + node_sim_in_container_event("peer", "1", NULL, PEER_ID, import); + node_sim_in_container_event("nut", "1", invite_nut, NUT_ID, import); + + wait_for_event(channel_conn_cb, 10); + assert_int_equal(joined, true); + + wait_for_event(channel_conn_cb, 10); + assert_int_equal(channel_opened, true); + + // Wait for NUT node instance to stop and start + + wait_for_event(channel_conn_cb, 60); + assert_int_equal(node_restarted, true); + + sleep(60); + + // After 1 min the channel between NUT and peer should result in error + + wait_for_event(channel_conn_cb, 10); + + + mesh_event_destroy(); + free(invite_nut); + free(import); + + assert_int_equal(received_error, true); + + return true; +} + +/* Execute channel connections Test Case # 5 - simulate a temporary network + failure of about 30 seconds, messages sent while the network was down + should be received by the other side after the network comes up again. */ +static void test_case_channel_conn_05(void **state) { + execute_test(test_steps_channel_conn_05, state); + return; +} + +/* Test Steps for channel connections Test Case # 5 + + Test Steps: + 1. Run NUT, relay & peer node instances with relay inviting NUT and peer + and open a channel between them + 2. Simulate a network failure in NUT's container for about 30 secs, + meanwhile send data via channel from NUT to peer. + 3. After restoring network, peer node receive's data via channel. + + Expected Result: + Peer node receives data via channel without any error after restoring network. +*/ +static bool test_steps_channel_conn_05(void) { + char *invite_nut, *invite_peer; + char *import; + + joined = false; + channel_opened = false; + channel_received = false; + + // Setup containers + + install_in_container("nut", "iptables"); + accept_port_rule("nut", "OUTPUT", "udp", 9000); + import = mesh_event_sock_create(eth_if_name); + invite_peer = invite_in_container("relay", "peer"); + invite_nut = invite_in_container("relay", "nut"); + assert(invite_nut); + assert(invite_peer); + + // Run node instances and open a channel between NUT and peer nodes + + node_sim_in_container_event("relay", "1", NULL, RELAY_ID, import); + node_sim_in_container_event("peer", "1", invite_peer, PEER_ID, import); + node_sim_in_container_event("nut", "1", invite_nut, NUT_ID, import); + + wait_for_event(channel_conn_cb, 30); + assert_int_equal(joined, true); + + wait_for_event(channel_conn_cb, 30); + assert_int_equal(channel_opened, true); + + // Create a network failure in NUT node's container with it's IP address + + block_node_ip("nut"); + + // Sending SIGUSR1 signal to node-under-test indicating the network failure + + node_step_in_container("nut", "SIGUSR1"); + sleep(30); + + // Restore the network + + unblock_node_ip("nut"); + + // Wait for peer to get data from NUT node via channel after restoring network in < 60 secs + + wait_for_event(channel_conn_cb, 60); + + mesh_event_destroy(); + free(invite_peer); + free(invite_nut); + free(import); + + assert_int_equal(channel_received, true); + + return true; +} + +/* Execute channel connections Test Case # 6 - a simulated network failure + of more than 1 minute, and sending messages over the channel during the + failure. Then after about 1 minute, the channel should receive an error */ +static void test_case_channel_conn_06(void **state) { + execute_test(test_steps_channel_conn_06, state); + return; +} + +/* Test Steps for channel connections Test Case # 6 + + Test Steps: + 1. Run NUT, relay & peer node instances with relay inviting NUT and peer + and open a channel between them + 2. Create a network failure for about 90 secs in NUT container + and signal NUT node about the network failure. + 3. Meanwhile NUT sends data to peer via channel and restore the network after + 90 secs. + + Expected Result: + Peer node should receive error closing the channel after channel timeout(60 secs). +*/ +static bool test_steps_channel_conn_06(void) { + char *invite_nut, *invite_peer; + char *import; + + joined = false; + channel_opened = false; + received_error = false; + + // Setup containers + + install_in_container("nut", "iptables"); + accept_port_rule("nut", "OUTPUT", "udp", 9000); + import = mesh_event_sock_create(eth_if_name); + invite_peer = invite_in_container("relay", "peer"); + assert(invite_peer); + invite_nut = invite_in_container("relay", "nut"); + assert(invite_nut); + + // Run nodes in containers and open a channel between NUt and peer + + node_sim_in_container_event("relay", "1", NULL, RELAY_ID, import); + node_sim_in_container_event("peer", "1", invite_peer, PEER_ID, import); + node_sim_in_container_event("nut", "1", invite_nut, NUT_ID, import); + + wait_for_event(channel_conn_cb, 30); + assert_int_equal(joined, true); + + wait_for_event(channel_conn_cb, 10); + assert_int_equal(channel_opened, true); + + // Simulate a network failure in NUT's container for > 60 secs + + block_node_ip("nut"); + + // Sending SIGUSR1 signal to node-under-test indicating the network failure + + node_step_in_container("nut", "SIGUSR1"); + sleep(90); + + // Restore the network after 90 secs + + unblock_node_ip("nut"); + + // Wait for channel to receive error and receive the event + + wait_for_event(channel_conn_cb, 90); + + mesh_event_destroy(); + free(invite_peer); + free(invite_nut); + free(import); + + assert_int_equal(received_error, true); + + return true; +} + +/* Execute channel connections Test Case # 7 - a simulated network failure + once node instance is made offline restore the network and send data via + channel */ +static void test_case_channel_conn_07(void **state) { + execute_test(test_steps_channel_conn_07, state); + return; +} + +/* Test Steps for channel connections Test Case # 7 + + Test Steps: + 1. Run NUT, relay & peer node instances with relay inviting NUT and peer + and open a channel between them + 2. Create a network failure in NUT container, bring NUT node offline + and receive the status at test driver and restore the network + 3. After peer node instance is reachable to NUT node send data via channel + + Expected Result: + Peer node should receive data from NUT without any error. +*/ +static bool test_steps_channel_conn_07(void) { + char *invite_nut, *invite_peer; + char *import; + + joined = false; + channel_opened = false; + node_unreachable = false; + node_reachable = false; + channel_received = false; + + // Setup containers + + install_in_container("nut", "iptables"); + accept_port_rule("nut", "OUTPUT", "udp", 9000); + import = mesh_event_sock_create(eth_if_name); + invite_peer = invite_in_container("relay", "peer"); + invite_nut = invite_in_container("relay", "nut"); + assert(invite_nut); + assert(invite_peer); + + // Run nodes and open a channel + + node_sim_in_container_event("relay", "1", NULL, RELAY_ID, import); + node_sim_in_container_event("peer", "1", invite_peer, PEER_ID, import); + node_sim_in_container_event("nut", "1", invite_nut, NUT_ID, import); + + wait_for_event(channel_conn_cb, 30); + assert_int_equal(joined, true); + + wait_for_event(channel_conn_cb, 15); + assert_int_equal(channel_opened, true); + + // Simulate a network failure + + node_reachable = false; + block_node_ip("nut"); + + // Sending SIGUSR1 signal to node-under-test indicating the network failure + + node_step_in_container("nut", "SIGUSR1"); + + // Wait for node to become unreachable + + wait_for_event(channel_conn_cb, 100); + assert_int_equal(node_unreachable, true); + + // Restore the network + + unblock_node_ip("nut"); + + // Wait for node to become reachable after restoring n/w + + wait_for_event(channel_conn_cb, 100); + assert_int_equal(node_reachable, true); + + // Wait for peer node to receive data via channel without any error + + wait_for_event(channel_conn_cb, 90); + + mesh_event_destroy(); + free(invite_peer); + free(invite_nut); + free(import); + + assert_int_equal(channel_received, true); + + return true; +} + +/* Execute channel connections Test Case # 8 - receiving an error when node-under-test + tries to send data on channel to peer node after peer node stops and starts the + node instance */ +static void test_case_channel_conn_08(void **state) { + execute_test(test_steps_channel_conn_08, state); + return; +} + +/* Test Steps for Meta-connections Test Case # 8 + + Test Steps: + 1. Run NUT, relay & peer node instances with relay inviting NUT and peer + and open a channel between them + 2. Stop and start the NUT node instance and wait for about > 60 secs. + 3. Send data via channel from Peer node and wait for event in test driver. + + Expected Result: + Peer node should receive error(as length = 0) in receive callback of peer node's instance. +*/ +static bool test_steps_channel_conn_08(void) { + char *invite_nut, *invite_peer; + char *import; + + joined = false; + channel_opened = false; + node_restarted = false; + received_error = false; + + // Setup containers + + import = mesh_event_sock_create(eth_if_name); + invite_peer = invite_in_container("relay", "peer"); + invite_nut = invite_in_container("relay", "nut"); + assert(invite_nut); + assert(invite_peer); + + // Run nodes and open a channel between NUT and peer + + node_sim_in_container_event("relay", "1", NULL, RELAY_ID, import); + node_sim_in_container_event("peer", "1", invite_peer, PEER_ID, import); + node_sim_in_container_event("nut", "1", invite_nut, NUT_ID, import); + + wait_for_event(channel_conn_cb, 10); + assert_int_equal(joined, true); + + wait_for_event(channel_conn_cb, 10); + assert_int_equal(channel_opened, true); + + // Wait for NUT node to restart it's instance + + wait_for_event(channel_conn_cb, 60); + assert_int_equal(node_restarted, true); + + sleep(60); + + // Signal peer to send data to NUT node via channel + + node_step_in_container("peer", "SIGUSR1"); + + // Wait for peer to receive channel error + + wait_for_event(channel_conn_cb, 10); + + mesh_event_destroy(); + free(invite_peer); + free(invite_nut); + free(import); + + assert_int_equal(received_error, true); + + return true; +} + +static int black_box_group_setup(void **state) { + (void)state; + + const char *nodes[] = { "peer", "nut", "relay" }; + int num_nodes = sizeof(nodes) / sizeof(nodes[0]); + + printf("Creating Containers\n"); + destroy_containers(); + create_containers(nodes, num_nodes); + + return 0; +} + +static int black_box_group_teardown(void **state) { + (void)state; + + printf("Destroying Containers\n"); + destroy_containers(); + + return 0; +} + +int test_meshlink_channel_conn(void) { + const struct CMUnitTest blackbox_group0_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_channel_conn_01, setup_test, teardown_test, + (void *)&test_case_channel_conn_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_channel_conn_02, setup_test, teardown_test, + (void *)&test_case_channel_conn_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_channel_conn_03, setup_test, teardown_test, + (void *)&test_case_channel_conn_03_state), + cmocka_unit_test_prestate_setup_teardown(test_case_channel_conn_04, setup_test, teardown_test, + (void *)&test_case_channel_conn_04_state), + cmocka_unit_test_prestate_setup_teardown(test_case_channel_conn_05, setup_test, teardown_test, + (void *)&test_case_channel_conn_05_state), + cmocka_unit_test_prestate_setup_teardown(test_case_channel_conn_06, setup_test, teardown_test, + (void *)&test_case_channel_conn_06_state), + cmocka_unit_test_prestate_setup_teardown(test_case_channel_conn_07, setup_test, teardown_test, + (void *)&test_case_channel_conn_07_state), + cmocka_unit_test_prestate_setup_teardown(test_case_channel_conn_08, setup_test, teardown_test, + (void *)&test_case_channel_conn_08_state), + }; + total_tests += sizeof(blackbox_group0_tests) / sizeof(blackbox_group0_tests[0]); + + return cmocka_run_group_tests(blackbox_group0_tests, black_box_group_setup, black_box_group_teardown); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_channel_conn.h b/test/blackbox/run_blackbox_tests/test_cases_channel_conn.h new file mode 100644 index 0000000..f05e165 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_channel_conn.h @@ -0,0 +1,29 @@ +#ifndef TEST_CASES_CHANNEL_CONN_H +#define TEST_CASES_CHANNEL_CONN_H + +/* + test_cases_channel_conn.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + + +#include + +extern int total_tests; +extern int test_meshlink_channel_conn(void); + +#endif // TEST_CASES_CHANNEL_CONN_H diff --git a/test/blackbox/run_blackbox_tests/test_cases_channel_ex.c b/test/blackbox/run_blackbox_tests/test_cases_channel_ex.c new file mode 100644 index 0000000..2687477 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_channel_ex.c @@ -0,0 +1,664 @@ +/* + test_cases_channel_ex.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_channel_ex.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "../../utils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG +/* Modify this to change the port number */ +#define PORT 8000 + +#define NUT "nut" +#define PEER "peer" +#define TEST_CHANNEL_OPEN "test_channel_open" +#define create_path(confbase, node_name, test_case_no) assert(snprintf(confbase, sizeof(confbase), TEST_CHANNEL_OPEN "_%ld_%s_%02d", (long) getpid(), node_name, test_case_no) > 0) + +typedef struct test_cb_data { + size_t cb_data_len; + size_t cb_total_data_len; + int total_cb_count; + void (*cb_handler)(void); + bool cb_flag; +} test_cb_t; + +static void test_case_channel_ex_01(void **state); +static bool test_steps_channel_ex_01(void); +static void test_case_channel_ex_02(void **state); +static bool test_steps_channel_ex_02(void); +static void test_case_channel_ex_03(void **state); +static bool test_steps_channel_ex_03(void); +static void test_case_channel_ex_04(void **state); +static bool test_steps_channel_ex_04(void); +static void test_case_channel_ex_05(void **state); +static bool test_steps_channel_ex_05(void); +static void test_case_channel_ex_06(void **state); +static bool test_steps_channel_ex_06(void); +static void test_case_channel_ex_07(void **state); +static bool test_steps_channel_ex_07(void); + +static void cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +/* channel_acc gives us access to test whether the accept callback has been invoked or not */ +static bool channel_acc; +/* mutex for the common variable */ +pthread_mutex_t lock; + +static black_box_state_t test_case_channel_ex_01_state = { + .test_case_name = "test_case_channel_ex_01", +}; +static black_box_state_t test_case_channel_ex_02_state = { + .test_case_name = "test_case_channel_ex_02", +}; +static black_box_state_t test_case_channel_ex_03_state = { + .test_case_name = "test_case_channel_ex_03", +}; +static black_box_state_t test_case_channel_ex_04_state = { + .test_case_name = "test_case_channel_ex_04", +}; +static black_box_state_t test_case_channel_ex_05_state = { + .test_case_name = "test_case_channel_ex_05", +}; +static black_box_state_t test_case_channel_ex_06_state = { + .test_case_name = "test_case_channel_ex_06", +}; +static black_box_state_t test_case_channel_ex_07_state = { + .test_case_name = "test_case_channel_ex_07", +}; +/* mutex for the common variable */ +static pthread_mutex_t accept_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t accept_cond = PTHREAD_COND_INITIALIZER; + +static bool channel_acc; + +/* channel receive callback */ +static void cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + (void)channel; + (void)dat; + (void)len; + + return; +} + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)mesh; + (void)channel; + (void)dat; + (void)len; + + assert_int_equal(port, PORT); + + pthread_mutex_lock(&accept_lock); + channel_acc = true; + assert(!pthread_cond_broadcast(&accept_cond)); + pthread_mutex_unlock(&accept_lock); + + return true; +} + +/* Execute meshlink_channel_open_ex Test Case # 1 - testing meshlink_channel_open_ex API's + valid case by passing all valid arguments */ +static void test_case_channel_ex_01(void **state) { + execute_test(test_steps_channel_ex_01, state); +} + +/* Test Steps for meshlink_channel_open_ex Test Case # 1 - Valid case + + Test Steps: + 1. Run NUT(Node Under Test) + 2. Open channel to ourself + + Expected Result: + Opens a channel and echoes the send queue data. +*/ +/* TODO: When send queue & send queue length are passed with some value other + than NULL it throws segmentation fault*/ +static bool test_steps_channel_ex_01(void) { + /* Set up logging for Meshlink */ + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + meshlink_handle_t *mesh_handle = meshlink_open("channelexconf", "nut", "node_sim", 1); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_set_node_status_cb(mesh_handle, meshlink_callback_node_status); + meshlink_set_channel_accept_cb(mesh_handle, channel_accept); + + assert(meshlink_start(mesh_handle)); + + /* Getting node handle for itself */ + meshlink_node_t *node = meshlink_get_self(mesh_handle); + assert(node != NULL); + + char string[100] = "Test the 1st case"; + pthread_mutex_lock(&lock); + channel_acc = false; + pthread_mutex_unlock(&lock); + + /* Passing all valid arguments for meshlink_channel_open_ex */ + meshlink_channel_t *channel = NULL; + channel = meshlink_channel_open_ex(mesh_handle, node, PORT, cb, string, strlen(string) + 1, MESHLINK_CHANNEL_UDP); + assert_int_not_equal(channel, NULL); + + // Delay for establishing a channel + sleep(1); + + pthread_mutex_lock(&lock); + bool ret = channel_acc; + pthread_mutex_unlock(&lock); + + assert_int_equal(ret, true); + + meshlink_close(mesh_handle); + assert(meshlink_destroy("channelexconf")); + + return true; +} + +/* Execute meshlink_channel_open_ex Test Case # 2 - testing API's valid case by passing NULL and + 0 for send queue & it's length respectively and others with valid arguments */ +static void test_case_channel_ex_02(void **state) { + execute_test(test_steps_channel_ex_02, state); +} +/* Test Steps for meshlink_channel_open_ex Test Case # 2 - Valid case (TCP channel) + + Test Steps: + 1. Run NUT(Node Under Test) + 2. Open channel to ourself + + Expected Result: + Opens a TCP channel successfully by setting channel_acc true*/ +static bool test_steps_channel_ex_02(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + meshlink_handle_t *mesh_handle = meshlink_open("channelexconf", "nut", "node_sim", 1); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_set_node_status_cb(mesh_handle, meshlink_callback_node_status); + meshlink_set_channel_accept_cb(mesh_handle, channel_accept); + + assert(meshlink_start(mesh_handle)); + + meshlink_node_t *node = meshlink_get_self(mesh_handle); + assert(node != NULL); + + pthread_mutex_lock(&lock); + channel_acc = false; + pthread_mutex_unlock(&lock); + sleep(1); + + PRINT_TEST_CASE_MSG("Opening TCP alike channel ex\n"); + /* Passing all valid arguments for meshlink_channel_open_ex */ + meshlink_channel_t *channel; + channel = meshlink_channel_open_ex(mesh_handle, node, PORT, cb, NULL, 0, MESHLINK_CHANNEL_TCP); + assert_int_not_equal(channel, NULL); + + // Delay for establishing a channel + sleep(1); + pthread_mutex_lock(&lock); + bool ret = channel_acc; + pthread_mutex_unlock(&lock); + + assert_int_equal(ret, true); + + meshlink_close(mesh_handle); + assert(meshlink_destroy("channelexconf")); + return true; +} + +/* Execute meshlink_channel_open_ex Test Case # 3 - Open a UDP channel */ +static void test_case_channel_ex_03(void **state) { + execute_test(test_steps_channel_ex_03, state); +} +/* Test Steps for meshlink_channel_open_ex Test Case # 3 - Valid case (UDP channel) + + Test Steps: + 1. Run NUT(Node Under Test) + 2. Open channel to ourself + + Expected Result: + Opens a UDP channel successfully by setting channel_acc true */ +static bool test_steps_channel_ex_03(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + meshlink_handle_t *mesh_handle = meshlink_open("channelexconf", "nut", "node_sim", 1); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_set_node_status_cb(mesh_handle, meshlink_callback_node_status); + meshlink_set_channel_accept_cb(mesh_handle, channel_accept); + + assert(meshlink_start(mesh_handle)); + + /* Getting node handle for itself */ + meshlink_node_t *node = meshlink_get_self(mesh_handle); + assert(node != NULL); + + pthread_mutex_lock(&lock); + channel_acc = false; + pthread_mutex_unlock(&lock); + sleep(1); + + /* Passing all valid arguments for meshlink_channel_open_ex */ + meshlink_channel_t *channel; + channel = meshlink_channel_open_ex(mesh_handle, node, PORT, cb, NULL, 0, MESHLINK_CHANNEL_UDP); + assert_int_not_equal(channel, NULL); + + // Delay for establishing a channel + sleep(1); + + pthread_mutex_lock(&lock); + bool ret = channel_acc; + pthread_mutex_unlock(&lock); + + assert_int_equal(ret, true); + + meshlink_close(mesh_handle); + assert(meshlink_destroy("channelexconf")); + return true; +} + +/* Execute meshlink_channel_open_ex Test Case # 4 - Open a TCP channel with no receive callback + and send queue */ +static void test_case_channel_ex_04(void **state) { + execute_test(test_steps_channel_ex_04, state); +} +/* Test Steps for meshlink_channel_open_ex Test Case # 4 - Valid Case (Disabling receive callback) + + Test Steps: + 1. Run NUT(Node Under Test) + 2. Open channel to ourself + + Expected Result: + Opens a channel +*/ + +static bool test_steps_channel_ex_04(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + meshlink_handle_t *mesh_handle = meshlink_open("channelexconf", "nut", "node_sim", 1); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_set_node_status_cb(mesh_handle, meshlink_callback_node_status); + meshlink_set_channel_accept_cb(mesh_handle, channel_accept); + + assert(meshlink_start(mesh_handle)); + + /* Getting node handle for itself */ + meshlink_node_t *node = meshlink_get_self(mesh_handle); + assert(node != NULL); + + pthread_mutex_lock(&lock); + channel_acc = false; + pthread_mutex_unlock(&lock); + + /* Passing all valid arguments for meshlink_channel_open_ex i.e disabling receive callback and send queue */ + meshlink_channel_t *channel; + channel = meshlink_channel_open_ex(mesh_handle, node, PORT, NULL, NULL, 0, MESHLINK_CHANNEL_UDP); + assert(channel != NULL); + // Delay for establishing a channel + + pthread_mutex_lock(&lock); + bool ret = channel_acc; + pthread_mutex_unlock(&lock); + + assert_int_equal(ret, true); + + meshlink_close(mesh_handle); + assert(meshlink_destroy("channelexconf")); + return true; +} + +/* Execute meshlink_channel_open_ex Test Case # 5 - Opening channel using NULL as mesh handle argument + for the API */ +static void test_case_channel_ex_05(void **state) { + execute_test(test_steps_channel_ex_05, state); +} +/* Test Steps for meshlink_channel_open_ex Test Case # 5 - Invalid case (NULL as mesh argument) + + Test Steps: + 1. Run NUT(Node Under Test) + 2. Open channel by passing NULL as argument for mesh handle + + Expected Result: + meshlink_channel_open_ex returns NULL as channel handle reporting error accordingly +*/ +static bool test_steps_channel_ex_05(void) { + /* Set up logging for Meshlink */ + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + meshlink_handle_t *mesh_handle = meshlink_open("channelexconf", "nut", "node_sim", 1); + assert(mesh_handle); + + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_set_node_status_cb(mesh_handle, meshlink_callback_node_status); + meshlink_set_channel_accept_cb(mesh_handle, channel_accept); + + assert(meshlink_start(mesh_handle)); + /* Getting node handle for itself */ + meshlink_node_t *node = meshlink_get_self(mesh_handle); + assert(node != NULL); + + /* Trying to open channel using mesh handle as NULL argument */ + meshlink_channel_t *channel = meshlink_channel_open_ex(NULL, node, PORT, cb, NULL, 0, MESHLINK_CHANNEL_TCP); + assert(channel == NULL); + + meshlink_close(mesh_handle); + assert(meshlink_destroy("channelexconf")); + return true; +} + +/* Execute meshlink_channel_open_ex Test Case # 6 - Opening channel using NULL as node handle argument + for the API*/ +static void test_case_channel_ex_06(void **state) { + execute_test(test_steps_channel_ex_06, state); +} + +/* Test Steps for meshlink_channel_open_ex Test Case # 6 - Invalid case (NULL as node argument) + + Test Steps: + 1. Run NUT(Node Under Test) + 2. Open channel by passing NULL as argument for node handle + + Expected Result: + meshlink_channel_open_ex returns NULL as channel handle reporting error accordingly +*/ +static bool test_steps_channel_ex_06(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + meshlink_handle_t *mesh_handle = meshlink_open("channelexconf", "nut", "node_sim", 1); + assert(mesh_handle); + + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_set_node_status_cb(mesh_handle, meshlink_callback_node_status); + meshlink_set_channel_accept_cb(mesh_handle, channel_accept); + + assert(meshlink_start(mesh_handle)); + + /* Trying to open channel using node handle as NULL argument */ + meshlink_channel_t *channel = meshlink_channel_open_ex(mesh_handle, NULL, PORT, cb, NULL, 0, MESHLINK_CHANNEL_TCP); + + assert_int_equal(channel, NULL); + + meshlink_close(mesh_handle); + assert(meshlink_destroy("channelexconf")); + return true; +} + +static test_cb_t recv_cb_data; +static test_cb_t nut_recv_cb_data; + +/* Peer node's receive callback handler */ +static void peer_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) { + (void)mesh; + (void)channel; + (void)data; + + (recv_cb_data.total_cb_count)++; + recv_cb_data.cb_total_data_len += len; + recv_cb_data.cb_data_len = len; + + assert_int_equal(meshlink_channel_send(mesh, channel, data, len), len); +} + +/* NUT's receive callback handler */ +static void nut_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) { + (void)mesh; + (void)channel; + (void)data; + + (nut_recv_cb_data.total_cb_count)++; + nut_recv_cb_data.cb_total_data_len += len; + nut_recv_cb_data.cb_data_len = len; + +} + +/* NUT's poll callback handler */ +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)mesh; + (void)channel; + + fail(); +} + +static bool peer_accept_flag; + +/* Peer node's accept callback handler */ +static bool accept_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + (void)port; + (void)data; + (void)len; + + channel->node->priv = channel; + meshlink_set_channel_receive_cb(mesh, channel, peer_receive_cb); + return peer_accept_flag; +} + +/* Execute meshlink_channel_open_ex Test Case # 7 - UDP channel corner cases */ +static void test_case_channel_ex_07(void **state) { + execute_test(test_steps_channel_ex_07, state); +} + +static bool test_steps_channel_ex_07(void) { + char nut_confbase[PATH_MAX]; + char peer_confbase[PATH_MAX]; + char *buffer; + size_t mss_size; + size_t send_size; + meshlink_channel_t *channel_peer; + create_path(nut_confbase, NUT, 5); + create_path(peer_confbase, PEER, 5); + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_CHANNEL_OPEN, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + meshlink_handle_t *mesh_peer = meshlink_open(peer_confbase, PEER, TEST_CHANNEL_OPEN, DEV_CLASS_STATIONARY); + assert_non_null(mesh_peer); + + link_meshlink_pair(mesh, mesh_peer); + meshlink_set_channel_accept_cb(mesh_peer, accept_cb); + bzero(&recv_cb_data, sizeof(recv_cb_data)); + + meshlink_node_t *node = meshlink_get_node(mesh, PEER); + meshlink_node_t *node_peer = meshlink_get_node(mesh_peer, NUT); + assert_true(meshlink_start(mesh)); + assert_true(meshlink_start(mesh_peer)); + + /* 1. Peer rejects the channel that's being opened by NUT, when data is sent on that rejected channel + it should not lead to any undefined behavior and the peer should ignore the data sent */ + + peer_accept_flag = false; + meshlink_channel_t *channel = meshlink_channel_open_ex(mesh, node, PORT, nut_receive_cb, NULL, 0, MESHLINK_CHANNEL_UDP); + assert_non_null(channel); + + assert_after(node_peer->priv, 5); + assert_after((nut_recv_cb_data.total_cb_count == 1), 5); + assert_int_equal(nut_recv_cb_data.cb_data_len, 0); + + mss_size = meshlink_channel_get_mss(mesh, channel); + + if((mss_size != -1) && !mss_size) { + buffer = malloc(mss_size); + assert_non_null(buffer); + assert_int_equal(meshlink_channel_send(mesh, channel, buffer, mss_size), mss_size); + sleep(5); + assert_int_equal(nut_recv_cb_data.total_cb_count, 0); + free(buffer); + } + + meshlink_channel_close(mesh, channel); + + /* 2. Open channel to an offline node and sleep for 30 seconds once the offline node comes back online + both the nodes should be able to create the channel */ + + peer_accept_flag = true; + meshlink_stop(mesh_peer); + node_peer->priv = NULL; + channel = meshlink_channel_open_ex(mesh, node, PORT, nut_receive_cb, NULL, 0, MESHLINK_CHANNEL_UDP); + assert_non_null(channel); + + sleep(30); + assert_true(meshlink_start(mesh_peer)); + + // Peer set's this while accepting channel + + assert_after(node_peer->priv, 5); + + /* 2. Active UDP channel should be able to do bi-directional data transfer */ + + bzero(&recv_cb_data, sizeof(recv_cb_data)); + bzero(&nut_recv_cb_data, sizeof(nut_recv_cb_data)); + buffer = malloc(mss_size); + assert_non_null(buffer); + mss_size = meshlink_channel_get_mss(mesh, channel); + assert_int_not_equal(mss_size, -1); + send_size = mss_size; + assert_int_equal(meshlink_channel_send(mesh, channel, buffer, send_size), send_size); + assert_after((recv_cb_data.cb_total_data_len == send_size), 5); + assert_int_equal(recv_cb_data.total_cb_count, 1); + assert_after((nut_recv_cb_data.cb_total_data_len == send_size), 5); + assert_int_equal(nut_recv_cb_data.total_cb_count, 1); + + /* 3. Set poll callback for an UDP channel - Even though poll callback's return value is void + according to the design poll callback is meant only for TCP channel. */ + + // Set the poll callback and sleep for 5 seconds, fail the test case if poll callback gets invoked + + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + sleep(5); + + /* 4. Sent data on the active channel with data length more than the obtained MSS value. + It's expected that peer node doesn't receive it if received then the MSS calculations might be wrong */ + + bzero(&recv_cb_data, sizeof(recv_cb_data)); + send_size = mss_size + 100; + assert_int_equal(meshlink_channel_send(mesh, channel, buffer, send_size), send_size); + sleep(5); + assert_int_equal(recv_cb_data.total_cb_count, 0); + + /* 5. Sent the minimum data (here 1 byte) possible to the peer node via the active UDP channel */ + + bzero(&recv_cb_data, sizeof(recv_cb_data)); + send_size = 1; + assert_int_equal(meshlink_channel_send(mesh, channel, buffer, send_size), send_size); + assert_after((recv_cb_data.cb_total_data_len == send_size), 5); + assert_int_equal(recv_cb_data.total_cb_count, 1); + + /* 6. Sent more than maximum allowed data i.e, > UDP max length */ + + bzero(&recv_cb_data, sizeof(recv_cb_data)); + send_size = USHRT_MAX + 2; // 65537 bytes should fail + assert_int_equal(meshlink_channel_send(mesh, channel, buffer, send_size), -1); + sleep(5); + assert_int_equal(recv_cb_data.total_cb_count, 0); + + /* 7. Pass get MSS API with NULL as mesh handle */ + + assert_int_equal(meshlink_channel_get_mss(NULL, channel), -1); + + /* 8. Pass get MSS API with NULL as channel handle */ + + assert_int_equal(meshlink_channel_get_mss(mesh, NULL), -1); + + /* 9. Obtained MSS value should be less than PMTU value */ + + ssize_t pmtu_size = meshlink_get_pmtu(mesh, node); + assert_int_not_equal(pmtu_size, -1); + assert_true(mss_size <= pmtu_size); + + /* 10. Close/free the channel at the NUT's end, but when peer node still tries to send data on that channel + meshlink should gracefully handle it */ + + bzero(&recv_cb_data, sizeof(recv_cb_data)); + bzero(&nut_recv_cb_data, sizeof(nut_recv_cb_data)); + recv_cb_data.cb_data_len = 1; + meshlink_channel_close(mesh, channel); + assert_after((recv_cb_data.total_cb_count == 1), 5); + assert_int_equal(recv_cb_data.cb_data_len, 0); + + channel_peer = node_peer->priv; + send_size = mss_size / 2; + assert_int_equal(meshlink_channel_send(mesh_peer, channel_peer, buffer, send_size), send_size); + sleep(5); + assert_int_equal(nut_recv_cb_data.total_cb_count, 0); + + /* 11. Getting MSS value on a node which is closed by other node but not freed/closed by the host node */ + + assert_int_equal(meshlink_channel_get_mss(mesh_peer, channel_peer), -1); + + // Cleanup + + free(buffer); + meshlink_close(mesh); + meshlink_close(mesh_peer); + assert_true(meshlink_destroy(nut_confbase)); + assert_true(meshlink_destroy(peer_confbase)); + return true; +} + +int test_meshlink_channel_open_ex(void) { + const struct CMUnitTest blackbox_channel_ex_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_channel_ex_01, NULL, NULL, + (void *)&test_case_channel_ex_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_channel_ex_02, NULL, NULL, + (void *)&test_case_channel_ex_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_channel_ex_03, NULL, NULL, + (void *)&test_case_channel_ex_03_state), + cmocka_unit_test_prestate_setup_teardown(test_case_channel_ex_04, NULL, NULL, + (void *)&test_case_channel_ex_04_state), + cmocka_unit_test_prestate_setup_teardown(test_case_channel_ex_05, NULL, NULL, + (void *)&test_case_channel_ex_05_state), + cmocka_unit_test_prestate_setup_teardown(test_case_channel_ex_06, NULL, NULL, + (void *)&test_case_channel_ex_06_state), + cmocka_unit_test_prestate_setup_teardown(test_case_channel_ex_07, NULL, NULL, + (void *)&test_case_channel_ex_07_state) + }; + + total_tests += sizeof(blackbox_channel_ex_tests) / sizeof(blackbox_channel_ex_tests[0]); + + assert(pthread_mutex_init(&lock, NULL) == 0); + int failed = cmocka_run_group_tests(blackbox_channel_ex_tests, NULL, NULL); + assert(pthread_mutex_destroy(&lock) == 0); + + return failed; +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_channel_ex.h b/test/blackbox/run_blackbox_tests/test_cases_channel_ex.h new file mode 100644 index 0000000..e15fa28 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_channel_ex.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_CHANNELS_EX_H +#define TEST_CASES_CHANNELS_EX_H + +/* + test_cases_channel_ex.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int total_tests; +extern int test_meshlink_channel_open_ex(void); + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_channel_get_flags.c b/test/blackbox/run_blackbox_tests/test_cases_channel_get_flags.c new file mode 100644 index 0000000..5bb2683 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_channel_get_flags.c @@ -0,0 +1,202 @@ +/* + test_cases_channel_get_flags.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_channel_get_flags.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include + +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG +/* Modify this to change the port number */ +#define PORT 8000 + +static void test_case_channel_get_flags_01(void **state); +static bool test_steps_channel_get_flags_01(void); +static void test_case_channel_get_flags_02(void **state); +static bool test_steps_channel_get_flags_02(void); +static void test_case_channel_get_flags_03(void **state); +static bool test_steps_channel_get_flags_03(void); + +static black_box_state_t test_case_channel_get_flags_01_state = { + .test_case_name = "test_case_channel_get_flags_01", +}; + +static black_box_state_t test_case_channel_get_flags_02_state = { + .test_case_name = "test_case_channel_get_flags_02", +}; + +static black_box_state_t test_case_channel_get_flags_03_state = { + .test_case_name = "test_case_channel_get_flags_03", +}; + +/* Execute meshlink_channel_get_flags Test Case # 1 - Valid case*/ +static void test_case_channel_get_flags_01(void **state) { + execute_test(test_steps_channel_get_flags_01, state); +} +/* Test Steps for meshlink_channel_get_flags Test Case # 1 + + Test Steps: + 1. Run NUT(Node Under Test) + 2. Open channel to ourself (with TCP semantic here) + 3. Get flag(s) of that channel + + Expected Result: + API returning exact flag that has been assigned while opening (here TCP) +*/ +static bool test_steps_channel_get_flags_01(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + meshlink_handle_t *mesh_handle = meshlink_open("getflagsconf", "nut", "node_sim", 1); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_set_node_status_cb(mesh_handle, meshlink_callback_node_status); + meshlink_set_channel_accept_cb(mesh_handle, NULL); + + assert(meshlink_start(mesh_handle)); + + meshlink_node_t *node = meshlink_get_self(mesh_handle); + assert(node != NULL); + sleep(1); + + /* Passing all valid arguments for meshlink_channel_open_ex */ + meshlink_channel_t *channel; + channel = meshlink_channel_open_ex(mesh_handle, node, PORT, NULL, NULL, 0, MESHLINK_CHANNEL_TCP); + assert(channel != NULL); + + // Obtaining channel flags using meshlink_channel_get_flags + uint32_t flags = meshlink_channel_get_flags(mesh_handle, channel); + assert_int_equal(flags, MESHLINK_CHANNEL_TCP); + + meshlink_close(mesh_handle); + assert(meshlink_destroy("getflagsconf")); + + return true; +} + +/* Execute meshlink_channel_get_flags Test Case # 2 - Invalid case*/ +static void test_case_channel_get_flags_02(void **state) { + execute_test(test_steps_channel_get_flags_02, state); +} +/* Test Steps for meshlink_channel_get_flags Test Case # 2 + + Test Steps: + 1. Run NUT(Node Under Test) + 2. Open channel to ourself (with TCP semantic here) + 3. Call meshlink_channel_get_flags by passing NULL as mesh handle argument + + Expected Result: + API reporting error accordingly. +*/ +static bool test_steps_channel_get_flags_02(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + meshlink_handle_t *mesh_handle = meshlink_open("getflagsconf", "nut", "node_sim", 1); + assert(mesh_handle); + + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_set_node_status_cb(mesh_handle, meshlink_callback_node_status); + meshlink_set_channel_accept_cb(mesh_handle, NULL); + + assert(meshlink_start(mesh_handle)); + + /* Getting node handle for itself */ + meshlink_node_t *node = meshlink_get_self(mesh_handle); + assert(node != NULL); + sleep(1); + + /* Passing all valid arguments for meshlink_channel_open_ex */ + meshlink_channel_t *channel; + channel = meshlink_channel_open_ex(mesh_handle, node, PORT, NULL, NULL, 0, MESHLINK_CHANNEL_TCP); + assert(channel != NULL); + + // passing NULL as mesh handle argument for meshlink_channel_get_flags + uint32_t flags = meshlink_channel_get_flags(NULL, channel); + + assert_int_equal((int32_t)flags, -1); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + meshlink_close(mesh_handle); + assert(meshlink_destroy("getflagsconf")); + return true; +} + +/* Execute meshlink_channel_get flags Test Case # 3 - Invalid case*/ +static void test_case_channel_get_flags_03(void **state) { + execute_test(test_steps_channel_get_flags_03, state); +} +/* Test Steps for meshlink_channel_get_flags Test Case # 3 + + Test Steps: + 1. Run NUT(Node Under Test) + 3. Call meshlink_channel_get_flags by passing NULL as channel handle argument + + Expected Result: + API reporting error accordingly. +*/ +static bool test_steps_channel_get_flags_03(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + meshlink_handle_t *mesh_handle = meshlink_open("getflagsconf", "nut", "node_sim", 1); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_set_node_status_cb(mesh_handle, meshlink_callback_node_status); + meshlink_set_channel_accept_cb(mesh_handle, NULL); + + assert(meshlink_start(mesh_handle)); + + // passing NULL as channel handle argument for meshlink_channel_get_flags + uint32_t flags = meshlink_channel_get_flags(mesh_handle, NULL); + + assert_int_equal((int32_t)flags, -1); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + meshlink_close(mesh_handle); + assert(meshlink_destroy("getflagsconf")); + return true; +} + + +int test_meshlink_channel_get_flags(void) { + const struct CMUnitTest blackbox_channel_get_flags_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_channel_get_flags_01, NULL, NULL, + (void *)&test_case_channel_get_flags_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_channel_get_flags_02, NULL, NULL, + (void *)&test_case_channel_get_flags_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_channel_get_flags_03, NULL, NULL, + (void *)&test_case_channel_get_flags_03_state) + }; + + total_tests += sizeof(blackbox_channel_get_flags_tests) / sizeof(blackbox_channel_get_flags_tests[0]); + + return cmocka_run_group_tests(blackbox_channel_get_flags_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_channel_get_flags.h b/test/blackbox/run_blackbox_tests/test_cases_channel_get_flags.h new file mode 100644 index 0000000..d60ec90 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_channel_get_flags.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_CHANNELS_SET_ACCEPT_H +#define TEST_CASES_CHANNELS_SET_ACCEPT_H + +/* + test_cases_get_self.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int total_tests; +extern int test_meshlink_channel_get_flags(void); + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_channel_open.c b/test/blackbox/run_blackbox_tests/test_cases_channel_open.c new file mode 100644 index 0000000..9792aec --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_channel_open.c @@ -0,0 +1,259 @@ +/* + test_cases_channel_open.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_channel_open.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include + +static void test_case_mesh_channel_open_01(void **state); +static bool test_steps_mesh_channel_open_01(void); +static void test_case_mesh_channel_open_02(void **state); +static bool test_steps_mesh_channel_open_02(void); +static void test_case_mesh_channel_open_03(void **state); +static bool test_steps_mesh_channel_open_03(void); +static void test_case_mesh_channel_open_04(void **state); +static bool test_steps_mesh_channel_open_04(void); + +/* State structure for meshlink_channel_open Test Case #1 */ +static black_box_state_t test_mesh_channel_open_01_state = { + .test_case_name = "test_case_mesh_channel_open_01", +}; + +/* State structure for meshlink_channel_open Test Case #2 */ +static black_box_state_t test_mesh_channel_open_02_state = { + .test_case_name = "test_case_mesh_channel_open_02", +}; + +/* State structure for meshlink_channel_open Test Case #3 */ +static black_box_state_t test_mesh_channel_open_03_state = { + .test_case_name = "test_case_mesh_channel_open_03", +}; + +/* State structure for meshlink_channel_open Test Case #4 */ +static black_box_state_t test_mesh_channel_open_04_state = { + .test_case_name = "test_case_mesh_channel_open_04", +}; + +/* Execute meshlink_channel_open Test Case # 1*/ +static void test_case_mesh_channel_open_01(void **state) { + execute_test(test_steps_mesh_channel_open_01, state); +} + +/* Test Steps for meshlink_channel_open Test Case # 1 + + Test Steps: + 1. Open both the node instances + 2. Join bar node with foo + 3. Open channel between the nodes + + Expected Result: + meshlink_channel_open should open a channel by returning a channel handler +*/ +static bool test_steps_mesh_channel_open_01(void) { + assert(meshlink_destroy("channels_conf.1")); + assert(meshlink_destroy("channels_conf.2")); + + // Open two new meshlink instance. + meshlink_handle_t *mesh1 = meshlink_open("channels_conf.1", "foo", "channels", DEV_CLASS_BACKBONE); + assert(mesh1 != NULL); + meshlink_handle_t *mesh2 = meshlink_open("channels_conf.2", "bar", "channels", DEV_CLASS_BACKBONE); + assert(mesh2 != NULL); + + // Import and export both side's data + char *exp = meshlink_export(mesh1); + assert(exp != NULL); + assert(meshlink_import(mesh2, exp)); + free(exp); + exp = meshlink_export(mesh2); + assert(exp != NULL); + assert(meshlink_import(mesh1, exp)); + free(exp); + + // Start both instances + assert(meshlink_start(mesh1)); + assert(meshlink_start(mesh2)); + sleep(2); + + // Open a channel from foo to bar. + meshlink_node_t *bar = meshlink_get_node(mesh1, "bar"); + assert(bar != NULL); + meshlink_channel_t *channel = meshlink_channel_open(mesh1, bar, 7000, NULL, NULL, 0); + assert_int_not_equal(channel, NULL); + + // Clean up. + meshlink_close(mesh2); + meshlink_close(mesh1); + assert(meshlink_destroy("channels_conf.1")); + assert(meshlink_destroy("channels_conf.2")); + return true; +} + +/* Execute meshlink_channel_open Test Case # 2 + + Test Steps: + 1. Open both the node instances + 2. Join bar node with foo + 3. Open channel between the nodes with NULL as receive callback argument + + Expected Result: + meshlink_channel_open should open a channel by returning a channel handler +*/ +static void test_case_mesh_channel_open_02(void **state) { + execute_test(test_steps_mesh_channel_open_02, state); +} + +/* Test Steps for meshlink_channel_open Test Case # 2*/ +static bool test_steps_mesh_channel_open_02(void) { + assert(meshlink_destroy("channels_conf.3")); + assert(meshlink_destroy("channels_conf.4")); + + // Open two new meshlink instance. + meshlink_handle_t *mesh1 = meshlink_open("channels_conf.3", "foo", "channels", DEV_CLASS_BACKBONE); + assert(mesh1 != NULL); + meshlink_handle_t *mesh2 = meshlink_open("channels_conf.4", "bar", "channels", DEV_CLASS_BACKBONE); + assert(mesh2 != NULL); + + char *exp = meshlink_export(mesh1); + assert(exp != NULL); + assert(meshlink_import(mesh2, exp)); + free(exp); + exp = meshlink_export(mesh2); + assert(exp != NULL); + assert(meshlink_import(mesh1, exp)); + free(exp); + + // Start both instances + assert(meshlink_start(mesh1)); + assert(meshlink_start(mesh2)); + sleep(1); + + // Open a channel from foo to bar. + + meshlink_node_t *bar = meshlink_get_node(mesh1, "bar"); + assert(bar != NULL); + + meshlink_channel_t *channel = meshlink_channel_open(mesh1, bar, 7000, NULL, NULL, 0); + assert_int_not_equal(channel, NULL); + + // Clean up. + meshlink_close(mesh2); + meshlink_close(mesh1); + assert(meshlink_destroy("channels_conf.3")); + assert(meshlink_destroy("channels_conf.4")); + return true; +} + +/* Execute meshlink_channel_open Test Case # 3 */ +static void test_case_mesh_channel_open_03(void **state) { + execute_test(test_steps_mesh_channel_open_03, state); +} + +/* Test Steps for meshlink_channel_open Test Case # 3 + + Test Steps: + 1. Create the node instance & obtain node handle + 2. Open a channel with NULL as mesh handle argument + and rest other arguments being valid. + + Expected Result: + meshlink_channel_open API handles the invalid parameter + when called by giving proper error number. +*/ +static bool test_steps_mesh_channel_open_03(void) { + assert(meshlink_destroy("channels_conf.5")); + + // Open two new meshlink instance. + meshlink_handle_t *mesh = meshlink_open("channels_conf.5", "foo", "channels", DEV_CLASS_BACKBONE); + assert(mesh != NULL); + + meshlink_node_t *node = meshlink_get_self(mesh); + assert(node); + + meshlink_channel_t *channel = meshlink_channel_open(NULL, node, 7000, NULL, NULL, 0); + assert_int_equal(channel, NULL); + + // Clean up. + meshlink_close(mesh); + assert(meshlink_destroy("channels_conf.5")); + return true; +} + +/* Execute meshlink_channel_open Test Case # 4*/ +static void test_case_mesh_channel_open_04(void **state) { + execute_test(test_steps_mesh_channel_open_04, state); +} + +/* Test Steps for meshlink_channel_open Test Case # 4 + + Test Steps: + 1. Create the node instance & obtain node handle + 2. Open a channel with NULL as node handle argument + and rest other arguments being valid. + + Expected Result: + meshlink_channel_open API handles the invalid parameter + when called by giving proper error number. +*/ +static bool test_steps_mesh_channel_open_04(void) { + assert(meshlink_destroy("channels_conf.7")); + + // Open two new meshlink instance. + meshlink_handle_t *mesh = meshlink_open("channels_conf.7", "foo", "channels", DEV_CLASS_BACKBONE); + assert(mesh != NULL); + + // Start both instances + assert(meshlink_start(mesh)); + + meshlink_channel_t *channel = meshlink_channel_open(mesh, NULL, 7000, NULL, NULL, 0); + assert_int_equal(channel, NULL); + + // Clean up. + meshlink_close(mesh); + assert(meshlink_destroy("channels_conf.7")); + return true; +} + +int test_meshlink_channel_open(void) { + const struct CMUnitTest blackbox_channel_open_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_channel_open_01, NULL, NULL, + (void *)&test_mesh_channel_open_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_channel_open_02, NULL, NULL, + (void *)&test_mesh_channel_open_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_channel_open_03, NULL, NULL, + (void *)&test_mesh_channel_open_03_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_channel_open_04, NULL, NULL, + (void *)&test_mesh_channel_open_04_state) + }; + + total_tests += sizeof(blackbox_channel_open_tests) / sizeof(blackbox_channel_open_tests[0]); + + return cmocka_run_group_tests(blackbox_channel_open_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_channel_open.h b/test/blackbox/run_blackbox_tests/test_cases_channel_open.h new file mode 100644 index 0000000..a1140a0 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_channel_open.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_CHANNEL_OPEN_H +#define TEST_CASES_CHANNEL_OPEN_H + +/* + test_cases_channel_open.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_channel_open(void); +extern int total_tests; + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_channel_send.c b/test/blackbox/run_blackbox_tests/test_cases_channel_send.c new file mode 100644 index 0000000..d818a0c --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_channel_send.c @@ -0,0 +1,365 @@ +/* + test_cases_channel_send.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_channel_send.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include +#include + +static void test_case_mesh_channel_send_01(void **state); +static bool test_steps_mesh_channel_send_01(void); +static void test_case_mesh_channel_send_02(void **state); +static bool test_steps_mesh_channel_send_02(void); +static void test_case_mesh_channel_send_03(void **state); +static bool test_steps_mesh_channel_send_03(void); +static void test_case_mesh_channel_send_04(void **state); +static bool test_steps_mesh_channel_send_04(void); + +static void receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static void status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable); +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len); + +/* State structure for meshlink_channel_send Test Case #1 */ +static black_box_state_t test_mesh_channel_send_01_state = { + .test_case_name = "test_case_mesh_channel_send_01", +}; + +/* State structure for meshlink_channel_send Test Case #2 */ +static black_box_state_t test_mesh_channel_send_02_state = { + .test_case_name = "test_case_mesh_channel_send_02", +}; + +/* State structure for meshlink_channel_send Test Case #3 */ +static black_box_state_t test_mesh_channel_send_03_state = { + .test_case_name = "test_case_mesh_channel_send_03", +}; + +/* State structure for meshlink_channel_send Test Case #4 */ +static black_box_state_t test_mesh_channel_send_04_state = { + .test_case_name = "test_case_mesh_channel_send_04", +}; + +/* Execute meshlink_channel_send Test Case # 1*/ +static void test_case_mesh_channel_send_01(void **state) { + execute_test(test_steps_mesh_channel_send_01, state); +} + +static pthread_mutex_t poll_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t bar_reach_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t bar_responded_lock = PTHREAD_MUTEX_INITIALIZER; + +static pthread_cond_t poll_cond = PTHREAD_COND_INITIALIZER; +static pthread_cond_t status_cond = PTHREAD_COND_INITIALIZER; +static pthread_cond_t send_cond = PTHREAD_COND_INITIALIZER; + +static bool polled; +static bool bar_reachable; +static bool bar_responded; + +static void status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcmp(node->name, "bar")) { + pthread_mutex_lock(& bar_reach_lock); + bar_reachable = reachable; + assert(!pthread_cond_broadcast(&status_cond)); + pthread_mutex_unlock(& bar_reach_lock); + } +} + +static bool reject_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + (void)mesh; + (void)channel; + (void)port; + (void)data; + (void)len; + return false; +} + +static bool accept_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + (void)data; + + assert(port == 7); + assert(!len); + + meshlink_set_channel_receive_cb(mesh, channel, receive_cb); + + return true; +} + +/* channel receive callback */ +static void receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + (void)channel; + + if(len == 5 && !memcmp(dat, "Hello", 5)) { + pthread_mutex_lock(& bar_responded_lock); + bar_responded = true; + assert(!pthread_cond_broadcast(&send_cond)); + pthread_mutex_unlock(& bar_responded_lock); + } +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + + meshlink_set_channel_poll_cb(mesh, channel, NULL); + pthread_mutex_lock(&poll_lock); + polled = true; + assert(!pthread_cond_broadcast(&poll_cond)); + pthread_mutex_unlock(&poll_lock); +} + + +/* Test Steps for meshlink_channel_send Test Case # 1*/ +static bool test_steps_mesh_channel_send_01(void) { + struct timespec timeout = {0}; + assert(meshlink_destroy("chan_send_conf.1")); + assert(meshlink_destroy("chan_send_conf.2")); + + // Open two new meshlink instance. + meshlink_handle_t *mesh1 = meshlink_open("chan_send_conf.1", "foo", "channels", DEV_CLASS_BACKBONE); + assert(mesh1 != NULL); + meshlink_handle_t *mesh2 = meshlink_open("chan_send_conf.2", "bar", "channels", DEV_CLASS_BACKBONE); + assert(mesh2 != NULL); + meshlink_set_log_cb(mesh1, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_log_cb(mesh2, MESHLINK_DEBUG, meshlink_callback_logger); + + char *data = meshlink_export(mesh1); + assert(data); + assert(meshlink_import(mesh2, data)); + free(data); + data = meshlink_export(mesh2); + assert(data); + assert(meshlink_import(mesh1, data)); + free(data); + + // Set the callbacks. + + meshlink_set_channel_accept_cb(mesh1, reject_cb); + meshlink_set_channel_accept_cb(mesh2, accept_cb); + + meshlink_set_node_status_cb(mesh1, status_cb); + + // Start both instances + bar_reachable = false; + assert(meshlink_start(mesh1)); + assert(meshlink_start(mesh2)); + + timeout.tv_sec = time(NULL) + 10; + pthread_mutex_lock(&bar_reach_lock); + + while(bar_reachable == false) { + assert(!pthread_cond_timedwait(&status_cond, &bar_reach_lock, &timeout)); + } + + pthread_mutex_unlock(&bar_reach_lock); + + // Open a channel from foo to bar. + + meshlink_node_t *bar = meshlink_get_node(mesh1, "bar"); + assert(bar); + + bar_responded = false; + meshlink_channel_t *channel = meshlink_channel_open(mesh1, bar, 7, NULL, NULL, 0); + meshlink_set_channel_poll_cb(mesh1, channel, poll_cb); + + timeout.tv_sec = time(NULL) + 10; + pthread_mutex_lock(&poll_lock); + + while(polled == false) { + assert(!pthread_cond_timedwait(&poll_cond, &poll_lock, &timeout)); + } + + pthread_mutex_unlock(&poll_lock); + + assert(meshlink_channel_send(mesh1, channel, "Hello", 5) >= 0); + + timeout.tv_sec = time(NULL) + 20; + pthread_mutex_lock(&bar_responded_lock); + + if(bar_responded == false) { + assert(!pthread_cond_timedwait(&send_cond, &bar_responded_lock, &timeout)); + } + + pthread_mutex_unlock(&bar_responded_lock); + + // Clean up. + + meshlink_close(mesh2); + meshlink_close(mesh1); + assert(meshlink_destroy("chan_send_conf.1")); + assert(meshlink_destroy("chan_send_conf.2")); + + return true; +} + +/* Execute meshlink_channel_send Test Case # 2*/ +static void test_case_mesh_channel_send_02(void **state) { + execute_test(test_steps_mesh_channel_send_02, state); +} + +/* Test Steps for meshlink_channel_send Test Case # 2*/ +static bool test_steps_mesh_channel_send_02(void) { + struct timespec timeout = {0}; + assert(meshlink_destroy("chan_send_conf.5")); + + // Open new meshlink instance. + + meshlink_handle_t *mesh1 = meshlink_open("chan_send_conf.5", "foo", "channels", DEV_CLASS_BACKBONE); + assert(mesh1 != NULL); + + meshlink_set_channel_accept_cb(mesh1, accept_cb); + + // Start node instance + + assert(meshlink_start(mesh1)); + + meshlink_node_t *node = meshlink_get_self(mesh1); + assert(node); + + meshlink_channel_t *channel = meshlink_channel_open(mesh1, node, 7, receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh1, channel, poll_cb); + + timeout.tv_sec = time(NULL) + 10; + pthread_mutex_lock(&poll_lock); + + while(polled == false) { + assert(!pthread_cond_timedwait(&poll_cond, &poll_lock, &timeout)); + } + + pthread_mutex_unlock(&poll_lock); + + ssize_t send_return = meshlink_channel_send(NULL, channel, "Hello", 5); + + assert_int_equal(send_return, -1); + + // Clean up. + + meshlink_close(mesh1); + assert(meshlink_destroy("chan_send_conf.5")); + + return true; +} + +/* Execute meshlink_channel_send Test Case # 3*/ +static void test_case_mesh_channel_send_03(void **state) { + execute_test(test_steps_mesh_channel_send_03, state); +} + +/* Test Steps for meshlink_channel_send Test Case # 3*/ +static bool test_steps_mesh_channel_send_03(void) { + assert(meshlink_destroy("chan_send_conf.7")); + // Open new meshlink instance. + + meshlink_handle_t *mesh1 = meshlink_open("chan_send_conf.7", "foo", "channels", DEV_CLASS_BACKBONE); + assert(mesh1 != NULL); + meshlink_set_channel_accept_cb(mesh1, accept_cb); + + // Start node instance + + assert(meshlink_start(mesh1)); + + ssize_t send_return = meshlink_channel_send(mesh1, NULL, "Hello", 5); + + assert_int_equal(send_return, -1); + + // Clean up. + + meshlink_close(mesh1); + assert(meshlink_destroy("chan_send_conf.7")); + + return true; +} + +/* Execute meshlink_channel_send Test Case # 4*/ +static void test_case_mesh_channel_send_04(void **state) { + execute_test(test_steps_mesh_channel_send_04, state); +} + +/* Test Steps for meshlink_channel_send Test Case # 4*/ +static bool test_steps_mesh_channel_send_04(void) { + struct timespec timeout = {0}; + assert(meshlink_destroy("chan_send_conf.9")); + // Open two new meshlink instance. + + meshlink_handle_t *mesh1 = meshlink_open("chan_send_conf.9", "foo", "channels", DEV_CLASS_BACKBONE); + assert(mesh1 != NULL); + + meshlink_set_channel_accept_cb(mesh1, accept_cb); + + // Start node instance + + assert(meshlink_start(mesh1)); + + meshlink_node_t *node = meshlink_get_self(mesh1); + assert(node); + + meshlink_channel_t *channel = meshlink_channel_open(mesh1, node, 7, receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh1, channel, poll_cb); + + timeout.tv_sec = time(NULL) + 10; + pthread_mutex_lock(&poll_lock); + + while(polled == false) { + assert(!pthread_cond_timedwait(&poll_cond, &poll_lock, &timeout)); + } + + pthread_mutex_unlock(&poll_lock); + + ssize_t send_return = meshlink_channel_send(mesh1, channel, NULL, 5); + + assert_int_equal(send_return, -1); + + // Clean up. + + meshlink_close(mesh1); + assert(meshlink_destroy("chan_send_conf.9")); + + return true; +} + +int test_meshlink_channel_send(void) { + const struct CMUnitTest blackbox_channel_send_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_channel_send_01, NULL, NULL, + (void *)&test_mesh_channel_send_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_channel_send_02, NULL, NULL, + (void *)&test_mesh_channel_send_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_channel_send_03, NULL, NULL, + (void *)&test_mesh_channel_send_03_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_channel_send_04, NULL, NULL, + (void *)&test_mesh_channel_send_04_state) + }; + + total_tests += sizeof(blackbox_channel_send_tests) / sizeof(blackbox_channel_send_tests[0]); + + return cmocka_run_group_tests(blackbox_channel_send_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_channel_send.h b/test/blackbox/run_blackbox_tests/test_cases_channel_send.h new file mode 100644 index 0000000..b9e624f --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_channel_send.h @@ -0,0 +1,31 @@ +#ifndef TEST_CASES_CHANNEL_SEND_H +#define TEST_CASES_CHANNEL_SEND_H + +/* + test_cases_channel_send.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_channel_send(void); +extern int total_tests; + + + + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_channel_set_accept_cb.c b/test/blackbox/run_blackbox_tests/test_cases_channel_set_accept_cb.c new file mode 100644 index 0000000..fa345b3 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_channel_set_accept_cb.c @@ -0,0 +1,271 @@ +/* + test_cases_channel_set_accept_cb.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "test_cases_channel_set_accept_cb.h" +#include +#include +#include +#include +#include +#include +#include + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG +/* Modify this to change the port number */ +#define PORT 8000 + +static void test_case_set_channel_accept_cb_01(void **state); +static bool test_steps_set_channel_accept_cb_01(void); +static void test_case_set_channel_accept_cb_02(void **state); +static bool test_steps_set_channel_accept_cb_02(void); + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len); +static bool channel_reject(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len); + +static bool channel_acc; +static bool polled; +static bool rejected; + +/* mutex for the common variable */ +static pthread_mutex_t accept_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t poll_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t lock_receive = PTHREAD_MUTEX_INITIALIZER; + +static pthread_cond_t accept_cond = PTHREAD_COND_INITIALIZER; +static pthread_cond_t poll_cond = PTHREAD_COND_INITIALIZER; +static pthread_cond_t receive_cond = PTHREAD_COND_INITIALIZER; + +static black_box_state_t test_case_channel_set_accept_cb_01_state = { + .test_case_name = "test_case_channel_set_accept_cb_01", +}; +static black_box_state_t test_case_channel_set_accept_cb_02_state = { + .test_case_name = "test_case_channel_set_accept_cb_02", +}; + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + (void)channel; + (void)dat; + + if(!len) { + if(!meshlink_errno) { + pthread_mutex_lock(&lock_receive); + rejected = true; + assert(!pthread_cond_broadcast(&receive_cond)); + pthread_mutex_unlock(&lock_receive); + } + } +} + +/* channel reject callback */ +static bool channel_reject(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + (void)mesh; + (void)channel; + (void)port; + (void)data; + (void)len; + return false; +} + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)mesh; + (void)channel; + (void)dat; + (void)len; + + assert_int_equal(port, PORT); + + pthread_mutex_lock(&accept_lock); + channel_acc = true; + assert(!pthread_cond_broadcast(&accept_cond)); + pthread_mutex_unlock(&accept_lock); + + return true; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + pthread_mutex_lock(&poll_lock); + polled = true; + assert(!pthread_cond_broadcast(&poll_cond)); + pthread_mutex_unlock(&poll_lock); +} + +/* Execute meshlink_channel_set_accept_cb Test Case # 1 - Valid case*/ +static void test_case_set_channel_accept_cb_01(void **state) { + execute_test(test_steps_set_channel_accept_cb_01, state); +} +/* Test Steps for meshlink_channel_set_accept_cb Test Case # 1 - Valid case + + Test Steps: + 1. Open NUT(Node Under Test) & bar meshes. + 2. Set channel_accept callback for NUT's meshlink_set_channel_accept_cb API. + 3. Export and Import nodes. + 4. Open a channel with NUT from bar to invoke channel accept callback + 5. Open a channel with bar from NUT to invoke channel accept callback + + Expected Result: + Opens a channel by invoking accept callback, when accept callback rejects the channel + it should invoke the other node's receive callback with length = 0 and no error. +*/ +static bool test_steps_set_channel_accept_cb_01(void) { + /* deleting the confbase if already exists */ + struct timespec timeout = {0}; + assert(meshlink_destroy("acceptconf1")); + assert(meshlink_destroy("acceptconf2")); + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instances */ + meshlink_handle_t *mesh1 = meshlink_open("acceptconf1", "nut", "chat", DEV_CLASS_STATIONARY); + assert(mesh1 != NULL); + meshlink_handle_t *mesh2 = meshlink_open("acceptconf2", "bar", "chat", DEV_CLASS_STATIONARY); + assert(mesh2 != NULL); + meshlink_set_log_cb(mesh1, MESHLINK_INFO, meshlink_callback_logger); + meshlink_set_log_cb(mesh2, MESHLINK_INFO, meshlink_callback_logger); + + meshlink_set_channel_accept_cb(mesh2, channel_reject); + meshlink_set_channel_accept_cb(mesh1, channel_accept); + + /* Export and Import on both sides */ + char *exp1 = meshlink_export(mesh1); + assert(exp1 != NULL); + char *exp2 = meshlink_export(mesh2); + assert(exp2 != NULL); + assert(meshlink_import(mesh1, exp2)); + assert(meshlink_import(mesh2, exp1)); + + assert(meshlink_start(mesh1)); + assert(meshlink_start(mesh2)); + sleep(1); + + meshlink_node_t *destination = meshlink_get_node(mesh2, "nut"); + assert(destination != NULL); + + /* Open channel for nut node from bar node which should be accepted */ + polled = false; + channel_acc = false; + meshlink_channel_t *channel2 = meshlink_channel_open(mesh2, destination, PORT, NULL, NULL, 0); + assert(channel2); + meshlink_set_channel_poll_cb(mesh2, channel2, poll_cb); + + timeout.tv_sec = time(NULL) + 10; + pthread_mutex_lock(&poll_lock); + + while(polled == false) { + assert(!pthread_cond_timedwait(&poll_cond, &poll_lock, &timeout)); + } + + pthread_mutex_unlock(&poll_lock); + + timeout.tv_sec = time(NULL) + 10; + pthread_mutex_lock(&accept_lock); + + while(channel_acc == false) { + assert(!pthread_cond_timedwait(&accept_cond, &accept_lock, &timeout)); + } + + pthread_mutex_unlock(&accept_lock); + + /* Open channel for bar node from nut node which should be rejected */ + polled = false; + rejected = false; + channel_acc = false; + destination = meshlink_get_node(mesh1, "bar"); + assert(destination != NULL); + + meshlink_channel_t *channel1 = meshlink_channel_open(mesh1, destination, PORT, channel_receive_cb, NULL, 0); + assert(channel1); + meshlink_set_channel_poll_cb(mesh1, channel1, poll_cb); + + timeout.tv_sec = time(NULL) + 10; + pthread_mutex_lock(&poll_lock); + + while(polled == false) { + assert(!pthread_cond_timedwait(&poll_cond, &poll_lock, &timeout)); + } + + pthread_mutex_unlock(&poll_lock); + + timeout.tv_sec = time(NULL) + 10; + pthread_mutex_lock(&lock_receive); + + while(rejected == false) { + assert(!pthread_cond_timedwait(&receive_cond, &lock_receive, &timeout)); + } + + pthread_mutex_unlock(&lock_receive); + + /* closing channel, meshes and destroying confbase */ + meshlink_close(mesh1); + meshlink_close(mesh2); + assert(meshlink_destroy("acceptconf1")); + assert(meshlink_destroy("acceptconf2")); + + return true; +} + +/* Execute meshlink_channel_set_accept_cb Test Case # 2 - Invalid case*/ +static void test_case_set_channel_accept_cb_02(void **state) { + execute_test(test_steps_set_channel_accept_cb_02, state); +} +/* Test Steps for meshlink_channel_set_accept_cb Test Case # 2 - Invalid case + + Test Steps: + 1. Passing NULL as mesh handle argument for channel accept callback. + + Expected Result: + meshlink_channel_set_accept_cb returning proper meshlink_errno. +*/ +static bool test_steps_set_channel_accept_cb_02(void) { + /* setting channel accept cb with NULL as mesh handle and valid callback */ + meshlink_set_channel_accept_cb(NULL, channel_accept); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + return true; +} + + +int test_meshlink_set_channel_accept_cb(void) { + const struct CMUnitTest blackbox_channel_set_accept_cb_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_set_channel_accept_cb_01, NULL, NULL, + (void *)&test_case_channel_set_accept_cb_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_set_channel_accept_cb_02, NULL, NULL, + (void *)&test_case_channel_set_accept_cb_02_state) + }; + + total_tests += sizeof(blackbox_channel_set_accept_cb_tests) / sizeof(blackbox_channel_set_accept_cb_tests[0]); + + int failed = cmocka_run_group_tests(blackbox_channel_set_accept_cb_tests, NULL, NULL); + + return failed; +} + + + diff --git a/test/blackbox/run_blackbox_tests/test_cases_channel_set_accept_cb.h b/test/blackbox/run_blackbox_tests/test_cases_channel_set_accept_cb.h new file mode 100644 index 0000000..9d91d59 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_channel_set_accept_cb.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_CHANNELS_SET_ACCEPT_CB_H +#define TEST_CASES_CHANNELS_SET_ACCEPT_CB_H + +/* + test_cases_channel_set_accept_cb.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int total_tests; +extern int test_meshlink_set_channel_accept_cb(void); + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_channel_set_poll_cb.c b/test/blackbox/run_blackbox_tests/test_cases_channel_set_poll_cb.c new file mode 100644 index 0000000..f0cfbfd --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_channel_set_poll_cb.c @@ -0,0 +1,519 @@ +/* + test_cases_channel_set_poll_cb.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_channel_set_poll_cb.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "../../utils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG +/* Modify this to change the port number */ +#define PORT 8000 + +#define NUT "nut" +#define PEER "peer" +#define TEST_POLL_CB "test_poll_cb" +#define create_path(confbase, node_name, test_case_no) assert(snprintf(confbase, sizeof(confbase), TEST_POLL_CB "_%ld_%s_%02d", (long) getpid(), node_name, test_case_no) > 0) + +typedef struct test_cb_data { + size_t cb_data_len; + size_t cb_total_data_len; + int total_cb_count; + void (*cb_handler)(void); + bool cb_flag; +} test_cb_t; + +static void test_case_channel_set_poll_cb_01(void **state); +static bool test_steps_channel_set_poll_cb_01(void); +static void test_case_channel_set_poll_cb_02(void **state); +static bool test_steps_channel_set_poll_cb_02(void); +static void test_case_channel_set_poll_cb_03(void **state); +static bool test_steps_channel_set_poll_cb_03(void); +static void test_case_channel_set_poll_cb_04(void **state); +static bool test_steps_channel_set_poll_cb_04(void); +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len); + +static black_box_state_t test_case_channel_set_poll_cb_01_state = { + .test_case_name = "test_case_channel_set_poll_cb_01", +}; +static black_box_state_t test_case_channel_set_poll_cb_02_state = { + .test_case_name = "test_case_channel_set_poll_cb_02", +}; +static black_box_state_t test_case_channel_set_poll_cb_03_state = { + .test_case_name = "test_case_channel_set_poll_cb_03", +}; +static black_box_state_t test_case_channel_set_poll_cb_04_state = { + .test_case_name = "test_case_channel_set_poll_cb_04", +}; + +static bool polled; +static bool reachable; + +static pthread_mutex_t poll_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t poll_cond = PTHREAD_COND_INITIALIZER; +static pthread_mutex_t reachable_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t reachable_cond = PTHREAD_COND_INITIALIZER; + +/* channel accept callback */ +static bool channel_accept_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + (void)mesh; + (void)channel; + (void)port; + (void)data; + (void)len; + return false; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + pthread_mutex_lock(&poll_lock); + polled = true; + assert(!pthread_cond_broadcast(&poll_cond)); + pthread_mutex_unlock(&poll_lock); +} + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *source, bool reach) { + (void)mesh; + (void)source; + + if(!reach) { + return; + } + + pthread_mutex_lock(&reachable_lock); + reachable = true; + assert(!pthread_cond_broadcast(&reachable_cond)); + pthread_mutex_unlock(&reachable_lock); +} + +/* Execute meshlink_channel_set_poll_cb Test Case # 1 */ +static void test_case_channel_set_poll_cb_01(void **state) { + execute_test(test_steps_channel_set_poll_cb_01, state); +} +/* Test Steps for meshlink_channel_set_poll_cb Test Case # 1 + + Test Steps: + 1. Run NUT + 2. Open channel of the NUT itself + + Expected Result: + Opens a channel and also invokes poll callback. +*/ +static bool test_steps_channel_set_poll_cb_01(void) { + /* deleting the confbase if already exists */ + struct timespec timeout = {0}; + assert(meshlink_destroy("pollconf1")); + assert(meshlink_destroy("pollconf2")); + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instances */ + meshlink_handle_t *mesh1 = meshlink_open("pollconf1", "nut", "chat", DEV_CLASS_STATIONARY); + assert(mesh1 != NULL); + meshlink_handle_t *mesh2 = meshlink_open("pollconf2", "bar", "chat", DEV_CLASS_STATIONARY); + assert(mesh2 != NULL); + meshlink_set_log_cb(mesh1, MESHLINK_INFO, meshlink_callback_logger); + meshlink_set_log_cb(mesh2, MESHLINK_INFO, meshlink_callback_logger); + meshlink_set_node_status_cb(mesh1, node_status_cb); + meshlink_set_channel_accept_cb(mesh1, channel_accept_cb); + + /* Export and Import on both sides */ + reachable = false; + char *exp1 = meshlink_export(mesh1); + assert(exp1 != NULL); + char *exp2 = meshlink_export(mesh2); + assert(exp2 != NULL); + assert(meshlink_import(mesh1, exp2)); + assert(meshlink_import(mesh2, exp1)); + + assert(meshlink_start(mesh1)); + assert(meshlink_start(mesh2)); + + timeout.tv_sec = time(NULL) + 10; + pthread_mutex_lock(&reachable_lock); + + while(reachable == false) { + assert(!pthread_cond_timedwait(&reachable_cond, &reachable_lock, &timeout)); + } + + pthread_mutex_unlock(&reachable_lock); + + meshlink_node_t *destination = meshlink_get_node(mesh2, "nut"); + assert(destination != NULL); + + /* Open channel for nut node from bar node which should be accepted */ + polled = false; + meshlink_channel_t *channel = meshlink_channel_open(mesh2, destination, PORT, NULL, NULL, 0); + assert(channel); + meshlink_set_channel_poll_cb(mesh2, channel, poll_cb); + + timeout.tv_sec = time(NULL) + 10; + pthread_mutex_lock(&poll_lock); + + while(polled == false) { + assert(!pthread_cond_timedwait(&poll_cond, &poll_lock, &timeout)); + } + + pthread_mutex_unlock(&poll_lock); + + /* closing channel, meshes and destroying confbase */ + meshlink_close(mesh1); + meshlink_close(mesh2); + assert(meshlink_destroy("pollconf1")); + assert(meshlink_destroy("pollconf2")); + + return true; +} + +/* Execute meshlink_channel_set_poll_cb Test Case # 2 */ +static void test_case_channel_set_poll_cb_02(void **state) { + execute_test(test_steps_channel_set_poll_cb_02, state); +} +/* Test Steps for meshlink_channel_set_poll_cb Test Case # 2 + + Test Steps: + 1. Run NUT + 2. Open channel of the NUT itself + 3. Pass NULL as mesh handle argument for meshlink_set_channel_poll_cb API + + Expected Result: + Reports error accordingly by returning NULL +*/ +static bool test_steps_channel_set_poll_cb_02(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + meshlink_handle_t *mesh_handle = meshlink_open("channelpollconf3", "nut", "node_sim", 1); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + assert(meshlink_start(mesh_handle)); + + meshlink_node_t *node = meshlink_get_self(mesh_handle); + assert(node != NULL); + + /* Opening channel */ + meshlink_channel_t *channel = meshlink_channel_open(mesh_handle, node, PORT, NULL, NULL, 0); + assert(channel != NULL); + + /* Setting poll cb with NULL as mesh handler */ + meshlink_set_channel_poll_cb(NULL, channel, poll_cb); + assert_int_not_equal(meshlink_errno, 0); + + meshlink_close(mesh_handle); + assert(meshlink_destroy("channelpollconf3")); + return true; +} + +/* Execute meshlink_channel_set_poll_cb Test Case # 3 */ +static void test_case_channel_set_poll_cb_03(void **state) { + execute_test(test_steps_channel_set_poll_cb_03, state); +} +/* Test Steps for meshlink_channel_set_poll_cb Test Case # 3 + + Test Steps: + 1. Run NUT + 2. Open channel of the NUT itself + 3. Pass NULL as channel handle argument for meshlink_set_channel_poll_cb API + + Expected Result: + Reports error accordingly by returning NULL +*/ +static bool test_steps_channel_set_poll_cb_03(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + meshlink_handle_t *mesh_handle = meshlink_open("channelpollconf4", "nut", "node_sim", 1); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + assert(meshlink_start(mesh_handle)); + + /* Setting poll cb with NULL as channel handler */ + meshlink_set_channel_poll_cb(mesh_handle, NULL, poll_cb); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + meshlink_close(mesh_handle); + assert(meshlink_destroy("channelpollconf4")); + return true; +} + +static test_cb_t poll_cb_data; +static test_cb_t recv_cb_data; +static meshlink_handle_t *mesh; + +/* Peer node channel receive callback's internal handler function - Blocks for 2 seconds whenever it gets invoked */ +static void recv_cb_data_handler(void) { + static int poll_cb_last_count; + + // Sleep for 1 second to allow NUT's callback to invoke already scheduled callbacks + // i.e, this sleeps prevents a condition where if the flag is set assuming that + // further callbacks are invalid then pending poll callbacks can misinterpreted as invalid. + // TODO: Fix this race condition in the test case without sleep() + + sleep(1); + + // Make sure there is change in the cumulative poll callbacks count + + if(!poll_cb_last_count) { + poll_cb_last_count = poll_cb_data.total_cb_count; + } else { + assert(poll_cb_data.total_cb_count > poll_cb_last_count); + } + + // Set the receive callback block flag and reset it back after 2 seconds sleep + + recv_cb_data.cb_flag = true; + sleep(2); + recv_cb_data.cb_flag = false; +} + +/* Peer node channel receive callback's internal handler function - Stops the NUT's instance and + resets it's own internal handler */ +static void recv_cb_data_handler2(void) { + + // Stop the NUT's meshlink instance, set the receive callback flag indicating that further + // poll callbacks are considered to be invalid + + meshlink_stop(mesh); + recv_cb_data.cb_flag = true; + + // Reset the callback handler (i.e, this is a one-time operation) + + recv_cb_data.cb_handler = NULL; +} + +/* Peer node channel receive callback's internal handler function - Blocks for straight 5 seconds and + resets it's own internal handler */ +static void recv_cb_data_handler3(void) { + sleep(5); + recv_cb_data.cb_handler = NULL; + recv_cb_data.cb_flag = false; +} + +/* NUT channel poll callback's internal handler function - Assert on peer node receive callback's flag */ +static void poll_cb_data_handler(void) { + assert_false(recv_cb_data.cb_flag); +} + +/* Peer node's receive callback handler */ +static void receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) { + (void)mesh; + (void)channel; + (void)data; + + // printf("Received %zu bytes\n", len); + (recv_cb_data.total_cb_count)++; + recv_cb_data.cb_total_data_len += len; + recv_cb_data.cb_data_len = len; + + if(recv_cb_data.cb_handler) { + recv_cb_data.cb_handler(); + } +} + +/* NUT's poll callback handler */ +static void poll_cb2(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)mesh; + (void)channel; + + // printf("Poll len: %zu\n", len); + assert(len); + (poll_cb_data.total_cb_count)++; + poll_cb_data.cb_total_data_len += len; + poll_cb_data.cb_data_len = len; + + if(poll_cb_data.cb_handler) { + (poll_cb_data.cb_handler)(); + } +} + +/* Peer node's accept callback handler */ +static bool accept_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + (void)port; + (void)data; + (void)len; + + channel->node->priv = channel; + meshlink_set_channel_receive_cb(mesh, channel, receive_cb); + return true; +} + +/* Execute meshlink_channel_set_poll_cb Test Case # 4 - Corner cases */ +static void test_case_channel_set_poll_cb_04(void **state) { + execute_test(test_steps_channel_set_poll_cb_04, state); +} +/* Test Steps for meshlink_channel_set_poll_cb Test Case # 4 + + Test Scenarios: + 1. Validate that Node-Under-Test never invokes the poll callback from a silent channel, here 65 seconds + 2. Send some data on to the data channel and block the reader end of the channel for a while where + at that instance nUT should not invokes any periodic callbacks. Once the peer node unblocks it's + instance it should continue to invokes callbacks. + This should repeat until the NUT channel sends it's complete data or the poll callback invokes with + max default size as length. + 3. Send a big packet of maximum send buffer size where length becomes 0 bytes, still NUT channel + should not invoke 0 length callback. Make sure by blocking the receiver and assert on the poll callback. + 4. NUT channel should not invoke the poll callback after self node going offline or due to it's reachability status. + 5. Modify the channel send buffer queue size which should be the new poll callback size further. +*/ +static bool test_steps_channel_set_poll_cb_04(void) { + char nut_confbase[PATH_MAX]; + char peer_confbase[PATH_MAX]; + create_path(nut_confbase, NUT, 4); + create_path(peer_confbase, PEER, 4); + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + mesh = meshlink_open(nut_confbase, NUT, TEST_POLL_CB, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + meshlink_handle_t *mesh_peer = meshlink_open(peer_confbase, PEER, TEST_POLL_CB, DEV_CLASS_STATIONARY); + assert_non_null(mesh_peer); + + link_meshlink_pair(mesh, mesh_peer); + meshlink_set_channel_accept_cb(mesh_peer, accept_cb); + + assert_true(meshlink_start(mesh)); + assert_true(meshlink_start(mesh_peer)); + meshlink_node_t *node = meshlink_get_node(mesh, PEER); + + /* 1. Accept and stay idle for 65 seconds */ + + bzero(&poll_cb_data, sizeof(poll_cb_data)); + bzero(&recv_cb_data, sizeof(recv_cb_data)); + + meshlink_channel_t *channel = meshlink_channel_open(mesh, node, PORT, NULL, NULL, 0); + assert_non_null(channel); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb2); + sleep(65); + assert_int_equal(poll_cb_data.total_cb_count, 1); + assert_int_not_equal(poll_cb_data.cb_data_len, 0); + size_t default_max_size = poll_cb_data.cb_data_len; + + // Send 7 MSS size packet + + char *buffer = malloc(default_max_size); + assert_non_null(buffer); + size_t send_size = default_max_size; + bzero(&poll_cb_data, sizeof(poll_cb_data)); + bzero(&recv_cb_data, sizeof(recv_cb_data)); + + size_t mss_size = meshlink_channel_get_mss(mesh, channel); + assert_int_not_equal(mss_size, -1); + + if(mss_size * 7 <= default_max_size) { + send_size = mss_size * 7; + } + + /* 2. Validate whether poll callback is invoked in case of channel is holding data in send buffer for a while */ + + bzero(&poll_cb_data, sizeof(poll_cb_data)); + bzero(&recv_cb_data, sizeof(recv_cb_data)); + poll_cb_data.cb_handler = poll_cb_data_handler; + recv_cb_data.cb_handler = recv_cb_data_handler; + assert_int_equal(meshlink_channel_send(mesh, channel, buffer, send_size), send_size); + assert_after(poll_cb_data.cb_data_len == default_max_size, 60); + assert_int_equal(recv_cb_data.cb_total_data_len, send_size); + + /* 3. On sending max send buffer sized packed on a channel should not invoke callback with length 0 */ + + bzero(&poll_cb_data, sizeof(poll_cb_data)); + bzero(&recv_cb_data, sizeof(recv_cb_data)); + poll_cb_data.cb_handler = poll_cb_data_handler; + recv_cb_data.cb_handler = recv_cb_data_handler3; + recv_cb_data.cb_flag = true; + assert_int_equal(meshlink_channel_send(mesh, channel, buffer, default_max_size), default_max_size); + assert_after(poll_cb_data.cb_data_len == default_max_size, 60); + + + /* 4. Poll callback should not be invoked when the self node is offline and it has data in it's buffer */ + + bzero(&recv_cb_data, sizeof(recv_cb_data)); + recv_cb_data.cb_handler = recv_cb_data_handler2; + poll_cb_data.cb_handler = poll_cb_data_handler; + assert_int_equal(meshlink_channel_send(mesh, channel, buffer, send_size), send_size); + assert_after(recv_cb_data.cb_flag, 20); + sleep(2); + assert_int_equal(meshlink_channel_send(mesh, channel, buffer, 50), 50); + sleep(2); + recv_cb_data.cb_flag = false; + assert_true(meshlink_start(mesh)); + assert_after(poll_cb_data.cb_data_len == default_max_size, 10); + + /* 5. Changing the sendq size should reflect on the poll callback length */ + + bzero(&poll_cb_data, sizeof(poll_cb_data)); + bzero(&recv_cb_data, sizeof(recv_cb_data)); + + size_t new_size = meshlink_channel_get_mss(mesh, channel) * 3; + assert_int_not_equal(new_size, -1); + meshlink_set_channel_sndbuf(mesh, channel, new_size); + assert_after(new_size == poll_cb_data.cb_data_len, 5); + send_size = new_size / 2; + assert_int_equal(meshlink_channel_send(mesh, channel, buffer, send_size), send_size); + assert_after(new_size == poll_cb_data.cb_data_len, 5); + + /* If peer node's channel is closed/freed but the host node is not freed then poll callback with length 0 is expected */ + + /*assert_int_not_equal(poll_cb_data.cb_total_data_len, 0); + + meshlink_node_t *channel_peer = node_peer->priv; + meshlink_channel_close(mesh_peer, channel_peer); + assert_after(!poll_cb_data.cb_data_len, 60);*/ + + // Cleanup + + free(buffer); + meshlink_close(mesh); + meshlink_close(mesh_peer); + assert_true(meshlink_destroy(nut_confbase)); + assert_true(meshlink_destroy(peer_confbase)); + return true; +} + +int test_meshlink_set_channel_poll_cb(void) { + const struct CMUnitTest blackbox_channel_set_poll_cb_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_channel_set_poll_cb_01, NULL, NULL, + (void *)&test_case_channel_set_poll_cb_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_channel_set_poll_cb_02, NULL, NULL, + (void *)&test_case_channel_set_poll_cb_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_channel_set_poll_cb_03, NULL, NULL, + (void *)&test_case_channel_set_poll_cb_03_state), + cmocka_unit_test_prestate_setup_teardown(test_case_channel_set_poll_cb_04, NULL, NULL, + (void *)&test_case_channel_set_poll_cb_04_state) + }; + total_tests += sizeof(blackbox_channel_set_poll_cb_tests) / sizeof(blackbox_channel_set_poll_cb_tests[0]); + + return cmocka_run_group_tests(blackbox_channel_set_poll_cb_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_channel_set_poll_cb.h b/test/blackbox/run_blackbox_tests/test_cases_channel_set_poll_cb.h new file mode 100644 index 0000000..71796e2 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_channel_set_poll_cb.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_SET_POLL_CB_H +#define TEST_CASES_SET_POLL_CB_H + +/* + test_cases_set_poll_cb.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_set_channel_poll_cb(void); +extern int total_tests; + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_channel_set_receive_cb.c b/test/blackbox/run_blackbox_tests/test_cases_channel_set_receive_cb.c new file mode 100644 index 0000000..697812e --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_channel_set_receive_cb.c @@ -0,0 +1,261 @@ +/* + test_cases_channel_set_receive_cb.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "test_cases_channel_set_receive_cb.h" +#include +#include +#include +#include +#include +#include +#include +#include + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +/* Modify this to change the port number */ +#define PORT 8000 + +/* Modify this to change the channel receive callback access buffer */ +#define TCP_TEST 8000 + +static void test_case_set_channel_receive_cb_01(void **state); +static bool test_steps_set_channel_receive_cb_01(void); +static void test_case_set_channel_receive_cb_02(void **state); +static bool test_steps_set_channel_receive_cb_02(void); +static void test_case_set_channel_receive_cb_03(void **state); +static bool test_steps_set_channel_receive_cb_03(void); + +static bool rec_stat = false; +static bool accept_stat = false; + +/* mutex for the receive callback common resources */ +static pthread_mutex_t lock_accept = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t lock_receive = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t accept_cond = PTHREAD_COND_INITIALIZER; +static pthread_cond_t receive_cond = PTHREAD_COND_INITIALIZER; + +static black_box_state_t test_case_channel_set_receive_cb_01_state = { + .test_case_name = "test_case_channel_set_receive_cb_01", +}; + +static black_box_state_t test_case_channel_set_receive_cb_02_state = { + .test_case_name = "test_case_channel_set_receive_cb_02", +}; + +static black_box_state_t test_case_channel_set_receive_cb_03_state = { + .test_case_name = "test_case_channel_set_receive_cb_03", +}; + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + (void)channel; + (void)dat; + (void)len; + + pthread_mutex_lock(& lock_receive); + rec_stat = true; + assert(!pthread_cond_broadcast(&receive_cond)); + pthread_mutex_unlock(& lock_receive); +} + +static bool accept_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + (void)port; + (void)data; + (void)len; + + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + + pthread_mutex_lock(& lock_accept); + accept_stat = true; + assert(!pthread_cond_broadcast(&accept_cond)); + pthread_mutex_unlock(& lock_accept); + + return true; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + + meshlink_set_channel_poll_cb(mesh, channel, NULL); + assert(meshlink_channel_send(mesh, channel, "Hello", 5) >= 0); +} + +/* Execute meshlink_channel_set_receive_cb Test Case # 1 */ +static void test_case_set_channel_receive_cb_01(void **state) { + execute_test(test_steps_set_channel_receive_cb_01, state); +} +/* Test Steps for meshlink_channel_set_receive_cb Test Case # 1 - Valid case + + Test Steps: + 1. Run NUT and Open channel for itself. + 2. Set channel receive callback and send data. + + Expected Result: + Opens a channel by invoking channel receive callback when data sent to it. +*/ +static bool test_steps_set_channel_receive_cb_01(void) { + struct timespec timeout = {0}; + assert(meshlink_destroy("channelreceiveconf")); + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + meshlink_handle_t *mesh_handle = meshlink_open("channelreceiveconf", "nut", "node_sim", 1); + assert(mesh_handle != NULL); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh_handle, accept_cb); + + assert(meshlink_start(mesh_handle)); + + meshlink_node_t *node = meshlink_get_self(mesh_handle); + assert(node != NULL); + + rec_stat = false; + accept_stat = false; + meshlink_channel_t *channel = meshlink_channel_open(mesh_handle, node, 8000, NULL, NULL, 0); + meshlink_set_channel_poll_cb(mesh_handle, channel, poll_cb); + + timeout.tv_sec = time(NULL) + 20; + pthread_mutex_lock(& lock_accept); + + while(accept_stat == false) { + assert(!pthread_cond_timedwait(&accept_cond, &lock_accept, &timeout)); + } + + pthread_mutex_unlock(& lock_accept); + + timeout.tv_sec = time(NULL) + 20; + pthread_mutex_lock(& lock_receive); + + while(rec_stat == false) { + assert(pthread_cond_timedwait(&receive_cond, &lock_receive, &timeout) == 0); + } + + pthread_mutex_unlock(& lock_receive); + + meshlink_close(mesh_handle); + assert(meshlink_destroy("channelreceiveconf")); + + return true; +} + +/* Execute meshlink_channel_set_receive_cb Test Case # 2 */ +static void test_case_set_channel_receive_cb_02(void **state) { + execute_test(test_steps_set_channel_receive_cb_02, state); +} +/* Test Steps for meshlink_channel_set_receive_cb Test Case # 2 - Invalid case + + Test Steps: + 1. Run NUT and Open channel for itself. + 2. Set channel receive callback with NULL as mesh handle. + + Expected Result: + meshlink_channel_set_receive_cb returning proper meshlink_errno. +*/ +static bool test_steps_set_channel_receive_cb_02(void) { + assert(meshlink_destroy("channelreceiveconf")); + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + meshlink_handle_t *mesh_handle = meshlink_open("channelreceiveconf", "nut", "node_sim", 1); + assert(mesh_handle != NULL); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh_handle, accept_cb); + + /* Starting NUT */ + assert(meshlink_start(mesh_handle)); + meshlink_node_t *node = meshlink_get_self(mesh_handle); + assert(node != NULL); + + meshlink_channel_t *channel = meshlink_channel_open_ex(mesh_handle, node, 8000, NULL, NULL, 0, MESHLINK_CHANNEL_UDP); + assert(channel != NULL); + meshlink_set_channel_poll_cb(mesh_handle, channel, poll_cb); + + /* Setting channel for NUT using meshlink_set_channel_receive_cb API with NULL as mesh handle */ + meshlink_set_channel_receive_cb(NULL, channel, channel_receive_cb); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + meshlink_close(mesh_handle); + assert(meshlink_destroy("channelreceiveconf")); + + return true; +} + +/* Execute meshlink_channel_set_receive_cb Test Case # 3 */ +static void test_case_set_channel_receive_cb_03(void **state) { + execute_test(test_steps_set_channel_receive_cb_03, state); +} +/* Test Steps for meshlink_channel_set_receive_cb Test Case # 3 - Invalid case + + Test Steps: + 1. Run NUT and Open channel for itself. + 2. Set channel receive callback with NULL as channel handle. + + Expected Result: + meshlink_channel_set_receive_cb returning proper meshlink_errno. +*/ +static bool test_steps_set_channel_receive_cb_03(void) { + assert(meshlink_destroy("channelreceiveconf")); + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + meshlink_handle_t *mesh_handle = meshlink_open("channelreceiveconf", "nut", "node_sim", 1); + fprintf(stderr, "meshlink_open status: %s\n", meshlink_strerror(meshlink_errno)); + assert(mesh_handle != NULL); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh_handle, accept_cb); + + /* Starting NUT */ + assert(meshlink_start(mesh_handle)); + + /* Setting channel for NUT using meshlink_set_channel_receive_cb API channel handle as NULL */ + meshlink_set_channel_receive_cb(mesh_handle, NULL, channel_receive_cb); + + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + meshlink_close(mesh_handle); + assert(meshlink_destroy("channelreceiveconf")); + return true; +} + + +int test_meshlink_set_channel_receive_cb(void) { + const struct CMUnitTest blackbox_channel_set_receive_cb_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_set_channel_receive_cb_01, NULL, NULL, + (void *)&test_case_channel_set_receive_cb_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_set_channel_receive_cb_02, NULL, NULL, + (void *)&test_case_channel_set_receive_cb_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_set_channel_receive_cb_03, NULL, NULL, + (void *)&test_case_channel_set_receive_cb_03_state) + }; + total_tests += sizeof(blackbox_channel_set_receive_cb_tests) / sizeof(blackbox_channel_set_receive_cb_tests[0]); + + int failed = cmocka_run_group_tests(blackbox_channel_set_receive_cb_tests, NULL, NULL); + + return failed; +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_channel_set_receive_cb.h b/test/blackbox/run_blackbox_tests/test_cases_channel_set_receive_cb.h new file mode 100644 index 0000000..e781f8c --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_channel_set_receive_cb.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_CHANNELS_SET_RECEIVE_H +#define TEST_CASES_CHANNELS_SET_RECEIVE_H + +/* + test_cases_channel_set_receive_cb.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_set_channel_receive_cb(void); +extern int total_tests; + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_channel_shutdown.c b/test/blackbox/run_blackbox_tests/test_cases_channel_shutdown.c new file mode 100644 index 0000000..c8f6ef4 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_channel_shutdown.c @@ -0,0 +1,348 @@ +/* + test_cases_channel_shutdown.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_channel_shutdown.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include +#include + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +static void test_case_mesh_channel_shutdown_01(void **state); +static bool test_steps_mesh_channel_shutdown_01(void); +static void test_case_mesh_channel_shutdown_02(void **state); +static bool test_steps_mesh_channel_shutdown_02(void); +static void test_case_mesh_channel_shutdown_03(void **state); +static bool test_steps_mesh_channel_shutdown_03(void); + +static void receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len); + +/* State structure for meshlink_channel_shutdown Test Case #1 */ +static black_box_state_t test_mesh_channel_shutdown_01_state = { + .test_case_name = "test_case_mesh_channel_shutdown_01", +}; + +/* State structure for meshlink_channel_shutdown Test Case #2 */ +static black_box_state_t test_mesh_channel_shutdown_02_state = { + .test_case_name = "test_case_mesh_channel_shutdown_02", +}; + +/* State structure for meshlink_channel_shutdown Test Case #3 */ +static black_box_state_t test_mesh_channel_shutdown_03_state = { + .test_case_name = "test_case_mesh_channel_shutdown_03", +}; + +static bool channel_acc; +static bool polled; +static bool foo_responded; +static bool bar_responded; + +/* mutex for the common variable */ +static pthread_mutex_t accept_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t poll_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t bar_responded_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t foo_responded_lock = PTHREAD_MUTEX_INITIALIZER; + +static pthread_cond_t accept_cond = PTHREAD_COND_INITIALIZER; +static pthread_cond_t poll_cond = PTHREAD_COND_INITIALIZER; +static pthread_cond_t foo_cond = PTHREAD_COND_INITIALIZER; +static pthread_cond_t bar_cond = PTHREAD_COND_INITIALIZER; + +static bool accept_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + (void)data; + + assert(port == 7); + assert(!len); + + meshlink_set_channel_receive_cb(mesh, channel, receive_cb); + channel->node->priv = channel; + pthread_mutex_lock(&accept_lock); + channel_acc = true; + assert(!pthread_cond_broadcast(&accept_cond)); + pthread_mutex_unlock(&accept_lock); + + return true; +} + +/* channel receive callback */ +static void receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)dat; + (void)len; + + if(!strcmp(mesh->name, "foo")) { + pthread_mutex_lock(& foo_responded_lock); + foo_responded = true; + assert(!pthread_cond_broadcast(&foo_cond)); + pthread_mutex_unlock(& foo_responded_lock); + + } else if(!strcmp(mesh->name, "bar")) { + pthread_mutex_lock(& bar_responded_lock); + bar_responded = true; + assert(!pthread_cond_broadcast(&bar_cond)); + pthread_mutex_unlock(& bar_responded_lock); + + assert(meshlink_channel_send(mesh, channel, "echo", 4) >= 0); + + } +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + + meshlink_set_channel_poll_cb(mesh, channel, NULL); + pthread_mutex_lock(&poll_lock); + polled = true; + assert(!pthread_cond_broadcast(&poll_cond)); + pthread_mutex_unlock(&poll_lock); +} + +/* Execute meshlink_channel_shutdown Test Case # 1*/ +static void test_case_mesh_channel_shutdown_01(void **state) { + execute_test(test_steps_mesh_channel_shutdown_01, state); +} + +/* Test Steps for meshlink_channel_shutdown Test Case # 1 - Valid case + + Test Steps: + 1. Open foo and bar instances and open a channel between them + 2. Send data through the channel. + 3. Shut down channel's read and send data + 4. Shutdown channel's write and send data + + Expected Result: + Data is able to receive through channel before shutting down, + On shutting down read its should not able to receive data and when write + is shut down its should be able to send data through channel. +*/ +static bool test_steps_mesh_channel_shutdown_01(void) { + struct timespec timeout = {0}; + assert(meshlink_destroy("chan_shutdown_conf.1")); + assert(meshlink_destroy("chan_shutdown_conf.2")); + // Open two new meshlink instance. + + meshlink_handle_t *mesh1 = meshlink_open("chan_shutdown_conf.1", "foo", "channels", DEV_CLASS_BACKBONE); + assert(mesh1 != NULL); + + meshlink_handle_t *mesh2 = meshlink_open("chan_shutdown_conf.2", "bar", "channels", DEV_CLASS_BACKBONE); + assert(mesh2 != NULL); + + char *data = meshlink_export(mesh1); + assert(data); + assert(meshlink_import(mesh2, data)); + free(data); + data = meshlink_export(mesh2); + assert(data); + assert(meshlink_import(mesh1, data)); + free(data); + + // Set the callbacks. + + meshlink_set_channel_accept_cb(mesh2, accept_cb); + + // Start both instances + + assert(meshlink_start(mesh1)); + assert(meshlink_start(mesh2)); + sleep(1); + + // Open a channel from foo to bar. + + meshlink_node_t *bar = meshlink_get_node(mesh1, "bar"); + assert(bar); + + meshlink_channel_t *channel1 = meshlink_channel_open(mesh1, bar, 7, receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh1, channel1, poll_cb); + + timeout.tv_sec = time(NULL) + 10; + pthread_mutex_lock(&poll_lock); + + while(polled == false) { + assert(!pthread_cond_timedwait(&poll_cond, &poll_lock, &timeout)); + } + + pthread_mutex_unlock(&poll_lock); + + timeout.tv_sec = time(NULL) + 10; + pthread_mutex_lock(&accept_lock); + + while(channel_acc == false) { + assert(!pthread_cond_timedwait(&accept_cond, &accept_lock, &timeout)); + } + + pthread_mutex_unlock(&accept_lock); + + // Sending to bar and testing the echo + + assert(meshlink_channel_send(mesh1, channel1, "echo", 4) >= 0); + + timeout.tv_sec = time(NULL) + 10; + pthread_mutex_lock(&foo_responded_lock); + + while(foo_responded == false) { + assert(!pthread_cond_timedwait(&foo_cond, &foo_responded_lock, &timeout)); + } + + pthread_mutex_unlock(&foo_responded_lock); + assert(foo_responded); + + // Shutting down channel read + + meshlink_channel_shutdown(mesh1, channel1, SHUT_RD); + bar_responded = false; + foo_responded = false; + assert(meshlink_channel_send(mesh1, channel1, "echo", 4) >= 0); + + timeout.tv_sec = time(NULL) + 10; + pthread_mutex_lock(&bar_responded_lock); + + while(bar_responded == false) { + assert(!pthread_cond_timedwait(&bar_cond, &bar_responded_lock, &timeout)); + } + + pthread_mutex_unlock(&bar_responded_lock); + assert_int_equal(bar_responded, true); + sleep(1); + assert_int_equal(foo_responded, false); + + // Shutting down channel write + + meshlink_channel_shutdown(mesh1, channel1, SHUT_WR); + + ssize_t send_ret = meshlink_channel_send(mesh1, channel1, "echo", 4); + assert_int_equal(send_ret, -1); + + // Clean up. + + meshlink_close(mesh2); + meshlink_close(mesh1); + assert(meshlink_destroy("chan_shutdown_conf.1")); + assert(meshlink_destroy("chan_shutdown_conf.2")); + + return true; +} + +/* Execute meshlink_channel_shutdown Test Case # 2*/ +static void test_case_mesh_channel_shutdown_02(void **state) { + execute_test(test_steps_mesh_channel_shutdown_02, state); +} + +/* Test Steps for meshlink_channel_shutdown Test Case # 2 - Invalid case + + Test Steps: + 1. Open node instance and create a channel + 2. Call meshlink_channel_shutdown API by passing NULL as mesh handle + + Expected Result: + meshlink_channel_shutdown API should report proper error handling +*/ +static bool test_steps_mesh_channel_shutdown_02(void) { + assert(meshlink_destroy("channelshutdownconf.3")); + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + meshlink_handle_t *mesh_handle = meshlink_open("channelshutdownconf.3", "nut", "node_sim", 1); + assert(mesh_handle != NULL); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh_handle, accept_cb); + + assert(meshlink_start(mesh_handle)); + + meshlink_node_t *node = meshlink_get_self(mesh_handle); + assert(node != NULL); + + meshlink_channel_t *channel = meshlink_channel_open(mesh_handle, node, 8000, NULL, NULL, 0); + assert(channel); + meshlink_set_channel_poll_cb(mesh_handle, channel, poll_cb); + + // Passing NULL as mesh handle and other arguments being valid + + meshlink_channel_shutdown(NULL, channel, SHUT_WR); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + meshlink_close(mesh_handle); + assert(meshlink_destroy("channelshutdownconf.3")); + + return true; +} + +/* Execute meshlink_channel_shutdown Test Case # 3*/ +static void test_case_mesh_channel_shutdown_03(void **state) { + execute_test(test_steps_mesh_channel_shutdown_03, state); +} + +/* Test Steps for meshlink_channel_shutdown Test Case # 3 + + Test Steps: + 1. Open node instance + 2. Call meshlink_channel_shutdown API by passing NULL as channel handle + + Expected Result: + meshlink_channel_shutdown API should report proper error handling +*/ +static bool test_steps_mesh_channel_shutdown_03(void) { + assert(meshlink_destroy("channelshutdownconf.4")); + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + meshlink_handle_t *mesh_handle = meshlink_open("channelshutdownconf.4", "nut", "node_sim", 1); + assert(mesh_handle != NULL); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh_handle, accept_cb); + + assert(meshlink_start(mesh_handle)); + + // Passing NULL as mesh handle and other arguments being valid + + meshlink_channel_shutdown(mesh_handle, NULL, SHUT_WR); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + meshlink_close(mesh_handle); + assert(meshlink_destroy("channelshutdownconf.4")); + + return true; +} + + +int test_meshlink_channel_shutdown(void) { + const struct CMUnitTest blackbox_channel_shutdown_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_channel_shutdown_01, NULL, NULL, + (void *)&test_mesh_channel_shutdown_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_channel_shutdown_02, NULL, NULL, + (void *)&test_mesh_channel_shutdown_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_channel_shutdown_03, NULL, NULL, + (void *)&test_mesh_channel_shutdown_03_state) + }; + total_tests += sizeof(blackbox_channel_shutdown_tests) / sizeof(blackbox_channel_shutdown_tests[0]); + + return cmocka_run_group_tests(blackbox_channel_shutdown_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_channel_shutdown.h b/test/blackbox/run_blackbox_tests/test_cases_channel_shutdown.h new file mode 100644 index 0000000..13f4690 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_channel_shutdown.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_CHANNEL_SHUTDOWN_H +#define TEST_CASES_CHANNEL_SHUTDOWN_H + +/* + test_cases_channel_shutdown.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_channel_shutdown(void); +extern int total_tests; + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_default_blacklist.c b/test/blackbox/run_blackbox_tests/test_cases_default_blacklist.c new file mode 100644 index 0000000..508e784 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_default_blacklist.c @@ -0,0 +1,207 @@ +/* + test_cases_default_blacklist.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include "execute_tests.h" +#include "test_cases_default_blacklist.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "../../utils.h" + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +static void test_case_mesh_default_blacklist_01(void **state); +static bool test_steps_mesh_default_blacklist_01(void); +static void test_case_mesh_default_blacklist_02(void **state); +static bool test_steps_mesh_default_blacklist_02(void); + +/* State structure for meshlink_default_blacklist Test Case #1 */ +static black_box_state_t test_mesh_default_blacklist_01_state = { + .test_case_name = "test_case_mesh_default_blacklist_01", +}; + +/* State structure for meshlink_default_blacklist Test Case #2 */ +static black_box_state_t test_mesh_default_blacklist_02_state = { + .test_case_name = "test_case_mesh_default_blacklist_02", +}; + +/* Execute meshlink_default_blacklist Test Case # 1*/ +static void test_case_mesh_default_blacklist_01(void **state) { + execute_test(test_steps_mesh_default_blacklist_01, state); + return; +} + +static bool received = false; + +static void receive(meshlink_handle_t *mesh, meshlink_node_t *src, const void *data, size_t len) { + (void)mesh; + (void)data; + + assert(len); + + if(!strcmp(src->name, "bar") || !strcmp(src->name, "foz")) { + received = true; + } +} + +static bool bar_reachable = false; +static bool foz_reachable = false; + +void status_cb1(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcmp(node->name, "bar")) { + bar_reachable = reachable; + } else if(!strcmp(node->name, "foz")) { + foz_reachable = reachable; + } +} + +/* Test Steps for meshlink_default_blacklist Test Case # 1 + + Test Steps: + 1. Open all the node instances & Disable default blacklist + 2. Join bar node with foo and Send & Receive data + 3. Enable default blacklist and join foz node with foo node + and follow the steps done for bar node + + Expected Result: + When default blacklist is disabled, foo node should receive data from bar + but when enabled foo node should not receive data from foz +*/ +static bool test_steps_mesh_default_blacklist_01(void) { + assert(meshlink_destroy("def_blacklist_conf.1")); + assert(meshlink_destroy("def_blacklist_conf.2")); + assert(meshlink_destroy("def_blacklist_conf.3")); + + // Open two new meshlink instance. + meshlink_handle_t *mesh1 = meshlink_open("def_blacklist_conf.1", "foo", "blacklist", DEV_CLASS_BACKBONE); + assert(mesh1 != NULL); + meshlink_set_log_cb(mesh1, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_handle_t *mesh2 = meshlink_open("def_blacklist_conf.2", "bar", "blacklist", DEV_CLASS_BACKBONE); + assert(mesh2 != NULL); + meshlink_set_log_cb(mesh2, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_handle_t *mesh3 = meshlink_open("def_blacklist_conf.3", "foz", "blacklist", DEV_CLASS_BACKBONE); + assert(mesh3); + meshlink_set_log_cb(mesh3, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_set_receive_cb(mesh1, receive); + + meshlink_set_default_blacklist(mesh1, false); + + // Start both instances + bar_reachable = false; + foz_reachable = false; + meshlink_set_node_status_cb(mesh1, status_cb1); + assert(meshlink_start(mesh1)); + assert(meshlink_start(mesh2)); + assert(meshlink_start(mesh3)); + sleep(1); + + char *foo_export = meshlink_export(mesh1); + assert(foo_export != NULL); + assert(meshlink_import(mesh2, foo_export)); + char *bar_export = meshlink_export(mesh2); + assert(meshlink_import(mesh1, bar_export)); + sleep(5); + assert(bar_reachable); + + // Nodes should learn about each other + meshlink_node_t *foo = NULL; + foo = meshlink_get_node(mesh2, "foo"); + assert(foo); + + received = false; + assert(meshlink_send(mesh2, foo, "test", 5)); + assert_after(received, 2); + + // Enable default blacklist and join another node + meshlink_set_default_blacklist(mesh1, true); + + char *foz_export = meshlink_export(mesh3); + assert(foz_export); + assert(meshlink_import(mesh1, foz_export)); + assert(meshlink_import(mesh3, foo_export)); + sleep(5); + assert(foz_reachable); + + foo = meshlink_get_node(mesh3, "foo"); + assert(foo); + assert(meshlink_send(mesh3, foo, "test", 5)); + received = false; + assert(meshlink_send(mesh3, foo, "test", 5)); + assert_after(!received, 2); + + // Clean up. + free(foo_export); + free(foz_export); + free(bar_export); + meshlink_close(mesh1); + meshlink_close(mesh2); + meshlink_close(mesh3); + assert(meshlink_destroy("def_blacklist_conf.1")); + assert(meshlink_destroy("def_blacklist_conf.2")); + assert(meshlink_destroy("def_blacklist_conf.3")); + + return true; +} + +/* Execute meshlink_default_blacklist Test Case # 2*/ +static void test_case_mesh_default_blacklist_02(void **state) { + execute_test(test_steps_mesh_default_blacklist_02, state); +} + +/* Test Steps for meshlink_default_blacklist Test Case # 2 + + Test Steps: + 1. Calling meshlink_default_blacklist with NULL as mesh handle argument. + + Expected Result: + meshlink_default_blacklist API handles the invalid parameter when called by giving proper error number. +*/ +static bool test_steps_mesh_default_blacklist_02(void) { + // Passing NULL as mesh handle argument to the API + meshlink_set_default_blacklist(NULL, true); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + return true; +} + +int test_meshlink_default_blacklist(void) { + const struct CMUnitTest blackbox_default_blacklist_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_default_blacklist_01, NULL, NULL, + (void *)&test_mesh_default_blacklist_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_default_blacklist_02, NULL, NULL, + (void *)&test_mesh_default_blacklist_02_state) + }; + + total_tests += sizeof(blackbox_default_blacklist_tests) / sizeof(blackbox_default_blacklist_tests[0]); + + return cmocka_run_group_tests(blackbox_default_blacklist_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_default_blacklist.h b/test/blackbox/run_blackbox_tests/test_cases_default_blacklist.h new file mode 100644 index 0000000..c8e7af2 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_default_blacklist.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_DEFAULT_BLACKLIST_H +#define TEST_CASES_DEFAULT_BLACKLIST_H + +/* + test_cases_default_blacklist.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_default_blacklist(void); +extern int total_tests; + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_destroy.c b/test/blackbox/run_blackbox_tests/test_cases_destroy.c new file mode 100644 index 0000000..489e4d6 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_destroy.c @@ -0,0 +1,154 @@ +/* + test_cases_destroy.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_destroy.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include +#include +#include + + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +static void test_case_meshlink_destroy_01(void **state); +static bool test_meshlink_destroy_01(void); +static void test_case_meshlink_destroy_02(void **state); +static bool test_meshlink_destroy_02(void); +static void test_case_meshlink_destroy_03(void **state); +static bool test_meshlink_destroy_03(void); + +static black_box_state_t test_case_meshlink_destroy_01_state = { + .test_case_name = "test_case_meshlink_destroy_01", +}; +static black_box_state_t test_case_meshlink_destroy_02_state = { + .test_case_name = "test_case_meshlink_destroy_02", +}; +static black_box_state_t test_case_meshlink_destroy_03_state = { + .test_case_name = "test_case_meshlink_destroy_03", +}; + + +/* Execute destroy Test Case # 1 - valid case*/ +static void test_case_meshlink_destroy_01(void **state) { + execute_test(test_meshlink_destroy_01, state); +} + +/* Test Steps for destroy Test Case # 1 - Valid case + Test Steps: + 1. Open instance for NUT + 2. Close NUT, and destroy the confbase + 3. Open the same confbase directory + + Expected Result: + confbase should be deleted +*/ +static bool test_meshlink_destroy_01(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + // Create meshlink instance + char *confbase = "destroyconf"; + mesh_handle = meshlink_open(confbase, "nut", "node_sim", 1); + assert(mesh_handle); + + meshlink_close(mesh_handle); + + // Destroying NUT's confbase + bool result = meshlink_destroy(confbase); + assert_int_equal(result, true); + + // Verify whether confbase is removed or not + DIR *dir = opendir(confbase); + assert_int_equal(dir, NULL); + + return true; +} + +/* Execute destroy Test Case # 2 - passing NULL argument to the API */ +static void test_case_meshlink_destroy_02(void **state) { + execute_test(test_meshlink_destroy_02, state); +} + +/* Test Steps for destroy Test Case # 2 - Invalid case + Test Steps: + 1. Just passing NULL as argument to the API + + Expected Result: + Return false reporting failure +*/ +static bool test_meshlink_destroy_02(void) { + // Passing NULL as an argument to meshlink_destroy + bool result = meshlink_destroy(NULL); + assert_int_equal(result, false); + + return true; +} + +/* Execute status Test Case # 3 - destroying non existing file */ +static void test_case_meshlink_destroy_03(void **state) { + execute_test(test_meshlink_destroy_03, state); +} +/* Test Steps for destroy Test Case # 3 - Invalid case + Test Steps: + 1. unlink if there's any such test file + 2. Call API with that file name + + Expected Result: + Return false reporting failure +*/ +static bool test_meshlink_destroy_03(void) { + bool result = false; + + // Deletes if there is any file named 'non_existing' already + unlink("non_existing"); + + // Passing non-existing file as an argument to meshlink_destroy + result = meshlink_destroy("non_existing"); + assert_int_equal(result, false); + + return true; +} + + +int test_meshlink_destroy(void) { + const struct CMUnitTest blackbox_destroy_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_meshlink_destroy_01, NULL, NULL, + (void *)&test_case_meshlink_destroy_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_meshlink_destroy_02, NULL, NULL, + (void *)&test_case_meshlink_destroy_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_meshlink_destroy_03, NULL, NULL, + (void *)&test_case_meshlink_destroy_03_state) + }; + + total_tests += sizeof(blackbox_destroy_tests) / sizeof(blackbox_destroy_tests[0]); + + return cmocka_run_group_tests(blackbox_destroy_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_destroy.h b/test/blackbox/run_blackbox_tests/test_cases_destroy.h new file mode 100644 index 0000000..3d90dcb --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_destroy.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_DESTROY_H +#define TEST_CASES_DESTROY_H + +/* + test_cases_destroy.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int total_tests; +extern int test_meshlink_destroy(void); + +#endif // TEST_CASES_DESTROY_H diff --git a/test/blackbox/run_blackbox_tests/test_cases_export.c b/test/blackbox/run_blackbox_tests/test_cases_export.c new file mode 100644 index 0000000..0281f0b --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_export.c @@ -0,0 +1,118 @@ +/* + test_cases_export.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_export.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include + + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +static void test_case_export_01(void **state); +static bool test_export_01(void); +static void test_case_export_02(void **state); +static bool test_export_02(void); + +/* State structure for export API Test Case #1 */ +static black_box_state_t test_case_export_01_state = { + .test_case_name = "test_case_export_01", +}; +/* State structure for export API Test Case #2 */ +static black_box_state_t test_case_export_02_state = { + .test_case_name = "test_case_export_02", +}; + + +/* Execute export Test Case # 1 - valid case*/ +static void test_case_export_01(void **state) { + execute_test(test_export_01, state); +} +/* Test Steps for export Test Case # 1 - Valid case + Test Steps: + 1. Run NUT + 2. Export mesh + + Expected Result: + API returns a NULL terminated string containing meta data of NUT. +*/ +static bool test_export_01(void) { + assert(meshlink_destroy("exportconf")); + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + meshlink_handle_t *mesh_handle = meshlink_open("exportconf", "nut", "node_sim", 1); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + char *expo = meshlink_export(mesh_handle); + assert_int_not_equal(expo, NULL); + + meshlink_close(mesh_handle); + assert(meshlink_destroy("exportconf")); + + return true; +} + +/* Execute export Test Case # 2 - Invalid case*/ +static void test_case_export_02(void **state) { + execute_test(test_export_02, state); +} +/* Test Steps for export Test Case # 2 - Invalid case + Test Steps: + 1. Run NUT + 2. calling meshlink_export by passing NULL as mesh handle + + Expected Result: + API returns NULL reporting error when NULL being passed as mesh handle. +*/ +static bool test_export_02(void) { + // Calling export API with NULL as mesh handle + char *expo = meshlink_export(NULL); + assert_int_equal(expo, NULL); + + return true; +} + + +int test_meshlink_export(void) { + const struct CMUnitTest blackbox_export_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_export_01, NULL, NULL, + (void *)&test_case_export_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_export_02, NULL, NULL, + (void *)&test_case_export_02_state) + }; + + total_tests += sizeof(blackbox_export_tests) / sizeof(blackbox_export_tests[0]); + + return cmocka_run_group_tests(blackbox_export_tests, NULL, NULL); +} + diff --git a/test/blackbox/run_blackbox_tests/test_cases_export.h b/test/blackbox/run_blackbox_tests/test_cases_export.h new file mode 100644 index 0000000..ecea12e --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_export.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_EXPORT_H +#define TEST_CASES_EXPORT_H + +/* + test_cases_export.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int total_tests; +extern int test_meshlink_export(void); + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_get_all_nodes.c b/test/blackbox/run_blackbox_tests/test_cases_get_all_nodes.c new file mode 100644 index 0000000..6fd409b --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_get_all_nodes.c @@ -0,0 +1,182 @@ +/* + test_cases_get_all_nodes.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "test_cases_get_all_nodes.h" +#include +#include +#include +#include +#include +#include + + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +static void test_case_get_all_nodes_01(void **state); +static bool test_get_all_nodes_01(void); +static void test_case_get_all_nodes_02(void **state); +static bool test_get_all_nodes_02(void); +static void test_case_get_all_nodes_03(void **state); +static bool test_get_all_nodes_03(void); + +/* State structure for get_all_nodes Test Case #1 */ +static black_box_state_t test_case_get_all_nodes_01_state = { + .test_case_name = "test_case_get_all_nodes_01", +}; + +/* State structure for get_all_nodes Test Case #2 */ +static black_box_state_t test_case_get_all_nodes_02_state = { + .test_case_name = "test_case_get_all_nodes_02", +}; + +/* State structure for get_all_nodes Test Case #3 */ +static black_box_state_t test_case_get_all_nodes_03_state = { + .test_case_name = "test_case_get_all_nodes_03", +}; + +/* Execute get_all_nodes Test Case # 1 - Valid case - get all nodes in the mesh */ +static void test_case_get_all_nodes_01(void **state) { + execute_test(test_get_all_nodes_01, state); +} +/* Test Steps for get_all_nodes Test Case # 1 - Valid case + + Test Steps: + 1. Open NUT and get list of nodes + 2. Open bar and join with NUT + 3. get list of nodes together + + Expected Result: + Obtaining list of nodes in the mesh at the given instance +*/ +static bool test_get_all_nodes_01(void) { + assert(meshlink_destroy("getnodeconf1")); + assert(meshlink_destroy("getnodeconf2")); + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance for NUT */ + meshlink_handle_t *mesh1 = meshlink_open("getnodeconf1", "nut", "node_sim", DEV_CLASS_STATIONARY); + assert(mesh1); + meshlink_set_log_cb(mesh1, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + size_t nnodes = 0; + meshlink_node_t **nodes = NULL; + nodes = meshlink_get_all_nodes(mesh1, nodes, &nnodes); + assert_int_not_equal(nodes, NULL); + assert_int_equal(nnodes, 1); + + /* Create meshlink instance for bar */ + meshlink_handle_t *mesh2 = meshlink_open("getnodeconf2", "bar", "node_sim", DEV_CLASS_STATIONARY); + assert(mesh2); + + /* importing and exporting mesh meta data */ + char *exp1 = meshlink_export(mesh1); + assert(exp1 != NULL); + char *exp2 = meshlink_export(mesh2); + assert(exp2 != NULL); + assert(meshlink_import(mesh1, exp2)); + assert(meshlink_import(mesh2, exp1)); + + nodes = meshlink_get_all_nodes(mesh1, nodes, &nnodes); + assert_int_not_equal(nodes, NULL); + assert_int_equal(nnodes, 2); + + meshlink_close(mesh1); + meshlink_close(mesh2); + assert(meshlink_destroy("getnodeconf1")); + assert(meshlink_destroy("getnodeconf2")); + + return true; +} + + + +/* Execute get_all_nodes Test Case # 2 - Invalid case - get all nodes in the mesh passing NULL */ +static void test_case_get_all_nodes_02(void **state) { + execute_test(test_get_all_nodes_02, state); +} + +/* Test Steps for get_all_nodes Test Case # 2 - Invalid case + + Test Steps: + 1. Passing NULL as mesh handle argument for meshlink_get_all_nodes + + Expected Result: + Error reported correctly by returning NULL +*/ +static bool test_get_all_nodes_02(void) { + size_t nmemb = 0; + + meshlink_node_t **nodes = meshlink_get_all_nodes(NULL, NULL, &nmemb); + assert_null(nodes); + assert_int_equal(nmemb, NULL); + + return true; +} + +/* Execute get_all_nodes Test Case # 3 - Invalid case - get all nodes in the mesh passing NULL as nmeb arg */ +static void test_case_get_all_nodes_03(void **state) { + execute_test(test_get_all_nodes_03, state); +} +/* Test Steps for get_all_nodes Test Case # 3 - Invalid case + + Test Steps: + 1. Passing NULL as pointer to node members argument for meshlink_get_all_nodes + + Expected Result: + Error reported correctly by returning NULL +*/ +static bool test_get_all_nodes_03(void) { + /* Create meshlink instance */ + meshlink_handle_t *mesh_handle = meshlink_open("getallnodesconf", "nut", "node_sim", 1); + assert(mesh_handle); + assert(meshlink_start(mesh_handle)); + + meshlink_node_t **nodes = NULL; + nodes = meshlink_get_all_nodes(mesh_handle, nodes, NULL); + assert_int_equal(nodes, NULL); + + meshlink_close(mesh_handle); + assert(meshlink_destroy("getallnodesconf")); + + return true; +} + +int test_meshlink_get_all_nodes(void) { + const struct CMUnitTest blackbox_get_all_nodes[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_get_all_nodes_01, NULL, NULL, + (void *)&test_case_get_all_nodes_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_get_all_nodes_02, NULL, NULL, + (void *)&test_case_get_all_nodes_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_get_all_nodes_03, NULL, NULL, + (void *)&test_case_get_all_nodes_03_state) + }; + total_tests += sizeof(blackbox_get_all_nodes) / sizeof(blackbox_get_all_nodes[0]); + + return cmocka_run_group_tests(blackbox_get_all_nodes, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_get_all_nodes.h b/test/blackbox/run_blackbox_tests/test_cases_get_all_nodes.h new file mode 100644 index 0000000..898114a --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_get_all_nodes.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_GET_ALL_NODES_H +#define TEST_CASES_GET_ALL_NODES_H + +/* + test_cases_get_all_nodes.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int total_tests; +extern int test_meshlink_get_all_nodes(void); + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_get_all_nodes_by_dev_class.c b/test/blackbox/run_blackbox_tests/test_cases_get_all_nodes_by_dev_class.c new file mode 100644 index 0000000..f3c73a9 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_get_all_nodes_by_dev_class.c @@ -0,0 +1,344 @@ +/* + test_cases_get_all_nodes_by_dev_class.c.c -- Execution of specific meshlink black box test cases + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_get_all_nodes_by_dev_class.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +static void test_case_mesh_get_node_by_dev_class_01(void **state); +static bool test_steps_mesh_get_node_by_dev_class_01(void); +static void test_case_mesh_get_node_by_dev_class_02(void **state); +static bool test_steps_mesh_get_node_by_dev_class_02(void); +static void test_case_mesh_get_node_dev_class_01(void **state); +static bool test_steps_mesh_get_node_dev_class_01(void); +static void test_case_mesh_get_node_dev_class_02(void **state); +static bool test_steps_mesh_get_node_dev_class_02(void); + +/* State structure for meshlink_get_node Test Case #1 */ +static black_box_state_t test_mesh_get_node_by_dev_class_01_state = { + .test_case_name = "test_case_mesh_get_node_by_dev_class_01", +}; + +/* State structure for meshlink_get_node Test Case #2 */ +static black_box_state_t test_mesh_get_node_by_dev_class_02_state = { + .test_case_name = "test_case_mesh_get_node_by_dev_class_02", +}; + +/* State structure for meshlink_get_node Test Case #3 */ +static black_box_state_t test_mesh_get_node_01_state = { + .test_case_name = "test_mesh_get_node_01_state", +}; + +/* State structure for meshlink_get_node Test Case #4 */ +static black_box_state_t test_mesh_get_node_02_state = { + .test_case_name = "test_mesh_get_node_02_state", +}; + +static void log_message(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) { + (void)mesh; + + static const char *levelstr[] = { + [MESHLINK_DEBUG] = "\x1b[34mDEBUG", + [MESHLINK_INFO] = "\x1b[32mINFO", + [MESHLINK_WARNING] = "\x1b[33mWARNING", + [MESHLINK_ERROR] = "\x1b[31mERROR", + [MESHLINK_CRITICAL] = "\x1b[31mCRITICAL", + }; + + fprintf(stderr, "%s(%s):\x1b[0m %s\n", mesh->name, levelstr[level], text); +} + +/* Execute meshlink_get_node Test Case # 1 */ +static void test_case_mesh_get_node_by_dev_class_01(void **state) { + execute_test(test_steps_mesh_get_node_by_dev_class_01, state); +} + +/* Test Steps for meshlink_get_node Test Case # 1 + + Test Steps: + 1. Open nut, peer1, relay1, relay2, relay3 node instances, export and + import the configuration of NUT with other nodes. + 2. Run the node instances. + 3. Call meshlink_get_all_nodes_by_dev_class API with NULL as nodes array parameter + for DEV_CLASS_STATIONARY + 4. Call meshlink_get_all_nodes_by_dev_class API with previously allocated nodes array + parameter for DEV_CLASS_BACKBONE + 5. Call meshlink_get_all_nodes_by_dev_class API with previously allocated nodes array + parameter for DEV_CLASS_PORTABLE + + Expected Result: + meshlink_get_all_nodes_by_dev_class API should return appropriate node array pointer and + node member parameter when called and return accordingly. +*/ +static bool test_steps_mesh_get_node_by_dev_class_01(void) { + meshlink_node_t **nodes; + size_t nnodes = 0, i; + + /* Create meshlink instance for NUT */ + meshlink_handle_t *mesh_nut = meshlink_open("getnodeconf.1", "nut", "node_sim", DEV_CLASS_STATIONARY); + assert(mesh_nut); + meshlink_set_log_cb(mesh_nut, TEST_MESHLINK_LOG_LEVEL, log_message); + + /* Create meshlink instance for peer1 */ + meshlink_handle_t *mesh_peer1 = meshlink_open("getnodeconf.2", "peer1", "node_sim", DEV_CLASS_STATIONARY); + assert(mesh_peer1); + meshlink_set_log_cb(mesh_peer1, TEST_MESHLINK_LOG_LEVEL, log_message); + + /* Create meshlink instance for relay1 */ + meshlink_handle_t *mesh_relay1 = meshlink_open("getnodeconf.3", "relay1", "node_sim", DEV_CLASS_BACKBONE); + assert(mesh_relay1); + meshlink_set_log_cb(mesh_relay1, TEST_MESHLINK_LOG_LEVEL, log_message); + + /* Create meshlink instance for relay2 */ + meshlink_handle_t *mesh_relay2 = meshlink_open("getnodeconf.4", "relay2", "node_sim", DEV_CLASS_BACKBONE); + assert(mesh_relay2); + meshlink_set_log_cb(mesh_relay2, TEST_MESHLINK_LOG_LEVEL, log_message); + + /* Create meshlink instance for relay3 */ + meshlink_handle_t *mesh_relay3 = meshlink_open("getnodeconf.5", "relay3", "node_sim", DEV_CLASS_BACKBONE); + assert(mesh_relay3); + meshlink_set_log_cb(mesh_relay3, TEST_MESHLINK_LOG_LEVEL, log_message); + + /* importing and exporting mesh meta data */ + char *exp_nut = meshlink_export(mesh_nut); + assert(exp_nut != NULL); + char *export = meshlink_export(mesh_peer1); + assert(export != NULL); + assert(meshlink_import(mesh_nut, export)); + assert(meshlink_import(mesh_peer1, exp_nut)); + free(export); + + export = meshlink_export(mesh_relay1); + assert(export != NULL); + assert(meshlink_import(mesh_nut, export)); + assert(meshlink_import(mesh_relay1, exp_nut)); + free(export); + + export = meshlink_export(mesh_relay2); + assert(export != NULL); + assert(meshlink_import(mesh_nut, export)); + assert(meshlink_import(mesh_relay2, exp_nut)); + free(export); + + export = meshlink_export(mesh_relay3); + assert(export != NULL); + assert(meshlink_import(mesh_nut, export)); + assert(meshlink_import(mesh_relay3, exp_nut)); + free(export); + free(exp_nut); + + nodes = meshlink_get_all_nodes_by_dev_class(mesh_nut, DEV_CLASS_STATIONARY, NULL, &nnodes); + assert_int_not_equal(nodes, NULL); + assert_int_equal(nnodes, 2); + + for(i = 0; i < nnodes; i++) { + if(strcasecmp(nodes[i]->name, "nut") && strcasecmp(nodes[i]->name, "peer1")) { + fail(); + } + } + + nodes = meshlink_get_all_nodes_by_dev_class(mesh_nut, DEV_CLASS_BACKBONE, nodes, &nnodes); + assert_int_not_equal(nodes, NULL); + assert_int_equal(nnodes, 3); + + for(i = 0; i < nnodes; i++) { + if(strcasecmp(nodes[i]->name, "relay1") && strcasecmp(nodes[i]->name, "relay2") && strcasecmp(nodes[i]->name, "relay3")) { + fail(); + } + } + + nodes = meshlink_get_all_nodes_by_dev_class(mesh_nut, DEV_CLASS_PORTABLE, nodes, &nnodes); + assert_int_equal(nodes, NULL); + assert_int_equal(nnodes, 0); + assert_int_equal(meshlink_errno, 0); + + free(nodes); + meshlink_close(mesh_nut); + meshlink_close(mesh_peer1); + meshlink_close(mesh_relay1); + meshlink_close(mesh_relay2); + meshlink_close(mesh_relay3); + + return true; +} + +/* Execute meshlink_get_node Test Case # 2 - Invalid case + Passing invalid parameters as input arguments */ +static void test_case_mesh_get_node_by_dev_class_02(void **state) { + execute_test(test_steps_mesh_get_node_by_dev_class_02, state); +} + +/* Test Steps for meshlink_get_node Test Case # 2 + + Test Steps: + 1. Create NUT + 2. Call meshlink_get_all_nodes_by_dev_class API with invalid parameters + + Expected Result: + meshlink_get_all_nodes_by_dev_class API should return NULL and set appropriate + meshlink_errno. +*/ +static bool test_steps_mesh_get_node_by_dev_class_02(void) { + meshlink_node_t **nodes; + size_t nnodes = 0; + + assert(meshlink_destroy("getnodeconf.1")); + + /* Create meshlink instance for NUT */ + meshlink_handle_t *mesh_nut = meshlink_open("getnodeconf.1", "nut", "node_sim", DEV_CLASS_STATIONARY); + assert(mesh_nut); + meshlink_set_log_cb(mesh_nut, TEST_MESHLINK_LOG_LEVEL, log_message); + + nodes = meshlink_get_all_nodes_by_dev_class(mesh_nut, DEV_CLASS_COUNT + 10, NULL, &nnodes); + assert_int_equal(nodes, NULL); + assert_int_not_equal(meshlink_errno, 0); + + nodes = meshlink_get_all_nodes_by_dev_class(mesh_nut, DEV_CLASS_STATIONARY, NULL, NULL); + assert_int_equal(nodes, NULL); + assert_int_not_equal(meshlink_errno, 0); + + nodes = meshlink_get_all_nodes_by_dev_class(NULL, DEV_CLASS_STATIONARY, NULL, &nnodes); + assert_int_equal(nodes, NULL); + assert_int_not_equal(meshlink_errno, 0); + + meshlink_close(mesh_nut); + assert(meshlink_destroy("getnodeconf.1")); + return true; +} + +/* Execute meshlink_get_node_dev_class Test Case # 1 */ +static void test_case_mesh_get_node_dev_class_01(void **state) { + execute_test(test_steps_mesh_get_node_dev_class_01, state); +} + +/* Test Steps for meshlink_get_node_dev_class Test Case # 1 + + Test Steps: + 1. Create NUT node with DEV_CLASS_STATIONARY device class and obtain node handle + 2. Call meshlink_get_node_dev_class API + + Expected Result: + meshlink_get_node_dev_class API should return DEV_CLASS_STATIONARY device class +*/ +static bool test_steps_mesh_get_node_dev_class_01(void) { + assert(meshlink_destroy("getnodeconf.1")); + + /* Create meshlink instance for NUT */ + meshlink_handle_t *mesh_nut = meshlink_open("getnodeconf.1", "nut", "node_sim", DEV_CLASS_STATIONARY); + assert(mesh_nut); + meshlink_set_log_cb(mesh_nut, TEST_MESHLINK_LOG_LEVEL, log_message); + + meshlink_node_t *node; + node = meshlink_get_self(mesh_nut); + assert(node); + + dev_class_t dev_class = meshlink_get_node_dev_class(mesh_nut, node); + assert_int_equal(dev_class, DEV_CLASS_STATIONARY); + + meshlink_close(mesh_nut); + assert(meshlink_destroy("getnodeconf.1")); + return true; +} + +/* Execute meshlink_get_node_dev_class Test Case # 2 */ +static void test_case_mesh_get_node_dev_class_02(void **state) { + execute_test(test_steps_mesh_get_node_dev_class_02, state); +} + +/* Test Steps for meshlink_get_node_dev_class Test Case # 2 + + Test Steps: + 1. Create NUT and obtain NUT node handle + 2. Call meshlink_get_node_dev_class API with invalid parameters + + Expected Result: + meshlink_get_node_dev_class API should return NULL and set appropriate + meshlink_errno. +*/ +static bool test_steps_mesh_get_node_dev_class_02(void) { + assert(meshlink_destroy("getnodeconf.1")); + + /* Create meshlink instance for NUT */ + meshlink_handle_t *mesh_nut = meshlink_open("getnodeconf.1", "nut", "node_sim", DEV_CLASS_STATIONARY); + assert(mesh_nut); + meshlink_set_log_cb(mesh_nut, TEST_MESHLINK_LOG_LEVEL, log_message); + + meshlink_node_t *node; + node = meshlink_get_self(mesh_nut); + assert(node); + + int dev_class = meshlink_get_node_dev_class(NULL, node); + assert_int_equal(dev_class, -1); + assert_int_not_equal(meshlink_errno, 0); + + dev_class = meshlink_get_node_dev_class(mesh_nut, NULL); + assert_int_equal(dev_class, -1); + assert_int_not_equal(meshlink_errno, 0); + + meshlink_close(mesh_nut); + assert(meshlink_destroy("getnodeconf.1")); + return true; +} + +static int black_box_setup_test_case(void **state) { + (void)state; + + fprintf(stderr, "Destroying confbases\n"); + assert(meshlink_destroy("getnodeconf.1")); + assert(meshlink_destroy("getnodeconf.2")); + assert(meshlink_destroy("getnodeconf.3")); + assert(meshlink_destroy("getnodeconf.4")); + assert(meshlink_destroy("getnodeconf.5")); + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_errno = MESHLINK_OK; + + return 0; +} + +int test_meshlink_get_all_node_by_device_class(void) { + const struct CMUnitTest blackbox_get_node_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_get_node_by_dev_class_01, black_box_setup_test_case, black_box_setup_test_case, + (void *)&test_mesh_get_node_by_dev_class_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_get_node_by_dev_class_02, NULL, NULL, + (void *)&test_mesh_get_node_by_dev_class_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_get_node_dev_class_01, NULL, NULL, + (void *)&test_mesh_get_node_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_get_node_dev_class_02, NULL, NULL, + (void *)&test_mesh_get_node_02_state), + }; + + total_tests += sizeof(blackbox_get_node_tests) / sizeof(blackbox_get_node_tests[0]); + + return cmocka_run_group_tests(blackbox_get_node_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_get_all_nodes_by_dev_class.h b/test/blackbox/run_blackbox_tests/test_cases_get_all_nodes_by_dev_class.h new file mode 100644 index 0000000..127a87d --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_get_all_nodes_by_dev_class.h @@ -0,0 +1,29 @@ +#ifndef TEST_CASES_GET_ALL_NODES_BY_DEV_CLASS_H +#define TEST_CASES_GET_ALL_NODES_BY_DEV_CLASS_H + + +/* + test_cases_get_all_nodes_by_dev_class.c.h -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_get_all_node_by_device_class(void); +extern int total_tests; + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_get_ex_addr.c b/test/blackbox/run_blackbox_tests/test_cases_get_ex_addr.c new file mode 100644 index 0000000..02cb4d6 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_get_ex_addr.c @@ -0,0 +1,147 @@ +/* + test_cases_get_ex_addr.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_get_ex_addr.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include + +static void test_case_mesh_get_address_01(void **state); +static bool test_steps_mesh_get_address_01(void); +static void test_case_mesh_get_address_02(void **state); +static bool test_steps_mesh_get_address_02(void); +static void test_case_mesh_get_address_03(void **state); +static bool test_steps_mesh_get_address_03(void); + +/* State structure for meshlink_get_external_address Test Case #1 */ +static black_box_state_t test_mesh_get_address_01_state = { + .test_case_name = "test_case_mesh_get_address_01", +}; + +/* State structure for meshlink_get_external_address Test Case #2 */ +static black_box_state_t test_mesh_get_address_02_state = { + .test_case_name = "test_case_mesh_get_address_02", +}; + +/* State structure for meshlink_get_external_address Test Case #3 */ +static black_box_state_t test_mesh_get_address_03_state = { + .test_case_name = "test_case_mesh_get_address_03", +}; + +/* Execute meshlink_get_external_address Test Case # 1 */ +static void test_case_mesh_get_address_01(void **state) { + execute_test(test_steps_mesh_get_address_01, state); +} + +/* Test Steps for meshlink_get_external_address Test Case # 1 + + Test Steps: + 1. Create an instance of the node & start it + 2. Get node's external address using meshlink_get_external_address + + Expected Result: + API returns the external address successfully. +*/ +static bool test_steps_mesh_get_address_01(void) { + meshlink_handle_t *mesh = meshlink_open("getex_conf", "foo", "test", DEV_CLASS_STATIONARY); + assert(mesh != NULL); + assert(meshlink_start(mesh)); + + char *addr = meshlink_get_external_address(mesh); + assert_int_not_equal(addr, NULL); + + free(addr); + meshlink_close(mesh); + assert(meshlink_destroy("getex_conf")); + return true; +} + +/* Execute meshlink_get_external_address Test Case # 2 */ +static void test_case_mesh_get_address_02(void **state) { + execute_test(test_steps_mesh_get_address_02, state); +} + +/* Test Steps for meshlink_get_external_address Test Case # 2 + + Test Steps: + 1. Obtain external address by passing NULL as mesh handle + to meshlink_get_external_address API + + Expected Result: + Return NULL by reporting error successfully. +*/ +static bool test_steps_mesh_get_address_02(void) { + char *ext = meshlink_get_external_address(NULL); + assert_int_equal(ext, NULL); + + return true; +} + +/* Execute meshlink_get_external_address Test Case # 3 */ +static void test_case_mesh_get_address_03(void **state) { + execute_test(test_steps_mesh_get_address_03, state); +} + +/* Test Steps for meshlink_get_external_address Test Case # 3 - Functionality test + + Test Steps: + 1. Create an instance of the node + 2. Get node's external address using meshlink_get_external_address + + Expected Result: + API returns the external address successfully even if the mesh is started. +*/ +static bool test_steps_mesh_get_address_03(void) { + meshlink_handle_t *mesh = meshlink_open("getex_conf", "foo", "test", DEV_CLASS_STATIONARY); + assert(mesh != NULL); + assert(meshlink_start(mesh)); + + char *addr = meshlink_get_external_address(mesh); + assert_int_not_equal(addr, NULL); + + free(addr); + meshlink_close(mesh); + assert(meshlink_destroy("getex_conf")); + return true; +} + +int test_meshlink_get_external_address(void) { + const struct CMUnitTest blackbox_get_ex_addr_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_get_address_01, NULL, NULL, + (void *)&test_mesh_get_address_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_get_address_02, NULL, NULL, + (void *)&test_mesh_get_address_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_get_address_03, NULL, NULL, + (void *)&test_mesh_get_address_03_state) + }; + total_tests += sizeof(blackbox_get_ex_addr_tests) / sizeof(blackbox_get_ex_addr_tests[0]); + + return cmocka_run_group_tests(blackbox_get_ex_addr_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_get_ex_addr.h b/test/blackbox/run_blackbox_tests/test_cases_get_ex_addr.h new file mode 100644 index 0000000..ccd4bb9 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_get_ex_addr.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_GET_EX_ADDR_H +#define TEST_CASES_GET_EX_ADDR_H + +/* + test_cases_get_ex_addr.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_get_external_address(void); +extern int total_tests; + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_get_fingerprint.c b/test/blackbox/run_blackbox_tests/test_cases_get_fingerprint.c new file mode 100644 index 0000000..3435f6e --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_get_fingerprint.c @@ -0,0 +1,180 @@ +/* + test_cases_get_fingerprint.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "test_cases_get_fingerprint.h" +#include +#include +#include +#include +#include +#include + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +static void test_case_get_fingerprint_cb_01(void **state); +static bool test_get_fingerprint_cb_01(void); +static void test_case_get_fingerprint_cb_02(void **state); +static bool test_get_fingerprint_cb_02(void); +static void test_case_get_fingerprint_cb_03(void **state); +static bool test_get_fingerprint_cb_03(void); + +/* State structure for get_fingerprint Test Case #1 */ +static black_box_state_t test_case_get_fingerprint_cb_01_state = { + .test_case_name = "test_case_get_fingerprint_cb_01", +}; +/* State structure for get_fingerprint Test Case #2 */ +static black_box_state_t test_case_get_fingerprint_cb_02_state = { + .test_case_name = "test_case_get_fingerprint_cb_02", +}; +/* State structure for get_fingerprint Test Case #3 */ +static black_box_state_t test_case_get_fingerprint_cb_03_state = { + .test_case_name = "test_case_get_fingerprint_cb_03", +}; + +/* Execute get_fingerprint Test Case # 1 - Valid Case of obtaing publickey of NUT */ +static void test_case_get_fingerprint_cb_01(void **state) { + execute_test(test_get_fingerprint_cb_01, state); +} +/* Test Steps for get_fingerprint Test Case # 1 - Valid case + + Test Steps: + 1. Run NUT(Node Under Test) + 2. Get node handle for ourself(for NUT) and obtain fingerprint + + Expected Result: + Obtain fingerprint of NUT successfully. +*/ +static bool test_get_fingerprint_cb_01(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + meshlink_handle_t *mesh_handle = meshlink_open("getfingerprintconf", "nut", "test", 1); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + meshlink_node_t *node = meshlink_get_self(mesh_handle); + assert(node != NULL); + + char *fp = meshlink_get_fingerprint(mesh_handle, node); + assert_int_not_equal(fp, NULL); + + meshlink_close(mesh_handle); + assert(meshlink_destroy("getfingerprintconf")); + + return true; +} + +/* Execute get_fingerprint Test Case # 2 - Invalid Case - trying t0 obtain publickey of a node in a + mesh by passing NULL as mesh handle argument*/ +static void test_case_get_fingerprint_cb_02(void **state) { + execute_test(test_get_fingerprint_cb_02, state); +} + +/* Test Steps for get_fingerprint Test Case # 2 - Invalid case + + Test Steps: + 1. Run NUT(Node Under Test) + 2. Get node handle for ourself(for NUT) + 3. Obtain fingerprint by passing NULL as mesh handle + + Expected Result: + Return NULL by reporting error successfully. +*/ +static bool test_get_fingerprint_cb_02(void) { + /* Set up logging for Meshlink */ + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + PRINT_TEST_CASE_MSG("Opening NUT\n"); + meshlink_handle_t *mesh_handle = meshlink_open("getfingerprintconf", "nut", "test", 1); + assert(mesh_handle); + + /* Set up logging for Meshlink with the newly acquired Mesh Handle */ + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + /* Getting node handle for itself */ + meshlink_node_t *node = meshlink_get_self(mesh_handle); + assert(node != NULL); + + /* passing NULL as mesh handle for meshlink_get_fingerprint API */ + char *fp = meshlink_get_fingerprint(NULL, node); + assert_int_equal(fp, NULL); + + meshlink_close(mesh_handle); + assert(meshlink_destroy("getfingerprintconf")); + + return true; +} + +/* Execute get_fingerprint Test Case # 3 - Invalid Case - trying t0 obtain publickey of a node in a + mesh by passing NULL as node handle argument */ +static void test_case_get_fingerprint_cb_03(void **state) { + execute_test(test_get_fingerprint_cb_03, state); +} +/* Test Steps for get_fingerprint Test Case # 3 - Invalid case + + Test Steps: + 1. Run NUT(Node Under Test) + 2. Get node handle for ourself(for NUT) + 3. Obtain fingerprint by passing NULL as node handle + + Expected Result: + Return NULL by reporting error successfully. +*/ +static bool test_get_fingerprint_cb_03(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + meshlink_handle_t *mesh_handle = meshlink_open("getfingerprintconf", "nut", "test", 1); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + char *fp = meshlink_get_fingerprint(mesh_handle, NULL); + assert_int_equal(fp, NULL); + + meshlink_close(mesh_handle); + assert(meshlink_destroy("getfingerprintconf")); + + return true; +} + +int test_meshlink_get_fingerprint(void) { + const struct CMUnitTest blackbox_get_fingerprint_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_get_fingerprint_cb_01, NULL, NULL, + (void *)&test_case_get_fingerprint_cb_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_get_fingerprint_cb_02, NULL, NULL, + (void *)&test_case_get_fingerprint_cb_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_get_fingerprint_cb_03, NULL, NULL, + (void *)&test_case_get_fingerprint_cb_03_state) + }; + + total_tests += sizeof(blackbox_get_fingerprint_tests) / sizeof(blackbox_get_fingerprint_tests[0]); + + return cmocka_run_group_tests(blackbox_get_fingerprint_tests, NULL, NULL); + +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_get_fingerprint.h b/test/blackbox/run_blackbox_tests/test_cases_get_fingerprint.h new file mode 100644 index 0000000..dafc4bf --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_get_fingerprint.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_GET_FINGERPRINT_H +#define TEST_CASES_GET_FINGERPRINT_H + +/* + test_cases_get_fingerprint.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_get_fingerprint(void); +extern int total_tests; + +#endif // TEST_CASES_GET_FINGERPRINT_H diff --git a/test/blackbox/run_blackbox_tests/test_cases_get_node.c b/test/blackbox/run_blackbox_tests/test_cases_get_node.c new file mode 100644 index 0000000..a13b475 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_get_node.c @@ -0,0 +1,209 @@ +/* + test_cases_get_node.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_get_node.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +static void test_case_mesh_get_node_01(void **state); +static bool test_steps_mesh_get_node_01(void); +static void test_case_mesh_get_node_02(void **state); +static bool test_steps_mesh_get_node_02(void); +static void test_case_mesh_get_node_03(void **state); +static bool test_steps_mesh_get_node_03(void); +static void test_case_mesh_get_node_04(void **state); +static bool test_steps_mesh_get_node_04(void); + +/* State structure for meshlink_get_node Test Case #1 */ +static black_box_state_t test_mesh_get_node_01_state = { + .test_case_name = "test_case_mesh_get_node_01", +}; + +/* State structure for meshlink_get_node Test Case #2 */ +static black_box_state_t test_mesh_get_node_02_state = { + .test_case_name = "test_case_mesh_get_node_02", +}; + +/* State structure for meshlink_get_node Test Case #3 */ +static black_box_state_t test_mesh_get_node_03_state = { + .test_case_name = "test_case_mesh_get_node_03", +}; + +/* State structure for meshlink_get_node Test Case #4 */ +static black_box_state_t test_mesh_get_node_04_state = { + .test_case_name = "test_case_mesh_get_node_04", +}; + +/* Execute meshlink_get_node Test Case # 1 */ +static void test_case_mesh_get_node_01(void **state) { + execute_test(test_steps_mesh_get_node_01, state); +} + +/* Test Steps for meshlink_get_node Test Case # 1 + + Test Steps: + 1. Open nodes instance + 2. Get node's handle + + Expected Result: + node handle of it's own is obtained +*/ +static bool test_steps_mesh_get_node_01(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + assert(meshlink_destroy("getnode1")); + assert(meshlink_destroy("getnode2")); + + // Opening NUT and bar nodes + meshlink_handle_t *mesh1 = meshlink_open("getnode1", "nut", "test", DEV_CLASS_STATIONARY); + assert(mesh1 != NULL); + meshlink_set_log_cb(mesh1, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_handle_t *mesh2 = meshlink_open("getnode2", "bar", "test", DEV_CLASS_STATIONARY); + assert(mesh2 != NULL); + meshlink_set_log_cb(mesh2, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + // Exporting and Importing mutually + char *exp1 = meshlink_export(mesh1); + assert(exp1 != NULL); + char *exp2 = meshlink_export(mesh2); + assert(exp2 != NULL); + bool imp1 = meshlink_import(mesh1, exp2); + assert(imp1); + bool imp2 = meshlink_import(mesh2, exp1); + assert(imp2); + + // Get node handles + meshlink_node_t *get_node = meshlink_get_node(mesh1, "bar"); + assert_int_not_equal(get_node, NULL); + get_node = meshlink_get_node(mesh1, "nut"); + assert_int_not_equal(get_node, NULL); + + // Cleanup + meshlink_close(mesh1); + meshlink_close(mesh2); + assert(meshlink_destroy("getnode1")); + assert(meshlink_destroy("getnode2")); + return true; +} + +/* Execute meshlink_get_node Test Case # 2 */ +static void test_case_mesh_get_node_02(void **state) { + execute_test(test_steps_mesh_get_node_02, state); +} + +/* Test Steps for meshlink_get_node Test Case # 2 + + Test Steps: + 1. Get node handles by passing NULL as mesh handle argument + + Expected Result: + Reports error successfully by returning NULL +*/ +static bool test_steps_mesh_get_node_02(void) { + meshlink_node_t *get_node = meshlink_get_node(NULL, "foo"); + assert_int_equal(get_node, NULL); + + return true; +} + +/* Execute meshlink_get_node Test Case # 3 */ +static void test_case_mesh_get_node_03(void **state) { + execute_test(test_steps_mesh_get_node_03, state); +} + +/* Test Steps for meshlink_get_node Test Case # 3 + + Test Steps: + 1. Get node handles by passing NULL as node name argument + + Expected Result: + Reports error successfully by returning NULL +*/ +static bool test_steps_mesh_get_node_03(void) { + meshlink_handle_t *mesh = meshlink_open("node_conf.3", "foo", "test", DEV_CLASS_STATIONARY); + assert(mesh); + assert(meshlink_start(mesh)); + + meshlink_node_t *get_node = meshlink_get_node(mesh, NULL); + assert_int_equal(get_node, NULL); + + meshlink_close(mesh); + assert(meshlink_destroy("node_conf.3")); + return true; +} + +/* Execute meshlink_get_node Test Case # 4 */ +static void test_case_mesh_get_node_04(void **state) { + execute_test(test_steps_mesh_get_node_04, state); +} + +/* Test Steps for meshlink_get_node Test Case # 4 + + Test Steps: + 1. Open node instance + 2. Get node handle with the name of the node + that's not in the mesh + + Expected Result: + Reports error successfully by returning NULL +*/ +static bool test_steps_mesh_get_node_04(void) { + meshlink_handle_t *mesh = meshlink_open("node_conf", "foo", "test", DEV_CLASS_STATIONARY); + assert(mesh); + assert(meshlink_start(mesh)); + + const char *nonexisting_node = "bar"; + meshlink_node_t *get_node = meshlink_get_node(mesh, nonexisting_node); + assert_int_equal(get_node, NULL); + + meshlink_close(mesh); + assert(meshlink_destroy("node_conf")); + return true; +} + +int test_meshlink_get_node(void) { + const struct CMUnitTest blackbox_get_node_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_get_node_01, NULL, NULL, + (void *)&test_mesh_get_node_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_get_node_02, NULL, NULL, + (void *)&test_mesh_get_node_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_get_node_03, NULL, NULL, + (void *)&test_mesh_get_node_03_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_get_node_04, NULL, NULL, + (void *)&test_mesh_get_node_04_state) + }; + + total_tests += sizeof(blackbox_get_node_tests) / sizeof(blackbox_get_node_tests[0]); + + return cmocka_run_group_tests(blackbox_get_node_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_get_node.h b/test/blackbox/run_blackbox_tests/test_cases_get_node.h new file mode 100644 index 0000000..7d5eb41 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_get_node.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_GET_NODE_H +#define TEST_CASES_GET_NODE_H + +/* + test_cases_get_node.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_get_node(void); +extern int total_tests; + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_get_node_reachability.c b/test/blackbox/run_blackbox_tests/test_cases_get_node_reachability.c new file mode 100644 index 0000000..d5ae46f --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_get_node_reachability.c @@ -0,0 +1,960 @@ +/* + test_cases_get_node_reachability.c -- Execution of specific meshlink black box test cases + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "execute_tests.h" +#include "test_cases_get_node_reachability.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "../../utils.h" + +#define NUT "nut" +#define PEER "peer" +#define PEER2 "peer2" +#define GET_NODE_REACHABILITY "test_get_node_reachability" +#define create_path(confbase, node_name, test_case_no) assert(snprintf(confbase, sizeof(confbase), GET_NODE_REACHABILITY "_%ld_%s_%02d", (long) getpid(), node_name, test_case_no) > 0) + +static struct sync_flag peer_reachable_status_cond = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static bool peer_reachable_status; +static struct sync_flag nut_reachable_status_cond = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static bool nut_reachable_status; +static struct sync_flag nut_started_status_cond = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static bool peer_node_callback_test_status; + +static void test_case_get_node_reachability_01(void **state); +static bool test_get_node_reachability_01(void); +static void test_case_get_node_reachability_02(void **state); +static bool test_get_node_reachability_02(void); +static void test_case_get_node_reachability_03(void **state); +static bool test_get_node_reachability_03(void); +static void test_case_get_node_reachability_04(void **state); +static bool test_get_node_reachability_04(void); +static void test_case_get_node_reachability_05(void **state); +static bool test_get_node_reachability_05(void); +static void test_case_get_node_reachability_06(void **state); +static bool test_get_node_reachability_06(void); +static void test_case_get_node_reachability_07(void **state); +static bool test_get_node_reachability_07(void); + +/* Node reachable status callback which signals the respective conditional varibale */ +static void meshlink_node_reachable_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable_status) { + if(meshlink_get_self(mesh) == node) { + return; + } + + if(!strcasecmp(mesh->name, NUT)) { + if(!strcasecmp(node->name, PEER)) { + peer_reachable_status = reachable_status; + set_sync_flag(&peer_reachable_status_cond, true); + } + } else if(!strcasecmp(mesh->name, PEER)) { + if(!strcasecmp(node->name, NUT)) { + nut_reachable_status = reachable_status; + set_sync_flag(&nut_reachable_status_cond, true); + } + } + + // Reset the node reachability status callback, as the two nodes making a simultaneous connection to each other, and then one connection will win and cause the other one to be disconnected. + meshlink_set_node_status_cb(mesh, NULL); +} + +static void meshlink_node_reachable_status_cb_2(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable_status) { + meshlink_node_t *peer_handle; + char *peer_name = NULL; + time_t last_unreachable, last_reachable; + static int count = 2; + + if(meshlink_get_self(mesh) == node) { + return; + } + + /* Of the 2 node reachable callbacks, the latest callback calls meshlink_get_node_reachability API + for the 1st node joined */ + if(count && reachable_status && !strcasecmp(mesh->name, NUT)) { + --count; + + if(!count) { + if(!strcasecmp(node->name, PEER)) { + peer_name = PEER2; + } else if(!strcasecmp(node->name, PEER2)) { + peer_name = PEER; + } + + peer_handle = meshlink_get_node(mesh, peer_name); + assert_non_null(peer_handle); + + bool status = meshlink_get_node_reachability(mesh, peer_handle, &last_reachable, &last_unreachable); + + peer_node_callback_test_status = status && last_reachable && !last_unreachable; + set_sync_flag(&peer_reachable_status_cond, true); + } + } +} + +/* SIGUSR2 signal handler that signals the NUT started and PEER node can join */ +void nut_started_user_signal_handler(int signum) { + if(signum == SIGUSR2) { + set_sync_flag(&nut_started_status_cond, true); + } + +} + +/* + Execute meshlink get last node reachability times feature Test Case # 1 - + Sanity API test +*/ +static void test_case_get_node_reachability_01(void **state) { + execute_test(test_get_node_reachability_01, state); +} + +/* Test Steps for meshlink_get_node_reachability Test Case # 1 + + Test steps and scenarios: + 1. Open Node-Under-Test (NUT) instance, Call meshlink_get_node_reachability API + with valid mesh handle, self node handle, last_reachable pointer and + last_unreachable pointer. + Expected Result: + API returns self node unreachable, last_reachable and last_unreachable values + as 0 seconds + + 2. Call meshlink_get_node_reachability API with valid mesh handle, self node handle. + But pass NULL pointers for last_reachable and last_unreachable arguments + Expected Result: + API returns self node unreachable + + 3. Call meshlink_get_node_reachability API with NULL as mesh handle, + valid self node handle, last_reachable pointer and last_unreachable pointer. + Expected Result: + API fails and sets MESHLINK_EINVAL as meshlink errno value + + 4. Call meshlink_get_node_reachability API with NULL as mesh handle, + valid self node handle, NULL pointers for last_reachable and last_unreachable + arguments + Expected Result: + API fails and sets MESHLINK_EINVAL as meshlink errno value + + 5. Call meshlink_get_node_reachability API with valid mesh handle, + NULL as self node handle, last_reachable pointer and last_unreachable pointer. + Expected Result: + API fails and sets MESHLINK_EINVAL as meshlink errno value + + 6. Call meshlink_get_node_reachability API with valid mesh handle, + NULL as self node handle, NULL pointers for last_reachable and last_unreachable + arguments + Expected Result: + API fails and sets MESHLINK_EINVAL as meshlink errno value + +*/ +static bool test_get_node_reachability_01(void) { + bool status; + time_t last_unreachable, last_reachable; + char nut_confbase[PATH_MAX]; + create_path(nut_confbase, NUT, 1); + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open Node-Under-Test node instance + + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, GET_NODE_REACHABILITY, DEV_CLASS_STATIONARY); + assert_int_not_equal(mesh, NULL); + + // Call meshlink_get_node_reachability API with all valid arguments + + status = meshlink_get_node_reachability(mesh, meshlink_get_self(mesh), &last_reachable, &last_unreachable); + assert_int_equal(status, false); + assert_int_equal(last_reachable, 0); + assert_int_equal(last_unreachable, 0); + + // Call meshlink_get_node_reachability API with all valid arguments + + status = meshlink_get_node_reachability(mesh, meshlink_get_self(mesh), NULL, NULL); + assert_int_equal(status, false); + + // Call meshlink_get_node_reachability API with invalid parameters + + meshlink_errno = MESHLINK_OK; + meshlink_get_node_reachability(NULL, meshlink_get_self(mesh), NULL, NULL); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + meshlink_errno = MESHLINK_OK; + meshlink_get_node_reachability(NULL, meshlink_get_self(mesh), &last_reachable, &last_unreachable); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + meshlink_errno = MESHLINK_OK; + meshlink_get_node_reachability(mesh, NULL, NULL, NULL); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + meshlink_errno = MESHLINK_OK; + meshlink_get_node_reachability(mesh, NULL, &last_reachable, &last_unreachable); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + // Cleanup + + meshlink_close(mesh); + assert_true(meshlink_destroy(nut_confbase)); + return true; +} + +/* + Execute meshlink get last node reachability times feature Test Case # 2 - + API testing with stand-alone node +*/ +static void test_case_get_node_reachability_02(void **state) { + execute_test(test_get_node_reachability_02, state); +} + +/* Test Steps for meshlink_get_node_reachability Test Case # 2 + + Test steps and scenarios: + 1. Open and start Node-Under-Test (NUT) instance, Call meshlink_get_node_reachability API. + Expected Result: + API returns self node reachable status, last_reachable as some positive non-zero integer + and last_unreachable value as 0 seconds + + 2. Stop the NUT instance, Call meshlink_get_node_reachability API. + Expected Result: + API returns self node unreachable, both last_reachable and last_unreachable values + as some positive non-zero time in seconds + + 3. Close and reopen NUT instance, Call meshlink_get_node_reachability API. + Expected Result: + API returns self node unreachable, both last_reachable and last_unreachable values + as some positive non-zero time in seconds + +*/ +static bool test_get_node_reachability_02(void) { + bool status; + time_t last_unreachable, last_reachable, last_peer_unreachable, last_peer_reachable; + char nut_confbase[PATH_MAX]; + create_path(nut_confbase, NUT, 2); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open and start Node-Under-Test node instance + + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, GET_NODE_REACHABILITY, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + assert_true(meshlink_start(mesh)); + + // Call meshlink_get_node_reachability API with all valid arguments + + status = meshlink_get_node_reachability(mesh, meshlink_get_self(mesh), &last_reachable, &last_unreachable); + assert_true(status); + assert_int_not_equal(last_reachable, 0); + assert_int_equal(last_unreachable, 0); + last_peer_reachable = last_reachable; + + // Stop NUT node instance + + meshlink_stop(mesh); + + // Call meshlink_get_node_reachability API with all valid arguments + + status = meshlink_get_node_reachability(mesh, meshlink_get_self(mesh), &last_reachable, &last_unreachable); + assert_false(status); + assert_int_not_equal(last_unreachable, 0); + assert_int_equal(last_reachable, last_peer_reachable); + last_peer_unreachable = last_unreachable; + + // Reinitialize NUT node instance + + meshlink_close(mesh); + mesh = meshlink_open(nut_confbase, NUT, GET_NODE_REACHABILITY, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + + // Call meshlink_get_node_reachability API with all valid arguments + + status = meshlink_get_node_reachability(mesh, meshlink_get_self(mesh), &last_reachable, &last_unreachable); + assert_false(status); + assert_int_equal(last_reachable, last_peer_reachable); + assert_int_equal(last_unreachable, last_peer_unreachable); + + // Cleanup + + meshlink_close(mesh); + assert_true(meshlink_destroy(nut_confbase)); + return true; +} + +/* + Execute meshlink get last node reachability times feature Test Case # 3 - + API testing with host node which already joined with a peer node which later + goes offline, test host node with an offline peer node case. +*/ +static void test_case_get_node_reachability_03(void **state) { + execute_test(test_get_node_reachability_03, state); +} + +/* Test Steps for meshlink_get_node_reachability Test Case # 3 + + Test steps and scenarios: + 1. Open Node-Under-Test (NUT) and peer node instance, start peer node instance + and invite NUT. NUT joins peer and destroy peer node instance. + Call meshlink_get_node_reachability API. + Expected Result: + API returns peer node unreachable status, last_reachable and last_unreachable + value as 0 seconds. + + 2. Start the NUT instance, Call meshlink_get_node_reachability API. + Expected Result: + API returns peer node unreachable status, last_reachable and last_unreachable + value as 0 seconds. + + 3. Stop the NUT instance, Call meshlink_get_node_reachability API. + Expected Result: + API returns peer node unreachable status, last_reachable and last_unreachable + value as 0 seconds. + + 4. Close and reopen NUT instance, Call meshlink_get_node_reachability API. + Expected Result: + API returns peer node unreachable status, last_reachable and last_unreachable + value as 0 seconds. + +*/ +static bool test_get_node_reachability_03(void) { + bool status; + time_t last_unreachable, last_reachable; + char nut_confbase[PATH_MAX]; + char peer_confbase[PATH_MAX]; + create_path(nut_confbase, NUT, 3); + create_path(peer_confbase, PEER, 3); + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open and start peer node instance, invite NUT. + + meshlink_handle_t *mesh_peer = meshlink_open(peer_confbase, PEER, GET_NODE_REACHABILITY, + DEV_CLASS_STATIONARY); + assert_non_null(mesh_peer); + assert_true(meshlink_start(mesh_peer)); + char *invitation = meshlink_invite(mesh_peer, NULL, NUT); + assert_non_null(invitation); + + // Open NUT node instance and join with the peer node + + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, GET_NODE_REACHABILITY, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + assert_true(meshlink_join(mesh, invitation)); + free(invitation); + meshlink_node_t *peer_handle = meshlink_get_node(mesh, PEER); + assert_non_null(peer_handle); + + // Cleanup peer node instance + + meshlink_close(mesh_peer); + assert_true(meshlink_destroy(peer_confbase)); + + // Call meshlink_get_node_reachability API with valid arguments + + status = meshlink_get_node_reachability(mesh, peer_handle, &last_reachable, &last_unreachable); + assert_false(status); + assert_int_equal(last_reachable, 0); + assert_int_equal(last_unreachable, 0); + + // Start NUT node instance + + assert_true(meshlink_start(mesh)); + + // Call meshlink_get_node_reachability API with valid arguments + + status = meshlink_get_node_reachability(mesh, peer_handle, &last_reachable, &last_unreachable); + assert_false(status); + assert_int_equal(last_reachable, 0); + assert_int_equal(last_unreachable, 0); + + // Stop NUT node instance + + meshlink_stop(mesh); + + // Call meshlink_get_node_reachability API with valid arguments + + status = meshlink_get_node_reachability(mesh, peer_handle, &last_reachable, &last_unreachable); + assert_false(status); + assert_int_equal(last_reachable, 0); + assert_int_equal(last_unreachable, 0); + + // Reinitialize NUT node instance + + meshlink_close(mesh); + mesh = meshlink_open(nut_confbase, NUT, GET_NODE_REACHABILITY, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + peer_handle = meshlink_get_node(mesh, PEER); + assert_non_null(peer_handle); + + // Call meshlink_get_node_reachability API with valid arguments + + status = meshlink_get_node_reachability(mesh, peer_handle, &last_reachable, &last_unreachable); + assert_false(status); + assert_int_equal(last_reachable, 0); + assert_int_equal(last_unreachable, 0); + + // Cleanup NUT + + meshlink_close(mesh); + assert_true(meshlink_destroy(nut_confbase)); + return true; +} + +/* + Execute meshlink get last node reachability times feature Test Case # 4 - + API testing around invited and invitee node. +*/ +static void test_case_get_node_reachability_04(void **state) { + execute_test(test_get_node_reachability_04, state); +} + +/* Test Steps for meshlink_get_node_reachability Test Case # 4 + + Test steps and scenarios: + 1. Open Node-Under-Test (NUT) and peer node instance, join both the node and + bring them online. Call meshlink_get_node_reachability API from both the nodes. + Expected Result: + API for both the nodes returns reachable status, last_reachable should be + some non-zero positive seconds and last_unreachable should be 0 seconds. + + 2. Stop both the node instances, Call meshlink_get_node_reachability API from both the nodes. + Expected Result: + API for both the nodes returns unreachable status. last_reachable should match with + the old value and last_unreachable should be non-zero positive value. + + 3. Restart both the node instances, Call meshlink_get_node_reachability APIs. + Expected Result: + API for both the nodes should return reachable status. last_reachable should not match with + the old value, but last_unreachable should remain same + + 4. Close and reopen both the node instances, Call meshlink_get_node_reachability APIs. + Expected Result: + API returns self node unreachable status, last_reachable should remain same + but last_unreachable should vary. + + 4. Start both the node instances, Call meshlink_get_node_reachability APIs. + Expected Result: + API returns self node reachable status, last_reachable should vary and + last_unreachable remains same. + +*/ +static bool test_get_node_reachability_04(void) { + bool status; + time_t last_nut_unreachable, last_nut_reachable; + time_t last_peer_unreachable, last_peer_reachable; + time_t last_reachable, last_unreachable; + char nut_confbase[PATH_MAX]; + char peer_confbase[PATH_MAX]; + create_path(nut_confbase, NUT, 4); + create_path(peer_confbase, PEER, 4); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open both NUT and peer node instance, invite and join NUT with peer node. + + meshlink_handle_t *mesh_peer = meshlink_open(peer_confbase, PEER, GET_NODE_REACHABILITY, + DEV_CLASS_STATIONARY); + assert_non_null(mesh_peer); + meshlink_set_node_status_cb(mesh_peer, meshlink_node_reachable_status_cb); + char *invitation = meshlink_invite(mesh_peer, NULL, NUT); + assert_non_null(invitation); + assert_true(meshlink_start(mesh_peer)); + + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, GET_NODE_REACHABILITY, + DEV_CLASS_STATIONARY); + assert_non_null(mesh); + meshlink_set_node_status_cb(mesh, meshlink_node_reachable_status_cb); + assert_true(meshlink_join(mesh, invitation)); + free(invitation); + + meshlink_node_t *peer_handle = meshlink_get_node(mesh, PEER); + assert_non_null(peer_handle); + meshlink_node_t *nut_handle = meshlink_get_node(mesh_peer, NUT); + assert_non_null(nut_handle); + + // Bring nodes online. + + set_sync_flag(&peer_reachable_status_cond, false); + set_sync_flag(&nut_reachable_status_cond, false); + assert_true(meshlink_start(mesh)); + assert_true(wait_sync_flag(&peer_reachable_status_cond, 60)); + assert_true(peer_reachable_status); + assert_true(wait_sync_flag(&nut_reachable_status_cond, 60)); + assert_true(nut_reachable_status); + + // Call meshlink_get_node_reachability API from joined node and also from joining node. + + status = meshlink_get_node_reachability(mesh, peer_handle, &last_reachable, &last_unreachable); + assert_true(status); + assert_int_not_equal(last_reachable, 0); + assert_int_equal(last_unreachable, 0); + last_peer_reachable = last_reachable; + + status = meshlink_get_node_reachability(mesh_peer, nut_handle, &last_reachable, &last_unreachable); + assert_true(status); + assert_int_not_equal(last_reachable, 0); + assert_int_equal(last_unreachable, 0); + last_nut_reachable = last_reachable; + + // Stop the node instances of both peer and NUT. + + meshlink_stop(mesh); + meshlink_stop(mesh_peer); + + // Call meshlink_get_node_reachability API from joined node and also from joining node. + + status = meshlink_get_node_reachability(mesh, peer_handle, &last_reachable, &last_unreachable); + assert_false(status); + assert_int_not_equal(last_unreachable, 0); + assert_int_equal(last_reachable, last_peer_reachable); + last_peer_unreachable = last_unreachable; + + status = meshlink_get_node_reachability(mesh_peer, nut_handle, &last_reachable, &last_unreachable); + assert_false(status); + assert_int_not_equal(last_unreachable, 0); + assert_int_equal(last_reachable, last_nut_reachable); + last_nut_unreachable = last_unreachable; + + // Restart the node instances of both peer and NUT and wait for nodes to come online + + sleep(2); + set_sync_flag(&peer_reachable_status_cond, false); + set_sync_flag(&nut_reachable_status_cond, false); + meshlink_set_node_status_cb(mesh, meshlink_node_reachable_status_cb); + meshlink_set_node_status_cb(mesh_peer, meshlink_node_reachable_status_cb); + assert_true(meshlink_start(mesh)); + assert_true(meshlink_start(mesh_peer)); + + assert_true(wait_sync_flag(&peer_reachable_status_cond, 60)); + assert_true(peer_reachable_status); + assert_true(wait_sync_flag(&nut_reachable_status_cond, 60)); + assert_true(nut_reachable_status); + + // Call meshlink_get_node_reachability API from joined node and also from joining node. + + status = meshlink_get_node_reachability(mesh, peer_handle, &last_reachable, &last_unreachable); + assert_true(status); + assert_int_not_equal(last_reachable, last_peer_reachable); + assert_true(last_unreachable >= last_peer_unreachable); + last_peer_reachable = last_reachable; + + status = meshlink_get_node_reachability(mesh_peer, nut_handle, &last_reachable, &last_unreachable); + assert_true(status); + assert_int_not_equal(last_reachable, last_nut_reachable); + assert_true(last_unreachable >= last_nut_unreachable); + last_nut_reachable = last_reachable; + + // Reinitialize the node instances of both peer and NUT + + meshlink_close(mesh); + meshlink_close(mesh_peer); + + sleep(2); + + mesh = meshlink_open(nut_confbase, NUT, GET_NODE_REACHABILITY, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + meshlink_set_node_status_cb(mesh, meshlink_node_reachable_status_cb); + mesh_peer = meshlink_open(peer_confbase, PEER, GET_NODE_REACHABILITY, + DEV_CLASS_STATIONARY); + assert_non_null(mesh_peer); + meshlink_set_node_status_cb(mesh_peer, meshlink_node_reachable_status_cb); + + peer_handle = meshlink_get_node(mesh, PEER); + assert_non_null(peer_handle); + nut_handle = meshlink_get_node(mesh_peer, NUT); + assert_non_null(nut_handle); + + // Call meshlink_get_node_reachability API from joined node and also from joining node. + + status = meshlink_get_node_reachability(mesh, peer_handle, &last_reachable, &last_unreachable); + assert_false(status); + assert_int_equal(last_reachable, last_peer_reachable); + assert_int_not_equal(last_unreachable, last_peer_unreachable); + last_peer_unreachable = last_unreachable; + + status = meshlink_get_node_reachability(mesh_peer, nut_handle, &last_reachable, &last_unreachable); + assert_false(status); + assert_int_equal(last_reachable, last_nut_reachable); + assert_int_not_equal(last_unreachable, last_nut_unreachable); + last_nut_unreachable = last_unreachable; + + // Restart the node instances of both peer and NUT + + set_sync_flag(&peer_reachable_status_cond, false); + set_sync_flag(&nut_reachable_status_cond, false); + + assert_true(meshlink_start(mesh)); + assert_true(meshlink_start(mesh_peer)); + + assert_true(wait_sync_flag(&peer_reachable_status_cond, 60)); + assert_true(peer_reachable_status); + assert_true(wait_sync_flag(&nut_reachable_status_cond, 60)); + assert_true(nut_reachable_status); + + // Call meshlink_get_node_reachability API from joined node and also from joining node. + + status = meshlink_get_node_reachability(mesh, peer_handle, &last_reachable, &last_unreachable); + assert_true(status); + assert_int_not_equal(last_reachable, last_peer_reachable); + assert_true(last_unreachable >= last_peer_unreachable); + + status = meshlink_get_node_reachability(mesh_peer, nut_handle, &last_reachable, &last_unreachable); + assert_true(status); + assert_int_not_equal(last_reachable, last_nut_reachable); + assert_true(last_unreachable >= last_nut_unreachable); + + // Cleanup + + meshlink_close(mesh); + meshlink_close(mesh_peer); + assert_true(meshlink_destroy(nut_confbase)); + assert_true(meshlink_destroy(peer_confbase)); + return true; +} + +/* + Execute meshlink get last node reachability times feature Test Case # 5 - + API testing by calling it in the meshlink callback(s) and also isolation property. +*/ +static void test_case_get_node_reachability_05(void **state) { + execute_test(test_get_node_reachability_05, state); +} + +/* Test Steps for meshlink_get_node_reachability Test Case # 5 + + Test steps and scenarios: + 1. Open Node-Under-Test (NUT), peer and peer2 node instances. Join both the peer nodes + with NUT and bring them online. + Expected Result: + API called from the node reachable callback of the latest peer node from NUT + about other peer node which joined 1st should return reachable status, + last_reachable status as some positive non-zero value and last unreachable value as 0. + +*/ +static bool test_get_node_reachability_05(void) { + char *invitation; + bool status; + time_t last_reachable, last_unreachable; + char nut_confbase[PATH_MAX]; + char peer_confbase[PATH_MAX]; + char peer2_confbase[PATH_MAX]; + create_path(nut_confbase, NUT, 5); + create_path(peer_confbase, PEER, 5); + create_path(peer2_confbase, PEER2, 5); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open NUT, peer and peer2 and join peer nodes with NUT. + + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, GET_NODE_REACHABILITY, + DEV_CLASS_STATIONARY); + assert_non_null(mesh); + meshlink_set_node_status_cb(mesh, meshlink_node_reachable_status_cb_2); + meshlink_handle_t *mesh_peer = meshlink_open(peer_confbase, PEER, GET_NODE_REACHABILITY, + DEV_CLASS_STATIONARY); + assert_non_null(mesh_peer); + meshlink_handle_t *mesh_peer2 = meshlink_open(peer2_confbase, PEER2, GET_NODE_REACHABILITY, + DEV_CLASS_STATIONARY); + assert_non_null(mesh_peer2); + + assert_true(meshlink_start(mesh)); + + invitation = meshlink_invite(mesh, NULL, PEER); + assert_non_null(invitation); + assert_true(meshlink_join(mesh_peer, invitation)); + invitation = meshlink_invite(mesh, NULL, PEER2); + assert_non_null(invitation); + assert_true(meshlink_join(mesh_peer2, invitation)); + + // Call meshlink_get_node_reachability API from NUT and check they remained 0 and unreachable + + status = meshlink_get_node_reachability(mesh, meshlink_get_node(mesh, PEER), &last_reachable, &last_unreachable); + assert_int_equal(status, false); + assert_int_equal(last_reachable, 0); + assert_int_equal(last_unreachable, 0); + status = meshlink_get_node_reachability(mesh, meshlink_get_node(mesh, PEER2), &last_reachable, &last_unreachable); + assert_int_equal(status, false); + assert_int_equal(last_reachable, 0); + assert_int_equal(last_unreachable, 0); + + // Start and wait for the signal from the node reachable callback which is raised when + // NUT is able to call meshlink_get_node_reachability API from callback of other peer node. + + set_sync_flag(&peer_reachable_status_cond, false); + assert_true(meshlink_start(mesh_peer)); + assert_true(meshlink_start(mesh_peer2)); + assert_true(wait_sync_flag(&peer_reachable_status_cond, 60)); + assert_true(peer_node_callback_test_status); + + // Cleanup + + meshlink_close(mesh); + meshlink_close(mesh_peer); + meshlink_close(mesh_peer2); + assert_true(meshlink_destroy(nut_confbase)); + assert_true(meshlink_destroy(peer_confbase)); + assert_true(meshlink_destroy(peer2_confbase)); + return true; +} + +/* + Execute meshlink get last node reachability times feature Test Case # 6 - + Persistence testing on the joining node. +*/ +static void test_case_get_node_reachability_06(void **state) { + execute_test(test_get_node_reachability_06, state); +} + +/* Test Steps for meshlink_get_node_reachability Test Case # 6 + + Test steps and scenarios: + 1. Open Node-Under-Test (NUT) and invite peer node and close it's instance. + Spawn a process which waits for the peer node to join and raises SIGINT if the + appropriate callback is received (on the other hand the test suite opens and joins + the peer node with NUT in the forked process). + Reopen NUT instance in the test suite process and call meshlink_get_node_reachability. + Expected Result: + API returns peer node unreachable, last_reachable and last_unreachable values + as 0 seconds. It is expected that this feature synchronize it at least for the first time + when the NUT receives that a new peer node joined. + +*/ +static bool test_get_node_reachability_06(void) { + bool status; + time_t last_reachable, last_unreachable; + pid_t pid; + int pid_status; + char nut_confbase[PATH_MAX]; + char peer_confbase[PATH_MAX]; + create_path(nut_confbase, NUT, 6); + create_path(peer_confbase, PEER, 6); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open NUT node instance and invite peer node. Close NUT node instance. + + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, GET_NODE_REACHABILITY, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + char *invitation = meshlink_invite(mesh, NULL, PEER); + meshlink_close(mesh); + + // Set the SIGUSR2 signal handler with handler that signal the condition to the test suite + + sighandler_t usr2sighandler = signal(SIGUSR2, nut_started_user_signal_handler); + assert_int_not_equal(usr2sighandler, SIG_ERR); + + // Fork a new process and run NUT in it which just waits for the peer node reachable status callback + // and terminates the process immediately. + + pid = fork(); + assert_int_not_equal(pid, -1); + + if(!pid) { + assert(signal(SIGUSR2, SIG_DFL) != SIG_ERR); + + mesh = meshlink_open(nut_confbase, NUT, GET_NODE_REACHABILITY, DEV_CLASS_STATIONARY); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb); + meshlink_set_node_status_cb(mesh, meshlink_node_reachable_status_cb); + + set_sync_flag(&peer_reachable_status_cond, false); + assert(meshlink_start(mesh)); + + assert(kill(getppid(), SIGUSR2) != -1); + + assert(wait_sync_flag(&peer_reachable_status_cond, 60)); + assert(peer_reachable_status); + + raise(SIGINT); + } + + // Open peer node instance and join with the invitation obtained. + + meshlink_handle_t *mesh_peer = meshlink_open(peer_confbase, PEER, GET_NODE_REACHABILITY, + DEV_CLASS_STATIONARY); + assert_non_null(mesh_peer); + + // Wait for the started signal from NUT and reset the previous SIGUSR2 signal handler + + assert_true(wait_sync_flag(&nut_started_status_cond, 60)); + assert_int_not_equal(signal(SIGUSR2, usr2sighandler), SIG_ERR); + + assert_true(meshlink_join(mesh_peer, invitation)); + assert_true(meshlink_start(mesh_peer)); + + // Wait for child exit and verify which signal terminated it + + assert_int_not_equal(waitpid(pid, &pid_status, 0), -1); + assert_int_equal(WIFSIGNALED(pid_status), true); + assert_int_equal(WTERMSIG(pid_status), SIGINT); + + // Reopen the NUT instance in the same test suite + + mesh = meshlink_open(nut_confbase, NUT, GET_NODE_REACHABILITY, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + + // Call meshlink_get_node_reachability API and verify that the time stamps has persisted. + + status = meshlink_get_node_reachability(mesh, meshlink_get_node(mesh, PEER), &last_reachable, &last_unreachable); + assert_int_equal(status, false); + assert_int_not_equal(last_reachable, 0); + assert_int_equal(last_unreachable, 0); + + // Cleanup + + meshlink_close(mesh); + meshlink_close(mesh_peer); + assert_true(meshlink_destroy(nut_confbase)); + assert_true(meshlink_destroy(peer_confbase)); + return true; +} + +/* + Execute meshlink get last node reachability times feature Test Case # 7 - + Persistence testing on the invited node. +*/ +static void test_case_get_node_reachability_07(void **state) { + execute_test(test_get_node_reachability_07, state); +} + +/* Test Steps for meshlink_get_node_reachability Test Case # 7 + + Test steps and scenarios: + 1. Open peer node instance, invite NUT and start peer node. Spawn a new process in + which it opens and joins the NUT with peer node. + Reopen NUT instance in the test suite process and call meshlink_get_node_reachability API. + Expected Result: + API returns peer node unreachable, last_reachable and last_unreachable values + as 0 seconds. It is expected that this feature synchronize it at least for the first time + when the Node-Under-Test joined with the peer node. + +*/ +static bool test_get_node_reachability_07(void) { + bool status; + time_t last_reachable, last_unreachable; + pid_t pid; + int pid_status; + char nut_confbase[PATH_MAX]; + char peer_confbase[PATH_MAX]; + create_path(nut_confbase, NUT, 7); + create_path(peer_confbase, PEER, 7); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open peer node instance and invite NUT. + + meshlink_handle_t *mesh_peer = meshlink_open(peer_confbase, PEER, GET_NODE_REACHABILITY, + DEV_CLASS_STATIONARY); + assert_int_not_equal(mesh_peer, NULL); + char *invitation = meshlink_invite(mesh_peer, NULL, NUT); + assert_non_null(invitation); + + assert_true(meshlink_start(mesh_peer)); + + // Fork a new process in which NUT is joins with the peer node and raises SIGINT to terminate. + + pid = fork(); + assert_int_not_equal(pid, -1); + + if(!pid) { + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, GET_NODE_REACHABILITY, DEV_CLASS_STATIONARY); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb); + + assert(meshlink_join(mesh, invitation)); + + raise(SIGINT); + } + + // Wait for child exit and verify which signal terminated it + + assert_int_not_equal(waitpid(pid, &pid_status, 0), -1); + assert_int_equal(WIFSIGNALED(pid_status), true); + assert_int_equal(WTERMSIG(pid_status), SIGINT); + + // Reopen the NUT instance in the same test suite + + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, GET_NODE_REACHABILITY, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + + // Call meshlink_get_node_reachability API and verify that the time stamps has persisted. + + status = meshlink_get_node_reachability(mesh, meshlink_get_node(mesh, PEER), &last_reachable, &last_unreachable); + assert_int_equal(status, false); + assert_int_equal(last_reachable, 0); + assert_int_equal(last_unreachable, 0); + + // Cleanup + + meshlink_close(mesh); + meshlink_close(mesh_peer); + assert_true(meshlink_destroy(nut_confbase)); + assert_true(meshlink_destroy(peer_confbase)); + return true; +} + +int test_get_node_reachability(void) { + /* State structures for get node reachability Test Cases */ + black_box_state_t test_case_get_node_reachability_01_state = { + .test_case_name = "test_case_get_node_reachability_01", + }; + black_box_state_t test_case_get_node_reachability_02_state = { + .test_case_name = "test_case_get_node_reachability_02", + }; + black_box_state_t test_case_get_node_reachability_03_state = { + .test_case_name = "test_case_get_node_reachability_03", + }; + black_box_state_t test_case_get_node_reachability_04_state = { + .test_case_name = "test_case_get_node_reachability_04", + }; + black_box_state_t test_case_get_node_reachability_05_state = { + .test_case_name = "test_case_get_node_reachability_05", + }; + black_box_state_t test_case_get_node_reachability_06_state = { + .test_case_name = "test_case_get_node_reachability_06", + }; + black_box_state_t test_case_get_node_reachability_07_state = { + .test_case_name = "test_case_get_node_reachability_07", + }; + + const struct CMUnitTest blackbox_status_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_get_node_reachability_01, NULL, NULL, + (void *)&test_case_get_node_reachability_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_get_node_reachability_02, NULL, NULL, + (void *)&test_case_get_node_reachability_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_get_node_reachability_03, NULL, NULL, + (void *)&test_case_get_node_reachability_03_state), + cmocka_unit_test_prestate_setup_teardown(test_case_get_node_reachability_04, NULL, NULL, + (void *)&test_case_get_node_reachability_04_state), + cmocka_unit_test_prestate_setup_teardown(test_case_get_node_reachability_05, NULL, NULL, + (void *)&test_case_get_node_reachability_05_state), + cmocka_unit_test_prestate_setup_teardown(test_case_get_node_reachability_06, NULL, NULL, + (void *)&test_case_get_node_reachability_06_state), + cmocka_unit_test_prestate_setup_teardown(test_case_get_node_reachability_07, NULL, NULL, + (void *)&test_case_get_node_reachability_07_state), + }; + total_tests += sizeof(blackbox_status_tests) / sizeof(blackbox_status_tests[0]); + + return cmocka_run_group_tests(blackbox_status_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_get_node_reachability.h b/test/blackbox/run_blackbox_tests/test_cases_get_node_reachability.h new file mode 100644 index 0000000..72a6ea3 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_get_node_reachability.h @@ -0,0 +1,26 @@ +#ifndef TEST_CASES_GET_NODE_REACHABILITY +#define TEST_CASES_GET_NODE_REACHABILITY + +/* + test_cases_get_node_reachability.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +extern int test_get_node_reachability(void); +extern int total_tests; + +#endif // TEST_CASES_GET_NODE_REACHABILITY diff --git a/test/blackbox/run_blackbox_tests/test_cases_get_port.c b/test/blackbox/run_blackbox_tests/test_cases_get_port.c new file mode 100644 index 0000000..71f7cb4 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_get_port.c @@ -0,0 +1,110 @@ +/* + test_cases_get_port.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_get_port.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include + +static void test_case_mesh_get_port_01(void **state); +static bool test_steps_mesh_get_port_01(void); +static void test_case_mesh_get_port_02(void **state); +static bool test_steps_mesh_get_port_02(void); + +/* State structure for meshlink_get_port Test Case #1 */ +static black_box_state_t test_mesh_get_port_01_state = { + .test_case_name = "test_case_mesh_get_port_01", +}; + +/* State structure for meshlink_get_port Test Case #2 */ +static black_box_state_t test_mesh_get_port_02_state = { + .test_case_name = "test_case_mesh_get_port_02", +}; + +/* Execute meshlink_get_port Test Case # 1 */ +static void test_case_mesh_get_port_01(void **state) { + execute_test(test_steps_mesh_get_port_01, state); +} + +/* Test Steps for meshlink_get_port Test Case # 1 + + Test Steps: + 1. Open node instance + 2. Run the node instance + 3. Obtain port of that mesh using meshlink_get_port API + + Expected Result: + API returns valid port number. +*/ +static bool test_steps_mesh_get_port_01(void) { + meshlink_handle_t *mesh = meshlink_open("port_conf", "foo", "chat", DEV_CLASS_STATIONARY); + assert(mesh); + assert(meshlink_start(mesh)); + + int port = meshlink_get_port(mesh); + assert_int_not_equal(port, -1); + + meshlink_close(mesh); + assert(meshlink_destroy("port_conf")); + return true; +} + +/* Execute meshlink_get_port Test Case # 2 */ +static void test_case_mesh_get_port_02(void **state) { + execute_test(test_steps_mesh_get_port_02, state); +} + +/* Test Steps for meshlink_get_port Test Case # 2 - Invalid case + + Test Steps: + 1. Pass NULL as mesh handle argument to meshlink_get_port API + + Expected Result: + Reports error successfully by returning -1 +*/ +static bool test_steps_mesh_get_port_02(void) { + int port = meshlink_get_port(NULL); + assert_int_equal(port, -1); + + return true; +} + +int test_meshlink_get_port(void) { + const struct CMUnitTest blackbox_get_port_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_get_port_01, NULL, NULL, + (void *)&test_mesh_get_port_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_get_port_02, NULL, NULL, + (void *)&test_mesh_get_port_02_state) + }; + + total_tests += sizeof(blackbox_get_port_tests) / sizeof(blackbox_get_port_tests[0]); + + return cmocka_run_group_tests(blackbox_get_port_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_get_port.h b/test/blackbox/run_blackbox_tests/test_cases_get_port.h new file mode 100644 index 0000000..0386a8a --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_get_port.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_GET_PORT_H +#define TEST_CASES_GET_PORT_H + +/* + test_cases_get_port.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_get_port(void); +extern int total_tests; + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_get_self.c b/test/blackbox/run_blackbox_tests/test_cases_get_self.c new file mode 100644 index 0000000..3368a42 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_get_self.c @@ -0,0 +1,115 @@ +/* + test_cases_get_port.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_get_self.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include + +static void test_case_mesh_get_self_01(void **state); +static bool test_steps_mesh_get_self_01(void); +static void test_case_mesh_get_self_02(void **state); +static bool test_steps_mesh_get_self_02(void); + +/* State structure for meshlink_get_self Test Case #1 */ +static black_box_state_t test_mesh_get_self_01_state = { + .test_case_name = "test_case_mesh_get_self_01", +}; + +/* State structure for meshlink_get_self Test Case #2 */ +static black_box_state_t test_mesh_get_self_02_state = { + .test_case_name = "test_case_mesh_get_self_02", +}; + +/* Execute meshlink_get_self Test Case # 1 */ +static void test_case_mesh_get_self_01(void **state) { + execute_test(test_steps_mesh_get_self_01, state); +} + +/* Test Steps for meshlink_get_self Test Case # 1 + + Test Steps: + 1. Open node instance + 2. Get node's self handle + + Expected Result: + node handle of it's own is obtained +*/ +static bool test_steps_mesh_get_self_01(void) { + meshlink_handle_t *mesh = meshlink_open("self_conf", "foo", "test", DEV_CLASS_STATIONARY); + assert(mesh); + + assert(meshlink_start(mesh)); + meshlink_node_t *dest_node = meshlink_get_self(mesh); + assert_int_not_equal(dest_node, NULL); + + if(strcmp(dest_node->name, "foo")) { + return false; + } + + meshlink_close(mesh); + assert(meshlink_destroy("self_conf")); + return true; + +} + +/* Execute meshlink_get_self Test Case # 2 */ +static void test_case_mesh_get_self_02(void **state) { + execute_test(test_steps_mesh_get_self_02, state); +} + +/* Test Steps for meshlink_get_self Test Case # 2 + + Test Steps: + 1. Open NUT(Node Under Test) & bar meshes. + 2. Export and Import mutually + + Expected Result: + Both the nodes imports successfully +*/ +static bool test_steps_mesh_get_self_02(void) { + meshlink_node_t *dest_node = meshlink_get_self(NULL); + assert_int_equal(dest_node, NULL); + + return true; +} + +int test_meshlink_get_self(void) { + const struct CMUnitTest blackbox_get_self_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_get_self_01, NULL, NULL, + (void *)&test_mesh_get_self_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_get_self_02, NULL, NULL, + (void *)&test_mesh_get_self_02_state) + }; + + total_tests += sizeof(blackbox_get_self_tests) / sizeof(blackbox_get_self_tests[0]); + + return cmocka_run_group_tests(blackbox_get_self_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_get_self.h b/test/blackbox/run_blackbox_tests/test_cases_get_self.h new file mode 100644 index 0000000..24d7fc9 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_get_self.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_GET_SELF_H +#define TEST_CASES_GET_SELF_H + +/* + test_cases_get_self.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_get_self(void); +extern int total_tests; + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_hint_address.c b/test/blackbox/run_blackbox_tests/test_cases_hint_address.c new file mode 100644 index 0000000..61be451 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_hint_address.c @@ -0,0 +1,142 @@ +/* + test_cases_hint_address.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_hint_address.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +/* Port number used in the structure */ +#define PORT 8000 + +/* hint address used in the socket structure */ +#define ADDR "10.1.1.1" + +static void test_case_hint_address_01(void **state); +static bool test_steps_hint_address_01(void); + +static black_box_state_t test_case_hint_address_01_state = { + .test_case_name = "test_case_hint_address_01", +}; + + +/* Execute meshlink_hint_address Test Case # 1 - Valid Case*/ +void test_case_hint_address_01(void **state) { + execute_test(test_steps_hint_address_01, state); +} +/* Test Steps for meshlink_hint_address Test Case # 1 - Valid case */ +bool test_steps_hint_address_01(void) { + assert(meshlink_destroy("hintconf1")); + assert(meshlink_destroy("hintconf2")); + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + // Create meshlink instance for the nodes + meshlink_handle_t *mesh1 = meshlink_open("hintconf1", "nut", "test", DEV_CLASS_STATIONARY); + assert(mesh1); + meshlink_handle_t *mesh2 = meshlink_open("hintconf2", "bar", "test", DEV_CLASS_STATIONARY); + assert(mesh2); + meshlink_set_log_cb(mesh1, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_set_log_cb(mesh2, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + // importing and exporting mesh meta data + char *exp1 = meshlink_export(mesh1); + assert(exp1 != NULL); + char *exp2 = meshlink_export(mesh2); + assert(exp2 != NULL); + assert(meshlink_import(mesh1, exp2)); + assert(meshlink_import(mesh2, exp1)); + free(exp1); + free(exp2); + + // Nodes should learn about each other + sleep(1); + + // Start the nodes + assert(meshlink_start(mesh1)); + assert(meshlink_start(mesh2)); + + // socket structure to be hinted + struct sockaddr_in hint; + hint.sin_family = AF_INET; + hint.sin_port = htons(PORT); + assert(inet_aton(ADDR, &hint.sin_addr)); + + // Getting node handle for the NUT itself + meshlink_node_t *node = meshlink_get_node(mesh1, "bar"); + assert(node != NULL); + + meshlink_hint_address(mesh_handle, node, (struct sockaddr *)&hint); + + int fp; + fp = open("./hintconf1/hosts/bar", O_RDONLY); + assert(fp >= 0); + off_t fsize = lseek(fp, 0, SEEK_END); + assert(fsize >= 0); + char *buff = (char *) calloc(1, fsize + 1); + assert(buff != NULL); + assert(lseek(fp, 0, SEEK_SET) == 0); + assert(read(fp, buff, fsize) >= 0); + buff[fsize] = '\0'; + assert(close(fp) != -1); + + assert_int_not_equal(strstr(buff, ADDR), NULL); + + free(buff); + meshlink_close(mesh1); + meshlink_close(mesh2); + assert(meshlink_destroy("hintconf1")); + assert(meshlink_destroy("hintconf2")); + + return true; +} + + +int test_meshlink_hint_address(void) { + const struct CMUnitTest blackbox_hint_address_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_hint_address_01, NULL, NULL, + (void *)&test_case_hint_address_01_state) + }; + + total_tests += sizeof(blackbox_hint_address_tests) / sizeof(blackbox_hint_address_tests[0]); + + return cmocka_run_group_tests(blackbox_hint_address_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_hint_address.h b/test/blackbox/run_blackbox_tests/test_cases_hint_address.h new file mode 100644 index 0000000..e64ba5e --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_hint_address.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_HINT_H +#define TEST_CASES_HINT_H + +/* + test_cases_hint_address.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_hint_address(void); +extern int total_tests; + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_import.c b/test/blackbox/run_blackbox_tests/test_cases_import.c new file mode 100644 index 0000000..fb1b559 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_import.c @@ -0,0 +1,317 @@ +/* + test_cases_import.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_import.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +static void test_case_import_01(void **state); +static bool test_import_01(void); +static void test_case_import_02(void **state); +static bool test_import_02(void); +static void test_case_import_03(void **state); +static bool test_import_03(void); +static void test_case_import_04(void **state); +static bool test_import_04(void); +static void test_case_import_05(void **state); +static bool test_import_05(void); + +/* State structure for import API Test Case #1 */ +static black_box_state_t test_case_import_01_state = { + .test_case_name = "test_case_import_01", +}; + +/* State structure for import API Test Case #2 */ +static black_box_state_t test_case_import_02_state = { + .test_case_name = "test_case_import_02", +}; + +/* State structure for import API Test Case #3 */ +static black_box_state_t test_case_import_03_state = { + .test_case_name = "test_case_import_03", +}; + +/* State structure for import API Test Case #4 */ +static black_box_state_t test_case_import_04_state = { + .test_case_name = "test_case_import_04", +}; + +/* State structure for import API Test Case #5 */ +static black_box_state_t test_case_import_05_state = { + .test_case_name = "test_case_import_05", +}; + +/* Execute import Test Case # 1 - valid case*/ +static void test_case_import_01(void **state) { + execute_test(test_import_01, state); +} +/* Test Steps for meshlink_import Test Case # 1 - Valid case + + Test Steps: + 1. Open NUT(Node Under Test) & bar meshes. + 2. Export and Import mutually + + Expected Result: + Both the nodes imports successfully +*/ +static bool test_import_01(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + assert(meshlink_destroy("importconf1")); + assert(meshlink_destroy("importconf2")); + + // Opening NUT and bar nodes + meshlink_handle_t *mesh1 = meshlink_open("importconf1", "nut", "test", DEV_CLASS_STATIONARY); + assert(mesh1 != NULL); + meshlink_set_log_cb(mesh1, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_handle_t *mesh2 = meshlink_open("importconf2", "bar", "test", DEV_CLASS_STATIONARY); + assert(mesh2 != NULL); + meshlink_set_log_cb(mesh2, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + // Exporting and Importing mutually + char *exp1 = meshlink_export(mesh1); + assert(exp1 != NULL); + char *exp2 = meshlink_export(mesh2); + assert(exp2 != NULL); + bool imp1 = meshlink_import(mesh1, exp2); + bool imp2 = meshlink_import(mesh2, exp1); + + assert_int_equal(imp1 && imp2, true); + + meshlink_close(mesh1); + meshlink_close(mesh2); + assert(meshlink_destroy("importconf1")); + assert(meshlink_destroy("importconf2")); + return imp1 && imp2; +} + +/* Execute import Test Case # 2 - invalid case*/ +static void test_case_import_02(void **state) { + execute_test(test_import_02, state); +} +/* Test Steps for meshlink_import Test Case # 2 - Invalid case + + Test Steps: + 1. Open NUT(Node Under Test) & bar meshes. + 2. Passing NULL as mesh handle argument for meshlink_import API + + Expected Result: + Reports error successfully by returning false +*/ +static bool test_import_02(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + assert(meshlink_destroy("importconf1")); + assert(meshlink_destroy("importconf2")); + + // Opening NUT and bar nodes + meshlink_handle_t *mesh1 = meshlink_open("importconf1", "nut", "test", DEV_CLASS_STATIONARY); + assert(mesh1 != NULL); + meshlink_set_log_cb(mesh1, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_handle_t *mesh2 = meshlink_open("importconf2", "bar", "test", DEV_CLASS_STATIONARY); + assert(mesh2 != NULL); + meshlink_set_log_cb(mesh2, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + // Exporting & Importing nodes + char *exp1 = meshlink_export(mesh1); + assert(exp1 != NULL); + char *exp2 = meshlink_export(mesh2); + assert(exp2 != NULL); + + bool imp1 = meshlink_import(NULL, exp2); + bool imp2 = meshlink_import(mesh2, exp1); + assert_int_equal((!imp1) && imp2, true); + + meshlink_close(mesh1); + meshlink_close(mesh2); + assert(meshlink_destroy("importconf1")); + assert(meshlink_destroy("importconf2")); + return true; +} + + +/* Execute import Test Case # 3 - invalid case*/ +static void test_case_import_03(void **state) { + execute_test(test_import_03, state); +} +/* Test Steps for meshlink_import Test Case # 3 - Invalid case + + Test Steps: + 1. Open NUT(Node Under Test) & bar meshes. + 2. Passing NULL as exported data argument for meshlink_import API + + Expected Result: + Reports error successfully by returning false +*/ +static bool test_import_03(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + assert(meshlink_destroy("importconf1")); + assert(meshlink_destroy("importconf2")); + + /* Opening NUT and bar nodes */ + meshlink_handle_t *mesh1 = meshlink_open("importconf1", "nut", "chat", DEV_CLASS_STATIONARY); + assert(mesh1 != NULL); + meshlink_set_log_cb(mesh1, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_handle_t *mesh2 = meshlink_open("importconf2", "bar", "chat", DEV_CLASS_STATIONARY); + assert(mesh2 != NULL); + meshlink_set_log_cb(mesh2, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Exporting & Importing nodes */ + char *exp1 = meshlink_export(mesh1); + assert(exp1 != NULL); + char *exp2 = meshlink_export(mesh2); + assert(exp2 != NULL); + + bool imp1 = meshlink_import(mesh1, NULL); + bool imp2 = meshlink_import(mesh2, exp1); + + assert_int_equal((!imp1) && imp2, true); + + meshlink_close(mesh1); + meshlink_close(mesh2); + assert(meshlink_destroy("importconf1")); + assert(meshlink_destroy("importconf2")); + return true; +} + +/* Execute import Test Case # 4 - invalid case garbage string*/ +static void test_case_import_04(void **state) { + execute_test(test_import_04, state); +} +/* Test Steps for meshlink_import Test Case # 4 - Invalid case + + Test Steps: + 1. Open NUT(Node Under Test) & bar meshes. + 2. Passing some garbage string(NULL terminated) + as an argument for meshlink_import API + + Expected Result: + Reports error successfully by returning false +*/ +static bool test_import_04(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + assert(meshlink_destroy("importconf1")); + assert(meshlink_destroy("importconf2")); + + // Opening NUT and bar nodes + meshlink_handle_t *mesh1 = meshlink_open("importconf1", "nut", "chat", DEV_CLASS_STATIONARY); + assert(mesh1 != NULL); + meshlink_set_log_cb(mesh1, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_handle_t *mesh2 = meshlink_open("importconf2", "bar", "chat", DEV_CLASS_STATIONARY); + assert(mesh2 != NULL); + meshlink_set_log_cb(mesh2, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + // Exporting & Importing nodes + char *exp1 = meshlink_export(mesh1); + assert(exp1 != NULL); + char *exp2 = meshlink_export(mesh2); + assert(exp2 != NULL); + + // Importing NUT with garbage string as exported data argument + bool imp1 = meshlink_import(mesh1, "1/2/3"); + bool imp2 = meshlink_import(mesh2, exp1); + assert_int_equal((!imp1) && imp2, true); + + meshlink_close(mesh1); + meshlink_close(mesh2); + assert(meshlink_destroy("importconf1")); + assert(meshlink_destroy("importconf2")); + return true; +} + +/* Execute import Test Case # 5 - valid case*/ +static void test_case_import_05(void **state) { + execute_test(test_import_05, state); +} +/* Test Steps for meshlink_import Test Case # 5 - Invalid case + + Test Steps: + 1. Open NUT(Node Under Test) & bar meshes. + 2. Export and Import mutually + 2. Try to import NUT again/twice at 'bar' node + + Expected Result: + Reports error successfully by returning false +*/ +static bool test_import_05(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + assert(meshlink_destroy("importconf1")); + assert(meshlink_destroy("importconf2")); + + /* Opening NUT and bar nodes */ + meshlink_handle_t *mesh1 = meshlink_open("importconf1", "nut", "chat", DEV_CLASS_STATIONARY); + assert(mesh1 != NULL); + meshlink_set_log_cb(mesh1, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_handle_t *mesh2 = meshlink_open("importconf2", "bar", "chat", DEV_CLASS_STATIONARY); + assert(mesh2 != NULL); + meshlink_set_log_cb(mesh2, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Exporting & Importing nodes */ + char *exp1 = meshlink_export(mesh1); + assert(exp1 != NULL); + char *exp2 = meshlink_export(mesh2); + assert(exp2 != NULL); + bool imp1 = meshlink_import(mesh1, exp2); + assert(imp1); + bool imp2 = meshlink_import(mesh2, exp1); + assert(imp2); + + /** Trying to import twice **/ + bool imp3 = meshlink_import(mesh2, exp1); + + assert_int_equal(imp3, false); + + meshlink_close(mesh1); + meshlink_close(mesh2); + assert(meshlink_destroy("importconf1")); + assert(meshlink_destroy("importconf2")); + return true; +} + +int test_meshlink_import(void) { + const struct CMUnitTest blackbox_import_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_import_01, NULL, NULL, + (void *)&test_case_import_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_import_02, NULL, NULL, + (void *)&test_case_import_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_import_03, NULL, NULL, + (void *)&test_case_import_03_state), + cmocka_unit_test_prestate_setup_teardown(test_case_import_04, NULL, NULL, + (void *)&test_case_import_04_state), + cmocka_unit_test_prestate_setup_teardown(test_case_import_05, NULL, NULL, + (void *)&test_case_import_05_state) + }; + total_tests += sizeof(blackbox_import_tests) / sizeof(blackbox_import_tests[0]); + + return cmocka_run_group_tests(blackbox_import_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_import.h b/test/blackbox/run_blackbox_tests/test_cases_import.h new file mode 100644 index 0000000..4316b93 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_import.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_IMPORT_H +#define TEST_CASES_IMPORT_H + +/* + test_cases_import.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_import(void); +extern int total_tests; + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_invite.c b/test/blackbox/run_blackbox_tests/test_cases_invite.c new file mode 100644 index 0000000..f04cb09 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_invite.c @@ -0,0 +1,287 @@ +/* + test_cases_invite.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_invite.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "../../utils.h" +#include +#include +#include +#include +#include +#include +#include +#include + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +#define NUT "nut" +#define PEER "peer" +#define TEST_MESHLINK_INVITE "test_invite" +#define create_path(confbase, node_name, test_case_no) assert(snprintf(confbase, sizeof(confbase), TEST_MESHLINK_INVITE "_%ld_%s_%02d", (long) getpid(), node_name, test_case_no) > 0) + +static void test_case_invite_01(void **state); +static bool test_invite_01(void); +static void test_case_invite_02(void **state); +static bool test_invite_02(void); +static void test_case_invite_03(void **state); +static bool test_invite_03(void); +static void test_case_invite_04(void **state); +static bool test_invite_04(void); +static void test_case_invite_05(void **state); +static bool test_invite_05(void); + +/* State structure for invite API Test Case #1 */ +static black_box_state_t test_case_invite_01_state = { + .test_case_name = "test_case_invite_01", +}; + +/* State structure for invite API Test Case #2 */ +static black_box_state_t test_case_invite_02_state = { + .test_case_name = "test_case_invite_02", +}; + +/* State structure for invite API Test Case #3 */ +static black_box_state_t test_case_invite_03_state = { + .test_case_name = "test_case_invite_03", +}; + +/* State structure for invite API Test Case #4 */ +static black_box_state_t test_case_invite_04_state = { + .test_case_name = "test_case_invite_04", +}; + +/* State structure for invite API Test Case #5 */ +static black_box_state_t test_case_invite_05_state = { + .test_case_name = "test_case_invite_05", +}; + +/* Execute invite Test Case # 1 - valid case*/ +static void test_case_invite_01(void **state) { + execute_test(test_invite_01, state); +} +/*Test Steps for meshlink_invite Test Case # 1 - Valid case + Test Steps: + 1. Run NUT + 2. Invite 'new' node + + Expected Result: + Generates an invitation +*/ +static bool test_invite_01(void) { + char nut_confbase[PATH_MAX]; + char peer_invitation[1000]; + create_path(nut_confbase, NUT, 1); + + // Create meshlink instance + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_INVITE, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + + char *invitation = meshlink_invite(mesh, NULL, "new"); + assert_non_null(invitation); + + free(invitation); + meshlink_close(mesh); + assert_true(meshlink_destroy(nut_confbase)); + return true; +} + +/* Execute invite Test Case # 2 - Invalid case*/ +static void test_case_invite_02(void **state) { + execute_test(test_invite_02, state); +} +/*Test Steps for meshlink_invite Test Case # 2 - Invalid case + Test Steps: + 1. Calling meshlink_invite API with NULL as mesh handle argument + + Expected Result: + Reports appropriate error by returning NULL +*/ +static bool test_invite_02(void) { + // Trying to generate INVITATION by passing NULL as mesh link handle + char *invitation = meshlink_invite(NULL, NULL, "nut"); + assert_int_equal(invitation, NULL); + + return true; +} + +/* Execute invite Test Case # 3 - Invalid case*/ +static void test_case_invite_03(void **state) { + execute_test(test_invite_03, state); +} +/*Test Steps for meshlink_invite Test Case # 3 - Invalid case + Test Steps: + 1. Run NUT + 2. Call meshlink_invite with NULL node name argument + + Expected Result: + Reports appropriate error by returning NULL +*/ +static bool test_invite_03(void) { + char nut_confbase[PATH_MAX]; + char peer_invitation[1000]; + create_path(nut_confbase, NUT, 3); + + // Create meshlink instance + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_INVITE, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + + char *invitation = meshlink_invite(mesh, NULL, NULL); + assert_int_equal(invitation, NULL); + + free(invitation); + meshlink_close(mesh); + assert_true(meshlink_destroy(nut_confbase)); + return true; +} + +/* Execute invite Test Case # 4 - Functionality test*/ +static void test_case_invite_04(void **state) { + execute_test(test_invite_04, state); +} +/*Test Steps for meshlink_invite Test Case # 4 - Functionality test + + Test Steps: + 1. Create node instance + 2. Add a new address to the mesh and invite a node + 3. Add another new address and invite a node + + Expected Result: + Newly added address should be there in the invitation. +*/ +static bool test_invite_04(void) { + char nut_confbase[PATH_MAX]; + char peer_invitation[1000]; + create_path(nut_confbase, NUT, 4); + + // Create meshlink instance + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_INVITE, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + + assert_true(meshlink_add_invitation_address(mesh, "11.11.11.11", "2020")); + char *invitation = meshlink_invite(mesh, NULL, "foo"); + assert_non_null(strstr(invitation, "11.11.11.11:2020")); + free(invitation); + + assert_true(meshlink_add_invitation_address(mesh, "fe80::1548:d713:3899:f645", "3030")); + invitation = meshlink_invite(mesh, NULL, "bar"); + assert_non_null(strstr(invitation, "11.11.11.11:2020")); + assert_non_null(strstr(invitation, "[fe80::1548:d713:3899:f645]:3030")); + free(invitation); + + meshlink_close(mesh); + assert_true(meshlink_destroy(nut_confbase)); + return true; +} + +/* Execute invite Test Case # 5 - Synchronization testing */ +static void test_case_invite_05(void **state) { + execute_test(test_invite_05, state); +} + +static bool test_invite_05(void) { + bool status; + pid_t pid; + int pid_status; + int pipefd[2]; + char nut_confbase[PATH_MAX]; + char peer_confbase[PATH_MAX]; + char peer_invitation[1000]; + create_path(nut_confbase, NUT, 5); + create_path(peer_confbase, PEER, 5); + + assert_int_not_equal(pipe(pipefd), -1); + + // Fork a new process in which NUT opens it's instance and raises SIGINT to terminate. + + pid = fork(); + assert_int_not_equal(pid, -1); + + if(!pid) { + assert(!close(pipefd[0])); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_INVITE, DEV_CLASS_STATIONARY); + assert(mesh); + + char *invitation = meshlink_invite(mesh, NULL, PEER); + write(pipefd[1], invitation, strlen(invitation) + 1); + + raise(SIGINT); + } + + // Wait for child exit and verify which signal terminated it + + assert_int_not_equal(waitpid(pid, &pid_status, 0), -1); + assert_int_equal(WIFSIGNALED(pid_status), true); + assert_int_equal(WTERMSIG(pid_status), SIGINT); + + assert_int_equal(close(pipefd[1]), 0); + assert_int_not_equal(read(pipefd[0], peer_invitation, sizeof(peer_invitation)), -1); + + // Reopen the NUT instance in the same test suite + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_INVITE, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + meshlink_handle_t *mesh_peer = meshlink_open(peer_confbase, PEER, TEST_MESHLINK_INVITE, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + assert_true(meshlink_start(mesh)); + assert_true(meshlink_join(mesh_peer, peer_invitation)); + + // Cleanup + + meshlink_close(mesh); + meshlink_close(mesh_peer); + assert_true(meshlink_destroy(nut_confbase)); + assert_true(meshlink_destroy(peer_confbase)); + return true; +} + +int test_meshlink_invite(void) { + const struct CMUnitTest blackbox_invite_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_invite_01, NULL, NULL, + (void *)&test_case_invite_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_invite_02, NULL, NULL, + (void *)&test_case_invite_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_invite_03, NULL, NULL, + (void *)&test_case_invite_03_state), + cmocka_unit_test_prestate_setup_teardown(test_case_invite_04, NULL, NULL, + (void *)&test_case_invite_04_state), + cmocka_unit_test_prestate_setup_teardown(test_case_invite_05, NULL, NULL, + (void *)&test_case_invite_05_state) + }; + + total_tests += sizeof(blackbox_invite_tests) / sizeof(blackbox_invite_tests[0]); + + return cmocka_run_group_tests(blackbox_invite_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_invite.h b/test/blackbox/run_blackbox_tests/test_cases_invite.h new file mode 100644 index 0000000..e8c06c7 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_invite.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_INVITE_H +#define TEST_CASES_INVITE_H + +/* + test_cases_invite.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int total_tests; +extern int test_meshlink_invite(void); + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_join.c b/test/blackbox/run_blackbox_tests/test_cases_join.c new file mode 100644 index 0000000..882ec2c --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_join.c @@ -0,0 +1,788 @@ +/* + test_cases_join.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "execute_tests.h" +#include "test_cases_get_node_reachability.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "../../utils.h" +#include "../../../src/devtools.h" + +#define NUT "nut" +#define PEER "peer" +#define PEER2 "peer2" +#define TEST_MESHLINK_JOIN "test_meshlink_join" +#define create_path(confbase, node_name, test_case_no) assert(snprintf(confbase, sizeof(confbase), TEST_MESHLINK_JOIN "_%ld_%s_%02d", (long) getpid(), node_name, test_case_no) > 0) + +static struct sync_flag peer_reachable_status_cond = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static bool peer_reachable_status; +static struct sync_flag nut_reachable_status_cond = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static bool nut_reachable_status; +static struct sync_flag nut_started_status_cond = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; + +/* Node reachable status callback which signals the respective conditional varibale */ +static void meshlink_node_reachable_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable_status) { + if(!strcasecmp(mesh->name, NUT)) { + if(!strcasecmp(node->name, PEER)) { + peer_reachable_status = reachable_status; + set_sync_flag(&peer_reachable_status_cond, true); + } + } else if(!strcasecmp(mesh->name, PEER)) { + if(!strcasecmp(node->name, NUT)) { + nut_reachable_status = reachable_status; + set_sync_flag(&nut_reachable_status_cond, true); + } + } +} + +/* SIGUSR2 signal handler that signals the NUT started and PEER node can join */ +static void nut_started_user_signal_handler(int signum) { + if(signum == SIGUSR2) { + set_sync_flag(&nut_started_status_cond, true); + } + +} + +/* Test Steps for meshlink_join Test Case # 1 - Valid case + + Test Steps: + 1. Open instances for NUT and peer, peer invites NUT and starts instance. + 2. NUT consumes the invitation generated by peer + + Expected Result: + NUT joins peer using the invitation generated. +*/ +static void test_case_meshlink_join_01(void **state) { + (void) state; + char nut_confbase[PATH_MAX]; + char peer_confbase[PATH_MAX]; + create_path(nut_confbase, NUT, 1); + create_path(peer_confbase, PEER, 1); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open both NUT and peer node instance, invite and join NUT with peer node. + + meshlink_handle_t *mesh_peer = meshlink_open(peer_confbase, PEER, TEST_MESHLINK_JOIN, + DEV_CLASS_STATIONARY); + assert_non_null(mesh_peer); + meshlink_set_inviter_commits_first(mesh_peer, true); + meshlink_set_node_status_cb(mesh_peer, meshlink_node_reachable_status_cb); + char *invitation = meshlink_invite(mesh_peer, NULL, NUT); + assert_non_null(invitation); + assert_true(meshlink_start(mesh_peer)); + + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_JOIN, + DEV_CLASS_STATIONARY); + assert_non_null(mesh); + meshlink_set_inviter_commits_first(mesh, true); + meshlink_set_node_status_cb(mesh, meshlink_node_reachable_status_cb); + assert_true(meshlink_join(mesh, invitation)); + free(invitation); + + meshlink_node_t *peer_handle = meshlink_get_node(mesh, PEER); + assert_non_null(peer_handle); + meshlink_node_t *nut_handle = meshlink_get_node(mesh_peer, NUT); + assert_non_null(nut_handle); + + // Bring nodes online. + + set_sync_flag(&peer_reachable_status_cond, false); + set_sync_flag(&nut_reachable_status_cond, false); + assert_true(meshlink_start(mesh)); + assert_true(wait_sync_flag(&peer_reachable_status_cond, 60)); + assert_true(peer_reachable_status); + assert_true(wait_sync_flag(&nut_reachable_status_cond, 60)); + assert_true(nut_reachable_status); + + // Cleanup + + meshlink_close(mesh); + meshlink_close(mesh_peer); + assert_true(meshlink_destroy(nut_confbase)); + assert_true(meshlink_destroy(peer_confbase)); + return; +} + +/* Test Steps for meshlink_join Test Case # 2 - Invalid case + + Test Steps: + 1. Call meshlink_join with NULL as mesh handler or node name argument. + + Expected Result: + NUT joining fails when NULL is passed as mesh handle or node name argument +*/ +static void test_case_meshlink_join_02(void **state) { + (void) state; + char nut_confbase[PATH_MAX]; + char peer_confbase[PATH_MAX]; + create_path(nut_confbase, NUT, 2); + create_path(peer_confbase, PEER, 2); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open both NUT and peer node instance, invite and join NUT with peer node. + + meshlink_handle_t *mesh_peer = meshlink_open(peer_confbase, PEER, TEST_MESHLINK_JOIN, + DEV_CLASS_STATIONARY); + assert_non_null(mesh_peer); + meshlink_set_node_status_cb(mesh_peer, meshlink_node_reachable_status_cb); + char *invitation = meshlink_invite(mesh_peer, NULL, NUT); + assert_non_null(invitation); + assert_true(meshlink_start(mesh_peer)); + + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_JOIN, + DEV_CLASS_STATIONARY); + assert_non_null(mesh); + meshlink_set_node_status_cb(mesh, meshlink_node_reachable_status_cb); + + // meshlink_join called with NULL as mesh handle and with valid invitation + + assert_int_equal(meshlink_join(NULL, invitation), false); + assert_int_equal(meshlink_join(mesh, NULL), false); + + // Cleanup + + free(invitation); + meshlink_close(mesh); + meshlink_close(mesh_peer); + assert_true(meshlink_destroy(nut_confbase)); + assert_true(meshlink_destroy(peer_confbase)); + return; +} + +/* Test Steps for meshlink_join Test Case # 3 - Persistence testing around inviter + + Test steps and scenarios: + 1. Open Node-Under-Test (NUT) and invite peer node and close it's instance. + Spawn a process which waits for the peer node to join and raises SIGINT if the + appropriate callback is received (on the other hand the test suite opens and joins + the peer node with NUT in the forked process). + Expected Result: + NUT joins peer successfully + + +*/ +static void test_case_meshlink_join_03(void **state) { + (void) state; + pid_t pid; + int pid_status; + char nut_confbase[PATH_MAX]; + char peer_confbase[PATH_MAX]; + create_path(nut_confbase, NUT, 3); + create_path(peer_confbase, PEER, 3); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open NUT node instance and invite peer node. Close NUT node instance. + + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_JOIN, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + char *invitation = meshlink_invite(mesh, NULL, PEER); + meshlink_close(mesh); + + // Set the SIGUSR2 signal handler with handler that signal the condition to the test suite + + sighandler_t usr2sighandler = signal(SIGUSR2, nut_started_user_signal_handler); + assert_int_not_equal(usr2sighandler, SIG_ERR); + + // Fork a new process and run NUT in it which just waits for the peer node reachable status callback + // and terminates the process immediately. + + pid = fork(); + assert_int_not_equal(pid, -1); + + if(!pid) { + assert(signal(SIGUSR2, SIG_DFL) != SIG_ERR); + + mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_JOIN, DEV_CLASS_STATIONARY); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb); + meshlink_set_node_status_cb(mesh, meshlink_node_reachable_status_cb); + + set_sync_flag(&peer_reachable_status_cond, false); + assert(meshlink_start(mesh)); + + assert(kill(getppid(), SIGUSR2) != -1); + + assert(wait_sync_flag(&peer_reachable_status_cond, 60)); + assert(peer_reachable_status); + + raise(SIGINT); + } + + // Open peer node instance and join with the invitation obtained. + + meshlink_handle_t *mesh_peer = meshlink_open(peer_confbase, PEER, TEST_MESHLINK_JOIN, + DEV_CLASS_STATIONARY); + assert_non_null(mesh_peer); + + // Wait for the started signal from NUT and reset the previous SIGUSR2 signal handler + + assert_true(wait_sync_flag(&nut_started_status_cond, 60)); + assert_int_not_equal(signal(SIGUSR2, usr2sighandler), SIG_ERR); + + assert_true(meshlink_join(mesh_peer, invitation)); + assert_true(meshlink_start(mesh_peer)); + + // Wait for child exit and verify which signal terminated it + + assert_int_not_equal(waitpid(pid, &pid_status, 0), -1); + assert_int_equal(WIFSIGNALED(pid_status), true); + assert_int_equal(WTERMSIG(pid_status), SIGINT); + + // Reopen the NUT instance in the same test suite + + mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_JOIN, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + + assert_non_null(meshlink_get_node(mesh, PEER)); + + // Cleanup + + meshlink_close(mesh); + meshlink_close(mesh_peer); + assert_true(meshlink_destroy(nut_confbase)); + assert_true(meshlink_destroy(peer_confbase)); + return; +} + +/* Test Steps for meshlink_get_node_reachability Test Case # 4 - Persistence testing around invitee + + Test steps and scenarios: + 1. Open peer node instance, invite NUT and start peer node. Spawn a new process in + which it opens and joins the NUT with peer node. + Reopen NUT instance in the test suite process and verify peer is joined. + Expected Result: + NUT joins peer successfully + +*/ +static void test_case_meshlink_join_04(void **state) { + (void) state; + pid_t pid; + int pid_status; + char nut_confbase[PATH_MAX]; + char peer_confbase[PATH_MAX]; + create_path(nut_confbase, NUT, 4); + create_path(peer_confbase, PEER, 4); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open peer node instance and invite NUT. + + meshlink_handle_t *mesh_peer = meshlink_open(peer_confbase, PEER, TEST_MESHLINK_JOIN, + DEV_CLASS_STATIONARY); + assert_int_not_equal(mesh_peer, NULL); + char *invitation = meshlink_invite(mesh_peer, NULL, NUT); + assert_non_null(invitation); + + assert_true(meshlink_start(mesh_peer)); + + // Fork a new process in which NUT is joins with the peer node and raises SIGINT to terminate. + + pid = fork(); + assert_int_not_equal(pid, -1); + + if(!pid) { + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_JOIN, DEV_CLASS_STATIONARY); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb); + + assert(meshlink_join(mesh, invitation)); + + raise(SIGINT); + } + + // Wait for child exit and verify which signal terminated it + + assert_int_not_equal(waitpid(pid, &pid_status, 0), -1); + assert_int_equal(WIFSIGNALED(pid_status), true); + assert_int_equal(WTERMSIG(pid_status), SIGINT); + + // Reopen the NUT instance in the same test suite + + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_JOIN, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + + assert_non_null(meshlink_get_node(mesh, PEER)); + + // Cleanup + + meshlink_close(mesh); + meshlink_close(mesh_peer); + assert_true(meshlink_destroy(nut_confbase)); + assert_true(meshlink_destroy(peer_confbase)); + return; +} + +static void nop_stage(bool stage) { + (void)stage; + return; +} + +static void debug_probe(bool stage) { + (void)stage; + raise(SIGINT); + return; +} + +/* Test Steps for meshlink_get_node_reachability Test Case # 5 - Test the invitee committing first scenario + + Test steps and scenarios: + 1. Open peer node instance, invite NUT and start peer node. Enable the debug probe, Spawn a new process in + which it opens and joins the NUT with peer node which terminates the NUT while joining. + Reopen NUT instance in the test suite process and verify peer is joined. + Expected Result: + NUT(invitee) commits the config file(s) first but peer is unaware of NUT. + +*/ +static void test_case_meshlink_join_05(void **state) { + (void) state; + pid_t pid; + int pid_status; + char nut_confbase[PATH_MAX]; + char peer_confbase[PATH_MAX]; + create_path(nut_confbase, NUT, 5); + create_path(peer_confbase, PEER, 5); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + assert(signal(SIGINT, SIG_DFL) != SIG_ERR); + assert(signal(SIGABRT, SIG_DFL) != SIG_ERR); + + // Set debug_probe callback + + devtool_set_inviter_commits_first = debug_probe; + + // Open peer node instance and invite NUT. + + meshlink_handle_t *mesh_peer = meshlink_open(peer_confbase, PEER, TEST_MESHLINK_JOIN, + DEV_CLASS_STATIONARY); + assert_int_not_equal(mesh_peer, NULL); + meshlink_set_inviter_commits_first(mesh_peer, false); + char *invitation = meshlink_invite(mesh_peer, NULL, NUT); + assert_non_null(invitation); + + assert_true(meshlink_start(mesh_peer)); + + // Fork a new process in which NUT is joins with the peer node and raises SIGINT to terminate. + + pid = fork(); + assert_int_not_equal(pid, -1); + + if(!pid) { + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_JOIN, DEV_CLASS_STATIONARY); + assert(mesh); + meshlink_set_inviter_commits_first(mesh_peer, false); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb); + + assert_true(meshlink_join(mesh, invitation)); + + raise(SIGABRT); + } + + // Wait for child exit and verify which signal terminated it + printf("\n"); + assert_int_not_equal(waitpid(pid, &pid_status, 0), -1); + assert_int_equal(WIFSIGNALED(pid_status), true); + assert_int_equal(WTERMSIG(pid_status), SIGINT); + + // Reopen the NUT instance in the same test suite + + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_JOIN, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + + // Invitee committed host config file but invitee should not + + assert_non_null(meshlink_get_node(mesh, PEER)); + assert_null(meshlink_get_node(mesh_peer, NUT)); + + // Cleanup + + free(invitation); + meshlink_close(mesh); + meshlink_close(mesh_peer); + assert_true(meshlink_destroy(nut_confbase)); + assert_true(meshlink_destroy(peer_confbase)); + + devtool_set_inviter_commits_first = nop_stage; + return; +} + +/* Test Steps for meshlink_get_node_reachability Test Case # 6 - Test the inviter committing first scenario + + Test steps and scenarios: + 1. Open NUT node instance, invite peer and close the instance. Enable the debug probe, Spawn a new process in + which it starts the NUT instance. At the parents/test vector thread wait for the signal that NUT raises after starting + and join peer with NUT. NUT terminates in debug probe after committing into the disk + Reopen NUT instance in the test suite process and verify peer is joined. + Expected Result: + NUT(inviter) commits the config file(s) first but peer is unaware of NUT. + +*/ +static void test_case_meshlink_join_06(void **state) { + (void) state; + pid_t pid; + int pid_status; + char nut_confbase[PATH_MAX]; + char peer_confbase[PATH_MAX]; + create_path(nut_confbase, NUT, 6); + create_path(peer_confbase, PEER, 6); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + assert(signal(SIGINT, SIG_DFL) != SIG_ERR); + assert(signal(SIGABRT, SIG_DFL) != SIG_ERR); + + // Set debug_probe callback + + devtool_set_inviter_commits_first = debug_probe; + + // Open NUT node instance and invite peer node. Close NUT node instance. + + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_JOIN, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + meshlink_set_inviter_commits_first(mesh, true); + char *invitation = meshlink_invite(mesh, NULL, PEER); + meshlink_close(mesh); + + // Set the SIGUSR2 signal handler with handler that signal the condition to the test suite + + sighandler_t usr2sighandler = signal(SIGUSR2, nut_started_user_signal_handler); + assert_int_not_equal(usr2sighandler, SIG_ERR); + set_sync_flag(&peer_reachable_status_cond, false); + + // Fork a new process and run NUT in it which just waits for the peer node reachable status callback + // and terminates the process immediately. + + pid = fork(); + assert_int_not_equal(pid, -1); + + if(!pid) { + assert(signal(SIGUSR2, SIG_DFL) != SIG_ERR); + + mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_JOIN, DEV_CLASS_STATIONARY); + assert(mesh); + meshlink_set_inviter_commits_first(mesh, true); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb); + meshlink_set_node_status_cb(mesh, meshlink_node_reachable_status_cb); + + assert(meshlink_start(mesh)); + + assert(kill(getppid(), SIGUSR2) != -1); + + sleep(10); + + raise(SIGABRT); + } + + // Open peer node instance and join with the invitation obtained. + + meshlink_handle_t *mesh_peer = meshlink_open(peer_confbase, PEER, TEST_MESHLINK_JOIN, + DEV_CLASS_STATIONARY); + assert_non_null(mesh_peer); + meshlink_set_inviter_commits_first(mesh_peer, true); + + // Wait for the started signal from NUT and reset the previous SIGUSR2 signal handler + + assert_true(wait_sync_flag(&nut_started_status_cond, 60)); + assert_int_not_equal(signal(SIGUSR2, usr2sighandler), SIG_ERR); + + assert_false(meshlink_join(mesh_peer, invitation)); + + // Wait for child exit and verify which signal terminated it + + assert_int_not_equal(waitpid(pid, &pid_status, 0), -1); + assert_int_equal(WIFSIGNALED(pid_status), true); + assert_int_equal(WTERMSIG(pid_status), SIGINT); + + // Reopen the NUT instance in the same test suite + + mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_JOIN, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + + // Inviter should first commit config file(s) into the disk + + assert_null(meshlink_get_node(mesh_peer, NUT)); + assert_non_null(meshlink_get_node(mesh, PEER)); + + // Cleanup + + free(invitation); + meshlink_close(mesh); + meshlink_close(mesh_peer); + assert_true(meshlink_destroy(nut_confbase)); + assert_true(meshlink_destroy(peer_confbase)); + devtool_set_inviter_commits_first = nop_stage; + return; +} + +/* Test Steps for meshlink_join Test Case # 7 - Inviter sets that invitee should commit first, + even invitee sets that inviter should commit first. + + Test Steps: + 1. Open instances for NUT and peer, peer invites NUT and starts instance. + Both the instances sets meshlink_set_inviter_commits_first API mutually exclusively + NUT tries to consume the invitation generated by peer + + Expected Result: + NUT fails to join peer using the invitation generated. +*/ +static void test_case_meshlink_join_07(void **state) { + (void) state; + char nut_confbase[PATH_MAX]; + char peer_confbase[PATH_MAX]; + create_path(nut_confbase, NUT, 7); + create_path(peer_confbase, PEER, 7); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open both NUT and peer node instance, invite and join NUT with peer node. + + meshlink_handle_t *mesh_peer = meshlink_open(peer_confbase, PEER, TEST_MESHLINK_JOIN, + DEV_CLASS_STATIONARY); + assert_non_null(mesh_peer); + meshlink_set_inviter_commits_first(mesh_peer, false); + meshlink_set_node_status_cb(mesh_peer, meshlink_node_reachable_status_cb); + char *invitation = meshlink_invite(mesh_peer, NULL, NUT); + assert_non_null(invitation); + assert_true(meshlink_start(mesh_peer)); + + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_JOIN, + DEV_CLASS_STATIONARY); + assert_non_null(mesh); + meshlink_set_inviter_commits_first(mesh, true); + meshlink_set_node_status_cb(mesh, meshlink_node_reachable_status_cb); + assert_false(meshlink_join(mesh, invitation)); + free(invitation); + + meshlink_node_t *peer_handle = meshlink_get_node(mesh, PEER); + assert_null(peer_handle); + meshlink_node_t *nut_handle = meshlink_get_node(mesh_peer, NUT); + assert_null(nut_handle); + + // Cleanup + + meshlink_close(mesh); + meshlink_close(mesh_peer); + assert_true(meshlink_destroy(nut_confbase)); + assert_true(meshlink_destroy(peer_confbase)); + return; +} + +/* Test Steps for meshlink_join Test Case # 8 - Inviter sets that it should commit first, + even invitee sets that it should commit first + + Test Steps: + 1. Open instances for NUT and peer, peer invites NUT and starts instance. + Both the instances sets meshlink_set_inviter_commits_first API mutually exclusively + NUT tries to consume the invitation generated by peer + + Expected Result: + NUT fails to join peer using the invitation generated. +*/ +static void test_case_meshlink_join_08(void **state) { + (void) state; + char nut_confbase[PATH_MAX]; + char peer_confbase[PATH_MAX]; + create_path(nut_confbase, NUT, 8); + create_path(peer_confbase, PEER, 8); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open both NUT and peer node instance, invite and join NUT with peer node. + + meshlink_handle_t *mesh_peer = meshlink_open(peer_confbase, PEER, TEST_MESHLINK_JOIN, + DEV_CLASS_STATIONARY); + assert_non_null(mesh_peer); + meshlink_set_inviter_commits_first(mesh_peer, true); + meshlink_set_node_status_cb(mesh_peer, meshlink_node_reachable_status_cb); + char *invitation = meshlink_invite(mesh_peer, NULL, NUT); + assert_non_null(invitation); + assert_true(meshlink_start(mesh_peer)); + + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_JOIN, + DEV_CLASS_STATIONARY); + assert_non_null(mesh); + meshlink_set_inviter_commits_first(mesh, false); + meshlink_set_node_status_cb(mesh, meshlink_node_reachable_status_cb); + assert_false(meshlink_join(mesh, invitation)); + free(invitation); + + meshlink_node_t *peer_handle = meshlink_get_node(mesh, PEER); + assert_null(peer_handle); + meshlink_node_t *nut_handle = meshlink_get_node(mesh_peer, NUT); + assert_null(nut_handle); + + // Cleanup + + meshlink_close(mesh); + meshlink_close(mesh_peer); + assert_true(meshlink_destroy(nut_confbase)); + assert_true(meshlink_destroy(peer_confbase)); + return; +} + +/* Test Steps for meshlink_join Test Case # 9 - Invitee already started its instance + + Test Steps: + 1. Open instances for NUT and peer, peer invites NUT and both the instances starts their instances. + NUT tries to join the peer with the generated invitation. + + Expected Result: + NUT fails to join peer using the invitation generated. +*/ +static void test_case_meshlink_join_09(void **state) { + (void) state; + char nut_confbase[PATH_MAX]; + char peer_confbase[PATH_MAX]; + create_path(nut_confbase, NUT, 9); + create_path(peer_confbase, PEER, 9); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open both NUT and peer node instance, invite and join NUT with peer node. + + meshlink_handle_t *mesh_peer = meshlink_open(peer_confbase, PEER, TEST_MESHLINK_JOIN, + DEV_CLASS_STATIONARY); + assert_non_null(mesh_peer); + meshlink_set_node_status_cb(mesh_peer, meshlink_node_reachable_status_cb); + char *invitation = meshlink_invite(mesh_peer, NULL, NUT); + assert_non_null(invitation); + assert_true(meshlink_start(mesh_peer)); + + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_JOIN, + DEV_CLASS_STATIONARY); + assert_non_null(mesh); + meshlink_set_node_status_cb(mesh, meshlink_node_reachable_status_cb); + + assert_true(meshlink_start(mesh)); + + assert_false(meshlink_join(mesh, invitation)); + free(invitation); + + meshlink_node_t *peer_handle = meshlink_get_node(mesh, PEER); + assert_null(peer_handle); + meshlink_node_t *nut_handle = meshlink_get_node(mesh_peer, NUT); + assert_null(nut_handle); + + // Cleanup + + meshlink_close(mesh); + meshlink_close(mesh_peer); + assert_true(meshlink_destroy(nut_confbase)); + assert_true(meshlink_destroy(peer_confbase)); + return; +} + +/* Test Steps for meshlink_join Test Case # 10 - Invitee already joined in a mesh + + Test Steps: + 1. Open instances for NUT, peer2 and peer, peer invites NUT. Peer2 and NUT both mutually imports data + i.e, both formed or joined the mesh. + NUT tries to join the peer with the generated invitation. + + Expected Result: + NUT fails to join peer using the invitation generated. +*/ +static void test_case_meshlink_join_10(void **state) { + (void) state; + char nut_confbase[PATH_MAX]; + char peer_confbase[PATH_MAX]; + char peer_confbase2[PATH_MAX]; + create_path(nut_confbase, NUT, 10); + create_path(peer_confbase, PEER, 10); + create_path(peer_confbase2, PEER2, 10); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open both NUT and peer node instance, invite and join NUT with peer node. + + meshlink_handle_t *mesh_peer = meshlink_open(peer_confbase, PEER, TEST_MESHLINK_JOIN, + DEV_CLASS_STATIONARY); + assert_non_null(mesh_peer); + meshlink_set_node_status_cb(mesh_peer, meshlink_node_reachable_status_cb); + char *invitation = meshlink_invite(mesh_peer, NULL, NUT); + assert_non_null(invitation); + assert_true(meshlink_start(mesh_peer)); + + meshlink_handle_t *mesh_peer2 = meshlink_open(peer_confbase2, PEER2, TEST_MESHLINK_JOIN, + DEV_CLASS_STATIONARY); + assert_non_null(mesh_peer2); + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_JOIN, + DEV_CLASS_STATIONARY); + assert_non_null(mesh); + meshlink_set_node_status_cb(mesh, meshlink_node_reachable_status_cb); + + char *data = meshlink_export(mesh); + assert_non_null(data); + assert_true(meshlink_import(mesh_peer2, data)); + free(data); + data = meshlink_export(mesh_peer2); + assert_non_null(data); + assert_true(meshlink_import(mesh, data)); + free(data); + + assert_true(meshlink_start(mesh)); + + assert_false(meshlink_join(mesh, invitation)); + free(invitation); + + meshlink_node_t *peer_handle = meshlink_get_node(mesh, PEER); + assert_null(peer_handle); + meshlink_node_t *nut_handle = meshlink_get_node(mesh_peer, NUT); + assert_null(nut_handle); + + // Cleanup + + meshlink_close(mesh); + meshlink_close(mesh_peer); + assert_true(meshlink_destroy(nut_confbase)); + assert_true(meshlink_destroy(peer_confbase)); + return; +} + +int test_meshlink_join(void) { + const struct CMUnitTest blackbox_join_tests[] = { + cmocka_unit_test(test_case_meshlink_join_01), + cmocka_unit_test(test_case_meshlink_join_02), + cmocka_unit_test(test_case_meshlink_join_03), + cmocka_unit_test(test_case_meshlink_join_04), + cmocka_unit_test(test_case_meshlink_join_05), + cmocka_unit_test(test_case_meshlink_join_06), + cmocka_unit_test(test_case_meshlink_join_07), + cmocka_unit_test(test_case_meshlink_join_08), + cmocka_unit_test(test_case_meshlink_join_09), + cmocka_unit_test(test_case_meshlink_join_10) + }; + total_tests += sizeof(blackbox_join_tests) / sizeof(blackbox_join_tests[0]); + + int failed = cmocka_run_group_tests(blackbox_join_tests, NULL, NULL); + + return failed; +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_join.h b/test/blackbox/run_blackbox_tests/test_cases_join.h new file mode 100644 index 0000000..c10af0f --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_join.h @@ -0,0 +1,29 @@ +#ifndef TEST_CASES_JOIN_H +#define TEST_CASES_JOIN_H + +/* + test_cases_join.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_join(void); +extern int total_tests; + + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_key_rotation.c b/test/blackbox/run_blackbox_tests/test_cases_key_rotation.c new file mode 100644 index 0000000..8987d7c --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_key_rotation.c @@ -0,0 +1,503 @@ +/* + test_cases_key_rotation.c -- Execution of specific meshlink black box test cases + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "execute_tests.h" +#include "test_cases_key_rotation.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "../../../src/devtools.h" +#include "../../utils.h" + +static void test_case_key_rotation_01(void **state); +static bool test_key_rotation_01(void); +static void test_case_key_rotation_02(void **state); +static bool test_key_rotation_02(void); +static void test_case_key_rotation_03(void **state); +static bool test_key_rotation_03(void); +static void test_case_key_rotation_04(void **state); +static bool test_key_rotation_04(void); +static void test_case_key_rotation_05(void **state); +static bool test_key_rotation_05(void); + +/* Execute key rotation Test Case # 1 - Sanity test */ +static void test_case_key_rotation_01(void **state) { + execute_test(test_key_rotation_01, state); +} + +/* Test Steps for key rotation Test Case # 1 + + Test Steps: + 1. Open encrypted node instance, call encrypted rotate API with + invalid input parameters to the call. + + Expected Result: + Key rotate should fail when called with invalid parameters. +*/ +static bool test_key_rotation_01(void) { + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + assert(meshlink_destroy("encrypted_conf")); + + // Open a new meshlink instance. + + meshlink_handle_t *mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6); + assert_int_not_equal(mesh, NULL); + + // Pass invalid arguments + + bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, NULL, 5); + assert_int_equal(keyrotation_status, false); + + keyrotation_status = meshlink_encrypted_key_rotate(NULL, "newkey", 6); + assert_int_equal(keyrotation_status, false); + + keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 0); + assert_int_equal(keyrotation_status, false); + + // Cleanup + + meshlink_close(mesh); + assert(meshlink_destroy("encrypted_conf")); + + return true; +} + +/* Execute key rotation Test Case # 2 - Sanity test */ +static void test_case_key_rotation_02(void **state) { + execute_test(test_key_rotation_02, state); +} + +/* Test Steps for key rotation Test Case # 2 + + Test Steps: + 1. Open encrypted node instance, rotate it's key with a newkey and close the node. + 2. Reopen the encrypted node instance with the newkey + + Expected Result: + Opening encrypted node instance should succeed when tried to open with newkey that's + been changed to new by key rotate API. +*/ +static bool test_key_rotation_02(void) { + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + assert(meshlink_destroy("encrypted_conf")); + + // Open a new meshlink instance. + + meshlink_handle_t *mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6); + assert_int_not_equal(mesh, NULL); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb); + + // Set a new port for the mesh + + int port = 0x1000 + (rand() & 0x7fff); + assert_int_equal(meshlink_set_port(mesh, port), true); + + // Key rotate the encrypted_conf storage with new key + + bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 6); + assert_int_equal(keyrotation_status, true); + + meshlink_close(mesh); + + // Reopen the meshlink instance with the new key + + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6); + assert_int_not_equal(mesh, NULL); + + // Validate the port number that we changed in the last run. + + assert_int_equal(meshlink_get_port(mesh), port); + + // Cleanup + + meshlink_close(mesh); + assert(meshlink_destroy("encrypted_conf")); + + return true; +} + +/* Execute key rotation Test Case # 3 - Sanity test */ +static void test_case_key_rotation_03(void **state) { + execute_test(test_key_rotation_03, state); +} + +/* Test Steps for key rotation Test Case # 3 + + Test Steps: + 1. Open encrypted node instance, rotate it's key with a newkey and close the node. + 2. Reopen the encrypted node instance with the oldkey + + Expected Result: + Opening encrypted node instance should fail when tried to open with oldkey that's + been changed to new by key rotate API. +*/ +static bool test_key_rotation_03(void) { + assert(meshlink_destroy("encrypted_conf")); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open a new meshlink instance. + + meshlink_handle_t *mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6); + assert_int_not_equal(mesh, NULL); + + // Key rotate the encrypted_conf storage with new key + + bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 6); + assert_int_equal(keyrotation_status, true); + + meshlink_close(mesh); + + // Reopen the meshlink instance with the new key + + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6); + assert_int_equal(mesh, NULL); + + // Cleanup + + assert(meshlink_destroy("encrypted_conf")); + + return true; +} + +/* Execute key rotation Test Case # 4 - Sanity test */ +static void test_case_key_rotation_04(void **state) { + execute_test(test_key_rotation_04, state); +} + +/* Test Steps for key rotation Test Case # 4 + Verify whether key rotation API gracefully handles invitations porting from + old key to new key. + + Test Steps: + 1. Open foo node instance and generate invitations for peer and bar. + 2. Do key rotation with newkey and verify invitation timestamps post key rotation. + 3. Change timestamp of peer key to expire and Open instances of foo, bar and peer nodes + and try to join bar and peer node. + + Expected Result: + Key rotation API should never change the any file status attributes of an invitation file. +*/ +static bool test_key_rotation_04(void) { + meshlink_handle_t *mesh; + meshlink_handle_t *mesh1; + meshlink_handle_t *mesh2; + struct dirent *ent; + DIR *d; + char invitation_path_buff[500]; + struct stat temp_stat; + struct stat peer_stat; + struct utimbuf timebuf; + bool join_status; + char *invitations_directory_path = "encrypted_conf/current/invitations/"; + + assert(meshlink_destroy("encrypted_conf")); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open a new meshlink instance. + + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6); + assert_int_not_equal(mesh, NULL); + + // Generate invitations + + char *invitation1 = meshlink_invite(mesh, NULL, "peer"); + assert_int_not_equal(invitation1, NULL); + + // Read the peer invitation file status structure + + strcpy(invitation_path_buff, invitations_directory_path); + d = opendir(invitation_path_buff); + assert(d); + + while((ent = readdir(d)) != NULL) { + if(ent->d_name[0] == '.') { + continue; + } + + strcpy(invitation_path_buff, invitations_directory_path); + strcat(invitation_path_buff, ent->d_name); + assert(stat(invitation_path_buff, &temp_stat) != -1); + + if((temp_stat.st_mode & S_IFMT) == S_IFREG) { + break; + } + } + + assert(ent); + + closedir(d); + + char *invitation2 = meshlink_invite(mesh, NULL, "bar"); + assert_int_not_equal(invitation2, NULL); + + // Key rotate the encrypted_conf storage with new key + + bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 6); + assert_int_equal(keyrotation_status, true); + + meshlink_close(mesh); + + // Compare invitation file timestamps of old key with new key + + assert(stat(invitation_path_buff, &peer_stat) != -1); + assert_int_equal(peer_stat.st_mtime, temp_stat.st_mtime); + + // Change timestamp for @ peer @ node invitation + + timebuf.actime = peer_stat.st_atime; + timebuf.modtime = peer_stat.st_mtime - 604805; // > 1 week + + assert(utime(invitation_path_buff, &timebuf) != -1); + + + // Reopen the meshlink instance with the new key + + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6); + assert_int_not_equal(mesh, NULL); + + mesh1 = meshlink_open("encrypted_conf.1", "peer", "encrypted", DEV_CLASS_BACKBONE); + assert_int_not_equal(mesh1, NULL); + + mesh2 = meshlink_open("encrypted_conf.2", "bar", "encrypted", DEV_CLASS_BACKBONE); + assert_int_not_equal(mesh2, NULL); + + assert(meshlink_start(mesh)); + + join_status = meshlink_join(mesh1, invitation1); + assert_int_equal(join_status, false); + + join_status = meshlink_join(mesh2, invitation2); + assert_int_equal(join_status, true); + + // Cleanup + + free(invitation1); + free(invitation2); + meshlink_close(mesh); + meshlink_close(mesh1); + meshlink_close(mesh2); + assert(meshlink_destroy("encrypted_conf")); + assert(meshlink_destroy("encrypted_conf.1")); + assert(meshlink_destroy("encrypted_conf.2")); + + return true; +} + +/* Execute key rotation Test Case # 5 - Atomicity test */ +static void test_case_key_rotation_05(void **state) { + execute_test(test_key_rotation_05, state); +} + +static int break_stage; + +static void nop_stage(int stage) { + (void)stage; + + return; +} + +static void debug_probe(int stage) { + + // Terminate the node at the specified stage (by @ break_stage @ ) + if(stage == break_stage) { + raise(SIGINT); + } else if((break_stage < 1) || (break_stage > 3)) { + fprintf(stderr, "INVALID stage break\n"); + raise(SIGABRT); + } + + return; +} + +/* Test Steps for key rotation Test Case # 5 + Debug all stages of key rotate API and verify it's atomicity + + Test Steps: + 1. Open foo node instance. + 2. In a loop break meshlink node instance at each stage incrementally + in a fork process + 3. Reopen node instance post termination. + + Expected Result: + Terminating node instance when meshlink_encrypted_key_rotate function called + at any stage should give atomic result when reopened. +*/ +static bool test_key_rotation_05(void) { + pid_t pid; + int status; + meshlink_handle_t *mesh; + assert(meshlink_destroy("encrypted_conf")); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + assert(signal(SIGINT, SIG_DFL) != SIG_ERR); + assert(signal(SIGABRT, SIG_DFL) != SIG_ERR); + + // Set debug_probe callback + + devtool_keyrotate_probe = debug_probe; + int new_port = 12000; + int pipefd[2]; + + // incrementally debug meshlink_encrypted_key_rotate API atomicity + + for(break_stage = 1; break_stage <= 3; break_stage += 1) { + fprintf(stderr, "Debugging stage %d\n", break_stage); + assert(meshlink_destroy("encrypted_conf")); + + assert(pipe(pipefd) != -1); + + pid = fork(); + assert(pid != -1); + + if(!pid) { + close(pipefd[0]); + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb); + meshlink_enable_discovery(mesh, false); + + assert(meshlink_set_port(mesh, new_port)); + + char *invitation = meshlink_invite(mesh, NULL, "bar"); + assert(invitation); + + assert(write(pipefd[1], invitation, strlen(invitation) + 1) != -1); + + assert(meshlink_encrypted_key_rotate(mesh, "newkey", 6)); + raise(SIGABRT); + } + + close(pipefd[1]); + + // Wait for child exit and verify which signal terminated it + + assert(waitpid(pid, &status, 0) != -1); + assert_int_equal(WIFSIGNALED(status), true); + assert_int_equal(WTERMSIG(status), SIGINT); + + // Reopen the node with invalid key other than old and new key should fail and should not affect + // the existing confbase + + fprintf(stderr, "Opening mesh with invalid key\n"); + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "invalidkey", 9); + assert_int_equal(mesh, NULL); + + // Reopen the node with the "newkey", if it failed to open with "newkey" then + // opening with the "oldkey" should succeed + + fprintf(stderr, "Opening mesh with new-key\n"); + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6); + + if(!mesh) { + fprintf(stderr, "Opening mesh with new-key failed trying to open with old-key\n"); + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6); + assert_int_not_equal(mesh, NULL); + } + + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb); + meshlink_enable_discovery(mesh, false); + + // Verify the newly set port and generated invitation + + int get_port = meshlink_get_port(mesh); + assert_int_equal(get_port, new_port); + + char invitation[200]; + assert(read(pipefd[0], invitation, sizeof(invitation)) != -1); + + assert(meshlink_start(mesh)); + + assert(meshlink_destroy("encrypted_conf.1")); + + meshlink_handle_t *mesh2 = meshlink_open("encrypted_conf.1", "bar", "bar", DEV_CLASS_BACKBONE); + assert(mesh2); + + meshlink_set_log_cb(mesh2, MESHLINK_DEBUG, log_cb); + meshlink_enable_discovery(mesh2, false); + + assert_int_equal(meshlink_join(mesh2, invitation), true); + + // cleanup + + meshlink_close(mesh); + meshlink_close(mesh2); + + close(pipefd[0]); + } + + // Cleanup + + assert(meshlink_destroy("encrypted_conf")); + assert(meshlink_destroy("encrypted_conf.1")); + devtool_keyrotate_probe = nop_stage; + return true; +} + +int test_meshlink_encrypted_key_rotation(void) { + /* State structures for key rotation Test Cases */ + black_box_state_t test_case_key_rotation_01_state = { + .test_case_name = "test_case_key_rotation_01", + }; + black_box_state_t test_case_key_rotation_02_state = { + .test_case_name = "test_case_key_rotation_02", + }; + black_box_state_t test_case_key_rotation_03_state = { + .test_case_name = "test_case_key_rotation_03", + }; + black_box_state_t test_case_key_rotation_04_state = { + .test_case_name = "test_case_key_rotation_04", + }; + black_box_state_t test_case_key_rotation_05_state = { + .test_case_name = "test_case_key_rotation_05", + }; + + const struct CMUnitTest blackbox_status_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_01, NULL, NULL, + (void *)&test_case_key_rotation_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_02, NULL, NULL, + (void *)&test_case_key_rotation_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_03, NULL, NULL, + (void *)&test_case_key_rotation_03_state), + cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_04, NULL, NULL, + (void *)&test_case_key_rotation_04_state), + cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_05, NULL, NULL, + (void *)&test_case_key_rotation_05_state), + }; + total_tests += sizeof(blackbox_status_tests) / sizeof(blackbox_status_tests[0]); + + return cmocka_run_group_tests(blackbox_status_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_key_rotation.h b/test/blackbox/run_blackbox_tests/test_cases_key_rotation.h new file mode 100644 index 0000000..1562b24 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_key_rotation.h @@ -0,0 +1,26 @@ +#ifndef TEST_CASES_KEY_ROTATION_H +#define TEST_CASES_KEY_ROTATION_H + +/* + test_cases_key_rotation.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +extern int test_meshlink_encrypted_key_rotation(void); +extern int total_tests; + +#endif // TEST_CASES_KEY_ROTATION_H diff --git a/test/blackbox/run_blackbox_tests/test_cases_open.c b/test/blackbox/run_blackbox_tests/test_cases_open.c new file mode 100644 index 0000000..a1c1b1e --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_open.c @@ -0,0 +1,355 @@ +/* + test_cases_open.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_open.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "../../utils.h" +#include +#include +#include +#include +#include +#include +#include +#include + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +#define NUT "nut" +#define PEER "peer" +#define TEST_MESHLINK_OPEN "test_open" +#define create_path(confbase, node_name, test_case_no) assert(snprintf(confbase, sizeof(confbase), TEST_MESHLINK_OPEN "_%ld_%s_%02d", (long) getpid(), node_name, test_case_no) > 0) + +static void test_case_mesh_open_01(void **state); +static bool test_steps_mesh_open_01(void); +static void test_case_mesh_open_02(void **state); +static bool test_steps_mesh_open_02(void); +static void test_case_mesh_open_03(void **state); +static bool test_steps_mesh_open_03(void); +static void test_case_mesh_open_04(void **state); +static bool test_steps_mesh_open_04(void); +static void test_case_mesh_open_05(void **state); +static bool test_steps_mesh_open_05(void); +static void test_case_mesh_open_06(void **state); +static bool test_steps_mesh_open_06(void); +static void test_case_mesh_open_07(void **state); +static bool test_steps_mesh_open_07(void); + +/* State structure for meshlink_open Test Case #1 */ +static black_box_state_t test_mesh_open_01_state = { + .test_case_name = "test_case_mesh_open_01", +}; + +/* State structure for meshlink_open Test Case #2 */ +static black_box_state_t test_mesh_open_02_state = { + .test_case_name = "test_case_mesh_open_02", +}; + +/* State structure for meshlink_open Test Case #3 */ +static black_box_state_t test_mesh_open_03_state = { + .test_case_name = "test_case_mesh_open_03", +}; + +/* State structure for meshlink_open Test Case #4 */ +static black_box_state_t test_mesh_open_04_state = { + .test_case_name = "test_case_mesh_open_04", +}; + +/* State structure for meshlink_open Test Case #5 */ +static black_box_state_t test_mesh_open_05_state = { + .test_case_name = "test_case_mesh_open_05", +}; + +/* State structure for meshlink_open Test Case #6 */ +static black_box_state_t test_mesh_open_06_state = { + .test_case_name = "test_case_mesh_open_06", +}; + +/* State structure for meshlink_open Test Case #7 */ +static black_box_state_t test_mesh_open_07_state = { + .test_case_name = "test_case_mesh_open_07", +}; + +/* Execute meshlink_open Test Case # 1*/ +static void test_case_mesh_open_01(void **state) { + execute_test(test_steps_mesh_open_01, state); +} + +/* Test Steps for meshlink_open Test Case # 1 + + Test Steps: + 1. Open the node instance using meshlink_open + + Expected Result: + meshlink_open API should successfully return a mesh handle. +*/ +static bool test_steps_mesh_open_01(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_handle_t *mesh = meshlink_open("open_conf", "foo", "test", DEV_CLASS_STATIONARY); + assert_int_not_equal(mesh, NULL); + + meshlink_close(mesh); + assert(meshlink_destroy("open_conf")); + return true; +} + +/* Execute meshlink_open Test Case # 2*/ +static void test_case_mesh_open_02(void **state) { + execute_test(test_steps_mesh_open_02, state); +} + +/* Test Steps for meshlink_open Test Case # 2 + + Test Steps: + 1. Open the node instance using meshlink_open with NULL as confbase argument + + Expected Result: + meshlink_open API should successfully report error by returning NULL pointer +*/ +static bool test_steps_mesh_open_02(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_handle_t *mesh = meshlink_open(NULL, "foo", "test", DEV_CLASS_STATIONARY); + assert_int_equal(mesh, NULL); + + return true; +} + +/* Execute meshlink_open Test Case # 3 */ +static void test_case_mesh_open_03(void **state) { + execute_test(test_steps_mesh_open_03, state); +} + +/* Test Steps for meshlink_open Test Case # 3 + + Test Steps: + 1. Open the node instance using meshlink_open with NULL as node name argument + + Expected Result: + meshlink_open API should successfully report error by returning NULL pointer +*/ +static bool test_steps_mesh_open_03(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_handle_t *mesh = meshlink_open("openconf", NULL, "test", DEV_CLASS_STATIONARY); + assert_int_equal(mesh, NULL); + + assert(meshlink_destroy("open_conf")); + return true; +} + +/* Execute meshlink_open Test Case # 4*/ +static void test_case_mesh_open_04(void **state) { + execute_test(test_steps_mesh_open_04, state); +} + +/* Test Steps for meshlink_open Test Case # 4 + + Test Steps: + 1. Open the node instance using meshlink_open with NULL as app name argument + + Expected Result: + meshlink_open API should successfully report error by returning NULL pointer +*/ +static bool test_steps_mesh_open_04(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_handle_t *mesh = meshlink_open("openconf", "foo", NULL, DEV_CLASS_STATIONARY); + assert_int_equal(mesh, NULL); + + assert(meshlink_destroy("open_conf")); + return true; +} + +/* Execute meshlink_open Test Case # 5*/ +static void test_case_mesh_open_05(void **state) { + execute_test(test_steps_mesh_open_05, state); +} + +/* Test Steps for meshlink_open Test Case # 5 + + Test Steps: + 1. Open the node instance using meshlink_open with invalid device class argument + + Expected Result: + meshlink_open API should successfully report error by returning NULL pointer +*/ +static bool test_steps_mesh_open_05(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_handle_t *mesh = meshlink_open("openconf", "foo", "test", -1); + assert_int_equal(mesh, NULL); + + assert(meshlink_destroy("open_conf")); + return true; +} + +/* Execute meshlink_open Test Case # 7 - Atomicity testing + Validate the meshlink_open behavior opened a new confbase and terminated immediately the open call. +*/ +static void test_case_mesh_open_06(void **state) { + execute_test(test_steps_mesh_open_06, state); +} + +static bool test_steps_mesh_open_06(void) { + bool status; + pid_t pid; + int pid_status; + char nut_confbase[PATH_MAX]; + create_path(nut_confbase, NUT, 6); + + // Fork a new process in which NUT opens it's instance and raises SIGINT to terminate. + + pid = fork(); + assert_int_not_equal(pid, -1); + + if(!pid) { + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_OPEN, DEV_CLASS_STATIONARY); + assert(mesh); + raise(SIGINT); + } + + // Wait for child exit and verify which signal terminated it + + assert_int_not_equal(waitpid(pid, &pid_status, 0), -1); + assert_int_equal(WIFSIGNALED(pid_status), true); + assert_int_equal(WTERMSIG(pid_status), SIGINT); + + // Reopen the NUT instance in the same test suite + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_OPEN, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + + // Validate parameters that were used to open meshlink instance. + + assert_int_equal(strcmp(mesh->name, NUT), 0); + meshlink_node_t *self = meshlink_get_self(mesh); + assert_int_equal(strcmp(self->name, NUT), 0); + assert_int_equal(meshlink_get_node_dev_class(mesh, self), DEV_CLASS_STATIONARY); + + // Cleanup + + meshlink_close(mesh); + assert_true(meshlink_destroy(nut_confbase)); + return true; +} + +/* Execute meshlink_open Test Case # 7 - Atomicity testing + Validate the meshlink_open behavior opened an existing confbase and terminated immediately the open call. +*/ +static void test_case_mesh_open_07(void **state) { + execute_test(test_steps_mesh_open_07, state); +} + +static bool test_steps_mesh_open_07(void) { + bool status; + pid_t pid; + int pid_status; + char nut_confbase[PATH_MAX]; + char peer_confbase[PATH_MAX]; + create_path(nut_confbase, NUT, 7); + create_path(peer_confbase, PEER, 7); + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_OPEN, DEV_CLASS_BACKBONE); + assert_non_null(mesh); + meshlink_handle_t *mesh_peer = meshlink_open(peer_confbase, PEER, TEST_MESHLINK_OPEN, DEV_CLASS_STATIONARY); + assert_non_null(mesh_peer); + + // Exporting and Importing mutually + char *export_data = meshlink_export(mesh); + assert_non_null(export_data); + assert_true(meshlink_import(mesh_peer, export_data)); + free(export_data); + export_data = meshlink_export(mesh_peer); + assert_non_null(export_data); + assert_true(meshlink_import(mesh, export_data)); + free(export_data); + + meshlink_close(mesh); + meshlink_close(mesh_peer); + + + // Fork a new process in which NUT reopens it's instance and raises SIGINT to terminate. + + pid = fork(); + assert_int_not_equal(pid, -1); + + if(!pid) { + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_OPEN, DEV_CLASS_BACKBONE); + assert(mesh); + raise(SIGINT); + } + + // Wait for child exit and verify which signal terminated it + + assert_int_not_equal(waitpid(pid, &pid_status, 0), -1); + assert_int_equal(WIFSIGNALED(pid_status), true); + assert_int_equal(WTERMSIG(pid_status), SIGINT); + + // Reopen the NUT instance in the same test suite + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_OPEN, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + + // Validate parameters that were used to open meshlink instance. + + assert_int_equal(strcmp(mesh->name, NUT), 0); + meshlink_node_t *self = meshlink_get_self(mesh); + assert_int_equal(strcmp(self->name, NUT), 0); + assert_int_equal(meshlink_get_node_dev_class(mesh, self), DEV_CLASS_STATIONARY); + + // Cleanup + + meshlink_close(mesh); + assert_true(meshlink_destroy(nut_confbase)); + assert_true(meshlink_destroy(peer_confbase)); + return true; +} + +int test_meshlink_open(void) { + const struct CMUnitTest blackbox_open_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_open_01, NULL, NULL, + (void *)&test_mesh_open_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_open_02, NULL, NULL, + (void *)&test_mesh_open_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_open_03, NULL, NULL, + (void *)&test_mesh_open_03_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_open_04, NULL, NULL, + (void *)&test_mesh_open_04_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_open_05, NULL, NULL, + (void *)&test_mesh_open_05_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_open_06, NULL, NULL, + (void *)&test_mesh_open_06_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_open_07, NULL, NULL, + (void *)&test_mesh_open_07_state) + + }; + total_tests += sizeof(blackbox_open_tests) / sizeof(blackbox_open_tests[0]); + + return cmocka_run_group_tests(blackbox_open_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_open.h b/test/blackbox/run_blackbox_tests/test_cases_open.h new file mode 100644 index 0000000..0c3f7d9 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_open.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_OPEN_H +#define TEST_CASES_OPEN_H + +/* + test_cases_open.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_open(void); +extern int total_tests; + +#endif // TEST_STEP_OPEN_H diff --git a/test/blackbox/run_blackbox_tests/test_cases_pmtu.c b/test/blackbox/run_blackbox_tests/test_cases_pmtu.c new file mode 100644 index 0000000..5867596 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_pmtu.c @@ -0,0 +1,159 @@ +/* + test_cases_pmtu.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_pmtu.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include + +static void test_case_mesh_pmtu_01(void **state); +static bool test_steps_mesh_pmtu_01(void); +static void test_case_mesh_pmtu_02(void **state); +static bool test_steps_mesh_pmtu_02(void); +static void test_case_mesh_pmtu_03(void **state); +static bool test_steps_mesh_pmtu_03(void); + +/* State structure for meshlink_get_pmtu Test Case #1 */ +static black_box_state_t test_mesh_pmtu_01_state = { + .test_case_name = "test_case_mesh_pmtu_01", +}; + +/* State structure for meshlink_get_pmtu Test Case #2 */ +static black_box_state_t test_mesh_pmtu_02_state = { + .test_case_name = "test_case_mesh_pmtu_02", +}; + +/* State structure for meshlink_get_pmtu Test Case #3 */ +static black_box_state_t test_mesh_pmtu_03_state = { + .test_case_name = "test_case_mesh_pmtu_03", +}; + +/* Execute meshlink_get_pmtu Test Case # 1 */ +static void test_case_mesh_pmtu_01(void **state) { + execute_test(test_steps_mesh_pmtu_01, state); +} + +/* Test Steps for meshlink_get_pmtu Test Case # 1 + + Test Steps: + 1. Create node instance & get self handle + 2. Obtain MTU size + + Expected Result: + meshlink_get_pmtu should return valid MTU size of a node +*/ +static bool test_steps_mesh_pmtu_01(void) { + meshlink_handle_t *mesh = meshlink_open("pmtu_conf", "foo", "test", DEV_CLASS_STATIONARY); + assert(mesh != NULL); + + assert(meshlink_start(mesh)); + meshlink_node_t *dest_node = meshlink_get_self(mesh); + assert(dest_node != NULL); + + ssize_t pmtu = meshlink_get_pmtu(mesh, dest_node); + assert_int_not_equal(pmtu, -1); + + meshlink_close(mesh); + assert(meshlink_destroy("pmtu_conf")); + return true; +} + +/* Execute meshlink_get_pmtu Test Case # 2 + + Test Steps: + 1. Create node instance & get self handle + 2. Try to obtain MTU size by passing NULL as mesh handle to API + + Expected Result: + meshlink_get_pmtu should return -1 reporting the error +*/ +static void test_case_mesh_pmtu_02(void **state) { + execute_test(test_steps_mesh_pmtu_02, state); +} + +/* Test Steps for meshlink_get_pmtu Test Case # 2*/ +static bool test_steps_mesh_pmtu_02(void) { + meshlink_handle_t *mesh = meshlink_open("pmtu_conf", "foo", "test", DEV_CLASS_STATIONARY); + assert(mesh != NULL); + + assert(meshlink_start(mesh)); + meshlink_node_t *dest_node = meshlink_get_self(mesh); + assert(dest_node != NULL); + + ssize_t pmtu = meshlink_get_pmtu(NULL, dest_node); + assert_int_equal(pmtu, -1); + + meshlink_close(mesh); + assert(meshlink_destroy("pmtu_conf")); + return true; +} + +/* Execute meshlink_get_pmtu Test Case # 3 */ +static void test_case_mesh_pmtu_03(void **state) { + execute_test(test_steps_mesh_pmtu_03, state); +} + +/* Test Steps for meshlink_get_pmtu Test Case # 3 + + Test Steps: + 1. Create node instance & get self handle + 2. Try to obtain MTU size by passing NULL as node handle to API + + Expected Result: + meshlink_get_pmtu should return -1 reporting the error +*/ +static bool test_steps_mesh_pmtu_03(void) { + meshlink_handle_t *mesh = meshlink_open("pmtu_conf", "foo", "test", DEV_CLASS_STATIONARY); + assert(mesh != NULL); + + assert(meshlink_start(mesh)); + + ssize_t pmtu = meshlink_get_pmtu(mesh, NULL); + assert_int_equal(pmtu, -1); + + meshlink_close(mesh); + assert(meshlink_destroy("pmtu_conf")); + return true; +} + +int test_meshlink_pmtu(void) { + const struct CMUnitTest blackbox_pmtu_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_pmtu_01, NULL, NULL, + (void *)&test_mesh_pmtu_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_pmtu_02, NULL, NULL, + (void *)&test_mesh_pmtu_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_pmtu_03, NULL, NULL, + (void *)&test_mesh_pmtu_03_state) + }; + + total_tests += sizeof(blackbox_pmtu_tests) / sizeof(blackbox_pmtu_tests[0]); + + return cmocka_run_group_tests(blackbox_pmtu_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_pmtu.h b/test/blackbox/run_blackbox_tests/test_cases_pmtu.h new file mode 100644 index 0000000..db9b5cd --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_pmtu.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_PMTU_H +#define TEST_CASES_PMTU_H + +/* + test_cases_pmtu.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_pmtu(void); +extern int total_tests; + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_random_port_bindings01.c b/test/blackbox/run_blackbox_tests/test_cases_random_port_bindings01.c new file mode 100644 index 0000000..23118c9 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_random_port_bindings01.c @@ -0,0 +1,302 @@ +/* + test_cases_random_port_bindings01.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +#include "execute_tests.h" +#include "test_cases_random_port_bindings01.h" +#include "../../../src/meshlink.h" +#include "../../../src/devtools.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static void test_case_mesh_random_port_bindings_01(void **state); +static bool test_steps_mesh_random_port_bindings_01(void); +static void test_case_mesh_random_port_bindings_02(void **state); +static bool test_steps_mesh_random_port_bindings_02(void); +static void test_case_mesh_random_port_bindings_03(void **state); +static bool test_steps_mesh_random_port_bindings_03(void); + +/* State structure for meshlink_random_port_bindings Test Case #1 */ +static black_box_state_t test_mesh_random_port_bindings_01_state = { + .test_case_name = "test_case_mesh_random_port_bindings_01", +}; + +/* State structure for meshlink_random_port_bindings Test Case #2 */ +static black_box_state_t test_mesh_random_port_bindings_02_state = { + .test_case_name = "test_case_mesh_random_port_bindings_02", +}; + +/* State structure for meshlink_random_port_bindings Test Case #3 */ +static black_box_state_t test_mesh_random_port_bindings_03_state = { + .test_case_name = "test_case_mesh_random_port_bindings_03", +}; + +static int sockfd = -1, ipv6_fd = -1; + +static void log_message(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) { + (void) mesh; + + static const char *levelstr[] = { + [MESHLINK_DEBUG] = "\x1b[34mDEBUG", + [MESHLINK_INFO] = "\x1b[32mINFO", + [MESHLINK_WARNING] = "\x1b[33mWARNING", + [MESHLINK_ERROR] = "\x1b[31mERROR", + [MESHLINK_CRITICAL] = "\x1b[31mCRITICAL", + }; + fprintf(stderr, "%s:\x1b[0m %s\n", levelstr[level], text); +} + +static void occupy_port(int port) { + int ret_val; + int mode = 1; + struct sockaddr_in servaddr; + struct sockaddr_in6 ipv6addr; + + sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + assert_int_not_equal(sockfd, -1); + memset(&servaddr, 0, sizeof(servaddr)); + + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = htonl(INADDR_ANY); + servaddr.sin_port = htons(port); + + assert_int_equal(bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)), 0); + + ipv6_fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); + assert_int_not_equal(ipv6_fd, -1); + + mode = 1; + setsockopt(ipv6_fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&mode, sizeof(mode)); + + memset(&ipv6addr, 0, sizeof(ipv6addr)); + + ipv6addr.sin6_family = AF_INET6; + ipv6addr.sin6_addr = in6addr_any; + ipv6addr.sin6_port = htons(port); + + if((ret_val = bind(ipv6_fd, (const struct sockaddr *)&ipv6addr, sizeof(ipv6addr))) < 0) { + fprintf(stderr, "Bind to ipv6 failed due to %s\n", strerror(errno)); + assert(false); + } + + listen(ipv6_fd, 5); + + return; +} + +static void occupy_trybind_port(void) { + occupy_port(10000); + return; +} + +/* Execute meshlink_random_port_bindings Test Case # 1*/ +void test_case_mesh_random_port_bindings_01(void **state) { + execute_test(test_steps_mesh_random_port_bindings_01, state); +} + +/* Test Steps for meshlink random port bindings Test Case # 1 + + Test Steps: + 1. Open a node instance + 2. Bind a Socket on port 10000 + 3. Call meshlink_set_port() with same port 10000 + + Expected Result: + The meshlink_set_port() API should fail and the Listening Port + of the instance should be unchanged. +*/ +bool test_steps_mesh_random_port_bindings_01(void) { + meshlink_handle_t *relay = NULL; + assert(meshlink_destroy("relay_conf")); + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_message); + + relay = meshlink_open("relay_conf", "relay", "test", DEV_CLASS_BACKBONE); + fprintf(stderr, "Got mesh handle %p\n", (void *)relay); + assert_non_null(relay); + + meshlink_set_log_cb(relay, MESHLINK_DEBUG, log_message); + meshlink_enable_discovery(relay, false); + + assert_true(meshlink_start(relay)); + + occupy_port(10000); + + meshlink_stop(relay); + fprintf(stderr, "Meshlink stop returned\n"); + + assert_int_equal(meshlink_set_port(relay, 10000), false); + fprintf(stderr, "Meshlink set port returned\n"); + + close(sockfd); + close(ipv6_fd); + + sockfd = -1; + ipv6_fd = -1; + + meshlink_close(relay); + assert(meshlink_destroy("relay_conf")); + + return true; +} + +/* Execute meshlink_blacklist Test Case # 2*/ +void test_case_mesh_random_port_bindings_02(void **state) { + execute_test(test_steps_mesh_random_port_bindings_02, state); +} + +/* Test Steps for meshlink random port bindings Test Case # 2 + + Test Steps: + 1. Open a node and start the instance. + 2. Call meshlink_set_port() with port 10000 + 3. When try bind succeeds block the port using devtool_trybind_probe() callback. + + Expected Result: + The meshlink_set_port() API should fail. +*/ +bool test_steps_mesh_random_port_bindings_02(void) { + meshlink_handle_t *relay = NULL; + assert(meshlink_destroy("relay_conf")); + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_message); + + relay = meshlink_open("relay_conf", "relay", "test", DEV_CLASS_BACKBONE); + fprintf(stderr, "Got mesh handle %p\n", (void *)relay); + assert_non_null(relay); + + meshlink_set_log_cb(relay, MESHLINK_DEBUG, log_message); + meshlink_enable_discovery(relay, false); + + assert_true(meshlink_start(relay)); + + sleep(1); + + devtool_trybind_probe = occupy_trybind_port; + meshlink_stop(relay); + + assert_int_equal(meshlink_set_port(relay, 10000), false); + + close(sockfd); + close(ipv6_fd); + + sockfd = -1; + ipv6_fd = -1; + + meshlink_close(relay); + assert(meshlink_destroy("relay_conf")); + return true; +} + +/* Execute meshlink_blacklist Test Case # 3*/ +void test_case_mesh_random_port_bindings_03(void **state) { + execute_test(test_steps_mesh_random_port_bindings_03, state); +} + +/* Test Steps for meshlink random port bindings Test Case # 3 + + Test Steps: + 1. Open a node and start the instance. + 2. Retrieve the port number of current instance using meshlink_get_port(). + 3. Close the instance and try to occupy the meshlink instance port. + 4. Start the instance again with same confdir. + + Expected Result: + The meshlink instance should start with a new random port different to + previous port number. +*/ +bool test_steps_mesh_random_port_bindings_03(void) { + int port, new_port; + meshlink_handle_t *relay = NULL; + assert(meshlink_destroy("relay_conf")); + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_message); + + relay = meshlink_open("relay_conf", "relay", "test", DEV_CLASS_BACKBONE); + fprintf(stderr, "Got mesh handle %p\n", (void *)relay); + assert_non_null(relay); + + meshlink_set_log_cb(relay, MESHLINK_DEBUG, log_message); + meshlink_enable_discovery(relay, false); + + assert_true(meshlink_start(relay)); + port = meshlink_get_port(relay); + + meshlink_close(relay); + + occupy_port(port); + + relay = meshlink_open("relay_conf", "relay", "test", DEV_CLASS_BACKBONE); + fprintf(stderr, "Got mesh handle %p\n", (void *)relay); + assert_non_null(relay); + + meshlink_set_log_cb(relay, MESHLINK_DEBUG, log_message); + meshlink_enable_discovery(relay, false); + + assert_true(meshlink_start(relay)); + + new_port = meshlink_get_port(relay); + + assert_int_not_equal(port, new_port); + + close(sockfd); + close(ipv6_fd); + + sockfd = -1; + ipv6_fd = -1; + + meshlink_close(relay); + assert(meshlink_destroy("relay_conf")); + return true; +} + +int test_meshlink_random_port_bindings01(void) { + const struct CMUnitTest blackbox_random_port_bindings_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_random_port_bindings_01, NULL, NULL, + (void *)&test_mesh_random_port_bindings_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_random_port_bindings_02, NULL, NULL, + (void *)&test_mesh_random_port_bindings_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_random_port_bindings_03, NULL, NULL, + (void *)&test_mesh_random_port_bindings_03_state) + }; + + total_tests += sizeof(blackbox_random_port_bindings_tests) / sizeof(blackbox_random_port_bindings_tests[0]); + + return cmocka_run_group_tests(blackbox_random_port_bindings_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_random_port_bindings01.h b/test/blackbox/run_blackbox_tests/test_cases_random_port_bindings01.h new file mode 100644 index 0000000..c79ea91 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_random_port_bindings01.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_RANDOM_PORT_BINDINGS01_H +#define TEST_CASES_RANDOM_PORT_BINDINGS01_H + +/* + test_cases_random_port_bindings01.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_random_port_bindings01(void); +extern int total_tests; + +#endif //TEST_CASES_RANDOM_PORT_BINDINGS01_H \ No newline at end of file diff --git a/test/blackbox/run_blackbox_tests/test_cases_random_port_bindings02.c b/test/blackbox/run_blackbox_tests/test_cases_random_port_bindings02.c new file mode 100644 index 0000000..168915a --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_random_port_bindings02.c @@ -0,0 +1,435 @@ +/* + test_optimal_pmtu.c -- Execution of specific meshlink black box test cases + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include "../../../src/meshlink.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "../common/network_namespace_framework.h" +#include "../../utils.h" +#include "test_cases_random_port_bindings02.h" + +static void test_case_mesh_random_port_bindings_04(void **state); +static bool test_steps_mesh_random_port_bindings_04(void); +static void test_case_mesh_random_port_bindings_05(void **state); +static bool test_steps_mesh_random_port_bindings_05(void); + +typedef bool (*test_step_func_t)(void); +static int setup_test(void **state); + +static meshlink_handle_t *peer, *nut_instance, *relay; +static char *peer_invite, *nut_invite; +struct sync_flag test_random_port_binding_node_connected = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +struct sync_flag test_random_port_binding_node_started = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +struct sync_flag test_random_port_binding_peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +struct sync_flag test_random_port_binding_make_switch = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +struct sync_flag test_random_port_binding_relay_closed = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +struct sync_flag test_random_port_binding_peer_closed = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +struct sync_flag test_random_port_binding_nut_closed = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static netns_state_t *test_random_port_bindings_state; +static bool localnode = false; + +static int setup_test(void **state) { + (void)state; + + netns_create_topology(test_random_port_bindings_state); + fprintf(stderr, "\nCreated topology\n"); + + set_sync_flag(&test_random_port_binding_node_connected, false); + set_sync_flag(&test_random_port_binding_node_started, false); + set_sync_flag(&test_random_port_binding_peer_reachable, false); + set_sync_flag(&test_random_port_binding_make_switch, false); + set_sync_flag(&test_random_port_binding_relay_closed, false); + set_sync_flag(&test_random_port_binding_peer_closed, false); + set_sync_flag(&test_random_port_binding_nut_closed, false); + + assert(meshlink_destroy("nut")); + assert(meshlink_destroy("peer")); + assert(meshlink_destroy("relay")); + + return EXIT_SUCCESS; +} + +static int teardown_test(void **state) { + (void)state; + + assert(meshlink_destroy("nut")); + assert(meshlink_destroy("peer")); + assert(meshlink_destroy("relay")); + netns_destroy_topology(test_random_port_bindings_state); + + return EXIT_SUCCESS; +} + +static void execute_test(test_step_func_t step_func, void **state) { + (void)state; + + + fprintf(stderr, "\n\x1b[32mRunning Test\x1b[0m\n"); + bool test_result = step_func(); + + if(!test_result) { + fail(); + } +} + +static void message_log(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) { + (void)level; + + char *levelstr = "\x1b[32mRELAY"; + + if(strcmp(mesh->name, "peer") == 0) { + if(strcmp("Connection with nut activated", text) == 0) { + set_sync_flag(&test_random_port_binding_node_connected, true); + } + + levelstr = "\x1b[34mPEER"; + } else if(strcmp(mesh->name, "nut") == 0) { + if(strcmp("Connection with peer activated", text) == 0) { + set_sync_flag(&test_random_port_binding_node_connected, true); + } + + levelstr = "\x1b[33mNUT"; + } + + fprintf(stderr, "%s:\x1b[0m %s\n", levelstr, text); +} + +static void node_status(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(reachable) { + if((strcmp(mesh->name, "nut") == 0) && (strcmp(node->name, "peer") == 0)) { + set_sync_flag(&test_random_port_binding_peer_reachable, true); + } + + fprintf(stderr, "%s: %s joined.\n", mesh->name, node->name); + } +} + +static void *relay_node(void *arg) { + mesh_arg_t *mesh_arg = (mesh_arg_t *)arg; + + //system("ifconfig"); + + assert(meshlink_destroy("relay")); + + relay = meshlink_open(mesh_arg->node_name, mesh_arg->confbase, mesh_arg->app_name, mesh_arg->dev_class); + assert(relay); + + assert_true(meshlink_start(relay)); + fprintf(stderr, "\n\x1b[32mRelay Started\x1b[0m\n"); + + assert((peer_invite = meshlink_invite(relay, NULL, "peer"))); + assert((nut_invite = meshlink_invite(relay, NULL, "nut"))); + + set_sync_flag(&test_random_port_binding_node_started, true); + + meshlink_set_log_cb(relay, MESHLINK_DEBUG, message_log); + + if(localnode == true) { + assert(wait_sync_flag(&test_random_port_binding_make_switch, 300)); + meshlink_close(relay); + assert(meshlink_destroy("relay")); + + + set_sync_flag(&test_random_port_binding_relay_closed, true); + + return NULL; + } + + assert(wait_sync_flag(&test_random_port_binding_node_connected, 300)); + + meshlink_close(relay); + assert(meshlink_destroy("relay")); + + + set_sync_flag(&test_random_port_binding_relay_closed, true); + + return NULL; +} + +static void *peer_node(void *arg) { + mesh_arg_t *mesh_arg = (mesh_arg_t *)arg; + + fprintf(stderr, "\n\x1b[32mPeer Thread Started\x1b[0m\n"); + + assert(meshlink_destroy("peer")); + + peer = meshlink_open(mesh_arg->node_name, mesh_arg->confbase, mesh_arg->app_name, mesh_arg->dev_class); + assert(peer); + meshlink_set_log_cb(peer, MESHLINK_DEBUG, message_log); + + fprintf(stderr, "\n\x1b[32mPeer joining relay\x1b[0m\n"); + + assert_true(meshlink_join(peer, (const char *)mesh_arg->join_invitation)); + + assert_true(meshlink_start(peer)); + + fprintf(stderr, "\n\x1b[32mPeer Started\x1b[0m\n"); + + set_sync_flag(&test_random_port_binding_node_started, true); + + assert(wait_sync_flag(&test_random_port_binding_make_switch, 300)); + + meshlink_stop(peer); + + //meshlink_set_log_cb(peer, MESHLINK_DEBUG, message_log); + + assert(meshlink_set_port(peer, 20000)); + + assert_true(meshlink_start(peer)); + + assert(wait_sync_flag(&test_random_port_binding_node_connected, 300)); + + meshlink_close(peer); + assert(meshlink_destroy("peer")); + + set_sync_flag(&test_random_port_binding_peer_closed, true); + + return NULL; +} + +static void *nut_node(void *arg) { + mesh_arg_t *mesh_arg = (mesh_arg_t *)arg; + + fprintf(stderr, "\n\x1b[32mNut Thread Started\x1b[0m\n"); + + assert(meshlink_destroy("nut")); + + nut_instance = meshlink_open(mesh_arg->node_name, mesh_arg->confbase, mesh_arg->app_name, mesh_arg->dev_class); + assert(nut_instance); + + meshlink_set_log_cb(nut_instance, MESHLINK_DEBUG, message_log); + + fprintf(stderr, "\n\x1b[32mNut joining relay\x1b[0m\n"); + + assert_true(meshlink_join(nut_instance, (const char *)mesh_arg->join_invitation)); + + meshlink_set_node_status_cb(nut_instance, node_status); + + assert_true(meshlink_start(nut_instance)); + + fprintf(stderr, "\n\x1b[32mNut Started\x1b[0m\n"); + sleep(5); + + set_sync_flag(&test_random_port_binding_node_started, true); + + assert(wait_sync_flag(&test_random_port_binding_make_switch, 300)); + + meshlink_stop(nut_instance); + + //meshlink_set_log_cb(nut_instance, MESHLINK_DEBUG, message_log); + + assert(meshlink_set_port(nut_instance, 30000)); + + assert_true(meshlink_start(nut_instance)); + + assert(wait_sync_flag(&test_random_port_binding_node_connected, 300)); + + meshlink_close(nut_instance); + assert(meshlink_destroy("nut")); + + set_sync_flag(&test_random_port_binding_nut_closed, true); + + return NULL; +} + +/* Test Steps for Random port bindings Test Case # 4 */ +static void test_case_mesh_random_port_bindings_04(void **state) { + execute_test(test_steps_mesh_random_port_bindings_04, state); + return; +} + +/* Test Steps for Random port bindings Test Case # 4 + + Test Steps: + 1. Create three node nut, peer and relay in three different name spaces. + 2. Join nut and peer to relay with invitation. + 3. Stop the three nodes and change the ports of nut and peer. + 4. Start all the nodes again. + Expected Result: + NUT and Peer should be able to discover each others port with the help + of RELAY and form the direct meta connection. +*/ +static bool test_steps_mesh_random_port_bindings_04(void) { + mesh_arg_t relay_arg = {.node_name = "relay", .confbase = "relay", .app_name = "chat", .dev_class = 0 }; + mesh_arg_t peer_arg = {.node_name = "peer", .confbase = "peer", .app_name = "chat", .dev_class = 1 }; + mesh_arg_t nut_arg = {.node_name = "nut", .confbase = "nut", .app_name = "chat", .dev_class = 1 }; + + netns_thread_t netns_relay_handle = {.namespace_name = "relay", .netns_thread = relay_node, .arg = &relay_arg}; + run_node_in_namespace_thread(&netns_relay_handle); + + assert(wait_sync_flag(&test_random_port_binding_node_started, 5)); + fprintf(stderr, "\n\x1b[32mTest-04 : Relay Started\x1b[0m\n"); + + set_sync_flag(&test_random_port_binding_node_started, false); + peer_arg.join_invitation = peer_invite; + fprintf(stderr, "\n\x1b[32mTest-04: Got Invite {%s} for peer\x1b[0m\n", peer_arg.join_invitation); + netns_thread_t netns_peer_handle = {.namespace_name = "peer", .netns_thread = peer_node, .arg = &peer_arg}; + run_node_in_namespace_thread(&netns_peer_handle); + + assert(wait_sync_flag(&test_random_port_binding_node_started, 20)); + fprintf(stderr, "\n\x1b[32mTest-04 : Peer Started\x1b[0m\n"); + + set_sync_flag(&test_random_port_binding_node_started, false); + nut_arg.join_invitation = nut_invite; + fprintf(stderr, "\n\x1b[32mTest-04: Got Invite {%s} for nut\x1b[0m\n", nut_arg.join_invitation); + netns_thread_t netns_nut_handle = {.namespace_name = "nut", .netns_thread = nut_node, .arg = &nut_arg}; + run_node_in_namespace_thread(&netns_nut_handle); + + assert(wait_sync_flag(&test_random_port_binding_node_started, 20)); + fprintf(stderr, "\n\x1b[32mTest-04 : Nut Started\x1b[0m\n"); + + set_sync_flag(&test_random_port_binding_make_switch, true); + fprintf(stderr, "\n\x1b[32mTest-04 : Making Switch\x1b[0m\n"); + + assert(wait_sync_flag(&test_random_port_binding_node_connected, 300)); + + fprintf(stderr, "\n\x1b[32mDone Test-04\x1b[0m\n"); + + assert(wait_sync_flag(&test_random_port_binding_relay_closed, 10)); + assert(wait_sync_flag(&test_random_port_binding_peer_closed, 10)); + assert(wait_sync_flag(&test_random_port_binding_nut_closed, 10)); + + return true; +} + +/* Test Steps for Random port bindings Test Case # 5 */ +static void test_case_mesh_random_port_bindings_05(void **state) { + execute_test(test_steps_mesh_random_port_bindings_05, state); + return; +} + +/* Test Steps for Random port bindings Test Case # 5 + + Test Steps: + 1. Create three node nut, peer and relay in same name spaces. + 2. Join nut and peer to relay with invitation. + 3. Stop the three nodes and change the ports of nut and peer. + 4. Close the relay node and start nut and peer nodes again. + Expected Result: + NUT and Peer should be able to discover each others port with the help + of CATTA and form the direct meta connection. +*/ +static bool test_steps_mesh_random_port_bindings_05(void) { + localnode = true; + + mesh_arg_t relay_arg = {.node_name = "relay", .confbase = "relay", .app_name = "chat", .dev_class = 1 }; + mesh_arg_t peer_arg = {.node_name = "peer", .confbase = "peer", .app_name = "chat", .dev_class = 1 }; + mesh_arg_t nut_arg = {.node_name = "nut", .confbase = "nut", .app_name = "chat", .dev_class = 1 }; + + netns_thread_t netns_relay_handle = {.namespace_name = "relay", .netns_thread = relay_node, .arg = &relay_arg}; + run_node_in_namespace_thread(&netns_relay_handle); + + assert(wait_sync_flag(&test_random_port_binding_node_started, 20)); + + set_sync_flag(&test_random_port_binding_node_started, false); + peer_arg.join_invitation = peer_invite; + netns_thread_t netns_peer_handle = {.namespace_name = "peer", .netns_thread = peer_node, .arg = &peer_arg}; + run_node_in_namespace_thread(&netns_peer_handle); + + assert(wait_sync_flag(&test_random_port_binding_node_started, 20)); + + set_sync_flag(&test_random_port_binding_node_started, false); + nut_arg.join_invitation = nut_invite; + netns_thread_t netns_nut_handle = {.namespace_name = "nut", .netns_thread = nut_node, .arg = &nut_arg}; + run_node_in_namespace_thread(&netns_nut_handle); + + assert(wait_sync_flag(&test_random_port_binding_node_started, 20)); + + assert(wait_sync_flag(&test_random_port_binding_peer_reachable, 300)); + + set_sync_flag(&test_random_port_binding_make_switch, true); + + assert(wait_sync_flag(&test_random_port_binding_node_connected, 300)); + + fprintf(stderr, "\n\x1b[32mDone Test-05\x1b[0m\n"); + + assert(wait_sync_flag(&test_random_port_binding_relay_closed, 10)); + assert(wait_sync_flag(&test_random_port_binding_peer_closed, 10)); + assert(wait_sync_flag(&test_random_port_binding_nut_closed, 10)); + + return true; +} + +// Optimal PMTU test case driver + +int test_meshlink_random_port_bindings02(void) { + interface_t nut_ifs[] = {{.if_peer = "wan_bridge"}}; + namespace_t nut = { + .name = "nut", + .type = HOST, + .interfaces = nut_ifs, + .interfaces_no = 1, + }; + + interface_t peer_ifs[] = {{.if_peer = "wan_bridge"}}; + namespace_t peer = { + .name = "peer", + .type = HOST, + .interfaces = peer_ifs, + .interfaces_no = 1, + }; + + interface_t relay_ifs[] = {{.if_peer = "wan_bridge"}}; + namespace_t relay = { + .name = "relay", + .type = HOST, + .interfaces = relay_ifs, + .interfaces_no = 1, + }; + + interface_t wan_ifs[] = { { .if_peer = "nut" }, { .if_peer = "peer" }, { .if_peer = "relay" } }; + namespace_t wan_bridge = { + .name = "wan_bridge", + .type = BRIDGE, + .interfaces = wan_ifs, + .interfaces_no = 3, + }; + + namespace_t test_random_port_bindings_02_nodes[] = {wan_bridge, nut, peer, relay }; + + netns_state_t test_port_bindings_nodes = { + .test_case_name = "test_case_random_port_bindings_02", + .namespaces = test_random_port_bindings_02_nodes, + .num_namespaces = 4, + }; + test_random_port_bindings_state = &test_port_bindings_nodes; + + const struct CMUnitTest blackbox_group0_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_random_port_bindings_04, setup_test, teardown_test, + (void *)&test_random_port_bindings_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_random_port_bindings_05, setup_test, teardown_test, + (void *)&test_random_port_bindings_state) + }; + total_tests += sizeof(blackbox_group0_tests) / sizeof(blackbox_group0_tests[0]); + + return cmocka_run_group_tests(blackbox_group0_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_random_port_bindings02.h b/test/blackbox/run_blackbox_tests/test_cases_random_port_bindings02.h new file mode 100644 index 0000000..03adb23 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_random_port_bindings02.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_RANDOM_PORT_BINDINGS02_H +#define TEST_CASES_RANDOM_PORT_BINDINGS02_H + +/* + test_cases_random_port_bindings02.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_random_port_bindings02(void); +extern int total_tests; + +#endif //TEST_CASES_RANDOM_PORT_BINDINGS02_H \ No newline at end of file diff --git a/test/blackbox/run_blackbox_tests/test_cases_rec_cb.c b/test/blackbox/run_blackbox_tests/test_cases_rec_cb.c new file mode 100644 index 0000000..e659ead --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_rec_cb.c @@ -0,0 +1,212 @@ +/* + test_cases_rec_cb.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "test_cases_rec_cb.h" +#include +#include +#include +#include +#include +#include +#include + + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +static void test_case_set_rec_cb_01(void **state); +static bool test_set_rec_cb_01(void); +static void test_case_set_rec_cb_02(void **state); +static bool test_set_rec_cb_02(void); +static void test_case_set_rec_cb_03(void **state); +static bool test_set_rec_cb_03(void); + +/* Test Steps for meshlink_set_receive_cb Test Case #1 */ +static black_box_state_t test_case_set_rec_cb_01_state = { + .test_case_name = "test_case_set_rec_cb_01", +}; + +/* Test Steps for meshlink_set_receive_cb Test Case #2 */ +static black_box_state_t test_case_set_rec_cb_02_state = { + .test_case_name = "test_case_set_rec_cb_02", +}; + +/* Test Steps for meshlink_set_receive_cb Test Case #3 */ +static black_box_state_t test_case_set_rec_cb_03_state = { + .test_case_name = "test_case_set_rec_cb_03", +}; + +static bool received; + +/* mutex for the common variable */ +static pthread_mutex_t lock; + +/* receive callback function */ +static void rec_cb(meshlink_handle_t *mesh, meshlink_node_t *source, const void *data, size_t len) { + (void)mesh; + (void)source; + + assert(len); + + pthread_mutex_lock(&lock); + + if(len == 5 && !memcmp(data, "test", 5)) { + received = true; + } + + pthread_mutex_unlock(&lock); +} + +/* Execute meshlink_set_receive_cb Test Case # 1 - Valid case */ +static void test_case_set_rec_cb_01(void **state) { + execute_test(test_set_rec_cb_01, state); +} +/* Test Steps for meshlink_set_receive_cb Test Case # 1 + + Test Steps: + 1. Open NUT + 2. Set receive callback for the NUT + 3. Echo NUT with some data. + + Expected Result: + Receive callback should be invoked when NUT echoes or sends data for itself. +*/ +static bool test_set_rec_cb_01(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + meshlink_handle_t *mesh_handle = meshlink_open("set_receive_cb_conf", "nut", "test", 1); + assert(mesh_handle); + meshlink_set_receive_cb(mesh_handle, rec_cb); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + assert(meshlink_start(mesh_handle)); + sleep(1); + + pthread_mutex_lock(&lock); + received = false; + pthread_mutex_unlock(&lock); + meshlink_node_t *node_handle = meshlink_get_self(mesh_handle); + assert(node_handle); + assert(meshlink_send(mesh_handle, node_handle, "test", 5)); + sleep(1); + + pthread_mutex_lock(&lock); + assert_int_equal(received, true); + pthread_mutex_unlock(&lock); + + meshlink_close(mesh_handle); + assert(meshlink_destroy("set_receive_cb_conf")); + return true; +} + + +/* Execute meshlink_set_receive_cb Test Case # 2 - Invalid case */ +static void test_case_set_rec_cb_02(void **state) { + execute_test(test_set_rec_cb_02, state); +} +/* Test Steps for meshlink_set_receive_cb Test Case # 2 + + Test Steps: + 1. Call meshlink_set_receive_cb with NULL as mesh handle argument + + Expected Result: + meshlink_set_receive_cb API reports proper error accordingly. +*/ +static bool test_set_rec_cb_02(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + // Setting receive callback with NULL as mesh handle + meshlink_set_receive_cb(NULL, rec_cb); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + return true; +} + +/* Execute meshlink_set_receive_cb Test Case # 3 - Functionality Test, Trying to set receive call back after + starting the mesh */ +static void test_case_set_rec_cb_03(void **state) { + execute_test(test_set_rec_cb_03, state); +} +/* Test Steps for meshlink_set_receive_cb Test Case # 3 + + Test Steps: + 1. Open NUT + 2. Starting mesh + 2. Set receive callback for the NUT + 3. Echo NUT with some data. + + Expected Result: + Receive callback can be invoked when NUT echoes or sends data for itself +*/ +static bool test_set_rec_cb_03(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + /* Create meshlink instance */ + meshlink_handle_t *mesh_handle = meshlink_open("set_receive_cb_conf", "nut", "test", 1); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + assert(meshlink_start(mesh_handle)); + sleep(1); + meshlink_set_receive_cb(mesh_handle, rec_cb); + + pthread_mutex_lock(&lock); + received = false; + pthread_mutex_unlock(&lock); + meshlink_node_t *node_handle = meshlink_get_self(mesh_handle); + assert(node_handle); + assert(meshlink_send(mesh_handle, node_handle, "test", 5)); + sleep(1); + + pthread_mutex_lock(&lock); + assert_int_equal(received, true); + pthread_mutex_unlock(&lock); + + meshlink_close(mesh_handle); + assert(meshlink_destroy("set_receive_cb_conf")); + return true; +} + +int test_meshlink_set_receive_cb(void) { + const struct CMUnitTest blackbox_receive_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_set_rec_cb_01, NULL, NULL, + (void *)&test_case_set_rec_cb_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_set_rec_cb_02, NULL, NULL, + (void *)&test_case_set_rec_cb_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_set_rec_cb_03, NULL, NULL, + (void *)&test_case_set_rec_cb_03_state) + }; + total_tests += sizeof(blackbox_receive_tests) / sizeof(blackbox_receive_tests[0]); + + assert(pthread_mutex_init(&lock, NULL) == 0); + int failed = cmocka_run_group_tests(blackbox_receive_tests, NULL, NULL); + assert(pthread_mutex_destroy(&lock) == 0); + + return failed; +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_rec_cb.h b/test/blackbox/run_blackbox_tests/test_cases_rec_cb.h new file mode 100644 index 0000000..c4467c8 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_rec_cb.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_SET_REC_CB_H +#define TEST_CASES_SET_REC_CB_H + +/* + test_cases_rec_cb.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_set_receive_cb(void); +extern int total_tests; + +#endif // TEST_CASES_SET_REC_CB_H diff --git a/test/blackbox/run_blackbox_tests/test_cases_send.c b/test/blackbox/run_blackbox_tests/test_cases_send.c new file mode 100644 index 0000000..1cf47fc --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_send.c @@ -0,0 +1,179 @@ +/* + test_cases_send.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_send.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include + +static void test_case_mesh_send_01(void **state); +static bool test_steps_mesh_send_01(void); +static void test_case_mesh_send_02(void **state); +static bool test_steps_mesh_send_02(void); +static void test_case_mesh_send_03(void **state); +static bool test_steps_mesh_send_03(void); + +/* State structure for meshlink_send Test Case #1 */ +static black_box_state_t test_mesh_send_01_state = { + .test_case_name = "test_case_mesh_send_01", +}; + +/* State structure for meshlink_send Test Case #2 */ +static black_box_state_t test_mesh_send_02_state = { + .test_case_name = "test_case_mesh_send_02", +}; + +/* State structure for meshlink_send Test Case #3 */ +static black_box_state_t test_mesh_send_03_state = { + .test_case_name = "test_case_mesh_send_03", +}; + +/* Execute meshlink_send Test Case # 1 */ +static void test_case_mesh_send_01(void **state) { + execute_test(test_steps_mesh_send_01, state); +} + +static bool receive_data = false; +static void receive(meshlink_handle_t *mesh, meshlink_node_t *dest_node, const void *data, size_t len) { + (void)mesh; + (void)dest_node; + + assert(len); + + if(!memcmp(data, "test", 5)) { + receive_data = true; + } +} + +/* Test Steps for meshlink_send Test Case # 1 + + Test Steps: + 1. Open instance of foo node + 2. Run and send data to itself + + Expected Result: + Node should receive data sent to itself +*/ +static bool test_steps_mesh_send_01(void) { + bool result = false; + + meshlink_handle_t *mesh = meshlink_open("send_conf", "foo", "test", DEV_CLASS_STATIONARY); + assert(mesh != NULL); + meshlink_set_receive_cb(mesh, receive); + assert(meshlink_start(mesh)); + sleep(1); + meshlink_node_t *dest_node = meshlink_get_self(mesh); + assert(dest_node); + + receive_data = false; + result = meshlink_send(mesh, dest_node, "test", 5); + assert_int_equal(result, true); + sleep(1); + assert_int_equal(receive_data, true); + + meshlink_close(mesh); + assert(meshlink_destroy("send_conf")); + return result; +} + +/* Execute meshlink_send Test Case # 2 + + Test Steps: + 1. Open instance of foo node + 2. meshlink_send with NULL as mesh handle + + Expected Result: + meshlink_send returns false because of NULL handle +*/ +static void test_case_mesh_send_02(void **state) { + execute_test(test_steps_mesh_send_02, state); +} + +/* Test Steps for meshlink_send Test Case # 2*/ +static bool test_steps_mesh_send_02(void) { + meshlink_handle_t *mesh = meshlink_open("send_conf", "foo", "chat", DEV_CLASS_STATIONARY); + assert(mesh != NULL); + meshlink_set_receive_cb(mesh, receive); + + assert(meshlink_start(mesh)); + meshlink_node_t *dest_node = meshlink_get_self(mesh); + assert(dest_node); + + bool ret = meshlink_send(NULL, dest_node, "test", 5); + assert_int_equal(ret, false); + + meshlink_close(mesh); + assert(meshlink_destroy("send_conf")); + return true; +} + +/* Execute meshlink_send Test Case # 3 + + Test Steps: + 1. Open instance of foo node + 2. meshlink_send with NULL as node handle + + Expected Result: + meshlink_send returns false because of NULL handle +*/ +static void test_case_mesh_send_03(void **state) { + execute_test(test_steps_mesh_send_03, state); +} + +/* Test Steps for meshlink_send Test Case # 3*/ +static bool test_steps_mesh_send_03(void) { + meshlink_handle_t *mesh = meshlink_open("send_conf", "foo", "chat", DEV_CLASS_STATIONARY); + assert(mesh != NULL); + meshlink_set_receive_cb(mesh, receive); + + assert(meshlink_start(mesh)); + + bool ret = meshlink_send(mesh, NULL, "test", 5); + assert_int_equal(ret, false); + + meshlink_close(mesh); + assert(meshlink_destroy("send_conf")); + return true; +} + +int test_meshlink_send(void) { + const struct CMUnitTest blackbox_send_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_send_01, NULL, NULL, + (void *)&test_mesh_send_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_send_02, NULL, NULL, + (void *)&test_mesh_send_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_send_03, NULL, NULL, + (void *)&test_mesh_send_03_state) + }; + + total_tests += sizeof(blackbox_send_tests) / sizeof(blackbox_send_tests[0]); + + return cmocka_run_group_tests(blackbox_send_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_send.h b/test/blackbox/run_blackbox_tests/test_cases_send.h new file mode 100644 index 0000000..6649b8f --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_send.h @@ -0,0 +1,29 @@ +#ifndef TEST_CASES_SEND_H +#define TEST_CASES_SEND_H + +/* + test_cases_send.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_send(void); +extern int total_tests; + + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_set_connection_try_cb.c b/test/blackbox/run_blackbox_tests/test_cases_set_connection_try_cb.c new file mode 100644 index 0000000..6eac857 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_set_connection_try_cb.c @@ -0,0 +1,152 @@ +/* + test_cases_set_connection_try_cb.c -- Execution of specific meshlink black box test cases + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_set_connection_try_cb.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "../../utils.h" +#include +#include +#include +#include + +static void test_case_set_connection_try_cb_01(void **state); +static bool test_set_connection_try_cb_01(void); + +static bool bar_reachable; +static int connection_attempts; +static struct sync_flag status_changed_cond = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag connection_attempt_cond = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *source, bool reachable) { + if(!strcmp(mesh->name, "foo") && !strcmp(source->name, "bar")) { + bar_reachable = reachable; + set_sync_flag(&status_changed_cond, true); + } +} + +/* Meta-connection try callback handler */ +static void connection_try_cb(meshlink_handle_t *mesh, meshlink_node_t *source) { + (void)source; + + if(!strcmp(mesh->name, "foo")) { + ++connection_attempts; + + if(connection_attempts > 3) { + set_sync_flag(&connection_attempt_cond, true); + } + } +} + +/* Execute set meta connection try callback Test Case # 1 */ +static void test_case_set_connection_try_cb_01(void **state) { + execute_test(test_set_connection_try_cb_01, state); +} + +/* Test Steps for meshlink_set_connection_try_cb Test Case # 1 + + Test Steps: + 1. Run foo and bar nodes after exporting and importing node's keys and addresses mutually. + 2. Close bar node. Wait for connection attempts and cleanup. + + Expected Result: + Connection try callback should be invoked initially when foo and bar forms a meta-connection. + After closing bar node it should invoke 3 connection try callbacks in span of about 30 seconds. +*/ +static bool test_set_connection_try_cb_01(void) { + assert(meshlink_destroy("meshlink_conf.1")); + assert(meshlink_destroy("meshlink_conf.2")); + + // Opening foo and bar nodes + meshlink_handle_t *mesh1 = meshlink_open("meshlink_conf.1", "foo", "test", DEV_CLASS_STATIONARY); + assert(mesh1 != NULL); + meshlink_set_log_cb(mesh1, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_enable_discovery(mesh1, false); + meshlink_handle_t *mesh2 = meshlink_open("meshlink_conf.2", "bar", "test", DEV_CLASS_STATIONARY); + assert(mesh2 != NULL); + + // Set up callback for node status + meshlink_set_node_status_cb(mesh1, node_status_cb); + meshlink_set_connection_try_cb(mesh1, connection_try_cb); + + // Exporting and Importing mutually + char *exp1 = meshlink_export(mesh1); + assert(exp1 != NULL); + char *exp2 = meshlink_export(mesh2); + assert(exp2 != NULL); + assert(meshlink_import(mesh1, exp2)); + assert(meshlink_import(mesh2, exp1)); + free(exp1); + free(exp2); + + assert(meshlink_start(mesh1)); + assert(meshlink_start(mesh2)); + + // Wait for foo and bar nodes to join + assert(wait_sync_flag(&status_changed_cond, 5)); + assert(bar_reachable); + + // Joining should in this case raise one connection try callback + assert_int_equal(connection_attempts, 1); + + // Close the bar node + set_sync_flag(&status_changed_cond, false); + meshlink_close(mesh2); + assert(wait_sync_flag(&status_changed_cond, 5)); + assert(!bar_reachable); + + // Wait for additional 3 connection try callbacks + time_t attempt_time_start = time(NULL); + assert(attempt_time_start != -1); + assert_int_equal(wait_sync_flag(&connection_attempt_cond, 60), true); + + // Close bar node and assert on number of callbacks invoked and the time taken. + meshlink_close(mesh1); + time_t attempt_time_stop = time(NULL); + assert(attempt_time_stop != -1); + assert_int_equal(connection_attempts, 4); + assert_in_range(attempt_time_stop - attempt_time_start, 25, 45); + + // Cleanup + assert(meshlink_destroy("meshlink_conf.1")); + assert(meshlink_destroy("meshlink_conf.2")); + + return true; +} + +int test_cases_connection_try(void) { + black_box_state_t test_case_set_connection_try_cb_01_state = { + .test_case_name = "test_case_set_connection_try_cb_01", + }; + + const struct CMUnitTest blackbox_connection_try_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_set_connection_try_cb_01, NULL, NULL, + (void *)&test_case_set_connection_try_cb_01_state), + }; + total_tests += sizeof(blackbox_connection_try_tests) / sizeof(blackbox_connection_try_tests[0]); + + int failed = cmocka_run_group_tests(blackbox_connection_try_tests, NULL, NULL); + + return failed; +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_set_connection_try_cb.h b/test/blackbox/run_blackbox_tests/test_cases_set_connection_try_cb.h new file mode 100644 index 0000000..8a3c0da --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_set_connection_try_cb.h @@ -0,0 +1,26 @@ +#ifndef TEST_CASES_SET_CONNECTION_TRY_CB_H +#define TEST_CASES_SET_CONNECTION_TRY_CB_H + +/* + test_cases_set_connection_try_cb.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +extern int test_cases_connection_try(void); +extern int total_tests; + +#endif // TEST_CASES_SET_CONNECTION_TRY_CB_H diff --git a/test/blackbox/run_blackbox_tests/test_cases_set_log_cb.c b/test/blackbox/run_blackbox_tests/test_cases_set_log_cb.c new file mode 100644 index 0000000..da49558 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_set_log_cb.c @@ -0,0 +1,147 @@ +/* + test_cases_set_log_cb.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_set_log_cb.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include +#include + + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +static void test_case_set_log_cb_01(void **state); +static bool test_set_log_cb_01(void); +static void test_case_set_log_cb_02(void **state); +static bool test_set_log_cb_02(void); + +/* log variable gives access to the log callback to know whether invoked or not */ +static bool log; + +/* State structure for log callback Test Case #1 */ +static black_box_state_t test_case_set_log_cb_01_state = { + .test_case_name = "test_case_set_log_cb_01", +}; + +/* State structure for log callback Test Case #2 */ +static black_box_state_t test_case_set_log_cb_02_state = { + .test_case_name = "test_case_set_log_cb_02", +}; + + +/* log callback */ +static void log_cb(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) { + (void)mesh; + (void)level; + + fprintf(stderr, "Received log text : %s\n", text); + log = true; +} + +/* Execute meshlink_set_log_cb Test Case # 1 - Valid case */ +static void test_case_set_log_cb_01(void **state) { + execute_test(test_set_log_cb_01, state); +} +/* Test Steps for meshlink_set_receive_cb Test Case # 1 + + Test Steps: + 1. Run relay and Open NUT + 2. Set log callback for the NUT and Start NUT + + Expected Result: + log callback should be invoked when NUT joins with relay. +*/ +static bool test_set_log_cb_01(void) { + assert(meshlink_destroy("logconf")); + + // Create meshlink instance for NUT + + meshlink_handle_t *mesh = meshlink_open("logconf", "nut", "test", DEV_CLASS_STATIONARY); + assert(mesh != NULL); + + // Set up logging for Meshlink with the newly acquired Mesh Handle + + log = false; + meshlink_set_log_cb(mesh, TEST_MESHLINK_LOG_LEVEL, log_cb); + + // Starting node to log + + bool mesh_start = meshlink_start(mesh); + assert(mesh_start); + + bool ret = log; + + assert_int_equal(ret, true); + + // closing meshes and destroying confbase + + meshlink_close(mesh); + assert(meshlink_destroy("logconf")); + + return true; +} + +/* Execute meshlink_set_log_cb Test Case # 2 - Invalid case */ +static void test_case_set_log_cb_02(void **state) { + execute_test(test_set_log_cb_02, state); +} +/* Test Steps for meshlink_set_poll_cb Test Case # 2 + + Test Steps: + 1. Calling meshlink_set_poll_cb with some invalid integer other than the valid enums. + + Expected Result: + set poll callback handles the invalid parameter when called by giving proper error number. +*/ +static bool test_set_log_cb_02(void) { + + // Setting an invalid level + + meshlink_set_log_cb(NULL, 1000, NULL); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + return true; +} + + +int test_meshlink_set_log_cb(void) { + const struct CMUnitTest blackbox_log_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_set_log_cb_01, NULL, NULL, + (void *)&test_case_set_log_cb_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_set_log_cb_02, NULL, NULL, + (void *)&test_case_set_log_cb_02_state) + }; + total_tests += sizeof(blackbox_log_tests) / sizeof(blackbox_log_tests[0]); + + int failed = cmocka_run_group_tests(blackbox_log_tests, NULL, NULL); + + return failed; +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_set_log_cb.h b/test/blackbox/run_blackbox_tests/test_cases_set_log_cb.h new file mode 100644 index 0000000..31b1aa9 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_set_log_cb.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_SET_LOG_CB_H +#define TEST_CASES_SET_LOG_CB_H + +/* + test_cases_set_log_cb.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_set_log_cb(void); +extern int total_tests; + +#endif // TEST_CASES_SET_LOG_H diff --git a/test/blackbox/run_blackbox_tests/test_cases_set_port.c b/test/blackbox/run_blackbox_tests/test_cases_set_port.c new file mode 100644 index 0000000..c215399 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_set_port.c @@ -0,0 +1,305 @@ +/* + test_cases_set_port.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_destroy.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "../../utils.h" +#include "test_cases_set_port.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +#define NUT "nut" +#define PEER "peer" +#define TEST_MESHLINK_SET_PORT "test_set_port" +#define create_path(confbase, node_name, test_case_no) assert(snprintf(confbase, sizeof(confbase), TEST_MESHLINK_SET_PORT "_%ld_%s_%02d", (long) getpid(), node_name, test_case_no) > 0) + +static void test_case_set_port_01(void **state); +static bool test_set_port_01(void); +static void test_case_set_port_02(void **state); +static bool test_set_port_02(void); +static void test_case_set_port_03(void **state); +static bool test_set_port_03(void); +static void test_case_set_port_04(void **state); +static bool test_set_port_04(void); + +/* State structure for set port API Test Case #1 */ +static black_box_state_t test_case_set_port_01_state = { + .test_case_name = "test_case_set_port_01", +}; +/* State structure for set port API Test Case #2 */ +static black_box_state_t test_case_set_port_02_state = { + .test_case_name = "test_case_set_port_02", +}; +/* State structure for set port API Test Case #3 */ +static black_box_state_t test_case_set_port_03_state = { + .test_case_name = "test_case_set_port_03", +}; +/* State structure for set port API Test Case #4 */ +static black_box_state_t test_case_set_port_04_state = { + .test_case_name = "test_case_set_port_04", +}; + +static bool try_bind(int portno) { + int socket_fd = socket(AF_INET, SOCK_STREAM, 0); + assert_int_not_equal(socket_fd, -1); + + struct sockaddr_in sin; + socklen_t len = sizeof(sin); + bzero(&sin, len); + + assert_int_not_equal(getsockname(socket_fd, (struct sockaddr *)&sin, &len), -1); + sin.sin_addr.s_addr = INADDR_ANY; + sin.sin_port = htons(portno); + + errno = 0; + int bind_status = bind(socket_fd, (struct sockaddr *)&sin, len); + + // Exempt EADDRINUSE error only + + if(bind_status) { + assert_int_equal(errno, EADDRINUSE); + } + + assert_int_not_equal(close(socket_fd), -1); + + return !bind_status; +} + +static void wait_for_socket_free(int portno) { + + // Wait upto 20 seconds and poll every second whether the port is freed or not + + for(int i = 0; i < 20; i++) { + if(try_bind(portno)) { + return; + } else { + sleep(1); + } + } + + fail(); +} + +static int get_free_port(void) { + + // Get a free port + + int socket_fd = socket(AF_INET, SOCK_STREAM, 0); + assert_int_not_equal(socket_fd, -1); + + struct sockaddr_in sin; + socklen_t len = sizeof(sin); + bzero(&sin, len); + + assert_int_not_equal(getsockname(socket_fd, (struct sockaddr *)&sin, &len), -1); + sin.sin_addr.s_addr = INADDR_ANY; + sin.sin_port = 0; + + assert_int_not_equal(bind(socket_fd, (struct sockaddr *)&sin, len), -1); + + assert_int_not_equal(getsockname(socket_fd, (struct sockaddr *)&sin, &len), -1); + + assert_int_not_equal(close(socket_fd), -1); + + return (int) sin.sin_port; +} + + +/* Execute meshlink_set_port Test Case # 1 - valid case*/ +static void test_case_set_port_01(void **state) { + execute_test(test_set_port_01, state); +} +/* Test Steps for meshlink_set_port Test Case # 1 - Valid case + + Test Steps: + 1. Open NUT(Node Under Test) + 2. Set Port for NUT + + Expected Result: + Set the new port to the NUT. +*/ +static bool test_set_port_01(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + // Create meshlink instance + + mesh_handle = meshlink_open("setportconf", "nut", "test", 1); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + // Get old port and set a new port number + + int port; + port = meshlink_get_port(mesh_handle); + assert(port > 0); + bool ret = meshlink_set_port(mesh_handle, 8000); + port = meshlink_get_port(mesh_handle); + + assert_int_equal(port, 8000); + assert_int_equal(ret, true); + + // Clean up + + meshlink_close(mesh_handle); + assert(meshlink_destroy("setportconf")); + return true; +} + + +/* Execute meshlink_set_port Test Case # 2 - Invalid arguments */ +static void test_case_set_port_02(void **state) { + execute_test(test_set_port_02, state); +} + +/* Test Steps for meshlink_set_port Test Case # 2 - functionality test + + Test Steps: + 1. Open and start NUT and then pass invalid arguments to the set port API + + Expected Result: + Meshlink set port API should fail and error out when invalid arguments are passed +*/ +static bool test_set_port_02(void) { + char nut_confbase[PATH_MAX]; + create_path(nut_confbase, NUT, 2); + + // Create meshlink instance + + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, log_cb); + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_SET_PORT, DEV_CLASS_STATIONARY); + meshlink_set_log_cb(mesh, TEST_MESHLINK_LOG_LEVEL, log_cb); + + // meshlink_set_port called using NULL as mesh handle + + meshlink_errno = MESHLINK_OK; + assert_false(meshlink_set_port(NULL, 8000)); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + // Setting port after starting NUT + meshlink_errno = MESHLINK_OK; + assert_false(meshlink_set_port(mesh, -1)); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + meshlink_errno = MESHLINK_OK; + assert_false(meshlink_set_port(mesh, 70000)); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + assert_true(meshlink_start(mesh)); + meshlink_errno = MESHLINK_OK; + assert_false(meshlink_set_port(mesh, 8000)); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + // Clean up + + meshlink_close(mesh); + assert_true(meshlink_destroy(nut_confbase)); + return true; +} + +/* Execute meshlink_set_port Test Case # 3 - Synchronization testing */ +static void test_case_set_port_03(void **state) { + execute_test(test_set_port_03, state); +} + +static bool test_set_port_03(void) { + pid_t pid; + int pid_status; + char nut_confbase[PATH_MAX]; + create_path(nut_confbase, NUT, 3); + + int new_port = get_free_port(); + + // Fork a new process in which NUT opens it's instance, set's the new port and raises SIGINT to terminate. + + pid = fork(); + assert_int_not_equal(pid, -1); + + if(!pid) { + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_SET_PORT, DEV_CLASS_STATIONARY); + assert(mesh); + + assert(meshlink_set_port(mesh, new_port)); + raise(SIGINT); + } + + // Wait for child exit and verify which signal terminated it + + assert_int_not_equal(waitpid(pid, &pid_status, 0), -1); + assert_int_equal(WIFSIGNALED(pid_status), true); + assert_int_equal(WTERMSIG(pid_status), SIGINT); + + // Wait for the NUT's listening socket to be freed. (i.e, preventing meshlink from binding to a new port + // when NUT instance is reopened and the actual port is not freed due EADDRINUSE) + + wait_for_socket_free(new_port); + + // Reopen the NUT instance in the same test suite + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + meshlink_handle_t *mesh = meshlink_open(nut_confbase, NUT, TEST_MESHLINK_SET_PORT, DEV_CLASS_STATIONARY); + assert_non_null(mesh); + + assert_false(try_bind(new_port)); + + // Validate the new port that's being set in the previous instance persists. + + int get_port = meshlink_get_port(mesh); + assert_int_equal(get_port, new_port); + + // Close the mesh instance and verify that the listening port is closed or not + + meshlink_close(mesh); + + wait_for_socket_free(new_port); + + assert_true(meshlink_destroy(nut_confbase)); + return true; +} + + +int test_meshlink_set_port(void) { + const struct CMUnitTest blackbox_set_port_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_set_port_01, NULL, NULL, + (void *)&test_case_set_port_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_set_port_02, NULL, NULL, + (void *)&test_case_set_port_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_set_port_03, NULL, NULL, + (void *)&test_case_set_port_03_state) + }; + total_tests += sizeof(blackbox_set_port_tests) / sizeof(blackbox_set_port_tests[0]); + return cmocka_run_group_tests(blackbox_set_port_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_set_port.h b/test/blackbox/run_blackbox_tests/test_cases_set_port.h new file mode 100644 index 0000000..fa8a768 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_set_port.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_SET_PORT_H +#define TEST_CASES_SET_PORT_H + +/* + test_cases_set_port.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int total_tests; +extern int test_meshlink_set_port(void); + +#endif // TEST_CASES_SET_PORT diff --git a/test/blackbox/run_blackbox_tests/test_cases_sign.c b/test/blackbox/run_blackbox_tests/test_cases_sign.c new file mode 100644 index 0000000..68ae352 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_sign.c @@ -0,0 +1,404 @@ +/* + test_cases_sign.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_sign.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +static void test_case_sign_01(void **state); +static bool test_sign_01(void); +static void test_case_sign_02(void **state); +static bool test_sign_02(void); +static void test_case_sign_03(void **state); +static bool test_sign_03(void); +static void test_case_sign_04(void **state); +static bool test_sign_04(void); +static void test_case_sign_05(void **state); +static bool test_sign_05(void); +static void test_case_sign_06(void **state); +static bool test_sign_06(void); +static void test_case_sign_07(void **state); +static bool test_sign_07(void); + +/* State structure for sign API Test Case #1 */ +static black_box_state_t test_case_sign_01_state = { + .test_case_name = "test_case_sign_01", +}; + +/* State structure for sign API Test Case #2 */ +static black_box_state_t test_case_sign_02_state = { + .test_case_name = "test_case_sign_02", +}; + +/* State structure for sign API Test Case #3 */ +static black_box_state_t test_case_sign_03_state = { + .test_case_name = "test_case_sign_03", +}; + +/* State structure for sign API Test Case #4 */ +static black_box_state_t test_case_sign_04_state = { + .test_case_name = "test_case_sign_04", +}; + +/* State structure for sign API Test Case #5 */ +static black_box_state_t test_case_sign_05_state = { + .test_case_name = "test_case_sign_05", +}; + +/* State structure for sign API Test Case #6 */ +static black_box_state_t test_case_sign_06_state = { + .test_case_name = "test_case_sign_06", +}; + +/* State structure for sign API Test Case #7 */ +static black_box_state_t test_case_sign_07_state = { + .test_case_name = "test_case_sign_07", +}; + + +/* Execute sign_data Test Case # 1 - Valid case - sign a data successfully*/ +static void test_case_sign_01(void **state) { + execute_test(test_sign_01, state); +} + +/* Test Steps for meshlink_sign Test Case # 1 - Valid case + + Test Steps: + 1. Run NUT(Node Under Test) + 2. Sign data + + Expected Result: + Signs data successfully +*/ +static bool test_sign_01(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + // Create meshlink instance + meshlink_handle_t *mesh_handle = meshlink_open("signconf", "nut", "node_sim", 1); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + assert(meshlink_start(mesh_handle)); + + // Signing data + + char *data = "Test"; + char sig[MESHLINK_SIGLEN]; + size_t ssize = MESHLINK_SIGLEN; + bool ret = meshlink_sign(mesh_handle, data, strlen(data) + 1, sig, &ssize); + + // Clean up + meshlink_close(mesh_handle); + assert(meshlink_destroy("signconf")); + + return ret; +} + +/* Execute sign_data Test Case # 2 - Invalid case - meshlink_sign passing NULL as mesh handle argument*/ +static void test_case_sign_02(void **state) { + execute_test(test_sign_02, state); + return; +} + +/* Test Steps for meshlink_sign Test Case # 2 - invalid case + + Test Steps: + 1. meshlink_sign API called by passing NULL as mesh handle argument + + Expected Result: + API returns false hinting the error. +*/ +static bool test_sign_02(void) { + char *data = "Test"; + char sig[MESHLINK_SIGLEN]; + size_t ssize = MESHLINK_SIGLEN; + + // Calling meshlink_sign API + bool ret = meshlink_sign(NULL, data, strlen(data) + 1, sig, &ssize); + + if(!ret) { + PRINT_TEST_CASE_MSG("meshlink_sign Successfully reported error on passing NULL as mesh_handle arg\n"); + return true; + } + + PRINT_TEST_CASE_MSG("meshlink_sign FAILED to report error on passing NULL as mesh_handle arg\n"); + return false; +} + +/* Execute sign_data Test Case # 3 - Invalid case - meshlink_sign passing data to be signed as NULL */ +static void test_case_sign_03(void **state) { + execute_test(test_sign_03, state); +} + +/* Test Steps for meshlink_sign Test Case # 3 - invalid case + + Test Steps: + 1. Run NUT(Node Under Test) + 2. meshlink_sign API called by passing NULL as data argument + that has to be signed. + + Expected Result: + API returns false hinting the error. +*/ +static bool test_sign_03(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + // Create meshlink instance + meshlink_handle_t *mesh_handle = meshlink_open("signconf", "nut", "node_sim", 1); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + assert(meshlink_start(mesh_handle)); + + // Signing Data + char *data = "Test"; + char sig[MESHLINK_SIGLEN]; + size_t ssize = MESHLINK_SIGLEN; + bool ret = meshlink_sign(mesh_handle, NULL, strlen(data) + 1, sig, &ssize); + + // Clean up + + meshlink_close(mesh_handle); + assert(meshlink_destroy("signconf")); + + if(!ret) { + PRINT_TEST_CASE_MSG("meshlink_sign Successfully reported error on passing NULL as data arg\n"); + return true; + } else { + PRINT_TEST_CASE_MSG("meshlink_sign FAILED to report error on passing NULL as data arg\n"); + return false; + } +} + +/* Execute sign_data Test Case # 4 - Invalid case - meshlink_sign passing 0 as size of data + to be signed */ +static void test_case_sign_04(void **state) { + execute_test(test_sign_04, state); +} + +/* Test Steps for meshlink_sign Test Case # 3 - invalid case + + Test Steps: + 1. Run NUT(Node Under Test) + 2. meshlink_sign API called by passing 0 as size of data to be signed + + Expected Result: + API returns false hinting the error. +*/ +static bool test_sign_04(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + // Create meshlink instance + + meshlink_handle_t *mesh_handle = meshlink_open("signconf", "nut", "node_sim", 1); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + assert(meshlink_start(mesh_handle)); + + // Signing data + + char *data = "Test"; + char sig[MESHLINK_SIGLEN]; + size_t ssize = MESHLINK_SIGLEN; + bool ret = meshlink_sign(mesh_handle, data, 0, sig, &ssize); + + // Clean up + + meshlink_close(mesh_handle); + assert(meshlink_destroy("signconf")); + + if(!ret) { + PRINT_TEST_CASE_MSG("meshlink_sign Successfully reported error on passing 0 as size of data arg\n"); + return true; + } + + PRINT_TEST_CASE_MSG("meshlink_sign FAILED to report error on passing 0 as size of data arg\n"); + return false; +} + +/* Execute sign_data Test Case # 5 - Invalid case - meshlink_sign passing NULL as + signature buffer argument*/ +static void test_case_sign_05(void **state) { + execute_test(test_sign_05, state); +} + +/* Test Steps for meshlink_sign Test Case # 5 - invalid case + + Test Steps: + 1. Run NUT(Node Under Test) + 2. meshlink_sign API called by passing NULL for signature buffer argument + + Expected Result: + API returns false hinting the error. +*/ +static bool test_sign_05(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + // Create meshlink instance + + meshlink_handle_t *mesh_handle = meshlink_open("signconf", "nut", "node_sim", 1); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + assert(meshlink_start(mesh_handle)); + + // Signing data + + char *data = "Test"; + size_t ssize = MESHLINK_SIGLEN; + bool ret = meshlink_sign(mesh_handle, data, strlen(data) + 1, NULL, &ssize); + + // Clean up + + meshlink_close(mesh_handle); + assert(meshlink_destroy("signconf")); + + if(!ret) { + PRINT_TEST_CASE_MSG("meshlink_sign Successfully reported error on passing NULL as sign arg\n"); + return true; + } + + PRINT_TEST_CASE_MSG("meshlink_sign FAILED to report error on passing NULL as sign arg\n"); + return false; +} + +/* Execute sign_data Test Case # 6 - Invalid case - meshlink_sign passing NULL for size of + signature argument */ +static void test_case_sign_06(void **state) { + execute_test(test_sign_06, state); +} + +/* Test Steps for meshlink_sign Test Case # 6 - invalid case + + Test Steps: + 1. Run NUT(Node Under Test) + 2. meshlink_sign API called by passing NULL for size of signature buffer argument + + Expected Result: + API returns false hinting the error. +*/ +static bool test_sign_06(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + // Create meshlink instance + meshlink_handle_t *mesh_handle = meshlink_open("signconf", "nut", "node_sim", 1); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + assert(meshlink_start(mesh_handle)); + + // Signing data + + char *data = "Test"; + char sig[MESHLINK_SIGLEN]; + bool ret = meshlink_sign(mesh_handle, data, strlen(data) + 1, sig, NULL); + + // Clean up + + meshlink_close(mesh_handle); + assert(meshlink_destroy("signconf")); + + if(!ret) { + PRINT_TEST_CASE_MSG("meshlink_sign Successfully reported error on passing NULL as signsize arg\n"); + return true; + } + + PRINT_TEST_CASE_MSG("meshlink_sign FAILED to report error on passing NULL as signsize arg\n"); + return false; +} + +/* Execute sign_data Test Case # 7 - Invalid case - meshlink_sign passing size of signature < MESHLINK_SIGLEN*/ +static void test_case_sign_07(void **state) { + execute_test(test_sign_07, state); +} + +/* Test Steps for meshlink_sign Test Case # 6 - invalid case + + Test Steps: + 1. Run NUT(Node Under Test) + 2. meshlink_sign API called by passing size of signature < MESHLINK_SIGLEN + + Expected Result: + API returns false hinting the error. +*/ +static bool test_sign_07(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + // Create meshlink instance + + meshlink_handle_t *mesh_handle = meshlink_open("signconf", "nut", "node_sim", 1); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + assert(meshlink_start(mesh_handle)); + + // Signing data + + char *data = "Test"; + char sig[MESHLINK_SIGLEN]; + size_t ssize = 5; //5 < MESHLINK_SIGLEN + bool ret = meshlink_sign(mesh_handle, data, strlen(data) + 1, sig, &ssize); + + // Cleanup + + meshlink_stop(mesh_handle); + meshlink_close(mesh_handle); + assert(meshlink_destroy("signconf")); + + if(!ret) { + PRINT_TEST_CASE_MSG("meshlink_sign Successfully reported error on passing signsize < MESHLINK_SIGLEN arg\n"); + return true; + } + + PRINT_TEST_CASE_MSG("meshlink_sign FAILED to report error on passing signsize < MESHLINK_SIGLEN arg\n"); + return false; +} + + +int test_meshlink_sign(void) { + const struct CMUnitTest blackbox_sign_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_sign_01, NULL, NULL, + (void *)&test_case_sign_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_sign_02, NULL, NULL, + (void *)&test_case_sign_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_sign_03, NULL, NULL, + (void *)&test_case_sign_03_state), + cmocka_unit_test_prestate_setup_teardown(test_case_sign_04, NULL, NULL, + (void *)&test_case_sign_04_state), + cmocka_unit_test_prestate_setup_teardown(test_case_sign_05, NULL, NULL, + (void *)&test_case_sign_05_state), + cmocka_unit_test_prestate_setup_teardown(test_case_sign_06, NULL, NULL, + (void *)&test_case_sign_06_state), + cmocka_unit_test_prestate_setup_teardown(test_case_sign_07, NULL, NULL, + (void *)&test_case_sign_07_state) + }; + total_tests += sizeof(blackbox_sign_tests) / sizeof(blackbox_sign_tests[0]); + + return cmocka_run_group_tests(blackbox_sign_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_sign.h b/test/blackbox/run_blackbox_tests/test_cases_sign.h new file mode 100644 index 0000000..bbb741b --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_sign.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_SIGN_H +#define TEST_CASES_SIGN_H + +/* + test_cases_sign.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int total_tests; +extern int test_meshlink_sign(void); + +#endif // TEST_CASES_SIGN_H diff --git a/test/blackbox/run_blackbox_tests/test_cases_start.c b/test/blackbox/run_blackbox_tests/test_cases_start.c new file mode 100644 index 0000000..be29b33 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_start.c @@ -0,0 +1,131 @@ +/* + test_cases_start.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_start.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include + +static void test_case_mesh_start_01(void **state); +static bool test_steps_mesh_start_01(void); +static void test_case_mesh_start_02(void **state); +static bool test_steps_mesh_start_02(void); + +/* State structure for meshlink_start Test Case #1 */ +static black_box_state_t test_mesh_start_01_state = { + .test_case_name = "test_case_mesh_start_01", +}; + +/* State structure for meshlink_start Test Case #2 */ +static black_box_state_t test_mesh_start_02_state = { + .test_case_name = "test_case_mesh_start_02", +}; + +/* Execute meshlink_start Test Case # 1*/ +static void test_case_mesh_start_01(void **state) { + execute_test(test_steps_mesh_start_01, state); +} + +/* Test Steps for meshlink_start Test Case # 1 + + Test Steps: + 1. Open Instance & start node + + Expected Result: + Successfully node instance should be running +*/ +static bool test_steps_mesh_start_01(void) { + + // Open instance + + bool result = false; + meshlink_handle_t *mesh = meshlink_open("start_conf", "foo", "test", DEV_CLASS_STATIONARY); + assert(mesh); + + // Run node instance + + result = meshlink_start(mesh); + + // Clean up + meshlink_close(mesh); + assert(meshlink_destroy("start_conf")); + + if(!result) { + fprintf(stderr, "meshlink_start status1: %s\n", meshlink_strerror(meshlink_errno)); + return false; + } else { + return true; + } +} + +/* Execute meshlink_start Test Case # 2*/ +static void test_case_mesh_start_02(void **state) { + execute_test(test_steps_mesh_start_02, state); +} + +/* Test Steps for meshlink_start Test Case # 2 + + Test Steps: + 1. Calling meshlink_start with NULL as mesh handle argument. + + Expected Result: + meshlink_start API handles the invalid parameter by returning false. +*/ +static bool test_steps_mesh_start_02(void) { + bool result = false; + assert(meshlink_destroy("start_conf")); + meshlink_handle_t *mesh = meshlink_open("start_conf", "foo", "test", DEV_CLASS_STATIONARY); + assert(mesh); + + // Run instance with NULL argument + + result = meshlink_start(NULL); + assert_int_equal(result, true); + + // Clean up + + meshlink_close(mesh); + assert(meshlink_destroy("start_conf")); + return true; +} + +int test_meshlink_start(void) { + const struct CMUnitTest blackbox_start_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_start_01, NULL, NULL, + (void *)&test_mesh_start_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_start_02, NULL, NULL, + (void *)&test_mesh_start_02_state) + + }; + + total_tests += sizeof(blackbox_start_tests) / sizeof(blackbox_start_tests[0]); + + return cmocka_run_group_tests(blackbox_start_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_start.h b/test/blackbox/run_blackbox_tests/test_cases_start.h new file mode 100644 index 0000000..835cfac --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_start.h @@ -0,0 +1,30 @@ +#ifndef TEST_CASES_START_H +#define TEST_CASES_START_H + +/* + test_cases_start.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_start(void); +extern int total_tests; + + + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_status_cb.c b/test/blackbox/run_blackbox_tests/test_cases_status_cb.c new file mode 100644 index 0000000..fedf3e6 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_status_cb.c @@ -0,0 +1,179 @@ +/* + test_cases_status_cb.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_status_cb.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include +#include + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +static void test_case_set_status_cb_01(void **state); +static bool test_set_status_cb_01(void); +static void test_case_set_status_cb_02(void **state); +static bool test_set_status_cb_02(void); + +/* status variable gives access to the status callback to know whether invoked or not */ +static bool status; + +/* State structure for status callback Test Case #1 */ +static black_box_state_t test_case_set_status_cb_01_state = { + .test_case_name = "test_case_set_status_cb_01", +}; + +/* State structure for status callback Test Case #2 */ +static black_box_state_t test_case_set_status_cb_02_state = { + .test_case_name = "test_case_set_status_cb_02", +}; + + +static void status_cb(meshlink_handle_t *mesh, meshlink_node_t *source, bool reach) { + (void)mesh; + + fprintf(stderr, "In status callback\n"); + + if(reach) { + fprintf(stderr, "[ %s ] node reachable\n", source->name); + } else { + fprintf(stderr, "[ %s ] node not reachable\n", source->name) ; + } + + status = reach; +} + +/* Execute status callback Test Case # 1 - valid case */ +static void test_case_set_status_cb_01(void **state) { + execute_test(test_set_status_cb_01, state); +} + +/* Test Steps for meshlink_set_status_cb Test Case # 1 + + Test Steps: + 1. Run bar and nut node instances + 2. Set status callback for the NUT and Start NUT + + Expected Result: + status callback should be invoked when NUT connects/disconnects with 'relay' node. +*/ +static bool test_set_status_cb_01(void) { + assert(meshlink_destroy("set_status_cb_conf.1")); + assert(meshlink_destroy("set_status_cb_conf.2")); + + // Opening NUT and bar nodes + meshlink_handle_t *mesh1 = meshlink_open("set_status_cb_conf.1", "nut", "test", DEV_CLASS_STATIONARY); + assert(mesh1 != NULL); + meshlink_set_log_cb(mesh1, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_handle_t *mesh2 = meshlink_open("set_status_cb_conf.2", "bar", "test", DEV_CLASS_STATIONARY); + assert(mesh2 != NULL); + meshlink_set_log_cb(mesh2, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + + // Set up callback for node status + meshlink_set_node_status_cb(mesh1, status_cb); + + // Exporting and Importing mutually + char *exp1 = meshlink_export(mesh1); + assert(exp1 != NULL); + char *exp2 = meshlink_export(mesh2); + assert(exp2 != NULL); + assert(meshlink_import(mesh1, exp2)); + assert(meshlink_import(mesh2, exp1)); + + assert(meshlink_start(mesh1)); + assert(meshlink_start(mesh2)); + sleep(1); + + // Test for status from status callback + assert_int_equal(status, true); + + meshlink_close(mesh2); + sleep(1); + + // Test for status from status callback + assert_int_equal(status, false); + + free(exp1); + free(exp2); + meshlink_close(mesh1); + assert(meshlink_destroy("set_status_cb_conf.1")); + assert(meshlink_destroy("set_status_cb_conf.2")); + + return true; +} + +/* Execute status callback Test Case # 2 - Invalid case */ +static void test_case_set_status_cb_02(void **state) { + execute_test(test_set_status_cb_02, state); +} + +/* Test Steps for meshlink_set_status_cb Test Case # 2 + + Test Steps: + 1. Calling meshlink_set_status_cb with NULL as mesh handle argument. + + Expected Result: + set poll callback handles the invalid parameter when called by giving proper error number. +*/ +static bool test_set_status_cb_02(void) { + + // Create meshlink instance + + assert(meshlink_destroy("set_status_cb_conf.3")); + meshlink_handle_t *mesh_handle = meshlink_open("set_status_cb_conf.3", "nut", "node_sim", 1); + assert(mesh_handle); + + // Pass NULL as meshlink_set_node_status_cb's argument + + meshlink_set_node_status_cb(NULL, status_cb); + meshlink_errno_t meshlink_errno_buff = meshlink_errno; + assert_int_equal(meshlink_errno_buff, MESHLINK_EINVAL); + + // Clean up + + meshlink_close(mesh_handle); + assert(meshlink_destroy("set_status_cb_conf.3")); + return true; +} + + +int test_meshlink_set_status_cb(void) { + const struct CMUnitTest blackbox_status_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_set_status_cb_01, NULL, NULL, + (void *)&test_case_set_status_cb_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_set_status_cb_02, NULL, NULL, + (void *)&test_case_set_status_cb_02_state) + }; + total_tests += sizeof(blackbox_status_tests) / sizeof(blackbox_status_tests[0]); + + int failed = cmocka_run_group_tests(blackbox_status_tests, NULL, NULL); + + return failed; +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_status_cb.h b/test/blackbox/run_blackbox_tests/test_cases_status_cb.h new file mode 100644 index 0000000..16e03e3 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_status_cb.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_SET_STATUS_CB_H +#define TEST_CASES_SET_STATUS_CB_H + +/* + test_cases_status_cb.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_set_status_cb(void); +extern int total_tests; + +#endif // TEST_CASES_SET_STATUS_CB_H diff --git a/test/blackbox/run_blackbox_tests/test_cases_stop_close.c b/test/blackbox/run_blackbox_tests/test_cases_stop_close.c new file mode 100644 index 0000000..991171a --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_stop_close.c @@ -0,0 +1,94 @@ +/* + test_cases_stop_close.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_stop_close.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include +#include + +#define CLOSE_FILE_PATH "/home/sairoop/meshlink/test/blackbox/test_case_close/mesh_close" +#define VALGRIND_LOG "valgrind.log" + +static void test_case_mesh_close_01(void **state); +static bool test_steps_mesh_close_01(void); +static void test_case_mesh_stop_01(void **state); +static bool test_steps_mesh_stop_01(void); + +/* State structure for meshlink_close Test Case #1 */ +static black_box_state_t test_mesh_close_01_state = { + .test_case_name = "test_case_mesh_close_01", +}; + +/* State structure for meshlink_close Test Case #1 */ +static black_box_state_t test_mesh_stop_01_state = { + .test_case_name = "test_case_mesh_stop_01", +}; + +/* Execute meshlink_close Test Case # 1*/ +static void test_case_mesh_close_01(void **state) { + execute_test(test_steps_mesh_close_01, state); +} + +/* Test Steps for meshlink_close Test Case # 1*/ + +static bool test_steps_mesh_close_01(void) { + meshlink_close(NULL); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + return true; +} + +/* Execute meshlink_stop Test Case # 1*/ +static void test_case_mesh_stop_01(void **state) { + execute_test(test_steps_mesh_stop_01, state); +} + +/* Test Steps for meshlink_stop Test Case # 1*/ +static bool test_steps_mesh_stop_01(void) { + meshlink_stop(NULL); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + return true; +} + +int test_meshlink_stop_close(void) { + const struct CMUnitTest blackbox_stop_close_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_stop_01, NULL, NULL, + (void *)&test_mesh_stop_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_close_01, NULL, NULL, + (void *)&test_mesh_close_01_state) + }; + + total_tests += sizeof(blackbox_stop_close_tests) / sizeof(blackbox_stop_close_tests[0]); + + return cmocka_run_group_tests(blackbox_stop_close_tests, NULL, NULL); +} + diff --git a/test/blackbox/run_blackbox_tests/test_cases_stop_close.h b/test/blackbox/run_blackbox_tests/test_cases_stop_close.h new file mode 100644 index 0000000..ede4e5b --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_stop_close.h @@ -0,0 +1,29 @@ +#ifndef TEST_CASES_STOP_CLOSE_H +#define TEST_CASES_STOP_CLOSE_H + +/* + test_cases_stop_close.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_stop_close(void); +extern int total_tests; + + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_cases_submesh01.c b/test/blackbox/run_blackbox_tests/test_cases_submesh01.c new file mode 100644 index 0000000..9c1b432 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_submesh01.c @@ -0,0 +1,186 @@ +/* + test_cases_submesh.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include "execute_tests.h" +#include "test_cases_submesh01.h" +#include "pthread.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "../common/mesh_event_handler.h" + +#define CORENODE1_ID "0" +#define APP1NODE1_ID "1" +#define APP2NODE1_ID "2" +#define CORENODE2_ID "3" +#define APP1NODE2_ID "4" +#define APP2NODE2_ID "5" + +#define INIT_ST 0 + +static bool test_case_status = false; + +static void test_case_submesh_01(void **state); +static bool test_steps_submesh_01(void); + +static char event_node_name[][10] = {"CORENODE1", "APP1NODE1", "APP2NODE1", "CORENODE2", + "APP1NODE2", "APP2NODE2" + }; +static const char *node_ids[] = { "corenode1", "app1node1", "app2node1", "corenode2", + "app1node2", "app2node2" + }; + +static mesh_event_t core_node1[] = { NODE_STARTED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED}; + +static mesh_event_t core_node2[] = { NODE_STARTED, NODE_JOINED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED}; + +static mesh_event_t app1_node1[] = { NODE_STARTED, NODE_JOINED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED}; + +static mesh_event_t app2_node1[] = { NODE_STARTED, NODE_JOINED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED}; + +static mesh_event_t app1_node2[] = { NODE_STARTED, NODE_JOINED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED, MESH_EVENT_COMPLETED}; + +static mesh_event_t app2_node2[] = { NODE_STARTED, NODE_JOINED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED, MESH_EVENT_COMPLETED}; + +/* State structure for SubMesh Test Case #1 */ +static char *test_case_submesh_1_nodes[] = { "corenode1", "app1node1", "app2node1", "corenode2", "app1node2", "app2node2" }; +static black_box_state_t test_case_submesh_1_state = { + .test_case_name = "test_cases_submesh01", + .node_names = test_case_submesh_1_nodes, + .num_nodes = 6 +}; + +static int black_box_group0_setup(void **state) { + (void)state; + + const char *nodes[] = { "corenode1", "app1node1", "app2node1", "corenode2", "app1node2", "app2node2" }; + int num_nodes = sizeof(nodes) / sizeof(nodes[0]); + + PRINT_TEST_CASE_MSG("Creating Containers\n"); + destroy_containers(); + create_containers(nodes, num_nodes); + + return 0; +} + +static int black_box_group0_teardown(void **state) { + (void)state; + + PRINT_TEST_CASE_MSG("Destroying Containers\n"); + destroy_containers(); + + return 0; +} + +static bool event_cb(mesh_event_payload_t payload) { + static node_status_t node_status[6] = { + {core_node1, 0, 3}, + {app1_node1, 0, 4}, + {app2_node1, 0, 4}, + {core_node2, 0, 4}, + {app1_node2, 0, 7}, + {app2_node2, 0, 7}, + }; + + fprintf(stderr, "%s(%lu) : %s\n", event_node_name[payload.client_id], time(NULL), event_status[payload.mesh_event]); + assert(change_state(&node_status[payload.client_id], payload.mesh_event)); + + if(payload.mesh_event == NODE_JOINED) { + signal_node_start(node_status, 1, 5, (char **)node_ids); + } + + if(check_nodes_finished(node_status, 6)) { + test_case_status = true; + return true; + } + + return false; +} + +/* Execute SubMesh Test Case # 1 */ +static void test_case_submesh_01(void **state) { + execute_test(test_steps_submesh_01, state); +} + +/* Test Steps for SubMesh Test Case # 1 + + Test Steps: + 1. Run corenode1, app1node1, app2node1, corenode2, app1node2 and app2node2 + 2. Generate invites to app1node1, app2node1, corenode2, app1node2 and app2node2 + from corenode1 to join corenode1. + 3. After Join is successful start channels from all nodes and exchange data on channels + 4. Try to fetch the node handle of one sub-mesh node from node in another sub-mesh + + Expected Result: + Channels should be formed between nodes of sub-mesh & coremesh, nodes with in sub-mesh + and should be able to exchange data. But node in one sub-mesh should not get the details + of node in another sub-mesh. +*/ +static bool test_steps_submesh_01(void) { + char *invite_corenode2, *invite_app1node1, *invite_app2node1, *invite_app1node2, *invite_app2node2; + char *import; + + import = mesh_event_sock_create(eth_if_name); + invite_corenode2 = invite_in_container("corenode1", "corenode2"); + invite_app1node1 = submesh_invite_in_container("corenode1", "app1node1", "app1"); + invite_app2node1 = submesh_invite_in_container("corenode1", "app2node1", "app2"); + invite_app1node2 = submesh_invite_in_container("corenode1", "app1node2", "app1"); + invite_app2node2 = submesh_invite_in_container("corenode1", "app2node2", "app2"); + + node_sim_in_container_event("corenode1", "1", NULL, CORENODE1_ID, import); + node_sim_in_container_event("corenode2", "1", invite_corenode2, CORENODE2_ID, import); + node_sim_in_container_event("app1node1", "1", invite_app1node1, APP1NODE1_ID, import); + node_sim_in_container_event("app2node1", "1", invite_app2node1, APP2NODE1_ID, import); + node_sim_in_container_event("app1node2", "1", invite_app1node2, APP1NODE2_ID, import); + node_sim_in_container_event("app2node2", "1", invite_app2node2, APP2NODE2_ID, import); + + PRINT_TEST_CASE_MSG("Waiting for nodes to get connected with corenode1\n"); + + assert(wait_for_event(event_cb, 240)); + assert(test_case_status); + + free(invite_corenode2); + free(invite_app1node1); + free(invite_app2node1); + free(invite_app1node2); + free(invite_app2node2); + + mesh_event_destroy(); + + return true; +} + +int test_cases_submesh01(void) { + const struct CMUnitTest blackbox_group0_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_submesh_01, setup_test, teardown_test, + (void *)&test_case_submesh_1_state) + }; + total_tests += sizeof(blackbox_group0_tests) / sizeof(blackbox_group0_tests[0]); + + return cmocka_run_group_tests(blackbox_group0_tests, black_box_group0_setup, black_box_group0_teardown); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_submesh01.h b/test/blackbox/run_blackbox_tests/test_cases_submesh01.h new file mode 100644 index 0000000..d4ebcca --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_submesh01.h @@ -0,0 +1,30 @@ +#ifndef TEST_CASES_SUBMESH_H +#define TEST_CASES_SUBMESH_H + +/* + test_cases_submesh01.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + + +#include +#include "../common/mesh_event_handler.h" + +extern int total_tests; +extern int test_cases_submesh01(void); + +#endif // TEST_CASES_SUBMESH_H \ No newline at end of file diff --git a/test/blackbox/run_blackbox_tests/test_cases_submesh02.c b/test/blackbox/run_blackbox_tests/test_cases_submesh02.c new file mode 100644 index 0000000..9f21850 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_submesh02.c @@ -0,0 +1,189 @@ +/* + test_cases_submesh02.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include "execute_tests.h" +#include "test_cases_submesh02.h" +#include "pthread.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "../common/mesh_event_handler.h" + +#define CORENODE1_ID "0" +#define APP1NODE1_ID "1" +#define APP2NODE1_ID "2" +#define CORENODE2_ID "3" +#define APP1NODE2_ID "4" +#define APP2NODE2_ID "5" + +#define INIT_ST 0 + +static bool test_case_status = false; + +static void test_case_submesh_02(void **state); +static bool test_steps_submesh_02(void); + +static char event_node_name[][10] = {"CORENODE1", "APP1NODE1", "APP2NODE1", "CORENODE2", + "APP1NODE2", "APP2NODE2" + }; +static const char *node_ids[] = { "corenode1", "app1node1", "app2node1", "corenode2", + "app1node2", "app2node2" + }; + +static mesh_event_t core_node1[] = { NODE_STARTED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED}; + +static mesh_event_t core_node2[] = { NODE_STARTED, NODE_JOINED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED}; + +static mesh_event_t app1_node1[] = { NODE_STARTED, NODE_JOINED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED}; + +static mesh_event_t app2_node1[] = { NODE_STARTED, NODE_JOINED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED}; + +static mesh_event_t app1_node2[] = { NODE_STARTED, NODE_JOINED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED, MESH_EVENT_COMPLETED}; + +static mesh_event_t app2_node2[] = { NODE_STARTED, NODE_JOINED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED, MESH_EVENT_COMPLETED}; + +/* State structure for SubMesh Test Case #1 */ +static char *test_case_submesh_2_nodes[] = { "corenode1", "app1node1", "app2node1", "corenode2", "app1node2", "app2node2" }; +static black_box_state_t test_case_submesh_2_state = { + .test_case_name = "test_cases_submesh02", + .node_names = test_case_submesh_2_nodes, + .num_nodes = 6 +}; + +static int black_box_group0_setup(void **state) { + (void)state; + + const char *nodes[] = { "corenode1", "app1node1", "app2node1", "corenode2", "app1node2", "app2node2" }; + int num_nodes = sizeof(nodes) / sizeof(nodes[0]); + + PRINT_TEST_CASE_MSG("Creating Containers\n"); + destroy_containers(); + create_containers(nodes, num_nodes); + + return 0; +} + +static int black_box_group0_teardown(void **state) { + (void)state; + + PRINT_TEST_CASE_MSG("Destroying Containers\n"); + destroy_containers(); + + return 0; +} + +static bool event_cb(mesh_event_payload_t payload) { + static node_status_t node_status[6] = { + {core_node1, 0, 3}, + {app1_node1, 0, 4}, + {app2_node1, 0, 4}, + {core_node2, 0, 4}, + {app1_node2, 0, 7}, + {app2_node2, 0, 7}, + }; + + fprintf(stderr, "%s(%lu) : %s\n", event_node_name[payload.client_id], time(NULL), event_status[payload.mesh_event]); + assert(change_state(&node_status[payload.client_id], payload.mesh_event)); + + if(payload.mesh_event == NODE_JOINED) { + signal_node_start(node_status, 1, 5, (char **)node_ids); + } + + if(check_nodes_finished(node_status, 6)) { + test_case_status = true; + return true; + } + + return false; +} + +/* Execute SubMesh Test Case # 2 */ +static void test_case_submesh_02(void **state) { + execute_test(test_steps_submesh_02, state); +} + +/* Test Steps for SubMesh Test Case # 2 + + Test Steps: + 1. Run corenode1, app1node1, app2node1, corenode2, app1node2 and app2node2 + 2. Generate invites to app1node1, app2node1, corenode2, app1node2 and app2node2 + from corenode1 to join corenode1. + 3. After Join is successful start channels from all nodes and exchange data on channels + 4. Try to fetch the list of all nodes and check if the nodes in other submesh does not + appear in the list. + 5. Try fetch all the nodes with a submesh handle and check only if both the nodes joining + the submesh are present. + + Expected Result: + Channels should be formed between nodes of sub-mesh & coremesh, nodes with in sub-mesh + and should be able to exchange data. Lis of all nodes should only contain four nodes + and the list of submesh should only contain two nodes of that submesh. +*/ +static bool test_steps_submesh_02(void) { + char *invite_corenode2, *invite_app1node1, *invite_app2node1, *invite_app1node2, *invite_app2node2; + char *import; + + import = mesh_event_sock_create(eth_if_name); + invite_corenode2 = invite_in_container("corenode1", "corenode2"); + invite_app1node1 = submesh_invite_in_container("corenode1", "app1node1", "app1"); + invite_app2node1 = submesh_invite_in_container("corenode1", "app2node1", "app2"); + invite_app1node2 = submesh_invite_in_container("corenode1", "app1node2", "app1"); + invite_app2node2 = submesh_invite_in_container("corenode1", "app2node2", "app2"); + + node_sim_in_container_event("corenode1", "1", NULL, CORENODE1_ID, import); + node_sim_in_container_event("corenode2", "1", invite_corenode2, CORENODE2_ID, import); + node_sim_in_container_event("app1node1", "1", invite_app1node1, APP1NODE1_ID, import); + node_sim_in_container_event("app2node1", "1", invite_app2node1, APP2NODE1_ID, import); + node_sim_in_container_event("app1node2", "1", invite_app1node2, APP1NODE2_ID, import); + node_sim_in_container_event("app2node2", "1", invite_app2node2, APP2NODE2_ID, import); + + PRINT_TEST_CASE_MSG("Waiting for nodes to get connected with corenode1\n"); + + assert(wait_for_event(event_cb, 240)); + assert(test_case_status); + + free(invite_corenode2); + free(invite_app1node1); + free(invite_app2node1); + free(invite_app1node2); + free(invite_app2node2); + + mesh_event_destroy(); + + return true; +} + +int test_cases_submesh02(void) { + const struct CMUnitTest blackbox_group0_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_submesh_02, setup_test, teardown_test, + (void *)&test_case_submesh_2_state) + }; + total_tests += sizeof(blackbox_group0_tests) / sizeof(blackbox_group0_tests[0]); + + return cmocka_run_group_tests(blackbox_group0_tests, black_box_group0_setup, black_box_group0_teardown); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_submesh02.h b/test/blackbox/run_blackbox_tests/test_cases_submesh02.h new file mode 100644 index 0000000..a571038 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_submesh02.h @@ -0,0 +1,30 @@ +#ifndef TEST_CASES_SUBMESH02_H +#define TEST_CASES_SUBMESH02_H + +/* + test_cases_submesh02.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + + +#include +#include "../common/mesh_event_handler.h" + +extern int total_tests; +extern int test_cases_submesh02(void); + +#endif // TEST_CASES_SUBMESH02_H \ No newline at end of file diff --git a/test/blackbox/run_blackbox_tests/test_cases_submesh03.c b/test/blackbox/run_blackbox_tests/test_cases_submesh03.c new file mode 100644 index 0000000..18a8025 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_submesh03.c @@ -0,0 +1,184 @@ +/* + test_cases_submesh03.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include "execute_tests.h" +#include "test_cases_submesh03.h" +#include "pthread.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "../common/mesh_event_handler.h" + +#define CORENODE1_ID "0" +#define APP1NODE1_ID "1" +#define APP1NODE2_ID "2" + +#define INIT_ST 0 + +static bool test_case_status = false; + +static void test_case_submesh_03(void **state); +static bool test_steps_submesh_03(void); + +static char event_node_name[][10] = {"CORENODE1", "APP1NODE1", "APP1NODE2"}; +static const char *node_ids[] = { "corenode1", "app1node1", "app1node2" }; + +static mesh_event_t core_node1[] = { NODE_STARTED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED}; + +static mesh_event_t app1_node1[] = { NODE_STARTED, NODE_JOINED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED}; + +static mesh_event_t app1_node2[] = { NODE_STARTED, NODE_JOINED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED, MESH_EVENT_COMPLETED}; + +static node_status_t node_status[3] = { + {core_node1, 0, 3}, + {app1_node1, 0, 4}, + {app1_node2, 0, 7}, +}; + +/* State structure for SubMesh Test Case #3 */ +static char *test_case_submesh_3_nodes[] = { "corenode1", "app1node1", "app1node2" }; +static black_box_state_t test_case_submesh_3_state = { + .test_case_name = "test_cases_submesh03", + .node_names = test_case_submesh_3_nodes, + .num_nodes = 3 +}; + +static int black_box_group0_setup(void **state) { + (void)state; + + const char *nodes[] = { "corenode1", "app1node1", "app1node2" }; + int num_nodes = sizeof(nodes) / sizeof(nodes[0]); + + PRINT_TEST_CASE_MSG("Creating Containers\n"); + destroy_containers(); + create_containers(nodes, num_nodes); + + return 0; +} + +static int black_box_group0_teardown(void **state) { + (void)state; + + PRINT_TEST_CASE_MSG("Destroying Containers\n"); + destroy_containers(); + + return 0; +} + +static void restart_all_nodes(char *import) { + int i; + + for(i = 0; i < 3; i++) { + node_step_in_container(node_ids[i], "SIGTERM"); + node_status[i].current_index = 0; + } + + sleep(5); + + node_sim_in_container_event("corenode1", "1", NULL, CORENODE1_ID, import); + node_sim_in_container_event("app1node1", "1", NULL, APP1NODE1_ID, import); + node_sim_in_container_event("app1node2", "1", NULL, APP1NODE2_ID, import); +} + +static bool event_cb(mesh_event_payload_t payload) { + + fprintf(stderr, "%s(%lu) : %s\n", event_node_name[payload.client_id], time(NULL), event_status[payload.mesh_event]); + assert(change_state(&node_status[payload.client_id], payload.mesh_event)); + + if(payload.mesh_event == NODE_JOINED) { + signal_node_start(node_status, 1, 2, (char **)node_ids); + } + + if(check_nodes_finished(node_status, 3)) { + test_case_status = true; + return true; + } + + return false; +} + +/* Execute SubMesh Test Case # 3 */ +static void test_case_submesh_03(void **state) { + execute_test(test_steps_submesh_03, state); +} + +/* Test Steps for SubMesh Test Case # 3 + + Test Steps: + 1. Run corenode1, app1node1, and app1node2 + 2. Generate invites to app1node1, and app1node2 + from corenode1 to join corenode1. + 3. After Join is successful start channels from all nodes and exchange data on channels + 4. Try to restart all the nodes at the same time. + + Expected Result: + Channels should be formed between nodes of sub-mesh & coremesh, nodes with in sub-mesh + and should be able to exchange data even after restart. +*/ +static bool test_steps_submesh_03(void) { + char *invite_app1node1, *invite_app1node2; + char *import; + + import = mesh_event_sock_create(eth_if_name); + invite_app1node1 = invite_in_container("corenode1", "app1node1"); + invite_app1node2 = invite_in_container("corenode1", "app1node2"); + + node_sim_in_container_event("corenode1", "1", NULL, CORENODE1_ID, import); + node_sim_in_container_event("app1node1", "1", invite_app1node1, APP1NODE1_ID, import); + node_sim_in_container_event("app1node2", "1", invite_app1node2, APP1NODE2_ID, import); + + PRINT_TEST_CASE_MSG("Waiting for nodes to get connected with corenode1\n"); + + assert(wait_for_event(event_cb, 120)); + assert(test_case_status); + + test_case_status = false; + + restart_all_nodes(import); + PRINT_TEST_CASE_MSG("Waiting for nodes to get restarted\n"); + + assert(wait_for_event(event_cb, 120)); + assert(test_case_status); + + free(invite_app1node1); + free(invite_app1node2); + + mesh_event_destroy(); + + return true; +} + +int test_cases_submesh03(void) { + const struct CMUnitTest blackbox_group0_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_submesh_03, setup_test, teardown_test, + (void *)&test_case_submesh_3_state) + }; + total_tests += sizeof(blackbox_group0_tests) / sizeof(blackbox_group0_tests[0]); + + return cmocka_run_group_tests(blackbox_group0_tests, black_box_group0_setup, black_box_group0_teardown); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_submesh03.h b/test/blackbox/run_blackbox_tests/test_cases_submesh03.h new file mode 100644 index 0000000..432c1e0 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_submesh03.h @@ -0,0 +1,30 @@ +#ifndef TEST_CASES_SUBMESH03_H +#define TEST_CASES_SUBMESH03_H + +/* + test_cases_submesh03.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + + +#include +#include "../common/mesh_event_handler.h" + +extern int total_tests; +extern int test_cases_submesh03(void); + +#endif // TEST_CASES_SUBMESH03_H \ No newline at end of file diff --git a/test/blackbox/run_blackbox_tests/test_cases_submesh04.c b/test/blackbox/run_blackbox_tests/test_cases_submesh04.c new file mode 100644 index 0000000..5476423 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_submesh04.c @@ -0,0 +1,163 @@ +/* + test_cases_submesh05.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include "execute_tests.h" +#include "test_cases_submesh04.h" +#include "pthread.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "../common/mesh_event_handler.h" + +#define CORENODE1_ID "0" +#define APP1NODE1_ID "1" +#define APP1NODE2_ID "2" + +#define INIT_ST 0 + +static bool test_case_status = false; + +static void test_case_submesh_04(void **state); +static bool test_steps_submesh_04(void); + +static char event_node_name[][10] = {"CORENODE1", "APP1NODE1", "APP1NODE2"}; +static const char *node_ids[] = { "corenode1", "app1node1", "app1node2" }; + +static mesh_event_t core_node1[] = { NODE_STARTED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED }; + +static mesh_event_t app1_node1[] = { NODE_STARTED, NODE_JOINED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED }; + +static mesh_event_t app1_node2[] = { NODE_STARTED, NODE_JOINED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED, CHANNEL_OPENED, CHANNEL_DATA_RECIEVED, MESH_EVENT_COMPLETED}; + +/* State structure for SubMesh Test Case #4 */ +static char *test_case_submesh_4_nodes[] = { "corenode1", "app1node1", "app1node2" }; +static black_box_state_t test_case_submesh_4_state = { + .test_case_name = "test_cases_submesh04", + .node_names = test_case_submesh_4_nodes, + .num_nodes = 3 +}; + +static int black_box_group0_setup(void **state) { + (void)state; + + const char *nodes[] = { "corenode1", "app1node1", "app1node2" }; + int num_nodes = sizeof(nodes) / sizeof(nodes[0]); + + PRINT_TEST_CASE_MSG("Creating Containers\n"); + destroy_containers(); + create_containers(nodes, num_nodes); + + return 0; +} + +static int black_box_group0_teardown(void **state) { + (void)state; + + PRINT_TEST_CASE_MSG("Destroying Containers\n"); + destroy_containers(); + + return 0; +} + +static bool event_cb(mesh_event_payload_t payload) { + static node_status_t node_status[3] = { + {core_node1, 0, 3}, + {app1_node1, 0, 4}, + {app1_node2, 0, 7}, + }; + + fprintf(stderr, "%s(%lu) : %s\n", event_node_name[payload.client_id], time(NULL), event_status[payload.mesh_event]); + assert(change_state(&node_status[payload.client_id], payload.mesh_event)); + + if(payload.mesh_event == NODE_JOINED) { + signal_node_start(node_status, 1, 2, (char **)node_ids); + } + + if(check_nodes_finished(node_status, 3)) { + test_case_status = true; + return true; + } + + return false; +} + +/* Execute SubMesh Test Case # 4 */ +static void test_case_submesh_04(void **state) { + execute_test(test_steps_submesh_04, state); +} + +/* Test Steps for SubMesh Test Case # 4 + + Test Steps: + 1. Run corenode1, app1node1, and app1node2 + 2. Generate invites to app1node1, app1node2 + from corenode1 to join corenode1. + 3. After Join is successful start channels from all nodes and exchange data on channels + 4. Black list a node in the submesh and check if it is successful + 5. White list the node and it should be form all the connections again + + Expected Result: + Channels should be formed between nodes of sub-mesh & coremesh, nodes with in sub-mesh + and should be able to exchange data. When black listed, other node should not get any + from the black listed node. When white listed again it has to form the connections as + they were previously before black listing. +*/ +static bool test_steps_submesh_04(void) { + char *invite_app1node1, *invite_app1node2; + char *import; + + import = mesh_event_sock_create(eth_if_name); + invite_app1node1 = invite_in_container("corenode1", "app1node1"); + invite_app1node2 = invite_in_container("corenode1", "app1node2"); + + node_sim_in_container_event("corenode1", "1", NULL, CORENODE1_ID, import); + node_sim_in_container_event("app1node1", "1", invite_app1node1, APP1NODE1_ID, import); + node_sim_in_container_event("app1node2", "1", invite_app1node2, APP1NODE2_ID, import); + + PRINT_TEST_CASE_MSG("Waiting for nodes to get connected with corenode1\n"); + + assert(wait_for_event(event_cb, 120)); + assert(test_case_status); + + free(invite_app1node1); + free(invite_app1node2); + + mesh_event_destroy(); + + return true; +} + +int test_cases_submesh04(void) { + const struct CMUnitTest blackbox_group0_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_submesh_04, setup_test, teardown_test, + (void *)&test_case_submesh_4_state) + }; + total_tests += sizeof(blackbox_group0_tests) / sizeof(blackbox_group0_tests[0]); + + return cmocka_run_group_tests(blackbox_group0_tests, black_box_group0_setup, black_box_group0_teardown); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_submesh04.h b/test/blackbox/run_blackbox_tests/test_cases_submesh04.h new file mode 100644 index 0000000..be7a22b --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_submesh04.h @@ -0,0 +1,30 @@ +#ifndef TEST_CASES_SUBMESH04_H +#define TEST_CASES_SUBMESH04_H + +/* + test_cases_submesh04.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + + +#include +#include "../common/mesh_event_handler.h" + +extern int total_tests; +extern int test_cases_submesh04(void); + +#endif // TEST_CASES_SUBMESH04_H \ No newline at end of file diff --git a/test/blackbox/run_blackbox_tests/test_cases_verify.c b/test/blackbox/run_blackbox_tests/test_cases_verify.c new file mode 100644 index 0000000..f811e16 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_verify.c @@ -0,0 +1,384 @@ +/* + test_cases_verify.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_verify.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include + +/* Modify this to change the logging level of Meshlink */ +#define TEST_MESHLINK_LOG_LEVEL MESHLINK_DEBUG + +static void test_case_verify_01(void **state); +static bool test_verify_01(void); +static void test_case_verify_02(void **state); +static bool test_verify_02(void); +static void test_case_verify_03(void **state); +static bool test_verify_03(void); +static void test_case_verify_04(void **state); +static bool test_verify_04(void); +static void test_case_verify_05(void **state); +static bool test_verify_05(void); +static void test_case_verify_06(void **state); +static bool test_verify_06(void); + +/* State structure for verify API Test Case #1 */ +static black_box_state_t test_case_verify_01_state = { + .test_case_name = "test_case_verify_01", +}; + +/* State structure for verify API Test Case #2 */ +static black_box_state_t test_case_verify_02_state = { + .test_case_name = "test_case_verify_02", +}; + +/* State structure for verify API Test Case #3 */ +static black_box_state_t test_case_verify_03_state = { + .test_case_name = "test_case_verify_03", +}; + +/* State structure for verify API Test Case #4 */ +static black_box_state_t test_case_verify_04_state = { + .test_case_name = "test_case_verify_04", +}; + +/* State structure for verify API Test Case #5 */ +static black_box_state_t test_case_verify_05_state = { + .test_case_name = "test_case_verify_05", +}; + +/* State structure for verify API Test Case #6 */ +static black_box_state_t test_case_verify_06_state = { + .test_case_name = "test_case_verify_06", +}; + + + +/* Execute meshlink_verify Test Case # 1 - Valid case - verify a data successfully*/ +void test_case_verify_01(void **state) { + execute_test(test_verify_01, state); +} + +/* Test Steps for meshlink_sign Test Case # 1 - Valid case + + Test Steps: + 1. Run NUT(Node Under Test) + 2. Sign data with meshlink_sign + 3. Verify data with the sign buffer used while signing + + Expected Result: + Verifies data successfully with the apt signature +*/ +bool test_verify_01(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_handle_t *mesh_handle = meshlink_open("verifyconf", "nut", "node_sim", DEV_CLASS_BACKBONE); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + assert(meshlink_start(mesh_handle)); + + char *data = "Test"; + char sig[MESHLINK_SIGLEN]; + size_t ssize = MESHLINK_SIGLEN; + bool ret = meshlink_sign(mesh_handle, data, strlen(data) + 1, sig, &ssize); + assert(ret); + + meshlink_node_t *source = meshlink_get_node(mesh_handle, "nut"); + assert(source); + ret = meshlink_verify(mesh_handle, source, data, strlen(data) + 1, sig, ssize); + meshlink_close(mesh_handle); + assert(meshlink_destroy("verifyconf")); + + if(!ret) { + PRINT_TEST_CASE_MSG("meshlink_verify FAILED to verify data\n"); + return false; + } + + PRINT_TEST_CASE_MSG("meshlink_verify Successfully verified data\n"); + return true; +} + + +/* Execute verify_data Test Case # 2 - Invalid case - meshlink_verify passing NULL args*/ +void test_case_verify_02(void **state) { + execute_test(test_verify_02, state); +} + +/* Test Steps for meshlink_sign Test Case # 2 - Invalid case + + Test Steps: + 1. Run NUT(Node Under Test) + 2. Sign data with meshlink_sign + 3. Trying to pass NULL as mesh handle argument + and other arguments being valid + + Expected Result: + Reports error accordingly by returning false +*/ +bool test_verify_02(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_handle_t *mesh_handle = meshlink_open("verifyconf", "nut", "node_sim", DEV_CLASS_BACKBONE); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + assert(meshlink_start(mesh_handle)); + + char *data = "Test"; + char sig[MESHLINK_SIGLEN]; + size_t ssize = MESHLINK_SIGLEN; + bool sret = meshlink_sign(mesh_handle, data, strlen(data) + 1, sig, &ssize); + assert(sret); + + meshlink_node_t *source = meshlink_get_node(mesh_handle, "nut"); + assert(source != NULL); + bool ret = meshlink_verify(NULL, source, data, strlen(data) + 1, sig, ssize); + meshlink_close(mesh_handle); + assert(meshlink_destroy("verifyconf")); + + if(!ret) { + PRINT_TEST_CASE_MSG("meshlink_sign Successfully reported error on passing NULL as mesh_handle arg\n"); + return true; + } + + PRINT_TEST_CASE_MSG("meshlink_sign FAILED to report error on passing NULL as mesh_handle arg\n"); + return false; +} + + +/* Execute verify_data Test Case # 3 - Invalid case - meshlink_verify passing NULL args*/ +void test_case_verify_03(void **state) { + execute_test(test_verify_03, state); +} + +/* Test Steps for meshlink_sign Test Case # 3 - Invalid case + + Test Steps: + 1. Run NUT(Node Under Test) + 2. Sign data with meshlink_sign + 3. Trying to pass NULL as source node handle argument + and other arguments being valid + + Expected Result: + Reports error accordingly by returning false +*/ +bool test_verify_03(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_handle_t *mesh_handle = meshlink_open("verifyconf", "nut", "node_sim", DEV_CLASS_BACKBONE); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + assert(meshlink_start(mesh_handle)); + + char *data = "Test"; + char sig[MESHLINK_SIGLEN]; + size_t ssize = MESHLINK_SIGLEN; + bool ret = meshlink_sign(mesh_handle, data, strlen(data) + 1, sig, &ssize); + assert(ret); + ret = meshlink_verify(mesh_handle, NULL, data, strlen(data) + 1, sig, ssize); + meshlink_close(mesh_handle); + assert(meshlink_destroy("verifyconf")); + + if(!ret) { + PRINT_TEST_CASE_MSG("meshlink_verify successfully reported NULL as node_handle arg\n"); + return true; + } + + PRINT_TEST_CASE_MSG("meshlink_verify FAILED to report NULL as node_handle arg\n"); + return false; +} + +/* Execute verify_data Test Case # 4 - Invalid case - meshlink_verify passing NULL args*/ +void test_case_verify_04(void **state) { + execute_test(test_verify_04, state); +} + +/* Test Steps for meshlink_sign Test Case # 4 - Invalid case + + Test Steps: + 1. Run NUT(Node Under Test) + 2. Sign data with meshlink_sign + 3. Trying to pass NULL as signed data argument + and other arguments being valid + + Expected Result: + Reports error accordingly by returning false +*/ +bool test_verify_04(void) { + assert(meshlink_destroy("verifyconf")); + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_handle_t *mesh_handle = meshlink_open("verifyconf", "nut", "node_sim", DEV_CLASS_BACKBONE); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + assert(meshlink_start(mesh_handle)); + + char *data = "Test"; + char sig[MESHLINK_SIGLEN]; + size_t ssize = MESHLINK_SIGLEN; + bool ret = meshlink_sign(mesh_handle, data, strlen(data) + 1, sig, &ssize); + assert(ret); + meshlink_node_t *source = meshlink_get_node(mesh_handle, "nut"); + assert(source != NULL); + ret = meshlink_verify(mesh_handle, source, NULL, strlen(data) + 1, sig, ssize); + meshlink_stop(mesh_handle); + meshlink_close(mesh_handle); + assert(meshlink_destroy("verifyconf")); + + if(!ret) { + PRINT_TEST_CASE_MSG("meshlink_verify successfully reported NULL as data arg\n"); + return true; + } + + PRINT_TEST_CASE_MSG("meshlink_verify FAILED to report NULL as data arg\n"); + return false; +} + + +/* Execute verify_data Test Case # 5 - Invalid case - meshlink_verify passing NULL args*/ +void test_case_verify_05(void **state) { + execute_test(test_verify_05, state); +} + +/* Test Steps for meshlink_sign Test Case # 5 - Invalid case + + Test Steps: + 1. Run NUT(Node Under Test) + 2. Sign data with meshlink_sign + 3. Trying to pass NULL as signature buffer argument + and other arguments being valid + + Expected Result: + Reports error accordingly by returning false +*/ +bool test_verify_05(void) { + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_handle_t *mesh_handle = meshlink_open("verifyconf", "nut", "node_sim", 1); + assert(mesh_handle); + meshlink_set_log_cb(mesh_handle, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + assert(meshlink_start(mesh_handle)); + + char *data = "Test"; + char sig[MESHLINK_SIGLEN]; + size_t ssize = MESHLINK_SIGLEN; + bool ret = meshlink_sign(mesh_handle, data, strlen(data) + 1, sig, &ssize); + assert(ret); + meshlink_node_t *source = meshlink_get_node(mesh_handle, "nut"); + assert(source != NULL); + + ret = meshlink_verify(mesh_handle, source, data, strlen(data) + 1, NULL, ssize); + meshlink_stop(mesh_handle); + meshlink_close(mesh_handle); + assert(meshlink_destroy("verifyconf")); + + if(!ret) { + PRINT_TEST_CASE_MSG("meshlink_verify successfully NULL as sign arg\n"); + return true; + } + + PRINT_TEST_CASE_MSG("meshlink_verify FAILED to report NULL as sign arg\n"); + return false; +} + +/* Execute verify_data Test Case # 6 - Functionality test, when a wrong source node is mentioned to verify + the signed data */ +void test_case_verify_06(void **state) { + execute_test(test_verify_06, state); +} + +/* Test Steps for meshlink_verify Test Case # 6 - Functionality Test + + Test Steps: + 1. Run NUT(Node Under Test) and peer + 2. Sign using peer as source node. + 3. Verify with NUT but passing NUT as source node rather than + 'peer' as source node + + Expected Result: + API returns false when it detects the wrong source node +*/ +bool test_verify_06(void) { + /* deleting the confbase if already exists */ + assert(meshlink_destroy("verifyconf1")); + assert(meshlink_destroy("verifyconf2")); + /* Set up logging for Meshlink */ + meshlink_set_log_cb(NULL, TEST_MESHLINK_LOG_LEVEL, meshlink_callback_logger); + meshlink_handle_t *mesh1 = meshlink_open("verifyconf1", "nut", "chat", DEV_CLASS_STATIONARY); + assert(mesh1); + meshlink_handle_t *mesh2 = meshlink_open("verifyconf2", "bar", "chat", DEV_CLASS_STATIONARY); + assert(mesh2); + + char *exp1 = meshlink_export(mesh1); + assert(exp1 != NULL); + char *exp2 = meshlink_export(mesh2); + assert(exp2 != NULL); + assert(meshlink_import(mesh1, exp2)); + assert(meshlink_import(mesh2, exp1)); + + /* signing done by peer node */ + char *data = "Test"; + char sig[MESHLINK_SIGLEN]; + size_t ssize = MESHLINK_SIGLEN; + bool ret = meshlink_sign(mesh2, data, strlen(data) + 1, sig, &ssize); + assert(ret); + + meshlink_node_t *source_nut = meshlink_get_self(mesh1); + assert(source_nut); + ret = meshlink_verify(mesh_handle, source_nut, data, strlen(data) + 1, sig, ssize); + meshlink_close(mesh1); + meshlink_close(mesh2); + assert(meshlink_destroy("verifyconf1")); + assert(meshlink_destroy("verifyconf2")); + + if(!ret) { + PRINT_TEST_CASE_MSG("meshlink_verify successfully returned 'false' when a wrong source node used to verify the data\n"); + return true; + } + + PRINT_TEST_CASE_MSG("meshlink_verify FAILED to report error when a wrong source is mentioned\n"); + return false; +} + + +int test_meshlink_verify(void) { + const struct CMUnitTest blackbox_verify_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_verify_01, NULL, NULL, + (void *)&test_case_verify_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_verify_02, NULL, NULL, + (void *)&test_case_verify_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_verify_03, NULL, NULL, + (void *)&test_case_verify_03_state), + cmocka_unit_test_prestate_setup_teardown(test_case_verify_04, NULL, NULL, + (void *)&test_case_verify_04_state), + cmocka_unit_test_prestate_setup_teardown(test_case_verify_05, NULL, NULL, + (void *)&test_case_verify_05_state), + cmocka_unit_test_prestate_setup_teardown(test_case_verify_06, NULL, NULL, + (void *)&test_case_verify_06_state) + }; + + total_tests += sizeof(blackbox_verify_tests) / sizeof(blackbox_verify_tests[0]); + + return cmocka_run_group_tests(blackbox_verify_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_verify.h b/test/blackbox/run_blackbox_tests/test_cases_verify.h new file mode 100644 index 0000000..39661a0 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_verify.h @@ -0,0 +1,29 @@ +#ifndef TEST_CASES_VERIFY_H +#define TEST_CASES_VERIFY_H + +/* + test_cases_verify.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + + +#include + +extern int total_tests; +extern int test_meshlink_verify(void); + +#endif // TEST_CASES_VERIFY_H diff --git a/test/blackbox/run_blackbox_tests/test_cases_whitelist.c b/test/blackbox/run_blackbox_tests/test_cases_whitelist.c new file mode 100644 index 0000000..68283a9 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_whitelist.c @@ -0,0 +1,332 @@ +/* + test_cases_whitelist.c -- Execution of specific meshlink black box test cases + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "execute_tests.h" +#include "test_cases_whitelist.h" +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../utils.h" + +static void test_case_mesh_whitelist_01(void **state); +static bool test_steps_mesh_whitelist_01(void); +static void test_case_mesh_whitelist_02(void **state); +static bool test_steps_mesh_whitelist_02(void); +static void test_case_mesh_whitelist_03(void **state); +static bool test_steps_mesh_whitelist_03(void); + +/* State structure for meshlink_whitelist Test Case #1 */ +static black_box_state_t test_mesh_whitelist_01_state = { + .test_case_name = "test_case_mesh_whitelist_01", +}; + +/* State structure for meshlink_whitelist Test Case #2 */ +static black_box_state_t test_mesh_whitelist_02_state = { + .test_case_name = "test_case_mesh_whitelist_02", +}; + +/* State structure for meshlink_whitelist Test Case #3 */ +static black_box_state_t test_mesh_whitelist_03_state = { + .test_case_name = "test_case_mesh_whitelist_03", +}; + +static bool rec_stat; +static bool reachable; +static pthread_mutex_t lock_receive = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t receive_cond = PTHREAD_COND_INITIALIZER; +static pthread_mutex_t reachable_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t reachable_cond = PTHREAD_COND_INITIALIZER; + + +/* Execute meshlink_whitelist Test Case # 1*/ +static void test_case_mesh_whitelist_01(void **state) { + execute_test(test_steps_mesh_whitelist_01, state); +} + + +static void receive(meshlink_handle_t *mesh, meshlink_node_t *src, const void *data, size_t len) { + (void)mesh; + (void)src; + (void)data; + + assert(len); + + pthread_mutex_lock(& lock_receive); + rec_stat = true; + assert(!pthread_cond_broadcast(&receive_cond)); + pthread_mutex_unlock(& lock_receive); + +} + +static void status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reach) { + (void)mesh; + + if(!strcmp(node->name, "bar")) { + pthread_mutex_lock(&reachable_lock); + reachable = reach; + assert(!pthread_cond_broadcast(&reachable_cond)); + pthread_mutex_unlock(&reachable_lock); + } +} + + +/* Test Steps for meshlink_whitelist Test Case # 1 + + Test Steps: + 1. Run 2 node instances + 2. Blacklist one node and again whitelist the blacklisted node + + Expected Result: + meshlink_whitelist API whitelists the blacklisted node +*/ +static bool test_steps_mesh_whitelist_01(void) { + struct timespec timeout = {0}; + + // Open two new meshlink instance. + + assert(meshlink_destroy("whitelist_conf.1")); + assert(meshlink_destroy("whitelist_conf.2")); + meshlink_handle_t *mesh1 = meshlink_open("whitelist_conf.1", "foo", "test", DEV_CLASS_BACKBONE); + assert(mesh1); + meshlink_set_log_cb(mesh1, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_handle_t *mesh2 = meshlink_open("whitelist_conf.2", "bar", "test", DEV_CLASS_BACKBONE); + assert(mesh2); + meshlink_set_log_cb(mesh2, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_receive_cb(mesh2, receive); + meshlink_set_receive_cb(mesh1, receive); + + // Export & Import to join the mesh + + reachable = false; + char *data = meshlink_export(mesh1); + assert(data); + assert(meshlink_import(mesh2, data)); + free(data); + data = meshlink_export(mesh2); + assert(data); + assert(meshlink_import(mesh1, data)); + free(data); + + // Start both instances + + meshlink_set_node_status_cb(mesh1, status_cb); + assert(meshlink_start(mesh1)); + assert(meshlink_start(mesh2)); + + // Nodes should know each other + timeout.tv_sec = time(NULL) + 10; + pthread_mutex_lock(&reachable_lock); + + while(reachable == false) { + assert(!pthread_cond_timedwait(&reachable_cond, &reachable_lock, &timeout)); + } + + pthread_mutex_unlock(&reachable_lock); + sleep(1); + + meshlink_node_t *bar = meshlink_get_node(mesh1, "bar"); + assert(bar); + meshlink_node_t *foo = meshlink_get_node(mesh2, "foo"); + assert(foo); + + rec_stat = false; + assert(meshlink_send(mesh1, bar, "test", 5)); + timeout.tv_sec = time(NULL) + 10; + pthread_mutex_lock(& lock_receive); + + if(rec_stat == false) { + assert(pthread_cond_timedwait(&receive_cond, &lock_receive, &timeout) == 0); + } + + pthread_mutex_unlock(& lock_receive); + + + assert(meshlink_blacklist(mesh1, foo)); + + rec_stat = false; + assert(meshlink_send(mesh1, bar, "test", 5)); + timeout.tv_sec = time(NULL) + 10; + pthread_mutex_lock(& lock_receive); + + if(rec_stat == false) { + int err = pthread_cond_timedwait(&receive_cond, &lock_receive, &timeout); + assert(err == ETIMEDOUT); + } + + pthread_mutex_unlock(& lock_receive); + assert(meshlink_whitelist(mesh1, foo)); + + rec_stat = false; + bool result = meshlink_send(mesh2, foo, "test", 5); + timeout.tv_sec = time(NULL) + 10; + pthread_mutex_lock(& lock_receive); + + if(rec_stat == false) { + assert(pthread_cond_timedwait(&receive_cond, &lock_receive, &timeout) == 0); + } + + pthread_mutex_unlock(& lock_receive); + + // Clean up. + + meshlink_close(mesh2); + meshlink_close(mesh1); + assert(meshlink_destroy("whitelist_conf.1")); + assert(meshlink_destroy("whitelist_conf.2")); + + return result; +} + +/* Test Steps for meshlink_whitelist Test Case # 2 + + Test Steps: + 1. Calling meshlink_whitelist with NULL as mesh handle argument. + + Expected Result: + meshlink_whitelist API handles the invalid parameter when called by giving proper error number. +*/ +static void test_case_mesh_whitelist_02(void **state) { + execute_test(test_steps_mesh_whitelist_02, state); +} + +/* Test Steps for meshlink_whitelist Test Case # 2*/ +static bool test_steps_mesh_whitelist_02(void) { + struct timespec timeout = {0}; + + // Open two new meshlink instance. + + assert(meshlink_destroy("whitelist_conf.3")); + assert(meshlink_destroy("whitelist_conf.4")); + meshlink_handle_t *mesh1 = meshlink_open("whitelist_conf.3", "foo", "test", DEV_CLASS_BACKBONE); + assert(mesh1); + meshlink_handle_t *mesh2 = meshlink_open("whitelist_conf.4", "bar", "test", DEV_CLASS_BACKBONE); + assert(mesh2); + meshlink_set_receive_cb(mesh2, receive); + meshlink_set_receive_cb(mesh1, receive); + + char *data = meshlink_export(mesh1); + assert(data); + assert(meshlink_import(mesh2, data)); + free(data); + data = meshlink_export(mesh2); + assert(data); + assert(meshlink_import(mesh1, data)); + free(data); + + // Start both instances + + reachable = false; + meshlink_set_node_status_cb(mesh1, status_cb); + assert(meshlink_start(mesh1)); + assert(meshlink_start(mesh2)); + + // Nodes should know each other + timeout.tv_sec = time(NULL) + 10; + pthread_mutex_lock(&reachable_lock); + + while(reachable == false) { + assert(!pthread_cond_timedwait(&reachable_cond, &reachable_lock, &timeout)); + } + + pthread_mutex_unlock(&reachable_lock); + + meshlink_node_t *bar = meshlink_get_node(mesh1, "bar"); + assert(bar); + meshlink_node_t *foo = meshlink_get_node(mesh2, "foo"); + assert(foo); + + assert(meshlink_send(mesh1, bar, "test", 5)); + + assert(meshlink_blacklist(mesh1, foo)); + + // Passing NULL as mesh handle but with valid node handle 'foo' + + assert(!meshlink_whitelist(NULL, foo)); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + // Clean up. + + meshlink_close(mesh2); + meshlink_close(mesh1); + assert(meshlink_destroy("whitelist_conf.3")); + assert(meshlink_destroy("whitelist_conf.4")); + + return true; +} + +/* Execute meshlink_whitelist Test Case # 3*/ +static void test_case_mesh_whitelist_03(void **state) { + execute_test(test_steps_mesh_whitelist_03, state); +} + +/* Test Steps for meshlink_whitelist Test Case # 3 + + Test Steps: + 1. Calling meshlink_whitelist with NULL as node handle argument. + + Expected Result: + meshlink_whitelist API handles the invalid parameter when called by giving proper error number. +*/ +static bool test_steps_mesh_whitelist_03(void) { + // Open meshlink instance. + + assert(meshlink_destroy("whitelist_conf")); + meshlink_handle_t *mesh = meshlink_open("whitelist_conf", "foo", "test", DEV_CLASS_BACKBONE); + assert(mesh); + + // Start instance + assert(meshlink_start(mesh)); + + assert(!meshlink_whitelist(mesh, NULL)); + assert_int_equal(meshlink_errno, MESHLINK_EINVAL); + + // Clean up. + + meshlink_close(mesh); + assert(meshlink_destroy("whitelist_conf")); + return true; +} + +int test_meshlink_whitelist(void) { + const struct CMUnitTest blackbox_whitelist_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_whitelist_01, NULL, NULL, + (void *)&test_mesh_whitelist_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_whitelist_02, NULL, NULL, + (void *)&test_mesh_whitelist_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_mesh_whitelist_03, NULL, NULL, + (void *)&test_mesh_whitelist_03_state) + }; + + total_tests += sizeof(blackbox_whitelist_tests) / sizeof(blackbox_whitelist_tests[0]); + + return cmocka_run_group_tests(blackbox_whitelist_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_whitelist.h b/test/blackbox/run_blackbox_tests/test_cases_whitelist.h new file mode 100644 index 0000000..f7f79c4 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_whitelist.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_WHITELIST_H +#define TEST_CASES_WHITELIST_H + +/* + test_cases_whitelist.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_meshlink_whitelist(void); +extern int total_tests; + +#endif diff --git a/test/blackbox/run_blackbox_tests/test_optimal_pmtu.c b/test/blackbox/run_blackbox_tests/test_optimal_pmtu.c new file mode 100644 index 0000000..1d74089 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_optimal_pmtu.c @@ -0,0 +1,651 @@ +/* + test_optimal_pmtu.c -- Execution of specific meshlink black box test cases + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include "../common/containers.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "../common/network_namespace_framework.h" +#include "../../utils.h" +#include "../test_case_optimal_pmtu_01/test_case_optimal_pmtu.h" +#include "test_optimal_pmtu.h" + +static void test_case_optimal_pmtu_01(void **state); +static bool test_steps_optimal_pmtu_01(void); +static void test_case_optimal_pmtu_02(void **state); +static bool test_steps_optimal_pmtu_02(void); +static void test_case_optimal_pmtu_03(void **state); +static bool test_steps_optimal_pmtu_03(void); +static void test_case_optimal_pmtu_04(void **state); +static bool test_steps_optimal_pmtu_04(void); +static void test_case_optimal_pmtu_05(void **state); +static bool test_steps_optimal_pmtu_05(void); +static void test_case_optimal_pmtu_06(void **state); +static bool test_steps_optimal_pmtu_06(void); +static void test_case_optimal_pmtu_07(void **state); +static bool test_steps_optimal_pmtu_07(void); + +extern void *node_sim_relay_01(void *arg); +extern void *node_sim_peer_01(void *arg); +extern void *node_sim_nut_01(void *arg); +extern pmtu_attr_t node_pmtu[2]; + +typedef bool (*test_step_func_t)(void); +static int setup_test(void **state); +bool test_pmtu_relay_running = true; +bool test_pmtu_peer_running = true; +bool test_pmtu_nut_running = true; +bool ping_channel_enable_07 = false; + +struct sync_flag test_pmtu_nut_closed = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static netns_state_t *test_pmtu_state; + +static int setup_test(void **state) { + (void)state; + + netns_create_topology(test_pmtu_state); + fprintf(stderr, "\nCreated topology\n"); + + test_pmtu_relay_running = true; + test_pmtu_peer_running = true; + test_pmtu_nut_running = true; + ping_channel_enable_07 = false; + memset(node_pmtu, 0, sizeof(node_pmtu)); + set_sync_flag(&test_pmtu_nut_closed, false); + assert(meshlink_destroy("nut")); + assert(meshlink_destroy("peer")); + assert(meshlink_destroy("relay")); + + return EXIT_SUCCESS; +} + +static int teardown_test(void **state) { + (void)state; + + assert(meshlink_destroy("nut")); + assert(meshlink_destroy("peer")); + assert(meshlink_destroy("relay")); + netns_destroy_topology(test_pmtu_state); + + return EXIT_SUCCESS; +} + +static void execute_test(test_step_func_t step_func, void **state) { + (void)state; + + + fprintf(stderr, "\n\x1b[32mRunning Test\x1b[0m\n"); + bool test_result = step_func(); + + if(!test_result) { + fail(); + } +} + +static void *gen_inv(void *arg) { + mesh_invite_arg_t *mesh_invite_arg = (mesh_invite_arg_t *)arg; + meshlink_handle_t *mesh; + mesh = meshlink_open(mesh_invite_arg->mesh_arg->node_name, mesh_invite_arg->mesh_arg->confbase, mesh_invite_arg->mesh_arg->app_name, mesh_invite_arg->mesh_arg->dev_class); + assert(mesh); + + char *invitation = meshlink_invite(mesh, NULL, mesh_invite_arg->invitee_name); + assert(invitation); + mesh_invite_arg->invite_str = invitation; + meshlink_close(mesh); + + return NULL; +} + +/* Test Steps for optimal PMTU discovery Test Case # 1 - + Validating NUT MTU parameters without blocking ICMP under designed + network topology */ +static void test_case_optimal_pmtu_01(void **state) { + execute_test(test_steps_optimal_pmtu_01, state); + return; +} + +/* Test Steps for optimal PMTU discovery Test Case # 1 - Success case + + Test Steps: + 1. Create NAT setup and run each node instances in discrete namespace. + 2. Open a channel from NUT to peer and hence triggering Peer to peer connection + 3. Send the analyzed MTU parameters mesh event information to test driver + Expected Result: + NUT and Peer should be able to hole puch the NATs and MTU parameters should be in + the expected range +*/ +static bool test_steps_optimal_pmtu_01(void) { + mesh_arg_t relay_arg = {.node_name = "relay", .confbase = "relay", .app_name = "relay", .dev_class = 0 }; + mesh_arg_t peer_arg = {.node_name = "peer", .confbase = "peer", .app_name = "peer", .dev_class = 1 }; + mesh_arg_t nut_arg = {.node_name = "nut", .confbase = "nut", .app_name = "nut", .dev_class = 1 }; + + + mesh_invite_arg_t relay_nut_invite_arg = {.mesh_arg = &relay_arg, .invitee_name = "nut" }; + netns_thread_t netns_relay_nut_invite = {.namespace_name = "relay", .netns_thread = gen_inv, .arg = &relay_nut_invite_arg}; + run_node_in_namespace_thread(&netns_relay_nut_invite); + sleep(1); + assert(relay_nut_invite_arg.invite_str); + nut_arg.join_invitation = relay_nut_invite_arg.invite_str; + + mesh_invite_arg_t relay_peer_invite_arg = {.mesh_arg = &relay_arg, .invitee_name = "peer" }; + netns_thread_t netns_relay_peer_invite = {.namespace_name = "relay", .netns_thread = gen_inv, .arg = &relay_peer_invite_arg}; + run_node_in_namespace_thread(&netns_relay_peer_invite); + sleep(1); + assert(relay_peer_invite_arg.invite_str); + peer_arg.join_invitation = relay_peer_invite_arg.invite_str; + + netns_thread_t netns_relay_handle = {.namespace_name = "relay", .netns_thread = node_sim_pmtu_relay_01, .arg = &relay_arg}; + run_node_in_namespace_thread(&netns_relay_handle); + + netns_thread_t netns_peer_handle = {.namespace_name = "peer", .netns_thread = node_sim_pmtu_peer_01, .arg = &peer_arg}; + run_node_in_namespace_thread(&netns_peer_handle); + + netns_thread_t netns_nut_handle = {.namespace_name = "nut", .netns_thread = node_sim_pmtu_nut_01, .arg = &nut_arg}; + run_node_in_namespace_thread(&netns_nut_handle); + + assert(wait_sync_flag(&test_pmtu_nut_closed, 300)); + test_pmtu_relay_running = false; + test_pmtu_peer_running = false; + + sleep(1); + assert_in_range(node_pmtu[NODE_PMTU_PEER].mtu_size, 1450, 1501); + assert_in_range(node_pmtu[NODE_PMTU_PEER].mtu_discovery.probes, 120, 160); + assert_in_range(node_pmtu[NODE_PMTU_RELAY].mtu_size, 1450, 1501); + assert_in_range(node_pmtu[NODE_PMTU_RELAY].mtu_discovery.probes, 120, 160); + + return true; +} + +/* Test Steps for optimal PMTU discovery Test Case # 2 - + Validating NUT MTU parameters blocking ICMP under designed + network topology */ +static void test_case_optimal_pmtu_02(void **state) { + execute_test(test_steps_optimal_pmtu_02, state); + return; +} + +/* Test Steps for optimal PMTU discovery Test Case # 2 - + Test Steps: + 1. Create NAT setup and run each node instances in discrete namespace, + 2. Block ICMP protocol at NUT's NAT + 3. Open a channel from NUT to peer and hence triggering Peer to peer connection + 4. Send the analyzed MTU parameters mesh event information to test driver + Expected Result: + NUT and Peer should be able to hole puch the NATs and MTU parameters should be in + the expected range +*/ +static bool test_steps_optimal_pmtu_02(void) { + mesh_arg_t relay_arg = {.node_name = "relay", .confbase = "relay", .app_name = "relay", .dev_class = 0 }; + mesh_arg_t peer_arg = {.node_name = "peer", .confbase = "peer", .app_name = "peer", .dev_class = 1 }; + mesh_arg_t nut_arg = {.node_name = "nut", .confbase = "nut", .app_name = "nut", .dev_class = 1 }; + + assert(system("ip netns exec peer_nat iptables -A FORWARD -p icmp -j DROP") == 0); + assert(system("ip netns exec nut_nat iptables -A FORWARD -p icmp -j DROP") == 0); + + mesh_invite_arg_t relay_nut_invite_arg = {.mesh_arg = &relay_arg, .invitee_name = "nut" }; + netns_thread_t netns_relay_nut_invite = {.namespace_name = "relay", .netns_thread = gen_inv, .arg = &relay_nut_invite_arg}; + run_node_in_namespace_thread(&netns_relay_nut_invite); + sleep(1); + assert(relay_nut_invite_arg.invite_str); + nut_arg.join_invitation = relay_nut_invite_arg.invite_str; + + mesh_invite_arg_t relay_peer_invite_arg = {.mesh_arg = &relay_arg, .invitee_name = "peer" }; + netns_thread_t netns_relay_peer_invite = {.namespace_name = "relay", .netns_thread = gen_inv, .arg = &relay_peer_invite_arg}; + run_node_in_namespace_thread(&netns_relay_peer_invite); + sleep(1); + assert(relay_peer_invite_arg.invite_str); + peer_arg.join_invitation = relay_peer_invite_arg.invite_str; + + netns_thread_t netns_relay_handle = {.namespace_name = "relay", .netns_thread = node_sim_pmtu_relay_01, .arg = &relay_arg}; + run_node_in_namespace_thread(&netns_relay_handle); + + netns_thread_t netns_peer_handle = {.namespace_name = "peer", .netns_thread = node_sim_pmtu_peer_01, .arg = &peer_arg}; + run_node_in_namespace_thread(&netns_peer_handle); + + netns_thread_t netns_nut_handle = {.namespace_name = "nut", .netns_thread = node_sim_pmtu_nut_01, .arg = &nut_arg}; + run_node_in_namespace_thread(&netns_nut_handle); + + assert(wait_sync_flag(&test_pmtu_nut_closed, 300)); + test_pmtu_relay_running = false; + test_pmtu_peer_running = false; + + sleep(1); + assert_in_range(node_pmtu[NODE_PMTU_PEER].mtu_size, 1450, 1501); + assert_in_range(node_pmtu[NODE_PMTU_PEER].mtu_discovery.probes, 120, 160); + assert_in_range(node_pmtu[NODE_PMTU_RELAY].mtu_size, 1450, 1501); + assert_in_range(node_pmtu[NODE_PMTU_RELAY].mtu_discovery.probes, 120, 160); + + return true; +} + +/* Test Steps for optimal PMTU discovery Test Case # 3 - + Validating NUT MTU parameters with MTU size of NAT = 1250 under designed + network topology */ +static void test_case_optimal_pmtu_03(void **state) { + execute_test(test_steps_optimal_pmtu_03, state); + return; +} + +/* Test Steps for optimal PMTU discovery Test Case # 3 - + Test Steps: + 1. Create NAT setup and run each node instances in discrete namespace, + 2. Change the MTU size of NUT's NAT to 1250 + 3. Open a channel from NUT to peer and hence triggering Peer to peer connection + 4. Send the analyzed MTU parameters mesh event information to test driver + Expected Result: + NUT and Peer should be able to hole puch the NATs and MTU parameters should be in + the expected range +*/ +static bool test_steps_optimal_pmtu_03(void) { + mesh_arg_t relay_arg = {.node_name = "relay", .confbase = "relay", .app_name = "relay", .dev_class = 0 }; + mesh_arg_t peer_arg = {.node_name = "peer", .confbase = "peer", .app_name = "peer", .dev_class = 1 }; + mesh_arg_t nut_arg = {.node_name = "nut", .confbase = "nut", .app_name = "nut", .dev_class = 1 }; + + assert(system("ip netns exec nut_nat ifconfig eth_nut mtu 1250") == 0); + + mesh_invite_arg_t relay_nut_invite_arg = {.mesh_arg = &relay_arg, .invitee_name = "nut" }; + netns_thread_t netns_relay_nut_invite = {.namespace_name = "relay", .netns_thread = gen_inv, .arg = &relay_nut_invite_arg}; + run_node_in_namespace_thread(&netns_relay_nut_invite); + sleep(1); + assert(relay_nut_invite_arg.invite_str); + nut_arg.join_invitation = relay_nut_invite_arg.invite_str; + + mesh_invite_arg_t relay_peer_invite_arg = {.mesh_arg = &relay_arg, .invitee_name = "peer" }; + netns_thread_t netns_relay_peer_invite = {.namespace_name = "relay", .netns_thread = gen_inv, .arg = &relay_peer_invite_arg}; + run_node_in_namespace_thread(&netns_relay_peer_invite); + sleep(1); + assert(relay_peer_invite_arg.invite_str); + peer_arg.join_invitation = relay_peer_invite_arg.invite_str; + + netns_thread_t netns_relay_handle = {.namespace_name = "relay", .netns_thread = node_sim_pmtu_relay_01, .arg = &relay_arg}; + run_node_in_namespace_thread(&netns_relay_handle); + + netns_thread_t netns_peer_handle = {.namespace_name = "peer", .netns_thread = node_sim_pmtu_peer_01, .arg = &peer_arg}; + run_node_in_namespace_thread(&netns_peer_handle); + + netns_thread_t netns_nut_handle = {.namespace_name = "nut", .netns_thread = node_sim_pmtu_nut_01, .arg = &nut_arg}; + run_node_in_namespace_thread(&netns_nut_handle); + + assert(wait_sync_flag(&test_pmtu_nut_closed, 300)); + test_pmtu_relay_running = false; + test_pmtu_peer_running = false; + + sleep(1); + assert_in_range(node_pmtu[NODE_PMTU_PEER].mtu_size, 1200, 1250); + assert_in_range(node_pmtu[NODE_PMTU_RELAY].mtu_size, 1200, 1250); + + return true; +} + +/* Test Steps for optimal PMTU discovery Test Case # 4 - + Validating NUT MTU parameters with MTU size of NAT = 1000 under designed + network topology */ +static void test_case_optimal_pmtu_04(void **state) { + execute_test(test_steps_optimal_pmtu_04, state); + return; +} + +/* Test Steps for optimal PMTU discovery Test Case # 4 - + Test Steps: + 1. Create NAT setup and run each node instances in discrete namespace, + 2. Change the MTU size of NUT's NAT to 1000 + 3. Open a channel from NUT to peer and hence triggering Peer to peer connection + 4. Send the analyzed MTU parameters mesh event information to test driver + Expected Result: + NUT and Peer should be able to hole puch the NATs and MTU parameters should be in + the expected range +*/ +static bool test_steps_optimal_pmtu_04(void) { + mesh_arg_t relay_arg = {.node_name = "relay", .confbase = "relay", .app_name = "relay", .dev_class = 0 }; + mesh_arg_t peer_arg = {.node_name = "peer", .confbase = "peer", .app_name = "peer", .dev_class = 1 }; + mesh_arg_t nut_arg = {.node_name = "nut", .confbase = "nut", .app_name = "nut", .dev_class = 1 }; + + assert(system("ip netns exec nut_nat ifconfig eth_nut mtu 1000") == 0); + + mesh_invite_arg_t relay_nut_invite_arg = {.mesh_arg = &relay_arg, .invitee_name = "nut" }; + netns_thread_t netns_relay_nut_invite = {.namespace_name = "relay", .netns_thread = gen_inv, .arg = &relay_nut_invite_arg}; + run_node_in_namespace_thread(&netns_relay_nut_invite); + sleep(1); + assert(relay_nut_invite_arg.invite_str); + nut_arg.join_invitation = relay_nut_invite_arg.invite_str; + + mesh_invite_arg_t relay_peer_invite_arg = {.mesh_arg = &relay_arg, .invitee_name = "peer" }; + netns_thread_t netns_relay_peer_invite = {.namespace_name = "relay", .netns_thread = gen_inv, .arg = &relay_peer_invite_arg}; + run_node_in_namespace_thread(&netns_relay_peer_invite); + sleep(1); + assert(relay_peer_invite_arg.invite_str); + peer_arg.join_invitation = relay_peer_invite_arg.invite_str; + + netns_thread_t netns_relay_handle = {.namespace_name = "relay", .netns_thread = node_sim_pmtu_relay_01, .arg = &relay_arg}; + run_node_in_namespace_thread(&netns_relay_handle); + + netns_thread_t netns_peer_handle = {.namespace_name = "peer", .netns_thread = node_sim_pmtu_peer_01, .arg = &peer_arg}; + run_node_in_namespace_thread(&netns_peer_handle); + + netns_thread_t netns_nut_handle = {.namespace_name = "nut", .netns_thread = node_sim_pmtu_nut_01, .arg = &nut_arg}; + run_node_in_namespace_thread(&netns_nut_handle); + + assert(wait_sync_flag(&test_pmtu_nut_closed, 300)); + test_pmtu_relay_running = false; + test_pmtu_peer_running = false; + + sleep(1); + assert_in_range(node_pmtu[NODE_PMTU_PEER].mtu_size, 925, 1000); + assert_in_range(node_pmtu[NODE_PMTU_RELAY].mtu_size, 925, 1000); + + return true; +} + +/* Test Steps for optimal PMTU discovery Test Case # 5 - + Validating NUT MTU parameters with MTU size of NAT = 800 under designed + network topology */ +static void test_case_optimal_pmtu_05(void **state) { + execute_test(test_steps_optimal_pmtu_05, state); + return; +} + +/* Test Steps for optimal PMTU discovery Test Case # 5 - + Test Steps: + 1. Create NAT setup and run each node instances in discrete namespace, + 2. Change the MTU size of NUT's NAT to 800 + 3. Open a channel from NUT to peer and hence triggering Peer to peer connection + 4. Send the analyzed MTU parameters mesh event information to test driver + Expected Result: + NUT and Peer should be able to hole puch the NATs and MTU parameters should be in + the expected range +*/ +static bool test_steps_optimal_pmtu_05(void) { + mesh_arg_t relay_arg = {.node_name = "relay", .confbase = "relay", .app_name = "relay", .dev_class = 0 }; + mesh_arg_t peer_arg = {.node_name = "peer", .confbase = "peer", .app_name = "peer", .dev_class = 1 }; + mesh_arg_t nut_arg = {.node_name = "nut", .confbase = "nut", .app_name = "nut", .dev_class = 1 }; + + assert(system("ip netns exec nut_nat ifconfig eth_nut mtu 750") == 0); + + mesh_invite_arg_t relay_nut_invite_arg = {.mesh_arg = &relay_arg, .invitee_name = "nut" }; + netns_thread_t netns_relay_nut_invite = {.namespace_name = "relay", .netns_thread = gen_inv, .arg = &relay_nut_invite_arg}; + run_node_in_namespace_thread(&netns_relay_nut_invite); + sleep(1); + assert(relay_nut_invite_arg.invite_str); + nut_arg.join_invitation = relay_nut_invite_arg.invite_str; + + mesh_invite_arg_t relay_peer_invite_arg = {.mesh_arg = &relay_arg, .invitee_name = "peer" }; + netns_thread_t netns_relay_peer_invite = {.namespace_name = "relay", .netns_thread = gen_inv, .arg = &relay_peer_invite_arg}; + run_node_in_namespace_thread(&netns_relay_peer_invite); + sleep(1); + assert(relay_peer_invite_arg.invite_str); + peer_arg.join_invitation = relay_peer_invite_arg.invite_str; + + netns_thread_t netns_relay_handle = {.namespace_name = "relay", .netns_thread = node_sim_pmtu_relay_01, .arg = &relay_arg}; + run_node_in_namespace_thread(&netns_relay_handle); + + netns_thread_t netns_peer_handle = {.namespace_name = "peer", .netns_thread = node_sim_pmtu_peer_01, .arg = &peer_arg}; + run_node_in_namespace_thread(&netns_peer_handle); + + netns_thread_t netns_nut_handle = {.namespace_name = "nut", .netns_thread = node_sim_pmtu_nut_01, .arg = &nut_arg}; + run_node_in_namespace_thread(&netns_nut_handle); + + assert(wait_sync_flag(&test_pmtu_nut_closed, 300)); + test_pmtu_relay_running = false; + test_pmtu_peer_running = false; + + sleep(1); + assert_in_range(node_pmtu[NODE_PMTU_PEER].mtu_size, 700, 750); + assert_in_range(node_pmtu[NODE_PMTU_RELAY].mtu_size, 700, 750); + + return true; +} + +/* Test Steps for optimal PMTU discovery Test Case # 6 - + Flushing the tracked connections via NUT NAT for every 60 seconds */ +static void test_case_optimal_pmtu_06(void **state) { + execute_test(test_steps_optimal_pmtu_06, state); + return; +} + +static bool run_conntrack; +static pthread_t pmtu_test_case_conntrack_thread; +static void *conntrack_flush(void *arg) { + (void)arg; + + // flushes mappings for every 60 seconds + + while(run_conntrack) { + sleep(100); + assert(system("ip netns exec nut_nat conntrack -F") == 0); + assert(system("ip netns exec peer_nat conntrack -F") == 0); + } + + pthread_exit(NULL); +} + +/* Test Steps for optimal PMTU discovery Test Case # 6 - + Test Steps: + 1. Create NAT setup and Launch conntrack thread which flushes the tracked connections for every 90 seconds + 2. Run each node instances in discrete namespace, + 3. Open a channel from NUT to peer and hence triggering Peer to peer connection + 4. Send the analyzed MTU parameters mesh event information to test driver + Expected Result: + NUT and Peer should be able to hole puch the NATs and MTU parameters should be in + the expected range +*/ +static bool test_steps_optimal_pmtu_06(void) { + mesh_arg_t relay_arg = {.node_name = "relay", .confbase = "relay", .app_name = "relay", .dev_class = 0 }; + mesh_arg_t peer_arg = {.node_name = "peer", .confbase = "peer", .app_name = "peer", .dev_class = 1 }; + mesh_arg_t nut_arg = {.node_name = "nut", .confbase = "nut", .app_name = "nut", .dev_class = 1 }; + + run_conntrack = true; + assert(!pthread_create(&pmtu_test_case_conntrack_thread, NULL, conntrack_flush, NULL)); + + mesh_invite_arg_t relay_nut_invite_arg = {.mesh_arg = &relay_arg, .invitee_name = "nut" }; + netns_thread_t netns_relay_nut_invite = {.namespace_name = "relay", .netns_thread = gen_inv, .arg = &relay_nut_invite_arg}; + run_node_in_namespace_thread(&netns_relay_nut_invite); + sleep(1); + assert(relay_nut_invite_arg.invite_str); + nut_arg.join_invitation = relay_nut_invite_arg.invite_str; + + mesh_invite_arg_t relay_peer_invite_arg = {.mesh_arg = &relay_arg, .invitee_name = "peer" }; + netns_thread_t netns_relay_peer_invite = {.namespace_name = "relay", .netns_thread = gen_inv, .arg = &relay_peer_invite_arg}; + run_node_in_namespace_thread(&netns_relay_peer_invite); + sleep(1); + assert(relay_peer_invite_arg.invite_str); + peer_arg.join_invitation = relay_peer_invite_arg.invite_str; + + netns_thread_t netns_relay_handle = {.namespace_name = "relay", .netns_thread = node_sim_pmtu_relay_01, .arg = &relay_arg}; + run_node_in_namespace_thread(&netns_relay_handle); + + netns_thread_t netns_peer_handle = {.namespace_name = "peer", .netns_thread = node_sim_pmtu_peer_01, .arg = &peer_arg}; + run_node_in_namespace_thread(&netns_peer_handle); + + netns_thread_t netns_nut_handle = {.namespace_name = "nut", .netns_thread = node_sim_pmtu_nut_01, .arg = &nut_arg}; + run_node_in_namespace_thread(&netns_nut_handle); + + assert(wait_sync_flag(&test_pmtu_nut_closed, 300)); + test_pmtu_relay_running = false; + test_pmtu_peer_running = false; + run_conntrack = false; + pthread_join(pmtu_test_case_conntrack_thread, NULL); + + sleep(1); + + assert_in_range(node_pmtu[NODE_PMTU_PEER].mtu_size, 1440, 1500); + assert_in_range(node_pmtu[NODE_PMTU_RELAY].mtu_size, 1440, 1500); + assert_in_range(node_pmtu[NODE_PMTU_PEER].mtu_ping.probes, 38, 42); + assert_in_range(node_pmtu[NODE_PMTU_RELAY].mtu_ping.probes, 38, 42); + + return true; +} + +/* Test Steps for optimal PMTU discovery Test Case # 7 - + NUT sending data to peer node via channel for every 30 seconds + */ +static void test_case_optimal_pmtu_07(void **state) { + execute_test(test_steps_optimal_pmtu_07, state); + return; +} + +/* Test Steps for optimal PMTU discovery Test Case # 7 - + Test Steps: + 1. Create NAT setup and run each node instances in discrete namespace. + 2. Open a channel from NUT to peer and hence triggering Peer to peer connection + 3. Send data periodically via channel from NUT to peer node. + 4. Send the analyzed MTU parameters mesh event information to test driver + Expected Result: + NUT and Peer should be able to hole puch the NATs and MTU parameters should be in + the expected range +*/ +static bool test_steps_optimal_pmtu_07(void) { + mesh_arg_t relay_arg = {.node_name = "relay", .confbase = "relay", .app_name = "relay", .dev_class = 0 }; + mesh_arg_t peer_arg = {.node_name = "peer", .confbase = "peer", .app_name = "peer", .dev_class = 1 }; + mesh_arg_t nut_arg = {.node_name = "nut", .confbase = "nut", .app_name = "nut", .dev_class = 1 }; + + ping_channel_enable_07 = true; + + mesh_invite_arg_t relay_nut_invite_arg = {.mesh_arg = &relay_arg, .invitee_name = "nut" }; + netns_thread_t netns_relay_nut_invite = {.namespace_name = "relay", .netns_thread = gen_inv, .arg = &relay_nut_invite_arg}; + run_node_in_namespace_thread(&netns_relay_nut_invite); + sleep(1); + assert(relay_nut_invite_arg.invite_str); + nut_arg.join_invitation = relay_nut_invite_arg.invite_str; + + mesh_invite_arg_t relay_peer_invite_arg = {.mesh_arg = &relay_arg, .invitee_name = "peer" }; + netns_thread_t netns_relay_peer_invite = {.namespace_name = "relay", .netns_thread = gen_inv, .arg = &relay_peer_invite_arg}; + run_node_in_namespace_thread(&netns_relay_peer_invite); + sleep(1); + assert(relay_peer_invite_arg.invite_str); + peer_arg.join_invitation = relay_peer_invite_arg.invite_str; + + netns_thread_t netns_relay_handle = {.namespace_name = "relay", .netns_thread = node_sim_pmtu_relay_01, .arg = &relay_arg}; + run_node_in_namespace_thread(&netns_relay_handle); + + netns_thread_t netns_peer_handle = {.namespace_name = "peer", .netns_thread = node_sim_pmtu_peer_01, .arg = &peer_arg}; + run_node_in_namespace_thread(&netns_peer_handle); + + netns_thread_t netns_nut_handle = {.namespace_name = "nut", .netns_thread = node_sim_pmtu_nut_01, .arg = &nut_arg}; + run_node_in_namespace_thread(&netns_nut_handle); + + assert(wait_sync_flag(&test_pmtu_nut_closed, 300)); + test_pmtu_relay_running = false; + test_pmtu_peer_running = false; + + sleep(1); + assert_in_range(node_pmtu[NODE_PMTU_PEER].mtu_size, 1450, 1501); + assert_in_range(node_pmtu[NODE_PMTU_PEER].mtu_discovery.probes, 120, 160); + assert_in_range(node_pmtu[NODE_PMTU_RELAY].mtu_size, 1450, 1501); + assert_in_range(node_pmtu[NODE_PMTU_RELAY].mtu_discovery.probes, 120, 160); + + return true; +} + +// Optimal PMTU test case driver + +int test_optimal_pmtu(void) { + interface_t nut_ifs[] = { { .if_peer = "nut_nat", .fetch_ip_netns_name = "nut_nat" } }; + namespace_t nut = { + .name = "nut", + .type = HOST, + .interfaces = nut_ifs, + .interfaces_no = 1, + }; + + interface_t peer_ifs[] = { { .if_peer = "peer_nat", .fetch_ip_netns_name = "peer_nat" } }; + namespace_t peer = { + .name = "peer", + .type = HOST, + .interfaces = peer_ifs, + .interfaces_no = 1, + }; + + interface_t relay_ifs[] = { { .if_peer = "wan_bridge" } }; + namespace_t relay = { + .name = "relay", + .type = HOST, + .interfaces = relay_ifs, + .interfaces_no = 1, + }; + + netns_fullcone_handle_t nut_nat_fullcone = { .snat_to_source = "wan_bridge", .dnat_to_destination = "nut" }; + netns_fullcone_handle_t *nut_nat_args[] = { &nut_nat_fullcone, NULL }; + interface_t nut_nat_ifs[] = { { .if_peer = "nut", .fetch_ip_netns_name = "nut_nat" }, { .if_peer = "wan_bridge" } }; + namespace_t nut_nat = { + .name = "nut_nat", + .type = FULL_CONE, + .nat_arg = nut_nat_args, + .static_config_net_addr = "192.168.1.0/24", + .interfaces = nut_nat_ifs, + .interfaces_no = 2, + }; + + netns_fullcone_handle_t peer_nat_fullcone = { .snat_to_source = "wan_bridge", .dnat_to_destination = "peer" }; + netns_fullcone_handle_t *peer_nat_args[] = { &peer_nat_fullcone, NULL }; + interface_t peer_nat_ifs[] = { { .if_peer = "peer", .fetch_ip_netns_name = "peer_nat" }, { .if_peer = "wan_bridge" } }; + namespace_t peer_nat = { + .name = "peer_nat", + .type = FULL_CONE, + .nat_arg = peer_nat_args, + .static_config_net_addr = "192.168.1.0/24", + .interfaces = peer_nat_ifs, + .interfaces_no = 2, + }; + + interface_t wan_ifs[] = { { .if_peer = "peer_nat" }, { .if_peer = "nut_nat" }, { .if_peer = "relay" } }; + namespace_t wan_bridge = { + .name = "wan_bridge", + .type = BRIDGE, + .interfaces = wan_ifs, + .interfaces_no = 3, + }; + + namespace_t test_optimal_pmtu_1_nodes[] = { nut_nat, peer_nat, wan_bridge, nut, peer, relay }; + + netns_state_t test_pmtu_nodes = { + .test_case_name = "test_case_optimal_pmtu", + .namespaces = test_optimal_pmtu_1_nodes, + .num_namespaces = 6, + }; + test_pmtu_state = &test_pmtu_nodes; + + const struct CMUnitTest blackbox_group0_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_optimal_pmtu_01, setup_test, teardown_test, + (void *)&test_pmtu_state), + cmocka_unit_test_prestate_setup_teardown(test_case_optimal_pmtu_02, setup_test, teardown_test, + (void *)&test_pmtu_state), + cmocka_unit_test_prestate_setup_teardown(test_case_optimal_pmtu_03, setup_test, teardown_test, + (void *)&test_pmtu_state), + cmocka_unit_test_prestate_setup_teardown(test_case_optimal_pmtu_04, setup_test, teardown_test, + (void *)&test_pmtu_state), + cmocka_unit_test_prestate_setup_teardown(test_case_optimal_pmtu_05, setup_test, teardown_test, + (void *)&test_pmtu_state), + cmocka_unit_test_prestate_setup_teardown(test_case_optimal_pmtu_06, setup_test, teardown_test, + (void *)&test_pmtu_state), + cmocka_unit_test_prestate_setup_teardown(test_case_optimal_pmtu_07, setup_test, teardown_test, + (void *)&test_pmtu_state), + }; + total_tests += sizeof(blackbox_group0_tests) / sizeof(blackbox_group0_tests[0]); + + return cmocka_run_group_tests(blackbox_group0_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_optimal_pmtu.h b/test/blackbox/run_blackbox_tests/test_optimal_pmtu.h new file mode 100644 index 0000000..3ee68f7 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_optimal_pmtu.h @@ -0,0 +1,62 @@ +#ifndef TEST_CASES_OPTIMAL_PMTU_H +#define TEST_CASES_OPTIMAL_PMTU_H + +/* + test_optimal_pmtu.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +extern int test_optimal_pmtu(void); +extern int total_tests; +extern char *lxc_path; + +typedef struct pmtu_attr_para { + int probes; + int probes_total_len; + int count; + time_t time; + time_t time_l; + time_t time_h; +} pmtu_attr_para_t; + +typedef struct pmtu_attr { + pmtu_attr_para_t mtu_sent_probes; + pmtu_attr_para_t mtu_recv_probes; + pmtu_attr_para_t mtu_discovery; + pmtu_attr_para_t mtu_ping; + pmtu_attr_para_t mtu_increase; + pmtu_attr_para_t mtu_start; + int mtu_size; +} pmtu_attr_t; + +#define NODE_PMTU_RELAY 0 +#define NODE_PMTU_PEER 1 + +#define find_node_index(i, node_name) if(!strcasecmp(node_name, "peer")) { \ + i = NODE_PMTU_PEER; \ + } else if(!strcasecmp(node_name, "relay")) { \ + i = NODE_PMTU_RELAY; \ + } else { \ + abort(); \ + } + +#define PING_TRACK_TIMEOUT 100 +#define CHANNEL_PORT 1234 + +#endif // TEST_CASES_OPTIMAL_PMTU_H diff --git a/test/blackbox/test_case_channel_blacklist_01/node_sim_nut_01.c b/test/blackbox/test_case_channel_blacklist_01/node_sim_nut_01.c new file mode 100644 index 0000000..9e04a8b --- /dev/null +++ b/test/blackbox/test_case_channel_blacklist_01/node_sim_nut_01.c @@ -0,0 +1,224 @@ +/* + node_sim_nut.c -- Implementation of Node Simulation for Meshlink Testing + for channel connections with respective to blacklisting their nodes + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../common/network_namespace_framework.h" +#include "../../utils.h" +#include "node_sim_nut_01.h" + +#define CHANNEL_PORT 1234 + +static bool blacklist_set; +int total_reachable_callbacks_01; +int total_unreachable_callbacks_01; +int total_channel_closure_callbacks_01; +bool channel_discon_case_ping; +bool channel_discon_network_failure_01; +bool channel_discon_network_failure_02; +bool test_blacklist_whitelist_01; +bool test_channel_restart_01; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag peer_unreachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag channels_closed = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + fprintf(stderr, "Node %s %s\n", node->name, reachable ? "reachable" : "unreachable"); + + if(!strcmp(node->name, "peer")) { + if(reachable) { + set_sync_flag(&peer_reachable, true); + + if(blacklist_set) { + ++total_reachable_callbacks_01; + } + } else { + set_sync_flag(&peer_unreachable, true); + + if(blacklist_set) { + ++total_unreachable_callbacks_01; + } + } + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + fprintf(stderr, "%s poll cb invoked\n", (char *)channel->priv); + meshlink_set_channel_poll_cb(mesh, channel, NULL); + assert(meshlink_channel_send(mesh, channel, "test", 5) >= 0); + return; +} + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + + if(len == 0) { + fprintf(stderr, "Closed channel with %s\n", (char *)channel->priv); + + if(blacklist_set) { + ++total_channel_closure_callbacks_01; + } + + if(total_channel_closure_callbacks_01 == 2) { + set_sync_flag(&channels_closed, true); + } + } + + if(!strcmp(channel->node->name, "peer")) { + if(len == 5 && !memcmp(dat, "reply", 5)) { + fprintf(stderr, "Channel opened with %s\n", (char *)channel->priv); + set_sync_flag(&channel_opened, true); + } + } + + return; +} + +static void log_message(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) { + (void)level; + + (void)mesh; + + fprintf(stderr, "\x1b[32m nut:\x1b[0m %s\n", text); +} + +void *test_channel_blacklist_disonnection_nut_01(void *arg) { + mesh_arg_t *mesh_arg = (mesh_arg_t *)arg; + total_reachable_callbacks_01 = 0; + total_unreachable_callbacks_01 = 0; + total_channel_closure_callbacks_01 = 0; + + set_sync_flag(&peer_reachable, false); + set_sync_flag(&peer_unreachable, false); + set_sync_flag(&channel_opened, false); + blacklist_set = false; + + assert(!channel_discon_network_failure_01 || !channel_discon_network_failure_02); + + // Run relay node instance + + meshlink_handle_t *mesh; + mesh = meshlink_open(mesh_arg->node_name, mesh_arg->confbase, mesh_arg->app_name, mesh_arg->dev_class); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_message); + meshlink_set_node_status_cb(mesh, node_status_cb); + + // Join relay node and if fails to join then try few more attempts + + if(mesh_arg->join_invitation) { + assert(meshlink_join(mesh, mesh_arg->join_invitation)); + } + + assert(meshlink_start(mesh)); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 30)); + + meshlink_node_t *peer_node = meshlink_get_node(mesh, "peer"); + assert(peer_node); + meshlink_channel_t *channel1 = meshlink_channel_open(mesh, peer_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + channel1->priv = "channel1"; + meshlink_set_channel_poll_cb(mesh, channel1, poll_cb); + + assert(wait_sync_flag(&channel_opened, 15)); + + set_sync_flag(&channel_opened, false); + + meshlink_channel_t *channel2 = meshlink_channel_open(mesh, peer_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + channel2->priv = "channel2"; + meshlink_set_channel_poll_cb(mesh, channel2, poll_cb); + + assert(wait_sync_flag(&channel_opened, 15)); + + blacklist_set = true; + + if(channel_discon_network_failure_01) { + fprintf(stderr, "Simulating network failure before blacklisting\n"); + assert(system("iptables -A INPUT -m statistic --mode random --probability 0.9 -j DROP") == 0); + assert(system("iptables -A OUTPUT -m statistic --mode random --probability 0.9 -j DROP") == 0); + sleep(1); + } + + fprintf(stderr, "Node blacklisted\n"); + set_sync_flag(&channels_closed, false); + assert(meshlink_blacklist(mesh, peer_node)); + + sleep(10); + + if(channel_discon_network_failure_02) { + fprintf(stderr, "Simulating network failure after blacklisting\n"); + assert(system("iptables -A INPUT -m statistic --mode random --probability 0.9 -j DROP") == 0); + assert(system("iptables -A OUTPUT -m statistic --mode random --probability 0.9 -j DROP") == 0); + sleep(1); + } + + if(channel_discon_case_ping) { + fprintf(stderr, "Sending data through channels after blacklisting\n"); + assert(meshlink_channel_send(mesh, channel1, "ping", 5) >= 0); + assert(meshlink_channel_send(mesh, channel2, "ping", 5) >= 0); + } + + if(wait_sync_flag(&channels_closed, 120) == false) { + set_sync_flag(&test_channel_discon_nut_close, true); + return NULL; + } + + if(channel_discon_network_failure_01 || channel_discon_network_failure_02) { + fprintf(stderr, "Simulating network failure after blacklisting\n"); + assert(system("iptables -D INPUT -m statistic --mode random --probability 0.9 -j DROP") == 0); + assert(system("iptables -D OUTPUT -m statistic --mode random --probability 0.9 -j DROP") == 0); + } + + set_sync_flag(&peer_reachable, false); + + assert(meshlink_whitelist(mesh, peer_node)); + fprintf(stderr, "Node whitelisted\n"); + + wait_sync_flag(&peer_reachable, 70); + + fprintf(stderr, "Closing NUT instance\n"); + blacklist_set = false; + + set_sync_flag(&test_channel_discon_nut_close, true); + + meshlink_close(mesh); + return NULL; +} diff --git a/test/blackbox/test_case_channel_blacklist_01/node_sim_nut_01.h b/test/blackbox/test_case_channel_blacklist_01/node_sim_nut_01.h new file mode 100644 index 0000000..e5dc0ac --- /dev/null +++ b/test/blackbox/test_case_channel_blacklist_01/node_sim_nut_01.h @@ -0,0 +1,31 @@ +#ifndef CHANNEL_BLACKLIST_NUT_01_H +#define CHANNEL_BLACKLIST_NUT_01_H + +/* + test_case_channel_disconnection.h -- Implementation of Node Simulation for Meshlink Testing + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +extern void *test_channel_blacklist_disonnection_peer_01(void *arg); +extern void *test_channel_blacklist_disonnection_nut_01(void *arg); +extern void *test_channel_blacklist_disonnection_relay_01(void *arg); +extern int total_blacklist_callbacks_01; +extern int total_whitelist_callbacks_01; +extern struct sync_flag test_channel_discon_nut_close; +extern bool test_case_signal_peer_restart_01; + +#endif diff --git a/test/blackbox/test_case_channel_blacklist_01/node_sim_peer_01.c b/test/blackbox/test_case_channel_blacklist_01/node_sim_peer_01.c new file mode 100644 index 0000000..4e36933 --- /dev/null +++ b/test/blackbox/test_case_channel_blacklist_01/node_sim_peer_01.c @@ -0,0 +1,109 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for channel connections with respective to blacklisting their nodes + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/network_namespace_framework.h" +#include "../../utils.h" + +#define CHANNEL_PORT 1234 + +bool test_channel_blacklist_disonnection_peer_01_running; +bool test_case_signal_peer_restart_01; + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + if(!strcmp(channel->node->name, "nut")) { + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + return true; + } + + return false; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + + if(len == 0) { + fprintf(stderr, "Channel closure\n"); + } + + if(!strcmp(channel->node->name, "nut")) { + if(!memcmp(dat, "test", 5)) { + assert(meshlink_channel_send(mesh, channel, "reply", 5) >= 0); + } + } + + return; +} + +void *test_channel_blacklist_disonnection_peer_01(void *arg) { + struct timeval main_loop_wait = { 2, 0 }; + mesh_arg_t *mesh_arg = (mesh_arg_t *)arg; + test_channel_blacklist_disonnection_peer_01_running = true; + + // Run relay node instance + + meshlink_handle_t *mesh; + mesh = meshlink_open(mesh_arg->node_name, mesh_arg->confbase, mesh_arg->app_name, mesh_arg->dev_class); + assert(mesh); + meshlink_set_channel_accept_cb(mesh, channel_accept); + + // Join relay node and if fails to join then try few more attempts + + if(mesh_arg->join_invitation) { + assert(meshlink_join(mesh, mesh_arg->join_invitation)); + } + + assert(meshlink_start(mesh)); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_channel_blacklist_disonnection_peer_01_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + + if(test_case_signal_peer_restart_01) { + meshlink_stop(mesh); + assert(meshlink_start(mesh)); + test_case_signal_peer_restart_01 = false; + } + } + + meshlink_close(mesh); + + return NULL; +} diff --git a/test/blackbox/test_case_channel_blacklist_01/node_sim_relay_01.c b/test/blackbox/test_case_channel_blacklist_01/node_sim_relay_01.c new file mode 100644 index 0000000..a6c293a --- /dev/null +++ b/test/blackbox/test_case_channel_blacklist_01/node_sim_relay_01.c @@ -0,0 +1,49 @@ +/* + node_sim_relay.c -- Implementation of Node Simulation for Meshlink Testing + for channel connections with respective to blacklisting their nodes + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/network_namespace_framework.h" + +bool test_channel_blacklist_disonnection_relay_01_running; + +void *test_channel_blacklist_disonnection_relay_01(void *arg) { + struct timeval main_loop_wait = { 2, 0 }; + mesh_arg_t *mesh_arg = (mesh_arg_t *)arg; + test_channel_blacklist_disonnection_relay_01_running = true; + + // Run relay node instance + + meshlink_handle_t *mesh; + mesh = meshlink_open(mesh_arg->node_name, mesh_arg->confbase, mesh_arg->app_name, mesh_arg->dev_class); + assert(mesh); + + assert(meshlink_start(mesh)); + + /* All test steps executed - wait for signals to stop/start or close the mesh */ + while(test_channel_blacklist_disonnection_relay_01_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return NULL; +} diff --git a/test/blackbox/test_case_channel_conn_01/Makefile.am b/test/blackbox/test_case_channel_conn_01/Makefile.am new file mode 100644 index 0000000..177c575 --- /dev/null +++ b/test/blackbox/test_case_channel_conn_01/Makefile.am @@ -0,0 +1,9 @@ +check_PROGRAMS = node_sim_peer node_sim_nut + +node_sim_peer_SOURCES = node_sim_peer.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_peer_LDADD = ../../../src/libmeshlink.la +node_sim_peer_CFLAGS = -D_GNU_SOURCE + +node_sim_nut_SOURCES = node_sim_nut.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_nut_LDADD = ../../../src/libmeshlink.la +node_sim_nut_CFLAGS = -D_GNU_SOURCE \ No newline at end of file diff --git a/test/blackbox/test_case_channel_conn_01/node_sim_nut.c b/test/blackbox/test_case_channel_conn_01/node_sim_nut.c new file mode 100644 index 0000000..25b187f --- /dev/null +++ b/test/blackbox/test_case_channel_conn_01/node_sim_nut.c @@ -0,0 +1,168 @@ +/* + node_sim_nut.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static int client_id = -1; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag sigusr_received = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; + +static void send_event(mesh_event_t event); +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable); + +static void mesh_siguser1_signal_handler(int sig_num) { + (void)sig_num; + + set_sync_flag(&sigusr_received, true); + + return; +} + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + fprintf(stderr, "SENT EVENT\n"); + return; +} + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcasecmp(node->name, "peer") && reachable) { + set_sync_flag(&peer_reachable, true); + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + assert(meshlink_channel_send(mesh, channel, "test", 5) >= 0); + return; +} + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + + if(len == 0) { + send_event(ERR_NETWORK); + assert(false); + } + + if(!strcmp(channel->node->name, "peer")) { + if(len == 5 && !memcmp(dat, "reply", 5)) { + set_sync_flag(&channel_opened, true); + } + } + + return; +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 2, 0 }; + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + signal(SIGUSR1, mesh_siguser1_signal_handler); + + // Execute test steps + + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 30)); + send_event(NODE_JOINED); + + // Open a channel to peer node + + meshlink_node_t *peer_node = meshlink_get_node(mesh, "peer"); + assert(peer_node); + meshlink_channel_t *channel = meshlink_channel_open(mesh, peer_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + + assert(wait_sync_flag(&channel_opened, 10)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&sigusr_received, 30)); + + sleep(10); + + assert(meshlink_channel_send(mesh, channel, "after", 6) >= 0); + + // All test steps executed - wait for signals to stop/start or close the mesh + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); +} diff --git a/test/blackbox/test_case_channel_conn_01/node_sim_peer.c b/test/blackbox/test_case_channel_conn_01/node_sim_peer.c new file mode 100644 index 0000000..ed74626 --- /dev/null +++ b/test/blackbox/test_case_channel_conn_01/node_sim_peer.c @@ -0,0 +1,127 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +static int client_id = -1; + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + if(!strcmp(channel->node->name, "nut")) { + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + return false; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + (void)channel; + (void)dat; + (void)len; + + if(len == 0) { + mesh_event_sock_send(client_id, ERR_NETWORK, NULL, 0); + assert(false); + } + + if(!strcmp(channel->node->name, "nut")) { + if(!memcmp(dat, "test", 5)) { + assert(meshlink_channel_send(mesh, channel, "reply", 5) >= 0); + } else if(!memcmp(dat, "after", 6)) { + assert(mesh_event_sock_send(client_id, CHANNEL_DATA_RECIEVED, NULL, 0)); + } + } + + return; +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 2, 0 }; + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + + // Run peer node instance + + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_case_channel_conn_02/Makefile.am b/test/blackbox/test_case_channel_conn_02/Makefile.am new file mode 100644 index 0000000..4cca831 --- /dev/null +++ b/test/blackbox/test_case_channel_conn_02/Makefile.am @@ -0,0 +1,9 @@ +check_PROGRAMS = node_sim_peer node_sim_nut + +node_sim_peer_SOURCES = node_sim_peer.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_peer_LDADD = ../../../src/libmeshlink.la +node_sim_peer_CFLAGS = -D_GNU_SOURCE + +node_sim_nut_SOURCES = node_sim_nut.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_nut_LDADD = ../../../src/libmeshlink.la +node_sim_nut_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/test_case_channel_conn_02/node_sim_nut.c b/test/blackbox/test_case_channel_conn_02/node_sim_nut.c new file mode 100644 index 0000000..f6b1668 --- /dev/null +++ b/test/blackbox/test_case_channel_conn_02/node_sim_nut.c @@ -0,0 +1,170 @@ +/* + node_sim_nut.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static int client_id = -1; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag channel_closed = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag sigusr_received = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; + +static void send_event(mesh_event_t event); +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable); + +static void mesh_siguser1_signal_handler(int sig_num) { + (void)sig_num; + + set_sync_flag(&sigusr_received, true); + return; +} + +static void send_event(mesh_event_t event) { + bool send_ret = false; + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + send_ret = mesh_event_sock_send(client_id, event, NULL, 0); + + if(send_ret) { + break; + } + } + + return; +} + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcasecmp(node->name, "peer") && reachable) { + set_sync_flag(&peer_reachable, true); + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + assert(meshlink_channel_send(mesh, channel, "test", 5) >= 0); + return; +} + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + + if(len == 0) { + set_sync_flag(&channel_closed, true); + send_event(ERR_NETWORK); + return; + } + + if(!strcmp(channel->node->name, "peer")) { + if(len == 5 && !memcmp(dat, "reply", 5)) { + set_sync_flag(&channel_opened, true); + } + } + + return; +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 2, 0 }; + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + signal(SIGUSR1, mesh_siguser1_signal_handler); + + // Execute test steps + + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 30)); + send_event(NODE_JOINED); + + // Open a channel to peer node + + meshlink_node_t *peer_node = meshlink_get_node(mesh, "peer"); + assert(peer_node); + meshlink_channel_t *channel = meshlink_channel_open(mesh, peer_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + + assert(wait_sync_flag(&channel_opened, 10)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&sigusr_received, 10)); + + assert(meshlink_channel_send(mesh, channel, "after", 6) >= 0); + + assert(wait_sync_flag(&channel_closed, 180)); + + // All test steps executed - wait for signals to stop/start or close the mesh + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); +} diff --git a/test/blackbox/test_case_channel_conn_02/node_sim_peer.c b/test/blackbox/test_case_channel_conn_02/node_sim_peer.c new file mode 100644 index 0000000..dc85304 --- /dev/null +++ b/test/blackbox/test_case_channel_conn_02/node_sim_peer.c @@ -0,0 +1,113 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static int client_id = -1; + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + if(!strcmp(channel->node->name, "nut")) { + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + return false; +} + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + if(len == 0) { + assert(mesh_event_sock_send(client_id, ERR_NETWORK, NULL, 0)); + return; + } + + if(!strcmp(channel->node->name, "nut")) { + if(!memcmp(dat, "test", 5)) { + assert(meshlink_channel_send(mesh, channel, "reply", 5) >= 0); + } + } + + return; +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 2, 0 }; + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Run peer node instance + + setup_signals(); + + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + // All test steps executed - wait for signals to stop/start or close the mesh + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_case_channel_conn_03/Makefile.am b/test/blackbox/test_case_channel_conn_03/Makefile.am new file mode 100644 index 0000000..4cca831 --- /dev/null +++ b/test/blackbox/test_case_channel_conn_03/Makefile.am @@ -0,0 +1,9 @@ +check_PROGRAMS = node_sim_peer node_sim_nut + +node_sim_peer_SOURCES = node_sim_peer.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_peer_LDADD = ../../../src/libmeshlink.la +node_sim_peer_CFLAGS = -D_GNU_SOURCE + +node_sim_nut_SOURCES = node_sim_nut.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_nut_LDADD = ../../../src/libmeshlink.la +node_sim_nut_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/test_case_channel_conn_03/node_sim_nut.c b/test/blackbox/test_case_channel_conn_03/node_sim_nut.c new file mode 100644 index 0000000..4bd8d28 --- /dev/null +++ b/test/blackbox/test_case_channel_conn_03/node_sim_nut.c @@ -0,0 +1,174 @@ +/* + node_sim_nut.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static int client_id = -1; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag peer_unreachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag sigusr_received = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; + +static void send_event(mesh_event_t event); +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable); + +static void mesh_siguser1_signal_handler(int sig_num) { + (void)sig_num; + + set_sync_flag(&sigusr_received, true); + return; +} + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcasecmp(node->name, "peer")) { + if(reachable) { + set_sync_flag(&peer_reachable, true); + } else { + set_sync_flag(&peer_unreachable, true); + } + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + assert(meshlink_channel_send(mesh, channel, "test", 5) >= 0); + return; +} + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + + if(!strcmp(channel->node->name, "peer")) { + if(len == 5 && !memcmp(dat, "reply", 5)) { + set_sync_flag(&channel_opened, true); + } + } + + return; +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 2, 0 }; + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + signal(SIGUSR1, mesh_siguser1_signal_handler); + + // Execute test steps + + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 30)); + send_event(NODE_JOINED); + + // Open a channel to peer node + + meshlink_node_t *peer_node = meshlink_get_node(mesh, "peer"); + assert(peer_node); + meshlink_channel_t *channel = meshlink_channel_open(mesh, peer_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + + assert(wait_sync_flag(&channel_opened, 10)); + send_event(CHANNEL_OPENED); + + peer_unreachable.flag = false; + peer_reachable.flag = false; + assert(wait_sync_flag(&sigusr_received, 10)); + + assert(wait_sync_flag(&peer_unreachable, 100)); + send_event(NODE_UNREACHABLE); + + assert(wait_sync_flag(&peer_reachable, 100)); + send_event(NODE_REACHABLE); + + assert(meshlink_channel_send(mesh, channel, "after", 6) >= 0); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); +} diff --git a/test/blackbox/test_case_channel_conn_03/node_sim_peer.c b/test/blackbox/test_case_channel_conn_03/node_sim_peer.c new file mode 100644 index 0000000..19c96f8 --- /dev/null +++ b/test/blackbox/test_case_channel_conn_03/node_sim_peer.c @@ -0,0 +1,115 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static int client_id = -1; + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + if(!strcmp(channel->node->name, "nut")) { + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + return false; +} + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + if(len == 0) { + assert(mesh_event_sock_send(client_id, ERR_NETWORK, NULL, 0)); + return; + } + + if(!strcmp(channel->node->name, "nut")) { + if(!memcmp(dat, "test", 5)) { + assert(meshlink_channel_send(mesh, channel, "reply", 5) >= 0); + } else if(!memcmp(dat, "after", 5)) { + assert(mesh_event_sock_send(client_id, CHANNEL_DATA_RECIEVED, NULL, 0)); + } + } + + return; +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 2, 0 }; + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Run peer node instance + + setup_signals(); + + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + // All test steps executed - wait for signals to stop/start or close the mesh + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_case_channel_conn_04/Makefile.am b/test/blackbox/test_case_channel_conn_04/Makefile.am new file mode 100644 index 0000000..4cca831 --- /dev/null +++ b/test/blackbox/test_case_channel_conn_04/Makefile.am @@ -0,0 +1,9 @@ +check_PROGRAMS = node_sim_peer node_sim_nut + +node_sim_peer_SOURCES = node_sim_peer.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_peer_LDADD = ../../../src/libmeshlink.la +node_sim_peer_CFLAGS = -D_GNU_SOURCE + +node_sim_nut_SOURCES = node_sim_nut.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_nut_LDADD = ../../../src/libmeshlink.la +node_sim_nut_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/test_case_channel_conn_04/node_sim_nut.c b/test/blackbox/test_case_channel_conn_04/node_sim_nut.c new file mode 100644 index 0000000..9579f01 --- /dev/null +++ b/test/blackbox/test_case_channel_conn_04/node_sim_nut.c @@ -0,0 +1,167 @@ +/* + node_sim_nut.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static int client_id = -1; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; + +static void send_event(mesh_event_t event); +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, + bool reachable); + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcasecmp(node->name, "peer")) { + if(reachable) { + set_sync_flag(&peer_reachable, true); + } else { + peer_reachable.flag = false; + } + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + assert(meshlink_channel_send(mesh, channel, "test", 5) >= 0); + return; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + + if(len == 0) { + //send_event(ERR_NETWORK); + return; + } + + if(!strcmp(channel->node->name, "peer")) { + if(!memcmp(dat, "reply", 5)) { + set_sync_flag(&channel_opened, true); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } + + return; +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 5, 0 }; + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + setup_signals(); + + // Execute test steps + + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 10)); + send_event(NODE_JOINED); + + // Open a channel to peer node + + meshlink_node_t *peer_node = meshlink_get_node(mesh, "peer"); + assert(peer_node); + meshlink_channel_t *channel = meshlink_channel_open(mesh, peer_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + + assert(wait_sync_flag(&channel_opened, 10)); + send_event(CHANNEL_OPENED); + + // Restarting the node instance + + meshlink_stop(mesh); + assert(meshlink_start(mesh)); + + assert(wait_sync_flag(&peer_reachable, 60)); + send_event(NODE_RESTARTED); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); +} diff --git a/test/blackbox/test_case_channel_conn_04/node_sim_peer.c b/test/blackbox/test_case_channel_conn_04/node_sim_peer.c new file mode 100644 index 0000000..b087341 --- /dev/null +++ b/test/blackbox/test_case_channel_conn_04/node_sim_peer.c @@ -0,0 +1,137 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +static struct sync_flag sigusr = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static int client_id = -1; + +static void mesh_siguser1_signal_handler(int sig_num) { + (void)sig_num; + + set_sync_flag(&sigusr, true); + + return; +} + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + if(!strcmp(channel->node->name, "nut")) { + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + return false; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + (void)channel; + (void)dat; + (void)len; + + if(len == 0) { + mesh_event_sock_send(client_id, ERR_NETWORK, NULL, 0); + return; + } + + if(!strcmp(channel->node->name, "nut") && !memcmp(dat, "test", 5)) { + assert(meshlink_channel_send(mesh, channel, "reply", 5) >= 0); + } + + return; +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 2, 0 }; + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + signal(SIGUSR1, mesh_siguser1_signal_handler); + + // Run peer node instance + + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + assert(wait_sync_flag(&sigusr, 140)); + meshlink_channel_t *channel = mesh->priv; + assert(meshlink_channel_send(mesh, channel, "failure", 7)); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_case_channel_conn_05/Makefile.am b/test/blackbox/test_case_channel_conn_05/Makefile.am new file mode 100644 index 0000000..8bde53d --- /dev/null +++ b/test/blackbox/test_case_channel_conn_05/Makefile.am @@ -0,0 +1,13 @@ +check_PROGRAMS = node_sim_peer node_sim_nut node_sim_relay + +node_sim_peer_SOURCES = node_sim_peer.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_peer_LDADD = ../../../src/libmeshlink.la +node_sim_peer_CFLAGS = -D_GNU_SOURCE + +node_sim_nut_SOURCES = node_sim_nut.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_nut_LDADD = ../../../src/libmeshlink.la +node_sim_nut_CFLAGS = -D_GNU_SOURCE + +node_sim_relay_SOURCES = node_sim_relay.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_relay_LDADD = ../../../src/libmeshlink.la +node_sim_relay_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/test_case_channel_conn_05/node_sim_nut.c b/test/blackbox/test_case_channel_conn_05/node_sim_nut.c new file mode 100644 index 0000000..f520699 --- /dev/null +++ b/test/blackbox/test_case_channel_conn_05/node_sim_nut.c @@ -0,0 +1,168 @@ +/* + node_sim_nut.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static int client_id = -1; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag sigusr_received = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; + +static void send_event(mesh_event_t event); +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable); + +static void mesh_siguser1_signal_handler(int sig_num) { + (void)sig_num; + + set_sync_flag(&sigusr_received, true); + + return; +} + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcasecmp(node->name, "peer") && reachable) { + set_sync_flag(&peer_reachable, true); + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + assert(meshlink_channel_send(mesh, channel, "test", 5) >= 0); + return; +} + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + + if(len == 0) { + send_event(ERR_NETWORK); + assert(false); + } + + if(!strcmp(channel->node->name, "peer")) { + if(len == 5 && !memcmp(dat, "reply", 5)) { + set_sync_flag(&channel_opened, true); + } + } + + return; +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 2, 0 }; + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + signal(SIGUSR1, mesh_siguser1_signal_handler); + + // Execute test steps + + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 30)); + send_event(NODE_JOINED); + + // Open a channel to peer node + + meshlink_node_t *peer_node = meshlink_get_node(mesh, "peer"); + assert(peer_node); + meshlink_channel_t *channel = meshlink_channel_open(mesh, peer_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + + assert(wait_sync_flag(&channel_opened, 10)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&sigusr_received, 10)); + + sleep(10); + + assert(meshlink_channel_send(mesh, channel, "after", 6) >= 0); + + // All test steps executed - wait for signals to stop/start or close the mesh + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); +} diff --git a/test/blackbox/test_case_channel_conn_05/node_sim_peer.c b/test/blackbox/test_case_channel_conn_05/node_sim_peer.c new file mode 100644 index 0000000..ed74626 --- /dev/null +++ b/test/blackbox/test_case_channel_conn_05/node_sim_peer.c @@ -0,0 +1,127 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +static int client_id = -1; + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + if(!strcmp(channel->node->name, "nut")) { + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + return false; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + (void)channel; + (void)dat; + (void)len; + + if(len == 0) { + mesh_event_sock_send(client_id, ERR_NETWORK, NULL, 0); + assert(false); + } + + if(!strcmp(channel->node->name, "nut")) { + if(!memcmp(dat, "test", 5)) { + assert(meshlink_channel_send(mesh, channel, "reply", 5) >= 0); + } else if(!memcmp(dat, "after", 6)) { + assert(mesh_event_sock_send(client_id, CHANNEL_DATA_RECIEVED, NULL, 0)); + } + } + + return; +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 2, 0 }; + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + + // Run peer node instance + + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_case_channel_conn_05/node_sim_relay.c b/test/blackbox/test_case_channel_conn_05/node_sim_relay.c new file mode 100644 index 0000000..8e5e66e --- /dev/null +++ b/test/blackbox/test_case_channel_conn_05/node_sim_relay.c @@ -0,0 +1,59 @@ +/* + node_sim_relay.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 5, 0 }; + + // Setup required signals + + setup_signals(); + + // Run relay node instance + + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + + assert(meshlink_start(mesh)); + + /* All test steps executed - wait for signals to stop/start or close the mesh */ + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return 0; +} diff --git a/test/blackbox/test_case_channel_conn_06/Makefile.am b/test/blackbox/test_case_channel_conn_06/Makefile.am new file mode 100644 index 0000000..8bde53d --- /dev/null +++ b/test/blackbox/test_case_channel_conn_06/Makefile.am @@ -0,0 +1,13 @@ +check_PROGRAMS = node_sim_peer node_sim_nut node_sim_relay + +node_sim_peer_SOURCES = node_sim_peer.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_peer_LDADD = ../../../src/libmeshlink.la +node_sim_peer_CFLAGS = -D_GNU_SOURCE + +node_sim_nut_SOURCES = node_sim_nut.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_nut_LDADD = ../../../src/libmeshlink.la +node_sim_nut_CFLAGS = -D_GNU_SOURCE + +node_sim_relay_SOURCES = node_sim_relay.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_relay_LDADD = ../../../src/libmeshlink.la +node_sim_relay_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/test_case_channel_conn_06/node_sim_nut.c b/test/blackbox/test_case_channel_conn_06/node_sim_nut.c new file mode 100644 index 0000000..cabd82e --- /dev/null +++ b/test/blackbox/test_case_channel_conn_06/node_sim_nut.c @@ -0,0 +1,174 @@ +/* + node_sim_nut.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static int client_id = -1; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag channel_closed = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag sigusr_received = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; + +static void send_event(mesh_event_t event); +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable); + +static void mesh_siguser1_signal_handler(int sig_num) { + (void)sig_num; + + set_sync_flag(&sigusr_received, true); + return; +} + +static void send_event(mesh_event_t event) { + bool send_ret = false; + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + send_ret = mesh_event_sock_send(client_id, event, NULL, 0); + + if(send_ret) { + break; + } + } + + return; +} + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + + if(!strcasecmp(node->name, "peer") && reachable) { + set_sync_flag(&peer_reachable, true); + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + assert(meshlink_channel_send(mesh, channel, "test", 5) >= 0); + return; +} + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + + + if(len == 0) { + set_sync_flag(&channel_closed, true); + send_event(ERR_NETWORK); + return; + } + + if(!strcmp(channel->node->name, "peer")) { + if(len == 5 && !memcmp(dat, "reply", 5)) { + set_sync_flag(&channel_opened, true); + } + } + + return; +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 2, 0 }; + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + signal(SIGUSR1, mesh_siguser1_signal_handler); + + // Execute test steps + + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 30)); + send_event(NODE_JOINED); + + // Open a channel to peer node + + meshlink_node_t *peer_node = meshlink_get_node(mesh, "peer"); + assert(peer_node); + meshlink_channel_t *channel = meshlink_channel_open(mesh, peer_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + + assert(wait_sync_flag(&channel_opened, 10)); + send_event(CHANNEL_OPENED); + assert(wait_sync_flag(&sigusr_received, 10)); + + sleep(40); + assert(meshlink_channel_send(mesh, channel, "after", 6) >= 0); + + + assert(wait_sync_flag(&channel_closed, 140)); + + + // All test steps executed - wait for signals to stop/start or close the mesh + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); +} diff --git a/test/blackbox/test_case_channel_conn_06/node_sim_peer.c b/test/blackbox/test_case_channel_conn_06/node_sim_peer.c new file mode 100644 index 0000000..5575c0e --- /dev/null +++ b/test/blackbox/test_case_channel_conn_06/node_sim_peer.c @@ -0,0 +1,115 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static int client_id = -1; + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + if(!strcmp(channel->node->name, "nut")) { + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + return false; +} + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + + if(len == 0) { + assert(mesh_event_sock_send(client_id, ERR_NETWORK, NULL, 0)); + return; + } + + if(!strcmp(channel->node->name, "nut")) { + if(!memcmp(dat, "test", 5)) { + assert(meshlink_channel_send(mesh, channel, "reply", 5) >= 0); + } + } + + return; +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 5, 0 }; + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Run peer node instance + + setup_signals(); + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + // All test steps executed - wait for signals to stop/start or close the mesh + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_case_channel_conn_06/node_sim_relay.c b/test/blackbox/test_case_channel_conn_06/node_sim_relay.c new file mode 100644 index 0000000..8e5e66e --- /dev/null +++ b/test/blackbox/test_case_channel_conn_06/node_sim_relay.c @@ -0,0 +1,59 @@ +/* + node_sim_relay.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 5, 0 }; + + // Setup required signals + + setup_signals(); + + // Run relay node instance + + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + + assert(meshlink_start(mesh)); + + /* All test steps executed - wait for signals to stop/start or close the mesh */ + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return 0; +} diff --git a/test/blackbox/test_case_channel_conn_07/Makefile.am b/test/blackbox/test_case_channel_conn_07/Makefile.am new file mode 100644 index 0000000..8bde53d --- /dev/null +++ b/test/blackbox/test_case_channel_conn_07/Makefile.am @@ -0,0 +1,13 @@ +check_PROGRAMS = node_sim_peer node_sim_nut node_sim_relay + +node_sim_peer_SOURCES = node_sim_peer.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_peer_LDADD = ../../../src/libmeshlink.la +node_sim_peer_CFLAGS = -D_GNU_SOURCE + +node_sim_nut_SOURCES = node_sim_nut.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_nut_LDADD = ../../../src/libmeshlink.la +node_sim_nut_CFLAGS = -D_GNU_SOURCE + +node_sim_relay_SOURCES = node_sim_relay.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_relay_LDADD = ../../../src/libmeshlink.la +node_sim_relay_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/test_case_channel_conn_07/node_sim_nut.c b/test/blackbox/test_case_channel_conn_07/node_sim_nut.c new file mode 100644 index 0000000..9be80b6 --- /dev/null +++ b/test/blackbox/test_case_channel_conn_07/node_sim_nut.c @@ -0,0 +1,180 @@ +/* + node_sim_nut.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static int client_id = -1; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag peer_unreachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag sigusr_received = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; + +static void send_event(mesh_event_t event); +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable); + +static void mesh_siguser1_signal_handler(int sig_num) { + (void)sig_num; + + set_sync_flag(&sigusr_received, true); + return; +} + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + + if(!strcasecmp(node->name, "peer")) { + if(reachable) { + set_sync_flag(&peer_reachable, true); + } else { + set_sync_flag(&peer_unreachable, true); + } + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + assert(meshlink_channel_send(mesh, channel, "test", 5) >= 0); + return; +} + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + + if(len == 0) { + send_event(ERR_NETWORK); + return; + } + + if(!strcmp(channel->node->name, "peer")) { + if(len == 5 && !memcmp(dat, "reply", 5)) { + set_sync_flag(&channel_opened, true); + } + } + + return; +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 5, 0 }; + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + signal(SIGUSR1, mesh_siguser1_signal_handler); + + // Execute test steps + + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + // Wait for peer node to join + assert(wait_sync_flag(&peer_reachable, 30)); + send_event(NODE_JOINED); + + // Open a channel to peer node + + meshlink_node_t *peer_node = meshlink_get_node(mesh, "peer"); + assert(peer_node); + meshlink_channel_t *channel = meshlink_channel_open(mesh, peer_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + + assert(wait_sync_flag(&channel_opened, 10)); + send_event(CHANNEL_OPENED); + + peer_unreachable.flag = false; + peer_reachable.flag = false; + assert(wait_sync_flag(&sigusr_received, 10)); + + assert(wait_sync_flag(&peer_unreachable, 100)); + send_event(NODE_UNREACHABLE); + + assert(wait_sync_flag(&peer_reachable, 100)); + send_event(NODE_REACHABLE); + + assert(meshlink_channel_send(mesh, channel, "after", 6) >= 0); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + assert(meshlink_channel_send(mesh, channel, "ping", 6) >= 0); + } + + meshlink_close(mesh); +} diff --git a/test/blackbox/test_case_channel_conn_07/node_sim_peer.c b/test/blackbox/test_case_channel_conn_07/node_sim_peer.c new file mode 100644 index 0000000..19c96f8 --- /dev/null +++ b/test/blackbox/test_case_channel_conn_07/node_sim_peer.c @@ -0,0 +1,115 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static int client_id = -1; + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + if(!strcmp(channel->node->name, "nut")) { + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + return false; +} + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + if(len == 0) { + assert(mesh_event_sock_send(client_id, ERR_NETWORK, NULL, 0)); + return; + } + + if(!strcmp(channel->node->name, "nut")) { + if(!memcmp(dat, "test", 5)) { + assert(meshlink_channel_send(mesh, channel, "reply", 5) >= 0); + } else if(!memcmp(dat, "after", 5)) { + assert(mesh_event_sock_send(client_id, CHANNEL_DATA_RECIEVED, NULL, 0)); + } + } + + return; +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 2, 0 }; + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Run peer node instance + + setup_signals(); + + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + // All test steps executed - wait for signals to stop/start or close the mesh + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_case_channel_conn_07/node_sim_relay.c b/test/blackbox/test_case_channel_conn_07/node_sim_relay.c new file mode 100644 index 0000000..8e5e66e --- /dev/null +++ b/test/blackbox/test_case_channel_conn_07/node_sim_relay.c @@ -0,0 +1,59 @@ +/* + node_sim_relay.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 5, 0 }; + + // Setup required signals + + setup_signals(); + + // Run relay node instance + + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + + assert(meshlink_start(mesh)); + + /* All test steps executed - wait for signals to stop/start or close the mesh */ + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return 0; +} diff --git a/test/blackbox/test_case_channel_conn_08/Makefile.am b/test/blackbox/test_case_channel_conn_08/Makefile.am new file mode 100644 index 0000000..8bde53d --- /dev/null +++ b/test/blackbox/test_case_channel_conn_08/Makefile.am @@ -0,0 +1,13 @@ +check_PROGRAMS = node_sim_peer node_sim_nut node_sim_relay + +node_sim_peer_SOURCES = node_sim_peer.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_peer_LDADD = ../../../src/libmeshlink.la +node_sim_peer_CFLAGS = -D_GNU_SOURCE + +node_sim_nut_SOURCES = node_sim_nut.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_nut_LDADD = ../../../src/libmeshlink.la +node_sim_nut_CFLAGS = -D_GNU_SOURCE + +node_sim_relay_SOURCES = node_sim_relay.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_relay_LDADD = ../../../src/libmeshlink.la +node_sim_relay_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/test_case_channel_conn_08/node_sim_nut.c b/test/blackbox/test_case_channel_conn_08/node_sim_nut.c new file mode 100644 index 0000000..9579f01 --- /dev/null +++ b/test/blackbox/test_case_channel_conn_08/node_sim_nut.c @@ -0,0 +1,167 @@ +/* + node_sim_nut.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static int client_id = -1; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; + +static void send_event(mesh_event_t event); +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, + bool reachable); + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcasecmp(node->name, "peer")) { + if(reachable) { + set_sync_flag(&peer_reachable, true); + } else { + peer_reachable.flag = false; + } + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + assert(meshlink_channel_send(mesh, channel, "test", 5) >= 0); + return; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + + if(len == 0) { + //send_event(ERR_NETWORK); + return; + } + + if(!strcmp(channel->node->name, "peer")) { + if(!memcmp(dat, "reply", 5)) { + set_sync_flag(&channel_opened, true); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } + + return; +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 5, 0 }; + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + setup_signals(); + + // Execute test steps + + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 10)); + send_event(NODE_JOINED); + + // Open a channel to peer node + + meshlink_node_t *peer_node = meshlink_get_node(mesh, "peer"); + assert(peer_node); + meshlink_channel_t *channel = meshlink_channel_open(mesh, peer_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + + assert(wait_sync_flag(&channel_opened, 10)); + send_event(CHANNEL_OPENED); + + // Restarting the node instance + + meshlink_stop(mesh); + assert(meshlink_start(mesh)); + + assert(wait_sync_flag(&peer_reachable, 60)); + send_event(NODE_RESTARTED); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); +} diff --git a/test/blackbox/test_case_channel_conn_08/node_sim_peer.c b/test/blackbox/test_case_channel_conn_08/node_sim_peer.c new file mode 100644 index 0000000..b087341 --- /dev/null +++ b/test/blackbox/test_case_channel_conn_08/node_sim_peer.c @@ -0,0 +1,137 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +static struct sync_flag sigusr = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static int client_id = -1; + +static void mesh_siguser1_signal_handler(int sig_num) { + (void)sig_num; + + set_sync_flag(&sigusr, true); + + return; +} + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + if(!strcmp(channel->node->name, "nut")) { + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + return false; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + (void)channel; + (void)dat; + (void)len; + + if(len == 0) { + mesh_event_sock_send(client_id, ERR_NETWORK, NULL, 0); + return; + } + + if(!strcmp(channel->node->name, "nut") && !memcmp(dat, "test", 5)) { + assert(meshlink_channel_send(mesh, channel, "reply", 5) >= 0); + } + + return; +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 2, 0 }; + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + signal(SIGUSR1, mesh_siguser1_signal_handler); + + // Run peer node instance + + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + assert(wait_sync_flag(&sigusr, 140)); + meshlink_channel_t *channel = mesh->priv; + assert(meshlink_channel_send(mesh, channel, "failure", 7)); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_case_channel_conn_08/node_sim_relay.c b/test/blackbox/test_case_channel_conn_08/node_sim_relay.c new file mode 100644 index 0000000..8e5e66e --- /dev/null +++ b/test/blackbox/test_case_channel_conn_08/node_sim_relay.c @@ -0,0 +1,59 @@ +/* + node_sim_relay.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 5, 0 }; + + // Setup required signals + + setup_signals(); + + // Run relay node instance + + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + + assert(meshlink_start(mesh)); + + /* All test steps executed - wait for signals to stop/start or close the mesh */ + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return 0; +} diff --git a/test/blackbox/test_case_meta_conn_01/Makefile.am b/test/blackbox/test_case_meta_conn_01/Makefile.am new file mode 100644 index 0000000..28fa44b --- /dev/null +++ b/test/blackbox/test_case_meta_conn_01/Makefile.am @@ -0,0 +1,13 @@ +check_PROGRAMS = node_sim_peer node_sim_relay node_sim_nut + +node_sim_peer_SOURCES = node_sim_peer.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c +node_sim_peer_LDADD = ../../../src/libmeshlink.la +node_sim_peer_CFLAGS = -D_GNU_SOURCE + +node_sim_relay_SOURCES = node_sim_relay.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c +node_sim_relay_LDADD = ../../../src/libmeshlink.la +node_sim_relay_CFLAGS = -D_GNU_SOURCE + +node_sim_nut_SOURCES = node_sim_nut.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c +node_sim_nut_LDADD = ../../../src/libmeshlink.la +node_sim_nut_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/test_case_meta_conn_01/node_sim_nut.c b/test/blackbox/test_case_meta_conn_01/node_sim_nut.c new file mode 100644 index 0000000..5cf624f --- /dev/null +++ b/test/blackbox/test_case_meta_conn_01/node_sim_nut.c @@ -0,0 +1,133 @@ +/* + node_sim.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 + +static bool conn_status = false; + +static void callback_logger(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) { + (void)mesh; + (void)level; + + char connection_match_msg[100]; + + fprintf(stderr, "meshlink>> %s\n", text); + + if(strstr(text, "Connection") || strstr(text, "connection")) { + assert(snprintf(connection_match_msg, sizeof(connection_match_msg), + "Connection with peer") >= 0); + + if(strstr(text, connection_match_msg) && strstr(text, "activated")) { + conn_status = true; + return; + } + + assert(snprintf(connection_match_msg, sizeof(connection_match_msg), + "Already connected to peer") >= 0); + + if(strstr(text, connection_match_msg)) { + conn_status = true; + return; + } + + assert(snprintf(connection_match_msg, sizeof(connection_match_msg), + "Connection closed by peer") >= 0); + + if(strstr(text, connection_match_msg)) { + conn_status = false; + return; + } + + assert(snprintf(connection_match_msg, sizeof(connection_match_msg), + "Closing connection with peer") >= 0); + + if(strstr(text, connection_match_msg)) { + conn_status = false; + return; + } + } + + return; +} + +int main(int argc, char *argv[]) { + (void)argc; + + int client_id = -1; + + if((argv[3]) && (argv[4])) { + client_id = atoi(argv[3]); + mesh_event_sock_connect(argv[4]); + } + + execute_open(argv[1], argv[2]); + meshlink_set_log_cb(mesh_handle, MESHLINK_DEBUG, callback_logger); + + if(argv[5]) { + execute_join(argv[5]); + } + + execute_start(); + + if(!mesh_event_sock_send(client_id, NODE_STARTED, NULL, 0)) { + fprintf(stderr, "Trying to resend mesh event\n"); + sleep(1); + } + + /* Connectivity of peer */ + while(!conn_status) { + sleep(1); + } + + fprintf(stderr, "Connected with Peer\n"); + assert(mesh_event_sock_send(client_id, META_CONN_SUCCESSFUL, NULL, 0)); + + /* Connectivity of peer */ + while(conn_status) { + sleep(1); + } + + fprintf(stderr, "Closed connection with Peer\n"); + assert(mesh_event_sock_send(client_id, META_CONN_CLOSED, NULL, 0)); + + /* Connectivity of peer */ + while(!conn_status) { + sleep(1); + } + + fprintf(stderr, "Connected with Peer\n"); + assert(mesh_event_sock_send(client_id, META_RECONN_SUCCESSFUL, NULL, 0)); + + execute_close(); + assert(meshlink_destroy(argv[1])); + return 0; +} diff --git a/test/blackbox/test_case_meta_conn_01/node_sim_peer.c b/test/blackbox/test_case_meta_conn_01/node_sim_peer.c new file mode 100644 index 0000000..5dd8807 --- /dev/null +++ b/test/blackbox/test_case_meta_conn_01/node_sim_peer.c @@ -0,0 +1,64 @@ +/* + node_sim.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 5, 0 }; + int client_id = -1; + + if((argv[3]) && (argv[4])) { + client_id = atoi(argv[3]); + mesh_event_sock_connect(argv[4]); + } + + /* Setup required signals */ + setup_signals(); + + /* Execute test steps */ + execute_open(argv[1], argv[2]); + + if(argv[5]) { + execute_join(argv[5]); + } + + execute_start(); + + if(client_id != -1) { + if(!mesh_event_sock_send(client_id, NODE_STARTED, NULL, 0)) { + fprintf(stderr, "Trying to resend mesh event\n"); + sleep(1); + } + } + + /* All test steps executed - wait for signals to stop/start or close the mesh */ + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + execute_close(); + assert(meshlink_destroy(argv[1])); +} diff --git a/test/blackbox/test_case_meta_conn_01/node_sim_relay.c b/test/blackbox/test_case_meta_conn_01/node_sim_relay.c new file mode 100644 index 0000000..cde644c --- /dev/null +++ b/test/blackbox/test_case_meta_conn_01/node_sim_relay.c @@ -0,0 +1,60 @@ +/* + node_sim.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 5, 0 }; + + int client_id = -1; + + if((argv[3]) && (argv[4])) { + client_id = atoi(argv[3]); + mesh_event_sock_connect(argv[4]); + } + + /* Setup required signals */ + setup_signals(); + + /* Execute test steps */ + execute_open(argv[1], argv[2]); + execute_start(); + + if(client_id != -1) { + if(!mesh_event_sock_send(client_id, NODE_STARTED, NULL, 0)) { + fprintf(stderr, "Trying to resend mesh event\n"); + sleep(1); + } + } + + /* All test steps executed - wait for signals to stop/start or close the mesh */ + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + execute_close(); + assert(meshlink_destroy(argv[1])); +} diff --git a/test/blackbox/test_case_meta_conn_01/test/node_step.sh b/test/blackbox/test_case_meta_conn_01/test/node_step.sh new file mode 100755 index 0000000..fe28423 --- /dev/null +++ b/test/blackbox/test_case_meta_conn_01/test/node_step.sh @@ -0,0 +1,25 @@ +# node_step.sh -- Script to send signal to control Mesh Node Simulation +# Copyright (C) 2018 Guus Sliepen +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# Read command-line arguments +prog_name=$1 +signal=$2 + +# Find instance of running program and send the named signal to it +pid=`/bin/pidof -s ${prog_name}` +kill -${signal} ${pid} +exit $? diff --git a/test/blackbox/test_case_meta_conn_02/Makefile.am b/test/blackbox/test_case_meta_conn_02/Makefile.am new file mode 100644 index 0000000..28fa44b --- /dev/null +++ b/test/blackbox/test_case_meta_conn_02/Makefile.am @@ -0,0 +1,13 @@ +check_PROGRAMS = node_sim_peer node_sim_relay node_sim_nut + +node_sim_peer_SOURCES = node_sim_peer.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c +node_sim_peer_LDADD = ../../../src/libmeshlink.la +node_sim_peer_CFLAGS = -D_GNU_SOURCE + +node_sim_relay_SOURCES = node_sim_relay.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c +node_sim_relay_LDADD = ../../../src/libmeshlink.la +node_sim_relay_CFLAGS = -D_GNU_SOURCE + +node_sim_nut_SOURCES = node_sim_nut.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c +node_sim_nut_LDADD = ../../../src/libmeshlink.la +node_sim_nut_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/test_case_meta_conn_02/node_sim_nut.c b/test/blackbox/test_case_meta_conn_02/node_sim_nut.c new file mode 100644 index 0000000..c197c58 --- /dev/null +++ b/test/blackbox/test_case_meta_conn_02/node_sim_nut.c @@ -0,0 +1,118 @@ +/* + node_sim.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 + +static bool conn_status = false; + +static void callback_logger(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) { + (void)mesh; + (void)level; + + char connection_match_msg[100]; + + fprintf(stderr, "meshlink>> %s\n", text); + + if(strstr(text, "Connection") || strstr(text, "connection")) { + assert(snprintf(connection_match_msg, sizeof(connection_match_msg), + "Connection with peer") >= 0); + + if(strstr(text, connection_match_msg) && strstr(text, "activated")) { + conn_status = true; + return; + } + + assert(snprintf(connection_match_msg, sizeof(connection_match_msg), + "Already connected to peer") >= 0); + + if(strstr(text, connection_match_msg)) { + conn_status = true; + return; + } + + assert(snprintf(connection_match_msg, sizeof(connection_match_msg), + "Connection closed by peer") >= 0); + + if(strstr(text, connection_match_msg)) { + conn_status = false; + return; + } + + assert(snprintf(connection_match_msg, sizeof(connection_match_msg), + "Closing connection with peer") >= 0); + + if(strstr(text, connection_match_msg)) { + conn_status = false; + return; + } + } + + return; +} + +int main(int argc, char *argv[]) { + (void)argc; + + int client_id = -1; + + if((argv[3]) && (argv[4])) { + client_id = atoi(argv[3]); + mesh_event_sock_connect(argv[4]); + } + + execute_open(argv[1], argv[2]); + meshlink_set_log_cb(mesh_handle, MESHLINK_INFO, callback_logger); + + if(argv[5]) { + execute_join(argv[5]); + } + + execute_start(); + + if(!mesh_event_sock_send(client_id, NODE_STARTED, NULL, 0)) { + fprintf(stderr, "Trying to resend mesh event\n"); + sleep(1); + } + + /* Connectivity of peer */ + while(!conn_status) { + sleep(1); + } + + fprintf(stderr, "Connected with Peer\n"); + assert(mesh_event_sock_send(client_id, META_CONN_SUCCESSFUL, NULL, 0)); + + execute_close(); + assert(meshlink_destroy(argv[1])); + + return 0; +} diff --git a/test/blackbox/test_case_meta_conn_02/node_sim_peer.c b/test/blackbox/test_case_meta_conn_02/node_sim_peer.c new file mode 100644 index 0000000..91ac601 --- /dev/null +++ b/test/blackbox/test_case_meta_conn_02/node_sim_peer.c @@ -0,0 +1,70 @@ +/* + node_sim.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 5, 0 }; + int client_id = -1; + + if((argv[3]) && (argv[4])) { + client_id = atoi(argv[3]); + mesh_event_sock_connect(argv[4]); + } + + /* Setup required signals */ + setup_signals(); + + /* Execute test steps */ + execute_open(argv[1], argv[2]); + + if(argv[5]) { + execute_join(argv[5]); + } + + execute_start(); + + if(client_id != -1) { + if(!mesh_event_sock_send(client_id, NODE_STARTED, NULL, 0)) { + fprintf(stderr, "Trying to resend mesh event\n"); + sleep(1); + } + } + + /* All test steps executed - wait for signals to stop/start or close the mesh */ + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + execute_close(); + assert(meshlink_destroy(argv[1])); +} diff --git a/test/blackbox/test_case_meta_conn_02/node_sim_relay.c b/test/blackbox/test_case_meta_conn_02/node_sim_relay.c new file mode 100644 index 0000000..57ea254 --- /dev/null +++ b/test/blackbox/test_case_meta_conn_02/node_sim_relay.c @@ -0,0 +1,66 @@ +/* + node_sim.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 5, 0 }; + + int client_id = -1; + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + /* Setup required signals */ + setup_signals(); + + /* Execute test steps */ + execute_open(argv[CMD_LINE_ARG_NODENAME], argv[CMD_LINE_ARG_DEVCLASS]); + execute_start(); + + if(client_id != -1) { + if(!mesh_event_sock_send(client_id, NODE_STARTED, NULL, 0)) { + fprintf(stderr, "Trying to resend mesh event\n"); + sleep(1); + } + } + + /* All test steps executed - wait for signals to stop/start or close the mesh */ + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + execute_close(); + assert(meshlink_destroy(argv[1])); +} diff --git a/test/blackbox/test_case_meta_conn_03/Makefile.am b/test/blackbox/test_case_meta_conn_03/Makefile.am new file mode 100644 index 0000000..28fa44b --- /dev/null +++ b/test/blackbox/test_case_meta_conn_03/Makefile.am @@ -0,0 +1,13 @@ +check_PROGRAMS = node_sim_peer node_sim_relay node_sim_nut + +node_sim_peer_SOURCES = node_sim_peer.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c +node_sim_peer_LDADD = ../../../src/libmeshlink.la +node_sim_peer_CFLAGS = -D_GNU_SOURCE + +node_sim_relay_SOURCES = node_sim_relay.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c +node_sim_relay_LDADD = ../../../src/libmeshlink.la +node_sim_relay_CFLAGS = -D_GNU_SOURCE + +node_sim_nut_SOURCES = node_sim_nut.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c +node_sim_nut_LDADD = ../../../src/libmeshlink.la +node_sim_nut_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/test_case_meta_conn_03/node_sim_nut.c b/test/blackbox/test_case_meta_conn_03/node_sim_nut.c new file mode 100644 index 0000000..4038d24 --- /dev/null +++ b/test/blackbox/test_case_meta_conn_03/node_sim_nut.c @@ -0,0 +1,131 @@ +/* + node_sim.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +static bool conn_status = false; + +void callback_logger(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) { + (void)mesh; + (void)level; + + char connection_match_msg[100]; + + fprintf(stderr, "meshlink>> %s\n", text); + + if(strstr(text, "Connection") || strstr(text, "connection")) { + assert(snprintf(connection_match_msg, sizeof(connection_match_msg), + "Connection with peer") >= 0); + + if(strstr(text, connection_match_msg) && strstr(text, "activated")) { + conn_status = true; + return; + } + + assert(snprintf(connection_match_msg, sizeof(connection_match_msg), + "Already connected to peer") >= 0); + + if(strstr(text, connection_match_msg)) { + conn_status = true; + return; + } + + assert(snprintf(connection_match_msg, sizeof(connection_match_msg), + "Connection closed by peer") >= 0); + + if(strstr(text, connection_match_msg)) { + conn_status = false; + return; + } + + assert(snprintf(connection_match_msg, sizeof(connection_match_msg), + "Closing connection with peer") >= 0); + + if(strstr(text, connection_match_msg)) { + conn_status = false; + return; + } + } + + return; +} + +int main(int argc, char *argv[]) { + (void)argc; + + int client_id = -1; + bool result = false; + int i; + + if((argv[3]) && (argv[4])) { + client_id = atoi(argv[3]); + mesh_event_sock_connect(argv[4]); + } + + execute_open(argv[1], argv[2]); + meshlink_set_log_cb(mesh_handle, MESHLINK_DEBUG, callback_logger); + + if(argv[5]) { + execute_join(argv[5]); + } + + execute_start(); + mesh_event_sock_send(client_id, NODE_STARTED, NULL, 0); + + /* Connectivity of peer is checked using meshlink_get_node API */ + while(!conn_status) { + sleep(1); + } + + sleep(1); + fprintf(stderr, "Connected with Peer\n"); + mesh_event_sock_send(client_id, META_CONN_SUCCESSFUL, NULL, 0); + + conn_status = false; + fprintf(stderr, "Waiting 120 sec for peer to be re-connected\n"); + + for(i = 0; i < 120; i++) { + if(conn_status) { + result = true; + break; + } + + sleep(1); + } + + if(result) { + fprintf(stderr, "Re-connected with Peer\n"); + mesh_event_sock_send(client_id, META_RECONN_SUCCESSFUL, NULL, 0); + } else { + fprintf(stderr, "Failed to reconnect with Peer\n"); + mesh_event_sock_send(client_id, META_RECONN_FAILURE, NULL, 0); + } + + execute_close(); + assert(meshlink_destroy(argv[1])); + + return 0; +} diff --git a/test/blackbox/test_case_meta_conn_03/node_sim_peer.c b/test/blackbox/test_case_meta_conn_03/node_sim_peer.c new file mode 100644 index 0000000..91ac601 --- /dev/null +++ b/test/blackbox/test_case_meta_conn_03/node_sim_peer.c @@ -0,0 +1,70 @@ +/* + node_sim.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 5, 0 }; + int client_id = -1; + + if((argv[3]) && (argv[4])) { + client_id = atoi(argv[3]); + mesh_event_sock_connect(argv[4]); + } + + /* Setup required signals */ + setup_signals(); + + /* Execute test steps */ + execute_open(argv[1], argv[2]); + + if(argv[5]) { + execute_join(argv[5]); + } + + execute_start(); + + if(client_id != -1) { + if(!mesh_event_sock_send(client_id, NODE_STARTED, NULL, 0)) { + fprintf(stderr, "Trying to resend mesh event\n"); + sleep(1); + } + } + + /* All test steps executed - wait for signals to stop/start or close the mesh */ + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + execute_close(); + assert(meshlink_destroy(argv[1])); +} diff --git a/test/blackbox/test_case_meta_conn_03/node_sim_relay.c b/test/blackbox/test_case_meta_conn_03/node_sim_relay.c new file mode 100644 index 0000000..a6f9177 --- /dev/null +++ b/test/blackbox/test_case_meta_conn_03/node_sim_relay.c @@ -0,0 +1,66 @@ +/* + node_sim.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 5, 0 }; + + int client_id = -1; + + if((argv[3]) && (argv[4])) { + client_id = atoi(argv[3]); + mesh_event_sock_connect(argv[4]); + } + + /* Setup required signals */ + setup_signals(); + + /* Execute test steps */ + execute_open(argv[1], argv[2]); + execute_start(); + + if(client_id != -1) { + if(!mesh_event_sock_send(client_id, NODE_STARTED, NULL, 0)) { + fprintf(stderr, "Trying to resend mesh event\n"); + sleep(1); + } + } + + /* All test steps executed - wait for signals to stop/start or close the mesh */ + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + execute_close(); + assert(meshlink_destroy(argv[1])); +} diff --git a/test/blackbox/test_case_meta_conn_04/Makefile.am b/test/blackbox/test_case_meta_conn_04/Makefile.am new file mode 100644 index 0000000..28fa44b --- /dev/null +++ b/test/blackbox/test_case_meta_conn_04/Makefile.am @@ -0,0 +1,13 @@ +check_PROGRAMS = node_sim_peer node_sim_relay node_sim_nut + +node_sim_peer_SOURCES = node_sim_peer.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c +node_sim_peer_LDADD = ../../../src/libmeshlink.la +node_sim_peer_CFLAGS = -D_GNU_SOURCE + +node_sim_relay_SOURCES = node_sim_relay.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c +node_sim_relay_LDADD = ../../../src/libmeshlink.la +node_sim_relay_CFLAGS = -D_GNU_SOURCE + +node_sim_nut_SOURCES = node_sim_nut.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c +node_sim_nut_LDADD = ../../../src/libmeshlink.la +node_sim_nut_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/test_case_meta_conn_04/node_sim_nut.c b/test/blackbox/test_case_meta_conn_04/node_sim_nut.c new file mode 100644 index 0000000..e208572 --- /dev/null +++ b/test/blackbox/test_case_meta_conn_04/node_sim_nut.c @@ -0,0 +1,130 @@ +/* + node_sim.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 + +static bool conn_status = false; + +void callback_logger(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) { + (void)mesh; + (void)level; + + char connection_match_msg[100]; + + fprintf(stderr, "meshlink>> %s\n", text); + + if(strstr(text, "Connection") || strstr(text, "connection")) { + assert(snprintf(connection_match_msg, sizeof(connection_match_msg), + "Connection with peer") >= 0); + + if(strstr(text, connection_match_msg) && strstr(text, "activated")) { + conn_status = true; + return; + } + + assert(snprintf(connection_match_msg, sizeof(connection_match_msg), + "Already connected to peer") >= 0); + + if(strstr(text, connection_match_msg)) { + conn_status = true; + return; + } + + assert(snprintf(connection_match_msg, sizeof(connection_match_msg), + "Connection closed by peer") >= 0); + + if(strstr(text, connection_match_msg)) { + conn_status = false; + return; + } + + assert(snprintf(connection_match_msg, sizeof(connection_match_msg), + "Closing connection with peer") >= 0); + + if(strstr(text, connection_match_msg)) { + conn_status = false; + return; + } + } + + return; +} + +int main(int argc, char *argv[]) { + (void)argc; + + int clientId = -1; + char *invite_peer; + + if((argv[3]) && (argv[4])) { + clientId = atoi(argv[3]); + mesh_event_sock_connect(argv[4]); + } + + execute_open(argv[1], argv[2]); + meshlink_set_log_cb(mesh_handle, MESHLINK_INFO, callback_logger); + + execute_start(); + + if(!mesh_event_sock_send(clientId, NODE_STARTED, NULL, 0)) { + fprintf(stderr, "Trying to resend mesh event\n"); + sleep(1); + } + + if(!argv[CMD_LINE_ARG_INVITEURL]) { + fprintf(stderr, "Generating Invitation to PEER\n"); + invite_peer = execute_invite("peer", NULL); + assert(invite_peer != NULL); + + if(!mesh_event_sock_send(clientId, NODE_INVITATION, invite_peer, strlen(invite_peer) + 1)) { + fprintf(stderr, "Trying to resend mesh event\n"); + sleep(1); + } + } + + fprintf(stderr, "Waiting for PEER to be connected\n"); + + /* Connectivity of peer is checked */ + while(!conn_status) { + sleep(1); + } + + fprintf(stderr, "Connected with Peer\n"); + + if(!mesh_event_sock_send(clientId, META_CONN_SUCCESSFUL, NULL, 0)) { + fprintf(stderr, "Trying to resend mesh event\n"); + sleep(1); + } + + execute_close(); + assert(meshlink_destroy(argv[1])); +} diff --git a/test/blackbox/test_case_meta_conn_04/node_sim_peer.c b/test/blackbox/test_case_meta_conn_04/node_sim_peer.c new file mode 100644 index 0000000..b003e06 --- /dev/null +++ b/test/blackbox/test_case_meta_conn_04/node_sim_peer.c @@ -0,0 +1,70 @@ +/* + node_sim.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 5, 0 }; + int client_id = -1; + + if((argv[3]) && (argv[4])) { + client_id = atoi(argv[3]); + mesh_event_sock_connect(argv[4]); + } + + /* Setup required signals */ + setup_signals(); + + /* Execute test steps */ + execute_open(argv[1], argv[2]); + + if(argv[5]) { + execute_join(argv[5]); + } + + execute_start(); + + if(client_id != -1) { + while(!mesh_event_sock_send(client_id, NODE_STARTED, NULL, 0)) { + fprintf(stderr, "Trying to resend mesh event\n"); + sleep(1); + } + } + + /* All test steps executed - wait for signals to stop/start or close the mesh */ + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + execute_close(); + assert(meshlink_destroy(argv[1])); +} diff --git a/test/blackbox/test_case_meta_conn_04/node_sim_relay.c b/test/blackbox/test_case_meta_conn_04/node_sim_relay.c new file mode 100644 index 0000000..37bb11d --- /dev/null +++ b/test/blackbox/test_case_meta_conn_04/node_sim_relay.c @@ -0,0 +1,63 @@ +/* + node_sim.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 5, 0 }; + + int clientid = -1; + + if((argv[3]) && (argv[4])) { + clientid = atoi(argv[3]); + mesh_event_sock_connect(argv[4]); + } + + /* Setup required signals */ + setup_signals(); + + /* Execute test steps */ + execute_open(argv[1], argv[2]); + execute_start(); + + if(clientid != -1) { + mesh_event_sock_send(clientid, NODE_STARTED, NULL, 0); + } + + /* All test steps executed - wait for signals to stop/start or close the mesh */ + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + execute_close(); + assert(meshlink_destroy(argv[1])); +} diff --git a/test/blackbox/test_case_meta_conn_05/Makefile.am b/test/blackbox/test_case_meta_conn_05/Makefile.am new file mode 100644 index 0000000..28fa44b --- /dev/null +++ b/test/blackbox/test_case_meta_conn_05/Makefile.am @@ -0,0 +1,13 @@ +check_PROGRAMS = node_sim_peer node_sim_relay node_sim_nut + +node_sim_peer_SOURCES = node_sim_peer.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c +node_sim_peer_LDADD = ../../../src/libmeshlink.la +node_sim_peer_CFLAGS = -D_GNU_SOURCE + +node_sim_relay_SOURCES = node_sim_relay.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c +node_sim_relay_LDADD = ../../../src/libmeshlink.la +node_sim_relay_CFLAGS = -D_GNU_SOURCE + +node_sim_nut_SOURCES = node_sim_nut.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c +node_sim_nut_LDADD = ../../../src/libmeshlink.la +node_sim_nut_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/test_case_meta_conn_05/node_sim_nut.c b/test/blackbox/test_case_meta_conn_05/node_sim_nut.c new file mode 100644 index 0000000..1581ceb --- /dev/null +++ b/test/blackbox/test_case_meta_conn_05/node_sim_nut.c @@ -0,0 +1,147 @@ +/* + node_sim.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 + +static bool conn_status = false; + +void callback_logger(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) { + (void)mesh; + (void)level; + + char connection_match_msg[100]; + + fprintf(stderr, "meshlink>> %s\n", text); + + if(strstr(text, "Connection") || strstr(text, "connection")) { + assert(snprintf(connection_match_msg, sizeof(connection_match_msg), + "Connection with peer") >= 0); + + if(strstr(text, connection_match_msg) && strstr(text, "activated")) { + conn_status = true; + return; + } + + assert(snprintf(connection_match_msg, sizeof(connection_match_msg), + "Already connected to peer") >= 0); + + if(strstr(text, connection_match_msg)) { + conn_status = true; + return; + } + + assert(snprintf(connection_match_msg, sizeof(connection_match_msg), + "Connection closed by peer") >= 0); + + if(strstr(text, connection_match_msg)) { + conn_status = false; + return; + } + + assert(snprintf(connection_match_msg, sizeof(connection_match_msg), + "Closing connection with peer") >= 0); + + if(strstr(text, connection_match_msg)) { + conn_status = false; + return; + } + } + + return; +} + +int main(int argc, char *argv[]) { + (void)argc; + + int clientId = -1; + char *invite_peer; + + if((argv[3]) && (argv[4])) { + clientId = atoi(argv[3]); + mesh_event_sock_connect(argv[4]); + } + + execute_open(argv[1], argv[2]); + meshlink_set_log_cb(mesh_handle, MESHLINK_INFO, callback_logger); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + execute_join(argv[CMD_LINE_ARG_INVITEURL]); + } + + execute_start(); + + if(!mesh_event_sock_send(clientId, NODE_STARTED, NULL, 0)) { + fprintf(stderr, "Trying to resend mesh event\n"); + sleep(1); + } + + fprintf(stderr, "Generating Invitation to PEER\n"); + invite_peer = execute_invite("peer", NULL); + assert(invite_peer != NULL); + + if(!mesh_event_sock_send(clientId, NODE_INVITATION, invite_peer, strlen(invite_peer) + 1)) { + fprintf(stderr, "Trying to resend mesh event\n"); + sleep(1); + } + + fprintf(stderr, "Waiting for PEER to be connected\n"); + + /* Connectivity of peer is checked */ + while(!conn_status) { + sleep(1); + } + + fprintf(stderr, "Connected with Peer\n"); + + if(!mesh_event_sock_send(clientId, META_CONN_SUCCESSFUL, NULL, 0)) { + fprintf(stderr, "Trying to resend mesh event\n"); + sleep(1); + } + + conn_status = false; + fprintf(stderr, "Waiting for PEER to be re-connected\n"); + + /* Connectivity of peer */ + while(!conn_status) { + sleep(1); + } + + fprintf(stderr, "Re-connected with Peer\n"); + + if(!mesh_event_sock_send(clientId, META_CONN_SUCCESSFUL, NULL, 0)) { + fprintf(stderr, "Trying to resend mesh event\n"); + sleep(1); + } + + execute_close(); + assert(meshlink_destroy(argv[1])); +} diff --git a/test/blackbox/test_case_meta_conn_05/node_sim_peer.c b/test/blackbox/test_case_meta_conn_05/node_sim_peer.c new file mode 100644 index 0000000..91ac601 --- /dev/null +++ b/test/blackbox/test_case_meta_conn_05/node_sim_peer.c @@ -0,0 +1,70 @@ +/* + node_sim.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 5, 0 }; + int client_id = -1; + + if((argv[3]) && (argv[4])) { + client_id = atoi(argv[3]); + mesh_event_sock_connect(argv[4]); + } + + /* Setup required signals */ + setup_signals(); + + /* Execute test steps */ + execute_open(argv[1], argv[2]); + + if(argv[5]) { + execute_join(argv[5]); + } + + execute_start(); + + if(client_id != -1) { + if(!mesh_event_sock_send(client_id, NODE_STARTED, NULL, 0)) { + fprintf(stderr, "Trying to resend mesh event\n"); + sleep(1); + } + } + + /* All test steps executed - wait for signals to stop/start or close the mesh */ + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + execute_close(); + assert(meshlink_destroy(argv[1])); +} diff --git a/test/blackbox/test_case_meta_conn_05/node_sim_relay.c b/test/blackbox/test_case_meta_conn_05/node_sim_relay.c new file mode 100644 index 0000000..37bb11d --- /dev/null +++ b/test/blackbox/test_case_meta_conn_05/node_sim_relay.c @@ -0,0 +1,63 @@ +/* + node_sim.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 5, 0 }; + + int clientid = -1; + + if((argv[3]) && (argv[4])) { + clientid = atoi(argv[3]); + mesh_event_sock_connect(argv[4]); + } + + /* Setup required signals */ + setup_signals(); + + /* Execute test steps */ + execute_open(argv[1], argv[2]); + execute_start(); + + if(clientid != -1) { + mesh_event_sock_send(clientid, NODE_STARTED, NULL, 0); + } + + /* All test steps executed - wait for signals to stop/start or close the mesh */ + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + execute_close(); + assert(meshlink_destroy(argv[1])); +} diff --git a/test/blackbox/test_case_optimal_pmtu_01/node_sim_nut.c b/test/blackbox/test_case_optimal_pmtu_01/node_sim_nut.c new file mode 100644 index 0000000..6e745b7 --- /dev/null +++ b/test/blackbox/test_case_optimal_pmtu_01/node_sim_nut.c @@ -0,0 +1,304 @@ +/* + node_sim_nut.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/network_namespace_framework.h" +#include "../../utils.h" +#include "../run_blackbox_tests/test_optimal_pmtu.h" + +extern bool test_pmtu_nut_running; +extern bool test_pmtu_peer_running; +extern bool test_pmtu_relay_running; +extern struct sync_flag test_pmtu_nut_closed; +extern bool ping_channel_enable_07; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, + bool reachable); +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); + +pmtu_attr_t node_pmtu[2]; +static time_t node_shutdown_time = 0; +static bool mtu_set = true; + +static void print_mtu_calc(pmtu_attr_t node_pmtu) { + fprintf(stderr, "MTU size : %d\n", node_pmtu.mtu_size); + fprintf(stderr, "Probes took for calculating PMTU discovery : %d\n", node_pmtu.mtu_discovery.probes); + fprintf(stderr, "Probes total length took for calculating PMTU discovery : %d\n", node_pmtu.mtu_discovery.probes_total_len); + fprintf(stderr, "Time took for calculating PMTU discovery : %lu\n", node_pmtu.mtu_discovery.time); + fprintf(stderr, "Total MTU ping probes : %d\n", node_pmtu.mtu_ping.probes); + fprintf(stderr, "Total MTU ping probes length : %d\n", node_pmtu.mtu_ping.probes_total_len); + float avg = 0; + + if(node_pmtu.mtu_ping.probes) { + avg = (float)node_pmtu.mtu_ping.time / (float)node_pmtu.mtu_ping.probes; + } + + fprintf(stderr, "Average MTU ping probes ping time : %f\n", avg); + fprintf(stderr, "Total probes received %d\n", node_pmtu.mtu_recv_probes.probes); + fprintf(stderr, "Total probes sent %d\n", node_pmtu.mtu_sent_probes.probes); +} + +// Node status callback +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + // Signal pthread_cond_wait if peer is reachable + if(!strcasecmp(node->name, "peer") && reachable) { + set_sync_flag(&peer_reachable, true); + } + + return; +} + +// Channel poll callback +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + + // Send data via channel to trigger UDP peer to peer hole punching + assert(meshlink_channel_send(mesh, channel, "test", 5) >= 0); + return; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + if(len == 0) { + fail(); + return; + } + + if(!strcmp(channel->node->name, "peer")) { + if(!memcmp(dat, "reply", 5)) { + set_sync_flag(&channel_opened, true); + } else if(!memcmp(dat, "test", 5)) { + assert(meshlink_channel_send(mesh, channel, "reply", 5) >= 0); + } + } + + return; +} + +// Meshlink log handler +static void meshlink_logger(meshlink_handle_t *mesh, meshlink_log_level_t level, + const char *text) { + (void)mesh; + (void)level; + int probe_len; + int mtu_len; + int probes; + char node_name[100]; + int i = -1; + + time_t cur_time; + time_t probe_interval; + + cur_time = time(NULL); + assert(cur_time != -1); + + if(node_shutdown_time && cur_time >= node_shutdown_time) { + test_pmtu_nut_running = false; + } + + if(level == MESHLINK_INFO) { + fprintf(stderr, "\x1b[32m nut:\x1b[0m %s\n", text); + } + + /* Calculate the MTU parameter values from the meshlink logs */ + if(sscanf(text, "Sending MTU probe length %d to %s", &probe_len, node_name) == 2) { + find_node_index(i, node_name); + node_pmtu[i].mtu_sent_probes.probes += 1; + node_pmtu[i].mtu_sent_probes.probes_total_len += probe_len; + + if(node_pmtu[i].mtu_size) { + if(node_pmtu[i].mtu_sent_probes.time > node_pmtu[i].mtu_recv_probes.time) { + probe_interval = cur_time - node_pmtu[i].mtu_sent_probes.time; + } else { + probe_interval = cur_time - node_pmtu[i].mtu_recv_probes.time; + } + + node_pmtu[i].mtu_ping.probes += 1; + node_pmtu[i].mtu_ping.time += probe_interval; + node_pmtu[i].mtu_ping.probes_total_len += probe_len; + } + + node_pmtu[i].mtu_sent_probes.time = cur_time; + + } else if(sscanf(text, "Got MTU probe length %d from %s", &probe_len, node_name) == 2) { + find_node_index(i, node_name); + node_pmtu[i].mtu_recv_probes.probes += 1; + node_pmtu[i].mtu_recv_probes.probes_total_len += probe_len; + + if(node_pmtu[i].mtu_size) { + if(node_pmtu[i].mtu_sent_probes.time > node_pmtu[i].mtu_recv_probes.time) { + probe_interval = cur_time - node_pmtu[i].mtu_sent_probes.time; + } else { + probe_interval = cur_time - node_pmtu[i].mtu_recv_probes.time; + } + + node_pmtu[i].mtu_ping.probes += 1; + node_pmtu[i].mtu_ping.time += probe_interval; + node_pmtu[i].mtu_ping.probes_total_len += probe_len; + } + + node_pmtu[i].mtu_recv_probes.time = cur_time; + + } else if(sscanf(text, "Fixing MTU of %s to %d after %d probes", node_name, &mtu_len, &probes) == 3) { + + if(!node_shutdown_time && !strcasecmp("peer", node_name) && mtu_set) { + node_shutdown_time = cur_time + PING_TRACK_TIMEOUT; + mtu_set = false; + } + + find_node_index(i, node_name); + node_pmtu[i].mtu_discovery.probes = node_pmtu[i].mtu_recv_probes.probes + node_pmtu[i].mtu_sent_probes.probes; + node_pmtu[i].mtu_discovery.probes_total_len = node_pmtu[i].mtu_sent_probes.probes_total_len + node_pmtu[i].mtu_recv_probes.probes_total_len; + node_pmtu[i].mtu_discovery.time = cur_time - node_pmtu[i].mtu_start.time; + node_pmtu[i].mtu_discovery.count += 1; + node_pmtu[i].mtu_size = mtu_len; + + } else if(sscanf(text, "SPTPS key exchange with %s successful", node_name) == 1) { + find_node_index(i, node_name); + node_pmtu[i].mtu_start.time = cur_time; + node_pmtu[i].mtu_start.count += 1; + memset(&node_pmtu[i].mtu_discovery, 0, sizeof(struct pmtu_attr_para)); + memset(&node_pmtu[i].mtu_ping, 0, sizeof(struct pmtu_attr_para)); + memset(&node_pmtu[i].mtu_increase, 0, sizeof(struct pmtu_attr_para)); + + } else if(sscanf(text, "Increase in PMTU to %s detected, restarting PMTU discovery", node_name) == 1) { + find_node_index(i, node_name); + node_pmtu[i].mtu_increase.time = cur_time - node_pmtu[i].mtu_start.time; + node_pmtu[i].mtu_increase.count += 1; + + } else if(sscanf(text, "Trying to send MTU probe to unreachable or rekeying node %s", node_name) == 1) { + + } else if(sscanf(text, "%s did not respond to UDP ping, restarting PMTU discovery", node_name) == 1) { + + } else if(sscanf(text, "No response to MTU probes from %s", node_name) == 1) { + + } else if((sscanf(text, "Connection with %s activated", node_name) == 1) || (sscanf(text, "Already connected to %s", node_name) == 1)) { + + } else if((sscanf(text, "Connection closed by %s", node_name) == 1) || (sscanf(text, "Closing connection with %s", node_name) == 1)) { + } +} + +void *node_sim_pmtu_nut_01(void *arg) { + mesh_arg_t *mesh_arg = (mesh_arg_t *)arg; + struct timeval main_loop_wait = { 5, 0 }; + + set_sync_flag(&peer_reachable, false); + set_sync_flag(&channel_opened, false); + node_shutdown_time = 0; + mtu_set = true; + + // Run relay node instance + + meshlink_handle_t *mesh; + mesh = meshlink_open(mesh_arg->node_name, mesh_arg->confbase, mesh_arg->app_name, mesh_arg->dev_class); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_logger); + meshlink_set_node_status_cb(mesh, node_status_cb); + sleep(1); + + // Join relay node and if fails to join then try few more attempts + + if(mesh_arg->join_invitation) { + int attempts; + bool join_ret; + + for(attempts = 0; attempts < 10; attempts++) { + join_ret = meshlink_join(mesh, mesh_arg->join_invitation); + + if(join_ret) { + break; + } + + sleep(1); + } + + if(attempts == 10) { + fail(); + } + } + + assert(meshlink_start(mesh)); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 10)); + + // Open a channel to peer node + + meshlink_node_t *peer_node = meshlink_get_node(mesh, "peer"); + assert(peer_node); + meshlink_channel_t *channel = meshlink_channel_open(mesh, peer_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + + assert(wait_sync_flag(&channel_opened, 30)); + + // All test steps executed - wait for signals to stop/start or close the mesh + + time_t time_stamp, send_time; + + time_stamp = time(NULL); + send_time = time_stamp + 10; + + while(test_pmtu_nut_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + + // Ping the channel for every 10 seconds if ping_channel_enable_07 is enabled + + if(ping_channel_enable_07) { + time_stamp = time(NULL); + + if(time_stamp >= send_time) { + send_time = time_stamp + 10; + assert(meshlink_channel_send(mesh, channel, "ping", 5) == 5); + } + } + } + + // Send MTU probe parameters data to the test driver + meshlink_close(mesh); + + set_sync_flag(&test_pmtu_nut_closed, true); + fprintf(stderr, "NODE_PMTU_PEER :\n"); + print_mtu_calc(node_pmtu[NODE_PMTU_PEER]); + fprintf(stderr, "\nNODE_PMTU_RELAY :\n"); + print_mtu_calc(node_pmtu[NODE_PMTU_RELAY]); + + return NULL; +} diff --git a/test/blackbox/test_case_optimal_pmtu_01/node_sim_peer.c b/test/blackbox/test_case_optimal_pmtu_01/node_sim_peer.c new file mode 100644 index 0000000..597f44c --- /dev/null +++ b/test/blackbox/test_case_optimal_pmtu_01/node_sim_peer.c @@ -0,0 +1,133 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/network_namespace_framework.h" +#include "../../utils.h" +#include "../run_blackbox_tests/test_optimal_pmtu.h" + +extern bool test_pmtu_peer_running; + +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + if(!strcmp(channel->node->name, "nut")) { + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + //channel->node->priv = channel; + + return true; + } + + return false; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + if(len == 0) { + // channel closed + fail(); + return; + } + + if(!strcmp(channel->node->name, "nut")) { + if(!memcmp(dat, "reply", 5)) { + set_sync_flag(&channel_opened, true); + } else if(!memcmp(dat, "test", 5)) { + assert(meshlink_channel_send(mesh, channel, "reply", 5) >= 0); + } + } + + return; +} + +static void log_message(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) { + (void)mesh; + + if(level == MESHLINK_INFO) { + fprintf(stderr, "\x1b[34m peer:\x1b[0m %s\n", text); + } +} + +void *node_sim_pmtu_peer_01(void *arg) { + mesh_arg_t *mesh_arg = (mesh_arg_t *)arg; + struct timeval main_loop_wait = { 5, 0 }; + + // Run relay node instance + + + meshlink_handle_t *mesh; + mesh = meshlink_open(mesh_arg->node_name, mesh_arg->confbase, mesh_arg->app_name, mesh_arg->dev_class); + assert(mesh); + + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_message); + meshlink_set_channel_accept_cb(mesh, channel_accept); + + if(mesh_arg->join_invitation) { + int attempts; + bool join_ret; + + for(attempts = 0; attempts < 10; attempts++) { + join_ret = meshlink_join(mesh, mesh_arg->join_invitation); + + if(join_ret) { + break; + } + + sleep(1); + } + + if(attempts == 10) { + abort(); + } + } + + assert(meshlink_start(mesh)); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_pmtu_peer_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return NULL; +} diff --git a/test/blackbox/test_case_optimal_pmtu_01/node_sim_relay.c b/test/blackbox/test_case_optimal_pmtu_01/node_sim_relay.c new file mode 100644 index 0000000..7823f4b --- /dev/null +++ b/test/blackbox/test_case_optimal_pmtu_01/node_sim_relay.c @@ -0,0 +1,64 @@ +/* + node_sim_relay.c -- Implementation of Node Simulation for Meshlink Testing + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/network_namespace_framework.h" +#include "../../utils.h" +#include "../run_blackbox_tests/test_optimal_pmtu.h" + +extern bool test_pmtu_relay_running; + +void *node_sim_pmtu_relay_01(void *arg) { + mesh_arg_t *mesh_arg = (mesh_arg_t *)arg; + struct timeval main_loop_wait = { 5, 0 }; + + // Run relay node instance + + + meshlink_handle_t *mesh; + mesh = meshlink_open(mesh_arg->node_name, mesh_arg->confbase, mesh_arg->app_name, mesh_arg->dev_class); + assert(mesh); + + //meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_enable_discovery(mesh, false); + + assert(meshlink_start(mesh)); + //send_event(NODE_STARTED); + + /* All test steps executed - wait for signals to stop/start or close the mesh */ + while(test_pmtu_relay_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return 0; +} diff --git a/test/blackbox/test_case_optimal_pmtu_01/test_case_optimal_pmtu.h b/test/blackbox/test_case_optimal_pmtu_01/test_case_optimal_pmtu.h new file mode 100644 index 0000000..655324b --- /dev/null +++ b/test/blackbox/test_case_optimal_pmtu_01/test_case_optimal_pmtu.h @@ -0,0 +1,22 @@ +/* + test_case_optimal_pmtu.h -- Implementation of Node Simulation for Meshlink Testing + Copyright (C) 2019 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +extern void *node_sim_pmtu_nut_01(void *arg); +extern void *node_sim_pmtu_peer_01(void *arg); +extern void *node_sim_pmtu_relay_01(void *arg); diff --git a/test/blackbox/test_case_optimal_pmtu_02/Makefile.am b/test/blackbox/test_case_optimal_pmtu_02/Makefile.am new file mode 100644 index 0000000..fc9b98f --- /dev/null +++ b/test/blackbox/test_case_optimal_pmtu_02/Makefile.am @@ -0,0 +1,13 @@ +check_PROGRAMS = node_sim_peer node_sim_nut node_sim_relay + +node_sim_peer_SOURCES = ../test_case_optimal_pmtu_01/node_sim_peer.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_peer_LDADD = ../../../src/libmeshlink.la +node_sim_peer_CFLAGS = -D_GNU_SOURCE + +node_sim_nut_SOURCES = ../test_case_optimal_pmtu_01/node_sim_nut.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_nut_LDADD = ../../../src/libmeshlink.la +node_sim_nut_CFLAGS = -D_GNU_SOURCE + +node_sim_relay_SOURCES = ../test_case_optimal_pmtu_01/node_sim_relay.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_relay_LDADD = ../../../src/libmeshlink.la +node_sim_relay_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/test_case_optimal_pmtu_03/Makefile.am b/test/blackbox/test_case_optimal_pmtu_03/Makefile.am new file mode 100644 index 0000000..fc9b98f --- /dev/null +++ b/test/blackbox/test_case_optimal_pmtu_03/Makefile.am @@ -0,0 +1,13 @@ +check_PROGRAMS = node_sim_peer node_sim_nut node_sim_relay + +node_sim_peer_SOURCES = ../test_case_optimal_pmtu_01/node_sim_peer.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_peer_LDADD = ../../../src/libmeshlink.la +node_sim_peer_CFLAGS = -D_GNU_SOURCE + +node_sim_nut_SOURCES = ../test_case_optimal_pmtu_01/node_sim_nut.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_nut_LDADD = ../../../src/libmeshlink.la +node_sim_nut_CFLAGS = -D_GNU_SOURCE + +node_sim_relay_SOURCES = ../test_case_optimal_pmtu_01/node_sim_relay.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_relay_LDADD = ../../../src/libmeshlink.la +node_sim_relay_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/test_case_optimal_pmtu_04/Makefile.am b/test/blackbox/test_case_optimal_pmtu_04/Makefile.am new file mode 100644 index 0000000..fc9b98f --- /dev/null +++ b/test/blackbox/test_case_optimal_pmtu_04/Makefile.am @@ -0,0 +1,13 @@ +check_PROGRAMS = node_sim_peer node_sim_nut node_sim_relay + +node_sim_peer_SOURCES = ../test_case_optimal_pmtu_01/node_sim_peer.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_peer_LDADD = ../../../src/libmeshlink.la +node_sim_peer_CFLAGS = -D_GNU_SOURCE + +node_sim_nut_SOURCES = ../test_case_optimal_pmtu_01/node_sim_nut.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_nut_LDADD = ../../../src/libmeshlink.la +node_sim_nut_CFLAGS = -D_GNU_SOURCE + +node_sim_relay_SOURCES = ../test_case_optimal_pmtu_01/node_sim_relay.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_relay_LDADD = ../../../src/libmeshlink.la +node_sim_relay_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/test_case_optimal_pmtu_05/Makefile.am b/test/blackbox/test_case_optimal_pmtu_05/Makefile.am new file mode 100644 index 0000000..fc9b98f --- /dev/null +++ b/test/blackbox/test_case_optimal_pmtu_05/Makefile.am @@ -0,0 +1,13 @@ +check_PROGRAMS = node_sim_peer node_sim_nut node_sim_relay + +node_sim_peer_SOURCES = ../test_case_optimal_pmtu_01/node_sim_peer.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_peer_LDADD = ../../../src/libmeshlink.la +node_sim_peer_CFLAGS = -D_GNU_SOURCE + +node_sim_nut_SOURCES = ../test_case_optimal_pmtu_01/node_sim_nut.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_nut_LDADD = ../../../src/libmeshlink.la +node_sim_nut_CFLAGS = -D_GNU_SOURCE + +node_sim_relay_SOURCES = ../test_case_optimal_pmtu_01/node_sim_relay.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_relay_LDADD = ../../../src/libmeshlink.la +node_sim_relay_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/test_case_optimal_pmtu_06/Makefile.am b/test/blackbox/test_case_optimal_pmtu_06/Makefile.am new file mode 100644 index 0000000..fc9b98f --- /dev/null +++ b/test/blackbox/test_case_optimal_pmtu_06/Makefile.am @@ -0,0 +1,13 @@ +check_PROGRAMS = node_sim_peer node_sim_nut node_sim_relay + +node_sim_peer_SOURCES = ../test_case_optimal_pmtu_01/node_sim_peer.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_peer_LDADD = ../../../src/libmeshlink.la +node_sim_peer_CFLAGS = -D_GNU_SOURCE + +node_sim_nut_SOURCES = ../test_case_optimal_pmtu_01/node_sim_nut.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_nut_LDADD = ../../../src/libmeshlink.la +node_sim_nut_CFLAGS = -D_GNU_SOURCE + +node_sim_relay_SOURCES = ../test_case_optimal_pmtu_01/node_sim_relay.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_relay_LDADD = ../../../src/libmeshlink.la +node_sim_relay_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/test_case_optimal_pmtu_07/Makefile.am b/test/blackbox/test_case_optimal_pmtu_07/Makefile.am new file mode 100644 index 0000000..8bde53d --- /dev/null +++ b/test/blackbox/test_case_optimal_pmtu_07/Makefile.am @@ -0,0 +1,13 @@ +check_PROGRAMS = node_sim_peer node_sim_nut node_sim_relay + +node_sim_peer_SOURCES = node_sim_peer.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_peer_LDADD = ../../../src/libmeshlink.la +node_sim_peer_CFLAGS = -D_GNU_SOURCE + +node_sim_nut_SOURCES = node_sim_nut.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_nut_LDADD = ../../../src/libmeshlink.la +node_sim_nut_CFLAGS = -D_GNU_SOURCE + +node_sim_relay_SOURCES = node_sim_relay.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_relay_LDADD = ../../../src/libmeshlink.la +node_sim_relay_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/test_case_optimal_pmtu_07/node_sim_nut.c b/test/blackbox/test_case_optimal_pmtu_07/node_sim_nut.c new file mode 100644 index 0000000..06332e4 --- /dev/null +++ b/test/blackbox/test_case_optimal_pmtu_07/node_sim_nut.c @@ -0,0 +1,330 @@ +/* + node_sim_nut.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" +#include "../run_blackbox_tests/test_optimal_pmtu.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +#pragma pack(1) + +static int client_id = -1; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, + bool reachable); +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +static pmtu_attr_t node_pmtu[2]; + +static void print_mtu_calc(pmtu_attr_t node_pmtu) { + fprintf(stderr, "MTU size : %d\n", node_pmtu.mtu_size); + fprintf(stderr, "Probes took for calculating PMTU discovery : %d\n", node_pmtu.mtu_discovery.probes); + fprintf(stderr, "Probes total length took for calculating PMTU discovery : %d\n", node_pmtu.mtu_discovery.probes_total_len); + fprintf(stderr, "Time took for calculating PMTU discovery : %lu\n", node_pmtu.mtu_discovery.time); + fprintf(stderr, "Total MTU ping probes : %d\n", node_pmtu.mtu_ping.probes); + fprintf(stderr, "Total MTU ping probes length : %d\n", node_pmtu.mtu_ping.probes_total_len); + float avg = 0; + + if(node_pmtu.mtu_ping.probes) { + avg = (float)node_pmtu.mtu_ping.time / (float)node_pmtu.mtu_ping.probes; + } + + fprintf(stderr, "Average MTU ping probes ping time : %f\n", avg); + fprintf(stderr, "Total probes received %d\n", node_pmtu.mtu_recv_probes.probes); + fprintf(stderr, "Total probes sent %d\n", node_pmtu.mtu_sent_probes.probes); +} + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, + bool reachable) { + if(!strcasecmp(node->name, "peer") && reachable) { + set_sync_flag(&peer_reachable, true); + } + + mesh_event_sock_send(client_id, reachable ? NODE_JOINED : NODE_LEFT, node->name, 100); + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + assert(meshlink_channel_send(mesh, channel, "test", 5) >= 0); + return; +} + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + if(!strcmp(channel->node->name, "peer")) { + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + return false; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + if(len == 0) { + mesh_event_sock_send(client_id, ERR_NETWORK, channel->node->name, 100); + return; + } + + if(!strcmp(channel->node->name, "peer")) { + if(!memcmp(dat, "reply", 5)) { + set_sync_flag(&channel_opened, true); + fprintf(stderr, "GOT REPLY FROM PEER\n"); + } else if(!memcmp(dat, "test", 5)) { + assert(meshlink_channel_send(mesh, channel, "reply", 5) >= 0); + } + } + + return; +} + +void meshlink_logger(meshlink_handle_t *mesh, meshlink_log_level_t level, + const char *text) { + (void)mesh; + (void)level; + int probe_len; + int mtu_len; + int probes; + char node_name[100]; + int i = -1; + + time_t cur_time; + time_t probe_interval; + + cur_time = time(NULL); + assert(cur_time != -1); + + static time_t node_shutdown_time = 0; + bool mtu_probe = false; + + if(node_shutdown_time && cur_time >= node_shutdown_time) { + test_running = false; + } + + static const char *levelstr[] = { + [MESHLINK_DEBUG] = "\x1b[34mDEBUG", + [MESHLINK_INFO] = "\x1b[32mINFO", + [MESHLINK_WARNING] = "\x1b[33mWARNING", + [MESHLINK_ERROR] = "\x1b[31mERROR", + [MESHLINK_CRITICAL] = "\x1b[31mCRITICAL", + }; + + fprintf(stderr, "%s:\x1b[0m %s\n", levelstr[level], text); + + if(sscanf(text, "Sending MTU probe length %d to %s", &probe_len, node_name) == 2) { + find_node_index(i, node_name); + node_pmtu[i].mtu_sent_probes.probes += 1; + node_pmtu[i].mtu_sent_probes.probes_total_len += probe_len; + + if(node_pmtu[i].mtu_size) { + if(node_pmtu[i].mtu_sent_probes.time > node_pmtu[i].mtu_recv_probes.time) { + probe_interval = cur_time - node_pmtu[i].mtu_sent_probes.time; + } else { + probe_interval = cur_time - node_pmtu[i].mtu_recv_probes.time; + } + + node_pmtu[i].mtu_ping.probes += 1; + node_pmtu[i].mtu_ping.time += probe_interval; + node_pmtu[i].mtu_ping.probes_total_len += probe_len; + } + + node_pmtu[i].mtu_sent_probes.time = cur_time; + + } else if(sscanf(text, "Got MTU probe length %d from %s", &probe_len, node_name) == 2) { + find_node_index(i, node_name); + node_pmtu[i].mtu_recv_probes.probes += 1; + node_pmtu[i].mtu_recv_probes.probes_total_len += probe_len; + + if(node_pmtu[i].mtu_size) { + if(node_pmtu[i].mtu_sent_probes.time > node_pmtu[i].mtu_recv_probes.time) { + probe_interval = cur_time - node_pmtu[i].mtu_sent_probes.time; + } else { + probe_interval = cur_time - node_pmtu[i].mtu_recv_probes.time; + } + + node_pmtu[i].mtu_ping.probes += 1; + node_pmtu[i].mtu_ping.time += probe_interval; + node_pmtu[i].mtu_ping.probes_total_len += probe_len; + } + + node_pmtu[i].mtu_recv_probes.time = cur_time; + + } else if(sscanf(text, "Fixing MTU of %s to %d after %d probes", node_name, &mtu_len, &probes) == 3) { + static bool mtu_set = true; + + if(!node_shutdown_time && !strcasecmp("relay", node_name) && mtu_set) { + node_shutdown_time = cur_time + PING_TRACK_TIMEOUT; + mtu_set = false; + } + + find_node_index(i, node_name); + node_pmtu[i].mtu_discovery.probes = node_pmtu[i].mtu_recv_probes.probes + node_pmtu[i].mtu_sent_probes.probes; + node_pmtu[i].mtu_discovery.probes_total_len = node_pmtu[i].mtu_sent_probes.probes_total_len + node_pmtu[i].mtu_recv_probes.probes_total_len; + node_pmtu[i].mtu_discovery.time = cur_time - node_pmtu[i].mtu_start.time; + node_pmtu[i].mtu_discovery.count += 1; + node_pmtu[i].mtu_size = mtu_len; + + } else if(sscanf(text, "SPTPS key exchange with %s successful", node_name) == 1) { + find_node_index(i, node_name); + node_pmtu[i].mtu_start.time = cur_time; + node_pmtu[i].mtu_start.count += 1; + memset(&node_pmtu[i].mtu_discovery, 0, sizeof(struct pmtu_attr_para)); + memset(&node_pmtu[i].mtu_ping, 0, sizeof(struct pmtu_attr_para)); + memset(&node_pmtu[i].mtu_increase, 0, sizeof(struct pmtu_attr_para)); + + } else if(sscanf(text, "Increase in PMTU to %s detected, restarting PMTU discovery", node_name) == 1) { + find_node_index(i, node_name); + node_pmtu[i].mtu_increase.time = cur_time - node_pmtu[i].mtu_start.time; + node_pmtu[i].mtu_increase.count += 1; + + } else if(sscanf(text, "Trying to send MTU probe to unreachable or rekeying node %s", node_name) == 1) { + + } else if(sscanf(text, "%s did not respond to UDP ping, restarting PMTU discovery", node_name) == 1) { + + } else if(sscanf(text, "No response to MTU probes from %s", node_name) == 1) { + + } else if((sscanf(text, "Connection with %s activated", node_name) == 1) || (sscanf(text, "Already connected to %s", node_name) == 1)) { + mesh_event_sock_send(client_id, META_CONN_SUCCESSFUL, node_name, sizeof(node_name)); + + } else if((sscanf(text, "Connection closed by %s", node_name) == 1) || (sscanf(text, "Closing connection with %s", node_name) == 1)) { + mesh_event_sock_send(client_id, META_CONN_CLOSED, node_name, sizeof(node_name)); + + } +} + +int main(int argc, char *argv[]) { + struct timeval main_loop_wait = { 5, 0 }; + int i; + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + setup_signals(); + + // Execute test steps + + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_logger); + meshlink_set_node_status_cb(mesh, node_status_cb); + meshlink_enable_discovery(mesh, false); + sleep(1); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + int attempts; + bool join_ret; + + for(attempts = 0; attempts < 10; attempts++) { + join_ret = meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL]); + + if(join_ret) { + break; + } + + sleep(1); + } + + if(attempts == 10) { + abort(); + } + } + + assert(meshlink_start(mesh)); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 10)); + + // Open a channel to peer node + + meshlink_node_t *peer_node = meshlink_get_node(mesh, "peer"); + assert(peer_node); + meshlink_channel_t *channel = meshlink_channel_open(mesh, peer_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + + assert(wait_sync_flag(&channel_opened, 30)); + assert(mesh_event_sock_send(client_id, CHANNEL_OPENED, NULL, 0)); + + // All test steps executed - wait for signals to stop/start or close the mesh + + time_t time_stamp, send_time; + + time_stamp = time(NULL); + send_time = time_stamp + 10; + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + + time_stamp = time(NULL); + + if(time_stamp >= send_time) { + send_time = time_stamp + 10; + meshlink_channel_send(mesh, channel, "ping", 5); + } + } + + pmtu_attr_t send_mtu_data; + send_mtu_data = node_pmtu[NODE_PMTU_PEER]; + print_mtu_calc(send_mtu_data); + assert(mesh_event_sock_send(client_id, OPTIMAL_PMTU_PEER, &send_mtu_data, sizeof(send_mtu_data))); + send_mtu_data = node_pmtu[NODE_PMTU_RELAY]; + print_mtu_calc(send_mtu_data); + assert(mesh_event_sock_send(client_id, OPTIMAL_PMTU_RELAY, &send_mtu_data, sizeof(send_mtu_data))); + + meshlink_close(mesh); +} diff --git a/test/blackbox/test_case_optimal_pmtu_07/node_sim_peer.c b/test/blackbox/test_case_optimal_pmtu_07/node_sim_peer.c new file mode 100644 index 0000000..6a9ec47 --- /dev/null +++ b/test/blackbox/test_case_optimal_pmtu_07/node_sim_peer.c @@ -0,0 +1,168 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static struct sync_flag nut_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER}; + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +static int client_id = -1; + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, + bool reachable) { + if(!strcasecmp(node->name, "nut") && reachable) { + //set_sync_flag(&nut_reachable, true); + mesh_event_sock_send(client_id, reachable ? NODE_JOINED : NODE_LEFT, node->name, 100); + } + + return; +} + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + if(!strcmp(channel->node->name, "nut")) { + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + //channel->node->priv = channel; + + return true; + } + + return false; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + assert(meshlink_channel_send(mesh, channel, "test", 5) >= 0); + return; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + if(len == 0) { + mesh_event_sock_send(client_id, ERR_NETWORK, channel->node->name, 100); + return; + } + + if(!strcmp(channel->node->name, "nut")) { + if(!memcmp(dat, "reply", 5)) { + set_sync_flag(&channel_opened, true); + } else if(!memcmp(dat, "test", 5)) { + assert(meshlink_channel_send(mesh, channel, "reply", 5) >= 0); + } + } + + return; +} + +int main(int argc, char *argv[]) { + struct timeval main_loop_wait = { 2, 0 }; + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + + // Run peer node instance + + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + meshlink_enable_discovery(mesh, false); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + int attempts; + bool join_ret; + + for(attempts = 0; attempts < 10; attempts++) { + join_ret = meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL]); + + if(join_ret) { + break; + } + + sleep(1); + } + + if(attempts == 10) { + abort(); + } + } + + assert(meshlink_start(mesh)); + mesh_event_sock_send(client_id, NODE_STARTED, NULL, 0); + + //assert(wait_sync_flag(&nut_reachable, 10)); + + /*meshlink_node_t *nut_node = meshlink_get_node(mesh, "nut"); + assert(nut_node); + meshlink_channel_t *channel = meshlink_channel_open(mesh, nut_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + + assert(wait_sync_flag(&channel_opened, 20)); + mesh_event_sock_send(client_id, NODE_STARTED, NULL, 0);*/ + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_case_optimal_pmtu_07/node_sim_relay.c b/test/blackbox/test_case_optimal_pmtu_07/node_sim_relay.c new file mode 100644 index 0000000..dd62209 --- /dev/null +++ b/test/blackbox/test_case_optimal_pmtu_07/node_sim_relay.c @@ -0,0 +1,59 @@ +/* + node_sim_relay.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 + +int main(int argc, char *argv[]) { + struct timeval main_loop_wait = { 5, 0 }; + + // Setup required signals + + setup_signals(); + + // Run relay node instance + + meshlink_handle_t *mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_enable_discovery(mesh, false); + + meshlink_start(mesh); + //send_event(NODE_STARTED); + + /* All test steps executed - wait for signals to stop/start or close the mesh */ + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return 0; +} diff --git a/test/blackbox/test_cases_submesh01/Makefile.am b/test/blackbox/test_cases_submesh01/Makefile.am new file mode 100644 index 0000000..bb0ff44 --- /dev/null +++ b/test/blackbox/test_cases_submesh01/Makefile.am @@ -0,0 +1,25 @@ +check_PROGRAMS = node_sim_corenode1 node_sim_corenode2 node_sim_app1node1 node_sim_app1node2 node_sim_app2node1 node_sim_app2node2 + +node_sim_corenode1_SOURCES = node_sim_corenode1.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_corenode1_LDADD = ../../../src/libmeshlink.la +node_sim_corenode1_CFLAGS = -D_GNU_SOURCE + +node_sim_corenode2_SOURCES = node_sim_corenode2.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_corenode2_LDADD = ../../../src/libmeshlink.la +node_sim_corenode2_CFLAGS = -D_GNU_SOURCE + +node_sim_app1node1_SOURCES = node_sim_app1node1.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_app1node1_LDADD = ../../../src/libmeshlink.la +node_sim_app1node1_CFLAGS = -D_GNU_SOURCE + +node_sim_app1node2_SOURCES = node_sim_app1node2.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_app1node2_LDADD = ../../../src/libmeshlink.la +node_sim_app1node2_CFLAGS = -D_GNU_SOURCE + +node_sim_app2node1_SOURCES = node_sim_app2node1.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_app2node1_LDADD = ../../../src/libmeshlink.la +node_sim_app2node1_CFLAGS = -D_GNU_SOURCE + +node_sim_app2node2_SOURCES = node_sim_app2node2.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_app2node2_LDADD = ../../../src/libmeshlink.la +node_sim_app2node2_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/test_cases_submesh01/node_sim_app1node1.c b/test/blackbox/test_cases_submesh01/node_sim_app1node1.c new file mode 100644 index 0000000..8c2655e --- /dev/null +++ b/test/blackbox/test_cases_submesh01/node_sim_app1node1.c @@ -0,0 +1,231 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +static int client_id = -1; +static meshlink_handle_t *mesh = NULL; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag start_test = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_data_recieved = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + fprintf(stderr, "\tapp1node1 got channel request from %s\n", channel->node->name); + + if(!strcmp(channel->node->name, "corenode1")) { + fprintf(stderr, "\tapp1node1 accepting channel request from %s at %lu\n", channel->node->name, time(NULL)); + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } else if(!strcmp(channel->node->name, "app1node2")) { + fprintf(stderr, "\tapp1node1 accepting channel request from %s at %lu\n", channel->node->name, time(NULL)); + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + fprintf(stderr, "\tapp1node1 rejecting channel request from %s at %lu\n", channel->node->name, time(NULL)); + return false; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + char data[100] = {0}; + char *message = "Channel Message"; + + if(len == 0) { + fprintf(stderr, "\tapp1node1 got error from %s at %lu\n", channel->node->name, time(NULL)); + send_event(ERR_NETWORK); + return; + } + + memcpy(data, dat, len); + + fprintf(stderr, "\tapp1node1 got message from %s as %s\n", channel->node->name, data); + + if(!strcmp(channel->node->name, "corenode1")) { + if(!memcmp(dat, "Channel Message", len)) { + set_sync_flag(&channel_data_recieved, true); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } else if(!strcmp(channel->node->name, "app1node2")) { + if(!memcmp(dat, "Channel Message", len)) { + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + char *message = "Channel Message"; + char *node = (char *)channel->node->name; + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + fprintf(stderr, "\tapp1node1's Channel request has been accepted by %s at : %lu\n", node, time(NULL)); + + if(0 == strcmp("corenode1", node)) { + set_sync_flag(&channel_opened, true); + } + + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + return; +} + + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcasecmp(node->name, "corenode1")) { + if(reachable) { + fprintf(stderr, "\tNode corenode1 became reachable\n"); + set_sync_flag(&peer_reachable, true); + } + } + + return; +} + +void mesh_start_test_handler(int signum) { + (void)signum; + + fprintf(stderr, "Starting test in app1node1\n"); + set_sync_flag(&start_test, true); +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 2, 0 }; + meshlink_channel_t *channel = NULL; + meshlink_node_t *core_node = NULL; + + fprintf(stderr, "\tMesh node 'app1node1' starting up........\n"); + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + signal(SIGIO, mesh_start_test_handler); + + // Run peer node instance + + mesh = meshlink_open("app1node1conf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + send_event(NODE_STARTED); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 15)); + send_event(NODE_JOINED); + + while(false == wait_sync_flag(&start_test, 10)); + + // Open a channel to peer node + core_node = meshlink_get_node(mesh, "corenode1"); + assert(core_node); + fprintf(stderr, "\tapp1node1 Sending Channel request to corenode1 at : %lu\n", time(NULL)); + channel = meshlink_channel_open(mesh, core_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + assert(wait_sync_flag(&channel_opened, 100)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 100)); + send_event(CHANNEL_DATA_RECIEVED); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_cases_submesh01/node_sim_app1node2.c b/test/blackbox/test_cases_submesh01/node_sim_app1node2.c new file mode 100644 index 0000000..051ebc3 --- /dev/null +++ b/test/blackbox/test_cases_submesh01/node_sim_app1node2.c @@ -0,0 +1,255 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +static int client_id = -1; +static meshlink_handle_t *mesh = NULL; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag start_test = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag app_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_data_recieved = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + fprintf(stderr, "\tapp1node2 got channel request from %s\n", channel->node->name); + + if(!strcmp(channel->node->name, "corenode1")) { + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + return false; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + + char data[100] = {0}; + + if(len == 0) { + fprintf(stderr, "\tapp1node2 got error from %s at %lu\n", channel->node->name, time(NULL)); + send_event(ERR_NETWORK); + return; + } + + memcpy(data, dat, len); + + fprintf(stderr, "\tapp1node2 got message from %s as %s\n", channel->node->name, data); + + if(!strcmp(channel->node->name, "corenode1")) { + if(!memcmp(dat, "Channel Message", len)) { + set_sync_flag(&channel_data_recieved, true); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } else if(!strcmp(channel->node->name, "app1node1")) { + if(!memcmp(dat, "Channel Message", len)) { + set_sync_flag(&channel_data_recieved, true); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } else { + assert(false); + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + char *message = "Channel Message"; + char *node = (char *)channel->node->name; + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + fprintf(stderr, "\tapp1node2's Channel request has been accepted by %s at : %lu\n", node, time(NULL)); + set_sync_flag(&channel_opened, true); + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + return; +} + + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcasecmp(node->name, "corenode1")) { + if(reachable) { + fprintf(stderr, "\tNode corenode1 became reachable\n"); + set_sync_flag(&peer_reachable, true); + } + } else if(!strcasecmp(node->name, "app1node1")) { + if(reachable) { + fprintf(stderr, "\tNode app1node1 became reachable\n"); + set_sync_flag(&app_reachable, true); + } + } + + return; +} + +void mesh_start_test_handler(int signum) { + (void)signum; + + fprintf(stderr, "Starting test in app1node2\n"); + set_sync_flag(&start_test, true); +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 2, 0 }; + meshlink_channel_t *channel = NULL; + meshlink_node_t *core_node = NULL; + + fprintf(stderr, "\tMesh node 'app1node2' starting up........\n"); + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + signal(SIGIO, mesh_start_test_handler); + + // Run peer node instance + + mesh = meshlink_open("app1node2conf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + send_event(NODE_STARTED); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 15)); + send_event(NODE_JOINED); + + while(false == wait_sync_flag(&start_test, 10)); + + // Open a channel to peer node + core_node = meshlink_get_node(mesh, "corenode1"); + assert(core_node); + fprintf(stderr, "\tapp1node2 Sending Channel request to corenode1 at : %lu\n", time(NULL)); + channel = meshlink_channel_open(mesh, core_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + assert(wait_sync_flag(&channel_opened, 15)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 30)); + send_event(CHANNEL_DATA_RECIEVED); + + // Open a channel to peer node + channel_opened.flag = false; + channel_data_recieved.flag = false; + + assert(wait_sync_flag(&app_reachable, 60)); + + core_node = meshlink_get_node(mesh, "app1node1"); + assert(core_node); + fprintf(stderr, "\tapp1node2 Sending Channel request to app1node1 at : %lu\n", time(NULL)); + channel = meshlink_channel_open(mesh, core_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + assert(wait_sync_flag(&channel_opened, 100)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 100)); + send_event(CHANNEL_DATA_RECIEVED); + + core_node = meshlink_get_node(mesh, "app2node1"); + + if(NULL != core_node) { + send_event(SIG_ABORT); + assert(false); + } + + send_event(MESH_EVENT_COMPLETED); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_cases_submesh01/node_sim_app2node1.c b/test/blackbox/test_cases_submesh01/node_sim_app2node1.c new file mode 100644 index 0000000..d50a10c --- /dev/null +++ b/test/blackbox/test_cases_submesh01/node_sim_app2node1.c @@ -0,0 +1,230 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +static int client_id = -1; +static meshlink_handle_t *mesh = NULL; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag start_test = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_data_recieved = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + fprintf(stderr, "\tapp2node1 got channel request from %s\n", channel->node->name); + + if(!strcmp(channel->node->name, "corenode1")) { + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } else if(!strcmp(channel->node->name, "app2node2")) { + fprintf(stderr, "\tapp2node1 accepting channel request from %s at %lu\n", channel->node->name, time(NULL)); + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + fprintf(stderr, "\tapp2node1 rejecting channel request from %s at %lu\n", channel->node->name, time(NULL)); + + return false; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + char *message = "Channel Message"; + char data[100] = {0}; + + if(len == 0) { + send_event(ERR_NETWORK); + return; + } + + memcpy(data, dat, len); + + fprintf(stderr, "\tapp2node1 got message from %s as %s\n", channel->node->name, data); + + if(!strcmp(channel->node->name, "corenode1")) { + if(!memcmp(dat, "Channel Message", len)) { + set_sync_flag(&channel_data_recieved, true); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } else if(!strcmp(channel->node->name, "app2node2")) { + if(!memcmp(dat, "Channel Message", len)) { + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + char *message = "Channel Message"; + char *node = (char *)channel->node->name; + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + fprintf(stderr, "\tapp2node1's Channel request has been accepted by corenode1 at : %lu\n", time(NULL)); + + if(0 == strcmp("corenode1", node)) { + set_sync_flag(&channel_opened, true); + } + + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + return; +} + + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcasecmp(node->name, "corenode1")) { + if(reachable) { + fprintf(stderr, "\tNode corenode1 became reachable\n"); + set_sync_flag(&peer_reachable, true); + } + } + + return; +} + +void mesh_start_test_handler(int signum) { + (void)signum; + + fprintf(stderr, "Starting test in app2node1\n"); + set_sync_flag(&start_test, true); +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 2, 0 }; + meshlink_channel_t *channel = NULL; + meshlink_node_t *core_node = NULL; + + fprintf(stderr, "\tMesh node 'app2node1' starting up........\n"); + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + signal(SIGIO, mesh_start_test_handler); + + // Run peer node instance + + mesh = meshlink_open("app2node1conf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + send_event(NODE_STARTED); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 15)); + send_event(NODE_JOINED); + + while(false == wait_sync_flag(&start_test, 10)); + + // Open a channel to peer node + core_node = meshlink_get_node(mesh, "corenode1"); + assert(core_node); + fprintf(stderr, "\tapp2node1 Sending Channel request to corenode1 at : %lu\n", time(NULL)); + channel = meshlink_channel_open(mesh, core_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + assert(wait_sync_flag(&channel_opened, 15)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 30)); + send_event(CHANNEL_DATA_RECIEVED); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_cases_submesh01/node_sim_app2node2.c b/test/blackbox/test_cases_submesh01/node_sim_app2node2.c new file mode 100644 index 0000000..a7a2d1d --- /dev/null +++ b/test/blackbox/test_cases_submesh01/node_sim_app2node2.c @@ -0,0 +1,255 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +static int client_id = -1; +static meshlink_handle_t *mesh = NULL; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag start_test = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag app_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_data_recieved = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + fprintf(stderr, "\tapp2node2 got channel request from %s\n", channel->node->name); + + if(!strcmp(channel->node->name, "corenode1")) { + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + return false; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + + char data[100] = {0}; + + if(len == 0) { + fprintf(stderr, "\tapp2node2 got error from %s at %lu\n", channel->node->name, time(NULL)); + send_event(ERR_NETWORK); + return; + } + + memcpy(data, dat, len); + + fprintf(stderr, "\tapp2node2 got message from %s as %s\n", channel->node->name, data); + + if(!strcmp(channel->node->name, "corenode1")) { + if(!memcmp(dat, "Channel Message", len)) { + set_sync_flag(&channel_data_recieved, true); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } else if(!strcmp(channel->node->name, "app2node1")) { + if(!memcmp(dat, "Channel Message", len)) { + set_sync_flag(&channel_data_recieved, true); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } else { + assert(false); + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + char *message = "Channel Message"; + char *node = (char *)channel->node->name; + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + fprintf(stderr, "\tapp2node2's Channel request has been accepted by %s at : %lu\n", node, time(NULL)); + set_sync_flag(&channel_opened, true); + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + return; +} + + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcasecmp(node->name, "corenode1")) { + if(reachable) { + fprintf(stderr, "\tNode corenode1 became reachable\n"); + set_sync_flag(&peer_reachable, true); + } + } else if(!strcasecmp(node->name, "app2node1")) { + if(reachable) { + fprintf(stderr, "\tNode app2node1 became reachable\n"); + set_sync_flag(&app_reachable, true); + } + } + + return; +} + +void mesh_start_test_handler(int signum) { + (void)signum; + + fprintf(stderr, "Starting test in app2node2\n"); + set_sync_flag(&start_test, true); +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 2, 0 }; + meshlink_channel_t *channel = NULL; + meshlink_node_t *core_node = NULL; + + fprintf(stderr, "\tMesh node 'app2node2' starting up........\n"); + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + signal(SIGIO, mesh_start_test_handler); + + // Run peer node instance + + mesh = meshlink_open("app2node2conf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + send_event(NODE_STARTED); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 15)); + send_event(NODE_JOINED); + + while(false == wait_sync_flag(&start_test, 10)); + + // Open a channel to peer node + core_node = meshlink_get_node(mesh, "corenode1"); + assert(core_node); + fprintf(stderr, "\tapp2node2 Sending Channel request to corenode1 at : %lu\n", time(NULL)); + channel = meshlink_channel_open(mesh, core_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + assert(wait_sync_flag(&channel_opened, 30)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 30)); + send_event(CHANNEL_DATA_RECIEVED); + + // Open a channel to peer node + channel_opened.flag = false; + channel_data_recieved.flag = false; + + assert(wait_sync_flag(&app_reachable, 60)); + + core_node = meshlink_get_node(mesh, "app2node1"); + assert(core_node); + fprintf(stderr, "\tapp2node2 Sending Channel request to app2node1 at : %lu\n", time(NULL)); + channel = meshlink_channel_open(mesh, core_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + assert(wait_sync_flag(&channel_opened, 30)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 30)); + send_event(CHANNEL_DATA_RECIEVED); + + core_node = meshlink_get_node(mesh, "app1node1"); + + if(NULL != core_node) { + send_event(SIG_ABORT); + assert(false); + } + + send_event(MESH_EVENT_COMPLETED); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_cases_submesh01/node_sim_corenode1.c b/test/blackbox/test_cases_submesh01/node_sim_corenode1.c new file mode 100644 index 0000000..8c8eb7c --- /dev/null +++ b/test/blackbox/test_cases_submesh01/node_sim_corenode1.c @@ -0,0 +1,190 @@ +/* + node_sim.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static int client_id = -1; + +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_data_recieved = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; + +static meshlink_handle_t *mesh = NULL; + +static void mesh_send_message_handler(const char *destination); + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + + char data[100] = {0}; + + if(len == 0) { + send_event(ERR_NETWORK); + return; + } + + memcpy(data, dat, len); + + fprintf(stderr, "corenode1 got message from %s as %s\n", channel->node->name, data); + + if(!memcmp(dat, "Channel Message", len)) { + mesh_send_message_handler(channel->node->name); + + if(0 == strcmp("corenode2", channel->node->name)) { + set_sync_flag(&channel_data_recieved, true); + } + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + + return; +} + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(reachable) { + fprintf(stderr, "Node %s became reachable\n", node->name); + } else { + fprintf(stderr, "Node %s is unreachable\n", node->name); + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + const char *message = "Channel Message"; + const char *node = channel->node->name; + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + fprintf(stderr, "corenode1's Channel request has been accepted by %s at : %lu\n", node, time(NULL)); + + if(0 == strcmp("corenode2", node)) { + set_sync_flag(&channel_opened, true); + } + + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + return; +} + +/* channel receive callback */ +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + fprintf(stderr, "corenode1 got channel request from %s\n", channel->node->name); + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + + return true; +} + +static void mesh_send_message_handler(const char *destination) { + meshlink_channel_t *channel = NULL; + meshlink_node_t *target_node = NULL; + + // Open a channel to destination node + target_node = meshlink_get_node(mesh, destination); + assert(target_node); + fprintf(stderr, "corenode1 Sending Channel request to %s at : %lu\n", destination, time(NULL)); + channel = meshlink_channel_open(mesh, target_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 5, 0 }; + + // Import mesh event handler + + fprintf(stderr, "Mesh node 'corenode1' starting up........\n"); + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + setup_signals(); + + // Execute test steps + + mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + send_event(NODE_STARTED); + + assert(wait_sync_flag(&channel_opened, 10)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 10)); + send_event(CHANNEL_DATA_RECIEVED); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return 0; +} diff --git a/test/blackbox/test_cases_submesh01/node_sim_corenode2.c b/test/blackbox/test_cases_submesh01/node_sim_corenode2.c new file mode 100644 index 0000000..d996272 --- /dev/null +++ b/test/blackbox/test_cases_submesh01/node_sim_corenode2.c @@ -0,0 +1,212 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +static int client_id = -1; +static meshlink_handle_t *mesh = NULL; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag start_test = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_data_recieved = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + fprintf(stderr, "corenode2 got channel request from %s", channel->node->name); + + if(!strcmp(channel->node->name, "corenode1")) { + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + return false; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + + char data[100] = {0}; + + if(len == 0) { + send_event(ERR_NETWORK); + return; + } + + memcpy(data, dat, len); + + fprintf(stderr, "corenode2 got message from %s as %s", channel->node->name, data); + + if(!strcmp(channel->node->name, "corenode1")) { + if(!memcmp(dat, "Channel Message", len)) { + set_sync_flag(&channel_data_recieved, true); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + char *message = "Channel Message"; + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + fprintf(stderr, "corenode2's Channel request has been accepted by corenode1 at : %lu", time(NULL)); + set_sync_flag(&channel_opened, true); + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + return; +} + + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcasecmp(node->name, "corenode1")) { + if(reachable) { + fprintf(stderr, "Node corenode2 became reachable"); + set_sync_flag(&peer_reachable, true); + } + } + + return; +} + +void mesh_start_test_handler(int signum) { + (void)signum; + + fprintf(stderr, "Starting test in corenode2\n"); + set_sync_flag(&start_test, true); +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 2, 0 }; + meshlink_channel_t *channel = NULL; + meshlink_node_t *core_node = NULL; + + fprintf(stderr, "Mesh node 'corenode2' starting up........\n"); + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + signal(SIGIO, mesh_start_test_handler); + + // Run peer node instance + + mesh = meshlink_open("corenode1conf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + send_event(NODE_STARTED); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 15)); + send_event(NODE_JOINED); + + while(false == wait_sync_flag(&start_test, 10)); + + // Open a channel to peer node + core_node = meshlink_get_node(mesh, "corenode1"); + assert(core_node); + fprintf(stderr, "corenode2 Sending Channel request to corenode1 at : %lu", time(NULL)); + channel = meshlink_channel_open(mesh, core_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + assert(wait_sync_flag(&channel_opened, 15)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 10)); + send_event(CHANNEL_DATA_RECIEVED); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_cases_submesh02/Makefile.am b/test/blackbox/test_cases_submesh02/Makefile.am new file mode 100644 index 0000000..bb0ff44 --- /dev/null +++ b/test/blackbox/test_cases_submesh02/Makefile.am @@ -0,0 +1,25 @@ +check_PROGRAMS = node_sim_corenode1 node_sim_corenode2 node_sim_app1node1 node_sim_app1node2 node_sim_app2node1 node_sim_app2node2 + +node_sim_corenode1_SOURCES = node_sim_corenode1.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_corenode1_LDADD = ../../../src/libmeshlink.la +node_sim_corenode1_CFLAGS = -D_GNU_SOURCE + +node_sim_corenode2_SOURCES = node_sim_corenode2.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_corenode2_LDADD = ../../../src/libmeshlink.la +node_sim_corenode2_CFLAGS = -D_GNU_SOURCE + +node_sim_app1node1_SOURCES = node_sim_app1node1.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_app1node1_LDADD = ../../../src/libmeshlink.la +node_sim_app1node1_CFLAGS = -D_GNU_SOURCE + +node_sim_app1node2_SOURCES = node_sim_app1node2.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_app1node2_LDADD = ../../../src/libmeshlink.la +node_sim_app1node2_CFLAGS = -D_GNU_SOURCE + +node_sim_app2node1_SOURCES = node_sim_app2node1.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_app2node1_LDADD = ../../../src/libmeshlink.la +node_sim_app2node1_CFLAGS = -D_GNU_SOURCE + +node_sim_app2node2_SOURCES = node_sim_app2node2.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_app2node2_LDADD = ../../../src/libmeshlink.la +node_sim_app2node2_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/test_cases_submesh02/node_sim_app1node1.c b/test/blackbox/test_cases_submesh02/node_sim_app1node1.c new file mode 100644 index 0000000..42ffe4f --- /dev/null +++ b/test/blackbox/test_cases_submesh02/node_sim_app1node1.c @@ -0,0 +1,231 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +static int client_id = -1; +static meshlink_handle_t *mesh = NULL; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag start_test = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_data_recieved = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + fprintf(stderr, "\tapp1node1 got channel request from %s\n", channel->node->name); + + if(!strcmp(channel->node->name, "corenode1")) { + fprintf(stderr, "\tapp1node1 accepting channel request from %s at %lu\n", channel->node->name, time(NULL)); + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } else if(!strcmp(channel->node->name, "app1node2")) { + fprintf(stderr, "\tapp1node1 accepting channel request from %s at %lu\n", channel->node->name, time(NULL)); + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + fprintf(stderr, "\tapp1node1 rejecting channel request from %s at %lu\n", channel->node->name, time(NULL)); + return false; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + char data[100] = {0}; + char *message = "Channel Message"; + + if(len == 0) { + fprintf(stderr, "\tapp1node1 got error from %s at %lu\n", channel->node->name, time(NULL)); + send_event(ERR_NETWORK); + return; + } + + memcpy(data, dat, len); + + fprintf(stderr, "\tapp1node1 got message from %s as %s\n", channel->node->name, data); + + if(!strcmp(channel->node->name, "corenode1")) { + if(!memcmp(dat, "Channel Message", len)) { + set_sync_flag(&channel_data_recieved, true); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } else if(!strcmp(channel->node->name, "app1node2")) { + if(!memcmp(dat, "Channel Message", len)) { + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + char *message = "Channel Message"; + char *node = (char *)channel->node->name; + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + fprintf(stderr, "\tapp1node1's Channel request has been accepted by %s at : %lu\n", node, time(NULL)); + + if(0 == strcmp("corenode1", node)) { + set_sync_flag(&channel_opened, true); + } + + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + return; +} + + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcasecmp(node->name, "corenode1")) { + if(reachable) { + fprintf(stderr, "\tNode corenode1 became reachable\n"); + set_sync_flag(&peer_reachable, true); + } + } + + return; +} + +void mesh_start_test_handler(int signum) { + (void)signum; + + fprintf(stderr, "Starting test in app1node1\n"); + set_sync_flag(&start_test, true); +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 2, 0 }; + meshlink_channel_t *channel = NULL; + meshlink_node_t *core_node = NULL; + + fprintf(stderr, "\tMesh node 'app1node1' starting up........\n"); + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + signal(SIGIO, mesh_start_test_handler); + + // Run peer node instance + + mesh = meshlink_open("app1node1conf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + send_event(NODE_STARTED); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 15)); + send_event(NODE_JOINED); + + while(false == wait_sync_flag(&start_test, 10)); + + // Open a channel to peer node + core_node = meshlink_get_node(mesh, "corenode1"); + assert(core_node); + fprintf(stderr, "\tapp1node1 Sending Channel request to corenode1 at : %lu\n", time(NULL)); + channel = meshlink_channel_open(mesh, core_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + assert(wait_sync_flag(&channel_opened, 15)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 30)); + send_event(CHANNEL_DATA_RECIEVED); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_cases_submesh02/node_sim_app1node2.c b/test/blackbox/test_cases_submesh02/node_sim_app1node2.c new file mode 100644 index 0000000..46a1010 --- /dev/null +++ b/test/blackbox/test_cases_submesh02/node_sim_app1node2.c @@ -0,0 +1,298 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +static int client_id = -1; +static meshlink_handle_t *mesh = NULL; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag start_test = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag app_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_data_recieved = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + fprintf(stderr, "\tapp1node2 got channel request from %s\n", channel->node->name); + + if(!strcmp(channel->node->name, "corenode1")) { + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + return false; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + + char data[100] = {0}; + + if(len == 0) { + fprintf(stderr, "\tapp1node2 got error from %s at %lu\n", channel->node->name, time(NULL)); + send_event(ERR_NETWORK); + return; + } + + memcpy(data, dat, len); + + fprintf(stderr, "\tapp1node2 got message from %s as %s\n", channel->node->name, data); + + if(!strcmp(channel->node->name, "corenode1")) { + if(!memcmp(dat, "Channel Message", len)) { + set_sync_flag(&channel_data_recieved, true); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } else if(!strcmp(channel->node->name, "app1node1")) { + if(!memcmp(dat, "Channel Message", len)) { + set_sync_flag(&channel_data_recieved, true); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } else { + assert(false); + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + char *message = "Channel Message"; + char *node = (char *)channel->node->name; + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + fprintf(stderr, "\tapp1node2's Channel request has been accepted by %s at : %lu\n", node, time(NULL)); + set_sync_flag(&channel_opened, true); + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + return; +} + + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcasecmp(node->name, "corenode1")) { + if(reachable) { + fprintf(stderr, "\tNode corenode1 became reachable\n"); + set_sync_flag(&peer_reachable, true); + } + } else if(!strcasecmp(node->name, "app1node1")) { + if(reachable) { + fprintf(stderr, "\tNode app1node1 became reachable\n"); + set_sync_flag(&app_reachable, true); + } + } + + return; +} + +void mesh_start_test_handler(int signum) { + (void)signum; + + fprintf(stderr, "Starting test in app1node2\n"); + set_sync_flag(&start_test, true); +} + +int main(int argc, char *argv[]) { + (void)argc; + + size_t num_nodes, i; + struct timeval main_loop_wait = { 2, 0 }; + meshlink_channel_t *channel = NULL; + meshlink_node_t *core_node = NULL; + meshlink_node_t **node_handles = NULL; + meshlink_submesh_t *submesh = NULL; + + fprintf(stderr, "\tMesh node 'app1node2' starting up........\n"); + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + signal(SIGIO, mesh_start_test_handler); + + // Run peer node instance + + mesh = meshlink_open("app1node2conf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + send_event(NODE_STARTED); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 15)); + send_event(NODE_JOINED); + + while(false == wait_sync_flag(&start_test, 10)); + + // Open a channel to peer node + core_node = meshlink_get_node(mesh, "corenode1"); + assert(core_node); + fprintf(stderr, "\tapp1node2 Sending Channel request to corenode1 at : %lu\n", time(NULL)); + channel = meshlink_channel_open(mesh, core_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + assert(wait_sync_flag(&channel_opened, 15)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 30)); + send_event(CHANNEL_DATA_RECIEVED); + + // Open a channel to peer node + channel_opened.flag = false; + channel_data_recieved.flag = false; + + assert(wait_sync_flag(&app_reachable, 60)); + + core_node = meshlink_get_node(mesh, "app1node1"); + assert(core_node); + fprintf(stderr, "\tapp1node2 Sending Channel request to app1node1 at : %lu\n", time(NULL)); + channel = meshlink_channel_open(mesh, core_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + assert(wait_sync_flag(&channel_opened, 30)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 30)); + send_event(CHANNEL_DATA_RECIEVED); + + num_nodes = 0; + node_handles = meshlink_get_all_nodes(mesh, NULL, &num_nodes); + fprintf(stderr, "\tGot %d nodes in list with error : %s\n", (int)num_nodes, meshlink_strerror(meshlink_errno)); + assert(node_handles); + assert((num_nodes == 4)); + + for(i = 0; i < num_nodes; i++) { + fprintf(stderr, "\tChecking the node : %s\n", node_handles[i]->name); + + if((0 == strcmp(node_handles[i]->name, "app2node1")) || (0 == strcmp(node_handles[i]->name, "app2node2"))) { + send_event(SIG_ABORT); + assert(false); + } + } + + meshlink_node_t *node = meshlink_get_self(mesh); + assert(node); + submesh = meshlink_get_node_submesh(mesh, node); + assert(submesh); + + node_handles = meshlink_get_all_nodes_by_submesh(mesh, submesh, node_handles, &num_nodes); + assert(node_handles); + assert((num_nodes == 2)); + + for(i = 0; i < num_nodes; i++) { + fprintf(stderr, "\tChecking the node : %s\n", node_handles[i]->name); + + if((0 == strcmp(node_handles[i]->name, "app2node1")) || (0 == strcmp(node_handles[i]->name, "app2node2"))) { + send_event(SIG_ABORT); + assert(false); + } + } + + submesh = meshlink_get_submesh(mesh, "app1"); + + if(submesh == NULL) { + fprintf(stderr, "\tapp1node2 Got invalid submesh handle\n"); + send_event(ERR_NETWORK); + } + + submesh = meshlink_get_submesh(mesh, "app2"); + + if(submesh != NULL) { + fprintf(stderr, "\tapp1node2 Submesh handle should be NULL\n"); + send_event(ERR_NETWORK); + } + + send_event(MESH_EVENT_COMPLETED); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_cases_submesh02/node_sim_app2node1.c b/test/blackbox/test_cases_submesh02/node_sim_app2node1.c new file mode 100644 index 0000000..d50a10c --- /dev/null +++ b/test/blackbox/test_cases_submesh02/node_sim_app2node1.c @@ -0,0 +1,230 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +static int client_id = -1; +static meshlink_handle_t *mesh = NULL; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag start_test = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_data_recieved = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + fprintf(stderr, "\tapp2node1 got channel request from %s\n", channel->node->name); + + if(!strcmp(channel->node->name, "corenode1")) { + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } else if(!strcmp(channel->node->name, "app2node2")) { + fprintf(stderr, "\tapp2node1 accepting channel request from %s at %lu\n", channel->node->name, time(NULL)); + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + fprintf(stderr, "\tapp2node1 rejecting channel request from %s at %lu\n", channel->node->name, time(NULL)); + + return false; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + char *message = "Channel Message"; + char data[100] = {0}; + + if(len == 0) { + send_event(ERR_NETWORK); + return; + } + + memcpy(data, dat, len); + + fprintf(stderr, "\tapp2node1 got message from %s as %s\n", channel->node->name, data); + + if(!strcmp(channel->node->name, "corenode1")) { + if(!memcmp(dat, "Channel Message", len)) { + set_sync_flag(&channel_data_recieved, true); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } else if(!strcmp(channel->node->name, "app2node2")) { + if(!memcmp(dat, "Channel Message", len)) { + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + char *message = "Channel Message"; + char *node = (char *)channel->node->name; + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + fprintf(stderr, "\tapp2node1's Channel request has been accepted by corenode1 at : %lu\n", time(NULL)); + + if(0 == strcmp("corenode1", node)) { + set_sync_flag(&channel_opened, true); + } + + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + return; +} + + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcasecmp(node->name, "corenode1")) { + if(reachable) { + fprintf(stderr, "\tNode corenode1 became reachable\n"); + set_sync_flag(&peer_reachable, true); + } + } + + return; +} + +void mesh_start_test_handler(int signum) { + (void)signum; + + fprintf(stderr, "Starting test in app2node1\n"); + set_sync_flag(&start_test, true); +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 2, 0 }; + meshlink_channel_t *channel = NULL; + meshlink_node_t *core_node = NULL; + + fprintf(stderr, "\tMesh node 'app2node1' starting up........\n"); + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + signal(SIGIO, mesh_start_test_handler); + + // Run peer node instance + + mesh = meshlink_open("app2node1conf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + send_event(NODE_STARTED); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 15)); + send_event(NODE_JOINED); + + while(false == wait_sync_flag(&start_test, 10)); + + // Open a channel to peer node + core_node = meshlink_get_node(mesh, "corenode1"); + assert(core_node); + fprintf(stderr, "\tapp2node1 Sending Channel request to corenode1 at : %lu\n", time(NULL)); + channel = meshlink_channel_open(mesh, core_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + assert(wait_sync_flag(&channel_opened, 15)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 30)); + send_event(CHANNEL_DATA_RECIEVED); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_cases_submesh02/node_sim_app2node2.c b/test/blackbox/test_cases_submesh02/node_sim_app2node2.c new file mode 100644 index 0000000..d29350f --- /dev/null +++ b/test/blackbox/test_cases_submesh02/node_sim_app2node2.c @@ -0,0 +1,301 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +static int client_id = -1; +static meshlink_handle_t *mesh = NULL; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag start_test = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag app_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_data_recieved = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + fprintf(stderr, "\tapp2node2 got channel request from %s\n", channel->node->name); + + if(!strcmp(channel->node->name, "corenode1")) { + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + return false; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + + char data[100] = {0}; + + if(len == 0) { + fprintf(stderr, "\tapp2node2 got error from %s at %lu\n", channel->node->name, time(NULL)); + send_event(ERR_NETWORK); + return; + } + + memcpy(data, dat, len); + + fprintf(stderr, "\tapp2node2 got message from %s as %s\n", channel->node->name, data); + + if(!strcmp(channel->node->name, "corenode1")) { + if(!memcmp(dat, "Channel Message", len)) { + set_sync_flag(&channel_data_recieved, true); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } else if(!strcmp(channel->node->name, "app2node1")) { + if(!memcmp(dat, "Channel Message", len)) { + set_sync_flag(&channel_data_recieved, true); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } else { + assert(false); + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + char *message = "Channel Message"; + char *node = (char *)channel->node->name; + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + fprintf(stderr, "\tapp2node2's Channel request has been accepted by %s at : %lu\n", node, time(NULL)); + set_sync_flag(&channel_opened, true); + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + return; +} + + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcasecmp(node->name, "corenode1")) { + if(reachable) { + fprintf(stderr, "\tNode corenode1 became reachable\n"); + set_sync_flag(&peer_reachable, true); + } + } else if(!strcasecmp(node->name, "app2node1")) { + if(reachable) { + fprintf(stderr, "\tNode app2node1 became reachable\n"); + set_sync_flag(&app_reachable, true); + } + } + + return; +} + +void mesh_start_test_handler(int signum) { + (void)signum; + + fprintf(stderr, "Starting test in app2node2\n"); + set_sync_flag(&start_test, true); +} + +int main(int argc, char *argv[]) { + (void)argc; + + size_t num_nodes, i; + struct timeval main_loop_wait = { 2, 0 }; + meshlink_channel_t *channel = NULL; + meshlink_node_t *core_node = NULL; + meshlink_node_t **node_handles = NULL; + meshlink_submesh_t *submesh = NULL; + + fprintf(stderr, "\tMesh node 'app2node2' starting up........\n"); + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + signal(SIGIO, mesh_start_test_handler); + + // Run peer node instance + + mesh = meshlink_open("app2node2conf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + send_event(NODE_STARTED); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 15)); + send_event(NODE_JOINED); + + while(false == wait_sync_flag(&start_test, 10)); + + // Open a channel to peer node + core_node = meshlink_get_node(mesh, "corenode1"); + assert(core_node); + fprintf(stderr, "\tapp2node2 Sending Channel request to corenode1 at : %lu\n", time(NULL)); + channel = meshlink_channel_open(mesh, core_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + assert(wait_sync_flag(&channel_opened, 30)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 30)); + send_event(CHANNEL_DATA_RECIEVED); + + // Open a channel to peer node + channel_opened.flag = false; + channel_data_recieved.flag = false; + + assert(wait_sync_flag(&app_reachable, 60)); + + core_node = meshlink_get_node(mesh, "app2node1"); + assert(core_node); + fprintf(stderr, "\tapp2node2 Sending Channel request to app2node1 at : %lu\n", time(NULL)); + channel = meshlink_channel_open(mesh, core_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + assert(wait_sync_flag(&channel_opened, 15)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 30)); + send_event(CHANNEL_DATA_RECIEVED); + + num_nodes = 0; + node_handles = meshlink_get_all_nodes(mesh, NULL, &num_nodes); + fprintf(stderr, "\tGot %d nodes in list with error : %s\n", (int)num_nodes, meshlink_strerror(meshlink_errno)); + assert(node_handles); + assert((num_nodes == 4)); + + for(i = 0; i < num_nodes; i++) { + fprintf(stderr, "\tChecking the node : %s\n", node_handles[i]->name); + + if(0 == strcmp(node_handles[i]->name, "app1node1")) { + send_event(SIG_ABORT); + assert(false); + } else if(0 == strcmp(node_handles[i]->name, "app1node2")) { + send_event(SIG_ABORT); + assert(false); + } + } + + meshlink_node_t *node = meshlink_get_self(mesh); + assert(node); + submesh = meshlink_get_node_submesh(mesh, node); + assert(submesh); + + node_handles = meshlink_get_all_nodes_by_submesh(mesh, submesh, node_handles, &num_nodes); + assert(node_handles); + assert((num_nodes == 2)); + + for(i = 0; i < num_nodes; i++) { + fprintf(stderr, "\tChecking the node : %s\n", node_handles[i]->name); + + if((0 == strcmp(node_handles[i]->name, "app1node1")) || (0 == strcmp(node_handles[i]->name, "app1node2"))) { + send_event(SIG_ABORT); + assert(false); + } + } + + submesh = meshlink_get_submesh(mesh, "app2"); + + if(submesh == NULL) { + fprintf(stderr, "\tapp2node2 Got invalid submesh handle\n"); + send_event(ERR_NETWORK); + } + + submesh = meshlink_get_submesh(mesh, "app1"); + + if(submesh != NULL) { + fprintf(stderr, "\tapp2node2 Submesh handle should be NULL\n"); + send_event(ERR_NETWORK); + } + + send_event(MESH_EVENT_COMPLETED); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_cases_submesh02/node_sim_corenode1.c b/test/blackbox/test_cases_submesh02/node_sim_corenode1.c new file mode 100644 index 0000000..8c8eb7c --- /dev/null +++ b/test/blackbox/test_cases_submesh02/node_sim_corenode1.c @@ -0,0 +1,190 @@ +/* + node_sim.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static int client_id = -1; + +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_data_recieved = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; + +static meshlink_handle_t *mesh = NULL; + +static void mesh_send_message_handler(const char *destination); + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + + char data[100] = {0}; + + if(len == 0) { + send_event(ERR_NETWORK); + return; + } + + memcpy(data, dat, len); + + fprintf(stderr, "corenode1 got message from %s as %s\n", channel->node->name, data); + + if(!memcmp(dat, "Channel Message", len)) { + mesh_send_message_handler(channel->node->name); + + if(0 == strcmp("corenode2", channel->node->name)) { + set_sync_flag(&channel_data_recieved, true); + } + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + + return; +} + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(reachable) { + fprintf(stderr, "Node %s became reachable\n", node->name); + } else { + fprintf(stderr, "Node %s is unreachable\n", node->name); + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + const char *message = "Channel Message"; + const char *node = channel->node->name; + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + fprintf(stderr, "corenode1's Channel request has been accepted by %s at : %lu\n", node, time(NULL)); + + if(0 == strcmp("corenode2", node)) { + set_sync_flag(&channel_opened, true); + } + + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + return; +} + +/* channel receive callback */ +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + fprintf(stderr, "corenode1 got channel request from %s\n", channel->node->name); + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + + return true; +} + +static void mesh_send_message_handler(const char *destination) { + meshlink_channel_t *channel = NULL; + meshlink_node_t *target_node = NULL; + + // Open a channel to destination node + target_node = meshlink_get_node(mesh, destination); + assert(target_node); + fprintf(stderr, "corenode1 Sending Channel request to %s at : %lu\n", destination, time(NULL)); + channel = meshlink_channel_open(mesh, target_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 5, 0 }; + + // Import mesh event handler + + fprintf(stderr, "Mesh node 'corenode1' starting up........\n"); + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + setup_signals(); + + // Execute test steps + + mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + send_event(NODE_STARTED); + + assert(wait_sync_flag(&channel_opened, 10)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 10)); + send_event(CHANNEL_DATA_RECIEVED); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return 0; +} diff --git a/test/blackbox/test_cases_submesh02/node_sim_corenode2.c b/test/blackbox/test_cases_submesh02/node_sim_corenode2.c new file mode 100644 index 0000000..d996272 --- /dev/null +++ b/test/blackbox/test_cases_submesh02/node_sim_corenode2.c @@ -0,0 +1,212 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +static int client_id = -1; +static meshlink_handle_t *mesh = NULL; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag start_test = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_data_recieved = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + fprintf(stderr, "corenode2 got channel request from %s", channel->node->name); + + if(!strcmp(channel->node->name, "corenode1")) { + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + return false; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + + char data[100] = {0}; + + if(len == 0) { + send_event(ERR_NETWORK); + return; + } + + memcpy(data, dat, len); + + fprintf(stderr, "corenode2 got message from %s as %s", channel->node->name, data); + + if(!strcmp(channel->node->name, "corenode1")) { + if(!memcmp(dat, "Channel Message", len)) { + set_sync_flag(&channel_data_recieved, true); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + char *message = "Channel Message"; + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + fprintf(stderr, "corenode2's Channel request has been accepted by corenode1 at : %lu", time(NULL)); + set_sync_flag(&channel_opened, true); + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + return; +} + + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcasecmp(node->name, "corenode1")) { + if(reachable) { + fprintf(stderr, "Node corenode2 became reachable"); + set_sync_flag(&peer_reachable, true); + } + } + + return; +} + +void mesh_start_test_handler(int signum) { + (void)signum; + + fprintf(stderr, "Starting test in corenode2\n"); + set_sync_flag(&start_test, true); +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 2, 0 }; + meshlink_channel_t *channel = NULL; + meshlink_node_t *core_node = NULL; + + fprintf(stderr, "Mesh node 'corenode2' starting up........\n"); + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + signal(SIGIO, mesh_start_test_handler); + + // Run peer node instance + + mesh = meshlink_open("corenode1conf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + send_event(NODE_STARTED); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 15)); + send_event(NODE_JOINED); + + while(false == wait_sync_flag(&start_test, 10)); + + // Open a channel to peer node + core_node = meshlink_get_node(mesh, "corenode1"); + assert(core_node); + fprintf(stderr, "corenode2 Sending Channel request to corenode1 at : %lu", time(NULL)); + channel = meshlink_channel_open(mesh, core_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + assert(wait_sync_flag(&channel_opened, 15)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 10)); + send_event(CHANNEL_DATA_RECIEVED); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_cases_submesh03/Makefile.am b/test/blackbox/test_cases_submesh03/Makefile.am new file mode 100644 index 0000000..7a7004a --- /dev/null +++ b/test/blackbox/test_cases_submesh03/Makefile.am @@ -0,0 +1,13 @@ +check_PROGRAMS = node_sim_corenode1 node_sim_app1node1 node_sim_app1node2 + +node_sim_corenode1_SOURCES = node_sim_corenode1.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_corenode1_LDADD = ../../../src/libmeshlink.la +node_sim_corenode1_CFLAGS = -D_GNU_SOURCE + +node_sim_app1node1_SOURCES = node_sim_app1node1.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_app1node1_LDADD = ../../../src/libmeshlink.la +node_sim_app1node1_CFLAGS = -D_GNU_SOURCE + +node_sim_app1node2_SOURCES = node_sim_app1node2.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_app1node2_LDADD = ../../../src/libmeshlink.la +node_sim_app1node2_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/test_cases_submesh03/node_sim_app1node1.c b/test/blackbox/test_cases_submesh03/node_sim_app1node1.c new file mode 100644 index 0000000..42ffe4f --- /dev/null +++ b/test/blackbox/test_cases_submesh03/node_sim_app1node1.c @@ -0,0 +1,231 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +static int client_id = -1; +static meshlink_handle_t *mesh = NULL; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag start_test = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_data_recieved = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + fprintf(stderr, "\tapp1node1 got channel request from %s\n", channel->node->name); + + if(!strcmp(channel->node->name, "corenode1")) { + fprintf(stderr, "\tapp1node1 accepting channel request from %s at %lu\n", channel->node->name, time(NULL)); + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } else if(!strcmp(channel->node->name, "app1node2")) { + fprintf(stderr, "\tapp1node1 accepting channel request from %s at %lu\n", channel->node->name, time(NULL)); + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + fprintf(stderr, "\tapp1node1 rejecting channel request from %s at %lu\n", channel->node->name, time(NULL)); + return false; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + char data[100] = {0}; + char *message = "Channel Message"; + + if(len == 0) { + fprintf(stderr, "\tapp1node1 got error from %s at %lu\n", channel->node->name, time(NULL)); + send_event(ERR_NETWORK); + return; + } + + memcpy(data, dat, len); + + fprintf(stderr, "\tapp1node1 got message from %s as %s\n", channel->node->name, data); + + if(!strcmp(channel->node->name, "corenode1")) { + if(!memcmp(dat, "Channel Message", len)) { + set_sync_flag(&channel_data_recieved, true); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } else if(!strcmp(channel->node->name, "app1node2")) { + if(!memcmp(dat, "Channel Message", len)) { + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + char *message = "Channel Message"; + char *node = (char *)channel->node->name; + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + fprintf(stderr, "\tapp1node1's Channel request has been accepted by %s at : %lu\n", node, time(NULL)); + + if(0 == strcmp("corenode1", node)) { + set_sync_flag(&channel_opened, true); + } + + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + return; +} + + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcasecmp(node->name, "corenode1")) { + if(reachable) { + fprintf(stderr, "\tNode corenode1 became reachable\n"); + set_sync_flag(&peer_reachable, true); + } + } + + return; +} + +void mesh_start_test_handler(int signum) { + (void)signum; + + fprintf(stderr, "Starting test in app1node1\n"); + set_sync_flag(&start_test, true); +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 2, 0 }; + meshlink_channel_t *channel = NULL; + meshlink_node_t *core_node = NULL; + + fprintf(stderr, "\tMesh node 'app1node1' starting up........\n"); + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + signal(SIGIO, mesh_start_test_handler); + + // Run peer node instance + + mesh = meshlink_open("app1node1conf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + send_event(NODE_STARTED); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 15)); + send_event(NODE_JOINED); + + while(false == wait_sync_flag(&start_test, 10)); + + // Open a channel to peer node + core_node = meshlink_get_node(mesh, "corenode1"); + assert(core_node); + fprintf(stderr, "\tapp1node1 Sending Channel request to corenode1 at : %lu\n", time(NULL)); + channel = meshlink_channel_open(mesh, core_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + assert(wait_sync_flag(&channel_opened, 15)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 30)); + send_event(CHANNEL_DATA_RECIEVED); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_cases_submesh03/node_sim_app1node2.c b/test/blackbox/test_cases_submesh03/node_sim_app1node2.c new file mode 100644 index 0000000..65fb80b --- /dev/null +++ b/test/blackbox/test_cases_submesh03/node_sim_app1node2.c @@ -0,0 +1,267 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +static int client_id = -1; +static meshlink_handle_t *mesh = NULL; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag start_test = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag app_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_data_recieved = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + fprintf(stderr, "\tapp1node2 got channel request from %s\n", channel->node->name); + + if(!strcmp(channel->node->name, "corenode1")) { + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + return false; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + + char data[100] = {0}; + + if(len == 0) { + fprintf(stderr, "\tapp1node2 got error from %s at %lu\n", channel->node->name, time(NULL)); + send_event(ERR_NETWORK); + return; + } + + memcpy(data, dat, len); + + fprintf(stderr, "\tapp1node2 got message from %s as %s\n", channel->node->name, data); + + if(!strcmp(channel->node->name, "corenode1")) { + if(!memcmp(dat, "Channel Message", len)) { + set_sync_flag(&channel_data_recieved, true); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } else if(!strcmp(channel->node->name, "app1node1")) { + if(!memcmp(dat, "Channel Message", len)) { + set_sync_flag(&channel_data_recieved, true); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } else { + assert(false); + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + char *message = "Channel Message"; + char *node = (char *)channel->node->name; + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + fprintf(stderr, "\tapp1node2's Channel request has been accepted by %s at : %lu\n", node, time(NULL)); + set_sync_flag(&channel_opened, true); + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + return; +} + + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcasecmp(node->name, "corenode1")) { + if(reachable) { + fprintf(stderr, "\tNode corenode1 became reachable\n"); + set_sync_flag(&peer_reachable, true); + } + } else if(!strcasecmp(node->name, "app1node1")) { + if(reachable) { + fprintf(stderr, "\tNode app1node1 became reachable\n"); + set_sync_flag(&app_reachable, true); + } + } + + return; +} + +void mesh_start_test_handler(int signum) { + (void)signum; + + fprintf(stderr, "Starting test in app1node2\n"); + set_sync_flag(&start_test, true); +} + +int main(int argc, char *argv[]) { + (void)argc; + + size_t num_nodes, i; + struct timeval main_loop_wait = { 2, 0 }; + meshlink_channel_t *channel = NULL; + meshlink_node_t *core_node = NULL; + meshlink_node_t **node_handles = NULL; + + fprintf(stderr, "\tMesh node 'app1node2' starting up........\n"); + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + signal(SIGIO, mesh_start_test_handler); + + // Run peer node instance + + mesh = meshlink_open("app1node2conf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + send_event(NODE_STARTED); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 15)); + send_event(NODE_JOINED); + + while(false == wait_sync_flag(&start_test, 10)); + + // Open a channel to peer node + core_node = meshlink_get_node(mesh, "corenode1"); + assert(core_node); + fprintf(stderr, "\tapp1node2 Sending Channel request to corenode1 at : %lu\n", time(NULL)); + channel = meshlink_channel_open(mesh, core_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + assert(wait_sync_flag(&channel_opened, 15)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 30)); + send_event(CHANNEL_DATA_RECIEVED); + + // Open a channel to peer node + channel_opened.flag = false; + channel_data_recieved.flag = false; + + assert(wait_sync_flag(&app_reachable, 60)); + + core_node = meshlink_get_node(mesh, "app1node1"); + assert(core_node); + fprintf(stderr, "\tapp1node2 Sending Channel request to app1node1 at : %lu\n", time(NULL)); + channel = meshlink_channel_open(mesh, core_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + assert(wait_sync_flag(&channel_opened, 15)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 30)); + send_event(CHANNEL_DATA_RECIEVED); + + num_nodes = 0; + node_handles = meshlink_get_all_nodes(mesh, NULL, &num_nodes); + fprintf(stderr, "\tGot %lu nodes in list with error : %s\n", num_nodes, meshlink_strerror(meshlink_errno)); + assert(node_handles); + + for(i = 0; i < num_nodes; i++) { + fprintf(stderr, "\tChecking the node : %s\n", node_handles[i]->name); + + if(0 == strcmp(node_handles[i]->name, "app2node1")) { + send_event(SIG_ABORT); + assert(false); + } else if(0 == strcmp(node_handles[i]->name, "app2node2")) { + send_event(SIG_ABORT); + assert(false); + } + } + + send_event(MESH_EVENT_COMPLETED); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_cases_submesh03/node_sim_corenode1.c b/test/blackbox/test_cases_submesh03/node_sim_corenode1.c new file mode 100644 index 0000000..6d6c94d --- /dev/null +++ b/test/blackbox/test_cases_submesh03/node_sim_corenode1.c @@ -0,0 +1,190 @@ +/* + node_sim.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static int client_id = -1; + +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_data_recieved = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; + +static meshlink_handle_t *mesh = NULL; + +static void mesh_send_message_handler(const char *destination); + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + + char data[100] = {0}; + + if(len == 0) { + send_event(ERR_NETWORK); + return; + } + + memcpy(data, dat, len); + + fprintf(stderr, "corenode1 got message from %s as %s\n", channel->node->name, data); + + if(!memcmp(dat, "Channel Message", len)) { + mesh_send_message_handler(channel->node->name); + + if(0 == strcmp("app1node1", channel->node->name)) { + set_sync_flag(&channel_data_recieved, true); + } + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + + return; +} + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(reachable) { + fprintf(stderr, "Node %s became reachable\n", node->name); + } else { + fprintf(stderr, "Node %s is unreachable\n", node->name); + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + const char *message = "Channel Message"; + const char *node = channel->node->name; + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + fprintf(stderr, "corenode1's Channel request has been accepted by %s at : %lu\n", node, time(NULL)); + + if(0 == strcmp("app1node1", node)) { + set_sync_flag(&channel_opened, true); + } + + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + return; +} + +/* channel receive callback */ +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + fprintf(stderr, "corenode1 got channel request from %s\n", channel->node->name); + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + + return true; +} + +static void mesh_send_message_handler(const char *destination) { + meshlink_channel_t *channel = NULL; + meshlink_node_t *target_node = NULL; + + // Open a channel to destination node + target_node = meshlink_get_node(mesh, destination); + assert(target_node); + fprintf(stderr, "corenode1 Sending Channel request to %s at : %lu\n", destination, time(NULL)); + channel = meshlink_channel_open(mesh, target_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 5, 0 }; + + // Import mesh event handler + + fprintf(stderr, "Mesh node 'corenode1' starting up........\n"); + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + setup_signals(); + + // Execute test steps + + mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + send_event(NODE_STARTED); + + assert(wait_sync_flag(&channel_opened, 10)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 10)); + send_event(CHANNEL_DATA_RECIEVED); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return 0; +} diff --git a/test/blackbox/test_cases_submesh04/Makefile.am b/test/blackbox/test_cases_submesh04/Makefile.am new file mode 100644 index 0000000..7a7004a --- /dev/null +++ b/test/blackbox/test_cases_submesh04/Makefile.am @@ -0,0 +1,13 @@ +check_PROGRAMS = node_sim_corenode1 node_sim_app1node1 node_sim_app1node2 + +node_sim_corenode1_SOURCES = node_sim_corenode1.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_corenode1_LDADD = ../../../src/libmeshlink.la +node_sim_corenode1_CFLAGS = -D_GNU_SOURCE + +node_sim_app1node1_SOURCES = node_sim_app1node1.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_app1node1_LDADD = ../../../src/libmeshlink.la +node_sim_app1node1_CFLAGS = -D_GNU_SOURCE + +node_sim_app1node2_SOURCES = node_sim_app1node2.c ../common/common_handlers.c ../common/test_step.c ../common/mesh_event_handler.c ../../utils.c +node_sim_app1node2_LDADD = ../../../src/libmeshlink.la +node_sim_app1node2_CFLAGS = -D_GNU_SOURCE diff --git a/test/blackbox/test_cases_submesh04/node_sim_app1node1.c b/test/blackbox/test_cases_submesh04/node_sim_app1node1.c new file mode 100644 index 0000000..42ffe4f --- /dev/null +++ b/test/blackbox/test_cases_submesh04/node_sim_app1node1.c @@ -0,0 +1,231 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +static int client_id = -1; +static meshlink_handle_t *mesh = NULL; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag start_test = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_data_recieved = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + fprintf(stderr, "\tapp1node1 got channel request from %s\n", channel->node->name); + + if(!strcmp(channel->node->name, "corenode1")) { + fprintf(stderr, "\tapp1node1 accepting channel request from %s at %lu\n", channel->node->name, time(NULL)); + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } else if(!strcmp(channel->node->name, "app1node2")) { + fprintf(stderr, "\tapp1node1 accepting channel request from %s at %lu\n", channel->node->name, time(NULL)); + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + fprintf(stderr, "\tapp1node1 rejecting channel request from %s at %lu\n", channel->node->name, time(NULL)); + return false; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + char data[100] = {0}; + char *message = "Channel Message"; + + if(len == 0) { + fprintf(stderr, "\tapp1node1 got error from %s at %lu\n", channel->node->name, time(NULL)); + send_event(ERR_NETWORK); + return; + } + + memcpy(data, dat, len); + + fprintf(stderr, "\tapp1node1 got message from %s as %s\n", channel->node->name, data); + + if(!strcmp(channel->node->name, "corenode1")) { + if(!memcmp(dat, "Channel Message", len)) { + set_sync_flag(&channel_data_recieved, true); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } else if(!strcmp(channel->node->name, "app1node2")) { + if(!memcmp(dat, "Channel Message", len)) { + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + char *message = "Channel Message"; + char *node = (char *)channel->node->name; + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + fprintf(stderr, "\tapp1node1's Channel request has been accepted by %s at : %lu\n", node, time(NULL)); + + if(0 == strcmp("corenode1", node)) { + set_sync_flag(&channel_opened, true); + } + + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + return; +} + + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcasecmp(node->name, "corenode1")) { + if(reachable) { + fprintf(stderr, "\tNode corenode1 became reachable\n"); + set_sync_flag(&peer_reachable, true); + } + } + + return; +} + +void mesh_start_test_handler(int signum) { + (void)signum; + + fprintf(stderr, "Starting test in app1node1\n"); + set_sync_flag(&start_test, true); +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 2, 0 }; + meshlink_channel_t *channel = NULL; + meshlink_node_t *core_node = NULL; + + fprintf(stderr, "\tMesh node 'app1node1' starting up........\n"); + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + signal(SIGIO, mesh_start_test_handler); + + // Run peer node instance + + mesh = meshlink_open("app1node1conf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + send_event(NODE_STARTED); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 15)); + send_event(NODE_JOINED); + + while(false == wait_sync_flag(&start_test, 10)); + + // Open a channel to peer node + core_node = meshlink_get_node(mesh, "corenode1"); + assert(core_node); + fprintf(stderr, "\tapp1node1 Sending Channel request to corenode1 at : %lu\n", time(NULL)); + channel = meshlink_channel_open(mesh, core_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + assert(wait_sync_flag(&channel_opened, 15)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 30)); + send_event(CHANNEL_DATA_RECIEVED); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_cases_submesh04/node_sim_app1node2.c b/test/blackbox/test_cases_submesh04/node_sim_app1node2.c new file mode 100644 index 0000000..8e47d7e --- /dev/null +++ b/test/blackbox/test_cases_submesh04/node_sim_app1node2.c @@ -0,0 +1,299 @@ +/* + node_sim_peer.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len); +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len); + +static int client_id = -1; +static meshlink_handle_t *mesh = NULL; + +static struct sync_flag peer_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag start_test = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag app_reachable = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_data_recieved = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static meshlink_channel_t *ch_app1node1 = NULL; + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + fprintf(stderr, "\tapp1node2 got channel request from %s\n", channel->node->name); + + if(!strcmp(channel->node->name, "corenode1")) { + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + mesh->priv = channel; + + return true; + } + + return false; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + + char data[100] = {0}; + + if(len == 0) { + fprintf(stderr, "\tapp1node2 got error from %s at %lu\n", channel->node->name, time(NULL)); + send_event(ERR_NETWORK); + return; + } + + memcpy(data, dat, len); + + fprintf(stderr, "\tapp1node2 got message from %s as %s\n", channel->node->name, data); + + if(!strcmp(channel->node->name, "corenode1")) { + if(!memcmp(dat, "Channel Message", len)) { + set_sync_flag(&channel_data_recieved, true); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } else if(!strcmp(channel->node->name, "app1node1")) { + if(!memcmp(dat, "Channel Message", len)) { + ch_app1node1 = channel; + set_sync_flag(&channel_data_recieved, true); + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + } else { + assert(false); + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + char *message = "Channel Message"; + char *node = (char *)channel->node->name; + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + fprintf(stderr, "\tapp1node2's Channel request has been accepted by %s at : %lu\n", node, time(NULL)); + set_sync_flag(&channel_opened, true); + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + return; +} + + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcasecmp(node->name, "corenode1")) { + if(reachable) { + fprintf(stderr, "\tNode corenode1 became reachable\n"); + set_sync_flag(&peer_reachable, true); + } + } else if(!strcasecmp(node->name, "app1node1")) { + if(reachable) { + fprintf(stderr, "\tNode app1node1 became reachable\n"); + set_sync_flag(&app_reachable, true); + } + } + + return; +} + +void mesh_start_test_handler(int signum) { + (void)signum; + + fprintf(stderr, "Starting test in app1node2\n"); + set_sync_flag(&start_test, true); +} + +int main(int argc, char *argv[]) { + (void)argc; + + size_t num_nodes, i; + struct timeval main_loop_wait = { 2, 0 }; + meshlink_channel_t *channel = NULL; + meshlink_node_t *core_node = NULL; + meshlink_node_t **node_handles = NULL; + + fprintf(stderr, "\tMesh node 'app1node2' starting up........\n"); + + // Import mesh event handler + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + // Setup required signals + + setup_signals(); + signal(SIGIO, mesh_start_test_handler); + + // Run peer node instance + + mesh = meshlink_open("app1node2conf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + send_event(NODE_STARTED); + + // Wait for peer node to join + + assert(wait_sync_flag(&peer_reachable, 15)); + send_event(NODE_JOINED); + + while(false == wait_sync_flag(&start_test, 10)); + + // Open a channel to peer node + core_node = meshlink_get_node(mesh, "corenode1"); + assert(core_node); + fprintf(stderr, "\tapp1node2 Sending Channel request to corenode1 at : %lu\n", time(NULL)); + channel = meshlink_channel_open(mesh, core_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + assert(wait_sync_flag(&channel_opened, 15)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 30)); + send_event(CHANNEL_DATA_RECIEVED); + + // Open a channel to peer node + channel_opened.flag = false; + channel_data_recieved.flag = false; + + assert(wait_sync_flag(&app_reachable, 60)); + + core_node = meshlink_get_node(mesh, "app1node1"); + assert(core_node); + fprintf(stderr, "\tapp1node2 Sending Channel request to app1node1 at : %lu\n", time(NULL)); + channel = meshlink_channel_open(mesh, core_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + assert(wait_sync_flag(&channel_opened, 15)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 30)); + send_event(CHANNEL_DATA_RECIEVED); + + num_nodes = 0; + node_handles = meshlink_get_all_nodes(mesh, NULL, &num_nodes); + fprintf(stderr, "\tGot %lu nodes in list with error : %s\n", num_nodes, meshlink_strerror(meshlink_errno)); + assert(node_handles); + + for(i = 0; i < num_nodes; i++) { + fprintf(stderr, "\tChecking the node : %s\n", node_handles[i]->name); + + if(0 == strcmp(node_handles[i]->name, "app2node1")) { + send_event(SIG_ABORT); + assert(false); + } else if(0 == strcmp(node_handles[i]->name, "app2node2")) { + send_event(SIG_ABORT); + assert(false); + } + } + + meshlink_node_t *app1_node1 = meshlink_get_node(mesh, "app1node1"); + + if(!app1_node1) { + send_event(SIG_ABORT); + assert(app1_node1); + } + + channel_data_recieved.flag = false; + assert(meshlink_blacklist(mesh, app1_node1)); + + sleep(2); + + assert(meshlink_channel_send(mesh, ch_app1node1, "test", 5) == 5); + + wait_sync_flag(&channel_data_recieved, 30); + + if(true == channel_data_recieved.flag) { + send_event(SIG_ABORT); + assert(false); + } + + channel_data_recieved.flag = false; + assert(meshlink_whitelist(mesh, app1_node1)); + + sleep(2); + + assert(meshlink_channel_send(mesh, ch_app1node1, "Channel Message", strlen("Channel Message")) == strlen("Channel Message")); + + assert(wait_sync_flag(&channel_data_recieved, 60)); + + send_event(MESH_EVENT_COMPLETED); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/test_cases_submesh04/node_sim_corenode1.c b/test/blackbox/test_cases_submesh04/node_sim_corenode1.c new file mode 100644 index 0000000..a2ff3ce --- /dev/null +++ b/test/blackbox/test_cases_submesh04/node_sim_corenode1.c @@ -0,0 +1,190 @@ +/* + node_sim.c -- Implementation of Node Simulation for Meshlink Testing + for meta connection test case 01 - re-connection of + two nodes when relay node goes down + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include +#include +#include +#include "../common/common_handlers.h" +#include "../common/test_step.h" +#include "../common/mesh_event_handler.h" +#include "../../utils.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_DEVCLASS 2 +#define CMD_LINE_ARG_CLIENTID 3 +#define CMD_LINE_ARG_IMPORTSTR 4 +#define CMD_LINE_ARG_INVITEURL 5 +#define CHANNEL_PORT 1234 + +static int client_id = -1; + +static struct sync_flag channel_opened = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; +static struct sync_flag channel_data_recieved = {.mutex = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .flag = false}; + +static meshlink_handle_t *mesh = NULL; + +static void mesh_send_message_handler(const char *destination); + +static void send_event(mesh_event_t event) { + int attempts; + + for(attempts = 0; attempts < 5; attempts += 1) { + if(mesh_event_sock_send(client_id, event, NULL, 0)) { + break; + } + } + + assert(attempts < 5); + + return; +} + +/* channel receive callback */ +static void channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *dat, size_t len) { + (void)mesh; + + char data[100] = {0}; + + if(len == 0) { + send_event(ERR_NETWORK); + return; + } + + memcpy(data, dat, len); + + fprintf(stderr, "corenode1 got message from %s as %s\n", channel->node->name, data); + + if(!memcmp(dat, "Channel Message", len)) { + mesh_send_message_handler(channel->node->name); + + if(0 == strcmp("app1node2", channel->node->name)) { + set_sync_flag(&channel_data_recieved, true); + } + } else if(!memcmp(dat, "failure", 7)) { + assert(false); + } + + return; +} + +static void node_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(reachable) { + fprintf(stderr, "Node %s became reachable\n", node->name); + } else { + fprintf(stderr, "Node %s is unreachable\n", node->name); + } + + return; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + const char *message = "Channel Message"; + const char *node = channel->node->name; + (void)len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + fprintf(stderr, "corenode1's Channel request has been accepted by %s at : %lu\n", node, time(NULL)); + + if(0 == strcmp("app1node2", node)) { + set_sync_flag(&channel_opened, true); + } + + assert(meshlink_channel_send(mesh, channel, message, strlen(message)) >= 0); + return; +} + +/* channel receive callback */ +static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *dat, size_t len) { + (void)dat; + (void)len; + + assert(port == CHANNEL_PORT); + + fprintf(stderr, "corenode1 got channel request from %s\n", channel->node->name); + meshlink_set_channel_receive_cb(mesh, channel, channel_receive_cb); + + return true; +} + +static void mesh_send_message_handler(const char *destination) { + meshlink_channel_t *channel = NULL; + meshlink_node_t *target_node = NULL; + + // Open a channel to destination node + target_node = meshlink_get_node(mesh, destination); + assert(target_node); + fprintf(stderr, "corenode1 Sending Channel request to %s at : %lu\n", destination, time(NULL)); + channel = meshlink_channel_open(mesh, target_node, CHANNEL_PORT, + channel_receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); +} + +int main(int argc, char *argv[]) { + (void)argc; + + struct timeval main_loop_wait = { 5, 0 }; + + // Import mesh event handler + + fprintf(stderr, "Mesh node 'corenode1' starting up........\n"); + + if((argv[CMD_LINE_ARG_CLIENTID]) && (argv[CMD_LINE_ARG_IMPORTSTR])) { + client_id = atoi(argv[CMD_LINE_ARG_CLIENTID]); + mesh_event_sock_connect(argv[CMD_LINE_ARG_IMPORTSTR]); + } + + setup_signals(); + + // Execute test steps + + mesh = meshlink_open("testconf", argv[CMD_LINE_ARG_NODENAME], + "test_channel_conn", atoi(argv[CMD_LINE_ARG_DEVCLASS])); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, meshlink_callback_logger); + meshlink_set_channel_accept_cb(mesh, channel_accept); + meshlink_set_node_status_cb(mesh, node_status_cb); + + if(argv[CMD_LINE_ARG_INVITEURL]) { + assert(meshlink_join(mesh, argv[CMD_LINE_ARG_INVITEURL])); + } + + assert(meshlink_start(mesh)); + + send_event(NODE_STARTED); + + assert(wait_sync_flag(&channel_opened, 50)); + send_event(CHANNEL_OPENED); + + assert(wait_sync_flag(&channel_data_recieved, 50)); + send_event(CHANNEL_DATA_RECIEVED); + + // All test steps executed - wait for signals to stop/start or close the mesh + + while(test_running) { + select(1, NULL, NULL, NULL, &main_loop_wait); + } + + meshlink_close(mesh); + + return 0; +} diff --git a/test/blackbox/util/build_container.sh b/test/blackbox/util/build_container.sh new file mode 100755 index 0000000..e03b018 --- /dev/null +++ b/test/blackbox/util/build_container.sh @@ -0,0 +1,97 @@ +#!/bin/sh +# build_container.sh -- Script to populate an LXC Container with the files +# required to run a Meshlink Node Simulation. +# Designed to run on unprivileged Containers. +# Copyright (C) 2018 Guus Sliepen +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# Read command-line arguments +testcase=$1 +nodename=$2 +meshlinkrootpath=$3 +setx=$4 + +# Set configuration for required folders, programs and scripts +# Folder Paths +ltlibfolder=".libs" +meshlinksrclibpath="${meshlinkrootpath}/src/${ltlibfolder}" +blackboxpath="${meshlinkrootpath}/test/blackbox" +blackboxlibpath="${meshlinkrootpath}/test/blackbox/${ltlibfolder}" +blackboxutilpath="${blackboxpath}/util" +testcasepath="${blackboxpath}/${testcase}" +testcaselibpath="${blackboxpath}/${testcase}/${ltlibfolder}" +mirrorfolder="test" +mirrorfolderpath="${testcasepath}/${mirrorfolder}" +mirrorfolderlibpath="${mirrorfolderpath}/${ltlibfolder}" +containerdstpath="/home/ubuntu/${mirrorfolder}" +containerconfbase="/testconf" +containerlogpath="" +# Program/Script Names +ltprefix="lt-" +nodestepscript="node_step.sh" +nodesimpgm="node_sim_${nodename}" +nodesimltscript="${ltprefix}${nodesimpgm}" +geninvitepgm="gen_invite" +geninviteltscript="${ltprefix}${geninvitepgm}" +lxccopydirscript="lxc_copy_dir.sh" +lxccopyfilescript="lxc_copy_file.sh" +lxcrunscript="lxc_run.sh" +# Container Name +containername="${testcase}_${nodename}" + +# Run Libtool Wrapper Scripts once in their built paths in order to generate lt- script inside .libs directory +${blackboxpath}/${geninvitepgm} >/dev/null 2>/dev/null +${testcasepath}/${nodesimpgm} >/dev/null 2>/dev/null + +set ${setx} + +# Create Meshlink Container Mirror Folder (Delete any existing folder before creating new folder) +rm -rf ${mirrorfolderpath} >/dev/null 2>/dev/null +mkdir ${mirrorfolderpath} + +# Populate Mirror Folder +# Copy Wrapper Scripts for Utility Programs +cp ${blackboxpath}/${geninvitepgm} ${mirrorfolderpath} +cp ${testcasepath}/${nodesimpgm} ${mirrorfolderpath} +# Copy Utility Scripts +cp ${blackboxutilpath}/${nodestepscript} ${mirrorfolderpath} +# Set Script Permissions +chmod 755 ${mirrorfolderpath}/* +# Copy Binaries, lt- Scripts and Required Libraries +mkdir ${mirrorfolderlibpath} +cp ${blackboxlibpath}/* ${mirrorfolderlibpath} +cp ${testcaselibpath}/*${nodesimpgm}* ${mirrorfolderlibpath} +cp ${meshlinksrclibpath}/* ${mirrorfolderlibpath} + +# Copy mirror folder into LXC Container +# Delete Destination Folder +${blackboxutilpath}/${lxcrunscript} "rm -rf ${containerdstpath}" ${containername} +# Delete Meshlink confbase folder and logs from Container - every new test case starts on a clean slate +${blackboxutilpath}/${lxcrunscript} "rm -rf ${containerconfbase}" ${containername} +${blackboxutilpath}/${lxcrunscript} "rm ${containerlogpath}/*.log" ${containername} +# Create Destination Folder and Copy Files +${blackboxutilpath}/${lxccopydirscript} ${mirrorfolderpath} ${containername} ${containerdstpath} +# Kill any running instances of the Node Simulation Program +${blackboxutilpath}/${lxcrunscript} "${containerdstpath}/${nodestepscript} ${ltprefix}${nodesimpgm} SIGTERM 2>/dev/null" ${containername} +# Restore the 'interfaces' file in the Container +echo "auto lo" > interfaces +echo "iface lo inet loopback" >> interfaces +echo "" >> interfaces +echo "auto eth0" >> interfaces +echo "iface eth0 inet dhcp" >> interfaces +${blackboxutilpath}/${lxccopyfilescript} interfaces ${containername} /etc/network/interfaces + +set +x diff --git a/test/blackbox/util/gen_invite.c b/test/blackbox/util/gen_invite.c new file mode 100644 index 0000000..365e9fb --- /dev/null +++ b/test/blackbox/util/gen_invite.c @@ -0,0 +1,51 @@ +/* + gen_invite.c -- Black Box Test Utility to generate a meshlink invite + Copyright (C) 2018 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include "../../../src/meshlink.h" +#include "../common/test_step.h" + +#define CMD_LINE_ARG_NODENAME 1 +#define CMD_LINE_ARG_INVITEE 2 +#define CMD_LINE_ARG_SUBMESH 3 + +int main(int argc, char *argv[]) { + char *invite = NULL; + meshlink_submesh_t *s = NULL; + + /* Start mesh, generate an invite and print out the invite */ + meshlink_handle_t *mesh = execute_open(argv[CMD_LINE_ARG_NODENAME], "1"); + execute_start(); + + if(argc > CMD_LINE_ARG_SUBMESH) { + s = meshlink_submesh_open(mesh, argv[CMD_LINE_ARG_SUBMESH]); + } + + invite = execute_invite(argv[CMD_LINE_ARG_INVITEE], s); + printf("%s\n", invite); + execute_close(); + + return EXIT_SUCCESS; +} diff --git a/test/blackbox/util/install_node_sim_copy.sh b/test/blackbox/util/install_node_sim_copy.sh new file mode 100755 index 0000000..af1175d --- /dev/null +++ b/test/blackbox/util/install_node_sim_copy.sh @@ -0,0 +1,3 @@ +cp ../${1}/node_sim_${2} . +mkdir .libs 2>/dev/null +cp ../${1}/.libs/*node_sim_${2}* .libs diff --git a/test/blackbox/util/install_packages.sh b/test/blackbox/util/install_packages.sh new file mode 100755 index 0000000..f427b22 --- /dev/null +++ b/test/blackbox/util/install_packages.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +# nat.sh - Script to create a NAT using LXC Container +# Designed to work on unprivileged Containers +# Copyright (C) 2019 Guus Sliepen +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# Read command-line arguments + +if [ $# -le 1 ] + then + echo "enter valid arguments" + exit 1 +fi + +container=$1 +update_cmd="apt-get update -y >> /dev/null" +echo "${update_cmd}" | lxc-attach -n ${container} -- + +while test $# -gt 1 +do + shift + pkg_name=$1 + install_cmd="apt-get install ${pkg_name} -y >> /dev/null" + echo "${install_cmd}" | lxc-attach -n ${container} -- + if [ $? -ne 0 ] + then + echo "${pkg_name} installation failed in ${container} retrying to install again" + sleep 1 + echo "${update_cmd}" | lxc-attach -n ${container} -- + sleep 1 + echo "${install_cmd}" | lxc-attach -n ${container} -- + if [ $? -ne 0 ] + then + echo "${pkg_name} installation failed in ${container} container" + exit 1 + fi + fi + echo "Installed ${pkg_name} in container ${container}" +done + +exit 0 diff --git a/test/blackbox/util/lxc_copy_dir.sh b/test/blackbox/util/lxc_copy_dir.sh new file mode 100755 index 0000000..d9be5f9 --- /dev/null +++ b/test/blackbox/util/lxc_copy_dir.sh @@ -0,0 +1,27 @@ +# lxc_copy.sh -- Script to transfer multiple files into an LXC Container +# Designed to work on unprivileged Containers +# Copyright (C) 2018 Guus Sliepen +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# Read command-line arguments +srcdir=$1 +containername=$2 +containerdstdir=$3 + +# Create destination directory inside container and copy source directory contents into it +# by 'tar'ing the source directory and un'tar'ing it inside the container +lxc-attach -n ${containername} -- mkdir ${containerdstdir} +tar -C ${srcdir} -c . | lxc-attach -n ${containername} -- tar -C ${containerdstdir} -xvp diff --git a/test/blackbox/util/lxc_copy_file.sh b/test/blackbox/util/lxc_copy_file.sh new file mode 100755 index 0000000..c833681 --- /dev/null +++ b/test/blackbox/util/lxc_copy_file.sh @@ -0,0 +1,25 @@ +# lxc_copy.sh -- Script to transfer multiple files into an LXC Container +# Designed to work on unprivileged Containers +# Copyright (C) 2018 Guus Sliepen +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# Read Command-line arguments +srcfilepath=$1 +containername=$2 +dstfilepath=$3 + +# Copy file into Container +cat ${srcfilepath} | lxc-attach -n ${containername} -- sh -c "cat > ${dstfilepath}" diff --git a/test/blackbox/util/lxc_rename.sh b/test/blackbox/util/lxc_rename.sh new file mode 100755 index 0000000..31c0bcf --- /dev/null +++ b/test/blackbox/util/lxc_rename.sh @@ -0,0 +1,29 @@ +# lxc_rename.sh - Script to rename an LXC Container +# Designed to work on unprivileged Containers +# Copyright (C) 2018 Guus Sliepen +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# Read command-line arguments +lxcpath=$1 +oldname=$2 +newname=$3 + +# Run command inside Container by attaching to the Container and sending it the command +mv ${lxcpath}/${oldname} ${lxcpath}/${newname} +sed {s/${oldname}/${newname}/} ${lxcpath}/${newname}/config > ${lxcpath}/${newname}/config1 +mv ${lxcpath}/${newname}/config1 ${lxcpath}/${newname}/config +#lxc-copy -n ${oldname} -P lxcpath -N ${newname} -p lxcpath -R +exit $? diff --git a/test/blackbox/util/lxc_run.sh b/test/blackbox/util/lxc_run.sh new file mode 100755 index 0000000..4a2456e --- /dev/null +++ b/test/blackbox/util/lxc_run.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# lxc_run.sh - Script to run a command inside an LXC Container +# Designed to work on unprivileged Containers +# Copyright (C) 2018 Guus Sliepen +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# Read command-line arguments +cmd=${1} +containername=${2} + +# Run command inside Container by attaching to the Container and sending it the command +echo "${cmd}" | lxc-attach -n ${containername} -- +exit $? diff --git a/test/blackbox/util/nat.sh b/test/blackbox/util/nat.sh new file mode 100755 index 0000000..314eff0 --- /dev/null +++ b/test/blackbox/util/nat.sh @@ -0,0 +1,130 @@ +#!/bin/bash + +# nat.sh - Script to create a NAT using LXC Container +# Designed to work on unprivileged Containers +# Copyright (C) 2019 Guus Sliepen +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# Read command-line arguments +if [ $# -ne 3 ] + then + echo "enter valid arguments" + exit 1 +fi +router_container=$1 +router_bridge="${router_container}_bridge" +router_conf_path="${2}/${router_container}/config" +meshlinkrootpath=$3 + +MAXCOUNT=10 +RANGE=16 +number1_1=$RANDOM +number1_2=$RANDOM +number2_1=$RANDOM +number2_2=$RANDOM + +let "number1_1 %= $RANGE" +let "number1_2 %= $RANGE" +let "number2_1 %= $RANGE" +let "number2_2 %= $RANGE" + +number1_1="$((echo "obase=16; ${number1_1}") | bc)" +number1_2="$((echo "obase=16; ${number1_2}") | bc)" +number2_1="$((echo "obase=16; ${number2_1}") | bc)" +number2_2="$((echo "obase=16; ${number2_2}") | bc)" + +echo + Creating nat bridge +ifconfig ${router_bridge} down >/dev/null 2>/dev/null +brctl delbr ${router_bridge} >/dev/null 2>/dev/null +brctl addbr ${router_bridge} +ifconfig ${router_bridge} up + +# Destroying the existing router if already exists +lxc-stop -n ${router_container} >/dev/null 2>/dev/null +lxc-destroy -n ${router_container} >/dev/null 2>/dev/null + +echo + Creating router +lxc-create -t download -n ${router_container} -- -d ubuntu -r trusty -a amd64 >> /dev/null +echo + Creating config file for router +echo "lxc.net.0.name = eth0" >> ${router_conf_path} +echo " " >> ${router_conf_path} +echo "lxc.net.1.type = veth" >> ${router_conf_path} +echo "lxc.net.1.flags = up" >> ${router_conf_path} +echo "lxc.net.1.link = ${router_bridge}" >> ${router_conf_path} +echo "lxc.net.1.name = eth1" >> ${router_conf_path} +echo "lxc.net.1.hwaddr = 00:16:3e:ab:32:2a" >> ${router_conf_path} + +echo + Starting Router +lxc-start -n ${router_container} + +echo + Waiting for IP address.. +while [ -z `lxc-info -n ${router_container} -iH` ] +do + sleep 1 +done +eth0_ip=`lxc-info -n ${router_container} -iH` +echo "Obtained IP address: ${eth0_ip}" + +############################################################################################################### + +echo "Installing and Configuring iptables, dnsmasq conntrack packages in ${1}" +${meshlinkrootpath}/test/blackbox/util/install_packages.sh ${1} iptables dnsmasq conntrack +if [ $? -ne 0 ] +then + exit 1 +fi + +cmd="echo \"interface=eth1\" >> /etc/dnsmasq.conf" +echo "${cmd}" | lxc-attach -n ${router_container} -- +cmd="echo \"bind-interfaces\" >> /etc/dnsmasq.conf" +echo "${cmd}" | lxc-attach -n ${router_container} -- +cmd="echo \"listen-address=172.16.0.1\" >> /etc/dnsmasq.conf" +echo "${cmd}" | lxc-attach -n ${router_container} -- +cmd="echo \"dhcp-range=172.16.0.2,172.16.0.254,12h\" >> /etc/dnsmasq.conf" +echo "${cmd}" | lxc-attach -n ${router_container} -- +cmd="ifconfig eth1 172.16.0.1 netmask 255.255.255.0 up" +echo "${cmd}" | lxc-attach -n ${router_container} -- +if [ $? -ne 0 ] +then + echo "Failed to configure eth1 interface" + exit 1 +fi +cmd="service dnsmasq restart >> /dev/null" +echo "${cmd}" | lxc-attach -n ${router_container} -- +if [ $? -ne 0 ] +then + echo "Failed to restart service" + exit 1 +fi + +echo + Configuring NAT for ${1}.... +cmd="iptables -t nat -A POSTROUTING -o eth0 -j SNAT --to-source ${eth0_ip} " +echo "${cmd}" | sudo lxc-attach -n ${router_container} -- +if [ $? -ne 0 ] +then + echo "Failed to apply NAT rule" + exit 1 +fi +cmd="iptables -t nat -A PREROUTING -i eth0 -j DNAT --to-destination 172.16.0.1 " +echo "${cmd}" | sudo lxc-attach -n ${router_container} -- +if [ $? -ne 0 ] +then + echo "Failed to apply NAT rule" + exit 1 +fi +echo "Router created and configured with Full-cone NAT" + +exit 0 diff --git a/test/blackbox/util/nat_destroy.sh b/test/blackbox/util/nat_destroy.sh new file mode 100755 index 0000000..2fa0f20 --- /dev/null +++ b/test/blackbox/util/nat_destroy.sh @@ -0,0 +1,16 @@ +#!/bin/bash +router=${1} +router_bridge="${1}_bridge" + +echo + Stopping router...... +lxc-stop -n ${router} + +echo + Removing NATs bridge.... + +ifconfig ${router_bridge} down + +brctl delbr ${router_bridge} + +echo + Destroing the routers..... + +lxc-destroy -n ${router} >> /dev/null diff --git a/test/blackbox/util/node_step.sh b/test/blackbox/util/node_step.sh new file mode 100755 index 0000000..fe28423 --- /dev/null +++ b/test/blackbox/util/node_step.sh @@ -0,0 +1,25 @@ +# node_step.sh -- Script to send signal to control Mesh Node Simulation +# Copyright (C) 2018 Guus Sliepen +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# Read command-line arguments +prog_name=$1 +signal=$2 + +# Find instance of running program and send the named signal to it +pid=`/bin/pidof -s ${prog_name}` +kill -${signal} ${pid} +exit $? diff --git a/test/blacklist.c b/test/blacklist.c new file mode 100644 index 0000000..cd54585 --- /dev/null +++ b/test/blacklist.c @@ -0,0 +1,257 @@ +#define _GNU_SOURCE + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "meshlink.h" +#include "devtools.h" +#include "utils.h" + +static struct sync_flag bar_connected; +static struct sync_flag bar_disconnected; +static struct sync_flag bar_blacklisted; +static struct sync_flag baz_connected; + +static void foo_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + (void)reachable; + + if(!strcmp(node->name, "bar")) { + if(reachable) { + set_sync_flag(&bar_connected, true); + } else { + set_sync_flag(&bar_disconnected, true); + } + } +} + +static void baz_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + (void)reachable; + + if(!strcmp(node->name, "bar")) { + if(reachable) { + set_sync_flag(&baz_connected, true); + } + } +} + +static void bar_blacklisted_cb(meshlink_handle_t *mesh, meshlink_node_t *node) { + (void)mesh; + + if(!strcmp(node->name, "foo")) { + set_sync_flag(&bar_blacklisted, true); + } +} + +int main(void) { + init_sync_flag(&bar_connected); + init_sync_flag(&bar_disconnected); + init_sync_flag(&bar_blacklisted); + init_sync_flag(&baz_connected); + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Create three instances. + + const char *name[3] = {"foo", "bar", "baz"}; + meshlink_handle_t *mesh[3]; + char *data[3]; + + for(int i = 0; i < 3; i++) { + char *path = NULL; + assert(asprintf(&path, "blacklist_conf.%d", i) != -1 && path); + + assert(meshlink_destroy(path)); + mesh[i] = meshlink_open(path, name[i], "blacklist", DEV_CLASS_BACKBONE); + assert(mesh[i]); + free(path); + + assert(meshlink_set_canonical_address(mesh[i], meshlink_get_self(mesh[i]), "localhost", NULL)); + + data[i] = meshlink_export(mesh[i]); + assert(data[i]); + + // Enable default blacklist on all nodes. + meshlink_set_default_blacklist(mesh[i], true); + } + + // The first node knows the two other nodes. + + for(int i = 1; i < 3; i++) { + assert(meshlink_import(mesh[i], data[0])); + assert(meshlink_import(mesh[0], data[i])); + + assert(meshlink_get_node(mesh[i], name[0])); + assert(meshlink_get_node(mesh[0], name[i])); + + } + + for(int i = 0; i < 3; i++) { + free(data[i]); + } + + // Second and third node should not know each other yet. + + assert(!meshlink_get_node(mesh[1], name[2])); + assert(!meshlink_get_node(mesh[2], name[1])); + + // Check default blacklist status + + assert(!meshlink_get_node_blacklisted(mesh[0], meshlink_get_self(mesh[0]))); + assert(!meshlink_get_node_blacklisted(mesh[0], meshlink_get_node(mesh[0], name[1]))); + assert(meshlink_get_node_blacklisted(mesh[1], meshlink_get_node(mesh[1], name[2]))); + assert(meshlink_get_node_blacklisted(mesh[2], meshlink_get_node(mesh[2], name[1]))); + + // Generate an invitation for a node that is about to be blacklisted + + char *invitation = meshlink_invite(mesh[0], NULL, "xyzzy"); + assert(invitation); + free(invitation); + + // Whitelisting and blacklisting by name should work. + + assert(meshlink_whitelist_by_name(mesh[0], "quux")); + assert(meshlink_blacklist_by_name(mesh[0], "xyzzy")); + assert(meshlink_get_node(mesh[0], "quux")); + assert(!meshlink_get_node_blacklisted(mesh[0], meshlink_get_node(mesh[0], "quux"))); + assert(meshlink_get_node_blacklisted(mesh[0], meshlink_get_node(mesh[0], "xyzzy"))); + + meshlink_node_t **nodes = NULL; + size_t nnodes = 0; + nodes = meshlink_get_all_nodes_by_blacklisted(mesh[0], true, nodes, &nnodes); + assert(nnodes == 1); + assert(!strcmp(nodes[0]->name, "xyzzy")); + + nodes = meshlink_get_all_nodes_by_blacklisted(mesh[0], false, nodes, &nnodes); + assert(nnodes == 4); + assert(!strcmp(nodes[0]->name, "bar")); + assert(!strcmp(nodes[1]->name, "baz")); + assert(!strcmp(nodes[2]->name, "foo")); + assert(!strcmp(nodes[3]->name, "quux")); + + free(nodes); + + // Check that blacklisted nodes are not allowed to be invited, and no invitations are left on disk. + + assert(!meshlink_invite(mesh[0], NULL, "xyzzy")); + + DIR *dir = opendir("blacklist_conf.0/current/invitations"); + assert(dir); + struct dirent *ent; + + while((ent = readdir(dir))) { + assert(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")); + } + + closedir(dir); + + // Since these nodes now exist we should be able to forget them. + + assert(meshlink_forget_node(mesh[0], meshlink_get_node(mesh[0], "quux"))); + assert(meshlink_get_node_blacklisted(mesh[0], meshlink_get_node(mesh[0], "quux"))); // default blacklisted again + + // Start the nodes. + + meshlink_set_node_status_cb(mesh[0], foo_status_cb); + meshlink_set_node_status_cb(mesh[2], baz_status_cb); + + for(int i = 0; i < 3; i++) { + assert(meshlink_start(mesh[i])); + } + + // Wait for them to connect. + + assert(wait_sync_flag(&bar_connected, 5)); + + // Blacklist bar + + meshlink_set_blacklisted_cb(mesh[1], bar_blacklisted_cb); + + set_sync_flag(&bar_disconnected, false); + assert(meshlink_blacklist(mesh[0], meshlink_get_node(mesh[0], name[1]))); + assert(wait_sync_flag(&bar_disconnected, 5)); + assert(meshlink_get_node_blacklisted(mesh[0], meshlink_get_node(mesh[0], name[1]))); + + assert(wait_sync_flag(&bar_blacklisted, 10)); + + // Whitelist bar + + set_sync_flag(&bar_connected, false); + assert(meshlink_whitelist(mesh[0], meshlink_get_node(mesh[0], name[1]))); + assert(wait_sync_flag(&bar_connected, 15)); + assert(!meshlink_get_node_blacklisted(mesh[0], meshlink_get_node(mesh[0], name[1]))); + + // Bar should not connect to baz + + assert(wait_sync_flag(&baz_connected, 5) == false); + + // But it should know about baz by now + + meshlink_node_t *bar = meshlink_get_node(mesh[2], "bar"); + meshlink_node_t *baz = meshlink_get_node(mesh[1], "baz"); + assert(bar); + assert(baz); + + // Have bar and baz whitelist each other + + assert(meshlink_whitelist(mesh[1], baz)); + assert(meshlink_whitelist(mesh[2], bar)); + assert(!meshlink_get_node_blacklisted(mesh[1], baz)); + assert(!meshlink_get_node_blacklisted(mesh[2], bar)); + + // They should connect to each other + + assert(wait_sync_flag(&baz_connected, 15)); + + // Trying to forget an active node should fail. + + assert(!meshlink_forget_node(mesh[1], baz)); + + // We need to re-acquire the handle to baz + + baz = meshlink_get_node(mesh[1], "baz"); + assert(baz); + + // Stop the mesh. + + for(int i = 0; i < 3; i++) { + meshlink_stop(mesh[i]); + } + + // Forgetting a node should work now. + + assert(meshlink_forget_node(mesh[1], baz)); + + // Clean up. + + for(int i = 0; i < 3; i++) { + meshlink_close(mesh[i]); + } + + // Check that foo has a config file for xyzzy but not quux + assert(access("blacklist_conf.0/current/hosts/xyzzy", F_OK) == 0); + assert(access("blacklist_conf.0/current/hosts/quux", F_OK) != 0 && errno == ENOENT); + + // Check that bar has no config file for baz + assert(access("blacklist_conf.2/current/hosts/bar", F_OK) == 0); + assert(access("blacklist_conf.1/current/hosts/baz", F_OK) != 0 && errno == ENOENT); + + // Check that we remember xyzzy but not quux after reopening the mesh + mesh[0] = meshlink_open("blacklist_conf.0", "foo", "blacklist", DEV_CLASS_BACKBONE); + assert(mesh[0]); + assert(meshlink_get_node(mesh[0], "xyzzy")); + assert(!meshlink_get_node(mesh[0], "quux")); + + meshlink_close(mesh[0]); +} diff --git a/test/channels-aio-abort.c b/test/channels-aio-abort.c new file mode 100644 index 0000000..19a2566 --- /dev/null +++ b/test/channels-aio-abort.c @@ -0,0 +1,125 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include + +#include "meshlink.h" +#include "utils.h" + +static const size_t size = 2000000; // size of data to transfer + +struct aio_info { + char *data; + int callbacks; + size_t size; + struct timespec ts; + struct sync_flag flag; +}; + +static void aio_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len, void *priv) { + fprintf(stderr, "%s aio_cb %s %p %zu\n", mesh->name, channel->node->name, data, len); + (void)mesh; + (void)channel; + (void)data; + (void)len; + + struct aio_info *info = priv; + clock_gettime(CLOCK_MONOTONIC, &info->ts); + info->callbacks++; + info->size += len; + set_sync_flag(&info->flag, true); + meshlink_channel_abort(mesh, channel); + free(info->data); +} + +static bool accept_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + fprintf(stderr, "%s accept %s\n", mesh->name, channel->node->name); + assert(port == 1); + assert(!data); + assert(!len); + + struct aio_info *info = mesh->priv; + + assert(meshlink_channel_aio_receive(mesh, channel, info->data, size / 2, aio_cb, info)); + + return true; +} + +int main(void) { + meshlink_set_log_cb(NULL, MESHLINK_WARNING, log_cb); + + struct aio_info in_info; + struct aio_info out_info; + + memset(&in_info, 0, sizeof(in_info)); + memset(&out_info, 0, sizeof(out_info)); + + init_sync_flag(&in_info.flag); + init_sync_flag(&out_info.flag); + + in_info.data = calloc(1, size / 2); + assert(in_info.data); + out_info.data = calloc(1, size); + assert(out_info.data); + + // Open two new meshlink instance. + + meshlink_handle_t *mesh_a, *mesh_b; + open_meshlink_pair(&mesh_a, &mesh_b, "channels_aio_abort"); + + // Set the callbacks. + + mesh_b->priv = &in_info; + meshlink_set_channel_accept_cb(mesh_b, accept_cb); + + // Start both instances + + start_meshlink_pair(mesh_a, mesh_b); + + // Open channel from a to b. + + meshlink_node_t *b = meshlink_get_node(mesh_a, "b"); + assert(b); + meshlink_channel_t *channel = meshlink_channel_open(mesh_a, b, 1, NULL, NULL, 0); + assert(channel); + + // Send data, receiver aborts halfway + + assert(meshlink_channel_aio_send(mesh_a, channel, out_info.data, size, aio_cb, &out_info)); + + // Wait for everyone to finish. + + assert(wait_sync_flag(&out_info.flag, 10)); + assert(wait_sync_flag(&in_info.flag, 10)); + + // Open a new data, now sender aborts halfway + + init_sync_flag(&in_info.flag); + init_sync_flag(&out_info.flag); + + in_info.data = calloc(1, size / 2); + assert(in_info.data); + out_info.data = calloc(1, size / 4); + assert(out_info.data); + + channel = meshlink_channel_open(mesh_a, b, 1, NULL, NULL, 0); + assert(channel); + assert(meshlink_channel_aio_send(mesh_a, channel, out_info.data, size / 4, aio_cb, &out_info)); + + // Wait for everyone to finish. + + assert(wait_sync_flag(&out_info.flag, 10)); + assert(wait_sync_flag(&in_info.flag, 10)); + + // Clean up. + + close_meshlink_pair(mesh_a, mesh_b); +} diff --git a/test/channels-aio-cornercases.c b/test/channels-aio-cornercases.c new file mode 100644 index 0000000..208c955 --- /dev/null +++ b/test/channels-aio-cornercases.c @@ -0,0 +1,201 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include + +#include "meshlink.h" +#include "utils.h" + +static const size_t size = 12000000; // size of data to transfer + +struct aio_info { + int port; + int callbacks; + size_t size; + struct timeval tv; + struct sync_flag flag; +}; + +struct channel_info { + char *data; + struct aio_info aio_infos[2]; +}; + +static struct sync_flag b_received_flag; + +static void aio_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len, void *priv) { + (void)mesh; + (void)channel; + (void)data; + (void)len; + + struct aio_info *info = priv; + + fprintf(stderr, "%d:%s aio_cb %s %p %zu\n", info->port, mesh->name, channel->node->name, data, len); + + gettimeofday(&info->tv, NULL); + info->callbacks++; + info->size += len; + set_sync_flag(&info->flag, true); +} + +static void aio_cb_close(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len, void *priv) { + aio_cb(mesh, channel, data, len, priv); + struct aio_info *info = priv; + fprintf(stderr, "%d:%s aio_cb %s closing\n", info->port, mesh->name, channel->node->name); + meshlink_channel_close(mesh, channel); +} + +static bool accept_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + assert(!data); + assert(!len); + + fprintf(stderr, "%d:%s accept_cb %s\n", port, mesh->name, channel->node->name); + + struct channel_info *infos = mesh->priv; + struct channel_info *info = &infos[port - 1]; + + switch(port) { + case 1: + case 3: + assert(meshlink_channel_aio_receive(mesh, channel, info->data, size / 4, aio_cb, &info->aio_infos[0])); + assert(meshlink_channel_aio_receive(mesh, channel, info->data + size / 4, size - size / 4, aio_cb_close, &info->aio_infos[1])); + break; + + case 2: + case 4: + assert(meshlink_channel_aio_receive(mesh, channel, info->data, size / 4, aio_cb_close, &info->aio_infos[0])); + assert(meshlink_channel_aio_receive(mesh, channel, info->data + size / 4, size - size / 4, aio_cb, &info->aio_infos[1])); + break; + + default: + return false; + } + + return true; +} + +int main(void) { + init_sync_flag(&b_received_flag); + + meshlink_set_log_cb(NULL, MESHLINK_WARNING, log_cb); + + // Prepare data buffers + + char *outdata = malloc(size); + assert(outdata); + + for(size_t i = 0; i < size; i++) { + outdata[i] = i; + } + + static const size_t nchannels = 4; + struct channel_info in_infos[nchannels]; + struct channel_info out_infos[nchannels]; + + memset(in_infos, 0, sizeof(in_infos)); + memset(out_infos, 0, sizeof(out_infos)); + + for(size_t i = 0; i < nchannels; i++) { + init_sync_flag(&in_infos[i].aio_infos[0].flag); + init_sync_flag(&in_infos[i].aio_infos[1].flag); + init_sync_flag(&out_infos[i].aio_infos[0].flag); + init_sync_flag(&out_infos[i].aio_infos[1].flag); + + in_infos[i].data = malloc(size); + assert(in_infos[i].data); + out_infos[i].data = outdata; + + out_infos[i].aio_infos[0].port = i + 1; + out_infos[i].aio_infos[1].port = i + 1; + in_infos[i].aio_infos[0].port = i + 1; + in_infos[i].aio_infos[1].port = i + 1; + } + + // Open two new meshlink instance. + + meshlink_handle_t *mesh_a, *mesh_b; + open_meshlink_pair(&mesh_a, &mesh_b, "channels_aio_cornercases"); + + // Set the callbacks. + + mesh_b->priv = in_infos; + + meshlink_set_channel_accept_cb(mesh_b, accept_cb); + + // Start both instances + + start_meshlink_pair(mesh_a, mesh_b); + sleep(1); + + // Open channels from a to b. + + meshlink_node_t *b = meshlink_get_node(mesh_a, "b"); + assert(b); + + meshlink_channel_t *channels[nchannels + 1]; + + // Send a large buffer of data on each channel. + + for(size_t i = 0; i < nchannels; i++) { + channels[i] = meshlink_channel_open(mesh_a, b, i + 1, NULL, NULL, 0); + assert(channels[i]); + + if(i < 2) { + assert(meshlink_channel_aio_send(mesh_a, channels[i], outdata, size / 3, aio_cb, &out_infos[i].aio_infos[0])); + assert(meshlink_channel_aio_send(mesh_a, channels[i], outdata + size / 3, size - size / 3, aio_cb_close, &out_infos[i].aio_infos[1])); + } else { + assert(meshlink_channel_aio_send(mesh_a, channels[i], outdata, size / 3, aio_cb_close, &out_infos[i].aio_infos[0])); + assert(meshlink_channel_aio_send(mesh_a, channels[i], outdata + size / 3, size - size / 3, aio_cb, &out_infos[i].aio_infos[1])); + } + } + + // Wait for all AIO buffers to finish. + + for(size_t i = 0; i < nchannels; i++) { + // The first chunk should always have succeeded + assert(wait_sync_flag(&in_infos[i].aio_infos[0].flag, 10)); + assert(wait_sync_flag(&out_infos[i].aio_infos[0].flag, 10)); + + // The second chunk should only have completed if we didn't close the channel yet + if(i % 2) { + assert(!check_sync_flag(&in_infos[i].aio_infos[1].flag)); + } else { + assert(wait_sync_flag(&in_infos[i].aio_infos[1].flag, 10)); + } + + if(i < 2) { + assert(wait_sync_flag(&out_infos[i].aio_infos[1].flag, 10)); + } else { + assert(!check_sync_flag(&out_infos[i].aio_infos[1].flag)); + } + + } + + // Check that everything is correct. + + assert(!memcmp(in_infos[0].data, out_infos[0].data, size)); + assert(!memcmp(in_infos[1].data, out_infos[1].data, size / 4)); + assert(memcmp(in_infos[1].data, out_infos[1].data + size / 4, size - size / 4)); + assert(!memcmp(in_infos[2].data, out_infos[2].data, size / 3)); + assert(memcmp(in_infos[2].data, out_infos[2].data + size / 3, size - size / 3)); + assert(!memcmp(in_infos[3].data, out_infos[3].data, size / 4)); + assert(memcmp(in_infos[3].data, out_infos[3].data + size / 4, size / 3 - size / 4)); + assert(memcmp(in_infos[3].data, out_infos[3].data + size / 3, size - size / 3)); + + // Clean up. + + close_meshlink_pair(mesh_a, mesh_b); + + free(outdata); + + for(size_t i = 0; i < nchannels; i++) { + free(in_infos[i].data); + } +} diff --git a/test/channels-aio-fd.c b/test/channels-aio-fd.c new file mode 100644 index 0000000..3cac823 --- /dev/null +++ b/test/channels-aio-fd.c @@ -0,0 +1,179 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include + +#include "meshlink.h" +#include "utils.h" + +static const size_t size = 1024 * 1024; // size of data to transfer +static const size_t nchannels = 4; // number of simultaneous channels + +struct aio_info { + int callbacks; + size_t size; + struct timespec ts; + struct sync_flag flag; +}; + +struct channel_info { + FILE *file; + struct aio_info aio_infos[2]; +}; + +static void aio_fd_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, int fd, size_t len, void *priv) { + (void)mesh; + (void)channel; + (void)fd; + (void)len; + + struct aio_info *info = priv; + clock_gettime(CLOCK_MONOTONIC, &info->ts); + info->callbacks++; + info->size += len; + set_sync_flag(&info->flag, true); +} + +static bool accept_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + assert(port && port <= nchannels); + assert(!data); + assert(!len); + + struct channel_info *infos = mesh->priv; + struct channel_info *info = &infos[port - 1]; + + assert(meshlink_channel_aio_fd_receive(mesh, channel, fileno(info->file), size / 4, aio_fd_cb, &info->aio_infos[0])); + assert(meshlink_channel_aio_fd_receive(mesh, channel, fileno(info->file), size - size / 4, aio_fd_cb, &info->aio_infos[1])); + + return true; +} + +int main(void) { + meshlink_set_log_cb(NULL, MESHLINK_WARNING, log_cb); + + // Prepare file + + char *outdata = malloc(size); + assert(outdata); + + for(size_t i = 0; i < size; i++) { + // Human readable output + outdata[i] = i % 96 ? i % 96 + 32 : '\n'; + } + + FILE *file = fopen("channels_aio_fd.in", "w"); + assert(file); + assert(fwrite(outdata, size, 1, file) == 1); + assert(fclose(file) == 0); + + struct channel_info in_infos[nchannels]; + struct channel_info out_infos[nchannels]; + + memset(in_infos, 0, sizeof(in_infos)); + memset(out_infos, 0, sizeof(out_infos)); + + for(size_t i = 0; i < nchannels; i++) { + init_sync_flag(&in_infos[i].aio_infos[0].flag); + init_sync_flag(&in_infos[i].aio_infos[1].flag); + init_sync_flag(&out_infos[i].aio_infos[0].flag); + init_sync_flag(&out_infos[i].aio_infos[1].flag); + + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "channels_aio_fd.out%d", (int)i); + in_infos[i].file = fopen(filename, "w"); + assert(in_infos[i].file); + out_infos[i].file = fopen("channels_aio_fd.in", "r"); + assert(out_infos[i].file); + } + + // Open two new meshlink instance. + + meshlink_handle_t *mesh_a, *mesh_b; + open_meshlink_pair(&mesh_a, &mesh_b, "channels_aio_fd"); + + mesh_b->priv = in_infos; + + meshlink_enable_discovery(mesh_a, false); + meshlink_enable_discovery(mesh_b, false); + + // Set the callbacks. + + meshlink_set_channel_accept_cb(mesh_b, accept_cb); + + // Start both instances + + start_meshlink_pair(mesh_a, mesh_b); + + // Open channels from a to b. + + meshlink_node_t *b = meshlink_get_node(mesh_a, "b"); + assert(b); + + meshlink_channel_t *channels[nchannels]; + + for(size_t i = 0; i < nchannels; i++) { + channels[i] = meshlink_channel_open(mesh_a, b, i + 1, NULL, NULL, 0); + assert(channels[i]); + } + + // Send a large buffer of data on each channel. + + for(size_t i = 0; i < nchannels; i++) { + assert(meshlink_channel_aio_fd_send(mesh_a, channels[i], fileno(out_infos[i].file), size / 3, aio_fd_cb, &out_infos[i].aio_infos[0])); + assert(meshlink_channel_aio_fd_send(mesh_a, channels[i], fileno(out_infos[i].file), size - size / 3, aio_fd_cb, &out_infos[i].aio_infos[1])); + } + + // Wait for everyone to finish. + + for(size_t i = 0; i < nchannels; i++) { + assert(wait_sync_flag(&out_infos[i].aio_infos[0].flag, 10)); + assert(wait_sync_flag(&out_infos[i].aio_infos[1].flag, 10)); + assert(wait_sync_flag(&in_infos[i].aio_infos[0].flag, 10)); + assert(wait_sync_flag(&in_infos[i].aio_infos[1].flag, 10)); + } + + // Check that everything is correct. + + for(size_t i = 0; i < nchannels; i++) { + assert(fclose(in_infos[i].file) == 0); + assert(fclose(out_infos[i].file) == 0); + + // One callback for each AIO buffer. + assert(out_infos[i].aio_infos[0].callbacks == 1); + assert(out_infos[i].aio_infos[1].callbacks == 1); + assert(in_infos[i].aio_infos[0].callbacks == 1); + assert(in_infos[i].aio_infos[1].callbacks == 1); + + // Correct size sent and received. + assert(out_infos[i].aio_infos[0].size == size / 3); + assert(out_infos[i].aio_infos[1].size == size - size / 3); + assert(in_infos[i].aio_infos[0].size == size / 4); + assert(in_infos[i].aio_infos[1].size == size - size / 4); + + // First batch of data should all be sent and received before the second batch + for(size_t j = 0; j < nchannels; j++) { + assert(timespec_lt(&out_infos[i].aio_infos[0].ts, &out_infos[j].aio_infos[1].ts)); + assert(timespec_lt(&in_infos[i].aio_infos[0].ts, &in_infos[j].aio_infos[1].ts)); + } + + // Files should be identical + char command[PATH_MAX]; + snprintf(command, sizeof(command), "cmp channels_aio_fd.in channels_aio_fd.out%d", (int)i); + assert(system(command) == 0); + + } + + // Clean up. + + close_meshlink_pair(mesh_a, mesh_b); + free(outdata); +} diff --git a/test/channels-aio.c b/test/channels-aio.c new file mode 100644 index 0000000..19472ba --- /dev/null +++ b/test/channels-aio.c @@ -0,0 +1,200 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include + +#include "meshlink.h" +#include "utils.h" + +static const size_t size = 25000000; // size of data to transfer +static const size_t smallsize = 100000; // size of the data to transfer without AIO +static const size_t nchannels = 4; // number of simultaneous channels + +struct aio_info { + int callbacks; + size_t size; + struct timespec ts; + struct sync_flag flag; +}; + +struct channel_info { + char *data; + struct aio_info aio_infos[2]; +}; + +static size_t b_received_len; +static struct timespec b_received_ts; +static struct sync_flag b_received_flag; + +static void aio_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len, void *priv) { + (void)mesh; + (void)channel; + (void)data; + (void)len; + + struct aio_info *info = priv; + clock_gettime(CLOCK_MONOTONIC, &info->ts); + info->callbacks++; + info->size += len; + set_sync_flag(&info->flag, true); +} + +static void receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) { + (void)mesh; + (void)channel; + (void)data; + + b_received_len += len; + + if(b_received_len >= smallsize) { + clock_gettime(CLOCK_MONOTONIC, &b_received_ts); + set_sync_flag(&b_received_flag, true); + } +} + +static bool accept_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + assert(port && port <= nchannels + 1); + assert(!data); + assert(!len); + + if(port <= nchannels) { + struct channel_info *infos = mesh->priv; + struct channel_info *info = &infos[port - 1]; + + assert(meshlink_channel_aio_receive(mesh, channel, info->data, size / 4, aio_cb, &info->aio_infos[0])); + assert(meshlink_channel_aio_receive(mesh, channel, info->data + size / 4, size - size / 4, aio_cb, &info->aio_infos[1])); + } else { + meshlink_set_channel_receive_cb(mesh, channel, receive_cb); + } + + return true; +} + +int main(void) { + init_sync_flag(&b_received_flag); + + meshlink_set_log_cb(NULL, MESHLINK_WARNING, log_cb); + + // Prepare data buffers + + char *outdata = malloc(size); + assert(outdata); + + for(size_t i = 0; i < size; i++) { + outdata[i] = i; + } + + struct channel_info in_infos[nchannels]; + + struct channel_info out_infos[nchannels]; + + memset(in_infos, 0, sizeof(in_infos)); + + memset(out_infos, 0, sizeof(out_infos)); + + for(size_t i = 0; i < nchannels; i++) { + init_sync_flag(&in_infos[i].aio_infos[0].flag); + init_sync_flag(&in_infos[i].aio_infos[1].flag); + init_sync_flag(&out_infos[i].aio_infos[0].flag); + init_sync_flag(&out_infos[i].aio_infos[1].flag); + + in_infos[i].data = malloc(size); + assert(in_infos[i].data); + out_infos[i].data = outdata; + } + + // Open two new meshlink instance. + + meshlink_handle_t *mesh_a, *mesh_b; + open_meshlink_pair(&mesh_a, &mesh_b, "channels_aio"); + + // Set the callbacks. + + mesh_b->priv = in_infos; + + meshlink_set_channel_accept_cb(mesh_b, accept_cb); + + // Start both instances + + start_meshlink_pair(mesh_a, mesh_b); + + // Open channels from a to b. + + meshlink_node_t *b = meshlink_get_node(mesh_a, "b"); + assert(b); + + meshlink_channel_t *channels[nchannels + 1]; + + for(size_t i = 0; i < nchannels + 1; i++) { + channels[i] = meshlink_channel_open(mesh_a, b, i + 1, NULL, NULL, 0); + assert(channels[i]); + } + + // Send a large buffer of data on each channel. + + for(size_t i = 0; i < nchannels; i++) { + assert(meshlink_channel_aio_send(mesh_a, channels[i], outdata, size / 3, aio_cb, &out_infos[i].aio_infos[0])); + assert(meshlink_channel_aio_send(mesh_a, channels[i], outdata + size / 3, size - size / 3, aio_cb, &out_infos[i].aio_infos[1])); + } + + // Send a little bit on the last channel using a regular send + + assert(meshlink_channel_send(mesh_a, channels[nchannels], outdata, smallsize) == (ssize_t)smallsize); + + // Wait for everyone to finish. + + assert(wait_sync_flag(&b_received_flag, 10)); + + for(size_t i = 0; i < nchannels; i++) { + assert(wait_sync_flag(&out_infos[i].aio_infos[0].flag, 10)); + assert(wait_sync_flag(&out_infos[i].aio_infos[1].flag, 10)); + assert(wait_sync_flag(&in_infos[i].aio_infos[0].flag, 10)); + assert(wait_sync_flag(&in_infos[i].aio_infos[1].flag, 10)); + } + + // Check that everything is correct. + + assert(b_received_len == smallsize); + + for(size_t i = 0; i < nchannels; i++) { + // Data should be transferred intact. + assert(!memcmp(in_infos[i].data, out_infos[i].data, size)); + + // One callback for each AIO buffer. + assert(out_infos[i].aio_infos[0].callbacks == 1); + assert(out_infos[i].aio_infos[1].callbacks == 1); + assert(in_infos[i].aio_infos[0].callbacks == 1); + assert(in_infos[i].aio_infos[1].callbacks == 1); + + // Correct size sent and received. + assert(out_infos[i].aio_infos[0].size == size / 3); + assert(out_infos[i].aio_infos[1].size == size - size / 3); + assert(in_infos[i].aio_infos[0].size == size / 4); + assert(in_infos[i].aio_infos[1].size == size - size / 4); + + // First batch of data should all be sent and received before the second batch + for(size_t j = 0; j < nchannels; j++) { + assert(timespec_lt(&out_infos[i].aio_infos[0].ts, &out_infos[j].aio_infos[1].ts)); + assert(timespec_lt(&in_infos[i].aio_infos[0].ts, &in_infos[j].aio_infos[1].ts)); + } + + // The non-AIO transfer should have completed before everything else + assert(!timespec_lt(&out_infos[i].aio_infos[0].ts, &b_received_ts)); + assert(!timespec_lt(&in_infos[i].aio_infos[0].ts, &b_received_ts)); + + free(in_infos[i].data); + } + + // Clean up. + + close_meshlink_pair(mesh_a, mesh_b); + free(outdata); +} diff --git a/test/channels-buffer-storage.c b/test/channels-buffer-storage.c new file mode 100644 index 0000000..af7b25b --- /dev/null +++ b/test/channels-buffer-storage.c @@ -0,0 +1,165 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "../src/meshlink.h" + +static struct sync_flag b_responded; +static struct sync_flag aio_finished; + +static const size_t size = 25000000; // size of data to transfer + +static void a_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) { + (void)mesh; + (void)channel; + + if(len == 5 && !memcmp(data, "Hello", 5)) { + set_sync_flag(&b_responded, true); + } +} + +static void b_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) { + assert(meshlink_channel_send(mesh, channel, data, len) == (ssize_t)len); +} + +static bool reject_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + (void)mesh; + (void)channel; + (void)port; + (void)data; + (void)len; + + return false; +} + +static bool accept_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + printf("accept_cb: (from %s on port %u) ", channel->node->name, (unsigned int)port); + + if(data) { + fwrite(data, 1, len, stdout); + } + + printf("\n"); + + if(port != 7) { + return false; + } + + meshlink_set_channel_receive_cb(mesh, channel, b_receive_cb); + meshlink_set_channel_sndbuf(mesh, channel, size); + + if(data) { + b_receive_cb(mesh, channel, data, len); + } + + return true; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + + meshlink_set_channel_poll_cb(mesh, channel, NULL); + + assert(meshlink_channel_send(mesh, channel, "Hello", 5) == 5); +} + +static void aio_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len, void *priv) { + (void)mesh; + (void)channel; + (void)data; + (void)len; + (void)priv; + + set_sync_flag(&aio_finished, true); +} + +int main(void) { + init_sync_flag(&b_responded); + init_sync_flag(&aio_finished); + + meshlink_set_log_cb(NULL, MESHLINK_INFO, log_cb); + + // Open two new meshlink instance. + + meshlink_handle_t *mesh_a, *mesh_b; + open_meshlink_pair(&mesh_a, &mesh_b, "channels-buffer-storage"); + + // Set the callbacks. + + meshlink_set_channel_accept_cb(mesh_a, reject_cb); + meshlink_set_channel_accept_cb(mesh_b, accept_cb); + + // Start both instances + + start_meshlink_pair(mesh_a, mesh_b); + + // Open a channel from a to b. + + meshlink_node_t *b = meshlink_get_node(mesh_a, "b"); + assert(b); + + meshlink_channel_t *channel = meshlink_channel_open(mesh_a, b, 7, a_receive_cb, NULL, 0); + assert(channel); + + size_t buf_size = 1024 * 1024; + char *sndbuf = malloc(1024 * 1024); + assert(sndbuf); + char *rcvbuf = malloc(1024 * 1024); + assert(rcvbuf); + + // Set external buffers + + meshlink_set_channel_sndbuf_storage(mesh_a, channel, sndbuf, buf_size); + meshlink_set_channel_rcvbuf_storage(mesh_a, channel, rcvbuf, buf_size); + + // Check that we can transition back and forth to external buffers + + meshlink_set_channel_sndbuf_storage(mesh_a, channel, NULL, 4096); + meshlink_set_channel_rcvbuf(mesh_a, channel, 4096); + + meshlink_set_channel_sndbuf_storage(mesh_a, channel, sndbuf, buf_size); + meshlink_set_channel_rcvbuf_storage(mesh_a, channel, rcvbuf, buf_size); + + // Wait for the channel to finish connecting + + meshlink_set_channel_poll_cb(mesh_a, channel, poll_cb); + assert(wait_sync_flag(&b_responded, 20)); + + // Send a lot of data + + char *outdata = malloc(size); + assert(outdata); + + for(size_t i = 0; i < size; i++) { + outdata[i] = i; + } + + char *indata = malloc(size); + assert(indata); + + assert(meshlink_channel_aio_receive(mesh_a, channel, indata, size, aio_cb, NULL)); + assert(meshlink_channel_aio_send(mesh_a, channel, outdata, size, NULL, NULL)); + assert(wait_sync_flag(&aio_finished, 20)); + assert(!memcmp(indata, outdata, size)); + + // Done + + meshlink_channel_close(mesh_a, channel); + + // Clean up. + + free(indata); + free(outdata); + free(rcvbuf); + free(sndbuf); + + close_meshlink_pair(mesh_a, mesh_b); +} diff --git a/test/channels-cornercases.c b/test/channels-cornercases.c new file mode 100644 index 0000000..ca0d9b6 --- /dev/null +++ b/test/channels-cornercases.c @@ -0,0 +1,152 @@ +#define _GNU_SOURCE + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "../src/meshlink.h" +#include "utils.h" + +static volatile bool b_responded = false; +static volatile bool b_closed = false; +static volatile size_t a_poll_cb_len; + +static void a_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) { + (void)mesh; + (void)channel; + + if(len == 5 && !memcmp(data, "Hello", 5)) { + b_responded = true; + } else if(len == 0) { + b_closed = true; + set_sync_flag(channel->priv, true); + } +} + +static void b_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) { + // Send one message back, then close the channel. + if(len) { + assert(meshlink_channel_send(mesh, channel, data, len) == (ssize_t)len); + } + + meshlink_channel_close(mesh, channel); +} + +static bool accept_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + (void)port; + + meshlink_set_channel_accept_cb(mesh, NULL); + meshlink_set_channel_receive_cb(mesh, channel, b_receive_cb); + + if(data) { + b_receive_cb(mesh, channel, data, len); + } + + return true; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + + meshlink_set_channel_poll_cb(mesh, channel, NULL); + set_sync_flag(channel->priv, true); +} + +static void poll_cb2(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)mesh; + (void)channel; + + a_poll_cb_len = len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + set_sync_flag(channel->priv, true); +} + +int main(void) { + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + meshlink_handle_t *a, *b; + open_meshlink_pair(&a, &b, "channels-cornercases"); + + // Set the callbacks. + + meshlink_set_channel_accept_cb(b, accept_cb); + + // Open a channel from a to b before starting the mesh. + + meshlink_node_t *nb = meshlink_get_node(a, "b"); + assert(nb); + + struct sync_flag channel_opened = {.flag = false}; + init_sync_flag(&channel_opened); + + meshlink_channel_t *channel = meshlink_channel_open(a, nb, 7, a_receive_cb, &channel_opened, 0); + assert(channel); + + meshlink_set_channel_poll_cb(a, channel, poll_cb); + + // Start MeshLink and wait for the channel to become connected. + start_meshlink_pair(a, b); + + assert(wait_sync_flag(&channel_opened, 20)); + + // Re-initialize everything + meshlink_channel_close(a, channel); + close_meshlink_pair(a, b); + b_responded = false; + b_closed = false; + channel_opened.flag = false; + open_meshlink_pair(&a, &b, "channels-cornercases"); + + meshlink_set_channel_accept_cb(b, accept_cb); + + start_meshlink_pair(a, b); + + // Create a channel to b + nb = meshlink_get_node(a, "b"); + assert(nb); + + channel = meshlink_channel_open(a, nb, 7, a_receive_cb, &channel_opened, 0); + assert(channel); + meshlink_set_channel_poll_cb(a, channel, poll_cb); + + assert(wait_sync_flag(&channel_opened, 20)); + + assert(!b_responded); + assert(!b_closed); + + // Send a message to b + + struct sync_flag channel_closed = {.flag = false}; + init_sync_flag(&channel_closed); + channel->priv = &channel_closed; + + assert(meshlink_channel_send(a, channel, "Hello", 5) == 5); + assert(wait_sync_flag(&channel_closed, 20)); + assert(b_responded); + assert(b_closed); + + // Try to create a second channel + + struct sync_flag channel_polled = {.flag = false}; + init_sync_flag(&channel_polled); + + meshlink_channel_t *channel2 = meshlink_channel_open(a, nb, 7, a_receive_cb, &channel_polled, 0); + assert(channel2); + meshlink_set_channel_poll_cb(a, channel2, poll_cb2); + + assert(wait_sync_flag(&channel_polled, 20)); + + assert(0 == a_poll_cb_len); + + meshlink_channel_close(a, channel); + meshlink_channel_close(a, channel2); + close_meshlink_pair(a, b); +} diff --git a/test/channels-failure.c b/test/channels-failure.c new file mode 100644 index 0000000..1ac50d3 --- /dev/null +++ b/test/channels-failure.c @@ -0,0 +1,157 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include + +#include "../src/meshlink.h" +#include "utils.h" + +static bool listen_cb(meshlink_handle_t *mesh, meshlink_node_t *node, uint16_t port) { + (void)mesh; + (void)node; + + return port == 7; +} + +static bool accept_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + (void)mesh; + (void)channel; + (void)port; + (void)data; + (void)len; + + return true; +} + +static struct sync_flag poll_flag; +static size_t poll_len; + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + meshlink_set_channel_poll_cb(mesh, channel, NULL); + poll_len = len; + set_sync_flag(&poll_flag, true); +} + +static struct sync_flag receive_flag; +static size_t receive_len; + +static void receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) { + (void)mesh; + (void)channel; + (void)data; + + receive_len = len; + set_sync_flag(&receive_flag, true); +} + +int main(void) { + init_sync_flag(&poll_flag); + init_sync_flag(&receive_flag); + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open two meshlink instances. + + meshlink_handle_t *mesh_a, *mesh_b; + open_meshlink_pair(&mesh_a, &mesh_b, "channels_failure"); + + // Set the callbacks. + + meshlink_set_channel_listen_cb(mesh_b, listen_cb); + meshlink_set_channel_accept_cb(mesh_b, accept_cb); + + // Open a channel from a to b + + meshlink_node_t *b = meshlink_get_node(mesh_a, "b"); + assert(b); + + meshlink_channel_t *channel = meshlink_channel_open(mesh_a, b, 7, receive_cb, NULL, 0); + assert(channel); + + meshlink_set_channel_poll_cb(mesh_a, channel, poll_cb); + + // Start both instances + + start_meshlink_pair(mesh_a, mesh_b); + + // Wait for the channel to be established + + assert(wait_sync_flag(&poll_flag, 10)); + assert(poll_len != 0); + + sleep(1); + + // Set a very small timeout for channels to b. + + meshlink_set_node_channel_timeout(mesh_a, b, 1); + + // Stop mesh_b. We should get a notification that the channel has closed after a while. + + meshlink_stop(mesh_b); + + assert(wait_sync_flag(&receive_flag, 5)); + assert(receive_len == 0); + + meshlink_channel_close(mesh_a, channel); + + // Try setting up a new channel while b is still down. + + reset_sync_flag(&poll_flag); + reset_sync_flag(&receive_flag); + + channel = meshlink_channel_open(mesh_a, b, 7, NULL, NULL, 0); + assert(channel); + + meshlink_set_channel_poll_cb(mesh_a, channel, poll_cb); + + assert(wait_sync_flag(&poll_flag, 5)); + assert(poll_len == 0); + + meshlink_channel_close(mesh_a, channel); + + // Restart b and create a new channel to the wrong port + + reset_sync_flag(&poll_flag); + reset_sync_flag(&receive_flag); + + meshlink_set_node_channel_timeout(mesh_a, b, 60); + + assert(meshlink_start(mesh_b)); + + channel = meshlink_channel_open(mesh_a, b, 42, receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh_a, channel, poll_cb); + assert(channel); + assert(wait_sync_flag(&poll_flag, 10)); + assert(poll_len == 0); + meshlink_channel_close(mesh_a, channel); + + // Create a channel that will be accepted + + reset_sync_flag(&poll_flag); + + channel = meshlink_channel_open(mesh_a, b, 7, receive_cb, NULL, 0); + meshlink_set_channel_poll_cb(mesh_a, channel, poll_cb); + assert(channel); + assert(wait_sync_flag(&poll_flag, 10)); + assert(poll_len != 0); + + // Close and reopen b, we should get a fast notification that the channel has been closed. + + meshlink_close(mesh_b); + mesh_b = meshlink_open("channels_failure_conf.2", "b", "channels_failure", DEV_CLASS_BACKBONE); + assert(mesh_b); + assert(meshlink_start(mesh_b)); + + assert(wait_sync_flag(&receive_flag, 10)); + assert(receive_len == 0); + + // Clean up. + + close_meshlink_pair(mesh_a, mesh_b); +} diff --git a/test/channels-fork.c b/test/channels-fork.c new file mode 100644 index 0000000..74aa84b --- /dev/null +++ b/test/channels-fork.c @@ -0,0 +1,199 @@ +#define _GNU_SOURCE 1 + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ +#include +#endif + +#include "utils.h" +#include "../src/meshlink.h" + +static struct sync_flag bar_responded; +static struct sync_flag foo_connected; +static struct sync_flag foo_gone; + +static void foo_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) { + (void)mesh; + (void)channel; + (void)len; + + if(len == 5 && !memcmp(data, "Hello", 5)) { + set_sync_flag(&bar_responded, true); + } +} + +static void bar_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcmp(node->name, "foo") && !reachable) { + set_sync_flag(&foo_gone, true); + } +} + +static void bar_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) { + // Echo the data back. + if(len) { + assert(meshlink_channel_send(mesh, channel, data, len) == (ssize_t)len); + } else { + meshlink_channel_close(mesh, channel); + } +} + +static bool accept_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + if(port != 7) { + return false; + } + + meshlink_set_node_status_cb(mesh, bar_status_cb); + meshlink_set_channel_receive_cb(mesh, channel, bar_receive_cb); + set_sync_flag(&foo_connected, true); + + if(data) { + bar_receive_cb(mesh, channel, data, len); + } + + return true; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + + meshlink_set_channel_poll_cb(mesh, channel, NULL); + + if(meshlink_channel_send(mesh, channel, "Hello", 5) != 5) { + fprintf(stderr, "Could not send whole message\n"); + } +} + +static int main1(int rfd, int wfd) { + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + assert(meshlink_destroy("channels_fork_conf.1")); + meshlink_handle_t *mesh = meshlink_open("channels_fork_conf.1", "foo", "channels-fork", DEV_CLASS_BACKBONE); + assert(mesh); + + meshlink_enable_discovery(mesh, false); + + assert(meshlink_set_canonical_address(mesh, meshlink_get_self(mesh), "localhost", NULL)); + + char *data = meshlink_export(mesh); + assert(data); + + ssize_t len = strlen(data); + assert(write(wfd, &len, sizeof(len)) == sizeof(len)); + assert(write(wfd, data, len) == len); + free(data); + + assert(read(rfd, &len, sizeof(len)) == sizeof(len)); + char indata[len + 1]; + assert(read(rfd, indata, len) == len); + indata[len] = 0; + + assert(meshlink_import(mesh, indata)); + + assert(meshlink_start(mesh)); + + // Open a channel from foo to bar. + + meshlink_node_t *bar = meshlink_get_node(mesh, "bar"); + assert(bar); + + meshlink_channel_t *channel = meshlink_channel_open(mesh, bar, 7, foo_receive_cb, NULL, 0); + assert(channel); + + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + + assert(wait_sync_flag(&bar_responded, 20)); + + meshlink_channel_close(mesh, channel); + + // Clean up. + + meshlink_close(mesh); + + return 0; +} + + +static int main2(int rfd, int wfd) { +#ifdef __linux__ + prctl(PR_SET_PDEATHSIG, SIGTERM); +#endif + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + assert(meshlink_destroy("channels_fork_conf.2")); + meshlink_handle_t *mesh = meshlink_open("channels_fork_conf.2", "bar", "channels-fork", DEV_CLASS_BACKBONE); + assert(mesh); + + meshlink_enable_discovery(mesh, false); + + assert(meshlink_set_canonical_address(mesh, meshlink_get_self(mesh), "localhost", NULL)); + + char *data = meshlink_export(mesh); + assert(data); + + ssize_t len = strlen(data); + assert(write(wfd, &len, sizeof(len)) == sizeof(len)); + assert(write(wfd, data, len) == len); + free(data); + + assert(read(rfd, &len, sizeof(len)) == sizeof(len)); + char indata[len + 1]; + assert(read(rfd, indata, len) == len); + indata[len] = 0; + + assert(meshlink_import(mesh, indata)); + + meshlink_set_channel_accept_cb(mesh, accept_cb); + + assert(meshlink_start(mesh)); + + assert(wait_sync_flag(&foo_connected, 20)); + assert(wait_sync_flag(&foo_gone, 20)); + + meshlink_close(mesh); + + return 0; +} + +static void alarm_handler(int sig) { + (void)sig; + assert(0); +} + +int main(void) { + init_sync_flag(&bar_responded); + init_sync_flag(&foo_connected); + init_sync_flag(&foo_gone); + + int fda[2], fdb[2]; + + assert(pipe(fda) != -1); + assert(pipe(fdb) != -1); + + if(!fork()) { + return main2(fdb[0], fda[1]); + } + + signal(SIGALRM, alarm_handler); + alarm(30); + assert(main1(fda[0], fdb[1]) == 0); + + int wstatus; + assert(wait(&wstatus) != -1 || errno == ECHILD); + assert(WIFEXITED(wstatus)); + assert(WEXITSTATUS(wstatus) == 0); +} diff --git a/test/channels-no-partial.c b/test/channels-no-partial.c new file mode 100644 index 0000000..43792be --- /dev/null +++ b/test/channels-no-partial.c @@ -0,0 +1,92 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include + +#include "meshlink.h" +#include "utils.h" + +static bool accept_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + (void)mesh; + (void)channel; + (void)port; + (void)data; + (void)len; + + return true; +} + +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Start two new meshlink instance. + + meshlink_handle_t *mesh_a; + meshlink_handle_t *mesh_b; + + open_meshlink_pair(&mesh_a, &mesh_b, "channels_no_partial"); + meshlink_set_channel_accept_cb(mesh_b, accept_cb); + start_meshlink_pair(mesh_a, mesh_b); + + // Open a channel + + meshlink_node_t *b = meshlink_get_node(mesh_a, "b"); + assert(b); + + meshlink_channel_t *channel = meshlink_channel_open_ex(mesh_a, b, 1, NULL, NULL, 0, MESHLINK_CHANNEL_TCP | MESHLINK_CHANNEL_NO_PARTIAL); + assert(channel); + + // Stop a to ensure we get deterministic behaviour for the channel send queue. + + meshlink_stop(mesh_a); + + // Verify that no partial sends succeed. + // If rejected sends would fit an empty send buffer, 0 should be returned, otherwise -1. + + char buf[512] = "data"; + + meshlink_set_channel_sndbuf(mesh_a, channel, 256); + assert(meshlink_channel_send(mesh_a, channel, buf, 257) == -1); + assert(meshlink_channel_send(mesh_a, channel, buf, 256) == 256); + + meshlink_set_channel_sndbuf(mesh_a, channel, 512); + assert(meshlink_channel_send(mesh_a, channel, buf, 257) == 0); + assert(meshlink_channel_send(mesh_a, channel, buf, 128) == 128); + assert(meshlink_channel_send(mesh_a, channel, buf, 129) == 0); + assert(meshlink_channel_send(mesh_a, channel, buf, 100) == 100); + assert(meshlink_channel_send(mesh_a, channel, buf, 29) == 0); + assert(meshlink_channel_send(mesh_a, channel, buf, 513) == -1); + + // Restart a to ensure it gets to flush the channel send queue. + + assert(meshlink_start(mesh_a)); + + assert_after(!meshlink_channel_get_sendq(mesh_a, channel), 30); + assert(meshlink_channel_send(mesh_a, channel, buf, 512) == 512); + + // Check that we can change the NO_PARTIAL flag + + assert_after(!meshlink_channel_get_sendq(mesh_a, channel), 30); + meshlink_set_channel_sndbuf(mesh_a, channel, 256); + assert(meshlink_channel_send(mesh_a, channel, buf, 257) == -1); + meshlink_set_channel_flags(mesh_a, channel, 0); + assert(meshlink_channel_send(mesh_a, channel, buf, 257) == 256); + + assert_after(!meshlink_channel_get_sendq(mesh_a, channel), 30); + meshlink_set_channel_flags(mesh_a, channel, MESHLINK_CHANNEL_NO_PARTIAL); + assert(meshlink_channel_send(mesh_a, channel, buf, 257) == -1); + assert(meshlink_channel_send(mesh_a, channel, buf, 256) == 256); + + // Clean up. + + close_meshlink_pair(mesh_a, mesh_b); +} diff --git a/test/channels-udp-cornercases.c b/test/channels-udp-cornercases.c new file mode 100644 index 0000000..0ae3755 --- /dev/null +++ b/test/channels-udp-cornercases.c @@ -0,0 +1,174 @@ +#define _GNU_SOURCE + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "../src/meshlink.h" +#include "utils.h" + +static struct sync_flag b_responded; +static struct sync_flag b_closed; +static size_t a_poll_cb_len; + +static void a_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) { + (void)mesh; + (void)channel; + + if(len == 5 && !memcmp(data, "Hello", 5)) { + set_sync_flag(&b_responded, true); + } else if(len == 0) { + set_sync_flag(&b_closed, true); + set_sync_flag(channel->priv, true); + } +} + +static void b_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) { + // Send one message back, then close the channel. + if(len) { + assert(meshlink_channel_send(mesh, channel, data, len) == (ssize_t)len); + } + + meshlink_channel_close(mesh, channel); +} + +static bool accept_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + (void)port; + + meshlink_set_channel_accept_cb(mesh, NULL); + meshlink_set_channel_receive_cb(mesh, channel, b_receive_cb); + + if(data) { + b_receive_cb(mesh, channel, data, len); + } + + return true; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + + meshlink_set_channel_poll_cb(mesh, channel, NULL); + set_sync_flag(channel->priv, true); +} + +static void poll_cb2(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)mesh; + (void)channel; + + a_poll_cb_len = len; + meshlink_set_channel_poll_cb(mesh, channel, NULL); + set_sync_flag(channel->priv, true); +} + +int main(void) { + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + init_sync_flag(&b_responded); + init_sync_flag(&b_closed); + + meshlink_handle_t *a, *b; + open_meshlink_pair(&a, &b, "channels-udp-cornercases"); + + // Set the callbacks. + + meshlink_set_channel_accept_cb(b, accept_cb); + + // Open a channel from a to b before starting the mesh. + + meshlink_node_t *nb = meshlink_get_node(a, "b"); + assert(nb); + + struct sync_flag channel_opened; + init_sync_flag(&channel_opened); + + meshlink_channel_t *channel = meshlink_channel_open_ex(a, nb, 7, a_receive_cb, &channel_opened, 0, MESHLINK_CHANNEL_UDP); + assert(channel); + + meshlink_set_channel_poll_cb(a, channel, poll_cb); + + // Check that the channel isn't established yet and sending a packet at this point returns 0 + assert(meshlink_channel_send(a, channel, "test", 4) == 0); + assert(wait_sync_flag(&channel_opened, 1) == false); + + // Start MeshLink and wait for the channel to become connected. + start_meshlink_pair(a, b); + + assert(wait_sync_flag(&channel_opened, 15)); + + // Re-initialize everything + meshlink_channel_close(a, channel); + close_meshlink_pair(a, b); + reset_sync_flag(&channel_opened); + reset_sync_flag(&b_responded); + reset_sync_flag(&b_closed); + open_meshlink_pair(&a, &b, "channels-udp-cornercases"); + + meshlink_set_channel_accept_cb(b, accept_cb); + + start_meshlink_pair(a, b); + + // Create a channel to b + nb = meshlink_get_node(a, "b"); + assert(nb); + + channel = meshlink_channel_open_ex(a, nb, 7, a_receive_cb, &channel_opened, 0, MESHLINK_CHANNEL_UDP); + assert(channel); + meshlink_set_channel_poll_cb(a, channel, poll_cb); + + assert(wait_sync_flag(&channel_opened, 15)); + + // Send a message to b + + struct sync_flag channel_closed; + init_sync_flag(&channel_closed); + channel->priv = &channel_closed; + + for(int i = 0; i < 10; i++) { + assert(meshlink_channel_send(a, channel, "Hello", 5) == 5); + + if(wait_sync_flag(&channel_closed, 1)) { + break; + } + } + + assert(wait_sync_flag(&channel_closed, 1)); + + wait_sync_flag(&b_responded, 1); + wait_sync_flag(&b_closed, 1); + + // Try to send data on a closed channel + + for(int i = 0; i < 10; i++) { + if(meshlink_channel_send(a, channel, "Hello", 5) == -1) { + break; + } + + assert(i != 9); + usleep(10000); + } + + // Try to create a second channel + + struct sync_flag channel_polled; + init_sync_flag(&channel_polled); + + meshlink_channel_t *channel2 = meshlink_channel_open_ex(a, nb, 7, a_receive_cb, &channel_polled, 0, MESHLINK_CHANNEL_UDP); + assert(channel2); + meshlink_set_channel_poll_cb(a, channel2, poll_cb2); + + assert(wait_sync_flag(&channel_polled, 5)); + + assert(0 == a_poll_cb_len); + + meshlink_channel_close(a, channel); + meshlink_channel_close(a, channel2); + close_meshlink_pair(a, b); +} diff --git a/test/channels-udp.c b/test/channels-udp.c new file mode 100644 index 0000000..6b71ebd --- /dev/null +++ b/test/channels-udp.c @@ -0,0 +1,188 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "../src/meshlink.h" +#include "utils.h" + +#define SMALL_SIZE 512 +#define SMALL_COUNT 2500 +#define LARGE_SIZE 131072 + +static struct sync_flag accept_flag; + +struct client { + meshlink_handle_t *mesh; + meshlink_channel_t *channel; + size_t received; + bool got_large_packet; + struct sync_flag close_flag; +}; + +static void client_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) { + assert(mesh->priv); + struct client *client = mesh->priv; + + if(!data && !len) { + set_sync_flag(&client->close_flag, true); + meshlink_channel_close(mesh, channel); + return; + } + + // We expect always the same amount of data from the server. + assert(len == 512 || len == LARGE_SIZE); + client->received += len; + + if(len == LARGE_SIZE) { + client->got_large_packet = true; + } +} + +static void status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + assert(mesh->priv); + struct client *client = mesh->priv; + + if(reachable && !strcmp(node->name, "server")) { + assert(!client->channel); + client->channel = meshlink_channel_open_ex(mesh, node, 1, client_receive_cb, NULL, 0, MESHLINK_CHANNEL_UDP); + assert(client->channel); + } +} + +static bool accept_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + (void)data; + (void)len; + + assert(port == 1); + assert(meshlink_channel_get_flags(mesh, channel) == MESHLINK_CHANNEL_UDP); + + assert(mesh->priv); + meshlink_channel_t **c = mesh->priv; + + for(int i = 0; i < 3; i++) { + if(c[i] == NULL) { + c[i] = channel; + + if(i == 2) { + set_sync_flag(&accept_flag, true); + } + + return true; + } + } + + return false; +} + +int main(void) { + init_sync_flag(&accept_flag); + + meshlink_set_log_cb(NULL, MESHLINK_WARNING, log_cb); + + // Open four new meshlink instance, the server and three peers. + + const char *names[3] = {"foo", "bar", "baz"}; + struct client clients[3]; + meshlink_channel_t *channels[3] = {NULL, NULL, NULL}; + memset(clients, 0, sizeof(clients)); + + assert(meshlink_destroy("channels_udp_conf.0")); + meshlink_handle_t *server = meshlink_open("channels_udp_conf.0", "server", "channels-udp", DEV_CLASS_BACKBONE); + assert(server); + meshlink_enable_discovery(server, false); + server->priv = channels; + meshlink_set_channel_accept_cb(server, accept_cb); + assert(meshlink_start(server)); + + for(int i = 0; i < 3; i++) { + char dir[100]; + snprintf(dir, sizeof(dir), "channels_udp_conf.%d", i + 1); + assert(meshlink_destroy(dir)); + init_sync_flag(&clients[i].close_flag); + clients[i].mesh = meshlink_open(dir, names[i], "channels-udp", DEV_CLASS_STATIONARY); + assert(clients[i].mesh); + clients[i].mesh->priv = &clients[i]; + meshlink_enable_discovery(clients[i].mesh, false); + link_meshlink_pair(server, clients[i].mesh); + meshlink_set_node_status_cb(clients[i].mesh, status_cb); + assert(meshlink_start(clients[i].mesh)); + } + + // Wait for all three channels to connect + + assert(wait_sync_flag(&accept_flag, 10)); + + for(int i = 0; i < 3; i++) { + assert(channels[i]); + assert(clients[i].channel); + } + + // Check that we can send up to 65535 bytes without errors + + char large_data[LARGE_SIZE] = ""; + + for(int i = 0; i < 3; i++) { + assert(meshlink_channel_send(server, channels[i], large_data, sizeof(large_data)) == sizeof(large_data)); + } + + // Assert that packets larger than 16 MiB are not allowed + + assert(meshlink_channel_send(server, channels[0], large_data, 16777216) == -1); + + // Stream packets from server to clients for 5 seconds at 40 Mbps (1 kB * 500 Hz) + + char data[SMALL_SIZE]; + memset(data, 'U', sizeof(data)); + + for(int j = 0; j < SMALL_COUNT; j++) { + const struct timespec req = {0, 2000000}; + nanosleep(&req, NULL); + + for(int i = 0; i < 3; i++) { + assert(meshlink_channel_send(server, channels[i], data, sizeof(data)) == sizeof(data)); + } + } + + // Shutdown the write side of the server's channels + + for(int i = 0; i < 3; i++) { + meshlink_channel_shutdown(server, channels[i], SHUT_WR); + } + + // Wait for the clients to finish reading all the data + + for(int i = 0; i < 3; i++) { + assert(wait_sync_flag(&clients[i].close_flag, 10)); + } + + // Check that the clients have received (most of) the data + + for(int i = 0; i < 3; i++) { + fprintf(stderr, "%s received %zu\n", clients[i].mesh->name, clients[i].received); + } + + for(int i = 0; i < 3; i++) { + size_t max_received = SMALL_SIZE * SMALL_COUNT + LARGE_SIZE; + assert(clients[i].received >= max_received / 2); + assert(clients[i].received <= max_received); + assert(clients[i].got_large_packet); + } + + // Clean up. + + for(int i = 0; i < 3; i++) { + meshlink_close(clients[i].mesh); + } + + meshlink_close(server); + + return 0; +} diff --git a/test/channels.c b/test/channels.c new file mode 100644 index 0000000..f8946d4 --- /dev/null +++ b/test/channels.c @@ -0,0 +1,102 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "../src/meshlink.h" + +static struct sync_flag b_responded; + +static void a_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) { + (void)mesh; + (void)channel; + + printf("a_receive_cb %zu: ", len); + fwrite(data, 1, len, stdout); + printf("\n"); + + if(len == 5 && !memcmp(data, "Hello", 5)) { + set_sync_flag(&b_responded, true); + } +} + +static void b_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) { + printf("b_receive_cb %zu: ", len); + fwrite(data, 1, len, stdout); + printf("\n"); + // Echo the data back. + assert(meshlink_channel_send(mesh, channel, data, len) == (ssize_t)len); +} + +static bool accept_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + printf("accept_cb: (from %s on port %u) ", channel->node->name, (unsigned int)port); + + if(data) { + fwrite(data, 1, len, stdout); + } + + printf("\n"); + + if(port != 7) { + return false; + } + + meshlink_set_channel_receive_cb(mesh, channel, b_receive_cb); + + if(data) { + b_receive_cb(mesh, channel, data, len); + } + + return true; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + + meshlink_set_channel_poll_cb(mesh, channel, NULL); + + assert(meshlink_channel_send(mesh, channel, "Hello", 5) == 5); +} + +int main(void) { + init_sync_flag(&b_responded); + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open two new meshlink instance. + + meshlink_handle_t *mesh_a, *mesh_b; + open_meshlink_pair(&mesh_a, &mesh_b, "channels"); + + // Set the callbacks. + + meshlink_set_channel_accept_cb(mesh_b, accept_cb); + + // Start both instances + + start_meshlink_pair(mesh_a, mesh_b); + + // Open a channel from a to b. + + meshlink_node_t *b = meshlink_get_node(mesh_a, "b"); + assert(b); + + meshlink_channel_t *channel = meshlink_channel_open(mesh_a, b, 7, a_receive_cb, NULL, 0); + assert(channel); + + meshlink_set_channel_poll_cb(mesh_a, channel, poll_cb); + assert(wait_sync_flag(&b_responded, 20)); + + meshlink_channel_abort(mesh_a, channel); + + // Clean up. + + close_meshlink_pair(mesh_a, mesh_b); +} diff --git a/test/duplicate.c b/test/duplicate.c new file mode 100644 index 0000000..df9f86b --- /dev/null +++ b/test/duplicate.c @@ -0,0 +1,78 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include + +#include "meshlink.h" +#include "utils.h" + +static struct sync_flag duplicate_detected; + +static void handle_duplicate(meshlink_handle_t *mesh, meshlink_node_t *node) { + set_sync_flag(&duplicate_detected, true); + assert(meshlink_blacklist(mesh, node)); +} + +int main(void) { + init_sync_flag(&duplicate_detected); + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open meshlink instances + + static const char *name[4] = {"foo", "bar", "baz", "foo"}; + meshlink_handle_t *mesh[4]; + + for(int i = 0; i < 4; i++) { + char dirname[100]; + snprintf(dirname, sizeof dirname, "duplicate_conf.%d", i); + + assert(meshlink_destroy(dirname)); + mesh[i] = meshlink_open(dirname, name[i], "duplicate", DEV_CLASS_BACKBONE); + assert(mesh[i]); + + assert(meshlink_set_canonical_address(mesh[i], meshlink_get_self(mesh[i]), "localhost", NULL)); + meshlink_enable_discovery(mesh[i], false); + + meshlink_set_node_duplicate_cb(mesh[i], handle_duplicate); + } + + // Link them in a chain + + char *data[4]; + + for(int i = 0; i < 4; i++) { + data[i] = meshlink_export(mesh[i]); + assert(data[i]); + } + + for(int i = 0; i < 3; i++) { + assert(meshlink_import(mesh[i], data[i + 1])); + assert(meshlink_import(mesh[i + 1], data[i])); + } + + for(int i = 0; i < 4; i++) { + free(data[i]); + } + + // Start the meshes + + for(int i = 0; i < 4; i++) { + assert(meshlink_start(mesh[i])); + } + + // Wait for the duplicate node to be detected + + assert(wait_sync_flag(&duplicate_detected, 20)); + + // Clean up + + for(int i = 0; i < 4; i++) { + meshlink_close(mesh[i]); + } +} diff --git a/test/echo-fork.c b/test/echo-fork.c new file mode 100644 index 0000000..33966da --- /dev/null +++ b/test/echo-fork.c @@ -0,0 +1,189 @@ +#define _GNU_SOURCE + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ +#include +#endif + +#include "meshlink.h" +#include "utils.h" + +/* + * To run this test case, direct a large file to strd + */ + +static struct sync_flag a_started; +static struct sync_flag a_stopped; +static struct sync_flag b_responded; + +static void a_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) { + (void)mesh; + (void)channel; + (void)data; + (void)len; + + // One way only. +} + +static void b_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) { + (void)mesh; + (void)channel; + + if(!len) { + set_sync_flag(&a_stopped, true); + meshlink_channel_close(mesh, channel); + return; + } + + assert(write(1, data, len) == (ssize_t)len); +} + +static bool accept_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) { + if(port != 7) { + return false; + } + + set_sync_flag(&a_started, true); + + meshlink_set_channel_receive_cb(mesh, channel, b_receive_cb); + + if(data) { + b_receive_cb(mesh, channel, data, len); + } + + return true; +} + +static void poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) { + (void)len; + + meshlink_set_channel_poll_cb(mesh, channel, NULL); + set_sync_flag(&b_responded, true); +} + +static int main1(void) { + close(1); + + meshlink_handle_t *mesh = meshlink_open("echo-fork_conf.1", "a", "echo-fork", DEV_CLASS_BACKBONE); + assert(mesh); + + assert(meshlink_start(mesh)); + + // Open a channel. + + meshlink_node_t *b = meshlink_get_node(mesh, "b"); + assert(b); + + meshlink_channel_t *channel = meshlink_channel_open(mesh, b, 7, a_receive_cb, NULL, 0); + assert(channel); + + meshlink_set_channel_poll_cb(mesh, channel, poll_cb); + + // read and buffer stdin + int BUF_SIZE = 1024 * 1024; + char buffer[BUF_SIZE]; + + assert(wait_sync_flag(&b_responded, 20)); + + do { + ssize_t len = read(0, buffer, BUF_SIZE); + + if(len <= 0) { + break; + } + + char *p = buffer; + + while(len > 0) { + ssize_t sent = meshlink_channel_send(mesh, channel, p, len); + + if(sent < 0) { + fprintf(stderr, "Sending message failed\n"); + return 1; + } + + if(!sent) { + usleep(100000); + } else { + len -= sent; + p += sent; + } + } + } while(true); + + meshlink_channel_close(mesh, channel); + + // Clean up. + + meshlink_close(mesh); + + return 0; +} + + +static int main2(void) { +#ifdef __linux__ + prctl(PR_SET_PDEATHSIG, SIGTERM); +#endif + + close(0); + + // Start mesh and wait for incoming channels. + + meshlink_handle_t *mesh = meshlink_open("echo-fork_conf.2", "b", "echo-fork", DEV_CLASS_BACKBONE); + assert(mesh); + + meshlink_set_channel_accept_cb(mesh, accept_cb); + + assert(meshlink_start(mesh)); + + // Let it run until a disappears. + + assert(wait_sync_flag(&a_started, 20)); + assert(wait_sync_flag(&a_stopped, 1000000)); + + // Clean up. + + meshlink_close(mesh); + + return 0; +} + + +int main(void) { + init_sync_flag(&a_started); + init_sync_flag(&a_stopped); + init_sync_flag(&b_responded); + + meshlink_set_log_cb(NULL, MESHLINK_WARNING, log_cb); + + // Initialize and exchange configuration. + + meshlink_handle_t *mesh_a, *mesh_b; + + open_meshlink_pair(&mesh_a, &mesh_b, "echo-fork"); + close_meshlink_pair(mesh_a, mesh_b); + + if(!fork()) { + return main2(); + } + + assert(main1() == 0); + + int wstatus = 0; + assert(wait(&wstatus) != -1); + assert(WIFEXITED(wstatus)); + assert(WEXITSTATUS(wstatus) == 0); +} diff --git a/test/encrypted.c b/test/encrypted.c new file mode 100644 index 0000000..ac0dfa9 --- /dev/null +++ b/test/encrypted.c @@ -0,0 +1,55 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "meshlink.h" +#include "utils.h" + +int main(void) { + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open a new meshlink instance. + + assert(meshlink_destroy("encrypted_conf")); + meshlink_handle_t *mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "right", 5); + assert(mesh); + + // Close the mesh and open it again, now with a different key. + + meshlink_close(mesh); + + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "wrong", 5); + assert(!mesh); + + // Open it again, now with the right key. + + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "right", 5); + assert(mesh); + + // That's it. + + meshlink_close(mesh); + + // Destroy the mesh. + + assert(meshlink_destroy("encrypted_conf")); + + DIR *dir = opendir("encrypted_conf"); + assert(dir); + struct dirent *ent; + + while((ent = readdir(dir))) { + fprintf(stderr, "%s\n", ent->d_name); + assert(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")); + } + + closedir(dir); +} diff --git a/test/ephemeral.c b/test/ephemeral.c new file mode 100644 index 0000000..07db7eb --- /dev/null +++ b/test/ephemeral.c @@ -0,0 +1,69 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include + +#include "meshlink.h" +#include "utils.h" + +int main(void) { + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open two ephemeral meshlink instance. + + meshlink_handle_t *mesh1 = meshlink_open_ephemeral("foo", "ephemeral", DEV_CLASS_BACKBONE); + meshlink_handle_t *mesh2 = meshlink_open_ephemeral("bar", "ephemeral", DEV_CLASS_BACKBONE); + + assert(mesh1); + assert(mesh2); + + meshlink_set_log_cb(mesh1, MESHLINK_DEBUG, log_cb); + meshlink_set_log_cb(mesh2, MESHLINK_DEBUG, log_cb); + + // Exchange data + + char *export1 = meshlink_export(mesh1); + char *export2 = meshlink_export(mesh2); + + assert(export1); + assert(export2); + + assert(meshlink_import(mesh1, export2)); + assert(meshlink_import(mesh2, export1)); + + free(export1); + free(export2); + + // Check that they know each other + + assert(meshlink_get_node(mesh1, "bar")); + assert(meshlink_get_node(mesh2, "foo")); + + // Close the ephemeral instances and reopen them. + + meshlink_close(mesh1); + meshlink_close(mesh2); + + mesh1 = meshlink_open_ephemeral("foo", "ephemeral", DEV_CLASS_BACKBONE); + mesh2 = meshlink_open_ephemeral("bar", "ephemeral", DEV_CLASS_BACKBONE); + + assert(mesh1); + assert(mesh2); + + meshlink_set_log_cb(mesh1, MESHLINK_DEBUG, log_cb); + meshlink_set_log_cb(mesh2, MESHLINK_DEBUG, log_cb); + + // Check that the nodes no longer know each other + + assert(!meshlink_get_node(mesh1, "bar")); + assert(!meshlink_get_node(mesh2, "foo")); + + // That's it. + + meshlink_close(mesh1); + meshlink_close(mesh2); +} diff --git a/test/get-all-nodes.c b/test/get-all-nodes.c new file mode 100644 index 0000000..33adb9a --- /dev/null +++ b/test/get-all-nodes.c @@ -0,0 +1,220 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "meshlink.h" +#include "utils.h" + +static struct sync_flag bar_reachable; + +static void status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(reachable && !strcmp(node->name, "bar")) { + set_sync_flag(&bar_reachable, true); + } +} + +int main(void) { + init_sync_flag(&bar_reachable); + + struct meshlink_node **nodes = NULL; + size_t nnodes = 0; + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open new meshlink instances. + + assert(meshlink_destroy("get_all_nodes_conf.1")); + assert(meshlink_destroy("get_all_nodes_conf.2")); + assert(meshlink_destroy("get_all_nodes_conf.3")); + + meshlink_handle_t *mesh[3]; + mesh[0] = meshlink_open("get_all_nodes_conf.1", "foo", "get-all-nodes", DEV_CLASS_BACKBONE); + assert(mesh[0]); + + mesh[1] = meshlink_open("get_all_nodes_conf.2", "bar", "get-all-nodes", DEV_CLASS_STATIONARY); + assert(mesh[1]); + + mesh[2] = meshlink_open("get_all_nodes_conf.3", "baz", "get-all-nodes", DEV_CLASS_STATIONARY); + assert(mesh[2]); + + // Check that we only know about ourself. + + nodes = meshlink_get_all_nodes(mesh[0], nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_self(mesh[0])); + + // We should never have been online. + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 0, -1, nodes, &nnodes); + assert(nnodes == 0); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 0, 0, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_self(mesh[0])); + + // Let nodes know about each other. + + for(int i = 0; i < 3; i++) { + meshlink_enable_discovery(mesh[i], false); + assert(meshlink_set_canonical_address(mesh[i], meshlink_get_self(mesh[i]), "localhost", NULL)); + char *data = meshlink_export(mesh[i]); + assert(data); + + for(int j = 0; j < 3; j++) { + if(i == j) { + continue; + } + + assert(meshlink_import(mesh[j], data)); + } + + free(data); + } + + // We should know about all nodes now, and their device class. + + nodes = meshlink_get_all_nodes(mesh[0], nodes, &nnodes); + assert(nnodes == 3); + + nodes = meshlink_get_all_nodes_by_dev_class(mesh[0], DEV_CLASS_BACKBONE, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_self(mesh[0])); + + nodes = meshlink_get_all_nodes_by_dev_class(mesh[0], DEV_CLASS_STATIONARY, nodes, &nnodes); + assert(nnodes == 2); + + // But no node should have been online. + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 0, -1, nodes, &nnodes); + assert(nnodes == 0); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 0, 0, nodes, &nnodes); + assert(nnodes == 3); + + // Start foo. + + time_t foo_started = time(NULL); + assert(meshlink_start(mesh[0])); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 0, -1, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_self(mesh[0])); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 0, 0, nodes, &nnodes); + assert(nnodes == 2); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], foo_started - 1, -1, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_self(mesh[0])); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 1, foo_started - 1, nodes, &nnodes); + assert(nnodes == 0); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 1, foo_started + 1, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_self(mesh[0])); + + // Start bar and wait for it to connect. + + meshlink_set_node_status_cb(mesh[0], status_cb); + + sleep(2); + assert(meshlink_start(mesh[1])); + assert(wait_sync_flag(&bar_reachable, 20)); + time_t bar_started = time(NULL); + + // Validate time ranges. + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 0, -1, nodes, &nnodes); + assert(nnodes == 2); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 0, 0, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_node(mesh[0], "baz")); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 1, foo_started + 1, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_self(mesh[0])); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], bar_started, bar_started, nodes, &nnodes); + assert(nnodes == 2); + assert(nodes[0] == meshlink_get_node(mesh[0], "bar")); + assert(nodes[1] == meshlink_get_self(mesh[0])); + + // Stop bar. + + meshlink_stop(mesh[1]); + sleep(2); + time_t bar_stopped = time(NULL); + + // Validate we can see when bar was reachable. + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], bar_stopped, bar_stopped, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_self(mesh[0])); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], bar_started, bar_started, nodes, &nnodes); + assert(nnodes == 2); + assert(nodes[0] == meshlink_get_node(mesh[0], "bar")); + assert(nodes[1] == meshlink_get_self(mesh[0])); + + // Close and restart foo, check that it remembers correctly. + + meshlink_close(mesh[0]); + sleep(2); + time_t foo_stopped = time(NULL); + mesh[0] = meshlink_open("get_all_nodes_conf.1", "foo", "get-all_nodes", DEV_CLASS_BACKBONE); + assert(mesh[0]); + + nodes = meshlink_get_all_nodes(mesh[0], nodes, &nnodes); + assert(nnodes == 3); + + nodes = meshlink_get_all_nodes_by_dev_class(mesh[0], DEV_CLASS_BACKBONE, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_self(mesh[0])); + + nodes = meshlink_get_all_nodes_by_dev_class(mesh[0], DEV_CLASS_STATIONARY, nodes, &nnodes); + assert(nnodes == 2); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 0, 0, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_node(mesh[0], "baz")); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 0, -1, nodes, &nnodes); + assert(nnodes == 2); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 1, foo_started - 1, nodes, &nnodes); + assert(nnodes == 0); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 1, foo_started + 1, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_self(mesh[0])); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], bar_started, bar_started, nodes, &nnodes); + assert(nnodes == 2); + assert(nodes[0] == meshlink_get_node(mesh[0], "bar")); + assert(nodes[1] == meshlink_get_self(mesh[0])); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], bar_stopped, bar_stopped, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_self(mesh[0])); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], foo_stopped, -1, nodes, &nnodes); + assert(nnodes == 0); + + // Clean up. + + for(int i = 0; i < 3; i++) { + meshlink_close(mesh[i]); + } +} diff --git a/test/import-export.c b/test/import-export.c new file mode 100644 index 0000000..15981ed --- /dev/null +++ b/test/import-export.c @@ -0,0 +1,126 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include + +#include "meshlink.h" +#include "utils.h" + +static struct sync_flag bar_reachable; +static struct sync_flag pmtu_flag; + +static void status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(reachable && !strcmp(node->name, "bar")) { + set_sync_flag(&bar_reachable, true); + } +} + +static void pmtu_cb(meshlink_handle_t *mesh, meshlink_node_t *node, uint16_t pmtu) { + (void)mesh; + + if(pmtu && !strcmp(node->name, "bar")) { + set_sync_flag(&pmtu_flag, true); + } +} + +int main(void) { + init_sync_flag(&bar_reachable); + init_sync_flag(&pmtu_flag); + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open two new meshlink instance. + + assert(meshlink_destroy("import_export_conf.1")); + assert(meshlink_destroy("import_export_conf.2")); + + meshlink_handle_t *mesh1 = meshlink_open("import_export_conf.1", "foo", "import-export", DEV_CLASS_BACKBONE); + assert(mesh1); + + meshlink_handle_t *mesh2 = meshlink_open("import_export_conf.2", "bar", "import-export", DEV_CLASS_BACKBONE); + assert(mesh2); + + // Disable local discovery + + meshlink_enable_discovery(mesh1, false); + meshlink_enable_discovery(mesh2, false); + + // Import and export both side's data + + assert(meshlink_set_canonical_address(mesh1, meshlink_get_self(mesh1), "localhost", NULL)); + assert(meshlink_set_canonical_address(mesh2, meshlink_get_self(mesh2), "localhost", NULL)); + + char *data = meshlink_export(mesh1); + assert(data); + + assert(meshlink_import(mesh2, data)); + free(data); + + data = meshlink_export(mesh2); + assert(data); + + assert(meshlink_import(mesh1, data)); + + // Check that importing twice is fine + assert(meshlink_import(mesh1, data)); + free(data); + + // Check that importing garbage is not fine + assert(!meshlink_import(mesh1, "Garbage\n")); + + // Check that foo knows bar, but that it is not reachable. + + time_t last_reachable; + time_t last_unreachable; + meshlink_node_t *bar = meshlink_get_node(mesh1, "bar"); + assert(bar); + assert(!meshlink_get_node_reachability(mesh1, bar, &last_reachable, &last_unreachable)); + assert(!last_reachable); + assert(!last_unreachable); + + // Start both instances + + meshlink_set_node_status_cb(mesh1, status_cb); + meshlink_set_node_pmtu_cb(mesh1, pmtu_cb); + + assert(meshlink_start(mesh1)); + assert(meshlink_start(mesh2)); + + // Wait for the two to connect. + + assert(wait_sync_flag(&bar_reachable, 10)); + + // Wait for UDP communication to become possible. + + assert(wait_sync_flag(&pmtu_flag, 10)); + + // Check that we now have reachability information + + assert(meshlink_get_node_reachability(mesh1, bar, &last_reachable, &last_unreachable)); + assert(last_reachable); + + // Stop the meshes. + + meshlink_stop(mesh1); + meshlink_stop(mesh2); + + // Check that bar is no longer reachable + + assert(!meshlink_get_node_reachability(mesh1, bar, &last_reachable, &last_unreachable)); + assert(last_reachable); + assert(last_unreachable); + assert(last_reachable <= last_unreachable); + + // Clean up. + + meshlink_close(mesh2); + meshlink_close(mesh1); +} diff --git a/test/invite-join.c b/test/invite-join.c new file mode 100644 index 0000000..5bd5d41 --- /dev/null +++ b/test/invite-join.c @@ -0,0 +1,429 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "meshlink.h" +#include "devtools.h" +#include "utils.h" + +static struct sync_flag baz_reachable; +static struct sync_flag seven_reachable; +static struct sync_flag commits_first_flag; + +static void status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(reachable && !strcmp(node->name, "baz")) { + set_sync_flag(&baz_reachable, true); + } + + if(reachable && !strcmp(node->name, "seven")) { + set_sync_flag(&seven_reachable, true); + } +} + +static void invitee_commits_first_cb(bool inviter_first) { + // Check that eight has committed foo's host config file, but foo hasn't committed eight's + assert(access("invite_join_conf.8/current/hosts/foo", F_OK) == 0); + assert(access("invite_join_conf.1/current/hosts/eight", F_OK) == -1 && errno == ENOENT); + set_sync_flag(&commits_first_flag, !inviter_first); +} + +static void inviter_commits_first_cb(bool inviter_first) { + // Check that foo has committed nine's host config file, but nine hasn't committed foo's + assert(access("invite_join_conf.1/current/hosts/nine", F_OK) == 0); + assert(access("invite_join_conf.9/current/hosts/foo", F_OK) == -1 && errno == ENOENT); + set_sync_flag(&commits_first_flag, inviter_first); +} + +int main(void) { + init_sync_flag(&baz_reachable); + init_sync_flag(&seven_reachable); + init_sync_flag(&commits_first_flag); + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + assert(meshlink_destroy("invite_join_conf.1")); + assert(meshlink_destroy("invite_join_conf.2")); + assert(meshlink_destroy("invite_join_conf.3")); + assert(meshlink_destroy("invite_join_conf.4")); + assert(meshlink_destroy("invite_join_conf.5")); + assert(meshlink_destroy("invite_join_conf.6")); + assert(meshlink_destroy("invite_join_conf.7")); + assert(meshlink_destroy("invite_join_conf.8")); + assert(meshlink_destroy("invite_join_conf.9")); + + // Open thee new meshlink instance. + + meshlink_handle_t *mesh1 = meshlink_open("invite_join_conf.1", "foo", "invite-join", DEV_CLASS_BACKBONE); + assert(mesh1); + + meshlink_handle_t *mesh2 = meshlink_open("invite_join_conf.2", "bar", "invite-join", DEV_CLASS_BACKBONE); + assert(mesh2); + + meshlink_handle_t *mesh3 = meshlink_open("invite_join_conf.3", "quux", "invite-join", DEV_CLASS_BACKBONE); + assert(mesh3); + + // Disable local discovery. + + meshlink_enable_discovery(mesh1, false); + meshlink_enable_discovery(mesh2, false); + meshlink_enable_discovery(mesh3, false); + + // Have the first instance generate invitations. + + meshlink_set_node_status_cb(mesh1, status_cb); + + assert(meshlink_set_canonical_address(mesh1, meshlink_get_self(mesh1), "localhost", NULL)); + + char *baz_url = meshlink_invite(mesh1, NULL, "baz"); + assert(baz_url); + + char *quux_url = meshlink_invite(mesh1, NULL, "quux"); + assert(quux_url); + + // Check that the second instances cannot join if it is already started + + assert(meshlink_start(mesh1)); + assert(meshlink_start(mesh2)); + meshlink_errno = MESHLINK_OK; + assert(!meshlink_join(mesh2, baz_url)); + assert(meshlink_errno = MESHLINK_EINVAL); + + // Have the second instance join the first. + + meshlink_stop(mesh2); + assert(meshlink_join(mesh2, baz_url)); + assert(meshlink_start(mesh2)); + + // Wait for the two to connect. + + assert(wait_sync_flag(&baz_reachable, 20)); + + // Wait for UDP communication to become possible. + + int pmtu = meshlink_get_pmtu(mesh1, meshlink_get_node(mesh1, "baz")); + + for(int i = 0; i < 10 && !pmtu; i++) { + sleep(1); + pmtu = meshlink_get_pmtu(mesh1, meshlink_get_node(mesh1, "baz")); + } + + assert(pmtu); + + // Check that an invitation cannot be used twice + + assert(!meshlink_join(mesh3, baz_url)); + free(baz_url); + + // Check that nodes cannot join with expired invitations + + meshlink_set_invitation_timeout(mesh1, 0); + + assert(!meshlink_join(mesh3, quux_url)); + free(quux_url); + + // Check that existing nodes cannot join another mesh + + char *corge_url = meshlink_invite(mesh3, NULL, "corge"); + assert(corge_url); + + assert(meshlink_start(mesh3)); + + meshlink_stop(mesh2); + + assert(!meshlink_join(mesh2, corge_url)); + free(corge_url); + + // Check that invitations work correctly after changing ports + + meshlink_set_invitation_timeout(mesh1, 86400); + meshlink_stop(mesh1); + meshlink_stop(mesh3); + + int oldport = meshlink_get_port(mesh1); + bool success = false; + + for(int i = 0; !success && i < 100; i++) { + success = meshlink_set_port(mesh1, 0x9000 + rand() % 0x1000); + } + + assert(success); + int newport = meshlink_get_port(mesh1); + assert(oldport != newport); + + assert(meshlink_set_canonical_address(mesh1, meshlink_get_self(mesh1), "localhost", NULL)); + + assert(meshlink_start(mesh1)); + quux_url = meshlink_invite(mesh1, NULL, "quux"); + assert(quux_url); + + // The old port should not be in the invitation URL + + char portstr[10]; + snprintf(portstr, sizeof(portstr), ":%d", oldport); + assert(!strstr(quux_url, portstr)); + + // The new port should be in the invitation URL + + snprintf(portstr, sizeof(portstr), ":%d", newport); + assert(strstr(quux_url, portstr)); + + // The invitation should work + + assert(meshlink_join(mesh3, quux_url)); + free(quux_url); + + // Check that adding duplicate addresses get removed correctly + + assert(meshlink_add_invitation_address(mesh1, "localhost", portstr + 1)); + corge_url = meshlink_invite(mesh1, NULL, "corge"); + assert(corge_url); + char *localhost = strstr(corge_url, "localhost"); + assert(localhost); + assert(!strstr(localhost + 1, "localhost")); + free(corge_url); + + // Check that resetting and adding multiple, different invitation address works + + meshlink_clear_invitation_addresses(mesh1); + assert(meshlink_add_invitation_address(mesh1, "1.invalid.", "12345")); + assert(meshlink_add_invitation_address(mesh1, "2.invalid.", NULL)); + assert(meshlink_add_invitation_address(mesh1, "3.invalid.", NULL)); + assert(meshlink_add_invitation_address(mesh1, "4.invalid.", NULL)); + assert(meshlink_add_invitation_address(mesh1, "5.invalid.", NULL)); + char *grault_url = meshlink_invite(mesh1, NULL, "grault"); + assert(grault_url); + localhost = strstr(grault_url, "localhost"); + assert(localhost); + char *invalid1 = strstr(grault_url, "1.invalid.:12345"); + assert(invalid1); + char *invalid5 = strstr(grault_url, "5.invalid."); + assert(invalid5); + + // Check that explicitly added invitation addresses come before others, in the order they were specified. + + assert(invalid1 < invalid5); + assert(invalid5 < localhost); + free(grault_url); + + // Check inviting nodes into a submesh + + assert(!meshlink_get_node_submesh(mesh1, meshlink_get_self(mesh1))); + + meshlink_handle_t *mesh4 = meshlink_open("invite_join_conf.4", "four", "invite-join", DEV_CLASS_BACKBONE); + meshlink_handle_t *mesh5 = meshlink_open("invite_join_conf.5", "five", "invite-join", DEV_CLASS_BACKBONE); + meshlink_handle_t *mesh6 = meshlink_open("invite_join_conf.6", "six", "invite-join", DEV_CLASS_BACKBONE); + assert(mesh4); + assert(mesh5); + assert(mesh6); + + meshlink_enable_discovery(mesh4, false); + meshlink_enable_discovery(mesh5, false); + meshlink_enable_discovery(mesh6, false); + + meshlink_submesh_t *submesh1 = meshlink_submesh_open(mesh1, "submesh1"); + meshlink_submesh_t *submesh2 = meshlink_submesh_open(mesh1, "submesh2"); + assert(submesh1); + assert(submesh2); + + char *four_url = meshlink_invite(mesh1, submesh1, mesh4->name); + char *five_url = meshlink_invite(mesh1, submesh1, mesh5->name); + char *six_url = meshlink_invite(mesh1, submesh2, mesh6->name); + assert(four_url); + assert(five_url); + assert(six_url); + + assert(meshlink_join(mesh4, four_url)); + assert(meshlink_join(mesh5, five_url)); + assert(meshlink_join(mesh6, six_url)); + + free(four_url); + free(five_url); + free(six_url); + + assert(meshlink_start(mesh2)); + assert(meshlink_start(mesh4)); + assert(meshlink_start(mesh5)); + assert(meshlink_start(mesh6)); + + // Check that each node knows in which submesh it is + + meshlink_submesh_t *mesh4_submesh = meshlink_get_node_submesh(mesh4, meshlink_get_self(mesh4)); + meshlink_submesh_t *mesh5_submesh = meshlink_get_node_submesh(mesh4, meshlink_get_self(mesh5)); + meshlink_submesh_t *mesh6_submesh = meshlink_get_node_submesh(mesh6, meshlink_get_self(mesh6)); + assert(mesh4_submesh); + assert(mesh5_submesh); + assert(mesh6_submesh); + assert(!strcmp(mesh4_submesh->name, "submesh1")); + assert(!strcmp(mesh5_submesh->name, "submesh1")); + assert(!strcmp(mesh6_submesh->name, "submesh2")); + + // Wait for nodes to connect, and check that foo sees the right submeshes + + sleep(2); + meshlink_node_t *mesh1_four = meshlink_get_node(mesh1, mesh4->name); + meshlink_node_t *mesh1_six = meshlink_get_node(mesh1, mesh6->name); + assert(meshlink_get_node_submesh(mesh1, meshlink_get_self(mesh1)) == NULL); + assert(meshlink_get_node_submesh(mesh1, mesh1_four) == submesh1); + assert(meshlink_get_node_submesh(mesh1, mesh1_six) == submesh2); + + // Check that the new invitees still have the right submesh information + + meshlink_node_t *mesh4_four = meshlink_get_node(mesh4, mesh4->name); + meshlink_node_t *mesh4_five = meshlink_get_node(mesh4, mesh5->name); + meshlink_node_t *mesh6_six = meshlink_get_node(mesh6, mesh6->name); + assert(meshlink_get_node_submesh(mesh4, mesh4_four) == mesh4_submesh); + assert(meshlink_get_node_submesh(mesh4, mesh4_five) == mesh4_submesh); + assert(meshlink_get_node_submesh(mesh6, mesh6_six) == mesh6_submesh); + + // Check that bar can see all the nodes in submeshes and vice versa + + assert(meshlink_get_node(mesh2, mesh4->name)); + assert(meshlink_get_node(mesh2, mesh5->name)); + assert(meshlink_get_node(mesh2, mesh6->name)); + assert(meshlink_get_node(mesh4, mesh2->name)); + assert(meshlink_get_node(mesh5, mesh2->name)); + assert(meshlink_get_node(mesh6, mesh2->name)); + + // Check that four and five can see each other + + assert(meshlink_get_node(mesh4, mesh5->name)); + assert(meshlink_get_node(mesh5, mesh4->name)); + + // Check that the nodes in different submeshes cannot see each other + + assert(!meshlink_get_node(mesh4, mesh6->name)); + assert(!meshlink_get_node(mesh5, mesh6->name)); + assert(!meshlink_get_node(mesh6, mesh4->name)); + assert(!meshlink_get_node(mesh6, mesh5->name)); + + // Check that bar sees the right submesh information for the nodes in submeshes + + meshlink_submesh_t *mesh2_four_submesh = meshlink_get_node_submesh(mesh2, meshlink_get_node(mesh2, mesh4->name)); + meshlink_submesh_t *mesh2_five_submesh = meshlink_get_node_submesh(mesh2, meshlink_get_node(mesh2, mesh5->name)); + meshlink_submesh_t *mesh2_six_submesh = meshlink_get_node_submesh(mesh2, meshlink_get_node(mesh2, mesh6->name)); + assert(mesh2_four_submesh); + assert(mesh2_five_submesh); + assert(mesh2_six_submesh); + assert(!strcmp(mesh2_four_submesh->name, "submesh1")); + assert(!strcmp(mesh2_five_submesh->name, "submesh1")); + assert(!strcmp(mesh2_six_submesh->name, "submesh2")); + + // Test case #2: check invalid parameters + + meshlink_handle_t *mesh7 = meshlink_open("invite_join_conf.7", "seven", "invite-join", DEV_CLASS_BACKBONE); + assert(mesh7); + meshlink_enable_discovery(mesh7, false); + char *seven_url = meshlink_invite(mesh1, NULL, "seven"); + assert(seven_url); + + meshlink_errno = MESHLINK_OK; + assert(!meshlink_invite(NULL, NULL, "seven")); + assert(meshlink_errno == MESHLINK_EINVAL); + + meshlink_errno = MESHLINK_OK; + assert(!meshlink_invite(mesh1, NULL, NULL)); + assert(meshlink_errno == MESHLINK_EINVAL); + + meshlink_errno = MESHLINK_OK; + assert(!meshlink_invite(mesh1, NULL, "")); + assert(meshlink_errno == MESHLINK_EINVAL); + + meshlink_errno = MESHLINK_OK; + assert(!meshlink_join(NULL, seven_url)); + assert(meshlink_errno == MESHLINK_EINVAL); + + meshlink_errno = MESHLINK_OK; + assert(!meshlink_join(mesh7, NULL)); + assert(meshlink_errno == MESHLINK_EINVAL); + + meshlink_errno = MESHLINK_OK; + assert(!meshlink_join(mesh7, "")); + assert(meshlink_errno == MESHLINK_EINVAL); + + // Test case #3 and #4: check persistence of inviter and invitee + + assert(meshlink_join(mesh7, seven_url)); + free(seven_url); + meshlink_close(mesh1); + meshlink_stop(mesh2); + meshlink_stop(mesh3); + meshlink_stop(mesh4); + meshlink_stop(mesh5); + meshlink_stop(mesh6); + meshlink_close(mesh7); + mesh1 = meshlink_open("invite_join_conf.1", "foo", "invite-join", DEV_CLASS_BACKBONE); + mesh7 = meshlink_open("invite_join_conf.7", "seven", "invite-join", DEV_CLASS_BACKBONE); + assert(mesh1); + assert(mesh7); + meshlink_enable_discovery(mesh1, false); + meshlink_enable_discovery(mesh7, false); + meshlink_set_node_status_cb(mesh1, status_cb); + assert(meshlink_start(mesh1)); + assert(meshlink_start(mesh7)); + assert(wait_sync_flag(&seven_reachable, 5)); + meshlink_stop(mesh7); + + // Test case #6 and #7: check invalid inviter_commits_first combinations + + meshlink_handle_t *mesh8 = meshlink_open("invite_join_conf.8", "eight", "invite-join", DEV_CLASS_BACKBONE); + assert(mesh8); + meshlink_enable_discovery(mesh8, false); + char *eight_url = meshlink_invite(mesh1, NULL, "eight"); + assert(eight_url); + meshlink_set_inviter_commits_first(mesh1, true); + meshlink_set_inviter_commits_first(mesh8, false); + assert(!meshlink_join(mesh8, eight_url)); + free(eight_url); + + eight_url = meshlink_invite(mesh1, NULL, "eight"); + meshlink_set_inviter_commits_first(mesh1, false); + meshlink_set_inviter_commits_first(mesh8, true); + assert(!meshlink_join(mesh8, eight_url)); + free(eight_url); + + // Test case #5: test invitee committing first scenario + + eight_url = meshlink_invite(mesh1, NULL, "eight"); + meshlink_set_inviter_commits_first(mesh1, false); + meshlink_set_inviter_commits_first(mesh8, false); + devtool_set_inviter_commits_first = invitee_commits_first_cb; + assert(meshlink_join(mesh8, eight_url)); + free(eight_url); + assert(wait_sync_flag(&commits_first_flag, 5)); + + // Test case #6: test inviter committing first scenario + + meshlink_handle_t *mesh9 = meshlink_open("invite_join_conf.9", "nine", "invite-join", DEV_CLASS_BACKBONE); + assert(mesh9); + meshlink_enable_discovery(mesh9, false); + char *nine_url = meshlink_invite(mesh1, NULL, "nine"); + meshlink_set_inviter_commits_first(mesh1, true); + meshlink_set_inviter_commits_first(mesh9, true); + devtool_set_inviter_commits_first = inviter_commits_first_cb; + reset_sync_flag(&commits_first_flag); + assert(meshlink_join(mesh9, nine_url)); + free(nine_url); + assert(wait_sync_flag(&commits_first_flag, 5)); + + // Clean up. + + meshlink_close(mesh9); + meshlink_close(mesh8); + meshlink_close(mesh7); + meshlink_close(mesh6); + meshlink_close(mesh5); + meshlink_close(mesh4); + meshlink_close(mesh3); + meshlink_close(mesh2); + meshlink_close(mesh1); +} diff --git a/test/meta-connections.c b/test/meta-connections.c new file mode 100644 index 0000000..22d7c21 --- /dev/null +++ b/test/meta-connections.c @@ -0,0 +1,89 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include + +#include "meshlink.h" +#include "devtools.h" +#include "netns_utils.h" +#include "utils.h" + +static struct sync_flag peer_reachable; +static struct sync_flag peer_unreachable; + +static void nut_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(!strcmp(node->name, "peer")) { + set_sync_flag(reachable ? &peer_reachable : &peer_unreachable, true); + } +} + +int main(void) { + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + init_sync_flag(&peer_reachable); + init_sync_flag(&peer_unreachable); + + // Set up relay, peer and NUT + peer_config_t *peers = setup_relay_peer_nut("metaconn"); + + // Wait for peer to connect to NUT + devtool_set_meta_status_cb(peers[2].mesh, nut_status_cb); + + for(int i = 0; i < 3; i++) { + assert(meshlink_start(peers[i].mesh)); + } + + assert(wait_sync_flag(&peer_reachable, 5)); + + // Test case #1: re-connection to peer after disconnection when connected to the relay node + + // Stop the peer and wait for it to become unreachable + reset_sync_flag(&peer_unreachable); + meshlink_stop(peers[1].mesh); + assert(wait_sync_flag(&peer_unreachable, 5)); + + // Restart the peer and wait for it to become reachable + reset_sync_flag(&peer_reachable); + assert(meshlink_start(peers[1].mesh)); + assert(wait_sync_flag(&peer_reachable, 5)); + + // Test case #2: re-connection to peer after changing peer and NUT's IP address simultaneously, + // while connected to the relay + + reset_sync_flag(&peer_reachable); + reset_sync_flag(&peer_unreachable); + + for(int i = 1; i < 3; i++) { + change_peer_ip(&peers[i]); + } + + for(int i = 1; i < 3; i++) { + meshlink_reset_timers(peers[i].mesh); + } + + assert(wait_sync_flag(&peer_unreachable, 75)); + assert(wait_sync_flag(&peer_reachable, 15)); + + // Test case #3: re-connect to peer after stopping NUT and changing peer's IP address, no relay + reset_sync_flag(&peer_unreachable); + + for(int i = 0; i < 2; i++) { + meshlink_stop(peers[i].mesh); + } + + change_peer_ip(&peers[1]); + assert(wait_sync_flag(&peer_unreachable, 15)); + + reset_sync_flag(&peer_reachable); + assert(meshlink_start(peers[1].mesh)); + assert(wait_sync_flag(&peer_reachable, 60)); + + // Done. + + close_relay_peer_nut(peers); +} diff --git a/test/netns_utils.c b/test/netns_utils.c new file mode 100644 index 0000000..d957d5b --- /dev/null +++ b/test/netns_utils.c @@ -0,0 +1,148 @@ +#define _GNU_SOURCE 1 + +#ifndef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include + +#include "../src/meshlink.h" +#include "netns_utils.h" +#include "utils.h" + +static int ip = 1; + +/// Create meshlink instances and network namespaces for a list of peers +static void create_peers(peer_config_t *peers, int npeers, const char *prefix) { + // We require root for network namespaces + if(getuid() != 0) { + exit(77); + } + + for(int i = 0; i < npeers; i++) { + assert(asprintf(&peers[i].netns_name, "%s%d", prefix, i) > 0); + char *command = NULL; + assert(asprintf(&command, + "/bin/ip netns delete %1$s 2>/dev/null || true;" + "/bin/ip netns add %1$s;" + "/bin/ip netns exec %1$s ip link set dev lo up;", + peers[i].netns_name)); + assert(command); + assert(system(command) == 0); + free(command); + + char *netns_path = NULL; + assert(asprintf(&netns_path, "/run/netns/%s", peers[i].netns_name)); + assert(netns_path); + peers[i].netns = open(netns_path, O_RDONLY); + assert(peers[i].netns != -1); + free(netns_path); + + char *conf_path = NULL; + assert(asprintf(&conf_path, "%s_conf.%d", prefix, i + 1) > 0); + assert(conf_path); + assert(meshlink_destroy(conf_path)); + + meshlink_open_params_t *params = meshlink_open_params_init(conf_path, peers[i].name, prefix, peers[i].devclass); + assert(params); + assert(meshlink_open_params_set_netns(params, peers[i].netns)); + + peers[i].mesh = meshlink_open_ex(params); + assert(peers[i].mesh); + free(params); + free(conf_path); + + meshlink_enable_discovery(peers[i].mesh, false); + } +} + +/// Set up a LAN topology where all peers can see each other directly +static void setup_lan_topology(peer_config_t *peers, int npeers) { + // Set up the LAN bridge + { + char *command = NULL; + assert(asprintf(&command, + "/bin/ip netns exec %1$s /bin/ip link add eth0 type bridge;" + "/bin/ip netns exec %1$s /bin/ip link set eth0 up;", + peers[0].netns_name)); + assert(command); + assert(system(command) == 0); + } + + // Add an interface to each peer that is connected to the bridge + for(int i = 1; i < npeers; i++) { + char *command = NULL; + assert(asprintf(&command, + "/bin/ip netns exec %1$s /bin/ip link add eth0 type veth peer eth%3$d netns %2$s;" + "/bin/ip netns exec %1$s /bin/ip link set dev eth0 up;" + "/bin/ip netns exec %2$s /bin/ip link set dev eth%3$d master eth0 up;", + peers[i].netns_name, peers[0].netns_name, i)); + assert(command); + assert(system(command) == 0); + free(command); + } + + // Configure addresses + for(int i = 0; i < npeers; i++) { + change_peer_ip(&peers[i]); + } +} + +/// Give a peer a unique IP address +void change_peer_ip(peer_config_t *peer) { + char *command = NULL; + assert(asprintf(&command, + "/bin/ip netns exec %1$s ip addr flush dev eth0;" + "/bin/ip netns exec %1$s ip addr add 203.0.113.%2$d/24 dev eth0;", + peer->netns_name, ip)); + ip++; + assert(command); + assert(system(command) == 0); + free(command); +} + +/// Let the first peer in a list invite all the subsequent peers +static void invite_peers(peer_config_t *peers, int npeers) { + assert(meshlink_start(peers[0].mesh)); + + for(int i = 1; i < npeers; i++) { + char *invitation = meshlink_invite_ex(peers[0].mesh, NULL, peers[i].name, MESHLINK_INVITE_LOCAL | MESHLINK_INVITE_NUMERIC); + assert(invitation); + assert(meshlink_join(peers[i].mesh, invitation)); + free(invitation); + } + + meshlink_stop(peers[0].mesh); +} + +/// Close meshlink instances and clean up +static void close_peers(peer_config_t *peers, int npeers) { + for(int i = 0; i < npeers; i++) { + meshlink_close(peers[i].mesh); + close(peers[i].netns); + free(peers[i].netns_name); + } +} + +/// Set up relay, peer and NUT that are directly connected +peer_config_t *setup_relay_peer_nut(const char *prefix) { + static peer_config_t peers[] = { + {"relay", DEV_CLASS_BACKBONE}, + {"peer", DEV_CLASS_STATIONARY}, + {"nut", DEV_CLASS_STATIONARY}, + }; + + create_peers(peers, 3, prefix); + setup_lan_topology(peers, 3); + invite_peers(peers, 3); + + return peers; +} + +void close_relay_peer_nut(peer_config_t *peers) { + close_peers(peers, 3); +} diff --git a/test/netns_utils.h b/test/netns_utils.h new file mode 100644 index 0000000..7bbe51a --- /dev/null +++ b/test/netns_utils.h @@ -0,0 +1,17 @@ +#ifndef MESHLINK_TEST_NETNS_UTILS_H +#define MESHLINK_TEST_NETNS_UTILS_H + +typedef struct peer_config { + const char *name; + const dev_class_t devclass; + + char *netns_name; + int netns; + meshlink_handle_t *mesh; +} peer_config_t; + +extern void change_peer_ip(peer_config_t *peer); +extern peer_config_t *setup_relay_peer_nut(const char *prefix); +extern void close_relay_peer_nut(peer_config_t *peers); + +#endif diff --git a/test/run_blackbox_tests.sh b/test/run_blackbox_tests.sh new file mode 100755 index 0000000..4bd9dd5 --- /dev/null +++ b/test/run_blackbox_tests.sh @@ -0,0 +1,11 @@ +#!/bin/sh +meshlinkrootpath=$(realpath ${0%/*}/..) +host=$(hostname) +lxcpath=$(lxc-config lxc.lxcpath) +lxcbridge="lxcbr0" +ethifname=$(ip route show default | awk '{print $5}' | head -1) +arch=$(dpkg --print-architecture) + +test -f $HOME/.config/meshlink_blackbox.conf && . $HOME/.config/meshlink_blackbox.conf + +${0%/*}/blackbox/run_blackbox_tests/run_blackbox_tests ${meshlinkrootpath} ${lxcpath} ${lxcbridge} ${ethifname} ${arch} 2> run_blackbox_test_cases.log diff --git a/test/sign-verify.c b/test/sign-verify.c new file mode 100644 index 0000000..a9ac115 --- /dev/null +++ b/test/sign-verify.c @@ -0,0 +1,51 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include + +#include "meshlink.h" +#include "utils.h" + +int main(void) { + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open two new meshlink instance. + + meshlink_handle_t *mesh_a, *mesh_b; + open_meshlink_pair(&mesh_a, &mesh_b, "sign_verify"); + + // Verify that a signature made on one node can be verified by its peer. + + static const char testdata1[] = "Test data 1."; + static const char testdata2[] = "Test data 2."; + + char sig[MESHLINK_SIGLEN * 2]; + size_t siglen = sizeof(sig) * 2; + + assert(meshlink_sign(mesh_a, testdata1, sizeof(testdata1), sig, &siglen)); + assert(siglen == MESHLINK_SIGLEN); + + meshlink_node_t *a = meshlink_get_node(mesh_b, "a"); + assert(a); + + meshlink_node_t *b = meshlink_get_node(mesh_b, "b"); + assert(b); + + assert(meshlink_verify(mesh_b, a, testdata1, sizeof(testdata1), sig, siglen)); + + // Check that bad signatures are revoked + + assert(!meshlink_verify(mesh_b, a, testdata1, sizeof(testdata1), sig, siglen / 2)); + assert(!meshlink_verify(mesh_b, a, testdata1, sizeof(testdata1), sig, siglen * 2)); + assert(!meshlink_verify(mesh_b, a, testdata2, sizeof(testdata2), sig, siglen)); + assert(!meshlink_verify(mesh_b, b, testdata1, sizeof(testdata1), sig, siglen)); + + // Clean up. + + close_meshlink_pair(mesh_a, mesh_b); +} diff --git a/test/storage-policy.c b/test/storage-policy.c new file mode 100644 index 0000000..a9aa5f4 --- /dev/null +++ b/test/storage-policy.c @@ -0,0 +1,211 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include + +#include "meshlink.h" +#include "utils.h" + +int main(void) { + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + meshlink_handle_t *mesh1; + meshlink_handle_t *mesh2; + + // Open two instances + + assert(meshlink_destroy("storage-policy_conf.1")); + assert(meshlink_destroy("storage-policy_conf.2")); + + mesh1 = meshlink_open("storage-policy_conf.1", "foo", "storage-policy", DEV_CLASS_BACKBONE); + mesh2 = meshlink_open("storage-policy_conf.2", "bar", "storage-policy", DEV_CLASS_BACKBONE); + assert(mesh1); + assert(mesh2); + meshlink_enable_discovery(mesh1, false); + meshlink_enable_discovery(mesh2, false); + meshlink_set_storage_policy(mesh1, MESHLINK_STORAGE_DISABLED); + meshlink_set_storage_policy(mesh2, MESHLINK_STORAGE_DISABLED); + + // Exchange data + + char *export1 = meshlink_export(mesh1); + char *export2 = meshlink_export(mesh2); + + assert(export1); + assert(export2); + + assert(meshlink_import(mesh1, export2)); + assert(meshlink_import(mesh2, export1)); + + // Check that they know each other + + assert(meshlink_get_node(mesh1, "bar")); + assert(meshlink_get_node(mesh2, "foo")); + + start_meshlink_pair(mesh1, mesh2); + + // Close the instances and reopen them. + + close_meshlink_pair(mesh1, mesh2); + + mesh1 = meshlink_open("storage-policy_conf.1", "foo", "storage-policy", DEV_CLASS_BACKBONE); + mesh2 = meshlink_open("storage-policy_conf.2", "bar", "storage-policy", DEV_CLASS_BACKBONE); + assert(mesh1); + assert(mesh2); + meshlink_enable_discovery(mesh1, false); + meshlink_enable_discovery(mesh2, false); + meshlink_set_storage_policy(mesh1, MESHLINK_STORAGE_KEYS_ONLY); + meshlink_set_storage_policy(mesh2, MESHLINK_STORAGE_KEYS_ONLY); + + // Check that the nodes no longer know each other + + assert(!meshlink_get_node(mesh1, "bar")); + assert(!meshlink_get_node(mesh2, "foo")); + + // Exchange data again + + assert(meshlink_import(mesh1, export2)); + assert(meshlink_import(mesh2, export1)); + + free(export1); + free(export2); + + // Close the instances and reopen them. + + close_meshlink_pair(mesh1, mesh2); + + mesh1 = meshlink_open("storage-policy_conf.1", "foo", "storage-policy", DEV_CLASS_BACKBONE); + mesh2 = meshlink_open("storage-policy_conf.2", "bar", "storage-policy", DEV_CLASS_BACKBONE); + assert(mesh1); + assert(mesh2); + meshlink_enable_discovery(mesh1, false); + meshlink_enable_discovery(mesh2, false); + meshlink_set_storage_policy(mesh1, MESHLINK_STORAGE_KEYS_ONLY); + meshlink_set_storage_policy(mesh2, MESHLINK_STORAGE_KEYS_ONLY); + + // Check that the nodes know each other + + assert(meshlink_get_node(mesh1, "bar")); + assert(meshlink_get_node(mesh2, "foo")); + + // Check that we update reachability + + time_t last_reachable; + time_t last_unreachable; + assert(!meshlink_get_node_reachability(mesh1, meshlink_get_node(mesh1, "bar"), &last_reachable, &last_unreachable)); + assert(!last_reachable); + assert(!last_unreachable); + + start_meshlink_pair(mesh1, mesh2); + stop_meshlink_pair(mesh1, mesh2); + + assert(!meshlink_get_node_reachability(mesh1, meshlink_get_node(mesh1, "bar"), &last_reachable, &last_unreachable)); + assert(last_reachable); + assert(last_unreachable); + + // But have not stored it + + close_meshlink_pair(mesh1, mesh2); + + mesh1 = meshlink_open("storage-policy_conf.1", "foo", "storage-policy", DEV_CLASS_BACKBONE); + mesh2 = meshlink_open("storage-policy_conf.2", "bar", "storage-policy", DEV_CLASS_BACKBONE); + assert(mesh1); + assert(mesh2); + meshlink_enable_discovery(mesh1, false); + meshlink_enable_discovery(mesh2, false); + meshlink_set_storage_policy(mesh1, MESHLINK_STORAGE_KEYS_ONLY); + meshlink_set_storage_policy(mesh2, MESHLINK_STORAGE_KEYS_ONLY); + + assert(meshlink_get_node(mesh1, "bar")); + assert(meshlink_get_node(mesh2, "foo")); + + assert(!meshlink_get_node_reachability(mesh1, meshlink_get_node(mesh1, "bar"), &last_reachable, &last_unreachable)); + assert(!last_reachable); + assert(!last_unreachable); + + // Check that if we change back to STORAGE_ENABLED right before closing, pending changes are still saved + + start_meshlink_pair(mesh1, mesh2); + stop_meshlink_pair(mesh1, mesh2); + + meshlink_set_storage_policy(mesh1, MESHLINK_STORAGE_ENABLED); + meshlink_set_storage_policy(mesh2, MESHLINK_STORAGE_ENABLED); + + close_meshlink_pair(mesh1, mesh2); + + mesh1 = meshlink_open("storage-policy_conf.1", "foo", "storage-policy", DEV_CLASS_BACKBONE); + mesh2 = meshlink_open("storage-policy_conf.2", "bar", "storage-policy", DEV_CLASS_BACKBONE); + assert(mesh1); + assert(mesh2); + meshlink_enable_discovery(mesh1, false); + meshlink_enable_discovery(mesh2, false); + + assert(!meshlink_get_node_reachability(mesh1, meshlink_get_node(mesh1, "bar"), &last_reachable, &last_unreachable)); + assert(last_reachable); + assert(last_unreachable); + + // Start again from scratch, now use invite/join instead of import/export + + close_meshlink_pair(mesh1, mesh2); + + assert(meshlink_destroy("storage-policy_conf.1")); + assert(meshlink_destroy("storage-policy_conf.2")); + + mesh1 = meshlink_open("storage-policy_conf.1", "foo", "storage-policy", DEV_CLASS_BACKBONE); + mesh2 = meshlink_open("storage-policy_conf.2", "bar", "storage-policy", DEV_CLASS_BACKBONE); + assert(mesh1); + assert(mesh2); + meshlink_enable_discovery(mesh1, false); + meshlink_enable_discovery(mesh2, false); + meshlink_set_storage_policy(mesh1, MESHLINK_STORAGE_DISABLED); + meshlink_set_storage_policy(mesh2, MESHLINK_STORAGE_DISABLED); + + // Check that joining is not possible with storage disabled + + assert(meshlink_set_canonical_address(mesh1, meshlink_get_self(mesh1), "localhost", NULL)); + char *invitation = meshlink_invite(mesh1, NULL, "bar"); + assert(invitation); + assert(meshlink_start(mesh1)); + assert(!meshlink_join(mesh2, invitation)); + assert(meshlink_errno == MESHLINK_EINVAL); + meshlink_stop(mesh1); + + // Try again with KEYS_ONLY + + meshlink_set_storage_policy(mesh1, MESHLINK_STORAGE_KEYS_ONLY); + meshlink_set_storage_policy(mesh2, MESHLINK_STORAGE_KEYS_ONLY); + + assert(meshlink_start(mesh1)); + assert(meshlink_join(mesh2, invitation)); + assert(meshlink_errno == MESHLINK_EINVAL); + meshlink_stop(mesh1); + + start_meshlink_pair(mesh1, mesh2); + + // Close the instances and reopen them. + + close_meshlink_pair(mesh1, mesh2); + + mesh1 = meshlink_open("storage-policy_conf.1", "foo", "storage-policy", DEV_CLASS_BACKBONE); + mesh2 = meshlink_open("storage-policy_conf.2", "bar", "storage-policy", DEV_CLASS_BACKBONE); + assert(mesh1); + assert(mesh2); + meshlink_enable_discovery(mesh1, false); + meshlink_enable_discovery(mesh2, false); + meshlink_set_storage_policy(mesh1, MESHLINK_STORAGE_KEYS_ONLY); + meshlink_set_storage_policy(mesh2, MESHLINK_STORAGE_KEYS_ONLY); + + // Check that the nodes know each other + + assert(meshlink_get_node(mesh1, "bar")); + assert(meshlink_get_node(mesh2, "foo")); + + // Done. + + close_meshlink_pair(mesh1, mesh2); + free(invitation); +} diff --git a/test/stream.c b/test/stream.c new file mode 100644 index 0000000..6176b9e --- /dev/null +++ b/test/stream.c @@ -0,0 +1,157 @@ +#define _GNU_SOURCE +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) { + static const struct option longopts[] = { + {"verify", 0, NULL, 'v'}, + {"rate", 1, NULL, 'r'}, + {"fps", 1, NULL, 'f'}, + {"total", 1, NULL, 't'}, + {NULL, 0, NULL, 0}, + }; + + int opt; + bool verify = false; + float rate = 1e6; + float fps = 60; + float total = 1.0 / 0.0; + + while((opt = getopt_long(argc, argv, "vr:f:t:", longopts, &optind)) != -1) { + switch(opt) { + case 'v': + verify = true; + break; + + case 'r': + rate = atof(optarg); + break; + + case 'f': + fps = atof(optarg); + break; + + case 't': + total = atof(optarg); + break; + + default: + fprintf(stderr, "Usage: %s [-v] [-r bitrate] [-f frames_per_second]\n", argv[0]); + return 1; + } + } + + size_t framesize = rate / fps / 8; + framesize &= ~0xf; + long interval = 1e9 / fps; + + if(!framesize || interval <= 0) { + err(1, "invalid parameters"); + } + + char *buf = malloc(framesize + 16); + + if(!buf) { + err(1, "malloc(%zu)", framesize); + } + + uint64_t counter = 0; + struct timespec now, next = {0}; + clock_gettime(CLOCK_REALTIME, &now); + + while(total > 0) { + if(!verify) { + size_t tosend = framesize; + char *p = buf; + + memcpy(buf, &now, sizeof(now)); + clock_gettime(CLOCK_REALTIME, &now); + + for(uint64_t *q = (uint64_t *)(buf + sizeof(now)); (char *)q < buf + framesize; q++) { + *q = counter++; + } + + while(tosend) { + ssize_t sent = write(1, p, tosend); + + if(sent <= 0) { + err(1, "write(1, %p, %zu)", (void *)p, tosend); + } + + tosend -= sent; + p += sent; + } + + next.tv_sec = 0; + next.tv_nsec = interval; + + while(next.tv_nsec >= 1000000000) { + next.tv_nsec -= 1000000000; + next.tv_sec++; + } + + nanosleep(&next, NULL); + total -= framesize; + } else { + struct timespec *ts = (struct timespec *)buf; + size_t toread = sizeof(*ts); + char *p = buf; + + while(toread) { + ssize_t result = read(0, p, toread); + + if(result <= 0) { + err(1, "read(1, %p, %zu)", (void *)p, toread); + } + + toread -= result; + p += result; + } + + clock_gettime(CLOCK_REALTIME, &now); + + toread = framesize - sizeof(now); + + while(toread) { + ssize_t result = read(0, p, toread); + + if(result <= 0) { + err(1, "read(1, %p, %zu)", (void *)p, toread); + } + + toread -= result; + p += result; + } + + clock_gettime(CLOCK_REALTIME, &next); + + for(uint64_t *q = (uint64_t *)(buf + sizeof(now)); (char *)q < buf + framesize; q++) { + if(*q != counter++) { + uint64_t offset = (counter - 1) * 8; + offset += ((counter * 8) / (framesize - sizeof(now))) * sizeof(now); + err(1, "verification failed at offset %lu", (unsigned long)offset); + } + } + + float dt1 = now.tv_sec - ts->tv_sec + 1e-9 * (now.tv_nsec - ts->tv_nsec); + float dt2 = next.tv_sec - now.tv_sec + 1e-9 * (next.tv_nsec - now.tv_nsec); + + fprintf(stderr, "\rDelay: %8.3f ms, burst bandwidth: %8.0f Mbit/s", dt1 * 1e3, (framesize - sizeof(now)) / dt2 * 8 / 1e6); + + total -= framesize; + } + } + + if(verify) { + fprintf(stderr, "\n"); + } +} diff --git a/test/trio.c b/test/trio.c new file mode 100644 index 0000000..bdb3904 --- /dev/null +++ b/test/trio.c @@ -0,0 +1,184 @@ +#define _GNU_SOURCE + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "meshlink.h" +#include "devtools.h" +#include "utils.h" + +static struct sync_flag received; +static struct sync_flag bar_learned_baz; +static struct sync_flag baz_learned_bar; + +static void receive_cb(meshlink_handle_t *mesh, meshlink_node_t *source, const void *data, size_t len) { + (void)mesh; + (void)source; + + if(len == 5 && !memcmp(data, "Hello", 5)) { + set_sync_flag(&received, true); + } +} + +static void bar_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + (void)reachable; + + if(!strcmp(node->name, "baz")) { + set_sync_flag(&bar_learned_baz, true); + } +} + +static void baz_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + (void)reachable; + + if(!strcmp(node->name, "bar")) { + set_sync_flag(&baz_learned_bar, true); + } +} + +int main(void) { + init_sync_flag(&received); + init_sync_flag(&bar_learned_baz); + init_sync_flag(&baz_learned_bar); + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Create three instances. + + const char *name[3] = {"foo", "bar", "baz"}; + meshlink_handle_t *mesh[3]; + char *data[3]; + + for(int i = 0; i < 3; i++) { + char *path = NULL; + assert(asprintf(&path, "trio_conf.%d", i) != -1 && path); + + assert(meshlink_destroy(path)); + mesh[i] = meshlink_open(path, name[i], "trio", DEV_CLASS_BACKBONE); + assert(mesh[i]); + free(path); + + assert(meshlink_set_canonical_address(mesh[i], meshlink_get_self(mesh[i]), "localhost", NULL)); + + data[i] = meshlink_export(mesh[i]); + assert(data[i]); + } + + // first node knows the two other nodes + + for(int i = 1; i < 3; i++) { + assert(meshlink_import(mesh[i], data[0])); + assert(meshlink_import(mesh[0], data[i])); + + assert(meshlink_get_node(mesh[i], name[0])); + assert(meshlink_get_node(mesh[0], name[i])); + } + + // second and third node should not know each other yet + + assert(!meshlink_get_node(mesh[1], name[2])); + assert(!meshlink_get_node(mesh[2], name[1])); + + // start the nodes + + meshlink_set_node_status_cb(mesh[1], bar_status_cb); + meshlink_set_node_status_cb(mesh[2], baz_status_cb); + + for(int i = 0; i < 3; i++) { + free(data[i]); + assert(meshlink_start(mesh[i])); + } + + // the nodes should now learn about each other + + assert(wait_sync_flag(&bar_learned_baz, 5)); + assert(wait_sync_flag(&baz_learned_bar, 5)); + + // Send a packet, expect it is received + + meshlink_set_receive_cb(mesh[1], receive_cb); + + for(int i = 0; i < 15; i++) { + assert(meshlink_send(mesh[2], meshlink_get_node(mesh[2], name[1]), "Hello", 5)); + + if(wait_sync_flag(&received, 1)) { + break; + } + } + + assert(wait_sync_flag(&received, 15)); + + // Check that the second and third node have autoconnected to each other + + devtool_edge_t *edges = NULL; + size_t nedges = 0; + assert_after((edges = devtool_get_all_edges(mesh[1], edges, &nedges), nedges == 3), 15); + free(edges); + + // Stop the first node + + meshlink_stop(mesh[0]); + sleep(1); + + // Communication should still be possible + + set_sync_flag(&received, false); + + for(int i = 0; i < 15; i++) { + assert(meshlink_send(mesh[2], meshlink_get_node(mesh[2], name[1]), "Hello", 5)); + + if(wait_sync_flag(&received, 1)) { + break; + } + } + + assert(wait_sync_flag(&received, 15)); + + // Stop the other nodes + + for(int i = 1; i < 3; i++) { + meshlink_stop(mesh[i]); + } + + sleep(1); + + // Start just the other two nodes + + for(int i = 1; i < 3; i++) { + assert(meshlink_start(mesh[i])); + } + + assert(meshlink_get_node(mesh[1], name[2])); + assert(meshlink_get_node(mesh[2], name[1])); + + // Communication should still be possible + + set_sync_flag(&received, false); + + for(int i = 0; i < 15; i++) { + assert(meshlink_send(mesh[2], meshlink_get_node(mesh[2], name[1]), "Hello", 5)); + + if(wait_sync_flag(&received, 1)) { + break; + } + } + + assert(wait_sync_flag(&received, 1)); + + // Clean up. + + for(int i = 0; i < 3; i++) { + meshlink_close(mesh[i]); + } +} diff --git a/test/trio2.c b/test/trio2.c new file mode 100644 index 0000000..dc57679 --- /dev/null +++ b/test/trio2.c @@ -0,0 +1,149 @@ +#define _GNU_SOURCE + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "meshlink.h" +#include "devtools.h" +#include "utils.h" + +static struct sync_flag received; +static struct sync_flag bar_learned_baz; +static struct sync_flag baz_learned_bar; + +static void receive_cb(meshlink_handle_t *mesh, meshlink_node_t *source, const void *data, size_t len) { + (void)mesh; + (void)source; + + if(len == 5 && !memcmp(data, "Hello", 5)) { + set_sync_flag(&received, true); + } +} + +static void bar_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + (void)reachable; + + if(!strcmp(node->name, "baz")) { + set_sync_flag(&bar_learned_baz, true); + } +} + +static void baz_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + (void)reachable; + + if(!strcmp(node->name, "bar")) { + set_sync_flag(&baz_learned_bar, true); + } +} + +int main(void) { + init_sync_flag(&received); + init_sync_flag(&bar_learned_baz); + init_sync_flag(&baz_learned_bar); + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Create three instances. + + const char *name[3] = {"foo", "bar", "baz"}; + meshlink_handle_t *mesh[3]; + char *data[3]; + + for(int i = 0; i < 3; i++) { + char *path = NULL; + assert(asprintf(&path, "trio2_conf.%d", i) != -1 && path); + + assert(meshlink_destroy(path)); + mesh[i] = meshlink_open(path, name[i], "trio2", DEV_CLASS_BACKBONE); + assert(mesh[i]); + free(path); + + assert(meshlink_set_canonical_address(mesh[i], meshlink_get_self(mesh[i]), "localhost", NULL)); + + data[i] = meshlink_export(mesh[i]); + assert(data[i]); + } + + // first node knows the two other nodes + + for(int i = 1; i < 3; i++) { + assert(meshlink_import(mesh[i], data[0])); + assert(meshlink_import(mesh[0], data[i])); + + assert(meshlink_get_node(mesh[i], name[0])); + assert(meshlink_get_node(mesh[0], name[i])); + } + + // second and third node should not know each other yet + + assert(!meshlink_get_node(mesh[1], name[2])); + assert(!meshlink_get_node(mesh[2], name[1])); + + // start the nodes + + meshlink_set_node_status_cb(mesh[1], bar_status_cb); + meshlink_set_node_status_cb(mesh[2], baz_status_cb); + + for(int i = 0; i < 3; i++) { + free(data[i]); + assert(meshlink_start(mesh[i])); + } + + // the nodes should now learn about each other + + assert(wait_sync_flag(&bar_learned_baz, 5)); + assert(wait_sync_flag(&baz_learned_bar, 5)); + + // Check that the second and third node autoconnect to each other + + devtool_edge_t *edges = NULL; + size_t nedges = 0; + assert_after((edges = devtool_get_all_edges(mesh[1], edges, &nedges), nedges == 3), 15); + free(edges); + + // Stop the nodes nodes + + for(int i = 0; i < 3; i++) { + meshlink_stop(mesh[i]); + } + + // Start just the other two nodes + + for(int i = 1; i < 3; i++) { + assert(meshlink_start(mesh[i])); + } + + assert(meshlink_get_node(mesh[1], name[2])); + assert(meshlink_get_node(mesh[2], name[1])); + + // Communication should still be possible + + meshlink_set_receive_cb(mesh[1], receive_cb); + + for(int i = 0; i < 25; i++) { + assert(meshlink_send(mesh[2], meshlink_get_node(mesh[2], name[1]), "Hello", 5)); + + if(wait_sync_flag(&received, 1)) { + break; + } + } + + assert(wait_sync_flag(&received, 1)); + + // Clean up. + + for(int i = 0; i < 3; i++) { + meshlink_close(mesh[i]); + } +} diff --git a/test/utcp-benchmark b/test/utcp-benchmark new file mode 100755 index 0000000..bf277fc --- /dev/null +++ b/test/utcp-benchmark @@ -0,0 +1,80 @@ +#!/bin/bash +set -e + +# Require root permissions +test "$(id -u)" = "0" || exit 77 + +# Configuration +LOG_PREFIX=/dev/shm/utcp-benchmark-log +SIZE=10000000 + +# Network parameters +# Some realistic values: +# - Gbit LAN connection: RATE=1gbit DELAY=0.4ms JITTER=0.04ms LOSS=0% +# - Fast WAN connection: RATE=100mbit DELAY=50ms JITTER=3ms LOSS=0% +# - 5GHz WiFi connection: RATE=90mbit DELAY=5ms JITTER=1ms LOSS=0% +RATE=100mbit +DELAY=10ms +JITTER=1ms +LOSS=0.1% + +# Maximum achievable bandwidth is limited to BUFSIZE / (2 * DELAY) +# The Linux kernel has a default maximum send buffer of 4 MiB +#export BUFSIZE=4194304 + +# Remove old log files +rm -f $LOG_PREFIX-* 2>/dev/null + +# Clean up old namespaces +ip link del utcp-left 2>/dev/null || true +ip link del utcp-right 2>/dev/null || true +ip netns delete utcp-left 2>/dev/null || true +ip netns delete utcp-right 2>/dev/null || true + +# Set up the left namespace +ip netns add utcp-left +ip link add name utcp-left type veth peer name utcp-right +ip link set utcp-left netns utcp-left + +ip netns exec utcp-left ethtool -K utcp-left tso off +ip netns exec utcp-left ip link set dev lo up +ip netns exec utcp-left ip addr add dev utcp-left 192.168.1.1/24 +ip netns exec utcp-left ip link set utcp-left up + +#ip netns exec utcp-left tc qdisc del dev utcp-left root +ip netns exec utcp-left tc qdisc add dev utcp-left root netem rate $RATE delay $DELAY $JITTER loss random $LOSS + +# Set up the right namespace +ip netns add utcp-right +ip link set utcp-right netns utcp-right + +ip netns exec utcp-right ethtool -K utcp-right tso off +ip netns exec utcp-right ip link set dev lo up +ip netns exec utcp-right ip addr add dev utcp-right 192.168.1.2/24 +ip netns exec utcp-right ip link set utcp-right up + +#ip netns exec utcp-right tc qdisc del dev utcp-right root +ip netns exec utcp-right tc qdisc add dev utcp-right root netem rate $RATE delay $DELAY $JITTER loss random $LOSS +# Test using kernel TCP +ip netns exec utcp-right tcpdump -i utcp-right -w $LOG_PREFIX-socat.pcap port 9999 2>/dev/null & +ip netns exec utcp-left socat TCP4-LISTEN:9999 - >/dev/null & +sleep 0.1 +head -c $SIZE /dev/zero | ip netns exec utcp-right time socat - TCP4:192.168.1.1:9999 2>$LOG_PREFIX-socat-client.txt >/dev/null +sleep 0.1 +kill $(jobs -p) 2>/dev/null + +# Test using UTCP +ip netns exec utcp-right tcpdump -i utcp-right -w $LOG_PREFIX-utcp.pcap udp port 9999 2>/dev/null & +ip netns exec utcp-left ../src/utcp-test 9999 2>$LOG_PREFIX-server.txt >/dev/null & +sleep 0.1 +head -c $SIZE /dev/zero | ip netns exec utcp-right time ../src/utcp-test 192.168.1.1 9999 2>$LOG_PREFIX-client.txt >/dev/null +sleep 0.1 +kill $(jobs -p) 2>/dev/null + +# Print timing statistics +echo "Regular TCP:" +tail -2 $LOG_PREFIX-socat-client.txt + +echo +echo "UTCP:" +tail -3 $LOG_PREFIX-client.txt diff --git a/test/utcp-benchmark-stream b/test/utcp-benchmark-stream new file mode 100755 index 0000000..1935820 --- /dev/null +++ b/test/utcp-benchmark-stream @@ -0,0 +1,85 @@ +#!/bin/bash +set -e + +# Require root permissions +test "$(id -u)" = "0" || exit 77 + +# Configuration +LOG_PREFIX=/dev/shm/utcp-benchmark-log + +# Size in bytes +SIZE=2e6 + +# Rate of generated stream in bits/s +STREAMRATE=10e6 + +# Network parameters +# Some realistic values: +# - Gbit LAN connection: RATE=1gbit DELAY=0.4ms JITTER=0.04ms LOSS=0% +# - Fast WAN connection: RATE=100mbit DELAY=50ms JITTER=3ms LOSS=0% +# - 5GHz WiFi connection: RATE=90mbit DELAY=5ms JITTER=1ms LOSS=0% +RATE=100mbit +DELAY=10ms +JITTER=1ms +LOSS=0.1% + +# Maximum achievable bandwidth is limited to BUFSIZE / (2 * DELAY) +# The Linux kernel has a default maximum send buffer of 4 MiB +#export BUFSIZE=4194304 + +# Remove old log files +rm -f $LOG_PREFIX-* 2>/dev/null + +# Clean up old namespaces +ip link del utcp-left 2>/dev/null || true +ip link del utcp-right 2>/dev/null || true +ip netns delete utcp-left 2>/dev/null || true +ip netns delete utcp-right 2>/dev/null || true + +# Set up the left namespace +ip netns add utcp-left +ip link add name utcp-left type veth peer name utcp-right +ip link set utcp-left netns utcp-left + +ip netns exec utcp-left ethtool -K utcp-left tso off +ip netns exec utcp-left ip link set dev lo up +ip netns exec utcp-left ip addr add dev utcp-left 192.168.1.1/24 +ip netns exec utcp-left ip link set utcp-left up + +#ip netns exec utcp-left tc qdisc del dev utcp-left root +ip netns exec utcp-left tc qdisc add dev utcp-left root netem rate $RATE delay $DELAY $JITTER loss random $LOSS + +# Set up the right namespace +ip netns add utcp-right +ip link set utcp-right netns utcp-right + +ip netns exec utcp-right ethtool -K utcp-right tso off +ip netns exec utcp-right ip link set dev lo up +ip netns exec utcp-right ip addr add dev utcp-right 192.168.1.2/24 +ip netns exec utcp-right ip link set utcp-right up + +#ip netns exec utcp-right tc qdisc del dev utcp-right root +ip netns exec utcp-right tc qdisc add dev utcp-right root netem rate $RATE delay $DELAY $JITTER loss random $LOSS +# Test using kernel TCP +ip netns exec utcp-right tcpdump -i utcp-right -w $LOG_PREFIX-socat.pcap port 9999 2>/dev/null & +ip netns exec utcp-left socat TCP4-LISTEN:9999 - $LOG_PREFIX-socat-client.txt >/dev/null +sleep 0.1 +kill $(jobs -p) 2>/dev/null + +# Test using UTCP +ip netns exec utcp-right tcpdump -i utcp-right -w $LOG_PREFIX-utcp.pcap udp port 9999 2>/dev/null & +ip netns exec utcp-left ../src/utcp-test 9999 2>$LOG_PREFIX-server.txt $LOG_PREFIX-client.txt >/dev/null +sleep 0.1 +kill $(jobs -p) 2>/dev/null + +# Print timing statistics +echo "Regular TCP:" +tail -2 $LOG_PREFIX-socat-client.txt + +echo +echo "UTCP:" +tail -3 $LOG_PREFIX-client.txt diff --git a/test/utils.c b/test/utils.c new file mode 100644 index 0000000..872060b --- /dev/null +++ b/test/utils.c @@ -0,0 +1,192 @@ +#define _GNU_SOURCE + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include + +#include "utils.h" + +void init_sync_flag(struct sync_flag *s) { + assert(pthread_mutex_init(&s->mutex, NULL) == 0); + assert(pthread_cond_init(&s->cond, NULL) == 0); + s->flag = false; +} + +void set_sync_flag(struct sync_flag *s, bool value) { + assert(pthread_mutex_lock(&s->mutex) == 0); + s->flag = value; + assert(pthread_cond_broadcast(&s->cond) == 0); + assert(pthread_mutex_unlock(&s->mutex) == 0); +} + +void reset_sync_flag(struct sync_flag *s) { + assert(pthread_mutex_lock(&s->mutex) == 0); + s->flag = false; + assert(pthread_mutex_unlock(&s->mutex) == 0); +} + +bool check_sync_flag(struct sync_flag *s) { + bool flag; + assert(pthread_mutex_lock(&s->mutex) == 0); + flag = s->flag; + assert(pthread_mutex_unlock(&s->mutex) == 0); + return flag; +} + +bool wait_sync_flag(struct sync_flag *s, int seconds) { + struct timespec timeout; + clock_gettime(CLOCK_REALTIME, &timeout); + timeout.tv_sec += seconds; + + assert(pthread_mutex_lock(&s->mutex) == 0); + + while(!s->flag) { + if(!pthread_cond_timedwait(&s->cond, &s->mutex, &timeout) || errno != EINTR) { + break; + } + } + + assert(pthread_mutex_unlock(&s->mutex) == 0); + + return s->flag; +} + +void link_meshlink_pair(meshlink_handle_t *a, meshlink_handle_t *b) { + // Import and export both side's data + + assert(meshlink_set_canonical_address(a, meshlink_get_self(a), "localhost", NULL)); + assert(meshlink_set_canonical_address(b, meshlink_get_self(b), "localhost", NULL)); + + char *data = meshlink_export(a); + assert(data); + assert(meshlink_import(b, data)); + free(data); + + data = meshlink_export(b); + assert(data); + assert(meshlink_import(a, data)); + free(data); +} + +void open_meshlink_pair(meshlink_handle_t **pa, meshlink_handle_t **pb, const char *prefix) { + // Create two new MeshLink instances + + *pa = *pb = NULL; + + char *a_name, *b_name; + + assert(asprintf(&a_name, "%s_conf.1", prefix) > 0); + assert(a_name); + + assert(asprintf(&b_name, "%s_conf.2", prefix) > 0); + assert(b_name); + + assert(meshlink_destroy(a_name)); + assert(meshlink_destroy(b_name)); + + meshlink_handle_t *a = meshlink_open(a_name, "a", prefix, DEV_CLASS_BACKBONE); + assert(a); + + meshlink_handle_t *b = meshlink_open(b_name, "b", prefix, DEV_CLASS_BACKBONE); + assert(b); + + free(a_name); + free(b_name); + + meshlink_enable_discovery(a, false); + meshlink_enable_discovery(b, false); + + link_meshlink_pair(a, b); + + *pa = a; + *pb = b; +} + +void open_meshlink_pair_ephemeral(meshlink_handle_t **pa, meshlink_handle_t **pb, const char *prefix) { + // Create two new MeshLink instances + + *pa = *pb = NULL; + + meshlink_handle_t *a = meshlink_open_ephemeral("a", prefix, DEV_CLASS_BACKBONE); + meshlink_handle_t *b = meshlink_open_ephemeral("b", prefix, DEV_CLASS_BACKBONE); + + assert(a); + assert(b); + + meshlink_enable_discovery(a, false); + meshlink_enable_discovery(b, false); + + link_meshlink_pair(a, b); + + *pa = a; + *pb = b; +} + +// Don't poll in the application thread, use a condition variable to signal when the peer is online. +static void pair_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)node; + + if(reachable && meshlink_get_self(mesh) != node) { + set_sync_flag(mesh->priv, true); + } +} + +void start_meshlink_pair(meshlink_handle_t *a, meshlink_handle_t *b) { + struct sync_flag pair_status = {.flag = false}; + init_sync_flag(&pair_status); + + a->priv = &pair_status; + meshlink_set_node_status_cb(a, pair_status_cb); + + assert(meshlink_start(a)); + assert(meshlink_start(b)); + + assert(wait_sync_flag(&pair_status, 5)); + + meshlink_set_node_status_cb(a, NULL); + a->priv = NULL; +} + +void stop_meshlink_pair(meshlink_handle_t *a, meshlink_handle_t *b) { + meshlink_stop(a); + meshlink_stop(b); +} + +void close_meshlink_pair(meshlink_handle_t *a, meshlink_handle_t *b) { + meshlink_close(a); + meshlink_close(b); +} + +void log_cb(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) { + static const char *levelstr[] = { + [MESHLINK_DEBUG] = "DEBUG", + [MESHLINK_INFO] = "INFO", + [MESHLINK_WARNING] = "WARNING", + [MESHLINK_ERROR] = "ERROR", + [MESHLINK_CRITICAL] = "CRITICAL", + }; + + static struct timespec ts0; + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + + if(ts0.tv_sec == 0) { + ts0 = ts; + } + + float diff = (ts.tv_sec - ts0.tv_sec) + (ts.tv_nsec - ts0.tv_nsec) * 1e-9; + + fprintf(stderr, "%7.3f (%s) [%s] %s\n", + diff, + mesh ? mesh->name : "", + levelstr[level], + text); +} diff --git a/test/utils.h b/test/utils.h new file mode 100644 index 0000000..11facfc --- /dev/null +++ b/test/utils.h @@ -0,0 +1,62 @@ +#ifndef MESHLINK_TEST_UTILS_H +#define MESHLINK_TEST_UTILS_H + +#include +#include +#include + +#include "../src/meshlink.h" + +// Simple synchronisation between threads +struct sync_flag { + pthread_mutex_t mutex; + pthread_cond_t cond; + bool flag; +}; + +extern void init_sync_flag(struct sync_flag *s); +extern void set_sync_flag(struct sync_flag *s, bool value); +extern void reset_sync_flag(struct sync_flag *s); +extern bool check_sync_flag(struct sync_flag *s); +extern bool wait_sync_flag(struct sync_flag *s, int seconds); + +/// Create a pair of meshlink instances that are already joined together. +extern void open_meshlink_pair(meshlink_handle_t **a, meshlink_handle_t **b, const char *prefix); +extern void open_meshlink_pair_ephemeral(meshlink_handle_t **a, meshlink_handle_t **b, const char *prefix); + +/// Start a pair of meshlink instances and wait for them to connect together. +extern void start_meshlink_pair(meshlink_handle_t *a, meshlink_handle_t *b); + +/// Stop a pair of meshlink instances. +extern void stop_meshlink_pair(meshlink_handle_t *a, meshlink_handle_t *b); + +/// Stop and cleanup a pair of meshlink instances. +extern void close_meshlink_pair(meshlink_handle_t *a, meshlink_handle_t *b); + +/// Link two meshlink instances. +extern void link_meshlink_pair(meshlink_handle_t *a, meshlink_handle_t *b); + +/// Default log callback +extern void log_cb(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text); + +#define assert_after(cond, timeout)\ + do {\ + for(int i = 0; i++ <= timeout;) {\ + if(cond)\ + break;\ + if(i == timeout)\ + assert(cond);\ + sleep(1);\ + }\ + } while(0) + +#endif + +/// Compare two timespec values. +static inline bool timespec_lt(const struct timespec *a, const struct timespec *b) { + if(a->tv_sec == b->tv_sec) { + return a->tv_nsec < b->tv_nsec; + } else { + return a->tv_sec < b->tv_sec; + } +} -- 2.39.5