#!/bin/sh

metadata_get() {
    fastboot -s "${TARGET_DEVICE_SERIAL}" getvar "$1" 2>&1 | grep -oP "${1}"': \K[^\r\n]*' || true
}

# Register device identity with the Raspberry Pi Connect management API.
# Requires RPI_CONNECT_API_KEY to be set in config.
#
# Signing is performed on-device via "oem fwcrypto sign-hash" so the
# device's ECDSA private key never leaves the firmware crypto hardware.
# The public key is retrieved via "getvar public-key".
#
# Sets CONNECT_REGISTERED_VALUE (NULL/0/1) and CONNECT_DEVICE_ID_VALUE
# for inclusion in the manufacturing database INSERT.
#
# Non-fatal: logs warnings on failure and allows provisioning to continue.
connect_register_device() {
    CONNECT_REGISTERED_VALUE="NULL"
    CONNECT_DEVICE_ID_VALUE=""

    # Skip if no API key configured
    if [ -z "${RPI_CONNECT_API_KEY}" ]; then
        return 0
    fi

    announce_start "Connect device identity registration"

    if ! command -v jq >/dev/null 2>&1 || ! command -v curl >/dev/null 2>&1; then
        log "WARNING: jq and curl are required for Connect registration, skipping"
        CONNECT_REGISTERED_VALUE="0"
        announce_stop "Connect device identity registration: missing dependencies"
        return 0
    fi

    _connect_tmpdir=$(mktemp -d)

    # Retrieve public key PEM from device.  Cannot use get_variable() here
    # because it truncates at the first newline and PEM is multiline.
    # Instead capture the full fastboot output and extract the PEM block.
    _connect_fb_output=$(fastboot -s "${FASTBOOT_DEVICE_SPECIFIER}" getvar public-key 2>&1 | tr -d '\r') || true
    _connect_public_key_pem=$(printf '%s\n' "${_connect_fb_output}" | \
        sed -n '/-----BEGIN PUBLIC KEY-----/,/-----END PUBLIC KEY-----/{
            s/^.*\(-----BEGIN PUBLIC KEY-----\)/\1/
            p
        }')
    if [ -z "${_connect_public_key_pem}" ]; then
        log "WARNING: Could not retrieve device public key, skipping Connect registration"
        log "fastboot output: ${_connect_fb_output}"
        CONNECT_REGISTERED_VALUE="0"
        rm -rf "${_connect_tmpdir}"
        announce_stop "Connect device identity registration: no public key"
        return 0
    fi

    # Build description: optional user prefix + board type + serial
    _connect_desc=""
    if [ -n "${RPI_CONNECT_DESCRIPTION}" ]; then
        _connect_desc="${RPI_CONNECT_DESCRIPTION} "
    fi
    _connect_desc="${_connect_desc}${BOARD_STR} ${TARGET_DEVICE_SERIAL}"

    _connect_base_url="${CONNECT_BASE_URL:-https://api.connect.raspberrypi.com}"
    _connect_url="${_connect_base_url}/organisation/device-identities"

    # Build JSON body (jq handles PEM newline escaping)
    _connect_body=$(jq -nc \
        --arg pk "${_connect_public_key_pem}" \
        --arg desc "${_connect_desc}" \
        '{public_key: $pk, description: $desc}')

    # SHA256 hex digest of the request body
    _connect_body_hash=$(printf '%s' "${_connect_body}" | openssl dgst -sha256 -hex)
    _connect_body_hash="${_connect_body_hash##* }"

    # Signature payload matching the Connect SignatureVerification.build_payload
    # protocol.  Only includes headers we actually send.
    _connect_sig_payload="POST
${_connect_url}
Authorization: Bearer ${RPI_CONNECT_API_KEY}
Content-Type: application/json
${_connect_body_hash}"

    # Hash the signature payload on the host, then have the device perform
    # the ECDSA sign via firmware crypto (private key never leaves hardware).
    _connect_payload_hash=$(printf '%s' "${_connect_sig_payload}" | openssl dgst -sha256 -hex)
    _connect_payload_hash="${_connect_payload_hash##* }"

    _connect_sign_result=$(fastboot -s "${FASTBOOT_DEVICE_SPECIFIER}" \
        oem fwcrypto sign-hash "${_connect_payload_hash}" 2>&1) || true
    _connect_signature=$(printf '%s' "${_connect_sign_result}" | \
        grep -oP '\(bootloader\) connect-signature:\K.*' || true)

    if [ -z "${_connect_signature}" ]; then
        log "WARNING: Device-side signing failed, skipping Connect registration"
        log "fastboot output: ${_connect_sign_result}"
        CONNECT_REGISTERED_VALUE="0"
        rm -rf "${_connect_tmpdir}"
        announce_stop "Connect device identity registration: signing failed"
        return 0
    fi

    # POST to Connect management API
    # -k: allow self-signed certs for local development environments
    # -H "Accept:": suppress curl's default Accept header to avoid signature mismatch
    # --connect-timeout/--max-time: bound the request so a network issue
    #   cannot block provisioning indefinitely
    _connect_http_code=$(curl -sk \
        --connect-timeout 10 --max-time 30 \
        -o "${_connect_tmpdir}/response.json" \
        -w '%{http_code}' \
        -X POST "${_connect_url}" \
        -H "Authorization: Bearer ${RPI_CONNECT_API_KEY}" \
        -H "Content-Type: application/json" \
        -H "Accept:" \
        -H "X-Connect-Identity-Signature: ${_connect_signature}" \
        -d "${_connect_body}")

    if [ "${_connect_http_code}" = "201" ]; then
        CONNECT_REGISTERED_VALUE="1"
        if [ -s "${_connect_tmpdir}/response.json" ]; then
            CONNECT_DEVICE_ID_VALUE=$(jq -r '.id // empty' "${_connect_tmpdir}/response.json" 2>/dev/null || true)
        fi
        log "Connect device identity registered (ID: ${CONNECT_DEVICE_ID_VALUE})"
    else
        CONNECT_REGISTERED_VALUE="0"
        log "WARNING: Connect device identity registration failed (HTTP ${_connect_http_code})"
        if [ -s "${_connect_tmpdir}/response.json" ]; then
            log "Response: $(cat "${_connect_tmpdir}/response.json")"
        fi
    fi

    rm -rf "${_connect_tmpdir}"
    announce_stop "Connect device identity registration"
}

