/*++

Copyright (c) 1997-2000  Microsoft Corporation

Module Name:

    rconvert.c

Abstract:

    Domain Name System (DNS) Server -- Admin Client Library

    RPC record conversion routines.
    Convert records in RPC buffer to DNS_RECORD type.

Author:

    Jim Gilroy (jamesg)     April, 1997

Revision History:

--*/


#include "dnsclip.h"


#define IS_COMPLETE_NODE( pRpcNode )    \
            (!!((pRpcNode)->dwFlags & DNS_RPC_NODE_FLAG_COMPLETE))

//
//  Copy-convert string from RPC format (UTF8) into DNS_RECORD buffer
//      - assume previously allocated required buffer
//
//  Note:  no difference between string and name conversion as we're
//          going FROM UTF8
//

#define COPY_UTF8_STR_TO_BUFFER( buf, psz, len, charSet ) \
        Dns_StringCopy(     \
            (buf),          \
            NULL,           \
            (psz),          \
            (len),          \
            DnsCharSetUtf8, \
            (charSet) )
#if 0
        Dns_StringCopy(     \
            (buf),          \   // result DNS_RECORD buffer
            NULL,           \   // buffer has required length
            (psz),          \   // in UTF8 string
            (len),          \   // string length (if known)
            DnsCharSetUtf8, \   // string is UTF8
            (charSet) )         // converting to this char set
#endif


//
//  RPC record to DNS_RECORD conversion routines
//

PDNS_RECORD
ARpcRecordConvert(
    IN      PDNS_RPC_RECORD pRpcRR,
    IN      DNS_CHARSET     CharSet
    )
/*++

Routine Description:

    Read A record data from packet.

Arguments:

    pRpcRR - message being read

Return Value:

    Ptr to new record if successful.
    NULL on failure.

--*/
{
    PDNS_RECORD precord;

    DNS_ASSERT( pRpcRR->wDataLength == sizeof(IP_ADDRESS) );

    precord = Dns_AllocateRecord( sizeof(IP_ADDRESS) );
    if ( !precord )
    {
        return( NULL );
    }
    precord->Data.A.IpAddress = pRpcRR->Data.A.ipAddress;

    return( precord );
}



PDNS_RECORD
PtrRpcRecordConvert(
    IN      PDNS_RPC_RECORD pRpcRR,
    IN      DNS_CHARSET     CharSet
    )
/*++

Routine Description:

    Process PTR compatible record from wire.
    Includes: NS, PTR, CNAME, MB, MR, MG, MD, MF

Arguments:

    pRpcRR - message being read

    CharSet - character set for resulting record

Return Value:

    Ptr to new record if successful.
    NULL on failure.

--*/
{
    PDNS_RECORD     precord;
    PDNS_RPC_NAME   pname = &pRpcRR->Data.PTR.nameNode;
    WORD            bufLength;

    //
    //  PTR data is another domain name
    //

    if ( ! DNS_IS_NAME_IN_RECORD(pRpcRR, pname) )
    {
        DNS_ASSERT( FALSE );
        return NULL;
    }

    //
    //  determine required buffer length and allocate
    //

    bufLength = sizeof( DNS_PTR_DATA )
                + STR_BUF_SIZE_GIVEN_UTF8_LEN( pname->cchNameLength, CharSet );

    precord = Dns_AllocateRecord( bufLength );
    if ( !precord )
    {
        return( NULL );
    }

    //
    //  write hostname into buffer, immediately following PTR data struct
    //

    precord->Data.PTR.pNameHost = (PCHAR)&precord->Data + sizeof(DNS_PTR_DATA);

    COPY_UTF8_STR_TO_BUFFER(
        precord->Data.PTR.pNameHost,
        pname->achName,
        pname->cchNameLength,
        CharSet
        );

    return( precord );
}



PDNS_RECORD
SoaRpcRecordConvert(
    IN      PDNS_RPC_RECORD pRpcRR,
    IN      DNS_CHARSET     CharSet
    )
