1 // Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors 2 // Licensed under the MIT License: 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a copy 5 // of this software and associated documentation files (the "Software"), to deal 6 // in the Software without restriction, including without limitation the rights 7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 // copies of the Software, and to permit persons to whom the Software is 9 // furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 // THE SOFTWARE. 21 22 module capnproto.PackedInputStream; 23 24 import java.io.IOException; 25 import java.nio.channels.ReadableByteChannel; 26 import java.nio.ByteBuffer; 27 28 import capnproto.BufferedInputStream; 29 30 final class PackedInputStream : ReadableByteChannel 31 { 32 public: //Methods. 33 this(BufferedInputStream input) 34 { 35 this.inner = input; 36 } 37 38 size_t read(ref ByteBuffer outBuf) 39 { 40 if(outBuf.buffer is null) 41 outBuf = ByteBuffer(new ubyte[](outBuf.remaining())); 42 auto len = outBuf.remaining(); 43 if(len == 0) 44 return 0; 45 if(len % 8 != 0) 46 throw new Error("PackedInputStream reads must be word-aligned."); 47 48 auto outPtr = outBuf.position; 49 auto outEnd = outPtr + len; 50 51 auto inBuf = this.inner.getReadBuffer(); 52 53 while(true) 54 { 55 ubyte tag = 0; 56 57 if(inBuf.remaining() < 10) 58 { 59 if(outBuf.remaining() == 0) 60 return len; 61 if(inBuf.remaining() == 0) 62 { 63 inBuf = this.inner.getReadBuffer(); 64 continue; 65 } 66 67 //# We have at least 1, but not 10, bytes available. We need to read 68 //# slowly, doing a bounds check on each byte. 69 70 tag = inBuf.get!ubyte(); 71 72 foreach(i; 0..8) 73 { 74 if((tag & (1 << i)) != 0) 75 { 76 if(inBuf.remaining() == 0) 77 inBuf = this.inner.getReadBuffer(); 78 outBuf.put!ubyte(inBuf.get!ubyte()); 79 } 80 else 81 outBuf.put!ubyte(0); 82 } 83 84 if(inBuf.remaining() == 0 && (tag == 0 || tag == cast(byte)0xff)) 85 inBuf = this.inner.getReadBuffer(); 86 } 87 else 88 { 89 tag = inBuf.get!ubyte(); 90 foreach(n; 0..8) 91 { 92 bool isNonzero = (tag & (1 << n)) != 0; 93 outBuf.put!ubyte(cast(byte)(inBuf.get!ubyte() & (isNonzero? -1 : 0))); 94 inBuf.position += isNonzero? 0 : -1; 95 } 96 } 97 98 if(tag == 0) 99 { 100 if(inBuf.remaining() == 0) 101 throw new Error("Should always have non-empty buffer here."); 102 103 int runLength = inBuf.get!ubyte() * 8; 104 if(runLength > outEnd - outPtr) 105 throw new Error("Packed input did not end cleanly on a segment boundary."); 106 107 foreach(i; 0..runLength) 108 outBuf.put!ubyte(0); 109 } 110 else if(tag == 0xff) 111 { 112 int runLength = inBuf.get!ubyte() * 8; 113 114 if(inBuf.remaining() >= runLength) 115 { 116 //# Fast path. 117 auto slice = inBuf.slice(); 118 slice.limit = runLength; 119 outBuf.put!ByteBuffer(slice); 120 inBuf.position += runLength; 121 } 122 else 123 { 124 //# Copy over the first buffer, then do one big read for the rest. 125 runLength -= inBuf.remaining(); 126 outBuf.put!ByteBuffer(*inBuf); 127 128 auto slice = outBuf.slice(); 129 slice.limit = runLength; 130 131 this.inner.read(slice); 132 outBuf.position += runLength; 133 134 if(outBuf.remaining() == 0) 135 return len; 136 137 inBuf = this.inner.getReadBuffer(); 138 continue; 139 } 140 } 141 142 if(outBuf.remaining() == 0) 143 return len; 144 } 145 } 146 147 void close() 148 { 149 inner.close(); 150 } 151 152 bool isOpen() 153 { 154 return inner.isOpen(); 155 } 156 157 private: //Variables. 158 BufferedInputStream inner; 159 }