//
//  Copyright © 2002, RESON Inc. All Rights Reserved.
//
//  No part of this file may be reproduced or transmitted in any form or by
//  any means, electronic or mechanical, including photocopy, recording, or
//  information storage or retrieval system, without permission in writing
//  from RESON Inc.
//
//  Filename:   6046Hasp.cpp
//
//  Project:    6046
//
//  Author(s):  W. Arcus -- Based on code from 6041.
//
//  Purpose:    Defines the 6046 Hasp class and associated dongle layout that is
//              backwards compatible to the 6041 configuration using version 4.
//
//  Notes:      
//

#include "StdAfx.h"
#include "6046Hasp.h"

#include <stddef.h>
#include "ctime.h"
#include <time.h>

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

namespace           // Begin unnamed namespace to ensure internal linkage only.
{
    // Set passwords internally to this module. Note that only encoded passwords plus algorithm are 
    // given here to help improve security. Unencoded passwords may only be found in the relevant 
    // development documentation and are defined by Aladin for the RESON developer code (AZDRX).

    class CPswd
    {
    private:

        const unsigned int m_uiEncoded;
        const unsigned int m_uiMask;

        CPswd ( const CPswd &rRhs );                    // Not implemented thus private.
        CPswd & operator = ( const CPswd &rRhs );       // Not implemented thus private.

    public:

        CPswd   (   const unsigned int &ruiEncoded = 0U,
                    const unsigned int &ruiMask    = 0U )

                :   m_uiEncoded (   ruiEncoded ),
                    m_uiMask    (   ruiMask )
        {
        }

        virtual ~CPswd( void )
        {
        }

        operator unsigned int ( void ) const
        {
            return ( m_uiEncoded ^ ( ~m_uiMask ) );
        }
    };

    const CPswd         Pw1_c( 0xf699, ~0xb4b4 );
    const CPswd         Pw2_c( 0xed68, ~0xb9b1 );

    const long          lDaysToSeconds_c    = 24L * 60L * 60L;
    
    int _days[] =
    {
        -1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364
    };

    // ChkAdd evaluates to TRUE if dest = src1 + src2 has overflowed

#define ChkAdd(dest, src1, src2)    ( ((src1 >= 0L) && (src2 >= 0L) \
                                        && (dest < 0L)) || ((src1 < 0L) && (src2 < 0L) && (dest >= 0L)) )

     // ChkMul evaluates to TRUE if dest = src1 * src2 has overflowed

#define ChkMul(dest, src1, src2)    ( src1 ? (dest/src1 != src2) : 0 )

}                   // end unnamed namespace.

///////////////////////////////////////////////////////////////////////////////
// C6046Hasp class implementation.

C6046Hasp::C6046Hasp(   const unsigned int         &ruiLptNumber,
                        const AutoSearchOptions    &reOptions )

          :CHasp    (   static_cast<unsigned int>( Pw1_c ),
                        static_cast<unsigned int>( Pw2_c ),
                        ruiLptNumber,
                        reOptions )
{
}

C6046Hasp::~C6046Hasp( void )
{
}

WORD C6046Hasp::GetVersion( void )
{
    return ReadWord( haspLayoutVersion );
}

C6046Hasp::ERESONHASPTYPE C6046Hasp::GetResonHaspType( void )
{
    // Object must be connected to dongle for this function to work.

    ERESONHASPTYPE  eHaspType = resonHaspTypeUnknown;
    WORD            wVersion  = GetVersion();

    switch( wVersion )
    {
        case resonHaspVersion1:                                 // 6041 Format

            eHaspType = resonHaspType;
            break;

        case resonHaspVersion2:                                                     // 6041 Keymaker

            if ( ( ReadWord( 1 ) == 0x2f47 ) && ( ReadWord( 2 ) == 0xb7aa ) )
            {
                eHaspType = resonHaspType6041KeyMaker;
            }

            break;

        case resonHaspVersion3:                                                     // 6041 Format.
        case resonHaspVersion4:                                                     // 6041 & 6046 Format.

            {
                // Check expiration dates etc.

                long lCurrentTime;                                                  // Current system time
                long lDemoExpireTime;                                               // Demo expiration time on the dongle at haspLayoutDemoTime
                long lUserTimeLastUpdate;                                           // User's last system time stored at hasp6041LayoutUserTime

                // Get current system time.

                time( &lCurrentTime );

                // Check to see if user has set back the system clock.

                if ( ReadWord( haspLayoutUserTimeUpper ) != 0 )                     // If 0, this is the first time demo used so proceed normally.
                {
                    lUserTimeLastUpdate = GetLongTime( haspLayoutUserTimeUpper );

                    if ( lUserTimeLastUpdate > ( lCurrentTime + ( 2 * 60 ) ) )      // Lets give a 2 hour benefit of the doubt
                    {
                        eHaspType = resonHaspTypeDemoExpired;
                        break;
                    }
                }

                // Get demo expiration time from the dongle.

                lDemoExpireTime = GetLongTime( haspLayoutDemoTimeUpper );

                if ( ( lDemoExpireTime == 0 ) || ( lCurrentTime < lDemoExpireTime ) )
                {
                    eHaspType = resonHaspType;

                    // Write the current system time to the dongle.

                    WriteLong( haspLayoutUserTimeUpper, lCurrentTime );
                }
                else
                {
                    // Demo time has expired.

                    eHaspType = resonHaspTypeDemoExpired;
                }
            }

            break;

        default:

            eHaspType = resonHaspTypeUnknown;
            break;
    }

    return eHaspType;
}

