LCOV - code coverage report
Current view: top level - src - generate.c (source / functions) Hit Total Coverage
Test: passgen-test.info Lines: 108 223 48.4 %
Date: 2024-11-15 06:04:45 Functions: 14 20 70.0 %

          Line data    Source code
       1             : #include "passgen/generate.h"
       2             : 
       3             : #include "passgen/assert.h"
       4             : #include "passgen/container/hashmap.h"
       5             : #include "passgen/container/stack.h"
       6             : #include "passgen/markov.h"
       7             : #include "passgen/pattern/group.h"
       8             : #include "passgen/pattern/literal.h"
       9             : #include "passgen/pattern/pattern.h"
      10             : #include "passgen/pattern/range.h"
      11             : #include "passgen/pattern/repeat.h"
      12             : #include "passgen/pattern/segment.h"
      13             : #include "passgen/pattern/segment_item.h"
      14             : #include "passgen/pattern/set.h"
      15             : #include "passgen/pattern/special.h"
      16             : #include "passgen/util/utf8.h"
      17             : #include "passgen/wordlist.h"
      18             : #include "try.h"
      19             : 
      20             : #include <string.h>
      21             : 
      22             : typedef struct {
      23             :     passgen_env *env;
      24             :     size_t depth;
      25             :     double *entropy;
      26             :     void *data;
      27             :     passgen_generate_cb *func;
      28             : } passgen_generate_context;
      29             : 
      30             : // Emit a character
      31         143 : static inline int emit(passgen_generate_context *context, uint32_t codepoint) {
      32         143 :     return context->func(context->data, codepoint);
      33             : }
      34             : 
      35             : // Recurse
      36          89 : static inline int descend(passgen_generate_context *context) {
      37          89 :     if(!context->depth) {
      38           3 :         return 1;
      39             :     }
      40             : 
      41          86 :     context->depth -= 1;
      42          86 :     return 0;
      43             : }
      44             : 
      45          83 : static inline void ascend(passgen_generate_context *context) {
      46          83 :     context->depth += 1;
      47          83 : }
      48             : 
      49             : static size_t passgen_generate_repeat(
      50             :     passgen_generate_context *context,
      51             :     const passgen_pattern_repeat *repeat);
      52             : 
      53             : static int passgen_generate_set(
      54             :     passgen_generate_context *context,
      55             :     const passgen_pattern_set *set);
      56             : 
      57             : static int passgen_generate_literal(
      58             :     passgen_generate_context *context,
      59             :     const passgen_pattern_literal *character);
      60             : 
      61             : static int passgen_generate_special_markov(
      62             :     passgen_generate_context *context,
      63             :     const passgen_pattern_special *special);
      64             : 
      65             : static int passgen_generate_special_wordlist(
      66             :     passgen_generate_context *context,
      67             :     const passgen_pattern_special *special);
      68             : 
      69             : static int passgen_generate_special(
      70             :     passgen_generate_context *context,
      71             :     const passgen_pattern_special *special);
      72             : 
      73             : static int passgen_generate_group(
      74             :     passgen_generate_context *context,
      75             :     const passgen_pattern_group *group);
      76             : 
      77             : static int passgen_generate_item(
      78             :     passgen_generate_context *context,
      79             :     const passgen_pattern_item *item);
      80             : 
      81             : static int passgen_generate_segment(
      82             :     passgen_generate_context *context,
      83             :     const passgen_pattern_segment *segment);
      84             : 
      85             : struct fillpos {
      86             :     uint32_t *buffer;
      87             :     size_t cur;
      88             :     size_t len;
      89             : };
      90             : 
      91             : struct fillpos_utf8 {
      92             :     uint8_t *buffer;
      93             :     size_t cur;
      94             :     size_t len;
      95             : };
      96             : 
      97          47 : static int passgen_generate_write_buffer(void *data, uint32_t codepoint) {
      98          47 :     struct fillpos *fillpos = data;
      99             : 
     100          47 :     if(fillpos->cur == fillpos->len) {
     101           0 :         return -1;
     102             :     }
     103             : 
     104          47 :     fillpos->buffer[fillpos->cur] = codepoint;
     105          47 :     fillpos->cur++;
     106             : 
     107          47 :     return 0;
     108             : }
     109             : 
     110          90 : static int passgen_generate_write_buffer_utf8(void *data, uint32_t codepoint) {
     111          90 :     struct fillpos_utf8 *fillpos = data;
     112             : 
     113          90 :     if(fillpos->cur == fillpos->len) {
     114           0 :         return -1;
     115             :     }
     116             : 
     117          90 :     if((fillpos->cur + 4) <= fillpos->len) {
     118          90 :         int bytes = passgen_utf8_encode_codepoint(
     119          90 :             (uint8_t *) &fillpos->buffer[fillpos->cur],
     120             :             codepoint);
     121             : 
     122          90 :         if(bytes < 0) {
     123             :             // error happened during encoding.
     124           0 :             return -1;
     125             :         }
     126             : 
     127          90 :         fillpos->cur += bytes;
     128             :     } else {
     129             :         char buffer[4];
     130             :         int bytes =
     131           0 :             passgen_utf8_encode_codepoint((uint8_t *) &buffer[0], codepoint);
     132             : 
     133           0 :         if(bytes < 0) {
     134             :             // error happened during encoding.
     135           0 :             return -1;
     136             :         }
     137             : 
     138           0 :         if(bytes <= (fillpos->len - fillpos->cur)) {
     139           0 :             memcpy(&fillpos->buffer[fillpos->cur], &buffer[0], bytes);
     140           0 :             fillpos->cur += bytes;
     141             :         } else {
     142             :             // error: encoded doesn't fit in buffer.
     143           0 :             return -1;
     144             :         }
     145             :     }
     146             : 
     147          90 :     return 0;
     148             : }
     149             : 
     150             : /// Write JSON-escaped UTF-8 to buffer.
     151             : static int
     152           0 : passgen_generate_write_buffer_json_utf8(void *data, uint32_t codepoint) {
     153           0 :     struct fillpos_utf8 *fillpos = data;
     154             : 
     155           0 :     if(fillpos->cur == fillpos->len) {
     156           0 :         return -1;
     157             :     }
     158             : 
     159           0 :     unsigned char buffer[4] = {'\\', 0};
     160           0 :     size_t bytes = 2;
     161           0 :     switch(codepoint) {
     162           0 :         case '"':
     163           0 :             buffer[1] = '"';
     164           0 :             break;
     165           0 :         case '\\':
     166           0 :             buffer[1] = '\\';
     167           0 :             break;
     168           0 :         case '\b':
     169           0 :             buffer[1] = 'b';
     170           0 :             break;
     171           0 :         case '\f':
     172           0 :             buffer[1] = 'f';
     173           0 :             break;
     174           0 :         case '\r':
     175           0 :             buffer[1] = 'r';
     176           0 :             break;
     177           0 :         case '\n':
     178           0 :             buffer[1] = 'n';
     179           0 :             break;
     180           0 :         case '\t':
     181           0 :             buffer[1] = 't';
     182           0 :             break;
     183           0 :         default:
     184           0 :             bytes = passgen_utf8_encode_codepoint(
     185             :                 (uint8_t *) &buffer[0],
     186             :                 codepoint);
     187             :     }
     188             : 
     189             :     // check that no error happened.
     190           0 :     if(!bytes) {
     191           0 :         return -1;
     192             :     }
     193             : 
     194             :     // make sure it fits.
     195           0 :     if(bytes <= (fillpos->len - fillpos->cur)) {
     196           0 :         memcpy(&fillpos->buffer[fillpos->cur], &buffer[0], bytes);
     197           0 :         fillpos->cur += bytes;
     198             :     } else {
     199           0 :         return -1;
     200             :     }
     201             : 
     202           0 :     return 0;
     203             : }
     204             : 
     205          28 : size_t passgen_generate_fill_unicode(
     206             :     const passgen_pattern *pattern,
     207             :     passgen_env *env,
     208             :     double *entropy,
     209             :     uint32_t *buffer,
     210             :     size_t len) {
     211          28 :     struct fillpos fillpos = {
     212             :         .buffer = buffer,
     213             :         .len = len,
     214             :         .cur = 0,
     215             :     };
     216             : 
     217          28 :     int ret = passgen_generate(
     218             :         pattern,
     219             :         env,
     220             :         entropy,
     221             :         &fillpos,
     222             :         passgen_generate_write_buffer);
     223             : 
     224          28 :     if(ret != 0) {
     225           0 :         return 0;
     226             :     }
     227             : 
     228          28 :     return fillpos.cur;
     229             : }
     230             : 
     231          31 : size_t passgen_generate_fill_utf8(
     232             :     const passgen_pattern *pattern,
     233             :     passgen_env *env,
     234             :     double *entropy,
     235             :     uint8_t *buffer,
     236             :     size_t len) {
     237          31 :     struct fillpos_utf8 fillpos = {
     238             :         .buffer = buffer,
     239             :         .len = len,
     240             :         .cur = 0,
     241             :     };
     242             : 
     243          31 :     int ret = passgen_generate(
     244             :         pattern,
     245             :         env,
     246             :         entropy,
     247             :         &fillpos,
     248             :         passgen_generate_write_buffer_utf8);
     249             : 
     250          31 :     if(ret != 0) {
     251           0 :         return 0;
     252             :     }
     253             : 
     254          31 :     return fillpos.cur;
     255             : }
     256             : 
     257           0 : size_t passgen_generate_fill_json_utf8(
     258             :     const passgen_pattern *pattern,
     259             :     passgen_env *env,
     260             :     double *entropy,
     261             :     uint8_t *buffer,
     262             :     size_t len) {
     263           0 :     struct fillpos_utf8 fillpos = {
     264             :         .buffer = buffer,
     265             :         .len = len,
     266             :         .cur = 0,
     267             :     };
     268             : 
     269           0 :     int ret = passgen_generate(
     270             :         pattern,
     271             :         env,
     272             :         entropy,
     273             :         &fillpos,
     274             :         passgen_generate_write_buffer_json_utf8);
     275             : 
     276           0 :     if(ret != 0) {
     277           0 :         return 0;
     278             :     }
     279             : 
     280           0 :     return fillpos.cur;
     281             : }
     282             : 
     283          84 : static size_t passgen_generate_repeat(
     284             :     passgen_generate_context *context,
     285             :     const passgen_pattern_repeat *repeat) {
     286          84 :     size_t difference = repeat->max - repeat->min;
     287             : 
     288             :     // if there is no difference to pick, just return here
     289          84 :     if(0 == difference) {
     290          78 :         return repeat->min;
     291             :     }
     292             : 
     293             :     // get random number to choose from the range
     294             :     size_t choice =
     295           6 :         passgen_random_u64_max(context->env->random, difference + 1);
     296             : 
     297             :     // keep track of entropy
     298           6 :     if(context->entropy) {
     299           4 :         *context->entropy *= difference + 1;
     300             :     }
     301             : 
     302           6 :     return repeat->min + choice;
     303             : }
     304             : 
     305          26 : static int passgen_generate_set(
     306             :     passgen_generate_context *context,
     307             :     const passgen_pattern_set *set) {
     308             :     // if this set is empty, we're done.
     309          26 :     if(set->items.len == 0) {
     310           0 :         return 0;
     311             :     }
     312             : 
     313             :     // compute number of possible codepoints
     314          26 :     size_t possible = set->choices_list[set->items.len - 1];
     315          26 :     passgen_assert(possible != 0);
     316          26 :     size_t choice = passgen_random_u64_max(context->env->random, possible);
     317             : 
     318             :     // keep track of entropy
     319          26 :     if(context->entropy) {
     320          14 :         *context->entropy *= possible;
     321             :     }
     322             : 
     323             :     // locate choice in list of choices.
     324             :     // TODO: binary search.
     325             :     size_t num;
     326          46 :     for(num = 0; num < set->items.len; num++) {
     327          46 :         if(choice < set->choices_list[num]) {
     328          26 :             break;
     329             :         }
     330             :     }
     331             : 
     332          26 :     passgen_assert(num != set->items.len);
     333             : 
     334             :     /* adjust choice to be relative offset */
     335          26 :     if(num) {
     336          12 :         choice -= set->choices_list[num - 1];
     337             :     }
     338             : 
     339          26 :     passgen_pattern_range *range = passgen_stack_get(&set->items, num);
     340             : 
     341          26 :     return emit(context, range->start + choice);
     342             : }
     343             : 
     344          50 : static int passgen_generate_literal(
     345             :     passgen_generate_context *context,
     346             :     const passgen_pattern_literal *literal) {
     347          50 :     passgen_assert(literal->count > 0);
     348          50 :     passgen_assert(literal->count < 8);
     349             : 
     350         167 :     for(size_t i = 0; i < literal->count; i++) {
     351         117 :         try(emit(context, literal->codepoints[i]));
     352             :     }
     353             : 
     354          50 :     return 0;
     355             : }
     356             : 
     357           0 : static int passgen_generate_special_markov(
     358             :     passgen_generate_context *context,
     359             :     const passgen_pattern_special *special) {
     360             :     passgen_hashmap_entry *entry =
     361           0 :         passgen_hashmap_lookup(&context->env->wordlists, special->parameters);
     362           0 :     if(!entry) {
     363           0 :         return -1;
     364             :     }
     365           0 :     passgen_wordlist *wordlist = entry->value;
     366           0 :     if(!wordlist->parsed) {
     367           0 :         passgen_wordlist_parse(wordlist);
     368             :     }
     369           0 :     if(!wordlist->parsed_markov) {
     370           0 :         passgen_wordlist_parse_markov(wordlist);
     371             :     }
     372           0 :     passgen_markov *markov = &wordlist->markov;
     373             :     uint32_t word[128];
     374           0 :     size_t pos = markov->level;
     375           0 :     memset(word, 0, pos * sizeof(uint32_t));
     376           0 :     double *entropy = context->entropy ? &*context->entropy : NULL;
     377             :     do {
     378           0 :         word[pos] = passgen_markov_generate(
     379             :             markov,
     380           0 :             &word[pos - markov->level],
     381           0 :             context->env->random,
     382             :             entropy);
     383           0 :         pos++;
     384           0 :     } while(word[pos - 1]);
     385             : 
     386           0 :     pos = markov->level;
     387           0 :     while(word[pos]) {
     388           0 :         try(emit(context, word[pos]));
     389           0 :         pos++;
     390             :     }
     391             : 
     392           0 :     return 0;
     393             : }
     394             : 
     395           0 : static int passgen_generate_special_wordlist(
     396             :     passgen_generate_context *context,
     397             :     const passgen_pattern_special *special) {
     398             :     passgen_hashmap_entry *entry =
     399           0 :         passgen_hashmap_lookup(&context->env->wordlists, special->parameters);
     400           0 :     if(!entry) {
     401           0 :         return -1;
     402             :     }
     403           0 :     passgen_wordlist *wordlist = entry->value;
     404           0 :     if(!wordlist->parsed) {
     405           0 :         passgen_wordlist_parse(wordlist);
     406             :     }
     407             : 
     408             :     // pick word at random
     409           0 :     const unsigned char *word = passgen_wordlist_random(wordlist, context->env->random);
     410             : 
     411             :     // UTF8-decode word and write codepoints
     412             :     // TODO: handle longer words
     413           0 :     size_t word_len = strlen(word);
     414           0 :     const char *word_pos = &word;
     415             :     uint32_t codepoints[128];
     416           0 :     uint32_t *codepoints_pos = &codepoints[0];
     417           0 :     try(passgen_utf8_decode(&codepoints_pos, 128, NULL, word_pos, word_len));
     418           0 :     for(int i = 0; &codepoints[i] < codepoints_pos; i++) {
     419           0 :         try(emit(context, codepoints[i]));
     420             :     }
     421             : 
     422           0 :     if(context->entropy) {
     423           0 :         *context->entropy *= passgen_wordlist_count(wordlist);
     424             :     }
     425             : 
     426           0 :     return 0;
     427             : }
     428             : 
     429           0 : static int passgen_generate_special_preset(
     430             :     passgen_generate_context *context,
     431             :     const passgen_pattern_special *special) {
     432             :     (void) special;
     433             :     // TODO: implement
     434           0 :     return 0;
     435             : }
     436             : 
     437           0 : static int passgen_generate_special(
     438             :     passgen_generate_context *context,
     439             :     const passgen_pattern_special *special) {
     440           0 :     switch(special->kind) {
     441           0 :         case PASSGEN_PATTERN_SPECIAL_MARKOV:
     442           0 :             return passgen_generate_special_markov(context, special);
     443           0 :         case PASSGEN_PATTERN_SPECIAL_WORDLIST:
     444           0 :             return passgen_generate_special_wordlist(context, special);
     445           0 :         case PASSGEN_PATTERN_SPECIAL_PRESET:
     446           0 :             return passgen_generate_special_preset(context, special);
     447           0 :         default:
     448           0 :             return 1;
     449             :     }
     450             :     return 0;
     451             : }
     452             : 
     453          85 : static int passgen_generate_item(
     454             :     passgen_generate_context *context,
     455             :     const passgen_pattern_item *item) {
     456             :     // if it is a maybe (has a question mark following it), decide first if we
     457             :     // want to emit it or not.
     458          85 :     if(item->maybe) {
     459           6 :         if(!passgen_random_bool(context->env->random)) {
     460           1 :             return 0;
     461             :         }
     462             :     }
     463             : 
     464             :     // compute random number of repetitions
     465          84 :     size_t reps = passgen_generate_repeat(context, &item->repeat);
     466             : 
     467         182 :     for(size_t i = 0; i < reps; i++) {
     468         101 :         switch(item->kind) {
     469          26 :             case PASSGEN_PATTERN_SET:
     470          26 :                 try(passgen_generate_set(context, &item->data.set));
     471          26 :                 break;
     472          50 :             case PASSGEN_PATTERN_LITERAL:
     473          50 :                 try(passgen_generate_literal(context, &item->data.literal));
     474          50 :                 break;
     475           0 :             case PASSGEN_PATTERN_SPECIAL:
     476           0 :                 try(passgen_generate_special(context, &item->data.special));
     477           0 :                 break;
     478          25 :             case PASSGEN_PATTERN_GROUP:
     479          25 :                 try(passgen_generate_group(context, &item->data.group));
     480          22 :                 break;
     481           0 :             default:
     482           0 :                 passgen_assert(false);
     483           0 :                 break;
     484             :         }
     485             :     }
     486             : 
     487             :     // unreachable
     488          81 :     return 0;
     489             : }
     490             : 
     491          86 : static int passgen_generate_segment(
     492             :     passgen_generate_context *context,
     493             :     const passgen_pattern_segment *segment) {
     494         168 :     for(size_t i = 0; i < segment->items.len; i++) {
     495          85 :         passgen_pattern_item *item = passgen_stack_get(&segment->items, i);
     496             : 
     497          85 :         try(passgen_generate_item(context, item));
     498             :     }
     499             : 
     500          83 :     return 0;
     501             : }
     502             : 
     503          89 : static int passgen_generate_group(
     504             :     passgen_generate_context *context,
     505             :     const passgen_pattern_group *group) {
     506             :     // descend in depth
     507          89 :     try(descend(context));
     508             : 
     509          86 :     if(group->multiplier_sum == 0) {
     510           0 :         return 1;
     511             :     }
     512             : 
     513             :     // choose random segment from segments
     514             :     size_t choice =
     515          86 :         passgen_random_u64_max(context->env->random, group->multiplier_sum);
     516          86 :     size_t segment_index = 0;
     517             :     passgen_pattern_segment *segment =
     518          86 :         passgen_stack_get(&group->segments, segment_index);
     519             : 
     520         100 :     while(choice >= segment->multiplier) {
     521          14 :         choice -= segment->multiplier;
     522          14 :         segment_index += 1;
     523          14 :         segment = passgen_stack_get(&group->segments, segment_index);
     524             :     }
     525             : 
     526             :     // keep track of entropy
     527          86 :     if(context->entropy) {
     528          44 :         *context->entropy *= group->multiplier_sum;
     529          44 :         *context->entropy /= segment->multiplier;
     530             :     }
     531             : 
     532          86 :     try(passgen_generate_segment(context, segment));
     533             : 
     534          83 :     ascend(context);
     535             : 
     536          83 :     return 0;
     537             : }
     538             : 
     539          64 : int passgen_generate(
     540             :     const passgen_pattern *pattern,
     541             :     passgen_env *env,
     542             :     double *entropy,
     543             :     void *data,
     544             :     passgen_generate_cb *func) {
     545             :     // when entropy collection is request (by passing a non-NULL pointer),
     546             :     // initialize it.
     547          64 :     if(entropy) {
     548          31 :         *entropy = 1.0;
     549             :     }
     550             : 
     551          64 :     passgen_generate_context context = {
     552             :         .env = env,
     553          64 :         .depth = env->depth_limit,
     554             :         .func = func,
     555             :         .data = data,
     556             :         .entropy = entropy,
     557             :     };
     558             : 
     559          64 :     return passgen_generate_group(&context, &pattern->group);
     560             : }

Generated by: LCOV version 1.14