/*
Copyright (c) 2009-2011 by Juliusz Chroboczek

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

/* For memmem. */
#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <time.h>
#include <fcntl.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

#ifndef HAVE_MEMMEM
#ifdef __GLIBC__
#define HAVE_MEMMEM
#endif
#endif

#ifndef MSG_CONFIRM
#define MSG_CONFIRM 0
#endif

/* We set sin_family to 0 to mark unused slots. */
#if AF_INET == 0 || AF_INET6 == 0
#error You lose
#endif

#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
/* nothing */
#elif defined(__GNUC__)
#define inline __inline
#if  (__GNUC__ >= 3)
#define restrict __restrict
#else
#define restrict /**/
#endif
#else
#define inline /**/
#define restrict /**/
#endif

#define MAX(x, y) ((x) >= (y) ? (x) : (y))
#define MIN(x, y) ((x) <= (y) ? (x) : (y))

static int send_ping(struct sockaddr *sa, int salen,
                     const unsigned char *tid, int tid_len);
static int send_pong(struct sockaddr *sa, int salen,
                     const unsigned char *tid, int tid_len);
static int send_find_node(struct sockaddr *sa, int salen,
                          const unsigned char *tid, int tid_len,
                          const unsigned char *target, int want, int confirm);
static int send_nodes(struct sockaddr *sa, int salen,
                      const unsigned char *tid, int tid_len,
                      const unsigned char *nodes, int nodes_len,
                      const unsigned char *nodes6, int nodes6_len);
static int send_random_nodes(struct sockaddr *sa, int salen,
                             const unsigned char *tid, int tid_len,
                             const unsigned char *id, int want);
static int send_error(struct sockaddr *sa, int salen,
                      unsigned char *tid, int tid_len,
                      int code, const char *message);

#define ERROR 0
#define REPLY 1
#define PING 2
#define FIND_NODE 3
#define GET_PEERS 4
#define ANNOUNCE_PEER 5

#define WANT4 1
#define WANT6 2

static int parse_message(const unsigned char *buf, int buflen,
                         unsigned char *tid_return, int *tid_len,
                         unsigned char *id_return,
                         unsigned char *info_hash_return,
                         unsigned char *target_return,
                         unsigned short *port_return,
                         unsigned char *token_return, int *token_len,
                         unsigned char *nodes_return, int *nodes_len,
                         unsigned char *nodes6_return, int *nodes6_len,
                         unsigned char *values_return, int *values_len,
                         unsigned char *values6_return, int *values6_len,
                         int *want_return);

static const unsigned char zeroes[20] = {0};
static const unsigned char ones[20] = {
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF
};
static const unsigned char v4prefix[16] = {
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0, 0, 0, 0
};

static unsigned char myid[20];
static int have_v = 0;
static unsigned char my_v[9];

static int dht_socket = -1;
static int dht_socket6 = -1;

struct node {
    unsigned char id[160];
    struct sockaddr_storage ss;
    socklen_t sslen;
};
    
#define CIRCULAR_LIST_SIZE 256

struct circular_list {
    int head;
    int tail;
    struct node nodes[CIRCULAR_LIST_SIZE];
};

struct circular_list v4_new, v6_new, v4_confirmed, v6_confirmed;

#define MAX_TOKEN_BUCKET_TOKENS 40
static time_t token_bucket_time;
static int token_bucket_tokens;

FILE *dht_debug = NULL;

#ifdef __GNUC__
    __attribute__ ((format (printf, 1, 2)))
#endif
static void
debugf(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    if(dht_debug)
        vfprintf(dht_debug, format, args);
    va_end(args);
    fflush(dht_debug);
}

static int
is_martian(struct sockaddr *sa)
{
    switch(sa->sa_family) {
    case AF_INET: {
        struct sockaddr_in *sin = (struct sockaddr_in*)sa;
        const unsigned char *address = (const unsigned char*)&sin->sin_addr;
        return sin->sin_port == 0 ||
            (address[0] == 0) ||
            (address[0] == 127) ||
            ((address[0] & 0xE0) == 0xE0);
    }
    case AF_INET6: {
        struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)sa;
        const unsigned char *address = (const unsigned char*)&sin6->sin6_addr;
        return sin6->sin6_port == 0 ||
            (address[0] == 0xFF) ||
            (address[0] == 0xFE && (address[1] & 0xC0) == 0x80) ||
            (memcmp(address, zeroes, 15) == 0 &&
             (address[15] == 0 || address[15] == 1)) ||
            (memcmp(address, v4prefix, 12) == 0);
    }

    default:
        return 0;
    }
}

