/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "Licence").
 * You may not use this file except in compliance with the Licence.
 *
 * You can obtain a copy of the licence at RISC OS path @.^.LICENCE
 * or  http://www.riscosdev.com/lanman98/LICENCE.CDDL
 * See the Licence for the specific language governing permissions
 * and limitations under the Licence.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the Licence file. If applicable, add the
 * following below this CDDL HEADER, with the fields enclosed by
 * brackets "[]" replaced with your own identifying information:
 * Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/* Portions Copyright 2026 RISC OS Developments */
 
/*
 *   Copyright 1996 Warm Silence Software Ltd.  All rights reserved.
 *   Use is subject to license terms.
 */
 
/*   PHBG 30/12/97: Initial version
 */

#include <string.h>
#include <stdlib.h>
#include "LanMan98BaseLib/memory.h"
#include "LanMan98BaseLib/error.h"
#include "data.h"
#include "vc.h"
#include "smbcmd.h"
#include "mb.h"

#include "trans.h"

#define MIN(x,y) ((x) < (y) ? (x) : (y))
#define MAX(x,y) ((x) > (y) ? (x) : (y))


void echo(vc_t vc, void (*send)(vc_t), void (*receive)(vc_t))
{
    data_t mb;
    ushort vwv[1];

    mb = VCBuffer(vc);
    MBSetCom(mb, SMBecho);
    vwv[0] = 1;
    MBSetVWV(mb, 1, vwv);
    MBAddRaw(mb, 6, "hello");
    send(vc);
    receive(vc);
}


trans_buf_t TransInitBuf(int in, int out)
{
    trans_buf_t tb;

    tb = Malloc(sizeof(*tb));
    tb->in = in;
    tb->out = out;
    tb->buf = Malloc(MAX(in, out));
    tb->refs = 1;
    return tb;
}

trans_buf_t TransDupBuf(trans_buf_t tb)
{
    tb->refs++;
    return tb;
}

void TransFreeBuf(trans_buf_t tb)
{
    if(tb)
    {
        tb->refs--;
        if(tb->refs <= 0)
        {
            Free(tb->buf);
            Free(tb);
        }
    }
}

/* A fragment must add new bytes. Merely adding its count lets overlapping
   replies finish a transaction while leaving holes in the output buffer. */
static void mark_fragment(unsigned char *seen, int displacement, int count)
{
    int i;
    for(i = displacement; i < displacement + count; i++)
    {
        unsigned char bit = (unsigned char)(1u << (i & 7));
        if(seen[i >> 3] & bit) Error("Overlapping Transaction reply fragments");
        seen[i >> 3] |= bit;
    }
}

