/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <inttypes.h>
#include <string.h>
#include <stdint.h>
#include <sys/endian.h>

#include <variant/variant.h>
#include <eventnums.h>

#include <plat/taggedPtr.h>
#include <plat/plat.h>
#include <plat/wdt.h>

#include <nanohub/crc.h>
#include <nanohub/rsa.h>
#include <nanohub/nanohub.h>

#include <bl.h>
#include <atomicBitset.h>
#include <atomic.h>
#include <hostIntf.h>
#include <hostIntf_priv.h>
#include <nanohubCommand.h>
#include <nanohubPacket.h>
#include <eeData.h>
#include <seos.h>
#include <seos_priv.h>
#include <util.h>
#include <mpu.h>
#include <heap.h>
#include <slab.h>
#include <sensType.h>
#include <timer.h>
#include <appSec.h>
#include <cpu.h>
#include <cpu/cpuMath.h>
#include <algos/ap_hub_sync.h>
#include <sensors_priv.h>

#include <chre.h>

#define NANOHUB_COMMAND(_reason, _fastHandler, _handler, _minReqType, _maxReqType) \
        { .reason = _reason, .fastHandler = _fastHandler, .handler = _handler, \
          .minDataLen = sizeof(_minReqType), .maxDataLen = sizeof(_maxReqType) }

#define NANOHUB_HAL_LEGACY_COMMAND(_msg, _handler) \
        { .msg = _msg, .handler = _handler }

#define NANOHUB_HAL_COMMAND(_msg, _handler, _minReqType, _maxReqType) \
        { .msg = _msg, .handler = _handler, \
          .minDataLen = sizeof(_minReqType), .maxDataLen = sizeof(_maxReqType) }

// maximum number of bytes to feed into appSecRxData at once
// The bigger the number, the more time we block other event processing
// appSecRxData only feeds 16 bytes at a time into writeCbk, so large
// numbers don't buy us that much
#define MAX_APP_SEC_RX_DATA_LEN 64

#define REQUIRE_SIGNED_IMAGE    true
#define DEBUG_APHUB_TIME_SYNC   false

#if DEBUG_APHUB_TIME_SYNC
static void syncDebugAdd(uint64_t, uint64_t);
#endif

struct DownloadState
{
    struct AppSecState *appSecState;
    uint32_t size;      // document size, as reported by client
    uint32_t srcOffset; // bytes received from client
    uint32_t dstOffset; // bytes sent to flash
    struct AppHdr *start;     // start of flash segment, where to write
    uint32_t crc;       // document CRC-32, as reported by client
    uint32_t srcCrc;    // current state of CRC-32 we generate from input
    uint8_t  data[NANOHUB_PACKET_PAYLOAD_MAX];
    uint8_t  len;
    uint8_t  lenLeft;
    uint8_t  chunkReply;
    bool     erase;
    bool     eraseScheduled;
};

static struct DownloadState *mDownloadState;
static AppSecErr mAppSecStatus;
static struct AppHdr *mApp;
static struct SlabAllocator *mEventSlab;
static struct HostIntfDataBuffer mTxCurr, mTxNext;
static uint8_t mTxCurrLength, mTxNextLength;
static uint8_t mPrefetchActive, mPrefetchTx;
static uint32_t mTxWakeCnt[2];
static struct ApHubSync mTimeSync;

static inline bool isSensorEvent(uint32_t evtType)
{
    return evtType > EVT_NO_FIRST_SENSOR_EVENT && evtType <= EVT_NO_FIRST_SENSOR_EVENT + SENS_TYPE_LAST_USER;
}

static void slabFree(void *ptr)
{
    slabAllocatorFree(mEventSlab, ptr);
}

void nanohubInitCommand(void)
{
    mEventSlab = slabAllocatorNew(NANOHUB_PACKET_PAYLOAD_MAX-sizeof(__le32), 4, 2);
}

static inline uint64_t unaligned_u64(uint64_t *val) {
    uint64_t local;
    memcpy(&local, val, sizeof(local));
    return local;
}

static inline uint32_t unaligned_u32(uint32_t *val) {
    uint32_t local;
    memcpy(&local, val, sizeof(local));
    return local;
}

static uint32_t getOsHwVersion(void *rx, uint8_t rx_len, void *tx, uint64_t timestamp)
{
    struct NanohubOsHwVersionsResponse *resp = tx;
    resp->hwType = htole16(platHwType());
    resp->hwVer = htole16(platHwVer());
    resp->blVer = htole16(platBlVer());
    resp->osVer = htole16(OS_VER);
    resp->variantVer = htole32(VARIANT_VER);

    return sizeof(*resp);
}

static uint32_t getAppVersion(void *rx, uint8_t rx_len, void *tx, uint64_t timestamp)
{
    struct NanohubAppVersionsRequest *req = rx;
    struct NanohubAppVersionsResponse *resp = tx;
    uint32_t appIdx, appVer, appSize;

    if (osAppInfoById(le64toh(unaligned_u64(&req->appId)), &appIdx, &appVer, &appSize)) {
        resp->appVer = htole32(appVer);
        return sizeof(*resp);
    }

    return 0;
}

static uint32_t queryAppInfo(void *rx, uint8_t rx_len, void *tx, uint64_t timestamp)
{
    struct NanohubAppInfoRequest *req = rx;
    struct NanohubAppInfoResponse *resp = tx;
    uint64_t appId;
    uint32_t appVer, appSize;

    if (osAppInfoByIndex(le32toh(unaligned_u32(&req->appIdx)), &appId, &appVer, &appSize)) {
        resp->appId = htole64(appId);
        resp->appVer = htole32(appVer);
        resp->appSize = htole32(appSize);
        return sizeof(*resp);
    }

    return 0;
}

static AppSecErr writeCbk(const void *data, uint32_t len)
{
    AppSecErr ret = APP_SEC_BAD;

    if (osWriteShared((uint8_t*)(mDownloadState->start) + mDownloadState->dstOffset, data, len)) {
        ret = APP_SEC_NO_ERROR;
        mDownloadState->dstOffset += len;
    }

    return ret;
}

static AppSecErr pubKeyFindCbk(const uint32_t *gotKey, bool *foundP)
{
    const uint32_t *ptr;
    uint32_t numKeys, i;

    *foundP = false;
    ptr = BL.blGetPubKeysInfo(&numKeys);
    for (i = 0; ptr && i < numKeys; i++, ptr += RSA_LIMBS) {
        if (!memcmp(gotKey, ptr, RSA_BYTES)) {
            *foundP = true;
            break;
        }
    }

    return APP_SEC_NO_ERROR;
}

static AppSecErr osSecretKeyLookup(uint64_t keyId, void *keyBuf)
{
    struct SeosEedataEncrKeyData kd;
    void *state = NULL;

    while(1) {
        uint32_t sz = sizeof(struct SeosEedataEncrKeyData);

        if (!eeDataGetAllVersions(EE_DATA_NAME_ENCR_KEY, &kd, &sz, &state))
            break;

        if (sz == sizeof(struct SeosEedataEncrKeyData) && kd.keyID == keyId) {
            if (keyBuf)
                memcpy(keyBuf, kd.key, sizeof(kd.key));
            return APP_SEC_NO_ERROR;
        }
    }

    return APP_SEC_KEY_NOT_FOUND;
}

static AppSecErr osSecretKeyDelete(uint64_t keyId)
{
    struct SeosEedataEncrKeyData kd;
    void *state = NULL;
    bool good = true;
    int count = 0;

    while(1) {
        uint32_t sz = sizeof(struct SeosEedataEncrKeyData);
        void *addr = eeDataGetAllVersions(EE_DATA_NAME_ENCR_KEY, &kd, &sz, &state);

        if (!addr)
            break;

        if (sz == sizeof(kd) && kd.keyID == keyId) {
            good = eeDataEraseOldVersion(EE_DATA_NAME_ENCR_KEY, addr) && good;
            count++;
        }
    }

    return count == 0 ? APP_SEC_KEY_NOT_FOUND : good ? APP_SEC_NO_ERROR : APP_SEC_BAD;
}

static AppSecErr osSecretKeyAdd(uint64_t keyId, void *keyBuf)
{
    struct SeosEedataEncrKeyData kd;

    // do not add key if it already exists
    if (osSecretKeyLookup(keyId, NULL) != APP_SEC_KEY_NOT_FOUND)
        return APP_SEC_BAD;

    memcpy(&kd.key, keyBuf, 32);
    kd.keyID = keyId;

    return eeDataSet(EE_DATA_NAME_ENCR_KEY, &kd, sizeof(kd)) ? APP_SEC_NO_ERROR : APP_SEC_BAD;
}

static void freeDownloadState()
{
    if (mDownloadState->appSecState)
        appSecDeinit(mDownloadState->appSecState);
    heapFree(mDownloadState);
    mDownloadState = NULL;
}

static bool resetDownloadState(bool initial, bool erase)
{
    bool doCreate = true;

    mAppSecStatus = APP_SEC_NO_ERROR;
    if (mDownloadState->appSecState)
        appSecDeinit(mDownloadState->appSecState);
    mDownloadState->appSecState = appSecInit(writeCbk, pubKeyFindCbk, osSecretKeyLookup, REQUIRE_SIGNED_IMAGE);
    mDownloadState->srcOffset = 0;
    mDownloadState->srcCrc = ~0;
    if (!initial) {
        // if no data was written, we can reuse the same segment
        if (mDownloadState->dstOffset)
            osAppSegmentClose(mDownloadState->start, mDownloadState->dstOffset, SEG_ST_ERASED);
        else
            doCreate = false;
    }
    mDownloadState->dstOffset = 0;
    if (doCreate)
        mDownloadState->start = osAppSegmentCreate(mDownloadState->size);
    if (!mDownloadState->start) {
        if (erase)
            mDownloadState->erase = true;
        else
            return false;
    }
    return true;
}