/* Forget about the ``XOR-metric''.  An id is just a path from the
   root of the tree, so bits are numbered from the start. */

static inline int
id_cmp(const unsigned char *restrict id1, const unsigned char *restrict id2)
{
    /* Memcmp is guaranteed to perform an unsigned comparison. */
    return memcmp(id1, id2, 20);
}

/* Our transaction-ids are 4-bytes long, with the first two bytes identi-
   fying the kind of request, and the remaining two a sequence number in
   host order. */

static void
make_tid(unsigned char *tid_return, const char *prefix, unsigned short seqno)
{
    tid_return[0] = prefix[0] & 0xFF;
    tid_return[1] = prefix[1] & 0xFF;
    memcpy(tid_return + 2, &seqno, 2);
}

static int
tid_match(const unsigned char *tid, const char *prefix,
          unsigned short *seqno_return)
{
    if(tid[0] == (prefix[0] & 0xFF) && tid[1] == (prefix[1] & 0xFF)) {
        if(seqno_return)
            memcpy(seqno_return, tid + 2, 2);
        return 1;
    } else
        return 0;
}

static inline int
circular(int from, int to)
{
    int x = to - from;
    if(x < 0)
        return x + CIRCULAR_LIST_SIZE;
    return x;
}

static int
list_elements(struct circular_list *list)
{
    return circular(list->head, list->tail);
}

static int
list_empty(struct circular_list *list)
{
    return list_elements(list) == 0;
}

static int
list_free(struct circular_list *list)
{
    return circular(list->tail + 1, list->head);
}

static int
list_pop(struct circular_list *list,
         struct sockaddr_storage *ss, socklen_t *sslen)
{
    if(list->head == list->tail)
        return 0;

    memcpy(ss, &list->nodes[list->head].ss, list->nodes[list->head].sslen);
    *sslen = list->nodes[list->head].sslen;
    list->head = (list->head + 1) % CIRCULAR_LIST_SIZE;
    return 1;
}

static int
list_random(struct circular_list *list, unsigned char *id,
            struct sockaddr_storage *ss, socklen_t *sslen)
{
    int n;
    if(list->head == list->tail)
        return 0;

    n = random() % (list->tail - list->head);
    n = (list->head + n) % CIRCULAR_LIST_SIZE;

    if(id)
        memcpy(id, &list->nodes[n].id, 20);
    memcpy(ss, &list->nodes[n].ss, list->nodes[n].sslen);
    *sslen = list->nodes[n].sslen;
    return 1;
}

/* We just learnt about a node, not necessarily a new one.  Confirm is 1 if
   the node sent a message, 2 if it sent us a reply. */

static int
new_node(unsigned char *id, struct sockaddr *sa, socklen_t salen, int confirm)
{
    struct circular_list *list;
    int i;

    if(sa->sa_family == AF_INET)
        list = confirm >= 2 ? &v4_confirmed : &v4_new;
    else if(sa->sa_family == AF_INET6)
        list = confirm >= 2 ? &v6_confirmed : &v6_new;
    else
        abort();

    /* A node that sends us a request is most probably bootstrapping.
       We want to avoid getting our tables full of very young nodes -- only
       include such a node if we have plenty of space. */

    if(confirm == 1 && list_free(list) < 32)
        return 0;

    for(i = list->head; i != list->tail; i = (i + 1) % CIRCULAR_LIST_SIZE) {
        struct node *n = &list->nodes[i];
        if(n->sslen == salen && memcmp(&n->ss, sa, salen) == 0)
            return 0;
    }

    memcpy(&list->nodes[list->tail].id, id, 160);
    memcpy(&list->nodes[list->tail].ss, sa, salen);
    list->nodes[list->tail].sslen = salen;
    list->tail = (list->tail + 1) % CIRCULAR_LIST_SIZE;
    if(list->head == list->tail)
        list->head = (list->head + 1) % CIRCULAR_LIST_SIZE;

    return 1;
}

static int
token_bucket(void)
{
    time_t now = time(NULL);
    if(token_bucket_tokens == 0) {
        token_bucket_tokens = MIN(MAX_TOKEN_BUCKET_TOKENS,
                                  4 * (now - token_bucket_time));
        token_bucket_time = now;
    }

    if(token_bucket_tokens == 0)
        return 0;

    token_bucket_tokens--;
    return 1;
}

