/*****************************************************************************
 * Copyright (C) Gemalto M2M GmbH
 *****************************************************************************
 * Project: IMC-RIL-Driver
 *****************************************************************************
 * Author : Wireless Modules RD SW A2
 *****************************************************************************
 * $Workfile: Y:/z_ms-d/src/working/Quinn/RilDrv/smsprs.cpp $
 * $Revision: /main/ms-d/19 $
 *     $Date: 2013-09-13 09:27:28 $
 *   $Author: pmackent $
 *****************************************************************************
 * Target:
 *  Microsoft Windows Mobile 5.0
 *****************************************************************************
 * Description:
 *  Gemalto IMC RIL
 *****************************************************************************
 *
 * $Log: Y:/z_ms-d/src/working/Quinn/RilDrv/smsprs.cpp$ 
 * 
 * /main/ms-d/19   2013-09-13 09:27:28   pmackent 
 * Cell Broadcast for CDMA (IPIS100091379)
 *
 * /main/ms-d/18   2013-08-20 15:43:37   pmackent
 * Handle voice mail notification in CDMA (IPIS100089960)
 *
 * /main/ms-d/17   2013-04-26 09:17:23   pmackent
 * Parsing of CDMA status reports added
 *
 * /main/ms-d/16   2012-09-25 10:18:12   pmackent
 * Handle CDMA SMS encoding "Latin"
 *
 * /main/ms-d/15   2012-06-14 14:49:25   pmackent
 * Typo in encoding constant resolved
 *
 * /main/ms-d/14   2012-06-14 14:29:13   pmackent
 * Fix problem with 16-bit CDMA-Unicode coding
 *
 * /main/ms-d/13   2012-04-26 13:02:34   pmackent
 * Further CDMA specific code
 *
 * /main/ms-d/12   2012-04-19 14:42:33   ctna0256
 * Small CDMA adaptions
 *
 * /main/ms-d/11   2012-04-18 13:46:34   ctna0256
 * Introducing PVS8
 * Implementing CDMA-SMS
 * Various CDMA enhancements
 *
 * /main/ms-d/10   2011-09-26 13:35:24   ctna0256
 * Comply with CE6 warning level 4
 *
 * /main/ms-d/9   2009-02-18 09:38:08   ctna0256
 * Merge from Microsoft WM65 RIL
 *
 * /main/ms-d/8   2008-10-06 16:04:21   ctna0256
 * New options: Disable SMS completely; Use SMS reply address
 *
 * /main/ms-d/7   2008-10-06 11:11:33   ctna0256
 * Disable KSC5601 per default
 *
 * /main/ms-d/6   2008-09-23 10:50:11   ctna0256
 * Support of SMS coded with KSC5601
 *
 * /main/ms-d/5   2008-08-22 10:28:03   ctna0256
 * Set Cinterion copyright text
 *
 * /main/ms-d/4   2008-08-21 09:35:37   ctna0256
 * Prepared for new disclaimer handling
 *
 * /main/ms-d/3   2008-04-03 14:42:02   mp49210
 * Introduction of new debug zones
 *
 * /main/ms-d/2   2007-12-05 13:57:03   mp49210
 * Proper use of debug prints
 *
 * /main/ms-d/1   2006-10-25 16:52:17   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"

//
// Command types
//
static const DWORD g_rgdwCommandTypes[] =
{
    0x00,   // RIL_MSGCMDTYPE_STATUSREQ
    0x01,   // RIL_MSGCMDTYPE_CANCELSTATUSREQ
    0x02,   // RIL_MSGCMDTYPE_DELETEMESSAGE
    0x03,   // RIL_MSGCMDTYPE_ENABLESTATUSREQ
};
#define NUM_COMMANDTYPES    (sizeof(g_rgdwCommandTypes) / sizeof(DWORD))

//
// Geographical Scope
//
static const DWORD g_rgdwGeographicalScopes[] =
{
    RIL_GEOSCOPE_CELL_IMMEDIATE,
    RIL_GEOSCOPE_PLMN,
    RIL_GEOSCOPE_LOCATIONAREA,
    RIL_GEOSCOPE_CELL,
};
#define NUM_GEOGRAPHICALSCOPES  (sizeof(g_rgdwGeographicalScopes) / sizeof(DWORD))

//
// DCS Languages
//
static const DWORD g_rgdwDCSLanguagesGroup1[] =
{
    RIL_DCSLANG_GERMAN,
    RIL_DCSLANG_ENGLISH,
    RIL_DCSLANG_ITALIAN,
    RIL_DCSLANG_FRENCH,
    RIL_DCSLANG_SPANISH,
    RIL_DCSLANG_DUTCH,
    RIL_DCSLANG_SWEDISH,
    RIL_DCSLANG_DANISH,
    RIL_DCSLANG_PORTUGUESE,
    RIL_DCSLANG_FINNISH,
    RIL_DCSLANG_NORWEGIAN,
    RIL_DCSLANG_GREEK,
    RIL_DCSLANG_TURKISH,
    RIL_DCSLANG_HUNGARIAN,
    RIL_DCSLANG_POLISH,
    RIL_DCSLANG_UNKNOWN,
};

static const DWORD g_rgdwDCSLanguagesGroup2[] =
{
    RIL_DCSLANG_CZECH,
#if defined(RIL_DCSLANG_HEBREW)
    RIL_DCSLANG_HEBREW,
    RIL_DCSLANG_ARABIC,
    RIL_DCSLANG_RUSSIAN,
    RIL_DCSLANG_ICELANDIC,
#else
    RIL_DCSLANG_UNKNOWN,
    RIL_DCSLANG_UNKNOWN,
    RIL_DCSLANG_UNKNOWN,
    RIL_DCSLANG_UNKNOWN,
#endif
    RIL_DCSLANG_UNKNOWN,
    RIL_DCSLANG_UNKNOWN,
    RIL_DCSLANG_UNKNOWN,
    RIL_DCSLANG_UNKNOWN,
    RIL_DCSLANG_UNKNOWN,
    RIL_DCSLANG_UNKNOWN,
    RIL_DCSLANG_UNKNOWN,
    RIL_DCSLANG_UNKNOWN,
    RIL_DCSLANG_UNKNOWN,
    RIL_DCSLANG_UNKNOWN,
};

// Length of the data of a single CDMA parameter
#define MAX_CDMA_PARAMETER    256

// Translation table from 4-Bit-CDMA-Address (see: IS-2000.5-A)
static WCHAR g_wc4BitDigit[] = { (WCHAR)'0', (WCHAR)'1', (WCHAR)'2', (WCHAR)'3', (WCHAR)'4', (WCHAR)'5', (WCHAR)'6', (WCHAR)'7', (WCHAR)'8', (WCHAR)'9', (WCHAR)'0', (WCHAR)'*', (WCHAR)'#', (WCHAR)'0', (WCHAR)'0'};

// Translation table for CDMa message priorities.
static DWORD g_dwPriorites[] = {RIL_MSGPRIORITY_NORMAL, RIL_MSGPRIORITY_HIGH, RIL_MSGPRIORITY_URGENT, RIL_MSGPRIORITY_EMERGENCY};

// Translation table for CDMS message deliery alerts.
static DWORD g_dwAlerts[] = {RIL_MSGDISPLAYMODE_MOBILEDEFAULT, RIL_MSGDISPLAYMODE_MOBILEDEFAULT, RIL_MSGDISPLAYMODE_MOBILEDEFAULT, RIL_MSGDISPLAYMODE_IMMEDIATE};

// Translation table for CDMS message deliery alerts.
static DWORD g_dwDisplayMode[] = {RIL_MSGDISPLAYMODE_IMMEDIATE, RIL_MSGDISPLAYMODE_MOBILEDEFAULT, RIL_MSGDISPLAYMODE_USERDEFAULT, RIL_MSGDISPLAYMODE_IMMEDIATE};

//
// Constants and macros for parsing Cell Broadcast messages
//
const UINT CELLBROADCAST_HEADER_LENGTH = 6;

#define MESSAGECODE_FROM_SERIALNUMBER(sn)   ((sn >> 4) & 0x3ff)
#define GEOSCOPE_FROM_SERIALNUMBER(sn)      g_rgdwGeographicalScopes[((sn >> 14) & 0x3)]
#define UPDATENUMBER_FROM_SERIALNUMBER(sn)  (sn & 0xf)

#define PAGENUMBER(b)   ((b >> 4) & 0xf)
#define TOTALPAGES(b)   (b & 0xf)


//
// Table used to map semi-byte values to BCD characters
//
static const WCHAR g_rgwchSemiByteToBCDMap[16] = { L'0', L'1', L'2', L'3', L'4', L'5', L'6', L'7', L'8', L'9', L'*', L'#', L'a', L'b', L'c', L' ' };


//
// Convert a semi-byte into a BCD value
//
static WCHAR SemiByteToBCD(const BYTE bByte, const BOOL fHigh)
{
    // If a semi-octect is a non-integer value != 0x0f, then the follow mapppings hold.
    // If a semi-octect prior to the last octect contains 0x0f, we skip that octet and continue on.
    // GSM 03.40 section 9.1.2.3

    BYTE bSemiByte = (fHigh ? (bByte & 0xf0) >> 4 : bByte & 0x0f);
    return g_rgwchSemiByteToBCDMap[bSemiByte];
}

//
// Converts a BCD coded byte into a normal integer.
//
static UINT BCD2INT(BYTE bByte)
{
    return (bByte&0xF)+(10*(bByte>>4));
}

//
// Fill in RILADDRESS structure from BYTE array
// see GSM 03.40 section 9.1.2.5 and GSM 04.11 8.2.5.2
//
static BOOL ParseMsgAddress(const BYTE* const pbIn, const BOOL fBeforePDU, RILADDRESS& rraAddress, UINT& rcbParsed, BOOL fGSM3dot40)
{
    UINT i;
    BYTE bType;
    BYTE bNumPlan = pbIn[1] & 0x0f;
    const BYTE* pbWalk;
    LPWSTR pwchWalk;
    UINT cchUsed;
    UINT cbAddress;
    WCHAR wchBCD;
    BOOL fRet = FALSE;

    // Initalize returned values
    rcbParsed = 0;
    (void)memset(&rraAddress, 0x00, sizeof(RILADDRESS));
    rraAddress.cbSize = sizeof(RILADDRESS);

    // If address length is 0, just get out. This shouldn't happen, but we've seen it.
    if (*pbIn==0)
    {
        if (fGSM3dot40)
        {
            // GSM 03.40 section 9.2.2.1 indicates that there will always be a MINIMUM of 2 octets for the TP-OA field
            // Some SMS messages we've seen in the wild have a TP-OA Address-Length of 0, Type-of-Address of International
            // and 0 bytes of data.  Setting rcbParsed to 2 below (rather than 1) is necessary to correctly parse these
            // messages.
            rcbParsed = 2;
        }
        else
        {
            // GSM 04.11 8.2.5.2 is ambiguous about whether a zero length address will include an address type and be
            // two bytes, or will just have a length and be one byte.  However we have seen at least one case in which
            // an address did not have a type, just a length.  There are no known instances yet when it has both a type
            // and a length, so for now we are assuming it will always be just one byte.
            rcbParsed = 1;
        }
        return TRUE;
    }

    // Determine the address type
    bType = (pbIn[1] & 0x70) >> 4;

    for (i = 0; i < NUM_ADDRTYPES; i++) {
        if (g_rgbAddrTypes[i] == bType) {
            rraAddress.dwType = i;
            rraAddress.dwParams |= RIL_PARAM_A_TYPE;
            break;
        }
    }

    if ((RIL_ADDRTYPE_UNKNOWN == rraAddress.dwType) ||
        (RIL_ADDRTYPE_INTERNATIONAL == rraAddress.dwType) ||
        (RIL_ADDRTYPE_NATIONAL == rraAddress.dwType)) {
        // Determine the numbering plan
        for (i = 0; i < NUM_NUMPLANS; i++) {
            if (g_rgbNumPlans[i] == bNumPlan) {
                rraAddress.dwNumPlan = i;
                rraAddress.dwParams |= RIL_PARAM_A_NUMPLAN;
                break;
            }
        }

        // If the number plan is not one that is listed in 03.40,
        // then it is a reserved value, which we are supposed to
        // treat as 'Unknown'.
        if (RIL_PARAM_A_NUMPLAN != (rraAddress.dwParams & RIL_PARAM_A_NUMPLAN))
        {
            rraAddress.dwNumPlan = RIL_NUMPLAN_UNKNOWN;
            rraAddress.dwParams |= RIL_PARAM_A_NUMPLAN;
        }
    }

    if (rraAddress.dwType == RIL_ADDRTYPE_ALPHANUM) {
        // It seems that alphanumeric address length is the number of semi-octets as specified in GSM 03.40 section 9.1.2.5
        // so we need to determine the number of bytes from semi-octets (essentially nibbles).
        //there are two semi-octets per byte, but we need the ceiling since a straggling remainder nibble takes up a whole byte.
        // so + 1, then divide by two...
        cbAddress = (*pbIn + 1) / 2;

        if (!(ConvertToUnicode(ENCODING_GSMDEFAULT, (LPCSTR) (pbIn + 2), cbAddress, rraAddress.wszAddress,
                                 MAXLENGTH_ADDRESS-1, cchUsed))) {
           goto Error;
        }
    } else {
        if (!fBeforePDU) {
            // Calculate size of semi-octet-encoded address, per GSM 03.40 section 9.1.2.3
            cbAddress = *pbIn / 2 + *pbIn % 2;
        } else {
            cbAddress = *pbIn - 1;
        }

        pbWalk = pbIn + 2;
        pwchWalk = rraAddress.wszAddress;

        for (i = 0; i < cbAddress; i++) {
            wchBCD = SemiByteToBCD(*pbWalk, FALSE);
            if (L' ' != wchBCD) {
                *pwchWalk++ = wchBCD;
            }
            wchBCD = SemiByteToBCD(*pbWalk, TRUE);
            if (L' ' != wchBCD) {
                *pwchWalk++ = wchBCD;
            }
            pbWalk++;
        }

        // NULL-terminate the address string
        *pwchWalk = L'\0';
    }
    rraAddress.dwParams |= RIL_PARAM_A_ADDRESS;

    rcbParsed = cbAddress + 2;
    fRet = TRUE;

Error:
    return fRet;
}


#pragma warning( push )
// Warning disabled to keep code align with reference RIL.
#pragma warning(disable : 4244)
//
// Fill in SYSTEMTIME structure from BYTE array
// see GSM 03.40 section 9.2.3.11
//
static BOOL ParseMsgTimeStamp(const BYTE* const pbIn, SYSTEMTIME& rstTime, UINT& rcbParsed)
{
    UINT nYear;
    const BYTE* pbWalk = pbIn;
    UINT nOffset;
    BOOL fOffsetNegative;
    FILETIME_ULARGEINT ftuliTime; memset(&ftuliTime,0,sizeof(ftuliTime)); // zero struct
    ULARGE_INTEGER uliOffset; memset(&uliOffset,0,sizeof(uliOffset)); // zero struct
    BOOL fRet = FALSE;

    rcbParsed = 0;
    (void)memset(&rstTime, 0x00, sizeof(SYSTEMTIME));

    // Parse year
    // We don't expect to see any messages with a timestamp prior to 1991.
    // This should give plenty of buffer for abnormally early timestamps while still leaving
    // working functionality until 2090.
    nYear = (*pbWalk & 0x0f) * 10 + ((*pbWalk & 0xf0) >> 4);
    if (nYear > 90) {
        rstTime.wYear = 1900 + nYear;
    } else {
        rstTime.wYear = 2000 + nYear;
    }
    pbWalk++;

    // Parse month
    rstTime.wMonth = (*pbWalk & 0x0f) * 10 + ((*pbWalk & 0xf0) >> 4);
    pbWalk++;

    // Parse day
    rstTime.wDay = (*pbWalk & 0x0f) * 10 + ((*pbWalk & 0xf0) >> 4);
    pbWalk++;

    // Parse hours
    rstTime.wHour = (*pbWalk & 0x0f) * 10 + ((*pbWalk & 0xf0) >> 4);
    pbWalk++;

    // Parse minutes
    rstTime.wMinute = (*pbWalk & 0x0f) * 10 + ((*pbWalk & 0xf0) >> 4);
    pbWalk++;

    // Parse seconds
    rstTime.wSecond = (*pbWalk & 0x0f) * 10 + ((*pbWalk & 0xf0) >> 4);
    pbWalk++;

#if defined(TRUST_TIMEZONE_FIELD)
    /*
    The following code to adjust rstTime according to the time-zone information
    from the input is unused because most shipping GSM phones seem to ignore
    that field.  Specifically, all SMSCs seem to fill in the TP-SCTS field based
    on their *local* time and then set the offset from GMT to *0*.  Unless the
    SMSC is actually based in the GMT+0 time zone, this behavior is incorrect.
    Because our devices know which time zone they're in, if they use the time
    zone information provided, then they will calculate a time that is adjusted
    for the device's offset from the SMSC's (incorrectly reported) offset and
    will obtain a resulting time that is incorrect.  This leads to SMS messages
    appearing in Inbox with incorrect time stamps and ends up confusing users.
    */

    // Parse timezone offset (LocalTime - GMT in in quarters of an hour)
    // See GSM 03.40 ver5.8.1 ch9.2.3.11
    nOffset = ((*pbWalk & 0x07) * 10 + ((*pbWalk & 0xf0) >> 4)) * 15;
    fOffsetNegative = (*pbWalk & 0x08);