static bool doStartFirmwareUpload(struct NanohubStartFirmwareUploadRequest *req, bool erase)
{
    if (!mDownloadState) {
        mDownloadState = heapAlloc(sizeof(struct DownloadState));

        if (!mDownloadState)
            return false;
        else
            memset(mDownloadState, 0x00, sizeof(struct DownloadState));
    }

    mDownloadState->size = le32toh(req->size);
    mDownloadState->crc = le32toh(req->crc);
    mDownloadState->chunkReply = NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED;
    return resetDownloadState(true, erase);
}

static uint32_t startFirmwareUpload(void *rx, uint8_t rx_len, void *tx, uint64_t timestamp)
{
    struct NanohubStartFirmwareUploadRequest *req = rx;
    struct NanohubStartFirmwareUploadResponse *resp = tx;

    resp->accepted = doStartFirmwareUpload(req, true);

    return sizeof(*resp);
}

static void deferredUpdateOs(void *cookie)
{
    const struct AppHdr *app = cookie;
    struct OsUpdateHdr *os = (struct OsUpdateHdr *)(&(app->hdr) + 1);
    uint32_t uploadStatus = OS_UPDT_HDR_CHECK_FAILED;
    uint8_t marker = OS_UPDT_MARKER_DOWNLOADED;
    struct Segment *seg = osGetSegment(app);
    uint32_t segSize = osSegmentGetSize(seg);

    osLog(LOG_INFO, "%s: checking OS image @ %p\n", __func__, os);
    // some sanity checks before asking BL to do image lookup
    hostIntfSetBusy(true);
    if (segSize >= (sizeof(*app) + sizeof(*os)) && segSize > os->size) {
        if (osWriteShared(&os->marker, &marker, sizeof(os->marker))) {
            wdtDisableClk();
            uploadStatus = BL.blVerifyOsUpdate();
            wdtEnableClk();
        } else {
            osLog(LOG_ERROR, "%s: could not set marker on OS image\n", __func__);
        }
    }
    hostIntfSetBusy(false);
    osLog(LOG_INFO, "%s: status=%" PRIu32 "\n", __func__, uploadStatus);
}

static AppSecErr updateKey(const struct AppHdr *app)
{
    AppSecErr ret;
    struct KeyInfo *ki = (struct KeyInfo *)(&(app->hdr) + 1);
    uint8_t *data = (uint8_t *)(ki + 1);
    uint64_t keyId = KEY_ID_MAKE(APP_ID_GET_VENDOR(app->hdr.appId), ki->id);
    const char *op;

    if ((app->hdr.fwFlags & FL_KEY_HDR_DELETE) != 0) {
        // removing existing key
        ret = osSecretKeyDelete(keyId);
        op = "Removing";
    } else {
        // adding new key
        ret = osSecretKeyAdd(keyId, data);
        op = "Adding";
    }
    osLog(LOG_INFO, "%s: %s key: id=%016" PRIX64 "; ret=%" PRIu32 "\n",
          __func__, op, keyId, ret);

    return ret;
}

static uint32_t appSecErrToNanohubReply(AppSecErr status)
{
    uint32_t reply;

    switch (status) {
    case APP_SEC_NO_ERROR:
        reply = NANOHUB_FIRMWARE_UPLOAD_SUCCESS;
        break;
    case APP_SEC_KEY_NOT_FOUND:
        reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_KEY_NOT_FOUND;
        break;
    case APP_SEC_HEADER_ERROR:
        reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_HEADER_ERROR;
        break;
    case APP_SEC_TOO_MUCH_DATA:
        reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_TOO_MUCH_DATA;
        break;
    case APP_SEC_TOO_LITTLE_DATA:
        reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_TOO_LITTLE_DATA;
        break;
    case APP_SEC_SIG_VERIFY_FAIL:
        reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_SIG_VERIFY_FAIL;
        break;
    case APP_SEC_SIG_DECODE_FAIL:
        reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_SIG_DECODE_FAIL;
        break;
    case APP_SEC_SIG_ROOT_UNKNOWN:
        reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_SIG_ROOT_UNKNOWN;
        break;
    case APP_SEC_MEMORY_ERROR:
        reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_MEMORY_ERROR;
        break;
    case APP_SEC_INVALID_DATA:
        reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_INVALID_DATA;
        break;
    case APP_SEC_VERIFY_FAILED:
        reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_VERIFY_FAILED;
        break;
    default:
        reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_BAD;
        break;
    }
    return reply;
}

static uint32_t firmwareFinish(bool valid)
{
    struct AppHdr *app;
    struct Segment *storageSeg;
    uint32_t segState;
    uint32_t ret = NANOHUB_FIRMWARE_UPLOAD_SUCCESS;

    if (!mDownloadState) {
        ret = appSecErrToNanohubReply(mAppSecStatus);
        osLog(LOG_INFO, "%s: no DL status; decoding secure status: %" PRIu32 "\n", __func__, ret);
        return ret;
    }

    app = mDownloadState->start;
    storageSeg = osGetSegment(app);

    if (mAppSecStatus == APP_SEC_NO_ERROR && valid) {
        osLog(LOG_INFO, "%s: Secure verification passed\n", __func__);
        if (storageSeg->state != SEG_ST_RESERVED ||
                mDownloadState->size < sizeof(struct FwCommonHdr) ||
                app->hdr.magic != APP_HDR_MAGIC ||
                app->hdr.fwVer != APP_HDR_VER_CUR) {
            segState = SEG_ST_ERASED;
            osLog(LOG_INFO, "%s: Header verification failed\n", __func__);
        } else {
            segState = SEG_ST_VALID;
        }
    } else {
        segState = SEG_ST_ERASED;
        osLog(LOG_INFO, "%s: Secure verification failed: valid=%d; status=%" PRIu32 "\n", __func__, valid, mAppSecStatus);
    }

    if (!osAppSegmentClose(app, mDownloadState->dstOffset, segState)) {
        osLog(LOG_INFO, "%s: Failed to close segment\n", __func__);
        valid = false;
        mApp = NULL;
    } else {
        segState = osAppSegmentGetState(app);
        mApp = app;
        valid = (segState == SEG_ST_VALID);
    }
    osLog(LOG_INFO, "Loaded %s image type %" PRIu8 ": %" PRIu32
                    " bytes @ %p; state=%02" PRIX32 "; crc=%08" PRIX32 "\n",
                    valid ? "valid" : "invalid",
                    app->hdr.payInfoType, mDownloadState->size,
                    mDownloadState->start, segState,
                    mApp ? osAppSegmentGetCrc(mApp) : 0xFFFFFFFF);

    freeDownloadState(); // no more access to mDownloadState

    if (!valid)
        ret = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_BAD;

    // take extra care about some special payload types
    if (ret == NANOHUB_FIRMWARE_UPLOAD_SUCCESS) {
        switch(app->hdr.payInfoType) {
        case LAYOUT_OS:
            osLog(LOG_INFO, "Performing OS update\n");
            // we want to give this message a chance to reach host before we start erasing stuff
            osDefer(deferredUpdateOs, (void*)app, false);
            break;
        case LAYOUT_KEY:
            ret = appSecErrToNanohubReply(updateKey(app));
            break;
        }
    }

    if (ret != NANOHUB_FIRMWARE_UPLOAD_SUCCESS || (app->hdr.fwFlags & FL_APP_HDR_VOLATILE)) {
        if ((app->hdr.fwFlags & FL_APP_HDR_SECURE))
            osAppWipeData((struct AppHdr*)app);
        osAppSegmentSetState(app, SEG_ST_ERASED);
    }

    // if any error happened after we downloaded and verified image, we say it is unknown fault
    // we don't have download status, so e have to save returned value in secure status field, because
    // host may request the same status multiple times
    if (ret != NANOHUB_FIRMWARE_UPLOAD_SUCCESS)
        mAppSecStatus = APP_SEC_BAD;

    return ret;
}

static void firmwareErase(void *cookie)
{
    if (mDownloadState->erase == true) {
        osLog(LOG_INFO, "%s: erasing shared area\n", __func__);
        osEraseShared();
        mDownloadState->start = osAppSegmentCreate(mDownloadState->size);
        if (!mDownloadState->start)
            firmwareFinish(false);
        mDownloadState->erase = false;
        hostIntfSetInterrupt(NANOHUB_INT_CMD_WAIT);
    }
    mDownloadState->eraseScheduled = false;
}

SET_PACKED_STRUCT_MODE_ON
struct FirmwareWriteCookie
{
    uint32_t evtType;
    union {
#ifdef LEGACY_HAL_ENABLED
        struct NanohubHalLegacyContUploadTx respLegacy;
#endif
        struct NanohubHalContUploadTx resp;
    };
} ATTRIBUTE_PACKED;
SET_PACKED_STRUCT_MODE_OFF

static void writeCookieFree(void *ptr)
{
    struct FirmwareWriteCookie *buf = container_of(ptr, struct FirmwareWriteCookie, resp);
    heapFree(buf);
}

