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.Serialize;
23 
24 import std.array : Appender;
25 
26 import java.io.IOException;
27 import java.nio.channels.ReadableByteChannel;
28 import java.nio.channels.WritableByteChannel;
29 import java.nio.ByteBuffer;
30 
31 import capnproto.Constants;
32 import capnproto.DecodeException;
33 import capnproto.MessageBuilder;
34 import capnproto.MessageReader;
35 import capnproto.ReaderOptions;
36 
37 struct Serialize
38 {
39 public: //Methods.
40 	static void fillBuffer(ref ByteBuffer buffer, ReadableByteChannel bc)
41 	{
42 		while(!buffer.empty())
43 		{
44 			auto r = bc.read(buffer);
45 			if(r < 0)
46 				throw new IOException("Premature EOF.");
47 			if(r == 0)
48 				break;
49 		}
50 	}
51 	
52 	static MessageReader read(ReadableByteChannel bc)
53 	{
54 		return read(bc, cast(ReaderOptions)ReaderOptions.DEFAULT_READER_OPTIONS);
55 	}
56 	
57 	static MessageReader read(ReadableByteChannel bc, ReaderOptions options)
58 	{
59 		auto firstWord = makeByteBuffer(Constants.BYTES_PER_WORD);
60 		fillBuffer(firstWord, bc);
61 		
62 		int segmentCount = 1 + firstWord.get!int(0);
63 		
64 		int segment0Size = 0;
65 		if(segmentCount > 0)
66 			segment0Size = firstWord.get!int(4);
67 		
68 		int totalWords = segment0Size;
69 		
70 		if(segmentCount > 512)
71 			throw new IOException("Too many segments.");
72 		
73 		//In words.
74 		Appender!(int[]) moreSizes;
75 		
76 		if(segmentCount > 1)
77 		{
78 			auto moreSizesRaw = makeByteBuffer(4 * (segmentCount & ~1));
79 			fillBuffer(moreSizesRaw, bc);
80 			foreach(ii; 0..segmentCount-1)
81 			{
82 				int size = moreSizesRaw.get!int(ii * 4);
83 				moreSizes ~= size;
84 				totalWords += size;
85 			}
86 		}
87 		
88 		if(totalWords > options.traversalLimitInWords)
89 			throw new DecodeException("Message size exceeds traversal limit.");
90 		
91 		auto allSegments = makeByteBuffer(totalWords * Constants.BYTES_PER_WORD);
92 		fillBuffer(allSegments, bc);
93 		
94 		auto segmentSlices = new ByteBuffer[](segmentCount);
95 		
96 		allSegments.rewind();
97 		segmentSlices[0] = allSegments.slice();
98 		segmentSlices[0].limit = segment0Size * Constants.BYTES_PER_WORD;
99 		
100 		int offset = segment0Size;
101 		foreach(ii; 1..segmentCount)
102 		{
103 			allSegments.position = offset * Constants.BYTES_PER_WORD;
104 			segmentSlices[ii] = allSegments.slice();
105 			segmentSlices[ii].limit = moreSizes.data[ii - 1] * Constants.BYTES_PER_WORD;
106 			offset += moreSizes.data[ii - 1];
107 		}
108 		
109 		return new MessageReader(segmentSlices, options);
110 	}
111 	
112 	static MessageReader read(ref ByteBuffer bb)
113 	{
114 		return read(bb, cast(ReaderOptions)ReaderOptions.DEFAULT_READER_OPTIONS);
115 	}
116 	
117 	///Upon return, `bb.position()` will be at the end of the message.
118 	static MessageReader read(ref ByteBuffer bb, ReaderOptions options)
119 	{
120 		int segmentCount = 1 + bb.get!int();
121 		if(segmentCount > 512)
122 			throw new IOException("Too many segments.");
123 		
124 		auto segmentSlices = new ByteBuffer[](segmentCount);
125 		
126 		auto segmentSizesBase = bb.position;
127 		int segmentSizesSize = segmentCount * 4;
128 		
129 		int align_ = Constants.BYTES_PER_WORD - 1;
130 		auto segmentBase = (segmentSizesBase + segmentSizesSize + align_) & ~align_;
131 		
132 		int totalWords = 0;
133 		
134 		foreach(ii; 0..segmentCount)
135 		{
136 			int segmentSize = bb.get!int(segmentSizesBase + ii * 4);
137 			
138 			bb.position = segmentBase + totalWords * Constants.BYTES_PER_WORD;
139 			segmentSlices[ii] = bb.slice();
140 			segmentSlices[ii].limit = segmentSize * Constants.BYTES_PER_WORD;
141 			
142 			totalWords += segmentSize;
143 		}
144 		bb.position = segmentBase + totalWords * Constants.BYTES_PER_WORD;
145 		
146 		if(totalWords > options.traversalLimitInWords)
147 			throw new DecodeException("Message size exceeds traversal limit.");
148 		
149 		return new MessageReader(segmentSlices, options);
150 	}
151 	
152 	static long computeSerializedSizeInWords(MessageBuilder message)
153 	{
154 		auto segments = message.getSegmentsForOutput();
155 		
156 		//From the capnproto documentation:
157 		//"When transmitting over a stream, the following should be sent..."
158 		long bytes = 0;
159 		//"(4 bytes) The number of segments, minus one..."
160 		bytes += 4;
161 		//"(N * 4 bytes) The size of each segment, in words."
162 		bytes += segments.length * 4;
163 		//"(0 or 4 bytes) Padding up to the next word boundary."
164 		if(bytes % 8 != 0)
165 			bytes += 4;
166 		
167 		//The content of each segment, in order.
168 		foreach(i; 0..segments.length)
169 		{
170 			auto s = segments[i];
171 			bytes += s.limit;
172 		}
173 		
174 		return bytes / Constants.BYTES_PER_WORD;
175 	}
176 	
177 	static void write(WritableByteChannel outputChannel, MessageBuilder message)
178 	{
179 		auto segments = message.getSegmentsForOutput();
180 		auto tableSize = (segments.length + 2) & (~1);
181 		
182 		auto table = ByteBuffer(new ubyte[](4 * tableSize));
183 		
184 		table.put!int(0, cast(int)(segments.length-1));
185 		
186 		foreach(i; 0..segments.length)
187 			table.put!int(4 * (i + 1), cast(int)(segments[i].limit/8));
188 		
189 		//Any padding is already zeroed.
190 		while(!table.empty())
191 			outputChannel.write(table);
192 		
193 		foreach(buffer; segments)
194 		{
195 			while(!buffer.empty())
196 				outputChannel.write(buffer);
197 		}
198 	}
199 
200 private: //Methods.
201 	static ByteBuffer makeByteBuffer(int bytes)
202 	{
203 		auto result = ByteBuffer.prepare(bytes);
204 		//result.mark(); //TODO: Is this used for anything?
205 		return result;
206 	}
207 }