/*****************************************************************************
 * Copyright (C) Gemalto M2M GmbH
 *****************************************************************************
 * Project: IMC-RIL-Driver
 *****************************************************************************
 * Author : Wireless Modules RD SW A2
 *****************************************************************************
 * $Workfile: Y:/z_ms-d/src/working/Quinn/RilDrv/smsenc.cpp $
 * $Revision: /main/ms-d/8 $
 *     $Date: 2012-10-24 10:53:28 $
 *   $Author: pmackent $
 *****************************************************************************
 * Target:
 *  Microsoft Windows Mobile 5.0
 *****************************************************************************
 * Description:
 *  Gemalto IMC RIL
 *****************************************************************************
 *
 * $Log: Y:/z_ms-d/src/working/Quinn/RilDrv/smsenc.cpp$ 
 * 
 * /main/ms-d/8   2012-10-24 10:53:28   pmackent 
 * Filter out format characters in CDMA address
 *
 * /main/ms-d/7   2012-06-14 14:29:10   pmackent
 * Fix problem with 16-bit CDMA-Unicode coding
 *
 * /main/ms-d/6   2012-04-19 14:42:37   ctna0256
 * Small CDMA adaptions
 *
 * /main/ms-d/5   2012-04-18 13:46:32   ctna0256
 * Introducing PVS8
 * Implementing CDMA-SMS
 * Various CDMA enhancements
 *
 * /main/ms-d/4   2011-09-26 13:35:20   ctna0256
 * Comply with CE6 warning level 4
 *
 * /main/ms-d/3   2008-08-22 10:27:58   ctna0256
 * Set Cinterion copyright text
 *
 * /main/ms-d/2   2008-08-21 09:35:35   ctna0256
 * Prepared for new disclaimer handling
 *
 * /main/ms-d/1   2006-10-25 16:52:14   wm36264
 * First transformation from classic RIL
 *
*/

//////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2008 Gemalto M2M GmbH
// Copyright (C) 2006-2008 Siemens AG
// Copyright (C) 2003 Intrinsyc Europe Ltd.
//
// Version: 1.0
// Date:    dummy
//
//////////////////////////////////////////////////////////////////////////////

/*++
THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
Copyright (c) 1995-1999  Microsoft Corporation
--*/

#include "precomp.h"

// ID for bearer ryply option (CDMA only)
static INT g_iCdmaReplyID = 0;


//
// Convert a RILMSGDCS struct into a TP-DCS BYTE for Submit messages
// see GSM 03.38
//
static HRESULT EncodeMsgDCS(const RILMSGDCS& rmdDCS, BYTE* const pbOut, UINT& rcbUsed)
{
    HRESULT hr = S_OK;

    rcbUsed = 0;

    if (!(rmdDCS.dwParams & RIL_PARAM_MDCS_TYPE)) {
        hr = E_INVALIDARG;
        goto Error;
    }
    *pbOut = 0x00;

    switch (rmdDCS.dwType)
    {
        case RIL_DCSTYPE_GENERAL:
            if ((rmdDCS.dwParams & RIL_PARAM_MDCS_FLAGS) &&
                (rmdDCS.dwFlags & RIL_DCSFLAG_COMPRESSED)) {
                *pbOut |= 0x20;
            }

            if (rmdDCS.dwParams & RIL_PARAM_MDCS_MSGCLASS) {
                switch (rmdDCS.dwMsgClass)
                {
                    case RIL_DCSMSGCLASS_0:
                        *pbOut |= 0x10;
                        break;

                    case RIL_DCSMSGCLASS_1:
                        *pbOut |= 0x11;
                        break;

                    case RIL_DCSMSGCLASS_2:
                        *pbOut |= 0x12;
                        break;

                    case RIL_DCSMSGCLASS_3:
                        *pbOut |= 0x13;
                        break;

                    default:
                        hr = E_INVALIDARG;
                        goto Error;
                }
            }

            if (rmdDCS.dwParams & RIL_PARAM_MDCS_ALPHABET) {
                switch (rmdDCS.dwAlphabet)
                {
                    case RIL_DCSALPHABET_DEFAULT:
                        *pbOut |= 0x00;
                        break;

                    case RIL_DCSALPHABET_8BIT:
                        *pbOut |= 0x04;
                        break;

                    case RIL_DCSALPHABET_UCS2:
                        *pbOut |= 0x08;
                        break;

                    default:
                        hr = E_INVALIDARG;
                        goto Error;
                }
            } else {
                hr = E_INVALIDARG;
                goto Error;
            }
            break;

        case RIL_DCSTYPE_MSGWAIT:
            if (!(rmdDCS.dwParams & RIL_PARAM_MDCS_FLAGS)) {
                hr = E_INVALIDARG;
                goto Error;
            }

            if (rmdDCS.dwFlags & RIL_DCSFLAG_DISCARD) {
                *pbOut |= 0xc0;
            } else if (rmdDCS.dwParams & RIL_PARAM_MDCS_ALPHABET) {
                switch (rmdDCS.dwAlphabet)
                {
                    case RIL_DCSALPHABET_DEFAULT:
                        *pbOut |= 0xd0;
                        break;

                    case RIL_DCSALPHABET_UCS2:
                        *pbOut |= 0xe0;
                        break;

                    default:
                        hr = E_INVALIDARG;
                        goto Error;
                }
            } else {
                hr = E_INVALIDARG;
                goto Error;
            }

            if (rmdDCS.dwParams & RIL_PARAM_MDCS_INDICATION &&
                rmdDCS.dwFlags & RIL_DCSFLAG_INDICATIONACTIVE) {
                switch (rmdDCS.dwIndication)
                {
                    case RIL_DCSINDICATION_VOICEMAIL:
                        *pbOut |= 0x08;
                        break;

                    case RIL_DCSINDICATION_FAX:
                        *pbOut |= 0x09;
                        break;

                    case RIL_DCSINDICATION_EMAIL:
                        *pbOut |= 0x0a;
                        break;

                    default:
                        hr = E_INVALIDARG;
                        goto Error;
                }
            }
            break;

        case RIL_DCSTYPE_MSGCLASS:
            // This type is currently not implemented
            hr = E_INVALIDARG;
            goto Error;

        default:
            hr = E_FAIL;
            goto Error;
    }
    rcbUsed = 1;

Error:
    return hr;
}


#pragma warning( push )
// Warning disabled to keep code align with reference RIL.
#pragma warning(disable : 4244)
//
// Encode the Service Center Time Stamp in a SYSTEMTIME struct
// see 9.2.3.11
//
static void EncodeMsgTimeStamp(const SYSTEMTIME& stTimeStamp, BYTE* const pbOut, UINT& rcbUsed)
{
    BYTE* pbWalk = pbOut;

    rcbUsed = 0;

    *pbWalk++ = ((stTimeStamp.wYear % 10) << 4)   | ((stTimeStamp.wYear / 10) % 10);
    *pbWalk++ = ((stTimeStamp.wMonth % 10) << 4)  | (stTimeStamp.wMonth / 10);
    *pbWalk++ = ((stTimeStamp.wDay % 10) << 4)    | (stTimeStamp.wDay / 10);
    *pbWalk++ = ((stTimeStamp.wHour % 10) << 4)   | (stTimeStamp.wHour / 10);
    *pbWalk++ = ((stTimeStamp.wMinute % 10) << 4) | (stTimeStamp.wMinute / 10);
    *pbWalk++ = ((stTimeStamp.wSecond % 10) << 4) | (stTimeStamp.wSecond / 10);
    *pbWalk++ = 0;      // Set time zone to UTC

    rcbUsed = pbWalk - pbOut;
}
#pragma warning( pop )

