/* 'checkit_tiff' is a conformance checker for baseline TIFFs
 *
 * author: Andreas Romeyke, 2015-2022
 * licensed under conditions of libtiff
 * (see http://libtiff.maptools.org/misc.html)
 *
 */

#ifndef FIXIT_TIFF_CHECK
#define FIXIT_TIFF_CHECK
#ifndef GNU_SOURCE
#define GNU_SOURCE
#endif
#include <stdlib.h>
#include <unistd.h> /* for off_t definition */
#include "tiff.h"
#include "ctstring.h"
#include <stdbool.h>

typedef int thandle_t;

typedef uint16 tag_t;

/* MINTAGS - MAXTAGS is range of possible existing TAG numbers */
#define MINTAGS 254U
#define MAXTAGS 65536U

typedef enum{ is_memmap, is_filep } ct_ioflag_t ; /* flag */

typedef enum{ unknown_tag_order, has_sorted_tags, has_unsorted_tags,  } tagorder_t;

typedef struct cifd_s {
    off_t position; /* offset in tif stream (in bytes) */
    uint16 tag_count; /* count of tags in first ifd */
    tagorder_t tagorder;
    int tag_if_subifd;
    int index_cache_by_tag[MAXTAGS];
    tag_t tag_cache_by_index[MAXTAGS];
} cifd_t;

typedef struct ctiff_s {
  cifd_t *ifd0; /* first IFD */
  char *filename; /* filename */
  uint8 * streamp; /* address of memory-mapped tif stream */
  uint8 * actual_streamp;
  size_t streamlen; /* length of tif stream/file (in bytes) */
  thandle_t fd; /* filedescriptor */
  ct_ioflag_t ioflag;
  bool isbyteswapped; /* true if BigEndian */
} ctiff_t;

typedef enum{
  rm_default,
  rm_file,
  rm_rule,
  rm_tag,
  rm_value,
  rm_expected,
  rm_hard_error,
  rm_error,
  rm_warning,
  rm_precondition,
  rm_logicalor_error,
  rm_logicalor_valid,
  rm_is_valid,
  rm_endrule,
  rm_endtiff,
  rm_mode,
  rm_lineno,
  rm_error_description,
  rm_count_valid,
  rm_count_invalid,
  rm_summary_valid,
  rm_summary_invalid,
} rm_type_t;

typedef struct retmsg_s {
  string_t rm_msg;
  struct retmsg_s * next;
  rm_type_t rm_type;
} retmsg_t;

#define ICC_ERROR_OFFSET 100

