#include "corrupter.h" /* cached operations */ const size_t UINT16_MAX_PLUS_1 = UINT16_MAX + 1; /* function definitions */ static bool get_chance(uint16_t desired_chance); static bool is_line_ending(byte c); /* function implementations */ static bool get_chance(uint16_t desired_chance) { /* TODO: remove this multi-line comment before merging into main branch // algorithm v1 // calculate the cumulative distribution function (CDF) uint8_t cdf[UINT8_MAX_PLUS_1]; memset(cdf, 0, sizeof(cdf)); for (uint8_t i = 0; i < UINT8_MAX; i++) cdf[i] = cdf[i] + ((i - 1) <= desired_chance); // generate a random number in the range of 0 to the total weight uint8_t random_number = mt_next() & UINT8_MAX; // use the CDF to determine the outcome for (uint8_t i = 0; i < UINT8_MAX; i++) if (random_number < cdf[i]) return true; return (random_number < cdf[UINT8_MAX] || random_number < cdf[UINT8_MAX_PLUS_1]); */ // algorithm v2 if (desired_chance == UINT16_MAX) return true; uint16_t cdf[UINT16_MAX_PLUS_1]; memset(cdf, 0, sizeof(cdf)); for (uint16_t i = 0; i < UINT16_MAX; i++) { cdf[i] = (i <= desired_chance) ? i + 1 : cdf[i - 1]; } uint16_t random_number = mt_next() & UINT16_MAX; return random_number < cdf[desired_chance]; } static bool is_line_ending(byte c) { return (c == '\n' || c == '\r'); } Corrupter_Result* corrupt_file(Corrupter_Param* param) { Corrupter_Result* result = malloc(sizeof(Corrupter_Result)); if (result == NULL) return NULL; // copy and cast parameters FILE* file = param->file; uint8_t probability = param->probability; off_t threshold = (off_t) param->threshold; uint8_t config = param->config; // refuse to operate on non-seekable streams if (fseeko(file, 0L, SEEK_SET) != 0 || ftello(file) != 0) { errno = ESPIPE; result->error = true; return result; } // determine file size off_t start = 0, end; fseeko(file, 0L, SEEK_END); end = ftello(file); // determine file boundaries const char* failed_function = NULL; file_boundaries_t* boundaries = NULL; switch (param->type) { case FILE_TYPE_BMP: boundaries = determine_boundaries_BMP(file); failed_function = "determine_boundaries_BMP"; goto File_Type_General_Case; case FILE_TYPE_WAV: boundaries = determine_boundaries_WAV(file); failed_function = "determine_boundaries_WAV"; File_Type_General_Case: if (boundaries == NULL) { PERROR_MACRO(failed_function); exit(EXIT_FAILURE); return NULL; } break; default: break; } // output data to result result->file_size = end; result->damaged_bytes = 0; // initialize the PRNG mt_seed(param->seed); for (size_t pass = 0; pass < param->passes; pass++) { // display information printf("Pass: %zu/%zu", pass + 1, param->passes); fflush(stdout); rewind(file); // iterate over the file contents for (off_t i = start; i < end; i++) { fseeko(file, i, SEEK_SET); byte byte_value; if (fread(&byte_value, sizeof(byte), 1, file) != 1) { result->error = true; fclose(file); return result; } if (READ_CONFIG(C_PRINTABLE) && !isprint(byte_value)) continue; else if (READ_CONFIG(C_LINE_ENDINGS) && is_line_ending(byte_value)) continue; // generate bit mask unsigned char damage_left = (unsigned char) param->threshold; static bool bit_mask[CHAR_BIT]; for (unsigned char bit = 0; bit < CHAR_BIT; bit++) { if (get_chance(probability) && (damage_left > 0)) { bit_mask[bit] = true; damage_left--; } else { bit_mask[bit] = false; } } bool damaged_byte = false; for (unsigned char bit = 0; bit < threshold; bit++) { if (bit_mask[bit] == false) continue; byte_value = FLIP_BIT(byte_value, bit); result->hit_counter++; damaged_byte = true; // write the modified byte back to the file fseeko(file, i, SEEK_SET); if (fwrite(&byte_value, sizeof(byte), 1, file) == 1) continue; // on error result->error = true; fclose(file); return result; } if (damaged_byte) result->damaged_bytes++; } puts(" [OK]"); } result->error = false; return result; }