static int
send_request(struct circular_list *list, int dopop, int doping, int want)
{
    unsigned char ttid[4];
    struct sockaddr_storage ss;
    socklen_t sslen;
    int rc;

    if(list_empty(list))
        return 0;

    if(dopop) {
        rc = list_pop(list, &ss, &sslen);
        if(rc == 0)
            return 0;
    } else {
        rc = list_random(list, NULL, &ss, &sslen);
        if(rc == 0)
            return 0;
    }

    if(doping) {
        make_tid(ttid, "pn", 0);
        debugf("Sending ping.\n");
        return send_ping((struct sockaddr*)&ss, sslen, ttid, 4);
    } else {
        unsigned char id[20];
        int i;
        for(i = 0; i < 20; i++)
            id[i] = random() & 0xFF;
        make_tid(ttid, "fn", 0);
        debugf("Sending find_node.\n");
        return send_find_node((struct sockaddr*)&ss, sslen, ttid, 4,
                              id, want, 0);
    }
}

int
main(int argc, char **argv)
{
    int port = 6881, quiet = 0, ipv4 = 1, ipv6 = 1;
    int opt, rc, i, send4;
    unsigned char ttid[4];

    while(1) {
        opt = getopt(argc, argv, "q46");
        if(opt < 0)
            break;

        switch(opt) {
        case 'q':
            quiet = 1;
            break;
        case '4':
            ipv6 = 0;
            break;
        case '6':
            ipv4 = 0;
            break;
        default:
            goto usage;
        }
    }

    i = optind;

    if(argc < i + 1)
        goto usage;

    port = atoi(argv[i++]);
    if(port <= 0 || port >= 0x10000)
        goto usage;

    if(ipv4) {
        dht_socket = socket(PF_INET, SOCK_DGRAM, 0);
        if(dht_socket < 0)
            perror("socket(IPv4)");
    }

    if(ipv6) {
        dht_socket6 = socket(PF_INET6, SOCK_DGRAM, 0);
        if(dht_socket6 < 0)
            perror("socket(IPv6)");
    }

    if(dht_socket < 0 && dht_socket6 < 0) {
        fprintf(stderr, "Eek!\n");
        exit(1);
    }

    if(dht_socket >= 0) {
        struct sockaddr_in sin;
        int rc;
        memset(&sin, 0, sizeof(sin));
        sin.sin_family = AF_INET;
        sin.sin_port = htons(port);
        rc = bind(dht_socket, (struct sockaddr*)&sin, sizeof(sin));
        if(rc < 0) {
            perror("bind(IPv4)");
            exit(1);
        }

        rc = fcntl(dht_socket, F_GETFL, 0);
        if(rc < 0) {
            perror("F_GETFL");
            exit(1);
        }
            
        rc = fcntl(dht_socket, F_SETFL, (rc | O_NONBLOCK));
        if(rc < 0) {
            perror("F_SETFL");
            exit(1);
        }
    }

    if(dht_socket6 >= 0) {
        struct sockaddr_in6 sin6;
        int rc;
        int val = 1;

        rc = setsockopt(dht_socket6, IPPROTO_IPV6, IPV6_V6ONLY,
                        (char *)&val, sizeof(val));
        if(rc < 0) {
            perror("setsockopt(IPV6_V6ONLY)");
            exit(1);
        }

        /* BEP-32 mandates that we should bind this socket to one of our
           global IPv6 addresses.  In this program, this only happens if
           the user used the -b flag. */

        memset(&sin6, 0, sizeof(sin6));
        sin6.sin6_family = AF_INET6;
        sin6.sin6_port = htons(port);
        rc = bind(dht_socket6, (struct sockaddr*)&sin6, sizeof(sin6));
        if(rc < 0) {
            perror("bind(IPv6)");
            exit(1);
        }

        rc = fcntl(dht_socket6, F_GETFL, 0);
        if(rc < 0) {
            perror("F_GETFL");
            exit(1);
        }
            
        rc = fcntl(dht_socket6, F_SETFL, (rc | O_NONBLOCK));
        if(rc < 0) {
            perror("F_SETFL");
            exit(1);
        }
    }

    {
        int fd;
        unsigned int seed;

        fd = open("/dev/urandom", O_RDONLY);
        if(fd < 0) {
            perror("open(random)");
            exit(1);
        }

        rc = read(fd, myid, 20);
        if(rc < 20) {
            perror("open(random)");
            exit(1);
        }

        rc = read(fd, &seed, sizeof(seed));
        srandom(seed);

        close(fd);
    }

    memcpy(my_v, "1:v4:JB\0\0", 9);
    have_v = 1;

    if(!quiet)
        dht_debug = stdout;
    
    while(i < argc) {
        struct addrinfo hints, *info, *infop;
        memset(&hints, 0, sizeof(hints));
        hints.ai_socktype = SOCK_DGRAM;
        hints.ai_family = 0;
        if(dht_socket < 0)
            hints.ai_family = AF_INET6;
        else if(dht_socket6 < 0)
            hints.ai_family |= AF_INET;
        rc = getaddrinfo(argv[i], argv[i + 1], &hints, &info);
        if(rc != 0) {
            fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rc));
            exit(1);
        }

        i++;
        if(i >= argc)
            goto usage;

        infop = info;
        while(infop) {
            make_tid(ttid, "pn", 0);
            debugf("Sending ping.\n");
            send_ping(infop->ai_addr, infop->ai_addrlen, ttid, 4);
            infop = infop->ai_next;
        }
        freeaddrinfo(info);

        i++;
    }

    token_bucket_time = time(NULL);
    token_bucket_tokens = MAX_TOKEN_BUCKET_TOKENS;

    while(1) {
        struct timeval tv;
        fd_set readfds;
        int rc;

        if((dht_socket >= 0 && list_elements(&v4_confirmed) <= 16) ||
           (dht_socket6 >= 0 && list_elements(&v6_confirmed) <= 16))
            tv.tv_sec = 0;
        else
            tv.tv_sec = random() % 30;
        tv.tv_usec = random() % 1000000;
        
        FD_ZERO(&readfds);
        if(dht_socket >= 0)
            FD_SET(dht_socket, &readfds);
        if(dht_socket6 >= 0)
            FD_SET(dht_socket6, &readfds);

        if(dht_debug)
            debugf("%d+%d %d+%d\n",
                   list_elements(&v4_confirmed), list_elements(&v6_confirmed),
                   list_elements(&v4_new), list_elements(&v6_new));

        rc = select(MAX(dht_socket, dht_socket6) + 1, &readfds, NULL, NULL,
                    &tv);

        if(rc < 0) {
            if(errno != EINTR) {
                perror("select");
                sleep(1);
            }
        }

        if(rc > 0) {
            int rc, message;
            unsigned char tid[16], id[20], info_hash[20], target[20];
            unsigned char buf[1536], nodes[256], nodes6[1024], token[128];
            int tid_len = 16, token_len = 128;
            int nodes_len = 256, nodes6_len = 1024;
            unsigned short port;
            unsigned char values[2048], values6[2048];
            int values_len = 2048, values6_len = 2048;
            int want;
            struct sockaddr_storage source_storage;
            struct sockaddr *source = (struct sockaddr*)&source_storage;
            socklen_t sourcelen = sizeof(source_storage);
            if(dht_socket >= 0 && FD_ISSET(dht_socket, &readfds)) {
                rc = recvfrom(dht_socket, buf, 1536, 0, source, &sourcelen);
            } else if(dht_socket6 >= 0 && FD_ISSET(dht_socket6, &readfds)) {
                rc = recvfrom(dht_socket6, buf, 1536, 0, source, &sourcelen);
            }

            if(rc < 0 || sourcelen > sizeof(struct sockaddr_storage))
                goto dontread;

            if(is_martian(source))
                goto dontread;

            /* There's a bug in parse_message -- it will happily overflow the
               buffer if it's not NUL-terminated.  For now, put a NUL at the
               end of buffers. */

            if(rc < 1536) {
                buf[rc] = '\0';
            } else {
                debugf("Overlong message.\n");
                goto dontread;
            }

            message = parse_message(buf, rc, tid, &tid_len, id, info_hash,
                                    target, &port, token, &token_len,
                                    nodes, &nodes_len, nodes6, &nodes6_len,
                                    values, &values_len, values6, &values6_len,
                                    &want);

            if(id_cmp(id, myid) == 0) {
                debugf("Received message from self.\n");
                goto dontread;
            }

            if(message > REPLY) {
                /* Rate limit requests. */
                if(!token_bucket()) {
                    debugf("Dropping request due to rate limiting.\n");
                    goto dontread;
                }
            }

            switch(message) {
            case REPLY:
                if(tid_len != 4) {
                    debugf("Broken node truncates transaction ids.\n");
                    goto dontread;
                }
                if(tid_match(tid, "pn", NULL)) {
                    debugf("Pong!\n");
                    new_node(id, source, sourcelen, 2);
                } else if(tid_match(tid, "fn", NULL)) {
                    debugf("Nodes found!\n");
                    if(nodes_len % 26 != 0 || nodes6_len % 38 != 0) {
                        debugf("Unexpected length for node info!\n");
                    } else {
                        new_node(id, source, sourcelen, 2);
                        for(i = 0; i < nodes_len / 26; i++) {
                            unsigned char *ni = nodes + i * 26;
                            struct sockaddr_in sin;
                            if(id_cmp(ni, myid) == 0)
                                continue;
                            memset(&sin, 0, sizeof(sin));
                            sin.sin_family = AF_INET;
                            memcpy(&sin.sin_addr, ni + 20, 4);
                            memcpy(&sin.sin_port, ni + 24, 2);
                            new_node(ni, (struct sockaddr*)&sin, sizeof(sin),
                                     0);
                        }
                        for(i = 0; i < nodes6_len / 38; i++) {
                            unsigned char *ni = nodes6 + i * 38;
                            struct sockaddr_in6 sin6;
                            if(id_cmp(ni, myid) == 0)
                                continue;
                            memset(&sin6, 0, sizeof(sin6));
                            sin6.sin6_family = AF_INET6;
                            memcpy(&sin6.sin6_addr, ni + 20, 16);
                            memcpy(&sin6.sin6_port, ni + 36, 2);
                            new_node(ni, (struct sockaddr*)&sin6, sizeof(sin6),
                                     0);
                        }
                    }
                } else {
                    debugf("Unexpected reply!\n");
                    goto dontread;
                }
                break;
            case PING:
                debugf("Ping (%d)!\n", tid_len);
                new_node(id, source, sourcelen, 1);
                debugf("Sending pong.\n");
                send_pong(source, sourcelen, tid, tid_len);
                break;

            case FIND_NODE:
            case GET_PEERS:
                if(message == FIND_NODE)
                    debugf("Find node!\n");
                else
                    debugf("Get peers!\n");
                new_node(id, source, sourcelen, 1);
                debugf("Sending nodes (%d).\n", want);
                send_random_nodes(source, sourcelen,
                                  tid, tid_len, target, want);
                break;
            case ANNOUNCE_PEER:
                debugf("Announce peer!\n");
                send_error(source, sourcelen, tid, tid_len,
                           203, "This node doesn't accept announces");
                break;
            }
        }

    dontread:

        /* We need to be careful to avoid a positive feedback loop.  Make
           sure we send at most one packet each time through the select
           loop. */

        if(dht_socket6 < 0)
            send4 = 1;
        else if(dht_socket < 0)
            send4 = 0;
        else
            send4 = random() % 2;

        if(send4) {
            int want =
                dht_socket6 >= 0 && list_free(&v6_new) > 8 ?
                (WANT4 | WANT6) : 0;
            if(!list_empty(&v4_new))
                send_request(&v4_new, 1, list_free(&v4_new) < 8, want);
            else if(!list_empty(&v4_confirmed))
                send_request(&v4_confirmed, 0, 0, want);
        } else {
            int want =
                dht_socket >= 0 && list_free(&v4_new) > 8 ?
                (WANT4 | WANT6) : 0;
            if(!list_empty(&v6_new))
                send_request(&v6_new, 1, list_free(&v6_new) < 8, want);
            else if(!list_empty(&v6_confirmed))
                send_request(&v6_confirmed, 0, 0, want);
        }
    }

    return 0;

 usage:
    fprintf(stderr, "dht-bootstrap [-q] [-4] [-6] port [node port...]\n");
    exit(1);
}

