LCOV - code coverage report
Current view: top level - src - generate.c (source / functions) Hit Total Coverage
Test: passgen-test.info Lines: 106 214 49.5 %
Date: 2024-05-03 06:05:14 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         146 : static inline int emit(passgen_generate_context *context, uint32_t codepoint) {
      32         146 :     return context->func(context->data, codepoint);
      33             : }
      34             : 
      35             : // Recurse
      36          90 : static inline int descend(passgen_generate_context *context) {
      37          90 :     if(!context->depth) {
      38           3 :         return 1;
      39             :     }
      40             : 
      41          87 :     context->depth -= 1;
      42          87 :     return 0;
      43             : }
      44             : 
      45          84 : static inline void ascend(passgen_generate_context *context) {
      46          84 :     context->depth += 1;
      47          84 : }
      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          94 : static int passgen_generate_write_buffer_utf8(void *data, uint32_t codepoint) {
     111          94 :     struct fillpos_utf8 *fillpos = data;
     112             : 
     113          94 :     if(fillpos->cur == fillpos->len) {
     114           0 :         return -1;
     115             :     }
     116             : 
     117          94 :     if((fillpos->cur + 4) <= fillpos->len) {
     118          94 :         int bytes = passgen_utf8_encode_codepoint(
     119          94 :             (uint8_t *) &fillpos->buffer[fillpos->cur],
     120             :             codepoint);
     121             : 
     122          94 :         if(bytes < 0) {
     123             :             // error happened during encoding.
     124           0 :             return -1;
     125             :         }
     126             : 
     127          94 :         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          94 :     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 :     try(passgen_generate(
     218             :         pattern,
     219             :         env,
     220             :         entropy,
     221             :         &fillpos,
     222             :         passgen_generate_write_buffer));
     223             : 
     224          28 :     return fillpos.cur;
     225             : }
     226             : 
     227          31 : size_t passgen_generate_fill_utf8(
     228             :     const passgen_pattern *pattern,
     229             :     passgen_env *env,
     230             :     double *entropy,
     231             :     uint8_t *buffer,
     232             :     size_t len) {
     233          31 :     struct fillpos_utf8 fillpos = {
     234             :         .buffer = buffer,
     235             :         .len = len,
     236             :         .cur = 0,
     237             :     };
     238             : 
     239          31 :     try(passgen_generate(
     240             :         pattern,
     241             :         env,
     242             :         entropy,
     243             :         &fillpos,
     244             :         passgen_generate_write_buffer_utf8));
     245             : 
     246          31 :     return fillpos.cur;
     247             : }
     248             : 
     249           0 : size_t passgen_generate_fill_json_utf8(
     250             :     const passgen_pattern *pattern,
     251             :     passgen_env *env,
     252             :     double *entropy,
     253             :     uint8_t *buffer,
     254             :     size_t len) {
     255           0 :     struct fillpos_utf8 fillpos = {
     256             :         .buffer = buffer,
     257             :         .len = len,
     258             :         .cur = 0,
     259             :     };
     260             : 
     261           0 :     try(passgen_generate(
     262             :         pattern,
     263             :         env,
     264             :         entropy,
     265             :         &fillpos,
     266             :         passgen_generate_write_buffer_json_utf8));
     267             : 
     268           0 :     return fillpos.cur;
     269             : }
     270             : 
     271          85 : static size_t passgen_generate_repeat(
     272             :     passgen_generate_context *context,
     273             :     const passgen_pattern_repeat *repeat) {
     274          85 :     size_t difference = repeat->max - repeat->min;
     275             : 
     276             :     // if there is no difference to pick, just return here
     277          85 :     if(0 == difference) {
     278          79 :         return repeat->min;
     279             :     }
     280             : 
     281             :     // get random number to choose from the range
     282             :     size_t choice =
     283           6 :         passgen_random_u64_max(context->env->random, difference + 1);
     284             : 
     285             :     // keep track of entropy
     286           6 :     if(context->entropy) {
     287           4 :         *context->entropy *= difference + 1;
     288             :     }
     289             : 
     290           6 :     return repeat->min + choice;
     291             : }
     292             : 
     293          28 : static int passgen_generate_set(
     294             :     passgen_generate_context *context,
     295             :     const passgen_pattern_set *set) {
     296             :     // if this set is empty, we're done.
     297          28 :     if(set->items.len == 0) {
     298           0 :         return 0;
     299             :     }
     300             : 
     301             :     // compute number of possible codepoints
     302             :     // TODO: generate this on the fly or on demand?
     303          28 :     size_t possible = set->choices_list[set->items.len - 1];
     304             : 
     305          28 :     passgen_assert(possible != 0);
     306             : 
     307          28 :     size_t choice = passgen_random_u64_max(context->env->random, possible);
     308             : 
     309             :     // keep track of entropy
     310          28 :     if(context->entropy) {
     311          14 :         *context->entropy *= possible;
     312             :     }
     313             : 
     314             :     // locate choice in list of choices.
     315             :     // TODO: binary search.
     316             :     size_t num;
     317          42 :     for(num = 0; num < set->items.len; num++) {
     318          42 :         if(choice < set->choices_list[num]) {
     319          28 :             break;
     320             :         }
     321             :     }
     322             : 
     323          28 :     passgen_assert(num != set->items.len);
     324             : 
     325             :     /* adjust choice to be relative offset */
     326          28 :     if(num) {
     327           8 :         choice -= set->choices_list[num - 1];
     328             :     }
     329             : 
     330          28 :     passgen_pattern_range *range = passgen_stack_get(&set->items, num);
     331             : 
     332          28 :     return emit(context, range->start + choice);
     333             : }
     334             : 
     335          52 : static int passgen_generate_literal(
     336             :     passgen_generate_context *context,
     337             :     const passgen_pattern_literal *literal) {
     338          52 :     passgen_assert(literal->count > 0);
     339          52 :     passgen_assert(literal->count < 8);
     340             : 
     341         170 :     for(size_t i = 0; i < literal->count; i++) {
     342         118 :         try(emit(context, literal->codepoints[i]));
     343             :     }
     344             : 
     345          52 :     return 0;
     346             : }
     347             : 
     348           0 : static int passgen_generate_special_markov(
     349             :     passgen_generate_context *context,
     350             :     const passgen_pattern_special *special) {
     351             :     passgen_hashmap_entry *entry =
     352           0 :         passgen_hashmap_lookup(&context->env->wordlists, special->parameters);
     353           0 :     if(!entry) {
     354           0 :         return -1;
     355             :     }
     356           0 :     passgen_wordlist *wordlist = entry->value;
     357           0 :     if(!wordlist->parsed) {
     358           0 :         passgen_wordlist_parse(wordlist);
     359             :     }
     360           0 :     if(!wordlist->parsed_markov) {
     361           0 :         passgen_wordlist_parse_markov(wordlist);
     362             :     }
     363           0 :     passgen_markov *markov = &wordlist->markov;
     364             :     uint32_t word[128];
     365           0 :     size_t pos = markov->level;
     366           0 :     memset(word, 0, pos * sizeof(uint32_t));
     367           0 :     double *entropy = context->entropy ? &*context->entropy : NULL;
     368             :     do {
     369           0 :         word[pos] = passgen_markov_generate(
     370             :             markov,
     371           0 :             &word[pos - markov->level],
     372           0 :             context->env->random,
     373             :             entropy);
     374           0 :         pos++;
     375           0 :     } while(word[pos - 1]);
     376             : 
     377           0 :     pos = markov->level;
     378           0 :     while(word[pos]) {
     379           0 :         try(emit(context, word[pos]));
     380           0 :         pos++;
     381             :     }
     382             : 
     383           0 :     return 0;
     384             : }
     385             : 
     386           0 : static int passgen_generate_special_wordlist(
     387             :     passgen_generate_context *context,
     388             :     const passgen_pattern_special *special) {
     389             :     passgen_hashmap_entry *entry =
     390           0 :         passgen_hashmap_lookup(&context->env->wordlists, special->parameters);
     391           0 :     if(!entry) {
     392           0 :         return -1;
     393             :     }
     394           0 :     passgen_wordlist *wordlist = entry->value;
     395           0 :     if(!wordlist->parsed) {
     396           0 :         passgen_wordlist_parse(wordlist);
     397             :     }
     398           0 :     const char *word = passgen_wordlist_random(wordlist, context->env->random);
     399           0 :     while(*word) {
     400           0 :         try(emit(context, *word));
     401           0 :         word++;
     402             :     }
     403             : 
     404           0 :     if(context->entropy) {
     405           0 :         *context->entropy *= passgen_wordlist_count(wordlist);
     406             :     }
     407             : 
     408           0 :     return 0;
     409             : }
     410             : 
     411           0 : static int passgen_generate_special_preset(
     412             :     passgen_generate_context *context,
     413             :     const passgen_pattern_special *special) {
     414             :     (void) special;
     415             :     // TODO: implement
     416           0 :     return 0;
     417             : }
     418             : 
     419           0 : static int passgen_generate_special(
     420             :     passgen_generate_context *context,
     421             :     const passgen_pattern_special *special) {
     422           0 :     switch(special->kind) {
     423           0 :         case PASSGEN_PATTERN_SPECIAL_MARKOV:
     424           0 :             return passgen_generate_special_markov(context, special);
     425           0 :         case PASSGEN_PATTERN_SPECIAL_WORDLIST:
     426           0 :             return passgen_generate_special_wordlist(context, special);
     427           0 :         case PASSGEN_PATTERN_SPECIAL_PRESET:
     428           0 :             return passgen_generate_special_preset(context, special);
     429           0 :         default:
     430           0 :             return 1;
     431             :     }
     432             :     return 0;
     433             : }
     434             : 
     435          86 : static int passgen_generate_item(
     436             :     passgen_generate_context *context,
     437             :     const passgen_pattern_item *item) {
     438             :     // if it is a maybe (has a question mark following it), decide first if we
     439             :     // want to emit it or not.
     440          86 :     if(item->maybe) {
     441           6 :         if(!passgen_random_bool(context->env->random)) {
     442           1 :             return 0;
     443             :         }
     444             :     }
     445             : 
     446             :     // compute random number of repetitions
     447          85 :     size_t reps = passgen_generate_repeat(context, &item->repeat);
     448             : 
     449         188 :     for(size_t i = 0; i < reps; i++) {
     450         106 :         switch(item->kind) {
     451          28 :             case PASSGEN_PATTERN_SET:
     452          28 :                 try(passgen_generate_set(context, &item->data.set));
     453          28 :                 break;
     454          52 :             case PASSGEN_PATTERN_LITERAL:
     455          52 :                 try(passgen_generate_literal(context, &item->data.literal));
     456          52 :                 break;
     457           0 :             case PASSGEN_PATTERN_SPECIAL:
     458           0 :                 try(passgen_generate_special(context, &item->data.special));
     459           0 :                 break;
     460          26 :             case PASSGEN_PATTERN_GROUP:
     461          26 :                 try(passgen_generate_group(context, &item->data.group));
     462          23 :                 break;
     463           0 :             default:
     464           0 :                 passgen_assert(false);
     465           0 :                 break;
     466             :         }
     467             :     }
     468             : 
     469             :     // unreachable
     470          82 :     return 0;
     471             : }
     472             : 
     473          87 : static int passgen_generate_segment(
     474             :     passgen_generate_context *context,
     475             :     const passgen_pattern_segment *segment) {
     476         170 :     for(size_t i = 0; i < segment->items.len; i++) {
     477          86 :         passgen_pattern_item *item = passgen_stack_get(&segment->items, i);
     478             : 
     479          86 :         try(passgen_generate_item(context, item));
     480             :     }
     481             : 
     482          84 :     return 0;
     483             : }
     484             : 
     485          90 : static int passgen_generate_group(
     486             :     passgen_generate_context *context,
     487             :     const passgen_pattern_group *group) {
     488             :     // descend in depth
     489          90 :     try(descend(context));
     490             : 
     491          87 :     if(group->multiplier_sum == 0) {
     492           0 :         return 1;
     493             :     }
     494             : 
     495             :     // choose random segment from segments
     496             :     size_t choice =
     497          87 :         passgen_random_u64_max(context->env->random, group->multiplier_sum);
     498          87 :     size_t segment_index = 0;
     499             :     passgen_pattern_segment *segment =
     500          87 :         passgen_stack_get(&group->segments, segment_index);
     501             : 
     502         103 :     while(choice >= segment->multiplier) {
     503          16 :         choice -= segment->multiplier;
     504          16 :         segment_index += 1;
     505          16 :         segment = passgen_stack_get(&group->segments, segment_index);
     506             :     }
     507             : 
     508             :     // keep track of entropy
     509          87 :     if(context->entropy) {
     510          45 :         *context->entropy *= group->multiplier_sum;
     511          45 :         *context->entropy /= segment->multiplier;
     512             :     }
     513             : 
     514          87 :     try(passgen_generate_segment(context, segment));
     515             : 
     516          84 :     ascend(context);
     517             : 
     518          84 :     return 0;
     519             : }
     520             : 
     521          64 : int passgen_generate(
     522             :     const passgen_pattern *pattern,
     523             :     passgen_env *env,
     524             :     double *entropy,
     525             :     void *data,
     526             :     passgen_generate_cb *func) {
     527             :     // when entropy collection is request (by passing a non-NULL pointer),
     528             :     // initialize it.
     529          64 :     if(entropy) {
     530          31 :         *entropy = 1.0;
     531             :     }
     532             : 
     533          64 :     passgen_generate_context context = {
     534             :         .env = env,
     535          64 :         .depth = env->depth_limit,
     536             :         .func = func,
     537             :         .data = data,
     538             :         .entropy = entropy,
     539             :     };
     540             : 
     541          64 :     return passgen_generate_group(&context, &pattern->group);
     542             : }

Generated by: LCOV version 1.14