Initial import
This commit is contained in:
commit
d95e10600c
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
obj/
|
8
Makefile
Normal file
8
Makefile
Normal file
@ -0,0 +1,8 @@
|
||||
PROG= bt
|
||||
NOMAN= noman
|
||||
|
||||
SRCS= bt.c bcode.c
|
||||
|
||||
WARNINGS= Yes
|
||||
|
||||
.include <bsd.prog.mk>
|
496
bcode.c
Normal file
496
bcode.c
Normal file
@ -0,0 +1,496 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Lucas Gabriel Vuotto <lucas@lgv5.net>
|
||||
*
|
||||
* 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 <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <vis.h>
|
||||
|
||||
#include "bcode.h"
|
||||
|
||||
|
||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||
|
||||
#define GROWTH_FACTOR 16
|
||||
|
||||
|
||||
enum bcode_type {
|
||||
BCODE_UNASSIGNED,
|
||||
BCODE_STRING,
|
||||
BCODE_INTEGER,
|
||||
BCODE_LIST,
|
||||
BCODE_DICTIONARY,
|
||||
};
|
||||
|
||||
struct bcode_string {
|
||||
size_t len;
|
||||
void *data;
|
||||
};
|
||||
|
||||
struct bcode_list {
|
||||
size_t sz;
|
||||
size_t cap;
|
||||
struct bcode *elems;
|
||||
};
|
||||
|
||||
struct bcode_dictionary {
|
||||
size_t sz;
|
||||
size_t cap;
|
||||
struct bcode_kv *elems;
|
||||
};
|
||||
|
||||
struct bcode {
|
||||
enum bcode_type type;
|
||||
union {
|
||||
struct bcode_string s;
|
||||
int64_t i;
|
||||
struct bcode_list l;
|
||||
struct bcode_dictionary d;
|
||||
} value;
|
||||
};
|
||||
|
||||
/*
|
||||
* This struct is needed because the value isn't a pointer; inlining it in
|
||||
* bcode_dictionary leads to a compiler error because of the use of an
|
||||
* undeclared type.
|
||||
*/
|
||||
struct bcode_kv {
|
||||
struct bcode_string k;
|
||||
struct bcode v;
|
||||
};
|
||||
|
||||
|
||||
static int kv_cmp(const void *, const void *);
|
||||
static int grow_list(struct bcode_list *);
|
||||
static int grow_dictionary(struct bcode_dictionary *);
|
||||
static size_t parse_string(struct bcode_string *, const uint8_t *, size_t);
|
||||
static size_t parse_integer(int64_t *, const uint8_t *, size_t);
|
||||
static size_t parse_list(struct bcode_list *, const uint8_t *, size_t);
|
||||
static size_t parse_dictionary(struct bcode_dictionary *, const uint8_t *,
|
||||
size_t);
|
||||
static size_t parse_internal(struct bcode *, const uint8_t *, size_t);
|
||||
|
||||
|
||||
static int
|
||||
kv_cmp(const void *ap, const void *bp)
|
||||
{
|
||||
const struct bcode_kv *a = ap, *b = bp;
|
||||
size_t min;
|
||||
int r;
|
||||
|
||||
min = MIN(a->k.len, b->k.len);
|
||||
r = memcmp(a->k.data, b->k.data, min);
|
||||
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
return a->k.len < b->k.len ? -1 : a->k.len > b->k.len ? 1 : 0;
|
||||
}
|
||||
|
||||
static int
|
||||
grow_list(struct bcode_list *l)
|
||||
{
|
||||
void *p;
|
||||
size_t newcap;
|
||||
|
||||
if (l->sz < l->cap)
|
||||
return 1;
|
||||
if (l->cap > SIZE_MAX - GROWTH_FACTOR)
|
||||
return 0;
|
||||
newcap = l->cap + GROWTH_FACTOR;
|
||||
|
||||
p = recallocarray(l->elems, l->cap, newcap, sizeof(*l->elems));
|
||||
if (p == NULL)
|
||||
return 0;
|
||||
l->elems = p;
|
||||
l->cap = newcap;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
grow_dictionary(struct bcode_dictionary *d)
|
||||
{
|
||||
void *p;
|
||||
size_t newcap;
|
||||
|
||||
if (d->sz < d->cap)
|
||||
return 1;
|
||||
if (d->cap > SIZE_MAX - GROWTH_FACTOR)
|
||||
return 0;
|
||||
newcap = d->cap + GROWTH_FACTOR;
|
||||
|
||||
p = recallocarray(d->elems, d->cap, newcap, sizeof(*d->elems));
|
||||
if (p == NULL)
|
||||
return 0;
|
||||
d->elems = p;
|
||||
d->cap = newcap;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static size_t
|
||||
parse_string(struct bcode_string *s, const uint8_t *dp, size_t sz)
|
||||
{
|
||||
const char *begin;
|
||||
char *end;
|
||||
size_t consumed = 0;
|
||||
uintmax_t umax;
|
||||
|
||||
/* Shortest string is "0:". */
|
||||
if (sz < 2)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* dp might be a binary string, so it isn't safe to blindly call
|
||||
* strtoumax.
|
||||
*/
|
||||
|
||||
begin = dp;
|
||||
|
||||
/* No leading zeros allowed. */
|
||||
if (*dp == '0' && sz != 2)
|
||||
return 0;
|
||||
|
||||
while (sz > 0 && isdigit(*dp)) {
|
||||
sz--;
|
||||
dp++;
|
||||
consumed++;
|
||||
}
|
||||
if (sz == 0)
|
||||
return 0;
|
||||
|
||||
errno = 0;
|
||||
umax = strtoumax(begin, &end, 10);
|
||||
if (begin == end)
|
||||
return 0;
|
||||
if (errno == ERANGE && umax == UINTMAX_MAX)
|
||||
return 0;
|
||||
if (umax > SIZE_MAX)
|
||||
return 0;
|
||||
if (*end != ':')
|
||||
return 0;
|
||||
|
||||
sz--;
|
||||
dp++;
|
||||
consumed++;
|
||||
|
||||
if ((size_t)umax > sz)
|
||||
return 0;
|
||||
|
||||
if (umax > 0) {
|
||||
s->data = malloc(umax);
|
||||
if (s->data == NULL)
|
||||
return 0;
|
||||
memcpy(s->data, dp, umax);
|
||||
s->len = umax;
|
||||
} else {
|
||||
s->data = NULL;
|
||||
s->len = 0;
|
||||
}
|
||||
|
||||
consumed += s->len;
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
static size_t
|
||||
parse_integer(int64_t *i, const uint8_t *dp, size_t sz)
|
||||
{
|
||||
const char *begin;
|
||||
char *end;
|
||||
size_t consumed = 0;
|
||||
intmax_t imax;
|
||||
|
||||
/* Shortest integer is "i0e". */
|
||||
if (sz < 3)
|
||||
return 0;
|
||||
|
||||
if (*dp != 'i')
|
||||
return 0;
|
||||
sz--;
|
||||
dp++;
|
||||
consumed++;
|
||||
|
||||
/*
|
||||
* dp might be a binary string, so it isn't safe to blindly call
|
||||
* strtoimax.
|
||||
*/
|
||||
|
||||
begin = dp;
|
||||
|
||||
/* No negative zeros allowed. */
|
||||
if (*dp == '-') {
|
||||
sz--;
|
||||
dp++;
|
||||
consumed++;
|
||||
if (*dp == '0')
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* No leading zeros allowed. */
|
||||
if (*dp == '0' && sz != 2)
|
||||
return 0;
|
||||
|
||||
while (sz > 0 && isdigit(*dp)) {
|
||||
sz--;
|
||||
dp++;
|
||||
consumed++;
|
||||
}
|
||||
if (sz == 0)
|
||||
return 0;
|
||||
|
||||
errno = 0;
|
||||
imax = strtoimax(begin, &end, 10);
|
||||
if (begin == end)
|
||||
return 0;
|
||||
if (errno == ERANGE && (imax == INTMAX_MAX || imax == INTMAX_MIN))
|
||||
return 0;
|
||||
if (imax > INT64_MAX || imax < INT64_MIN)
|
||||
return 0;
|
||||
|
||||
if (*end != 'e')
|
||||
return 0;
|
||||
consumed++;
|
||||
|
||||
*i = imax;
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
static size_t
|
||||
parse_list(struct bcode_list *l, const uint8_t *dp, size_t sz)
|
||||
{
|
||||
size_t n, consumed = 0;
|
||||
|
||||
/* Shortest list is "le". */
|
||||
if (sz < 2)
|
||||
return 0;
|
||||
|
||||
if (*dp != 'l')
|
||||
return 0;
|
||||
sz--;
|
||||
dp++;
|
||||
consumed++;
|
||||
|
||||
while (*dp != 'e' && sz > 0) {
|
||||
grow_list(l);
|
||||
|
||||
n = parse_internal(&l->elems[l->sz], dp, sz);
|
||||
if (n == 0)
|
||||
return 0;
|
||||
sz -= n;
|
||||
dp += n;
|
||||
consumed += n;
|
||||
|
||||
l->sz++;
|
||||
}
|
||||
|
||||
if (*dp != 'e')
|
||||
return 0;
|
||||
consumed++;
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
static size_t
|
||||
parse_dictionary(struct bcode_dictionary *d, const uint8_t *dp, size_t sz)
|
||||
{
|
||||
size_t n, consumed = 0;
|
||||
|
||||
/* Shortest dictionary is "de". */
|
||||
if (sz < 2)
|
||||
return 0;
|
||||
|
||||
if (*dp != 'd')
|
||||
return 0;
|
||||
sz--;
|
||||
dp++;
|
||||
consumed++;
|
||||
|
||||
while (*dp != 'e' && sz > 0) {
|
||||
grow_dictionary(d);
|
||||
|
||||
n = parse_string(&d->elems[d->sz].k, dp, sz);
|
||||
if (n == 0)
|
||||
return 0;
|
||||
sz -= n;
|
||||
dp += n;
|
||||
consumed += n;
|
||||
|
||||
n = parse_internal(&d->elems[d->sz].v, dp, sz);
|
||||
if (n == 0)
|
||||
return 0;
|
||||
sz -= n;
|
||||
dp += n;
|
||||
consumed += n;
|
||||
|
||||
d->sz++;
|
||||
}
|
||||
|
||||
if (*dp != 'e')
|
||||
return 0;
|
||||
consumed++;
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
static size_t
|
||||
parse_internal(struct bcode *bcode, const uint8_t *dp, size_t sz)
|
||||
{
|
||||
size_t n;
|
||||
|
||||
switch (*dp) {
|
||||
case 'i':
|
||||
n = parse_integer(&bcode->value.i, dp, sz);
|
||||
if (n > 0)
|
||||
bcode->type = BCODE_INTEGER;
|
||||
break;
|
||||
case 'l':
|
||||
n = parse_list(&bcode->value.l, dp, sz);
|
||||
if (n > 0)
|
||||
bcode->type = BCODE_LIST;
|
||||
break;
|
||||
case 'd':
|
||||
n = parse_dictionary(&bcode->value.d, dp, sz);
|
||||
if (n > 0) {
|
||||
struct bcode_dictionary d = bcode->value.d;
|
||||
|
||||
qsort(d.elems, d.sz, sizeof(*d.elems), &kv_cmp);
|
||||
bcode->type = BCODE_DICTIONARY;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
n = parse_string(&bcode->value.s, dp, sz);
|
||||
if (n > 0)
|
||||
bcode->type = BCODE_STRING;
|
||||
break;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
struct bcode *
|
||||
bcode_parse(const uint8_t *dp, size_t sz)
|
||||
{
|
||||
struct bcode *bcode;
|
||||
|
||||
bcode = calloc(1, sizeof(*bcode));
|
||||
if (bcode == NULL)
|
||||
return NULL;
|
||||
|
||||
if (parse_internal(bcode, dp, sz) != sz) {
|
||||
bcode_free(bcode);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return bcode;
|
||||
}
|
||||
|
||||
void
|
||||
bcode_free(struct bcode *bcode)
|
||||
{
|
||||
if (bcode == NULL)
|
||||
return;
|
||||
|
||||
free(bcode);
|
||||
}
|
||||
|
||||
static void
|
||||
print_string(const struct bcode_string *s, FILE *fp)
|
||||
{
|
||||
char buf[5];
|
||||
size_t i;
|
||||
int nextc;
|
||||
|
||||
fputc('"', fp);
|
||||
for (i = 0; i < s->len; i++) {
|
||||
if (i + 1 == s->len)
|
||||
nextc = '"';
|
||||
else
|
||||
nextc = ((unsigned char *)s->data)[i + 1];
|
||||
vis(buf, ((unsigned char *)s->data)[i],
|
||||
VIS_TAB|VIS_NL|VIS_CSTYLE, nextc);
|
||||
fputs(buf, fp);
|
||||
}
|
||||
fputc('"', fp);
|
||||
}
|
||||
|
||||
static void
|
||||
print_internal(const struct bcode *bcode, FILE *fp, size_t lvl)
|
||||
{
|
||||
size_t i, k;
|
||||
|
||||
switch (bcode->type) {
|
||||
case BCODE_STRING:
|
||||
print_string(&bcode->value.s, fp);
|
||||
break;
|
||||
case BCODE_INTEGER:
|
||||
fprintf(fp, "%" PRIi64, bcode->value.i);
|
||||
break;
|
||||
case BCODE_LIST: {
|
||||
struct bcode_list l = bcode->value.l;
|
||||
|
||||
fputs("[\n", fp);
|
||||
lvl++;
|
||||
for (i = 0; i < l.sz; i++) {
|
||||
for (k = 0; k < lvl; k++)
|
||||
fputs(" ", fp);
|
||||
print_internal(&l.elems[i], fp, lvl);
|
||||
fputs(",\n", fp);
|
||||
}
|
||||
lvl--;
|
||||
for (k = 0; k < lvl; k++)
|
||||
fputs(" ", fp);
|
||||
fputc(']', fp);
|
||||
|
||||
break;
|
||||
}
|
||||
case BCODE_DICTIONARY: {
|
||||
struct bcode_dictionary d = bcode->value.d;
|
||||
|
||||
fputs("{\n", fp);
|
||||
lvl++;
|
||||
for (i = 0; i < d.sz; i++) {
|
||||
for (k = 0; k < lvl; k++)
|
||||
fputs(" ", fp);
|
||||
print_string(&d.elems[i].k, fp);
|
||||
fputs(": ", fp);
|
||||
print_internal(&d.elems[i].v, fp, lvl);
|
||||
fputs(",\n", fp);
|
||||
}
|
||||
lvl--;
|
||||
for (k = 0; k < lvl; k++)
|
||||
fputs(" ", fp);
|
||||
fputc('}', fp);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
/* Do nothing. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
bcode_dump(const struct bcode *bcode, FILE *fp)
|
||||
{
|
||||
print_internal(bcode, fp, 0);
|
||||
fputc('\n', fp);
|
||||
}
|
26
bcode.h
Normal file
26
bcode.h
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Lucas Gabriel Vuotto <lucas@lgv5.net>
|
||||
*
|
||||
* 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 <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
struct bcode;
|
||||
|
||||
|
||||
struct bcode * bcode_parse(const uint8_t *, size_t);
|
||||
void bcode_free(struct bcode *);
|
||||
void bcode_dump(const struct bcode *, FILE *);
|
50
bt.c
Normal file
50
bt.c
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Lucas Gabriel Vuotto <lucas@lgv5.net>
|
||||
*
|
||||
* 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 <err.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "bcode.h"
|
||||
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
struct bcode *bcode;
|
||||
uint8_t buf[256 * 1024];
|
||||
FILE *fp;
|
||||
size_t n;
|
||||
|
||||
fp = fopen("/tmp/af24ae3037cfc1ad8226b159103a148fbc81b173.torrent", "r");
|
||||
if (fp == NULL)
|
||||
err(1, "fopen");
|
||||
n = fread(buf, 1, sizeof(buf), fp);
|
||||
if (ferror(fp))
|
||||
err(1, "fread");
|
||||
if (!feof(fp))
|
||||
errx(1, "short read");
|
||||
|
||||
bcode = bcode_parse(buf, n);
|
||||
if (bcode == NULL)
|
||||
errx(1, "bcode_parse: parse error");
|
||||
bcode_dump(bcode, stdout);
|
||||
bcode_free(bcode);
|
||||
|
||||
(void)fclose(fp);
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user