static void firmwareWrite(void *cookie)
{
    bool valid;
    bool finished = false;
    struct FirmwareWriteCookie *resp = cookie;
    // only check crc when cookie is NULL (write came from kernel, not HAL)
    bool checkCrc = !cookie;

    if (mAppSecStatus == APP_SEC_NEED_MORE_TIME) {
        mAppSecStatus = appSecDoSomeProcessing(mDownloadState->appSecState);
    } else if (mDownloadState->lenLeft) {
        const uint8_t *data = mDownloadState->data + mDownloadState->len - mDownloadState->lenLeft;
        uint32_t len = mDownloadState->lenLeft, lenLeft, lenRem = 0;

        if (len > MAX_APP_SEC_RX_DATA_LEN) {
            lenRem = len - MAX_APP_SEC_RX_DATA_LEN;
            len = MAX_APP_SEC_RX_DATA_LEN;
        }

        mAppSecStatus = appSecRxData(mDownloadState->appSecState, data, len, &lenLeft);
        mDownloadState->lenLeft = lenLeft + lenRem;
    }

    valid = (mAppSecStatus == APP_SEC_NO_ERROR);
    if (mAppSecStatus == APP_SEC_NEED_MORE_TIME || mDownloadState->lenLeft) {
        osDefer(firmwareWrite, cookie, false);
        return;
    } else if (valid) {
        if (mDownloadState->srcOffset == mDownloadState->size) {
            mAppSecStatus = appSecRxDataOver(mDownloadState->appSecState);
            finished = true;
            valid = !checkCrc || mDownloadState->crc == ~mDownloadState->srcCrc;
        } else if (mDownloadState->srcOffset > mDownloadState->size) {
            valid = false;
        }
    }
    if (!valid)
        finished = true;
    if (finished) {
        if (firmwareFinish(valid) != NANOHUB_FIRMWARE_UPLOAD_SUCCESS)
            valid = false;
    }
    if (resp) {
        if (resp->evtType == EVT_APP_TO_HOST) {
#ifdef LEGACY_HAL_ENABLED
            resp->respLegacy.success = valid;
            osEnqueueEvtOrFree(EVT_APP_TO_HOST, &resp->respLegacy, writeCookieFree);
#endif
        } else {
            resp->resp.ret.status = !valid;
            osEnqueueEvtOrFree(EVT_APP_TO_HOST_CHRE, &resp->resp, writeCookieFree);
        }
    }
}

static uint32_t doFirmwareChunk(uint8_t *data, uint32_t offset, uint32_t len, void *cookie)
{
    uint32_t reply, ret;

    if (!mDownloadState) {
        reply = NANOHUB_FIRMWARE_CHUNK_REPLY_CANCEL_NO_RETRY;
    } else if (mAppSecStatus == APP_SEC_NEED_MORE_TIME || mDownloadState->lenLeft) {
        reply = NANOHUB_FIRMWARE_CHUNK_REPLY_RESEND;
    } else if (mDownloadState->chunkReply != NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED) {
        reply = mDownloadState->chunkReply;
        firmwareFinish(false);
    } else {
        if (mDownloadState->erase == true) {
            reply = NANOHUB_FIRMWARE_CHUNK_REPLY_WAIT;
            if (!mDownloadState->eraseScheduled) {
                ret = osExtAppStopAppsByAppId(APP_ID_ANY);
                osLog(LOG_INFO, "%s: unloaded apps, ret=%08lx\n", __func__, ret);
                mDownloadState->eraseScheduled = osDefer(firmwareErase, NULL, false);
            }
        } else if (!mDownloadState->start) {
            // this means we can't allocate enough space even after we did erase
            reply = NANOHUB_FIRMWARE_CHUNK_REPLY_CANCEL_NO_RETRY;
            firmwareFinish(false);
        } else if (offset != mDownloadState->srcOffset) {
            reply = NANOHUB_FIRMWARE_CHUNK_REPLY_RESTART;
            resetDownloadState(false, true);
        } else {
            if (!cookie)
                mDownloadState->srcCrc = soft_crc32(data, len, mDownloadState->srcCrc);
            mDownloadState->srcOffset += len;
            memcpy(mDownloadState->data, data, len);
            mDownloadState->lenLeft = mDownloadState->len = len;
            reply = NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED;
            osDefer(firmwareWrite, cookie, false);
        }
    }

    return reply;
}

static uint32_t firmwareChunk(void *rx, uint8_t rx_len, void *tx, uint64_t timestamp)
{
    struct NanohubFirmwareChunkRequest *req = rx;
    struct NanohubFirmwareChunkResponse *resp = tx;
    uint32_t offset = le32toh(req->offset);
    uint8_t len = rx_len - sizeof(req->offset);

    resp->chunkReply = doFirmwareChunk(req->data, offset, len, NULL);

    return sizeof(*resp);
}

static uint32_t doFinishFirmwareUpload(uint32_t *addr, uint32_t *crc)
{
    uint32_t reply;

    if (!mDownloadState) {
        reply = appSecErrToNanohubReply(mAppSecStatus);
        if (addr) {
            if (mApp)
                *addr = (uint32_t)mApp;
            else
                *addr = 0xFFFFFFFF;
        }
        if (crc) {
            if (mApp)
                *crc = osAppSegmentGetCrc(mApp);
            else
                *crc = 0xFFFFFFFF;
        }
    } else if (mDownloadState->srcOffset == mDownloadState->size) {
        reply = NANOHUB_FIRMWARE_UPLOAD_PROCESSING;
    } else {
        reply = firmwareFinish(false);
    }

    return reply;
}

static uint32_t finishFirmwareUpload(void *rx, uint8_t rx_len, void *tx, uint64_t timestamp)
{
    struct NanohubFinishFirmwareUploadResponse *resp = tx;
    resp->uploadReply = doFinishFirmwareUpload(NULL, NULL);
    if (resp->uploadReply != NANOHUB_FIRMWARE_UPLOAD_PROCESSING)
        osLog(LOG_INFO, "%s: reply=%" PRIu8 "\n", __func__, resp->uploadReply);
    return sizeof(*resp);
}

static uint32_t getInterrupt(void *rx, uint8_t rx_len, void *tx, uint64_t timestamp)
{
    struct NanohubGetInterruptRequest *req = rx;
    struct NanohubGetInterruptResponse *resp = tx;
    int i;

    if (rx_len == sizeof(struct NanohubGetInterruptRequest)) {
        for (i = 0; i < HOSTINTF_MAX_INTERRUPTS; i++) {
            if (req->clear[i/32] & (1UL << (i & 31)))
                hostIntfClearInterrupt(i);
        }
    }

    hostIntfCopyInterrupts(resp->interrupts, HOSTINTF_MAX_INTERRUPTS);

    return sizeof(*resp);
}

static uint32_t maskInterrupt(void *rx, uint8_t rx_len, void *tx, uint64_t timestamp)
{
    struct NanohubMaskInterruptRequest *req = rx;
    struct NanohubMaskInterruptResponse *resp = tx;

    hostIntfSetInterruptMask(req->interrupt);

    resp->accepted = true;
    return sizeof(*resp);
}

static uint32_t unmaskInterrupt(void *rx, uint8_t rx_len, void *tx, uint64_t timestamp)
{
    struct NanohubUnmaskInterruptRequest *req = rx;
    struct NanohubUnmaskInterruptResponse *resp = tx;

    hostIntfClearInterruptMask(req->interrupt);

    resp->accepted = true;
    return sizeof(*resp);
}

static void addDelta(struct ApHubSync *sync, uint64_t apTime, uint64_t hubTime)
{
#if DEBUG_APHUB_TIME_SYNC
    syncDebugAdd(apTime, hubTime);
#endif
    apHubSyncAddDelta(sync, apTime, hubTime);
}

static int64_t getAvgDelta(struct ApHubSync *sync)
{
    return apHubSyncGetDelta(sync, sensorGetTime());
}

static int fillBuffer(void *tx, uint32_t totLength, uint32_t *wakeup, uint32_t *nonwakeup)
{
    struct HostIntfDataBuffer *packet = &mTxNext;
    struct HostIntfDataBuffer *firstPacket = tx;
    uint8_t *buf = tx;
    uint32_t length;
    uint32_t prevWakeup, prevNonWakeup;

    prevWakeup = *wakeup;
    prevNonWakeup = *nonwakeup;

    while (hostIntfPacketDequeue(&mTxNext, wakeup, nonwakeup)) {
        length = packet->length + sizeof(packet->evtType);
        if (packet->sensType == SENS_TYPE_INVALID) {
            switch (packet->dataType) {
            case HOSTINTF_DATA_TYPE_APP_TO_HOST:
                packet->evtType = htole32(EVT_APP_TO_HOST);
                break;
            case HOSTINTF_DATA_TYPE_RESET_REASON:
                packet->evtType = htole32(EVT_RESET_REASON);
                break;
            case HOSTINTF_DATA_TYPE_APP_TO_SENSOR_HAL:
                packet->evtType = htole32(EVT_APP_TO_SENSOR_HAL_DATA);
                break;
#ifdef DEBUG_LOG_EVT
            case HOSTINTF_DATA_TYPE_LOG:
                packet->evtType = htole32(HOST_EVT_DEBUG_LOG);
                break;
#endif
            default:
                packet->evtType = htole32(0x00000000);
                break;
            }
        } else {
            packet->evtType = htole32(EVT_NO_FIRST_SENSOR_EVENT + packet->sensType);
            if (packet->referenceTime)
                packet->referenceTime += getAvgDelta(&mTimeSync);

            if (*wakeup > 0)
                packet->firstSample.interrupt = NANOHUB_INT_WAKEUP;
        }

        if ((!totLength || (isSensorEvent(firstPacket->evtType) && isSensorEvent(packet->evtType))) &&
             totLength + length <= sizeof(struct HostIntfDataBuffer)) {
            memcpy(buf + totLength, &mTxNext, length);
            totLength += length;
            if (isSensorEvent(packet->evtType) && packet->firstSample.interrupt == NANOHUB_INT_WAKEUP)
                firstPacket->firstSample.interrupt = NANOHUB_INT_WAKEUP;
        } else {
            mTxNextLength = length;
            *wakeup = prevWakeup;
            *nonwakeup = prevNonWakeup;
            break;
        }

        prevWakeup = *wakeup;
        prevNonWakeup = *nonwakeup;
    }

    return totLength;
}

