GRPCWebToHTTP2StateMachineTests.swift 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  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. @testable import GRPC
  17. import NIOCore
  18. import NIOHPACK
  19. import NIOHTTP1
  20. import NIOHTTP2
  21. import XCTest
  22. final class GRPCWebToHTTP2StateMachineTests: GRPCTestCase {
  23. fileprivate typealias StateMachine = GRPCWebToHTTP2ServerCodec.StateMachine
  24. private let allocator = ByteBufferAllocator()
  25. private func makeStateMachine(scheme: String = "http") -> StateMachine {
  26. return StateMachine(scheme: scheme)
  27. }
  28. private func makeRequestHead(
  29. version: HTTPVersion = .http1_1,
  30. method: HTTPMethod = .POST,
  31. uri: String,
  32. headers: HTTPHeaders = [:]
  33. ) -> HTTPServerRequestPart {
  34. return .head(.init(version: version, method: method, uri: uri, headers: headers))
  35. }
  36. // MARK: - grpc-web
  37. func test_gRPCWeb_requestHeaders() {
  38. var state = self.makeStateMachine(scheme: "http")
  39. let head = self.makeRequestHead(method: .POST, uri: "foo", headers: ["host": "localhost"])
  40. let action = state.processInbound(serverRequestPart: head, allocator: self.allocator)
  41. action.assertRead { payload in
  42. payload.assertHeaders { payload in
  43. XCTAssertFalse(payload.endStream)
  44. XCTAssertEqual(payload.headers[canonicalForm: ":path"], ["foo"])
  45. XCTAssertEqual(payload.headers[canonicalForm: ":method"], ["POST"])
  46. XCTAssertEqual(payload.headers[canonicalForm: ":scheme"], ["http"])
  47. XCTAssertEqual(payload.headers[canonicalForm: ":authority"], ["localhost"])
  48. }
  49. }
  50. }
  51. func test_gRPCWeb_requestBody() {
  52. var state = self.makeStateMachine()
  53. let head = self.makeRequestHead(
  54. uri: "foo",
  55. headers: ["content-type": "application/grpc-web"]
  56. )
  57. state.processInbound(serverRequestPart: head, allocator: self.allocator).assertRead {
  58. $0.assertHeaders()
  59. }
  60. let b1 = ByteBuffer(string: "hello")
  61. for _ in 0 ..< 5 {
  62. state.processInbound(serverRequestPart: .body(b1), allocator: self.allocator).assertRead {
  63. $0.assertData {
  64. XCTAssertFalse($0.endStream)
  65. $0.data.assertByteBuffer { buffer in
  66. var buffer = buffer
  67. XCTAssertEqual(buffer.readString(length: buffer.readableBytes), "hello")
  68. }
  69. }
  70. }
  71. }
  72. state.processInbound(serverRequestPart: .end(nil), allocator: self.allocator).assertRead {
  73. $0.assertEmptyDataWithEndStream()
  74. }
  75. }
  76. private func checkResponseHeaders(
  77. from state: StateMachine,
  78. expectConnectionCloseHeader: Bool,
  79. line: UInt = #line
  80. ) {
  81. var state = state
  82. state.processOutbound(
  83. framePayload: .headers(.init(headers: [":status": "200"])),
  84. promise: nil,
  85. allocator: self.allocator
  86. ).assertWrite { write in
  87. write.part.assertHead {
  88. XCTAssertEqual($0.status, .ok, line: line)
  89. XCTAssertFalse($0.headers.contains(name: ":status"), line: line)
  90. if expectConnectionCloseHeader {
  91. XCTAssertEqual($0.headers[canonicalForm: "connection"], ["close"], line: line)
  92. } else {
  93. XCTAssertFalse($0.headers.contains(name: "connection"), line: line)
  94. }
  95. }
  96. XCTAssertNil(write.additionalPart, line: line)
  97. XCTAssertFalse(write.closeChannel, line: line)
  98. }
  99. }
  100. func test_gRPCWeb_responseHeaders() {
  101. for connectionClose in [true, false] {
  102. let headers: HTTPHeaders = connectionClose ? ["connection": "close"] : [:]
  103. let requestHead = self.makeRequestHead(uri: "/echo", headers: headers)
  104. var state = self.makeStateMachine()
  105. state.processInbound(serverRequestPart: requestHead, allocator: self.allocator).assertRead()
  106. self.checkResponseHeaders(from: state, expectConnectionCloseHeader: connectionClose)
  107. // Do it again with the request stream closed.
  108. state.processInbound(serverRequestPart: .end(nil), allocator: self.allocator).assertRead()
  109. self.checkResponseHeaders(from: state, expectConnectionCloseHeader: connectionClose)
  110. }
  111. }
  112. private func checkTrailersOnlyResponse(
  113. from state: StateMachine,
  114. expectConnectionCloseHeader: Bool,
  115. line: UInt = #line
  116. ) {
  117. var state = state
  118. state.processOutbound(
  119. framePayload: .headers(.init(headers: [":status": "415"], endStream: true)),
  120. promise: nil,
  121. allocator: self.allocator
  122. ).assertWrite { write in
  123. write.part.assertHead {
  124. XCTAssertEqual($0.status, .unsupportedMediaType, line: line)
  125. XCTAssertFalse($0.headers.contains(name: ":status"), line: line)
  126. if expectConnectionCloseHeader {
  127. XCTAssertEqual($0.headers[canonicalForm: "connection"], ["close"], line: line)
  128. } else {
  129. XCTAssertFalse($0.headers.contains(name: "connection"), line: line)
  130. }
  131. }
  132. // Should also send end.
  133. write.additionalPart.assertSome { $0.assertEnd() }
  134. XCTAssertEqual(write.closeChannel, expectConnectionCloseHeader, line: line)
  135. }
  136. }
  137. func test_gRPCWeb_responseTrailersOnly() {
  138. for connectionClose in [true, false] {
  139. let headers: HTTPHeaders = connectionClose ? ["connection": "close"] : [:]
  140. let requestHead = self.makeRequestHead(uri: "/echo", headers: headers)
  141. var state = self.makeStateMachine()
  142. state.processInbound(serverRequestPart: requestHead, allocator: self.allocator).assertRead()
  143. self.checkTrailersOnlyResponse(from: state, expectConnectionCloseHeader: connectionClose)
  144. // Do it again with the request stream closed.
  145. state.processInbound(serverRequestPart: .end(nil), allocator: self.allocator).assertRead()
  146. self.checkTrailersOnlyResponse(from: state, expectConnectionCloseHeader: connectionClose)
  147. }
  148. }
  149. private func checkGRPCWebResponseData(from state: StateMachine, line: UInt = #line) {
  150. var state = state
  151. for i in 0 ..< 10 {
  152. let buffer = ByteBuffer(string: "foo-\(i)")
  153. state.processOutbound(
  154. framePayload: .data(.init(data: .byteBuffer(buffer))),
  155. promise: nil,
  156. allocator: self.allocator
  157. ).assertWrite { write in
  158. write.part.assertBody {
  159. XCTAssertEqual($0, buffer, line: line)
  160. }
  161. XCTAssertNil(write.additionalPart, line: line)
  162. XCTAssertFalse(write.closeChannel, line: line)
  163. }
  164. }
  165. }
  166. func test_gRPCWeb_responseData() {
  167. var state = self.makeStateMachine()
  168. let requestHead = self.makeRequestHead(
  169. uri: "/echo",
  170. headers: ["content-type": "application/grpc-web"]
  171. )
  172. state.processInbound(serverRequestPart: requestHead, allocator: self.allocator).assertRead()
  173. state.processOutbound(
  174. framePayload: .headers(.init(headers: [":status": "200"])),
  175. promise: nil,
  176. allocator: self.allocator
  177. ).assertWrite()
  178. // Request stream is open.
  179. self.checkGRPCWebResponseData(from: state)
  180. // Close request stream and test again.
  181. state.processInbound(serverRequestPart: .end(nil), allocator: self.allocator).assertRead()
  182. self.checkGRPCWebResponseData(from: state)
  183. }
  184. private func checkGRPCWebResponseTrailers(
  185. from state: StateMachine,
  186. expectChannelClose: Bool,
  187. line: UInt = #line
  188. ) {
  189. var state = state
  190. state.processOutbound(
  191. framePayload: .headers(.init(headers: ["grpc-status": "0"], endStream: true)),
  192. promise: nil,
  193. allocator: self.allocator
  194. ).assertWrite { write in
  195. write.part.assertBody { buffer in
  196. var buffer = buffer
  197. let trailers = buffer.readLengthPrefixedMessage().map { String(buffer: $0) }
  198. XCTAssertEqual(trailers, "grpc-status: 0\r\n")
  199. }
  200. XCTAssertEqual(write.closeChannel, expectChannelClose)
  201. }
  202. }
  203. func test_gRPCWeb_responseTrailers() {
  204. for connectionClose in [true, false] {
  205. let headers: HTTPHeaders = connectionClose ? ["connection": "close"] : [:]
  206. let requestHead = self.makeRequestHead(uri: "/echo", headers: headers)
  207. var state = self.makeStateMachine()
  208. state.processInbound(serverRequestPart: requestHead, allocator: self.allocator).assertRead()
  209. state.processOutbound(
  210. framePayload: .headers(.init(headers: [":status": "200"])),
  211. promise: nil,
  212. allocator: self.allocator
  213. ).assertWrite()
  214. // Request stream is open.
  215. self.checkGRPCWebResponseTrailers(from: state, expectChannelClose: connectionClose)
  216. // Check again with request stream closed.
  217. state.processInbound(serverRequestPart: .end(nil), allocator: self.allocator).assertRead()
  218. self.checkGRPCWebResponseTrailers(from: state, expectChannelClose: connectionClose)
  219. }
  220. }
  221. // MARK: - grpc-web-text
  222. func test_gRPCWebText_requestBody() {
  223. var state = self.makeStateMachine()
  224. let head = self.makeRequestHead(
  225. uri: "foo",
  226. headers: ["content-type": "application/grpc-web-text"]
  227. )
  228. state.processInbound(serverRequestPart: head, allocator: self.allocator).assertRead {
  229. $0.assertHeaders()
  230. }
  231. let expected = ["hel", "lo"]
  232. let buffers = [ByteBuffer(string: "aGVsb"), ByteBuffer(string: "G8=")]
  233. for (buffer, expected) in zip(buffers, expected) {
  234. state.processInbound(serverRequestPart: .body(buffer), allocator: self.allocator).assertRead {
  235. $0.assertData {
  236. XCTAssertFalse($0.endStream)
  237. $0.data.assertByteBuffer { buffer in
  238. var buffer = buffer
  239. XCTAssertEqual(buffer.readString(length: buffer.readableBytes), expected)
  240. }
  241. }
  242. }
  243. }
  244. // If there's not enough to decode, there's nothing to do.
  245. let buffer = ByteBuffer(string: "a")
  246. state.processInbound(serverRequestPart: .body(buffer), allocator: self.allocator).assertNone()
  247. state.processInbound(serverRequestPart: .end(nil), allocator: self.allocator).assertRead {
  248. $0.assertEmptyDataWithEndStream()
  249. }
  250. }
  251. private func checkResponseDataAndTrailersForGRPCWebText(
  252. from state: StateMachine,
  253. line: UInt = #line
  254. ) {
  255. var state = state
  256. state.processOutbound(
  257. framePayload: .headers(.init(headers: [":status": "200"])),
  258. promise: nil,
  259. allocator: self.allocator
  260. ).assertWrite()
  261. // Write some bytes.
  262. for text in ["hello", ", world!"] {
  263. let buffer = ByteBuffer(string: text)
  264. state.processOutbound(
  265. framePayload: .data(.init(data: .byteBuffer(buffer))),
  266. promise: nil,
  267. allocator: self.allocator
  268. ).assertCompletePromise { error in
  269. XCTAssertNil(error)
  270. }
  271. }
  272. state.processOutbound(
  273. framePayload: .headers(.init(headers: ["grpc-status": "0"], endStream: true)),
  274. promise: nil,
  275. allocator: self.allocator
  276. ).assertWrite { write in
  277. // The response is encoded by:
  278. // - accumulating the bytes of request messages (these would normally be gRPC length prefixed
  279. // messages)
  280. // - appending a 'trailers' byte (0x80)
  281. // - appending the UInt32 length of the trailers when encoded as HTTP/1 header lines
  282. // - the encoded headers
  283. write.part.assertBody { buffer in
  284. var buffer = buffer
  285. let base64Encoded = buffer.readString(length: buffer.readableBytes)!
  286. XCTAssertEqual(base64Encoded, "aGVsbG8sIHdvcmxkIYAAAAAQZ3JwYy1zdGF0dXM6IDANCg==")
  287. let data = Data(base64Encoded: base64Encoded)!
  288. buffer.writeData(data)
  289. XCTAssertEqual(buffer.readString(length: 13), "hello, world!")
  290. XCTAssertEqual(buffer.readInteger(), UInt8(0x80))
  291. XCTAssertEqual(buffer.readInteger(), UInt32(16))
  292. XCTAssertEqual(buffer.readString(length: 16), "grpc-status: 0\r\n")
  293. XCTAssertEqual(buffer.readableBytes, 0)
  294. }
  295. // There should be an end now.
  296. write.additionalPart.assertSome { $0.assertEnd() }
  297. XCTAssertFalse(write.closeChannel)
  298. }
  299. }
  300. func test_gRPCWebText_responseDataAndTrailers() {
  301. var state = self.makeStateMachine()
  302. let requestHead = self.makeRequestHead(
  303. uri: "/echo",
  304. headers: ["content-type": "application/grpc-web-text"]
  305. )
  306. state.processInbound(serverRequestPart: requestHead, allocator: self.allocator).assertRead()
  307. // Request stream is still open.
  308. self.checkResponseDataAndTrailersForGRPCWebText(from: state)
  309. // Check again with request stream closed.
  310. state.processInbound(serverRequestPart: .end(nil), allocator: self.allocator).assertRead()
  311. self.checkResponseDataAndTrailersForGRPCWebText(from: state)
  312. }
  313. // MARK: - General
  314. func test_requestPartsAfterServerClosed() {
  315. var state = self.makeStateMachine()
  316. let requestHead = self.makeRequestHead(uri: "/echo")
  317. state.processInbound(serverRequestPart: requestHead, allocator: self.allocator).assertRead()
  318. // Close the response stream.
  319. state.processOutbound(
  320. framePayload: .headers(.init(headers: [":status": "415"], endStream: true)),
  321. promise: nil,
  322. allocator: self.allocator
  323. ).assertWrite()
  324. state.processInbound(
  325. serverRequestPart: .body(ByteBuffer(string: "hello world")),
  326. allocator: self.allocator
  327. ).assertRead {
  328. $0.assertData {
  329. XCTAssertFalse($0.endStream)
  330. $0.data.assertByteBuffer { buffer in
  331. XCTAssertTrue(buffer.readableBytesView.elementsEqual("hello world".utf8))
  332. }
  333. }
  334. }
  335. state.processInbound(serverRequestPart: .end(nil), allocator: self.allocator).assertRead {
  336. $0.assertEmptyDataWithEndStream()
  337. }
  338. }
  339. func test_responsePartsAfterServerClosed() {
  340. var state = self.makeStateMachine()
  341. let requestHead = self.makeRequestHead(uri: "/echo")
  342. state.processInbound(serverRequestPart: requestHead, allocator: self.allocator).assertRead()
  343. // Close the response stream.
  344. state.processOutbound(
  345. framePayload: .headers(.init(headers: [":status": "415"], endStream: true)),
  346. promise: nil,
  347. allocator: self.allocator
  348. ).assertWrite()
  349. // More writes should be told to fail their promise.
  350. state.processOutbound(
  351. framePayload: .headers(.init(headers: .init())), promise: nil, allocator: self.allocator
  352. ).assertCompletePromise { error in
  353. XCTAssertNotNil(error)
  354. }
  355. state.processOutbound(
  356. framePayload: .data(.init(data: .byteBuffer(.init()))),
  357. promise: nil,
  358. allocator: self.allocator
  359. ).assertCompletePromise { error in
  360. XCTAssertNotNil(error)
  361. }
  362. }
  363. func test_handleMultipleRequests() {
  364. func sendRequestHead(_ state: inout StateMachine, contentType: ContentType) -> StateMachine
  365. .Action {
  366. let requestHead = self.makeRequestHead(
  367. uri: "/echo", headers: ["content-type": contentType.canonicalValue]
  368. )
  369. return state.processInbound(serverRequestPart: requestHead, allocator: self.allocator)
  370. }
  371. func sendRequestBody(_ state: inout StateMachine, buffer: ByteBuffer) -> StateMachine.Action {
  372. return state.processInbound(serverRequestPart: .body(buffer), allocator: self.allocator)
  373. }
  374. func sendRequestEnd(_ state: inout StateMachine) -> StateMachine.Action {
  375. return state.processInbound(serverRequestPart: .end(nil), allocator: self.allocator)
  376. }
  377. func sendResponseHeaders(
  378. _ state: inout StateMachine,
  379. headers: HPACKHeaders,
  380. endStream: Bool = false
  381. ) -> StateMachine.Action {
  382. return state.processOutbound(
  383. framePayload: .headers(.init(headers: headers, endStream: endStream)),
  384. promise: nil,
  385. allocator: self.allocator
  386. )
  387. }
  388. func sendResponseData(
  389. _ state: inout StateMachine,
  390. buffer: ByteBuffer
  391. ) -> StateMachine.Action {
  392. return state.processOutbound(
  393. framePayload: .data(.init(data: .byteBuffer(buffer))),
  394. promise: nil,
  395. allocator: self.allocator
  396. )
  397. }
  398. var state = self.makeStateMachine()
  399. // gRPC-Web, all request parts then all response parts.
  400. sendRequestHead(&state, contentType: .webProtobuf).assertRead()
  401. sendRequestBody(&state, buffer: .init(string: "hello")).assertRead()
  402. sendRequestEnd(&state).assertRead()
  403. sendResponseHeaders(&state, headers: [":status": "200"]).assertWrite()
  404. sendResponseData(&state, buffer: .init(string: "bye")).assertWrite()
  405. sendResponseHeaders(&state, headers: ["grpc-status": "0"], endStream: true).assertWrite()
  406. // gRPC-Web text, all requests then all response parts.
  407. sendRequestHead(&state, contentType: .webTextProtobuf).assertRead()
  408. sendRequestBody(&state, buffer: .init(string: "hello")).assertRead()
  409. sendRequestEnd(&state).assertRead()
  410. sendResponseHeaders(&state, headers: [":status": "200"]).assertWrite()
  411. // nothing; buffered and sent with end.
  412. sendResponseData(&state, buffer: .init(string: "bye")).assertCompletePromise()
  413. sendResponseHeaders(&state, headers: ["grpc-status": "0"], endStream: true).assertWrite()
  414. // gRPC-Web, interleaving
  415. sendRequestHead(&state, contentType: .webProtobuf).assertRead()
  416. sendResponseHeaders(&state, headers: [":status": "200"]).assertWrite()
  417. sendRequestBody(&state, buffer: .init(string: "hello")).assertRead()
  418. sendResponseData(&state, buffer: .init(string: "bye")).assertWrite()
  419. sendRequestEnd(&state).assertRead()
  420. sendResponseHeaders(&state, headers: ["grpc-status": "0"], endStream: true).assertWrite()
  421. // gRPC-Web text, interleaving
  422. sendRequestHead(&state, contentType: .webTextProtobuf).assertRead()
  423. sendResponseHeaders(&state, headers: [":status": "200"]).assertWrite()
  424. sendRequestBody(&state, buffer: .init(string: "hello")).assertRead()
  425. sendResponseData(&state, buffer: .init(string: "bye")).assertCompletePromise()
  426. sendRequestEnd(&state).assertRead()
  427. sendResponseHeaders(&state, headers: ["grpc-status": "0"], endStream: true).assertWrite()
  428. // gRPC-Web, server closes immediately.
  429. sendRequestHead(&state, contentType: .webProtobuf).assertRead()
  430. sendResponseHeaders(&state, headers: [":status": "415"], endStream: true).assertWrite()
  431. sendRequestBody(&state, buffer: .init(string: "hello")).assertRead()
  432. sendRequestEnd(&state).assertRead()
  433. // gRPC-Web text, server closes immediately.
  434. sendRequestHead(&state, contentType: .webTextProtobuf).assertRead()
  435. sendResponseHeaders(&state, headers: [":status": "415"], endStream: true).assertWrite()
  436. sendRequestBody(&state, buffer: .init(string: "hello")).assertRead()
  437. sendRequestEnd(&state).assertRead()
  438. }
  439. }
  440. // MARK: - Assertions
  441. extension GRPCWebToHTTP2ServerCodec.StateMachine.Action {
  442. func assertRead(
  443. file: StaticString = #filePath,
  444. line: UInt = #line,
  445. verify: (HTTP2Frame.FramePayload) -> Void = { _ in }
  446. ) {
  447. if case let .fireChannelRead(payload) = self {
  448. verify(payload)
  449. } else {
  450. XCTFail("Expected '.fireChannelRead' but got '\(self)'", file: file, line: line)
  451. }
  452. }
  453. func assertWrite(
  454. file: StaticString = #filePath,
  455. line: UInt = #line,
  456. verify: (Write) -> Void = { _ in }
  457. ) {
  458. if case let .write(write) = self {
  459. verify(write)
  460. } else {
  461. XCTFail("Expected '.write' but got '\(self)'", file: file, line: line)
  462. }
  463. }
  464. func assertCompletePromise(
  465. file: StaticString = #filePath,
  466. line: UInt = #line,
  467. verify: (Error?) -> Void = { _ in }
  468. ) {
  469. if case let .completePromise(_, result) = self {
  470. do {
  471. try result.get()
  472. verify(nil)
  473. } catch {
  474. verify(error)
  475. }
  476. } else {
  477. XCTFail("Expected '.completePromise' but got '\(self)'", file: file, line: line)
  478. }
  479. }
  480. func assertNone(
  481. file: StaticString = #filePath,
  482. line: UInt = #line
  483. ) {
  484. if case .none = self {
  485. ()
  486. } else {
  487. XCTFail("Expected '.none' but got '\(self)'", file: file, line: line)
  488. }
  489. }
  490. }
  491. extension HTTP2Frame.FramePayload {
  492. func assertHeaders(
  493. file: StaticString = #filePath,
  494. line: UInt = #line,
  495. verify: (Headers) -> Void = { _ in }
  496. ) {
  497. if case let .headers(headers) = self {
  498. verify(headers)
  499. } else {
  500. XCTFail("Expected '.headers' but got '\(self)'", file: file, line: line)
  501. }
  502. }
  503. func assertData(
  504. file: StaticString = #filePath,
  505. line: UInt = #line,
  506. verify: (Data) -> Void = { _ in }
  507. ) {
  508. if case let .data(data) = self {
  509. verify(data)
  510. } else {
  511. XCTFail("Expected '.data' but got '\(self)'", file: file, line: line)
  512. }
  513. }
  514. func assertEmptyDataWithEndStream(
  515. file: StaticString = #filePath,
  516. line: UInt = #line
  517. ) {
  518. self.assertData(file: file, line: line) {
  519. XCTAssertTrue($0.endStream)
  520. $0.data.assertByteBuffer { buffer in
  521. XCTAssertEqual(buffer.readableBytes, 0)
  522. }
  523. }
  524. }
  525. }
  526. extension HTTPServerResponsePart {
  527. func assertHead(
  528. file: StaticString = #filePath,
  529. line: UInt = #line,
  530. verify: (HTTPResponseHead) -> Void = { _ in }
  531. ) {
  532. if case let .head(head) = self {
  533. verify(head)
  534. } else {
  535. XCTFail("Expected '.head' but got '\(self)'", file: file, line: line)
  536. }
  537. }
  538. func assertBody(
  539. file: StaticString = #filePath,
  540. line: UInt = #line,
  541. verify: (ByteBuffer) -> Void = { _ in }
  542. ) {
  543. if case let .body(.byteBuffer(buffer)) = self {
  544. verify(buffer)
  545. } else {
  546. XCTFail("Expected '.body(.byteBuffer)' but got '\(self)'", file: file, line: line)
  547. }
  548. }
  549. func assertEnd(
  550. file: StaticString = #filePath,
  551. line: UInt = #line,
  552. verify: (HTTPHeaders?) -> Void = { _ in }
  553. ) {
  554. if case let .end(trailers) = self {
  555. verify(trailers)
  556. } else {
  557. XCTFail("Expected '.end' but got '\(self)'", file: file, line: line)
  558. }
  559. }
  560. }
  561. extension IOData {
  562. func assertByteBuffer(
  563. file: StaticString = #filePath,
  564. line: UInt = #line,
  565. verify: (ByteBuffer) -> Void = { _ in }
  566. ) {
  567. if case let .byteBuffer(buffer) = self {
  568. verify(buffer)
  569. } else {
  570. XCTFail("Expected '.byteBuffer' but got '\(self)'", file: file, line: line)
  571. }
  572. }
  573. }
  574. extension Optional {
  575. func assertSome(
  576. file: StaticString = #filePath,
  577. line: UInt = #line,
  578. verify: (Wrapped) -> Void = { _ in }
  579. ) {
  580. switch self {
  581. case let .some(wrapped):
  582. verify(wrapped)
  583. case .none:
  584. XCTFail("Expected '.some' but got 'nil'", file: file, line: line)
  585. }
  586. }
  587. }
  588. extension ByteBuffer {
  589. mutating func readLengthPrefixedMessage() -> ByteBuffer? {
  590. // Read off and ignore the compression byte.
  591. if self.readInteger(as: UInt8.self) == nil {
  592. return nil
  593. }
  594. return self.readLengthPrefixedSlice(as: UInt32.self)
  595. }
  596. }