/*******************************************************************
** receive_gmess.c  fuer den "raspberry PI B" Computer, OS=raspbian
** 14.12.2013 Aufgabe:
** Die seriellen Daten von dem gmess microcontroller lesen
** und in Dateien bereitstellen
** Der Update der Dateien jeweils nach dem Parameter -t, Fehlwert 900 Sekunden
** Die Dateien werden dan von einem jeweiligen CGI-Programm für das
** home-web aufbereitet.
**
** The Raspberry Pi has a 32-bit microprocessor.
**
** Name                C++     Bytes  Range
** bool                          1    true or false
** signed char         int8_t    1    -128 to 127
** unsigned char       uint8_t   1    0 to 255
** short int           int16_t   2    -32768 to 32767
** unsigned short int  uint16_t  2    0 to 65535
** int                 int32_t   4    -2147483648 to 2147483647
** unsigned int        uint32_t  4    0 to 4294967295
** long int            int32_t   4    -2147483648 to 2147483647
** unsigned long int   uint32_t  4    0 to 4294967295
** long long           int64_t   8    -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
** unsigned long long  uint64_t  8    0 to 18,446,744,073,709,551,615
** float                         4    +/- 3.4e +/- 38 (~7 digits)
** double                        8    +/- 1.7e +/- 308 (~15 digits)
** long double                   8    +/- 1.7e +/- 308 (~15 digits)
********************************************************************
** Die Datenwerte werden in 4 verschiedenen shared memory bereichen gespeichert.
** die shared memory Bereiche bleiben nach Beendigung des Programms erhalten.
** Nur nach einem reboot des Systems müssen sie neu angelegt werden.
** deswegen werden die shared memory Bereiche alle 2 Stunden und nach
** einer Bendigung des Programms gesichert.
*/

#include <stdio.h>      // standard input / output functions
#include <stdlib.h>
#include <string.h>     // string function definitions
#include <unistd.h>     // UNIX standard function definitions
#include <fcntl.h>      // File control definitions
#include <errno.h>      // Error number definitions
#include <time.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/shm.h>    //Used for shared memory
#include <sys/ipc.h>
#include "rxdcb.h"

// definitionen für update
#define WRITE_REGEN   0x0001
#define WRITE_VARXX   0x0010
#define WRITE_TEMP    0x0100
#define WRITE_COUNTER 0x0400

#define BUFSIZE_FIFO   256    // use 2**n : 32, 64, 128, 256, 512 ...

#define RUNNING_DIR  "/home/pi/gmess"
#define LOG_FILE     "rxdgmess.log"

//#define MMTRACE

// +++++++++++++++++++++++++++++++++++ globale variablen ++++++++++++++++++++

// *************************************** shared memory Basis ***************
int shared_memory1_id;
S_BASIS *a_basis = NULL;        // Basis Datenteil

int shared_memory2_id;
char *tdata = NULL;             // Temperatur Werte

int shared_memory3_id;
unsigned char *regenwerte=NULL; // regenwerte für 4 jahre

int shared_memory4_id;
unsigned char *counter = NULL;  // Zaehlerwerte Strom Gas u. Brauchwasser

int shared_memory5_id;
S_RDWR *a_rdwr = NULL;          // read write communication area

S_FIFO rx_fifo;                 // Stuktur FIFO empfangene Zeichen
int tfifo_da = 0;               // text fifo open

typedef struct
{
   unsigned char ss;            // Sekunden
   unsigned char mm;            // Minute
   unsigned char hh;            // Stunde
   unsigned char day;           // Tag
   unsigned char wday;          // Wochentag
   unsigned char mon;           // Monat
   unsigned char year;          // Jahr
   unsigned char dmz;           // MEZ oder MESZ
   unsigned char message_flag;  // time message überwachung
} S_CLOCK;

S_CLOCK s_clock;                // Zeit gmess


int fd_celsius;       // handle temperaturen file
int fd_regen;         // handle regenfile file
int fd_var;           // handle var file
int fd_counter;       // handle counter file
int fd_serial = -1;   // handle serial io PORT

unsigned int salt;

char text[40];
int msgindex = 0;

volatile int loop = 1;           // im signal handler auf 0

int rgflag = 99;
int initsw = 0;
unsigned int update_file_sw = 0;

                       // 1   2   3   4   5   6   7   8   9   10  11  12
