Completed
Push — master ( ff6124...8caa6f )
by Jan
23s queued 12s
created

FrameReader.consumeBuffer   A

Complexity

Conditions 2

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 2
1
import * as ID3Util from "./ID3Util"
2
import { TextEncoding } from "./definitions/Encoding"
3
4
type FrameReaderOptions = {
5
    consumeEncodingByte?: boolean
6
}
7
8
type Size = {
9
    size?: number
10
}
11
12
export class FrameReader {
13
    private _encoding: TextEncoding = TextEncoding.ISO_8859_1
14
    private _buffer: Buffer
15
16
    constructor(
17
        buffer: Buffer,
18
        {
19
            consumeEncodingByte = false
20
        }: FrameReaderOptions = {}
21
    ) {
22
        this._buffer = buffer
23
        if (consumeEncodingByte) {
24
            const encoding = this.consumeBuffer({ size: 1 })
25
            this._encoding = encoding[0] as TextEncoding
26
        }
27
    }
28
29
    isBufferEmpty() {
30
        return this._buffer.length === 0
31
    }
32
33
    consumePossiblyEmptyBuffer(
34
        { size = this._buffer.length }: Size = {}
35
    ): Buffer {
36
        if (size > this._buffer.length) {
37
            throw new RangeError(
38
                `Requested size ${size} larger than the buffer size ${this._buffer.length}`
39
            )
40
        }
41
        const consumed = this._buffer.subarray(0, size)
42
        this._buffer = this._buffer.subarray(size)
43
        return consumed
44
    }
45
46
    consumeBuffer(size?: Size): Buffer {
47
        if (this._buffer.length === 0) {
48
            throw new RangeError("Buffer empty")
49
        }
50
        return this.consumePossiblyEmptyBuffer(size)
51
    }
52
53
    consumeNumber({ size }: { size: number }): number {
54
        const buffer = this.consumeBuffer({ size })
55
        return parseInt(buffer.toString('hex'), 16)
56
    }
57
58
    consumeText({ size, encoding = TextEncoding.ISO_8859_1 }: {
59
        size?: number
60
        encoding?: TextEncoding
61
    } = {}): string {
62
        const buffer = this.consumeBuffer({ size })
63
        return ID3Util.bufferToDecodedString(buffer, encoding)
64
    }
65
66
    consumeTextWithFrameEncoding(size: Size = {}): string {
67
        return this.consumeText({...size, encoding: this._encoding})
68
    }
69
70
    consumeTerminatedText(
71
        encoding: TextEncoding = TextEncoding.ISO_8859_1
72
    ): string {
73
        const [consumed, remainder] =
74
            splitNullTerminatedBuffer(this._buffer, encoding)
75
        this._buffer = remainder
76
        return ID3Util.bufferToDecodedString(consumed, encoding)
77
    }
78
79
    consumeTerminatedTextWithFrameEncoding(): string {
80
        return this.consumeTerminatedText(this._encoding)
81
    }
82
}
83
84
/**
85
 * @param buffer A buffer starting with a null-terminated text string.
86
 * @param encoding The encoding type in which the text string is encoded.
87
 * @returns A split buffer containing the bytes before and after the null
88
 *          termination. If no null termination is found, considers that
89
 *          the buffer was not containing a text string and returns
90
 *          the given buffer as the remainder in the split buffer.
91
 */
92
export function splitNullTerminatedBuffer(
93
    buffer: Buffer,
94
    encoding: TextEncoding
95
) {
96
    const charSize = ([
97
        TextEncoding.UTF_16_WITH_BOM,
98
        TextEncoding.UTF_16_BE
99
    ] as number[]).includes(encoding) ? 2 : 1
100
101
    for (let pos = 0; pos <= buffer.length - charSize; pos += charSize) {
102
        if (buffer.readUIntBE(pos, charSize) === 0) {
103
            return [
104
                buffer.subarray(0, pos),
105
                buffer.subarray(pos + charSize)
106
            ] as const
107
        }
108
    }
109
    throw new RangeError("Terminating character not found")
110
}
111