Chunker.swift 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. /**
  2. * Copyright (c) 2018 Threema GmbH / SaltyRTC Contributors
  3. *
  4. * Licensed under the Apache License, Version 2.0, <see LICENSE-APACHE file> or
  5. * the MIT license <see LICENSE-MIT file>, at your option. This file may not be
  6. * copied, modified, or distributed except according to those terms.
  7. */
  8. import Foundation
  9. /// All errors that can occur inside the `Chunker`.
  10. enum ChunkerError: Error {
  11. /// The chunk size must be at least 10 bytes.
  12. case chunkSizeTooSmall
  13. /// The data to be chunked must not be empty.
  14. case dataEmpty
  15. }
  16. /// Create a new chunk.
  17. func makeChunkBytes(id: UInt32, serial: UInt32, endOfMessage: Bool, data: ArraySlice<UInt8>) -> [UInt8] {
  18. var chunk = [UInt8](repeating: 0, count: data.count + Int(Common.headerLength))
  19. // Write options
  20. let options: UInt8 = endOfMessage ? 1 : 0
  21. chunk[0] = options
  22. // Write id
  23. chunk[1] = UInt8((id >> 24) & 0xff)
  24. chunk[2] = UInt8((id >> 16) & 0xff)
  25. chunk[3] = UInt8((id >> 8) & 0xff)
  26. chunk[4] = UInt8(id & 0xff)
  27. // Write serial
  28. chunk[5] = UInt8((serial >> 24) & 0xff)
  29. chunk[6] = UInt8((serial >> 16) & 0xff)
  30. chunk[7] = UInt8((serial >> 8) & 0xff)
  31. chunk[8] = UInt8(serial & 0xff)
  32. // Write chunk data
  33. chunk[9..<9+data.count] = ArraySlice(data)
  34. return chunk
  35. }
  36. /// A `Chunker` splits up a `Data` instance into multiple chunks.
  37. ///
  38. /// The `Chunker` is initialized with an ID. For each message to be chunked, a
  39. /// new `Chunker` instance is required.
  40. ///
  41. /// This type implements `Sequence` and `IteratorProtocol`, so it can be
  42. /// iterated over (but only once, after which it has been consumed).
  43. class Chunker: Sequence, IteratorProtocol {
  44. private let id: UInt32
  45. private let data: Data
  46. private let chunkDataSize: UInt32
  47. private var chunkId: UInt32 = 0
  48. init(id: UInt32, data: Data, chunkSize: UInt32) throws {
  49. if chunkSize < Common.headerLength + 1 {
  50. throw ChunkerError.chunkSizeTooSmall
  51. }
  52. if data.isEmpty {
  53. throw ChunkerError.dataEmpty
  54. }
  55. self.id = id
  56. self.data = data
  57. self.chunkDataSize = chunkSize - Common.headerLength
  58. }
  59. func hasNext() -> Bool {
  60. let currentIndex = chunkId * chunkDataSize
  61. let remaining = data.count - Int(currentIndex)
  62. return remaining >= 1
  63. }
  64. func next() -> [UInt8]? {
  65. if !self.hasNext() {
  66. return nil
  67. }
  68. // Create next chunk
  69. let currentIndex = Int(self.chunkId * self.chunkDataSize)
  70. let remaining = self.data.count - currentIndex
  71. let effectiveChunkDataSize = Swift.min(remaining, Int(self.chunkDataSize))
  72. let endOfMessage = remaining <= effectiveChunkDataSize
  73. let chunk = makeChunkBytes(
  74. id: self.id,
  75. serial: self.nextSerial(),
  76. endOfMessage: endOfMessage,
  77. data: ArraySlice(self.data[currentIndex ..< currentIndex+effectiveChunkDataSize])
  78. )
  79. return chunk
  80. }
  81. func makeIterator() -> Chunker {
  82. return self
  83. }
  84. /// Return and post-increment the id of the next block
  85. private func nextSerial() -> UInt32 {
  86. let serial = self.chunkId
  87. self.chunkId += 1
  88. return serial
  89. }
  90. }