unsigned char motab[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
char logline[256];

extern int errno;

char rxbuffer[ BUFSIZE_FIFO ];

// +++++++++++++++++++++++++++++++++++ Prototypen +++++++++++++++++++++++++++
void fifo_init (S_FIFO *ff, char* buf, int size);
int  fifo_put(S_FIFO *ff, char data);
int  fifo_get(S_FIFO *ff);

void sig_handler(int signo);
int a_to_i( char *quelle, int anzahl);
int berechne_PZ( char *s, unsigned char len );
void write_logfile( char* );
void decode_message(void);
void set_missing_value( int von, int anz );
int get_day_regen_addr( unsigned char jj, unsigned char mm, unsigned char day );
unsigned char current_temperatur_switch(void);
void update_files( void );
void close_all(void);
unsigned int compute_addr( int varnum , unsigned char mon,  unsigned char year );
void save_counter( int addr, int l );
int read_counter( int addr );
void check_time( int update );
int create_shared_memory(void);
void try_restore( int );
void shared_befehl( void );
void writex( int len );

void MD5Init     ( MD5Context *context);
void MD5Update   ( MD5Context *context, unsigned char *buf, unsigned long len);
void MD5Final    ( unsigned char digest[16], MD5Context *context);
static void MD5Transform(  unsigned long buf[4], unsigned long const in[16]);
static void byteReverse (  unsigned char *buf, unsigned long longs);

/****************************************************************************
**  main
*/
int main(int argc, char *argv[])
{
   time_t current, lasttime, current2, start;
   int c;
   int bytes,i,todo;
   char buffer[200];
   int  updatetime = 7200;          // jede 2. Stunde sichern
   char line[256];
   int n;
#ifdef MMTRACE
   printf("mtr 0001\n");
#endif
   
#ifdef MMTRACE
   printf("mtr 0002\n");
#endif

   chdir(RUNNING_DIR);              // change running directory
                                    // weitere parameter
   for ( i=2; i <= argc; i++ )
   {
      strcpy( line, argv[i-1] );
      if ( line[0] == '-' )
      {
         c = line[1];
         switch( c )
         {
           case 'h':
            write_logfile("use: rxdgmess [-h] [-t<nnn>]");
            write_logfile("parameter -h        = help");
            write_logfile("parameter -t<nnn>   = Updatezeit in Sekunden");
            return 0;
            break;
           
           case 't':
            bytes = strlen(line);
            if ( bytes > 2 && bytes < 8 )
            {
               updatetime = a_to_i( line+2, bytes-2 );
               sprintf( logline,"param updatetime %d Sekunden\n",updatetime);
               write_logfile( logline );
            }
            break;
         }
      }
   }
#ifdef MMTRACE
   printf("mtr 0003\n");
#endif
   if ( create_shared_memory() )     // shared memory Adressen zuweisen (neu oder vorhandene)
      return 2;

   lasttime = time(NULL);  // hole zeit
   current2 = lasttime;
   a_basis->stime = lasttime;        // merke Startzeit
   a_basis->uart_ok = 0.0;
   a_basis->uart_error = 0.0;
                                     // Ueberlaufbereiche Temperatur mit missing value belege

   if ( initsw & 0x0001 )
   {
      check_time(2);
   }
   else
   {
      set_missing_value( 0, 16384 );
      set_missing_value( 16384, 16384 );
      check_time(0);
   }
   a_basis->clear_day = s_clock.day;
   a_basis->temperatur_switch = current_temperatur_switch();
                                                    // signal handler eintragen
   if ( signal( SIGUSR1, sig_handler) == SIG_ERR )
      write_logfile("can't catch SIGUSR1");
   if ( signal( SIGTERM, sig_handler) == SIG_ERR )
      write_logfile("can't catch SIGTERM");
   if ( signal( SIGTSTP, sig_handler) == SIG_ERR )
      write_logfile("can't catch SIGTSTP");

#ifdef MMTRACE
   printf("mtr 0004\n");
#endif

   write_logfile("try serial open /dev/ttyUSB0");

   fd_serial = open( "/dev/ttyUSB0", O_RDWR| O_NONBLOCK | O_NDELAY );
   if (fd_serial < 0)
   {
      sprintf( logline,"error opening /dev/ttyUSB0 : %s",strerror(errno));
      write_logfile( logline );
      return 1;
   }
#ifdef MMTRACE
   printf("mtr 0005\n");
#endif

                   // 8N1 und speed setzen
   n = system( "stty -F /dev/ttyUSB0 ispeed 600 ospeed 600 cread -evenp raw");
   if ( n < 0 )
   {
      sprintf( logline,"error stty -F /dev/ttyUSB0 ispeed 600 ospeed 600 cread -evenp raw : %s",strerror(errno));
      write_logfile( logline );
      return 1;
   }
#ifdef MMTRACE
   printf("mtr 0006\n");
#endif

   msgindex = 0;
   fifo_init( &rx_fifo, rxbuffer, BUFSIZE_FIFO );
   start = time(NULL);                 // hole aktuelle zeit

#ifdef MMTRACE
   printf("mtr 0007\n");
#endif
   // ********************************************************************
   // warte auf serial input und verarbeite die daten
   // ********************************************************************
   do
   {
      usleep( 30000 );         // sleep 30 msek
                             // kein Warten, wenn keine Bytes da sind
      bytes = read( fd_serial, &buffer, sizeof(buffer));

      if ( bytes > 0 )       // was vorhanden
      {
         for ( i=0; i < bytes; i++ )
         {
            if ( fifo_put( &rx_fifo, buffer[i]) == 0 )
            {
               sprintf( logline,"error fifo_put, fifo buffer full");
               write_logfile( logline );
               close_all();
               return 1;
            }
         }
      }
                             // fifo buffer auslesen
      do
      {
         c = fifo_get( &rx_fifo );
         if ( c != -1 )               // fifo leer ??
         {
            if ( c == 0x02 )          // start of text ??
            {
               msgindex = 0;
               text[ msgindex++ ] = (char)c;
            }
            else if( c == 0x03)       // end of text ??
            {
               text[ msgindex++ ] = (char)c;
               text[ msgindex ] = '\0';
                                      // *****************************
               decode_message();      // *** Nachricht verarbeiten ***
                                      // *****************************
               msgindex = 0;
            }
            else
            {
               text[ msgindex++ ] = (char)c;
               if ( msgindex > 35 )
                  msgindex = 0;
            }
         }
      } while( c != -1 );                   // loop solange zeichen vorhanden

      current = time(NULL);                 // hole aktuelle zeit
      if ( current > current2 )             // gleiche Zeit ??
      {
         current2 = current;
         if ( lasttime != 0 )
         {
            if ( (current - lasttime) > updatetime )        // update time limit da ??
            {
               lasttime = (current/updatetime)*updatetime;  // sync auf nn sekunden
               update_file_sw = WRITE_VARXX + WRITE_COUNTER + WRITE_REGEN + WRITE_TEMP;
            }
         }
      }

      if ( a_rdwr->flag == CFLAG_EXEC )      // Wartung shared memory via web
      {
         a_rdwr->flag = CFLAG_BUSY;
         shared_befehl();
      }
                                             // sicherung shared memory notwendig
      if (  update_file_sw )
         update_files();

   } while( loop == 1 );

   sprintf( logline, "msg reveived ok...: %.0f", a_basis->uart_ok);
   write_logfile( logline );

   sprintf( logline, "msg reveived error: %.0f", a_basis->uart_error);
   write_logfile( logline );

   close_all();

   return loop;
}



/*****************************************************************************
** inter process communication
** 9.1.14 salt request dazu, rxdgmess definiert Salt !
** 13.1.14 commands 61-68 dazu, try restore shared memory files
*/
void shared_befehl( void )
{
   int i;
   unsigned int x;
   unsigned char *s;
   unsigned char rc = 1;
   MD5Context md5ctx[ 1 ] ;
   unsigned char code[18] = "01234567Key477349";
   unsigned char ergebnis[16];              // md5 128 Bits

   if ( a_rdwr->command == 0x69 )           // salt request
   {
      salt = (unsigned int)time(NULL);
      salt ^= 0xAFFEDADA ;
      a_rdwr->salt = salt;
      a_rdwr->flag = CFLAG_FIN;
      return;
   }
                                            // hier normaler request
   x = salt;
   for ( i=7; i >= 0; i-- )
   {
      code[i] = (x & 0x0F) + 0x30;          //
      x >>= 4;
   }

   MD5Init( md5ctx );
   MD5Update( md5ctx, code, 17 );
   MD5Final ( ergebnis, md5ctx);            // MD5 cmd

   s = a_rdwr->md5;                         //
   for ( i=0; i < 16; i++)
   {
      if ( s[i] != ergebnis[i] )            //
      {
         rc = 0;
         break;
      }
   }

   a_rdwr->salt = 0;                        // clear pass

   if ( rc )
   {
      switch ( a_rdwr->command )
      {
        case 21: writex( 1 ); break;
        case 22: writex( 2 ); break;
        case 24: writex( 4 ); break;
        case 28: writex( 8 ); break;

        case 61: try_restore( 1 ); a_rdwr->rc = C_OK; break;
        case 62: try_restore( 2 ); a_rdwr->rc = C_OK; break;
        case 64: try_restore( 4 ); a_rdwr->rc = C_OK; break;
        case 68: try_restore( 8 ); a_rdwr->rc = C_OK; break;

        default:
         a_rdwr->rc = C_ERROR;
         a_rdwr->flag = CFLAG_FIN;
         break;
      }
   }
   else
   {
      a_rdwr->rc = C_MD5ERROR;
      a_rdwr->flag = CFLAG_FIN;
   }
}
/*****************************************************************************
** write n bytes
*/
void writex( int len )
{
   int i,a;

   a_rdwr->rc = C_ERROR;

   switch ( a_rdwr->memsw )
   {
     // ................................................
     case SHR_VARXX:        // shared_varxx
      a_rdwr->rc = C_OK;
      break;

     // ................................................
     case SHR_CELSIUS:      // shared_celsius
      a_rdwr->rc = C_OK;
      break;

     // ................................................
     case SHR_COUNTER:      // shared counter
      if ( (a_rdwr->adresse + len) < 768 )
      {
         a = a_rdwr->adresse;
         for ( i=0; i < len; i++)
            counter[a++] = a_rdwr->reg[i];
         a_rdwr->rc = C_OK;
      }
      break;

     // ................................................
     case SHR_REGEN:        // shared regen
      if ( (a_rdwr->adresse + len) < 4096 )
      {
         a = a_rdwr->adresse;
         for ( i=0; i < len; i++)
            regenwerte[a++] = a_rdwr->reg[i];
         a_rdwr->rc = C_OK;
      }
      break;
      // ................................................
   }
   a_rdwr->flag = CFLAG_FIN;
}



/*****************************************************************************
** create shared memory
*/
int create_shared_memory(void)
{
   void *ptr;
   //----------------------------------------------------------------------------------
   // CREATE SHARED MEMORY -----
   // mem_id = shmget( Shared memory key , Size in bytes, Permission flags)
   // |                |                   |>             |> Unix permission octal | IPC_CREAT
   //                  |
   //                  |> Unique non zero integer (usually 32 bit).
   shared_memory1_id = shmget((key_t)1433571165, sizeof(S_BASIS), 0644 | IPC_CREAT);

   if ( shared_memory1_id == -1 )
   {
      write_logfile("Shared memory1 shmget() failed");
      return 2;
   }                                           // Make the shared memory accessible to the program
   ptr = shmat( shared_memory1_id, (void *)0, 0);
   if ( ptr == (void *)-1)
   {
      write_logfile( "Shared memory1 shmat() failed");
      return 2;
   }
   sprintf( logline, "Shared memory1 attached at %X", (int)ptr);
   write_logfile(logline);
   a_basis = (S_BASIS*)ptr;                // Assign the shared_memory segment
#ifdef MMTRACE
   printf("cr_sh 01\n");
#endif
   // textfifo_init( &a_basis->logFiFO, a_basis->ftexte );

   if ( a_basis->version == 7 && a_basis->notation == 0x76543210 )
   {
      initsw = 1;
      write_logfile("Utilizable shared memory1 found");
   }
   else
   {
      memset( (char*)a_basis, '\0', (size_t)sizeof(S_BASIS) );
      a_basis->version = 7;
      a_basis->notation = 0x76543210;
   }

                                           // Temperatur Speicher
   shared_memory2_id = shmget((key_t)1433571166, 32768, 0644 | IPC_CREAT);

   if ( shared_memory2_id == -1 )
   {
      write_logfile("Shared memory2 shmget() failed");
      return 2;
   }
                                           // Make the shared memory accessible to the program
   ptr = shmat( shared_memory2_id, (void *)0, 0);
   if ( ptr == (void *)-1)
   {
      write_logfile("Shared memory2 shmat() failed");
      return 2;
   }
   sprintf( logline, "Shared memory2 attached at %X", (int)ptr);
   write_logfile(logline);
   tdata = (char*)ptr;                    // Assign the shared_memory segment
#ifdef MMTRACE
   printf("cr_sh 02\n");
#endif
                                           // Regenwerte
   shared_memory3_id = shmget((key_t)1433571167, 4096, 0644 | IPC_CREAT);

   if ( shared_memory3_id == -1 )
   {
      write_logfile( "Shared memory3 shmget() failed");
      return 2;
   }
                                           // Make the shared memory accessible to the program
   ptr = shmat( shared_memory3_id, (void *)0, 0);
   if ( ptr == (void *)-1)
   {
      write_logfile( "Shared memory3 shmat() failed");
      return 2;
   }
   sprintf( logline, "Shared memory3 attached at %X", (int)ptr);
   write_logfile( logline);
   regenwerte = (unsigned char*)ptr;      // Assign the shared_memory segment
#ifdef MMTRACE
   printf("cr_sh 03\n");
#endif
                                           // Counter
   shared_memory4_id = shmget((key_t)1433571168, 768, 0644 | IPC_CREAT);

   if ( shared_memory4_id == -1 )
   {
      write_logfile( "Shared memory4 shmget() failed");
      return 2;
   }
                                           // Make the shared memory accessible to the program
   ptr = shmat( shared_memory4_id, (void *)0, 0);
   if ( ptr == (void *)-1)
   {
      write_logfile("Shared memory4 shmat() failed");
      return 2;
   }
   sprintf( logline, "Shared memory4 attached at %X", (int)ptr);
   write_logfile( logline );
   counter = (unsigned char*)ptr;          // Assign the shared_memory segment
#ifdef MMTRACE
   printf("cr_sh 04\n");
#endif
                                           // RDWR Area
   shared_memory5_id = shmget((key_t)1433571169, sizeof(S_RDWR), 0666 | IPC_CREAT);

   if ( shared_memory5_id == -1 )
   {
      write_logfile( "Shared memory5 shmget() failed");
      return 2;
   }
                                           // Make the shared memory accessible to the program
   ptr = shmat( shared_memory5_id, (void *)0, 0);
   if ( ptr == (void *)-1)
   {
      write_logfile("Shared memory5 shmat() failed");
      return 2;
   }
   sprintf( logline, "Shared memory5 attached at %X", (int)ptr);
   write_logfile( logline );
   a_rdwr  = (S_RDWR*)ptr;          // Assign the shared_memory segment
   memset( a_rdwr, '\0', sizeof( S_RDWR));   // clear
   a_rdwr->pid = getpid();          // eigene pid speichern

#ifdef MMTRACE
   printf("cr_sh 05\n");
#endif

   if ( initsw == 0 )
   {
#ifdef MMTRACE
      printf("cr_sh 06\n");
#endif

      try_restore( 0x0F );           // alle 4 shared speicher
   }
#ifdef MMTRACE
   else
      printf("cr_sh 07\n");
#endif
   return 0;
}


/*****************************************************************************
** fifo struct initialisieren
*/
void fifo_init (S_FIFO *ff, char *buffer, int size)
{
   ff->count = 0;
   ff->read  = 0;
   ff->write = 0;
   ff->buffer = buffer;
}


/*****************************************************************************
** zeichen in den fifo buffer ablegen
*/
int fifo_put( S_FIFO *ff, char data )
{
   if ( ff->count >= BUFSIZE_FIFO )
      return 0;

   ff->write &= BUFSIZE_FIFO-1;   // wrap around
   ff->buffer[ ff->write++ ] = data;
   ff->count++;
   return 1;
}


/*****************************************************************************
** zeichen aus dem fifo buffer holen
*/
int fifo_get(S_FIFO *ff)
{
   char data;

   if ( ff->count == 0 )
      return -1 ;

   ff->read &= BUFSIZE_FIFO-1;   // wrap around
   data = ff->buffer[ ff->read++ ];
   ff->count--;

   return data;
}


/*****************************************************************************
** u_clock setzen und Tageswechsel erkennen
*/
void check_time( int update )
{
   int i,n;
   time_t xt;
   unsigned char x,c;
   char *ct;
   char tt[30];
   char mtext[50];

   xt = time(NULL);
   a_basis->last_time = (int)xt;

   ct = ctime( &xt );                       // Zeit aufbereiten
   strcpy( tt, ct );

   i = a_to_i( tt+17, 2 );                  // Sekunde
   s_clock.ss  = (unsigned char)i;

   i = a_to_i( tt+14, 2 );                  // Minute
   s_clock.mm  = (unsigned char)i;

   i = a_to_i( tt+11, 2 );                  // Stunde
   s_clock.hh  = (unsigned char)i;

   i = a_to_i( tt+8, 2 );                   // Tag
   s_clock.day = (unsigned char)i;

   // Monate: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
   c = tt[4];
   switch ( c )
   {
     case 'F': s_clock.mon = 2; break;

     case 'M':
      if ( tt[6] == 'r' )
         s_clock.mon = 3;
      else
         s_clock.mon = 5;
      break;

     case 'A':
      if ( tt[5] == 'p' )
         s_clock.mon = 4;
      else
         s_clock.mon = 8;
      break;

     case 'J':
      if ( tt[5] == 'a' )
         s_clock.mon = 1;
      else if ( tt[6] == 'n' )
         s_clock.mon = 6;
      else
         s_clock.mon = 7;
      break;

     case 'S':  s_clock.mon = 9; break;
     case 'O':  s_clock.mon = 10; break;
     case 'N':  s_clock.mon = 11; break;
     case 'D':  s_clock.mon = 12; break;
   }

   i = a_to_i( tt+22, 2 );                  // Jahr
   s_clock.year = (unsigned char)i;

   if ( update )
   {
      x = (unsigned char)a_basis->clear_day ;  // CLEAR DAY
      if ( x != s_clock.day )                  // tageswechsel
      {
         a_basis->clear_day = s_clock.day;
         c = current_temperatur_switch();      // laufender Tages switch
         a_basis->temperatur_switch = c ;      // change t-Flag

         i = 0;                                // clear neuer Tag
         if ( c )                              // Temperatur flag == 1
            i = 16384 ;

         for ( n=0; n < 8192; n++ )            // clear new
         {
            tdata[i] = '=';
            i += 2;
         }

         if ( update == 2 )                    // nur in der Init Phase
         {
                                               // Regen tageswechsel nicht erkannt
            a_basis->regen_vorgestern = a_basis->regen_gestern;
            a_basis->regen_gestern = a_basis->regen_heute;
            a_basis->regen_heute = 0;             // regen heute = 0
         }
         update_file_sw = WRITE_VARXX + WRITE_TEMP + WRITE_REGEN + WRITE_COUNTER;
      }
   }
}



/*****************************************************************************
**  Laufender Tag seit dem 1.1.2013
*/
unsigned char current_temperatur_switch(void)
{
   unsigned char j,i;
   unsigned char tage=0;
                               // 1. Schritt = berechnen der Tage des aktuellen Tages
   for( j=13; j <= s_clock.year; j++ )
   {
      if ( j % 4 == 0 )        // Schaltjahr ??
         motab[1] = 29;
      else
         motab[1] = 28;

      if ( j == s_clock.year )
      {
         for ( i=0; i < (s_clock.mon-1) ; i++)
         {
            tage += motab[i];
            tage &= 0x01;
         }
         tage += s_clock.day;
      }
      else
      {
         if ( j % 4 != 0 )      // Schaltjahr ??
            tage += 1;
      }
      tage &= 0x01;
   }
   return( tage & 0x01);
}



/******************************************************************************************
** gesicherte daten laden
**
** gmess_var30
** gmess_regen
** gmess_celsius
** gmess_counter
*/
void try_restore( int param )
{
                     // 01234567890123
   char regenname[]  = "gmess_regen";
   char celsius[]    = "gmess_celsius";
   char *s;
   char *sc;
   char filename[256] = "";
   int rbytes;

   // .............................................. gmess_var30 ............
   if ( param & 0x01 )
   {
      strcpy(filename,"gmess_var30");
#ifdef MMTRACE
      printf("%s\n",filename);
#endif
      fd_var = open( filename, O_RDONLY );
      if ( fd_var == -1 )
      {
         sprintf( logline, "error %d open %s", errno, filename);
#ifdef MMTRACE
         printf("%s",logline);
#endif
         write_logfile( logline );
      }
      else
      {
         rbytes = read( fd_var, (char*)a_basis, sizeof(S_BASIS));
         close( fd_var );
         sprintf( logline, "restored %d bytes from %s", rbytes, filename);
#ifdef MMTRACE
         printf("%s",logline);
#endif
         write_logfile( logline );
      }
   }

   // .............................................. gmess_regen ............
   if ( param & 0x02 )
   {
      s = regenwerte;
      strcpy(filename,regenname);
#ifdef MMTRACE
      printf("%s\n",filename);
#endif
      fd_regen = open( regenname, O_RDONLY );
      if ( fd_regen == -1 )
      {
         sprintf( logline,"error %d open %s", errno, filename );
#ifdef MMTRACE
         printf("%s",logline);
#endif
         write_logfile( logline );
      }
      else
      {
         rbytes = read( fd_regen, (char *)s, 4096 );
         close( fd_regen );
         sprintf( logline, "restored %d bytes from %s", rbytes, filename);
#ifdef MMTRACE
         printf("%s",logline);
#endif
         write_logfile( logline );
      }
   }

   // .............................................. gmess_celsius ............
   if ( param & 0x04 )
   {
      sc = tdata;
      strcpy(filename,celsius);
#ifdef MMTRACE
      printf("%s\n",filename);
#endif
      fd_celsius = open( filename, O_RDONLY );
      if ( fd_celsius == -1 )

      {
         sprintf( logline,"error %d open %s", errno, filename );
#ifdef MMTRACE
         printf("%s",logline);
#endif
         write_logfile( logline );
      }
      else
      {
         rbytes = read( fd_celsius, sc, 32768 );
         close( fd_celsius );
         sprintf( logline, "restored %d bytes from %s", rbytes, filename);
#ifdef MMTRACE
         printf("%s",logline);
#endif
         write_logfile( logline );
      }
   }
   // .............................................. gmess_counter ............
   if ( param & 0x08 )
   {
      sc = counter;
      strcpy(filename,"gmess_counter");
#ifdef MMTRACE
      printf("%s\n",filename);
#endif
      fd_counter = open( filename, O_RDONLY );

      if ( fd_counter == -1 )
      {
         sprintf( logline,"error %d open %s", errno, filename);
#ifdef MMTRACE
         printf("%s",logline);
#endif
         write_logfile( logline );
      }
      else
      {
         rbytes = read( fd_counter, sc, 768 );
         close( fd_counter );
         sprintf( logline, "restored %d bytes from %s", rbytes, filename);
#ifdef MMTRACE
         printf("%s",logline);
#endif
         write_logfile( logline );
      }
   }
}



/******************************************************************************************
** jeweils die daten sichern
** 30.12.2013 li jeweils nur eine Datei sichern
**               dazwischen read loop, dann die nächste Datei usw. bis update_file_sw=0
**
** gmess_var30
** gmess_regen
** gmess_celsius
** gmess_counter
*/
void update_files( void )
{
                     // 01234567890123
   char regenname[]  = "gmess_regen";
   char celsius[]    = "gmess_celsius";
   char *s;
   char *sc;
   char filename[256];

   if ( update_file_sw & WRITE_VARXX )
   {
      strcpy(filename,"gmess_var30");

      fd_var = open( filename, O_WRONLY | O_CREAT, 0644 );
      if ( fd_var == -1 )
      {
         sprintf( logline,"error %d open %s", errno, filename);
         write_logfile( logline );
      }
      else
      {
         write( fd_var, (char*)a_basis, sizeof(S_BASIS));
         close( fd_var );
      }
      update_file_sw &= ~WRITE_VARXX;
      return;
   }

   if ( update_file_sw & WRITE_REGEN )
   {
      s = regenwerte;

      strcpy(filename,regenname);

      fd_regen = open( regenname, O_WRONLY | O_CREAT, 0644 );
      if ( fd_regen == -1 )
      {
         sprintf( logline,"error %d open %s", errno, filename );
         write_logfile( logline );
      }
      else
      {
         write( fd_regen, (char *)s, 4096 );
         close( fd_regen );
      }
      update_file_sw &= ~WRITE_REGEN;
      return;
   }

   if ( update_file_sw & WRITE_TEMP )
   {
      sc = tdata;
      strcpy(filename,celsius);

      fd_celsius = open( filename, O_WRONLY | O_CREAT, 0644 );
      if ( fd_celsius == -1 )
      {
         sprintf( logline,"error %d open %s", errno, filename );
         write_logfile( logline );
      }
      else
      {
         write( fd_celsius, sc, 32768 );
         close( fd_celsius );
      }
      update_file_sw &= ~WRITE_TEMP;
      return;
   }

   if ( update_file_sw & WRITE_COUNTER )
   {
      strcpy(filename,"gmess_counter");

      fd_counter = open( filename, O_WRONLY | O_CREAT, 0644 );
      if ( fd_counter == -1 )
      {
         sprintf( logline, "error %d open %s", errno, filename);
         write_logfile( logline );
      }
      else
      {
         s = counter;
         write( fd_counter, (char *)s, 768 );
         close( fd_counter );
      }
      update_file_sw &= ~WRITE_COUNTER;
      return;
   }
}



/*****************************************************************************
** missing value kann ein base64 (eigener key!) wert >4000 sein.
** zur besseren Unterscheidung wird hier =M eingetragen
*/
void set_missing_value( int von, int anz )
{
   int i;

   for ( i=0; i < anz; i+=2 )
   {
      tdata[von] = '=';
      tdata[von+1] = 'M';
      von += 2;
   }
}



/*****************************************************************************
**  UART Message von gmess_v2 (Heizung) dekodieren
**
**  sLmmmmzzzzPe   in minuten Sonnen Auf / Untergang
**  sDddmmjjwPe    Datum: tag monat jahr wochentag
**  sThhmmSPe      Zeit: stunde minute MESZ
**  sSssPe         Sekunde
**  sCwwwwPe       Temperaturwert + 1024 Aussen
**  sGwwwwPe       Temperaturwert + 1024 Gewaechshaus
**  sVwwwwPe       Temperaturwert + 1024 Vorlauf
**  sBwwwwPe       Temperaturwert + 1024 Rücklauf
**  sWwwwwPe       Temperaturwert + 1024 Warm Wasser
**  sRFnnnPe       Regenmesser Ticks ( max. 999 je Stunde, dann reset)
**  s<aaPe         aa = Alarm Flag, P = Pruefziffer
**  sXnnnnnnnnPe   Gas Zählerstand ( 2 komma stellen )
**  sYnnnnnnnnPe   Brauchwasser Anteil ( 2 komma stellen )
**  sZnnnnnnnnPe   Stromzählerstand Heizung ( 3 komma stellen )
*/
void decode_message( void )
{
   unsigned char p, c;
   int rest;
   int regen, y, z, x;
   int addr=0;
   int oldaddr;
   int utemp=0;
   int lx,ly;
   char *s;
                    // eigener base64 key
                    //           1         2         3         4         5         6
                    // 0123456789012345678901234567890123456789012345678901234567890123
   static char key[] ="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz*=";

   c = text[1];
   s = &text[2];

   check_time(1);   // current unix Zeit aufbereiten

   switch ( c )     // 2. Zeichen = Message Kennung
   {
     case 'S':      // Sekunde += 1
      p = berechne_PZ( s, 2 ) + 0x30 ;    // Pruefzeichen
      if ( p == s[2] )                    // check transfer
      {
         a_basis->uart_ok++;
      }
      else
      {
         a_basis->uart_error++;
      }
      break;

     case 'T':        // ***************   Zeit
      p = berechne_PZ( s, 5 ) + 0x30 ;                    // Pruefzeichen
      if ( p == s[5] )                                    // check transfer
      {
         s_clock.dmz = (unsigned char)a_to_i( s+4, 1 );   // dmz
         a_basis->uart_ok++;
      }
      else
      {
         a_basis->uart_error++;
      }
      break;

     case 'D':        // ****************  Datum
      p = berechne_PZ( s, 7 ) + 0x30 ;    // Pruefzeichen
      if ( p == s[7] )                    // check transfer
      {
         s_clock.wday = (unsigned char)a_to_i( s+6, 1 );  // wochentag
         a_basis->uart_ok++;
      }
      else
      {
         a_basis->uart_error++;
      }
      break;

     case 'L':                           // Solar Zeiten: use var_array 0 und 1
      p = berechne_PZ( s, 8 ) + 0x30 ;   // Pruefzeichen
      if ( p == s[8] )                   // check transfer
      {
         y = a_to_i( s, 4 );             // Zeit in Minuten Auf oder Untergang
         a_basis->sonnen_auf =  y ;
         y = a_to_i( s+4, 4 );           // Zeit in Minuten Auf oder Untergang
         a_basis->sonnen_unt =  y ;
         a_basis->uart_ok++;
      }
      else
      {
         a_basis->uart_error++;
      }
      break;

                      // #define A_TEG    BIT0        // .... ...1 Tuer Terasse auf
                      // #define A_TWA    BIT1        // .... ..1. Tuer Waschkueche auf
                      // #define A_TKE    BIT2        // .... .1.. Keller Tuer zu
                      // #define A_BWM    BIT3        // .... 1... Bewegungsmelder
                      // #define A_FEG    BIT4        // ...1 .... Fenster EG auf
                      // #define A_REG    BIT5        // ..1. .... RegenM. tot
                      // #define A_BTOT   BIT6        // .1.. .... BASIS TOT

     case '<':        // Alarm
      p = berechne_PZ( s, 2 ) + 0x30 ;    // Pruefzeichen
      if ( p == s[2] )                    // check transfer
      {
         a_basis->alarm = a_to_i( s, 2 );
         a_basis->uart_ok++;
      }
      else
      {
         a_basis->uart_error++;
      }
      break;
                      // **************************************** raspberry Version
     case 'C':        // Celsius Aussen Temperatur +1024
     case 'G':        // Celsius Gewächshaus +1024
     case 'V':        // Celsius Vorlauf + 1024
     case 'B':        // Celsius Rücklauf + 1024
     case 'W':        // Celsius WarmWasser + 1024
      p = berechne_PZ( s, 4 ) + 0x30 ;    // Pruefzeichen
      if ( p == s[4] )                    // check transfer
      {
         a_basis->uart_ok++;
         y = a_to_i( s, 4 );             // Temperaturwert

         if ( a_basis->temperatur_switch & 0x01 )   // Temperatur flag == 1
            addr = 16384;
         else
            addr = 0;
                      // ** 07.2.2013 * Temperaturen speichern geändert
                      // 60 * 24 = 1440 Werte (je Minute ein Wert)
                      // Speicherung als base64 Zahl
                      // 1440 * 2 = 2880 Bytes
                      //
         if ( c == 'C' )
         {
            a_basis->temperatur_C = y;
            addr += (3072*2);             // 0x1680
         }
         else if ( c == 'G' )
         {
            a_basis->temperatur_G = y;
            addr += (3072*4);             // 0x2d00
         }
         else if ( c == 'V' )
         {
            a_basis->temperatur_V = y;
         }
         else if ( c == 'B' )
         {
            a_basis->temperatur_B = y;
            addr += 3072;                 // 0x0B40
         }
         else if ( c == 'W' )
         {
            a_basis->temperatur_W = y;
            addr += (3072*3);             // 0x21c0
         }
                                          // Zeit offset berechnen
         utemp = s_clock.hh;
         utemp *= 120;                    // 60 * 2 = 120 Byte je Stunde
         utemp += (s_clock.mm * 2);       // 2 Bytes je Minute 0-59
         addr  += utemp;

         if ( addr > 32766 )
         {
            sprintf( logline, "addr %d > 32766 T=%c hh=%d mm=%d Jahr=%d", addr, c, s_clock.hh, s_clock.mm, s_clock.year);
            write_logfile( logline );
         }
         else
         {         // temperatur als 2Byte Wert base64 speichern
            x = (y >> 6) & 0x003F;           // /= 64    high Wert
            x = key[x];
            tdata[addr++] = (char)x ;
            x = (y & 0x003F);                // rest 64  low Wert
            x = key[x];
            tdata[addr] = (char)x ;
         }
      }
      else
      {
         a_basis->uart_error++;
      }
      break;

          // 13.2.2013 neue Organisation der Werte
          // Stundenwechsel: regenwert ist für die alte Stunde zu addieren
     case 'R':                                 // Regenmesser 20.03.2011
      p = berechne_PZ( s, 5 ) + 0x30 ;         // Pruefzeichen
      if ( p == s[5] )                         // check transfer
      {
         regen = a_to_i( s, 5 );               // regenmenge der letzten Stunde
         rest  = regen >> 12;
         regen &= 0x0FFF;                      // Filter die menge

         a_basis->regen_stunde = regen;        // Save stunden wert

         if ( rgflag != rest )                 // Stunden wechsel ?
         {
            rgflag = rest ;                    // merke neue Stunde
            a_basis->regen_heute += regen;     // save summe nach heute

            if ( rgflag == 0 )                 // Tageswechsel ?
            {
               addr = a_basis->regen_adresse;  // adresse letzter Stunden wechsel holen
               z = a_basis->regen_heute;
               c = (unsigned char)(z >> 8) & 0xFF;
               regenwerte[ addr++ ] =  c;
               c = (unsigned char)(z & 0xFF);
               regenwerte[ addr ] =  c;
               a_basis->regen_vorgestern = a_basis->regen_gestern;
               a_basis->regen_gestern = a_basis->regen_heute;
               a_basis->regen_heute = 0;             // regen heute = 0
            }
            else
            {
                                                // adresse heute ermitteln
               a_basis->regen_adresse = get_day_regen_addr( s_clock.year, s_clock.mon, s_clock.day );
            }
            a_basis->regen_stunde = 0;           // clear stunden wert
         }
         a_basis->uart_ok++;
      }
      else
      {
         a_basis->uart_error++;
      }
      break;
                                   // Wrap around 12 Monate --------------------
     case 'X':                     // Gas   Zaehlerstand 8.9.2011
     case 'Y':                     // Gas   Brauchwasser 8.9.2011
     case 'Z':                     // Strom Zaehlerstand 8.9.2011
      p = berechne_PZ( s, 8 ) + 0x30 ;   // Pruefzeichen
      if ( p == s[8] )             // check transfer
      {
         a_basis->uart_ok++;
         addr = compute_addr( 0 , s_clock.mon, s_clock.year );

         if ( c == 'X' )
         {
            z = 0;                 // offset
            rest = 0;              // offset zu flag
            utemp = 10;            // korrektur faktor
         }
         else if ( c == 'Y' )
         {
            z = 4;                 // offset
            rest=1;                // offset zu flag
            utemp = 10;            // korrektur faktor
         }
         else if ( c == 'Z' )
         {
            z = 8;                 // offset
            rest= 2;               // offset zu flag
            utemp = 1;             // korrektur faktor
         }

         lx = a_to_i( s, 8 );      // long
         lx *= utemp;              // korrektur auf 3 stellen nach dem komma

         ly = read_counter( addr + z );      // read last value
         save_counter( addr + z, lx );

         if ( (ly + 5000) < lx )  // neue Ablesung ???
         {
            unsigned char last_monat =  s_clock.mon;
            unsigned char last_jahr  =  s_clock.year;
            last_monat -= 1;
            if ( last_monat == 0 )
            {
               last_monat = 12;
               last_jahr -= 1;
            }
            oldaddr = compute_addr( 0 , last_monat, last_jahr );
            save_counter( oldaddr + z, lx );   // neuer wert = ende letzter Monat
         }

                                   // Wenn neuer Wert kleiner als der alte
                                   // und neuer Wert kleiner als 3,000
         if ( lx < ly && lx < 3000 )  // dann Zählerwechsel ??????
         {
            p = counter[ 512] ;    // ja, freie Patch area holen
            c = (p+1) & 0x0F;
            if ( c == 0 )
               c =1;
            counter[ 512] =  c ;   // update patch area pointer

            c = p;                 // patchnummer sichern
            c <<= 4;
            c += s_clock.mon;                // mon von 1-12
            counter[ addr+rest+12] =  c ;
            counter[ addr+15] =  s_clock.year ;

            addr = p & 0x0F;                 // patch adresse ermitteln
            addr *= 8;                       // jeweils 8 Byte
            addr += 520 ;                    // beginn der area
            save_counter( addr, ly );        // Ende Wert sichern
            save_counter( addr+4, lx );      // Start Wert sichern
         }
         else
         {
            c = counter[addr+15];             // lese Jahres Merker
            p = counter[ addr+rest+12 ];      // patch flag lesen

            if ( (c+1) < s_clock.year )       // 2 Jahre alter Eintrag ??
               p = 0;
            else
               p &= 0xF0;
            p += s_clock.mon;                 // mon von 1-12
            counter[ addr+rest+12] =  p ;
            counter[ addr+15] = s_clock.year ;
         }
      }
      else
      {
         a_basis->uart_error++;
      }
      break;

     default:
      a_basis->uart_error++;
      break;
   }
}



/**************************************************************
** save 4 Byte int
*/
void save_counter( int addr, int l )
{
   unsigned char x;

   x = (unsigned char)(l >> 24) & 0xFF;
   counter[ addr++ ] =  x;
   x = (unsigned char)(l >> 16) & 0xFF;
   counter[ addr++ ] =  x;
   x = (unsigned char)(l >> 8) & 0xFF;
   counter[ addr++ ] =  x;
   x = (unsigned char)(l & 0xFF);
   counter[ addr ] =  x;
}



/**************************************************************
** read 4 Byte int
*/
int read_counter( int addr )
{
   int l;
   l =  counter[ addr++ ];
   l <<= 8;
   l += counter[ addr++ ];
   l <<= 8;
   l += counter[ addr++ ];
   l <<= 8;
   l += counter[ addr ];
   return l;
}



/**************************************************************
** SRAM Adresse für den counter berechnen
** .... .... dmmm mmcc
**           ||     |- Counter 0-3
**           ||------- Monate minus ( maximal 23, deswegen 5 Bits )
**           |-------- Differenz Bilden
*/
unsigned int compute_addr( int varnum , unsigned char mon,  unsigned char year )
{
   unsigned int adr, r;

   r = varnum & 0x007C;              // Monat Filter
   r >>= 2;                          // anzahl monate zurück
   adr = mon;                        // aktueller Monat
   if ( r >= adr )
   {
      adr +=
      12;
      year -= 1;
   }
   if ( r >= adr )
   {
      adr += 12;
      year -= 1;
   }
   adr -= r;
   adr -= 1;
   adr *= 16;
   if ( year & 0x01 )
      adr += 0x100;
   else
      adr += 0;
   r = (varnum & 0x0003);
   r *= 4;                            // counter liegen je 4 Byte auseinander
   adr += r;
   return adr;
}



/*****************************************************************************
** adresse regen tag
*/
int get_day_regen_addr( unsigned char jj, unsigned char mm, unsigned char day )
{
   unsigned char i;
   int addr = 0, jahr;

   if ( jj % 4 == 0 )       // Schaltjahr ??
      motab[1] = 29;
   else
      motab[1] = 28;

   for ( i=0; i < (mm -1); i++)
      addr += motab[i];

   addr += day;
   addr *= 2;

   jahr = jj & 0x03 ;       // speicher Adresse Start ermitteln
   jahr *= 0x0400;
   addr += jahr;
   return addr;             // byte Adresse !!
}



/*****************************************************************************
** logfile mit append
*/
void write_logfile( char *message )
{
   FILE *logfile;
   
   logfile = fopen( LOG_FILE, "a" );
   if( !logfile )
      return;
   fprintf( logfile, "%s\n", message);
   fclose(logfile);
}



/*****************************************************************************
** close
*/
void close_all(void)
{
   if ( fd_serial != -1 )             // serial io close
      close( fd_serial );

   update_file_sw = WRITE_VARXX+WRITE_COUNTER+WRITE_REGEN+WRITE_TEMP;
   do
   {
      update_files(); // alles sichern
   } while( update_file_sw );
}



/*****************************************************************************
** Signal handler
*/
void sig_handler(int signo)
{
   time_t xt;
   char *ct;

   xt = time(NULL);
   ct = ctime( &xt );                       // Zeit aufbereiten

   if (signo == SIGUSR1)
   {
      sprintf( logline, "received SIGUSR1 at %s",ct);
      write_logfile( logline );
   }
   else if (signo == SIGTSTP)
   {
      sprintf( logline, "received SIGTSTP at %s",ct);
      write_logfile( logline );
      loop = 0;
   }
   else if (signo == SIGTERM)
   {
      sprintf( logline, "received SIGTERM at %s",ct);
      write_logfile( logline );
      loop = 0;
   }
}



/*****************************************************************************
**  ascii to int
*/
int a_to_i( char *quelle, int anzahl)
{
   int x=0;
   unsigned char n;

   for ( n=0; n < anzahl; n++)
   {
      x *= 10;
      x += *quelle & 0x0F;
      quelle++;
   }
   return x;
}



/*****************************************************************************
** Berechnung der Pruefzahl
** 11.09.2009  Einfacherer Berechnung mit dem Gewicht 2 3 4 ...
** 16.09.2009  Verbesserung wenn viele 0 Zeichen
**
** Code        : 2  9  1  2  0  9  1
** n+1           3 10  2  3  1 10  2
** Gewichtung  : 2  3  4  5  6  7  8
** Produkte    : 6 30  8 15  6 70 16
** Summe       : 151
** Modulo 64   : 151 / 64 = 2 Rest 23
** Pruefzahl   : Differenz zu 64 = 64 - 23 = 41
** Pruefzeichen= pz + 0x30 ergibt ASCII Code 0x30 bis 0x6F  0x29 +0x30 = 0x59  = ';'
*/
int berechne_PZ( char *s, unsigned char len )
{
   unsigned char i,n;
   unsigned char w = 2;          // start mit 2
   int summe = 0;

   for ( i=0; i < len; i++ )
   {
      n = *s++ & 0x0F ;          // ASCII zahlen 0-9
      summe += (n+1) * w ;       // 16.09.2009 n+1
      w++;                       // 2 3 4 usw.
   }
   return( 64 - (summe % 64));
}



/*
**  Anpassung an Intel Byte Notation
*/
static void byteReverse ( unsigned char *buf, unsigned long longs)
{
   unsigned long t;
   unsigned char test[5] = { 0x01,0x02,0x03,0x04 };
   unsigned long *p ;

   p = (unsigned long*)test;
   t = *p & 0xff ;

   switch ( t )
   {
     case 1:         /* intel normal */
       break;

     case 4:
       p = (unsigned long*)buf;
       do
       {
          t = *p++ ;
          *buf++ = (unsigned char )((t ) & 0xFF);
          *buf++ = (unsigned char )((t >> 8 )  & 0xFF) ;
          *buf++ = (unsigned char )((t >> 16 ) & 0xFF) ;
          *buf++ = (unsigned char )((t >> 24 ) & 0xFF) ;
       } while( longs--);
       break;
   }
}



/*
** Start der MD5-Akkumulation. Setzt den Bit-Zaehler auf 0
** und initialisiert die MD5-Konstanten
*/
void MD5Init( MD5Context *ctx )
{
   ctx->buf[0] = 0x67452301;
   ctx->buf[1] = 0xefcdab89;
   ctx->buf[2] = 0x98badcfe;
   ctx->buf[3] = 0x10325476;

   ctx->bits[0] = 0;
   ctx->bits[1] = 0;
}


/*
**  Update des Kontexts
*/
void MD5Update( MD5Context *ctx, unsigned char *buf, unsigned long len)
{
   unsigned long t;

                         // Update bitcount
   t = ctx->bits[0];

   if ( (ctx->bits[0] = t + ((unsigned long) len << 3)) < t )
      ctx->bits[1]++;

   ctx->bits[1] += len >> 29;
   t = (t >> 3) & 0x3f;
   if (t)
   {
      unsigned char *p = (unsigned char *) ctx->in + t;
      t = 64 - t;
      if (len < t)
      {
         memcpy(p, buf, len);
         return;
      }
      memcpy(p, buf, t);

      byteReverse(ctx->in, 16);

      MD5Transform( ctx->buf, (unsigned long *) ctx->in);

      buf += t;
      len -= t;
   }

                         // Erstellen der 64-byte Bloecke
   while (len >= 64)
   {
      memcpy(ctx->in, buf, 64);
      byteReverse(ctx->in, 16);
      MD5Transform(ctx->buf, (unsigned long *) ctx->in);
      buf += 64;
      len -= 64;
   }

   /* Handle any remaining bytes of data. */
   memcpy(ctx->in, buf, len);
}



/* ausgabe des ergebnisses
 */
void MD5Final( unsigned char digest[16], MD5Context *ctx )
{
   unsigned count;
   unsigned char *p;

                           // Berechne die Anzahl der Bytes mod 64
   count = (ctx->bits[0] >> 3) & 0x3F;

   /*
    * Zuerst wird mit 0x80 aufgefuellt, weil immer mindestens ein Byte
    * frei ist
    */

   p = ctx->in + count;

   *p++ = 0x80;
   /* Bytes um auf 64 bytes aufzufuellen */
   count = 64 - 1 - count;
   /* Auffuellen bis auf 56 mod 64 */

   if (count < 8)
   {
       /* Fuelle den ersten Block auf 64 bytes */

       memset(p, 0, count);
       byteReverse(ctx->in, 16);
       MD5Transform(ctx->buf, (unsigned long *) ctx->in);

       /* Fuelle den naechsten Block mit 56 bytes */
       memset(ctx->in, 0, 56);
   }
   else
   {
      memset(p, 0, count - 8);
   }
   byteReverse(ctx->in, 14);

   /* haengt die Laenge Nachricht an */

   ((unsigned long *) ctx->in)[14] = ctx->bits[0];
   ((unsigned long *) ctx->in)[15] = ctx->bits[1];

   MD5Transform(ctx->buf, (unsigned long *) ctx->in);

   byteReverse((unsigned char *) ctx->buf, 4); /* ergebnis verdrehen */
   memcpy(digest, ctx->buf, 16);               /* MD5 Ergebnis       */
   memset(ctx, 0, sizeof(ctx));
}


/*
** Die vier Kernfunktionen des Algorithmus
*/
#define F1(x, y, z) ((x & y) | (~x & z))
#define F2(x, y, z) ((x & z) | (y & ~z))
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))


