GRPCTLSConfiguration.swift 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. /*
  2. * Copyright 2021, gRPC Authors 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. import NIOSSL
  17. #if canImport(Network)
  18. import Network
  19. import NIOTransportServices
  20. import Security
  21. #endif
  22. /// TLS configuration.
  23. ///
  24. /// This structure allow configuring TLS for a wide range of TLS implementations. Some
  25. /// options are removed from the user's control to ensure the configuration complies with
  26. /// the gRPC specification.
  27. public struct GRPCTLSConfiguration {
  28. fileprivate enum Backend {
  29. /// Configuration for NIOSSSL.
  30. case nio(NIOConfiguration)
  31. #if canImport(Network)
  32. /// Configuration for Network.framework.
  33. case network(NetworkConfiguration)
  34. #endif
  35. }
  36. /// The TLS backend.
  37. private var backend: Backend
  38. private init(backend: Backend) {
  39. self.backend = backend
  40. }
  41. /// Return the configuration for NIOSSL or `nil` if Network.framework is being used as the
  42. /// TLS backend.
  43. internal var nioConfiguration: NIOConfiguration? {
  44. switch self.backend {
  45. case let .nio(configuration):
  46. return configuration
  47. #if canImport(Network)
  48. case .network:
  49. return nil
  50. #endif
  51. }
  52. }
  53. internal var isNetworkFrameworkTLSBackend: Bool {
  54. switch self.backend {
  55. case .nio:
  56. return false
  57. #if canImport(Network)
  58. case .network:
  59. return true
  60. #endif
  61. }
  62. }
  63. /// The server hostname override as used by the TLS SNI extension.
  64. ///
  65. /// This value is ignored when the configuration is used for a server.
  66. ///
  67. /// - Note: when using the Network.framework backend, this value may not be set to `nil`.
  68. internal var hostnameOverride: String? {
  69. get {
  70. switch self.backend {
  71. case let .nio(config):
  72. return config.hostnameOverride
  73. #if canImport(Network)
  74. case let .network(config):
  75. return config.hostnameOverride
  76. #endif
  77. }
  78. }
  79. set {
  80. switch self.backend {
  81. case var .nio(config):
  82. config.hostnameOverride = newValue
  83. self.backend = .nio(config)
  84. #if canImport(Network)
  85. case var .network(config):
  86. if #available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *) {
  87. if let hostnameOverride = newValue {
  88. config.updateHostnameOverride(to: hostnameOverride)
  89. } else {
  90. // We can't unset the value so error instead.
  91. fatalError("Can't unset hostname override when using Network.framework TLS backend.")
  92. // FIXME: lazily set the value on the backend when applying the options.
  93. }
  94. } else {
  95. // We can only make the `.network` backend if we meet the above availability checks so
  96. // this should be unreachable.
  97. preconditionFailure()
  98. }
  99. self.backend = .network(config)
  100. #endif
  101. }
  102. }
  103. }
  104. /// Whether the configuration requires ALPN to be used.
  105. ///
  106. /// The Network.framework backend does not support this option and always requires ALPN.
  107. internal var requireALPN: Bool {
  108. get {
  109. switch self.backend {
  110. case let .nio(config):
  111. return config.requireALPN
  112. #if canImport(Network)
  113. case .network:
  114. return true
  115. #endif
  116. }
  117. }
  118. set {
  119. switch self.backend {
  120. case var .nio(config):
  121. config.requireALPN = newValue
  122. self.backend = .nio(config)
  123. #if canImport(Network)
  124. case .network:
  125. ()
  126. #endif
  127. }
  128. }
  129. }
  130. // Marked to silence the deprecation warning
  131. @available(*, deprecated)
  132. internal init(transforming deprecated: ClientConnection.Configuration.TLS) {
  133. self.backend = .nio(
  134. .init(
  135. configuration: deprecated.configuration,
  136. customVerificationCallback: deprecated.customVerificationCallback,
  137. hostnameOverride: deprecated.hostnameOverride,
  138. requireALPN: false // Not currently supported.
  139. )
  140. )
  141. }
  142. // Marked to silence the deprecation warning
  143. @available(*, deprecated)
  144. internal init(transforming deprecated: Server.Configuration.TLS) {
  145. self.backend = .nio(
  146. .init(configuration: deprecated.configuration, requireALPN: deprecated.requireALPN)
  147. )
  148. }
  149. @available(*, deprecated)
  150. internal var asDeprecatedClientConfiguration: ClientConnection.Configuration.TLS? {
  151. if case let .nio(config) = self.backend {
  152. var tls = ClientConnection.Configuration.TLS(
  153. configuration: config.configuration,
  154. hostnameOverride: config.hostnameOverride
  155. )
  156. tls.customVerificationCallback = config.customVerificationCallback
  157. return tls
  158. }
  159. return nil
  160. }
  161. @available(*, deprecated)
  162. internal var asDeprecatedServerConfiguration: Server.Configuration.TLS? {
  163. if case let .nio(config) = self.backend {
  164. return Server.Configuration.TLS(configuration: config.configuration)
  165. }
  166. return nil
  167. }
  168. }
  169. // MARK: - NIO Backend
  170. extension GRPCTLSConfiguration {
  171. internal struct NIOConfiguration {
  172. var configuration: TLSConfiguration
  173. var customVerificationCallback: NIOSSLCustomVerificationCallback?
  174. var hostnameOverride: String?
  175. // The client doesn't support this yet (https://github.com/grpc/grpc-swift/issues/1042).
  176. var requireALPN: Bool
  177. }
  178. /// TLS Configuration with suitable defaults for clients, using `NIOSSL`.
  179. ///
  180. /// This is a wrapper around `NIOSSL.TLSConfiguration` to restrict input to values which comply
  181. /// with the gRPC protocol.
  182. ///
  183. /// - Parameter certificateChain: The certificate to offer during negotiation, defaults to an
  184. /// empty array.
  185. /// - Parameter privateKey: The private key associated with the leaf certificate. This defaults
  186. /// to `nil`.
  187. /// - Parameter trustRoots: The trust roots to validate certificates, this defaults to using a
  188. /// root provided by the platform.
  189. /// - Parameter certificateVerification: Whether to verify the remote certificate. Defaults to
  190. /// `.fullVerification`.
  191. /// - Parameter hostnameOverride: Value to use for TLS SNI extension; this must not be an IP
  192. /// address, defaults to `nil`.
  193. /// - Parameter customVerificationCallback: A callback to provide to override the certificate verification logic,
  194. /// defaults to `nil`.
  195. public static func makeClientConfigurationBackedByNIOSSL(
  196. certificateChain: [NIOSSLCertificateSource] = [],
  197. privateKey: NIOSSLPrivateKeySource? = nil,
  198. trustRoots: NIOSSLTrustRoots = .default,
  199. certificateVerification: CertificateVerification = .fullVerification,
  200. hostnameOverride: String? = nil,
  201. customVerificationCallback: NIOSSLCustomVerificationCallback? = nil
  202. ) -> GRPCTLSConfiguration {
  203. var configuration = TLSConfiguration.makeClientConfiguration()
  204. configuration.minimumTLSVersion = .tlsv12
  205. configuration.certificateVerification = certificateVerification
  206. configuration.trustRoots = trustRoots
  207. configuration.certificateChain = certificateChain
  208. configuration.privateKey = privateKey
  209. configuration.applicationProtocols = GRPCApplicationProtocolIdentifier.client
  210. return GRPCTLSConfiguration.makeClientConfigurationBackedByNIOSSL(
  211. configuration: configuration,
  212. hostnameOverride: hostnameOverride,
  213. customVerificationCallback: customVerificationCallback
  214. )
  215. }
  216. /// Creates a gRPC TLS Configuration using the given `NIOSSL.TLSConfiguration`.
  217. ///
  218. /// - Note: If no ALPN tokens are set in `configuration.applicationProtocols` then "grpc-exp"
  219. /// and "h2" will be used.
  220. /// - Parameters:
  221. /// - configuration: The `NIOSSL.TLSConfiguration` to base this configuration on.
  222. /// - hostnameOverride: The hostname override to use for the TLS SNI extension.
  223. public static func makeClientConfigurationBackedByNIOSSL(
  224. configuration: TLSConfiguration,
  225. hostnameOverride: String? = nil,
  226. customVerificationCallback: NIOSSLCustomVerificationCallback? = nil
  227. ) -> GRPCTLSConfiguration {
  228. var configuration = configuration
  229. // Set the ALPN tokens if none were set.
  230. if configuration.applicationProtocols.isEmpty {
  231. configuration.applicationProtocols = GRPCApplicationProtocolIdentifier.client
  232. }
  233. let nioConfiguration = NIOConfiguration(
  234. configuration: configuration,
  235. customVerificationCallback: customVerificationCallback,
  236. hostnameOverride: hostnameOverride,
  237. requireALPN: false // We don't currently support this.
  238. )
  239. return GRPCTLSConfiguration(backend: .nio(nioConfiguration))
  240. }
  241. /// TLS Configuration with suitable defaults for servers.
  242. ///
  243. /// This is a wrapper around `NIOSSL.TLSConfiguration` to restrict input to values which comply
  244. /// with the gRPC protocol.
  245. ///
  246. /// - Parameter certificateChain: The certificate to offer during negotiation.
  247. /// - Parameter privateKey: The private key associated with the leaf certificate.
  248. /// - Parameter trustRoots: The trust roots to validate certificates, this defaults to using a
  249. /// root provided by the platform.
  250. /// - Parameter certificateVerification: Whether to verify the remote certificate. Defaults to
  251. /// `.none`.
  252. /// - Parameter requireALPN: Whether ALPN is required or not.
  253. public static func makeServerConfigurationBackedByNIOSSL(
  254. certificateChain: [NIOSSLCertificateSource],
  255. privateKey: NIOSSLPrivateKeySource,
  256. trustRoots: NIOSSLTrustRoots = .default,
  257. certificateVerification: CertificateVerification = .none,
  258. requireALPN: Bool = true
  259. ) -> GRPCTLSConfiguration {
  260. var configuration = TLSConfiguration.makeServerConfiguration(
  261. certificateChain: certificateChain,
  262. privateKey: privateKey
  263. )
  264. configuration.minimumTLSVersion = .tlsv12
  265. configuration.certificateVerification = certificateVerification
  266. configuration.trustRoots = trustRoots
  267. configuration.applicationProtocols = GRPCApplicationProtocolIdentifier.server
  268. return GRPCTLSConfiguration.makeServerConfigurationBackedByNIOSSL(
  269. configuration: configuration,
  270. requireALPN: requireALPN
  271. )
  272. }
  273. /// Creates a gRPC TLS Configuration suitable for servers using the given
  274. /// `NIOSSL.TLSConfiguration`.
  275. ///
  276. /// - Note: If no ALPN tokens are set in `configuration.applicationProtocols` then "grpc-exp",
  277. /// "h2", and "http/1.1" will be used.
  278. /// - Parameters:
  279. /// - configuration: The `NIOSSL.TLSConfiguration` to base this configuration on.
  280. /// - requiresALPN: Whether the server enforces ALPN. Defaults to `true`.
  281. public static func makeServerConfigurationBackedByNIOSSL(
  282. configuration: TLSConfiguration,
  283. requireALPN: Bool = true
  284. ) -> GRPCTLSConfiguration {
  285. var configuration = configuration
  286. // Set the ALPN tokens if none were set.
  287. if configuration.applicationProtocols.isEmpty {
  288. configuration.applicationProtocols = GRPCApplicationProtocolIdentifier.server
  289. }
  290. let nioConfiguration = NIOConfiguration(
  291. configuration: configuration,
  292. customVerificationCallback: nil,
  293. hostnameOverride: nil,
  294. requireALPN: requireALPN
  295. )
  296. return GRPCTLSConfiguration(backend: .nio(nioConfiguration))
  297. }
  298. @usableFromInline
  299. internal func makeNIOSSLContext() throws -> NIOSSLContext? {
  300. switch self.backend {
  301. case let .nio(configuration):
  302. return try NIOSSLContext(configuration: configuration.configuration)
  303. #if canImport(Network)
  304. case .network:
  305. return nil
  306. #endif
  307. }
  308. }
  309. internal var nioSSLCustomVerificationCallback: NIOSSLCustomVerificationCallback? {
  310. switch self.backend {
  311. case let .nio(configuration):
  312. return configuration.customVerificationCallback
  313. #if canImport(Network)
  314. case .network:
  315. return nil
  316. #endif
  317. }
  318. }
  319. internal mutating func updateNIOCertificateChain(to certificateChain: [NIOSSLCertificate]) {
  320. self.modifyingNIOConfiguration {
  321. $0.configuration.certificateChain = certificateChain.map { .certificate($0) }
  322. }
  323. }
  324. internal mutating func updateNIOPrivateKey(to privateKey: NIOSSLPrivateKey) {
  325. self.modifyingNIOConfiguration {
  326. $0.configuration.privateKey = .privateKey(privateKey)
  327. }
  328. }
  329. internal mutating func updateNIOTrustRoots(to trustRoots: NIOSSLTrustRoots) {
  330. self.modifyingNIOConfiguration {
  331. $0.configuration.trustRoots = trustRoots
  332. }
  333. }
  334. internal mutating func updateNIOCertificateVerification(
  335. to verification: CertificateVerification
  336. ) {
  337. self.modifyingNIOConfiguration {
  338. $0.configuration.certificateVerification = verification
  339. }
  340. }
  341. internal mutating func updateNIOCustomVerificationCallback(
  342. to callback: @escaping NIOSSLCustomVerificationCallback
  343. ) {
  344. self.modifyingNIOConfiguration {
  345. $0.customVerificationCallback = callback
  346. }
  347. }
  348. private mutating func modifyingNIOConfiguration(_ modify: (inout NIOConfiguration) -> Void) {
  349. switch self.backend {
  350. case var .nio(configuration):
  351. modify(&configuration)
  352. self.backend = .nio(configuration)
  353. #if canImport(Network)
  354. case .network:
  355. preconditionFailure()
  356. #endif
  357. }
  358. }
  359. }
  360. // MARK: - Network Backend
  361. #if canImport(Network)
  362. extension GRPCTLSConfiguration {
  363. internal struct NetworkConfiguration {
  364. @available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
  365. internal var options: NWProtocolTLS.Options {
  366. get {
  367. return self._options as! NWProtocolTLS.Options
  368. }
  369. set {
  370. self._options = newValue
  371. }
  372. }
  373. /// Always a NWProtocolTLS.Options.
  374. ///
  375. /// This somewhat insane type-erasure is necessary because we need to availability-guard the NWProtocolTLS.Options
  376. /// (it isn't available in older SDKs), but we cannot have stored properties guarded by availability in this way, only
  377. /// computed ones. To that end, we have to erase the type and then un-erase it. This is fairly silly.
  378. private var _options: Any
  379. // This is set privately via `updateHostnameOverride(to:)` because we require availability
  380. // guards to update the value in the underlying `sec_protocol_options`.
  381. internal private(set) var hostnameOverride: String?
  382. @available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
  383. init(options: NWProtocolTLS.Options, hostnameOverride: String?) {
  384. self._options = options
  385. self.hostnameOverride = hostnameOverride
  386. }
  387. @available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
  388. internal mutating func updateHostnameOverride(to hostnameOverride: String) {
  389. self.hostnameOverride = hostnameOverride
  390. sec_protocol_options_set_tls_server_name(
  391. self.options.securityProtocolOptions,
  392. hostnameOverride
  393. )
  394. }
  395. }
  396. @available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
  397. public static func makeClientConfigurationBackedByNetworkFramework(
  398. identity: SecIdentity? = nil,
  399. hostnameOverride: String? = nil,
  400. verifyCallbackWithQueue: (sec_protocol_verify_t, DispatchQueue)? = nil
  401. ) -> GRPCTLSConfiguration {
  402. let options = NWProtocolTLS.Options()
  403. if let identity = identity {
  404. sec_protocol_options_set_local_identity(
  405. options.securityProtocolOptions,
  406. sec_identity_create(identity)!
  407. )
  408. }
  409. if let hostnameOverride = hostnameOverride {
  410. sec_protocol_options_set_tls_server_name(
  411. options.securityProtocolOptions,
  412. hostnameOverride
  413. )
  414. }
  415. if let verifyCallbackWithQueue = verifyCallbackWithQueue {
  416. sec_protocol_options_set_verify_block(
  417. options.securityProtocolOptions,
  418. verifyCallbackWithQueue.0,
  419. verifyCallbackWithQueue.1
  420. )
  421. }
  422. if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) {
  423. sec_protocol_options_set_min_tls_protocol_version(options.securityProtocolOptions, .TLSv12)
  424. } else {
  425. sec_protocol_options_set_tls_min_version(options.securityProtocolOptions, .tlsProtocol12)
  426. }
  427. for `protocol` in GRPCApplicationProtocolIdentifier.client {
  428. sec_protocol_options_add_tls_application_protocol(
  429. options.securityProtocolOptions,
  430. `protocol`
  431. )
  432. }
  433. return .makeClientConfigurationBackedByNetworkFramework(
  434. options: options,
  435. hostnameOverride: hostnameOverride
  436. )
  437. }
  438. @available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
  439. public static func makeClientConfigurationBackedByNetworkFramework(
  440. options: NWProtocolTLS.Options,
  441. hostnameOverride: String? = nil
  442. ) -> GRPCTLSConfiguration {
  443. let network = NetworkConfiguration(options: options, hostnameOverride: hostnameOverride)
  444. return GRPCTLSConfiguration(backend: .network(network))
  445. }
  446. @available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
  447. public static func makeServerConfigurationBackedByNetworkFramework(
  448. identity: SecIdentity
  449. ) -> GRPCTLSConfiguration {
  450. let options = NWProtocolTLS.Options()
  451. sec_protocol_options_set_local_identity(
  452. options.securityProtocolOptions,
  453. sec_identity_create(identity)!
  454. )
  455. if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) {
  456. sec_protocol_options_set_min_tls_protocol_version(options.securityProtocolOptions, .TLSv12)
  457. } else {
  458. sec_protocol_options_set_tls_min_version(options.securityProtocolOptions, .tlsProtocol12)
  459. }
  460. for `protocol` in GRPCApplicationProtocolIdentifier.server {
  461. sec_protocol_options_add_tls_application_protocol(
  462. options.securityProtocolOptions,
  463. `protocol`
  464. )
  465. }
  466. return GRPCTLSConfiguration.makeServerConfigurationBackedByNetworkFramework(options: options)
  467. }
  468. @available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
  469. public static func makeServerConfigurationBackedByNetworkFramework(
  470. options: NWProtocolTLS.Options
  471. ) -> GRPCTLSConfiguration {
  472. let network = NetworkConfiguration(options: options, hostnameOverride: nil)
  473. return GRPCTLSConfiguration(backend: .network(network))
  474. }
  475. @available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
  476. internal mutating func updateNetworkLocalIdentity(to identity: SecIdentity) {
  477. self.modifyingNetworkConfiguration {
  478. sec_protocol_options_set_local_identity(
  479. $0.options.securityProtocolOptions,
  480. sec_identity_create(identity)!
  481. )
  482. }
  483. }
  484. @available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
  485. internal mutating func updateNetworkVerifyCallbackWithQueue(
  486. callback: @escaping sec_protocol_verify_t,
  487. queue: DispatchQueue
  488. ) {
  489. self.modifyingNetworkConfiguration {
  490. sec_protocol_options_set_verify_block(
  491. $0.options.securityProtocolOptions,
  492. callback,
  493. queue
  494. )
  495. }
  496. }
  497. private mutating func modifyingNetworkConfiguration(
  498. _ modify: (inout NetworkConfiguration) -> Void
  499. ) {
  500. switch self.backend {
  501. case var .network(_configuration):
  502. modify(&_configuration)
  503. self.backend = .network(_configuration)
  504. case .nio:
  505. preconditionFailure()
  506. }
  507. }
  508. }
  509. #endif
  510. #if canImport(Network)
  511. extension GRPCTLSConfiguration {
  512. @available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
  513. internal func applyNetworkTLSOptions(
  514. to bootstrap: NIOTSConnectionBootstrap
  515. ) -> NIOTSConnectionBootstrap {
  516. switch self.backend {
  517. case let .network(_configuration):
  518. return bootstrap.tlsOptions(_configuration.options)
  519. case .nio:
  520. // We're using NIOSSL with Network.framework; that's okay and permitted for backwards
  521. // compatibility.
  522. return bootstrap
  523. }
  524. }
  525. @available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
  526. internal func applyNetworkTLSOptions(
  527. to bootstrap: NIOTSListenerBootstrap
  528. ) -> NIOTSListenerBootstrap {
  529. switch self.backend {
  530. case let .network(_configuration):
  531. return bootstrap.tlsOptions(_configuration.options)
  532. case .nio:
  533. // We're using NIOSSL with Network.framework; that's okay and permitted for backwards
  534. // compatibility.
  535. return bootstrap
  536. }
  537. }
  538. }
  539. @available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
  540. extension NIOTSConnectionBootstrap {
  541. internal func tlsOptions(
  542. from _configuration: GRPCTLSConfiguration
  543. ) -> NIOTSConnectionBootstrap {
  544. return _configuration.applyNetworkTLSOptions(to: self)
  545. }
  546. }
  547. @available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
  548. extension NIOTSListenerBootstrap {
  549. internal func tlsOptions(
  550. from _configuration: GRPCTLSConfiguration
  551. ) -> NIOTSListenerBootstrap {
  552. return _configuration.applyNetworkTLSOptions(to: self)
  553. }
  554. }
  555. #endif