//
//
//
// GSM 3.40, Section 9.2.3.12.1
//
// TP-VP Value  Validity period value                       Range
// ----------------------------------
//  0   - 143   (TP-VP + 1) x 5 minutes                     5 mins - 720 mins (.5 day)
//  144 - 167   12 hours + ((TP-VP - 143) x 30 minutes)     750 mins - 1440 mins (1 day)
//  168 - 196   (TP-VP - 166) x 1 day                       2 days -  30 days (43200 mins)
//  197 - 255   (TP-VP - 192) x 1 week                      35 days - 441 days (635040 mins)
//
//
// This function encodes a SYSTEMTIME structure into a TP-VP Value according to the chart above.
// Only a subset of the SYSTEMTIME structure is used: wDay, wHour, wMinute. These are the only
// units that have a consistent conversion, and values greater than 441 days are not used, so
// these members are sufficient for expressing any supported validity period.
//
// If the expressed system time does not fall exactly onto a validity period increment, then
// the period is rounded up to the next increment.
//
static void EncodeRelativeValidityPeriod(const SYSTEMTIME& stVP, BYTE* const pbOut, UINT& rcbUsed)
{
    UINT uMinutes = 0;

    uMinutes = stVP.wMinute + 60 * (stVP.wHour + 24 * stVP.wDay);

    if (0 == uMinutes)
    {
        *pbOut = 0;
    }
    else if (uMinutes <= 720)
    {
        *pbOut = (BYTE)((uMinutes+4) / 5) - 1;
    }
    else if (uMinutes <= 1440)
    {
        *pbOut = (BYTE)((uMinutes+29) / 30) + 119;
    }
    else if (uMinutes <= 43200)
    {
        *pbOut = (BYTE)((uMinutes+1439) / 1440) + 166;
    }
    else if (uMinutes <= 635040)
    {
        *pbOut = (BYTE)((uMinutes+10079) / 10080) + 192;
    }
    else
    {
        *pbOut = 255;
    }

    rcbUsed = 1;
}

//
// Convert a VP time stamp to BYTE data
// see GSM 03.40 section 9.2.3.12
//
static HRESULT EncodeMsgValidityPeriod(const SYSTEMTIME& stVP, const DWORD dwVPFormat, BYTE* const pbOut, UINT& rcbUsed)
{
    HRESULT hr = S_OK;

    rcbUsed = 0;

    if (RIL_MSGVP_ABSOLUTE == dwVPFormat) {
        EncodeMsgTimeStamp(stVP, pbOut, rcbUsed);
    } else {
        switch (dwVPFormat)
        {
            case RIL_MSGVP_NONE:
                // Do nothing
                break;

            case RIL_MSGVP_ENHANCED:
                // Enhanced isn't supported. We should never get here,
                // but just in case, treat the SYSTEMTIME as relative.

            case RIL_MSGVP_RELATIVE:
                EncodeRelativeValidityPeriod(stVP, pbOut, rcbUsed);
                break;

            default:
                hr = E_INVALIDARG;
                goto Error;
        }
    }

Error:
    return hr;
}

#pragma warning( push )
// Warning disabled to keep code align with reference RIL.
#pragma warning(disable : 4244)
//
// Function to encode an address from an RILADDRESS struct
// see GSM 03.40 section 9.1.2.5
//
static HRESULT EncodeMsgAddress(const RILADDRESS& raAddress, const BOOL fBeforePDU, BYTE* const pbOut, UINT& rcbUsed)
{
    HRESULT hr = S_OK;
    UINT j;
    UINT cchAddressLen;
    BYTE bTypeOfAddress;
    UINT nTemp;
    WCHAR wszRILAddress[MAXLENGTH_ADDRESS + 1];
    LPWSTR wszRILAddressWalk;
    char szAddress[MAXLENGTH_ADDRESS + 1];
    WCHAR wszBuffer[2];
    BYTE* pbWalk = pbOut;

    rcbUsed = 0;

    if (!(raAddress.dwParams & RIL_PARAM_A_ADDRESS) ||
        !(raAddress.dwParams & RIL_PARAM_A_TYPE)) {
        // Address and type must be specified
        hr = E_INVALIDARG;
        goto Error;
    }

    if (NUM_ADDRTYPES <= raAddress.dwType) {
        // Incorrect type specified
        hr = E_INVALIDARG;
        goto Error;
    }

    // Set address type, top bit is always set
    bTypeOfAddress = 0x80 | (g_rgbAddrTypes[raAddress.dwType] << 4);

    // Set numplan type, if necessary
    if (RIL_ADDRTYPE_UNKNOWN == raAddress.dwType ||
        RIL_ADDRTYPE_INTERNATIONAL == raAddress.dwType ||
        RIL_ADDRTYPE_NATIONAL == raAddress.dwType) {

        if (!(raAddress.dwParams & RIL_PARAM_A_NUMPLAN) ||
            NUM_NUMPLANS <= raAddress.dwNumPlan) {
            hr = E_INVALIDARG;
            goto Error;
        }

        bTypeOfAddress |= g_rgbNumPlans[raAddress.dwNumPlan];
    }

    // Copy the address, removing all unwanted characters (Ex: '(', '-', '.')
    // Note that '+' is automatically removed from INTERNATIONAL addresses
    wszRILAddressWalk = wszRILAddress;
    for(j = 0 ; j < ARRAY_LENGTH(raAddress.wszAddress) ; j++) {
        if(strchr("1234567890*#ABCD", raAddress.wszAddress[j])) {
            *wszRILAddressWalk++ = raAddress.wszAddress[j];
        } else if(L'\0' == raAddress.wszAddress[j]) {
            break;  // Done
        }
    }
    *wszRILAddressWalk = L'\0';

    // Note: Untested for ALPHANUM addresses
    cchAddressLen = wcslen(wszRILAddress);
    if (fBeforePDU) {
        *pbWalk++ = ((cchAddressLen + 1)/ 2) + 1;
    } else {
        *pbWalk++ = cchAddressLen;
    }

    *pbWalk++ = bTypeOfAddress;

    if (RIL_ADDRTYPE_ALPHANUM == raAddress.dwType) {
        hr = RILAddressToString(raAddress, szAddress, MAXLENGTH_ADDRESS + 1, bTypeOfAddress);
        if (FAILED(hr)) {
            goto Error;
        }
        memcpy(pbWalk, szAddress, strlen(szAddress));
    } else {
        wszBuffer[1] = L'\0';  // Not changed below
        for (j = 0; j < cchAddressLen; j += 2) {
            wszBuffer[0] = wszRILAddress[j];
            nTemp = _wtoi(wszBuffer);
            *pbWalk = nTemp;

            if (j + 1 < cchAddressLen) {
                wszBuffer[0] = wszRILAddress[j + 1];
                nTemp = _wtoi(wszBuffer);
                *pbWalk++ |= nTemp << 4;
            } else {
                *pbWalk++ |= 0xf0;
            }
        }
    }
    rcbUsed = pbWalk - pbOut;

Error:
    return hr;
}
#pragma warning( pop )


//
// Encode the ProtocolID for an outgoing message
// see GSM 03.40 section 9.2.3.9
//
static HRESULT EncodeMsgProtocolID(const DWORD dwProtocolID, BYTE* const pbOut, UINT& rcbUsed)
{
    HRESULT hr = S_OK;

    rcbUsed = 0;

    if (dwProtocolID >= NUM_PROTOCOLIDS) {
        hr = E_INVALIDARG;
        goto Error;
    }

    if (dwProtocolID == RIL_MSGPROTOCOL_SMETOSME) {
        *pbOut = 0x00;
    } else {
        *pbOut = (BYTE)g_rgdwProtocolIDs[dwProtocolID];
    }
    rcbUsed = 1;

Error:
    return hr;
}

