Message+BinaryAdditions.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. // Sources/SwiftProtobuf/Message+BinaryAdditions.swift - Per-type binary coding
  2. //
  3. // Copyright (c) 2014 - 2016 Apple Inc. and the project authors
  4. // Licensed under Apache License v2.0 with Runtime Library Exception
  5. //
  6. // See LICENSE.txt for license information:
  7. // https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
  8. //
  9. // -----------------------------------------------------------------------------
  10. ///
  11. /// Extensions to `Message` to provide binary coding and decoding.
  12. ///
  13. // -----------------------------------------------------------------------------
  14. import Foundation
  15. /// Binary encoding and decoding methods for messages.
  16. extension Message {
  17. /// Returns a ``SwiftProtobufContiguousBytes`` instance containing the Protocol Buffer binary
  18. /// format serialization of the message.
  19. ///
  20. /// - Parameters:
  21. /// - partial: If `false` (the default), this method will check
  22. /// `Message.isInitialized` before encoding to verify that all required
  23. /// fields are present. If any are missing, this method throws.
  24. /// ``BinaryEncodingError/missingRequiredFields``.
  25. /// - options: The ``BinaryEncodingOptions`` to use.
  26. /// - Returns: A ``SwiftProtobufContiguousBytes`` instance containing the binary serialization
  27. /// of the message.
  28. ///
  29. /// - Throws: ``SwiftProtobufError`` or ``BinaryEncodingError`` if encoding fails.
  30. public func serializedBytes<Bytes: SwiftProtobufContiguousBytes>(
  31. partial: Bool = false,
  32. options: BinaryEncodingOptions = BinaryEncodingOptions()
  33. ) throws -> Bytes {
  34. if !partial && !isInitialized {
  35. throw BinaryEncodingError.missingRequiredFields
  36. }
  37. // Note that this assumes `options` will not change the required size.
  38. let requiredSize = try serializedDataSize()
  39. // Messages have a 2GB limit in encoded size, the upstread C++ code
  40. // (message_lite, etc.) does this enforcement also.
  41. // https://protobuf.dev/programming-guides/encoding/#cheat-sheet
  42. //
  43. // Testing here enables the limit without adding extra conditionals to all
  44. // the places that encode message fields (or strings/bytes fields), keeping
  45. // the overhead of the check to a minimum.
  46. guard requiredSize < 0x7fff_ffff else {
  47. // Adding a new error is a breaking change.
  48. throw BinaryEncodingError.missingRequiredFields
  49. }
  50. var data = Bytes(repeating: 0, count: requiredSize)
  51. try data.withUnsafeMutableBytes { (body: UnsafeMutableRawBufferPointer) in
  52. var visitor = BinaryEncodingVisitor(forWritingInto: body, options: options)
  53. try traverse(visitor: &visitor)
  54. // Currently not exposing this from the api because it really would be
  55. // an internal error in the library and should never happen.
  56. assert(visitor.encoder.remainder.count == 0)
  57. }
  58. return data
  59. }
  60. /// Returns the size in bytes required to encode the message in binary format.
  61. /// This is used by `serializedData()` to precalculate the size of the buffer
  62. /// so that encoding can proceed without bounds checks or reallocation.
  63. internal func serializedDataSize() throws -> Int {
  64. // Note: since this api is internal, it doesn't currently worry about
  65. // needing a partial argument to handle required fields. If this become
  66. // public, it will need that added.
  67. var visitor = BinaryEncodingSizeVisitor()
  68. try traverse(visitor: &visitor)
  69. return visitor.serializedSize
  70. }
  71. /// Creates a new message by decoding the given `SwiftProtobufContiguousBytes` value
  72. /// containing a serialized message in Protocol Buffer binary format.
  73. ///
  74. /// - Parameters:
  75. /// - serializedBytes: The binary-encoded message data to decode.
  76. /// - extensions: An ``ExtensionMap`` used to look up and decode any
  77. /// extensions in this message or messages nested within this message's
  78. /// fields.
  79. /// - partial: If `false` (the default), this method will check
  80. /// ``Message/isInitialized-6abgi`` after decoding to verify that all required
  81. /// fields are present. If any are missing, this method throws
  82. /// ``BinaryDecodingError/missingRequiredFields``.
  83. /// - options: The ``BinaryDecodingOptions`` to use.
  84. /// - Throws: ``BinaryDecodingError`` if decoding fails.
  85. @inlinable
  86. public init<Bytes: SwiftProtobufContiguousBytes>(
  87. serializedBytes bytes: Bytes,
  88. extensions: (any ExtensionMap)? = nil,
  89. partial: Bool = false,
  90. options: BinaryDecodingOptions = BinaryDecodingOptions()
  91. ) throws {
  92. self.init()
  93. try merge(serializedBytes: bytes, extensions: extensions, partial: partial, options: options)
  94. }
  95. #if compiler(>=6.2)
  96. /// Creates a new message by decoding the bytes provided by a `RawSpan`
  97. /// containing a serialized message in Protocol Buffer binary format.
  98. ///
  99. /// - Parameters:
  100. /// - serializedBytes: The `RawSpan` of binary-encoded message data to decode.
  101. /// - extensions: An ``ExtensionMap`` used to look up and decode any
  102. /// extensions in this message or messages nested within this message's
  103. /// fields.
  104. /// - partial: If `false` (the default), this method will check
  105. /// ``Message/isInitialized-6abgi`` after decoding to verify that all required
  106. /// fields are present. If any are missing, this method throws
  107. /// ``BinaryDecodingError/missingRequiredFields``.
  108. /// - options: The ``BinaryDecodingOptions`` to use.
  109. /// - Throws: ``BinaryDecodingError`` if decoding fails.
  110. @inlinable
  111. @available(macOS 10.14.4, iOS 12.2, watchOS 5.2, tvOS 12.2, visionOS 1.0, *)
  112. public init(
  113. serializedBytes bytes: RawSpan,
  114. extensions: (any ExtensionMap)? = nil,
  115. partial: Bool = false,
  116. options: BinaryDecodingOptions = BinaryDecodingOptions()
  117. ) throws {
  118. self.init()
  119. try merge(serializedBytes: bytes, extensions: extensions, partial: partial, options: options)
  120. }
  121. #endif
  122. /// Updates the message by decoding the given `SwiftProtobufContiguousBytes` value
  123. /// containing a serialized message in Protocol Buffer binary format into the
  124. /// receiver.
  125. ///
  126. /// - Note: If this method throws an error, the message may still have been
  127. /// partially mutated by the binary data that was decoded before the error
  128. /// occurred.
  129. ///
  130. /// - Parameters:
  131. /// - serializedBytes: The binary-encoded message data to decode.
  132. /// - extensions: An ``ExtensionMap`` used to look up and decode any
  133. /// extensions in this message or messages nested within this message's
  134. /// fields.
  135. /// - partial: If `false` (the default), this method will check
  136. /// ``Message/isInitialized-6abgi`` after decoding to verify that all required
  137. /// fields are present. If any are missing, this method throws
  138. /// ``BinaryDecodingError/missingRequiredFields``.
  139. /// - options: The ``BinaryDecodingOptions`` to use.
  140. /// - Throws: ``BinaryDecodingError`` if decoding fails.
  141. @inlinable
  142. public mutating func merge<Bytes: SwiftProtobufContiguousBytes>(
  143. serializedBytes bytes: Bytes,
  144. extensions: (any ExtensionMap)? = nil,
  145. partial: Bool = false,
  146. options: BinaryDecodingOptions = BinaryDecodingOptions()
  147. ) throws {
  148. try bytes.withUnsafeBytes { (body: UnsafeRawBufferPointer) in
  149. try _merge(rawBuffer: body, extensions: extensions, partial: partial, options: options)
  150. }
  151. }
  152. #if compiler(>=6.2)
  153. /// Updates the message by decoding the bytes provided by a `RawSpan` containing
  154. /// a serialized message in Protocol Buffer binary format into the receiver.
  155. ///
  156. /// - Note: If this method throws an error, the message may still have been
  157. /// partially mutated by the binary data that was decoded before the error
  158. /// occurred.
  159. ///
  160. /// - Parameters:
  161. /// - serializedBytes: The `RawSpan` of binary-encoded message data to decode.
  162. /// - extensions: An ``ExtensionMap`` used to look up and decode any
  163. /// extensions in this message or messages nested within this message's
  164. /// fields.
  165. /// - partial: If `false` (the default), this method will check
  166. /// ``Message/isInitialized-6abgi`` after decoding to verify that all required
  167. /// fields are present. If any are missing, this method throws
  168. /// ``BinaryDecodingError/missingRequiredFields``.
  169. /// - options: The ``BinaryDecodingOptions`` to use.
  170. /// - Throws: ``BinaryDecodingError`` if decoding fails.
  171. @inlinable
  172. @available(macOS 10.14.4, iOS 12.2, watchOS 5.2, tvOS 12.2, visionOS 1.0, *)
  173. public mutating func merge(
  174. serializedBytes bytes: RawSpan,
  175. extensions: (any ExtensionMap)? = nil,
  176. partial: Bool = false,
  177. options: BinaryDecodingOptions = BinaryDecodingOptions()
  178. ) throws {
  179. try bytes.withUnsafeBytes { (body: UnsafeRawBufferPointer) in
  180. try _merge(rawBuffer: body, extensions: extensions, partial: partial, options: options)
  181. }
  182. }
  183. #endif
  184. // Helper for `merge()`s to keep the Decoder internal to SwiftProtobuf while
  185. // allowing the generic over `SwiftProtobufContiguousBytes` to get better codegen from the
  186. // compiler by being `@inlinable`. For some discussion on this see
  187. // https://github.com/apple/swift-protobuf/pull/914#issuecomment-555458153
  188. @usableFromInline
  189. internal mutating func _merge(
  190. rawBuffer body: UnsafeRawBufferPointer,
  191. extensions: (any ExtensionMap)?,
  192. partial: Bool,
  193. options: BinaryDecodingOptions
  194. ) throws {
  195. if let baseAddress = body.baseAddress, body.count > 0 {
  196. var decoder = BinaryDecoder(
  197. forReadingFrom: baseAddress,
  198. count: body.count,
  199. options: options,
  200. extensions: extensions
  201. )
  202. try decoder.decodeFullMessage(message: &self)
  203. }
  204. if !partial && !isInitialized {
  205. throw BinaryDecodingError.missingRequiredFields
  206. }
  207. }
  208. }