ByteBuffer.swift (15540B)
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 63 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 71 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 81 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 121 /// Constructor that creates a Flatbuffer object from a UInt8 122 /// - Parameter bytes: Array of UInt8 123 public init(bytes: [UInt8]) { 124 var b = bytes 125 _storage = Storage(count: bytes.count, alignment: alignment) 126 _writerSize = _storage.capacity 127 b.withUnsafeMutableBytes { bufferPointer in 128 self._storage.copy(from: bufferPointer.baseAddress!, count: bytes.count) 129 } 130 } 131 132 #if !os(WASI) 133 /// Constructor that creates a Flatbuffer from the Swift Data type object 134 /// - Parameter data: Swift data Object 135 public init(data: Data) { 136 var b = data 137 _storage = Storage(count: data.count, alignment: alignment) 138 _writerSize = _storage.capacity 139 b.withUnsafeMutableBytes { bufferPointer in 140 self._storage.copy(from: bufferPointer.baseAddress!, count: data.count) 141 } 142 } 143 #endif 144 145 /// Constructor that creates a Flatbuffer instance with a size 146 /// - Parameter size: Length of the buffer 147 init(initialSize size: Int) { 148 let size = size.convertToPowerofTwo 149 _storage = Storage(count: size, alignment: alignment) 150 _storage.initialize(for: size) 151 } 152 153 #if swift(>=5.0) && !os(WASI) 154 /// Constructor that creates a Flatbuffer object from a ContiguousBytes 155 /// - Parameters: 156 /// - contiguousBytes: Binary stripe to use as the buffer 157 /// - count: amount of readable bytes 158 public init<Bytes: ContiguousBytes>( 159 contiguousBytes: Bytes, 160 count: Int) 161 { 162 _storage = Storage(count: count, alignment: alignment) 163 _writerSize = _storage.capacity 164 contiguousBytes.withUnsafeBytes { buf in 165 _storage.copy(from: buf.baseAddress!, count: buf.count) 166 } 167 } 168 #endif 169 170 /// Constructor that creates a Flatbuffer from unsafe memory region without copying 171 /// - Parameter assumingMemoryBound: The unsafe memory region 172 /// - Parameter capacity: The size of the given memory region 173 public init( 174 assumingMemoryBound memory: UnsafeMutableRawPointer, 175 capacity: Int) 176 { 177 _storage = Storage(memory: memory, capacity: capacity, unowned: true) 178 _writerSize = capacity 179 } 180 181 /// Creates a copy of the buffer that's being built by calling sizedBuffer 182 /// - Parameters: 183 /// - memory: Current memory of the buffer 184 /// - count: count of bytes 185 init(memory: UnsafeMutableRawPointer, count: Int) { 186 _storage = Storage(count: count, alignment: alignment) 187 _storage.copy(from: memory, count: count) 188 _writerSize = _storage.capacity 189 } 190 191 /// Creates a copy of the existing flatbuffer, by copying it to a different memory. 192 /// - Parameters: 193 /// - memory: Current memory of the buffer 194 /// - count: count of bytes 195 /// - removeBytes: Removes a number of bytes from the current size 196 init( 197 memory: UnsafeMutableRawPointer, 198 count: Int, 199 removing removeBytes: Int) 200 { 201 _storage = Storage(count: count, alignment: alignment) 202 _storage.copy(from: memory, count: count) 203 _writerSize = removeBytes 204 } 205 206 /// Fills the buffer with padding by adding to the writersize 207 /// - Parameter padding: Amount of padding between two to be serialized objects 208 @inline(__always) 209 @usableFromInline 210 mutating func fill(padding: Int) { 211 assert(padding >= 0, "Fill should be larger than or equal to zero") 212 ensureSpace(size: padding) 213 _writerSize = _writerSize &+ (MemoryLayout<UInt8>.size &* padding) 214 } 215 216 /// Adds an array of type Scalar to the buffer memory 217 /// - Parameter elements: An array of Scalars 218 @inline(__always) 219 @usableFromInline 220 mutating func push<T: Scalar>(elements: [T]) { 221 let size = elements.count &* MemoryLayout<T>.size 222 ensureSpace(size: size) 223 elements.reversed().forEach { s in 224 push(value: s, len: MemoryLayout.size(ofValue: s)) 225 } 226 } 227 228 /// Adds an object of type NativeStruct into the buffer 229 /// - Parameters: 230 /// - value: Object that will be written to the buffer 231 /// - size: size to subtract from the WriterIndex 232 @usableFromInline 233 @inline(__always) 234 mutating func push<T: NativeStruct>(struct value: T, size: Int) { 235 ensureSpace(size: size) 236 var v = value 237 memcpy(_storage.memory.advanced(by: writerIndex &- size), &v, size) 238 _writerSize = _writerSize &+ size 239 } 240 241 /// Adds an object of type Scalar into the buffer 242 /// - Parameters: 243 /// - value: Object that will be written to the buffer 244 /// - len: Offset to subtract from the WriterIndex 245 @inline(__always) 246 @usableFromInline 247 mutating func push<T: Scalar>(value: T, len: Int) { 248 ensureSpace(size: len) 249 var v = value 250 memcpy(_storage.memory.advanced(by: writerIndex &- len), &v, len) 251 _writerSize = _writerSize &+ len 252 } 253 254 /// Adds a string to the buffer using swift.utf8 object 255 /// - Parameter str: String that will be added to the buffer 256 /// - Parameter len: length of the string 257 @inline(__always) 258 @usableFromInline 259 mutating func push(string str: String, len: Int) { 260 ensureSpace(size: len) 261 if str.utf8 262 .withContiguousStorageIfAvailable({ self.push(bytes: $0, len: len) }) != 263 nil 264 { 265 } else { 266 let utf8View = str.utf8 267 for c in utf8View.reversed() { 268 push(value: c, len: 1) 269 } 270 } 271 } 272 273 /// Writes a string to Bytebuffer using UTF8View 274 /// - Parameters: 275 /// - bytes: Pointer to the view 276 /// - len: Size of string 277 @usableFromInline 278 @inline(__always) 279 mutating func push( 280 bytes: UnsafeBufferPointer<String.UTF8View.Element>, 281 len: Int) -> Bool 282 { 283 memcpy( 284 _storage.memory.advanced(by: writerIndex &- len), 285 UnsafeRawPointer(bytes.baseAddress!), 286 len) 287 _writerSize = _writerSize &+ len 288 return true 289 } 290 291 /// Write stores an object into the buffer directly or indirectly. 292 /// 293 /// Direct: ignores the capacity of buffer which would mean we are referring to the direct point in memory 294 /// indirect: takes into respect the current capacity of the buffer (capacity - index), writing to the buffer from the end 295 /// - Parameters: 296 /// - value: Value that needs to be written to the buffer 297 /// - index: index to write to 298 /// - direct: Should take into consideration the capacity of the buffer 299 @inline(__always) 300 func write<T>(value: T, index: Int, direct: Bool = false) { 301 var index = index 302 if !direct { 303 index = _storage.capacity &- index 304 } 305 assert(index < _storage.capacity, "Write index is out of writing bound") 306 assert(index >= 0, "Writer index should be above zero") 307 _storage.memory.storeBytes(of: value, toByteOffset: index, as: T.self) 308 } 309 310 /// Makes sure that buffer has enouch space for each of the objects that will be written into it 311 /// - Parameter size: size of object 312 @discardableResult 313 @usableFromInline 314 @inline(__always) 315 mutating func ensureSpace(size: Int) -> Int { 316 if size &+ _writerSize > _storage.capacity { 317 _storage.reallocate(size, writerSize: _writerSize, alignment: alignment) 318 } 319 assert(size < FlatBufferMaxSize, "Buffer can't grow beyond 2 Gigabytes") 320 return size 321 } 322 323 /// pops the written VTable if it's already written into the buffer 324 /// - Parameter size: size of the `VTable` 325 @usableFromInline 326 @inline(__always) 327 mutating func pop(_ size: Int) { 328 assert( 329 (_writerSize &- size) > 0, 330 "New size should NOT be a negative number") 331 memset(_storage.memory.advanced(by: writerIndex), 0, _writerSize &- size) 332 _writerSize = size 333 } 334 335 /// Clears the current size of the buffer 336 @inline(__always) 337 mutating public func clearSize() { 338 _writerSize = 0 339 } 340 341 /// Clears the current instance of the buffer, replacing it with new memory 342 @inline(__always) 343 mutating public func clear() { 344 _writerSize = 0 345 alignment = 1 346 _storage.initialize(for: _storage.capacity) 347 } 348 349 /// Reads an object from the buffer 350 /// - Parameters: 351 /// - def: Type of the object 352 /// - position: the index of the object in the buffer 353 @inline(__always) 354 public func read<T>(def: T.Type, position: Int) -> T { 355 _storage.memory.advanced(by: position).load(as: T.self) 356 } 357 358 /// Reads a slice from the memory assuming a type of T 359 /// - Parameters: 360 /// - index: index of the object to be read from the buffer 361 /// - count: count of bytes in memory 362 @inline(__always) 363 public func readSlice<T>( 364 index: Int, 365 count: Int) -> [T] 366 { 367 assert( 368 index + count <= _storage.capacity, 369 "Reading out of bounds is illegal") 370 let start = _storage.memory.advanced(by: index) 371 .assumingMemoryBound(to: T.self) 372 let array = UnsafeBufferPointer(start: start, count: count) 373 return Array(array) 374 } 375 376 #if !os(WASI) 377 /// Reads a string from the buffer and encodes it to a swift string 378 /// - Parameters: 379 /// - index: index of the string in the buffer 380 /// - count: length of the string 381 /// - type: Encoding of the string 382 @inline(__always) 383 public func readString( 384 at index: Int, 385 count: Int, 386 type: String.Encoding = .utf8) -> String? 387 { 388 assert( 389 index + count <= _storage.capacity, 390 "Reading out of bounds is illegal") 391 let start = _storage.memory.advanced(by: index) 392 .assumingMemoryBound(to: UInt8.self) 393 let bufprt = UnsafeBufferPointer(start: start, count: count) 394 return String(bytes: Array(bufprt), encoding: type) 395 } 396 #else 397 /// Reads a string from the buffer and encodes it to a swift string 398 /// - Parameters: 399 /// - index: index of the string in the buffer 400 /// - count: length of the string 401 /// - type: Encoding of the string 402 @inline(__always) 403 public func readString( 404 at index: Int, 405 count: Int) -> String? 406 { 407 assert( 408 index + count <= _storage.capacity, 409 "Reading out of bounds is illegal") 410 let start = _storage.memory.advanced(by: index) 411 .assumingMemoryBound(to: UInt8.self) 412 let bufprt = UnsafeBufferPointer(start: start, count: count) 413 return String(cString: bufprt.baseAddress!) 414 } 415 #endif 416 417 /// Creates a new Flatbuffer object that's duplicated from the current one 418 /// - Parameter removeBytes: the amount of bytes to remove from the current Size 419 @inline(__always) 420 public func duplicate(removing removeBytes: Int = 0) -> ByteBuffer { 421 assert(removeBytes > 0, "Can NOT remove negative bytes") 422 assert( 423 removeBytes < _storage.capacity, 424 "Can NOT remove more bytes than the ones allocated") 425 return ByteBuffer( 426 memory: _storage.memory, 427 count: _storage.capacity, 428 removing: _writerSize &- removeBytes) 429 } 430 431 /// Returns the written bytes into the ``ByteBuffer`` 432 public var underlyingBytes: [UInt8] { 433 let cp = capacity &- writerIndex 434 let start = memory.advanced(by: writerIndex) 435 .bindMemory(to: UInt8.self, capacity: cp) 436 437 let ptr = UnsafeBufferPointer<UInt8>(start: start, count: cp) 438 return Array(ptr) 439 } 440 441 /// SkipPrefix Skips the first 4 bytes in case one of the following 442 /// functions are called `getPrefixedSizeCheckedRoot` & `getPrefixedSizeRoot` 443 /// which allows us to skip the first 4 bytes instead of recreating the buffer 444 @discardableResult 445 @usableFromInline 446 @inline(__always) 447 mutating func skipPrefix() -> Int32 { 448 _writerSize = _writerSize &- MemoryLayout<Int32>.size 449 return read(def: Int32.self, position: 0) 450 } 451 452 } 453 454 extension ByteBuffer: CustomDebugStringConvertible { 455 456 public var debugDescription: String { 457 """ 458 buffer located at: \(_storage.memory), with capacity of \(_storage.capacity) 459 { writerSize: \(_writerSize), readerSize: \(reader), writerIndex: \(writerIndex) } 460 """ 461 } 462 }