Skip to content

Commit c98b56b

Browse files
authored
Update GitPktLineDecoder.mts
1 parent e938b68 commit c98b56b

1 file changed

Lines changed: 110 additions & 32 deletions

File tree

src/GitPktLineDecoder.mts

Lines changed: 110 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,138 @@
11
import {
22
GitPktLine,
3-
} from "./GitPktLine.mjs"
3+
} from './GitPktLine.mjs'
44

5+
class GitPktLineDecoder implements Transformer<Uint8Array, GitPktLine> {
6+
private static readonly SPECIALS_LEN_TBL: Record<number, GitPktLine> =
7+
GitPktLine.SPECIAL_LINES.reduce((acc: Record<number, GitPktLine>, line) => {
8+
acc[line.rawLength] = line
9+
return acc
10+
}, {})
511

6-
class GitPktLineDecoder implements Transformer<string, GitPktLine> {
7-
private static readonly SPECIALS_RAW_LEN_TBL = GitPktLine.SPECIAL_LINES.reduce((acc: Record<number, GitPktLine>, line) => {
8-
acc[line.rawLength] = line
9-
return acc
10-
}, {})
12+
// Internal byte buffer
13+
private buffer: Uint8Array = new Uint8Array(0)
14+
private offset = 0 // start index into buffer
1115

12-
private buffer: string = ''
16+
public async transform(chunk: Uint8Array, controller: TransformStreamDefaultController<GitPktLine>) {
17+
this.appendChunk(chunk)
1318

14-
public async transform(chunk: string, controller: TransformStreamDefaultController) {
15-
this.buffer += chunk
19+
// Process as many complete pkt-lines as possible
20+
// We always require at least 4 bytes for the header.
21+
while (this.availableBytes() >= GitPktLine.HEX_LENGTH) {
22+
const headerView = this.buffer.subarray(this.offset, this.offset + GitPktLine.HEX_LENGTH)
23+
const length = GitPktLineDecoder.parseHeaderLength(headerView)
1624

17-
while ( this.buffer.length >= GitPktLine.HEX_LENGTH ) {
18-
const hexLengthStr = this.buffer.slice(0, GitPktLine.HEX_LENGTH)
19-
const pktLineLength = parseInt(hexLengthStr, 16)
20-
21-
if ( pktLineLength > GitPktLine.MAX_RAW_LENGTH ) {
22-
throw new Error(`Invalid packet: Length \`${pktLineLength}\` exceeds the maximum limit`)
25+
// Enforce application max early
26+
if (length > GitPktLine.MAX_RAW_LENGTH) {
27+
throw new Error(`Invalid packet: length ${length} exceeds maximum limit ${GitPktLine.MAX_RAW_LENGTH}`)
2328
}
2429

25-
if ( pktLineLength < GitPktLine.HEX_LENGTH ) {
26-
const pktLine = GitPktLineDecoder.SPECIALS_RAW_LEN_TBL[pktLineLength]
27-
if ( pktLine ) {
28-
controller.enqueue(pktLine)
29-
} else {
30-
throw new Error("Invalid pkt line length")
30+
// Control packets: 0000, 0001, 0002
31+
if (length < GitPktLine.HEX_LENGTH) {
32+
const special = GitPktLineDecoder.SPECIALS_LEN_TBL[length]
33+
if (!special) {
34+
throw new Error(`Invalid pkt-line control length: ${length}`)
3135
}
3236

33-
this.buffer = this.buffer.slice(GitPktLine.HEX_LENGTH)
37+
// Control packets are just the 4-byte header; no payload, no newline.
38+
this.consumeBytes(GitPktLine.HEX_LENGTH)
39+
controller.enqueue(special)
3440
continue
3541
}
3642

37-
if ( this.buffer.length < pktLineLength ) {
43+
// Data packets: need the full frame before we can decode
44+
if (this.availableBytes() < length) {
45+
// Wait for more data
3846
break
3947
}
4048

41-
const end = this.buffer.charAt(pktLineLength - 1) === '\n' ? pktLineLength - 1 : pktLineLength
42-
controller.enqueue(new GitPktLine({
43-
content: this.buffer.slice(GitPktLine.HEX_LENGTH, end),
44-
rawLine: this.buffer.slice(0, pktLineLength),
45-
}))
46-
this.buffer = this.buffer.slice(pktLineLength)
49+
const raw = this.buffer.subarray(this.offset, this.offset + length)
50+
51+
// Let GitPktLine do full validation (newline, protocol max, etc.)
52+
const pkt = GitPktLine.fromRaw(raw, /* precompile */ true)
53+
controller.enqueue(pkt)
54+
55+
this.consumeBytes(length)
56+
}
57+
}
58+
59+
// ---- Buffer management ----
60+
61+
private availableBytes(): number {
62+
return this.buffer.length - this.offset
63+
}
64+
65+
private appendChunk(chunk: Uint8Array): void {
66+
if (chunk.length === 0) return
67+
68+
if (this.buffer.length === 0) {
69+
// Fast path: no existing data
70+
this.buffer = chunk
71+
this.offset = 0
72+
return
73+
}
74+
75+
// Compact if we've consumed a lot
76+
if (this.offset > 0) {
77+
const remaining = this.buffer.subarray(this.offset)
78+
const merged = new Uint8Array(remaining.length + chunk.length)
79+
merged.set(remaining, 0)
80+
merged.set(chunk, remaining.length)
81+
this.buffer = merged
82+
this.offset = 0
83+
} else {
84+
// No consumed prefix; just append
85+
const merged = new Uint8Array(this.buffer.length + chunk.length)
86+
merged.set(this.buffer, 0)
87+
merged.set(chunk, this.buffer.length)
88+
this.buffer = merged
4789
}
4890
}
91+
92+
private consumeBytes(count: number): void {
93+
this.offset += count
94+
if (this.offset >= this.buffer.length) {
95+
// Fully consumed; reset
96+
this.buffer = new Uint8Array(0)
97+
this.offset = 0
98+
}
99+
}
100+
101+
// ---- Header parsing ----
102+
103+
private static parseHeaderLength(header: Uint8Array): number {
104+
if (header.length !== GitPktLine.HEX_LENGTH) {
105+
throw new Error('Pkt-line header must be exactly 4 bytes')
106+
}
107+
108+
let hex = ''
109+
for (let i = 0; i < GitPktLine.HEX_LENGTH; i++) {
110+
hex += String.fromCharCode(header[i])
111+
}
112+
113+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
114+
throw new Error(`Invalid pkt-line length header: "${hex}"`)
115+
}
116+
117+
const length = parseInt(hex, 16)
118+
if (!Number.isFinite(length) || length < 0 || length > 0xffff) {
119+
throw new Error(`Invalid pkt-line length value: ${length}`)
120+
}
121+
122+
return length
123+
}
49124
}
50125

51-
class GitPktLineDecoderStream extends TransformStream<string, GitPktLine> {
52-
constructor(_?: unknown, writableStrategy?: QueuingStrategy<string>, readableStrategy?: QueuingStrategy<GitPktLine>) {
126+
class GitPktLineDecoderStream extends TransformStream<Uint8Array, GitPktLine> {
127+
constructor(
128+
_?: unknown,
129+
writableStrategy?: QueuingStrategy<Uint8Array>,
130+
readableStrategy?: QueuingStrategy<GitPktLine>,
131+
) {
53132
super(new GitPktLineDecoder(), writableStrategy, readableStrategy)
54133
}
55134
}
56135

57-
58136
export {
59137
GitPktLineDecoder,
60138
GitPktLineDecoderStream,

0 commit comments

Comments
 (0)