/*
 * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com).
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1.  Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *
 * 2.  Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <fstream>
#include <vector>
#include <string>

#include <rnp/rnp.h>
#include "rnp_tests.h"
#include "support.h"
#include "librepgp/stream-common.h"
#include "librepgp/stream-packet.h"
#include "utils.h"
#include <json.h>
#include <vector>
#include <string>

// structure for filling input
typedef struct {
    uint32_t remaining;
    uint8_t  dummy;
} dummy_reader_ctx_st;

// reader of sequence of dummy bytes
static bool
dummy_reader(void *app_ctx, void *buf, size_t len, size_t *read)
{
    size_t               filled = 0;
    dummy_reader_ctx_st *ctx = NULL;
    ctx = (dummy_reader_ctx_st *) app_ctx;
    filled = (len > ctx->remaining) ? ctx->remaining : len;
    if (filled > 0) {
        memset(buf, ctx->dummy, filled);
        ctx->remaining -= filled;
    }
    *read = filled;
    return true;
}

static void
test_partial_length_init(rnp_ffi_t *ffi, uint32_t key_flags)
{
    rnp_input_t input = NULL;
    // init ffi
    assert_rnp_success(rnp_ffi_create(ffi, "GPG", "GPG"));
    assert_rnp_success(
      rnp_ffi_set_pass_provider(*ffi, ffi_string_password_provider, (void *) "password"));
    if (key_flags & RNP_LOAD_SAVE_SECRET_KEYS) {
        assert_rnp_success(rnp_input_from_path(&input, "data/keyrings/1/secring.gpg"));
        assert_rnp_success(rnp_load_keys(*ffi, "GPG", input, key_flags));
        assert_rnp_success(rnp_input_destroy(input));
    }
    if (key_flags & RNP_LOAD_SAVE_PUBLIC_KEYS) {
        assert_rnp_success(rnp_input_from_path(&input, "data/keyrings/1/pubring.gpg"));
        assert_rnp_success(rnp_load_keys(*ffi, "GPG", input, key_flags));
        assert_rnp_success(rnp_input_destroy(input));
    }
}

TEST_F(rnp_tests, test_partial_length_public_key)
{
    rnp_input_t input = NULL;
    rnp_ffi_t   ffi = NULL;
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_partial_length/pubring.gpg.partial"));
    assert_int_equal(rnp_load_keys(ffi, "GPG", input, RNP_LOAD_SAVE_PUBLIC_KEYS),
                     RNP_ERROR_BAD_FORMAT);
    assert_rnp_success(rnp_input_destroy(input));
    assert_rnp_success(rnp_ffi_destroy(ffi));
}

TEST_F(rnp_tests, test_partial_length_signature)
{
    rnp_ffi_t    ffi = NULL;
    rnp_input_t  input = NULL;
    rnp_output_t output = NULL;
    // init ffi
    test_partial_length_init(&ffi, RNP_LOAD_SAVE_PUBLIC_KEYS);
    // message having partial length signature packet
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_partial_length/message.txt.partial-signed"));
    assert_rnp_success(rnp_output_to_null(&output));
    rnp_op_verify_t verify = NULL;
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_failure(rnp_op_verify_execute(verify));
    // cleanup
    assert_rnp_success(rnp_op_verify_destroy(verify));
    assert_rnp_success(rnp_input_destroy(input));
    assert_rnp_success(rnp_output_destroy(output));
    assert_rnp_success(rnp_ffi_destroy(ffi));
}

TEST_F(rnp_tests, test_partial_length_first_packet_256)
{
    rnp_ffi_t    ffi = NULL;
    rnp_input_t  input = NULL;
    rnp_output_t output = NULL;
    // init ffi
    test_partial_length_init(&ffi, RNP_LOAD_SAVE_PUBLIC_KEYS);
    // message having first partial length packet of 256 bytes
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_partial_length/message.txt.partial-256"));
    assert_rnp_success(rnp_output_to_null(&output));
    rnp_op_verify_t verify = NULL;
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    // cleanup
    assert_rnp_success(rnp_op_verify_destroy(verify));
    assert_rnp_success(rnp_input_destroy(input));
    assert_rnp_success(rnp_output_destroy(output));
    assert_rnp_success(rnp_ffi_destroy(ffi));
}

TEST_F(rnp_tests, test_partial_length_zero_last_chunk)
{
    rnp_ffi_t    ffi = NULL;
    rnp_input_t  input = NULL;
    rnp_output_t output = NULL;
    // init ffi
    test_partial_length_init(&ffi, RNP_LOAD_SAVE_PUBLIC_KEYS);
    // message in partial packets having 0-size last chunk
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_partial_length/message.txt.partial-zero-last"));
    assert_rnp_success(rnp_output_to_null(&output));
    rnp_op_verify_t verify = NULL;
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    // cleanup
    assert_rnp_success(rnp_op_verify_destroy(verify));
    assert_rnp_success(rnp_input_destroy(input));
    assert_rnp_success(rnp_output_destroy(output));
    assert_rnp_success(rnp_ffi_destroy(ffi));
}

TEST_F(rnp_tests, test_partial_length_largest)
{
    rnp_ffi_t    ffi = NULL;
    rnp_input_t  input = NULL;
    rnp_output_t output = NULL;
    // init ffi
    test_partial_length_init(&ffi, RNP_LOAD_SAVE_PUBLIC_KEYS);
    // message having largest possible partial packet
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_partial_length/message.txt.partial-1g"));
    assert_rnp_success(rnp_output_to_null(&output));
    rnp_op_verify_t verify = NULL;
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    // cleanup
    assert_rnp_success(rnp_op_verify_destroy(verify));
    assert_rnp_success(rnp_input_destroy(input));
    assert_rnp_success(rnp_output_destroy(output));
    assert_rnp_success(rnp_ffi_destroy(ffi));
}

TEST_F(rnp_tests, test_partial_length_first_packet_length)
{
    rnp_ffi_t        ffi = NULL;
    rnp_input_t      input = NULL;
    rnp_output_t     output = NULL;
    rnp_op_sign_t    sign = NULL;
    rnp_key_handle_t key = NULL;
    // uncacheable size will emulate unknown length from callback source
    size_t uncacheable_size = PGP_INPUT_CACHE_SIZE + 1;
    // init ffi
    test_partial_length_init(&ffi, RNP_LOAD_SAVE_SECRET_KEYS);
    // generate a sequence of octets with appropriate length using callback
    dummy_reader_ctx_st reader_ctx;
    reader_ctx.dummy = 'X';
    reader_ctx.remaining = uncacheable_size;
    assert_rnp_success(rnp_input_from_callback(&input, dummy_reader, NULL, &reader_ctx));
    assert_rnp_success(rnp_output_to_memory(&output, uncacheable_size + 1024));
    assert_rnp_success(rnp_op_sign_create(&sign, ffi, input, output));
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "7BC6709B15C23A4A", &key));
    assert_rnp_success(rnp_op_sign_add_signature(sign, key, NULL));
    assert_rnp_success(rnp_key_handle_destroy(key));
    key = NULL;
    // signing
    assert_rnp_success(rnp_op_sign_execute(sign));
    // read from the saved packets
    pgp_source_t src;
    uint8_t *    mem = NULL;
    size_t       len = 0;
    assert_rnp_success(rnp_output_memory_get_buf(output, &mem, &len, false));
    assert_rnp_success(init_mem_src(&src, mem, len, false));
    // skip first packet (one-pass signature)
    pgp_packet_body_t body(PGP_PKT_ONE_PASS_SIG);
    assert_rnp_success(body.read(src));
    // checking next packet header (should be partial length literal data)
    uint8_t flags = 0;
    assert_true(src_read_eq(&src, &flags, 1));
    assert_int_equal(flags, PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT | PGP_PKT_LITDATA);
    // checking length
    bool last = true; // should be reset by stream_read_partial_chunk_len()
    assert_true(stream_read_partial_chunk_len(&src, &len, &last));
    assert_true(len >= PGP_PARTIAL_PKT_FIRST_PART_MIN_SIZE);
    assert_false(last);
    // cleanup
    src_close(&src);
    assert_rnp_success(rnp_op_sign_destroy(sign));
    assert_rnp_success(rnp_input_destroy(input));
    assert_rnp_success(rnp_output_destroy(output));
    assert_rnp_success(rnp_ffi_destroy(ffi));
}
