cache.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. /**
  2. * This file is part of Threema Web.
  3. *
  4. * Threema Web is free software: you can redistribute it and/or modify it
  5. * under the terms of the GNU Affero General Public License as published by
  6. * the Free Software Foundation, either version 3 of the License, or (at
  7. * your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful, but
  10. * WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  12. * General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Affero General Public License
  15. * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. import {SequenceNumber} from './sequence_number';
  18. export type CachedChunk = Uint8Array | null;
  19. /**
  20. * Contains chunks that have not yet been acknowledged.
  21. */
  22. export class ChunkCache {
  23. private _sequenceNumber: SequenceNumber;
  24. private _byteLength = 0;
  25. private cache: CachedChunk[] = [];
  26. constructor(sequenceNumber: SequenceNumber) {
  27. this._sequenceNumber = sequenceNumber;
  28. }
  29. /**
  30. * Get the current sequence number (e.g. of the **next** chunk to be added).
  31. */
  32. public get sequenceNumber(): SequenceNumber {
  33. return this._sequenceNumber;
  34. }
  35. /**
  36. * Get the total size of currently cached chunks in bytes.
  37. */
  38. public get byteLength(): number {
  39. return this._byteLength;
  40. }
  41. /**
  42. * Get a reference to the currently cached chunks.
  43. *
  44. * Note: Blacklisted chunks will be filtered automatically.
  45. */
  46. public get chunks(): CachedChunk[] {
  47. return this.cache.filter((chunk) => chunk !== null);
  48. }
  49. /**
  50. * Transfer an array of cached chunks to this cache instance and return the
  51. * amount of chunks that have been transferred.
  52. */
  53. public transfer(cache: CachedChunk[]): number {
  54. // Add chunks but remove all which should not be retransmitted
  55. cache = cache.filter((chunk) => chunk !== null);
  56. const count = cache.length;
  57. for (const chunk of cache) {
  58. this.append(chunk);
  59. }
  60. return count;
  61. }
  62. /**
  63. * Append a chunk to the chunk cache.
  64. */
  65. public append(chunk: CachedChunk): void {
  66. // Update sequence number, update size & append chunk
  67. this._sequenceNumber.increment();
  68. if (chunk !== null) {
  69. this._byteLength += chunk.byteLength;
  70. }
  71. this.cache.push(chunk);
  72. }
  73. /**
  74. * Prune cached chunks that have been acknowledged. Return the
  75. * amount of chunks which have been acknowledged and the amount of
  76. * chunks left in the cache.
  77. */
  78. public prune(theirSequenceNumber: number): { acknowledged: number, left: number } {
  79. try {
  80. this._sequenceNumber.validate(theirSequenceNumber);
  81. } catch (error) {
  82. throw new Error(`Remote sent us an invalid sequence number: ${theirSequenceNumber}`);
  83. }
  84. // Calculate the slice start index for the chunk cache
  85. // Important: Our sequence number is one chunk ahead!
  86. const beginOffset = theirSequenceNumber - this._sequenceNumber.get();
  87. if (beginOffset > 0) {
  88. throw new Error('Remote travelled through time and acknowledged a chunk which is in the future');
  89. } else if (-beginOffset > this.cache.length) {
  90. throw new Error('Remote travelled back in time and acknowledged a chunk it has already acknowledged');
  91. }
  92. // Slice our cache & recalculate size
  93. const chunkCountBefore = this.cache.length;
  94. this.cache = beginOffset === 0 ? [] : this.cache.slice(beginOffset);
  95. this._byteLength = this.cache
  96. .filter((chunk) => chunk !== null)
  97. .reduce((sum, chunk) => sum + chunk.byteLength, 0);
  98. return {
  99. acknowledged: chunkCountBefore + beginOffset,
  100. left: this.cache.length,
  101. };
  102. }
  103. }