#pragma warning( push )
// Warning disabled to keep code align with reference RIL.
#pragma warning(disable : 4244)
//
// calculate the total length of the user data in an outgoing message
// if fGSMDefault is trure, then the length is measured in septets, otherwise in octets
//
static void EncodeEncDataLength(const DWORD cchMsgLength, const DWORD cbHdrLength, const DWORD dwAlphabet, BYTE* const pbOut,
                                UINT& rcbUsed)
{
    UINT cchUserData;

    rcbUsed = 0;

    if (!cchMsgLength && !cbHdrLength) {
        // Neither header nor body are specified
        cchUserData = 0;
    } else {
        if (RIL_DCSALPHABET_UCS2 == dwAlphabet) {
            // Account for the fact that every UCS2 char takes up 2 bytes
            cchUserData = cchMsgLength * 2;
        } else {
            cchUserData = cchMsgLength;
        }

        if (cbHdrLength) {
            // Add the size of the header, and a byte for the header-length indicator, plus any fill-bits
            //    (for GSM default alpabet)

            if (RIL_DCSALPHABET_DEFAULT == dwAlphabet) {
                cchUserData += (DIVIDE_ROUNDING_UP((1 + cbHdrLength) * 8, 7));
            } else {
                cchUserData += (1 + cbHdrLength);
            }
        }
    }

    *pbOut = cchUserData;
    rcbUsed = 1;
}


//
// Fill out a BYTE array for the header of an outgoing message
//
static void EncodeMsgHdr(const BYTE* const pbIn, const cbHdrLength, const DWORD /*dwAlphabet*/, BYTE* const pbOut, UINT& rcbUsed)
{
    BYTE* pbWalk = pbOut;

    rcbUsed = 0;

    if (cbHdrLength) {
        *pbWalk++ = cbHdrLength;

        memcpy(pbWalk, pbIn, min(cbHdrLength, MAXLENGTH_ADDRESS));
        pbWalk += cbHdrLength;
    }
    rcbUsed = pbWalk - pbOut;
}
#pragma warning( pop )

//
//
//
static HRESULT EncodeMsgDlvStatus(const DWORD dwStatus, BYTE* const pbOut, UINT& rcbUsed)
{
    HRESULT hr = S_OK;

    rcbUsed = 0;

    if (NUM_DLVSTATUS <= dwStatus) {
        hr = E_INVALIDARG;
        goto Error;
    }

    *pbOut = (BYTE) g_rgdwDlvStatus[dwStatus];
    rcbUsed = 1;

Error:
    return hr;
}

#pragma warning( push )
// Warning disabled to keep code align with reference RIL.
#pragma warning(disable : 4244)
static void ShiftPackedBufferNBitsLeft(BYTE *pbyBuffer, DWORD cBufferBytes, DWORD cShiftRightBits)
{
    if (0 != cBufferBytes)
    {
        BYTE *pbyCurrentByte = pbyBuffer + cBufferBytes - 1;
        while (pbyCurrentByte > pbyBuffer )
        {
            *pbyCurrentByte-- = ((*pbyCurrentByte << cShiftRightBits) |
                                 (*(pbyCurrentByte - 1) >> (8 - cShiftRightBits)));
        }
        *pbyCurrentByte = (*pbyCurrentByte << cShiftRightBits);
    }
}
#pragma warning( pop )

//
//
//
static void EncodeMsgHeaderAndBody(const DWORD dwParams, const DWORD dwFlags, const DWORD cchMsgLength, const DWORD cbHdrLength,
                                   const BYTE* const pbHdr, const BYTE* const pbMsg, const RILMSGDCS* const prmdDataCoding,
                                   BYTE* const pbOut, UINT& rcbUsed)
{
    BYTE* pbWalk = pbOut;
    DWORD dwAlphabet;
    UINT cbMsgBytes;
    UINT cbAdvancedBy;
    DWORD dwBitsToShift = 0;
//  DWORD i;

    rcbUsed = 0;

    if (!(dwFlags & RIL_MSGFLAG_HEADER) && !cchMsgLength) {
        // There is no header or data for this message, so set the data length to zero,
        //    and skip the rest of the function
        *pbWalk++ = 0;
    } else {
        if (prmdDataCoding && (prmdDataCoding->dwParams & RIL_PARAM_MDCS_ALPHABET)) {
            dwAlphabet = prmdDataCoding->dwAlphabet;
        } else {
            // Default to 8-bit alphabet
            dwAlphabet = RIL_DCSALPHABET_8BIT;
        }

        // Calculate the length of the user data in the message
        EncodeEncDataLength(cchMsgLength, (dwFlags & RIL_MSGFLAG_HEADER) ? cbHdrLength : 0, dwAlphabet, pbWalk, cbAdvancedBy);
        pbWalk += cbAdvancedBy;

        // Set the header, if it exists
        if ((dwParams & RIL_PARAM_M_HDRLENGTH) && (dwParams & RIL_PARAM_M_HDR)) {
            // Encode message header
            EncodeMsgHdr(pbHdr, cbHdrLength, dwAlphabet, pbWalk, cbAdvancedBy);
            pbWalk += cbAdvancedBy;
            if (RIL_DCSALPHABET_DEFAULT == dwAlphabet) {
                dwBitsToShift = 7-((cbAdvancedBy * 8) % 7);
                if (dwBitsToShift == 7) dwBitsToShift = 0;
            }
        }

        // Set the message data
        if (cchMsgLength) {
            // Figure out the number of bytes the body would occupy
            if (RIL_DCSALPHABET_DEFAULT == dwAlphabet) {
                cbMsgBytes = (cchMsgLength * 7 + 7) / 8;
            } else if (RIL_DCSALPHABET_UCS2 == dwAlphabet) {
                cbMsgBytes = cchMsgLength * 2;
            } else {
                cbMsgBytes = cchMsgLength;
            }

            cbAdvancedBy = min(cbMsgBytes, MAXLENGTH_MSG);
            memcpy(pbWalk, pbMsg, cbAdvancedBy);


            // make sure any GSM characters start on the septet boundry, not on the byte boundry.
            if (dwBitsToShift) {
                if (((cchMsgLength * 7 + 7 + dwBitsToShift) / 8) > cbMsgBytes)
                {
                    ASSERT(cbAdvancedBy < MAXLENGTH_MSG);
                    if(cbAdvancedBy < MAXLENGTH_MSG)
                    {
                        // our shift will overflow into the next byte
                        pbWalk[cbAdvancedBy++] = 0;
                    }
                }
                ShiftPackedBufferNBitsLeft(pbWalk, cbAdvancedBy, dwBitsToShift);
            }


            pbWalk += cbAdvancedBy;
        }
        else
        {
            // If there is no message body then we may need to walk one more byte to make it to snag
            // the next septet boundry.
            if (dwBitsToShift)
            {
                pbWalk++;
            }
        }
    }
    rcbUsed = pbWalk - pbOut;
}


