/*
 * 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.
 */

#ifndef STREAM_PACKET_H_
#define STREAM_PACKET_H_

#include <stdint.h>
#include <stdbool.h>
#include <sys/types.h>
#include "types.h"
#include "stream-common.h"

/* maximum size of the 'small' packet */
#define PGP_MAX_PKT_SIZE 0x100000

/* maximum size of indeterminate-size packet allowed with old length format */
#define PGP_MAX_OLD_LEN_INDETERMINATE_PKT_SIZE 0x40000000

typedef struct pgp_packet_hdr_t {
    pgp_pkt_type_t tag;                      /* packet tag */
    uint8_t        hdr[PGP_MAX_HEADER_SIZE]; /* PGP packet header, needed for AEAD */
    size_t         hdr_len;                  /* length of the header */
    size_t         pkt_len;       /* packet body length if non-partial and non-indeterminate */
    bool           partial;       /* partial length packet */
    bool           indeterminate; /* indeterminate length packet */
} pgp_packet_hdr_t;

/* structure for convenient writing or parsing of non-stream packets */
typedef struct pgp_packet_body_t {
  private:
    pgp_pkt_type_t       tag_;  /* packet tag */
    std::vector<uint8_t> data_; /* packet bytes */
    /* fields below are filled only for parsed packet */
    uint8_t hdr_[PGP_MAX_HEADER_SIZE]{}; /* packet header bytes */
    size_t  hdr_len_{};                  /* number of bytes in hdr */
    size_t  pos_{};                      /* current read position in packet data */
    bool secure_{}; /* contents of the packet are secure so must be wiped in the destructor */
  public:
    /** @brief initialize writing of packet body
     *  @param tag tag of the packet
     **/
    pgp_packet_body_t(pgp_pkt_type_t tag);
    /** @brief init packet body (without headers) with memory. Used for easier data parsing.
     *  @param data buffer with packet body part
     *  @param len number of available bytes in mem
     */
    pgp_packet_body_t(const uint8_t *data, size_t len);

    pgp_packet_body_t(const pgp_packet_body_t &src) = delete;
    pgp_packet_body_t(pgp_packet_body_t &&src) = delete;
    pgp_packet_body_t &operator=(const pgp_packet_body_t &) = delete;
    pgp_packet_body_t &operator=(pgp_packet_body_t &&) = delete;
    ~pgp_packet_body_t();

    /** @brief pointer to the data, kept in the packet */
    uint8_t *data() noexcept;
    /** @brief number of bytes, kept in the packet (without the header) */
    size_t size() const noexcept;
    /** @brief number of bytes left to read */
    size_t left() const noexcept;
    /** @brief get next byte from the packet body, populated with read() call.
     *  @param val result will be stored here on success
     *  @return true on success or false otherwise (if end of the packet is reached)
     **/
    bool get(uint8_t &val) noexcept;
    /** @brief get next big-endian uint16 from the packet body, populated with read() call.
     *  @param val result will be stored here on success
     *  @return true on success or false otherwise (if end of the packet is reached)
     **/
    bool get(uint16_t &val) noexcept;
    /** @brief get next big-endian uint32 from the packet body, populated with read() call.
     *  @param val result will be stored here on success
     *  @return true on success or false otherwise (if end of the packet is reached)
     **/
    bool get(uint32_t &val) noexcept;
    /** @brief get some bytes from the packet body, populated with read() call.
     *  @param val packet body bytes will be stored here. Must be capable of storing len bytes.
     *  @param len number of bytes to read
     *  @return true on success or false otherwise (if end of the packet is reached)
     **/
    bool get(uint8_t *val, size_t len) noexcept;
    /** @brief get next keyid from the packet body, populated with read() call.
     *  @param val result will be stored here on success
     *  @return true on success or false otherwise (if end of the packet is reached)
     **/
    bool get(pgp_key_id_t &val) noexcept;
    /** @brief get next mpi from the packet body, populated with read() call.
     *  @param val result will be stored here on success
     *  @return true on success or false otherwise (if end of the packet is reached
     *          or mpi is ill-formed)
     **/
    bool get(pgp_mpi_t &val) noexcept;
    /** @brief Read ECC key curve and convert it to pgp_curve_t */
    bool get(pgp_curve_t &val) noexcept;
    /** @brief read s2k from the packet */
    bool get(pgp_s2k_t &s2k) noexcept;
    /** @brief append some bytes to the packet body */
    void add(const void *data, size_t len);
    /** @brief append single byte to the packet body */
    void add_byte(uint8_t bt);
    /** @brief append big endian 16-bit value to the packet body */
    void add_uint16(uint16_t val);
    /** @brief append big endian 32-bit value to the packet body */
    void add_uint32(uint32_t val);
    /** @brief append keyid to the packet body */
    void add(const pgp_key_id_t &val);
    /** @brief add pgp mpi (including header) to the packet body */
    void add(const pgp_mpi_t &val);
    /**
     * @brief add pgp signature subpackets (including their length) to the packet body
     * @param sig signature, containing subpackets
     * @param hashed whether write hashed or not hashed subpackets
     */
    void add_subpackets(const pgp_signature_t &sig, bool hashed);
    /** @brief add ec curve description to the packet body */
    void add(const pgp_curve_t curve);
    /** @brief add s2k description to the packet body */
    void add(const pgp_s2k_t &s2k);
    /** @brief read 'short-length' packet body (including tag and length bytes) from the source
     *  @param src source to read from
     *  @return RNP_SUCCESS or error code if operation failed
     **/
    rnp_result_t read(pgp_source_t &src) noexcept;
    /** @brief write packet header, length and body to the dst
     *  @param dst destination to write to.
     *  @param hdr write packet's header or not
     **/
    void write(pgp_dest_t &dst, bool hdr = true) noexcept;
    /** @brief mark contents as secure, so secure_clear() must be called in the destructor */
    void mark_secure(bool secure = true) noexcept;
} pgp_packet_body_t;

