/** @file
 * PDM - Networking Helpers, Inlined Code. (DEV,++)
 *
 * This is all inlined because it's too tedious to create 2-3 libraries to
 * contain it all (same bad excuse as for intnetinline.h).
 */

/*
 * Copyright (C) 2010-2015 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 *
 * The contents of this file may alternatively be used under the terms
 * of the Common Development and Distribution License Version 1.0
 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
 * VirtualBox OSE distribution, in which case the provisions of the
 * CDDL are applicable instead of those of the GPL.
 *
 * You may elect to license modified versions of this file under the
 * terms and conditions of either the GPL or the CDDL or both.
 */


/*******************************************************************************
*   Header Files                                                               *
*******************************************************************************/
#include <VBox/log.h>
#include <VBox/types.h>
#include <iprt/asm.h>
#include <iprt/assert.h>
#include <iprt/net.h>
#include <iprt/string.h>



/** @defgroup grp_pdm_net_inline    The PDM Networking Helper APIs
 * @ingroup grp_pdm
 * @{
 */


/**
 * Checksum type.
 */
typedef enum PDMNETCSUMTYPE
{
    /** No checksum. */
    PDMNETCSUMTYPE_NONE = 0,
    /** Normal TCP checksum. */
    PDMNETCSUMTYPE_COMPLETE,
    /** Checksum on pseudo header (used with GSO). */
    PDMNETCSUMTYPE_PSEUDO,
    /** The usual 32-bit hack. */
    PDMNETCSUMTYPE_32_BIT_HACK = 0x7fffffff
} PDMNETCSUMTYPE;


/**
 * Validates the GSO context.
 *
 * @returns true if valid, false if not (not asserted or logged).
 * @param   pGso                The GSO context.
 * @param   cbGsoMax            The max size of the GSO context.
 * @param   cbFrame             The max size of the GSO frame (use to validate
 *                              the MSS).
 */
DECLINLINE(bool) PDMNetGsoIsValid(PCPDMNETWORKGSO pGso, size_t cbGsoMax, size_t cbFrame)
{
    PDMNETWORKGSOTYPE enmType;

    if (RT_LIKELY(cbGsoMax >= sizeof(*pGso)))
    { /* likely */ } else return false;

    enmType = (PDMNETWORKGSOTYPE)pGso->u8Type;
    if (RT_LIKELY( enmType > PDMNETWORKGSOTYPE_INVALID && enmType < PDMNETWORKGSOTYPE_END ))
    { /* likely */ } else return false;

    /* all types requires both headers. */
    if (RT_LIKELY( pGso->offHdr1 >= sizeof(RTNETETHERHDR) ))
    { /* likely */ } else return false;
    if (RT_LIKELY( pGso->offHdr2 > pGso->offHdr1 ))
    { /* likely */ } else return false;
    if (RT_LIKELY( pGso->cbHdrsTotal > pGso->offHdr2 ))
    { /* likely */ } else return false;

    /* min size of the 1st header(s). */
    switch (enmType)
    {
        case PDMNETWORKGSOTYPE_IPV4_TCP:
        case PDMNETWORKGSOTYPE_IPV4_UDP:
            if (RT_LIKELY( (unsigned)pGso->offHdr2 - pGso->offHdr1 >= RTNETIPV4_MIN_LEN ))
            { /* likely */ } else return false;
            break;
        case PDMNETWORKGSOTYPE_IPV6_TCP:
        case PDMNETWORKGSOTYPE_IPV6_UDP:
            if (RT_LIKELY( (unsigned)pGso->offHdr2 - pGso->offHdr1 >= RTNETIPV6_MIN_LEN ))
            { /* likely */ } else return false;
            break;
        case PDMNETWORKGSOTYPE_IPV4_IPV6_TCP:
        case PDMNETWORKGSOTYPE_IPV4_IPV6_UDP:
            if (RT_LIKELY( (unsigned)pGso->offHdr2 - pGso->offHdr1 >= RTNETIPV4_MIN_LEN + RTNETIPV6_MIN_LEN ))
            { /* likely */ } else return false;
            break;
        case PDMNETWORKGSOTYPE_INVALID:
        case PDMNETWORKGSOTYPE_END:
            break;
        /* no default case! want gcc warnings. */
    }

    /* min size of the 2nd header. */
    switch (enmType)
    {
        case PDMNETWORKGSOTYPE_IPV4_TCP:
        case PDMNETWORKGSOTYPE_IPV6_TCP:
        case PDMNETWORKGSOTYPE_IPV4_IPV6_TCP:
            if (RT_LIKELY( (unsigned)pGso->cbHdrsTotal - pGso->offHdr2 >= RTNETTCP_MIN_LEN ))
            { /* likely */ } else return false;
            break;
        case PDMNETWORKGSOTYPE_IPV4_UDP:
        case PDMNETWORKGSOTYPE_IPV6_UDP:
        case PDMNETWORKGSOTYPE_IPV4_IPV6_UDP:
            if (RT_LIKELY( (unsigned)pGso->cbHdrsTotal - pGso->offHdr2 >= RTNETUDP_MIN_LEN ))
            { /* likely */ } else return false;
            break;
        case PDMNETWORKGSOTYPE_INVALID:
        case PDMNETWORKGSOTYPE_END:
            break;
        /* no default case! want gcc warnings. */
    }

    /* There must be at more than one segment. */
    if (RT_LIKELY( cbFrame > pGso->cbHdrsTotal ))
    { /* likely */ } else return false;
    if (RT_LIKELY( cbFrame - pGso->cbHdrsTotal >= pGso->cbMaxSeg ))
    { /* likely */ } else return false;

    return true;
}