//
// Fill out a BYTE array for a Submit message
//
static HRESULT EncodeSubmitMsg(const RILMESSAGE& rmMsg, BYTE* const pbOut, const UINT cbOut, UINT& rcbUsed)
{
    BYTE* pbWalk = pbOut;
    UINT cbAdvancedBy;
    HRESULT hr = S_OK;

    (void)memset(pbOut, 0, cbOut);

    // These parameters are mandatory, and if not present, we error out
    if (!(rmMsg.dwParams & RIL_PARAM_M_TYPE) || !(rmMsg.dwParams & RIL_PARAM_M_DESTADDRESS) ||
        !(rmMsg.dwParams & RIL_PARAM_M_PROTOCOLID) || !(rmMsg.dwParams & RIL_PARAM_M_MSGLENGTH) ||
        !(rmMsg.dwParams & RIL_PARAM_M_FLAGS) || !(rmMsg.dwParams & RIL_PARAM_M_VPFORMAT) ||
        !(rmMsg.dwParams & RIL_PARAM_M_DATACODING)) {
        hr = E_INVALIDARG;
        goto Error;
    }

    // Set Type, per GSM 03.40 section 9.2.3.1
    *pbWalk |= 0x01;

    // RIL_MSGFLAGS_REJECTDUPS, GSM 03.40 section 9.2.3.25
    if (rmMsg.dwFlags & RIL_MSGFLAG_REJECTDUPS) {
        *pbWalk |= 0x04;
    }

    // Validity Period Format, GSM 03.40 section 9.2.3.3
    switch (rmMsg.msgOutSubmit.dwVPFormat)
    {
        case RIL_MSGVP_NONE:
            *pbWalk |= 0x00;
            break;

        case RIL_MSGVP_ENHANCED:
            //*pbWalk |= 0x08;
            ASSERT(FALSE);      // not supported, treat SYSTEMTIME as relative
            // fall through...

        case RIL_MSGVP_RELATIVE:
            *pbWalk |= 0x10;
            break;

        case RIL_MSGVP_ABSOLUTE:
            *pbWalk |= 0x18;
            break;

        default:
            hr = E_INVALIDARG;
            goto Error;
    }

    // RIL_MSGFLAG_STATUSREPORTREQUESTED, GSM 03.40 section 9.2.3.5
    if (rmMsg.dwFlags & RIL_MSGFLAG_STATUSREPORTREQUESTED) {
        *pbWalk |= 0x20;
    }

    // RIL_MSGFLAG_HEADER, GSM 03.40 section 9.2.3.23
    if (rmMsg.dwFlags & RIL_MSGFLAG_HEADER) {
        *pbWalk |= 0x40;
    }

    // RIL_MSGFLAG_REPLYPATH, GSM 03.40 section 9.2.3.17
    if (rmMsg.dwFlags & RIL_MSGFLAG_REPLYPATH) {
        *pbWalk |= 0x80;
    }

    pbWalk++;

    // RIL_PARAM_M_MSGREFERENCE, GSM 03.40 section 9.2.3.6
    // NOTE: it appears that we can put anything into this field -- the radio generates correct msg reference for us
    pbWalk++;

    // Encode the destination address.  pbWalk should be set properly by the function
    hr = EncodeMsgAddress(rmMsg.msgOutSubmit.raDestAddress, FALSE, pbWalk, cbAdvancedBy);
    if (FAILED(hr)) {
        goto Error;
    }
    pbWalk += cbAdvancedBy;

    // Set ProtocolID, see GSM 03.40 section 9.2.3.9
    hr = EncodeMsgProtocolID(rmMsg.msgOutSubmit.dwProtocolID, pbWalk, cbAdvancedBy);
    if (FAILED(hr)) {
        goto Error;
    }
    pbWalk += cbAdvancedBy;

    // Reconstruct DCS, see GSM 03.40 section 9.2.3.10
    hr = EncodeMsgDCS(rmMsg.msgOutSubmit.rmdDataCoding, pbWalk, cbAdvancedBy);
    if (FAILED(hr)) {
        goto Error;
    }
    pbWalk += cbAdvancedBy;

    if (rmMsg.dwParams & RIL_PARAM_M_VP) {

        // See GSM 03.40 section 9.2.3.12
        hr = EncodeMsgValidityPeriod(rmMsg.msgOutSubmit.stVP, rmMsg.msgOutSubmit.dwVPFormat, pbWalk, cbAdvancedBy);
        if (FAILED(hr)) {
            goto Error;
        }
        pbWalk += cbAdvancedBy;
    }

    // Encode header and body
    EncodeMsgHeaderAndBody(rmMsg.dwParams, rmMsg.dwFlags, rmMsg.msgOutSubmit.cchMsgLength,
                           rmMsg.msgOutSubmit.cbHdrLength, rmMsg.msgOutSubmit.rgbHdr, rmMsg.msgOutSubmit.rgbMsg,
                           &rmMsg.msgOutSubmit.rmdDataCoding, pbWalk, cbAdvancedBy);
    pbWalk += cbAdvancedBy;

    rcbUsed = pbWalk - pbOut;

Error:
    return hr;
}


//
// WriteBitwiseToBuffer()
// Write <iBits> bits to <bBuffer> (length: <iLen>) at bit position
// <piBitPos>. <piBitPos> gets increased after data have been written.
//
static void WriteBitwiseToBuffer(BYTE *bBuffer, INT iLen, UINT uiData, INT iBits, INT *piBitPos)
{
    INT bitPos = *piBitPos;

    if ((iBits < 0) || (iBits > 8) || ((bitPos + iBits) > iLen * 8)) {
        RILRetailTraceZone(ZONE_ERROR, (TEXT("RilDrv: WriteBitwiseToBuffer() - illegal write\r\n")));
        return;
    }
    uiData &= (0xFFFF >> (16 - iBits));
    INT index = bitPos >> 3;
    INT offset = 16 - (bitPos & 0x07) - iBits;  // &7==%8
    uiData <<= offset;
    *piBitPos += iBits;
    bBuffer[index] |= uiData >> 8;
    if (offset < 8) bBuffer[index + 1] |= uiData & 0xFF;
}

//
// CleanCdmaAddress()
// Windows mobile might include format characters in the address string. These
// characters must be removed before sending.
//
static void CleanCdmaAddress(char *szAddress)
{
    char szTemp[MAX_ATCMD_LEN];
    INT nLen = strlen(szAddress);
    INT nPosWrite = 0;
    INT nPos;

    for (nPos = 0; nPos < nLen; nPos++) {
        if (szAddress[nPos] >= '0' && szAddress[nPos] <= '9') {
            szTemp[nPosWrite] = szAddress[nPos];
            nPosWrite++;
        }
    }
    szTemp[nPosWrite] = '\0';
    (void)strncpyz(szAddress, szTemp, nLen+1);
}

// Translation table to 4-Bit-CDMA-Address
static INT g_c4BDigit[] = { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0};

