/*
 * NDR Serialization Services
 *
 * Copyright (c) 2007 Robert Shearman for CodeWeavers
 *
 * This library 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 2.1 of the License, or (at your option) any later version.
 *
 * This library 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 this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <stdarg.h>
#include <stdio.h>

#include "windef.h"
#include "winbase.h"
#include "winerror.h"
#include "rpc.h"
#include "midles.h"
#include "ndrtypes.h"

#include "ndr_misc.h"
#include "ndr_stubless.h"

#include "wine/debug.h"
#include "wine/rpcfc.h"

WINE_DEFAULT_DEBUG_CHANNEL(ole);

static inline void init_MIDL_ES_MESSAGE(MIDL_ES_MESSAGE *pEsMsg)
{
    memset(pEsMsg, 0, sizeof(*pEsMsg));
    /* even if we are unmarshalling, as we don't want pointers to be pointed
     * to buffer memory */
    pEsMsg->StubMsg.IsClient = TRUE;
}

/***********************************************************************
 *            MesEncodeIncrementalHandleCreate [RPCRT4.@]
 */
RPC_STATUS WINAPI MesEncodeIncrementalHandleCreate(
    void *UserState, MIDL_ES_ALLOC AllocFn, MIDL_ES_WRITE WriteFn,
    handle_t *pHandle)
{
    MIDL_ES_MESSAGE *pEsMsg;

    TRACE("(%p, %p, %p, %p)\n", UserState, AllocFn, WriteFn, pHandle);

    pEsMsg = HeapAlloc(GetProcessHeap(), 0, sizeof(*pEsMsg));
    if (!pEsMsg)
        return ERROR_OUTOFMEMORY;

    init_MIDL_ES_MESSAGE(pEsMsg);

    pEsMsg->Operation = MES_ENCODE;
    pEsMsg->UserState = UserState;
    pEsMsg->HandleStyle = MES_INCREMENTAL_HANDLE;
    pEsMsg->Alloc = AllocFn;
    pEsMsg->Write = WriteFn;

    *pHandle = (handle_t)pEsMsg;

    return RPC_S_OK;
}

/***********************************************************************
 *            MesDecodeIncrementalHandleCreate [RPCRT4.@]
 */
RPC_STATUS WINAPI MesDecodeIncrementalHandleCreate(
    void *UserState, MIDL_ES_READ ReadFn, handle_t *pHandle)
{
    MIDL_ES_MESSAGE *pEsMsg;

    TRACE("(%p, %p, %p)\n", UserState, ReadFn, pHandle);

    pEsMsg = HeapAlloc(GetProcessHeap(), 0, sizeof(*pEsMsg));
    if (!pEsMsg)
        return ERROR_OUTOFMEMORY;

    init_MIDL_ES_MESSAGE(pEsMsg);

    pEsMsg->Operation = MES_DECODE;
    pEsMsg->UserState = UserState;
    pEsMsg->HandleStyle = MES_INCREMENTAL_HANDLE;
    pEsMsg->Read = ReadFn;

    *pHandle = (handle_t)pEsMsg;

    return RPC_S_OK;
}

/***********************************************************************
 *            MesIncrementalHandleReset [RPCRT4.@]
 */
RPC_STATUS WINAPI MesIncrementalHandleReset(
    handle_t Handle, void *UserState, MIDL_ES_ALLOC AllocFn,
    MIDL_ES_WRITE WriteFn, MIDL_ES_READ ReadFn, MIDL_ES_CODE Operation)
{
    MIDL_ES_MESSAGE *pEsMsg = Handle;

    TRACE("(%p, %p, %p, %p, %p, %d)\n", Handle, UserState, AllocFn,
        WriteFn, ReadFn, Operation);

    init_MIDL_ES_MESSAGE(pEsMsg);

    pEsMsg->Operation = Operation;
    pEsMsg->UserState = UserState;
    pEsMsg->HandleStyle = MES_INCREMENTAL_HANDLE;
    pEsMsg->Alloc = AllocFn;
    pEsMsg->Write = WriteFn;
    pEsMsg->Read = ReadFn;

    return RPC_S_OK;
}