/**
 * Returns the length of header for a particular segment/fragment.
 *
 * We cannot simply treat UDP header as a part of payload because we do not
 * want to modify the payload but still need to modify the checksum field in
 * UDP header. So we want to include UDP header when calculating the length
 * of headers in the first segment getting it copied to a temporary buffer
 * along with other headers.
 *
 * @returns Length of headers (including UDP header for the first fragment).
 * @param   pGso                The GSO context.
 * @param   iSeg                The segment index.
 */
DECLINLINE(uint8_t) pdmNetSegHdrLen(PCPDMNETWORKGSO pGso, uint32_t iSeg)
{
    return iSeg ? pGso->cbHdrsSeg : pGso->cbHdrsTotal;
}

/**
 * Returns the length of payload for a particular segment/fragment.
 *
 * The first segment does not contain UDP header. The size of UDP header is
 * determined as the difference between the total headers size and the size
 * used during segmentation.
 *
 * @returns Length of payload (including UDP header for the first fragment).
 * @param   pGso                The GSO context.
 * @param   iSeg                The segment that we're carving out (0-based).
 * @param   cSegs               The number of segments in the GSO frame.
 * @param   cbFrame             The size of the GSO frame.
 */
DECLINLINE(uint32_t) pdmNetSegPayloadLen(PCPDMNETWORKGSO pGso, uint32_t iSeg, uint32_t cSegs, uint32_t cbFrame)
{
    if (iSeg + 1 == cSegs)
        return cbFrame - iSeg * pGso->cbMaxSeg - pdmNetSegHdrLen(pGso, iSeg);
    else
        return pGso->cbMaxSeg - (iSeg ? 0 : pGso->cbHdrsTotal - pGso->cbHdrsSeg);
}

/**
 * Calculates the number of segments a GSO frame will be segmented into.
 *
 * @returns Segment count.
 * @param   pGso                The GSO context.
 * @param   cbFrame             The GSO frame size (header proto + payload).
 */
DECLINLINE(uint32_t) PDMNetGsoCalcSegmentCount(PCPDMNETWORKGSO pGso, size_t cbFrame)
{
    size_t cbPayload;
    Assert(PDMNetGsoIsValid(pGso, sizeof(*pGso), cbFrame));
    cbPayload = cbFrame - pGso->cbHdrsSeg;
    return (uint32_t)((cbPayload + pGso->cbMaxSeg - 1) / pGso->cbMaxSeg);
}


/**
 * Used to find the IPv6 header when handling 4to6 tunneling.
 *
 * @returns Offset of the IPv6 header.
 * @param   pbSegHdrs           The headers / frame start.
 * @param   offIPv4Hdr          The offset of the IPv4 header.
 */
DECLINLINE(uint8_t) pgmNetGsoCalcIpv6Offset(uint8_t *pbSegHdrs, uint8_t offIPv4Hdr)
{
    PCRTNETIPV4 pIPv4Hdr = (PCRTNETIPV4)&pbSegHdrs[offIPv4Hdr];
    return offIPv4Hdr + pIPv4Hdr->ip_hl * 4;
}


/**
 * Update an UDP header after carving out a segment
 *
 * @param   u32PseudoSum        The pseudo checksum.
 * @param   pbSegHdrs           Pointer to the header bytes / frame start.
 * @param   offUdpHdr           The offset into @a pbSegHdrs of the UDP header.
 * @param   pbPayload           Pointer to the payload bytes.
 * @param   cbPayload           The amount of payload.
 * @param   cbHdrs              The size of all the headers.
 * @param   enmCsumType         Whether to checksum the payload, the pseudo
 *                              header or nothing.
 * @internal
 */