/*++

Routine Description:

    Read SOA record from wire.

Arguments:

    pRR - ptr to record with RR set context

    pRpcRR - message being read

    pchData - ptr to RR data field

    pchEnd - ptr to byte after data field

Return Value:

    Ptr to new record if successful.
    NULL on failure.

--*/
{
    PDNS_RECORD     precord;
    WORD            bufLength;
    DWORD           dwLength;
    PDNS_RPC_NAME   pnamePrimary = &pRpcRR->Data.SOA.namePrimaryServer;
    PDNS_RPC_NAME   pnameAdmin;

    //
    //  verify names in SOA record
    //

    if ( ! DNS_IS_NAME_IN_RECORD(pRpcRR, pnamePrimary) )
    {
        DNS_ASSERT( FALSE );
        return NULL;
    }
    pnameAdmin = DNS_GET_NEXT_NAME(pnamePrimary);
    if ( ! DNS_IS_NAME_IN_RECORD(pRpcRR, pnameAdmin) )
    {
        DNS_ASSERT( FALSE );
        return NULL;
    }

    //
    //  determine required buffer length and allocate
    //

    bufLength = sizeof( DNS_SOA_DATA )
                + STR_BUF_SIZE_GIVEN_UTF8_LEN( pnamePrimary->cchNameLength, CharSet )
                + STR_BUF_SIZE_GIVEN_UTF8_LEN( pnameAdmin->cchNameLength, CharSet );

    precord = Dns_AllocateRecord( bufLength );
    if ( !precord )
    {
        return( NULL );
    }

    //
    //  copy fixed fields
    //

    RtlCopyMemory(
        (PCHAR) & precord->Data.SOA.dwSerialNo,
        (PCHAR) & pRpcRR->Data.SOA.dwSerialNo,
        SIZEOF_SOA_FIXED_DATA );

    //
    //  copy names into RR buffer
    //      - primary server immediately follows SOA data struct
    //      - responsible party follows primary server
    //

    precord->Data.SOA.pNamePrimaryServer = (PCHAR)&precord->Data
                                            + sizeof(DNS_SOA_DATA);
    dwLength  =
        COPY_UTF8_STR_TO_BUFFER(
            precord->Data.SOA.pNamePrimaryServer,
            pnamePrimary->achName,
            (DWORD)pnamePrimary->cchNameLength,
            CharSet );
    precord->Data.SOA.pNameAdministrator = precord->Data.SOA.pNamePrimaryServer + dwLength;

    COPY_UTF8_STR_TO_BUFFER(
        precord->Data.SOA.pNameAdministrator,
        pnameAdmin->achName,
        (DWORD)pnameAdmin->cchNameLength,
        CharSet );

    return( precord );
}



PDNS_RECORD
TxtRpcRecordConvert(
    IN      PDNS_RPC_RECORD pRpcRR,
    IN      DNS_CHARSET     CharSet
    )
