XML Parsing in C

In order to program an EPP Client in C I need to be able to parse XML.

Recap

In my article Creating a Nominet-Compatible EPP Client I created a simple TLS HTTP(S) client. It doesn't have much functionality, but that is because there is no interaction.

In the follow-up article TCP Sockets in C I created a TCP server which uses a header like EPP does to determine how much it needs to read from a buffer.

In this article I am going to take a look at parsing XML in C, including how to deconstruct XML and how to (re)construct XML.

Extending the TCP Server

The TCP server I wrote seems like the logical place to add the code for XML parsing as it doesn't rely on external servers. If I were to add it to the EPP client there is a good chance I'd end up with bad code getting me booted from Nominet's EPP Testbed server in the same way a bad loop once got me AKILLed on IRC.

After reading everything from the buffer, it needs to:

  1. Check the XML is valid.
  2. Take apart the XML.
  3. Add/remove things to/from the XML.
  4. Put the XML back together.
  5. Check the new XML is valid.
  6. Save the new XML in a file.

The reason I want to save the XML in a file is for record keeping. I will want a copy of every EPP client request (excluding logins) and every EPP server response.

The most logical file naming convention would be naming the files after the serial number (the client transaction identifier, or clTRID) of the request. The next obvious question then is how to format my clTRIDs.

EPP Client Transaction Identifier

Every clTRID will start with JC- so I can easily see files (requests/responses) that are suspect.

Next, the date in YYYYmmdd (%Y%m%d) format followed by a hyphen. For example JC-20151212-.

The next part entirely depends on how long the clTRID can be.

The element clTRID is of type epp:trIDStringType. That has a maximum length of 64 characters. I am using 12 characters so that leaves me with 52 characters before I hit the limit.

The next obvious thing to add is the time (UTC timezone) in the format HHMMSS (%H%M%S) followed by another hyphen. For example, JC-20151212-030441-. I am now up to 19 characters and have 45 characters remaining.

Since nanoseconds are the smallest resolution currently available, I can add them (%N) giving me something like JC-20151212-031719-292112505.

I might as well combine the entire date into one value (assuming I am using hyphen-separated values) so it will be JC-YYYYmmddTHHMMSS.N- or something that looks like JC-20151212T031839.196540572-. I am now up to 29 characters and have 35 remaining.

There is the possibility I will be running more than one client, and I might also have some sort of priority queuing in place. I will reserve a character for each of client 'number' and priority 'number'.

There are 95 printable ASCII characters, 90 of which I could use in XML without escaping, but I am also going to be using them in filenames. I am therefore going to go with alphanumeric characters (in regex, that would be [0-9A-Za-z]) giving me 10+26+26 = 62 possible values for each field. On second thought, let's give the client field a length of two and add a hyphen between it and the priority: JC-20151212T033250.336738936-00-0-. 34 characters, 30 remaining.

At this point I have my initials, the date and time down to the nanosecond, a client (3,844 possible) and a priority (62 possible). Natural sorting order would place the files in date order.

The only other thing that makes sense to include in the clTRID string would be the Nominet registrant ID (<contact:id>). At present it looks like these are a maximum of 8 digits long.

So, with a clTRID string of JC-20151212T035800.726528055-00-0-12345678 I am now at 42 characters. With 22 characters to spare I can afford to replace my initials with my Nominet Tag.

JOHNCOOK-20151212T035800.726528055-00-0-12345678 is 48 characters long, and in terms of hyphen-separated values is in the format of TAG, date, client number, priority number, registrant ID.

If Nominet were to go up to 16 digit client IDs (up to 10 quadrillion) then this format of clTRID string would support a registrar TAG up to a length of 16 characters.

With up to 64 characters used for the clTRID, all that is left to do is to decide on a file extension for the request and the response.

For my own sanity, I will want the extensions to make logical sense when ordered (i.e. the request comes before the response so the files should be likewise ordered).

.in does not come after .out, so that leaves .req and .resp, or .request and .response. I am not sure which I prefer.

I think I will go with .req and .resp as they are shorter and in the event I'm using a terminal window I am less likely to need to rely on the tab key.

That would give a filename of JOHNCOOK-20151212T043558.395675175-00-0-12345678.req for the EPP client request, and a filename of JOHNCOOK-20151212T043558.395675175-00-0-12345678.resp for the EPP server response.

The next question is at what point to I decide what the timestamp is? Obviously it will be added to the XML at point number 3 in the above list, but when do I grab the time?