#else // defined(TRUST_TIMEZONE_FIELD)
    /*
    Until SMSCs start getting properly configured, the best option seems to be
    to effectively ignore the time zone information (as other GSM phones do).
    However, in order for us to ignore the time zone field, we actually need
    to fill in the SYSTEMTIME structure with the UTC representation of the local
    time we just parsed.  That means offsetting it by the current time zone bias
    of the device.
    */

    {
    TIME_ZONE_INFORMATION tzi;
    LONG lBias = 0;
    const DWORD dwGetTimeZoneInformationResult = GetTimeZoneInformation(&tzi);
    if(TIME_ZONE_ID_STANDARD == dwGetTimeZoneInformationResult) {
        lBias = tzi.Bias+tzi.StandardBias;
    } else if(TIME_ZONE_ID_DAYLIGHT == dwGetTimeZoneInformationResult) {
        lBias = tzi.Bias+tzi.DaylightBias;
    } else if(TIME_ZONE_ID_UNKNOWN == dwGetTimeZoneInformationResult) {
        lBias = tzi.Bias;
    } else {  // Would like to check for error with TIME_ZONE_ID_INVALID, but it's not defined by our OS headers
        RILRetailTraceZone(ZONE_ERROR, (TEXT("RilDrv: Invalid time zone\r\n")));
    }
    fOffsetNegative = (lBias < 0);
    nOffset = ((fOffsetNegative) ? (-lBias) : (lBias));
    // Now that we've used fOffsetNegative to set nOffset=abs(lBias), we need
    // to flip fOffsetNegative because we want to calculate the UTC value that
    // represents the same temporal time as the local clock time we just
    // extracted.
    fOffsetNegative = !fOffsetNegative;
    }
#endif // defined(TRUST_TIMEZONE_FIELD)
    pbWalk++;


    // Convert our local time to filetime
    if (!SystemTimeToFileTime(&rstTime, &ftuliTime.ft)) {
        goto Error;
    }

    // Convert timezone offset to filetime
    uliOffset.QuadPart = nOffset * (__int64)600000000;

    // Apply the timezone offset
    // (NOTE: the offset is LocalTime - GMT, so we need to *add* it to the LocalTime if the offset is negative,
    //        and *subtract* it if the offset is positive)
    if (fOffsetNegative) {
        ftuliTime.uli.QuadPart += uliOffset.QuadPart;
    } else {
        ftuliTime.uli.QuadPart -= uliOffset.QuadPart;
    }

    // Convert the updated filetime back to the SYSTEMTIME structure
    if (!FileTimeToSystemTime(&ftuliTime.ft, &rstTime)) {
        goto Error;
    }

    rcbParsed = pbWalk - pbIn;
    fRet = TRUE;

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


//
// Set the ProtocolID of an Incoming SMS Message
// see GSM 03.40 section 9.2.3.9
//
static BOOL ParseMsgProtocolID(const BYTE* const pbIn, DWORD& rdwProtocolID, UINT& rcbParsed)
{
    UINT i;
    BOOL fRet = FALSE;

    rcbParsed = 0;

    if ((0 == (*pbIn & 0xe0)) || (0x80 == (*pbIn & 0xc0))) {  // Reserved or unsupported values should be treated as 0x00
        rdwProtocolID = RIL_MSGPROTOCOL_SMETOSME;
        fRet = TRUE;
    } else {
        for (i = 0; i < NUM_PROTOCOLIDS; i++) {
            if (g_rgdwProtocolIDs[i] == *pbIn) {
                rdwProtocolID = i;
                fRet = TRUE;
                break;
            }
        }

        if (!fRet)
        {
            rdwProtocolID = RIL_MSGPROTOCOL_UNKNOWN;
            fRet = TRUE;
        }
    }

    if (fRet) {
        rcbParsed = 1;
    }
    return fRet;
}


//
// Set Data Coding Scheme of Incoming SMS Message
// see GSM 03.38
//
static BOOL ParseMsgDCS(const BYTE* const pbIn, RILMSGDCS& rrmdDCS, UINT& rcbParsed)
{
    BYTE bDCS = *pbIn;
    BOOL fRet = FALSE;

    rcbParsed = 0;
    (void)memset(&rrmdDCS, 0x00, sizeof(RILMSGDCS));
    rrmdDCS.cbSize = sizeof(RILMSGDCS);

    switch (bDCS & 0xf0)
    {
        case 0x00:
        case 0x10:
        case 0x20:
        case 0x30:
            rrmdDCS.dwType = RIL_DCSTYPE_GENERAL;
            rrmdDCS.dwParams |= RIL_PARAM_MDCS_TYPE;

            if (bDCS & 0x20) {
                rrmdDCS.dwFlags |= RIL_DCSFLAG_COMPRESSED;
                rrmdDCS.dwParams |= RIL_PARAM_MDCS_FLAGS;
            }

            if (bDCS & 0x10) {
                switch (bDCS & 0x03)
                {
                    case 0x00:
                        rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_0;
                        break;

                    case 0x01:
                        rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_1;
                        break;

                    case 0x02:
                        rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_2;
                        break;

                    case 0x03:
                        rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_3;
                        break;

                    default:
                        goto Error;
                }
                rrmdDCS.dwParams |= RIL_PARAM_MDCS_MSGCLASS;
            }

            switch (bDCS & 0x0c)
            {
                case 0x00:
                    rrmdDCS.dwAlphabet = RIL_DCSALPHABET_DEFAULT;
                    break;

                case 0x04:
                    rrmdDCS.dwAlphabet = RIL_DCSALPHABET_8BIT;
                    break;

                case 0x08:
#ifdef RIL_SENDMSG_DEBUG_TRACE
                    RILRetailTraceZone(ZONE_INFO, (TEXT("RilDrv: UCS2 encoded message\r\n")));
#endif
                    rrmdDCS.dwAlphabet = RIL_DCSALPHABET_UCS2;
                    break;

                default:
                    goto Error;
            }
            rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
            break;

#if defined(RIL_DCSALPHABET_KSC5601)
        // The KSC5601 character set is not part of the 3GPP 03.38 and
        // therefore not supported by the radio. So the option
        // RIL_DCSALPHABET_KSC5601 is not enabled in the RIL.
        case 0x80:
        case 0x90:
            if (bDCS == 0x84 || bDCS == 0x95) {
                rrmdDCS.dwAlphabet = RIL_DCSALPHABET_KSC5601;       // special coding scheme for KSC-5601 codede messages
                rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
            }
            else
                goto Error;

            break;
#endif

        case 0xc0:
            rrmdDCS.dwFlags |= RIL_DCSFLAG_DISCARD;
            rrmdDCS.dwParams |= RIL_PARAM_MDCS_FLAGS;
            //
            // Fall through
            //
        case 0xd0:
            rrmdDCS.dwAlphabet = RIL_DCSALPHABET_DEFAULT;
            rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
            //
            // Fall through
            //
        case 0xe0:
            rrmdDCS.dwType = RIL_DCSTYPE_MSGWAIT;
            rrmdDCS.dwParams |= RIL_PARAM_MDCS_TYPE;

            if (!(rrmdDCS.dwParams & RIL_PARAM_MDCS_ALPHABET)) {
                rrmdDCS.dwAlphabet = RIL_DCSALPHABET_UCS2;
                rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
            }

            if (bDCS & 0x08) {
                rrmdDCS.dwFlags |= RIL_DCSFLAG_INDICATIONACTIVE;
                rrmdDCS.dwParams |= RIL_PARAM_MDCS_FLAGS;
            }

            switch (bDCS & 0x03)
            {
                case 0x00:
                    rrmdDCS.dwIndication = RIL_DCSINDICATION_VOICEMAIL;
                    break;
                case 0x01:
                    rrmdDCS.dwIndication = RIL_DCSINDICATION_FAX;
                    break;
                case 0x02:
                    rrmdDCS.dwIndication = RIL_DCSINDICATION_EMAIL;
                    break;
                case 0x03:
                    rrmdDCS.dwIndication = RIL_DCSINDICATION_OTHER;
                    break;
                default:
                    goto Error;
            }
            rrmdDCS.dwParams |= RIL_PARAM_MDCS_INDICATION;
            break;

        case 0xf0:
            rrmdDCS.dwType = RIL_DCSTYPE_MSGCLASS;
            rrmdDCS.dwParams |= RIL_PARAM_MDCS_TYPE;

            if (bDCS & 0x04) {
                rrmdDCS.dwAlphabet = RIL_DCSALPHABET_8BIT;
            } else {
                rrmdDCS.dwAlphabet = RIL_DCSALPHABET_DEFAULT;
            }
            rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;

            switch (bDCS & 0x03)
            {
                case 0x00:
                    rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_0;
                    break;
                case 0x01:
                    rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_1;
                    break;
                case 0x02:
                    rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_2;
                    break;
                case 0x03:
                    rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_3;
                    break;
                default:
                    goto Error;
            }
            rrmdDCS.dwParams |= RIL_PARAM_MDCS_MSGCLASS;
            break;

        default:
            goto Error;
    }
    rcbParsed = 1;
    fRet = TRUE;

Error:
    return fRet;
}