/***********************************************************************
 *            MesHandleFree [RPCRT4.@]
 */
RPC_STATUS WINAPI MesHandleFree(handle_t Handle)
{
    TRACE("(%p)\n", Handle);
    HeapFree(GetProcessHeap(), 0, Handle);
    return RPC_S_OK;
}

/***********************************************************************
 *            MesEncodeFixedBufferHandleCreate [RPCRT4.@]
 */
RPC_STATUS RPC_ENTRY MesEncodeFixedBufferHandleCreate(
    char *Buffer, ULONG BufferSize, ULONG *pEncodedSize, handle_t *pHandle)
{
    MIDL_ES_MESSAGE *pEsMsg;

    TRACE("(%p, %d, %p, %p)\n", Buffer, BufferSize, pEncodedSize, pHandle);

    pEsMsg = HeapAlloc(GetProcessHeap(), 0, sizeof(*pEsMsg));
    if (!pEsMsg)
        return ERROR_OUTOFMEMORY;

    init_MIDL_ES_MESSAGE(pEsMsg);

    pEsMsg->Operation = MES_ENCODE;
    pEsMsg->HandleStyle = MES_FIXED_BUFFER_HANDLE;
    pEsMsg->Buffer = (unsigned char *)Buffer;
    pEsMsg->BufferSize = BufferSize;
    pEsMsg->pEncodedSize = pEncodedSize;

    *pHandle = (handle_t)pEsMsg;

    return RPC_S_OK;
}

/***********************************************************************
 *            MesEncodeDynBufferHandleCreate [RPCRT4.@]
 */
RPC_STATUS RPC_ENTRY MesEncodeDynBufferHandleCreate(char **ppBuffer,
        ULONG *pEncodedSize, handle_t *pHandle)
{
    FIXME("%p %p %p stub\n", ppBuffer, pEncodedSize, pHandle);
    return RPC_S_OK;
}

/***********************************************************************
 *            MesDecodeBufferHandleCreate [RPCRT4.@]
 */
RPC_STATUS RPC_ENTRY MesDecodeBufferHandleCreate(
    char *Buffer, ULONG BufferSize, handle_t *pHandle)
{
    MIDL_ES_MESSAGE *pEsMsg;

    TRACE("(%p, %d, %p)\n", Buffer, BufferSize, pHandle);

    pEsMsg = HeapAlloc(GetProcessHeap(), 0, sizeof(*pEsMsg));
    if (!pEsMsg)
        return ERROR_OUTOFMEMORY;

    init_MIDL_ES_MESSAGE(pEsMsg);

    pEsMsg->Operation = MES_DECODE;
    pEsMsg->HandleStyle = MES_FIXED_BUFFER_HANDLE;
    pEsMsg->Buffer = (unsigned char *)Buffer;
    pEsMsg->BufferSize = BufferSize;

    *pHandle = (handle_t)pEsMsg;

    return RPC_S_OK;
}

static void es_data_alloc(MIDL_ES_MESSAGE *pEsMsg, ULONG size)
{
    if (pEsMsg->HandleStyle == MES_INCREMENTAL_HANDLE)
    {
        unsigned int tmpsize = size;
        TRACE("%d with incremental handle\n", size);
        pEsMsg->Alloc(pEsMsg->UserState, (char **)&pEsMsg->StubMsg.Buffer, &tmpsize);
        if (tmpsize < size)
        {
            ERR("not enough bytes allocated - requested %d, got %d\n", size, tmpsize);
            RpcRaiseException(ERROR_OUTOFMEMORY);
        }
    }
    else if (pEsMsg->HandleStyle == MES_FIXED_BUFFER_HANDLE)
    {
        TRACE("%d with fixed buffer handle\n", size);
        pEsMsg->StubMsg.Buffer = pEsMsg->Buffer;
    }
    pEsMsg->StubMsg.RpcMsg->Buffer = pEsMsg->StubMsg.BufferStart = pEsMsg->StubMsg.Buffer;
}