/* We could use a proper bencoding printer and parser, but the format of
   DHT messages is fairly stylised, so this seemed simpler. */

#define CHECK(offset, delta, size)                      \
    if(delta < 0 || offset + delta > size) goto fail

#define INC(offset, delta, size)                        \
    CHECK(offset, delta, size);                         \
    offset += delta

#define COPY(buf, offset, src, delta, size)             \
    CHECK(offset, delta, size);                         \
    memcpy(buf + offset, src, delta);                   \
    offset += delta;

#define ADD_V(buf, offset, size)                        \
    if(have_v) {                                        \
        COPY(buf, offset, my_v, sizeof(my_v), size);    \
    }

static int
dht_send(const void *buf, size_t len, int flags,
         const struct sockaddr *sa, int salen)
{
    int s;

    if(salen == 0)
        abort();

    if(sa->sa_family == AF_INET)
        s = dht_socket;
    else if(sa->sa_family == AF_INET6)
        s = dht_socket6;
    else
        s = -1;

    if(s < 0) {
        errno = EAFNOSUPPORT;
        return -1;
    }

    return sendto(s, buf, len, flags, sa, salen);
}

int
send_ping(struct sockaddr *sa, int salen,
          const unsigned char *tid, int tid_len)
{
    char buf[512];
    int i = 0, rc;
    rc = snprintf(buf + i, 512 - i, "d1:ad2:id20:"); INC(i, rc, 512);
    COPY(buf, i, myid, 20, 512);
    rc = snprintf(buf + i, 512 - i, "e1:q4:ping1:t%d:", tid_len);
    INC(i, rc, 512);
    COPY(buf, i, tid, tid_len, 512);
    ADD_V(buf, i, 512);
    rc = snprintf(buf + i, 512 - i, "1:y1:qe"); INC(i, rc, 512);
    return dht_send(buf, i, 0, sa, salen);

 fail:
    errno = ENOSPC;
    return -1;
}