DECLINLINE(void) pdmNetGsoUpdateUdpHdr(uint32_t u32PseudoSum, uint8_t *pbSegHdrs, uint8_t offUdpHdr,
                                       uint8_t const *pbPayload, uint32_t cbPayload, uint8_t cbHdrs,
                                       PDMNETCSUMTYPE enmCsumType)
{
    PRTNETUDP pUdpHdr = (PRTNETUDP)&pbSegHdrs[offUdpHdr];
    pUdpHdr->uh_ulen  = RT_H2N_U16(cbPayload + cbHdrs - offUdpHdr);
    switch (enmCsumType)
    {
        case PDMNETCSUMTYPE_NONE:
            pUdpHdr->uh_sum = 0;
            break;
        case PDMNETCSUMTYPE_COMPLETE:
            pUdpHdr->uh_sum = RTNetUDPChecksum(u32PseudoSum, pUdpHdr);
            break;
        case PDMNETCSUMTYPE_PSEUDO:
            pUdpHdr->uh_sum = ~RTNetIPv4FinalizeChecksum(u32PseudoSum);
            break;
        default:
            NOREF(pbPayload);
            AssertFailed();
            break;
    }
}


/**
 * Update an UDP header after carving out an IP fragment
 *
 * @param   u32PseudoSum        The pseudo checksum.
 * @param   pbSegHdrs           Pointer to the header bytes copy
 * @param   pbFrame             Pointer to the frame start.
 * @param   offUdpHdr           The offset into @a pbSegHdrs of the UDP header.
 *
 * @internal
 */
DECLINLINE(void) pdmNetGsoUpdateUdpHdrUfo(uint32_t u32PseudoSum, uint8_t *pbSegHdrs, const uint8_t *pbFrame, uint8_t offUdpHdr)
{
    PCRTNETUDP pcUdpHdrOrig = (PCRTNETUDP)&pbFrame[offUdpHdr];
    PRTNETUDP  pUdpHdr = (PRTNETUDP)&pbSegHdrs[offUdpHdr];
    pUdpHdr->uh_sum = RTNetUDPChecksum(u32PseudoSum, pcUdpHdrOrig);
}


/**
 * Update a TCP header after carving out a segment.
 *
 * @param   u32PseudoSum        The pseudo checksum.
 * @param   pbSegHdrs           Pointer to the header bytes / frame start.
 * @param   offTcpHdr           The offset into @a pbSegHdrs of the TCP header.
 * @param   pbPayload           Pointer to the payload bytes.
 * @param   cbPayload           The amount of payload.
 * @param   offPayload          The offset into the payload that we're splitting
 *                              up.  We're ASSUMING that the payload follows
 *                              immediately after the TCP header w/ options.
 * @param   cbHdrs              The size of all the headers.
 * @param   fLastSeg            Set if this is the last segment.
 * @param   enmCsumType         Whether to checksum the payload, the pseudo
 *                              header or nothing.
 * @internal
 */
DECLINLINE(void) pdmNetGsoUpdateTcpHdr(uint32_t u32PseudoSum, uint8_t *pbSegHdrs, uint8_t offTcpHdr,
                                       uint8_t const *pbPayload, uint32_t cbPayload, uint32_t offPayload, uint8_t cbHdrs,
                                       bool fLastSeg, PDMNETCSUMTYPE enmCsumType)
{
    PRTNETTCP pTcpHdr = (PRTNETTCP)&pbSegHdrs[offTcpHdr];
    pTcpHdr->th_seq = RT_H2N_U32(RT_N2H_U32(pTcpHdr->th_seq) + offPayload);
    if (!fLastSeg)
        pTcpHdr->th_flags &= ~(RTNETTCP_F_FIN | RTNETTCP_F_PSH);
    switch (enmCsumType)
    {
        case PDMNETCSUMTYPE_NONE:
            pTcpHdr->th_sum = 0;
            break;
        case PDMNETCSUMTYPE_COMPLETE:
            pTcpHdr->th_sum = RTNetTCPChecksum(u32PseudoSum, pTcpHdr, pbPayload, cbPayload);
            break;
        case PDMNETCSUMTYPE_PSEUDO:
            pTcpHdr->th_sum = ~RTNetIPv4FinalizeChecksum(u32PseudoSum);
            break;
        default:
            NOREF(cbHdrs);
            AssertFailed();
            break;
    }
}


/**
 * Updates a IPv6 header after carving out a segment.
 *
 * @returns 32-bit intermediary checksum value for the pseudo header.
 * @param   pbSegHdrs           Pointer to the header bytes.
 * @param   offIpHdr            The offset into @a pbSegHdrs of the IP header.
 * @param   cbSegPayload        The amount of segmented payload.  Not to be
 *                              confused with the IP payload.
 * @param   cbHdrs              The size of all the headers.
 * @param   offPktHdr           Offset of the protocol packet header.  For the
 *                              pseudo header checksum calulation.
 * @param   bProtocol           The protocol type.  For the pseudo header.
 * @internal
 */
