New network-client writer's checklist ====================================== Glossary --------- concrete ncli network client that is designed to be the final client in the call chain. E.g., the socket-client is concrete ncli, because it finally writes the actual bytes to the network. wrapper ncli network client that is designed to be put on top of some other client in the call chain. E.g., the logging client logs the data being read or written, and then calls the underlying concrete ncli, which e.g. writes actual bytes to the network. ncli method one of the five methods that every network client could wish to implement: connect(), disconnect(), read(), write(), flush(). Boilerplate code ----------------- + create new files for your network client, let's call them foo-client.c and foo-client.h; + copy header from network-client.[ch] to those programs; #ifdef-protect the foo-client.h from double inclusion; + register those files in Makefile.am, in value of cvs_SOURCES variable; + #include "network-client.h" into both foo-client.h and foo-client.c; + #include "foo-client.h" into foo-client.c; + declare the constructor for foo-client as such: struct network_client *create_foo_client(cvsroot_t *CVSROOT, ncli_error_cb error_cb); + declare struct for client-specific data like this: struct foo_client_data { int bar; char *baz; } typedef struct foo_client_data *FCD; + declare methods that are needed to be implemented in your network client. Methods should be declared as static and have the following prototypes: static int foo_connect(NCLI); static int foo_disconnect(NCLI); static int foo_read(NCLI, char *buf, int len); static int foo_write(NCLI, char *buf, int len); static int foo_flush(NCLI); If you don't need some of those methods (i.e., your network client depends upon (inherits from) some other client), simply do not declare those unneeded methods. + implement constructor for foo-client with the following structure: struct foo_client *create_foo_client (cvsroot_t *CVSROOT, ncli_error_cb error_cb) { FCD fcd; struct network_client *nc = malloc(sizeof(struct network_client)); if (nc == NULL) return NULL; fcd = malloc(sizeof(struct foo_client_data)); if (fcd == NULL) return NULL; /* you can initialize some or all fields from FCD. If some fields are not initialized here, the should be initialized in foo_connect() method (and this method should be present */ fcd->bar = 0; /* fcd->baz could be initialized in connect() method; */ nc->client_data = fcd; nc->connect = foo_connect; nc->disconnect = foo_disconnect; nc->read = foo_read; nc->write = foo_write; nc->flush = foo_flush; nc->error = error_cb; return nc; } + put all #include's that your network client needs into foo-client.c. Methods implementation ----------------------- + if your network-client does not implement some methods, those methods should be explicitly initialized to NULL in the constructor; + implement all methods that your network-client has to implement. The first line of every methods should probably be FCD fcd = (FCD) ncli->client_data; After that you may use client-specific fields as you wish. + If any of the ncli methods wishes to signal error, the only correct way to do that is like this (example taken from logging-client.c): logfile = fopen(logfile_name, "w"); if (logfile == NULL) return ncli->error(ncli, "Cannot open %s for writing: %s", logfile_name, strerror(errno)); The "ncli->error()" is an error callback provided to create_foo_client() constructor by the client code. Most usually this callback does not ever return, so it is wrong to omit the "return" statement. + Functions that are not ncli methods should _never_ call the ncli->error(), but instead signal the error in some traditional way (returning NULL, -1, or such). + foo_connect() should return NCLI_SUCCESS if nothing went wrong. + foo_disconnect() of "concrete ncli" should return NCLI_SUCCESS if nothing went wrong. If your client is "wrapper ncli", the final statement should look exactly like this: return ncli->next->disconnect(ncli->next); Do not bother checking whether ncli->next == NULL. + foo_read() should return either number of bytes read or -1, and set errno=EAGAIN. Every caller of ->read() method MUST be prepared to deal with this case (but no other values for errno could be returned currently). if your client is "wrapper ncli", then its "read" method should look like this: static int foo_read (NCLI ncli, char *buf, int len) { FCD fcd = (FCD) ncli->client_data; int read = ncli->next->read(ncli->next, buf, len); /* additionally process the data read, considering case where (read == -1)&&(errno == EAGAIN) */ return read; } + foo_write() of "concrete ncli" should return NCLI_SUCCESS if all data has been successfully written, or signal the error in standard way. foo_write() of "wrapper ncli" looks like this: static int foo_write (NCLI ncli, char *buf, int len) { FCD fcd = (FCD) ncli->client_data; /* preprocess the data being written */ return ncli->next->write(ncli->next, buf, len); } + foo_flush() of "concrete ncli" should return NCLI_SUCCESS if the data has been successfully flushed, or signal the error in standard way. foo_flush() of "wrapper ncli" looks like this: static int foo_flush (NCLI ncli) { /* pre-flush the data if needed */ return ncli->next->flush(ncli->next); } If the "wrapper ncli" does not need special processing of "flush" method, then it'll contain only the last line (calling the underlying ncli).