--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+module thrift.transport.memory;
+
+import core.exception : onOutOfMemoryError;
+import core.stdc.stdlib : free, realloc;
+import std.algorithm : min;
+import std.conv : text;
+import thrift.transport.base;
+
+/**
+ * A transport that simply reads from and writes to an in-memory buffer. Every
+ * time you call write on it, the data is simply placed into a buffer, and
+ * every time you call read, data is consumed from that buffer.
+ *
+ * Currently, the storage for written data is never reclaimed, even if the
+ * buffer contents have already been read out again.
+ */
+final class TMemoryBuffer : TBaseTransport {
+ /**
+ * Constructs a new memory transport with an empty internal buffer.
+ */
+ this() {}
+
+ /**
+ * Constructs a new memory transport with an empty internal buffer,
+ * reserving space for capacity bytes in advance.
+ *
+ * If the amount of data which will be written to the buffer is already
+ * known on construction, this can better performance over the default
+ * constructor because reallocations can be avoided.
+ *
+ * If the preallocated buffer is exhausted, data can still be written to the
+ * transport, but reallocations will happen.
+ *
+ * Params:
+ * capacity = Size of the initially reserved buffer (in bytes).
+ */
+ this(size_t capacity) {
+ reset(capacity);
+ }
+
+ /**
+ * Constructs a new memory transport initially containing the passed data.
+ *
+ * For now, the passed buffer is not intelligently used, the data is just
+ * copied to the internal buffer.
+ *
+ * Params:
+ * buffer = Initial contents available to be read.
+ */
+ this(in ubyte[] contents) {
+ auto size = contents.length;
+ reset(size);
+ buffer_[0 .. size] = contents[];
+ writeOffset_ = size;
+ }
+
+ /**
+ * Destructor, frees the internally allocated buffer.
+ */
+ ~this() {
+ free(buffer_);
+ }
+
+ /**
+ * Returns a read-only view of the current buffer contents.
+ *
+ * Note: For performance reasons, the returned slice is only valid for the
+ * life of this object, and may be invalidated on the next write() call at
+ * will – you might want to immediately .dup it if you intend to keep it
+ * around.
+ */
+ const(ubyte)[] getContents() {
+ return buffer_[readOffset_ .. writeOffset_];
+ }
+
+ /**
+ * A memory transport is always open.
+ */
+ override bool isOpen() @property {
+ return true;
+ }
+
+ override bool peek() {
+ return writeOffset_ - readOffset_ > 0;
+ }
+
+ /**
+ * Opening is a no-op() for a memory buffer.
+ */
+ override void open() {}
+
+ /**
+ * Closing is a no-op() for a memory buffer, it is always open.
+ */
+ override void close() {}
+
+ override size_t read(ubyte[] buf) {
+ auto size = min(buf.length, writeOffset_ - readOffset_);
+ buf[0 .. size] = buffer_[readOffset_ .. readOffset_ + size];
+ readOffset_ += size;
+ return size;
+ }
+
+ /**
+ * Shortcut version of readAll() – using this over TBaseTransport.readAll()
+ * can give us a nice speed increase because gives us a nice speed increase
+ * because it is typically a very hot path during deserialization.
+ */
+ override void readAll(ubyte[] buf) {
+ auto available = writeOffset_ - readOffset_;
+ if (buf.length > available) {
+ throw new TTransportException(text("Cannot readAll() ", buf.length,
+ " bytes of data because only ", available, " bytes are available."),
+ TTransportException.Type.END_OF_FILE);
+ }
+
+ buf[] = buffer_[readOffset_ .. readOffset_ + buf.length];
+ readOffset_ += buf.length;
+ }
+
+ override void write(in ubyte[] buf) {
+ auto need = buf.length;
+ if (bufferLen_ - writeOffset_ < need) {
+ // Exponential growth.
+ auto newLen = bufferLen_ + 1;
+ while (newLen - writeOffset_ < need) newLen *= 2;
+ cRealloc(buffer_, newLen);
+ bufferLen_ = newLen;
+ }
+
+ buffer_[writeOffset_ .. writeOffset_ + need] = buf[];
+ writeOffset_ += need;
+ }
+
+ override const(ubyte)[] borrow(ubyte* buf, size_t len) {
+ if (len <= writeOffset_ - readOffset_) {
+ return buffer_[readOffset_ .. writeOffset_];
+ } else {
+ return null;
+ }
+ }
+
+ override void consume(size_t len) {
+ readOffset_ += len;
+ }
+
+ void reset() {
+ readOffset_ = 0;
+ writeOffset_ = 0;
+ }
+
+ void reset(size_t capacity) {
+ readOffset_ = 0;
+ writeOffset_ = 0;
+ if (bufferLen_ < capacity) {
+ cRealloc(buffer_, capacity);
+ bufferLen_ = capacity;
+ }
+ }
+
+private:
+ ubyte* buffer_;
+ size_t bufferLen_;
+ size_t readOffset_;
+ size_t writeOffset_;
+}
+
+private {
+ void cRealloc(ref ubyte* data, size_t newSize) {
+ auto result = realloc(data, newSize);
+ if (result is null) onOutOfMemoryError();
+ data = cast(ubyte*)result;
+ }
+}
+
+version (unittest) {
+ import std.exception;
+}
+
+unittest {
+ auto a = new TMemoryBuffer(5);
+ immutable(ubyte[]) testData = [1, 2, 3, 4];
+ auto buf = new ubyte[testData.length];
+ enforce(a.isOpen);
+
+ // a should be empty.
+ enforce(!a.peek());
+ enforce(a.read(buf) == 0);
+ assertThrown!TTransportException(a.readAll(buf));
+
+ // Write some data and read it back again.
+ a.write(testData);
+ enforce(a.peek());
+ enforce(a.getContents() == testData);
+ enforce(a.read(buf) == testData.length);
+ enforce(buf == testData);
+
+ // a should be empty again.
+ enforce(!a.peek());
+ enforce(a.read(buf) == 0);
+ assertThrown!TTransportException(a.readAll(buf));
+
+ // Test the constructor which directly accepts initial data.
+ auto b = new TMemoryBuffer(testData);
+ enforce(b.isOpen);
+ enforce(b.peek());
+ enforce(b.getContents() == testData);
+
+ // Test borrow().
+ auto borrowed = b.borrow(null, testData.length);
+ enforce(borrowed == testData);
+ enforce(b.peek());
+ b.consume(testData.length);
+ enforce(!b.peek());
+}