DECLINLINE(uint32_t) pdmNetGsoUpdateIPv6Hdr(uint8_t *pbSegHdrs, uint8_t offIpHdr, uint32_t cbSegPayload, uint8_t cbHdrs,
                                            uint8_t offPktHdr, uint8_t bProtocol)
{
    PRTNETIPV6 pIpHdr  = (PRTNETIPV6)&pbSegHdrs[offIpHdr];
    uint16_t cbPayload = (uint16_t)(cbHdrs - (offIpHdr + sizeof(RTNETIPV6)) + cbSegPayload);
    pIpHdr->ip6_plen   = RT_H2N_U16(cbPayload);
    return RTNetIPv6PseudoChecksumEx(pIpHdr, bProtocol, (uint16_t)(cbHdrs - offPktHdr + cbSegPayload));
}


/**
 * Updates a IPv4 header after carving out a segment.
 *
 * @returns 32-bit intermediary checksum value for the pseudo header.
 * @param   pbSegHdrs           Pointer to the header bytes.
 * @param   offIpHdr            The offset into @a pbSegHdrs of the IP header.
 * @param   cbSegPayload        The amount of segmented payload.
 * @param   iSeg                The segment index.
 * @param   cbHdrs              The size of all the headers.
 * @internal
 */
DECLINLINE(uint32_t) pdmNetGsoUpdateIPv4Hdr(uint8_t *pbSegHdrs, uint8_t offIpHdr, uint32_t cbSegPayload,
                                            uint32_t iSeg, uint8_t cbHdrs)
{
    PRTNETIPV4 pIpHdr = (PRTNETIPV4)&pbSegHdrs[offIpHdr];
    pIpHdr->ip_len    = RT_H2N_U16(cbHdrs - offIpHdr + cbSegPayload);
    pIpHdr->ip_id     = RT_H2N_U16(RT_N2H_U16(pIpHdr->ip_id) + iSeg);
    pIpHdr->ip_sum    = RTNetIPv4HdrChecksum(pIpHdr);
    return RTNetIPv4PseudoChecksum(pIpHdr);
}


/**
 * Updates a IPv4 header after carving out an IP fragment.
 *
 * @param   pbSegHdrs           Pointer to the header bytes.
 * @param   offIpHdr            The offset into @a pbSegHdrs of the IP header.
 * @param   cbSegPayload        The amount of segmented payload.
 * @param   offFragment         The offset of this fragment for reassembly.
 * @param   cbHdrs              The size of all the headers.
 * @param   fLastFragment       True if this is the last fragment of datagram.
 * @internal
 */
DECLINLINE(void) pdmNetGsoUpdateIPv4HdrUfo(uint8_t *pbSegHdrs, uint8_t offIpHdr, uint32_t cbSegPayload,
                                           uint32_t offFragment, uint8_t cbHdrs, bool fLastFragment)
{
    PRTNETIPV4 pIpHdr = (PRTNETIPV4)&pbSegHdrs[offIpHdr];
    pIpHdr->ip_len    = RT_H2N_U16(cbHdrs - offIpHdr + cbSegPayload);
    pIpHdr->ip_off    = RT_H2N_U16((offFragment / 8) | (fLastFragment ? 0 : RTNETIPV4_FLAGS_MF));
    pIpHdr->ip_sum    = RTNetIPv4HdrChecksum(pIpHdr);
}


/**
 * Carves out the specified segment in a destructive manner.
 *
 * This is for sequentially carving out segments and pushing them along for
 * processing or sending.  To avoid allocating a temporary buffer for
 * constructing the segment in, we trash the previous frame by putting the
 * header at the end of it.
 *
 * @returns Pointer to the segment frame that we've carved out.
 * @param   pGso                The GSO context data.
 * @param   pbFrame             Pointer to the GSO frame.
 * @param   cbFrame             The size of the GSO frame.
 * @param   pbHdrScatch         Pointer to a pGso->cbHdrs sized area where we
 *                              can save the original header prototypes on the
 *                              first call (@a iSeg is 0) and retrieve it on
 *                              susequent calls. (Just use a 256 bytes
 *                              buffer to make life easy.)
 * @param   iSeg                The segment that we're carving out (0-based).
 * @param   cSegs               The number of segments in the GSO frame.  Use
 *                              PDMNetGsoCalcSegmentCount to find this.
 * @param   pcbSegFrame         Where to return the size of the returned segment
 *                              frame.
 */