static void updateInterrupts(void)
{
    uint32_t wakeup = atomicRead32bits(&mTxWakeCnt[0]);
    uint32_t nonwakeup = atomicRead32bits(&mTxWakeCnt[1]);
    bool wakeupStatus = hostIntfGetInterrupt(NANOHUB_INT_WAKEUP);
    bool nonwakeupStatus = hostIntfGetInterrupt(NANOHUB_INT_NONWAKEUP);

    if (!wakeup && wakeupStatus)
        hostIntfClearInterrupt(NANOHUB_INT_WAKEUP);
    else if (wakeup && !wakeupStatus)
        hostIntfSetInterrupt(NANOHUB_INT_WAKEUP);

    if (!nonwakeup && nonwakeupStatus)
        hostIntfClearInterrupt(NANOHUB_INT_NONWAKEUP);
    else if (nonwakeup && !nonwakeupStatus)
        hostIntfSetInterrupt(NANOHUB_INT_NONWAKEUP);
}

void nanohubPrefetchTx(uint32_t interrupt, uint32_t wakeup, uint32_t nonwakeup)
{
    uint64_t state;

    if (wakeup < atomicRead32bits(&mTxWakeCnt[0]))
        wakeup = atomicRead32bits(&mTxWakeCnt[0]);

    if (nonwakeup < atomicRead32bits(&mTxWakeCnt[1]))
        nonwakeup = atomicRead32bits(&mTxWakeCnt[1]);

    if (interrupt == HOSTINTF_MAX_INTERRUPTS && !hostIntfGetInterrupt(NANOHUB_INT_WAKEUP) && !hostIntfGetInterrupt(NANOHUB_INT_NONWAKEUP))
        return;

    atomicWriteByte(&mPrefetchActive, 1);

    if (interrupt < HOSTINTF_MAX_INTERRUPTS)
        hostIntfSetInterrupt(interrupt);

    do {
        if (atomicReadByte(&mTxCurrLength) == 0 && mTxNextLength > 0) {
            memcpy(&mTxCurr, &mTxNext, mTxNextLength);
            atomicWriteByte(&mTxCurrLength, mTxNextLength);
            mTxNextLength = 0;
        }

        if (mTxNextLength == 0) {
            atomicWriteByte(&mTxCurrLength, fillBuffer(&mTxCurr, atomicReadByte(&mTxCurrLength), &wakeup, &nonwakeup));
            atomicWrite32bits(&mTxWakeCnt[0], wakeup);
            atomicWrite32bits(&mTxWakeCnt[1], nonwakeup);
        }

        atomicWriteByte(&mPrefetchActive, 0);

        if (atomicReadByte(&mPrefetchTx)) {
            state = cpuIntsOff();

            // interrupt occured during this call
            // take care of it
            hostIntfTxAck(&mTxCurr, atomicReadByte(&mTxCurrLength));
            atomicWriteByte(&mPrefetchTx, 0);
            atomicWriteByte(&mTxCurrLength, 0);

            cpuIntsRestore(state);

            updateInterrupts();
        } else {
            break;
        }
    } while (mTxNextLength > 0);
}

static void nanohubPrefetchTxDefer(void *cookie)
{
    nanohubPrefetchTx(HOSTINTF_MAX_INTERRUPTS, 0, 0);
}

static uint32_t readEventFast(void *rx, uint8_t rx_len, void *tx, uint64_t timestamp)
{
    struct NanohubReadEventRequest *req = rx;
    uint8_t ret = 0;

    if (atomicReadByte(&mPrefetchActive)) {
        atomicWriteByte(&mPrefetchTx, 1);
        return NANOHUB_FAST_DONT_ACK;
    } else {
        if ((ret = atomicReadByte(&mTxCurrLength))) {
            addDelta(&mTimeSync, req->apBootTime, timestamp);

            memcpy(tx, &mTxCurr, ret);
            atomicWriteByte(&mTxCurrLength, 0);

            updateInterrupts();
            osDefer(nanohubPrefetchTxDefer, NULL, true);
        } else {
            return NANOHUB_FAST_UNHANDLED_ACK;
        }
    }

    return ret;
}

static uint32_t readEvent(void *rx, uint8_t rx_len, void *tx, uint64_t timestamp)
{
    struct NanohubReadEventRequest *req = rx;
    uint8_t *buf = tx;
    uint32_t length, wakeup, nonwakeup;
    uint32_t totLength = 0;

    addDelta(&mTimeSync, req->apBootTime, timestamp);

    if ((totLength = atomicReadByte(&mTxCurrLength))) {
        memcpy(tx, &mTxCurr, totLength);
        atomicWriteByte(&mTxCurrLength, 0);
        updateInterrupts();
        return totLength;
    }

    wakeup = atomicRead32bits(&mTxWakeCnt[0]);
    nonwakeup = atomicRead32bits(&mTxWakeCnt[1]);

    if (mTxNextLength > 0) {
        length = mTxNextLength;
        memcpy(buf, &mTxNext, length);
        totLength = length;
        mTxNextLength = 0;
    }

    totLength = fillBuffer(buf, totLength, &wakeup, &nonwakeup);
    atomicWrite32bits(&mTxWakeCnt[0], wakeup);
    atomicWrite32bits(&mTxWakeCnt[1], nonwakeup);

    if (totLength) {
        updateInterrupts();
    } else {
        hostIntfClearInterrupt(NANOHUB_INT_WAKEUP);
        hostIntfClearInterrupt(NANOHUB_INT_NONWAKEUP);
    }

    return totLength;
}

static bool forwardPacket(uint32_t event, void *data, size_t data_size,
                          void *hdr, size_t hdr_size, uint32_t tid)
{
    bool res;
    uint8_t *hostPacket = data;
    uint8_t *packet = slabAllocatorAlloc(mEventSlab);
    EventFreeF free = slabFree;

    if (!packet) {
        packet = heapAlloc(data_size + hdr_size);
        free = heapFree;
    }
    if (!packet)
        return false;

    if (hdr && hdr_size)
        memcpy(packet, hdr, hdr_size);

    memcpy(packet + hdr_size, hostPacket, data_size);
    if (tid) {
        // send to specific TID
        res = osEnqueuePrivateEvt(event, packet, free, tid);
        if (!res)
            free(packet);
    } else {
        // broadcast to all
        res = osEnqueueEvtOrFree(event, packet, free);
    }

    return res;
}

static uint32_t writeEvent(void *rx, uint8_t rx_len, void *tx, uint64_t timestamp)
{
    struct NanohubWriteEventRequest *req = rx;
    struct NanohubWriteEventResponse *resp = tx;
    uint32_t tid;
    uint32_t event = le32toh(req->evtType);

    if (event == EVT_APP_FROM_HOST) {
        // old version of HAL; message_type is not delivered to CHRE apps
        struct HostMsgHdr *hostPacket = rx;
        if (rx_len >= sizeof(struct HostMsgHdr) &&
            rx_len == sizeof(struct HostMsgHdr) + hostPacket->len &&
            osTidById(&hostPacket->appId, &tid)) {
            resp->accepted = forwardPacket(event, hostPacket + 1, hostPacket->len,
                                           &hostPacket->len, sizeof(hostPacket->len), tid);
        } else {
            resp->accepted = false;
        }
    } else if (event == EVT_APP_FROM_HOST_CHRE) {
        // new version of HAL; full support for CHRE apps
        struct HostMsgHdrChreV10 *hostPacketV10 = rx;
        struct HostMsgHdrChre *hostPacket = rx;
        if (rx_len >= sizeof(struct HostMsgHdrChre) &&
            rx_len == sizeof(struct HostMsgHdrChre) + hostPacket->len &&
            osTidById(&hostPacket->appId, &tid)) {
            if (osAppChreVersion(tid) >= CHRE_API_VERSION_1_1) {
                struct NanohubMsgChreHdr hdr = {
                    .size = hostPacket->len,
                    .endpoint = hostPacket->endpoint,
                    .appEvent = hostPacket->appEventId,
                };
                // CHRE app receives message in new format
                resp->accepted = forwardPacket(event, hostPacket + 1, hostPacket->len,
                                               &hdr, sizeof(hdr), tid);
            } else if (osAppChreVersion(tid) == CHRE_API_VERSION_1_0) {
                struct NanohubMsgChreHdrV10 hdr = {
                    .size = hostPacket->len,
                    .appEvent = hostPacket->appEventId,
                };
                // CHRE app receives message in new format
                resp->accepted = forwardPacket(event, hostPacket + 1, hostPacket->len,
                                               &hdr, sizeof(hdr), tid);
            } else {
                // legacy app receives message in old format
                resp->accepted = forwardPacket(EVT_APP_FROM_HOST, hostPacket + 1, hostPacket->len,
                                               &hostPacket->len, sizeof(hostPacket->len), tid);
            }
        } else if (rx_len >= sizeof(struct HostMsgHdrChreV10) &&
                   rx_len == sizeof(struct HostMsgHdrChreV10) + hostPacketV10->len &&
                   osTidById(&hostPacketV10->appId, &tid)) {
            if (osAppChreVersion(tid) >= CHRE_API_VERSION_1_1) {
                struct NanohubMsgChreHdr hdr = {
                    .size = hostPacketV10->len,
                    .endpoint = CHRE_HOST_ENDPOINT_UNSPECIFIED,
                    .appEvent = hostPacketV10->appEventId,
                };
                // CHRE app receives message in new format
                resp->accepted = forwardPacket(event, hostPacketV10 + 1, hostPacketV10->len,
                                               &hdr, sizeof(hdr), tid);
            } else if (osAppChreVersion(tid) == CHRE_API_VERSION_1_0) {
                struct NanohubMsgChreHdrV10 hdr = {
                    .size = hostPacketV10->len,
                    .appEvent = hostPacketV10->appEventId,
                };
                // CHRE app receives message in new format
                resp->accepted = forwardPacket(event, hostPacketV10 + 1, hostPacketV10->len,
                                               &hdr, sizeof(hdr), tid);
            } else {
                // legacy app receives message in old format
                resp->accepted = forwardPacket(EVT_APP_FROM_HOST, hostPacketV10 + 1, hostPacketV10->len,
                                               &hostPacketV10->len, sizeof(hostPacketV10->len), tid);
            }
        } else {
            resp->accepted = false;
        }
    } else {
        resp->accepted = forwardPacket(event,
                                       req->evtData, rx_len - sizeof(req->evtType),
                                       NULL, 0, 0);
    }

    return sizeof(*resp);
}

