tls_wrap: use writev when possible
[platform/upstream/nodejs.git] / src / node_crypto_bio.cc
index a314bf6..84dc42b 100644 (file)
@@ -25,7 +25,7 @@
 
 namespace node {
 
-BIO_METHOD NodeBIO::method_ = {
+const BIO_METHOD NodeBIO::method = {
   BIO_TYPE_MEM,
   "node.js SSL buffer",
   NodeBIO::Write,
@@ -39,6 +39,13 @@ BIO_METHOD NodeBIO::method_ = {
 };
 
 
+BIO* NodeBIO::New() {
+  // The const_cast doesn't violate const correctness.  OpenSSL's usage of
+  // BIO_METHOD is effectively const but BIO_new() takes a non-const argument.
+  return BIO_new(const_cast<BIO_METHOD*>(&method));
+}
+
+
 int NodeBIO::New(BIO* bio) {
   bio->ptr = new NodeBIO();
 
@@ -52,7 +59,8 @@ int NodeBIO::New(BIO* bio) {
 
 
 int NodeBIO::Free(BIO* bio) {
-  if (bio == NULL) return 0;
+  if (bio == NULL)
+    return 0;
 
   if (bio->shutdown) {
     if (bio->init && bio->ptr != NULL) {
@@ -82,6 +90,39 @@ int NodeBIO::Read(BIO* bio, char* out, int len) {
 }
 
 
+char* NodeBIO::Peek(size_t* size) {
+  *size = read_head_->write_pos_ - read_head_->read_pos_;
+  return read_head_->data_ + read_head_->read_pos_;
+}
+
+
+size_t NodeBIO::PeekMultiple(char** out, size_t* size, size_t* count) {
+  Buffer* pos = read_head_;
+  size_t max = *count;
+  size_t total = 0;
+
+  size_t i;
+  for (i = 0; i < max; i++) {
+    size[i] = pos->write_pos_ - pos->read_pos_;
+    total += size[i];
+    out[i] = pos->data_ + pos->read_pos_;
+
+    /* Don't get past write head */
+    if (pos == write_head_)
+      break;
+    else
+      pos = pos->next_;
+  }
+
+  if (i == max)
+    *count = i;
+  else
+    *count = i + 1;
+
+  return total;
+}
+
+
 int NodeBIO::Write(BIO* bio, const char* data, int len) {
   BIO_clear_retry_flags(bio);
 
@@ -105,10 +146,12 @@ int NodeBIO::Gets(BIO* bio, char* out, int size) {
   int i = nbio->IndexOf('\n', size);
 
   // Include '\n'
-  if (i < size) i++;
+  if (i < size)
+    i++;
 
   // Shift `i` a bit to NULL-terminate string later
-  if (size == i) i--;
+  if (size == i)
+    i--;
 
   // Flush read data
   nbio->Read(out, i);
@@ -127,98 +170,147 @@ long NodeBIO::Ctrl(BIO* bio, int cmd, long num, void* ptr) {
   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;
+    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;
 }
 
 
+void NodeBIO::TryMoveReadHead() {
+  // `read_pos_` and `write_pos_` means the position of the reader and writer
+  // inside the buffer, respectively. When they're equal - its safe to reset
+  // them, because both reader and writer will continue doing their stuff
+  // from new (zero) positions.
+  if (read_head_->read_pos_ != read_head_->write_pos_)
+    return;
+
+  // Reset positions
+  read_head_->read_pos_ = 0;
+  read_head_->write_pos_ = 0;
+
+  // Move read_head_ forward, just in case if there're still some data to
+  // read in the next buffer.
+  if (read_head_ != write_head_)
+    read_head_ = read_head_->next_;
+}
+
+
 size_t NodeBIO::Read(char* out, size_t size) {
   size_t bytes_read = 0;
   size_t expected = Length() > size ? size : Length();
+  size_t offset = 0;
+  size_t left = size;
 
   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;
+    if (avail > left)
+      avail = left;
 
     // Copy data
     if (out != NULL)
-      memcpy(out, read_head_->data_ + read_head_->read_pos_, avail);
+      memcpy(out + offset, read_head_->data_ + read_head_->read_pos_, avail);
     read_head_->read_pos_ += avail;
 
     // Move pointers
     bytes_read += avail;
-    out += avail;
-    size -= avail;
+    offset += avail;
+    left -= 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_;
-    }
+    TryMoveReadHead();
   }
   assert(expected == bytes_read);
   length_ -= bytes_read;
 
+  // Free all empty buffers, but write_head's child
+  FreeEmpty();
+
   return bytes_read;
 }
 
 
+void NodeBIO::FreeEmpty() {
+  Buffer* child = write_head_->next_;
+  if (child == write_head_ || child == read_head_)
+    return;
+  Buffer* cur = child->next_;
+  if (cur == write_head_ || cur == read_head_)
+    return;
+
+  Buffer* prev = child;
+  while (cur != read_head_) {
+    // Skip embedded buffer, and continue deallocating again starting from it
+    if (cur == &head_) {
+      prev->next_ = cur;
+      prev = cur;
+      cur = head_.next_;
+      continue;
+    }
+    assert(cur != write_head_);
+    assert(cur->write_pos_ == cur->read_pos_);
+
+    Buffer* next = cur->next_;
+    delete cur;
+    cur = next;
+  }
+  assert(prev == child || prev == &head_);
+  prev->next_ = cur;
+}
+
+
 size_t NodeBIO::IndexOf(char delim, size_t limit) {
   size_t bytes_read = 0;
   size_t max = Length() > limit ? limit : Length();
+  size_t left = limit;
   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;
+    if (avail > left)
+      avail = left;
 
     // Walk through data
     char* tmp = current->data_ + current->read_pos_;
@@ -230,7 +322,7 @@ size_t NodeBIO::IndexOf(char delim, size_t limit) {
 
     // Move pointers
     bytes_read += off;
-    limit -= off;
+    left -= off;
 
     // Found `delim`
     if (off != avail) {
@@ -248,9 +340,11 @@ size_t NodeBIO::IndexOf(char delim, size_t limit) {
 }
 
 
-void NodeBIO::Write(const char* data, size_t len) {
-  while (len > 0) {
-    size_t to_write = len;
+void NodeBIO::Write(const char* data, size_t size) {
+  size_t offset = 0;
+  size_t left = size;
+  while (left > 0) {
+    size_t to_write = left;
     assert(write_head_->write_pos_ <= kBufferLength);
     size_t avail = kBufferLength - write_head_->write_pos_;
 
@@ -258,28 +352,65 @@ void NodeBIO::Write(const char* data, size_t len) {
       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);
+    memcpy(write_head_->data_ + write_head_->write_pos_,
+           data + offset,
+           to_write);
 
     // Move pointers
-    len -= to_write;
-    data += to_write;
+    left -= to_write;
+    offset += to_write;
     length_ += to_write;
+    write_head_->write_pos_ += to_write;
+    assert(write_head_->write_pos_ <= kBufferLength);
+
+    // Go to next buffer if there still are some bytes to write
+    if (left != 0) {
+      assert(write_head_->write_pos_ == kBufferLength);
+      TryAllocateForWrite();
+      write_head_ = write_head_->next_;
 
-    // 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;
+      // Additionally, since we're moved to the next buffer, read head
+      // may be moved as well.
+      TryMoveReadHead();
     }
+  }
+  assert(left == 0);
+}
+
+
+char* NodeBIO::PeekWritable(size_t* size) {
+  size_t available = kBufferLength - write_head_->write_pos_;
+  if (*size != 0 && available > *size)
+    available = *size;
+  else
+    *size = available;
+
+  return write_head_->data_ + write_head_->write_pos_;
+}
+
+
+void NodeBIO::Commit(size_t size) {
+  write_head_->write_pos_ += size;
+  length_ += size;
+  assert(write_head_->write_pos_ <= kBufferLength);
+
+  // Allocate new buffer if write head is full,
+  // and there're no other place to go
+  TryAllocateForWrite();
+  if (write_head_->write_pos_ == kBufferLength)
     write_head_ = write_head_->next_;
+}
+
+
+void NodeBIO::TryAllocateForWrite() {
+  // If write head is full, next buffer is either read head or not empty.
+  if (write_head_->write_pos_ == kBufferLength &&
+      (write_head_->next_ == read_head_ ||
+       write_head_->next_->write_pos_ != 0)) {
+    Buffer* next = new Buffer();
+    next->next_ = write_head_->next_;
+    write_head_->next_ = next;
   }
-  assert(len == 0);
 }
 
 
@@ -293,6 +424,7 @@ void NodeBIO::Reset() {
 
     read_head_ = read_head_->next_;
   }
+  write_head_ = read_head_;
   assert(length_ == 0);
 }
 
@@ -309,4 +441,4 @@ NodeBIO::~NodeBIO() {
   write_head_ = NULL;
 }
 
-} // namespace node
+}  // namespace node