export class ByteStream { constructor(parameters = {}) { if ("view" in parameters) { this.fromUint8Array(parameters.view); } else if ("buffer" in parameters) { this.fromArrayBuffer(parameters.buffer); } else if ("string" in parameters) { this.fromString(parameters.string); } else if ("hexstring" in parameters) { this.fromHexString(parameters.hexstring); } else { if ("length" in parameters && parameters.length > 0) { this.length = parameters.length; if (parameters.stub) { for (let i = 0; i < this._view.length; i++) { this._view[i] = parameters.stub; } } } else { this.length = 0; } } } set buffer(value) { this._buffer = value; this._view = new Uint8Array(this._buffer); } get buffer() { return this._buffer; } set view(value) { this._buffer = new ArrayBuffer(value.length); this._view = new Uint8Array(this._buffer); this._view.set(value); } get view() { return this._view; } get length() { return this.view.byteLength; } set length(value) { this._buffer = new ArrayBuffer(value); this._view = new Uint8Array(this._buffer); } clear() { this._buffer = new ArrayBuffer(0); this._view = new Uint8Array(this._buffer); } fromArrayBuffer(array) { this._buffer = array; this._view = new Uint8Array(this._buffer); } fromUint8Array(array) { this.fromArrayBuffer(new Uint8Array(array).buffer); } fromString(string) { const stringLength = string.length; this.length = stringLength; for (let i = 0; i < stringLength; i++) this.view[i] = string.charCodeAt(i); } toString(start = 0, length = (this.view.length - start)) { let result = ""; if ((start >= this.view.length) || (start < 0)) { start = 0; } if ((length >= this.view.length) || (length < 0)) { length = this.view.length - start; } for (let i = start; i < (start + length); i++) result += String.fromCharCode(this.view[i]); return result; } fromHexString(hexString) { const stringLength = hexString.length; this.buffer = new ArrayBuffer(stringLength >> 1); this.view = new Uint8Array(this.buffer); const hexMap = new Map(); hexMap.set("0", 0x00); hexMap.set("1", 0x01); hexMap.set("2", 0x02); hexMap.set("3", 0x03); hexMap.set("4", 0x04); hexMap.set("5", 0x05); hexMap.set("6", 0x06); hexMap.set("7", 0x07); hexMap.set("8", 0x08); hexMap.set("9", 0x09); hexMap.set("A", 0x0A); hexMap.set("a", 0x0A); hexMap.set("B", 0x0B); hexMap.set("b", 0x0B); hexMap.set("C", 0x0C); hexMap.set("c", 0x0C); hexMap.set("D", 0x0D); hexMap.set("d", 0x0D); hexMap.set("E", 0x0E); hexMap.set("e", 0x0E); hexMap.set("F", 0x0F); hexMap.set("f", 0x0F); let j = 0; let temp = 0x00; for (let i = 0; i < stringLength; i++) { if (!(i % 2)) { temp = hexMap.get(hexString.charAt(i)) << 4; } else { temp |= hexMap.get(hexString.charAt(i)); this.view[j] = temp; j++; } } } toHexString(start = 0, length = (this.view.length - start)) { let result = ""; if ((start >= this.view.length) || (start < 0)) { start = 0; } if ((length >= this.view.length) || (length < 0)) { length = this.view.length - start; } for (let i = start; i < (start + length); i++) { const str = this.view[i].toString(16).toUpperCase(); result = result + ((str.length == 1) ? "0" : "") + str; } return result; } copy(start = 0, length = (this.length - start)) { if (!start && !this.length) { return new ByteStream(); } if ((start < 0) || (start > (this.length - 1))) { throw new Error(`Wrong start position: ${start}`); } const stream = new ByteStream({ buffer: this._buffer.slice(start, start + length) }); return stream; } slice(start = 0, end = this.length) { if (!start && !this.length) { return new ByteStream(); } if ((start < 0) || (start > (this.length - 1))) { throw new Error(`Wrong start position: ${start}`); } const stream = new ByteStream({ buffer: this._buffer.slice(start, end), }); return stream; } realloc(size) { const buffer = new ArrayBuffer(size); const view = new Uint8Array(buffer); if (size > this._view.length) view.set(this._view); else { view.set(new Uint8Array(this._buffer, 0, size)); } this._buffer = buffer; this._view = new Uint8Array(this._buffer); } append(stream) { const initialSize = this.length; const streamViewLength = stream.length; const subarrayView = stream._view.subarray(); this.realloc(initialSize + streamViewLength); this._view.set(subarrayView, initialSize); } insert(stream, start = 0, length = (this.length - start)) { if (start > (this.length - 1)) return false; if (length > (this.length - start)) { length = this.length - start; } if (length > stream.length) { length = stream.length; } if (length == stream.length) this._view.set(stream._view, start); else { this._view.set(stream._view.subarray(0, length), start); } return true; } isEqual(stream) { if (this.length != stream.length) return false; for (let i = 0; i < stream.length; i++) { if (this.view[i] != stream.view[i]) return false; } return true; } isEqualView(view) { if (view.length != this.view.length) return false; for (let i = 0; i < view.length; i++) { if (this.view[i] != view[i]) return false; } return true; } findPattern(pattern, start_, length_, backward_) { const { start, length, backward } = this.prepareFindParameters(start_, length_, backward_); const patternLength = pattern.length; if (patternLength > length) { return (-1); } const patternArray = []; for (let i = 0; i < patternLength; i++) patternArray.push(pattern.view[i]); for (let i = 0; i <= (length - patternLength); i++) { let equal = true; const equalStart = (backward) ? (start - patternLength - i) : (start + i); for (let j = 0; j < patternLength; j++) { if (this.view[j + equalStart] != patternArray[j]) { equal = false; break; } } if (equal) { return (backward) ? (start - patternLength - i) : (start + patternLength + i); } } return (-1); } findFirstIn(patterns, start_, length_, backward_) { const { start, length, backward } = this.prepareFindParameters(start_, length_, backward_); const result = { id: (-1), position: (backward) ? 0 : (start + length), length: 0 }; for (let i = 0; i < patterns.length; i++) { const position = this.findPattern(patterns[i], start, length, backward); if (position != (-1)) { let valid = false; const patternLength = patterns[i].length; if (backward) { if ((position - patternLength) >= (result.position - result.length)) valid = true; } else { if ((position - patternLength) <= (result.position - result.length)) valid = true; } if (valid) { result.position = position; result.id = i; result.length = patternLength; } } } return result; } findAllIn(patterns, start_, length_) { let { start, length } = this.prepareFindParameters(start_, length_); const result = []; let patternFound = { id: (-1), position: start }; do { const position = patternFound.position; patternFound = this.findFirstIn(patterns, patternFound.position, length); if (patternFound.id == (-1)) { break; } length -= (patternFound.position - position); result.push({ id: patternFound.id, position: patternFound.position }); } while (true); return result; } findAllPatternIn(pattern, start_, length_) { const { start, length } = this.prepareFindParameters(start_, length_); const result = []; const patternLength = pattern.length; if (patternLength > length) { return (-1); } const patternArray = Array.from(pattern.view); for (let i = 0; i <= (length - patternLength); i++) { let equal = true; const equalStart = start + i; for (let j = 0; j < patternLength; j++) { if (this.view[j + equalStart] != patternArray[j]) { equal = false; break; } } if (equal) { result.push(start + patternLength + i); i += (patternLength - 1); } } return result; } findFirstNotIn(patterns, start_, length_, backward_) { let { start, length, backward } = this.prepareFindParameters(start_, length_, backward_); const result = { left: { id: (-1), position: start }, right: { id: (-1), position: 0 }, value: new ByteStream() }; let currentLength = length; while (currentLength > 0) { result.right = this.findFirstIn(patterns, (backward) ? (start - length + currentLength) : (start + length - currentLength), currentLength, backward); if (result.right.id == (-1)) { length = currentLength; if (backward) { start -= length; } else { start = result.left.position; } result.value = new ByteStream({ buffer: this._buffer.slice(start, start + length), }); break; } if (result.right.position != ((backward) ? (result.left.position - patterns[result.right.id].length) : (result.left.position + patterns[result.right.id].length))) { if (backward) { start = result.right.position + patterns[result.right.id].length; length = result.left.position - result.right.position - patterns[result.right.id].length; } else { start = result.left.position; length = result.right.position - result.left.position - patterns[result.right.id].length; } result.value = new ByteStream({ buffer: this._buffer.slice(start, start + length), }); break; } result.left = result.right; currentLength -= patterns[result.right.id].length; } if (backward) { const temp = result.right; result.right = result.left; result.left = temp; } return result; } findAllNotIn(patterns, start_, length_) { let { start, length } = this.prepareFindParameters(start_, length_); const result = []; let patternFound = { left: { id: (-1), position: start }, right: { id: (-1), position: start }, value: new ByteStream() }; do { const position = patternFound.right.position; patternFound = this.findFirstNotIn(patterns, patternFound.right.position, length); length -= (patternFound.right.position - position); result.push({ left: { id: patternFound.left.id, position: patternFound.left.position }, right: { id: patternFound.right.id, position: patternFound.right.position }, value: patternFound.value }); } while (patternFound.right.id != (-1)); return result; } findFirstSequence(patterns, start_, length_, backward_) { let { start, length, backward } = this.prepareFindParameters(start_, length_, backward_); const firstIn = this.skipNotPatterns(patterns, start, length, backward); if (firstIn == (-1)) { return { position: (-1), value: new ByteStream() }; } const firstNotIn = this.skipPatterns(patterns, firstIn, length - ((backward) ? (start - firstIn) : (firstIn - start)), backward); if (backward) { start = firstNotIn; length = (firstIn - firstNotIn); } else { start = firstIn; length = (firstNotIn - firstIn); } const value = new ByteStream({ buffer: this._buffer.slice(start, start + length), }); return { position: firstNotIn, value }; } findAllSequences(patterns, start_, length_) { let { start, length } = this.prepareFindParameters(start_, length_); const result = []; let patternFound = { position: start, value: new ByteStream() }; do { const position = patternFound.position; patternFound = this.findFirstSequence(patterns, patternFound.position, length); if (patternFound.position != (-1)) { length -= (patternFound.position - position); result.push({ position: patternFound.position, value: patternFound.value, }); } } while (patternFound.position != (-1)); return result; } findPairedPatterns(leftPattern, rightPattern, start_, length_) { const result = []; if (leftPattern.isEqual(rightPattern)) return result; const { start, length } = this.prepareFindParameters(start_, length_); let currentPositionLeft = 0; const leftPatterns = this.findAllPatternIn(leftPattern, start, length); if (!Array.isArray(leftPatterns) || leftPatterns.length == 0) { return result; } const rightPatterns = this.findAllPatternIn(rightPattern, start, length); if (!Array.isArray(rightPatterns) || rightPatterns.length == 0) { return result; } while (currentPositionLeft < leftPatterns.length) { if (rightPatterns.length == 0) { break; } if (leftPatterns[0] == rightPatterns[0]) { result.push({ left: leftPatterns[0], right: rightPatterns[0] }); leftPatterns.splice(0, 1); rightPatterns.splice(0, 1); continue; } if (leftPatterns[currentPositionLeft] > rightPatterns[0]) { break; } while (leftPatterns[currentPositionLeft] < rightPatterns[0]) { currentPositionLeft++; if (currentPositionLeft >= leftPatterns.length) { break; } } result.push({ left: leftPatterns[currentPositionLeft - 1], right: rightPatterns[0] }); leftPatterns.splice(currentPositionLeft - 1, 1); rightPatterns.splice(0, 1); currentPositionLeft = 0; } result.sort((a, b) => (a.left - b.left)); return result; } findPairedArrays(inputLeftPatterns, inputRightPatterns, start_, length_) { const { start, length } = this.prepareFindParameters(start_, length_); const result = []; let currentPositionLeft = 0; const leftPatterns = this.findAllIn(inputLeftPatterns, start, length); if (leftPatterns.length == 0) return result; const rightPatterns = this.findAllIn(inputRightPatterns, start, length); if (rightPatterns.length == 0) return result; while (currentPositionLeft < leftPatterns.length) { if (rightPatterns.length == 0) { break; } if (leftPatterns[0].position == rightPatterns[0].position) { result.push({ left: leftPatterns[0], right: rightPatterns[0] }); leftPatterns.splice(0, 1); rightPatterns.splice(0, 1); continue; } if (leftPatterns[currentPositionLeft].position > rightPatterns[0].position) { break; } while (leftPatterns[currentPositionLeft].position < rightPatterns[0].position) { currentPositionLeft++; if (currentPositionLeft >= leftPatterns.length) { break; } } result.push({ left: leftPatterns[currentPositionLeft - 1], right: rightPatterns[0] }); leftPatterns.splice(currentPositionLeft - 1, 1); rightPatterns.splice(0, 1); currentPositionLeft = 0; } result.sort((a, b) => (a.left.position - b.left.position)); return result; } replacePattern(searchPattern, replacePattern, start_, length_, findAllResult = null) { let result = []; let i; const output = { status: (-1), searchPatternPositions: [], replacePatternPositions: [] }; const { start, length } = this.prepareFindParameters(start_, length_); if (findAllResult == null) { result = this.findAllIn([searchPattern], start, length); if (result.length == 0) { return output; } } else { result = findAllResult; } output.searchPatternPositions.push(...Array.from(result, element => element.position)); const patternDifference = searchPattern.length - replacePattern.length; const changedBuffer = new ArrayBuffer(this.view.length - (result.length * patternDifference)); const changedView = new Uint8Array(changedBuffer); changedView.set(new Uint8Array(this.buffer, 0, start)); for (i = 0; i < result.length; i++) { const currentPosition = (i == 0) ? start : result[i - 1].position; changedView.set(new Uint8Array(this.buffer, currentPosition, result[i].position - searchPattern.length - currentPosition), currentPosition - i * patternDifference); changedView.set(replacePattern.view, result[i].position - searchPattern.length - i * patternDifference); output.replacePatternPositions.push(result[i].position - searchPattern.length - i * patternDifference); } i--; changedView.set(new Uint8Array(this.buffer, result[i].position, this.length - result[i].position), result[i].position - searchPattern.length + replacePattern.length - i * patternDifference); this.buffer = changedBuffer; this.view = new Uint8Array(this.buffer); output.status = 1; return output; } skipPatterns(patterns, start_, length_, backward_) { const { start, length, backward } = this.prepareFindParameters(start_, length_, backward_); let result = start; for (let k = 0; k < patterns.length; k++) { const patternLength = patterns[k].length; const equalStart = (backward) ? (result - patternLength) : (result); let equal = true; for (let j = 0; j < patternLength; j++) { if (this.view[j + equalStart] != patterns[k].view[j]) { equal = false; break; } } if (equal) { k = (-1); if (backward) { result -= patternLength; if (result <= 0) return result; } else { result += patternLength; if (result >= (start + length)) return result; } } } return result; } skipNotPatterns(patterns, start_, length_, backward_) { const { start, length, backward } = this.prepareFindParameters(start_, length_, backward_); let result = (-1); for (let i = 0; i < length; i++) { for (let k = 0; k < patterns.length; k++) { const patternLength = patterns[k].length; const equalStart = (backward) ? (start - i - patternLength) : (start + i); let equal = true; for (let j = 0; j < patternLength; j++) { if (this.view[j + equalStart] != patterns[k].view[j]) { equal = false; break; } } if (equal) { result = (backward) ? (start - i) : (start + i); break; } } if (result != (-1)) { break; } } return result; } prepareFindParameters(start = null, length = null, backward = false) { if (start === null) { start = (backward) ? this.length : 0; } if (start > this.length) { start = this.length; } if (backward) { if (length === null) { length = start; } if (length > start) { length = start; } } else { if (length === null) { length = this.length - start; } if (length > (this.length - start)) { length = this.length - start; } } return { start, length, backward }; } }