/*++

Routine Description:

    Read TXT compatible record from wire.
    Includes: TXT, X25, HINFO, ISDN

Arguments:

    pRpcRR - message being read

Return Value:

    Ptr to new record if successful.
    NULL on failure.

--*/
{
    PDNS_RECORD     precord;
    DWORD           bufLength = 0;
    DWORD           length = 0;
    INT             count = 0;
    PCHAR           pch;
    PCHAR           pchend;
    PCHAR           pchbuffer;
    PCHAR *         ppstring;
    PDNS_RPC_NAME   pname = &pRpcRR->Data.TXT.stringData;

    //
    //  determine required buffer length and allocate
    //      - allocate space for each string
    //      - and ptr for each string
    //

    pch = (PCHAR)&pRpcRR->Data.TXT;
    pchend = pch + pRpcRR->wDataLength;

    while ( pch < pchend )
    {
        length = (UCHAR) *pch++;
        pch += length;
        count++;
        bufLength += STR_BUF_SIZE_GIVEN_UTF8_LEN( length, CharSet );
    }
    if ( pch != pchend )
    {
        DNS_PRINT((
            "ERROR:  Invalid RPCstring data.\n"
            "\tpch = %p\n"
            "\tpchEnd = %p\n"
            "\tcount = %d\n"
            "\tlength = %d\n",
            pch, pchend, count, length
            ));
        SetLastError( ERROR_INVALID_DATA );
        return( NULL );
    }

    //  allocate

    bufLength += (WORD) DNS_TEXT_RECORD_LENGTH(count);
    precord = Dns_AllocateRecord( (WORD)bufLength );
    if ( !precord )
    {
        return( NULL );
    }
    precord->Data.TXT.dwStringCount = count;

    //
    //  go back through list copying strings to buffer
    //      - ptrs to strings are saved to argv like data section
    //          ppstring walks through this section
    //      - first string written immediately following data section
    //      - each subsequent string immediately follows predecessor
    //          pchbuffer keeps ptr to position to write strings
    //

    pch = (PCHAR)&pRpcRR->Data.TXT;
    ppstring = precord->Data.TXT.pStringArray;
    pchbuffer = (PCHAR)ppstring + (count * sizeof(PCHAR));

    while ( pch < pchend )
    {
        length = (DWORD)((UCHAR) *pch++);
        *ppstring++ = pchbuffer;

        pchbuffer += COPY_UTF8_STR_TO_BUFFER(
                        pchbuffer,
                        pch,
                        length,
                        CharSet );
        pch += length;
#if DBG
        DNS_PRINT((
            "Read text string %s\n",
            * (ppstring - 1)
            ));
        count--;
#endif
    }
    DNS_ASSERT( pch == pchend && count == 0 );

    return( precord );
}



PDNS_RECORD
MinfoRpcRecordConvert(
    IN      PDNS_RPC_RECORD pRpcRR,
    IN      DNS_CHARSET     CharSet
    )
/*++

Routine Description:

    Read MINFO record from wire.

Arguments:

    pRpcRR - message being read

Return Value:

    Ptr to new record if successful.
    NULL on failure.

--*/
{
    PDNS_RECORD     precord;
    WORD            bufLength;
    DWORD           dwLength;
    PDNS_RPC_NAME   pname1 = &pRpcRR->Data.MINFO.nameMailBox;
    PDNS_RPC_NAME   pname2;

    //
    //  verify names in MINFO record
    //

    if ( ! DNS_IS_NAME_IN_RECORD(pRpcRR, pname1) )
    {
        DNS_ASSERT( FALSE );
        return NULL;
    }
    pname2 = DNS_GET_NEXT_NAME(pname1);
    if ( ! DNS_IS_NAME_IN_RECORD(pRpcRR, pname2) )
    {
        DNS_ASSERT( FALSE );
        return NULL;
    }

    //
    //  determine required buffer length and allocate
    //

    bufLength = (WORD)
                ( sizeof( DNS_MINFO_DATA )
                + STR_BUF_SIZE_GIVEN_UTF8_LEN( pname1->cchNameLength, CharSet )
                + STR_BUF_SIZE_GIVEN_UTF8_LEN( pname2->cchNameLength, CharSet ) );

    precord = Dns_AllocateRecord( bufLength );
    if ( !precord )
    {
        return( NULL );
    }

    //
    //  copy names into RR buffer
    //      - mailbox immediately follows MINFO data struct
    //      - errors mailbox immediately follows primary server
    //

    precord->Data.MINFO.pNameMailbox
                    = (PCHAR)&precord->Data + sizeof( DNS_MINFO_DATA );

    dwLength =
        COPY_UTF8_STR_TO_BUFFER(
            precord->Data.MINFO.pNameMailbox,
            pname1->achName,
            (DWORD)pname1->cchNameLength,
            CharSet );
    precord->Data.MINFO.pNameErrorsMailbox = precord->Data.MINFO.pNameMailbox + dwLength;

    COPY_UTF8_STR_TO_BUFFER(
        precord->Data.MINFO.pNameErrorsMailbox,
        pname2->achName,
        (DWORD)pname2->cchNameLength,
        CharSet );

    return( precord );
}



