Add SparkplugB dissector
Add a dissector for SparkplugB as a heuristic subdissector of MQTT and which calls protobuf to dissect the messages payload.pespin/racap
parent
2c62e2eb3f
commit
f6ad4812a2
|
@ -2274,6 +2274,23 @@ add_custom_command(
|
|||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
)
|
||||
|
||||
file(GLOB _protobuf_src_files RELATIVE "${CMAKE_SOURCE_DIR}" protobuf/*.proto)
|
||||
set (_protobuf_data_files)
|
||||
foreach(_data_file ${_protobuf_src_files})
|
||||
list(APPEND _protobuf_data_files "${DATAFILE_DIR}/${_data_file}")
|
||||
endforeach()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${_protobuf_data_files}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${DATAFILE_DIR}/protobuf"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${_protobuf_src_files}"
|
||||
"${DATAFILE_DIR}/protobuf"
|
||||
VERBATIM
|
||||
DEPENDS ${_protobuf_src_files}
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
)
|
||||
|
||||
file(GLOB _profiles_src_files RELATIVE "${CMAKE_SOURCE_DIR}" profiles/*/*)
|
||||
set (_profiles_data_files)
|
||||
foreach(_data_file ${_profiles_src_files})
|
||||
|
@ -2290,6 +2307,7 @@ list(APPEND copy_data_files_depends
|
|||
${_dtds_data_files}
|
||||
${_diameter_data_files}
|
||||
${_radius_data_files}
|
||||
${_protobuf_data_files}
|
||||
${_profiles_data_files}
|
||||
)
|
||||
|
||||
|
|
4
NEWS
4
NEWS
|
@ -86,8 +86,8 @@ Wireshark 3.5.0 Release Notes
|
|||
New Protocol Support
|
||||
|
||||
Kerberos SPAKE, O-RAN fronthaul UC-plane (O-RAN), PDU Transport
|
||||
Protocol, R09.x (R09), State Synchronization Protocol (SSyncP), and
|
||||
UAVCAN\CAN
|
||||
Protocol, R09.x (R09), SparkplugB, State Synchronization Protocol (SSyncP),
|
||||
and UAVCAN\CAN.
|
||||
|
||||
Updated Protocol Support
|
||||
|
||||
|
|
|
@ -88,6 +88,7 @@ They previously shipped with Npcap 1.20.
|
|||
Kerberos SPAKE
|
||||
O-RAN fronthaul UC-plane (O-RAN)
|
||||
PDU Transport Protocol
|
||||
SparkplugB
|
||||
State Synchronization Protocol (SSyncP)
|
||||
UAVCAN\CAN
|
||||
R09.x (R09)
|
||||
|
|
|
@ -1773,6 +1773,7 @@ set(DISSECTOR_SRC
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/packet-someip.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/packet-someip-sd.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/packet-soupbintcp.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/packet-sparkplug.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/packet-spdy.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/packet-spice.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/packet-spp.c
|
||||
|
|
|
@ -752,7 +752,7 @@ static gboolean mqtt_user_decode_message(proto_tree *tree, proto_tree *mqtt_tree
|
|||
message_decode_entry->payload_proto_name);
|
||||
proto_item_set_generated(ti);
|
||||
|
||||
call_dissector(message_decode_entry->payload_proto, msg_tvb, pinfo, tree);
|
||||
call_dissector_with_data(message_decode_entry->payload_proto, msg_tvb, pinfo, tree, (void*)topic_str);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
/* packet-sparkplug.c
|
||||
* Routines for Sparkplug dissection
|
||||
* Copyright 2021 Graham Bloice <graham.bloice<at>trihedral.com>
|
||||
*
|
||||
* Wireshark - Network traffic analyzer
|
||||
* By Gerald Combs <gerald@wireshark.org>
|
||||
* Copyright 1998 Gerald Combs
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <epan/packet.h>
|
||||
#include <epan/expert.h>
|
||||
|
||||
/*
|
||||
* See
|
||||
*
|
||||
* https://cirrus-link.com/mqtt-sparkplug-tahu/
|
||||
*
|
||||
* and the specification is at
|
||||
*
|
||||
* https://www.eclipse.org/tahu/spec/Sparkplug%20Topic%20Namespace%20and%20State%20ManagementV2.2-with%20appendix%20B%20format%20-%20Eclipse.pdf
|
||||
*
|
||||
*/
|
||||
|
||||
/* Prototypes */
|
||||
/* (Required to prevent [-Wmissing-prototypes] warnings */
|
||||
void proto_reg_handoff_sparkplug(void);
|
||||
|
||||
/* Initialize the protocol field */
|
||||
static int proto_sparkplugb = -1;
|
||||
|
||||
/* Initialize the subtree pointers */
|
||||
static gint ett_sparkplugb = -1;
|
||||
static gint ett_sparkplugb_namespace = -1;
|
||||
|
||||
/* The handle to the protobuf dissector */
|
||||
dissector_handle_t protobuf_handle = NULL;
|
||||
|
||||
/* The hf items */
|
||||
static int hf_sparkplugb_namespace = -1;
|
||||
static int hf_sparkplugb_groupid = -1;
|
||||
static int hf_sparkplugb_messagetype = -1;
|
||||
static int hf_sparkplugb_edgenodeid = -1;
|
||||
static int hf_sparkplugb_deviceid = -1;
|
||||
|
||||
/* The expert info items */
|
||||
static expert_field ei_sparkplugb_missing_groupid = EI_INIT;
|
||||
static expert_field ei_sparkplugb_missing_messagetype = EI_INIT;
|
||||
static expert_field ei_sparkplugb_missing_edgenodeid = EI_INIT;
|
||||
|
||||
static gboolean
|
||||
dissect_sparkplugb(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
|
||||
{
|
||||
proto_item *ti;
|
||||
proto_tree *sparkplugb_tree, *namespace_tree;
|
||||
gchar **topic_elements, **current_element;
|
||||
char *topic = (char *)data;
|
||||
|
||||
/* Parse the topic into the elements */
|
||||
topic_elements = g_strsplit(topic, "/", 5);
|
||||
|
||||
/* Heuristic check that the first element of the topic is the SparkplugB namespace */
|
||||
if (!topic_elements || (strcmp("spBv1.0", topic_elements[0]) != 0)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Make entries in Protocol column */
|
||||
col_set_str(pinfo->cinfo, COL_PROTOCOL, "SparkplugB");
|
||||
|
||||
/* Adjust the info column */
|
||||
col_clear(pinfo->cinfo, COL_INFO);
|
||||
col_set_str(pinfo->cinfo, COL_INFO, "SparkplugB");
|
||||
|
||||
/* create display subtree for the protocol */
|
||||
ti = proto_tree_add_item(tree, proto_sparkplugb, tvb, 0, -1, ENC_NA);
|
||||
sparkplugb_tree = proto_item_add_subtree(ti, ett_sparkplugb);
|
||||
|
||||
/* Add the elements parsed out from the topic string */
|
||||
namespace_tree = proto_tree_add_subtree(sparkplugb_tree, tvb, 0, 0, ett_sparkplugb_namespace, &ti, "Topic Namespace");
|
||||
proto_item_set_generated(ti);
|
||||
|
||||
current_element = topic_elements;
|
||||
ti = proto_tree_add_string(namespace_tree, hf_sparkplugb_namespace, tvb, 0, 0, current_element[0]);
|
||||
proto_item_set_generated(ti);
|
||||
|
||||
current_element += 1;
|
||||
ti = proto_tree_add_string(namespace_tree, hf_sparkplugb_groupid, tvb, 0, 0, current_element[0]);
|
||||
proto_item_set_generated(ti);
|
||||
if (!current_element[0]) {
|
||||
expert_add_info(pinfo, ti, &ei_sparkplugb_missing_groupid);
|
||||
}
|
||||
|
||||
/* Adjust the info colum text with the message type */
|
||||
current_element += 1;
|
||||
if (current_element[0]) {
|
||||
col_append_sep_str(pinfo->cinfo, COL_INFO, NULL, current_element[0]);
|
||||
col_set_fence(pinfo->cinfo, COL_INFO);
|
||||
}
|
||||
|
||||
ti = proto_tree_add_string(namespace_tree, hf_sparkplugb_messagetype, tvb, 0, 0, current_element[0]);
|
||||
proto_item_set_generated(ti);
|
||||
if (!current_element[0]) {
|
||||
expert_add_info(pinfo, ti, &ei_sparkplugb_missing_messagetype);
|
||||
}
|
||||
|
||||
current_element += 1;
|
||||
ti = proto_tree_add_string(namespace_tree, hf_sparkplugb_edgenodeid, tvb, 0, 0, current_element[0]);
|
||||
proto_item_set_generated(ti);
|
||||
if (!current_element[0]) {
|
||||
expert_add_info(pinfo, ti, &ei_sparkplugb_missing_edgenodeid);
|
||||
}
|
||||
|
||||
/* Device ID is optional */
|
||||
current_element += 1;
|
||||
ti = proto_tree_add_string(namespace_tree, hf_sparkplugb_deviceid, tvb, 0, 0, current_element[0]);
|
||||
proto_item_set_generated(ti);
|
||||
|
||||
g_strfreev(topic_elements);
|
||||
|
||||
/* Now handoff the Payload message to the protobuf dissector */
|
||||
call_dissector_with_data(protobuf_handle, tvb, pinfo, sparkplugb_tree, "message,com.cirruslink.sparkplug.protobuf.Payload");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void proto_register_sparkplug(void)
|
||||
{
|
||||
expert_module_t* expert_sparkplugb;
|
||||
|
||||
static gint *ett[] = {
|
||||
&ett_sparkplugb,
|
||||
&ett_sparkplugb_namespace
|
||||
};
|
||||
|
||||
static hf_register_info hf[] = {
|
||||
{&hf_sparkplugb_namespace, {"Namespace", "sparkplugb.namespace", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL}},
|
||||
{&hf_sparkplugb_groupid, {"Group ID", "sparkplugb.groupid", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL}},
|
||||
{&hf_sparkplugb_messagetype, {"Message Type", "sparkplugb.messagetype", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL}},
|
||||
{&hf_sparkplugb_edgenodeid, {"Edge Node ID", "sparkplugb.edgenodeid", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL}},
|
||||
{&hf_sparkplugb_deviceid, {"Device ID", "sparkplugb.deviceid", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL}},
|
||||
};
|
||||
|
||||
static ei_register_info ei[] = {
|
||||
{ &ei_sparkplugb_missing_groupid, { "sparkplugb.missing_groupid", PI_MALFORMED, PI_ERROR, "Missing Group ID", EXPFILL }},
|
||||
{ &ei_sparkplugb_missing_messagetype, { "sparkplugb.missing_messagetype", PI_MALFORMED, PI_ERROR, "Missing Message Type", EXPFILL }},
|
||||
{ &ei_sparkplugb_missing_edgenodeid, { "sparkplugb.missing_edgenodeid", PI_MALFORMED, PI_ERROR, "Missing Edge Node ID", EXPFILL }},
|
||||
};
|
||||
|
||||
/* Register the protocol name and description, fields and trees */
|
||||
proto_sparkplugb = proto_register_protocol("SparkplugB", "SparkplugB", "sparkplugb");
|
||||
register_dissector("sparkplugb", dissect_sparkplugb, proto_sparkplugb);
|
||||
|
||||
proto_register_field_array(proto_sparkplugb, hf, array_length(hf));
|
||||
proto_register_subtree_array(ett, array_length(ett));
|
||||
|
||||
expert_sparkplugb = expert_register_protocol(proto_sparkplugb);
|
||||
expert_register_field_array(expert_sparkplugb, ei, array_length(ei));
|
||||
}
|
||||
|
||||
void proto_reg_handoff_sparkplug(void)
|
||||
{
|
||||
protobuf_handle = find_dissector_add_dependency("protobuf", proto_sparkplugb);
|
||||
|
||||
/* register as heuristic dissector with MQTT */
|
||||
heur_dissector_add("mqtt.topic", dissect_sparkplugb, "SparkplugB over MQTT",
|
||||
"sparkplugb_mqtt", proto_sparkplugb, HEURISTIC_ENABLE);
|
||||
}
|
|
@ -215,6 +215,7 @@ Delete "$INSTDIR\snmp\*.*"
|
|||
Delete "$INSTDIR\snmp\mibs\*.*"
|
||||
Delete "$INSTDIR\styles\translations\*.*"
|
||||
Delete "$INSTDIR\styles\*.*"
|
||||
Delete "$INSTDIR\protobuf\*.*"
|
||||
Delete "$INSTDIR\tpncp\*.*"
|
||||
Delete "$INSTDIR\translations\*.*"
|
||||
Delete "$INSTDIR\ui\*.*"
|
||||
|
@ -295,6 +296,7 @@ RMDir "$INSTDIR\snmp\mibs"
|
|||
RMDir "$INSTDIR\snmp"
|
||||
RMDir "$INSTDIR\radius"
|
||||
RMDir "$INSTDIR\dtds"
|
||||
RMDir "$INSTDIR\protobuf"
|
||||
RMDir "$INSTDIR\tpncp"
|
||||
RMDir "$INSTDIR\translations"
|
||||
RMDir "$INSTDIR\ui"
|
||||
|
|
|
@ -857,6 +857,12 @@ SetOutPath $INSTDIR
|
|||
; Create the extcap directory
|
||||
CreateDirectory $INSTDIR\extcap
|
||||
|
||||
;
|
||||
; install the protobuf .proto definitions in the protobuf subdirectory
|
||||
;
|
||||
SetOutPath $INSTDIR\protobuf
|
||||
File "${STAGING_DIR}\protobuf\sparkplug_b.proto"
|
||||
|
||||
; Install the TPNCP DAT file in the "tpncp" subdirectory
|
||||
; of the installation directory.
|
||||
SetOutPath $INSTDIR\tpncp
|
||||
|
|
|
@ -221,6 +221,20 @@
|
|||
</ComponentGroup>
|
||||
</Fragment>
|
||||
|
||||
<!-- protobuf TLV definitions -->
|
||||
<Fragment>
|
||||
<DirectoryRef Id="dirProtobuf">
|
||||
<Component Id="cmpProtobuf_sparkplug_b_proto" Guid="*">
|
||||
<File Id="filProtobuf_sparkplug_b_proto" KeyPath="yes" Source="$(var.Protobuf.Dir)\sparkplug_b.proto" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
</Fragment>
|
||||
<Fragment>
|
||||
<ComponentGroup Id="CG.Protobuf">
|
||||
<ComponentRef Id="cmpProtobuf_sparkplug_b_proto" />
|
||||
</ComponentGroup>
|
||||
</Fragment>
|
||||
|
||||
<!-- TShark -->
|
||||
<Fragment>
|
||||
<DirectoryRef Id="INSTALLFOLDER">
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
<Directory Id="dirTpncp" Name="tpncp"/>
|
||||
<Directory Id="dirTranslations" Name="translations"/>
|
||||
<Directory Id="dirWimaxasncp" Name="wimaxasncp"/>
|
||||
<Directory Id="dirProtobuf" Name="protobuf"/>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
<ComponentGroupRef Id="CG.Dtds" />
|
||||
<ComponentGroupRef Id="CG.Tpncp" />
|
||||
<ComponentGroupRef Id="CG.Wimaxasncp" />
|
||||
<ComponentGroupRef Id="CG.Protobuf" />
|
||||
<ComponentGroupRef Id="CG.RequiredDependencies" />
|
||||
|
||||
</Feature>
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
<?define Dtds.Dir ="$(var.Staging.Dir)\dtds" ?>
|
||||
<?define Tpncp.Dir ="$(var.Staging.Dir)\tpncp" ?>
|
||||
<?define Wimaxasncp.Dir ="$(var.Staging.Dir)\wimaxasncp" ?>
|
||||
<?define Protobuf.Dir ="$(var.Staging.Dir)\protobuf" ?>
|
||||
<?define Help.Dir ="$(var.Staging.Dir)\help" ?>
|
||||
<?define Epan.Lua.Dir ="..\..\epan\wslua" ?>
|
||||
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
syntax = "proto2";
|
||||
|
||||
//
|
||||
// To compile:
|
||||
// cd client_libraries/java
|
||||
// protoc --proto_path=../../ --java_out=src/main/java ../../sparkplug_b.proto
|
||||
//
|
||||
package com.cirruslink.sparkplug.protobuf;
|
||||
|
||||
option java_package = "com.cirruslink.sparkplug.protobuf";
|
||||
option java_outer_classname = "SparkplugBProto";
|
||||
|
||||
message Payload {
|
||||
/*
|
||||
// Indexes of Data Types
|
||||
|
||||
// Unknown placeholder for future expansion.
|
||||
Unknown = 0;
|
||||
|
||||
// Basic Types
|
||||
Int8 = 1;
|
||||
Int16 = 2;
|
||||
Int32 = 3;
|
||||
Int64 = 4;
|
||||
UInt8 = 5;
|
||||
UInt16 = 6;
|
||||
UInt32 = 7;
|
||||
UInt64 = 8;
|
||||
Float = 9;
|
||||
Double = 10;
|
||||
Boolean = 11;
|
||||
String = 12;
|
||||
DateTime = 13;
|
||||
Text = 14;
|
||||
|
||||
// Additional Metric Types
|
||||
UUID = 15;
|
||||
DataSet = 16;
|
||||
Bytes = 17;
|
||||
File = 18;
|
||||
Template = 19;
|
||||
|
||||
// Additional PropertyValue Types
|
||||
PropertySet = 20;
|
||||
PropertySetList = 21;
|
||||
|
||||
*/
|
||||
|
||||
message Template {
|
||||
|
||||
message Parameter {
|
||||
optional string name = 1;
|
||||
optional uint32 type = 2;
|
||||
|
||||
oneof value {
|
||||
uint32 int_value = 3;
|
||||
uint64 long_value = 4;
|
||||
float float_value = 5;
|
||||
double double_value = 6;
|
||||
bool boolean_value = 7;
|
||||
string string_value = 8;
|
||||
ParameterValueExtension extension_value = 9;
|
||||
}
|
||||
|
||||
message ParameterValueExtension {
|
||||
extensions 1 to max;
|
||||
}
|
||||
}
|
||||
|
||||
optional string version = 1; // The version of the Template to prevent mismatches
|
||||
repeated Metric metrics = 2; // Each metric is the name of the metric and the datatype of the member but does not contain a value
|
||||
repeated Parameter parameters = 3;
|
||||
optional string template_ref = 4; // Reference to a template if this is extending a Template or an instance - must exist if an instance
|
||||
optional bool is_definition = 5;
|
||||
extensions 6 to max;
|
||||
}
|
||||
|
||||
message DataSet {
|
||||
|
||||
message DataSetValue {
|
||||
|
||||
oneof value {
|
||||
uint32 int_value = 1;
|
||||
uint64 long_value = 2;
|
||||
float float_value = 3;
|
||||
double double_value = 4;
|
||||
bool boolean_value = 5;
|
||||
string string_value = 6;
|
||||
DataSetValueExtension extension_value = 7;
|
||||
}
|
||||
|
||||
message DataSetValueExtension {
|
||||
extensions 1 to max;
|
||||
}
|
||||
}
|
||||
|
||||
message Row {
|
||||
repeated DataSetValue elements = 1;
|
||||
extensions 2 to max; // For third party extensions
|
||||
}
|
||||
|
||||
optional uint64 num_of_columns = 1;
|
||||
repeated string columns = 2;
|
||||
repeated uint32 types = 3;
|
||||
repeated Row rows = 4;
|
||||
extensions 5 to max; // For third party extensions
|
||||
}
|
||||
|
||||
message PropertyValue {
|
||||
|
||||
optional uint32 type = 1;
|
||||
optional bool is_null = 2;
|
||||
|
||||
oneof value {
|
||||
uint32 int_value = 3;
|
||||
uint64 long_value = 4;
|
||||
float float_value = 5;
|
||||
double double_value = 6;
|
||||
bool boolean_value = 7;
|
||||
string string_value = 8;
|
||||
PropertySet propertyset_value = 9;
|
||||
PropertySetList propertysets_value = 10; // List of Property Values
|
||||
PropertyValueExtension extension_value = 11;
|
||||
}
|
||||
|
||||
message PropertyValueExtension {
|
||||
extensions 1 to max;
|
||||
}
|
||||
}
|
||||
|
||||
message PropertySet {
|
||||
repeated string keys = 1; // Names of the properties
|
||||
repeated PropertyValue values = 2;
|
||||
extensions 3 to max;
|
||||
}
|
||||
|
||||
message PropertySetList {
|
||||
repeated PropertySet propertyset = 1;
|
||||
extensions 2 to max;
|
||||
}
|
||||
|
||||
message MetaData {
|
||||
// Bytes specific metadata
|
||||
optional bool is_multi_part = 1;
|
||||
|
||||
// General metadata
|
||||
optional string content_type = 2; // Content/Media type
|
||||
optional uint64 size = 3; // File size, String size, Multi-part size, etc
|
||||
optional uint64 seq = 4; // Sequence number for multi-part messages
|
||||
|
||||
// File metadata
|
||||
optional string file_name = 5; // File name
|
||||
optional string file_type = 6; // File type (i.e. xml, json, txt, cpp, etc)
|
||||
optional string md5 = 7; // md5 of data
|
||||
|
||||
// Catchalls and future expansion
|
||||
optional string description = 8; // Could be anything such as json or xml of custom properties
|
||||
extensions 9 to max;
|
||||
}
|
||||
|
||||
message Metric {
|
||||
|
||||
optional string name = 1; // Metric name - should only be included on birth
|
||||
optional uint64 alias = 2; // Metric alias - tied to name on birth and included in all later DATA messages
|
||||
optional uint64 timestamp = 3; // Timestamp associated with data acquisition time
|
||||
optional uint32 datatype = 4; // DataType of the metric/tag value
|
||||
optional bool is_historical = 5; // If this is historical data and should not update real time tag
|
||||
optional bool is_transient = 6; // Tells consuming clients such as MQTT Engine to not store this as a tag
|
||||
optional bool is_null = 7; // If this is null - explicitly say so rather than using -1, false, etc for some datatypes.
|
||||
optional MetaData metadata = 8; // Metadata for the payload
|
||||
optional PropertySet properties = 9;
|
||||
|
||||
oneof value {
|
||||
uint32 int_value = 10;
|
||||
uint64 long_value = 11;
|
||||
float float_value = 12;
|
||||
double double_value = 13;
|
||||
bool boolean_value = 14;
|
||||
string string_value = 15;
|
||||
bytes bytes_value = 16; // Bytes, File
|
||||
DataSet dataset_value = 17;
|
||||
Template template_value = 18;
|
||||
MetricValueExtension extension_value = 19;
|
||||
}
|
||||
|
||||
message MetricValueExtension {
|
||||
extensions 1 to max;
|
||||
}
|
||||
}
|
||||
|
||||
optional uint64 timestamp = 1; // Timestamp at message sending time
|
||||
repeated Metric metrics = 2; // Repeated forever - no limit in Google Protobufs
|
||||
optional uint64 seq = 3; // Sequence number
|
||||
optional string uuid = 4; // UUID to track message type in terms of schema definitions
|
||||
optional bytes body = 5; // To optionally bypass the whole definition above
|
||||
extensions 6 to max; // For third party extensions
|
||||
}
|
Loading…
Reference in New Issue