int
send_pong(struct sockaddr *sa, int salen,
          const unsigned char *tid, int tid_len)
{
    char buf[512];
    int i = 0, rc;
    rc = snprintf(buf + i, 512 - i, "d1:rd2:id20:"); INC(i, rc, 512);
    COPY(buf, i, myid, 20, 512);
    rc = snprintf(buf + i, 512 - i, "e1:t%d:", tid_len); INC(i, rc, 512);
    COPY(buf, i, tid, tid_len, 512);
    ADD_V(buf, i, 512);
    rc = snprintf(buf + i, 512 - i, "1:y1:re"); INC(i, rc, 512);
    return dht_send(buf, i, 0, sa, salen);

 fail:
    errno = ENOSPC;
    return -1;
}

int
send_find_node(struct sockaddr *sa, int salen,
               const unsigned char *tid, int tid_len,
               const unsigned char *target, int want, int confirm)
{
    char buf[512];
    int i = 0, rc;
    rc = snprintf(buf + i, 512 - i, "d1:ad2:id20:"); INC(i, rc, 512);
    COPY(buf, i, myid, 20, 512);
    rc = snprintf(buf + i, 512 - i, "6:target20:"); INC(i, rc, 512);
    COPY(buf, i, target, 20, 512);
    if(want > 0) {
        rc = snprintf(buf + i, 512 - i, "4:wantl%s%se",
                      (want & WANT4) ? "2:n4" : "",
                      (want & WANT6) ? "2:n6" : "");
        INC(i, rc, 512);
    }
    rc = snprintf(buf + i, 512 - i, "e1:q9:find_node1:t%d:", tid_len);
    INC(i, rc, 512);
    COPY(buf, i, tid, tid_len, 512);
    ADD_V(buf, i, 512);
    rc = snprintf(buf + i, 512 - i, "1:y1:qe"); INC(i, rc, 512);
    return dht_send(buf, i, confirm ? MSG_CONFIRM : 0, sa, salen);

 fail:
    errno = ENOSPC;
    return -1;
}

