polonium/src/corrupter.c

166 lines
4 KiB
C

#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;
}