import base64
import json
import os

from django.conf import settings
from waiting import wait, TimeoutExpired

from sandbox.apps.devices.core.emulators import emulators_controller
from sandbox.libs.logs.core.ws import ws_log_message
from sandbox.apps.provision.core.common import provision_generated_predicate
from sandbox.libs.context.models import SandboxActions
from sandbox.apps.devices.models import Device
from sandbox.libs.cmd_process import CmdProcessInteractive
from sandbox.libs.decorators import threaded_action
from sandbox.libs.logger import logger_by_name
from sandbox.libs.utils import find_files


REQUESTS_FILE = os.path.join(settings.SANDBOX_TEMP_DIR, 'card-requests.txt')
DEVICE_INFO_FILE = os.path.join(settings.SANDBOX_TEMP_DIR, 'device-info.txt')


@threaded_action(action=SandboxActions.PERFORM_PROVISION,
                 forbidden_on=[SandboxActions.GENERATE_PROVISION,
                               SandboxActions.PERFORM_PROVISION,
                               SandboxActions.REGISTER_DEVICES,
                               SandboxActions.PUBLISH_NEW_TRUST_LIST],
                 predicate=provision_generated_predicate)
def perform_provision():
    """
    1) Start initializers emulators, if needed
    2) Perform provision, by launching Virgil Device Initializer
    """
    # Find devices are needed to be provisioned
    to_be_provisioned = [d for d in Device.objects.all() if d.is_ready_for_provision]
    # Prepare list of mac addresses of emulators
    macs = [d.mac for d in to_be_provisioned if d.is_emulator]
    try:
        # Start initializers emulators
        emulators_controller.start_initializers(macs)
        # Start Device Initializer
        initializer = _get_initializer_process()
        initializer.run_in_thread()
        # Wait for Initializer return code
        timeout = len(to_be_provisioned) * 10  # give up to 10 seconds to each initializer
        try:
            wait(lambda: initializer.return_code == 0,
                 timeout_seconds=timeout,
                 waiting_for='Initializer successfully finished')
        except TimeoutExpired:
            raise Exception(f'Failed to initialize devices during {timeout} seconds\n'
                            f'Virgil Device Initializer return code: {initializer.return_code}')
    finally:
        emulators_controller.stop_initializers(macs)


def _get_initializer_process():
    pp_dir = settings.SANDBOX_PROVISION_PACK_DIR

    # Prepare params for initializer launch
    auth_pub_key_1, auth_pub_key_2 = find_files(pp_dir, 'auth_.*pub', regex=True)
    rec_pub_key_1, rec_pub_key_2 = find_files(pp_dir, 'recovery_.*pub', regex=True)
    tl_pub_key_1, tl_pub_key_2 = find_files(pp_dir, 'tl_.*pub', regex=True)
    fw_pub_key_1, fw_pub_key_2 = find_files(pp_dir, 'firmware_.*pub', regex=True)
    trust_list, *_ = find_files(pp_dir, 'TrustList_')
    factory_key, *_ = find_files(pp_dir, 'factory_.*key', regex=True)

    # Prepare process
    cmd = (
        f'virgil-device-initializer '
        f'--output "{REQUESTS_FILE}" '
        f'--device_info_output "{DEVICE_INFO_FILE}" '
        f'--auth_pub_key_1 "{auth_pub_key_1}" '
        f'--auth_pub_key_2 "{auth_pub_key_2}" '
        f'--rec_pub_key_1 "{rec_pub_key_1}" '
        f'--rec_pub_key_2 "{rec_pub_key_2}" '
        f'--tl_pub_key_1 "{tl_pub_key_1}" '
        f'--tl_pub_key_2 "{tl_pub_key_2}" '
        f'--fw_pub_key_1 "{fw_pub_key_1}" '
        f'--fw_pub_key_2 "{fw_pub_key_2}" '
        f'--trust_list "{trust_list}" '
        f'--factory_key "{factory_key}"'
    )

    logger = logger_by_name('virgil-device-initializer')
    process = CmdProcessInteractive(cmd, logger=logger)
    _register_callbacks(process)
    return process


def _register_callbacks(process):
    def on_initialized(line):
        card_request_b64 = line.split()[-1].strip()

        # Get mac of initialized thing
        card_request = base64.b64decode(card_request_b64).decode()
        signatures = json.loads(card_request).get('signatures')
        self_sign, *_ = [s for s in signatures if s.get('signer') == 'self']
        snapshot_b64 = self_sign.get('snapshot')
        snapshot = base64.b64decode(snapshot_b64).decode()
        mac = json.loads(snapshot).get('mac').lower()
        if not mac:
            raise Exception(f'Failed to extract mac from card request: {card_request_b64}')

        # Update device in model
        device = Device.objects.get(mac=mac)
        device.on_initialized(card_request_b64)

        # Log
        ws_log_message(f'Successfully initialized: {device.device_type} {mac}')

    # Assumed that when event occurs in log - device is initialized
    process.register_callback_on_event(on_initialized, 'Card request:')

import base64
import json
import os

from django.conf import settings
from waiting import wait, TimeoutExpired

