#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include "cifs.h"

static Pkt *
t2hdr(Session *s, Share *sp, int cmd)
{
       Pkt *p;

       p = cifshdr(s, sp, SMB_COM_TRANSACTION2);

       p->tbase = pl16(p, 0);  /* 0  Total parameter bytes to be sent, filled later */
       pl16(p, 0);             /* 2  Total data bytes to be sent, filled later */
       pl16(p, 64);                    /* 4  Max parameter to return */
       pl16(p, (s->mtu - T2HDRLEN)-64); /* 6  Max data to return */
       p8(p, 0);                       /* 8  Max setup count to return */
       p8(p, 0);                       /* 9  Reserved */
       pl16(p, 0);                     /* 10 Flags */
       pl32(p, 1000);                  /* 12 Timeout (ms) */
       pl16(p, 0);                     /* 16 Reserved */
       pl16(p, 0);                     /* 18 Parameter count, filled later */
       pl16(p, 0);                     /* 20 Parameter offset, filled later */
       pl16(p, 0);                     /* 22 Data count, filled later */
       pl16(p, 0);                     /* 24 Data offset, filled later */
       p8(p, 1);                       /* 26 Setup count (in words) */
       p8(p, 0);                       /* 27 Reserved */
       pl16(p, cmd);                   /* setup[0] */
       pbytes(p);
       p8(p, 0);                       /* padding ??!?!? */

       return p;
}

static void
pt2param(Pkt *p)
{
       uchar *pos = p->pos;

       assert(p->tbase != 0);
       p->pos = p->tbase + 20;
       pl16(p, (pos - p->buf) - NBHDRLEN); /* param offset */

       p->tparam = p->pos = pos;
}

static void
pt2data(Pkt *p)
{
       uchar *pos = p->pos;

       assert(p->tbase != 0);
       assert(p->tparam != 0);

       p->pos = p->tbase +0;
       pl16(p, pos - p->tparam);               /* total param count */

       p->pos = p->tbase +18;
       pl16(p, pos - p->tparam);               /* param count */

       p->pos = p->tbase +24;
       pl16(p, (pos - p->buf) - NBHDRLEN);     /* data offset */

       p->tdata = p->pos = pos;
}

static int
t2rpc(Pkt *p)
{
       int got;
       uchar *pos;

       assert(p->tbase != 0);
       assert(p->tdata != 0);

       pos = p->pos;

       p->pos = p->tbase +2;
       pl16(p, pos - p->tdata);                /* total data count */

       p->pos = p->tbase +22;
       pl16(p, pos - p->tdata);                /* data count */

       p->pos = pos;
       if((got = cifsrpc(p)) == -1)
               return -1;

       gl16(p);                        /* Total parameter count */
       gl16(p);                        /* Total data count */
       gl16(p);                        /* Reserved */
       gl16(p);                        /* Parameter count in this buffer */
       p->tparam = p->buf +NBHDRLEN +gl16(p); /* Parameter offset */
       gl16(p);                        /* Parameter displacement */
       gl16(p);                        /* Data count (this buffer); */
       p->tdata = p->buf +NBHDRLEN +gl16(p); /* Data offset */
       gl16(p);                        /* Data displacement */
       g8(p);                          /* Setup count */
       g8(p);                          /* Reserved */

       return got;
}

static void
gt2param(Pkt *p)
{
       p->pos = p->tparam;
}

static void
gt2data(Pkt *p)
{
       p->pos = p->tdata;
}


