damus

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

Verifier.swift (7501B)


      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 /// Verifier that check if the buffer passed into it is a valid,
     24 /// safe, aligned Flatbuffers object since swift read from `unsafeMemory`
     25 public struct Verifier {
     26 
     27   /// Flag to check for alignment if true
     28   fileprivate let _checkAlignment: Bool
     29   /// Capacity of the current buffer
     30   fileprivate var _capacity: Int
     31   /// Current ApparentSize
     32   fileprivate var _apparentSize: UOffset = 0
     33   /// Amount of tables present within a buffer
     34   fileprivate var _tableCount = 0
     35 
     36   /// Capacity of the buffer
     37   internal var capacity: Int { _capacity }
     38   /// Current reached depth within the buffer
     39   internal var _depth = 0
     40   /// Current verifiable ByteBuffer
     41   internal var _buffer: ByteBuffer
     42   /// Options for verification
     43   internal let _options: VerifierOptions
     44 
     45   /// Initializer for the verifier
     46   /// - Parameters:
     47   ///   - buffer: Bytebuffer that is required to be verified
     48   ///   - options: `VerifierOptions` that set the rule for some of the verification done
     49   ///   - checkAlignment: If alignment check is required to be preformed
     50   /// - Throws: `exceedsMaxSizeAllowed` if capacity of the buffer is more than 2GiB
     51   public init(
     52     buffer: inout ByteBuffer,
     53     options: VerifierOptions = .init(),
     54     checkAlignment: Bool = true) throws
     55   {
     56     guard buffer.capacity < FlatBufferMaxSize else {
     57       throw FlatbuffersErrors.exceedsMaxSizeAllowed
     58     }
     59 
     60     _buffer = buffer
     61     _capacity = buffer.capacity
     62     _checkAlignment = checkAlignment
     63     _options = options
     64   }
     65 
     66   /// Resets the verifier to initial state
     67   public mutating func reset() {
     68     _depth = 0
     69     _tableCount = 0
     70   }
     71 
     72   /// Checks if the value of type `T` is aligned properly in the buffer
     73   /// - Parameters:
     74   ///   - position: Current position
     75   ///   - type: Type of value to check
     76   /// - Throws: `missAlignedPointer` if the pointer is not aligned properly
     77   public mutating func isAligned<T>(position: Int, type: T.Type) throws {
     78 
     79     /// If check alignment is false this mutating function doesnt continue
     80     if !_checkAlignment { return }
     81 
     82     /// advance pointer to position X
     83     let ptr = _buffer._storage.memory.advanced(by: position)
     84     /// Check if the pointer is aligned
     85     if Int(bitPattern: ptr) & (MemoryLayout<T>.alignment &- 1) == 0 {
     86       return
     87     }
     88 
     89     throw FlatbuffersErrors.missAlignedPointer(
     90       position: position,
     91       type: String(describing: T.self))
     92   }
     93 
     94   /// Checks if the value of Size "X" is within the range of the buffer
     95   /// - Parameters:
     96   ///   - position: Current postion to be read
     97   ///   - size: `Byte` Size of readable object within the buffer
     98   /// - Throws: `outOfBounds` if the value is out of the bounds of the buffer
     99   /// and `apparentSizeTooLarge` if the apparent size is bigger than the one specified
    100   /// in `VerifierOptions`
    101   public mutating func rangeInBuffer(position: Int, size: Int) throws {
    102     let end = UInt(clamping: (position &+ size).magnitude)
    103     if end > _buffer.capacity {
    104       throw FlatbuffersErrors.outOfBounds(position: end, end: capacity)
    105     }
    106     _apparentSize = _apparentSize &+ UInt32(size)
    107     if _apparentSize > _options._maxApparentSize {
    108       throw FlatbuffersErrors.apparentSizeTooLarge
    109     }
    110   }
    111 
    112   /// Validates if a value of type `T` is aligned and within the bounds of
    113   /// the buffer
    114   /// - Parameters:
    115   ///   - position: Current readable position
    116   ///   - type: Type of value to check
    117   /// - Throws: FlatbuffersErrors
    118   public mutating func inBuffer<T>(position: Int, of type: T.Type) throws {
    119     try isAligned(position: position, type: type)
    120     try rangeInBuffer(position: position, size: MemoryLayout<T>.size)
    121   }
    122 
    123   /// Visits a table at the current position and validates if the table meets
    124   /// the rules specified in the `VerifierOptions`
    125   /// - Parameter position: Current position to be read
    126   /// - Throws: FlatbuffersErrors
    127   /// - Returns: A `TableVerifier` at the current readable table
    128   public mutating func visitTable(at position: Int) throws -> TableVerifier {
    129     let vtablePosition = try derefOffset(position: position)
    130     let vtableLength: VOffset = try getValue(at: vtablePosition)
    131 
    132     let length = Int(vtableLength)
    133     try isAligned(
    134       position: Int(clamping: (vtablePosition + length).magnitude),
    135       type: VOffset.self)
    136     try rangeInBuffer(position: vtablePosition, size: length)
    137 
    138     _tableCount += 1
    139 
    140     if _tableCount > _options._maxTableCount {
    141       throw FlatbuffersErrors.maximumTables
    142     }
    143 
    144     _depth += 1
    145 
    146     if _depth > _options._maxDepth {
    147       throw FlatbuffersErrors.maximumDepth
    148     }
    149 
    150     return TableVerifier(
    151       position: position,
    152       vtable: vtablePosition,
    153       vtableLength: length,
    154       verifier: &self)
    155   }
    156 
    157   /// Validates if a value of type `T` is within the buffer and returns it
    158   /// - Parameter position: Current position to be read
    159   /// - Throws: `inBuffer` errors
    160   /// - Returns: a value of type `T` usually a `VTable` or a table offset
    161   internal mutating func getValue<T>(at position: Int) throws -> T {
    162     try inBuffer(position: position, of: T.self)
    163     return _buffer.read(def: T.self, position: position)
    164   }
    165 
    166   /// derefrences an offset within a vtable to get the position of the field
    167   /// in the bytebuffer
    168   /// - Parameter position: Current readable position
    169   /// - Throws: `inBuffer` errors & `signedOffsetOutOfBounds`
    170   /// - Returns: Current readable position for a field
    171   @inline(__always)
    172   internal mutating func derefOffset(position: Int) throws -> Int {
    173     try inBuffer(position: position, of: Int32.self)
    174 
    175     let offset = _buffer.read(def: Int32.self, position: position)
    176     // switching to int32 since swift's default Int is int64
    177     // this should be safe since we already checked if its within
    178     // the buffer
    179     let _int32Position = UInt32(position)
    180 
    181     let reportedOverflow: (partialValue: UInt32, overflow: Bool)
    182     if offset > 0 {
    183       reportedOverflow = _int32Position
    184         .subtractingReportingOverflow(offset.magnitude)
    185     } else {
    186       reportedOverflow = _int32Position
    187         .addingReportingOverflow(offset.magnitude)
    188     }
    189 
    190     /// since `subtractingReportingOverflow` & `addingReportingOverflow` returns true,
    191     /// if there is overflow we return failure
    192     if reportedOverflow.overflow || reportedOverflow.partialValue > _buffer
    193       .capacity
    194     {
    195       throw FlatbuffersErrors.signedOffsetOutOfBounds(
    196         offset: Int(offset),
    197         position: position)
    198     }
    199 
    200     return Int(reportedOverflow.partialValue)
    201   }
    202 
    203   /// finishes the current iteration of verification on an object
    204   internal mutating func finish() {
    205     _depth -= 1
    206   }
    207 
    208   mutating func verify(id: String) throws {
    209     let size = MemoryLayout<Int32>.size
    210     let str = _buffer.readString(at: size, count: size)
    211     if id == str {
    212       return
    213     }
    214     throw FlatbuffersErrors.bufferIdDidntMatchPassedId
    215   }
    216 
    217 }