/** public-key encrypted session key packet */
typedef struct pgp_pk_sesskey_t {
    unsigned             version{};
    pgp_key_id_t         key_id{};
    pgp_pubkey_alg_t     alg{};
    std::vector<uint8_t> material_buf{};

    void         write(pgp_dest_t &dst) const;
    rnp_result_t parse(pgp_source_t &src);
    /**
     * @brief Parse encrypted material which is stored in packet in raw.
     * @param material on success parsed material will be stored here.
     * @return true on success or false otherwise. May also throw an exception.
     */
    bool parse_material(pgp_encrypted_material_t &material) const;
    /**
     * @brief Write encrypted material to the material_buf.
     * @param material populated encrypted material.
     */
    void write_material(const pgp_encrypted_material_t &material);
} pgp_pk_sesskey_t;

/** pkp_sk_sesskey_t */
typedef struct pgp_sk_sesskey_t {
    unsigned       version{};
    pgp_symm_alg_t alg{};
    pgp_s2k_t      s2k{};
    uint8_t        enckey[PGP_MAX_KEY_SIZE + PGP_AEAD_MAX_TAG_LEN + 1]{};
    unsigned       enckeylen{};
    /* v5 specific fields */
    pgp_aead_alg_t aalg{};
    uint8_t        iv[PGP_MAX_BLOCK_SIZE]{};
    unsigned       ivlen{};

    void         write(pgp_dest_t &dst) const;
    rnp_result_t parse(pgp_source_t &src);
} pgp_sk_sesskey_t;

/** pgp_one_pass_sig_t */
typedef struct pgp_one_pass_sig_t {
    uint8_t          version{};
    pgp_sig_type_t   type{};
    pgp_hash_alg_t   halg{};
    pgp_pubkey_alg_t palg{};
    pgp_key_id_t     keyid{};
    unsigned         nested{};

    void         write(pgp_dest_t &dst) const;
    rnp_result_t parse(pgp_source_t &src);
} pgp_one_pass_sig_t;

/** Struct to hold userid or userattr packet. We don't parse userattr now, just storing the
 *  binary blob as it is. It may be distinguished by tag field.
 */
