|  | // 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 <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; | 
|  |  | 
|  | /* if set, the number of bytes requested from readbuf that were not | 
|  | * fulfilled (due to readbuf being empty) */ | 
|  | int read_requested; | 
|  | }; | 
|  |  | 
|  | /*--------------- 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 can be read out of the buffer without wrapping */ | 
|  | static int memio_buffer_used_contiguous(const struct memio_buffer *mb); | 
|  |  | 
|  | /* How many bytes exist after the wrap? */ | 
|  | static int memio_buffer_wrapped_bytes(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); | 
|  |  | 
|  | /* 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->bufsize = 0; | 
|  | mb->head = 0; | 
|  | mb->tail = 0; | 
|  | } | 
|  |  | 
|  | /* 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 exist after the wrap? */ | 
|  | static int memio_buffer_wrapped_bytes(const struct memio_buffer *mb) | 
|  | { | 
|  | return (mb->tail >= mb->head) ? 0 : mb->tail; | 
|  | } | 
|  |  | 
|  | /* 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); | 
|  | } | 
|  |  | 
|  | /* 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) { | 
|  | secret->read_requested = len; | 
|  | /* If there is no more data in the buffer, report any pending errors | 
|  | * that were previously observed. Note that both the readbuf and the | 
|  | * writebuf are checked for errors, since the application may have | 
|  | * encountered a socket error while writing that would otherwise not | 
|  | * be reported until the application attempted to write again - which | 
|  | * it may never do. | 
|  | */ | 
|  | if (mb->last_err) | 
|  | PR_SetError(mb->last_err, 0); | 
|  | else if (secret->writebuf.last_err) | 
|  | PR_SetError(secret->writebuf.last_err, 0); | 
|  | else | 
|  | PR_SetError(PR_WOULD_BLOCK_ERROR, 0); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | secret->read_requested = 0; | 
|  | 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); | 
|  |  | 
|  | /* Note that the read error state is not reported, because it cannot be | 
|  | * reported until all buffered data has been read. If there is an error | 
|  | * with the next layer, attempting to call Send again will report the | 
|  | * error appropriately. | 
|  | */ | 
|  | 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 readbufsize, int writebufsize) | 
|  | { | 
|  | 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, readbufsize); | 
|  | memio_buffer_new(&secret->writebuf, writebufsize); | 
|  | 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_GetReadRequest(memio_Private *secret) | 
|  | { | 
|  | return ((PRFilePrivate *)secret)->read_requested; | 
|  | } | 
|  |  | 
|  | 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); | 
|  | } | 
|  |  | 
|  | int memio_GetReadableBufferSize(memio_Private *secret) | 
|  | { | 
|  | struct memio_buffer* mb = &((PRFilePrivate *)secret)->readbuf; | 
|  | PR_ASSERT(mb->bufsize); | 
|  |  | 
|  | return memio_buffer_used_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; | 
|  | } | 
|  |  | 
|  | /* Clear read_requested now that the read has been satisfied. */ | 
|  | ((PRFilePrivate *)secret)->read_requested = 0; | 
|  | } | 
|  |  | 
|  | int memio_GetWriteParams(memio_Private *secret, | 
|  | const char **buf1, unsigned int *len1, | 
|  | const char **buf2, unsigned int *len2) | 
|  | { | 
|  | struct memio_buffer* mb = &((PRFilePrivate *)secret)->writebuf; | 
|  | PR_ASSERT(mb->bufsize); | 
|  |  | 
|  | if (mb->last_err) | 
|  | return mb->last_err; | 
|  |  | 
|  | *buf1 = &mb->buf[mb->head]; | 
|  | *len1 = memio_buffer_used_contiguous(mb); | 
|  | *buf2 = mb->buf; | 
|  | *len2 = memio_buffer_wrapped_bytes(mb); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | 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 -= mb->bufsize; | 
|  | } 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_unused_contiguous(&mb), TEST_BUFLEN-1); | 
|  | CHECKEQ(memio_buffer_used_contiguous(&mb), 0); | 
|  |  | 
|  | CHECKEQ(memio_buffer_put(&mb, "howdy", 5), 5); | 
|  |  | 
|  | CHECKEQ(memio_buffer_unused_contiguous(&mb), TEST_BUFLEN-1-5); | 
|  | CHECKEQ(memio_buffer_used_contiguous(&mb), 5); | 
|  | CHECKEQ(memio_buffer_wrapped_bytes(&mb), 0); | 
|  |  | 
|  | CHECKEQ(memio_buffer_put(&mb, "!", 1), 1); | 
|  |  | 
|  | CHECKEQ(memio_buffer_unused_contiguous(&mb), 0); | 
|  | CHECKEQ(memio_buffer_used_contiguous(&mb), 6); | 
|  | CHECKEQ(memio_buffer_wrapped_bytes(&mb), 0); | 
|  |  | 
|  | CHECKEQ(memio_buffer_get(&mb, buf, 6), 6); | 
|  | CHECKEQ(memcmp(buf, "howdy!", 6), 0); | 
|  |  | 
|  | CHECKEQ(memio_buffer_unused_contiguous(&mb), 1); | 
|  | CHECKEQ(memio_buffer_used_contiguous(&mb), 0); | 
|  |  | 
|  | CHECKEQ(memio_buffer_put(&mb, "01234", 5), 5); | 
|  |  | 
|  | CHECKEQ(memio_buffer_used_contiguous(&mb), 1); | 
|  | CHECKEQ(memio_buffer_wrapped_bytes(&mb), 4); | 
|  | CHECKEQ(memio_buffer_unused_contiguous(&mb), TEST_BUFLEN-1-5); | 
|  |  | 
|  | CHECKEQ(memio_buffer_put(&mb, "5", 1), 1); | 
|  |  | 
|  | CHECKEQ(memio_buffer_unused_contiguous(&mb), 0); | 
|  | CHECKEQ(memio_buffer_used_contiguous(&mb), 1); | 
|  |  | 
|  | /* TODO: add more cases */ | 
|  |  | 
|  | printf("Test passed\n"); | 
|  | exit(0); | 
|  | } | 
|  |  | 
|  | #endif |