/**
 ** Simple entropy harvester based upon the havege RNG
 **
 ** Copyright 2018-2022 Jirka Hladky hladky DOT jiri AT gmail DOT com
 ** Copyright 2009-2014 Gary Wuertz gary@issiweb.com
 ** Copyright 2011-2012 BenEleventh Consulting manolson@beneleventh.com
 **
 ** This program is free software: you can redistribute it and/or modify
 ** it under the terms of the GNU General Public License as published by
 ** the Free Software Foundation, either version 3 of the License, or
 ** (at your option) any later version.
 **
 ** This program is distributed in the hope that it will be useful,
 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 ** GNU General Public License for more details.
 **
 ** You should have received a copy of the GNU General Public License
 ** along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/**
 * This compile unit isolates the operation of the HAVEGE algorithm to better
 * deal with compiler issues. Extensive macro expansion used to deal with
 * hardware variations.
 */
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "havegecollect.h"
#include "havegetest.h"
#include "havegetune.h"
/**
 * Injection and capture diagnostics
 */
#if defined(RAW_IN_ENABLE) || defined(RAW_OUT_ENABLE)
#define  DIAGNOSTICS_ENABLE
#endif
/**
 * Option to use clockgettime() as timer source
 */
#if defined(ENABLE_CLOCK_GETTIME)
#include <time.h>

#undef  HARDCLOCK
#define HARDCLOCK(x) x = havege_clock()
/**
 * Provide a generic timer fallback
 */
static H_UINT havege_clock(void)
{
   struct timespec ts;

   clock_gettime(CLOCK_MONOTONIC, &ts);
   return (H_UINT)(ts.tv_nsec + ts.tv_sec * 1000000000LL);
}
#endif

/**
 * Memory allocation sizing
 */
#define  SZH_INIT       sizeof(H_COLLECT)+sizeof(char *)*(LOOP_CT + 2)
#define  SZH_COLLECT(a) sizeof(H_COLLECT)+sizeof(H_UINT)*(a+16384-1)
/**
 * The HAVEGE collector is created by interleaving instructions generated by
 * oneiteration.h with the LOOP() output to control the sequence. At each
 * LOOP() point, the following actions are possible.
 */
typedef enum {
   LOOP_NEXT,        /* Next loop  */
   LOOP_ENTER,       /* First loop */
   LOOP_EXIT         /* Last loop  */
} LOOP_BRANCH;
/**
 * The LOOP macro labels calculation sequences generated by oneiteration.h in
 * decreasing order from LOOPCT down to 0. During a normal collection, the
 * loop construct only introduces an extra conditional branch into the instruction
 * stream. For the exceptional conditions (initialization, end-of-loop, and
 * raw HARDCLOCK capture), the return from havege_cp() is used to determine
 * the action to be taken.
 */
#define LOOP(n,m) loop##n: if (n < h_ctxt->havege_cdidx) { \
                              switch(havege_cp(h_ctxt,i,n,LOOP_PT(n))) { \
                                 case LOOP_NEXT:   goto loop##m; \
                                 case LOOP_ENTER:  goto loop_enter; \
                                 case LOOP_EXIT:   goto loop_exit; \
                                 } \
                              }
/**
 * These macros below bind the code contained in oneiteration.h to the H_COLLECT
 * instance defined above
 */
#define ANDPT   (h_ctxt->havege_andpt)
#define PTTEST  (h_ctxt->havege_PTtest)
#define PT      (h_ctxt->havege_PT)
#define PT1     (h_ctxt->havege_pt2)
#define PT2     (h_ctxt->havege_PT2)
#define PWALK   (h_ctxt->havege_pwalk)
#define RESULT  (h_ctxt->havege_bigarray)
/**
 * Previous diagnostic support has been replaced. The new implementation provides
 * simultaneous access to both the noise source (i.e. the timer tics) and the
 * output. The tics buffer is also used by the injection diagnostic if enabled
 */
#ifdef DIAGNOSTICS_ENABLE
#define HTICK1          (h_ctxt->havege_tics[i>>3])
#define HTICK2          (h_ctxt->havege_tics[i>>3])
#define SZ_TICK         ((h_ptr->i_collectSz)>>3)
#else
#define HTICK1          (h_ctxt->havege_tic)
#define HTICK2          (h_ctxt->havege_tic)
#define SZ_TICK         0
#endif

/**
 * If the injection diagnostic is enabled, use a wrapper for the timer source
 */
#ifdef   RAW_IN_ENABLE
static H_UINT havege_inject(H_COLLECT *h_ctxt, H_UINT x);

