/*
 * $ Copyright Cypress Semiconductor Apache2 $
 */

#ifndef UNTAR_H__
#define UNTAR_H__   1


#include <stdint.h>
#include "cy_result.h"

#ifdef __cplusplus
extern "C" {
#endif


/**
 * @ Unix Standard Tar header format
 *
 * The Open Group Base Specifications Issue 7, 2018 edition
 * IEEE Std 1003.1-2017 (Revision of IEEE Std 1003.1-2008)
 * Copyright © 2001-2018 IEEE and The Open Group
 *
 * ustar Interchange Format
 * A ustar archive tape or file shall contain a series of logical records.
 * Each logical record shall be a fixed-size logical record of 512 octets (see below).
 * Although this format may be thought of as being stored on 9-track industry-standard
 * 12.7 mm (0.5 in) magnetic tape, other types of transportable media are not excluded.
 * Each file archived shall be represented by a header logical record that describes
 * the file, followed by zero or more logical records that give the contents of the file.
 * At the end of the archive file there shall be two 512-octet logical records filled with
 * binary zeros, interpreted as an end-of-archive indicator.
 * The logical records may be grouped for physical I/O operations, as described under the
 * -b blocksize and -x ustar options. Each group of logical records may be written with a
 * single operation equivalent to the write() function. On magnetic tape, the result of
 * this write shall be a single tape physical block. The last physical block shall always
 * be the full size, so logical records after the two zero logical records may contain undefined data.
 *
 */

#define TAR_BLOCK_SIZE  512

/* General definitions */
#define TNAMELEN    100     /* name field length */
#define TMAGIC      "ustar " /* ustar plus null byte. */
#define TMAGLEN     6       /* Length of the above. */
#define TVERSION    "00"    /* 00 without a null byte. */
#define TVERSLEN    2       /* Length of the above. */

/* Typeflag field definitions */
#define REGTYPE     '0'     /* Regular file. */
#define AREGTYPE    '\0'    /* Regular file. */
#define LNKTYPE     '1'     /* Link. */
#define SYMTYPE     '2'     /* Symbolic link. */
#define CHRTYPE     '3'     /* Character special. */
#define BLKTYPE     '4'     /* Block special. */
#define DIRTYPE     '5'     /* Directory. */
#define FIFOTYPE    '6'     /* FIFO special. */
#define CONTTYPE    '7'     /* Reserved. */

/* Mode field bit definitions (octal) */
#define TSUID       04000   /* Set UID on execution. */
#define TSGID       02000   /* Set GID on execution. */
#define TSVTX       01000   /* On directories, restricted deletion flag. */
#define TUREAD      00400   /* Read by owner. */
#define TUWRITE     00200   /* Write by owner. */
#define TUEXEC      00100   /* Execute/search by owner. */
#define TGREAD      00040   /* Read by group. */
#define TGWRITE     00020   /* Write by group. */
#define TGEXEC      00010   /* Execute/search by group. */
#define TOREAD      00004   /* Read by other. */
#define TOWRITE     00002   /* Write by other. */
#define TOEXEC      00001   /* Execute/search by other. */

/**
 * Cypress definitions
 */

#define CY_UNTAR_CONTEXT_MAGIC      (0x981345A0)
#define CY_MAX_TAR_FILES            (8)
#define CY_TAR_COALESCE_BUFFER_SIZE (TAR_BLOCK_SIZE * 2)
#define CY_FILE_TYPE_LEN            (16)
#define CY_VERSION_STRING_MAX       (16)

typedef enum {
    CY_UNTAR_SUCCESS          = 0,
    CY_UNTAR_ERROR,
    CY_UNTAR_INVALID,
    CY_UNTAR_NOT_ENOUGH_DATA,
    CY_UNTAR_COMPONENTS_JSON_PARSE_FAIL
} cy_untar_result_t;

typedef enum {
    CY_TAR_PARSE_UNINITIALIZED = 0,
    CY_TAR_PARSE_FIND_HEADER,       /* tar file header */
    CY_TAR_PARSE_DATA,
} cy_tar_parse_state_t;


typedef struct ustar_header_s {
                                    /* offset       size                                */
    uint8_t name[TNAMELEN];         /*    0  0x00   100   file name (NUL terminated)    */
    uint8_t mode[8];                /*  100  0x64     8   See mode bits above           */
    uint8_t uid[8];                 /*  108  0x6c     8   User ID                       */
    uint8_t gid[8];                 /*  116  0x74     8   Group ID                      */
    uint8_t size[12];               /*  124  0x7c    12   file size                     */
                                                     /* 12 digit octal in ASCII         */
                                                     /* The number of logical records   */
                                                     /* written following the header    */
                                                     /* shall be (size+511)/512,        */
                                                     /* ignoring any fraction in the    */
                                                     /* result of the division.         */
    uint8_t mtime[12];              /*  136  0x88    12   modified time ?               */
    uint8_t chksum[8];              /*  148  0x94     8   checksum                      */
    uint8_t typeflag;               /*  156  0x9c     1   type (see above)              */
    uint8_t linkname[TNAMELEN];     /*  157  0x9d   100   linked file name              */
    uint8_t magic[TMAGLEN];         /*  257 0x101     6   TMAGIC (NUL termiinated)      */
    uint8_t version[TVERSLEN];      /*  263 0x107     2   TVERSION                      */
    uint8_t uname[32];              /*  265 0x109    32   user name                     */
    uint8_t gname[32];              /*  297 0x129    32   group name                    */
    uint8_t devmajor[8];            /*  329 0x149     8   major                         */
    uint8_t devminor[8];            /*  337 0x151     8   minor                         */
    uint8_t prefix[155];            /*  345 0x159   155   prefix                        */
                                    /*  500 0x1f4    12   padding implied to end of 512 byte block   */
} ustar_header_t;


typedef struct cy_ota_file_info_s {
    char                name[TNAMELEN];     /* copied from the components.json file         */
    char                type[CY_FILE_TYPE_LEN]; /* from components.json                         */
    int                 found_in_tar;       /* encountered the header in the tar file       */
    uint32_t            header_offset;      /* offset of header in tar file                 */
    uint32_t            size;               /* from components.json, verified from header   */
    uint32_t            processed;          /* bytes processed from tar file                */
} cy_ota_file_info_t;

/**
 * @brief callback to handle tar data
 *
 * @param ctxt          untar context
 * @param file_index    index into ctxt->files for the data
 * @param buffer        data to use
 * @param file_offset   offset into the file to store data
 * @param chunk_size    amount of data in buffer to use
 * @param cb_arg        argument passed into initialization
 *
 * return   CY_UNTAR_SUCCESS
 *          CY_UNTAR_ERROR
 */
typedef struct cy_untar_context_s * cy_untar_context_ptr;
typedef cy_untar_result_t (*untar_write_callback_t)(cy_untar_context_ptr ctxt, uint16_t file_index, uint8_t *buffer, uint32_t file_offset, uint32_t chunk_size, void *cb_arg);

typedef struct cy_untar_context_s {
    uint32_t                magic;                          /* CY_UNTAR_CONTEXT_MAGIC               */
    cy_tar_parse_state_t    state;                          /* curent parsing state                 */
    untar_write_callback_t  cb_func;                        /* callback function to deal with data  */
    void                    *cb_arg;                        /* opaque arg passed to callback        */

    int                     already_parsed_components_json; /* True if we've parsed components.json */
    uint32_t                bytes_processed;                /* bytes processed from the archive     */

    /* for JSON parsing */
    char                    version[CY_VERSION_STRING_MAX]; /* string of major.minor.build          */
    uint16_t                num_files_in_json;              /* number of files in components.json   */
    uint16_t                curr_file_in_json;              /* parsing file info in components.json */

    uint16_t                current_file;                   /* currently processing this file from the tar archive  */
    uint16_t                num_files;                      /* number of files encountered                          */
    cy_ota_file_info_t      files[CY_MAX_TAR_FILES];        /* file info                                            */

    uint32_t                coalesce_stream_offset;         /* offset into stream where coalesce buffer starts  */
    uint32_t                coalesce_bytes;                 /* number of bytes in coalesce buffer               */
    uint32_t                coalesce_needs;                 /* number of bytes needed in coalesce buffer        */
    uint8_t                 coalesce_buffer[CY_TAR_COALESCE_BUFFER_SIZE];
} cy_untar_context_t;

/**
 * @brief Determine if this is a tar header
 *
 * @param buffer[in]        ptr to data buffer
 * @param size[in]          size of buffer
 *
 * @return  CY_UNTAR_NOT_VALID
 *          CY_UNTAR_VALID
 *          CY_UNTAR_NOT_ENOUGH_DATA
 */
cy_untar_result_t cy_is_tar_header( uint8_t *buffer, uint32_t size );

/**
 * @brief Initialize our tar context
 *
 * @param ctxt[in,out]      ptr to context structure
 * @param cb_func[in]       callback function
 *
 * @return  CY_UNTAR_SUCCESS
 *          CY_UNTAR_ERROR
 */
cy_untar_result_t cy_untar_init( cy_untar_context_t *ctxt, untar_write_callback_t cb_func, void *cb_arg );

/**
 * @brief De-Initialize our tar context
 *
 * @param ctxt[in,out]      ptr to context structure
 *
 * @return  CY_UNTAR_SUCCESS
 *          CY_UNTAR_ERROR
 */
cy_untar_result_t cy_untar_deinit( cy_untar_context_t *ctxt );

/**
 * @brief Parse the tar data
 *
 * NOTE: This is meant to be called for each chunk of data received
 *       callback will be invoked when there is data to write
 *
 * @param ctxt[in,out]      ptr to context structure, gets updated
 * @param stream_offset     byte offset in current stream of start of tar_buffer
 * @param tar_buffer[in]    ptr to the next buffer of input
 * @param size[in]          bytes in tar_buffer
 * @param consumed[out]     bytes used in callbacks (or skipped at end of file)
 *
 * @return  CY_UNTAR_SUCCESS
 *          CY_UNTAR_ERROR
 */
cy_untar_result_t cy_untar_parse( cy_untar_context_t *ctxt, uint32_t stream_offset, uint8_t *tar_buffer, uint32_t size, uint32_t *consumed);


#ifdef __cplusplus
} /*extern "C" */
#endif


#endif  /* UNTAR_H__ */