├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── cmd ├── bsdiff │ └── main.go └── bspatch │ └── main.go ├── go.mod ├── go.sum ├── go.test.sh ├── n.go ├── n_test.go └── pkg ├── bsdiff ├── bsdiff.go └── bsdiff_test.go ├── bspatch ├── bspatch.go └── bspatch_test.go └── util └── io.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | tmp/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.10.x 5 | - 1.11.x 6 | - 1.12.x 7 | 8 | before_install: 9 | - go get golang.org/x/tools/cmd/cover 10 | - go get github.com/mattn/goveralls 11 | 12 | script: 13 | - go test -v -covermode=count -coverprofile=coverage.out ./... 14 | - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN 15 | - ./go.test.sh 16 | 17 | after_success: 18 | - bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Gabriel Ochsenhofer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-bsdiff 2 | Pure Go implementation of [bsdiff](http://www.daemonology.net/bsdiff/) 4. 3 | 4 | [![GoDoc](https://godoc.org/github.com/gabstv/go-bsdiff?status.svg)](https://godoc.org/github.com/gabstv/go-bsdiff) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/gabstv/go-bsdiff)](https://goreportcard.com/report/github.com/gabstv/go-bsdiff) 6 | [![Build Status](https://travis-ci.org/gabstv/go-bsdiff.svg?branch=master)](https://travis-ci.org/gabstv/go-bsdiff) 7 | [![Coverage Status](https://coveralls.io/repos/github/gabstv/go-bsdiff/badge.svg?branch=master)](https://coveralls.io/github/gabstv/go-bsdiff?branch=master) 8 | 9 | 10 | bsdiff and bspatch are tools for building and applying patches to binary files. By using suffix sorting (specifically, Larsson and Sadakane's [qsufsort](http://www.larsson.dogma.net/ssrev-tr.pdf)) and taking advantage of how executable files change. 11 | 12 | The package can be used as a library (pkg/bsdiff pkg/bspatch) or as a cli program (cmd/bsdiff cmd/bspatch). 13 | 14 | ## As a library 15 | 16 | ### Bsdiff Bytes 17 | ```Go 18 | package main 19 | 20 | import ( 21 | "fmt" 22 | "bytes" 23 | 24 | "github.com/gabstv/go-bsdiff/pkg/bsdiff" 25 | "github.com/gabstv/go-bsdiff/pkg/bspatch" 26 | ) 27 | 28 | func main(){ 29 | // example files 30 | oldfile := []byte{0xfa, 0xdd, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff} 31 | newfile := []byte{0xfa, 0xdd, 0x00, 0x00, 0x00, 0xee, 0xee, 0x00, 0x00, 0xff, 0xfe, 0xfe} 32 | 33 | // generate a BSDIFF4 patch 34 | patch, err := bsdiff.Bytes(oldfile, newfile) 35 | if err != nil { 36 | panic(err) 37 | } 38 | fmt.Println(patch) 39 | 40 | // Apply a BSDIFF4 patch 41 | newfile2, err := bspatch.Bytes(oldfile, patch) 42 | if err != nil { 43 | panic(err) 44 | } 45 | if !bytes.Equal(newfile, newfile2) { 46 | panic() 47 | } 48 | } 49 | ``` 50 | ### Bsdiff Reader 51 | ```Go 52 | package main 53 | 54 | import ( 55 | "fmt" 56 | "bytes" 57 | 58 | "github.com/gabstv/go-bsdiff/pkg/bsdiff" 59 | "github.com/gabstv/go-bsdiff/pkg/bspatch" 60 | ) 61 | 62 | func main(){ 63 | oldrdr := bytes.NewReader([]byte{0xfa, 0xdd, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff}) 64 | newrdr := bytes.NewReader([]byte{0xfa, 0xdd, 0x00, 0x00, 0x00, 0xee, 0xee, 0x00, 0x00, 0xff, 0xfe, 0xfe}) 65 | patch := new(bytes.Buffer) 66 | 67 | // generate a BSDIFF4 patch 68 | if err := bsdiff.Reader(oldrdr, newrdr, patch); err != nil { 69 | panic(err) 70 | } 71 | 72 | newpatchedf := new(bytes.Buffer) 73 | oldrdr.Seek(0, 0) 74 | 75 | // Apply a BSDIFF4 patch 76 | if err := bspatch.Reader(oldrdr, newpatchedf, patch); err != nil { 77 | panic(err) 78 | } 79 | fmt.Println(newpatchedf.Bytes()) 80 | } 81 | ``` 82 | 83 | ## As a program (CLI) 84 | ```sh 85 | go get -u -v github.com/gabstv/go-bsdiff/cmd/... 86 | 87 | bsdiff oldfile newfile patch 88 | bspatch oldfile newfile2 patch 89 | ``` -------------------------------------------------------------------------------- /cmd/bsdiff/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/gabstv/go-bsdiff/pkg/bsdiff" 7 | ) 8 | 9 | func main() { 10 | if len(os.Args) != 4 { 11 | printusage(1) 12 | } 13 | err := bsdiff.File(os.Args[1], os.Args[2], os.Args[3]) 14 | if err != nil { 15 | println(err.Error()) 16 | printusage(1) 17 | } 18 | } 19 | 20 | func printusage(exitcode int) { 21 | println("usage: " + os.Args[0] + " oldfile newfile patchfile") 22 | os.Exit(exitcode) 23 | } 24 | -------------------------------------------------------------------------------- /cmd/bspatch/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/gabstv/go-bsdiff/pkg/bspatch" 7 | ) 8 | 9 | func main() { 10 | if len(os.Args) != 4 { 11 | printusage(1) 12 | } 13 | err := bspatch.File(os.Args[1], os.Args[2], os.Args[3]) 14 | if err != nil { 15 | println(err.Error()) 16 | printusage(1) 17 | } 18 | } 19 | 20 | func printusage(exitcode int) { 21 | println("usage: " + os.Args[0] + " oldfile newfile patchfile") 22 | os.Exit(exitcode) 23 | } 24 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gabstv/go-bsdiff 2 | 3 | require github.com/dsnet/compress v0.0.0-20171208185109-cc9eb1d7ad76 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/dsnet/compress v0.0.0-20171208185109-cc9eb1d7ad76 h1:eX+pdPPlD279OWgdx7f6KqIRSONuK7egk+jDx7OM3Ac= 2 | github.com/dsnet/compress v0.0.0-20171208185109-cc9eb1d7ad76/go.mod h1:KjxHHirfLaw19iGT70HvVjHQsL1vq1SRQB4yOsAfy2s= 3 | -------------------------------------------------------------------------------- /go.test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | echo "" > coverage.txt 5 | 6 | for d in $(go list ./... | grep -v vendor); do 7 | go test -race -coverprofile=profile.out -covermode=atomic "$d" 8 | if [ -f profile.out ]; then 9 | cat profile.out >> coverage.txt 10 | rm profile.out 11 | fi 12 | done -------------------------------------------------------------------------------- /n.go: -------------------------------------------------------------------------------- 1 | // Package bsdiff is a pure Go implementation of Bsdiff 4. 2 | // 3 | // Example: 4 | // package main 5 | // 6 | // import ( 7 | // "fmt" 8 | // "bytes" 9 | // 10 | // "github.com/gabstv/go-bsdiff/pkg/bsdiff" 11 | // "github.com/gabstv/go-bsdiff/pkg/bspatch" 12 | // ) 13 | // 14 | // func main(){ 15 | // // example files 16 | // oldfile := []byte{0xfa, 0xdd, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff} 17 | // newfile := []byte{0xfa, 0xdd, 0x00, 0x00, 0x00, 0xee, 0xee, 0x00, 0x00, 0xff, 0xfe, 0xfe} 18 | // 19 | // // generate a BSDIFF4 patch 20 | // patch, err := bsdiff.Bytes(oldfile, newfile) 21 | // if err != nil { 22 | // panic(err) 23 | // } 24 | // fmt.Println(patch) 25 | // 26 | // // Apply a BSDIFF4 patch 27 | // newfile2, err := bspatch.Bytes(oldfile, patch) 28 | // if err != nil { 29 | // panic(err) 30 | // } 31 | // if !bytes.Equal(newfile, newfile2) { 32 | // panic() 33 | // } 34 | // } 35 | package bsdiff 36 | 37 | func v() int { 38 | return 1 39 | } 40 | -------------------------------------------------------------------------------- /n_test.go: -------------------------------------------------------------------------------- 1 | package bsdiff 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/gabstv/go-bsdiff/pkg/bsdiff" 8 | "github.com/gabstv/go-bsdiff/pkg/bspatch" 9 | ) 10 | 11 | func TestDiffPatch(t *testing.T) { 12 | oldbs := []byte{0xFF, 0xFA, 0xB7, 0xDD} 13 | newbs := []byte{0xFF, 0xFA, 0x90, 0xB7, 0xDD, 0xFE} 14 | patch, err := bsdiff.Bytes(oldbs, newbs) 15 | if err != nil { 16 | t.Fatal(err.Error()) 17 | } 18 | newbs2, err := bspatch.Bytes(oldbs, patch) 19 | if err != nil { 20 | t.Fatal(err.Error()) 21 | } 22 | if !bytes.Equal(newbs, newbs2) { 23 | t.Fatal(newbs2, "!=", newbs) 24 | } 25 | if v() != 1 { 26 | t.Fatal("cover") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pkg/bsdiff/bsdiff.go: -------------------------------------------------------------------------------- 1 | // * Copyright 2003-2005 Colin Percival 2 | // * All rights reserved 3 | // * 4 | // * Redistribution and use in source and binary forms, with or without 5 | // * modification, are permitted providing that the following conditions 6 | // * are met: 7 | // * 1. Redistributions of source code must retain the above copyright 8 | // * notice, this list of conditions and the following disclaimer. 9 | // * 2. Redistributions in binary form must reproduce the above copyright 10 | // * notice, this list of conditions and the following disclaimer in the 11 | // * documentation and/or other materials provided with the distribution. 12 | // * 13 | // * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 14 | // * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | // * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 17 | // * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | // * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 | // * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 | // * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 21 | // * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 22 | // * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | // * POSSIBILITY OF SUCH DAMAGE. 24 | 25 | // Package bsdiff is a binary diff program using suffix sorting. 26 | package bsdiff 27 | 28 | import ( 29 | "bytes" 30 | "fmt" 31 | "io" 32 | "io/ioutil" 33 | 34 | "github.com/dsnet/compress/bzip2" 35 | "github.com/gabstv/go-bsdiff/pkg/util" 36 | ) 37 | 38 | // Bytes takes the old and new byte slices and outputs the diff 39 | func Bytes(oldbs, newbs []byte) ([]byte, error) { 40 | return diffb(oldbs, newbs) 41 | } 42 | 43 | // Reader takes the old and new binaries and outputs to a stream of the diff file 44 | func Reader(oldbin io.Reader, newbin io.Reader, patchf io.Writer) error { 45 | oldbs, err := ioutil.ReadAll(oldbin) 46 | if err != nil { 47 | return err 48 | } 49 | newbs, err := ioutil.ReadAll(newbin) 50 | if err != nil { 51 | return err 52 | } 53 | diffbytes, err := diffb(oldbs, newbs) 54 | if err != nil { 55 | return err 56 | } 57 | return util.PutWriter(patchf, diffbytes) 58 | } 59 | 60 | // File reads the old and new files to create a diff patch file 61 | func File(oldfile, newfile, patchfile string) error { 62 | oldbs, err := ioutil.ReadFile(oldfile) 63 | if err != nil { 64 | return fmt.Errorf("could not read oldfile '%v': %v", oldfile, err.Error()) 65 | } 66 | newbs, err := ioutil.ReadFile(newfile) 67 | if err != nil { 68 | return fmt.Errorf("could not read newfile '%v': %v", newfile, err.Error()) 69 | } 70 | diffbytes, err := diffb(oldbs, newbs) 71 | if err != nil { 72 | return fmt.Errorf("bsdiff: %v", err.Error()) 73 | } 74 | if err := ioutil.WriteFile(patchfile, diffbytes, 0644); err != nil { 75 | return fmt.Errorf("could create patchfile '%v': %v", patchfile, err.Error()) 76 | } 77 | return nil 78 | } 79 | 80 | func diffb(oldbin, newbin []byte) ([]byte, error) { 81 | bziprule := &bzip2.WriterConfig{ 82 | Level: bzip2.BestCompression, 83 | } 84 | iii := make([]int, len(oldbin)+1) 85 | qsufsort(iii, oldbin) 86 | 87 | //var db 88 | var dblen, eblen int 89 | 90 | // create the patch file 91 | pf := new(util.BufWriter) 92 | 93 | // Header is 94 | // 0 8 "BSDIFF40" 95 | // 8 8 length of bzip2ed ctrl block 96 | // 16 8 length of bzip2ed diff block 97 | // 24 8 length of pnew file */ 98 | // File is 99 | // 0 32 Header 100 | // 32 ?? Bzip2ed ctrl block 101 | // ?? ?? Bzip2ed diff block 102 | // ?? ?? Bzip2ed extra block 103 | 104 | newsize := len(newbin) 105 | oldsize := len(oldbin) 106 | 107 | header := make([]byte, 32) 108 | buf := make([]byte, 8) 109 | 110 | copy(header, []byte("BSDIFF40")) 111 | offtout(0, header[8:]) 112 | offtout(0, header[16:]) 113 | offtout(newsize, header[24:]) 114 | if _, err := pf.Write(header); err != nil { 115 | return nil, err 116 | } 117 | // Compute the differences, writing ctrl as we go 118 | pfbz2, err := bzip2.NewWriter(pf, bziprule) 119 | if err != nil { 120 | return nil, err 121 | } 122 | var scan, ln, lastscan, lastpos, lastoffset int 123 | 124 | var oldscore, scsc int 125 | var pos int 126 | 127 | var s, Sf, lenf, Sb, lenb int 128 | var overlap, Ss, lens int 129 | 130 | db := make([]byte, newsize+1) 131 | eb := make([]byte, newsize+1) 132 | 133 | defer func() { 134 | if pfbz2 != nil { 135 | pfbz2.Close() 136 | } 137 | }() 138 | 139 | for scan < newsize { 140 | oldscore = 0 141 | 142 | // scsc = scan += len 143 | scan += ln 144 | scsc = scan 145 | for scan < newsize { 146 | ln = search(iii, oldbin, newbin[scan:], 0, oldsize, &pos) 147 | 148 | for scsc < scan+ln { 149 | if scsc+lastoffset < oldsize && oldbin[scsc+lastoffset] == newbin[scsc] { 150 | oldscore++ 151 | } 152 | scsc++ 153 | } 154 | if ln == oldscore && ln != 0 { 155 | break 156 | } 157 | if ln > oldscore+8 { 158 | break 159 | } 160 | if scan+lastoffset < oldsize && oldbin[scan+lastoffset] == newbin[scan] { 161 | oldscore-- 162 | } 163 | // 164 | scan++ 165 | } 166 | 167 | if ln != oldscore || scan == newsize { 168 | s = 0 169 | Sf = 0 170 | lenf = 0 171 | i := 0 172 | for lastscan+i < scan && lastpos+i < oldsize { 173 | if oldbin[lastpos+i] == newbin[lastscan+i] { 174 | s++ 175 | } 176 | i++ 177 | if s*2-i > Sf*2-lenf { 178 | Sf = s 179 | lenf = i 180 | } 181 | } 182 | 183 | lenb = 0 184 | if scan < newsize { 185 | s = 0 186 | Sb = 0 187 | for i = 1; scan >= lastscan+i && pos >= i; i++ { 188 | if oldbin[pos-i] == newbin[scan-i] { 189 | s++ 190 | } 191 | if s*2-i > Sb*2-lenb { 192 | Sb = s 193 | lenb = i 194 | } 195 | } 196 | } 197 | 198 | if lastscan+lenf > scan-lenb { 199 | overlap = (lastscan + lenf) - (scan - lenb) 200 | s = 0 201 | Ss = 0 202 | lens = 0 203 | for i = 0; i < overlap; i++ { 204 | if newbin[lastscan+lenf-overlap+i] == oldbin[lastpos+lenf-overlap+i] { 205 | s++ 206 | } 207 | 208 | if newbin[scan-lenb+i] == oldbin[pos-lenb+i] { 209 | s-- 210 | } 211 | if s > Ss { 212 | Ss = s 213 | lens = i + 1 214 | } 215 | } 216 | 217 | lenf += lens - overlap 218 | lenb -= lens 219 | } 220 | 221 | for i = 0; i < lenf; i++ { 222 | db[dblen+i] = newbin[lastscan+i] - oldbin[lastpos+i] 223 | } 224 | for i = 0; i < (scan-lenb)-(lastscan+lenf); i++ { 225 | eb[eblen+i] = newbin[lastscan+lenf+i] 226 | } 227 | 228 | dblen += lenf 229 | eblen += (scan - lenb) - (lastscan + lenf) 230 | 231 | offtout(lenf, buf) 232 | if _, err := pfbz2.Write(buf); err != nil { 233 | return nil, err 234 | } 235 | 236 | offtout((scan-lenb)-(lastscan+lenf), buf) 237 | if _, err := pfbz2.Write(buf); err != nil { 238 | return nil, err 239 | } 240 | 241 | offtout((pos-lenb)-(lastpos+lenf), buf) 242 | if _, err := pfbz2.Write(buf); err != nil { 243 | return nil, err 244 | } 245 | 246 | lastscan = scan - lenb 247 | lastpos = pos - lenb 248 | lastoffset = pos - scan 249 | } 250 | } 251 | if err = pfbz2.Close(); err != nil { 252 | return nil, err 253 | } 254 | 255 | // Compute size of compressed ctrl data 256 | ln = pf.Len() 257 | offtout(ln-32, header[8:]) 258 | 259 | // Write compressed diff data 260 | pfbz2, err = bzip2.NewWriter(pf, bziprule) 261 | if err != nil { 262 | return nil, err 263 | } 264 | if _, err = pfbz2.Write(db[:dblen]); err != nil { 265 | return nil, err 266 | } 267 | 268 | if err = pfbz2.Close(); err != nil { 269 | return nil, err 270 | } 271 | // Compute size of compressed diff data 272 | newsize = pf.Len() 273 | offtout(newsize-ln, header[16:]) 274 | // Write compressed extra data 275 | pfbz2, err = bzip2.NewWriter(pf, bziprule) 276 | if err != nil { 277 | return nil, err 278 | } 279 | if _, err = pfbz2.Write(eb[:eblen]); err != nil { 280 | return nil, err 281 | } 282 | if err = pfbz2.Close(); err != nil { 283 | return nil, err 284 | } 285 | // Seek to the beginning, write the header, and close the file 286 | if _, err = pf.Seek(0, io.SeekStart); err != nil { 287 | return nil, err 288 | } 289 | if _, err = pf.Write(header); err != nil { 290 | return nil, err 291 | } 292 | 293 | return pf.Bytes(), nil 294 | } 295 | 296 | func search(iii []int, oldbin []byte, newbin []byte, st, en int, pos *int) int { 297 | var x, y int 298 | oldsize := len(oldbin) 299 | newsize := len(newbin) 300 | 301 | if en-st < 2 { 302 | x = matchlen(oldbin[iii[st]:], newbin) 303 | y = matchlen(oldbin[iii[en]:], newbin) 304 | 305 | if x > y { 306 | *pos = iii[st] 307 | return x 308 | } 309 | *pos = iii[en] 310 | return y 311 | } 312 | 313 | x = st + (en-st)/2 314 | cmpln := util.Min(oldsize-iii[x], newsize) 315 | if bytes.Compare(oldbin[iii[x]:iii[x]+cmpln], newbin[:cmpln]) < 0 { 316 | return search(iii, oldbin, newbin, x, en, pos) 317 | } 318 | return search(iii, oldbin, newbin, st, x, pos) 319 | } 320 | 321 | func matchlen(oldbin []byte, newbin []byte) int { 322 | var i int 323 | oldsize := len(oldbin) 324 | newsize := len(newbin) 325 | for (i < oldsize) && (i < newsize) { 326 | if oldbin[i] != newbin[i] { 327 | break 328 | } 329 | i++ 330 | } 331 | return i 332 | } 333 | 334 | // offtout puts an int64 (little endian) to buf 335 | func offtout(x int, buf []byte) { 336 | var y int 337 | if x < 0 { 338 | y = -x 339 | } else { 340 | y = x 341 | } 342 | buf[0] = byte(y % 256) 343 | y -= int(buf[0]) 344 | y = y / 256 345 | buf[1] = byte(y % 256) 346 | y -= int(buf[1]) 347 | y = y / 256 348 | buf[2] = byte(y % 256) 349 | y -= int(buf[2]) 350 | y = y / 256 351 | buf[3] = byte(y % 256) 352 | y -= int(buf[3]) 353 | y = y / 256 354 | buf[4] = byte(y % 256) 355 | y -= int(buf[4]) 356 | y = y / 256 357 | buf[5] = byte(y % 256) 358 | y -= int(buf[5]) 359 | y = y / 256 360 | buf[6] = byte(y % 256) 361 | y -= int(buf[6]) 362 | y = y / 256 363 | buf[7] = byte(y % 256) 364 | 365 | if x < 0 { 366 | buf[7] |= 0x80 367 | } 368 | } 369 | 370 | func qsufsort(iii []int, buf []byte) { 371 | buckets := make([]int, 256) 372 | vvv := make([]int, len(iii)) 373 | var i, h, ln int 374 | bufzise := len(buf) 375 | 376 | for i = 0; i < bufzise; i++ { 377 | buckets[buf[i]]++ 378 | } 379 | 380 | for i = 1; i < 256; i++ { 381 | buckets[i] += buckets[i-1] 382 | } 383 | 384 | for i = 255; i > 0; i-- { 385 | buckets[i] = buckets[i-1] 386 | } 387 | buckets[0] = 0 388 | 389 | for i = 0; i < bufzise; i++ { 390 | buckets[buf[i]]++ 391 | iii[buckets[buf[i]]] = i 392 | } 393 | iii[0] = bufzise 394 | 395 | for i = 0; i < bufzise; i++ { 396 | vvv[i] = buckets[buf[i]] 397 | } 398 | vvv[bufzise] = 0 399 | 400 | for i = 1; i < 256; i++ { 401 | if buckets[i] == buckets[i-1]+1 { 402 | iii[buckets[i]] = -1 403 | } 404 | } 405 | iii[0] = -1 406 | 407 | for h = 1; iii[0] != -(bufzise + 1); h += h { 408 | ln = 0 409 | 410 | i = 0 411 | for i < bufzise+1 { 412 | if iii[i] < 0 { 413 | ln -= iii[i] 414 | i -= iii[i] 415 | } else { 416 | if ln != 0 { 417 | iii[i-ln] = -ln 418 | } 419 | ln = vvv[iii[i]] + 1 - i 420 | split(iii, vvv, i, ln, h) 421 | i += ln 422 | ln = 0 423 | } 424 | } 425 | if ln != 0 { 426 | iii[i-ln] = -ln 427 | } 428 | } 429 | 430 | for i = 0; i < bufzise+1; i++ { 431 | iii[vvv[i]] = i 432 | } 433 | } 434 | 435 | func split(iii, vvv []int, start, ln, h int) { 436 | var i, j, k, x int 437 | 438 | if ln < 16 { 439 | for k = start; k < start+ln; k += j { 440 | j = 1 441 | x = vvv[iii[k]+h] 442 | for i = 1; k+i < start+ln; i++ { 443 | if vvv[iii[k+i]+h] < x { 444 | x = vvv[iii[k+i]+h] 445 | j = 0 446 | } 447 | if vvv[iii[k+i]+h] == x { 448 | iii[k+j], iii[k+i] = iii[k+i], iii[k+j] 449 | j++ 450 | } 451 | } 452 | for i = 0; i < j; i++ { 453 | vvv[iii[k+i]] = k + j - 1 454 | } 455 | if j == 1 { 456 | iii[k] = -1 457 | } 458 | } 459 | return 460 | } 461 | 462 | x = vvv[iii[start+(ln/2)]+h] 463 | var jj, kk int 464 | for i = start; i < start+ln; i++ { 465 | if vvv[iii[i]+h] < x { 466 | jj++ 467 | } else if vvv[iii[i]+h] == x { 468 | kk++ 469 | } 470 | } 471 | jj += start 472 | kk += jj 473 | 474 | i = start 475 | j = 0 476 | k = 0 477 | for i < jj { 478 | if vvv[iii[i]+h] < x { 479 | i++ 480 | } else if vvv[iii[i]+h] == x { 481 | iii[i], iii[jj+j] = iii[jj+j], iii[i] 482 | j++ 483 | } else { 484 | iii[i], iii[kk+k] = iii[kk+k], iii[i] 485 | k++ 486 | } 487 | } 488 | for jj+j < kk { 489 | if vvv[iii[jj+j]+h] == x { 490 | j++ 491 | } else { 492 | iii[jj+j], iii[kk+k] = iii[kk+k], iii[jj+j] 493 | k++ 494 | } 495 | } 496 | if jj > start { 497 | split(iii, vvv, start, jj-start, h) 498 | } 499 | 500 | for i = 0; i < kk-jj; i++ { 501 | vvv[iii[jj+i]] = kk - 1 502 | } 503 | if jj == kk-1 { 504 | iii[jj] = -1 505 | } 506 | 507 | if start+ln > kk { 508 | split(iii, vvv, kk, start+ln-kk, h) 509 | } 510 | } 511 | -------------------------------------------------------------------------------- /pkg/bsdiff/bsdiff_test.go: -------------------------------------------------------------------------------- 1 | package bsdiff 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "io/ioutil" 7 | "math/rand" 8 | "os" 9 | "testing" 10 | "time" 11 | 12 | "github.com/gabstv/go-bsdiff/pkg/util" 13 | ) 14 | 15 | func TestDiff(t *testing.T) { 16 | oldbs := []byte{0xFF, 0xFA, 0xB7, 0xDD} 17 | newbs := []byte{0xFF, 0xFA, 0x90, 0xB7, 0xDD, 0xFE} 18 | var diffbs []byte 19 | var err error 20 | if diffbs, err = Bytes(oldbs, newbs); err != nil { 21 | t.Fatal(err) 22 | } 23 | z := []byte{ 24 | 0x42, 0x53, 0x44, 0x49, 0x46, 0x46, 0x34, 0x30, 0x2D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 25 | 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 26 | 0x42, 0x5A, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26, 0x53, 0x59, 0xC9, 0x9D, 0x1D, 0x33, 0x00, 0x00, 27 | 0x06, 0xC0, 0x40, 0x5C, 0x00, 0x40, 0x00, 0x20, 0x00, 0x21, 0x8C, 0xA0, 0x60, 0x6C, 0xE2, 0xC8, 28 | 0xF1, 0x47, 0xC5, 0xDC, 0x91, 0x4E, 0x14, 0x24, 0x32, 0x67, 0x47, 0x4C, 0xC0, 0x42, 0x5A, 0x68, 29 | 0x39, 0x31, 0x41, 0x59, 0x26, 0x53, 0x59, 0xFF, 0x48, 0x9B, 0x82, 0x00, 0x00, 0x00, 0xC0, 0x00, 30 | 0x40, 0x00, 0x20, 0x00, 0x21, 0x18, 0x46, 0xC2, 0xEE, 0x48, 0xA7, 0x0A, 0x12, 0x1F, 0xE9, 0x13, 31 | 0x70, 0x40, 0x42, 0x5A, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26, 0x53, 0x59, 0xDD, 0x13, 0xBF, 0x5C, 32 | 0x00, 0x00, 0x00, 0x00, 0x2A, 0xC0, 0x00, 0x00, 0x80, 0x00, 0x02, 0x00, 0x01, 0x20, 0x00, 0x22, 33 | 0x1B, 0x03, 0x0C, 0x70, 0xC2, 0xEE, 0x48, 0xA7, 0x0A, 0x12, 0x1B, 0xA2, 0x77, 0xEB, 0x80, 34 | } 35 | if !bytes.Equal(diffbs[:len(z)], z) { 36 | t.Fatal(diffbs[:len(z)], "!=", z) 37 | } 38 | } 39 | 40 | func TestOfftout(t *testing.T) { 41 | buf := make([]byte, 8) 42 | offtout(9001, buf) 43 | n := binary.LittleEndian.Uint64(buf) 44 | if n != 9001 { 45 | t.Fatal(n, "!=", 9001) 46 | } 47 | // 48 | offtout(9002, buf) 49 | n = binary.LittleEndian.Uint64(buf) 50 | if n != 9002 { 51 | t.Fatal(n, "!=", 9002) 52 | } 53 | } 54 | 55 | func TestReader(t *testing.T) { 56 | rand.Seed(time.Now().UnixNano()) 57 | file1 := make([]byte, 512) 58 | file2 := make([]byte, 1024) 59 | rand.Read(file1) 60 | copy(file2, file1) 61 | rand.Read(file2[512:]) 62 | rold := bytes.NewReader(file1) 63 | rnew := bytes.NewReader(file2) 64 | rpatch := new(bytes.Buffer) 65 | if err := Reader(rold, rnew, rpatch); err != nil { 66 | t.Fatal(err) 67 | } 68 | b := make([]byte, 8) 69 | rpatch.Read(b) 70 | if !bytes.Equal(b, []byte("BSDIFF40")) { 71 | t.Fail() 72 | } 73 | } 74 | 75 | func TestFile(t *testing.T) { 76 | rand.Seed(time.Now().UnixNano()) 77 | file1 := make([]byte, 1024*32) 78 | file2 := make([]byte, 1024*33) 79 | rand.Read(file1) 80 | copy(file2, file1) 81 | rand.Read(file2[1024*32:]) 82 | rand.Read(file2[100:1024]) 83 | tf0, err := ioutil.TempFile(os.TempDir(), "") 84 | if err != nil { 85 | t.Fatal(err) 86 | } 87 | t0n := tf0.Name() 88 | tf1, err := ioutil.TempFile(os.TempDir(), "") 89 | if err != nil { 90 | t.Fatal(err) 91 | } 92 | t1n := tf1.Name() 93 | if err := util.PutWriter(tf0, file1); err != nil { 94 | tf0.Close() 95 | tf1.Close() 96 | os.Remove(t0n) 97 | os.Remove(t1n) 98 | t.Fatal(err) 99 | } 100 | if err := util.PutWriter(tf1, file2); err != nil { 101 | tf0.Close() 102 | tf1.Close() 103 | os.Remove(t0n) 104 | os.Remove(t1n) 105 | t.Fatal(err) 106 | } 107 | tf0.Close() 108 | tf1.Close() 109 | tp, err := ioutil.TempFile(os.TempDir(), "") 110 | if err != nil { 111 | os.Remove(t0n) 112 | os.Remove(t1n) 113 | t.Fatal(err) 114 | } 115 | tpp := tp.Name() 116 | tp.Close() 117 | if err := File(t0n, t1n, tpp); err != nil { 118 | os.Remove(t0n) 119 | os.Remove(t1n) 120 | os.Remove(tpp) 121 | t.Fatal(err) 122 | } 123 | os.Remove(t0n) 124 | os.Remove(t1n) 125 | os.Remove(tpp) 126 | } 127 | -------------------------------------------------------------------------------- /pkg/bspatch/bspatch.go: -------------------------------------------------------------------------------- 1 | // * Copyright 2003-2005 Colin Percival 2 | // * All rights reserved 3 | // * 4 | // * Redistribution and use in source and binary forms, with or without 5 | // * modification, are permitted providing that the following conditions 6 | // * are met: 7 | // * 1. Redistributions of source code must retain the above copyright 8 | // * notice, this list of conditions and the following disclaimer. 9 | // * 2. Redistributions in binary form must reproduce the above copyright 10 | // * notice, this list of conditions and the following disclaimer in the 11 | // * documentation and/or other materials provided with the distribution. 12 | // * 13 | // * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 14 | // * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | // * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 17 | // * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | // * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 | // * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 | // * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 21 | // * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 22 | // * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | // * POSSIBILITY OF SUCH DAMAGE. 24 | 25 | // Package bspatch is a binary diff program using suffix sorting. 26 | package bspatch 27 | 28 | import ( 29 | "bytes" 30 | "fmt" 31 | "io" 32 | "io/ioutil" 33 | 34 | "github.com/dsnet/compress/bzip2" 35 | "github.com/gabstv/go-bsdiff/pkg/util" 36 | ) 37 | 38 | // Bytes applies a patch with the oldfile to create the newfile 39 | func Bytes(oldfile, patch []byte) (newfile []byte, err error) { 40 | return patchb(oldfile, patch) 41 | } 42 | 43 | // Reader applies a BSDIFF4 patch (using oldbin and patchf) to create the newbin 44 | func Reader(oldbin io.Reader, newbin io.Writer, patchf io.Reader) error { 45 | oldbs, err := ioutil.ReadAll(oldbin) 46 | if err != nil { 47 | return err 48 | } 49 | diffbytes, err := ioutil.ReadAll(patchf) 50 | if err != nil { 51 | return err 52 | } 53 | newbs, err := patchb(oldbs, diffbytes) 54 | if err != nil { 55 | return err 56 | } 57 | return util.PutWriter(newbin, newbs) 58 | } 59 | 60 | // File applies a BSDIFF4 patch (using oldfile and patchfile) to create the newfile 61 | func File(oldfile, newfile, patchfile string) error { 62 | oldbs, err := ioutil.ReadFile(oldfile) 63 | if err != nil { 64 | return fmt.Errorf("could not read oldfile '%v': %v", oldfile, err.Error()) 65 | } 66 | patchbs, err := ioutil.ReadFile(patchfile) 67 | if err != nil { 68 | return fmt.Errorf("could not read patchfile '%v': %v", patchfile, err.Error()) 69 | } 70 | newbytes, err := patchb(oldbs, patchbs) 71 | if err != nil { 72 | return fmt.Errorf("bspatch: %v", err.Error()) 73 | } 74 | if err := ioutil.WriteFile(newfile, newbytes, 0644); err != nil { 75 | return fmt.Errorf("could not create newfile '%v': %v", newfile, err.Error()) 76 | } 77 | return nil 78 | } 79 | 80 | func patchb(oldfile, patch []byte) ([]byte, error) { 81 | oldsize := len(oldfile) 82 | var newsize int 83 | header := make([]byte, 32) 84 | buf := make([]byte, 8) 85 | var lenread int 86 | var i int 87 | ctrl := make([]int, 3) 88 | 89 | f := bytes.NewReader(patch) 90 | 91 | // File format: 92 | // 0 8 "BSDIFF40" 93 | // 8 8 X 94 | // 16 8 Y 95 | // 24 8 sizeof(newfile) 96 | // 32 X bzip2(control block) 97 | // 32+X Y bzip2(diff block) 98 | // 32+X+Y ??? bzip2(extra block) 99 | // with control block a set of triples (x,y,z) meaning "add x bytes 100 | // from oldfile to x bytes from the diff block; copy y bytes from the 101 | // extra block; seek forwards in oldfile by z bytes". 102 | 103 | // Read header 104 | if n, err := f.Read(header); err != nil || n < 32 { 105 | if err != nil { 106 | return nil, fmt.Errorf("corrupt patch %v", err.Error()) 107 | } 108 | return nil, fmt.Errorf("corrupt patch (n %v < 32)", n) 109 | } 110 | // Check for appropriate magic 111 | if bytes.Compare(header[:8], []byte("BSDIFF40")) != 0 { 112 | return nil, fmt.Errorf("corrupt patch (header BSDIFF40)") 113 | } 114 | 115 | // Read lengths from header 116 | bzctrllen := offtin(header[8:]) 117 | bzdatalen := offtin(header[16:]) 118 | newsize = offtin(header[24:]) 119 | 120 | if bzctrllen < 0 || bzdatalen < 0 || newsize < 0 { 121 | return nil, fmt.Errorf("corrupt patch (bzctrllen %v bzdatalen %v newsize %v)", bzctrllen, bzdatalen, newsize) 122 | } 123 | 124 | // Close patch file and re-open it via libbzip2 at the right places 125 | f = nil 126 | cpf := bytes.NewReader(patch) 127 | if _, err := cpf.Seek(32, io.SeekStart); err != nil { 128 | return nil, err 129 | } 130 | cpfbz2, err := bzip2.NewReader(cpf, nil) 131 | if err != nil { 132 | return nil, err 133 | } 134 | dpf := bytes.NewReader(patch) 135 | if _, err := dpf.Seek(int64(32+bzctrllen), io.SeekStart); err != nil { 136 | return nil, err 137 | } 138 | dpfbz2, err := bzip2.NewReader(dpf, nil) 139 | if err != nil { 140 | return nil, err 141 | } 142 | epf := bytes.NewReader(patch) 143 | if _, err := epf.Seek(int64(32+bzctrllen+bzdatalen), io.SeekStart); err != nil { 144 | return nil, err 145 | } 146 | epfbz2, err := bzip2.NewReader(epf, nil) 147 | if err != nil { 148 | return nil, err 149 | } 150 | 151 | pnew := make([]byte, newsize) 152 | 153 | oldpos := 0 154 | newpos := 0 155 | 156 | for newpos < newsize { 157 | // Read control data 158 | for i = 0; i <= 2; i++ { 159 | lenread, err = zreadall(cpfbz2, buf, 8) 160 | if lenread != 8 || (err != nil && err != io.EOF) { 161 | e0 := "" 162 | if err != nil { 163 | e0 = err.Error() 164 | } 165 | return nil, fmt.Errorf("corrupt patch or bzstream ended: %s (read: %v/8)", e0, lenread) 166 | } 167 | ctrl[i] = offtin(buf) 168 | } 169 | // Sanity-check 170 | if newpos+ctrl[0] > newsize { 171 | return nil, fmt.Errorf("corrupt patch (sanity check)") 172 | } 173 | 174 | // Read diff string 175 | // lenread, err = dpfbz2.Read(pnew[newpos : newpos+ctrl[0]]) 176 | lenread, err = zreadall(dpfbz2, pnew[newpos:newpos+ctrl[0]], ctrl[0]) 177 | if lenread < ctrl[0] || (err != nil && err != io.EOF) { 178 | e0 := "" 179 | if err != nil { 180 | e0 = err.Error() 181 | } 182 | return nil, fmt.Errorf("corrupt patch or bzstream ended (2): %s", e0) 183 | } 184 | // Add pold data to diff string 185 | for i = 0; i < ctrl[0]; i++ { 186 | if oldpos+i >= 0 && oldpos+i < oldsize { 187 | pnew[newpos+i] += oldfile[oldpos+i] 188 | } 189 | } 190 | 191 | // Adjust pointers 192 | newpos += ctrl[0] 193 | oldpos += ctrl[0] 194 | 195 | // Sanity-check 196 | if newpos+ctrl[1] > newsize { 197 | return nil, fmt.Errorf("corrupt patch newpos+ctrl[1] newsize") 198 | } 199 | 200 | // Read extra string 201 | // epfbz2.Read was not reading all the requested bytes, probably an internal buffer limitation ? 202 | // it was encapsulated by zreadall to work around the issue 203 | lenread, err = zreadall(epfbz2, pnew[newpos:newpos+ctrl[1]], ctrl[1]) 204 | if lenread < ctrl[1] || (err != nil && err != io.EOF) { 205 | e0 := "" 206 | if err != nil { 207 | e0 = err.Error() 208 | } 209 | return nil, fmt.Errorf("corrupt patch or bzstream ended (3): %s", e0) 210 | } 211 | // Adjust pointers 212 | newpos += ctrl[1] 213 | oldpos += ctrl[2] 214 | } 215 | 216 | // Clean up the bzip2 reads 217 | if err = cpfbz2.Close(); err != nil { 218 | return nil, err 219 | } 220 | if err = dpfbz2.Close(); err != nil { 221 | return nil, err 222 | } 223 | if err = epfbz2.Close(); err != nil { 224 | return nil, err 225 | } 226 | cpfbz2 = nil 227 | dpfbz2 = nil 228 | epfbz2 = nil 229 | cpf = nil 230 | dpf = nil 231 | epf = nil 232 | 233 | return pnew, nil 234 | } 235 | 236 | // offtin reads an int64 (little endian) 237 | func offtin(buf []byte) int { 238 | 239 | y := int(buf[7] & 0x7f) 240 | y = y * 256 241 | y += int(buf[6]) 242 | y = y * 256 243 | y += int(buf[5]) 244 | y = y * 256 245 | y += int(buf[4]) 246 | y = y * 256 247 | y += int(buf[3]) 248 | y = y * 256 249 | y += int(buf[2]) 250 | y = y * 256 251 | y += int(buf[1]) 252 | y = y * 256 253 | y += int(buf[0]) 254 | 255 | if (buf[7] & 0x80) != 0 { 256 | y = -y 257 | } 258 | return y 259 | } 260 | 261 | func zreadall(r io.Reader, b []byte, expected int) (int, error) { 262 | var allread int 263 | var offset int 264 | for { 265 | nread, err := r.Read(b[offset:]) 266 | if nread == expected { 267 | return nread, err 268 | } 269 | if err != nil { 270 | return allread + nread, err 271 | } 272 | allread += nread 273 | if allread >= expected { 274 | return allread, nil 275 | } 276 | offset += nread 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /pkg/bspatch/bspatch_test.go: -------------------------------------------------------------------------------- 1 | package bspatch 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "testing" 10 | 11 | "github.com/gabstv/go-bsdiff/pkg/util" 12 | ) 13 | 14 | func TestPatch(t *testing.T) { 15 | oldfile := []byte{ 16 | 0x66, 0xFF, 0xD1, 0x55, 0x56, 0x10, 0x30, 0x00, 17 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD1, 18 | } 19 | newfilecomp := []byte{ 20 | 0x66, 0xFF, 0xD1, 0x55, 0x56, 0x10, 0x30, 0x00, 21 | 0x44, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 22 | 0xD1, 0xFF, 0xD1, 23 | } 24 | patchfile := []byte{ 25 | 0x42, 0x53, 0x44, 0x49, 0x46, 0x46, 0x34, 0x30, 26 | 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 27 | 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 28 | 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 29 | 0x42, 0x5A, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26, 30 | 0x53, 0x59, 0xDA, 0xE4, 0x46, 0xF2, 0x00, 0x00, 31 | 0x05, 0xC0, 0x00, 0x4A, 0x09, 0x20, 0x00, 0x22, 32 | 0x34, 0xD9, 0x06, 0x06, 0x4B, 0x21, 0xEE, 0x17, 33 | 0x72, 0x45, 0x38, 0x50, 0x90, 0xDA, 0xE4, 0x46, 34 | 0xF2, 0x42, 0x5A, 0x68, 0x39, 0x31, 0x41, 0x59, 35 | 0x26, 0x53, 0x59, 0x30, 0x88, 0x1C, 0x89, 0x00, 36 | 0x00, 0x02, 0xC4, 0x00, 0x44, 0x00, 0x06, 0x00, 37 | 0x20, 0x00, 0x21, 0x21, 0xA0, 0xC3, 0x1B, 0x03, 38 | 0x3C, 0x5D, 0xC9, 0x14, 0xE1, 0x42, 0x40, 0xC2, 39 | 0x20, 0x72, 0x24, 0x42, 0x5A, 0x68, 0x39, 0x31, 40 | 0x41, 0x59, 0x26, 0x53, 0x59, 0x65, 0x25, 0x30, 41 | 0x43, 0x00, 0x00, 0x00, 0x40, 0x02, 0xC0, 0x00, 42 | 0x20, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x22, 0x1F, 43 | 0xA4, 0x19, 0x82, 0x58, 0x5D, 0xC9, 0x14, 0xE1, 44 | 0x42, 0x41, 0x94, 0x94, 0xC1, 0x0C, 45 | } 46 | newfile, err := Bytes(oldfile, patchfile) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | if !bytes.Equal(newfile, newfilecomp) { 51 | t.Fatal("expected:", newfilecomp, "got:", newfile) 52 | } 53 | // test invalid patch 54 | _, err = Bytes(oldfile, oldfile) 55 | if err == nil { 56 | t.Fail() 57 | } 58 | } 59 | 60 | func TestOfftin(t *testing.T) { 61 | buf := make([]byte, 8) 62 | binary.LittleEndian.PutUint64(buf, 9001) 63 | n := offtin(buf) 64 | if n != 9001 { 65 | t.Fatal(n, "!=", 9001) 66 | } 67 | } 68 | 69 | func TestReader(t *testing.T) { 70 | oldfile := []byte{ 71 | 0x66, 0xFF, 0xD1, 0x55, 0x56, 0x10, 0x30, 0x00, 72 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD1, 73 | } 74 | patchfile := []byte{ 75 | 0x42, 0x53, 0x44, 0x49, 0x46, 0x46, 0x34, 0x30, 76 | 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 77 | 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 78 | 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 79 | 0x42, 0x5A, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26, 80 | 0x53, 0x59, 0xDA, 0xE4, 0x46, 0xF2, 0x00, 0x00, 81 | 0x05, 0xC0, 0x00, 0x4A, 0x09, 0x20, 0x00, 0x22, 82 | 0x34, 0xD9, 0x06, 0x06, 0x4B, 0x21, 0xEE, 0x17, 83 | 0x72, 0x45, 0x38, 0x50, 0x90, 0xDA, 0xE4, 0x46, 84 | 0xF2, 0x42, 0x5A, 0x68, 0x39, 0x31, 0x41, 0x59, 85 | 0x26, 0x53, 0x59, 0x30, 0x88, 0x1C, 0x89, 0x00, 86 | 0x00, 0x02, 0xC4, 0x00, 0x44, 0x00, 0x06, 0x00, 87 | 0x20, 0x00, 0x21, 0x21, 0xA0, 0xC3, 0x1B, 0x03, 88 | 0x3C, 0x5D, 0xC9, 0x14, 0xE1, 0x42, 0x40, 0xC2, 89 | 0x20, 0x72, 0x24, 0x42, 0x5A, 0x68, 0x39, 0x31, 90 | 0x41, 0x59, 0x26, 0x53, 0x59, 0x65, 0x25, 0x30, 91 | 0x43, 0x00, 0x00, 0x00, 0x40, 0x02, 0xC0, 0x00, 92 | 0x20, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x22, 0x1F, 93 | 0xA4, 0x19, 0x82, 0x58, 0x5D, 0xC9, 0x14, 0xE1, 94 | 0x42, 0x41, 0x94, 0x94, 0xC1, 0x0C, 95 | } 96 | oldrdr := bytes.NewReader(oldfile) 97 | prdr := bytes.NewReader(patchfile) 98 | newf := new(bytes.Buffer) 99 | if err := Reader(oldrdr, newf, prdr); err != nil { 100 | t.Fatal(err) 101 | } 102 | buf := make([]byte, 8) 103 | newf.Read(buf) 104 | if !bytes.Equal(buf, []byte{0x66, 0xFF, 0xD1, 0x55, 0x56, 0x10, 0x30, 0x00}) { 105 | t.Fatal(buf) 106 | } 107 | } 108 | 109 | func TestFile(t *testing.T) { 110 | oldfile := []byte{ 111 | 0x66, 0xFF, 0xD1, 0x55, 0x56, 0x10, 0x30, 0x00, 112 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD1, 113 | } 114 | patchfile := []byte{ 115 | 0x42, 0x53, 0x44, 0x49, 0x46, 0x46, 0x34, 0x30, 116 | 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 117 | 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 118 | 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 119 | 0x42, 0x5A, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26, 120 | 0x53, 0x59, 0xDA, 0xE4, 0x46, 0xF2, 0x00, 0x00, 121 | 0x05, 0xC0, 0x00, 0x4A, 0x09, 0x20, 0x00, 0x22, 122 | 0x34, 0xD9, 0x06, 0x06, 0x4B, 0x21, 0xEE, 0x17, 123 | 0x72, 0x45, 0x38, 0x50, 0x90, 0xDA, 0xE4, 0x46, 124 | 0xF2, 0x42, 0x5A, 0x68, 0x39, 0x31, 0x41, 0x59, 125 | 0x26, 0x53, 0x59, 0x30, 0x88, 0x1C, 0x89, 0x00, 126 | 0x00, 0x02, 0xC4, 0x00, 0x44, 0x00, 0x06, 0x00, 127 | 0x20, 0x00, 0x21, 0x21, 0xA0, 0xC3, 0x1B, 0x03, 128 | 0x3C, 0x5D, 0xC9, 0x14, 0xE1, 0x42, 0x40, 0xC2, 129 | 0x20, 0x72, 0x24, 0x42, 0x5A, 0x68, 0x39, 0x31, 130 | 0x41, 0x59, 0x26, 0x53, 0x59, 0x65, 0x25, 0x30, 131 | 0x43, 0x00, 0x00, 0x00, 0x40, 0x02, 0xC0, 0x00, 132 | 0x20, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x22, 0x1F, 133 | 0xA4, 0x19, 0x82, 0x58, 0x5D, 0xC9, 0x14, 0xE1, 134 | 0x42, 0x41, 0x94, 0x94, 0xC1, 0x0C, 135 | } 136 | tf0, err := ioutil.TempFile(os.TempDir(), "") 137 | if err != nil { 138 | t.Fatal(err) 139 | } 140 | t0n := tf0.Name() 141 | tf1, err := ioutil.TempFile(os.TempDir(), "") 142 | if err != nil { 143 | t.Fatal(err) 144 | } 145 | t1n := tf1.Name() 146 | if err := util.PutWriter(tf0, oldfile); err != nil { 147 | tf0.Close() 148 | tf1.Close() 149 | os.Remove(t0n) 150 | os.Remove(t1n) 151 | t.Fatal(err) 152 | } 153 | if err := util.PutWriter(tf1, patchfile); err != nil { 154 | tf0.Close() 155 | tf1.Close() 156 | os.Remove(t0n) 157 | os.Remove(t1n) 158 | t.Fatal(err) 159 | } 160 | tf0.Close() 161 | tf1.Close() 162 | tp, err := ioutil.TempFile(os.TempDir(), "") 163 | if err != nil { 164 | os.Remove(t0n) 165 | os.Remove(t1n) 166 | t.Fatal(err) 167 | } 168 | tpp := tp.Name() 169 | tp.Close() 170 | if err := File(t0n, tpp, t1n); err != nil { 171 | os.Remove(t0n) 172 | os.Remove(t1n) 173 | os.Remove(tpp) 174 | t.Fatal(err) 175 | } 176 | os.Remove(t0n) 177 | os.Remove(t1n) 178 | os.Remove(tpp) 179 | } 180 | 181 | func TestFileErr(t *testing.T) { 182 | // oldfile err 183 | if err := File("__nil__", "__nil__", "__nil__"); err == nil { 184 | t.Fail() 185 | } 186 | tfl, err := ioutil.TempFile("", "") 187 | if err != nil { 188 | t.Fail() 189 | } 190 | tfl.Write([]byte{10, 11, 12, 13, 14, 15, 16, 17}) 191 | fn := tfl.Name() 192 | tfl.Close() 193 | defer func() { 194 | os.Remove(fn) 195 | }() 196 | if err := File(fn, "__nil__", "__nil__"); err == nil { 197 | t.Fail() 198 | } 199 | if err := File(fn, fn, "__nil__"); err == nil { 200 | t.Fail() 201 | } 202 | if err := File(fn, fn, fn); err == nil { 203 | t.Fail() 204 | } 205 | } 206 | 207 | type corruptReader int 208 | 209 | func (r *corruptReader) Read(p []byte) (n int, err error) { 210 | return 0, fmt.Errorf("testing") 211 | } 212 | 213 | func TestReaderError(t *testing.T) { 214 | cr := corruptReader(0) 215 | b0 := bytes.NewReader([]byte{0, 0, 0, 0, 0, 1, 2, 3, 4, 5}) 216 | b1 := new(bytes.Buffer) 217 | if err := Reader(&cr, b1, b0); err == nil { 218 | t.Fail() 219 | } 220 | if err := Reader(b0, b1, &cr); err == nil { 221 | t.Fail() 222 | } 223 | } 224 | 225 | func TestCorruptHeader(t *testing.T) { 226 | corruptPatch := []byte{ 227 | 0x41, 0x53, 0x44, 0x49, 0x46, 0x46, 0x34, 0x30, 228 | 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 229 | 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 230 | 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 231 | } 232 | _, err := Bytes(corruptPatch, corruptPatch[:30]) 233 | if err == nil { 234 | t.Fatal("header should be corrupt") 235 | } 236 | if err.Error()[:13] != "corrupt patch" { 237 | t.Fatal("header should be corrupt (2)") 238 | } 239 | _, err = Bytes(corruptPatch, corruptPatch) 240 | if err == nil { 241 | t.Fatal("header should be corrupt (3)") 242 | } 243 | if err.Error() != "corrupt patch (header BSDIFF40)" { 244 | t.Fatal("header should be corrupt (4)") 245 | } 246 | corruptPatch[0] = 0x42 247 | corruptLen := []byte{100, 0, 0, 0, 0, 0, 0, 128} 248 | copy(corruptPatch[8:], corruptLen) 249 | _, err = Bytes(corruptPatch, corruptPatch) 250 | if err == nil { 251 | t.Fatal("header should be corrupt (5)") 252 | } 253 | if err.Error()[:15] != "corrupt patch (" { 254 | t.Fatal("header should be corrupt (6)") 255 | } 256 | } 257 | 258 | type lowcaprdr struct { 259 | read []byte 260 | n int 261 | } 262 | 263 | func (r *lowcaprdr) Read(b []byte) (int, error) { 264 | if len(b) > 8 { 265 | copy(r.read[r.n:], b[:8]) 266 | r.n += 8 267 | return 8, nil 268 | } 269 | copy(r.read[r.n:], b) 270 | r.n += len(b) 271 | return len(b), nil 272 | } 273 | 274 | func TestZReadAll(t *testing.T) { 275 | buf := []byte{ 276 | 0x10, 0x10, 0x10, 0x10, 0x20, 0x20, 0x20, 0x20, 277 | 0x30, 0x30, 0x30, 0x30, 0x40, 0x40, 0x40, 0x40, 278 | 0x43, 279 | } 280 | rr := &lowcaprdr{ 281 | read: make([]byte, 1024), 282 | } 283 | nr, err := zreadall(rr, buf, len(buf)) 284 | if err != nil { 285 | t.Fail() 286 | } 287 | if nr != len(buf) { 288 | t.Fail() 289 | } 290 | if buf[16] != rr.read[16] { 291 | t.Fail() 292 | } 293 | if buf[7] != rr.read[7] { 294 | t.Fail() 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /pkg/util/io.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "sync" 7 | ) 8 | 9 | const ( 10 | buffersize = 1024 * 16 11 | ) 12 | 13 | // PutWriter writes all content from b to target 14 | func PutWriter(target io.Writer, b []byte) error { 15 | lb := len(b) 16 | if lb < buffersize { 17 | n, err := target.Write(b) 18 | if err != nil { 19 | return err 20 | } 21 | if lb != n { 22 | return fmt.Errorf("%v of %v bytes written", n, lb) 23 | } 24 | return nil 25 | } 26 | offs := 0 27 | 28 | for offs < lb { 29 | n := Min(buffersize, lb-offs) 30 | n2, err := target.Write(b[offs : offs+n]) 31 | if err != nil { 32 | return err 33 | } 34 | if n2 != n { 35 | return fmt.Errorf("%v of %v bytes written", offs+n2, lb) 36 | } 37 | offs += n 38 | } 39 | return nil 40 | } 41 | 42 | // Min int 43 | func Min(a, b int) int { 44 | if a < b { 45 | return a 46 | } 47 | return b 48 | } 49 | 50 | // BufWriter is byte slice buffer that implements io.WriteSeeker 51 | type BufWriter struct { 52 | lock sync.Mutex 53 | buf []byte 54 | pos int 55 | } 56 | 57 | // Write the contents of p and return the bytes written 58 | func (m *BufWriter) Write(p []byte) (n int, err error) { 59 | m.lock.Lock() 60 | defer m.lock.Unlock() 61 | if m.buf == nil { 62 | m.buf = make([]byte, 0) 63 | m.pos = 0 64 | } 65 | minCap := m.pos + len(p) 66 | if minCap > cap(m.buf) { // Make sure buf has enough capacity: 67 | buf2 := make([]byte, len(m.buf), minCap+len(p)) // add some extra 68 | copy(buf2, m.buf) 69 | m.buf = buf2 70 | } 71 | if minCap > len(m.buf) { 72 | m.buf = m.buf[:minCap] 73 | } 74 | copy(m.buf[m.pos:], p) 75 | m.pos += len(p) 76 | return len(p), nil 77 | } 78 | 79 | // Seek to a position on the byte slice 80 | func (m *BufWriter) Seek(offset int64, whence int) (int64, error) { 81 | newPos, offs := 0, int(offset) 82 | switch whence { 83 | case io.SeekStart: 84 | newPos = offs 85 | case io.SeekCurrent: 86 | newPos = m.pos + offs 87 | case io.SeekEnd: 88 | newPos = len(m.buf) + offs 89 | } 90 | if newPos < 0 { 91 | return 0, fmt.Errorf("negative result pos") 92 | } 93 | m.pos = newPos 94 | return int64(newPos), nil 95 | } 96 | 97 | // Len returns the length of the internal byte slice 98 | func (m *BufWriter) Len() int { 99 | return len(m.buf) 100 | } 101 | 102 | // Bytes return a copy of the internal byte slice 103 | func (m *BufWriter) Bytes() []byte { 104 | b2 := make([]byte, len(m.buf)) 105 | copy(b2, m.buf) 106 | return b2 107 | } 108 | --------------------------------------------------------------------------------