const static struct NanohubCommand mBuiltinCommands[] = {
    NANOHUB_COMMAND(NANOHUB_REASON_GET_OS_HW_VERSIONS,
                    getOsHwVersion,
                    getOsHwVersion,
                    struct NanohubOsHwVersionsRequest,
                    struct NanohubOsHwVersionsRequest),
    NANOHUB_COMMAND(NANOHUB_REASON_GET_APP_VERSIONS,
                    NULL,
                    getAppVersion,
                    struct NanohubAppVersionsRequest,
                    struct NanohubAppVersionsRequest),
    NANOHUB_COMMAND(NANOHUB_REASON_QUERY_APP_INFO,
                    NULL,
                    queryAppInfo,
                    struct NanohubAppInfoRequest,
                    struct NanohubAppInfoRequest),
    NANOHUB_COMMAND(NANOHUB_REASON_START_FIRMWARE_UPLOAD,
                    NULL,
                    startFirmwareUpload,
                    struct NanohubStartFirmwareUploadRequest,
                    struct NanohubStartFirmwareUploadRequest),
    NANOHUB_COMMAND(NANOHUB_REASON_FIRMWARE_CHUNK,
                    NULL,
                    firmwareChunk,
                    __le32,
                    struct NanohubFirmwareChunkRequest),
    NANOHUB_COMMAND(NANOHUB_REASON_FINISH_FIRMWARE_UPLOAD,
                    NULL,
                    finishFirmwareUpload,
                    struct NanohubFinishFirmwareUploadRequest,
                    struct NanohubFinishFirmwareUploadRequest),
    NANOHUB_COMMAND(NANOHUB_REASON_GET_INTERRUPT,
                    getInterrupt,
                    getInterrupt,
                    struct { },
                    struct NanohubGetInterruptRequest),
    NANOHUB_COMMAND(NANOHUB_REASON_MASK_INTERRUPT,
                    maskInterrupt,
                    maskInterrupt,
                    struct NanohubMaskInterruptRequest,
                    struct NanohubMaskInterruptRequest),
    NANOHUB_COMMAND(NANOHUB_REASON_UNMASK_INTERRUPT,
                    unmaskInterrupt,
                    unmaskInterrupt,
                    struct NanohubUnmaskInterruptRequest,
                    struct NanohubUnmaskInterruptRequest),
    NANOHUB_COMMAND(NANOHUB_REASON_READ_EVENT,
                    readEventFast,
                    readEvent,
                    struct NanohubReadEventRequest,
                    struct NanohubReadEventRequest),
    NANOHUB_COMMAND(NANOHUB_REASON_WRITE_EVENT,
                    writeEvent,
                    writeEvent,
                    __le32,
                    struct NanohubWriteEventRequest),
};

const struct NanohubCommand *nanohubFindCommand(uint32_t packetReason)
{
    uint32_t i;

    for (i = 0; i < ARRAY_SIZE(mBuiltinCommands); i++) {
        const struct NanohubCommand *cmd = &mBuiltinCommands[i];
        if (cmd->reason == packetReason)
            return cmd;
    }
    return NULL;
}

#ifdef LEGACY_HAL_ENABLED

static void halSendLegacyMgmtResponse(uint32_t cmd, uint32_t status)
{
    struct NanohubHalLegacyMgmtTx *resp;

    resp = heapAlloc(sizeof(*resp));
    if (resp) {
        resp->hdr = (struct NanohubHalLegacyHdr) {
            .appId = APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 0),
            .len = sizeof(*resp) - sizeof(resp->hdr) + sizeof(resp->hdr.msg),
            .msg = cmd,
        };
        resp->status = htole32(status);
        osEnqueueEvtOrFree(EVT_APP_TO_HOST, resp, heapFree);
    }
}

static void halLegacyExtAppsOn(void *rx, uint8_t rx_len)
{
    struct NanohubHalLegacyMgmtRx *req = rx;

    halSendLegacyMgmtResponse(NANOHUB_HAL_LEGACY_EXT_APPS_ON, osExtAppStartAppsByAppId(le64toh(unaligned_u64(&req->appId))));
}

static void halLegacyExtAppsOff(void *rx, uint8_t rx_len)
{
    struct NanohubHalLegacyMgmtRx *req = rx;

    halSendLegacyMgmtResponse(NANOHUB_HAL_LEGACY_EXT_APPS_OFF, osExtAppStopAppsByAppId(le64toh(unaligned_u64(&req->appId))));
}

static void halLegacyExtAppDelete(void *rx, uint8_t rx_len)
{
    struct NanohubHalLegacyMgmtRx *req = rx;

    halSendLegacyMgmtResponse(NANOHUB_HAL_LEGACY_EXT_APP_DELETE, osExtAppEraseAppsByAppId(le64toh(unaligned_u64(&req->appId))));
}

static void halLegacyQueryMemInfo(void *rx, uint8_t rx_len)
{
}

static void halLegacyQueryApps(void *rx, uint8_t rx_len)
{
    struct NanohubHalLegacyQueryAppsRx *req = rx;
    struct NanohubHalLegacyQueryAppsTx *resp;
    struct NanohubHalLegacyHdr *hdr;
    uint64_t appId;
    uint32_t appVer, appSize;

    if (osExtAppInfoByIndex(le32toh(req->idx), &appId, &appVer, &appSize)) {
        resp = heapAlloc(sizeof(*resp));
        if (resp) {
            resp->hdr.appId = APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 0);
            resp->hdr.len = sizeof(*resp) - sizeof(struct NanohubHalLegacyHdr) + 1;
            resp->hdr.msg = NANOHUB_HAL_LEGACY_QUERY_APPS;
            resp->appId = appId;
            resp->version = appVer;
            resp->flashUse = appSize;
            resp->ramUse = 0;
            osEnqueueEvtOrFree(EVT_APP_TO_HOST, resp, heapFree);
        }
    } else {
        hdr = heapAlloc(sizeof(*hdr));
        if (hdr) {
            hdr->appId = APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 0);
            hdr->len = 1;
            hdr->msg = NANOHUB_HAL_LEGACY_QUERY_APPS;
            osEnqueueEvtOrFree(EVT_APP_TO_HOST, hdr, heapFree);
        }
    }
}

static void halLegacyQueryRsaKeys(void *rx, uint8_t rx_len)
{
    struct NanohubHalLegacyQueryRsaKeysRx *req = rx;
    struct NanohubHalLegacyQueryRsaKeysTx *resp;
    int len = 0;
    const uint32_t *ptr;
    uint32_t numKeys;

    if (!(resp = heapAlloc(sizeof(*resp) + NANOHUB_RSA_KEY_CHUNK_LEN)))
        return;

    ptr = BL.blGetPubKeysInfo(&numKeys);
    if (ptr && numKeys * RSA_BYTES > req->offset) {
        len = numKeys * RSA_BYTES - req->offset;
        if (len > NANOHUB_RSA_KEY_CHUNK_LEN)
            len = NANOHUB_RSA_KEY_CHUNK_LEN;
        memcpy(resp->data, (uint8_t *)ptr + req->offset, len);
    }

    resp->hdr.appId = APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 0);
    resp->hdr.len = sizeof(*resp) - sizeof(struct NanohubHalLegacyHdr) + 1 + len;
    resp->hdr.msg = NANOHUB_HAL_LEGACY_QUERY_RSA_KEYS;

    osEnqueueEvtOrFree(EVT_APP_TO_HOST, resp, heapFree);
}

static void halLegacyStartUpload(void *rx, uint8_t rx_len)
{
    struct NanohubHalLegacyStartUploadRx *req = rx;
    struct NanohubStartFirmwareUploadRequest hwReq = {
        .size = req->length
    };
    struct NanohubHalLegacyStartUploadTx *resp;

    if (!(resp = heapAlloc(sizeof(*resp))))
        return;

    resp->hdr.appId = APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 0);
    resp->hdr.len = sizeof(*resp) - sizeof(struct NanohubHalLegacyHdr) + 1;
    resp->hdr.msg = NANOHUB_HAL_LEGACY_START_UPLOAD;
    resp->success = doStartFirmwareUpload(&hwReq, true);

    osEnqueueEvtOrFree(EVT_APP_TO_HOST, resp, heapFree);
}

static void halLegacyContUpload(void *rx, uint8_t rx_len)
{
    uint32_t offset;
    uint32_t reply;
    uint8_t len;
    struct NanohubHalLegacyContUploadRx *req = rx;
    struct FirmwareWriteCookie *cookie;

    if (!(cookie = heapAlloc(sizeof(*cookie))))
        return;

    cookie->evtType = EVT_APP_TO_HOST;
    cookie->respLegacy.hdr = (struct NanohubHalLegacyHdr) {
        .appId = APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 0),
        .len = sizeof(cookie->respLegacy) - sizeof(struct NanohubHalLegacyHdr) + 1,
        .msg = NANOHUB_HAL_LEGACY_CONT_UPLOAD,
    };
    cookie->respLegacy.success = false;

    if (!mDownloadState) {
        reply = NANOHUB_FIRMWARE_CHUNK_REPLY_CANCEL_NO_RETRY;
    } else {
        offset = le32toh(req->offset);
        len = rx_len - sizeof(req->offset);
        reply = doFirmwareChunk(req->data, offset, len, cookie);
    }
    if (reply != NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED) {
        osLog(LOG_ERROR, "%s: reply=%" PRIu32 "\n", __func__, reply);

        osEnqueueEvtOrFree(EVT_APP_TO_HOST, &cookie->respLegacy, writeCookieFree);
    }
}