PDNS_RECORD
MxRpcRecordConvert(
    IN      PDNS_RPC_RECORD pRpcRR,
    IN      DNS_CHARSET     CharSet
    )
/*++

Routine Description:

    Read MX compatible record from wire.
    Includes: MX, RT, AFSDB

Arguments:

    pRpcRR - message being read

Return Value:

    Ptr to new record if successful.
    NULL on failure.

--*/
{
    PDNS_RECORD     precord;
    PDNS_RPC_NAME   pname = &pRpcRR->Data.MX.nameExchange;
    WORD            bufLength;

    //
    //  MX exchange is another DNS name
    //

    if ( ! DNS_IS_NAME_IN_RECORD(pRpcRR, pname) )
    {
        DNS_ASSERT( FALSE );
        return NULL;
    }

    //
    //  determine required buffer length and allocate
    //

    bufLength = sizeof( DNS_MX_DATA )
                + STR_BUF_SIZE_GIVEN_UTF8_LEN( pname->cchNameLength, CharSet );

    precord = Dns_AllocateRecord( bufLength );
    if ( !precord )
    {
        return( NULL );
    }

    //
    //  copy preference
    //

    precord->Data.MX.wPreference = pRpcRR->Data.MX.wPreference;

    //
    //  write hostname into buffer, immediately following MX struct
    //

    precord->Data.MX.pNameExchange = (PCHAR)&precord->Data + sizeof( DNS_MX_DATA );

    COPY_UTF8_STR_TO_BUFFER(
        precord->Data.MX.pNameExchange,
        pname->achName,
        pname->cchNameLength,
        CharSet );

    return( precord );
}



PDNS_RECORD
FlatRpcRecordConvert(
    IN      PDNS_RPC_RECORD pRpcRR,
    IN      DNS_CHARSET     CharSet
    )
/*++

Routine Description:

    Read memory copy compatible record from wire.
    Includes AAAA and WINS types.

Arguments:

    pRpcRR - message being read

Return Value:

    Ptr to new record if successful.
    NULL on failure.

--*/
{
    PDNS_RECORD precord;
    WORD        bufLength;

    //
    //  determine required buffer length and allocate
    //

    bufLength = pRpcRR->wDataLength;

    precord = Dns_AllocateRecord( bufLength );
    if ( !precord )
    {
        return( NULL );
    }

    //
    //  copy packet data to record
    //

    RtlCopyMemory(
        & precord->Data,
        (PCHAR) &pRpcRR->Data.A,
        bufLength );

    return( precord );
}



PDNS_RECORD
SrvRpcRecordConvert(
    IN      PDNS_RPC_RECORD pRpcRR,
    IN      DNS_CHARSET     CharSet
    )
/*++

Routine Description:

    Read SRV record from wire.

Arguments:

    pRpcRR - message being read

Return Value:

    Ptr to new record if successful.
    NULL on failure.

--*/
{
    PDNS_RECORD     precord;
    PDNS_RPC_NAME   pname = &pRpcRR->Data.SRV.nameTarget;
    WORD            bufLength;

    //
    //  SRV target host is another DNS name
    //

    if ( ! DNS_IS_NAME_IN_RECORD(pRpcRR, pname) )
    {
        DNS_ASSERT( FALSE );
        return NULL;
    }

    //
    //  determine required buffer length and allocate
    //

    bufLength = sizeof( DNS_SRV_DATA )
                + STR_BUF_SIZE_GIVEN_UTF8_LEN( pname->cchNameLength, CharSet );

    precord = Dns_AllocateRecord( bufLength );
    if ( !precord )
    {
        return( NULL );
    }

    //
    //  copy SRV fixed fields
    //

    precord->Data.SRV.wPriority = pRpcRR->Data.SRV.wPriority;
    precord->Data.SRV.wWeight = pRpcRR->Data.SRV.wWeight;
    precord->Data.SRV.wPort = pRpcRR->Data.SRV.wPort;

    //
    //  write hostname into buffer, immediately following SRV struct
    //

    precord->Data.SRV.pNameTarget = (PCHAR)&precord->Data + sizeof( DNS_SRV_DATA );

    COPY_UTF8_STR_TO_BUFFER(
        precord->Data.SRV.pNameTarget,
        pname->achName,
        pname->cchNameLength,
        CharSet );

    return( precord );
}