#define  HARDCLOCKR(x)  x=havege_inject(h_ctxt, x)
#else
#define  HARDCLOCKR(x)  HARDCLOCK(x)
#endif
/**
 * inline optimization - left conditional for legacy systems
 */
#if 0
#define ROR32(value,shift)   ((value >> (shift)) | (value << (32-shift)))
#else
inline static H_UINT ror32(const H_UINT value, const H_UINT shift) {
   return (value >> shift) | (value << (32 - shift));
}
#define ROR32(value,shift) ror32(value, shift)
#endif
/**
 * Local prototypes
 */
static LOOP_BRANCH havege_cp(H_COLLECT *h_ctxt, H_UINT i, H_UINT n, char *p);
/**
 * Protect the collection mechanism against ever-increasing gcc optimization
 */
#if defined (GCC_VERSION) && GCC_VERSION >= 40400
static int havege_gather(H_COLLECT * h_ctxt) __attribute__((optimize(1)));
#else
static int  havege_gather(H_COLLECT * h_ctxt);
#endif
static void havege_ndinit(H_PTR h_ptr, struct h_collect *h_ctxt);

/**
 * Create a collector
 */
H_COLLECT *havege_ndcreate(/* RETURN: NULL on failure          */
   H_PTR h_ptr,            /* IN-OUT: application instance     */
   H_UINT nCollector)      /* IN: The collector instance       */
{
   H_UINT      i,offs,*p,d_cache;
   H_UINT      szBuffer;
   H_COLLECT   *h_ctxt;

   szBuffer = h_ptr->i_collectSz;
   d_cache  = ((CACHE_INST *)(h_ptr->dataCache))->size;
   h_ctxt   = (H_COLLECT *) calloc(SZH_COLLECT(szBuffer + SZ_TICK),1);
   if (NULL != h_ctxt) {
      h_ctxt->havege_app        = h_ptr;
      h_ctxt->havege_idx        = nCollector;
      h_ctxt->havege_raw        = h_ptr->havege_opts & 0xff00;
      h_ctxt->havege_rawInput   = h_ptr->inject;
      h_ctxt->havege_szCollect  = szBuffer;
      h_ctxt->havege_szFill     = szBuffer>>3;
      h_ctxt->havege_cdidx      = h_ptr->i_idx;
      p                         = (H_UINT *) RESULT;
      h_ctxt->havege_err        = H_NOERR;
      h_ctxt->havege_tests      = 0;
      h_ctxt->havege_extra      = 0;
      h_ctxt->havege_tics       = p+szBuffer;
      
      /** An intermediate walk table twice the size of the L1 cache is allocated
       ** for use in permuting time stamp readings. The is meant to exercise
       ** processor TLBs.
       */
      ANDPT = ((2*d_cache*1024)/sizeof(H_UINT))-1;
      p     = (H_UINT *) calloc((ANDPT + 4097)*sizeof(H_UINT),1);
      if (NULL != p) {
         h_ctxt->havege_extra = p;
         offs = (H_UINT)((((unsigned long)&p[4096])&0xfff)/sizeof(H_UINT));
         PWALK = &p[4096-offs];
         /**
          * Warm up the generator, running the startup tests
          */
#if defined(RAW_IN_ENABLE)
      if (0 == (h_ctxt->havege_raw & H_DEBUG_TEST_IN))
#endif
         {
            H_UINT t0=0;
            
            (void)havege_gather(h_ctxt);           /* first sample   */
            t0 = HTICK1;
            for(i=1;i<MININITRAND;i++)
               (void)havege_gather(h_ctxt);        /* warmup rng     */
            if (HTICK1==t0) {                      /* timer stuck?   */
               h_ptr->error = H_NOTIMER;
               havege_nddestroy(h_ctxt);
               return NULL;
               }
         }
#ifdef ONLINE_TESTS_ENABLE
         {
            procShared *ps = (procShared *)(h_ptr->testData);
            while(0!=ps->run(h_ctxt, 0)) {      /* run tot tests  */
               (void)havege_gather(h_ctxt);
               }
         }
         if (H_NOERR != (h_ptr->error = h_ctxt->havege_err)) {
            havege_nddestroy(h_ctxt);
            return NULL;
            }
#endif
         h_ctxt->havege_nptr = szBuffer;
         if (0 == (h_ctxt->havege_raw & H_DEBUG_RAW_OUT))
            h_ctxt->havege_szFill = szBuffer;
         }
      else {
         havege_nddestroy(h_ctxt);
         h_ptr->error = H_NOWALK;
         return NULL;
         }
      }
   else h_ptr->error = H_NOCOLLECT;
   return h_ctxt;
}
/**
 * Destruct a collector
 */
