• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 import Foundation
2 
3 public final class ByteBuffer {
4 
5     /// pointer to the start of the buffer object in memory
6     private var _memory: UnsafeMutableRawPointer
7     /// The size of the elements written to the buffer + their paddings
8     private var _writerSize: Int = 0
9     /// Capacity of UInt8 the buffer can hold
10     private var _capacity: Int
11 
12     /// Aliginment of the current  memory being written to the buffer
13     internal var alignment = 1
14     /// Current Index which is being used to write to the buffer, it is written from the end to the start of the buffer
15     internal var writerIndex: Int { return _capacity - _writerSize }
16 
17     /// Reader is the position of the current Writer Index (capacity - size)
18     public var reader: Int { return writerIndex }
19     /// Current size of the buffer
20     public var size: UOffset { return UOffset(_writerSize) }
21     /// Public Pointer to the buffer object in memory. This should NOT be modified for any reason
22     public var memory: UnsafeMutableRawPointer { return _memory }
23     /// Current capacity for the buffer
24     public var capacity: Int { return _capacity }
25 
26     /// Constructor that creates a Flatbuffer object from a UInt8
27     /// - Parameter bytes: Array of UInt8
28     public init(bytes: [UInt8]) {
29         let ptr = UnsafePointer(bytes)
30         _memory = UnsafeMutableRawPointer.allocate(byteCount: bytes.count, alignment: alignment)
31         _memory.copyMemory(from: ptr, byteCount: bytes.count)
32         _capacity = bytes.count
33         _writerSize = _capacity
34     }
35 
36     /// Constructor that creates a Flatbuffer from the Swift Data type object
37     /// - Parameter data: Swift data Object
38     public init(data: Data) {
39         let pointer = UnsafeMutablePointer<UInt8>.allocate(capacity: data.count)
40         data.copyBytes(to: pointer, count: data.count)
41         _memory = UnsafeMutableRawPointer(pointer)
42         _capacity = data.count
43         _writerSize = _capacity
44     }
45 
46     /// Constructor that creates a Flatbuffer instance with a size
47     /// - Parameter size: Length of the buffer
48     init(initialSize size: Int) {
49         let size = size.convertToPowerofTwo
50         _memory = UnsafeMutableRawPointer.allocate(byteCount: size, alignment: alignment)
51         _memory.initializeMemory(as: UInt8.self, repeating: 0, count: size)
52         _capacity = size
53     }
54 
55 #if swift(>=5.0)
56     /// Constructor that creates a Flatbuffer object from a ContiguousBytes
57     /// - Parameters:
58     ///   - contiguousBytes: Binary stripe to use as the buffer
59     ///   - count: amount of readable bytes
60     public init<Bytes: ContiguousBytes>(
61         contiguousBytes: Bytes,
62         count: Int
63     ) {
64         _memory = UnsafeMutableRawPointer.allocate(byteCount: count, alignment: alignment)
65         _capacity = count
66         _writerSize = _capacity
67         contiguousBytes.withUnsafeBytes { buf in
68             _memory.copyMemory(from: buf.baseAddress!, byteCount: buf.count)
69         }
70     }
71 #endif
72 
73     /// Creates a copy of the buffer that's being built by calling sizedBuffer
74     /// - Parameters:
75     ///   - memory: Current memory of the buffer
76     ///   - count: count of bytes
77     internal init(memory: UnsafeMutableRawPointer, count: Int) {
78         _memory = UnsafeMutableRawPointer.allocate(byteCount: count, alignment: alignment)
79         _memory.copyMemory(from: memory, byteCount: count)
80         _capacity = count
81         _writerSize = _capacity
82     }
83 
84     /// Creates a copy of the existing flatbuffer, by copying it to a different memory.
85     /// - Parameters:
86     ///   - memory: Current memory of the buffer
87     ///   - count: count of bytes
88     ///   - removeBytes: Removes a number of bytes from the current size
89     internal init(memory: UnsafeMutableRawPointer, count: Int, removing removeBytes: Int) {
90         _memory = UnsafeMutableRawPointer.allocate(byteCount: count, alignment: alignment)
91         _memory.copyMemory(from: memory, byteCount: count)
92         _capacity = count
93         _writerSize = removeBytes
94     }
95 
96     deinit { _memory.deallocate() }
97 
98     /// Fills the buffer with padding by adding to the writersize
99     /// - Parameter padding: Amount of padding between two to be serialized objects
fillnull100     func fill(padding: UInt32) {
101         ensureSpace(size: padding)
102         _writerSize += (MemoryLayout<UInt8>.size * Int(padding))
103     }
104 
105     ///Adds an array of type Scalar to the buffer memory
106     /// - Parameter elements: An array of Scalars
push<T: Scalar>null107     func push<T: Scalar>(elements: [T]) {
108         let size = elements.count * MemoryLayout<T>.size
109         ensureSpace(size: UInt32(size))
110         elements.lazy.reversed().forEach { (s) in
111             push(value: s, len: MemoryLayout.size(ofValue: s))
112         }
113     }
114 
115     /// A custom type of structs that are padded according to the flatbuffer padding,
116     /// - Parameters:
117     ///   - value: Pointer to the object in memory
118     ///   - size: Size of Value being written to the buffer
pushnull119     func push(struct value: UnsafeMutableRawPointer, size: Int) {
120         ensureSpace(size: UInt32(size))
121         memcpy(_memory.advanced(by: writerIndex - size), value, size)
122         defer { value.deallocate() }
123         _writerSize += size
124     }
125 
126     /// Adds an object of type Scalar into the buffer
127     /// - Parameters:
128     ///   - value: Object  that will be written to the buffer
129     ///   - len: Offset to subtract from the WriterIndex
push<T: Scalar>null130     func push<T: Scalar>(value: T, len: Int) {
131         ensureSpace(size: UInt32(len))
132         var v = value.convertedEndian
133         memcpy(_memory.advanced(by: writerIndex - len), &v, len)
134         _writerSize += len
135     }
136 
137     /// Adds a string to the buffer using swift.utf8 object
138     /// - Parameter str: String that will be added to the buffer
139     /// - Parameter len: length of the string
pushnull140     func push(string str: String, len: Int) {
141         ensureSpace(size: UInt32(len))
142         if str.utf8.withContiguousStorageIfAvailable({ self.push(bytes: $0, len: len) }) != nil {
143         } else {
144             let utf8View = str.utf8
145             for c in utf8View.lazy.reversed() {
146                 push(value: c, len: 1)
147             }
148         }
149     }
150 
151     /// Writes a string to Bytebuffer using UTF8View
152     /// - Parameters:
153     ///   - bytes: Pointer to the view
154     ///   - len: Size of string
pushnull155     private func push(bytes: UnsafeBufferPointer<String.UTF8View.Element>, len: Int) -> Bool {
156         _memory.advanced(by: writerIndex - len).copyMemory(from:
157             UnsafeRawPointer(bytes.baseAddress!), byteCount: len)
158         _writerSize += len
159         return true
160     }
161 
162     /// Write stores an object into the buffer directly or indirectly.
163     ///
164     /// Direct: ignores the capacity of buffer which would mean we are referring to the direct point in memory
165     /// indirect: takes into respect the current capacity of the buffer (capacity - index), writing to the buffer from the end
166     /// - Parameters:
167     ///   - value: Value that needs to be written to the buffer
168     ///   - index: index to write to
169     ///   - direct: Should take into consideration the capacity of the buffer
write<T>null170     func write<T>(value: T, index: Int, direct: Bool = false) {
171         var index = index
172         if !direct {
173             index = _capacity - index
174         }
175         _memory.storeBytes(of: value, toByteOffset: index, as: T.self)
176     }
177 
178     /// Makes sure that buffer has enouch space for each of the objects that will be written into it
179     /// - Parameter size: size of object
180     @discardableResult
ensureSpacenull181     func ensureSpace(size: UInt32) -> UInt32 {
182         if Int(size) + _writerSize > _capacity { reallocate(size) }
183         assert(size < FlatBufferMaxSize, "Buffer can't grow beyond 2 Gigabytes")
184         return size
185     }
186 
187     /// Reallocates the buffer incase the object to be written doesnt fit in the current buffer
188     /// - Parameter size: Size of the current object
reallocatenull189     fileprivate func reallocate(_ size: UInt32) {
190         let currentWritingIndex = writerIndex
191         while _capacity <= _writerSize + Int(size) {
192             _capacity = _capacity << 1
193         }
194 
195         /// solution take from Apple-NIO
196         _capacity = _capacity.convertToPowerofTwo
197 
198         let newData = UnsafeMutableRawPointer.allocate(byteCount: _capacity, alignment: alignment)
199         newData.initializeMemory(as: UInt8.self, repeating: 0, count: _capacity)
200         newData
201             .advanced(by: writerIndex)
202             .copyMemory(from: _memory.advanced(by: currentWritingIndex), byteCount: _writerSize)
203         _memory.deallocate()
204         _memory = newData
205     }
206 
207     /// Clears the current size of the buffer
clearSizenull208     public func clearSize() {
209         _writerSize = 0
210     }
211 
212     /// Clears the current instance of the buffer, replacing it with new memory
clearnull213     public func clear() {
214         _writerSize = 0
215         alignment = 1
216         _memory.deallocate()
217         _memory = UnsafeMutableRawPointer.allocate(byteCount: _capacity, alignment: alignment)
218     }
219 
220     /// Resizes the buffer size
221     /// - Parameter size: new size for the buffer
resizenull222     internal func resize(_ size: Int) {
223         _writerSize = size
224     }
225 
226     /// Reads an object from the buffer
227     /// - Parameters:
228     ///   - def: Type of the object
229     ///   - position: the index of the object in the buffer
read<T>null230     public func read<T>(def: T.Type, position: Int) -> T {
231         return _memory.advanced(by: position).load(as: T.self)
232     }
233 
234     /// Reads a slice from the memory assuming a type of T
235     /// - Parameters:
236     ///   - index: index of the object to be read from the buffer
237     ///   - count: count of bytes in memory
238     public func readSlice<T>(index: Int32,
239                              count: Int32) -> [T] {
240         let start = _memory.advanced(by: Int(index)).assumingMemoryBound(to: T.self)
241         let array = UnsafeBufferPointer(start: start, count: Int(count))
242         return Array(array)
243     }
244 
245     /// Reads a string from the buffer and encodes it to a swift string
246     /// - Parameters:
247     ///   - index: index of the string in the buffer
248     ///   - count: length of the string
249     ///   - type: Encoding of the string
250     public func readString(at index: Int32,
251                            count: Int32,
252                            type: String.Encoding = .utf8) -> String? {
253         let start = _memory.advanced(by: Int(index)).assumingMemoryBound(to: UInt8.self)
254         let bufprt = UnsafeBufferPointer(start: start, count: Int(count))
255         return String(bytes: Array(bufprt), encoding: type)
256     }
257 
258     /// Creates a new Flatbuffer object that's duplicated from the current one
259     /// - Parameter removeBytes: the amount of bytes to remove from the current Size
duplicatenull260     public func duplicate(removing removeBytes: Int = 0) -> ByteBuffer {
261         return ByteBuffer(memory: _memory, count: _capacity, removing: _writerSize - removeBytes)
262     }
263 }
264 
265 extension ByteBuffer: CustomDebugStringConvertible {
266 
267     public var debugDescription: String {
268         """
269         buffer located at: \(_memory), with capacity of \(_capacity)
270         { writerSize: \(_writerSize), readerSize: \(reader), writerIndex: \(writerIndex) }
271         """
272     }
273 }
274