crypto: use better memory BIO implementation
authorFedor Indutny <fedor.indutny@gmail.com>
Wed, 3 Apr 2013 12:58:10 +0000 (16:58 +0400)
committerFedor Indutny <fedor.indutny@gmail.com>
Fri, 5 Apr 2013 00:12:13 +0000 (04:12 +0400)
node.gyp
src/node_crypto.cc
src/node_crypto_bio.cc [new file with mode: 0644]
src/node_crypto_bio.h [new file with mode: 0644]

index fc7e215..efeff53 100644 (file)
--- a/node.gyp
+++ b/node.gyp
         'src/node_buffer.h',
         'src/node_constants.h',
         'src/node_crypto.h',
+        'src/node_crypto_bio.h',
         'src/node_extensions.h',
         'src/node_file.h',
         'src/node_http_parser.h',
       'conditions': [
         [ 'node_use_openssl=="true"', {
           'defines': [ 'HAVE_OPENSSL=1' ],
-          'sources': [ 'src/node_crypto.cc' ],
+          'sources': [ 'src/node_crypto.cc', 'src/node_crypto_bio.cc' ],
           'conditions': [
             [ 'node_shared_openssl=="false"', {
               'dependencies': [ './deps/openssl/openssl.gyp:openssl' ],
index ec2dbe4..3c0e76d 100644 (file)
@@ -20,6 +20,7 @@
 // USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 #include "node_crypto.h"
+#include "node_crypto_bio.h"
 #include "node_crypto_groups.h"
 #include "v8.h"
 
@@ -291,7 +292,7 @@ int SecureContext::NewSessionCallback(SSL* s, SSL_SESSION* sess) {
 // Takes a string or buffer and loads it into a BIO.
 // Caller responsible for BIO_free-ing the returned object.
 static BIO* LoadBIO (Handle<Value> v) {
-  BIO *bio = BIO_new(BIO_s_mem());
+  BIO *bio = BIO_new(NodeBIO::GetMethod());
   if (!bio) return NULL;
 
   HandleScope scope;
@@ -544,7 +545,7 @@ Handle<Value> SecureContext::AddRootCerts(const Arguments& args) {
     root_cert_store = X509_STORE_new();
 
     for (int i = 0; root_certs[i]; i++) {
-      BIO *bp = BIO_new(BIO_s_mem());
+      BIO *bp = BIO_new(NodeBIO::GetMethod());
 
       if (!BIO_write(bp, root_certs[i], strlen(root_certs[i]))) {
         BIO_free(bp);
@@ -1201,8 +1202,8 @@ Handle<Value> Connection::New(const Arguments& args) {
   bool is_server = args[1]->BooleanValue();
 
   p->ssl_ = SSL_new(sc->ctx_);
-  p->bio_read_ = BIO_new(BIO_s_mem());
-  p->bio_write_ = BIO_new(BIO_s_mem());
+  p->bio_read_ = BIO_new(NodeBIO::GetMethod());
+  p->bio_write_ = BIO_new(NodeBIO::GetMethod());
 
   SSL_set_app_data(p->ssl_, p);
 
diff --git a/src/node_crypto_bio.cc b/src/node_crypto_bio.cc
new file mode 100644 (file)
index 0000000..a314bf6
--- /dev/null
@@ -0,0 +1,312 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include "node_crypto_bio.h"
+#include "openssl/bio.h"
+#include <string.h>
+
+namespace node {
+
+BIO_METHOD NodeBIO::method_ = {
+  BIO_TYPE_MEM,
+  "node.js SSL buffer",
+  NodeBIO::Write,
+  NodeBIO::Read,
+  NodeBIO::Puts,
+  NodeBIO::Gets,
+  NodeBIO::Ctrl,
+  NodeBIO::New,
+  NodeBIO::Free,
+  NULL
+};
+
+
+int NodeBIO::New(BIO* bio) {
+  bio->ptr = new NodeBIO();
+
+  // XXX Why am I doing it?!
+  bio->shutdown = 1;
+  bio->init = 1;
+  bio->num = -1;
+
+  return 1;
+}
+
+
+int NodeBIO::Free(BIO* bio) {
+  if (bio == NULL) return 0;
+
+  if (bio->shutdown) {
+    if (bio->init && bio->ptr != NULL) {
+      delete FromBIO(bio);
+      bio->ptr = NULL;
+    }
+  }
+
+  return 1;
+}
+
+
+int NodeBIO::Read(BIO* bio, char* out, int len) {
+  int bytes;
+  BIO_clear_retry_flags(bio);
+
+  bytes = FromBIO(bio)->Read(out, len);
+
+  if (bytes == 0) {
+    bytes = bio->num;
+    if (bytes != 0) {
+      BIO_set_retry_read(bio);
+    }
+  }
+
+  return bytes;
+}
+
+
+int NodeBIO::Write(BIO* bio, const char* data, int len) {
+  BIO_clear_retry_flags(bio);
+
+  FromBIO(bio)->Write(data, len);
+
+  return len;
+}
+
+
+int NodeBIO::Puts(BIO* bio, const char* str) {
+  return Write(bio, str, strlen(str));
+}
+
+
+int NodeBIO::Gets(BIO* bio, char* out, int size) {
+  NodeBIO* nbio =  FromBIO(bio);
+
+  if (nbio->Length() == 0)
+    return 0;
+
+  int i = nbio->IndexOf('\n', size);
+
+  // Include '\n'
+  if (i < size) i++;
+
+  // Shift `i` a bit to NULL-terminate string later
+  if (size == i) i--;
+
+  // Flush read data
+  nbio->Read(out, i);
+
+  out[i] = 0;
+
+  return i;
+}
+
+
+long NodeBIO::Ctrl(BIO* bio, int cmd, long num, void* ptr) {
+  NodeBIO* nbio;
+  long ret;
+
+  nbio = FromBIO(bio);
+  ret = 1;
+
+  switch (cmd) {
+   case BIO_CTRL_RESET:
+    nbio->Reset();
+    break;
+   case BIO_CTRL_EOF:
+    ret = nbio->Length() == 0;
+    break;
+   case BIO_C_SET_BUF_MEM_EOF_RETURN:
+    bio->num = num;
+    break;
+   case BIO_CTRL_INFO:
+    ret = nbio->Length();
+    if (ptr != NULL)
+      *reinterpret_cast<void**>(ptr) = NULL;
+    break;
+   case BIO_C_SET_BUF_MEM:
+    assert(0 && "Can't use SET_BUF_MEM_PTR with NodeBIO");
+    abort();
+    break;
+   case BIO_C_GET_BUF_MEM_PTR:
+    assert(0 && "Can't use GET_BUF_MEM_PTR with NodeBIO");
+    ret = 0;
+    break;
+   case BIO_CTRL_GET_CLOSE:
+    ret = bio->shutdown;
+    break;
+   case BIO_CTRL_SET_CLOSE:
+    bio->shutdown = num;
+    break;
+   case BIO_CTRL_WPENDING:
+    ret = 0;
+    break;
+   case BIO_CTRL_PENDING:
+    ret = nbio->Length();
+    break;
+   case BIO_CTRL_DUP:
+   case BIO_CTRL_FLUSH:
+    ret = 1;
+    break;
+   case BIO_CTRL_PUSH:
+   case BIO_CTRL_POP:
+   default:
+    ret = 0;
+    break;
+  }
+  return ret;
+}
+
+
+size_t NodeBIO::Read(char* out, size_t size) {
+  size_t bytes_read = 0;
+  size_t expected = Length() > size ? size : Length();
+
+  while (bytes_read < expected) {
+    assert(read_head_->read_pos_ <= read_head_->write_pos_);
+    size_t avail = read_head_->write_pos_ - read_head_->read_pos_;
+    if (avail > size)
+      avail = size;
+
+    // Copy data
+    if (out != NULL)
+      memcpy(out, read_head_->data_ + read_head_->read_pos_, avail);
+    read_head_->read_pos_ += avail;
+
+    // Move pointers
+    bytes_read += avail;
+    out += avail;
+    size -= avail;
+
+    // Move to next buffer
+    if (read_head_->read_pos_ == read_head_->write_pos_) {
+      read_head_->read_pos_ = 0;
+      read_head_->write_pos_ = 0;
+      read_head_ = read_head_->next_;
+    }
+  }
+  assert(expected == bytes_read);
+  length_ -= bytes_read;
+
+  return bytes_read;
+}
+
+
+size_t NodeBIO::IndexOf(char delim, size_t limit) {
+  size_t bytes_read = 0;
+  size_t max = Length() > limit ? limit : Length();
+  Buffer* current = read_head_;
+
+  while (bytes_read < max) {
+    assert(current->read_pos_ <= current->write_pos_);
+    size_t avail = current->write_pos_ - current->read_pos_;
+    if (avail > limit)
+      avail = limit;
+
+    // Walk through data
+    char* tmp = current->data_ + current->read_pos_;
+    size_t off = 0;
+    while (off < avail && *tmp != delim) {
+      off++;
+      tmp++;
+    }
+
+    // Move pointers
+    bytes_read += off;
+    limit -= off;
+
+    // Found `delim`
+    if (off != avail) {
+      return bytes_read;
+    }
+
+    // Move to next buffer
+    if (current->read_pos_ + avail == kBufferLength) {
+      current = current->next_;
+    }
+  }
+  assert(max == bytes_read);
+
+  return max;
+}
+
+
+void NodeBIO::Write(const char* data, size_t len) {
+  while (len > 0) {
+    size_t to_write = len;
+    assert(write_head_->write_pos_ <= kBufferLength);
+    size_t avail = kBufferLength - write_head_->write_pos_;
+
+    if (to_write > avail)
+      to_write = avail;
+
+    // Copy data
+    memcpy(write_head_->data_ + write_head_->write_pos_, data, to_write);
+    write_head_->write_pos_ += to_write;
+    assert(write_head_->write_pos_ <= kBufferLength);
+
+    // Move pointers
+    len -= to_write;
+    data += to_write;
+    length_ += to_write;
+
+    // Still have some bytes left:
+    //  1. Go to next buffer
+    //  2. Allocate new if next is already full or is partially read
+    //     (is read head)
+    if (write_head_->next_->write_pos_ == kBufferLength ||
+        write_head_->next_->read_pos_ != 0) {
+      Buffer* next = new Buffer();
+      next->next_ = write_head_->next_;
+      write_head_->next_ = next;
+    }
+    write_head_ = write_head_->next_;
+  }
+  assert(len == 0);
+}
+
+
+void NodeBIO::Reset() {
+  while (read_head_->read_pos_ != read_head_->write_pos_) {
+    assert(read_head_->write_pos_ > read_head_->read_pos_);
+
+    length_ -= read_head_->write_pos_ - read_head_->read_pos_;
+    read_head_->write_pos_ = 0;
+    read_head_->read_pos_ = 0;
+
+    read_head_ = read_head_->next_;
+  }
+  assert(length_ == 0);
+}
+
+
+NodeBIO::~NodeBIO() {
+  Buffer* current = head_.next_;
+  while (current != &head_) {
+    Buffer* next = current->next_;
+    delete current;
+    current = next;
+  }
+
+  read_head_ = NULL;
+  write_head_ = NULL;
+}
+
+} // namespace node
diff --git a/src/node_crypto_bio.h b/src/node_crypto_bio.h
new file mode 100644 (file)
index 0000000..95d173e
--- /dev/null
@@ -0,0 +1,93 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include "openssl/bio.h"
+#include <assert.h>
+
+namespace node {
+
+class NodeBIO {
+ public:
+  static inline BIO_METHOD* GetMethod() {
+    return &method_;
+  }
+
+  static int New(BIO* bio);
+  static int Free(BIO* bio);
+  static int Read(BIO* bio, char* out, int len);
+  static int Write(BIO* bio, const char* data, int len);
+  static int Puts(BIO* bio, const char* str);
+  static int Gets(BIO* bio, char* out, int size);
+  static long Ctrl(BIO* bio, int cmd, long num, void* ptr);
+
+ protected:
+  static const size_t kBufferLength = 16 * 1024;
+
+  class Buffer {
+   public:
+    Buffer() : read_pos_(0), write_pos_(0), next_(NULL) {
+    }
+
+    size_t read_pos_;
+    size_t write_pos_;
+    Buffer* next_;
+    char data_[kBufferLength];
+  };
+
+  NodeBIO() : length_(0), read_head_(&head_), write_head_(&head_) {
+    // Loop head
+    head_.next_ = &head_;
+  }
+
+  ~NodeBIO();
+
+  // Read `len` bytes maximum into `out`, return actual number of read bytes
+  size_t Read(char* out, size_t size);
+
+  // Find first appearance of `delim` in buffer or `limit` if `delim`
+  // wasn't found.
+  size_t IndexOf(char delim, size_t limit);
+
+  // Discard all available data
+  void Reset();
+
+  // Put `len` bytes from `data` into buffer
+  void Write(const char* data, size_t len);
+
+  // Return size of buffer in bytes
+  size_t inline Length() {
+    return length_;
+  }
+
+  static inline NodeBIO* FromBIO(BIO* bio) {
+    assert(bio->ptr != NULL);
+    return static_cast<NodeBIO*>(bio->ptr);
+  }
+
+  size_t length_;
+  Buffer head_;
+  Buffer* read_head_;
+  Buffer* write_head_;
+
+  static BIO_METHOD method_;
+};
+
+} // namespace node