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 }