166 lines
4 KiB
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;
|
|
}
|