aboutsummaryrefslogtreecommitdiff
path: root/proto
diff options
context:
space:
mode:
authorAudrius Butkevicius <audrius.butkevicius@gmail.com>2020-08-25 07:11:14 +0100
committerGitHub <noreply@github.com>2020-08-25 08:11:14 +0200
commitd507d932b8975a0c6efeef326d69bac1cab059d1 (patch)
treeb844cdc4254cdc51d817484f0334cf4d35aec826 /proto
parent5b953033c752f4908804f723442c5c97cef712db (diff)
downloadsyncthing-d507d932b8975a0c6efeef326d69bac1cab059d1.tar.gz
syncthing-d507d932b8975a0c6efeef326d69bac1cab059d1.zip
all: Use protobuf to generate config structs (fixes #6734) (#6900)
Diffstat (limited to 'proto')
-rw-r--r--proto/ext.proto24
-rw-r--r--proto/ext/ext.pb.go131
-rw-r--r--proto/generate.go55
-rw-r--r--proto/lib/config/authmode.proto14
-rw-r--r--proto/lib/config/blockpullorder.proto9
-rw-r--r--proto/lib/config/config.proto23
-rw-r--r--proto/lib/config/deviceconfiguration.proto27
-rw-r--r--proto/lib/config/folderconfiguration.proto61
-rw-r--r--proto/lib/config/foldertype.proto9
-rw-r--r--proto/lib/config/guiconfiguration.proto23
-rw-r--r--proto/lib/config/ldapconfiguration.proto17
-rw-r--r--proto/lib/config/ldaptransport.proto15
-rw-r--r--proto/lib/config/observed.proto20
-rw-r--r--proto/lib/config/optionsconfiguration.proto66
-rw-r--r--proto/lib/config/pullorder.proto16
-rw-r--r--proto/lib/config/size.proto14
-rw-r--r--proto/lib/config/tuning.proto13
-rw-r--r--proto/lib/config/versioningconfiguration.proto14
-rw-r--r--proto/lib/fs/copyrangemethod.proto12
-rw-r--r--proto/lib/fs/types.proto8
-rw-r--r--proto/lib/protocol/bep.proto214
-rw-r--r--proto/scripts/dump_tags.go68
-rw-r--r--proto/scripts/protoc_plugin.go291
-rw-r--r--proto/scripts/protofmt.go121
24 files changed, 1265 insertions, 0 deletions
diff --git a/proto/ext.proto b/proto/ext.proto
new file mode 100644
index 000000000..1b3da600b
--- /dev/null
+++ b/proto/ext.proto
@@ -0,0 +1,24 @@
+syntax = "proto2";
+
+package ext;
+
+import "google/protobuf/descriptor.proto";
+
+option go_package = "github.com/syncthing/syncthing/proto/ext";
+
+extend google.protobuf.MessageOptions {
+ optional bool xml_tags = 74001;
+}
+
+extend google.protobuf.FieldOptions {
+ optional string xml = 75005;
+ optional string json = 75006;
+ optional string default = 75007;
+ optional bool restart = 75008;
+ optional bool device_id = 75009;
+ optional string goname = 75010;
+}
+
+extend google.protobuf.EnumValueOptions {
+ optional string enumgoname = 76010;
+}
diff --git a/proto/ext/ext.pb.go b/proto/ext/ext.pb.go
new file mode 100644
index 000000000..82a6747c6
--- /dev/null
+++ b/proto/ext/ext.pb.go
@@ -0,0 +1,131 @@
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: ext.proto
+
+package ext
+
+import (
+ fmt "fmt"
+ proto "github.com/gogo/protobuf/proto"
+ descriptor "github.com/gogo/protobuf/protoc-gen-gogo/descriptor"
+ math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
+
+var E_XmlTags = &proto.ExtensionDesc{
+ ExtendedType: (*descriptor.MessageOptions)(nil),
+ ExtensionType: (*bool)(nil),
+ Field: 74001,
+ Name: "ext.xml_tags",
+ Tag: "varint,74001,opt,name=xml_tags",
+ Filename: "ext.proto",
+}
+
+var E_Xml = &proto.ExtensionDesc{
+ ExtendedType: (*descriptor.FieldOptions)(nil),
+ ExtensionType: (*string)(nil),
+ Field: 75005,
+ Name: "ext.xml",
+ Tag: "bytes,75005,opt,name=xml",
+ Filename: "ext.proto",
+}
+
+var E_Json = &proto.ExtensionDesc{
+ ExtendedType: (*descriptor.FieldOptions)(nil),
+ ExtensionType: (*string)(nil),
+ Field: 75006,
+ Name: "ext.json",
+ Tag: "bytes,75006,opt,name=json",
+ Filename: "ext.proto",
+}
+
+var E_Default = &proto.ExtensionDesc{
+ ExtendedType: (*descriptor.FieldOptions)(nil),
+ ExtensionType: (*string)(nil),
+ Field: 75007,
+ Name: "ext.default",
+ Tag: "bytes,75007,opt,name=default",
+ Filename: "ext.proto",
+}
+
+var E_Restart = &proto.ExtensionDesc{
+ ExtendedType: (*descriptor.FieldOptions)(nil),
+ ExtensionType: (*bool)(nil),
+ Field: 75008,
+ Name: "ext.restart",
+ Tag: "varint,75008,opt,name=restart",
+ Filename: "ext.proto",
+}
+
+var E_DeviceId = &proto.ExtensionDesc{
+ ExtendedType: (*descriptor.FieldOptions)(nil),
+ ExtensionType: (*bool)(nil),
+ Field: 75009,
+ Name: "ext.device_id",
+ Tag: "varint,75009,opt,name=device_id",
+ Filename: "ext.proto",
+}
+
+var E_Goname = &proto.ExtensionDesc{
+ ExtendedType: (*descriptor.FieldOptions)(nil),
+ ExtensionType: (*string)(nil),
+ Field: 75010,
+ Name: "ext.goname",
+ Tag: "bytes,75010,opt,name=goname",
+ Filename: "ext.proto",
+}
+
+var E_Enumgoname = &proto.ExtensionDesc{
+ ExtendedType: (*descriptor.EnumValueOptions)(nil),
+ ExtensionType: (*string)(nil),
+ Field: 76010,
+ Name: "ext.enumgoname",
+ Tag: "bytes,76010,opt,name=enumgoname",
+ Filename: "ext.proto",
+}
+
+func init() {
+ proto.RegisterExtension(E_XmlTags)
+ proto.RegisterExtension(E_Xml)
+ proto.RegisterExtension(E_Json)
+ proto.RegisterExtension(E_Default)
+ proto.RegisterExtension(E_Restart)
+ proto.RegisterExtension(E_DeviceId)
+ proto.RegisterExtension(E_Goname)
+ proto.RegisterExtension(E_Enumgoname)
+}
+
+func init() { proto.RegisterFile("ext.proto", fileDescriptor_95fe6908ffcf64d3) }
+
+var fileDescriptor_95fe6908ffcf64d3 = []byte{
+ // 305 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0xd1, 0x3d, 0x4b, 0x03, 0x31,
+ 0x18, 0xc0, 0x71, 0x8e, 0x16, 0xdb, 0x66, 0xec, 0x24, 0x82, 0xb5, 0x6e, 0x9d, 0xee, 0x10, 0x41,
+ 0x31, 0x74, 0x52, 0x14, 0x1c, 0x44, 0x28, 0xe2, 0xe0, 0x52, 0xd2, 0xcb, 0xd3, 0x34, 0x92, 0x97,
+ 0x72, 0x49, 0x24, 0x6e, 0xea, 0x37, 0xf0, 0x2b, 0x39, 0x69, 0x27, 0xfd, 0x06, 0xd2, 0xd1, 0xef,
+ 0xe0, 0x0b, 0x77, 0x97, 0x93, 0x42, 0x87, 0xdb, 0x42, 0xf8, 0xff, 0x1e, 0x12, 0x1e, 0xd4, 0x01,
+ 0x6f, 0xe3, 0x79, 0xa6, 0xad, 0xee, 0x36, 0xc0, 0xdb, 0xad, 0x3e, 0xd3, 0x9a, 0x09, 0x48, 0x8a,
+ 0xab, 0x89, 0x9b, 0x26, 0x14, 0x4c, 0x9a, 0xf1, 0xb9, 0xd5, 0x59, 0x99, 0xe1, 0x21, 0x6a, 0x7b,
+ 0x29, 0xc6, 0x96, 0x30, 0xd3, 0xdd, 0x89, 0xcb, 0x3c, 0xae, 0xf2, 0xf8, 0x02, 0x8c, 0x21, 0x0c,
+ 0x2e, 0xe7, 0x96, 0x6b, 0x65, 0x36, 0x9f, 0x5f, 0x9a, 0xfd, 0x68, 0xd0, 0x1e, 0xb5, 0xbc, 0x14,
+ 0x57, 0x84, 0x19, 0xbc, 0x87, 0x1a, 0x5e, 0x8a, 0xee, 0xf6, 0x1a, 0x3c, 0xe3, 0x20, 0x68, 0xc5,
+ 0xbe, 0xdf, 0x72, 0xd6, 0x19, 0xe5, 0x2d, 0xde, 0x47, 0xcd, 0x5b, 0xa3, 0x55, 0x9d, 0xf9, 0x09,
+ 0xa6, 0x88, 0xf1, 0x11, 0x6a, 0x51, 0x98, 0x12, 0x27, 0x6c, 0x9d, 0xfb, 0x0d, 0xae, 0xea, 0x73,
+ 0x9a, 0x81, 0xb1, 0x24, 0xab, 0xa5, 0x0f, 0x8b, 0xf0, 0xbb, 0xd0, 0xe3, 0x21, 0xea, 0x50, 0xb8,
+ 0xe3, 0x29, 0x8c, 0x39, 0xad, 0xc3, 0x8f, 0x01, 0xb7, 0x4b, 0x71, 0x4e, 0xf1, 0x21, 0xda, 0x60,
+ 0x5a, 0x11, 0x09, 0x75, 0xf4, 0x69, 0x51, 0x3e, 0x39, 0xe4, 0xf8, 0x04, 0x21, 0x50, 0x4e, 0x06,
+ 0xbc, 0xbb, 0x86, 0x4f, 0x95, 0x93, 0xd7, 0x44, 0xb8, 0xff, 0xb5, 0x7c, 0x7d, 0x94, 0x03, 0x56,
+ 0xd8, 0xf1, 0xc1, 0xeb, 0xb2, 0x17, 0xbd, 0x2f, 0x7b, 0xd1, 0xe7, 0xb2, 0x17, 0xdd, 0x0c, 0x18,
+ 0xb7, 0x33, 0x37, 0x89, 0x53, 0x2d, 0x13, 0x73, 0xaf, 0x52, 0x3b, 0xe3, 0x8a, 0xad, 0x9c, 0x8a,
+ 0xd9, 0x09, 0x78, 0xfb, 0x17, 0x00, 0x00, 0xff, 0xff, 0xcd, 0x40, 0x4c, 0x73, 0x42, 0x02, 0x00,
+ 0x00,
+}
diff --git a/proto/generate.go b/proto/generate.go
new file mode 100644
index 000000000..4cf477dca
--- /dev/null
+++ b/proto/generate.go
@@ -0,0 +1,55 @@
+// Copyright (C) 2020 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+//+build ignore
+
+package main
+
+import (
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+)
+
+//go:generate go run scripts/protofmt.go .
+
+// First generate extensions using standard proto compiler.
+//go:generate protoc -I ../ -I . --gogofast_out=Mgoogle/protobuf/descriptor.proto=github.com/gogo/protobuf/protoc-gen-gogo/descriptor,paths=source_relative:ext ext.proto
+
+// Then build our vanity compiler that uses the new extensions
+//go:generate go build -o scripts/protoc-gen-gosyncthing scripts/protoc_plugin.go
+
+// Inception, go generate calls the script itself that then deals with generation.
+// This is only done because go:generate does not support wildcards in paths.
+//go:generate go run generate.go lib/config lib/fs
+
+// Use the standard compiler here. We can revisit this later, but we don't plan on exposing this via any APIs.
+//go:generate protoc -I ../ -I . --gogofast_out=paths=source_relative:.. lib/protocol/bep.proto
+
+func main() {
+ for _, path := range os.Args[1:] {
+ matches, err := filepath.Glob(filepath.Join(path, "*proto"))
+ if err != nil {
+ log.Fatal(err)
+ }
+ log.Println(path, "returned:", matches)
+ args := []string{
+ "-I", "..",
+ "-I", ".",
+ "--plugin=protoc-gen-gosyncthing=scripts/protoc-gen-gosyncthing",
+ "--gosyncthing_out=paths=source_relative:..",
+ }
+ args = append(args, matches...)
+ cmd := exec.Command("protoc", args...)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+
+ if err := cmd.Run(); err != nil {
+ log.Fatal("Failed generating", path)
+ }
+ }
+}
diff --git a/proto/lib/config/authmode.proto b/proto/lib/config/authmode.proto
new file mode 100644
index 000000000..c045385f6
--- /dev/null
+++ b/proto/lib/config/authmode.proto
@@ -0,0 +1,14 @@
+syntax = "proto3";
+
+package config;
+
+import "repos/protobuf/gogoproto/gogo.proto";
+
+import "ext.proto";
+
+enum AuthMode {
+ option (gogoproto.goproto_enum_stringer) = false;
+
+ AUTH_MODE_STATIC = 0;
+ AUTH_MODE_LDAP = 1 [(ext.enumgoname) = "AuthModeLDAP"];
+}
diff --git a/proto/lib/config/blockpullorder.proto b/proto/lib/config/blockpullorder.proto
new file mode 100644
index 000000000..124b54844
--- /dev/null
+++ b/proto/lib/config/blockpullorder.proto
@@ -0,0 +1,9 @@
+syntax = "proto3";
+
+package config;
+
+enum BlockPullOrder {
+ BLOCK_PULL_ORDER_STANDARD = 0;
+ BLOCK_PULL_ORDER_RANDOM = 1;
+ BLOCK_PULL_ORDER_IN_ORDER = 2;
+}
diff --git a/proto/lib/config/config.proto b/proto/lib/config/config.proto
new file mode 100644
index 000000000..3b4f526ce
--- /dev/null
+++ b/proto/lib/config/config.proto
@@ -0,0 +1,23 @@
+syntax = "proto3";
+
+package config;
+
+import "lib/config/folderconfiguration.proto";
+import "lib/config/deviceconfiguration.proto";
+import "lib/config/guiconfiguration.proto";
+import "lib/config/ldapconfiguration.proto";
+import "lib/config/optionsconfiguration.proto";
+import "lib/config/observed.proto";
+
+import "ext.proto";
+
+message Configuration {
+ int32 version = 1 [(ext.xml) = "version,attr"];
+ repeated FolderConfiguration folders = 2;
+ repeated DeviceConfiguration devices = 3;
+ GUIConfiguration gui = 4 [(ext.goname) = "GUI"];
+ LDAPConfiguration ldap = 5 [(ext.goname) = "LDAP"];
+ OptionsConfiguration options = 6;
+ repeated ObservedDevice ignored_devices = 7 [(ext.json) = "remoteIgnoredDevices", (ext.xml) = "remoteIgnoredDevice"];
+ repeated ObservedDevice pending_devices = 8;
+}
diff --git a/proto/lib/config/deviceconfiguration.proto b/proto/lib/config/deviceconfiguration.proto
new file mode 100644
index 000000000..3494a413d
--- /dev/null
+++ b/proto/lib/config/deviceconfiguration.proto
@@ -0,0 +1,27 @@
+syntax = "proto3";
+
+package config;
+
+import "lib/protocol/bep.proto";
+import "lib/config/observed.proto";
+
+import "ext.proto";
+
+message DeviceConfiguration {
+ bytes device_id = 1 [(ext.goname) = "DeviceID", (ext.xml) = "id,attr", (ext.json) = "deviceID", (ext.device_id) = true];
+ string name = 2 [(ext.xml) = "name,attr,omitempty"];
+ repeated string addresses = 3 [(ext.xml) = "address,omitempty", (ext.default) = "dynamic"];
+ protocol.Compression compression = 4 [(ext.xml) = "compression,attr"];
+ string cert_name = 5 [(ext.xml) = "certName,attr,omitempty"];
+ bool introducer = 6 [(ext.xml) = "introducer,attr"];
+ bool skip_introduction_removals = 7 [(ext.xml) = "skipIntroductionRemovals,attr"];
+ bytes introduced_by = 8 [(ext.xml) = "introducedBy,attr", (ext.device_id) = true];
+ bool paused = 9;
+ repeated string allowed_networks = 10 [(ext.xml) = "allowedNetwork,omitempty"];
+ bool auto_accept_folders = 11;
+ int32 max_send_kbps = 12;
+ int32 max_recv_kbps = 13;
+ repeated ObservedFolder ignored_folders = 14;
+ repeated ObservedFolder pending_folders = 15;
+ int32 max_request_kib = 16 [(ext.goname) = "MaxRequestKiB", (ext.xml) = "maxRequestKiB", (ext.json) = "maxRequestKiB"];
+}
diff --git a/proto/lib/config/folderconfiguration.proto b/proto/lib/config/folderconfiguration.proto
new file mode 100644
index 000000000..0b7093060
--- /dev/null
+++ b/proto/lib/config/folderconfiguration.proto
@@ -0,0 +1,61 @@
+syntax = "proto3";
+
+package config;
+
+import "lib/config/foldertype.proto";
+import "lib/config/size.proto";
+import "lib/config/pullorder.proto";
+import "lib/config/versioningconfiguration.proto";
+import "lib/config/blockpullorder.proto";
+
+import "lib/fs/types.proto";
+import "lib/fs/copyrangemethod.proto";
+
+import "ext.proto";
+
+message FolderDeviceConfiguration {
+ bytes device_id = 1 [(ext.goname) = "DeviceID", (ext.xml) = "id,attr", (ext.json) = "deviceID", (ext.device_id) = true];
+ bytes introduced_by = 2 [(ext.xml) = "introducedBy,attr", (ext.device_id) = true];
+}
+
+message FolderConfiguration {
+ string id = 1 [(ext.goname) = "ID", (ext.xml) = "id,attr"];
+ string label = 2 [(ext.xml) = "label,attr", (ext.restart) = false];
+ fs.FilesystemType filesystem_type = 3;
+ string path = 4 [(ext.xml) = "path,attr"];
+ FolderType type = 5 [(ext.xml) = "type,attr"];
+ repeated FolderDeviceConfiguration devices = 6;
+ int32 rescan_interval_s = 7 [(ext.xml) = "rescanIntervalS,attr", (ext.default) = "3600"];
+ bool fs_watcher_enabled = 8 [(ext.goname) = "FSWatcherEnabled", (ext.xml) = "fsWatcherEnabled,attr", (ext.default) = "true"];
+ int32 fs_watcher_delay_s = 9 [(ext.goname) = "FSWatcherDelayS", (ext.xml) = "fsWatcherDelayS,attr", (ext.default) = "10"];
+ bool ignore_perms = 10 [(ext.xml) = "ignorePerms,attr"];
+ bool auto_normalize = 11 [(ext.xml) = "autoNormalize,attr", (ext.default) = "true"];
+ Size min_disk_free = 12;
+ VersioningConfiguration versioning = 13;
+ int32 copiers = 14;
+ int32 puller_max_pending_kib = 15 [(ext.goname) = "PullerMaxPendingKiB", (ext.xml) = "pullerMaxPendingKiB", (ext.json) = "pullerMaxPendingKiB"];
+ int32 hashers = 16;
+ PullOrder order = 17;
+ bool ignore_delete = 18;
+ int32 scan_progress_interval_s = 19;
+ int32 puller_pause_s = 20;
+ int32 max_conflicts = 21 [(ext.default) = "-1"];
+ bool disable_sparse_files = 22;
+ bool disable_temp_indexes = 23;
+ bool paused = 24;
+ int32 weak_hash_threshold_pct = 25;
+ string marker_name = 26;
+ bool copy_ownership_from_parent = 27;
+ int32 mod_time_window_s = 28 [(ext.goname) = "RawModTimeWindowS"];
+ int32 max_concurrent_writes = 29 [(ext.default) = "2"];
+ bool disable_fsync = 30;
+ BlockPullOrder block_pull_order = 31;
+ fs.CopyRangeMethod copy_range_method = 32 [(ext.default) = "standard"];
+ bool case_sensitive_fs = 33 [(ext.goname) = "CaseSensitiveFS", (ext.xml) = "caseSensitiveFS", (ext.json) = "caseSensitiveFS"];
+ bool follow_junctions = 34 [(ext.goname) = "JunctionsAsDirs", (ext.xml) = "junctionsAsDirs", (ext.json) = "junctionsAsDirs"];
+
+ // Legacy deprecated
+ bool read_only = 9000 [deprecated=true, (ext.xml) = "ro,attr,omitempty"];
+ double min_disk_free_pct = 9001 [deprecated=true];
+ int32 pullers = 9002 [deprecated=true];
+}
diff --git a/proto/lib/config/foldertype.proto b/proto/lib/config/foldertype.proto
new file mode 100644
index 000000000..5ab498c31
--- /dev/null
+++ b/proto/lib/config/foldertype.proto
@@ -0,0 +1,9 @@
+syntax = "proto3";
+
+package config;
+
+enum FolderType {
+ FOLDER_TYPE_SEND_RECEIVE = 0;
+ FOLDER_TYPE_SEND_ONLY = 1;
+ FOLDER_TYPE_RECEIVE_ONLY = 2;
+}
diff --git a/proto/lib/config/guiconfiguration.proto b/proto/lib/config/guiconfiguration.proto
new file mode 100644
index 000000000..ed0927599
--- /dev/null
+++ b/proto/lib/config/guiconfiguration.proto
@@ -0,0 +1,23 @@
+syntax = "proto3";
+
+package config;
+
+import "lib/config/authmode.proto";
+
+import "ext.proto";
+
+message GUIConfiguration {
+ bool enabled = 1 [(ext.xml) = "enabled,attr", (ext.default) = "true"];
+ string address = 2 [(ext.goname) = "RawAddress", (ext.default) = "127.0.0.1:8384"];
+ string unix_socket_permissions = 3 [(ext.goname) = "RawUnixSocketPermissions", (ext.xml) = "unixSocketPermissions,omitempty"];
+ string user = 4 [(ext.xml) = "user,omitempty"];
+ string password = 5 [(ext.xml) = "password,omitempty"];
+ AuthMode auth_mode = 6 [(ext.xml) = "authMode,omitempty"];
+ bool use_tls = 7 [(ext.goname) = "RawUseTLS", (ext.xml) = "tls,attr", (ext.json) = "useTLS"];
+ string api_key = 8 [(ext.goname) = "APIKey", (ext.xml) = "apikey,omitempty"];
+ bool insecure_admin_access = 9 [(ext.xml) = "insecureAdminAccess,omitempty"];
+ string theme = 10 [(ext.default) = "default"];
+ bool debugging = 11 [(ext.xml) = "debugging,attr"];
+ bool insecure_skip_host_check = 12 [(ext.xml) = "insecureSkipHostcheck,omitempty", (ext.json) = "insecureSkipHostcheck"];
+ bool insecure_allow_frame_loading = 13 [(ext.xml) = "insecureAllowFrameLoading,omitempty"];
+}
diff --git a/proto/lib/config/ldapconfiguration.proto b/proto/lib/config/ldapconfiguration.proto
new file mode 100644
index 000000000..6ad989662
--- /dev/null
+++ b/proto/lib/config/ldapconfiguration.proto
@@ -0,0 +1,17 @@
+syntax = "proto3";
+
+package config;
+
+import "lib/config/ldaptransport.proto";
+
+import "ext.proto";
+
+
+message LDAPConfiguration {
+ string address = 1 [(ext.xml) = "address,omitempty"];
+ string bind_dn = 2 [(ext.goname) = "BindDN", (ext.xml) = "bindDN,omitempty", (ext.json) = "bindDN"];
+ LDAPTransport transport = 3 [(ext.xml) = "transport,omitempty"];
+ bool insecure_skip_verify = 4 [(ext.xml) = "insecureSkipVerify,omitempty", (ext.default) = "false"];
+ string search_base_dn = 5 [(ext.goname) = "SearchBaseDN", (ext.xml) = "searchBaseDN,omitempty", (ext.json) = "searchBaseDN"];
+ string search_filter = 6 [(ext.xml) = "searchFilter,omitempty"];
+}
diff --git a/proto/lib/config/ldaptransport.proto b/proto/lib/config/ldaptransport.proto
new file mode 100644
index 000000000..938f65e90
--- /dev/null
+++ b/proto/lib/config/ldaptransport.proto
@@ -0,0 +1,15 @@
+syntax = "proto3";
+
+package config;
+
+import "repos/protobuf/gogoproto/gogo.proto";
+
+import "ext.proto";
+
+enum LDAPTransport {
+ option (gogoproto.goproto_enum_stringer) = false;
+
+ LDAP_TRANSPORT_PLAIN = 0 [(ext.enumgoname) = "LDAPTransportPlain"];
+ LDAP_TRANSPORT_TLS = 2 [(ext.enumgoname) = "LDAPTransportTLS"];
+ LDAP_TRANSPORT_START_TLS = 3 [(ext.enumgoname) = "LDAPTransportStartTLS"];
+}
diff --git a/proto/lib/config/observed.proto b/proto/lib/config/observed.proto
new file mode 100644
index 000000000..a1b44bcf7
--- /dev/null
+++ b/proto/lib/config/observed.proto
@@ -0,0 +1,20 @@
+syntax = "proto3";
+
+package config;
+
+import "google/protobuf/timestamp.proto";
+
+import "ext.proto";
+
+message ObservedFolder {
+ google.protobuf.Timestamp time = 1 [(ext.xml) = "time,attr"];
+ string id = 2 [(ext.goname) = "ID", (ext.xml) = "id,attr"];
+ string label = 3 [(ext.xml) = "label,attr"];
+}
+
+message ObservedDevice {
+ google.protobuf.Timestamp time = 1 [(ext.xml) = "time,attr"];
+ bytes id = 2 [(ext.goname) = "ID", (ext.json) = "deviceID", (ext.xml) = "id,attr", (ext.device_id) = true];
+ string name = 3 [(ext.xml) = "name,attr"];
+ string address = 4 [(ext.xml) = "address,attr"];
+}
diff --git a/proto/lib/config/optionsconfiguration.proto b/proto/lib/config/optionsconfiguration.proto
new file mode 100644
index 000000000..dfed02241
--- /dev/null
+++ b/proto/lib/config/optionsconfiguration.proto
@@ -0,0 +1,66 @@
+syntax = "proto3";
+
+package config;
+
+import "lib/config/tuning.proto";
+import "lib/config/size.proto";
+
+import "ext.proto";
+
+message OptionsConfiguration {
+ repeated string listen_addresses = 1 [(ext.goname) = "RawListenAddresses", (ext.default) = "default"];
+ repeated string global_discovery_servers = 2 [(ext.goname) = "RawGlobalAnnServers", (ext.xml) = "globalAnnounceServer", (ext.json) = "globalAnnounceServers", (ext.default) = "default"];
+ bool global_discovery_enabled = 3 [(ext.goname) = "GlobalAnnEnabled", (ext.xml) = "globalAnnounceEnabled", (ext.json) = "globalAnnounceEnabled", (ext.default) = "true"];
+ bool local_discovery_enabled = 4 [(ext.goname) = "LocalAnnEnabled", (ext.xml) = "localAnnounceEnabled", (ext.json) = "localAnnounceEnabled", (ext.default) = "true"];
+ int32 local_announce_port = 5 [(ext.goname) = "LocalAnnPort", (ext.xml) = "localAnnouncePort", (ext.json) = "localAnnouncePort", (ext.default) = "21027"];
+ string local_announce_multicast_address = 6 [(ext.goname) = "LocalAnnMCAddr", (ext.xml) = "localAnnounceMCAddr", (ext.json) = "localAnnounceMCAddr", (ext.default) = "[ff12::8384]:21027"];
+ int32 max_send_kbps = 7;
+ int32 max_recv_kbps = 8;
+ int32 reconnection_interval_s = 9 [(ext.goname) = "ReconnectIntervalS", (ext.default) = "60"];
+ bool relays_enabled = 10 [(ext.default) = "true"];
+ int32 relays_reconnect_interval_m = 11 [(ext.goname) = "RelayReconnectIntervalM", (ext.xml) = "relayReconnectIntervalM", (ext.json) = "relayReconnectIntervalM", (ext.default) = "10"];
+ bool start_browser = 12 [(ext.default) = "true"];
+ bool nat_traversal_enabled = 14 [(ext.goname) = "NATEnabled", (ext.xml) = "natEnabled", (ext.json) = "natEnabled", (ext.default) = "true"];
+ int32 nat_traversal_lease_m = 15 [(ext.goname) = "NATLeaseM", (ext.xml) = "natLeaseMinutes", (ext.json) = "natLeaseMinutes", (ext.default) = "60"];
+ int32 nat_traversal_renewal_m = 16 [(ext.goname) = "NATRenewalM", (ext.xml) = "natRenewalMinutes", (ext.json) = "natRenewalMinutes", (ext.default) = "30"];
+ int32 nat_traversal_timeout_s = 17 [(ext.goname) = "NATTimeoutS", (ext.xml) = "natTimeoutSeconds", (ext.json) = "natTimeoutSeconds", (ext.default) = "10"];
+ int32 usage_reporting_accepted = 18 [(ext.goname) = "URAccepted", (ext.xml) = "urAccepted", (ext.json) = "urAccepted"];
+ int32 usage_reporting_seen = 19 [(ext.goname) = "URSeen", (ext.xml) = "urSeen", (ext.json) = "urSeen"];
+ string usage_reporting_unique_id = 20 [(ext.goname) = "URUniqueID", (ext.xml) = "urUniqueID", (ext.json) = "urUniqueId"];
+ string usage_reporting_url = 21 [(ext.goname) = "URURL", (ext.xml) = "urURL", (ext.json) = "urURL", (ext.default) = "https://data.syncthing.net/newdata"];
+ bool usage_reporting_post_insecurely = 22 [(ext.goname) = "URPostInsecurely", (ext.xml) = "urPostInsecurely", (ext.json) = "urPostInsecurely", (ext.default) = "false"];
+ int32 usage_reporting_initial_delay_s = 23 [(ext.goname) = "URInitialDelayS", (ext.xml) = "urInitialDelayS", (ext.json) = "urInitialDelayS", (ext.default) = "1800"];
+ bool restart_on_wakeup = 24 [(ext.default) = "true"];
+ int32 auto_upgrade_interval_h = 25 [(ext.default) = "12"];
+ bool upgrade_to_pre_releases = 26;
+ int32 keep_temporaries_h = 27 [(ext.default) = "24"];
+ bool cache_ignored_files = 28 [(ext.default) = "false"];
+ int32 progress_update_interval_s = 29 [(ext.default) = "5"];
+ bool limit_bandwidth_in_lan = 30 [(ext.default) = "false"];
+ Size min_home_disk_free = 31 [(ext.default) = "1 %"];
+ string releases_url = 32 [(ext.goname) = "ReleasesURL", (ext.xml) = "releasesURL", (ext.json) = "releasesURL", (ext.default) = "https://upgrades.syncthing.net/meta.json"];
+ repeated string always_local_nets = 33;
+ bool overwrite_remote_device_names_on_connect = 34 [(ext.goname) = "OverwriteRemoteDevNames", (ext.default) = "false"];
+ int32 temp_index_min_blocks = 35 [(ext.default) = "10"];
+ repeated string unacked_notification_ids = 36 [(ext.goname) = "UnackedNotificationIDs", (ext.xml) = "unackedNotificationID", (ext.json) = "unackedNotificationIDs"];
+ int32 traffic_class = 37;
+ string default_folder_path = 38 [(ext.default) = "~"];
+ bool set_low_priority = 39 [(ext.default) = "true"];
+ int32 max_folder_concurrency = 40 [(ext.goname) = "RawMaxFolderConcurrency"];
+ string crash_reporting_url = 41 [(ext.goname) = "CRURL", (ext.xml) = "crashReportingURL", (ext.json) = "crURL", (ext.default) = "https://crash.syncthing.net/newcrash"];
+ bool crash_reporting_enabled = 42 [(ext.goname) = "CREnabled", (ext.default) = "true"];
+ int32 stun_keepalive_start_s = 43 [(ext.default) = "180"];
+ int32 stun_keepalive_min_s = 44 [(ext.default) = "20"];
+ repeated string stun_servers = 45 [(ext.goname) = "RawStunServers", (ext.default) = "default"];
+ Tuning database_tuning = 46 [(ext.restart) = true];
+ int32 max_concurrent_incoming_request_kib = 47 [(ext.goname) = "RawMaxCIRequestKiB", (ext.xml) = "maxConcurrentIncomingRequestKiB", (ext.json) = "maxConcurrentIncomingRequestKiB"];
+
+ // Legacy deprecated
+ bool upnp_enabled = 9000 [deprecated = true, (ext.goname) = "DeprecatedUPnPEnabled"];
+ int32 upnp_lease_m = 9001 [deprecated = true, (ext.goname) = "DeprecatedUPnPLeaseM", (ext.xml) = "upnpLeaseMinutes,omitempty"];
+ int32 upnp_renewal_m = 9002 [deprecated = true, (ext.goname) = "DeprecatedUPnPRenewalM", (ext.xml) = "upnpRenewalMinutes,omitempty"];
+ int32 upnp_timeout_s = 9003 [deprecated = true, (ext.goname) = "DeprecatedUPnPTimeoutS", (ext.xml) = "upnpTimeoutSeconds,omitempty"];
+ repeated string relay_servers = 9004 [deprecated = true];
+ double min_home_disk_free_pct = 9005 [deprecated = true];
+ int32 max_concurrent_scans = 9006 [deprecated = true];
+}
diff --git a/proto/lib/config/pullorder.proto b/proto/lib/config/pullorder.proto
new file mode 100644
index 000000000..b9afb8e0d
--- /dev/null
+++ b/proto/lib/config/pullorder.proto
@@ -0,0 +1,16 @@
+syntax = "proto3";
+
+package config;
+
+import "repos/protobuf/gogoproto/gogo.proto";
+
+enum PullOrder {
+ option (gogoproto.goproto_enum_stringer) = false;
+
+ PULL_ORDER_RANDOM = 0;
+ PULL_ORDER_ALPHABETIC = 1;
+ PULL_ORDER_SMALLEST_FIRST = 2;
+ PULL_ORDER_LARGEST_FIRST = 3;
+ PULL_ORDER_OLDEST_FIRST = 4;
+ PULL_ORDER_NEWEST_FIRST = 5;
+}
diff --git a/proto/lib/config/size.proto b/proto/lib/config/size.proto
new file mode 100644
index 000000000..e1795ddb1
--- /dev/null
+++ b/proto/lib/config/size.proto
@@ -0,0 +1,14 @@
+syntax = "proto3";
+
+package config;
+
+import "repos/protobuf/gogoproto/gogo.proto";
+
+import "ext.proto";
+
+message Size {
+ option (gogoproto.goproto_stringer) = false;
+
+ double value = 1 [(ext.xml) = ",chardata"];
+ string unit = 2 [(ext.xml) = "unit,attr"];
+}
diff --git a/proto/lib/config/tuning.proto b/proto/lib/config/tuning.proto
new file mode 100644
index 000000000..edbbf3193
--- /dev/null
+++ b/proto/lib/config/tuning.proto
@@ -0,0 +1,13 @@
+syntax = "proto3";
+
+package config;
+
+import "repos/protobuf/gogoproto/gogo.proto";
+
+enum Tuning {
+ option (gogoproto.goproto_enum_stringer) = false;
+
+ TUNING_AUTO = 0;
+ TUNING_SMALL = 1;
+ TUNING_LARGE = 2;
+}
diff --git a/proto/lib/config/versioningconfiguration.proto b/proto/lib/config/versioningconfiguration.proto
new file mode 100644
index 000000000..1aab2fb18
--- /dev/null
+++ b/proto/lib/config/versioningconfiguration.proto
@@ -0,0 +1,14 @@
+syntax = "proto3";
+
+package config;
+
+import "ext.proto";
+
+// VersioningConfiguration is used in the code and for JSON serialization
+message VersioningConfiguration {
+ option (ext.xml_tags) = false;
+
+ string type = 1;
+ map<string, string> parameters = 2 [(ext.goname) = "Params", (ext.json) = "params"];
+ int32 cleanup_interval_s = 3 [(ext.default) = "3600"];
+}
diff --git a/proto/lib/fs/copyrangemethod.proto b/proto/lib/fs/copyrangemethod.proto
new file mode 100644
index 000000000..5adaed385
--- /dev/null
+++ b/proto/lib/fs/copyrangemethod.proto
@@ -0,0 +1,12 @@
+syntax = "proto3";
+
+package fs;
+
+enum CopyRangeMethod {
+ COPY_RANGE_METHOD_STANDARD = 0;
+ COPY_RANGE_METHOD_IOCTL = 1;
+ COPY_RANGE_METHOD_COPY_FILE_RANGE = 2;
+ COPY_RANGE_METHOD_SEND_FILE = 3;
+ COPY_RANGE_METHOD_DUPLICATE_EXTENTS = 4;
+ COPY_RANGE_METHOD_ALL_WITH_FALLBACK = 5;
+}
diff --git a/proto/lib/fs/types.proto b/proto/lib/fs/types.proto
new file mode 100644
index 000000000..994ac123f
--- /dev/null
+++ b/proto/lib/fs/types.proto
@@ -0,0 +1,8 @@
+syntax = "proto3";
+
+package fs;
+
+enum FilesystemType {
+ FILESYSTEM_TYPE_BASIC = 0;
+ FILESYSTEM_TYPE_FAKE = 1;
+}
diff --git a/proto/lib/protocol/bep.proto b/proto/lib/protocol/bep.proto
new file mode 100644
index 000000000..f2c222d2c
--- /dev/null
+++ b/proto/lib/protocol/bep.proto
@@ -0,0 +1,214 @@
+syntax = "proto3";
+
+package protocol;
+
+import "repos/protobuf/gogoproto/gogo.proto";
+
+option (gogoproto.goproto_getters_all) = false;
+option (gogoproto.sizer_all) = false;
+option (gogoproto.protosizer_all) = true;
+option (gogoproto.goproto_enum_stringer_all) = true;
+option (gogoproto.goproto_enum_prefix_all) = false;
+option (gogoproto.goproto_unkeyed_all) = false;
+option (gogoproto.goproto_unrecognized_all) = false;
+option (gogoproto.goproto_sizecache_all) = false;
+
+// --- Pre-auth ---
+
+message Hello {
+ string device_name = 1;
+ string client_name = 2;
+ string client_version = 3;
+}
+
+// --- Header ---
+
+message Header {
+ MessageType type = 1;
+ MessageCompression compression = 2;
+}
+
+enum MessageType {
+ CLUSTER_CONFIG = 0 [(gogoproto.enumvalue_customname) = "messageTypeClusterConfig"];
+ INDEX = 1 [(gogoproto.enumvalue_customname) = "messageTypeIndex"];
+ INDEX_UPDATE = 2 [(gogoproto.enumvalue_customname) = "messageTypeIndexUpdate"];
+ REQUEST = 3 [(gogoproto.enumvalue_customname) = "messageTypeRequest"];
+ RESPONSE = 4 [(gogoproto.enumvalue_customname) = "messageTypeResponse"];
+ DOWNLOAD_PROGRESS = 5 [(gogoproto.enumvalue_customname) = "messageTypeDownloadProgress"];
+ PING = 6 [(gogoproto.enumvalue_customname) = "messageTypePing"];
+ CLOSE = 7 [(gogoproto.enumvalue_customname) = "messageTypeClose"];
+}
+
+enum MessageCompression {
+ NONE = 0 [(gogoproto.enumvalue_customname) = "MessageCompressionNone"];
+ LZ4 = 1 [(gogoproto.enumvalue_customname) = "MessageCompressionLZ4"];
+}
+
+// --- Actual messages ---
+
+// Cluster Config
+
+message ClusterConfig {
+ repeated Folder folders = 1 [(gogoproto.nullable) = false];
+}
+
+message Folder {
+ string id = 1 [(gogoproto.customname) = "ID"];
+ string label = 2;
+ bool read_only = 3;
+ bool ignore_permissions = 4;
+ bool ignore_delete = 5;
+ bool disable_temp_indexes = 6;
+ bool paused = 7;
+
+ repeated Device devices = 16 [(gogoproto.nullable) = false];
+}
+
+message Device {
+ bytes id = 1 [(gogoproto.customname) = "ID", (gogoproto.customtype) = "DeviceID", (gogoproto.nullable) = false];
+ string name = 2;
+ repeated string addresses = 3;
+ Compression compression = 4;
+ string cert_name = 5;
+ int64 max_sequence = 6;
+ bool introducer = 7;
+ uint64 index_id = 8 [(gogoproto.customname) = "IndexID", (gogoproto.customtype) = "IndexID", (gogoproto.nullable) = false];
+ bool skip_introduction_removals = 9;
+}
+
+enum Compression {
+ METADATA = 0 [(gogoproto.enumvalue_customname) = "CompressMetadata"];
+ NEVER = 1 [(gogoproto.enumvalue_customname) = "CompressNever"];
+ ALWAYS = 2 [(gogoproto.enumvalue_customname) = "CompressAlways"];
+}
+
+// Index and Index Update
+
+message Index {
+ string folder = 1;
+ repeated FileInfo files = 2 [(gogoproto.nullable) = false];
+}
+
+message IndexUpdate {
+ string folder = 1;
+ repeated FileInfo files = 2 [(gogoproto.nullable) = false];
+}
+
+message FileInfo {
+ option (gogoproto.goproto_stringer) = false;
+
+ // The field ordering here optimizes for struct size / alignment --
+ // large types come before smaller ones.
+
+ string name = 1;
+ int64 size = 3;
+ int64 modified_s = 5;
+ uint64 modified_by = 12 [(gogoproto.customtype) = "ShortID", (gogoproto.nullable) = false];
+ Vector version = 9 [(gogoproto.nullable) = false];
+ int64 sequence = 10;
+ repeated BlockInfo blocks = 16 [(gogoproto.nullable) = false];
+ string symlink_target = 17;
+ bytes blocks_hash = 18;
+ FileInfoType type = 2;
+ uint32 permissions = 4;
+ int32 modified_ns = 11;
+ int32 block_size = 13 [(gogoproto.customname) = "RawBlockSize"];
+
+ // The local_flags fields stores flags that are relevant to the local
+ // host only. It is not part of the protocol, doesn't get sent or
+ // received (we make sure to zero it), nonetheless we need it on our
+ // struct and to be able to serialize it to/from the database.
+ uint32 local_flags = 1000;
+ // The version_hash is an implementation detail and not part of the wire
+ // format.
+ bytes version_hash = 1001;
+
+ bool deleted = 6;
+ bool invalid = 7 [(gogoproto.customname) = "RawInvalid"];
+ bool no_permissions = 8;
+}
+
+enum FileInfoType {
+ FILE = 0 [(gogoproto.enumvalue_customname) = "FileInfoTypeFile"];
+ DIRECTORY = 1 [(gogoproto.enumvalue_customname) = "FileInfoTypeDirectory"];
+ SYMLINK_FILE = 2 [(gogoproto.enumvalue_customname) = "FileInfoTypeDeprecatedSymlinkFile", deprecated = true];
+ SYMLINK_DIRECTORY = 3 [(gogoproto.enumvalue_customname) = "FileInfoTypeDeprecatedSymlinkDirectory", deprecated = true];
+ SYMLINK = 4 [(gogoproto.enumvalue_customname) = "FileInfoTypeSymlink"];
+}
+
+message BlockInfo {
+ option (gogoproto.goproto_stringer) = false;
+ bytes hash = 3;
+ int64 offset = 1;
+ int32 size = 2;
+ uint32 weak_hash = 4;
+}
+
+message Vector {
+ repeated Counter counters = 1 [(gogoproto.nullable) = false];
+}
+
+message Counter {
+ uint64 id = 1 [(gogoproto.customname) = "ID", (gogoproto.customtype) = "ShortID", (gogoproto.nullable) = false];
+ uint64 value = 2;
+}
+
+// Request
+
+message Request {
+ int32 id = 1 [(gogoproto.customname) = "ID"];
+ string folder = 2;
+ string name = 3;
+ int64 offset = 4;
+ int32 size = 5;
+ bytes hash = 6;
+ bool from_temporary = 7;
+ uint32 weak_hash = 8;
+}
+
+// Response
+
+message Response {
+ int32 id = 1 [(gogoproto.customname) = "ID"];
+ bytes data = 2;
+ ErrorCode code = 3;
+}
+
+enum ErrorCode {
+ NO_ERROR = 0 [(gogoproto.enumvalue_customname) = "ErrorCodeNoError"];
+ GENERIC = 1 [(gogoproto.enumvalue_customname) = "ErrorCodeGeneric"];
+ NO_SUCH_FILE = 2 [(gogoproto.enumvalue_customname) = "ErrorCodeNoSuchFile"];
+ INVALID_FILE = 3 [(gogoproto.enumvalue_customname) = "ErrorCodeInvalidFile"];
+}
+
+// DownloadProgress
+
+message DownloadProgress {
+ string folder = 1;
+ repeated FileDownloadProgressUpdate updates = 2 [(gogoproto.nullable) = false];
+}
+
+message FileDownloadProgressUpdate {
+ FileDownloadProgressUpdateType update_type = 1;
+ string name = 2;
+ Vector version = 3 [(gogoproto.nullable) = false];
+ repeated int32 block_indexes = 4 [packed=false];
+ int32 block_size = 5;
+}
+
+enum FileDownloadProgressUpdateType {
+ APPEND = 0 [(gogoproto.enumvalue_customname) = "UpdateTypeAppend"];
+ FORGET = 1 [(gogoproto.enumvalue_customname) = "UpdateTypeForget"];
+}
+
+// Ping
+
+message Ping {
+}
+
+// Close
+
+message Close {
+ string reason = 1;
+}
+
diff --git a/proto/scripts/dump_tags.go b/proto/scripts/dump_tags.go
new file mode 100644
index 000000000..71d089d4f
--- /dev/null
+++ b/proto/scripts/dump_tags.go
@@ -0,0 +1,68 @@
+// Copyright (C) 2020 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+//+build ignore
+
+package main
+
+import (
+ "encoding/csv"
+ "fmt"
+ "os"
+ "path/filepath"
+ "reflect"
+ "strings"
+
+ "github.com/syncthing/syncthing/lib/config"
+)
+
+func main() {
+ new, err := os.Create("tags.csv")
+ if err != nil {
+ panic(err)
+ }
+ fmt.Println(filepath.Abs(new.Name()))
+ w := csv.NewWriter(new)
+ w.Write([]string{
+ "path", "json", "xml", "default", "restart",
+ })
+ walk(w, "", &config.Configuration{})
+ w.Flush()
+ new.Close()
+}
+
+func walk(w *csv.Writer, prefix string, data interface{}) {
+ s := reflect.ValueOf(data).Elem()
+ t := s.Type()
+ for i := 0; i < s.NumField(); i++ {
+ f := s.Field(i)
+ ft := t.Field(i)
+
+ for f.Kind() == reflect.Ptr {
+ f = f.Elem()
+ }
+
+ pfx := prefix + "." + s.Type().Field(i).Name
+ if f.Kind() == reflect.Slice {
+ slc := reflect.MakeSlice(f.Type(), 1, 1)
+ f = slc.Index(0)
+ pfx = prefix + "." + s.Type().Field(i).Name + "[]"
+ }
+
+ if f.Kind() == reflect.Struct && strings.HasPrefix(f.Type().PkgPath(), "github.com/syncthing/syncthing") {
+ walk(w, pfx, f.Addr().Interface())
+ } else {
+ jsonTag := ft.Tag.Get("json")
+ xmlTag := ft.Tag.Get("xml")
+ defaultTag := ft.Tag.Get("default")
+ restartTag := ft.Tag.Get("restart")
+ w.Write([]string{
+ strings.ToLower(pfx), jsonTag, xmlTag, defaultTag, restartTag,
+ })
+ }
+ }
+
+}
diff --git a/proto/scripts/protoc_plugin.go b/proto/scripts/protoc_plugin.go
new file mode 100644
index 000000000..29565c169
--- /dev/null
+++ b/proto/scripts/protoc_plugin.go
@@ -0,0 +1,291 @@
+// Copyright (C) 2020 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+//+build ignore
+
+package main
+
+import (
+ "fmt"
+ "path/filepath"
+ "strings"
+ "unicode"
+
+ "github.com/syncthing/syncthing/proto/ext"
+
+ "github.com/gogo/protobuf/gogoproto"
+ "github.com/gogo/protobuf/proto"
+ "github.com/gogo/protobuf/protoc-gen-gogo/descriptor"
+ "github.com/gogo/protobuf/vanity"
+ "github.com/gogo/protobuf/vanity/command"
+)
+
+func main() {
+ req := command.Read()
+ files := req.GetProtoFile()
+ files = vanity.FilterFiles(files, vanity.NotGoogleProtobufDescriptorProto)
+
+ vanity.ForEachFile(files, vanity.TurnOffGoGettersAll)
+ vanity.ForEachFile(files, TurnOnProtoSizerAll)
+ vanity.ForEachFile(files, vanity.TurnOffGoEnumPrefixAll)
+ vanity.ForEachFile(files, vanity.TurnOffGoUnrecognizedAll)
+ vanity.ForEachFile(files, vanity.TurnOffGoUnkeyedAll)
+ vanity.ForEachFile(files, vanity.TurnOffGoSizecacheAll)
+ vanity.ForEachFile(files, vanity.TurnOffGoEnumStringerAll)
+ vanity.ForEachEnumInFiles(files, HandleCustomEnumExtensions)
+ vanity.ForEachFile(files, SetPackagePrefix("github.com/syncthing/syncthing"))
+
+ vanity.ForEachMessageInFiles(files, HandleCustomExtensions)
+ vanity.ForEachFieldInFilesExcludingExtensions(files, TurnOffNullableForMessages)
+
+ resp := command.Generate(req)
+ command.Write(resp)
+}
+
+func TurnOnProtoSizerAll(file *descriptor.FileDescriptorProto) {
+ vanity.SetBoolFileOption(gogoproto.E_ProtosizerAll, true)(file)
+}
+
+func TurnOffNullableForMessages(field *descriptor.FieldDescriptorProto) {
+ if !vanity.FieldHasBoolExtension(field, gogoproto.E_Nullable) {
+ _, hasCustomType := GetFieldStringExtension(field, gogoproto.E_Customtype)
+ if field.IsMessage() || hasCustomType {
+ vanity.SetBoolFieldOption(gogoproto.E_Nullable, false)(field)
+ }
+ }
+}
+
+func HandleCustomEnumExtensions(enum *descriptor.EnumDescriptorProto) {
+ for _, field := range enum.Value {
+ if field == nil {
+ continue
+ }
+ if field.Options == nil {
+ field.Options = &descriptor.EnumValueOptions{}
+ }
+ customName := gogoproto.GetEnumValueCustomName(field)
+ if customName != "" {
+ continue
+ }
+ if v, ok := GetEnumValueStringExtension(field, ext.E_Enumgoname); ok {
+ SetEnumValueStringFieldOption(field, gogoproto.E_EnumvalueCustomname, v)
+ } else {
+ SetEnumValueStringFieldOption(field, gogoproto.E_EnumvalueCustomname, toCamelCase(*field.Name, true))
+ }
+
+ }
+}
+
+func SetPackagePrefix(prefix string) func(file *descriptor.FileDescriptorProto) {
+ return func(file *descriptor.FileDescriptorProto) {
+ if file.Options.GoPackage == nil {
+ pkg, _ := filepath.Split(file.GetName())
+ fullPkg := prefix + "/" + strings.TrimSuffix(pkg, "/")
+ file.Options.GoPackage = &fullPkg
+ }
+ }
+}
+
+func toCamelCase(input string, firstUpper bool) string {
+ runes := []rune(strings.ToLower(input))
+ outputRunes := make([]rune, 0, len(runes))
+
+ nextUpper := false
+ for i, rune := range runes {
+ if rune == '_' {
+ nextUpper = true
+ continue
+ }
+ if (firstUpper && i == 0) || nextUpper {
+ rune = unicode.ToUpper(rune)
+ nextUpper = false
+ }
+ outputRunes = append(outputRunes, rune)
+ }
+ return string(outputRunes)
+}
+
+func SetStringFieldOption(field *descriptor.FieldDescriptorProto, extension *proto.ExtensionDesc, value string) {
+ if _, ok := GetFieldStringExtension(field, extension); ok {
+ return
+ }
+ if field.Options == nil {
+ field.Options = &descriptor.FieldOptions{}
+ }
+ if err := proto.SetExtension(field.Options, extension, &value); err != nil {
+ panic(err)
+ }
+}
+
+func SetEnumValueStringFieldOption(field *descriptor.EnumValueDescriptorProto, extension *proto.ExtensionDesc, value string) {
+ if _, ok := GetEnumValueStringExtension(field, extension); ok {
+ return
+ }
+ if field.Options == nil {
+ field.Options = &descriptor.EnumValueOptions{}
+ }
+ if err := proto.SetExtension(field.Options, extension, &value); err != nil {
+ panic(err)
+ }
+}
+
+func GetEnumValueStringExtension(enumValue *descriptor.EnumValueDescriptorProto, extension *proto.ExtensionDesc) (string, bool) {
+ if enumValue.Options == nil {
+ return "", false
+ }
+ value, err := proto.GetExtension(enumValue.Options, extension)
+ if err != nil {
+ return "", false
+ }
+ if value == nil {
+ return "", false
+ }
+ if v, ok := value.(*string); !ok || v == nil {
+ return "", false
+ } else {
+ return *v, true
+ }
+}
+
+func GetFieldStringExtension(field *descriptor.FieldDescriptorProto, extension *proto.ExtensionDesc) (string, bool) {
+ if field.Options == nil {
+ return "", false
+ }
+ value, err := proto.GetExtension(field.Options, extension)
+ if err != nil {
+ return "", false
+ }
+ if value == nil {
+ return "", false
+ }
+ if v, ok := value.(*string); !ok || v == nil {
+ return "", false
+ } else {
+ return *v, true
+ }
+}
+
+func GetFieldBooleanExtension(field *descriptor.FieldDescriptorProto, extension *proto.ExtensionDesc) (bool, bool) {
+ if field.Options == nil {
+ return false, false
+ }
+ value, err := proto.GetExtension(field.Options, extension)
+ if err != nil {
+ return false, false
+ }
+ if value == nil {
+ return false, false
+ }
+ if v, ok := value.(*bool); !ok || v == nil {
+ return false, false
+ } else {
+ return *v, true
+ }
+}
+
+func GetMessageBoolExtension(msg *descriptor.DescriptorProto, extension *proto.ExtensionDesc) (bool, bool) {
+ if msg.Options == nil {
+ return false, false
+ }
+ value, err := proto.GetExtension(msg.Options, extension)
+ if err != nil {
+ return false, false
+ }
+ if value == nil {
+ return false, false
+ }
+ val, ok := value.(*bool)
+ if !ok || val == nil {
+ return false, false
+ }
+ return *val, true
+}
+
+func HandleCustomExtensions(msg *descriptor.DescriptorProto) {
+ generateXmlTags := true
+ if generate, ok := GetMessageBoolExtension(msg, ext.E_XmlTags); ok {
+ generateXmlTags = generate
+ }
+
+ vanity.ForEachField([]*descriptor.DescriptorProto{msg}, func(field *descriptor.FieldDescriptorProto) {
+ if field.Options == nil {
+ field.Options = &descriptor.FieldOptions{}
+ }
+ deprecated := field.Options.Deprecated != nil && *field.Options.Deprecated == true
+
+ if field.Type != nil && *field.Type == descriptor.FieldDescriptorProto_TYPE_INT32 {
+ SetStringFieldOption(field, gogoproto.E_Casttype, "int")
+ }
+
+ if field.TypeName != nil && *field.TypeName == ".google.protobuf.Timestamp" {
+ vanity.SetBoolFieldOption(gogoproto.E_Stdtime, true)(field)
+ }
+
+ if goName, ok := GetFieldStringExtension(field, ext.E_Goname); ok {
+ SetStringFieldOption(field, gogoproto.E_Customname, goName)
+ } else if deprecated {
+ SetStringFieldOption(field, gogoproto.E_Customname, "Deprecated"+toCamelCase(*field.Name, true))
+ }
+
+ if val, ok := GetFieldBooleanExtension(field, ext.E_DeviceId); ok && val {
+ SetStringFieldOption(field, gogoproto.E_Customtype, "github.com/syncthing/syncthing/lib/protocol.DeviceID")
+ }
+
+ if jsonValue, ok := GetFieldStringExtension(field, ext.E_Json); ok {
+ SetStringFieldOption(field, gogoproto.E_Jsontag, jsonValue)
+ } else if deprecated {
+ SetStringFieldOption(field, gogoproto.E_Jsontag, "-")
+ } else {
+ SetStringFieldOption(field, gogoproto.E_Jsontag, toCamelCase(*field.Name, false))
+ }
+
+ current := ""
+ if v, ok := GetFieldStringExtension(field, gogoproto.E_Moretags); ok {
+ current = v
+ }
+
+ if generateXmlTags {
+ if len(current) > 0 {
+ current += " "
+ }
+ if xmlValue, ok := GetFieldStringExtension(field, ext.E_Xml); ok {
+ current += fmt.Sprintf(`xml:"%s"`, xmlValue)
+ } else {
+ xmlValue = toCamelCase(*field.Name, false)
+ // XML dictates element name within the collection, not collection name, so trim plural suffix.
+ if field.IsRepeated() {
+ if strings.HasSuffix(xmlValue, "ses") {
+ // addresses -> address
+ xmlValue = strings.TrimSuffix(xmlValue, "es")
+ } else {
+ // devices -> device
+ xmlValue = strings.TrimSuffix(xmlValue, "s")
+ }
+ }
+ if deprecated {
+ xmlValue += ",omitempty"
+ }
+ current += fmt.Sprintf(`xml:"%s"`, xmlValue)
+ }
+ }
+
+ if defaultValue, ok := GetFieldStringExtension(field, ext.E_Default); ok {
+ if len(current) > 0 {
+ current += " "
+ }
+ current += fmt.Sprintf(`default:"%s"`, defaultValue)
+ }
+
+ if restartValue, ok := GetFieldBooleanExtension(field, ext.E_Restart); ok {
+ if len(current) > 0 {
+ current += " "
+ }
+ current += fmt.Sprintf(`restart:"%t"`, restartValue)
+ }
+
+ SetStringFieldOption(field, gogoproto.E_Moretags, current)
+ })
+}
diff --git a/proto/scripts/protofmt.go b/proto/scripts/protofmt.go
new file mode 100644
index 000000000..2f0ae69d5
--- /dev/null
+++ b/proto/scripts/protofmt.go
@@ -0,0 +1,121 @@
+// Copyright (C) 2016 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+// +build ignore
+
+package main
+
+import (
+ "bufio"
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "text/tabwriter"
+)
+
+func main() {
+ flag.Parse()
+ for _, arg := range flag.Args() {
+ matches, err := filepath.Glob(arg)
+ if err != nil {
+ log.Fatal(err)
+ }
+ for _, file := range matches {
+ if stat, err := os.Stat(file); err != nil {
+ log.Fatal(err)
+ } else if stat.IsDir() {
+ err := filepath.Walk(file, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ if filepath.Ext(path) == ".proto" {
+ return formatProtoFile(path)
+ }
+ return nil
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+ } else {
+ if err := formatProtoFile(file); err != nil {
+ log.Fatal(err)
+ }
+ }
+ }
+ }
+}
+
+func formatProtoFile(file string) error {
+ log.Println("Formatting", file)
+ in, err := os.Open(file)
+ if err != nil {
+ return err
+ }
+ defer in.Close()
+ out, err := os.Create(file + ".tmp")
+ if err != nil {
+ return err
+ }
+ defer out.Close()
+ if err := formatProto(in, out); err != nil {
+ return err
+ }
+ in.Close()
+ out.Close()
+ return os.Rename(file+".tmp", file)
+}
+
+func formatProto(in io.Reader, out io.Writer) error {
+ sc := bufio.NewScanner(in)
+ lineExp := regexp.MustCompile(`([^=]+)\s+([^=\s]+?)\s*=(.+)`)
+ var tw *tabwriter.Writer
+ for sc.Scan() {
+ line := sc.Text()
+ if strings.HasPrefix(line, "//") {
+ if _, err := fmt.Fprintln(out, line); err != nil {
+ return err
+ }
+ continue
+ }
+
+ ms := lineExp.FindStringSubmatch(line)
+ for i := range ms {
+ ms[i] = strings.TrimSpace(ms[i])
+ }
+ if len(ms) == 4 && ms[1] != "option" {
+ typ := strings.Join(strings.Fields(ms[1]), " ")
+ name := ms[2]
+ id := ms[3]
+ if tw == nil {
+ tw = tabwriter.NewWriter(out, 4, 4, 1, ' ', 0)
+ }
+ if typ == "" {
+ // We're in an enum
+ fmt.Fprintf(tw, "\t%s\t= %s\n", name, id)
+ } else {
+ // Message
+ fmt.Fprintf(tw, "\t%s\t%s\t= %s\n", typ, name, id)
+ }
+ } else {
+ if tw != nil {
+ if err := tw.Flush(); err != nil {
+ return err
+ }
+ tw = nil
+ }
+ if _, err := fmt.Fprintln(out, line); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}