libeech

BitTorrent library
git clone git://z3bra.org/libeech.git
Log | Files | Refs | README | LICENSE

commit 25c4a9d5da6502bfe86acf4b83a58097397c3e00
Author: z3bra <contactatz3bradotorg>
Date:   Tue Oct 17 19:27:29 +0200

Initial commit

Diffstat:
LICENSE | 13+++++++++++++
README | 34++++++++++++++++++++++++++++++++++
be.c | 318+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
be.h | 29+++++++++++++++++++++++++++++
config.mk | 12++++++++++++
libeech.c | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
libeech.h | 16++++++++++++++++
makefile | 23+++++++++++++++++++++++
sha1.c | 272+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
sha1.h | 19+++++++++++++++++++
torrent.c | 22++++++++++++++++++++++
util.c | 18++++++++++++++++++
util.h | 3+++
13 files changed, 846 insertions(+), 0 deletions(-)
diff --git a/LICENSE b/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2016 Willy Goiffon <willyatmailoodotorg> + +Permission to use, copy, modify, and/or 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. diff --git a/README b/README @@ -0,0 +1,34 @@ +libeech +======= + +BitTorrent library + +The libeech BitTorrent library (will) implement all the functionality +described in the bittorrent protocol RFC[0], as well as the distributed +hash table (DHT) protocol[1]. + +Installation +------------ +Edit the `config.mk` file to match your setup, then run: + + $ make + # make install + +Usage +----- +Include the header file as follow: + + #include <libeech.h> + +Link your program against it with + + cc pgm.c -leech -o pgm + +Until v1.0 is complete, please check the `leec.h` for the API. + +License +------- +ISC License. See LICENSE file for copyright and license details. + +[0] http://jonas.nitro.dk/bittorrent/bittorrent-rfc.html +[1] https://en.wikipedia.org/wiki/Distributed_hash_table diff --git a/be.c b/be.c @@ -0,0 +1,318 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <limits.h> +#include <string.h> + +#include "be.h" + +#define MIN(a,b) ((a)<(b)?(a):(b)) +#define MAX(a,b) ((a)>(b)?(a):(b)) + +int +beinit(struct be *b, char *s, size_t l) +{ + if (!b || !s || !l) + return -1; + + memset(b, 0, sizeof(*b)); + b->start = s; + b->end = b->start + l - 1; + b->off = b->start; + + return 0; +} + +int +beatol(char **str, long *l) +{ + long s = 1; + long v = 0; + char *sp = *str; + + if (!sp) + return -1; + + /* define the sign of our number */ + if (*sp == '-') { + s = -1; + sp++; + /* -0 is invalid, even for "-03" */ + if (*sp == '0') + return -1; + } + + /* 0 followed by a number is considered invalid */ + if (sp[0] == '0' && isdigit(sp[1])) + return -1; + + /* read out number until next non-number, or end of string */ + while(isdigit(*sp)) { + v *= 10; + v += *sp++ - '0'; + } + + if (l) + *l = v * s; + + /* move initial pointer to the actual read data */ + *str = sp; + + return 0; +} + +ssize_t +beint(struct be *b, long *n) +{ + char *sp; + long num; + + if (!b) + return -1; + + sp = b->off; + + if (*(sp++) != 'i') + return -1; + + beatol(&sp, &num); + + if (*sp != 'e') + return -1; + + if (n) + *n = num; + + return sp - b->off + 1; +} + +ssize_t +bestr(struct be *b, char **s, size_t *l) +{ + char *sp; + ssize_t len; + + if (!b) + return -1; + + sp = b->off; + + if (beatol(&sp, &len) < 0) + return -1; + + if (len < 0 || *sp++ != ':') + return -1; + + if (s) + *s = sp; + + if (l) + *l = (size_t)len; + + return sp - b->off + len; +} + +ssize_t +belist(struct be *b, size_t *n) +{ + size_t c = 0; + struct be i; + + if (!b) + return -1; + + beinit(&i, b->off, b->end - b->off + 1); + + while(belistover(&i)) { + belistnext(&i); + c++; + } + + if (*i.off == 'e') + i.off++; + + if (n) + *n = c; + + return i.off - b->off; +} + +ssize_t +bedict(struct be *b, size_t *n) +{ + size_t c = 0; + struct be i; + + if (!b) + return -1; + + beinit(&i, b->off, b->end - b->off + 1); + + while(!belistover(&i)) { + bedictnext(&i, NULL, NULL, NULL); + c++; + } + + if (*i.off == 'e') + i.off++; + + if (n) + *n = c; + + return i.off - b->off; +} + +int +belistover(struct be *b) { + return b->off >= b->end || *b->off == 'e'; +} + +int +belistnext(struct be *b) +{ + if (!b || *b->off == 'e') + return -1; + + if (b->off == b->start && *b->off == 'l') { + b->off++; + return 0; + } + + return benext(b) > 0; +} + +int +bedictnext(struct be *b, char **k, size_t *l, struct be *v) +{ + if (!b || *b->off == 'e') + return -1; + + /* move to first element if we're at the start */ + if (b->off == b->start && *b->off == 'd') + b->off++; + + /* retrieve key name and length */ + if (bestr(b, k, l) < 0) + return -1; + + if (benext(b) > 0 && v) + beinit(v, b->off, b->end - b->off + 1); + + return benext(b) > 0; +} + +ssize_t +benext(struct be *b) +{ + int r = 0; + + if (!b) + return -1; + + /* check for end of buffer */ + if (b->off >= b->end) + return -1; + + /* TODO: implement betype() */ + switch(betype(b)) { + case 'i': + r = beint(b, NULL); + break; + case 'l': + r = belist(b, NULL); + break; + case 'd': + r = bedict(b, NULL); + break; + case 's': + r = bestr(b, NULL, NULL); + break; + } + + b->off += r; + + return r; +} + +char +betype(struct be *b) +{ + switch(*b->off) { + case 'i': + case 'l': + case 'd': + return *b->off; + break; /* NOTREACHED */ + } + return isdigit(*b->off) ? 's' : 0; +} + +int +bekv(struct be *b, char *k, size_t l, struct be *v) +{ + char *key = NULL; + size_t klen = 0; + struct be i; + + if (!b) + return -1; + + if (*b->off != 'd') + return -1; + + beinit(&i, b->off, b->end - b->off + 1); + + /* search the data 'till the end */ + while (!belistover(&i) && bedictnext(&i, &key, &klen, v)) { + /* we found our key! */ + if (!strncmp(k, key, MIN(l, klen))) + return 0; + + /* recursive call to search inner dictionaries */ + if (betype(&i) == 'd' && !bekv(&i, k, l, v)) + return 0; + } + + /* couldn't find anything, sorry */ + return -1; +} + +int +bepath(struct be *b, char **p, size_t l) +{ + char *s; + size_t r; + struct be i; + + if (!b || betype(b) != 'l') + return -1; + + beinit(&i, b->off, b->end - b->off + 1); + + while(belistnext(&i) && !belistover(&i)) { + if (!bestr(&i, &s, &r)) + continue; + strncat(*p, "/", l); + strncat(*p, s, r); + } + return 0; +} + +char * +bekstr(struct be *b, char *k, size_t kl) +{ + char *sp; + size_t vl; + struct be v; + static char s[LINE_MAX]; + + memset(s, 0, LINE_MAX); + if (bekv(b, k, kl, &v) < 0) + return NULL; + if (bestr(&v, &sp, &vl) < 0) + return NULL; + + memcpy(s, sp, MIN(vl, LINE_MAX)); + s[MIN(vl, LINE_MAX - 1)] = 0; + + return s; +} diff --git a/be.h b/be.h @@ -0,0 +1,29 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef _BE_H +#define _BE_H + +#include <stdint.h> +#include <unistd.h> + +struct be { + char *start; + char *end; + char *off; +}; + +int beinit(struct be *, char *, size_t); +int beatol(char **, long *); +ssize_t beint(struct be *, long *); +ssize_t bestr(struct be *, char **, size_t *); +ssize_t belist(struct be *, size_t *); +ssize_t bedict(struct be *, size_t *); +ssize_t benext(struct be *); +int belistover(struct be *); +int belistnext(struct be *); +int bedictnext(struct be *, char **, size_t *, struct be *); +char betype(struct be *); +int bekv(struct be *, char *, size_t, struct be *); +int bepath(struct be *, char **, size_t); +char * bekstr(struct be *, char *, size_t); + +#endif diff --git a/config.mk b/config.mk @@ -0,0 +1,12 @@ +VERSION = 0.0 + +CC = cc +LD = ${CC} + +PREFIX = /usr/local +MANDIR = ${PREFIX}/man + +CPPFLAGS = -DVERSION=\"${VERSION}\" +CFLAGS = ${CPPFLAGS} -Wall -Wextra -pedantic -g +LDFLAGS = +LDLIBS = diff --git a/libeech.c b/libeech.c @@ -0,0 +1,67 @@ +/* See LICENSE file for copyright and license details. */ +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "be.h" +#include "sha1.h" +#include "util.h" +#include "libeech.h" + +static char * peerid(); + +static char * +peerid() +{ + int n; + char hexa[40]; + unsigned char hash[20]; + static char peerid[21] = "-GT0000-XXXXXXXXXXXX"; + + srand(time(NULL)); /* good-enough seed */ + n = rand(); + snprintf(hexa, 40, "%08x%08x\n", n, ~n); + sha1((unsigned char *)hexa, 16, hash); + memcpy(peerid + 8, tohex(hash, hexa, 12), 12); + + return peerid; +} + +int +glch_loadtorrent(struct torrent *t, char *path) +{ + FILE *f; + char *b, *sp; + struct stat sb; + /* read torrent file into a memory buffer */ + if (stat(path, &sb)) + return -1; + if (!(f = fopen(path, "r"))) + return -1; + b = malloc(sb.st_size); + fread(b, 1, sb.st_size, f); + b[sb.st_size - 1] = '\0'; + fclose(f); + + /* retrieve "announce" key */ + beinit(&t->be, b, sb.st_size); + sp = bekstr(&t->be, "announce", 8); + if (!sp) + return -1; + memcpy(t->tr, sp, strlen(sp)); + + sp = peerid(); + if (!sp) + return -1; + memcpy(t->id, sp, 20); + t->id[20] = 0; + + return 0; +} diff --git a/libeech.h b/libeech.h @@ -0,0 +1,16 @@ +/* See LICENSE file for copyright and license details. */ +#include <limits.h> + +#include "be.h" + +#define PCESIZ 1048576 +#define BLKSIZ 16384 +#define MSGSIZ ((BLKSIZ) + 13) + +struct torrent { + char id[21]; + char tr[PATH_MAX]; + struct be be; +}; + +int glch_loadtorrent(struct torrent *, char *); diff --git a/makefile b/makefile @@ -0,0 +1,23 @@ +include config.mk + +all: libeech.a torrent +libeech.a: libeech.a(libeech.o) libeech.a(util.o) libeech.a(be.o) libeech.a(sha1.o) +libeech.o: libeech.c libeech.h + +torrent: torrent.c libeech.a + $(CC) $< -I. -L. -leech -o $@ +.o.a: + $(AR) rcs $@ $< + +install: libeech.a libeech.h + mkdir -p $(DESTDIR)$(PREFIX)/lib + mkdir -p $(DESTDIR)$(PREFIX)/include + cp libeech.a $(DESTDIR)$(PREFIX)/lib/ + cp libeech.h $(DESTDIR)$(PREFIX)/include/ + +uninstall: + rm $(DESTDIR)$(PREFIX)/lib/libeech.a + rm $(DESTDIR)$(PREFIX)/include/libeech.h + +clean: + rm -f libeech.a torrent *.o diff --git a/sha1.c b/sha1.c @@ -0,0 +1,272 @@ +/* LibTomCrypt, modular cryptographic library -- Tom St Denis + * + * LibTomCrypt is a library that provides various cryptographic + * algorithms in a highly modular and flexible manner. + * + * The library is free for all purposes without any express + * guarantee it works. + */ + +#include <stddef.h> +#include <stdint.h> + +#include "sha1.h" + +#define F0(x,y,z) (z ^ (x & (y ^ z))) +#define F1(x,y,z) (x ^ y ^ z) +#define F2(x,y,z) ((x & y) | (z & (x | y))) +#define F3(x,y,z) (x ^ y ^ z) + +#define ROL(x,n) ( (((x)<<(n))&(UINT32_MAX))|(((x)>>(sizeof(x)*8-(n)))&(UINT32_MAX)) ) + +#define MAX(x, y) ( ((x)>(y))?(x):(y) ) +#define MIN(x, y) ( ((x)<(y))?(x):(y) ) + +#define STORE32L(x, y) \ + do { (y)[3] = (unsigned char)(((x)>>24)&255); (y)[2] = (unsigned char)(((x)>>16)&255); \ + (y)[1] = (unsigned char)(((x)>>8)&255); (y)[0] = (unsigned char)((x)&255); } while(0) + +#define LOAD32L(x, y) \ + do { x = ((uint32_t)((y)[3] & 255)<<24) | \ + ((uint32_t)((y)[2] & 255)<<16) | \ + ((uint32_t)((y)[1] & 255)<<8) | \ + ((uint32_t)((y)[0] & 255)); } while(0) + +#define STORE64L(x, y) \ + do { (y)[7] = (unsigned char)(((x)>>56)&255); (y)[6] = (unsigned char)(((x)>>48)&255); \ + (y)[5] = (unsigned char)(((x)>>40)&255); (y)[4] = (unsigned char)(((x)>>32)&255); \ + (y)[3] = (unsigned char)(((x)>>24)&255); (y)[2] = (unsigned char)(((x)>>16)&255); \ + (y)[1] = (unsigned char)(((x)>>8)&255); (y)[0] = (unsigned char)((x)&255); } while(0) + +#define LOAD64L(x, y) \ + do { x = (((uint64_t)((y)[7] & 255))<<56)|(((uint64_t)((y)[6] & 255))<<48)| \ + (((uint64_t)((y)[5] & 255))<<40)|(((uint64_t)((y)[4] & 255))<<32)| \ + (((uint64_t)((y)[3] & 255))<<24)|(((uint64_t)((y)[2] & 255))<<16)| \ + (((uint64_t)((y)[1] & 255))<<8)|(((uint64_t)((y)[0] & 255))); } while(0) + +#define STORE32H(x, y) \ + do { (y)[0] = (unsigned char)(((x)>>24)&255); (y)[1] = (unsigned char)(((x)>>16)&255); \ + (y)[2] = (unsigned char)(((x)>>8)&255); (y)[3] = (unsigned char)((x)&255); } while(0) + +#define LOAD32H(x, y) \ + do { x = ((uint32_t)((y)[0] & 255)<<24) | \ + ((uint32_t)((y)[1] & 255)<<16) | \ + ((uint32_t)((y)[2] & 255)<<8) | \ + ((uint32_t)((y)[3] & 255)); } while(0) + +#define STORE64H(x, y) \ +do { (y)[0] = (unsigned char)(((x)>>56)&255); (y)[1] = (unsigned char)(((x)>>48)&255); \ + (y)[2] = (unsigned char)(((x)>>40)&255); (y)[3] = (unsigned char)(((x)>>32)&255); \ + (y)[4] = (unsigned char)(((x)>>24)&255); (y)[5] = (unsigned char)(((x)>>16)&255); \ + (y)[6] = (unsigned char)(((x)>>8)&255); (y)[7] = (unsigned char)((x)&255); } while(0) + +#define LOAD64H(x, y) \ +do { x = (((uint64_t)((y)[0] & 255))<<56)|(((uint64_t)((y)[1] & 255))<<48) | \ + (((uint64_t)((y)[2] & 255))<<40)|(((uint64_t)((y)[3] & 255))<<32) | \ + (((uint64_t)((y)[4] & 255))<<24)|(((uint64_t)((y)[5] & 255))<<16) | \ + (((uint64_t)((y)[6] & 255))<<8)|(((uint64_t)((y)[7] & 255))); } while(0) + +static int sha1_compress(sha1_context *md, unsigned char *buf) +{ + uint32_t a,b,c,d,e,W[80],i; + + /* copy the state into 512-bits into W[0..15] */ + for (i = 0; i < 16; i++) { + LOAD32H(W[i], buf + (4*i)); + } + + /* copy state */ + a = md->state[0]; + b = md->state[1]; + c = md->state[2]; + d = md->state[3]; + e = md->state[4]; + + /* expand it */ + for (i = 16; i < 80; i++) { + W[i] = ROL(W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16], 1); + } + + /* compress */ + /* round one */ + #define FF0(a,b,c,d,e,i) e = (ROL(a, 5) + F0(b,c,d) + e + W[i] + 0x5a827999UL); b = ROL(b, 30); + #define FF1(a,b,c,d,e,i) e = (ROL(a, 5) + F1(b,c,d) + e + W[i] + 0x6ed9eba1UL); b = ROL(b, 30); + #define FF2(a,b,c,d,e,i) e = (ROL(a, 5) + F2(b,c,d) + e + W[i] + 0x8f1bbcdcUL); b = ROL(b, 30); + #define FF3(a,b,c,d,e,i) e = (ROL(a, 5) + F3(b,c,d) + e + W[i] + 0xca62c1d6UL); b = ROL(b, 30); + + for (i = 0; i < 20; ) { + FF0(a,b,c,d,e,i++); + FF0(e,a,b,c,d,i++); + FF0(d,e,a,b,c,i++); + FF0(c,d,e,a,b,i++); + FF0(b,c,d,e,a,i++); + } + + /* round two */ + for (; i < 40; ) { + FF1(a,b,c,d,e,i++); + FF1(e,a,b,c,d,i++); + FF1(d,e,a,b,c,i++); + FF1(c,d,e,a,b,i++); + FF1(b,c,d,e,a,i++); + } + + /* round three */ + for (; i < 60; ) { + FF2(a,b,c,d,e,i++); + FF2(e,a,b,c,d,i++); + FF2(d,e,a,b,c,i++); + FF2(c,d,e,a,b,i++); + FF2(b,c,d,e,a,i++); + } + + /* round four */ + for (; i < 80; ) { + FF3(a,b,c,d,e,i++); + FF3(e,a,b,c,d,i++); + FF3(d,e,a,b,c,i++); + FF3(c,d,e,a,b,i++); + FF3(b,c,d,e,a,i++); + } + + #undef FF0 + #undef FF1 + #undef FF2 + #undef FF3 + + /* store */ + md->state[0] = md->state[0] + a; + md->state[1] = md->state[1] + b; + md->state[2] = md->state[2] + c; + md->state[3] = md->state[3] + d; + md->state[4] = md->state[4] + e; + + return 0; +} + +/** + Initialize the hash state + @param md The hash state you wish to initialize + @return 0 if successful +*/ +int sha1_init(sha1_context * md) +{ + if (md == NULL) return 1; + md->state[0] = 0x67452301UL; + md->state[1] = 0xefcdab89UL; + md->state[2] = 0x98badcfeUL; + md->state[3] = 0x10325476UL; + md->state[4] = 0xc3d2e1f0UL; + md->curlen = 0; + md->length = 0; + return 0; +} + +/** + Process a block of memory though the hash + @param md The hash state + @param in The data to hash + @param inlen The length of the data (octets) + @return 0 if successful +*/ +int sha1_process (sha1_context * md, const unsigned char *in, unsigned long inlen) +{ + size_t n; + size_t i; + int err; + if (md == NULL) return 1; + if (in == NULL) return 1; + if (md->curlen > sizeof(md->buf)) { + return 1; + } + if ((md->length + inlen) < md->length) { + return 1; + } + while (inlen > 0) { + if (md->curlen == 0 && inlen >= 64) { + if ((err = sha1_compress (md, (unsigned char *)in)) != 0) { + return err; + } + md->length += 64 * 8; + in += 64; + inlen -= 64; + } else { + n = MIN(inlen, (64 - md->curlen)); + for (i = 0; i < n; i++) { + md->buf[md->curlen + i] = in[i]; + } + md->curlen += n; + in += n; + inlen -= n; + if (md->curlen == 64) { + if ((err = sha1_compress (md, md->buf)) != 0) { + return err; + } + md->length += 8*64; + md->curlen = 0; + } + } + } + return 0; +} + +/** + Terminate the hash to get the digest + @param md The hash state + @param out [out] The destination of the hash (20 bytes) + @return 0 if successful +*/ +int sha1_done(sha1_context * md, unsigned char *out) +{ + int i; + + if (md == NULL) return 1; + if (out == NULL) return 1; + + if (md->curlen >= sizeof(md->buf)) { + return 1; + } + + /* increase the length of the message */ + md->length += md->curlen * 8; + + /* append the '1' bit */ + md->buf[md->curlen++] = (unsigned char)0x80; + + /* if the length is currently above 56 bytes we append zeros + * then compress. Then we can fall back to padding zeros and length + * encoding like normal. + */ + if (md->curlen > 56) { + while (md->curlen < 64) { + md->buf[md->curlen++] = (unsigned char)0; + } + sha1_compress(md, md->buf); + md->curlen = 0; + } + + /* pad upto 56 bytes of zeroes */ + while (md->curlen < 56) { + md->buf[md->curlen++] = (unsigned char)0; + } + + /* store length */ + STORE64H(md->length, md->buf+56); + sha1_compress(md, md->buf); + + /* copy output */ + for (i = 0; i < 5; i++) { + STORE32H(md->state[i], out+(4*i)); + } + return 0; +} + +int sha1(const unsigned char *msg, size_t len, unsigned char *hash) +{ + sha1_context ctx; + int ret; + if ((ret = sha1_init(&ctx))) return ret; + if ((ret = sha1_process(&ctx, msg, len))) return ret; + if ((ret = sha1_done(&ctx, hash))) return ret; + return 0; +} diff --git a/sha1.h b/sha1.h @@ -0,0 +1,19 @@ +/* LibTomCrypt, modular cryptographic library -- Tom St Denis + * + * LibTomCrypt is a library that provides various cryptographic + * algorithms in a highly modular and flexible manner. + * + * The library is free for all purposes without any express + * guarantee it works. + */ + +typedef struct { + uint64_t length; + uint32_t state[5], curlen; + unsigned char buf[64]; +} sha1_context; + +int sha1_init(sha1_context * md); +int sha1_process(sha1_context * md, const unsigned char *in, unsigned long inlen); +int sha1_done(sha1_context * md, unsigned char *hash); +int sha1(const unsigned char *in, size_t len, unsigned char *hash); diff --git a/torrent.c b/torrent.c @@ -0,0 +1,22 @@ +#include <stdio.h> +#include <stdlib.h> + +#include <libeech.h> + +int +main(int argc, char *argv[]) +{ + struct torrent t; + + if (argc < 2) { + fprintf(stderr, "%s TORRENT\n", argv[0]); + return -1; + } + + if (!glch_loadtorrent(&t, argv[1])) { + printf("Peer ID: %s\n", t.id); + printf("Tracker: %s\n", t.tr); + } + + return 0; +} diff --git a/util.c b/util.c @@ -0,0 +1,18 @@ +/* See LICENSE file for copyright and license details. */ +#include <string.h> + +char * +tohex(unsigned char *in, char *out, size_t len) +{ + size_t i, j; + char hex[] = "0123456789abcdef"; + + memset(out, 0, len*2 + 1); + for (i=0, j=0; i<len; i++, j++) { + out[j] = hex[in[i] >> 4]; + out[++j] = hex[in[i] & 15]; + } + + return out; +} + diff --git a/util.h b/util.h @@ -0,0 +1,3 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdlib.h> +char * tohex(unsigned char *, char *, size_t);