/*
 * Copyright (C) 2012 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 <keystore.h>
#include <keystore_client.h>

#include <cutils/sockets.h>

#define LOG_TAG "keystore_client"
#include <cutils/log.h>

ResponseCode keystore_cmd(command_code_t cmd, Keystore_Reply* reply, int numArgs, ...) {
    int sock;

    sock = socket_local_client("keystore", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM);
    if (sock == -1) {
        return SYSTEM_ERROR;
    }

    if (TEMP_FAILURE_RETRY(send(sock, &cmd, 1, MSG_NOSIGNAL)) != 1) {
        close(sock);
        return SYSTEM_ERROR;
    }

    va_list vl;
    va_start(vl, numArgs);
    for (int i = 0; i < numArgs; i++) {
        size_t argLen = va_arg(vl, size_t);
        uint8_t* arg = va_arg(vl, uint8_t*);

        if (argLen > KEYSTORE_MESSAGE_SIZE) {
            ALOGE("code called us with an argLen out of bounds: %llu", (unsigned long long) argLen);
            close(sock);
            return SYSTEM_ERROR;
        }

        uint8_t bytes[2] = { argLen >> 8, argLen };
        if (TEMP_FAILURE_RETRY(send(sock, bytes, 2, MSG_NOSIGNAL)) != 2
                || TEMP_FAILURE_RETRY(send(sock, arg, argLen, MSG_NOSIGNAL))
                        != static_cast<ssize_t>(argLen)) {
            ALOGW("truncated write to keystore");
            close(sock);
            return SYSTEM_ERROR;
        }
    }
    va_end(vl);

    uint8_t code = 0;
    if (shutdown(sock, SHUT_WR) != 0
            || TEMP_FAILURE_RETRY(recv(sock, &code, 1, 0)) != 1
            || code != NO_ERROR) {
        ALOGW("Error from keystore: %d", code);
        close(sock);
        return SYSTEM_ERROR;
    }

    if (reply != NULL) {
        reply->setCode(static_cast<ResponseCode>(code));

        uint8_t bytes[2];
        uint8_t* data = reply->get();
        if (TEMP_FAILURE_RETRY(recv(sock, &bytes[0], 1, 0)) == 1
                && TEMP_FAILURE_RETRY(recv(sock, &bytes[1], 1, 0)) == 1) {
            int offset = 0;
            int length = bytes[0] << 8 | bytes[1];
            while (offset < length) {
                int n = TEMP_FAILURE_RETRY(recv(sock, &data[offset], length - offset, 0));
                if (n <= 0) {
                    ALOGW("truncated read from keystore for data");
                    code = SYSTEM_ERROR;
                    break;
                }
                offset += n;
            }
            reply->setLength(length);
        } else {
            ALOGW("truncated read from keystore for length");
            code = SYSTEM_ERROR;
        }
    }

    close(sock);
    return static_cast<ResponseCode>(code);
}

Keystore_Reply::Keystore_Reply()
        : mCode(SYSTEM_ERROR)
        , mLength(-1) {
    mData = new uint8_t[KEYSTORE_MESSAGE_SIZE];
}

Keystore_Reply::~Keystore_Reply() {
    delete[] mData;
}

uint8_t* Keystore_Reply::get() {
    return mData;
}

void Keystore_Reply::setLength(size_t length) {
    mLength = length;
}

size_t Keystore_Reply::length() const {
    return mLength;
}

void Keystore_Reply::setCode(ResponseCode code) {
    mCode = code;
}

ResponseCode Keystore_Reply::code() const {
    return mCode;
}

uint8_t* Keystore_Reply::release() {
    uint8_t* data = mData;
    mData = NULL;
    return data;
}