int
send_nodes(struct sockaddr *sa, int salen,
           const unsigned char *tid, int tid_len,
           const unsigned char *nodes, int nodes_len,
           const unsigned char *nodes6, int nodes6_len)
{
    char buf[2048];
    int i = 0, rc;

    rc = snprintf(buf + i, 2048 - i, "d1:rd2:id20:"); INC(i, rc, 2048);
    COPY(buf, i, myid, 20, 2048);
    if(nodes_len > 0) {
        rc = snprintf(buf + i, 2048 - i, "5:nodes%d:", nodes_len);
        INC(i, rc, 2048);
        COPY(buf, i, nodes, nodes_len, 2048);
    }
    if(nodes6_len > 0) {
         rc = snprintf(buf + i, 2048 - i, "6:nodes6%d:", nodes6_len);
         INC(i, rc, 2048);
         COPY(buf, i, nodes6, nodes6_len, 2048);
    }

    rc = snprintf(buf + i, 2048 - i, "e1:t%d:", tid_len); INC(i, rc, 2048);
    COPY(buf, i, tid, tid_len, 2048);
    ADD_V(buf, i, 2048);
    rc = snprintf(buf + i, 2048 - i, "1:y1:re"); INC(i, rc, 2048);

    return dht_send(buf, i, 0, sa, salen);

 fail:
    errno = ENOSPC;
    return -1;
}

static int
buffer_random_nodes(int af, unsigned char *nodes)
{
    struct circular_list *list;
    struct sockaddr_storage ss;
    socklen_t sslen;
    unsigned char id[20];
    int n;
    int rc;

    switch(af) {
    case AF_INET: list = &v4_confirmed; break;
    case AF_INET6: list = &v6_confirmed; break;
    default: abort();
    }

    n = 0;
    while(n < 8) {
        rc = list_random(list, id, &ss, &sslen);
        if(rc < 1)
            break;
        switch(af) {
        case AF_INET: {
            struct sockaddr_in *sin = (struct sockaddr_in*)&ss;
            memcpy(nodes + n * 26, id, 20);
            memcpy(nodes + n * 26 + 20, &sin->sin_addr, 4);
            memcpy(nodes + n * 26 + 24, &sin->sin_port, 2);
            n++;
            break;
        }
        case AF_INET6: {
            struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&ss;
            memcpy(nodes + n * 38, id, 20);
            memcpy(nodes + n * 38 + 20, &sin6->sin6_addr, 16);
            memcpy(nodes + n * 38 + 36, &sin6->sin6_port, 2);
            n++;
            break;
        }
        default:
            abort();
        }
    }
    return n;
}

