bep_extensions.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. // Copyright (C) 2014 The Protocol Authors.
  2. //go:generate go run ../../script/protofmt.go bep.proto
  3. //go:generate protoc -I ../../ -I . --gogofast_out=. bep.proto
  4. package protocol
  5. import (
  6. "bytes"
  7. "encoding/binary"
  8. "errors"
  9. "fmt"
  10. "runtime"
  11. "time"
  12. "github.com/syncthing/syncthing/lib/rand"
  13. "github.com/syncthing/syncthing/lib/sha256"
  14. )
  15. const (
  16. SyntheticDirectorySize = 128
  17. HelloMessageMagic uint32 = 0x2EA7D90B
  18. Version13HelloMagic uint32 = 0x9F79BC40 // old
  19. )
  20. // FileIntf is the set of methods implemented by both FileInfo and
  21. // db.FileInfoTruncated.
  22. type FileIntf interface {
  23. FileSize() int64
  24. FileName() string
  25. FileLocalFlags() uint32
  26. IsDeleted() bool
  27. IsInvalid() bool
  28. IsIgnored() bool
  29. IsUnsupported() bool
  30. MustRescan() bool
  31. IsReceiveOnlyChanged() bool
  32. IsDirectory() bool
  33. IsSymlink() bool
  34. ShouldConflict() bool
  35. HasPermissionBits() bool
  36. SequenceNo() int64
  37. BlockSize() int
  38. FileVersion() Vector
  39. FileType() FileInfoType
  40. FilePermissions() uint32
  41. FileModifiedBy() ShortID
  42. ModTime() time.Time
  43. }
  44. func (m Hello) Magic() uint32 {
  45. return HelloMessageMagic
  46. }
  47. func (f FileInfo) String() string {
  48. switch f.Type {
  49. case FileInfoTypeDirectory:
  50. return fmt.Sprintf("Directory{Name:%q, Sequence:%d, Permissions:0%o, ModTime:%v, Version:%v, Deleted:%v, Invalid:%v, LocalFlags:0x%x, NoPermissions:%v}",
  51. f.Name, f.Sequence, f.Permissions, f.ModTime(), f.Version, f.Deleted, f.RawInvalid, f.LocalFlags, f.NoPermissions)
  52. case FileInfoTypeFile:
  53. return fmt.Sprintf("File{Name:%q, Sequence:%d, Permissions:0%o, ModTime:%v, Version:%v, Length:%d, Deleted:%v, Invalid:%v, LocalFlags:0x%x, NoPermissions:%v, BlockSize:%d, Blocks:%v, BlocksHash:%x}",
  54. f.Name, f.Sequence, f.Permissions, f.ModTime(), f.Version, f.Size, f.Deleted, f.RawInvalid, f.LocalFlags, f.NoPermissions, f.RawBlockSize, f.Blocks, f.BlocksHash)
  55. case FileInfoTypeSymlink, FileInfoTypeDeprecatedSymlinkDirectory, FileInfoTypeDeprecatedSymlinkFile:
  56. return fmt.Sprintf("Symlink{Name:%q, Type:%v, Sequence:%d, Version:%v, Deleted:%v, Invalid:%v, LocalFlags:0x%x, NoPermissions:%v, SymlinkTarget:%q}",
  57. f.Name, f.Type, f.Sequence, f.Version, f.Deleted, f.RawInvalid, f.LocalFlags, f.NoPermissions, f.SymlinkTarget)
  58. default:
  59. panic("mystery file type detected")
  60. }
  61. }
  62. func (f FileInfo) IsDeleted() bool {
  63. return f.Deleted
  64. }
  65. func (f FileInfo) IsInvalid() bool {
  66. return f.RawInvalid || f.LocalFlags&LocalInvalidFlags != 0
  67. }
  68. func (f FileInfo) IsUnsupported() bool {
  69. return f.LocalFlags&FlagLocalUnsupported != 0
  70. }
  71. func (f FileInfo) IsIgnored() bool {
  72. return f.LocalFlags&FlagLocalIgnored != 0
  73. }
  74. func (f FileInfo) MustRescan() bool {
  75. return f.LocalFlags&FlagLocalMustRescan != 0
  76. }
  77. func (f FileInfo) IsReceiveOnlyChanged() bool {
  78. return f.LocalFlags&FlagLocalReceiveOnly != 0
  79. }
  80. func (f FileInfo) IsDirectory() bool {
  81. return f.Type == FileInfoTypeDirectory
  82. }
  83. func (f FileInfo) ShouldConflict() bool {
  84. return f.LocalFlags&LocalConflictFlags != 0
  85. }
  86. func (f FileInfo) IsSymlink() bool {
  87. switch f.Type {
  88. case FileInfoTypeSymlink, FileInfoTypeDeprecatedSymlinkDirectory, FileInfoTypeDeprecatedSymlinkFile:
  89. return true
  90. default:
  91. return false
  92. }
  93. }
  94. func (f FileInfo) HasPermissionBits() bool {
  95. return !f.NoPermissions
  96. }
  97. func (f FileInfo) FileSize() int64 {
  98. if f.Deleted {
  99. return 0
  100. }
  101. if f.IsDirectory() || f.IsSymlink() {
  102. return SyntheticDirectorySize
  103. }
  104. return f.Size
  105. }
  106. func (f FileInfo) BlockSize() int {
  107. if f.RawBlockSize == 0 {
  108. return MinBlockSize
  109. }
  110. return int(f.RawBlockSize)
  111. }
  112. func (f FileInfo) FileName() string {
  113. return f.Name
  114. }
  115. func (f FileInfo) FileLocalFlags() uint32 {
  116. return f.LocalFlags
  117. }
  118. func (f FileInfo) ModTime() time.Time {
  119. return time.Unix(f.ModifiedS, int64(f.ModifiedNs))
  120. }
  121. func (f FileInfo) SequenceNo() int64 {
  122. return f.Sequence
  123. }
  124. func (f FileInfo) FileVersion() Vector {
  125. return f.Version
  126. }
  127. func (f FileInfo) FileType() FileInfoType {
  128. return f.Type
  129. }
  130. func (f FileInfo) FilePermissions() uint32 {
  131. return f.Permissions
  132. }
  133. func (f FileInfo) FileModifiedBy() ShortID {
  134. return f.ModifiedBy
  135. }
  136. // WinsConflict returns true if "f" is the one to choose when it is in
  137. // conflict with "other".
  138. func WinsConflict(f, other FileIntf) bool {
  139. // If only one of the files is invalid, that one loses.
  140. if f.IsInvalid() != other.IsInvalid() {
  141. return !f.IsInvalid()
  142. }
  143. // If a modification is in conflict with a delete, we pick the
  144. // modification.
  145. if !f.IsDeleted() && other.IsDeleted() {
  146. return true
  147. }
  148. if f.IsDeleted() && !other.IsDeleted() {
  149. return false
  150. }
  151. // The one with the newer modification time wins.
  152. if f.ModTime().After(other.ModTime()) {
  153. return true
  154. }
  155. if f.ModTime().Before(other.ModTime()) {
  156. return false
  157. }
  158. // The modification times were equal. Use the device ID in the version
  159. // vector as tie breaker.
  160. return f.FileVersion().Compare(other.FileVersion()) == ConcurrentGreater
  161. }
  162. func (f FileInfo) IsEmpty() bool {
  163. return f.Version.Counters == nil
  164. }
  165. func (f FileInfo) IsEquivalent(other FileInfo, modTimeWindow time.Duration) bool {
  166. return f.isEquivalent(other, modTimeWindow, false, false, 0)
  167. }
  168. func (f FileInfo) IsEquivalentOptional(other FileInfo, modTimeWindow time.Duration, ignorePerms bool, ignoreBlocks bool, ignoreFlags uint32) bool {
  169. return f.isEquivalent(other, modTimeWindow, ignorePerms, ignoreBlocks, ignoreFlags)
  170. }
  171. // isEquivalent checks that the two file infos represent the same actual file content,
  172. // i.e. it does purposely not check only selected (see below) struct members.
  173. // Permissions (config) and blocks (scanning) can be excluded from the comparison.
  174. // Any file info is not "equivalent", if it has different
  175. // - type
  176. // - deleted flag
  177. // - invalid flag
  178. // - permissions, unless they are ignored
  179. // A file is not "equivalent", if it has different
  180. // - modification time (difference bigger than modTimeWindow)
  181. // - size
  182. // - blocks, unless there are no blocks to compare (scanning)
  183. // A symlink is not "equivalent", if it has different
  184. // - target
  185. // A directory does not have anything specific to check.
  186. func (f FileInfo) isEquivalent(other FileInfo, modTimeWindow time.Duration, ignorePerms bool, ignoreBlocks bool, ignoreFlags uint32) bool {
  187. if f.MustRescan() || other.MustRescan() {
  188. // These are per definition not equivalent because they don't
  189. // represent a valid state, even if both happen to have the
  190. // MustRescan bit set.
  191. return false
  192. }
  193. // Mask out the ignored local flags before checking IsInvalid() below
  194. f.LocalFlags &^= ignoreFlags
  195. other.LocalFlags &^= ignoreFlags
  196. if f.Name != other.Name || f.Type != other.Type || f.Deleted != other.Deleted || f.IsInvalid() != other.IsInvalid() {
  197. return false
  198. }
  199. if !ignorePerms && !f.NoPermissions && !other.NoPermissions && !PermsEqual(f.Permissions, other.Permissions) {
  200. return false
  201. }
  202. switch f.Type {
  203. case FileInfoTypeFile:
  204. return f.Size == other.Size && ModTimeEqual(f.ModTime(), other.ModTime(), modTimeWindow) && (ignoreBlocks || f.BlocksEqual(other))
  205. case FileInfoTypeSymlink:
  206. return f.SymlinkTarget == other.SymlinkTarget
  207. case FileInfoTypeDirectory:
  208. return true
  209. }
  210. return false
  211. }
  212. func ModTimeEqual(a, b time.Time, modTimeWindow time.Duration) bool {
  213. if a.Equal(b) {
  214. return true
  215. }
  216. diff := a.Sub(b)
  217. if diff < 0 {
  218. diff *= -1
  219. }
  220. return diff < modTimeWindow
  221. }
  222. func PermsEqual(a, b uint32) bool {
  223. switch runtime.GOOS {
  224. case "windows":
  225. // There is only writeable and read only, represented for user, group
  226. // and other equally. We only compare against user.
  227. return a&0600 == b&0600
  228. default:
  229. // All bits count
  230. return a&0777 == b&0777
  231. }
  232. }
  233. // BlocksEqual returns true when the two files have identical block lists.
  234. func (f FileInfo) BlocksEqual(other FileInfo) bool {
  235. // If both sides have blocks hashes then we can just compare those.
  236. if len(f.BlocksHash) > 0 && len(other.BlocksHash) > 0 {
  237. return bytes.Equal(f.BlocksHash, other.BlocksHash)
  238. }
  239. // Actually compare the block lists in full.
  240. return blocksEqual(f.Blocks, other.Blocks)
  241. }
  242. // blocksEqual returns whether two slices of blocks are exactly the same hash
  243. // and index pair wise.
  244. func blocksEqual(a, b []BlockInfo) bool {
  245. if len(b) != len(a) {
  246. return false
  247. }
  248. for i, sblk := range a {
  249. if !bytes.Equal(sblk.Hash, b[i].Hash) {
  250. return false
  251. }
  252. }
  253. return true
  254. }
  255. func (f *FileInfo) SetMustRescan(by ShortID) {
  256. f.setLocalFlags(by, FlagLocalMustRescan)
  257. }
  258. func (f *FileInfo) SetIgnored(by ShortID) {
  259. f.setLocalFlags(by, FlagLocalIgnored)
  260. }
  261. func (f *FileInfo) SetUnsupported(by ShortID) {
  262. f.setLocalFlags(by, FlagLocalUnsupported)
  263. }
  264. func (f *FileInfo) SetDeleted(by ShortID) {
  265. f.ModifiedBy = by
  266. f.Deleted = true
  267. f.Version = f.Version.Update(by)
  268. f.ModifiedS = time.Now().Unix()
  269. f.setNoContent()
  270. }
  271. func (f *FileInfo) setLocalFlags(by ShortID, flags uint32) {
  272. f.RawInvalid = false
  273. f.LocalFlags = flags
  274. f.ModifiedBy = by
  275. f.setNoContent()
  276. }
  277. func (f *FileInfo) setNoContent() {
  278. f.Blocks = nil
  279. f.BlocksHash = nil
  280. f.Size = 0
  281. }
  282. func (b BlockInfo) String() string {
  283. return fmt.Sprintf("Block{%d/%d/%d/%x}", b.Offset, b.Size, b.WeakHash, b.Hash)
  284. }
  285. // IsEmpty returns true if the block is a full block of zeroes.
  286. func (b BlockInfo) IsEmpty() bool {
  287. if v, ok := sha256OfEmptyBlock[int(b.Size)]; ok {
  288. return bytes.Equal(b.Hash, v[:])
  289. }
  290. return false
  291. }
  292. type IndexID uint64
  293. func (i IndexID) String() string {
  294. return fmt.Sprintf("0x%016X", uint64(i))
  295. }
  296. func (i IndexID) Marshal() ([]byte, error) {
  297. bs := make([]byte, 8)
  298. binary.BigEndian.PutUint64(bs, uint64(i))
  299. return bs, nil
  300. }
  301. func (i *IndexID) Unmarshal(bs []byte) error {
  302. if len(bs) != 8 {
  303. return errors.New("incorrect IndexID length")
  304. }
  305. *i = IndexID(binary.BigEndian.Uint64(bs))
  306. return nil
  307. }
  308. func NewIndexID() IndexID {
  309. return IndexID(rand.Uint64())
  310. }
  311. func (f Folder) Description() string {
  312. // used by logging stuff
  313. if f.Label == "" {
  314. return f.ID
  315. }
  316. return fmt.Sprintf("%q (%s)", f.Label, f.ID)
  317. }
  318. func BlocksHash(bs []BlockInfo) []byte {
  319. h := sha256.New()
  320. for _, b := range bs {
  321. _, _ = h.Write(b.Hash)
  322. }
  323. return h.Sum(nil)
  324. }
  325. func VectorHash(v Vector) []byte {
  326. h := sha256.New()
  327. for _, c := range v.Counters {
  328. if err := binary.Write(h, binary.BigEndian, c.ID); err != nil {
  329. panic("impossible: failed to write c.ID to hash function: " + err.Error())
  330. }
  331. if err := binary.Write(h, binary.BigEndian, c.Value); err != nil {
  332. panic("impossible: failed to write c.Value to hash function: " + err.Error())
  333. }
  334. }
  335. return h.Sum(nil)
  336. }