// Copyright (c) 2022 Boston Dynamics, Inc.  All rights reserved.
//
// Downloading, reproducing, distributing or otherwise using the SDK Software
// is subject to the terms and conditions of the Boston Dynamics Software
// Development Kit License (20191101-BDSDK-SL).

syntax = "proto3";

import "google/protobuf/timestamp.proto";

package bosdyn.api;

// The file format consists of 3 kinds of blocks with simple framing:
//  - Serialized DescriptorBlock protos, describing either the main file,
//     a defining a series of data blocks, or a file index,
//  - Serialized DataDescriptorBlock protos, describing a single data block, and
//  - Binary data.

// A Descriptor block typically describes a series of messages, but the descriptor at the
//  start of the file describes the contents of the file as a whole, and the descriptor
//  at the end of the file is an index structure to allow efficient access to the contents
//  of the file.
message DescriptorBlock {
    oneof DescriptorType {
        FileFormatDescriptor file_descriptor = 1;
        SeriesDescriptor series_descriptor = 2;
        SeriesBlockIndex series_block_index = 3;
        FileIndex file_index = 4;
    };
}

// A DataDescriptor describes a data block which immediately follows it in the file.
// A corresponding SeriesDescriptor with a matching series_index must precede this in the file.
message DataDescriptor {
    // The series_index references the SeriesDescriptor to which the data following is associated.
    uint32 series_index = 1;

    // The time at which the data is considered to be captured/sampled.
    // E.g., the shutter-close time of a captured image.
    google.protobuf.Timestamp timestamp = 2;

    // Sometimes a visualizer will want to organize message by data timestamp, sometimes by
    //  the time messages were published or logged.
    // The additional_indexes field allows extra indexes or timestamps to be associated with
    //  each data block for this purpose.
    // Other identifying information may also be used here, such as the PID of the process which
    //  originated the data (e.g., for detecting if and when that process restarted).
    // The values in this field should correspond to the labels defined in "additional_index_names"
    //  in the corresponding SeriesDescriptor.
    repeated int64 additional_indexes = 3;
}

// The first block in the file should be a DescriptorBlock containing a FileFormatDescriptor.
// FileFormatDescriptor indicates the file format version and annotations.
// Annotations describe things like the robot from which the log was taken and the release id.
// The format of annotation keys should be
//   {project-or-organization}/{annotation-name}
// For example, 'bosdyn/robot-serial-number'.
message FileFormatDescriptor {
    // The version number of the BDDF file.
    FileFormatVersion version = 1;

    // File/stream-wide annotations to describe the content of the file.
    map<string, string> annotations = 2;

    enum CheckSumType {
        // Checksum type is unspecified.  Should not be used.
        CHECKSUM_TYPE_UNKNOWN = 0;

        // The writer of this stream is not computing a checksum.
        // The stream checksum at the end of the file will be 160 bits all set to 0.
        CHECKSUM_TYPE_NONE = 1;

        // A 160 bit SHA1 checksum will be included at the end of the stream.
        // This checksum will be computed over all data before digest itself at the
        //  end of the stream, and can be used to verify the stream was received uncorrupted.
        CHECKSUM_TYPE_SHA1 = 2;
    }
    // The type of checksum supported by this stream.
    // For BDDF version 1.0.0 this should be SHA1.
    CheckSumType checksum_type = 3;

    // The number of bytes used for the BDDF checksum.
    // For BDDF version 1.0.0 this should always be 20, even if CHECKSUM_NONE is used.
    uint32 checksum_num_bytes = 4;
}

// The current data file format is 1.0.0.
message FileFormatVersion {
    uint32 major_version = 1;
    uint32 minor_version = 2;
    uint32 patch_level = 3;
}

// A description of a series of data blocks.
// These data blocks may either represent binary messages of a variable size, or they may
//  represent a sequence of samples of POD data samples: single/vector/matrix/... of integer
//  or floating-point values.
message SeriesDescriptor {
    // This index for the series is unique within the data file.
    uint32 series_index = 1;

    // This is the globally unique {key -> value} mapping to identify the series.
    SeriesIdentifier series_identifier = 2;

    // This is a hash of the series_identifier.
    // The hash is the first 64 bits (read as a big-endian encoded uint64_t) of
    //  SHA1(S K1 V1 K2 V2 ...) where,
    //   - S is series identifier text,
    //   - K1 and V1 are the key and value of the first key and value of the `spec`,
    //   - K2 and V2 are the second key and value of the spec, etc...
    // Here, all strings are encoded as utf-8, and keys are sorted lexicographically using this
    //  encoding (K1 < K2 < ...).
    uint64 identifier_hash = 3;
    oneof DataType {
        MessageTypeDescriptor message_type = 4;
        PodTypeDescriptor pod_type = 5;
        StructTypeDescriptor struct_type = 6;
    };

    // Annotations are a {key -> value} mapping for associating additional information with
    //  the series.
    // The format of annotation keys should be
    //   {project-or-organization}/{annotation-name}
    // For example, 'bosdyn/channel-name', 'bosdyn/protobuf-type'.
    // Annotation keys without a '/' are reserved.
    // The only current key in the reserved namespace is 'units': e.g., {'units': 'm/s2'}.
    map<string, string> annotations = 7;

    // Labels for additional index values which should be attached to each DataDescriptor
    //  in the series.
    // See the description of "additional_indexes" in DataDescriptor.
    repeated string additional_index_names = 8;
    string description = 9;
}

