That is a simple program to hide text messages in text files. I'd like to hear any advices to improve my code and style.
Some notes:
- There is no error handling to simplify the program. It is not a «real» program anyway. There is many assertions instead error handling.
- I wrote my own implementations to
strdup
andstrrev
because these functions are not a part of ISO C. I called they asreverseString
andduplicateString
corresponding since names likestr*
is reserved by ISO C. - I check the validity of input arguments in functions to prevent mistakes of using internal API.
- All the functions marked as
static
because all they in a single translation unit.
And one another question: the input file should be opened in text mode while temporary file is always opens in binary mode (w+b
). Could it lead to some problems?
Compiler
I'm using Clang to compile the program with such flags:
-std=c11
-Weverything
-Wpedantic
-fsanitize=address
-D_CRT_SECURE_NO_WARNINGS
It gives only one warning:
warning: implicit conversion turns floating-point number into integer: 'double' to 'size_t' (aka 'unsigned int') [-Wfloat-conversion]
const size_t newCapacity = ceil(s->capacity * DYNAMIC_STRING_GROW_FACTOR);
Static code analyzer
Also I have checked the code by CppCheck and it gives only one error: «Memory leak: ret
in function stringToBinary
»:
char *stringToBinary(const char *s)
{
assert(s);
assert(strlen(s) > 0);
char *ret = calloc((strlen(s) + 1) * CHAR_BIT + 1, 1);
assert(ret);
for (size_t i = 0; s[i] != '\0'; i++)
strcat(ret, charToBinary(s[i]));
return strcat(ret, charToBinary('\0'));
}
But I think it is a false positive because ret
is freed in the hideMessage
function:
char *msgBinary = stringToBinary(msg);
...
free(msgBinary);
I'll be glad if you give me any tips to improve my code or correct my mistakes. Thanks you in advance! Best regards :)
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <assert.h>
#define DYNAMIC_STRING_GROW_FACTOR 1.5
typedef struct DynamicString {
char *s;
size_t capacity;
size_t length;
} DynamicString;
static void createDynamicString(DynamicString *, size_t);
static void destroyDynamicString(DynamicString *);
static void increaseDynamicStringCapacity(DynamicString *);
static void appendCharToDynamicString(DynamicString *, char);
static void hideMessage(FILE *, const char *);
static char *extractMessage(FILE *);
static void copyFile(FILE *, FILE *);
static char *stringToBinary(const char *);
static char *charToBinary(char);
static char charFromBinary(const char *);
static char *reverseString(char *);
static char *duplicateString(const char *s);
int main(void)
{
FILE *file = fopen("file.txt", "r+");
assert(file);
hideMessage(file, "hello, world");
char *msg = extractMessage(file);
assert(msg);
puts(msg);
free(msg);
fclose(file);
}
/* The HIDEMESSAGE function
*
* The function hides text message into the file by that way: 1) the message
* converts to binary; 2) each bit of the converted message writes at the end
* of each file's line (right before the new-line character): if the bit
* is 1 then a space (' ') appends to the line otherwise it's nothing appends
* to the line.
*
* Assumes the the file does not contain any spaces right before new-line
* characters. Also assumes that the file has enough lines for storing
* the message.
*/
void hideMessage(FILE *f, const char *msg)
{
assert(f);
assert(msg);
assert(strlen(msg) > 0);
char *msgBinary = stringToBinary(msg);
assert(msgBinary);
FILE *tempFile = tmpfile();
assert(tempFile);
for (int ch, i = 0; (ch = fgetc(f)) != EOF;) {
if (msgBinary[i] && (ch == '\n'))
if (msgBinary[i++] == '1')
fputc(' ', tempFile);
fputc(ch, tempFile);
}
copyFile(f, tempFile);
free(msgBinary);
fclose(tempFile);
}
/* The EXTRACTMESSAGE function
*
* The function extracts message hidden by the HIDEMESSAGE function from
* the input file and returns a pointer to heap-allocated string which
* contains the message.
*/
char *extractMessage(FILE *f)
{
assert(f);
DynamicString msgBuffer;
createDynamicString(&msgBuffer, 128);
char charInBinary[CHAR_BIT + 1] = {0};
for (int prevCh = 0, ch, i = 0; (ch = fgetc(f)) != EOF; prevCh = ch) {
if (ch == '\n')
charInBinary[i++] = (prevCh == ' ') ? '1' : '0';
if (i % CHAR_BIT == 0 && i != 0) {
if (!strcmp(charInBinary, charToBinary('\0')))
break;
i = 0;
appendCharToDynamicString(&msgBuffer, charFromBinary(charInBinary));
}
}
char *ret = duplicateString(msgBuffer.s);
assert(ret);
destroyDynamicString(&msgBuffer);
return ret;
}
/* The CREATEDYNAMICSTRING function
*
* The function initializes a DynamicString passing by the first argument.
* The initial capacity of the string is passing by the second argument.
* Capacity is the max length of the string. At the same time length is
* current length of the string. Thus the function allocates capacity + 1
* bytes for the string (considering the null-character).
*
* The input pointer to DynamicString struture should be a valid pointer and
* capacity should be greater than 0.
*/
void createDynamicString(DynamicString *ret, size_t capacity)
{
assert(ret);
assert(capacity > 0);
ret->s = malloc(capacity + 1);
assert(ret->s);
ret->length = 0;
ret->capacity = capacity;
}
/* The APPENDCHARTODYNAMICSTRING function
*
* The function appends a character to the input DynamicString. If capacity of
* the string is not enough the function increases it.
*
* The input pointer to a DynamicString should be a valid pointer as well as
* its string buffer.
*/
void appendCharToDynamicString(DynamicString *s, char c)
{
assert(s);
assert(s->s);
if (s->length == s->capacity)
increaseDynamicStringCapacity(s);
s->s[s->length++] = c;
s->s[s->length] = '\0';
}
/* The INCREASEDYNAMICSTRINGCAPACITY function
*
* The function increases capacity of the input DynamicString. Grow factor
* is sets by a macro constant DYNAMIC_STRING_GROW_FACTOR.
*
* The input pointer to a DynamicString struture should be a valid pointer
* as well as its string buffer.
*/
void increaseDynamicStringCapacity(DynamicString *s)
{
assert(s);
assert(s->s);
const size_t newCapacity = ceil(s->capacity * DYNAMIC_STRING_GROW_FACTOR);
s->s = realloc(s->s, newCapacity + 1);
assert(s->s);
s->capacity = newCapacity;
}
/* The DESTROYDYNAMICSTRING function
*
* The function destroys the input DynamicString. It frees the string buffer
* of the input DynamicString.
*
* The input pointer to a DynamicString should be a valid pointer as well as
* its string buffer.
*/
void destroyDynamicString(DynamicString *s)
{
assert(s);
assert(s->s);
free(s->s);
}
/* The COPYFILE function
*
* The function copies all the contents of src to dest. Both arguments should
* be valid pointers. dest should be open for writing, src should be open
* for reading. The function does not close the files. The both file cursor
* position sets to the beginning.
*/
void copyFile(FILE *dest, FILE *src)
{
assert(dest);
assert(src);
rewind(dest);
rewind(src);
for (int ch; (ch = fgetc(src)) != EOF;)
fputc(ch, dest);
rewind(dest);
rewind(src);
}
/* The CHARFROMBINARY function
*
* The function converts the input string returned by the CHARTOBINARY function
* to a character.
*
* The input string should be a valid null-terminated string and its length
* should be greater 0.
*
* charFromBinary(charToBinary(c)) == c
*/
char charFromBinary(const char *s)
{
assert(s);
assert(strlen(s) > 0);
char ret = 0;
unsigned int p = 1;
for (size_t i = strlen(s); i-- > 0; p *= 2)
if (s[i] == '1')
ret += p;
return ret;
}
/* The STRINGTOBINARY function
*
* The function converts the input string to binary form and returns a pointer
* to heap-allocated null-terminated string. Null-terminator of the input
* string also converts to binary form and appends to the result. The caller
* should free memory allocated for the output string.
*
* The input string should be a valid null-terminated string and its length
* should be greater 0.
*
* stringToBinary("cat") => "01100011011000010111010000000000"
* stringToBinary("dog") => "01100100011011110110011100000000"
* stringToBinary("R\0") => "0101001000000000"
*/
char *stringToBinary(const char *s)
{
assert(s);
assert(strlen(s) > 0);
char *ret = calloc((strlen(s) + 1) * CHAR_BIT + 1, 1);
assert(ret);
for (size_t i = 0; s[i] != '\0'; i++)
strcat(ret, charToBinary(s[i]));
return strcat(ret, charToBinary('\0'));
}
/* The CHARTOBINARY function
*
* The function converts value of the input character to binary form and
* returns a pointer to the static null-terminated string which contains
* the result. The result contains leading zeroes.
*
* charToBinary(100) => "01100100"
* charToBinary('A') => "01000001"
* charToBinary('\0') => "00000000"
*/
char *charToBinary(char c)
{
static char ret[CHAR_BIT + 1];
memset(ret, '0', sizeof ret);
ret[sizeof ret - 1] = '\0';
for (size_t i = 0; c; i++) {
ret[i] = (c % 2) ? '1' : '0';
c /= 2;
}
return reverseString(ret);
}
/* The REVERSESTRING function
*
* The input string should be a valid pointer to a null-terminated string.
* If the input string is empty the function does noting.
*
* reverseString("hello") => "olleh"
* reverseString("a") => "a"
* reverseString("") => "a"
*/
char *reverseString(char *s)
{
assert(s);
char *begin = s;
char *end = s + strlen(s) - 1;
for (; begin < end; begin++, end--) {
const char t = *begin;
*begin = *end;
*end = t;
}
return s;
}
/* The DUPLICATESTRING function
*
* The function returns a pointer to a heap-allocated string, which is a
* duplicate of the input string. The returned pointer can be passed to the
* free function.
*/
char *duplicateString(const char *s)
{
assert(s);
assert(strlen(s) > 0);
char *copy = malloc(strlen(s) + 1);
assert(copy);
return strcpy(copy, s);
}
P.S. I'm not a native English speaker so… sorry for the inconvenience.