this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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.

+123 -2
+123 -2
gitdiff/parser.go
··· 2 2 3 3 import ( 4 4 "bufio" 5 + "bytes" 6 + "compress/flate" 7 + "encoding/ascii85" 5 8 "fmt" 6 9 "io" 7 10 "strconv" ··· 324 327 325 328 f.IsBinary = true 326 329 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 328 346 } 329 347 330 - return 0, nil 348 + return n, nil 331 349 } 332 350 333 351 func (p *parser) ParseBinaryMarker() (isBinary bool, hasData bool, err error) { ··· 344 362 return false, false, err 345 363 } 346 364 return true, hasData, nil 365 + } 366 + 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 347 468 } 348 469 349 470 func parseRange(s string) (start int64, end int64, err error) {