
''' *****************************************************************************
*
* Sentinel LDK LICENSING API HASP DEMO SAMPLE 
*
* Copyright (C) 2024 THALES. All rights reserved.
*
***************************************************************************** '''

import sys
import hasp_vcode
import ctypes
import string

MAX_MEMORY = 128
HASP_DEFAULT_FID = 0
CUSTOM_FEATURE = 42

# adding Sentinel LDK licensing API module path to the system path
sys.path.insert(0, "../intf/hasp_lib")

import hasp_licensing_api_lib as hasp

SCOPE = """<?xml version="1.0" encoding="UTF-8" ?>
<haspscope>
    <license_manager hostname="localhost" />
</haspscope>""";

#helper function: dumps a given block of data, in hex and ascii

# Converts a byte to hex digit and writes to the supplied buffer
	
def byte_to_hex(b):
    hex_chars = '0123456789ABCDEF'
    high = (b & 0xf0) >> 4
    low = b & 0x0f
    return hex_chars[high] + hex_chars[low]
    
# Converts a byte array to hex string	
def to_hex_string(block):
    return ':'.join(f'{b:02x}' for b in block)

def dump(data, margin):
    if len(data) == 0:
        return

    s = bytearray(16)
    j = 0

    for i in range(len(data)):
        if j == 0:
            print(margin, end='')

        b = data[i]
        s[j] = b if 32 <= b <= 127 else ord('.')

        hex_value = f'{b:02x}'
        print(hex_value, end=' ')

        j += 1
        if j % 4 == 0 and j < 15:
            print('| ', end='')

        if j > 15:
            print("[" + s[:j].decode('latin1') + "]")
            j = 0
            s[0] = 0

    if j != 0:
        while j < 16:
            print("   ", end='')
            j += 1
            if j % 4 == 0 and j < 15:
                print('| ', end='')

        print(" [" + s[:j].decode('latin1') + "]")

def encrypt_decrypt(handle) -> hasp.hasp_constants.hasp_status_t:
    '''hasp_encrypt encrypts a block of data using the Sentinel key
    (minimum buffer size is 16 bytes).'''

    data = ctypes.create_string_buffer(16)
    data = b'1234567890123456'
    datalen = len(data);
    dump(data, '>> ')

    status = hasp.hasp_encrypt(handle, data, datalen);

    match status:
        case hasp.hasp_constants.HASP_STATUS_OK:
            print('encryption ok:');
            dump(data, '>> ')

        case hasp.hasp_constants.HASP_INV_HND:
            print('handle not active');

        case hasp.hasp_constants.HASP_TOO_SHORT:
            print ('data length too short');

        case hasp.hasp_constants.HASP_ENC_NOT_SUPP:
            print ('attached key does not support AES encryption');

        case hasp.hasp_constants.HASP_CONTAINER_NOT_FOUND:
            print ('key/license container not available');

        case _:
            print('encryption failed');

    if status != 0:
        return status

    '''hasp_decrypt decrypts a block of data using the Sentinel key
    (minimum buffer size is 16 bytes)'''

    status = hasp.hasp_decrypt(handle, data, datalen);

    match status:
        case hasp.hasp_constants.HASP_STATUS_OK:
            print('decryption ok:');
            dump(data, '>> ')

        case hasp.hasp_constants.HASP_INV_HND:
            print('handle not active');

        case hasp.hasp_constants.HASP_TOO_SHORT:
            print('data length too short');

        case hasp.hasp_constants.HASP_ENC_NOT_SUPP:
            print('attached key does not support AES encryption');

        case _:
            print('decryption failed');

    if status != 0:
        return status