int
T2findfirst(Session *s, Share *sp, int slots, char *path, int *got,
       long *resume, FInfo *fip)
{
       int pktlen, i, n, sh;
       uchar *next;
       Pkt *p;

       p = t2hdr(s, sp, TRANS2_FIND_FIRST2);
       p8(p, 'D');                     /* OS/2 */
       p8(p, ' ');                     /* OS/2 */

       pt2param(p);
       pl16(p, ATTR_HIDDEN|ATTR_SYSTEM|ATTR_DIRECTORY); /* Search attributes */
       pl16(p, slots);                 /* Search count */
       pl16(p, CIFS_SEARCH_RETURN_RESUME); /* Flags */
       pl16(p, SMB_FIND_FILE_FULL_DIRECTORY_INFO); /* Information level */
       pl32(p, 0);                     /* SearchStorage type (?) */
       ppath(p, path);                 /* path */

       pt2data(p);
       if((pktlen = t2rpc(p)) == -1){
               free(p);
               return -1;
       }

       s->lastfind = nsec();
       gt2param(p);

       sh = gl16(p);                   /* Sid (search handle) */
       *got = gl16(p);                 /* number of slots received */
       gl16(p);                        /* End of search flag */
       gl16(p);                        /* Offset into EA list if EA error */
       gl16(p);                        /* Offset into data to file name of last entry */

       gt2data(p);
       memset(fip, 0, slots * sizeof(FInfo));
       for(i = 0; i < *got; i++){
               next = p->pos;
               next += gl32(p);        /* offset to next entry */
               /*
                * bug in Windows - somtimes it lies about how many
                * directory entries it has put in the packet
                */
               if(next - p->buf > pktlen){
                       *got = i;
                       break;
               }

               *resume = gl32(p);              /* resume key for search */
               fip[i].created = gvtime(p);     /* creation time */
               fip[i].accessed = gvtime(p);    /* last access time */
               fip[i].written = gvtime(p);     /* last written time */
               fip[i].changed = gvtime(p);     /* change time */
               fip[i].size = gl64(p);          /* file size */
               gl64(p);                        /* bytes allocated */
               fip[i].attribs = gl32(p);       /* extended attributes */
               n = gl32(p);                    /* name length */
               gl32(p);                        /* EA size */
               gstr(p, fip[i].name, n);        /* name */
               p->pos = next;
       }

       free(p);
       return sh;

}

int
T2findnext(Session *s, Share *sp, int slots, char *path, int *got,
       long *resume, FInfo *fip, int sh)
{
       Pkt *p;
       int i, n;
       uchar *next;

       /*
        * So I believe from comp.protocols.smb if you send
        * TRANS2_FIND_NEXT2 requests too quickly to windows 95, it can
        * get confused and fail to reply, so we slow up a bit in these
        * circumstances.
        */
       if(!(s->caps & CAP_NT_SMBS) && nsec() - s->lastfind < 200000000LL)
               sleep(200);

       p = t2hdr(s, sp, TRANS2_FIND_NEXT2);
       p8(p, 'D');                     /* OS/2 */
       p8(p, ' ');                     /* OS/2 */

       pt2param(p);
       pl16(p, sh);                            /* search handle */
       pl16(p, slots);                         /* Search count */
       pl16(p, SMB_FIND_FILE_FULL_DIRECTORY_INFO); /* Information level */
       pl32(p, *resume);                       /* resume key */
       pl16(p, CIFS_SEARCH_CONTINUE_FROM_LAST); /* Flags */
       ppath(p, path);                         /* file+path to resume */

       pt2data(p);
       if(t2rpc(p) == -1){
               free(p);
               return -1;
       }

       s->lastfind = nsec();

       gt2param(p);
       *got = gl16(p);         /* number of slots received */
       gl16(p);                /* End of search flag */
       gl16(p);                /* Offset into EA list if EA error */
       gl16(p);                /* Offset into data to file name of last entry */

       gt2data(p);
       memset(fip, 0, slots * sizeof(FInfo));
       for(i = 0; i < *got; i++){
               next = p->pos;
               next += gl32(p);                /* offset to next entry */
               *resume = gl32(p);              /* resume key for search */
               fip[i].created = gvtime(p);     /* creation time */
               fip[i].accessed = gvtime(p);    /* last access time */
               fip[i].written = gvtime(p);     /* last written time */
               fip[i].changed = gvtime(p);     /* change time */
               fip[i].size = gl64(p);          /* file size */
               gl64(p);                        /* bytes allocated */
               fip[i].attribs = gl32(p);       /* extended attributes */
               n = gl32(p);                    /* name length */
               gl32(p);                        /* EA size */
               gstr(p, fip[i].name, n);        /* name */
               p->pos = next;
       }
       free(p);
       return 0;
}


