summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDimitri Sokolyuk <demon@dim13.org>2008-11-13 16:17:55 +0000
committerDimitri Sokolyuk <demon@dim13.org>2008-11-13 16:17:55 +0000
commit51b913eb2ff91adfe8cd5e417759a6057c5a8a21 (patch)
tree95c550a91c9111de2f65b3c86ee0c81fef8f5398
XBitTorrent
-rw-r--r--Makefile16
-rw-r--r--Makefile.inc28
-rw-r--r--bencode.c285
-rw-r--r--bencode.h56
-rw-r--r--btcheck.c127
-rw-r--r--btcheck/Makefile7
-rw-r--r--btget.c114
-rw-r--r--btget/Makefile7
-rw-r--r--btshow.c104
-rw-r--r--btshow/Makefile7
-rw-r--r--clients.c69
-rw-r--r--context.c59
-rw-r--r--context.h53
-rw-r--r--files.c271
-rw-r--r--files.h42
-rw-r--r--lib/Makefile16
-rw-r--r--lib/shlib_version2
-rw-r--r--meta.c265
-rw-r--r--meta.h91
-rw-r--r--netio.c185
-rw-r--r--netio.h28
-rw-r--r--peer.c487
-rw-r--r--peer.h130
-rw-r--r--tools.c230
-rw-r--r--tools.h40
-rw-r--r--tracker.c398
-rw-r--r--tracker.h74
-rw-r--r--version.h9
28 files changed, 3200 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..74e8024
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,16 @@
+# $Id$
+
+.include <bsd.own.mk>
+
+SUBDIR= lib btshow btcheck btget # xbittorrent
+
+tags:
+ ctags -dt *.[ch]
+
+cs: tags
+ ls -1 *.[ch] > cscope.files
+ cscope-indexer -v
+
+.PHONY: tags cs
+
+.include <bsd.subdir.mk>
diff --git a/Makefile.inc b/Makefile.inc
new file mode 100644
index 0000000..ae40db6
--- /dev/null
+++ b/Makefile.inc
@@ -0,0 +1,28 @@
+# $Id$
+
+.PATH: ${.CURDIR}/..
+CFLAGS+= -I${.CURDIR}/..
+
+CFLAGS+= -DMMAP -DFAILURE
+#DEBUG= -DDEBUG
+#DEBUG= -DDEBUGTRACKER
+WARNINGS= yes
+
+CDIAGFLAGS+= -Wall -Wpointer-arith \
+ -Wstrict-prototypes -Wmissing-prototypes \
+ -ggdb
+LDFLAGS+= -pg
+CFLAGS+= -pg
+#CDIAGFLAGS+= -Wconversion -ansi -pedantic
+
+.include <bsd.obj.mk>
+
+.if exists(${.CURDIR}/../lib/${__objdir})
+LDADD+= -L${.CURDIR}/../lib/${__objdir} -lxbt
+DPADD+= ${.CURDIR}/../lib/${__objdir}/libxbt.a
+.else
+LDADD+= -L${.CURDIR}/../lib -lxbt
+DPADD+= ${.CURDIR}/../lib/libxbt.a
+.endif
+LDADD+= -lcrypto
+DPADD+= ${LIBCRYPTO}
diff --git a/bencode.c b/bencode.c
new file mode 100644
index 0000000..d03523b
--- /dev/null
+++ b/bencode.c
@@ -0,0 +1,285 @@
+/* $Id$ */
+/*
+ * Copyright (c) 2005 Dimitri Sokolyuk <demon@vhost.dyndns.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "bencode.h"
+#include "tools.h"
+
+#ifndef lint
+static const char rcsid[] = "@(#)$Id$";
+#endif /* not lint */
+
+static struct btnode *dispatch(char **);
+static struct btnode *getdict(char **);
+static struct btnode *getlist(char **);
+static struct btnode *getint(char **);
+static struct btnode *getstr(char **);
+static off_t getnum(char **);
+#if DEBUG
+static char *indent(int); /* used only for debug purposes */
+#endif /* DEBUG */
+
+struct btnode *
+btbencode(char *str)
+{
+ return dispatch(&str);
+}
+
+void
+btfreenode(struct btnode *np)
+{
+ if (np == NULL)
+ return;
+
+ if (np->next != NULL)
+ btfreenode(np->next);
+
+ switch (np->type) {
+ case NODE_DICT:
+ btfreenode(np->dict.key);
+ btfreenode(np->dict.val);
+ break;
+ case NODE_LIST:
+ btfreenode(np->list);
+ break;
+ case NODE_INT:
+ /* nothing */
+ break;
+ case NODE_STR:
+ free(np->str);
+ break;
+ }
+ free(np);
+}
+
+struct btnode *
+btfinddict(struct btnode *np, char *key)
+{
+ if (np == NULL || key == NULL)
+ return NULL;
+
+ if (np->type != NODE_DICT)
+ return NULL;
+
+ if (np->dict.key == NULL)
+ return NULL;
+
+ /* the key is always string */
+ if (np->dict.key->type != NODE_STR)
+ return NULL;
+
+ if (np->dict.key->str == NULL)
+ return NULL;
+
+ /* there is only one key entry per dict possible */
+ if (strncmp(np->dict.key->str, key,
+ strlen(np->dict.key->str)) == 0)
+ return np->dict.val;
+
+ return btfinddict(np->next, key);
+}
+
+static struct btnode *
+dispatch(char **c)
+{
+ switch (*(*c)++) {
+ case 'd':
+ return getdict(c);
+ case 'l':
+ return getlist(c);
+ case 'i':
+ return getint(c);
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ --*c;
+ return getstr(c);
+ case 'e':
+ default:
+ return NULL;
+ }
+ /* NOTREACHED */
+}
+
+static struct btnode *
+getdict(char **c)
+{
+ struct btnode *np;
+ char *off;
+
+ if ((np = calloc(1, sizeof(struct btnode))) == NULL)
+ return NULL;
+
+ np->type = NODE_DICT;
+
+ if ((np->dict.key = dispatch(c)) == NULL) {
+ free(np); /* empty dictonary */
+ return NULL;
+ }
+
+ off = *c;
+ np->dict.val = dispatch(c);
+ if (np->dict.val != NULL) {
+ np->dict.val->aux.off = off;
+ np->dict.val->aux.len = *c - off;
+ }
+
+ np->next = getdict(c);
+
+ return np;
+}
+
+static struct btnode *
+getlist(char **c)
+{
+ struct btnode *np;
+
+ if ((np = calloc(1, sizeof(struct btnode))) == NULL)
+ return NULL;
+
+ np->type = NODE_LIST;
+
+ if ((np->list = dispatch(c)) == NULL) {
+ free(np); /* empty list */
+ return NULL;
+ }
+
+ np->next = getlist(c);
+
+ return np;
+}
+
+static struct btnode *
+getint(char **c)
+{
+ struct btnode *np;
+
+ if ((np = calloc(1, sizeof(struct btnode))) == NULL)
+ return NULL;
+
+ np->type = NODE_INT;
+ np->num = getnum(c);
+
+ return np;
+}
+
+static off_t
+getnum(char **c)
+{
+ off_t n = 0;
+ int mflag = 0;
+
+ if (**c == '-') {
+ mflag = 1;
+ ++*c;
+ }
+
+ while (**c >= '0' && **c <= '9') {
+ n *= 10;
+ n += *(*c)++ - '0';
+ }
+ ++*c; /* skip the last char (usually `e' or `:') */
+
+ return mflag ? -n : n;
+}
+
+static struct btnode *
+getstr(char **c)
+{
+ off_t len;
+ struct btnode *np;
+
+ if ((len = getnum(c)) <= 0)
+ return NULL;
+
+ if ((np = calloc(1, sizeof(struct btnode))) == NULL)
+ return NULL;
+
+ np->type = NODE_STR;
+ np->len = len;
+
+ if ((np->str = calloc(len + 1, sizeof(char))) == NULL) {
+ free(np);
+ return NULL;
+ }
+
+ memcpy(np->str, *c, len);
+ np->str[len] = NULL;
+ *c += len;
+
+ return np;
+}
+
+#if DEBUG
+static char *
+indent(int n)
+{
+ int i = 0;
+ static char buf[80] = { NULL };
+
+ while (i < n && i < 79)
+ buf[i++] = ' ';
+ buf[i] = NULL;
+
+ return buf;
+}
+
+void
+btdumpnode(struct btnode *np)
+{
+ const char *type[] = { "Dictonary", "List", "Integer", "String" };
+ static int i = 0;
+
+ if (np == NULL)
+ return;
+ printf("%s%s\n", indent(i), type[np->type]);
+ switch (np->type) {
+ case NODE_DICT:
+ i += 8;
+ btdumpnode(np->dict.key);
+ btdumpnode(np->dict.val);
+ i -= 8;
+ break;
+ case NODE_LIST:
+ i += 8;
+ btdumpnode(np->list);
+ i -= 8;
+ break;
+ case NODE_INT:
+ printf("%s%lld\n", indent(i + 4), np->num);
+ break;
+ case NODE_STR:
+ printf("%s(%d) %s\n", indent(i + 4), np->len, np->str);
+ break;
+ }
+
+ if (np->next != NULL)
+ btdumpnode(np->next);
+}
+#endif /* DEBUG */
diff --git a/bencode.h b/bencode.h
new file mode 100644
index 0000000..ca4dd91
--- /dev/null
+++ b/bencode.h
@@ -0,0 +1,56 @@
+/* $Id$ */
+/*
+ * Copyright (c) 2005 Dimitri Sokolyuk <demon@vhost.dyndns.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _BENCODE_H_
+#define _BENCODE_H_
+
+enum node {
+ NODE_DICT,
+ NODE_LIST,
+ NODE_INT,
+ NODE_STR
+};
+
+struct btnode {
+ enum node type;
+ char *str;
+ int len;
+ off_t num;
+ struct btnode *list;
+ struct {
+ struct btnode *key;
+ struct btnode *val;
+ } dict;
+ struct btnode *next;
+
+ /* auxiliary variables, needed only to calculate info_hash */
+ struct {
+ char *off; /* offset of node in string */
+ long len; /* entire length of node */
+ } aux;
+};
+
+__BEGIN_DECLS
+struct btnode *btbencode(char *);
+void btfreenode(struct btnode *);
+struct btnode *btfinddict(struct btnode *, char *);
+#if DEBUG
+void btdumpnode(struct btnode *);
+#endif /* DEBUG */
+__END_DECLS
+
+#endif /* Not _BENCODE_H_ */
diff --git a/btcheck.c b/btcheck.c
new file mode 100644
index 0000000..de62d72
--- /dev/null
+++ b/btcheck.c
@@ -0,0 +1,127 @@
+/* $Id$ */
+/*
+ * Copyright (c) 2005 Dimitri Sokolyuk <demon@vhost.dyndns.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/queue.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "meta.h"
+#include "files.h"
+#include "tools.h"
+#include "version.h"
+
+#ifndef lint
+static const char rcsid[] = "@(#)$Id$";
+#endif /* not lint */
+
+const char usagearg[] = "[-v] torrent [torrent ...]";
+
+void progress(struct btmeta *, int);
+void done(struct btmeta *);
+void printbf(char *, int);
+
+void
+printbf(char *bf, int sz)
+{
+ int i;
+
+ for (i = 0; i < sz * NBBY; i++) {
+ printf("%c", "01"[!!btisset(bf, i)]);
+ if ((i + 1) % 8 == 0)
+ printf("%c", "\n "[!!((i + 1) % 0x40)]);
+ }
+ printf("\n");
+}
+
+int
+main(int argc, char **argv)
+{
+ extern char *__progname;
+ struct btmeta *mp;
+ struct progresscb cb;
+ int ch, vflag;
+
+ vflag = 0;
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ vflag = 1;
+ break;
+ case '?':
+ default:
+ usage(usagearg);
+ /* NOTREACHED */
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage(usagearg);
+
+ printf("%s %d.%d - check BitTorrent files\n",
+ __progname, VERSION_MAJOR, VERSION_MINOR);
+
+ for (; *argv != NULL; ++argv) {
+ if ((mp = btreadmeta(*argv)) == NULL) {
+ warnx("cannot read %s\n", *argv);
+ continue;
+ }
+
+ if (btmkhier(mp) == -1)
+ return -1;
+ printf("\nfile: %s\n", mp->fname);
+ printf("size: %s\n", metric((double) mp->size));
+#if 0
+ btchkfiles(mp, progress);
+ done(mp);
+#else
+ cb.progress = progress;
+ cb.done = done;
+ btchkfiles(mp, &cb);
+#endif
+ if (vflag)
+ printbf(mp->bitfield, mp->bitlen);
+
+ btfreemeta(mp);
+ }
+ return 0;
+}
+
+void
+progress(struct btmeta *mp, int n)
+{
+ if (n % 10 == 0) {
+ printf("\rchecking %d of %d (%d ok)",
+ n, mp->npieces, mp->good);
+ fflush(stdout);
+ }
+}
+
+void
+done(struct btmeta *mp)
+{
+ printf("\rtotal good pieces %d/%d (%d%%)\n",
+ mp->good, mp->npieces, mp->good * 100 / mp->npieces);
+
+ printf("left: %s\n", metric((double) mp->left));
+}
diff --git a/btcheck/Makefile b/btcheck/Makefile
new file mode 100644
index 0000000..2f9f2f8
--- /dev/null
+++ b/btcheck/Makefile
@@ -0,0 +1,7 @@
+# $Id$
+
+PROG= btcheck
+SRCS= btcheck.c
+NOMAN= yes
+
+.include <bsd.prog.mk>
diff --git a/btget.c b/btget.c
new file mode 100644
index 0000000..03cb37f
--- /dev/null
+++ b/btget.c
@@ -0,0 +1,114 @@
+/* $Id$ */
+/*
+ * Copyright (c) 2005 Dimitri Sokolyuk <demon@vhost.dyndns.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h> /* close(), getopt() */
+
+#include "bencode.h"
+#include "context.h"
+#include "meta.h"
+#include "files.h"
+#include "peer.h"
+#include "tools.h"
+#include "tracker.h"
+#include "version.h"
+
+const char usagearg[] = "torrent";
+
+int
+main(int argc, char **argv)
+{
+ struct btmeta *mp;
+ struct bttracker *tp;
+ struct progresscb cb;
+ enum event ev = EV_NONE;
+ int ch, port = DEFPORT + 1;
+
+ while ((ch = getopt(argc, argv, "p:skn")) != -1)
+ switch (ch) {
+ case 'p':
+ port = atoi(optarg);
+ if (port > 65535 || port < 1024)
+ usage(usagearg);
+ /* NOTREACHED */
+ break;
+ case 's':
+ ev = EV_STARTED;
+ break;
+ case 'k':
+ ev = EV_STOPPED;
+ break;
+ case 'n':
+ ev = EV_NONE;
+ break;
+ case '?':
+ default:
+ usage(usagearg);
+ /* NOTREACHED */
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1 || (mp = btreadmeta(*argv)) == NULL)
+ usage(usagearg);
+
+ if (btmkhier(mp) == -1)
+ return -1;
+ cb.progress = btchkprogress;
+ cb.done = btchkdone;
+ btchkfiles(mp, &cb);
+
+ tp = btinittracker(mp, port);
+
+ /* ---8<------ */
+ memcpy(tp->localid, "-XB0100-AAAAAAAAAAAA", SHA1LEN);
+ tp->event = ev;
+
+ /* DEBUG: initial call */
+ tp->event = EV_STARTED;
+ btcalltracker(tp);
+ btdumpplist(&tp->plist);
+ printf("def interval: %d\n", tp->interval);
+ printf("min interval: %d\n", tp->minterval);
+
+ sleep(10);
+
+ tp->event = EV_NONE;
+ btcalltracker(tp);
+ btdumpplist(&tp->plist);
+
+ /* DEBUG: final call */
+ tp->event = EV_STOPPED;
+ btcalltracker(tp);
+
+ /* btmainloop(); */
+
+ btfreetracker(tp);
+
+ btfreemeta(mp);
+ return 0;
+}
diff --git a/btget/Makefile b/btget/Makefile
new file mode 100644
index 0000000..4e20220
--- /dev/null
+++ b/btget/Makefile
@@ -0,0 +1,7 @@
+# $Id$
+
+PROG= btget
+SRCS= btget.c
+NOMAN= yes
+
+.include <bsd.prog.mk>
diff --git a/btshow.c b/btshow.c
new file mode 100644
index 0000000..3cf5c88
--- /dev/null
+++ b/btshow.c
@@ -0,0 +1,104 @@
+/* $Id$ */
+/*
+ * Copyright (c) 2005 Dimitri Sokolyuk <demon@vhost.dyndns.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <time.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <libgen.h> /* basename(3) */
+#include "meta.h"
+#include "tools.h"
+#include "version.h"
+
+#ifndef lint
+static const char rcsid[] = "@(#)$Id$";
+#endif /* not lint */
+
+const char usagearg[] = "torrent [torrent ...]";
+
+extern char *__progname;
+
+int
+main(int argc, char **argv)
+{
+ struct btmeta *mp;
+ struct btfile *fp;
+ char hash[HASHSZS];
+
+ if (argc < 2)
+ usage(usagearg);
+
+ printf("%s %d.%d - decode BitTorrent metainfo files\n\n",
+ __progname, VERSION_MAJOR, VERSION_MINOR);
+
+ while(*++argv != NULL) {
+ if ((mp = btreadmeta(*argv)) == NULL) {
+ warnx("cannot read %s\n", *argv);
+ continue;
+ }
+
+ printf("metainfo file.: %s\n", basename(mp->fname));
+ bthexdump(hash, mp->infohash, sizeof(hash));
+ printf("info hash.....: %s\n", hash);
+
+ if (mp->nfiles == 1) {
+ printf("file name.....: %s\n", mp->name);
+ } else {
+ printf("directory name: %s\n", mp->name);
+ printf("files.........: %d\n", mp->nfiles);
+
+ SIMPLEQ_FOREACH(fp, &mp->flist, link) {
+ printf(" %s (%lld) %s\n",
+ fp->path,
+ fp->length,
+ metric((double) fp->length));
+ }
+ }
+
+ printf("%s %lld (%lld * %lld + %lld) %s\n",
+ mp->nfiles == 1 ?
+ "file size.....:" :
+ "archive size..:",
+ mp->size, mp->size / mp->plength,
+ mp->plength, mp->size % mp->plength,
+ metric((double) mp->size));
+
+ printf("announce url..: http://%s", mp->announce->host);
+ if (mp->announce->port != 80)
+ printf(":%d", mp->announce->port);
+ putchar('/');
+ if (mp->announce->path != NULL)
+ printf("%s", mp->announce->path);
+ putchar('\n');
+
+ /* optional fields */
+ if (mp->date != 0)
+ printf("creation date.: %s", ctime(&mp->date));
+ if (mp->comment != NULL)
+ printf("comment.......: %s\n", mp->comment);
+ if (mp->by != NULL)
+ printf("created by....: %s\n", mp->by);
+
+ btfreemeta(mp);
+ printf("\n");
+ }
+
+ return 0;
+}
diff --git a/btshow/Makefile b/btshow/Makefile
new file mode 100644
index 0000000..24a52f8
--- /dev/null
+++ b/btshow/Makefile
@@ -0,0 +1,7 @@
+# $Id$
+
+PROG= btshow
+SRCS= btshow.c
+NOMAN= yes
+
+.include <bsd.prog.mk>
diff --git a/clients.c b/clients.c
new file mode 100644
index 0000000..de3aa73
--- /dev/null
+++ b/clients.c
@@ -0,0 +1,69 @@
+struct client {
+ char *id;
+ char *name;
+};
+
+/* Azureus-style uses the following encoding:
+ '-', two characters for client id,
+ four ascii digits for version number,
+ '-',
+ followed by random numbers.
+
+ For example: '-AZ2060-'...
+ */
+
+struct client azureus[] = {
+ { "AR", "Arctic" },
+ { "AX", "BitPump" },
+ { "AZ", "Azureus" },
+ { "BB", "BitBuddy" },
+ { "BC", "BitComet" },
+ { "BS", "BTSlave" },
+ { "BX", "Bittorrent X" },
+ { "CD", "Enahnced CTorrent" },
+ { "CT", "CTorrent" },
+ { "LP", "Lphant" },
+ { "LT", "libtorrent" },
+ { "lt", "libTorrent" },
+ { "MP", "MooPolice" },
+ { "MT", "MoonlightTorrent" },
+ { "QT", "Qt 4 Torrent example" },
+ { "RT", "Retriever" },
+ { "SB", "Swiftbit" },
+ { "SS", "SwarmScope" },
+ { "SZ", "Shareaza" },
+ { "TN", "TorrentDotNET" },
+ { "TR", "Transmission" },
+ { "TS", "Torrentstorm" },
+ { "UT", "uTorrent" }, /* µTorrent */
+ { "XT", "XanTorrent" },
+ { "XB", "XBitTorrent" },
+ { "ZT", "ZipTorrent" },
+ { NULL, NULL }
+};
+
+/* Shadow's style uses the following encoding:
+ one ascii alphanumeric for client identification,
+ three ascii digits for version number,
+ '----',
+ followed by random numbers.
+
+ For example: 'S587----'...
+ */
+
+struct client shadow[] = {
+ { "A", "ABC" },
+ { "O", "Osprey Permaseed" },
+ { "R", "Tribler" },
+ { "S", "Shadow's client" },
+ { "T", "BitTornado" },
+ { "U", "UPnP NAT Bit Torrent" },
+ { NULL, NULL }
+};
+
+/* Bram's client now uses this style... 'M3-4-2--' */
+
+struct client bram[] = {
+ { "M", "BitTorrent" },
+ { NULL, NULL }
+};
diff --git a/context.c b/context.c
new file mode 100644
index 0000000..8b2fded
--- /dev/null
+++ b/context.c
@@ -0,0 +1,59 @@
+/* $Id$ */
+/*
+ * Copyright (c) 2005 Dimitri Sokolyuk <demon@vhost.dyndns.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "context.h"
+#include "meta.h"
+#include "peer.h"
+#include "tracker.h"
+#include "tools.h"
+
+#ifndef lint
+static const char rcsid[] = "@(#)$Id$";
+#endif /* not lint */
+
+void
+btmainloop(int listener)
+{
+ struct timeval tv;
+ fd_set readfds;
+ int fdmax;
+
+ tv.tv_sec = 60;
+ tv.tv_usec = 0;
+
+ FD_ZERO(&readfds);
+ FD_SET(listener, &readfds);
+ fdmax = listener;
+
+ for (;;) {
+ if (select(fdmax + 1, &readfds, NULL, NULL, &tv) == -1)
+ err(1, "select");
+ }
+}
diff --git a/context.h b/context.h
new file mode 100644
index 0000000..7d15ac8
--- /dev/null
+++ b/context.h
@@ -0,0 +1,53 @@
+/* $Id$ */
+/*
+ * Copyright (c) 2005 Dimitri Sokolyuk <demon@vhost.dyndns.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _CONTEXT_H_
+#define _CONTEXT_H_
+
+#define DEFPORT 6881
+#define MINPORT 6881
+#define MAXPORT 6889
+
+struct btsock {
+ int sockd;
+ struct sockaddr_in sin;
+ struct btsock *next; /* isn't used yet */
+};
+
+struct btcontext {
+ struct btsock listen;
+ struct btsock *peers;
+};
+
+#if 0
+LIST_HEAD(btplist, btpeer);
+SIMPLEQ_HEAD(btflist, btfile); /* ??? */
+
+struct btorrent {
+ struct btannonce *annonce; /* tracker */
+ struct btsock *server; /* local server */
+ struct btplist *plist; /* peer list */
+ struct btmeta *meta; /* meta file */
+ struct btfile *flist; /* file list */
+};
+#endif
+
+__BEGIN_DECLS
+void btmainloop(int);
+__END_DECLS
+
+#endif /* not _CONTEXT_H_ */
diff --git a/files.c b/files.c
new file mode 100644
index 0000000..4fb6d49
--- /dev/null
+++ b/files.c
@@ -0,0 +1,271 @@
+/* $Id$ */
+/*
+ * Copyright (c) 2005 Dimitri Sokolyuk <demon@vhost.dyndns.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <openssl/sha.h>
+
+#include <sys/param.h> /* bitfield */
+#include <sys/queue.h>
+#include <sys/types.h> /* mkdir() */
+#include <sys/stat.h>
+
+#include <err.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "meta.h"
+#include "files.h"
+#include "tools.h"
+
+#ifndef lint
+static const char rcsid[] = "@(#)$Id$";
+#endif /* not lint */
+
+/*
+ * 0 1 2 3 4 5 6 7
+ * pieces +---+---+---+---+---+---+---+-+
+ * files +--------+-------+-+----------+
+ * 0 1 2 3
+ */
+
+/* TODO: rewrite btread() and btwrite() so, that
+ * only required files are open (pending, DONE)
+ * on the one hand it makes more overhead on opening files, on the other
+ * hand we need only one file descriptor and we have no limitations on number of files
+ * so more descriptors remains for the network communication by the same rlimit value
+ */
+
+#define FMODE "a+"
+
+int
+btwrite(struct btmeta *mp, char *piece, off_t len, int npiece)
+{
+ FILE *fd;
+ struct btfile *fp;
+ off_t off, eof, left;
+
+ fp = SIMPLEQ_FIRST(&mp->flist);
+ /* TODO: check piece first */
+ off = mp->plength * npiece;
+ eof = fp->length;
+
+ /* find right file */
+ while (off > eof) {
+ off -= fp->length;
+ fp = SIMPLEQ_NEXT(fp, link);
+ eof = fp->length;
+ }
+ if (fp == NULL)
+ return -1; /* XXX */
+
+ left = len;
+ fd = fopen(fp->path, FMODE);
+ fseek(fd, off, SEEK_SET);
+ if ((off + len) <= eof) {
+ left -= fwrite(piece, sizeof(char), len, fd);
+ } else {
+ left -= fwrite(piece, sizeof(char), eof - off, fd);
+ fclose(fd);
+ fp = SIMPLEQ_NEXT(fp, link);
+
+ for (; left > fp->length; fp = SIMPLEQ_NEXT(fp, link)) {
+ fd = fopen(fp->path, FMODE);
+ fseek(fd, 0L, SEEK_SET);
+ left -= fwrite(piece + len - left, sizeof(char),
+ fp->length, fd);
+ fclose(fd);
+ }
+
+ fd = fopen(fp->path, FMODE);
+ fseek(fd, 0L, SEEK_SET);
+ left -= fwrite(piece + len - left, sizeof(char), left, fd);
+ }
+ fclose(fd);
+
+ return ((len - left) == 0);
+}
+
+int
+btread(struct btmeta *mp, char *piece, off_t len, int npiece)
+{
+ FILE *fd;
+ struct btfile *fp;
+ off_t off, eof, left, ret;
+
+ fp = SIMPLEQ_FIRST(&mp->flist);
+ off = mp->plength * npiece;
+ eof = fp->length;
+
+ /* find right file */
+ while (off > eof) {
+ off -= fp->length;
+ fp = SIMPLEQ_NEXT(fp, link);
+ eof = fp->length;
+ }
+
+ left = len;
+ fd = fopen(fp->path, FMODE);
+ fseek(fd, off, SEEK_SET);
+ if ((off + len) <= eof) {
+ ret = fread(piece, sizeof(char), len, fd);
+ left -= (ret > 0) ? ret : len;
+ } else {
+ ret = fread(piece, sizeof(char), eof - off, fd);
+ left -= (ret > 0) ? ret : eof - off;
+ fclose(fd);
+ fp = SIMPLEQ_NEXT(fp, link);
+
+ for (; left > fp->length; fp = SIMPLEQ_NEXT(fp, link)) {
+ fd = fopen(fp->path, FMODE);
+ fseek(fd, 0L, SEEK_SET);
+ ret = fread(piece + len - left, sizeof(char),
+ fp->length, fd);
+ left -= (ret > 0) ? ret : fp->length;
+ fclose(fd);
+ }
+
+ fd = fopen(fp->path, FMODE);
+ fseek(fd, 0L, SEEK_SET);
+ ret = fread(piece + len - left, sizeof(char), left, fd);
+ left -= (ret > 0) ? ret : left;
+ }
+ fclose(fd);
+
+ return (ret > 0);
+}
+
+off_t
+btsizeofpiece(struct btmeta *mp, int n)
+{
+ return (n == mp->npieces - 1) ? mp->size % mp->plength : mp->plength;
+}
+
+int
+btmkhier(struct btmeta *mp)
+{
+ struct btfile *fp;
+ struct stat st;
+ char path[MAXPATHLEN], *p;
+
+ if (mp->nfiles > 1) {
+ SIMPLEQ_FOREACH(fp, &mp->flist, link) {
+ strlcpy(path, fp->path, sizeof(path));
+ p = path;
+ while ((p = strchr(p, '/')) != NULL) {
+ /* TODO: add proper checking */
+ *p = '\0';
+ if (stat(path, &st) == -1)
+ mkdir(path, 0755);
+ else if (!(st.st_mode & S_IFDIR)) {
+ fprintf(stderr, "remove %s\n", path);
+ return -1;
+ }
+ *p++ = '/';
+ }
+ }
+ }
+
+ return 0;
+}
+
+int
+btcheck(u_char *key, char *src, off_t len)
+{
+ u_char sha[SHA1LEN];
+
+ /* FIXME: it's so ugly */
+ SHA1((u_char *) src, len, sha);
+ return (strncmp((char *) key, (char *) sha, SHA1LEN) == 0);
+}
+
+void
+#if 0
+btchkfiles(struct btmeta *mp, void (*progress)(struct btmeta *, int))
+#else
+btchkfiles(struct btmeta *mp, struct progresscb *cb)
+#endif
+{
+ char *buf;
+ off_t len;
+ int n;
+
+ if ((buf = calloc(mp->plength, sizeof(char))) == NULL)
+ return;
+
+ mp->left = mp->size;
+
+ for (n = 0, mp->good = 0; n < mp->npieces; n++) {
+ len = btsizeofpiece(mp, n);
+ if (btread(mp, buf, len, n)) {
+ if (btcheck(mp->pieces[n], buf, len)) {
+ btsetbit(mp->bitfield, n);
+ mp->good++;
+ mp->left -= len;
+ }
+ }
+#if 0
+ if (progress != NULL)
+ (*progress)(mp, n + 1);
+ }
+ if (progress != NULL)
+ (*progress)(mp, n + 1);
+#else
+ if (cb && cb->progress)
+ (*cb->progress)(mp, n + 1);
+ }
+ if (cb && cb->done)
+ (*cb->done)(mp);
+#endif
+
+ free(buf);
+}
+
+/* XXX */
+int
+btchkwrite(struct btmeta *mp, char *buf, int n)
+{
+ off_t len;
+
+ len = btsizeofpiece(mp, n);
+
+ if (btcheck(mp->pieces[n], buf, len)) {
+ if (btwrite(mp, buf, len, n)) {
+ btsetbit(mp->bitfield, n);
+ mp->left -= len;
+ return 0;
+ }
+ }
+ return -1;
+}
+
+/* fallback progress indicator */
+
+void
+btchkprogress(struct btmeta *mp, int n)
+{
+ printf("\r%s: checking %d of %d (%d ok)", mp->fname,
+ n, mp->npieces, mp->good);
+ fflush(stdout);
+}
+
+void
+btchkdone(struct btmeta *mp)
+{
+ printf("\r%s: total good pieces %d/%d (%d%%)\n", mp->fname,
+ mp->good, mp->npieces, mp->good * 100 / mp->npieces);
+}
diff --git a/files.h b/files.h
new file mode 100644
index 0000000..19898de
--- /dev/null
+++ b/files.h
@@ -0,0 +1,42 @@
+/* $Id$ */
+/*
+ * Copyright (c) 2005 Dimitri Sokolyuk <demon@vhost.dyndns.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _FILES_H_
+#define _FILES_H_
+
+struct progresscb {
+ void (*progress)(struct btmeta *, int);
+ void (*done)(struct btmeta *);
+};
+
+__BEGIN_DECLS
+off_t btsizeofpiece(struct btmeta *, int);
+int btcheck(u_char *, char *, off_t);
+int btwrite(struct btmeta *, char *, off_t, int);
+int btread(struct btmeta *, char *, off_t, int);
+int btmkhier(struct btmeta *);
+#if 0
+void btchkfiles(struct btmeta *, void(*)(struct btmeta *, int));
+#else
+void btchkfiles(struct btmeta *, struct progresscb *);
+#endif
+int btchkwrite(struct btmeta *, char *, int);
+void btchkprogress(struct btmeta *, int);
+void btchkdone(struct btmeta *);
+__END_DECLS
+
+#endif /* not _FILES_H_ */
diff --git a/lib/Makefile b/lib/Makefile
new file mode 100644
index 0000000..8fbfd71
--- /dev/null
+++ b/lib/Makefile
@@ -0,0 +1,16 @@
+# $Id$
+
+LIB= xbt
+SRCS= bencode.c \
+ context.c \
+ files.c \
+ meta.c \
+ peer.c \
+ tools.c \
+ tracker.c
+# netio.c
+NOMAN= yes
+NOPROFILE= yes
+NOPIC= yes
+
+.include <bsd.lib.mk>
diff --git a/lib/shlib_version b/lib/shlib_version
new file mode 100644
index 0000000..1edea46
--- /dev/null
+++ b/lib/shlib_version
@@ -0,0 +1,2 @@
+major=1
+minor=0
diff --git a/meta.c b/meta.c
new file mode 100644
index 0000000..6255e2e
--- /dev/null
+++ b/meta.c
@@ -0,0 +1,265 @@
+/* $Id$ */
+/*
+ * Copyright (c) 2005 Dimitri Sokolyuk <demon@vhost.dyndns.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <openssl/sha.h>
+
+#include "bencode.h"
+#include "meta.h"
+#include "files.h"
+#include "tools.h"
+
+#ifndef lint
+static const char rcsid[] = "@(#)$Id$";
+#endif /* not lint */
+
+static char *encpath(struct btnode *, char *);
+static struct btmeta *parsemeta(struct btnode *);
+static struct btannounce *getannounce(char *);
+static void freeannounce(struct btannounce *);
+
+struct btmeta *
+btreadmeta(char *fname)
+{
+ /* TODO: realloc btmeta, if the next torrent is to read */
+ struct btnode *np;
+ struct btmeta *mp;
+ struct stat st;
+ char *buf;
+
+ if (stat(fname, &st) == -1 || !(st.st_mode & S_IFREG))
+ return NULL;
+
+#if MMAP
+ off_t len;
+ if ((buf = (char *) btmmapfile(fname, &len)) == NULL)
+#else
+ if ((buf = btfreadall(fname)) == NULL)
+#endif
+ return NULL;
+
+ /* the first node must be a dictionary followed by a digit */
+ if (*buf != 'd' || !(isascii(*(buf + 1)) && isdigit(*(buf + 1))))
+ return NULL;
+
+ np = btbencode(buf);
+ if ((mp = parsemeta(np)) == NULL) {
+ fprintf(stderr, "parsemeta\n");
+ return NULL;
+ }
+
+#if DEBUG
+ btdumpnode(np);
+#endif
+
+ mp->fname = strdup(fname);
+
+#if MMAP
+ btunmapfile((void *) buf, len);
+#else
+ free(buf);
+#endif
+ btfreenode(np);
+
+ return mp;
+}
+
+void
+btfreemeta(struct btmeta *mp)
+{
+ struct btfile *fp, *nxt;
+
+ if (mp == NULL)
+ return;
+
+ freeannounce(mp->announce);
+ free(mp->name);
+ free(mp->fname);
+ for (fp = SIMPLEQ_FIRST(&mp->flist); fp != SIMPLEQ_END(&mp->flist); fp = nxt) {
+ nxt = SIMPLEQ_NEXT(fp, link);
+ free(fp->path);
+ free(fp);
+ }
+ SIMPLEQ_INIT(&mp->flist);
+ free(mp->pieces);
+ if (mp->comment != NULL)
+ free(mp->comment);
+ if (mp->by != NULL)
+ free(mp->by);
+ free(mp->bitfield);
+ free(mp);
+}
+
+static char *
+encpath(struct btnode *np, char *parent)
+{
+ char buf[MAXPATHLEN];
+
+ if (np->list == NULL)
+ return NULL;
+
+ strlcpy(buf, parent, sizeof(buf));
+ strlcat(buf, "/", sizeof(buf));
+ while (np != NULL) {
+ strlcat(buf, np->list->str, sizeof(buf));
+ strlcat(buf, "/", sizeof(buf));
+ np = np->next;
+ }
+ buf[strlen(buf) - 1] = '\0';
+
+ return strdup(buf);
+}
+
+/* FIXME: should it all be there or in main? */
+static struct btmeta *
+parsemeta(struct btnode *np)
+{
+ struct btnode *found, *info, *files;
+ struct btmeta *mp;
+ struct btfile *fp;
+
+ if ((mp = calloc(1, sizeof(struct btmeta))) == NULL)
+ return NULL;
+
+ if ((found = btfinddict(np, "announce")) != NULL)
+ if ((mp->announce = getannounce(found->str)) == NULL)
+ return NULL;
+
+ /* TODO: announce-list */
+
+ if ((found = btfinddict(np, "comment")) != NULL)
+ mp->comment = strdup(found->str);
+
+ if ((found = btfinddict(np, "creation date")) != NULL)
+ mp->date = found->num;
+
+ if ((found = btfinddict(np, "created by")) != NULL)
+ mp->by = strdup(found->str);
+
+ if ((info = btfinddict(np, "info")) == NULL)
+ return NULL;
+
+ SHA1(info->aux.off, info->aux.len, mp->infohash);
+
+ if ((found = btfinddict(info, "name")) != NULL)
+ mp->name = strdup(found->str);
+
+ SIMPLEQ_INIT(&mp->flist);
+
+ if ((found = btfinddict(info, "length")) != NULL) {
+ if ((fp = calloc(1, sizeof(struct btfile))) == NULL)
+ return NULL;
+
+ fp->length = mp->size = found->num;
+ fp->path = strdup(mp->name);
+
+ SIMPLEQ_INSERT_TAIL(&mp->flist, fp, link);
+ mp->nfiles = 1;
+ } else {
+ mp->size = 0;
+ mp->nfiles = 0;
+ files = btfinddict(info, "files");
+ while (files != NULL) {
+ if ((fp = calloc(1, sizeof(struct btfile))) == NULL)
+ return NULL;
+ if ((found = btfinddict(files->list, "length")) != NULL) {
+ fp->length = found->num;
+ mp->size += found->num;
+ }
+
+ /* TODO: change encpath so that it returns (char **) */
+ if ((found = btfinddict(files->list, "path")) != NULL)
+ fp->path = encpath(found, mp->name);
+
+ SIMPLEQ_INSERT_TAIL(&mp->flist, fp, link);
+ ++mp->nfiles;
+
+ files = files->next;
+ }
+ }
+
+ if ((found = btfinddict(info, "piece length")) != NULL)
+ mp->plength = found->num;
+
+ if ((found = btfinddict(info, "pieces")) != NULL) {
+ int i;
+ mp->npieces = howmany(mp->size, mp->plength);
+ if ((mp->pieces = calloc(mp->npieces, sizeof(*mp->pieces))) == NULL)
+ return NULL;
+ for (i = 0; i < mp->npieces; i++)
+ memcpy(mp->pieces[i], &found->str[i * SHA1LEN], SHA1LEN);
+ }
+
+ /* initialize bitfield */
+ mp->bitlen = howmany(mp->npieces, sizeof(char) * NBBY);
+ if ((mp->bitfield = calloc(mp->bitlen, sizeof(char))) == NULL)
+ return NULL;
+
+ return mp;
+}
+
+static struct btannounce *
+getannounce(char *src)
+{
+#define HTTP_URL "http://"
+#define HTTP_PORT 80
+
+ struct btannounce *ap;
+ char *buf, *host, *port, *path;
+
+ buf = strdup(src);
+
+ if ((ap = calloc(1, sizeof(struct btannounce))) == NULL)
+ return NULL;
+
+ if ((host = strcasestr(buf, HTTP_URL)) == NULL) {
+ free(buf);
+ return NULL;
+ } else
+ host += sizeof(HTTP_URL) - 1;
+
+ if ((path = strchr(host, '/')) != NULL)
+ *path++ = '\0';
+ ap->path = path ? strdup(path) : NULL;
+
+ if ((port = strrchr(host, ':')) != NULL)
+ *port++ = '\0';
+ ap->port = port ? atoi(port) : HTTP_PORT;
+
+ ap->host = strdup(host); /* have to be last */
+
+ free(buf);
+
+ return ap;
+}
+
+static void
+freeannounce(struct btannounce *ap)
+{
+ if (ap->path)
+ free(ap->path); /* XXX */
+ free(ap->host);
+ free(ap);
+}
diff --git a/meta.h b/meta.h
new file mode 100644
index 0000000..c6ba67c
--- /dev/null
+++ b/meta.h
@@ -0,0 +1,91 @@
+/* $Id$ */
+/*
+ * Copyright (c) 2005 Dimitri Sokolyuk <demon@vhost.dyndns.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef _META_H_
+#define _META_H_
+
+#if 0
+#include <netinet/in.h>
+#endif
+
+#ifndef SHA1LEN
+#define SHA1LEN 20
+#endif
+
+#define btsetbit(a, i) ((a)[(i) / NBBY] |= 1 << (NBBY - ((i) % NBBY) - 1))
+#define btclrbit(a, i) ((a)[(i) / NBBY] &= ~(1 << (NBBY - ((i) % NBBY) - 1)))
+#define btisset(a, i) ((a)[(i) / NBBY] & (1 << (NBBY - ((i) % NBBY) - 1)))
+#define btisclr(a, i) (((a)[(i) / NBBY] & (1 << (NBBY - ((i) % NBBY) - 1))) == 0)
+
+struct btannounce {
+ SIMPLEQ_ENTRY(btannounce) link; /* isn't used jet, preparation for an ann-list */
+#if 0 /* obsolete */
+ struct sockaddr_in sin;
+#endif
+ char *host;
+ int port;
+ char *path;
+};
+
+SIMPLEQ_HEAD(btalist, btannounce);
+
+struct btfile {
+ SIMPLEQ_ENTRY(btfile) link;
+ off_t length;
+ char *path;
+};
+
+SIMPLEQ_HEAD(btflist, btfile);
+
+struct btmeta {
+ char *fname;
+
+ int nann; /* number of tracker */
+ struct btalist btalist; /* SIMPLEQ_HEAD */
+
+ struct btannounce *announce; /* announce url */
+ char *name; /* name of file or directory */
+ off_t plength; /* length of one piece */
+ int npieces; /* number of pieces */
+ u_char (*pieces)[SHA1LEN]; /* pieces SHA1 check-sum */
+ u_char infohash[SHA1LEN]; /* needed by peer */
+ off_t size; /* size overall */
+ int nfiles; /* num of files */
+ struct btflist flist; /* SIMPLEQ_HEAD */
+
+ /* aux */
+ char *comment;
+ char *by; /* publisher */
+ time_t date; /* published */
+
+ /* should that all be there? */
+ int bitlen;
+ char *bitfield; /* needed by peer (bitfield) */
+ int good; /* good pieces */
+
+ off_t left; /* needed by tracker */
+#if 0
+ struct btmeta *next; /* XXX next torrent, isn't used yet */
+#endif
+};
+
+__BEGIN_DECLS
+struct btmeta *btreadmeta(char *);
+void btfreemeta(struct btmeta *);
+__END_DECLS
+
+#endif /* not _META_H_ */
+
diff --git a/netio.c b/netio.c
new file mode 100644
index 0000000..6fc4d4b
--- /dev/null
+++ b/netio.c
@@ -0,0 +1,185 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/queue.h>
+
+#include <assert.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "meta.h"
+#include "files.h"
+#include "peer.h"
+#include "netio.h"
+
+LIST_HEAD(btclist, btchunk) chead;
+LIST_HEAD(btrlist, btrequest) rhead;
+
+struct btrequest *allocrequest(struct btmeta *, struct btpeer *, int, off_t, off_t);
+int freerequest(struct btmeta *, struct btpeer *, int n, off_t, off_t);
+struct btchunk *allocchunk(struct btmeta *, int);
+int freechunk(struct btmeta *, int);
+
+/* DON'T FORGET:
+
+LIST_INIT(&chead);
+LIST_INIT(&rhead);
+
+ */
+
+struct btrequest *
+btallocrequest(struct btmeta *mp, struct btpeer *p, int n, off_t off, off_t len)
+{
+ struct btrequest *r;
+
+ r = malloc(sizeof(struct btrequest));
+ assert(r);
+
+ r->peer = p;
+ r->offset = off;
+ r->length = len;
+ r->chunk = allocchunk(mp, n);
+ r->transmitted = 0;
+
+ LIST_INSERT_HEAD(&rhead, r, link);
+
+ return r;
+}
+
+int
+btfreerequest(struct btmeta *mp, struct btpeer *p, int n, off_t off, off_t len)
+{
+ struct btrequest *r, *nxt;
+
+ for (r = LIST_FIRST(&rhead); r != LIST_END(&rhead); r = nxt) {
+ nxt = LIST_NEXT(r, link);
+ if (r->peer == p && r->offset == off && r->length == len) {
+ if (freechunk(mp, r->chunk->piece) == -1)
+ return -1;
+ LIST_REMOVE(r, link);
+ free(r);
+ return 0;
+ }
+ }
+ return -1;
+}
+
+int
+btwriterequest(struct btpeer *p, int n, off_t off, off_t len)
+{
+ struct btrequest *r, *nxt;
+
+ for (r = LIST_FIRST(&rhead); r != LIST_END(&rhead); r = nxt) {
+ nxt = LIST_NEXT(r, link);
+ if (r->peer == p && r->offset == off && r->length == len) {
+ memcpy(r->chunk->data + r->offset, p->msgbuf, p->buflen);
+ return 0;
+ }
+ }
+ return -1;
+}
+
+struct btchunk *
+allocchunk(struct btmeta *mp, int n)
+{
+ struct btchunk *p;
+
+ /* look if we have already that chunk */
+ LIST_FOREACH(p, &chead, link) {
+ if (p->piece == n) {
+ ++p->ref;
+ return p;
+ }
+ }
+
+ /* not found, allocate a new one */
+ p = malloc(sizeof(struct btchunk));
+ assert(p);
+ p->ref = 1;
+ p->piece = n;
+ p->sha1 = mp->pieces[n];
+ p->len = btsizeofpiece(mp, n);
+ p->data = calloc(p->len, sizeof(char));
+ assert(p->data);
+ if (btisset(mp->bitfield, n))
+ btread(mp, p->data, p->len, n);
+
+ return p;
+}
+
+int
+freechunk(struct btmeta *mp, int n)
+{
+ struct btchunk *p, *nxt;
+
+ for (p = LIST_FIRST(&chead); p != LIST_END(&chead); p = nxt) {
+ nxt = LIST_NEXT(p, link);
+ if (p->piece == n) {
+ /* do we have it already? */
+ if (btisset(mp->bitfield, n))
+ return 1;
+
+ /* check and write */
+ if (!btcheck(p->sha1, p->data, p->len) || !btwrite(mp, p->data, p->len, n))
+ return -1;
+ btsetbit(mp->bitfield, n);
+
+ if (--p->ref <= 0) {
+ /* nobody refers anymore to this piece */
+ LIST_REMOVE(p, link);
+ free(p->data);
+ free(p);
+ }
+
+ /* announce all that we have that piece */
+ return 0;
+ }
+ }
+
+ /* nothing found */
+ return -1;
+}
+
+
+
+
+#if 0
+
+/* put message on queue */
+/* needed vars:
+ data, length,
+ send/recv (*fp)()
+ socket
+ */
+
+void
+btputqueue(struct btqueue *head, struct btpeer *p, ssize_t (*fp)(int, void *, size_t, int))
+{
+ struct btqentry *q;
+
+ q = malloc(sizeof(struct btqentry));
+ assert(q);
+
+ q->data = p->data + p->offset;
+ q->length = p->length;
+ q->doit = fp;
+ q->sockd = p->sockd;
+
+ SIMPLEQ_INSERT_TAIL(head, q, link);
+}
+
+int
+nonblock(int s)
+{
+ int flags;
+
+ flags = fcntl(s, F_GETFL);
+ if (flags < 0 || fcntl(s, F_SETFL, flags|O_NONBLOCK) < 0)
+ return -1;
+
+ return s;
+}
+
+#endif
diff --git a/netio.h b/netio.h
new file mode 100644
index 0000000..1728d4f
--- /dev/null
+++ b/netio.h
@@ -0,0 +1,28 @@
+struct btchunk { /* rbuf: request buffer */
+ LIST_ENTRY(btchunk) link;
+ int ref; /* how many peers points here */
+ int piece; /* requested piece numer */
+ u_char *sha1;
+ size_t len;
+ char *data;
+
+ int nblk; /* bitfield of blocks ? */
+ int blkdone; /* increment by read or write
+ since that both operations
+ cannot be simultan it isn't
+ a problem */
+};
+
+// LIST_HEAD(btplist, btpiece);
+
+struct btrequest {
+ LIST_ENTRY(btrequest) link;
+ struct btpeer *peer;
+ struct btchunk *chunk;
+ size_t offset;
+ size_t length;
+ size_t transmitted; /* howmany is already read/written */
+
+};
+
+// LIST_HEAD(btrlist, btrequest);
diff --git a/peer.c b/peer.c
new file mode 100644
index 0000000..d8078b1
--- /dev/null
+++ b/peer.c
@@ -0,0 +1,487 @@
+/* $Id$ */
+/*
+ * Copyright (c) 2005 Dimitri Sokolyuk <demon@vhost.dyndns.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <assert.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "meta.h"
+#include "peer.h" /* not used */
+#include "tools.h" /* peerid() */
+#include "files.h"
+
+#ifndef lint
+static const char rcsid[] = "@(#)$Id$";
+#endif /* not lint */
+
+#define HSLEN 68
+#define PROTO "BitTorrent protocol"
+#define PROTOLEN 19
+#define FLAGSLEN 8
+
+// LIST_HEAD(btplist, btpeer) phead;
+
+char *
+btgethandshake(const u_char *info, const u_char *id)
+{
+ /* Handshake ::= <plen(1)> <pstr(19)> <flags(8)> <info_hash(20)> <peer_id(20)> */
+ static char buf[HSLEN];
+ int len, off;
+
+ off = 0; len = 1;
+ memset(buf, PROTOLEN, len);
+
+ off += len; len = PROTOLEN;
+ memcpy(buf + off, PROTO, len);
+
+ off += len; len = FLAGSLEN;
+ memset(buf + off, 0, len);
+
+ off += len; len = SHA1LEN;
+ memcpy(buf + off, info, len);
+
+ off += len; len = SHA1LEN;
+ if (id == NULL)
+ memset(buf + off, 0, len);
+ else
+ memcpy(buf + off, id, len);
+
+ return buf;
+}
+
+int
+btchkhandshake(const char *src, const u_char *info, const u_char *id)
+{
+ char buf[HSLEN], *etalon;
+
+ if (src == NULL)
+ return 0;
+
+ /* ignore flags */
+ memcpy(buf, src, HSLEN);
+ memset(buf + PROTOLEN + 1, 0, FLAGSLEN);
+ etalon = btgethandshake(info, id);
+
+ return (memcmp(buf, etalon, id == NULL ? HSLEN - SHA1LEN : HSLEN) == 0);
+}
+
+/**********************************************************************/
+
+int
+btaddpeer(struct btplist *plist, struct btpeer *peer)
+{
+ struct btpeer *p;
+
+ LIST_FOREACH(p, plist, link) {
+ if (memcmp(&peer->sin, &p->sin, sizeof(struct sockaddr_in)) == 0) {
+ /* found same peer, reset its ttl value */
+ p->ttl = MAX_TTL;
+ return 1;
+ }
+ }
+
+ /* initial values */
+ setbit(&peer->flags, LOCAL_CHOKED);
+ clrbit(&peer->flags, LOCAL_INTERESTED);
+ setbit(&peer->flags, REMOTE_CHOKED);
+ clrbit(&peer->flags, REMOTE_INTERESTED);
+ peer->ttl = MAX_TTL;
+ peer->state = IDLE;
+ LIST_INSERT_HEAD(plist, peer, link);
+
+ return 0;
+}
+
+void
+btdumpplist(struct btplist *plist)
+{
+ struct btpeer *p;
+ int i = 0;
+
+ LIST_FOREACH(p, plist, link) {
+ printf("%2d: %s:%d (%d) %c%c:%c%c\n",
+ ++i,
+ inet_ntoa(p->sin.sin_addr),
+ ntohs(p->sin.sin_port),
+ p->ttl,
+ "cC"[!!isset(&p->flags, LOCAL_CHOKED)],
+ "iI"[!!isset(&p->flags, LOCAL_INTERESTED)],
+ "cC"[!!isset(&p->flags, REMOTE_CHOKED)],
+ "iI"[!!isset(&p->flags, REMOTE_INTERESTED)]
+ );
+ }
+}
+
+int
+btclearoldpeers(struct btplist *plist)
+{
+ struct btpeer *p, *nxt;
+ int n = 0;
+
+ for (p = LIST_FIRST(plist); p != LIST_END(plist); p = nxt) {
+ nxt = LIST_NEXT(p, link);
+ if (--p->ttl <= 0 && p->state == FAILED) {
+ LIST_REMOVE(p, link);
+ free(p);
+ } else
+ ++n;
+ }
+
+ return n;
+}
+
+void
+btdelplist(struct btplist *plist)
+{
+ struct btpeer *p, *nxt;
+
+ for (p = LIST_FIRST(plist); p != LIST_END(plist); p = nxt) {
+ nxt = LIST_NEXT(p, link);
+ free(p);
+ }
+ LIST_INIT(plist);
+}
+
+void
+btdbg(struct btpeer *p, char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ fprintf(stderr, "%s:%d ",
+ inet_ntoa(p->sin.sin_addr),
+ ntohs(p->sin.sin_port));
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+
+/**********************************************************************/
+
+// newpeer()
+/* alloc space, alloc 68 bytes for msgbuf (handshake length) */
+
+#if 0
+int
+chkhandshake(struct btpeer *p)
+{
+ char buf[20];
+
+ read(p->sockd, buf, 1);
+ if (*buf != 19)
+ drop(p);
+ read(p->sockd, buf, 19);
+ if (memcmp(buf, "Bittorrent Protocol", 19) != 0)
+ drop(p);
+ read(p->sockd, buf, 8);
+ read(p->sockd, buf, 20);
+ if (memcmp(buf, mp->infohash, 20) != 0)
+ drop(p);
+ read(p->sockd, buf, 20);
+ memcpy(p->peerid, buf, 20);
+
+// nonblock(p->sockid);
+ return 0;
+}
+
+
+int
+readmsg(struct btpeer *p)
+{
+ size_t len, ret;
+
+ if (p->msgbuf == NULL) {
+ read(p->sockd, &len, sizeof(size_t));
+ p->buflen = ntohl(len);
+ p->msgbuf = malloc(p->buflen);
+ p->bufoff = 0;
+ } else {
+ ret = read(p->sockd, p->msgbuf + p->bufoff, p->buflen - p->bufoff);
+ p->bufoff += ret;
+ }
+
+ if (p->msgbuf != NULL && p->buflen - p->bufoff == 0) {
+ dispatch(p);
+ free(p->msgbuf);
+ p->msgbuf = NULL;
+ }
+}
+
+int
+dispatch(struct btmeta *mp, struct btpeer *p)
+{
+ int piece, offset, length;
+
+ switch (p->state) {
+ case IDLE:
+ case CONNECTING:
+ case HANDSHAKE:
+ case SUCCESS:
+ switch (*p->msgbuf) {
+ case MSG_CHOKE:
+ setbit(&p->flags, REMOTE_CHOKED);
+ break;
+ case MSG_UNCHOKE:
+ clrbit(&p->flags, REMOTE_CHOKED);
+ break;
+ case MSG_INTERESTED:
+ setbit(&p->flags, REMOTE_INTERESTED);
+ break;
+ case MSG_UNINTERESTED:
+ clrbit(&p->flags, REMOTE_INTERESTED);
+ break;
+ case MSG_HAVE:
+ piece = htonl(*(int *)&p->msgbuf[1]);
+ btsetbit(p->bitfield, piece);
+
+ sendhave();
+
+ break;
+ case MSG_BITFIELD:
+ memcpy(p->bitfield, &p->msgbuf[1], p->buflen);
+ break;
+ case MSG_REQUEST:
+ piece = ntohl(*(int *)&p->msgbuf[1]);
+ offset = ntohl(*(int *)&p->msgbuf[5]);
+ length = ntohl(*(int *)&p->msgbuf[9]);
+
+ btallocrequest(mp, p, piece, offset, length);
+ break;
+ case MSG_PIECE:
+ piece = ntohl(*(int *)&p->msgbuf[1]);
+ offset = ntohl(*(int *)&p->msgbuf[5]);
+ length = p->length - 9;
+
+ btwriterequest(p, piece, offset, length, &p->msgbuf[9]);
+ btfreerequest(mp, p, piece, offset, length);
+ break;
+ case MSG_CANCEL:
+ piece = ntohl(*(int *)&p->msgbuf[1]);
+ offset = ntohl(*(int *)&p->msgbuf[5]);
+ length = ntohl(*(int *)&p->msgbuf[9]);
+ btfreerequest(mp, p, piece, offset, length);
+ break;
+ }
+ break;
+ case FAILED:
+ }
+}
+
+
+#endif
+
+/**********************************************************************/
+
+/* messages:
+-1 keep alive | length int |
+ 0 choke | length int | id char |
+ 1 unchoke | length int | id char |
+ 2 interested | length int | id char |
+ 3 uninterested | length int | id char |
+ 4 bitfield | length int | id char | bitfield bfsz |
+ 5 have | length int | id char | piece int |
+ 6 request | length int | id char | piece int | offset int | length int |
+ 7 piece | length int | id char | piece int | offset int | data blksz |
+ 8 cancel | length int | id char | piece int | offset int | lenght int |
+ */
+
+char *
+btmessage(struct btmeta *m, struct btpeer *p, size_t *mlen)
+{
+ size_t off;
+ int tmp;
+ char *buf;
+
+ *mlen = sizeof(int);
+ if (p->msg >= MSG_CHOKE)
+ *mlen += sizeof(char);
+ if (p->msg == MSG_BITFIELD)
+ *mlen += m->bitlen;
+ if (p->msg >= MSG_HAVE)
+ *mlen += sizeof(int);
+ if (p->msg >= MSG_REQUEST) {
+ *mlen += sizeof(int);
+ if (p->msg == MSG_PIECE)
+ *mlen += BLOCKSZ;
+ else
+ *mlen += sizeof(int);
+ }
+
+ buf = calloc(*mlen, sizeof(char));
+ if (buf == NULL)
+ return NULL;
+
+ tmp = htonl(*mlen - sizeof(int));
+ memcpy(buf, &tmp, sizeof(int));
+ off = sizeof(int);
+
+ if (p->msg >= MSG_CHOKE) {
+ memcpy(buf + off, &p->msg, sizeof(char));
+ off += sizeof(char);
+ }
+ if (p->msg == MSG_BITFIELD)
+ memcpy(buf + off, m->bitfield, m->bitlen);
+ if (p->msg >= MSG_HAVE) {
+ tmp = htonl(p->piece);
+ memcpy(buf + off, &tmp, sizeof(int));
+ off += sizeof(int);
+ }
+ if (p->msg >= MSG_REQUEST) {
+ tmp = htonl(p->offset);
+ memcpy(buf + off, &tmp, sizeof(int));
+ off += sizeof(int);
+ if (p->msg == MSG_PIECE)
+ memcpy(buf + off, p->data, BLOCKSZ);
+ else {
+ tmp = htonl(p->length);
+ memcpy(buf + off, &tmp, sizeof(int));
+ }
+ }
+
+ return buf;
+}
+
+void
+handlemsg(struct btmeta *mp, struct btpeer *p, char *buf, size_t buflen)
+{
+ size_t length;
+ enum msg msg;
+ int piece, offset, blklen;
+
+ length = ntohl(*(int *)buf);
+ buf += sizeof(int);
+
+ if (length == 0)
+ return;
+
+ msg = *(char *)buf;
+ buf += sizeof(char);
+ length -= sizeof(char);
+
+ switch (msg) {
+ case MSG_CHOKE:
+ setbit(&p->flags, REMOTE_CHOKED);
+ break;
+ case MSG_UNCHOKE:
+ clrbit(&p->flags, REMOTE_CHOKED);
+ break;
+ case MSG_INTERESTED:
+ setbit(&p->flags, REMOTE_INTERESTED);
+ break;
+ case MSG_UNINTERESTED:
+ clrbit(&p->flags, REMOTE_INTERESTED);
+ break;
+ case MSG_HAVE:
+ assert(p->bitfield);
+ piece = htonl(*(int *)buf);
+ btsetbit(p->bitfield, piece);
+ break;
+ case MSG_BITFIELD:
+ if (p->bitfield == NULL) {
+ p->bitfield = calloc(length, sizeof(char));
+ assert(p->bitfield);
+ }
+ memcpy(p->bitfield, buf, length);
+ break;
+ case MSG_REQUEST:
+ piece = htonl(*(int *)buf);
+ buf += sizeof(int);
+ offset = htonl(*(int *)buf);
+ buf += sizeof(int);
+ blklen = htonl(*(int *)buf);
+
+ if (p->data == NULL) {
+ p->data = calloc(mp->plength, sizeof(char));
+ assert(p->data);
+ // btread(mp, p->data, piece);
+ }
+
+ /* XXX send, put on queue */
+
+ break;
+ case MSG_PIECE:
+ piece = htonl(*(int *)buf);
+ buf += sizeof(int);
+ offset = htonl(*(int *)buf);
+ blklen = buflen - sizeof(int) * 2;
+
+ if (p->data == NULL) {
+ p->data = calloc(mp->plength, sizeof(char));
+ assert(p->data);
+ }
+
+ /* XXX recieve, put on queue */
+
+ break;
+ case MSG_CANCEL:
+ piece = htonl(*(int *)buf);
+ buf += sizeof(int);
+ offset = htonl(*(int *)buf);
+ buf += sizeof(int);
+ blklen = htonl(*(int *)buf);
+
+ /* XXX remove from queue */
+
+ break;
+ default:
+ break;
+ }
+}
+
+#if 0
+payload(enum msg *msg)
+{
+ switch (msg) {
+ case MSG_CHOKE:
+ case MSG_UNCHOKE:
+ case MSG_INTERESTED:
+ case MSG_UNINTERESTED:
+ buf[0] = (int)4;
+ buf[3] = (char)msg;
+ }
+}
+
+btsndchoked(struct btpeer *p)
+{
+ /* | length | id */
+ int buf[2];
+
+ p->msg = MSG_CHOKE;
+
+ buf[0] = sizeof(p->msg);
+ buf[1] = p->msg;
+}
+
+btsndhave(struct btpeer *p)
+{
+ int buf[3];
+ p->msg = MSG_HAVE;
+
+ buf[0] = sizeof(p->msg) + sizeof(p->piece);
+ buf[1] = p->msg;
+ buf[2] = p->piece;
+}
+#endif
diff --git a/peer.h b/peer.h
new file mode 100644
index 0000000..9eccbe3
--- /dev/null
+++ b/peer.h
@@ -0,0 +1,130 @@
+/* $Id$ */
+/*
+ * Copyright (c) 2005 Dimitri Sokolyuk <demon@vhost.dyndns.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _PEER_H_
+#define _PEER_H_
+
+#if 0
+#include <sys/queue.h>
+#endif
+
+#if 0
+snum stage {
+ PIECELESS, /* threshold 5% */
+ NORMAL,
+ ENDGAME, /* threshold 95% */
+ SEEDER
+};
+#endif
+
+enum msg {
+ MSG_KEEPALIVE = -1, /* no payload at all */
+ MSG_CHOKE,
+ MSG_UNCHOKE,
+ MSG_INTERESTED,
+ MSG_UNINTERESTED,
+ MSG_HAVE,
+ MSG_BITFIELD,
+ MSG_REQUEST,
+ MSG_PIECE,
+ MSG_CANCEL
+};
+
+/* peer wire message */
+struct btpwm {
+ u_int length;
+ u_char id;
+ void *payload;
+};
+
+enum state {
+ IDLE,
+ CONNECTING,
+ HANDSHAKE,
+ SUCCESS,
+ FAILED
+};
+
+#define MAX_TTL 10
+#define BLOCKSZ (16<10)
+
+struct btpeerstat { /* isn't used jet */
+ unsigned int choked:1;
+ unsigned int interested:1;
+};
+
+enum flags {
+ LOCAL_CHOKED,
+ LOCAL_INTERESTED,
+ REMOTE_CHOKED,
+ REMOTE_INTERESTED,
+ OPTIMISTIC_CHOKING
+};
+
+struct btpeer {
+ LIST_ENTRY(btpeer) link;
+#if 1
+ int sockd;
+ struct sockaddr_in sin;
+#else
+ struct btsock sock;
+#endif
+ u_char peerid[SHA1LEN]; /* isn't known in `compact' mode */
+ enum msg msg; /* ??? local/remote */
+ enum state state;
+ int ttl; /* TODO: choose resonable value */
+ char flags; /* chocked / interested / ... */
+ char *bitfield; /* alloc space to hold bitfield and
+ data arrays on successful connect */
+#if 1 /* obsolete */
+ /* request part */
+ int piece;
+ off_t offset;
+ size_t length;
+ void *data_in; /* holds unfinished parts of piece */
+ void *data_out;
+ void *data;
+#endif
+
+ char *msgbuf;
+ size_t buflen;
+ size_t bufoff;
+// size_t expect; /* expect to read */
+
+ /* statistics */
+ int dl; /* download rate */
+ int ul; /* upload rate */
+ int rank;
+};
+
+/* todo: read first 4 bytes, allocate space in msgbuf, read entry message,
+ decide what to do with it */
+
+LIST_HEAD(btplist, btpeer);
+
+__BEGIN_DECLS
+char *btgethandshake(const u_char *, const u_char *);
+int btchkhandshake(const char *, const u_char *, const u_char *);
+
+void btdumpplist(struct btplist *);
+int btaddpeer(struct btplist *, struct btpeer *);
+int btclearoldpeers(struct btplist *);
+void btdelplist(struct btplist *);
+void btdbg(struct btpeer *, char *, ...);
+__END_DECLS
+
+#endif /* not _PEER_H_ */
diff --git a/tools.c b/tools.c
new file mode 100644
index 0000000..d9264f6
--- /dev/null
+++ b/tools.c
@@ -0,0 +1,230 @@
+/* $Id$ */
+/*
+ * Copyright (c) 2005 Dimitri Sokolyuk <demon@vhost.dyndns.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/time.h> /* struct timeval */
+#include <sys/queue.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/socket.h> /* setsockopt() */
+
+#include <ctype.h>
+#include <fcntl.h> /* open() */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h> /* strlcat() */
+#include <unistd.h> /* sysconf() */
+#include "meta.h" /* SHA1LEN */
+#include "tools.h"
+#include "version.h"
+
+#ifndef lint
+static const char rcsid[] = "@(#)$Id$";
+#endif /* not lint */
+
+int
+raisenofile(void)
+{
+ struct rlimit rl;
+
+ if (getrlimit(RLIMIT_NOFILE, &rl) == 0) {
+ rl.rlim_cur = rl.rlim_max;
+ return setrlimit(RLIMIT_NOFILE, &rl);
+ }
+ return -1;
+}
+
+/* usable file desctriptors */
+int
+nofile(void)
+{
+ struct rlimit rl;
+ struct stat st;
+ int i, ret;
+
+ if (getrlimit(RLIMIT_NOFILE, &rl) == 0) {
+ ret = i = rl.rlim_cur;
+ while (i)
+ if (!fstat(--i, &st))
+ --ret;
+ return ret;
+ }
+ return -1;
+}
+
+
+int
+bthexdump(char *dst, u_char *src, int len)
+{
+ /* src is a SHA1 hash and is 20 bytes long
+ * dst should be at least 41 bytes long for a simple hexdump,
+ * and 61 bytes long for a quoted printable
+ * (including the terminating `\0' character)
+ */
+ int i, done;
+ char *fmt;
+
+ if (len <= 2 * SHA1LEN)
+ return -1;
+
+ for (i = SHA1LEN, done = 0, *dst = NULL; i > 0; i--) {
+ if (len > 3 * SHA1LEN) {
+ if (isascii(*src) && (isalnum(*src) ||
+ *src == '-' || *src == '.' ||
+ *src == '_' || *src == '~'))
+ fmt = "%c";
+ else
+ fmt = "%%%.2X";
+ } else
+ fmt = "%.2x";
+ done += snprintf(dst + done, len - done, fmt, *src++);
+ }
+
+ return done;
+}
+
+char *
+btfreadall(char *name)
+{
+ FILE *fd;
+ char *buf;
+ off_t len;
+
+ if ((fd = fopen(name, "r")) == NULL)
+ return NULL;
+
+ fseek(fd, 0L, SEEK_END);
+ len = ftell(fd);
+ fseek(fd, 0L, SEEK_SET);
+
+ if ((buf = calloc(len, sizeof(char))) != NULL)
+ if ((fread(buf, sizeof(char), len, fd)) == 0) {
+ free(buf);
+ return NULL;
+ }
+
+ fclose(fd);
+
+ return buf;
+}
+
+#if MMAP
+void *
+btmmapfile(char *name, off_t *len)
+{
+ struct stat stat;
+ int fd;
+ off_t page;
+ void *buf;
+
+ if ((fd = open(name, O_RDONLY)) == -1) {
+ perror("open");
+ return NULL;
+ }
+
+ if (fstat(fd, &stat) == -1) {
+ perror("fstat");
+ close(fd);
+ return NULL;
+ }
+
+ page = sysconf(_SC_PAGESIZE);
+ *len = stat.st_size;
+ if (*len == 0)
+ return NULL;
+ *len += page - (*len % page);
+
+ if ((buf = mmap((void *) NULL, *len, PROT_READ, MAP_SHARED, fd, 0L)) == NULL) {
+ perror("mmap");
+ close(fd);
+ return NULL;
+ }
+
+ close(fd);
+
+ return buf;
+}
+
+void
+btunmapfile(void *buf, off_t len)
+{
+ munmap(buf, len);
+}
+#endif /* MMAP */
+
+void
+btpeerid(u_char *str)
+{
+ const char hex[16] = "0123456789ABCDEF";
+ int i;
+
+ /* we only use unreserved characters (RFC 3986) as PEER_ID,
+ * so we don't need to percent-encode them and
+ * it's still random enough (16^12 possibilities)
+ * OBSOLETE since we use array to store that value
+ */
+ snprintf((char *) str, 9, "-XB%.2d%.2d-", VERSION_MAJOR, VERSION_MINOR);
+ for (i = 8; i < SHA1LEN; i++)
+ str[i] = hex[arc4random() % sizeof(hex)];
+}
+
+__dead void
+usage(const char *usagearg)
+{
+ extern char *__progname;
+
+ fprintf(stderr, "usage: %s %s\n", __progname, usagearg);
+ exit(1);
+}
+
+int
+setrcvtimeo(int s, int msec)
+{
+ struct timeval tv;
+
+ tv.tv_sec = msec / 1000;
+ tv.tv_usec = (msec % 1000) * 1000;
+
+ return setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
+}
+
+void
+twiddle(void)
+{
+ static int pos;
+
+ putchar("|/-\\"[pos++ & 3]);
+ putchar('\b');
+}
+
+char *
+metric(double len)
+{
+ static char buf[16];
+ char *units[] = {
+ "B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", NULL
+ };
+ int n = 0;
+
+ for (n = 0; len >= 1024 && n < sizeof(units) / sizeof(units[0]); n++)
+ len /= 1024;
+
+ snprintf(buf, sizeof(buf), "%.2f %s", len, units[n]);
+
+ return buf;
+}
diff --git a/tools.h b/tools.h
new file mode 100644
index 0000000..4ed4717
--- /dev/null
+++ b/tools.h
@@ -0,0 +1,40 @@
+/* $Id$ */
+/*
+ * Copyright (c) 2005 Dimitri Sokolyuk <demon@vhost.dyndns.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _TOOLS_H_
+#define _TOOLS_H_
+
+#define HASHSZS 41 /* 2 * SHA1LEN + 1 */
+#define HASHSZL 61 /* 3 * SHA1LEN + 1 */
+
+__BEGIN_DECLS
+int raisenofile(void);
+int nofile(void);
+int bthexdump(char *, u_char *, int);
+char *btfreadall(char *);
+#if MMAP
+void *btmmapfile(char *, off_t *);
+void btunmapfile(void *, off_t);
+#endif /* MMAP */
+void btpeerid(u_char *);
+__dead void usage(const char *);
+int setrcvtimeo(int, int);
+void twiddle(void);
+char *metric(double);
+__END_DECLS
+
+#endif /* not _TOOLS_H_ */
diff --git a/tracker.c b/tracker.c
new file mode 100644
index 0000000..599b214
--- /dev/null
+++ b/tracker.c
@@ -0,0 +1,398 @@
+/* $Id$ */
+/*
+ * Copyright (c) 2005 Dimitri Sokolyuk <demon@vhost.dyndns.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h> /* htons() */
+#include <sys/time.h>
+#include <sys/socket.h> /* AF_INET */
+#include <sys/queue.h>
+#include <netinet/in.h>
+#include <arpa/inet.h> /* inet_ntoa() */
+#include <netdb.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h> /* getpagesize() */
+
+#include "bencode.h"
+#include "meta.h"
+#include "peer.h"
+#include "tracker.h"
+#include "tools.h"
+#include "version.h"
+
+#ifndef lint
+static const char rcsid[] = "@(#)$Id$";
+#endif /* not lint */
+
+#if FAILURE
+/* ref: http://wiki.theory.org/BitTorrentTrackerProtocol */
+static const struct {
+ int code;
+ char *msg;
+} failure[] = {
+ { 100, "Invalid Request Type" },
+ { 101, "Missing `info_hash'" },
+ { 102, "Missing `peer_id'" },
+ { 103, "Missing `port'" },
+ { 150, "Invalid `info_hash'" },
+ { 151, "Invalid `peer_id'" },
+ { 152, "Invalid `numwait'" },
+ { 200, "`info_hash' not found in database" },
+ { 500, "Client sent an event request before the specified time" },
+ { 900, "Generic Error" },
+};
+#endif
+
+static int parseanswer(struct bttracker *, char *);
+static int parseplist(struct bttracker *, struct btnode *);
+static char *buildrequest(struct bttracker *);
+static int fillsin(char *, int, struct sockaddr_in *);
+static int startlistener(int *);
+
+static int
+parseanswer(struct bttracker *tp, char *src)
+{
+ struct btnode *nhead, *np;
+
+ src = strstr(src, "\r\n\r\n") + 4;
+
+ if ((nhead = btbencode(src)) == NULL)
+ errx(1, "fatal error: %s\n", src);
+
+ if ((np = btfinddict(nhead, "failure reason")) != NULL)
+ errx(1, "failure reason: %s\n", np->str);
+
+#if FAILURE
+ if ((np = btfinddict(nhead, "failure code")) != NULL) {
+ int i;
+ for (i = 0; failure[i].code != 900; i++)
+ if (np->num == failure[i].code)
+ break;
+ errx(1, failure[i].msg);
+ }
+#endif
+
+ if ((np = btfinddict(nhead, "warning message")) != NULL)
+ warnx("warning message: %s\n", np->str);
+
+ if ((np = btfinddict(nhead, "interval")) != NULL)
+ tp->interval = np->num;
+
+ if ((np = btfinddict(nhead, "min interval")) != NULL)
+ tp->minterval = np->num;
+
+ if ((np = btfinddict(nhead, "tracker id")) != NULL)
+ memcpy(tp->trackerid, np->str, SHA1LEN);
+
+ if ((np = btfinddict(nhead, "complete")) != NULL)
+ tp->complete = np->num;
+
+ if ((np = btfinddict(nhead, "incomplete")) != NULL)
+ tp->incomplete = np->num;
+
+ if ((np = btfinddict(nhead, "peers")) != NULL)
+ parseplist(tp, np);
+
+#if 0 /* removing dead peers is senseless;
+ collect all peers until we finish downloading */
+ tp->npeers = btclearoldpeers(&tp->plist);
+#endif
+ btfreenode(nhead);
+ return 0;
+}
+
+static int
+parseplist(struct bttracker *tp, struct btnode *node)
+{
+ struct btnode *np;
+ struct btpeer *p;
+ char *host;
+ int port, n;
+
+ switch (node->type) {
+ case NODE_LIST:
+ /* if the tracker answers with the peer list
+ * remove the compact flag for further requests */
+ tp->compact = COMP_NO;
+
+ for (; node != NULL; node = node->next) {
+ if ((p = calloc(1, sizeof(struct btpeer))) == NULL)
+ return -1;
+
+ host = NULL;
+ port = 0;
+
+ if ((np = btfinddict(node->list, "peer id")) != NULL)
+ memcpy(&p->peerid, np->str, SHA1LEN);
+ if ((np = btfinddict(node->list, "ip")) != NULL)
+ host = np->str;
+ if ((np = btfinddict(node->list, "port")) != NULL)
+ port = np->num;
+
+ if (host != NULL) {
+ fillsin(host, port, &p->sin);
+ if (btaddpeer(&tp->plist, p))
+ free(p);
+ }
+ }
+ break;
+ case NODE_STR: /* compact mode */
+ tp->compact = COMP_YES; /* just to be sure */
+ n = node->len / 6;
+
+ while (n-- != 0) {
+ if ((p = calloc(1, sizeof(struct btpeer))) == NULL)
+ return -1;
+
+ p->sin.sin_family = AF_INET;
+ memcpy(&p->sin.sin_addr, node->str + 6 * n, 4);
+ memcpy(&p->sin.sin_port, node->str + 6 * n + 4, 2);
+
+ if (btaddpeer(&tp->plist, p) == 1)
+ free(p);
+ }
+ break;
+ default:
+ return -1;
+ }
+
+ return 0;
+}
+
+static const char *event[] = { NULL, "started", "completed", "stopped" };
+
+static char *
+buildrequest(struct bttracker *tp)
+{
+ char hash[HASHSZL];
+ char *buf;
+ int len;
+ size_t buflen = 1024;
+
+ if ((buf = calloc(buflen, sizeof(char))) == NULL)
+ return NULL;
+
+ bthexdump(hash, tp->infohash, sizeof(hash));
+ len = snprintf(buf, buflen, "GET /%s?info_hash=%s", tp->announce->path, hash);
+
+ bthexdump(hash, tp->localid, sizeof(hash));
+ len += snprintf(buf + len, buflen - len, "&peer_id=%s", hash);
+
+ len += snprintf(buf + len, buflen - len, "&port=%d", tp->localport);
+
+ if (tp->key != NULL)
+ len += snprintf(buf + len, buflen - len, "key=%s", tp->key);
+
+ if (*tp->trackerid != NULL) {
+ bthexdump(hash, tp->trackerid, sizeof(hash));
+ len += snprintf(buf + len, buflen - len, "trackerid=%s", hash);
+ }
+
+ len += snprintf(buf + len, buflen - len,
+ "&uploaded=%lld&downloaded=%lld&left=%lld",
+ tp->uploaded, tp->downloaded, tp->left);
+
+ if (tp->compact != COMP_NO)
+ len += snprintf(buf + len, buflen - len,
+ "&compact=%d", tp->compact);
+
+ if (tp->numwait != 0)
+ len += snprintf(buf + len, buflen - len,
+ "&numwait=%d", tp->numwait);
+
+ if (tp->event != EV_NONE)
+ len += snprintf(buf + len, buflen - len,
+ "&event=%s", event[tp->event]);
+
+ len += snprintf(buf + len, buflen - len,
+ " HTTP/1.0\nHost: %s\nUser-Agent: %s/%d.%d\n\n",
+ tp->announce->host, USERAGENT, VERSION_MAJOR, VERSION_MINOR);
+
+ return buf;
+}
+
+/* TODO: review and add proper error handling
+ * as well as move it all into main select loop
+ */
+int
+btcalltracker(struct bttracker *tp)
+{
+ char *buf;
+ size_t buflen, offset;
+ int sockd, ret;
+ struct timeval tv;
+
+ if (gettimeofday(&tv, NULL) == -1)
+ err(1, "gettimeofday");
+ tp->lastrqst = tv.tv_sec; /* set time mark */
+
+ if ((sockd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
+ err(1, "socket");
+ if (connect(sockd, (struct sockaddr *)&tp->sin,
+ sizeof(struct sockaddr)) == -1)
+ err(1, "connect");
+
+ if ((buf = buildrequest(tp)) == NULL)
+ return -1;
+#if DEBUGTRACKER
+ printf("BUF (%d): %s\n", strlen(buf), buf);
+#endif
+ if (send(sockd, buf, strlen(buf), 0) == -1)
+ err(1, "send");
+ free(buf);
+
+ buflen = sysconf(_SC_PAGESIZE); /* XXX */
+ if ((buf = calloc(buflen, sizeof(char))) == NULL)
+ err(1, "alloc");
+
+ offset = 0;
+ for(;;) {
+ if (offset >= buflen) {
+ char *nbuf;
+ buflen *= 2;
+ if ((nbuf = realloc(buf, buflen)) == NULL)
+ err(1, "realloc");
+ buf = nbuf;
+ }
+ if ((ret = recv(sockd, buf + offset, buflen - offset, 0)) == -1)
+ err(1, "recv");
+ if (ret == 0)
+ break;
+ offset += ret;
+ }
+ close(sockd);
+#if DEBUGTRACKER
+ /* remove all '\0' chars */
+ if (strlen(buf) != 0) {
+ while ((strlen(buf)) < buflen)
+ buf[strlen(buf)] = ' ';
+ buf[buflen] = '\0';
+ }
+ printf("BUF (%d): %s\n", buflen, buf);
+#endif
+
+ ret = parseanswer(tp, buf);
+ free(buf);
+
+ return ret;
+}
+
+struct bttracker *
+btinittracker(struct btmeta *mp, int port)
+{
+ struct bttracker *tp;
+
+ if ((tp = calloc(1, sizeof(struct bttracker))) == NULL)
+ return NULL;
+
+ if (fillsin(mp->announce->host, mp->announce->port, &tp->sin) == -1)
+ return NULL;
+
+ tp->announce = mp->announce;
+ tp->infohash = mp->infohash;
+ btpeerid(tp->localid);
+
+ if ((tp->listener = startlistener(&port)) == -1)
+ err(1, "listen");
+ tp->localport = port;
+
+ tp->compact = COMP_YES; /* we start always in compact mode */
+ *tp->trackerid = '\0'; /* isn't known at begin */
+ tp->downloaded = 0;
+ tp->uploaded = 0;
+ tp->left = mp->left;
+
+ tp->event = EV_STARTED; /* init value */
+
+ LIST_INIT(&tp->plist);
+
+ return tp;
+}
+
+/* TODO: if at all it should be called from `statistics calulator' */
+void
+btupdatetracker(struct bttracker *tp, int dl, int ul, enum event e)
+{
+ tp->downloaded += dl;
+ tp->uploaded += ul;
+ tp->event = e;
+ /* XXX tp->left = mp->left; */
+}
+
+void
+btfreetracker(struct bttracker *tp)
+{
+ btdelplist(&tp->plist);
+ free(tp);
+}
+
+static int
+fillsin(char *host, int port, struct sockaddr_in *sin)
+{
+ struct hostent *h;
+
+ memset(sin, 0, sizeof(struct sockaddr_in));
+ sin->sin_family = AF_INET;
+ if (inet_aton(host, &sin->sin_addr) == 0) {
+ h = gethostbyname(host);
+ if (h == NULL) {
+ err(1, "gethostbyname");
+ return -1;
+ }
+ memcpy(&sin->sin_addr, h->h_addr, h->h_length);
+ }
+ sin->sin_port = htons(port);
+
+ return 0;
+}
+
+int
+startlistener(int *port)
+{
+ struct sockaddr_in sin;
+ int maxport, yes = 1;
+ int sockd;
+
+ maxport = *port + 8;
+ if (maxport > 65535)
+ maxport = 65535;
+
+ if ((sockd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
+ return -1;
+
+ if (setsockopt(sockd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1)
+ return -1;
+
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_ANY);
+ memset(&sin.sin_zero, '\0', 8);
+
+ for (; *port <= maxport; *port++) {
+ sin.sin_port = htons(*port);
+ if (bind(sockd, (struct sockaddr *)&sin, sizeof(sin)) == 0)
+ break;
+ }
+
+ if (*port > maxport || listen(sockd, SOMAXCONN) == -1) {
+ close(sockd);
+ return -1;
+ }
+
+ return sockd;
+}
diff --git a/tracker.h b/tracker.h
new file mode 100644
index 0000000..4683715
--- /dev/null
+++ b/tracker.h
@@ -0,0 +1,74 @@
+/* $Id$ */
+/*
+ * Copyright (c) 2005 Dimitri Sokolyuk <demon@vhost.dyndns.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _TRACKER_H_
+#define _TRACKER_H_
+
+#if 0
+#include <sys/queue.h>
+#endif
+
+enum compact {
+ COMP_NO,
+ COMP_YES
+};
+
+enum event {
+ EV_NONE,
+ EV_STARTED,
+ EV_COMPLETED,
+ EV_STOPPED
+};
+
+struct bttracker {
+ struct sockaddr_in sin; /* tracker */
+ int listener; /* sockd */
+ /* request part */
+ struct btannounce *announce;
+ u_char *infohash; /* meta->infohash */
+ u_char localid[SHA1LEN];
+ u_char trackerid[SHA1LEN];
+ char *key; /* ??? */
+ int localport; /* my port, should be set by btstartserver() */
+
+ off_t uploaded; /* statistics */
+ off_t downloaded;
+ off_t left;
+
+ enum event event;
+ enum compact compact;
+ int numwait;
+
+ /* answer part */
+ struct btplist plist; /* LIST_HEAD */
+
+ long lastrqst;
+ int interval;
+ int minterval;
+ int complete;
+ int incomplete;
+ int npeers;
+};
+
+__BEGIN_DECLS
+struct bttracker *btinittracker(struct btmeta *, int);
+int btcalltracker(struct bttracker *);
+void btupdatetracker(struct bttracker *, int, int, enum event);
+void btfreetracker(struct bttracker *);
+__END_DECLS
+
+#endif /* not _TRACKER_H_ */
diff --git a/version.h b/version.h
new file mode 100644
index 0000000..3c4d834
--- /dev/null
+++ b/version.h
@@ -0,0 +1,9 @@
+/* $Id$ */
+#ifndef _VERSION_H_
+#define _VERSION_H_
+
+#define USERAGENT "XBitTorrent"
+#define VERSION_MAJOR 1
+#define VERSION_MINOR 0
+
+#endif /* Not _VERSION_H_ */