Skip to content

Commit 5a521ed

Browse files
committed
Implement binary fragment parsing
Parse forward and optionally reverse fragments, decoding and inflating the ascii85 encoded data in a binary patch. This is completely untested at the moment and probably has obvious and stupid bugs.
1 parent 013e581 commit 5a521ed

File tree

1 file changed

+123
-2
lines changed

1 file changed

+123
-2
lines changed

gitdiff/parser.go

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package gitdiff
22

33
import (
44
"bufio"
5+
"bytes"
6+
"compress/flate"
7+
"encoding/ascii85"
58
"fmt"
69
"io"
710
"strconv"
@@ -324,10 +327,25 @@ func (p *parser) ParseBinaryFragments(f *File) (n int, err error) {
324327

325328
f.IsBinary = true
326329
if hasData {
327-
panic("TODO(bkeyes): unimplemented")
330+
forward, err := p.ParseBinaryFragment()
331+
if err != nil {
332+
return n, err
333+
}
334+
if forward == nil {
335+
return n, p.Errorf(0, "missing data for binary patch")
336+
}
337+
f.BinaryFragment = forward
338+
n++
339+
340+
// valid for reverse to not exist, but it must be valid if present
341+
reverse, err := p.ParseBinaryFragment()
342+
if err != nil {
343+
return n, err
344+
}
345+
f.ReverseBinaryFragment = reverse
328346
}
329347

330-
return 0, nil
348+
return n, nil
331349
}
332350

333351
func (p *parser) ParseBinaryMarker() (isBinary bool, hasData bool, err error) {
@@ -346,6 +364,109 @@ func (p *parser) ParseBinaryMarker() (isBinary bool, hasData bool, err error) {
346364
return true, hasData, nil
347365
}
348366

367+
func (p *parser) ParseBinaryFragment() (*BinaryFragment, error) {
368+
// TODO(bkeyes): split this function into small parts
369+
// TODO(bkeyes): add summary of data format so this is less mysterious
370+
const (
371+
shortestValidLine = "A00000\n"
372+
maxBytesPerLine = 52
373+
)
374+
375+
parts := strings.SplitN(p.Line(0), " ", 2)
376+
if len(parts) < 2 {
377+
return nil, nil
378+
}
379+
380+
frag := &BinaryFragment{}
381+
switch parts[0] {
382+
case "delta":
383+
frag.Method = BinaryPatchDelta
384+
case "literal":
385+
frag.Method = BinaryPatchLiteral
386+
default:
387+
return nil, nil
388+
}
389+
390+
totalBytes, err := strconv.ParseInt(parts[1], 10, 64)
391+
if err != nil {
392+
nerr := err.(*strconv.NumError)
393+
return nil, p.Errorf(0, "binary patch: invalid data length: %v", nerr.Err)
394+
}
395+
396+
var data bytes.Buffer
397+
buf := make([]byte, maxBytesPerLine)
398+
399+
for {
400+
if err := p.Next(); err != nil {
401+
if err == io.EOF {
402+
break
403+
}
404+
return nil, err
405+
}
406+
line := p.Line(0)
407+
408+
if line == "\n" {
409+
// blank line indicates the end of the fragment
410+
break
411+
}
412+
413+
// base85 encoding means each line is a multiple of 5 + first char and newline
414+
if len(line) < len(shortestValidLine) || (len(line)-2)%5 != 0 {
415+
return nil, p.Errorf(0, "binary patch: corrupt data line")
416+
}
417+
418+
byteCount := int(line[0])
419+
switch {
420+
case 'A' <= byteCount && byteCount <= 'Z':
421+
byteCount = byteCount - 'A' + 1
422+
case 'a' <= byteCount && byteCount <= 'z':
423+
byteCount = byteCount - 'a' + 27
424+
default:
425+
return nil, p.Errorf(0, "binary patch: invalid length byte: %q", line[0])
426+
}
427+
428+
// base85 encodes every 4 bytes into 5 characters, with up to 3 bytes of end padding
429+
maxByteCount := (len(line) - 2) / 5 * 4
430+
if byteCount >= maxByteCount || byteCount < maxByteCount-3 {
431+
return nil, p.Errorf(0, "binary patch: incorrect byte count: %d", byteCount)
432+
}
433+
434+
ndst, _, err := ascii85.Decode(buf, []byte(line[1:]), byteCount < maxBytesPerLine)
435+
if err != nil {
436+
return nil, p.Errorf(0, "binary patch: %v", err)
437+
}
438+
if ndst != byteCount {
439+
return nil, p.Errorf(0, "binary patch: expected %d bytes, but decoded %d", byteCount, ndst)
440+
}
441+
data.Write(buf[:ndst])
442+
}
443+
444+
if err := inflateBinaryChunk(frag, &data, totalBytes); err != nil {
445+
return nil, p.Errorf(0, "binary patch: %v", err)
446+
}
447+
448+
// consume the empty line that ended the fragment
449+
if err := p.Next(); err != nil && err != io.EOF {
450+
return nil, err
451+
}
452+
return frag, nil
453+
}
454+
455+
func inflateBinaryChunk(frag *BinaryFragment, r io.Reader, length int64) error {
456+
data := make([]byte, length)
457+
458+
inflater := flate.NewReader(r)
459+
if _, err := io.ReadFull(inflater, frag.Data); err != nil {
460+
return err
461+
}
462+
if err := inflater.Close(); err != nil {
463+
return err
464+
}
465+
466+
frag.Data = data
467+
return nil
468+
}
469+
349470
func parseRange(s string) (start int64, end int64, err error) {
350471
parts := strings.SplitN(s, ",", 2)
351472

0 commit comments

Comments
 (0)