A friend of mine asked some questions relating to socket programming which spurred me to create a simple example based on an application I wrote a while back. It’s pretty basic stuff but I decided to post it here in case anyone has any feedback to offer.
I use flexible arrays where suitable so warm up your C99-compilers. There is virtually no error checking done in the example so be vary, also I didn’t compile it.
static const uint32_t PROTOCOL_MAGIC=0xdead001;
struct HelloHeader {
uint32_t magic;
uint32_t version;
};
enum Command {
Command_NONE,
Command_FILE_LIST,
Command_GET_FILE,
};
struct CmdHeader {
uint32_t cmd;
};
struct File {
char name[256];
uint64_t size;
};
struct FileList {
uint32_t numFiles;
File[] files;
};
struct GetFile {
uint64_t len;
/* null-terminated filename followed by data */
char[256] name;
char[] data;
};
uint64_t ntohll(uint64_t n)
{
#ifdef BIG_ENDIAN
return n;
#else
return (((uint64_t) ntohl(n)) << 32) + ntohl(n >> 32);
#endif
}
char* processCommand000301(char* buffer, size_t bufferLen)
{
/* ... */
}
char* processCommand010000(char* buffer, size_t bufferLen)
{
char* ptr = buffer;
struct CmdHeader* commandHeader = (struct CmdHeader*) ptr;
commandHeader->cmd = ntohl(commandHeader->cmd);
ptr += sizeof(struct CmdHeader);
switch (commandHeader->cmd) {
case Command_FILE_LIST: {
struct FileList* fileList = (struct FileList*) ptr;
fileList->numFiles = ntohl(fileList->numFiles);
ptr += sizeof(struct FileList);
for (int i = 0; i < fileList.numFiles; ++i) {
fileList.files[i].size = ntohll(fileList.files[i].size);
printf("%s %lld\n", fileList.files[i].name, fileList.files[i].size);
ptr += sizeof(struct File);
}
break;
}
case Command_GET_FILE: {
struct GetFile* getFile = (struct GetFile*) ptr;
getFile->len = ntohll(getFile->len);
FILE* file = fopen(getFile.name, "w");
fwrite(ptr, 1, getFile.len, file);
ptr += getFile.len;
fclose(file);
break;
}
default:
/* Unrecognized command! */
return NULL;
}
return ptr;
}
int processBuffer(char* buffer, size_t bufferLen)
{
char* ptr = buffer;
struct HelloHeader* helloHeader = (struct HelloHeader*) buffer;
helloHeader->magic = ntohl(helloHeader->magic);
helloHeader->version = ntohl(helloHeader->version);
if (helloHeader->magic != PROTOCOL_MAGIC) {
/* Invalid header! */
return -1;
}
ptr += sizeof(struct HelloHeader);
while (ptr < (buffer + bufferLen)) {
switch (helloHeader->version) {
case 0x00010000:
/* version 1.0.0 */
ptr = processCommand010000(buffer, (size_t) ptr - buffer);
break;
case 0x00000301:
/* version 0.3.1 */
ptr = processCommand000301(buffer, (size_t) ptr - buffer);
break;
default:
/* Unsupported version! */
return -1;
}
}
return 0;
}
P.S. I’m impressed by WordPress’ ability to consistently botch preformatted text when using the WYSIWYG editor. D.S.
What is the purpose of the ntohll function?
This was initially written for a network protocol so I assumed all data types were in network byte order during transmission. POSIX does not define a function for converting 64-bit values from network byte order so that function implements it.
I see now that it isn’t endian safe though so I will update the listing.
If you want to be extra careful you should probably memcpy the structures out of the buffers to a local representation to avoid alignment issues.