├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── app └── ssdeep.go ├── distance.go ├── example_test.go ├── go.mod ├── go.sum ├── hash.go ├── hash_test.go ├── score.go ├── score_test.go ├── ssdeep.go ├── ssdeep_results.json └── ssdeep_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Set up Go 16 | uses: actions/setup-go@v3 17 | with: 18 | go-version: 1.17 19 | 20 | - name: Build 21 | run: go build -v ./... 22 | 23 | - name: Checks 24 | run: | 25 | go install github.com/alexkohler/prealloc@v1.0.0 26 | prealloc -set_exit_status ./... 27 | go install github.com/alexkohler/nakedret@b2c0146 28 | nakedret -set_exit_status ./... 29 | go install github.com/ashanbrown/makezero@v1.1.1 30 | makezero -set_exit_status ./... 31 | 32 | - name: Test 33 | run: go test -v ./... 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | ssdeep 27 | dist/ 28 | 29 | .idea 30 | .vscode 31 | pprof/ 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Lukas Rist 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 | 23 | 24 | BSD License 25 | 26 | Copyright (c) 2015, Arbo von Monkiewitsch All rights reserved. 27 | 28 | Redistribution and use in source and binary forms, with or without 29 | modification, are permitted provided that the following conditions are 30 | met: 31 | 32 | 1. Redistributions of source code must retain the above copyright 33 | notice, this list of conditions and the following disclaimer. 34 | 35 | 2. Redistributions in binary form must reproduce the above copyright 36 | notice, this list of conditions and the following disclaimer in the 37 | documentation and/or other materials provided with the distribution. 38 | 39 | 3. Neither the name of the copyright holder nor the names of its 40 | contributors may be used to endorse or promote products derived from 41 | this software without specific prior written permission. 42 | 43 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 44 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 45 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 46 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 47 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 48 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 49 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 50 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 51 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 52 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 53 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 54 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION := v0.4.0 2 | NAME := ssdeep 3 | BUILDSTRING := $(shell git log --pretty=format:'%h' -n 1) 4 | VERSIONSTRING := $(VERSION)+$(BUILDSTRING) 5 | BUILDDATE := $(shell date -u -Iseconds) 6 | OUTPUT = dist/$(NAME) 7 | LDFLAGS := "-X \"main.VERSION=$(VERSIONSTRING)\" -X \"main.BUILDDATE=$(BUILDDATE)\"" 8 | 9 | default: build 10 | 11 | build: $(OUTPUT) 12 | 13 | $(OUTPUT): app/ssdeep.go ssdeep.go score.go 14 | @mkdir -p dist/ 15 | go build -o $(OUTPUT) -ldflags=$(LDFLAGS) app/ssdeep.go 16 | 17 | .PHONY: clean 18 | clean: 19 | rm -rf dist/ 20 | rm -rf pprof/ 21 | rm -rf ssdeep.test 22 | rm -rf bench_current.test 23 | rm -rf bench_head.test 24 | 25 | .PHONY: tag 26 | tag: 27 | git tag $(VERSION) 28 | git push origin --tags 29 | 30 | .PHONY: build_release 31 | build_release: clean 32 | cd app; gox -arch="amd64" -os="windows darwin" -output="../dist/$(NAME)-{{.Arch}}-{{.OS}}" -ldflags=$(LDFLAGS) 33 | cd app; gox -arch="amd64 arm" -os="linux" -output="../dist/$(NAME)-{{.Arch}}-{{.OS}}" -ldflags=$(LDFLAGS) 34 | 35 | .PHONY: upx 36 | upx: 37 | cd dist; find . -type f -exec upx "{}" \; 38 | 39 | .PHONY: bench 40 | bench: 41 | go test -bench=. 42 | 43 | .PHONY: test 44 | test: 45 | go test . -v 46 | 47 | .PHONY: profile 48 | profile: 49 | @mkdir -p pprof/ 50 | go test -cpuprofile pprof/cpu.prof -memprofile pprof/mem.prof -bench . 51 | go tool pprof -pdf pprof/cpu.prof > pprof/cpu.pdf 52 | xdg-open pprof/cpu.pdf 53 | go tool pprof -weblist=.* pprof/cpu.prof 54 | 55 | .PHONY: benchcmp 56 | benchcmp: 57 | # ensure no govenor weirdness 58 | # sudo cpufreq-set -g performance 59 | go test -test.benchmem=true -run=NONE -bench=. ./... > bench_current.test 60 | git stash save "stashing for benchcmp" 61 | @go test -test.benchmem=true -run=NONE -bench=. ./... > bench_head.test 62 | git stash pop 63 | benchstat bench_head.test bench_current.test 64 | 65 | sample: 66 | if [ ! -f /tmp/data ]; then \ 67 | head -c 10M < /dev/urandom > /tmp/data; fi 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![example workflow](https://github.com/glaslos/ssdeep/actions/workflows/go.yml/badge.svg) 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/glaslos/ssdeep)](https://goreportcard.com/report/github.com/glaslos/ssdeep) 3 | [![Go Reference](https://pkg.go.dev/badge/github.com/glaslos/ssdeep.svg)](https://pkg.go.dev/github.com/glaslos/ssdeep) 4 | 5 | # SSDEEP 6 | 7 | Golang implementation based on the [paper](https://github.com/emintham/Papers/blob/6053eb1a180a511c127d12f7760c20202b1fcd3b/Kornblum-%20Identifying%20almost%20identical%20files%20using%20context%20triggered%20piecewise%20hashing.pdf) and [implementation](https://sourceforge.net/p/ssdeep/code/HEAD/tree/trunk/fuzzy.c) by Jesse Kornblum. 8 | 9 | See the [example](/app/ssdeep.go) in the app directory for the usage. 10 | 11 | ## Tools 12 | 13 | For CPU profiling: `apt install graphviz` 14 | 15 | For banchmark comparison `go install golang.org/x/perf/cmd/benchstat@latest` 16 | -------------------------------------------------------------------------------- /app/ssdeep.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/glaslos/ssdeep" 9 | ) 10 | 11 | var ( 12 | // VERSION is set by the makefile 13 | VERSION = "v0.0.0" 14 | // BUILDDATE is set by the makefile 15 | BUILDDATE = "" 16 | ) 17 | 18 | var ( 19 | forceHash bool 20 | showVersion bool 21 | ) 22 | 23 | func main() { 24 | fmt.Printf("ssdeep,%s--blocksize:hash:hash,filename\n", VERSION) 25 | 26 | flag.BoolVar(&forceHash, "force", false, "Force hash on error or invalid input length") 27 | flag.BoolVar(&showVersion, "version", false, "Print version") 28 | flag.Parse() 29 | 30 | if showVersion { 31 | fmt.Printf("%s %s\n", VERSION, BUILDDATE) 32 | return 33 | } 34 | ssdeep.Force = forceHash 35 | 36 | args := flag.Args() 37 | if len(args) < 1 { 38 | fmt.Println("Please provide a file path: ./ssdeep /tmp/file") 39 | os.Exit(1) 40 | } 41 | 42 | h1, err := ssdeep.FuzzyFilename(args[0]) 43 | if err != nil && !ssdeep.Force { 44 | fmt.Println(err) 45 | os.Exit(1) 46 | } 47 | 48 | if len(args) == 2 { 49 | var h2 string 50 | h2, err = ssdeep.FuzzyFilename(args[1]) 51 | if err != nil && !ssdeep.Force { 52 | fmt.Println(err) 53 | os.Exit(1) 54 | } 55 | 56 | var score int 57 | score, err = ssdeep.Distance(h1, h2) 58 | if score != 0 { 59 | fmt.Printf("%s matches %s (%d)\n", args[0], args[1], score) 60 | } else if err != nil { 61 | fmt.Println(err) 62 | } else { 63 | fmt.Println("The files doesn't match") 64 | } 65 | } else { 66 | if err != nil { 67 | fmt.Printf("%s,\"%s\"\n%s\n", h1, args[0], err) 68 | } else { 69 | fmt.Printf("%s,\"%s\"\n", h1, args[0]) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /distance.go: -------------------------------------------------------------------------------- 1 | package ssdeep 2 | 3 | // Copyright (c) 2015, Arbo von Monkiewitsch All rights reserved. 4 | // Copyright (c) 2017, Lukas Rist All rights reserved. 5 | // Use of this source code is governed by a BSD-style 6 | // license that can be found in the LICENSE file. 7 | 8 | func distance(str1, str2 string) int { 9 | var cost, lastdiag, olddiag int 10 | s1 := []rune(str1) 11 | s2 := []rune(str2) 12 | 13 | lenS1 := len(s1) 14 | lenS2 := len(s2) 15 | 16 | column := make([]int, lenS1+1) 17 | 18 | for y := 1; y <= lenS1; y++ { 19 | column[y] = y 20 | } 21 | 22 | for x := 1; x <= lenS2; x++ { 23 | column[0] = x 24 | lastdiag = x - 1 25 | for y := 1; y <= lenS1; y++ { 26 | olddiag = column[y] 27 | cost = 0 28 | if s1[y-1] != s2[x-1] { 29 | // Replace costs 2 in ssdeep 30 | cost = 2 31 | } 32 | column[y] = min( 33 | column[y]+1, 34 | column[y-1]+1, 35 | lastdiag+cost) 36 | lastdiag = olddiag 37 | } 38 | } 39 | return column[lenS1] 40 | } 41 | 42 | func min(a, b, c int) int { 43 | if a < b { 44 | if a < c { 45 | return a 46 | } 47 | } else { 48 | if b < c { 49 | return b 50 | } 51 | } 52 | return c 53 | } 54 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package ssdeep_test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "math/rand" 8 | "os" 9 | 10 | "github.com/glaslos/ssdeep" 11 | ) 12 | 13 | func ExampleFuzzyFile() { 14 | f, err := os.Open("file.txt") 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | defer f.Close() 19 | 20 | h, err := ssdeep.FuzzyFile(f) 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | fmt.Println(h) 25 | } 26 | 27 | func ExampleFuzzyBytes() { 28 | buffer := make([]byte, 4097) 29 | rand.Read(buffer) 30 | h, err := ssdeep.FuzzyBytes(buffer) 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | fmt.Println(h) 35 | } 36 | 37 | func ExampleFuzzyReader() { 38 | buffer := make([]byte, 4097) 39 | rand.Read(buffer) 40 | h, err := ssdeep.FuzzyReader(bytes.NewReader(buffer)) 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | fmt.Println(h) 45 | } 46 | 47 | func ExampleFuzzyFilename() { 48 | h, err := ssdeep.FuzzyFilename("file.txt") 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | fmt.Println(h) 53 | } 54 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/glaslos/ssdeep 2 | 3 | go 1.17 4 | 5 | require github.com/stretchr/testify v1.9.0 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | gopkg.in/yaml.v3 v3.0.1 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 5 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 6 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 7 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 8 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 9 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 10 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 11 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 12 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 13 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 14 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 15 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 16 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 17 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 18 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 19 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 20 | -------------------------------------------------------------------------------- /hash.go: -------------------------------------------------------------------------------- 1 | package ssdeep 2 | 3 | import "hash" 4 | 5 | var _ hash.Hash = &ssdeepState{} 6 | 7 | // New instance of the SSDEEP hash 8 | func New() *ssdeepState { 9 | s := newSSDEEPState() 10 | return &s 11 | } 12 | 13 | // Sum appends the current hash state to b. 14 | func (state *ssdeepState) Sum(b []byte) []byte { 15 | digest, _ := state.digest() 16 | return append(b, digest...) 17 | } 18 | 19 | // BlockSize returns the acceptable minimum amount of data 20 | func (state *ssdeepState) BlockSize() int { 21 | return blockMin 22 | } 23 | 24 | // Size of the hash to be returned 25 | func (state *ssdeepState) Size() int { 26 | return spamSumLength 27 | } 28 | 29 | // Reset the hash to initial state 30 | func (state *ssdeepState) Reset() { 31 | *state = newSSDEEPState() 32 | } 33 | -------------------------------------------------------------------------------- /hash_test.go: -------------------------------------------------------------------------------- 1 | package ssdeep 2 | 3 | import ( 4 | "hash" 5 | "io" 6 | "os" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestHashinterface(t *testing.T) { 13 | h := New() 14 | var _ hash.Hash = h 15 | t.Log(h.BlockSize()) 16 | h.Reset() 17 | 18 | fh, err := os.Open("ssdeep_results.json") 19 | require.NoError(t, err) 20 | b, err := io.ReadAll(fh) 21 | require.NoError(t, err) 22 | 23 | n, err := h.Write(b) 24 | require.NoError(t, err) 25 | 26 | t.Log(n) 27 | t.Log(h.Size()) 28 | 29 | hashResult := h.Sum(nil) 30 | 31 | expectedResult := "1536:74peLhFipssVfuInITTTZzMoW0379xy3u:VVFosEfudTj579k3u" 32 | require.Equal(t, expectedResult, string(hashResult)) 33 | 34 | t.Log(hashResult) 35 | t.Logf("%x", hashResult[:]) 36 | } 37 | 38 | func TestHashWrite(t *testing.T) { 39 | // hash using the hash.Hash interface methods 40 | fh, err := os.Open("ssdeep_results.json") 41 | require.NoError(t, err) 42 | b, err := io.ReadAll(fh) 43 | require.NoError(t, err) 44 | 45 | h1 := New() 46 | h1.Write([]byte("1234")) 47 | h1.Write(b) 48 | t.Logf("h1: %x", h1.Sum(nil)) 49 | 50 | // hash from read 51 | h2, err := FuzzyBytes(append([]byte("1234"), b...)) 52 | require.NoError(t, err) 53 | t.Logf("h2: %s", h2) 54 | 55 | // compare hashes 56 | diff := distance(string(h1.Sum(nil)), h2) 57 | if diff != 0 { 58 | t.Errorf("hashes differ by: %d", diff) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /score.go: -------------------------------------------------------------------------------- 1 | package ssdeep 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | var ( 12 | // ErrEmptyHash is returned when no hash string is provided for scoring. 13 | ErrEmptyHash = errors.New("empty string") 14 | 15 | // ErrInvalidFormat is returned when a hash string is malformed. 16 | ErrInvalidFormat = errors.New("invalid ssdeep format") 17 | ) 18 | 19 | // Distance computes the match score between two fuzzy hash signatures. 20 | // Returns a value from zero to 100 indicating the match score of the two signatures. 21 | // A match score of zero indicates the signatures did not match. 22 | // Returns an error when one of the inputs are not valid signatures. 23 | func Distance(hash1, hash2 string) (int, error) { 24 | var score int 25 | hash1BlockSize, hash1String1, hash1String2, err := splitSsdeep(hash1) 26 | if err != nil { 27 | return score, err 28 | } 29 | hash2BlockSize, hash2String1, hash2String2, err := splitSsdeep(hash2) 30 | if err != nil { 31 | return score, err 32 | } 33 | 34 | if hash1BlockSize == hash2BlockSize && hash1String1 == hash2String1 { 35 | return 100, nil 36 | } 37 | 38 | // We can only compare equal or *2 block sizes 39 | if hash1BlockSize != hash2BlockSize && hash1BlockSize != hash2BlockSize*2 && hash2BlockSize != hash1BlockSize*2 { 40 | return score, err 41 | } 42 | 43 | if hash1BlockSize == hash2BlockSize { 44 | d1 := scoreDistance(hash1String1, hash2String1, hash1BlockSize) 45 | d2 := scoreDistance(hash1String2, hash2String2, hash1BlockSize*2) 46 | score = int(math.Max(float64(d1), float64(d2))) 47 | } else if hash1BlockSize == hash2BlockSize*2 { 48 | score = scoreDistance(hash1String1, hash2String2, hash1BlockSize) 49 | } else { 50 | score = scoreDistance(hash1String2, hash2String1, hash2BlockSize) 51 | } 52 | return score, nil 53 | } 54 | 55 | func splitSsdeep(hash string) (int, string, string, error) { 56 | if hash == "" { 57 | return 0, "", "", ErrEmptyHash 58 | } 59 | 60 | parts := strings.Split(hash, ":") 61 | if len(parts) != 3 { 62 | return 0, "", "", ErrInvalidFormat 63 | } 64 | 65 | blockSize, err := strconv.Atoi(parts[0]) 66 | if err != nil { 67 | return blockSize, "", "", fmt.Errorf("%s: %w", ErrInvalidFormat.Error(), err) 68 | } 69 | 70 | return blockSize, parts[1], parts[2], nil 71 | } 72 | 73 | func scoreDistance(h1, h2 string, _ int) int { 74 | if !hasCommonSubstring(h1, h2) { 75 | return 0 76 | } 77 | d := distance(h1, h2) 78 | d = (d * spamSumLength) / (len(h1) + len(h2)) 79 | d = (100 * d) / spamSumLength 80 | d = 100 - d 81 | /* TODO: Figure out this black magic... 82 | matchSize := float64(blockSize) / float64(blockMin) * math.Min(float64(len(h1)), float64(len(h2))) 83 | if d > int(matchSize) { 84 | d = int(matchSize) 85 | } 86 | */ 87 | return d 88 | } 89 | func hasCommonSubstring(s1, s2 string) bool { 90 | i := 0 91 | j := 0 92 | s1Len := len(s1) 93 | s2Len := len(s2) 94 | hashes := make([]uint32, (spamSumLength - (rollingWindow - 1))) 95 | if s1Len < rollingWindow || s2Len < rollingWindow { 96 | return false 97 | } 98 | state := &rollingState{} 99 | for i = 0; i < rollingWindow-1; i++ { 100 | state.rollHash(s1[i]) 101 | } 102 | for i = rollingWindow - 1; i < s1Len; i++ { 103 | state.rollHash(s1[i]) 104 | hashes[i-(rollingWindow-1)] = state.rollSum() 105 | } 106 | s1Len -= (rollingWindow - 1) 107 | state.rollReset() 108 | for j = 0; j < rollingWindow-1; j++ { 109 | state.rollHash(s2[j]) 110 | } 111 | for j = 0; j < s2Len-(rollingWindow-1); j++ { 112 | state.rollHash(s2[j+(rollingWindow-1)]) 113 | var h = state.rollSum() 114 | for i = 0; i < s1Len; i++ { 115 | if hashes[i] == h && s1[i:i+rollingWindow] == s2[j:j+rollingWindow] { 116 | return true 117 | } 118 | } 119 | } 120 | return false 121 | } 122 | -------------------------------------------------------------------------------- /score_test.go: -------------------------------------------------------------------------------- 1 | package ssdeep 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | var h1 = "192:MUPMinqP6+wNQ7Q40L/iB3n2rIBrP0GZKF4jsef+0FVQLSwbLbj41iH8nFVYv980:x0CllivQiFmt" 10 | 11 | var h2 = "192:JkjRcePWsNVQza3ntZStn5VfsoXMhRD9+xJMinqF6+wNQ7Q40L/i737rPVt:JkjlQyIrx+kll2" 12 | 13 | var h3 = "196608:pDSC8olnoL1v/uawvbQD7XlZUFYzYyMb615NktYHF7dREN/JNnQrmhnUPI+/n2Yr:5DHoJXv7XOq7Mb2TwYHXREN/3QrmktPd" 14 | 15 | var h4 = "196608:7DSC8olnoL1v/uawvbQD7XlZUFYzYyMb615NktYHF7dREN/JNnQrmhnUPI+/n2Y7:3DHoJXv7XOq7Mb2TwYHXREN/3QrmktPt" 16 | 17 | var h5 = "24:YDVLfsT1ds/1H9Wpgq7n4XMijV6h4Z3QCw4qat:YD51H9CiMuV6uACwVat" 18 | 19 | var h6 = "24:YDVLfyvDj+C+opg8DV0Mdle6hPZ3QCw4qat:YDMvDj+C+kBOM+6HACwVat" 20 | 21 | func assertDistanceEqual(t *testing.T, expected, actual int) { 22 | if expected != actual { 23 | t.Fatalf("Distance mismatch: %d (expected)\n"+ 24 | " != %d (actual)", expected, actual) 25 | } 26 | } 27 | 28 | func TestHashDistanceSame(t *testing.T) { 29 | d, err := Distance(h1, h1) 30 | require.NoError(t, err) 31 | assertDistanceEqual(t, 100, d) 32 | } 33 | 34 | func TestHashDistance1(t *testing.T) { 35 | d, err := Distance(h1, h2) 36 | require.NoError(t, err) 37 | assertDistanceEqual(t, 35, d) 38 | } 39 | 40 | func TestHashDistance2(t *testing.T) { 41 | d, err := Distance(h3, h4) 42 | require.NoError(t, err) 43 | assertDistanceEqual(t, 97, d) 44 | } 45 | 46 | func TestHashDistance3(t *testing.T) { 47 | d, err := Distance(h5, h6) 48 | require.NoError(t, err) 49 | assertDistanceEqual(t, 54, d) 50 | } 51 | 52 | func TestEmptyHash1(t *testing.T) { 53 | d, err := Distance("", h2) 54 | require.Error(t, err) 55 | assertDistanceEqual(t, 0, d) 56 | } 57 | 58 | func TestEmptyHash2(t *testing.T) { 59 | d, err := Distance(h1, "") 60 | require.Error(t, err) 61 | if d != 0 { 62 | t.Errorf("hash2 is nil: %d", d) 63 | } 64 | } 65 | 66 | func TestInvalidHash1(t *testing.T) { 67 | d, err := Distance("192:asdasd", h1) 68 | require.Error(t, err) 69 | 70 | if d != 0 { 71 | t.Errorf("hash1 and hash2 are nil: %d", d) 72 | } 73 | } 74 | 75 | func TestInvalidHash2(t *testing.T) { 76 | d, err := Distance(h1, "asd:asdasd:aaaa") 77 | require.Error(t, err) 78 | if d != 0 { 79 | t.Errorf("hash1 and hash2 are nil: %d", d) 80 | } 81 | } 82 | 83 | func BenchmarkDistance(b *testing.B) { 84 | var h1 = `7DSC8olnoL1v/uawvbQD7XlZUFYzYyMb615NktYHF7dREN/JNnQrmhnUPI+/n2Y7` 85 | var h2 = `7DSC8olnoL1v/uawvbQD7XlZUFYzYyMb615NktYHF7dREN/JNnQrmhnUPI+/ngrr` 86 | for i := 0; i < b.N; i++ { 87 | distance(h1, h2) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /ssdeep.go: -------------------------------------------------------------------------------- 1 | package ssdeep 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "math" 9 | "os" 10 | ) 11 | 12 | var ( 13 | ErrFileTooSmall = errors.New("did not process files large enough to produce meaningful results") 14 | ErrBlockSizeTooSmall = errors.New("unable to establish a sufficient block size") 15 | ErrZeroBlockSize = errors.New("reached zero block size, unable to compute hash") 16 | ErrFileTooBig = errors.New("input file length exceeds max processable length") 17 | ) 18 | 19 | type Hash interface { 20 | io.Writer 21 | Sum(b []byte) []byte 22 | } 23 | 24 | const ( 25 | rollingWindow = 7 26 | blockMin = 3 27 | spamSumLength = 64 28 | minFileSize = 4096 29 | hashPrime = 0x93 30 | hashInit = 0x27 31 | b64String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" 32 | halfspamSumLength = spamSumLength / 2 33 | numBlockhashes = 31 34 | maxTotalSize = blockMin << (numBlockhashes - 1) * spamSumLength 35 | ) 36 | 37 | var ( 38 | b64 = []byte(b64String) 39 | // Force calculates the hash on invalid input 40 | Force = false 41 | ) 42 | 43 | type rollingState struct { 44 | window [rollingWindow + 1]byte 45 | h1 uint32 46 | h2 uint32 47 | h3 uint32 48 | n uint32 49 | } 50 | 51 | func (rs *rollingState) rollSum() uint32 { 52 | return rs.h1 + rs.h2 + rs.h3 53 | } 54 | func (rs *rollingState) rollReset() { 55 | rs.h1 = 0 56 | rs.h2 = 0 57 | rs.h3 = 0 58 | rs.n = 0 59 | for i := 0; i < len(rs.window); i++ { 60 | rs.window[i] = 0 61 | } 62 | } 63 | 64 | type ssdeepState struct { 65 | rollingState rollingState 66 | iStart, iEnd int 67 | totalSize uint64 68 | bsizeMask uint32 69 | blocks [numBlockhashes]blockHashState 70 | } 71 | 72 | type blockHashState struct { 73 | hashString []byte 74 | blockSize uint32 75 | blockHash1, blockHash2 byte 76 | tail1, tail2 byte 77 | } 78 | 79 | func newSSDEEPState() ssdeepState { 80 | s := ssdeepState{ 81 | iEnd: 1, 82 | } 83 | for i := range s.blocks { 84 | s.blocks[i].blockSize = blockMin << i 85 | s.blocks[i].blockHash1 = hashInit 86 | s.blocks[i].blockHash2 = hashInit 87 | } 88 | return s 89 | } 90 | 91 | // sumHash based on FNV hash 92 | func sumHash(c byte, h byte) byte { 93 | return ((h * hashPrime) ^ c) % 64 94 | } 95 | 96 | // rollHash based on Adler checksum 97 | func (rs *rollingState) rollHash(c byte) { 98 | rs.h2 -= rs.h1 99 | rs.h2 += rollingWindow * uint32(c) 100 | rs.h1 += uint32(c) 101 | rs.h1 -= uint32(rs.window[rs.n]) 102 | rs.window[rs.n] = c 103 | rs.n++ 104 | if rs.n == rollingWindow { 105 | rs.n = 0 106 | } 107 | rs.h3 = rs.h3 << 5 108 | rs.h3 ^= uint32(c) 109 | } 110 | 111 | func (state *ssdeepState) processByte(b byte) { 112 | for i := state.iStart; i < state.iEnd; i++ { 113 | state.blocks[i].blockHash1 = sumHash(b, state.blocks[i].blockHash1) 114 | state.blocks[i].blockHash2 = sumHash(b, state.blocks[i].blockHash2) 115 | } 116 | 117 | state.rollingState.rollHash(b) 118 | rh := state.rollingState.rollSum() 119 | if rh == math.MaxUint32 { 120 | return 121 | } 122 | // rh % 2**N > 0 will match all rh % (3 * 2**N) > 0 but can use fast bitmask 123 | if ((rh+1)/blockMin)&state.bsizeMask > 0 { 124 | return 125 | } 126 | if (rh+1)%blockMin > 0 { 127 | return 128 | } 129 | 130 | for i := state.iStart; i < state.iEnd; i++ { 131 | block := &state.blocks[i] 132 | if rh%block.blockSize == (block.blockSize - 1) { 133 | if len(block.hashString) == 0 { 134 | old := &state.blocks[state.iEnd-1] 135 | if state.iEnd <= numBlockhashes-1 { 136 | newb := &state.blocks[state.iEnd] 137 | newb.blockHash1 = old.blockHash1 138 | newb.blockHash2 = old.blockHash2 139 | state.iEnd++ 140 | } 141 | } 142 | block.tail1 = block.blockHash1 143 | block.tail2 = block.blockHash2 144 | if len(block.hashString) < spamSumLength-1 { 145 | block.hashString = append(block.hashString, block.tail1) 146 | block.tail1 = 0 147 | block.blockHash1 = hashInit 148 | if len(block.hashString) < halfspamSumLength { 149 | block.blockHash2 = hashInit 150 | block.tail2 = 0 151 | } 152 | } else if state.isStartBlockFull() { 153 | state.iStart++ 154 | state.bsizeMask = (state.bsizeMask << 1) + 1 155 | } 156 | } 157 | } 158 | } 159 | 160 | func (state *ssdeepState) isStartBlockFull() bool { 161 | return state.totalSize > uint64(state.blocks[state.iStart].blockSize*spamSumLength) && 162 | len(state.blocks[state.iStart+1].hashString) >= halfspamSumLength 163 | } 164 | 165 | // Reader is the minimum interface that ssdeep needs in order to calculate the fuzzy hash. 166 | // Reader groups io.Seeker and io.Reader. 167 | type Reader interface { 168 | io.Seeker 169 | io.Reader 170 | } 171 | 172 | func (state *ssdeepState) Write(r []byte) (n int, err error) { 173 | state.totalSize += uint64(len(r)) 174 | for _, b := range r { 175 | state.processByte(b) 176 | } 177 | return len(r), nil 178 | } 179 | 180 | // FuzzyReader computes the fuzzy hash of a Reader interface with a given input size. 181 | // It is the caller's responsibility to append the filename, if any, to result after computation. 182 | // Returns an error when ssdeep could not be computed on the Reader. 183 | func FuzzyReader(f io.Reader) (string, error) { 184 | state := newSSDEEPState() 185 | if _, err := io.Copy(&state, f); err != nil { 186 | return "", err 187 | } 188 | digest, err := state.digest() 189 | return digest, err 190 | } 191 | 192 | func (state *ssdeepState) digest() (string, error) { 193 | if !Force && state.totalSize <= minFileSize { 194 | return "", ErrFileTooSmall 195 | } 196 | if state.totalSize > maxTotalSize { 197 | return "", ErrFileTooBig 198 | } 199 | 200 | var i = state.iStart 201 | for ; uint64(uint32(blockMin)<= state.iEnd { 205 | i = state.iEnd - 1 206 | } 207 | for i > state.iStart && len(state.blocks[i].hashString) < halfspamSumLength { 208 | i-- 209 | } 210 | var bl1 = state.blocks[i] 211 | var bl2 = state.blocks[i+1] 212 | if i >= state.iEnd-1 { 213 | bl2 = state.blocks[i] 214 | bl2.hashString = append([]byte{}, bl1.hashString...) 215 | } 216 | var rh = state.rollingState.rollSum() 217 | 218 | if len(bl2.hashString) > halfspamSumLength-1 { 219 | bl2.hashString = bl2.hashString[:halfspamSumLength-1] 220 | } 221 | 222 | if rh != 0 { 223 | bl1.hashString = append(bl1.hashString, bl1.blockHash1) 224 | bl2.hashString = append(bl2.hashString, bl2.blockHash2) 225 | } else { 226 | if len(bl1.hashString) == spamSumLength-1 && bl1.tail1 != 0 { 227 | bl1.hashString = append(bl1.hashString, bl1.tail1) 228 | } 229 | if bl2.tail2 != 0 { 230 | bl2.hashString = append(bl2.hashString, bl2.tail2) 231 | } 232 | } 233 | for i := range bl1.hashString { 234 | bl1.hashString[i] = b64[bl1.hashString[i]] 235 | } 236 | for i := range bl2.hashString { 237 | bl2.hashString[i] = b64[bl2.hashString[i]] 238 | } 239 | 240 | return fmt.Sprintf("%d:%s:%s", bl1.blockSize, bl1.hashString, bl2.hashString), nil 241 | } 242 | 243 | // FuzzyFilename computes the fuzzy hash of a file. 244 | // FuzzyFilename will opens, reads, and hashes the contents of the file 'filename'. 245 | // It is the caller's responsibility to append the filename to the result after computation. 246 | // Returns an error when the file doesn't exist or ssdeep could not be computed on the file. 247 | func FuzzyFilename(filename string) (string, error) { 248 | f, err := os.Open(filename) 249 | if err != nil { 250 | return "", err 251 | } 252 | defer f.Close() 253 | 254 | return FuzzyFile(f) 255 | } 256 | 257 | // FuzzyFile computes the fuzzy hash of a file using os.File pointer. 258 | // FuzzyFile will computes the fuzzy hash of the contents of the open file, starting at the beginning of the file. 259 | // When finished, the file pointer is returned to its original position. 260 | // If an error occurs, the file pointer's value is undefined. 261 | // It is the callers's responsibility to append the filename to the result after computation. 262 | // Returns an error when ssdeep could not be computed on the file. 263 | func FuzzyFile(f *os.File) (string, error) { 264 | currentPosition, err := f.Seek(0, io.SeekCurrent) 265 | if err != nil { 266 | return "", err 267 | } 268 | if _, err = f.Seek(0, io.SeekStart); err != nil { 269 | return "", err 270 | } 271 | out, err := FuzzyReader(f) 272 | if err != nil { 273 | return out, err 274 | } 275 | _, err = f.Seek(currentPosition, io.SeekStart) 276 | return out, err 277 | } 278 | 279 | // FuzzyBytes computes the fuzzy hash of a slice of byte. 280 | // It is the caller's responsibility to append the filename, if any, to result after computation. 281 | // Returns an error when ssdeep could not be computed on the buffer. 282 | func FuzzyBytes(buffer []byte) (string, error) { 283 | br := bytes.NewReader(buffer) 284 | 285 | result, err := FuzzyReader(br) 286 | if err != nil { 287 | return "", err 288 | } 289 | 290 | return result, nil 291 | } 292 | -------------------------------------------------------------------------------- /ssdeep_results.json: -------------------------------------------------------------------------------- 1 | { 2 | "10039296": "196608:WfdY0HpZ9PegaQ8ngCzFPXIWjKzwpH5+yGm4NLhc5froidG7eYOm/v:WfbJZ92gy4WuzWZ+yGJLeroIYN3", 3 | "10080256": "196608:iW2Ol+pbe5D4qS4HJw9nsu0r351xSwZNRZu9/m4SYGu6:iPc+M0gpynyTBraRmqGu6", 4 | "10121216": "196608:AivJGRTnPZffP/qF4qRCldsAaHOw4NZZVvo/t7h02hU+wmWRP5nJQY:AioRTnRffP/qQnZdLVvMS+wmShKY", 5 | "10162176": "196608:Bjx9lKK2y7+Xc4r/k5bOygOeSZHBqxeP5+ZvJ/BFq4P7ptpEoSjQWv:f9lK8+vr/kJVgO3ZhqxE+ZvjFqwlfE9v", 6 | "10203136": "196608:DEhuoj4ZB8/mzumfuA7EAN7+8Z2OLCFvkILiQOQ8wFUKYpLNhfok:IgojF/mz52EnN7NZ2OUkILivy6xH", 7 | "10244096": "196608:LaG6Jp5VjRUPcVFXOtkcpe+rk5fyErN8ChXTRc61R:mH/OtI+r4fHNbXlc63", 8 | "1028096": "24576:qT76nF87MgyEabDTU2p5GlSnlFRt+yUiZZ5qOaH:46nF82EagW5zvRt7UiL0H", 9 | "10285056": "196608:7YFQZwew4vOxBhSA8uwfs3jmMJQqXAB8rbszm2xTXk1AP4udJnFbEjY:U2qeTgz8tfs3jhWqXyyszmC14udXgY", 10 | "10326016": "196608:C5r+A9sL6/VfLzwYSNR9RZbBfBSaf0ZMzmdTOpFPpOsxUXHPFXtA:C5qAsLGzcBRFBfd06zmdc0siXv5tA", 11 | "10366976": "196608:U0iYFAtXEEwQmmobk8Sq3VU9QIwEo4Jr2WgOos50mY8VQEdVrwHMV:kfil4zo8QZX0TvP5OEDrwsV", 12 | "10407936": "196608:VCy+WIfrI1o4zLObSPlfiu1HP9vLvdnhr8//LpBiuacnzKT:VCy+6q4vObSPhx1HFvDXA/Wuacnze", 13 | "10448896": "196608:7zfjQwpXIVkBAzsZ0NkwKQ2PyCnN8pu4OJIkxwRUeAtMtm2/kcWs+uC:7zfZp4aSzPK6OqsJ1x6Ue9tm2/k/kC", 14 | "10489856": "196608:JLgxGUTP+aE3KbSSgHc7IaRJvs8KQjaPxFGE3bw1eji90RmKl61rYHlOxUJ:SxGUaK3gH8C8KQjoPZrE/qkKlerYHlOw", 15 | "10530816": "196608:hl0wqts4tXfvEccriSWu1K7tRQ/UcAvAhArDU8x4MyFoSO6ymcPr2PmO:h2PttPvE7pk7DAYDU8uXF2Dr2uO", 16 | "10571776": "196608:tmDhmjMNwCvRtZnQKD4vDwK2toLNa3WJCLcbC0IeBBzCJLQLj:QQMGGrZwvDzamJCLcbCpSm2j", 17 | "10612736": "196608:6J7aV7Ppt7Of350rPcPf5y8Y+YxGimtj/WA/+Hd9/:Wa9PptIeYn5ZYpB+81", 18 | "10653696": "196608:DppHoHyCNEmvazVhcdaC9ci2YbCwNOnz/JJXkGoAXna:DjH8NfvazsdDl27wNgz/zoA3a", 19 | "1069056": "24576:MNbF/6HNjS9gOCm1+pI2HZKN1riTkJmgDGGKzDQneDp:6/x9gOR1+pI+OrHJfD1TK", 20 | "10694656": "196608:EIk+209Ywru5DRQ3yAVodeKnueD2N4xtFJZUQ6SzoqLSbkxErOYipBlXW:9XTvmDmCIopu2Jn6S+bk+rO3pTG", 21 | "10735616": "196608:QCql/91UH2g4BrgME3drlDnfGRLnjtPI0ioYzDrlCqhUX+Vc/X:NqlvDg4BUModrVeNV0l0d", 22 | "10776576": "196608:bg0Z2PlUtskMmZqd9BzhYS0VdVVsOPbkTZwdkUnHvEsXQaemz:fZ2PluMFrBNYt2OmZWkkEAQT6", 23 | "10817536": "196608:LHBwc8+ek1D1OlGLZ0BtLGWPf2mMFn0rfQsGcAM5BdqEsM:iP+FD1cGuLG6f2mMFn072cAM9qEN", 24 | "10858496": "196608:C1Gs2mIrP4eyU9fK5sQbgWh8+EtIVDdaBE0hGE1Ir9GE7xGb3:C1G5HRHKKUh8XIVDoBdoxGr", 25 | "10899456": "196608:eLFe9EtW5MDlC9wtE7vemPU+GROeEeLxbHrOntBBBbHM7tRIX3cKGC5:SxQ6lCOKS+eBEAZOHALIXMKv", 26 | "10940416": "196608:5+ju6HsagI0G0nrD5wIe8KT2VSItXPAYBv8g0++ByCWhMxpcFJMi28M07Kde8NK:0URPrhKwXRp0+ABQJI8MtYH", 27 | "10981376": "196608:gdgczFOJ9eIln9mc/jcQjNBMBmoYwd/OP0WaLlm88gQz:Yg8A/lnvjb/MrOPuGRz", 28 | "11022336": "196608:btwDjpoOwuB3RnabpvbLX4Y/tjWdl+Hquw2v1uYWHvu0+WSB:uD1oOwsJyFbLXlFiAd3uu0GB", 29 | "11063296": "196608:TNLSn7zrcuvPuW4V6xj8i8SjFHT/sbuzrbW455ZLw5IuL:TNLS/rcuvP2Wp5T8uzrbW0ZLHuL", 30 | "1110016": "24576:Ti0xVanCkecA8sWkRcTTEfi7GmT0lOPQ6P4CdeCVMbKZ0ecqM:TBVU0+sWVTw3efPD4CZuq3M", 31 | "11104256": "196608:QUigvoxh5oPw41iLV8gEoLIgyWo/oUcuDgcd+fK9dKtvRd70rPY8XTzOZoY:foSPPKV5EoLWW49crjf0dKtpd7qPD3Ob", 32 | "11145216": "196608:ufe171ocW5cX4Wi/vklNN1QCfDkF4/PImlcXO2fciDJcwzl5fkuKVlIAxXRO3x:ken5UvqbThYmqNJDJc85oIAWB", 33 | "11186176": "196608:vYHVJYXwj7hsnFY1ltqhRrMXJDIUsvggCe2js14siDpHXws+r/NkdKqd5:g1xNsOlwhRrMZUPgbe2yCpHgs4/3y5", 34 | "11227136": "196608:sx97ngpe8SlYpYzoYAstfeqALi/2zDbKqstTyeduoYiLRr5J6BW9dkS6yD9:67gRyJN8iOzDVstTycuoxjNlD9", 35 | "11268096": "196608:pBpvRKgrd3+VncLJSVgT4EJgiqHYUQqXYX3qor34QSQVc3Y7Sm7pTyN01H:pB2Mp+YJPT0iQYUQ9aor34QSNYjRd", 36 | "11309056": "196608:ZsUJRfx3mgdhSx43vKycqcXpx+WPcSXRgS4mwPPff/B299qxy9Bzq7Ps8JVER:NJDvdMCzbcXTxDX/a//E9qxKBzq48JGR", 37 | "11350016": "196608:+zVM3C8iw9nMuLW9xvOk7GGBDGPrad2ljsKjy6s4/iywwUlwIPBdlAEK:+cNi9NOY5B2aMCKjyj4/qtPnlW", 38 | "11390976": "196608:INIvkj/5lbSpMqYXzMvnX8jJ5nKNdnN8qg1iyBWAN1r8TzleMq8O:WmkHSeqvf8jJ5KfNLg1JWqR8TzA18O", 39 | "11431936": "196608:1Z87N63eqht3QPXayZPxNJ9DX03IvMSFESpXNGdJbm0jwkdmyVsoZ/ZEZQNtuUaR:1Z6NA4KyFxNJpE3IvREydrA9ZftubBj", 40 | "11472896": "196608:kNtSmMZ7xlO6/Nl3OpG5I5ubzSyV5rOPIIszBBw/cIAUy7S7siKq+/hayBUx69d8:gtSXhOWl2G5oubzSyV5wIIszB2cHU6WF", 41 | "1150976": "24576:CNP0r7Da2QFt+qFqVFt8RUtkvcWGOqhtuYjW+D5FzwoC9n:CNQDQFwq0x8K6eOQuv45yoC9n", 42 | "11513856": "196608:Xtm9BKfPmOS6eAd1HtQGl2Doml7JxTr9IAPeyAtUjH5WiElVs5tpzjEcAJ75Rn:XA943Z7rd3QGE8WJx39ImJACjgiI65tG", 43 | "11554816": "196608:9MINZHHxlLw3JlY+gWPHFXhMlrVWmdf5wZa6Sxqy0g0GvoDkn2v9BlBBEdc+FM:97HjOJVgqHFXhE0+f5pTn0JHvtvR+S", 44 | "11595776": "196608:N4nbL9TvjhcQBo5j0nrygJKfOv3gdygNwt10MpHQndugK76DqlZ7xHu:NOcQB5Qfpw0pdJO6DqlFI", 45 | "11636736": "196608:N18DprcKM5v2C8nRNnVgSN1rnqsNPpLtqTVpBrrTA+jhI:NqppM05XZN1rqSpLeBr72", 46 | "11677696": "196608:F2h9CMFblDKkKzoGIQOFui/F+pW9P6GG88++7pJcBelqLVruG/PSoqYzvqEs0ioE:FFMFbRKkKEGIQOFR/F+YFGZETdIYzvK", 47 | "11718656": "196608:+uxHE9WOKZoBOIG79hzIDTc+vxivo1pTQGlzCBnouAH65Q7jWsYpp8:p5OKOBOn9hVkxivoJAdAa5Q7hYpO", 48 | "11759616": "196608:tl9XqNL0dIV98H94FkpS9p+OTLxq3nmw9J+NqLk/7wQ062mAKakuq5FJCnsf6/Y1:tlW0238H94FDBFTw33LkEjmAejJCFY1v", 49 | "11800576": "196608:N3MmgKSP9XcppVPbD8jgHq7GTuzpAZMtTERBelB3JkbqkaNfWtdfZUOl7bosMaI:N8m+cppVjDEIqCuzpAmt4RG5JkbqJfWC", 50 | "11841536": "196608:Yv6YoWGJrDQLSBLk9L0rEVBMarjYLtxopSeWnrZopYHoTBT2kepO96q6ldZulVrq:n8iw2BLk+rEbjYLt+ENoFTBTIpAblV3+", 51 | "11882496": "196608:XYwjrw9pY4TI9z3bYgoXlk8/GmS49fTFzkUSXio4MrUsEkMAqEKTKAY3NS:XYycHYXnYgYlCu9LFFSXixW7dqx7Y3U", 52 | "1191936": "24576:HV9r5pxpkIPXZdmuD7fxS3Xg0qrV9Tr05Lmw76uzPTXBN/NMUBFGesozZk3V2:Hv5zXXiuDzxS3wFrPr05Lmw76+LL1TBh", 53 | "11923456": "196608:gFZ/VZPlKJ6J9ouaUYomngM/8+f3YCKJGo4lrtiAJ2UkepZpEjnYvrptb:gFpd2dV8Sn6ylrtiAJRF6jngVtb", 54 | "11964416": "196608:/beoBaBZhb3FTbbJoXWFdHJBNc+alNNArhAZ+Cg+M5saLfBj3H+FlkAVr54ZUfTN:jeoQ3dbbJHRalNNArha+Cg+6saLfBTeB", 55 | "12005376": "196608:hG/8nERI9VIBiBg0zJxJuvCTcH0UIX4Q6Cmz2yG0YQYWV0eOtsffECT98:M8nd9VvB/JaC80f6CmqTQ5VPOtgT98", 56 | "12046336": "196608:VgdlhAWGTaTtKjIkScDreBBVpCpKdApYIlbpNl1fU1QO4q4v9aPZobR1d:VgXDkScDrGxEpH51fU6q4FaPZCvd", 57 | "12087296": "196608:bZiZm4ITnBLl6XZfHP/4h/b1JMh+pTG7ruM8mzp0dLJeUSEpg:bZGsnT6pfHo5BU+pTGnuM86UJFpg", 58 | "12128256": "196608:/pkKSeH9x7WYCkGIFkkMrxwMCPAFwfAgDWU+efOR6q9ukmOrWenTMrfyuD9v/ifA:/aKPdhWWbMrxxMACNfK3rW8WquD9v/i4", 59 | "12169216": "196608:N8f6E5uOK/MrZdohgtN4dpHQgtEZ9F2Qb2UHGRyR4rZNNIZWkvH9Xm7UqNhSceK:NDVOOMrTU/QtZL2XKKZNuHff0hTeK", 60 | "12210176": "196608:OqlyJ3eGVQ4+030ei/4ypKxAN3u/gPEFH8zodlpS22rIkYazo1QddxlQLR/T8GJI:OqlaQ703+wypAAN3cgPqBdTSXckYJ2YU", 61 | "12251136": "196608:PnDCu5tJu9/W/kW4yXkKOLlOIdoAfOS6+EhdHWGG+3in3c2v/K1FGZZkUxa:PDCCa/WcHx1Bd6+Ehd27n3y1FGZ0", 62 | "12292096": "196608:lcBcNXoCqZSyhxSxNVE7DcISLmaDBIqTdmL/VdJtCbmeYJzQ7QC2WzBSrY:QcohxSpE7Dx2fTMJbxJM7QC7zeY", 63 | "1232896": "24576:TsmGACX5kfNEVzJ2J2IMdekhgRg0OmtnzcGkMPBqqHftr:IDr54EzzVdl0Ltz9kGqqHftr", 64 | "12333056": "196608:z+tvTwr83xso2w7unquQzE+S3zGEOs2cslX93fBAYTt3AEA9:zaJ3qxwB5iN2csHKYTWEA9", 65 | "12374016": "196608:e+J5d+H68d8B7GujDWojxD9h9Bzsk5tXVriarovwsb0dymq5iOvAzQrCqR9uVxu8:dJv+HG7lnWuGk5tXVriarovwsb0UP5iH", 66 | "12414976": "196608:WaiDjxKCjvdbWT+OcZQ6QXCZnDMdCHfls0lIKOY4QzLdN80a+IRN:WRxZjpC+BK6QeMdUflfZOY4QzT9a+IRN", 67 | "12455936": "196608:0/IFEHwzUyRyWMDZ4rhuTB+qkcYVZowCzfSeCyBZqikMJKhn6hpOd/CnzfL7:0nHwzUyRyberhuNbYVZXgJfaMJRbOd/q", 68 | "12496896": "196608:mUDlxUsBfP4Ea4/SCNNOXru2xhTha8LNqacBw27qsDoayTmieDoD7T:mwxUsB3RVVNB2HThL4acBw27RfNiYoL", 69 | "12537856": "196608:gk7xuhs44POqRENvCQfUqfaCnAifSpSCv2F7KOD/La1pWfUonWDp1FADE/war/mx:gk7YhHeOtjfU8p+n2F2OS1p+Wp1iUwsM", 70 | "12578816": "196608:+M4bwidM3uPCF01quBBcX6HHBPdVKBnllv0H5ABVWFvqpCkiD4fZOwMubDx7JaNZ:Oq+PCaffcXY+nllvmuBgFvqckiDKAwMx", 71 | "12619776": "393216:b/8NSGaqpAkxRhDrIpF7ArPv4ipv/9Hx7H6HQU:bENSqpAkxRhDrecPvnp3dx7H6wU", 72 | "12660736": "393216:pWPqvGMCMHYGqB+QbbUDjQk3Y6lcbVDRNgD98l:pkKE+kUDjQ76qhVSD98l", 73 | "126976": "3072:pwP2ZmVLsvDAyshOZIzFkGxIE++3ysSsZCj3JwAjpn:ps2/DAyKIaRyE++RSsUj3JwaJ", 74 | "12701696": "196608:buNsxPfYAodm4XKIGz5RJH2YCb4jHdXks1hVUm3nDG9DRsOKMVm12QHzGA/mj08p:6Ns1AOICnbdrdXjhVUinS9KOwRzlAp", 75 | "1273856": "24576:EL03aUeBBeP7WL85KwHrGbdJAPF8SMZMfHZI5PY2CW3BMu+pUUpwlmdY:YJPBBWWL8YwLGbdEezM/ZI5drGUH", 76 | "12742656": "196608:5Di7Fz/hTuINRnlKj7Z0veWT7HAWWR4QV0JOAMqEqQGV3BElORoGvPf4zYSBKMt:2FzJJRnUSTHHAWVQAIKVa73zzsi", 77 | "12783616": "196608:HMgE0VO0dNbbMRJVEoVEaHM7wyfhL9V17UzeBmPKZJEI7bTq1eriY4Hs:Hn1s0dNERJZl6Z6FPK5Tqori7Hs", 78 | "12824576": "393216:qCvRPoR6i/DyOWXscyND0/lgwLptRWEv0R5FDIP:PvJoTVcyB0/ZLptRWHRnDIP", 79 | "12865536": "393216:z1B2LTeQs9C/ZosH7G6nXlPjknhBjlDuqWtY:+LTjsgesbVkn3RuqWC", 80 | "12906496": "393216:qUjRjsfNXvQsnxWZFbHB2OrY96TUpgwzmBadn:GVXBuFDYd93GV8dn", 81 | "12947456": "196608:6vPotPVMC92cCvDPN5uv+O8JETBw9EppaGgnKddzjyybpbGDk53Fmg7SRfxaY8eR:6vU192nD7umMTcN2vrcms1Qek+", 82 | "12988416": "196608:Gnau+YkQAXVxZ2ixCLMm+9AXPhlrllxfO3CWX1nHE9nlC9u67V6mLkabtBU:GnHCVX2ixk7+kpDfOv1nHEsrtkCHU", 83 | "13029376": "196608:1aIU7a4mkRNQVIZ36tVdYemGr1ZFQiHkNN2dNsFuqZfy9EMWw8tMAy7rgdxB4REZ:QICMuQCQ7R0iHNsN1/ltMAy7EZ4hFih", 84 | "13070336": "196608:Av/Sh3Zw+0SZUOYAnzBmaRdwNxDSyD588axHHQl6eIgjlszgiqvZm:Av/wS+0SpYAzlnwNRr8oXOU/c", 85 | "13111296": "196608:n7hjuK+s4klglQM0DN7zDal7hvxw3lfECUfOEkymJsF+aNT9+JPjvJYeQ/ah6:n7hjSlQMCDohZwmCaOam4NgJPjveeQJ", 86 | "1314816": "24576:s7AM90K2ETR5xqZ+CZ0iCmACIC+8WsHeXCBAf209bvUCf:sff2m56+LLCIC2Yr0hff", 87 | "13152256": "196608:2PnTDLIyPcbtBmVk3gFzdBl3rzAx95nnvTYU5STLpuI1CG5XACO0v48F0Ks7J:2P0yP0Mk3sdL3A3dgXAIkiwCO0v/WJ", 88 | "13193216": "393216:QROi9V/dI/Y944JtZBxxizZpMlWOqRJMoiZTEqjs:QEGdew9XtPSzZRqHEqY", 89 | "13234176": "393216:6NESW8WKTxtxQxITiXsKX7V2wV0pixRc5MCUS0f:6+xKTxPi+duxWAxW2f", 90 | "13275136": "196608:IIiGHEfUn2BB6zBnxKLApk4vL0M5rtz4zyH9+8TBvUbCjG1HkeBt9tyMZE1:aGh2k4ANvjtz4zyd+8KbVkeJ03", 91 | "13316096": "393216:Ogi7LceK7/NgVFkhMILy3NUI4SYSPTa9x24nj9r:Ogi27lCJI0NUXLSPWy4N", 92 | "13357056": "196608:ZvE2YbTqnorutjBT+uYwpSf0qAWSu1vLLmMRZcGuagXMri3FrId1z3i+VS:a2EqnorutjB6Y0F7vLKu05XEd1OmS", 93 | "13398016": "393216:biA6+B5VMsm3T7xobNloKcwJwijsu1S2cpEx:bL6+B5NmvSb7JchEB1S2YEx", 94 | "13438976": "393216:F0zNq6MC1RNblrU8bWOZ7bebZNpcLNCIjW:F0zNqfC1RNR9b9Z7bebLpcLR6", 95 | "13479936": "196608:ARdch+b97SvJVCWk5vfrRyvTLLfgDiu8fPjYuH9ocorof6o/lmZuYAqPYjGKutFV:AsQx7OCLvYL3YoLYqqRgl/lQ/Vm5Q", 96 | "13520896": "393216:BAktoBRE5b1fuSiC9PezuT30jJVaf9oLp:VtoBGbBBT9Gz+30j/aloLp", 97 | "1355776": "24576:FdM7LnrQO0rdWRtbeutF6isXVcE/ddoxYlrGe1y4DrXcgR+dQ6Q7NNbNoVxwa1YR:FdG0O0rStbeTOE/ToxYlae04DIgR8QPF", 98 | "13561856": "196608:WP32GZl0XAr1KsShstXPhO6qi4mWloMpa06vV5n+M7lmgoJvx5+WL:k2A0QrlWst/hO3i4m8tpaXvV5r7NoJxv", 99 | "13602816": "393216:6XNVXTEz/AGCWe9vTY5ewnvdUx4ZfvKWbPHp:+NVXTiAGu93k1kIR", 100 | "13643776": "393216:Mt52hfTpCyRKjgySnfYeESjhebXxbZraEk6p:22T5Cgdnf1tjGdaH6p", 101 | "13684736": "393216:sBhG/Ny71nLqbbqYLZUZtOmtyj2RqMKdVho:EG/NyhLqbvLZUymty3Mkho", 102 | "13725696": "393216:F3aI1D8UaN1K/SxMpoGel6bsy5Ym19YdxXYpsc20:F3v83NiAyimHCxisg", 103 | "13766656": "196608:m2MBT/a2qma/dRflHVG7p9PE+3sVBat8M05CzsxKZY//WRoZcV/Bp7ICix:mtBTSf5lRflHIPX3sVBBQsAe726", 104 | "13807616": "393216:XDW7imfEOx/zxIWnJUhgciiDahuQ6CxBa4/dxVhA6Fr:XDDmMOxpy9xDakQ1x/BSG", 105 | "13848576": "393216:lm2Zlbe4jZn26/k0iSZuLZIdFdLHPp9Cp4jcIvEj5:JzjZn2Z0iSkLZINPjacccs5", 106 | "13889536": "393216:nCoBogQhw+zq2eL8RlTjrwA5SoezdCagKCT:nEHG8tc/Ta", 107 | "13930496": "393216:7ZwcAncCqucpF+mAtw6afFYF+1dQvcX6uK2qkmg:7Zw0CqucpFJAtfafO+rf/", 108 | "1396736": "24576:he1maHliXG1awFEkOY8vDG9yyItmEkNoKTKfuagrqjV6rjAXogoCY:w12W1aD7YKU+tqpag20rjApY", 109 | "13971456": "196608:SJJ8PL0/2o9E2RRcPlnpIWLejdm8AirwGjASmh6Nw2UCGVFR/kF3fiPPW00q:IJ8o+oQnpdCdZmYw4+gliPz0q", 110 | "14012416": "393216:24rPQy/Bcj6YzWAhlkcBWmsIVUr+yYntZsNWmcy:n9evWWlkcQWyataNWhy", 111 | "14053376": "393216:ZvshxlbOweWPKDPuDI/DaxvvlUkF90vKkzhtYRq4RsMws:Zv4x8wePuj1HOvKkMRUM", 112 | "14094336": "393216:7V7SvjRQadLetOXOa5TSMQ2CCFiNCPMPFkqAke7Osg67J9:7V7SvjRZKtOXpWp0MNCUd9e7S6v", 113 | "14135296": "393216:02nMaJxpB3KOnb5uS/bxr57tgykJ5IlIg8tcxjH+:02nMmNnb5u0tNCxu88jH+", 114 | "14176256": "196608:pRMppyD0k3FDSlHIab1x4oJBOf/WVnxG8raTrnQlR9NmTg0d7vbITmAc15K0fnKH:7MppyZVDwUUEUDeTrovNmT/QlcbZ2yS5", 115 | "14217216": "393216:qZK0gm4ZYsKcQjJqLIeglIOziS18emW/ToeuYla+9:uKJmaYyQj8LH+68/Buwr", 116 | "14258176": "393216:KJHPEbkxOSTm0hTcQgyNhSw3MfnqPbMOPVJ+:KlPEbxJbONmfotPVJ+", 117 | "14299136": "196608:BgmbOiSeeK8KnMSME8p0nkAd8v76MwvvL2vNUXKEUzDh3xQMiYrgAiUrws1avcjA:BFp8ekA27616vNUaV1XiYrgMrwLvc1WP", 118 | "14340096": "393216:Z/Qtf+jek+X3euwwgle1t2rDNdtrwyWp2LYAyXQ2c:Zoh+y37ZZt2rDXtrwyLngu", 119 | "1437696": "24576:K0vq07e+wwrcMOmhYKdPZ4lAN4vJhdwPIQ9p3EBsdct9yj5x5J/:P1zjfhJ6AmHw/9p0BP9yjlJ/", 120 | "14381056": "393216:Or9SyISXmnr0GmsRbIY5tOlsLKAVoGXD2fWrjq698Zsl:OZ3QmDY586LKida0N98Zsl", 121 | "14422016": "393216:h22S3ZcWyMHb8vSnfjNi5wzXzb5JxbgM5BhwYGtHF/1:T2yMovSxgwzXXDLbhJGtl1", 122 | "14462976": "393216:8eK76/iNxnt/ykVEpYdZEHhAgSqTK+CvQbFp:8eK76kxnt/z6WEHtSqTKIxp", 123 | "14503936": "393216:p6Qd++kzXXz+Ymq3eNA4hgS61fh9RLCWSew:p6kqXXz+coG1HZCWbw", 124 | "14544896": "393216:PckEzp67RZF8gPbDHot1DDXxgfFPn5r52gsAg5U2:M67TFhM1HG55rRg5U2", 125 | "14585856": "393216:ajDdurnWDmA1jvZlCOMjJdj/OHXg/GHSXHFzv5V8cE4Z+Z2Oz:adOWCBOMtwHQ/GCp78h4ZVOz", 126 | "14626816": "393216:m7mVxR7ksZIkPentZpnIVumDk182XyG4s24iQ/4f:wmfR7ksG5ntTIu18IN2z9f", 127 | "14667776": "393216:/3ljwpqo7TUWjRAnNQ63f30PbogD1qzSh4MOMq5PUQk:/3l8pRU1m63fiP5YFgqC/", 128 | "14708736": "393216:BRjQkIXj4qAzO3oRHuSp/mN/prR1/C1czk85MDgwRS:BRjQzzAi4O9rRhCJ82gwRS", 129 | "14749696": "393216:gRE8uZAkMEukke9rs6RxlnxwlGIY16koUbgT0yUT8:oE8umk1ukkf6R9HrIUbgoT8", 130 | "1478656": "24576:Cxxq+N1m6wzfaDc0TnUdODKHXVBvh1hvm59YqOnWeuecJCc7LJkIRZVbu:qNwQc0jUdODK3fvNO0rb7cwcZpE", 131 | "14790656": "393216:OMi2IlpKt/tivTeptk+5TAUZnvE/7D79MKeagbtw:ODLKt/GeP/C/3eagbW", 132 | "14831616": "393216:Ihng8qQrZNlUtIVeDyHJ90maE/h4qRHjPKO2WzNyZhE:Ihng8RZLw+Hjach4qRDPCWz0E", 133 | "14872576": "196608:awDs60XIwoDNE/APdrjqSNuUB5dxFK7HD3ZAkGRaHxPcbUQ9VKKa0XTBxO5Yso1V:Ng6ihMvfdxQ7LZvbREAOVke3O5Yso11X", 134 | "14913536": "393216:MEma6ZmTLiNtopJ2j1mLOqVNgJY/1HR4sy/pcl:nT6ZmTLiN+pJ2j10O4gKLjy/il", 135 | "14954496": "393216:dwPBP5IQ7jABUyQIkecDybfyNPUSBG+eF:i5xIyABzTfcWbY1B4F", 136 | "14995456": "393216:WZk3DYVMCbT1RvkQYsJNxjcJi14+qhy1GtZ:i9bT1Rs/sTpai1bTGtZ", 137 | "15036416": "393216:wp/0pZq6ELkOymuEzCG7UC4GxDpmyrCV5qCY6kDnbIuio7I:s/0pZyQhm3BxYdPVYNUuF0", 138 | "15077376": "393216:hDeH0hHFm6hy3cZhjVM8axbitMrdTvM3LfaEhEVePt0R1hWN:haH01MQhjabiteW37hQs0R1hWN", 139 | "15118336": "393216:ln5P6+ijFafxVxhO4rXe7Z8xV83FNWQ57ZMioqNA:lbnxVxhOE5xK1NWQwqNA", 140 | "15159296": "393216:+T/WYuirlHs0UuI2udL+o+xgOTuP9S6m5oor+FZ75PMV2:+T/WYuirhb1IBdLPgTuQoPFPMV2", 141 | "1519616": "24576:iwoORWddkRshKO8dhFf9o+4T/SbjMkfrWRcBtqH5rTSN0ry1i+6h9AZ:iwo/dCBO8/J9o+4zgMkfrWRcu59ryihi", 142 | "15200256": "393216:QFpzUqmovVVkQUgxc1asDZVcVs4HlZfE5XDpIUaEec6EwOVIg:QFVrViRB1asFeZlZMtDDJ6aag", 143 | "15241216": "393216:2T/8m2bkWgoua8msq0zNT2twLfmU30xu54vXBBwOBIkHmx:2TUmcB8msLNatO005WBbja", 144 | "15282176": "393216:6afSYizbhRe6zKusV6oMQlEPHZta6djrK0zIbzlefH:6aY/HsgHZgCIbkH", 145 | "15323136": "393216:ZBaO2v9JP/rreJ2aSHWHZGEAAhdkW/Bp68cDZitQHrt3:faO2VJPfP8HZIwdkW/+8cFIQJ3", 146 | "15364096": "393216:j99Vi9nEVkPKUGbGFC3m6iih9raSUs5daXtt5b7g:jAEgKtbGIW6igck5daXFbc", 147 | "15405056": "393216:rqORA3s5JCk3F5Gg9Odhv2KR4GHxHJYLK4SAYGOUDps:rfwk3F0g96R5xGKhPUDO", 148 | "15446016": "393216:tCk0QifRIpJssO+ZEHCJAlCGOr9HYsLlQ80XjpO7Bz:tD0QiQJ2+qHeWOrHxCSN", 149 | "15486976": "393216:QcSsjtXAOdcBXQam5ps0Vq8lMa5n/iXAVLKX4MDNtXEHmVXdHT:QcSsjtXjds7m5q0AGMa5n/AAVRMDNti2", 150 | "15527936": "393216:QclyOoBfuvtBvL2hU/i2tqsHUyBKjDQzPUzreBh0:NIG/2MiJ27BODQzPUzq8", 151 | "15568896": "393216:zBTG5xvDWaP8PRT/Zc+6t9UZJuOD0YBDHfJ+P/At6h2dC+vZl:zBohZP6TRc+ZJuOD0UDfa269Ol", 152 | "1560576": "24576:IE4NJ7XUYk+BRdnF+RpWh6Pm3wTW0K/B8SW8J3pCxQG5HEFjTlRiDcTxiMQNY99:I9D7TkC+Rp26Pn+ur866G5kFjTjPViEj", 153 | "15609856": "393216:pSYHyFGT8oagyjQ9IcQ2kMgrgI2FmtROpNpMPPFD1YmcFUBKP:pSYHnT8BPI2gTWcpLOFWN6KP", 154 | "15650816": "393216:D26oErAyiH8lGI9qqQwIZo51G6Ugojhg98MYutaK:toEri8x9q8IZobG/Djhg98MlQK", 155 | "15691776": "393216:k3WnEy8b6NixoNHaI5BIwsTgdK527i2L/eqEcqRj:sWnElb1oNHaMpsTqKox/eqERRj", 156 | "15732736": "393216:umw05uWmJRoR+iLKO/J8TFv+j3d/fTxj6thSyd70auo3JjMK:uf05uFJRpY8Gj1fTxj6thF70auo5jv", 157 | "15773696": "393216:fDQTPvswta3kzdSuUarsbLw0+P18sbN6RQtJ9DYSoH:fDQjvsm6SsbM0w/5nDYSoH", 158 | "15814656": "393216:+MSInMAxBfDKwwxWP5APYFAhmVzgiGPOKXpEl693POL:+XAzrZp5AQ6hmVg35ClAPC", 159 | "15855616": "393216:0sP1yW4tYWLPmMjMXYJrt9JgnERYO/P9FTahfl5H1wX3QblEGu:nCtY6jAgFH++XTulRWX6lO", 160 | "15896576": "196608:365R0Q10mErMUaQ05dF+BBGK73ESfQGmAc/BBZFsLnT5LcuUIKHjOPjDffoYUHSM:36P0qFEoBQIz+Bv9GFsNOjAXkRdviehj", 161 | "15937536": "393216:1qFt3lcPN59yhrYWG3MVzw88uzJ75rZDoJBH+:0j3uF5KYMs83zfli1+", 162 | "15978496": "393216:wow2a8p8XcVL3PJxKQnGPIhRS6yFqOZKt:w0DDPJIBPMS6gDZKt", 163 | "1601536": "49152:5CnM9xj1Gc0OzyHKhSw09ohDHBbt/Pm5qT1C:5wMh901IIohjPmsC", 164 | "16019456": "393216:2VptyxKDIPbhNgRu96KdH303iHzZSs2bnUHWmsw:2n4Asomr022dw", 165 | "16060416": "393216:t10Qw5HHftfgCv8GT5USAM2G21E1IQ8g8BaoKCmHIcwiLjxIk3b:t10QKlYA7WZX1OIQp8DKlvHIub", 166 | "16101376": "393216:CpAHOb2U4YizT7PnCKAvaTAiYPcF15ahZ2VFRR6Guz+j:KgOb2UAPsSTAiThKAV3Y+j", 167 | "16142336": "393216:wqlYc0nBG4O65LVxT87+HrBBcQ4R231eceCgbJ1VnkKEUtmy/q:wqlin150+tBoIlecQ5kVUrq", 168 | "16183296": "393216:SAws1VlY6qqqOA1VgVwu02/m0a7rIPoR1lHEDvIl:PwsPlhNdA1VgVNa7r6qsDQl", 169 | "16224256": "393216:vS4WZ1sFJIyRBLq7jz+MLAIkHZg388FGXkIwH2CSTOb:v2ZGJv+naMUIkHZscKlh", 170 | "16265216": "393216:oxinhi7p0bqpJ3oqC/Dnm+xpZ6X7Os7seuK1aoMlQXJNaAEAoeGD:oxghidJ2/DnmqZ6rZJAAoTD", 171 | "16306176": "393216:d/xCzBul/+44HzI4KwauCPA35h+cj9u82:d/xCcl/+4P49au935AuI1", 172 | "16347136": "393216:BDM6kUziCFP3h320g1Hre9nb/N8oMilUv83R1:BQ6kCZy1yJ/Kilbz", 173 | "16388096": "393216:Myzk+S8NUCEg75HG7G+ecvU+sBV1/L93HE44yMmQFC5:tzk+h754ecvUD9XE44y115", 174 | "1642496": "49152:CztmDYKIZuBlEtRz32qLwUkBb8eFZ+kHTaP2:rieK2q0UkBb8evee", 175 | "16429056": "393216:QlDLMaQpdFZujcsaKWP6Jx9mINR+2g3/E:ukxpdjqaKxxwGR+dM", 176 | "16470016": "393216:shOCnPvZIcxQTsojjscydmtwaoLUKzoM9uh0AabW/VV:shO6PvZIcksojakhn", 177 | "16510976": "393216:Aahx5J9hzNarBmmOvwT/T/wMFhsEyuvGCdmMqtqkAfbTf9XaIo618:AaxDz4smOvCLfmUpdItqdj9qP", 178 | "16551936": "393216:DIJS52s/NWvRMw2+84PN2qS2Ucb3u0djaA566fkAvmkmworj0Xaz2g:D858QN2q4i3bj8RkjSj0qV", 179 | "16592896": "393216:aDTpBx4ZhIgxPwwi4ETO2ks2EPNKNn6I28lnZwTMvivi0WH8R8+ljkiVN:ANcZhdY5iEVOn6I1QOIX1R8+CiVN", 180 | "16633856": "393216:8lUP/cDkUxWqjSaGhhRbfTV8Z7amkf+riaDT:8lUPonUqjVGhhxR8pAu/DT", 181 | "16674816": "393216:+1V3UxRwX5IRqIX0TKG4YAo8JpP7J4m34h2YV7IRVI7Pn9Dqo:+1dUxRaaRYTKG4YAo8Pl4m22YBsIL9Dt", 182 | "16715776": "393216:Ly6+GV3zffJo+xcS58ZXHxLIF5BFMZThLvi0mql2/SliCK:Ly6+5GKZ3xAMrq0mg8LZ", 183 | "16756736": "393216:073NLT8pcyjNwwIfUtuqzyKoha2PFnob0LM2heiv6:4N38pnwUmxPFnw0LhheE6", 184 | "167936": "3072:20RnMAMjfifg0w9B9pd4RcuCOpjSFkhfZn8bA7KT3Dwp8iKXDgBU7bocn2INL9WJ:zRfvw9B9pd47+qfZ0A+T3DWFK04kcXNe", 185 | "16797696": "393216:WSOYbBY+X+h/RVsLI2Ku91q9At0+RRUMZaT056vCqbMOEB7d/N:tbPXyVQHN91q9AK8RXZG6eElT", 186 | "1683456": "24576:SMrLqG3fKN5Wr8EatIG87cKwOjIdd2FIK8cWeJPU3dw4yfu22L0pA7o/2pOxc6EM:SPLtIB1oLTKZWeJPcdwAApBu+nqbPg", 187 | "16838656": "393216:AKfWEQ18n8HJ8rUt5vSHpt2nfTkxf0EgLnYdiCKeHXuTPPG6z1:AK+J88dLvupt2Qxf0EQYdibeezPGk", 188 | "16879616": "393216:/Ey62C4jctPSP5n/00EToj9YnDKwgUchnFenOetQLwDz/6b+2H/ij/:/ERsjctPyuTmiDK9UchnfgQez2fe", 189 | "16920576": "393216:QOcTvWiamybAveWZJxMFL4n2NRsteFhxS:QR/coZJWFEmRs0zxS", 190 | "16961536": "393216:v5g8aF3Nypbh9buyHMULbkKs/+Tg5VdZXn3RJZu2tfQAApv:qvNy9h+ULwKsCGvZXBJZujA4", 191 | "17002496": "393216:mMRF2feWTh2PM0Hx0XgN8tGrmHKiizc8mQ29/vzU9l9Nx:mMZLM0HDrHii32VCNx", 192 | "17043456": "393216:lBZ+frGZn8CtdcPZg8srZjr8lvBuuBQQM50njQukxH2hj:lC0hIgVrZjolvBVB45EkxWhj", 193 | "17084416": "393216:bc+6leuyfhD+Oys1tXVrLTVEsb735XU07Te/BdhSjiBvXC7E:baAZrLxEA35y5dkjiQA", 194 | "17125376": "196608:esMBaTYrd4aV4a8yj6AXV+Bw+hBS0g4wQIaoGgGLgN6kSYY0xBNeYcA9SNMqlzQc:KaTUWa5MBxOwrgE9kJcAENM8eeLcsD", 195 | "17166336": "393216:4IBrjwxrKsVDGPyD1Y00TLoR9afwytnJZGnXdJa8:4IUxrlVGKDe00/oR9VQZGnT", 196 | "17207296": "393216:4g8Fn/hQ6jhDZsT0x90OAfwW1Sz5NppRwwu2TUrypsDE7csjf:4g8FpQchDZsmG/zSD3RvOypsET", 197 | "1724416": "49152:7EmY2DZHs76bqMOzbJI50nRi3S/6gFdDf7:gmYYhsSqTbJISM3axVT", 198 | "17248256": "393216:yuK2JvyIl3bj7wReTuykI901G7dfvAGAj6W7RWU1GzbGtzM/T/bN/Y+hMrkbw:zbjs8l01YYp6URrsKzkT/Z/Y+2Sw", 199 | "17289216": "393216:nU448qusLfbgNVHjf+NW7xqqUBFREwu350NqQXP5DkATfJUQIz5GnK:nU448quszqzbzmFREUBrTqPsnK", 200 | "17330176": "393216:rB3gJxHCFkMA9vr1vlhrje4Z7sTzV55fu/mC77LSQBX:rBa8FXODtje+mBfCvLlR", 201 | "17371136": "393216:IbsMHCO5P8K72QOIrWItuywpZPLNrEup/CuL05S3:esMixK6QOIKzy8hL9/9CuLP3", 202 | "17412096": "393216:6aMPoL8CnZVRVFrifMFg7Exjr1M2SwwNrNDZ5HgsYqeUp:65PoL8CnZVRVgf0g7mpExF5H2la", 203 | "17453056": "393216:AUgiSuSeXXiADRLTjkoowIkewGm4hkP55qLkSrERcPE59:AkFiADRbQwP74hkP5OS++", 204 | "17494016": "393216:Ar2ayR+uU29LJCMJaOzzcs8f8sx5dyz2spGWNDYEKFqHrcZJ:82LoV2zPzVKspp4ElryJ", 205 | "17534976": "393216:M3MtLssG2SKcYgMZ4Km2qX/zszRnysC9XCip/Abrpv/yp627UNs:M8JseSKnRteYzCp/+brV/y7d", 206 | "17575936": "393216:j/N4AXIjBsPBRDmoqTNvGqGZTeuuIGUExxsFEtxpd1ayfUxpy0w+TfI7IDNr:NXgsZ9dqJvWZC/sSND16dT0+", 207 | "17616896": "393216:mpJpyO08rJxyvJpo81VLmbnul7MbyZnLVeBWWJe4VCGH5/ZasGz:BORxgs81VanuRMmZBeBCq9ZhE", 208 | "1765376": "49152:KKFhRvvovoHdoooAQtZ1ZI7gpWMtyKtfRrwokst:KKFhxvVrox1c5ufRrwobt", 209 | "17657856": "393216:PFNOvJK0lrq+GKFFdpy64Ezq//c3RfqYG6FVp1whKFjH:qxK0lrbJpy64f/wdGKR", 210 | "17698816": "393216:LcJ7QK7RQTBz/Q+FNluXaVZ19voTxtDutSOzHCteW+dRO:LcJvRQVzoCwqZ15oPuoG7WP", 211 | "17739776": "196608:/Lb3zVIWI9elvxXxytePsveh0xHJcoXgvlSan6u4OkrSyTcIqKRKsAkqVUC5WS6E:/3DVIsvBEtxzxpMp6vOkrSMcIhdC5sXS", 212 | "17780736": "393216:Al4VV6T49gH/NZ7yW8aVjdXEceFe2Cm8JR9ZpMIw6QWr/a:AWVt9elZ2WLCceFLr8JR9ZpMhCa", 213 | "17821696": "393216:99crPoIQnRPgGqLL5LV7cOuxE1fl0FTU08sRI+UfQJ:YPPQn4LtxuwflwTkyUf0", 214 | "17862656": "393216:pgzPj64syN1AfciLLUXqp5p+sEiP349kaHzBnoMrYoh:pMr64py/LLUXk33uNH1n4oh", 215 | "17903616": "393216:ODOeBf408gSQUOGrfduiYfCO+gOWTHnexJk3qd0gquAkwSAIqH1sAVRDZ23+nB:MOeBJHozI7fCJgfexyI0zmwrIzcRDE+B", 216 | "17944576": "393216:NjMOq0e0F2OqLCXxEDrbrYBgkdR+2evPlJ+MiVlX7dQZgW6aax43Jyiig0Nmy9O:NY0pF25AEDEBgcxevPlwMylLde6aax4D", 217 | "17985536": "393216:/jKFDAwmHh3Qs5It1bTryrOQtZecVe3K8+R7sSdM9/yGzB:/jKMhz2L7yrOVcUKYSUfd", 218 | "18026496": "393216:IVDt21xijysDrFFeBvDlC28ugmBGXDlIVUimpRlx8rqFNTnlIb6rvzM:IvaiGsDZQ9Dj8ugmfcvxBlC8M", 219 | "1806336": "49152:N8kEG304O4mMdMutYa+OE8t/3Rsy6GzssoBS+w/kK:N8V4xmM+utgOE8t/3OFBBSFkK", 220 | "18067456": "393216:Z0dv7aXWzWYC18zpHHHwcZu+SdiupyhnzCiASjUV5W81bbgbyQftVIG:SdzaXWz+8zJwGu/suYzJjMt1HgWQLIG", 221 | "18108416": "393216:AXTYebsw6wdWsn/uTDJ6SqMJCX+L0W4fmfHg3+YWSG:OTYe6uZnWR6f+Ll4fmIVG", 222 | "18149376": "393216:fZ6EhXpbaMkePhF12gP5QmNe7cI9rmQj9QjWuC+bwy/2ya1OO:ftuMkePZfxjw7VaQOjWuCw/2TAO", 223 | "18190336": "393216:extbyH29k2svsII57pFYhN/YV/WBS/KKu4fIOWGMrXh+BXNmZ:ytmHQ9II5pFYhNwW9QwGuExNmZ", 224 | "18231296": "393216:0kIp8mFyftoFUqxKAEV1O6HzPRO0VxkgJDeB12Wvn96PL385S/6aJCgl9P:q8mcfEymuV7DU2W/UPb80yQ7l9P", 225 | "18272256": "393216:DoDceiUydCtZqh9JeG3FLUWUqfL12rNtVWDvW614xu1Jq+ZQGM93j:ySU0CeVeG3FLHL1iVWDeMSGMFj", 226 | "18313216": "393216:3qoGp9xlJ2w7NuoxUpmFbHtTBvhFl982XRRVptc4CfoszOOHWTYArt+KnJ:V2xlJ2YNuVpkHtTBvh982XRRVkfX5SY6", 227 | "18354176": "393216:/C+1ibGpJDna8T4+7YhbMtHtPGaQYfZIyv7ktNUwGPhId+ZfBHr:/r18i5a8x7EbMtBvZIaaHGPxZf5", 228 | "18395136": "393216:a2aur0QPOjsONQ/CXWh1wUSo+n38G+jgRfkeTNSVEWHrH:CuD0sqQ/OO1R5jg2QNGEQrH", 229 | "18436096": "393216:g9PUEJEGO41gJW3E+zjpk/qDooU1vv6PqE7dtRmuJvQBl4avi:WP7551IWlzju9oYv6TBtRmuYBDvi", 230 | "1847296": "49152:xwJRQZbg1H5RH4dcwlJ5OPKUos4WRxenB:xmRubgFDH4d1GKUb4ienB", 231 | "18477056": "393216:Hzk3I15+ctiX5+OEDJ0Nf+B262rzoFmrPJIP2CdnC/Tpy2+ZajlLP/:HMI15jEJ+OuONfHPu2JIe9TpuZQlLP/", 232 | "18518016": "393216:0Cq0YNlzc2zKRpI63D0d8Ldl8p9OJFxlukYx83fWB2iBacsiidcI:vyzcsKpy8LdlcEJXcGzSsiidcI", 233 | "18558976": "393216:o6Wb0EqsHNxKx5l8i7/ch+O1CZGd000PNk/CyeJ2UiQIrxEgEfsvX7cmliP4PTyw:PtiEoGFM00NuJN1g6sz7liP4PT056d", 234 | "18599936": "393216:PcygvOomn6KmChN9D0hIPw1m8eitlosleRny41iNnEMSB+LJ1F:P3Y46K9vuIPw1Pas0RpiNnEBO1F", 235 | "18640896": "393216:Awjzneyhx5ls3KoPgRwBme9U5NC8YYGmNsRyMYLpaFDkkkE:AwjznVhh2Nse9U5iXgsb6vkkE", 236 | "18681856": "393216:d6el5IxYxph9Ob9uNxKRt560ExPhpdt1FBUUH:bfI4I9sxKRtk0ePhp7BUUH", 237 | "18722816": "393216:lTX0DMYYSuKEYk0TPsMm7lYUtaMlbrajCX+X5QWWqYk4VR:tEgYfEX0oMwYU8MlvLXLR7kA", 238 | "18763776": "393216:qdfErlokvkXt/xNzxUeS1NdcnuzH/sI0JEEvsk7E:/WkvkXZ/xidrJYE0FE", 239 | "18804736": "393216:+pSE+TUc524FtG72ukaFQhLU5SpYq4nW3ahMuwGDPVcjm6vhqDHI0qlBaWugHJgS:+QEcUco4FY72uxK4nIWMdCc5/lE8JP", 240 | "18845696": "393216:6QSmyap3Unm4vBCI6R+Ty02hREz1QFIZP+/bbgTiXTKV7AB:rSZtnVvBCr0yF+z1QSZm/bsTiXa7AB", 241 | "1888256": "24576:0shvXUadXqllO+fOuj5dam77IO60dJO5/8SPct5eM+/24jOc5XqiXguC0sEIGVf7:ZMvlBGcHJGUCyq/u9iguhJhViI5jX", 242 | "18886656": "393216:eef0Br46wMBJhaYOSq9241+F7LGuHoFImJlfVWmVRoZ5XZ1q:TM06tiSY241+F7LgGYFVHKjXG", 243 | "18927616": "393216:yhO37RrzHyCajKjpZR2NrTFhxg2rkuFEQzNkxlNWKNFp0MCAX9WdGFz21lVXyJ8e:39yFWjwlTFrRlz2xl8KN/0Mu8EVXxxBa", 244 | "18968576": "393216:P2DDxsFp6l6P0AbgUK0RQRBoWYK+Ey/Asw+GWlXO4l7qsWocr91WZr:P0s248AEUvRcoa+Eyo7UOnXrPWd", 245 | "19009536": "393216:FrqArWHsxNEybmdjRdxIxuoZpE7sEgIMRNhd2y9Sg/kU4QVYd4OX0p1po:FrYHeN/6dtdmxuoZ84IMRNuyjkUJM30S", 246 | "19050496": "393216:dvQqNQ9t101WXuOA6fAqHLVkwFhCRX2BkNcuQqASt+Wz:dXQ8mdnLVkwFsjNcsASPz", 247 | "19091456": "393216:8Aedta4/IO6lkuBsbBgyDDXKTGFsqtUZ7VwbrEECFLK73d:8AQkkWESZqWZar73d", 248 | "19132416": "393216:oAgnQf8GwbOMMNWjrxpggrI/0+X6XPP72mJ99iO4x2MbHFvCcWb1ekpjVP:VW68GwanNvl/0+X6XPPamJ999ObH2ek7", 249 | "19173376": "393216:Y2u5OfimAQWWtvRbarixSEIONVOdt2qu1VYjEWglY:7K6TvRbarKIONSI1VYEflY", 250 | "19214336": "393216:MOcO6yg1iqZiYHGu8MNkgrKjKbLEzCfYbqhLqPOHybZznqw7Bc:MpO6IqoYT8MNBKebWCB/Uzn77m", 251 | "19255296": "393216:dDM2qWXwhGJTDhjfKwVVRzHQ7c0iuz2D7sEDVesC+Ln2W3Mqgqs:7qWAhGJTDVKw3VHSliuz2DVJesC+L2o+", 252 | "1929216": "49152:HWAKdApCQZRj6xuUQyG/19eqSxOuet6xn2N9lG:ZKCp/R9UQyOzLWOuoQ2N9lG", 253 | "19296256": "393216:sFtcGpnlZo2iu5rNkyL7nrmjkQ8neALTUq4G3tCqrVTTMRu:OlhzbcyL7+loBLtrVTTMc", 254 | "19337216": "393216:owbqLgprHRQipJDrikSKD5ehqlFKHlN1wJH88VJusF6smnMiOITsP:3bO6rxQSR1RD52NlzwZzL5XmMiOeA", 255 | "19378176": "393216:go6WjQQcJRQbDXq78Fx5tJxikjkLFzahmfQkEB4IiWeWcA/NrjO0KOuQMJA:b6WjQJJ0UkPUkuVahmfQwT7W51rjGxJA", 256 | "19419136": "393216:WrfX4oFUr7Jp34DxA/g9GJKlvYWDie25m15GjePP6S:+Pb6p8A/g96KFv2mrGaP6S", 257 | "19460096": "393216:QKuGpf5i4j6knSykcB1UPxPOfXajLd/9Cf60mqshb73jfaJXS4sBDTJ59u67:QKuGa4jHFkwqpWfXajLdx0mFp3cXSj77", 258 | "19501056": "393216:S+K6XNyOVgG5dJR2Cbv00U0Z3GF/uJ+3bdkjy/VAbOxG:St6dyOpJRljU8WF/G+ZkjytAbSG", 259 | "19542016": "393216:w8LHtWpcAUO415bJnRlWHKXHYk2uJXxoW0UHsDChJmqUkniK:XBWj541vzQQ7XEUHsDCEEiK", 260 | "19582976": "393216:hxh2KBelcZ7tUOiAihfpLB1vFbXouwVsVbmMi39tFuELRAs4RYK62:hvrBelAfAfDfXovVspYUELRAs7K62", 261 | "19623936": "393216:n0dMAsDLcRU5ZgBY8yzX9Wn6AR23P5tJ3ZUV/6h1l0KLE6EK8N5ZPZ6jQcBB:n0dW4+5Zh9WnN8t9ZUVWLpLE6QwH", 262 | "19664896": "393216:GLrw8K814BKA/dhRqI7knD8/elGitvJB1BGzWkOzYl6vQFtAW:GL0/81oKA/BqY5iRJUWkOzYIvQFGW", 263 | "1970176": "49152:fMNM5clubNfUADcR0X4rYJCrA+KixDUmCFax:ECbNfUAw0IrYJCrbxDXCFax", 264 | "19705856": "393216:g+/OJf2pIQI1dVe08qdGjVBa1x+KofXxoLbtoCjjVEF+Fu5VoMMg:g+/OJZbWkGjVBEofibiCjhq+F/Pg", 265 | "19746816": "393216:acFjcBhrD/CNpgR7kyONDK9pemWC7/4FRsuMePwkKghS:acFYzjCj1zWDnb4fwkKwS", 266 | "19787776": "393216:Zu2lVOwbiOARMKF+jexY3I44v39xm7Kv7MeKWq7ZX3UqSfjqJ:NcRMKgey3Ev3fm7OjKWoZX4S", 267 | "19828736": "393216:wRlRZ2TE7W1k+mfM2H6w2cO5uze0/uDA3NcDPkTrkrYnqAeIbr:GlRf7MkzE2H6qO5uzRapKHeIbr", 268 | "19869696": "393216:K3Up575ivdftxoOqxL11Br1+skruUgn+hHNYtdPMIFr43k:KYdAaRLBFBUgn+htuMlU", 269 | "19910656": "393216:uxDSG/ICAOh5KswDph08o59nfYy8WxqXMhD:UPACAAQswH0t5pfYy8W0iD", 270 | "19951616": "393216:yhYx0G4EgKilzflB9to4fyIFkO0Eu6JkDAwHgCeOgSuTn:p6d9B9tlFkau6WAwH6JjTn", 271 | "19992576": "393216:kj89mFPYCh2zQYCu4XnhaODrXI1ZA8q2P6mx6cDs2tdjST68:O1YChXYCu4XkGYXA8pS6Dltp78", 272 | "20033536": "393216:ecs3g0axxXy462yT4VYllT4KQdh0oCF0TGWG+GkjlKRwzpm2PUi3dWYxDwRK2NzB:e93uxxXy4mT9rT4Lzi0KWG+GkKSzJUi2", 273 | "20074496": "393216:3WCbOIoHHK7iLBBYxBa+NlwFNzhpHZwQmHFzi/mXLutFGNdGluBbW15:37HCH9BBFsE3QhFzs/GNAkQn", 274 | "2011136": "49152:JYWZ97c+c74/1PXUv/+BTxLxViCeq2HEq5spKsouftv5bjWwlCw:JYWZ97caPXKeTZ3iCeqQBspln5//", 275 | "20115456": "393216:R1/Pq9RUaaM3IjC6Zy31xEWoY8Xtg8ZGKw+73DnntFamhl0YaofM:7otFxIhg88Kj3rtM0bM", 276 | "20156416": "393216:AxTCiJYzI9uOK0uHVqpT7tYxxfH/0fSPmi0aVKu+bSlKlAF1:KOig6xuHVqpT7SfMfKmi0u7KlY1", 277 | "20197376": "393216:isCHpnfxl85j4eDfweHu4PZ8v9qeJta0YPXMPXhHXmCGhCOP3Rx57:iJb8VHLFPZ8vIefa0Y2ICGEO/D57", 278 | "20238336": "393216:Nh5nnwVt7yh+qnJb1zcPtfklo1V6S6AYoQMre1cwuJ3e5xFZ5QDxgeh:NjnnUt7GhBclU+Nb13re1cwNxidHh", 279 | "20279296": "393216:CWh4e95279rPy08d0B7bjoxWqMDP2kmUdKz1jbcVXc/:CWd95279b78iULMDndKpaXc/", 280 | "20320256": "393216:ywWXUTIbrEtYZHncaLJ/UFKbkvFboSjonzvz0f4SyWpYisJWLoB39P6iX:qXMziHcaVssbk9oUoEQSy4iWLnM", 281 | "20361216": "393216:GmULbgYjhNHKcAKjSWLEi8R3hJy6v0PzWNYTMrVyK8lspXFh:GmULJjNhjSZika6vIzSMh8", 282 | "20402176": "393216:U8C/EYggx43onLHCDmOfCKzM6gqksg9nzc2FqHOA2bSYNxjR:tP4/LHCDmy7TftknpquAsSGjR", 283 | "20443136": "393216:/Dp5H+8qdb6j+LMc4mlO7c0QlUQvWuSCZdegbLtmQbe4dNgOb7/MNjA6:Lp5jqdb6etlO7hQlBWuf/xd1kNj1", 284 | "20484096": "393216:Go2FWWc8iaOYfzNIQ3uia5I+BHgWwXunwXhHvPtP6YyZgVTGJt:4Wdefz6lia++6pRxtP6YJGf", 285 | "2052096": "49152:ogMTkinLyKAApkFS/l5fjXSb5vTcYgaq5:ogMAinttpeS/lN7yQwk", 286 | "20525056": "393216:yLmSLgipNY4hn1IebbjbN00QZwIEWh8E9wQxmmSVd2oJZGJ/2LN5B:yh1pNth1IePjbEw3W6EeQdSVdGJyB", 287 | "20566016": "393216:KCdvUV0FGZ5vU1I3Ew/3kUTyoSaZvfhnVC7Jcz9MvpxO1O2X+NE1R9Yg7DN:KEvu0cjxECJpvJVC7JE9Mvpc1OibXp", 288 | "20606976": "393216:fKOi+vG9JefU2W2oxbzFsvR+wZ6vP9qp0sVaG0YvHrxSe5NZ7if5cL+r:di+O9JefUZxbzI+Qp0E7PvLxr5NZ7iHr", 289 | "20647936": "393216:jyHKlh7AeaKA66MDN3YwW9DFI0Ip7lmKsTQ2Uhwzz9U6dfjB:jl7vaKA66INEM6TQ2UmzdbB", 290 | "20688896": "393216:KKgtD6cmlIWo0YelBLmR1d7KmTYASDGsWWMJ09vNqQpuf:KNtdmmWvlBLmdKmwRW10pNqquf", 291 | "20729856": "393216:oD796Dgqh8AP79PdYpmZgxLSFJWUAlLg3ztVjNS8iXwTG0XsdBw/xVUzu2TBLcdj:oDWgqh8A9dYcuCtAlkvjNscCnw/E10", 292 | "20770816": "393216:G0WhKAJrG9qsvWc5rldM8TWYgmrIN1DRyRHA4pFRjPhH0TCmvuYRG4jz8n:/HiJsuIrOYgmsQRHPFRzhH0NvBjI", 293 | "20811776": "393216:IePrJ3jRLcmlY9pP1R43/dzvNPxP9QFTjrWy06QZEu9y3tLoRgr:IGrxRrYvM1znlQ1Z8ZDy6G", 294 | "20852736": "393216:Sj2IouhB7xlBWLQlqrMGMHTqTx6l9anOexjkKmzda1d6Ki70Z:+3hB7REQkrMXHTQ6lcn1xjBmxMd6KgA", 295 | "208896": "6144:tG4fQHdGW3TvR07E9kJ5slz0RLEB0+3wHt18F7xgMf:WOGkigLC/AH07qW", 296 | "20893696": "393216:qbR8tF2AvCFD5XGiJ6YwmDzNHKVlM1udT62uKUxhe63W9eyNuOf33zi+kImB:VtF2bFDA2plHKA1auNI63W9eBg3DHs", 297 | "2093056": "49152:fjv2UzHdi8yNLwzG9W1HL2lOEMUy74MIqZ7Hn2XQZD:fCMHI8yNczG9W1rCMUdohF", 298 | "20934656": "393216:R+/ubO/EP/FXOGCA70L6t4VFOFEFDZuxmNHMg0DTyd48VdXHm5:R+tk/0PAeVFOFEoasg06LXXs", 299 | "2134016": "49152:c4D3RthvEwBLKVXFh5Uu/QC7MPv5sv/42J+FOi5p/kPNM:cyBt5lB26OQvPv5O42J+Xb/kPNM", 300 | "2174976": "49152:oEdSISULg62LtLYuGQw9fsyvCAu3NFc0TjFQdxxRJkht+:oEwWLg6kpYuGQw9seCNm0TJQLzY+", 301 | "2215936": "49152:/G8g+bNMsxwH4VSU/3fIYHLqAcdPcTqMQfYFoUbcdZuQ3h7uX:/G85besxi4VS+3gRNiTqs73X", 302 | "2256896": "49152:XOvrCrxNxjCXBZlSbSWJk9GjBA/zNgwZiMjiVZltvhf0KCA4jgBZv:+jCrJj+WmGji/biVtvhsKOcv", 303 | "2297856": "49152:HBigHoUPjfuUSxZ0iknHiXWk5lZobMMTSSGbx0HkCtTFOTOq6YcZR0M9GkPNE/Q:HBioJ7BnnCHloMiSJxfCbCt6Y0DPNt", 304 | "2338816": "49152:xulyr1YdVHvrv7UBUWFGYH1FmEaDLmik5GkkPC9diUAhi9Ye+JjUifr8:trSPPPW5KE2aik5GkkP041YqpJpo", 305 | "2379776": "49152:nZMwSq65aPT+exMZDptOr14HPx9T37l95sdfUvjJ2Kb6ukiun:nEq0aJMD/OSLTLloGd2abbC", 306 | "2420736": "49152:sfsezMDKAT+7GZ8erF7evGcODVKroxoOkeE6dOR7ubB86:sxYxTi68eh7evGcODV/Ae3dORSbB86", 307 | "2461696": "49152:FGX7HjCFcb6kQoVPiGv//cSva24BRYa5pdwB2eMeuCUh4D4vcBrcF:OXzVxVPiY0SWBRN7wBzMTCLD4irw", 308 | "249856": "6144:VGwInW/N5ZmutksBsFdXXr7hg1HbZGP1UfWBtC1yhwu9zbE:YwInWXDtXBmpXiHS1Uf8C1yhwgI", 309 | "2502656": "49152:2S5bPFjtg+KeWffxmYNb6AzOZosBHSviSRcwrQPLL0jF7:nJtSDeYZ6LBHWiEx7", 310 | "2543616": "49152:IFr8MRm7nfsa0XBlMkUwTr7uCgmuLAnI+Wls7312p8G5y4TNDIgL+M:YlRSnE5lMTwffgRL8Cs73YpJlTNDIa+M", 311 | "2584576": "49152:DV4/E8X3eIm4R+NOv0+hyGNZDqLtIUdRyKP3Be/LnLYwT9UJ3D6FJ:ijOImp8v0LG+IUdEsBTA983+7", 312 | "2625536": "49152:pLYiU9khOAtE9SyqTp+oVzpGQtkzbh+x4Khqfti5HlhZ5IzcUM7HQxBy6Zp:2iKqOAtSSyqN+o7GEkzbb26CFv5Izc9E", 313 | "2666496": "49152:WiTytoGceIg7DAlZ9u+72AQnYjQzs19qHtS9JKo2JuCrK5tbNMVc4CQGzLoNRfKO:PytoGceIg7D18QZS+yCJuCrK7b2/DGzq", 314 | "2707456": "49152:aP97EmLS9oCMf263PmjsJIzwOXkV+uN6VgRVAjE7gWnVOu9c10Y7H:2mUFH2MAspTj6VEV82Fm1Z7H", 315 | "2748416": "49152:1pOTl9/F0lQvSoBEPROJ868WurDN/UcnuaGwkTlkZqUDNk0ZsmcmLw7T:2ZFSf9iurDN/PnuaGwkq5smcm07T", 316 | "2789376": "49152:RG+G6FqzvE+GC0DV6fG5382YBP/UGN0I9dC8+JIMtTsmyLe:Re6FAGC0IG5anU/8dAIgAmyy", 317 | "2830336": "49152:AMFaUWkGX4/hsE/EBfVhuPLw66wXwq2O36udqXcatPa8gyFNFXiTM+88WdxjaDyT:AMFkkvsE/EFVAjwfvqFhcceaVY2N88M9", 318 | "2871296": "49152:yfz+uCF+ukWvWv7pUoK28/Sqsc3y2NNL+b53m4KiCqCDYuv5DmGPPonb1CDBZxS:Q+uCFmWm7pZC/SqsA3NNP4NCvYyD5PPS", 319 | "290816": "6144:wO7HBdrkH5vu1MSsRtqUW5BXrdL4CeFwgcIz9pMsnn0Mb:wOzBdryxSsRq5JdL4Ce0EAsDb", 320 | "2912256": "49152:gKLIqEa447GTXeuvDdlGni7g4dLSdnO/qvLW4pMJHpFwd+eCS1/mE6tG:XbCcQMi7g4BEnO2CgMJJFA+o/mM", 321 | "2953216": "49152:KUzEblfvDr2Z5zvzF24vxZ9sDSJGxp6oaDPvScY8FS/ux6bVw55B:KSKb8trFhZODTuD9FS/u6ubB", 322 | "2994176": "49152:f34zgjjmm1LxyAhx+IOokjGhm7msfALtn5gLNMGXuHEssCCHJ+AhqFaQWBW:A+jm4LxyAhYUkqhmqsfStQNMGzFUAhqV", 323 | "3035136": "49152:tH6PeVJoEIW7N3Gy2riBt7irYlU8NCfX9lIAzGFX3j4lkVnBgVO56XZ0aDnvKEQC:JbHrI2tLQfX9lI+ismgO5udDnyEQC", 324 | "3076096": "49152:2gZp86oPVhsFDd7ljT0b28scsbZljg4kCpmv0/F/NWstFoURRK5qDq44shqv1C:2upTUKNljg4kCpmO7WstFoUnb4shcC", 325 | "3117056": "49152:k0238WAV5XPQKUUPrOxsU3TzbVztiR9b8N+xeMVwm+u5UiME2rRLDkeJREVIPLnX:k/6XP2aC2UDP9URZxejuDGDkeJ6IPjX", 326 | "3158016": "49152:GJvAYTnFhzGEnL9NoolggScuLBqkpzRLk9mZekP9fS8/hFjKOSx2/3RPA+Hc/L6k:PYTF5RLLomggS15RLkGeU9DPjG2j8FJ1", 327 | "3198976": "49152:QsxlfJFMVFcSAW510EfzOj9Unu9SGZsikU3/sNarxkazrI4+VRln4ddFdtyXKa:1xLF0ytWz0Ef/SaTU3kNekankFSFty6a", 328 | "3239936": "49152:Q0EgJlBUZfFhBvH32bbQI/skK9DZMD6sZDfEDZmeSVXz6RLoq8Bc3deOqHGRWvsX:hEC8BdH32QQS2/SDYvrme5HGQs5Zxuy", 329 | "3280896": "49152:DhThqG5vlCGB9z/pyaOcokWWWLRuDIjhxOzm6hnstQItw749fzAM:DW2dL/p0c8LRhhxOS6hnstQkz", 330 | "331776": "6144:ez9s3+ORVHz4GMDODcZV9mnkQZ1+hVkg+iR5R4/gLFEeHq05K5afA3apz3qBp7KZ:As3+ORVHz4GMDffAn/j+hVSyycHqAKyt", 331 | "3321856": "49152:PAWSkce+MyxWptuiBiGcYtIo4CosKf2bSwgdmjuZXEQXKv28OTsjsj6UDpj:YWlccu+FuoiPubSwgMHBOTsjmPD5", 332 | "3362816": "98304:Mx1dUixWrRxCl9jPJHxWEASCnND/nWNmQfqX:MxlWrRxC77JRAS43OqX", 333 | "3403776": "98304:K39+vOBO1LE/fAVZmg10hW15aIAGoH067LiS:W9YgO1LE/UfbzAGoHpL", 334 | "3444736": "98304:s1Go8hfJdBC14sUz/1YpgQgmDRtyx/moKwzxzqyol:s129Z/1YpdVryx+oKwQyol", 335 | "3485696": "98304:V+6NjYxngH8Y03Zk96Tt22AcM/Lm51qW9h3DArlQuuMuc:V+6N58zTLAxcEW9VDAtuc", 336 | "3526656": "98304:KYFaKVpZuGEE38pbJzupNwBJiH/mm8r6ZosO85ir7xMJMT:zRRvEE3yNz6wBJifmm8r6ZosdExMJMT", 337 | "3567616": "98304:40YbCdrgnN7ggPOGeSeBaiOSZDACmFRypH+:40YbcglpPOJtFOADHmFRypH+", 338 | "3608576": "98304:Hhq1PLWoG/equaBUwIUmVKzInGHLXjcCbiaj:aPaXfzdmVKfYA3", 339 | "3649536": "98304:809Xpo+z96ZuUS4p6OdZKj4iG571pVBZbaBqrEERsiw:8CZo+/upej4iG57NBV+aHRsl", 340 | "3690496": "98304:Z4aexS/863O2mhETi0yBEFtloaNhuY71b+:PeD634LB9Y71b+", 341 | "372736": "6144:AnfQiSzhY1bWzpTwFvu8ZuFaj593aYEpCNkaAOPtGpZrHSM0KqaZzkgCPlPR:AOOyzpTwFvbYE3aYSCNPdPMZrynUBCD", 342 | "3731456": "98304:FDvUc7Ht9iCTypceE3adz84ywAHl6NJzBN1LU:Fzxurbdz/Wl6nH14", 343 | "3772416": "98304:KiQ0VPmvtRrlzYpJU3BB1edS+/UxgDhcsuHmdLPc:KiQ05KtRxzEsBfeSHsgmG", 344 | "3813376": "98304:IwAnGSBjhN0hh0/frBLfg1u7qewKJewydgeas/zEG:Wh2P0nNvgwXG7F", 345 | "3854336": "98304:g48Sg5M6eJraq+X+skbo84ZxpV/AC3zyOrVK7m:g48SWM6suq61v8exv/AqVK7m", 346 | "3895296": "98304:Kzyjb+ih4eeLq/AeWlk06cp6zvnqY5qsL:moee/AeWz6XzxL", 347 | "3936256": "98304:00jiZB7cxEkEp+ikse82dPGiLtKyvOQ/nr/TY5BNiCKp:QPkEp+iHeJUiUyvLnrkEvp", 348 | "3977216": "98304:hbD2qyfjY1kyGpAGOUFVPIKeeGZdv1b2vC39czY1vNqPi72xS20DF0:hbDhcjekpAnCftUb2KRvEP1Mdy", 349 | "4018176": "98304:5CUgWO9tOzEB6npTl71KD00zIIzPFeEF81vffGd+Xq:5ZgWO9tOwcpZxyrFFofGEa", 350 | "4059136": "98304:1oIQRBRCLQUn2cLGOEKY3/Vfrsj3ee84FrUjsLgdt8:IBRCLH2cCOE53/VfoueBd/7", 351 | "4097": "96:yNDH/iNQaSXRLmOSxu1aQP4iWgC8JbkiA5Ix:yNLaNQhSxEgVYkiA5Ix", 352 | "4100096": "98304:EdL/bnUgT88LHoFIvmYUunpgFF9zccbHEsIBEJwRh:2/DUHmmwnKFF9zccDmBNh", 353 | "413696": "12288:kPal5Npap7mIQyl8UBJtMl55K+IotLgqGru931dKym:Nl52GmMlsAFG0aym", 354 | "4141056": "98304:/wFUymP/s2MtQGnmhKLGwQm1UR+Qeb9hGcnxFPxEXWQ6:GBYxDGmQGj1+b9hGsxF5EGb", 355 | "4182016": "98304:DbXjxivZ/EU/spbh7LesqRW/EToHME31NJIxZ2i/LP9pdw:XlivZutnefRUETonNJgZ2i/LFM", 356 | "4222976": "98304:imoI+Y7UJI23Di06N1RlB4clLDuf8u68FRxXuWVD1m7:LhwP3YNHlBfRDuf8tuxXfD1K", 357 | "4263936": "98304:gPPIhQiTw8/gF3XZYVhYdwzcr4SRoO7c1gfwmRztLEK9Yv:sPIFtsXMh4wzcdBffr9Yv", 358 | "4304896": "98304:clyTqOGAGOxbXoct3NEaPLzPA2bzVLEoWPW7r3yYL7ILDocEfu:c74bXp5NEWTFQ3Pkr3hFbfu", 359 | "4345856": "98304:mcEdXerzxtUu2b+FaoYSA9/nDh4Nn9EZPWZIz1HILYAEY3sSW:mcEdYsNyaw29Wa1ZiYm31W", 360 | "4386816": "98304:QiFjp+cwV6+RgNIjRmC1UXfprWrpDzI0s57tx:pjp+cwVDAIjUCOhQJkP577", 361 | "4427776": "98304:g5AYBDE5dB83V7qtuYd5dshEeN47Vjmf3oCsl9t1drTSJt:lYB4j83VetuIyeI47Vjmf3cxw", 362 | "4468736": "98304:z9ZcimcYnRrYq0kEcpVgrJocsfCZCJp46uUKG0RMbRN:RZ92RrYqrfgrJOfCZQoo0gN", 363 | "45056": "768:mlHmRZnCRFRwSuK/UiwY37TMbsDEsb1Jqi6dcXoWpKXIUxpQDOAvWpPK:mqhCJwjmJD31DzbDwd+oGo9AvOi", 364 | "4509696": "98304:7oJVB6Skzk3U2AULUbVO0Ofqm0qzSx0YC8XFRm7GpFKL0L0:cErpuUbk9j0mYC8XSG7KL04", 365 | "454656": "12288:YzIApNT6A01ovIjuHU7kFvIw2N8uVYmK8+YZS:MIApNZUowWikFgwxuVYmn+YZS", 366 | "4550656": "98304:8RQ163aQf/dKG59pWY8tWLiNYxrqrlx9YeiREV0e:8m1saQf/p4tWLiNOrqrlqREJ", 367 | "4591616": "98304:ZNGbYFVqIqaItZGUzbnOKOWKjVI0/2++GyMzNgnCB:nAYzqraUVfKl8Men+", 368 | "4632576": "98304:f+IQAf0p+iCZtHZLzEl0wesjo6edA3BbQIla/sqtABbnTluk9nACw:f+IShiXLRDLjdA3BkII5iuaACw", 369 | "4673536": "98304:Yj4e2ZCoVXJo2hJbwBki+xBTor/402BkkNdY0oBQenH/oo5IF:YMGo5Jb7iEP02he0oBvAoKF", 370 | "4714496": "98304:pAnOcFtJ0sCoKph9/u4PXdw8wDajAyhkiJJsCSN0mcMX1pi:pAVJnan9hwvaBPicq/i", 371 | "4755456": "98304:ujRmkRE1+48lTY9Z109tpffQk0ZIu66U5ZC9uBGwM0VInEJuOx3z:2mKHTY9Z+pffeiu6N5o9PV0iEJuOz", 372 | "4796416": "98304:KYRaMzuZ1QZfM9ahRr5hZJD7AnDjKV6JIDBkDzJZZcZBN8UreDJOWZl9b+MyY:BfzuMfMSrlJD0nDjm6CFEZcGk+xyY", 373 | "4837376": "98304:lzPkk2UBzqDejo4azP2A6+0ZRjQeRFj+Ko+H0Rh/dsOAzUvRM/PiCw:p8kLB+Dejo4azPZIueRFbo+URNd0kI6V", 374 | "4878336": "98304:RuQZWO+HhKp6Pv+6/EB+3YaCMRASFN3duWcL3NaIGpmRTddn:R/ZWcs+qEY3YaCkFXuVL3NNGp0dn", 375 | "4919296": "98304:LabjdJ+RdHrsDAw3jgcf8Vmsl6082Re7rqn00RFz0tJaEFIc15Xu:LQwJmAMVkVmVrkefqn00vc0c15+", 376 | "495616": "12288:sMI4vQyepN1wYIcogaYLQoskJi8LQLhgVtClNHV7+Mb9tkwE:sMI4vQJ8Yyg3L3skJDL+gVtCJ+MbEwE", 377 | "4960256": "98304:KyHWJ0VNrflwR9M3rjuujdLafRVtyTYIcb3c6+uK9tuVRKrHQetbZ3Q7:KMM8flSq33uux+fR2NY3auwtuV8rwwbo", 378 | "5001216": "98304:DJXy6NayrdibvnDqCC9dMp8r9iS3hh9vnDG9fI/xynMfsCT:NTNaUgTnDq9dMqLb9vnEIE5s", 379 | "5042176": "98304:nZ8NREJHlDQAacsZHQkACUlT8616Cdhi7bcZRaDnCxCyjUadZmUSQ:nZURcHlVsZAlT8I6Gi3cZIDnCxCB6Zm8", 380 | "5083136": "98304:cIAje3yJ/Kpq351yXlou7qdvVLAtTVMLfTd9m90MxTDgrC9PNLzfd7:cnjB/KE3oloOqdtLcVMLB9m97xCgPtf5", 381 | "5124096": "98304:eLpYPS+e6iTbsQ+jPPnJCNnWpg+pPkQBM3A3ZZd0eMsdACsMtHw:eeq+e6x7JcEguM3wZZd4sdVst", 382 | "5165056": "98304:qsUr1kJZ7+v3AJWnNadBIICoOuFVk4EySfPEhj4bv/sNC6r/RiPz:V2KJJ+vVUIKOuF64EyqPEpWv0Zr/gb", 383 | "5206016": "98304:BFV19vUr9BQmvsPnRkJl1uvGe6az6XMoIroS3kDBWuiM0Nyohubc6fDyhl/:BFV0lm+31mGe6az6XMUSY3iM0NyohuQn", 384 | "5246976": "98304:EFIPK63VZNDqRyqqcaGLLjn9b5JKq+A4NiajP399Meq:EFV63VZNeZLLjl5JK0Y/99nq", 385 | "5287936": "98304:73H9hL9AAHD8q29bDArN5ri2nGF9CYrVeYp/3kPMs5MSdBRbPniJa3XOTzbG7:739hxFy/sNARYYrhp/3Wf5MYbPKKOTz6", 386 | "5328896": "98304:GcRF0sJrKiuH3YktEMLK8OdwRjCFspGcD7pZ/lCOid5EI27aY0pW94BWhV9fOcA2:0spA3YYK8OiR2FhcD7L9id5I7kpLBMfl", 387 | "536576": "12288:hgSibFzU4GK3rzvWkcm0Cd6QOrNdrijZpnE0gOrCJ1TT3:2rQ8CkN0MrIdOrEqrCJh3", 388 | "5369856": "98304:XyE1n+VfugB0a++PhmYVoRj+Ju/lLEnPsxaQmKSW9t7jbW:XytdB0LOvydFQPkaQ/tt7vW", 389 | "5410816": "98304:G1COKki25uA7rXB+XUTMMyuisic9CEB0+k2bi3xx8SRQICWYVNs1IZH:G15K12o+RjMMXIp+7ujHGIpYVNgIZH", 390 | "5451776": "98304:guzV2PjK26MWfHNzDUT4I3Tc/i5xXTtAeyeykETQLr+EycycO+ulW6j06pUt:Fx2zONUb3A/q65YvrZypjpy", 391 | "5492736": "98304:B8+z5pT+AZ8Z6cOidDx65QLRPbBbCfE1JWQMOODWuSRojOfmyteML1D+KwX:n+e84cOkFlBmfE60ODWdRoafDtpt+T", 392 | "5533696": "98304:pkzE0F2OLwuwijR3BVHfcu3t+Cf2RycW25ggY2QYFm/jrznWDd4vq6vmxwLD:6zRQ0+UR3rHuCf2RJvgg5Q/LWDd0fvB", 393 | "5574656": "98304:inq5WMKCyNmugAUZrickRKreKbXly7DsIuk0n2ONB4GLBTHhrcgr4:wqMr/NTMricQuC7s2+4GNJ0", 394 | "5615616": "98304:nW0YOzt/tV3QC1HbsXiEgQYalD4Nv7PiNmuCpuirRBrksrIFcRoqguP/y:n73/tn17sXi84Nv7qNvguirtrIeSRuHy", 395 | "5656576": "98304:mRpqNSMOiit/jqp1OxE3jr8NOWSuB6d3Ons/nulUb6Kt9jMDPFwu2ZnN:mPsS98EOTr+OJuBAMl7KID9wu2ZN", 396 | "5697536": "98304:DIIUKB9veQ5MQPXdY4ye+JjW5HfaILkhZb/Sh7sCUl+Ic22:cI/B9vH5PVY4MMyILc47ZUkIcR", 397 | "5738496": "98304:25Grqq96QVuHk0o/qjyHgWURJzSSOBPLYWOcUCar2+FT1lyMC/YyXIPfGOGk:Esd9hu6HTs1YPLYsUC01lyMC/ZXIPfDZ", 398 | "577536": "12288:2bKbBpR7sTE2CnLcnrgnHOC8vHnylBnbSOWcVLAMIATMu:2bGHLMrguPHoBbS5cVLR1", 399 | "5779456": "98304:X6y7bW9RQtwgbxYU3bAvD+2GYABwTr/yXi3WXUGx+KnlEigMtmcFbAgBwqM:X5GStZ3cD89qZ3GEW6ijAgB9M", 400 | "5820416": "98304:x6GW8FpYJJPe+46JnAUJET8ilMcX4CKYDAvBjA0JzhZEE8WoZLWJUXPYV8F2sSK:x6uEbPe+f7ChD6lJzh+E8fZoUfDSK", 401 | "5861376": "98304:2EADV+JRYLVhBfBbU/HZSYeAihgMnaEn2DiYjsSmd6oZgktL3QMbJf:cwvSnBpIRithlPCiYOdWQcMF", 402 | "5902336": "98304:609uISjpwxVzv2jEUo8VvOMtXsNMuv+As8PN7pGqqtV7Xfp8gB/To9OPJSxu+S2H:/9uFZxVNsqu2AJkJ7PptTo9OPJatNH", 403 | "5943296": "98304:m8TEaRIZzgpVE/7geKaaXypgaAM/78c4rEvL4AJu8xoGGR9VGDOQVWLSliG2:miVE/Wucc4Az4AJfoG+VG5oLSliG2", 404 | "5984256": "98304:Zp/E8kL+BxPe5iMXn28PJXrMHwCtVC5GpkkZCi4mcQvhjMdqgdxwz0dDT:ZVEf+Q3n2894QwYwpk6jEQvlcdx2QDT", 405 | "6025216": "98304:PARt+7m5qkko2+ufI+Vxj4uWHQu84f7EXI3qgjyz/K4MbzOzyOr3vytKUU3SA1uO:PARtn5phQVxUTHFf7EXoqgmzSnPODyg5", 406 | "6066176": "98304:5E4XhAWOts7F+9RQaXkG+V1k1TiYDyJBOjGxzCjucCz0b/pMOuU6/+AVcHqg5:5JXksZgakQYaUKA6vzg/pix/+RR5", 407 | "6107136": "98304:t641GCPZ3mh8WG9V2LjdeibU5cjavOp4LJV5Z7tSV4M89/5X74xCdGo3lMgiwOC4:tZB32roVwjrnjavOpCfoV0RF7CeGvgMV", 408 | "6148096": "98304:fUx1k9phSC4K/3b9s1EAwdThBHVCuQ4jzQ4b0lxtHDVd7zA3NVp1VtjbqUV17WRT:2yhZvb9s1EDdrHsuJo4Q3tHx1aND1bqr", 409 | "618496": "12288:rbP/lcEmaWZtviqZoMRYn1qvp9IaqGoN1gZfpqCg1k3oF9BPeLlEigTGdXH:PnlVmaWnKJ8Yn1IgWoufTg1k+BWLlRg0", 410 | "6189056": "98304:44AN5GO0K2EmQzoyqMTU5pJ90tnWrAOdFFg6tvWQ1NFsRdtlMmRyuDcDls:qQO0Ku8LdUpJ9LljFg6pNOTlXRyu4G", 411 | "6230016": "98304:VUnj8I4hvLa9LKud8lHMIw7o3crQXQfu9DPBLln0S4eoTptZVuW/vNCmCVfIiI:bjBu6HMt7DrEQkDP5l0S4eUtZcW3NCmF", 412 | "6270976": "98304:WQZ9WIyz6DVPb7VeOmNgZwrBQ1b5CKz96XE2HipDqoEZEZJbxmZQ1hQzhDDhrCsh:DJNP1ymCy1bgK2EioEKsZQ1hoVlr/h", 413 | "6311936": "98304:N3tjahS7eYTrfdFuXHw61YkZjVTnW6TtJIESdbC5QU5lvIdYLsIwVApln:Zd2YTrfdwXHt1YktZ7ZBk9U5l00KAP", 414 | "6352896": "196608:enjg7ZdDKC+jZUetIc27TTiuImngZOmXi9Oo:ujg7ZdDKxmetIx73vImnSXuOo", 415 | "6393856": "196608:5Xr8oQ54IUP2PeSSK1mLU3g4iRtP/m6Zp:9rh248dLUUQv/XZp", 416 | "6434816": "98304:txHkEaowwXGdEbTFY86eTWWeQYrU4RccpBCsMR+zFwXP9GPzBmus6Kd3p8c77PTe:HEELhzir9N/FzFIP93usJKc7r6", 417 | "6475776": "196608:17K5a2ouDi9a1N0p+0K/inaoikbbN6gJR/Wn:Esu+9aup+B1Dkbbd+n", 418 | "6516736": "98304:f1n/HXbqjmVxzPn+zThA+V9ORAHMcD/zLU5al9+hEiwzUkAAjXKP8Ro:9Prq6Vxj+zi+V9S08cn+hEi6XtQ8o", 419 | "6557696": "196608:L1zGG2oYBtU4S9iwOCVe7gLAn2ZDWJeSMcT:ZzQvBK4S9qCVVEn0DOeSMcT", 420 | "659456": "12288:qUGs5xV3qRbStWlBU9JSmLBqk9SBl9k044+fNl9rgkpCKnJsoq4Qj5wXH:qdsMbSt4Uu+p9S5kR4+fb9dB4xw3", 421 | "6598656": "196608:1r5Y42hOfJZ5tHs3hRiDFRB9pRls2U9kPY5g0:d3n35tHsxRiBRBLPs5kt0", 422 | "6639616": "98304:1+SWE1nKBrZm3rpebdr5WVveKY0ba7jPnyNIRdiWps4t3HohdaINKqsKVND2:cSWE1WrZkr4h5WV5avnKIVj5IqIN0K72", 423 | "6680576": "196608:tZ+FKeKsEl5gZlXli0HN5oqrnh6HtctXWZs4:tzeFEruli0tXnP2s4", 424 | "6721536": "196608:EiKyaLEs3mHc4gj/CwHYul6uZ12RrARHp5:Iy7sEczzHYulxz2RcRHT", 425 | "6762496": "98304:OvxiMtFW4rZu6RVmNE3O7BLUhEBQG1P5mEgZwXut7YbU7m1x8j7IvBpTw7d:OJiM3WG06fP+7BudGPPZb+Iyj7OrTMd", 426 | "6803456": "196608:r2knOKWvLzXUtvzN4svBZ65Zddw8ROX1ff:r7nONLDuz3v6Z280x", 427 | "6844416": "196608:AuCd3HZgJzJustZhgZYU/QwzqQpMXRPwzOL8mlTuP1sFN35G2DmWFw/rR:AezpgZYU/QwWQK1l8QTuar353zw/rR", 428 | "6885376": "196608:cjfx5KCFbl2g+ylqrixswgJqKDTYNBm/oCIPyRBkLH12r/KjkEd7NY54M:cL23IEmWoCi3Ir+w", 429 | "6926336": "98304:SIj2dQlNYj41QdBrixhuGYHjwBgDffOTupdye7UehZJmUxwskrCxHkFQ5:S6XYjzBriGGY06DUte5hZJmD7e8W", 430 | "6967296": "196608:xmWC3fVvqM7MAHJsnsK/0zfUXDBQ7UDTfxSUeapzxsDLyuk0:cWCNCbAHJsnN/cfUXbSUnwp", 431 | "700416": "12288:4exW6w3jQ9PusR+RysgEVI6HINkypnb3cQHr+NfujDMF:4HX3jwuo+MsdHmH+lTF", 432 | "7008256": "196608:eJBEOygKPFnrb/0qkL4DQY3spgYItetPEgddYK1TgT:Q+7Fn//sL2QyspgYWetPEgx1TgT", 433 | "7049216": "98304:pdIIks4OfZUppAIntwhTIUS2joDVWbUb9qp1EvoP8YpyYIOaWADujeTn7iEhc6y6:pt4OuHlntGSsUbwHLEYIOW2efqHa", 434 | "7090176": "196608:9/FNyxhhbJZtriO0/HMQGPPPXdsxDMYc0R2:RmxzbJZtO7jYPPtsxDMYc0R2", 435 | "7131136": "196608:Z9S5GO8o5HtaONjWnX/SsQgqk/NQ/rZ2eTjq2LmTNw:vMwo6ONjWKs1qk1Q/PjxqNw", 436 | "7172096": "196608:0Ffc/C7ch/vbdTqOGqYD7w7pIjfAPhv8ZKzg:gE/C7ch/vbdTZxI71qto", 437 | "7213056": "98304:vJgOMMYsQ5q9/a13WpeiHQs81lqjCQte5f8OB3KKcd2xv4qXGqHIblx4xZm/DQtU:ve5SR/asMsiImFUJN0vixDQtOKB9U", 438 | "7254016": "196608:PGHzAT7oRYapoe7dAY8ual8vbk92E+ujHx9dkQw1:PQzAT7o3poe+DEvbkeuzxPe1", 439 | "7294976": "196608:qYnY29+xztGbUg+yH9oKjsQjd39RI3L72NaZR7KNJcu:XnDo7GbUg+S/wQjd39RI+037qF", 440 | "7335936": "196608:Cnq2Uue5oSmHNek+2pXcAyxFWqW8zOdlUDY9fjhabiGnvFy:M5EoSm7+kXcAUqPVjwlnvFy", 441 | "7376896": "196608:wj7yBhoF6x8wYzcQN/CCLQ6+5KtfbjGJuJ:2yBE6GnzPIQNt2JuJ", 442 | "741376": "12288:1NY06IdQkjaS6sLVtf8FwlO0dA6wJDz1jiJXTSv3clk9K6zkx:1kNkl6sptkao0exX49xBv", 443 | "7417856": "196608:LtYXBcKhS1mvuOxZg2ntTKxNRYaopRtMbBoHUApbmH3reDA:LSXBc2SgWKD9KxNRIpRqbKHUObmXrd", 444 | "7458816": "196608:0nnNNfKpfs5KHdN9OpgxPCy32c0bxCbC7fx8yVBL:0NNfKps5K9KAPCcWA+rxfVBL", 445 | "7499776": "196608:c2eIQ8HF5z393YywCChfhST9I/kgxTdEefUpTiq2ljnIF:FeIlHF5tf5CbST9I/+MUpuq2lnm", 446 | "7540736": "196608:2po2mXCITqCc2le6WdKw5o8n37aa2uWUao:222mmU/W5b7vqo", 447 | "7581696": "196608:zu5ifoSLoqS24uuUswccYrT4nr4M2audHiKWHAzETx9lbK5D:qIwkolwVYrqr9ICLAzETx9lbKt", 448 | "7622656": "196608:8QLuZgpwqEI2O8Wrr6tZXPZc05S5jGpsfa0:7Ucwq5/rm/P2eVa/", 449 | "7663616": "196608:NYHW23bY8j8rZKQ8V4U/AijPpWisX223GxGoCI:aWaN+kjPANXXU", 450 | "7704576": "196608:qFAON0tmhShe8oZ/fkWmenCrIvCNU5GEhUlsQjMRLia2:9ON0twS08oZXkWXJCNU5GEhUuNRLg", 451 | "7745536": "196608:tf+7IYbRVn2meT2jhuRuykoeYRSeQ80FrI3mQHebyS9:hENVAT2Vubkp8SeQXVc6b5", 452 | "7786496": "196608:GaN+7V+njlYYlaebcjwkVU6RX2pskyCvCx5elq9ul9H:3NXGwYwkVjXqsk1vCHmV", 453 | "782336": "12288:T8JFm7kgheF75Y3uCY/YMaaP/TkKiC2KY02qsWEU6I5DyvKAsn77TkUbOyanakD7:ToUNeDqVK/dBnJsWVhDxn7X/d6r6I6a", 454 | "7827456": "196608:UjAv4kLm0qL2fJ8V5ugwrpslOTeaQi49DrV+oLur5:FwkLC2fJeurpslOiIBgO5", 455 | "7868416": "98304:b6VFjHt7TAm6Rmi2L1Y5SduRopk0pmFER3LAGkGxKnbI75btWClB4g44+ONChDiI:mFj18Ei865DGPjLdknqbcMCgFb3ctT", 456 | "7909376": "196608:T9Clhj4sLvfkGsI7N0VkNnOUdk9lOQlqJYlWkne:T9OB4DGsIwkBjdmplUYQB", 457 | "7950336": "196608:Y7Aqdh2PLbyaM3Mb3inbT7P72neoutrfkplu1c1mgDq9148:Nqdh2Pyp/nHn2nErkppVDq91P", 458 | "7991296": "196608:J7mfQNSRStNYLU6aVdkbVo7biQpB8CTT2Cw8DVpCJ376G0CINt7kZ9ns:JafJYWLGVdPaQRTTtwRx+fCeI1s", 459 | "8032256": "196608:ryxI0vu0BkEUqJ9QDfozDDxn+9V983sS4J24Lqo:ryPvuJfoVnKV63sO4eo", 460 | "8073216": "196608:fI/8umb6lJ/9AiEZeBYh9nhTDM1WHqMmbtPEJO7nK:fI/8um+lp9ApxDuumxPMenK", 461 | "8114176": "196608:32gITxvwBwpkK+yCLpyVWAO3w4MzAKjBllTpL:+XGJyCLpykAO3w4MzA6x", 462 | "8155136": "196608:0gCA7fQBvGWE5f1fPbt98W7VIfvXa7wgMosxGsdfNt:JCA7fufWbnxVP7Gopgj", 463 | "8196096": "196608:9RBkkI3wEGHYrpC5H/c5Lv2UaGPS4b0S9N4YJFfs:9RUjG4V0HENv2UFLcYns", 464 | "823296": "12288:Dymzfk8j06oxopAlSnuyTFmEyyqQUwxKP7xbGYghuzPAxBgbK3:DVkDZCXTFmzn//zPAQG3", 465 | "8237056": "196608:6qqCEGIZlYY/81aP7unWPcGxM7nvs/AWhbsYp+3yvshO:6j7ZMCqWkGm7nE1hvp+3yvsI", 466 | "8278016": "196608:12XWBJXRnDO5Z+BHIb/R+tukjC6ABmhq/g8vML4Dkqgswts:NBJBncZ+Bo7ULj/sI8rDDF", 467 | "8318976": "196608:QSzahwDSaCOTm1zsgN4v8Kqaf7q8Y6S89Yiybr0T1Pds5ssIyQiVgcyd9q:QKaq+5kM85fWRbQK6Nyc7q", 468 | "8359936": "196608:oyhTrZR5Z4YETiXTs722QgVPKO0EVr5vDy2aQ:o8R5EEt2Q0h1FDz", 469 | "8400896": "196608:VLcxu9mfhlTM88Q1LVE1T+PGQBNeTeSCgq1H8d7yColaxU/Tga:VLcxu9m488Q1iycTmb1HjColaxU/9", 470 | "8441856": "196608:ycNQgi6wlSZprCMCttYVNR5VNCCrCz0ZBHzYs2NgPjgJt:NxWvtt05VYg5BHEsRbE", 471 | "8482816": "196608:eEXtorGOgLROwT0nW7I3vcj0EeMIF/UQqLdW0:pXtorGOcRxuvDXU7Jj", 472 | "8523776": "196608:Gk3MAB8JCj7ewvwsk2MDsqpCfMVYGYgANv/1eCZjsnuqb/EMT6DqwOsrF8:HMAB8s/eeI2MvCkVfYgANv/1VsLb/EuT", 473 | "8564736": "196608:Ah/2KqCIkz+sL1+rgjmcWRp0+ZwDlxRF2500Lgx8n0:6/2KFI4+sL1+rKLPRF2500Lgxs0", 474 | "86016": "1536:Jdr3F6yZG0agLg/b6G6REjI+WUhWDKRSpzKjSUT4plmjvX6ex7RwdsHIGV:PrVbZG0BuuGzc+WcdRilmbPx7RwGV", 475 | "8605696": "196608:8VF9LnfPtuLDAG886NjgZHlo1NpE12ozZ8srvQQtrAMsAB2:29Tnu6aZy1N+122ZPrvQQtk3M2", 476 | "864256": "24576:Ixh3A9q3JY2WZNK0M7SAd4qsdc2uOiK94Xc:8W47WZLMpdrse2uugc", 477 | "8646656": "196608:iv2cc4TIJDmnw/QeaHHFMpDRQjJFhI33LpCzcIq:s2ecJDmnw/iSQj7hItqy", 478 | "8687616": "196608:8CGG/k/nlxSLwE33TdyAiYqInCyrz75NVM8+NPcORroc:y7/nUyALqCz6j", 479 | "8728576": "196608:3JSFiS+JsyEJmLpMMmq7MIuxAktSzTAefyP0UD:ZSysjJm7zG2z0Qq", 480 | "8769536": "196608:AMBzCLMpGD/zUilYJvWoadCk3F/L7IZgvhB6MmQeM8Y:tzQWGD/rlivC9hE31Y", 481 | "8810496": "196608:uaekAwyCf7rRlgHZviuvYbJmnwidO3wzjZ/LyORdnBUBK7T/:uaekNyCfpyR9gSlOgzjVxnBUiD", 482 | "8851456": "196608:AVsHsq1ZZIvx6wo9lJt1KY8wS/rGAzSb8tYbQ4Um:AVoIAwoXJDxSJz4k4Um", 483 | "8892416": "196608:IqpyOTrySESD5Zs2ro2tYxntoOLIDn/CU3izzygHCm/n0wV:I2NEeSP4cnvLm/CU3Mb70wV", 484 | "8933376": "196608:yNQCwQSfAaQMPorvaSzDb01V4Zsxj3CKQ2BKqGkVZuXHLj4D:yoAalIi+P2VIsxj3xLBKq3VZuXHLu", 485 | "8974336": "196608:Mt1uyZs0UdiJz89wQZgRd4YyTTb4LP3iDeFQC22odqnv5:sZ6wQK4YyTn4LP3TQbgh", 486 | "9015296": "196608:YYTFdBJ8oyL/auBUcP0zPdoi36ZpSZO+VvNz23TSdqCcZxZ4NOK:YwFdkL/uTdF6PsOiNzjncZHdK", 487 | "905216": "24576:2kC0QHPt57MLn4t8MDIdXowi4djVc+9Gd:2kCnHPtG4tpA3dS+9Gd", 488 | "9056256": "196608:Wc8mMip7HQMwY3QaHypf16eKtk08pXmkJYB3YW16:Wc8DWQ23Q9hkeKt38dJEY", 489 | "9097216": "196608:sekOdej4bXXtrS7q3usR1kjmoDA2RHM5RQPu8aJyfd5OqsWTo9rn4:L+Ydrkq3BsZLgE/WWTo9rn4", 490 | "9138176": "196608:FEUYKmqnJ9V1uN1k3ikBFtsW2jiKTlsBCr+MEEi1f05WTg:FEUisnLT3fBXzKT+cEElYg", 491 | "9179136": "196608:EatAJ8ZE7Py92GlrdI3+ZQOYyfL8tZQ0nrxhAbYAUjc0:VPYAVdQ+ZdYyfLcCYxjj", 492 | "9220096": "196608:3xX30PUceOyQgmLee7P51raBnqykQIVBbiluEKkFL4t8VAmjTNO4xcUoycsdy29:3xn0PUyy6hPkC+s0MwrjhOlmcsD", 493 | "9261056": "196608:1diyuxETykOw1Bi7Voz0MynLVL0Xbw6E53BQGn0kBiO+/hRlo:1kyuExOL7qmnLQbMLTGO+i", 494 | "9302016": "196608:Ju9HpHNsdPdHukERc3QUctB7VihKl6ph1gpbg2y7PRxMfm8582h:c9JePHuJ23QUmLl6ptJrMfma/h", 495 | "9342976": "196608:HA9aKUbjKY0NPLH7oOZw0A2vuWg7PZ6amLhYdx57tgTUVIYD:gsK2lIA+6z1AhqyYVJD", 496 | "9383936": "196608:im7KPyBu5zt42myq6HrgIApJran9RFDSyniPsY5hLYogAtH0S3G9:H7KqBcqF6HYpJ2xD+PXLfgUH0S29", 497 | "9424896": "196608:ZxPtYRjx4ZztCWs6shC3lkvefJNtX4cCPafrDWrscJ+yayVEJF/Ymx:XP6RjxsB2s3lkvefJNtI9QDqsY3EBYmx", 498 | "946176": "24576:65H9hyTFWPEx9W8huzxbQcSX4Xr30x8x+f8TKlAZ8eP5EJI:KdvEx9W8hutMoXr3c8ckmlg//", 499 | "9465856": "196608:j8ApTnBGrnFYPXeaASQVK1Yawnz5F2ezffJiRFW/w0LaDn8:j8Ap7wr+V4VkYVz26xOW/wEaD8", 500 | "9506816": "196608:/UxEX/ZoC47tsOwxEZ7mtIT5pA9uv/9+6AqhkOhpTbLkh0au/B:/3X/eCImxEmI929c9AqhZhpTboh0auZ", 501 | "9547776": "196608:e/7oSUDZ9IUlIXFUAgj5FzmAQp6jHv1lsiw5:e/7biRKFUHj5FhvAiq", 502 | "9588736": "196608:NmuiI5zmZHZzaA3VerNLtRntAqg/Op2x8M7OQLdoTLk:ViIFmZltVerNLtRn2qgGp2x8MzBoM", 503 | "9629696": "196608:I6EU9xRHGluq8PyD5/n44CnLIxiijdPC9HuudwHWyuJN:IhU9gF8aFP7HiUy74ON", 504 | "9670656": "196608:EKeEXaRxyyCRjy3ONv5PGkxoBcDEu2TwVU3H0Oc4wKqbmCopy7:EZEKSyCRjz/PGFBcEt8VW0ObwKqbmbC", 505 | "9711616": "196608:EeWt5juL2vOJraMQ/YztpvParJh/s3rErhwYPnL37AWLKjfCHp3DO:E0aLYzthCYrEr/P77LkfCJzO", 506 | "9752576": "196608:J+ncDYaOyG/kPl7pyNX82reAs1+CNwwMz3nba0AebgFsFUGzUY:kncDYGJtpKX8K4hEyeIsHx", 507 | "9793536": "196608:aqpK3u/jgv/dkhbzS8hMT+YwcFjZj5jfHJbcTeagIemOCV1+:aqpBgybiLZFjrzJbcTeagIB/s", 508 | "9834496": "196608:TP4XmJcL2W407yy0ahtRH3MHTBIIN7uzbVkgF915:TgXmyLj77r0ahtRH8NIXR1rz", 509 | "987136": "24576:kcyTcEzoJ5TjC0ZYD9NFysObMTlTaYrCVDNWFm:k+G0c9NFKbMhTatsc", 510 | "9875456": "196608:hjf/oEt+dnFh/kdeEJpXIb3wnnxYQxkgSVaSi+flZIWL000yM2Fk6b:hjnoEt+dDRoXIMxYQZSiilZIWg0u2Fp", 511 | "9916416": "196608:2IJABi/GVwcAq2qrYYaA26dpVUm9nfBGu5pNCPEB10coPVRyei+zHjLrB:/Ao/Gv3Y6Znku5nOEX0PPVRKgHXrB", 512 | "9957376": "196608:Ytzz2ddjO/uEnxyE/2uMY/keUUFWGI13Q1AjrHlDVVLMnwi3pkhGl0c7qsHDUMv:gAxOmExhRcexU13ughDWwi5khe7qO", 513 | "9998336": "196608:N41JLDGbSliGaKEwMD3ReHGMg3G1At8nQyktUgwFNZ1ys5teV5L1FAAkt+:N4LGRKw3RYoW1A+ltNZ1Ttezit+" 514 | } -------------------------------------------------------------------------------- /ssdeep_test.go: -------------------------------------------------------------------------------- 1 | package ssdeep 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "math/rand" 9 | "os" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestIntegrity(t *testing.T) { 16 | rand.Seed(1) 17 | 18 | fh, err := os.Open("ssdeep_results.json") 19 | require.NoError(t, err) 20 | 21 | originalResults := make(map[string]string) 22 | err = json.NewDecoder(fh).Decode(&originalResults) 23 | require.NoError(t, err) 24 | 25 | for i := 4097; i < 10*1024*1024; i += 4096 * 10 { 26 | t.Run(fmt.Sprintf("Bytes in size of %d", i), func(t *testing.T) { 27 | size := i 28 | if size == 4097 { 29 | i-- 30 | } 31 | blob := make([]byte, size) 32 | _, err = rand.Read(blob) 33 | require.NoError(t, err) 34 | result, err := FuzzyBytes(blob) 35 | require.NoError(t, err) 36 | require.Equal(t, originalResults[fmt.Sprint(size)], result) 37 | }) 38 | } 39 | } 40 | 41 | func concatCopyPreAllocate(slices [][]byte) []byte { 42 | var totalLen int 43 | for _, s := range slices { 44 | totalLen += len(s) 45 | } 46 | tmp := make([]byte, totalLen) 47 | var i int 48 | for _, s := range slices { 49 | i += copy(tmp[i:], s) 50 | } 51 | return tmp 52 | } 53 | 54 | func TestRollingHash(t *testing.T) { 55 | s := rollingState{} 56 | s.rollHash(byte('A')) 57 | rh := s.rollSum() 58 | require.Equal(t, rh, uint32(585), "Rolling hash not matching") 59 | } 60 | 61 | func TestFuzzyHashOutputsTheRightResult(t *testing.T) { 62 | fh, err := os.Open("LICENSE") 63 | require.NoError(t, err) 64 | b, err := io.ReadAll(fh) 65 | require.NoError(t, err) 66 | 67 | b = concatCopyPreAllocate([][]byte{b, b}) 68 | s := New() 69 | 70 | _, err = io.Copy(s, bytes.NewReader(b)) 71 | require.NoError(t, err) 72 | 73 | expectedResult := "96:PuNQHTo6pYrYJWrYJ6N3w53hpYTdhuNQHTo6pYrYJWrYJ6N3w53hpYTP:+QHTrpYrsWrs6N3g3LaGQHTrpYrsWrsa" 74 | prepend := []byte("prepend") 75 | 76 | sum := s.Sum(prepend) 77 | 78 | require.Equal(t, string(append(prepend, expectedResult...)), string(sum)) 79 | } 80 | 81 | func TestFuzzyBytesOutputsTheRightResult(t *testing.T) { 82 | fh, err := os.Open("LICENSE") 83 | require.NoError(t, err) 84 | b, err := io.ReadAll(fh) 85 | require.NoError(t, err) 86 | 87 | b = concatCopyPreAllocate([][]byte{b, b}) 88 | hashResult, err := FuzzyBytes(b) 89 | require.NoError(t, err) 90 | 91 | expectedResult := "96:PuNQHTo6pYrYJWrYJ6N3w53hpYTdhuNQHTo6pYrYJWrYJ6N3w53hpYTP:+QHTrpYrsWrs6N3g3LaGQHTrpYrsWrsa" 92 | require.Equal(t, expectedResult, hashResult) 93 | } 94 | 95 | func TestFuzzyFileOutputsTheRightResult(t *testing.T) { 96 | f, err := os.Open("ssdeep_results.json") 97 | require.NoError(t, err) 98 | defer f.Close() 99 | 100 | hashResult, err := FuzzyFile(f) 101 | require.NoError(t, err) 102 | 103 | expectedResult := "1536:74peLhFipssVfuInITTTZzMoW0379xy3u:VVFosEfudTj579k3u" 104 | require.Equal(t, expectedResult, hashResult) 105 | 106 | } 107 | 108 | func TestFuzzyFileOutputsAnErrorForSmallFiles(t *testing.T) { 109 | f, err := os.Open("LICENSE") 110 | require.NoError(t, err) 111 | defer f.Close() 112 | 113 | _, err = FuzzyFile(f) 114 | require.Error(t, err) 115 | } 116 | 117 | func TestFuzzyFilenameOutputsTheRightResult(t *testing.T) { 118 | hashResult, err := FuzzyFilename("ssdeep_results.json") 119 | require.NoError(t, err) 120 | 121 | expectedResult := "1536:74peLhFipssVfuInITTTZzMoW0379xy3u:VVFosEfudTj579k3u" 122 | require.Equal(t, expectedResult, hashResult) 123 | } 124 | 125 | func TestFuzzyFilenameOutputsErrorWhenFileNotExists(t *testing.T) { 126 | _, err := FuzzyFilename("foo.bar") 127 | require.Error(t, err) 128 | } 129 | 130 | func TestFuzzyBytesWithLenLessThanMinimumOutputsAnError(t *testing.T) { 131 | _, err := FuzzyBytes([]byte{}) 132 | require.Error(t, err) 133 | } 134 | 135 | func TestFuzzyBytesWithOutputsAnError(t *testing.T) { 136 | _, err := FuzzyBytes(make([]byte, 4096)) 137 | require.Error(t, err) 138 | } 139 | 140 | func BenchmarkRollingHash(b *testing.B) { 141 | s := newSSDEEPState() 142 | for i := 0; i < b.N; i++ { 143 | s.rollingState.rollHash(byte(i)) 144 | } 145 | } 146 | 147 | func BenchmarkSumHash(b *testing.B) { 148 | var testHash byte = hashInit 149 | data := []byte("Hereyougojustsomedatatomakeyouhappy") 150 | for i := 0; i < b.N; i++ { 151 | testHash = sumHash(data[rand.Intn(len(data))], testHash) 152 | } 153 | } 154 | 155 | func BenchmarkProcessByte(b *testing.B) { 156 | s := newSSDEEPState() 157 | for i := 0; i < b.N; i++ { 158 | s.processByte(byte(i)) 159 | } 160 | } 161 | --------------------------------------------------------------------------------