typedef enum {
  is_valid=0, /* no problems detected */
  calling_error_count_size, /* a called function has wrong arguments */
  could_not_allocate_memory, /* malloc fails */
  could_not_print, /* snprintf, fprintf, print fails */
  should_not_occur, /* dummy, for dead code */
  tagerror_expected_count_differs, /* if a tag reports count=m, but the rule expects count=n */
  tagerror_expected_count_iszero, /* if a tag reports count=0, but the rule expects count=n */
  tagerror_expected_count_isgreaterone,
  tagerror_expected_count_less_than_four, /* needed for geotiff */
  tagerror_expected_count_not_a_multiple_of_four, /* needed for geotiff */
  tagerror_value_differs, /* if a tag reports value=m, but the rule expects value=n */
  tagerror_unexpected_type_found, /* if a tag reports a type=m, but a type=n was expected */
  tagerror_datetime_not_plausible, /* if a datetime tag has date values out of range */
  tagerror_datetime_wrong_format, /* if a datetime tag has date with wrong format string */
  tagerror_datetime_wrong_size, /* if a datetime tag has date with wrong size */
  tagerror_value_not_found, /* if a value of a given tag is not found */
  tagwarn_type_of_unknown_tag_could_not_be_checked, /* there is no explicite type check defined yet */
  tag_exist, /* if a tag exists, but should not (only used by fc_notag) */
  tag_does_not_exist, /* if a tag does not exist, but should (only used by fc_tag_quiet) */
  tagerror_encoded_as_value_excited_space, /* tag encoded as value instead as offset, but there is no enough space to hold it, pE. ICC-Profile encoded without offset */
  tagerror_pcre_nomatch, /*  regex does not match value */
  pcre_compile_error, /* regex is wrong */
  tagerror_multiple_zeros_in_asciivalue, /* doubled \0 in string */
  tagerror_no_zero_as_end_of_string_in_asciivalue,
  tagerror_denominator_is_zero_in_fract,
  ifderror_offset_used_twice,
  ifderror_multiple_ifd_detected,
  ifderror_tags_not_in_ascending_order,
  tagerror_offset_not_word_aligned,
  ifderror_offset_not_word_aligned,
  iccerror_profileclass= ICC_ERROR_OFFSET + 0, /* profile class ('%s'), should be one of following strings for device classes: 'scnr', 'mntr', 'prtr' or for profile classes: 'link', 'spac', 'abst', 'nmcl'" */
  iccerror_colorspacedata= ICC_ERROR_OFFSET + 1, /* colorspace data ('%s'), should be one of following strings: 'XYZ ' 'Lab ' 'Luv ' 'YCbr' 'Yvx ' 'RGB ' 'GRAY' 'HSV ' 'HLS ' 'CMYK' 'CMY ' '2CLR' '3CLR' '4CLR' '5CLR' '6CLR' '7CLR' '8CLR' '9CLR' 'ACLR' 'BCLR' 'CCLR' 'DCLR' 'ECLR' 'FCLR'" */
  iccerror_connectionspacedata= ICC_ERROR_OFFSET + 2, /* "connection space data ('%s') should be one of following strings: 'XYZ ' 'Lab '" */
  iccerror_primaryplatformsignature= ICC_ERROR_OFFSET + 3, /* primary plattform signature ('%s') should be empty or one of following strings: 'APPL', 'MSFT', 'SGI ', 'SUNW', 'TGNT' */
  iccerror_header_1v43_2010= ICC_ERROR_OFFSET + 4, /* Invalid ICC profile 1v43_2010, see http://www.color.org/specification/ICC1v43_2010-12.pdf for details  */
  iccerror_header_v240_v430= ICC_ERROR_OFFSET + 5, /* Invalid ICC profile ICC.1:2001-04, see http://www.color.org/ICC_Minor_Revision_for_Web.pdf for details */
  iccerror_header_generic= ICC_ERROR_OFFSET + 6, /* size < 10 */
  iccerror_preferredcmmtype= ICC_ERROR_OFFSET + 7, /* preferred cmmtype ('%s') should be empty or (possibly, because ICC validation is alpha code) one of following strings: 'ADBE' 'ACMS' 'appl' 'CCMS' 'UCCM' 'UCMS' 'EFI ' 'FF  ' 'EXAC' 'HCMM' 'argl' 'LgoS' 'HDM ' 'lcms' 'KCMS' 'MCML' 'WCS ' 'SIGN' 'RGMS' 'SICC' 'TCMM' '32BT' 'WTG ' 'zc00'" */
  iccerror_committed_size_differs   = ICC_ERROR_OFFSET + 8,
  iccerror_header_v500_2016 = ICC_ERROR_OFFSET +9,
  iccerror_header_version_undefined = ICC_ERROR_OFFSET +10,
  iccerror_header_version_outdated  = ICC_ERROR_OFFSET +11,
  iccerror_badprofile_id            = ICC_ERROR_OFFSET +12, /* if md5 missmatch */
  iccerror_not_ascii                = ICC_ERROR_OFFSET +13, /* if text fields uses 8bit */
  iccerror_profile_description_tag  = ICC_ERROR_OFFSET +14, /* if profile description tag has wrong length */
  iccerror_error_found_by_IccProfLib      = ICC_ERROR_OFFSET +90, /* generic IccProfLib */
  iccerror_harderror_found_by_IccProfLib      = ICC_ERROR_OFFSET +91, /* generic IccProfLib */
  iccerror_should_not_occure = ICC_ERROR_OFFSET + 99,
  tiff_seek_error_header,
  tiff_read_error_header,
  tiff_seek_error_offset,
  tiff_read_error_offset,
  tiff_byteorder_error, /* not an expected byteorder found */
  tiff_ifd0_offset_must_be_greater_than_eight, /* must be greater than 8, because first 8 Bytes contains the TIFF header */
  code_error_streampointer_empty,
  code_error_filedescriptor_empty,
  code_error_ctif_empty,
  parser_error_wrong_function_found_in_parser_state_exe_stack,
  parser_logicalor_error,
  tagerror_expected_offsetdata,
  tagerror_count_results_in_offsets_overflow, /* example: rational is defined as 2 uint32. offset is defined as uint32. If we read count>2147483647 we got offset overflow */
  parser_logical_combine_open, /* if fc_logicalcombine was called first (no error) */
  parser_logical_combine_close, /* if fc_logicalcombine was called first (no error) */
  tagerror_offset_is_zero,
  ifderror_different_cardinality,
  tagerror_not_white_listed,
  ifderror_offset_out_of_filesize,
  ifderror_zero_tags,
} returncode_t;

typedef struct ret_s {
  returncode_t returncode;
  int logical_or_count;
  string_t value_found;
  string_t expected_value;
} ret_t;

typedef struct ifd_entry_s {
  uint32 count; /*  count of data */
  TIFFDataType datatype;
  enum{ is_offset, is_value, is_error } value_or_offset;
  union {
    uint32 data32;
    uint16 data16[2];
    uint8 data8[4];
    uint32 data32offset;
  };
} ifd_entry_t;

