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;
}