PDNS_RECORD
NbstatRpcRecordConvert(
    IN      PDNS_RPC_RECORD pRpcRR,
    IN      DNS_CHARSET     CharSet
    )
/*++

Routine Description:

    Read WINSR record from wire.

Arguments:

    pRpcRR - message being read

Return Value:

    Ptr to new record if successful.
    NULL on failure.

--*/
{
    PDNS_RECORD     precord;
    PDNS_RPC_NAME   pname = &pRpcRR->Data.WINSR.nameResultDomain;
    WORD            bufLength;

    //
    //  WINSR target host is another DNS name
    //

    if ( ! DNS_IS_NAME_IN_RECORD(pRpcRR, pname) )
    {
        DNS_ASSERT( FALSE );
        return NULL;
    }

    //
    //  determine required buffer length and allocate
    //

    bufLength = sizeof( DNS_WINSR_DATA )
                + STR_BUF_SIZE_GIVEN_UTF8_LEN( pname->cchNameLength, CharSet );

    precord = Dns_AllocateRecord( bufLength );
    if ( !precord )
    {
        return( NULL );
    }

    //
    //  copy WINSR fixed fields
    //

    precord->Data.WINSR.dwMappingFlag = pRpcRR->Data.WINSR.dwMappingFlag;
    precord->Data.WINSR.dwLookupTimeout = pRpcRR->Data.WINSR.dwLookupTimeout;
    precord->Data.WINSR.dwCacheTimeout = pRpcRR->Data.WINSR.dwCacheTimeout;

    //
    //  write hostname into buffer, immediately following WINSR struct
    //

    precord->Data.WINSR.pNameResultDomain
                        = (PCHAR)&precord->Data + sizeof( DNS_WINSR_DATA );
    COPY_UTF8_STR_TO_BUFFER(
        precord->Data.WINSR.pNameResultDomain,
        pname->achName,
        pname->cchNameLength,
        CharSet );

    return( precord );
}



//
//  RR conversion from RPC buffer to DNS_RECORD
//

typedef PDNS_RECORD (* RR_CONVERT_FUNCTION)( PDNS_RPC_RECORD, DNS_CHARSET );