//
// Fill out a BYTE array for a IS637 Submit message
//
static HRESULT EncodeIS637SubmitMsg(const RILMESSAGE& rmMsg, BYTE* const pbOut, const UINT cbOut, UINT& rcbUsed)
{
    HRESULT hr = E_FAIL;
    INT iBitPos = 0;
    INT iMsgEncoding;
    INT iMsgBits;
    INT i;
    UINT uiUDLen;
    UINT uiAckLen = 0;
    UINT uiPrioLen = 0;
    UINT uiPrivLen = 0;
    UINT uiTeleService = 0x1002;
    UINT uiNumFields = 0;

    memset(pbOut, 0, cbOut);

    // See specification 3GPP2 C.S0015-A
    WriteBitwiseToBuffer(pbOut, cbOut, 0, 8, &iBitPos);    // Message type = point to point

    // Teleservice Identifier (3.4.3.1)
    switch (rmMsg.msgIS637OutSubmit.dwTeleservice) {
        case RIL_MSGTELESERVICE_PAGING:
            uiTeleService = 0x1001;
            break;
        case RIL_MSGTELESERVICE_MESSAGING:
            uiTeleService = 0x1002;
            break;
        case RIL_MSGTELESERVICE_VOICEMAIL:
            uiTeleService = 0x1003;
            break;
        case RIL_MSGTELESERVICE_WAP:
            uiTeleService = 0x1004;
            break;
        default:
            RILRetailTraceZone(ZONE_ERROR, (TEXT("RilDrv: WriteBitwiseToBuffer() - unknown teleservice\r\n")));
            uiTeleService = 0x1002;    // Use messaging as default
    }
    WriteBitwiseToBuffer(pbOut, cbOut, 0, 8, &iBitPos);    // Parameter ID
    WriteBitwiseToBuffer(pbOut, cbOut, 2, 8, &iBitPos);    // Parameter Len
    WriteBitwiseToBuffer(pbOut, cbOut, uiTeleService >> 8, 8, &iBitPos);    // Identifier
    WriteBitwiseToBuffer(pbOut, cbOut, uiTeleService & 0xFF, 8, &iBitPos);

   // Service Category (3.4.3.2) not coded by Windows Mobile

    // Destination address (3.4.3.3)
    if (rmMsg.dwParams & RIL_PARAM_M_DESTADDRESS) {
        char szAddress[MAX_ATCMD_LEN];
        UINT uiBits = 10;

        (void)_snprintfz(szAddress, MAX_ATCMD_LEN, "%S", rmMsg.msgIS637OutSubmit.raDestAddress.wszAddress);
        CleanCdmaAddress(szAddress);
        if (rmMsg.msgIS637OutSubmit.bDigit) {   // 8-Bit
            uiBits += strlen(szAddress) * 8;
            uiBits += 3;  // number_type
            if (rmMsg.msgIS637OutSubmit.raDestAddress.dwNumPlan > 0)
                uiBits += 4;  // number_plan
        } else {   // 4-Bit
            uiBits += strlen(szAddress) * 4;
        }
        while (uiBits & 7) uiBits++;   // Round to next byte
        WriteBitwiseToBuffer(pbOut, cbOut, 4, 8, &iBitPos);    // Parameter ID
        WriteBitwiseToBuffer(pbOut, cbOut, (uiBits+7)/8, 8, &iBitPos);    // Parameter Len
        if (rmMsg.msgIS637OutSubmit.bDigit) {   // 8-Bit
            WriteBitwiseToBuffer(pbOut, cbOut, 1, 1, &iBitPos);    // Digit mode
            if (rmMsg.msgIS637OutSubmit.raDestAddress.dwNumPlan > 0)
                WriteBitwiseToBuffer(pbOut, cbOut, 0, 1, &iBitPos);    // Number mode
            else
                WriteBitwiseToBuffer(pbOut, cbOut, 1, 1, &iBitPos);    // Number mode
            WriteBitwiseToBuffer(pbOut, cbOut, rmMsg.msgIS637OutSubmit.raDestAddress.dwType & 0x07, 3, &iBitPos);    // Number type
            if (rmMsg.msgIS637OutSubmit.raDestAddress.dwNumPlan > 0)
                WriteBitwiseToBuffer(pbOut, cbOut, rmMsg.msgIS637OutSubmit.raDestAddress.dwNumPlan & 0x0F, 4, &iBitPos);    // Number plan
        } else {   // 4-Bit
            WriteBitwiseToBuffer(pbOut, cbOut, 0, 1, &iBitPos);    // Digit mode
            if (rmMsg.msgIS637OutSubmit.raDestAddress.dwNumPlan > 0)
                WriteBitwiseToBuffer(pbOut, cbOut, 0, 1, &iBitPos);    // Number mode
            else
                WriteBitwiseToBuffer(pbOut, cbOut, 1, 1, &iBitPos);    // Number mode
        }
        WriteBitwiseToBuffer(pbOut, cbOut, strlen(szAddress), 8, &iBitPos);    // Num fields
        if (rmMsg.msgIS637OutSubmit.bDigit) {   // 8-Bit
            for (i = 0; i < (INT)strlen(szAddress); i++)
                WriteBitwiseToBuffer(pbOut, cbOut, szAddress[i], 8, &iBitPos);    // Num fields
        }
        else {   // 4-Bit
            for (i = 0; i < (INT)strlen(szAddress); i++)
                WriteBitwiseToBuffer(pbOut, cbOut, g_c4BDigit[(szAddress[i]-'0')&0xF], 4, &iBitPos);    // Num fields
        }
        // Skip bits until byte order is reached.
        while (iBitPos & 7) WriteBitwiseToBuffer(pbOut, cbOut, 0, 1, &iBitPos);  // Reserved
    }

    // Destination Subaddress (3.4.3.4)
    if (rmMsg.dwParams & RIL_PARAM_M_DESTSUBADDRESS) {
        char szAddress[MAX_ATCMD_LEN];

        (void)_snprintfz(szAddress, MAX_ATCMD_LEN, "%S", rmMsg.msgIS637OutSubmit.rsaDestSubaddr.wszSubAddress);
        CleanCdmaAddress(szAddress);
        WriteBitwiseToBuffer(pbOut, cbOut, 5, 8, &iBitPos);   // Parameter ID
        WriteBitwiseToBuffer(pbOut, cbOut, strlen(szAddress) + 2, 8, &iBitPos);   // len
        if (rmMsg.msgIS637OutSubmit.rsaDestSubaddr.dwType == RIL_SUBADDRTYPE_NSAP)
            WriteBitwiseToBuffer(pbOut, cbOut,  0, 3, &iBitPos);   // Type
        else
            WriteBitwiseToBuffer(pbOut, cbOut,  1, 3, &iBitPos);   // Type
        WriteBitwiseToBuffer(pbOut, cbOut, strlen(szAddress)&1, 1, &iBitPos);   // Odd
        WriteBitwiseToBuffer(pbOut, cbOut, strlen(szAddress), 8, &iBitPos);   // Len

        for (i=0; i<(int)strlen(szAddress); i++)
            WriteBitwiseToBuffer(pbOut, cbOut, szAddress[i], 8, &iBitPos);

        WriteBitwiseToBuffer(pbOut, cbOut, 0, 4, &iBitPos); // reserved
    }

    // Bearer Reply Option (3.4.3.5)
    WriteBitwiseToBuffer(pbOut, cbOut, 6, 8, &iBitPos);    // Parameter ID
    WriteBitwiseToBuffer(pbOut, cbOut, 1, 8, &iBitPos);    // Parameter Len
    WriteBitwiseToBuffer(pbOut, cbOut,( (++g_iCdmaReplyID)& 0x3F)<<2, 8, &iBitPos);    // Unique Reply ID

    // Bearer Data (3.4.3.7)
    switch (rmMsg.msgIS637OutSubmit.dwMsgEncoding) {
        case RIL_MSGCODING_7BITASCII:
            iMsgEncoding = 2;
            iMsgBits = 7;
            uiNumFields = rmMsg.msgIS637OutSubmit.cchMsgLength;
            break;
        case RIL_MSGCODING_UNICODE:
            iMsgEncoding = 4;
            iMsgBits = 8;    // Actually unicode are 16 bits, but the length parameter includes the factor 2
            uiNumFields = rmMsg.msgIS637OutSubmit.cchMsgLength / 2;
            break;
        default:
            RILRetailTraceZone(ZONE_ERROR, (TEXT("RilDrv: unknwon encoding %d\r\n"), rmMsg.msgIS637OutSubmit.dwMsgEncoding));
            goto Error;
    }
    uiUDLen = (5+8+(iMsgBits*rmMsg.msgIS637OutSubmit.cchMsgLength)+7)>>3;
    if (rmMsg.dwParams & (RIL_PARAM_M_DELIVERYACK|RIL_PARAM_M_BEARERREPLYACK|RIL_PARAM_M_USERACK)) {
        BOOL bDeliveryAckRequest = FALSE;
        BOOL bUserAckRequest = FALSE;
        BOOL bBearerReplyRequest = FALSE;

        // Write this subparameter only if at least one option is set.
        if (rmMsg.dwParams & (RIL_PARAM_M_USERACK))
            bUserAckRequest = rmMsg.msgIS637OutSubmit.bUserAckRequest;
        if (rmMsg.dwParams & (RIL_PARAM_M_DELIVERYACK))
            bDeliveryAckRequest = rmMsg.msgIS637OutSubmit.bDeliveryAckRequest;
        if (rmMsg.dwParams & (RIL_PARAM_M_BEARERREPLYACK))
            bBearerReplyRequest = rmMsg.msgIS637OutSubmit.bBearerReplyRequest;
        if (bDeliveryAckRequest || bUserAckRequest || bBearerReplyRequest)
            uiAckLen = 3;
    }
    if (rmMsg.dwParams & RIL_PARAM_M_PRIORITY)
        uiPrioLen = 3;
    if (rmMsg.dwParams & RIL_PARAM_M_PRIVACY)
        uiPrivLen = 3;

    WriteBitwiseToBuffer(pbOut, cbOut, 8, 8, &iBitPos);    // Parameter ID
    WriteBitwiseToBuffer(pbOut, cbOut, 5 + (2+uiUDLen) + uiAckLen + uiPrioLen + uiPrivLen, 8, &iBitPos);    // Parameter Len (Message Identifier + User Data + options)

    // Message Identifier (4.5.1)
    WriteBitwiseToBuffer(pbOut, cbOut, 0, 8, &iBitPos);    // SubParameter ID
    WriteBitwiseToBuffer(pbOut, cbOut, 3, 8, &iBitPos);    // SubParameter Len
    WriteBitwiseToBuffer(pbOut, cbOut, 2, 4, &iBitPos);    // Message type = Submit
    if (rmMsg.dwParams & RIL_PARAM_M_MSGID) {
        WriteBitwiseToBuffer(pbOut, cbOut, rmMsg.msgIS637OutSubmit.dwMsgID >> 8, 8, &iBitPos);    // Message ID
        WriteBitwiseToBuffer(pbOut, cbOut, rmMsg.msgIS637OutSubmit.dwMsgID & 0xFF, 8, &iBitPos);
    }
    else {
        WriteBitwiseToBuffer(pbOut, cbOut, 0, 8, &iBitPos);    // Message ID (not reequired)
        WriteBitwiseToBuffer(pbOut, cbOut, 0, 8, &iBitPos);    // Message ID (not reequired)
    }
    WriteBitwiseToBuffer(pbOut, cbOut, 0, 1, &iBitPos);    // UDH indication
    WriteBitwiseToBuffer(pbOut, cbOut, 0, 3, &iBitPos);    // reserved

    // Generate User Data (4.5.2)
    WriteBitwiseToBuffer(pbOut, cbOut, 1, 8, &iBitPos);    // SubParameter ID
    WriteBitwiseToBuffer(pbOut, cbOut, uiUDLen, 8, &iBitPos);    // SubParameter Len
    WriteBitwiseToBuffer(pbOut, cbOut, iMsgEncoding, 5, &iBitPos);    // Message encoding (see: TSB-58-D)
    WriteBitwiseToBuffer(pbOut, cbOut, uiNumFields, 8, &iBitPos);    // Num fiels
    for(i=0; i < (INT)rmMsg.msgIS637OutSubmit.cchMsgLength; i++) {
        WriteBitwiseToBuffer(pbOut, cbOut, rmMsg.msgIS637OutSubmit.rgbMsg[i], iMsgBits, &iBitPos);    // CHARi
    }
    // Skip bits until byte order is reached.
    while (iBitPos & 7) WriteBitwiseToBuffer(pbOut, cbOut, 0, 1, &iBitPos);  // Reserved
    // Priority Indicator (4.5.9)
    if (uiPrioLen > 0) {
        WriteBitwiseToBuffer(pbOut, cbOut, 8, 8, &iBitPos);    // SubParameter ID
        WriteBitwiseToBuffer(pbOut, cbOut, 1, 8, &iBitPos);    // SubParameter Len
        switch (rmMsg.msgIS637OutSubmit.dwMsgPriority) {
            case RIL_MSGPRIORITY_HIGH:
                WriteBitwiseToBuffer(pbOut, cbOut, 1, 2, &iBitPos);    // Priority
                break;
            case RIL_MSGPRIORITY_URGENT:
                WriteBitwiseToBuffer(pbOut, cbOut, 2, 2, &iBitPos);    // Priority
                break;
            case RIL_MSGPRIORITY_EMERGENCY:
                WriteBitwiseToBuffer(pbOut, cbOut, 3, 2, &iBitPos);    // Priority
                break;
            case RIL_MSGPRIORITY_NORMAL:
            default:
                WriteBitwiseToBuffer(pbOut, cbOut, 0, 2, &iBitPos);    // Priority
        }
        WriteBitwiseToBuffer(pbOut, cbOut, 0, 6, &iBitPos);    // Reserved
    }

    // Privacy Indicator (4.5.10)
    if (uiPrivLen > 0) {
        WriteBitwiseToBuffer(pbOut, cbOut, 9, 8, &iBitPos);    // SubParameter ID
        WriteBitwiseToBuffer(pbOut, cbOut, 1, 8, &iBitPos);    // SubParameter Len
        switch (rmMsg.msgIS637OutSubmit.dwMsgPriority) {
            case RIL_MSGDISPLAYMODE_IMMEDIATE:
                WriteBitwiseToBuffer(pbOut, cbOut, 3, 2, &iBitPos);    // Priority
                break;
            case RIL_MSGDISPLAYMODE_MOBILEDEFAULT:
            default:
                WriteBitwiseToBuffer(pbOut, cbOut, 0, 2, &iBitPos);    // Priority
        }
        WriteBitwiseToBuffer(pbOut, cbOut, 0, 6, &iBitPos);    // Reserved
    }

    // Reply Option (4.5.11) (optional)
    if (uiAckLen > 0) {
        BOOL bDeliveryAckRequest = FALSE;
        BOOL bUserAckRequest = FALSE;
        BOOL bBearerReplyRequest = FALSE;

        if (rmMsg.dwParams & (RIL_PARAM_M_USERACK))
            bUserAckRequest = rmMsg.msgIS637OutSubmit.bUserAckRequest;
        if (rmMsg.dwParams & (RIL_PARAM_M_DELIVERYACK))
            bDeliveryAckRequest = rmMsg.msgIS637OutSubmit.bDeliveryAckRequest;
        if (rmMsg.dwParams & (RIL_PARAM_M_BEARERREPLYACK))
            bBearerReplyRequest = rmMsg.msgIS637OutSubmit.bBearerReplyRequest;
        // Already checked: at least one option is set.
        WriteBitwiseToBuffer(pbOut, cbOut, 10, 8, &iBitPos);    // SubParameter ID
        WriteBitwiseToBuffer(pbOut, cbOut, 1, 8, &iBitPos);    // SubParameter Len
        WriteBitwiseToBuffer(pbOut, cbOut, bUserAckRequest&1, 1, &iBitPos);    // User Ack
        WriteBitwiseToBuffer(pbOut, cbOut, bDeliveryAckRequest&1, 1, &iBitPos);    // Delivery Ack
        WriteBitwiseToBuffer(pbOut, cbOut, bBearerReplyRequest&1, 1, &iBitPos);    // Bearer Ack
        WriteBitwiseToBuffer(pbOut, cbOut, 0, 5, &iBitPos);    // Reserved
    }

    rcbUsed = (iBitPos+7)>>3;

    hr = S_OK;
Error:
    return hr;
}

