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 # Search backwards through existing vtables, because similar vtables 197 # are likely to have been recently appended. See 198 # BenchmarkVtableDeduplication for a case in which this heuristic 199 # saves about 30% of the time used in writing objects with duplicate 200 # tables. 201 202 i = len(self.vtables) - 1 203 while i >= 0: 204 # Find the other vtable, which is associated with `i`: 205 vt2Offset = self.vtables[i] 206 vt2Start = len(self.Bytes) - vt2Offset 207 vt2Len = encode.Get(packer.voffset, self.Bytes, vt2Start) 208 209 metadata = VtableMetadataFields * N.VOffsetTFlags.bytewidth 210 vt2End = vt2Start + vt2Len 211 vt2 = self.Bytes[vt2Start+metadata:vt2End] 212 213 # Compare the other vtable to the one under consideration. 214 # If they are equal, store the offset and break: 215 if vtableEqual(self.current_vtable, objectOffset, vt2): 216 existingVtable = vt2Offset 217 break 218 219 i -= 1 220 221 if existingVtable is None: 222 # Did not find a vtable, so write this one to the buffer. 223 224 # Write out the current vtable in reverse , because 225 # serialization occurs in last-first order: 226 i = len(self.current_vtable) - 1 227 while i >= 0: 228 off = 0 229 if self.current_vtable[i] != 0: 230 # Forward reference to field; 231 # use 32bit number to ensure no overflow: 232 off = objectOffset - self.current_vtable[i] 233 234 self.PrependVOffsetT(off) 235 i -= 1 236 237 # The two metadata fields are written last. 238 239 # First, store the object bytesize: 240 objectSize = UOffsetTFlags.py_type(objectOffset - self.objectEnd) 241 self.PrependVOffsetT(VOffsetTFlags.py_type(objectSize)) 242 243 # Second, store the vtable bytesize: 244 vBytes = len(self.current_vtable) + VtableMetadataFields 245 vBytes *= N.VOffsetTFlags.bytewidth 246 self.PrependVOffsetT(VOffsetTFlags.py_type(vBytes)) 247 248 # Next, write the offset to the new vtable in the 249 # already-allocated SOffsetT at the beginning of this object: 250 objectStart = SOffsetTFlags.py_type(len(self.Bytes) - objectOffset) 251 encode.Write(packer.soffset, self.Bytes, objectStart, 252 SOffsetTFlags.py_type(self.Offset() - objectOffset)) 253 254 # Finally, store this vtable in memory for future 255 # deduplication: 256 self.vtables.append(self.Offset()) 257 else: 258 # Found a duplicate vtable. 259 260 objectStart = SOffsetTFlags.py_type(len(self.Bytes) - objectOffset) 261 self.head = UOffsetTFlags.py_type(objectStart) 262 263 # Write the offset to the found vtable in the 264 # already-allocated SOffsetT at the beginning of this object: 265 encode.Write(packer.soffset, self.Bytes, self.Head(), 266 SOffsetTFlags.py_type(existingVtable - objectOffset)) 267 268 self.current_vtable = None 269 return objectOffset 270 271 def EndObject(self): 272 """EndObject writes data necessary to finish object construction.""" 273 self.assertNested() 274 self.nested = False 275 return self.WriteVtable() 276 277 def growByteBuffer(self): 278 """Doubles the size of the byteslice, and copies the old data towards 279 the end of the new buffer (since we build the buffer backwards).""" 280 if len(self.Bytes) == Builder.MAX_BUFFER_SIZE: 281 msg = "flatbuffers: cannot grow buffer beyond 2 gigabytes" 282 raise BuilderSizeError(msg) 283 284 newSize = min(len(self.Bytes) * 2, Builder.MAX_BUFFER_SIZE) 285 if newSize == 0: 286 newSize = 1 287 bytes2 = bytearray(newSize) 288 bytes2[newSize-len(self.Bytes):] = self.Bytes 289 self.Bytes = bytes2 290 ## @endcond 291 292 def Head(self): 293 """Get the start of useful data in the underlying byte buffer. 294 295 Note: unlike other functions, this value is interpreted as from the 296 left. 297 """ 298 ## @cond FLATBUFFERS_INTERNAL 299 return self.head 300 ## @endcond 301 302 ## @cond FLATBUFFERS_INTERNAL 303 def Offset(self): 304 """Offset relative to the end of the buffer.""" 305 return UOffsetTFlags.py_type(len(self.Bytes) - self.Head()) 306 307 def Pad(self, n): 308 """Pad places zeros at the current offset.""" 309 for i in range_func(n): 310 self.Place(0, N.Uint8Flags) 311 312 def Prep(self, size, additionalBytes): 313 """ 314 Prep prepares to write an element of `size` after `additional_bytes` 315 have been written, e.g. if you write a string, you need to align 316 such the int length field is aligned to SizeInt32, and the string 317 data follows it directly. 318 If all you need to do is align, `additionalBytes` will be 0. 319 """ 320 321 # Track the biggest thing we've ever aligned to. 322 if size > self.minalign: 323 self.minalign = size 324 325 # Find the amount of alignment needed such that `size` is properly 326 # aligned after `additionalBytes`: 327 alignSize = (~(len(self.Bytes) - self.Head() + additionalBytes)) + 1 328 alignSize &= (size - 1) 329 330 # Reallocate the buffer if needed: 331 while self.Head() < alignSize+size+additionalBytes: 332 oldBufSize = len(self.Bytes) 333 self.growByteBuffer() 334 updated_head = self.head + len(self.Bytes) - oldBufSize 335 self.head = UOffsetTFlags.py_type(updated_head) 336 self.Pad(alignSize) 337 338 def PrependSOffsetTRelative(self, off): 339 """ 340 PrependSOffsetTRelative prepends an SOffsetT, relative to where it 341 will be written. 342 """ 343 344 # Ensure alignment is already done: 345 self.Prep(N.SOffsetTFlags.bytewidth, 0) 346 if not (off <= self.Offset()): 347 msg = "flatbuffers: Offset arithmetic error." 348 raise OffsetArithmeticError(msg) 349 off2 = self.Offset() - off + N.SOffsetTFlags.bytewidth 350 self.PlaceSOffsetT(off2) 351 ## @endcond 352 353 def PrependUOffsetTRelative(self, off): 354 """Prepends an unsigned offset into vector data, relative to where it 355 will be written. 356 """ 357 358 # Ensure alignment is already done: 359 self.Prep(N.UOffsetTFlags.bytewidth, 0) 360 if not (off <= self.Offset()): 361 msg = "flatbuffers: Offset arithmetic error." 362 raise OffsetArithmeticError(msg) 363 off2 = self.Offset() - off + N.UOffsetTFlags.bytewidth 364 self.PlaceUOffsetT(off2) 365 366 ## @cond FLATBUFFERS_INTERNAL 367 def StartVector(self, elemSize, numElems, alignment): 368 """ 369 StartVector initializes bookkeeping for writing a new vector. 370 371 A vector has the following format: 372 - <UOffsetT: number of elements in this vector> 373 - <T: data>+, where T is the type of elements of this vector. 374 """ 375 376 self.assertNotNested() 377 self.nested = True 378 self.Prep(N.Uint32Flags.bytewidth, elemSize*numElems) 379 self.Prep(alignment, elemSize*numElems) # In case alignment > int. 380 return self.Offset() 381 ## @endcond 382 383 def EndVector(self, vectorNumElems): 384 """EndVector writes data necessary to finish vector construction.""" 385 386 self.assertNested() 387 ## @cond FLATBUFFERS_INTERNAL 388 self.nested = False 389 ## @endcond 390 # we already made space for this, so write without PrependUint32 391 self.PlaceUOffsetT(vectorNumElems) 392 return self.Offset() 393 394 def CreateString(self, s, encoding='utf-8', errors='strict'): 395 """CreateString writes a null-terminated byte string as a vector.""" 396 397 self.assertNotNested() 398 ## @cond FLATBUFFERS_INTERNAL 399 self.nested = True 400 ## @endcond 401 402 if isinstance(s, compat.string_types): 403 x = s.encode(encoding, errors) 404 elif isinstance(s, compat.binary_types): 405 x = s 406 else: 407 raise TypeError("non-string passed to CreateString") 408 409 self.Prep(N.UOffsetTFlags.bytewidth, (len(x)+1)*N.Uint8Flags.bytewidth) 410 self.Place(0, N.Uint8Flags) 411 412 l = UOffsetTFlags.py_type(len(s)) 413 ## @cond FLATBUFFERS_INTERNAL 414 self.head = UOffsetTFlags.py_type(self.Head() - l) 415 ## @endcond 416 self.Bytes[self.Head():self.Head()+l] = x 417 418 return self.EndVector(len(x)) 419 420 ## @cond FLATBUFFERS_INTERNAL 421 def assertNested(self): 422 """ 423 Check that we are in the process of building an object. 424 """ 425 426 if not self.nested: 427 raise IsNotNestedError() 428 429 def assertNotNested(self): 430 """ 431 Check that no other objects are being built while making this 432 object. If not, raise an exception. 433 """ 434 435 if self.nested: 436 raise IsNestedError() 437 438 def assertStructIsInline(self, obj): 439 """ 440 Structs are always stored inline, so need to be created right 441 where they are used. You'll get this error if you created it 442 elsewhere. 443 """ 444 445 N.enforce_number(obj, N.UOffsetTFlags) 446 if obj != self.Offset(): 447 msg = ("flatbuffers: Tried to write a Struct at an Offset that " 448 "is different from the current Offset of the Builder.") 449 raise StructIsNotInlineError(msg) 450 451 def Slot(self, slotnum): 452 """ 453 Slot sets the vtable key `voffset` to the current location in the 454 buffer. 455 456 """ 457 self.assertNested() 458 self.current_vtable[slotnum] = self.Offset() 459 ## @endcond 460 461 def Finish(self, rootTable): 462 """Finish finalizes a buffer, pointing to the given `rootTable`.""" 463 N.enforce_number(rootTable, N.UOffsetTFlags) 464 self.Prep(self.minalign, N.UOffsetTFlags.bytewidth) 465 self.PrependUOffsetTRelative(rootTable) 466 self.finished = True 467 return self.Head() 468 469 ## @cond FLATBUFFERS_INTERNAL 470 def Prepend(self, flags, off): 471 self.Prep(flags.bytewidth, 0) 472 self.Place(off, flags) 473 474 def PrependSlot(self, flags, o, x, d): 475 N.enforce_number(x, flags) 476 N.enforce_number(d, flags) 477 if x != d: 478 self.Prepend(flags, x) 479 self.Slot(o) 480 481 def PrependBoolSlot(self, *args): self.PrependSlot(N.BoolFlags, *args) 482 483 def PrependByteSlot(self, *args): self.PrependSlot(N.Uint8Flags, *args) 484 485 def PrependUint8Slot(self, *args): self.PrependSlot(N.Uint8Flags, *args) 486 487 def PrependUint16Slot(self, *args): self.PrependSlot(N.Uint16Flags, *args) 488 489 def PrependUint32Slot(self, *args): self.PrependSlot(N.Uint32Flags, *args) 490 491 def PrependUint64Slot(self, *args): self.PrependSlot(N.Uint64Flags, *args) 492 493 def PrependInt8Slot(self, *args): self.PrependSlot(N.Int8Flags, *args) 494 495 def PrependInt16Slot(self, *args): self.PrependSlot(N.Int16Flags, *args) 496 497 def PrependInt32Slot(self, *args): self.PrependSlot(N.Int32Flags, *args) 498 499 def PrependInt64Slot(self, *args): self.PrependSlot(N.Int64Flags, *args) 500 501 def PrependFloat32Slot(self, *args): self.PrependSlot(N.Float32Flags, 502 *args) 503 504 def PrependFloat64Slot(self, *args): self.PrependSlot(N.Float64Flags, 505 *args) 506 507 def PrependUOffsetTRelativeSlot(self, o, x, d): 508 """ 509 PrependUOffsetTRelativeSlot prepends an UOffsetT onto the object at 510 vtable slot `o`. If value `x` equals default `d`, then the slot will 511 be set to zero and no other data will be written. 512 """ 513 514 if x != d: 515 self.PrependUOffsetTRelative(x) 516 self.Slot(o) 517 518 def PrependStructSlot(self, v, x, d): 519 """ 520 PrependStructSlot prepends a struct onto the object at vtable slot `o`. 521 Structs are stored inline, so nothing additional is being added. 522 In generated code, `d` is always 0. 523 """ 524 525 N.enforce_number(d, N.UOffsetTFlags) 526 if x != d: 527 self.assertStructIsInline(x) 528 self.Slot(v) 529 530 ## @endcond 531 532 def PrependBool(self, x): 533 """Prepend a `bool` to the Builder buffer. 534 535 Note: aligns and checks for space. 536 """ 537 self.Prepend(N.BoolFlags, x) 538 539 def PrependByte(self, x): 540 """Prepend a `byte` to the Builder buffer. 541 542 Note: aligns and checks for space. 543 """ 544 self.Prepend(N.Uint8Flags, x) 545 546 def PrependUint8(self, x): 547 """Prepend an `uint8` to the Builder buffer. 548 549 Note: aligns and checks for space. 550 """ 551 self.Prepend(N.Uint8Flags, x) 552 553 def PrependUint16(self, x): 554 """Prepend an `uint16` to the Builder buffer. 555 556 Note: aligns and checks for space. 557 """ 558 self.Prepend(N.Uint16Flags, x) 559 560 def PrependUint32(self, x): 561 """Prepend an `uint32` to the Builder buffer. 562 563 Note: aligns and checks for space. 564 """ 565 self.Prepend(N.Uint32Flags, x) 566 567 def PrependUint64(self, x): 568 """Prepend an `uint64` to the Builder buffer. 569 570 Note: aligns and checks for space. 571 """ 572 self.Prepend(N.Uint64Flags, x) 573 574 def PrependInt8(self, x): 575 """Prepend an `int8` to the Builder buffer. 576 577 Note: aligns and checks for space. 578 """ 579 self.Prepend(N.Int8Flags, x) 580 581 def PrependInt16(self, x): 582 """Prepend an `int16` to the Builder buffer. 583 584 Note: aligns and checks for space. 585 """ 586 self.Prepend(N.Int16Flags, x) 587 588 def PrependInt32(self, x): 589 """Prepend an `int32` to the Builder buffer. 590 591 Note: aligns and checks for space. 592 """ 593 self.Prepend(N.Int32Flags, x) 594 595 def PrependInt64(self, x): 596 """Prepend an `int64` to the Builder buffer. 597 598 Note: aligns and checks for space. 599 """ 600 self.Prepend(N.Int64Flags, x) 601 602 def PrependFloat32(self, x): 603 """Prepend a `float32` to the Builder buffer. 604 605 Note: aligns and checks for space. 606 """ 607 self.Prepend(N.Float32Flags, x) 608 609 def PrependFloat64(self, x): 610 """Prepend a `float64` to the Builder buffer. 611 612 Note: aligns and checks for space. 613 """ 614 self.Prepend(N.Float64Flags, x) 615 616############################################################## 617 618 ## @cond FLATBUFFERS_INTERNAL 619 def PrependVOffsetT(self, x): self.Prepend(N.VOffsetTFlags, x) 620 621 def Place(self, x, flags): 622 """ 623 Place prepends a value specified by `flags` to the Builder, 624 without checking for available space. 625 """ 626 627 N.enforce_number(x, flags) 628 self.head = self.head - flags.bytewidth 629 encode.Write(flags.packer_type, self.Bytes, self.Head(), x) 630 631 def PlaceVOffsetT(self, x): 632 """PlaceVOffsetT prepends a VOffsetT to the Builder, without checking 633 for space. 634 """ 635 N.enforce_number(x, N.VOffsetTFlags) 636 self.head = self.head - N.VOffsetTFlags.bytewidth 637 encode.Write(packer.voffset, self.Bytes, self.Head(), x) 638 639 def PlaceSOffsetT(self, x): 640 """PlaceSOffsetT prepends a SOffsetT to the Builder, without checking 641 for space. 642 """ 643 N.enforce_number(x, N.SOffsetTFlags) 644 self.head = self.head - N.SOffsetTFlags.bytewidth 645 encode.Write(packer.soffset, self.Bytes, self.Head(), x) 646 647 def PlaceUOffsetT(self, x): 648 """PlaceUOffsetT prepends a UOffsetT to the Builder, without checking 649 for space. 650 """ 651 N.enforce_number(x, N.UOffsetTFlags) 652 self.head = self.head - N.UOffsetTFlags.bytewidth 653 encode.Write(packer.uoffset, self.Bytes, self.Head(), x) 654 ## @endcond 655 656## @cond FLATBUFFERS_INTERNAL 657def vtableEqual(a, objectStart, b): 658 """vtableEqual compares an unwritten vtable to a written vtable.""" 659 660 N.enforce_number(objectStart, N.UOffsetTFlags) 661 662 if len(a) * N.VOffsetTFlags.bytewidth != len(b): 663 return False 664 665 for i, elem in enumerate(a): 666 x = encode.Get(packer.voffset, b, i * N.VOffsetTFlags.bytewidth) 667 668 # Skip vtable entries that indicate a default value. 669 if x == 0 and elem == 0: 670 pass 671 else: 672 y = objectStart - elem 673 if x != y: 674 return False 675 return True 676## @endcond 677## @} 678