typedef struct offset_s {
  uint32 count; /*  count of data */
  TIFFDataType datatype;
  union {
    uint8  *data8p;
    uint16 *data16p;
    uint32 *data32p;
    char   *datacharp;
    int8   *datas8p;
    int16  *datas16p;
    int32  *datas32p;
    float  *datafloatp;
    double *datadoublep;
    uint64 *data64p;
    int64  *datas64p;
  };
} offset_t;


static const int no_index_found = -1;
static const int no_file_found = -1;
#define EXPECTSTRLEN 64
#define VALUESTRLEN 1024
#define TIFFAILSTRLEN (EXPECTSTRLEN + VALUESTRLEN)
#define MAXRESULT 48000U

#ifdef HARDEN
#define TIFP_CHECK( ctif, ret ) \
  if (NULL == ctif) { ret.returncode = code_error_ctif_empty; ret.value_found=str("code error, empty ctif-structure"); return ret; };\
  if ((0 > ctif->fd) && (NULL == ctif->streamp)) {\
    if (0 > ctif->fd) { ret.returncode = code_error_filedescriptor_empty; ret.value_found=str("empty filedescriptor"); return ret; };\
    if (NULL == ctif->streamp) { ret.returncode = code_error_streampointer_empty; ret.value_found=str("empty stream pointer"); return ret; }; \
  }
#else
#define TIFP_CHECK( ctif, ret ) ;
#endif


//returncode_t tifp_check( ctiff_t * ctif);
/*
ret_t tif_returns(tag_t tag, const char* expected, const char* value);
ret_t tif_fails_tag(tag_t tag, const char* expected, const char* value);
ret_t tif_fails(const char* fail_message);
ret_t tif_fails_by_returns( ret_t ret );
ret_t tif_rules_tag(tag_t tag, const char *msg);
ret_t tif_rules(const char *msg);
ret_t tif_files(const char *msg);
ret_t tif_no_tag(tag_t tag);
ret_t _empty_result();
*/

string_t float2str(float v);
string_t tag2str(tag_t tag);
string_t int2str(int v);
string_t uint2str(unsigned int v);
string_t frac2str(int d, int n);
string_t range2str(int d, int n);
off_t ct_seek(ctiff_t * ctif, off_t pos, int whence);
ssize_t ct_read8(ctiff_t * ctif, uint8 *buf, size_t byte_count);
ssize_t ct_read16(ctiff_t * ctif, uint16 *buf, size_t word_count);
ssize_t ct_read32(ctiff_t * ctif, uint32 *buf, size_t quadruple_count);
ret_t check_tag_has_some_of_these_values( ctiff_t * ctif, tag_t tag, unsigned int count, const unsigned int * values);
ret_t check_tag_has_valuelist( ctiff_t * ctif, tag_t tag, unsigned int count, const unsigned int * values);
ret_t check_tag_has_value_in_range(ctiff_t * ctif, tag_t tag, unsigned int a, unsigned int b);
ret_t check_tag_has_value(ctiff_t * ctif, tag_t tag, unsigned int value);
ret_t check_tag_has_value_quiet(ctiff_t * ctif, tag_t tag, unsigned int expected_value);
ret_t check_tag(ctiff_t * ctif, tag_t tag);
returncode_t check_tag_quiet(ctiff_t * ctif, tag_t tag);
ret_t check_notag(ctiff_t * ctif, tag_t tag);
ret_t check_tag_has_valid_type(ctiff_t * ctif, tag_t tag);
ret_t check_datetime(ctiff_t * ctif);
ret_t check_icc(ctiff_t * ctif);
ret_t check_has_only_one_ifd(ctiff_t * ctif);
ret_t check_tagorder(ctiff_t * ctif);
ret_t check_tag_has_valid_asciivalue(ctiff_t * ctif, tag_t tag);
ret_t check_tag_has_value_matching_regex(ctiff_t * ctif, tag_t tag, const char* regex_string);
ret_t check_all_offsets_are_word_aligned(ctiff_t *ctif);
ret_t check_all_offsets_are_used_once_only(ctiff_t * ctif);
ret_t check_all_offsets_are_greater_zero(ctiff_t * ctif);
ret_t check_all_IFDs_are_word_aligned(ctiff_t *ctif);
ret_t check_all_geotiff_tags( ctiff_t * ctif);
ret_t check_cardinality_of_some_tags_are_equal( ctiff_t * ctif);
ret_t check_all_offsets_within_filesize(ctiff_t * ctif);
ret_t check_all_IFDs_have_tags(ctiff_t * ctif);
void clean_ret(ret_t ret);

#endif
/* _FIXIT_TIFF_CHECK */
