WebSocketTests.swift 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. //
  2. // WebSocketTests.swift
  3. // Alamofire
  4. //
  5. // Created by Jon Shier on 1/17/21.
  6. // Copyright © 2021 Alamofire. All rights reserved.
  7. //
  8. #if canImport(Darwin) && !canImport(FoundationNetworking)
  9. import Alamofire
  10. import Foundation
  11. import XCTest
  12. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  13. final class WebSocketTests: BaseTestCase {
  14. func testThatWebSocketsCanReceiveMessageEvents() {
  15. // Given
  16. let didConnect = expectation(description: "didConnect")
  17. let didReceiveMessage = expectation(description: "didReceiveMessage")
  18. let didDisconnect = expectation(description: "didDisconnect")
  19. let didComplete = expectation(description: "didComplete")
  20. let session = stored(Session())
  21. var connectedProtocol: String?
  22. var message: URLSessionWebSocketTask.Message?
  23. var closeCode: URLSessionWebSocketTask.CloseCode?
  24. var closeReason: Data?
  25. var receivedCompletion: WebSocketRequest.Completion?
  26. // When
  27. session.websocketRequest(.websocket()).streamMessageEvents { event in
  28. switch event.kind {
  29. case let .connected(`protocol`):
  30. connectedProtocol = `protocol`
  31. didConnect.fulfill()
  32. case let .receivedMessage(receivedMessage):
  33. message = receivedMessage
  34. didReceiveMessage.fulfill()
  35. case let .disconnected(code, reason):
  36. closeCode = code
  37. closeReason = reason
  38. didDisconnect.fulfill()
  39. case let .completed(completion):
  40. receivedCompletion = completion
  41. didComplete.fulfill()
  42. }
  43. }
  44. wait(for: [didConnect, didReceiveMessage, didDisconnect, didComplete],
  45. timeout: timeout,
  46. enforceOrder: true)
  47. // Then
  48. XCTAssertNil(connectedProtocol)
  49. XCTAssertNotNil(message)
  50. XCTAssertEqual(closeCode, .normalClosure)
  51. XCTAssertNil(closeReason)
  52. XCTAssertNil(receivedCompletion?.error)
  53. }
  54. func testThatWebSocketsCanReceiveAMessage() {
  55. // Given
  56. let didReceiveMessage = expectation(description: "didReceiveMessage")
  57. let didComplete = expectation(description: "didComplete")
  58. let session = stored(Session())
  59. var receivedMessage: URLSessionWebSocketTask.Message?
  60. // When
  61. session.websocketRequest(.websocket()).streamMessages { message in
  62. receivedMessage = message
  63. didReceiveMessage.fulfill()
  64. }
  65. .onCompletion {
  66. didComplete.fulfill()
  67. }
  68. wait(for: [didReceiveMessage, didComplete], timeout: timeout, enforceOrder: true)
  69. // Then
  70. XCTAssertNotNil(receivedMessage)
  71. XCTAssertNotNil(receivedMessage?.data)
  72. }
  73. func testThatWebSocketsCanReceiveADecodableMessage() {
  74. // Given
  75. let didConnect = expectation(description: "didConnect")
  76. let didReceiveMessage = expectation(description: "didReceiveMessage")
  77. let didDisconnect = expectation(description: "didDisconnect")
  78. let didComplete = expectation(description: "didComplete")
  79. let session = stored(Session())
  80. var connectedProtocol: String?
  81. var message: TestResponse?
  82. var closeCode: URLSessionWebSocketTask.CloseCode?
  83. var closeReason: Data?
  84. var receivedCompletion: WebSocketRequest.Completion?
  85. // When
  86. session.websocketRequest(.websocketCount(1)).streamDecodableEvents(TestResponse.self) { event in
  87. switch event.kind {
  88. case let .connected(`protocol`):
  89. connectedProtocol = `protocol`
  90. didConnect.fulfill()
  91. case let .receivedMessage(receivedMessage):
  92. message = receivedMessage
  93. didReceiveMessage.fulfill()
  94. case let .serializerFailed(error):
  95. XCTFail("websocket message serialization failed with error: \(error)")
  96. case let .disconnected(code, reason):
  97. closeCode = code
  98. closeReason = reason
  99. didDisconnect.fulfill()
  100. case let .completed(completion):
  101. receivedCompletion = completion
  102. didComplete.fulfill()
  103. }
  104. }
  105. wait(for: [didConnect, didReceiveMessage, didDisconnect, didComplete],
  106. timeout: timeout,
  107. enforceOrder: true)
  108. // Then
  109. XCTAssertNil(connectedProtocol)
  110. XCTAssertNotNil(message)
  111. XCTAssertEqual(closeCode, .normalClosure)
  112. XCTAssertNil(closeReason)
  113. XCTAssertNil(receivedCompletion?.error)
  114. }
  115. func testThatWebSocketsCanReceiveADecodableValue() {
  116. // Given
  117. let didReceiveValue = expectation(description: "didReceiveMessage")
  118. let didComplete = expectation(description: "didComplete")
  119. let session = stored(Session())
  120. var receivedValue: TestResponse?
  121. // When
  122. session.websocketRequest(.websocket()).streamDecodable(TestResponse.self) { value in
  123. receivedValue = value
  124. didReceiveValue.fulfill()
  125. }
  126. .onCompletion {
  127. didComplete.fulfill()
  128. }
  129. wait(for: [didReceiveValue, didComplete], timeout: timeout, enforceOrder: true)
  130. // Then
  131. XCTAssertNotNil(receivedValue)
  132. }
  133. func testThatWebSocketsCanReceiveAMessageWithAProtocol() {
  134. // Given
  135. let didConnect = expectation(description: "didConnect")
  136. let didReceiveMessage = expectation(description: "didReceiveMessage")
  137. let didDisconnect = expectation(description: "didDisconnect")
  138. let didComplete = expectation(description: "didComplete")
  139. let session = stored(Session())
  140. let `protocol` = "protocol"
  141. var connectedProtocol: String?
  142. var message: URLSessionWebSocketTask.Message?
  143. var closeCode: URLSessionWebSocketTask.CloseCode?
  144. var closeReason: Data?
  145. var receivedCompletion: WebSocketRequest.Completion?
  146. // When
  147. session.websocketRequest(.websocket(), protocol: `protocol`).streamMessageEvents { event in
  148. switch event.kind {
  149. case let .connected(`protocol`):
  150. connectedProtocol = `protocol`
  151. didConnect.fulfill()
  152. case let .receivedMessage(receivedMessage):
  153. message = receivedMessage
  154. didReceiveMessage.fulfill()
  155. case let .disconnected(code, reason):
  156. closeCode = code
  157. closeReason = reason
  158. didDisconnect.fulfill()
  159. case let .completed(completion):
  160. receivedCompletion = completion
  161. didComplete.fulfill()
  162. }
  163. }
  164. wait(for: [didConnect, didReceiveMessage, didDisconnect, didComplete],
  165. timeout: timeout,
  166. enforceOrder: true)
  167. // Then
  168. XCTAssertEqual(connectedProtocol, `protocol`)
  169. XCTAssertNotNil(message)
  170. XCTAssertEqual(closeCode, .normalClosure)
  171. XCTAssertNil(closeReason)
  172. XCTAssertNil(receivedCompletion?.error)
  173. }
  174. func testThatWebSocketsCanReceiveMultipleMessages() {
  175. // Given
  176. let count = 5
  177. let didConnect = expectation(description: "didConnect")
  178. let didReceiveMessage = expectation(description: "didReceiveMessage")
  179. didReceiveMessage.expectedFulfillmentCount = count
  180. let didDisconnect = expectation(description: "didDisconnect")
  181. let didComplete = expectation(description: "didComplete")
  182. let session = stored(Session())
  183. var connectedProtocol: String?
  184. var messages: [URLSessionWebSocketTask.Message] = []
  185. var closeCode: URLSessionWebSocketTask.CloseCode?
  186. var closeReason: Data?
  187. var receivedCompletion: WebSocketRequest.Completion?
  188. // When
  189. session.websocketRequest(.websocketCount(count)).streamMessageEvents { event in
  190. switch event.kind {
  191. case let .connected(`protocol`):
  192. connectedProtocol = `protocol`
  193. didConnect.fulfill()
  194. case let .receivedMessage(receivedMessage):
  195. messages.append(receivedMessage)
  196. didReceiveMessage.fulfill()
  197. case let .disconnected(code, reason):
  198. closeCode = code
  199. closeReason = reason
  200. didDisconnect.fulfill()
  201. case let .completed(completion):
  202. receivedCompletion = completion
  203. didComplete.fulfill()
  204. }
  205. }
  206. wait(for: [didConnect, didReceiveMessage, didDisconnect, didComplete], timeout: timeout, enforceOrder: true)
  207. // Then
  208. XCTAssertNil(connectedProtocol)
  209. XCTAssertEqual(messages.count, count)
  210. XCTAssertEqual(closeCode, .normalClosure)
  211. XCTAssertNil(closeReason)
  212. XCTAssertNil(receivedCompletion?.error)
  213. }
  214. func testThatWebSocketsCanSendAndReceiveMessages() {
  215. // Given
  216. let didConnect = expectation(description: "didConnect")
  217. let didSend = expectation(description: "didSend")
  218. let didReceiveMessage = expectation(description: "didReceiveMessage")
  219. let didDisconnect = expectation(description: "didDisconnect")
  220. let didComplete = expectation(description: "didComplete")
  221. let session = stored(Session())
  222. let sentMessage = URLSessionWebSocketTask.Message.string("Echo")
  223. var connectedProtocol: String?
  224. var message: URLSessionWebSocketTask.Message?
  225. var closeCode: URLSessionWebSocketTask.CloseCode?
  226. var closeReason: Data?
  227. var receivedCompletion: WebSocketRequest.Completion?
  228. // When
  229. let request = session.websocketRequest(.websocketEcho)
  230. request.streamMessageEvents { event in
  231. switch event.kind {
  232. case let .connected(`protocol`):
  233. connectedProtocol = `protocol`
  234. didConnect.fulfill()
  235. request.send(sentMessage) { _ in didSend.fulfill() }
  236. case let .receivedMessage(receivedMessage):
  237. message = receivedMessage
  238. event.close(sending: .normalClosure)
  239. didReceiveMessage.fulfill()
  240. case let .disconnected(code, reason):
  241. closeCode = code
  242. closeReason = reason
  243. didDisconnect.fulfill()
  244. case let .completed(completion):
  245. receivedCompletion = completion
  246. didComplete.fulfill()
  247. }
  248. }
  249. wait(for: [didConnect, didSend, didReceiveMessage, didDisconnect, didComplete],
  250. timeout: timeout,
  251. enforceOrder: true)
  252. // Then
  253. XCTAssertNil(connectedProtocol)
  254. XCTAssertNotNil(message)
  255. XCTAssertEqual(sentMessage, message)
  256. XCTAssertEqual(closeCode, .normalClosure)
  257. XCTAssertNil(closeReason)
  258. XCTAssertNil(receivedCompletion?.error)
  259. }
  260. func testThatWebSocketsCanBeCancelled() {
  261. // Given
  262. let didConnect = expectation(description: "didConnect")
  263. let didComplete = expectation(description: "didComplete")
  264. let session = stored(Session())
  265. var connectedProtocol: String?
  266. var receivedCompletion: WebSocketRequest.Completion?
  267. // When
  268. let request = session.websocketRequest(.websocketEcho)
  269. request.streamMessageEvents { event in
  270. switch event.kind {
  271. case let .connected(`protocol`):
  272. connectedProtocol = `protocol`
  273. didConnect.fulfill()
  274. request.cancel()
  275. case let .receivedMessage(receivedMessage):
  276. XCTFail("cancelled socket received message: \(receivedMessage)")
  277. case .disconnected:
  278. XCTFail("cancelled socket shouldn't receive disconnected event")
  279. case let .completed(completion):
  280. receivedCompletion = completion
  281. didComplete.fulfill()
  282. }
  283. }
  284. wait(for: [didConnect, didComplete], timeout: timeout, enforceOrder: true)
  285. // Then
  286. XCTAssertNil(connectedProtocol)
  287. XCTAssertTrue(receivedCompletion?.error?.isExplicitlyCancelledError == true)
  288. XCTAssertTrue(request.error?.isExplicitlyCancelledError == true)
  289. }
  290. func testOnePingOnly() {
  291. // Given
  292. let didConnect = expectation(description: "didConnect")
  293. let didSend = expectation(description: "didSend")
  294. let didReceiveMessage = expectation(description: "didReceiveMessage")
  295. let didReceivePong = expectation(description: "didReceivePong")
  296. didReceivePong.expectedFulfillmentCount = 100
  297. let didDisconnect = expectation(description: "didDisconnect")
  298. let didComplete = expectation(description: "didComplete")
  299. let session = stored(Session())
  300. let sentMessage = URLSessionWebSocketTask.Message.string("Echo")
  301. var connectedProtocol: String?
  302. var message: URLSessionWebSocketTask.Message?
  303. var receivedPong: WebSocketRequest.PingResponse.Pong?
  304. var closeCode: URLSessionWebSocketTask.CloseCode?
  305. var closeReason: Data?
  306. var receivedCompletion: WebSocketRequest.Completion?
  307. // When
  308. let request = session.websocketRequest(.websocketEcho)
  309. request.streamMessageEvents { event in
  310. switch event.kind {
  311. case let .connected(`protocol`):
  312. connectedProtocol = `protocol`
  313. didConnect.fulfill()
  314. request.send(sentMessage) { _ in didSend.fulfill() }
  315. case let .receivedMessage(receivedMessage):
  316. message = receivedMessage
  317. didReceiveMessage.fulfill()
  318. for count in 0..<100 {
  319. request.sendPing { response in
  320. switch response {
  321. case let .pong(pong):
  322. receivedPong = pong
  323. default:
  324. break
  325. }
  326. didReceivePong.fulfill()
  327. if count == 99 {
  328. request.close(sending: .normalClosure)
  329. }
  330. }
  331. }
  332. case let .disconnected(code, reason):
  333. closeCode = code
  334. closeReason = reason
  335. didDisconnect.fulfill()
  336. case let .completed(completion):
  337. receivedCompletion = completion
  338. didComplete.fulfill()
  339. }
  340. }
  341. wait(for: [didConnect, didSend, didReceiveMessage, didReceivePong, didDisconnect, didComplete],
  342. timeout: timeout,
  343. enforceOrder: true)
  344. // Then
  345. XCTAssertNil(connectedProtocol)
  346. XCTAssertNotNil(message)
  347. XCTAssertEqual(sentMessage, message)
  348. XCTAssertEqual(closeCode, .normalClosure)
  349. XCTAssertNil(closeReason)
  350. XCTAssertNotNil(receivedCompletion)
  351. XCTAssertNil(receivedCompletion?.error)
  352. XCTAssertNotNil(receivedPong)
  353. }
  354. func testThatTimePingsOccur() {
  355. // Given
  356. let didConnect = expectation(description: "didConnect")
  357. let didDisconnect = expectation(description: "didDisconnect")
  358. let didComplete = expectation(description: "didComplete")
  359. let session = stored(Session())
  360. var connectedProtocol: String?
  361. var closeCode: URLSessionWebSocketTask.CloseCode?
  362. var closeReason: Data?
  363. var receivedCompletion: WebSocketRequest.Completion?
  364. // When
  365. let request = session.websocketRequest(.websocketPings(), pingInterval: 0.01)
  366. request.streamMessageEvents { event in
  367. switch event.kind {
  368. case let .connected(`protocol`):
  369. connectedProtocol = `protocol`
  370. didConnect.fulfill()
  371. case .receivedMessage:
  372. break
  373. case let .disconnected(code, reason):
  374. closeCode = code
  375. closeReason = reason
  376. didDisconnect.fulfill()
  377. case let .completed(completion):
  378. receivedCompletion = completion
  379. didComplete.fulfill()
  380. }
  381. }
  382. wait(for: [didConnect, didDisconnect, didComplete], timeout: timeout, enforceOrder: true)
  383. // Then
  384. XCTAssertNil(connectedProtocol)
  385. XCTAssertEqual(closeCode, .goingAway) // Default Vapor close() code.
  386. XCTAssertNil(closeReason)
  387. XCTAssertNotNil(receivedCompletion)
  388. XCTAssertNil(receivedCompletion?.error)
  389. }
  390. func testThatWebSocketFailsWithTooSmallMaximumMessageSize() {
  391. // Given
  392. let didConnect = expectation(description: "didConnect")
  393. let didComplete = expectation(description: "didComplete")
  394. let session = stored(Session())
  395. var connectedProtocol: String?
  396. var receivedCompletion: WebSocketRequest.Completion?
  397. // When
  398. session.websocketRequest(.websocket(), maximumMessageSize: 1).streamMessageEvents { event in
  399. switch event.kind {
  400. case let .connected(`protocol`):
  401. connectedProtocol = `protocol`
  402. didConnect.fulfill()
  403. case .receivedMessage, .disconnected:
  404. break
  405. case let .completed(completion):
  406. receivedCompletion = completion
  407. didComplete.fulfill()
  408. }
  409. }
  410. wait(for: [didConnect, didComplete], timeout: timeout, enforceOrder: true)
  411. // Then
  412. XCTAssertNil(connectedProtocol)
  413. XCTAssertNotNil(receivedCompletion?.error)
  414. }
  415. func testThatWebSocketsFinishAfterNonNormalResponseCode() {
  416. // Given
  417. let didConnect = expectation(description: "didConnect")
  418. let didReceiveMessage = expectation(description: "didReceiveMessage")
  419. let didDisconnect = expectation(description: "didDisconnect")
  420. let didComplete = expectation(description: "didComplete")
  421. let session = stored(Session())
  422. var connectedProtocol: String?
  423. var message: URLSessionWebSocketTask.Message?
  424. var closeCode: URLSessionWebSocketTask.CloseCode?
  425. var closeReason: Data?
  426. var receivedCompletion: WebSocketRequest.Completion?
  427. // When
  428. session.websocketRequest(.websocket(closeCode: .goingAway)).streamMessageEvents { event in
  429. switch event.kind {
  430. case let .connected(`protocol`):
  431. connectedProtocol = `protocol`
  432. didConnect.fulfill()
  433. case let .receivedMessage(receivedMessage):
  434. message = receivedMessage
  435. didReceiveMessage.fulfill()
  436. case let .disconnected(code, reason):
  437. closeCode = code
  438. closeReason = reason
  439. didDisconnect.fulfill()
  440. case let .completed(completion):
  441. receivedCompletion = completion
  442. didComplete.fulfill()
  443. }
  444. }
  445. wait(for: [didConnect, didReceiveMessage, didDisconnect, didComplete],
  446. timeout: timeout,
  447. enforceOrder: true)
  448. // Then
  449. XCTAssertNil(connectedProtocol)
  450. XCTAssertNotNil(message)
  451. XCTAssertEqual(closeCode, .goingAway)
  452. XCTAssertNil(closeReason)
  453. XCTAssertNil(receivedCompletion?.error)
  454. }
  455. func testThatWebSocketsCanHaveMultipleHandlers() {
  456. // Given
  457. let didConnect = expectation(description: "didConnect")
  458. didConnect.expectedFulfillmentCount = 2
  459. let didReceiveMessage = expectation(description: "didReceiveMessage")
  460. didReceiveMessage.expectedFulfillmentCount = 2
  461. let didDisconnect = expectation(description: "didDisconnect")
  462. didDisconnect.expectedFulfillmentCount = 2
  463. let didComplete = expectation(description: "didComplete")
  464. didComplete.expectedFulfillmentCount = 2
  465. let session = stored(Session())
  466. var firstConnectedProtocol: String?
  467. var firstMessage: URLSessionWebSocketTask.Message?
  468. var firstCloseCode: URLSessionWebSocketTask.CloseCode?
  469. var firstCloseReason: Data?
  470. var firstReceivedCompletion: WebSocketRequest.Completion?
  471. var secondConnectedProtocol: String?
  472. var secondMessage: URLSessionWebSocketTask.Message?
  473. var secondCloseCode: URLSessionWebSocketTask.CloseCode?
  474. var secondCloseReason: Data?
  475. var secondReceivedCompletion: WebSocketRequest.Completion?
  476. // When
  477. session.websocketRequest(.websocket(closeCode: .goingAway)).streamMessageEvents { event in
  478. switch event.kind {
  479. case let .connected(`protocol`):
  480. firstConnectedProtocol = `protocol`
  481. didConnect.fulfill()
  482. case let .receivedMessage(receivedMessage):
  483. firstMessage = receivedMessage
  484. didReceiveMessage.fulfill()
  485. case let .disconnected(code, reason):
  486. firstCloseCode = code
  487. firstCloseReason = reason
  488. didDisconnect.fulfill()
  489. case let .completed(completion):
  490. firstReceivedCompletion = completion
  491. didComplete.fulfill()
  492. }
  493. }
  494. .streamMessageEvents { event in
  495. switch event.kind {
  496. case let .connected(`protocol`):
  497. secondConnectedProtocol = `protocol`
  498. didConnect.fulfill()
  499. case let .receivedMessage(receivedMessage):
  500. secondMessage = receivedMessage
  501. didReceiveMessage.fulfill()
  502. case let .disconnected(code, reason):
  503. secondCloseCode = code
  504. secondCloseReason = reason
  505. didDisconnect.fulfill()
  506. case let .completed(completion):
  507. secondReceivedCompletion = completion
  508. didComplete.fulfill()
  509. }
  510. }
  511. wait(for: [didConnect, didReceiveMessage, didDisconnect, didComplete],
  512. timeout: timeout,
  513. enforceOrder: true)
  514. // Then
  515. XCTAssertNil(firstConnectedProtocol)
  516. XCTAssertEqual(firstConnectedProtocol, secondConnectedProtocol)
  517. XCTAssertNotNil(firstMessage)
  518. XCTAssertEqual(firstMessage, secondMessage)
  519. XCTAssertEqual(firstCloseCode, .goingAway)
  520. XCTAssertEqual(firstCloseCode, secondCloseCode)
  521. XCTAssertNil(firstCloseReason)
  522. XCTAssertEqual(firstCloseReason, secondCloseReason)
  523. XCTAssertNil(firstReceivedCompletion?.error)
  524. XCTAssertNil(secondReceivedCompletion?.error)
  525. }
  526. }
  527. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  528. extension WebSocketRequest {
  529. @discardableResult
  530. func onCompletion(queue: DispatchQueue = .main, handler: @escaping () -> Void) -> Self {
  531. streamMessageEvents(on: queue) { event in
  532. guard case .completed = event.kind else { return }
  533. handler()
  534. }
  535. }
  536. }
  537. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  538. extension URLSessionWebSocketTask.Message: Equatable {
  539. public static func ==(lhs: URLSessionWebSocketTask.Message, rhs: URLSessionWebSocketTask.Message) -> Bool {
  540. switch (lhs, rhs) {
  541. case let (.string(left), .string(right)):
  542. return left == right
  543. case let (.data(left), .data(right)):
  544. return left == right
  545. default:
  546. return false
  547. }
  548. }
  549. var string: String? {
  550. guard case let .string(string) = self else { return nil }
  551. return string
  552. }
  553. var data: Data? {
  554. guard case let .data(data) = self else { return nil }
  555. return data
  556. }
  557. }
  558. #endif