/* SPDX-License-Identifier: GPL-2.0 */ /** \file sideband.c Implementation of the sideband API. */ #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 the transformation. * * If a subsequent call to \ref sb_received() indicates a packet size larger * than the specified maximal size, the input will be rejected as invalid. The * trafo context is ignored if the transformation pointer is NULL. * * \return An opaque sideband handle. This function never fails. */ struct sb_context *sb_new_recv(size_t max_size, sb_transformation t, void *trafo_context) { struct sb_context *c = zalloc(sizeof(*c)); c->max_size = max_size; c->trafo = t; c->trafo_context = trafo_context; return c; } /* Return true if iov points to a non-empty buffer. */ static bool iov_empty(const struct iovec *iov) { return !iov || !iov->iov_base || iov->iov_len == 0; } /** * 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 sideband buffer. In this case only the band * designator is sent through the sideband channel. Otherwise, if dont_free * is false, the sideband buffer 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 = zalloc(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_empty(&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. * * The handle is a pointer which was returned by an earlier call to \ref * sb_new_recv() or \ref sb_new_send(). It's OK to pass NULL, the function * does nothing in this case. */ 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). * * This function initializes the buffer pointers and size values of the given * I/O vector array for a subsequent call to \ref xwritev() or similar, keeping * track of the amount of data that has already been written by earlier calls. * * \param c As returned by \ref sb_new_send(). * \param iov Identifies header and data. * * \return The number of buffers that need to be send to complete the sideband * packet. Either 1 or 2, no errors possible. * * \sa \ref sb_get_recv_buffer(), \ref sb_sent(). */ 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_empty(&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 As returned by \ref sb_new_send(). * \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 As returned by \ref sb_new_recv(). * \param iov Result IO vector. * * This sets the I/O vector pointer to the buffer for the next chunk of * received data and the I/O vector length to the buffer size. */ 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 Handle returned by \ref sb_new_recv(). * \param nbytes The number of bytes that have been received. * \param result The received sideband packet, including the band designator. * * \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 values if the result sideband buffer * are defined. */ 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 = alloc(sbb->iov.iov_len + 1); return 0; /* ready to read body */ success: *result = c->sbb; free(c); return 1; }