RR_CONVERT_FUNCTION   RRRpcConvertTable[] =
{
    NULL,                       //  ZERO
    ARpcRecordConvert,          //  A
    PtrRpcRecordConvert,        //  NS
    PtrRpcRecordConvert,        //  MD
    PtrRpcRecordConvert,        //  MF
    PtrRpcRecordConvert,        //  CNAME
    SoaRpcRecordConvert,        //  SOA
    PtrRpcRecordConvert,        //  MB
    PtrRpcRecordConvert,        //  MG
    PtrRpcRecordConvert,        //  MR
    NULL,                       //  NULL
    FlatRpcRecordConvert,       //  WKS
    PtrRpcRecordConvert,        //  PTR
    TxtRpcRecordConvert,        //  HINFO
    MinfoRpcRecordConvert,      //  MINFO
    MxRpcRecordConvert,         //  MX
    TxtRpcRecordConvert,        //  TXT
    MinfoRpcRecordConvert,      //  RP
    MxRpcRecordConvert,         //  AFSDB
    TxtRpcRecordConvert,        //  X25
    TxtRpcRecordConvert,        //  ISDN
    MxRpcRecordConvert,         //  RT
    NULL,                       //  NSAP
    NULL,                       //  NSAPPTR
    NULL,                       //  SIG
    NULL,                       //  KEY
    NULL,                       //  PX
    NULL,                       //  GPOS
    FlatRpcRecordConvert,       //  AAAA
    NULL,                       //  29
    NULL,                       //  30
    NULL,                       //  31
    NULL,                       //  32
    SrvRpcRecordConvert,        //  SRV
    NULL,                       //  ATMA
    NULL,                       //  35
    NULL,                       //  36
    NULL,                       //  37
    NULL,                       //  38
    NULL,                       //  39
    NULL,                       //  40
    NULL,                       //  OPT
    NULL,                       //  42
    NULL,                       //  43
    NULL,                       //  44
    NULL,                       //  45
    NULL,                       //  46
    NULL,                       //  47
    NULL,                       //  48

    //
    //  NOTE:  last type indexed by type ID MUST be set
    //         as MAX_SELF_INDEXED_TYPE #define in record.h
    //         (see note above in record info table)

    //  note these follow, but require OFFSET_TO_WINS_RR subtraction
    //  from actual type value

    NULL,                       //  TKEY
    NULL,                       //  TSIG
    FlatRpcRecordConvert,       //  WINS
    NbstatRpcRecordConvert      //  WINS-R
};



//
//  API for doing conversion
//

PDNS_RECORD
DnsConvertRpcBufferToRecords(
    IN      PBYTE *         ppByte,
    IN      PBYTE           pStopByte,
    IN      DWORD           cRecords,
    IN      PDNS_NAME       pszNodeName,
    IN      BOOLEAN         fUnicode
    )
/*++

Routine Description:

    Convert RPC buffer records to standard DNS records.

Arguments:

    ppByte -- addr of ptr into buffer where records start

    pStopByte -- stop byte of buffer

    cRecords -- number of records to convert

    pszNodeName -- node name (in desired format, not converted)

    fUnicode -- flag, write records into unicode

Return Value:

    Ptr to new record(s) if successful.
    NULL on failure.

--*/
{
    PDNS_RPC_RECORD prpcRecord = (PDNS_RPC_RECORD)*ppByte;
    PDNS_RECORD     precord;
    DNS_RRSET       rrset;
    WORD            index;
    WORD            type;
    DNS_CHARSET     charSet;
    RR_CONVERT_FUNCTION pFunc;

    DNS_ASSERT( DNS_IS_DWORD_ALIGNED(prpcRecord) );
    IF_DNSDBG( RPC2 )
    {
        DNS_PRINT((
            "Enter DnsConvertRpcBufferToRecords()\n"
            "\tpRpcRecord   = %p\n"
            "\tCount        = %d\n"
            "\tNodename     = %s%S\n",
            prpcRecord,
            cRecords,
            DNSSTRING_UTF8( fUnicode, pszNodeName ),
            DNSSTRING_WIDE( fUnicode, pszNodeName ) ));
    }

    DNS_RRSET_INIT( rrset );

    //
    //  loop until out of nodes
    //

    while( cRecords-- )
    {
        if ( (PBYTE)prpcRecord >= pStopByte ||
            (PBYTE)&prpcRecord->Data + prpcRecord->wDataLength > pStopByte )
        {
            DNS_PRINT((
                "ERROR:  Bogus buffer at %p\n"
                "\tRecord leads past buffer end at %p\n"
                "\twith %d records remaining.\n",
                prpcRecord,
                pStopByte,
                cRecords+1 ));
            DNS_ASSERT( FALSE );
            return NULL;
        }

        //
        //  convert record
        //      set unicode flag if converting
        //

        charSet = DnsCharSetUtf8;
        if ( fUnicode )
        {
            charSet = DnsCharSetUnicode;
        }

        type = prpcRecord->wType;
        index = INDEX_FOR_TYPE( type );
        DNS_ASSERT( index <= MAX_RECORD_TYPE_INDEX );

        if ( !index || !(pFunc = RRRpcConvertTable[ index ]) )
        {
            //  if unknown type try flat record copy -- best we can
            //  do to protect if server added new types since admin built

            DNS_PRINT((
                "ERROR:  no RPC to DNS_RECORD conversion routine for type %d.\n"
                "\tusing flat conversion routine.\n",
                type ));
            pFunc = FlatRpcRecordConvert;
        }

        precord = (*pFunc)( prpcRecord, charSet );
        if ( ! precord )
        {
            DNS_PRINT((
                "ERROR:  Record build routine failure for record type %d.\n"
                "\tstatus = %p\n\n",
                type,
                GetLastError() ));

            prpcRecord = DNS_GET_NEXT_RPC_RECORD(prpcRecord);
            continue;
        }

        //
        //  fill out record structure
        //

        precord->pName = pszNodeName;
        precord->wType = type;
        RECORD_CHARSET( precord ) = charSet;

        //
        //  DEVNOTE: data types (root hint, glue set)
        //      - need way to default that works for NT4
        //      JJW: this is probably an obsolete B*GB*G
        //

        if ( prpcRecord->dwFlags & DNS_RPC_RECORD_FLAG_CACHE_DATA )
        {
            precord->Flags.S.Section = DNSREC_CACHE_DATA;
        }
        else
        {
            precord->Flags.S.Section = DNSREC_ZONE_DATA;
        }

        IF_DNSDBG( INIT )
        {
            DnsDbg_Record(
                "New record built\n",
                precord );
        }

        //
        //  link into RR set
        //

        DNS_RRSET_ADD( rrset, precord );

        prpcRecord = DNS_GET_NEXT_RPC_RECORD(prpcRecord);
    }

    IF_DNSDBG( RPC2 )
    {
        DnsDbg_RecordSet(
            "Finished DnsConvertRpcBufferToRecords() ",
            rrset.pFirstRR );
    }

    //  reset ptr in buffer

    *ppByte = (PBYTE) prpcRecord;

    return( rrset.pFirstRR );
}



