diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go
index d0fab3a8a..67e937efd 100644
--- a/cmd/syncthing/main.go
+++ b/cmd/syncthing/main.go
@@ -46,7 +46,6 @@ import (
"github.com/syncthing/syncthing/lib/sha256"
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/upgrade"
- "github.com/syncthing/syncthing/lib/weakhash"
"github.com/thejerf/suture"
@@ -697,26 +696,8 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
},
}
- if opts := cfg.Options(); opts.WeakHashSelectionMethod == config.WeakHashAuto {
- perfWithWeakHash := cpuBench(3, 150*time.Millisecond, true)
- l.Infof("Hashing performance with rolling hash is %.02f MB/s", perfWithWeakHash)
- perfWithoutWeakHash := cpuBench(3, 150*time.Millisecond, false)
- l.Infof("Hashing performance without rolling hash is %.02f MB/s", perfWithoutWeakHash)
-
- if perfWithoutWeakHash*0.8 > perfWithWeakHash {
- l.Infof("Rolling hash disabled, as it has an unacceptable performance impact.")
- weakhash.Enabled = false
- } else {
- l.Infof("Rolling hash enabled, as it has an acceptable performance impact.")
- weakhash.Enabled = true
- }
- } else if opts.WeakHashSelectionMethod == config.WeakHashNever {
- l.Infof("Disabling rolling hash")
- weakhash.Enabled = false
- } else if opts.WeakHashSelectionMethod == config.WeakHashAlways {
- l.Infof("Enabling rolling hash")
- weakhash.Enabled = true
- }
+ perf := cpuBench(3, 150*time.Millisecond, true)
+ l.Infof("Hashing performance is %.02f MB/s", perf)
dbFile := locations[locDatabase]
ldb, err := db.Open(dbFile)
diff --git a/cmd/syncthing/usage_report.go b/cmd/syncthing/usage_report.go
index 403dab39a..f7db1c362 100644
--- a/cmd/syncthing/usage_report.go
+++ b/cmd/syncthing/usage_report.go
@@ -26,7 +26,6 @@ import (
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/scanner"
"github.com/syncthing/syncthing/lib/upgrade"
- "github.com/syncthing/syncthing/lib/weakhash"
)
// Current version number of the usage report, for acceptance purposes. If
@@ -190,8 +189,6 @@ func reportData(cfg configIntf, m modelIntf, connectionsService connectionsIntf,
res["overwriteRemoteDeviceNames"] = opts.OverwriteRemoteDevNames
res["progressEmitterEnabled"] = opts.ProgressUpdateIntervalS > -1
res["customDefaultFolderPath"] = opts.DefaultFolderPath != "~"
- res["weakHashSelection"] = opts.WeakHashSelectionMethod.String()
- res["weakHashEnabled"] = weakhash.Enabled
res["customTrafficClass"] = opts.TrafficClass != 0
res["customTempIndexMinBlocks"] = opts.TempIndexMinBlocks != 10
res["temporariesDisabled"] = opts.KeepTemporariesH == 0
diff --git a/lib/config/config_test.go b/lib/config/config_test.go
index 0cf74bb21..74716692c 100644
--- a/lib/config/config_test.go
+++ b/lib/config/config_test.go
@@ -67,7 +67,6 @@ func TestDefaultValues(t *testing.T) {
OverwriteRemoteDevNames: false,
TempIndexMinBlocks: 10,
UnackedNotificationIDs: []string{},
- WeakHashSelectionMethod: WeakHashAuto,
DefaultFolderPath: "~",
SetLowPriority: true,
}
@@ -209,9 +208,8 @@ func TestOverriddenValues(t *testing.T) {
"channelNotification", // added in 17->18 migration
"fsWatcherNotification", // added in 27->28 migration
},
- WeakHashSelectionMethod: WeakHashNever,
- DefaultFolderPath: "/media/syncthing",
- SetLowPriority: false,
+ DefaultFolderPath: "/media/syncthing",
+ SetLowPriority: false,
}
os.Unsetenv("STNOUPGRADE")
diff --git a/lib/config/optionsconfiguration.go b/lib/config/optionsconfiguration.go
index 2f2e6197e..7c70fc94a 100644
--- a/lib/config/optionsconfiguration.go
+++ b/lib/config/optionsconfiguration.go
@@ -7,135 +7,50 @@
package config
import (
- "encoding/json"
- "encoding/xml"
"fmt"
"github.com/syncthing/syncthing/lib/util"
)
-type WeakHashSelectionMethod int
-
-const (
- WeakHashAuto WeakHashSelectionMethod = iota
- WeakHashAlways
- WeakHashNever
-)
-
-func (m WeakHashSelectionMethod) MarshalString() (string, error) {
- switch m {
- case WeakHashAuto:
- return "auto", nil
- case WeakHashAlways:
- return "always", nil
- case WeakHashNever:
- return "never", nil
- default:
- return "", fmt.Errorf("unrecognized hash selection method")
- }
-}
-
-func (m WeakHashSelectionMethod) String() string {
- s, err := m.MarshalString()
- if err != nil {
- panic(err)
- }
- return s
-}
-
-func (m *WeakHashSelectionMethod) UnmarshalString(value string) error {
- switch value {
- case "auto":
- *m = WeakHashAuto
- return nil
- case "always":
- *m = WeakHashAlways
- return nil
- case "never":
- *m = WeakHashNever
- return nil
- }
- return fmt.Errorf("unrecognized hash selection method")
-}
-
-func (m WeakHashSelectionMethod) MarshalJSON() ([]byte, error) {
- val, err := m.MarshalString()
- if err != nil {
- return nil, err
- }
- return json.Marshal(val)
-}
-
-func (m *WeakHashSelectionMethod) UnmarshalJSON(data []byte) error {
- var value string
- if err := json.Unmarshal(data, &value); err != nil {
- return err
- }
- return m.UnmarshalString(value)
-}
-
-func (m WeakHashSelectionMethod) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
- val, err := m.MarshalString()
- if err != nil {
- return err
- }
- return e.EncodeElement(val, start)
-}
-
-func (m *WeakHashSelectionMethod) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
- var value string
- if err := d.DecodeElement(&value, &start); err != nil {
- return err
- }
- return m.UnmarshalString(value)
-}
-
-func (WeakHashSelectionMethod) ParseDefault(value string) (interface{}, error) {
- var m WeakHashSelectionMethod
- err := m.UnmarshalString(value)
- return m, err
-}
-
type OptionsConfiguration struct {
- ListenAddresses []string `xml:"listenAddress" json:"listenAddresses" default:"default"`
- GlobalAnnServers []string `xml:"globalAnnounceServer" json:"globalAnnounceServers" json:"globalAnnounceServer" default:"default" restart:"true"`
- GlobalAnnEnabled bool `xml:"globalAnnounceEnabled" json:"globalAnnounceEnabled" default:"true" restart:"true"`
- LocalAnnEnabled bool `xml:"localAnnounceEnabled" json:"localAnnounceEnabled" default:"true" restart:"true"`
- LocalAnnPort int `xml:"localAnnouncePort" json:"localAnnouncePort" default:"21027" restart:"true"`
- LocalAnnMCAddr string `xml:"localAnnounceMCAddr" json:"localAnnounceMCAddr" default:"[ff12::8384]:21027" restart:"true"`
- MaxSendKbps int `xml:"maxSendKbps" json:"maxSendKbps"`
- MaxRecvKbps int `xml:"maxRecvKbps" json:"maxRecvKbps"`
- ReconnectIntervalS int `xml:"reconnectionIntervalS" json:"reconnectionIntervalS" default:"60"`
- RelaysEnabled bool `xml:"relaysEnabled" json:"relaysEnabled" default:"true"`
- RelayReconnectIntervalM int `xml:"relayReconnectIntervalM" json:"relayReconnectIntervalM" default:"10"`
- StartBrowser bool `xml:"startBrowser" json:"startBrowser" default:"true"`
- NATEnabled bool `xml:"natEnabled" json:"natEnabled" default:"true"`
- NATLeaseM int `xml:"natLeaseMinutes" json:"natLeaseMinutes" default:"60"`
- NATRenewalM int `xml:"natRenewalMinutes" json:"natRenewalMinutes" default:"30"`
- NATTimeoutS int `xml:"natTimeoutSeconds" json:"natTimeoutSeconds" default:"10"`
- URAccepted int `xml:"urAccepted" json:"urAccepted"` // Accepted usage reporting version; 0 for off (undecided), -1 for off (permanently)
- URSeen int `xml:"urSeen" json:"urSeen"` // Report which the user has been prompted for.
- URUniqueID string `xml:"urUniqueID" json:"urUniqueId"` // Unique ID for reporting purposes, regenerated when UR is turned on.
- URURL string `xml:"urURL" json:"urURL" default:"https://data.syncthing.net/newdata"`
- URPostInsecurely bool `xml:"urPostInsecurely" json:"urPostInsecurely" default:"false"` // For testing
- URInitialDelayS int `xml:"urInitialDelayS" json:"urInitialDelayS" default:"1800"`
- RestartOnWakeup bool `xml:"restartOnWakeup" json:"restartOnWakeup" default:"true" restart:"true"`
- AutoUpgradeIntervalH int `xml:"autoUpgradeIntervalH" json:"autoUpgradeIntervalH" default:"12" restart:"true"` // 0 for off
- UpgradeToPreReleases bool `xml:"upgradeToPreReleases" json:"upgradeToPreReleases" restart:"true"` // when auto upgrades are enabled
- KeepTemporariesH int `xml:"keepTemporariesH" json:"keepTemporariesH" default:"24"` // 0 for off
- CacheIgnoredFiles bool `xml:"cacheIgnoredFiles" json:"cacheIgnoredFiles" default:"false" restart:"true"`
- ProgressUpdateIntervalS int `xml:"progressUpdateIntervalS" json:"progressUpdateIntervalS" default:"5"`
- LimitBandwidthInLan bool `xml:"limitBandwidthInLan" json:"limitBandwidthInLan" default:"false"`
- MinHomeDiskFree Size `xml:"minHomeDiskFree" json:"minHomeDiskFree" default:"1 %"`
- ReleasesURL string `xml:"releasesURL" json:"releasesURL" default:"https://upgrades.syncthing.net/meta.json" restart:"true"`
- AlwaysLocalNets []string `xml:"alwaysLocalNet" json:"alwaysLocalNets"`
- OverwriteRemoteDevNames bool `xml:"overwriteRemoteDeviceNamesOnConnect" json:"overwriteRemoteDeviceNamesOnConnect" default:"false"`
- TempIndexMinBlocks int `xml:"tempIndexMinBlocks" json:"tempIndexMinBlocks" default:"10"`
- UnackedNotificationIDs []string `xml:"unackedNotificationID" json:"unackedNotificationIDs"`
- TrafficClass int `xml:"trafficClass" json:"trafficClass"`
- WeakHashSelectionMethod WeakHashSelectionMethod `xml:"weakHashSelectionMethod" json:"weakHashSelectionMethod" restart:"true"`
- DefaultFolderPath string `xml:"defaultFolderPath" json:"defaultFolderPath" default:"~"`
- SetLowPriority bool `xml:"setLowPriority" json:"setLowPriority" default:"true"`
+ ListenAddresses []string `xml:"listenAddress" json:"listenAddresses" default:"default"`
+ GlobalAnnServers []string `xml:"globalAnnounceServer" json:"globalAnnounceServers" json:"globalAnnounceServer" default:"default" restart:"true"`
+ GlobalAnnEnabled bool `xml:"globalAnnounceEnabled" json:"globalAnnounceEnabled" default:"true" restart:"true"`
+ LocalAnnEnabled bool `xml:"localAnnounceEnabled" json:"localAnnounceEnabled" default:"true" restart:"true"`
+ LocalAnnPort int `xml:"localAnnouncePort" json:"localAnnouncePort" default:"21027" restart:"true"`
+ LocalAnnMCAddr string `xml:"localAnnounceMCAddr" json:"localAnnounceMCAddr" default:"[ff12::8384]:21027" restart:"true"`
+ MaxSendKbps int `xml:"maxSendKbps" json:"maxSendKbps"`
+ MaxRecvKbps int `xml:"maxRecvKbps" json:"maxRecvKbps"`
+ ReconnectIntervalS int `xml:"reconnectionIntervalS" json:"reconnectionIntervalS" default:"60"`
+ RelaysEnabled bool `xml:"relaysEnabled" json:"relaysEnabled" default:"true"`
+ RelayReconnectIntervalM int `xml:"relayReconnectIntervalM" json:"relayReconnectIntervalM" default:"10"`
+ StartBrowser bool `xml:"startBrowser" json:"startBrowser" default:"true"`
+ NATEnabled bool `xml:"natEnabled" json:"natEnabled" default:"true"`
+ NATLeaseM int `xml:"natLeaseMinutes" json:"natLeaseMinutes" default:"60"`
+ NATRenewalM int `xml:"natRenewalMinutes" json:"natRenewalMinutes" default:"30"`
+ NATTimeoutS int `xml:"natTimeoutSeconds" json:"natTimeoutSeconds" default:"10"`
+ URAccepted int `xml:"urAccepted" json:"urAccepted"` // Accepted usage reporting version; 0 for off (undecided), -1 for off (permanently)
+ URSeen int `xml:"urSeen" json:"urSeen"` // Report which the user has been prompted for.
+ URUniqueID string `xml:"urUniqueID" json:"urUniqueId"` // Unique ID for reporting purposes, regenerated when UR is turned on.
+ URURL string `xml:"urURL" json:"urURL" default:"https://data.syncthing.net/newdata"`
+ URPostInsecurely bool `xml:"urPostInsecurely" json:"urPostInsecurely" default:"false"` // For testing
+ URInitialDelayS int `xml:"urInitialDelayS" json:"urInitialDelayS" default:"1800"`
+ RestartOnWakeup bool `xml:"restartOnWakeup" json:"restartOnWakeup" default:"true" restart:"true"`
+ AutoUpgradeIntervalH int `xml:"autoUpgradeIntervalH" json:"autoUpgradeIntervalH" default:"12" restart:"true"` // 0 for off
+ UpgradeToPreReleases bool `xml:"upgradeToPreReleases" json:"upgradeToPreReleases" restart:"true"` // when auto upgrades are enabled
+ KeepTemporariesH int `xml:"keepTemporariesH" json:"keepTemporariesH" default:"24"` // 0 for off
+ CacheIgnoredFiles bool `xml:"cacheIgnoredFiles" json:"cacheIgnoredFiles" default:"false" restart:"true"`
+ ProgressUpdateIntervalS int `xml:"progressUpdateIntervalS" json:"progressUpdateIntervalS" default:"5"`
+ LimitBandwidthInLan bool `xml:"limitBandwidthInLan" json:"limitBandwidthInLan" default:"false"`
+ MinHomeDiskFree Size `xml:"minHomeDiskFree" json:"minHomeDiskFree" default:"1 %"`
+ ReleasesURL string `xml:"releasesURL" json:"releasesURL" default:"https://upgrades.syncthing.net/meta.json" restart:"true"`
+ AlwaysLocalNets []string `xml:"alwaysLocalNet" json:"alwaysLocalNets"`
+ OverwriteRemoteDevNames bool `xml:"overwriteRemoteDeviceNamesOnConnect" json:"overwriteRemoteDeviceNamesOnConnect" default:"false"`
+ TempIndexMinBlocks int `xml:"tempIndexMinBlocks" json:"tempIndexMinBlocks" default:"10"`
+ UnackedNotificationIDs []string `xml:"unackedNotificationID" json:"unackedNotificationIDs"`
+ TrafficClass int `xml:"trafficClass" json:"trafficClass"`
+ DefaultFolderPath string `xml:"defaultFolderPath" json:"defaultFolderPath" default:"~"`
+ SetLowPriority bool `xml:"setLowPriority" json:"setLowPriority" default:"true"`
DeprecatedUPnPEnabled bool `xml:"upnpEnabled,omitempty" json:"-"`
DeprecatedUPnPLeaseM int `xml:"upnpLeaseMinutes,omitempty" json:"-"`
diff --git a/lib/config/testdata/overridenvalues.xml b/lib/config/testdata/overridenvalues.xml
index 84cde1b4e..315f464a1 100644
--- a/lib/config/testdata/overridenvalues.xml
+++ b/lib/config/testdata/overridenvalues.xml
@@ -34,7 +34,6 @@
https://localhost/releases
true
100
- never
/media/syncthing
false
diff --git a/lib/db/blockmap.go b/lib/db/blockmap.go
index cd28eedf8..7e9f5f563 100644
--- a/lib/db/blockmap.go
+++ b/lib/db/blockmap.go
@@ -182,19 +182,6 @@ func (f *BlockFinder) Iterate(folders []string, hash []byte, iterFn func(string,
return false
}
-// Fix repairs incorrect blockmap entries, removing the old entry and
-// replacing it with a new entry for the given block
-func (f *BlockFinder) Fix(folder, file string, index int32, oldHash, newHash []byte) error {
- buf := make([]byte, 4)
- binary.BigEndian.PutUint32(buf, uint32(index))
-
- folderID := f.db.folderIdx.ID([]byte(folder))
- batch := new(leveldb.Batch)
- batch.Delete(blockKeyInto(nil, oldHash, folderID, file))
- batch.Put(blockKeyInto(nil, newHash, folderID, file), buf)
- return f.db.Write(batch, nil)
-}
-
// m.blockKey returns a byte slice encoding the following information:
// keyTypeBlock (1 byte)
// folder (4 bytes)
diff --git a/lib/db/blockmap_test.go b/lib/db/blockmap_test.go
index 735ff3ae9..daa668d9f 100644
--- a/lib/db/blockmap_test.go
+++ b/lib/db/blockmap_test.go
@@ -219,34 +219,3 @@ func TestBlockFinderLookup(t *testing.T) {
f1.Deleted = false
}
-
-func TestBlockFinderFix(t *testing.T) {
- db, f := setup()
-
- iterFn := func(folder, file string, index int32) bool {
- return true
- }
-
- m := NewBlockMap(db, db.folderIdx.ID([]byte("folder1")))
- err := m.Add([]protocol.FileInfo{f1})
- if err != nil {
- t.Fatal(err)
- }
-
- if !f.Iterate(folders, f1.Blocks[0].Hash, iterFn) {
- t.Fatal("Block not found")
- }
-
- err = f.Fix("folder1", f1.Name, 0, f1.Blocks[0].Hash, f2.Blocks[0].Hash)
- if err != nil {
- t.Fatal(err)
- }
-
- if f.Iterate(folders, f1.Blocks[0].Hash, iterFn) {
- t.Fatal("Unexpected block")
- }
-
- if !f.Iterate(folders, f2.Blocks[0].Hash, iterFn) {
- t.Fatal("Block not found")
- }
-}
diff --git a/lib/model/model.go b/lib/model/model.go
index a6d232a8b..ab043aa7d 100644
--- a/lib/model/model.go
+++ b/lib/model/model.go
@@ -7,6 +7,7 @@
package model
import (
+ "bytes"
"context"
"crypto/tls"
"encoding/json"
@@ -34,7 +35,6 @@ import (
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/upgrade"
"github.com/syncthing/syncthing/lib/versioner"
- "github.com/syncthing/syncthing/lib/weakhash"
"github.com/thejerf/suture"
)
@@ -1304,7 +1304,7 @@ func (m *Model) closeLocked(device protocol.DeviceID) {
// Request returns the specified data segment by reading it from local disk.
// Implements the protocol.Model interface.
-func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, offset int64, hash []byte, fromTemporary bool, buf []byte) error {
+func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, offset int64, hash []byte, weakHash uint32, fromTemporary bool, buf []byte) error {
if offset < 0 {
return protocol.ErrInvalid
}
@@ -1362,8 +1362,8 @@ func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, offset
// other than a regular file.
return protocol.ErrNoSuchFile
}
-
- if err := readOffsetIntoBuf(folderFs, tempFn, offset, buf); err == nil {
+ err := readOffsetIntoBuf(folderFs, tempFn, offset, buf)
+ if err == nil && scanner.Validate(buf, hash, weakHash) {
return nil
}
// Fall through to reading from a non-temp file, just incase the temp
@@ -1382,9 +1382,55 @@ func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, offset
return protocol.ErrGeneric
}
+ if !scanner.Validate(buf, hash, weakHash) {
+ m.recheckFile(deviceID, folderFs, folder, name, int(offset)/len(buf), hash)
+ return protocol.ErrNoSuchFile
+ }
+
return nil
}
+func (m *Model) recheckFile(deviceID protocol.DeviceID, folderFs fs.Filesystem, folder, name string, blockIndex int, hash []byte) {
+ cf, ok := m.CurrentFolderFile(folder, name)
+ if !ok {
+ l.Debugf("%v recheckFile: %s: %q / %q: no current file", m, deviceID, folder, name)
+ return
+ }
+
+ if cf.IsDeleted() || cf.IsInvalid() || cf.IsSymlink() || cf.IsDirectory() {
+ l.Debugf("%v recheckFile: %s: %q / %q: not a regular file", m, deviceID, folder, name)
+ return
+ }
+
+ if blockIndex > len(cf.Blocks) {
+ l.Debugf("%v recheckFile: %s: %q / %q i=%d: block index too far", m, deviceID, folder, name, blockIndex)
+ return
+ }
+
+ block := cf.Blocks[blockIndex]
+
+ // Seems to want a different version of the file, whatever.
+ if !bytes.Equal(block.Hash, hash) {
+ l.Debugf("%v recheckFile: %s: %q / %q i=%d: hash mismatch %x != %x", m, deviceID, folder, name, blockIndex, block.Hash, hash)
+ return
+ }
+
+ // The hashes provided part of the request match what we expect to find according
+ // to what we have in the database, yet the content we've read off the filesystem doesn't
+ // Something is fishy, invalidate the file and rescan it.
+ cf.Invalidate(m.shortID)
+
+ // Update the index and tell others
+ // The file will temporarily become invalid, which is ok as the content is messed up.
+ m.updateLocalsFromScanning(folder, []protocol.FileInfo{cf})
+
+ if err := m.ScanFolderSubdirs(folder, []string{name}); err != nil {
+ l.Debugf("%v recheckFile: %s: %q / %q rescan: %s", m, deviceID, folder, name, err)
+ } else {
+ l.Debugf("%v recheckFile: %s: %q / %q", m, deviceID, folder, name)
+ }
+}
+
func (m *Model) CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool) {
m.fmut.RLock()
fs, ok := m.folderFiles[folder]
@@ -1836,7 +1882,7 @@ func (m *Model) diskChangeDetected(folderCfg config.FolderConfiguration, files [
}
}
-func (m *Model) requestGlobal(deviceID protocol.DeviceID, folder, name string, offset int64, size int, hash []byte, fromTemporary bool) ([]byte, error) {
+func (m *Model) requestGlobal(deviceID protocol.DeviceID, folder, name string, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
m.pmut.RLock()
nc, ok := m.conn[deviceID]
m.pmut.RUnlock()
@@ -1845,9 +1891,9 @@ func (m *Model) requestGlobal(deviceID protocol.DeviceID, folder, name string, o
return nil, fmt.Errorf("requestGlobal: no such device: %s", deviceID)
}
- l.Debugf("%v REQ(out): %s: %q / %q o=%d s=%d h=%x ft=%t", m, deviceID, folder, name, offset, size, hash, fromTemporary)
+ l.Debugf("%v REQ(out): %s: %q / %q o=%d s=%d h=%x wh=%x ft=%t", m, deviceID, folder, name, offset, size, hash, weakHash, fromTemporary)
- return nc.Request(folder, name, offset, size, hash, fromTemporary)
+ return nc.Request(folder, name, offset, size, hash, weakHash, fromTemporary)
}
func (m *Model) ScanFolders() map[string]error {
@@ -1973,7 +2019,6 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
Hashers: m.numHashers(folder),
ShortID: m.shortID,
ProgressTickIntervalS: folderCfg.ScanProgressIntervalS,
- UseWeakHashes: weakhash.Enabled,
UseLargeBlocks: folderCfg.UseLargeBlocks,
})
diff --git a/lib/model/model_test.go b/lib/model/model_test.go
index e21158fe9..749756a28 100644
--- a/lib/model/model_test.go
+++ b/lib/model/model_test.go
@@ -181,7 +181,7 @@ func TestRequest(t *testing.T) {
// Existing, shared file
bs = bs[:6]
- err := m.Request(device1, "default", "foo", 0, nil, false, bs)
+ err := m.Request(device1, "default", "foo", 0, nil, 0, false, bs)
if err != nil {
t.Error(err)
}
@@ -190,32 +190,32 @@ func TestRequest(t *testing.T) {
}
// Existing, nonshared file
- err = m.Request(device2, "default", "foo", 0, nil, false, bs)
+ err = m.Request(device2, "default", "foo", 0, nil, 0, false, bs)
if err == nil {
t.Error("Unexpected nil error on insecure file read")
}
// Nonexistent file
- err = m.Request(device1, "default", "nonexistent", 0, nil, false, bs)
+ err = m.Request(device1, "default", "nonexistent", 0, nil, 0, false, bs)
if err == nil {
t.Error("Unexpected nil error on insecure file read")
}
// Shared folder, but disallowed file name
- err = m.Request(device1, "default", "../walk.go", 0, nil, false, bs)
+ err = m.Request(device1, "default", "../walk.go", 0, nil, 0, false, bs)
if err == nil {
t.Error("Unexpected nil error on insecure file read")
}
// Negative offset
- err = m.Request(device1, "default", "foo", -4, nil, false, bs[:0])
+ err = m.Request(device1, "default", "foo", -4, nil, 0, false, bs[:0])
if err == nil {
t.Error("Unexpected nil error on insecure file read")
}
// Larger block than available
bs = bs[:42]
- err = m.Request(device1, "default", "foo", 0, nil, false, bs)
+ err = m.Request(device1, "default", "foo", 0, nil, 0, false, bs)
if err == nil {
t.Error("Unexpected nil error on insecure file read")
}
@@ -357,7 +357,7 @@ func (f *fakeConnection) IndexUpdate(folder string, fs []protocol.FileInfo) erro
return nil
}
-func (f *fakeConnection) Request(folder, name string, offset int64, size int, hash []byte, fromTemporary bool) ([]byte, error) {
+func (f *fakeConnection) Request(folder, name string, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
f.mut.Lock()
defer f.mut.Unlock()
if f.requestFn != nil {
@@ -485,7 +485,7 @@ func BenchmarkRequestOut(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- data, err := m.requestGlobal(device1, "default", files[i%n].Name, 0, 32, nil, false)
+ data, err := m.requestGlobal(device1, "default", files[i%n].Name, 0, 32, nil, 0, false)
if err != nil {
b.Error(err)
}
@@ -513,7 +513,7 @@ func BenchmarkRequestInSingleFile(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- if err := m.Request(device1, "default", "request/for/a/file/in/a/couple/of/dirs/128k", 0, nil, false, buf); err != nil {
+ if err := m.Request(device1, "default", "request/for/a/file/in/a/couple/of/dirs/128k", 0, nil, 0, false, buf); err != nil {
b.Error(err)
}
}
diff --git a/lib/model/requests_test.go b/lib/model/requests_test.go
index 0a79ad547..4c1514daf 100644
--- a/lib/model/requests_test.go
+++ b/lib/model/requests_test.go
@@ -93,7 +93,7 @@ func TestSymlinkTraversalRead(t *testing.T) {
// Request a file by traversing the symlink
buf := make([]byte, 10)
- err := m.Request(device1, "default", "symlink/requests_test.go", 0, nil, false, buf)
+ err := m.Request(device1, "default", "symlink/requests_test.go", 0, nil, 0, false, buf)
if err == nil || !bytes.Equal(buf, make([]byte, 10)) {
t.Error("Managed to traverse symlink")
}
@@ -464,6 +464,73 @@ func TestIssue4841(t *testing.T) {
}
}
+func TestRescanIfHaveInvalidContent(t *testing.T) {
+ m, fc, tmpDir := setupModelWithConnection()
+ defer m.Stop()
+ defer os.RemoveAll(tmpDir)
+
+ payload := []byte("hello")
+
+ if err := ioutil.WriteFile(filepath.Join(tmpDir, "foo"), payload, 0777); err != nil {
+ t.Fatal(err)
+ }
+
+ received := make(chan protocol.FileInfo)
+ fc.mut.Lock()
+ fc.indexFn = func(folder string, fs []protocol.FileInfo) {
+ if len(fs) != 1 {
+ t.Fatalf("Sent index with %d files, should be 1", len(fs))
+ }
+ if fs[0].Name != "foo" {
+ t.Fatalf(`Sent index with file %v, should be "foo"`, fs[0].Name)
+ }
+ received <- fs[0]
+ return
+ }
+ fc.mut.Unlock()
+
+ // Scan without ignore patterns with "foo" not existing locally
+ if err := m.ScanFolder("default"); err != nil {
+ t.Fatal("Failed scanning:", err)
+ }
+
+ f := <-received
+ if f.Blocks[0].WeakHash != 103547413 {
+ t.Fatalf("unexpected weak hash: %d != 103547413", f.Blocks[0].WeakHash)
+ }
+
+ buf := make([]byte, len(payload))
+
+ err := m.Request(device2, "default", "foo", 0, f.Blocks[0].Hash, f.Blocks[0].WeakHash, false, buf)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(buf, payload) {
+ t.Errorf("%s != %s", buf, payload)
+ }
+
+ payload = []byte("bye")
+ buf = make([]byte, len(payload))
+
+ if err := ioutil.WriteFile(filepath.Join(tmpDir, "foo"), payload, 0777); err != nil {
+ t.Fatal(err)
+ }
+
+ err = m.Request(device2, "default", "foo", 0, f.Blocks[0].Hash, f.Blocks[0].WeakHash, false, buf)
+ if err == nil {
+ t.Fatalf("expected failure")
+ }
+
+ select {
+ case f := <-received:
+ if f.Blocks[0].WeakHash != 41943361 {
+ t.Fatalf("unexpected weak hash: %d != 41943361", f.Blocks[0].WeakHash)
+ }
+ case <-time.After(time.Second):
+ t.Fatalf("timed out")
+ }
+}
+
func setupModelWithConnection() (*Model, *fakeConnection, string) {
tmpDir := createTmpDir()
cfg := defaultCfgWrapper.RawCopy()
diff --git a/lib/model/rwfolder.go b/lib/model/rwfolder.go
index b14ba2128..8007222ce 100644
--- a/lib/model/rwfolder.go
+++ b/lib/model/rwfolder.go
@@ -1186,36 +1186,32 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
var file fs.File
var weakHashFinder *weakhash.Finder
- if weakhash.Enabled {
- blocksPercentChanged := 0
- if tot := len(state.file.Blocks); tot > 0 {
- blocksPercentChanged = (tot - state.have) * 100 / tot
+ blocksPercentChanged := 0
+ if tot := len(state.file.Blocks); tot > 0 {
+ blocksPercentChanged = (tot - state.have) * 100 / tot
+ }
+
+ if blocksPercentChanged >= f.WeakHashThresholdPct {
+ hashesToFind := make([]uint32, 0, len(state.blocks))
+ for _, block := range state.blocks {
+ if block.WeakHash != 0 {
+ hashesToFind = append(hashesToFind, block.WeakHash)
+ }
}
- if blocksPercentChanged >= f.WeakHashThresholdPct {
- hashesToFind := make([]uint32, 0, len(state.blocks))
- for _, block := range state.blocks {
- if block.WeakHash != 0 {
- hashesToFind = append(hashesToFind, block.WeakHash)
+ if len(hashesToFind) > 0 {
+ file, err = f.fs.Open(state.file.Name)
+ if err == nil {
+ weakHashFinder, err = weakhash.NewFinder(file, int(state.file.BlockSize()), hashesToFind)
+ if err != nil {
+ l.Debugln("weak hasher", err)
}
}
-
- if len(hashesToFind) > 0 {
- file, err = f.fs.Open(state.file.Name)
- if err == nil {
- weakHashFinder, err = weakhash.NewFinder(file, int(state.file.BlockSize()), hashesToFind)
- if err != nil {
- l.Debugln("weak hasher", err)
- }
- }
- } else {
- l.Debugf("not weak hashing %s. file did not contain any weak hashes", state.file.Name)
- }
} else {
- l.Debugf("not weak hashing %s. not enough changed %.02f < %d", state.file.Name, blocksPercentChanged, f.WeakHashThresholdPct)
+ l.Debugf("not weak hashing %s. file did not contain any weak hashes", state.file.Name)
}
} else {
- l.Debugf("not weak hashing %s. weak hashing disabled", state.file.Name)
+ l.Debugf("not weak hashing %s. not enough changed %.02f < %d", state.file.Name, blocksPercentChanged, f.WeakHashThresholdPct)
}
for _, block := range state.blocks {
@@ -1239,7 +1235,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
}
found, err := weakHashFinder.Iterate(block.WeakHash, buf, func(offset int64) bool {
- if _, err := verifyBuffer(buf, block); err != nil {
+ if verifyBuffer(buf, block) != nil {
return true
}
@@ -1274,17 +1270,8 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
return false
}
- hash, err := verifyBuffer(buf, block)
- if err != nil {
- if hash != nil {
- l.Debugf("Finder block mismatch in %s:%s:%d expected %q got %q", folder, path, index, block.Hash, hash)
- err = f.model.finder.Fix(folder, path, index, block.Hash, hash)
- if err != nil {
- l.Warnln("finder fix:", err)
- }
- } else {
- l.Debugln("Finder failed to verify buffer", err)
- }
+ if err := verifyBuffer(buf, block); err != nil {
+ l.Debugln("Finder failed to verify buffer", err)
return false
}
@@ -1324,22 +1311,22 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
}
}
-func verifyBuffer(buf []byte, block protocol.BlockInfo) ([]byte, error) {
+func verifyBuffer(buf []byte, block protocol.BlockInfo) error {
if len(buf) != int(block.Size) {
- return nil, fmt.Errorf("length mismatch %d != %d", len(buf), block.Size)
+ return fmt.Errorf("length mismatch %d != %d", len(buf), block.Size)
}
hf := sha256.New()
_, err := hf.Write(buf)
if err != nil {
- return nil, err
+ return err
}
hash := hf.Sum(nil)
if !bytes.Equal(hash, block.Hash) {
- return hash, fmt.Errorf("hash mismatch %x != %x", hash, block.Hash)
+ return fmt.Errorf("hash mismatch %x != %x", hash, block.Hash)
}
- return hash, nil
+ return nil
}
func (f *sendReceiveFolder) pullerRoutine(in <-chan pullBlockState, out chan<- *sharedPullerState) {
@@ -1411,7 +1398,7 @@ func (f *sendReceiveFolder) pullBlock(state pullBlockState, out chan<- *sharedPu
// Fetch the block, while marking the selected device as in use so that
// leastBusy can select another device when someone else asks.
activity.using(selected)
- buf, lastError := f.model.requestGlobal(selected.ID, f.folderID, state.file.Name, state.block.Offset, int(state.block.Size), state.block.Hash, selected.FromTemporary)
+ buf, lastError := f.model.requestGlobal(selected.ID, f.folderID, state.file.Name, state.block.Offset, int(state.block.Size), state.block.Hash, state.block.WeakHash, selected.FromTemporary)
activity.done(selected)
if lastError != nil {
l.Debugln("request:", f.folderID, state.file.Name, state.block.Offset, state.block.Size, "returned error:", lastError)
@@ -1420,7 +1407,7 @@ func (f *sendReceiveFolder) pullBlock(state pullBlockState, out chan<- *sharedPu
// Verify that the received block matches the desired hash, if not
// try pulling it from another device.
- _, lastError = verifyBuffer(buf, state.block)
+ lastError = verifyBuffer(buf, state.block)
if lastError != nil {
l.Debugln("request:", f.folderID, state.file.Name, state.block.Offset, state.block.Size, "hash mismatch")
continue
diff --git a/lib/model/rwfolder_test.go b/lib/model/rwfolder_test.go
index e555a5855..5d7bfa300 100644
--- a/lib/model/rwfolder_test.go
+++ b/lib/model/rwfolder_test.go
@@ -434,52 +434,6 @@ func TestCopierCleanup(t *testing.T) {
}
}
-// Make sure that the copier routine hashes the content when asked, and pulls
-// if it fails to find the block.
-func TestLastResortPulling(t *testing.T) {
- // Add a file to index (with the incorrect block representation, as content
- // doesn't actually match the block list)
- file := setUpFile("empty", []int{0})
- m := setUpModel(file)
-
- // Pretend that we are handling a new file of the same content but
- // with a different name (causing to copy that particular block)
- file.Name = "newfile"
-
- iterFn := func(folder, file string, index int32) bool {
- return true
- }
-
- f := setUpSendReceiveFolder(m)
-
- copyChan := make(chan copyBlocksState)
- pullChan := make(chan pullBlockState, 1)
- finisherChan := make(chan *sharedPullerState, 1)
- dbUpdateChan := make(chan dbUpdateJob, 1)
-
- // Run a single copier routine
- go f.copierRoutine(copyChan, pullChan, finisherChan)
-
- f.handleFile(file, copyChan, finisherChan, dbUpdateChan)
-
- // Copier should hash empty file, realise that the region it has read
- // doesn't match the hash which was advertised by the block map, fix it
- // and ask to pull the block.
- <-pullChan
-
- // Verify that it did fix the incorrect hash.
- if m.finder.Iterate(folders, blocks[0].Hash, iterFn) {
- t.Error("Found unexpected block")
- }
-
- if !m.finder.Iterate(folders, scanner.SHA256OfNothing, iterFn) {
- t.Error("Expected block not found")
- }
-
- (<-finisherChan).fd.Close()
- os.Remove(filepath.Join("testdata", fs.TempName("newfile")))
-}
-
func TestDeregisterOnFailInCopy(t *testing.T) {
file := setUpFile("filex", []int{0, 2, 0, 0, 5, 0, 0, 8})
defer os.Remove("testdata/" + fs.TempName("filex"))
diff --git a/lib/protocol/benchmark_test.go b/lib/protocol/benchmark_test.go
index 0b35d9c55..f840adaa3 100644
--- a/lib/protocol/benchmark_test.go
+++ b/lib/protocol/benchmark_test.go
@@ -80,9 +80,9 @@ func benchmarkRequestsConnPair(b *testing.B, conn0, conn1 net.Conn) {
// Use c0 and c1 for each alternating request, so we get as much
// data flowing in both directions.
if i%2 == 0 {
- buf, err = c0.Request("folder", "file", int64(i), 128<<10, nil, false)
+ buf, err = c0.Request("folder", "file", int64(i), 128<<10, nil, 0, false)
} else {
- buf, err = c1.Request("folder", "file", int64(i), 128<<10, nil, false)
+ buf, err = c1.Request("folder", "file", int64(i), 128<<10, nil, 0, false)
}
if err != nil {
@@ -171,7 +171,7 @@ func (m *fakeModel) Index(deviceID DeviceID, folder string, files []FileInfo) {
func (m *fakeModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo) {
}
-func (m *fakeModel) Request(deviceID DeviceID, folder string, name string, offset int64, hash []byte, fromTemporary bool, buf []byte) error {
+func (m *fakeModel) Request(deviceID DeviceID, folder string, name string, offset int64, hash []byte, weakHAsh uint32, fromTemporary bool, buf []byte) error {
// We write the offset to the end of the buffer, so the receiver
// can verify that it did in fact get some data back over the
// connection.
diff --git a/lib/protocol/bep.pb.go b/lib/protocol/bep.pb.go
index a98cef07e..d65227318 100644
--- a/lib/protocol/bep.pb.go
+++ b/lib/protocol/bep.pb.go
@@ -353,6 +353,7 @@ type Request struct {
Size int32 `protobuf:"varint,5,opt,name=size,proto3" json:"size,omitempty"`
Hash []byte `protobuf:"bytes,6,opt,name=hash,proto3" json:"hash,omitempty"`
FromTemporary bool `protobuf:"varint,7,opt,name=from_temporary,json=fromTemporary,proto3" json:"from_temporary,omitempty"`
+ WeakHash uint32 `protobuf:"varint,8,opt,name=weak_hash,json=weakHash,proto3" json:"weak_hash,omitempty"`
}
func (m *Request) Reset() { *m = Request{} }
@@ -1062,6 +1063,11 @@ func (m *Request) MarshalTo(dAtA []byte) (int, error) {
}
i++
}
+ if m.WeakHash != 0 {
+ dAtA[i] = 0x40
+ i++
+ i = encodeVarintBep(dAtA, i, uint64(m.WeakHash))
+ }
return i, nil
}
@@ -1519,6 +1525,9 @@ func (m *Request) ProtoSize() (n int) {
if m.FromTemporary {
n += 2
}
+ if m.WeakHash != 0 {
+ n += 1 + sovBep(uint64(m.WeakHash))
+ }
return n
}
@@ -3515,6 +3524,25 @@ func (m *Request) Unmarshal(dAtA []byte) error {
}
}
m.FromTemporary = bool(v != 0)
+ case 8:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field WeakHash", wireType)
+ }
+ m.WeakHash = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowBep
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.WeakHash |= (uint32(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
default:
iNdEx = preIndex
skippy, err := skipBep(dAtA[iNdEx:])
@@ -4192,115 +4220,116 @@ var (
func init() { proto.RegisterFile("bep.proto", fileDescriptorBep) }
var fileDescriptorBep = []byte{
- // 1757 bytes of a gzipped FileDescriptorProto
+ // 1762 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0x4f, 0x8f, 0xdb, 0xc6,
- 0x15, 0x17, 0x25, 0xea, 0xdf, 0x93, 0xb4, 0xe1, 0x8e, 0xed, 0x2d, 0xcb, 0x6c, 0x24, 0x5a, 0xb1,
- 0xe3, 0xcd, 0x22, 0x59, 0xbb, 0x49, 0xda, 0xa2, 0x45, 0x5b, 0x40, 0x7f, 0xb8, 0x6b, 0xa1, 0x32,
- 0xa5, 0x8e, 0xb4, 0x4e, 0x9d, 0x43, 0x09, 0x4a, 0x1c, 0x69, 0x09, 0x53, 0x1c, 0x95, 0xa4, 0xd6,
- 0x56, 0x3e, 0x82, 0x3e, 0x41, 0x2f, 0x02, 0x02, 0xf4, 0x54, 0xa0, 0xc7, 0x7e, 0x08, 0x1f, 0x83,
- 0x1e, 0x7a, 0xe8, 0xc1, 0x68, 0xb6, 0x97, 0x1e, 0xfb, 0x09, 0x8a, 0x82, 0x33, 0xa4, 0x44, 0xed,
- 0xda, 0x81, 0x0f, 0x39, 0x71, 0xe6, 0xbd, 0xdf, 0xbc, 0x99, 0xf9, 0xcd, 0xef, 0xbd, 0x47, 0x28,
- 0x8e, 0xc8, 0xfc, 0x64, 0xee, 0xd1, 0x80, 0xa2, 0x02, 0xfb, 0x8c, 0xa9, 0xa3, 0x7c, 0x3a, 0xb5,
- 0x83, 0x8b, 0xc5, 0xe8, 0x64, 0x4c, 0x67, 0x0f, 0xa7, 0x74, 0x4a, 0x1f, 0x32, 0xcf, 0x68, 0x31,
- 0x61, 0x33, 0x36, 0x61, 0x23, 0xbe, 0xb0, 0x3e, 0x87, 0xec, 0x63, 0xe2, 0x38, 0x14, 0xd5, 0xa0,
- 0x64, 0x91, 0x4b, 0x7b, 0x4c, 0x0c, 0xd7, 0x9c, 0x11, 0x59, 0x50, 0x85, 0xa3, 0x22, 0x06, 0x6e,
- 0xd2, 0xcd, 0x19, 0x09, 0x01, 0x63, 0xc7, 0x26, 0x6e, 0xc0, 0x01, 0x69, 0x0e, 0xe0, 0x26, 0x06,
- 0xb8, 0x0f, 0x7b, 0x11, 0xe0, 0x92, 0x78, 0xbe, 0x4d, 0x5d, 0x39, 0xc3, 0x30, 0x15, 0x6e, 0x7d,
- 0xca, 0x8d, 0x75, 0x1f, 0x72, 0x8f, 0x89, 0x69, 0x11, 0x0f, 0x7d, 0x0c, 0x62, 0xb0, 0x9c, 0xf3,
- 0xbd, 0xf6, 0x3e, 0xbb, 0x73, 0x12, 0xdf, 0xe1, 0xe4, 0x09, 0xf1, 0x7d, 0x73, 0x4a, 0x86, 0xcb,
- 0x39, 0xc1, 0x0c, 0x82, 0x7e, 0x03, 0xa5, 0x31, 0x9d, 0xcd, 0x3d, 0xe2, 0xb3, 0xc0, 0x69, 0xb6,
- 0xe2, 0xf0, 0xc6, 0x8a, 0xd6, 0x16, 0x83, 0x93, 0x0b, 0xea, 0x0d, 0xa8, 0xb4, 0x9c, 0x85, 0x1f,
- 0x10, 0xaf, 0x45, 0xdd, 0x89, 0x3d, 0x45, 0x8f, 0x20, 0x3f, 0xa1, 0x8e, 0x45, 0x3c, 0x5f, 0x16,
- 0xd4, 0xcc, 0x51, 0xe9, 0x33, 0x69, 0x1b, 0xec, 0x94, 0x39, 0x9a, 0xe2, 0xab, 0xd7, 0xb5, 0x14,
- 0x8e, 0x61, 0xf5, 0x3f, 0xa7, 0x21, 0xc7, 0x3d, 0xe8, 0x00, 0xd2, 0xb6, 0xc5, 0x29, 0x6a, 0xe6,
- 0xae, 0x5e, 0xd7, 0xd2, 0x9d, 0x36, 0x4e, 0xdb, 0x16, 0xba, 0x0d, 0x59, 0xc7, 0x1c, 0x11, 0x27,
- 0x22, 0x87, 0x4f, 0xd0, 0xfb, 0x50, 0xf4, 0x88, 0x69, 0x19, 0xd4, 0x75, 0x96, 0x8c, 0x92, 0x02,
- 0x2e, 0x84, 0x86, 0x9e, 0xeb, 0x2c, 0xd1, 0xa7, 0x80, 0xec, 0xa9, 0x4b, 0x3d, 0x62, 0xcc, 0x89,
- 0x37, 0xb3, 0xd9, 0x69, 0x7d, 0x59, 0x64, 0xa8, 0x7d, 0xee, 0xe9, 0x6f, 0x1d, 0xe8, 0x43, 0xa8,
- 0x44, 0x70, 0x8b, 0x38, 0x24, 0x20, 0x72, 0x96, 0x21, 0xcb, 0xdc, 0xd8, 0x66, 0x36, 0xf4, 0x08,
- 0x6e, 0x5b, 0xb6, 0x6f, 0x8e, 0x1c, 0x62, 0x04, 0x64, 0x36, 0x37, 0x6c, 0xd7, 0x22, 0x2f, 0x89,
- 0x2f, 0xe7, 0x18, 0x16, 0x45, 0xbe, 0x21, 0x99, 0xcd, 0x3b, 0xdc, 0x83, 0x0e, 0x20, 0x37, 0x37,
- 0x17, 0x3e, 0xb1, 0xe4, 0x3c, 0xc3, 0x44, 0xb3, 0x90, 0x25, 0xae, 0x00, 0x5f, 0x96, 0xae, 0xb3,
- 0xd4, 0x66, 0x8e, 0x98, 0xa5, 0x08, 0x56, 0xff, 0x6f, 0x1a, 0x72, 0xdc, 0x83, 0x3e, 0xda, 0xb0,
- 0x54, 0x6e, 0x1e, 0x84, 0xa8, 0x7f, 0xbe, 0xae, 0x15, 0xb8, 0xaf, 0xd3, 0x4e, 0xb0, 0x86, 0x40,
- 0x4c, 0x28, 0x8a, 0x8d, 0xd1, 0x21, 0x14, 0x4d, 0xcb, 0x0a, 0x5f, 0x8f, 0xf8, 0x72, 0x46, 0xcd,
- 0x1c, 0x15, 0xf1, 0xd6, 0x80, 0x7e, 0xbe, 0xab, 0x06, 0xf1, 0xba, 0x7e, 0xde, 0x26, 0x83, 0xf0,
- 0x29, 0xc6, 0xc4, 0x8b, 0x14, 0x9c, 0x65, 0xfb, 0x15, 0x42, 0x03, 0xd3, 0xef, 0x5d, 0x28, 0xcf,
- 0xcc, 0x97, 0x86, 0x4f, 0xfe, 0xb8, 0x20, 0xee, 0x98, 0x30, 0xba, 0x32, 0xb8, 0x34, 0x33, 0x5f,
- 0x0e, 0x22, 0x13, 0xaa, 0x02, 0xd8, 0x6e, 0xe0, 0x51, 0x6b, 0x31, 0x26, 0x5e, 0xc4, 0x55, 0xc2,
- 0x82, 0x7e, 0x0a, 0x05, 0x46, 0xb6, 0x61, 0x5b, 0x72, 0x41, 0x15, 0x8e, 0xc4, 0xa6, 0x12, 0x5d,
- 0x3c, 0xcf, 0xa8, 0x66, 0xf7, 0x8e, 0x87, 0x38, 0xcf, 0xb0, 0x1d, 0x0b, 0xfd, 0x0a, 0x14, 0xff,
- 0xb9, 0x1d, 0x3e, 0x14, 0x8f, 0x14, 0xd8, 0xd4, 0x35, 0x3c, 0x32, 0xa3, 0x97, 0xa6, 0xe3, 0xcb,
- 0x45, 0xb6, 0x8d, 0x1c, 0x22, 0x3a, 0x09, 0x00, 0x8e, 0xfc, 0xf5, 0x1e, 0x64, 0x59, 0xc4, 0xf0,
- 0x15, 0xb9, 0x58, 0xa3, 0xec, 0x8d, 0x66, 0xe8, 0x04, 0xb2, 0x13, 0xdb, 0x21, 0xbe, 0x9c, 0x66,
- 0x6f, 0x88, 0x12, 0x4a, 0xb7, 0x1d, 0xd2, 0x71, 0x27, 0x34, 0x7a, 0x45, 0x0e, 0xab, 0x9f, 0x43,
- 0x89, 0x05, 0x3c, 0x9f, 0x5b, 0x66, 0x40, 0x7e, 0xb0, 0xb0, 0x7f, 0x15, 0xa1, 0x10, 0x7b, 0x36,
- 0x8f, 0x2e, 0x24, 0x1e, 0xfd, 0x38, 0xaa, 0x07, 0x3c, 0xbb, 0x0f, 0x6e, 0xc6, 0x4b, 0x14, 0x04,
- 0x04, 0xa2, 0x6f, 0x7f, 0x4d, 0x58, 0x3e, 0x65, 0x30, 0x1b, 0x23, 0x15, 0x4a, 0xd7, 0x93, 0xa8,
- 0x82, 0x93, 0x26, 0xf4, 0x01, 0xc0, 0x8c, 0x5a, 0xf6, 0xc4, 0x26, 0x96, 0xe1, 0x33, 0x01, 0x64,
- 0x70, 0x31, 0xb6, 0x0c, 0x90, 0x1c, 0xca, 0x3d, 0x4c, 0x21, 0x2b, 0xca, 0x95, 0x78, 0x1a, 0x7a,
- 0x6c, 0xf7, 0xd2, 0x74, 0xec, 0x38, 0x43, 0xe2, 0x69, 0x58, 0xf5, 0x5c, 0xba, 0x93, 0xbc, 0x05,
- 0x06, 0xa8, 0xb8, 0x34, 0x99, 0xb8, 0x8f, 0x20, 0x1f, 0x57, 0xc5, 0xf0, 0x3d, 0x77, 0x32, 0xe9,
- 0x29, 0x19, 0x07, 0x74, 0x53, 0x6f, 0x22, 0x18, 0x52, 0xa0, 0xb0, 0x91, 0x22, 0xb0, 0x93, 0x6e,
- 0xe6, 0x61, 0x2d, 0xde, 0xdc, 0xc3, 0xf5, 0xe5, 0x92, 0x2a, 0x1c, 0x65, 0xf1, 0xe6, 0x6a, 0x7a,
- 0xb8, 0xdd, 0x16, 0x30, 0x5a, 0xca, 0x65, 0xa6, 0xc5, 0xf7, 0x62, 0x2d, 0x0e, 0x2e, 0xa8, 0x17,
- 0x74, 0xda, 0xdb, 0x15, 0xcd, 0x25, 0x7a, 0x08, 0x30, 0x72, 0xe8, 0xf8, 0xb9, 0xc1, 0x68, 0xad,
- 0x84, 0x11, 0x9b, 0xd2, 0xd5, 0xeb, 0x5a, 0x19, 0x9b, 0x2f, 0x9a, 0xa1, 0x63, 0x60, 0x7f, 0x4d,
- 0x70, 0x71, 0x14, 0x0f, 0xd1, 0x4f, 0x20, 0xc7, 0xec, 0x71, 0x69, 0xb8, 0xb5, 0xbd, 0x10, 0xb3,
- 0x27, 0x04, 0x10, 0x01, 0x43, 0xae, 0xfc, 0xe5, 0xcc, 0xb1, 0xdd, 0xe7, 0x46, 0x60, 0x7a, 0x53,
- 0x12, 0xc8, 0xfb, 0xbc, 0x43, 0x44, 0xd6, 0x21, 0x33, 0xfe, 0x52, 0xfc, 0xd3, 0x37, 0xb5, 0x54,
- 0xdd, 0x85, 0xe2, 0x26, 0x4e, 0xa8, 0x41, 0x3a, 0x99, 0xf8, 0x24, 0x60, 0x82, 0xc9, 0xe0, 0x68,
- 0xb6, 0x91, 0x41, 0x9a, 0x31, 0xc0, 0x65, 0x80, 0x40, 0xbc, 0x30, 0xfd, 0x0b, 0x26, 0x8d, 0x32,
- 0x66, 0xe3, 0x30, 0xf1, 0x5f, 0x10, 0xf3, 0xb9, 0xc1, 0x1c, 0x5c, 0x18, 0x85, 0xd0, 0xf0, 0xd8,
- 0xf4, 0x2f, 0xa2, 0xfd, 0x7e, 0x0d, 0x39, 0xfe, 0x10, 0xe8, 0x73, 0x28, 0x8c, 0xe9, 0xc2, 0x0d,
- 0xb6, 0xcd, 0x61, 0x3f, 0x59, 0x5b, 0x98, 0x27, 0xba, 0xd9, 0x06, 0x58, 0x3f, 0x85, 0x7c, 0xe4,
- 0x42, 0xf7, 0x37, 0x85, 0x4f, 0x6c, 0xde, 0xb9, 0xc6, 0xf9, 0x6e, 0xb7, 0xb8, 0x34, 0x9d, 0x05,
- 0x3f, 0xbc, 0x88, 0xf9, 0xa4, 0xfe, 0x37, 0x01, 0xf2, 0x38, 0x7c, 0x67, 0x3f, 0x48, 0xf4, 0x99,
- 0xec, 0x4e, 0x9f, 0xd9, 0x66, 0x64, 0x7a, 0x27, 0x23, 0xe3, 0xa4, 0xca, 0x24, 0x92, 0x6a, 0xcb,
- 0x9c, 0xf8, 0x46, 0xe6, 0xb2, 0x6f, 0x60, 0x2e, 0x97, 0x60, 0xee, 0x3e, 0xec, 0x4d, 0x3c, 0x3a,
- 0x63, 0x9d, 0x84, 0x7a, 0xa6, 0xb7, 0x8c, 0x12, 0xa0, 0x12, 0x5a, 0x87, 0xb1, 0xb1, 0x6e, 0x40,
- 0x01, 0x13, 0x7f, 0x4e, 0x5d, 0x9f, 0xbc, 0xf5, 0xd8, 0x08, 0x44, 0xcb, 0x0c, 0x4c, 0x76, 0xe8,
- 0x32, 0x66, 0x63, 0xf4, 0x00, 0xc4, 0x31, 0xb5, 0xf8, 0x91, 0xf7, 0x92, 0x1a, 0xd2, 0x3c, 0x8f,
- 0x7a, 0x2d, 0x6a, 0x11, 0xcc, 0x00, 0xf5, 0x39, 0x48, 0x6d, 0xfa, 0xc2, 0x75, 0xa8, 0x69, 0xf5,
- 0x3d, 0x3a, 0x0d, 0x2b, 0xfa, 0x5b, 0x2b, 0x53, 0x1b, 0xf2, 0x0b, 0x56, 0xbb, 0xe2, 0xda, 0x74,
- 0x6f, 0xb7, 0x96, 0x5c, 0x0f, 0xc4, 0x0b, 0x5d, 0x9c, 0x80, 0xd1, 0xd2, 0xfa, 0x3f, 0x04, 0x50,
- 0xde, 0x8e, 0x46, 0x1d, 0x28, 0x71, 0xa4, 0x91, 0xf8, 0x89, 0x39, 0x7a, 0x97, 0x8d, 0x58, 0x19,
- 0x83, 0xc5, 0x66, 0xfc, 0xc6, 0x0e, 0x98, 0x28, 0x18, 0x99, 0x77, 0x2b, 0x18, 0x0f, 0xa0, 0xc2,
- 0x33, 0x38, 0xee, 0xf7, 0xa2, 0x9a, 0x39, 0xca, 0x36, 0xd3, 0x52, 0x0a, 0x97, 0x47, 0x3c, 0x93,
- 0x98, 0xbd, 0x9e, 0x03, 0xb1, 0x6f, 0xbb, 0xd3, 0x7a, 0x0d, 0xb2, 0x2d, 0x87, 0xb2, 0x07, 0xcb,
- 0x79, 0xc4, 0xf4, 0xa9, 0x1b, 0xf3, 0xc8, 0x67, 0xc7, 0x7f, 0x4f, 0x43, 0x29, 0xf1, 0x2f, 0x86,
- 0x1e, 0xc1, 0x5e, 0xab, 0x7b, 0x3e, 0x18, 0x6a, 0xd8, 0x68, 0xf5, 0xf4, 0xd3, 0xce, 0x99, 0x94,
- 0x52, 0x0e, 0x57, 0x6b, 0x55, 0x9e, 0x6d, 0x41, 0xbb, 0xbf, 0x59, 0x35, 0xc8, 0x76, 0xf4, 0xb6,
- 0xf6, 0x7b, 0x49, 0x50, 0x6e, 0xaf, 0xd6, 0xaa, 0x94, 0x00, 0xf2, 0x9e, 0xf5, 0x09, 0x94, 0x19,
- 0xc0, 0x38, 0xef, 0xb7, 0x1b, 0x43, 0x4d, 0x4a, 0x2b, 0xca, 0x6a, 0xad, 0x1e, 0x5c, 0xc7, 0x45,
- 0x9c, 0x7f, 0x08, 0x79, 0xac, 0xfd, 0xee, 0x5c, 0x1b, 0x0c, 0xa5, 0x8c, 0x72, 0xb0, 0x5a, 0xab,
- 0x28, 0x01, 0x8c, 0xb3, 0xe6, 0x3e, 0x14, 0xb0, 0x36, 0xe8, 0xf7, 0xf4, 0x81, 0x26, 0x89, 0xca,
- 0x8f, 0x56, 0x6b, 0xf5, 0xd6, 0x0e, 0x2a, 0x52, 0xe9, 0xcf, 0x60, 0xbf, 0xdd, 0xfb, 0x52, 0xef,
- 0xf6, 0x1a, 0x6d, 0xa3, 0x8f, 0x7b, 0x67, 0x58, 0x1b, 0x0c, 0xa4, 0xac, 0x52, 0x5b, 0xad, 0xd5,
- 0xf7, 0x13, 0xf8, 0x1b, 0xa2, 0xfb, 0x00, 0xc4, 0x7e, 0x47, 0x3f, 0x93, 0x72, 0xca, 0xad, 0xd5,
- 0x5a, 0x7d, 0x2f, 0x01, 0x0d, 0x49, 0x0d, 0x6f, 0xdc, 0xea, 0xf6, 0x06, 0x9a, 0x94, 0xbf, 0x71,
- 0x63, 0x46, 0xf6, 0xf1, 0x1f, 0x00, 0xdd, 0xfc, 0x5b, 0x45, 0xf7, 0x40, 0xd4, 0x7b, 0xba, 0x26,
- 0xa5, 0xf8, 0xfd, 0x6f, 0x22, 0x74, 0xea, 0x12, 0x54, 0x87, 0x4c, 0xf7, 0xab, 0x2f, 0x24, 0x41,
- 0xf9, 0xf1, 0x6a, 0xad, 0xde, 0xb9, 0x09, 0xea, 0x7e, 0xf5, 0xc5, 0x31, 0x85, 0x52, 0x32, 0x70,
- 0x1d, 0x0a, 0x4f, 0xb4, 0x61, 0xa3, 0xdd, 0x18, 0x36, 0xa4, 0x14, 0x3f, 0x52, 0xec, 0x7e, 0x42,
- 0x02, 0x93, 0x25, 0xe1, 0x21, 0x64, 0x75, 0xed, 0xa9, 0x86, 0x25, 0x41, 0xd9, 0x5f, 0xad, 0xd5,
- 0x4a, 0x0c, 0xd0, 0xc9, 0x25, 0xf1, 0x50, 0x15, 0x72, 0x8d, 0xee, 0x97, 0x8d, 0x67, 0x03, 0x29,
- 0xad, 0xa0, 0xd5, 0x5a, 0xdd, 0x8b, 0xdd, 0x0d, 0xe7, 0x85, 0xb9, 0xf4, 0x8f, 0xff, 0x27, 0x40,
- 0x39, 0xd9, 0xa1, 0x51, 0x15, 0xc4, 0xd3, 0x4e, 0x57, 0x8b, 0xb7, 0x4b, 0xfa, 0xc2, 0x31, 0x3a,
- 0x82, 0x62, 0xbb, 0x83, 0xb5, 0xd6, 0xb0, 0x87, 0x9f, 0xc5, 0x77, 0x49, 0x82, 0xda, 0xb6, 0xc7,
- 0x04, 0xbe, 0x44, 0xbf, 0x80, 0xf2, 0xe0, 0xd9, 0x93, 0x6e, 0x47, 0xff, 0xad, 0xc1, 0x22, 0xa6,
- 0x95, 0x07, 0xab, 0xb5, 0x7a, 0x77, 0x07, 0x4c, 0xe6, 0x1e, 0x19, 0x9b, 0x01, 0xb1, 0x06, 0xbc,
- 0x89, 0x84, 0xce, 0x82, 0x80, 0x5a, 0xb0, 0x1f, 0x2f, 0xdd, 0x6e, 0x96, 0x51, 0x3e, 0x59, 0xad,
- 0xd5, 0x8f, 0xbe, 0x77, 0xfd, 0x66, 0xf7, 0x82, 0x80, 0xee, 0x41, 0x3e, 0x0a, 0x12, 0x2b, 0x29,
- 0xb9, 0x34, 0x5a, 0x70, 0xfc, 0x17, 0x01, 0x8a, 0x9b, 0x72, 0x15, 0x12, 0xae, 0xf7, 0x0c, 0x0d,
- 0xe3, 0x1e, 0x8e, 0x19, 0xd8, 0x38, 0x75, 0xca, 0x86, 0xe8, 0x2e, 0xe4, 0xcf, 0x34, 0x5d, 0xc3,
- 0x9d, 0x56, 0x9c, 0x18, 0x1b, 0xc8, 0x19, 0x71, 0x89, 0x67, 0x8f, 0xd1, 0xc7, 0x50, 0xd6, 0x7b,
- 0xc6, 0xe0, 0xbc, 0xf5, 0x38, 0xbe, 0x3a, 0xdb, 0x3f, 0x11, 0x6a, 0xb0, 0x18, 0x5f, 0x30, 0x3e,
- 0x8f, 0xc3, 0x1c, 0x7a, 0xda, 0xe8, 0x76, 0xda, 0x1c, 0x9a, 0x51, 0xe4, 0xd5, 0x5a, 0xbd, 0xbd,
- 0x81, 0x76, 0xf8, 0xaf, 0x4a, 0x88, 0x3d, 0xb6, 0xa0, 0xfa, 0xfd, 0x85, 0x09, 0xa9, 0x90, 0x6b,
- 0xf4, 0xfb, 0x9a, 0xde, 0x8e, 0x4f, 0xbf, 0xf5, 0x35, 0xe6, 0x73, 0xe2, 0x5a, 0x21, 0xe2, 0xb4,
- 0x87, 0xcf, 0xb4, 0x61, 0x7c, 0xf8, 0x2d, 0xe2, 0x94, 0x86, 0x1d, 0xbc, 0x79, 0xf8, 0xea, 0xbb,
- 0x6a, 0xea, 0xdb, 0xef, 0xaa, 0xa9, 0x57, 0x57, 0x55, 0xe1, 0xdb, 0xab, 0xaa, 0xf0, 0xaf, 0xab,
- 0x6a, 0xea, 0x3f, 0x57, 0x55, 0xe1, 0x9b, 0x7f, 0x57, 0x85, 0x51, 0x8e, 0x15, 0xb2, 0xcf, 0xff,
- 0x1f, 0x00, 0x00, 0xff, 0xff, 0x0d, 0x36, 0x7c, 0x9b, 0xc0, 0x0e, 0x00, 0x00,
+ 0x15, 0x5f, 0x4a, 0x94, 0x44, 0x3d, 0x69, 0x37, 0xdc, 0xb1, 0xbd, 0x65, 0x99, 0x8d, 0x44, 0x2b,
+ 0x76, 0xbc, 0x59, 0x24, 0x6b, 0x37, 0x49, 0x5b, 0xb4, 0x68, 0x0b, 0xe8, 0x0f, 0x77, 0x2d, 0x54,
+ 0xa6, 0xd4, 0x91, 0xd6, 0xa9, 0x73, 0x28, 0x41, 0x89, 0x23, 0x2d, 0x61, 0x8a, 0xa3, 0x92, 0xd4,
+ 0xda, 0xca, 0x47, 0xd0, 0x27, 0xe8, 0x45, 0x40, 0x80, 0x9e, 0x0a, 0xf4, 0x83, 0xf8, 0x98, 0xf6,
+ 0xd0, 0x43, 0x0f, 0x46, 0xb3, 0xbd, 0xf4, 0xd8, 0x4f, 0x50, 0x14, 0x9c, 0x21, 0x25, 0x6a, 0xd7,
+ 0x0e, 0x7c, 0xc8, 0x89, 0x33, 0xef, 0xfd, 0xe6, 0x0d, 0xdf, 0x6f, 0x7e, 0xef, 0xcd, 0x40, 0x71,
+ 0x48, 0x66, 0x27, 0x33, 0x9f, 0x86, 0x14, 0x49, 0xec, 0x33, 0xa2, 0xae, 0xfa, 0xe9, 0xc4, 0x09,
+ 0x2f, 0xe6, 0xc3, 0x93, 0x11, 0x9d, 0x3e, 0x9c, 0xd0, 0x09, 0x7d, 0xc8, 0x3c, 0xc3, 0xf9, 0x98,
+ 0xcd, 0xd8, 0x84, 0x8d, 0xf8, 0xc2, 0xda, 0x0c, 0x72, 0x8f, 0x89, 0xeb, 0x52, 0x54, 0x85, 0x92,
+ 0x4d, 0x2e, 0x9d, 0x11, 0x31, 0x3d, 0x6b, 0x4a, 0x14, 0x41, 0x13, 0x8e, 0x8a, 0x18, 0xb8, 0xc9,
+ 0xb0, 0xa6, 0x24, 0x02, 0x8c, 0x5c, 0x87, 0x78, 0x21, 0x07, 0x64, 0x38, 0x80, 0x9b, 0x18, 0xe0,
+ 0x3e, 0xec, 0xc5, 0x80, 0x4b, 0xe2, 0x07, 0x0e, 0xf5, 0x94, 0x2c, 0xc3, 0xec, 0x72, 0xeb, 0x53,
+ 0x6e, 0xac, 0x05, 0x90, 0x7f, 0x4c, 0x2c, 0x9b, 0xf8, 0xe8, 0x63, 0x10, 0xc3, 0xc5, 0x8c, 0xef,
+ 0xb5, 0xf7, 0xd9, 0x9d, 0x93, 0x24, 0x87, 0x93, 0x27, 0x24, 0x08, 0xac, 0x09, 0x19, 0x2c, 0x66,
+ 0x04, 0x33, 0x08, 0xfa, 0x0d, 0x94, 0x46, 0x74, 0x3a, 0xf3, 0x49, 0xc0, 0x02, 0x67, 0xd8, 0x8a,
+ 0xc3, 0x1b, 0x2b, 0x9a, 0x1b, 0x0c, 0x4e, 0x2f, 0xa8, 0xd5, 0x61, 0xb7, 0xe9, 0xce, 0x83, 0x90,
+ 0xf8, 0x4d, 0xea, 0x8d, 0x9d, 0x09, 0x7a, 0x04, 0x85, 0x31, 0x75, 0x6d, 0xe2, 0x07, 0x8a, 0xa0,
+ 0x65, 0x8f, 0x4a, 0x9f, 0xc9, 0x9b, 0x60, 0xa7, 0xcc, 0xd1, 0x10, 0x5f, 0xbd, 0xae, 0xee, 0xe0,
+ 0x04, 0x56, 0xfb, 0x73, 0x06, 0xf2, 0xdc, 0x83, 0x0e, 0x20, 0xe3, 0xd8, 0x9c, 0xa2, 0x46, 0xfe,
+ 0xea, 0x75, 0x35, 0xd3, 0x6e, 0xe1, 0x8c, 0x63, 0xa3, 0xdb, 0x90, 0x73, 0xad, 0x21, 0x71, 0x63,
+ 0x72, 0xf8, 0x04, 0xbd, 0x0f, 0x45, 0x9f, 0x58, 0xb6, 0x49, 0x3d, 0x77, 0xc1, 0x28, 0x91, 0xb0,
+ 0x14, 0x19, 0xba, 0x9e, 0xbb, 0x40, 0x9f, 0x02, 0x72, 0x26, 0x1e, 0xf5, 0x89, 0x39, 0x23, 0xfe,
+ 0xd4, 0x61, 0x7f, 0x1b, 0x28, 0x22, 0x43, 0xed, 0x73, 0x4f, 0x6f, 0xe3, 0x40, 0x1f, 0xc2, 0x6e,
+ 0x0c, 0xb7, 0x89, 0x4b, 0x42, 0xa2, 0xe4, 0x18, 0xb2, 0xcc, 0x8d, 0x2d, 0x66, 0x43, 0x8f, 0xe0,
+ 0xb6, 0xed, 0x04, 0xd6, 0xd0, 0x25, 0x66, 0x48, 0xa6, 0x33, 0xd3, 0xf1, 0x6c, 0xf2, 0x92, 0x04,
+ 0x4a, 0x9e, 0x61, 0x51, 0xec, 0x1b, 0x90, 0xe9, 0xac, 0xcd, 0x3d, 0xe8, 0x00, 0xf2, 0x33, 0x6b,
+ 0x1e, 0x10, 0x5b, 0x29, 0x30, 0x4c, 0x3c, 0x8b, 0x58, 0xe2, 0x0a, 0x08, 0x14, 0xf9, 0x3a, 0x4b,
+ 0x2d, 0xe6, 0x48, 0x58, 0x8a, 0x61, 0xb5, 0xff, 0x66, 0x20, 0xcf, 0x3d, 0xe8, 0xa3, 0x35, 0x4b,
+ 0xe5, 0xc6, 0x41, 0x84, 0xfa, 0xe7, 0xeb, 0xaa, 0xc4, 0x7d, 0xed, 0x56, 0x8a, 0x35, 0x04, 0x62,
+ 0x4a, 0x51, 0x6c, 0x8c, 0x0e, 0xa1, 0x68, 0xd9, 0x76, 0x74, 0x7a, 0x24, 0x50, 0xb2, 0x5a, 0xf6,
+ 0xa8, 0x88, 0x37, 0x06, 0xf4, 0xf3, 0x6d, 0x35, 0x88, 0xd7, 0xf5, 0xf3, 0x36, 0x19, 0x44, 0x47,
+ 0x31, 0x22, 0x7e, 0xac, 0xe0, 0x1c, 0xdb, 0x4f, 0x8a, 0x0c, 0x4c, 0xbf, 0x77, 0xa1, 0x3c, 0xb5,
+ 0x5e, 0x9a, 0x01, 0xf9, 0xe3, 0x9c, 0x78, 0x23, 0xc2, 0xe8, 0xca, 0xe2, 0xd2, 0xd4, 0x7a, 0xd9,
+ 0x8f, 0x4d, 0xa8, 0x02, 0xe0, 0x78, 0xa1, 0x4f, 0xed, 0xf9, 0x88, 0xf8, 0x31, 0x57, 0x29, 0x0b,
+ 0xfa, 0x29, 0x48, 0x8c, 0x6c, 0xd3, 0xb1, 0x15, 0x49, 0x13, 0x8e, 0xc4, 0x86, 0x1a, 0x27, 0x5e,
+ 0x60, 0x54, 0xb3, 0xbc, 0x93, 0x21, 0x2e, 0x30, 0x6c, 0xdb, 0x46, 0xbf, 0x02, 0x35, 0x78, 0xee,
+ 0x44, 0x07, 0xc5, 0x23, 0x85, 0x0e, 0xf5, 0x4c, 0x9f, 0x4c, 0xe9, 0xa5, 0xe5, 0x06, 0x4a, 0x91,
+ 0x6d, 0xa3, 0x44, 0x88, 0x76, 0x0a, 0x80, 0x63, 0x7f, 0xad, 0x0b, 0x39, 0x16, 0x31, 0x3a, 0x45,
+ 0x2e, 0xd6, 0xb8, 0x7a, 0xe3, 0x19, 0x3a, 0x81, 0xdc, 0xd8, 0x71, 0x49, 0xa0, 0x64, 0xd8, 0x19,
+ 0xa2, 0x94, 0xd2, 0x1d, 0x97, 0xb4, 0xbd, 0x31, 0x8d, 0x4f, 0x91, 0xc3, 0x6a, 0xe7, 0x50, 0x62,
+ 0x01, 0xcf, 0x67, 0xb6, 0x15, 0x92, 0x1f, 0x2c, 0xec, 0x5f, 0x45, 0x90, 0x12, 0xcf, 0xfa, 0xd0,
+ 0x85, 0xd4, 0xa1, 0x1f, 0xc7, 0xfd, 0x80, 0x57, 0xf7, 0xc1, 0xcd, 0x78, 0xa9, 0x86, 0x80, 0x40,
+ 0x0c, 0x9c, 0xaf, 0x09, 0xab, 0xa7, 0x2c, 0x66, 0x63, 0xa4, 0x41, 0xe9, 0x7a, 0x11, 0xed, 0xe2,
+ 0xb4, 0x09, 0x7d, 0x00, 0x30, 0xa5, 0xb6, 0x33, 0x76, 0x88, 0x6d, 0x06, 0x4c, 0x00, 0x59, 0x5c,
+ 0x4c, 0x2c, 0x7d, 0xa4, 0x44, 0x72, 0x8f, 0x4a, 0xc8, 0x8e, 0x6b, 0x25, 0x99, 0x46, 0x1e, 0xc7,
+ 0xbb, 0xb4, 0x5c, 0x27, 0xa9, 0x90, 0x64, 0x1a, 0x75, 0x3d, 0x8f, 0x6e, 0x15, 0xaf, 0xc4, 0x00,
+ 0xbb, 0x1e, 0x4d, 0x17, 0xee, 0x23, 0x28, 0x24, 0x5d, 0x31, 0x3a, 0xcf, 0xad, 0x4a, 0x7a, 0x4a,
+ 0x46, 0x21, 0x5d, 0xf7, 0x9b, 0x18, 0x86, 0x54, 0x90, 0xd6, 0x52, 0x04, 0xf6, 0xa7, 0xeb, 0x79,
+ 0xd4, 0x8b, 0xd7, 0x79, 0x78, 0x81, 0x52, 0xd2, 0x84, 0xa3, 0x1c, 0x5e, 0xa7, 0x66, 0x44, 0xdb,
+ 0x6d, 0x00, 0xc3, 0x85, 0x52, 0x66, 0x5a, 0x7c, 0x2f, 0xd1, 0x62, 0xff, 0x82, 0xfa, 0x61, 0xbb,
+ 0xb5, 0x59, 0xd1, 0x58, 0xa0, 0x87, 0x00, 0x43, 0x97, 0x8e, 0x9e, 0x9b, 0x8c, 0xd6, 0xdd, 0x28,
+ 0x62, 0x43, 0xbe, 0x7a, 0x5d, 0x2d, 0x63, 0xeb, 0x45, 0x23, 0x72, 0xf4, 0x9d, 0xaf, 0x09, 0x2e,
+ 0x0e, 0x93, 0x21, 0xfa, 0x09, 0xe4, 0x99, 0x3d, 0x69, 0x0d, 0xb7, 0x36, 0x09, 0x31, 0x7b, 0x4a,
+ 0x00, 0x31, 0x30, 0xe2, 0x2a, 0x58, 0x4c, 0x5d, 0xc7, 0x7b, 0x6e, 0x86, 0x96, 0x3f, 0x21, 0xa1,
+ 0xb2, 0xcf, 0x6f, 0x88, 0xd8, 0x3a, 0x60, 0xc6, 0x5f, 0x8a, 0x7f, 0xfa, 0xa6, 0xba, 0x53, 0xf3,
+ 0xa0, 0xb8, 0x8e, 0x13, 0x69, 0x90, 0x8e, 0xc7, 0x01, 0x09, 0x99, 0x60, 0xb2, 0x38, 0x9e, 0xad,
+ 0x65, 0x90, 0x61, 0x0c, 0x70, 0x19, 0x20, 0x10, 0x2f, 0xac, 0xe0, 0x82, 0x49, 0xa3, 0x8c, 0xd9,
+ 0x38, 0x2a, 0xfc, 0x17, 0xc4, 0x7a, 0x6e, 0x32, 0x07, 0x17, 0x86, 0x14, 0x19, 0x1e, 0x5b, 0xc1,
+ 0x45, 0xbc, 0xdf, 0xaf, 0x21, 0xcf, 0x0f, 0x02, 0x7d, 0x0e, 0xd2, 0x88, 0xce, 0xbd, 0x70, 0x73,
+ 0x39, 0xec, 0xa7, 0x7b, 0x0b, 0xf3, 0xc4, 0x99, 0xad, 0x81, 0xb5, 0x53, 0x28, 0xc4, 0x2e, 0x74,
+ 0x7f, 0xdd, 0xf8, 0xc4, 0xc6, 0x9d, 0x6b, 0x9c, 0x6f, 0xdf, 0x16, 0x97, 0x96, 0x3b, 0xe7, 0x3f,
+ 0x2f, 0x62, 0x3e, 0xa9, 0xfd, 0x4d, 0x80, 0x02, 0x8e, 0xce, 0x39, 0x08, 0x53, 0xf7, 0x4c, 0x6e,
+ 0xeb, 0x9e, 0xd9, 0x54, 0x64, 0x66, 0xab, 0x22, 0x93, 0xa2, 0xca, 0xa6, 0x8a, 0x6a, 0xc3, 0x9c,
+ 0xf8, 0x46, 0xe6, 0x72, 0x6f, 0x60, 0x2e, 0x9f, 0x62, 0xee, 0x3e, 0xec, 0x8d, 0x7d, 0x3a, 0x65,
+ 0x37, 0x09, 0xf5, 0x2d, 0x7f, 0x11, 0x17, 0xc0, 0x6e, 0x64, 0x1d, 0x24, 0xc6, 0x6d, 0x82, 0xa5,
+ 0x6d, 0x82, 0x6b, 0x26, 0x48, 0x98, 0x04, 0x33, 0xea, 0x05, 0xe4, 0xad, 0x39, 0x21, 0x10, 0x6d,
+ 0x2b, 0xb4, 0x58, 0x46, 0x65, 0xcc, 0xc6, 0xe8, 0x01, 0x88, 0x23, 0x6a, 0xf3, 0x7c, 0xf6, 0xd2,
+ 0x02, 0xd3, 0x7d, 0x9f, 0xfa, 0x4d, 0x6a, 0x13, 0xcc, 0x00, 0xb5, 0x19, 0xc8, 0x2d, 0xfa, 0xc2,
+ 0x73, 0xa9, 0x65, 0xf7, 0x7c, 0x3a, 0x89, 0xda, 0xfd, 0x5b, 0xdb, 0x56, 0x0b, 0x0a, 0x73, 0xd6,
+ 0xd8, 0x92, 0xc6, 0x75, 0x6f, 0xbb, 0xd1, 0x5c, 0x0f, 0xc4, 0xbb, 0x60, 0x52, 0x9d, 0xf1, 0xd2,
+ 0xda, 0x3f, 0x04, 0x50, 0xdf, 0x8e, 0x46, 0x6d, 0x28, 0x71, 0xa4, 0x99, 0x7a, 0xe1, 0x1c, 0xbd,
+ 0xcb, 0x46, 0xac, 0xc7, 0xc1, 0x7c, 0x3d, 0x7e, 0xe3, 0xf5, 0x98, 0xea, 0x26, 0xd9, 0x77, 0xeb,
+ 0x26, 0x0f, 0x60, 0x97, 0x97, 0x77, 0xf2, 0x18, 0x10, 0xb5, 0xec, 0x51, 0xae, 0x91, 0x91, 0x77,
+ 0x70, 0x79, 0xc8, 0xcb, 0x8c, 0xd9, 0x6b, 0x79, 0x10, 0x7b, 0x8e, 0x37, 0xa9, 0x55, 0x21, 0xd7,
+ 0x74, 0x29, 0x3b, 0xb0, 0xbc, 0x4f, 0xac, 0x80, 0x7a, 0x09, 0x8f, 0x7c, 0x76, 0xfc, 0xf7, 0x0c,
+ 0x94, 0x52, 0x0f, 0x35, 0xf4, 0x08, 0xf6, 0x9a, 0x9d, 0xf3, 0xfe, 0x40, 0xc7, 0x66, 0xb3, 0x6b,
+ 0x9c, 0xb6, 0xcf, 0xe4, 0x1d, 0xf5, 0x70, 0xb9, 0xd2, 0x94, 0xe9, 0x06, 0xb4, 0xfd, 0x06, 0xab,
+ 0x42, 0xae, 0x6d, 0xb4, 0xf4, 0xdf, 0xcb, 0x82, 0x7a, 0x7b, 0xb9, 0xd2, 0xe4, 0x14, 0x90, 0x5f,
+ 0x68, 0x9f, 0x40, 0x99, 0x01, 0xcc, 0xf3, 0x5e, 0xab, 0x3e, 0xd0, 0xe5, 0x8c, 0xaa, 0x2e, 0x57,
+ 0xda, 0xc1, 0x75, 0x5c, 0xcc, 0xf9, 0x87, 0x50, 0xc0, 0xfa, 0xef, 0xce, 0xf5, 0xfe, 0x40, 0xce,
+ 0xaa, 0x07, 0xcb, 0x95, 0x86, 0x52, 0xc0, 0xa4, 0xa4, 0xee, 0x83, 0x84, 0xf5, 0x7e, 0xaf, 0x6b,
+ 0xf4, 0x75, 0x59, 0x54, 0x7f, 0xb4, 0x5c, 0x69, 0xb7, 0xb6, 0x50, 0xb1, 0x4a, 0x7f, 0x06, 0xfb,
+ 0xad, 0xee, 0x97, 0x46, 0xa7, 0x5b, 0x6f, 0x99, 0x3d, 0xdc, 0x3d, 0xc3, 0x7a, 0xbf, 0x2f, 0xe7,
+ 0xd4, 0xea, 0x72, 0xa5, 0xbd, 0x9f, 0xc2, 0xdf, 0x10, 0xdd, 0x07, 0x20, 0xf6, 0xda, 0xc6, 0x99,
+ 0x9c, 0x57, 0x6f, 0x2d, 0x57, 0xda, 0x7b, 0x29, 0x68, 0x44, 0x6a, 0x94, 0x71, 0xb3, 0xd3, 0xed,
+ 0xeb, 0x72, 0xe1, 0x46, 0xc6, 0x8c, 0xec, 0xe3, 0x3f, 0x00, 0xba, 0xf9, 0x94, 0x45, 0xf7, 0x40,
+ 0x34, 0xba, 0x86, 0x2e, 0xef, 0xf0, 0xfc, 0x6f, 0x22, 0x0c, 0xea, 0x11, 0x54, 0x83, 0x6c, 0xe7,
+ 0xab, 0x2f, 0x64, 0x41, 0xfd, 0xf1, 0x72, 0xa5, 0xdd, 0xb9, 0x09, 0xea, 0x7c, 0xf5, 0xc5, 0x31,
+ 0x85, 0x52, 0x3a, 0x70, 0x0d, 0xa4, 0x27, 0xfa, 0xa0, 0xde, 0xaa, 0x0f, 0xea, 0xf2, 0x0e, 0xff,
+ 0xa5, 0xc4, 0xfd, 0x84, 0x84, 0x16, 0x2b, 0xc2, 0x43, 0xc8, 0x19, 0xfa, 0x53, 0x1d, 0xcb, 0x82,
+ 0xba, 0xbf, 0x5c, 0x69, 0xbb, 0x09, 0xc0, 0x20, 0x97, 0xc4, 0x47, 0x15, 0xc8, 0xd7, 0x3b, 0x5f,
+ 0xd6, 0x9f, 0xf5, 0xe5, 0x8c, 0x8a, 0x96, 0x2b, 0x6d, 0x2f, 0x71, 0xd7, 0xdd, 0x17, 0xd6, 0x22,
+ 0x38, 0xfe, 0x9f, 0x00, 0xe5, 0xf4, 0xf5, 0x8d, 0x2a, 0x20, 0x9e, 0xb6, 0x3b, 0x7a, 0xb2, 0x5d,
+ 0xda, 0x17, 0x8d, 0xd1, 0x11, 0x14, 0x5b, 0x6d, 0xac, 0x37, 0x07, 0x5d, 0xfc, 0x2c, 0xc9, 0x25,
+ 0x0d, 0x6a, 0x39, 0x3e, 0x13, 0xf8, 0x02, 0xfd, 0x02, 0xca, 0xfd, 0x67, 0x4f, 0x3a, 0x6d, 0xe3,
+ 0xb7, 0x26, 0x8b, 0x98, 0x51, 0x1f, 0x2c, 0x57, 0xda, 0xdd, 0x2d, 0x30, 0x99, 0xf9, 0x64, 0x64,
+ 0x85, 0xc4, 0xee, 0xf3, 0x1b, 0x26, 0x72, 0x4a, 0x02, 0x6a, 0xc2, 0x7e, 0xb2, 0x74, 0xb3, 0x59,
+ 0x56, 0xfd, 0x64, 0xb9, 0xd2, 0x3e, 0xfa, 0xde, 0xf5, 0xeb, 0xdd, 0x25, 0x01, 0xdd, 0x83, 0x42,
+ 0x1c, 0x24, 0x51, 0x52, 0x7a, 0x69, 0xbc, 0xe0, 0xf8, 0x2f, 0x02, 0x14, 0xd7, 0xed, 0x2a, 0x22,
+ 0xdc, 0xe8, 0x9a, 0x3a, 0xc6, 0x5d, 0x9c, 0x30, 0xb0, 0x76, 0x1a, 0x94, 0x0d, 0xd1, 0x5d, 0x28,
+ 0x9c, 0xe9, 0x86, 0x8e, 0xdb, 0xcd, 0xa4, 0x30, 0xd6, 0x90, 0x33, 0xe2, 0x11, 0xdf, 0x19, 0xa1,
+ 0x8f, 0xa1, 0x6c, 0x74, 0xcd, 0xfe, 0x79, 0xf3, 0x71, 0x92, 0x3a, 0xdb, 0x3f, 0x15, 0xaa, 0x3f,
+ 0x1f, 0x5d, 0x30, 0x3e, 0x8f, 0xa3, 0x1a, 0x7a, 0x5a, 0xef, 0xb4, 0x5b, 0x1c, 0x9a, 0x55, 0x95,
+ 0xe5, 0x4a, 0xbb, 0xbd, 0x86, 0xb6, 0xf9, 0x3b, 0x26, 0xc2, 0x1e, 0xdb, 0x50, 0xf9, 0xfe, 0xc6,
+ 0x84, 0x34, 0xc8, 0xd7, 0x7b, 0x3d, 0xdd, 0x68, 0x25, 0x7f, 0xbf, 0xf1, 0xd5, 0x67, 0x33, 0xe2,
+ 0xd9, 0x11, 0xe2, 0xb4, 0x8b, 0xcf, 0xf4, 0x41, 0xf2, 0xf3, 0x1b, 0xc4, 0x29, 0x8d, 0xae, 0xf7,
+ 0xc6, 0xe1, 0xab, 0xef, 0x2a, 0x3b, 0xdf, 0x7e, 0x57, 0xd9, 0x79, 0x75, 0x55, 0x11, 0xbe, 0xbd,
+ 0xaa, 0x08, 0xff, 0xba, 0xaa, 0xec, 0xfc, 0xe7, 0xaa, 0x22, 0x7c, 0xf3, 0xef, 0x8a, 0x30, 0xcc,
+ 0xb3, 0x46, 0xf6, 0xf9, 0xff, 0x03, 0x00, 0x00, 0xff, 0xff, 0xe5, 0x17, 0x2b, 0x62, 0xdd, 0x0e,
+ 0x00, 0x00,
}
diff --git a/lib/protocol/bep.proto b/lib/protocol/bep.proto
index 008801e57..5ded2e1e0 100644
--- a/lib/protocol/bep.proto
+++ b/lib/protocol/bep.proto
@@ -146,6 +146,7 @@ message Request {
int32 size = 5;
bytes hash = 6;
bool from_temporary = 7;
+ uint32 weak_hash = 8;
}
// Response
diff --git a/lib/protocol/common_test.go b/lib/protocol/common_test.go
index 6fdee7a1b..a12e36104 100644
--- a/lib/protocol/common_test.go
+++ b/lib/protocol/common_test.go
@@ -11,6 +11,7 @@ type TestModel struct {
offset int64
size int
hash []byte
+ weakHash uint32
fromTemporary bool
closedCh chan struct{}
closedErr error
@@ -28,12 +29,13 @@ func (t *TestModel) Index(deviceID DeviceID, folder string, files []FileInfo) {
func (t *TestModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo) {
}
-func (t *TestModel) Request(deviceID DeviceID, folder, name string, offset int64, hash []byte, fromTemporary bool, buf []byte) error {
+func (t *TestModel) Request(deviceID DeviceID, folder, name string, offset int64, hash []byte, weakHash uint32, fromTemporary bool, buf []byte) error {
t.folder = folder
t.name = name
t.offset = offset
t.size = len(buf)
t.hash = hash
+ t.weakHash = weakHash
t.fromTemporary = fromTemporary
copy(buf, t.data)
return nil
diff --git a/lib/protocol/nativemodel_darwin.go b/lib/protocol/nativemodel_darwin.go
index e9ca162cd..8beca8d20 100644
--- a/lib/protocol/nativemodel_darwin.go
+++ b/lib/protocol/nativemodel_darwin.go
@@ -26,7 +26,7 @@ func (m nativeModel) IndexUpdate(deviceID DeviceID, folder string, files []FileI
m.Model.IndexUpdate(deviceID, folder, files)
}
-func (m nativeModel) Request(deviceID DeviceID, folder string, name string, offset int64, hash []byte, fromTemporary bool, buf []byte) error {
+func (m nativeModel) Request(deviceID DeviceID, folder string, name string, offset int64, hash []byte, weakHash uint32, fromTemporary bool, buf []byte) error {
name = norm.NFD.String(name)
- return m.Model.Request(deviceID, folder, name, offset, hash, fromTemporary, buf)
+ return m.Model.Request(deviceID, folder, name, offset, hash, weakHash, fromTemporary, buf)
}
diff --git a/lib/protocol/nativemodel_windows.go b/lib/protocol/nativemodel_windows.go
index db728a212..508625bb6 100644
--- a/lib/protocol/nativemodel_windows.go
+++ b/lib/protocol/nativemodel_windows.go
@@ -25,14 +25,14 @@ func (m nativeModel) IndexUpdate(deviceID DeviceID, folder string, files []FileI
m.Model.IndexUpdate(deviceID, folder, files)
}
-func (m nativeModel) Request(deviceID DeviceID, folder string, name string, offset int64, hash []byte, fromTemporary bool, buf []byte) error {
+func (m nativeModel) Request(deviceID DeviceID, folder string, name string, offset int64, hash []byte, weakHash uint32, fromTemporary bool, buf []byte) error {
if strings.Contains(name, `\`) {
l.Warnf("Dropping request for %s, contains invalid path separator", name)
return ErrNoSuchFile
}
name = filepath.FromSlash(name)
- return m.Model.Request(deviceID, folder, name, offset, hash, fromTemporary, buf)
+ return m.Model.Request(deviceID, folder, name, offset, hash, weakHash, fromTemporary, buf)
}
func fixupFiles(files []FileInfo) []FileInfo {
diff --git a/lib/protocol/protocol.go b/lib/protocol/protocol.go
index 870fe2102..3aac3ccd3 100644
--- a/lib/protocol/protocol.go
+++ b/lib/protocol/protocol.go
@@ -107,7 +107,7 @@ type Model interface {
// An index update was received from the peer device
IndexUpdate(deviceID DeviceID, folder string, files []FileInfo)
// A request was made by the peer device
- Request(deviceID DeviceID, folder string, name string, offset int64, hash []byte, fromTemporary bool, buf []byte) error
+ Request(deviceID DeviceID, folder string, name string, offset int64, hash []byte, weakHash uint32, fromTemporary bool, buf []byte) error
// A cluster configuration message was received
ClusterConfig(deviceID DeviceID, config ClusterConfig)
// The peer device closed the connection
@@ -122,7 +122,7 @@ type Connection interface {
Name() string
Index(folder string, files []FileInfo) error
IndexUpdate(folder string, files []FileInfo) error
- Request(folder string, name string, offset int64, size int, hash []byte, fromTemporary bool) ([]byte, error)
+ Request(folder string, name string, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error)
ClusterConfig(config ClusterConfig)
DownloadProgress(folder string, updates []FileDownloadProgressUpdate)
Statistics() Statistics
@@ -254,7 +254,7 @@ func (c *rawConnection) IndexUpdate(folder string, idx []FileInfo) error {
}
// Request returns the bytes for the specified block after fetching them from the connected peer.
-func (c *rawConnection) Request(folder string, name string, offset int64, size int, hash []byte, fromTemporary bool) ([]byte, error) {
+func (c *rawConnection) Request(folder string, name string, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
c.nextIDMut.Lock()
id := c.nextID
c.nextID++
@@ -275,6 +275,7 @@ func (c *rawConnection) Request(folder string, name string, offset int64, size i
Offset: offset,
Size: int32(size),
Hash: hash,
+ WeakHash: weakHash,
FromTemporary: fromTemporary,
}, nil)
if !ok {
@@ -584,7 +585,7 @@ func (c *rawConnection) handleRequest(req Request) {
buf = make([]byte, size)
}
- err := c.receiver.Request(c.id, req.Folder, req.Name, req.Offset, req.Hash, req.FromTemporary, buf)
+ err := c.receiver.Request(c.id, req.Folder, req.Name, req.Offset, req.Hash, req.WeakHash, req.FromTemporary, buf)
if err != nil {
c.send(&Response{
ID: req.ID,
diff --git a/lib/protocol/protocol_test.go b/lib/protocol/protocol_test.go
index bccff8d52..8d193a28e 100644
--- a/lib/protocol/protocol_test.go
+++ b/lib/protocol/protocol_test.go
@@ -72,7 +72,7 @@ func TestClose(t *testing.T) {
c0.Index("default", nil)
c0.Index("default", nil)
- if _, err := c0.Request("default", "foo", 0, 0, nil, false); err == nil {
+ if _, err := c0.Request("default", "foo", 0, 0, nil, 0, false); err == nil {
t.Error("Request should return an error")
}
}
diff --git a/lib/protocol/wireformat.go b/lib/protocol/wireformat.go
index bc76ec145..cf465d5c8 100644
--- a/lib/protocol/wireformat.go
+++ b/lib/protocol/wireformat.go
@@ -34,7 +34,7 @@ func (c wireFormatConnection) IndexUpdate(folder string, fs []FileInfo) error {
return c.Connection.IndexUpdate(folder, myFs)
}
-func (c wireFormatConnection) Request(folder, name string, offset int64, size int, hash []byte, fromTemporary bool) ([]byte, error) {
+func (c wireFormatConnection) Request(folder, name string, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
name = norm.NFC.String(filepath.ToSlash(name))
- return c.Connection.Request(folder, name, offset, size, hash, fromTemporary)
+ return c.Connection.Request(folder, name, offset, size, hash, weakHash, fromTemporary)
}
diff --git a/lib/scanner/blockqueue.go b/lib/scanner/blockqueue.go
index 2cfeec84f..6e91352ed 100644
--- a/lib/scanner/blockqueue.go
+++ b/lib/scanner/blockqueue.go
@@ -62,26 +62,24 @@ func HashFile(ctx context.Context, fs fs.Filesystem, path string, blockSize int,
// workers are used in parallel. The outbox will become closed when the inbox
// is closed and all items handled.
type parallelHasher struct {
- fs fs.Filesystem
- workers int
- outbox chan<- protocol.FileInfo
- inbox <-chan protocol.FileInfo
- counter Counter
- done chan<- struct{}
- useWeakHashes bool
- wg sync.WaitGroup
+ fs fs.Filesystem
+ workers int
+ outbox chan<- protocol.FileInfo
+ inbox <-chan protocol.FileInfo
+ counter Counter
+ done chan<- struct{}
+ wg sync.WaitGroup
}
-func newParallelHasher(ctx context.Context, fs fs.Filesystem, workers int, outbox chan<- protocol.FileInfo, inbox <-chan protocol.FileInfo, counter Counter, done chan<- struct{}, useWeakHashes bool) {
+func newParallelHasher(ctx context.Context, fs fs.Filesystem, workers int, outbox chan<- protocol.FileInfo, inbox <-chan protocol.FileInfo, counter Counter, done chan<- struct{}) {
ph := ¶llelHasher{
- fs: fs,
- workers: workers,
- outbox: outbox,
- inbox: inbox,
- counter: counter,
- done: done,
- useWeakHashes: useWeakHashes,
- wg: sync.NewWaitGroup(),
+ fs: fs,
+ workers: workers,
+ outbox: outbox,
+ inbox: inbox,
+ counter: counter,
+ done: done,
+ wg: sync.NewWaitGroup(),
}
for i := 0; i < workers; i++ {
@@ -106,7 +104,7 @@ func (ph *parallelHasher) hashFiles(ctx context.Context) {
panic("Bug. Asked to hash a directory or a deleted file.")
}
- blocks, err := HashFile(ctx, ph.fs, f.Name, f.BlockSize(), ph.counter, ph.useWeakHashes)
+ blocks, err := HashFile(ctx, ph.fs, f.Name, f.BlockSize(), ph.counter, true)
if err != nil {
l.Debugln("hash error:", f.Name, err)
continue
diff --git a/lib/scanner/blocks.go b/lib/scanner/blocks.go
index 88287b681..3d3e641ec 100644
--- a/lib/scanner/blocks.go
+++ b/lib/scanner/blocks.go
@@ -7,6 +7,7 @@
package scanner
import (
+ "bytes"
"context"
"hash"
"io"
@@ -107,6 +108,29 @@ func Blocks(ctx context.Context, r io.Reader, blocksize int, sizehint int64, cou
return blocks, nil
}
+func Validate(buf, hash []byte, weakHash uint32) bool {
+ rd := bytes.NewReader(buf)
+ if weakHash != 0 {
+ whf := adler32.New()
+ if _, err := io.Copy(whf, rd); err == nil && whf.Sum32() == weakHash {
+ return true
+ }
+ // Copy error or mismatch, go to next algo.
+ rd.Seek(0, io.SeekStart)
+ }
+
+ if len(hash) > 0 {
+ hf := sha256.New()
+ if _, err := io.Copy(hf, rd); err == nil {
+ // Sum allocates, so let's hope we don't hit this often.
+ return bytes.Equal(hf.Sum(nil), hash)
+ }
+ }
+
+ // Both algos failed or no hashes were specified. Assume it's all good.
+ return true
+}
+
type noopHash struct{}
func (noopHash) Sum32() uint32 { return 0 }
diff --git a/lib/scanner/walk.go b/lib/scanner/walk.go
index 0be3fde05..3f1f22905 100644
--- a/lib/scanner/walk.go
+++ b/lib/scanner/walk.go
@@ -64,8 +64,6 @@ type Config struct {
// Optional progress tick interval which defines how often FolderScanProgress
// events are emitted. Negative number means disabled.
ProgressTickIntervalS int
- // Whether or not we should also compute weak hashes
- UseWeakHashes bool
// Whether to use large blocks for large files or the old standard of 128KiB for everything.
UseLargeBlocks bool
}
@@ -120,7 +118,7 @@ func (w *walker) walk(ctx context.Context) chan protocol.FileInfo {
// We're not required to emit scan progress events, just kick off hashers,
// and feed inputs directly from the walker.
if w.ProgressTickIntervalS < 0 {
- newParallelHasher(ctx, w.Filesystem, w.Hashers, finishedChan, toHashChan, nil, nil, w.UseWeakHashes)
+ newParallelHasher(ctx, w.Filesystem, w.Hashers, finishedChan, toHashChan, nil, nil)
return finishedChan
}
@@ -151,7 +149,7 @@ func (w *walker) walk(ctx context.Context) chan protocol.FileInfo {
done := make(chan struct{})
progress := newByteCounter()
- newParallelHasher(ctx, w.Filesystem, w.Hashers, finishedChan, realToHashChan, progress, done, w.UseWeakHashes)
+ newParallelHasher(ctx, w.Filesystem, w.Hashers, finishedChan, realToHashChan, progress, done)
// A routine which actually emits the FolderScanProgress events
// every w.ProgressTicker ticks, until the hasher routines terminate.
diff --git a/lib/weakhash/benchmark_test.go b/lib/weakhash/benchmark_test.go
index 87db3df4a..a067e9547 100644
--- a/lib/weakhash/benchmark_test.go
+++ b/lib/weakhash/benchmark_test.go
@@ -11,6 +11,10 @@ import (
"testing"
"github.com/chmduquesne/rollinghash/adler32"
+ "github.com/chmduquesne/rollinghash/bozo32"
+ "github.com/chmduquesne/rollinghash/buzhash32"
+ "github.com/chmduquesne/rollinghash/buzhash64"
+ "github.com/chmduquesne/rollinghash/rabinkarp64"
)
const testFile = "../model/testdata/~syncthing~file.tmp"
@@ -59,3 +63,115 @@ func BenchmarkWeakHashAdler32Roll(b *testing.B) {
b.SetBytes(size)
}
+
+func BenchmarkWeakHashRabinKarp64(b *testing.B) {
+ data := make([]byte, size)
+ hf := rabinkarp64.New()
+
+ for i := 0; i < b.N; i++ {
+ hf.Write(data)
+ }
+
+ _ = hf.Sum64()
+ b.SetBytes(size)
+}
+
+func BenchmarkWeakHashRabinKarp64Roll(b *testing.B) {
+ data := make([]byte, size)
+ hf := rabinkarp64.New()
+ hf.Write(data)
+
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ for i := 0; i <= size; i++ {
+ hf.Roll('a')
+ }
+ }
+
+ b.SetBytes(size)
+}
+
+func BenchmarkWeakHashBozo32(b *testing.B) {
+ data := make([]byte, size)
+ hf := bozo32.New()
+
+ for i := 0; i < b.N; i++ {
+ hf.Write(data)
+ }
+
+ _ = hf.Sum32()
+ b.SetBytes(size)
+}
+
+func BenchmarkWeakHashBozo32Roll(b *testing.B) {
+ data := make([]byte, size)
+ hf := bozo32.New()
+ hf.Write(data)
+
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ for i := 0; i <= size; i++ {
+ hf.Roll('a')
+ }
+ }
+
+ b.SetBytes(size)
+}
+
+func BenchmarkWeakHashBuzhash32(b *testing.B) {
+ data := make([]byte, size)
+ hf := buzhash32.New()
+
+ for i := 0; i < b.N; i++ {
+ hf.Write(data)
+ }
+
+ _ = hf.Sum32()
+ b.SetBytes(size)
+}
+
+func BenchmarkWeakHashBuzhash32Roll(b *testing.B) {
+ data := make([]byte, size)
+ hf := buzhash32.New()
+ hf.Write(data)
+
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ for i := 0; i <= size; i++ {
+ hf.Roll('a')
+ }
+ }
+
+ b.SetBytes(size)
+}
+
+func BenchmarkWeakHashBuzhash64(b *testing.B) {
+ data := make([]byte, size)
+ hf := buzhash64.New()
+
+ for i := 0; i < b.N; i++ {
+ hf.Write(data)
+ }
+
+ _ = hf.Sum64()
+ b.SetBytes(size)
+}
+
+func BenchmarkWeakHashBuzhash64Roll(b *testing.B) {
+ data := make([]byte, size)
+ hf := buzhash64.New()
+ hf.Write(data)
+
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ for i := 0; i <= size; i++ {
+ hf.Roll('a')
+ }
+ }
+
+ b.SetBytes(size)
+}
diff --git a/lib/weakhash/weakhash.go b/lib/weakhash/weakhash.go
index fabacfeee..58e6f0d6e 100644
--- a/lib/weakhash/weakhash.go
+++ b/lib/weakhash/weakhash.go
@@ -20,10 +20,6 @@ const (
maxWeakhashFinderHits = 10
)
-var (
- Enabled = true
-)
-
// Find finds all the blocks of the given size within io.Reader that matches
// the hashes provided, and returns a hash -> slice of offsets within reader
// map, that produces the same weak hash.
diff --git a/vendor/manifest b/vendor/manifest
index 0dc380ad4..cfe366d0c 100644
--- a/vendor/manifest
+++ b/vendor/manifest
@@ -94,7 +94,7 @@
"importpath": "github.com/chmduquesne/rollinghash",
"repository": "https://github.com/chmduquesne/rollinghash",
"vcs": "git",
- "revision": "3dc7875a1f890f9bcf0619adb5571fc6f7d516bb",
+ "revision": "abb8cbaf9915e48ee20cae94bcd94221b61707a2",
"branch": "master",
"notests": true
},