/*
 *  Copyright (C) 2017 - This file is part of libecc project
 *
 *  Authors:
 *      Ryad BENADJILA <ryadbenadjila@gmail.com>
 *      Arnaud EBALARD <arnaud.ebalard@ssi.gouv.fr>
 *      Jean-Pierre FLORI <jean-pierre.flori@ssi.gouv.fr>
 *
 *  Contributors:
 *      Nicolas VIVET <nicolas.vivet@ssi.gouv.fr>
 *      Karim KHALFALLAH <karim.khalfallah@ssi.gouv.fr>
 *
 *  This software is licensed under a dual BSD and GPL v2 license.
 *  See LICENSE file at the root folder of the project.
 */
#ifndef __TYPES_H__
#define __TYPES_H__

/*** Handling the target compiler and its specificities ***/
#ifdef __GNUC__
/* gcc and clang */
#define ATTRIBUTE_UNUSED __attribute__((unused))
#define ATTRIBUTE_USED __attribute__((used))
#define ATTRIBUTE_PACKED __attribute__((packed))
#define ATTRIBUTE_SECTION(a) __attribute__((__section__((a))))
#else
#define ATTRIBUTE_UNUSED
#define ATTRIBUTE_USED
#define ATTRIBUTE_PACKED
#define ATTRIBUTE_SECTION(a)
#endif

/* Macro to trick the compiler of thinking a variable is used.
 * Although this should not happen, sometimes because of #define
 * oddities we might force this.
 */
#define FORCE_USED_VAR(a) ((void)(a))

/*** Handling the types ****/
#ifdef WITH_STDLIB

/*
 * User explicitly needs to build w/ stdlib. Let's include the headers
 * we need to get basic types: (uint*_t), NULL, etc. You can see below
 * (i.e. under #else) what is precisely needed.
 */
#include <stdint.h>
#include <stddef.h>
#else /* WITH_STDLIB */

/*
 * User does not want to build w/ stdlib. Let's define basic types:
 * (uint*_t), NULL, etc.
 */
#define NULL ((void *)0)
typedef unsigned long int size_t;
typedef long int ssize_t;
/* Here is the big picture of the main programming models
 * and their primitive types sizes in bits:
 * (see http://www.unix.org/whitepapers/64bit.html)
 *
 *           | LP32 | ILP32 | LLP64 | ILP64 | LP64 |
 *           ---------------------------------------
 *  char     |   8  |   8   |   8   |   8   |   8  |
 *  short    |  16  |  16   |  16   |  16   |  16  |
 *  int      |  16  |  32   |  32   |  64   |  32  |
 *  long     |  32  |  32   |  32   |  64   |  64  |
 *  long long|  64  |  64   |  64   |  64   |  64  |
 *           --------------------------------------
 *           (long long type existence depends on the C compiler
 *	      but should be *mandatory* in C99 compliant ones)
 *
 * This means that:
 *	1) We are sure that long long are 64-bit, short are 16-bit,
 *	   and char are 8-bit (on the vast majority of platforms).
 *	2) The two types that are not consistent across platforms are
 *	the int and long types (e.g., int is 16 bits and long is 32 bits
 *	on msp430-gcc LP32, while int is 32 bits and long is 32 bits
 *	on x86_64 Linux platforms LLP64, and long becomes 64 bits on
 *	x86_64 Windows platforms LP64).
 *
 * Hence, we take a wild guess for uint32_t mapping on a primitive type
 * and check this at compilation time using the check_data_types 'union'
 * defined below.
 * Our guess depends on the WORDSIZE the user provides us, since we assume
 * this corresponds to a 'native' word size of the platform (8-bit platforms
 * such as AVR and 16-bit platforms such as MSP430 are either LP32 and ILP32,
 * which means that a 'long' type will be mapped to a 32 bits native type).
 * Consequently, when the user provides us a WORDSIZE=16, we infer that
 * uint32_t is mapped on a long type.
 *
 */
typedef unsigned long uint64_t;
/* GCC defines the size of an int, use this information
 * if it is provided
 */
#ifdef __SIZEOF_INT__
#if(__SIZEOF_INT__ == 2)
typedef unsigned long uint32_t;
#else
typedef unsigned int uint32_t;
#endif /* (__SIZEOF_INT__ == 2) */
#else
#if(WORDSIZE == 16)
/* The user has provided WORDSIZE=16, so we guess that
 * we have LP32 or ILP32: a long type would be 32-bit.
 */
typedef unsigned long uint32_t;
#else
/* Wild guess for uint32_t mapping */
typedef unsigned int uint32_t;
#endif /* (WORDSIZE == 16) */
#endif /* __SIZEOF_INT__ */
typedef unsigned short uint16_t;
typedef unsigned char uint8_t;
/* Useful macros for our new defined types */
#define UINT8_MAX  (0xff)
#define UINT16_MAX (0xffff)
#define UINT32_MAX (0xffffffff)
#define UINT64_MAX (0xffffffffffffffffULL)
#define UINT8_C(c) ((uint8_t)(c ## UL))
#define UINT16_C(c) ((uint16_t)(c ## UL))
#define UINT32_C(c) ((uint32_t)(c ## UL))
#define UINT64_C(c) (c ## ULL)

/* Sanity check on our guess for primitive types sizes.
 * See https://barrgroup.com/Embedded-Systems/How-To/C-Fixed-Width-Integers-C99
 *
 * TODO: if you get a compilation error at this point, this means that we failed
 * at guessing the C primitive types sizes for the current platform. You should
 * try to adapt the uint8_t/uint16_t/uint32_t/uint64_t types definitions in this
 * file, or find the C99 compliant stdint headers for your compiler/platform
 * and include it.
 */
typedef union {
	char uint8_t_incorrect[sizeof(uint8_t) == 1 ? 1 : -1];
	char uint16_t_incorrect[sizeof(uint16_t) == 2 ? 1 : -1];
	char uint32_t_incorrect[sizeof(uint32_t) == 4 ? 1 : -1];
	char uint64_t_incorrect[sizeof(uint64_t) == 8 ? 1 : -1];
} check_data_types;

#endif /* WITH_STDLIB */

#endif /* __TYPES_H__ */