metadata_gather() {
    announce_start "Metadata extraction"
    
    ETH_MAC_ADDRESS=$(metadata_get "mac-ethernet")
    WIFI_MAC_ADDRESS=$(metadata_get "mac-wifi")
    BT_MAC_ADDRESS=$(metadata_get "mac-bt")
    
    MMC_SECTOR_SIZE=$(metadata_get "mmc-sector-size")
    MMC_SECTOR_SIZE=${MMC_SECTOR_SIZE:-0}
    MMC_SECTOR_COUNT=$(metadata_get "mmc-sector-count")
    MMC_SECTOR_COUNT=${MMC_SECTOR_COUNT:-0}
    MMC_SIZE=$(( MMC_SECTOR_SIZE * MMC_SECTOR_COUNT ))
    MMC_CID=$(metadata_get "mmc-cid")
    MMC_CID=${MMC_CID:-""}
    
    RPI_DUID=$(metadata_get "rpi-duid")
    
    TYPE=$(metadata_get "revision-type")
    PROCESSOR=$(metadata_get "revision-processor")
    SDRAM_SIZE_BYTES=$(metadata_get "sdram-size-bytes")
    MEMORY=$(metadata_get "revision-memory")
    MANUFACTURER=$(metadata_get "revision-manufacturer")
    REVISION=$(metadata_get "revision-revision")
    SECURE=$(metadata_get "secure")
    # fastboot's GetSecure returns "yes"/"no"/"error" (see
    # rpi-fastbootd variables.cpp GetSignedOtp/GetSecure); normalise
    # to the 1/0 the schema and downstream comparisons expect.
    case "${SECURE}" in
        yes) SECURE="1" ;;
        no)  SECURE="0" ;;
        *)   SECURE=""  ;;
    esac

    case ${TYPE} in
        "0x06") BOARD_STR="CM1" ;;
        "0x08") BOARD_STR="3B" ;;
        "0x09") BOARD_STR="Zero" ;;
        "0x0A") BOARD_STR="CM3" ;;
        "0x0C") BOARD_STR="Zero W" ;;
        "0x0D") BOARD_STR="3B+" ;;
        "0x0E") BOARD_STR="3A+" ;;
        "0x10") BOARD_STR="CM3+" ;;
        "0x11") BOARD_STR="4B" ;;
        "0x12") BOARD_STR="Zero 2 W" ;;
        "0x13") BOARD_STR="400" ;;
        "0x14") BOARD_STR="CM4" ;;
        "0x15") BOARD_STR="CM4S" ;;
        "0x17") BOARD_STR="5" ;;
        "0x18") BOARD_STR="CM5" ;;
        "0x19") BOARD_STR="500" ;;
        "0x1A") BOARD_STR="CM5 Lite" ;;
        *)
            BOARD_STR="Unsupported Board: ${TYPE}"
    esac
    
    case ${PROCESSOR} in
        "0x0") PROCESSOR_STR="BCM2835" ;;
        "0x1") PROCESSOR_STR="BCM2836" ;;
        "0x2") PROCESSOR_STR="BCM2837" ;;
        "0x3") PROCESSOR_STR="BCM2711" ;;
        "0x4") PROCESSOR_STR="BCM2712" ;;
        *)
            PROCESSOR_STR="Unknown: ${PROCESSOR}"
    esac
    
    if [ -n "${SDRAM_SIZE_BYTES}" ] && [ "${SDRAM_SIZE_BYTES}" -gt 0 ] 2>/dev/null; then
        # Use DT-derived SDRAM size (more durable than OTP code lookup)
        SDRAM_MB=$(( SDRAM_SIZE_BYTES / 1000 / 1000 ))
        if [ "${SDRAM_MB}" -ge 1000 ]; then
            MEMORY_STR="$(( SDRAM_MB / 1000 ))GB"
        else
            MEMORY_STR="${SDRAM_MB}MB"
        fi
    else
        # Fallback to OTP revision code
        case ${MEMORY} in
            "0x0") MEMORY_STR="256MB" ;;
            "0x1") MEMORY_STR="512MB" ;;
            "0x2") MEMORY_STR="1GB" ;;
            "0x3") MEMORY_STR="2GB" ;;
            "0x4") MEMORY_STR="4GB" ;;
            "0x5") MEMORY_STR="8GB" ;;
            "0x6") MEMORY_STR="16GB" ;;
            *)
                MEMORY_STR="Unknown: ${MEMORY}"
        esac
    fi
    
    case ${MANUFACTURER} in
        "0x0") MANUFACTURER_STR="Sony UK" ;;
        "0x1") MANUFACTURER_STR="Egoman" ;;
        "0x2") MANUFACTURER_STR="Embest" ;;
        "0x3") MANUFACTURER_STR="Sony Japan" ;;
        "0x4") MANUFACTURER_STR="Embest" ;;
        "0x5") MANUFACTURER_STR="Stadium" ;;
        *)
            MANUFACTURER_STR="Unknown: ${MANUFACTURER}"
    esac
    
    echo "Board is: ${BOARD_STR}, with revision number ${REVISION}. Has Processor ${PROCESSOR_STR} with Memory ${MEMORY_STR}. Was manufactured by ${MANUFACTURER_STR}"
    
    if [ -f "${RPI_SB_PROVISIONER_MANUFACTURING_DB}" ]; then
        announce_start "Manufacturing Database Insertion"
        check_command_exists sqlite3
        
        # Ensure WAL journal mode
        sqlite3 "${RPI_SB_PROVISIONER_MANUFACTURING_DB}" "PRAGMA journal_mode=WAL;" > /dev/null 2>&1
        
        # Define the schema for devices table
        # Security tracking fields default to NULL to distinguish between not-applied vs unknown state
        EXPECTED_SCHEMA="id              integer primary key,
                    boardname       varchar(255)        not null,
                    serial          char(8)             not null,
                    eth_mac         char(17)            not null,
                    wifi_mac        char(17)            not null,
                    bt_mac          char(17)            not null,
                    mmc_size        integer             not null,
                    mmc_cid         char(32)            not null,
                    rpi_duid        char(32)            not null,
                    board_revision  varchar(255)        not null,
                    processor       varchar(255)        not null,
                    memory          varchar(255)        not null,
                    manufacturer    varchar(255)        not null,
                    secure          integer             not null,
                    jtag_locked     integer             DEFAULT NULL,
                    eeprom_write_protected integer      DEFAULT NULL,
                    pubkey_programmed integer           DEFAULT NULL,
                    devkey_revoked   integer           DEFAULT NULL,
                    signed_boot_enabled integer         DEFAULT NULL,
                    os_image_filename varchar(255)      DEFAULT NULL,
                    os_image_sha256  char(64)           DEFAULT NULL,
                    connect_registered integer          DEFAULT NULL,
                    connect_device_id varchar(255)      DEFAULT NULL,
                    eeprom_size     integer             DEFAULT NULL,
                    eeprom_jedec    char(6)             DEFAULT NULL,
                    eeprom_unique_id varchar(32)        DEFAULT NULL,
                    bootloader_build_timestamp integer  DEFAULT NULL,
                    provision_ts    timestamp           default current_timestamp"

        # Check if the table exists
        TABLE_EXISTS=$(sqlite3 "${RPI_SB_PROVISIONER_MANUFACTURING_DB}" "SELECT count(*) FROM sqlite_master WHERE type='table' AND name='devices';")

        if [ "$TABLE_EXISTS" -eq 0 ]; then
            # Table doesn't exist, create it
            sqlite3 "${RPI_SB_PROVISIONER_MANUFACTURING_DB}" "CREATE TABLE devices($EXPECTED_SCHEMA);" > /dev/null 2>&1
        fi
        
        # Security flags: record observed state from the device, not
        # provisioning intent. Each flag maps a firmware-published
        # getvar to 1/0/NULL. "not available" (the device-tree node is
        # absent on older firmware) and any unexpected value fall
        # through to NULL.
        signed_flag_value() {
            case "$(printf "%s" "$1" | tr '[:upper:]' '[:lower:]')" in
                present)            echo "1" ;;
                "not present")      echo "0" ;;
                *)                  echo "NULL" ;;
            esac
        }

        # Customer pubkey hash programmed in OTP (bit 3 of
        # /proc/device-tree/chosen/bootloader/signed). When present,
        # firmware authenticates boot.img against this hash.
        PUBKEY_PROGRAMMED_VALUE=$(signed_flag_value "$(metadata_get "secure-otp")")

        # Development-key revocation bit (bit 2). 'present' means the
        # in-firmware dev key has been revoked.
        DEVKEY_REVOKED_VALUE=$(signed_flag_value "$(metadata_get "secure-devkey")")

        # Signed boot enforcement is observable via the customer pubkey
        # hash being programmed -- firmware will only honour signed
        # boot.img once that hash is present.
        SIGNED_BOOT_VALUE="${PUBKEY_PROGRAMMED_VALUE}"

        # JTAG-lock and EEPROM-write-protect are not currently
        # observable via fastboot -- the provisioner applies them via
        # signed config.txt directives (program_jtag_lock /
        # eeprom_write_protect) but cannot read the resulting OTP /
        # SPI-WP state back. Record provisioning intent so the UI can
        # surface a "Requested" state; switch these to true observed
        # reads once rpi-fastbootd grows the corresponding getvars.
        JTAG_LOCKED_VALUE="NULL"
        if [ -n "${RPI_DEVICE_LOCK_JTAG}" ]; then
            JTAG_LOCKED_VALUE="1"
        elif [ "${SECURE}" = "1" ]; then
            JTAG_LOCKED_VALUE="0"
        fi

        EEPROM_WP_VALUE="NULL"
        if [ -n "${RPI_DEVICE_EEPROM_WP_SET}" ]; then
            EEPROM_WP_VALUE="1"
        elif [ "${SECURE}" = "1" ]; then
            EEPROM_WP_VALUE="0"
        fi

        # Bootloader SPI flash identity + state, captured from fastbootd's
        # in-process libflashrom + /dev/spidev queries. Each getvar reports
        # "not available" on older firmware or non-applicable platforms;
        # normalise to empty so the column ends up NULL rather than the
        # literal string.
        eeprom_value() {
            v=$(metadata_get "$1")
            case "${v}" in
                ""|"not available") echo "" ;;
                *) echo "${v}" ;;
            esac
        }
        EEPROM_SIZE=$(eeprom_value "eeprom-size")
        EEPROM_JEDEC=$(eeprom_value "eeprom-jedec")
        EEPROM_UNIQUE_ID=$(eeprom_value "eeprom-unique-id")
        BOOTLOADER_BUILD_TS=$(eeprom_value "bootloader-build-timestamp")

        # Integer columns: convert empty to literal NULL so the SQL is valid.
        sql_int() { if [ -z "$1" ]; then echo "NULL"; else echo "$1"; fi; }
        # String columns: render as a quoted SQL literal, or the bare token
        # NULL when the value is absent. Single-quotes inside the value are
        # SQL-escaped by doubling. This preserves the distinction between
        # "not measured" (NULL) and "measured as empty" ('').
        sql_str() {
            if [ -z "$1" ]; then
                echo "NULL"
            else
                printf "'%s'" "$(printf '%s' "$1" | sed "s/'/''/g")"
            fi
        }
        EEPROM_SIZE_SQL=$(sql_int "${EEPROM_SIZE}")
        BOOTLOADER_BUILD_TS_SQL=$(sql_int "${BOOTLOADER_BUILD_TS}")
        EEPROM_JEDEC_SQL=$(sql_str "${EEPROM_JEDEC}")
        EEPROM_UNIQUE_ID_SQL=$(sql_str "${EEPROM_UNIQUE_ID}")

	        # OS image identifier: use the same one-line summary that
	        # state.db carries during provisioning so the device-details
	        # page stays coherent before and after the manufacturing record
	        # is written.  compute_image_summary is provided by
	        # state-recording and was called by the calling provisioner
	        # right after read_config.
	        OS_IMAGE_FILENAME="${IMAGE_SUMMARY:-}"
	        OS_IMAGE_SHA256=""
	        if [ -n "${GOLD_MASTER_OS_FILE}" ] && [ -f "${GOLD_MASTER_OS_FILE}" ]; then
	            # Prefer sidecar file written by UI backend: <image>.sha256 (first token/line)
	            if [ -f "${GOLD_MASTER_OS_FILE}.sha256" ]; then
	                OS_IMAGE_SHA256="$(head -n1 "${GOLD_MASTER_OS_FILE}.sha256" | awk '{print $1}')"
	            fi
	            # Fallback to calculating if sidecar missing or empty
	            if [ -z "${OS_IMAGE_SHA256}" ] && command -v sha256sum >/dev/null 2>&1; then
	                OS_IMAGE_SHA256="$(sha256sum "${GOLD_MASTER_OS_FILE}" | awk '{print $1}')"
	                # Best-effort: persist to sidecar for reuse by UI and future runs
	                printf '%s\n' "${OS_IMAGE_SHA256}" > "${GOLD_MASTER_OS_FILE}.sha256" 2>/dev/null || true
	            fi
	        elif [ -n "${GOLD_MASTER_OS_FILE}" ] && [ -d "${GOLD_MASTER_OS_FILE}" ]; then
	            # IDP artefact directory: read the archive SHA256 from the
	            # sidecar written by the UI at upload time (<directory>.sha256).
	            # This is the hash of the original .tar.xz/.tar.zst archive,
	            # consistent with what the web UI displays.
	            if [ -f "${GOLD_MASTER_OS_FILE}.sha256" ]; then
	                OS_IMAGE_SHA256="$(head -n1 "${GOLD_MASTER_OS_FILE}.sha256" | awk '{print $1}')"
	            fi
	        fi

        # Register with Raspberry Pi Connect (if API key is configured).
        # Disable errexit: registration is non-fatal and must never abort
        # provisioning regardless of network, fastboot, or crypto failures.
        set +e
        connect_register_device
        set -e

        # Escape single quotes so the inline SQL stays well-formed when the
        # image summary carries spaces/parentheses/slashes (e.g. IDP one-liner).
        OS_IMAGE_FILENAME=$(printf '%s' "${OS_IMAGE_FILENAME}" | sed "s/'/''/g")

        # Insert new device data
        sqlite3 "${RPI_SB_PROVISIONER_MANUFACTURING_DB}" \
        "INSERT INTO devices(           \
                    boardname,                  \
                    serial,                     \
                    eth_mac,                    \
                    wifi_mac,                   \
                    bt_mac,                     \
                    mmc_size,                   \
                    mmc_cid,                    \
                    rpi_duid,                   \
                    board_revision,             \
                    processor,                  \
                    memory,                     \
                    manufacturer,               \
                    secure,                     \
                    jtag_locked,                \
                    eeprom_write_protected,     \
                    pubkey_programmed,          \
                    devkey_revoked,             \
                    signed_boot_enabled,        \
                    os_image_filename,          \
                    os_image_sha256,            \
                    connect_registered,         \
                    connect_device_id,          \
                    eeprom_size,                \
                    eeprom_jedec,               \
                    eeprom_unique_id,           \
                    bootloader_build_timestamp  \
                ) VALUES (                      \
                    '${BOARD_STR}',               \
                    '${TARGET_DEVICE_SERIAL}',    \
                    '${ETH_MAC_ADDRESS}',         \
                    '${WIFI_MAC_ADDRESS}',        \
                    '${BT_MAC_ADDRESS}',          \
                    '${MMC_SIZE}',                \
                    '${MMC_CID}',                 \
                    '${RPI_DUID}',                \
                    '${REVISION}',                \
                    '${PROCESSOR_STR}',           \
                    '${MEMORY_STR}',              \
                    '${MANUFACTURER_STR}',        \
                    '${SECURE}',                  \
                    ${JTAG_LOCKED_VALUE},         \
                    ${EEPROM_WP_VALUE},           \
                    ${PUBKEY_PROGRAMMED_VALUE},   \
                    ${DEVKEY_REVOKED_VALUE},      \
                    ${SIGNED_BOOT_VALUE},         \
                    '${OS_IMAGE_FILENAME}',       \
                    '${OS_IMAGE_SHA256}',         \
                    ${CONNECT_REGISTERED_VALUE},  \
                    '${CONNECT_DEVICE_ID_VALUE}', \
                    ${EEPROM_SIZE_SQL},           \
                    ${EEPROM_JEDEC_SQL},          \
                    ${EEPROM_UNIQUE_ID_SQL},      \
                    ${BOOTLOADER_BUILD_TS_SQL}    \
        );" > /dev/null 2>&1

        # Nudge the UI service to re-read after a manufacturing.db row write.
        # Without this push, manufacturing changes would rely on the next
        # record_state call (which happens to follow in the current
        # provisioning flow) -- an implicit ordering invariant we'd rather
        # not bake in. Fire-and-forget so a UI outage can't fail manufacturing.
        # See host-support/state-recording for the token rationale.
        notify_url=${PROVISIONER_MANUFACTURING_NOTIFY_URL:-http://127.0.0.1:3142/internal/manufacturing-recorded}
        if [ -r /run/rpi-sb-provisioner/internal.token ]; then
            internal_token=$(cat /run/rpi-sb-provisioner/internal.token 2>/dev/null || true)
            if [ -n "${internal_token}" ]; then
                curl --max-time 1 --connect-timeout 1 -fsS -X POST -o /dev/null \
                    -H "X-Internal-Token: ${internal_token}" \
                    "${notify_url}" >/dev/null 2>&1 || true
            fi
        fi

        announce_stop "Manufacturing Database Insertion"
    fi
    announce_stop "Metadata extraction"
}