PDNS_NODE
DnsConvertRpcBufferNode(
    IN      PDNS_RPC_NODE   pRpcNode,
    IN      PBYTE           pStopByte,
    IN      BOOLEAN         fUnicode
    )
/*++

Routine Description:

    Convert RPC buffer records to standard DNS records.

Arguments:

    pRpcNode -- ptr to RPC node in buffer

    pStopByte -- stop byte of buffer

    fUnicode -- flag, write records into unicode

Return Value:

    Ptr to new node if successful.
    NULL on failure.

--*/
{
    PDNS_NODE       pnode;
    PDNS_RPC_NAME   pname;
    PBYTE           pendNode;


    IF_DNSDBG( RPC2 )
    {
        DnsDbg_RpcNode(
            "Enter DnsConvertRpcBufferNode() ",
            pRpcNode );
    }

    //
    //  validate node
    //

    DNS_ASSERT( DNS_IS_DWORD_ALIGNED(pRpcNode) );
    pendNode = (PBYTE)pRpcNode + pRpcNode->wLength;
    if ( pendNode > pStopByte )
    {
        DNS_ASSERT( FALSE );
        return( NULL );
    }
    pname = &pRpcNode->dnsNodeName;
    if ( (PBYTE)DNS_GET_NEXT_NAME(pname) > pendNode )
    {
        DNS_ASSERT( FALSE );
        return( NULL );
    }

    //
    //  create node
    //

    pnode = (PDNS_NODE) ALLOCATE_HEAP( sizeof(DNS_NODE)
                    + STR_BUF_SIZE_GIVEN_UTF8_LEN( pname->cchNameLength, fUnicode ) );
    if ( !pnode )
    {
        return( NULL );
    }
    pnode->pNext = NULL;
    pnode->pRecord = NULL;
    pnode->Flags.W = 0;

    //
    //  copy owner name, starts directly after node structure
    //

    pnode->pName = (PWCHAR) ((PBYTE)pnode + sizeof(DNS_NODE));

    if ( ! Dns_StringCopy(
                (PCHAR) pnode->pName,
                NULL,
                pname->achName,
                pname->cchNameLength,
                DnsCharSetUtf8,         // UTF8 in
                fUnicode ? DnsCharSetUnicode : DnsCharSetUtf8
                ) )
    {
        //  name conversion error
        DNS_ASSERT( FALSE );
        FREE_HEAP( pnode );
        return( NULL );
    }
    IF_DNSDBG( RPC2 )
    {
        DnsDbg_RpcName(
            "Node name in RPC buffer: ",
            pname,
            "\n" );
        DnsDbg_String(
            "Converted name ",
            (PCHAR) pnode->pName,
            fUnicode,
            "\n" );
    }

    //
    //  set flags
    //      - name always internal
    //      - catch domain roots
    //

    pnode->Flags.S.Unicode = fUnicode;

    if ( pRpcNode->dwChildCount ||
        (pRpcNode->dwFlags & DNS_RPC_NODE_FLAG_STICKY) )
    {
        pnode->Flags.S.Domain = TRUE;
    }

    IF_DNSDBG( RPC2 )
    {
        DnsDbg_Node(
            "Finished DnsConvertRpcBufferNode() ",
            pnode,
            TRUE        // view the records
            );
    }
    return( pnode );
}