int
send_random_nodes(struct sockaddr *sa, int salen,
                  const unsigned char *tid, int tid_len,
                  const unsigned char *id, int want)
{
    unsigned char nodes[8 * 26];
    unsigned char nodes6[8 * 38];
    int numnodes = 0, numnodes6 = 0;

    if(want < 0)
        want = sa->sa_family == AF_INET ? WANT4 : WANT6;

    if((want & WANT4))
        numnodes = buffer_random_nodes(AF_INET, nodes);

    if((want & WANT6))
        numnodes6 = buffer_random_nodes(AF_INET6, nodes6);

    return send_nodes(sa, salen, tid, tid_len,
                      nodes, numnodes * 26,
                      nodes6, numnodes6 * 38);
}

static int
send_error(struct sockaddr *sa, int salen,
           unsigned char *tid, int tid_len,
           int code, const char *message)
{
    char buf[512];
    int i = 0, rc;

    rc = snprintf(buf + i, 512 - i, "d1:eli%de%d:",
                  code, (int)strlen(message));
    INC(i, rc, 512);
    COPY(buf, i, message, (int)strlen(message), 512);
    rc = snprintf(buf + i, 512 - i, "e1:t%d:", tid_len); INC(i, rc, 512);
    COPY(buf, i, tid, tid_len, 512);
    ADD_V(buf, i, 512);
    rc = snprintf(buf + i, 512 - i, "1:y1:ee"); INC(i, rc, 512);
    return dht_send(buf, i, 0, sa, salen);

 fail:
    errno = ENOSPC;
    return -1;
}

#undef CHECK
#undef INC
#undef COPY
#undef ADD_V

#ifndef HAVE_MEMMEM
static void *
memmem(const void *haystack, size_t haystacklen,
       const void *needle, size_t needlelen)
{
    const char *h = haystack;
    const char *n = needle;
    size_t i;

    /* size_t is unsigned */
    if(needlelen > haystacklen)
        return NULL;

    for(i = 0; i <= haystacklen - needlelen; i++) {
        if(memcmp(h + i, n, needlelen) == 0)
            return (void*)(h + i);
    }
    return NULL;
}
#endif