WORD C6046Hasp::SecureCheck( UINT nAttribute )
{
    // Perform a check and return a true/false to indicate if the selected
    // attribute is enabled.

    WORD wAttribute = 0;

    try
    {
        // We have the hasp info, is it a demo hasp

        switch( GetVersion() )
        {
            case resonHaspVersion1:                             // Hasp M1
            case resonHaspVersion2:                             // Keymaker

                wAttribute = ReadWord( nAttribute );
                break;

            case resonHaspVersion3:                             // Hasp M1 Time - possible demo mode read year attribute to determine if demo mode set.
            case resonHaspVersion4:                             // HASP4 Time for 6046 and backwards compatible with 6041 HASP M1 Time.

                {
                    long    lHaspTime;
                    long    lLastTime;
                    long    lDemoTime;
                    BYTE    byCount;

                    // First check to see if this is a demo dongle.

                    if ( ReadWord( haspLayoutDemoTimeUpper ) > 0 )
                    {
                        // It's in demo mode so do the following:
            
                        // 1)   Verify this is a time dongle.
                        // 2)   Read date from timer.
                        // 3)   Compare to saved date
                        //          if dates are the same then
                        //              increment count
                        //          else if count over limit then
                        //              disable dongle.
                        //              return
                        //          endif
                        // 4)   Save count.
                        // 5)   Compare timer date to demo date,
                        //          if timer date <= demo date then
                        //              allow demo to work
                        //          else
                        //              disable dongle
                        //              return
                        //          endif

                        if ( ! IsTimeHasp() )
                        {
                            throw 0;        // Not a time HASP -- not allowed.
                        }

                        lHaspTime = TimeHASP_ReadTime();
                        lLastTime = TimeHASP_ReadLong( haspLayoutLastTimeUpper );

                        if ( lHaspTime == lLastTime )
                        {
                            // Possible stopped timer

                            byCount = TimeHASP_ReadByte( haspLayoutCount );

                            if ( ++byCount > 10 )
                            {
                                // Assume timer battery died.
                                throw 0;        
                            }

                            // Save new count.

                            TimeHASP_WriteByte( haspLayoutCount, byCount );
                        }
                        else
                        {
                            // Times not same so clear the count.

                            TimeHASP_WriteByte( haspLayoutCount, 0 );
                            TimeHASP_WriteLong( haspLayoutLastTimeUpper, lHaspTime );
                        }

                        // Check to see if demo expired.

                        lDemoTime = ReadLong( haspLayoutDemoTimeUpper );

                        if ( lDemoTime < lHaspTime )
                        {
                            throw 0;                                // Demo has expired.
                        }

                        wAttribute = ReadWord( nAttribute );
                    }
                    else
                    {
                        wAttribute = ReadWord( nAttribute );        // Purchased copy -- just read the requested location.
                    }
                }

                break;

            default:

                throw 0;
                break;
        }
    }
    catch ( const int &riErrorCode )
    {
        wAttribute = 0;
        TRACE( _T( "C6046Hasp::SecureCheck(), Error code: %d\n" ), riErrorCode );
    }
    catch ( CHaspException e )
    {
        wAttribute = 0;
        TRACE( _T( "C6046Hasp::SecureCheck(), CHaspException: %d\n" ), e.GetStatus() );
    }

    return wAttribute;
}

inline
long C6046Hasp::ReadLong( int iOffset )
{
    return ( ( static_cast<long>( ReadWord( iOffset     ) ) << 16L ) |
             ( static_cast<long>( ReadWord( iOffset + 1 ) )        ) );
}

