github.com/stffabi/git-lfs@v2.3.5-0.20180214015214-8eeaa8d88902+incompatible/git/odb/pack/chain_delta.go (about)

     1  package pack
     2  
     3  import (
     4  	"github.com/git-lfs/git-lfs/errors"
     5  )
     6  
     7  // ChainDelta represents a "delta" component of a delta-base chain.
     8  type ChainDelta struct {
     9  	// Base is the base delta-base chain that this delta should be applied
    10  	// to. It can be a ChainBase in the simple case, or it can itself be a
    11  	// ChainDelta, which resolves against another ChainBase, when the
    12  	// delta-base chain is of length greater than 2.
    13  	base Chain
    14  	// delta is the set of copy/add instructions to apply on top of the
    15  	// base.
    16  	delta []byte
    17  }
    18  
    19  // Unpack applies the delta operation to the previous delta-base chain, "base".
    20  //
    21  // If any of the delta-base instructions were invalid, an error will be
    22  // returned.
    23  func (d *ChainDelta) Unpack() ([]byte, error) {
    24  	base, err := d.base.Unpack()
    25  	if err != nil {
    26  		return nil, err
    27  	}
    28  
    29  	return patch(base, d.delta)
    30  }
    31  
    32  // Type returns the type of the base of the delta-base chain.
    33  func (d *ChainDelta) Type() PackedObjectType {
    34  	return d.base.Type()
    35  }
    36  
    37  // patch applies the delta instructions in "delta" to the base given as "base".
    38  // It returns the result of applying those patch instructions to base, but does
    39  // not modify base itself.
    40  //
    41  // If any of the delta instructions were malformed, or otherwise could not be
    42  // applied to the given base, an error will returned, along with an empty set of
    43  // data.
    44  func patch(base, delta []byte) ([]byte, error) {
    45  	srcSize, pos := patchDeltaHeader(delta, 0)
    46  	if srcSize != int64(len(base)) {
    47  		// The header of the delta gives the size of the source contents
    48  		// that it is a patch over.
    49  		//
    50  		// If this does not match with the srcSize, return an error
    51  		// early so as to avoid a possible bounds error below.
    52  		return nil, errors.New("git/odb/pack: invalid delta data")
    53  	}
    54  
    55  	// The remainder of the delta header contains the destination size, and
    56  	// moves the "pos" offset to the correct position to begin the set of
    57  	// delta instructions.
    58  	destSize, pos := patchDeltaHeader(delta, pos)
    59  
    60  	dest := make([]byte, 0, destSize)
    61  
    62  	for pos < len(delta) {
    63  		c := int(delta[pos])
    64  		pos += 1
    65  
    66  		if c&0x80 != 0 {
    67  			// If the most significant bit (MSB, at position 0x80)
    68  			// is set, this is a copy instruction. Advance the
    69  			// position one byte backwards, and initialize variables
    70  			// for the copy offset and size instructions.
    71  			pos -= 1
    72  
    73  			var co, cs int
    74  
    75  			// The lower-half of "c" (0000 1111) defines a "bitmask"
    76  			// for the copy offset.
    77  			if c&0x1 != 0 {
    78  				pos += 1
    79  				co = int(delta[pos])
    80  			}
    81  			if c&0x2 != 0 {
    82  				pos += 1
    83  				co |= (int(delta[pos]) << 8)
    84  			}
    85  			if c&0x4 != 0 {
    86  				pos += 1
    87  				co |= (int(delta[pos]) << 16)
    88  			}
    89  			if c&0x8 != 0 {
    90  				pos += 1
    91  				co |= (int(delta[pos]) << 24)
    92  			}
    93  
    94  			// The upper-half of "c" (1111 0000) defines a "bitmask"
    95  			// for the size of the copy instruction.
    96  			if c&0x10 != 0 {
    97  				pos += 1
    98  				cs = int(delta[pos])
    99  			}
   100  			if c&0x20 != 0 {
   101  				pos += 1
   102  				cs |= (int(delta[pos]) << 8)
   103  			}
   104  			if c&0x40 != 0 {
   105  				pos += 1
   106  				cs |= (int(delta[pos]) << 16)
   107  			}
   108  
   109  			if cs == 0 {
   110  				// If the copy size is zero, we assume that it
   111  				// is the next whole number after the max uint32
   112  				// value.
   113  				cs = 0x10000
   114  			}
   115  			pos += 1
   116  
   117  			// Once we have the copy offset and length defined, copy
   118  			// that number of bytes from the base into the
   119  			// destination. Since we are copying from the base and
   120  			// not the delta, the position into the delta ("pos")
   121  			// need not be updated.
   122  			dest = append(dest, base[co:co+cs]...)
   123  		} else if c != 0 {
   124  			// If the most significant bit (MSB) is _not_ set, we
   125  			// instead process a copy instruction, where "c" is the
   126  			// number of successive bytes in the delta patch to add
   127  			// to the output.
   128  			//
   129  			// Copy the bytes and increment the read pointer
   130  			// forward.
   131  			dest = append(dest, delta[pos:int(pos)+c]...)
   132  
   133  			pos += int(c)
   134  		} else {
   135  			// Otherwise, "c" is 0, and is an invalid delta
   136  			// instruction.
   137  			//
   138  			// Return immediately.
   139  			return nil, errors.New(
   140  				"git/odb/pack: invalid delta data")
   141  		}
   142  	}
   143  
   144  	if destSize != int64(len(dest)) {
   145  		// If after patching the delta against the base, the destination
   146  		// size is different than the expected destination size, we have
   147  		// an invalid set of patch instructions.
   148  		//
   149  		// Return immediately.
   150  		return nil, errors.New("git/odb/pack: invalid delta data")
   151  	}
   152  	return dest, nil
   153  }
   154  
   155  // patchDeltaHeader examines the header within delta at the given offset, and
   156  // returns the size encoded within it, as well as the ending offset where begins
   157  // the next header, or the patch instructions.
   158  func patchDeltaHeader(delta []byte, pos int) (size int64, end int) {
   159  	var shift uint
   160  	var c int64
   161  
   162  	for shift == 0 || c&0x80 != 0 {
   163  		if len(delta) <= pos {
   164  			panic("git/odb/pack: invalid delta header")
   165  		}
   166  
   167  		c = int64(delta[pos])
   168  
   169  		pos++
   170  		size |= (c & 0x7f) << shift
   171  		shift += 7
   172  	}
   173  
   174  	return size, pos
   175  }