static int
parse_message(const unsigned char *buf, int buflen,
              unsigned char *tid_return, int *tid_len,
              unsigned char *id_return, unsigned char *info_hash_return,
              unsigned char *target_return, unsigned short *port_return,
              unsigned char *token_return, int *token_len,
              unsigned char *nodes_return, int *nodes_len,
              unsigned char *nodes6_return, int *nodes6_len,
              unsigned char *values_return, int *values_len,
              unsigned char *values6_return, int *values6_len,
              int *want_return)
{
    const unsigned char *p;

    /* This code will happily crash if the buffer is not NUL-terminated. */
    if(buf[buflen] != '\0') {
        debugf("Eek!  parse_message with unterminated buffer.\n");
        return -1;
    }

#define CHECK(ptr, len)                                                 \
    if(((unsigned char*)ptr) + (len) > (buf) + (buflen)) goto overflow;

    if(tid_return) {
        p = memmem(buf, buflen, "1:t", 3);
        if(p) {
            long l;
            char *q;
            l = strtol((char*)p + 3, &q, 10);
            if(q && *q == ':' && l > 0 && l < *tid_len) {
                CHECK(q + 1, l);
                memcpy(tid_return, q + 1, l);
                *tid_len = l;
            } else
                *tid_len = 0;
        }
    }
    if(id_return) {
        p = memmem(buf, buflen, "2:id20:", 7);
        if(p) {
            CHECK(p + 7, 20);
            memcpy(id_return, p + 7, 20);
        } else {
            memset(id_return, 0, 20);
        }
    }
    if(info_hash_return) {
        p = memmem(buf, buflen, "9:info_hash20:", 14);
        if(p) {
            CHECK(p + 14, 20);
            memcpy(info_hash_return, p + 14, 20);
        } else {
            memset(info_hash_return, 0, 20);
        }
    }
    if(port_return) {
        p = memmem(buf, buflen, "porti", 5);
        if(p) {
            long l;
            char *q;
            l = strtol((char*)p + 5, &q, 10);
            if(q && *q == 'e' && l > 0 && l < 0x10000)
                *port_return = l;
            else
                *port_return = 0;
        } else
            *port_return = 0;
    }
    if(target_return) {
        p = memmem(buf, buflen, "6:target20:", 11);
        if(p) {
            CHECK(p + 11, 20);
            memcpy(target_return, p + 11, 20);
        } else {
            memset(target_return, 0, 20);
        }
    }
    if(token_return) {
        p = memmem(buf, buflen, "5:token", 7);
        if(p) {
            long l;
            char *q;
            l = strtol((char*)p + 7, &q, 10);
            if(q && *q == ':' && l > 0 && l < *token_len) {
                CHECK(q + 1, l);
                memcpy(token_return, q + 1, l);
                *token_len = l;
            } else
                *token_len = 0;
        } else
            *token_len = 0;
    }

    if(nodes_len) {
        p = memmem(buf, buflen, "5:nodes", 7);
        if(p) {
            long l;
            char *q;
            l = strtol((char*)p + 7, &q, 10);
            if(q && *q == ':' && l > 0 && l < *nodes_len) {
                CHECK(q + 1, l);
                memcpy(nodes_return, q + 1, l);
                *nodes_len = l;
            } else
                *nodes_len = 0;
        } else
            *nodes_len = 0;
    }

    if(nodes6_len) {
        p = memmem(buf, buflen, "6:nodes6", 8);
        if(p) {
            long l;
            char *q;
            l = strtol((char*)p + 8, &q, 10);
            if(q && *q == ':' && l > 0 && l < *nodes6_len) {
                CHECK(q + 1, l);
                memcpy(nodes6_return, q + 1, l);
                *nodes6_len = l;
            } else
                *nodes6_len = 0;
        } else
            *nodes6_len = 0;
    }

    if(values_len || values6_len) {
        p = memmem(buf, buflen, "6:valuesl", 9);
        if(p) {
            int i = p - buf + 9;
            int j = 0, j6 = 0;
            while(1) {
                long l;
                char *q;
                l = strtol((char*)buf + i, &q, 10);
                if(q && *q == ':' && l > 0) {
                    CHECK(q + 1, l);
                    if(l == 6) {
                        if(j + l > *values_len)
                            continue;
                        i = q + 1 + l - (char*)buf;
                        memcpy((char*)values_return + j, q + 1, l);
                        j += l;
                    } else if(l == 18) {
                        if(j6 + l > *values6_len)
                            continue;
                        i = q + 1 + l - (char*)buf;
                        memcpy((char*)values6_return + j6, q + 1, l);
                        j6 += l;
                    } else {
                        debugf("Received weird value -- %d bytes.\n", (int)l);
                        i = q + 1 + l - (char*)buf;
                    }
                } else {
                    break;
                }
            }
            if(i >= buflen || buf[i] != 'e')
                debugf("eek... unexpected end for values.\n");
            if(values_len)
                *values_len = j;
            if(values6_len)
                *values6_len = j6;
        } else {
            *values_len = 0;
            *values6_len = 0;
        }
    }

    if(want_return) {
        p = memmem(buf, buflen, "4:wantl", 7);
        if(p) {
            int i = p - buf + 7;
            *want_return = 0;
            while(buf[i] > '0' && buf[i] <= '9' && buf[i + 1] == ':' &&
                  i + 2 + buf[i] - '0' < buflen) {
                CHECK(buf + i + 2, buf[i] - '0');
                if(buf[i] == '2' && memcmp(buf + i + 2, "n4", 2) == 0)
                    *want_return |= WANT4;
                else if(buf[i] == '2' && memcmp(buf + i + 2, "n6", 2) == 0)
                    *want_return |= WANT6;
                else
                    debugf("eek... unexpected want flag (%c)\n", buf[i]);
                i += 2 + buf[i] - '0';
            }
            if(i >= buflen || buf[i] != 'e')
                debugf("eek... unexpected end for want.\n");
        } else {
            *want_return = -1;
        }
    }

#undef CHECK

    if(memmem(buf, buflen, "1:y1:r", 6))
        return REPLY;
    if(memmem(buf, buflen, "1:y1:e", 6))
        return ERROR;
    if(!memmem(buf, buflen, "1:y1:q", 6))
        return -1;
    if(memmem(buf, buflen, "1:q4:ping", 9))
        return PING;
    if(memmem(buf, buflen, "1:q9:find_node", 14))
       return FIND_NODE;
    if(memmem(buf, buflen, "1:q9:get_peers", 14))
        return GET_PEERS;
    if(memmem(buf, buflen, "1:q13:announce_peer", 19))
       return ANNOUNCE_PEER;
    return -1;

 overflow:
    debugf("Truncated message.\n");
    return -1;
}
