123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115 |
- /**
- * This file is part of Threema Web.
- *
- * Threema Web is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
- * General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
- */
- import {SequenceNumber} from './sequence_number';
- export type CachedChunk = Uint8Array | null;
- /**
- * Contains chunks that have not yet been acknowledged.
- */
- export class ChunkCache {
- private _sequenceNumber: SequenceNumber;
- private _byteLength = 0;
- private cache: CachedChunk[] = [];
- constructor(sequenceNumber: SequenceNumber) {
- this._sequenceNumber = sequenceNumber;
- }
- /**
- * Get the current sequence number (e.g. of the **next** chunk to be added).
- */
- public get sequenceNumber(): SequenceNumber {
- return this._sequenceNumber;
- }
- /**
- * Get the total size of currently cached chunks in bytes.
- */
- public get byteLength(): number {
- return this._byteLength;
- }
- /**
- * Get a reference to the currently cached chunks.
- *
- * Note: Blacklisted chunks will be filtered automatically.
- */
- public get chunks(): CachedChunk[] {
- return this.cache.filter((chunk) => chunk !== null);
- }
- /**
- * Transfer an array of cached chunks to this cache instance and return the
- * amount of chunks that have been transferred.
- */
- public transfer(cache: CachedChunk[]): number {
- // Add chunks but remove all which should not be retransmitted
- cache = cache.filter((chunk) => chunk !== null);
- const count = cache.length;
- for (const chunk of cache) {
- this.append(chunk);
- }
- return count;
- }
- /**
- * Append a chunk to the chunk cache.
- */
- public append(chunk: CachedChunk): void {
- // Update sequence number, update size & append chunk
- this._sequenceNumber.increment();
- if (chunk !== null) {
- this._byteLength += chunk.byteLength;
- }
- this.cache.push(chunk);
- }
- /**
- * Prune cached chunks that have been acknowledged. Return the
- * amount of chunks which have been acknowledged and the amount of
- * chunks left in the cache.
- */
- public prune(theirSequenceNumber: number): { acknowledged: number, left: number } {
- try {
- this._sequenceNumber.validate(theirSequenceNumber);
- } catch (error) {
- throw new Error(`Remote sent us an invalid sequence number: ${theirSequenceNumber}`);
- }
- // Calculate the slice start index for the chunk cache
- // Important: Our sequence number is one chunk ahead!
- const beginOffset = theirSequenceNumber - this._sequenceNumber.get();
- if (beginOffset > 0) {
- throw new Error('Remote travelled through time and acknowledged a chunk which is in the future');
- } else if (-beginOffset > this.cache.length) {
- throw new Error('Remote travelled back in time and acknowledged a chunk it has already acknowledged');
- }
- // Slice our cache & recalculate size
- const chunkCountBefore = this.cache.length;
- this.cache = beginOffset === 0 ? [] : this.cache.slice(beginOffset);
- this._byteLength = this.cache
- .filter((chunk) => chunk !== null)
- .reduce((sum, chunk) => sum + chunk.byteLength, 0);
- return {
- acknowledged: chunkCountBefore + beginOffset,
- left: this.cache.length,
- };
- }
- }
|