Uusi kokeilu DB2 ja DBS 0.03

Kaikki oikeudet pidätetään. Olen jo jonkin aikaa ihmetellyt satunnaisbittejä ja terttu on jäänyt vähemmälle huomiolle. Jos haluat lukea valmiimmista ohjelmista, tässä fort (http://moijari.com/?p=964), tässä ressu1.7 (http://moijari.com/?p=798), ja tässä ressu1.8 (http://moijari.com/?p=842). Tässä postissa esitellään uusi yritys tertusta. Ohjelma koostuu tietokannasta (DB2) ja html-lomakeohjelmasta (DBS). db_ alkuiset rutiinit kuuluvat tietokantaan ja dbs_alkuiset rutiinit html-liittymään. Postin aluksi käydään läpi DB2 rutiinit ja sitten jatketaan DBS rutiineilla. Tässä postissa on listattuna kaikki rutiinit eli periaatteessa tästä voi copy-pastata toimivan version.

Edit: Julkaisun jälkeiset muutokset

Tietosisältö on nyt seuraavankaltaisessa tiedostossa:

;
; Asiakas
;
'app' "asiakas", 'appname' "Asiakasluettelo"
'memberid' "asiakasnumero", 'text', "Asiakasnumero" 'key'="key"
'memberid' "asiakkaan nimi", 'text', "Asiakkaan nimi", 'length'="32"
'memberid' "asiakkaan osoite", 'text', "Asiakkaan osoite", 'length'="32", 'mode' "Optional"
'memberid' "asiakkaan postitoimipaikka", 'text', "Asiakkaan postitoimipaikka", 'length'="10", 'mode' "Optional"
'memberid' "asiakkaan postiosoite", 'text', "Asiakkaan postiosoite", 'length'="32", 'mode' "Optional"
'memberid' "asiakkaan maa", 'text', "Asiakkaan maa", 'length'="32", 'mode' "Optional"
'memberid' "asiakkaan email", 'text', "Asiakkaan email", 'length'="32", 'mode' "Required"
'app' "asiakas", 'chapter' "header", 'sort' "01", 'memberid' "asiakasnumero"
'app' "asiakas", 'chapter' "header", 'sort' "02", 'memberid' "asiakkaan nimi"
'app' "asiakas", 'chapter' "header", 'sort' "03", 'memberid' "asiakkaan osoite"
'app' "asiakas", 'chapter' "header", 'sort' "04", 'memberid' "asiakkaan postitoimipaikka"
'app' "asiakas", 'chapter' "header", 'sort' "05", 'memberid' "asiakkaan postiosoite"
'app' "asiakas", 'chapter' "header", 'sort' "06", 'memberid' "asiakkaan maa"
'app' "asiakas", 'chapter' "header", 'sort' "07", 'memberid' "asiakkaan email"
'asiakasnumero' "1000", 'asiakkaan nimi' "Firma 1", 'asiakkaan osoite' "Venekatu 1", 'asiakkaan postitoimipaikka' "00500", 'asiakkaan postiosoite' "111111", 'asiakkaan maa' "Finland", 'asiakkaan email' "jarik@example.com"
;
; Tuote
;
'app' "tuote", 'appname' "Tuoteluettelo"
'memberid' "tuotenumero" 'key'="key"
'memberid' "tuotteen nimi", 'text', "Tuotteen nimi", 'length'="32"
'app' "tuote", 'chapter' "header", 'sort' "01", 'memberid' "tuotenumero"
'app' "tuote", 'chapter' "header", 'sort' "02", 'memberid' "tuotteen nimi"
'app' "tuote", 'chapter' "header", 'sort' "03", 'memberid' "tuotteen hinta"
'tuotenumero' "1000", 'tuotteen nimi' "Tappi", 'tuotteen hinta' "5,00"
;
; Tilaus
;
'app' "tilaus", 'appname' "tilaus"
'app' "tilaus", 'fromapp' "asiakas", 'toapp' "tilaus"
'app' "tilaus", 'fromapp' "tuote", 'toapp' "tilaus"
'memberid' "tilausnumero" 'key'="key"
'memberid' "tilauksen asiakkaan nimi", 'text', "Tilauksen asiakkaan nimi", 'length'="32"
'memberid' "tilauksen rivinumero" 'key'="key"
'memberid' "tilauksen tuotteen nimi", 'text', "Tilauksen tuotteen nimi", 'length'="32"
'app' "tilaus", 'chapter' "header", 'sort' "01", 'memberid' "tilausnumero"
'app' "tilaus", 'chapter' "header", 'sort' "02", 'memberid' "tilauksen asiakasnumero"
'app' "tilaus", 'chapter' "header", 'sort' "03", 'memberid' "tilauksen asiakkaan nimi"
'app' "tilaus", 'chapter' "lines", 'sort' "04", 'memberid' "tilausnumero"
'app' "tilaus", 'chapter' "lines", 'sort' "05", 'memberid' "tilauksen rivinumero"
'app' "tilaus", 'chapter' "lines", 'sort' "06", 'memberid' "tilauksen tuotenumero"
'app' "tilaus", 'chapter' "lines", 'sort' "07", 'memberid' "tilauksen tuotteen nimi"
'app' "tilaus", 'chapter' "lines", 'sort' "08", 'memberid' "tilauksen tuotteen hinta"
'app' "tilaus", 'chapter' "lines", 'sort' "09", 'memberid' "tilattu määrä"
'tilausnumero' "1000", 'tilauksen asiakasnumero' "1000", 'tilauksen asiakkaan nimi' = "Firma 1"
'tilausnumero' "1000", 'tilauksen rivinumero' "10", 'tilauksen tuotenumero' = "3000", 'tilauksen tuotteen nimi' = "Tappi", 'tilauksen tuotteen hinta' = "5,00", 'tilattu määrä' = "1"
'app' "tilausrivit", 'appname' "Tilausrivit"
'app' "tilausrivit", 'chapter' "header", 'sort' "01", 'memberid' "tilausnumero"
'app' "tilausrivit", 'chapter' "header", 'sort' "02", 'memberid' "tilauksen asiakasnumero"
'app' "tilausrivit", 'chapter' "header", 'sort' "03", 'memberid' "tilauksen asiakkaan nimi"
'app' "tilausrivit", 'chapter' "header", 'sort' "04", 'memberid' "tilauksen rivinumero"
'app' "tilausrivit", 'chapter' "header", 'sort' "05", 'memberid' "tilauksen tuotenumero"
'app' "tilausrivit", 'chapter' "header", 'sort' "06", 'memberid' "tilauksen tuotteen nimi"
'app' "tilausrivit", 'chapter' "header", 'sort' "07", 'memberid' "tilauksen tuotteen hinta"
'app' "tilausrivit", 'chapter' "header", 'sort' "08", 'memberid' "tilattu määrä"

Sisäinen rakenne on seuraavanlainen: Ylimpänä on lista kyselyitä, joiden sisälle rakentuu kyselyn vastaus.

struct data {
  unsigned char *data;
  char changes;
  struct data *next_data;
};

struct query {
  unsigned char *query;
  unsigned char *keys;
  unsigned long count;
  struct data *first_data;
  struct query *next_query;
};

Db2:sen muuttujia:

struct query *first_query=NULL;

#define MAXNAME 33
#define MAXVALUE 1024

Query rakenteen query osoite osoittaa kyselyyn ja data rakenteen data osoite osoittaa vastausriviin.

DB2 puolen pääfunktiot:

void db_open(char *filename) — tällä avataan tietokanta

void db_query(char *query) — Tällä määritellään kysely ja haetaan vastaus

struct data *db_query_get_first(struct query *q) — Tällä saadaan kyselyn ensimmäinen vastausrivi.

struct data *db_data_get_next(struct data *d) — Tällä saadaan kyselyn seuraava vastausrivi

int db_data_get_field(struct data *d, unsigned char *name2, int value2len, unsigned char *value2) — luetaan kenttä tietueesta

void db_save(struct data *d) — Talletetaan tietue

Db2 funktiot:

static void db_skipwhite(unsigned char **p2);
static unsigned char *save_string(unsigned char *string);
static struct data *db_create_data(unsigned char *data);
static struct query *db_create_query(unsigned char *query);
static struct query *db_fetch_query(char *query);
void dump_queries();
static struct data *db_add_data(struct data **data, unsigned char *buf);
static struct data *db_query_add_data(struct query *q,unsigned char *buf);
static int db_parsename(int namelen, unsigned char *name, unsigned char **p2);
static int db_parsevalue(int valuelen, unsigned char *value, unsigned char **p2);
static int db_parse_nameandvalue(int namelen, unsigned char *name, int valuelen, unsigned char *value, unsigned char **p2);
static int db_compare(unsigned char *query, unsigned char *string);
static void db_get_elements(int lenstring2, unsigned char *string2, unsigned char *query, unsigned char *string);
struct query *db_query(char *query);
struct data *db_query_get_first(struct query *q);
struct data *db_data_get_next(struct data *d);
int db_data_get_field(struct data *d, unsigned char *name2, int value2len, unsigned char *value2);
static void db_parse_header(int headersize,unsigned char *header, unsigned char *string);
void db_open(char *filename);

Seuraava kappale on fort:in satunnaisuuden keräämistä varten:

#define DB2_EVENTS 2
static unsigned int db2_events = 1;
static unsigned int db2_event_mode = 6;

#ifdef DB2_EVENTS
#define DB2_EVENTS_START(source) \
  IUTIME micros; \
  static int \
    pool=0, pool2=0; \
  if(db2_events) { \
    fort_add_random_event_time(&pool, \
    source, db2_event_mode); \
    fort_add_random_event_timer_start(&micros); \
  }
#else
#define DB2_EVENTS_START(source)
#endif

#ifdef DB2_EVENTS
#define DB2_EVENTS_END(source) \
  if(db2_events) \
    fort_add_random_event_timer_do(&pool2, \
        source, db2_event_mode, &micros);
#else
#define DB2_EVENTS_END(source)
#endif

Tämä rutiini tulostaa muistissa olevat kyselyt:

void dump_queries()
{
  struct query *q=first_query;
  struct data *d;

  while(q!=NULL) {
    fprintf(stdout,"Header: (addr:%p",q);
    fprintf(stdout,", text=\"%s\"",q->query);
    fprintf(stdout,", keys=\"%s\"",q->keys);
    fprintf(stdout,", count=%ld",q->count);
    fprintf(stdout,", first_data=%p",q->first_data);
    fprintf(stdout,", next=%p",q->next_query);
    fprintf(stdout,")\n");
    fflush(stdout);

    d=q->first_data;
    while(d!=NULL) {
      fprintf(stdout,"Data:   (addr:%p",d);
      fprintf(stdout,", data=\"%s\"",d->data);
      fprintf(stdout,", next_data=%p",d->next_data);
      fprintf(stdout,")\n");
      fflush(stdout);
      d=d->next_data;
    }
    q=q->next_query;
  }
}
Rutiini tallettaa merkkijonon ja palauttaa osoitteen siihen:
static unsigned char *save_string(unsigned char *string)
{
  unsigned char *temp=malloc(strlen(string)+1);
  strcpy(temp, string);
  return(temp);
}

Tällä allokoidaan tila yhdelle data lohkolle:

static struct data *db_create_data(unsigned char *data)
{
  struct data *temp;

  if((temp = malloc(sizeof(struct data))) != NULL) {
    temp->data = save_string(data);
    temp->changes = 0;
    temp->next_data = NULL;
    return(temp);
  }
  return(NULL);
}

Tällä taas saadaan tila yhdelle query-rakenteelle:

static struct query *db_create_query(unsigned char *query)
{
  struct query *temp;

  if((temp = malloc(sizeof(struct query))) != NULL) {
    temp->query = save_string(query);
    temp->keys = NULL;
    temp->count = 0;
    temp->first_data = NULL;
    temp->next_query = NULL;
  }
  return(temp);
}

Seuraava rutiini hakee halutun kyselyn ja palauttaa osoitteen siihen: aluksi while(*ppquery) luuppi etsii kyselyä, joka on samanlainen parametrinä annetun kyselyn kanssa. Jos sellainen löytyy, se poistetaan query ketjusta ja lisätään takaisin ketjun alkuun (if found). Näin useimmin käytetyt kyselyt ovat ketjussa ensimmäisinä. Jos taas ketjua ei löydyy, kysely ketjun alkuun lisätään uusi kysely (else). Rutiini palauttaa osoitteen query-rakenteeseen.

static struct query *db_fetch_query(char *query)
{
  int found;
  struct query **ppquery, *thisq;

  ppquery = &first_query;
  found = 0;
  while(*ppquery != NULL) {
    if(!strcmp(query, (*ppquery)->query)) {
      found = 1;
      break;
    }
    ppquery = &((*ppquery)->next_query);
  }
  if(found) {
    // remove from list                                                                                                                                  
    thisq = *ppquery;
    *ppquery = thisq->next_query;
    // add back to 1st of list                                                                                                                           
    thisq->next_query = first_query;
    first_query = thisq;
  } else {
    // create new                                                                                                                                        
    thisq = db_create_query(query);
    thisq->next_query = first_query;
    first_query = thisq;
  }

  return(thisq);
}

Seuraava rutiini lisää yhden kyselyn vastaustietueen data ketjuun. Jos tietue on jo ketjussa, sitä ei lisätä, ja jos tietuetta ei ole tietue luodaan (if (!found)). Ketjua ylläpidetään siten että data ketju on aakkosjärjestyksessä.

static struct data *db_add_data(struct data **data, unsigned char *buf) {
  int found;
  struct data **pdata = data;
  struct data *pnew;

  DB2_EVENTS_START(202)

  found=0;
  while(*pdata!=NULL) {
    if(!strcmp(buf,(*pdata)->data)) {
      found = 1;
      break;
    }
    if(strcmp(buf,(*pdata)->data)<0) {
      break;
    }
    pdata=&((*pdata)->next_data);
  }
  if(!found) {
    pnew = db_create_data(buf);
    pnew -> next_data = *pdata;
    *pdata = pnew;
  }

  DB2_EVENTS_END(203)

  return(pnew);
}

static struct data *db_query_add_data(struct query *q,unsigned char *buf)
{
  if(db_add_data(&q->first_data,buf) != NULL)
    q->count++;
}

Sitten funktio, jolla ohitetaan merkkijonosta välilyönnit ja tabit:

static void db_skipwhite(unsigned char **p2)
{
  unsigned char *p;

  p=*p2;
  while(*p==' ' || *p=='\t')
    p++;
  *p2=p;
}

Tällä luetaan nimi merkkijonosta. Nimi koostuu heittomerkeistä ja niiden välissä olevista merkeistä. Merkkejä ei aakkostarkasteta, joten nimessä voi olla mitä tahansa merkistöä tahansa. Myöskään nimiä (ja arvoja) ei vielä unicode tarkasteta, pitkissä nimissä (tai arvoissa) ei ole merkkikatkoa. Unicodessa:han yksi merkki muodostuu useammista fyysisistä merkeistä, ja jos kirjainmerkki katkaistaan kesken fyysisten merkkien voi tulla ongelmia.

Näissä funktioissa paikka merkkijonossa kerrotaan p2 osoitteella. Sen sisältö osoittaa seuraavaan parsittavaan merkkiin.

static int db_parsename(int namelen, unsigned char *name, unsigned char **p2)
{
  int stat, ncount;
  unsigned char *p, *n;

  p=*p2;
  n=name;
  ncount=0;
  stat=0;

  db_skipwhite(&p);
  if(*p == '\'') { // name with '                                                                                                                          
    p++;
    while(*p != '\'' && *p != '\0') {
      if(++ncount < namelen)
        *n++ = *p;
      p++;
      stat = 1;
    }
    if(*p == '\'')
      p++;
  }
  *n = '\0';
  *p2 = p;

  return(stat);
}

Arvon lukuun tarkoitettu rutiini: kuten kenttien nimissä tämäkin voi koostua mistä tahansa merkistöistä. Arvo on aina tuplahipsujen (“) välissä. P2 muuttuja osoittaa merkkiin jota käsitellään ja sinne päivitetään rutiinin lopussa seuraava merkki.

static int db_parsevalue(int valuelen, unsigned char *value, unsigned char **p2)
{
  int stat, vcount;
  unsigned char *p, *v;

  p = *p2;
  v = value;
  vcount = 0;
  stat = 0;

  db_skipwhite(&p);
  if(*p == '"') { // value with " -- skip                                                                                                                  
    p++;
    while(*p != '\"' && *p != '\0') {
      if(++vcount < valuelen)
         *v++ = *p;
      p++;
      stat = 2;
    }
    if(*p == '\"')
      p++;
  }
  *v = '\0';
  *p2 = p;

  return(stat);
}

Tällä parsitaan nimi arvo pari. Tässä nimen ja arvon välissä voi olla ‘=’.

static int db_parse_nameandvalue(int namelen, unsigned char *name, int valuelen, unsigned char *value, unsigned char **p2)
{
  int stat;
  unsigned char *p,*n,*v;

  DB2_EVENTS_START(204)

  p = *p2;
  n = name;
  *n = '\0';
  v = value;
  *v = '\0';
  stat = 0;

  db_skipwhite(&p);
  stat=db_parsename(namelen,name,&p);
  db_skipwhite(&p);
  if(*p == '=') {
    p++;
  }
  db_skipwhite(&p);
  if(*p == '\"') {
    stat=db_parsevalue(valuelen,value,&p);
  }
  if(*p == ',') {
    p++;
  }
  *p2 = p;

  DB2_EVENTS_END(205)

  return(stat);
}

Tällä verrataan tietuetta kyselyyn ja palautetaan 1 jos tietue mätsää 0 jos ei.

static int db_compare_all(unsigned char *query, unsigned char *string)
{
  int stat,stat2,ok,ok2;
  unsigned char *q, *s;
  unsigned char name[MAXNAME], value[MAXVALUE],
      name2[MAXNAME], value2[MAXVALUE];

  DB2_EVENTS_START(206)

  ok = 1;
  q = query;
  db_skipwhite(&q);
  while((stat = db_parse_nameandvalue(sizeof(name), name,
      sizeof(value), value, &q))>0) {
    ok2 = 0;
    s = string;
    db_skipwhite(&s);
    while((stat2 = db_parse_nameandvalue( sizeof(name2), name2,
        sizeof(value2), value2, &s))>0) {
      ok2=0;
      if(!strcmp(name, name2)) {
        if(stat == 2 && stat2 == 2) {
          if(!strcmp(value, value2))
            ok2 = 1;
        } else if(stat==1 && stat2==2) {
          ok2 = 1;
        }
      }
      if(ok2)
        break;
    }
    if(!ok2)
      ok = 0;
  }

  DB2_EVENTS_END(207)

  return(ok);
}

Tämä poimii kyselyn sisältämät elementit merkkijonosta:

static void db_get_elements(int lenstring2, unsigned char *string2, unsigned char *query, unsigned char *string)
{
  int first,stat,stat2;
  unsigned char name[MAXNAME], value[MAXVALUE], name2[MAXNAME], value2[MAXVALUE];
  unsigned char *q,*s;

  *string2='\0';
  first=1;
  q=query;
  db_skipwhite(&q);
  while((stat = db_parse_nameandvalue(sizeof(name), name,
      sizeof(value), value,&q))>0) {
    s = string;
    db_skipwhite(&s);
    while((stat2 = db_parse_nameandvalue(sizeof(name2), name2,
        sizeof(value2), value2, &s))>0) {
      if(!strcmp(name, name2)) {
        if((strlen(string2) + strlen(name2) +
            strlen(value2) +5)<lenstring2) {
          if(!first)
            strcat(string2,", ");
          strcat(string2,"\'");
          strcat(string2,name2);
          strcat(string2,"\'");
          strcat(string2," ");
          strcat(string2,"\"");
          strcat(string2,value2);
          strcat(string2,"\"");
          first=0;
        }
      }
    }
  }
}

Tällä tarkistetaan sisältääkö merkkijono halutun elementin. Tätä käytetään kun päätellään tietueen avaimet.

static int db_contains_element(char *name2, char *string)
{
  unsigned char *s;
  unsigned char name[MAXNAME], value[MAXVALUE];

  s = string;
  db_skipwhite(&s);
  while(db_parse_nameandvalue(sizeof(name), name,
      sizeof(value), value, &s)>0) {
    if(!strcmp(name, name2))
      return(1);
  }
  return(0);
}

Tämä rutiini suorittaa kyselyn. Tällä hetkellä se on vain simppeli tiedoston luku, siihen on joukko muutoksia jonossa. (muut kyselyt pohjana, vierasavainten päässä olevien kenttien “join”, suurista tiedostoista vain osa muistissa, jne.)

static struct query *db_query2(char *query)
{
  struct query *q;
  unsigned char
      string[8192],
      string2[8192],
      header[8192];

  FILE *fp1;

  DB2_EVENTS_START(208)

  q = db_fetch_query(query);

  if((fp1 = fopen(dbfilename, "r")) != NULL) {
    while(fgets(string, sizeof(string), fp1) != NULL) {
      string[strlen(string)-1] = '\0';
      if(string[0] != ';') {
        if(db_compare_all(query, string)) {
          db_get_elements(sizeof(string2), string2,
              query, string);
          db_query_add_data(q, string2);
        }
      }
    }
    fclose(fp1);
  }

  DB2_EVENTS_END(209)

  return(q);
}

Tämä funktio palauttaa tietueesta jonkin tietueen kentän arvon:

int db_data_get_field(struct data *d, unsigned char *name2, int value2len, unsigned char *value2)
{
  int stat;
  unsigned char name[MAXNAME], value[MAXVALUE];
  unsigned char *p;

  p = d->data;
  db_skipwhite(&p);
  while((stat = db_parse_nameandvalue(sizeof(name), name,
      sizeof(value), value, &p))>0) {
    if(!strncmp(name, name2,
        strlen(name2))) {
      strncpy(value2, value, value2len);
      if(value2len <= strlen(value))
        value2[value2len-1] = '\0';
      return(1);
    }
  }
  return(0);
}

Seuraava lisää kyselyyn listan kyselyn avaimista. Tätä tarvitaan esimerkiksi save-toiminnossa.

void db_update_keys(struct query *q)
{
  int found = 0;
  unsigned char keys[1024];
  struct query *q2;
  struct data *d2;

  keys[0] = '\0';
  q2=db_query2("'memberid', 'key' \"key\"");
  d2=db_query_get_first(q2);
  while(d2 != NULL) {
    int first = 1;
    unsigned char memberid[MAXNAME];

    if(db_data_get_field(d2, "memberid", sizeof(memberid),
        memberid)) {
      if(db_contains_element(memberid, q->query)) {
        if(!first)
          strcat(keys, ", ");
        strcat(keys, "'");
        strcat(keys, memberid);
	strcat(keys, "'");
        found = 1;
        first = 0;
      }
    }
    d2 = db_data_get_next(d2);
  }
  if(found)
    q->keys = save_string(keys);
  else
    q->keys = save_string("none");
}

Tätä rutiinia kutsutaan kyselyssä, ja se vain kutsuu kysely kakkosta. Tämä se vuoksi että kyselyn tulkinnassa voidaan tehdä lisäkyselyjä.

struct query *db_query(char *query)
{
  struct query *q,*q2;
  struct data *d2;

  q = db_query2(query);

  if(q->keys == NULL) {
    db_update_keys(q);
  }

  return(q);
}

Seuraavalla saadaan kyselyn palauttamasta query-tietueesta ensimmäinen kyselyn vastausrivi:

struct data *db_query_get_first(struct query *q)
{
  return(q->first_data);
}

Seuraavalla saadaan kyselyn data-tietueesta seuraava data tietue:

struct data *db_data_get_next(struct data *d)
{
  return(d->next_data);
}

Funktio palauttaa tietueesta tietueen kenttien nimet, eli kyselyn, jonka vastaus tietue on.

static void db_parse_header(int headersize,unsigned char *header, unsigned char *string)
{
  int first;
  unsigned char *p, name[MAXNAME], value[MAXVALUE];

  *header = '\0';
  first = 1;
  p = string;
  db_skipwhite(&p);
  while(db_parse_nameandvalue(sizeof(name), name,
      sizeof(value), value, &p)>0) {
    if((strlen(header) + strlen(name) + 2) < headersize) {
      if(!first)
        strcat(header, ", ");
      strcat(header, "'");
      strcat(header, name);
      strcat(header, "'");
      first = 0;
    }
  }
}

Seuraavalla funktiolla avataan tietokanta:

void db_open(char *filename)
{
  int stringlen = 8192, headerlen = 8192;
  unsigned char *string, *header;
  unsigned char buffer[1024];
  FILE *fp1;

  string = malloc(stringlen);
  header = malloc(headerlen);

  strcpy(dbfilename, filename);

  if((fp1 = fopen(dbfilename, "r")) != NULL) {
    while(fgets(string, stringlen, fp1) != NULL) {
      while(string[strlen(string)-1] != '\n') {
        string = realloc(string, stringlen*2);
        fgets(string+stringlen-1, stringlen+1, fp1);
        stringlen *= 2;
      }
      string[strlen(string)-1] = '\0';
      db_parse_header(sizeof(header), header, string);
      db_query(header);
    }
    fclose(fp1);
  }

  free(string);
  free(header);

  fprintf(stdout,"\n");
}

Seuraavaksi siirrytään DBS alkuisiin rutiineihin. Aluksi muutamia muuttujia:

char *procname;
char *programname = "DBS version 0.03";

char htmlin[32768],htmlin2[32768],html1[32768], html2[32768], *html;

int usehttps=1;

#define HTMLTIMEFORMAT "%a, %d %b %Y %H:%M:%S GMT"

unsigned char htmlmethod[10];
unsigned char htmlpath[128];
unsigned char htmlversion[32];
unsigned char htmlfilename[64];
unsigned char htmlsessionid[33];
unsigned char htmluserid[24];
unsigned char htmlpassword[24];
unsigned char *htmlparams=NULL;
unsigned char htmldigest[HashLen*2+1];
unsigned char htmlmode[10];
unsigned char htmlip[32];
unsigned char htmlport[10];
#define TIMEFORMAT "%Z%Y%m%d%H%M%S"

Näillä ensimmäisillä rutiineilla muodostetaan käyttäjälle istuntoavain. Satunnaisbittien tekemiseen käytetään tässä sekä fort:ia ja ressua. Jos haluat kokeilla sovellusta käytännössä, mukaan on lisätty kevytversio ressusta, näin fort:ia ja ressua (+sha256) ei tarvitse kopioida.

Session id annetaan istunnolle kun käyttäjä syöttää käyttäjätunnuksen ja salasanan onnistuneesti (dbs_logon). Session id:stä tehdään kookie selaimelle.

Ensimmäinen merkki session id:ssä on pieni tai iso kirjain ja loput merkit ovat numeroita, pieniä tai isoja kirjaimia. Pieniä ja isoja kirjaimia on 26+26 on 52 ja siihen numerot, erilaisia merkkejä on 62, eli yksi merkki on vajaat 6 bittiä. Erilaisia istunto id:itä on 52*62^31=1.90e57.

#define aFORT 2
#define aRESSU 2

unsigned char dbs_clockbyte() /* JariK 2013 */
{
  struct timeval tv;

  gettimeofday(&tv,NULL);

  return(tv.tv_usec & 0xff);
}

void dbs_ressu_genbytes_fast(int size, unsigned char *buffer) /* JariK 09/2020 */
{
  int c, d, e, f, byte;

  f=0;

  for(c=0; c<8 || c%8!=0 || c<16 ; c++) {
    for(d=0; d<size; d++) {
      e = buffer[d];
      e = ((e&0x80)>>7) | ((e&0x7f)<<1);
      byte = dbs_clockbyte();
      buffer[d] = e^byte;
    }
    for(d=0; d<size; d++) {
      f = (f+buffer[d])%size;
      e = buffer[d];
      buffer[d] = buffer[f];
      buffer[f] = e;
    }
  }
}

#define RANDOMCNT 256
#define aDBS_RANDOM_CLEAR 2

static unsigned char dbs_random_bytes[RANDOMCNT];
static int dbs_random_byte = 999999999;
static int dbs_random_cnt = RANDOMCNT;

int dbs_random_genbyte()
{
  if(dbs_random_byte>=dbs_random_cnt) {
#ifdef DBS_RANDOM_CLEAR
    memset(dbs_random_bytes, 0, dbs_random_cnt);
#else
    if(dbs_random_byte == 999999999)
      memset(dbs_random_bytes, 0, dbs_random_cnt);
#endif
#ifdef FORT
    fort_random_data(dbs_random_cnt, dbs_random_bytes);
#endif
#ifdef RESSU
    ressu_genbytes(dbs_random_cnt, dbs_random_bytes);
#endif
    dbs_ressu_genbytes_fast(dbs_random_cnt, dbs_random_bytes);
    dbs_random_byte = 0;
  }
  return(dbs_random_bytes[dbs_random_byte++]);
}

int dbs_random_genbyte_limit(int limit)
{
  int c;

  while((c = dbs_random_genbyte()) >=
      (256/limit)*limit);
  /* while((c = fort_random_data_byte())>                                                                                                                                                                                                                                                                                                                                      
      (256/limit)*limit); little bug */
  return(c % limit);
}

void dbs_random_clear()
{
  fort_clear();
  ressu_clear();
  memset(dbs_random_bytes, 0, dbs_random_cnt);
  dbs_random_byte = 999999998;
}

void dbs_random_genbuffer(int size, unsigned char *buffer)
{
  int c;

  for(c = 0;c < size;c++)
    buffer[c] ^= dbs_random_genbyte();
}

void dbs_gensessionid(int size, unsigned char *buffer)
{
  int len,byte,first;
  unsigned char chars[] =
    "0123456789" \
    "abcdefghijklmnopqrstuvwxyz" \
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  unsigned char *n;


  dbs_random_clear();
  n=buffer;
  len=0;
  first=1;
  while(++len<size) {
    if(first)
      byte=dbs_random_genbyte_limit(sizeof(chars)-11)+10;
    else
      byte=dbs_random_genbyte_limit(sizeof(chars)-1);
    *n++=chars[byte];
    first=0;
  }
  *n='\0';
  dbs_random_clear();
}

Tulostetaan kysely debukkausta varten:

void dbs_dump_query(struct query *q)
{
  struct data *d;

  html_printf("query: %s<br>",q->query);
  d = db_query_get_first(q);
  while(d != NULL) {
    html_printf("data: %s<br>",d->data);
    d = db_data_get_next(d);
  }
}

Seuraavalla rutiinilla lisätään ohjelman kookie selaimen muistiin: kookiena talletetaan session id ja htmlmoodi. Max-Age kertoo kuinka kauan sekunteina kookie on käyttökelpoinen, tässä 24 tuntia. Kookie päivitetään aina html lähetyksen yhteydessä, joten kookie on käyttökelpoinen 24 tuntia viimeisen html-lomakkeen vastaanotosta. Kookieta ei tarkasteta vielä alkuperäiseen kookieen.

#define DBS_RENEW_SESSION_HOURS 24

void dbs_renew_cookie()
{
  html=html1;
  html_printf("Set-Cookie: sessionid=%s; Max-Age=%d\r\n",
              htmlsessionid, 3600*DBS_RENEW_SESSION_HOURS);
  html_printf("Set-Cookie: mode=%s; Max-Age=%d\r\n",
              htmlmode, 3600*DBS_RENEW_SESSION_HOURS);
  html=html2;
}

get_seconds rutiini on jo vanha tuttu:

unsigned long dbs_getseconds()
{
  struct timeval tv;

  gettimeofday(&tv, NULL);

  return((IUTIME)tv.tv_sec);
}

Seuraava aliohjelma tulostaa printf tyyppisellä määrityksellä tehdyn merkkijonon html-puskuriin.

#include <stdarg.h>

void html_printf(const char *format, ...)
{
  int c, done;
  va_list args;
  unsigned char buffer[8192], *b, *h;

  va_start(args, format);
  vsnprintf(buffer, sizeof buffer, format, args);
  va_end(args);

  b=buffer;
  h=html + strlen(html);

  while(*b != '\0') {
    *h++ = *b++;
  }
  *h = '\0';
}

Tällä luetaan html parametri:

int dbs_parse_hex1(unsigned char **str) /* 20131003 JariK */
{
  char *p;
  int digit;

  p=*str;
  digit = 0;
  if(*p>='a' && *p<='f')
    digit=*p-'a'+10, p++;
  else if(*p>='A' && *p<='F')
    digit=*p-'A'+10, p++;
  else if(*p>='0' && *p<='9')
    digit=*p-'0', p++;
  else digit=-1;

  *str = p;
  return(digit);
}

int dbs_parse_html_string(int stringlen, unsigned char *string, unsigned char **html)
{
  int c,d,count,ok;
  unsigned char *s=string;
  unsigned char *h=*html;

  ok=0;
  count=0;

  while(isalnum(*h)||*h=='%'||*h=='+'||*h=='-'||*h=='_' || *h=='.' || *h=='*') {
    ok=1;
    if(*h=='%') {
      h++;
      if((c=dbs_parse_hex1(&h)) >= 0) {
        if((d=dbs_parse_hex1(&h)) >= 0) {
          c = c*16+d;
        } else
          c = c;
      } else
        c = 64;
      if(++count < stringlen)
        *s++ = c;
    } else if(*h=='+') {
      if(++count < stringlen)
        *s++ = ' ';
      h++;
    } else {
      if(++count < stringlen)
        *s++ = *h;
      h++;
    }
  }
  *s = '\0';
  *html = h;

  return(ok);
}

int dbs_parse_html_parameter(int namelen, unsigned char *name, int valuelen,unsigned char *value, unsigned char **s)
{
  DBS_EVENTS_START(300)

  dbs_parse_html_string(namelen, name, s);
  if((**s)=='=')
    (*s)++;
  dbs_parse_html_string(valuelen, value, s);
  if(**s=='&')
    (*s)++;

  DBS_EVENTS_END(301)
}

int dbs_get_parameter(char *name, int valuelen, unsigned char *value)
{
  int ok;
  unsigned char name2[32];
  unsigned char *h=htmlparams;

  DBS_EVENTS_START(302)

  value[0]='\0';
  ok=0;

  while(*h!='\0') {
    dbs_parse_html_parameter(sizeof(name2), name2, valuelen, value, &h);
    if(!strcmp(name,name2)) {
      ok=1;
      break;
    }
  }

  DBS_EVENTS_END(303)

  return(ok);
}

Tässä dbs:n logon näyttö:

void dbs_logon()
{
  unsigned char htmltext[128];
  unsigned char userid[32];
  unsigned char password[32];

  if(dbs_get_parameter("userid-0", sizeof(userid), userid)==1 &&
     dbs_get_parameter("password-0", sizeof(password),
         password)==1) {
    if(!strcmp(userid,"testi") && !strcmp(password,"testaus")) {
      dbs_gensessionid(sizeof(htmlsessionid), htmlsessionid);
      dbs_renew_cookie();
      fprintf(stdout,"Userid=\"%s\"", userid);
      fprintf(stdout,", password=\"%s\", password);
      fprintf(stdout,", sessionid=\"%s\"\n", htmlsessionid);

      FILE *fp1;

      if((fp1=fopen("dbssessions.deb","a"))!=NULL) {
        fprintf(fp1,"sessionid=%s",htmlsessionid);
        fprintf(fp1,", user=%s",userid);

        unsigned long seconds = dbs_getseconds();
        char timebuf[128];
        strftime(timebuf, sizeof(timebuf), TIMEFORMAT,
               localtime((time_t *)&seconds));

        fprintf(fp1,", logontime=%s",timebuf);
        fprintf(fp1,", ip=%s",htmlip);
        fprintf(fp1,"\n");
        fclose(fp1);
      }
    }
  }

  if(*htmlsessionid=='\0') {

    sprintf(htmltext,"<form action=\"logon\" method=\"POST\">");
    html_printf(htmltext);

    html_printf("<table border=\"0\">");
    html_printf("<tr>");
    html_printf("Testiohjelmaan lokkaantuminen Userid=testi,"
        "password=testaus");
    html_printf("</tr>");
    html_printf("<td>");
    html_printf("Userid");
    html_printf("</td>");
    html_printf("<td>");

    html_printf("<input type=\"char\" name=\"userid-0\" value=\"%s\">", htmluserid);
    html_printf("</td>");
    html_printf("</tr>");

    html_printf("<tr>");
    html_printf("<td>");
    html_printf("Password");
    html_printf("</td>");
    html_printf("<td>");
    html_printf("<input type=\"password\" name=\"password-0\" value=\"%s\">", htmlpassword);
    html_printf("</td>");
    html_printf("</tr>");

    html_printf("<tr>");
    //html_printf("<input type=\"hidden\" name=\"sessionid-0\" value=\"%s\">",htmlsessionid);                                                                                                                                                                                                                     
    html_printf("</tr>");

    html_printf("</table>");

    html_printf("<input type=\"submit\" value=\"Submit\">");

    html_printf("</form>");
  }
}

Luetaan html merkkijono:

int html_get_string(unsigned char *string,unsigned char **str,int len)
{
  int c,d,ok,count;
  unsigned char *p,*n;

  n=string;
  p=*str;
  ok=0;

  count=0;
  while(isalnum(*p)||*p=='%'||*p=='+'||*p=='-'||*p=='_'||*p=='=' || *p=='&' || *p=='.' || *p=='*') {
    ok=1;
    if(*p=='%') {
      p++;
      if((c=parse_hex1(&p))>=0) {
	if((d=parse_hex1(&p))>=0) {
          c=c*16+d;
        } else
          c=c;
      } else
	c=64;
      if(count++<len)
        *n++=c;
    } else if(*p=='+') {
      if(count++<len)
	*n++=' ';
      p++;
    } else {
      if(count++<len)
	*n++=*p++;
      else
        p++;
    }
  }
  *n='\0';
  *str=p;

  return(ok);
}

Seuraavassa sovelluskoodi syöttölomakkeille:

void dbs_app()
{
  int first, lineno;
  unsigned char *p;

  char htmlstring[1024];
  unsigned char htmltext[128];
  unsigned char query[128];
  unsigned char app[32];
  unsigned char appname[32];
  unsigned char chapter[32];
  unsigned char memberid[32];
  unsigned char value[128];
  struct query *qappname;
  struct data *dappname;
  struct query *qchapter;
  struct data *dchapter;
  struct query *qheadermemberid;
  struct query *qlinesmemberid;
  struct query *qmemberid;
  struct data *dmemberid;
  struct query *qheaderdata;
  struct query *qlinesdata;
  struct query *qdata;
  struct data *ddata;

  qheadermemberid = NULL; // save for dump                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
  qheaderdata = NULL;
  qlinesmemberid = NULL;
  qlinesdata = NULL;

  char func[10];
  if(dbs_get_parameter("func-0", sizeof(func), func)==1)
    fprintf(stdout,"FUNC:\"%s\"\n",func);

  if(!strcmp(func, "Display") ||
     !strcmp(func, "Change"))
    strcpy(htmlmode, func);

  dbs_renew_cookie();

  p = htmlparams;
  html_get_string(htmlstring, &p, 1024);
  fprintf(stdout,"htmlstring: \"%s\"", htmlstring);

  if(dbs_get_parameter("app-0", sizeof(app), app)==0)
    sprintf(app,"asiakas");
  else
    fprintf(stdout,"APP:\"%s\"\n", app);

  lineno = 0;

  sprintf(htmltext,"<form action=\"app\" method=\"post\">");
  html_printf(htmltext);
  // app selection
  sprintf(htmltext,"<input type=\"text\" value=\"%s\" name=\"app-%d\" size=\"10\">", app, lineno);
  html_printf(htmltext);
  // print buttons
  html_printf("<input type=\"submit\" name=\"func-%d\" value=\"Submit\">", lineno);
  html_printf("<input type=\"submit\" name=\"func-%d\" value=\"Reset\">", lineno);
  html_printf("<input type=\"submit\" name=\"func-%d\" value=\"Display\">", lineno);
  html_printf("<input type=\"submit\" name=\"func-%d\" value=\"Change\">", lineno);
  html_printf("<input type=\"submit\" name=\"func-%d\" value=\"Check\">", lineno);
  html_printf("<input type=\"submit\" name=\"func-%d\" value=\"Save\">", lineno);
  html_printf("<br>", app);

  fprintf(stdout,"htmlparams:\"%s\"\n", htmlparams);
  fprintf(stdout,"app: %s\n", app);

  // Foreign keys                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
  struct query *qfromapp;
  struct data *dfromapp;
  struct query *qfrommemberid;
  struct data *dfrommemberid;
  struct query *qtomemberid;
  struct data *dtomemberid;
  struct query *qallmemberid;
  struct data *dallmemberid;

  sprintf(query,"'app', 'fromapp', 'toapp' \"%s\"",app);
  qfromapp=db_query(query);

  dfromapp = db_query_get_first(qfromapp);
  while(dfromapp != NULL) {
    unsigned char app2[32], fromapp2[32], toapp2[32];
    unsigned char frommemberid[32], tomemberid[32],
        allmemberid[32];
    unsigned char savefrommemberid[32], savetomemberid[32];
    db_data_get_field(dfromapp,"app", sizeof(app2), app2);
    db_data_get_field(dfromapp,"fromapp",
        sizeof(fromapp2),fromapp2);
    db_data_get_field(dfromapp, "toapp",
        sizeof(toapp2),toapp2);
    fprintf(stdout,"2app=%s, fromapp=%s, toapp=%s\n", app2,
        fromapp2, toapp2);
    if(!strcmp(toapp2, app2)) {
      sprintf(query,"'app' \"%s\", 'chapter', 'memberid'",
          fromapp2);
      qfrommemberid = db_query(query);

      sprintf(query,"'app' \"%s\", 'chapter', 'memberid'",
        toapp2);
      qtomemberid = db_query(query);

      sprintf(query, "'memberid'");
      qallmemberid = db_query(query);

      dfrommemberid = db_query_get_first(qfrommemberid);
      while(dfrommemberid != NULL) {
        int longest;
        db_data_get_field(dfrommemberid, "memberid",
            sizeof(frommemberid), frommemberid);
        longest = 0;
        savetomemberid[0] = '\0';
        savefrommemberid[0] = '\0';

        dtomemberid = db_query_get_first(qtomemberid);
        while(dtomemberid != NULL) {
          db_data_get_field(dtomemberid, "memberid",
              sizeof(tomemberid), tomemberid);
          dallmemberid=db_query_get_first(qallmemberid);
          while(dallmemberid != NULL) {
            db_data_get_field(dallmemberid, "memberid",
                sizeof(allmemberid), allmemberid);
            if((strstr(frommemberid, allmemberid) != NULL) &&
               (strstr(tomemberid, allmemberid) != NULL)) {
              if(longest < strlen(allmemberid)) {
                longest = strlen(allmemberid);
                strcpy(savetomemberid, tomemberid);
                strcpy(savefrommemberid, frommemberid);
              }
            }
            dallmemberid = db_data_get_next(dallmemberid);
          }
          dtomemberid = db_data_get_next(dtomemberid);
        }
        if(longest > 0) {
          fprintf(stdout, "savefrommemberid:%s savetomemberid=%s\n", savefrommemberid, savetomemberid);
        }
        dfrommemberid = db_data_get_next(dfrommemberid);
      }
    }
    dfromapp = db_data_get_next(dfromapp);
  }
  fprintf(stdout,"query: %s\n", query);

  sprintf(query,"'app' \"%s\", 'appname'", app);
  fprintf(stdout,"query: %s\n", query);
  qappname=db_query(query);
  dappname=db_query_get_first(qappname);
  fprintf(stdout,"dappname->data=%s\n", dappname->data);
  fflush(stdout);
  if(db_data_get_field(dappname,"appname",
      sizeof(appname),appname)) {
    sprintf(htmltext, "<h1>%s</h1>", appname);
    html_printf(htmltext);
    fprintf(stdout,"app=%s", app);
    fprintf(stdout,"\n");
  }

  int formerrors = 0;

  sprintf(query,"'app' \"%s\", 'chapter'", app);
  fprintf(stdout,"query: %s\n", query);
  qchapter=db_query(query);
  dchapter=db_query_get_first(qchapter);

  while(dchapter != NULL) {
    if(db_data_get_field(dchapter, "chapter",
        sizeof(chapter),chapter)) {
      fprintf(stdout,"chapter=%s", chapter);
      fprintf(stdout,"\n");
    }
    sprintf(query,"'app' \"%s\", 'chapter' \"%s\", 'sort', 'memberid'", app, chapter);
    fprintf(stdout,"query: %s\n", query);
    qmemberid=db_query(query);
    dmemberid=db_query_get_first(qmemberid);
    // generate query for data
    *query = '\0';
    first = 1;

    while(dmemberid != NULL) {
      db_data_get_field(dmemberid,"memberid",
          sizeof(memberid), memberid);
      if(!first)
        strcat(query,", ");
      strcat(query,"'");
      strcat(query,memberid);
      strcat(query,"'");
      first=0;

      dmemberid=db_data_get_next(dmemberid);
    }

    fprintf(stdout,"query: %s\n", query);
    qdata=db_query(query);
    ddata=db_query_get_first(qdata);
    while(ddata != NULL) {
      fprintf(stdout,"data: %s\n", ddata->data);
      ddata=db_data_get_next(ddata);
    }

    if(!strcmp(chapter,"header")) {
      lineno++;

      html_printf("<table border=\"0\">");

      ddata=db_query_get_first(qdata);

      qheadermemberid = qmemberid; // save for dump                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
      qheaderdata = qdata;

      dmemberid = db_query_get_first(qmemberid);
      while(dmemberid != NULL) {
        db_data_get_field(dmemberid, "memberid",
            sizeof(memberid), memberid);
        struct query *qmemberiddata;
        struct data *dmemberiddata;
        unsigned char lengths[10];
        int length;

        // Find length of field
        length = 10;
        sprintf(query,"'memberid' \"%s\", 'length'", memberid);
        qmemberiddata=db_query(query);
        if((dmemberiddata = db_query_get_first(qmemberiddata))
            != NULL) {
          if(db_data_get_field(dmemberiddata, "length",
              sizeof(lengths), lengths)) {
            fprintf(stdout,"query: %s", query);
            fprintf(stdout,", length: %s\n", lengths);
            length=atoi(lengths);
          }
        }

        // Find mode of field
        if(!strcmp(func, "Reset"))
          value[0] = '\0';
        else {
          // get field value
          char tempmemberid[64];
          sprintf(tempmemberid,"%s-%d", memberid, lineno);
          if(dbs_get_parameter(tempmemberid, sizeof(value),
                value)==0) {
            db_data_get_field(ddata, memberid,
                sizeof(value), value);
          }
        }

        char mode[10];
        strcpy(mode, "Required");
        sprintf(query,"'memberid' \"%s\", 'mode'", memberid);
        qmemberiddata = db_query(query);
        if((dmemberiddata = db_query_get_first(qmemberiddata))
            != NULL) {
          if(db_data_get_field(dmemberiddata,"mode",
              sizeof(mode), mode)) {
            fprintf(stdout,"query: %s", query);
            fprintf(stdout,", mode: %s\n", mode);
            length=atoi(lengths);
          }
        }

        int errors = 0;

        // Do field checks
        if(!strcmp(mode, "Required") && value[0] == '\0') {
          errors++;
          formerrors++;
        }

        html_printf("<tr>");
        html_printf("<td>");
        sprintf(htmltext,"%s", memberid);
        html_printf(htmltext);
        html_printf("</td>");
        html_printf("<td>");
        // print field input
        sprintf(htmltext,"<input type=\"text\" value=\"%s\" name=\"%s-%d\" size=\"%d\"", value, memberid, lineno, length);
        if(strcmp(htmlmode, "Change"))
          strcat(htmltext, " readonly");
        // red if errors
        if(errors != 0 && !strcmp(func, "Check"))
          strcat(htmltext," style=\"border-color:red;\"");
        strcat(htmltext,"><br>");
        html_printf(htmltext);
        html_printf("</td>");
        html_printf("</tr>");
        dmemberid=db_data_get_next(dmemberid);
      }
      html_printf("</table>");

    } else if(!strcmp(chapter,"lines")) {

      html_printf("<table border=\"0\">");
      html_printf("<tr>");

      dmemberid=db_query_get_first(qmemberid);

      qlinesmemberid = qmemberid; // save for dump                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
      qlinesdata = qdata;

      while(dmemberid != NULL) {
        db_data_get_field(dmemberid, "memberid",
            sizeof(memberid), memberid);
        html_printf("<td>");
        html_printf("%s", memberid);
        html_printf("</td>");
        dmemberid=db_data_get_next(dmemberid);
      }
      html_printf("</tr>");
      ddata = db_query_get_first(qdata);

      while(ddata != NULL) {
        lineno++;

        fprintf(stdout,"%s\n", ddata->data);
        html_printf("<tr>");
        dmemberid=db_query_get_first(qmemberid);
        while(dmemberid != NULL) {
          db_data_get_field(dmemberid, "memberid",
              sizeof(memberid), memberid);
          struct query *qmemberiddata;
          struct data *dmemberiddata;
          unsigned char lengths[10];
          int length;

          length = 10;
          sprintf(query,"'memberid' \"%s\", 'length'",
              memberid);
          qmemberiddata = db_query(query);
          if((dmemberiddata = db_query_get_first(qmemberiddata))   
              != NULL) {
            if(db_data_get_field(dmemberiddata, "length",
                sizeof(lengths), lengths)) {
              fprintf(stdout,"query: %s", query);
              fprintf(stdout,", length: %s\n", lengths);
              length=atoi(lengths);
            }
          }
          if(!strcmp(func, "Reset"))
            value[0] = '\0';
          else {
            char tempmemberid[32];
            sprintf(tempmemberid,"%s-%d", memberid, lineno);
            if(dbs_get_parameter(tempmemberid, sizeof(value),
                value)==0) {
              db_data_get_field(ddata, memberid, sizeof(value),
                  value);
            }
          }

          char mode[10];
          strcpy(mode,"Required");
          sprintf(query,"'memberid' \"%s\", 'mode'", memberid);
          qmemberiddata = db_query(query);
          if((dmemberiddata = db_query_get_first(qmemberiddata))
              != NULL) {
            if(db_data_get_field(dmemberiddata, "mode",
               sizeof(mode), mode)) {
              fprintf(stdout,"query: %s", query);
              fprintf(stdout,", mode: %s\n", mode);
              length = atoi(lengths);
            }
          }

          int errors = 0;

          if(!strcmp(mode, "Required") && value[0] == '\0') {
            errors++;
            formerrors++;
          }

          html_printf("<td>");
          sprintf(htmltext,"<input type=\"text\" value=\"%s\" name=\"%s-%d\" size=\"%d\"", value, memberid, lineno, length);
          if(strcmp(htmlmode, "Change"))
            strcat(htmltext, " readonly");
          if(errors != 0 && !strcmp(func, "Check"))
            strcat(htmltext," style=\"border-color:red;\"");
          strcat(htmltext,"><br>");

          html_printf(htmltext);
          html_printf("</td>");
          dmemberid=db_data_get_next(dmemberid);
        }
        html_printf("</tr>");
        ddata=db_data_get_next(ddata);
      }
      html_printf("</table>");
    } // end of if(!strcmp(chapter,"lines"))                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
    dchapter=db_data_get_next(dchapter);
  } // end of while(dchapter!=NULL) {                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                

  if(!strcmp(func, "Save") && formerrors == 0) {

  }

  html_printf("<p>application: %s(%s)</p>", app, appname);
  html_printf("<p>htmlparams: %s</p>", htmlparams);
  html_printf("<p>htmlstring: %s</p>", htmlstring);

  html_printf("<code>");
  dbs_dump_query(qappname);
  dbs_dump_query(qchapter);
  if(qheadermemberid != NULL)
    dbs_dump_query(qheadermemberid);
  if(qlinesmemberid != NULL)
    dbs_dump_query(qlinesmemberid);
  if(qheaderdata != NULL)
    dbs_dump_query(qheaderdata);
  if(qlinesdata != NULL)
    dbs_dump_query(qlinesdata);
  html_printf("</code>");
  html_printf("</form>");
  html_printf("<br><br>%s sha256(%s<br><br>", programname,
      htmldigest);
}

Seuraavassa html otsakkeen lukuun käytettyjä funktioita: Ensiksiksi kuitenkin malli html otsakkeista: ensimmäiseltä tietueelta luetaan html-method ja path. Cookie-riviltä luetaan mode ja session id. Parametrit riviltä luetaan html lomakkeen parametrit.

Metodirivi on tiedoston ensimmäinen rivi ja sillä on välilyönnein eroteltuna method, path ja html-type. Cookie rivi alkaa “Cookie:” tekstillä.

Parametririviä edeltää neljä merkkiä, “\r\n\r\n” merkkijono.

Content-Length kenttä sisältää “payload” kentän pituuden eli tässä tapauksessa parametrien pituuden. Palvelimen lähettäessä html-sivun Content-Length kenttä sisältää html tekstin pituuden.

Otsakkeessa rivien välissä on aina ‘\r\n’.

POST /app HTTP/1.1^M
Host: 192.168.1.14:5004^M
Content-Type: application/x-www-form-urlencoded^M
Origin: https://192.168.1.14:5004^M
Accept-Encoding: br, gzip, deflate^M
Cookie: mode=Display;sessionid=ObVhVnVzVEhQ96gUfKKS7HxguJR99r9Z^M
Connection: keep-alive^M
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8^M
User-Agent: Mozilla/5.0 (iPad; CPU OS 12_4_8 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Mobile/15E148 Safari/604.1^M
Referer: https://192.168.1.14:5004/logon^M
Content-Length: 230^M
Accept-Language: en-us^M
^M
app-0=tilaus&func-0=Submit&asiakasnumero-1=1000&asiakkaan+nimi-1=Firma+1&asiakkaan+osoite-1=Venekatu+1&asiakkaan+postitoimipaikka-1=00500&asiakkaan+postiosoite-1=111111&asiakkaan+maa-1=Finland&asiakkaan+\
email-1=jarik%40example.com"
, htmlparams: "app-0=tilaus&func-0=Submit&asiakasnumero-1=1000&asiakkaan+nimi-1=Firma+1&asiakkaan+osoite-1=Venekatu+1&asiakkaan+postitoimipaikka-1=00500&asiakkaan+postiosoite-1=111111&asiakkaan+maa-1=Fin\
land&asiakkaan+email-1=esimerkki%40example.com", htmlsessionid: "ObVhVnVzVEhQ96gUfKKS7HxguJR99r9Z", htmlmode: "Display"

Dbs_html_get_request_line:lla haetaan otsakkeen rivi, jonka alku sisältää helutun merkkijonon.

unsigned char *dbs_html_get_request_line(unsigned char *name)
{
  unsigned char *p;

  p = htmlin;
  while(*p != '\0') {
    if(!strncmp(p, "\r\n", 2)) {
      p += 2;
      if(!strncmp(p, name, strlen(name))) {
        p += strlen(name);
        if(*p == ':') {
          p++;
          while(isblank(*p))
            p++;
          return(p);
        }
      }
    }
    p++;
  }
  return(NULL);
}

dbs_html_get_request_line_num on numeerinen versio edellisestä: tällä luetaan ainakin Content-Length kentän arvo.

void dbs_html_get_request_line_num(unsigned char *name, int lenbuffer, unsigned char *buffer)
{
  int len;
  unsigned char *n, *p;

  if((p = dbs_html_get_request_line(name)) == NULL)
    sprintf(buffer, "0");
  else {
    len = 0;
    n = buffer;
    while(isdigit(*p)) {
      if(++len < lenbuffer)
        *n++ = *p;
      p++;
    }
    *n = '\0';
  }
}

Dbs_get_cookie funktiolla luetaan cookiet, jotka löytyvät otsakkeen “Cookie:” merkkijonolla alkavalta riviltä.

void dbs_get_cookie(unsigned char *name, int valuelen, unsigned char *value)
{
  int len;
  unsigned char *p,*n;

  if((p = dbs_html_get_request_line("Cookie")) == NULL) {
    return;
  }

  while(*p!='\n' && *p!='\0') {
    while(isblank(*p))
      p++;
    if(!strncmp(p, name, strlen(name))) {
      p += strlen(name);
      while(isblank(*p))
        p++;
      if(*p == '=')
        p++;
      while(isblank(*p))
        p++;
      n = value;
      len = 0;
      while(*p != ';' && isprint(*p)) {
        if(++len < sizeof(htmlsessionid))
          *n++ = *p;
        p++;
      }
      *n = '\0';
      return;
    }
    while(*p != ';' && *p != '\n' && *p != '\0')
      p++;
    if(*p == ';')
      p++;
    while(isblank(*p))
      p++;
  }
}

Aliohjelma kopioi välilyönnillä/välilyönneillä erotetun merkkijonon buf merkkijonoon.

void dbs_html_parse_string(int buflen, char *buf, unsigned char **p2)
{
  int len;
  unsigned char *p, *n;

  p = *p2;
  len = 0;
  while(!isblank(*p) && *p!='\n' && *p!='\r') {
    if(++len < buflen) {
      *buf++ = *p;
    }
    p++;
  }
  *buf = '\0';

  while(isblank(*p))
    p++;

  *p2 = p;
}

Seuraava rutiini kopioi tiedostonimen tiedostopolusta buf merkkijonomuuttujaan. Tiedostopolkuhan alkaa viimeisen kauttaviivan jälkeisestä merkistä.

void dbs_html_parse_filename(int buflen, char *buf, unsigned char *p)
{
  int len;
  unsigned char *n;

  len = 0;
  n = buf;

  while(*p != '\0') {
    if(*p == '/') {
      n = buf;
      len = 0;
    } else {
      if(++len < buflen)
        *n++ = *p;
    }
    p++;
  }
  *n = '\0';
}

Seuraava rutiini palauttaa osoitteen html-otsakkeen parameters kenttään. Se löytyy “\r\n\r\n” merkkijonon jälkeen.

unsigned char *dbs_html_get_params()
{
  unsigned char *p = htmlin;

  while(*p != '\0') {
    if(!strncmp(p, "\r\n\r\n", 4)) {
      p += 4;
      break;
    }
    p++;
  }
  return(p);
}

Seuraavalla rutiinilla lasketaan suoritettavalle tiedostolle SHA256 tiiviste. Tiiviste löytyy kaikkien sivujen alalaidasta.

void dbs_file_digest(char *filename,unsigned char *hash)
{
  int c;
  unsigned char buffer[1024];
  FILE *fp1;
  HashCtx ctx;

  HashInit(&ctx);
  if((fp1 = fopen(filename, "rb")) != NULL) {
    while((c = fread(buffer, 1, sizeof(buffer), fp1)) > 0)
      HashUpdate(&ctx, buffer, c);
    fclose(fp1);
  }
  HashFinal(hash, &ctx);
}
#include <errno.h>
#include <openssl/ssl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/* See: http://h41379.www4.hpe.com/doc/83final/ba554_90007/ch04s03.html */

#include <netdb.h>

#define DEFAULT_PORT "5004"
#define backlog 5

int s,news;
char *cert_file = "cert.pem";
char *privatekey_file = "key.pem";

char *myport=NULL;

void main(int argc,char *argv[])
{
  int c, listenfd, status, status2, addr_size,
      len, callid;
  unsigned char timebuf[128];

  time_t now;

  SSL_METHOD *method = NULL;
  SSL_CTX *ctx = NULL;
  SSL *ssl;
  X509 *peer_cert;

  struct sockaddr sa_serv;
  struct sockaddr_in sa_cli;
  struct addrinfo hints;
  struct addrinfo *res;

  unsigned char *p;

  FILE *fp1;
  unsigned char buffer10[10];

  unsigned char filedigest[HashLen];

  dbs_file_digest("/proc/self/exe", filedigest);
  htmldigest[0] = '\0';
  for(c = 0;c < HashLen;c++) {
    char twodigits[3];
    sprintf(twodigits,"%02x", filedigest[c]);
    strcat(htmldigest, twodigits);
  }
  fprintf(stdout,"%s sha256(%s)\n", programname, htmldigest);
  fflush(stdout);

  ressu_init();
  fort_init();

  callid = 0;

  if((fp1 = fopen("dbscallid.deb", "r")) != NULL) {
    fgets(buffer10, sizeof(buffer10), fp1);
    callid = atoi(buffer10);
    fclose(fp1);
  }

  if((fp1 = fopen("dbspid.deb", "w")) != NULL) {
    fprintf(fp1,"%d\n", getpid());
    fclose(fp1);
  }

  signal(SIGPIPE, SIG_IGN);

  procname =argv[0];
  myport = DEFAULT_PORT;

  SSL_library_init();

  OpenSSL_add_ssl_algorithms();

  SSL_load_error_strings();

  if((method = (SSL_METHOD *)
      SSLv23_server_method()) == NULL) {
    fprintf(stderr,"\n%s: cannot SSLv3_server_method()",
        procname);
    fflush(stderr);
  }

  if((ctx = SSL_CTX_new(method))==NULL) {
    fprintf(stderr,"\n%s: cannot SSL_CTX_new()", procname);
    fflush(stderr);
  }

  if(SSL_CTX_use_certificate_file(ctx,
    cert_file, SSL_FILETYPE_PEM)<=0) {
    fprintf(stderr,"\n%s: cannot SSL_CTX_use_certificate()",
        procname);
    fflush(stderr);
  }

  if(SSL_CTX_use_PrivateKey_file(ctx,
      privatekey_file, SSL_FILETYPE_PEM)<=0) {
    fprintf(stderr,"\n%s: cannot SSL_CTX_use_certificate()",
        procname);
    fflush(stderr);
  }

  if(!SSL_CTX_load_verify_locations(ctx,
      cert_file, NULL)) {
    fprintf(stderr,"\n%s: cannot SSL_CTX_verify_locations()",
        procname);
    fflush(stderr);
  }

  memset(&hints, 0, sizeof(hints));
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_flags = AI_PASSIVE;

  if ((status = getaddrinfo(NULL, myport, &hints, &res)) != 0) {
    fprintf(stderr, "\n%s: getaddrinfo error: %s",
            procname, gai_strerror(status));
    fprintf(stderr, ", error code %d\n", status);
    fflush(stderr);
  }

  if((s = socket(res->ai_family, res->ai_socktype, res->ai_protocol))==-1) {
    fprintf(stderr, "%s: socket(), error: %d\n",
        procname, errno);
    perror("socket");
    fflush(stderr);
  }

  if(bind(s, res->ai_addr, res->ai_addrlen)==-1) {
    if(errno==98) {
      fprintf(stdout,"%s cannot bind, waiting to bind",procname);
      fflush(stdout);
      while(errno==98) {
        sleep(10);
        fprintf(stdout,".");
        fflush(stdout);
        bind(s, res->ai_addr, res->ai_addrlen);
      }
      fprintf(stdout,"bind done!\n");
      fflush(stdout);
    } else {
      fprintf(stderr,"\n%s: cannot bind(), error: %d\n",
          procname, errno);
      perror("bind");
      fflush(stderr);
    }
  }

  freeaddrinfo(res);

  if((listenfd=listen(s, backlog))==-1) {
    fprintf(stderr,"\n%s: cannot listen()\n", procname);
    perror("listen");
    fflush(stderr);
  }
  for(;;) {
    addr_size = sizeof(sa_cli);
    news=accept(s, (struct sockaddr *)&sa_cli, &addr_size);

    strcpy(htmlip, inet_ntoa(sa_cli.sin_addr));
    sprintf(htmlport,"%d", sa_cli.sin_port);

    fprintf(stdout,"\nConnection from %x, ip %s, port %s",
            sa_cli.sin_addr.s_addr,
            htmlip,
            htmlport);

    if((ssl = SSL_new(ctx)) == NULL) {
      fprintf(stderr,"\n%s: cannot SSL_new()", procname);
      fflush(stderr);
    }

    if(SSL_set_fd(ssl,news) != 1) {
      fprintf(stderr,"\n%s: cannot SSL_set_fd()", procname);
      fflush(stderr);
    }

    if((status = SSL_accept(ssl)) < 0) {
      // Try to fix SSL_accept error 5
      if(SSL_get_error(ssl,status) == 5) {                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
        fprintf(stdout,"SSL_access failed, retry");
        continue;
      }
      fprintf(stderr,"\n%s: cannot SSL_accept(), status: %d, SSL error: %d",  procname, status, SSL_get_error(ssl,status));
      fflush(stderr);
    }


    peer_cert=SSL_get_peer_certificate(ssl);
    if(peer_cert == NULL) {
      fprintf(stdout,", No peer certificate");
      fflush(stdout);
    }
    memset(htmlin, 0, sizeof(htmlin));

    if((status = SSL_read(ssl, htmlin+strlen(htmlin), sizeof(htmlin)-strlen(htmlin))) < 1) {
      fprintf(stderr,"\n%s: cannot SSL_read(), status: %d, SSL_error: %d",
              procname, status, SSL_get_error(ssl,status));
      fflush(stderr);
    }
    fprintf(stdout,"\nreceived %d chars, \"%s\"\n", status, htmlin);

    unsigned char *p;
    unsigned char *n;

    p = htmlin;
    dbs_html_parse_string(sizeof(htmlmethod), htmlmethod, &p);
    fprintf(stdout,"htmlmethod: \"%s\"", htmlmethod);
    fflush(stdout);

    dbs_html_parse_string(sizeof(htmlpath), htmlpath, &p);
    fprintf(stdout,", htmlpath: \"%s\"", htmlpath);
    fflush(stdout);

    dbs_html_parse_string(sizeof(htmlversion), htmlversion, &p);
    fprintf(stdout,", htmlversion: \"%s\"", htmlversion);
    fflush(stdout);

    dbs_html_parse_filename(sizeof(htmlfilename),
        htmlfilename, htmlpath);
    fprintf(stdout,", htmlfilename: \"%s\"", htmlfilename);
    fflush(stdout);


    htmlparams = dbs_html_get_params();
    fprintf(stdout,", htmlparamslen: \"%ld\"",
        strlen(htmlparams));

    int clen = 0;
    // Content-Length: 33                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
    dbs_html_get_request_line_num("Content-Length",
        sizeof(buffer10), buffer10);
    clen=atoi(buffer10);
    fprintf(stdout,", Content-Length: \"%d\"",clen);

    if(clen - strlen(htmlparams) > 0) {
      if((status = SSL_read(ssl, htmlin+strlen(htmlin),
          sizeof(htmlin) - strlen(htmlin)))<1) {
        fprintf(stderr,"\n%s: cannot SSL_read(), status2: %d, SSL_error: %d",
                procname, status, SSL_get_error(ssl,status));
        fflush(stderr);
      }
      fprintf(stdout,"\nreceived %d chars, \"%s\"\n", status, htmlin);
    }
    fprintf(stdout,", htmlparams: \"%s\"", htmlparams);

    strcpy(htmlmode,"Display");
    htmlsessionid[0] = '\0';

    dbs_get_cookie("sessionid",
        sizeof(htmlsessionid), htmlsessionid);
    fprintf(stdout,", htmlsessionid: \"%s\"", htmlsessionid);
    fflush(stdout);

    dbs_get_cookie("mode", sizeof(htmlmode), htmlmode);
    fprintf(stdout,", htmlmode: \"%s\"\n", htmlmode);
    fflush(stdout);

    html=html1;
    html[0]='\0';

    now = time(NULL);
    strftime(timebuf, sizeof(timebuf), HTMLTIMEFORMAT, gmtime(&now));

    html_printf("HTTP/1.0 200 OK\r\n");
    html_printf("Location: \r\n");
    html_printf("Server: %s\r\n", programname);
    html_printf("Date: %s\r\n", timebuf);

    html=html2;
    html[0]='\0';

    html_printf("\n<!doctype html>\r\n");
    html_printf("<html lang=\"fi\">");

    html_printf("<head>");
    html_printf("<meta charset=\"utf-8\">");
    html_printf("<title>dbs</title>");
    html_printf("<meta name=\"author\" content=\"Jari Kuivaniemi\">");
    html_printf("</head>");

    html_printf("<body>");

    if(htmlsessionid[0] == '\0')
      dbs_logon();
    else if(!strcmp(htmlfilename, "logon")) {
      htmlsessionid[0] = '\0';
      dbs_logon();
    }

    if(htmlsessionid[0] != '\0') {
      dbs_app();
    }

#ifdef TEST
    html_printf("<h1>Hello, world! åäö</h1>");
    html_printf("<form action=\"httpstest2\" method=\"post\">");
    html_printf("<input type=\"submit\" name=\"func\" value=\"Submit\">");
    html_printf("<input type=\"text\" value=\"1234åäö\" name=\"sovellusä\" size=\"10\">");
    html_printf("<p>htmlparams: %s</p>",htmlparams);
    html_printf("<p>htmlstring: %s</p>",htmlstring);
    html_printf("</form>");
#endif

    html_printf("</body>");

    html_printf("</html>");

    len = strlen(html2);
    html = html1;
    html_printf("Content-Length: %d",len);
    html_printf("\r\n\r\n");

    if((status = SSL_write(ssl, html1, strlen(html1))) <1) {
      fprintf(stderr,"\n%s: cannot SSL_write(), status: %d, SSL error: %d",
              procname, status, SSL_get_error(ssl,status));
      fflush(stderr);
    }
    if((status = SSL_write(ssl, html2, strlen(html2))) < 1) {
      fprintf(stderr,"\n%s: cannot SSL_write(), status: %d, SSL error: %d",
              procname, status, SSL_get_error(ssl,status));
      fflush(stderr);
    }
    fprintf(stdout,"\nSSL connection using %s",
        SSL_get_cipher(ssl));
    fflush(stderr);
    SSL_free(ssl);

    if(close(news) == -1) {
      fprintf(stderr,"\n%s: cannot close()\n", procname);
      perror("close");
      fflush(stderr);
    }

    fprintf(stdout,"\ncallid: %d\n", callid++);
    if((fp1 = fopen("dbscallid.deb","w")) != NULL) {
      fprintf(fp1,"%d\n", callid);
      fclose(fp1);
    }

    fflush(stdout);
  } // for(;;)                                                                                                                                                                                              
  SSL_CTX_free(ctx);
}

FORT v0.4 uusi versio

Fortin edellisestä versiosta (http://moijari.com/?p=634) on kulunut jo jonkin verran aikaa ja uudessa versiossa on muutoksia, joten ajattelin postata uuden version. Oikeastaan varsinaisessa koodissa ei ole paljon muutoksia, ainoastaan nuo lohkon alun (FORT_INTERNAL_EVENTS_START) ja lopun (FORT_INTERNAL_EVENTS_END) makrot. Funktioiden esittelyt ovat muuttuneet siten, että tämän tiedoston (fort.c) sisäisiin apufunktioihin on lisätty static sana, joka estää funktioiden käytön fort.c tiedoston ulkopuolella. Lisäksi globaaleissa muuttujissa on static sana, jos ei ole toivottavaa että niitä käytetään fort.c:n ulkopuolelta.

Jos haluat lisätietoa Fortunasta, tässä on wikipediasivu https://en.wikipedia.org/wiki/Fortuna_(PRNG).

Edit: tehty korjauksia ja muutoksia julkaisun jälkeisten viikkojen aikana:

Poistettu fort_add_random_event_time():sta toistuva toinen merkki. Näin tapahtuma tehdään vain alimmista kahdeksasta bitistä, joissa on eniten satunnaisuutta ja seuraavat kahdeksan bittiä, joissa satunnaisuutta ei ole ohitetaan. Toinen merkki lähetetään puskuriin vain jos se poikkeaa edellisestä toisesta merkistä. Poistosta etuna saadaan se että puskurien kerättyjen merkkien määrä kuvaa tarkemmin todella satunnaisia merkkejä, ja tällä lisätään satunnaisuutta satunnaistapahtumien datan pituuteen.

Lisätty fort_reseed() koodiin aikavyöhykkeen vaihto. Jos ajon aikana aikavyöhyke vaihtuu, suoritetaan save ja report koodit. Näin saadaan raporttien välit laskettua oikein heti uuden aikavyöhykkeen alusta. Ennen vaihtoa uuden aikavyöhykkeen ensimmäinen katko tuli edellisen aikavyöhykkeen mukaan, jolloin katko ei ollut selkeä. Nyt raportissa näkyy aikavyöhykkeen vaihto rivi vaihdon kellonajalla ja heti seuraava katko on laskettu oikein huomioiden uusi aikavyöhyke.

Fort_init:iin on lisätty mahdollisuus käyttää /dev/random ja /dev/urandom tiedostoja fort:in avainnukseen.

Fort_reseed():iin on lisätty automaattinen tallennus 10 minuutin välein. Tallennus tapahtuu fort_reseed:issä, jos reseediä ei suoriteta tallennusta ei tapahdu. Tallennuksen yhteydessä tulostetaan tiedostoon fortpools.deb raporttia puskureiden sisältämistä merkkimääristä. Raportin määrä lyhennetään lukukelpoiseksi (4 merkkiä) rutiinilla readable_length_decimal().

Satunnaistapahtuman lisäykseen on lisätty 5 ja 6 moodit. Myös puskurien määrää voi kasvattaa 64:een (FORT_64_POOLS).

Erotettu satunnaisgeneraattorin tilan tallennus ja puskurien kerätyt merkit raportti toisistaan. Näin niiden väliset ajat voidaan valita erikseen.

Lisätty puskurien kerätyt merkit raporttiin binäärimoodi, jossa katkokohta on 1000 (decimal) sijasta 1024(binary). Moodi valitaan muuttujalla FORT_DUMP_POOLS_BIN. Binääritulosteesta on malli postin lopussa.

Jos haluat tehdä esittelytiedoston fort:in toiminnoista tässä mahdollinen fort.h tiedosto pääfunktioista.

typedef unsigned long long IUTIME;

void fort_add_random_event(int *pool, int source, int mode, int len, unsigned char *buf);
void fort_add_random_event_timer_start(IUTIME *micros);
void fort_add_random_event_timer_do(int *pool, int source, int mode, IUTIME *micros);
void fort_add_random_event_time(int *pool, int source, int mode);
void fort_add_random_event_split(int *pool, int source, int mode, int len, unsigned char *buf, int size);
void fort_rekey(unsigned char *buf);
void fort_pseudo_random_data(int len, unsigned char *buf);
void fort_reseed(int len, unsigned char *buf);
void fort_random_data(int len, unsigned char *buf);
int fort_random_data_byte();
void fort_clear();
int fort_random_data_byte_limit(int limit);
void fort_random_data_buffer(int size, unsigned char *buffer);
void fort_save();
void fort_restore();
void dump_pools(char *header);

Seuraavassa fort ohjelman käyttämät aliohjelmat, inccvar() ja clearcvar() 16 merkkisen laskurin käsittelyyn. Gettenths():llä fort_random_data() hakee sekunnin kymmenesosat, perustoiminnassa avainnus voidaan tehdä pienimmillään kymmenesosa sekunnin päästä edellisestä avainnuksesta. Getmicroseconds() taas on aikaan liittyvien satunnaistapahtumia tekevien rutiinien käytössä. Getseconds():illa haetaan sekunnit seuraavan automaattisen fort_save():n ajoitusta varten. Varsinainen ajoitettu save koodi on fort_reseed() funktiossa.

Huomaa, että näiden rutiinien määrittelyyn on lisätty static sana, joka tarkoittaa sitä että rutiinia voidaan kutsua vain tästä tiedostosta (fort.c). Samoin aiemmin globaaleihin muuttujiin, kuten seuraava cvar[] on lisätty static sana ja näin sen pitäisi olla käsiteltävissä vain tämän tiedoston rutiineilla.

Jos haluat käyttää forttia ilman ressua, voit lisätä pikku-a:n seuraavaan RESSU sanaan. Tarvitset kuitenkin SHA256:n (http://moijari.com/?p=752) tiivisteiden laskentaan. Jos poistat ressun lisäksi sisäiset tapahtumat, FORT_USE_RANDOM määritykset ja save():n luoman fort.rnd tiedoston, on main() rutiinin tulostama salasana-merkkijono aina sama.

#define RESSU 2
#define DEBUG 2
#define FORT_INTERNAL_EVENTS 2
static unsigned int fort_internal_events = 1;
static unsigned int fort_internal_event_mode = 1;
static unsigned int fort_reseed_always = 1;
static unsigned int fort_fill_pool_when_reseeding = 1;
#define FORT_USE_URANDOM 2
#define aFORT_USE_RANDOM 2

#define aFORT_64_POOLS 2

#ifdef FORT_64_POOLS
#define FORT_POOLS 64
typedef unsigned long FORT_COUNTER;
#else
#define FORT_POOLS 32
typedef unsigned int FORT_COUNTER;
#endif

static unsigned char cvar[16];

static void inccvar()
{
  int c = 0;

  /* 16 bytes, LSB first */
  while(++cvar[c] == 0 && c < sizeof(cvar)-1)
    c++;
}

static void clearcvar()
{
  int c;

  for(c=0; c < sizeof(cvar); c++)
    cvar[c] = 0;
}

static IUTIME gettenths()
{
  struct timeval tv;

  gettimeofday(&tv, NULL);

  return((IUTIME)tv.tv_sec*10 +
      (int)tv.tv_usec/100000);
}

static IUTIME getmicroseconds()
{
  struct timeval tv;

  gettimeofday(&tv, NULL);

  return((IUTIME)tv.tv_sec*1000000 +
      tv.tv_usec);
}

static IUTIME getseconds()
{
  struct timeval tv;

  gettimeofday(&tv, NULL);

  return((IUTIME)tv.tv_sec);
}

Nämä ovat edellisestä postista (http://moijari.com/?p=937) tuttuja satunnaistapahtumien muodostusrutiineja, joita käytetään haluttujen lohkojen alussa ja lopussa. Huomaa taas static sanat pool ja pool2 muuttujissa, Static sanalla muuttujan arvo saadaan säilymään funktion kutsukertojen välillä.

#ifdef FORT_INTERNAL_EVENTS
#define FORT_INTERNAL_EVENTS_START(source) \
  IUTIME micros; \
  static int \
    pool=0, pool2=0; \
  if(fort_internal_events) { \
    fort_add_random_event_time(&pool, \
    source, fort_internal_event_mode); \
    fort_add_random_event_timer_start(&micros); \
  }
#else
#define FORT_INTERNAL_EVENTS_START(source)
#endif

#ifdef FORT_INTERNAL_EVENTS
#define FORT_INTERNAL_EVENTS_END(source) \
  if(fort_internal_events) \
    fort_add_random_event_timer_do(&pool2, \
      source, fort_internal_event_mode, \
      &micros);
#else
#define FORT_INTERNAL_EVENTS_END(source)
#endif

Seuraavassa satunnaistapahtumien luomiseen käytetyt kutsut: fort_add_random_event on päärutiini ja muut käyttävät sitä varsinaisen tapahtuman tekemiseen.

Luomisen yhteydessä tapahtuvaan seuraavan satunnaistapahtumapuskurin valintaan on kaksi uutta toimintoa. 1 on perusvaihtoehto, jossa puskurin numeroa kasvatetaan aina yhdellä. 2 on vaihtoehto, joka pyrkii täyttämään viimeksi käytetyt puskurit ensin. 3 ei tee puskurille mitään, kutsuja voi päättää seuraavan puskurin itse. 4 valitsee satunnaisen puskurin seuraavaksi puskuriksi. 5 toimii siten että jos käsitellään puskuria 0 täytetään puskuri minimiin, muuten vaihdetaan seuraavaan puskuriin. 6 täyttää kohdalle osuvan vajaan puskurin minimiin ja jatkaa sitten seuraavaan puskuriin. Näistä 5 ja 6 ovat uusia. Jos verrataan niitä kakkoseen ne täyttävät enemmän suuria puskureita,

Fort_add_random_event_timer_start() ja fort_add_random_event_timer_do() tekevät funktion tai lohkon suorittamisen kestosta ajan millisekunteina . Fort_add_random_event_time() tekee kellonajasta ajan millisekunteina. Molemmat lähettävät fort_add_random_event() rutiinilla satunnaistapahtuman puskureihin.

Fort_add_random_event_split() jakaa pidemmän tapahtuman useampaan puskuriin.

struct fort_pool {
  unsigned long length;
  HashCtx pool;
};
static struct fort_pool fort_pools[FORT_POOLS];

#define FORT_MIN_POOL_SIZE 64

#ifdef DEBUG
static int event_id = 0;
static char fort_events_file[128] =
    "fortevents.deb";
#endif

void fort_add_random_event(int *pool, int source, int mode, int len, unsigned char *buf)
{
  while(len>1 && buf[len-1]==0)
    len--;

  HashUpdate(&fort_pools[*pool].pool, buf, len);
  fort_pools[*pool].length+=len;

#ifdef DEBUG
  if(event_id < 65536) {
    FILE *fp1;
    if((fp1=fopen(fort_events_file, "a"))!=NULL) {
      fprintf(fp1,"id=%d", event_id);
      fprintf(fp1,", source=%02d", source);
      fprintf(fp1,", pool=%02d", *pool);
      fprintf(fp1,", mode=%d", mode);
      fprintf(fp1,", len=%d", len);
      fprintf(fp1,", data=");
      for(int c=0;c<len;c++)
        fprintf(fp1,"%02x", buf[c]);
      fprintf(fp1,"\n");
      fflush(fp1);
      fclose(fp1);
      event_id++;
    }
  }
#endif

  switch(mode) {

  case 1:
    (*pool) = ((*pool)+1) % FORT_POOLS;
    break;

  case 2:
    if((*pool) >= FORT_POOLS-1 ||
        fort_pools[*pool].length <
        fort_pools[*pool+1].length)
      (*pool) = 0;
    else
      (*pool) = ((*pool)+1) % FORT_POOLS;
    break;

  case 3:
    break; // caller decides next pool                                                                                                                                                                                                                          

  case 4:
#ifdef RESSU
    (*pool) = ressu_genbyte_limit(FORT_POOLS);
#else
    (*pool) = ((*pool)+1) % FORT_POOLS;
#endif
    break;

  case 5:
    if((*pool)==0 && fort_pools[*pool].length <
        FORT_MIN_POOL_SIZE)
      (*pool) = 0;
    else
      (*pool) = ((*pool)+1) % FORT_POOLS;
    break;

  case 6:
    if(fort_pools[*pool].length >=
        FORT_MIN_POOL_SIZE)
      (*pool) = ((*pool)+1) % FORT_POOLS;
    break;
  }
}

void fort_add_random_event_timer_start(IUTIME *micros)
{
  *micros = getmicroseconds();
}

void fort_add_random_event_timer_do(int *pool, int source, int mode, IUTIME *micros)
{
  unsigned char temp[2];
  IUTIME t;

  t = getmicroseconds()-*micros;
  temp[0] = t & 0xff;
  temp[1] = (t>>8) & 0xff;

  fort_add_random_event(pool, source, mode,
               sizeof(temp), temp);
}

void fort_add_random_event_time(int *pool, int source, int mode)
{
  int len;
  unsigned char temp[2];
  IUTIME t;
  static int prev_second_byte = -1;

  t = getmicroseconds();
  temp[0] = t & 0xff;
  temp[1] = (t>>8) & 0xff;
  len=2;

  if(prev_second_byte == temp[1]) {
    len--;
  } else {
    prev_second_byte = temp[1];
  }

#ifdef RESSUEVENT
  ressu_genbuffer(len, temp);
#endif

  fort_add_random_event(pool, source, mode,
	       len, temp);
}

void fort_add_random_event_split(int *pool, int source, int mode, int len, unsigned char *buf, int size)
{
  int n;

  while(len != 0) {
    n = (size<len)? size:len;
    fort_add_random_event(pool, source,
        mode, n, buf);
    buf += n;
    len -= n;
  }
}

Seuraavassa ensimmäiset satunnaisuutta keräävät tapahtumat. Näillä muodostetaan SHA256 tiiviste halutusta merkkijonosta. Init funktio täyttää vain SHA256 yhteysalueen state[] ja count kentät, ja sen tuottama kesto tapahtuma on yleensä nolla. Update funktio tallettaa lyhyet alle 64 merkkiset merkkijonot yhteysalueen buffer muuttujaan ja siinäkin usein kestotapahtuma on lähellä nollaa. Kun puskuri täyttyy, ajetaan transform ja se vie hiukan pidemmän ajan (katso http://moijari.com/?p=752). Final on samantapainen kun update, se ajaa lopuksi transformin ja siitä tulee pidempiä kestoja.

Suoritusaikatapahtumissa näissä kaikissa on satunnainen kellonaika (ainakin alimmat 8 bittiä), suoritusaikahan on mikrosekunteina.

static void hash_init(HashCtx *hash)
{
  FORT_INTERNAL_EVENTS_START(10)

  HashInit(hash);

  FORT_INTERNAL_EVENTS_END(11)
}

static void hash_update(HashCtx *hash, unsigned char *data, int len)
{
  FORT_INTERNAL_EVENTS_START(12)

  HashUpdate(hash, data, len);

  FORT_INTERNAL_EVENTS_END(13)
}

static void hash_final(unsigned char digest[HashLen], HashCtx *hash)
{
  FORT_INTERNAL_EVENTS_START(14)

  HashFinal(digest, hash);

  FORT_INTERNAL_EVENTS_END(15)
}

Seuraavana fort_pseudo_random_data() funktio ja sen apufunktio fort_rekey(). Fort_rekey() funktiolla palautetaan käyttäjän antamaan muistialueeseen (tässä buf) satunnaisbittisarja. Fort_pseudo_random_data() käyttää rekeytä satunnaisbittien tekemiseen ja fort_key avaimen päivittämiseen.

static unsigned char fort_key[HashLen];

void fort_rekey(unsigned char *buf)
{
  HashCtx hash;

  FORT_INTERNAL_EVENTS_START(16)

  hash_init(&hash);
  hash_update(&hash, fort_key, sizeof(fort_key));
  hash_update(&hash, (unsigned char *)&cvar,
    sizeof(cvar));
  hash_final(buf, &hash);
  inccvar();

  // Forget hash
  memset(&hash, 0, sizeof(hash));

  FORT_INTERNAL_EVENTS_END(17)
}

#define FORT_PSEUDO_LIMIT 1048576

void fort_pseudo_random_data(int len, 
    unsigned char *buf)
{
  unsigned char tmp[HashLen];
  unsigned int n, blockbytes;

  FORT_INTERNAL_EVENTS_START(18)

  blockbytes = 0;

  while(len != 0) {
    FORT_INTERNAL_EVENTS_START(19)

    fort_rekey(tmp);
    n = (len<HashLen) ? len : HashLen;
    memcpy(buf, tmp, n);
    buf += n;
    len -= n;
    blockbytes += n;

    // rekey every 1048576 bytes if data left
    if(blockbytes >= FORT_PSEUDO_LIMIT &&
        len > 0) {                                                                                                              
      fort_rekey(fort_key);
      blockbytes = 0;
    }

    FORT_INTERNAL_EVENTS_END(20)
  }

  fort_rekey(fort_key);

  // Forget last bytes
  memset(tmp, 0, sizeof(tmp));

  FORT_INTERNAL_EVENTS_END(21)
}

Fort_reseed laskee yhteistiivisteen edellisestä fort_key:stä, cvar laskurista ja käyttäjän antamasta merkkijonosta ja muodostaa niistä uuden fort_key avaimen. Pohjana on tässä edellinen fort_key avain, eli “uusi” satunnaisuus lisätään edelliseen.

Lisäksi fort_reseed tekee fort_save():n aina kymmenen minuutin välein (FORT_SECONDS_BETWEEN_SAVES). Save tehdään vasta kun ohjelma suorittaa seuraavan kerran fort_reseed():iä.

Reseed() kirjoittaa myös raportin puskurien sisältämästä merkkimäärästä tiedostoon fortpools.rnd. Myöhempänä on mallituloste.

Voit valita sekä talletuksien että raporttien välisen ajan sekunteina. Talletuksien väli valitaan FORT_SECONDS_BETWEEN_SAVES muuttujalla ja raporttien väli valitaan FORT_SECONDS_BETWEEN_POOLS_REPORTS:llä. Väli kerrotaan sekunteina. “Takuulla” toimivista vaihtoehdoista on lista koodin kommentissa. Voit toki valita ajat miten haluat, mutta nämä tasatunteihin tai vuorokausiin perustuvat ajat ovat helpompia ymmärtää.

Fort_random_data() tekee avainnuksen tarvittaessa uudestaan määritellen fort_key:n (kutsumalla fort_reseed():iä). Muodostettavaan avaimeen käytetään fort_pools[] puskureihin talletettujen satunnaismerkkien tiivisteitä. Mahdollisen avainnuksen jälkeen fort_random_data kutsuu fort_pseudo_random_data() rutiinia, joka palauttaa varsinaiset satunnaisbitit kutsujalle.

Fort_random_data() funktiossa suurimmat erot suoritusaika tapahtumissa johtuvat siitä että ressu_genbuffer() palauttaa suurimman osan tapahtumista puskuristaan ja lopuille täytetään puskuri uudestaan, joka vie aikaa.

// choose one of                                                                                                                                                                       
//     60 (1 minute)                                                                                                                                                                   
//     300 (5 minutes)                                                                                                                                                                 
//     600 (10 minutes)                                                                                                                                                                
//     900 (15 minutes)                                                                                                                                                                
//     1200 (20 minutes)                                                                                                                                                               
//     1800 (30 minutes)                                                                                                                                                               
//     3600 (hour)                                                                                                                                                                     
//     7200 (2 hours)                                                                                                                                                                  
//     10800 (3 hours)                                                                                                                                                                 
//     14400 (4 hours)                                                                                                                                                                 
//     21600 (6 hours)                                                                                                                                                                 
//     28800 (8 hours)                                                                                                                                                                 
//     43200 (12 hours                                                                                                                                                                 
//     86400 (24 hours)                                                                                                                                                                
// to get readable report..                                                                                                                                                            

#define FORT_SECONDS_BETWEEN_SAVES 600
static IUTIME fort_next_save = 0;

#ifdef DEBUG
#define FORT_SECONDS_BETWEEN_POOLS_REPORTS 3600
static IUTIME fort_next_pools_report = 0;
#define TIMEFORMAT "%Z%Y%m%d%H%M%S"
#endif

void dump_pools(char *header);

static char current_timezone[10];

void fort_reseed(int len, unsigned char *buf)
{
  HashCtx hash;

  FORT_INTERNAL_EVENTS_START(22)

  hash_init(&hash);
  hash_update(&hash, fort_key, sizeof(fort_key));
  hash_update(&hash, (unsigned char *)&cvar,
      sizeof(cvar));
  hash_update(&hash, buf, len);
  hash_final(fort_key, &hash);
  inccvar();

  memset(&hash, 0, sizeof(hash));

  if(fort_next_save != 0 ||
     fort_next_pools_report != 0) {
    IUTIME seconds;
    seconds = getseconds();

    char timezone[10];
    strftime(timezone, sizeof(timezone), "%Z",
             localtime((time_t *)&seconds) );
    if(strcmp(current_timezone,timezone)) {
      fort_next_save=1;
      fort_next_pools_report=1;
      strcpy(current_timezone,timezone);
    }

    if(fort_next_save != 0 &&
       seconds >= fort_next_save) {

      IUTIME diff=
        timegm(localtime((time_t *)&seconds))-
          seconds;

      fort_next_save = seconds -
        ((seconds+diff) %
         FORT_SECONDS_BETWEEN_SAVES) +
        FORT_SECONDS_BETWEEN_SAVES;

      fort_save();
    }

#ifdef DEBUG

    if(fort_next_pools_report != 0 &&
       seconds >= fort_next_pools_report) {

      IUTIME diff=
        timegm(localtime((time_t *)&seconds))-
          seconds;

      char timebuf[128];
      strftime(timebuf, sizeof(timebuf), TIMEFORMAT,
               localtime((time_t *)&seconds));

      fort_next_pools_report = seconds -
        ((seconds+diff) %
         FORT_SECONDS_BETWEEN_POOLS_REPORTS) +
        FORT_SECONDS_BETWEEN_POOLS_REPORTS;

      dump_pools(timebuf);
    }
#endif
  }

  FORT_INTERNAL_EVENTS_END(23)
}

static FORT_COUNTER fort_reseed_count = 0;
static IUTIME fort_next_reseed = 0;

void fort_random_data(int len, unsigned char *buf)
{
  int c;
  IUTIME tenths;
  HashCtx hash;
  unsigned char buffer[HashLen];

  FORT_INTERNAL_EVENTS_START(24)

  tenths=gettenths();

  if( (fort_reseed_always == 1) ||
      (fort_next_reseed == 0) ||
      (fort_next_reseed <= tenths &&
      fort_pools[0].length >=
      FORT_MIN_POOL_SIZE) ) {

    // next tenth of a second
    fort_next_reseed = tenths + 1;                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
    fort_reseed_count++;

    hash_init(&hash);
    c=0;

    while(c < FORT_POOLS && (fort_reseed_count
        % (1<<c)) == 0) {

      FORT_INTERNAL_EVENTS_START(25)

      if(fort_fill_pool_when_reseeding==1 &&
          fort_pools[c].length <
          FORT_MIN_POOL_SIZE) {

        unsigned char temp[64];
        int len = FORT_MIN_POOL_SIZE -
            fort_pools[c].length;
        int n;

        while(len != 0) {

          FORT_INTERNAL_EVENTS_START(26)

          n = (len < sizeof(temp)) ? len :
              sizeof(temp);
          fort_pseudo_random_data(n, temp);
#ifdef RESSU
          ressu_genbuffer(n, temp);
#endif
          int pooltemp = c;
          fort_add_random_event(&pooltemp,
              27, fort_internal_event_mode,
              n, temp);
          len-=n;

          FORT_INTERNAL_EVENTS_END(28)
        }
        memset(temp, 0, sizeof(temp));
      }
      hash_final(buffer, &fort_pools[c].pool);
      hash_update(&hash, buffer, sizeof(buffer));
      fort_pools[c].length = 0;
      HashInit(&fort_pools[c].pool);
      // save earlier pool to new one
      hash_update(&fort_pools[c].pool,
          buffer, sizeof(buffer));                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
      c++;

      FORT_INTERNAL_EVENTS_END(29)
    }
    hash_update(&hash, (unsigned char *)
        &cvar, sizeof(cvar));
    hash_final(buffer, &hash);
    fort_reseed(sizeof(buffer), buffer);
    // Forget hash context
    memset(&hash, 0, sizeof(hash));
    // Forget reseed key
    memset(buffer, 0, sizeof(buffer));
    inccvar();
  }
  fort_pseudo_random_data(len, buf);

  FORT_INTERNAL_EVENTS_END(30)
}

Seuraavana fortin aliohjelmia: alun muuttujat muodostavat puskurin joka täytetään uudestaan sen tyhjentyessä. Fort_random_data_byte() täyttää puskurin uudestaan sen tyhjentyessä ja palauttaa seuraavan käyttämättömän merkin. Fort_clear():illa voidaan tyhjentää puskuri, jolloin seuraavalla esimerkiksi fort_random_data_byte():llä se täytetään uudestaan. Fort_clear():ia voi käyttää kriittisten satunnaislukujen haun alussa ja lopussa, jolloin bitit ovat muistissa “näkyvissä” vain minimiajan. Fort_random_data_byte_limit() palauttaa parametrinä annettua rajaa (limit) pienemmän satunnaisluvun. Fort_random_data_buffer() palauttaa puskurillisen satunnaisbittejä.

#define FORTCNT 128

static unsigned int fort_cnt = FORTCNT;
static unsigned char fort_bytes[FORTCNT];
static int fort_byte = 999999999;

int fort_random_data_byte()
{
  if(fort_byte >= fort_cnt) {
    fort_random_data(fort_cnt, fort_bytes);
    fort_byte = 0;
  }
  return(fort_bytes[fort_byte++]);
}

void fort_clear()
{
  memset(fort_bytes, 0, fort_cnt);
  fort_byte = 999999998;
}

int fort_random_data_byte_limit(int limit)
{
  int c;

  while((c = fort_random_data_byte())>=
      (256/limit)*limit);
  /* while((c = fort_random_data_byte())>
      (256/limit)*limit); little bug */
  return(c % limit);
}

void fort_random_data_buffer(int size,
    unsigned char *buffer)
{
  int c;

  for(c=0; c<size; c++)
    buffer[c] ^= fort_random_data_byte();
}

Fort_save() ja fort_restore() tallettavat ja lukevat takaisin fort generaattorin tilan. Fort_save tallettaa jonon fort_pseudo_random_data():lla luettuja merkkejä fort.rnd tiedostoon. Fort_restore() lukee edellisellä talletuksella kirjoitetut merkit ja tekee niillä fort_reseed():in. Jos fort_restore() ei onnistu avaamaan tiedostoa, se generoi merkit fort_reseed():ille käyttäen ressu_genbuffer():ia.

#define FORT_SAVE_SIZE 1024
char fort_random_file[128] = "fort.rnd";

void fort_save()
{
  FILE *fp1;
  unsigned char buffer[FORT_SAVE_SIZE];

  if((fp1 = fopen(fort_random_file, "w"))
      != NULL) {
    fort_pseudo_random_data(sizeof(buffer), buffer);
    fwrite(buffer, 1, sizeof(buffer), fp1);
    memset(buffer, 0, sizeof(buffer));
    fclose(fp1);
  }
}

void fort_restore()
{
  int d;
  FILE *fp1;
  unsigned char buffer[FORT_SAVE_SIZE];

  if((fp1 = fopen(fort_random_file, "rb"))
      != NULL) {
    d = fread(buffer, 1, sizeof(buffer), fp1);
    fclose(fp1);
  } else {
    fort_pseudo_random_data(sizeof(buffer), buffer);
#ifdef RESSU
    ressu_genbuffer(sizeof(buffer), buffer);
#endif
    d = sizeof(buffer);
  }
  fort_reseed(d, buffer);
  memset(buffer, 0, sizeof(buffer));

  fort_save();
}

Seuraavaa fort_init rutiinia kutsutaan ohjelman main rutiinin alussa ressu_init() rutiinin jälkeen. Fort_readfile_xor() on /dev/random:in ja /dev/urandom:in lukemista varten.

#if defined FORT_USE_URANDOM || \
    defined FORT_USE_RANDOM

static void fort_readfile_xor(int len,
    unsigned char *buf,
    unsigned char *filename)
{
  int c, n;
  unsigned char temp[64];
  FILE *fp1;

  if((fp1 = fopen(filename, "rb"))
      != NULL) {
    while(len != 0) {
      n = (len < sizeof(temp)) ?
          len : sizeof(temp);
      fread(temp, 1, n, fp1);
      for(c = 0; c < n; c++)
        buf[c] ^= temp[c];
      len -= n;
      buf += n;
    }
    fclose(fp1);
  }
}

#endif

#define aFORT_DUMP_POOLS_BIN 2

#ifdef FORT_DUMP_POOLS_BIN
#define FORT_DUMP_POOLS_HIGH 1023
#define FORT_DUMP_POOLS_DIVIDER 1024
#define FORT_DUMP_POOLS_FIELD_WIDTH 6
#else
#define FORT_DUMP_POOLS_HIGH 999
#define FORT_DUMP_POOLS_DIVIDER 1000
#define FORT_DUMP_POOLS_FIELD_WIDTH 5
#endif

#define FORT_DUMP_POOLS_WIDTH 32

static void readable_length(char *buf,
    unsigned long length)
{
  int c, low;

  // B = byte
  // K = kilo   10^3   2^10
  // M = mega   10^6   2^20
  // G = giga   10^9   2^30
  // T = tera   10^12  2^40
  // P = peta   10^15  2^50
  // E = exa    10^18  2^60
  // Z = zetta  10^21  2^70
  // Y = yotta  10^24  2^80
  char units[] = "BKMGTPEZY";

  strcpy(buf,"***");
  low=0;

  for(c=0; length>=low &&
      c<sizeof(units)-1; c++) {
    if(length>=low &&
        length<=FORT_DUMP_POOLS_HIGH) {
      if(units[c]!='B')
        sprintf(buf,"%ld%c", length, units[c]);
      else
        sprintf(buf,"%ld", length);
      break;
    }
    length/=FORT_DUMP_POOLS_DIVIDER;
    low=1;
  }
}

void dump_pools(char *header)
{
  FILE *fp1;
  unsigned char readable[10];

  if((fp1 = fopen("fortpools.deb", "a"))
      !=NULL) {
    fprintf(fp1,"%-25s", header);
    for(int c=0;c<FORT_POOLS;c++) {
      if(c>0 && c%FORT_DUMP_POOLS_WIDTH==0)
        fprintf(fp1,"\n%-25s","");
      readable_length(readable,
          fort_pools[c].length);
      fprintf(fp1,"%*s",
              FORT_DUMP_POOLS_FIELD_WIDTH,
              readable);
    }
    fprintf(fp1, "\n");
    fclose(fp1);
  }
}

void fort_init()
{
  int c, pooltemp;

  fprintf(stdout,"Fort v0.491");
  fprintf(stdout,", hash: %s",
      HashName);
  fprintf(stdout,", pools: %d*%ld", FORT_POOLS,
      sizeof(struct fort_pool));
  fprintf(stdout,", HashCtx: %ld",
      sizeof(HashCtx));
  fprintf(stdout,", hashsize: %d",
      HashLen);
  fprintf(stdout,"\n");

  unsigned int save_fort_internal_events;
  save_fort_internal_events=fort_internal_events;
  fort_internal_events=1;

  fort_next_save = 0;
  clearcvar();

  FILE *fp1;

  if((fp1 = fopen("fortpools.deb", "w"))!=NULL)
    fclose(fp1);

  dump_pools("Emptying pools report");

#ifdef RESSU
  ressu_genbuffer(sizeof(fort_key), fort_key);
  dump_pools("Generate fort key w ressu");
#endif

  // Initialize buffers                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
  for(c=0; c<FORT_POOLS; c++) {
    fort_pools[c].length = 0;
    HashInit(&fort_pools[c].pool);
  }

  dump_pools("Initialize buffers");

#ifdef DEBUG
  // Empty events file                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
  if((fp1 = fopen(fort_events_file, "w"))!=NULL)
    fclose(fp1);
  event_id = 0;

  dump_pools("Emptying events");

#endif

  unsigned char temp[64];

#ifdef FORT_USE_URANDOM
  memset(temp, 0, sizeof(temp));
  fort_readfile_xor(sizeof(temp), temp,
      "/dev/urandom");
  fort_reseed(sizeof(temp), temp);

  dump_pools("Randomness from urandom");
#endif

#ifdef FORT_USE_RANDOM
  memset(temp, 0, sizeof(temp));
  fort_readfile_xor(sizeof(temp), temp,
      "/dev/random");
  fort_reseed(sizeof(temp), temp);

  dump_pools("Randomness from random");
#endif

  for(c=0; c<FORT_POOLS; c++) {
    unsigned char buffer[HashLen];
    fort_pseudo_random_data(sizeof(buffer), buffer);
#ifdef RESSU
    ressu_genbuffer(sizeof(buffer), buffer);
#endif
    hash_update(&fort_pools[c].pool,
      buffer, sizeof(buffer));
  }
  dump_pools("Initialize buffers 2");

  fort_pseudo_random_data(sizeof(temp), temp);
#ifdef RESSU
  ressu_genbuffer(sizeof(temp), temp);
#endif
  pooltemp = 0;
  fort_add_random_event_split(&pooltemp,
      31, 2, sizeof(temp), temp, 1);

  dump_pools("Fill first buffers");

#ifdef FORT_INTERNAL_EVENTS
  if(fort_internal_events) {
    // Create some internal events                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
    for(c=0; c<64; c++)
      fort_random_data(sizeof(temp), temp);
  }
  dump_pools("Internal events");
#endif

  fort_reseed_count = 0;
  fort_next_reseed = 0;

  // Reseed fort_key with new events                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
  fort_random_data(sizeof(temp), temp);
  fort_reseed(sizeof(temp), temp);

  dump_pools("Reseed");

  fort_restore();

  dump_pools("Restore");

  // Events to random pools                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
  fort_pseudo_random_data(sizeof(temp), temp);
#ifdef RESSU
  ressu_genbuffer(sizeof(temp), temp);
#endif
  pooltemp=0;
  fort_add_random_event_split(&pooltemp,
      32, 4, sizeof(temp), temp, 1);

  dump_pools("Events to random pools");

  // Fill first pool                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
  fort_pseudo_random_data(sizeof(temp), temp);
#ifdef RESSU
  ressu_genbuffer(sizeof(temp), temp);
#endif
  pooltemp=0;
  fort_add_random_event(&pooltemp,
      33, 1, sizeof(temp), temp);

  dump_pools("Fill 1st pool");

  // Forget temp                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
  memset(temp,0,sizeof(temp));

  fort_internal_events = save_fort_internal_events;

  fort_reseed_count = 0;
  fort_next_reseed = 0;

  fort_next_save = 1;
  fort_next_pools_report = 1;
}

Tässä tuloste dump_pools():in raportista (fortpools.deb tiedostosta): Raportti on tulostettu 26.7 kello 20:30 (ja 20:40) runsaasti satunnaislukuja tekevällä ohjelmalla. Rivien alussa on aikavyöhyke, vuosi, kuukausi, päivä, tunnit, minuutit ja sekunnit. Loppurivi koostuu ohjelman puskurien kerättyjen merkkien määristä. Tässä puskureita on 64. Tällä raportilla on kaksi riviä, joiden väli on tässä 10 minuuttia eli 600 sekuntia (FORT_SECONDS_BETWEEN_POOLS_REPORTS). Yksiköt ovat tarkemmin funktiossa readable_length(). Kerättyjen määrien yksiköt olevat yksiköt ovat kiloa(K), megaa(M) ja gigaa(G). Luvut ovat desimaaleja eli 1 kilo on 1000 merkkiä, mega on 1000 kiloa ja giga on 1000 megaa. Näin ne mahtuvat välilyönteineen viiteen merkkiin, raportti olisi leveämpi jos yksiköt olisivat binäärisiä eli 1024 merkkiä vastaa kiloa, 1024 kiloa vastaa megaa ja 1024 megaa vastaa gigaa. Oikeassa tulosteessa luku vie viisi merkkiä, tässä on näköjään 1 väli + numerot + yksikkö (=2-5 merkkiä), eli luvut eivät ole oikeissa sarakkeissa. Rivin alussa olevia puskureita käytetään useimmin ja rivin lopussa olevia käytetään harvemmin, tässä 2G puskureita ei ole vielä käytetty. Koska raporttirivi tulostetaan heti avainnuksen jälkeen voidaan arvailla että ensimmäisellä raporttirivillä on käytetty äskettäin puskuri 0 ja seuraavalla rivillä on käytetty puskurit 0, 1 ja 2. Noiden puskurien kerätty määrä on nollattu äskettäin..

EEST20200726203000 3 145 141 136 136 121 121 121 427 426 419 2K 2K 2K 2K 2K 55K 162K 162K 162K 1M 2M 2M 9M 23M 23M 23M 23M 23M 23M 902M 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G
EEST20200726204000 5 3 0 198 196 196 292 380 380 381 1K 2K 2K 2K 16K 43K 43K 43K 257K 686K 1M 1M 4M 11M 11M 39M 39M 39M 39M 39M 918M 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G 2G

Tässä vielä toinen versio pools raportista, jossa on käytetty binääri versiota tulosteesta (FORT_DUMP_POOLS_BIN): 1000 ja 1023:n väliin osuvat pituudet ovat sen verran harvinaisia ettei tähänkään raporttiin osunut kuin yksi sellainen tapaus, 1002K rivin alkupuolella.

EEST20200802123000 72 74 75 232 232 290 330 412 627 1K 1K 1K 1K 7K 7K 7K 59K 164K 164K 164K 1002K 2M 2M 9M 22M 48M 100M 100M 100M 100M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M 938M

Vielä pieni pääohjelma joka tekee fortilla salasanoiksi sopivaa merkkijonoa: ohjelmassa on malli fort_clear():in käytöstä, sekä satunnaistapahtumien luomisesta. Ensin kuitenkin uudet makrot satunnaistapahtumien lisäämiselle asiakasohjelmaan:

#define FORT_EVENTS 2
static unsigned int fort_events = 1;
static unsigned int fort_event_mode = 6;

#ifdef FORT_EVENTS
#define FORT_EVENTS_START(source) \
  IUTIME micros; \
  static int \
    pool=0, pool2=0; \
  if(fort_events) { \
    fort_add_random_event_time(&pool, \
    source, fort_event_mode); \
    fort_add_random_event_timer_start(&micros); \
  }
#else
#define FORT_EVENTS_START(source)
#endif

#ifdef FORT_EVENTS
#define FORT_EVENTS_END(source) \
  if(fort_events) \
    fort_add_random_event_timer_do(&pool2, \
	source, fort_event_mode, &micros);
#else
#define FORT_EVENTS_END(source)
#endif
int main(int argc, char *argv[])
{
#ifdef RESSU
  ressu_init();
#endif
  fort_init();

  dump_pools("To main");

  unsigned char chars[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
    "abcdefghijklmnopqrstuvwxyz" \
    "0123456789";

  fort_clear();
  for(int c=0; c<1024; c++) {
    FORT_EVENTS_START(100)
    
    if(c>0 && c%128 == 0)
      fprintf(stdout, "\n");
    int byte=fort_random_data_byte_limit(
        sizeof(chars)-1);
    fprintf(stdout,"%c", chars[byte]);

    FORT_EVENTS_END(101)
  }
  fort_clear();
  fprintf(stdout, "\n");
  fflush(stdout);

  dump_pools("End of main");

  fort_save();
}

FORT sisäisten satunnaistapahtumien lisääminen uudistunut

Tässä postissa esitelty tapa helpottaa fort koodin lukemista ja ohjelmasi omien tapahtumien lisäämistä. Tämä lyhentää koodia runsaasti ja helpottanee sen ymmärtämistä. Jos lisäät tapahtumia omaan ohjelmaasi, tee makroista ja internal_events -muuttujasta uudet versiot ilman internal sanaa. Ennen satunnaistapahtumat lisättiin seuraavalla lohkojen alkuun ja loppuun lisättävällä koodilla: Tässä lähetetään satunnaistapahtumia, joiden source on 10 ja 11.

void hash_init(HashCtx *hash)
{
#ifdef FORT_INTERNAL_EVENTS
  unsigned long micros;
  if(fort_internal_events) {
    fort_add_random_event_time(&time_pool,
        10, fort_event_mode);
    fort_add_random_event_timer_start(&micros);
  }
#endif
  HashInit(hash);
#ifdef FORT_INTERNAL_EVENTS
  if(fort_internal_events)
    fort_add_random_event_timer_do(&init_pool,
        11, fort_event_mode, &micros);
#endif
}

Uudessa versiossa tehdään seuraavat uudet #define määritykset: FORT_INTERNAL_EVENTS_START on lohkon alkuun lisättävä makro ja FORT_INTERNAL_EVENTS_END on lohkon loppuun lisättävä makro. Nämä periaatteessa sisältävät nuo edelliset satunnaistapahtumien koodilohkot, mutta ne kirjoitetaan vain kerran makron määritykseen, ei enää uudestaan joka käyttökerralla. Nämä uudet makrot sisältävät myös pool ja pool2 määrittelyt, eli int-muuttujat joissa talletetaan seuraavan tapahtuman puskurinumero. Static tyyppisinä nämä tuntuvat toimivan niinkuin halutaan, eli että jokaisella lohkolla on omat muuttujansa, jopa sisäkkäisillä, ja että kenttien arvot säilyvät funktion ajokerrasta seuraavaan. Pool muuttujaa käytetään aikatapahtumiin ja pool2 muuttujaa käytetään kestotapahtumiin. Tosin ennen aikatapahtumille oli vain yksi pool-muuttuja, nyt niitä on oma jokaiselle FORT_INTERNAL_EVENTS lohkolle.

#ifdef FORT_INTERNAL_EVENTS
#define FORT_INTERNAL_EVENTS_START(source) \
  unsigned long micros; \
  static int pool=0, pool2=0; \
  if(fort_internal_events) { \
    fort_add_random_event_time(&pool, source, \
        fort_event_mode); \
    fort_add_random_event_timer_start(&micros); \
  }
#else
#define FORT_INTERNAL_EVENTS_START(source)
#endif

#ifdef FORT_INTERNAL_EVENTS
#define FORT_INTERNAL_EVENTS_END(source) \
  if(fort_internal_events) \
    fort_add_random_event_timer_do(&pool2, \
        source, fort_event_mode, &micros);
#else
#define FORT_INTERNAL_EVENTS_END(source)
#endif

Näiden makrojen avulla koodista tulee luettavampaa ja enää ei koodata erillisiä _pool-loppuisia muuttujia. Seuraavassa edellinen hash_init kirjoitettuna näillä uusilla makroilla: Sulkeissa kerrottu arvo on source kentän arvo. Huomaa että makrorivien lopussa ei ole puolipistettä (;).

void hash_init(HashCtx *hash)
{
  FORT_INTERNAL_EVENTS_START(10)

  HashInit(hash);

  FORT_INTERNAL_EVENTS_END(11)
}
Tässä vielä esimerkki pidemmästä rutiinista:
void fort_random_data(int len, unsigned char *buf)
{
  int c;
  unsigned long tenths;
  HashCtx hash;
  unsigned char buffer[HashLen];

  FORT_INTERNAL_EVENTS_START(22)

  tenths=gettenths();

  if( (fort_reseed_always==1) ||
      (fort_next_reseed==0) ||
      (fort_next_reseed<=tenths &&
       fort_pools[0].length >=
           FORT_MIN_POOL_SIZE) ) {

    // next tenth of a second
    fort_next_reseed = tenths + 1;                                                                                                                                                                                                                                                      
    fort_reseed_count++;

    hash_init(&hash);
    c=0;

    while(c < 32 &&
        (fort_reseed_count % (1<<c))==0) {

      FORT_INTERNAL_EVENTS_START(23)

#ifdef RESSU
      if(fort_fill_pool_when_reseeding==1 &&
         fort_pools[c].length <
              FORT_MIN_POOL_SIZE) {
        unsigned char tmp32[32];
        int len=FORT_MIN_POOL_SIZE -
            fort_pools[c].length;
        int n;

        while(len>0) {

          FORT_INTERNAL_EVENTS_START(24)

          n=(len<sizeof(tmp32)) ? len :
              sizeof(tmp32);
          ressu_genbuffer(n, tmp32);

          int pooltemp=c;
          fort_add_random_event(&pooltemp, 29,
              fort_event_mode, n, tmp32);
          len-=n;

          FORT_INTERNAL_EVENTS_END(25)
        } // while(len>0)
      } // if(fort_fill...
#endif
      hash_final(buffer, &fort_pools[c].pool);
      hash_update(&hash, buffer, sizeof(buffer));
      fort_pools[c].length = 0;
      HashInit(&fort_pools[c].pool);
      // save earlier pool to new one
      hash_update(&fort_pools[c].pool,
          buffer, sizeof(buffer));                                                                                                                                                                                                                   
      c++;

      FORT_INTERNAL_EVENTS_END(26)
    } // while(c < 32...
    hash_update(&hash, (unsigned char *)&cvar,
        sizeof(cvar));
    hash_final(buffer, &hash);
    fort_reseed(sizeof(buffer), buffer);
    // Forget hash context
    memset(&hash, 0, sizeof(hash));
    /* Forget reseed key */
    memset(buffer, 0, sizeof(buffer));
    inccvar();
  } // if( (fort_reseed...
  fort_pseudo_random_data(len, buf);

  FORT_INTERNAL_EVENTS_END(27)
}

Debuggaillaan ressun kelloketjuja

Tässä uusi debukkailuversio ressun kellon luvusta. Tämä lukee kelloa puskurillisen kerrallaan, tavallisen kellomerkki kerrallaan tavan sijasta. Jälkimmäinen tapa ei ole kovin mukava debukkailijan kannalta, koska kellojono tietysti etenee debukkaustulosteiden tulostamisen aikana ja kellojonot lyhenevät, joskus häviävät kokonaan. Kellomerkkejä voi olla vain yksi per kellon arvo.

Edit: Tehty korjauksia julkaisun jälkeisten viikkojen aikana. Lisäksi muotoilin ohjelman uudelleen siten että myös puuttuvat ketjut lisäävät tarvittaessa yhden bitin teoreettista satunnaisuutta. Muutokset ovat kappaleessa if(prevbyte != byte).

Tässä postissa on myös kellojonojen debukkailuun tarkoitettu versio ressu_genbytes() rutiinista. Se tulostaa teoreettisten satunnaisbittien laskentaan liittyviä tietoja.

Mutta ensin uusi versio kellon luennasta. Ohjelma lukee kellojonon merkit clock_bytes[] taulukkoon ja palauttaa merkkejä yhden kerrallaan. Uudessa luennassa yritetään uusi puskuri aloittaa edellisen puskurin viimeisellä arvolla (cb), jos tämä ei ole ensimmäinen puskuri (clock_byte!=99…). Clock_bytes[], clock_byte, clock_cnt ja niiden käyttö ovat tuttuja jo ressu_genbyte() rutiinista, toki ressu_ alkuisina kenttinä. Kun haluat lopettaa kellon debukkaamisen, lisää kommentti #define DEBUG_CLOCK lauseeseen tai lisää pikku-a sen DEBUG_CLOCK sanan ensimmäiseksi kirjaimeksi.

Kellojonot toki pitenevät tässä versiossa, koska ne talletetaan tässä nopeassa luupissa (for(int c=0…). Toisaalta luulisin että ilman debukkia kellojonojen pituuksissa on enemmän vaihtelua.

#define DEBUG_CLOCK 2

#ifdef DEBUG_CLOCK
#define CLOCKCNT 256

unsigned char clock_bytes[CLOCKCNT];
int clock_byte = 999999999;
int clock_cnt = CLOCKCNT;

unsigned char clockbyte() /* JariK 2020 */
{
  if(clock_byte >= clock_cnt) {
    struct timeval tv;
    if(clock_byte != 999999999) {
      unsigned char cb;
      gettimeofday(&tv, NULL);
      cb = tv.tv_usec & 0xff;
      while(cb != clock_bytes[clock_cnt-1]) {
        gettimeofday(&tv, NULL);
        cb = tv.tv_usec & 0xff;
      }
    }
    for(int c = 0; c < CLOCKCNT; c++) {
      gettimeofday(&tv, NULL);
      clock_bytes[c] = tv.tv_usec & 0xff;
    }
    clock_byte = 0;
  }
  return(clock_bytes[clock_byte++]);
}

#else //#ifdef DEBUG_CLOCK                                                                                                                                                                                  

unsigned char clockbyte() /* JariK 2013 */
{
  struct timeval tv;

  gettimeofday(&tv, NULL);

  return(tv.tv_usec & 0xff);
}

#endif //#ifdef DEBUG_CLOCK

Sitten kellojonojen debukkailuun tarkoitettu versio ressu_genbytes() rutiinista: Raportin tulostaa kappale, joka alkaa tulostustiedoston avaamisella (fp1=fopen).

Toinen ero oikeaan versio 1.8 rutiiniin on tuo rndbits kentän kasvatus kahdella, eli yksi poikkeusjono on kaksi bittiä satunnaisuutta. Ajatus on että poikkeus olisi “ykkösbitti” ja sille voidaan laskea ylimääräinen “nollabitti” noista perusjonoista, joita yritetään olla laskematta. Tämä teoriassa lyhentää suoritusaikaa. Hmmmmm…

void ressu_genbytes(int size, unsigned char *buffer) /* JariK 2020 v1.8 */
{
  int c, d, e, f, g,
    byte, prevbyte,
    clockbytes = 0, rndbits = 0,
    chainbytes = 0, oldindex = 0, oldcount;
  unsigned char olddata[10];

  memset(olddata, 0, sizeof(olddata));
  prevbyte = clockbyte();
  f = 0;

  for(c = 0; c < 8 || c%8 != 0 || 
      clockbytes < 2000 ||
      rndbits < 8*size; c++) {

    for(d = 0; d < size; d++) {
      e = buffer[d];
      e = ((e&0x80)>>7) | ((e&0x7f)<<1);
      byte = clockbyte();
      buffer[d] = e^byte;

      if(prevbyte != byte) {
        oldcount = 0;
        for(g = 0; g < sizeof(olddata); g++) {
          if(olddata[g] == chainbytes)
            oldcount++;
        }

        int tbit, tbit2;
        tbit=0;
        tbit2=0;
        if(oldcount < 4) {
          tbit=1;
          rndbits+=2;                                                                                                                            
        } else if((prevbyte+1)%256 != byte) {
          tbit2=1;
          rndbits++;
        }

        FILE *fp1;
        if((fp1 = fopen("ressuchains.deb",
            "a")) != NULL) {
          fprintf(fp1,"id=%d", id);
          fprintf(fp1,", c=%3d", c);
          fprintf(fp1,", c%%8=%d", c%8);
          fprintf(fp1,", prevbyte=%3d",
              prevbyte);
          fprintf(fp1,", byte=%3d",
              byte);
          fprintf(fp1,", tbits=%4d",
              rndbits);
          fprintf(fp1,", length=%2d",
              chainbytes);
          fprintf(fp1,", tbit=%d", tbit);
          fprintf(fp1,", tbit2=%d", tbit2);
          fprintf(fp1,", oldcount=%d",
              oldcount);
          fprintf(fp1,", data=");
          for(g = 0; g < sizeof(olddata); g++)
            fprintf(fp1," %02d",
                olddata[g]);
          fprintf(fp1,"\n");
          fclose(fp1);
          id++;
        } // if((fp1

        olddata[oldindex] = chainbytes;
        oldindex = (oldindex + 1) %
            sizeof(olddata);

        clockbytes += chainbytes;
        chainbytes = 0;

        prevbyte = byte;
      } // if(prevbyte != byte)
      chainbytes++;
    } // for(d = 0;

    for(d = 0; d < size; d++) {
      f = (f + buffer[d]) % size;
      e = buffer[d];
      buffer[d] = buffer[f];
      buffer[f] = e;
    } // for(d = 0
  } // for(c = 0
}

Edellisen rutiinin tuloste: Tämä on ehkä vähän lukukelvoton tällä kapealla palstalla, mutta tässä näkyy kuitenkin tärkeimmät numerot, c:ssä on pääluupin (for(c=0) kierroksen numero, c%8 kertoo käsiteltävän bitin numeron välillä 0-7, byte kertoo kellomerkin arvon, tbits kertoo teoreettisten bittien lukumäärän, length taas tämän kellojonon pituuden, tbit kertoo onko tällä rivillä ollut teoreettinen satunnaisbitti vai ei, oldcount antaa tämän pituuden esiintymien lukumäärän. Data kentässä on kaikkien kymmenen viimeisen kellojonon pituudet.

id=568, c= 52, c%8=4, byte=127, tbits= 552, length=23, tbit=1, oldcount=1, data= 49 26 24 26 24 26 22 26 23 51
id=569, c= 53, c%8=5, byte=128, tbits= 552, length=26, tbit=0, oldcount=5, data= 49 26 24 26 24 26 22 26 23 26
id=570, c= 53, c%8=5, byte=129, tbits= 554, length=27, tbit=1, oldcount=1, data= 27 26 24 26 24 26 22 26 23 26
id=571, c= 53, c%8=5, byte=130, tbits= 556, length=25, tbit=1, oldcount=1, data= 27 25 24 26 24 26 22 26 23 26
id=572, c= 53, c%8=5, byte=131, tbits= 558, length=23, tbit=1, oldcount=2, data= 27 25 23 26 24 26 22 26 23 26
id=573, c= 53, c%8=5, byte=132, tbits= 560, length=27, tbit=1, oldcount=2, data= 27 25 23 27 24 26 22 26 23 26
id=574, c= 54, c%8=6, byte=133, tbits= 560, length=26, tbit=0, oldcount=4, data= 27 25 23 27 26 26 22 26 23 26
id=575, c= 54, c%8=6, byte=134, tbits= 562, length=25, tbit=1, oldcount=2, data= 27 25 23 27 26 25 22 26 23 26

Vielä yksi sivuhuomautus: tässä ovat esiintyneet kaikki byte arvot eli kellon arvot välillä 127-134, mikä lienee yleisin tapaus. Voisi kuvitella että on tilanteita jossa ohitetaan yksi kelloarvo kokonaan, esimerkiksi hidas kone tms. Se voisi olla teoreettisen satunnaisbitin paikka, ainakin siinä tapauksessa ettei bittejä muuten saada laskettua. Jos esimerkiksi kaikki kellojonot on yhden merkin pituisia, meillä on data taulukko täynnä ykkösiä (10 kpl), jälleen uutta ykköstä ei tietenkään lasketa. Jos tässä tilanteessa olisi kellon arvoilla välejä saataisiin silti kasvatettua satunnaisbittejä. Teoreettinen huomautus, toivottavasti.

Sitten pieni pääohjelma

void main(int argc,char *argv[])
{
  int c;
  unsigned char buffer[128];

  FILE *fp1;
  if((fp1 = fopen("ressuchains.deb",
      "w")) != NULL)
    fclose(fp1);

  for(c = 0; c < 20; c++)
    ressu_genbytes(sizeof(buffer), buffer);

  for(c = 0; c < sizeof(buffer); c++) {
    if(c > 0 && c%32 == 0)
      fprintf(stdout,"\n");
    fprintf(stdout," %02x", buffer[c]);
  }
  fprintf(stdout,"\n");
}

SHA256 tiivisteen laskentaohjelman vakiot

SHA256 laskentaohjelmaa kuvaavassa postissani mainitsin vakionumerot, joilla täytetään tiivisteen alustuksessa tiivisteen tila (state[]) ja transformissa käytettävä k256 taulu. Postissa sanoin, että ne tulevat alkulukujen neliöjuuren (state[]) ja kuutiojuuren (k256[]) desimaaleista. Tässä postissa käydään läpi ohjelma, joilla ne voidaan tarkistaa tai laskea. Postin mittaan käydään ohjelma ja sen tuloste läpi kappaleittain ja lopussa on ohjelma kokonaisuudessaan.

Ohjelman laskemiseen tarvitaan alkulukuja, joten ensimmäisissä kappaleissa tehdään lista niistä: ohjelmassa Eratostheneen seulan toteutus. Toteutuksessa haetaan alkuluvut 400 ensimmäisen luvun joukosta, tilataululle tarvitaan alkuluvut 2-19 ja k256[]-taululle alkuluvut 2-311. Alussa taulu alustetaan ykkösillä (for(c=0…) ja merkataan 0 ja 1 ei alkuluvuiksi. Sitten käydään taulu läpi taulun pituuden neliöjuureen asti (for(c=2…). Taulusta etsitään ykköset (alkulukuja) ja käydään läpi niiden kerrannaiset (for(d=c*2…) ja merkataan ne nolliksi, ne eivät kerrannaisina ole alkulukuja.

  int c, d, count, id;
  char primes[400];

  for(c=0; c<sizeof(primes); c++)
    primes[c] = 1;

  primes[0] = 0;
  primes[1] = 0;

  for(c=2; c<=sqrt(sizeof(primes)-1); c++) {
  // Little bug 30.6 JariK
  //for(c=2; c<=sqrt(sizeof(primes)); c++) {
    if(primes[c] == 1) {
      fprintf(stdout,"*%d", c);
      count = 0;
      for(d = c*2; d < sizeof(primes); d+=c) {
        primes[d] = 0;
        if(count++ < 20)
          fprintf(stdout," %d", d);
      }
      if(count > 20)
        fprintf(stdout,"...");
      fprintf(stdout,"\n");
    }
  }

Näiden ensimmäisten kappaleiden tuloste on seuraavanlainen: tähdellä merkitty on alkuluku, jota käsitellään ja samalla rivillä sen kanssa ovat kerrannaiset, jotka poistetaan listalta. Riville tulostetaan count muuttujan avulla vain 20 ensimmäistä kerrannaista. Ohjelman on käytävä läpi vain 19 ensimmäistä alkulukua, luku 20 ei ole alkuluku ja sen kerrannaiset menisivät taulun yli (20*20=400). Siis neliöjuuren jälkeen taulussa ei voi olla yhdistettyjä lukuja, joita ei ole vielä merkitty. Neliöjuurta pienempien tekijöiden luvut on jo merkitty, ja neliöjuurta suurempien tekijöiden luvut (joissa ei ole pientä tekijää) sijoittuvat taulun ulkopuolelle.

*2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42...
*3 6 9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 60 63...
*5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 100 105...
*7 14 21 28 35 42 49 56 63 70 77 84 91 98 105 112 119 126 133 140 147...
*11 22 33 44 55 66 77 88 99 110 121 132 143 154 165 176 187 198 209 220 231...
*13 26 39 52 65 78 91 104 117 130 143 156 169 182 195 208 221 234 247 260 273...
*17 34 51 68 85 102 119 136 153 170 187 204 221 238 255 272 289 306 323 340 357...
*19 38 57 76 95 114 133 152 171 190 209 228 247 266 285 304 323 342 361 380 399

Seuraavassa kappaleessa tulostetaan lista alkuluvuista 2-19 ja niiden perusteella lasketaan neliojuuri (sqrt()), neliöjuuren kokonaisosa (isqrt), näiden erotuksena neliöjuuren desimaalit (decimalpart), kerrotaan desimaalit kokonaisluvuksi ja sen perusteella tulostetaan neliöjuuren desimaalit desimaalina (decimal) ja heksana (hex). Näistä heksaluvuilla on täytetty state[] taulu.

  id=0;
  for(c=2; c<=19; c++) {
    if(primes[c] == 1) {
      int isqrt;
      fprintf(stdout,"id:%d", id);
      fprintf(stdout,", c:%2d", c);
      fprintf(stdout,", sqrt:%f", sqrt(c));
      isqrt = (int)sqrt(c);
      fprintf(stdout,", isqrt:%2d", isqrt);
      double decimalpart = sqrt(c)-isqrt;
      fprintf(stdout,", decimalpart:%f",
          decimalpart);
      fprintf(stdout,", decimalpart*16^8:%017f",
          decimalpart*pow(16,8));
      fprintf(stdout,", decimal:%010lu",
          (long)(decimalpart*pow(16,8)));
      fprintf(stdout,", hex:%08lx",
          (long)(decimalpart*pow(16,8)));
      fprintf(stdout,"\n");
      id++;
    }
  }

Edellinen kappale tulostaa seuraavan taulun: rivillä on alkuluku (c), sen neliöjuuri (sqrt), neliöjuuren kokonaisosa (isqrt), neliöjuuren desimaaliosa (decimalpart), edellinen kerrottuna kokonaisluvuksi, tulos desimaaleina ja heksana.

id:0, c: 2, sqrt:1.414214, isqrt: 1, decimalpart:0.414214, decimalpart*16^8:1779033703.952100, decimal:1779033703, hex:6a09e667
id:1, c: 3, sqrt:1.732051, isqrt: 1, decimalpart:0.732051, decimalpart*16^8:3144134277.518717, decimal:3144134277, hex:bb67ae85
id:2, c: 5, sqrt:2.236068, isqrt: 2, decimalpart:0.236068, decimalpart*16^8:1013904242.994461, decimal:1013904242, hex:3c6ef372
id:3, c: 7, sqrt:2.645751, isqrt: 2, decimalpart:0.645751, decimalpart*16^8:2773480762.371540, decimal:2773480762, hex:a54ff53a
id:4, c:11, sqrt:3.316625, isqrt: 3, decimalpart:0.316625, decimalpart*16^8:1359893119.679298, decimal:1359893119, hex:510e527f
id:5, c:13, sqrt:3.605551, isqrt: 3, decimalpart:0.605551, decimalpart*16^8:2600822924.168921, decimal:2600822924, hex:9b05688c
id:6, c:17, sqrt:4.123106, isqrt: 4, decimalpart:0.123106, decimalpart*16^8:0528734635.981472, decimal:0528734635, hex:1f83d9ab
id:7, c:19, sqrt:4.358899, isqrt: 4, decimalpart:0.358899, decimalpart*16^8:1541459225.076145, decimal:1541459225, hex:5be0cd19

Sitten alkuluvut 2-311 k256[] taulua varten: käsittely on oikeastaan kopio edellisestä paitsi että käytetään kuutiojuurta: lasketaan kuutiojuuri, sen kokonaisosa, niistä desimaaliosa, kerrotaan desimaaliosa kokonaisiksi, ja tulostetaan desimaaliosa desimaalina ja heksana.

  id=0;
  for(c=2; c<=311; c++) {
    if(primes[c] == 1) {
      int icbrt;
      fprintf(stdout,"id:%d", id);
      fprintf(stdout,", c:%3d", c);
      fprintf(stdout,", cbrt:%f", cbrt(c));
      icbrt = (int)cbrt((double)c);
      fprintf(stdout,", icbrt:%2d", icbrt);
      double decimalpart = cbrt(c)-icbrt;
      fprintf(stdout,", decimalpart:%f",
          decimalpart);
      fprintf(stdout,", decimalpart*16^8:%017f",
          decimalpart*pow(16,8));
      fprintf(stdout,", decimal:%010lu",
          (long)(decimalpart*pow(16,8)));
      fprintf(stdout,", hex:%08lx",
          (long)(decimalpart*pow(16,8)));
      //if((long)(decimalpart*pow(16,8))
      //    == k256[id])
	//fprintf(stdout,"ok");
      fprintf(stdout,"\n");
      id++;
    }
  }

Edellisen tuloste:

id:0, c:  2, cbrt:1.259921, icbrt: 1, decimalpart:0.259921, decimalpart*16^8:1116352408.840466, decimal:1116352408, hex:428a2f98
id:1, c:  3, cbrt:1.442250, icbrt: 1, decimalpart:0.442250, decimalpart*16^8:1899447441.140371, decimal:1899447441, hex:71374491
id:2, c:  5, cbrt:1.709976, icbrt: 1, decimalpart:0.709976, decimalpart*16^8:3049323471.923053, decimal:3049323471, hex:b5c0fbcf
id:3, c:  7, cbrt:1.912931, icbrt: 1, decimalpart:0.912931, decimalpart*16^8:3921009573.506011, decimal:3921009573, hex:e9b5dba5
id:4, c: 11, cbrt:2.223980, icbrt: 2, decimalpart:0.223980, decimalpart*16^8:0961987163.950329, decimal:0961987163, hex:3956c25b
id:5, c: 13, cbrt:2.351335, icbrt: 2, decimalpart:0.351335, decimalpart*16^8:1508970993.711027, decimal:1508970993, hex:59f111f1
id:6, c: 17, cbrt:2.571282, icbrt: 2, decimalpart:0.571282, decimalpart*16^8:2453635748.683981, decimal:2453635748, hex:923f82a4
id:7, c: 19, cbrt:2.668402, icbrt: 2, decimalpart:0.668402, decimalpart*16^8:2870763221.853235, decimal:2870763221, hex:ab1c5ed5
id:8, c: 23, cbrt:2.843867, icbrt: 2, decimalpart:0.843867, decimalpart*16^8:3624381080.636766, decimal:3624381080, hex:d807aa98
id:9, c: 29, cbrt:3.072317, icbrt: 3, decimalpart:0.072317, decimalpart*16^8:0310598401.271248, decimal:0310598401, hex:12835b01
id:10, c: 31, cbrt:3.141381, icbrt: 3, decimalpart:0.141381, decimalpart*16^8:0607225278.308178, decimal:0607225278, hex:243185be
id:11, c: 37, cbrt:3.332222, icbrt: 3, decimalpart:0.332222, decimalpart*16^8:1426881987.835932, decimal:1426881987, hex:550c7dc3
id:12, c: 41, cbrt:3.448217, icbrt: 3, decimalpart:0.448217, decimalpart*16^8:1925078388.947197, decimal:1925078388, hex:72be5d74
id:13, c: 43, cbrt:3.503398, icbrt: 3, decimalpart:0.503398, decimalpart*16^8:2162078206.230814, decimal:2162078206, hex:80deb1fe
id:14, c: 47, cbrt:3.608826, icbrt: 3, decimalpart:0.608826, decimalpart*16^8:2614888103.147570, decimal:2614888103, hex:9bdc06a7
id:15, c: 53, cbrt:3.756286, icbrt: 3, decimalpart:0.756286, decimalpart*16^8:3248222580.810200, decimal:3248222580, hex:c19bf174
id:16, c: 59, cbrt:3.892996, icbrt: 3, decimalpart:0.892996, decimalpart*16^8:3835390401.620871, decimal:3835390401, hex:e49b69c1
id:17, c: 61, cbrt:3.936497, icbrt: 3, decimalpart:0.936497, decimalpart*16^8:4022224774.219959, decimal:4022224774, hex:efbe4786
id:18, c: 67, cbrt:4.061548, icbrt: 4, decimalpart:0.061548, decimalpart*16^8:0264347078.545116, decimal:0264347078, hex:0fc19dc6
id:19, c: 71, cbrt:4.140818, icbrt: 4, decimalpart:0.140818, decimalpart*16^8:0604807628.467480, decimal:0604807628, hex:240ca1cc
id:20, c: 73, cbrt:4.179339, icbrt: 4, decimalpart:0.179339, decimalpart*16^8:0770255983.348312, decimal:0770255983, hex:2de92c6f
id:21, c: 79, cbrt:4.290840, icbrt: 4, decimalpart:0.290840, decimalpart*16^8:1249150122.432236, decimal:1249150122, hex:4a7484aa
id:22, c: 83, cbrt:4.362071, icbrt: 4, decimalpart:0.362071, decimalpart*16^8:1555081692.739288, decimal:1555081692, hex:5cb0a9dc
id:23, c: 89, cbrt:4.464745, icbrt: 4, decimalpart:0.464745, decimalpart*16^8:1996064986.511982, decimal:1996064986, hex:76f988da
id:24, c: 97, cbrt:4.594701, icbrt: 4, decimalpart:0.594701, decimalpart*16^8:2554220882.931259, decimal:2554220882, hex:983e5152
id:25, c:101, cbrt:4.657010, icbrt: 4, decimalpart:0.657010, decimalpart*16^8:2821834349.178532, decimal:2821834349, hex:a831c66d
id:26, c:103, cbrt:4.687548, icbrt: 4, decimalpart:0.687548, decimalpart*16^8:2952996808.597584, decimal:2952996808, hex:b00327c8
id:27, c:107, cbrt:4.747459, icbrt: 4, decimalpart:0.747459, decimalpart*16^8:3210313671.745834, decimal:3210313671, hex:bf597fc7
id:28, c:109, cbrt:4.776856, icbrt: 4, decimalpart:0.776856, decimalpart*16^8:3336571891.240852, decimal:3336571891, hex:c6e00bf3
id:29, c:113, cbrt:4.834588, icbrt: 4, decimalpart:0.834588, decimalpart*16^8:3584528711.574383, decimal:3584528711, hex:d5a79147
id:30, c:127, cbrt:5.026526, icbrt: 5, decimalpart:0.026526, decimalpart*16^8:0113926993.875053, decimal:0113926993, hex:06ca6351
id:31, c:131, cbrt:5.078753, icbrt: 5, decimalpart:0.078753, decimalpart*16^8:0338241895.039284, decimal:0338241895, hex:14292967
id:32, c:137, cbrt:5.155137, icbrt: 5, decimalpart:0.155137, decimalpart*16^8:0666307205.276646, decimal:0666307205, hex:27b70a85
id:33, c:139, cbrt:5.180101, icbrt: 5, decimalpart:0.180101, decimalpart*16^8:0773529912.359966, decimal:0773529912, hex:2e1b2138
id:34, c:149, cbrt:5.301459, icbrt: 5, decimalpart:0.301459, decimalpart*16^8:1294757372.354557, decimal:1294757372, hex:4d2c6dfc
id:35, c:151, cbrt:5.325074, icbrt: 5, decimalpart:0.325074, decimalpart*16^8:1396182291.615566, decimal:1396182291, hex:53380d13
id:36, c:157, cbrt:5.394691, icbrt: 5, decimalpart:0.394691, decimalpart*16^8:1695183700.545647, decimal:1695183700, hex:650a7354
id:37, c:163, cbrt:5.462556, icbrt: 5, decimalpart:0.462556, decimalpart*16^8:1986661051.236202, decimal:1986661051, hex:766a0abb
id:38, c:167, cbrt:5.506878, icbrt: 5, decimalpart:0.506878, decimalpart*16^8:2177026350.280975, decimal:2177026350, hex:81c2c92e
id:39, c:173, cbrt:5.572055, icbrt: 5, decimalpart:0.572055, decimalpart*16^8:2456956037.080109, decimal:2456956037, hex:92722c85
id:40, c:179, cbrt:5.635741, icbrt: 5, decimalpart:0.635741, decimalpart*16^8:2730485921.300552, decimal:2730485921, hex:a2bfe8a1
id:41, c:181, cbrt:5.656653, icbrt: 5, decimalpart:0.656653, decimalpart*16^8:2820302411.735386, decimal:2820302411, hex:a81a664b
id:42, c:191, cbrt:5.758965, icbrt: 5, decimalpart:0.758965, decimalpart*16^8:3259730800.816296, decimal:3259730800, hex:c24b8b70
id:43, c:193, cbrt:5.778997, icbrt: 5, decimalpart:0.778997, decimalpart*16^8:3345764771.024734, decimal:3345764771, hex:c76c51a3
id:44, c:197, cbrt:5.818648, icbrt: 5, decimalpart:0.818648, decimalpart*16^8:3516065817.839592, decimal:3516065817, hex:d192e819
id:45, c:199, cbrt:5.838272, icbrt: 5, decimalpart:0.838272, decimalpart*16^8:3600352804.333588, decimal:3600352804, hex:d6990624
id:46, c:211, cbrt:5.953342, icbrt: 5, decimalpart:0.953342, decimalpart*16^8:4094571909.341568, decimal:4094571909, hex:f40e3585
id:47, c:223, cbrt:6.064127, icbrt: 6, decimalpart:0.064127, decimalpart*16^8:0275423344.198177, decimal:0275423344, hex:106aa070
id:48, c:227, cbrt:6.100170, icbrt: 6, decimalpart:0.100170, decimalpart*16^8:0430227734.721970, decimal:0430227734, hex:19a4c116
id:49, c:229, cbrt:6.118033, icbrt: 6, decimalpart:0.118033, decimalpart*16^8:0506948616.317406, decimal:0506948616, hex:1e376c08
id:50, c:233, cbrt:6.153449, icbrt: 6, decimalpart:0.153449, decimalpart*16^8:0659060556.873276, decimal:0659060556, hex:2748774c
id:51, c:239, cbrt:6.205822, icbrt: 6, decimalpart:0.205822, decimalpart*16^8:0883997877.881279, decimal:0883997877, hex:34b0bcb5
id:52, c:241, cbrt:6.223084, icbrt: 6, decimalpart:0.223084, decimalpart*16^8:0958139571.772606, decimal:0958139571, hex:391c0cb3
id:53, c:251, cbrt:6.307994, icbrt: 6, decimalpart:0.307994, decimalpart*16^8:1322822218.887722, decimal:1322822218, hex:4ed8aa4a
id:54, c:257, cbrt:6.357861, icbrt: 6, decimalpart:0.357861, decimalpart*16^8:1537002063.466370, decimal:1537002063, hex:5b9cca4f
id:55, c:263, cbrt:6.406959, icbrt: 6, decimalpart:0.406959, decimalpart*16^8:1747873779.838661, decimal:1747873779, hex:682e6ff3
id:56, c:269, cbrt:6.455315, icbrt: 6, decimalpart:0.455315, decimalpart*16^8:1955562222.366940, decimal:1955562222, hex:748f82ee
id:57, c:271, cbrt:6.471274, icbrt: 6, decimalpart:0.471274, decimalpart*16^8:2024104815.262074, decimal:2024104815, hex:78a5636f
id:58, c:277, cbrt:6.518684, icbrt: 6, decimalpart:0.518684, decimalpart*16^8:2227730452.632576, decimal:2227730452, hex:84c87814
id:59, c:281, cbrt:6.549912, icbrt: 6, decimalpart:0.549912, decimalpart*16^8:2361852424.103092, decimal:2361852424, hex:8cc70208
id:60, c:283, cbrt:6.565414, icbrt: 6, decimalpart:0.565414, decimalpart*16^8:2428436474.138229, decimal:2428436474, hex:90befffa
id:61, c:293, cbrt:6.641852, icbrt: 6, decimalpart:0.641852, decimalpart*16^8:2756734187.869183, decimal:2756734187, hex:a4506ceb
id:62, c:307, cbrt:6.745997, icbrt: 6, decimalpart:0.745997, decimalpart*16^8:3204031479.698341, decimal:3204031479, hex:bef9a3f7
id:63, c:311, cbrt:6.775169, icbrt: 6, decimalpart:0.775169, decimalpart*16^8:3329325298.888462, decimal:3329325298, hex:c67178f2

Tulosteessa on alkuluku (c), kuutiojuuri (cbrt), kuutiojuuren kokonaisosa (icbrt), desimaaliosa, desimaaliosa kerrottuna kokonaisosaksi, edellinen desimaalina ja heksana. Heksaarvoilla on täytetty taulu k256[].

Vielä ohjelma kokonaisuudessaan:

void main(int argc,char *argv[])
{
  int c, d, count, id;
  char primes[400];

  for(c=0; c<sizeof(primes); c++)
    primes[c] = 1;

  primes[0] = 0;
  primes[1] = 0;

  for(c=2; c<=sqrt(sizeof(primes)-1); c++) {
    if(primes[c] == 1) {
      fprintf(stdout,"*%d", c);
      count = 0;
      for(d=c*2; d<sizeof(primes); d+=c) {
        primes[d] = 0;
        if(count++ < 20)
          fprintf(stdout," %d", d);
      }
      if(count > 20)
        fprintf(stdout,"...");
      fprintf(stdout,"\n");
    }
  }

  fprintf(stdout,"\n");

  id = 0;
  for(c=2; c<=19; c++) {
    if(primes[c] == 1) {
      int isqrt;
      fprintf(stdout,"id:%d", id);
      fprintf(stdout,", c:%2d", c);
      fprintf(stdout,", sqrt:%f", sqrt(c));
      isqrt = (int)sqrt(c);
      fprintf(stdout,", isqrt:%2d", isqrt);
      double decimalpart = sqrt(c)-isqrt;
      fprintf(stdout,", decimalpart:%f",
          decimalpart);
      fprintf(stdout,", decimalpart*16^8:%017f",
          decimalpart*pow(16,8));
      fprintf(stdout,", decimal:%010lu",
          (long)(decimalpart*pow(16,8)));
      fprintf(stdout,", hex:%08lx",
          (long)(decimalpart*pow(16,8)));
      fprintf(stdout,"\n");
      id++;
    }
  }

  fprintf(stdout,"\n");

  id = 0;
  for(c=2; c<=311; c++) {
    if(primes[c] == 1) {
      int icbrt;
      fprintf(stdout,"id:%d", id);
      fprintf(stdout,", c:%3d", c);
      fprintf(stdout,", cbrt:%f", cbrt(c));
      icbrt = (int)cbrt((double)c);
      fprintf(stdout,", icbrt:%2d", icbrt);
      double decimalpart = cbrt(c)-icbrt;
      fprintf(stdout,", decimalpart:%f",
          decimalpart);
      fprintf(stdout,", decimalpart*16^8:%017f",
          decimalpart*pow(16,8));
      fprintf(stdout,", decimal:%010lu",
          (long)(decimalpart*pow(16,8)));
      fprintf(stdout,", hex:%08lx",
          (long)(decimalpart*pow(16,8)));
      fprintf(stdout,"\n");
      id++;
    }
  }
}

RESSU 1.8

Ressun edellisessä versiossa(1.7) oli ongelma että myös perusriveistä laskettiin teoreettisia satunnaisbittejä. Tässä pyritään laskemaan mukaan ainoastaan poikkeukset säännöstä. Jos esimerkiksi perusketjut ovat kuuden merkin pituisia, tämän pitäisi laskea vain kuudesta poikkeavat ketjut, eli seiskat ja viitoset kuten edellisen postin esimerkissä. Tällä tavoin teoreettisten satunnaisbittien lukumäärä on lähempänä oikeata.

Tässä varsinainen uusi koodi kokonaisuudessaan, muutetut rivit kuvataan postin lopussa: Kuvaus koodista löytyy edellisestä postista http://moijari.com/?p=798.

void ressu_genbytes(int size, unsigned
    char *buffer) /* (c) JariK 2013-2020 v1.8 */
{
  int c, d, e, f, g,
    byte, prevbyte,
    clockbytes = 0, rndbits = 0,
    chainbytes = 0, oldindex = 0, oldcount;
  unsigned char olddata[10];

  memset(olddata, 0, sizeof(olddata));
  prevbyte=clockbyte();
  f=0;

  for(c=0; c<8 || c%8!=0 ||
      clockbytes<2000 ||
      rndbits < 8*size; c++) {
    for(d=0; d<size; d++) {
      e = buffer[d];
      e = ((e&0x80)>>7) | ((e&0x7f)<<1);
      byte = clockbyte();
      buffer[d] = e^byte;
      if(prevbyte != byte) {
        prevbyte = byte;
        olddata[oldindex] = chainbytes;
        oldindex = (oldindex + 1)
            % sizeof(olddata);
        oldcount = 0;
        for(g=0; g < sizeof(olddata); g++) {
          if(olddata[g] == chainbytes)
            oldcount++;
        }
        if(oldcount < 4)
          rndbits++;

        clockbytes += chainbytes;
        chainbytes = 0;
      } // if(prevbyte
      chainbytes++;
    } // for(d=0
    for(d=0; d<size; d++) {
      f = (f+buffer[d])%size;
      e = buffer[d];
      buffer[d] = buffer[f];
      buffer[f] = e;
    } // for(d=0
  } // for(c=0
}

Lisätyt muuttujat: chainbytes sisältää tämän ketjun pituuden, oldindex kertoo, mihin “positioon” olddata taulussa seuraava ketjun pituus laitetaan, oldcount kenttään lasketaan tämänpituisten ketjujen lukumäärä ja olddata taulukkoon kirjoitetaan viimeisten kymmenen ketjun pituudet.

  int chainbytes = 0, oldindex = 0, oldcount;
  unsigned char olddata[10];

Tyhjennetään olddata taulukko:

  memset(olddata, 0, sizeof(olddata));

Olddata taulussa pidetään yllä kymmentä viimeistä ketjun pituutta, ja tässä uusi ketjun pituus lisätään tauluun: Oldindex kentän arvoa kasvatetaan aina uuden pituuden lisäyksen jälkeen yhdellä. Jos oldindex menee ympäri, aloitetaan taas nollasta.

  olddata[oldindex] = chainbytes;
  oldindex = (oldindex + 1) % sizeof(olddata);

Lasketaan kuinka monta edellisen pituista lohkoa taulussa on:

  oldcount = 0;
  for(g=0; g<sizeof(olddata); g++) {
    if(olddata[g] == chainbytes)
      oldcount++;
  }

Lisätään bitti satunnaisuuteen vain jos esiintymiä on vähemmän kuin 4:

  if(oldcount < 4)
    rndbits++;

Lisätään tämän ketjun pituus kokonaispituuteen ja nollataan tämän ketjun pituus:

  clockbytes += chainbytes;
  chainbytes = 0;

Lasketaan tämän ketjun pituutta.

  chainbytes++;

Apuohjelmat ja tarkemman kuvauksen saat tietysti edellisestä postista.

Uusi versio Ressu:sta (v 1.7)

Tässä vielä uusi versio ressu generaattorista: uudessa versiossa ei ole enää aiempien versioiden b-muuttujaa eikä siihen liittyvää laskentaa, eli ei tarvita erillistä pääohjelmaa. Edellisessä versiossa oli useampia tapoja laskea teoreettisia bittejä, niistä on vain yksi jäljellä.

Ressufunktio muodostuu pääluupista, jonka alla on kaksi pienempää luuppia. Pääluuppi (for(c=) sanelee että kaikki puskurin bitit pitää kiertää vähintään kerran (c<8), että generaattori ei lopeta ennenkuin kaikki merkin bitit on käsitelty (c%8!=0), eli generaattorin katkokohdassa on käsitelty kaikki puskurin merkkien kahdeksan bittiä. Kellosta tulevia merkkejä pitää käsitellä vähintään 2000 kappaletta (clockbytes<2000). Lisäksi varmistetaan että teoreettisia bittejä on vähintään yksi jokaiselle generoidulle bitille (rndbits < 8*size).

Pääluupin alla on kaksi aliluuppia, joista ensimmäinen järjestelee merkin bittejä siten että ylin bitti merkissä kierretään alimmaksi ja loput bitit siirretään yhden bitin verran ylemmäksi. Lisäksi bitteihin xor:ataan yksi merkki kellosarjaa. Vuorotellen kukin bitti siirtyy alimmaiseksi ja näin kahdeksan kierroksen aikana käydään kaikki bitit läpi. Ensimmäinen aliluuppi käsittelee kaikki puskurin merkit yksi merkki kerrallaan.

Lisäksi ensimmäinen aliluuppi laskee teoreettisia satunnaisuusbittejä. Teoreettisten satunnaisbittien määrää (rndbits) lisätään yhdellä aina kun kellosarjasta tulleen merkin arvo muuttuu. Seuraavassa bittisarjassa on yksi teoreettinen bitti numeroille 46, 47, 48, 49, 4a, 4b, 4c, 4d, 4e ja 4f, eli yhteensä 10 teoreettista bittiä.

4646464646464747474747474848484848484949494949494a4a4a4a4a4a4b4b4b4b4b4b4b4c4c4c4c4c4d4d4d4d4d4d4e4e4e4e4e4f4f4f4f4f4f

Satunnaisuus edellisessä kellojonossa tulee siitä että eri merkkien lukumäärä vaihtelee, seuraavassa kunkin merkin määrä kellojonossa: F muuttuja tekee sen, että jos yksikin f:ään lisättävä merkki muuttuu kaikki loppumerkit muuttuvat erilaisiksi. Voi muuten väittää että seuraavan taulukon kuutosriveissä ei ole kovin paljon satunnaista, että satunnaisuus tulee pääasiassa 7 ja 5 pituuksisista riveistä, eli poikkeuksista sääntöön.

464646464646    6
474747474747    6
484848484848    6
494949494949    6
4a4a4a4a4a4a    6
4b4b4b4b4b4b4b  7
4c4c4c4c4c      5
4d4d4d4d4d4d    6
4e4e4e4e4e      5
4f4f4f4f4f4f    6

Toinen aliluuppi käy myös kaikki merkit läpi ja vaihtaa jokaisen merkin (muuttuja d) satunnaisen puskurin merkin kanssa (muuttuja f).

Tässä varsinainen koodi pääfunktiolle.

void ressu_genbytes(int size,
        unsigned char *buffer) /* JariK 2020 v1.7 */
{
  int c, d, e, f,
    byte, prevbyte,
    clockbytes = 0, rndbits = 0;

  prevbyte = clockbyte();
  f = 0;

  for(c=0; c < 8 || c%8 != 0 ||
      clockbytes < 2000 ||
      rndbits < 8*size; c++) {
    for(d=0; d < size; d++) {
      e = buffer[d];
      e = ((e & 0x80) >> 7) |
          ((e & 0x7f) << 1);
      byte = clockbyte();
      buffer[d] = e ^ byte;
      if(prevbyte != byte) {
        prevbyte = byte;
        rndbits++;
      }
      clockbytes++;
    }
    for(d=0; d < size; d++) {
      f = (f + buffer[d]) % size;
      e = buffer[d];
      buffer[d] = buffer[f];
      buffer[f] = e;
    }
  }
}

Tässä vielä käytetty kellonluku:

unsigned char clockbyte() /* JariK 2013-2020 */
{
  struct timeval tv;

  gettimeofday(&tv,NULL);

  return(tv.tv_usec & 0xff);
}

Seuraavassa ohjelman apufunktiot.

#define RESSUCNT 128
#define RESSU_CLEAR 2

unsigned char ressu_bytes[RESSUCNT];
int ressu_byte = 999999999;
int ressu_cnt = RESSUCNT;

void ressu_init()
{
}

int ressu_genbyte()
{
  if(ressu_byte>=ressu_cnt) {
#ifdef RESSU_CLEAR
    memset(ressu_bytes,0,ressu_cnt);
#else
    if(ressu_byte == 999999999)
      memset(ressu_bytes, 0, ressu_cnt);
#endif
    ressu_genbytes(ressu_cnt,
        ressu_bytes);
    ressu_byte=0;
  }
  return(ressu_bytes[ressu_byte++]);
}

void ressu_clear()
{
    memset(ressu_bytes, 0, ressu_cnt);
    ressu_byte = 999999998;
}

int ressu_genbyte_limit(int limit)
{
  int c;

  while((c = ressu_genbyte())>
      (256 / limit) * limit);
  return(c % limit);
}

int ressu_genshort()
{
  return(ressu_genbyte()|
         ressu_genbyte() << 8);
}

int ressu_genshort_limit(int limit)
{
  int c;

  while((c = ressu_genshort())>
      (65536 / limit) * limit);
  return(c % limit);
}

int ressu_gen32bits()
{
  return(ressu_genbyte()|
         ressu_genbyte() << 8|
         ressu_genbyte() << 16|
         ressu_genbyte() << 24);
}

void ressu_genbuffer(int size, unsigned char *buffer)
{
  int c;

  for(c = 0; c < size; c++)
    buffer[c] ^= ressu_genbyte();
}

SHA256 tiivisteen laskentaohjelma

Tässä tiivisteohjelma edellista Fort satunnaisbittigeneraattoria varten.

Alun definet määrittelevät Fort-ohjelman käyttämille HashInit, HashUpdate ja HashFinal funktioille todelliset nimet. Lisäksi määritellään HashName, joka tulostetaan fort_init:in alussa ja HashLen joka sisältää tiivisteen pituuden.

Sitten typedef:it määrittelevät uudet tyypit IUBYTE, joka on 8 bittiä (1 merkkiä) pitkä., IUWORD, joka on 32 bittiä (4 merkkiä) pitkä ja IULONG, joka on 64 bittiä (8 merkkiä) pitkä.

Seuraava kappale kuvaa SHA256 yhteysalueen. Yhteysalueen jälkeen on ensimmäinen koodi init, joka alustaa SHA yhteysalueen. Count kentässä on tähänastisten merkkien määrä yhteysalueessa eli ohjelman suorituksen alussa nolla. State[] kenttien arvot ovat 8 ensimmäisen alkuluvun (2-19) neliöjuuren desimaaliosa 32 bittisenä lukuna.

#define HashName   "SHA256"
#define HashInit   SHA256Init
#define HashUpdate SHA256Update
#define HashFinal  SHA256Final
#define HashLen    32
#define HashCtx    SHA256_CONTEXT

typedef unsigned char IUBYTE;
typedef unsigned int IUWORD;
typedef unsigned long long IULONG;

typedef struct {
    IUWORD state[8];
    IULONG count;
    IUBYTE buffer[64];
} SHA256_CONTEXT;

void SHA256Init(SHA256_CONTEXT *sha256)
{
  sha256->state[0] = 0x6a09e667;
  sha256->state[1] = 0xbb67ae85;
  sha256->state[2] = 0x3c6ef372;
  sha256->state[3] = 0xa54ff53a;
  sha256->state[4] = 0x510e527f;
  sha256->state[5] = 0x9b05688c;
  sha256->state[6] = 0x1f83d9ab;
  sha256->state[7] = 0x5be0cd19;

  sha256->count = 0;
}

Seuraavana update funktio: Aliohjelma jakaa asiakkaan toimittaman datan 64 merkin pituisiin lohkoihin ja kutsuu transform rutiinia jokaisella lohkolla. Lohkossa voi olla ylijäämämerkkejä edellisestä update kutsusta. Samoin tämän kutsun jälkeen voi jäädä ylimääräisiä merkkejä. Ylimääräiset merkit talletetaan yhteysalueen buffer taulukkoon.

void SHA256Update(SHA256_CONTEXT *sha256,
    unsigned char *data, int len)
{
  int i,j;

  // overflow from previous update
  j = (int)sha256->count & 63;

  sha256->count+=len;

  // if full blocks available
  if((j+len)>63) {

    // last bytes of next block to buffer
    memcpy(&sha256->buffer[j],data,64-j); 

    // first full block from buffer
    SHA256Transform(sha256->state,
        sha256->buffer);

    // rest of the full blocks from data
    for (i = 64 - j ; i + 63 < len; i += 64) {
      SHA256Transform(sha256->state, &data[i]);
    }

    // rest of the bytes to beginning of buffer
    j = 0;
  } else
    // copy all data from beginning of data
    i = 0;

  // overflow from this update to buffer
  memcpy(&sha256->buffer[j],&data[i],len-i);
}

Transform funktion käyttämä taulukko: taulun arvot ovat 64 ensimmäisen alkuluvun (2-311) kuutiojuuren desimaaliosa 32 bittisenä lukuna.

IUWORD k256[64] = {
  0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
  0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
  0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
  0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
  0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
  0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
  0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
  0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
  0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
  0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
  0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
  0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
  0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
  0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
  0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
  0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};

Transform:in käyttämät makrot

#define RR32(word,bits) ( ((word) >> (bits)) | \
        ((word) << (32 - (bits))) )
#define RS32(word,bits) ( ((word) >> (bits)) )

Transform funktio käsittelee yhden lohkon kutsujan toimittamaa tietoa ja päivittää tila kenttiä lohkon sisältämän tiedon perusteella.

Ensimmäinen “for(i=0” luuppi siirtää datalohkon (buffer) w taulukon 16 ensimmäiseen IUWORD:iin. Seuraava “for(i=16” luuppi muodostaa loput 64 IUWORD:stä yhdistelemällä aiempia rivejä.

Seuraavan kappale siirtää tiivistetoiminnon (hash) tilan a, b,c … h muuttujiin. Seuraava “for(i=64” luuppi tekee a-h, w[] ja k256 kentille laskentaa ja siirtelyä muodostaen uudet a-h kentät. Kun kaikki 64 kierrosta on tehty yhdistetään a-h kentät taas state[] kenttiin.

Sitten palataan laskemaan seuraavaa lohkoa.

void SHA256Transform(IUWORD state[8], IUBYTE buffer[64])
{
  int i;

  IUWORD w[64], s0;
  IUWORD a, b, c, d, e, f, g, h;
  IUWORD maj, t2, s1;
  IUWORD ch, t1;

  // fill first 16 w[] entries
  for(i=0;i<16;i++) {
    w[i] =
      (IUWORD)buffer[i*4+0] << 24 |
      (IUWORD)buffer[i*4+1] << 16 |
      (IUWORD)buffer[i*4+2] << 8  |
      (IUWORD)buffer[i*4+3];
  }

  // fill entries 16-63 of w[]
  for(i=16;i<64;i++) {
    s0 = (RR32(w[i-15],7)) ^ 
         (RR32(w[i-15],18)) ^
         (RS32(w[i-15],3));
    s1 = (RR32(w[i-2],17)) ^
         (RR32(w[i-2],19)) ^
         (RS32(w[i-2],10));
    w[i] = w[i-16] + s0 + w[i-7] + s1;
  }

  // copy state
  a = state[0];
  b = state[1];
  c = state[2];
  d = state[3];
  e = state[4];
  f = state[5];
  g = state[6];
  h = state[7];

  for(i=0;i<64;i++) {
    s0 = (RR32(a,2)) ^ (RR32(a,13)) ^
           (RR32(a,22));
    maj = (a & b) ^ (a & c) ^ (b & c);
    t2 = s0 + maj;
    s1 = (RR32(e,6)) ^ (RR32(e,11)) ^
           (RR32(e,25));
    ch = (e & f) ^ ((~ e) & g);
    t1 = h + s1 + ch + k256[i] + w[i];

    h = g;
    g = f;
    f = e;
    e = d + t1;
    d = c;
    c = b;
    b = a;
    a = t1 + t2;
  }

  // add compression result to state
  state[0] += a;
  state[1] += b;
  state[2] += c;
  state[3] += d;
  state[4] += e;
  state[5] += f;
  state[6] += g;
  state[7] += h;
}

Maj funktion testitaulu on seuraavankaltainen: a b ja c ovat parametrit ja m rivillä on maj:n arvo. Maj:ssa on bittiarvo, jota on eniten. Jos nollia on kolme tai kaksi, maj:n arvo on 0. Jos taas ykkösiä on kolme tai kaksi maj:n arvo on 1.

a01000011110001000011101001001111
b00000101110001110000111001100001
c11101111011010100011111101010111
M01000111110001100011111001000111

Ch funktio taas taas poimii bitin kentän e mukaan f:stä tai g:stä. Jos bitti on 0 poimitaan g:n bitti, jos taas 1 poimitaan f:n bitti.

e00001000111000011100101011111001
f00011000010001101000101000101000
g11011110100111111100110001011110
C11011110010111101000111000101110

Final funktio käsittelee viimeisen update kutsun ylijääneen osalohkon lisäten materiaalin kokonaismerkkien lukumäärän yhteysalueen count kentästä tiedon loppuun. LIsäksi funktio palauttaa SHA256 tiivisteen digest parametrissa.

void SHA256Final(unsigned char digest[32], SHA256_CONTEXT *sha256)
{
  int i,j;
  unsigned char filelenght[8];
  IULONG count;

  count=sha256->count << 3;
  SHA256Update(sha256,"\200",1);

  while((sha256->count & 63) != 56)
    SHA256Update(sha256,"\0",1);

  filelenght[0]=(unsigned char)
      (count >> 56)&0xff;
  filelenght[1]=(unsigned char)
      (count >> 48)&0xff;
  filelenght[2]=(unsigned char)
      (count >> 40)&0xff;
  filelenght[3]=(unsigned char)
      (count >> 32)&0xff;
  filelenght[4]=(unsigned char)
      (count >> 24)&0xff;
  filelenght[5]=(unsigned char)
      (count >> 16)&0xff;
  filelenght[6]=(unsigned char)
      (count >> 8 )&0xff;
  filelenght[7]=(unsigned char)
      (count&0xff);

  SHA256Update(sha256,filelenght,
      sizeof(filelenght));

  for(i=0,j=0;i<8;i++) {
    digest[j++]=(unsigned char)
        (sha256->state[i] >> 24)&0xff;
    digest[j++]=(unsigned char)
        (sha256->state[i] >> 16)&0xff;
    digest[j++]=(unsigned char)
        (sha256->state[i] >> 8)&0xff;
    digest[j++]=(unsigned char)
        (sha256->state[i] )&0xff;
  }
}

Pieni testipääohjelma:

int SHA256Test(char *string,int rounds,
      char *hashs) {
  int ok;
  char temp[3];
  unsigned char result[32];
  HashCtx hash;

  fprintf(stdout,"s=%s\n",string);
  fprintf(stdout,"h=%s\n",hashs);
  HashInit(&hash);
  for(int c=0;c<rounds;c++)
    HashUpdate(&hash,string,strlen(string));
  HashFinal(result,&hash);
  fprintf(stdout,"r=");
  ok=1;
  for(int c=0;c<sizeof(result);c++) {
    sprintf(temp,"%02x",result[c]);
    fprintf(stdout,"%s",temp);
    if(strncmp(temp,hashs+c*2,2))
      ok=0;
  }
  if(ok)
    fprintf(stdout,", ok");
  else
    fprintf(stdout,", error");
  fprintf(stdout,"\n");

  return(ok);
}

void main(int argc,char *argv[])
{
  int ok;

  fprintf(stdout,"sizeof(IUBYTE):%lu",
      sizeof(IUBYTE));
  fprintf(stdout,", sizeof(IUWORD):%lu",
      sizeof(IUWORD));
  fprintf(stdout,", sizeof(IULONG):%lu",
      sizeof(IULONG));
  fprintf(stdout,"\n");

SHA256Test("abc",1,"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
SHA256Test("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",1,"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1");
SHA256Test("a",1000000,"cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0");
SHA256Test("The quick brown fox jumps over the lazy dog",1,"d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592");
}

Fort, Fortunan kaltainen satunnaisbittigeneraattori

Fort:in päärakenne tallettaa satunnaisbittigeneraattorin lähteet (pool), joihin satunnaisuutta lisätään ja joiden pohjalta sitä generoidaan. Lähteitä on 32 kappaletta ja niiden muistirakenne sisältää satunnaismerkkien lukumäärän lähteessä ja hash toiminnon yhteysalueen (HashCtx).

struct fort_pool {
  int length;
  HashCtx pool;
};
struct fort_pool fort_pools[32];

Merkkien lukumäärää ensimmäisessä poolissa (0) käytetään sen selvittämiseksi onko lähteissä tarpeeksi satunnaisuutta seuraavaa avainnusta varten. Lähteen (tai puskurin) HashCtx yhteysalueeseen lisätään uusi ohjelmalle annettu satunnaisuus HashUpdate toiminnolla.

Satunnaisuus lisätään tapahtumilla, joita lisäävä pääfunktio on fort_add_random_event. Funktio lisää tapahtuman pool muuttujalla kerrottuun lähteeseen, satunnaisuuden ohjelmalähde kerrotaan source muuttujalla, seuraavan puskurin numeron laskentatapa kerrotaan muuttujalla mode ja satunnaismerkkien määrä merkkeinä on kentässä len ja varsinaiset satunnaismerkit ovat buf osoitteen osoittamassa puskurissa.

char fort_events_file[128] = "fortevents.deb";
int dump_events=0;

void fort_add_random_event(int *pool, int source, int mode, int len, unsigned char *buf)
{
  while(len > 1 && buf[len-1] == 0)
    len--;
  
  HashUpdate(&fort_pools[*pool].pool, buf, len);
  fort_pools[*pool].length+=len;

  if(dump_events<4*1048576) {
    FILE *fp1;
    if((fp1=fopen(fort_events_file,"a"))!=NULL) {
      fprintf(fp1,"id=%d",dump_events);
      fprintf(fp1,", source=%d",source);
      fprintf(fp1,", pool=%d",*pool);
      fprintf(fp1,", mode=%d",mode);
      fprintf(fp1,", len=%d",len);
      fprintf(fp1,", data=");
      for(int c=0;c<len;c++)
        fprintf(fp1,"%02x",buf[c]);
      fprintf(fp1,"\n");
      fflush(fp1);
      fclose(fp1);
      dump_events++;
    }
  }

  switch(mode) {
  case 1:
    (*pool)=((*pool)+1)%32;
    break;
  case 2:
    if((*pool)>=31 || fort_pools[*pool].length <
      fort_pools[*pool+1].length)
      (*pool)=0;
    else
      (*pool)=((*pool)+1)%32;
    break;
  case 3:
    break; // caller decides next pool                                                                                    
  case 4:
#ifdef RESSU
    (*pool)=ressu_genbyte_limit(32);
#endif
    break;
  }
}

Funktiossa satunnaisuus lisätään satunnaislukulähteeseen HashUpdate funktiolla, lasketaan satunnaismerkkien lukumäärää, tehdään ensimmäisistä satunnaislukutapahtumista listaa ja mode-kentän mukaan lasketaan seuraavan käytettävän satunnaislukulähteen numero.

Ensimmäiset tapahtumat (4*1048576=4M tapahtumaa) raportoidaan fort_events_file kentän sisältämään tiedostoon.

Mode voi saada arvot yhdestä neljään:
1:s moodilla satunnaislukulähdettä kasvatetaan aina yhdellä. Kun päästään lähteeseen 32 lähteiden kierto aloitetaan alusta siirtymällä takaisin lähteeseen nolla
2:s moodilla täytetään numeroltaan pienimpiä satunnaislukulähteistä nopeasti, eli täytetään ne, jotka on viimeisessä uudelleen avainnuksessa käytetty
3:s moodi ei tee pool muuttujalle mitään, kutsujan odotetaan päättelevän seuraavan satunnaislukulähteen
4:s seuraava satunnaislukulähde on satunnainen välillä 0-31.

ressu_genbyte_limit() on aiemmassa postissa.

Fortevents.deb tiedostoon raportoidaan seuraavankaltaisia tietueita: tietueissa on tietueen järjestysnumero ja kenttien arvot add event kutsusta. Tiedostosta voi esimerkiksi grep:llä poimia tietyn lähteen rivejä ja katsoa niistä miten niiden data-arvot vaihtelevat (grep source=23 fortevents.deb), eli sisältävätkö rivit todella ja riittävästi satunnaisuutta.

id=0, source=100, pool=4, mode=3, len=2, data=b4f4
id=1, source=22, pool=4, mode=2, len=2, data=d6f4
id=2, source=10, pool=5, mode=2, len=2, data=e9f4
id=3, source=11, pool=30, mode=2, len=2, data=0000
id=4, source=23, pool=6, mode=2, len=2, data=0ff5
id=5, source=14, pool=7, mode=2, len=2, data=16f5
id=6, source=15, pool=6, mode=2, len=2, data=0200
id=7, source=12, pool=8, mode=2, len=2, data=26f5
id=8, source=13, pool=17, mode=2, len=2, data=0000
id=9, source=10, pool=9, mode=2, len=2, data=33f5
id=10, source=11, pool=31, mode=2, len=2, data=0000
id=11, source=12, pool=10, mode=2, len=2, data=41f5
id=12, source=13, pool=18, mode=2, len=2, data=0000
id=13, source=26, pool=0, mode=2, len=2, data=4a00
id=14, source=12, pool=11, mode=2, len=2, data=66f5
id=15, source=13, pool=19, mode=2, len=2, data=0000

Nämä rivit ovat sisäisistä lähteistä tulevia satunnaisbittejä, niissä source kentän arvo on välillä 10-27. Sisäisissä lähteissä on kahta eri mallia, ensimmäisessä data on suoritushetken kellonajan mikrosekuntien 16 alinta bittiä (fort_add_random_event_time) ja toisessa mallissa satunnaisuus tulee funktion suoritusajan eli keston alimmista 16 bitistä (fort_add_random_event_timer_star ja fort_add_random_event_timer_do).

Jos ohjelmasi lähettää riittävästi satunnaistapahtumia, voit halutessasi poistaa sisäiset lähteet fort:ista poistamalla #define FORT_INTERNAL_EVENTS määrityksen. Alkuperäisen Fortunan kuvauksessa ei ole sisäisiä lähteitä. Ilman ohjelmasi satunnaisuustapahtumia fort tarvitsee joko ressun tai sisäiset tapahtumat mieluummin molemmat, että sen tuottamat satunnaisbitit ovat luotettavia.

Satunnaistapahtumien lisäämiseen on myös seuraavat apufunktiot, joilla voidaan mitata esimerkiksi funktion suoritusaika mikrosekunteina, ja käyttää siitä alimmat 16 bittiä satunnaisuuslähteenä:

void fort_add_random_event_timer_start(unsigned long *micros)
{
  *micros=getmicroseconds();
}

void fort_add_random_event_timer_do(int *pool, int source, int mode, unsigned long *micros)
{
  unsigned char temp[2];
  unsigned long l;

  l=getmicroseconds()-*micros;
  temp[0] = l & 0xff;                                                                                                                           
  temp[1] = l>>8 & 0xff;                                                                                                                                 
                                                                                                                                                         
  fort_add_random_event(pool, source, mode,
               sizeof(temp), temp);                                                                                         
}

Tällä tehdään kutsumispisteen kellonajasta 16 bittinen satunnaislukutapahtuma.

void fort_add_random_event_time(int *pool, int source, int mode)
{
  unsigned char temp[2];
  unsigned long l;

  l=getmicroseconds();
  temp[0] = l & 0xff;
  temp[1] = l>>8 & 0xff;

#ifdef RESSUEVENT
  ressu_genbuffer(sizeof(temp),temp);
#endif
                                                                                                                                 
  fort_add_random_event(pool, source, mode,
               sizeof(temp), temp);
}

Tällä funktiolla jaetaan pidempi satunnaismerkkijono useampaan puskuriin:

void fort_add_random_event_split(int *pool,
  int source, int mode, int len,
  unsigned char *buf, int size)
{
  int n;

  while(len!=0) {
    n=(size<len)? size:len;
    fort_add_random_event(pool, source,
      mode, n, buf);
    buf+=n;
    len-=n;
  }
}

Edellisten apufunktio:

unsigned long getmicroseconds()
{
  struct timeval tv;

  gettimeofday(&tv, NULL);

  return((unsigned long)tv.tv_sec*1000000 +
          tv.tv_usec);
}

Seuraava funktio muodostaa uuden fort_key avaimen edellisestä fort_key avaimesta, juoksevasta 16 merkkisestä numerosarjasta cvar ja tallettaa sen parametrina annettuun puskuriin (buf). Lisäksi kasvatetaan cvar numerosarjaa (inccvar). Fort_rekey funktiota käytetään myös satunnaisbittien tekemiseen. Funktion alussa ja lopussa on satunnaistapahtumakutsut tapahtuman muodostukselle kellon ajasta (fort_add_random_event_time) ja funktion suorituksen kestosta (fort_add_random_event_timer_start ja fort_add_random_event_timer_do). time_pool ja rekey_pool muuttujissa talletetaan seuraavan satunnaisuuspuskurin numero kellonaikatapahtumille ja suoritusaikatapahtumille. Micros muuttujan avulla lasketaan funktion suorituksen kesto. Add random funktioiden toinen kenttä kertoo lähteen eli source kentän tapahtumatiedostossa. Tässä kellonaika tapahtuman source on 16 ja suoritusaika tapahtuman source on 17.

#define FORT_INTERNAL_EVENTS 1
int fort_internal_events=1;
unsigned int fort_event_mode = 2;
unsigned char fort_key[HashLen];
unsigned char cvar[16];
int time_pool=0;
int rekey_pool=0;

void fort_rekey(char *buf)
{
  HashCtx hash;
#ifdef FORT_INTERNAL_EVENTS
  unsigned long micros;
  if(fort_internal_events) {
    fort_add_random_event_time(&time_pool,
         16, fort_event_mode);
    fort_add_random_event_timer_start(&micros);
  }
#endif

  hash_init(&hash);
  hash_update(&hash, fort_key, sizeof(fort_key));
  hash_update(&hash, (unsigned char *)&cvar,
      sizeof(cvar));
  hash_final(buf, &hash);
  inccvar();

  /* Forget hash */
  memset(&hash, 0, sizeof(hash));

#ifdef FORT_INTERNAL_EVENTS
  if(fort_internal_events)
    fort_add_random_event_timer_do(&rekey_pool,
        17, fort_event_mode, &micros);
#endif
}

Edellisen apufunktiot:

void inccvar()
{
  int c=0;

  /* 16 bytes, LSB first */
  while(++cvar[c]==0 && c<sizeof(cvar)-1)
    c++;
}

void clearcvar()
{
  int c;

  for(c=0; c<sizeof(cvar); c++)
    cvar[c]=0;
}

cvar on 16 merkkiä pitkä muuttuja ja aina sitä käytettäessä siihen lisätään yksi.

Seuraavalla funktiolla muodostetaan kutsujan toimittamaan puskuriin buf len merkkiä pitkä satunnaisbittisarja. Funktion alussa ja lopussa ovat standardit satunnaistapahtumien muodostuskutsut aika ja kesto tapahtumille. Tämän funktion antamia satunnaisbittejä pääset katsomaan >grep source=18 fortevents.deb ja >grep source=19 fortevents.deb komennoilla.

#define FORT_PSEUDO_LIMIT 1048576
int pseudo_pool=0;

void fort_pseudo_random_data(int len, unsigned char *buf)
{
  unsigned char tmp[HashLen];
  unsigned int n,blockbytes;
#ifdef FORT_INTERNAL_EVENTS
  unsigned long micros;
  if(fort_internal_events) {
    fort_add_random_event_time(&time_pool,
        18, fort_event_mode);
    fort_add_random_event_timer_start(&micros);
  }
#endif

  blockbytes=0;

  while(len!=0) {
    fort_rekey(tmp);
    n=(len<HashLen) ? len : HashLen;
    memcpy(buf, tmp, n);
    buf+=n;
    len-=n;
    blockbytes+=n;
     // rekey every 1048576 bytes if data left
    if(blockbytes >= FORT_PSEUDO_LIMIT &&
        len > 0) {
      fort_rekey(fort_key);
      blockbytes=0;
    }
  }

  fort_rekey(fort_key);

  /* Forget last bytes */
  memset(tmp, 0, sizeof(tmp));

#ifdef FORT_INTERNAL_EVENTS
  if(fort_internal_events)
    fort_add_random_event_timer_do(&pseudo_pool,
        19, fort_event_mode, &micros);
#endif
}

Ohjelma muodostaa satunnaisbitit kutsumalla fort_rekey funktiota ja kopioimalla sen antamat bitit asiakkaan puskuriin ja toistamalla tätä kunnes kaikki tarvittavat bitit on luotu. Funktio tekee uuden fort_key:n aina 1048576 merkin jälkeen. Se tehdään myös samalla fort_rekey funktiolla. Suorituksen lopuksi uusitaan vielä fort_key samalla fort_rekey funktiolla.

Fort reseed funktiolla muodostetaan uusi fort_key avain asiakkaan len pituisesta buf bittijonosta. Avaimeen käytetään vanhaa fort_key:tä, cvaria ja asiakkaan bittijonoa. time_pool on määritelty aiemmin. Lisäksi kasvatetaan cvaria. Fort_reseedillä ei voi tässä saada aikaiseksi samoja satunnaisbittejä uudestaan, kuten alkuperäisessä fortuna dokumentaatiossa. Periaatteessa ominaisuuden voi toteuttaa poistamalla fort_key:n ja cvar:in HashUpdate:t koodista.

int reseed_pool=0;

void fort_reseed(int len, unsigned char *buf)
{
  HashCtx hash;
#ifdef FORT_INTERNAL_EVENTS
  unsigned long micros;
  if(fort_internal_events) {
    fort_add_random_event_time(&time_pool,
        20, fort_event_mode);
    fort_add_random_event_timer_start(&micros);
  }
#endif

  hash_init(&hash);
  hash_update(&hash, fort_key, sizeof(fort_key));
  hash_update(&hash, (unsigned char *)&cvar,
      sizeof(cvar));
  hash_update(&hash, buf, len);
  hash_final(fort_key, &hash);
  inccvar();

  memset(&hash, 0, sizeof(hash));
#ifdef FORT_INTERNAL_EVENTS
  if(fort_internal_events)
    fort_add_random_event_timer_do(&reseed_pool,
        21, fort_event_mode, &micros);
#endif
}

Seuraavalla funktiolla muodostetaan satunnaisbittejä. Erona fort_pseudo_random_data funktioon on että tarvittaessa vaihdetaan fort_key avainta. Itseasiassa varsinaiset satunnaisbitit muodostetaan fort_key avaimen muodostuksen jälkeen aiemmalla fort_pseudo_random_data funktiolla.

Aliohjelman ensimmäisessä varsinaisessa iffissä päätellään tehdäänkö avaimen muodostus tällä kutsulla. Muodostus tehdään, jos halutaan tehdä avainnus jokaisella ajokerralla (fort_reseed_always==1), tai jos on ensimmäinen kutsukerta (fort_next_reseed==0) tai edellisestä avainnuksesta on kulunut sekunnin kymmennys (tenth) ja ensimmäinen puskuri on täynnä (64 merkkiä satunnaisuutta).

Jos avainnus tehdään, c muuttujaa käyttävässä luupissa käydään asiaankuuluvat puskurit läpi. Jos (fort_fill_pool_when_reseeding==1) lippu on päällä ja puskuri ei ole täynnä täytetään puskuri merkeillä ressu_genbuffer rutiinista. Kaikkien asiaan kuuluvien puskurien sisältö kerätään hash-nimiseen tilamuuttujaan (hash_update) ja nollataan käytettyjen puskurien pituus (length) muuttuja. Käytetyn puskurin hash tyhjennetään (hash_init) ja sen arvoksi asetetaan puskurin edellinen hash (hash_update). Kaikkien valittujen puskurien hash:illä (hash_final) päivitetään fort_reseed():llä fort_key:n uudeksi arvoksi.

Puskureita käytetään puskurista 0 lähtien siten että puskuria nolla käytetään jokaisessa avaimen muodostuksessa, puskuria yksi käytetään joka toisessa avaimen muodostuksessa, puskuria kaksi käytetään joka neljännessä avaimen muodostuksessa, puskuria kolme käytetään joka kahdeksanneksessa avaimen muodostuksessa: rivin ensimmäinen numero on fort_reseed_count ja sen jälkeen ovat puskurin numerot:

01 0
02 0 1
03 0
04 0 1 2
05 0
06 0 1
07 0
08 0 1 2 3
09 0
10 0 1
11 0
12 0 1 2
13 0
14 0 1
15 0
16 0 1 2 3 4

Eli puskuria 4 käytetään joka kuudennessatoista kierroksessa. Tässä vielä viimeiset kierrokset ennen kun laskuri menee ympäri:

4294967278 0 1
4294967279 0
4294967280 0 1 2 3 4
4294967281 0
4294967282 0 1
4294967283 0
4294967284 0 1 2
4294967285 0
4294967286 0 1
4294967287 0
4294967288 0 1 2 3
4294967289 0
4294967290 0 1
4294967291 0
4294967292 0 1 2
4294967293 0
4294967294 0 1
4294967295 0
00 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29 30 31
01 0
02 0 1
03 0

Vielä lista kierroksista, joilla puskuria käytetään ensimmäisen kerran. Esimerkiksi puskuria 6 käytetään ensimmäisen kerran kierroksella 64.

02 0 1
04 0 1 2
08 0 1 2 3
16 0 1 2 3 4
32 0 1 2 3 4 5
64 0 1 2 3 4 5 6
128 0 1 2 3 4 5 6 7
256 0 1 2 3 4 5 6 7 8
512 0 1 2 3 4 5 6 7 8 9
1024 0 1 2 3 4 5 6 7 8 9 10
2048 0 1 2 3 4 5 6 7 8 9 10 11
4096 0 1 2 3 4 5 6 7 8 9 10 11 12
8192 0 1 2 3 4 5 6 7 8 9 10 11 12 13
16384 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
32768 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
65536 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
131072 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
262144 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
524288 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
1048576 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
2097152 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
4194304 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
8388608 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
16777216 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
33554432 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

Puskureita käytetään aina tietty määrä nollapuskurista ylöspäin, ja käytön jälkeen ne täyttyvät nopeimmin fort_add_random_event funktion modella 2.

Ja vielä varsinainen fort_random_data funktion koodi:

#define FORT_MIN_POOL_SIZE 64
unsigned int fort_reseed_always=1;
unsigned int fort_fill_pool_when_reseeding=1;
unsigned int fort_reseed_count=0;
unsigned long fort_next_reseed=0;

int random_pool=0;

void fort_random_data(int len, unsigned char *buf)
{
  int c;
  unsigned long tenths;
  HashCtx hash;
  unsigned char buffer[HashLen];
#ifdef FORT_INTERNAL_EVENTS
  unsigned long micros;
  if(fort_internal_events) {
    fort_add_random_event_time(&time_pool,
        22, fort_event_mode);
    fort_add_random_event_timer_start(&micros);
  }
#endif

  tenths=gettenths();

  if( (fort_reseed_always==1) ||
      (fort_next_reseed==0) ||
      (fort_next_reseed<=tenths &&
       fort_pools[0].length >=
           FORT_MIN_POOL_SIZE) ) {

    // next tenth of a second
    fort_next_reseed = tenths + 1;                                                                                                                                                                                                                                                                                                                                                                                                               
    fort_reseed_count++;
    hash_init(&hash);
    c=0;

    while(c < 32 &&
        (fort_reseed_count % (1<<c))==0) {
#ifdef FORT_INTERNAL_EVENTS
      unsigned long micros;
      if(fort_internal_events) {
        fort_add_random_event_time(&time_pool,
            23, fort_event_mode);
        fort_add_random_event_timer_start(
            &micros);
      } // if(fort_internal_events)
#endif
#ifdef RESSU
      if(fort_fill_pool_when_reseeding==1 &&
         fort_pools[c].length<
              FORT_MIN_POOL_SIZE) {
        unsigned char tmp32[32];
        int len=FORT_MIN_POOL_SIZE -
            fort_pools[c].length;
        int n;

        while(len>0) {
#ifdef FORT_INTERNAL_EVENTS
          unsigned long micros;
          if(fort_internal_events) {
            fort_add_random_event_time(
                &time_pool, 24,
                fort_event_mode);
            fort_add_random_event_timer_start(&micros);
          } // if(fort_internal_events)
#endif
          n=(len<sizeof(tmp32)) ?
              len : sizeof(tmp32);
          memset(tmp32, 0, sizeof(tmp32));
          ressu_genbuffer(n, tmp32);
          int pool2=c;
          fort_add_random_event(&pool2, 29,
              fort_event_mode, n, tmp32);
          len-=n;
#ifdef FORT_INTERNAL_EVENTS
          if(fort_internal_events)
            fort_add_random_event_timer_do(
                &random_pool, 25,
                fort_event_mode, &micros);
#endif
        } // while(len>0)
      } // if(fort_fill_pool_when_reseeding==1
#endif
      hash_final(buffer, &fort_pools[c].pool);
      hash_update(&hash, buffer, sizeof(buffer));
      fort_pools[c].length = 0;
      HashInit(&fort_pools[c].pool);
      // save earlier pool to new one
      hash_update(&fort_pools[c].pool,
          buffer, sizeof(buffer));                                                                                                                                                                                                                                                                                                                                                                            
      c++;
#ifdef FORT_INTERNAL_EVENTS
      if(fort_internal_events)
        fort_add_random_event_timer_do(
             &random_pool, 26,
             fort_event_mode, &micros);
#endif
    } // while(c < 32
    hash_update(&hash, (unsigned char *)&cvar,
        sizeof(cvar));
    hash_final(buffer, &hash);
    fort_reseed(sizeof(buffer), buffer);
    /* Forget hash context */
    memset(&hash, 0, sizeof(hash));
    /* Forget reseed key */
    memset(buffer, 0, sizeof(buffer));  
    inccvar();
  } // if( (fort_reseed_always==1
  fort_pseudo_random_data(len, buf);

#ifdef FORT_INTERNAL_EVENTS
  if(fort_internal_events)
    fort_add_random_event_timer_do(&random_pool,
        27, fort_event_mode,
        &micros);
#endif
}

Tässä lyhennetty versio, josta on poistettu FORT_INTERNAL_EVENTS ja RESSU rivit:

void fort_random_data_readable(int len, unsigned char *buf)
{
  int c;
  unsigned long tenths;
  HashCtx hash;
  unsigned char buffer[HashLen];

  tenths=gettenths();

  if( (fort_reseed_always==1) ||
      (fort_next_reseed==0) ||
      (fort_next_reseed<=tenths &&
       fort_pools[0].length >=
           FORT_MIN_POOL_SIZE) ) {

    // next tenth of a second
    fort_next_reseed = tenths + 1;                                                                                                                                                                                                                                                                                                                                                                                                               
    fort_reseed_count++;
    hash_init(&hash);
    c=0;

    while(c < 32 &&
        (fort_reseed_count % (1<<c))==0) {
      hash_final(buffer, &fort_pools[c].pool);
      hash_update(&hash, buffer, sizeof(buffer));
      fort_pools[c].length = 0;
      HashInit(&fort_pools[c].pool);
      // save earlier pool to new one
      hash_update(&fort_pools[c].pool,
          buffer, sizeof(buffer));                                                                                                                                                                                                                                                                                                                                                                            
      c++;
    } // while(c < 32
    hash_update(&hash, (unsigned char *)&cvar,
        sizeof(cvar));
    hash_final(buffer, &hash);
    fort_reseed(sizeof(buffer), buffer);
    /* Forget hash context */
    memset(&hash, 0, sizeof(hash));
    /* Forget reseed key */
    memset(buffer, 0, sizeof(buffer));  
    inccvar();
  } // if( (fort_reseed_always==1
  fort_pseudo_random_data(len, buf);
}

Edellisen aliohjelmat

unsigned long gettenths()
{
  struct timeval tv;
  gettimeofday(&tv, NULL);
  return((unsigned long)tv.tv_sec*10 +
      (int)tv.tv_usec/100000);
}

Fort_save toiminnolla voidaan tallettaa generaattorin “tila”, ja fort_read_file taas lukee generaattorin tilan.

char fort_random_file[128] = "fort.rnd";
#define FORT_SAVE_SIZE 1024

void fort_save()
{
  FILE *fp1;
  unsigned char buffer[FORT_SAVE_SIZE];

  fp1=fopen(fort_random_file,"w");

  if(fp1!=NULL) {
    fort_pseudo_random_data(sizeof(buffer),
        buffer);
    fwrite(buffer, 1, sizeof(buffer), fp1);
    fclose(fp1);
  }
  memset(buffer, 0, sizeof(buffer));
}

void fort_read_file()
{
  int d;
  FILE *fp1;
  unsigned char buffer[FORT_SAVE_SIZE];

  fp1=fopen(fort_random_file, "rb");

  if(fp1==NULL) {
    fort_pseudo_random_data(
        sizeof(buffer), buffer);
#ifdef RESSU
    ressu_genbuffer(sizeof(buffer), buffer);
#endif
    d=sizeof(buffer);
  } else {
    d=fread(buffer, 1, sizeof(buffer), fp1);
    fclose(fp1);
  }
  fort_reseed(d, buffer);

  fort_save();
  memset(buffer, 0, sizeof(buffer));
}

Fort_save generoi joukon satunnaisbittejä fort_pseudo_random_data funktiolla ja kirjoittaa ne tiedostoon. Fort_read_file taas lukee edellä generoidut satunnaisbitit ja määrittelee uuden fort_key avaimen fort_reseed() rutiinilla. Jos fort_read_file ei onnistu avaamaan tallennettujen satunnaismerkkien tiedostoa, se generoi avainnukseen käytetyt satunnaismerkit ressulla.

Käytetty ressugen aliohjelma:

void ressu_genbuffer(int size, unsigned char *buffer)
{
  int c;

  for(c=0;c<size;c++)
    buffer[c]^=ressu_genbyte();
}

Aliohjelmat yksittäisten satunnaisten merkkien ja puskurien luomiseen:

#define FORT_CLEAR

#define FORTCNT 128

unsigned int fort_cnt=FORTCNT;
unsigned char fort_bytes[FORTCNT];
int fort_byte = 999999999;

int fort_random_data_byte()
{
  if(fort_byte >= fort_cnt) {
#ifdef FORT_CLEAR
    memset(fort_bytes, 0, fort_cnt);
#else
    if(fort_byte == 999999999)
        memset(fort_bytes, 0, fort_cnt);
#endif
    fort_random_data(fort_cnt, fort_bytes);
    fort_byte=0;
  }
  return(fort_bytes[fort_byte++]);
}

int fort_random_data_byte_limit(int limit)
{
  int c;

  while((c=fort_random_data_byte())>
      (256/limit)*limit);
  return(c%limit);
}

void fort_random_data_buffer(int size, unsigned char *buffer)
{
  int c;

  for(c=0;c<size;c++)
    buffer[c]^=fort_random_data_byte();
}

Seuraavana funktio, jolla alustetaan generaattori käyttöä varten. Se ajetaan yleensä ressu_init:in jälkeen pääohjelmassa. Ohjelma tulostaa ensiksi rivin fort:in teknisistä tiedoista. Seuraavaksi tyhjennetään cvar ja alustetaan poolit (puskurit).

Huomaa että alustettaessa ei voida käyttää satunnaistapahtumia luovia versioita HashInit:istä, koska silloin puskuri voi vastaanottaa tapahtuman, vaikka sitä ei ole alustettu. Fort_random_data:ssa on sama ongelma kun vanha puskuri tyhjennetään ja alustetaan uudestaan.

Seuraavaksi generoidaan ressulla fort_key avain. Jos ressua ei ole, fort_key ei vielä tässä vaiheessa ole kunnossa. Jos sisäiset tapahtumat ovat käytössä, fort_key luodaan kunnolla myöhemmin tulevassa sisäisiä tapahtumia luovassa kappaleessa.

Fopen funktiolla tyhjennetään fortevents.deb tiedosto ja dump_events muuttujaan laitetaan ensimmäisen tapahtuman numero nolla.

Sitten tulee edellä mainittu sisäisten tapahtumien luontikappale. Tämän kappaleen suorituksen jälkeen puskureissa on hyvä määrä satunnaisuutta vaikka ressua ei olisikaan käytössä. Sisäisten tapahtumien luonnin jälkeen avainnetaan fort_key uudestaan.

Seuraava kappale tekee tapahtumia (events) satunnaisiin ressu:lla arvottuihin lähteisiin. (mode=4)

Lopuksi täytetään vielä 0 puskuria lisäämällä satunnaismerkkejä ja alustetaan fort_reseed_count ja fort_next_reseed nollaksi.

void fort_init()                                                                                                                                                                       
{
  fprintf(stdout,"Fort v0.4");
  fprintf(stdout,", hash: %s", HashName);
  fprintf(stdout,", pools: %d*%ld", 32,
      sizeof(HashCtx));
  fprintf(stdout,", hashsize: %d", HashLen);

  unsigned int save_fort_internal_events;
  save_fort_internal_events=fort_internal_events;
  fort_internal_events=1;

  clearcvar();
  
  // Initialize buffers
  for(int c=0;c<32;c++) {
    fort_pools[c].length = 0;
    HashInit(&fort_pools[c].pool);
  }

#ifdef RESSU
  ressu_genbuffer(sizeof(fort_key),fort_key);
#endif

  FILE *fp1;
  // Empty events file
  if((fp1=fopen(fort_events_file,"w"))!=NULL)
    fclose(fp1);
  dump_events=0;

  unsigned char temp[64];

  memset(temp, 0, sizeof(temp));

#ifdef FORT_INTERNAL_EVENTS
  if(fort_internal_events) {
    // Create some internal events
    for(int c=0;c<64;c++)
      fort_random_data(sizeof(temp), temp);
  }
#endif
  fort_reseed_count=0;
  fort_next_reseed=0;

  // Rekey fort_key with new events
  fort_random_data(sizeof(temp),temp);
#ifdef RESSU
  ressu_genbuffer(sizeof(temp),temp);
#endif
  fort_reseed(sizeof(temp),temp);

  fort_read_file();

#ifdef FORT_INTERNAL_EVENTS
  if(fort_internal_events) {
    // Events to random pools
    fort_pseudo_random_data(sizeof(temp), temp);
#ifdef RESSU
    ressu_genbuffer(sizeof(temp),temp);
#endif
    int pool2=0;
    fort_add_random_event_split(&pool2,
      30, 4, sizeof(temp), temp, 1);
  }
#endif

#ifdef FORT_INTERNAL_EVENTS
  if(fort_internal_events) {
    // Fill first pool
    fort_pseudo_random_data(sizeof(temp), temp);
#ifdef RESSU
    ressu_genbuffer(sizeof(temp), temp);
#endif
    int pool2=0;
    fort_add_random_event(&pool2,
      28, 1, sizeof(temp), temp);
  }
#endif

  // Forget temp variable
  memset(temp, 0, sizeof(temp));

  fort_internal_events =
    save_fort_internal_events;

  fort_reseed_count=0;
  fort_next_reseed=0;
}

Tässä vielä apurutiineja, joiden avulla saadaan kerättyä satunnaisuutta hash:ien käsittelyä tekevistä init, update ja final rutiineista.

int time_pool=0;
int init_pool=0;

void hash_init(HashCtx *hash)
{
#ifdef FORT_INTERNAL_EVENTS
  unsigned long micros;
  if(fort_internal_events) {
    fort_add_random_event_time(&time_pool,
        10, fort_event_mode);
    fort_add_random_event_timer_start(&micros);
  }
#endif
  HashInit(hash);
#ifdef FORT_INTERNAL_EVENTS
  if(fort_internal_events)
    fort_add_random_event_timer_do(&init_pool,
        11, fort_event_mode, &micros);
#endif
}

int update_pool=0;

void hash_update(HashCtx *hash, unsigned char *data, int len)
{
#ifdef FORT_INTERNAL_EVENTS
  unsigned long micros;
  if(fort_internal_events) {
    fort_add_random_event_time(&time_pool, 
        12, fort_event_mode);
    fort_add_random_event_timer_start(&micros);
  }
#endif
  HashUpdate(hash, data, len);
#ifdef FORT_INTERNAL_EVENTS
  if(fort_internal_events)
    fort_add_random_event_timer_do(&update_pool,
        13, fort_event_mode, &micros);
#endif
}

int final_pool=0;

void hash_final(unsigned char digest[HashLen], HashCtx *hash)
{
#ifdef FORT_INTERNAL_EVENTS
  unsigned long micros;
  if(fort_internal_events) {
    fort_add_random_event_time(&time_pool,
        14, fort_event_mode);
    fort_add_random_event_timer_start(&micros);
  }
#endif
  HashFinal(digest,hash);
#ifdef FORT_INTERNAL_EVENTS
  if(fort_internal_events)
    fort_add_random_event_timer_do(&final_pool,
        15, fort_event_mode, &micros);
#endif
}

Vielä pieni testipääohjelma:

int main(int argc,char *argv[])
{
  unsigned char buffer[32];

  ressu_init();
  fort_init();

  ressu_genbuffer(sizeof(buffer), buffer);
  fort_reseed(sizeof(buffer), buffer);

  fort_random_data(sizeof(buffer), buffer);

  for(int c=0;c<sizeof(buffer);c++)
    fprintf(stdout,"%02x",buffer[c]);
  fprintf(stdout,"\n");

  fort_save();

  return(0);
}

Kellosarjassa olevat satunnaiset bitit

Tässä uusimmassa versiossa yritetään laskea paremmin kellosarjasta tulevia satunnaisbittejä. Ohjelmassa lasketaan satunnaisbittejä neljällä eri tavalla. Ensimmäisessä tavassa (muuttuja b) lasketaan kellosarjojen pituuksia, eli sitä kuinka monta kertaa peräkkäin sama numero ilmenee. Tämä tapa on ollut mukana alusta asti. Toinen tapa on periaatteessa sama, aina kun kellosarjan numero vaihtuu, lisätään teoreettiseen satunnaisuuteen yksi bitti (muuttuja rndbits). Kolmannessa tavassa lisätään satunnaisuuteen yksi bitti aina kun kellosarjan pituus vaihtuu (muuttuja rndbits2). Neljännessä tavassa (rndbits3) tarkastellaan viittä erilaista edellistä kellosarjan pituutta, ja aina kun tulee edellisistä poikkeava kellosarjan pituus lisätään satunnaisuutta yhden bitin verran.

Kaikkien näiden tapojen pitää toteutua eli laskennallisia satunnaisbittejä jokaisella tavalla tulee olla enemmän kun haluttuja satunnaisbittejä puskurissa.

Clockbytes ehto varmistaa että kellomateriaalia käsitellään ainakin 2 kiloa.

Sen verran ovat palautetut satunnaisbitit parantuneet, että ensimmäisissä testeissä dieharder ei huomannut yhtään heikkoutta. Aiemmin niitä oli pari kolme.

void ressu_genbytes(int size, unsigned char *buffer, int b) /* (c) JariK 2013-2019 */
{
  int c, d, e, f, g,
    byte, prevbyte,
    chainbytes, prevchainbytes = -1,
    clockbytes = 0,
    rndbits = 0, rndbits2 = 0,
rndbits3 = 0; unsigned char oldchains[5] = { 0,0,0,0,0 }; prevbyte=clockbyte(); chainbytes=1; f=0; for(c=0; c < 8*b || clockbytes < 2000 || rndbits < 8*size || (rndbits2 > 0 && rndbits2 < 8*size ) || (rndbits3 > 0 && rndbits3 < 8*size); c++) { for(d=0; d<size; d++) { e = buffer[d]; e = ((e&0x80)>>7) | ((e&0x7f)<<1); byte = clockbyte(); buffer[d] = e^byte; if(prevbyte != byte) { prevbyte = byte; rndbits++; if(prevchainbytes != chainbytes) { prevchainbytes = chainbytes; rndbits2++; for(g = 0; g < sizeof(oldchains); g++) { if(oldchains[g] == chainbytes) break; } if(g == sizeof(oldchains)) { rndbits3++; g--; } while(g > 0) { oldchains[g] = oldchains[g-1]; g--; } oldchains[0] = chainbytes; } // if(prevchainbytes clockbytes+= chainbytes; chainbytes = 0; } // if(prevbyte chainbytes++; } // for(d for(d=0; d<size; d++) { f = (f+buffer[d])%size; e = buffer[d]; buffer[d] = buffer[f]; buffer[f] = e; } } // for(c }

Tässä vielä rutiini alkuperäisen b:n laskentaan.

int ressu_calculate_b()
{
  int c,d,e,total,count,bytes;

  total=0;
  count=0;
  d=clockbyte();
  for(c=0;c<4096;c++) {
    bytes=1;
    while((e=clockbyte())==d)
      bytes++;
    total+=bytes;
    count++;
    d=e;
  }

  return((int)((float)total/count));
}

Clockbyte löytyy aiemmasta postista.

Kotisivu