DECLINLINE(void *) PDMNetGsoCarveSegmentQD(PCPDMNETWORKGSO pGso, uint8_t *pbFrame, size_t cbFrame, uint8_t *pbHdrScatch,
                                           uint32_t iSeg, uint32_t cSegs, uint32_t *pcbSegFrame)
{
    /*
     * Figure out where the payload is and where the header starts before we
     * do the protocol specific carving.
     */
    uint8_t * const pbSegHdrs    = pbFrame + pGso->cbMaxSeg * iSeg;
    uint8_t * const pbSegPayload = pbSegHdrs + pGso->cbHdrsSeg;
    uint32_t const  cbSegPayload = pdmNetSegPayloadLen(pGso, iSeg, cSegs, (uint32_t)cbFrame);
    uint32_t const  cbSegFrame   = cbSegPayload + pGso->cbHdrsSeg;

    /*
     * Check assumptions (doing it after declaring the variables because of C).
     */
    Assert(iSeg < cSegs);
    Assert(cSegs == PDMNetGsoCalcSegmentCount(pGso, cbFrame));
    Assert(PDMNetGsoIsValid(pGso, sizeof(*pGso), cbFrame));

    /*
     * Copy the header and do the protocol specific massaging of it.
     */
    if (iSeg != 0)
        memcpy(pbSegHdrs, pbHdrScatch, pGso->cbHdrsSeg);
    else
        memcpy(pbHdrScatch, pbSegHdrs, pGso->cbHdrsSeg); /* There is no need to save UDP header */

    switch ((PDMNETWORKGSOTYPE)pGso->u8Type)
    {
        case PDMNETWORKGSOTYPE_IPV4_TCP:
            pdmNetGsoUpdateTcpHdr(pdmNetGsoUpdateIPv4Hdr(pbSegHdrs, pGso->offHdr1, cbSegPayload, iSeg, pGso->cbHdrsSeg),
                                  pbSegHdrs, pGso->offHdr2, pbSegPayload, cbSegPayload, iSeg * pGso->cbMaxSeg,
                                  pGso->cbHdrsSeg, iSeg + 1 == cSegs, PDMNETCSUMTYPE_COMPLETE);
            break;
        case PDMNETWORKGSOTYPE_IPV4_UDP:
            if (iSeg == 0)
                pdmNetGsoUpdateUdpHdrUfo(RTNetIPv4PseudoChecksum((PRTNETIPV4)&pbFrame[pGso->offHdr1]),
                                         pbSegHdrs, pbFrame, pGso->offHdr2);
            pdmNetGsoUpdateIPv4HdrUfo(pbSegHdrs, pGso->offHdr1, cbSegPayload, iSeg * pGso->cbMaxSeg,
                                      pdmNetSegHdrLen(pGso, iSeg), iSeg + 1 == cSegs);
            break;
        case PDMNETWORKGSOTYPE_IPV6_TCP:
            pdmNetGsoUpdateTcpHdr(pdmNetGsoUpdateIPv6Hdr(pbSegHdrs, pGso->offHdr1, cbSegPayload, pGso->cbHdrsSeg,
                                                         pGso->offHdr2, RTNETIPV4_PROT_TCP),
                                  pbSegHdrs, pGso->offHdr2, pbSegPayload, cbSegPayload, iSeg * pGso->cbMaxSeg,
                                  pGso->cbHdrsSeg, iSeg + 1 == cSegs, PDMNETCSUMTYPE_COMPLETE);
            break;
        case PDMNETWORKGSOTYPE_IPV6_UDP:
            pdmNetGsoUpdateUdpHdr(pdmNetGsoUpdateIPv6Hdr(pbSegHdrs, pGso->offHdr1, cbSegPayload, pGso->cbHdrsSeg,
                                                         pGso->offHdr2, RTNETIPV4_PROT_UDP),
                                  pbSegHdrs, pGso->offHdr2, pbSegPayload, cbSegPayload, pGso->cbHdrsSeg, PDMNETCSUMTYPE_COMPLETE);
            break;
        case PDMNETWORKGSOTYPE_IPV4_IPV6_TCP:
            pdmNetGsoUpdateIPv4Hdr(pbSegHdrs, pGso->offHdr1, cbSegPayload, iSeg, pGso->cbHdrsSeg);
            pdmNetGsoUpdateTcpHdr(pdmNetGsoUpdateIPv6Hdr(pbSegHdrs, pgmNetGsoCalcIpv6Offset(pbSegHdrs, pGso->offHdr1),
                                                         cbSegPayload, pGso->cbHdrsSeg, pGso->offHdr2, RTNETIPV4_PROT_TCP),
                                  pbSegHdrs, pGso->offHdr2, pbSegPayload, cbSegPayload, iSeg * pGso->cbMaxSeg,
                                  pGso->cbHdrsSeg, iSeg + 1 == cSegs, PDMNETCSUMTYPE_COMPLETE);
            break;
        case PDMNETWORKGSOTYPE_IPV4_IPV6_UDP:
            pdmNetGsoUpdateIPv4Hdr(pbSegHdrs, pGso->offHdr1, cbSegPayload, iSeg, pGso->cbHdrsSeg);
            pdmNetGsoUpdateUdpHdr(pdmNetGsoUpdateIPv6Hdr(pbSegHdrs, pgmNetGsoCalcIpv6Offset(pbSegHdrs, pGso->offHdr1),
                                                         cbSegPayload, pGso->cbHdrsSeg, pGso->offHdr2, RTNETIPV4_PROT_UDP),
                                  pbSegHdrs, pGso->offHdr2, pbSegPayload, cbSegPayload, pGso->cbHdrsSeg, PDMNETCSUMTYPE_COMPLETE);
            break;
        case PDMNETWORKGSOTYPE_INVALID:
        case PDMNETWORKGSOTYPE_END:
            /* no default! wnat gcc warnings. */
            break;
    }

    *pcbSegFrame = cbSegFrame;
    return pbSegHdrs;
}


