damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

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 }