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 }