There are two logical options:

  1. When the TCP server starts receiving data from a TCP client.
  2. When the TCP server has received all data from a TCP client.

The second option would save CPU cycles when a TCP client doesn't finish sending a request for some reason, so I will go with that.

clTRID code

I ended up splitting the code for creating the clTRID into two sections. The first creates and sets the variables and then calls a function called GetclTRID(), and the second is the GetclTRID() function.

	char clTRID[64] = "";
	bzero((char *) clTRID, 65);

	char *TAG = "JOHNCOOK";
	char client[3] = "00";
	char priority[2] = "0";

	char *registrantID = "123456";

	GetclTRID(TAG, client, priority, registrantID, clTRID);

The registrantID I have hard-coded here is just a placeholder to make sure that the code works and formats the clTRID as expected.

clTRID, TAG, and registrantID are dynamically sized character arrays (strings), with client and priority being fixed sized character arrays. That will allow TAG and registrantID to have up to a combined 32 characters without hard-coding them to a limit of 16 (17 with NUL terminator) each.

void GetclTRID(char *TAG, char *client, char *priority, char *registrantID, char *clTRID)
{
	struct timespec unixTime;
	clock_gettime(CLOCK_REALTIME, &unixTime);

	time_t epochSecs;
	epochSecs = unixTime.tv_sec;

	struct tm *dateTime;
	dateTime = gmtime(&epochSecs);

	memcpy(clTRID, TAG, strlen(TAG));
	memcpy(clTRID + strlen(clTRID), "-", 1);
	strftime(clTRID + strlen(clTRID), 17, "%Y%m%dT%H%M%S.", dateTime);
	snprintf(clTRID + strlen(clTRID), 10, "%09ld", unixTime.tv_nsec);
	memcpy(clTRID + strlen(clTRID), "-", 1);
	memcpy(clTRID + strlen(clTRID), client, 2);
	memcpy(clTRID + strlen(clTRID), "-", 1);
	memcpy(clTRID + strlen(clTRID), priority, 1);
	memcpy(clTRID + strlen(clTRID), "-", 1);
	memcpy(clTRID + strlen(clTRID), registrantID, strlen(registrantID));
}

After condensing those memcpy commands a bit, I ended up with the following GetclTRID() function:

void GetclTRID(char *TAG, char *client, char *priority, char *registrantID, char *clTRID)
{
	struct timespec unixTime;
	clock_gettime(CLOCK_REALTIME, &unixTime);

	time_t epochSecs;
	epochSecs = unixTime.tv_sec;

	struct tm *dateTime;
	dateTime = gmtime(&epochSecs);

	memcpy(clTRID, TAG, strlen(TAG));
	strftime(clTRID + strlen(clTRID), 18, "-%Y%m%dT%H%M%S.", dateTime);
	snprintf(clTRID + strlen(clTRID), 16, "%09ld%s%s%s%s%s", unixTime.tv_nsec, "-", client, "-", priority, "-");
	memcpy(clTRID + strlen(clTRID), registrantID, strlen(registrantID));
}

I'm sure if I wasn't so new to C it wouldn't have taken me more than a day to write that function.

Validating XML

While I could just check XML validity once, it makes sense to do it before (or while) breaking the XML apart into a DOM, tree, or similar structure.

I think I will be using the Libxml2 library for XML parsing and validation.

Planning what I wanted to do actually helped me end up with code that does what I want it to do. Since I wanted to validate the XML after modifying it I also had to get my head around XML namespaces in libxml2, especially the NULL/no-name/default XML namespace (xmlns).

The following code was added to main() just before the while(1) loop:

	xmlDocPtr pSchemaDoc;
	xmlSchemaParserCtxtPtr pParser;
	xmlSchemaPtr pSchema;
	xmlSchemaValidCtxtPtr pSchemaCtxt;

	LIBXML_TEST_VERSION;
	xmlInitMemory();

	char *xsdFile = "/home/thejc/Scripts/epp/nom-std-1.0.9-schemas/nom-root-std-1.0.9.xsd";
	pSchemaDoc = xmlReadFile(xsdFile, NULL, XML_PARSE_NONET);
	pParser = xmlSchemaNewDocParserCtxt(pSchemaDoc);
	pSchema = xmlSchemaParse(pParser);
	pSchemaCtxt = xmlSchemaNewValidCtxt(pSchema);

	xmlSchemaSetValidErrors(pSchemaCtxt, NULL, NULL, NULL);