/*
** Der zentrale Schritt des Algorithmus
*/
#define MD5STEP(f, w, x, y, z, data, s)  ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )


/*
** Dies ist der Kern des MD5-Algorithmus je 64 Byte input data
*/
static void MD5Transform( unsigned long buf[4], unsigned long const in[16] )
{
   unsigned long a, b, c, d;

   a = buf[0];
   b = buf[1];
   c = buf[2];
   d = buf[3];

   MD5STEP(F1, a, b, c, d, in[0]  + 0xd76aa478, 7);
   MD5STEP(F1, d, a, b, c, in[1]  + 0xe8c7b756, 12);
   MD5STEP(F1, c, d, a, b, in[2]  + 0x242070db, 17);
   MD5STEP(F1, b, c, d, a, in[3]  + 0xc1bdceee, 22);
   MD5STEP(F1, a, b, c, d, in[4]  + 0xf57c0faf, 7);
   MD5STEP(F1, d, a, b, c, in[5]  + 0x4787c62a, 12);
   MD5STEP(F1, c, d, a, b, in[6]  + 0xa8304613, 17);
   MD5STEP(F1, b, c, d, a, in[7]  + 0xfd469501, 22);
   MD5STEP(F1, a, b, c, d, in[8]  + 0x698098d8, 7);
   MD5STEP(F1, d, a, b, c, in[9]  + 0x8b44f7af, 12);
   MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
   MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
   MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
   MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
   MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
   MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);

   MD5STEP(F2, a, b, c, d, in[1]  + 0xf61e2562, 5);
   MD5STEP(F2, d, a, b, c, in[6]  + 0xc040b340, 9);
   MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
   MD5STEP(F2, b, c, d, a, in[0]  + 0xe9b6c7aa, 20);
   MD5STEP(F2, a, b, c, d, in[5]  + 0xd62f105d, 5);
   MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
   MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
   MD5STEP(F2, b, c, d, a, in[4]  + 0xe7d3fbc8, 20);
   MD5STEP(F2, a, b, c, d, in[9]  + 0x21e1cde6, 5);
   MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
   MD5STEP(F2, c, d, a, b, in[3]  + 0xf4d50d87, 14);
   MD5STEP(F2, b, c, d, a, in[8]  + 0x455a14ed, 20);
   MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
   MD5STEP(F2, d, a, b, c, in[2]  + 0xfcefa3f8, 9);
   MD5STEP(F2, c, d, a, b, in[7]  + 0x676f02d9, 14);
   MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);

   MD5STEP(F3, a, b, c, d, in[5]  + 0xfffa3942, 4);
   MD5STEP(F3, d, a, b, c, in[8]  + 0x8771f681, 11);
   MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
   MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
   MD5STEP(F3, a, b, c, d, in[1]  + 0xa4beea44, 4);
   MD5STEP(F3, d, a, b, c, in[4]  + 0x4bdecfa9, 11);
   MD5STEP(F3, c, d, a, b, in[7]  + 0xf6bb4b60, 16);
   MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
   MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
   MD5STEP(F3, d, a, b, c, in[0]  + 0xeaa127fa, 11);
   MD5STEP(F3, c, d, a, b, in[3]  + 0xd4ef3085, 16);
   MD5STEP(F3, b, c, d, a, in[6]  + 0x04881d05, 23);
   MD5STEP(F3, a, b, c, d, in[9]  + 0xd9d4d039, 4);
   MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
   MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
   MD5STEP(F3, b, c, d, a, in[2]  + 0xc4ac5665, 23);

   MD5STEP(F4, a, b, c, d, in[0]  + 0xf4292244, 6);
   MD5STEP(F4, d, a, b, c, in[7]  + 0x432aff97, 10);
   MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
   MD5STEP(F4, b, c, d, a, in[5]  + 0xfc93a039, 21);
   MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
   MD5STEP(F4, d, a, b, c, in[3]  + 0x8f0ccc92, 10);
   MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
   MD5STEP(F4, b, c, d, a, in[1]  + 0x85845dd1, 21);
   MD5STEP(F4, a, b, c, d, in[8]  + 0x6fa87e4f, 6);
   MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
   MD5STEP(F4, c, d, a, b, in[6]  + 0xa3014314, 15);
   MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
   MD5STEP(F4, a, b, c, d, in[4]  + 0xf7537e82, 6);
   MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
   MD5STEP(F4, c, d, a, b, in[2]  + 0x2ad7d2bb, 15);
   MD5STEP(F4, b, c, d, a, in[9]  + 0xeb86d391, 21);

   buf[0] += a;
   buf[1] += b;
   buf[2] += c;
   buf[3] += d;
}
