/*
  $NiH: stream_decode.c,v 1.6 2002/05/10 21:09:20 wiz Exp $

  stream_decode.c -- low-level decoding
  Copyright (C) 2002 Dieter Baron and Thomas Klausner

  This file is part of cg, a program to assemble and decode binary Usenet
  postings.  The authors can be contacted at <nih@giga.or.at>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program 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 General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include <stddef.h>

#include "stream.h"
#include "stream_types.h"
#include "util.h"

enum decode_state { DS_OK, DS_ILL, DS_EOF };

struct stream_decode {
    stream st;

    int *table;			/* decoding table to use */

    enum decode_state state;	/* state we're in */
    char *buf;			/* buffer for decoded data */
    int buf_alen;		/* allocation length of buf */
    int rest;			/* partially decoded quadruple */
    int no;			/* number of characters in rest */
};

static int dec_close(struct stream_decode *st);
static int dec_fill(struct stream_decode *this, int i, int no, int rest);
static token *dec_get(struct stream_decode *st);



stream *
stream_decode_open(struct stream *source, int *table)
{
    struct stream_decode *this;

    this = (struct stream_decode *)stream_new(sizeof(struct stream_decode),
					      dec_get, dec_close, source);

    this->table = table;
    this->rest = this->no = 0;
    this->buf = NULL;
    this->buf_alen = 0;
    this->state = DS_OK;

    return (stream *)this;
}



static int
dec_close(struct stream_decode *this)
{
    /* XXX: skip to EOF? */

    stream_free((stream *)this);

    return 0;
}



static token *
dec_get(struct stream_decode *this)
{
    token *t;
    int rest, no, b, i;

    switch (this->state) {
    case DS_ILL:
	/* XXX: specify exact error */
	this->state = DS_EOF;
	return token_printf3(&this->st.tok, TOK_ERR, TOK_ERR_ERROR,
			     "illegal character in encoded data");

    case DS_EOF:
	return TOKEN_EOF;

    case DS_OK:
	t = stream_get(this->st.source);

	rest = this->rest;
	no = this->no;
	i = 0;

	if (t->type != TOK_LINE) {
	    if (no) {
		i = dec_fill(this, 0, no, rest);
		token_set3(stream_enqueue((stream *)this),
			   TOK_DATA, i, this->buf);
		this->rest = this->no = 0;
	    }
	    return t;
	}

	while (*t->line) {
	    b = this->table[(unsigned char)*(t->line++)];
	    if (b < 0) {
		switch (b) {
		case DEC_END:
		    if (this->state != DS_OK)
			this->state = DS_ILL;
		    else if (no == 0)
			this->state = DS_EOF;
		    else
			this->state = DS_ILL;
		    break;

		case DEC_PAD:
		    if (this->state == DS_OK) {
			switch (no) {
			case 0:
			case 1:
			    this->state = DS_ILL;
			    break;
			case 2:
			case 3:
			    this->state = DS_EOF;
			    break;
			}
		    }
		    break;

		default:
		    /* XXX: recover, don't abort decoding */
		    no = 0;
		    this->state = DS_ILL;
		}
	    }
	    else {
		rest = (rest << 6) | (b & 0x3f);
		no++;
	    }

	    if (no == 4 || this->state != DS_OK) {
		i += dec_fill(this, i, no, rest);
		rest = no = 0;
	    }
	}
    }

    this->rest = rest;
    this->no = no;

    return token_set3(&this->st.tok, TOK_DATA, i, this->buf);
}



static int
dec_fill(struct stream_decode *this, int i, int no, int rest)
{
    if (i+2 >= this->buf_alen) {
	this->buf_alen = (this->buf_alen ? this->buf_alen*2 : 64);
	this->buf = xrealloc(this->buf, this->buf_alen);
    }

    switch (no) {
    case 2:
	this->buf[i++] = rest >> 4;
	break;

    case 3:
	this->buf[i++] = rest >> 10;
	this->buf[i++] = (rest>>2) & 0xff;
	break;
	
    case 4:
	this->buf[i++] = rest >> 16;
	this->buf[i++] = (rest>>8) & 0xff;
	this->buf[i++] = (rest & 0xff);
	break;
    }

    return no-1;
}


syntax highlighted by Code2HTML, v. 0.9.1