static void halLegacyFinishUpload(void *rx, uint8_t rx_len)
{
    struct NanohubHalLegacyFinishUploadTx *resp;
    uint32_t reply;

    if (!(resp = heapAlloc(sizeof(*resp))))
        return;

    resp->hdr.appId = APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 0);
    resp->hdr.len = sizeof(*resp) - sizeof(struct NanohubHalLegacyHdr) + 1;
    resp->hdr.msg = NANOHUB_HAL_LEGACY_FINISH_UPLOAD;

    reply = doFinishFirmwareUpload(NULL, NULL);

    osLog(LOG_INFO, "%s: reply=%" PRIu32 "\n", __func__, reply);

    resp->success = (reply == NANOHUB_FIRMWARE_UPLOAD_SUCCESS);

    osEnqueueEvtOrFree(EVT_APP_TO_HOST, resp, heapFree);
}

static void halLegacyReboot(void *rx, uint8_t rx_len)
{
    BL.blReboot();
}

const static struct NanohubHalLegacyCommand mBuiltinHalLegacyCommands[] = {
    NANOHUB_HAL_LEGACY_COMMAND(NANOHUB_HAL_LEGACY_EXT_APPS_ON,
                            halLegacyExtAppsOn),
    NANOHUB_HAL_LEGACY_COMMAND(NANOHUB_HAL_LEGACY_EXT_APPS_OFF,
                            halLegacyExtAppsOff),
    NANOHUB_HAL_LEGACY_COMMAND(NANOHUB_HAL_LEGACY_EXT_APP_DELETE,
                            halLegacyExtAppDelete),
    NANOHUB_HAL_LEGACY_COMMAND(NANOHUB_HAL_LEGACY_QUERY_MEMINFO,
                            halLegacyQueryMemInfo),
    NANOHUB_HAL_LEGACY_COMMAND(NANOHUB_HAL_LEGACY_QUERY_APPS,
                            halLegacyQueryApps),
    NANOHUB_HAL_LEGACY_COMMAND(NANOHUB_HAL_LEGACY_QUERY_RSA_KEYS,
                            halLegacyQueryRsaKeys),
    NANOHUB_HAL_LEGACY_COMMAND(NANOHUB_HAL_LEGACY_START_UPLOAD,
                            halLegacyStartUpload),
    NANOHUB_HAL_LEGACY_COMMAND(NANOHUB_HAL_LEGACY_CONT_UPLOAD,
                            halLegacyContUpload),
    NANOHUB_HAL_LEGACY_COMMAND(NANOHUB_HAL_LEGACY_FINISH_UPLOAD,
                            halLegacyFinishUpload),
    NANOHUB_HAL_LEGACY_COMMAND(NANOHUB_HAL_LEGACY_REBOOT,
                            halLegacyReboot),
};

const struct NanohubHalLegacyCommand *nanohubHalLegacyFindCommand(uint8_t msg)
{
    uint32_t i;

    for (i = 0; i < ARRAY_SIZE(mBuiltinHalLegacyCommands); i++) {
        const struct NanohubHalLegacyCommand *cmd = &mBuiltinHalLegacyCommands[i];
        if (cmd->msg == msg)
            return cmd;
    }
    return NULL;
}

#endif /* LEGACY_HAL_ENABLED */

static void halSendAppMgmtResponse(struct NanohubHalAppMgmtRx *req, uint32_t status, struct MgmtStatus stat, uint32_t transactionId)
{
    struct NanohubHalAppMgmtTx *resp;

    resp = heapAlloc(sizeof(*resp));
    if (resp) {
        resp->hdr = (struct NanohubHalHdr) {
            .appId = APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 0),
            .len = sizeof(*resp) - sizeof(resp->hdr),
            .transactionId = transactionId,
        };
        resp->ret = (struct NanohubHalRet) {
            .msg = NANOHUB_HAL_APP_MGMT,
            .status = htole32(status),
        };
        resp->cmd = req->cmd;
        resp->stat = stat;
        osEnqueueEvtOrFree(EVT_APP_TO_HOST_CHRE, resp, heapFree);
    }
}

static void halAppMgmt(void *rx, uint8_t rx_len, uint32_t transactionId)
{
    struct NanohubHalAppMgmtRx *req = rx;
    struct MgmtStatus stat;
    uint32_t ret;

    switch (req->cmd) {
    case NANOHUB_HAL_APP_MGMT_START:
        stat.value= osExtAppStartAppsByAppId(le64toh(unaligned_u64(&req->appId)));
        ret = stat.op > 0 ? 0 : -1;
        break;
    case NANOHUB_HAL_APP_MGMT_STOP:
        stat.value = osExtAppStopAppsByAppId(le64toh(unaligned_u64(&req->appId)));
        ret = stat.op > 0 ? 0 : -1;
        break;
    case NANOHUB_HAL_APP_MGMT_UNLOAD:
        stat.value = osExtAppStopAppsByAppId(le64toh(unaligned_u64(&req->appId)));
        ret = stat.op > 0 ? 0 : -1;
        break;
    case NANOHUB_HAL_APP_MGMT_DELETE:
        stat.value = osExtAppEraseAppsByAppId(le64toh(unaligned_u64(&req->appId)));
        ret = stat.erase > 0 ? 0 : -1;
        break;
    default:
        return;
    }

    halSendAppMgmtResponse(req, ret, stat, transactionId);
}

static void deferHalSysMgmtErase(void *cookie)
{
    struct NanohubHalSysMgmtTx *resp = cookie;

    bool success = osEraseShared();

    if (success)
        resp->ret.status = htole32(0);
    else
        resp->ret.status = htole32(-1);

    osEnqueueEvtOrFree(EVT_APP_TO_HOST_CHRE, resp, heapFree);
}

static void halSysMgmt(void *rx, uint8_t rx_len, uint32_t transactionId)
{
    struct NanohubHalSysMgmtRx *req = rx;
    struct NanohubHalSysMgmtTx *resp;
    uint32_t ret = 0;

    if (!(resp = heapAlloc(sizeof(*resp))))
        return;

    resp->hdr = (struct NanohubHalHdr) {
        .appId = APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 0),
        .len = sizeof(*resp) - sizeof(resp->hdr),
        .transactionId = transactionId,
    };
    resp->ret = (struct NanohubHalRet) {
        .msg = NANOHUB_HAL_SYS_MGMT,
    };
    resp->cmd = req->cmd;

    switch (req->cmd) {
    case NANOHUB_HAL_SYS_MGMT_ERASE:
        ret = osExtAppStopAppsByAppId(APP_ID_ANY);
        osLog(LOG_INFO, "%s: unloaded apps, ret=%08lx\n", __func__, ret);
        // delay to make sure all apps are unloaded before erasing
        if (osDefer(deferHalSysMgmtErase, resp, false) == false) {
            resp->ret.status = htole32(-1);
            osEnqueueEvtOrFree(EVT_APP_TO_HOST_CHRE, resp, heapFree);
        }
        break;
    case NANOHUB_HAL_SYS_MGMT_REBOOT:
        BL.blReboot();
        break;
    default:
        resp->ret.status = htole32(-1);
        osEnqueueEvtOrFree(EVT_APP_TO_HOST_CHRE, resp, heapFree);
    }
}

static bool copyTLV64(uint8_t *buf, size_t *offset, size_t max_len, uint8_t tag, uint64_t val)
{
    if (*offset + sizeof(uint8_t) + sizeof(uint8_t) + sizeof(uint64_t) > max_len)
        return false;
    buf[(*offset)++] = tag;
    buf[(*offset)++] = sizeof(uint64_t);
    memcpy(&buf[*offset], &val, sizeof(uint64_t));
    *offset += sizeof(uint64_t);
    return true;
}

static bool copyTLV32(uint8_t *buf, size_t *offset, size_t max_len, uint8_t tag, uint32_t val)
{
    if (*offset + sizeof(uint8_t) + sizeof(uint8_t) + sizeof(uint32_t) > max_len)
        return false;
    buf[(*offset)++] = tag;
    buf[(*offset)++] = sizeof(uint32_t);
    memcpy(&buf[*offset], &val, sizeof(uint32_t));
    *offset += sizeof(uint32_t);
    return true;
}

static bool copyTLV8(uint8_t *buf, size_t *offset, size_t max_len, uint8_t tag, uint8_t val)
{
    if (*offset + sizeof(uint8_t) + sizeof(uint8_t) + sizeof(uint8_t) > max_len)
        return false;
    buf[(*offset)++] = tag;
    buf[(*offset)++] = sizeof(uint8_t);
    memcpy(&buf[*offset], &val, sizeof(uint8_t));
    *offset += sizeof(uint8_t);
    return true;
}

static bool copyTLVEmpty(uint8_t *buf, size_t *offset, size_t max_len, uint8_t tag)
{
    if (*offset + sizeof(uint8_t) + sizeof(uint8_t) > max_len)
        return false;
    buf[(*offset)++] = tag;
    buf[(*offset)++] = 0;
    return true;
}