inline
void C6046Hasp::WriteLong( int iOffset, long lValue )
{
    WriteWord( iOffset + 1, lValue & static_cast<long>( 0x0000ffff ) );
    WriteWord( iOffset,     lValue >> 16L );
}

inline
long C6046Hasp::TimeHASP_ReadLong( int iOffset )
{
    long lValue;

    lValue = TimeHASP_ReadByte( iOffset );
    lValue <<= 8;

    lValue += TimeHASP_ReadByte( iOffset + 1 );
    lValue <<= 8;

    lValue += TimeHASP_ReadByte( iOffset + 2 );
    lValue <<= 8;

    lValue += TimeHASP_ReadByte( iOffset + 3 );

    return lValue;
}

inline
void C6046Hasp::TimeHASP_WriteLong( int iOffset, long lValue )
{
    TimeHASP_WriteByte( iOffset + 3,   lValue        & 0x000000ff );
    TimeHASP_WriteByte( iOffset + 2, ( lValue >> 8 ) & 0x000000ff );
    TimeHASP_WriteByte( iOffset + 1, ( lValue >> 16 )& 0x000000ff );
    TimeHASP_WriteByte( iOffset,     ( lValue >> 24 )& 0x000000ff );
}

inline
long C6046Hasp::TimeHASP_ReadTime( void )
{
    int t[6];                   // Year, Month, Day, Hour, Min, Sec
    tm  T;

    TimeHASP_GetTime( &t[ 3 ], &t[ 4 ], &t[ 5 ] );
    TimeHASP_GetDate( &t[ 0 ], &t[ 1 ], &t[ 2 ] );

    T.tm_year = (( t[0] >= 92 ) ? t[0] + 1900 : t[0] + 2000 ) - 1900;
    T.tm_mon =  t[ 1 ] - 1;  // 0 to 11
    T.tm_mday = t[ 2 ];
    T.tm_hour = t[ 3 ];
    T.tm_min =  t[ 4 ];
    T.tm_sec =  t[ 5 ];

    T.tm_isdst = -1;            // Unknown Daylight Savings Time

    return mkgmtime( &T );
}

inline
long C6046Hasp::GetLongTime( int iIndex )
{
    // Typically used to read words:
    // 54 and 55 (haspLayoutDemoTimeUpper and haspLayoutDemoTimeLower respectively) or
    // 52 and 53 (haspLayoutUserTimeUpper and haspLayoutUserTimeLower respectively).

    return ( ( static_cast<long>( ReadWord( iIndex     ) ) << 16L ) +
               static_cast<long>( ReadWord( iIndex + 1 ) )          );
}

///////////////////////////////////////////////////////////////////////////////
// Pulblic methods only defined for internal RESON purposes. They include:
//      Configure()
//      Dump()

#ifdef _RESON_INTERNAL_

bool C6046Hasp::Configure(  const WORD          &rwVersion,
                            const bool          &rbEraseAllMemory,
                            const WORD          *pwMemoryBlock,
                            const unsigned int  &ruiMemoryWords,
                            const unsigned int  &ruiMemoryOffset,
                            const bool          &rbSetDemo )
{
    bool bSuccess = false;

    try
    {
        // Zero key memory area.

        int iWord;

        if ( rbEraseAllMemory )
        {
            for ( iWord = 0; iWord < ( GetMemorySize() / sizeof( WORD ) ); iWord++ )
            {
                WriteWord( iWord, 0 );
            }
        }

        // Write format data.

        if ( ( ruiMemoryWords > 0U ) && ( pwMemoryBlock != NULL ) )
        {
            WriteWordBlock( pwMemoryBlock, ruiMemoryOffset, ruiMemoryWords );
        }

        // If it is a time key, set the date and demo mode as appropriate.

        if ( IsTimeHasp() )
        {
            // Zero the timer memory.

            for ( iWord = 0; iWord < 16; iWord ++ )
            {
                TimeHASP_WriteByte( iWord, 0 );
            }

            // Set the HASP time from the computer's system time.

            long    lTime;
            int     iYear;
            tm      sTime;

            time( &lTime );             // Get the system time.
            sTime = *gmtime( &lTime );  // Convert to GMT.
            ///sTime = *localtime( &lTime );  // 

            iYear = (( sTime.tm_year < 100 ) ? sTime.tm_year : sTime.tm_year - 100 );

            TimeHASP_SetTime( sTime.tm_hour, sTime.tm_min, sTime.tm_sec );
            TimeHASP_SetDate( iYear, sTime.tm_mon + 1, sTime.tm_mday );

            // If this is a demo version, set the demo expiration date 30 days hence.

            long lDemoTime = 0L;
            
            if ( rbSetDemo )
            {
                lDemoTime = lTime + ( 30 * lDaysToSeconds_c );
            }

            WriteLong( haspLayoutDemoTimeUpper, lDemoTime );
        }

        WriteWord( haspLayoutVersion, rwVersion );

        // Although not an error, warn the developer (in debug mode) that the 6041 reserved space
        // that has been reallocated to 6046 attributes only supported in version 4 HASP format an beyond.

        ASSERT( GetVersion() > resonHaspVersion3 );

        bSuccess = true;
    }
    catch ( CHaspException e )
    {
        bSuccess = false;
        TRACE( _T( "C6046Hasp::Configure(), CHaspException: %d\n" ), e.GetStatus() );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "C6046Hasp::Configure(), Unspecfied exception caught\n" ) );
    }

    return bSuccess;
}