from sandbox.apps.devices.core.emulators import emulators_controller
from sandbox.libs.logs.core.ws import ws_log_message
from sandbox.apps.provision.core.common import provision_generated_predicate
from sandbox.libs.context.models import SandboxActions
from sandbox.apps.devices.models import Device
from sandbox.libs.cmd_process import CmdProcessInteractive
from sandbox.libs.decorators import threaded_action
from sandbox.libs.logger import logger_by_name
from sandbox.libs.utils import find_files


REQUESTS_FILE = os.path.join(settings.SANDBOX_TEMP_DIR, 'card-requests.txt')
DEVICE_INFO_FILE = os.path.join(settings.SANDBOX_TEMP_DIR, 'device-info.txt')


@threaded_action(action=SandboxActions.PERFORM_PROVISION,
                 forbidden_on=[SandboxActions.GENERATE_PROVISION,
                               SandboxActions.PERFORM_PROVISION,
                               SandboxActions.REGISTER_DEVICES,
                               SandboxActions.PUBLISH_NEW_TRUST_LIST],
                 predicate=provision_generated_predicate)
def perform_provision():
    """
    1) Start initializers emulators, if needed
    2) Perform provision, by launching Virgil Device Initializer
    """
    # Find devices are needed to be provisioned
    to_be_provisioned = [d for d in Device.objects.all() if d.is_ready_for_provision]
    # Prepare list of mac addresses of emulators
    macs = [d.mac for d in to_be_provisioned if d.is_emulator]
    try:
        # Start initializers emulators
        emulators_controller.start_initializers(macs)
        # Start Device Initializer
        initializer = _get_initializer_process()
        initializer.run_in_thread()
        # Wait for Initializer return code
        timeout = len(to_be_provisioned) * 10  # give up to 10 seconds to each initializer
        try:
            wait(lambda: initializer.return_code == 0,
                 timeout_seconds=timeout,
                 waiting_for='Initializer successfully finished')
        except TimeoutExpired:
            raise Exception(f'Failed to initialize devices during {timeout} seconds\n'
                            f'Virgil Device Initializer return code: {initializer.return_code}')
    finally:
        emulators_controller.stop_initializers(macs)


def _get_initializer_process():
    pp_dir = settings.SANDBOX_PROVISION_PACK_DIR

    # Prepare params for initializer launch
    auth_pub_key_1, auth_pub_key_2 = find_files(pp_dir, 'auth_.*pub', regex=True)
    rec_pub_key_1, rec_pub_key_2 = find_files(pp_dir, 'recovery_.*pub', regex=True)
    tl_pub_key_1, tl_pub_key_2 = find_files(pp_dir, 'tl_.*pub', regex=True)
    fw_pub_key_1, fw_pub_key_2 = find_files(pp_dir, 'firmware_.*pub', regex=True)
    trust_list, *_ = find_files(pp_dir, 'TrustList_')
    factory_key, *_ = find_files(pp_dir, 'factory_.*key', regex=True)

    # Prepare process
    cmd = (
        f'virgil-device-initializer '
        f'--output "{REQUESTS_FILE}" '
        f'--device_info_output "{DEVICE_INFO_FILE}" '
        f'--auth_pub_key_1 "{auth_pub_key_1}" '
        f'--auth_pub_key_2 "{auth_pub_key_2}" '
        f'--rec_pub_key_1 "{rec_pub_key_1}" '
        f'--rec_pub_key_2 "{rec_pub_key_2}" '
        f'--tl_pub_key_1 "{tl_pub_key_1}" '
        f'--tl_pub_key_2 "{tl_pub_key_2}" '
        f'--fw_pub_key_1 "{fw_pub_key_1}" '
        f'--fw_pub_key_2 "{fw_pub_key_2}" '
        f'--trust_list "{trust_list}" '
        f'--factory_key "{factory_key}"'
    )

    logger = logger_by_name('virgil-device-initializer')
    process = CmdProcessInteractive(cmd, logger=logger)
    _register_callbacks(process)
    return process


def _register_callbacks(process):
    def on_initialized(line):
        card_request_b64 = line.split()[-1].strip()

        # Get mac of initialized thing
        card_request = base64.b64decode(card_request_b64).decode()
        signatures = json.loads(card_request).get('signatures')
        self_sign, *_ = [s for s in signatures if s.get('signer') == 'self']
        snapshot_b64 = self_sign.get('snapshot')
        snapshot = base64.b64decode(snapshot_b64).decode()
        mac = json.loads(snapshot).get('mac').lower()
        if not mac:
            raise Exception(f'Failed to extract mac from card request: {card_request_b64}')

        # Update device in model
        device = Device.objects.get(mac=mac)
        device.on_initialized(card_request_b64)

        # Log
        ws_log_message(f'Successfully initialized: {device.device_type} {mac}')

    # Assumed that when event occurs in log - device is initialized
    process.register_callback_on_event(on_initialized, 'Card request:')

1) Start initializers emulators, if needed     2) Perform provision, by launching Virgil Device Initializer # Find devices are needed to be provisioned # Prepare list of mac addresses of emulators # Start initializers emulators # Start Device Initializer # Wait for Initializer return code # give up to 10 seconds to each initializer # Prepare params for initializer launch # Prepare process # Get mac of initialized thing # Update device in model # Log # Assumed that when event occurs in log - device is initialized