/*
 * btlib.c
 *
 * Copyright (C) 2011-22 - ntop.org
 *               Contributed by Vitaly Lavrov <vel21ripn@gmail.com>
 *
 * This file is part of nDPI, an open source deep packet inspection
 * library based on the OpenDPI and PACE technology by ipoque GmbH
 *
 * nDPI is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * nDPI is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with nDPI.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "ndpi_api.h"
#if 0
#ifndef NDPI_NO_STD_INC
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <unistd.h>
#include <strings.h>

/*
typedef unsigned char u_int8_t;
typedef unsigned short int u_int16_t;
typedef unsigned long long int u_int64_t;
*/

#include <stdint.h>
#include <stdlib.h>
#ifndef WIN32
#include <arpa/inet.h>
#endif
#endif
#endif

#include "btlib.h"

int bt_parse_debug = 0;

#if 0
void dump_bt_proto_struct(struct bt_parse_protocol *p) {
  char b20h[128];
  int i;

  if(p->y_e && p->e_msg)  {
    printf("Error %s/%u\n", p->e_msg, p->e_len);
  }
  if(p->y_q) {
    printf("Query ");
    if(p->q_ping) printf("ping\n");
    if(p->q_g_peers) printf("get_peers\n");
    if(p->q_f_node) printf("find_node\n");
    if(p->q_a_peer) printf("announce_peer\n");
  }
  if(p->y_r)
    printf("Reply\n");

  if(p->t) printf("\tt\t%llx\n",p->t);
  if(p->v) printf("\tv\t%llx\n",p->v);
  if(p->ip) printf("\tIP\t%s\n",print_ip_p(b20h,p->ip,0));

  if(p->a.port) printf("\tport\t%d\n",htons(p->a.port));
  if(p->a.id) printf("\tID\t%s\n",print20b(b20h,p->a.id));
  if(p->a.target) printf("\ttarget\t%s\n",print20b(b20h,p->a.target));
  if(p->a.token) printf("\ttoken\t%s\n",printXb(b20h,p->a.token,p->a.t_len));
  if(p->a.info_hash) printf("\ti_hash\t%s\n",print20b(b20h,p->a.info_hash));
  if(p->a.name && p->a.name_len) printf("\tname\t%.*s\n",p->a.name_len,p->a.name);

  if(p->r.ip) printf("\tip\t%s\n",print_ip_p(b20h,p->r.ip,1));
  if(p->r.port) printf("\tport\t%d\n",htons(p->r.port));
  if(p->r.id) printf("\tID\t%s\n",print20b(b20h,p->r.id));
  if(p->r.token) printf("\ttoken\t%s\n",printXb(b20h,p->r.token,p->r.t_len));
  if(p->r.name && p->r.name_len) printf("\tname\t%.*s\n",p->r.name_len,p->r.name);
  if(p->r.values && p->r.nv) {
    struct bt_ipv4p2 *n = (struct bt_ipv4p2 *)p->r.values;
    for(i=0;i < p->r.nv; i++,n++) {
      printf("\tvalues\t%s\n", print_ip_p(b20h,&n->d,0));
    }
  }
  if(p->r.values6 && p->r.nv6) {
    struct bt_ipv6p2 *n = (struct bt_ipv6p2 *)p->r.values6;
    for(i=0;i < p->r.nv6; i++,n++) {
      printf("\tvalues6\t%s\n", print_ip6_p(b20h,&n->d,0));
    }
  }
  if(p->r.nodes && p->r.nn) {
    for(i=0;i < p->r.nn; i++) {
      printf("\tnodes\t%s\n",print_id_ip_p(b20h,p->r.nodes+i));
    }
  }
  if(p->r.nodes6 && p->r.nn6) {
    for(i=0;i < p->r.nn6; i++) {
      printf("\tnodes6\t%s\n",print_id_ip6_p(b20h,p->r.nodes6+i));
    }
  }

  if(p->peers && p->n_peers) {
    for(i=0;i < p->n_peers; i++) {
      printf("\tpeers\t%s\n",print_ip_p(b20h,p->peers+i,0));
    }
  }

  if(p->interval) printf("\tinterval\t%d\n",p->interval);
  if(p->min_interval) printf("\tmin interval\t%d\n",p->min_interval);
}
#endif