This was also added to the end of main() just before return 0:

	xmlFreeDoc(pSchemaDoc);
	xmlSchemaFreeValidCtxt(pSchemaCtxt);
	xmlSchemaCleanupTypes();
	xmlCleanupParser();
	xmlMemoryDump();

In order to pass the schema validator to child processes void established_connection(int sock) became void established_connection(int sock, xmlSchemaValidCtxtPtr pSchemaCtxt).

This was added to established_connection() where the code to print msg_data previously was (near the end):

	char *msgPtr;
	msgPtr = msg_data;
	xml_parse(msgPtr, msg_data_length, pSchemaCtxt);

And this is how xml_parse() ended up looking:

void xml_parse(char *msg_data, uint32_t file_size, xmlSchemaValidCtxtPtr pSchemaCtxt)
{
	char *log_directory = "/home/thejc/Scripts/epp/logs/";
	char *log_file = log_directory;
	/*
	If the message contains a NUL character printf is not suitable here.
	*/
	//printf("%s\n", msg_data);

	xmlKeepBlanksDefault(0);
	xmlDocPtr doc = NULL;
	doc = xmlReadMemory(msg_data, file_size, "noname.xml", NULL, 0);
	if (doc == NULL)
	{
		error("xmlReadMemory");
	}

	int invalid;
	invalid = xmlSchemaValidateDoc(pSchemaCtxt, doc);

	if (invalid)
	{
		error("xmlSchemaValidateDoc");
	}


	char clTRID[64] = "";
	bzero((char *) clTRID, 65);

	char *TAG = "JOHNCOOK";
	char client[3] = "00";
	char priority[2] = "0";

	char *registrantID = "123456";

	GetclTRID(TAG, client, priority, registrantID, clTRID);

	if (clTRID != "")
	{
		xmlNodePtr root_node = NULL;
		xmlNodePtr command_node = NULL;
		xmlNodePtr clTRID_node = NULL;
		xmlNodePtr nodeLevel1 = NULL;
		xmlNodePtr nodeLevel2 = NULL;
		xmlNodePtr nodeLevel3 = NULL;
		xmlNsPtr ns = NULL;

		for(nodeLevel1 = doc->children; nodeLevel1 != NULL; nodeLevel1 = nodeLevel1->next)
		{
			root_node = nodeLevel1;
			xmlDocSetRootElement(doc, root_node);
			ns = xmlSearchNs(doc, root_node, NULL);

			for(nodeLevel2 = nodeLevel1->children; nodeLevel2 != NULL; nodeLevel2 = nodeLevel2->next)
			{
				if (strcmp((char *) nodeLevel2->name,"command") == 0)
				{
					command_node = nodeLevel2;
					for(nodeLevel3 = nodeLevel2->children; nodeLevel3 != NULL; nodeLevel3 = nodeLevel3->next)
					{
						if (strcmp((char *) nodeLevel3->name,"clTRID") == 0)
						{
							clTRID_node = nodeLevel3;
						}
					}
				}
			}
		}
		if (command_node != NULL && clTRID_node == NULL)
		{
			xmlNewChild(command_node, ns, "clTRID", (xmlChar *) clTRID);
		}
		else if (command_node != NULL && clTRID_node != NULL)
		{
			xmlNodeSetContent(clTRID_node,(xmlChar *) clTRID);
		}

		invalid = xmlSchemaValidateDoc(pSchemaCtxt, doc);

		if (invalid)
		{
			error("xmlSchemaValidateDoc");
		}

		char logfile_req[strlen(log_file) + strlen(clTRID) + strlen(".req") + 1];
		memset(logfile_req, 0, sizeof(logfile_req));
		memcpy(logfile_req, log_file, strlen(log_file));
		memcpy(logfile_req + strlen(logfile_req), clTRID, strlen(clTRID));
		memcpy(logfile_req + strlen(logfile_req), ".req\0", 5);

		xmlDocFormatDump(stdout,doc,1);

		int savedBytes = xmlSaveFormatFileEnc(logfile_req, doc, "utf-8", 1);
		if (savedBytes < 0)
		{
			fprintf(stderr, "Unable to save file %s.\n",logfile_req);
		}

	}

	xmlFreeDoc(doc);
}

This function is just the foundation and will likely be improved upon later, but it does do what I want it to.

The biggest challenge with the function was working out how libxml2 does things without having a firm grasp on how C does things.

I decided to use for loops rather than XPath to iterate through the document for a couple of reasons:

  1. The documentation had an example that I could easily modify to suit my needs.
  2. I had the assumption that XPath would be slower than looping through the whole document.

