• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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