//
// Fill out a BYTE array for a Command message
//
static HRESULT EncodeCmdMsg(const RILMESSAGE& rmMsg, BYTE* const pbOut, const UINT cbOut, UINT& rcbUsed)
{
    BYTE* pbWalk = pbOut;
    UINT cbAdvancedBy;
    HRESULT hr = S_OK;

    (void)memset(pbOut, 0, cbOut);

    // These parameters are mandatory, and if not present, we error out.
    if (!(rmMsg.dwParams & RIL_PARAM_M_TYPE) || !(rmMsg.dwParams & RIL_PARAM_M_DESTADDRESS) ||
        !(rmMsg.dwParams & RIL_PARAM_M_PROTOCOLID) || !(rmMsg.dwParams & RIL_PARAM_M_COMMANDTYPE) ||
        !(rmMsg.dwParams & RIL_PARAM_M_TGTMSGREFERENCE) || !(rmMsg.dwParams & RIL_PARAM_M_CMDLENGTH) ||
        !(rmMsg.dwParams & RIL_PARAM_M_FLAGS)) {
        hr = E_INVALIDARG;
        goto Error;
    }

    // Set Type, per GSM 03.40 section 9.2.3.1
    *pbWalk |= 0x02;

    // See GSM 03.40 section 9.2.3.5
    if (rmMsg.dwFlags & RIL_MSGFLAG_STATUSREPORTREQUESTED) {
        *pbWalk |= 0x20;
    }
    // See GSM 03.40 section 9.2.3.23
    if (rmMsg.dwFlags & RIL_MSGFLAG_HEADER) {
        *pbWalk |= 0x40;
    }
    pbWalk++;

    // Add a zero for the message reference (see GSM 03.40 section 9.2.3.6)
    // NOTE: it appears that we can put anything into this field -- the module generates correct msg reference for us
    pbWalk++;

    // See GSM 03.40 section 9.2.3.9
    hr = EncodeMsgProtocolID(rmMsg.msgOutCommand.dwProtocolID, pbWalk, cbAdvancedBy);
    if (FAILED(hr)) {
        goto Error;
    }
    pbWalk += cbAdvancedBy;

    // See GSM 03.40 section 9.2.3.19
    switch (rmMsg.msgOutCommand.dwCommandType)
    {
        case RIL_MSGCMDTYPE_STATUSREQ:
            *pbWalk++ = 0x00;
            break;

        case RIL_MSGCMDTYPE_CANCELSTATUSREQ:
            *pbWalk++ = 0x01;
            break;

        case RIL_MSGCMDTYPE_DELETEMESSAGE:
            *pbWalk++ = 0x02;
            break;

        case RIL_MSGCMDTYPE_ENABLESTATUSREQ:
            *pbWalk++ = 0x03;
            break;

        default:
            hr = E_INVALIDARG;
            goto Error;
    }

    // See GSM 03.40 section 9.2.3.18
    if (0xff < rmMsg.msgOutCommand.dwTgtMsgReference) {
        hr = E_INVALIDARG;
        goto Error;
    }
    *pbWalk++ = (BYTE)rmMsg.msgOutCommand.dwTgtMsgReference;

    // Encode the destination address.  pbMsg should be set properly by the function
    hr = EncodeMsgAddress(rmMsg.msgOutCommand.raDestAddress, FALSE, pbWalk, cbAdvancedBy);
    if (FAILED(hr)) {
        goto Error;
    }
    pbWalk += cbAdvancedBy;

    // Encode command data length
    *pbWalk++ = (BYTE)rmMsg.msgOutCommand.cbCmdLength;

    // Encode command data
    if (rmMsg.dwParams & RIL_PARAM_M_CMD) {
        cbAdvancedBy = min(rmMsg.msgOutCommand.cbCmdLength, MAXLENGTH_CMD);
        memcpy(pbWalk, rmMsg.msgOutCommand.rgbCmd, cbAdvancedBy);
        pbWalk += cbAdvancedBy;
    }
    rcbUsed = pbWalk - pbOut;

Error:
    return hr;
}