#ifdef BTLIB_DEBUG
static void _print_safe_str(char *msg,char *k,const u_int8_t *s,size_t l) {
  static const char *th="0123456789abcdef?";
  char *buf = (char*)ndpi_malloc((size_t)(l*3+2));

  int sl = l;
  if(buf) {
    char *b = buf;
    for(;l > 0; s++,l--) {
      if(*s < ' ' || *s >= 127) {
	*b++ = '%';
	*b++ = th[(*s >> 4)&0xf];
	*b++ = th[(*s)&0xf];
      } else *b++ = *s;
    }
    *b = 0;
  
    printf("%s %s %s len %d\n",msg,k,buf ? buf:"",sl);

    ndpi_free(buf);
  }
}

static void print_safe_str(char *msg,bt_parse_data_cb_t *cbd) {
  _print_safe_str(msg,cbd->buf,cbd->v.s.s,cbd->v.s.l);
}
#define DEBUG_TRACE(cmd) { if(bt_parse_debug) cmd; }

#else
#define DEBUG_TRACE(cmd) ;
#endif


#define STREQ(a,b) !strcmp(a,b)


void cb_data(bt_parse_data_cb_t *cbd,int *ret) {
  struct bt_parse_protocol *p = &(cbd->p);
  const u_int8_t *s;
  const char *ss;

  if(cbd->t == 0)  return;

  if(cbd->t == 1) {
    DEBUG_TRACE(printf("%s %lld\n",cbd->buf, (long long)cbd->v.i));

    if(STREQ(cbd->buf,"a.port")) {
      p->a.port = (u_int16_t)(cbd->v.i & 0xffff);
      return;
    }
    if(
       STREQ(cbd->buf,"a.implied_port") ||
       STREQ(cbd->buf,"a.noseed") ||
       STREQ(cbd->buf,"a.scrape") ||
       STREQ(cbd->buf,"a.seed") ||
       STREQ(cbd->buf,"a.vote")
       ) {
      return;
    }
    if(STREQ(cbd->buf,"r.port") || STREQ(cbd->buf,"r.p")) {
      p->r.port = (u_int16_t)(cbd->v.i & 0xffff);
      return;
    }
    if(STREQ(cbd->buf,"interval")) {
      p->interval = (u_int16_t)(cbd->v.i & 0x7fffffff);
      p->h_int = 1;
      return;
    }
    if(STREQ(cbd->buf,"min interval")) {
      p->min_interval = (u_int16_t)(cbd->v.i & 0x7fffffff);
      p->h_mint = 1;
      return;
    }
    DEBUG_TRACE(printf("UNKNOWN %s %lld\n",cbd->buf, (long long)cbd->v.i));
    return;
  }
  if(cbd->t != 2) {
    DEBUG_TRACE(printf("BUG! t=%d %s\n",cbd->t,cbd->buf));
    return;
  }
  DEBUG_TRACE(print_safe_str("",cbd));

  s = cbd->v.s.s;
  ss = (char *)s;

  if(STREQ(cbd->buf,"a.id")) {
    p->a.id = s;
    return;
  }
  if(STREQ(cbd->buf,"a.info_hash")) {
    p->a.info_hash = s;
    return;
  }
  if(STREQ(cbd->buf,"a.target")) {
    p->a.target = s;
    return;
  }
  if(STREQ(cbd->buf,"a.token")) {
    p->a.token = s;
    p->a.t_len = cbd->v.s.l;
    return;
  }
  if(STREQ(cbd->buf,"a.name")) {
    p->a.name = s;
    p->a.name_len = cbd->v.s.l;
    return;
  }
  if(STREQ(cbd->buf,"a.want")) {
    return;
  }

  if(STREQ(cbd->buf,"r.id")) {
    p->r.id = s;
    return;
  }
  if(STREQ(cbd->buf,"r.ip")) {
    if(cbd->v.s.l != 4) {
      DEBUG_TRACE(printf("BUG! r.ip with port\n"));
      return;
    }
    p->r.ip = (struct bt_ipv4p *)s;
    return;
  }
  if(STREQ(cbd->buf,"r.token")) {
    p->r.token = s;
    p->r.t_len = cbd->v.s.l;
    return;
  }
  if(STREQ(cbd->buf,"r.values")) {
    if(cbd->v.s.l == 18) {
      if(!p->r.values6) {
	p->r.values6 = s;
	p->r.nv6 = 1;
      } else {
	if(s != p->r.values6+(p->r.nv6*21)) {
	  // DEBUG_TRACE(printf("BUG! r.values6 not in list! %08x %08x \n", p->r.values+(p->r.nv6*21),s));
	  return;
	}
	p->r.nv6++;
      }
      return;
    }
    if(cbd->v.s.l == 6) {
      if(!p->r.values) {
	p->r.values = s;
	p->r.nv = 1;
      } else {
	if(s != p->r.values+(p->r.nv*8)) {
	  // DEBUG_TRACE(printf("BUG! r.values not in list! %u \n",s-p->r.values+(p->r.nv*8)));
	  return;
	}
	p->r.nv++;
      }
      return;
    }
    return;
  }

  if(STREQ(cbd->buf,"r.name") || STREQ(cbd->buf,"r.n")) {
    p->r.name = s;
    p->r.name_len = cbd->v.s.l;
    return;
  }
  if(STREQ(cbd->buf,"r.nodes")) {
    if(cbd->v.s.l % 26) {
      // DEBUG_TRACE(printf("BUG! r.nodes length %d not %% 26\n",cbd->v.s.l));
      return;
    }
    p->r.nodes = (struct bt_nodes_data *)s;
    p->r.nn = cbd->v.s.l / 26;
    return;
  }
  if(STREQ(cbd->buf,"r.nodes6")) {
    if(cbd->v.s.l % 38) {
      // DEBUG_TRACE(printf("BUG! r.nodes length %d not %% 38\n",cbd->v.s.l));
      return;
    }
    p->r.nodes6 = (struct bt_nodes6_data *)s;
    p->r.nn6 = cbd->v.s.l / 38;
    return;
  }

  if(cbd->buf[0] == 'y' && !cbd->buf[1]) {
    if(cbd->v.s.l != 1) return;
    if(*ss == 'q') { p->y_q = 1; return; }
    if(*ss == 'r') { p->y_r = 1; return; }
    if(*ss == 'e') { p->y_e = 1; return; }
    return;
  }
  if(cbd->buf[0] == 'q' && !cbd->buf[1]) {
    if(!strncmp(ss,"announce_peer",13)) {
      p->q_a_peer = 1;
      return;
    }
    if(!strncmp(ss,"find_node",9)) {
      p->q_f_node = 1;
      return;
    }
    if(!strncmp(ss,"get_peers",9)) {
      p->q_g_peers = 1;
      return;
    }
    if(!strncmp(ss,"ping",4)) {
      p->q_ping = 1;
      return;
    }
    if(!strncmp(ss,"vote",4)) {
      return;
    }
  }
  if(STREQ(cbd->buf,"ip")) {
    if(cbd->v.s.l != 6) {
      // DEBUG_TRACE(printf("BUG! r.ip w/o port\n"));
    }
    p->ip = (struct bt_ipv4p *)s;
    p->h_ip = 1;
    return;
  }
  if(STREQ(cbd->buf,"peers")) {
    if(cbd->v.s.l % 6) return;
    p->peers = (struct bt_ipv4p *)s;
    p->n_peers = cbd->v.s.l / 6;
    return;
  }
  if((*cbd->buf == 't' || *cbd->buf == 'v') && !cbd->buf[1]) {
    u_int64_t d = *(u_int64_t*)s;
    switch(cbd->v.s.l) {
    case 2:
      d &= 0xffffllu; d = htons(d); break;
    case 4:
      d &= 0xffffffffllu; d = htonl(d); break;
    case 6:
      d &= 0xffffffffffffllu; d = (htonl(d & 0xffffffff) << 16) |
				(htons(d >> 32) & 0xffff);
      break;
    case 8: d = ((u_int64_t)htonl(d & 0xffffffff) << 32) |
	htonl(d >> 32);
      break;
    default: d = 0;
    }
    if(*cbd->buf == 'v') cbd->p.v = d;
    else cbd->p.t = d;
    return;
  }

  if(cbd->buf[0] == 'e') {
    p->e_msg = s;
    p->e_len = cbd->v.s.l;
    return;
  }
  // DEBUG_TRACE(print_safe_str("UNKNOWN",cbd));
}


