contract.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. // Copyright (C) 2020 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package contract
  7. import (
  8. "database/sql/driver"
  9. "encoding/json"
  10. "errors"
  11. "reflect"
  12. "strconv"
  13. "time"
  14. "github.com/syncthing/syncthing/lib/structutil"
  15. )
  16. type Report struct {
  17. // Generated
  18. Received time.Time `json:"-"` // Only from DB
  19. Date string `json:"date,omitempty"`
  20. Address string `json:"address,omitempty"`
  21. // v1 fields
  22. UniqueID string `json:"uniqueID,omitempty" since:"1"`
  23. Version string `json:"version,omitempty" since:"1"`
  24. LongVersion string `json:"longVersion,omitempty" since:"1"`
  25. Platform string `json:"platform,omitempty" since:"1"`
  26. NumFolders int `json:"numFolders,omitempty" since:"1"`
  27. NumDevices int `json:"numDevices,omitempty" since:"1"`
  28. TotFiles int `json:"totFiles,omitempty" since:"1"`
  29. FolderMaxFiles int `json:"folderMaxFiles,omitempty" since:"1"`
  30. TotMiB int `json:"totMiB,omitempty" since:"1"`
  31. FolderMaxMiB int `json:"folderMaxMiB,omitempty" since:"1"`
  32. MemoryUsageMiB int `json:"memoryUsageMiB,omitempty" since:"1"`
  33. SHA256Perf float64 `json:"sha256Perf,omitempty" since:"1"`
  34. HashPerf float64 `json:"hashPerf,omitempty" since:"1"` // Was previously not stored server-side
  35. MemorySize int `json:"memorySize,omitempty" since:"1"`
  36. // v2 fields
  37. URVersion int `json:"urVersion,omitempty" since:"2"`
  38. NumCPU int `json:"numCPU,omitempty" since:"2"`
  39. FolderUses struct {
  40. SendOnly int `json:"sendonly,omitempty" since:"2"`
  41. SendReceive int `json:"sendreceive,omitempty" since:"2"` // Was previously not stored server-side
  42. ReceiveOnly int `json:"receiveonly,omitempty" since:"2"`
  43. IgnorePerms int `json:"ignorePerms,omitempty" since:"2"`
  44. IgnoreDelete int `json:"ignoreDelete,omitempty" since:"2"`
  45. AutoNormalize int `json:"autoNormalize,omitempty" since:"2"`
  46. SimpleVersioning int `json:"simpleVersioning,omitempty" since:"2"`
  47. ExternalVersioning int `json:"externalVersioning,omitempty" since:"2"`
  48. StaggeredVersioning int `json:"staggeredVersioning,omitempty" since:"2"`
  49. TrashcanVersioning int `json:"trashcanVersioning,omitempty" since:"2"`
  50. } `json:"folderUses,omitempty" since:"2"`
  51. DeviceUses struct {
  52. Introducer int `json:"introducer,omitempty" since:"2"`
  53. CustomCertName int `json:"customCertName,omitempty" since:"2"`
  54. CompressAlways int `json:"compressAlways,omitempty" since:"2"`
  55. CompressMetadata int `json:"compressMetadata,omitempty" since:"2"`
  56. CompressNever int `json:"compressNever,omitempty" since:"2"`
  57. DynamicAddr int `json:"dynamicAddr,omitempty" since:"2"`
  58. StaticAddr int `json:"staticAddr,omitempty" since:"2"`
  59. } `json:"deviceUses,omitempty" since:"2"`
  60. Announce struct {
  61. GlobalEnabled bool `json:"globalEnabled,omitempty" since:"2"`
  62. LocalEnabled bool `json:"localEnabled,omitempty" since:"2"`
  63. DefaultServersDNS int `json:"defaultServersDNS,omitempty" since:"2"`
  64. DefaultServersIP int `json:"defaultServersIP,omitempty" since:"2"` // Deprecated and not provided client-side anymore
  65. OtherServers int `json:"otherServers,omitempty" since:"2"`
  66. } `json:"announce,omitempty" since:"2"`
  67. Relays struct {
  68. Enabled bool `json:"enabled,omitempty" since:"2"`
  69. DefaultServers int `json:"defaultServers,omitempty" since:"2"`
  70. OtherServers int `json:"otherServers,omitempty" since:"2"`
  71. } `json:"relays,omitempty" since:"2"`
  72. UsesRateLimit bool `json:"usesRateLimit,omitempty" since:"2"`
  73. UpgradeAllowedManual bool `json:"upgradeAllowedManual,omitempty" since:"2"`
  74. UpgradeAllowedAuto bool `json:"upgradeAllowedAuto,omitempty" since:"2"`
  75. // V2.5 fields (fields that were in v2 but never added to the database
  76. UpgradeAllowedPre bool `json:"upgradeAllowedPre,omitempty" since:"2"`
  77. RescanIntvs []int `json:"rescanIntvs,omitempty" since:"2"`
  78. // v3 fields
  79. Uptime int `json:"uptime,omitempty" since:"3"`
  80. NATType string `json:"natType,omitempty" since:"3"`
  81. AlwaysLocalNets bool `json:"alwaysLocalNets,omitempty" since:"3"`
  82. CacheIgnoredFiles bool `json:"cacheIgnoredFiles,omitempty" since:"3"`
  83. OverwriteRemoteDeviceNames bool `json:"overwriteRemoteDeviceNames,omitempty" since:"3"`
  84. ProgressEmitterEnabled bool `json:"progressEmitterEnabled,omitempty" since:"3"`
  85. CustomDefaultFolderPath bool `json:"customDefaultFolderPath,omitempty" since:"3"`
  86. WeakHashSelection string `json:"weakHashSelection,omitempty" since:"3"` // Deprecated and not provided client-side anymore
  87. CustomTrafficClass bool `json:"customTrafficClass,omitempty" since:"3"`
  88. CustomTempIndexMinBlocks bool `json:"customTempIndexMinBlocks,omitempty" since:"3"`
  89. TemporariesDisabled bool `json:"temporariesDisabled,omitempty" since:"3"`
  90. TemporariesCustom bool `json:"temporariesCustom,omitempty" since:"3"`
  91. LimitBandwidthInLan bool `json:"limitBandwidthInLan,omitempty" since:"3"`
  92. CustomReleaseURL bool `json:"customReleaseURL,omitempty" since:"3"`
  93. RestartOnWakeup bool `json:"restartOnWakeup,omitempty" since:"3"`
  94. CustomStunServers bool `json:"customStunServers,omitempty" since:"3"`
  95. FolderUsesV3 struct {
  96. ScanProgressDisabled int `json:"scanProgressDisabled,omitempty" since:"3"`
  97. ConflictsDisabled int `json:"conflictsDisabled,omitempty" since:"3"`
  98. ConflictsUnlimited int `json:"conflictsUnlimited,omitempty" since:"3"`
  99. ConflictsOther int `json:"conflictsOther,omitempty" since:"3"`
  100. DisableSparseFiles int `json:"disableSparseFiles,omitempty" since:"3"`
  101. DisableTempIndexes int `json:"disableTempIndexes,omitempty" since:"3"`
  102. AlwaysWeakHash int `json:"alwaysWeakHash,omitempty" since:"3"`
  103. CustomWeakHashThreshold int `json:"customWeakHashThreshold,omitempty" since:"3"`
  104. FsWatcherEnabled int `json:"fsWatcherEnabled,omitempty" since:"3"`
  105. PullOrder map[string]int `json:"pullOrder,omitempty" since:"3"`
  106. FilesystemType map[string]int `json:"filesystemType,omitempty" since:"3"`
  107. FsWatcherDelays []int `json:"fsWatcherDelays,omitempty" since:"3"`
  108. CustomMarkerName int `json:"customMarkerName,omitempty" since:"3"`
  109. CopyOwnershipFromParent int `json:"copyOwnershipFromParent,omitempty" since:"3"`
  110. ModTimeWindowS []int `json:"modTimeWindowS,omitempty" since:"3"`
  111. MaxConcurrentWrites []int `json:"maxConcurrentWrites,omitempty" since:"3"`
  112. DisableFsync int `json:"disableFsync,omitempty" since:"3"`
  113. BlockPullOrder map[string]int `json:"blockPullOrder,omitempty" since:"3"`
  114. CopyRangeMethod map[string]int `json:"copyRangeMethod,omitempty" since:"3"`
  115. CaseSensitiveFS int `json:"caseSensitiveFS,omitempty" since:"3"`
  116. ReceiveEncrypted int `json:"receiveencrypted,omitempty" since:"3"`
  117. } `json:"folderUsesV3,omitempty" since:"3"`
  118. DeviceUsesV3 struct {
  119. Untrusted int `json:"untrusted,omitempty" since:"3"`
  120. } `json:"deviceUsesV3,omitempty" since:"3"`
  121. GUIStats struct {
  122. Enabled int `json:"enabled,omitempty" since:"3"`
  123. UseTLS int `json:"useTLS,omitempty" since:"3"`
  124. UseAuth int `json:"useAuth,omitempty" since:"3"`
  125. InsecureAdminAccess int `json:"insecureAdminAccess,omitempty" since:"3"`
  126. Debugging int `json:"debugging,omitempty" since:"3"`
  127. InsecureSkipHostCheck int `json:"insecureSkipHostCheck,omitempty" since:"3"`
  128. InsecureAllowFrameLoading int `json:"insecureAllowFrameLoading,omitempty" since:"3"`
  129. ListenLocal int `json:"listenLocal,omitempty" since:"3"`
  130. ListenUnspecified int `json:"listenUnspecified,omitempty" since:"3"`
  131. Theme map[string]int `json:"theme,omitempty" since:"3"`
  132. } `json:"guiStats,omitempty" since:"3"`
  133. BlockStats struct {
  134. Total int `json:"total,omitempty" since:"3"`
  135. Renamed int `json:"renamed,omitempty" since:"3"`
  136. Reused int `json:"reused,omitempty" since:"3"`
  137. Pulled int `json:"pulled,omitempty" since:"3"`
  138. CopyOrigin int `json:"copyOrigin,omitempty" since:"3"`
  139. CopyOriginShifted int `json:"copyOriginShifted,omitempty" since:"3"`
  140. CopyElsewhere int `json:"copyElsewhere,omitempty" since:"3"`
  141. } `json:"blockStats,omitempty" since:"3"`
  142. TransportStats map[string]int `json:"transportStats,omitempty" since:"3"`
  143. IgnoreStats struct {
  144. Lines int `json:"lines,omitempty" since:"3"`
  145. Inverts int `json:"inverts,omitempty" since:"3"`
  146. Folded int `json:"folded,omitempty" since:"3"`
  147. Deletable int `json:"deletable,omitempty" since:"3"`
  148. Rooted int `json:"rooted,omitempty" since:"3"`
  149. Includes int `json:"includes,omitempty" since:"3"`
  150. EscapedIncludes int `json:"escapedIncludes,omitempty" since:"3"`
  151. DoubleStars int `json:"doubleStars,omitempty" since:"3"`
  152. Stars int `json:"stars,omitempty" since:"3"`
  153. } `json:"ignoreStats,omitempty" since:"3"`
  154. // V3 fields added late in the RC
  155. WeakHashEnabled bool `json:"weakHashEnabled,omitempty" since:"3"` // Deprecated and not provided client-side anymore
  156. }
  157. func New() *Report {
  158. r := &Report{}
  159. structutil.FillNil(r)
  160. return r
  161. }
  162. func (r *Report) Validate() error {
  163. if r.UniqueID == "" || r.Version == "" || r.Platform == "" {
  164. return errors.New("missing required field")
  165. }
  166. if len(r.Date) != 8 {
  167. return errors.New("date not initialized")
  168. }
  169. // Some fields may not be null.
  170. if r.RescanIntvs == nil {
  171. r.RescanIntvs = []int{}
  172. }
  173. if r.FolderUsesV3.FsWatcherDelays == nil {
  174. r.FolderUsesV3.FsWatcherDelays = []int{}
  175. }
  176. return nil
  177. }
  178. func (r *Report) ClearForVersion(version int) error {
  179. return clear(r, version)
  180. }
  181. func (r *Report) FieldPointers() []interface{} {
  182. // All the fields of the Report, in the same order as the database fields.
  183. return []interface{}{
  184. &r.Received, &r.UniqueID, &r.Version, &r.LongVersion, &r.Platform,
  185. &r.NumFolders, &r.NumDevices, &r.TotFiles, &r.FolderMaxFiles,
  186. &r.TotMiB, &r.FolderMaxMiB, &r.MemoryUsageMiB, &r.SHA256Perf,
  187. &r.MemorySize, &r.Date,
  188. // V2
  189. &r.URVersion, &r.NumCPU, &r.FolderUses.SendOnly, &r.FolderUses.IgnorePerms,
  190. &r.FolderUses.IgnoreDelete, &r.FolderUses.AutoNormalize, &r.DeviceUses.Introducer,
  191. &r.DeviceUses.CustomCertName, &r.DeviceUses.CompressAlways,
  192. &r.DeviceUses.CompressMetadata, &r.DeviceUses.CompressNever,
  193. &r.DeviceUses.DynamicAddr, &r.DeviceUses.StaticAddr,
  194. &r.Announce.GlobalEnabled, &r.Announce.LocalEnabled,
  195. &r.Announce.DefaultServersDNS, &r.Announce.DefaultServersIP,
  196. &r.Announce.OtherServers, &r.Relays.Enabled, &r.Relays.DefaultServers,
  197. &r.Relays.OtherServers, &r.UsesRateLimit, &r.UpgradeAllowedManual,
  198. &r.UpgradeAllowedAuto, &r.FolderUses.SimpleVersioning,
  199. &r.FolderUses.ExternalVersioning, &r.FolderUses.StaggeredVersioning,
  200. &r.FolderUses.TrashcanVersioning,
  201. // V2.5
  202. &r.UpgradeAllowedPre,
  203. // V3
  204. &r.Uptime, &r.NATType, &r.AlwaysLocalNets, &r.CacheIgnoredFiles,
  205. &r.OverwriteRemoteDeviceNames, &r.ProgressEmitterEnabled, &r.CustomDefaultFolderPath,
  206. &r.WeakHashSelection, &r.CustomTrafficClass, &r.CustomTempIndexMinBlocks,
  207. &r.TemporariesDisabled, &r.TemporariesCustom, &r.LimitBandwidthInLan,
  208. &r.CustomReleaseURL, &r.RestartOnWakeup, &r.CustomStunServers,
  209. &r.FolderUsesV3.ScanProgressDisabled, &r.FolderUsesV3.ConflictsDisabled,
  210. &r.FolderUsesV3.ConflictsUnlimited, &r.FolderUsesV3.ConflictsOther,
  211. &r.FolderUsesV3.DisableSparseFiles, &r.FolderUsesV3.DisableTempIndexes,
  212. &r.FolderUsesV3.AlwaysWeakHash, &r.FolderUsesV3.CustomWeakHashThreshold,
  213. &r.FolderUsesV3.FsWatcherEnabled,
  214. &r.GUIStats.Enabled, &r.GUIStats.UseTLS, &r.GUIStats.UseAuth,
  215. &r.GUIStats.InsecureAdminAccess,
  216. &r.GUIStats.Debugging, &r.GUIStats.InsecureSkipHostCheck,
  217. &r.GUIStats.InsecureAllowFrameLoading, &r.GUIStats.ListenLocal,
  218. &r.GUIStats.ListenUnspecified,
  219. &r.BlockStats.Total, &r.BlockStats.Renamed,
  220. &r.BlockStats.Reused, &r.BlockStats.Pulled, &r.BlockStats.CopyOrigin,
  221. &r.BlockStats.CopyOriginShifted, &r.BlockStats.CopyElsewhere,
  222. &r.IgnoreStats.Lines, &r.IgnoreStats.Inverts, &r.IgnoreStats.Folded,
  223. &r.IgnoreStats.Deletable, &r.IgnoreStats.Rooted, &r.IgnoreStats.Includes,
  224. &r.IgnoreStats.EscapedIncludes, &r.IgnoreStats.DoubleStars, &r.IgnoreStats.Stars,
  225. // V3 added late in the RC
  226. &r.WeakHashEnabled,
  227. &r.Address,
  228. // Receive only folders
  229. &r.FolderUses.ReceiveOnly,
  230. }
  231. }
  232. func (*Report) FieldNames() []string {
  233. // The database fields that back this struct in PostgreSQL
  234. return []string{
  235. // V1
  236. "Received",
  237. "UniqueID",
  238. "Version",
  239. "LongVersion",
  240. "Platform",
  241. "NumFolders",
  242. "NumDevices",
  243. "TotFiles",
  244. "FolderMaxFiles",
  245. "TotMiB",
  246. "FolderMaxMiB",
  247. "MemoryUsageMiB",
  248. "SHA256Perf",
  249. "MemorySize",
  250. "Date",
  251. // V2
  252. "ReportVersion",
  253. "NumCPU",
  254. "FolderRO",
  255. "FolderIgnorePerms",
  256. "FolderIgnoreDelete",
  257. "FolderAutoNormalize",
  258. "DeviceIntroducer",
  259. "DeviceCustomCertName",
  260. "DeviceCompressionAlways",
  261. "DeviceCompressionMetadata",
  262. "DeviceCompressionNever",
  263. "DeviceDynamicAddr",
  264. "DeviceStaticAddr",
  265. "AnnounceGlobalEnabled",
  266. "AnnounceLocalEnabled",
  267. "AnnounceDefaultServersDNS",
  268. "AnnounceDefaultServersIP",
  269. "AnnounceOtherServers",
  270. "RelayEnabled",
  271. "RelayDefaultServers",
  272. "RelayOtherServers",
  273. "RateLimitEnabled",
  274. "UpgradeAllowedManual",
  275. "UpgradeAllowedAuto",
  276. // v0.12.19+
  277. "FolderSimpleVersioning",
  278. "FolderExternalVersioning",
  279. "FolderStaggeredVersioning",
  280. "FolderTrashcanVersioning",
  281. // V2.5
  282. "UpgradeAllowedPre",
  283. // V3
  284. "Uptime",
  285. "NATType",
  286. "AlwaysLocalNets",
  287. "CacheIgnoredFiles",
  288. "OverwriteRemoteDeviceNames",
  289. "ProgressEmitterEnabled",
  290. "CustomDefaultFolderPath",
  291. "WeakHashSelection",
  292. "CustomTrafficClass",
  293. "CustomTempIndexMinBlocks",
  294. "TemporariesDisabled",
  295. "TemporariesCustom",
  296. "LimitBandwidthInLan",
  297. "CustomReleaseURL",
  298. "RestartOnWakeup",
  299. "CustomStunServers",
  300. "FolderScanProgressDisabled",
  301. "FolderConflictsDisabled",
  302. "FolderConflictsUnlimited",
  303. "FolderConflictsOther",
  304. "FolderDisableSparseFiles",
  305. "FolderDisableTempIndexes",
  306. "FolderAlwaysWeakHash",
  307. "FolderCustomWeakHashThreshold",
  308. "FolderFsWatcherEnabled",
  309. "GUIEnabled",
  310. "GUIUseTLS",
  311. "GUIUseAuth",
  312. "GUIInsecureAdminAccess",
  313. "GUIDebugging",
  314. "GUIInsecureSkipHostCheck",
  315. "GUIInsecureAllowFrameLoading",
  316. "GUIListenLocal",
  317. "GUIListenUnspecified",
  318. "BlocksTotal",
  319. "BlocksRenamed",
  320. "BlocksReused",
  321. "BlocksPulled",
  322. "BlocksCopyOrigin",
  323. "BlocksCopyOriginShifted",
  324. "BlocksCopyElsewhere",
  325. "IgnoreLines",
  326. "IgnoreInverts",
  327. "IgnoreFolded",
  328. "IgnoreDeletable",
  329. "IgnoreRooted",
  330. "IgnoreIncludes",
  331. "IgnoreEscapedIncludes",
  332. "IgnoreDoubleStars",
  333. "IgnoreStars",
  334. // V3 added late in the RC
  335. "WeakHashEnabled",
  336. "Address",
  337. // Receive only folders
  338. "FolderRecvOnly",
  339. }
  340. }
  341. func (r Report) Value() (driver.Value, error) {
  342. // This needs to be string, yet we read back bytes..
  343. bs, err := json.Marshal(r)
  344. return string(bs), err
  345. }
  346. func (r *Report) Scan(value interface{}) error {
  347. // Zero out the previous value
  348. // JSON un-marshaller does not touch fields that are not in the payload, so we carry over values from a previous
  349. // scan.
  350. *r = Report{}
  351. b, ok := value.([]byte)
  352. if !ok {
  353. return errors.New("type assertion to []byte failed")
  354. }
  355. return json.Unmarshal(b, &r)
  356. }
  357. func clear(v interface{}, since int) error {
  358. s := reflect.ValueOf(v).Elem()
  359. t := s.Type()
  360. for i := 0; i < s.NumField(); i++ {
  361. f := s.Field(i)
  362. tag := t.Field(i).Tag
  363. v := tag.Get("since")
  364. if v == "" {
  365. f.Set(reflect.Zero(f.Type()))
  366. continue
  367. }
  368. vn, err := strconv.Atoi(v)
  369. if err != nil {
  370. return err
  371. }
  372. if vn > since {
  373. f.Set(reflect.Zero(f.Type()))
  374. continue
  375. }
  376. // Dive deeper
  377. if f.Kind() == reflect.Ptr {
  378. f = f.Elem()
  379. }
  380. if f.Kind() == reflect.Struct {
  381. if err := clear(f.Addr().Interface(), since); err != nil {
  382. return err
  383. }
  384. }
  385. }
  386. return nil
  387. }