GRPCWebToHTTP2StateMachineTests.swift 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  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 NIOCore
  17. import NIOHPACK
  18. import NIOHTTP1
  19. import NIOHTTP2
  20. import XCTest
  21. @testable import GRPC
  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())),
  352. promise: nil,
  353. allocator: self.allocator
  354. ).assertCompletePromise { error in
  355. XCTAssertNotNil(error)
  356. }
  357. state.processOutbound(
  358. framePayload: .data(.init(data: .byteBuffer(.init()))),
  359. promise: nil,
  360. allocator: self.allocator
  361. ).assertCompletePromise { error in
  362. XCTAssertNotNil(error)
  363. }
  364. }
  365. func test_handleMultipleRequests() {
  366. func sendRequestHead(
  367. _ state: inout StateMachine,
  368. contentType: ContentType
  369. )
  370. -> StateMachine
  371. .Action
  372. {
  373. let requestHead = self.makeRequestHead(
  374. uri: "/echo",
  375. headers: ["content-type": contentType.canonicalValue]
  376. )
  377. return state.processInbound(serverRequestPart: requestHead, allocator: self.allocator)
  378. }
  379. func sendRequestBody(_ state: inout StateMachine, buffer: ByteBuffer) -> StateMachine.Action {
  380. return state.processInbound(serverRequestPart: .body(buffer), allocator: self.allocator)
  381. }
  382. func sendRequestEnd(_ state: inout StateMachine) -> StateMachine.Action {
  383. return state.processInbound(serverRequestPart: .end(nil), allocator: self.allocator)
  384. }
  385. func sendResponseHeaders(
  386. _ state: inout StateMachine,
  387. headers: HPACKHeaders,
  388. endStream: Bool = false
  389. ) -> StateMachine.Action {
  390. return state.processOutbound(
  391. framePayload: .headers(.init(headers: headers, endStream: endStream)),
  392. promise: nil,
  393. allocator: self.allocator
  394. )
  395. }
  396. func sendResponseData(
  397. _ state: inout StateMachine,
  398. buffer: ByteBuffer
  399. ) -> StateMachine.Action {
  400. return state.processOutbound(
  401. framePayload: .data(.init(data: .byteBuffer(buffer))),
  402. promise: nil,
  403. allocator: self.allocator
  404. )
  405. }
  406. var state = self.makeStateMachine()
  407. // gRPC-Web, all request parts then all response parts.
  408. sendRequestHead(&state, contentType: .webProtobuf).assertRead()
  409. sendRequestBody(&state, buffer: .init(string: "hello")).assertRead()
  410. sendRequestEnd(&state).assertRead()
  411. sendResponseHeaders(&state, headers: [":status": "200"]).assertWrite()
  412. sendResponseData(&state, buffer: .init(string: "bye")).assertWrite()
  413. sendResponseHeaders(&state, headers: ["grpc-status": "0"], endStream: true).assertWrite()
  414. // gRPC-Web text, all requests then all response parts.
  415. sendRequestHead(&state, contentType: .webTextProtobuf).assertRead()
  416. sendRequestBody(&state, buffer: .init(string: "hello")).assertRead()
  417. sendRequestEnd(&state).assertRead()
  418. sendResponseHeaders(&state, headers: [":status": "200"]).assertWrite()
  419. // nothing; buffered and sent with end.
  420. sendResponseData(&state, buffer: .init(string: "bye")).assertCompletePromise()
  421. sendResponseHeaders(&state, headers: ["grpc-status": "0"], endStream: true).assertWrite()
  422. // gRPC-Web, interleaving
  423. sendRequestHead(&state, contentType: .webProtobuf).assertRead()
  424. sendResponseHeaders(&state, headers: [":status": "200"]).assertWrite()
  425. sendRequestBody(&state, buffer: .init(string: "hello")).assertRead()
  426. sendResponseData(&state, buffer: .init(string: "bye")).assertWrite()
  427. sendRequestEnd(&state).assertRead()
  428. sendResponseHeaders(&state, headers: ["grpc-status": "0"], endStream: true).assertWrite()
  429. // gRPC-Web text, interleaving
  430. sendRequestHead(&state, contentType: .webTextProtobuf).assertRead()
  431. sendResponseHeaders(&state, headers: [":status": "200"]).assertWrite()
  432. sendRequestBody(&state, buffer: .init(string: "hello")).assertRead()
  433. sendResponseData(&state, buffer: .init(string: "bye")).assertCompletePromise()
  434. sendRequestEnd(&state).assertRead()
  435. sendResponseHeaders(&state, headers: ["grpc-status": "0"], endStream: true).assertWrite()
  436. // gRPC-Web, server closes immediately.
  437. sendRequestHead(&state, contentType: .webProtobuf).assertRead()
  438. sendResponseHeaders(&state, headers: [":status": "415"], endStream: true).assertWrite()
  439. sendRequestBody(&state, buffer: .init(string: "hello")).assertRead()
  440. sendRequestEnd(&state).assertRead()
  441. // gRPC-Web text, server closes immediately.
  442. sendRequestHead(&state, contentType: .webTextProtobuf).assertRead()
  443. sendResponseHeaders(&state, headers: [":status": "415"], endStream: true).assertWrite()
  444. sendRequestBody(&state, buffer: .init(string: "hello")).assertRead()
  445. sendRequestEnd(&state).assertRead()
  446. }
  447. }
  448. // MARK: - Assertions
  449. extension GRPCWebToHTTP2ServerCodec.StateMachine.Action {
  450. func assertRead(
  451. file: StaticString = #filePath,
  452. line: UInt = #line,
  453. verify: (HTTP2Frame.FramePayload) -> Void = { _ in }
  454. ) {
  455. if case let .fireChannelRead(payload) = self {
  456. verify(payload)
  457. } else {
  458. XCTFail("Expected '.fireChannelRead' but got '\(self)'", file: file, line: line)
  459. }
  460. }
  461. func assertWrite(
  462. file: StaticString = #filePath,
  463. line: UInt = #line,
  464. verify: (Write) -> Void = { _ in }
  465. ) {
  466. if case let .write(write) = self {
  467. verify(write)
  468. } else {
  469. XCTFail("Expected '.write' but got '\(self)'", file: file, line: line)
  470. }
  471. }
  472. func assertCompletePromise(
  473. file: StaticString = #filePath,
  474. line: UInt = #line,
  475. verify: (Error?) -> Void = { _ in }
  476. ) {
  477. if case let .completePromise(_, result) = self {
  478. do {
  479. try result.get()
  480. verify(nil)
  481. } catch {
  482. verify(error)
  483. }
  484. } else {
  485. XCTFail("Expected '.completePromise' but got '\(self)'", file: file, line: line)
  486. }
  487. }
  488. func assertNone(
  489. file: StaticString = #filePath,
  490. line: UInt = #line
  491. ) {
  492. if case .none = self {
  493. ()
  494. } else {
  495. XCTFail("Expected '.none' but got '\(self)'", file: file, line: line)
  496. }
  497. }
  498. }
  499. extension HTTP2Frame.FramePayload {
  500. func assertHeaders(
  501. file: StaticString = #filePath,
  502. line: UInt = #line,
  503. verify: (Headers) -> Void = { _ in }
  504. ) {
  505. if case let .headers(headers) = self {
  506. verify(headers)
  507. } else {
  508. XCTFail("Expected '.headers' but got '\(self)'", file: file, line: line)
  509. }
  510. }
  511. func assertData(
  512. file: StaticString = #filePath,
  513. line: UInt = #line,
  514. verify: (Data) -> Void = { _ in }
  515. ) {
  516. if case let .data(data) = self {
  517. verify(data)
  518. } else {
  519. XCTFail("Expected '.data' but got '\(self)'", file: file, line: line)
  520. }
  521. }
  522. func assertEmptyDataWithEndStream(
  523. file: StaticString = #filePath,
  524. line: UInt = #line
  525. ) {
  526. self.assertData(file: file, line: line) {
  527. XCTAssertTrue($0.endStream)
  528. $0.data.assertByteBuffer { buffer in
  529. XCTAssertEqual(buffer.readableBytes, 0)
  530. }
  531. }
  532. }
  533. }
  534. extension HTTPServerResponsePart {
  535. func assertHead(
  536. file: StaticString = #filePath,
  537. line: UInt = #line,
  538. verify: (HTTPResponseHead) -> Void = { _ in }
  539. ) {
  540. if case let .head(head) = self {
  541. verify(head)
  542. } else {
  543. XCTFail("Expected '.head' but got '\(self)'", file: file, line: line)
  544. }
  545. }
  546. func assertBody(
  547. file: StaticString = #filePath,
  548. line: UInt = #line,
  549. verify: (ByteBuffer) -> Void = { _ in }
  550. ) {
  551. if case let .body(.byteBuffer(buffer)) = self {
  552. verify(buffer)
  553. } else {
  554. XCTFail("Expected '.body(.byteBuffer)' but got '\(self)'", file: file, line: line)
  555. }
  556. }
  557. func assertEnd(
  558. file: StaticString = #filePath,
  559. line: UInt = #line,
  560. verify: (HTTPHeaders?) -> Void = { _ in }
  561. ) {
  562. if case let .end(trailers) = self {
  563. verify(trailers)
  564. } else {
  565. XCTFail("Expected '.end' but got '\(self)'", file: file, line: line)
  566. }
  567. }
  568. }
  569. extension IOData {
  570. func assertByteBuffer(
  571. file: StaticString = #filePath,
  572. line: UInt = #line,
  573. verify: (ByteBuffer) -> Void = { _ in }
  574. ) {
  575. if case let .byteBuffer(buffer) = self {
  576. verify(buffer)
  577. } else {
  578. XCTFail("Expected '.byteBuffer' but got '\(self)'", file: file, line: line)
  579. }
  580. }
  581. }
  582. extension Optional {
  583. func assertSome(
  584. file: StaticString = #filePath,
  585. line: UInt = #line,
  586. verify: (Wrapped) -> Void = { _ in }
  587. ) {
  588. switch self {
  589. case let .some(wrapped):
  590. verify(wrapped)
  591. case .none:
  592. XCTFail("Expected '.some' but got 'nil'", file: file, line: line)
  593. }
  594. }
  595. }
  596. extension ByteBuffer {
  597. mutating func readLengthPrefixedMessage() -> ByteBuffer? {
  598. // Read off and ignore the compression byte.
  599. if self.readInteger(as: UInt8.self) == nil {
  600. return nil
  601. }
  602. return self.readLengthPrefixedSlice(as: UInt32.self)
  603. }
  604. }