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