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