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.WireHelpers;
23 
24 import std.algorithm : max;
25 
26 import java.nio.ByteBuffer;
27 
28 import capnproto.BuilderArena;
29 import capnproto.Constants;
30 import capnproto.Data;
31 import capnproto.DecodeException;
32 import capnproto.FarPointer;
33 import capnproto.ElementSize;
34 import capnproto.ListBuilder;
35 import capnproto.ListPointer;
36 import capnproto.ListReader;
37 import capnproto.SegmentBuilder;
38 import capnproto.SegmentReader;
39 import capnproto.StructBuilder;
40 import capnproto.StructPointer;
41 import capnproto.StructReader;
42 import capnproto.StructSize;
43 import capnproto.Text;
44 import capnproto.WirePointer;
45 
46 struct WireHelpers
47 {
48 package:
49 	static struct AllocateResult
50 	{
51 		int ptr;
52 		int refOffset;
53 		SegmentBuilder* segment;
54 		
55 		this(int ptr, int refOffset, SegmentBuilder* segment)
56 		{
57 			this.ptr = ptr;
58 			this.refOffset = refOffset;
59 			this.segment = segment;
60 		}
61 	}
62 
63 	static struct FollowBuilderFarsResult
64 	{
65 		int ptr;
66 		long ref_;
67 		SegmentBuilder* segment;
68 		
69 		this(int ptr, long ref_, SegmentBuilder* segment)
70 		{
71 			this.ptr = ptr;
72 			this.ref_ = ref_;
73 			this.segment = segment;
74 		}
75 	}
76 	
77 	static struct FollowFarsResult
78 	{
79 		int ptr;
80 		long ref_;
81 		SegmentReader* segment;
82 		
83 		this(int ptr, long ref_, SegmentReader* segment)
84 		{
85 			this.ptr = ptr;
86 			this.ref_ = ref_;
87 			this.segment = segment;
88 		}
89 	}
90 	
91 	static int roundBytesUpToWords(int bytes)
92 	{
93 		return (bytes + 7) / 8;
94 	}
95 	
96 	static int roundBitsUpToBytes(int bits)
97 	{
98 		return (bits + 7) / Constants.BITS_PER_BYTE;
99 	}
100 	
101 	static int roundBitsUpToWords(long bits)
102 	{
103 		//# This code assumes 64-bit words.
104 		return cast(int)((bits + 63) / (cast(long)Constants.BITS_PER_WORD));
105 	}
106 	
107 	static AllocateResult allocate(int refOffset, SegmentBuilder* segment, int amount/*in words*/, byte kind)
108 	{
109 		long ref_ = segment.get(refOffset);
110 		if(!WirePointer.isNull(ref_))
111 			zeroObject(segment, refOffset);
112 		
113 		if(amount == 0 && kind == WirePointer.STRUCT)
114 		{
115 			WirePointer.setKindAndTargetForEmptyStruct(segment.buffer, refOffset);
116 			return AllocateResult(refOffset, refOffset, segment);
117 		}
118 		
119 		auto ptr = cast(int)segment.allocate(amount);
120 		if(ptr == SegmentBuilder.FAILED_ALLOCATION)
121 		{
122 			//# Need to allocate in a new segment. We'll need to
123 			//# allocate an extra pointer worth of space to act as
124 			//# the landing pad for a far pointer.
125 			
126 			int amountPlusRef = amount + Constants.POINTER_SIZE_IN_WORDS;
127 			BuilderArena.AllocateResult allocation = segment.getArena().allocate(amountPlusRef);
128 			
129 			//# Set up the original pointer to be a far pointer to
130 			//# the new segment.
131 			FarPointer.set(segment.buffer, refOffset, false, allocation.offset);
132 			FarPointer.setSegmentId(segment.buffer, refOffset, allocation.segment.id);
133 			
134 			//# Initialize the landing pad to indicate that the
135 			//# data immediately follows the pad.
136 			int resultRefOffset = allocation.offset;
137 			int ptr1 = allocation.offset + Constants.POINTER_SIZE_IN_WORDS;
138 			
139 			WirePointer.setKindAndTarget(allocation.segment.buffer, resultRefOffset, kind, ptr1);
140 			
141 			return AllocateResult(ptr1, resultRefOffset, allocation.segment);
142 		}
143 		WirePointer.setKindAndTarget(segment.buffer, refOffset, kind, ptr);
144 		return AllocateResult(ptr, refOffset, segment);
145 	}
146 	
147 	static FollowBuilderFarsResult followBuilderFars(long ref_, int refTarget, SegmentBuilder* segment)
148 	{
149 		//# If `ref` is a far pointer, follow it. On return, `ref` will
150 		//# have been updated to point at a WirePointer that contains
151 		//# the type information about the target object, and a pointer
152 		//# to the object contents is returned. The caller must NOT use
153 		//# `ref->target()` as this may or may not actually return a
154 		//# valid pointer. `segment` is also updated to point at the
155 		//# segment which actually contains the object.
156 		//#
157 		//# If `ref` is not a far pointer, this simply returns
158 		//# `refTarget`. Usually, `refTarget` should be the same as
159 		//# `ref->target()`, but may not be in cases where `ref` is
160 		//# only a tag.
161 		
162 		if(WirePointer.kind(ref_) == WirePointer.FAR)
163 		{
164 			auto resultSegment = segment.getArena().getSegment(FarPointer.getSegmentId(ref_));
165 			
166 			int padOffset = FarPointer.positionInSegment(ref_);
167 			long pad = resultSegment.get(padOffset);
168 			if(!FarPointer.isDoubleFar(ref_))
169 				return FollowBuilderFarsResult(WirePointer.target(padOffset, pad), pad, resultSegment);
170 			
171 			//# Landing pad is another far pointer. It is followed by a
172 			//# tag describing the pointed-to object.
173 			int refOffset = padOffset + 1;
174 			ref_ = resultSegment.get(refOffset);
175 			
176 			resultSegment = resultSegment.getArena().getSegment(FarPointer.getSegmentId(pad));
177 			return FollowBuilderFarsResult(FarPointer.positionInSegment(pad), ref_, resultSegment);
178 		}
179 		return FollowBuilderFarsResult(refTarget, ref_, segment);
180 	}
181 	
182 	static FollowFarsResult followFars(long ref_, int refTarget, SegmentReader* segment)
183 	{
184 		//# If the segment is null, this is an unchecked message,
185 		//# so there are no FAR pointers.
186 		if(segment !is null && WirePointer.kind(ref_) == WirePointer.FAR)
187 		{
188 			auto resultSegment = segment.arena.tryGetSegment(FarPointer.getSegmentId(ref_));
189 			int padOffset = FarPointer.positionInSegment(ref_);
190 			long pad = resultSegment.get(padOffset);
191 			
192 			int padWords = FarPointer.isDoubleFar(ref_)? 2 : 1;
193 			//TODO: Read limiting.
194 			
195 			if(!FarPointer.isDoubleFar(ref_))
196 				return FollowFarsResult(WirePointer.target(padOffset, pad), pad, resultSegment);
197 			//# Landing pad is another far pointer. It is
198 			//# followed by a tag describing the pointed-to
199 			//# object.
200 			
201 			long tag = resultSegment.get(padOffset + 1);
202 			resultSegment = resultSegment.arena.tryGetSegment(FarPointer.getSegmentId(pad));
203 			return FollowFarsResult(FarPointer.positionInSegment(pad), tag, resultSegment);
204 		}
205 		return FollowFarsResult(refTarget, ref_, segment);
206 	}
207 	
208 	static void zeroObject(SegmentBuilder* segment, int refOffset)
209 	{
210 		//# Zero out the pointed-to object. Use when the pointer is
211 		//# about to be overwritten making the target object no longer
212 		//# reachable.
213 		
214 		//# We shouldn't zero out external data linked into the message.
215 		if(!segment.isWritable())
216 			return;
217 		
218 		long ref_ = segment.get(refOffset);
219 		
220 		final switch(WirePointer.kind(ref_))
221 		{
222 			case WirePointer.STRUCT:
223 			case WirePointer.LIST:
224 				zeroObject(segment, ref_, WirePointer.target(refOffset, ref_));
225 				break;
226 			case WirePointer.FAR:
227 			{
228 				segment = segment.getArena().getSegment(FarPointer.getSegmentId(ref_));
229 				if(segment.isWritable()) //# Don't zero external data.
230 				{
231 					int padOffset = FarPointer.positionInSegment(ref_);
232 					long pad = segment.get(padOffset);
233 					if(FarPointer.isDoubleFar(ref_))
234 					{
235 						auto otherSegment = segment.getArena().getSegment(FarPointer.getSegmentId(ref_));
236 						if(otherSegment.isWritable())
237 							zeroObject(otherSegment, padOffset + 1, FarPointer.positionInSegment(pad));
238 						segment.buffer.put!long(padOffset * 8, 0L);
239 						segment.buffer.put!long((padOffset + 1) * 8, 0L);
240 					}
241 					else
242 					{
243 						zeroObject(segment, padOffset);
244 						segment.buffer.put!long(padOffset * 8, 0L);
245 					}
246 				}
247 				break;
248 			}
249 			case WirePointer.OTHER:
250 			{
251 				//TODO.
252 			}
253 		}
254 	}
255 	
256 	static void zeroObject(SegmentBuilder* segment, long tag, int ptr)
257 	{
258 		//# We shouldn't zero out external data linked into the message.
259 		if(!segment.isWritable())
260 			return;
261 		
262 		final switch(WirePointer.kind(tag))
263 		{
264 			case WirePointer.STRUCT:
265 			{
266 				int pointerSection = ptr + StructPointer.dataSize(tag);
267 				int count = StructPointer.ptrCount(tag);
268 				foreach(ii; 0..count)
269 					zeroObject(segment, pointerSection + ii);
270 				memset(segment.buffer, ptr * Constants.BYTES_PER_WORD, cast(byte)0, StructPointer.wordSize(tag) * Constants.BYTES_PER_WORD);
271 				break;
272 			}
273 			case WirePointer.LIST:
274 			{
275 				final switch(ListPointer.elementSize(tag))
276 				{
277 					case ElementSize.VOID:
278 						break;
279 					case ElementSize.BIT:
280 					case ElementSize.BYTE:
281 					case ElementSize.TWO_BYTES:
282 					case ElementSize.FOUR_BYTES:
283 					case ElementSize.EIGHT_BYTES:
284 					{
285 						memset(segment.buffer, ptr * Constants.BYTES_PER_WORD, cast(byte)0,
286 							roundBitsUpToWords(ListPointer.elementCount(tag) * ElementSize.dataBitsPerElement(ListPointer.elementSize(tag))) * Constants.BYTES_PER_WORD);
287 						break;
288 					}
289 					case ElementSize.POINTER:
290 					{
291 						int count = ListPointer.elementCount(tag);
292 						foreach(ii; 0..count)
293 							zeroObject(segment, ptr + ii);
294 						memset(segment.buffer, ptr * Constants.BYTES_PER_WORD, cast(byte)0, count * Constants.BYTES_PER_WORD);
295 						break;
296 					}
297 					case ElementSize.INLINE_COMPOSITE:
298 					{
299 						long elementTag = segment.get(ptr);
300 						if(WirePointer.kind(elementTag) != WirePointer.STRUCT)
301 							throw new Error("Don't know how to handle non-STRUCT inline composite.");
302 						int dataSize = StructPointer.dataSize(elementTag);
303 						int pointerCount = StructPointer.ptrCount(elementTag);
304 						
305 						int pos = ptr + Constants.POINTER_SIZE_IN_WORDS;
306 						int count = WirePointer.inlineCompositeListElementCount(elementTag);
307 						foreach(ii; 0..count)
308 						{
309 							pos += dataSize;
310 							foreach(jj; 0..pointerCount)
311 							{
312 								zeroObject(segment, pos);
313 								pos += Constants.POINTER_SIZE_IN_WORDS;
314 							}
315 						}
316 						
317 						memset(segment.buffer, ptr * Constants.BYTES_PER_WORD, cast(byte)0, (StructPointer.wordSize(elementTag) * count + Constants.POINTER_SIZE_IN_WORDS) * Constants.BYTES_PER_WORD);
318 						break;
319 					}
320 				}
321 				break;
322 			}
323 			case WirePointer.FAR:
324 				throw new Error("Unexpected FAR pointer.");
325 			case WirePointer.OTHER:
326 				throw new Error("Unexpected OTHER pointer.");
327 		}
328 	}
329 	
330 	static void zeroPointerAndFars(SegmentBuilder* segment, int refOffset)
331 	{
332 		//# Zero out the pointer itself and, if it is a far pointer, zero the landing pad as well,
333 		//# but do not zero the object body. Used when upgrading.
334 		
335 		long ref_ = segment.get(refOffset);
336 		if(WirePointer.kind(ref_) == WirePointer.FAR)
337 		{
338 			auto padSegment = segment.getArena().getSegment(FarPointer.getSegmentId(ref_));
339 			if(padSegment.isWritable()) //# Don't zero external data.
340 			{
341 				int padOffset = FarPointer.positionInSegment(ref_);
342 				padSegment.buffer.put!long(padOffset * Constants.BYTES_PER_WORD, 0L);
343 				if(FarPointer.isDoubleFar(ref_))
344 					padSegment.buffer.put!long(padOffset * Constants.BYTES_PER_WORD + 1, 0L);
345 			}
346 		}
347 		segment.put(refOffset, 0L);
348 	}
349 	
350 	static void transferPointer(SegmentBuilder* dstSegment, int dstOffset, SegmentBuilder* srcSegment, int srcOffset)
351 	{
352 		//# Make *dst point to the same object as *src. Both must reside in the same message, but can
353 		//# be in different segments.
354 		//#
355 		//# Caller MUST zero out the source pointer after calling this, to make sure no later code
356 		//# mistakenly thinks the source location still owns the object.  transferPointer() doesn't do
357 		//# this zeroing itself because many callers transfer several pointers in a loop then zero out
358 		//# the whole section.
359 		
360 		long src = srcSegment.get(srcOffset);
361 		if(WirePointer.isNull(src))
362 		{
363 			dstSegment.put(dstOffset, 0L);
364 		}
365 		else if(WirePointer.kind(src) == WirePointer.FAR)
366 		{
367 			//# Far pointers are position-independent, so we can just copy.
368 			dstSegment.put(dstOffset, srcSegment.get(srcOffset));
369 		}
370 		else
371 			transferPointer(dstSegment, dstOffset, srcSegment, srcOffset, WirePointer.target(srcOffset, src));
372 	}
373 	
374 	static void transferPointer(SegmentBuilder* dstSegment, int dstOffset, SegmentBuilder* srcSegment, int srcOffset, int srcTargetOffset)
375 	{
376 		//# Like the other overload, but splits src into a tag and a target. Particularly useful for
377 		//# OrphanBuilder.
378 		
379 		long src = srcSegment.get(srcOffset);
380 		long srcTarget = srcSegment.get(srcTargetOffset);
381 		
382 		if(dstSegment == srcSegment)
383 		{
384 			//# Same segment, so create a direct pointer.
385 			if(WirePointer.kind(src) == WirePointer.STRUCT && StructPointer.wordSize(src) == 0)
386 				WirePointer.setKindAndTargetForEmptyStruct(dstSegment.buffer, dstOffset);
387 			else
388 				WirePointer.setKindAndTarget(dstSegment.buffer, dstOffset, WirePointer.kind(src), srcTargetOffset);
389 			
390 			//We can just copy the upper 32 bits.
391 			dstSegment.buffer.put!int(dstOffset * Constants.BYTES_PER_WORD + 4, srcSegment.buffer.get!int(srcOffset * Constants.BYTES_PER_WORD + 4));
392 		}
393 		else
394 		{
395 			//# Need to create a far pointer. Try to allocate it in the same segment as the source,
396 			//# so that it doesn't need to be a double-far.
397 			
398 			int landingPadOffset = cast(int)srcSegment.allocate(1);
399 			if(landingPadOffset == SegmentBuilder.FAILED_ALLOCATION)
400 			{
401 				//# Darn, need a double-far.
402 				
403 				BuilderArena.AllocateResult allocation = srcSegment.getArena().allocate(2);
404 				auto farSegment = allocation.segment;
405 				landingPadOffset = allocation.offset;
406 				
407 				FarPointer.set(farSegment.buffer, landingPadOffset, false, srcTargetOffset);
408 				FarPointer.setSegmentId(farSegment.buffer, landingPadOffset, srcSegment.id);
409 				
410 				WirePointer.setKindWithZeroOffset(farSegment.buffer, landingPadOffset + 1, WirePointer.kind(src));
411 				
412 				farSegment.buffer.put!int((landingPadOffset + 1) * Constants.BYTES_PER_WORD + 4, srcSegment.buffer.get!int(srcOffset * Constants.BYTES_PER_WORD + 4));
413 				
414 				FarPointer.set(dstSegment.buffer, dstOffset, true, landingPadOffset);
415 				FarPointer.setSegmentId(dstSegment.buffer, dstOffset, farSegment.id);
416 			}
417 			else
418 			{
419 				//# Simple landing pad is just a pointer.
420 				
421 				WirePointer.setKindAndTarget(srcSegment.buffer, landingPadOffset, WirePointer.kind(srcTarget), srcTargetOffset);
422 				srcSegment.buffer.put!int(landingPadOffset * Constants.BYTES_PER_WORD + 4, srcSegment.buffer.get!int(srcOffset * Constants.BYTES_PER_WORD + 4));
423 				
424 				FarPointer.set(dstSegment.buffer, dstOffset, false, landingPadOffset);
425 				FarPointer.setSegmentId(dstSegment.buffer, dstOffset, srcSegment.id);
426 			}
427 		}
428 	}
429 	
430 	static T initStructPointer(T)(int refOffset, SegmentBuilder* segment, immutable(StructSize) size)
431 	{
432 		AllocateResult allocation = allocate(refOffset, segment, size.total(), WirePointer.STRUCT);
433 		StructPointer.setFromStructSize(allocation.segment.buffer, allocation.refOffset, size);
434 		return T(allocation.segment, allocation.ptr * Constants.BYTES_PER_WORD, allocation.ptr + size.data, size.data * 64, size.pointers);
435 	}
436 	
437 	static T getWritableStructPointer(T)(int refOffset, SegmentBuilder* segment, immutable(StructSize) size, SegmentReader* defaultSegment, int defaultOffset)
438 	{
439 		long ref_ = segment.get(refOffset);
440 		int target = WirePointer.target(refOffset, ref_);
441 		if(WirePointer.isNull(ref_))
442 		{
443 			if(defaultSegment is null)
444 				return initStructPointer!T(refOffset, segment, size);
445 			throw new Error("Unimplemented.");
446 		}
447 		FollowBuilderFarsResult resolved = followBuilderFars(ref_, target, segment);
448 		
449 		short oldDataSize = StructPointer.dataSize(resolved.ref_);
450 		short oldPointerCount = StructPointer.ptrCount(resolved.ref_);
451 		int oldPointerSection = resolved.ptr + oldDataSize;
452 		
453 		if(oldDataSize < size.data || oldPointerCount < size.pointers)
454 		{
455 			//# The space allocated for this struct is too small. Unlike with readers, we can't just
456 			//# run with it and do bounds checks at access time, because how would we handle writes?
457 			//# Instead, we have to copy the struct to a new space now.
458 			
459 			short newDataSize = cast(short)max(oldDataSize, size.data);
460 			short newPointerCount = cast(short)max(oldPointerCount, size.pointers);
461 			int totalSize = newDataSize + newPointerCount * Constants.WORDS_PER_POINTER;
462 			
463 			//# Don't let allocate() zero out the object just yet.
464 			zeroPointerAndFars(segment, refOffset);
465 			
466 			AllocateResult allocation = allocate(refOffset, segment, totalSize, WirePointer.STRUCT);
467 			
468 			StructPointer.set(allocation.segment.buffer, allocation.refOffset, newDataSize, newPointerCount);
469 			
470 			//# Copy data section.
471 			memcpy(allocation.segment.buffer, allocation.ptr * Constants.BYTES_PER_WORD, resolved.segment.buffer, resolved.ptr * Constants.BYTES_PER_WORD, oldDataSize * Constants.BYTES_PER_WORD);
472 			
473 			//# Copy pointer section.
474 			int newPointerSection = allocation.ptr + newDataSize;
475 			foreach(ii; 0..oldPointerCount)
476 				transferPointer(allocation.segment, newPointerSection + ii, resolved.segment, oldPointerSection + ii);
477 			
478 			//# Zero out old location.  This has two purposes:
479 			//# 1) We don't want to leak the original contents of the struct when the message is written
480 			//#    out as it may contain secrets that the caller intends to remove from the new copy.
481 			//# 2) Zeros will be deflated by packing, making this dead memory almost-free if it ever
482 			//#    hits the wire.
483 			memset(resolved.segment.buffer, resolved.ptr * Constants.BYTES_PER_WORD, cast(byte)0, (oldDataSize + oldPointerCount * Constants.WORDS_PER_POINTER) * Constants.BYTES_PER_WORD);
484 			
485 			return T(allocation.segment, allocation.ptr * Constants.BYTES_PER_WORD, newPointerSection, newDataSize * Constants.BITS_PER_WORD, newPointerCount);
486 		}
487 		return T(resolved.segment, resolved.ptr * Constants.BYTES_PER_WORD, oldPointerSection, oldDataSize * Constants.BITS_PER_WORD, oldPointerCount);
488 	}
489 	
490 	static T initListPointer(T)(int refOffset, SegmentBuilder* segment, int elementCount, ubyte elementSize)
491 	{
492 		assert(elementSize != ElementSize.INLINE_COMPOSITE, "Should have called initStructListPointer instead.");
493 		
494 		int dataSize = ElementSize.dataBitsPerElement(elementSize);
495 		int pointerCount = ElementSize.pointersPerElement(elementSize);
496 		int step = dataSize + pointerCount * Constants.BITS_PER_POINTER;
497 		int wordCount = roundBitsUpToWords(cast(long)elementCount * cast(long)step);
498 		AllocateResult allocation = allocate(refOffset, segment, wordCount, WirePointer.LIST);
499 		
500 		ListPointer.set(allocation.segment.buffer, allocation.refOffset, elementSize, elementCount);
501 		
502 		return T(allocation.segment, allocation.ptr * Constants.BYTES_PER_WORD, elementCount, step, dataSize, cast(short)pointerCount);
503 	}
504 	
505 	static T initStructListPointer(T)(int refOffset, SegmentBuilder* segment, int elementCount, immutable(StructSize) elementSize)
506 	{
507 		int wordsPerElement = elementSize.total();
508 		
509 		//# Allocate the list, prefixed by a single WirePointer.
510 		int wordCount = elementCount * wordsPerElement;
511 		AllocateResult allocation = allocate(refOffset, segment, Constants.POINTER_SIZE_IN_WORDS + wordCount, WirePointer.LIST);
512 		
513 		//# Initialize the pointer.
514 		ListPointer.setInlineComposite(allocation.segment.buffer, allocation.refOffset, wordCount);
515 		WirePointer.setKindAndInlineCompositeListElementCount(allocation.segment.buffer, allocation.ptr, WirePointer.STRUCT, elementCount);
516 		StructPointer.setFromStructSize(allocation.segment.buffer, allocation.ptr, elementSize);
517 		return T(allocation.segment, (allocation.ptr + 1) * Constants.BYTES_PER_WORD, elementCount, wordsPerElement * Constants.BITS_PER_WORD, elementSize.data * Constants.BITS_PER_WORD, elementSize.pointers);
518 	}
519 	
520 	static T getWritableListPointer(T)(int origRefOffset, SegmentBuilder* origSegment, byte elementSize, SegmentReader* defaultSegment, int defaultOffset)
521 	{
522 		assert(elementSize != ElementSize.INLINE_COMPOSITE, "Use getWritableStructListPointer() for struct lists.");
523 		
524 		long origRef = origSegment.get(origRefOffset);
525 		int origRefTarget = WirePointer.target(origRefOffset, origRef);
526 		
527 		if(WirePointer.isNull(origRef))
528 			throw new Error("Unimplemented.");
529 		
530 		//# We must verify that the pointer has the right size. Unlike
531 		//# in getWritableStructListPointer(), we never need to
532 		//# "upgrade" the data, because this method is called only for
533 		//# non-struct lists, and there is no allowed upgrade path *to*
534 		//# a non-struct list, only *from* them.
535 		
536 		FollowBuilderFarsResult resolved = followBuilderFars(origRef, origRefTarget, origSegment);
537 		
538 		if(WirePointer.kind(resolved.ref_) != WirePointer.LIST)
539 			throw new DecodeException("Called getList{Field,Element}() but existing pointer is not a list.");
540 		
541 		byte oldSize = ListPointer.elementSize(resolved.ref_);
542 		
543 		if(oldSize == ElementSize.INLINE_COMPOSITE)
544 		{
545 			//# The existing element size is InlineComposite, which
546 			//# means that it is at least two words, which makes it
547 			//# bigger than the expected element size. Since fields can
548 			//# only grow when upgraded, the existing data must have
549 			//# been written with a newer version of the protocol. We
550 			//# therefore never need to upgrade the data in this case,
551 			//# but we do need to validate that it is a valid upgrade
552 			//# from what we expected.
553 			throw new Error("Unimplemented.");
554 		}
555 		else
556 		{
557 			int dataSize = ElementSize.dataBitsPerElement(oldSize);
558 			int pointerCount = ElementSize.pointersPerElement(oldSize);
559 			
560 			if(dataSize < ElementSize.dataBitsPerElement(elementSize))
561 				throw new DecodeException("Existing list value is incompatible with expected type.");
562 			if(pointerCount < ElementSize.pointersPerElement(elementSize))
563 				throw new DecodeException("Existing list value is incompatible with expected type.");
564 			
565 			int step = dataSize + pointerCount * Constants.BITS_PER_POINTER;
566 			
567 			return T(resolved.segment, resolved.ptr * Constants.BYTES_PER_WORD, ListPointer.elementCount(resolved.ref_), step, dataSize, cast(short)pointerCount);
568 		}
569 	}
570 	
571 	static T getWritableStructListPointer(T)(int origRefOffset, SegmentBuilder* origSegment, immutable(StructSize) elementSize, SegmentReader* defaultSegment, int defaultOffset)
572 	{
573 		long origRef = origSegment.get(origRefOffset);
574 		int origRefTarget = WirePointer.target(origRefOffset, origRef);
575 		
576 		if(WirePointer.isNull(origRef))
577 			throw new Error("Unimplemented.");
578 		
579 		//# We must verify that the pointer has the right size and potentially upgrade it if not.
580 		
581 		FollowBuilderFarsResult resolved = followBuilderFars(origRef, origRefTarget, origSegment);
582 		if(WirePointer.kind(resolved.ref_) != WirePointer.LIST)
583 			throw new DecodeException("Called getList{Field,Element}() but existing pointer is not a list.");
584 		
585 		byte oldSize = ListPointer.elementSize(resolved.ref_);
586 		
587 		if(oldSize == ElementSize.INLINE_COMPOSITE)
588 		{
589 			//# Existing list is INLINE_COMPOSITE, but we need to verify that the sizes match.
590 			long oldTag = resolved.segment.get(resolved.ptr);
591 			int oldPtr = resolved.ptr + Constants.POINTER_SIZE_IN_WORDS;
592 			if(WirePointer.kind(oldTag) != WirePointer.STRUCT)
593 				throw new DecodeException("INLINE_COMPOSITE list with non-STRUCT elements not supported.");
594 			int oldDataSize = StructPointer.dataSize(oldTag);
595 			short oldPointerCount = StructPointer.ptrCount(oldTag);
596 			int oldStep = (oldDataSize + oldPointerCount * Constants.POINTER_SIZE_IN_WORDS);
597 			int elementCount = WirePointer.inlineCompositeListElementCount(oldTag);
598 			
599 			if(oldDataSize >= elementSize.data && oldPointerCount >= elementSize.pointers)
600 			{
601 				//# Old size is at least as large as we need. Ship it.
602 				return T(resolved.segment, oldPtr * Constants.BYTES_PER_WORD, elementCount, oldStep * Constants.BITS_PER_WORD, oldDataSize * Constants.BITS_PER_WORD, oldPointerCount);
603 			}
604 			
605 			//# The structs in this list are smaller than expected, probably written using an older
606 			//# version of the protocol. We need to make a copy and expand them.
607 			
608 			short newDataSize = cast(short)max(oldDataSize, elementSize.data);
609 			short newPointerCount = cast(short)max(oldPointerCount, elementSize.pointers);
610 			int newStep = newDataSize + newPointerCount * Constants.WORDS_PER_POINTER;
611 			int totalSize = newStep * elementCount;
612 			
613 			//# Don't let allocate() zero out the object just yet.
614 			zeroPointerAndFars(origSegment, origRefOffset);
615 			
616 			auto allocation = allocate(origRefOffset, origSegment, totalSize + Constants.POINTER_SIZE_IN_WORDS, WirePointer.LIST);
617 			
618 			ListPointer.setInlineComposite(allocation.segment.buffer, allocation.refOffset, totalSize);
619 			
620 			long tag = allocation.segment.get(allocation.ptr);
621 			WirePointer.setKindAndInlineCompositeListElementCount(allocation.segment.buffer, allocation.ptr, WirePointer.STRUCT, elementCount);
622 			StructPointer.set(allocation.segment.buffer, allocation.ptr, newDataSize, newPointerCount);
623 			int newPtr = allocation.ptr + Constants.POINTER_SIZE_IN_WORDS;
624 			
625 			int src = oldPtr;
626 			int dst = newPtr;
627 			foreach(ii; 0..elementCount)
628 			{
629 				//# Copy data section.
630 				memcpy(allocation.segment.buffer, dst * Constants.BYTES_PER_WORD, resolved.segment.buffer, src * Constants.BYTES_PER_WORD, oldDataSize * Constants.BYTES_PER_WORD);
631 				
632 				//# Copy pointer section.
633 				int newPointerSection = dst + newDataSize;
634 				int oldPointerSection = src + oldDataSize;
635 				foreach(jj; 0..oldPointerCount)
636 					transferPointer(allocation.segment, newPointerSection + jj, resolved.segment, oldPointerSection + jj);
637 				
638 				dst += newStep;
639 				src += oldStep;
640 			}
641 			
642 			//# Zero out old location. See explanation in getWritableStructPointer().
643 			//# Make sure to include the tag word.
644 			memset(resolved.segment.buffer, resolved.ptr * Constants.BYTES_PER_WORD, cast(byte)0, (1 + oldStep * elementCount) * Constants.BYTES_PER_WORD);
645 			
646 			return T(allocation.segment, newPtr * Constants.BYTES_PER_WORD, elementCount, newStep * Constants.BITS_PER_WORD, newDataSize * Constants.BITS_PER_WORD, newPointerCount);
647 		}
648 		//# We're upgrading from a non-struct list.
649 		
650 		int oldDataSize = ElementSize.dataBitsPerElement(oldSize);
651 		int oldPointerCount = ElementSize.pointersPerElement(oldSize);
652 		int oldStep = oldDataSize + oldPointerCount * Constants.BITS_PER_POINTER;
653 		int elementCount = ListPointer.elementCount(origRef);
654 		
655 		if(oldSize == ElementSize.VOID)
656 		{
657 			//# Nothing to copy, just allocate a new list.
658 			return initStructListPointer!T(origRefOffset, origSegment, elementCount, elementSize);
659 		}
660 		//# Upgrading to an inline composite list.
661 		
662 		if(oldSize == ElementSize.BIT)
663 			throw new Error("Found bit list where struct list was expected; upgrading boolean lists to struct is no longer supported.");
664 		
665 		short newDataSize = elementSize.data;
666 		short newPointerCount = elementSize.pointers;
667 		
668 		if(oldSize == ElementSize.POINTER)
669 			newPointerCount = cast(short)max(newPointerCount, 1);
670 		else
671 		{
672 			//# Old list contains data elements, so we need at least 1 word of data.
673 			newDataSize = cast(short)max(newDataSize, 1);
674 		}
675 		
676 		int newStep = (newDataSize + newPointerCount * Constants.WORDS_PER_POINTER);
677 		int totalWords = elementCount * newStep;
678 		
679 		//# Don't let allocate() zero out the object just yet.
680 		zeroPointerAndFars(origSegment, origRefOffset);
681 		
682 		auto allocation = allocate(origRefOffset, origSegment, totalWords + Constants.POINTER_SIZE_IN_WORDS, WirePointer.LIST);
683 		
684 		ListPointer.setInlineComposite(allocation.segment.buffer, allocation.refOffset, totalWords);
685 		
686 		long tag = allocation.segment.get(allocation.ptr);
687 		WirePointer.setKindAndInlineCompositeListElementCount(allocation.segment.buffer, allocation.ptr, WirePointer.STRUCT, elementCount);
688 		StructPointer.set(allocation.segment.buffer, allocation.ptr, newDataSize, newPointerCount);
689 		int newPtr = allocation.ptr + Constants.POINTER_SIZE_IN_WORDS;
690 		
691 		if(oldSize == ElementSize.POINTER)
692 		{
693 			int dst = newPtr + newDataSize;
694 			int src = resolved.ptr;
695 			foreach(ii; 0..elementCount)
696 			{
697 				transferPointer(origSegment, dst, resolved.segment, src);
698 				dst += newStep / Constants.WORDS_PER_POINTER;
699 				src += 1;
700 			}
701 		}
702 		else
703 		{
704 			int dst = newPtr;
705 			int srcByteOffset = resolved.ptr * Constants.BYTES_PER_WORD;
706 			int oldByteStep = oldDataSize / Constants.BITS_PER_BYTE;
707 			foreach(ii; 0..elementCount)
708 			{
709 				memcpy(allocation.segment.buffer, dst * Constants.BYTES_PER_WORD, resolved.segment.buffer, srcByteOffset, oldByteStep);
710 				srcByteOffset += oldByteStep;
711 				dst += newStep;
712 			}
713 		}
714 		
715 		//# Zero out old location. See explanation in getWritableStructPointer().
716 		memset(resolved.segment.buffer, resolved.ptr * Constants.BYTES_PER_WORD, cast(byte)0, roundBitsUpToBytes(oldStep * elementCount));
717 		
718 		return T(allocation.segment, newPtr * Constants.BYTES_PER_WORD, elementCount, newStep * Constants.BITS_PER_WORD, newDataSize * Constants.BITS_PER_WORD, newPointerCount);
719 	}
720 
721 	///Size is in bytes.
722 	static Text.Builder initTextPointer(int refOffset, SegmentBuilder* segment, int size)
723 	{
724 		//# The byte list must include a NUL terminator.
725 		int byteSize = size + 1;
726 		
727 		//# Allocate the space.
728 		auto allocation = allocate(refOffset, segment, roundBytesUpToWords(byteSize), WirePointer.LIST);
729 		
730 		//# Initialize the pointer.
731 		ListPointer.set(allocation.segment.buffer, allocation.refOffset, ElementSize.BYTE, byteSize);
732 		
733 		return Text.Builder(allocation.segment.buffer, allocation.ptr * Constants.BYTES_PER_WORD, size);
734 	}
735 	
736 	static Text.Builder setTextPointer(int refOffset, SegmentBuilder* segment, Text.Reader value)
737 	{
738 		Text.Builder builder = initTextPointer(refOffset, segment, cast(int)value.size);
739 		
740 		auto slice = value.buffer;
741 		slice.position = value.offset;
742 		slice.limit = value.offset + value.size;
743 		builder.buffer.position = builder.offset;
744 		builder.buffer.put!ByteBuffer(slice);
745 		return builder;
746 	}
747 
748 	static Text.Builder getWritableTextPointer(int refOffset, SegmentBuilder* segment, ByteBuffer* defaultBuffer, int defaultOffset, int defaultSize)
749 	{
750 		long ref_ = segment.get(refOffset);
751 		
752 		if(WirePointer.isNull(ref_))
753 		{
754 			if(defaultBuffer is null)
755 				return Text.Builder();
756 			Text.Builder builder = initTextPointer(refOffset, segment, defaultSize);
757 			builder.buffer.buffer[builder.offset..builder.offset + builder.size] = defaultBuffer.buffer[defaultOffset * 8..defaultOffset * 8+builder.size];
758 			return builder;
759 		}
760 		
761 		int refTarget = WirePointer.target(refOffset, ref_);
762 		FollowBuilderFarsResult resolved = followBuilderFars(ref_, refTarget, segment);
763 		
764 		if(WirePointer.kind(resolved.ref_) != WirePointer.LIST)
765 			throw new DecodeException("Called getText{Field,Element} but existing pointer is not a list.");
766 		if(ListPointer.elementSize(resolved.ref_) != ElementSize.BYTE)
767 			throw new DecodeException("Called getText{Field,Element} but existing list pointer is not byte-sized.");
768 		
769 		int size = ListPointer.elementCount(resolved.ref_);
770 		if(size == 0 || resolved.segment.buffer.get!ubyte(resolved.ptr * Constants.BYTES_PER_WORD + size - 1) != 0)
771 			throw new DecodeException("Text blob missing NUL terminator.");
772 		return Text.Builder(resolved.segment.buffer, resolved.ptr * Constants.BYTES_PER_WORD, size - 1);
773 	}
774 	
775 	// size is in bytes
776 	static Data.Builder initDataPointer(int refOffset, SegmentBuilder* segment, int size)
777 	{
778 		//# Allocate the space.
779 		auto allocation = allocate(refOffset, segment, roundBytesUpToWords(size), WirePointer.LIST);
780 		
781 		//# Initialize the pointer.
782 		ListPointer.set(allocation.segment.buffer, allocation.refOffset, ElementSize.BYTE, size);
783 		
784 		return Data.Builder(allocation.segment.buffer, allocation.ptr * Constants.BYTES_PER_WORD, size);
785 	}
786 	
787 	static Data.Builder setDataPointer(int refOffset, SegmentBuilder* segment, Data.Reader value)
788 	{
789 		auto builder = initDataPointer(refOffset, segment, cast(int)value.size);
790 		
791 		//TODO: Is there a way to do this with bulk methods?
792 		foreach(i; 0..builder.size)
793 			builder.buffer.put!ubyte(builder.offset + i, value.buffer.get!ubyte(value.offset + i));
794 		return builder;
795 	}
796 
797 	static Data.Builder getWritableDataPointer(int refOffset, SegmentBuilder* segment, ByteBuffer* defaultBuffer, int defaultOffset, int defaultSize)
798 	{
799 		long ref_ = segment.get(refOffset);
800 		
801 		if(WirePointer.isNull(ref_))
802 		{
803 			if(defaultBuffer is null)
804 				return Data.Builder();
805 			auto builder = initDataPointer(refOffset, segment, defaultSize);
806 			//TODO: Is there a way to do this with bulk methods?
807 			foreach(i; 0..builder.size)
808 				builder.buffer.put!ubyte(builder.offset + i, defaultBuffer.get!ubyte(defaultOffset * 8 + i));
809 			return builder;
810 		}
811 		
812 		int refTarget = WirePointer.target(refOffset, ref_);
813 		FollowBuilderFarsResult resolved = followBuilderFars(ref_, refTarget, segment);
814 		
815 		if(WirePointer.kind(resolved.ref_) != WirePointer.LIST)
816 			throw new DecodeException("Called getData{Field,Element} but existing pointer is not a list.");
817 		if(ListPointer.elementSize(resolved.ref_) != ElementSize.BYTE)
818 			throw new DecodeException("Called getData{Field,Element} but existing list pointer is not byte-sized.");
819 		return Data.Builder(resolved.segment.buffer, resolved.ptr * Constants.BYTES_PER_WORD, ListPointer.elementCount(resolved.ref_));
820 	}
821 
822 	static T readStructPointer(T)(SegmentReader* segment, int refOffset, SegmentReader* defaultSegment, int defaultOffset, int nestingLimit)
823 	{
824 		long ref_ = segment.get(refOffset);
825 		if(WirePointer.isNull(ref_))
826 		{
827 			if(defaultSegment is null)
828 				return T(cast(SegmentReader*)&SegmentReader.EMPTY, 0, 0, 0, cast(short)0, 0x7fffffff);
829 			segment = defaultSegment;
830 			refOffset = defaultOffset;
831 			ref_ = segment.get(refOffset);
832 		}
833 		
834 		if(nestingLimit <= 0)
835 			throw new DecodeException("Message is too deeply nested or contains cycles.");
836 		
837 		int refTarget = WirePointer.target(refOffset, ref_);
838 		auto resolved = followFars(ref_, refTarget, segment);
839 		
840 		int dataSizeWords = StructPointer.dataSize(resolved.ref_);
841 		
842 		if(WirePointer.kind(resolved.ref_) != WirePointer.STRUCT)
843 			throw new DecodeException("Message contains non-struct pointer where struct pointer was expected.");
844 		
845 		resolved.segment.arena.checkReadLimit(StructPointer.wordSize(resolved.ref_));
846 		
847 		return T(resolved.segment, resolved.ptr * Constants.BYTES_PER_WORD, resolved.ptr + dataSizeWords, dataSizeWords * Constants.BITS_PER_WORD, StructPointer.ptrCount(resolved.ref_), nestingLimit - 1);
848 	}
849 	
850 	static SegmentBuilder* setStructPointer(SegmentBuilder* segment, int refOffset, StructReader value)
851 	{
852 		short dataSize = cast(short)roundBitsUpToWords(value.dataSize);
853 		int totalSize = dataSize + value.pointerCount * Constants.POINTER_SIZE_IN_WORDS;
854 		
855 		auto allocation = allocate(refOffset, segment, totalSize, WirePointer.STRUCT);
856 		StructPointer.set(allocation.segment.buffer, allocation.refOffset, dataSize, value.pointerCount);
857 		
858 		if(value.dataSize == 1)
859 			throw new Error("single bit case not handled");
860 		memcpy(allocation.segment.buffer, allocation.ptr * Constants.BYTES_PER_WORD, value.segment.buffer, value.data, value.dataSize / Constants.BITS_PER_BYTE);
861 		
862 		int pointerSection = allocation.ptr + dataSize;
863 		foreach(i; 0..value.pointerCount)
864 			copyPointer(allocation.segment, pointerSection + i, value.segment, value.pointers + i, value.nestingLimit);
865 		return allocation.segment;
866 	};
867 
868 	static SegmentBuilder* setListPointer(SegmentBuilder* segment, int refOffset, ListReader value)
869 	{
870 		import std.conv : to;
871 		int totalSize = roundBitsUpToWords(value.elementCount * value.step);
872 		
873 		if(value.step <= Constants.BITS_PER_WORD)
874 		{
875 			//# List of non-structs.
876 			auto allocation = allocate(refOffset, segment, totalSize, WirePointer.LIST);
877 			
878 			if(value.structPointerCount == 1)
879 			{
880 				//# List of pointers.
881 				ListPointer.set(allocation.segment.buffer, allocation.refOffset, ElementSize.POINTER, value.elementCount);
882 				foreach(i; 0..value.elementCount)
883 					copyPointer(allocation.segment, allocation.ptr + i, value.segment, value.ptr / Constants.BYTES_PER_WORD + i, value.nestingLimit);
884 			}
885 			else
886 			{
887 				//# List of data.
888 				byte elementSize = ElementSize.VOID;
889 				switch(value.step)
890 				{
891 					case 0:
892 						elementSize = ElementSize.VOID;
893 						break;
894 					case 1:
895 						elementSize = ElementSize.BIT;
896 						break;
897 					case 8:
898 						elementSize = ElementSize.BYTE;
899 						break;
900 					case 16:
901 						elementSize = ElementSize.TWO_BYTES;
902 						break;
903 					case 32:
904 						elementSize = ElementSize.FOUR_BYTES;
905 						break;
906 					case 64:
907 						elementSize = ElementSize.EIGHT_BYTES;
908 						break;
909 					default:
910 						throw new Error("Invalid list step size: " ~ to!string(value.step));
911 				}
912 				
913 				ListPointer.set(allocation.segment.buffer, allocation.refOffset, elementSize, value.elementCount);
914 				memcpy(allocation.segment.buffer, allocation.ptr * Constants.BYTES_PER_WORD, value.segment.buffer, value.ptr, totalSize * Constants.BYTES_PER_WORD);
915 			}
916 			return allocation.segment;
917 		}
918 		//# List of structs.
919 		auto allocation = allocate(refOffset, segment, totalSize + Constants.POINTER_SIZE_IN_WORDS, WirePointer.LIST);
920 		ListPointer.setInlineComposite(allocation.segment.buffer, allocation.refOffset, totalSize);
921 		short dataSize = cast(short)roundBitsUpToWords(value.structDataSize);
922 		short pointerCount = value.structPointerCount;
923 		
924 		WirePointer.setKindAndInlineCompositeListElementCount(allocation.segment.buffer, allocation.ptr, WirePointer.STRUCT, value.elementCount);
925 		StructPointer.set(allocation.segment.buffer, allocation.ptr, dataSize, pointerCount);
926 		
927 		int dstOffset = allocation.ptr + Constants.POINTER_SIZE_IN_WORDS;
928 		int srcOffset = value.ptr / Constants.BYTES_PER_WORD;
929 		
930 		foreach(i; 0..value.elementCount)
931 		{
932 			memcpy(allocation.segment.buffer, dstOffset * Constants.BYTES_PER_WORD, value.segment.buffer, srcOffset * Constants.BYTES_PER_WORD, value.structDataSize / Constants.BITS_PER_BYTE);
933 			dstOffset += dataSize;
934 			srcOffset += dataSize;
935 			
936 			foreach(j; 0..pointerCount)
937 			{
938 				copyPointer(allocation.segment, dstOffset, value.segment, srcOffset, value.nestingLimit);
939 				dstOffset += Constants.POINTER_SIZE_IN_WORDS;
940 				srcOffset += Constants.POINTER_SIZE_IN_WORDS;
941 			}
942 		}
943 		return allocation.segment;
944 	}
945 
946 	static void memset(ref ByteBuffer dstBuffer, int dstByteOffset, byte value, int length)
947 	{
948 		dstBuffer.buffer[dstByteOffset..dstByteOffset+length] = value;
949 	}
950 	
951 	static void memcpy(ref ByteBuffer dstBuffer, int dstByteOffset, ref ByteBuffer srcBuffer, int srcByteOffset, int length)
952 	{
953 		auto dstDup = dstBuffer;
954 		dstDup.position = dstByteOffset;
955 		dstDup.limit = dstByteOffset + length;
956 		auto srcDup = srcBuffer;
957 		srcDup.position = srcByteOffset;
958 		srcDup.limit = srcByteOffset + length;
959 		dstDup.put!ByteBuffer(srcDup);
960 	}
961 	
962 	static SegmentBuilder* copyPointer(SegmentBuilder* dstSegment, int dstOffset, SegmentReader* srcSegment, int srcOffset, int nestingLimit)
963 	{
964 		//Deep-copy the object pointed to by src into dst.  It turns out we can't reuse
965 		//readStructPointer(), etc. because they do type checking whereas here we want to accept any
966 		//valid pointer.
967 		
968 		long srcRef = srcSegment.get(srcOffset);
969 		
970 		if(WirePointer.isNull(srcRef))
971 		{
972 			dstSegment.buffer.put!long(dstOffset * 8, 0L);
973 			return dstSegment;
974 		}
975 		
976 		int srcTarget = WirePointer.target(srcOffset, srcRef);
977 		auto resolved = followFars(srcRef, srcTarget, srcSegment);
978 		
979 		final switch(WirePointer.kind(resolved.ref_))
980 		{
981 			case WirePointer.STRUCT:
982 				if(nestingLimit <= 0)
983 					throw new DecodeException("Message is too deeply nested or contains cycles. See org.capnproto.ReaderOptions.");
984 				resolved.segment.arena.checkReadLimit(StructPointer.wordSize(resolved.ref_));
985 				return setStructPointer(dstSegment, dstOffset,
986 				                        StructReader(resolved.segment, resolved.ptr * Constants.BYTES_PER_WORD, resolved.ptr + StructPointer.dataSize(resolved.ref_),
987 				                                     StructPointer.dataSize(resolved.ref_) * Constants.BITS_PER_WORD, StructPointer.ptrCount(resolved.ref_), nestingLimit - 1));
988 			case WirePointer.LIST:
989 			{
990 				byte elementSize = ListPointer.elementSize(resolved.ref_);
991 				if(nestingLimit <= 0)
992 					throw new DecodeException("Message is too deeply nested or contains cycles. See org.capnproto.ReaderOptions.");
993 				if(elementSize == ElementSize.INLINE_COMPOSITE)
994 				{
995 					int wordCount = ListPointer.inlineCompositeWordCount(resolved.ref_);
996 					long tag = resolved.segment.get(resolved.ptr);
997 					int ptr = resolved.ptr + 1;
998 					
999 					resolved.segment.arena.checkReadLimit(wordCount + 1);
1000 					
1001 					if(WirePointer.kind(tag) != WirePointer.STRUCT)
1002 						throw new DecodeException("INLINE_COMPOSITE lists of non-STRUCT type are not supported.");
1003 					
1004 					int elementCount = WirePointer.inlineCompositeListElementCount(tag);
1005 					int wordsPerElement = StructPointer.wordSize(tag);
1006 					if(cast(long)wordsPerElement * elementCount > wordCount)
1007 						throw new DecodeException("INLINE_COMPOSITE list's elements overrun its word count.");
1008 					
1009 					if(wordsPerElement == 0)
1010 					{
1011 						//Watch out for lists of zero-sized structs, which can claim to be arbitrarily
1012 						//large without having sent actual data.
1013 						resolved.segment.arena.checkReadLimit(elementCount);
1014 					}
1015 					
1016 					return setListPointer(dstSegment, dstOffset,
1017 					                      ListReader(resolved.segment, ptr * Constants.BYTES_PER_WORD, elementCount, wordsPerElement * Constants.BITS_PER_WORD,
1018 					                                 StructPointer.dataSize(tag) * Constants.BITS_PER_WORD, StructPointer.ptrCount(tag), nestingLimit - 1));
1019 				}
1020 				int dataSize = ElementSize.dataBitsPerElement(elementSize);
1021 				short pointerCount = ElementSize.pointersPerElement(elementSize);
1022 				int step = dataSize + pointerCount * Constants.BITS_PER_POINTER;
1023 				int elementCount = ListPointer.elementCount(resolved.ref_);
1024 				int wordCount = roundBitsUpToWords(cast(long)elementCount * step);
1025 				
1026 				resolved.segment.arena.checkReadLimit(wordCount);
1027 				
1028 				if(elementSize == ElementSize.VOID)
1029 				{
1030 					//Watch out for lists of void, which can claim to be arbitrarily large without
1031 					//having sent actual data.
1032 					resolved.segment.arena.checkReadLimit(elementCount);
1033 				}
1034 				
1035 				return setListPointer(dstSegment, dstOffset, ListReader(resolved.segment, resolved.ptr * Constants.BYTES_PER_WORD, elementCount, step, dataSize, pointerCount, nestingLimit - 1));
1036 			}
1037 			case WirePointer.FAR:
1038 				throw new DecodeException("Unexpected FAR pointer.");
1039 			case WirePointer.OTHER:
1040 				throw new Error("CopyPointer is unimplemented for OTHER pointers.");
1041 		}
1042 		assert(0, "Unreachable.");
1043 	}
1044 	
1045 	static T readListPointer(T)(SegmentReader* segment, int refOffset, SegmentReader* defaultSegment, int defaultOffset, byte expectedElementSize, int nestingLimit)
1046 	{
1047 		long ref_ = segment.get(refOffset);
1048 		
1049 		if(WirePointer.isNull(ref_))
1050 		{
1051 			if(defaultSegment is null)
1052 				return T(cast(SegmentReader*)&SegmentReader.EMPTY, 0, 0, 0, 0, cast(short)0, 0x7fffffff);
1053 			segment = defaultSegment;
1054 			refOffset = defaultOffset;
1055 			ref_ = segment.get(refOffset);
1056 		}
1057 		
1058 		if(nestingLimit <= 0)
1059 			throw new Error("Nesting limit exceeded.");
1060 		
1061 		int refTarget = WirePointer.target(refOffset, ref_);
1062 		
1063 		auto resolved = followFars(ref_, refTarget, segment);
1064 		
1065 		byte elementSize = ListPointer.elementSize(resolved.ref_);
1066 		switch(elementSize)
1067 		{
1068 			case ElementSize.INLINE_COMPOSITE:
1069 			{
1070 				int wordCount = ListPointer.inlineCompositeWordCount(resolved.ref_);
1071 				
1072 				long tag = resolved.segment.get(resolved.ptr);
1073 				int ptr = resolved.ptr + 1;
1074 				
1075 				resolved.segment.arena.checkReadLimit(wordCount + 1);
1076 				
1077 				int size = WirePointer.inlineCompositeListElementCount(tag);
1078 				
1079 				int wordsPerElement = StructPointer.wordSize(tag);
1080 				
1081 				if(cast(long)size * wordsPerElement > wordCount)
1082 					throw new DecodeException("INLINE_COMPOSITE list's elements overrun its word count.");
1083 				
1084 				if(wordsPerElement == 0)
1085 				{
1086 					//Watch out for lists of zero-sized structs, which can claim to be arbitrarily
1087 					//large without having sent actual data.
1088 					resolved.segment.arena.checkReadLimit(size);
1089 				}
1090 				
1091 				//TODO: Check whether the size is compatible.
1092 				
1093 				return T(resolved.segment, ptr * Constants.BYTES_PER_WORD, size, wordsPerElement * Constants.BITS_PER_WORD,
1094 				         StructPointer.dataSize(tag) * Constants.BITS_PER_WORD, StructPointer.ptrCount(tag), nestingLimit - 1);
1095 			}
1096 			default:
1097 				break;
1098 		}
1099 		
1100 		//# This is a primitive or pointer list, but all such
1101 		//# lists can also be interpreted as struct lists. We
1102 		//# need to compute the data size and pointer count for
1103 		//# such structs.
1104 		int dataSize = ElementSize.dataBitsPerElement(ListPointer.elementSize(resolved.ref_));
1105 		int pointerCount = ElementSize.pointersPerElement(ListPointer.elementSize(resolved.ref_));
1106 		int elementCount = ListPointer.elementCount(resolved.ref_);
1107 		int step = dataSize + pointerCount * Constants.BITS_PER_POINTER;
1108 		
1109 		resolved.segment.arena.checkReadLimit(roundBitsUpToWords(elementCount * step));
1110 		
1111 		if(elementSize == ElementSize.VOID)
1112 		{
1113 			//Watch out for lists of void, which can claim to be arbitrarily large without
1114 			//having sent actual data.
1115 			resolved.segment.arena.checkReadLimit(elementCount);
1116 		}
1117 		
1118 		//# Verify that the elements are at least as large as
1119 		//# the expected type. Note that if we expected
1120 		//# InlineComposite, the expected sizes here will be
1121 		//# zero, because bounds checking will be performed at
1122 		//# field access time. So this check here is for the
1123 		//# case where we expected a list of some primitive or
1124 		//# pointer type.
1125 		
1126 		int expectedDataBitsPerElement = ElementSize.dataBitsPerElement(expectedElementSize);
1127 		int expectedPointersPerElement = ElementSize.pointersPerElement(expectedElementSize);
1128 		
1129 		if(expectedDataBitsPerElement > dataSize)
1130 			throw new DecodeException("Message contains list with incompatible element type.");
1131 		if(expectedPointersPerElement > pointerCount)
1132 			throw new DecodeException("Message contains list with incompatible element type.");
1133 		
1134 		return T(resolved.segment, resolved.ptr * Constants.BYTES_PER_WORD, ListPointer.elementCount(resolved.ref_), step, dataSize, cast(short)pointerCount, nestingLimit - 1);
1135 	}
1136 	
1137 	static Text.Reader readTextPointer(SegmentReader* segment, int refOffset, ByteBuffer* defaultBuffer, int defaultOffset, int defaultSize)
1138 	{
1139 		long ref_ = segment.get(refOffset);
1140 		
1141 		if(WirePointer.isNull(ref_))
1142 		{
1143 			if(defaultBuffer is null)
1144 				return Text.Reader();
1145 			return Text.Reader(*defaultBuffer, defaultOffset, defaultSize);
1146 		}
1147 		
1148 		int refTarget = WirePointer.target(refOffset, ref_);
1149 		
1150 		auto resolved = followFars(ref_, refTarget, segment);
1151 		
1152 		int size = ListPointer.elementCount(resolved.ref_);
1153 		
1154 		if(WirePointer.kind(resolved.ref_) != WirePointer.LIST)
1155 			throw new DecodeException("Message contains non-list pointer where text was expected.");
1156 		
1157 		if(ListPointer.elementSize(resolved.ref_) != ElementSize.BYTE)
1158 			throw new DecodeException("Message contains list pointer of non-bytes where text was expected.");
1159 		
1160 		resolved.segment.arena.checkReadLimit(roundBytesUpToWords(size));
1161 		
1162 		if(size == 0 || resolved.segment.buffer.get!ubyte(8 * resolved.ptr + size - 1) != 0)
1163 			throw new DecodeException("Message contains text that is not NUL-terminated.");
1164 		
1165 		return Text.Reader(resolved.segment.buffer, resolved.ptr, size - 1);
1166 	}
1167 	
1168 	static Data.Reader readDataPointer(SegmentReader* segment, int refOffset, ByteBuffer* defaultBuffer, int defaultOffset, int defaultSize)
1169 	{
1170 		long ref_ = segment.get(refOffset);
1171 		
1172 		if(WirePointer.isNull(ref_))
1173 		{
1174 			if(defaultBuffer is null)
1175 				return Data.Reader();
1176 			return Data.Reader(*defaultBuffer, defaultOffset, defaultSize);
1177 		}
1178 		
1179 		int refTarget = WirePointer.target(refOffset, ref_);
1180 		
1181 		auto resolved = followFars(ref_, refTarget, segment);
1182 		
1183 		int size = ListPointer.elementCount(resolved.ref_);
1184 		
1185 		if(WirePointer.kind(resolved.ref_) != WirePointer.LIST)
1186 			throw new DecodeException("Message contains non-list pointer where data was expected.");
1187 		
1188 		if(ListPointer.elementSize(resolved.ref_) != ElementSize.BYTE)
1189 			throw new DecodeException("Message contains list pointer of non-bytes where data was expected.");
1190 		
1191 		resolved.segment.arena.checkReadLimit(roundBytesUpToWords(size));
1192 		
1193 		return Data.Reader(resolved.segment.buffer, resolved.ptr, size);
1194 	}
1195 }