void C6046Hasp::Dump( FILE *pFile )
{
    ASSERT( pFile != NULL );

    if ( pFile == NULL )
    {
        return;
    }

    fprintf( pFile, "Hasp ID: %ld\n", HaspID() );

    int nBytes = GetMemorySize();

    fprintf( pFile, "Memory:  %d bytes\n", nBytes );

    bool bHasTimer = ( IsTimeHasp() ) ? true : false;

    fprintf( pFile, "Timer:   %s\n", ( bHasTimer ) ? "YES" : "NO" );

    // Display data.

    if ( bHasTimer )
    {
       time_t tt;
       tm     TT;

       time( &tt );
       TT = *gmtime( &tt );
       fprintf( pFile, "\nComputer Reports: GMT(%d)  %s", tt, asctime( &TT ) );

       time_t t = TimeHASP_ReadTime();
       tm T =     *gmtime( &t );
       fprintf( pFile, "Hasp Reports:     GMT(%d)  %s", t, asctime( &T ) );
       T =     *localtime( &t );
       fprintf( pFile, "                  Local           %s", asctime( &T ) );
       CTime ct( t );
       fprintf( pFile, "                  %s\n", ct.Format("%Z") );
       t = ReadLong( haspLayoutDemoTimeUpper );

       if ( t != -1 )
       {
            CTime ct1( t );
            fprintf( pFile, "Demo Expires:     %ld, %s\n", t, ct1.FormatGmt("%d %b %Y") );
       }
    }

    // HASP memory.

    fprintf( pFile, "Memory dump:\n" );

    for( int i = 0; i < ( nBytes / 2 ); i++ )
    {
        fprintf( pFile, "%04x ", ReadWord( i ) );

        if ( ( ( i + 1 ) % 8 ) == 0 )
        {
            fprintf( pFile, "\n" );
        }
    }
}

#endif

/***
*static time_t make_time_t(tb, ultflag) -
*
*Purpose:
*       Converts a struct tm value to a time_t value, then updates the struct
*       tm value. Either local time or UTC is supported, based on ultflag.
*       This is the routine that actually does the work for both mktime() and
*       _mkgmtime().
*
*Entry:
*       struct tm *tb - pointer to a tm time structure to convert and
*                       normalize
*       int ultflag   - use local time flag. the tb structure is assumed
*                       to represent a local date/time if ultflag > 0.
*                       otherwise, UTC is assumed.
*
*Exit:
*       If successful, mktime returns the specified calender time encoded as
*       a time_t value. Otherwise, (time_t)(-1) is returned to indicate an
*       error.
*
*Exceptions:
*       None.
*
*******************************************************************************/