def main():
    print();
    print('A simple demo program for the Sentinel LDK licensing functions');
    print('Copyright (C) THALES. All rights reserved.');
    print;

    version = []
    status = hasp.hasp_get_version(hasp_vcode.VENDOR_CODE,version)
    match status:
       case hasp.hasp_constants.HASP_STATUS_OK:
           print('version = ' + version[0])
       case _:
            print('Failed to fetch the runtime api version,status= ', status);
   
    if status != 0:
        exit(1);
   
    
    '''******************************************************
    * hasp_login
    *   establishes a context for Sentinel services.
    *
    *   login to default feature (0). This default feature
    *   is available on any key search for local and
    *   remote Sentinel keys
    ******************************************************'''
    print('login to default feature         : ');
    handle = [] 
    status = hasp.hasp_login(HASP_DEFAULT_FID, hasp_vcode.VENDOR_CODE, handle);

    match status:
        case hasp.hasp_constants.HASP_STATUS_OK:
            print('OK');

        case hasp.hasp_constants.HASP_FEATURE_NOT_FOUND:
            print('login to default feature failed');

        case hasp.hasp_constants.HASP_CONTAINER_NOT_FOUND:
            print('no Sentinel key with vendor code DEMOMA found');

        case hasp.hasp_constants.HASP_OLD_DRIVER:
            print('outdated driver version installed');

        case hasp.hasp_constants.HASP_NO_DRIVER:
            print('Sentinel driver not installed');

        case hasp.hasp_constants.HASP_INV_VCODE:
            print('invalid vendor code');

        case _:
            print('login to default feature failed with status ', status);

    if status != 0:
        exit(1);

  
    '''******************************************************
    * hasp_get_sessioninfo
    *   retrieve Sentinel key attributes
    *
    * Please note:
    *   In case of performing an activation we recommend
    *   to use hasp_get_info() instead of
    *   hasp_get_sessioninfo(), as demonstrated in the
    *   activation sample. hasp_get_info() can be called
    *   without performing a login.
    ******************************************************'''
    print('get session info                 : ');
    info = []
    status = hasp.hasp_get_sessioninfo(handle, hasp.hasp_constants.HASP_KEYINFO, info);
    match status:
        case hasp.hasp_constants.HASP_STATUS_OK:
            print('OK, Sentinel key attributes retrieved');
            print('Key info:===============' + info[0].value.decode() +'===============');
            '''*****************************************************************
                  * hasp_free
                  *   frees memory allocated by hasp_get_info, hasp_get_sessioninfo
                  *   or hasp_update (if an acknowledge was requested)
            *****************************************************************'''
            hasp.hasp_free(info[0]);

        case hasp.hasp_constants.HASP_INV_HND:
              print('handle not active');

        case hasp.hasp_constants.HASP_INV_FORMAT:
              print('unrecognized format');

        case hasp.hasp_constants.HASP_CONTAINER_NOT_FOUND:
              print('key/license container not available');

        case _:
            print('hasp_get_sessioninfo failed');
    if status != 0:
        exit(1);

    '''******************************************************
    * hasp_get_size retrieve the memory size of the
    * Sentinel key.
    ******************************************************'''

    print('retrieving the key''s memory size : ');
    fsize = [];
    status = hasp.hasp_get_size(handle, hasp.hasp_constants.HASP_FILEID_RW, fsize);
    match status:
        case hasp.hasp_constants.HASP_STATUS_OK:
            print(fsize[0], ' bytes');

        case hasp.hasp_constants.HASP_INV_HND:
            print('handle not active');

        case hasp.hasp_constants.HASP_INV_FILEID:
            print('invalid file id');

        case hasp.hasp_constants.HASP_CONTAINER_NOT_FOUND:
            print('key/license container not available');

        case _:
            print('could not retrieve memory size');

    if status != 0:
        exit(1);

  # skip memory access if no memory available
              
    if fsize[0] != 0:
        '''******************************************************
        * hasp_read
        *   read from Sentinel key memory.
        *
        *   limit memory size to be used in this demo program.
        ******************************************************'''
        if fsize[0] > MAX_MEMORY:
            fsize[0] = MAX_MEMORY;

        membuffer = ctypes.create_string_buffer(fsize[0])
        print('reading ', str(fsize), ' bytes from memory   : ');

        status = hasp.hasp_read(handle, hasp.hasp_constants.HASP_FILEID_RW, 0, fsize[0], membuffer);

        match status:

          case hasp.hasp_constants.HASP_STATUS_OK:
              print('OK');
              dump(membuffer.raw, '>> ')
            
          case hasp.hasp_constants.HASP_INV_HND:
            print('handle not active');

          case hasp.hasp_constants.HASP_INV_FILEID:
            print('invalid file id');

          case hasp.hasp_constants.HASP_MEM_RANGE:
            print('beyond memory range of attached Sentinel key');

          case hasp.hasp_constants.HASP_CONTAINER_NOT_FOUND:
            print('key/license container not available');

          case _:
              print('read memory failed');

        if status != 0:
            exit(1);
        '''********************************************************
          * hasp_write
          *   write to Sentinel key memory
        ********************************************************'''

        membuffer = b'Hello World!'
        print('writing ', len(membuffer), ' bytes to memory     : ');
        status = hasp.hasp_write(handle, hasp.hasp_constants.HASP_FILEID_RW, 0, len(membuffer), membuffer);

        match status:

          case hasp.hasp_constants.HASP_STATUS_OK:
            print('OK');
            dump(membuffer, '>> ')

          case hasp.hasp_constants.HASP_INV_HND:
            print('handle not active');

          case hasp.hasp_constants.HASP_INV_FILEID:
            print('invalid file id');

          case hasp.hasp_constants.HASP_MEM_RANGE:
            print('beyond memory range of attached Sentinel key');

          case hasp.hasp_constants.HASP_CONTAINER_NOT_FOUND:
            print('beyond memory range of attached Sentinel key');
          case _:
            print('print memory failed');

        
        if status != 0:
            exit(1);

        '''*************************************************
          * hasp_read
          *   read from Sentinel key memory
        *************************************************'''

        print('reading ', str(fsize), ' bytes from memory   : ');
        mem_buffer = ctypes.create_string_buffer(fsize[0])
        status = hasp.hasp_read(handle, hasp.hasp_constants.HASP_FILEID_RW, 0, fsize[0], mem_buffer);

        match status:

          case hasp.hasp_constants.HASP_STATUS_OK:
              print('OK');
              dump(mem_buffer.raw, '>> ')
              

          case hasp.hasp_constants.HASP_INV_HND:
              print('handle not active');

          case hasp.hasp_constants.HASP_INV_FILEID:
              print('invalid file id');

          case hasp.hasp_constants.HASP_MEM_RANGE:
              print('beyond memory range of attached Sentinel key');

          case hasp.hasp_constants.HASP_CONTAINER_NOT_FOUND:
              print('key/license container not available');

          case _:
              print('read memory failed');

        if status != 0:
            exit(1);

    print;
    print('Encrypting a data buffer:');
    encrypt_decrypt(handle);

    '''*************************************************
    * hasp_get_rtc
    *   read current time from Sentinel Time key.
    *************************************************'''

    print('reading current time and date    : ');
    time = []
    status = hasp.hasp_get_rtc(handle, time);

    match status:

        case hasp.hasp_constants.HASP_STATUS_OK:
            day = []
            month = []
            year = []
            hour = []
            minute = []
            second = []
            
            status = hasp.hasp_hasptime_to_datetime(time[0], day, month, year, hour, minute, second);

            match status:
              case hasp.hasp_constants.HASP_STATUS_OK:
                  print('time: ' + str(hour[0]) + ':' +  str(minute[0]) +  ':' + str(second[0]) + ' H/M/S');
                  print('date: ' +  str(day[0]) + '/' + str(month[0])  + '/' +  str(year[0]) + ' D/M/Y');
              case hasp.hasp_constants.HASP_INV_TIME:
                  print('time value outside supported range');

              case _:
                  print('time conversion failed');

            if status != 0:
                exit(1);

        case hasp.hasp_constants.HASP_INV_HND:
          print('handle not active');

        case hasp.hasp_constants.HASP_NO_TIME:
          print('no Sentinel Time Key connected');

        case hasp.hasp_constants.HASP_CONTAINER_NOT_FOUND:
          print('key/license container not available');
        case _:
            print('could not read time from Sentinel key, status ', status);

    if status != 0:
        exit(1);

  
    '''*************************************************************
    * hasp_logout
    *   closes established session and releases allocated memory.
    *************************************************************'''
    print('logout from default feature      : ');
    status = hasp.hasp_logout(handle);
    match status:

        case hasp.hasp_constants.HASP_STATUS_OK:
          print('OK');

        case hasp.hasp_constants.HASP_INV_HND:
          print('failed: handle not active');

        case _:
            print('failed');

    if status != 0:
        exit(1);

    '''*****************************************************
    * hasp_login
    *
    *   establishes a context for Sentinel services.
    *   A new handle is used to log into another feature.
    *****************************************************'''
    print('login to feature ', str(CUSTOM_FEATURE), '            : ');
    handle2 = []
    status = hasp.hasp_login(CUSTOM_FEATURE, hasp_vcode.VENDOR_CODE, handle2);

    match status:

        case hasp.hasp_constants.HASP_STATUS_OK:
          print('OK');

        case hasp.hasp_constants.HASP_FEATURE_NOT_FOUND:
          print('no DemoMA Sentinel key found with feature ', CUSTOM_FEATURE);

        case hasp.hasp_constants.HASP_CONTAINER_NOT_FOUND:
          print('key not available');

        case hasp.hasp_constants.HASP_OLD_DRIVER:
          print('outdated driver version installed');

        case hasp.hasp_constants.HASP_NO_DRIVER:
          print('Sentinel driver not installed');

        case hasp.hasp_constants.HASP_INV_VCODE:
          print('invalid vendor code');

        case _:
            print('failed with status ', status);

    if status != 0:
        exit(1);

    print;
    print('encrypt/decrypt again to see different encryption for different features:');
    encrypt_decrypt(handle2);

    '''*************************************************************
    * hasp_logout
    *   closes established session and releases allocated memory.
    *************************************************************'''

    print('logout from feature ', str(CUSTOM_FEATURE), '         : ');
    status = hasp.hasp_logout(handle2);
    match status:
        case hasp.hasp_constants.HASP_STATUS_OK:
            print('OK');
        case hasp.hasp_constants.HASP_INV_HND:
            print('failed: handle not active');
        case _:
            print('failed with status ', status);

    if status != 0:
        exit(1);

    

  
    '''**************************************************
    * hasp_login_scope
    *   establishes a context for Sentinel services.
    *   Allows specification of serveral restrictions.
    **************************************************'''

    print('restricting the license to "local" :');

    print('hasp_login_scope                 : ');
    MY_SCOPE = '<haspscope />';
    handle = []
    status = hasp.hasp_login_scope(HASP_DEFAULT_FID, SCOPE, hasp_vcode.VENDOR_CODE, handle);

    match status:

        case hasp.hasp_constants.HASP_STATUS_OK:
          print('OK');

        case hasp.hasp_constants.HASP_FEATURE_NOT_FOUND:
          print('login to default feature failed');

        case hasp.hasp_constants.HASP_CONTAINER_NOT_FOUND:
          print('login to default feature failed');

        case hasp.hasp_constants.HASP_OLD_DRIVER:
          print('outdated driver version installed');

        case hasp.hasp_constants.HASP_NO_DRIVER:
          print('Sentinel driver not installed');

        case hasp.hasp_constants.HASP_INV_VCODE:
          print('invalid vendor code');

        case hasp.hasp_constants.HASP_INV_SCOPE:
          print('invalid XML scope');
        case _:
            print('login to default feature failed with status ', status);

    if status != 0:
        exit(1);

    print('getting information about connected keys and usage : ');

    MY_VIEW = """<haspformat root="my_custom_scope"> 
      <hasp>
        <attribute name="id" />
        <attribute name="type" />
        <feature>
          <attribute name="id" />
          <element name="concurrency" />
          <element name="license" />
          <session>
            <element name="username" />
            <element name="hostname" />
            <element name="ip" />
            <element name="apiversion" />
          </session>
        </feature>
      </hasp> 
    </haspformat>""";

    info.clear()
    status = hasp.hasp_get_info(MY_SCOPE, MY_VIEW ,hasp_vcode.VENDOR_CODE, info);

    match status:

        case hasp.hasp_constants.HASP_STATUS_OK:
            print('OK');
            print(info[0].value.decode());
            '''*****************************************************************
              * hasp_free
              *   frees memory allocated by hasp_get_info, hasp_get_sessioninfo
              *   or hasp_update (if an acknowledge was requested)
            *****************************************************************'''
            hasp.hasp_free(info[0]);

        case hasp.hasp_constants.HASP_INV_FORMAT:
          print('invalid XML info format');

        case hasp.hasp_constants.HASP_INV_SCOPE:
          print('invalid XML scope');

        case _:
            print('hasp_get_info failed with status ', status);

    if status != 0:
        exit(1);

    '''************************************************************
    * hasp_logout
    *   closes established session and releases allocated memory
    ************************************************************'''

    print('logout                           : ');
    status = hasp.hasp_logout(handle);

    match status:

        case hasp.hasp_constants.HASP_STATUS_OK:
          print('OK');

        case hasp.hasp_constants.HASP_INV_HND:
          print('failed: handle not active');

        case _:
            print('failed');

    if status != 0:
        exit(1);

    print('sample finished successfully!');

if __name__ == "__main__":
    main()
