#include "vnc.h"

#define SHORT(p) (((p)[0]<<8)|((p)[1]))
#define LONG(p) ((SHORT(p)<<16)|SHORT(p+2))

uchar zero[64];

Vnc*
vncinit(int fd, int cfd, Vnc *v)
{
       if(v == nil)
               v = mallocz(sizeof(*v), 1);
       Binit(&v->in, fd, OREAD);
       Binit(&v->out, fd, OWRITE);
       v->datafd = fd;
       v->ctlfd = cfd;
       return v;
}

void
vncterm(Vnc *v)
{
       Bterm(&v->out);
       Bterm(&v->in);
}

void
vncflush(Vnc *v)
{
       if(Bflush(&v->out) < 0){
               if(verbose > 1)
                       fprint(2, "hungup while sending flush: %r\n");
               vnchungup(v);
       }
}

uchar
vncrdchar(Vnc *v)
{
       uchar buf[1];

       vncrdbytes(v, buf, 1);
       return buf[0];
}

ushort
vncrdshort(Vnc *v)
{
       uchar buf[2];

       vncrdbytes(v, buf, 2);
       return SHORT(buf);
}

ulong
vncrdlong(Vnc *v)
{
       uchar buf[4];

       vncrdbytes(v, buf, 4);
       return LONG(buf);
}

Point
vncrdpoint(Vnc *v)
{
       Point p;

       p.x = vncrdshort(v);
       p.y = vncrdshort(v);
       return p;
}

Rectangle
vncrdrect(Vnc *v)
{
       Rectangle r;

       r.min.x = vncrdshort(v);
       r.min.y = vncrdshort(v);
       r.max.x = r.min.x + vncrdshort(v);
       r.max.y = r.min.y + vncrdshort(v);
       return r;
}

Rectangle
vncrdcorect(Vnc *v)
{
       Rectangle r;

       r.min.x = vncrdchar(v);
       r.min.y = vncrdchar(v);
       r.max.x = r.min.x + vncrdchar(v);
       r.max.y = r.min.y + vncrdchar(v);
       return r;
}

void
vncrdbytes(Vnc *v, void *a, int n)
{
       if(Bread(&v->in, a, n) != n){
               if(verbose > 1)
                       fprint(2, "hungup while reading\n");
               vnchungup(v);
       }
}

Pixfmt
vncrdpixfmt(Vnc *v)
{
       Pixfmt fmt;
       uchar pad[3];

       fmt.bpp = vncrdchar(v);
       fmt.depth = vncrdchar(v);
       fmt.bigendian = vncrdchar(v);
       fmt.truecolor = vncrdchar(v);
       fmt.red.max = vncrdshort(v);
       fmt.green.max = vncrdshort(v);
       fmt.blue.max = vncrdshort(v);
       fmt.red.shift = vncrdchar(v);
       fmt.green.shift = vncrdchar(v);
       fmt.blue.shift = vncrdchar(v);
       vncrdbytes(v, pad, 3);
       return fmt;
}

char*
vncrdstring(Vnc *v)
{
       ulong len;
       char *s;

       len = vncrdlong(v);
       s = malloc(len+1);
       assert(s != nil);

       vncrdbytes(v, s, len);
       s[len] = '\0';
       return s;
}

/*
* on the server side of the negotiation protocol, we read
* the client response and then run the negotiated function.
* in some cases (e.g., TLS) the negotiated function needs to
* use v->datafd directly and be sure that no data has been
* buffered away in the Bio.  since we know the client is waiting
* for our response, it won't have sent any until we respond.
* thus we read the response with vncrdstringx, which goes
* behind bio's back.
*/
char*
vncrdstringx(Vnc *v)
{
       char tmp[4];
       char *s;
       ulong len;

       assert(Bbuffered(&v->in) == 0);
       if(readn(v->datafd, tmp, 4) != 4){
               fprint(2, "cannot rdstringx: %r");
               vnchungup(v);
       }
       len = LONG(tmp);
       s = malloc(len+1);
       assert(s != nil);
       if(readn(v->datafd, s, len) != len){
               fprint(2, "cannot rdstringx len %lud: %r", len);
               vnchungup(v);
       }
       s[len] = '\0';
       return s;
}

void
vncwrstring(Vnc *v, char *s)
{
       ulong len;

       len = strlen(s);
       vncwrlong(v, len);
       vncwrbytes(v, s, len);
}

void
vncwrbytes(Vnc *v, void *a, int n)
{
       if(Bwrite(&v->out, a, n) < 0){
               if(verbose > 1)
                       fprint(2, "hungup while writing bytes\n");
               vnchungup(v);
       }
}

void
vncwrlong(Vnc *v, ulong u)
{
       uchar buf[4];

       buf[0] = u>>24;
       buf[1] = u>>16;
       buf[2] = u>>8;
       buf[3] = u;
       vncwrbytes(v, buf, 4);
}

void
vncwrshort(Vnc *v, ushort u)
{
       uchar buf[2];

       buf[0] = u>>8;
       buf[1] = u;
       vncwrbytes(v, buf, 2);
}

void
vncwrchar(Vnc *v, uchar c)
{
       vncwrbytes(v, &c, 1);
}

void
vncwrpixfmt(Vnc *v, Pixfmt *fmt)
{
       vncwrchar(v, fmt->bpp);
       vncwrchar(v, fmt->depth);
       vncwrchar(v, fmt->bigendian);
       vncwrchar(v, fmt->truecolor);
       vncwrshort(v, fmt->red.max);
       vncwrshort(v, fmt->green.max);
       vncwrshort(v, fmt->blue.max);
       vncwrchar(v, fmt->red.shift);
       vncwrchar(v, fmt->green.shift);
       vncwrchar(v, fmt->blue.shift);
       vncwrbytes(v, zero, 3);
}

void
vncwrrect(Vnc *v, Rectangle r)
{
       vncwrshort(v, r.min.x);
       vncwrshort(v, r.min.y);
       vncwrshort(v, r.max.x-r.min.x);
       vncwrshort(v, r.max.y-r.min.y);
}

void
vncwrpoint(Vnc *v, Point p)
{
       vncwrshort(v, p.x);
       vncwrshort(v, p.y);
}

void
vnclock(Vnc *v)
{
       qlock(v);
}

void
vncunlock(Vnc *v)
{
       qunlock(v);
}

void
hexdump(void *a, int n)
{
       uchar *p, *ep;

       p = a;
       ep = p+n;

       for(; p<ep; p++)
               print("%.2ux ", *p);
       print("\n");
}

void
vncgobble(Vnc *v, long n)
{
       uchar buf[8192];
       long m;

       while(n > 0){
               m = n;
               if(m > sizeof(buf))
                       m = sizeof(buf);
               vncrdbytes(v, buf, m);
               n -= m;
       }
}