inline
time_t C6046Hasp::_make_time_t ( struct tm *tb, int ultflag )
{
    long tmptm1, tmptm2, tmptm3;
    struct tm *tbtemp;

    /*
     * First, make sure tm_year is reasonably close to being in range.
     */
    if ( ((tmptm1 = tb->tm_year) < _BASE_YEAR - 1) || (tmptm1 > _MAX_YEAR
      + 1) )
        goto err_mktime;


    /*
     * Adjust month value so it is in the range 0 - 11.  This is because
     * we don't know how many days are in months 12, 13, 14, etc.
     */

    if ( (tb->tm_mon < 0) || (tb->tm_mon > 11) ) {

        /*
         * no danger of overflow because the range check above.
         */
        tmptm1 += (tb->tm_mon / 12);

        if ( (tb->tm_mon %= 12) < 0 ) {
            tb->tm_mon += 12;
            tmptm1--;
        }

        /*
         * Make sure year count is still in range.
         */
        if ( (tmptm1 < _BASE_YEAR - 1) || (tmptm1 > _MAX_YEAR + 1) )
            goto err_mktime;
    }

    /***** HERE: tmptm1 holds number of elapsed years *****/

    /*
     * Calculate days elapsed minus one, in the given year, to the given
     * month. Check for leap year and adjust if necessary.
     */
    tmptm2 = _days[tb->tm_mon];
    if ( !(tmptm1 & 3) && (tb->tm_mon > 1) )
            tmptm2++;

    /*
     * Calculate elapsed days since base date (midnight, 1/1/70, UTC)
     *
     *
     * 365 days for each elapsed year since 1970, plus one more day for
     * each elapsed leap year. no danger of overflow because of the range
     * check (above) on tmptm1.
     */
    tmptm3 = (tmptm1 - _BASE_YEAR) * 365L + ((tmptm1 - 1L) >> 2)
      - _LEAP_YEAR_ADJUST;

    /*
     * elapsed days to current month (still no possible overflow)
     */
    tmptm3 += tmptm2;

    /*
     * elapsed days to current date. overflow is now possible.
     */
    tmptm1 = tmptm3 + (tmptm2 = (long)(tb->tm_mday));
    if ( ChkAdd(tmptm1, tmptm3, tmptm2) )
        goto err_mktime;

    /***** HERE: tmptm1 holds number of elapsed days *****/

    /*
     * Calculate elapsed hours since base date
     */
    tmptm2 = tmptm1 * 24L;
    if ( ChkMul(tmptm2, tmptm1, 24L) )
        goto err_mktime;

    tmptm1 = tmptm2 + (tmptm3 = (long)tb->tm_hour);
    if ( ChkAdd(tmptm1, tmptm2, tmptm3) )
        goto err_mktime;

    /***** HERE: tmptm1 holds number of elapsed hours *****/

    /*
     * Calculate elapsed minutes since base date
     */

    tmptm2 = tmptm1 * 60L;
    if ( ChkMul(tmptm2, tmptm1, 60L) )
        goto err_mktime;

    tmptm1 = tmptm2 + (tmptm3 = (long)tb->tm_min);
    if ( ChkAdd(tmptm1, tmptm2, tmptm3) )
        goto err_mktime;

    /***** HERE: tmptm1 holds number of elapsed minutes *****/

    /*
     * Calculate elapsed seconds since base date
     */

    tmptm2 = tmptm1 * 60L;
    if ( ChkMul(tmptm2, tmptm1, 60L) )
        goto err_mktime;

    tmptm1 = tmptm2 + (tmptm3 = (long)tb->tm_sec);
    if ( ChkAdd(tmptm1, tmptm2, tmptm3) )
        goto err_mktime;

    /***** HERE: tmptm1 holds number of elapsed seconds *****/

    if  ( ultflag ) {

        /*
         * Adjust for timezone. No need to check for overflow since
         * localtime() will check its arg value
         */

#ifdef _WIN32
//            __tzset();
    _tzset();
#else  /* _WIN32 */
#if defined (_M_MPPC) || defined (_M_M68K)
    _tzset();
#endif  /* defined (_M_MPPC) || defined (_M_M68K) */
#endif  /* _WIN32 */

        tmptm1 += _timezone;

        /*
         * Convert this second count back into a time block structure.
         * If localtime returns NULL, return an error.
         */
        if ( (tbtemp = localtime(&tmptm1)) == NULL )
            goto err_mktime;

        /*
         * Now must compensate for DST. The ANSI rules are to use the
         * passed-in tm_isdst flag if it is non-negative. Otherwise,
         * compute if DST applies. Recall that tbtemp has the time without
         * DST compensation, but has set tm_isdst correctly.
         */
        if ( (tb->tm_isdst > 0) || ((tb->tm_isdst < 0) &&
          (tbtemp->tm_isdst > 0)) ) {
            tmptm1 += _dstbias;
            tbtemp = localtime(&tmptm1);    /* reconvert, can't get NULL */
        }

    }
    else {
        if ( (tbtemp = gmtime(&tmptm1)) == NULL )
            goto err_mktime;
    }

    /***** HERE: tmptm1 holds number of elapsed seconds, adjusted *****/
    /*****       for local time if requested                      *****/

    *tb = *tbtemp;
    return (time_t)tmptm1;

err_mktime:
    /*
     * All errors come to here
     */
    return (time_t)(-1);
}

inline
time_t C6046Hasp::mktime( struct tm *tb )
{
    return( _make_time_t( tb, 1 ) );
}

inline
time_t C6046Hasp::mkgmtime( struct tm *tb )
{
    return( _make_time_t( tb, 0 ) );
}

