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

import sys
import hasp_vcode
import ctypes
import string

MAX_MEMORY = 1024
HASP_DEFAULT_FID = 0
CUSTOM_FEATURE = 1402

# 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>""";

def usage():
    print();
    print('Usage:     hasp_update <option> [filename]');
    print;
    print('Options:   u: updates a Sentinel protection key/attaches a detached license');
    print('           i: retrieves Sentinel protection key information');
    print('           d: detaches a license from Sentinel SL key');
    print('           r: rehost a license from a Sentinel SL-AdminMode/SL-UserMode key');
    print('           f: retrieves fingerprint information');
    print;
    print('Filename:  Path to the V2C/R2H file (in case of update/attach),');
    print('              OR');
    print('           Path to the C2V/H2R file (uses stdout if filename is not specified)');
    print;

def get_file_contents(file_name) :
    file_to_read = open(file_name , "r+")
    file_contents = file_to_read.read()
    file_to_read.close()
    return file_contents

def write_to_file(file_name , file_data):
    file_to_write = open(file_name , "w")
    file_to_write.write(file_data)
    file_to_write.close()

def process_detach(info, file_name):
    recipient = info;

    #detach license for local recipient (duration 120 seconds.)
    action = """<?xml version="1.0" encoding="UTF-8" ?>
                    <detach>
                       <duration>120</duration>
                   </detach> """;
    scope = '<haspscope><product id="1242" /></haspscope>';

    h2r = []
    status = hasp.hasp_transfer(action, scope, hasp_vcode.VENDOR_CODE, recipient, h2r);

    match status :
        case hasp.hasp_constants.HASP_STATUS_OK :
            print(h2r[0].value.decode())
            if len(file_name) > 0 :
                write_to_file(file_name,h2r[0].value.decode())

        case hasp.hasp_constants.HASP_INV_DETACH_ACTION:
            print('Invalid XML detach_action parameter.');

        case hasp.hasp_constants.HASP_INV_RECIPIENT:
            print('Invalid XML recipient parameter.');

        case hasp.hasp_constants.HASP_TOO_MANY_PRODUCTS:
            print('Scope for hasp_detach does not select a unique product.');

        case hasp.hasp_constants.HASP_ACCESS_DENIED:
            print('Request denied because of LMS access restrictions.');

        case hasp.hasp_constants.HASP_INV_PRODUCT:
            print('Invalid product information.');

        case hasp.hasp_constants.HASP_INSUF_MEM:
            print('Out of memory');

        case hasp.hasp_constants.HASP_DEVICE_ERR:
            print('Input/Output error.');

        case hasp.hasp_constants.HASP_LOCAL_COMM_ERR:
            print('Communication error.');

        case hasp.hasp_constants.HASP_REMOTE_COMM_ERR:
            print('Remote communication error.');

        case hasp.hasp_constants.HASP_INV_SCOPE:
            print('Unrecognized scope string');

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

def detach_license(file_name):
    SCOPE = """<haspscope>
                <license_manager hostname="localhost" />
               </haspscope>  """;
   
    info = []
    #get local recipient information
    status = hasp.hasp_get_info(SCOPE, hasp.hasp_constants.HASP_RECIPIENT ,hasp_vcode.VENDOR_CODE , info);

    match status :
        case hasp.hasp_constants.HASP_STATUS_OK:
            process_detach(info[0] , file_name)

        case hasp.hasp_constants.HASP_SCOPE_RESULTS_EMPTY:
            print('Unable to locate a feature matching the scope.');

        case hasp.hasp_constants.HASP_INSUF_MEM:
            print('Out of memory.');

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

        case hasp.hasp_constants.HASP_UNKNOWN_VCODE:
            print('Unknown vendor code.');

        case hasp.hasp_constants.HASP_INVALID_PARAMETER:
            print('Invalid parameter (scope/format too long?).');

        case hasp.hasp_constants.HASP_DEVICE_ERR:
            print('Input/Output error.');

        case hasp.hasp_constants.HASP_LOCAL_COMM_ERR:
            print('Communication error.');

        case hasp.hasp_constants.HASP_REMOTE_COMM_ERR:
            print('Remote communication error.');

        case hasp.hasp_constants.HASP_TOO_MANY_KEYS:
            print('Too many keys connected.');

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

        case hasp.hasp_constants.HASP_INV_SCOPE:
            print('Unrecognized scope string.');

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

