I'm writing a C application for the Networking course at the University. Basically it's a UDP server that can receive two commands:
GET key
SET key value
That are executed against a Hash Table data structure. I'm now writing the code that parses the string into a command struct, and the logic is the following:
- A command cannot be empty and is recognized only if it starts with
GET
orSET
- A key cannot be null and cannot be longer than 100 characters.
- In a
SET
command the data cannot be null
The code seems to work so far, but I'm not so skilled in writing C code, so I'm worried about some hidden bugs or mistakes that I did.
So, if someone can review my code it will be greatly appreciated!
The header file:
#ifndef DUCKY_COMMAND_H
#define DUCKY_COMMAND_H
#include <stddef.h>
#define MAX_KEY_LENGTH 100
#define ERR_COMMAND_NOT_RECOGNIZED -1
#define ERR_KEY_LENGTH -2
#define ERR_NO_KEY -3
#define ERR_NO_DATA -4
typedef struct command {
enum {
GET, SET
} command_type;
char *key;
char *data;
} command;
int parse_command(char *buffer, command *c);
#endif
The function that parses a string into a command struct:
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include "command.h"
int parse_command(char * buffer, command* c) {
int i = 0;
char *delimiter = " ";
char *token = strtok(buffer, delimiter);
char **arr = calloc(3, sizeof(char *)); // I know that a command can have at maximum 3 tokens
// Split the string into an array of strings
while (token != NULL) {
token[strcspn(token, "\n")] = '\0'; // Remove trailing new lines
arr[i] = calloc(1, strlen(token) + 1);
strcpy(arr[i], token);
token = strtok(NULL, delimiter); // Next token
i++;
}
if (strcmp(arr[0], "SET") != 0 && strcmp(arr[0], "GET") != 0) {
return ERR_COMMAND_NOT_RECOGNIZED;
}
// Parse SET command
if (strcmp(arr[0], "SET") == 0) {
if (arr[1] == NULL) {
return ERR_NO_KEY;
}
// Validate key length
if (strlen(arr[1]) > MAX_KEY_LENGTH) {
return ERR_KEY_LENGTH;
}
// Validate data
if (arr[2] == NULL) {
return ERR_NO_DATA;
}
c->command_type = SET;
c->key = arr[1];
c->data = arr[2];
}
// Parse GET command
if (strcmp(arr[0], "GET") == 0) {
if (arr[1] == NULL) {
return ERR_NO_KEY;
}
// Validate key length
if (strlen(arr[1]) > MAX_KEY_LENGTH) {
return ERR_KEY_LENGTH;
}
c->command_type = GET;
c->key = arr[1];
}
return 0;
}
Here are the unit tests (for which I'm using greatest):
#include "../lib/greatest.h"
#include "../src/command.h"
TEST should_parse_a_SET_command_from_a_string(void) {
command c;
char buffer[] = "SET key string";
int result = parse_command(buffer, &c);
ASSERT_EQ(0, result);
ASSERT_EQ(SET, c.command_type);
ASSERT_STR_EQ("key", c.key);
ASSERT_STR_EQ("string", c.data);
PASS();
}
TEST should_return_ERR_NO_KEY_if_the_SET_command_has_not_an_associated_key(void) {
command c;
char buffer[] = "SET";
int result = parse_command(buffer, &c);
ASSERT_EQ(ERR_NO_KEY, result);
PASS();
}
TEST should_return_ERR_NO_DATA_if_the_SET_command_has_no_data_associated(void) {
command c;
char buffer[] = "SET key ";
int result = parse_command(buffer, &c);
ASSERT_EQ(ERR_NO_DATA, result);
PASS();
}
TEST should_parse_a_GET_command_from_a_string(void) {
command c;
char buffer[] = "GET key";
int result = parse_command(buffer, &c);
ASSERT_EQ(0, result);
ASSERT_EQ(GET, c.command_type);
ASSERT_STR_EQ("key", c.key);
PASS();
}
TEST should_return_ERR_NO_KEY_if_the_GET_command_has_not_an_associated_key(void) {
command get;
char buffer[] = "GET";
int result = parse_command(buffer, &get);
ASSERT_EQ(ERR_NO_KEY, result);
PASS();
}
TEST should_return_ERR_KEY_LENGTH_if_the_GET_or_SET_key_length_is_greater_than_100_chars(void) {
int result;
command get, set;
char get_buffer[] = "GET abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdebcde 10 string";
char set_buffer[] = "SET abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdebcde 10 string";
result = parse_command(get_buffer, &get);
ASSERT_EQ(ERR_KEY_LENGTH, result);
result = parse_command(set_buffer, &set);
ASSERT_EQ(ERR_KEY_LENGTH, result);
PASS();
}
TEST should_return_ERR_COMMAND_NOT_RECOGNIZED_if_the_command_is_not_recognized() {
command c;
char buffer[] = "FOO BAR key 10 20";
int result = parse_command(buffer, &c);
ASSERT_EQ(ERR_COMMAND_NOT_RECOGNIZED, result);
PASS();
}
SUITE(suite) {
RUN_TEST(should_parse_a_SET_command_from_a_string);
RUN_TEST(should_return_ERR_NO_KEY_if_the_SET_command_has_not_an_associated_key);
RUN_TEST(should_return_ERR_NO_DATA_if_the_SET_command_has_no_data_associated);
RUN_TEST(should_parse_a_GET_command_from_a_string);
RUN_TEST(should_return_ERR_NO_KEY_if_the_GET_command_has_not_an_associated_key);
RUN_TEST(should_return_ERR_KEY_LENGTH_if_the_GET_or_SET_key_length_is_greater_than_100_chars);
RUN_TEST(should_return_ERR_COMMAND_NOT_RECOGNIZED_if_the_command_is_not_recognized);
}
GREATEST_MAIN_DEFS();
int main(int argc, char *argv[]) {
GREATEST_MAIN_BEGIN();
RUN_SUITE(suite);
GREATEST_MAIN_END();
}