The document is parsed into a tree in memory, xmlKeepBlanksDefault(0) makes sure whitespace is removed, doc is validated against the Nominet EPP schema bundle, and the clTRID is generated.

The doc is then iterated through, setting the root node of the document and getting the default (NULL) namespace of the root node pointer.

At the next depth the command element is looked for (and if found the pointer command_node is set), and the code then checks if the command node has a clTRID child node.

After the entire document has been looped over, and a command node was found, either a clTRID node is added if one didn't exist or the content of an existing clTRID node is modified.

The in memory document is then validated against the Nominet EPP schema bundle again, and then the filename of the log file is generated

Finally the document is dumped to standard output with indentation, and is also dumped to a logfile using UTF-8 encoding and indenting.

If I were to dump the output elsewhere, such as to Nominet's EPP server, I would do so without indenting because, as well as bandwidth savings, it will probably involve a bit less processing because of the lack of whitespace text nodes.

Current source Code

At this point tcp-server.c looks like this:

/* Standard Libraries */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
/* Socket Libraries */
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
/* XML Libraries */
#include <libxml/tree.h>
#include <libxml/parser.h>
#include <libxml/xmlreader.h>
#include <libxml/xmlmemory.h>
/* Time and Date */
#include <time.h>

void established_connection(int sock, xmlSchemaValidCtxtPtr pSchemaCtxt);
void xml_parse(char *msg_data, uint32_t file_size, xmlSchemaValidCtxtPtr pSchemaCtxt);
void GetclTRID(char *TAG, char *client, char *priority, char *registrantID, char *clTRID);

void error(char *msg)
{
	perror(msg);
	exit(1);
}

