master
  1package gitdiff
  2
  3import (
  4	"errors"
  5	"fmt"
  6	"io"
  7)
  8
  9// BinaryApplier applies binary changes described in a fragment to source data.
 10// The applier must be closed after use.
 11type BinaryApplier struct {
 12	dst io.Writer
 13	src io.ReaderAt
 14
 15	closed bool
 16	dirty  bool
 17}
 18
 19// NewBinaryApplier creates an BinaryApplier that reads data from src and
 20// writes modified data to dst.
 21func NewBinaryApplier(dst io.Writer, src io.ReaderAt) *BinaryApplier {
 22	a := BinaryApplier{
 23		dst: dst,
 24		src: src,
 25	}
 26	return &a
 27}
 28
 29// ApplyFragment applies the changes in the fragment f and writes the result to
 30// dst. ApplyFragment can be called at most once.
 31//
 32// If an error occurs while applying, ApplyFragment returns an *ApplyError that
 33// annotates the error with additional information. If the error is because of
 34// a conflict between a fragment and the source, the wrapped error will be a
 35// *Conflict.
 36func (a *BinaryApplier) ApplyFragment(f *BinaryFragment) error {
 37	if f == nil {
 38		return applyError(errors.New("nil fragment"))
 39	}
 40	if a.closed {
 41		return applyError(errApplierClosed)
 42	}
 43	if a.dirty {
 44		return applyError(errApplyInProgress)
 45	}
 46
 47	// mark an apply as in progress, even if it fails before making changes
 48	a.dirty = true
 49
 50	switch f.Method {
 51	case BinaryPatchLiteral:
 52		if _, err := a.dst.Write(f.Data); err != nil {
 53			return applyError(err)
 54		}
 55	case BinaryPatchDelta:
 56		if err := applyBinaryDeltaFragment(a.dst, a.src, f.Data); err != nil {
 57			return applyError(err)
 58		}
 59	default:
 60		return applyError(fmt.Errorf("unsupported binary patch method: %v", f.Method))
 61	}
 62	return nil
 63}
 64
 65// Close writes any data following the last applied fragment and prevents
 66// future calls to ApplyFragment.
 67func (a *BinaryApplier) Close() (err error) {
 68	if a.closed {
 69		return nil
 70	}
 71
 72	a.closed = true
 73	if !a.dirty {
 74		_, err = copyFrom(a.dst, a.src, 0)
 75	} else {
 76		// do nothing, applying a binary fragment copies all data
 77	}
 78	return err
 79}
 80
 81func applyBinaryDeltaFragment(dst io.Writer, src io.ReaderAt, frag []byte) error {
 82	srcSize, delta := readBinaryDeltaSize(frag)
 83	if err := checkBinarySrcSize(src, srcSize); err != nil {
 84		return err
 85	}
 86
 87	dstSize, delta := readBinaryDeltaSize(delta)
 88
 89	for len(delta) > 0 {
 90		op := delta[0]
 91		if op == 0 {
 92			return errors.New("invalid delta opcode 0")
 93		}
 94
 95		var n int64
 96		var err error
 97		switch op & 0x80 {
 98		case 0x80:
 99			n, delta, err = applyBinaryDeltaCopy(dst, op, delta[1:], src)
100		case 0x00:
101			n, delta, err = applyBinaryDeltaAdd(dst, op, delta[1:])
102		}
103		if err != nil {
104			return err
105		}
106		dstSize -= n
107	}
108
109	if dstSize != 0 {
110		return errors.New("corrupt binary delta: insufficient or extra data")
111	}
112	return nil
113}
114
115// readBinaryDeltaSize reads a variable length size from a delta-encoded binary
116// fragment, returing the size and the unused data. Data is encoded as:
117//
118//	[[1xxxxxxx]...] [0xxxxxxx]
119//
120// in little-endian order, with 7 bits of the value per byte.
121func readBinaryDeltaSize(d []byte) (size int64, rest []byte) {
122	shift := uint(0)
123	for i, b := range d {
124		size |= int64(b&0x7F) << shift
125		shift += 7
126		if b <= 0x7F {
127			return size, d[i+1:]
128		}
129	}
130	return size, nil
131}
132
133// applyBinaryDeltaAdd applies an add opcode in a delta-encoded binary
134// fragment, returning the amount of data written and the usused part of the
135// fragment. An add operation takes the form:
136//
137//	[0xxxxxx][[data1]...]
138//
139// where the lower seven bits of the opcode is the number of data bytes
140// following the opcode. See also pack-format.txt in the Git source.
141func applyBinaryDeltaAdd(w io.Writer, op byte, delta []byte) (n int64, rest []byte, err error) {
142	size := int(op)
143	if len(delta) < size {
144		return 0, delta, errors.New("corrupt binary delta: incomplete add")
145	}
146	_, err = w.Write(delta[:size])
147	return int64(size), delta[size:], err
148}
149
150// applyBinaryDeltaCopy applies a copy opcode in a delta-encoded binary
151// fragment, returing the amount of data written and the unused part of the
152// fragment. A copy operation takes the form:
153//
154//	[1xxxxxxx][offset1][offset2][offset3][offset4][size1][size2][size3]
155//
156// where the lower seven bits of the opcode determine which non-zero offset and
157// size bytes are present in little-endian order: if bit 0 is set, offset1 is
158// present, etc. If no offset or size bytes are present, offset is 0 and size
159// is 0x10000. See also pack-format.txt in the Git source.
160func applyBinaryDeltaCopy(w io.Writer, op byte, delta []byte, src io.ReaderAt) (n int64, rest []byte, err error) {
161	const defaultSize = 0x10000
162
163	unpack := func(start, bits uint) (v int64) {
164		for i := uint(0); i < bits; i++ {
165			mask := byte(1 << (i + start))
166			if op&mask > 0 {
167				if len(delta) == 0 {
168					err = errors.New("corrupt binary delta: incomplete copy")
169					return
170				}
171				v |= int64(delta[0]) << (8 * i)
172				delta = delta[1:]
173			}
174		}
175		return
176	}
177
178	offset := unpack(0, 4)
179	size := unpack(4, 3)
180	if err != nil {
181		return 0, delta, err
182	}
183	if size == 0 {
184		size = defaultSize
185	}
186
187	// TODO(bkeyes): consider pooling these buffers
188	b := make([]byte, size)
189	if _, err := src.ReadAt(b, offset); err != nil {
190		return 0, delta, err
191	}
192
193	_, err = w.Write(b)
194	return size, delta, err
195}
196
197func checkBinarySrcSize(r io.ReaderAt, size int64) error {
198	ok, err := isLen(r, size)
199	if err != nil {
200		return err
201	}
202	if !ok {
203		return &Conflict{"fragment src size does not match actual src size"}
204	}
205	return nil
206}