/* This software was written by Dirk Engling It is considered beerware. Prost. Skol. Cheers or whatever. */ /* System */ #include #include #include #include /* Opentracker */ #include "ot_mutex.h" #include "trackerlogic.h" #include "test.h" /* The swarm itself: what an announce does to a torrent, and what a scrape says about it afterwards. The counts are read back through the udp scrape because that is the one reply the tracker builds as plain numbers. */ static struct ot_workstruct ws; static void hash_named(ot_hash hash, const char *name) { memset(hash, 0, sizeof(ot_hash)); memcpy(hash, name, strlen(name) < sizeof(ot_hash) ? strlen(name) : sizeof(ot_hash)); } /* An IPv4 peer arrives as the v4 mapped form the tracker stores */ static void peer_v4(ot_peer6 peer, uint8_t last, uint16_t port) { ot_ip6 ip = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 10, 0, 0, 0}; uint16_t net_port = htons(port); ip[15] = last; memset(peer, 0, sizeof(ot_peer6)); OT_SETIP(peer, ip); OT_SETPORT(peer, &net_port); OT_PEERFLAG(peer) = 0; } static void peer_v6(ot_peer6 peer, uint8_t last, uint16_t port) { ot_ip6 ip = {0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; uint16_t net_port = htons(port); ip[15] = last; memset(peer, 0, sizeof(ot_peer6)); OT_SETIP(peer, ip); OT_SETPORT(peer, &net_port); OT_PEERFLAG(peer) = 0; } static void announce(ot_hash *hash, ot_peer6 peer) { ws.hash = hash; memcpy(ws.peer, peer, sizeof(ot_peer6)); ws.reply = ws.outbuf; add_peer_to_torrent_and_return_peers(FLAG_TCP, &ws, 50); } /* seeders, downloads completed, leechers */ static void scrape(ot_hash hash, uint32_t counts[3]) { char reply[12]; return_udp_scrape_for_torrent(hash, reply); counts[0] = ntohl(((uint32_t *)reply)[0]); counts[1] = ntohl(((uint32_t *)reply)[1]); counts[2] = ntohl(((uint32_t *)reply)[2]); } static void case_an_announce_makes_a_torrent(void) { ot_hash hash; ot_peer6 peer; uint32_t counts[3]; size_t before = mutex_get_torrent_count(); test_case("the first announce for a hash creates its torrent"); hash_named(hash, "first-announce"); peer_v4(peer, 1, 6881); announce(&hash, peer); CHECK_INT((int)(mutex_get_torrent_count() - before), 1); scrape(hash, counts); CHECK_INT((int)counts[2], 1); /* one leecher */ CHECK_INT((int)counts[0], 0); } static void case_the_same_peer_twice_is_one_peer(void) { ot_hash hash; ot_peer6 peer; uint32_t counts[3]; test_case("announcing twice renews a peer rather than adding another"); hash_named(hash, "renewing"); peer_v4(peer, 2, 6881); announce(&hash, peer); announce(&hash, peer); scrape(hash, counts); CHECK_INT((int)counts[2], 1); } static void case_a_port_makes_a_different_peer(void) { ot_hash hash; ot_peer6 peer; uint32_t counts[3]; test_case("the same address on another port is another peer"); hash_named(hash, "two-ports"); peer_v4(peer, 3, 6881); announce(&hash, peer); peer_v4(peer, 3, 6882); announce(&hash, peer); scrape(hash, counts); CHECK_INT((int)counts[2], 2); } static void case_seeding_is_counted_apart(void) { ot_hash hash; ot_peer6 peer; uint32_t counts[3]; test_case("a seeder counts as complete, a leecher as incomplete"); hash_named(hash, "seed-and-leech"); peer_v4(peer, 4, 6881); OT_PEERFLAG(peer) |= PEER_FLAG_SEEDING; announce(&hash, peer); peer_v4(peer, 5, 6881); announce(&hash, peer); scrape(hash, counts); CHECK_INT((int)counts[0], 1); CHECK_INT((int)counts[2], 1); } static void case_completion_without_seeding_is_not_believed(void) { ot_hash hash; ot_peer6 peer; uint32_t counts[3]; test_case("a peer that claims to have completed but is still leeching is not counted"); hash_named(hash, "claiming"); peer_v4(peer, 6, 6881); OT_PEERFLAG(peer) |= PEER_FLAG_COMPLETED; announce(&hash, peer); scrape(hash, counts); CHECK_INT((int)counts[1], 0); CHECK_INT((int)counts[2], 1); /* still a leecher */ } static void case_completing_is_counted_once(void) { ot_hash hash; ot_peer6 peer; uint32_t counts[3]; test_case("a seeder reporting completion is counted once, however often it says so"); hash_named(hash, "completing"); peer_v4(peer, 7, 6881); OT_PEERFLAG(peer) |= PEER_FLAG_COMPLETED | PEER_FLAG_SEEDING; announce(&hash, peer); announce(&hash, peer); announce(&hash, peer); scrape(hash, counts); CHECK_INT((int)counts[1], 1); CHECK_INT((int)counts[0], 1); } static void case_a_peer_can_leave(void) { ot_hash hash; ot_peer6 peer; uint32_t counts[3]; test_case("a peer that stops is gone from the swarm"); hash_named(hash, "leaving"); peer_v4(peer, 7, 6881); announce(&hash, peer); peer_v4(peer, 8, 6881); announce(&hash, peer); scrape(hash, counts); CHECK_INT((int)counts[2], 2); ws.hash = &hash; memcpy(ws.peer, peer, sizeof(ot_peer6)); remove_peer_from_torrent(FLAG_TCP, &ws); scrape(hash, counts); CHECK_INT((int)counts[2], 1); } static void case_the_families_are_kept_apart(void) { ot_hash hash; ot_peer6 peer; uint32_t counts[3]; test_case("a v4 and a v6 peer of one swarm are both counted"); hash_named(hash, "dual-stack"); peer_v4(peer, 9, 6881); announce(&hash, peer); peer_v6(peer, 9, 6881); announce(&hash, peer); scrape(hash, counts); CHECK_INT((int)counts[2], 2); } static void case_a_hash_nobody_announced(void) { ot_hash hash; uint32_t counts[3]; test_case("a hash nobody has announced scrapes as nothing at all"); hash_named(hash, "never-seen"); scrape(hash, counts); CHECK_INT((int)counts[0], 0); CHECK_INT((int)counts[1], 0); CHECK_INT((int)counts[2], 0); } void test_trackerlogic(void) { sigset_t signal_mask; /* accesslist_init starts a thread that waits for SIGHUP and is stopped by being sent one, so the signal has to be blocked here as the tracker blocks it, or stopping the tracker logic ends this process instead */ sigemptyset(&signal_mask); sigaddset(&signal_mask, SIGHUP); pthread_sigmask(SIG_BLOCK, &signal_mask, NULL); trackerlogic_init(); CHECK_INT(workstruct_init(&ws), 0); case_an_announce_makes_a_torrent(); case_the_same_peer_twice_is_one_peer(); case_a_port_makes_a_different_peer(); case_seeding_is_counted_apart(); case_completion_without_seeding_is_not_believed(); case_completing_is_counted_once(); case_a_peer_can_leave(); case_the_families_are_kept_apart(); case_a_hash_nobody_announced(); workstruct_deinit(&ws); trackerlogic_deinit(); }