ReflectionService.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. /*
  2. * Copyright 2023, 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 DequeModule
  17. import Foundation
  18. import GRPC
  19. import SwiftProtobuf
  20. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  21. public final class ReflectionService: CallHandlerProvider, Sendable {
  22. private let reflectionService: ReflectionServiceProvider
  23. public var serviceName: Substring {
  24. self.reflectionService.serviceName
  25. }
  26. /// Creates a `ReflectionService` by loading serialized reflection data created by `protoc-gen-grpc-swift`.
  27. ///
  28. /// You can generate serialized reflection data using the `protoc-gen-grpc-swift` plugin for `protoc` by
  29. /// setting the `ReflectionData` option to `True`. The paths provided should be absolute or relative to the
  30. /// current working directory.
  31. ///
  32. /// - Parameter filePaths: The paths to files containing serialized reflection data.
  33. ///
  34. /// - Throws: When a file can't be read from disk or parsed.
  35. public init(serializedFileDescriptorProtoFilePaths filePaths: [String]) throws {
  36. let fileDescriptorProtos = try ReflectionService.readSerializedFileDescriptorProtos(
  37. atPaths: filePaths
  38. )
  39. self.reflectionService = try ReflectionServiceProvider(
  40. fileDescriptorProtos: fileDescriptorProtos
  41. )
  42. }
  43. public init(fileDescriptors: [Google_Protobuf_FileDescriptorProto]) throws {
  44. self.reflectionService = try ReflectionServiceProvider(fileDescriptorProtos: fileDescriptors)
  45. }
  46. public func handle(
  47. method name: Substring,
  48. context: GRPC.CallHandlerContext
  49. ) -> GRPC.GRPCServerHandlerProtocol? {
  50. self.reflectionService.handle(method: name, context: context)
  51. }
  52. }
  53. internal struct ReflectionServiceData: Sendable {
  54. internal struct FileDescriptorProtoData: Sendable {
  55. internal var serializedFileDescriptorProto: Data
  56. internal var dependencyFileNames: [String]
  57. }
  58. private struct ExtensionDescriptor: Sendable, Hashable {
  59. internal let extendeeTypeName: String
  60. internal let fieldNumber: Int32
  61. }
  62. internal var fileDescriptorDataByFilename: [String: FileDescriptorProtoData]
  63. internal var serviceNames: [String]
  64. internal var fileNameBySymbol: [String: String]
  65. // Stores the file names for each extension identified by an ExtensionDescriptor object.
  66. private var fileNameByExtensionDescriptor: [ExtensionDescriptor: String]
  67. // Stores the field numbers for each type that has extensions.
  68. private var fieldNumbersByType: [String: [Int32]]
  69. internal init(fileDescriptors: [Google_Protobuf_FileDescriptorProto]) throws {
  70. self.serviceNames = []
  71. self.fileDescriptorDataByFilename = [:]
  72. self.fileNameBySymbol = [:]
  73. self.fileNameByExtensionDescriptor = [:]
  74. self.fieldNumbersByType = [:]
  75. for fileDescriptorProto in fileDescriptors {
  76. let serializedFileDescriptorProto: Data
  77. do {
  78. serializedFileDescriptorProto = try fileDescriptorProto.serializedData()
  79. } catch {
  80. throw GRPCStatus(
  81. code: .invalidArgument,
  82. message:
  83. "The \(fileDescriptorProto.name) could not be serialized."
  84. )
  85. }
  86. let protoData = FileDescriptorProtoData(
  87. serializedFileDescriptorProto: serializedFileDescriptorProto,
  88. dependencyFileNames: fileDescriptorProto.dependency
  89. )
  90. self.fileDescriptorDataByFilename[fileDescriptorProto.name] = protoData
  91. self.serviceNames.append(contentsOf: fileDescriptorProto.service.map { $0.name })
  92. // Populating the <symbol, file name> dictionary.
  93. for qualifiedSybolName in fileDescriptorProto.qualifiedSymbolNames {
  94. let oldValue = self.fileNameBySymbol.updateValue(
  95. fileDescriptorProto.name,
  96. forKey: qualifiedSybolName
  97. )
  98. if let oldValue = oldValue {
  99. throw GRPCStatus(
  100. code: .alreadyExists,
  101. message:
  102. "The \(qualifiedSybolName) symbol from \(fileDescriptorProto.name) already exists in \(oldValue)."
  103. )
  104. }
  105. }
  106. for typeName in fileDescriptorProto.qualifiedMessageTypes {
  107. self.fieldNumbersByType[typeName] = []
  108. }
  109. // Populating the <extension descriptor, file name> dictionary and the <typeName, [FieldNumber]> one.
  110. for `extension` in fileDescriptorProto.extension {
  111. let typeName = String(`extension`.extendee.drop(while: { $0 == "." }))
  112. let extensionDescriptor = ExtensionDescriptor(
  113. extendeeTypeName: typeName,
  114. fieldNumber: `extension`.number
  115. )
  116. let oldFileName = self.fileNameByExtensionDescriptor.updateValue(
  117. fileDescriptorProto.name,
  118. forKey: extensionDescriptor
  119. )
  120. if let oldFileName = oldFileName {
  121. throw GRPCStatus(
  122. code: .alreadyExists,
  123. message:
  124. """
  125. The extension of the \(extensionDescriptor.extendeeTypeName) type with the field number equal to \
  126. \(extensionDescriptor.fieldNumber) from \(fileDescriptorProto.name) already exists in \(oldFileName).
  127. """
  128. )
  129. }
  130. self.fieldNumbersByType[typeName, default: []].append(`extension`.number)
  131. }
  132. }
  133. }
  134. internal func serialisedFileDescriptorProtosForDependenciesOfFile(
  135. named fileName: String
  136. ) -> Result<[Data], GRPCStatus> {
  137. var toVisit = Deque<String>()
  138. var visited = Set<String>()
  139. var serializedFileDescriptorProtos: [Data] = []
  140. toVisit.append(fileName)
  141. while let currentFileName = toVisit.popFirst() {
  142. if let protoData = self.fileDescriptorDataByFilename[currentFileName] {
  143. toVisit.append(
  144. contentsOf: protoData.dependencyFileNames
  145. .filter { name in
  146. return !visited.contains(name)
  147. }
  148. )
  149. let serializedFileDescriptorProto = protoData.serializedFileDescriptorProto
  150. serializedFileDescriptorProtos.append(serializedFileDescriptorProto)
  151. } else {
  152. return .failure(
  153. GRPCStatus(
  154. code: .notFound,
  155. message: "The provided file or a dependency of the provided file could not be found."
  156. )
  157. )
  158. }
  159. visited.insert(currentFileName)
  160. }
  161. return .success(serializedFileDescriptorProtos)
  162. }
  163. internal func nameOfFileContainingSymbol(named symbolName: String) -> Result<String, GRPCStatus> {
  164. guard let fileName = self.fileNameBySymbol[symbolName] else {
  165. return .failure(
  166. GRPCStatus(
  167. code: .notFound,
  168. message: "The provided symbol could not be found."
  169. )
  170. )
  171. }
  172. return .success(fileName)
  173. }
  174. internal func nameOfFileContainingExtension(
  175. extendeeName: String,
  176. fieldNumber number: Int32
  177. ) -> Result<String, GRPCStatus> {
  178. let key = ExtensionDescriptor(extendeeTypeName: extendeeName, fieldNumber: number)
  179. guard let fileName = self.fileNameByExtensionDescriptor[key] else {
  180. return .failure(
  181. GRPCStatus(
  182. code: .notFound,
  183. message: "The provided extension could not be found."
  184. )
  185. )
  186. }
  187. return .success(fileName)
  188. }
  189. // Returns an empty array if the type has no extensions.
  190. internal func extensionsFieldNumbersOfType(
  191. named typeName: String
  192. ) -> Result<[Int32], GRPCStatus> {
  193. guard let fieldNumbers = self.fieldNumbersByType[typeName] else {
  194. return .failure(
  195. GRPCStatus(
  196. code: .invalidArgument,
  197. message: "The provided type is invalid."
  198. )
  199. )
  200. }
  201. return .success(fieldNumbers)
  202. }
  203. }
  204. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  205. internal final class ReflectionServiceProvider: Grpc_Reflection_V1_ServerReflectionAsyncProvider {
  206. private let protoRegistry: ReflectionServiceData
  207. internal init(fileDescriptorProtos: [Google_Protobuf_FileDescriptorProto]) throws {
  208. self.protoRegistry = try ReflectionServiceData(
  209. fileDescriptors: fileDescriptorProtos
  210. )
  211. }
  212. internal func _findFileByFileName(
  213. _ fileName: String
  214. ) -> Result<Grpc_Reflection_V1_ServerReflectionResponse.OneOf_MessageResponse, GRPCStatus> {
  215. return self.protoRegistry
  216. .serialisedFileDescriptorProtosForDependenciesOfFile(named: fileName)
  217. .map { fileDescriptorProtos in
  218. Grpc_Reflection_V1_ServerReflectionResponse.OneOf_MessageResponse.fileDescriptorResponse(
  219. .with {
  220. $0.fileDescriptorProto = fileDescriptorProtos
  221. }
  222. )
  223. }
  224. }
  225. internal func findFileByFileName(
  226. _ fileName: String,
  227. request: Grpc_Reflection_V1_ServerReflectionRequest
  228. ) -> Grpc_Reflection_V1_ServerReflectionResponse {
  229. let result = self._findFileByFileName(fileName)
  230. return result.makeResponse(request: request)
  231. }
  232. internal func getServicesNames(
  233. request: Grpc_Reflection_V1_ServerReflectionRequest
  234. ) throws -> Grpc_Reflection_V1_ServerReflectionResponse {
  235. var listServicesResponse = Grpc_Reflection_V1_ListServiceResponse()
  236. listServicesResponse.service = self.protoRegistry.serviceNames.map { serviceName in
  237. Grpc_Reflection_V1_ServiceResponse.with {
  238. $0.name = serviceName
  239. }
  240. }
  241. return Grpc_Reflection_V1_ServerReflectionResponse(
  242. request: request,
  243. messageResponse: .listServicesResponse(listServicesResponse)
  244. )
  245. }
  246. internal func findFileBySymbol(
  247. _ symbolName: String,
  248. request: Grpc_Reflection_V1_ServerReflectionRequest
  249. ) -> Grpc_Reflection_V1_ServerReflectionResponse {
  250. let result = self.protoRegistry.nameOfFileContainingSymbol(
  251. named: symbolName
  252. ).flatMap {
  253. self._findFileByFileName($0)
  254. }
  255. return result.makeResponse(request: request)
  256. }
  257. internal func findFileByExtension(
  258. extensionRequest: Grpc_Reflection_V1_ExtensionRequest,
  259. request: Grpc_Reflection_V1_ServerReflectionRequest
  260. ) -> Grpc_Reflection_V1_ServerReflectionResponse {
  261. let result = self.protoRegistry.nameOfFileContainingExtension(
  262. extendeeName: extensionRequest.containingType,
  263. fieldNumber: extensionRequest.extensionNumber
  264. ).flatMap {
  265. self._findFileByFileName($0)
  266. }
  267. return result.makeResponse(request: request)
  268. }
  269. internal func findExtensionsFieldNumbersOfType(
  270. named typeName: String,
  271. request: Grpc_Reflection_V1_ServerReflectionRequest
  272. ) -> Grpc_Reflection_V1_ServerReflectionResponse {
  273. let result = self.protoRegistry.extensionsFieldNumbersOfType(
  274. named: typeName
  275. ).map { fieldNumbers in
  276. Grpc_Reflection_V1_ServerReflectionResponse.OneOf_MessageResponse.allExtensionNumbersResponse(
  277. Grpc_Reflection_V1_ExtensionNumberResponse.with {
  278. $0.baseTypeName = typeName
  279. $0.extensionNumber = fieldNumbers
  280. }
  281. )
  282. }
  283. return result.makeResponse(request: request)
  284. }
  285. internal func serverReflectionInfo(
  286. requestStream: GRPCAsyncRequestStream<Grpc_Reflection_V1_ServerReflectionRequest>,
  287. responseStream: GRPCAsyncResponseStreamWriter<Grpc_Reflection_V1_ServerReflectionResponse>,
  288. context: GRPCAsyncServerCallContext
  289. ) async throws {
  290. for try await request in requestStream {
  291. switch request.messageRequest {
  292. case let .fileByFilename(fileName):
  293. let response = self.findFileByFileName(
  294. fileName,
  295. request: request
  296. )
  297. try await responseStream.send(response)
  298. case .listServices:
  299. let response = try self.getServicesNames(request: request)
  300. try await responseStream.send(response)
  301. case let .fileContainingSymbol(symbolName):
  302. let response = self.findFileBySymbol(
  303. symbolName,
  304. request: request
  305. )
  306. try await responseStream.send(response)
  307. case let .fileContainingExtension(extensionRequest):
  308. let response = self.findFileByExtension(
  309. extensionRequest: extensionRequest,
  310. request: request
  311. )
  312. try await responseStream.send(response)
  313. case let .allExtensionNumbersOfType(typeName):
  314. let response = self.findExtensionsFieldNumbersOfType(
  315. named: typeName,
  316. request: request
  317. )
  318. try await responseStream.send(response)
  319. default:
  320. let response = Grpc_Reflection_V1_ServerReflectionResponse(
  321. request: request,
  322. messageResponse: .errorResponse(
  323. Grpc_Reflection_V1_ErrorResponse.with {
  324. $0.errorCode = Int32(GRPCStatus.Code.unimplemented.rawValue)
  325. $0.errorMessage = "The request is not implemented."
  326. }
  327. )
  328. )
  329. try await responseStream.send(response)
  330. }
  331. }
  332. }
  333. }
  334. extension Grpc_Reflection_V1_ServerReflectionResponse {
  335. init(
  336. request: Grpc_Reflection_V1_ServerReflectionRequest,
  337. messageResponse: Grpc_Reflection_V1_ServerReflectionResponse.OneOf_MessageResponse
  338. ) {
  339. self = .with {
  340. $0.validHost = request.host
  341. $0.originalRequest = request
  342. $0.messageResponse = messageResponse
  343. }
  344. }
  345. }
  346. extension Google_Protobuf_FileDescriptorProto {
  347. var qualifiedServiceAndMethodNames: [String] {
  348. var names: [String] = []
  349. for service in self.service {
  350. names.append(self.package + "." + service.name)
  351. names.append(
  352. contentsOf: service.method
  353. .map { self.package + "." + service.name + "." + $0.name }
  354. )
  355. }
  356. return names
  357. }
  358. var qualifiedMessageTypes: [String] {
  359. return self.messageType.map {
  360. self.package + "." + $0.name
  361. }
  362. }
  363. var qualifiedEnumTypes: [String] {
  364. return self.enumType.map {
  365. self.package + "." + $0.name
  366. }
  367. }
  368. var qualifiedSymbolNames: [String] {
  369. var names = self.qualifiedServiceAndMethodNames
  370. names.append(contentsOf: self.qualifiedMessageTypes)
  371. names.append(contentsOf: self.qualifiedEnumTypes)
  372. return names
  373. }
  374. }
  375. extension Result<Grpc_Reflection_V1_ServerReflectionResponse.OneOf_MessageResponse, GRPCStatus> {
  376. func recover() -> Result<Grpc_Reflection_V1_ServerReflectionResponse.OneOf_MessageResponse, Never>
  377. {
  378. self.flatMapError { status in
  379. let error = Grpc_Reflection_V1_ErrorResponse.with {
  380. $0.errorCode = Int32(status.code.rawValue)
  381. $0.errorMessage = status.message ?? ""
  382. }
  383. return .success(.errorResponse(error))
  384. }
  385. }
  386. func makeResponse(
  387. request: Grpc_Reflection_V1_ServerReflectionRequest
  388. ) -> Grpc_Reflection_V1_ServerReflectionResponse {
  389. let result = self.recover().attachRequest(request)
  390. // Safe to '!' as the failure type is 'Never'.
  391. return try! result.get()
  392. }
  393. }
  394. extension Result
  395. where Success == Grpc_Reflection_V1_ServerReflectionResponse.OneOf_MessageResponse {
  396. func attachRequest(
  397. _ request: Grpc_Reflection_V1_ServerReflectionRequest
  398. ) -> Result<Grpc_Reflection_V1_ServerReflectionResponse, Failure> {
  399. self.map { message in
  400. Grpc_Reflection_V1_ServerReflectionResponse(request: request, messageResponse: message)
  401. }
  402. }
  403. }
  404. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  405. extension ReflectionService {
  406. static func readSerializedFileDescriptorProto(
  407. atPath path: String
  408. ) throws -> Google_Protobuf_FileDescriptorProto {
  409. let fileURL: URL
  410. #if os(Linux)
  411. fileURL = URL(fileURLWithPath: path)
  412. #else
  413. if #available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) {
  414. fileURL = URL(filePath: path, directoryHint: .notDirectory)
  415. } else {
  416. fileURL = URL(fileURLWithPath: path)
  417. }
  418. #endif
  419. let binaryData = try Data(contentsOf: fileURL)
  420. guard let serializedData = Data(base64Encoded: binaryData) else {
  421. throw GRPCStatus(
  422. code: .invalidArgument,
  423. message:
  424. """
  425. The \(path) file contents could not be transformed \
  426. into serialized data representing a file descriptor proto.
  427. """
  428. )
  429. }
  430. return try Google_Protobuf_FileDescriptorProto(serializedData: serializedData)
  431. }
  432. static func readSerializedFileDescriptorProtos(
  433. atPaths paths: [String]
  434. ) throws -> [Google_Protobuf_FileDescriptorProto] {
  435. var fileDescriptorProtos = [Google_Protobuf_FileDescriptorProto]()
  436. fileDescriptorProtos.reserveCapacity(paths.count)
  437. for path in paths {
  438. try fileDescriptorProtos.append(readSerializedFileDescriptorProto(atPath: path))
  439. }
  440. return fileDescriptorProtos
  441. }
  442. }