static void es_data_read(MIDL_ES_MESSAGE *pEsMsg, ULONG size)
{
    if (pEsMsg->HandleStyle == MES_INCREMENTAL_HANDLE)
    {
        unsigned int tmpsize = size;
        TRACE("%d from incremental handle\n", size);
        pEsMsg->Read(pEsMsg->UserState, (char **)&pEsMsg->StubMsg.Buffer, &tmpsize);
        if (tmpsize < size)
        {
            ERR("not enough bytes read - requested %d, got %d\n", size, tmpsize);
            RpcRaiseException(ERROR_OUTOFMEMORY);
        }
    }
    else
    {
        TRACE("%d from fixed or dynamic buffer handle\n", size);
        /* FIXME: validate BufferSize? */
        pEsMsg->StubMsg.Buffer = pEsMsg->Buffer;
        pEsMsg->Buffer += size;
        pEsMsg->BufferSize -= size;
    }
    pEsMsg->StubMsg.BufferLength = size;
    pEsMsg->StubMsg.RpcMsg->Buffer = pEsMsg->StubMsg.BufferStart = pEsMsg->StubMsg.Buffer;
    pEsMsg->StubMsg.BufferEnd = pEsMsg->StubMsg.Buffer + size;
}

static void es_data_write(MIDL_ES_MESSAGE *pEsMsg, ULONG size)
{
    if (pEsMsg->HandleStyle == MES_INCREMENTAL_HANDLE)
    {
        TRACE("%d to incremental handle\n", size);
        pEsMsg->Write(pEsMsg->UserState, (char *)pEsMsg->StubMsg.BufferStart, size);
    }
    else
    {
        TRACE("%d to dynamic or fixed buffer handle\n", size);
        *pEsMsg->pEncodedSize += size;
    }
}

static inline ULONG mes_proc_header_buffer_size(void)
{
    return 4 + 2*sizeof(RPC_SYNTAX_IDENTIFIER) + 12;
}

static void mes_proc_header_marshal(MIDL_ES_MESSAGE *pEsMsg)
{
    const RPC_CLIENT_INTERFACE *client_interface = pEsMsg->StubMsg.StubDesc->RpcInterfaceInformation;
    *(WORD *)pEsMsg->StubMsg.Buffer = 0x0101;
    pEsMsg->StubMsg.Buffer += 2;
    *(WORD *)pEsMsg->StubMsg.Buffer = 0xcccc;
    pEsMsg->StubMsg.Buffer += 2;
    memcpy(pEsMsg->StubMsg.Buffer, &client_interface->TransferSyntax, sizeof(RPC_SYNTAX_IDENTIFIER));
    pEsMsg->StubMsg.Buffer += sizeof(RPC_SYNTAX_IDENTIFIER);
    memcpy(pEsMsg->StubMsg.Buffer, &pEsMsg->InterfaceId, sizeof(RPC_SYNTAX_IDENTIFIER));
    pEsMsg->StubMsg.Buffer += sizeof(RPC_SYNTAX_IDENTIFIER);
    *(DWORD *)pEsMsg->StubMsg.Buffer = pEsMsg->ProcNumber;
    pEsMsg->StubMsg.Buffer += 4;
    *(DWORD *)pEsMsg->StubMsg.Buffer = 0x00000001;
    pEsMsg->StubMsg.Buffer += 4;
    *(DWORD *)pEsMsg->StubMsg.Buffer = pEsMsg->ByteCount;
    pEsMsg->StubMsg.Buffer += 4;
}