/**
 * Carves out the specified segment in a non-destructive manner.
 *
 * The segment headers and segment payload is kept separate here.  The GSO frame
 * is still expected to be one linear chunk of data, but we don't modify any of
 * it.
 *
 * @returns The offset into the GSO frame of the payload.
 * @param   pGso                The GSO context data.
 * @param   pbFrame             Pointer to the GSO frame.  Used for retrieving
 *                              the header prototype and for checksumming the
 *                              payload.  The buffer is not modified.
 * @param   cbFrame             The size of the GSO frame.
 * @param   iSeg                The segment that we're carving out (0-based).
 * @param   cSegs               The number of segments in the GSO frame.  Use
 *                              PDMNetGsoCalcSegmentCount to find this.
 * @param   pbSegHdrs           Where to return the headers for the segment
 *                              that's been carved out.  The buffer must be at
 *                              least pGso->cbHdrs in size, using a 256 byte
 *                              buffer is a recommended simplification.
 * @param   pcbSegHdrs          Where to return the size of the returned
 *                              segment headers.
 * @param   pcbSegPayload       Where to return the size of the returned
 *                              segment payload.
 */
DECLINLINE(uint32_t) PDMNetGsoCarveSegment(PCPDMNETWORKGSO pGso, const uint8_t *pbFrame, size_t cbFrame,
                                           uint32_t iSeg, uint32_t cSegs, uint8_t *pbSegHdrs,
                                           uint32_t *pcbSegHdrs, uint32_t *pcbSegPayload)
{
    /*
     * Figure out where the payload is and where the header starts before we
     * do the protocol specific carving.
     */
    uint32_t const        cbSegHdrs    = pdmNetSegHdrLen(pGso, iSeg);
    uint8_t const * const pbSegPayload = pbFrame + cbSegHdrs + iSeg * pGso->cbMaxSeg;
    uint32_t const        cbSegPayload = pdmNetSegPayloadLen(pGso, iSeg, cSegs, (uint32_t)cbFrame);

    /*
     * Check assumptions (doing it after declaring the variables because of C).
     */
    Assert(iSeg < cSegs);
    Assert(cSegs == PDMNetGsoCalcSegmentCount(pGso, cbFrame));
    Assert(PDMNetGsoIsValid(pGso, sizeof(*pGso), cbFrame));

    /*
     * Copy the header and do the protocol specific massaging of it.
     */
    memcpy(pbSegHdrs, pbFrame, pGso->cbHdrsTotal); /* include UDP header */

    switch ((PDMNETWORKGSOTYPE)pGso->u8Type)
    {
        case PDMNETWORKGSOTYPE_IPV4_TCP:
            pdmNetGsoUpdateTcpHdr(pdmNetGsoUpdateIPv4Hdr(pbSegHdrs, pGso->offHdr1, cbSegPayload, iSeg, cbSegHdrs),
                                  pbSegHdrs, pGso->offHdr2, pbSegPayload, cbSegPayload, iSeg * pGso->cbMaxSeg,
                                  cbSegHdrs, iSeg + 1 == cSegs, PDMNETCSUMTYPE_COMPLETE);
            break;
        case PDMNETWORKGSOTYPE_IPV4_UDP:
            if (iSeg == 0)
                pdmNetGsoUpdateUdpHdrUfo(RTNetIPv4PseudoChecksum((PRTNETIPV4)&pbFrame[pGso->offHdr1]),
                                         pbSegHdrs, pbFrame, pGso->offHdr2);
            pdmNetGsoUpdateIPv4HdrUfo(pbSegHdrs, pGso->offHdr1, cbSegPayload, iSeg * pGso->cbMaxSeg,
                                      cbSegHdrs, iSeg + 1 == cSegs);
            break;
        case PDMNETWORKGSOTYPE_IPV6_TCP:
            pdmNetGsoUpdateTcpHdr(pdmNetGsoUpdateIPv6Hdr(pbSegHdrs, pGso->offHdr1, cbSegPayload, cbSegHdrs,
                                                         pGso->offHdr2, RTNETIPV4_PROT_TCP),
                                  pbSegHdrs, pGso->offHdr2, pbSegPayload, cbSegPayload, iSeg * pGso->cbMaxSeg,
                                  cbSegHdrs, iSeg + 1 == cSegs, PDMNETCSUMTYPE_COMPLETE);
            break;
        case PDMNETWORKGSOTYPE_IPV6_UDP:
            pdmNetGsoUpdateUdpHdr(pdmNetGsoUpdateIPv6Hdr(pbSegHdrs, pGso->offHdr1, cbSegPayload, cbSegHdrs,
                                                         pGso->offHdr2, RTNETIPV4_PROT_UDP),
                                  pbSegHdrs, pGso->offHdr2, pbSegPayload, cbSegPayload, cbSegHdrs, PDMNETCSUMTYPE_COMPLETE);
            break;
        case PDMNETWORKGSOTYPE_IPV4_IPV6_TCP:
            pdmNetGsoUpdateIPv4Hdr(pbSegHdrs, pGso->offHdr1, cbSegPayload, iSeg, cbSegHdrs);
            pdmNetGsoUpdateTcpHdr(pdmNetGsoUpdateIPv6Hdr(pbSegHdrs, pgmNetGsoCalcIpv6Offset(pbSegHdrs, pGso->offHdr1),
                                                         cbSegPayload, cbSegHdrs, pGso->offHdr2, RTNETIPV4_PROT_TCP),
                                  pbSegHdrs, pGso->offHdr2, pbSegPayload, cbSegPayload, iSeg * pGso->cbMaxSeg,
                                  cbSegHdrs, iSeg + 1 == cSegs, PDMNETCSUMTYPE_COMPLETE);
            break;
        case PDMNETWORKGSOTYPE_IPV4_IPV6_UDP:
            pdmNetGsoUpdateIPv4Hdr(pbSegHdrs, pGso->offHdr1, cbSegPayload, iSeg, cbSegHdrs);
            pdmNetGsoUpdateUdpHdr(pdmNetGsoUpdateIPv6Hdr(pbSegHdrs, pgmNetGsoCalcIpv6Offset(pbSegHdrs, pGso->offHdr1),
                                                         cbSegPayload, cbSegHdrs, pGso->offHdr2, RTNETIPV4_PROT_UDP),
                                  pbSegHdrs, pGso->offHdr2, pbSegPayload, cbSegPayload, cbSegHdrs, PDMNETCSUMTYPE_COMPLETE);
            break;
        case PDMNETWORKGSOTYPE_INVALID:
        case PDMNETWORKGSOTYPE_END:
            /* no default! wnat gcc warnings. */
            break;
    }

    *pcbSegHdrs    = cbSegHdrs;
    *pcbSegPayload = cbSegPayload;
    return cbSegHdrs + iSeg * pGso->cbMaxSeg;
}