//
// Encode a Deliver message for SIM storage
//
static HRESULT EncodeDeliverMsg(const RILMESSAGE& rmMsg, BYTE* const pbOut, const UINT cbOut, UINT& rcbUsed)
{
    BYTE* pbWalk = pbOut;
    UINT cbAdvancedBy;
    HRESULT hr = S_OK;

    (void)memset(pbOut, 0, cbOut);

    // These parameters are mandatory, and if not present, we error out.
    if (!(rmMsg.dwParams & RIL_PARAM_M_TYPE) || !(rmMsg.dwParams & RIL_PARAM_M_FLAGS) ||
        !(rmMsg.dwParams & RIL_PARAM_M_ORIGADDRESS) || !(rmMsg.dwParams & RIL_PARAM_M_PROTOCOLID) ||
        !(rmMsg.dwParams & RIL_PARAM_M_MSGLENGTH) || !(rmMsg.dwParams & RIL_PARAM_M_SCRECEIVETIME) ||
        !(rmMsg.dwParams & RIL_PARAM_M_DATACODING)) {
        hr = E_INVALIDARG;
        goto Error;
    }

    // Set Type, per GSM 03.40 section 9.2.3.1
    *pbWalk |= 0x00;

    // RIL_MSGFLAGS_MORETOSEND, GSM 03.40 section 9.2.3.2
    if (!(rmMsg.dwFlags & RIL_MSGFLAG_MORETOSEND)) {
        *pbWalk |= 0x04;
    }

    // RIL_MSGFLAG_STATUSREPORTRETURNED, GSM 03.40 section 9.2.3.4
    if (rmMsg.dwFlags & RIL_MSGFLAG_STATUSREPORTRETURNED) {
        *pbWalk |= 0x20;
    }

    // RIL_MSGFLAG_HEADER, GSM 03.40 section 9.2.3.23
    if (rmMsg.dwFlags & RIL_MSGFLAG_HEADER) {
        *pbWalk |= 0x40;
    }

    // RIL_MSGFLAG_REPLYPATH, GSM 03.40 section 9.2.3.17
    if (rmMsg.dwFlags & RIL_MSGFLAG_REPLYPATH) {
        *pbWalk |= 0x80;
    }
    pbWalk++;

    // Encode the origination address
    hr = EncodeMsgAddress(rmMsg.msgInDeliver.raOrigAddress, FALSE, pbWalk, cbAdvancedBy);
    if (FAILED(hr)) {
        goto Error;
    }
    pbWalk += cbAdvancedBy;

    // Set ProtocolID, see GSM 03.40 section 9.2.3.9
    hr = EncodeMsgProtocolID(rmMsg.msgInDeliver.dwProtocolID, pbWalk, cbAdvancedBy);
    if (FAILED(hr)) {
        goto Error;
    }
    pbWalk += cbAdvancedBy;

    // Reconstruct DCS, see GSM 03.40 section 9.2.3.10
    hr = EncodeMsgDCS(rmMsg.msgInDeliver.rmdDataCoding, pbWalk, cbAdvancedBy);
    if (FAILED(hr)) {
        goto Error;
    }
    pbWalk += cbAdvancedBy;

    // Encode receive timestamp
    if (rmMsg.dwParams & RIL_PARAM_M_SCRECEIVETIME) {
        EncodeMsgTimeStamp(rmMsg.msgInDeliver.stSCReceiveTime, pbWalk, cbAdvancedBy);
        pbWalk += cbAdvancedBy;
    }

    // Encode header and body
    EncodeMsgHeaderAndBody(rmMsg.dwParams, rmMsg.dwFlags, rmMsg.msgInDeliver.cchMsgLength,
                           rmMsg.msgInDeliver.cbHdrLength, rmMsg.msgInDeliver.rgbHdr, rmMsg.msgInDeliver.rgbMsg,
                           &rmMsg.msgInDeliver.rmdDataCoding, pbWalk, cbAdvancedBy);
    pbWalk += cbAdvancedBy;

    rcbUsed = pbWalk - pbOut;

Error:
    return hr;
}