/* supported by 2k/XP/NT4 */
int
T2queryall(Session *s, Share *sp, char *path, FInfo *fip)
{
       int n;
       Pkt *p;

       p = t2hdr(s, sp, TRANS2_QUERY_PATH_INFORMATION);
       pt2param(p);
       pl16(p, SMB_QUERY_FILE_ALL_INFO); /* Information level   */
       pl32(p, 0);                     /* reserved */
       ppath(p, path);                 /* path */

       pt2data(p);
       if(t2rpc(p) == -1){
               free(p);
               return -1;
       }
       gt2data(p);

       /*
        * The layout of this struct is wrong in the SINA
        * document, this layout gained by inspection.
        */
       memset(fip, 0, sizeof(FInfo));
       fip->created = gvtime(p);       /* creation time */
       fip->accessed = gvtime(p);      /* last access time */
       fip->written = gvtime(p);       /* last written time */
       fip->changed = gvtime(p);       /* change time */
       fip->attribs = gl32(p);         /* attributes */
       gl32(p);                        /* reserved */
       gl64(p);                        /* bytes allocated */
       fip->size = gl64(p);            /* file size */
       gl32(p);                        /* number of hard links */
       g8(p);                          /* delete pending */
       g8(p);                          /* is a directory */
       gl16(p);                        /* reserved */
       gl32(p);                        /* EA size */

       n = gl32(p);
       if(n >= sizeof fip->name)
               n = sizeof fip->name - 1;
       gstr(p, fip->name, n);

       free(p);
       return 0;
}

/* supported by 95/98/ME */
int
T2querystandard(Session *s, Share *sp, char *path, FInfo *fip)
{
       Pkt *p;

       p = t2hdr(s, sp, TRANS2_QUERY_PATH_INFORMATION);
       pt2param(p);
       pl16(p, SMB_INFO_STANDARD);     /* Information level */
       pl32(p, 0);                     /* reserved */
       ppath(p, path);                 /* path */

       pt2data(p);
       if(t2rpc(p) == -1){
               free(p);
               return -1;
       }
       gt2data(p);
       memset(fip, 0, sizeof(FInfo));
       fip->created = gdatetime(p);    /* creation time */
       fip->accessed = gdatetime(p);   /* last access time */
       fip->written = gdatetime(p);    /* last written time */
       fip->changed = fip->written;    /* change time */
       fip->size = gl32(p);            /* file size */
       gl32(p);                        /* bytes allocated */
       fip->attribs = gl16(p);         /* attributes */
       gl32(p);                        /* EA size */

       free(p);
       return 0;
}

int
T2setpathinfo(Session *s, Share *sp, char *path, FInfo *fip)
{
       int rc;
       Pkt *p;

       p = t2hdr(s, sp, TRANS2_SET_PATH_INFORMATION);
       pt2param(p);
       pl16(p, SMB_INFO_STANDARD);     /* Information level */
       pl32(p, 0);                     /* reserved */
       ppath(p, path);                 /* path */

       pt2data(p);
       pdatetime(p, fip->created);     /* created */
       pdatetime(p, fip->accessed);    /* accessed */
       pdatetime(p, fip->written);     /* written */
       pl32(p, fip->size);             /* size */
       pl32(p, 0);                     /* allocated */
       pl16(p, fip->attribs);          /* attributes */
       pl32(p, 0);                     /* EA size */
       pl16(p, 0);                     /* reserved */

       rc = t2rpc(p);
       free(p);
       return rc;

}

int
T2setfilelength(Session *s, Share *sp, int fh, FInfo *fip) /* FIXME: maybe broken, needs testing */
{
       int rc;
       Pkt *p;

       p = t2hdr(s, sp, TRANS2_SET_FILE_INFORMATION);
       pt2param(p);
       pl16(p, fh);                    /* file handle */
       pl16(p, SMB_SET_FILE_END_OF_FILE_INFO); /* Information level */
       pl16(p, 0);                     /* reserved */

       pt2data(p);
       pl64(p, fip->size);
       pl32(p, 0);                     /* padding ?! */
       pl16(p, 0);

       rc = t2rpc(p);
       free(p);
       return rc;
}


int
T2fsvolumeinfo(Session *s, Share *sp, long *created, long *serialno,
       char *label, int labellen)
{
       Pkt *p;
       long ct, sn, n;

       p = t2hdr(s, sp, TRANS2_QUERY_FS_INFORMATION);
       pt2param(p);
       pl16(p, SMB_QUERY_FS_VOLUME_INFO);      /* Information level */

       pt2data(p);

       if(t2rpc(p) == -1){
               free(p);
               return -1;
       }

       gt2data(p);
       ct = gvtime(p);                 /* creation time */
       sn = gl32(p);                   /* serial number */
       n = gl32(p);                    /* label name length */
       g8(p);                                  /* reserved */
       g8(p);                                  /* reserved */

       memset(label, 0, labellen);
       if(n < labellen && n > 0)
               gstr(p, label, n);      /* file system label */
       if(created)
               *created = ct;
       if(serialno)
               *serialno = sn;

       free(p);
       return 0;
}