PDNS_NODE
DnsConvertRpcBuffer(
    OUT     PDNS_NODE *     ppNodeLast,
    IN      DWORD           dwBufferLength,
    IN      BYTE            abBuffer[],
    IN      BOOLEAN         fUnicode
    )
{
    PBYTE       pbyte;
    PBYTE       pstopByte;
    INT         countRecords;
    PDNS_NODE   pnode;
    PDNS_NODE   pnodeFirst = NULL;
    PDNS_NODE   pnodeLast = NULL;
    PDNS_RECORD precord;

    IF_DNSDBG( RPC2 )
    {
        DNS_PRINT((
            "DnsConvertRpcBuffer( %p ), len = %d\n",
            abBuffer,
            dwBufferLength ));
    }

    //
    //  find stop byte
    //

    DNS_ASSERT( DNS_IS_DWORD_ALIGNED(abBuffer) );

    pstopByte = abBuffer + dwBufferLength;
    pbyte = abBuffer;

    //
    //  loop until out of nodes
    //

    while( pbyte < pstopByte )
    {
        //
        //  build owner node
        //      - only build complete nodes
        //      - add to list
        //

        if ( !IS_COMPLETE_NODE( (PDNS_RPC_NODE)pbyte ) )
        {
            break;
        }
        pnode = DnsConvertRpcBufferNode(
                    (PDNS_RPC_NODE)pbyte,
                    pstopByte,
                    fUnicode );
        if ( !pnode )
        {
            DNS_ASSERT( FALSE );
            //  DEVNOTE: cleanup
            return( NULL );
        }
        if ( !pnodeFirst )
        {
            pnodeFirst = pnode;
            pnodeLast = pnode;
        }
        else
        {
            pnodeLast->pNext = pnode;
            pnodeLast = pnode;
        }

        countRecords = ((PDNS_RPC_NODE)pbyte)->wRecordCount;
        pbyte += ((PDNS_RPC_NODE)pbyte)->wLength;
        pbyte = DNS_NEXT_DWORD_PTR(pbyte);

        //
        //  for each node, build all records
        //

        if ( countRecords )
        {
            precord = DnsConvertRpcBufferToRecords(
                            & pbyte,
                            pstopByte,
                            countRecords,
                            (PCHAR) pnode->pName,
                            fUnicode );
            if ( !precord )
            {
                DNS_ASSERT( FALSE );
            }
            pnode->pRecord = precord;
        }
    }

    //  set last node and return first node

    *ppNodeLast = pnodeLast;

    return( pnodeFirst );
}

//
//  End rconvert.c
//