void havege_nddestroy(        /* RETURN: none           */
   H_COLLECT *h_ctxt)         /* IN: collector context  */
{
   if (0 != h_ctxt) {
      if (h_ctxt->havege_extra!=0) {
         free(h_ctxt->havege_extra);
         h_ctxt->havege_extra = 0;
         }
      if (h_ctxt->havege_tests!=0) {
         free(h_ctxt->havege_tests);
         h_ctxt->havege_tests = 0;
         }
      free((void *)h_ctxt);
   }
}
/**
 * Read from the collector.
 */
H_UINT havege_ndread(         /* RETURN: data value     */
   H_COLLECT *h_ctxt)         /* IN: collector context  */
{
   if (h_ctxt->havege_nptr >= h_ctxt->havege_szFill) {
      H_PTR h_ptr = (H_PTR)(h_ctxt->havege_app);
      pMeter pm;

      if (0 != (pm = h_ptr->metering))
         (*pm)(h_ctxt->havege_idx, 0);
#ifdef ONLINE_TESTS_ENABLE
      {
         procShared *ps = (procShared *)(h_ptr->testData);
         do {
            (void) havege_gather(h_ctxt);
            (void) ps->run(h_ctxt, 1);
            } while(ps->discard(h_ctxt)>0);
      }
#else
      (void) havege_gather(h_ctxt);
#endif
      h_ptr->n_fills += 1;
      if (0 != pm)
         (*pm)(h_ctxt->havege_idx, 1);
      h_ctxt->havege_nptr = 0;
      }
#ifdef   RAW_OUT_ENABLE
   if (0!=(h_ctxt->havege_raw & H_DEBUG_RAW_OUT))
      return h_ctxt->havege_tics[h_ctxt->havege_nptr++];
#endif
   return RESULT[h_ctxt->havege_nptr++];
}
/**
 * Setup haveged
 */
void havege_ndsetup(       /* RETURN: None                     */
   H_PTR h_ptr)            /* IN-OUT: application instance     */
{
   char  wkspc[SZH_INIT];
   
   memset(wkspc, 0, SZH_INIT);
   havege_ndinit(h_ptr, (struct h_collect *) wkspc);
}
/**
 * This method is called only for control points NOT part of a normal collection:
 *
 *   a) For a collection loop after all iterations are performed, this function
 *      determines if the collection buffer is full.
 *   b) For initialization, this method saves the address of the collection point
 *      for analysis at the end of the loop.
 */
static LOOP_BRANCH havege_cp( /* RETURN: branch to take */
   H_COLLECT *h_ctxt,         /* IN: collection context */
   H_UINT i,                  /* IN: collection offset  */
   H_UINT n,                  /* IN: iteration index    */
   char *p)                   /* IN: code pointer       */
{

   if (h_ctxt->havege_cdidx <= LOOP_CT)
      return i < h_ctxt->havege_szCollect? LOOP_ENTER : LOOP_EXIT;
   ((char **)RESULT)[n] = CODE_PT(p);
   if (n==0) h_ctxt->havege_cdidx = 0;
   return LOOP_NEXT;
}

/**
 * The collection loop is constructed by repetitions of oneinteration.h interleaved
 * with control points generated by the LOOP macro.
 */