int
T2fsdeviceinfo(Session *s, Share *sp, int *type, int *flags)
{
       Pkt *p;
       long t, f;

       p = t2hdr(s, sp, TRANS2_QUERY_FS_INFORMATION);
       pt2param(p);
       pl16(p, SMB_QUERY_FS_DEVICE_INFO);      /* Information level */

       pt2data(p);

       if(t2rpc(p) == -1){
               free(p);
               return -1;
       }

       gt2data(p);
       t = gl32(p);                    /* device type */
       f = gl32(p);                    /* device characteristics */

       if(type)
               *type = t;
       if(flags)
               *flags = f;
       free(p);
       return 0;
}

int
T2fssizeinfo(Session *s, Share *sp, uvlong *total, uvlong *unused)
{
       Pkt *p;
       uvlong t, f, n, b;

       p = t2hdr(s, sp, TRANS2_QUERY_FS_INFORMATION);
       pt2param(p);
       pl16(p, SMB_QUERY_FS_SIZE_INFO);        /* Information level */

       pt2data(p);

       if(t2rpc(p) == -1){
               free(p);
               return -1;
       }

       gt2data(p);
       t = gl64(p);            /* total blocks */
       f = gl64(p);            /* free blocks */
       n = gl32(p);            /* sectors per block */
       b = gl32(p);            /* bytes per sector */

       if(free)
               *unused = f * n * b;
       if(total)
               *total = t * n * b;

       free(p);
       return 0;
}

int
T2getdfsreferral(Session *s, Share *sp, char *path, int *gflags, int *used,
       Refer *re, int nent)
{
       int i, vers, nret, len;
       char tmp[1024];
       uchar *base;
       Pkt *p;

       p = t2hdr(s, sp, TRANS2_GET_DFS_REFERRAL);
       pt2param(p);
       pl16(p, 3); /* max info level we understand, must be >= 3 for domain requests */
       ppath(p, path);

       pt2data(p);

       if(t2rpc(p) == -1){
               free(p);
               return -1;
       }

       memset(re, 0, sizeof *re * nent);
       gt2data(p);

       *used = gl16(p) / 2;    /* length used (/2 as Windows counts in runes) */
       nret = gl16(p);         /* number of referrals returned */
       *gflags = gl32(p);      /* global flags */

       for(i = 0; i < nret && i < nent && i < 16; i++){
               base = p->pos;
               vers = gl16(p);         /* version of records */
               len = gl16(p);          /* length of records */
               re[i].type = gl16(p);   /* server type */
               re[i].flags = gl16(p);  /* referal flags */
               switch(vers){
               case 1:
                       re[i].ttl = 300;                /* 30 mins */
                       gstr(p, tmp, sizeof tmp);
                       re[i].addr = estrdup9p(tmp);
                       re[i].path = estrdup9p(tmp);
                       break;
               case 2:
                       re[i].ttl = gl32(p);
                       if(re[i].ttl == 0)
                               re[i].ttl = 1800;
                       goff(p, base, re[i].path, sizeof tmp);
                       re[i].path = estrdup9p(tmp);
                       goff(p, base, re[i].path, sizeof tmp);/* spurious 8.3 path */
                       goff(p, base, tmp, sizeof tmp);
                       re[i].addr = estrdup9p(tmp);
                       break;
               case 3:
                       if(re[i].flags & DFS_REFERAL_LIST){     /* normal referal */
                               re[i].ttl = gl32(p);
                               if(re[i].ttl == 0)
                                       re[i].ttl = 1800;
                               goff(p, base, tmp, sizeof tmp);
                               re[i].path = estrdup9p(tmp);
                               gl16(p);
                               goff(p, base, tmp, sizeof tmp);
                               re[i].addr = estrdup9p(tmp);
                       }
                       else{                                   /* domain root */
                               re[i].ttl = gl32(p);
                               if(re[i].ttl == 0)
                                       re[i].ttl = 300;
                               goff(p, base, tmp, sizeof tmp);
                               re[i].path = estrdup9p(tmp);
                               gl16(p);        /* spurious 8.3 path */
                               goff(p, base, tmp, sizeof tmp);
                               re[i].addr = estrdup9p(tmp);
                               /* GUID (historic) here, skipped below as we know the record length */
                       }
                       break;
               default:
                       /*
                        * this should never happen as we specify our maximum
                        * understood level in the request (above)
                        */
                       fprint(2, "%d - unsupported DFS infolevel\n", vers);
                       re[i].path = estrdup9p(tmp);
                       re[i].addr = estrdup9p(tmp);
                       break;
               }
               p->pos = base+len;
       }

       free(p);
       return i;
}