//
// Set the Status value of a message
// see GSM 03.40 section 9.2.3.15
//
static BOOL ParseMsgStatus(const BYTE* const pbIn, DWORD& rdwStatus, UINT& rcbParsed)
{
    DWORD MsgStatus = (DWORD)*pbIn;
    DWORD Status;

    // Search the table for common status mappings
    for (Status = 1; Status < NUM_DLVSTATUS; Status++)
    {
        if (g_rgdwDlvStatus[Status] == MsgStatus)
        {
            break;
        }
    }

    // Did we find it in the table?
    if (Status<NUM_DLVSTATUS)
    {
        // Nothing to do
    }
    else if ((MsgStatus>=0x3) && (MsgStatus<=0xF))
    {
        Status = RIL_MSGDLVSTATUS_RESERVED_COMPLETED;
    }
    else if ((MsgStatus>=0x10) && (MsgStatus<=0x1F))
    {
        Status = RIL_MSGDLVSTATUS_SCSPECIFIC_COMPLETED;
    }
    else if ((MsgStatus>=0x26) && (MsgStatus<=0x2F))
    {
        Status = RIL_MSGDLVSTATUS_RESERVED_TRYING;
    }
    else if ((MsgStatus>=0x30) && (MsgStatus<=0x3F))
    {
        Status = RIL_MSGDLVSTATUS_SCSPECIFIC_TRYING;
    }
    else if ((MsgStatus>=0x4A) && (MsgStatus<=0x4F))
    {
        Status = RIL_MSGDLVSTATUS_RESERVED_ERROR;
    }
    else if ((MsgStatus>=0x50) && (MsgStatus<=0x5F))
    {
        Status = RIL_MSGDLVSTATUS_SCSPECIFIC_ERROR;
    }
    else if ((MsgStatus>=0x66) && (MsgStatus<=0x6F))
    {
        Status = RIL_MSGDLVSTATUS_RESERVED_TMPERROR;
    }
    else if ((MsgStatus>=0x70) && (MsgStatus<=0x7F))
    {
        Status = RIL_MSGDLVSTATUS_SCSPECIFIC_TMPERROR;
    }
    else
    {
        rcbParsed=0;
        return FALSE;
    }

    rdwStatus = Status;
    rcbParsed = 1;
    return TRUE;
}


//
// Set the CommandType of an Incoming SMS Message
// see GSM 03.40 section 9.2.3.21
//
static BOOL ParseMsgCommandType(const BYTE* const pbIn, DWORD& rdwCommandType, UINT& rcbParsed)
{
    UINT i;
    BOOL fRet = FALSE;

    rcbParsed = 0;

    for (i = 0; i < NUM_COMMANDTYPES; i++) {
        if (g_rgdwCommandTypes[i] == *pbIn) {
            rdwCommandType = i;
            fRet = TRUE;
            break;
        }
    }

    if (fRet) {
        rcbParsed = 1;
    }
    return fRet;
}


//
// Set Hdr data for an incoming message
//
static void ParseMsgHdr(const BYTE* const pbIn, const DWORD /*dwAlphabet*/, BYTE* const pbOut, DWORD& rcbHdrLength, UINT& rcbParsed)
{
    const BYTE* pbWalk = pbIn;

    rcbHdrLength = *pbWalk;
    pbWalk++;
    rcbParsed = 1;

    if (rcbHdrLength) {
        memcpy(pbOut, pbWalk, min(rcbHdrLength, MAXLENGTH_HDR));
        rcbParsed += min(rcbHdrLength, MAXLENGTH_HDR);
    }
}

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

//
//
//
static BOOL ParseMsgHeaderAndBody(const BYTE* const pbIn, const BYTE* const pbEnd, const DWORD dwFlags, const RILMSGDCS& rmdDataCoding, DWORD& rdwParams,
                                  BYTE* const pbHdr, BYTE* const pbMsg, DWORD& rcbHdrLength, DWORD& rcchMsgLength,
                                  UINT& rcbParsed)
{
    const BYTE* pbWalk = pbIn;
    UINT cchUserDataLength;
    DWORD dwAlphabet;
    UINT cbParsed;
    BOOL fRet = FALSE;
    DWORD dwBitsToShift = 0;

    rcbParsed = 0;

    if (rmdDataCoding.dwParams & RIL_PARAM_MDCS_ALPHABET) {
        dwAlphabet = rmdDataCoding.dwAlphabet;
    } else {
        // Default to 8-bit alphabet
        dwAlphabet = RIL_DCSALPHABET_8BIT;
    }

    // GSM 03.38 section 4 says that we don't need to parse the message body if fDiscard is set, but there's no sense losing data we've already got, so continue on regardless
    // fDiscard = (rmdDataCoding.dwParams & RIL_PARAM_MDCS_FLAGS) && (rmdDataCoding.dwFlags & RIL_DCSFLAG_DISCARD);

    // Figure out message length.  See GSM 03.40 section 9.2.3.24
    cchUserDataLength = *pbWalk++;

    // If there is less data in the buffer than is specified by the data length,
    // then abort now to avoid reading past the end of the buffer.
    if (RIL_DCSALPHABET_DEFAULT == dwAlphabet)
    {
        if (pbWalk + ((cchUserDataLength * 7 + 7) / 8) > pbEnd)
        {
            goto Error;
        }
    }
    else
    {
        if (pbWalk + cchUserDataLength > pbEnd)
        {
            RILRetailTraceZone(ZONE_ERROR, (TEXT("RilDrv: Invalid SMS user data length detected.\r\n")));
#if defined(RIL_DCSALPHABET_KSC5601)
            // If an operator uses DCS coding schemes for text messages, which are not
            // defined by GSM 03.38, the specification requires, to treat it like a 7 bit
            // coding scheme. This is a problem, on SK Telecom, which uses the KSC5601
            // coding scheme, which is not part of the GSM 03.38 and has got 16 bit. In
            // this case the radio cuts the user data. To be able to display the message
            // anyhow, the RIL now adjusts the lenght acording to the numbers of bytes
            // given by the radio.
            // Since the KSC5601 character set is not part of the 3GPP 03.38 it is not
            // supported by the radio. For this reason the option RIL_DCSALPHABET_KSC5601
            // is not enabled in the RIL.
            if (dwAlphabet == RIL_DCSALPHABET_KSC5601)
            {
                // Shorten the user data length to the actually available bytes.
                cchUserDataLength = pbEnd - pbWalk;
            }
            else
            {
                goto Error;
            }
#else
            goto Error;
#endif
        }
    }

    if (cchUserDataLength /* && !fDiscard */) {
        if (dwFlags & RIL_MSGFLAG_HEADER) {
            // Header present -- parse it
            ParseMsgHdr(pbWalk, dwAlphabet, pbHdr, rcbHdrLength, cbParsed);

            rdwParams |= (RIL_PARAM_M_HDR | RIL_PARAM_M_HDRLENGTH);
            pbWalk += cbParsed;

        } else {
            // No header present
            cbParsed = 0;
        }
        if (RIL_DCSALPHABET_DEFAULT == dwAlphabet) {
            // for phase 0 compatibility the data will start on a septet boundry
            // We want to pass it back starting at an octet boundry so we may
            // have to shift a little.
            dwBitsToShift = 7-((cbParsed * 8) % 7);
            if (dwBitsToShift == 7) dwBitsToShift = 0;
            ASSERT(((cbParsed * 8 + dwBitsToShift) % 7) == 0);
            rcchMsgLength =  cchUserDataLength - ((cbParsed * 8 + dwBitsToShift) / 7);
        } else if (RIL_DCSALPHABET_UCS2 == dwAlphabet) {
            rcchMsgLength = (cchUserDataLength - cbParsed) / 2;
        } else {
            rcchMsgLength = cchUserDataLength - cbParsed;
        }

        // Parse message data
        if (rcchMsgLength) {
            if (RIL_DCSALPHABET_DEFAULT == dwAlphabet) {
                cbParsed = (rcchMsgLength * 7 + 7 + dwBitsToShift) / 8;
            } else if (RIL_DCSALPHABET_UCS2 == dwAlphabet) {
                cbParsed = rcchMsgLength * 2;
            } else {
                cbParsed = rcchMsgLength;
            }

            UINT cbToCopy = min(cbParsed,MAXLENGTH_MSG);

            (void)memcpy(pbMsg, pbWalk, cbToCopy);
            // make sure any GSM characters start on the byte boundry, not on the septet boundry.
            if (dwBitsToShift) {
                ShiftPackedBufferNBitsRight(pbMsg, cbToCopy, dwBitsToShift);
            }
            pbWalk += cbToCopy;

            rdwParams |= (RIL_PARAM_M_MSG | RIL_PARAM_M_MSGLENGTH);
        }
    }
    fRet = TRUE;
Error:
    rcbParsed = pbWalk - pbIn;
    return fRet;
}

#pragma warning( push )
// Warning disabled to keep code align with reference RIL.
#pragma warning(disable : 4244)
//
//
//
// 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)
//
static BOOL ParseRelativeValidityPeriod(const BYTE* const pbIn, SYSTEMTIME& rstVP, UINT& rcbParsed)
{
    UINT uVP = (UINT)*pbIn;
    UINT uMinutes = 0;

    if (uVP <= 143)
    {
        uMinutes = (uVP + 1) * 5;
    }
    else if (uVP <= 167)
    {
        uMinutes = 12 * 60 + (uVP - 143) * 30;
    }
    else if (uVP <= 196)
    {
        uMinutes = (uVP - 166) * 24 * 60;
    }
    else
    {
        uMinutes = (uVP - 192) * 7 * 24 * 60;
    }

    rstVP.wDay = uMinutes / 1440;
    uMinutes = uMinutes % 1440;
    rstVP.wHour = uMinutes / 60;
    rstVP.wMinute = uMinutes % 60;

    rcbParsed = 1;
    return TRUE;
}

static BOOL ParseEnhancedValidityPeriod(const BYTE* const pbIn, SYSTEMTIME& rstVP, UINT& rcbParsed)
{
    const BYTE * pbWalk = pbIn;
    const BYTE * pbEnd = pbWalk + 7;

    // Read function from the first octet
    const BYTE bFunction = *pbWalk;

    // Advance past extended functionality indicators
    for ( ; (*pbWalk & 0x80) && (pbWalk < pbEnd); pbWalk++)
        ;

    switch (bFunction & 0x7)
    {
        case 0x0:
            // None
            break;

        case 0x1:
            // Same as relative
            if (pbWalk < pbEnd)
            {
                (void)ParseRelativeValidityPeriod(pbIn, rstVP, rcbParsed);
            }
            break;

        case 0x2:
            // In seconds
            if (pbWalk < pbEnd)
            {
                rstVP.wSecond = *pbWalk;
            }
            break;

        case 0x3:
            // Semi-octet representation of hours, minutes, and seconds
            if (pbWalk + 3 <= pbEnd)
            {
                // Parse hours
                rstVP.wHour = (*pbWalk & 0x0f) * 10 + ((*pbWalk & 0xf0) >> 4);
                pbWalk++;

                // Parse minutes
                rstVP.wMinute = (*pbWalk & 0x0f) * 10 + ((*pbWalk & 0xf0) >> 4);
                pbWalk++;

                // Parse seconds
                rstVP.wSecond = (*pbWalk & 0x0f) * 10 + ((*pbWalk & 0xf0) >> 4);
                pbWalk++;

            }
            break;

        default:
            break;
    }

    rcbParsed = 7;
    return TRUE;
}
#pragma warning( push )
// Warning disabled to keep code align with reference RIL.
#pragma warning(disable : 4815)