static void mes_proc_header_unmarshal(MIDL_ES_MESSAGE *pEsMsg)
{
    const RPC_CLIENT_INTERFACE *client_interface = pEsMsg->StubMsg.StubDesc->RpcInterfaceInformation;

    es_data_read(pEsMsg, mes_proc_header_buffer_size());

    if (*(WORD *)pEsMsg->StubMsg.Buffer != 0x0101)
    {
        FIXME("unknown value at Buffer[0] 0x%04x\n", *(WORD *)pEsMsg->StubMsg.Buffer);
        RpcRaiseException(RPC_X_WRONG_ES_VERSION);
    }
    pEsMsg->StubMsg.Buffer += 2;
    if (*(WORD *)pEsMsg->StubMsg.Buffer != 0xcccc)
        FIXME("unknown value at Buffer[2] 0x%04x\n", *(WORD *)pEsMsg->StubMsg.Buffer);
    pEsMsg->StubMsg.Buffer += 2;
    if (memcmp(pEsMsg->StubMsg.Buffer, &client_interface->TransferSyntax, sizeof(RPC_SYNTAX_IDENTIFIER)))
    {
        const RPC_SYNTAX_IDENTIFIER *AlienTransferSyntax = (const RPC_SYNTAX_IDENTIFIER *)pEsMsg->StubMsg.Buffer;
        ERR("bad transfer syntax %s {%d.%d}\n", debugstr_guid(&AlienTransferSyntax->SyntaxGUID),
            AlienTransferSyntax->SyntaxVersion.MajorVersion,
            AlienTransferSyntax->SyntaxVersion.MinorVersion);
        RpcRaiseException(RPC_S_UNSUPPORTED_TRANS_SYN);
    }
    pEsMsg->StubMsg.Buffer += sizeof(RPC_SYNTAX_IDENTIFIER);
    memcpy(&pEsMsg->InterfaceId, pEsMsg->StubMsg.Buffer, sizeof(RPC_SYNTAX_IDENTIFIER));
    pEsMsg->StubMsg.Buffer += sizeof(RPC_SYNTAX_IDENTIFIER);
    pEsMsg->ProcNumber = *(DWORD *)pEsMsg->StubMsg.Buffer;
    pEsMsg->StubMsg.Buffer += 4;
    if (*(DWORD *)pEsMsg->StubMsg.Buffer != 0x00000001)
        FIXME("unknown value 0x%08x, expected 0x00000001\n", *(DWORD *)pEsMsg->StubMsg.Buffer);
    pEsMsg->StubMsg.Buffer += 4;
    pEsMsg->ByteCount = *(DWORD *)pEsMsg->StubMsg.Buffer;
    pEsMsg->StubMsg.Buffer += 4;
    if (pEsMsg->ByteCount + mes_proc_header_buffer_size() < pEsMsg->ByteCount)
        RpcRaiseException(RPC_S_INVALID_BOUND);
}

/***********************************************************************
 *            NdrMesProcEncodeDecode [RPCRT4.@]
 */
