• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 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 import Foundation
18 
19 @frozen
20 public struct ByteBuffer {
21 
22   /// Storage is a container that would hold the memory pointer to solve the issue of
23   /// deallocating the memory that was held by (memory: UnsafeMutableRawPointer)
24   @usableFromInline
25   final class Storage {
26     // This storage doesn't own the memory, therefore, we won't deallocate on deinit.
27     private let unowned: Bool
28     /// pointer to the start of the buffer object in memory
29     var memory: UnsafeMutableRawPointer
30     /// Capacity of UInt8 the buffer can hold
31     var capacity: Int
32 
33     @usableFromInline
34     init(count: Int, alignment: Int) {
35       memory = UnsafeMutableRawPointer.allocate(byteCount: count, alignment: alignment)
36       capacity = count
37       unowned = false
38     }
39 
40     @usableFromInline
41     init(memory: UnsafeMutableRawPointer, capacity: Int, unowned: Bool) {
42       self.memory = memory
43       self.capacity = capacity
44       self.unowned = unowned
45     }
46 
47     deinit {
48       if !unowned {
49         memory.deallocate()
50       }
51     }
52 
53     @usableFromInline
copynull54     func copy(from ptr: UnsafeRawPointer, count: Int) {
55       assert(
56         !unowned,
57         "copy should NOT be called on a buffer that is built by assumingMemoryBound")
58       memory.copyMemory(from: ptr, byteCount: count)
59     }
60 
61     @usableFromInline
initializenull62     func initialize(for size: Int) {
63       assert(
64         !unowned,
65         "initalize should NOT be called on a buffer that is built by assumingMemoryBound")
66       memset(memory, 0, size)
67     }
68 
69     /// Reallocates the buffer incase the object to be written doesnt fit in the current buffer
70     /// - Parameter size: Size of the current object
71     @usableFromInline
reallocatenull72     internal func reallocate(_ size: Int, writerSize: Int, alignment: Int) {
73       let currentWritingIndex = capacity &- writerSize
74       while capacity <= writerSize &+ size {
75         capacity = capacity << 1
76       }
77 
78       /// solution take from Apple-NIO
79       capacity = capacity.convertToPowerofTwo
80 
81       let newData = UnsafeMutableRawPointer.allocate(byteCount: capacity, alignment: alignment)
82       memset(newData, 0, capacity &- writerSize)
83       memcpy(
84         newData.advanced(by: capacity &- writerSize),
85         memory.advanced(by: currentWritingIndex),
86         writerSize)
87       memory.deallocate()
88       memory = newData
89     }
90   }
91 
92   @usableFromInline var _storage: Storage
93 
94   /// The size of the elements written to the buffer + their paddings
95   private var _writerSize: Int = 0
96   /// Aliginment of the current  memory being written to the buffer
97   internal var alignment = 1
98   /// Current Index which is being used to write to the buffer, it is written from the end to the start of the buffer
99   internal var writerIndex: Int { _storage.capacity &- _writerSize }
100 
101   /// Reader is the position of the current Writer Index (capacity - size)
102   public var reader: Int { writerIndex }
103   /// Current size of the buffer
104   public var size: UOffset { UOffset(_writerSize) }
105   /// Public Pointer to the buffer object in memory. This should NOT be modified for any reason
106   public var memory: UnsafeMutableRawPointer { _storage.memory }
107   /// Current capacity for the buffer
108   public var capacity: Int { _storage.capacity }
109 
110   /// Constructor that creates a Flatbuffer object from a UInt8
111   /// - Parameter bytes: Array of UInt8
112   public init(bytes: [UInt8]) {
113     var b = bytes
114     _storage = Storage(count: bytes.count, alignment: alignment)
115     _writerSize = _storage.capacity
116     b.withUnsafeMutableBytes { bufferPointer in
117       self._storage.copy(from: bufferPointer.baseAddress!, count: bytes.count)
118     }
119   }
120 
121   /// Constructor that creates a Flatbuffer from the Swift Data type object
122   /// - Parameter data: Swift data Object
123   public init(data: Data) {
124     var b = data
125     _storage = Storage(count: data.count, alignment: alignment)
126     _writerSize = _storage.capacity
127     b.withUnsafeMutableBytes { bufferPointer in
128       self._storage.copy(from: bufferPointer.baseAddress!, count: data.count)
129     }
130   }
131 
132   /// Constructor that creates a Flatbuffer instance with a size
133   /// - Parameter size: Length of the buffer
134   init(initialSize size: Int) {
135     let size = size.convertToPowerofTwo
136     _storage = Storage(count: size, alignment: alignment)
137     _storage.initialize(for: size)
138   }
139 
140   #if swift(>=5.0)
141   /// Constructor that creates a Flatbuffer object from a ContiguousBytes
142   /// - Parameters:
143   ///   - contiguousBytes: Binary stripe to use as the buffer
144   ///   - count: amount of readable bytes
145   public init<Bytes: ContiguousBytes>(
146     contiguousBytes: Bytes,
147     count: Int)
148   {
149     _storage = Storage(count: count, alignment: alignment)
150     _writerSize = _storage.capacity
151     contiguousBytes.withUnsafeBytes { buf in
152       _storage.copy(from: buf.baseAddress!, count: buf.count)
153     }
154   }
155   #endif
156 
157   /// Constructor that creates a Flatbuffer from unsafe memory region without copying
158   /// - Parameter assumingMemoryBound: The unsafe memory region
159   /// - Parameter capacity: The size of the given memory region
160   public init(assumingMemoryBound memory: UnsafeMutableRawPointer, capacity: Int) {
161     _storage = Storage(memory: memory, capacity: capacity, unowned: true)
162     _writerSize = capacity
163   }
164 
165   /// Creates a copy of the buffer that's being built by calling sizedBuffer
166   /// - Parameters:
167   ///   - memory: Current memory of the buffer
168   ///   - count: count of bytes
169   internal init(memory: UnsafeMutableRawPointer, count: Int) {
170     _storage = Storage(count: count, alignment: alignment)
171     _storage.copy(from: memory, count: count)
172     _writerSize = _storage.capacity
173   }
174 
175   /// Creates a copy of the existing flatbuffer, by copying it to a different memory.
176   /// - Parameters:
177   ///   - memory: Current memory of the buffer
178   ///   - count: count of bytes
179   ///   - removeBytes: Removes a number of bytes from the current size
180   internal init(memory: UnsafeMutableRawPointer, count: Int, removing removeBytes: Int) {
181     _storage = Storage(count: count, alignment: alignment)
182     _storage.copy(from: memory, count: count)
183     _writerSize = removeBytes
184   }
185 
186   /// Fills the buffer with padding by adding to the writersize
187   /// - Parameter padding: Amount of padding between two to be serialized objects
188   @usableFromInline
fillnull189   mutating func fill(padding: Int) {
190     assert(padding >= 0, "Fill should be larger than or equal to zero")
191     ensureSpace(size: padding)
192     _writerSize = _writerSize &+ (MemoryLayout<UInt8>.size &* padding)
193   }
194 
195   /// Adds an array of type Scalar to the buffer memory
196   /// - Parameter elements: An array of Scalars
197   @usableFromInline
push<T: Scalar>null198   mutating func push<T: Scalar>(elements: [T]) {
199     let size = elements.count &* MemoryLayout<T>.size
200     ensureSpace(size: size)
201     elements.reversed().forEach { s in
202       push(value: s, len: MemoryLayout.size(ofValue: s))
203     }
204   }
205 
206   /// Adds an object of type NativeStruct into the buffer
207   /// - Parameters:
208   ///   - value: Object  that will be written to the buffer
209   ///   - size: size to subtract from the WriterIndex
210   @inline(__always)
push<T: NativeStruct>null211   mutating func push<T: NativeStruct>(struct value: T, size: Int) {
212     ensureSpace(size: size)
213     var v = value
214     memcpy(_storage.memory.advanced(by: writerIndex &- size), &v, size)
215     _writerSize = _writerSize &+ size
216   }
217 
218   /// Adds an object of type Scalar into the buffer
219   /// - Parameters:
220   ///   - value: Object  that will be written to the buffer
221   ///   - len: Offset to subtract from the WriterIndex
222   @usableFromInline
push<T: Scalar>null223   mutating func push<T: Scalar>(value: T, len: Int) {
224     ensureSpace(size: len)
225     var v = value
226     memcpy(_storage.memory.advanced(by: writerIndex &- len), &v, len)
227     _writerSize = _writerSize &+ len
228   }
229 
230   /// Adds a string to the buffer using swift.utf8 object
231   /// - Parameter str: String that will be added to the buffer
232   /// - Parameter len: length of the string
233   @usableFromInline
pushnull234   mutating func push(string str: String, len: Int) {
235     ensureSpace(size: len)
236     if str.utf8.withContiguousStorageIfAvailable({ self.push(bytes: $0, len: len) }) != nil {
237     } else {
238       let utf8View = str.utf8
239       for c in utf8View.reversed() {
240         push(value: c, len: 1)
241       }
242     }
243   }
244 
245   /// Writes a string to Bytebuffer using UTF8View
246   /// - Parameters:
247   ///   - bytes: Pointer to the view
248   ///   - len: Size of string
249   @inline(__always)
250   mutating internal func push(
251     bytes: UnsafeBufferPointer<String.UTF8View.Element>,
252     len: Int) -> Bool
253   {
254     memcpy(
255       _storage.memory.advanced(by: writerIndex &- len),
256       UnsafeRawPointer(bytes.baseAddress!),
257       len)
258     _writerSize = _writerSize &+ len
259     return true
260   }
261 
262   /// Write stores an object into the buffer directly or indirectly.
263   ///
264   /// Direct: ignores the capacity of buffer which would mean we are referring to the direct point in memory
265   /// indirect: takes into respect the current capacity of the buffer (capacity - index), writing to the buffer from the end
266   /// - Parameters:
267   ///   - value: Value that needs to be written to the buffer
268   ///   - index: index to write to
269   ///   - direct: Should take into consideration the capacity of the buffer
write<T>null270   func write<T>(value: T, index: Int, direct: Bool = false) {
271     var index = index
272     if !direct {
273       index = _storage.capacity &- index
274     }
275     assert(index < _storage.capacity, "Write index is out of writing bound")
276     assert(index >= 0, "Writer index should be above zero")
277     _storage.memory.storeBytes(of: value, toByteOffset: index, as: T.self)
278   }
279 
280   /// Makes sure that buffer has enouch space for each of the objects that will be written into it
281   /// - Parameter size: size of object
282   @discardableResult
283   @inline(__always)
ensureSpacenull284   mutating func ensureSpace(size: Int) -> Int {
285     if size &+ _writerSize > _storage.capacity {
286       _storage.reallocate(size, writerSize: _writerSize, alignment: alignment)
287     }
288     assert(size < FlatBufferMaxSize, "Buffer can't grow beyond 2 Gigabytes")
289     return size
290   }
291 
292   /// pops the written VTable if it's already written into the buffer
293   /// - Parameter size: size of the `VTable`
294   @inline(__always)
popnull295   mutating internal func pop(_ size: Int) {
296     assert((_writerSize &- size) > 0, "New size should NOT be a negative number")
297     memset(_storage.memory.advanced(by: writerIndex), 0, _writerSize &- size)
298     _writerSize = size
299   }
300 
301   /// Clears the current size of the buffer
302   @inline(__always)
clearSizenull303   mutating public func clearSize() {
304     _writerSize = 0
305   }
306 
307   /// Clears the current instance of the buffer, replacing it with new memory
308   @inline(__always)
clearnull309   mutating public func clear() {
310     _writerSize = 0
311     alignment = 1
312     _storage.initialize(for: _storage.capacity)
313   }
314 
315   /// Reads an object from the buffer
316   /// - Parameters:
317   ///   - def: Type of the object
318   ///   - position: the index of the object in the buffer
read<T>null319   public func read<T>(def: T.Type, position: Int) -> T {
320     assert(
321       position + MemoryLayout<T>.size <= _storage.capacity,
322       "Reading out of bounds is illegal")
323     return _storage.memory.advanced(by: position).load(as: T.self)
324   }
325 
326   /// Reads a slice from the memory assuming a type of T
327   /// - Parameters:
328   ///   - index: index of the object to be read from the buffer
329   ///   - count: count of bytes in memory
330   @inline(__always)
331   public func readSlice<T>(
332     index: Int32,
333     count: Int32) -> [T]
334   {
335     let _index = Int(index)
336     let _count = Int(count)
337     assert(_index + _count <= _storage.capacity, "Reading out of bounds is illegal")
338     let start = _storage.memory.advanced(by: _index).assumingMemoryBound(to: T.self)
339     let array = UnsafeBufferPointer(start: start, count: _count)
340     return Array(array)
341   }
342 
343   /// Reads a string from the buffer and encodes it to a swift string
344   /// - Parameters:
345   ///   - index: index of the string in the buffer
346   ///   - count: length of the string
347   ///   - type: Encoding of the string
348   @inline(__always)
349   public func readString(
350     at index: Int32,
351     count: Int32,
352     type: String.Encoding = .utf8) -> String?
353   {
354     let _index = Int(index)
355     let _count = Int(count)
356     assert(_index + _count <= _storage.capacity, "Reading out of bounds is illegal")
357     let start = _storage.memory.advanced(by: _index).assumingMemoryBound(to: UInt8.self)
358     let bufprt = UnsafeBufferPointer(start: start, count: _count)
359     return String(bytes: Array(bufprt), encoding: type)
360   }
361 
362   /// Creates a new Flatbuffer object that's duplicated from the current one
363   /// - Parameter removeBytes: the amount of bytes to remove from the current Size
duplicatenull364   public func duplicate(removing removeBytes: Int = 0) -> ByteBuffer {
365     assert(removeBytes > 0, "Can NOT remove negative bytes")
366     assert(removeBytes < _storage.capacity, "Can NOT remove more bytes than the ones allocated")
367     return ByteBuffer(
368       memory: _storage.memory,
369       count: _storage.capacity,
370       removing: _writerSize &- removeBytes)
371   }
372 }
373 
374 extension ByteBuffer: CustomDebugStringConvertible {
375 
376   public var debugDescription: String {
377     """
378     buffer located at: \(_storage.memory), with capacity of \(_storage.capacity)
379     { writerSize: \(_writerSize), readerSize: \(reader), writerIndex: \(writerIndex) }
380     """
381   }
382 }
383