/* main() is server management. Established connections are not handled here. */
int main(int argc, char *argv[])
{
	char *BIND_PORT = "9999";
	char *BIND_IP = "::1";

	int sock_fd, newsock_fd, port_number, pid;
	socklen_t client_addr_len;
	struct sockaddr_in6 server_addr, client_addr;

	port_number = atoi(BIND_PORT);

	sock_fd = socket(AF_INET6, SOCK_STREAM, 0);
	if (sock_fd < 0)
	{
		error("socket");
	}

	bzero((char *) &server_addr, sizeof(server_addr));
	server_addr.sin6_family = AF_INET6;
	server_addr.sin6_port = htons(port_number);
	inet_pton(AF_INET6, BIND_IP, &server_addr.sin6_addr);

	int optval = 1;
	if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
	{
		error("setsockopt");
	}

	if (bind(sock_fd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0)
	{
		error("bind");
	}

	listen(sock_fd, 5);

	client_addr_len = sizeof(client_addr);

	xmlDocPtr pSchemaDoc;
	xmlSchemaParserCtxtPtr pParser;
	xmlSchemaPtr pSchema;
	xmlSchemaValidCtxtPtr pSchemaCtxt;

	LIBXML_TEST_VERSION;
	xmlInitMemory();

	char *xsdFile = "/home/thejc/Scripts/epp/nom-std-1.0.9-schemas/nom-root-std-1.0.9.xsd";
	pSchemaDoc = xmlReadFile(xsdFile, NULL, XML_PARSE_NONET);
	pParser = xmlSchemaNewDocParserCtxt(pSchemaDoc);
	pSchema = xmlSchemaParse(pParser);
	pSchemaCtxt = xmlSchemaNewValidCtxt(pSchema);

	xmlSchemaSetValidErrors(pSchemaCtxt, NULL, NULL, NULL);

	while (1)
	{
		struct timeval timeout_recv;
		timeout_recv.tv_sec = 300; // 5
		timeout_recv.tv_usec = 0;
		struct timeval timeout_send;
		timeout_send.tv_sec = 90; // 90
		timeout_send.tv_usec = 0;

		newsock_fd = accept(sock_fd, (struct sockaddr *) &client_addr, &client_addr_len);
		if (newsock_fd < 0)
		{
			error("accept");
		}
		if (setsockopt(newsock_fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout_recv, sizeof(timeout_recv)) < 0)
		{
			error("setsockopt");
		}
		if (setsockopt(newsock_fd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout_send, sizeof(timeout_send)) < 0)
		{
			error("setsockopt");
		}
		pid = fork();
		if (pid < 0)
		{
			error("pid");
		}
		if (pid == 0)
		{
			close(sock_fd);
			established_connection(newsock_fd, pSchemaCtxt);
			exit(0);
		}
		else {
			close(newsock_fd);
		}
	}
	close(sock_fd);

	xmlFreeDoc(pSchemaDoc);
	xmlSchemaFreeValidCtxt(pSchemaCtxt);
	xmlSchemaCleanupTypes();
	xmlCleanupParser();
	xmlMemoryDump();

	return 0;

}

/* After a connection is successfully established, processing is no longer in main().
 * Instead, all processing within a client connection is handled within established_connection(). */
void established_connection(int sock, xmlSchemaValidCtxtPtr pSchemaCtxt)
{
	uint32_t buffer_size = 256;
	uint32_t header_size = 4;

	uint32_t buffer_size_chars = buffer_size - 1;

	uint32_t msg_data_length, position, remaining_chars = 0;
	char buffer[buffer_size], temp[buffer_size];
	int n = 0;

	union {
		uint32_t whole;
		char bytes[header_size];
	} msg_length;

	bzero((char *) buffer, buffer_size);
	n = read(sock, buffer, buffer_size_chars);
	if (errno == EAGAIN)
	{
		exit(0);
	}
	if (n < 0)
	{
		error("first read");
	}
	memcpy(msg_length.bytes, buffer, header_size);
	msg_length.whole = ntohl(msg_length.whole);
	if (msg_length.whole < header_size) {
		fprintf(stderr,"Error: Data length less than header size.\n");
		printf("--------------------------------------------------------------------------------\n");
		exit(1);
	}
	msg_data_length = msg_length.whole - header_size;

	bzero((char *) temp, buffer_size);
	memcpy(temp, buffer + header_size, buffer_size - header_size);

	/*
	Make msg_data large enough to hold the entire message.
	*/
	char msg_data[msg_data_length - header_size + 1]; // With NUL.
	memset(msg_data, 0, sizeof(msg_data));

	int read_chars;
	/*
	If "msg_data_length" is less than "buffer size chars (buffer size minus 1) minus header_size", then the buffer contains the entire data message.
	*/
	if (msg_data_length < buffer_size_chars - header_size)
	{
		read_chars = msg_data_length - header_size;
	}
	/* Otherwise, it only contains the start of the message. */
	else
	{
		read_chars = buffer_size_chars - header_size;
	}
	memcpy(msg_data, temp, read_chars);

	/* Position is how many bytes we have parsed. */
	position = buffer_size_chars - header_size;
	/*
	Keep parsing until position equals the size indicated in the header.
	*/
	while (position < msg_data_length)
	{
		remaining_chars = msg_data_length - position;
		int read_chars;
		bzero((char *) buffer, buffer_size);

		if (remaining_chars > buffer_size_chars)
		{
			read_chars = buffer_size_chars;
		}
		else if (remaining_chars > 0)
		{
			read_chars = remaining_chars;
		}

		n = read(sock, buffer, read_chars);

		if (errno == EAGAIN)
		{
			/* Connection closed due to read timeout. */
			fprintf(stderr, "Error: Timeout while waiting for rest of data.\n");
			printf("--------------------------------------------------------------------------------\n");
			exit(1);
		}
		if (n < 0)
		{
			error("subsequent read");
		}
		if (n == 0)
		{
			error("Possible infinite loop");
		}
		memcpy(msg_data + position, buffer, read_chars);
		position = position + n;
		char *end_of_root = NULL;
		end_of_root = strstr(msg_data, "</epp>");
		if (end_of_root != NULL)
		{
			msg_data_length = end_of_root + 6 - msg_data;
			break;
		}
	}

	char *msgPtr;
	msgPtr = msg_data;
	xml_parse(msgPtr, msg_data_length, pSchemaCtxt);

	printf("\n--------------------------------------------------------------------------------\n");

	char ack_msg[] = "Message received.\n";

	n = write(sock, ack_msg, sizeof(ack_msg));
	if (n < 0)
	{
		error("write");
	}

}

void xml_parse(char *msg_data, uint32_t file_size, xmlSchemaValidCtxtPtr pSchemaCtxt)
{
	char *log_directory = "/home/thejc/Scripts/epp/logs/";
	char *log_file = log_directory;
	/*
	If the message contains a NUL character printf is not suitable here.
	*/
	//printf("%s\n", msg_data);

	xmlKeepBlanksDefault(0);
	xmlDocPtr doc = NULL;
	doc = xmlReadMemory(msg_data, file_size, "noname.xml", NULL, 0);
	if (doc == NULL)
	{
		error("xmlReadMemory");
	}

	int invalid;
	invalid = xmlSchemaValidateDoc(pSchemaCtxt, doc);

	if (invalid)
	{
		error("xmlSchemaValidateDoc");
	}


	char clTRID[64] = "";
	bzero((char *) clTRID, 65);

	char *TAG = "JOHNCOOK";
	char client[3] = "00";
	char priority[2] = "0";

	char *registrantID = "123456";

	GetclTRID(TAG, client, priority, registrantID, clTRID);

	if (clTRID != "")
	{
		xmlNodePtr root_node = NULL;
		xmlNodePtr command_node = NULL;
		xmlNodePtr clTRID_node = NULL;
		xmlNodePtr nodeLevel1 = NULL;
		xmlNodePtr nodeLevel2 = NULL;
		xmlNodePtr nodeLevel3 = NULL;
		xmlNsPtr ns = NULL;

		for(nodeLevel1 = doc->children; nodeLevel1 != NULL; nodeLevel1 = nodeLevel1->next)
		{
			root_node = nodeLevel1;
			xmlDocSetRootElement(doc, root_node);
			ns = xmlSearchNs(doc, root_node, NULL);

			for(nodeLevel2 = nodeLevel1->children; nodeLevel2 != NULL; nodeLevel2 = nodeLevel2->next)
			{
				if (strcmp((char *) nodeLevel2->name,"command") == 0)
				{
					command_node = nodeLevel2;
					for(nodeLevel3 = nodeLevel2->children; nodeLevel3 != NULL; nodeLevel3 = nodeLevel3->next)
					{
						if (strcmp((char *) nodeLevel3->name,"clTRID") == 0)
						{
							clTRID_node = nodeLevel3;
						}
					}
				}
			}
		}
		if (command_node != NULL && clTRID_node == NULL)
		{
			xmlNewChild(command_node, ns, "clTRID", (xmlChar *) clTRID);
		}
		else if (command_node != NULL && clTRID_node != NULL)
		{
			xmlNodeSetContent(clTRID_node,(xmlChar *) clTRID);
		}

		invalid = xmlSchemaValidateDoc(pSchemaCtxt, doc);

		if (invalid)
		{
			error("xmlSchemaValidateDoc");
		}

		char logfile_req[strlen(log_file) + strlen(clTRID) + strlen(".req") + 1];
		memset(logfile_req, 0, sizeof(logfile_req));
		memcpy(logfile_req, log_file, strlen(log_file));
		memcpy(logfile_req + strlen(logfile_req), clTRID, strlen(clTRID));
		memcpy(logfile_req + strlen(logfile_req), ".req\0", 5);

		xmlDocFormatDump(stdout,doc,1);

		int savedBytes = xmlSaveFormatFileEnc(logfile_req, doc, "utf-8", 1);
		if (savedBytes < 0)
		{
			fprintf(stderr, "Unable to save file %s.\n",logfile_req);
		}

	}

	xmlFreeDoc(doc);
}

void GetclTRID(char *TAG, char *client, char *priority, char *registrantID, char *clTRID)
{
	struct timespec unixTime;
	clock_gettime(CLOCK_REALTIME, &unixTime);

	time_t epochSecs;
	epochSecs = unixTime.tv_sec;

	struct tm *dateTime;
	dateTime = gmtime(&epochSecs);

	memcpy(clTRID, TAG, strlen(TAG));
	strftime(clTRID + strlen(clTRID), 18, "-%Y%m%dT%H%M%S.", dateTime);
	snprintf(clTRID + strlen(clTRID), 16, "%09ld%s%s%s%s%s", unixTime.tv_nsec, "-", client, "-", priority, "-");
	memcpy(clTRID + strlen(clTRID), registrantID, strlen(registrantID));
}

Next Steps

I still need to move some code around, and I have yet to decide how to modify xml_parse so it can be used in connection.c for both requests and responses.

A lot of things are hard-coded into both tcp-server.c and connection.c, although connection.c does have some things that can be set with command line parameters.

What I think I need to do next is to research configuration file standards and how to parse them, as things like EPP keys shouldn't really be given to a program through a command line parameter.

After that I can start work on getting connection.c to establish a connection, send some login XML, send some keep alive XML to keep the connection up, and send some logout XML if the program receives a shutdown signal like SIGINT.

I also need to look at making some of the code more reusable. For example, tcp-server.c is a TCP server that receives and acts on fully valid EPP (with Nominet extensions) XML input. I might decide to go a different route and use redis as a queue, with my programming popping items from the queue and then generating the XML. I need to make some of the code more general so it can be reused.

My Nominet-compatible EPP client is slowly becoming a reality.