diff options
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | docs/examples.md | 4 | ||||
| -rw-r--r-- | docs/fetch-tcp.example.c | 71 | ||||
| -rw-r--r-- | libsparklepaw/request.c | 118 | ||||
| -rw-r--r-- | libsparklepaw/request.h | 88 | ||||
| -rw-r--r-- | libsparklepaw/transport.c | 75 | ||||
| -rw-r--r-- | libsparklepaw/transport.h | 93 |
7 files changed, 444 insertions, 7 deletions
@@ -7,7 +7,7 @@ LIBDIR ?= $(PREFIX)/lib INCLUDEDIR ?= $(PREFIX)/include MANDIR ?= $(PREFIX)/share/man -CFLAGS := -Wall -Wextra -flto +CFLAGS := -Wall -Wextra -flto -funsigned-char ### build mode diff --git a/docs/examples.md b/docs/examples.md index 9d2a6d1..e10e646 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -8,5 +8,5 @@ Example of constructing a service and writing it out: Example of reading a service in and printing it for debugging: \include read-service.example.c -Example of making a request (to stdout instead of a transport): -\include write-request.example.c +Example of making a request to a daemon via TCP transport: +\include fetch-tcp.example.c diff --git a/docs/fetch-tcp.example.c b/docs/fetch-tcp.example.c new file mode 100644 index 0000000..3d3b608 --- /dev/null +++ b/docs/fetch-tcp.example.c @@ -0,0 +1,71 @@ +#include <arpa/inet.h> +#include <errno.h> +#include <libsparklepaw/transport.h> +#include <netinet/in.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> + +int main(int argc, char** argv) { + if (argc != 3) { + fprintf(stderr, "usage: %s <inet4-addr> <tcp-port>\n", argv[0]); + return 1; + } + + // key for my example zone, replace it with one you can test against + uint8_t key[0x20] = { + 0xc5, 0x82, 0x1e, 0x05, 0x34, 0x80, 0x17, 0x25, + 0x3c, 0xfc, 0x80, 0x87, 0x0b, 0xca, 0x03, 0xed, + 0x0a, 0x8b, 0x93, 0xbc, 0x33, 0x58, 0x33, 0x9e, + 0x7e, 0xee, 0x44, 0x08, 0xff, 0xfd, 0x2c, 0x2d + }; + + struct sprkl_request* req = sprkl_request_make_fetchservice( + SPRKL_SIGALGO_ED25519, key, 0 + ); + + // horrible awful code practices ensue. don't write your code like this, i + // just did this cause it's shorter for the example :3 + + struct sockaddr_in saddr; + saddr.sin_family = AF_INET; + saddr.sin_addr.s_addr = inet_addr(argv[1]); + saddr.sin_port = htons(atoi(argv[2])); + + int sock = socket(AF_INET, SOCK_STREAM, 6); + errno = 0; + if (connect(sock, (struct sockaddr*) &saddr, sizeof saddr)) { + fprintf(stderr, "connect error: %s\n", strerror(errno)); + return errno; + } + + FILE* stream = fdopen(sock, "r+"); + struct sprkl_transport t = sprkl_transport_make_stream(stream); + + int err = sprkl_transport_sendrequest(&t, req); + if (err) { + fprintf(stderr, "req error: %s\n", sprkl_transport_strerror(&t, err)); + return err; + } + + struct sprkl_response resp; + resp.verb = req->verb; + err = sprkl_transport_recvresponse(&t, &resp); + if (err) { + fprintf(stderr, "resp error: %s\n", sprkl_transport_strerror(&t, err)); + return err; + } + + fclose(stream); + + fprintf(stderr, "response status code is %x\n", resp.status); + if (resp.status != SPRKL_STATUS_OK) { + fprintf(stderr, "server said: %*s\n", resp.errmsgsz, resp.errmsg); + } else { + sprkl_service_printdebug(resp.fetchsvc.svc, stdout); + } + + sprkl_response_freeparts(&resp, true); + sprkl_request_freeall(req); +} diff --git a/libsparklepaw/request.c b/libsparklepaw/request.c index 440cdd9..f8b3f88 100644 --- a/libsparklepaw/request.c +++ b/libsparklepaw/request.c @@ -33,7 +33,7 @@ int sprkl_request_write(struct sprkl_request* request, FILE* stream) { int sprkl_request_read(struct sprkl_request* request, FILE* stream) { int n = 0; uint8_t buf[SPRKL_REQUEST_MAGIC_SZ]; - n += fread(buf, 1, 8, stream); + n += fread(buf, 1, sizeof buf, stream); if (memcmp(buf, SPRKL_REQUEST_MAGIC, sizeof buf) != 0) { errno = EPROTO; return n; @@ -92,9 +92,9 @@ struct sprkl_request* sprkl_request_make_fetchservice( uint8_t* key, uint16_t serviceidx ) { - // FIXME: check this properly instead of using assert i think + //! \todo check the algorithm properly instead of using assert i think assert(keyalgo == SPRKL_SIGALGO_ED25519 && "i only support Ed25519"); - uint16_t keysz = 0x40; // based on the above assertion + uint16_t keysz = 0x20; // based on the above assertion struct sprkl_request* req = malloc(sizeof(struct sprkl_request)); uint8_t* copiedkey = malloc(keysz); memcpy(copiedkey, key, keysz); @@ -108,3 +108,115 @@ struct sprkl_request* sprkl_request_make_fetchservice( req->fetchsvc = fetchsvc; return req; } + + +char* sprkl_response_statusmsg(enum sprkl_response_status status) { + switch (status) { + case SPRKL_STATUS_OK: + return "success"; + case SPRKL_STATUS_REQ_ERR: + return "bad request"; + case SPRKL_STATUS_NOT_FOUND: + return "resource not found"; + case SPRKL_STATUS_RESP_ERR: + return "unexpected internal responder error"; + case SPRKL_STATUS_NOT_IMPL: + return "operation not implemented"; + case SPRKL_STATUS_UNAVAIL: + return "responder temporarily unavailable"; + case SPRKL_STATUS_VERB_UNK: + return "unrecognised verb"; + } + return "mystery error???"; +} + +int sprkl_response_write(struct sprkl_response* response, FILE* stream) { + int n = 0; + n += fwrite(SPRKL_RESPONSE_MAGIC, 1, SPRKL_RESPONSE_MAGIC_SZ, stream); + n += sprkl_writeuint16be(SPRKL_PROTO_VERSION, stream); + n += sprkl_writeuint16be(response->status, stream); + if (response->status != SPRKL_STATUS_OK) { + if (response->errmsgsz > 0) { + n += sprkl_writeuint16be(response->errmsgsz, stream); + n += fwrite( + response->errmsg, 1, + response->errmsgsz, stream + ); + } else { + char* msg = sprkl_response_statusmsg(response->status); + n += sprkl_writeuint16be(strlen(msg), stream); + n += fwrite(msg, 1, strlen(msg), stream); + } + return n; + } + // status is ok + switch (response->verb) { + case SPRKL_VERB_ECHO: + n += fwrite( + response->echo.data, 1, + sizeof response->echo.data, stream + ); + break; + case SPRKL_VERB_FETCHSERVICE: + n += sprkl_service_write(response->fetchsvc.svc, stream); + break; + } + return n; +} + +int sprkl_response_read(struct sprkl_response* response, FILE* stream) { + assert(response->verb != 0 && "response's request verb must be set"); + int n = 0; + uint8_t buf[SPRKL_RESPONSE_MAGIC_SZ]; + n += fread(buf, 1, sizeof buf, stream); + if (memcmp(buf, SPRKL_RESPONSE_MAGIC, sizeof buf) != 0) { + errno = EPROTO; + return n; + } + uint16_t version; + n += sprkl_readuint16be(&version, stream); + if (version != SPRKL_PROTO_VERSION) { + errno = EPROTO; + return n; + } + n += sprkl_readuint16be((uint16_t*) &response->status, stream); + if (response->status != SPRKL_STATUS_OK) { + n += sprkl_readuint16be(&response->errmsgsz, stream); + response->errmsg = malloc(response->errmsgsz); + n += fread(response->errmsg, 1, response->errmsgsz, stream); + return n; + } + // status is ok + response->errmsgsz = 0; + response->errmsg = NULL; + switch (response->verb) { // i really hope this was set for us + case SPRKL_VERB_ECHO: + n += fwrite( + response->echo.data, 1, + sizeof response->echo.data, stream + ); + break; + case SPRKL_VERB_FETCHSERVICE: + response->fetchsvc.svc = malloc(sizeof(struct sprkl_service)); + n += sprkl_service_read(response->fetchsvc.svc, stream); + } + return n; +} + +void sprkl_response_freeparts(struct sprkl_response* response, bool errmsg) { + if (response->errmsg != NULL && errmsg) free(response->errmsg); + if (response->status != SPRKL_STATUS_OK) return; + switch (response->verb) { + case SPRKL_VERB_ECHO: + // do nothing + break; + case SPRKL_VERB_FETCHSERVICE: + sprkl_service_freeall(response->fetchsvc.svc); + break; + } +} + +void sprkl_response_freeall(struct sprkl_response* response) { + sprkl_response_freeparts(response, true); + free(response); +} diff --git a/libsparklepaw/request.h b/libsparklepaw/request.h index bf568f6..55cb79b 100644 --- a/libsparklepaw/request.h +++ b/libsparklepaw/request.h @@ -5,6 +5,7 @@ #define SPRKL_REQUEST_H #include "common.h" +#include "service.h" #include <stdint.h> #include <stdio.h> @@ -12,6 +13,9 @@ /** Magic bytes for requests. */ #define SPRKL_REQUEST_MAGIC "PawRqust" #define SPRKL_REQUEST_MAGIC_SZ ((sizeof SPRKL_REQUEST_MAGIC) - 1) +/** Magic bytes for responses. */ +#define SPRKL_RESPONSE_MAGIC "PawRspns" +#define SPRKL_RESPONSE_MAGIC_SZ ((sizeof SPRKL_RESPONSE_MAGIC) - 1) /** Currently supported protocol version. */ #define SPRKL_PROTO_VERSION 1 @@ -22,9 +26,35 @@ enum sprkl_request_verb { //SPRKL_VERB_QUERYSERVICES = 2 }; +/** Response status code, indicates the outcome of a request. */ +enum sprkl_response_status { + /** Success. */ + SPRKL_STATUS_OK = 0x0200, + /** Requester ("client") made an unspecified mistake. */ + SPRKL_STATUS_REQ_ERR = 0x0400, + /** Requested resource was not found. */ + SPRKL_STATUS_NOT_FOUND = 0x0404, + /** Responder ("server") encountered an unspecified error. */ + SPRKL_STATUS_RESP_ERR = 0x0500, + /** Operation not implemented. + * This should not be used for unimplemented verbs; see + * \ref SPRKL_STATUS_VERB_UNK. */ + SPRKL_STATUS_NOT_IMPL = 0x0501, + /** Responder is temporarily unable to process the request (for example + * due to maintenance or overload). */ + SPRKL_STATUS_UNAVAIL = 0x0503, + /** Responder doesn't know how to deal with the request verb. */ + SPRKL_STATUS_VERB_UNK = 0x0505, +}; + /** Request payload for \ref SPRKL_VERB_ECHO. */ struct sprkl_request_echo { - uint8_t data[16]; /**< Data to be returned by the remote party. */ + uint8_t data[16]; /**< Data to be returned by the responder. */ +}; + +/** Response payload for \ref SPRKL_VERB_ECHO (same as request). */ +struct sprkl_response_echo { + uint8_t data[16]; /**< Data given by the requester. */ }; /** Request payload for \ref SPRKL_VERB_FETCHSERVICE. */ @@ -35,6 +65,11 @@ struct sprkl_request_fetchservice { uint16_t index; /**< Index of the service within the zone. */ }; +/** Response payload for \ref SPRKL_VERB_FETCHSERVICE. */ +struct sprkl_response_fetchservice { + struct sprkl_service* svc; +}; + /** Request tagged union. */ struct sprkl_request { enum sprkl_request_verb verb; /**< Union tag, request verb. */ @@ -45,6 +80,18 @@ struct sprkl_request { }; }; +/** Response tagged union (varies on the associated request verb and status). */ +struct sprkl_response { + enum sprkl_request_verb verb; /**< Union tag, original request verb. */ + enum sprkl_response_status status; /**< Also union tag, status code. */ + uint16_t errmsgsz; /**< Length of the error message (optional). */ + uint8_t* errmsg; /**< Error message (optional; UTF-8, no terminator). */ + union { + struct sprkl_response_echo echo; + struct sprkl_response_fetchservice fetchsvc; + }; +}; + /** Encodes and writes a request in wire format to a stream. * \param[in] request Request to write. * \param[out] stream Stream to write the request to. @@ -84,4 +131,43 @@ struct sprkl_request* sprkl_request_make_fetchservice( uint16_t serviceidx ); + +/** Returns a default message for the given status code. + * \param status The status code to look up. + * \returns the message as a null-terminated non-localised string (does not need + * to be freed). */ +char* sprkl_response_statusmsg(enum sprkl_response_status status); + +/** Encodes and writes a response in wire format to a stream. + * \param[in] response Response to write. + * \param[out] stream Stream to write the response to. + * \returns number of bytes written. */ +int sprkl_response_write(struct sprkl_response* response, FILE* stream); + +/** Reads and decodes a response in wire format from a stream. This could fail + * if the protocol magic constant is wrong or the version is unsupported, in + * which case it will set \c errno to \c EPROTO, or if the verb is unsupported, + * in which case it will set \c errno to \c ENOTSUP. + * Will allocate various buffers; use sprkl_response_freeparts() or + * sprkl_response_freeall() to get rid of them. + * \param[out] response Response to read into. Should be empty except for + * sprkl_response::verb, which is needed in order to parse the response + * properly. If this isn't set it will trigger an assertion, assuming assertions + * are enabled (i.e. the library was built in debug mode). + * \param[in] stream Stream to read it from. + * \returns number of bytes read. */ +int sprkl_response_read(struct sprkl_response* response, FILE* stream); + +/** Frees any pointers contained within the response, but not the response + * itself. What exactly gets freed varies depending on the request verb. + * \param response Response to free. + * \param errmsg If false, never free the error message buffer, even if it's + * not null. */ +void sprkl_response_freeparts(struct sprkl_response* response, bool errmsg); + +/** Frees any pointers contained within the response, and then the response + * itself. Same as sprkl_response_freeparts() followed by free(response). + * \param response Response to free. */ +void sprkl_response_freeall(struct sprkl_response* response); + #endif diff --git a/libsparklepaw/transport.c b/libsparklepaw/transport.c new file mode 100644 index 0000000..cb92b40 --- /dev/null +++ b/libsparklepaw/transport.c @@ -0,0 +1,75 @@ +#include "request.h" +#include "transport.h" + +#include <errno.h> +#include <string.h> + +int sprkl_transport_sendrequest( + struct sprkl_transport* t, struct sprkl_request* request) { + return t->sendrequest(t, request); +} + +int sprkl_transport_recvrequest( + struct sprkl_transport* t, struct sprkl_request* request) { + return t->recvrequest(t, request); +} + +int sprkl_transport_sendresponse( + struct sprkl_transport* t, struct sprkl_response* response) { + return t->sendresponse(t, response); +} + +int sprkl_transport_recvresponse( + struct sprkl_transport* t, struct sprkl_response* response) { + return t->recvresponse(t, response); +} + +char* sprkl_transport_strerror(struct sprkl_transport* t, int error) { + return t->strerror(t, error); +} + + +static int _transport_stream_sendrequest( + struct sprkl_transport* self, struct sprkl_request* request) { + errno = 0; + sprkl_request_write(request, (FILE*) self->opaque); + return errno; +} + +static int _transport_stream_recvrequest( + struct sprkl_transport* self, struct sprkl_request* request) { + errno = 0; + sprkl_request_read(request, (FILE*) self->opaque); + return errno; +} + +static int _transport_stream_sendresponse( + struct sprkl_transport* self, struct sprkl_response* response) { + errno = 0; + sprkl_response_write(response, (FILE*) self->opaque); + return errno; +} + +static int _transport_stream_recvresponse( + struct sprkl_transport* self, struct sprkl_response* response) { + errno = 0; + sprkl_response_read(response, (FILE*) self->opaque); + return errno; +} + +static char* _transport_stream_strerror(struct sprkl_transport* t, int error) { + (void) t; // shut up -Wunused-parameter + return strerror(error); +} + +struct sprkl_transport sprkl_transport_make_stream(FILE* stream) { + struct sprkl_transport t = { + .opaque = stream, + .sendrequest = _transport_stream_sendrequest, + .recvrequest = _transport_stream_recvrequest, + .sendresponse = _transport_stream_sendresponse, + .recvresponse = _transport_stream_recvresponse, + .strerror = _transport_stream_strerror + }; + return t; +} diff --git a/libsparklepaw/transport.h b/libsparklepaw/transport.h new file mode 100644 index 0000000..152eaf1 --- /dev/null +++ b/libsparklepaw/transport.h @@ -0,0 +1,93 @@ +/** \file + * Generic transport mechanism and concrete implementations. */ + +#ifndef SPRKL_TRANSPORT_H +#define SPRKL_TRANSPORT_H + +#include "request.h" + +#include <stdio.h> + +/** Generic transport vtable. + * Generally you shouldn't use the contained functions directly, instead invoke + * them via their wrappers, e.g. sprkl_trasport_sendrequest(). + * \todo Non-blocking version? */ +struct sprkl_transport { + void* opaque; /**< Instance-specific data. */ + /** Sends a request through the transport. + * \param 1 Reference to this transport itself. + * \param 2 The request to send. + * \returns 0 if successful or some number otherwise. */ + int (*sendrequest)(struct sprkl_transport*, struct sprkl_request*); + /** Waits for and receives a request through the transport. + * \param 1 Reference to this transport itself. + * \param 2 Empty request object to fill the data into. + * \returns 0 if successful or some number otherwise. */ + int (*recvrequest)(struct sprkl_transport*, struct sprkl_request*); + /** Sends a response through the transport. + * \param 1 Reference to this transport itself. + * \param 2 The response to send. + * \returns 0 if successful or some number otherwise. */ + int (*sendresponse)(struct sprkl_transport*, struct sprkl_response*); + /** Waits for and receives a response through the transport. + * \param 1 Reference to this transport itself. + * \param 2 Empty response object to fill the data into. (verb preset by + * caller) + * \returns 0 if successful or some number otherwise. */ + int (*recvresponse)(struct sprkl_transport*, struct sprkl_response*); + /** Converts an error number to a string representation. + * \param 1 Reference to this transport itself. + * \param 2 The error number as returned by one of the other virtual + * functions. + * \returns Null-terminated string pointer that should \b not be freed + * by the caller. */ + char* (*strerror)(struct sprkl_transport*, int); +}; + +/** Sends a request via the given transport. + * \param t Concrete transport to use. + * \param request Request to send. + * \returns 0 if successful, or a transport-defined error number. */ +int sprkl_transport_sendrequest( + struct sprkl_transport* t, struct sprkl_request* request); + +/** Receives a request via the given transport. + * \param t Concrete transport to use. + * \param request Empty request to receive data into. + * \returns 0 if successful, or a transport-defined error number. */ +int sprkl_transport_recvrequest( + struct sprkl_transport* t, struct sprkl_request* request); + +/** Sends a response via the given transport. + * \param t Concrete transport to use. + * \param response Response to send. + * \returns 0 if successful, or a transport-defined error number. */ +int sprkl_transport_sendresponse( + struct sprkl_transport* t, struct sprkl_response* response); + +/** Receives a response via the given transport. + * \param t Concrete transport to use. + * \param response Response struct to receive data into. It should be empty + * except for the sprkl_response::verb field, which you should set to match the + * verb of the associated request, in order for the response to be parsed + * correctly. + * \returns 0 if successfulm or a transport-defined error number. */ +int sprkl_transport_recvresponse( + struct sprkl_transport* t, struct sprkl_response* response); + +/** Converts a transport-defined error number to a string description. + * \param t Transport which generated the error. + * \param error Error number returned from another transport function. + * \returns Null-terminated string describing the error. Should not be freed by + * caller. */ +char* sprkl_transport_strerror(struct sprkl_transport* t, int error); + + +/** Creates a new transport operating on the given stdio stream. + * The stream will be left open after using transport functions, so remember + * to close it yourself. + * \param stream Read-write stream, for example a network socket. + * \returns a transport that reads and writes to the stream. */ +struct sprkl_transport sprkl_transport_make_stream(FILE* stream); + +#endif |