const u_int8_t *bt_decode(const u_int8_t *b, size_t *l, int *ret, bt_parse_data_cb_t *cbd) {

  unsigned int n=0,neg=0;
  int64_t d = 0;
  u_int8_t c;

  if(!l || *l == 0) return NULL;
  if(cbd->level > BDEC_MAXDEPT) goto bad_data;
  c = *b++; (*l)--;
  if(c == 'i') { // integer
    while(*l) {
      c = *b++; (*l)--;
      n++;
      if(c == '-') {
	if(n != 1) goto bad_data;
	n--;
	neg=1;
	continue;
      }
      if(c >= '0' && c <= '9') {
	if(c == '0' && n > 1 && !d && *b != 'e') goto bad_data;
	d *= 10;
	d += c-'0';
	continue;
      }
      if(c != 'e') goto bad_data;
      break;
    }
    if(neg) d=-d;
    cbd->t = 1;
    cbd->v.i = neg ? -d:d;
    return b;
  }
  if(c >= '1' && c <= '9') { //string
    d=c-'0';
    while(*l) {
      c = *b++; (*l)--;
      n++;
      if(c >= '0' && c <= '9') {
	if(c == '0' && n > 1 && d == 0) goto bad_data;
	d *= 10;
	d += c-'0';
	continue;
      }
      if(c != ':') goto bad_data;
      break;
    }
    if((size_t)d > *l) goto bad_data;
    cbd->t = 2;
    cbd->v.s.s = b;
    cbd->v.s.l = d;
    b += d;
    *l -= d;
    return b;
  }
  if(c == 'l') {
    cbd->level++;
    do {
      b = bt_decode(b,l,ret,cbd);
      if(*ret < 0 || *l == 0) goto bad_data;
      cb_data(cbd,ret);
      if(*ret < 0) goto bad_data;
      cbd->t = 0;
    } while (*b != 'e' && *l != 0);
    b++; (*l)--;
    cbd->level--;
    return b;
  }
  if(c == 'd') {
    cbd->level++;
    do {
      char *ls = cbd->buf + strlen(cbd->buf);
      int l1 = ls != cbd->buf ? 1:0;
      if(!(*b >= '1' && *b <= '9')) goto bad_data;
      b = bt_decode(b,l,ret,cbd);
      if(*ret < 0 || *l == 0) goto bad_data;
      if(ls+cbd->v.s.l+l1 < &cbd->buf[sizeof(cbd->buf)-1]) {
	if(l1)	ls[0]='.';
	strncpy(ls+l1,(char *)cbd->v.s.s,cbd->v.s.l);
	ls[cbd->v.s.l+l1]=0;
      }
      b = bt_decode(b,l,ret,cbd);
      if(*ret < 0 || *l == 0) goto bad_data;
      cb_data(cbd,ret);
      if(*ret < 0) goto bad_data;
      cbd->t = 0;
      *ls = 0;
    } while (*b != 'e' && l != 0);

    b++; (*l)--;
    cbd->level--;
    return b;
  }

 bad_data:
  *ret=-1;
  return b;
}
