diff options
Diffstat (limited to 'net/base/nss_memio.c')
-rw-r--r-- | net/base/nss_memio.c | 547 |
1 files changed, 547 insertions, 0 deletions
diff --git a/net/base/nss_memio.c b/net/base/nss_memio.c new file mode 100644 index 0000000..ca3421e --- /dev/null +++ b/net/base/nss_memio.c @@ -0,0 +1,547 @@ +// Copyright (c) 2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Written in NSPR style to also be suitable for adding to the NSS demo suite + +/* memio is a simple NSPR I/O layer that lets you decouple NSS from + * the real network. It's rather like openssl's memory bio, + * and is useful when your app absolutely, positively doesn't + * want to let NSS do its own networking. + */ + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <prerror.h> +#include <prinit.h> +#include <prlog.h> + +#include "nss_memio.h" + +/*--------------- private memio types -----------------------*/ + +/*---------------------------------------------------------------------- + Simple private circular buffer class. Size cannot be changed once allocated. +----------------------------------------------------------------------*/ + +struct memio_buffer { + int head; /* where to take next byte out of buf */ + int tail; /* where to put next byte into buf */ + int bufsize; /* number of bytes allocated to buf */ + /* TODO(port): error handling is pessimistic right now. + * Once an error is set, the socket is considered broken + * (PR_WOULD_BLOCK_ERROR not included). + */ + PRErrorCode last_err; + char *buf; +}; + + +/* The 'secret' field of a PRFileDesc created by memio_CreateIOLayer points + * to one of these. + * In the public header, we use struct memio_Private as a typesafe alias + * for this. This causes a few ugly typecasts in the private file, but + * seems safer. + */ +struct PRFilePrivate { + /* read requests are satisfied from this buffer */ + struct memio_buffer readbuf; + + /* write requests are satisfied from this buffer */ + struct memio_buffer writebuf; + + /* SSL needs to know socket peer's name */ + PRNetAddr peername; + + /* if set, empty I/O returns EOF instead of EWOULDBLOCK */ + int eof; +}; + +/*--------------- private memio_buffer functions ---------------------*/ + +/* Forward declarations. */ + +/* Allocate a memio_buffer of given size. */ +static void memio_buffer_new(struct memio_buffer *mb, int size); + +/* Deallocate a memio_buffer allocated by memio_buffer_new. */ +static void memio_buffer_destroy(struct memio_buffer *mb); + +/* How many bytes have been put into the buffer */ +static int memio_buffer_used(const struct memio_buffer *mb); + +/* How many bytes can be read out of the buffer without wrapping */ +static int memio_buffer_used_contiguous(const struct memio_buffer *mb); + +/* How many bytes can still be put into the buffer */ +static int memio_buffer_unused(const struct memio_buffer *mb); + +/* How many bytes can be written into the buffer without wrapping */ +static int memio_buffer_unused_contiguous(const struct memio_buffer *mb); + +/* Is buffer completely empty? */ +static int memio_buffer_empty(const struct memio_buffer *mb); + +/* Is buffer completely full? */ +static int memio_buffer_full(const struct memio_buffer *mb); + +/* Write n bytes into the buffer. Returns number of bytes written. */ +static int memio_buffer_put(struct memio_buffer *mb, const char *buf, int n); + +/* Read n bytes from the buffer. Returns number of bytes read. */ +static int memio_buffer_get(struct memio_buffer *mb, char *buf, int n); + +/* Allocate a memio_buffer of given size. */ +static void memio_buffer_new(struct memio_buffer *mb, int size) +{ + mb->head = 0; + mb->tail = 0; + mb->bufsize = size; + mb->buf = malloc(size); +} + +/* Deallocate a memio_buffer allocated by memio_buffer_new. */ +static void memio_buffer_destroy(struct memio_buffer *mb) +{ + free(mb->buf); + mb->buf = NULL; + mb->head = 0; + mb->tail = 0; +} + +/* How many bytes have been put into the buffer */ +static int memio_buffer_used(const struct memio_buffer *mb) +{ + int n = mb->tail - mb->head; + if (n < 0) n += mb->bufsize; + return n; +} + +/* How many bytes can be read out of the buffer without wrapping */ +static int memio_buffer_used_contiguous(const struct memio_buffer *mb) +{ + return (((mb->tail >= mb->head) ? mb->tail : mb->bufsize) - mb->head); +} + +/* How many bytes can still be put into the buffer */ +static int memio_buffer_unused(const struct memio_buffer *mb) +{ + return mb->bufsize - memio_buffer_used(mb) - 1; +} + +/* How many bytes can be written into the buffer without wrapping */ +static int memio_buffer_unused_contiguous(const struct memio_buffer *mb) +{ + if (mb->head > mb->tail) return mb->head - mb->tail - 1; + return mb->bufsize - mb->tail - (mb->head == 0); +} + +/* Is buffer completely empty? */ +static int memio_buffer_empty(const struct memio_buffer *mb) +{ + return mb->head == mb->tail; +} + +/* Is buffer completely full? */ +static int memio_buffer_full(const struct memio_buffer *mb) +{ + return memio_buffer_unused(mb) == 0; +} + +/* Write n bytes into the buffer. Returns number of bytes written. */ +static int memio_buffer_put(struct memio_buffer *mb, const char *buf, int n) +{ + int len; + int transferred = 0; + + /* Handle part before wrap */ + len = PR_MIN(n, memio_buffer_unused_contiguous(mb)); + if (len > 0) { + /* Buffer not full */ + memcpy(&mb->buf[mb->tail], buf, len); + mb->tail += len; + if (mb->tail == mb->bufsize) + mb->tail = 0; + n -= len; + buf += len; + transferred += len; + + /* Handle part after wrap */ + len = PR_MIN(n, memio_buffer_unused_contiguous(mb)); + if (len > 0) { + /* Output buffer still not full, input buffer still not empty */ + memcpy(&mb->buf[mb->tail], buf, len); + mb->tail += len; + if (mb->tail == mb->bufsize) + mb->tail = 0; + transferred += len; + } + } + + return transferred; +} + + +/* Read n bytes from the buffer. Returns number of bytes read. */ +static int memio_buffer_get(struct memio_buffer *mb, char *buf, int n) +{ + int len; + int transferred = 0; + + /* Handle part before wrap */ + len = PR_MIN(n, memio_buffer_used_contiguous(mb)); + if (len) { + memcpy(buf, &mb->buf[mb->head], len); + mb->head += len; + if (mb->head == mb->bufsize) + mb->head = 0; + n -= len; + buf += len; + transferred += len; + + /* Handle part after wrap */ + len = PR_MIN(n, memio_buffer_used_contiguous(mb)); + if (len) { + memcpy(buf, &mb->buf[mb->head], len); + mb->head += len; + if (mb->head == mb->bufsize) + mb->head = 0; + transferred += len; + } + } + + return transferred; +} + +/*--------------- private memio functions -----------------------*/ + +static PRStatus PR_CALLBACK memio_Close(PRFileDesc *fd) +{ + struct PRFilePrivate *secret = fd->secret; + memio_buffer_destroy(&secret->readbuf); + memio_buffer_destroy(&secret->writebuf); + free(secret); + fd->dtor(fd); + return PR_SUCCESS; +} + +static PRStatus PR_CALLBACK memio_Shutdown(PRFileDesc *fd, PRIntn how) +{ + /* TODO: pass shutdown status to app somehow */ + return PR_SUCCESS; +} + +/* If there was a network error in the past taking bytes + * out of the buffer, return it to the next call that + * tries to read from an empty buffer. + */ +static int PR_CALLBACK memio_Recv(PRFileDesc *fd, void *buf, PRInt32 len, + PRIntn flags, PRIntervalTime timeout) +{ + struct PRFilePrivate *secret; + struct memio_buffer *mb; + int rv; + + if (flags) { + PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); + return -1; + } + + secret = fd->secret; + mb = &secret->readbuf; + PR_ASSERT(mb->bufsize); + rv = memio_buffer_get(mb, buf, len); + if (rv == 0 && !secret->eof) { + if (mb->last_err) + PR_SetError(mb->last_err, 0); + else + PR_SetError(PR_WOULD_BLOCK_ERROR, 0); + return -1; + } + + return rv; +} + +static int PR_CALLBACK memio_Read(PRFileDesc *fd, void *buf, PRInt32 len) +{ + /* pull bytes from buffer */ + return memio_Recv(fd, buf, len, 0, PR_INTERVAL_NO_TIMEOUT); +} + +static int PR_CALLBACK memio_Send(PRFileDesc *fd, const void *buf, PRInt32 len, + PRIntn flags, PRIntervalTime timeout) +{ + struct PRFilePrivate *secret; + struct memio_buffer *mb; + int rv; + + secret = fd->secret; + mb = &secret->writebuf; + PR_ASSERT(mb->bufsize); + + if (mb->last_err) { + PR_SetError(mb->last_err, 0); + return -1; + } + rv = memio_buffer_put(mb, buf, len); + if (rv == 0) { + PR_SetError(PR_WOULD_BLOCK_ERROR, 0); + return -1; + } + return rv; +} + +static int PR_CALLBACK memio_Write(PRFileDesc *fd, const void *buf, PRInt32 len) +{ + /* append bytes to buffer */ + return memio_Send(fd, buf, len, 0, PR_INTERVAL_NO_TIMEOUT); +} + +static PRStatus PR_CALLBACK memio_GetPeerName(PRFileDesc *fd, PRNetAddr *addr) +{ + /* TODO: fail if memio_SetPeerName has not been called */ + struct PRFilePrivate *secret = fd->secret; + *addr = secret->peername; + return PR_SUCCESS; +} + +static PRStatus memio_GetSocketOption(PRFileDesc *fd, PRSocketOptionData *data) +{ + /* + * Even in the original version for real tcp sockets, + * PR_SockOpt_Nonblocking is a special case that does not + * translate to a getsockopt() call + */ + if (PR_SockOpt_Nonblocking == data->option) { + data->value.non_blocking = PR_TRUE; + return PR_SUCCESS; + } + PR_SetError(PR_OPERATION_NOT_SUPPORTED_ERROR, 0); + return PR_FAILURE; +} + +/*--------------- private memio data -----------------------*/ + +/* + * Implement just the bare minimum number of methods needed to make ssl happy. + * + * Oddly, PR_Recv calls ssl_Recv calls ssl_SocketIsBlocking calls + * PR_GetSocketOption, so we have to provide an implementation of + * PR_GetSocketOption that just says "I'm nonblocking". + */ + +static struct PRIOMethods memio_layer_methods = { + PR_DESC_LAYERED, + memio_Close, + memio_Read, + memio_Write, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + memio_Shutdown, + memio_Recv, + memio_Send, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + memio_GetPeerName, + NULL, + NULL, + memio_GetSocketOption, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +static PRDescIdentity memio_identity = PR_INVALID_IO_LAYER; + +static PRStatus memio_InitializeLayerName(void) +{ + memio_identity = PR_GetUniqueIdentity("memio"); + return PR_SUCCESS; +} + +/*--------------- public memio functions -----------------------*/ + +PRFileDesc *memio_CreateIOLayer(int bufsize) +{ + PRFileDesc *fd; + struct PRFilePrivate *secret; + static PRCallOnceType once; + + PR_CallOnce(&once, memio_InitializeLayerName); + + fd = PR_CreateIOLayerStub(memio_identity, &memio_layer_methods); + secret = malloc(sizeof(struct PRFilePrivate)); + memset(secret, 0, sizeof(*secret)); + + memio_buffer_new(&secret->readbuf, bufsize); + memio_buffer_new(&secret->writebuf, bufsize); + fd->secret = secret; + return fd; +} + +void memio_SetPeerName(PRFileDesc *fd, const PRNetAddr *peername) +{ + PRFileDesc *memiofd = PR_GetIdentitiesLayer(fd, memio_identity); + struct PRFilePrivate *secret = memiofd->secret; + secret->peername = *peername; +} + +memio_Private *memio_GetSecret(PRFileDesc *fd) +{ + PRFileDesc *memiofd = PR_GetIdentitiesLayer(fd, memio_identity); + struct PRFilePrivate *secret = memiofd->secret; + return (memio_Private *)secret; +} + +int memio_GetReadParams(memio_Private *secret, char **buf) +{ + struct memio_buffer* mb = &((PRFilePrivate *)secret)->readbuf; + PR_ASSERT(mb->bufsize); + + *buf = &mb->buf[mb->tail]; + return memio_buffer_unused_contiguous(mb); +} + +void memio_PutReadResult(memio_Private *secret, int bytes_read) +{ + struct memio_buffer* mb = &((PRFilePrivate *)secret)->readbuf; + PR_ASSERT(mb->bufsize); + + if (bytes_read > 0) { + mb->tail += bytes_read; + if (mb->tail == mb->bufsize) + mb->tail = 0; + } else if (bytes_read == 0) { + /* Record EOF condition and report to caller when buffer runs dry */ + ((PRFilePrivate *)secret)->eof = PR_TRUE; + } else /* if (bytes_read < 0) */ { + mb->last_err = bytes_read; + } +} + +int memio_GetWriteParams(memio_Private *secret, const char **buf) +{ + struct memio_buffer* mb = &((PRFilePrivate *)secret)->writebuf; + PR_ASSERT(mb->bufsize); + + *buf = &mb->buf[mb->head]; + return memio_buffer_used_contiguous(mb); +} + +void memio_PutWriteResult(memio_Private *secret, int bytes_written) +{ + struct memio_buffer* mb = &((PRFilePrivate *)secret)->writebuf; + PR_ASSERT(mb->bufsize); + + if (bytes_written > 0) { + mb->head += bytes_written; + if (mb->head == mb->bufsize) + mb->head = 0; + } else if (bytes_written < 0) { + mb->last_err = bytes_written; + } +} + +/*--------------- private memio_buffer self-test -----------------*/ + +/* Even a trivial unit test is very helpful when doing circular buffers. */ +/*#define TRIVIAL_SELF_TEST*/ +#ifdef TRIVIAL_SELF_TEST +#include <stdio.h> + +#define TEST_BUFLEN 7 + +#define CHECKEQ(a, b) { \ + if ((a) != (b)) { \ + printf("%d != %d, Test failed line %d\n", a, b, __LINE__); \ + exit(1); \ + } \ +} + +int main() +{ + struct memio_buffer mb; + char buf[100]; + int i; + + memio_buffer_new(&mb, TEST_BUFLEN); + + CHECKEQ(memio_buffer_empty(&mb), TRUE); + CHECKEQ(memio_buffer_full(&mb), FALSE); + CHECKEQ(memio_buffer_unused_contiguous(&mb), TEST_BUFLEN-1); + CHECKEQ(memio_buffer_unused(&mb), TEST_BUFLEN-1); + CHECKEQ(memio_buffer_used_contiguous(&mb), 0); + CHECKEQ(memio_buffer_used(&mb), 0); + + CHECKEQ(memio_buffer_put(&mb, "howdy", 5), 5); + + CHECKEQ(memio_buffer_empty(&mb), FALSE); + CHECKEQ(memio_buffer_full(&mb), FALSE); + CHECKEQ(memio_buffer_unused_contiguous(&mb), TEST_BUFLEN-1-5); + CHECKEQ(memio_buffer_unused(&mb), TEST_BUFLEN-1-5); + CHECKEQ(memio_buffer_used_contiguous(&mb), 5); + CHECKEQ(memio_buffer_used(&mb), 5); + + CHECKEQ(memio_buffer_put(&mb, "!", 1), 1); + + CHECKEQ(memio_buffer_empty(&mb), FALSE); + CHECKEQ(memio_buffer_full(&mb), TRUE); + CHECKEQ(memio_buffer_unused_contiguous(&mb), 0); + CHECKEQ(memio_buffer_unused(&mb), 0); + CHECKEQ(memio_buffer_used_contiguous(&mb), 6); + CHECKEQ(memio_buffer_used(&mb), 6); + + CHECKEQ(memio_buffer_get(&mb, buf, 6), 6); + CHECKEQ(memcmp(buf, "howdy!", 6), 0); + + CHECKEQ(memio_buffer_empty(&mb), TRUE); + CHECKEQ(memio_buffer_full(&mb), FALSE); + CHECKEQ(memio_buffer_unused(&mb), TEST_BUFLEN-1); + CHECKEQ(memio_buffer_unused_contiguous(&mb), 1); + CHECKEQ(memio_buffer_used_contiguous(&mb), 0); + CHECKEQ(memio_buffer_used(&mb), 0); + + CHECKEQ(memio_buffer_put(&mb, "01234", 5), 5); + + CHECKEQ(memio_buffer_empty(&mb), FALSE); + CHECKEQ(memio_buffer_full(&mb), FALSE); + CHECKEQ(memio_buffer_used(&mb), 5); + CHECKEQ(memio_buffer_used_contiguous(&mb), 1); + CHECKEQ(memio_buffer_unused_contiguous(&mb), TEST_BUFLEN-1-5); + CHECKEQ(memio_buffer_unused(&mb), TEST_BUFLEN-1-5); + + CHECKEQ(memio_buffer_put(&mb, "5", 1), 1); + + CHECKEQ(memio_buffer_empty(&mb), FALSE); + CHECKEQ(memio_buffer_full(&mb), TRUE); + CHECKEQ(memio_buffer_unused_contiguous(&mb), 0); + CHECKEQ(memio_buffer_unused(&mb), 0); + CHECKEQ(memio_buffer_used_contiguous(&mb), 1); + CHECKEQ(memio_buffer_used(&mb), 6); + + /* TODO: add more cases */ + + printf("Test passed\n"); + exit(0); +} + +#endif |