// If a data series contains a sequence of binary messages, the encoding and format of these
//  messages is described by a MesssageTypeDescriptor.
message MessageTypeDescriptor {
    // Description of the content type.
    // E.g., "application/protobuf", "image/jpeg", "text/csv", ...
    string content_type = 1;

    // If content_type is "application/protobuf", this is the full-name of the protobuf type.
    string type_name = 2;

    // If true, message contents are necessary for interpreting other messages.
    // If the content of this file is split into multiple output files, these messages should be
    //  copied into each.
    bool is_metadata = 3;
}

// If a data series contains signals-style data of time-sampled "plain old datatypes", this
//  describes the content of the series.
// All POD data stored in data blocks is stored in little-endian byte order.
// Any number of samples may be stored within a given data block.
message PodTypeDescriptor {
    // The type of machine-readable values stored.
    PodTypeEnum pod_type = 1;

    // If empty, indicates a single POD per sample.
    // If one-element, indicates a vector of the given size per sample.
    // If two-elements, indicates a matrix of the given size, and so on.
    // An M x N x .. x P array of data is traversed from innermost (P) to outermost (M) dimension.
    repeated uint32 dimension = 2;
}

// A struct series is a composite formed by a set of other series whose messages or signals-ticks
//  are sampled at the same time.
// For example, all there may be a struct series for a set of signals variables, all from a
//  process with an 'update()' function within which all all variables are sampled with the
//  same timestamp.
// DataBlocks will not directly reference this series, but only child series of this series.
// Struct series may reference other struct series, but the series structure must be a directed
//  acyclic graph (DAG): no circular reference structures.
message StructTypeDescriptor {
    // A map of a name-reference to a series, identified by its series_identifer_hash.
    map<string, uint64> key_to_series_identifier_hash = 1;
}

// "Plain old data" types which may be stored within POD data blocks.
enum PodTypeEnum {
    TYPE_UNSPECIFIED = 0;
    TYPE_INT8 = 1;
    TYPE_INT16 = 2;
    TYPE_INT32 = 3;
    TYPE_INT64 = 4;
    TYPE_UINT8 = 5;
    TYPE_UINT16 = 6;
    TYPE_UINT32 = 7;
    TYPE_UINT64 = 8;
    TYPE_FLOAT32 = 9;
    TYPE_FLOAT64 = 10;
};

// As a file is closed, a DescriptorBlock containing a FileIndex should be written.
// The FileIndex summarizes the data series stored in the file and the location of the
//  block-indexes for each type in the file.
// Each series is assigned a "series_index" within the file, and this index may be used to
//  index into the repeated fields in this message.
// E.g., for the series with series_index N, you can access its SeriesIdentifier by accessing
//  element N the of the series_identifiers repeated field.
message FileIndex {
    // SeriesIdentifer for each series in this file.
    repeated SeriesIdentifier series_identifiers = 1;

    // The offset from the start of the file of the SeriesBlockIndex block for each series.
    repeated uint64 series_block_index_offsets = 2;

    // The hash of the series_identifier for each series.
    repeated uint64 series_identifier_hashes = 3;
}

// This describes the location of the SeriesDescriptor DescriptorBlock for the series, and
//  the timestamp and location in the file of every data block in the series.
message SeriesBlockIndex {
    // The series_index for the series described by this index block.
    uint32 series_index = 1;

    // Offset of type descriptor block from start of file.
    uint64 descriptor_file_offset = 2;

    message BlockEntry {
        // The timestamp of data in this block.
        google.protobuf.Timestamp timestamp = 1;

        // The offset of the data block from the start of the file.
        uint64 file_offset = 2;

        // Values of the additional indexes for describing this block.
        repeated int64 additional_indexes = 3;
    }
    // The timestamp and location of each data block for this series.
    repeated BlockEntry block_entries = 3;

    // The total size of the data stored in the data blocks of this series.
    uint64 total_bytes = 4;
}

// A key or description for selecting a message series.
// Because there may be multiple ways of describing a message series, we identify
//  them by a unique mapping of {key -> value}.
// A series_type corresponds to a set of keys which are expected in the mapping.
// A 'bosdyn:grpc:requests' series_type, containing GRPC robot-id request messages, might
//  thus be specified as:
//   {'service': 'robot_id', 'message': 'bosdyn.api.RobotIdRequest'}
// A 'bosdyn:logtick' series_type, containing a signals data variable from LogTick
//   annotations might be specified as:
//   {'varname': 'tablet.wifi.rssi', 'schema': 'tablet-comms', 'client': 'bd-tablet'}
message SeriesIdentifier {
    // This is the kind of spec, which should correspond to a set of keys which are expected
    //  in the spec.
    string series_type = 1;

    // This is the "key" for naming the series within the file.
    // A key->value description which should be unique for this series within the file
    //  with this series_type.
    map<string, string> spec = 2;
}