static int processAppTags(const struct AppHdr *app, uint32_t crc, uint32_t size, uint8_t *data, uint8_t *tags, int cnt, bool req_tid)
{
    int i;
    size_t offset = 0;
    const size_t max_len = HOST_HUB_CHRE_PACKET_MAX_LEN - sizeof(struct NanohubHalRet);
    bool success = true;
    uint32_t tid;
    bool tid_valid = false;
    struct Task *task;

    if (app->hdr.magic != APP_HDR_MAGIC ||
        app->hdr.fwVer != APP_HDR_VER_CUR ||
        (app->hdr.fwFlags & FL_APP_HDR_APPLICATION) == 0 ||
        app->hdr.payInfoType != LAYOUT_APP) {
        return 0;
    }

    if (osTidById(&app->hdr.appId, &tid)) {
        tid_valid = true;
        task = osTaskFindByTid(tid);
        if (task) {
            if (task->app != app)
                tid_valid = false;
        } else
            tid_valid = false;
    }

    if (!tid_valid && req_tid)
        return 0;

    for (i=0; i<cnt && success; i++) {
        switch(tags[i]) {
        case NANOHUB_HAL_APP_INFO_APPID:
            success = copyTLV64(data, &offset, max_len, tags[i], app->hdr.appId);
            break;
        case NANOHUB_HAL_APP_INFO_CRC:
            if (size)
                success = copyTLV32(data, &offset, max_len, tags[i], crc);
            else
                success = copyTLVEmpty(data, &offset, max_len, tags[i]);
            break;
        case NANOHUB_HAL_APP_INFO_TID:
            if (tid_valid)
                success = copyTLV32(data, &offset, max_len, tags[i], tid);
            else
                success = copyTLVEmpty(data, &offset, max_len, tags[i]);
            break;
        case NANOHUB_HAL_APP_INFO_VERSION:
            success = copyTLV32(data, &offset, max_len, tags[i], app->hdr.appVer);
            break;
        case NANOHUB_HAL_APP_INFO_ADDR:
            success = copyTLV32(data, &offset, max_len, tags[i], (uint32_t)app);
            break;
        case NANOHUB_HAL_APP_INFO_SIZE:
            if (size)
                success = copyTLV32(data, &offset, max_len, tags[i], size);
            else
                success = copyTLVEmpty(data, &offset, max_len, tags[i]);
            break;
        case NANOHUB_HAL_APP_INFO_HEAP:
            if (tid_valid)
                success = copyTLV32(data, &offset, max_len, tags[i], heapGetTaskSize(tid));
            else
                success = copyTLVEmpty(data, &offset, max_len, tags[i]);
            break;
        case NANOHUB_HAL_APP_INFO_DATA:
            success = copyTLV32(data, &offset, max_len, tags[i], app->sect.got_end - app->sect.data_start);
            break;
        case NANOHUB_HAL_APP_INFO_BSS:
            success = copyTLV32(data, &offset, max_len, tags[i], app->sect.bss_end - app->sect.bss_start);
            break;
        case NANOHUB_HAL_APP_INFO_CHRE_MAJOR:
            if (app->hdr.fwFlags & FL_APP_HDR_CHRE)
                success = copyTLV8(data, &offset, max_len, tags[i],
                    (app->hdr.chreApiMajor == 0xFF && app->hdr.chreApiMinor == 0xFF) ? 0x01 :
                        app->hdr.chreApiMajor);
            else
                success = copyTLVEmpty(data, &offset, max_len, tags[i]);
            break;
        case NANOHUB_HAL_APP_INFO_CHRE_MINOR:
            if (app->hdr.fwFlags & FL_APP_HDR_CHRE)
                success = copyTLV8(data, &offset, max_len, tags[i],
                    (app->hdr.chreApiMajor == 0xFF && app->hdr.chreApiMinor == 0xFF) ? 0x00 :
                        app->hdr.chreApiMinor);
            else
                success = copyTLVEmpty(data, &offset, max_len, tags[i]);
            break;
        case NANOHUB_HAL_APP_INFO_END:
        default:
            success = false;
            copyTLVEmpty(data, &offset, max_len, NANOHUB_HAL_APP_INFO_END);
            break;
        }
    }

    return offset;
}

static void halAppInfo(void *rx, uint8_t rx_len, uint32_t transactionId)
{
    struct NanohubHalAppInfoRx *req = rx;
    struct NanohubHalAppInfoTx *resp;
    struct SegmentIterator it;
    uint32_t state;
    int ret, i;
    uint32_t sharedSize, numApps;
    const struct AppHdr *internal;
    const uint8_t *shared;

    if (!(resp = heapAlloc(sizeof(*resp))))
        return;

    resp->hdr = (struct NanohubHalHdr) {
        .appId = APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 0),
        .len = sizeof(*resp) - sizeof(resp->hdr) - sizeof(resp->data),
        .transactionId = transactionId,
    };
    resp->ret = (struct NanohubHalRet) {
        .msg = NANOHUB_HAL_APP_INFO,
    };

    shared = platGetSharedAreaInfo(&sharedSize);
    internal = platGetInternalAppList(&numApps);

    if ((le32toh(req->addr) >= (uint32_t)shared && le32toh(req->addr) < (uint32_t)shared + sharedSize) ||
        (le32toh(req->addr) < (uint32_t)shared &&
            ((uint32_t)shared < (uint32_t)internal ||
                (numApps > 0 && le32toh(req->addr) > (uint32_t)(internal+numApps-1))))) {
        osSegmentIteratorInit(&it);
        while (osSegmentIteratorNext(&it)) {
            state = osSegmentGetState(it.seg);
            switch (state) {
            case SEG_ST_EMPTY:
            case SEG_ST_RESERVED:
                 osEnqueueEvtOrFree(EVT_APP_TO_HOST_CHRE, resp, heapFree);
                 return;
            case SEG_ST_ERASED:
            case SEG_ST_VALID:
                if (le32toh(req->addr) <= (uint32_t)osSegmentGetData(it.seg)) {
                    ret = processAppTags(osSegmentGetData(it.seg), osSegmentGetCrc(it.seg), osSegmentGetSize(it.seg), resp->data, req->tags, rx_len - 4, state == SEG_ST_ERASED);
                    if (ret > 0) {
                        resp->hdr.len += ret;
                        osEnqueueEvtOrFree(EVT_APP_TO_HOST_CHRE, resp, heapFree);
                        return;
                    }
                }
                break;
            }
        }
    } else {
        for (i = 0; i < numApps; i++, internal++) {
            if (le32toh(req->addr) <= (uint32_t)internal) {
                ret = processAppTags(internal, 0, 0, resp->data, req->tags, rx_len - 4, false);
                if (ret > 0) {
                    resp->hdr.len += ret;
                    osEnqueueEvtOrFree(EVT_APP_TO_HOST_CHRE, resp, heapFree);
                    return;
                }
            }
        }
    }

    osEnqueueEvtOrFree(EVT_APP_TO_HOST_CHRE, resp, heapFree);
}

static void halSysInfo(void *rx, uint8_t rx_len, uint32_t transactionId)
{
    extern uint8_t __code_start[];
    extern uint8_t __code_end[];
    extern uint8_t __text_end[];
    extern uint8_t __ram_start[];
    extern uint8_t __ram_end[];

    struct NanohubHalSysInfoRx *req = rx;
    struct NanohubHalSysInfoTx *resp;
    int i;
    size_t offset = 0;
    const size_t max_len = HOST_HUB_CHRE_PACKET_MAX_LEN - sizeof(struct NanohubHalRet);
    bool success = true;
    int free, chunks, largest;
    uint32_t shared_size;

    free = heapGetFreeSize(&chunks, &largest);

    if (!(resp = heapAlloc(sizeof(*resp))))
        return;

    resp->hdr = (struct NanohubHalHdr) {
        .appId = APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 0),
        .len = sizeof(*resp) - sizeof(resp->hdr) - sizeof(resp->data),
        .transactionId = transactionId,
    };
    resp->ret = (struct NanohubHalRet) {
        .msg = NANOHUB_HAL_SYS_INFO,
    };

    for (i=0; i<rx_len && success; i++) {
        switch(req->tags[i]) {
        case NANOHUB_HAL_SYS_INFO_HEAP_FREE:
            if (free >= 0)
                success = copyTLV32(resp->data, &offset, max_len, req->tags[i], free);
            else
                success = copyTLVEmpty(resp->data, &offset, max_len, req->tags[i]);
            break;
        case NANOHUB_HAL_SYS_INFO_RAM_SIZE:
            success = copyTLV32(resp->data, &offset, max_len, req->tags[i], __ram_end - __ram_start);
            break;
        case NANOHUB_HAL_SYS_INFO_EEDATA_SIZE:
            success = copyTLV32(resp->data, &offset, max_len, req->tags[i], eeDataGetSize());
            break;
        case NANOHUB_HAL_SYS_INFO_EEDATA_FREE:
            success = copyTLV32(resp->data, &offset, max_len, req->tags[i], eeDataGetFree());
            break;
        case NANOHUB_HAL_SYS_INFO_CODE_SIZE:
            success = copyTLV32(resp->data, &offset, max_len, req->tags[i], __code_end - __code_start);
            break;
        case NANOHUB_HAL_SYS_INFO_CODE_FREE:
            success = copyTLV32(resp->data, &offset, max_len, req->tags[i], __code_end - __text_end);
            break;
        case NANOHUB_HAL_SYS_INFO_SHARED_SIZE:
            platGetSharedAreaInfo(&shared_size);
            success = copyTLV32(resp->data, &offset, max_len, req->tags[i], shared_size);
            break;
        case NANOHUB_HAL_SYS_INFO_SHARED_FREE:
            success = copyTLV32(resp->data, &offset, max_len, req->tags[i], osSegmentGetFree());
            break;
        case NANOHUB_HAL_SYS_INFO_END:
        default:
            success = false;
            copyTLVEmpty(resp->data, &offset, max_len, NANOHUB_HAL_APP_INFO_END);
            break;
        }
    }

    resp->hdr.len += offset;

    osEnqueueEvtOrFree(EVT_APP_TO_HOST_CHRE, resp, heapFree);
}

static void halKeyInfo(void *rx, uint8_t rx_len, uint32_t transactionId)
{
    struct NanohubHalKeyInfoRx *req = rx;
    struct NanohubHalKeyInfoTx *resp;
    const uint32_t *ptr;
    uint32_t numKeys;
    uint32_t dataLength;

    if (!(resp = heapAlloc(sizeof(*resp))))
        return;

    ptr = BL.blGetPubKeysInfo(&numKeys);

    resp->hdr = (struct NanohubHalHdr) {
        .appId = APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 0),
        .len = sizeof(*resp) - sizeof(resp->hdr) - sizeof(resp->data),
        .transactionId = transactionId,
    };
    resp->ret = (struct NanohubHalRet) {
        .msg = NANOHUB_HAL_KEY_INFO,
    };

    resp->keyLength = 0;

    if (ptr && req->keyNum < numKeys) {
        if (req->dataOffset < RSA_BYTES) {
            resp->keyLength = RSA_BYTES;
            if (RSA_BYTES - req->dataOffset > NANOHUB_RSA_KEY_CHUNK_LEN)
                dataLength = NANOHUB_RSA_KEY_CHUNK_LEN;
            else
                dataLength = RSA_BYTES - req->dataOffset;
            memcpy(resp->data, (const uint8_t *)ptr + (req->keyNum * RSA_BYTES) + req->dataOffset, dataLength);
            resp->hdr.len += dataLength;
        }
    }

    osEnqueueEvtOrFree(EVT_APP_TO_HOST_CHRE, resp, heapFree);
}