static int havege_gather(     /* RETURN: 1 if initialized    */
   H_COLLECT * h_ctxt)        /* IN:     collector context  */
{
   H_UINT   i=0,pt=0,inter=0;
   H_UINT  *Pt0, *Pt1, *Pt2, *Pt3, *Ptinter;

#if defined(RAW_IN_ENABLE)
if (0 != (h_ctxt->havege_raw & H_DEBUG_RAW_IN)) {
   (*h_ctxt->havege_rawInput)(h_ctxt->havege_tics, h_ctxt->havege_szCollect>>3);
   h_ctxt->havege_tic = h_ctxt->havege_tics[0];
   }
else if (0 != (h_ctxt->havege_raw & H_DEBUG_TEST_IN)) {
   (*h_ctxt->havege_rawInput)(RESULT, h_ctxt->havege_szCollect);
   return 1;
   }
#endif

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"

loop_enter:
LOOP(40,39)
   #include "oneiteration.h"
LOOP(39,38)
   #include "oneiteration.h"
LOOP(38,37)
   #include "oneiteration.h"
LOOP(37,36)
   #include "oneiteration.h"
LOOP(36,35)
   #include "oneiteration.h"
LOOP(35,34)
   #include "oneiteration.h"
LOOP(34,33)
   #include "oneiteration.h"
LOOP(33,32)
   #include "oneiteration.h"
LOOP(32,31)
   #include "oneiteration.h"
LOOP(31,30)
   #include "oneiteration.h"
LOOP(30,29)
   #include "oneiteration.h"
LOOP(29,28)
   #include "oneiteration.h"
LOOP(28,27)
   #include "oneiteration.h"
LOOP(27,26)
   #include "oneiteration.h"
LOOP(26,25)
   #include "oneiteration.h"
LOOP(25,24)
   #include "oneiteration.h"
LOOP(24,23)
   #include "oneiteration.h"
LOOP(23,22)
   #include "oneiteration.h"
LOOP(22,21)
   #include "oneiteration.h"
LOOP(21,20)
   #include "oneiteration.h"
LOOP(20,19)
   #include "oneiteration.h"
LOOP(19,18)
   #include "oneiteration.h"
LOOP(18,17)
   #include "oneiteration.h"
LOOP(17,16)
   #include "oneiteration.h"
LOOP(16,15)
   #include "oneiteration.h"
LOOP(15,14)
   #include "oneiteration.h"
LOOP(14,13)
   #include "oneiteration.h"
LOOP(13,12)
   #include "oneiteration.h"
LOOP(12,11)
   #include "oneiteration.h"
LOOP(11,10)
   #include "oneiteration.h"
LOOP(10,9)
   #include "oneiteration.h"
LOOP(9,8)
   #include "oneiteration.h"
LOOP(8,7)
   #include "oneiteration.h"
LOOP(7,6)
   #include "oneiteration.h"
LOOP(6,5)
   #include "oneiteration.h"
LOOP(5,4)
   #include "oneiteration.h"
LOOP(4,3)
   #include "oneiteration.h"
LOOP(3,2)
   #include "oneiteration.h"
LOOP(2,1)
   #include "oneiteration.h"
LOOP(1,0)
   #include "oneiteration.h"
LOOP(0,0)
   (void)havege_cp(h_ctxt, i,0,LOOP_PT(0));
#pragma GCC diagnostic pop

loop_exit:
   return ANDPT==0? 0 : 1;
}
#ifdef   RAW_IN_ENABLE
/**
 * Wrapper for noise injector. When input is injected, the hardclock
 * call is not made and the contents of the tic buffer are used
 * unchanged from when the inject call was made at the top of the
 * loop.
 */
static H_UINT havege_inject(     /* RETURN: clock value           */
   H_COLLECT *h_ctxt,            /* IN: workspace                 */
   H_UINT x)                     /* IN: injected value            */
{
   if (0==(h_ctxt->havege_raw & H_DEBUG_RAW_IN)) {
      HARDCLOCK(x);
      }
   return x;
}
#endif
/**
 * Initialize the collection loop
 */
#if defined (GCC_VERSION) && GCC_VERSION >= 40600
#pragma GCC diagnostic ignored "-Warray-bounds"
#endif

static void havege_ndinit(       /* RETURN: None                     */
   H_PTR h_ptr,                  /* IN-OUT: application instance     */
   struct h_collect *h_ctxt)     /* IN: workspace                    */
{
   char  **addr = (char **)(&RESULT[0]);
   H_UINT sz;
   int   i;

   h_ctxt->havege_cdidx = LOOP_CT + 1;
   (void)havege_gather(h_ctxt);
   for (i=0;i<=LOOP_CT;i++) {
      if (0 != (h_ptr->havege_opts & H_DEBUG_COMPILE)) {
         h_ptr->print_msg("Address %u=%p\n", i, addr[i]);
         }
      RESULT[i] = labs(addr[i] - addr[LOOP_CT]);
      if (i > 0 && 0 != (h_ptr->havege_opts & H_DEBUG_LOOP)) {
         h_ptr->print_msg("Loop %u: offset=%u, delta=%u\n", i,RESULT[i],RESULT[i-1]-RESULT[i]);
         }
      }
   h_ptr->i_maxidx = LOOP_CT;
   h_ptr->i_maxsz  = RESULT[1];
   sz = ((CACHE_INST *)(h_ptr->instCache))->size * 1024;
   for(i=LOOP_CT;i>0;i--)
      if (RESULT[i]>sz)
         break;
   h_ptr->i_idx = ++i;
   h_ptr->i_sz  = RESULT[i];
}