//
// Encode a Status message for SIM storage
//
static HRESULT EncodeStatusMsg(const RILMESSAGE& rmMsg, BYTE* const pbOut, const UINT cbOut, UINT& rcbUsed)
{
    BYTE* pbWalk = pbOut;
    UINT cbAdvancedBy;
    HRESULT hr = S_OK;

    (void)memset(pbOut, 0, cbOut);

    // These parameters are mandatory, and if not present, we error out.
    if (!(rmMsg.dwParams & RIL_PARAM_M_TYPE) || !(rmMsg.dwParams & RIL_PARAM_M_FLAGS) ||
        !(rmMsg.dwParams & RIL_PARAM_M_TGTRECIPADDRESS) || !(rmMsg.dwParams & RIL_PARAM_M_TGTSCRECEIVETIME) ||
        !(rmMsg.dwParams & RIL_PARAM_M_TGTDISCHARGETIME) || !(rmMsg.dwParams & RIL_PARAM_M_TGTDLVSTATUS)) {
        hr = E_INVALIDARG;
        goto Error;
    }

    // Set Type, per GSM 03.40 section 9.2.3.1
    *pbWalk |= 0x02;

    // RIL_MSGFLAGS_MORETOSEND, GSM 03.40 section 9.2.3.2
    if (!(rmMsg.dwFlags & RIL_MSGFLAG_MORETOSEND)) {
        *pbWalk |= 0x04;
    }

    // RIL_MSGFLAG_CAUSEDBYCOMMAND, GSM 03.40 section 9.2.3.26
    if (rmMsg.dwFlags & RIL_MSGFLAG_CAUSEDBYCOMMAND) {
        *pbWalk |= 0x20;
    }

    // RIL_MSGFLAG_HEADER, GSM 03.40 section 9.2.3.23
    if (rmMsg.dwFlags & RIL_MSGFLAG_HEADER) {
        *pbWalk |= 0x40;
    }
    pbWalk++;

    // Encode the Message Reference number
    if (0xff < rmMsg.msgInStatus.dwTgtMsgReference) {
        hr = E_INVALIDARG;
        goto Error;
    }
    *pbWalk++ = (BYTE)rmMsg.msgInStatus.dwTgtMsgReference;

    // Encode the destination address.  pbMsg should be set properly by the function
    hr = EncodeMsgAddress(rmMsg.msgInStatus.raTgtRecipAddress, FALSE, pbWalk, cbAdvancedBy);
    if (FAILED(hr)) {
        goto Error;
    }
    pbWalk += cbAdvancedBy;

    // Encode receive timestamp
    EncodeMsgTimeStamp(rmMsg.msgInStatus.stTgtSCReceiveTime, pbWalk, cbAdvancedBy);
    pbWalk += cbAdvancedBy;

    // Encode discharge timestamp
    EncodeMsgTimeStamp(rmMsg.msgInStatus.stTgtDischargeTime, pbWalk, cbAdvancedBy);
    pbWalk += cbAdvancedBy;

    // Encode devivery status
    hr = EncodeMsgDlvStatus(rmMsg.msgInStatus.dwTgtDlvStatus, pbWalk, cbAdvancedBy);
    if (FAILED(hr)) {
        goto Error;
    }
    pbWalk += cbAdvancedBy;

    // Set the Parameter Indicator and encode the indicated fields
    // See GSM 03.40 section 9.2.3.27
    if (rmMsg.dwParams & RIL_PARAM_M_PROTOCOLID ||
        rmMsg.dwParams & RIL_PARAM_M_DATACODING ||
        rmMsg.dwParams & RIL_PARAM_M_MSGLENGTH) {
        *pbWalk = 0;
        if (rmMsg.dwParams & RIL_PARAM_M_PROTOCOLID) {
            *pbWalk |= 0x01;
        }
        if (rmMsg.dwParams & RIL_PARAM_M_DATACODING) {
            *pbWalk |= 0x20;
        }
        if (rmMsg.dwParams & RIL_PARAM_M_MSGLENGTH) {
            *pbWalk |= 0x40;
        }
        pbWalk++;

        // Set ProtocolID if it exists.  see GSM 03.40 section 9.2.3.9
        if (rmMsg.dwParams & RIL_PARAM_M_PROTOCOLID) {
            hr = EncodeMsgProtocolID(rmMsg.msgInStatus.dwProtocolID, pbWalk, cbAdvancedBy);
            if (FAILED(hr)) {
                goto Error;
            }
            pbWalk += cbAdvancedBy;
        }

        // Set DCS if it it exists.  see GSM 03.40 section 9.2.3.10
        if (rmMsg.dwParams & RIL_PARAM_M_DATACODING) {
            hr = EncodeMsgDCS(rmMsg.msgInStatus.rmdDataCoding, pbWalk, cbAdvancedBy);
            if (FAILED(hr)) {
                goto Error;
            }
            pbWalk += cbAdvancedBy;
        }

        // Set Msg Data
        if (rmMsg.dwParams & RIL_PARAM_M_MSGLENGTH) {
            // Encode header and body
            EncodeMsgHeaderAndBody(rmMsg.dwParams, rmMsg.dwFlags, rmMsg.msgInStatus.cchMsgLength,
                                   rmMsg.msgInStatus.cbHdrLength, rmMsg.msgInStatus.rgbHdr, rmMsg.msgInStatus.rgbMsg,
                                   &rmMsg.msgInStatus.rmdDataCoding, pbWalk, cbAdvancedBy);
            pbWalk += cbAdvancedBy;
        }
    }
    rcbUsed = pbWalk - pbOut;

Error:
    return hr;
}

//
// Fill out a BYTE array for a Raw message
//
static HRESULT EncodeRawMsg(const RILMESSAGE& rmMsg, BYTE* const pbOut, const UINT cbOut, UINT& rcbUsed)
{
    HRESULT hr = S_OK;

    // These parameters are mandatory, and if not present, we error out.
    if (!(rmMsg.dwParams & RIL_PARAM_M_TYPE) || !(rmMsg.dwParams & RIL_PARAM_M_MSGLENGTH)) {
        hr = E_INVALIDARG;
        goto Error;
    }

    if (rmMsg.msgOutRaw.cchMsgLength > cbOut) {
        hr = E_FAIL;
        goto Error;
    }

    rcbUsed = min(cbOut, rmMsg.msgOutRaw.cchMsgLength);
    memcpy(pbOut, rmMsg.msgOutRaw.rgbMsg, rcbUsed);

Error:
    return hr;
}

//
// Encode an outgoing message from an RILMESSAGE to a BYTE array
//
HRESULT EncodeSMSMessage(const RILMESSAGE& rmMsg, const LPSTR sOut, const UINT cbOut, UINT& rcbUsed,
                         UINT& rnGSMLength)
{
    HRESULT hr = S_OK;
    BYTE rgbData[MAX_MSGBUFFER];
    UINT cbDataUsed;
    UINT cbAddress = 0;

    // Encode the SMSC address
    if (rmMsg.dwParams & RIL_PARAM_M_SVCCTRADDRESS) {
        hr = EncodeMsgAddress(rmMsg.raSvcCtrAddress, TRUE, rgbData, cbAddress);
        if (FAILED(hr)) {
            goto Error;
        }
    } else {
        if (rmMsg.dwType != RIL_MSGTYPE_OUT_IS637SUBMIT) {
            // SMSC address isn't specified -- indicate that by a 0 length byte
            *rgbData = 0;
            cbAddress = 1;
        }
    }

    if ( MAX_MSGBUFFER <= cbAddress )
    {
        hr = E_ABORT;
        goto Error;
    }

    // Encode the rest of the
    switch (rmMsg.dwType)
    {
    case RIL_MSGTYPE_OUT_SUBMIT:
        hr = EncodeSubmitMsg(rmMsg, &rgbData[cbAddress], MAX_MSGBUFFER - cbAddress, cbDataUsed);
        break;

    case RIL_MSGTYPE_OUT_COMMAND:
        hr = EncodeCmdMsg(rmMsg, &rgbData[cbAddress], MAX_MSGBUFFER - cbAddress, cbDataUsed);
        break;

    case RIL_MSGTYPE_OUT_IS637SUBMIT:
        hr = EncodeIS637SubmitMsg(rmMsg, &rgbData[cbAddress], MAX_MSGBUFFER - cbAddress, cbDataUsed);
        break;

    case RIL_MSGTYPE_IN_DELIVER:
        hr = EncodeDeliverMsg(rmMsg, &rgbData[cbAddress], MAX_MSGBUFFER - cbAddress, cbDataUsed);
        break;

    case RIL_MSGTYPE_IN_STATUS:
        hr = EncodeStatusMsg(rmMsg, &rgbData[cbAddress], MAX_MSGBUFFER - cbAddress, cbDataUsed);
        break;

    case RIL_MSGTYPE_OUT_RAW:
        hr = EncodeRawMsg(rmMsg, &rgbData[cbAddress], MAX_MSGBUFFER - cbAddress, cbDataUsed);
        break;

    default:
        RILRetailTraceZone(ZONE_ERROR, (TEXT("RilDrv: unknwon message type %X\r\n"), rmMsg.dwType));
        hr = E_FAIL;
        goto Error;
    }

    // GSM length is the number of characters used for the message itself (not including the SMSC address upfront)
    rnGSMLength = cbDataUsed;

    // Convert the byte array into the GSMHex representation (2 chars per byte)
    if (!GSMToGSMHex((LPCSTR)rgbData, cbDataUsed + cbAddress, sOut, cbOut, rcbUsed)) {
        hr = E_FAIL;
        goto Error;
    }

Error:
    return hr;
}