//
// Decode the Validity Period for a message
// see GSM 03.40 section 9.2.3.12
//
static BOOL ParseMsgValidityPeriod(const BYTE* const pbIn, const DWORD dwVPFormat, SYSTEMTIME& rstVP, UINT& rcbParsed)
{
    BOOL fRet = FALSE;

    rcbParsed = 0;

    switch (dwVPFormat)
    {
        case RIL_MSGVP_NONE:
            // Do nothing
            rcbParsed = 0;
            break;

        case RIL_MSGVP_RELATIVE:
            fRet = ParseRelativeValidityPeriod(pbIn, rstVP, rcbParsed);
            goto Error;

        case RIL_MSGVP_ENHANCED:
            fRet = ParseEnhancedValidityPeriod(pbIn, rstVP, rcbParsed);
            goto Error;

        case RIL_MSGVP_ABSOLUTE:
            // An absolute VP is the same as a normal TimeStamp, so call the appropriate function
            fRet = ParseMsgTimeStamp(pbIn, rstVP, rcbParsed);
            goto Error;

        default:
            goto Error;
    }
    fRet = TRUE;

Error:
    return fRet;
}


//
// Helper function to parse a Deliver SMS message
//
static BOOL ParseDeliverMsg(const BYTE* const pbIn, const UINT cbIn, RILMESSAGE& rrmMsg)
{
    UINT cbParsed;
    const BYTE* pbWalk;
    const BYTE* pbEnd;
    BOOL fRet = FALSE;

    pbWalk = pbIn;
    pbEnd = pbIn + cbIn;

    // Set Type.  See GSM 03.40 section 9.2.3.1
    rrmMsg.dwType = RIL_MSGTYPE_IN_DELIVER;
    rrmMsg.dwParams |= RIL_PARAM_M_TYPE;

    // Set dwFlags
    rrmMsg.dwFlags = RIL_MSGFLAG_NONE;
    rrmMsg.dwParams |= RIL_PARAM_M_FLAGS;

    // GSM 03.40 section 9.2.3.2
    if (!(*pbWalk & 0x04)) {
        rrmMsg.dwFlags |= RIL_MSGFLAG_MORETOSEND;
    }
    // GSM 03.40 section 9.2.3.4
    if (*pbWalk & 0x20) {
        rrmMsg.dwFlags |= RIL_MSGFLAG_STATUSREPORTRETURNED;
    }
    // GSM 03.40 section 9.2.3.23
    if (*pbWalk & 0x40) {
        rrmMsg.dwFlags |= RIL_MSGFLAG_HEADER;
    }
    // GSM 03.40 section 9.2.3.17
    if (*pbWalk & 0x80) {
        rrmMsg.dwFlags |= RIL_MSGFLAG_REPLYPATH;
    }
    pbWalk++;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse address
    if (!ParseMsgAddress(pbWalk, FALSE, rrmMsg.msgInDeliver.raOrigAddress, cbParsed, TRUE)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_ORIGADDRESS;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse protocol ID
    if (!ParseMsgProtocolID(pbWalk, rrmMsg.msgInDeliver.dwProtocolID, cbParsed)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_PROTOCOLID;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse DCS
    if (!ParseMsgDCS(pbWalk, rrmMsg.msgInDeliver.rmdDataCoding, cbParsed)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_DATACODING;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse time stamp
    if (!ParseMsgTimeStamp(pbWalk, rrmMsg.msgInDeliver.stSCReceiveTime, cbParsed)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_SCRECEIVETIME;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse message header and body
    if(!ParseMsgHeaderAndBody(pbWalk, pbEnd, rrmMsg.dwFlags, rrmMsg.msgInDeliver.rmdDataCoding, rrmMsg.dwParams,
                          rrmMsg.msgInDeliver.rgbHdr, rrmMsg.msgInDeliver.rgbMsg, rrmMsg.msgInDeliver.cbHdrLength,
                          rrmMsg.msgInDeliver.cchMsgLength, cbParsed)) {
        goto Error;
    }
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    if (g_dwUseReplyAddressIncoming)
    {
        BYTE bUDH[MAXLENGTH_HDR];
        UINT uiUDHSize = 0;

        if (ExtractUDHInformation(rrmMsg.msgInDeliver.rgbHdr,  rrmMsg.msgInDeliver.cbHdrLength, UDH_ELEMENTTYPE_REPLY, bUDH, MAXLENGTH_HDR, uiUDHSize))
        {
            char szReply[MAXLENGTH_ADDRESS];

            memset(szReply, 0, MAXLENGTH_ADDRESS);
            if (uiUDHSize > 2)
            {
                UINT uiNumDigits = (UINT)(bUDH[0] & 0x0F);
                UINT iPos = 0;

                while (iPos < uiNumDigits && (iPos>>1) < (uiUDHSize-1))
                {
                    if (iPos & 0x01)
                        szReply[iPos] = '0' + ((bUDH[(iPos>>1)+2]&0xF0)>>4);
                    else
                        szReply[iPos] = '0' + (bUDH[(iPos>>1)+2]&0x0F);
                    iPos++;
                }
            }

            if (strlen(szReply) > 0)
            {
                rrmMsg.msgInDeliver.raOrigAddress.dwParams = RIL_PARAM_A_ALL;
                rrmMsg.msgInDeliver.raOrigAddress.dwType = RIL_ADDRTYPE_NATIONAL;
                rrmMsg.msgInDeliver.raOrigAddress.dwNumPlan = RIL_NUMPLAN_TELEPHONE;
                (void)_snwprintfz(rrmMsg.msgInDeliver.raOrigAddress.wszAddress, MAXLENGTH_ADDRESS, L"%S", szReply);
                rrmMsg.dwParams |= RIL_PARAM_M_ORIGADDRESS;
            }
        }
    }
    fRet = TRUE;

Error:
    return fRet;
}


//
// Helper function to parse a Status Report SMS message
//
static BOOL ParseStatusMsg(const BYTE* const pbIn, const UINT cbIn, RILMESSAGE& rrmMsg)
{
    UINT cbParsed;
    BYTE bParamInd;
    const BYTE* pbWalk;
    const BYTE* pbEnd;
    BOOL fRet = FALSE;

    pbWalk = pbIn;
    pbEnd = pbIn + cbIn;

    // Set message Type.  see GSM 03.40 section 9.2.3.1
    rrmMsg.dwType = RIL_MSGTYPE_IN_STATUS;
    rrmMsg.dwParams |= RIL_PARAM_M_TYPE;

    // Set flags
    rrmMsg.dwParams |= RIL_PARAM_M_FLAGS;
    rrmMsg.dwFlags = RIL_MSGFLAG_NONE;

    if (!(*pbWalk & 0x04)) {
        // GSM 03.40 section 9.2.3.2
        rrmMsg.dwFlags |= RIL_MSGFLAG_MORETOSEND;
    }
    if (*pbWalk & 0x20) {
        // GSM 03.40 section 9.2.3.26, TP-Status-Report-Qualifier
        rrmMsg.dwFlags |= RIL_MSGFLAG_CAUSEDBYCOMMAND;
    }
    if (*pbWalk & 0x40) {
        // GSM 03.40 section 9.2.3.23
        rrmMsg.dwFlags |= RIL_MSGFLAG_HEADER;
    }
    pbWalk++;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse message reference
    rrmMsg.msgInStatus.dwTgtMsgReference = *pbWalk++;
    rrmMsg.dwParams |= RIL_PARAM_M_TGTMSGREFERENCE;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse address
    if (!ParseMsgAddress(pbWalk, FALSE, rrmMsg.msgInStatus.raTgtRecipAddress, cbParsed, TRUE)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_TGTRECIPADDRESS;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse receive time
    if (!ParseMsgTimeStamp(pbWalk, rrmMsg.msgInStatus.stTgtSCReceiveTime, cbParsed)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_TGTSCRECEIVETIME;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse discharge time
    if (!ParseMsgTimeStamp(pbWalk, rrmMsg.msgInStatus.stTgtDischargeTime, cbParsed)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_TGTDISCHARGETIME;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse message status value
    if (!ParseMsgStatus(pbWalk, rrmMsg.msgInStatus.dwTgtDlvStatus, cbParsed)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_TGTDLVSTATUS;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Check Parameter indicator, GSM 03.40 section 9.2.3.27
    if (pbWalk < pbEnd) {
        bParamInd = *pbWalk++;
        if (pbWalk > pbEnd) {
            goto Error;
        }

        // Check for ProtocolID
        if (bParamInd & 0x01) {
            if (!ParseMsgProtocolID(pbWalk, rrmMsg.msgInStatus.dwProtocolID, cbParsed)) {
                goto Error;
            }
            rrmMsg.dwParams |= RIL_PARAM_M_PROTOCOLID;
            pbWalk += cbParsed;
            if (pbWalk > pbEnd) {
                goto Error;
            }
        }

        // Check for DCS
        if (bParamInd & 0x02) {
            if (!ParseMsgDCS(pbWalk, rrmMsg.msgInStatus.rmdDataCoding, cbParsed)) {
                goto Error;
            }
            rrmMsg.dwParams |= RIL_PARAM_M_DATACODING;
            pbWalk += cbParsed;
            if (pbWalk > pbEnd) {
                goto Error;
            }
        }

        // Check for UDL
        if (bParamInd & 0x04) {
            // Parse message header and body
            if(!ParseMsgHeaderAndBody(pbWalk, pbEnd, rrmMsg.dwFlags, rrmMsg.msgInStatus.rmdDataCoding, rrmMsg.dwParams,
                                  rrmMsg.msgInStatus.rgbHdr, rrmMsg.msgInStatus.rgbMsg, rrmMsg.msgInStatus.cbHdrLength,
                                  rrmMsg.msgInStatus.cchMsgLength, cbParsed)) {
                goto Error;
            }
            pbWalk += cbParsed;
            if (pbWalk > pbEnd) {
                goto Error;
            }
        }
    }
    fRet = TRUE;

Error:
    return fRet;
}


//
// Parse a Submit message
//
static BOOL ParseSubmitMsg(const BYTE* const pbIn, const UINT cbIn, RILMESSAGE& rrmMsg)
{
    UINT cbParsed;
    const BYTE* pbWalk;
    const BYTE* pbEnd;
    BOOL fRet = FALSE;

    pbWalk = pbIn;
    pbEnd = pbIn + cbIn;

    // Set Type.  see GSM 03.40 section 9.2.3.1
    rrmMsg.dwParams |= RIL_PARAM_M_TYPE;
    rrmMsg.dwType = RIL_MSGTYPE_OUT_SUBMIT;

    // Set dwFlags
    rrmMsg.dwParams |= RIL_PARAM_M_FLAGS;
    rrmMsg.dwFlags = RIL_MSGFLAG_NONE;

    if (*pbWalk & 0x04) {
        // GSM 03.40 section 9.2.3.25
        rrmMsg.dwFlags |= RIL_MSGFLAG_REJECTDUPS;
    }
    if (*pbWalk & 0x20) {
        // GSM 03.40 section 9.2.3.5
        rrmMsg.dwFlags |= RIL_MSGFLAG_STATUSREPORTREQUESTED;
    }
    if (*pbWalk & 0x40) {
        // GSM 03.40 section 9.2.3.23
        rrmMsg.dwFlags |= RIL_MSGFLAG_HEADER;
    }
    if (*pbWalk & 0x80) {
        // GSM 03.40 sectoin 9.2.3.17
        rrmMsg.dwFlags |= RIL_MSGFLAG_REPLYPATH;
    }

    // Parse Validity Period Format.  see GSM 03.40 section 9.2.3.3
    switch((*pbWalk & 0x18) >> 3)
    {
        case 0x00:
            rrmMsg.msgOutSubmit.dwVPFormat = RIL_MSGVP_NONE;
            break;

        case 0x01:
            rrmMsg.msgOutSubmit.dwVPFormat = RIL_MSGVP_ENHANCED;
            break;

        case 0x02:
            rrmMsg.msgOutSubmit.dwVPFormat = RIL_MSGVP_RELATIVE;
            break;

        case 0x03:
            rrmMsg.msgOutSubmit.dwVPFormat = RIL_MSGVP_ABSOLUTE;
            break;

        default:
            goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_VPFORMAT;
    pbWalk++;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Skip the message reference
    pbWalk++;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse address
    if (!ParseMsgAddress(pbWalk, FALSE, rrmMsg.msgOutSubmit.raDestAddress, cbParsed, TRUE)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_DESTADDRESS;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse protocol ID
    if (!ParseMsgProtocolID(pbWalk, rrmMsg.msgOutSubmit.dwProtocolID, cbParsed)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_PROTOCOLID;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse DCS
    if (!ParseMsgDCS(pbWalk, rrmMsg.msgOutSubmit.rmdDataCoding, cbParsed)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_DATACODING;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse validity period
    if (!ParseMsgValidityPeriod(pbWalk, rrmMsg.msgOutSubmit.dwVPFormat, rrmMsg.msgOutSubmit.stVP, cbParsed)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_VP;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse message header and body
    if(!ParseMsgHeaderAndBody(pbWalk, pbEnd, rrmMsg.dwFlags, rrmMsg.msgOutSubmit.rmdDataCoding, rrmMsg.dwParams,
                          rrmMsg.msgOutSubmit.rgbHdr, rrmMsg.msgOutSubmit.rgbMsg, rrmMsg.msgOutSubmit.cbHdrLength,
                          rrmMsg.msgOutSubmit.cchMsgLength, cbParsed)) {
        goto Error;
    }
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }
    fRet = TRUE;

Error:
    return fRet;
}


//
// Parse an SMS-COMMAND message
//
static BOOL ParseCommandMsg(const BYTE* const pbIn, const UINT cbIn, RILMESSAGE& rrmMsg)
{
    UINT cbParsed;
    const BYTE* pbWalk;
    const BYTE* pbEnd;
    BOOL fRet = FALSE;

    pbWalk = pbIn;
    pbEnd = pbIn + cbIn;

    // Set Type.  see GSM 03.40 section 9.2.3.1
    rrmMsg.dwParams |= RIL_PARAM_M_TYPE;
    rrmMsg.dwType = RIL_MSGTYPE_OUT_COMMAND;

    // Set dwFlags
    rrmMsg.dwParams |= RIL_PARAM_M_FLAGS;
    rrmMsg.dwFlags = RIL_MSGFLAG_NONE;

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

    // Skip the message reference
    pbWalk++;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse protocol ID
    if (!ParseMsgProtocolID(pbWalk, rrmMsg.msgOutCommand.dwProtocolID, cbParsed)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_PROTOCOLID;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse command type
    if (!ParseMsgCommandType(pbWalk, rrmMsg.msgOutCommand.dwCommandType, cbParsed)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_COMMANDTYPE;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse the target message reference
    rrmMsg.msgOutCommand.dwTgtMsgReference = *pbWalk++;
    rrmMsg.dwParams |= RIL_PARAM_M_TGTMSGREFERENCE;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse address
    if (!ParseMsgAddress(pbWalk, FALSE, rrmMsg.msgOutCommand.raDestAddress, cbParsed, TRUE)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_DESTADDRESS;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse command data length
    rrmMsg.msgOutCommand.cbCmdLength = *pbWalk++;
    rrmMsg.dwParams |= RIL_PARAM_M_CMDLENGTH;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse command data
    (void)memcpy(rrmMsg.msgOutCommand.rgbCmd, pbWalk, min(rrmMsg.msgOutCommand.cbCmdLength, MAXLENGTH_CMD));
    rrmMsg.dwParams |= RIL_PARAM_M_CMD;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }
    fRet = TRUE;

Error:
    return fRet;
}


//
// Parse an Incoming SMS GSM message into a RILMESSAGE structure
//
HRESULT ParseSMSMessage(const LPCSTR sIn, const UINT cbIn, BOOL fIncoming, BOOL fPrependedSMSC, RILMESSAGE& rrmMsg)
{
    BYTE* pbGSMBytes = NULL;
    UINT cbGSMBytes;
    UINT cbParsed = 0;
    BYTE bMsgType;
    BOOL fRet = TRUE;
    HRESULT hr = S_OK;

    // Zero out the message structure
    (void)memset(&rrmMsg, 0x00, sizeof(RILMESSAGE));
    rrmMsg.cbSize = sizeof(RILMESSAGE);

    // HW-SPECIFIC: WaveCom Series 2 hardware doesn't encode the SMSC address prepended to a message

    if ('+' == *sIn) {
        UINT i;
        LPWSTR pwchAddress;
        LPCSTR pchIn;
        LPCSTR sAfterSMSCAddress;
        UINT cbAfterSMSCAddress;

        // Assume the type and numplan
        rrmMsg.raSvcCtrAddress.dwType = RIL_ADDRTYPE_INTERNATIONAL;
        rrmMsg.raSvcCtrAddress.dwNumPlan = RIL_NUMPLAN_TELEPHONE;

        // Copy the phone number over
        pwchAddress = rrmMsg.raSvcCtrAddress.wszAddress;
        pchIn = sIn + 1;
        for (i = 0; i < SMS_BOGUS_ADDR_LENGTH ; i++) {
            *pwchAddress++ = *pchIn & 0x00ff;
        }
        *pwchAddress = L'\0';

        // Set approproiate param flags
        rrmMsg.raSvcCtrAddress.dwParams |= (RIL_PARAM_A_TYPE | RIL_PARAM_A_NUMPLAN | RIL_PARAM_A_ADDRESS);
        rrmMsg.dwParams |= RIL_PARAM_M_SVCCTRADDRESS;

        // Convert the rest of the message from GSM default HEX represention into GSM default
        sAfterSMSCAddress = sIn + SMS_BOGUS_ADDR_LENGTH;
        cbAfterSMSCAddress = cbIn - SMS_BOGUS_ADDR_LENGTH;
        pbGSMBytes = new BYTE[cbAfterSMSCAddress/ 2 + 1];
        if (!pbGSMBytes) {
            hr = E_OUTOFMEMORY;
            goto Error;
        }
        if (!GSMHexToGSM(sAfterSMSCAddress, cbAfterSMSCAddress, (LPSTR)pbGSMBytes, cbAfterSMSCAddress / 2 + 1, cbGSMBytes)) {
            hr = E_FAIL;
            goto Error;
        }
    } else {

        // Convert the whole message from GSM default HEX represention into GSM default
        pbGSMBytes = new BYTE[cbIn / 2 + 1];
        if (!pbGSMBytes) {
            hr = E_OUTOFMEMORY;
            goto Error;
        }
        if (!GSMHexToGSM(sIn, cbIn, (LPSTR)pbGSMBytes, cbIn / 2 + 1, cbGSMBytes)) {
            hr = E_FAIL;
            goto Error;
        }

        if (fPrependedSMSC)
        {
            // Grab the prepended SMSC address
            if (!ParseMsgAddress(pbGSMBytes, TRUE, rrmMsg.raSvcCtrAddress, cbParsed, FALSE)) {
                hr = E_FAIL;
                goto Error;
            }
            rrmMsg.dwParams |= RIL_PARAM_M_SVCCTRADDRESS;
        }

    }

    // Mask the bottom two bits to get the message type (GSM 03.40 section 9.2.3.1)
    bMsgType = *(pbGSMBytes + cbParsed) & 0x03;
    switch (bMsgType)
    {
        case 0x00:  // SMS-DELIVER or SMS-DELIVER-REPORT
            if (!fIncoming) {
                // We don't support SMS-DELIVER-REPORTs
                hr = E_FAIL;
                goto Error;
            }
            fRet = ParseDeliverMsg(pbGSMBytes + cbParsed, cbGSMBytes - cbParsed, rrmMsg);
            break;

        case 0x01:  // SMS-SUBMIT or SMS-SUBMIT-REPORT
            if (fIncoming) {
                // We don't support SMS-SUBMIT-REPORTs
                hr = E_FAIL;
                goto Error;
            }
            fRet = ParseSubmitMsg(pbGSMBytes + cbParsed, cbGSMBytes - cbParsed, rrmMsg);
            break;

        case 0x02:  // SMS-STATUS-REPORT or SMS_COMMAND
            if (fIncoming) {
                fRet = ParseStatusMsg(pbGSMBytes + cbParsed, cbGSMBytes - cbParsed, rrmMsg);
            } else {
                fRet = ParseCommandMsg(pbGSMBytes + cbParsed, cbGSMBytes - cbParsed, rrmMsg);
            }
            break;

        default:
            hr = E_FAIL;
            goto Error;
    }

    if (!fRet) {
        hr = E_FAIL;
    }

Error:
    delete[] pbGSMBytes;
    return hr;
}

//
// readBitwiseFromBuffer
// Reads <iBits> bits from <bBuffer> (length: <len>) at bit position
// <piBitPos>. <piBitPos> get increased after data have been read.
//
static UINT ReadBitwiseFromBuffer(BYTE *bBuffer, INT iLen, INT iBits, INT *piBitPos)
{
    INT bitPos = *piBitPos;
    INT index = bitPos >> 3;
    INT offset = 16 - (bitPos & 0x07) - iBits;

    if ((iBits < 0) || (iBits > 8) || ((bitPos + iBits) > iLen * 8)) {
        RILRetailTraceZone(ZONE_ERROR, (TEXT("RilDrv: ReadBitwiseFromBuffer() - illegal read\r\n")));
        return 0;
    }

    UINT uiData = (bBuffer[index] & 0xFF) << 8;
    if (offset < 8) uiData |= bBuffer[index + 1] & 0xFF;
    uiData >>= offset;
    uiData &= (0xFFFF >> (16 - iBits));
    *piBitPos += iBits;

    return uiData;
}

static BOOL ReadCDMAParameter(BYTE *pbBuffer, UINT uiBufferLen, UINT *piBufferPos, BYTE *pbParameter, UINT uiParamBufferLen, UINT *uiParamID, UINT *uiParamLen)
{
    BOOL fRet = FALSE;

    if (*piBufferPos + 2 > uiBufferLen) {
        goto Error;
    }
    *uiParamID = pbBuffer[*piBufferPos];
    *uiParamLen = pbBuffer[*piBufferPos + 1];
    if (*piBufferPos + 2 + *uiParamLen > uiBufferLen) {
        RILRetailTraceZone(ZONE_ERROR, (TEXT("RilDrv: ReadCDMAParameter() - incomplete parameter in buffer\r\n")));
        goto Error;
    }
    if (*uiParamLen > uiParamBufferLen) {
        RILRetailTraceZone(ZONE_ERROR, (TEXT("RilDrv: ReadCDMAParameter() - cannot store entire parameter\r\n")));
        goto Error;
    }
    memcpy(pbParameter, pbBuffer + *piBufferPos + 2, *uiParamLen);
    *piBufferPos = *piBufferPos + 2 + *uiParamLen;
    fRet = TRUE;
Error:
    return fRet;
}

//
// Decode CDMA address, see specification 3GPP2 C.S0015-A
// chapter 3.4.3.3 for details.
// If fCallBackAddress is TRUE, use call back address coding
// according chapter 4.5.15 instead of 3.4.3.3.
//
static BOOL DecodeCdmaAddress(RILADDRESS *pAddress, BYTE *bParameter, INT iParamLen, BOOL fCallBackAddress)
{
    INT iBitPos = 0;
    INT iDigitMode;
    INT iNumberMode = 0;
    INT iNumberType = 0;
    INT iNumberPlan = 0;
    INT iNumFields;
    INT iDigit;
    INT i;

    memset(pAddress, 0, sizeof(RILADDRESS));
    pAddress->cbSize = sizeof(RILADDRESS);

    iDigitMode = ReadBitwiseFromBuffer(bParameter, iParamLen, 1, &iBitPos);
    if (fCallBackAddress) {
        if (iDigitMode == 1) {
            iNumberType = ReadBitwiseFromBuffer(bParameter, iParamLen, 3, &iBitPos);
            iNumberMode = 1;
        }
    }
    else {
        iNumberMode = ReadBitwiseFromBuffer(bParameter, iParamLen, 1, &iBitPos);
        if (iNumberMode == 1)
            iNumberType = ReadBitwiseFromBuffer(bParameter, iParamLen, 3, &iBitPos);
    }
    if (iNumberMode == 1) {
        iNumberPlan = ReadBitwiseFromBuffer(bParameter, iParamLen, 4, &iBitPos);
        pAddress->dwNumPlan = iNumberPlan;
        pAddress->dwParams |= RIL_PARAM_A_NUMPLAN;
    }
    iNumFields = ReadBitwiseFromBuffer(bParameter, iParamLen, 8, &iBitPos);
    if (iNumFields > 0)
        pAddress->dwParams |= RIL_PARAM_A_ADDRESS;
    if (iDigitMode == 1) {     // 8-Bit
        for (i = 0; i < iNumFields; i++) {
            iDigit = ReadBitwiseFromBuffer(bParameter, iParamLen, 8, &iBitPos);
            if (i < MAXLENGTH_ADDRESS) {
                pAddress->wszAddress[i] = (WCHAR)iDigit;
            }
        }
    }
    else {       // 4-Bit
        for (i = 0; i < iNumFields; i++) {
            iDigit = ReadBitwiseFromBuffer(bParameter, iParamLen, 4, &iBitPos);
            if (i < MAXLENGTH_ADDRESS) {
                pAddress->wszAddress[i] = g_wc4BitDigit[iDigit & 0xF];
            }
        }
    }
    return TRUE;
}

//
// Decode CDMA bearer data, see specification 3GPP2 C.S0015-A
// chapter 3.4.3.7 for details.
//
static BOOL DecodeCdmaBearer(RILMESSAGE *pMessage, BYTE *bBearer, INT iBearerLen)
{
    UINT iBufferPos = 0;
    BYTE bParameter[MAX_CDMA_PARAMETER];
    UINT uiParamID;
    UINT uiParamLen;
    UINT uiMessageID;
    UINT uiMessageCoding;
    UINT uiMessageType = 0;
    UINT uiNumFields;
    INT iBitPos;
    INT iBits;
    INT iMultiplier = 1;
    UINT i;
    BOOL fLatinToUnicode = FALSE;
    BOOL fLatinToAscii = FALSE;

    while (ReadCDMAParameter(bBearer, iBearerLen, &iBufferPos, bParameter, MAX_CDMA_PARAMETER, &uiParamID, &uiParamLen)) {
        switch (uiParamID) {
            case 0:    // Message Identifier (4.5.1)
                if (uiParamLen >= 2) {
                    uiMessageID = bParameter[0] << 8 | bParameter[1];
                    if (uiMessageID > 0) {
                        pMessage->msgIS637InDeliver.dwMsgID = uiMessageID;
                        pMessage->dwParams |= RIL_PARAM_M_MSGID;
                    }
                }
                break;
            case 1:   // User Data (4.5.2)
                iBitPos = 0;
                uiMessageCoding = ReadBitwiseFromBuffer(bParameter, uiParamLen, 5, &iBitPos);
                if (uiMessageCoding == 2) {
                    pMessage->msgIS637InDeliver.dwMsgEncoding = RIL_MSGCODING_7BITASCII;
                    iBits = 7;
                    iMultiplier = 1;
                }
                else if (uiMessageCoding == 4) {
                    pMessage->msgIS637InDeliver.dwMsgEncoding = RIL_MSGCODING_UNICODE;
                    iBits = 8;
                    iMultiplier = 2;  // Actually Unicode uses 16 bits, so we have to multiply the numbers of characters to get the numbers of bytes
                }
                else if (uiMessageCoding == 8) {
                    DWORD dwReg = 0;

                    // Problem: Windows Mobile doesn't know the coding "Latin" (ISO 8859-1).
                    // There are 2 ways to deal with this situation:
                    // Transform to ASCII: this works with standard Windows Mobile, but all
                    //      character 80h to FFh are lost.
                    // Transform to Unicode: all characters can be transformed, but standard
                    //      Windows Mobile doesn't supprt Unicode for CDMA.
                    (void)GetRegistryDWORD(HKEY_LOCAL_MACHINE, g_tszRegKeyRIL, TEXT("EncodeCdmaLatinAsUnicode"), &dwReg);

                    if (dwReg == 1) {
                        pMessage->msgIS637InDeliver.dwMsgEncoding = RIL_MSGCODING_UNICODE;
                        iBits = 8;
                        iMultiplier = 2;
                        fLatinToUnicode = TRUE;
                    }
                    else {
                        pMessage->msgIS637InDeliver.dwMsgEncoding = RIL_MSGCODING_7BITASCII;
                        iBits = 8;
                        iMultiplier = 1;
                        fLatinToAscii = TRUE;
                    }
                }
                else {
                    RILRetailTraceZone(ZONE_ERROR, (TEXT("RilDrv: DecodeCdmaBearer() - unkonwn message coding %d\r\n"), uiMessageCoding));
                    return FALSE;
                }
                if (uiMessageCoding == 1)
                    uiMessageType = ReadBitwiseFromBuffer(bParameter, uiParamLen, 8, &iBitPos);
                pMessage->dwParams |= RIL_PARAM_M_ENCODING;
                uiNumFields = ReadBitwiseFromBuffer(bParameter, uiParamLen, 8, &iBitPos) * iMultiplier;
                if (uiNumFields > 0)
                    pMessage->dwParams |= RIL_PARAM_M_MSG;
                for (i = 0; i < uiNumFields; i++) {
                    if (!fLatinToUnicode || (i&1)) {
                        UINT uiData = ReadBitwiseFromBuffer(bParameter, uiParamLen, iBits, &iBitPos);

                        // Transform Latin to ASCII: character 80h to FFh cannot be encoded,
                        // so they are replaced with blanks.
                        if (fLatinToAscii && uiData >= 0x80)
                            uiData = 32;   // <Space> character

                        pMessage->msgIS637InDeliver.rgbMsg[i] = (BYTE)uiData;
                    }
                    else {
                        // Transform to Unicode: set high byte of Unicode word to 0.
                        pMessage->msgIS637InDeliver.rgbMsg[i] = 0;
                    }
                }
                pMessage->msgIS637InDeliver.cchMsgLength = uiNumFields;
                pMessage->dwParams |= RIL_PARAM_M_MSGLENGTH;
                break;
            case 3:   // Message Center Timestamp (4.5.4)
                if (uiParamLen >= 6) {
                    // Year range is from 1996 to 2095
                    pMessage->msgIS637InDeliver.stSCReceiveTime.wYear = 2000+BCD2INT(bParameter[0]);
                    if (pMessage->msgIS637InDeliver.stSCReceiveTime.wYear > 2095)
                        pMessage->msgIS637InDeliver.stSCReceiveTime.wYear -= 100;
                    pMessage->msgIS637InDeliver.stSCReceiveTime.wMonth = BCD2INT(bParameter[1]);
                    pMessage->msgIS637InDeliver.stSCReceiveTime.wDay = BCD2INT(bParameter[2]);
                    pMessage->msgIS637InDeliver.stSCReceiveTime.wHour = BCD2INT(bParameter[3]);
                    pMessage->msgIS637InDeliver.stSCReceiveTime.wMinute = BCD2INT(bParameter[4]);
                    pMessage->msgIS637InDeliver.stSCReceiveTime.wSecond = BCD2INT(bParameter[5]);
                    pMessage->dwParams |= RIL_PARAM_M_SCRECEIVETIME;
                }
                break;
            case 8:   // Message Priority (4.5.9)
                if (uiParamLen >= 1) {
                    UINT uiPriority = bParameter[0];

                    if (uiPriority< 4) {
                        pMessage->msgIS637InDeliver.dwMsgPriority = g_dwPriorites[uiPriority];
                        pMessage->dwParams |= RIL_PARAM_M_PRIORITY;
                    }
                }
                break;
            case 11:   // Number of Message (4.5.12)
                if (uiParamLen >= 1) {
                    UINT uiNumMsg = bParameter[0];

                    // Number of messages is in BCD format.
                    pMessage->msgIS637InDeliver.dwNumMsgs = (uiNumMsg & 0xF) + 10 * ((uiNumMsg >> 4) & 0xF);
                    pMessage->dwParams |= RIL_PARAM_M_NUMMSGS;
                }
                break;
            case 12:   // Alert on Message Delivery (4.5.13)
                if (uiParamLen >= 1) {
                    UINT uiAlert = bParameter[0] >> 6;

                    if (uiAlert < 4) {
                        pMessage->msgIS637InDeliver.dwMsgDisplayMode = g_dwAlerts[uiAlert];
                        pMessage->dwParams |= RIL_PARAM_M_DISPLAYMODE;
                    }
                }
                break;
            case 14:   // Decode Callback number (4.5.15)
                if (DecodeCdmaAddress(&pMessage->msgIS637InDeliver.raCallBackNumber, bParameter, uiParamLen, TRUE))
                    pMessage->dwParams |= RIL_PARAM_M_CALLBACKNUM;
                break;
            case 15:   // Message Display Mode (4.5.16)
                if (uiParamLen >= 1) {
                    UINT uiDisplayMode = bParameter[0] >> 6;

                    if (uiDisplayMode < 4) {
                        pMessage->msgIS637InDeliver.dwMsgDisplayMode = g_dwDisplayMode[uiDisplayMode];
                        pMessage->dwParams |= RIL_PARAM_M_DISPLAYMODE;
                    }
                }
                break;
            default:
                // Windows Mobile doesn't support all CDMA parameter IDs.
                RILRetailTraceZone(ZONE_INFO, (TEXT("RilDrv: DecodeCdmaBearer() - unkonwn parameter %d\r\n"), uiParamID));
        }
    }

    if ((pMessage->dwParams & RIL_PARAM_M_NUMMSGS) && pMessage->msgIS637InDeliver.dwTeleservice == RIL_MSGTELESERVICE_VOICEMAIL) {
        DWORD dwReg = 0;

        // The voice mail indication might contain a text. The RIL can
        // delete this text. If the text is needed, set the registry key
        // "DeleteTextOnCdmaVoiceMail" to 0 (default), otherwise to 1.
        (void)GetRegistryDWORD(HKEY_LOCAL_MACHINE, g_tszRegKeyRIL, TEXT("DeleteTextOnCdmaVoiceMail"), &dwReg);
        if (dwReg == 1) {
            pMessage->dwParams &= ~RIL_PARAM_M_MSG;
            pMessage->msgIS637InDeliver.cchMsgLength = 0;
            pMessage->dwParams &= ~RIL_PARAM_M_MSGLENGTH;
            pMessage->msgIS637InDeliver.dwMsgEncoding = 0;
            pMessage->dwParams &= ~RIL_PARAM_M_ENCODING;
            memset(&(pMessage->msgIS637InDeliver.raOrigAddress), 0, sizeof(RILADDRESS));
            pMessage->dwParams &= ~RIL_PARAM_M_ORIGADDRESS;
            pMessage->msgIS637InDeliver.dwMsgPriority = 0;
            pMessage->dwParams &= ~RIL_PARAM_M_PRIORITY;
            pMessage->msgIS637InDeliver.dwMsgDisplayMode = RIL_MSGDISPLAYMODE_MOBILEDEFAULT;
            pMessage->dwParams &= ~RIL_PARAM_M_DISPLAYMODE;
            memset(&(pMessage->msgIS637InDeliver.stSCReceiveTime), 0, sizeof(SYSTEMTIME));
            pMessage->dwParams &= ~RIL_PARAM_M_SCRECEIVETIME;
            pMessage->dwParams &= ~RIL_PARAM_M_FLAGS;
        }
        // The message ID is not relevant for voice mail and must be set to 0 (see chapter 4.5.1, field MESSAGE_ID).
        pMessage->msgIS637InDeliver.dwMsgID = 0;
    }
    return TRUE;
}

//
// Decode CDMA bearer data for delivery status reports, see specification 3GPP2 C.S0015-A
// chapter 3.4.3.7 for details.
//
static BOOL DecodeCdmaDsBearer(RILMESSAGE *pMessage, BYTE *bBearer, INT iBearerLen)
{
    UINT iBufferPos = 0;
    BYTE bParameter[MAX_CDMA_PARAMETER];
    UINT uiParamID;
    UINT uiParamLen;
    UINT uiMessageID;
    UINT uiMessageCoding;
    UINT uiMessageType = 0;
    UINT uiNumFields;
    INT iBitPos;
    INT iBits;
    INT iMultiplier = 1;
    UINT i;
    BOOL fLatinToUnicode = FALSE;
    BOOL fLatinToAscii = FALSE;

    while (ReadCDMAParameter(bBearer, iBearerLen, &iBufferPos, bParameter, MAX_CDMA_PARAMETER, &uiParamID, &uiParamLen)) {
        switch (uiParamID) {
            case 0:    // Message Identifier (4.5.1)
                if (uiParamLen >= 2) {
                    uiMessageID = bParameter[0] << 8 | bParameter[1];
                    if (uiMessageID > 0) {
                        pMessage->msgIS637InStatus.dwMsgID = uiMessageID;
                        pMessage->dwParams |= RIL_PARAM_M_MSGID;
                    }
                }
                break;
            case 1:   // User Data (4.5.2)
                iBitPos = 0;
                uiMessageCoding = ReadBitwiseFromBuffer(bParameter, uiParamLen, 5, &iBitPos);
                if (uiMessageCoding == 2) {
                    pMessage->msgIS637InStatus.dwMsgEncoding = RIL_MSGCODING_7BITASCII;
                    iBits = 7;
                    iMultiplier = 1;
                }
                else if (uiMessageCoding == 4) {
                    pMessage->msgIS637InStatus.dwMsgEncoding = RIL_MSGCODING_UNICODE;
                    iBits = 8;
                    iMultiplier = 2;  // Actually Unicode uses 16 bits, so we have to multiply the numbers of characters to get the numbers of bytes
                }
                else if (uiMessageCoding == 8) {
                    DWORD dwReg = 0;

                    // Problem: Windows Mobile doesn't know the coding "Latin" (ISO 8859-1).
                    // There are 2 ways to deal with this situation:
                    // Transform to ASCII: this works with standard Windows Mobile, but all
                    //      character 80h to FFh are lost.
                    // Transform to Unicode: all characters can be transformed, but standard
                    //      Windows Mobile doesn't supprt Unicode for CDMA.
                    (void)GetRegistryDWORD(HKEY_LOCAL_MACHINE, g_tszRegKeyRIL, TEXT("EncodeCdmaLatinAsUnicode"), &dwReg);

                    if (dwReg == 1) {
                        pMessage->msgIS637InStatus.dwMsgEncoding = RIL_MSGCODING_UNICODE;
                        iBits = 8;
                        iMultiplier = 2;
                        fLatinToUnicode = TRUE;
                    }
                    else {
                        pMessage->msgIS637InStatus.dwMsgEncoding = RIL_MSGCODING_7BITASCII;
                        iBits = 8;
                        iMultiplier = 1;
                        fLatinToAscii = TRUE;
                    }
                }
                else {
                    RILRetailTraceZone(ZONE_ERROR, (TEXT("RilDrv: DecodeCdmaBearer() - unkonwn message coding %d\r\n"), uiMessageCoding));
                    return FALSE;
                }
                if (uiMessageCoding == 1)
                    uiMessageType = ReadBitwiseFromBuffer(bParameter, uiParamLen, 8, &iBitPos);
                pMessage->dwParams |= RIL_PARAM_M_ENCODING;
                uiNumFields = ReadBitwiseFromBuffer(bParameter, uiParamLen, 8, &iBitPos) * iMultiplier;
                if (uiNumFields > 0)
                    pMessage->dwParams |= RIL_PARAM_M_MSG;
                for (i = 0; i < uiNumFields; i++) {
                    if (!fLatinToUnicode || (i&1)) {
                        UINT uiData = ReadBitwiseFromBuffer(bParameter, uiParamLen, iBits, &iBitPos);

                        // Transform Latin to ASCII: character 80h to FFh cannot be encoded,
                        // so they are replaced with blanks.
                        if (fLatinToAscii && uiData >= 0x80)
                            uiData = 32;   // <Space> character

                        pMessage->msgIS637InStatus.rgbMsg[i] = (BYTE)uiData;
                    }
                    else {
                        // Transform to Unicode: set high byte of Unicode word to 0.
                        pMessage->msgIS637InStatus.rgbMsg[i] = 0;
                    }
                }
                pMessage->msgIS637InStatus.cchMsgLength = uiNumFields;
                pMessage->dwParams |= RIL_PARAM_M_MSGLENGTH;
                break;
            case 2:    // User Response Code (4.5.3)
                if (uiParamLen >= 1) {
                    UINT uiResponseCode = bParameter[0];

                    pMessage->msgIS637InStatus.dwUserResponseCode = uiResponseCode;
                    pMessage->dwParams |= RIL_PARAM_M_USERRESPONSECODE;
                }
                break;
            case 3:   // Message Center Timestamp (4.5.4)
                if (uiParamLen >= 6) {
                    // Year range is from 1996 to 2095
                    pMessage->msgIS637InStatus.stSCReceiveTime.wYear = 2000+BCD2INT(bParameter[0]);
                    if (pMessage->msgIS637InStatus.stSCReceiveTime.wYear > 2095)
                        pMessage->msgIS637InStatus.stSCReceiveTime.wYear -= 100;
                    pMessage->msgIS637InStatus.stSCReceiveTime.wMonth = BCD2INT(bParameter[1]);
                    pMessage->msgIS637InStatus.stSCReceiveTime.wDay = BCD2INT(bParameter[2]);
                    pMessage->msgIS637InStatus.stSCReceiveTime.wHour = BCD2INT(bParameter[3]);
                    pMessage->msgIS637InStatus.stSCReceiveTime.wMinute = BCD2INT(bParameter[4]);
                    pMessage->msgIS637InStatus.stSCReceiveTime.wSecond = BCD2INT(bParameter[5]);
                    pMessage->dwParams |= RIL_PARAM_M_SCRECEIVETIME;
                }
                break;
            case 8:   // Message Priority (4.5.9)
                if (uiParamLen >= 1) {
                    UINT uiPriority = bParameter[0];

                    RILRetailTraceZone(ZONE_INFO, (TEXT("RilDrv: DecodeCdmaDsBearer() - unused parameter priority = %d\r\n"), uiPriority));
                }
                break;
            case 12:   // Alert on Message Delivery (4.5.13)
                if (uiParamLen >= 1) {
                    UINT uiAlert = bParameter[0] >> 6;

                    RILRetailTraceZone(ZONE_INFO, (TEXT("RilDrv: DecodeCdmaDsBearer() - unused parameter alert = %d\r\n"), uiAlert));
                }
                break;
            default:
                // Windows Mobile doesn't support all CDMA parameter IDs.
                RILRetailTraceZone(ZONE_INFO, (TEXT("RilDrv: DecodeCdmaDsBearer() - unkonwn parameter %d\r\n"), uiParamID));
        }
    }
    return TRUE;
}

//
// Parse an Incoming CDMA SMS message into a RILMESSAGE structure
//
HRESULT ParseSMSCDMAMessage(const LPCSTR sIn, const UINT cbIn, BOOL /*fIncoming*/, RILMESSAGE& rrmMsg)
{
    BYTE* pbCDMABytes = NULL;
    UINT cbCDMABytes;
    HRESULT hr = S_OK;
    BYTE bParameter[MAX_CDMA_PARAMETER];
    UINT uiParamID;
    UINT uiParamLen;
    UINT iBufferPos = 0;
    UINT uiTeleService;

    // Zero out the message structure
    (void)memset(&rrmMsg, 0x00, sizeof(RILMESSAGE));
    rrmMsg.cbSize = sizeof(RILMESSAGE);
    // Convert the whole message from GSM default HEX represention into GSM default
    pbCDMABytes = new BYTE[cbIn / 2 + 1];
    if (!pbCDMABytes) {
        hr = E_OUTOFMEMORY;
        goto Error;
    }
    if (!GSMHexToGSM(sIn, cbIn, (LPSTR)pbCDMABytes, cbIn / 2 + 1, cbCDMABytes)) {
        hr = E_FAIL;
        goto Error;
    }

    // See specification 3GPP2 C.S0015-A
    // Check if message got the type Point-to-Point
    if (pbCDMABytes[iBufferPos] !=0) {
        RILRetailTraceZone(ZONE_ERROR, (TEXT("RilDrv: WriteBitwiseToBuffer() - unknown message type %d\r\n"), pbCDMABytes[iBufferPos]));
        hr = E_FAIL;
        goto Error;
    }
    iBufferPos++;

    while (ReadCDMAParameter(pbCDMABytes, cbCDMABytes, &iBufferPos, bParameter, MAX_CDMA_PARAMETER, &uiParamID, &uiParamLen)) {
        switch (uiParamID) {
            case 0:   // Teleservice Identifier (3.4.3.1)
                if (uiParamLen >= 2) {
                    uiTeleService = bParameter[0] << 8 | bParameter[1];
                    if (uiTeleService == 0x1001)
                        rrmMsg.msgIS637InDeliver.dwTeleservice = RIL_MSGTELESERVICE_PAGING;
                    else if (uiTeleService == 0x1002)
                        rrmMsg.msgIS637InDeliver.dwTeleservice = RIL_MSGTELESERVICE_MESSAGING;
                    else if (uiTeleService == 0x1003)
                        rrmMsg.msgIS637InDeliver.dwTeleservice = RIL_MSGTELESERVICE_VOICEMAIL;
                    else if (uiTeleService == 0x1004)
                        rrmMsg.msgIS637InDeliver.dwTeleservice = RIL_MSGTELESERVICE_WAP;
                    else {
                        RILRetailTraceZone(ZONE_ERROR, (TEXT("RilDrv: WriteBitwiseToBuffer() - unknown teleservice (%04X)\r\n"), uiTeleService));
                        hr = E_FAIL;
                        goto Error;
                    }
                    rrmMsg.dwParams |= RIL_PARAM_M_TELESERVICE;
                }
                break;
            case 2:  // Originate address (3.4.3.3)
                if (DecodeCdmaAddress(&rrmMsg.msgIS637InDeliver.raOrigAddress, bParameter, uiParamLen, FALSE))
                    rrmMsg.dwParams |= RIL_PARAM_M_ORIGADDRESS;
                break;
            case 6:  // Bearer Reply Option (3.4.3.5)
                if (uiParamLen >= 1) {
                    UINT uiReplySequence;

                    uiReplySequence = bParameter[0] >> 2;
                    RILRetailTraceZone(ZONE_INFO, (TEXT("RilDrv: ParseSMSCDMAMessage() - unused parameter: reply sequence no %d\r\n"), uiReplySequence));
                }
                break;
            case 8:  // Bearer Data (3.4.3.7)
                if (!DecodeCdmaBearer(&rrmMsg, bParameter, uiParamLen)) {
                    hr = E_FAIL;
                    goto Error;
                }
                break;
            default:
                // Windows Mobile doesn't support all CDMA parameter IDs.
                RILRetailTraceZone(ZONE_INFO, (TEXT("RilDrv: ParseSMSCDMAMessage() - unkonwn parameter %d\r\n"), uiParamID));
        }
    }

    rrmMsg.dwType = RIL_MSGTYPE_IN_IS637DELIVER;
    rrmMsg.dwParams |= RIL_PARAM_M_TYPE;
    if (rrmMsg.msgIS637InDeliver.dwTeleservice != RIL_MSGTELESERVICE_VOICEMAIL) {
        // Flags not relevant for voice mail notifications.
        rrmMsg.dwFlags = RIL_MSGFLAG_NONE;
        rrmMsg.dwParams |= RIL_PARAM_M_FLAGS;
    }

Error:
    delete[] pbCDMABytes;
    return hr;
}


//
// Helper for setting RILMSGDCS language properties
//
static void SetDCSLanguageType(RILMSGDCS& rrmdDCS, DWORD dwAlphabet, DWORD dwLanguage)
{
    rrmdDCS.dwType = RIL_DCSTYPE_LANGUAGE;
    rrmdDCS.dwParams |= RIL_PARAM_MDCS_TYPE;

    rrmdDCS.dwAlphabet = dwAlphabet;
    rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;

    rrmdDCS.dwLanguage = dwLanguage;
    rrmdDCS.dwParams |= RIL_PARAM_MDCS_LANGUAGE;
}

//
// Set Data Coding Scheme of Incoming Cell Broadcast Message
// see GSM 03.38
//
static BOOL ParseCellBroadcastDCS(const BYTE bIn, RILMSGDCS& rrmdDCS, UINT& rcbLangInMsgBody)
{
    rcbLangInMsgBody = 0;

    (void)memset(&rrmdDCS, 0x00, sizeof(RILMSGDCS));
    rrmdDCS.cbSize = sizeof(RILMSGDCS);

    switch (bIn & 0xf0)
    {
        case 0x00:
            SetDCSLanguageType(rrmdDCS, RIL_DCSALPHABET_DEFAULT, g_rgdwDCSLanguagesGroup1[bIn & 0x0f]);
            break;

        case 0x10:
            switch (bIn & 0x0f)
            {
                case 0x00:
                    // First 3 characters of message body contain language.
                    rcbLangInMsgBody = 3;

                    SetDCSLanguageType(rrmdDCS, RIL_DCSALPHABET_DEFAULT, RIL_DCSLANG_UNKNOWN);
                    break;

                case 0x01:
                    // First 2 characters of message body contain language.
                    rcbLangInMsgBody = 2;

                    SetDCSLanguageType(rrmdDCS, RIL_DCSALPHABET_UCS2, RIL_DCSLANG_UNKNOWN);
                    break;

                default:
                    SetDCSLanguageType(rrmdDCS, RIL_DCSALPHABET_DEFAULT, RIL_DCSLANG_UNKNOWN);
                    break;
            }
            break;

        case 0x20:
            if (0x00 == (bIn & 0x0f))
            {
                SetDCSLanguageType(rrmdDCS, RIL_DCSALPHABET_DEFAULT, g_rgdwDCSLanguagesGroup2[bIn & 0x0f]);
                break;
            }
            // else fall through...

        case 0x30:
            SetDCSLanguageType(rrmdDCS, RIL_DCSALPHABET_DEFAULT, RIL_DCSLANG_UNKNOWN);
            break;

        // The BCHDR is only supported for Windows Mobile 6.5
#if defined(RIL_PARAM_MDCS_BCHDR)
        case 0x90:
            rrmdDCS.dwParams |= RIL_PARAM_MDCS_BCHDR;
            switch(bIn & 0x03)
            {
                case 0x00:
                    rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_0;
                    rrmdDCS.dwParams |= RIL_PARAM_MDCS_MSGCLASS;
                    break;

                case 0x01:
                    rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_1;
                    rrmdDCS.dwParams |= RIL_PARAM_MDCS_MSGCLASS;
                    break;

                case 0x02:
                    rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_2;
                    rrmdDCS.dwParams |= RIL_PARAM_MDCS_MSGCLASS;
                    break;

                case 0x03:
                    rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_3;
                    rrmdDCS.dwParams |= RIL_PARAM_MDCS_MSGCLASS;
                    break;
            }
            switch ((bIn >> 2) & 0x03)
            {
                case 0x00:
                    rrmdDCS.dwAlphabet = RIL_DCSALPHABET_DEFAULT;
                    rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
                    break;

                case 0x01:
                    rrmdDCS.dwAlphabet = RIL_DCSALPHABET_8BIT;
                    rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
                    break;

                case 0x02:
                    rrmdDCS.dwAlphabet = RIL_DCSALPHABET_UCS2;
                    rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
                    break;

                case 0x03:
                    // Reserved... so use assume alphabet
                    rrmdDCS.dwAlphabet = RIL_DCSALPHABET_DEFAULT;
                    rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
                    break;
            }
            break;
#endif

        case 0xf0:
            rrmdDCS.dwType = RIL_DCSTYPE_GENERAL;
            rrmdDCS.dwParams |= RIL_PARAM_MDCS_TYPE;

            if (bIn & 0x04)
            {
                rrmdDCS.dwAlphabet = RIL_DCSALPHABET_8BIT;
                rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
            }
            else
            {
                rrmdDCS.dwAlphabet = RIL_DCSALPHABET_DEFAULT;
                rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
            }

            switch (bIn & 0x03)
            {
                case 0x00:
                    // None
                    break;

                case 0x01:
                    rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_1;
                    rrmdDCS.dwParams |= RIL_PARAM_MDCS_MSGCLASS;
                    break;

                case 0x02:
                    rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_2;
                    rrmdDCS.dwParams |= RIL_PARAM_MDCS_MSGCLASS;
                    break;

                case 0x03:
                    rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_3;
                    rrmdDCS.dwParams |= RIL_PARAM_MDCS_MSGCLASS;
                    break;
            }
            break;

        default:
            if (bIn & 0x40)
            {
                rrmdDCS.dwType = RIL_DCSTYPE_GENERAL;
                rrmdDCS.dwParams |= RIL_PARAM_MDCS_TYPE;

                if (bIn & 0x20)
                {
                    rrmdDCS.dwFlags |= RIL_DCSFLAG_COMPRESSED;
                    rrmdDCS.dwParams |= RIL_PARAM_MDCS_FLAGS;
                }

                if (bIn & 0x10)
                {
                    switch (bIn & 0x03)
                    {
                        case 0x00:
                            rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_0;
                            rrmdDCS.dwParams |= RIL_PARAM_MDCS_MSGCLASS;
                            break;

                        case 0x01:
                            rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_1;
                            rrmdDCS.dwParams |= RIL_PARAM_MDCS_MSGCLASS;
                            break;

                        case 0x02:
                            rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_2;
                            rrmdDCS.dwParams |= RIL_PARAM_MDCS_MSGCLASS;
                            break;

                        case 0x03:
                            rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_3;
                            rrmdDCS.dwParams |= RIL_PARAM_MDCS_MSGCLASS;
                            break;
                    }
                }

                switch ((bIn >> 2) & 0x03)
                {
                    case 0x00:
                        rrmdDCS.dwAlphabet = RIL_DCSALPHABET_DEFAULT;
                        rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
                        break;

                    case 0x01:
                        rrmdDCS.dwAlphabet = RIL_DCSALPHABET_8BIT;
                        rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
                        break;

                    case 0x02:
                        rrmdDCS.dwAlphabet = RIL_DCSALPHABET_UCS2;
                        rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
                        break;

                    case 0x03:
                        // Reserved... so use assume alphabet
                        rrmdDCS.dwAlphabet = RIL_DCSALPHABET_DEFAULT;
                        rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
                        break;
                }
            }
            else
            {
                // All other cases are reserved and assumed to use default alphabet.
                SetDCSLanguageType(rrmdDCS, RIL_DCSALPHABET_DEFAULT, RIL_DCSLANG_UNKNOWN);
            }
            break;
    }


    return TRUE;
}


//
// Parses header information for
//
static BOOL ParseCellBroadcastHeader(const BYTE* const pbIn, const UINT cbIn, RILMESSAGE& rrmMsg, UINT& rcbParsed)
{
    rcbParsed = 0;

    if (cbIn < CELLBROADCAST_HEADER_LENGTH)
    {
        return FALSE;
    }

    // Parse values from the first 2 octets (the serial number)
    WORD wSerialNumber                  = MAKEWORD(*(pbIn+1), *pbIn);
    rrmMsg.msgBcGeneral.dwGeoScope      = GEOSCOPE_FROM_SERIALNUMBER    (wSerialNumber);
    rrmMsg.msgBcGeneral.dwMsgCode       = MESSAGECODE_FROM_SERIALNUMBER (wSerialNumber);
    rrmMsg.msgBcGeneral.dwUpdateNumber  = UPDATENUMBER_FROM_SERIALNUMBER(wSerialNumber);

    // Parse the message ID
    rrmMsg.msgBcGeneral.dwID            = MAKEWORD(*(pbIn+3), *(pbIn+2));

    // Parse DCS info
    UINT cbLanguageInMessageBody = 0;
    if (!ParseCellBroadcastDCS(*(pbIn+4), rrmMsg.msgBcGeneral.rmdDataCoding, cbLanguageInMessageBody))
    {
        return FALSE;
    }

    // Parse page info
    BYTE bPageInfo = *(pbIn+5);
    rrmMsg.msgBcGeneral.dwTotalPages    = TOTALPAGES(bPageInfo);
    rrmMsg.msgBcGeneral.dwPageNumber    = PAGENUMBER(bPageInfo);

    if (rrmMsg.msgBcGeneral.dwTotalPages == 0 ||
        rrmMsg.msgBcGeneral.dwPageNumber == 0)
    {
        rrmMsg.msgBcGeneral.dwTotalPages = 1;
        rrmMsg.msgBcGeneral.dwPageNumber = 1;
    }

    rcbParsed = CELLBROADCAST_HEADER_LENGTH + cbLanguageInMessageBody;

    return TRUE;
}


HRESULT ParseCellBroadcastMessage(const LPCSTR sIn, const UINT cbIn, RILMESSAGE& rrmMsg)
{
    HRESULT hr = S_OK;
    BYTE* pbGSMBytes = NULL;
    UINT cbGSMBytes;
    UINT cbParsed = 0;

    // Zero out the message structure
    (void)memset(&rrmMsg, 0x00, sizeof(RILMESSAGE));
    rrmMsg.cbSize = sizeof(RILMESSAGE);

    // Convert the whole message from GSM default HEX represention into GSM default
    pbGSMBytes = new BYTE[cbIn / 2 + 1];
    if (!pbGSMBytes) {
        hr = E_OUTOFMEMORY;
        goto Error;
    }
    if (!GSMHexToGSM(sIn, cbIn, (LPSTR)pbGSMBytes, cbIn / 2 + 1, cbGSMBytes)) {
        hr = E_FAIL;
        goto Error;
    }

    // Parse the header information
    if (!ParseCellBroadcastHeader(pbGSMBytes, cbGSMBytes, rrmMsg, cbParsed))
    {
        hr = E_FAIL;
        goto Error;
    }

    // This is a cell broadcast message.
    rrmMsg.dwType = RIL_MSGTYPE_BC_GENERAL;

    // Copy the message body to the output structure
    rrmMsg.msgBcGeneral.cchMsgLength = min(cbGSMBytes - cbParsed, MAXLENGTH_MSG);
    memcpy(rrmMsg.msgBcGeneral.rgbMsg, pbGSMBytes + cbParsed, rrmMsg.msgBcGeneral.cchMsgLength);

    // We always get all the parameters for cell broadcast
    rrmMsg.dwParams = RIL_PARAM_M_ALL_BC_GENERAL;

Error:
    delete[] pbGSMBytes;
    return hr;
}


//
// Parse a CDMA delivery status message into a RILMESSAGE structure
//
HRESULT ParseCdmaStatusMessage(const LPCSTR sIn, const UINT cbIn, RILMESSAGE& rrmMsg)
{
    BYTE* pbCDMABytes = NULL;
    UINT cbCDMABytes;
    HRESULT hr = S_OK;
    BYTE bParameter[MAX_CDMA_PARAMETER];
    UINT uiParamID;
    UINT uiParamLen;
    UINT iBufferPos = 0;
    UINT uiTeleService;

    // Zero out the message structure
    (void)memset(&rrmMsg, 0x00, sizeof(RILMESSAGE));
    rrmMsg.cbSize = sizeof(RILMESSAGE);
    // Convert the whole message from GSM default HEX represention into GSM default
    pbCDMABytes = new BYTE[cbIn / 2 + 1];
    if (!pbCDMABytes) {
        hr = E_OUTOFMEMORY;
        goto Error;
    }
    if (!GSMHexToGSM(sIn, cbIn, (LPSTR)pbCDMABytes, cbIn / 2 + 1, cbCDMABytes)) {
        hr = E_FAIL;
        goto Error;
    }

    // See specification 3GPP2 C.S0015-A
    // Check if message got the type Point-to-Point
    if (pbCDMABytes[iBufferPos] !=0) {
        RILRetailTraceZone(ZONE_ERROR, (TEXT("RilDrv: WriteBitwiseToBuffer() - unknown message type %d\r\n"), pbCDMABytes[iBufferPos]));
        goto Error;
    }
    iBufferPos++;

    while (ReadCDMAParameter(pbCDMABytes, cbCDMABytes, &iBufferPos, bParameter, MAX_CDMA_PARAMETER, &uiParamID, &uiParamLen)) {
        switch (uiParamID) {
            case 0:   // Teleservice Identifier (3.4.3.1)
                if (uiParamLen >= 2) {
                    uiTeleService = bParameter[0] << 8 | bParameter[1];
                    RILRetailTraceZone(ZONE_INFO, (TEXT("RilDrv: ParseCdmaStatusMessage() - unused parameter: tele service %d\r\n"), uiTeleService));
                }
                break;
            case 2:  // Originate address (3.4.3.3)
                if (DecodeCdmaAddress(&rrmMsg.msgIS637InStatus.raOrigAddress, bParameter, uiParamLen, FALSE))
                    rrmMsg.dwParams |= RIL_PARAM_M_ORIGADDRESS;
                break;
            case 6:  // Bearer Reply Option (3.4.3.5)
                if (uiParamLen >= 1) {
                    UINT uiReplySequence;

                    uiReplySequence = bParameter[0] >> 2;
                    rrmMsg.msgIS637InStatus.dwReplySeqNumber = uiReplySequence;
                    rrmMsg.dwParams |= RIL_PARAM_M_REPLYSEQNUMBER;
                }
                break;
            case 7:  // Cause Code Option (3.4.3.6)
                if (uiParamLen >= 1) {
                    UINT uiReplySequence;

                    uiReplySequence = bParameter[0] >> 2;
                    rrmMsg.msgIS637InStatus.dwReplySeqNumber = uiReplySequence;
                    rrmMsg.dwParams |= RIL_PARAM_M_REPLYSEQNUMBER;
                }
                if (uiParamLen >= 2) {
                    UINT uiCauseCode;

                    uiCauseCode = bParameter[1];
                    rrmMsg.msgIS637InStatus.dwCauseCode = uiCauseCode;
                    rrmMsg.dwParams |= RIL_PARAM_M_CAUSECODE;
                }
                break;
            case 8:  // Bearer Data (3.4.3.7)
                if (!DecodeCdmaDsBearer(&rrmMsg, bParameter, uiParamLen))
                    goto Error;
                break;
            default:
                // Windows Mobile doesn't support all CDMA parameter IDs.
                RILRetailTraceZone(ZONE_INFO, (TEXT("RilDrv: ParseCdmaStatusMessage() - unkonwn parameter %d\r\n"), uiParamID));
        }
    }

    rrmMsg.dwType = RIL_MSGTYPE_IN_IS637STATUS;
    rrmMsg.dwParams |= RIL_PARAM_M_TYPE;
    rrmMsg.dwFlags = RIL_MSGFLAG_NONE;
    rrmMsg.dwParams |= RIL_PARAM_M_FLAGS;

Error:
    delete[] pbCDMABytes;
    return hr;
}