static void trans_communicate(trans_buf_t setup, trans_buf_t parm, trans_buf_t data, vc_t vc, void (*send)(vc_t), void (*receive)(vc_t), char *name)
{
    data_t mb;
    int n_vwv;
    ushort *vwv;
    int parm_received, data_received;
    int parm_displacement, data_displacement;
    int parm_offset, parm_count, data_offset, data_count;
    int parm_pad, data_pad;
    int dummy = 0, first;
    int len;
    volatile int multi, parts;
    unsigned char *parm_seen = NULL, *data_seen = NULL;
    int parm_end = 0, data_end = 0;

    multi = 0;
    parts = 0;
    parm_displacement = 0;
    data_displacement = 0;

    mb = VCBuffer(vc);
    n_vwv = 14 + setup->in/2;
    parm_offset = HDRSIZE + 1 + n_vwv * sizeof(short) + sizeof(short);
    if(name) parm_offset += strlen(name) + 1;
    parm_pad = -parm_offset & 3;
    parm_offset += parm_pad;
    parm_count = MIN(parm->in - parm_displacement, mb.size - parm_offset);
    data_offset = parm_offset + parm_count;
    data_pad = -data_offset & 3;
    data_offset += data_pad;
    data_count = MIN(data->in - data_displacement, mb.size - data_offset);
    if(parm_count == 0)
    {
        parm_offset = 0;
    }
    if(data_count == 0)
    {
        data_offset = data_pad = 0;
    }

    MBSetCom(mb, name ? SMBtransaction : SMBtransaction2);
    vwv = Malloc(MAX(28+setup->in, 20+setup->out));
    ExceptTry
    {
        parm_seen = Malloc((parm->out + 7) / 8 + 1);
        data_seen = Malloc((data->out + 7) / 8 + 1);
        memset(parm_seen, 0, (parm->out + 7) / 8 + 1);
        memset(data_seen, 0, (data->out + 7) / 8 + 1);
        vwv[0] = parm->in;
        vwv[1] = data->in;
        vwv[2] = parm->out;
        vwv[3] = data->out;
        vwv[4] = setup->out;
        vwv[5] = 0;
        vwv[6] = 100; // Timeout ???
        vwv[7] = 100; // Timeout ???
        vwv[8] = 0;
        vwv[9] = parm_count;
        vwv[10] = parm_offset;
        vwv[11] = data_count;
        vwv[12] = data_offset;
        vwv[13] = setup->in/2;
        memcpy(vwv+14, setup->buf, setup->in);
        MBSetVWV(mb, n_vwv, vwv);
        if(name) MBAddRaw(mb, strlen(name)+1, name);
        if(parm_pad) MBAddRaw(mb, parm_pad, &dummy);
        if(parm_count) MBAddRaw(mb, parm_count, ((char *)parm->buf) + parm_displacement);
        if(data_pad) MBAddRaw(mb, data_pad, &dummy);
        if(data_count) MBAddRaw(mb, data_count, ((char *)data->buf) + data_displacement);
        send(vc);
        parm_displacement += parm_count;
        data_displacement += data_count;

        if(parm_displacement < parm->in || data_displacement < data->in)
            receive(vc);
        while(parm_displacement < parm->in || data_displacement < data->in)
        {
            n_vwv = name ? 8 : 9;
            parm_offset = HDRSIZE + 1 + n_vwv * sizeof(short) + sizeof(short);
            parm_pad = -parm_offset & 3;
            parm_offset += parm_pad;
            parm_count = MIN(parm->in - parm_displacement, mb.size - parm_offset);
            data_offset = parm_offset + parm_count;
            data_pad = -data_offset & 3;
            data_offset += data_pad;
            data_count = MIN(data->in - data_displacement, mb.size - data_offset);
            if(data_count == 0) data_pad = 0;

            MBSetCom(mb, name ? SMBtransaction_secondary : SMBtransaction2secondary);
            vwv[0] = parm->in;
            vwv[1] = data->in;
            vwv[2] = parm_count;
            vwv[3] = parm_offset;
            vwv[4] = parm_displacement;
            vwv[5] = data_count;
            vwv[6] = data_offset;
            vwv[7] = data_displacement;
            if(name == NULL) vwv[8] = (ushort) -1;
            MBSetVWV(mb, n_vwv, vwv);
            if(parm_pad) MBAddRaw(mb, parm_pad, &dummy);
            if(parm_count) MBAddRaw(mb, parm_count, ((char *)parm->buf) + parm_displacement);
            if(data_pad) MBAddRaw(mb, data_pad, &dummy);
            if(data_count) MBAddRaw(mb, data_count, ((char *)data->buf) + data_displacement);
            send(vc);
            multi = 1;
            parm_displacement += parm_count;
            data_displacement += data_count;
        }

        parm_received = data_received = 0;
        first = 1;
        while(parm_received < parm->out || data_received < data->out)
        {
            receive(vc);
            parts++;
            MBGetVWV(mb, 10 + setup->out/2, vwv);
            if(vwv[0] > parm->out || vwv[1] > data->out || vwv[9] > setup->out/2)
                Error("Unexpectedly large response from Transaction");
            /*
                The counts, offsets and displacements each piece is copied
                by are the server's word every bit as much as the totals
                just checked, and they were taken as read: a displacement
                past the end of the buffer put the server's bytes wherever
                it pointed, and an offset past the end of the packet took
                them from wherever it pointed.  A piece has to fit both the
                buffer it goes into and the packet it comes out of.
            */
            len = VCLength(vc);
            if(vwv[3] > parm->out || vwv[5] > parm->out - vwv[3] ||
               vwv[4] > len || vwv[3] > len - vwv[4] ||
               vwv[6] > data->out || vwv[8] > data->out - vwv[6] ||
               vwv[7] > len || vwv[6] > len - vwv[7])
                Error("Malformed response to Transaction");
            if(vwv[0] < parm_end || vwv[1] < data_end ||
               (vwv[3] && (vwv[3] > vwv[0] || vwv[5] > vwv[0] - vwv[3])) ||
               (vwv[6] && (vwv[6] > vwv[1] || vwv[8] > vwv[1] - vwv[6])))
                Error("Transaction fragment exceeds its declared total");
            mark_fragment(parm_seen, vwv[5], vwv[3]);
            mark_fragment(data_seen, vwv[8], vwv[6]);
            if(vwv[3] && vwv[5] + vwv[3] > parm_end) parm_end = vwv[5] + vwv[3];
            if(vwv[6] && vwv[8] + vwv[6] > data_end) data_end = vwv[8] + vwv[6];
            if(vwv[0] < parm->out) parm->out = vwv[0];
            if(vwv[1] < data->out) data->out = vwv[1];
            if(vwv[9] < setup->out/2 && first) setup->out = vwv[9]*2;
            first = 0;
            /*
                Nothing in this piece and more still expected: the loop
                below would wait for the rest for ever, which a server can
                do on purpose.  A reply that carries nothing and says it
                has nothing - the answer to setting an attribute, say - has
                already cut the totals above to nothing and is not this.
            */
            if(vwv[3] == 0 && vwv[6] == 0 &&
               (parm_received < parm->out || data_received < data->out))
                Error("Response to Transaction made no progress");


            if(vwv[9]) memcpy(setup->buf, vwv+10, vwv[9]*2);
            if(vwv[3]) memcpy(((char *)parm->buf) + vwv[5], mb.buf + vwv[4], vwv[3]);
            if(vwv[6]) memcpy(((char *)data->buf) + vwv[8], mb.buf + vwv[7], vwv[6]);

            parm_received += vwv[3];
            data_received += vwv[6];
        }
    }
    ExceptCatch
    {
        if(parm_seen) Free(parm_seen);
        if(data_seen) Free(data_seen);
        Free(vwv);
        ExceptRethrow();
    }
    Free(parm_seen); Free(data_seen);
    Free(vwv);
    /*
        An echo used to follow every transaction, and nothing records why.
        Replies are read in order rather than matched, so after an exchange
        of one packet each way there is nothing it could put right - and it
        cost a round trip every time a name was looked up or a directory
        read.  It is kept where a transaction took more than one packet,
        the only place a server could still be part way through, and
        LanMan98$TransEcho puts it back everywhere for a server that turns
        out to need it.
    */
    if(multi || (parts > 1) || getenv("LanMan98$TransEcho"))
        echo(vc, send, receive);
}

void TransCommunicate(trans_buf_t setup, trans_buf_t parm, trans_buf_t data, vc_t vc, void (*send)(vc_t), void (*receive)(vc_t))
{
    trans_communicate(setup, parm, data, vc, send, receive, NULL);
}

void TransRAP(trans_buf_t parm, trans_buf_t data, vc_t vc, void (*send)(vc_t), void (*receive)(vc_t))
{
    trans_buf_t setup;

    setup = TransInitBuf(0, 0);
    ExceptTry
    {
        trans_communicate(setup, parm, data, vc, send, receive, "\\PIPE\\LANMAN");
    }
    ExceptCatch
    {
        TransFreeBuf(setup);
        ExceptRethrow();
    }
    TransFreeBuf(setup);
}