def rehost_license(file_name):
    scope = """ <haspscope>
                  <license_manager hostname="localhost" /> 
                </haspscope>""";
   
    info = []
    #get local recipient information }
    status = hasp.hasp_get_info(scope, hasp.hasp_constants.HASP_RECIPIENT, hasp_vcode.VENDOR_CODE, info);

    match status:

        case hasp.hasp_constants.HASP_STATUS_OK:
            recipient = info[0];

            '''hasp_transfer - This function is used to rehost the v2c form one machine
               to another machine. For this we will use the recipient
               information generated from hasp_get_info function. '''

            action = """<?xml version="1.0" encoding="UTF-8" ?>
                         <rehost><hasp id="139770659290457102"/></rehost> """;
            scope = '<haspscope><hasp id="139770659290457102"/></haspscope>';

            v2c = []
            status = hasp.hasp_transfer(action , scope , hasp_vcode.VENDOR_CODE , recipient, v2c);

            match status:

                case hasp.hasp_constants.HASP_STATUS_OK:
                    print(v2c[0].value.decode())
                    if len(file_name) > 0 :
                        write_to_file(file_name,v2c[0].value.decode())

                case hasp.hasp_constants.HASP_INV_DETACH_ACTION:
                    print('Invalid XML detach_action parameter.');

                case hasp.hasp_constants.HASP_INV_RECIPIENT:
                    print('Invalid XML recipient parameter.');

                case hasp.hasp_constants.HASP_TOO_MANY_PRODUCTS:
                    print('Scope for hasp_detach does not select a unique product.');

                case hasp.hasp_constants.HASP_ACCESS_DENIED:
                    print('Request denied because of LMS access restrictions.');

                case hasp.hasp_constants.HASP_INV_PRODUCT:
                    print('Invalid product information.');

                case hasp.hasp_constants.HASP_INSUF_MEM:
                    print('Out of memory');

                case hasp.hasp_constants.HASP_DEVICE_ERR:
                    print('Input/Output error.');

                case hasp.hasp_constants.HASP_LOCAL_COMM_ERR:
                    print('Communication error.');

                case hasp.hasp_constants.HASP_REMOTE_COMM_ERR:
                    print('Remote communication error.');

                case hasp.hasp_constants.HASP_INV_SCOPE:
                    print('Unrecognized scope string');

                case _:
                    print('hasp_transfer failed with status ', status);
                    
        case hasp.hasp_constants.HASP_SCOPE_RESULTS_EMPTY:
            print('Unable to locate a feature matching the scope.');

        case hasp.hasp_constants.HASP_INSUF_MEM:
            print('Out of memory.');

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

        case hasp.hasp_constants.HASP_UNKNOWN_VCODE:
            print('Unknown vendor code.');

        case hasp.hasp_constants.HASP_INVALID_PARAMETER:
            print('Invalid parameter (scope/format too long?).');

        case hasp.hasp_constants.HASP_DEVICE_ERR:
            print('Input/Output error.');

        case hasp.hasp_constants.HASP_LOCAL_COMM_ERR:
            print('Communication error.');

        case hasp.hasp_constants.HASP_REMOTE_COMM_ERR:
            print('Remote communication error.');

        case hasp.hasp_constants.HASP_TOO_MANY_KEYS:
            print('Too many keys connected.');

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

        case hasp.hasp_constants.HASP_INV_SCOPE:
            print('Unrecognized scope string.');

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

def update_license(file_name):
    info = []
    if len(file_name) > 0 :
        buffer = get_file_contents(file_name)
    else :
        print( 'v2c file name empty')
        exit(1)

    print(buffer)
    status = hasp.hasp_update(buffer, info);

    match status :

        case hasp.hasp_constants.HASP_STATUS_OK:
            if info[0] is not None and info[0].value is not None:
                print(info[0].value.decode())
        

        case hasp.hasp_constants.HASP_INV_UPDATE_DATA:
            print('Update data is invalid');

        case hasp.hasp_constants.HASP_INV_UPDATE_NOTSUPP:
            print('Update data is invalid');

        case hasp.hasp_constants.HASP_INV_UPDATE_CNTR:
            print('Update counter not set correctly');

        case hasp.hasp_constants.HASP_INSUF_MEM:
            print('Out of memory');

        case hasp.hasp_constants.HASP_DEVICE_ERR:
            print('Input/Output error');

        case hasp.hasp_constants.HASP_LOCAL_COMM_ERR:
            print('Communication error');

        case hasp.hasp_constants.HASP_NO_ACK_SPACE:
            print('Acknowledge pointer is NULL');

        case hasp.hasp_constants.HASP_UNKNOWN_ALG:
            print('Unknown v2c algorithm');

        case hasp.hasp_constants.HASP_INV_SIG:
            print('v2c signature broken');

        case hasp.hasp_constants.HASP_TOO_MANY_KEYS:
            print('Too many keys connected');

        case hasp.hasp_constants.HASP_HARDWARE_MODIFIED:
            print('v2c data and SL key data mismatch');

        case hasp.hasp_constants.HASP_UPDATE_TOO_OLD:
            print('v2c was already installed');

        case hasp.hasp_constants.HASP_UPDATE_TOO_NEW:
            print('v2c is too new (update counter too high)');

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

    return 0

