• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2023 Google Inc. All rights reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #if !os(WASI)
18 import Foundation
19 #else
20 import SwiftOverlayShims
21 #endif
22 
23 /// `ByteBuffer` is the interface that stores the data for a `Flatbuffers` object
24 /// it allows users to write and read data directly from memory thus the use of its
25 /// functions should be used
26 @frozen
27 public struct ByteBuffer {
28 
29   /// Storage is a container that would hold the memory pointer to solve the issue of
30   /// deallocating the memory that was held by (memory: UnsafeMutableRawPointer)
31   @usableFromInline
32   final class Storage {
33     // This storage doesn't own the memory, therefore, we won't deallocate on deinit.
34     private let unowned: Bool
35     /// pointer to the start of the buffer object in memory
36     var memory: UnsafeMutableRawPointer
37     /// Capacity of UInt8 the buffer can hold
38     var capacity: Int
39 
40     @usableFromInline
41     init(count: Int, alignment: Int) {
42       memory = UnsafeMutableRawPointer.allocate(
43         byteCount: count,
44         alignment: alignment)
45       capacity = count
46       unowned = false
47     }
48 
49     @usableFromInline
50     init(memory: UnsafeMutableRawPointer, capacity: Int, unowned: Bool) {
51       self.memory = memory
52       self.capacity = capacity
53       self.unowned = unowned
54     }
55 
56     deinit {
57       if !unowned {
58         memory.deallocate()
59       }
60     }
61 
62     @usableFromInline
copynull63     func copy(from ptr: UnsafeRawPointer, count: Int) {
64       assert(
65         !unowned,
66         "copy should NOT be called on a buffer that is built by assumingMemoryBound")
67       memory.copyMemory(from: ptr, byteCount: count)
68     }
69 
70     @usableFromInline
initializenull71     func initialize(for size: Int) {
72       assert(
73         !unowned,
74         "initalize should NOT be called on a buffer that is built by assumingMemoryBound")
75       memset(memory, 0, size)
76     }
77 
78     /// Reallocates the buffer incase the object to be written doesnt fit in the current buffer
79     /// - Parameter size: Size of the current object
80     @usableFromInline
reallocatenull81     func reallocate(_ size: Int, writerSize: Int, alignment: Int) {
82       let currentWritingIndex = capacity &- writerSize
83       while capacity <= writerSize &+ size {
84         capacity = capacity << 1
85       }
86 
87       /// solution take from Apple-NIO
88       capacity = capacity.convertToPowerofTwo
89 
90       let newData = UnsafeMutableRawPointer.allocate(
91         byteCount: capacity,
92         alignment: alignment)
93       memset(newData, 0, capacity &- writerSize)
94       memcpy(
95         newData.advanced(by: capacity &- writerSize),
96         memory.advanced(by: currentWritingIndex),
97         writerSize)
98       memory.deallocate()
99       memory = newData
100     }
101   }
102 
103   @usableFromInline var _storage: Storage
104 
105   /// The size of the elements written to the buffer + their paddings
106   private var _writerSize: Int = 0
107   /// Aliginment of the current  memory being written to the buffer
108   var alignment = 1
109   /// Current Index which is being used to write to the buffer, it is written from the end to the start of the buffer
110   var writerIndex: Int { _storage.capacity &- _writerSize }
111 
112   /// Reader is the position of the current Writer Index (capacity - size)
113   public var reader: Int { writerIndex }
114   /// Current size of the buffer
115   public var size: UOffset { UOffset(_writerSize) }
116   /// Public Pointer to the buffer object in memory. This should NOT be modified for any reason
117   public var memory: UnsafeMutableRawPointer { _storage.memory }
118   /// Current capacity for the buffer
119   public var capacity: Int { _storage.capacity }
120   /// Crash if the trying to read an unaligned buffer instead of allowing users to read them.
121   public let allowReadingUnalignedBuffers: Bool
122 
123   /// Constructor that creates a Flatbuffer object from a UInt8
124   /// - Parameter
125   ///   - bytes: Array of UInt8
126   ///   - allowReadingUnalignedBuffers: allow reading from unaligned buffer
127   public init(
128     bytes: [UInt8],
129     allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false)
130   {
131     var b = bytes
132     _storage = Storage(count: bytes.count, alignment: alignment)
133     _writerSize = _storage.capacity
134     allowReadingUnalignedBuffers = allowUnalignedBuffers
135     b.withUnsafeMutableBytes { bufferPointer in
136       self._storage.copy(from: bufferPointer.baseAddress!, count: bytes.count)
137     }
138   }
139 
140   #if !os(WASI)
141   /// Constructor that creates a Flatbuffer from the Swift Data type object
142   /// - Parameter
143   ///   - data: Swift data Object
144   ///   - allowReadingUnalignedBuffers: allow reading from unaligned buffer
145   public init(
146     data: Data,
147     allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false)
148   {
149     var b = data
150     _storage = Storage(count: data.count, alignment: alignment)
151     _writerSize = _storage.capacity
152     allowReadingUnalignedBuffers = allowUnalignedBuffers
153     b.withUnsafeMutableBytes { bufferPointer in
154       self._storage.copy(from: bufferPointer.baseAddress!, count: data.count)
155     }
156   }
157   #endif
158 
159   /// Constructor that creates a Flatbuffer instance with a size
160   /// - Parameter:
161   ///   - size: Length of the buffer
162   ///   - allowReadingUnalignedBuffers: allow reading from unaligned buffer
163   init(initialSize size: Int) {
164     let size = size.convertToPowerofTwo
165     _storage = Storage(count: size, alignment: alignment)
166     _storage.initialize(for: size)
167     allowReadingUnalignedBuffers = false
168   }
169 
170   #if swift(>=5.0) && !os(WASI)
171   /// Constructor that creates a Flatbuffer object from a ContiguousBytes
172   /// - Parameters:
173   ///   - contiguousBytes: Binary stripe to use as the buffer
174   ///   - count: amount of readable bytes
175   ///   - allowReadingUnalignedBuffers: allow reading from unaligned buffer
176   public init<Bytes: ContiguousBytes>(
177     contiguousBytes: Bytes,
178     count: Int,
179     allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false)
180   {
181     _storage = Storage(count: count, alignment: alignment)
182     _writerSize = _storage.capacity
183     allowReadingUnalignedBuffers = allowUnalignedBuffers
184     contiguousBytes.withUnsafeBytes { buf in
185       _storage.copy(from: buf.baseAddress!, count: buf.count)
186     }
187   }
188   #endif
189 
190   /// Constructor that creates a Flatbuffer from unsafe memory region without copying
191   /// - Parameter:
192   ///   - assumingMemoryBound: The unsafe memory region
193   ///   - capacity: The size of the given memory region
194   ///   - allowReadingUnalignedBuffers: allow reading from unaligned buffer
195   public init(
196     assumingMemoryBound memory: UnsafeMutableRawPointer,
197     capacity: Int,
198     allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false)
199   {
200     _storage = Storage(memory: memory, capacity: capacity, unowned: true)
201     _writerSize = capacity
202     allowReadingUnalignedBuffers = allowUnalignedBuffers
203   }
204 
205   /// Creates a copy of the buffer that's being built by calling sizedBuffer
206   /// - Parameters:
207   ///   - memory: Current memory of the buffer
208   ///   - count: count of bytes
209   ///   - allowReadingUnalignedBuffers: allow reading from unaligned buffer
210   init(
211     memory: UnsafeMutableRawPointer,
212     count: Int,
213     allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false)
214   {
215     _storage = Storage(count: count, alignment: alignment)
216     _storage.copy(from: memory, count: count)
217     _writerSize = _storage.capacity
218     allowReadingUnalignedBuffers = allowUnalignedBuffers
219   }
220 
221   /// Creates a copy of the existing flatbuffer, by copying it to a different memory.
222   /// - Parameters:
223   ///   - memory: Current memory of the buffer
224   ///   - count: count of bytes
225   ///   - removeBytes: Removes a number of bytes from the current size
226   ///   - allowReadingUnalignedBuffers: allow reading from unaligned buffer
227   init(
228     memory: UnsafeMutableRawPointer,
229     count: Int,
230     removing removeBytes: Int,
231     allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false)
232   {
233     _storage = Storage(count: count, alignment: alignment)
234     _storage.copy(from: memory, count: count)
235     _writerSize = removeBytes
236     allowReadingUnalignedBuffers = allowUnalignedBuffers
237   }
238 
239   /// Fills the buffer with padding by adding to the writersize
240   /// - Parameter padding: Amount of padding between two to be serialized objects
241   @inline(__always)
242   @usableFromInline
fillnull243   mutating func fill(padding: Int) {
244     assert(padding >= 0, "Fill should be larger than or equal to zero")
245     ensureSpace(size: padding)
246     _writerSize = _writerSize &+ (MemoryLayout<UInt8>.size &* padding)
247   }
248 
249   /// Adds an array of type Scalar to the buffer memory
250   /// - Parameter elements: An array of Scalars
251   @inline(__always)
252   @usableFromInline
push<T: Scalar>null253   mutating func push<T: Scalar>(elements: [T]) {
254     elements.withUnsafeBytes { ptr in
255       ensureSpace(size: ptr.count)
256       memcpy(
257         _storage.memory.advanced(by: writerIndex &- ptr.count),
258         UnsafeRawPointer(ptr.baseAddress!),
259         ptr.count)
260       self._writerSize = self._writerSize &+ ptr.count
261     }
262   }
263 
264   /// Adds an array of type Scalar to the buffer memory
265   /// - Parameter elements: An array of Scalars
266   @inline(__always)
267   @usableFromInline
push<T: NativeStruct>null268   mutating func push<T: NativeStruct>(elements: [T]) {
269     elements.withUnsafeBytes { ptr in
270       ensureSpace(size: ptr.count)
271       _storage.memory
272         .advanced(by: writerIndex &- ptr.count)
273         .copyMemory(from: ptr.baseAddress!, byteCount: ptr.count)
274       self._writerSize = self._writerSize &+ ptr.count
275     }
276   }
277 
278   /// Adds a `ContiguousBytes` to buffer memory
279   /// - Parameter value: bytes to copy
280   #if swift(>=5.0) && !os(WASI)
281   @inline(__always)
282   @usableFromInline
pushnull283   mutating func push(bytes: ContiguousBytes) {
284     bytes.withUnsafeBytes { ptr in
285       ensureSpace(size: ptr.count)
286       memcpy(
287         _storage.memory.advanced(by: writerIndex &- ptr.count),
288         UnsafeRawPointer(ptr.baseAddress!),
289         ptr.count)
290       self._writerSize = self._writerSize &+ ptr.count
291     }
292   }
293   #endif
294 
295   /// Adds an object of type NativeStruct into the buffer
296   /// - Parameters:
297   ///   - value: Object  that will be written to the buffer
298   ///   - size: size to subtract from the WriterIndex
299   @usableFromInline
300   @inline(__always)
push<T: NativeStruct>null301   mutating func push<T: NativeStruct>(struct value: T, size: Int) {
302     ensureSpace(size: size)
303     var v = value
304     withUnsafeBytes(of: &v) {
305       memcpy(
306         _storage.memory.advanced(by: writerIndex &- size),
307         $0.baseAddress!,
308         size)
309       self._writerSize = self._writerSize &+ size
310     }
311   }
312 
313   /// Adds an object of type Scalar into the buffer
314   /// - Parameters:
315   ///   - value: Object  that will be written to the buffer
316   ///   - len: Offset to subtract from the WriterIndex
317   @inline(__always)
318   @usableFromInline
push<T: Scalar>null319   mutating func push<T: Scalar>(value: T, len: Int) {
320     ensureSpace(size: len)
321     var v = value
322     withUnsafeBytes(of: &v) {
323       memcpy(
324         _storage.memory.advanced(by: writerIndex &- len),
325         $0.baseAddress!,
326         len)
327       self._writerSize = self._writerSize &+ len
328     }
329   }
330 
331   /// Adds a string to the buffer using swift.utf8 object
332   /// - Parameter str: String that will be added to the buffer
333   /// - Parameter len: length of the string
334   @inline(__always)
335   @usableFromInline
pushnull336   mutating func push(string str: String, len: Int) {
337     ensureSpace(size: len)
338     if str.utf8
339       .withContiguousStorageIfAvailable({ self.push(bytes: $0, len: len) }) !=
340       nil
341     {
342     } else {
343       let utf8View = str.utf8
344       for c in utf8View.reversed() {
345         push(value: c, len: 1)
346       }
347     }
348   }
349 
350   /// Writes a string to Bytebuffer using UTF8View
351   /// - Parameters:
352   ///   - bytes: Pointer to the view
353   ///   - len: Size of string
354   @usableFromInline
355   @inline(__always)
356   mutating func push(
357     bytes: UnsafeBufferPointer<String.UTF8View.Element>,
358     len: Int) -> Bool
359   {
360     memcpy(
361       _storage.memory.advanced(by: writerIndex &- len),
362       UnsafeRawPointer(bytes.baseAddress!),
363       len)
364     _writerSize = _writerSize &+ len
365     return true
366   }
367 
368   /// Write stores an object into the buffer directly or indirectly.
369   ///
370   /// Direct: ignores the capacity of buffer which would mean we are referring to the direct point in memory
371   /// indirect: takes into respect the current capacity of the buffer (capacity - index), writing to the buffer from the end
372   /// - Parameters:
373   ///   - value: Value that needs to be written to the buffer
374   ///   - index: index to write to
375   ///   - direct: Should take into consideration the capacity of the buffer
376   @inline(__always)
write<T>null377   func write<T>(value: T, index: Int, direct: Bool = false) {
378     var index = index
379     if !direct {
380       index = _storage.capacity &- index
381     }
382     assert(index < _storage.capacity, "Write index is out of writing bound")
383     assert(index >= 0, "Writer index should be above zero")
384     _storage.memory.storeBytes(of: value, toByteOffset: index, as: T.self)
385   }
386 
387   /// Makes sure that buffer has enouch space for each of the objects that will be written into it
388   /// - Parameter size: size of object
389   @discardableResult
390   @usableFromInline
391   @inline(__always)
ensureSpacenull392   mutating func ensureSpace(size: Int) -> Int {
393     if size &+ _writerSize > _storage.capacity {
394       _storage.reallocate(size, writerSize: _writerSize, alignment: alignment)
395     }
396     assert(size < FlatBufferMaxSize, "Buffer can't grow beyond 2 Gigabytes")
397     return size
398   }
399 
400   /// pops the written VTable if it's already written into the buffer
401   /// - Parameter size: size of the `VTable`
402   @usableFromInline
403   @inline(__always)
popnull404   mutating func pop(_ size: Int) {
405     assert(
406       (_writerSize &- size) > 0,
407       "New size should NOT be a negative number")
408     memset(_storage.memory.advanced(by: writerIndex), 0, _writerSize &- size)
409     _writerSize = size
410   }
411 
412   /// Clears the current size of the buffer
413   @inline(__always)
clearSizenull414   mutating public func clearSize() {
415     _writerSize = 0
416   }
417 
418   /// Clears the current instance of the buffer, replacing it with new memory
419   @inline(__always)
clearnull420   mutating public func clear() {
421     _writerSize = 0
422     alignment = 1
423     _storage.initialize(for: _storage.capacity)
424   }
425 
426   /// Reads an object from the buffer
427   /// - Parameters:
428   ///   - def: Type of the object
429   ///   - position: the index of the object in the buffer
430   @inline(__always)
read<T>null431   public func read<T>(def: T.Type, position: Int) -> T {
432     #if swift(>=5.7)
433     if allowReadingUnalignedBuffers {
434       return _storage.memory.advanced(by: position).loadUnaligned(as: T.self)
435     }
436     #endif
437     return _storage.memory.advanced(by: position).load(as: T.self)
438   }
439 
440   /// Reads a slice from the memory assuming a type of T
441   /// - Parameters:
442   ///   - index: index of the object to be read from the buffer
443   ///   - count: count of bytes in memory
444   @inline(__always)
445   public func readSlice<T>(
446     index: Int,
447     count: Int) -> [T]
448   {
449     assert(
450       index + count <= _storage.capacity,
451       "Reading out of bounds is illegal")
452     let start = _storage.memory.advanced(by: index)
453       .assumingMemoryBound(to: T.self)
454     let array = UnsafeBufferPointer(start: start, count: count)
455     return Array(array)
456   }
457 
458   #if !os(WASI)
459   /// Reads a string from the buffer and encodes it to a swift string
460   /// - Parameters:
461   ///   - index: index of the string in the buffer
462   ///   - count: length of the string
463   ///   - type: Encoding of the string
464   @inline(__always)
465   public func readString(
466     at index: Int,
467     count: Int,
468     type: String.Encoding = .utf8) -> String?
469   {
470     assert(
471       index + count <= _storage.capacity,
472       "Reading out of bounds is illegal")
473     let start = _storage.memory.advanced(by: index)
474       .assumingMemoryBound(to: UInt8.self)
475     let bufprt = UnsafeBufferPointer(start: start, count: count)
476     return String(bytes: Array(bufprt), encoding: type)
477   }
478   #else
479   /// Reads a string from the buffer and encodes it to a swift string
480   /// - Parameters:
481   ///   - index: index of the string in the buffer
482   ///   - count: length of the string
483   ///   - type: Encoding of the string
484   @inline(__always)
485   public func readString(
486     at index: Int,
487     count: Int) -> String?
488   {
489     assert(
490       index + count <= _storage.capacity,
491       "Reading out of bounds is illegal")
492     let start = _storage.memory.advanced(by: index)
493       .assumingMemoryBound(to: UInt8.self)
494     let bufprt = UnsafeBufferPointer(start: start, count: count)
495     return String(cString: bufprt.baseAddress!)
496   }
497   #endif
498 
499   /// Creates a new Flatbuffer object that's duplicated from the current one
500   /// - Parameter removeBytes: the amount of bytes to remove from the current Size
501   @inline(__always)
duplicatenull502   public func duplicate(removing removeBytes: Int = 0) -> ByteBuffer {
503     assert(removeBytes > 0, "Can NOT remove negative bytes")
504     assert(
505       removeBytes < _storage.capacity,
506       "Can NOT remove more bytes than the ones allocated")
507     return ByteBuffer(
508       memory: _storage.memory,
509       count: _storage.capacity,
510       removing: _writerSize &- removeBytes)
511   }
512 
513   /// Returns the written bytes into the ``ByteBuffer``
514   public var underlyingBytes: [UInt8] {
515     let cp = capacity &- writerIndex
516     let start = memory.advanced(by: writerIndex)
517       .bindMemory(to: UInt8.self, capacity: cp)
518 
519     let ptr = UnsafeBufferPointer<UInt8>(start: start, count: cp)
520     return Array(ptr)
521   }
522 
523   /// SkipPrefix Skips the first 4 bytes in case one of the following
524   /// functions are called `getPrefixedSizeCheckedRoot` & `getPrefixedSizeRoot`
525   /// which allows us to skip the first 4 bytes instead of recreating the buffer
526   @discardableResult
527   @usableFromInline
528   @inline(__always)
skipPrefixnull529   mutating func skipPrefix() -> Int32 {
530     _writerSize = _writerSize &- MemoryLayout<Int32>.size
531     return read(def: Int32.self, position: 0)
532   }
533 
534 }
535 
536 extension ByteBuffer: CustomDebugStringConvertible {
537 
538   public var debugDescription: String {
539     """
540     buffer located at: \(_storage.memory), with capacity of \(_storage.capacity)
541     { writerSize: \(_writerSize), readerSize: \(reader), writerIndex: \(writerIndex) }
542     """
543   }
544 }
545