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