1# Copyright 2014 Google Inc. All rights reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15from . import number_types as N 16from .number_types import (UOffsetTFlags, SOffsetTFlags, VOffsetTFlags) 17 18from . import encode 19from . import packer 20 21from . import compat 22from .compat import range_func 23from .compat import memoryview_type 24 25 26## @file 27## @addtogroup flatbuffers_python_api 28## @{ 29 30## @cond FLATBUFFERS_INTERNAL 31class OffsetArithmeticError(RuntimeError): 32 """ 33 Error caused by an Offset arithmetic error. Probably caused by bad 34 writing of fields. This is considered an unreachable situation in 35 normal circumstances. 36 """ 37 pass 38 39 40class IsNotNestedError(RuntimeError): 41 """ 42 Error caused by using a Builder to write Object data when not inside 43 an Object. 44 """ 45 pass 46 47 48class IsNestedError(RuntimeError): 49 """ 50 Error caused by using a Builder to begin an Object when an Object is 51 already being built. 52 """ 53 pass 54 55 56class StructIsNotInlineError(RuntimeError): 57 """ 58 Error caused by using a Builder to write a Struct at a location that 59 is not the current Offset. 60 """ 61 pass 62 63 64class BuilderSizeError(RuntimeError): 65 """ 66 Error caused by causing a Builder to exceed the hardcoded limit of 2 67 gigabytes. 68 """ 69 pass 70 71class BuilderNotFinishedError(RuntimeError): 72 """ 73 Error caused by not calling `Finish` before calling `Output`. 74 """ 75 pass 76 77 78# VtableMetadataFields is the count of metadata fields in each vtable. 79VtableMetadataFields = 2 80## @endcond 81 82class Builder(object): 83 """ A Builder is used to construct one or more FlatBuffers. 84 85 Typically, Builder objects will be used from code generated by the `flatc` 86 compiler. 87 88 A Builder constructs byte buffers in a last-first manner for simplicity and 89 performance during reading. 90 91 Internally, a Builder is a state machine for creating FlatBuffer objects. 92 93 It holds the following internal state: 94 - Bytes: an array of bytes. 95 - current_vtable: a list of integers. 96 - vtables: a list of vtable entries (i.e. a list of list of integers). 97 98 Attributes: 99 Bytes: The internal `bytearray` for the Builder. 100 finished: A boolean determining if the Builder has been finalized. 101 """ 102 103 ## @cond FLATBUFFERS_INTENRAL 104 __slots__ = ("Bytes", "current_vtable", "head", "minalign", "objectEnd", 105 "vtables", "nested", "finished") 106 107 """Maximum buffer size constant, in bytes. 108 109 Builder will never allow it's buffer grow over this size. 110 Currently equals 2Gb. 111 """ 112 MAX_BUFFER_SIZE = 2**31 113 ## @endcond 114 115 def __init__(self, initialSize): 116 """Initializes a Builder of size `initial_size`. 117 118 The internal buffer is grown as needed. 119 """ 120 121 if not (0 <= initialSize <= Builder.MAX_BUFFER_SIZE): 122 msg = "flatbuffers: Cannot create Builder larger than 2 gigabytes." 123 raise BuilderSizeError(msg) 124 125 self.Bytes = bytearray(initialSize) 126 ## @cond FLATBUFFERS_INTERNAL 127 self.current_vtable = None 128 self.head = UOffsetTFlags.py_type(initialSize) 129 self.minalign = 1 130 self.objectEnd = None 131 self.vtables = [] 132 self.nested = False 133 ## @endcond 134 self.finished = False 135 136 137 def Output(self): 138 """Return the portion of the buffer that has been used for writing data. 139 140 This is the typical way to access the FlatBuffer data inside the 141 builder. If you try to access `Builder.Bytes` directly, you would need 142 to manually index it with `Head()`, since the buffer is constructed 143 backwards. 144 145 It raises BuilderNotFinishedError if the buffer has not been finished 146 with `Finish`. 147 """ 148 149 if not self.finished: 150 raise BuilderNotFinishedError() 151 152 return self.Bytes[self.Head():] 153 154 ## @cond FLATBUFFERS_INTERNAL 155 def StartObject(self, numfields): 156 """StartObject initializes bookkeeping for writing a new object.""" 157 158 self.assertNotNested() 159 160 # use 32-bit offsets so that arithmetic doesn't overflow. 161 self.current_vtable = [0 for _ in range_func(numfields)] 162 self.objectEnd = self.Offset() 163 self.minalign = 1 164 self.nested = True 165 166 def WriteVtable(self): 167 """ 168 WriteVtable serializes the vtable for the current object, if needed. 169 170 Before writing out the vtable, this checks pre-existing vtables for 171 equality to this one. If an equal vtable is found, point the object to 172 the existing vtable and return. 173 174 Because vtable values are sensitive to alignment of object data, not 175 all logically-equal vtables will be deduplicated. 176 177 A vtable has the following format: 178 <VOffsetT: size of the vtable in bytes, including this value> 179 <VOffsetT: size of the object in bytes, including the vtable offset> 180 <VOffsetT: offset for a field> * N, where N is the number of fields 181 in the schema for this type. Includes deprecated fields. 182 Thus, a vtable is made of 2 + N elements, each VOffsetT bytes wide. 183 184 An object has the following format: 185 <SOffsetT: offset to this object's vtable (may be negative)> 186 <byte: data>+ 187 """ 188 189 # Prepend a zero scalar to the object. Later in this function we'll 190 # write an offset here that points to the object's vtable: 191 self.PrependSOffsetTRelative(0) 192 193 objectOffset = self.Offset() 194 existingVtable = None 195 196 # Trim trailing 0 offsets. 197 while self.current_vtable and self.current_vtable[-1] == 0: 198 self.current_vtable.pop() 199 200 # Search backwards through existing vtables, because similar vtables 201 # are likely to have been recently appended. See 202 # BenchmarkVtableDeduplication for a case in which this heuristic 203 # saves about 30% of the time used in writing objects with duplicate 204 # tables. 205 206 i = len(self.vtables) - 1 207 while i >= 0: 208 # Find the other vtable, which is associated with `i`: 209 vt2Offset = self.vtables[i] 210 vt2Start = len(self.Bytes) - vt2Offset 211 vt2Len = encode.Get(packer.voffset, self.Bytes, vt2Start) 212 213 metadata = VtableMetadataFields * N.VOffsetTFlags.bytewidth 214 vt2End = vt2Start + vt2Len 215 vt2 = self.Bytes[vt2Start+metadata:vt2End] 216 217 # Compare the other vtable to the one under consideration. 218 # If they are equal, store the offset and break: 219 if vtableEqual(self.current_vtable, objectOffset, vt2): 220 existingVtable = vt2Offset 221 break 222 223 i -= 1 224 225 if existingVtable is None: 226 # Did not find a vtable, so write this one to the buffer. 227 228 # Write out the current vtable in reverse , because 229 # serialization occurs in last-first order: 230 i = len(self.current_vtable) - 1 231 while i >= 0: 232 off = 0 233 if self.current_vtable[i] != 0: 234 # Forward reference to field; 235 # use 32bit number to ensure no overflow: 236 off = objectOffset - self.current_vtable[i] 237 238 self.PrependVOffsetT(off) 239 i -= 1 240 241 # The two metadata fields are written last. 242 243 # First, store the object bytesize: 244 objectSize = UOffsetTFlags.py_type(objectOffset - self.objectEnd) 245 self.PrependVOffsetT(VOffsetTFlags.py_type(objectSize)) 246 247 # Second, store the vtable bytesize: 248 vBytes = len(self.current_vtable) + VtableMetadataFields 249 vBytes *= N.VOffsetTFlags.bytewidth 250 self.PrependVOffsetT(VOffsetTFlags.py_type(vBytes)) 251 252 # Next, write the offset to the new vtable in the 253 # already-allocated SOffsetT at the beginning of this object: 254 objectStart = SOffsetTFlags.py_type(len(self.Bytes) - objectOffset) 255 encode.Write(packer.soffset, self.Bytes, objectStart, 256 SOffsetTFlags.py_type(self.Offset() - objectOffset)) 257 258 # Finally, store this vtable in memory for future 259 # deduplication: 260 self.vtables.append(self.Offset()) 261 else: 262 # Found a duplicate vtable. 263 264 objectStart = SOffsetTFlags.py_type(len(self.Bytes) - objectOffset) 265 self.head = UOffsetTFlags.py_type(objectStart) 266 267 # Write the offset to the found vtable in the 268 # already-allocated SOffsetT at the beginning of this object: 269 encode.Write(packer.soffset, self.Bytes, self.Head(), 270 SOffsetTFlags.py_type(existingVtable - objectOffset)) 271 272 self.current_vtable = None 273 return objectOffset 274 275 def EndObject(self): 276 """EndObject writes data necessary to finish object construction.""" 277 self.assertNested() 278 self.nested = False 279 return self.WriteVtable() 280 281 def growByteBuffer(self): 282 """Doubles the size of the byteslice, and copies the old data towards 283 the end of the new buffer (since we build the buffer backwards).""" 284 if len(self.Bytes) == Builder.MAX_BUFFER_SIZE: 285 msg = "flatbuffers: cannot grow buffer beyond 2 gigabytes" 286 raise BuilderSizeError(msg) 287 288 newSize = min(len(self.Bytes) * 2, Builder.MAX_BUFFER_SIZE) 289 if newSize == 0: 290 newSize = 1 291 bytes2 = bytearray(newSize) 292 bytes2[newSize-len(self.Bytes):] = self.Bytes 293 self.Bytes = bytes2 294 ## @endcond 295 296 def Head(self): 297 """Get the start of useful data in the underlying byte buffer. 298 299 Note: unlike other functions, this value is interpreted as from the 300 left. 301 """ 302 ## @cond FLATBUFFERS_INTERNAL 303 return self.head 304 ## @endcond 305 306 ## @cond FLATBUFFERS_INTERNAL 307 def Offset(self): 308 """Offset relative to the end of the buffer.""" 309 return UOffsetTFlags.py_type(len(self.Bytes) - self.Head()) 310 311 def Pad(self, n): 312 """Pad places zeros at the current offset.""" 313 for i in range_func(n): 314 self.Place(0, N.Uint8Flags) 315 316 def Prep(self, size, additionalBytes): 317 """ 318 Prep prepares to write an element of `size` after `additional_bytes` 319 have been written, e.g. if you write a string, you need to align 320 such the int length field is aligned to SizeInt32, and the string 321 data follows it directly. 322 If all you need to do is align, `additionalBytes` will be 0. 323 """ 324 325 # Track the biggest thing we've ever aligned to. 326 if size > self.minalign: 327 self.minalign = size 328 329 # Find the amount of alignment needed such that `size` is properly 330 # aligned after `additionalBytes`: 331 alignSize = (~(len(self.Bytes) - self.Head() + additionalBytes)) + 1 332 alignSize &= (size - 1) 333 334 # Reallocate the buffer if needed: 335 while self.Head() < alignSize+size+additionalBytes: 336 oldBufSize = len(self.Bytes) 337 self.growByteBuffer() 338 updated_head = self.head + len(self.Bytes) - oldBufSize 339 self.head = UOffsetTFlags.py_type(updated_head) 340 self.Pad(alignSize) 341 342 def PrependSOffsetTRelative(self, off): 343 """ 344 PrependSOffsetTRelative prepends an SOffsetT, relative to where it 345 will be written. 346 """ 347 348 # Ensure alignment is already done: 349 self.Prep(N.SOffsetTFlags.bytewidth, 0) 350 if not (off <= self.Offset()): 351 msg = "flatbuffers: Offset arithmetic error." 352 raise OffsetArithmeticError(msg) 353 off2 = self.Offset() - off + N.SOffsetTFlags.bytewidth 354 self.PlaceSOffsetT(off2) 355 ## @endcond 356 357 def PrependUOffsetTRelative(self, off): 358 """Prepends an unsigned offset into vector data, relative to where it 359 will be written. 360 """ 361 362 # Ensure alignment is already done: 363 self.Prep(N.UOffsetTFlags.bytewidth, 0) 364 if not (off <= self.Offset()): 365 msg = "flatbuffers: Offset arithmetic error." 366 raise OffsetArithmeticError(msg) 367 off2 = self.Offset() - off + N.UOffsetTFlags.bytewidth 368 self.PlaceUOffsetT(off2) 369 370 ## @cond FLATBUFFERS_INTERNAL 371 def StartVector(self, elemSize, numElems, alignment): 372 """ 373 StartVector initializes bookkeeping for writing a new vector. 374 375 A vector has the following format: 376 - <UOffsetT: number of elements in this vector> 377 - <T: data>+, where T is the type of elements of this vector. 378 """ 379 380 self.assertNotNested() 381 self.nested = True 382 self.Prep(N.Uint32Flags.bytewidth, elemSize*numElems) 383 self.Prep(alignment, elemSize*numElems) # In case alignment > int. 384 return self.Offset() 385 ## @endcond 386 387 def EndVector(self, vectorNumElems): 388 """EndVector writes data necessary to finish vector construction.""" 389 390 self.assertNested() 391 ## @cond FLATBUFFERS_INTERNAL 392 self.nested = False 393 ## @endcond 394 # we already made space for this, so write without PrependUint32 395 self.PlaceUOffsetT(vectorNumElems) 396 return self.Offset() 397 398 def CreateString(self, s, encoding='utf-8', errors='strict'): 399 """CreateString writes a null-terminated byte string as a vector.""" 400 401 self.assertNotNested() 402 ## @cond FLATBUFFERS_INTERNAL 403 self.nested = True 404 ## @endcond 405 406 if isinstance(s, compat.string_types): 407 x = s.encode(encoding, errors) 408 elif isinstance(s, compat.binary_types): 409 x = s 410 else: 411 raise TypeError("non-string passed to CreateString") 412 413 self.Prep(N.UOffsetTFlags.bytewidth, (len(x)+1)*N.Uint8Flags.bytewidth) 414 self.Place(0, N.Uint8Flags) 415 416 l = UOffsetTFlags.py_type(len(s)) 417 ## @cond FLATBUFFERS_INTERNAL 418 self.head = UOffsetTFlags.py_type(self.Head() - l) 419 ## @endcond 420 self.Bytes[self.Head():self.Head()+l] = x 421 422 return self.EndVector(len(x)) 423 424 def CreateByteVector(self, x): 425 """CreateString writes a byte vector.""" 426 427 self.assertNotNested() 428 ## @cond FLATBUFFERS_INTERNAL 429 self.nested = True 430 ## @endcond 431 432 if not isinstance(x, compat.binary_types): 433 raise TypeError("non-byte vector passed to CreateByteVector") 434 435 self.Prep(N.UOffsetTFlags.bytewidth, len(x)*N.Uint8Flags.bytewidth) 436 437 l = UOffsetTFlags.py_type(len(x)) 438 ## @cond FLATBUFFERS_INTERNAL 439 self.head = UOffsetTFlags.py_type(self.Head() - l) 440 ## @endcond 441 self.Bytes[self.Head():self.Head()+l] = x 442 443 return self.EndVector(len(x)) 444 445 ## @cond FLATBUFFERS_INTERNAL 446 def assertNested(self): 447 """ 448 Check that we are in the process of building an object. 449 """ 450 451 if not self.nested: 452 raise IsNotNestedError() 453 454 def assertNotNested(self): 455 """ 456 Check that no other objects are being built while making this 457 object. If not, raise an exception. 458 """ 459 460 if self.nested: 461 raise IsNestedError() 462 463 def assertStructIsInline(self, obj): 464 """ 465 Structs are always stored inline, so need to be created right 466 where they are used. You'll get this error if you created it 467 elsewhere. 468 """ 469 470 N.enforce_number(obj, N.UOffsetTFlags) 471 if obj != self.Offset(): 472 msg = ("flatbuffers: Tried to write a Struct at an Offset that " 473 "is different from the current Offset of the Builder.") 474 raise StructIsNotInlineError(msg) 475 476 def Slot(self, slotnum): 477 """ 478 Slot sets the vtable key `voffset` to the current location in the 479 buffer. 480 481 """ 482 self.assertNested() 483 self.current_vtable[slotnum] = self.Offset() 484 ## @endcond 485 486 def Finish(self, rootTable): 487 """Finish finalizes a buffer, pointing to the given `rootTable`.""" 488 N.enforce_number(rootTable, N.UOffsetTFlags) 489 self.Prep(self.minalign, N.UOffsetTFlags.bytewidth) 490 self.PrependUOffsetTRelative(rootTable) 491 self.finished = True 492 return self.Head() 493 494 ## @cond FLATBUFFERS_INTERNAL 495 def Prepend(self, flags, off): 496 self.Prep(flags.bytewidth, 0) 497 self.Place(off, flags) 498 499 def PrependSlot(self, flags, o, x, d): 500 N.enforce_number(x, flags) 501 N.enforce_number(d, flags) 502 if x != d: 503 self.Prepend(flags, x) 504 self.Slot(o) 505 506 def PrependBoolSlot(self, *args): self.PrependSlot(N.BoolFlags, *args) 507 508 def PrependByteSlot(self, *args): self.PrependSlot(N.Uint8Flags, *args) 509 510 def PrependUint8Slot(self, *args): self.PrependSlot(N.Uint8Flags, *args) 511 512 def PrependUint16Slot(self, *args): self.PrependSlot(N.Uint16Flags, *args) 513 514 def PrependUint32Slot(self, *args): self.PrependSlot(N.Uint32Flags, *args) 515 516 def PrependUint64Slot(self, *args): self.PrependSlot(N.Uint64Flags, *args) 517 518 def PrependInt8Slot(self, *args): self.PrependSlot(N.Int8Flags, *args) 519 520 def PrependInt16Slot(self, *args): self.PrependSlot(N.Int16Flags, *args) 521 522 def PrependInt32Slot(self, *args): self.PrependSlot(N.Int32Flags, *args) 523 524 def PrependInt64Slot(self, *args): self.PrependSlot(N.Int64Flags, *args) 525 526 def PrependFloat32Slot(self, *args): self.PrependSlot(N.Float32Flags, 527 *args) 528 529 def PrependFloat64Slot(self, *args): self.PrependSlot(N.Float64Flags, 530 *args) 531 532 def PrependUOffsetTRelativeSlot(self, o, x, d): 533 """ 534 PrependUOffsetTRelativeSlot prepends an UOffsetT onto the object at 535 vtable slot `o`. If value `x` equals default `d`, then the slot will 536 be set to zero and no other data will be written. 537 """ 538 539 if x != d: 540 self.PrependUOffsetTRelative(x) 541 self.Slot(o) 542 543 def PrependStructSlot(self, v, x, d): 544 """ 545 PrependStructSlot prepends a struct onto the object at vtable slot `o`. 546 Structs are stored inline, so nothing additional is being added. 547 In generated code, `d` is always 0. 548 """ 549 550 N.enforce_number(d, N.UOffsetTFlags) 551 if x != d: 552 self.assertStructIsInline(x) 553 self.Slot(v) 554 555 ## @endcond 556 557 def PrependBool(self, x): 558 """Prepend a `bool` to the Builder buffer. 559 560 Note: aligns and checks for space. 561 """ 562 self.Prepend(N.BoolFlags, x) 563 564 def PrependByte(self, x): 565 """Prepend a `byte` to the Builder buffer. 566 567 Note: aligns and checks for space. 568 """ 569 self.Prepend(N.Uint8Flags, x) 570 571 def PrependUint8(self, x): 572 """Prepend an `uint8` to the Builder buffer. 573 574 Note: aligns and checks for space. 575 """ 576 self.Prepend(N.Uint8Flags, x) 577 578 def PrependUint16(self, x): 579 """Prepend an `uint16` to the Builder buffer. 580 581 Note: aligns and checks for space. 582 """ 583 self.Prepend(N.Uint16Flags, x) 584 585 def PrependUint32(self, x): 586 """Prepend an `uint32` to the Builder buffer. 587 588 Note: aligns and checks for space. 589 """ 590 self.Prepend(N.Uint32Flags, x) 591 592 def PrependUint64(self, x): 593 """Prepend an `uint64` to the Builder buffer. 594 595 Note: aligns and checks for space. 596 """ 597 self.Prepend(N.Uint64Flags, x) 598 599 def PrependInt8(self, x): 600 """Prepend an `int8` to the Builder buffer. 601 602 Note: aligns and checks for space. 603 """ 604 self.Prepend(N.Int8Flags, x) 605 606 def PrependInt16(self, x): 607 """Prepend an `int16` to the Builder buffer. 608 609 Note: aligns and checks for space. 610 """ 611 self.Prepend(N.Int16Flags, x) 612 613 def PrependInt32(self, x): 614 """Prepend an `int32` to the Builder buffer. 615 616 Note: aligns and checks for space. 617 """ 618 self.Prepend(N.Int32Flags, x) 619 620 def PrependInt64(self, x): 621 """Prepend an `int64` to the Builder buffer. 622 623 Note: aligns and checks for space. 624 """ 625 self.Prepend(N.Int64Flags, x) 626 627 def PrependFloat32(self, x): 628 """Prepend a `float32` to the Builder buffer. 629 630 Note: aligns and checks for space. 631 """ 632 self.Prepend(N.Float32Flags, x) 633 634 def PrependFloat64(self, x): 635 """Prepend a `float64` to the Builder buffer. 636 637 Note: aligns and checks for space. 638 """ 639 self.Prepend(N.Float64Flags, x) 640 641############################################################## 642 643 ## @cond FLATBUFFERS_INTERNAL 644 def PrependVOffsetT(self, x): self.Prepend(N.VOffsetTFlags, x) 645 646 def Place(self, x, flags): 647 """ 648 Place prepends a value specified by `flags` to the Builder, 649 without checking for available space. 650 """ 651 652 N.enforce_number(x, flags) 653 self.head = self.head - flags.bytewidth 654 encode.Write(flags.packer_type, self.Bytes, self.Head(), x) 655 656 def PlaceVOffsetT(self, x): 657 """PlaceVOffsetT prepends a VOffsetT to the Builder, without checking 658 for space. 659 """ 660 N.enforce_number(x, N.VOffsetTFlags) 661 self.head = self.head - N.VOffsetTFlags.bytewidth 662 encode.Write(packer.voffset, self.Bytes, self.Head(), x) 663 664 def PlaceSOffsetT(self, x): 665 """PlaceSOffsetT prepends a SOffsetT to the Builder, without checking 666 for space. 667 """ 668 N.enforce_number(x, N.SOffsetTFlags) 669 self.head = self.head - N.SOffsetTFlags.bytewidth 670 encode.Write(packer.soffset, self.Bytes, self.Head(), x) 671 672 def PlaceUOffsetT(self, x): 673 """PlaceUOffsetT prepends a UOffsetT to the Builder, without checking 674 for space. 675 """ 676 N.enforce_number(x, N.UOffsetTFlags) 677 self.head = self.head - N.UOffsetTFlags.bytewidth 678 encode.Write(packer.uoffset, self.Bytes, self.Head(), x) 679 ## @endcond 680 681## @cond FLATBUFFERS_INTERNAL 682def vtableEqual(a, objectStart, b): 683 """vtableEqual compares an unwritten vtable to a written vtable.""" 684 685 N.enforce_number(objectStart, N.UOffsetTFlags) 686 687 if len(a) * N.VOffsetTFlags.bytewidth != len(b): 688 return False 689 690 for i, elem in enumerate(a): 691 x = encode.Get(packer.voffset, b, i * N.VOffsetTFlags.bytewidth) 692 693 # Skip vtable entries that indicate a default value. 694 if x == 0 and elem == 0: 695 pass 696 else: 697 y = objectStart - elem 698 if x != y: 699 return False 700 return True 701## @endcond 702## @} 703