def generate_c2v(file_name):
    ''' restrict the c2v to local Sentinel keys '''
    scope = """<haspscope>
                  <license_manager hostname="localhost" />
               </haspscope> """;

    info = []
    status = hasp.hasp_get_info(scope , hasp.hasp_constants.HASP_UPDATEINFO ,hasp_vcode.VENDOR_CODE , info);

    match status:

        case hasp.hasp_constants.HASP_STATUS_OK :
            print(info[0].value.decode())
            if len(file_name) > 0 :
                write_to_file(file_name,info[0].value.decode())

        case hasp.hasp_constants.HASP_SCOPE_RESULTS_EMPTY:
            print('Unable to locate a Feature matching the scope');

        case hasp.hasp_constants.HASP_INSUF_MEM:
            print('Out of memory');

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

        case hasp.hasp_constants.HASP_UNKNOWN_VCODE:
            print('Unknown vendor code');

        case hasp.hasp_constants.HASP_INVALID_PARAMETER:
            print('Invalid parameter (scope/format too long?)');

        case hasp.hasp_constants.HASP_DEVICE_ERR:
            print('Input/Output error');

        case hasp.hasp_constants.HASP_LOCAL_COMM_ERR:
            print('Communication error');

        case hasp.hasp_constants.HASP_REMOTE_COMM_ERR:
            print('Remote communication error');

        case hasp.hasp_constants.HASP_TOO_MANY_KEYS:
            print('Too many keys connected');

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

        case hasp.hasp_constants.HASP_INV_SCOPE:
            print('Unrecognized scope string');

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

    return 0
def retrieves_fingerprint(file_name):
    ''' restrict the c2v to local Sentinel keys '''
    scope = """<haspscope>
                  <license_manager hostname="localhost" />
               </haspscope> """;
    info = []
    
    status = hasp.hasp_get_info(scope, hasp.hasp_constants.HASP_FINGERPRINT_INFO , hasp_vcode.VENDOR_CODE , info);
    
    match status:    
        
        case hasp.hasp_constants.HASP_STATUS_OK :
            print(info[0].value.decode())
            if len(file_name) > 0 :
                write_to_file(file_name,info[0].value.decode())
        
        case hasp.hasp_constants.HASP_SCOPE_RESULTS_EMPTY:
            print('Unable to locate a Feature matching the scope');

        case hasp.hasp_constants.HASP_INSUF_MEM:
            print('Out of memory');

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

        case hasp.hasp_constants.HASP_UNKNOWN_VCODE:
            print('Unknown vendor code');

        case hasp.hasp_constants.HASP_INVALID_PARAMETER:
            print('Invalid parameter (scope/format too long?)');

        case hasp.hasp_constants.HASP_DEVICE_ERR:
            print('Input/Output error');

        case hasp.hasp_constants.HASP_LOCAL_COMM_ERR:
            print('Communication error');

        case hasp.hasp_constants.HASP_REMOTE_COMM_ERR:
            print('Remote communication error');

        case hasp.hasp_constants.HASP_TOO_MANY_KEYS:
            print('Too many keys connected');

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

        case hasp.hasp_constants.HASP_INV_SCOPE:
            print('Unrecognized scope string');

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

    return 0

    
def main():
    print('This is a simple demo program for the Sentinel LDK update functions');
    print('Copyright (C) THALES. All rights reserved.');
    print;

    if len(sys.argv) <=  1 :
        usage()
        sys.exit(0)

    if len(sys.argv) > 2 :
        file_name = sys.argv[2]
    else:
        file_name = ""
    

    match sys.argv[1] :
        case 'd':
            detach_license(file_name)

        case 'r':
            rehost_license(file_name)

        case 'u':
            update_license(file_name)

        case 'i':
            generate_c2v(file_name)
        case 'f':
            retrieves_fingerprint(file_name)
        case _:
            usage()


if __name__ == "__main__":
    main()