/**
 * Prepares the GSO frame for direct use without any segmenting.
 *
 * @param   pGso                The GSO context.
 * @param   pvFrame             The frame to prepare.
 * @param   cbFrame             The frame size.
 * @param   enmCsumType         Whether to checksum the payload, the pseudo
 *                              header or nothing.
 */
DECLINLINE(void) PDMNetGsoPrepForDirectUse(PCPDMNETWORKGSO pGso, void *pvFrame, size_t cbFrame, PDMNETCSUMTYPE enmCsumType)
{
    /*
     * Figure out where the payload is and where the header starts before we
     * do the protocol bits.
     */
    uint8_t * const pbHdrs    = (uint8_t *)pvFrame;
    uint8_t * const pbPayload = pbHdrs  + pGso->cbHdrsTotal;
    uint32_t  const cbFrame32 = (uint32_t)cbFrame;
    uint32_t  const cbPayload = cbFrame32 - pGso->cbHdrsTotal;

    /*
     * Check assumptions (doing it after declaring the variables because of C).
     */
    Assert(PDMNetGsoIsValid(pGso, sizeof(*pGso), cbFrame));

    /*
     * Get down to busienss.
     */
    switch ((PDMNETWORKGSOTYPE)pGso->u8Type)
    {
        case PDMNETWORKGSOTYPE_IPV4_TCP:
            pdmNetGsoUpdateTcpHdr(pdmNetGsoUpdateIPv4Hdr(pbHdrs, pGso->offHdr1, cbFrame32 - pGso->cbHdrsTotal, 0, pGso->cbHdrsTotal),
                                  pbHdrs, pGso->offHdr2, pbPayload, cbPayload, 0, pGso->cbHdrsTotal, true, enmCsumType);
            break;
        case PDMNETWORKGSOTYPE_IPV4_UDP:
            pdmNetGsoUpdateUdpHdr(pdmNetGsoUpdateIPv4Hdr(pbHdrs, pGso->offHdr1, cbFrame32 - pGso->cbHdrsTotal, 0, pGso->cbHdrsTotal),
                                  pbHdrs, pGso->offHdr2, pbPayload, cbPayload, pGso->cbHdrsTotal, enmCsumType);
            break;
        case PDMNETWORKGSOTYPE_IPV6_TCP:
            pdmNetGsoUpdateTcpHdr(pdmNetGsoUpdateIPv6Hdr(pbHdrs, pGso->offHdr1, cbPayload, pGso->cbHdrsTotal,
                                                         pGso->offHdr2, RTNETIPV4_PROT_TCP),
                                  pbHdrs, pGso->offHdr2, pbPayload, cbPayload, 0, pGso->cbHdrsTotal, true, enmCsumType);
            break;
        case PDMNETWORKGSOTYPE_IPV6_UDP:
            pdmNetGsoUpdateUdpHdr(pdmNetGsoUpdateIPv6Hdr(pbHdrs, pGso->offHdr1, cbPayload, pGso->cbHdrsTotal,
                                                         pGso->offHdr2, RTNETIPV4_PROT_UDP),
                                  pbHdrs, pGso->offHdr2, pbPayload, cbPayload, pGso->cbHdrsTotal, enmCsumType);
            break;
        case PDMNETWORKGSOTYPE_IPV4_IPV6_TCP:
            pdmNetGsoUpdateIPv4Hdr(pbHdrs, pGso->offHdr1, cbPayload, 0, pGso->cbHdrsTotal);
            pdmNetGsoUpdateTcpHdr(pdmNetGsoUpdateIPv6Hdr(pbHdrs, pgmNetGsoCalcIpv6Offset(pbHdrs, pGso->offHdr1),
                                                         cbPayload, pGso->cbHdrsTotal, pGso->offHdr2, RTNETIPV4_PROT_TCP),
                                  pbHdrs, pGso->offHdr2, pbPayload, cbPayload, 0, pGso->cbHdrsTotal, true, enmCsumType);
            break;
        case PDMNETWORKGSOTYPE_IPV4_IPV6_UDP:
            pdmNetGsoUpdateIPv4Hdr(pbHdrs, pGso->offHdr1, cbPayload, 0, pGso->cbHdrsTotal);
            pdmNetGsoUpdateUdpHdr(pdmNetGsoUpdateIPv6Hdr(pbHdrs, pgmNetGsoCalcIpv6Offset(pbHdrs, pGso->offHdr1),
                                                         cbPayload, pGso->cbHdrsTotal, pGso->offHdr2, RTNETIPV4_PROT_UDP),
                                  pbHdrs, pGso->offHdr2, pbPayload, cbPayload, pGso->cbHdrsTotal, enmCsumType);
            break;
        case PDMNETWORKGSOTYPE_INVALID:
        case PDMNETWORKGSOTYPE_END:
            /* no default! wnat gcc warnings. */
            break;
    }
}


/**
 * Gets the GSO type name string.
 *
 * @returns Pointer to read only name string.
 * @param   enmType             The type.
 */
DECLINLINE(const char *) PDMNetGsoTypeName(PDMNETWORKGSOTYPE enmType)
{
    switch (enmType)
    {
        case PDMNETWORKGSOTYPE_IPV4_TCP:        return "TCPv4";
        case PDMNETWORKGSOTYPE_IPV6_TCP:        return "TCPv6";
        case PDMNETWORKGSOTYPE_IPV4_UDP:        return "UDPv4";
        case PDMNETWORKGSOTYPE_IPV6_UDP:        return "UDPv6";
        case PDMNETWORKGSOTYPE_IPV4_IPV6_TCP:   return "4to6TCP";
        case PDMNETWORKGSOTYPE_IPV4_IPV6_UDP:   return "4to6UDP";
        case PDMNETWORKGSOTYPE_INVALID:         return "invalid";
        case PDMNETWORKGSOTYPE_END:             return "end";
    }
    return "bad-gso-type";
}

/** @} */