void WINAPIV NdrMesProcEncodeDecode(handle_t Handle, const MIDL_STUB_DESC * pStubDesc, PFORMAT_STRING pFormat, ...)
{
    /* pointer to start of stack where arguments start */
    RPC_MESSAGE rpcMsg;
    MIDL_ES_MESSAGE *pEsMsg = Handle;
    /* size of stack */
    unsigned short stack_size;
    /* header for procedure string */
    const NDR_PROC_HEADER *pProcHeader = (const NDR_PROC_HEADER *)&pFormat[0];
    /* the value to return to the client from the remote procedure */
    LONG_PTR RetVal = 0;
    const RPC_CLIENT_INTERFACE *client_interface;

    TRACE("Handle %p, pStubDesc %p, pFormat %p, ...\n", Handle, pStubDesc, pFormat);

    /* Later NDR language versions probably won't be backwards compatible */
    if (pStubDesc->Version > 0x50002)
    {
        FIXME("Incompatible stub description version: 0x%x\n", pStubDesc->Version);
        RpcRaiseException(RPC_X_WRONG_STUB_VERSION);
    }

    client_interface = pStubDesc->RpcInterfaceInformation;
    pEsMsg->InterfaceId = client_interface->InterfaceId;

    if (pProcHeader->Oi_flags & RPC_FC_PROC_OIF_RPCFLAGS)
    {
        const NDR_PROC_HEADER_RPC *pProcHeader = (const NDR_PROC_HEADER_RPC *)&pFormat[0];
        stack_size = pProcHeader->stack_size;
        pEsMsg->ProcNumber = pProcHeader->proc_num;
        pFormat += sizeof(NDR_PROC_HEADER_RPC);
    }
    else
    {
        stack_size = pProcHeader->stack_size;
        pEsMsg->ProcNumber = pProcHeader->proc_num;
        pFormat += sizeof(NDR_PROC_HEADER);
    }

    if (pProcHeader->handle_type == RPC_FC_BIND_EXPLICIT)
    {
        switch (*pFormat) /* handle_type */
        {
        case RPC_FC_BIND_PRIMITIVE: /* explicit primitive */
            pFormat += sizeof(NDR_EHD_PRIMITIVE);
            break;
        case RPC_FC_BIND_GENERIC: /* explicit generic */
            pFormat += sizeof(NDR_EHD_GENERIC);
            break;
        case RPC_FC_BIND_CONTEXT: /* explicit context */
            pFormat += sizeof(NDR_EHD_CONTEXT);
            break;
        default:
            ERR("bad explicit binding handle type (0x%02x)\n", pProcHeader->handle_type);
            RpcRaiseException(RPC_X_BAD_STUB_DATA);
        }
    }

    TRACE("stack size: 0x%x\n", stack_size);
    TRACE("proc num: %d\n", pEsMsg->ProcNumber);

    memset(&rpcMsg, 0, sizeof(rpcMsg));
    pEsMsg->StubMsg.RpcMsg = &rpcMsg;
    pEsMsg->StubMsg.StubDesc = pStubDesc;
    pEsMsg->StubMsg.pfnAllocate = pStubDesc->pfnAllocate;
    pEsMsg->StubMsg.pfnFree = pStubDesc->pfnFree;

    /* create the full pointer translation tables, if requested */
    if (pProcHeader->Oi_flags & RPC_FC_PROC_OIF_FULLPTR)
        pEsMsg->StubMsg.FullPtrXlatTables = NdrFullPointerXlatInit(0,XLAT_CLIENT);

    TRACE("Oi_flags = 0x%02x\n", pProcHeader->Oi_flags);
    TRACE("stubdesc version = 0x%x\n", pStubDesc->Version);
    TRACE("MIDL stub version = 0x%x\n", pStubDesc->MIDLVersion);

    /* needed for conformance of top-level objects */
#ifdef __i386__
    pEsMsg->StubMsg.StackTop = *(unsigned char **)(&pFormat+1);
#else
# warning Stack not retrieved for your CPU architecture
#endif

    switch (pEsMsg->Operation)
    {
    case MES_ENCODE:
        pEsMsg->StubMsg.BufferLength = mes_proc_header_buffer_size();

        client_do_args_old_format(&pEsMsg->StubMsg, pFormat, PROXY_CALCSIZE,
            pEsMsg->StubMsg.StackTop, stack_size, (unsigned char *)&RetVal,
            FALSE /* object_proc */, TRUE /* ignore_retval */);

        pEsMsg->ByteCount = pEsMsg->StubMsg.BufferLength - mes_proc_header_buffer_size();
        es_data_alloc(pEsMsg, pEsMsg->StubMsg.BufferLength);

        mes_proc_header_marshal(pEsMsg);

        client_do_args_old_format(&pEsMsg->StubMsg, pFormat, PROXY_MARSHAL,
            pEsMsg->StubMsg.StackTop, stack_size, (unsigned char *)&RetVal,
            FALSE /* object_proc */, TRUE /* ignore_retval */);

        es_data_write(pEsMsg, pEsMsg->ByteCount);
        break;
    case MES_DECODE:
        mes_proc_header_unmarshal(pEsMsg);

        es_data_read(pEsMsg, pEsMsg->ByteCount);

        client_do_args_old_format(&pEsMsg->StubMsg, pFormat, PROXY_UNMARSHAL,
            pEsMsg->StubMsg.StackTop, stack_size, (unsigned char *)&RetVal,
            FALSE /* object_proc */, TRUE /* ignore_retval */);
        break;
    default:
        RpcRaiseException(RPC_S_INTERNAL_ERROR);
        return;
    }
    /* free the full pointer translation tables */
    if (pProcHeader->Oi_flags & RPC_FC_PROC_OIF_FULLPTR)
        NdrFullPointerXlatFree(pEsMsg->StubMsg.FullPtrXlatTables);
}