]> git.tuebingen.mpg.de Git - paraslash.git/blobdiff - sideband.c
Add sideband implementation.
[paraslash.git] / sideband.c
diff --git a/sideband.c b/sideband.c
new file mode 100644 (file)
index 0000000..bf99088
--- /dev/null
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2012 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file sideband.c Implementation of the sideband API. */
+
+#include <regex.h>
+#include <sys/uio.h>
+
+#include "para.h"
+#include "error.h"
+#include "portable_io.h"
+#include "string.h"
+#include "sideband.h"
+
+/** Each sideband packet consists of a header and a data part. */
+#define SIDEBAND_HEADER_SIZE 5
+
+struct sb_context {
+       char header[SIDEBAND_HEADER_SIZE];
+       size_t bytes_dispatched; /* including header */
+       sb_transformation trafo;
+       void *trafo_context;
+       struct sb_buffer sbb;
+       size_t max_size;
+       bool dont_free;
+};
+
+/**
+ * Prepare to receive a sideband packet.
+ *
+ * \param max_size Do not allocate more than this many bytes.
+ * \param t Optional sideband transformation.
+ * \param trafo_context Passed verbatim to \a t.
+ *
+ * \a trafo_context is ignored if \a t is \p NULL.
+ *
+ * \return An opaque sideband handle.
+ */
+struct sb_context *sb_new_recv(size_t max_size, sb_transformation t,
+               void *trafo_context)
+{
+       struct sb_context *c = para_calloc(sizeof(*c));
+
+       c->max_size = max_size;
+       c->trafo = t;
+       c->trafo_context = trafo_context;
+       return c;
+}
+
+/**
+ * Prepare to write a sideband packet.
+ *
+ * \param sbb Data and meta data to send.
+ * \param dont_free Do not try to deallocate the sideband buffer.
+ * \param t See \ref sb_new_recv().
+ * \param trafo_context See \ref sb_new_recv().
+ *
+ * It's OK to supply a zero-sized buffer in \a sbb. In this case only the band
+ * designator is sent through the sideband channel. Otherwise, if \a dont_free
+ * is false, the buffer of \a sbb is freed after the data has been sent.
+ *
+ * \return See \ref sb_new_recv().
+ */
+struct sb_context *sb_new_send(struct sb_buffer *sbb, bool dont_free,
+               sb_transformation t, void *trafo_context)
+{
+       struct sb_context *c = para_calloc(sizeof(*c));
+       struct iovec src, dst, *srcp, *dstp;
+
+       assert(sbb);
+       c->trafo = t;
+       c->trafo_context = trafo_context;
+       c->dont_free = dont_free;
+       c->sbb = *sbb;
+       write_u32(c->header, sbb->iov.iov_len);
+       write_u8(c->header + 4, sbb->band);
+       if (!t)
+               goto out;
+       src = (typeof(src)){.iov_base = c->header, .iov_len = SIDEBAND_HEADER_SIZE};
+       t(&src, &dst, trafo_context);
+       if (src.iov_base != dst.iov_base) {
+               memcpy(c->header, dst.iov_base, SIDEBAND_HEADER_SIZE);
+               free(dst.iov_base);
+       }
+       if (!iov_valid(&sbb->iov))
+               goto out;
+       srcp = &sbb->iov;
+       dstp = &c->sbb.iov;
+       t(srcp, dstp, trafo_context);
+       if (srcp->iov_base != dstp->iov_base) {
+               if (!c->dont_free)
+                       free(srcp->iov_base);
+               c->dont_free = false;
+       }
+out:
+       return c;
+}
+
+/**
+ * Deallocate all memory associated with a sideband handle.
+ *
+ * \param c The sideband handle.
+ *
+ * \a c must point to a handle previously returned by \ref sb_new_recv() or
+ * \ref sb_new_send(). It \a c is \p NULL, the function does nothing.
+ */
+void sb_free(struct sb_context *c)
+{
+       if (!c)
+               return;
+       if (!c->dont_free)
+               free(c->sbb.iov.iov_base);
+       free(c);
+}
+
+/**
+ * Obtain pointer(s) to the sideband send buffer(s).
+ *
+ * \param c The sideband handle.
+ * \param iov Array of two I/O vectors.
+ *
+ * \return The number of buffers that need to be sent, either 1 or 2.
+ *
+ * This function fills out the buffers described by \a iov. The result can be
+ * passed to \ref xwritev() or similar.
+ *
+ * \sa \ref sb_get_recv_buffer().
+ */
+int sb_get_send_buffers(struct sb_context *c, struct iovec iov[2])
+{
+       struct sb_buffer *sbb = &c->sbb;
+       size_t n = c->bytes_dispatched;
+
+       if (n < SIDEBAND_HEADER_SIZE) {
+               iov[0].iov_base = c->header + n;
+               iov[0].iov_len = SIDEBAND_HEADER_SIZE - n;
+               if (!iov_valid(&sbb->iov))
+                       goto out;
+               iov[1] = sbb->iov;
+               return 2;
+       }
+       n -= SIDEBAND_HEADER_SIZE;
+       assert(n < sbb->iov.iov_len);
+       iov[0].iov_base = sbb->iov.iov_base + n;
+       iov[0].iov_len = sbb->iov.iov_len - n;
+out:
+       iov[1].iov_base = NULL;
+       iov[1].iov_len = 0;
+       return 1;
+}
+
+/**
+ * Update the sideband context after data has been sent.
+ *
+ * \param c The sideband handle.
+ * \param nbytes The number of sent bytes.
+ *
+ * \return False if more data must be sent to complete the sideband transfer,
+ * true if the transfer is complete. In this case all resources are freed and
+ * the sideband handle must not be used any more.
+ */
+bool sb_sent(struct sb_context *c, size_t nbytes)
+{
+       struct sb_buffer *sbb = &c->sbb;
+       size_t sz = SIDEBAND_HEADER_SIZE + sbb->iov.iov_len;
+
+       assert(c->bytes_dispatched + nbytes <= sz);
+       c->bytes_dispatched += nbytes;
+       if (c->bytes_dispatched < sz)
+               return false;
+       sb_free(c);
+       return true;
+}
+
+/**
+ * Obtain a pointer to the next sideband read buffer.
+ *
+ * \param c The sideband handle.
+ * \param iov Result IO vector.
+ *
+ * This fills in \a iov to point to the buffer to which the next chunk of
+ * received data should be written.
+ */
+void sb_get_recv_buffer(struct sb_context *c, struct iovec *iov)
+{
+       struct sb_buffer *sbb = &c->sbb;
+       size_t n = c->bytes_dispatched;
+
+       if (n < SIDEBAND_HEADER_SIZE) {
+               iov->iov_base = c->header + n;
+               iov->iov_len = SIDEBAND_HEADER_SIZE - n;
+               return;
+       }
+       n -= SIDEBAND_HEADER_SIZE;
+       assert(sbb->iov.iov_base);
+       assert(sbb->iov.iov_len > n);
+       iov->iov_base = sbb->iov.iov_base + n;
+       iov->iov_len = sbb->iov.iov_len - n;
+}
+
+/**
+ * Update the sideband context after data has been received.
+ *
+ * \param c The sideband handle.
+ * \param nbytes The number of bytes that have been received.
+ * \param result The received sideband packet.
+ *
+ * \return Negative on errors, zero if more data needs to be read to complete
+ * this sideband packet, one if the sideband packet has been received
+ * completely.
+ *
+ * Only if the function returns one, the sideband buffer pointed to by \a
+ * result is set to point to the received data.
+ */
+int sb_received(struct sb_context *c, size_t nbytes, struct sb_buffer *result)
+{
+       struct sb_buffer *sbb = &c->sbb;
+       size_t n = c->bytes_dispatched,
+               sz = SIDEBAND_HEADER_SIZE + sbb->iov.iov_len;
+
+       assert(n + nbytes <= sz);
+       c->bytes_dispatched += nbytes;
+       if (c->bytes_dispatched < SIDEBAND_HEADER_SIZE)
+               return 0;
+       if (n >= SIDEBAND_HEADER_SIZE) { /* header has already been received */
+               if (c->bytes_dispatched < sz) /* need to recv more body data */
+                       return 0;
+               /* received everything, decrypt and return sbb */
+               if (c->trafo) {
+                       struct iovec dst;
+                       c->trafo(&sbb->iov, &dst, c->trafo_context);
+                       if (sbb->iov.iov_base != dst.iov_base) {
+                               free(sbb->iov.iov_base);
+                               sbb->iov.iov_base = dst.iov_base;
+                       }
+               }
+               ((char *)(sbb->iov.iov_base))[sbb->iov.iov_len] = '\0';
+               goto success;
+       }
+       /* header has been received, decrypt and decode it */
+       if (c->trafo) { /* decrypt */
+               struct iovec dst, src = (typeof(src)) {
+                       .iov_base = c->header,
+                       .iov_len = SIDEBAND_HEADER_SIZE
+               };
+               c->trafo(&src, &dst, c->trafo_context);
+               if (src.iov_base != dst.iov_base) {
+                       memcpy(c->header, dst.iov_base,
+                               SIDEBAND_HEADER_SIZE);
+                       free(dst.iov_base);
+               }
+       }
+       /* Decode header, setup sbb */
+       sbb->iov.iov_len = read_u32(c->header);
+       sbb->band = read_u8(c->header + 4);
+       sbb->iov.iov_base = NULL;
+       if (sbb->iov.iov_len == 0) /* zero-sized msg */
+               goto success;
+       if (c->max_size > 0 && sbb->iov.iov_len > c->max_size) {
+               PARA_ERROR_LOG("packet too big (is %zu, max %zu)\n",
+                       sbb->iov.iov_len, c->max_size);
+               return -E_SB_PACKET_SIZE;
+       }
+       /*
+        * We like to reserve one extra byte for the terminating NULL
+        * character. However, we must make sure the +1 below does not
+        * overflow iov_len.
+        */
+       if (sbb->iov.iov_len == (size_t)-1)
+               return -E_SB_PACKET_SIZE;
+       sbb->iov.iov_base = para_malloc(sbb->iov.iov_len + 1);
+       return 0; /* ready to read body */
+success:
+       *result = c->sbb;
+       free(c);
+       return 1;
+}