aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--docs/examples.md4
-rw-r--r--docs/fetch-tcp.example.c71
-rw-r--r--libsparklepaw/request.c118
-rw-r--r--libsparklepaw/request.h88
-rw-r--r--libsparklepaw/transport.c75
-rw-r--r--libsparklepaw/transport.h93
7 files changed, 444 insertions, 7 deletions
diff --git a/Makefile b/Makefile
index 07afb7e..19eb0e7 100644
--- a/Makefile
+++ b/Makefile
@@ -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