static void halStartUpload(void *rx, uint8_t rx_len, uint32_t transactionId)
{
    struct NanohubHalStartUploadRx *req = rx;
    struct NanohubStartFirmwareUploadRequest hwReq = {
        .size = req->length
    };
    struct NanohubHalStartUploadTx *resp;

    if (!(resp = heapAlloc(sizeof(*resp))))
        return;

    resp->hdr = (struct NanohubHalHdr) {
        .appId = APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 0),
        .len = sizeof(*resp) - sizeof(resp->hdr),
        .transactionId = transactionId,
    };

    resp->ret.msg = NANOHUB_HAL_START_UPLOAD;
    if (doStartFirmwareUpload(&hwReq, false))
        resp->ret.status = NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED;
    else
        resp->ret.status = NANOHUB_FIRMWARE_CHUNK_REPLY_NO_SPACE;

    osEnqueueEvtOrFree(EVT_APP_TO_HOST_CHRE, resp, heapFree);
}

static void halContUpload(void *rx, uint8_t rx_len, uint32_t transactionId)
{
    uint32_t offset;
    uint32_t reply;
    uint8_t len;
    struct NanohubHalContUploadRx *req = rx;
    struct FirmwareWriteCookie *cookie;

    if (!(cookie = heapAlloc(sizeof(*cookie))))
        return;

    cookie->evtType = EVT_APP_TO_HOST_CHRE;
    cookie->resp.hdr = (struct NanohubHalHdr) {
        .appId = APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 0),
        .len = sizeof(cookie->resp) - sizeof(cookie->resp.hdr),
        .transactionId = transactionId,
    };
    cookie->resp.ret = (struct NanohubHalRet) {
        .msg = NANOHUB_HAL_CONT_UPLOAD,
    };

    if (!mDownloadState) {
        reply = NANOHUB_FIRMWARE_CHUNK_REPLY_CANCEL_NO_RETRY;
    } else {
        offset = le32toh(req->offset);
        len = rx_len - sizeof(req->offset);
        reply = doFirmwareChunk(req->data, offset, len, cookie);
    }
    if (reply != NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED) {
        osLog(LOG_ERROR, "%s: reply=%" PRIu32 "\n", __func__, reply);

        cookie->resp.ret.status = reply;

        osEnqueueEvtOrFree(EVT_APP_TO_HOST_CHRE, &cookie->resp, writeCookieFree);
    }
}

static void halFinishUpload(void *rx, uint8_t rx_len, uint32_t transactionId)
{
    struct NanohubHalFinishUploadTx *resp;
    uint32_t reply;
    uint32_t addr = 0xFFFFFFFF;
    uint32_t crc = 0xFFFFFFFF;

    if (!(resp = heapAlloc(sizeof(*resp))))
        return;

    resp->hdr = (struct NanohubHalHdr) {
        .appId = APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 0),
        .len = sizeof(*resp) - sizeof(resp->hdr),
        .transactionId = transactionId,
    };

    reply = doFinishFirmwareUpload(&addr, &crc);

    osLog(LOG_INFO, "%s: reply=%" PRIu32 "\n", __func__, reply);

    resp->ret = (struct NanohubHalRet) {
        .msg = NANOHUB_HAL_FINISH_UPLOAD,
        .status = reply,
    };

    resp->addr = addr;
    resp->crc = crc;

    osEnqueueEvtOrFree(EVT_APP_TO_HOST_CHRE, resp, heapFree);
}

const static struct NanohubHalCommand mBuiltinHalCommands[] = {
    NANOHUB_HAL_COMMAND(NANOHUB_HAL_APP_MGMT,
                            halAppMgmt,
                            struct NanohubHalAppMgmtRx,
                            struct NanohubHalAppMgmtRx),
    NANOHUB_HAL_COMMAND(NANOHUB_HAL_SYS_MGMT,
                            halSysMgmt,
                            struct NanohubHalSysMgmtRx,
                            struct NanohubHalSysMgmtRx),
    NANOHUB_HAL_COMMAND(NANOHUB_HAL_APP_INFO,
                            halAppInfo,
                            __le32,
                            struct NanohubHalAppInfoRx),
    NANOHUB_HAL_COMMAND(NANOHUB_HAL_SYS_INFO,
                            halSysInfo,
                            struct { },
                            struct NanohubHalSysInfoRx),
    NANOHUB_HAL_COMMAND(NANOHUB_HAL_KEY_INFO,
                            halKeyInfo,
                            struct NanohubHalKeyInfoRx,
                            struct NanohubHalKeyInfoRx),
    NANOHUB_HAL_COMMAND(NANOHUB_HAL_START_UPLOAD,
                            halStartUpload,
                            struct NanohubHalStartUploadRx,
                            struct NanohubHalStartUploadRx),
    NANOHUB_HAL_COMMAND(NANOHUB_HAL_CONT_UPLOAD,
                            halContUpload,
                            __le32,
                            struct NanohubHalContUploadRx),
    NANOHUB_HAL_COMMAND(NANOHUB_HAL_FINISH_UPLOAD,
                            halFinishUpload,
                            struct { },
                            struct { }),
};

const struct NanohubHalCommand *nanohubHalFindCommand(uint8_t msg)
{
    uint32_t i;

    for (i = 0; i < ARRAY_SIZE(mBuiltinHalCommands); i++) {
        const struct NanohubHalCommand *cmd = &mBuiltinHalCommands[i];
        if (cmd->msg == msg)
            return cmd;
    }
    return NULL;
}


int64_t hostGetTimeDelta(void)
{
    int64_t delta = getAvgDelta(&mTimeSync);

    if (delta == INT64_MIN)
        return 0ULL;
    else
        return delta;
}

uint64_t hostGetTime(void)
{
    int64_t delta = getAvgDelta(&mTimeSync);

    if (!delta || delta == INT64_MIN)
        return 0ULL;
    else
        return sensorGetTime() + delta;
}

#if DEBUG_APHUB_TIME_SYNC

#define N_APHUB_SYNC_DATA 256
#define PRINT_DELAY 20000000  // unit ns, 20ms
struct ApHubSyncDebug {
    uint64_t apFirst;
    uint64_t hubFirst;
    uint32_t apDelta[N_APHUB_SYNC_DATA]; // us
    uint32_t hubDelta[N_APHUB_SYNC_DATA]; // us
    int printIndex; //negative means not printing
    int writeIndex;

    uint32_t printTimer;
};

static struct ApHubSyncDebug mApHubSyncDebug;

static void syncDebugCallback(uint32_t timerId, void *data)
{

    if (mApHubSyncDebug.printIndex >= mApHubSyncDebug.writeIndex ||
        mApHubSyncDebug.printIndex >= N_APHUB_SYNC_DATA) {
        timTimerCancel(mApHubSyncDebug.printTimer);

        osLog(LOG_DEBUG, "APHUB Done printing %d items", mApHubSyncDebug.printIndex);
        mApHubSyncDebug.writeIndex = 0;
        mApHubSyncDebug.printIndex = -1;

        mApHubSyncDebug.printTimer = 0;
    } else {
        if (mApHubSyncDebug.printIndex == 0) {
            osLog(LOG_DEBUG, "APHUB init %" PRIu64 " %" PRIu64,
                  mApHubSyncDebug.apFirst,
                  mApHubSyncDebug.hubFirst);
        }

        osLog(LOG_DEBUG, "APHUB %d %" PRIu32 " %" PRIu32,
              mApHubSyncDebug.printIndex,
              mApHubSyncDebug.apDelta[mApHubSyncDebug.printIndex],
              mApHubSyncDebug.hubDelta[mApHubSyncDebug.printIndex]);

        mApHubSyncDebug.printIndex++;
    }
}

static void syncDebugTriggerPrint()
{
    if (mApHubSyncDebug.printTimer) {
        //printing already going
        return;
    }

    mApHubSyncDebug.printIndex = 0;

    syncDebugCallback(0, NULL);
    if (!(mApHubSyncDebug.printTimer =
          timTimerSet(PRINT_DELAY, 0, 50, syncDebugCallback, NULL, false /*oneShot*/))) {
        osLog(LOG_WARN, "Cannot get timer for printing");

        mApHubSyncDebug.writeIndex = 0; // discard all data
        mApHubSyncDebug.printIndex = -1; // not printing
    }
}

static void syncDebugAdd(uint64_t ap, uint64_t hub)
{
    if (mApHubSyncDebug.writeIndex >= N_APHUB_SYNC_DATA) {
        //full
        syncDebugTriggerPrint();
        return;
    }

    if (mApHubSyncDebug.writeIndex == 0) {
        mApHubSyncDebug.apFirst = ap;
        mApHubSyncDebug.hubFirst = hub;
    }

    // convert ns to us
    mApHubSyncDebug.apDelta[mApHubSyncDebug.writeIndex] =
            (uint32_t) U64_DIV_BY_CONST_U16((ap - mApHubSyncDebug.apFirst), 1000u);
    mApHubSyncDebug.hubDelta[mApHubSyncDebug.writeIndex] =
            (uint32_t) U64_DIV_BY_CONST_U16((hub - mApHubSyncDebug.hubFirst), 1000u);

    ++mApHubSyncDebug.writeIndex;
}
#endif