typedef struct pgp_userid_pkt_t {
    pgp_pkt_type_t tag;
    uint8_t *      uid;
    size_t         uid_len;

    pgp_userid_pkt_t() : tag(PGP_PKT_RESERVED), uid(NULL), uid_len(0){};
    pgp_userid_pkt_t(const pgp_userid_pkt_t &src);
    pgp_userid_pkt_t(pgp_userid_pkt_t &&src);
    pgp_userid_pkt_t &operator=(pgp_userid_pkt_t &&src);
    pgp_userid_pkt_t &operator=(const pgp_userid_pkt_t &src);
    bool              operator==(const pgp_userid_pkt_t &src) const;
    bool              operator!=(const pgp_userid_pkt_t &src) const;
    ~pgp_userid_pkt_t();

    void         write(pgp_dest_t &dst) const;
    rnp_result_t parse(pgp_source_t &src);
} pgp_userid_pkt_t;

uint16_t read_uint16(const uint8_t *buf);

uint32_t read_uint32(const uint8_t *buf);

void write_uint16(uint8_t *buf, uint16_t val);

/** @brief write new packet length
 *  @param buf pre-allocated buffer, must have 5 bytes
 *  @param len packet length
 *  @return number of bytes, saved in buf
 **/
size_t write_packet_len(uint8_t *buf, size_t len);

/** @brief get packet type from the packet header byte
 *  @param ptag first byte of the packet header
 *  @return packet type or -1 if ptag is wrong
 **/
int get_packet_type(uint8_t ptag);

/** @brief peek the packet type from the stream
 *  @param src source to peek from
 *  @return packet tag or -1 if read failed or packet header is malformed
 */
int stream_pkt_type(pgp_source_t &src);

/** @brief Peek length of the packet header. Returns false on error.
 *  @param src source to read length from
 *  @param hdrlen header length will be put here on success. Cannot be NULL.
 *  @return true on success or false if there is a read error or packet length
 *          is ill-formed
 **/
bool stream_pkt_hdr_len(pgp_source_t &src, size_t &hdrlen);

bool stream_old_indeterminate_pkt_len(pgp_source_t *src);

bool stream_partial_pkt_len(pgp_source_t *src);

size_t get_partial_pkt_len(uint8_t blen);

/** @brief Read packet length for fixed-size (say, small) packet. Returns false on error.
 *  Will also read packet tag byte. We do not allow partial length here as well as large
 *  packets (so ignoring possible size_t overflow)
 *
 *  @param src source to read length from
 *  @param pktlen packet length will be stored here on success. Cannot be NULL.
 *  @return true on success or false if there is read error or packet length is ill-formed
 **/
bool stream_read_pkt_len(pgp_source_t *src, size_t *pktlen);

/** @brief Read partial packet chunk length.
 *
 *  @param src source to read length from
 *  @param clen chunk length will be stored here on success. Cannot be NULL.
 *  @param last will be set to true if chunk is last (i.e. has non-partial length)
 *  @return true on success or false if there is read error or packet length is ill-formed
 **/
bool stream_read_partial_chunk_len(pgp_source_t *src, size_t *clen, bool *last);

/** @brief get and parse OpenPGP packet header to the structure.
 *         Note: this will not read but just peek required bytes.
 *
 *  @param src source to read from
 *  @param hdr header structure
 *  @return RNP_SUCCESS or error code if operation failed
 **/
rnp_result_t stream_peek_packet_hdr(pgp_source_t *src, pgp_packet_hdr_t *hdr);

/* Packet handling functions */

/** @brief read OpenPGP packet from the stream, and write its contents to another stream.
 *  @param src source with packet data
 *  @param dst destination to write packet contents. All write failures on dst
 *             will be ignored. Can be NULL if you need just to skip packet.
 *  @return RNP_SUCCESS or error code if operation failed.
 */
rnp_result_t stream_read_packet(pgp_source_t *src, pgp_dest_t *dst);

rnp_result_t stream_skip_packet(pgp_source_t *src);

rnp_result_t stream_parse_marker(pgp_source_t &src);

/* Public/Private key or Subkey */

bool is_key_pkt(int tag);

bool is_subkey_pkt(int tag);

bool is_primary_key_pkt(int tag);

bool is_public_key_pkt(int tag);

bool is_secret_key_pkt(int tag);

bool is_rsa_key_alg(pgp_pubkey_alg_t alg);

#endif
