diff --git a/CHANGELOG.md b/CHANGELOG.md
index ae76c08121..2b1476532f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ For details about compatibility between different releases, see the **Commitment
- New `ListBands` RPC on the `Configuration` service.
- Support for loading end device template from Device Repository when importing devices using a CSV file.
+- Experimental support for normalized payload.
### Changed
diff --git a/api/api.md b/api/api.md
index 87d1faa951..e5e5d1879b 100644
--- a/api/api.md
+++ b/api/api.md
@@ -484,6 +484,8 @@
- [Message `ApplicationUp`](#ttn.lorawan.v3.ApplicationUp)
- [Message `ApplicationUplink`](#ttn.lorawan.v3.ApplicationUplink)
- [Message `ApplicationUplink.LocationsEntry`](#ttn.lorawan.v3.ApplicationUplink.LocationsEntry)
+ - [Message `ApplicationUplinkNormalized`](#ttn.lorawan.v3.ApplicationUplinkNormalized)
+ - [Message `ApplicationUplinkNormalized.LocationsEntry`](#ttn.lorawan.v3.ApplicationUplinkNormalized.LocationsEntry)
- [Message `DownlinkMessage`](#ttn.lorawan.v3.DownlinkMessage)
- [Message `DownlinkQueueOperationErrorDetails`](#ttn.lorawan.v3.DownlinkQueueOperationErrorDetails)
- [Message `DownlinkQueueRequest`](#ttn.lorawan.v3.DownlinkQueueRequest)
@@ -1522,6 +1524,7 @@ The ApplicationUpStorage service can be used to query stored application upstrea
| `downlink_push` | [`ApplicationPubSub.Message`](#ttn.lorawan.v3.ApplicationPubSub.Message) | | The topic to which the Application Server subscribes for downlink queue push operations. |
| `downlink_replace` | [`ApplicationPubSub.Message`](#ttn.lorawan.v3.ApplicationPubSub.Message) | | The topic to which the Application Server subscribes for downlink queue replace operations. |
| `uplink_message` | [`ApplicationPubSub.Message`](#ttn.lorawan.v3.ApplicationPubSub.Message) | | |
+| `uplink_normalized` | [`ApplicationPubSub.Message`](#ttn.lorawan.v3.ApplicationPubSub.Message) | | |
| `join_accept` | [`ApplicationPubSub.Message`](#ttn.lorawan.v3.ApplicationPubSub.Message) | | |
| `downlink_ack` | [`ApplicationPubSub.Message`](#ttn.lorawan.v3.ApplicationPubSub.Message) | | |
| `downlink_nack` | [`ApplicationPubSub.Message`](#ttn.lorawan.v3.ApplicationPubSub.Message) | | |
@@ -1780,6 +1783,7 @@ The NATS provider settings.
| `template_fields` | [`ApplicationWebhook.TemplateFieldsEntry`](#ttn.lorawan.v3.ApplicationWebhook.TemplateFieldsEntry) | repeated | The value of the fields used by the template. Maps field.id to the value. |
| `downlink_api_key` | [`string`](#string) | | The API key to be used for downlink queue operations. The field is provided for convenience reasons, and can contain API keys with additional rights (albeit this is discouraged). |
| `uplink_message` | [`ApplicationWebhook.Message`](#ttn.lorawan.v3.ApplicationWebhook.Message) | | |
+| `uplink_normalized` | [`ApplicationWebhook.Message`](#ttn.lorawan.v3.ApplicationWebhook.Message) | | |
| `join_accept` | [`ApplicationWebhook.Message`](#ttn.lorawan.v3.ApplicationWebhook.Message) | | |
| `downlink_ack` | [`ApplicationWebhook.Message`](#ttn.lorawan.v3.ApplicationWebhook.Message) | | |
| `downlink_nack` | [`ApplicationWebhook.Message`](#ttn.lorawan.v3.ApplicationWebhook.Message) | | |
@@ -1894,6 +1898,7 @@ The NATS provider settings.
| `fields` | [`ApplicationWebhookTemplateField`](#ttn.lorawan.v3.ApplicationWebhookTemplateField) | repeated | |
| `create_downlink_api_key` | [`bool`](#bool) | | Control the creation of the downlink queue operations API key. |
| `uplink_message` | [`ApplicationWebhookTemplate.Message`](#ttn.lorawan.v3.ApplicationWebhookTemplate.Message) | | |
+| `uplink_normalized` | [`ApplicationWebhookTemplate.Message`](#ttn.lorawan.v3.ApplicationWebhookTemplate.Message) | | |
| `join_accept` | [`ApplicationWebhookTemplate.Message`](#ttn.lorawan.v3.ApplicationWebhookTemplate.Message) | | |
| `downlink_ack` | [`ApplicationWebhookTemplate.Message`](#ttn.lorawan.v3.ApplicationWebhookTemplate.Message) | | |
| `downlink_nack` | [`ApplicationWebhookTemplate.Message`](#ttn.lorawan.v3.ApplicationWebhookTemplate.Message) | | |
@@ -6933,6 +6938,7 @@ Transmission settings for downlink.
| Field | Validations |
| ----- | ----------- |
| `session_key_id` |
`bytes.max_len`: `2048`
|
+| `received_at` | `timestamp.required`: `true`
|
### Message `ApplicationLocation`
@@ -6973,6 +6979,7 @@ Application uplink message.
| `correlation_ids` | [`string`](#string) | repeated | |
| `received_at` | [`google.protobuf.Timestamp`](#google.protobuf.Timestamp) | | Server time when the Application Server received the message. |
| `uplink_message` | [`ApplicationUplink`](#ttn.lorawan.v3.ApplicationUplink) | | |
+| `uplink_normalized` | [`ApplicationUplinkNormalized`](#ttn.lorawan.v3.ApplicationUplinkNormalized) | | |
| `join_accept` | [`ApplicationJoinAccept`](#ttn.lorawan.v3.ApplicationJoinAccept) | | |
| `downlink_ack` | [`ApplicationDownlink`](#ttn.lorawan.v3.ApplicationDownlink) | | |
| `downlink_nack` | [`ApplicationDownlink`](#ttn.lorawan.v3.ApplicationDownlink) | | |
@@ -6996,18 +7003,20 @@ Application uplink message.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `session_key_id` | [`bytes`](#bytes) | | Join Server issued identifier for the session keys used by this uplink. |
-| `f_port` | [`uint32`](#uint32) | | |
-| `f_cnt` | [`uint32`](#uint32) | | |
+| `f_port` | [`uint32`](#uint32) | | LoRaWAN FPort of the uplink message. |
+| `f_cnt` | [`uint32`](#uint32) | | LoRaWAN FCntUp of the uplink message. |
| `frm_payload` | [`bytes`](#bytes) | | The frame payload of the uplink message. The payload is still encrypted if the skip_payload_crypto field of the EndDevice is true, which is indicated by the presence of the app_s_key field. |
| `decoded_payload` | [`google.protobuf.Struct`](#google.protobuf.Struct) | | The decoded frame payload of the uplink message. This field is set by the message processor that is configured for the end device (see formatters) or application (see default_formatters). |
| `decoded_payload_warnings` | [`string`](#string) | repeated | Warnings generated by the message processor while decoding the frm_payload. |
+| `normalized_payload` | [`google.protobuf.Struct`](#google.protobuf.Struct) | repeated | The normalized frame payload of the uplink message. This field is set by the message processor that is configured for the end device (see formatters) or application (see default_formatters). If the message processor is a custom script, there is no uplink normalizer script and the decoded output is valid normalized payload, this field contains the decoded payload. |
+| `normalized_payload_warnings` | [`string`](#string) | repeated | Warnings generated by the message processor while normalizing the decoded payload. |
| `rx_metadata` | [`RxMetadata`](#ttn.lorawan.v3.RxMetadata) | repeated | A list of metadata for each antenna of each gateway that received this message. |
-| `settings` | [`TxSettings`](#ttn.lorawan.v3.TxSettings) | | Settings for the transmission. |
+| `settings` | [`TxSettings`](#ttn.lorawan.v3.TxSettings) | | Transmission settings used by the end device. |
| `received_at` | [`google.protobuf.Timestamp`](#google.protobuf.Timestamp) | | Server time when the Network Server received the message. |
| `app_s_key` | [`KeyEnvelope`](#ttn.lorawan.v3.KeyEnvelope) | | The AppSKey of the current session. This field is only present if the skip_payload_crypto field of the EndDevice is true. Can be used to decrypt uplink payloads and encrypt downlink payloads. |
| `last_a_f_cnt_down` | [`uint32`](#uint32) | | The last AFCntDown of the current session. This field is only present if the skip_payload_crypto field of the EndDevice is true. Can be used with app_s_key to encrypt downlink payloads. |
-| `confirmed` | [`bool`](#bool) | | |
-| `consumed_airtime` | [`google.protobuf.Duration`](#google.protobuf.Duration) | | Consumed airtime for the transmission of the uplink message. Calculated by Network Server using the RawPayload size and the transmission settings. |
+| `confirmed` | [`bool`](#bool) | | Indicates whether the end device used confirmed data uplink. |
+| `consumed_airtime` | [`google.protobuf.Duration`](#google.protobuf.Duration) | | Consumed airtime for the transmission of the uplink message. Calculated by Network Server using the raw payload size and the transmission settings. |
| `locations` | [`ApplicationUplink.LocationsEntry`](#ttn.lorawan.v3.ApplicationUplink.LocationsEntry) | repeated | End device location metadata, set by the Application Server while handling the message. |
| `version_ids` | [`EndDeviceVersionIdentifiers`](#ttn.lorawan.v3.EndDeviceVersionIdentifiers) | | End device version identifiers, set by the Application Server while handling the message. |
| `network_ids` | [`NetworkIdentifiers`](#ttn.lorawan.v3.NetworkIdentifiers) | | Network identifiers, set by the Network Server that handles the message. |
@@ -7027,6 +7036,42 @@ Application uplink message.
| `key` | [`string`](#string) | | |
| `value` | [`Location`](#ttn.lorawan.v3.Location) | | |
+### Message `ApplicationUplinkNormalized`
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `session_key_id` | [`bytes`](#bytes) | | Join Server issued identifier for the session keys used by this uplink. |
+| `f_port` | [`uint32`](#uint32) | | LoRaWAN FPort of the uplink message. |
+| `f_cnt` | [`uint32`](#uint32) | | LoRaWAN FCntUp of the uplink message. |
+| `frm_payload` | [`bytes`](#bytes) | | The frame payload of the uplink message. This field is always decrypted with AppSKey. |
+| `normalized_payload` | [`google.protobuf.Struct`](#google.protobuf.Struct) | | The normalized frame payload of the uplink message. This field is set for each item in normalized_payload in the corresponding ApplicationUplink message. |
+| `normalized_payload_warnings` | [`string`](#string) | repeated | This field is set to normalized_payload_warnings in the corresponding ApplicationUplink message. |
+| `rx_metadata` | [`RxMetadata`](#ttn.lorawan.v3.RxMetadata) | repeated | A list of metadata for each antenna of each gateway that received this message. |
+| `settings` | [`TxSettings`](#ttn.lorawan.v3.TxSettings) | | Transmission settings used by the end device. |
+| `received_at` | [`google.protobuf.Timestamp`](#google.protobuf.Timestamp) | | Server time when the Network Server received the message. |
+| `confirmed` | [`bool`](#bool) | | Indicates whether the end device used confirmed data uplink. |
+| `consumed_airtime` | [`google.protobuf.Duration`](#google.protobuf.Duration) | | Consumed airtime for the transmission of the uplink message. Calculated by Network Server using the raw payload size and the transmission settings. |
+| `locations` | [`ApplicationUplinkNormalized.LocationsEntry`](#ttn.lorawan.v3.ApplicationUplinkNormalized.LocationsEntry) | repeated | End device location metadata, set by the Application Server while handling the message. |
+| `version_ids` | [`EndDeviceVersionIdentifiers`](#ttn.lorawan.v3.EndDeviceVersionIdentifiers) | | End device version identifiers, set by the Application Server while handling the message. |
+| `network_ids` | [`NetworkIdentifiers`](#ttn.lorawan.v3.NetworkIdentifiers) | | Network identifiers, set by the Network Server that handles the message. |
+
+#### Field Rules
+
+| Field | Validations |
+| ----- | ----------- |
+| `session_key_id` | `bytes.max_len`: `2048`
|
+| `f_port` | `uint32.lte`: `255`
`uint32.gte`: `1`
`uint32.not_in`: `[224]`
|
+| `normalized_payload` | `message.required`: `true`
|
+| `settings` | `message.required`: `true`
|
+| `received_at` | `timestamp.required`: `true`
|
+
+### Message `ApplicationUplinkNormalized.LocationsEntry`
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `key` | [`string`](#string) | | |
+| `value` | [`Location`](#ttn.lorawan.v3.Location) | | |
+
### Message `DownlinkMessage`
Downlink message from the network to the end device
diff --git a/api/api.swagger.json b/api/api.swagger.json
index a5a9359d33..ebe3d561d5 100644
--- a/api/api.swagger.json
+++ b/api/api.swagger.json
@@ -4166,6 +4166,9 @@
"uplink_message": {
"$ref": "#/definitions/v3ApplicationUplink"
},
+ "uplink_normalized": {
+ "$ref": "#/definitions/v3ApplicationUplinkNormalized"
+ },
"join_accept": {
"$ref": "#/definitions/v3ApplicationJoinAccept"
},
@@ -4701,6 +4704,9 @@
"uplink_message": {
"$ref": "#/definitions/v3ApplicationPubSubMessage"
},
+ "uplink_normalized": {
+ "$ref": "#/definitions/v3ApplicationPubSubMessage"
+ },
"join_accept": {
"$ref": "#/definitions/v3ApplicationPubSubMessage"
},
@@ -4826,6 +4832,9 @@
"uplink_message": {
"$ref": "#/definitions/v3ApplicationPubSubMessage"
},
+ "uplink_normalized": {
+ "$ref": "#/definitions/v3ApplicationPubSubMessage"
+ },
"join_accept": {
"$ref": "#/definitions/v3ApplicationPubSubMessage"
},
@@ -5156,6 +5165,9 @@
"uplink_message": {
"$ref": "#/definitions/v3ApplicationWebhookMessage"
},
+ "uplink_normalized": {
+ "$ref": "#/definitions/v3ApplicationWebhookMessage"
+ },
"join_accept": {
"$ref": "#/definitions/v3ApplicationWebhookMessage"
},
@@ -5292,6 +5304,9 @@
"uplink_message": {
"$ref": "#/definitions/v3ApplicationWebhookMessage"
},
+ "uplink_normalized": {
+ "$ref": "#/definitions/v3ApplicationWebhookMessage"
+ },
"join_accept": {
"$ref": "#/definitions/v3ApplicationWebhookMessage"
},
@@ -19219,6 +19234,9 @@
"uplink_message": {
"$ref": "#/definitions/v3ApplicationPubSubMessage"
},
+ "uplink_normalized": {
+ "$ref": "#/definitions/v3ApplicationPubSubMessage"
+ },
"join_accept": {
"$ref": "#/definitions/v3ApplicationPubSubMessage"
},
@@ -19322,6 +19340,9 @@
"uplink_message": {
"$ref": "#/definitions/v3ApplicationUplink"
},
+ "uplink_normalized": {
+ "$ref": "#/definitions/v3ApplicationUplinkNormalized"
+ },
"join_accept": {
"$ref": "#/definitions/v3ApplicationJoinAccept"
},
@@ -19366,11 +19387,13 @@
},
"f_port": {
"type": "integer",
- "format": "int64"
+ "format": "int64",
+ "description": "LoRaWAN FPort of the uplink message."
},
"f_cnt": {
"type": "integer",
- "format": "int64"
+ "format": "int64",
+ "description": "LoRaWAN FCntUp of the uplink message."
},
"frm_payload": {
"type": "string",
@@ -19388,6 +19411,20 @@
},
"description": "Warnings generated by the message processor while decoding the frm_payload."
},
+ "normalized_payload": {
+ "type": "array",
+ "items": {
+ "type": "object"
+ },
+ "description": "The normalized frame payload of the uplink message.\nThis field is set by the message processor that is configured for the end device (see formatters) or application (see default_formatters).\nIf the message processor is a custom script, there is no uplink normalizer script and the decoded output is valid\nnormalized payload, this field contains the decoded payload."
+ },
+ "normalized_payload_warnings": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Warnings generated by the message processor while normalizing the decoded payload."
+ },
"rx_metadata": {
"type": "array",
"items": {
@@ -19397,7 +19434,7 @@
},
"settings": {
"$ref": "#/definitions/lorawanv3TxSettings",
- "description": "Settings for the transmission."
+ "description": "Transmission settings used by the end device."
},
"received_at": {
"type": "string",
@@ -19414,11 +19451,87 @@
"description": "The last AFCntDown of the current session.\nThis field is only present if the skip_payload_crypto field of the EndDevice\nis true.\nCan be used with app_s_key to encrypt downlink payloads."
},
"confirmed": {
- "type": "boolean"
+ "type": "boolean",
+ "description": "Indicates whether the end device used confirmed data uplink."
},
"consumed_airtime": {
"type": "string",
- "description": "Consumed airtime for the transmission of the uplink message. Calculated by Network Server using the RawPayload size and the transmission settings."
+ "description": "Consumed airtime for the transmission of the uplink message. Calculated by Network Server using the raw payload size and the transmission settings."
+ },
+ "locations": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/definitions/lorawanv3Location"
+ },
+ "description": "End device location metadata, set by the Application Server while handling the message."
+ },
+ "version_ids": {
+ "$ref": "#/definitions/v3EndDeviceVersionIdentifiers",
+ "description": "End device version identifiers, set by the Application Server while handling the message."
+ },
+ "network_ids": {
+ "$ref": "#/definitions/v3NetworkIdentifiers",
+ "description": "Network identifiers, set by the Network Server that handles the message."
+ }
+ }
+ },
+ "v3ApplicationUplinkNormalized": {
+ "type": "object",
+ "properties": {
+ "session_key_id": {
+ "type": "string",
+ "format": "byte",
+ "description": "Join Server issued identifier for the session keys used by this uplink."
+ },
+ "f_port": {
+ "type": "integer",
+ "format": "int64",
+ "description": "LoRaWAN FPort of the uplink message."
+ },
+ "f_cnt": {
+ "type": "integer",
+ "format": "int64",
+ "description": "LoRaWAN FCntUp of the uplink message."
+ },
+ "frm_payload": {
+ "type": "string",
+ "format": "byte",
+ "description": "The frame payload of the uplink message.\nThis field is always decrypted with AppSKey."
+ },
+ "normalized_payload": {
+ "type": "object",
+ "description": "The normalized frame payload of the uplink message.\nThis field is set for each item in normalized_payload in the corresponding ApplicationUplink message."
+ },
+ "normalized_payload_warnings": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "This field is set to normalized_payload_warnings in the corresponding ApplicationUplink message."
+ },
+ "rx_metadata": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/lorawanv3RxMetadata"
+ },
+ "description": "A list of metadata for each antenna of each gateway that received this message."
+ },
+ "settings": {
+ "$ref": "#/definitions/lorawanv3TxSettings",
+ "description": "Transmission settings used by the end device."
+ },
+ "received_at": {
+ "type": "string",
+ "format": "date-time",
+ "description": "Server time when the Network Server received the message."
+ },
+ "confirmed": {
+ "type": "boolean",
+ "description": "Indicates whether the end device used confirmed data uplink."
+ },
+ "consumed_airtime": {
+ "type": "string",
+ "description": "Consumed airtime for the transmission of the uplink message. Calculated by Network Server using the raw payload size and the transmission settings."
},
"locations": {
"type": "object",
@@ -19484,6 +19597,9 @@
"uplink_message": {
"$ref": "#/definitions/v3ApplicationWebhookMessage"
},
+ "uplink_normalized": {
+ "$ref": "#/definitions/v3ApplicationWebhookMessage"
+ },
"join_accept": {
"$ref": "#/definitions/v3ApplicationWebhookMessage"
},
@@ -19610,6 +19726,9 @@
"uplink_message": {
"$ref": "#/definitions/v3ApplicationWebhookTemplateMessage"
},
+ "uplink_normalized": {
+ "$ref": "#/definitions/v3ApplicationWebhookTemplateMessage"
+ },
"join_accept": {
"$ref": "#/definitions/v3ApplicationWebhookTemplateMessage"
},
diff --git a/api/applicationserver_pubsub.proto b/api/applicationserver_pubsub.proto
index 75068f880f..b106088b25 100644
--- a/api/applicationserver_pubsub.proto
+++ b/api/applicationserver_pubsub.proto
@@ -174,6 +174,7 @@ message ApplicationPubSub {
Message downlink_replace = 8;
Message uplink_message = 9;
+ Message uplink_normalized = 20;
Message join_accept = 10;
Message downlink_ack = 11;
Message downlink_nack = 12;
@@ -184,7 +185,7 @@ message ApplicationPubSub {
Message location_solved = 16;
Message service_data = 18;
- // next: 20
+ // next: 21
}
message ApplicationPubSubs {
diff --git a/api/applicationserver_web.proto b/api/applicationserver_web.proto
index d7725f04ab..9976f79a8b 100644
--- a/api/applicationserver_web.proto
+++ b/api/applicationserver_web.proto
@@ -88,6 +88,7 @@ message ApplicationWebhookTemplate {
string path = 1 [(validate.rules).string.max_len = 64];
}
Message uplink_message = 11;
+ Message uplink_normalized = 23;
Message join_accept = 12;
Message downlink_ack = 13;
Message downlink_nack = 14;
@@ -100,7 +101,7 @@ message ApplicationWebhookTemplate {
google.protobuf.FieldMask field_mask = 22;
- // next: 23
+ // next: 24
}
message ApplicationWebhookTemplates {
@@ -166,6 +167,7 @@ message ApplicationWebhook {
string path = 1 [(validate.rules).string.max_len = 64];
}
Message uplink_message = 7;
+ Message uplink_normalized = 22;
Message join_accept = 8;
Message downlink_ack = 9;
Message downlink_nack = 10;
@@ -180,7 +182,7 @@ message ApplicationWebhook {
google.protobuf.FieldMask field_mask = 21;
- // next: 22
+ // next: 23
}
message ApplicationWebhooks {
diff --git a/api/messages.proto b/api/messages.proto
index 7ee9f3d4c7..515908d101 100644
--- a/api/messages.proto
+++ b/api/messages.proto
@@ -190,7 +190,9 @@ message ApplicationUplink {
option (thethings.flags.message) = { select: true, set: false };
// Join Server issued identifier for the session keys used by this uplink.
bytes session_key_id = 1 [(validate.rules).bytes.max_len = 2048];
+ // LoRaWAN FPort of the uplink message.
uint32 f_port = 2 [(validate.rules).uint32 = {lte: 255, not_in: [224]}];
+ // LoRaWAN FCntUp of the uplink message.
uint32 f_cnt = 3;
// The frame payload of the uplink message.
@@ -204,10 +206,18 @@ message ApplicationUplink {
// Warnings generated by the message processor while decoding the frm_payload.
repeated string decoded_payload_warnings = 12;
+ // The normalized frame payload of the uplink message.
+ // This field is set by the message processor that is configured for the end device (see formatters) or application (see default_formatters).
+ // If the message processor is a custom script, there is no uplink normalizer script and the decoded output is valid
+ // normalized payload, this field contains the decoded payload.
+ repeated google.protobuf.Struct normalized_payload = 17;
+ // Warnings generated by the message processor while normalizing the decoded payload.
+ repeated string normalized_payload_warnings = 18;
+
// A list of metadata for each antenna of each gateway that received this message.
repeated RxMetadata rx_metadata = 6;
- // Settings for the transmission.
+ // Transmission settings used by the end device.
TxSettings settings = 7 [(validate.rules).message.required = true];
// Server time when the Network Server received the message.
@@ -224,9 +234,10 @@ message ApplicationUplink {
// Can be used with app_s_key to encrypt downlink payloads.
uint32 last_a_f_cnt_down = 10;
+ // Indicates whether the end device used confirmed data uplink.
bool confirmed = 11;
- // Consumed airtime for the transmission of the uplink message. Calculated by Network Server using the RawPayload size and the transmission settings.
+ // Consumed airtime for the transmission of the uplink message. Calculated by Network Server using the raw payload size and the transmission settings.
google.protobuf.Duration consumed_airtime = 13;
// End device location metadata, set by the Application Server while handling the message.
@@ -237,6 +248,54 @@ message ApplicationUplink {
// Network identifiers, set by the Network Server that handles the message.
NetworkIdentifiers network_ids = 16;
+
+ // next: 19
+}
+
+message ApplicationUplinkNormalized {
+ option (thethings.flags.message) = { select: true, set: false };
+ // Join Server issued identifier for the session keys used by this uplink.
+ bytes session_key_id = 1 [(validate.rules).bytes.max_len = 2048];
+ // LoRaWAN FPort of the uplink message.
+ uint32 f_port = 2 [(validate.rules).uint32 = {gte: 1, lte: 255, not_in: [224]}];
+ // LoRaWAN FCntUp of the uplink message.
+ uint32 f_cnt = 3;
+
+ // The frame payload of the uplink message.
+ // This field is always decrypted with AppSKey.
+ bytes frm_payload = 4;
+
+ // The normalized frame payload of the uplink message.
+ // This field is set for each item in normalized_payload in the corresponding ApplicationUplink message.
+ google.protobuf.Struct normalized_payload = 5 [(validate.rules).message.required = true];
+ // This field is set to normalized_payload_warnings in the corresponding ApplicationUplink message.
+ repeated string normalized_payload_warnings = 6;
+
+ // A list of metadata for each antenna of each gateway that received this message.
+ repeated RxMetadata rx_metadata = 7;
+
+ // Transmission settings used by the end device.
+ TxSettings settings = 8 [(validate.rules).message.required = true];
+
+ // Server time when the Network Server received the message.
+ google.protobuf.Timestamp received_at = 9 [(validate.rules).timestamp.required = true];
+
+ // Indicates whether the end device used confirmed data uplink.
+ bool confirmed = 10;
+
+ // Consumed airtime for the transmission of the uplink message. Calculated by Network Server using the raw payload size and the transmission settings.
+ google.protobuf.Duration consumed_airtime = 11;
+
+ // End device location metadata, set by the Application Server while handling the message.
+ map locations = 12;
+
+ // End device version identifiers, set by the Application Server while handling the message.
+ EndDeviceVersionIdentifiers version_ids = 13;
+
+ // Network identifiers, set by the Network Server that handles the message.
+ NetworkIdentifiers network_ids = 14;
+
+ // next: 15
}
message ApplicationLocation {
@@ -264,7 +323,7 @@ message ApplicationJoinAccept {
// rejoin-request.
bool pending_session = 4;
// Server time when the Network Server received the message.
- google.protobuf.Timestamp received_at = 8;
+ google.protobuf.Timestamp received_at = 8 [(validate.rules).timestamp.required = true];
}
message ApplicationDownlink {
@@ -389,6 +448,7 @@ message ApplicationUp {
option (validate.required) = true;
ApplicationUplink uplink_message = 3;
+ ApplicationUplinkNormalized uplink_normalized = 15;
ApplicationJoinAccept join_accept = 4;
ApplicationDownlink downlink_ack = 5;
ApplicationDownlink downlink_nack = 6;
@@ -402,6 +462,8 @@ message ApplicationUp {
// Signals if the message is coming from the Network Server or is simulated.
bool simulated = 14;
+
+ // next: 17
}
enum PayloadFormatter {
diff --git a/config/messages.json b/config/messages.json
index 74311d334b..e4ca5c2d83 100644
--- a/config/messages.json
+++ b/config/messages.json
@@ -6938,6 +6938,60 @@
"file": "javascript.go"
}
},
+ "error:pkg/messageprocessors/normalizedpayload:field_exclusive_maximum": {
+ "translations": {
+ "en": "`{path}` should be less than `{maximum}`"
+ },
+ "description": {
+ "package": "pkg/messageprocessors/normalizedpayload",
+ "file": "uplink.go"
+ }
+ },
+ "error:pkg/messageprocessors/normalizedpayload:field_exclusive_minimum": {
+ "translations": {
+ "en": "`{path}` should be greater than `{minimum}`"
+ },
+ "description": {
+ "package": "pkg/messageprocessors/normalizedpayload",
+ "file": "uplink.go"
+ }
+ },
+ "error:pkg/messageprocessors/normalizedpayload:field_maximum": {
+ "translations": {
+ "en": "`{path}` should be equal or less than `{maximum}`"
+ },
+ "description": {
+ "package": "pkg/messageprocessors/normalizedpayload",
+ "file": "uplink.go"
+ }
+ },
+ "error:pkg/messageprocessors/normalizedpayload:field_minimum": {
+ "translations": {
+ "en": "`{path}` should be equal or greater than `{minimum}`"
+ },
+ "description": {
+ "package": "pkg/messageprocessors/normalizedpayload",
+ "file": "uplink.go"
+ }
+ },
+ "error:pkg/messageprocessors/normalizedpayload:field_type": {
+ "translations": {
+ "en": "invalid field type of `{path}`"
+ },
+ "description": {
+ "package": "pkg/messageprocessors/normalizedpayload",
+ "file": "uplink.go"
+ }
+ },
+ "error:pkg/messageprocessors/normalizedpayload:unknown_field": {
+ "translations": {
+ "en": "unknown field `{path}`"
+ },
+ "description": {
+ "package": "pkg/messageprocessors/normalizedpayload",
+ "file": "uplink.go"
+ }
+ },
"error:pkg/messageprocessors:formatter_not_configured": {
"translations": {
"en": "formatter `{formatter}` is not configured"
@@ -9260,6 +9314,15 @@
"file": "observability.go"
}
},
+ "event:as.up.data.normalize.warning": {
+ "translations": {
+ "en": "normalize uplink data message warning"
+ },
+ "description": {
+ "package": "pkg/applicationserver",
+ "file": "observability.go"
+ }
+ },
"event:as.up.data.receive": {
"translations": {
"en": "receive uplink data message"
@@ -9305,6 +9368,15 @@
"file": "observability.go"
}
},
+ "event:as.up.normalized.forward": {
+ "translations": {
+ "en": "forward normalized uplink message"
+ },
+ "description": {
+ "package": "pkg/applicationserver",
+ "file": "observability.go"
+ }
+ },
"event:as.up.service.forward": {
"translations": {
"en": "forward service data message"
diff --git a/cypress/integration/console/integrations/webhooks/create-with-template.spec.js b/cypress/integration/console/integrations/webhooks/create-with-template.spec.js
index 9edeb48036..c6336d833b 100644
--- a/cypress/integration/console/integrations/webhooks/create-with-template.spec.js
+++ b/cypress/integration/console/integrations/webhooks/create-with-template.spec.js
@@ -37,7 +37,7 @@ describe('Application Webhook create', () => {
cy.loginConsole({ user_id: userId, password: user.password })
cy.intercept(
'GET',
- `/api/v3/as/webhook-templates?field_mask=base_url,create_downlink_api_key,description,documentation_url,downlink_ack,downlink_failed,downlink_nack,downlink_queue_invalidated,downlink_queued,downlink_sent,fields,format,headers,ids,info_url,join_accept,location_solved,logo_url,name,service_data,uplink_message`,
+ `/api/v3/as/webhook-templates?field_mask=base_url,create_downlink_api_key,description,documentation_url,downlink_ack,downlink_failed,downlink_nack,downlink_queue_invalidated,downlink_queued,downlink_sent,fields,format,headers,ids,info_url,join_accept,location_solved,logo_url,name,service_data,uplink_message,uplink_normalized`,
{ fixture: 'console/application/integrations/webhook/template.json' },
)
cy.visit(
diff --git a/go.mod b/go.mod
index 87ea8f6617..608cc11c17 100644
--- a/go.mod
+++ b/go.mod
@@ -59,7 +59,7 @@ require (
github.com/blevesearch/bleve v1.0.14
github.com/bluele/gcache v0.0.2
github.com/disintegration/imaging v1.6.2
- github.com/dop251/goja v0.0.0-20220214123719-b09a6bfa842f
+ github.com/dop251/goja v0.0.0-20220815083517-0c74f9139fd6
github.com/dustin/go-humanize v1.0.0
github.com/eclipse/paho.mqtt.golang v1.3.5
github.com/emersion/go-smtp v0.15.0
@@ -180,7 +180,7 @@ require (
github.com/couchbase/vellum v1.0.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
- github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect
+ github.com/dlclark/regexp2 v1.7.0 // indirect
github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-kit/log v0.2.0 // indirect
diff --git a/go.sum b/go.sum
index 2079d24941..cd6f8f4dbf 100644
--- a/go.sum
+++ b/go.sum
@@ -193,9 +193,15 @@ github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E=
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
+github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
+github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
+github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
github.com/dop251/goja v0.0.0-20220214123719-b09a6bfa842f h1:ztRywKO1rqqS8li0TDcnwi9AGsqAH0ky9NaND69/Ccc=
github.com/dop251/goja v0.0.0-20220214123719-b09a6bfa842f/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
+github.com/dop251/goja v0.0.0-20220815083517-0c74f9139fd6 h1:xHdUVG+c8SWJnct16Z3QJOVlaYo3OwoJyamo6kR6OL0=
+github.com/dop251/goja v0.0.0-20220815083517-0c74f9139fd6/go.mod h1:yRkwfj0CBpOGre+TwBsqPV0IH0Pk73e4PXJOeNDboGs=
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
+github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eclipse/paho.mqtt.golang v1.3.5 h1:sWtmgNxYM9P2sP+xEItMozsR3w0cqZFlqnNN1bdl41Y=
diff --git a/pkg/applicationserver/applicationserver.go b/pkg/applicationserver/applicationserver.go
index dec319150c..5ff4576b82 100644
--- a/pkg/applicationserver/applicationserver.go
+++ b/pkg/applicationserver/applicationserver.go
@@ -124,10 +124,7 @@ func New(c *component.Component, conf *Config) (as *ApplicationServer, err error
deviceRegistry: wrapEndDeviceRegistryWithReplacedFields(conf.Devices, replacedEndDeviceFields...),
appUpsRegistry: conf.UplinkStorage.Registry,
locationRegistry: conf.EndDeviceMetadataStorage.Location.Registry,
- formatters: messageprocessors.MapPayloadProcessor{
- ttnpb.PayloadFormatter_FORMATTER_JAVASCRIPT: javascript.New(),
- ttnpb.PayloadFormatter_FORMATTER_CAYENNELPP: cayennelpp.New(),
- },
+ formatters: make(messageprocessors.MapPayloadProcessor),
clusterDistributor: distribution.NewPubSubDistributor(
ctx,
c,
@@ -145,11 +142,16 @@ func New(c *component.Component, conf *Config) (as *ApplicationServer, err error
interopClient: interopCl,
interopID: conf.Interop.ID,
}
+
+ as.formatters[ttnpb.PayloadFormatter_FORMATTER_JAVASCRIPT] = javascript.New()
+ as.formatters[ttnpb.PayloadFormatter_FORMATTER_CAYENNELPP] = cayennelpp.New()
+ as.formatters[ttnpb.PayloadFormatter_FORMATTER_REPOSITORY] = devicerepository.New(as.formatters, as)
+
as.activationPool = workerpool.NewWorkerPool(workerpool.Config[*ttnpb.EndDeviceIdentifiers]{
Component: c,
Context: ctx,
- Name: "save_activation_status",
- Handler: as.saveActivationStatus,
+ Name: "set_activated",
+ Handler: as.setActivated,
})
as.processingPool = workerpool.NewWorkerPool(workerpool.Config[*ttnpb.ApplicationUp]{
Component: c,
@@ -157,16 +159,13 @@ func New(c *component.Component, conf *Config) (as *ApplicationServer, err error
Name: "process_application_uplinks",
Handler: as.processUpAsync,
})
-
as.deviceLastSeenPool = workerpool.NewWorkerPool(workerpool.Config[lastSeenAtInfo]{
Component: c,
Context: ctx,
- Name: "save_device_last_seen_from_uplink",
- Handler: as.processDeviceLastSeenAsync,
+ Name: "store_device_last_seen_from_uplink",
+ Handler: as.storeDeviceLastSeen,
})
- as.formatters[ttnpb.PayloadFormatter_FORMATTER_REPOSITORY] = devicerepository.New(as.formatters, as)
-
as.grpc.asDevices = asEndDeviceRegistryServer{
AS: as,
kekLabel: conf.DeviceKEKLabel,
@@ -282,24 +281,24 @@ func (as *ApplicationServer) RegisterServices(s *grpc.Server) {
if ps := as.pubsub; ps != nil {
ttnpb.RegisterApplicationPubSubRegistryServer(s, ps)
}
- if packages := as.appPackages; packages != nil {
- packages.RegisterServices(s)
+ if pkgs := as.appPackages; pkgs != nil {
+ pkgs.RegisterServices(s)
}
}
// RegisterHandlers registers gRPC handlers.
func (as *ApplicationServer) RegisterHandlers(s *runtime.ServeMux, conn *grpc.ClientConn) {
- ttnpb.RegisterAsHandler(as.Context(), s, conn)
- ttnpb.RegisterAsEndDeviceRegistryHandler(as.Context(), s, conn)
- ttnpb.RegisterAppAsHandler(as.Context(), s, conn)
+ ttnpb.RegisterAsHandler(as.Context(), s, conn) //nolint:errcheck
+ ttnpb.RegisterAsEndDeviceRegistryHandler(as.Context(), s, conn) //nolint:errcheck
+ ttnpb.RegisterAppAsHandler(as.Context(), s, conn) //nolint:errcheck
if as.webhooks != nil {
- ttnpb.RegisterApplicationWebhookRegistryHandler(as.Context(), s, conn)
+ ttnpb.RegisterApplicationWebhookRegistryHandler(as.Context(), s, conn) //nolint:errcheck
}
if as.pubsub != nil {
- ttnpb.RegisterApplicationPubSubRegistryHandler(as.Context(), s, conn)
+ ttnpb.RegisterApplicationPubSubRegistryHandler(as.Context(), s, conn) //nolint:errcheck
}
- if packages := as.appPackages; packages != nil {
- packages.RegisterHandlers(s, conn)
+ if pkgs := as.appPackages; pkgs != nil {
+ pkgs.RegisterHandlers(s, conn)
}
}
@@ -308,13 +307,13 @@ func (as *ApplicationServer) RegisterRoutes(s *web.Server) {
if wh := as.webhooks; wh != nil {
wh.RegisterRoutes(s)
}
- if packages := as.appPackages; packages != nil {
- packages.RegisterRoutes(s)
+ if pkgs := as.appPackages; pkgs != nil {
+ pkgs.RegisterRoutes(s)
}
}
// Roles returns the roles that the Application Server fulfills.
-func (as *ApplicationServer) Roles() []ttnpb.ClusterRole {
+func (*ApplicationServer) Roles() []ttnpb.ClusterRole {
return []ttnpb.ClusterRole{ttnpb.ClusterRole_APPLICATION_SERVER}
}
@@ -359,10 +358,9 @@ type lastSeenAtInfo struct {
lastSeenAt *pbtypes.Timestamp
}
-func (as *ApplicationServer) processDeviceLastSeenAsync(ctx context.Context, lastSeenEntry lastSeenAtInfo) {
+func (as *ApplicationServer) storeDeviceLastSeen(ctx context.Context, lastSeenEntry lastSeenAtInfo) {
if err := as.deviceLastSeenProvider.PushLastSeenFromUplink(ctx, lastSeenEntry.ids, lastSeenEntry.lastSeenAt); err != nil {
- log.FromContext(ctx).WithError(err).Warn("Failed to update device last seen timestamp")
- return
+ log.FromContext(ctx).WithError(err).Warn("Failed to set device last seen timestamp")
}
}
@@ -374,8 +372,6 @@ func (as *ApplicationServer) processUp(ctx context.Context, up *ttnpb.Applicatio
up.CorrelationIds = events.CorrelationIDsFromContext(ctx)
registerReceiveUp(ctx, up)
- up.ReceivedAt = ttnpb.ProtoTimePtr(time.Now())
-
pass, err := as.handleUp(ctx, up, link)
if err != nil {
log.FromContext(ctx).WithError(err).Warn("Failed to process upstream message")
@@ -459,8 +455,8 @@ func (as *ApplicationServer) buildSessionsFromError(ctx context.Context, dev *tt
}, nil
}
- ttnErr, ok := err.(errors.ErrorDetails)
- if !ok {
+ var ttnErr errors.ErrorDetails
+ if !errors.As(err, &ttnErr) {
return nil, err
}
details := ttnErr.Details()
@@ -469,6 +465,7 @@ func (as *ApplicationServer) buildSessionsFromError(ctx context.Context, dev *tt
}
var diagnostics *ttnpb.DownlinkQueueOperationErrorDetails
for _, detail := range details {
+ var ok bool
diagnostics, ok = detail.(*ttnpb.DownlinkQueueOperationErrorDetails)
if ok {
break
@@ -799,7 +796,9 @@ func (as *ApplicationServer) handleUp(ctx context.Context, up *ttnpb.Application
case *ttnpb.ApplicationUp_JoinAccept:
return true, as.handleJoinAccept(ctx, up.EndDeviceIds, p.JoinAccept, link)
case *ttnpb.ApplicationUp_UplinkMessage:
- return true, as.handleUplink(ctx, up.EndDeviceIds, p.UplinkMessage, link)
+ return true, as.handleUplink(ctx, uplinkInfo{up.EndDeviceIds, up.ReceivedAt, p.UplinkMessage, false, link})
+ case *ttnpb.ApplicationUp_UplinkNormalized:
+ return true, nil
case *ttnpb.ApplicationUp_DownlinkQueueInvalidated:
return as.handleDownlinkQueueInvalidated(ctx, up.EndDeviceIds, p.DownlinkQueueInvalidated, link)
case *ttnpb.ApplicationUp_DownlinkSent:
@@ -822,7 +821,7 @@ func (as *ApplicationServer) handleUp(ctx context.Context, up *ttnpb.Application
func (as *ApplicationServer) handleSimulatedUp(ctx context.Context, up *ttnpb.ApplicationUp, link *ttnpb.ApplicationLink) error {
switch p := up.Up.(type) {
case *ttnpb.ApplicationUp_UplinkMessage:
- return as.handleSimulatedUplink(ctx, up.EndDeviceIds, p.UplinkMessage, link)
+ return as.handleSimulatedUplink(ctx, uplinkInfo{up.EndDeviceIds, up.ReceivedAt, p.UplinkMessage, true, link})
default:
return nil
}
@@ -833,7 +832,7 @@ var errFetchAppSKey = errors.Define("app_s_key", "failed to get AppSKey")
// handleJoinAccept handles a join-accept message.
// If the application or device is not configured to skip application crypto, the InvalidatedDownlinks and the AppSKey
// in the given join-accept message is reset.
-func (as *ApplicationServer) handleJoinAccept(ctx context.Context, ids *ttnpb.EndDeviceIdentifiers, joinAccept *ttnpb.ApplicationJoinAccept, link *ttnpb.ApplicationLink) (err error) {
+func (as *ApplicationServer) handleJoinAccept(ctx context.Context, ids *ttnpb.EndDeviceIdentifiers, joinAccept *ttnpb.ApplicationJoinAccept, link *ttnpb.ApplicationLink) error {
defer trace.StartRegion(ctx, "handle join accept").End()
logger := log.FromContext(ctx).WithFields(log.Fields(
@@ -845,6 +844,7 @@ func (as *ApplicationServer) handleJoinAccept(ctx context.Context, ids *ttnpb.En
if err != nil {
return err
}
+
_, err = as.deviceRegistry.Set(ctx, ids,
[]string{
"formatters",
@@ -927,10 +927,19 @@ func (as *ApplicationServer) handleJoinAccept(ctx context.Context, ids *ttnpb.En
return dev, mask, nil
},
)
- if err == nil {
- as.deviceLastSeenPool.Publish(ctx, lastSeenAtInfo{ids: ids, lastSeenAt: joinAccept.ReceivedAt})
+ if err != nil {
+ return err
}
- return err
+
+ // Publish last seen event.
+ if err := as.deviceLastSeenPool.Publish(ctx, lastSeenAtInfo{
+ ids: ids,
+ lastSeenAt: joinAccept.ReceivedAt,
+ }); err != nil {
+ logger.WithError(err).Warn("Failed to publish last seen event")
+ }
+
+ return nil
}
var errUnknownSession = errors.DefineNotFound("unknown_session", "unknown session")
@@ -939,9 +948,9 @@ var errUnknownSession = errors.DefineNotFound("unknown_session", "unknown sessio
// This function will mutate the provided ttnpb.EndDevice and migrate the Session field to the session that matches
// the provided session key ID.
// The following fields are expected to be part of the provided ttnpb.EndDevice:
-// - session and pending_session - used to decide which session is currently active.
-// - formatters, version_ids - used by the downlink queue encoders, in cases in which the queue must be recalculated.
-// - skip_payload_crypto_override - used by the downlink queue migration mechanism in order to avoid payload encryption.
+// - session and pending_session, used to decide which session is currently active.
+// - formatters, version_ids, used by the downlink queue encoders, in cases in which the queue must be recalculated.
+// - skip_payload_crypto_override, used by the downlink queue migration mechanism in order to avoid payload encryption.
func (as *ApplicationServer) matchSession(ctx context.Context, ids *ttnpb.EndDeviceIdentifiers, dev *ttnpb.EndDevice, link *ttnpb.ApplicationLink, sessionKeyID []byte) ([]string, error) {
logger := log.FromContext(ctx)
var mask []string
@@ -976,8 +985,12 @@ func (as *ApplicationServer) matchSession(ctx context.Context, ids *ttnpb.EndDev
// Only fields which are used by integrations are stored.
// The fields which are stored are based on the following usages:
// - io/packages/loragls/v3/package.go#multiFrameQuery
-// - io/packages/loragls/v3/api/objects.go#parseRxMetadata
-func (as *ApplicationServer) storeUplink(ctx context.Context, ids *ttnpb.EndDeviceIdentifiers, uplink *ttnpb.ApplicationUplink) error {
+// - io/packages/loragls/v3/api/objects.go#parseRxMetadata.
+func (as *ApplicationServer) storeUplink(
+ ctx context.Context,
+ ids *ttnpb.EndDeviceIdentifiers,
+ uplink *ttnpb.ApplicationUplink,
+) error {
cleanUplink := &ttnpb.ApplicationUplink{
RxMetadata: make([]*ttnpb.RxMetadata, 0, len(uplink.RxMetadata)),
ReceivedAt: uplink.ReceivedAt,
@@ -1000,11 +1013,11 @@ func (as *ApplicationServer) storeUplink(ctx context.Context, ids *ttnpb.EndDevi
return as.appUpsRegistry.Push(ctx, ids, cleanUplink)
}
-// saveActivationStatus attempts to mark the end device as activated in the Entity Registry.
+// setActivated attempts to mark the end device as activated in the Entity Registry.
// If the update succeeds, the end device will be updated in the Application Server end device registry
// in order to avoid subsequent calls.
-func (as *ApplicationServer) saveActivationStatus(ctx context.Context, ids *ttnpb.EndDeviceIdentifiers) {
- defer trace.StartRegion(ctx, "save activation status").End()
+func (as *ApplicationServer) setActivated(ctx context.Context, ids *ttnpb.EndDeviceIdentifiers) {
+ defer trace.StartRegion(ctx, "set activated").End()
cc, err := as.GetPeerConn(ctx, ttnpb.ClusterRole_ENTITY_REGISTRY, nil)
if err != nil {
@@ -1038,14 +1051,51 @@ func (as *ApplicationServer) saveActivationStatus(ctx context.Context, ids *ttnp
}
}
-func (as *ApplicationServer) handleUplink(ctx context.Context, ids *ttnpb.EndDeviceIdentifiers, uplink *ttnpb.ApplicationUplink, link *ttnpb.ApplicationLink) (err error) {
+func (as *ApplicationServer) publishNormalizedUplink(ctx context.Context, info uplinkInfo) error {
+ for _, measurement := range info.uplink.NormalizedPayload {
+ if err := as.Publish(ctx, &ttnpb.ApplicationUp{
+ EndDeviceIds: info.ids,
+ CorrelationIds: events.CorrelationIDsFromContext(ctx),
+ ReceivedAt: info.receivedAt,
+ Up: &ttnpb.ApplicationUp_UplinkNormalized{
+ UplinkNormalized: &ttnpb.ApplicationUplinkNormalized{
+ SessionKeyId: info.uplink.SessionKeyId,
+ FPort: info.uplink.FPort,
+ FCnt: info.uplink.FCnt,
+ FrmPayload: info.uplink.FrmPayload,
+ NormalizedPayload: measurement,
+ NormalizedPayloadWarnings: info.uplink.NormalizedPayloadWarnings,
+ RxMetadata: info.uplink.RxMetadata,
+ Settings: info.uplink.Settings,
+ ReceivedAt: info.uplink.ReceivedAt,
+ Confirmed: info.uplink.Confirmed,
+ ConsumedAirtime: info.uplink.ConsumedAirtime,
+ Locations: info.uplink.Locations,
+ VersionIds: info.uplink.VersionIds,
+ NetworkIds: info.uplink.NetworkIds,
+ },
+ },
+ Simulated: info.simulated,
+ }); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+type uplinkInfo struct {
+ ids *ttnpb.EndDeviceIdentifiers
+ receivedAt *pbtypes.Timestamp
+ uplink *ttnpb.ApplicationUplink
+ simulated bool
+ link *ttnpb.ApplicationLink
+}
+
+func (as *ApplicationServer) handleUplink(ctx context.Context, info uplinkInfo) error {
defer trace.StartRegion(ctx, "handle uplink").End()
- ctx = log.NewContextWithField(ctx, "session_key_id", uplink.SessionKeyId)
- if uplink.ReceivedAt == nil {
- panic("no NS timestamp in ApplicationUplink")
- }
- dev, err := as.deviceRegistry.Set(ctx, ids,
+ ctx = log.NewContextWithField(ctx, "session_key_id", info.uplink.SessionKeyId)
+ dev, err := as.deviceRegistry.Set(ctx, info.ids,
[]string{
"activated_at",
"formatters",
@@ -1056,9 +1106,9 @@ func (as *ApplicationServer) handleUplink(ctx context.Context, ids *ttnpb.EndDev
},
func(dev *ttnpb.EndDevice) (*ttnpb.EndDevice, []string, error) {
if dev == nil {
- return nil, nil, errDeviceNotFound.WithAttributes("device_uid", unique.ID(ctx, ids))
+ return nil, nil, errDeviceNotFound.WithAttributes("device_uid", unique.ID(ctx, info.ids))
}
- mask, err := as.matchSession(ctx, ids, dev, link, uplink.SessionKeyId)
+ mask, err := as.matchSession(ctx, info.ids, dev, info.link, info.uplink.SessionKeyId)
if err != nil {
return nil, nil, err
}
@@ -1071,40 +1121,44 @@ func (as *ApplicationServer) handleUplink(ctx context.Context, ids *ttnpb.EndDev
if err != nil {
return err
}
- if !as.skipPayloadCrypto(ctx, link, dev, dev.Session) {
- if err := as.decryptAndDecodeUplink(ctx, dev, uplink, link.DefaultFormatters); err != nil {
+
+ if !as.skipPayloadCrypto(ctx, info.link, dev, dev.Session) {
+ if err := as.decryptAndDecodeUplink(ctx, dev, info.uplink, info.link.DefaultFormatters); err != nil {
+ return err
+ }
+ if err := as.publishNormalizedUplink(ctx, info); err != nil {
return err
}
- if err := as.storeUplink(ctx, ids, uplink); err != nil {
+ if err := as.storeUplink(ctx, info.ids, info.uplink); err != nil {
return err
}
} else if appSKey := dev.GetSession().GetKeys().GetAppSKey(); appSKey != nil {
- uplink.AppSKey = appSKey
- uplink.LastAFCntDown = dev.Session.LastAFCntDown
+ info.uplink.AppSKey = appSKey
+ info.uplink.LastAFCntDown = dev.Session.LastAFCntDown
}
- registerUplinkLatency(ctx, uplink)
+ registerUplinkLatency(ctx, info.uplink)
if dev.VersionIds != nil {
- uplink.VersionIds = dev.VersionIds
+ info.uplink.VersionIds = dev.VersionIds
}
- if locations, err := as.locationRegistry.Get(ctx, ids); err != nil {
+ // Set location in message and publish location solved if the payload contains location information.
+ if locations, err := as.locationRegistry.Get(ctx, info.ids); err != nil {
log.FromContext(ctx).WithError(err).Warn("Failed to retrieve end device locations")
} else {
- uplink.Locations = locations
+ info.uplink.Locations = locations
}
-
- loc := as.locationFromDecodedPayload(uplink)
+ loc := as.locationFromPayload(info.uplink)
if loc != nil {
- if uplink.Locations == nil {
- uplink.Locations = make(map[string]*ttnpb.Location, 1)
+ if info.uplink.Locations == nil {
+ info.uplink.Locations = make(map[string]*ttnpb.Location, 1)
}
- uplink.Locations["frm-payload"] = loc
+ info.uplink.Locations["frm-payload"] = loc
if err := as.Publish(ctx, &ttnpb.ApplicationUp{
- EndDeviceIds: ids,
+ EndDeviceIds: info.ids,
CorrelationIds: events.CorrelationIDsFromContext(ctx),
- ReceivedAt: uplink.ReceivedAt,
+ ReceivedAt: info.receivedAt,
Up: &ttnpb.ApplicationUp_LocationSolved{
LocationSolved: &ttnpb.ApplicationLocation{
Service: "frm-payload",
@@ -1116,22 +1170,29 @@ func (as *ApplicationServer) handleUplink(ctx context.Context, ids *ttnpb.EndDev
}
}
+ // If the device has not been activated before, publish the activation event.
if dev.ActivatedAt == nil {
- as.activationPool.Publish(ctx, ids)
+ if err := as.activationPool.Publish(ctx, info.ids); err != nil {
+ log.FromContext(ctx).WithError(err).Warn("Failed to publish activation event")
+ }
}
- if err == nil {
- as.deviceLastSeenPool.Publish(ctx, lastSeenAtInfo{ids: ids, lastSeenAt: uplink.ReceivedAt})
+ // Publish last seen event.
+ if err := as.deviceLastSeenPool.Publish(ctx, lastSeenAtInfo{
+ ids: info.ids,
+ lastSeenAt: info.uplink.ReceivedAt,
+ }); err != nil {
+ log.FromContext(ctx).WithError(err).Warn("Failed to publish last seen event")
}
return nil
}
-func (as *ApplicationServer) handleSimulatedUplink(ctx context.Context, ids *ttnpb.EndDeviceIdentifiers, uplink *ttnpb.ApplicationUplink, link *ttnpb.ApplicationLink) error {
+func (as *ApplicationServer) handleSimulatedUplink(ctx context.Context, info uplinkInfo) error {
defer trace.StartRegion(ctx, "handle simulated uplink").End()
- ctx = log.NewContextWithField(ctx, "session_key_id", uplink.SessionKeyId)
- dev, err := as.deviceRegistry.Get(ctx, ids,
+ ctx = log.NewContextWithField(ctx, "session_key_id", info.uplink.SessionKeyId)
+ dev, err := as.deviceRegistry.Get(ctx, info.ids,
[]string{
"formatters",
"version_ids",
@@ -1141,13 +1202,16 @@ func (as *ApplicationServer) handleSimulatedUplink(ctx context.Context, ids *ttn
return err
}
- if locations, err := as.locationRegistry.Get(ctx, ids); err != nil {
+ if locations, err := as.locationRegistry.Get(ctx, info.ids); err != nil {
log.FromContext(ctx).WithError(err).Warn("Failed to retrieve end device locations")
} else {
- uplink.Locations = locations
+ info.uplink.Locations = locations
}
- return as.decodeUplink(ctx, dev, uplink, link.DefaultFormatters)
+ if err := as.decodeUplink(ctx, dev, info.uplink, info.link.DefaultFormatters); err != nil {
+ return err
+ }
+ return as.publishNormalizedUplink(ctx, info)
}
func (as *ApplicationServer) handleDownlinkQueueInvalidated(ctx context.Context, ids *ttnpb.EndDeviceIdentifiers, invalid *ttnpb.ApplicationInvalidatedDownlinks, link *ttnpb.ApplicationLink) (pass bool, err error) {
@@ -1330,11 +1394,11 @@ func (as *ApplicationServer) GetConfig(ctx context.Context) (*Config, error) {
// GetMQTTConfig returns the MQTT frontend configuration based on the context.
func (as *ApplicationServer) GetMQTTConfig(ctx context.Context) (*config.MQTT, error) {
- config, err := as.GetConfig(ctx)
+ cfg, err := as.GetConfig(ctx)
if err != nil {
return nil, err
}
- return &config.MQTT, nil
+ return &cfg.MQTT, nil
}
// RangeUplinks ranges the application uplinks and calls the callback function, until false is returned.
diff --git a/pkg/applicationserver/applicationserver_test.go b/pkg/applicationserver/applicationserver_test.go
index d9fde14ba3..89bf475901 100644
--- a/pkg/applicationserver/applicationserver_test.go
+++ b/pkg/applicationserver/applicationserver_test.go
@@ -502,6 +502,9 @@ func TestApplicationServer(t *testing.T) {
UplinkMessage: &ttnpb.ApplicationPubSub_Message{
Topic: "up.uplink.message",
},
+ UplinkNormalized: &ttnpb.ApplicationPubSub_Message{
+ Topic: "up.uplink.normalized",
+ },
JoinAccept: &ttnpb.ApplicationPubSub_Message{
Topic: "up.join.accept",
},
@@ -535,17 +538,18 @@ func TestApplicationServer(t *testing.T) {
"downlink_ack",
"downlink_failed",
"downlink_nack",
- "downlink_queued",
- "downlink_queue_invalidated",
- "downlink_sent",
"downlink_push",
+ "downlink_queue_invalidated",
+ "downlink_queued",
"downlink_replace",
+ "downlink_sent",
"format",
- "provider",
- "service_data",
"join_accept",
"location_solved",
+ "provider",
+ "service_data",
"uplink_message",
+ "uplink_normalized",
),
}
if _, err := client.Set(ctx, req, creds); err != nil {
@@ -644,6 +648,9 @@ func TestApplicationServer(t *testing.T) {
UplinkMessage: &ttnpb.ApplicationPubSub_Message{
Topic: "up/uplink/message",
},
+ UplinkNormalized: &ttnpb.ApplicationPubSub_Message{
+ Topic: "up/uplink/normalized",
+ },
JoinAccept: &ttnpb.ApplicationPubSub_Message{
Topic: "up/join/accept",
},
@@ -677,17 +684,18 @@ func TestApplicationServer(t *testing.T) {
"downlink_ack",
"downlink_failed",
"downlink_nack",
- "downlink_queued",
- "downlink_queue_invalidated",
- "downlink_sent",
"downlink_push",
+ "downlink_queue_invalidated",
+ "downlink_queued",
"downlink_replace",
+ "downlink_sent",
"format",
- "provider",
- "service_data",
"join_accept",
"location_solved",
+ "provider",
+ "service_data",
"uplink_message",
+ "uplink_normalized",
),
}
if _, err := client.Set(ctx, req, creds); err != nil {
@@ -781,31 +789,33 @@ func TestApplicationServer(t *testing.T) {
client := ttnpb.NewApplicationWebhookRegistryClient(as.LoopbackConn())
req := &ttnpb.SetApplicationWebhookRequest{
Webhook: &ttnpb.ApplicationWebhook{
- Ids: registeredApplicationWebhookID,
- BaseUrl: webhookTarget.URL,
- Format: "json",
- UplinkMessage: &ttnpb.ApplicationWebhook_Message{Path: ""},
- JoinAccept: &ttnpb.ApplicationWebhook_Message{Path: ""},
- DownlinkAck: &ttnpb.ApplicationWebhook_Message{Path: ""},
- DownlinkNack: &ttnpb.ApplicationWebhook_Message{Path: ""},
- DownlinkQueued: &ttnpb.ApplicationWebhook_Message{Path: ""},
- DownlinkSent: &ttnpb.ApplicationWebhook_Message{Path: ""},
- DownlinkFailed: &ttnpb.ApplicationWebhook_Message{Path: ""},
- LocationSolved: &ttnpb.ApplicationWebhook_Message{Path: ""},
- ServiceData: &ttnpb.ApplicationWebhook_Message{Path: ""},
+ Ids: registeredApplicationWebhookID,
+ BaseUrl: webhookTarget.URL,
+ Format: "json",
+ UplinkMessage: &ttnpb.ApplicationWebhook_Message{Path: ""},
+ UplinkNormalized: &ttnpb.ApplicationWebhook_Message{Path: ""},
+ JoinAccept: &ttnpb.ApplicationWebhook_Message{Path: ""},
+ DownlinkAck: &ttnpb.ApplicationWebhook_Message{Path: ""},
+ DownlinkNack: &ttnpb.ApplicationWebhook_Message{Path: ""},
+ DownlinkQueued: &ttnpb.ApplicationWebhook_Message{Path: ""},
+ DownlinkSent: &ttnpb.ApplicationWebhook_Message{Path: ""},
+ DownlinkFailed: &ttnpb.ApplicationWebhook_Message{Path: ""},
+ LocationSolved: &ttnpb.ApplicationWebhook_Message{Path: ""},
+ ServiceData: &ttnpb.ApplicationWebhook_Message{Path: ""},
},
FieldMask: ttnpb.FieldMask(
"base_url",
- "format",
- "uplink_message",
- "service_data",
- "join_accept",
"downlink_ack",
+ "downlink_failed",
"downlink_nack",
"downlink_queued",
"downlink_sent",
- "downlink_failed",
+ "format",
+ "join_accept",
"location_solved",
+ "service_data",
+ "uplink_message",
+ "uplink_normalized",
),
}
if _, err := client.Set(ctx, req, creds); err != nil {
@@ -939,6 +949,7 @@ func TestApplicationServer(t *testing.T) {
Up: &ttnpb.ApplicationUp_JoinAccept{
JoinAccept: &ttnpb.ApplicationJoinAccept{
SessionKeyId: []byte{0x11},
+ ReceivedAt: ttnpb.ProtoTimePtr(now),
},
},
},
@@ -949,6 +960,7 @@ func TestApplicationServer(t *testing.T) {
Up: &ttnpb.ApplicationUp_JoinAccept{
JoinAccept: &ttnpb.ApplicationJoinAccept{
SessionKeyId: []byte{0x11},
+ ReceivedAt: up.GetJoinAccept().ReceivedAt,
},
},
CorrelationIds: up.CorrelationIds,
@@ -985,6 +997,7 @@ func TestApplicationServer(t *testing.T) {
EncryptedKey: []byte{0x39, 0x11, 0x40, 0x98, 0xa1, 0x5d, 0x6f, 0x92, 0xd7, 0xf0, 0x13, 0x21, 0x5b, 0x5b, 0x41, 0xa8, 0x98, 0x2d, 0xac, 0x59, 0x34, 0x76, 0x36, 0x18},
KekLabel: "test",
},
+ ReceivedAt: ttnpb.ProtoTimePtr(now),
},
},
},
@@ -995,6 +1008,7 @@ func TestApplicationServer(t *testing.T) {
Up: &ttnpb.ApplicationUp_JoinAccept{
JoinAccept: &ttnpb.ApplicationJoinAccept{
SessionKeyId: []byte{0x22},
+ ReceivedAt: up.GetJoinAccept().ReceivedAt,
},
},
CorrelationIds: up.CorrelationIds,
@@ -1106,6 +1120,7 @@ func TestApplicationServer(t *testing.T) {
FrmPayload: []byte{0xb, 0x8f, 0x94, 0xe6},
},
},
+ ReceivedAt: ttnpb.ProtoTimePtr(now),
},
},
},
@@ -1116,6 +1131,7 @@ func TestApplicationServer(t *testing.T) {
Up: &ttnpb.ApplicationUp_JoinAccept{
JoinAccept: &ttnpb.ApplicationJoinAccept{
SessionKeyId: []byte{0x33},
+ ReceivedAt: up.GetJoinAccept().ReceivedAt,
},
},
CorrelationIds: up.CorrelationIds,
@@ -1499,6 +1515,7 @@ func TestApplicationServer(t *testing.T) {
FrmPayload: []byte{0x2f, 0x3f, 0x31, 0x2c},
},
},
+ ReceivedAt: ttnpb.ProtoTimePtr(now),
},
},
},
@@ -1510,6 +1527,7 @@ func TestApplicationServer(t *testing.T) {
JoinAccept: &ttnpb.ApplicationJoinAccept{
SessionKeyId: []byte{0x44},
PendingSession: true,
+ ReceivedAt: up.GetJoinAccept().ReceivedAt,
},
},
CorrelationIds: up.CorrelationIds,
@@ -1884,6 +1902,69 @@ func TestApplicationServer(t *testing.T) {
a.So(queue, should.Resemble, []*ttnpb.ApplicationDownlink{})
},
},
+ {
+ Name: "RegisteredDevice/UplinkMessage/KnownSession/",
+ IDs: registeredDevice.Ids,
+ Message: &ttnpb.ApplicationUp{
+ EndDeviceIds: withDevAddr(registeredDevice.Ids, types.DevAddr{0x55, 0x55, 0x55, 0x55}),
+ Up: &ttnpb.ApplicationUp_UplinkMessage{
+ UplinkMessage: &ttnpb.ApplicationUplink{
+ RxMetadata: []*ttnpb.RxMetadata{{GatewayIds: &ttnpb.GatewayIdentifiers{GatewayId: "gtw"}}},
+ Settings: &ttnpb.TxSettings{DataRate: &ttnpb.DataRate{Modulation: &ttnpb.DataRate_Lora{Lora: &ttnpb.LoRaDataRate{}}}},
+ SessionKeyId: []byte{0x55},
+ FPort: 42,
+ FCnt: 42,
+ FrmPayload: []byte{0xd1, 0x43, 0x6a},
+ ReceivedAt: ttnpb.ProtoTimePtr(now),
+ },
+ },
+ },
+ AssertUp: func(t *testing.T, up *ttnpb.ApplicationUp) {
+ a := assertions.New(t)
+ a.So(up, should.Resemble, &ttnpb.ApplicationUp{
+ EndDeviceIds: withDevAddr(registeredDevice.Ids, types.DevAddr{0x55, 0x55, 0x55, 0x55}),
+ Up: &ttnpb.ApplicationUp_UplinkMessage{
+ UplinkMessage: &ttnpb.ApplicationUplink{
+ RxMetadata: []*ttnpb.RxMetadata{{GatewayIds: &ttnpb.GatewayIdentifiers{GatewayId: "gtw"}}},
+ Settings: &ttnpb.TxSettings{DataRate: &ttnpb.DataRate{Modulation: &ttnpb.DataRate_Lora{Lora: &ttnpb.LoRaDataRate{}}}},
+ SessionKeyId: []byte{0x55},
+ FPort: 42,
+ FCnt: 42,
+ FrmPayload: []byte{0x2a, 0x2a, 0x2a},
+ DecodedPayload: &pbtypes.Struct{
+ Fields: map[string]*pbtypes.Value{
+ "sum": {
+ Kind: &pbtypes.Value_NumberValue{
+ NumberValue: 126, // Payload formatter sums the bytes in FRMPayload.
+ },
+ },
+ },
+ },
+ VersionIds: registeredDevice.VersionIds,
+ ReceivedAt: up.GetUplinkMessage().ReceivedAt,
+ },
+ },
+ CorrelationIds: up.CorrelationIds,
+ ReceivedAt: up.ReceivedAt,
+ })
+ },
+ AssertDevice: func(t *testing.T, dev *ttnpb.EndDevice, queue []*ttnpb.ApplicationDownlink) {
+ a := assertions.New(t)
+ a.So(dev.Session, should.Resemble, &ttnpb.Session{
+ DevAddr: types.DevAddr{0x55, 0x55, 0x55, 0x55}.Bytes(),
+ Keys: &ttnpb.SessionKeys{
+ SessionKeyId: []byte{0x55},
+ AppSKey: &ttnpb.KeyEnvelope{
+ EncryptedKey: []byte{0x56, 0x15, 0xaa, 0x22, 0xb7, 0x5f, 0xc, 0x24, 0x79, 0x6, 0x84, 0x68, 0x89, 0x0, 0xa6, 0x16, 0x4a, 0x9c, 0xef, 0xdb, 0xbf, 0x61, 0x6f, 0x0},
+ KekLabel: "test",
+ },
+ },
+ LastAFCntDown: 0,
+ })
+ a.So(dev.PendingSession, should.BeNil)
+ a.So(queue, should.Resemble, []*ttnpb.ApplicationDownlink{})
+ },
+ },
{
Name: "UnregisteredDevice/JoinAccept",
IDs: unregisteredDeviceID,
@@ -1898,6 +1979,7 @@ func TestApplicationServer(t *testing.T) {
EncryptedKey: []byte{0x56, 0x15, 0xaa, 0x22, 0xb7, 0x5f, 0xc, 0x24, 0x79, 0x6, 0x84, 0x68, 0x89, 0x0, 0xa6, 0x16, 0x4a, 0x9c, 0xef, 0xdb, 0xbf, 0x61, 0x6f, 0x0},
KekLabel: "test",
},
+ ReceivedAt: ttnpb.ProtoTimePtr(now),
},
},
},
@@ -2430,6 +2512,7 @@ func TestSkipPayloadCrypto(t *testing.T) {
EncryptedKey: []byte{0x39, 0x11, 0x40, 0x98, 0xa1, 0x5d, 0x6f, 0x92, 0xd7, 0xf0, 0x13, 0x21, 0x5b, 0x5b, 0x41, 0xa8, 0x98, 0x2d, 0xac, 0x59, 0x34, 0x76, 0x36, 0x18},
KekLabel: kekLabel,
},
+ ReceivedAt: ttnpb.ProtoTimePtr(now),
},
},
},
@@ -2447,6 +2530,7 @@ func TestSkipPayloadCrypto(t *testing.T) {
EncryptedKey: []byte{0x39, 0x11, 0x40, 0x98, 0xa1, 0x5d, 0x6f, 0x92, 0xd7, 0xf0, 0x13, 0x21, 0x5b, 0x5b, 0x41, 0xa8, 0x98, 0x2d, 0xac, 0x59, 0x34, 0x76, 0x36, 0x18},
KekLabel: kekLabel,
},
+ ReceivedAt: up.GetJoinAccept().ReceivedAt,
},
},
CorrelationIds: up.CorrelationIds,
@@ -2458,6 +2542,7 @@ func TestSkipPayloadCrypto(t *testing.T) {
Up: &ttnpb.ApplicationUp_JoinAccept{
JoinAccept: &ttnpb.ApplicationJoinAccept{
SessionKeyId: []byte{0x22},
+ ReceivedAt: up.GetJoinAccept().ReceivedAt,
},
},
CorrelationIds: up.CorrelationIds,
@@ -2894,6 +2979,188 @@ func TestLocationFromPayload(t *testing.T) {
}
}
+func TestUplinkNormalized(t *testing.T) {
+ a, ctx := test.New(t)
+
+ registeredApplicationID := ttnpb.ApplicationIdentifiers{ApplicationId: "foo-app"}
+
+ // This device gets registered in the device registry of the Application Server.
+ registeredDevice := &ttnpb.EndDevice{
+ Ids: &ttnpb.EndDeviceIdentifiers{
+ ApplicationIds: ®isteredApplicationID,
+ DeviceId: "foo-device",
+ JoinEui: types.EUI64{0x42, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}.Bytes(),
+ DevEui: types.EUI64{0x42, 0x42, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}.Bytes(),
+ },
+ Session: &ttnpb.Session{
+ DevAddr: types.DevAddr{0x11, 0x11, 0x11, 0x11}.Bytes(),
+ Keys: &ttnpb.SessionKeys{
+ SessionKeyId: []byte{0x11},
+ AppSKey: &ttnpb.KeyEnvelope{
+ Key: types.AES128Key{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}.Bytes(), //nolint:lll
+ },
+ },
+ },
+ Formatters: &ttnpb.MessagePayloadFormatters{
+ UpFormatter: ttnpb.PayloadFormatter_FORMATTER_JAVASCRIPT,
+ UpFormatterParameter: `function decodeUplink(input) {
+ return {
+ data: {
+ air: {
+ temperature: 21.5,
+ }
+ }
+ };
+ }`,
+ },
+ }
+
+ is, isAddr, closeIS := mockis.New(ctx)
+ defer closeIS()
+ is.EndDeviceRegistry().Add(ctx, registeredDevice)
+
+ devsRedisClient, devsFlush := test.NewRedis(ctx, "applicationserver_test", "devices")
+ defer devsFlush()
+ defer devsRedisClient.Close()
+ deviceRegistry := &redis.DeviceRegistry{Redis: devsRedisClient, LockTTL: test.Delay << 10}
+ if err := deviceRegistry.Init(ctx); !a.So(err, should.BeNil) {
+ t.FailNow()
+ }
+ _, err := deviceRegistry.Set(ctx, registeredDevice.Ids, nil, func(ed *ttnpb.EndDevice) (*ttnpb.EndDevice, []string, error) {
+ return registeredDevice, []string{"ids", "session", "formatters"}, nil
+ })
+ if err != nil {
+ t.Fatalf("Failed to set device in registry: %s", err)
+ }
+
+ linksRedisClient, linksFlush := test.NewRedis(ctx, "applicationserver_test", "links")
+ defer linksFlush()
+ defer linksRedisClient.Close()
+ linkRegistry := &redis.LinkRegistry{Redis: linksRedisClient, LockTTL: test.Delay << 10}
+ if err := linkRegistry.Init(ctx); !a.So(err, should.BeNil) {
+ t.FailNow()
+ }
+ _, err = linkRegistry.Set(ctx, ®isteredApplicationID, nil, func(_ *ttnpb.ApplicationLink) (*ttnpb.ApplicationLink, []string, error) {
+ return &ttnpb.ApplicationLink{}, nil, nil
+ })
+ if err != nil {
+ t.Fatalf("Failed to set link in registry: %s", err)
+ }
+
+ distribRedisClient, distribFlush := test.NewRedis(ctx, "applicationserver_test", "traffic")
+ defer distribFlush()
+ defer distribRedisClient.Close()
+ distribPubSub := distribredis.PubSub{Redis: distribRedisClient}
+
+ applicationUpsRedisClient, applicationUpsFlush := test.NewRedis(ctx, "applicationserver_test", "applicationups")
+ defer applicationUpsFlush()
+ defer applicationUpsRedisClient.Close()
+ applicationUpsRegistry := &redis.ApplicationUplinkRegistry{
+ Redis: applicationUpsRedisClient,
+ Limit: 16,
+ }
+
+ c := componenttest.NewComponent(t, &component.Config{
+ ServiceBase: config.ServiceBase{
+ GRPC: config.GRPC{
+ Listen: ":9189",
+ AllowInsecureForCredentials: true,
+ },
+ Cluster: cluster.Config{
+ IdentityServer: isAddr,
+ },
+ HTTP: config.HTTP{
+ Listen: ":8100",
+ },
+ },
+ })
+ config := &applicationserver.Config{
+ Devices: deviceRegistry,
+ Links: linkRegistry,
+ UplinkStorage: applicationserver.UplinkStorageConfig{
+ Registry: applicationUpsRegistry,
+ Limit: 16,
+ },
+ Distribution: applicationserver.DistributionConfig{
+ Global: applicationserver.GlobalDistributorConfig{
+ PubSub: distribPubSub,
+ },
+ },
+ EndDeviceMetadataStorage: applicationserver.EndDeviceMetadataStorageConfig{
+ Location: applicationserver.EndDeviceLocationStorageConfig{
+ Registry: metadata.NewClusterEndDeviceLocationRegistry(c, (1<<4)*Timeout),
+ },
+ },
+ }
+ as, err := applicationserver.New(c, config)
+ if !a.So(err, should.BeNil) {
+ t.FailNow()
+ }
+
+ roles := as.Roles()
+ a.So(len(roles), should.Equal, 1)
+ a.So(roles[0], should.Equal, ttnpb.ClusterRole_APPLICATION_SERVER)
+
+ componenttest.StartComponent(t, c)
+ defer c.Close()
+
+ mustHavePeer(ctx, c, ttnpb.ClusterRole_ENTITY_REGISTRY)
+
+ sub, err := as.Subscribe(ctx, "test", nil, false)
+ a.So(err, should.BeNil)
+
+ now := time.Now().UTC()
+ err = as.Publish(ctx, &ttnpb.ApplicationUp{
+ EndDeviceIds: registeredDevice.Ids,
+ Up: &ttnpb.ApplicationUp_UplinkMessage{
+ UplinkMessage: &ttnpb.ApplicationUplink{
+ RxMetadata: []*ttnpb.RxMetadata{{GatewayIds: &ttnpb.GatewayIdentifiers{GatewayId: "gtw"}}},
+ Settings: &ttnpb.TxSettings{DataRate: &ttnpb.DataRate{Modulation: &ttnpb.DataRate_Lora{Lora: &ttnpb.LoRaDataRate{}}}},
+ SessionKeyId: []byte{0x11},
+ FPort: 11,
+ FCnt: 11,
+ FrmPayload: []byte{0x11},
+ ReceivedAt: ttnpb.ProtoTimePtr(now),
+ },
+ },
+ })
+ a.So(err, should.BeNil)
+
+ // The uplink message and the normalized payload message may come out of order.
+ // Expect exactly two messages.
+ var normalized *ttnpb.ApplicationUplinkNormalized
+ for i := 0; i < 2; i++ {
+ select {
+ case msg := <-sub.Up():
+ if n := msg.GetUplinkNormalized(); n != nil {
+ normalized = n
+ }
+ case <-time.After(Timeout):
+ t.Fatalf("Expected upstream message %d timed out", i)
+ }
+ }
+ if normalized == nil {
+ t.Fatalf("Expected uplink normalized message")
+ }
+ a.So(normalized.NormalizedPayload, should.Resemble, &pbtypes.Struct{
+ Fields: map[string]*pbtypes.Value{
+ "air": {
+ Kind: &pbtypes.Value_StructValue{
+ StructValue: &pbtypes.Struct{
+ Fields: map[string]*pbtypes.Value{
+ "temperature": {
+ Kind: &pbtypes.Value_NumberValue{
+ NumberValue: 21.5,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ })
+}
+
func TestApplicationServerCleanup(t *testing.T) {
a, ctx := test.New(t)
diff --git a/pkg/applicationserver/grpc.go b/pkg/applicationserver/grpc.go
index 3d340fba38..3fb51acf7e 100644
--- a/pkg/applicationserver/grpc.go
+++ b/pkg/applicationserver/grpc.go
@@ -17,6 +17,7 @@ package applicationserver
import (
"context"
"fmt"
+ "time"
pbtypes "github.com/gogo/protobuf/types"
clusterauth "go.thethings.network/lorawan-stack/v3/pkg/auth/cluster"
@@ -125,6 +126,7 @@ func (as *ApplicationServer) GetConfiguration(ctx context.Context, _ *ttnpb.GetA
// HandleUplink implements ttnpb.NsAsServer.
func (as *ApplicationServer) HandleUplink(ctx context.Context, req *ttnpb.NsAsHandleUplinkRequest) (*pbtypes.Empty, error) {
+ now := time.Now()
if err := clusterauth.Authorized(ctx); err != nil {
return nil, err
}
@@ -136,6 +138,7 @@ func (as *ApplicationServer) HandleUplink(ctx context.Context, req *ttnpb.NsAsHa
return nil, err
}
for _, up := range req.ApplicationUps {
+ up.ReceivedAt = ttnpb.ProtoTimePtr(now)
if err := as.processUp(ctx, up, link); err != nil {
return nil, err
}
diff --git a/pkg/applicationserver/io/grpc/grpc_test.go b/pkg/applicationserver/io/grpc/grpc_test.go
index eb0f9e6bd1..8ec7a5ce64 100644
--- a/pkg/applicationserver/io/grpc/grpc_test.go
+++ b/pkg/applicationserver/io/grpc/grpc_test.go
@@ -53,6 +53,7 @@ var (
)
func TestAuthentication(t *testing.T) {
+ t.Parallel()
ctx := log.NewContext(test.Context(), test.GetLogger(t))
is, isAddr, closeIS := mockis.New(ctx)
@@ -80,6 +81,7 @@ func TestAuthentication(t *testing.T) {
client := ttnpb.NewAppAsClient(c.LoopbackConn())
+ //nolint:paralleltest
for _, tc := range []struct {
ID *ttnpb.ApplicationIdentifiers
Key string
@@ -138,6 +140,7 @@ type erroredApplicationUp struct {
}
func TestTraffic(t *testing.T) {
+ t.Parallel()
ctx := log.NewContext(test.Context(), test.GetLogger(t))
ctx, cancel := context.WithCancel(ctx)
defer cancel()
@@ -197,6 +200,7 @@ func TestTraffic(t *testing.T) {
t.Fatal("Subscription timeout")
}
+ //nolint:paralleltest
t.Run("Upstream", func(t *testing.T) {
a := assertions.New(t)
@@ -224,6 +228,7 @@ func TestTraffic(t *testing.T) {
}
})
+ //nolint:paralleltest
t.Run("Downstream", func(t *testing.T) {
a := assertions.New(t)
ids := ttnpb.EndDeviceIdentifiers{
@@ -365,6 +370,7 @@ func (p mockMQTTConfigProvider) GetMQTTConfig(context.Context) (*config.MQTT, er
}
func TestMQTTConfig(t *testing.T) {
+ t.Parallel()
a := assertions.New(t)
ctx := log.NewContext(test.Context(), test.GetLogger(t))
@@ -416,6 +422,7 @@ func TestMQTTConfig(t *testing.T) {
}
func TestSimulateUplink(t *testing.T) {
+ t.Parallel()
a := assertions.New(t)
ctx := log.NewContext(test.Context(), test.GetLogger(t))
@@ -471,6 +478,7 @@ func TestSimulateUplink(t *testing.T) {
}()
<-as.Subscriptions()
+ //nolint:paralleltest
for _, tc := range []struct {
name string
up *ttnpb.ApplicationUp
@@ -537,6 +545,7 @@ func TestSimulateUplink(t *testing.T) {
}
func TestMessageProcessors(t *testing.T) {
+ t.Parallel()
a := assertions.New(t)
ctx := log.NewContext(test.Context(), test.GetLogger(t))
@@ -611,8 +620,12 @@ func TestMessageProcessors(t *testing.T) {
Uplink: &ttnpb.ApplicationUplink{
FrmPayload: []byte{1, 0, 255},
RxMetadata: []*ttnpb.RxMetadata{{GatewayIds: &ttnpb.GatewayIdentifiers{GatewayId: "gtw"}}},
- Settings: &ttnpb.TxSettings{DataRate: &ttnpb.DataRate{Modulation: &ttnpb.DataRate_Lora{Lora: &ttnpb.LoRaDataRate{}}}},
- FPort: 1,
+ Settings: &ttnpb.TxSettings{
+ DataRate: &ttnpb.DataRate{
+ Modulation: &ttnpb.DataRate_Lora{Lora: &ttnpb.LoRaDataRate{}},
+ },
+ },
+ FPort: 1,
},
Formatter: ttnpb.PayloadFormatter_FORMATTER_CAYENNELPP,
}, creds)
diff --git a/pkg/applicationserver/io/mqtt/format.go b/pkg/applicationserver/io/mqtt/format.go
index 19dfbabea0..37c644eb5d 100644
--- a/pkg/applicationserver/io/mqtt/format.go
+++ b/pkg/applicationserver/io/mqtt/format.go
@@ -33,7 +33,9 @@ func TopicParts(up *io.ContextualApplicationUp, layout topics.Layout) []string {
var f func(string, string) []string
switch up.Up.(type) {
case *ttnpb.ApplicationUp_UplinkMessage:
- f = layout.UplinkTopic
+ f = layout.UplinkMessageTopic
+ case *ttnpb.ApplicationUp_UplinkNormalized:
+ f = layout.UplinkNormalizedTopic
case *ttnpb.ApplicationUp_JoinAccept:
f = layout.JoinAcceptTopic
case *ttnpb.ApplicationUp_DownlinkAck:
diff --git a/pkg/applicationserver/io/mqtt/mqtt.go b/pkg/applicationserver/io/mqtt/mqtt.go
index 5bf597e69d..4482171125 100644
--- a/pkg/applicationserver/io/mqtt/mqtt.go
+++ b/pkg/applicationserver/io/mqtt/mqtt.go
@@ -99,9 +99,10 @@ func setupConnection(ctx context.Context, mqttConn mqttnet.Conn, format Format,
logger.WithError(err).Warn("Failed to marshal upstream message")
continue
}
- logger.Debug("Publish upstream message")
+ topicName := topic.Join(topicParts)
+ logger.WithField("topic", topicName).Debug("Publish upstream message")
session.Publish(&packet.PublishPacket{
- TopicName: topic.Join(topicParts),
+ TopicName: topicName,
TopicParts: topicParts,
QoS: qosUpstream,
Message: buf,
@@ -183,7 +184,8 @@ func (c *connection) Connect(ctx context.Context, info *auth.Info) (_ context.Co
}
if err := rights.RequireApplication(ctx, ids, ttnpb.Right_RIGHT_APPLICATION_TRAFFIC_READ); err == nil {
access.reads = append(access.reads,
- c.format.UplinkTopic(uid, topic.PartWildcard),
+ c.format.UplinkMessageTopic(uid, topic.PartWildcard),
+ c.format.UplinkNormalizedTopic(uid, topic.PartWildcard),
c.format.JoinAcceptTopic(uid, topic.PartWildcard),
c.format.DownlinkAckTopic(uid, topic.PartWildcard),
c.format.DownlinkNackTopic(uid, topic.PartWildcard),
diff --git a/pkg/applicationserver/io/mqtt/mqtt_test.go b/pkg/applicationserver/io/mqtt/mqtt_test.go
index acd2c26982..424641a8c1 100644
--- a/pkg/applicationserver/io/mqtt/mqtt_test.go
+++ b/pkg/applicationserver/io/mqtt/mqtt_test.go
@@ -22,6 +22,7 @@ import (
"time"
mqtt "github.com/eclipse/paho.mqtt.golang"
+ pbtypes "github.com/gogo/protobuf/types"
"github.com/smartystreets/assertions"
"go.thethings.network/lorawan-stack/v3/pkg/applicationserver/io"
"go.thethings.network/lorawan-stack/v3/pkg/applicationserver/io/mock"
@@ -210,6 +211,38 @@ func TestTraffic(t *testing.T) {
},
OK: true,
},
+ {
+ Topic: fmt.Sprintf("v3/%v/devices/%v/up/normalized", unique.ID(ctx, registeredDeviceID.ApplicationIds), registeredDeviceID.DeviceId),
+ Message: &ttnpb.ApplicationUp{
+ EndDeviceIds: registeredDeviceID,
+ Up: &ttnpb.ApplicationUp_UplinkNormalized{
+ UplinkNormalized: &ttnpb.ApplicationUplinkNormalized{
+ SessionKeyId: []byte{0x11},
+ FPort: 42,
+ FCnt: 42,
+ FrmPayload: []byte{0x1, 0x2, 0x3},
+ NormalizedPayload: &pbtypes.Struct{
+ Fields: map[string]*pbtypes.Value{
+ "air": {
+ Kind: &pbtypes.Value_StructValue{
+ StructValue: &pbtypes.Struct{
+ Fields: map[string]*pbtypes.Value{
+ "temperature": {
+ Kind: &pbtypes.Value_NumberValue{
+ NumberValue: 21.5,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ OK: true,
+ },
{
Topic: fmt.Sprintf("v3/%v/devices/%v/join", unique.ID(ctx, registeredDeviceID.ApplicationIds), registeredDeviceID.DeviceId),
Message: &ttnpb.ApplicationUp{
diff --git a/pkg/applicationserver/io/mqtt/topics/layout.go b/pkg/applicationserver/io/mqtt/topics/layout.go
index 49ddd0569a..915f29886a 100644
--- a/pkg/applicationserver/io/mqtt/topics/layout.go
+++ b/pkg/applicationserver/io/mqtt/topics/layout.go
@@ -12,13 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+// Package topics implements MQTT topic layouts.
package topics
// Layout represents an MQTT topic layout.
type Layout interface {
AcceptedTopic(applicationUID string, requested []string) (accepted []string, ok bool)
- UplinkTopic(applicationUID, deviceID string) []string
+ UplinkMessageTopic(applicationUID, deviceID string) []string
+ UplinkNormalizedTopic(applicationUID, deviceID string) []string
JoinAcceptTopic(applicationUID, deviceID string) []string
DownlinkAckTopic(applicationUID, deviceID string) []string
DownlinkNackTopic(applicationUID, deviceID string) []string
diff --git a/pkg/applicationserver/io/mqtt/topics/v3.go b/pkg/applicationserver/io/mqtt/topics/v3.go
index c06e338073..d68c99440f 100644
--- a/pkg/applicationserver/io/mqtt/topics/v3.go
+++ b/pkg/applicationserver/io/mqtt/topics/v3.go
@@ -44,10 +44,14 @@ func (v3) AcceptedTopic(applicationUID string, requested []string) ([]string, bo
return nil, false
}
-func (v3) UplinkTopic(applicationUID, deviceID string) []string {
+func (v3) UplinkMessageTopic(applicationUID, deviceID string) []string {
return []string{topicV3, applicationUID, "devices", deviceID, "up"}
}
+func (v3) UplinkNormalizedTopic(applicationUID, deviceID string) []string {
+ return []string{topicV3, applicationUID, "devices", deviceID, "up", "normalized"}
+}
+
func (v3) JoinAcceptTopic(applicationUID, deviceID string) []string {
return []string{topicV3, applicationUID, "devices", deviceID, "join"}
}
diff --git a/pkg/applicationserver/io/mqtt/topics/v3_test.go b/pkg/applicationserver/io/mqtt/topics/v3_test.go
index 5e53d96c23..bdf336eada 100644
--- a/pkg/applicationserver/io/mqtt/topics/v3_test.go
+++ b/pkg/applicationserver/io/mqtt/topics/v3_test.go
@@ -30,6 +30,7 @@ import (
)
func TestV3AcceptedTopic(t *testing.T) {
+ t.Parallel()
uid := unique.ID(test.Context(), &ttnpb.ApplicationIdentifiers{ApplicationId: "foo-app"})
for i, tc := range []struct {
Requested,
@@ -63,7 +64,9 @@ func TestV3AcceptedTopic(t *testing.T) {
OK: true,
},
} {
+ tc := tc
t.Run(strconv.Itoa(i), func(t *testing.T) {
+ t.Parallel()
a := assertions.New(t)
actual, ok := topics.Default.AcceptedTopic(uid, topic.Split(tc.Requested))
if !a.So(ok, should.Equal, tc.OK) {
@@ -75,6 +78,7 @@ func TestV3AcceptedTopic(t *testing.T) {
}
func TestV3Topics(t *testing.T) {
+ t.Parallel()
appUID := unique.ID(test.Context(), &ttnpb.ApplicationIdentifiers{ApplicationId: "foo-app"})
devID := "foo-device"
@@ -83,9 +87,13 @@ func TestV3Topics(t *testing.T) {
Expected string
}{
{
- Fn: topics.Default.UplinkTopic,
+ Fn: topics.Default.UplinkMessageTopic,
Expected: fmt.Sprintf("v3/%s/devices/%s/up", appUID, devID),
},
+ {
+ Fn: topics.Default.UplinkNormalizedTopic,
+ Expected: fmt.Sprintf("v3/%s/devices/%s/up/normalized", appUID, devID),
+ },
{
Fn: topics.Default.JoinAcceptTopic,
Expected: fmt.Sprintf("v3/%s/devices/%s/join", appUID, devID),
@@ -131,7 +139,9 @@ func TestV3Topics(t *testing.T) {
Expected: fmt.Sprintf("v3/%s/devices/%s/down/replace", appUID, devID),
},
} {
+ tc := tc
t.Run(tc.Expected, func(t *testing.T) {
+ t.Parallel()
actual := strings.Join(tc.Fn(appUID, devID), "/")
assertions.New(t).So(actual, should.Equal, tc.Expected)
})
diff --git a/pkg/applicationserver/io/pubsub/provider/connection.go b/pkg/applicationserver/io/pubsub/provider/connection.go
index 7f684e51c7..d32503b7e9 100644
--- a/pkg/applicationserver/io/pubsub/provider/connection.go
+++ b/pkg/applicationserver/io/pubsub/provider/connection.go
@@ -42,6 +42,7 @@ func (ds *DownlinkSubscriptions) Shutdown(ctx context.Context) error {
// UplinkTopics contains the topics for the uplink messages.
type UplinkTopics struct {
UplinkMessage *pubsub.Topic
+ UplinkNormalized *pubsub.Topic
JoinAccept *pubsub.Topic
DownlinkAck *pubsub.Topic
DownlinkNack *pubsub.Topic
@@ -57,6 +58,7 @@ type UplinkTopics struct {
func (ut *UplinkTopics) Shutdown(ctx context.Context) error {
return shutdown(ctx,
ut.UplinkMessage,
+ ut.UplinkNormalized,
ut.JoinAccept,
ut.DownlinkAck,
ut.DownlinkNack,
@@ -75,7 +77,7 @@ type Shutdowner interface {
}
// ProviderConnection is an interface that represents a provider specific connection.
-type ProviderConnection interface {
+type ProviderConnection interface { //nolint:revive
Shutdowner
}
diff --git a/pkg/applicationserver/io/pubsub/provider/mock/provider.go b/pkg/applicationserver/io/pubsub/provider/mock/provider.go
index 73b6fa5f88..a10aefa06f 100644
--- a/pkg/applicationserver/io/pubsub/provider/mock/provider.go
+++ b/pkg/applicationserver/io/pubsub/provider/mock/provider.go
@@ -48,6 +48,7 @@ type Connection struct {
Replace *pubsub.Topic
UplinkMessage *pubsub.Subscription
+ UplinkNormalized *pubsub.Subscription
JoinAccept *pubsub.Subscription
DownlinkAck *pubsub.Subscription
DownlinkNack *pubsub.Subscription
@@ -80,6 +81,7 @@ func (c *Connection) Shutdown(ctx context.Context) (err error) {
c.Replace,
c.UplinkMessage,
+ c.UplinkNormalized,
c.JoinAccept,
c.DownlinkAck,
c.DownlinkNack,
@@ -132,6 +134,10 @@ func (i *Impl) OpenConnection(ctx context.Context, target provider.Target, enabl
topic: &pc.Topics.UplinkMessage,
subscription: &conn.UplinkMessage,
},
+ {
+ topic: &pc.Topics.UplinkNormalized,
+ subscription: &conn.UplinkNormalized,
+ },
{
topic: &pc.Topics.JoinAccept,
subscription: &conn.JoinAccept,
diff --git a/pkg/applicationserver/io/pubsub/provider/nats/provider.go b/pkg/applicationserver/io/pubsub/provider/nats/provider.go
index 133297afc3..536d164519 100644
--- a/pkg/applicationserver/io/pubsub/provider/nats/provider.go
+++ b/pkg/applicationserver/io/pubsub/provider/nats/provider.go
@@ -64,6 +64,10 @@ func (impl) OpenConnection(ctx context.Context, target provider.Target, enabler
topic: &pc.Topics.UplinkMessage,
message: target.GetUplinkMessage(),
},
+ {
+ topic: &pc.Topics.UplinkNormalized,
+ message: target.GetUplinkNormalized(),
+ },
{
topic: &pc.Topics.JoinAccept,
message: target.GetJoinAccept(),
diff --git a/pkg/applicationserver/io/pubsub/provider/nats/provider_test.go b/pkg/applicationserver/io/pubsub/provider/nats/provider_test.go
index 6b991e3ec0..63d6d75432 100644
--- a/pkg/applicationserver/io/pubsub/provider/nats/provider_test.go
+++ b/pkg/applicationserver/io/pubsub/provider/nats/provider_test.go
@@ -77,6 +77,9 @@ func TestOpenConnection(t *testing.T) {
UplinkMessage: &ttnpb.ApplicationPubSub_Message{
Topic: "uplink.message",
},
+ UplinkNormalized: &ttnpb.ApplicationPubSub_Message{
+ Topic: "uplink.normalized",
+ },
JoinAccept: &ttnpb.ApplicationPubSub_Message{
Topic: "join.accept",
},
@@ -204,6 +207,11 @@ func TestOpenConnection(t *testing.T) {
subject: "app1.ps1.uplink.message",
topic: conn.Topics.UplinkMessage,
},
+ {
+ name: "ValidNormalizedUplink",
+ subject: "app1.ps1.uplink.normalized",
+ topic: conn.Topics.UplinkNormalized,
+ },
{
name: "ValidJoinAccept",
subject: "app1.ps1.join.accept",
diff --git a/pkg/applicationserver/io/pubsub/provider/provider.go b/pkg/applicationserver/io/pubsub/provider/provider.go
index 64a908f4f9..41a8d82af4 100644
--- a/pkg/applicationserver/io/pubsub/provider/provider.go
+++ b/pkg/applicationserver/io/pubsub/provider/provider.go
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+// Package provider implements pub/sub provider interfaces and registration.
package provider
import (
@@ -27,6 +28,7 @@ import (
type Topics interface {
GetBaseTopic() string
GetUplinkMessage() *ttnpb.ApplicationPubSub_Message
+ GetUplinkNormalized() *ttnpb.ApplicationPubSub_Message
GetJoinAccept() *ttnpb.ApplicationPubSub_Message
GetDownlinkAck() *ttnpb.ApplicationPubSub_Message
GetDownlinkNack() *ttnpb.ApplicationPubSub_Message
@@ -57,8 +59,14 @@ type Provider interface {
}
var (
- errNotImplemented = errors.DefineUnimplemented("provider_not_implemented", "provider `{provider_id}` is not implemented")
- errAlreadyRegistered = errors.DefineAlreadyExists("provider_already_registered", "provider `{provider_id}` already registered")
+ errNotImplemented = errors.DefineUnimplemented(
+ "provider_not_implemented",
+ "provider `{provider_id}` is not implemented",
+ )
+ errAlreadyRegistered = errors.DefineAlreadyExists(
+ "provider_already_registered",
+ "provider `{provider_id}` already registered",
+ )
providers = map[reflect.Type]Provider{}
)
diff --git a/pkg/applicationserver/io/pubsub/pubsub.go b/pkg/applicationserver/io/pubsub/pubsub.go
index a6db1f4471..751c0807a3 100644
--- a/pkg/applicationserver/io/pubsub/pubsub.go
+++ b/pkg/applicationserver/io/pubsub/pubsub.go
@@ -47,7 +47,12 @@ type PubSub struct {
}
// New creates a new pusub frontend.
-func New(c *component.Component, server io.Server, registry Registry, providerStatuses ProviderStatuses) (*PubSub, error) {
+func New(
+ c *component.Component,
+ server io.Server,
+ registry Registry,
+ providerStatuses ProviderStatuses,
+) (*PubSub, error) {
ctx := log.NewContextWithField(c.Context(), "namespace", "applicationserver/io/pubsub")
ps := &PubSub{
Component: c,
@@ -130,6 +135,8 @@ func (i *integration) handleUp(ctx context.Context) {
switch up.ApplicationUp.Up.(type) {
case *ttnpb.ApplicationUp_UplinkMessage:
topic = i.conn.Topics.UplinkMessage
+ case *ttnpb.ApplicationUp_UplinkNormalized:
+ topic = i.conn.Topics.UplinkNormalized
case *ttnpb.ApplicationUp_JoinAccept:
topic = i.conn.Topics.JoinAccept
case *ttnpb.ApplicationUp_DownlinkAck:
@@ -170,7 +177,11 @@ func (i *integration) handleUp(ctx context.Context) {
}
}
-func (i *integration) handleDown(ctx context.Context, op func(io.Server, context.Context, *ttnpb.EndDeviceIdentifiers, []*ttnpb.ApplicationDownlink) error, subscription *pubsub.Subscription) {
+func (i *integration) handleDown(
+ ctx context.Context,
+ op func(io.Server, context.Context, *ttnpb.EndDeviceIdentifiers, []*ttnpb.ApplicationDownlink) error,
+ subscription *pubsub.Subscription,
+) {
logger := log.FromContext(ctx)
for {
select {
@@ -268,14 +279,14 @@ func (ps *PubSub) start(ctx context.Context, pb *ttnpb.ApplicationPubSub) (err e
}
}()
- provider, err := provider.GetProvider(pb)
+ p, err := provider.GetProvider(pb)
if err != nil {
return err
}
if err := ps.providerStatuses.Enabled(ctx, pb.GetProvider()); err != nil {
return err
}
- i.conn, err = provider.OpenConnection(ctx, pb, ps.providerStatuses)
+ i.conn, err = p.OpenConnection(ctx, pb, ps.providerStatuses)
if err != nil {
return err
}
diff --git a/pkg/applicationserver/io/pubsub/pubsub_test.go b/pkg/applicationserver/io/pubsub/pubsub_test.go
index 9ca729f1a0..70f612bb6c 100644
--- a/pkg/applicationserver/io/pubsub/pubsub_test.go
+++ b/pkg/applicationserver/io/pubsub/pubsub_test.go
@@ -19,7 +19,7 @@ import (
"testing"
"time"
- "github.com/gogo/protobuf/types"
+ pbtypes "github.com/gogo/protobuf/types"
"go.thethings.network/lorawan-stack/v3/pkg/applicationserver/io/formatters"
mock_server "go.thethings.network/lorawan-stack/v3/pkg/applicationserver/io/mock"
"go.thethings.network/lorawan-stack/v3/pkg/applicationserver/io/pubsub"
@@ -41,6 +41,7 @@ type messageWithError struct {
}
func TestPubSub(t *testing.T) {
+ t.Parallel()
a, ctx := test.New(t)
redisClient, flush := test.NewRedis(ctx, "pubsub_test")
@@ -79,6 +80,9 @@ func TestPubSub(t *testing.T) {
UplinkMessage: &ttnpb.ApplicationPubSub_Message{
Topic: "uplink.message",
},
+ UplinkNormalized: &ttnpb.ApplicationPubSub_Message{
+ Topic: "uplink.normalized",
+ },
JoinAccept: &ttnpb.ApplicationPubSub_Message{
Topic: "join.accept",
},
@@ -122,6 +126,7 @@ func TestPubSub(t *testing.T) {
"provider",
"join_accept",
"location_solved",
+ "uplink_normalized",
"uplink_message",
"service_data",
}
@@ -158,6 +163,7 @@ func TestPubSub(t *testing.T) {
sub := <-io.Subscriptions()
conn := <-mockImpl.OpenConnectionCh
+ //nolint:paralleltest
t.Run("Upstream", func(t *testing.T) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
@@ -182,6 +188,38 @@ func TestPubSub(t *testing.T) {
},
Subscription: conn.UplinkMessage,
},
+ {
+ Name: "UplinkNormalized",
+ Message: &ttnpb.ApplicationUp{
+ EndDeviceIds: registeredDeviceID,
+ Up: &ttnpb.ApplicationUp_UplinkNormalized{
+ UplinkNormalized: &ttnpb.ApplicationUplinkNormalized{
+ SessionKeyId: []byte{0x11},
+ FPort: 42,
+ FCnt: 42,
+ FrmPayload: []byte{0x1, 0x2, 0x3},
+ NormalizedPayload: &pbtypes.Struct{
+ Fields: map[string]*pbtypes.Value{
+ "air": {
+ Kind: &pbtypes.Value_StructValue{
+ StructValue: &pbtypes.Struct{
+ Fields: map[string]*pbtypes.Value{
+ "temperature": {
+ Kind: &pbtypes.Value_NumberValue{
+ NumberValue: 21.5,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ Subscription: conn.UplinkNormalized,
+ },
{
Name: "JoinAccept",
Message: &ttnpb.ApplicationUp{
@@ -318,10 +356,10 @@ func TestPubSub(t *testing.T) {
EndDeviceIds: registeredDeviceID,
Up: &ttnpb.ApplicationUp_ServiceData{
ServiceData: &ttnpb.ApplicationServiceData{
- Data: &types.Struct{
- Fields: map[string]*types.Value{
+ Data: &pbtypes.Struct{
+ Fields: map[string]*pbtypes.Value{
"battery": {
- Kind: &types.Value_NumberValue{
+ Kind: &pbtypes.Value_NumberValue{
NumberValue: 42.0,
},
},
@@ -370,6 +408,7 @@ func TestPubSub(t *testing.T) {
}
})
+ //nolint:paralleltest
t.Run("Downstream", func(t *testing.T) {
for _, tc := range []struct {
Name string
diff --git a/pkg/applicationserver/io/web/templates.go b/pkg/applicationserver/io/web/templates.go
index 9bf7925c19..bb63c0e857 100644
--- a/pkg/applicationserver/io/web/templates.go
+++ b/pkg/applicationserver/io/web/templates.go
@@ -224,6 +224,7 @@ func (f webhookTemplateField) toPB() *ttnpb.ApplicationWebhookTemplateField {
type webhookTemplatePaths struct {
UplinkMessage *string `yaml:"uplink-message,omitempty"`
+ UplinkNormalized *string `yaml:"uplink-normalized,omitempty"`
JoinAccept *string `yaml:"join-accept,omitempty"`
DownlinkAck *string `yaml:"downlink-ack,omitempty"`
DownlinkNack *string `yaml:"downlink-nack,omitempty"`
@@ -288,6 +289,7 @@ func (t webhookTemplate) toPB() *ttnpb.ApplicationWebhookTemplate {
Fields: t.pbFields(),
CreateDownlinkApiKey: t.CreateDownlinkAPIKey,
UplinkMessage: t.pathToMessage(t.Paths.UplinkMessage),
+ UplinkNormalized: t.pathToMessage(t.Paths.UplinkNormalized),
JoinAccept: t.pathToMessage(t.Paths.JoinAccept),
DownlinkAck: t.pathToMessage(t.Paths.DownlinkAck),
DownlinkNack: t.pathToMessage(t.Paths.DownlinkNack),
diff --git a/pkg/applicationserver/io/web/webhooks.go b/pkg/applicationserver/io/web/webhooks.go
index 318e2e00ff..0af9b0cf8a 100644
--- a/pkg/applicationserver/io/web/webhooks.go
+++ b/pkg/applicationserver/io/web/webhooks.go
@@ -83,7 +83,7 @@ func (s *HTTPClientSink) Process(req *http.Request) error {
return errRequest.WithCause(err).WithDetails(createRequestErrorDetails(req, res)...)
}
defer res.Body.Close()
- defer stdio.Copy(stdio.Discard, res.Body)
+ defer stdio.Copy(stdio.Discard, res.Body) //nolint:errcheck
if res.StatusCode >= 200 && res.StatusCode <= 299 {
return nil
}
@@ -125,10 +125,7 @@ func NewPooledSink(ctx context.Context, c workerpool.Component, sink Sink, worke
// Process sends the request to the workers.
// This method returns immediately. An error is returned when the workers are too busy.
func (s *pooledSink) Process(req *http.Request) error {
- if err := s.pool.Publish(req.Context(), req); err != nil {
- return err
- }
- return nil
+ return s.pool.Publish(req.Context(), req)
}
// Webhooks is an interface for registering incoming webhooks for downlink and creating a subscription to outgoing
@@ -147,7 +144,13 @@ type webhooks struct {
}
// NewWebhooks returns a new Webhooks.
-func NewWebhooks(ctx context.Context, server io.Server, registry WebhookRegistry, target Sink, downlinks DownlinksConfig) (Webhooks, error) {
+func NewWebhooks(
+ ctx context.Context,
+ server io.Server,
+ registry WebhookRegistry,
+ target Sink,
+ downlinks DownlinksConfig,
+) (Webhooks, error) {
ctx = log.NewContextWithField(ctx, "namespace", namespace)
w := &webhooks{
ctx: ctx,
@@ -174,7 +177,9 @@ func (w *webhooks) Registry() WebhookRegistry { return w.registry }
// RegisterRoutes registers the webhooks to the web server to handle downlink requests.
func (w *webhooks) RegisterRoutes(server *ttnweb.Server) {
- router := server.Prefix(ttnpb.HTTPAPIPrefix + "/as/applications/{application_id}/webhooks/{webhook_id}/devices/{device_id}/down").Subrouter()
+ router := server.Prefix(
+ ttnpb.HTTPAPIPrefix + "/as/applications/{application_id}/webhooks/{webhook_id}/devices/{device_id}/down",
+ ).Subrouter()
router.Use(
mux.MiddlewareFunc(webmiddleware.Namespace("applicationserver/io/web")),
mux.MiddlewareFunc(webmiddleware.Metadata("Authorization")),
@@ -197,7 +202,12 @@ const (
domainHeader = "X-Tts-Domain"
)
-func (w *webhooks) createDownlinkURL(ctx context.Context, webhookID *ttnpb.ApplicationWebhookIdentifiers, devID *ttnpb.EndDeviceIdentifiers, op string) string {
+func (w *webhooks) downlinkURL(
+ _ context.Context,
+ webhookID *ttnpb.ApplicationWebhookIdentifiers,
+ devID *ttnpb.EndDeviceIdentifiers,
+ op string,
+) string {
downlinks := w.downlinks
baseURL := downlinks.PublicTLSAddress
if baseURL == "" {
@@ -212,7 +222,7 @@ func (w *webhooks) createDownlinkURL(ctx context.Context, webhookID *ttnpb.Appli
)
}
-func (w *webhooks) createDomain(ctx context.Context) string {
+func (w *webhooks) domain(_ context.Context) string {
downlinks := w.downlinks
baseURL := downlinks.PublicTLSAddress
if baseURL == "" {
@@ -245,6 +255,7 @@ func (w *webhooks) handleUp(ctx context.Context, msg *ttnpb.ApplicationUp) error
"location_solved",
"service_data",
"uplink_message",
+ "uplink_normalized",
},
)
if err != nil {
@@ -279,12 +290,14 @@ func (w *webhooks) handleUp(ctx context.Context, msg *ttnpb.ApplicationUp) error
return nil
}
-func activeWebhookUplinkMessage(
+func webhookMessage(
msg *ttnpb.ApplicationUp, hook *ttnpb.ApplicationWebhook,
) *ttnpb.ApplicationWebhook_Message {
switch msg.Up.(type) {
case *ttnpb.ApplicationUp_UplinkMessage:
return hook.UplinkMessage
+ case *ttnpb.ApplicationUp_UplinkNormalized:
+ return hook.UplinkNormalized
case *ttnpb.ApplicationUp_JoinAccept:
return hook.JoinAccept
case *ttnpb.ApplicationUp_DownlinkAck:
@@ -311,6 +324,8 @@ func webhookUplinkMessageMask(msg *ttnpb.ApplicationUp) string {
switch msg.Up.(type) {
case *ttnpb.ApplicationUp_UplinkMessage:
return "up.uplink_message"
+ case *ttnpb.ApplicationUp_UplinkNormalized:
+ return "up.uplink_normalized"
case *ttnpb.ApplicationUp_JoinAccept:
return "up.join_accept"
case *ttnpb.ApplicationUp_DownlinkAck:
@@ -333,12 +348,14 @@ func webhookUplinkMessageMask(msg *ttnpb.ApplicationUp) string {
return ""
}
+// newRequest returns an HTTP request.
+// This method returns nil, nil if the hook is not configured for the message.
func (w *webhooks) newRequest(
ctx context.Context, msg *ttnpb.ApplicationUp, hook *ttnpb.ApplicationWebhook,
) (*http.Request, error) {
- cfg := activeWebhookUplinkMessage(msg, hook)
+ cfg := webhookMessage(msg, hook)
if cfg == nil {
- return nil, nil
+ return nil, nil //nolint:nilnil
}
baseURL, err := expandVariables(hook.BaseUrl, msg)
if err != nil {
@@ -393,10 +410,10 @@ func (w *webhooks) newRequest(
}
if hook.DownlinkApiKey != "" {
req.Header.Set(downlinkKeyHeader, hook.DownlinkApiKey)
- req.Header.Set(downlinkPushHeader, w.createDownlinkURL(ctx, hook.Ids, msg.EndDeviceIds, "push"))
- req.Header.Set(downlinkReplaceHeader, w.createDownlinkURL(ctx, hook.Ids, msg.EndDeviceIds, "replace"))
+ req.Header.Set(downlinkPushHeader, w.downlinkURL(ctx, hook.Ids, msg.EndDeviceIds, "push"))
+ req.Header.Set(downlinkReplaceHeader, w.downlinkURL(ctx, hook.Ids, msg.EndDeviceIds, "replace"))
}
- if domain := w.createDomain(ctx); domain != "" {
+ if domain := w.domain(ctx); domain != "" {
req.Header.Set(domainHeader, domain)
}
req.Header.Set("Content-Type", format.ContentType)
@@ -410,7 +427,9 @@ var (
errValidateBody = errors.DefineInvalidArgument("validate_body", "validate body")
)
-func (w *webhooks) handleDown(op func(io.Server, context.Context, *ttnpb.EndDeviceIdentifiers, []*ttnpb.ApplicationDownlink) error) http.Handler {
+func (w *webhooks) handleDown(
+ op func(io.Server, context.Context, *ttnpb.EndDeviceIdentifiers, []*ttnpb.ApplicationDownlink) error,
+) http.Handler {
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
ctx := req.Context()
devID := deviceIDFromContext(ctx)
diff --git a/pkg/applicationserver/io/web/webhooks_test.go b/pkg/applicationserver/io/web/webhooks_test.go
index 88263ff7af..0212715877 100644
--- a/pkg/applicationserver/io/web/webhooks_test.go
+++ b/pkg/applicationserver/io/web/webhooks_test.go
@@ -51,11 +51,12 @@ func (mockComponent) FromRequestContext(ctx context.Context) context.Context {
return ctx
}
-func createdPooledSink(ctx context.Context, t *testing.T, sink web.Sink) web.Sink {
+func pooledSink(ctx context.Context, sink web.Sink) web.Sink {
return web.NewPooledSink(ctx, mockComponent{}, sink, 1, 4)
}
func TestWebhooks(t *testing.T) {
+ t.Parallel()
a, ctx := test.New(t)
redisClient, flush := test.NewRedis(ctx, "web_test")
@@ -79,6 +80,7 @@ func TestWebhooks(t *testing.T) {
// Use a dummy JWT for auth check.
longAuthHeader := "Bearer eyJhbGciOiJIUzI1NiJ9.eyJSb2xlIjoidGVzdHdocm9sZSIsIklzc3VlciI6Iklzc3VlciIsIlVzZXJuYW1lIjoidGVzdHVzZXIiLCJleHAiOjE2NDI0MjU3NDgsImlhdCI6MTY0MjQyNTc0OH0.imuGY_5xnhZYSqjPrc6EUoYV1eapswDBUIBXKVCIYSw" //nolint:lll
+ //nolint:paralleltest
for _, tc := range []struct {
prefix string
suffix string
@@ -114,6 +116,9 @@ func TestWebhooks(t *testing.T) {
UplinkMessage: &ttnpb.ApplicationWebhook_Message{
Path: tc.prefix + "up{?devEUI}",
},
+ UplinkNormalized: &ttnpb.ApplicationWebhook_Message{
+ Path: tc.prefix + "up/normalized{?devEUI}",
+ },
JoinAccept: &ttnpb.ApplicationWebhook_Message{
Path: tc.prefix + "join{?joinEUI}",
},
@@ -156,25 +161,27 @@ func TestWebhooks(t *testing.T) {
"up.location_solved",
"up.service_data",
"up.uplink_message",
+ "up.uplink_normalized",
),
},
[]string{
"base_url",
- "downlink_api_key",
"downlink_ack",
+ "downlink_api_key",
"downlink_failed",
"downlink_nack",
- "downlink_queued",
"downlink_queue_invalidated",
+ "downlink_queued",
"downlink_sent",
"field_mask",
"format",
"headers",
"ids",
- "service_data",
"join_accept",
"location_solved",
+ "service_data",
"uplink_message",
+ "uplink_normalized",
}, nil
})
if err != nil {
@@ -182,15 +189,17 @@ func TestWebhooks(t *testing.T) {
}
t.Run("Upstream", func(t *testing.T) {
- baseURL := fmt.Sprintf("https://myapp.com/api/ttn/v3/%s/%s", registeredApplicationID.ApplicationId, registeredDeviceID.DeviceId)
+ baseURL := fmt.Sprintf(
+ "https://myapp.com/api/ttn/v3/%s/%s", registeredApplicationID.ApplicationId, registeredDeviceID.DeviceId,
+ )
testSink := &mockSink{
ch: make(chan *http.Request, 1),
}
for _, sink := range []web.Sink{
testSink,
- createdPooledSink(ctx, t, testSink),
- createdPooledSink(ctx, t,
- createdPooledSink(ctx, t, testSink),
+ pooledSink(ctx, testSink),
+ pooledSink(ctx,
+ pooledSink(ctx, testSink),
),
} {
t.Run(fmt.Sprintf("%T", sink), func(t *testing.T) {
@@ -224,6 +233,39 @@ func TestWebhooks(t *testing.T) {
OK: true,
URL: fmt.Sprintf("%s/up?devEUI=%s", baseURL, types.MustEUI64(registeredDeviceID.DevEui)),
},
+ {
+ Name: "UplinkNormalized/RegisteredDevice",
+ Message: &ttnpb.ApplicationUp{
+ EndDeviceIds: registeredDeviceID,
+ Up: &ttnpb.ApplicationUp_UplinkNormalized{
+ UplinkNormalized: &ttnpb.ApplicationUplinkNormalized{
+ SessionKeyId: []byte{0x11},
+ FPort: 42,
+ FCnt: 42,
+ FrmPayload: []byte{0x1, 0x2, 0x3},
+ NormalizedPayload: &pbtypes.Struct{
+ Fields: map[string]*pbtypes.Value{
+ "air": {
+ Kind: &pbtypes.Value_StructValue{
+ StructValue: &pbtypes.Struct{
+ Fields: map[string]*pbtypes.Value{
+ "temperature": {
+ Kind: &pbtypes.Value_NumberValue{
+ NumberValue: 21.5,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ OK: true,
+ URL: fmt.Sprintf("%s/up/normalized?devEUI=%s", baseURL, types.MustEUI64(registeredDeviceID.DevEui)),
+ },
{
Name: "UplinkMessage/UnregisteredDevice",
Message: &ttnpb.ApplicationUp{
@@ -413,20 +455,21 @@ func TestWebhooks(t *testing.T) {
t.Fatalf("Did not expect message but received: %v", req)
}
case <-time.After(Timeout):
- if tc.OK {
- t.Fatal("Expected message but nothing received")
- } else {
+ if !tc.OK {
return
}
+ t.Fatal("Expected message but nothing received")
}
a.So(req.URL.String(), should.Equal, tc.URL)
a.So(req.Header.Get("Authorization"), should.Equal, longAuthHeader)
a.So(req.Header.Get("Content-Type"), should.Equal, "application/json")
a.So(req.Header.Get("X-Downlink-Apikey"), should.Equal, "foo.secret")
a.So(req.Header.Get("X-Downlink-Push"), should.Equal,
- "https://example.com/api/v3/as/applications/foo-app/webhooks/foo-hook/devices/foo-device/down/push")
+ "https://example.com/api/v3/as/applications/foo-app/webhooks/foo-hook/devices/foo-device/down/push",
+ )
a.So(req.Header.Get("X-Downlink-Replace"), should.Equal,
- "https://example.com/api/v3/as/applications/foo-app/webhooks/foo-hook/devices/foo-device/down/replace")
+ "https://example.com/api/v3/as/applications/foo-app/webhooks/foo-hook/devices/foo-device/down/replace", //nolint:lll
+ )
a.So(req.Header.Get("X-Tts-Domain"), should.Equal, "example.com")
actualBody, err := stdio.ReadAll(req.Body)
if !a.So(err, should.BeNil) {
@@ -445,6 +488,7 @@ func TestWebhooks(t *testing.T) {
})
}
+ //nolint:paralleltest
t.Run("Downstream", func(t *testing.T) {
is, isAddr, closeIS := mockis.New(ctx)
defer closeIS()
@@ -482,6 +526,7 @@ func TestWebhooks(t *testing.T) {
mustHavePeer(ctx, c, ttnpb.ClusterRole_ENTITY_REGISTRY)
+ //nolint:paralleltest
t.Run("Authorization", func(t *testing.T) {
for _, tc := range []struct {
Name string
diff --git a/pkg/applicationserver/observability.go b/pkg/applicationserver/observability.go
index d66900db00..4160dcff24 100644
--- a/pkg/applicationserver/observability.go
+++ b/pkg/applicationserver/observability.go
@@ -56,6 +56,12 @@ var (
events.WithDataType(&ttnpb.ApplicationUplink{}),
events.WithPropagateToParent(),
)
+ evtNormalizeWarningDataUp = events.Define(
+ "as.up.data.normalize.warning", "normalize uplink data message warning",
+ events.WithVisibility(ttnpb.Right_RIGHT_APPLICATION_TRAFFIC_READ),
+ events.WithDataType(&ttnpb.ApplicationUplink{}),
+ events.WithPropagateToParent(),
+ )
evtReceiveJoinAccept = events.Define(
"as.up.join.receive", "receive join-accept message",
events.WithVisibility(ttnpb.Right_RIGHT_APPLICATION_TRAFFIC_READ),
@@ -72,6 +78,12 @@ var (
events.WithDataType(&ttnpb.ApplicationUp{}),
events.WithPropagateToParent(),
)
+ evtForwardNormalizedUp = events.Define(
+ "as.up.normalized.forward", "forward normalized uplink message",
+ events.WithVisibility(ttnpb.Right_RIGHT_APPLICATION_TRAFFIC_READ),
+ events.WithDataType(&ttnpb.ApplicationUp{}),
+ events.WithPropagateToParent(),
+ )
evtForwardLocationSolved = events.Define(
"as.up.location.forward", "forward location solved message",
events.WithVisibility(ttnpb.Right_RIGHT_APPLICATION_TRAFFIC_READ),
@@ -133,7 +145,6 @@ const (
subsystem = "as"
unknown = "unknown"
networkServer = "network_server"
- protocol = "protocol"
applicationID = "application_id"
)
@@ -245,8 +256,8 @@ func (m messageMetrics) Collect(ch chan<- prometheus.Metric) {
func registerReceiveUp(ctx context.Context, msg *ttnpb.ApplicationUp) {
ns := "application"
- if peer, ok := peer.FromContext(ctx); ok {
- ns = peer.Addr.String()
+ if p, ok := peer.FromContext(ctx); ok {
+ ns = p.Addr.String()
}
switch msg.Up.(type) {
case *ttnpb.ApplicationUp_JoinAccept:
@@ -265,6 +276,8 @@ func registerForwardUp(ctx context.Context, msg *ttnpb.ApplicationUp) {
events.Publish(evtForwardJoinAccept.NewWithIdentifiersAndData(ctx, msg.EndDeviceIds, msg))
case *ttnpb.ApplicationUp_UplinkMessage:
events.Publish(evtForwardDataUp.NewWithIdentifiersAndData(ctx, msg.EndDeviceIds, msg))
+ case *ttnpb.ApplicationUp_UplinkNormalized:
+ events.Publish(evtForwardNormalizedUp.NewWithIdentifiersAndData(ctx, msg.EndDeviceIds, msg))
case *ttnpb.ApplicationUp_LocationSolved:
events.Publish(evtForwardLocationSolved.NewWithIdentifiersAndData(ctx, msg.EndDeviceIds, msg))
case *ttnpb.ApplicationUp_ServiceData:
@@ -326,9 +339,12 @@ func registerDropDownlink(ctx context.Context, ids *ttnpb.EndDeviceIdentifiers,
}
func (as *ApplicationServer) registerDropDownlinks(ctx context.Context, ids *ttnpb.EndDeviceIdentifiers, items []*ttnpb.ApplicationDownlink, err error) {
- var errorDetails ttnpb.ErrorDetails
- if ttnErr, ok := err.(errors.ErrorDetails); ok {
- errorDetails = *ttnpb.ErrorDetailsToProto(ttnErr)
+ var (
+ errorDetails errors.ErrorDetails
+ pbErrorDetails *ttnpb.ErrorDetails
+ )
+ if errors.As(err, &errorDetails) {
+ pbErrorDetails = ttnpb.ErrorDetailsToProto(errorDetails)
}
for _, item := range items {
if err := as.publishUp(ctx, &ttnpb.ApplicationUp{
@@ -337,7 +353,7 @@ func (as *ApplicationServer) registerDropDownlinks(ctx context.Context, ids *ttn
Up: &ttnpb.ApplicationUp_DownlinkFailed{
DownlinkFailed: &ttnpb.ApplicationDownlinkFailed{
Downlink: item,
- Error: &errorDetails,
+ Error: pbErrorDetails,
},
},
}); err != nil {
diff --git a/pkg/applicationserver/payload.go b/pkg/applicationserver/payload.go
index 484a8150ab..dbb9175f16 100644
--- a/pkg/applicationserver/payload.go
+++ b/pkg/applicationserver/payload.go
@@ -151,8 +151,13 @@ func (as *ApplicationServer) decodeUplink(ctx context.Context, dev *ttnpb.EndDev
if err := as.formatters.DecodeUplink(ctx, dev.Ids, dev.VersionIds, uplink, formatter, parameter); err != nil {
log.FromContext(ctx).WithError(err).Warn("Failed to decode uplink")
events.Publish(evtDecodeFailDataUp.NewWithIdentifiersAndData(ctx, dev.Ids, err))
- } else if len(uplink.DecodedPayloadWarnings) > 0 {
- events.Publish(evtDecodeWarningDataUp.NewWithIdentifiersAndData(ctx, dev.Ids, uplink))
+ } else {
+ if len(uplink.DecodedPayloadWarnings) > 0 {
+ events.Publish(evtDecodeWarningDataUp.NewWithIdentifiersAndData(ctx, dev.Ids, uplink))
+ }
+ if len(uplink.NormalizedPayloadWarnings) > 0 {
+ events.Publish(evtNormalizeWarningDataUp.NewWithIdentifiersAndData(ctx, dev.Ids, uplink))
+ }
}
}
return nil
@@ -211,7 +216,8 @@ func (as *ApplicationServer) decodeDownlink(ctx context.Context, dev *ttnpb.EndD
return nil
}
-func (as *ApplicationServer) locationFromDecodedPayload(uplink *ttnpb.ApplicationUplink) (res *ttnpb.Location) {
+func (*ApplicationServer) locationFromPayload(uplink *ttnpb.ApplicationUplink) (res *ttnpb.Location) {
+ // TODO: Prefer location from normalized payload (https://github.com/TheThingsNetwork/lorawan-stack/issues/5429)
m, err := gogoproto.Map(uplink.DecodedPayload)
if err != nil {
return nil
diff --git a/pkg/applicationserver/util_test.go b/pkg/applicationserver/util_test.go
index efb1a95542..0ae363eba9 100644
--- a/pkg/applicationserver/util_test.go
+++ b/pkg/applicationserver/util_test.go
@@ -50,10 +50,6 @@ func mustHavePeer(ctx context.Context, c *component.Component, role ttnpb.Cluste
panic("could not connect to peer")
}
-func eui64Ptr(eui types.EUI64) *types.EUI64 {
- return &eui
-}
-
func withDevAddr(ids *ttnpb.EndDeviceIdentifiers, devAddr types.DevAddr) *ttnpb.EndDeviceIdentifiers {
newIds := &ttnpb.EndDeviceIdentifiers{}
if err := newIds.SetFields(ids, ttnpb.EndDeviceIdentifiersFieldPathsNested...); err != nil {
@@ -90,7 +86,11 @@ func startMockNS(ctx context.Context, link chan *mockNSASConn) (*mockNS, string)
if err != nil {
panic(err)
}
- go srv.Serve(lis)
+ go func() {
+ if err := srv.Serve(lis); err != nil {
+ panic(err)
+ }
+ }()
return ns, lis.Addr().String()
}
@@ -164,7 +164,11 @@ func startMockJS(ctx context.Context) (*mockJS, string) {
if err != nil {
panic(err)
}
- go srv.Serve(lis)
+ go func() {
+ if err := srv.Serve(lis); err != nil {
+ panic(err)
+ }
+ }()
return js, lis.Addr().String()
}
diff --git a/pkg/messageprocessors/devicerepository/devicerepository.go b/pkg/messageprocessors/devicerepository/devicerepository.go
index 156f0aa90a..a916448ccb 100644
--- a/pkg/messageprocessors/devicerepository/devicerepository.go
+++ b/pkg/messageprocessors/devicerepository/devicerepository.go
@@ -32,9 +32,9 @@ import (
type codecType string
const (
- downlinkEncoder = codecType("downlinkEncoder")
- downlinkDecoder = codecType("downlinkDecoder")
- uplinkDecoder = codecType("uplinkDecoder")
+ downlinkEncoder codecType = "downlinkEncoder"
+ downlinkDecoder codecType = "downlinkDecoder"
+ uplinkDecoder codecType = "uplinkDecoder"
// cacheTTL is the TTL for cached payload formatters.
cacheTTL = time.Hour
@@ -183,7 +183,7 @@ func (h *host) compileProcessor(ctx context.Context, codec codecType, formatter
uplinkProcessor: run,
}, nil
default:
- panic(fmt.Sprintf("Invalid codec type: %v", codec))
+ panic(fmt.Sprintf("invalid codec type: %v", codec))
}
}
@@ -207,7 +207,7 @@ func (h *host) compileProcessor(ctx context.Context, codec codecType, formatter
},
}, nil
default:
- panic(fmt.Sprintf("Invalid codec type: %v", codec))
+ panic(fmt.Sprintf("invalid codec type: %v", codec))
}
}
diff --git a/pkg/messageprocessors/javascript/javascript.go b/pkg/messageprocessors/javascript/javascript.go
index 1fd32bc690..65f77d2ad7 100644
--- a/pkg/messageprocessors/javascript/javascript.go
+++ b/pkg/messageprocessors/javascript/javascript.go
@@ -18,12 +18,15 @@ package javascript
import (
"context"
"fmt"
+ "reflect"
"runtime/trace"
"strings"
+ pbtypes "github.com/gogo/protobuf/types"
"go.thethings.network/lorawan-stack/v3/pkg/errors"
"go.thethings.network/lorawan-stack/v3/pkg/gogoproto"
"go.thethings.network/lorawan-stack/v3/pkg/messageprocessors"
+ "go.thethings.network/lorawan-stack/v3/pkg/messageprocessors/normalizedpayload"
"go.thethings.network/lorawan-stack/v3/pkg/scripting"
js "go.thethings.network/lorawan-stack/v3/pkg/scripting/javascript"
"go.thethings.network/lorawan-stack/v3/pkg/ttnpb"
@@ -59,16 +62,17 @@ var (
)
func wrapDownlinkEncoderScript(script string) string {
- // Fallback to legacy Encoder() function for backwards compatibility with The Things Network Stack V2 payload functions.
+ // Fallback to Encoder() for backwards compatibility with The Things Network Stack V2 payload functions.
return fmt.Sprintf(`
%s
function main(input) {
+ const { data, fPort } = input;
if (typeof encodeDownlink === 'function') {
- return encodeDownlink(input);
+ return encodeDownlink({ data, fPort });
}
return {
- bytes: Encoder(input.data, input.fPort),
+ bytes: Encoder(data, fPort),
fPort: input.fPort
}
}
@@ -76,7 +80,17 @@ func wrapDownlinkEncoderScript(script string) string {
}
// CompileDownlinkEncoder generates a downlink encoder from the provided script.
-func (h *host) CompileDownlinkEncoder(ctx context.Context, script string) (func(context.Context, *ttnpb.EndDeviceIdentifiers, *ttnpb.EndDeviceVersionIdentifiers, *ttnpb.ApplicationDownlink) error, error) {
+func (h *host) CompileDownlinkEncoder(
+ ctx context.Context, script string,
+) (
+ func(
+ context.Context,
+ *ttnpb.EndDeviceIdentifiers,
+ *ttnpb.EndDeviceVersionIdentifiers,
+ *ttnpb.ApplicationDownlink,
+ ) error,
+ error,
+) {
defer trace.StartRegion(ctx, "compile downlink encoder").End()
run, err := h.engine.Compile(ctx, wrapDownlinkEncoderScript(script))
@@ -84,20 +98,35 @@ func (h *host) CompileDownlinkEncoder(ctx context.Context, script string) (func(
return nil, err
}
- return func(ctx context.Context, ids *ttnpb.EndDeviceIdentifiers, version *ttnpb.EndDeviceVersionIdentifiers, msg *ttnpb.ApplicationDownlink) error {
- return h.encodeDownlink(ctx, ids, version, msg, run)
+ return func(
+ ctx context.Context,
+ ids *ttnpb.EndDeviceIdentifiers,
+ version *ttnpb.EndDeviceVersionIdentifiers,
+ msg *ttnpb.ApplicationDownlink,
+ ) error {
+ return h.encodeDownlink(ctx, msg, run)
}, nil
}
// EncodeDownlink encodes the message's DecodedPayload to FRMPayload using the given script.
-func (h *host) EncodeDownlink(ctx context.Context, ids *ttnpb.EndDeviceIdentifiers, version *ttnpb.EndDeviceVersionIdentifiers, msg *ttnpb.ApplicationDownlink, script string) error {
+func (h *host) EncodeDownlink(
+ ctx context.Context,
+ _ *ttnpb.EndDeviceIdentifiers,
+ _ *ttnpb.EndDeviceVersionIdentifiers,
+ msg *ttnpb.ApplicationDownlink,
+ script string,
+) error {
run := func(ctx context.Context, fn string, params ...interface{}) (func(interface{}) error, error) {
return h.engine.Run(ctx, wrapDownlinkEncoderScript(script), fn, params...)
}
- return h.encodeDownlink(ctx, ids, version, msg, run)
+ return h.encodeDownlink(ctx, msg, run)
}
-func (h *host) encodeDownlink(ctx context.Context, ids *ttnpb.EndDeviceIdentifiers, version *ttnpb.EndDeviceVersionIdentifiers, msg *ttnpb.ApplicationDownlink, run func(context.Context, string, ...interface{}) (func(interface{}) error, error)) error {
+func (*host) encodeDownlink(
+ ctx context.Context,
+ msg *ttnpb.ApplicationDownlink,
+ run func(context.Context, string, ...interface{}) (func(interface{}) error, error),
+) error {
defer trace.StartRegion(ctx, "encode downlink message").End()
decoded := msg.DecodedPayload
@@ -150,28 +179,57 @@ type decodeUplinkOutput struct {
Errors []string `json:"errors"`
}
+type normalizeUplinkOutput struct {
+ Data interface{} `json:"data"`
+ Warnings []string `json:"warnings"`
+ Errors []string `json:"errors"`
+}
+
+type uplinkDecoderOutput struct {
+ Decoded decodeUplinkOutput `json:"decoded"`
+ Normalized *normalizeUplinkOutput `json:"normalized"`
+}
+
func wrapUplinkDecoderScript(script string) string {
- // Fallback to legacy Decoder() function for backwards compatibility with The Things Network Stack V2 payload functions.
+ // This wrapper executes decodeUplink() if it is defined. Then, it executes normalizeUplink() if it is defined too,
+ // and if the output of decodeUplink() didn't return errors.
+ // Fallback to Decoder() for backwards compatibility with The Things Network Stack V2 payload functions.
return fmt.Sprintf(`
%s
function main(input) {
- input = {
- bytes: input.bytes.slice(),
- fPort: input.fPort,
- }
+ const bytes = input.bytes.slice();
+ const { fPort } = input;
if (typeof decodeUplink === 'function') {
- return decodeUplink(input);
+ const decoded = decodeUplink({ bytes, fPort });
+ let normalized;
+ const { data, errors } = decoded;
+ if ((!errors || !errors.length) && data && typeof normalizeUplink === 'function') {
+ normalized = normalizeUplink({ data });
+ }
+ return { decoded, normalized };
}
return {
- data: Decoder(input.bytes, input.fPort)
+ decoded: {
+ data: Decoder(bytes, fPort)
+ }
}
}
`, script)
}
// CompileUplinkDecoder generates an uplink decoder from the provided script.
-func (h *host) CompileUplinkDecoder(ctx context.Context, script string) (func(context.Context, *ttnpb.EndDeviceIdentifiers, *ttnpb.EndDeviceVersionIdentifiers, *ttnpb.ApplicationUplink) error, error) {
+func (h *host) CompileUplinkDecoder(
+ ctx context.Context, script string,
+) (
+ func(
+ context.Context,
+ *ttnpb.EndDeviceIdentifiers,
+ *ttnpb.EndDeviceVersionIdentifiers,
+ *ttnpb.ApplicationUplink,
+ ) error,
+ error,
+) {
defer trace.StartRegion(ctx, "compile uplink decoder").End()
run, err := h.engine.Compile(ctx, wrapUplinkDecoderScript(script))
@@ -179,20 +237,35 @@ func (h *host) CompileUplinkDecoder(ctx context.Context, script string) (func(co
return nil, err
}
- return func(ctx context.Context, ids *ttnpb.EndDeviceIdentifiers, version *ttnpb.EndDeviceVersionIdentifiers, msg *ttnpb.ApplicationUplink) error {
- return h.decodeUplink(ctx, ids, version, msg, run)
+ return func(
+ ctx context.Context,
+ ids *ttnpb.EndDeviceIdentifiers,
+ version *ttnpb.EndDeviceVersionIdentifiers,
+ msg *ttnpb.ApplicationUplink,
+ ) error {
+ return h.decodeUplink(ctx, msg, run)
}, nil
}
// DecodeUplink decodes the message's FRMPayload to DecodedPayload using the given script.
-func (h *host) DecodeUplink(ctx context.Context, ids *ttnpb.EndDeviceIdentifiers, version *ttnpb.EndDeviceVersionIdentifiers, msg *ttnpb.ApplicationUplink, script string) error {
+func (h *host) DecodeUplink(
+ ctx context.Context,
+ _ *ttnpb.EndDeviceIdentifiers,
+ _ *ttnpb.EndDeviceVersionIdentifiers,
+ msg *ttnpb.ApplicationUplink,
+ script string,
+) error {
run := func(ctx context.Context, fn string, params ...interface{}) (func(interface{}) error, error) {
return h.engine.Run(ctx, wrapUplinkDecoderScript(script), fn, params...)
}
- return h.decodeUplink(ctx, ids, version, msg, run)
+ return h.decodeUplink(ctx, msg, run)
}
-func (h *host) decodeUplink(ctx context.Context, ids *ttnpb.EndDeviceIdentifiers, version *ttnpb.EndDeviceVersionIdentifiers, msg *ttnpb.ApplicationUplink, run func(context.Context, string, ...interface{}) (func(interface{}) error, error)) error {
+func (*host) decodeUplink(
+ ctx context.Context,
+ msg *ttnpb.ApplicationUplink,
+ run func(context.Context, string, ...interface{}) (func(interface{}) error, error),
+) error {
defer trace.StartRegion(ctx, "decode uplink message").End()
input := decodeUplinkInput{
@@ -205,21 +278,72 @@ func (h *host) decodeUplink(ctx context.Context, ids *ttnpb.EndDeviceIdentifiers
return err
}
- var output decodeUplinkOutput
+ var output uplinkDecoderOutput
err = valueAs(&output)
if err != nil {
return errOutput.WithCause(err)
}
- if len(output.Errors) > 0 {
- return errOutputErrors.WithAttributes("errors", strings.Join(output.Errors, ", "))
- }
- s, err := gogoproto.Struct(output.Data)
+ if errs := output.Decoded.Errors; len(errs) > 0 {
+ return errOutputErrors.WithAttributes("errors", strings.Join(errs, ", "))
+ }
+ decodedPayload, err := gogoproto.Struct(output.Decoded.Data)
if err != nil {
return errOutput.WithCause(err)
}
- msg.DecodedPayload = s
- msg.DecodedPayloadWarnings = output.Warnings
+ msg.DecodedPayload, msg.DecodedPayloadWarnings = decodedPayload, output.Decoded.Warnings
+
+ if normalized := output.Normalized; normalized != nil {
+ if errs := normalized.Errors; len(errs) > 0 {
+ return errOutputErrors.WithAttributes("errors", strings.Join(errs, ", "))
+ }
+ if normalized.Data == nil {
+ return errOutput.New()
+ }
+ // The returned data can be an array of measurements or a single measurement object.
+ var measurements []map[string]interface{}
+ if val := reflect.ValueOf(normalized.Data); val.Kind() == reflect.Slice {
+ measurements = make([]map[string]interface{}, val.Len())
+ for i := 0; i < val.Len(); i++ {
+ measurement, ok := val.Index(i).Interface().(map[string]interface{})
+ if !ok {
+ return errOutput.New()
+ }
+ measurements[i] = measurement
+ }
+ } else {
+ measurement, ok := normalized.Data.(map[string]interface{})
+ if !ok {
+ return errOutput.New()
+ }
+ measurements = []map[string]interface{}{measurement}
+ }
+ normalizedPayload := make([]*pbtypes.Struct, len(measurements))
+ for i := range measurements {
+ pb, err := gogoproto.Struct(measurements[i])
+ if err != nil {
+ return errOutput.WithCause(err)
+ }
+ normalizedPayload[i] = pb
+ }
+ // Validate the normalized payload.
+ _, err := normalizedpayload.Parse(normalizedPayload)
+ if err != nil {
+ return errOutput.WithCause(err)
+ }
+ msg.NormalizedPayload, msg.NormalizedPayloadWarnings = normalizedPayload, normalized.Warnings
+ } else {
+ // If the normalizer is not set, the decoder may return already normalized payload.
+ // This is a best effort attempt to parse the decoded payload as normalized payload.
+ // If that does not return an error, the decoded payload is assumed to be normalized.
+ normalizedPayload := []*pbtypes.Struct{
+ decodedPayload,
+ }
+ _, err := normalizedpayload.Parse(normalizedPayload)
+ if err == nil {
+ msg.NormalizedPayload, msg.NormalizedPayloadWarnings = normalizedPayload, nil
+ }
+ }
return nil
}
@@ -239,17 +363,25 @@ func wrapDownlinkDecoderScript(script string) string {
%s
function main(input) {
- input = {
- bytes: input.bytes.slice(),
- fPort: input.fPort,
- }
- return decodeDownlink(input);
+ const bytes = input.bytes.slice();
+ const { fPort } = input;
+ return decodeDownlink({ bytes, fPort });
}
`, script)
}
// CompileDownlinkDecoder generates a downlink decoder from the provided script.
-func (h *host) CompileDownlinkDecoder(ctx context.Context, script string) (func(context.Context, *ttnpb.EndDeviceIdentifiers, *ttnpb.EndDeviceVersionIdentifiers, *ttnpb.ApplicationDownlink) error, error) {
+func (h *host) CompileDownlinkDecoder(
+ ctx context.Context, script string,
+) (
+ func(
+ context.Context,
+ *ttnpb.EndDeviceIdentifiers,
+ *ttnpb.EndDeviceVersionIdentifiers,
+ *ttnpb.ApplicationDownlink,
+ ) error,
+ error,
+) {
defer trace.StartRegion(ctx, "compile downlink decoder").End()
run, err := h.engine.Compile(ctx, wrapDownlinkDecoderScript(script))
@@ -257,20 +389,35 @@ func (h *host) CompileDownlinkDecoder(ctx context.Context, script string) (func(
return nil, err
}
- return func(ctx context.Context, ids *ttnpb.EndDeviceIdentifiers, version *ttnpb.EndDeviceVersionIdentifiers, msg *ttnpb.ApplicationDownlink) error {
- return h.decodeDownlink(ctx, ids, version, msg, run)
+ return func(
+ ctx context.Context,
+ ids *ttnpb.EndDeviceIdentifiers,
+ version *ttnpb.EndDeviceVersionIdentifiers,
+ msg *ttnpb.ApplicationDownlink,
+ ) error {
+ return h.decodeDownlink(ctx, msg, run)
}, nil
}
// DecodeUplink decodes the message's FRMPayload to DecodedPayload using the given script.
-func (h *host) DecodeDownlink(ctx context.Context, ids *ttnpb.EndDeviceIdentifiers, version *ttnpb.EndDeviceVersionIdentifiers, msg *ttnpb.ApplicationDownlink, script string) error {
+func (h *host) DecodeDownlink(
+ ctx context.Context,
+ _ *ttnpb.EndDeviceIdentifiers,
+ _ *ttnpb.EndDeviceVersionIdentifiers,
+ msg *ttnpb.ApplicationDownlink,
+ script string,
+) error {
run := func(ctx context.Context, fn string, params ...interface{}) (func(interface{}) error, error) {
return h.engine.Run(ctx, wrapDownlinkDecoderScript(script), fn, params...)
}
- return h.decodeDownlink(ctx, ids, version, msg, run)
+ return h.decodeDownlink(ctx, msg, run)
}
-func (h *host) decodeDownlink(ctx context.Context, ids *ttnpb.EndDeviceIdentifiers, version *ttnpb.EndDeviceVersionIdentifiers, msg *ttnpb.ApplicationDownlink, run func(context.Context, string, ...interface{}) (func(interface{}) error, error)) error {
+func (*host) decodeDownlink(
+ ctx context.Context,
+ msg *ttnpb.ApplicationDownlink,
+ run func(context.Context, string, ...interface{}) (func(interface{}) error, error),
+) error {
defer trace.StartRegion(ctx, "decode downlink message").End()
input := decodeDownlinkInput{
diff --git a/pkg/messageprocessors/javascript/javascript_test.go b/pkg/messageprocessors/javascript/javascript_test.go
index b9a3fbe8ee..86a1d43c81 100644
--- a/pkg/messageprocessors/javascript/javascript_test.go
+++ b/pkg/messageprocessors/javascript/javascript_test.go
@@ -19,7 +19,9 @@ import (
pbtypes "github.com/gogo/protobuf/types"
"github.com/smartystreets/assertions"
+ "go.thethings.network/lorawan-stack/v3/pkg/errors"
"go.thethings.network/lorawan-stack/v3/pkg/gogoproto"
+ "go.thethings.network/lorawan-stack/v3/pkg/messageprocessors/normalizedpayload"
"go.thethings.network/lorawan-stack/v3/pkg/ttnpb"
"go.thethings.network/lorawan-stack/v3/pkg/types"
"go.thethings.network/lorawan-stack/v3/pkg/util/test"
@@ -27,6 +29,7 @@ import (
)
func TestLegacyEncodeDownlink(t *testing.T) {
+ t.Parallel()
a := assertions.New(t)
ctx := test.Context()
@@ -96,6 +99,7 @@ func TestLegacyEncodeDownlink(t *testing.T) {
}
func TestEncodeDownlink(t *testing.T) {
+ t.Parallel()
a := assertions.New(t)
ctx := test.Context()
@@ -231,6 +235,7 @@ func TestEncodeDownlink(t *testing.T) {
}
func TestLegacyDecodeUplink(t *testing.T) {
+ t.Parallel()
a := assertions.New(t)
ctx := test.Context()
@@ -308,7 +313,12 @@ func TestLegacyDecodeUplink(t *testing.T) {
}
}
+func float64Ptr(f float64) *float64 {
+ return &f
+}
+
func TestDecodeUplink(t *testing.T) {
+ t.Parallel()
a := assertions.New(t)
ctx := test.Context()
@@ -324,10 +334,10 @@ func TestDecodeUplink(t *testing.T) {
}
message := &ttnpb.ApplicationUplink{
- FrmPayload: []byte{0xF7, 0xAE},
+ FrmPayload: []byte{0xF7, 0xAE, 0xF7, 0xD8},
}
- // Decode bytes.
+ // Decode and normalize a single measurement with a decoder warning.
{
script := `
function decodeUplink(input) {
@@ -343,15 +353,194 @@ func TestDecodeUplink(t *testing.T) {
warnings: warnings
}
}
+
+ function normalizeUplink(input) {
+ return {
+ data: {
+ air: {
+ temperature: input.data.temperature
+ }
+ }
+ }
+ }
`
err := host.DecodeUplink(ctx, ids, nil, message, script)
a.So(err, should.BeNil)
- m, err := gogoproto.Map(message.DecodedPayload)
+
+ decodedPayload, err := gogoproto.Map(message.DecodedPayload)
a.So(err, should.BeNil)
- a.So(m, should.Resemble, map[string]interface{}{
+ a.So(decodedPayload, should.Resemble, map[string]interface{}{
"temperature": -21.3,
})
a.So(message.DecodedPayloadWarnings, should.Resemble, []string{"it's cold"})
+
+ a.So(message.NormalizedPayload, should.HaveLength, 1)
+ measurements, err := normalizedpayload.Parse(message.NormalizedPayload)
+ a.So(err, should.BeNil)
+ a.So(measurements[0], should.Resemble, normalizedpayload.Measurement{
+ Air: &normalizedpayload.Air{
+ Temperature: float64Ptr(-21.3),
+ },
+ })
+ }
+
+ // Decode a single measurement that is already normalized.
+ {
+ //nolint:lll
+ script := `
+ function decodeUplink(input) {
+ return {
+ data: {
+ air: {
+ temperature: (((input.bytes[0] & 0x80 ? input.bytes[0] - 0x100 : input.bytes[0]) << 8) | input.bytes[1]) / 100
+ }
+ }
+ }
+ }
+ `
+ err := host.DecodeUplink(ctx, ids, nil, message, script)
+ a.So(err, should.BeNil)
+ a.So(message.NormalizedPayload, should.HaveLength, 1)
+ a.So(message.DecodedPayload, should.Resemble, message.NormalizedPayload[0])
+
+ measurements, err := normalizedpayload.Parse(message.NormalizedPayload)
+ a.So(err, should.BeNil)
+ a.So(measurements[0], should.Resemble, normalizedpayload.Measurement{
+ Air: &normalizedpayload.Air{
+ Temperature: float64Ptr(-21.3),
+ },
+ })
+ }
+
+ // Decode and normalize two measurements.
+ {
+ script := `
+ function decodeUplink(input) {
+ var data = {
+ temperatures: []
+ };
+ for (var i = 0; i < input.bytes.length; i += 2) {
+ var temp = (((input.bytes[i] & 0x80 ? input.bytes[i] - 0x100 : input.bytes[i]) << 8) | input.bytes[i+1]) / 100;
+ data.temperatures.push(temp);
+ }
+ return {
+ data,
+ }
+ }
+
+ function normalizeUplink(input) {
+ return {
+ data: input.data.temperatures.map((d) => {
+ return {
+ air: {
+ temperature: d
+ }
+ }
+ })
+ }
+ }
+ `
+ err := host.DecodeUplink(ctx, ids, nil, message, script)
+ a.So(err, should.BeNil)
+
+ decodedPayload, err := gogoproto.Map(message.DecodedPayload)
+ a.So(err, should.BeNil)
+ a.So(decodedPayload, should.Resemble, map[string]interface{}{
+ "temperatures": []interface{}{-21.3, -20.88},
+ })
+
+ a.So(message.NormalizedPayload, should.HaveLength, 2)
+ measurements, err := normalizedpayload.Parse(message.NormalizedPayload)
+ a.So(err, should.BeNil)
+ a.So(measurements, should.Resemble, []normalizedpayload.Measurement{
+ {
+ Air: &normalizedpayload.Air{
+ Temperature: float64Ptr(-21.3),
+ },
+ },
+ {
+ Air: &normalizedpayload.Air{
+ Temperature: float64Ptr(-20.88),
+ },
+ },
+ })
+ }
+
+ // Return errors from decoder and ensure that the normalizer isn't called.
+ {
+ script := `
+ function decodeUplink(input) {
+ return {
+ errors: ["test error"]
+ }
+ }
+
+ function normalizeUplink(input) {
+ throw new Error("this should not be called")
+ }
+ `
+ err := host.DecodeUplink(ctx, ids, nil, message, script)
+ a.So(err, should.NotBeNil)
+ a.So(errors.IsAborted(err), should.BeTrue)
+ }
+
+ // Decode and normalize a single measurement with a normalizer error.
+ {
+ script := `
+ function decodeUplink(input) {
+ var data = {
+ temperature: (((input.bytes[0] & 0x80 ? input.bytes[0] - 0x100 : input.bytes[0]) << 8) | input.bytes[1]) / 100
+ }
+ var warnings = [];
+ if (data.temperature < -10) {
+ warnings.push("it's cold");
+ }
+ return {
+ data: data,
+ warnings: warnings
+ }
+ }
+
+ function normalizeUplink(input) {
+ return {
+ errors: ["test error"]
+ }
+ }
+ `
+ err := host.DecodeUplink(ctx, ids, nil, message, script)
+ a.So(err, should.NotBeNil)
+ a.So(errors.IsAborted(err), should.BeTrue)
+ }
+
+ // Decode and normalize a single measurement with out-of-range value.
+ {
+ message := &ttnpb.ApplicationUplink{
+ FPort: 4,
+ FrmPayload: []byte{0x80, 0x42}, // Temperature is -327.02 °C which is below absolute zero
+ }
+ //nolint:lll
+ script := `
+ function decodeUplink(input) {
+ return {
+ data: {
+ temperature: (((input.bytes[0] & 0x80 ? input.bytes[0] - 0x100 : input.bytes[0]) << 8) | input.bytes[1]) / 100
+ }
+ }
+ }
+
+ function normalizeUplink(input) {
+ return {
+ data: {
+ air: {
+ temperature: input.data.temperature
+ }
+ }
+ }
+ }
+ `
+ err := host.DecodeUplink(ctx, ids, nil, message, script)
+ a.So(err, should.NotBeNil)
+ a.So(errors.IsInvalidArgument(err), should.BeTrue)
}
// The Things Node example.
@@ -360,6 +549,7 @@ func TestDecodeUplink(t *testing.T) {
FPort: 4,
FrmPayload: []byte{0x0C, 0xB2, 0x04, 0x80, 0xF7, 0xAE},
}
+ //nolint:lll
script := `
function decodeUplink(input) {
var data = {};
@@ -444,6 +634,7 @@ func TestDecodeUplink(t *testing.T) {
}
func TestDecodeDownlink(t *testing.T) {
+ t.Parallel()
a := assertions.New(t)
ctx := test.Context()
diff --git a/pkg/messageprocessors/normalizedpayload/uplink.go b/pkg/messageprocessors/normalizedpayload/uplink.go
new file mode 100644
index 0000000000..a8fc4fe6fe
--- /dev/null
+++ b/pkg/messageprocessors/normalizedpayload/uplink.go
@@ -0,0 +1,263 @@
+// Copyright © 2022 The Things Network Foundation, The Things Industries B.V.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package normalizedpayload implements functionality for parsing and validating normalized payload.
+package normalizedpayload
+
+import (
+ "fmt"
+ "time"
+
+ pbtypes "github.com/gogo/protobuf/types"
+ "go.thethings.network/lorawan-stack/v3/pkg/errors"
+ "golang.org/x/exp/constraints"
+)
+
+// Air is an air measurement.
+type Air struct {
+ Temperature *float64
+ RelativeHumidity *float64
+ Pressure *float64
+}
+
+// Wind is a wind measurement.
+type Wind struct {
+ Speed *float64
+ Direction *float64
+}
+
+// Measurement is a measurement.
+type Measurement struct {
+ Time *time.Time
+ Air *Air
+ Wind *Wind
+}
+
+var (
+ errFieldType = errors.DefineInvalidArgument("field_type", "invalid field type of `{path}`")
+ errFieldMinimum = errors.DefineInvalidArgument(
+ "field_minimum",
+ "`{path}` should be equal or greater than `{minimum}`",
+ )
+ //nolint:unused
+ errFieldExclusiveMinimum = errors.DefineInvalidArgument(
+ "field_exclusive_minimum",
+ "`{path}` should be greater than `{minimum}`",
+ )
+ errFieldMaximum = errors.DefineInvalidArgument(
+ "field_maximum",
+ "`{path}` should be equal or less than `{maximum}`",
+ )
+ errFieldExclusiveMaximum = errors.DefineInvalidArgument(
+ "field_exclusive_maximum",
+ "`{path}` should be less than `{maximum}`",
+ )
+ errUnknownField = errors.DefineInvalidArgument("unknown_field", "unknown field `{path}`")
+)
+
+type fieldParser func(dst *Measurement, src *pbtypes.Value, path string) error
+
+// object validates that the path is a structure and sets the target to an empty value.
+func object[T any](selector func(*Measurement) **T) fieldParser {
+ return func(dst *Measurement, src *pbtypes.Value, path string) error {
+ _, ok := src.Kind.(*pbtypes.Value_StructValue)
+ if !ok {
+ return errFieldType.WithAttributes("path", path)
+ }
+ *selector(dst) = new(T)
+ return nil
+ }
+}
+
+type fieldValidator[T any] func(v T, path string) error
+
+func validate[T any](val T, validators []fieldValidator[T], path string) error {
+ for _, v := range validators {
+ if err := v(val, path); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// parseTime parses and validates the time. The input value must be RFC3339.
+func parseTime(selector func(dst *Measurement) **time.Time, vals ...fieldValidator[time.Time]) fieldParser {
+ return func(dst *Measurement, src *pbtypes.Value, path string) error {
+ val, ok := src.Kind.(*pbtypes.Value_StringValue)
+ if !ok {
+ return errFieldType.WithAttributes("path", path)
+ }
+ t, err := time.Parse(time.RFC3339Nano, val.StringValue)
+ if err != nil {
+ return err
+ }
+ if err := validate(t, vals, path); err != nil {
+ return err
+ }
+ *selector(dst) = &t
+ return nil
+ }
+}
+
+// parseNumber parses and validates a number.
+func parseNumber(selector func(dst *Measurement) **float64, vals ...fieldValidator[float64]) fieldParser {
+ return func(dst *Measurement, src *pbtypes.Value, path string) error {
+ val, ok := src.Kind.(*pbtypes.Value_NumberValue)
+ if !ok {
+ return errFieldType.WithAttributes("path", path)
+ }
+ n := val.NumberValue
+ if err := validate(n, vals, path); err != nil {
+ return err
+ }
+ *selector(dst) = &n
+ return nil
+ }
+}
+
+// minimum returns a field validator that checks the inclusive minimum.
+func minimum[T constraints.Ordered](min T) fieldValidator[T] {
+ return func(v T, path string) error {
+ if v < min {
+ return errFieldMinimum.WithAttributes(
+ "path", path,
+ "minimum", min,
+ )
+ }
+ return nil
+ }
+}
+
+// exclusiveMinimum returns a field validator that checks the exclusive minimum.
+//
+//nolint:unused,deadcode
+func exclusiveMinimum[T constraints.Ordered](min T) fieldValidator[T] {
+ return func(v T, path string) error {
+ if v <= min {
+ return errFieldExclusiveMinimum.WithAttributes(
+ "path", path,
+ "minimum", min,
+ )
+ }
+ return nil
+ }
+}
+
+// maximum returns a field validator that checks the inclusive maximum.
+func maximum[T constraints.Ordered](max T) fieldValidator[T] {
+ return func(v T, path string) error {
+ if v > max {
+ return errFieldMaximum.WithAttributes(
+ "path", path,
+ "maximum", max,
+ )
+ }
+ return nil
+ }
+}
+
+// exclusiveMaximum returns a field validator that checks the exclusive maximum.
+func exclusiveMaximum[T constraints.Ordered](max T) fieldValidator[T] {
+ return func(v T, path string) error {
+ if v >= max {
+ return errFieldExclusiveMaximum.WithAttributes(
+ "path", path,
+ "maximum", max,
+ )
+ }
+ return nil
+ }
+}
+
+var fieldParsers = map[string]fieldParser{
+ "time": parseTime(
+ func(dst *Measurement) **time.Time {
+ return &dst.Time
+ },
+ ),
+ "air": object(
+ func(dst *Measurement) **Air {
+ return &dst.Air
+ },
+ ),
+ "air.temperature": parseNumber(
+ func(dst *Measurement) **float64 {
+ return &dst.Air.Temperature
+ },
+ minimum(-273.15),
+ ),
+ "air.relativeHumidity": parseNumber(
+ func(dst *Measurement) **float64 {
+ return &dst.Air.RelativeHumidity
+ },
+ minimum(0.0),
+ maximum(100.0),
+ ),
+ "air.pressure": parseNumber(
+ func(dst *Measurement) **float64 {
+ return &dst.Air.Pressure
+ },
+ minimum(900.0),
+ maximum(1100.0),
+ ),
+ "wind": object(
+ func(dst *Measurement) **Wind {
+ return &dst.Wind
+ },
+ ),
+ "wind.speed": parseNumber(
+ func(dst *Measurement) **float64 {
+ return &dst.Wind.Speed
+ },
+ minimum(0.0),
+ ),
+ "wind.direction": parseNumber(
+ func(dst *Measurement) **float64 {
+ return &dst.Wind.Direction
+ },
+ minimum(0.0),
+ exclusiveMaximum(360.0),
+ ),
+}
+
+// Parse parses and validates the measurements.
+func Parse(measurements []*pbtypes.Struct) ([]Measurement, error) {
+ res := make([]Measurement, len(measurements))
+ for i, src := range measurements {
+ err := parse(&res[i], src, "")
+ if err != nil {
+ return nil, err
+ }
+ }
+ return res, nil
+}
+
+func parse(dst *Measurement, src *pbtypes.Struct, prefix string) error {
+ for k, v := range src.GetFields() {
+ path := fmt.Sprintf("%s%s", prefix, k)
+ parser, ok := fieldParsers[path]
+ if !ok {
+ return errUnknownField.WithAttributes("path", path)
+ }
+ if err := parser(dst, v, path); err != nil {
+ return err
+ }
+ if s, ok := v.Kind.(*pbtypes.Value_StructValue); ok {
+ if err := parse(dst, s.StructValue, path+"."); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
diff --git a/pkg/messageprocessors/normalizedpayload/uplink_test.go b/pkg/messageprocessors/normalizedpayload/uplink_test.go
new file mode 100644
index 0000000000..5f847f46ad
--- /dev/null
+++ b/pkg/messageprocessors/normalizedpayload/uplink_test.go
@@ -0,0 +1,223 @@
+// Copyright © 2022 The Things Network Foundation, The Things Industries B.V.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package normalizedpayload_test
+
+import (
+ "testing"
+ "time"
+
+ pbtypes "github.com/gogo/protobuf/types"
+ "github.com/smartystreets/assertions"
+ "go.thethings.network/lorawan-stack/v3/pkg/errors"
+ "go.thethings.network/lorawan-stack/v3/pkg/messageprocessors/normalizedpayload"
+ "go.thethings.network/lorawan-stack/v3/pkg/util/test/assertions/should"
+)
+
+func timePtr(t time.Time) *time.Time {
+ return &t
+}
+
+func float64Ptr(f float64) *float64 {
+ return &f
+}
+
+func TestUplink(t *testing.T) {
+ t.Parallel()
+
+ for _, tc := range []struct {
+ name string
+ normalizedPayload []*pbtypes.Struct
+ expected []normalizedpayload.Measurement
+ errorAssertion func(error) bool
+ }{
+ {
+ name: "single timestamp",
+ normalizedPayload: []*pbtypes.Struct{
+ {
+ Fields: map[string]*pbtypes.Value{
+ "time": {
+ Kind: &pbtypes.Value_StringValue{
+ StringValue: "2022-08-23T17:13:42Z",
+ },
+ },
+ },
+ },
+ },
+ expected: []normalizedpayload.Measurement{
+ {
+ Time: timePtr(time.Date(2022, 8, 23, 17, 13, 42, 0, time.UTC)),
+ },
+ },
+ },
+ {
+ name: "two air temperatures",
+ normalizedPayload: []*pbtypes.Struct{
+ {
+ Fields: map[string]*pbtypes.Value{
+ "air": {
+ Kind: &pbtypes.Value_StructValue{
+ StructValue: &pbtypes.Struct{
+ Fields: map[string]*pbtypes.Value{
+ "temperature": {
+ Kind: &pbtypes.Value_NumberValue{
+ NumberValue: 20.42,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ Fields: map[string]*pbtypes.Value{
+ "air": {
+ Kind: &pbtypes.Value_StructValue{
+ StructValue: &pbtypes.Struct{
+ Fields: map[string]*pbtypes.Value{
+ "temperature": {
+ Kind: &pbtypes.Value_NumberValue{
+ NumberValue: 19.61,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ expected: []normalizedpayload.Measurement{
+ {
+ Air: &normalizedpayload.Air{
+ Temperature: float64Ptr(20.42),
+ },
+ },
+ {
+ Air: &normalizedpayload.Air{
+ Temperature: float64Ptr(19.61),
+ },
+ },
+ },
+ },
+ {
+ name: "below absolute zero",
+ normalizedPayload: []*pbtypes.Struct{
+ {
+ Fields: map[string]*pbtypes.Value{
+ "air": {
+ Kind: &pbtypes.Value_StructValue{
+ StructValue: &pbtypes.Struct{
+ Fields: map[string]*pbtypes.Value{
+ "temperature": {
+ Kind: &pbtypes.Value_NumberValue{
+ NumberValue: -300,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ errorAssertion: errors.IsInvalidArgument,
+ },
+ {
+ name: "invalid direction",
+ normalizedPayload: []*pbtypes.Struct{
+ {
+ Fields: map[string]*pbtypes.Value{
+ "wind": {
+ Kind: &pbtypes.Value_StructValue{
+ StructValue: &pbtypes.Struct{
+ Fields: map[string]*pbtypes.Value{
+ "direction": {
+ Kind: &pbtypes.Value_NumberValue{
+ NumberValue: 360, // this is 0 degrees
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ errorAssertion: errors.IsInvalidArgument,
+ },
+ {
+ name: "invalid type",
+ normalizedPayload: []*pbtypes.Struct{
+ {
+ Fields: map[string]*pbtypes.Value{
+ "air": {
+ Kind: &pbtypes.Value_StructValue{
+ StructValue: &pbtypes.Struct{
+ Fields: map[string]*pbtypes.Value{
+ "temperature": {
+ Kind: &pbtypes.Value_StringValue{
+ StringValue: "test",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ errorAssertion: errors.IsInvalidArgument,
+ },
+ {
+ name: "unknown field",
+ normalizedPayload: []*pbtypes.Struct{
+ {
+ Fields: map[string]*pbtypes.Value{
+ "air": {
+ Kind: &pbtypes.Value_StructValue{
+ StructValue: &pbtypes.Struct{
+ Fields: map[string]*pbtypes.Value{
+ "unknown": {
+ Kind: &pbtypes.Value_StringValue{
+ StringValue: "test",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ errorAssertion: errors.IsInvalidArgument,
+ },
+ } {
+ tc := tc
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+ a := assertions.New(t)
+
+ measurements, err := normalizedpayload.Parse(tc.normalizedPayload)
+ if tc.errorAssertion != nil {
+ a.So(err, should.NotBeNil)
+ a.So(tc.errorAssertion(err), should.BeTrue)
+ } else {
+ a.So(err, should.BeNil)
+ a.So(measurements, should.Resemble, tc.expected)
+ }
+ })
+ }
+}
diff --git a/pkg/ttnpb/applicationserver.pb.paths.fm.go b/pkg/ttnpb/applicationserver.pb.paths.fm.go
index 4a54879d9a..a99451c177 100644
--- a/pkg/ttnpb/applicationserver.pb.paths.fm.go
+++ b/pkg/ttnpb/applicationserver.pb.paths.fm.go
@@ -187,6 +187,8 @@ var DecodeUplinkRequestFieldPathsNested = []string{
"uplink.network_ids.net_id",
"uplink.network_ids.tenant_address",
"uplink.network_ids.tenant_id",
+ "uplink.normalized_payload",
+ "uplink.normalized_payload_warnings",
"uplink.received_at",
"uplink.rx_metadata",
"uplink.session_key_id",
@@ -260,6 +262,8 @@ var DecodeUplinkResponseFieldPathsNested = []string{
"uplink.network_ids.net_id",
"uplink.network_ids.tenant_address",
"uplink.network_ids.tenant_id",
+ "uplink.normalized_payload",
+ "uplink.normalized_payload_warnings",
"uplink.received_at",
"uplink.rx_metadata",
"uplink.session_key_id",
diff --git a/pkg/ttnpb/applicationserver_pubsub.pb.go b/pkg/ttnpb/applicationserver_pubsub.pb.go
index e091fe77c5..e6418da20f 100644
--- a/pkg/ttnpb/applicationserver_pubsub.pb.go
+++ b/pkg/ttnpb/applicationserver_pubsub.pb.go
@@ -127,6 +127,7 @@ type ApplicationPubSub struct {
// The topic to which the Application Server subscribes for downlink queue replace operations.
DownlinkReplace *ApplicationPubSub_Message `protobuf:"bytes,8,opt,name=downlink_replace,json=downlinkReplace,proto3" json:"downlink_replace,omitempty"`
UplinkMessage *ApplicationPubSub_Message `protobuf:"bytes,9,opt,name=uplink_message,json=uplinkMessage,proto3" json:"uplink_message,omitempty"`
+ UplinkNormalized *ApplicationPubSub_Message `protobuf:"bytes,20,opt,name=uplink_normalized,json=uplinkNormalized,proto3" json:"uplink_normalized,omitempty"`
JoinAccept *ApplicationPubSub_Message `protobuf:"bytes,10,opt,name=join_accept,json=joinAccept,proto3" json:"join_accept,omitempty"`
DownlinkAck *ApplicationPubSub_Message `protobuf:"bytes,11,opt,name=downlink_ack,json=downlinkAck,proto3" json:"downlink_ack,omitempty"`
DownlinkNack *ApplicationPubSub_Message `protobuf:"bytes,12,opt,name=downlink_nack,json=downlinkNack,proto3" json:"downlink_nack,omitempty"`
@@ -267,6 +268,13 @@ func (m *ApplicationPubSub) GetUplinkMessage() *ApplicationPubSub_Message {
return nil
}
+func (m *ApplicationPubSub) GetUplinkNormalized() *ApplicationPubSub_Message {
+ if m != nil {
+ return m.UplinkNormalized
+ }
+ return nil
+}
+
func (m *ApplicationPubSub) GetJoinAccept() *ApplicationPubSub_Message {
if m != nil {
return m.JoinAccept
@@ -1061,145 +1069,146 @@ func init() {
}
var fileDescriptor_1dce56ec18597200 = []byte{
- // 2194 bytes of a gzipped FileDescriptorProto
- 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x59, 0x41, 0x6f, 0xdb, 0xc8,
- 0x15, 0xce, 0x58, 0xb1, 0x6c, 0x3d, 0xc9, 0xb6, 0x32, 0xbb, 0x6d, 0x68, 0x65, 0x93, 0x75, 0xb5,
- 0xc6, 0xae, 0xec, 0x84, 0x52, 0x22, 0x77, 0xb7, 0x1b, 0x05, 0x45, 0x22, 0xda, 0x4e, 0xec, 0x26,
- 0x71, 0x62, 0x4a, 0x8b, 0x6d, 0x12, 0x27, 0xc4, 0x48, 0x1c, 0xcb, 0x8c, 0x29, 0x92, 0xe1, 0x0c,
- 0xed, 0x78, 0x93, 0x00, 0xc1, 0x5e, 0x5a, 0xf4, 0x50, 0x2c, 0xd0, 0x43, 0x51, 0x14, 0x45, 0x81,
- 0x16, 0x68, 0x81, 0x1c, 0x8b, 0x9e, 0xbb, 0x3d, 0x16, 0x05, 0x8a, 0x1e, 0xfa, 0x13, 0x5a, 0x14,
- 0x68, 0x2f, 0x05, 0xf6, 0x54, 0xb8, 0x40, 0x51, 0x70, 0x48, 0x8a, 0xb4, 0x95, 0x8d, 0x2d, 0x07,
- 0x3d, 0xec, 0x25, 0x79, 0xc3, 0xf7, 0xde, 0x37, 0xdf, 0x7b, 0xf3, 0x34, 0x6f, 0x66, 0x0c, 0xe7,
- 0x4d, 0xdb, 0x25, 0xdb, 0xc4, 0x92, 0x19, 0x27, 0xed, 0xcd, 0x0a, 0x71, 0x8c, 0x0a, 0x71, 0x1c,
- 0xd3, 0x68, 0x13, 0x6e, 0xd8, 0x16, 0xa3, 0xee, 0x16, 0x75, 0x35, 0xc7, 0x6b, 0x31, 0xaf, 0x55,
- 0x76, 0x5c, 0x9b, 0xdb, 0x78, 0x9c, 0x73, 0xab, 0x1c, 0x7a, 0x95, 0xb7, 0xe6, 0x0a, 0x0b, 0x1d,
- 0x83, 0x6f, 0x78, 0xad, 0x72, 0xdb, 0xee, 0x56, 0x9a, 0x1b, 0xb4, 0xb9, 0x61, 0x58, 0x1d, 0xb6,
- 0x6c, 0xe9, 0x1e, 0xe3, 0xae, 0x41, 0x59, 0x45, 0x78, 0xb5, 0xe5, 0x0e, 0xb5, 0xe4, 0x8e, 0x2d,
- 0xaf, 0x9b, 0xa4, 0xc3, 0x2a, 0xc4, 0xb2, 0x6c, 0x1e, 0xcc, 0x10, 0xa0, 0x16, 0xea, 0x09, 0x14,
- 0x6a, 0x6d, 0xd9, 0x3b, 0x8e, 0x6b, 0x3f, 0xde, 0x49, 0x3a, 0x6f, 0x11, 0xd3, 0xd0, 0x09, 0xa7,
- 0x95, 0x3e, 0x21, 0x84, 0x90, 0x13, 0x10, 0x1d, 0xbb, 0x63, 0x07, 0xce, 0x2d, 0x6f, 0x5d, 0x8c,
- 0xc4, 0x40, 0x48, 0xa1, 0xf9, 0xfc, 0x40, 0xbc, 0x1f, 0x32, 0xdb, 0x7a, 0x09, 0xed, 0xb7, 0x3a,
- 0xb6, 0xdd, 0x31, 0x69, 0x90, 0xb7, 0x3e, 0xed, 0x99, 0x50, 0xdb, 0x23, 0xa2, 0x7b, 0xae, 0x30,
- 0x08, 0xf5, 0xa7, 0xf6, 0xeb, 0x69, 0xd7, 0xe1, 0x3b, 0xa1, 0x72, 0x6a, 0xbf, 0x72, 0xdd, 0xa0,
- 0xa6, 0xae, 0x75, 0x09, 0xdb, 0x0c, 0x2d, 0xde, 0xde, 0x6f, 0xc1, 0x8d, 0x2e, 0x65, 0x9c, 0x74,
- 0x9d, 0xd0, 0xe0, 0x9d, 0xfe, 0xc5, 0x35, 0x74, 0x6a, 0x71, 0x63, 0xdd, 0xa0, 0x6e, 0x48, 0xb2,
- 0xf8, 0x47, 0x04, 0x6f, 0xd5, 0xe3, 0x25, 0xbf, 0xed, 0xb5, 0x1a, 0x5e, 0x6b, 0x39, 0x36, 0xc3,
- 0x77, 0x60, 0x22, 0x51, 0x12, 0x9a, 0xa1, 0x33, 0x09, 0x4d, 0xa1, 0x52, 0xb6, 0xfa, 0x6e, 0x79,
- 0x6f, 0x29, 0x94, 0x13, 0x30, 0x09, 0x00, 0x65, 0x74, 0x57, 0x19, 0xfe, 0x01, 0x1a, 0xca, 0x23,
- 0x75, 0x9c, 0x24, 0x2d, 0x18, 0x5e, 0x04, 0x70, 0xbc, 0x96, 0xc6, 0xbc, 0x96, 0x66, 0xe8, 0xd2,
- 0xd0, 0x14, 0x2a, 0x65, 0x94, 0xf7, 0x76, 0x95, 0x69, 0xb7, 0x28, 0x4d, 0x57, 0xcf, 0x3c, 0xb8,
- 0x47, 0xe4, 0x4f, 0xce, 0xcb, 0x17, 0xef, 0x97, 0x2e, 0xd7, 0xee, 0xc9, 0xf7, 0x2f, 0x47, 0xc3,
- 0x99, 0x27, 0xd5, 0x73, 0xcf, 0xa6, 0xd5, 0x51, 0x27, 0xa4, 0x5a, 0x1b, 0xfd, 0xe2, 0xc5, 0xe4,
- 0xf1, 0xd1, 0x63, 0x79, 0x54, 0xfc, 0xc7, 0x14, 0x9c, 0xe8, 0x0b, 0x06, 0xdf, 0x86, 0x54, 0xcc,
- 0xfa, 0xdc, 0x2b, 0x58, 0xf7, 0x05, 0xaf, 0xe4, 0x23, 0xee, 0xc1, 0x14, 0x25, 0xa4, 0xfa, 0x50,
- 0x78, 0x1e, 0xa0, 0xed, 0x52, 0xc2, 0xa9, 0xae, 0x11, 0x2e, 0x88, 0x67, 0xab, 0x85, 0x72, 0xb0,
- 0x1e, 0xe5, 0x68, 0x3d, 0xca, 0xcd, 0x68, 0x3d, 0x94, 0x88, 0xe1, 0x31, 0x35, 0x13, 0xfa, 0xd5,
- 0xb9, 0x0f, 0xe2, 0x39, 0x7a, 0x04, 0x92, 0x1a, 0x04, 0x24, 0xf4, 0xab, 0x73, 0x7c, 0x19, 0xd2,
- 0xeb, 0xb6, 0xdb, 0x25, 0x5c, 0x3a, 0x9e, 0x4c, 0xdf, 0x9b, 0x07, 0xa6, 0x2f, 0x74, 0xc3, 0x0b,
- 0x70, 0xdc, 0x22, 0x9c, 0x49, 0x27, 0xc4, 0xfc, 0xe5, 0x03, 0xb3, 0x53, 0x5e, 0xa9, 0x37, 0x1b,
- 0xb7, 0x5d, 0x7b, 0xcb, 0xd0, 0xa9, 0xbb, 0x74, 0x4c, 0x15, 0xde, 0x3e, 0x4a, 0xf7, 0x11, 0xe7,
- 0xd2, 0xe4, 0x61, 0x51, 0x6e, 0xae, 0x36, 0x9b, 0x49, 0x14, 0xdf, 0x1b, 0x5f, 0x87, 0x11, 0xb2,
- 0xcd, 0x34, 0xc3, 0xe6, 0x12, 0x15, 0x40, 0xe7, 0x0f, 0x06, 0xaa, 0x7f, 0xdc, 0x58, 0xb6, 0x93,
- 0x50, 0x69, 0xb2, 0xcd, 0x96, 0x6d, 0x8e, 0xdf, 0x05, 0x68, 0x11, 0x46, 0x35, 0x6e, 0x3b, 0x46,
- 0x5b, 0x4a, 0x8b, 0xec, 0x8c, 0xec, 0x2a, 0xc7, 0xdd, 0x21, 0x49, 0x57, 0x33, 0xbe, 0xaa, 0xe9,
- 0x6b, 0xf0, 0x0a, 0x8c, 0xe9, 0xf6, 0xb6, 0x65, 0x1a, 0xd6, 0xa6, 0xe6, 0x78, 0x6c, 0x43, 0x1a,
- 0x11, 0x53, 0xcf, 0x1c, 0x22, 0x06, 0xca, 0x18, 0xe9, 0x50, 0x35, 0x17, 0xf9, 0xdf, 0xf6, 0xd8,
- 0x06, 0x6e, 0x42, 0xbe, 0x87, 0xe7, 0x52, 0xc7, 0x24, 0x6d, 0x2a, 0x8d, 0x0e, 0x0a, 0x39, 0x11,
- 0x41, 0xa8, 0x01, 0x02, 0xbe, 0x0d, 0xe3, 0x9e, 0x23, 0x30, 0xbb, 0x81, 0x89, 0x94, 0x19, 0x14,
- 0x73, 0x2c, 0x00, 0x08, 0x87, 0xf8, 0x3b, 0x90, 0x7d, 0x68, 0x1b, 0x96, 0x46, 0xda, 0x6d, 0xea,
- 0x70, 0x09, 0x06, 0x85, 0x03, 0xdf, 0xbb, 0x2e, 0x9c, 0xf1, 0x0d, 0xe8, 0xe5, 0x40, 0x23, 0xed,
- 0x4d, 0x29, 0x3b, 0x28, 0x58, 0x36, 0x72, 0xaf, 0xb7, 0x37, 0xf7, 0xac, 0x88, 0xe5, 0xc3, 0xe5,
- 0x8e, 0xbc, 0x22, 0x2b, 0x64, 0x1f, 0x1e, 0xa3, 0x16, 0x97, 0xc6, 0x8e, 0x8c, 0xd7, 0xa0, 0x16,
- 0xc7, 0x2a, 0xf4, 0x96, 0x47, 0x5b, 0x27, 0x86, 0x49, 0x75, 0x69, 0x7c, 0x50, 0xc4, 0xf1, 0x08,
- 0xe1, 0xaa, 0x00, 0xd8, 0x83, 0xf9, 0xc8, 0xa3, 0x1e, 0xd5, 0xa5, 0x89, 0x23, 0x63, 0xae, 0x0a,
- 0x00, 0xdc, 0x81, 0xc2, 0x5e, 0x4c, 0xcd, 0xb0, 0xa2, 0xa6, 0xa9, 0x4b, 0x6f, 0x0c, 0x0a, 0x2f,
- 0xed, 0x81, 0x5f, 0x8e, 0xa1, 0x7c, 0xf2, 0xa6, 0x1d, 0xf6, 0x07, 0x66, 0x9b, 0x5b, 0x54, 0x97,
- 0xf2, 0x03, 0x93, 0x8f, 0x10, 0x1a, 0x02, 0xc0, 0x2f, 0x29, 0xff, 0xf8, 0x61, 0xb4, 0xa9, 0xa6,
- 0x13, 0x4e, 0x24, 0x3c, 0x70, 0x49, 0x85, 0xee, 0x0b, 0x84, 0x93, 0x42, 0x1d, 0x72, 0xc9, 0x7d,
- 0x0b, 0xbf, 0x07, 0x10, 0x1e, 0x6e, 0x3c, 0xd7, 0x14, 0x9d, 0x21, 0x23, 0xfa, 0x94, 0x9b, 0xfa,
- 0x3e, 0x42, 0x6a, 0x26, 0xd0, 0x7d, 0xe4, 0x9a, 0x61, 0x6f, 0x41, 0x79, 0x54, 0xf8, 0x6d, 0x06,
- 0x72, 0xc9, 0x5d, 0xeb, 0xd0, 0x18, 0x78, 0x1a, 0x32, 0x6d, 0xd3, 0xa0, 0x16, 0x8f, 0xbb, 0x5c,
- 0xb8, 0x11, 0x9d, 0x54, 0x47, 0x03, 0xcd, 0xb2, 0x8e, 0xdf, 0x81, 0x51, 0x8f, 0x51, 0xd7, 0x22,
- 0x5d, 0x2a, 0x9a, 0x41, 0x62, 0xb7, 0xea, 0x29, 0x7c, 0x23, 0x87, 0x30, 0xb6, 0x6d, 0xbb, 0x7a,
- 0xb8, 0xe1, 0xc7, 0x46, 0x91, 0x02, 0x7f, 0x0c, 0x63, 0xcc, 0x6b, 0xb1, 0xb6, 0x6b, 0xb4, 0xa8,
- 0xf6, 0xc8, 0x66, 0xd2, 0xf0, 0x14, 0x2a, 0x8d, 0x57, 0xab, 0x83, 0xed, 0xca, 0xe5, 0x55, 0xbb,
- 0xa1, 0xe6, 0x7a, 0x40, 0xab, 0x36, 0xc3, 0x0d, 0xc8, 0x3a, 0x5e, 0xcb, 0x34, 0xd8, 0x86, 0x80,
- 0x4d, 0x1f, 0x19, 0x16, 0x42, 0x18, 0x1f, 0xf4, 0x24, 0x8c, 0x78, 0xfe, 0x36, 0x6d, 0x32, 0xb1,
- 0xf3, 0x8e, 0xaa, 0x69, 0x8f, 0xd1, 0xa6, 0xc9, 0xf0, 0xef, 0x10, 0xa4, 0xb9, 0xc9, 0xb4, 0x36,
- 0x11, 0xfb, 0x67, 0x4e, 0xf9, 0x15, 0xda, 0x55, 0x86, 0x3f, 0x49, 0x49, 0xcf, 0xaf, 0x7c, 0xf1,
- 0x62, 0xf2, 0x27, 0xa8, 0xb0, 0x72, 0x84, 0xa3, 0xa7, 0xf8, 0xd7, 0x31, 0xbd, 0x8e, 0x61, 0x95,
- 0x57, 0xe8, 0xf6, 0x12, 0x7d, 0xac, 0xec, 0x70, 0xca, 0xae, 0x9a, 0xa4, 0x53, 0xbc, 0xf6, 0x9a,
- 0x78, 0xd7, 0x28, 0x17, 0x60, 0xea, 0x30, 0x37, 0xd9, 0x3c, 0xc1, 0x7f, 0x46, 0x30, 0x21, 0x02,
- 0x08, 0x16, 0xbf, 0x4d, 0x5d, 0x2e, 0x76, 0xed, 0xaf, 0x50, 0x24, 0x63, 0x7e, 0x24, 0x82, 0xfe,
- 0x3c, 0x75, 0x39, 0xfe, 0x13, 0x82, 0xf1, 0x44, 0x44, 0x9b, 0x74, 0x47, 0xf4, 0x8d, 0xaf, 0x50,
- 0x40, 0xb9, 0x5e, 0x40, 0xd7, 0xe9, 0x0e, 0xfe, 0x08, 0x46, 0x36, 0x28, 0xd1, 0xa9, 0xcb, 0xa4,
- 0xec, 0x54, 0xaa, 0x94, 0xad, 0x5e, 0x1a, 0xb0, 0x98, 0x97, 0x02, 0xef, 0x45, 0x8b, 0xbb, 0x3b,
- 0x6a, 0x84, 0x55, 0xa8, 0x41, 0x2e, 0xa9, 0xc0, 0x79, 0x48, 0xf9, 0xa9, 0x12, 0x5b, 0x84, 0xea,
- 0x8b, 0xf8, 0x4d, 0x18, 0xde, 0x22, 0xa6, 0x47, 0x83, 0xed, 0x40, 0x0d, 0x06, 0xb5, 0xa1, 0x0f,
- 0x51, 0x71, 0x01, 0x52, 0xab, 0x76, 0x03, 0xe7, 0x21, 0x57, 0x6f, 0x6a, 0x37, 0x6f, 0x35, 0x9a,
- 0xda, 0xad, 0x95, 0xf9, 0xc5, 0xfc, 0x31, 0x7c, 0x02, 0xc6, 0xea, 0x4d, 0xed, 0xc6, 0x62, 0x3d,
- 0xfa, 0x84, 0x7c, 0xa3, 0xc5, 0xef, 0xd6, 0xe7, 0x9b, 0x37, 0xee, 0x04, 0x5f, 0x86, 0x0a, 0xe9,
- 0x7f, 0xbe, 0x98, 0x1c, 0x92, 0x50, 0x62, 0xdb, 0xfa, 0x1e, 0xc0, 0xf8, 0xde, 0x33, 0x12, 0xfe,
- 0xe9, 0x10, 0xa4, 0x5d, 0xda, 0x31, 0x6c, 0x2b, 0xdc, 0xb5, 0x3e, 0x1d, 0xda, 0x55, 0xfe, 0x8b,
- 0xdc, 0xff, 0x20, 0x15, 0xc8, 0xba, 0xcc, 0x6c, 0x8f, 0x6f, 0xc8, 0x17, 0xd4, 0x0c, 0x71, 0x64,
- 0x4a, 0x18, 0x97, 0x2f, 0xf8, 0x07, 0x77, 0xd9, 0xb2, 0x5d, 0xbe, 0xf1, 0xd2, 0x71, 0x55, 0x05,
- 0xe2, 0xf4, 0xdc, 0xc6, 0x23, 0x39, 0x61, 0x1b, 0x8f, 0xab, 0x6a, 0xae, 0x4d, 0xe4, 0x36, 0xb5,
- 0xb8, 0x4b, 0x4c, 0xf9, 0x82, 0x9a, 0xa3, 0x5e, 0x62, 0x04, 0xd4, 0x0b, 0x70, 0x43, 0xb9, 0x47,
- 0x85, 0x7a, 0xf2, 0x36, 0x15, 0x70, 0x3d, 0xb1, 0x1a, 0x8b, 0x73, 0x2a, 0x74, 0x69, 0x6c, 0xcc,
- 0x48, 0xc4, 0x3b, 0xe3, 0xb1, 0x3e, 0xb1, 0x2a, 0xc4, 0x08, 0x2d, 0x12, 0xab, 0x6a, 0x98, 0x12,
- 0x7c, 0x07, 0xc0, 0x3f, 0x12, 0x31, 0x26, 0xca, 0x3b, 0x38, 0xdb, 0xd7, 0x06, 0x3d, 0x87, 0x96,
- 0xeb, 0x02, 0xe2, 0x3a, 0xdd, 0x51, 0x33, 0x24, 0x12, 0xf1, 0x1a, 0x64, 0x09, 0x63, 0x5e, 0x97,
- 0x6a, 0xae, 0x6d, 0xd2, 0xf0, 0xc8, 0x7f, 0x69, 0x70, 0x6c, 0x81, 0xa1, 0xda, 0x26, 0x55, 0x81,
- 0xf4, 0x64, 0xfc, 0x4b, 0x04, 0x79, 0x6a, 0xe9, 0x8e, 0x6d, 0x58, 0x5c, 0x23, 0xba, 0xee, 0x52,
- 0xc6, 0xc2, 0x26, 0xf1, 0x78, 0x57, 0xf1, 0x5c, 0x26, 0x3d, 0x47, 0x55, 0xeb, 0x41, 0xa9, 0x54,
- 0xf2, 0xaf, 0x02, 0x75, 0xf9, 0xae, 0x7f, 0x1b, 0x78, 0x9a, 0x90, 0x63, 0x71, 0x4d, 0xbe, 0x3f,
- 0x9b, 0x50, 0xcc, 0xac, 0x95, 0x67, 0x66, 0x4b, 0xf7, 0xea, 0xf2, 0xdd, 0xf0, 0x0e, 0xf1, 0x34,
- 0x21, 0xc7, 0xa2, 0xf0, 0x8a, 0x15, 0x33, 0x4f, 0x67, 0xa6, 0xd5, 0x89, 0x88, 0x51, 0x3d, 0x20,
- 0x84, 0x35, 0x18, 0xd1, 0xe9, 0x3a, 0xf1, 0x4c, 0x2e, 0xda, 0x52, 0xb6, 0x3a, 0x3f, 0x70, 0xfc,
- 0x0b, 0x81, 0xff, 0xb2, 0xc5, 0x69, 0x27, 0xb8, 0x50, 0x2f, 0x1d, 0x53, 0x23, 0xd4, 0xc2, 0x6f,
- 0x10, 0x64, 0x7a, 0xd9, 0xc7, 0xdf, 0x82, 0xb1, 0x78, 0x35, 0xfd, 0xfe, 0x1b, 0x54, 0xfc, 0x1b,
- 0xbb, 0x4a, 0xde, 0x1d, 0xcf, 0xe7, 0xfd, 0x94, 0x8c, 0x3c, 0xb8, 0xb7, 0xb6, 0x7d, 0x7f, 0x76,
- 0x5a, 0xcd, 0xf6, 0x56, 0x6a, 0x59, 0xc7, 0x73, 0x70, 0x82, 0xd1, 0xb6, 0x4b, 0xb9, 0xb6, 0xaf,
- 0x1a, 0x7a, 0x2d, 0xb7, 0xa4, 0x4e, 0x04, 0x16, 0xf1, 0x6c, 0x32, 0x8c, 0x31, 0xca, 0x98, 0x7f,
- 0x0e, 0xe2, 0xf6, 0x26, 0xb5, 0xc2, 0x46, 0x1e, 0x9c, 0x0a, 0xa4, 0xe7, 0x43, 0x6a, 0x2e, 0x54,
- 0x37, 0x7d, 0x6d, 0xe2, 0x57, 0xfa, 0x77, 0x04, 0x10, 0x2f, 0x2b, 0xbe, 0x0e, 0x29, 0xe2, 0x46,
- 0xbf, 0xce, 0x8b, 0xbb, 0xca, 0x07, 0xee, 0x37, 0xab, 0xd5, 0x07, 0xc4, 0xb5, 0x6a, 0x64, 0x9b,
- 0xd5, 0x0c, 0xd2, 0xad, 0xd5, 0xee, 0xf9, 0x29, 0x7e, 0x72, 0xa1, 0xfa, 0xac, 0xe6, 0xd7, 0xd2,
- 0x5a, 0x25, 0x4e, 0xbc, 0x76, 0xf6, 0xdb, 0xe7, 0xca, 0x57, 0xe4, 0xfb, 0x67, 0xa7, 0x55, 0x1f,
- 0x05, 0x5f, 0x82, 0x2c, 0x7d, 0xcc, 0xfd, 0x03, 0x84, 0x19, 0x1f, 0x40, 0x0a, 0xbb, 0xca, 0x49,
- 0xf7, 0x6b, 0xd2, 0x1f, 0x32, 0xd5, 0xbc, 0x1f, 0xbe, 0x70, 0xa9, 0xad, 0x55, 0x64, 0x3f, 0x0f,
- 0x10, 0x99, 0x2f, 0xeb, 0x78, 0x01, 0xf2, 0x51, 0x44, 0xd1, 0xeb, 0x45, 0x58, 0xb7, 0x93, 0x7d,
- 0x57, 0xd5, 0x85, 0xd0, 0xc0, 0xcf, 0x8b, 0x70, 0x89, 0x3e, 0x24, 0x02, 0x25, 0x80, 0xfb, 0x97,
- 0x0f, 0xd7, 0x01, 0xc4, 0x1b, 0x85, 0x26, 0x4e, 0x3f, 0x41, 0xd8, 0xc5, 0x5d, 0xe5, 0x6d, 0xf7,
- 0xb4, 0xbf, 0x40, 0xd2, 0x83, 0x30, 0xb8, 0x7d, 0xb5, 0x36, 0xad, 0x66, 0x84, 0xd7, 0x0a, 0xe9,
- 0xd2, 0x78, 0x8a, 0x58, 0x52, 0x72, 0x00, 0x3a, 0x75, 0x4c, 0x7b, 0xa7, 0x4b, 0x2d, 0x5e, 0xa8,
- 0xc2, 0x48, 0x74, 0xf7, 0x39, 0x0d, 0xc3, 0xc1, 0xb5, 0x10, 0xed, 0x3d, 0x43, 0x05, 0x5f, 0x5f,
- 0x8a, 0x35, 0x01, 0xa3, 0x4e, 0xb4, 0x81, 0xa6, 0xfe, 0xad, 0xa0, 0xe2, 0x2a, 0xe0, 0xbe, 0x4a,
- 0x65, 0xf8, 0x12, 0x8c, 0x04, 0xcf, 0x65, 0x4c, 0x42, 0xa2, 0xa3, 0x7c, 0xe3, 0xc0, 0xf2, 0x56,
- 0x23, 0x8f, 0xe2, 0xaf, 0x11, 0x48, 0x7d, 0xea, 0xab, 0xe2, 0x9e, 0xce, 0xf0, 0x2d, 0x18, 0x09,
- 0xae, 0xec, 0x11, 0xf2, 0xfb, 0x07, 0x22, 0x87, 0xae, 0xe5, 0xf0, 0xff, 0xb0, 0x4b, 0x85, 0x28,
- 0x7e, 0x97, 0x4a, 0x2a, 0x06, 0xea, 0x52, 0xbf, 0x40, 0x70, 0xea, 0x1a, 0xe5, 0xfd, 0xb1, 0xd0,
- 0x47, 0x1e, 0x65, 0x1c, 0x2f, 0x1d, 0xfd, 0xc9, 0x25, 0x7e, 0x2e, 0x12, 0x4f, 0x2d, 0x17, 0x01,
- 0xe2, 0x97, 0xaf, 0x2f, 0x7d, 0x6a, 0xb9, 0xea, 0x9b, 0xdc, 0x24, 0x6c, 0x53, 0xcd, 0xac, 0x47,
- 0x62, 0xf1, 0x73, 0x04, 0xa7, 0x6f, 0x18, 0xac, 0x9f, 0x25, 0x8b, 0x68, 0xfe, 0x1f, 0xdf, 0xb6,
- 0x8e, 0xce, 0x3b, 0xf1, 0x9e, 0xf5, 0x33, 0x04, 0xa7, 0x1a, 0xaf, 0x48, 0xf3, 0x3c, 0xa4, 0x83,
- 0xda, 0x09, 0x69, 0x1f, 0x5c, 0x6c, 0x09, 0xc6, 0xa1, 0xeb, 0x6b, 0x30, 0xad, 0x7e, 0x9e, 0x86,
- 0xc9, 0x97, 0x90, 0xeb, 0x18, 0xcc, 0x2f, 0xa8, 0x87, 0x00, 0xd7, 0x28, 0x8f, 0xea, 0xf7, 0xeb,
- 0x7d, 0x90, 0x8b, 0x5d, 0x87, 0xef, 0x14, 0x4a, 0x87, 0x2d, 0xe3, 0x62, 0xe1, 0xd3, 0xbf, 0xfc,
- 0xed, 0x47, 0x43, 0x6f, 0x62, 0x5c, 0x21, 0xac, 0x12, 0x90, 0x97, 0xc3, 0x62, 0xc6, 0x3f, 0x47,
- 0x90, 0xba, 0x46, 0x39, 0x3e, 0xbb, 0x1f, 0xed, 0x15, 0x55, 0x5a, 0x38, 0x38, 0x5d, 0xc5, 0x25,
- 0x31, 0xa7, 0x82, 0xaf, 0xc4, 0x73, 0x56, 0x9e, 0x18, 0x3a, 0x2b, 0xef, 0xab, 0x9b, 0x7d, 0xe3,
- 0x67, 0x81, 0x51, 0xfc, 0xba, 0xf9, 0x0c, 0xff, 0x10, 0xc1, 0x71, 0xbf, 0x1a, 0xb1, 0xbc, 0x7f,
- 0xd6, 0x57, 0xd6, 0x68, 0xa1, 0x78, 0x20, 0x49, 0x56, 0x9c, 0x13, 0x2c, 0x65, 0x7c, 0x36, 0xc9,
- 0xf2, 0x00, 0x86, 0xf8, 0x5f, 0x08, 0x52, 0x8d, 0x97, 0xa5, 0xac, 0xf1, 0x7a, 0x29, 0xfb, 0x31,
- 0x12, 0x6c, 0x3e, 0x43, 0x85, 0x95, 0x24, 0x9d, 0xf0, 0xaf, 0x08, 0x87, 0xca, 0x5d, 0xc2, 0x36,
- 0x91, 0xc2, 0x1a, 0x9a, 0xbd, 0x7b, 0xa9, 0xf8, 0xc1, 0xd1, 0x40, 0x6b, 0x68, 0x16, 0x7f, 0x86,
- 0x20, 0xbd, 0x40, 0x4d, 0xca, 0x29, 0x1e, 0x68, 0x4f, 0x2a, 0x7c, 0x49, 0xed, 0x16, 0xaf, 0x88,
- 0x48, 0x6b, 0xb3, 0x1f, 0x0e, 0x90, 0x77, 0x41, 0x3a, 0x0a, 0x49, 0x79, 0xff, 0xf7, 0x7f, 0x3d,
- 0x83, 0xee, 0x56, 0x3a, 0x76, 0x99, 0x6f, 0x50, 0x2e, 0xae, 0x35, 0x65, 0x8b, 0xf2, 0x6d, 0xdb,
- 0xdd, 0xac, 0xec, 0x7d, 0xbe, 0xdf, 0x9a, 0xab, 0x38, 0x9b, 0x9d, 0x0a, 0xe7, 0x96, 0xd3, 0x6a,
- 0xa5, 0x05, 0x91, 0xb9, 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0x96, 0x7e, 0x6c, 0x88, 0xc0, 0x19,
- 0x00, 0x00,
+ // 2217 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x59, 0xcf, 0x6f, 0xdb, 0xc8,
+ 0x15, 0xce, 0x58, 0xb1, 0x6c, 0x3d, 0xc9, 0xb6, 0x32, 0x9b, 0x76, 0x69, 0x65, 0x93, 0xcd, 0x6a,
+ 0x8d, 0x5d, 0xd9, 0x09, 0xa5, 0x44, 0xee, 0x6e, 0x37, 0x0a, 0x8a, 0x44, 0xb4, 0x9d, 0xd8, 0x4d,
+ 0xe2, 0xc4, 0x94, 0xb6, 0xdb, 0x24, 0x4e, 0x88, 0x91, 0x38, 0x96, 0x19, 0x53, 0x24, 0xc3, 0x19,
+ 0xda, 0x71, 0x7e, 0x00, 0xc1, 0x5e, 0x5a, 0xf4, 0x50, 0x2c, 0xd0, 0x43, 0x51, 0x14, 0x45, 0x81,
+ 0x16, 0x68, 0x81, 0x1c, 0x8b, 0x9e, 0xbb, 0xed, 0xad, 0x28, 0x50, 0xf4, 0xd0, 0x3f, 0xa1, 0x45,
+ 0x0f, 0xbd, 0x14, 0xd8, 0x53, 0xe1, 0x02, 0x45, 0xc1, 0x21, 0x29, 0xd2, 0x56, 0x36, 0xb6, 0x1c,
+ 0xf4, 0xb0, 0x97, 0xe4, 0x0d, 0xdf, 0x7b, 0xdf, 0x7c, 0xef, 0xcd, 0xf3, 0xbc, 0x99, 0x11, 0x9c,
+ 0x33, 0x6d, 0x97, 0x6c, 0x11, 0x4b, 0x66, 0x9c, 0xb4, 0x37, 0x2a, 0xc4, 0x31, 0x2a, 0xc4, 0x71,
+ 0x4c, 0xa3, 0x4d, 0xb8, 0x61, 0x5b, 0x8c, 0xba, 0x9b, 0xd4, 0xd5, 0x1c, 0xaf, 0xc5, 0xbc, 0x56,
+ 0xd9, 0x71, 0x6d, 0x6e, 0xe3, 0x71, 0xce, 0xad, 0x72, 0xe8, 0x55, 0xde, 0x9c, 0x2d, 0xcc, 0x77,
+ 0x0c, 0xbe, 0xee, 0xb5, 0xca, 0x6d, 0xbb, 0x5b, 0x69, 0xae, 0xd3, 0xe6, 0xba, 0x61, 0x75, 0xd8,
+ 0x92, 0xa5, 0x7b, 0x8c, 0xbb, 0x06, 0x65, 0x15, 0xe1, 0xd5, 0x96, 0x3b, 0xd4, 0x92, 0x3b, 0xb6,
+ 0xbc, 0x66, 0x92, 0x0e, 0xab, 0x10, 0xcb, 0xb2, 0x79, 0x30, 0x43, 0x80, 0x5a, 0xa8, 0x27, 0x50,
+ 0xa8, 0xb5, 0x69, 0x6f, 0x3b, 0xae, 0xfd, 0x68, 0x3b, 0xe9, 0xbc, 0x49, 0x4c, 0x43, 0x27, 0x9c,
+ 0x56, 0xfa, 0x84, 0x10, 0x42, 0x4e, 0x40, 0x74, 0xec, 0x8e, 0x1d, 0x38, 0xb7, 0xbc, 0x35, 0x31,
+ 0x12, 0x03, 0x21, 0x85, 0xe6, 0x73, 0x03, 0xf1, 0x7e, 0xc0, 0x6c, 0xeb, 0x25, 0xb4, 0xdf, 0xea,
+ 0xd8, 0x76, 0xc7, 0xa4, 0x41, 0xde, 0xfa, 0xb4, 0xa7, 0x42, 0x6d, 0x8f, 0x88, 0xee, 0xb9, 0xc2,
+ 0x20, 0xd4, 0x9f, 0xd8, 0xab, 0xa7, 0x5d, 0x87, 0x6f, 0x87, 0xca, 0xd3, 0x7b, 0x95, 0x6b, 0x06,
+ 0x35, 0x75, 0xad, 0x4b, 0xd8, 0x46, 0x68, 0xf1, 0xf6, 0x5e, 0x0b, 0x6e, 0x74, 0x29, 0xe3, 0xa4,
+ 0xeb, 0x84, 0x06, 0xef, 0xf6, 0x2f, 0xae, 0xa1, 0x53, 0x8b, 0x1b, 0x6b, 0x06, 0x75, 0x43, 0x92,
+ 0xc5, 0x3f, 0x21, 0x78, 0xab, 0x1e, 0x2f, 0xf9, 0x2d, 0xaf, 0xd5, 0xf0, 0x5a, 0x4b, 0xb1, 0x19,
+ 0xbe, 0x0d, 0x13, 0x89, 0x92, 0xd0, 0x0c, 0x9d, 0x49, 0xe8, 0x34, 0x2a, 0x65, 0xab, 0xef, 0x95,
+ 0x77, 0x97, 0x42, 0x39, 0x01, 0x93, 0x00, 0x50, 0x46, 0x77, 0x94, 0xe1, 0x1f, 0xa0, 0xa1, 0x3c,
+ 0x52, 0xc7, 0x49, 0xd2, 0x82, 0xe1, 0x05, 0x00, 0xc7, 0x6b, 0x69, 0xcc, 0x6b, 0x69, 0x86, 0x2e,
+ 0x0d, 0x9d, 0x46, 0xa5, 0x8c, 0xf2, 0xfe, 0x8e, 0x32, 0xe5, 0x16, 0xa5, 0xa9, 0xea, 0xa9, 0xfb,
+ 0x77, 0x89, 0xfc, 0xf8, 0x9c, 0x7c, 0xe1, 0x5e, 0xe9, 0x52, 0xed, 0xae, 0x7c, 0xef, 0x52, 0x34,
+ 0x9c, 0x7e, 0x52, 0x3d, 0xfb, 0x6c, 0x4a, 0x1d, 0x75, 0x42, 0xaa, 0xb5, 0xd1, 0x2f, 0x5e, 0x4c,
+ 0x1e, 0x1d, 0x3d, 0x92, 0x47, 0xc5, 0x3f, 0xbc, 0x03, 0xc7, 0xfa, 0x82, 0xc1, 0xb7, 0x20, 0x15,
+ 0xb3, 0x3e, 0xfb, 0x0a, 0xd6, 0x7d, 0xc1, 0x2b, 0xf9, 0x88, 0x7b, 0x30, 0x45, 0x09, 0xa9, 0x3e,
+ 0x14, 0x9e, 0x03, 0x68, 0xbb, 0x94, 0x70, 0xaa, 0x6b, 0x84, 0x0b, 0xe2, 0xd9, 0x6a, 0xa1, 0x1c,
+ 0xac, 0x47, 0x39, 0x5a, 0x8f, 0x72, 0x33, 0x5a, 0x0f, 0x25, 0x62, 0x78, 0x44, 0xcd, 0x84, 0x7e,
+ 0x75, 0xee, 0x83, 0x78, 0x8e, 0x1e, 0x81, 0xa4, 0x06, 0x01, 0x09, 0xfd, 0xea, 0x1c, 0x5f, 0x82,
+ 0xf4, 0x9a, 0xed, 0x76, 0x09, 0x97, 0x8e, 0x26, 0xd3, 0x77, 0x7c, 0xdf, 0xf4, 0x85, 0x6e, 0x78,
+ 0x1e, 0x8e, 0x5a, 0x84, 0x33, 0xe9, 0x98, 0x98, 0xbf, 0xbc, 0x6f, 0x76, 0xca, 0xcb, 0xf5, 0x66,
+ 0xe3, 0x96, 0x6b, 0x6f, 0x1a, 0x3a, 0x75, 0x17, 0x8f, 0xa8, 0xc2, 0xdb, 0x47, 0xe9, 0x3e, 0xe4,
+ 0x5c, 0x9a, 0x3c, 0x28, 0xca, 0x8d, 0x95, 0x66, 0x33, 0x89, 0xe2, 0x7b, 0xe3, 0x6b, 0x30, 0x42,
+ 0xb6, 0x98, 0x66, 0xd8, 0x5c, 0xa2, 0x02, 0xe8, 0xdc, 0xfe, 0x40, 0xf5, 0x4f, 0x1a, 0x4b, 0x76,
+ 0x12, 0x2a, 0x4d, 0xb6, 0xd8, 0x92, 0xcd, 0xf1, 0x7b, 0x00, 0x2d, 0xc2, 0xa8, 0xc6, 0x6d, 0xc7,
+ 0x68, 0x4b, 0x69, 0x91, 0x9d, 0x91, 0x1d, 0xe5, 0xa8, 0x3b, 0x24, 0xe9, 0x6a, 0xc6, 0x57, 0x35,
+ 0x7d, 0x0d, 0x5e, 0x86, 0x31, 0xdd, 0xde, 0xb2, 0x4c, 0xc3, 0xda, 0xd0, 0x1c, 0x8f, 0xad, 0x4b,
+ 0x23, 0x62, 0xea, 0xe9, 0x03, 0xc4, 0x40, 0x19, 0x23, 0x1d, 0xaa, 0xe6, 0x22, 0xff, 0x5b, 0x1e,
+ 0x5b, 0xc7, 0x4d, 0xc8, 0xf7, 0xf0, 0x5c, 0xea, 0x98, 0xa4, 0x4d, 0xa5, 0xd1, 0x41, 0x21, 0x27,
+ 0x22, 0x08, 0x35, 0x40, 0xc0, 0xb7, 0x60, 0xdc, 0x73, 0x04, 0x66, 0x37, 0x30, 0x91, 0x32, 0x83,
+ 0x62, 0x8e, 0x05, 0x00, 0xe1, 0x10, 0x7f, 0x07, 0x8e, 0x85, 0x88, 0x96, 0x5f, 0x09, 0xa6, 0xf1,
+ 0x98, 0xea, 0xd2, 0xf1, 0x41, 0x41, 0xf3, 0x01, 0xc6, 0x72, 0x0f, 0x02, 0x7f, 0x1b, 0xb2, 0x0f,
+ 0x6c, 0xc3, 0xd2, 0x48, 0xbb, 0x4d, 0x1d, 0x2e, 0xc1, 0xa0, 0x88, 0xe0, 0x7b, 0xd7, 0x85, 0x33,
+ 0xbe, 0x0e, 0xbd, 0xdc, 0x6a, 0xa4, 0xbd, 0x21, 0x65, 0x07, 0x05, 0xcb, 0x46, 0xee, 0xf5, 0xf6,
+ 0xc6, 0xae, 0x95, 0xb6, 0x7c, 0xb8, 0xdc, 0xa1, 0x57, 0x7a, 0x99, 0xec, 0xc1, 0x63, 0xd4, 0xe2,
+ 0xd2, 0xd8, 0xa1, 0xf1, 0x1a, 0xd4, 0xe2, 0x58, 0x85, 0xde, 0xb2, 0x6b, 0x6b, 0xc4, 0x30, 0xa9,
+ 0x2e, 0x8d, 0x0f, 0x8a, 0x38, 0x1e, 0x21, 0x5c, 0x11, 0x00, 0xbb, 0x30, 0x1f, 0x7a, 0xd4, 0xa3,
+ 0xba, 0x34, 0x71, 0x68, 0xcc, 0x15, 0x01, 0x80, 0x3b, 0x50, 0xd8, 0x8d, 0xa9, 0x19, 0x56, 0xd4,
+ 0x8c, 0x75, 0xe9, 0x8d, 0x41, 0xe1, 0xa5, 0x5d, 0xf0, 0x4b, 0x31, 0x94, 0x4f, 0xde, 0xb4, 0xc3,
+ 0xbe, 0xc3, 0x6c, 0x73, 0x93, 0xea, 0x52, 0x7e, 0x60, 0xf2, 0x11, 0x42, 0x43, 0x00, 0xf8, 0x25,
+ 0xe5, 0x1f, 0x6b, 0x8c, 0x36, 0xd5, 0x74, 0xc2, 0x89, 0x84, 0x07, 0x2e, 0xa9, 0xd0, 0x7d, 0x9e,
+ 0x70, 0x52, 0xa8, 0x43, 0x2e, 0xb9, 0x1f, 0xe2, 0xf7, 0x01, 0xc2, 0x43, 0x93, 0xe7, 0x9a, 0xa2,
+ 0xe3, 0x64, 0x44, 0xff, 0x73, 0x53, 0xdf, 0x47, 0x48, 0xcd, 0x04, 0xba, 0x8f, 0x5d, 0x33, 0xec,
+ 0x59, 0x28, 0x8f, 0x0a, 0xbf, 0xcd, 0x40, 0x2e, 0xb9, 0x1b, 0x1e, 0x18, 0x03, 0x4f, 0x41, 0xa6,
+ 0x6d, 0x1a, 0xd4, 0xe2, 0x71, 0xf7, 0x0c, 0x37, 0xb8, 0x37, 0xd5, 0xd1, 0x40, 0xb3, 0xa4, 0xe3,
+ 0x77, 0x61, 0xd4, 0x63, 0xd4, 0xb5, 0x48, 0x97, 0x8a, 0x26, 0x93, 0xd8, 0x05, 0x7b, 0x0a, 0xdf,
+ 0xc8, 0x21, 0x8c, 0x6d, 0xd9, 0xae, 0x1e, 0x36, 0x92, 0xd8, 0x28, 0x52, 0xe0, 0x4f, 0x60, 0x8c,
+ 0x79, 0x2d, 0xd6, 0x76, 0x8d, 0x16, 0xd5, 0x1e, 0xda, 0x4c, 0x1a, 0x3e, 0x8d, 0x4a, 0xe3, 0xd5,
+ 0xea, 0x60, 0xbb, 0x7d, 0x79, 0xc5, 0x6e, 0xa8, 0xb9, 0x1e, 0xd0, 0x8a, 0xcd, 0x70, 0x03, 0xb2,
+ 0x8e, 0xd7, 0x32, 0x0d, 0xb6, 0x2e, 0x60, 0xd3, 0x87, 0x86, 0x85, 0x10, 0xc6, 0x07, 0x7d, 0x13,
+ 0x46, 0x3c, 0x7f, 0xfb, 0x37, 0x99, 0xd8, 0xd1, 0x47, 0xd5, 0xb4, 0xc7, 0x68, 0xd3, 0x64, 0xf8,
+ 0x77, 0x08, 0xd2, 0xdc, 0x64, 0x5a, 0x9b, 0x88, 0x7d, 0x39, 0xa7, 0xfc, 0x0a, 0xed, 0x28, 0xc3,
+ 0x8f, 0x53, 0xd2, 0xf3, 0xcb, 0x5f, 0xbc, 0x98, 0xfc, 0x09, 0x2a, 0x2c, 0x1f, 0xe2, 0x48, 0x2b,
+ 0xfe, 0x75, 0x4c, 0xaf, 0x63, 0x58, 0xe5, 0x65, 0xba, 0xb5, 0x48, 0x1f, 0x29, 0xdb, 0x9c, 0xb2,
+ 0x2b, 0x26, 0xe9, 0x14, 0xaf, 0xbe, 0x26, 0xde, 0x55, 0xca, 0x05, 0x98, 0x3a, 0xcc, 0x4d, 0x36,
+ 0x47, 0xf0, 0x5f, 0x10, 0x4c, 0x88, 0x00, 0x82, 0xc5, 0x6f, 0x53, 0x97, 0x8b, 0x6e, 0xf0, 0x15,
+ 0x8a, 0x64, 0xcc, 0x8f, 0x44, 0xd0, 0x9f, 0xa3, 0x2e, 0xc7, 0x7f, 0x46, 0x30, 0x9e, 0x88, 0x68,
+ 0x83, 0x6e, 0x8b, 0xbe, 0xf1, 0x15, 0x0a, 0x28, 0xd7, 0x0b, 0xe8, 0x1a, 0xdd, 0xc6, 0x1f, 0xc3,
+ 0xc8, 0x3a, 0x25, 0x3a, 0x75, 0x99, 0x94, 0x3d, 0x9d, 0x2a, 0x65, 0xab, 0x17, 0x07, 0x2c, 0xe6,
+ 0xc5, 0xc0, 0x7b, 0xc1, 0xe2, 0xee, 0xb6, 0x1a, 0x61, 0x15, 0x6a, 0x90, 0x4b, 0x2a, 0x70, 0x1e,
+ 0x52, 0x7e, 0xaa, 0xc4, 0x16, 0xa1, 0xfa, 0x22, 0x3e, 0x0e, 0xc3, 0x9b, 0xc4, 0xf4, 0x68, 0xb0,
+ 0x1d, 0xa8, 0xc1, 0xa0, 0x36, 0xf4, 0x11, 0x2a, 0xce, 0x43, 0x6a, 0xc5, 0x6e, 0xe0, 0x3c, 0xe4,
+ 0xea, 0x4d, 0xed, 0xc6, 0xcd, 0x46, 0x53, 0xbb, 0xb9, 0x3c, 0xb7, 0x90, 0x3f, 0x82, 0x8f, 0xc1,
+ 0x58, 0xbd, 0xa9, 0x5d, 0x5f, 0xa8, 0x47, 0x9f, 0x90, 0x6f, 0xb4, 0xf0, 0xdd, 0xfa, 0x5c, 0xf3,
+ 0xfa, 0xed, 0xe0, 0xcb, 0x50, 0x21, 0xfd, 0xcf, 0x17, 0x93, 0x43, 0x12, 0x4a, 0x6c, 0x5b, 0xdf,
+ 0x03, 0x18, 0xdf, 0x7d, 0xf6, 0xc2, 0x3f, 0x1d, 0x82, 0xb4, 0x4b, 0x3b, 0x86, 0x6d, 0x85, 0xbb,
+ 0xd6, 0xa7, 0x43, 0x3b, 0xca, 0x7f, 0x91, 0xfb, 0x1f, 0xa4, 0x02, 0x59, 0x93, 0x99, 0xed, 0xf1,
+ 0x75, 0xf9, 0xbc, 0x9a, 0x21, 0x8e, 0x4c, 0x09, 0xe3, 0xf2, 0x79, 0xff, 0x42, 0x20, 0x5b, 0xb6,
+ 0xcb, 0xd7, 0x5f, 0x3a, 0xae, 0xaa, 0x40, 0x9c, 0x9e, 0xdb, 0x78, 0x24, 0x27, 0x6c, 0xe3, 0x71,
+ 0x55, 0xcd, 0xb5, 0x89, 0xdc, 0xa6, 0x16, 0x77, 0x89, 0x29, 0x9f, 0x57, 0x73, 0xd4, 0x4b, 0x8c,
+ 0x80, 0x7a, 0x01, 0x6e, 0x28, 0xf7, 0xa8, 0x50, 0x4f, 0xde, 0xa2, 0x02, 0xae, 0x27, 0x56, 0x63,
+ 0x71, 0x56, 0x85, 0x2e, 0x8d, 0x8d, 0x19, 0x89, 0x78, 0x67, 0x3c, 0xd6, 0x27, 0x56, 0x85, 0x18,
+ 0xa1, 0x45, 0x62, 0x55, 0x0d, 0x53, 0x82, 0x6f, 0x03, 0xf8, 0x47, 0x22, 0xc6, 0x44, 0x79, 0x07,
+ 0x77, 0x86, 0xda, 0xa0, 0xe7, 0xdb, 0x72, 0x5d, 0x40, 0x5c, 0xa3, 0xdb, 0x6a, 0x86, 0x44, 0x22,
+ 0x5e, 0x85, 0x2c, 0x61, 0xcc, 0xeb, 0x52, 0xcd, 0xb5, 0x4d, 0x1a, 0x5e, 0x25, 0x2e, 0x0e, 0x8e,
+ 0x2d, 0x30, 0x54, 0xdb, 0xa4, 0x2a, 0x90, 0x9e, 0x8c, 0x7f, 0x89, 0x20, 0x4f, 0x2d, 0xdd, 0xb1,
+ 0x0d, 0x8b, 0x6b, 0x44, 0xd7, 0x5d, 0xca, 0x58, 0xd8, 0x24, 0x1e, 0xed, 0x28, 0x9e, 0xcb, 0xa4,
+ 0xe7, 0xa8, 0x6a, 0xdd, 0x2f, 0x95, 0x4a, 0xfe, 0x15, 0xa3, 0x2e, 0xdf, 0xf1, 0x6f, 0x19, 0x4f,
+ 0x13, 0x72, 0x2c, 0xae, 0xca, 0xf7, 0x66, 0x12, 0x8a, 0xe9, 0xd5, 0xf2, 0xf4, 0x4c, 0xe9, 0x6e,
+ 0x5d, 0xbe, 0x13, 0xde, 0x4d, 0x9e, 0x26, 0xe4, 0x58, 0x14, 0x5e, 0xb1, 0x62, 0xfa, 0xe9, 0xf4,
+ 0x94, 0x3a, 0x11, 0x31, 0xaa, 0x07, 0x84, 0xb0, 0x06, 0x23, 0x3a, 0x5d, 0x23, 0x9e, 0xc9, 0x45,
+ 0x5b, 0xca, 0x56, 0xe7, 0x06, 0x8e, 0x7f, 0x3e, 0xf0, 0x5f, 0xb2, 0x38, 0xed, 0x04, 0x17, 0xf5,
+ 0xc5, 0x23, 0x6a, 0x84, 0x5a, 0xf8, 0x0d, 0x82, 0x4c, 0x2f, 0xfb, 0xf8, 0x9b, 0x30, 0x16, 0xaf,
+ 0xa6, 0xdf, 0x7f, 0x83, 0x8a, 0x7f, 0x63, 0x47, 0xc9, 0xbb, 0xe3, 0xf9, 0xbc, 0x9f, 0x92, 0x91,
+ 0xfb, 0x77, 0x57, 0xb7, 0xee, 0xcd, 0x4c, 0xa9, 0xd9, 0xde, 0x4a, 0x2d, 0xe9, 0x78, 0x16, 0x8e,
+ 0x31, 0xda, 0x76, 0x29, 0xd7, 0xf6, 0x54, 0x43, 0xaf, 0xe5, 0x96, 0xd4, 0x89, 0xc0, 0x22, 0x9e,
+ 0x4d, 0x86, 0x31, 0x46, 0x19, 0xf3, 0xcf, 0x41, 0xdc, 0xde, 0xa0, 0x56, 0xd8, 0xc8, 0x83, 0x53,
+ 0x81, 0xf4, 0x7c, 0x48, 0xcd, 0x85, 0xea, 0xa6, 0xaf, 0x4d, 0xfc, 0x95, 0xfe, 0x03, 0x01, 0xc4,
+ 0xcb, 0x8a, 0xaf, 0x41, 0x8a, 0xb8, 0xd1, 0x5f, 0xe7, 0x85, 0x1d, 0xe5, 0x43, 0xf7, 0x1b, 0xd5,
+ 0xea, 0x7d, 0xe2, 0x5a, 0x35, 0xb2, 0xc5, 0x6a, 0x06, 0xe9, 0xd6, 0x6a, 0x77, 0xfd, 0x14, 0x3f,
+ 0x39, 0x5f, 0x7d, 0x56, 0xf3, 0x6b, 0x69, 0xb5, 0x12, 0x27, 0x5e, 0x3b, 0xf3, 0xad, 0xb3, 0xe5,
+ 0xcb, 0xf2, 0xbd, 0x33, 0x53, 0xaa, 0x8f, 0x82, 0x2f, 0x42, 0x96, 0x3e, 0xe2, 0xfe, 0x01, 0xc2,
+ 0x8c, 0x0f, 0x20, 0x85, 0x1d, 0xe5, 0x4d, 0xf7, 0x6b, 0xd2, 0x1f, 0x33, 0xd5, 0xbc, 0x1f, 0xbe,
+ 0x70, 0xa9, 0xad, 0x56, 0x64, 0x3f, 0x0f, 0x10, 0x99, 0x2f, 0xe9, 0x78, 0x1e, 0xf2, 0x51, 0x44,
+ 0xd1, 0xab, 0x48, 0x58, 0xb7, 0x93, 0x7d, 0x57, 0xe0, 0xf9, 0xd0, 0xc0, 0xcf, 0x8b, 0x70, 0x89,
+ 0x3e, 0x24, 0x02, 0x25, 0x80, 0xfb, 0x97, 0x0f, 0xd7, 0x01, 0xc4, 0xdb, 0x87, 0x26, 0x4e, 0x3f,
+ 0x41, 0xd8, 0xc5, 0x1d, 0xe5, 0x6d, 0xf7, 0xa4, 0xbf, 0x40, 0xd2, 0xfd, 0x30, 0xb8, 0x3d, 0xb5,
+ 0x36, 0xa5, 0x66, 0x84, 0xd7, 0x32, 0xe9, 0xd2, 0x78, 0x8a, 0x58, 0x52, 0x72, 0x00, 0x3a, 0x75,
+ 0x4c, 0x7b, 0xbb, 0x4b, 0x2d, 0x5e, 0xa8, 0xc2, 0x48, 0x74, 0xa7, 0x3a, 0x09, 0xc3, 0xc1, 0x75,
+ 0x13, 0xed, 0x3e, 0x43, 0x05, 0x5f, 0x5f, 0x8a, 0x35, 0x01, 0xa3, 0x4e, 0xb4, 0x81, 0xa6, 0xfe,
+ 0xad, 0xa0, 0xe2, 0x0a, 0xe0, 0xbe, 0x4a, 0x65, 0xf8, 0x22, 0x8c, 0x04, 0xcf, 0x70, 0x4c, 0x42,
+ 0xa2, 0xa3, 0xbc, 0xb3, 0x6f, 0x79, 0xab, 0x91, 0x47, 0xf1, 0xd7, 0x08, 0xa4, 0x3e, 0xf5, 0x15,
+ 0x71, 0xff, 0x67, 0xf8, 0x26, 0x8c, 0x04, 0x4f, 0x01, 0x11, 0xf2, 0x07, 0xfb, 0x22, 0x87, 0xae,
+ 0xe5, 0xf0, 0xff, 0xb0, 0x4b, 0x85, 0x28, 0x7e, 0x97, 0x4a, 0x2a, 0x06, 0xea, 0x52, 0xbf, 0x40,
+ 0x70, 0xe2, 0x2a, 0xe5, 0xfd, 0xb1, 0xd0, 0x87, 0x1e, 0x65, 0x1c, 0x2f, 0x1e, 0xfe, 0x29, 0x27,
+ 0x7e, 0x86, 0x12, 0x4f, 0x38, 0x17, 0x00, 0xe2, 0x17, 0xb5, 0x2f, 0x7d, 0xc2, 0xb9, 0xe2, 0x9b,
+ 0xdc, 0x20, 0x6c, 0x43, 0xcd, 0xac, 0x45, 0x62, 0xf1, 0x73, 0x04, 0x27, 0xaf, 0x1b, 0xac, 0x9f,
+ 0x25, 0x8b, 0x68, 0xfe, 0x1f, 0xdf, 0xcc, 0x0e, 0xcf, 0x3b, 0xf1, 0x4e, 0xf6, 0x33, 0x04, 0x27,
+ 0x1a, 0xaf, 0x48, 0xf3, 0x1c, 0xa4, 0x83, 0xda, 0x09, 0x69, 0xef, 0x5f, 0x6c, 0x09, 0xc6, 0xa1,
+ 0xeb, 0x6b, 0x30, 0xad, 0x7e, 0x9e, 0x86, 0xc9, 0x97, 0x90, 0xeb, 0x18, 0xcc, 0x2f, 0xa8, 0x07,
+ 0x00, 0x57, 0x29, 0x8f, 0xea, 0xf7, 0xeb, 0x7d, 0x90, 0x0b, 0x5d, 0x87, 0x6f, 0x17, 0x4a, 0x07,
+ 0x2d, 0xe3, 0x62, 0xe1, 0xd3, 0xbf, 0xfe, 0xfd, 0x47, 0x43, 0xc7, 0x31, 0xae, 0x10, 0x56, 0x09,
+ 0xc8, 0xcb, 0x61, 0x31, 0xe3, 0x9f, 0x23, 0x48, 0x5d, 0xa5, 0x1c, 0x9f, 0xd9, 0x8b, 0xf6, 0x8a,
+ 0x2a, 0x2d, 0xec, 0x9f, 0xae, 0xe2, 0xa2, 0x98, 0x53, 0xc1, 0x97, 0xe3, 0x39, 0x2b, 0x4f, 0x0c,
+ 0x9d, 0x95, 0xf7, 0xd4, 0xcd, 0x9e, 0xf1, 0xb3, 0xc0, 0x28, 0x7e, 0x35, 0x7d, 0x86, 0x7f, 0x88,
+ 0xe0, 0xa8, 0x5f, 0x8d, 0x58, 0xde, 0x3b, 0xeb, 0x2b, 0x6b, 0xb4, 0x50, 0xdc, 0x97, 0x24, 0x2b,
+ 0xce, 0x0a, 0x96, 0x32, 0x3e, 0x93, 0x64, 0xb9, 0x0f, 0x43, 0xfc, 0x2f, 0x04, 0xa9, 0xc6, 0xcb,
+ 0x52, 0xd6, 0x78, 0xbd, 0x94, 0xfd, 0x18, 0x09, 0x36, 0x9f, 0xa1, 0xc2, 0x72, 0x92, 0x4e, 0xf8,
+ 0xeb, 0xc4, 0x81, 0x72, 0x97, 0xb0, 0x4d, 0xa4, 0xb0, 0x86, 0x66, 0xee, 0x5c, 0x2c, 0x7e, 0x78,
+ 0x38, 0xd0, 0x1a, 0x9a, 0xc1, 0x9f, 0x21, 0x48, 0xcf, 0x53, 0x93, 0x72, 0x8a, 0x07, 0xda, 0x93,
+ 0x0a, 0x5f, 0x52, 0xbb, 0xc5, 0xcb, 0x22, 0xd2, 0xda, 0xcc, 0x47, 0x03, 0xe4, 0x5d, 0x90, 0x8e,
+ 0x42, 0x52, 0x3e, 0xf8, 0xfd, 0xdf, 0x4e, 0xa1, 0x3b, 0x95, 0x8e, 0x5d, 0xe6, 0xeb, 0x94, 0x8b,
+ 0x6b, 0x4d, 0xd9, 0xa2, 0x7c, 0xcb, 0x76, 0x37, 0x2a, 0xbb, 0x7f, 0x16, 0xd8, 0x9c, 0xad, 0x38,
+ 0x1b, 0x9d, 0x0a, 0xe7, 0x96, 0xd3, 0x6a, 0xa5, 0x05, 0x91, 0xd9, 0xff, 0x05, 0x00, 0x00, 0xff,
+ 0xff, 0x08, 0xc8, 0x2f, 0x15, 0x18, 0x1a, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
diff --git a/pkg/ttnpb/applicationserver_pubsub.pb.paths.fm.go b/pkg/ttnpb/applicationserver_pubsub.pb.paths.fm.go
index e703b97936..5d37b55266 100644
--- a/pkg/ttnpb/applicationserver_pubsub.pb.paths.fm.go
+++ b/pkg/ttnpb/applicationserver_pubsub.pb.paths.fm.go
@@ -74,6 +74,8 @@ var ApplicationPubSubFieldPathsNested = []string{
"updated_at",
"uplink_message",
"uplink_message.topic",
+ "uplink_normalized",
+ "uplink_normalized.topic",
}
var ApplicationPubSubFieldPathsTopLevel = []string{
@@ -95,6 +97,7 @@ var ApplicationPubSubFieldPathsTopLevel = []string{
"service_data",
"updated_at",
"uplink_message",
+ "uplink_normalized",
}
var ApplicationPubSubsFieldPathsNested = []string{
"pubsubs",
@@ -196,6 +199,8 @@ var SetApplicationPubSubRequestFieldPathsNested = []string{
"pubsub.updated_at",
"pubsub.uplink_message",
"pubsub.uplink_message.topic",
+ "pubsub.uplink_normalized",
+ "pubsub.uplink_normalized.topic",
}
var SetApplicationPubSubRequestFieldPathsTopLevel = []string{
diff --git a/pkg/ttnpb/applicationserver_pubsub.pb.setters.fm.go b/pkg/ttnpb/applicationserver_pubsub.pb.setters.fm.go
index a6b1a246b7..167a55f091 100644
--- a/pkg/ttnpb/applicationserver_pubsub.pb.setters.fm.go
+++ b/pkg/ttnpb/applicationserver_pubsub.pb.setters.fm.go
@@ -191,6 +191,31 @@ func (dst *ApplicationPubSub) SetFields(src *ApplicationPubSub, paths ...string)
dst.UplinkMessage = nil
}
}
+ case "uplink_normalized":
+ if len(subs) > 0 {
+ var newDst, newSrc *ApplicationPubSub_Message
+ if (src == nil || src.UplinkNormalized == nil) && dst.UplinkNormalized == nil {
+ continue
+ }
+ if src != nil {
+ newSrc = src.UplinkNormalized
+ }
+ if dst.UplinkNormalized != nil {
+ newDst = dst.UplinkNormalized
+ } else {
+ newDst = &ApplicationPubSub_Message{}
+ dst.UplinkNormalized = newDst
+ }
+ if err := newDst.SetFields(newSrc, subs...); err != nil {
+ return err
+ }
+ } else {
+ if src != nil {
+ dst.UplinkNormalized = src.UplinkNormalized
+ } else {
+ dst.UplinkNormalized = nil
+ }
+ }
case "join_accept":
if len(subs) > 0 {
var newDst, newSrc *ApplicationPubSub_Message
diff --git a/pkg/ttnpb/applicationserver_pubsub.pb.validate.go b/pkg/ttnpb/applicationserver_pubsub.pb.validate.go
index c02334058d..edcc2ab7ff 100644
--- a/pkg/ttnpb/applicationserver_pubsub.pb.validate.go
+++ b/pkg/ttnpb/applicationserver_pubsub.pb.validate.go
@@ -273,6 +273,18 @@ func (m *ApplicationPubSub) ValidateFields(paths ...string) error {
}
}
+ case "uplink_normalized":
+
+ if v, ok := interface{}(m.GetUplinkNormalized()).(interface{ ValidateFields(...string) error }); ok {
+ if err := v.ValidateFields(subs...); err != nil {
+ return ApplicationPubSubValidationError{
+ field: "uplink_normalized",
+ reason: "embedded message failed validation",
+ cause: err,
+ }
+ }
+ }
+
case "join_accept":
if v, ok := interface{}(m.GetJoinAccept()).(interface{ ValidateFields(...string) error }); ok {
diff --git a/pkg/ttnpb/applicationserver_pubsub_flags.pb.go b/pkg/ttnpb/applicationserver_pubsub_flags.pb.go
index e65cacfaf6..8e90b514f4 100644
--- a/pkg/ttnpb/applicationserver_pubsub_flags.pb.go
+++ b/pkg/ttnpb/applicationserver_pubsub_flags.pb.go
@@ -546,6 +546,8 @@ func AddSelectFlagsForApplicationPubSub(flags *pflag.FlagSet, prefix string, hid
AddSelectFlagsForApplicationPubSub_Message(flags, flagsplugin.Prefix("downlink-replace", prefix), hidden)
flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("uplink-message", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("uplink-message", prefix), true), flagsplugin.WithHidden(hidden)))
AddSelectFlagsForApplicationPubSub_Message(flags, flagsplugin.Prefix("uplink-message", prefix), hidden)
+ flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("uplink-normalized", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("uplink-normalized", prefix), true), flagsplugin.WithHidden(hidden)))
+ AddSelectFlagsForApplicationPubSub_Message(flags, flagsplugin.Prefix("uplink-normalized", prefix), hidden)
flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("join-accept", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("join-accept", prefix), true), flagsplugin.WithHidden(hidden)))
AddSelectFlagsForApplicationPubSub_Message(flags, flagsplugin.Prefix("join-accept", prefix), hidden)
flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("downlink-ack", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("downlink-ack", prefix), true), flagsplugin.WithHidden(hidden)))
@@ -638,6 +640,16 @@ func PathsFromSelectFlagsForApplicationPubSub(flags *pflag.FlagSet, prefix strin
} else {
paths = append(paths, selectPaths...)
}
+ if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("uplink_normalized", prefix)); err != nil {
+ return nil, err
+ } else if selected && val {
+ paths = append(paths, flagsplugin.Prefix("uplink_normalized", prefix))
+ }
+ if selectPaths, err := PathsFromSelectFlagsForApplicationPubSub_Message(flags, flagsplugin.Prefix("uplink_normalized", prefix)); err != nil {
+ return nil, err
+ } else {
+ paths = append(paths, selectPaths...)
+ }
if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("join_accept", prefix)); err != nil {
return nil, err
} else if selected && val {
@@ -742,6 +754,7 @@ func AddSetFlagsForApplicationPubSub(flags *pflag.FlagSet, prefix string, hidden
AddSetFlagsForApplicationPubSub_Message(flags, flagsplugin.Prefix("downlink-push", prefix), hidden)
AddSetFlagsForApplicationPubSub_Message(flags, flagsplugin.Prefix("downlink-replace", prefix), hidden)
AddSetFlagsForApplicationPubSub_Message(flags, flagsplugin.Prefix("uplink-message", prefix), hidden)
+ AddSetFlagsForApplicationPubSub_Message(flags, flagsplugin.Prefix("uplink-normalized", prefix), hidden)
AddSetFlagsForApplicationPubSub_Message(flags, flagsplugin.Prefix("join-accept", prefix), hidden)
AddSetFlagsForApplicationPubSub_Message(flags, flagsplugin.Prefix("downlink-ack", prefix), hidden)
AddSetFlagsForApplicationPubSub_Message(flags, flagsplugin.Prefix("downlink-nack", prefix), hidden)
@@ -843,6 +856,16 @@ func (m *ApplicationPubSub) SetFromFlags(flags *pflag.FlagSet, prefix string) (p
paths = append(paths, setPaths...)
}
}
+ if changed := flagsplugin.IsAnyPrefixSet(flags, flagsplugin.Prefix("uplink_normalized", prefix)); changed {
+ if m.UplinkNormalized == nil {
+ m.UplinkNormalized = &ApplicationPubSub_Message{}
+ }
+ if setPaths, err := m.UplinkNormalized.SetFromFlags(flags, flagsplugin.Prefix("uplink_normalized", prefix)); err != nil {
+ return nil, err
+ } else {
+ paths = append(paths, setPaths...)
+ }
+ }
if changed := flagsplugin.IsAnyPrefixSet(flags, flagsplugin.Prefix("join_accept", prefix)); changed {
if m.JoinAccept == nil {
m.JoinAccept = &ApplicationPubSub_Message{}
diff --git a/pkg/ttnpb/applicationserver_pubsub_json.pb.go b/pkg/ttnpb/applicationserver_pubsub_json.pb.go
index 4c61527e8f..01147b77d7 100644
--- a/pkg/ttnpb/applicationserver_pubsub_json.pb.go
+++ b/pkg/ttnpb/applicationserver_pubsub_json.pb.go
@@ -265,6 +265,12 @@ func (x *ApplicationPubSub) MarshalProtoJSON(s *jsonplugin.MarshalState) {
// NOTE: ApplicationPubSub_Message does not seem to implement MarshalProtoJSON.
gogo.MarshalMessage(s, x.UplinkMessage)
}
+ if x.UplinkNormalized != nil || s.HasField("uplink_normalized") {
+ s.WriteMoreIf(&wroteField)
+ s.WriteObjectField("uplink_normalized")
+ // NOTE: ApplicationPubSub_Message does not seem to implement MarshalProtoJSON.
+ gogo.MarshalMessage(s, x.UplinkNormalized)
+ }
if x.JoinAccept != nil || s.HasField("join_accept") {
s.WriteMoreIf(&wroteField)
s.WriteObjectField("join_accept")
@@ -437,6 +443,16 @@ func (x *ApplicationPubSub) UnmarshalProtoJSON(s *jsonplugin.UnmarshalState) {
var v ApplicationPubSub_Message
gogo.UnmarshalMessage(s, &v)
x.UplinkMessage = &v
+ case "uplink_normalized", "uplinkNormalized":
+ s.AddField("uplink_normalized")
+ if s.ReadNil() {
+ x.UplinkNormalized = nil
+ return
+ }
+ // NOTE: ApplicationPubSub_Message does not seem to implement UnmarshalProtoJSON.
+ var v ApplicationPubSub_Message
+ gogo.UnmarshalMessage(s, &v)
+ x.UplinkNormalized = &v
case "join_accept", "joinAccept":
s.AddField("join_accept")
if s.ReadNil() {
diff --git a/pkg/ttnpb/applicationserver_web.pb.go b/pkg/ttnpb/applicationserver_web.pb.go
index c7f9646acb..4cae2f9f49 100644
--- a/pkg/ttnpb/applicationserver_web.pb.go
+++ b/pkg/ttnpb/applicationserver_web.pb.go
@@ -213,6 +213,7 @@ type ApplicationWebhookTemplate struct {
// Control the creation of the downlink queue operations API key.
CreateDownlinkApiKey bool `protobuf:"varint,19,opt,name=create_downlink_api_key,json=createDownlinkApiKey,proto3" json:"create_downlink_api_key,omitempty"`
UplinkMessage *ApplicationWebhookTemplate_Message `protobuf:"bytes,11,opt,name=uplink_message,json=uplinkMessage,proto3" json:"uplink_message,omitempty"`
+ UplinkNormalized *ApplicationWebhookTemplate_Message `protobuf:"bytes,23,opt,name=uplink_normalized,json=uplinkNormalized,proto3" json:"uplink_normalized,omitempty"`
JoinAccept *ApplicationWebhookTemplate_Message `protobuf:"bytes,12,opt,name=join_accept,json=joinAccept,proto3" json:"join_accept,omitempty"`
DownlinkAck *ApplicationWebhookTemplate_Message `protobuf:"bytes,13,opt,name=downlink_ack,json=downlinkAck,proto3" json:"downlink_ack,omitempty"`
DownlinkNack *ApplicationWebhookTemplate_Message `protobuf:"bytes,14,opt,name=downlink_nack,json=downlinkNack,proto3" json:"downlink_nack,omitempty"`
@@ -336,6 +337,13 @@ func (m *ApplicationWebhookTemplate) GetUplinkMessage() *ApplicationWebhookTempl
return nil
}
+func (m *ApplicationWebhookTemplate) GetUplinkNormalized() *ApplicationWebhookTemplate_Message {
+ if m != nil {
+ return m.UplinkNormalized
+ }
+ return nil
+}
+
func (m *ApplicationWebhookTemplate) GetJoinAccept() *ApplicationWebhookTemplate_Message {
if m != nil {
return m.JoinAccept
@@ -671,6 +679,7 @@ type ApplicationWebhook struct {
// The field is provided for convenience reasons, and can contain API keys with additional rights (albeit this is discouraged).
DownlinkApiKey string `protobuf:"bytes,17,opt,name=downlink_api_key,json=downlinkApiKey,proto3" json:"downlink_api_key,omitempty"`
UplinkMessage *ApplicationWebhook_Message `protobuf:"bytes,7,opt,name=uplink_message,json=uplinkMessage,proto3" json:"uplink_message,omitempty"`
+ UplinkNormalized *ApplicationWebhook_Message `protobuf:"bytes,22,opt,name=uplink_normalized,json=uplinkNormalized,proto3" json:"uplink_normalized,omitempty"`
JoinAccept *ApplicationWebhook_Message `protobuf:"bytes,8,opt,name=join_accept,json=joinAccept,proto3" json:"join_accept,omitempty"`
DownlinkAck *ApplicationWebhook_Message `protobuf:"bytes,9,opt,name=downlink_ack,json=downlinkAck,proto3" json:"downlink_ack,omitempty"`
DownlinkNack *ApplicationWebhook_Message `protobuf:"bytes,10,opt,name=downlink_nack,json=downlinkNack,proto3" json:"downlink_nack,omitempty"`
@@ -781,6 +790,13 @@ func (m *ApplicationWebhook) GetUplinkMessage() *ApplicationWebhook_Message {
return nil
}
+func (m *ApplicationWebhook) GetUplinkNormalized() *ApplicationWebhook_Message {
+ if m != nil {
+ return m.UplinkNormalized
+ }
+ return nil
+}
+
func (m *ApplicationWebhook) GetJoinAccept() *ApplicationWebhook_Message {
if m != nil {
return m.JoinAccept
@@ -1253,130 +1269,132 @@ func init() {
}
var fileDescriptor_2652f2d8eaceda0e = []byte{
- // 1959 bytes of a gzipped FileDescriptorProto
- 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x59, 0x4f, 0x73, 0x1b, 0x49,
- 0x15, 0xcf, 0xc8, 0xff, 0xa4, 0x27, 0xff, 0x51, 0xda, 0x76, 0x76, 0x56, 0x76, 0xb2, 0xae, 0x49,
- 0x48, 0x1c, 0x13, 0x49, 0x5b, 0x0e, 0x66, 0x89, 0x8b, 0xda, 0xac, 0xb4, 0x8e, 0xed, 0xb0, 0x98,
- 0x25, 0xa3, 0x84, 0x25, 0x9b, 0x5a, 0x44, 0x5b, 0xd3, 0x96, 0x06, 0x8d, 0x66, 0x66, 0xa7, 0x5b,
- 0x36, 0x22, 0x95, 0xaa, 0x2d, 0x8a, 0x03, 0x70, 0xa1, 0x8a, 0x3d, 0x50, 0xc5, 0x81, 0x82, 0x82,
- 0x0b, 0x7b, 0xa4, 0xa8, 0x3d, 0xef, 0x07, 0xe0, 0xc6, 0x47, 0x80, 0x4f, 0xc0, 0x89, 0xca, 0x89,
- 0x9a, 0x9e, 0x1e, 0x69, 0xfe, 0x48, 0xf1, 0x8c, 0xcc, 0xee, 0x49, 0xd3, 0xd3, 0xef, 0xfd, 0xde,
- 0xeb, 0xd7, 0xaf, 0xdf, 0xfb, 0x4d, 0x0b, 0x4a, 0x86, 0xe5, 0xe0, 0x33, 0x6c, 0x96, 0x28, 0xc3,
- 0xcd, 0x4e, 0x05, 0xdb, 0x7a, 0x05, 0xdb, 0xb6, 0xa1, 0x37, 0x31, 0xd3, 0x2d, 0x93, 0x12, 0xe7,
- 0x94, 0x38, 0x8d, 0x33, 0x72, 0x5c, 0xb6, 0x1d, 0x8b, 0x59, 0x68, 0x91, 0x31, 0xb3, 0x2c, 0x54,
- 0xca, 0xa7, 0x77, 0x8b, 0x7b, 0x2d, 0x9d, 0xb5, 0x7b, 0xc7, 0xe5, 0xa6, 0xd5, 0xad, 0x3c, 0x6e,
- 0x93, 0xc7, 0x6d, 0xdd, 0x6c, 0xd1, 0x87, 0xa6, 0xd6, 0xa3, 0xcc, 0xd1, 0x09, 0xad, 0x70, 0xad,
- 0x66, 0xa9, 0x45, 0xcc, 0x52, 0xcb, 0x2a, 0x9d, 0x18, 0xb8, 0x45, 0x2b, 0xd8, 0x34, 0x2d, 0xe6,
- 0xc1, 0x7b, 0xa8, 0xc5, 0x6a, 0x00, 0x85, 0x98, 0xa7, 0x56, 0xdf, 0x76, 0xac, 0x9f, 0xf6, 0x83,
- 0xca, 0xa7, 0xd8, 0xd0, 0x35, 0xcc, 0x48, 0x25, 0xf6, 0x20, 0x20, 0x4a, 0x01, 0x88, 0x96, 0xd5,
- 0xb2, 0x3c, 0xe5, 0xe3, 0xde, 0x09, 0x1f, 0xf1, 0x01, 0x7f, 0x12, 0xe2, 0xeb, 0x2d, 0xcb, 0x6a,
- 0x19, 0xc4, 0x5b, 0x6f, 0xcc, 0x9f, 0x35, 0x31, 0x3b, 0xc0, 0x20, 0x5d, 0x9b, 0xf5, 0xc5, 0xe4,
- 0x46, 0x74, 0xf2, 0x44, 0x27, 0x86, 0xd6, 0xe8, 0x62, 0xda, 0x11, 0x12, 0x6f, 0x44, 0x25, 0x98,
- 0xde, 0x25, 0x94, 0xe1, 0xae, 0x2d, 0x04, 0xae, 0xc6, 0x83, 0x4e, 0x1c, 0xc7, 0x72, 0xc4, 0xf4,
- 0xf5, 0xf8, 0xb4, 0xae, 0x11, 0x93, 0xe9, 0x27, 0x3a, 0x71, 0x84, 0x8f, 0xca, 0x3f, 0x24, 0xb8,
- 0x5a, 0x1d, 0xee, 0xd4, 0x07, 0xe4, 0xb8, 0x6d, 0x59, 0x9d, 0x87, 0x43, 0x39, 0xf4, 0x14, 0x96,
- 0x02, 0x5b, 0xd9, 0xd0, 0x35, 0x2a, 0x4b, 0x1b, 0xd2, 0x66, 0x7e, 0xfb, 0x66, 0x39, 0xbc, 0x8b,
- 0xe5, 0x00, 0x4e, 0x00, 0xa0, 0x96, 0x7d, 0x59, 0x9b, 0xf9, 0xb5, 0x94, 0x29, 0x48, 0xea, 0x22,
- 0x0e, 0x4a, 0x50, 0xb4, 0x0f, 0x70, 0xe6, 0x19, 0x6c, 0xe8, 0x9a, 0x9c, 0xd9, 0x90, 0x36, 0x73,
- 0xb5, 0x5b, 0x2f, 0x6b, 0x37, 0x1c, 0x45, 0xbe, 0xb1, 0x7d, 0xed, 0x47, 0xcf, 0x70, 0xe9, 0x67,
- 0x6f, 0x96, 0xee, 0x7d, 0xb4, 0x79, 0x7f, 0xf7, 0x59, 0xe9, 0xa3, 0xfb, 0xfe, 0xf0, 0xf6, 0xf3,
- 0xed, 0x3b, 0x2f, 0x6e, 0xa8, 0xb9, 0x33, 0xdf, 0xd7, 0xdd, 0xec, 0x7f, 0x3e, 0x7b, 0x7d, 0x3a,
- 0x2b, 0x15, 0x24, 0xe5, 0x39, 0x7c, 0x2d, 0xbe, 0x9a, 0xc7, 0xa4, 0x6b, 0x1b, 0x98, 0x91, 0xe0,
- 0xaa, 0x0e, 0x21, 0xcf, 0xc4, 0x6b, 0xd7, 0xb6, 0x94, 0xce, 0x36, 0xb0, 0x01, 0x64, 0xc0, 0xf8,
- 0x2f, 0x32, 0xf0, 0xc6, 0x78, 0xeb, 0xfb, 0xee, 0xfe, 0xa2, 0xb7, 0x20, 0x93, 0xde, 0x5c, 0x46,
- 0xd7, 0xd0, 0x1a, 0x4c, 0x9b, 0xb8, 0x4b, 0x44, 0x94, 0xe6, 0x5e, 0xd6, 0xa6, 0x9d, 0x8c, 0xbc,
- 0xa2, 0xf2, 0x97, 0xe8, 0x36, 0xe4, 0x35, 0x42, 0x9b, 0x8e, 0x6e, 0xbb, 0x86, 0xe5, 0xa9, 0xa0,
- 0x8c, 0xa6, 0x06, 0xe7, 0xd0, 0x15, 0x98, 0xa5, 0xa4, 0xe9, 0x10, 0x26, 0x4f, 0x6f, 0x48, 0x9b,
- 0x59, 0x55, 0x8c, 0xd0, 0x1d, 0x58, 0xd0, 0xc8, 0x09, 0xee, 0x19, 0xac, 0x71, 0x8a, 0x8d, 0x1e,
- 0x91, 0x67, 0xc2, 0x20, 0xf3, 0x62, 0xf6, 0x07, 0xee, 0x24, 0x2a, 0x42, 0xd6, 0xe2, 0x78, 0xd8,
- 0x90, 0x67, 0x39, 0xce, 0x60, 0xac, 0x7c, 0xb1, 0x00, 0xc5, 0xf1, 0x61, 0x40, 0x8f, 0x60, 0x6a,
- 0x98, 0x43, 0x3b, 0xaf, 0xc8, 0xa1, 0xf1, 0xbb, 0x17, 0x48, 0x29, 0x17, 0xeb, 0xff, 0x16, 0x9b,
- 0xeb, 0x90, 0x35, 0xac, 0x96, 0xd5, 0xe8, 0x39, 0x06, 0x8f, 0x4e, 0x8e, 0x1b, 0x72, 0xa6, 0x7e,
- 0x29, 0x49, 0xea, 0x9c, 0x3b, 0xf3, 0xc4, 0x31, 0x5c, 0x21, 0xdd, 0x3c, 0xf1, 0x84, 0x66, 0xa2,
- 0x42, 0xee, 0x8c, 0x2b, 0xb4, 0x03, 0x97, 0x35, 0xab, 0xd9, 0xeb, 0x12, 0xd3, 0x2b, 0x09, 0x5c,
- 0x7a, 0x36, 0x22, 0x5d, 0x08, 0x89, 0x08, 0xec, 0x63, 0x4c, 0x09, 0x97, 0x9e, 0x8b, 0x62, 0xbb,
- 0x33, 0xae, 0x50, 0x1b, 0xe6, 0xda, 0x04, 0x6b, 0xc4, 0xa1, 0x72, 0x76, 0x63, 0x6a, 0x33, 0xbf,
- 0xfd, 0x56, 0xf2, 0x20, 0x96, 0x0f, 0x3d, 0xcd, 0x07, 0x26, 0x73, 0xfa, 0xb5, 0xd5, 0x97, 0x35,
- 0xf4, 0x7b, 0x69, 0xa9, 0xb0, 0xad, 0xb8, 0xc1, 0x78, 0x67, 0x6b, 0xc6, 0x99, 0x92, 0x3f, 0xc9,
- 0xa8, 0x3e, 0x3c, 0xba, 0x0f, 0xb3, 0x27, 0x96, 0xd3, 0xc5, 0x4c, 0xce, 0x05, 0x13, 0x76, 0xe5,
- 0xdc, 0x84, 0x15, 0x6a, 0xe8, 0x00, 0x66, 0x79, 0x59, 0xa3, 0x32, 0x70, 0x4f, 0x2b, 0xc9, 0x3d,
- 0xe5, 0xc7, 0x45, 0x15, 0xea, 0x68, 0x07, 0x5e, 0x6b, 0x3a, 0xc4, 0x3d, 0xac, 0x9a, 0x75, 0x66,
- 0x1a, 0xba, 0xd9, 0x69, 0x60, 0x5b, 0x6f, 0x74, 0x48, 0x5f, 0x5e, 0xe6, 0xe9, 0xb7, 0xe2, 0x4d,
- 0xef, 0x89, 0xd9, 0xaa, 0xad, 0xbf, 0x47, 0xfa, 0xe8, 0x29, 0x2c, 0xf6, 0x6c, 0x2e, 0xdd, 0x25,
- 0x94, 0xe2, 0x16, 0x91, 0xf3, 0x3c, 0xed, 0xb6, 0x53, 0x44, 0xec, 0xc8, 0xd3, 0x54, 0x17, 0x3c,
- 0x24, 0x31, 0x44, 0x75, 0xc8, 0xff, 0xc4, 0xd2, 0xcd, 0x06, 0x6e, 0x36, 0x89, 0xcd, 0xe4, 0xf9,
- 0x89, 0x71, 0xc1, 0x85, 0xa9, 0x72, 0x14, 0xf4, 0x04, 0xe6, 0x87, 0xeb, 0x6b, 0x76, 0xe4, 0x85,
- 0x89, 0x51, 0xf3, 0x3e, 0x4e, 0xb5, 0xd9, 0x41, 0x1f, 0xc0, 0xc2, 0x00, 0xd6, 0x74, 0x71, 0x17,
- 0x27, 0xc6, 0x1d, 0xf8, 0xf7, 0x3d, 0x1c, 0x01, 0xa6, 0xc4, 0x64, 0xf2, 0xd2, 0xc5, 0x81, 0xeb,
- 0xc4, 0x64, 0xe8, 0x19, 0x2c, 0x0d, 0x80, 0x4f, 0xb0, 0x6e, 0x10, 0x4d, 0x2e, 0x4c, 0x0c, 0xbd,
- 0xe8, 0x43, 0xed, 0x73, 0xa4, 0x10, 0xf8, 0xc7, 0x3d, 0xd2, 0x23, 0x9a, 0x7c, 0xf9, 0xe2, 0xe0,
- 0x8f, 0x38, 0x12, 0xb2, 0xa1, 0x18, 0x06, 0x6f, 0xe8, 0xa6, 0x4f, 0x32, 0x34, 0x79, 0x75, 0x62,
- 0x3b, 0x72, 0xc8, 0xce, 0xc3, 0x21, 0xa6, 0xbb, 0x1c, 0xc3, 0x12, 0xdd, 0x99, 0x5a, 0xc6, 0x29,
- 0xd1, 0x64, 0x34, 0xf9, 0x72, 0x7c, 0xa8, 0x3a, 0x47, 0x72, 0x33, 0xd2, 0x65, 0x6f, 0x7a, 0x93,
- 0x34, 0x34, 0xcc, 0xb0, 0xbc, 0x32, 0x79, 0x46, 0x0a, 0x9c, 0x3d, 0xcc, 0x30, 0xba, 0x07, 0x30,
- 0xe4, 0x3b, 0xf2, 0x15, 0x0e, 0x5a, 0x2c, 0x7b, 0x84, 0xa7, 0xec, 0x13, 0x9e, 0x32, 0xaf, 0x01,
- 0x47, 0x98, 0x76, 0xd4, 0xdc, 0x89, 0xff, 0x58, 0xdc, 0x85, 0xf9, 0x60, 0x11, 0x43, 0x05, 0x98,
- 0x72, 0xcb, 0x00, 0x6f, 0xa9, 0xaa, 0xfb, 0x88, 0x56, 0x60, 0xc6, 0x6b, 0x61, 0xbc, 0x1f, 0xa8,
- 0xde, 0x60, 0x37, 0xf3, 0x2d, 0xa9, 0x78, 0x13, 0xe6, 0xfc, 0xf3, 0xbb, 0x06, 0xd3, 0x36, 0x66,
- 0x6d, 0xd1, 0x8a, 0x45, 0x3f, 0x78, 0x47, 0xe5, 0x2f, 0x95, 0x16, 0xac, 0x8d, 0x5f, 0x91, 0x4b,
- 0x1e, 0x72, 0x3e, 0x01, 0x70, 0x1b, 0x99, 0x5b, 0xd9, 0xb6, 0x92, 0x47, 0x44, 0x1d, 0x2a, 0x2b,
- 0x9f, 0x4d, 0x83, 0x1c, 0x97, 0x3c, 0x24, 0xd8, 0x60, 0x6d, 0xd4, 0xe0, 0x85, 0xde, 0x60, 0xed,
- 0xbe, 0xe8, 0x96, 0xef, 0x9e, 0x6f, 0xc4, 0x53, 0x2d, 0x87, 0x46, 0x75, 0x86, 0x59, 0x8f, 0x7a,
- 0xcf, 0xfd, 0xc3, 0x4b, 0xaa, 0x8f, 0x8a, 0x08, 0xe4, 0x7a, 0xa6, 0x6f, 0x22, 0xc3, 0x4d, 0x3c,
- 0xb8, 0x88, 0x89, 0x27, 0x3e, 0xd8, 0xe1, 0x25, 0x75, 0x88, 0x5c, 0xbc, 0x09, 0xc5, 0xf1, 0xfe,
- 0x0c, 0xf8, 0xd3, 0xa5, 0xe2, 0xaf, 0x32, 0xb0, 0xfe, 0x2a, 0x54, 0x74, 0x0b, 0x96, 0xbc, 0x62,
- 0xd0, 0xc0, 0xcc, 0x8d, 0x21, 0xf3, 0x68, 0xc4, 0xb4, 0xba, 0xe8, 0xbd, 0xae, 0x8a, 0xb7, 0xe8,
- 0x29, 0x5c, 0x31, 0x30, 0x65, 0x8d, 0xb0, 0x74, 0x03, 0x33, 0xb1, 0xca, 0x78, 0xaa, 0x3d, 0xf6,
- 0xb9, 0x35, 0xef, 0xb8, 0x7f, 0x93, 0x32, 0x59, 0x49, 0x5d, 0x76, 0x31, 0xf6, 0x83, 0xc8, 0x55,
- 0xb7, 0x32, 0xad, 0x8d, 0x82, 0xd6, 0x08, 0xc3, 0xba, 0x41, 0x39, 0xbd, 0xc8, 0x6f, 0xaf, 0x47,
- 0xa3, 0xf8, 0xc0, 0xe5, 0xe5, 0x7b, 0x9e, 0x8c, 0x2a, 0xc7, 0x70, 0xc5, 0xcc, 0x30, 0x16, 0xc3,
- 0xa7, 0x5a, 0x16, 0x66, 0x29, 0x8f, 0x83, 0xf2, 0xf9, 0x22, 0xa0, 0xf8, 0x76, 0x84, 0x09, 0x55,
- 0xe9, 0xfc, 0xfd, 0x0b, 0x12, 0xa9, 0x82, 0x4f, 0xa4, 0xb8, 0xb9, 0x4b, 0x9b, 0x82, 0x50, 0xbd,
- 0x0b, 0xe0, 0xf5, 0x53, 0x2d, 0x61, 0xcc, 0x3c, 0xf5, 0xc2, 0x25, 0x35, 0x27, 0xf4, 0xaa, 0xcc,
- 0x05, 0xe9, 0xd9, 0x9a, 0x0f, 0x32, 0x95, 0x06, 0x44, 0xe8, 0x55, 0x59, 0x88, 0x11, 0x4d, 0x8f,
- 0x63, 0x44, 0x3f, 0x1e, 0x32, 0xa2, 0x99, 0xa4, 0x3c, 0x23, 0x01, 0x13, 0xda, 0x18, 0xc5, 0x84,
- 0x66, 0x27, 0x63, 0x42, 0x3f, 0x84, 0xf9, 0xc0, 0xf7, 0x06, 0x15, 0x8d, 0x72, 0x32, 0xfa, 0xab,
- 0xe6, 0x87, 0x9f, 0x1f, 0x14, 0x35, 0x60, 0x69, 0x80, 0x2c, 0xc8, 0x56, 0x81, 0x07, 0xe1, 0x9b,
- 0x09, 0x82, 0x10, 0x62, 0x5b, 0x5e, 0x2c, 0xd4, 0x45, 0x16, 0x7a, 0x89, 0xb6, 0xa1, 0x10, 0x23,
- 0x5d, 0x97, 0x03, 0x5b, 0x21, 0x7f, 0x22, 0x0d, 0xbb, 0xa0, 0x20, 0x5e, 0x8f, 0x62, 0xc4, 0x6b,
- 0x8e, 0x2f, 0x38, 0x41, 0x99, 0x1c, 0x47, 0xb8, 0xde, 0x0b, 0x13, 0xae, 0x6c, 0x6a, 0xbc, 0x20,
- 0xd1, 0x3a, 0x8a, 0x10, 0xad, 0x5c, 0x6a, 0xb4, 0x10, 0xc1, 0x7a, 0x3f, 0x4a, 0xb0, 0x20, 0x35,
- 0x5e, 0x98, 0x58, 0xbd, 0x1f, 0x25, 0x56, 0xf9, 0xc9, 0x01, 0x39, 0xa1, 0xaa, 0xc7, 0x09, 0xd5,
- 0x7c, 0x6a, 0xc8, 0x28, 0x91, 0xaa, 0xc7, 0x89, 0xd4, 0xc2, 0xe4, 0xa0, 0x82, 0x40, 0xb5, 0x5f,
- 0x49, 0xa0, 0x96, 0x53, 0xe3, 0x8f, 0x27, 0x4e, 0xf5, 0x38, 0x71, 0x5a, 0x4c, 0xef, 0x7e, 0x84,
- 0x30, 0x1d, 0x45, 0x08, 0x13, 0x4a, 0x9f, 0x59, 0x41, 0xa2, 0x74, 0x04, 0x0b, 0x5e, 0xf7, 0x6b,
- 0x78, 0x4d, 0x40, 0x10, 0xb0, 0xcd, 0xa4, 0x6d, 0x5a, 0x9d, 0x6f, 0x07, 0x5a, 0x69, 0x84, 0x77,
- 0xad, 0x7e, 0x55, 0xbc, 0xab, 0x0a, 0xcb, 0x23, 0x2a, 0x4d, 0x2a, 0x88, 0x37, 0x93, 0x51, 0xb7,
- 0xe1, 0x75, 0x4c, 0xe0, 0x62, 0xe6, 0x09, 0x2c, 0xc7, 0xe3, 0x43, 0xd1, 0xdb, 0x90, 0x15, 0x77,
- 0x48, 0x3e, 0x8b, 0x53, 0xce, 0x0f, 0xab, 0x3a, 0xd0, 0x51, 0xfe, 0x2a, 0xc1, 0xeb, 0x71, 0x81,
- 0x7d, 0x5e, 0xf0, 0x29, 0xfa, 0x3e, 0xcc, 0x79, 0xb5, 0xdf, 0x07, 0x4f, 0x50, 0x8f, 0x85, 0x6e,
- 0x59, 0xfc, 0x7a, 0xf5, 0xd8, 0x87, 0x71, 0x77, 0x20, 0x38, 0x91, 0x26, 0x7c, 0xca, 0x5f, 0x24,
- 0x58, 0x3f, 0x20, 0x6c, 0xc4, 0x7a, 0xc8, 0xc7, 0x3d, 0x42, 0x19, 0x7a, 0x78, 0x01, 0x16, 0x11,
- 0xb9, 0x8e, 0x09, 0x27, 0x59, 0x26, 0x45, 0x92, 0x29, 0x9f, 0x4b, 0x70, 0xed, 0xbb, 0x3a, 0x1d,
- 0xe1, 0x27, 0xf5, 0x1d, 0xfd, 0x12, 0xef, 0x23, 0x2f, 0xe0, 0xf8, 0x9f, 0x24, 0x58, 0xaf, 0xbf,
- 0x2a, 0xbe, 0xfb, 0x30, 0x27, 0x12, 0x47, 0xb8, 0x9b, 0x20, 0xd7, 0x02, 0xae, 0xfa, 0xca, 0x17,
- 0xf1, 0xf1, 0xef, 0x12, 0xdc, 0x18, 0x99, 0x03, 0x83, 0x2f, 0x13, 0xe1, 0xeb, 0x97, 0x70, 0x45,
- 0x77, 0x01, 0xb7, 0x9b, 0x70, 0x73, 0x74, 0x4a, 0x0c, 0x3e, 0xc8, 0x7c, 0xbf, 0xc3, 0x46, 0xa4,
- 0x14, 0x46, 0xb6, 0x7f, 0x93, 0x1b, 0x75, 0x69, 0xa9, 0x92, 0x96, 0x4e, 0xdd, 0xa3, 0x66, 0x00,
- 0x1c, 0x10, 0xe6, 0x1f, 0xed, 0x2b, 0x31, 0xcc, 0x07, 0x5d, 0x9b, 0xf5, 0x8b, 0xb7, 0x13, 0x9f,
- 0x70, 0x65, 0xed, 0xe7, 0xff, 0xfc, 0xf7, 0xa7, 0x99, 0x55, 0xb4, 0x5c, 0xc1, 0xb4, 0x22, 0xf6,
- 0xb6, 0x24, 0x0e, 0x3a, 0xfa, 0xa3, 0x04, 0xf9, 0x03, 0xc2, 0x06, 0x57, 0xa6, 0xdf, 0x88, 0xe2,
- 0x26, 0xd9, 0xc5, 0x62, 0x8a, 0x4f, 0x52, 0xa5, 0xc2, 0xdd, 0xb9, 0x8d, 0x6e, 0x05, 0xdd, 0x19,
- 0x7c, 0xa6, 0x56, 0x9e, 0xeb, 0x1a, 0x2d, 0x07, 0xf8, 0xeb, 0x0b, 0xf4, 0xa9, 0x04, 0x0b, 0xee,
- 0xae, 0x0c, 0x3f, 0x8a, 0x63, 0xe5, 0x2d, 0xd9, 0xa6, 0x15, 0xbf, 0x9e, 0xdc, 0x4d, 0xaa, 0x5c,
- 0xe5, 0x7e, 0xbe, 0x86, 0x56, 0x47, 0xfa, 0x89, 0xfe, 0x2c, 0xc1, 0xd4, 0x01, 0x61, 0xe8, 0x4e,
- 0xa2, 0x80, 0xf9, 0x1e, 0x24, 0x38, 0x89, 0xca, 0x77, 0xb8, 0xe1, 0x3d, 0x54, 0x0b, 0x18, 0x16,
- 0x71, 0x89, 0x54, 0xa3, 0xc8, 0xf8, 0x85, 0x27, 0x34, 0xfc, 0x9f, 0xe3, 0x05, 0xfa, 0xad, 0x04,
- 0xd3, 0x6e, 0x70, 0x50, 0x39, 0x59, 0xc8, 0x06, 0xa1, 0xba, 0x7e, 0xbe, 0xa3, 0x54, 0xd9, 0xe1,
- 0x9e, 0x56, 0x50, 0x29, 0xec, 0xe9, 0x39, 0x5e, 0xa2, 0xff, 0x4a, 0x30, 0x55, 0x1f, 0x15, 0xba,
- 0xfa, 0x45, 0x43, 0xf7, 0x07, 0x89, 0x7b, 0xf4, 0x3b, 0xa9, 0xa8, 0x86, 0x5d, 0x12, 0x4f, 0xe5,
- 0x44, 0x41, 0x0c, 0x0a, 0x07, 0x82, 0xb9, 0x2b, 0x6d, 0x7d, 0xf8, 0xb6, 0x72, 0x6f, 0x62, 0xe0,
- 0x5d, 0x69, 0xcb, 0xcd, 0xe5, 0xd9, 0x3d, 0x62, 0x10, 0x46, 0x50, 0xba, 0xc6, 0x57, 0x1c, 0x53,
- 0x08, 0x94, 0x1a, 0x5f, 0xf1, 0xb7, 0xb7, 0x76, 0x53, 0xed, 0xc1, 0xc0, 0x71, 0x77, 0x50, 0xdb,
- 0xf9, 0xe2, 0x5f, 0xd7, 0xa4, 0x0f, 0x2b, 0x2d, 0xab, 0xcc, 0xda, 0x84, 0xf1, 0x7f, 0x44, 0xcb,
- 0x26, 0x61, 0x67, 0x96, 0xd3, 0xa9, 0x84, 0xff, 0xd9, 0x3b, 0xbd, 0x5b, 0xb1, 0x3b, 0xad, 0x0a,
- 0x63, 0xa6, 0x7d, 0x7c, 0x3c, 0xcb, 0x5d, 0xb9, 0xfb, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x3f,
- 0x21, 0x2c, 0x09, 0x92, 0x1d, 0x00, 0x00,
+ // 1990 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x99, 0x4d, 0x73, 0xdb, 0xc6,
+ 0x19, 0xc7, 0x05, 0xea, 0x8d, 0x7c, 0xa8, 0x17, 0x7a, 0xf5, 0x62, 0x84, 0x92, 0x1d, 0x0d, 0xec,
+ 0xda, 0xb2, 0x6a, 0x92, 0x19, 0xb9, 0x6a, 0x6a, 0x4d, 0x27, 0x0e, 0x19, 0x59, 0x92, 0x9b, 0x2a,
+ 0xa9, 0x41, 0xbb, 0xae, 0xe3, 0x49, 0xd9, 0x15, 0xb1, 0x22, 0x51, 0x82, 0x00, 0x82, 0x5d, 0x4a,
+ 0x55, 0x3c, 0x9e, 0xc9, 0x74, 0x7a, 0x68, 0x7b, 0xc9, 0x4c, 0x73, 0xe8, 0x4c, 0x0f, 0x9d, 0x66,
+ 0xda, 0x4b, 0x73, 0xec, 0x74, 0x7a, 0xee, 0x07, 0xe8, 0xad, 0x1f, 0xa1, 0xfd, 0x04, 0x3d, 0x75,
+ 0x7c, 0xea, 0x60, 0xb1, 0x20, 0xf1, 0x42, 0x59, 0x00, 0x55, 0xe7, 0x44, 0x2c, 0xf6, 0xd9, 0xdf,
+ 0xf3, 0xec, 0xb3, 0x8b, 0x7d, 0xfe, 0x00, 0xa1, 0x64, 0x58, 0x0e, 0x3e, 0xc1, 0x66, 0x89, 0x32,
+ 0xdc, 0xec, 0x54, 0xb0, 0xad, 0x57, 0xb0, 0x6d, 0x1b, 0x7a, 0x13, 0x33, 0xdd, 0x32, 0x29, 0x71,
+ 0x8e, 0x89, 0xd3, 0x38, 0x21, 0x87, 0x65, 0xdb, 0xb1, 0x98, 0x85, 0xe6, 0x18, 0x33, 0xcb, 0x62,
+ 0x48, 0xf9, 0xf8, 0x4e, 0x71, 0xa7, 0xa5, 0xb3, 0x76, 0xef, 0xb0, 0xdc, 0xb4, 0xba, 0x95, 0x47,
+ 0x6d, 0xf2, 0xa8, 0xad, 0x9b, 0x2d, 0xfa, 0xc0, 0xd4, 0x7a, 0x94, 0x39, 0x3a, 0xa1, 0x15, 0x3e,
+ 0xaa, 0x59, 0x6a, 0x11, 0xb3, 0xd4, 0xb2, 0x4a, 0x47, 0x06, 0x6e, 0xd1, 0x0a, 0x36, 0x4d, 0x8b,
+ 0x79, 0x78, 0x8f, 0x5a, 0xac, 0x06, 0x28, 0xc4, 0x3c, 0xb6, 0x4e, 0x6d, 0xc7, 0xfa, 0xd9, 0x69,
+ 0x70, 0xf0, 0x31, 0x36, 0x74, 0x0d, 0x33, 0x52, 0x89, 0x5d, 0x08, 0x44, 0x29, 0x80, 0x68, 0x59,
+ 0x2d, 0xcb, 0x1b, 0x7c, 0xd8, 0x3b, 0xe2, 0x2d, 0xde, 0xe0, 0x57, 0xc2, 0x7c, 0xb5, 0x65, 0x59,
+ 0x2d, 0x83, 0x78, 0xf3, 0x8d, 0xc5, 0xb3, 0x22, 0x7a, 0xfb, 0x0c, 0xd2, 0xb5, 0xd9, 0xa9, 0xe8,
+ 0x5c, 0x8b, 0x76, 0x1e, 0xe9, 0xc4, 0xd0, 0x1a, 0x5d, 0x4c, 0x3b, 0xc2, 0xe2, 0xcd, 0xa8, 0x05,
+ 0xd3, 0xbb, 0x84, 0x32, 0xdc, 0xb5, 0x85, 0xc1, 0x95, 0x78, 0xd2, 0x89, 0xe3, 0x58, 0x8e, 0xe8,
+ 0xbe, 0x16, 0xef, 0xd6, 0x35, 0x62, 0x32, 0xfd, 0x48, 0x27, 0x8e, 0x88, 0x51, 0xf9, 0x87, 0x04,
+ 0x57, 0xaa, 0x83, 0x95, 0x7a, 0x42, 0x0e, 0xdb, 0x96, 0xd5, 0x79, 0x30, 0xb0, 0x43, 0x4f, 0x61,
+ 0x3e, 0xb0, 0x94, 0x0d, 0x5d, 0xa3, 0xb2, 0xb4, 0x26, 0xad, 0xe7, 0x37, 0x6f, 0x94, 0xc3, 0xab,
+ 0x58, 0x0e, 0x70, 0x02, 0x80, 0x5a, 0xf6, 0x65, 0x6d, 0xf2, 0xd7, 0x52, 0xa6, 0x20, 0xa9, 0x73,
+ 0x38, 0x68, 0x41, 0xd1, 0x2e, 0xc0, 0x89, 0xe7, 0xb0, 0xa1, 0x6b, 0x72, 0x66, 0x4d, 0x5a, 0xcf,
+ 0xd5, 0x6e, 0xbe, 0xac, 0x5d, 0x77, 0x14, 0xf9, 0xfa, 0xe6, 0xd5, 0x1f, 0x3f, 0xc3, 0xa5, 0x4f,
+ 0xdf, 0x2a, 0xdd, 0xfd, 0x78, 0xfd, 0xde, 0xf6, 0xb3, 0xd2, 0xc7, 0xf7, 0xfc, 0xe6, 0xad, 0xe7,
+ 0x9b, 0xb7, 0x5f, 0x5c, 0x57, 0x73, 0x27, 0x7e, 0xac, 0xdb, 0xd9, 0xff, 0x7c, 0xf5, 0xc6, 0x44,
+ 0x56, 0x2a, 0x48, 0xca, 0x73, 0xf8, 0x46, 0x7c, 0x36, 0x8f, 0x48, 0xd7, 0x36, 0x30, 0x23, 0xc1,
+ 0x59, 0xed, 0x43, 0x9e, 0x89, 0xdb, 0xae, 0x6f, 0x29, 0x9d, 0x6f, 0x60, 0x7d, 0x64, 0xc0, 0xf9,
+ 0x2f, 0x32, 0xf0, 0xe6, 0xd9, 0xde, 0x77, 0xdd, 0xf5, 0x45, 0x6f, 0x43, 0x26, 0xbd, 0xbb, 0x8c,
+ 0xae, 0xa1, 0x15, 0x98, 0x30, 0x71, 0x97, 0x88, 0x2c, 0x4d, 0xbf, 0xac, 0x4d, 0x38, 0x19, 0x79,
+ 0x51, 0xe5, 0x37, 0xd1, 0x2d, 0xc8, 0x6b, 0x84, 0x36, 0x1d, 0xdd, 0x76, 0x1d, 0xcb, 0xe3, 0x41,
+ 0x1b, 0x4d, 0x0d, 0xf6, 0xa1, 0x65, 0x98, 0xa2, 0xa4, 0xe9, 0x10, 0x26, 0x4f, 0xac, 0x49, 0xeb,
+ 0x59, 0x55, 0xb4, 0xd0, 0x6d, 0x98, 0xd5, 0xc8, 0x11, 0xee, 0x19, 0xac, 0x71, 0x8c, 0x8d, 0x1e,
+ 0x91, 0x27, 0xc3, 0x90, 0x19, 0xd1, 0xfb, 0x43, 0xb7, 0x13, 0x15, 0x21, 0x6b, 0x71, 0x1e, 0x36,
+ 0xe4, 0x29, 0xce, 0xe9, 0xb7, 0x95, 0x2f, 0xe7, 0xa0, 0x78, 0x76, 0x1a, 0xd0, 0x43, 0x18, 0x1f,
+ 0xec, 0xa1, 0xad, 0x57, 0xec, 0xa1, 0xb3, 0x57, 0x2f, 0xb0, 0xa5, 0x5c, 0xd6, 0xff, 0x2d, 0x37,
+ 0xd7, 0x20, 0x6b, 0x58, 0x2d, 0xab, 0xd1, 0x73, 0x0c, 0x9e, 0x9d, 0x1c, 0x77, 0xe4, 0x8c, 0xff,
+ 0x52, 0x92, 0xd4, 0x69, 0xb7, 0xe7, 0xb1, 0x63, 0xb8, 0x46, 0xba, 0x79, 0xe4, 0x19, 0x4d, 0x46,
+ 0x8d, 0xdc, 0x1e, 0xd7, 0x68, 0x0b, 0x2e, 0x69, 0x56, 0xb3, 0xd7, 0x25, 0xa6, 0x77, 0x24, 0x70,
+ 0xeb, 0xa9, 0x88, 0x75, 0x21, 0x64, 0x22, 0xd8, 0x87, 0x98, 0x12, 0x6e, 0x3d, 0x1d, 0x65, 0xbb,
+ 0x3d, 0xae, 0x51, 0x1b, 0xa6, 0xdb, 0x04, 0x6b, 0xc4, 0xa1, 0x72, 0x76, 0x6d, 0x7c, 0x3d, 0xbf,
+ 0xf9, 0x76, 0xf2, 0x24, 0x96, 0xf7, 0xbd, 0x91, 0xf7, 0x4d, 0xe6, 0x9c, 0xd6, 0x96, 0x5e, 0xd6,
+ 0xd0, 0xef, 0xa4, 0xf9, 0xc2, 0xa6, 0xe2, 0x26, 0xe3, 0xdd, 0x8d, 0x49, 0x67, 0x5c, 0xfe, 0x2c,
+ 0xa3, 0xfa, 0x78, 0x74, 0x0f, 0xa6, 0x8e, 0x2c, 0xa7, 0x8b, 0x99, 0x9c, 0x0b, 0x6e, 0xd8, 0xc5,
+ 0x73, 0x37, 0xac, 0x18, 0x86, 0xf6, 0x60, 0x8a, 0x1f, 0x6b, 0x54, 0x06, 0x1e, 0x69, 0x25, 0x79,
+ 0xa4, 0xfc, 0x71, 0x51, 0xc5, 0x70, 0xb4, 0x05, 0x97, 0x9b, 0x0e, 0x71, 0x1f, 0x56, 0xcd, 0x3a,
+ 0x31, 0x0d, 0xdd, 0xec, 0x34, 0xb0, 0xad, 0x37, 0x3a, 0xe4, 0x54, 0x5e, 0xe0, 0xdb, 0x6f, 0xd1,
+ 0xeb, 0xde, 0x11, 0xbd, 0x55, 0x5b, 0x7f, 0x9f, 0x9c, 0xa2, 0xa7, 0x30, 0xd7, 0xb3, 0xb9, 0x75,
+ 0x97, 0x50, 0x8a, 0x5b, 0x44, 0xce, 0xf3, 0x6d, 0xb7, 0x99, 0x22, 0x63, 0x07, 0xde, 0x48, 0x75,
+ 0xd6, 0x23, 0x89, 0x26, 0x6a, 0xc0, 0x25, 0x81, 0x36, 0xdd, 0xb9, 0x1a, 0xfa, 0xa7, 0x44, 0x93,
+ 0x2f, 0x8f, 0x4c, 0x2f, 0x78, 0xb0, 0x0f, 0xfa, 0x2c, 0x54, 0x87, 0xfc, 0x4f, 0x2d, 0xdd, 0x6c,
+ 0xe0, 0x66, 0x93, 0xd8, 0x4c, 0x9e, 0x19, 0x19, 0x0d, 0x2e, 0xa6, 0xca, 0x29, 0xe8, 0x31, 0xcc,
+ 0x0c, 0x12, 0xd8, 0xec, 0xc8, 0xb3, 0x23, 0x53, 0xf3, 0x3e, 0xa7, 0xda, 0xec, 0xa0, 0x27, 0x30,
+ 0xdb, 0xc7, 0x9a, 0x2e, 0x77, 0x6e, 0x64, 0x6e, 0x3f, 0xbe, 0x0f, 0x70, 0x04, 0x4c, 0x89, 0xc9,
+ 0xe4, 0xf9, 0x8b, 0x83, 0xeb, 0xc4, 0x64, 0xe8, 0x19, 0xcc, 0xf7, 0xc1, 0x47, 0x58, 0x37, 0x88,
+ 0x26, 0x17, 0x46, 0x46, 0xcf, 0xf9, 0xa8, 0x5d, 0x4e, 0x0a, 0xc1, 0x3f, 0xe9, 0x91, 0x1e, 0xd1,
+ 0xe4, 0x4b, 0x17, 0x87, 0x3f, 0xe4, 0x24, 0x64, 0x43, 0x31, 0x0c, 0x6f, 0xe8, 0xa6, 0xaf, 0x62,
+ 0x34, 0x79, 0x69, 0x64, 0x3f, 0x72, 0xc8, 0xcf, 0x83, 0x01, 0xd3, 0x9d, 0x8e, 0x61, 0x89, 0xf2,
+ 0x4f, 0x2d, 0xe3, 0x98, 0x68, 0x32, 0x1a, 0x7d, 0x3a, 0x3e, 0xaa, 0xce, 0x49, 0xee, 0x8e, 0x74,
+ 0xe5, 0xa1, 0xde, 0x24, 0x0d, 0x0d, 0x33, 0x2c, 0x2f, 0x8e, 0xbe, 0x23, 0x05, 0x67, 0x07, 0x33,
+ 0x8c, 0xee, 0x02, 0x0c, 0x04, 0x95, 0xbc, 0xcc, 0xa1, 0xc5, 0xb2, 0xa7, 0xa8, 0xca, 0xbe, 0xa2,
+ 0x2a, 0xf3, 0x43, 0xe6, 0x00, 0xd3, 0x8e, 0x9a, 0x3b, 0xf2, 0x2f, 0x8b, 0xdb, 0x30, 0x13, 0x3c,
+ 0x25, 0x51, 0x01, 0xc6, 0xdd, 0x73, 0x86, 0xd7, 0x6c, 0xd5, 0xbd, 0x44, 0x8b, 0x30, 0xe9, 0xd5,
+ 0x48, 0x5e, 0x70, 0x54, 0xaf, 0xb1, 0x9d, 0xf9, 0x8e, 0x54, 0xbc, 0x01, 0xd3, 0xfe, 0x01, 0xb1,
+ 0x02, 0x13, 0x36, 0x66, 0x6d, 0x51, 0xeb, 0x45, 0xc1, 0x79, 0x57, 0xe5, 0x37, 0x95, 0x16, 0xac,
+ 0x9c, 0x3d, 0x23, 0x57, 0x9d, 0xe4, 0x7c, 0x85, 0xe1, 0x56, 0x4a, 0xf7, 0xe8, 0xdc, 0x48, 0x9e,
+ 0x11, 0x75, 0x30, 0x58, 0xf9, 0x6a, 0x02, 0xe4, 0xb8, 0xe5, 0x3e, 0xc1, 0x06, 0x6b, 0xa3, 0x06,
+ 0xaf, 0x24, 0x06, 0x6b, 0x9f, 0x8a, 0x72, 0xfc, 0xde, 0xf9, 0x4e, 0xbc, 0xa1, 0xe5, 0x50, 0xab,
+ 0xce, 0x30, 0xeb, 0x51, 0xef, 0xfa, 0x74, 0x7f, 0x4c, 0xf5, 0xa9, 0x88, 0x40, 0xae, 0x67, 0xfa,
+ 0x2e, 0x32, 0xdc, 0xc5, 0xfd, 0x8b, 0xb8, 0x78, 0xec, 0xc3, 0xf6, 0xc7, 0xd4, 0x01, 0xb9, 0x78,
+ 0x03, 0x8a, 0x67, 0xc7, 0xd3, 0x17, 0x68, 0x63, 0xc5, 0x5f, 0x65, 0x60, 0xf5, 0x55, 0x54, 0x74,
+ 0x13, 0xe6, 0xbd, 0xc3, 0xa0, 0x81, 0x99, 0x9b, 0x43, 0xe6, 0xe9, 0x94, 0x09, 0x75, 0xce, 0xbb,
+ 0x5d, 0x15, 0x77, 0xd1, 0x53, 0x58, 0x36, 0x30, 0x65, 0x8d, 0xb0, 0x75, 0x03, 0x33, 0x31, 0xcb,
+ 0xf8, 0x56, 0x7b, 0xe4, 0x8b, 0x77, 0x5e, 0xd2, 0xff, 0x22, 0x65, 0xb2, 0x92, 0xba, 0xe0, 0x32,
+ 0x76, 0x83, 0xe4, 0xaa, 0x7b, 0x32, 0xad, 0x0c, 0x43, 0x6b, 0x84, 0x61, 0xdd, 0xa0, 0x5c, 0xbf,
+ 0xe4, 0x37, 0x57, 0xa3, 0x59, 0xbc, 0xef, 0x0a, 0xff, 0x1d, 0xcf, 0x46, 0x95, 0x63, 0x5c, 0xd1,
+ 0x33, 0xc8, 0xc5, 0xe0, 0xaa, 0x96, 0x85, 0x29, 0xca, 0xf3, 0xa0, 0x7c, 0x3e, 0x0f, 0x28, 0xbe,
+ 0x1c, 0x61, 0xc5, 0x56, 0x3a, 0x7f, 0xfd, 0x82, 0x4a, 0xad, 0xe0, 0x2b, 0x35, 0xee, 0x6e, 0x6c,
+ 0x5d, 0x28, 0xb6, 0xf7, 0x00, 0xbc, 0x82, 0xad, 0x25, 0xcc, 0x99, 0x37, 0xbc, 0x30, 0xa6, 0xe6,
+ 0xc4, 0xb8, 0x2a, 0x73, 0x21, 0x3d, 0x5b, 0xf3, 0x21, 0xe3, 0x69, 0x20, 0x62, 0x5c, 0x95, 0x85,
+ 0x24, 0xd7, 0xc4, 0x59, 0x92, 0xeb, 0x27, 0x03, 0xc9, 0x35, 0x99, 0x54, 0xc8, 0x24, 0x90, 0x5a,
+ 0x6b, 0xc3, 0xa4, 0xd6, 0xd4, 0x68, 0x52, 0xeb, 0x47, 0x30, 0x13, 0x78, 0xa1, 0xa1, 0xa2, 0x50,
+ 0x8e, 0xa6, 0xaf, 0xd5, 0xfc, 0xe0, 0xfd, 0x86, 0xa2, 0x06, 0xcc, 0xf7, 0xc9, 0x42, 0xcd, 0x15,
+ 0x78, 0x12, 0xbe, 0x9d, 0x20, 0x09, 0x21, 0x39, 0xe7, 0xe5, 0x42, 0x9d, 0x63, 0xa1, 0x9b, 0x68,
+ 0x13, 0x0a, 0x31, 0x55, 0x77, 0x29, 0xb0, 0x14, 0xf2, 0x67, 0xd2, 0xa0, 0x0a, 0x0a, 0x65, 0xf7,
+ 0x30, 0xa6, 0xec, 0xa6, 0xf9, 0x84, 0x13, 0x1c, 0x93, 0x67, 0x29, 0xba, 0x27, 0xc3, 0x14, 0xdd,
+ 0x72, 0x6a, 0x6a, 0x5c, 0xc9, 0xbd, 0x1f, 0x56, 0x72, 0xd9, 0xd4, 0xc8, 0xa0, 0x82, 0x3b, 0x88,
+ 0x28, 0xb8, 0x5c, 0x6a, 0x5a, 0x48, 0xb9, 0x7d, 0x18, 0x55, 0x6e, 0x90, 0x9a, 0x17, 0x56, 0x6c,
+ 0x1f, 0x46, 0x15, 0x5b, 0x7e, 0x74, 0x20, 0x57, 0x6a, 0xf5, 0xb8, 0x52, 0x9b, 0x49, 0x8d, 0x8c,
+ 0x2a, 0xb4, 0x7a, 0x5c, 0xa1, 0xcd, 0x8e, 0x0e, 0x15, 0xca, 0xac, 0xfd, 0x4a, 0x65, 0xb6, 0x90,
+ 0x9a, 0x7f, 0xb6, 0x22, 0xab, 0xc7, 0x15, 0xd9, 0x5c, 0xfa, 0xf0, 0x23, 0x4a, 0xec, 0x20, 0xa2,
+ 0xc4, 0x50, 0xfa, 0x9d, 0x15, 0x54, 0x60, 0x07, 0x30, 0xeb, 0x95, 0xd5, 0x86, 0x57, 0x5d, 0x84,
+ 0xb2, 0x5b, 0x4f, 0x5a, 0xff, 0xd5, 0x99, 0x76, 0xa0, 0x46, 0x47, 0x04, 0xdd, 0xd2, 0xd7, 0x25,
+ 0xe8, 0xaa, 0xb0, 0x30, 0xe4, 0x08, 0x4b, 0x85, 0x78, 0x2b, 0x99, 0x26, 0x1c, 0x7c, 0x48, 0x0a,
+ 0x7c, 0x52, 0x7a, 0x0c, 0x0b, 0xf1, 0xfc, 0x50, 0xf4, 0x0e, 0x64, 0xc5, 0xd7, 0x2f, 0x5f, 0x1e,
+ 0x2a, 0xe7, 0xa7, 0x55, 0xed, 0x8f, 0x51, 0xfe, 0x2c, 0xc1, 0x1b, 0x71, 0x83, 0x5d, 0x5e, 0x49,
+ 0x28, 0xfa, 0x01, 0x4c, 0x7b, 0x45, 0xc5, 0x87, 0x27, 0x38, 0xe8, 0xc5, 0xd8, 0xb2, 0xf8, 0xf5,
+ 0x0e, 0x7a, 0x1f, 0xe3, 0xae, 0x40, 0xb0, 0x23, 0x4d, 0xfa, 0x94, 0x3f, 0x49, 0xb0, 0xba, 0x47,
+ 0xd8, 0x90, 0xf9, 0x90, 0x4f, 0x7a, 0x84, 0x32, 0xf4, 0xe0, 0x02, 0xf2, 0x24, 0xf2, 0x21, 0x29,
+ 0xbc, 0xc9, 0x32, 0x29, 0x36, 0x99, 0xf2, 0x37, 0x09, 0xae, 0x7e, 0x5f, 0xa7, 0x43, 0xe2, 0xa4,
+ 0x7e, 0xa0, 0xaf, 0xf1, 0x4b, 0xea, 0x05, 0x02, 0xff, 0x52, 0x82, 0xd5, 0xfa, 0xab, 0xf2, 0xbb,
+ 0x0b, 0xd3, 0x62, 0xe3, 0x88, 0x70, 0x13, 0xec, 0xb5, 0x40, 0xa8, 0xfe, 0xe0, 0x8b, 0xc4, 0xf8,
+ 0x57, 0x09, 0xae, 0x0f, 0xdd, 0x03, 0xfd, 0x57, 0x1e, 0x11, 0xeb, 0x6b, 0xf8, 0xb8, 0x78, 0x81,
+ 0xb0, 0x9b, 0x70, 0x63, 0xf8, 0x96, 0xe8, 0xbf, 0xe9, 0xf9, 0x71, 0x87, 0x9d, 0x48, 0x29, 0x9c,
+ 0x6c, 0x7e, 0x9e, 0x1b, 0xf6, 0xb9, 0x55, 0x25, 0x2d, 0x9d, 0xba, 0x8f, 0x9a, 0x01, 0xb0, 0x47,
+ 0x98, 0xff, 0x68, 0x2f, 0xc7, 0x98, 0xf7, 0xbb, 0x36, 0x3b, 0x2d, 0xde, 0x4a, 0xfc, 0x84, 0x2b,
+ 0x2b, 0x3f, 0xff, 0xe7, 0xbf, 0xbf, 0xc8, 0x2c, 0xa1, 0x85, 0x0a, 0xa6, 0x15, 0xb1, 0xb6, 0x25,
+ 0xf1, 0xa0, 0xa3, 0x3f, 0x48, 0x90, 0xdf, 0x23, 0xac, 0xff, 0xb1, 0xf7, 0x5b, 0x51, 0x6e, 0x92,
+ 0x55, 0x2c, 0xa6, 0x78, 0xd7, 0x55, 0x2a, 0x3c, 0x9c, 0x5b, 0xe8, 0x66, 0x30, 0x9c, 0xfe, 0xfb,
+ 0x6f, 0xe5, 0xb9, 0xae, 0xd1, 0x72, 0x40, 0x18, 0xbf, 0x40, 0x5f, 0x48, 0x30, 0xeb, 0xae, 0xca,
+ 0xe0, 0x6d, 0x3b, 0x76, 0xbc, 0x25, 0x5b, 0xb4, 0xe2, 0x37, 0x93, 0x87, 0x49, 0x95, 0x2b, 0x3c,
+ 0xce, 0xcb, 0x68, 0x69, 0x68, 0x9c, 0xe8, 0x8f, 0x12, 0x8c, 0xef, 0x11, 0x86, 0x6e, 0x27, 0x4a,
+ 0x98, 0x1f, 0x41, 0x82, 0x27, 0x51, 0xf9, 0x1e, 0x77, 0xbc, 0x83, 0x6a, 0x01, 0xc7, 0x22, 0x2f,
+ 0x91, 0xd3, 0x28, 0xd2, 0x7e, 0xe1, 0x19, 0x0d, 0xfe, 0xa1, 0x79, 0x81, 0x7e, 0x23, 0xc1, 0x84,
+ 0x9b, 0x1c, 0x54, 0x4e, 0x96, 0xb2, 0x7e, 0xaa, 0xae, 0x9d, 0x1f, 0x28, 0x55, 0xb6, 0x78, 0xa4,
+ 0x15, 0x54, 0x0a, 0x47, 0x7a, 0x4e, 0x94, 0xe8, 0xbf, 0x12, 0x8c, 0xd7, 0x87, 0xa5, 0xae, 0x7e,
+ 0xd1, 0xd4, 0xfd, 0x5e, 0xe2, 0x11, 0xfd, 0x56, 0x2a, 0xaa, 0xe1, 0x90, 0xc4, 0x55, 0x39, 0x51,
+ 0x12, 0x83, 0xc6, 0x81, 0x64, 0x6e, 0x4b, 0x1b, 0x1f, 0xbd, 0xa3, 0xdc, 0x1d, 0x19, 0xbc, 0x2d,
+ 0x6d, 0xb8, 0x7b, 0x79, 0x6a, 0x87, 0x18, 0x84, 0x11, 0x94, 0xae, 0xf0, 0x15, 0xcf, 0x38, 0x08,
+ 0x94, 0x1a, 0x9f, 0xf1, 0x77, 0x37, 0xb6, 0x53, 0xad, 0x41, 0x3f, 0x70, 0xb7, 0x51, 0xdb, 0xfa,
+ 0xfb, 0xbf, 0xae, 0x4a, 0x1f, 0x55, 0x5a, 0x56, 0x99, 0xb5, 0x09, 0xe3, 0xff, 0xe5, 0x96, 0x4d,
+ 0xc2, 0x4e, 0x2c, 0xa7, 0x53, 0x09, 0xff, 0x27, 0x79, 0x7c, 0xa7, 0x62, 0x77, 0x5a, 0x15, 0xc6,
+ 0x4c, 0xfb, 0xf0, 0x70, 0x8a, 0x87, 0x72, 0xe7, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xe0, 0x13,
+ 0xd7, 0xe3, 0x4c, 0x1e, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
diff --git a/pkg/ttnpb/applicationserver_web.pb.paths.fm.go b/pkg/ttnpb/applicationserver_web.pb.paths.fm.go
index 664f1f628d..0f59d502de 100644
--- a/pkg/ttnpb/applicationserver_web.pb.paths.fm.go
+++ b/pkg/ttnpb/applicationserver_web.pb.paths.fm.go
@@ -70,6 +70,8 @@ var ApplicationWebhookTemplateFieldPathsNested = []string{
"service_data.path",
"uplink_message",
"uplink_message.path",
+ "uplink_normalized",
+ "uplink_normalized.path",
}
var ApplicationWebhookTemplateFieldPathsTopLevel = []string{
@@ -95,6 +97,7 @@ var ApplicationWebhookTemplateFieldPathsTopLevel = []string{
"name",
"service_data",
"uplink_message",
+ "uplink_normalized",
}
var ApplicationWebhookTemplatesFieldPathsNested = []string{
"templates",
@@ -183,6 +186,8 @@ var ApplicationWebhookFieldPathsNested = []string{
"updated_at",
"uplink_message",
"uplink_message.path",
+ "uplink_normalized",
+ "uplink_normalized.path",
}
var ApplicationWebhookFieldPathsTopLevel = []string{
@@ -207,6 +212,7 @@ var ApplicationWebhookFieldPathsTopLevel = []string{
"template_ids",
"updated_at",
"uplink_message",
+ "uplink_normalized",
}
var ApplicationWebhooksFieldPathsNested = []string{
"webhooks",
@@ -301,6 +307,8 @@ var SetApplicationWebhookRequestFieldPathsNested = []string{
"webhook.updated_at",
"webhook.uplink_message",
"webhook.uplink_message.path",
+ "webhook.uplink_normalized",
+ "webhook.uplink_normalized.path",
}
var SetApplicationWebhookRequestFieldPathsTopLevel = []string{
diff --git a/pkg/ttnpb/applicationserver_web.pb.setters.fm.go b/pkg/ttnpb/applicationserver_web.pb.setters.fm.go
index 6edb2364f0..399a3a8c6f 100644
--- a/pkg/ttnpb/applicationserver_web.pb.setters.fm.go
+++ b/pkg/ttnpb/applicationserver_web.pb.setters.fm.go
@@ -293,6 +293,31 @@ func (dst *ApplicationWebhookTemplate) SetFields(src *ApplicationWebhookTemplate
dst.UplinkMessage = nil
}
}
+ case "uplink_normalized":
+ if len(subs) > 0 {
+ var newDst, newSrc *ApplicationWebhookTemplate_Message
+ if (src == nil || src.UplinkNormalized == nil) && dst.UplinkNormalized == nil {
+ continue
+ }
+ if src != nil {
+ newSrc = src.UplinkNormalized
+ }
+ if dst.UplinkNormalized != nil {
+ newDst = dst.UplinkNormalized
+ } else {
+ newDst = &ApplicationWebhookTemplate_Message{}
+ dst.UplinkNormalized = newDst
+ }
+ if err := newDst.SetFields(newSrc, subs...); err != nil {
+ return err
+ }
+ } else {
+ if src != nil {
+ dst.UplinkNormalized = src.UplinkNormalized
+ } else {
+ dst.UplinkNormalized = nil
+ }
+ }
case "join_accept":
if len(subs) > 0 {
var newDst, newSrc *ApplicationWebhookTemplate_Message
@@ -803,6 +828,31 @@ func (dst *ApplicationWebhook) SetFields(src *ApplicationWebhook, paths ...strin
dst.UplinkMessage = nil
}
}
+ case "uplink_normalized":
+ if len(subs) > 0 {
+ var newDst, newSrc *ApplicationWebhook_Message
+ if (src == nil || src.UplinkNormalized == nil) && dst.UplinkNormalized == nil {
+ continue
+ }
+ if src != nil {
+ newSrc = src.UplinkNormalized
+ }
+ if dst.UplinkNormalized != nil {
+ newDst = dst.UplinkNormalized
+ } else {
+ newDst = &ApplicationWebhook_Message{}
+ dst.UplinkNormalized = newDst
+ }
+ if err := newDst.SetFields(newSrc, subs...); err != nil {
+ return err
+ }
+ } else {
+ if src != nil {
+ dst.UplinkNormalized = src.UplinkNormalized
+ } else {
+ dst.UplinkNormalized = nil
+ }
+ }
case "join_accept":
if len(subs) > 0 {
var newDst, newSrc *ApplicationWebhook_Message
diff --git a/pkg/ttnpb/applicationserver_web.pb.validate.go b/pkg/ttnpb/applicationserver_web.pb.validate.go
index e8ba19cec0..6d19cb93ce 100644
--- a/pkg/ttnpb/applicationserver_web.pb.validate.go
+++ b/pkg/ttnpb/applicationserver_web.pb.validate.go
@@ -572,6 +572,18 @@ func (m *ApplicationWebhookTemplate) ValidateFields(paths ...string) error {
}
}
+ case "uplink_normalized":
+
+ if v, ok := interface{}(m.GetUplinkNormalized()).(interface{ ValidateFields(...string) error }); ok {
+ if err := v.ValidateFields(subs...); err != nil {
+ return ApplicationWebhookTemplateValidationError{
+ field: "uplink_normalized",
+ reason: "embedded message failed validation",
+ cause: err,
+ }
+ }
+ }
+
case "join_accept":
if v, ok := interface{}(m.GetJoinAccept()).(interface{ ValidateFields(...string) error }); ok {
@@ -1137,6 +1149,18 @@ func (m *ApplicationWebhook) ValidateFields(paths ...string) error {
}
}
+ case "uplink_normalized":
+
+ if v, ok := interface{}(m.GetUplinkNormalized()).(interface{ ValidateFields(...string) error }); ok {
+ if err := v.ValidateFields(subs...); err != nil {
+ return ApplicationWebhookValidationError{
+ field: "uplink_normalized",
+ reason: "embedded message failed validation",
+ cause: err,
+ }
+ }
+ }
+
case "join_accept":
if v, ok := interface{}(m.GetJoinAccept()).(interface{ ValidateFields(...string) error }); ok {
diff --git a/pkg/ttnpb/applicationserver_web_flags.pb.go b/pkg/ttnpb/applicationserver_web_flags.pb.go
index 562f409a3b..84936fa8d3 100644
--- a/pkg/ttnpb/applicationserver_web_flags.pb.go
+++ b/pkg/ttnpb/applicationserver_web_flags.pb.go
@@ -210,6 +210,8 @@ func AddSelectFlagsForApplicationWebhook(flags *pflag.FlagSet, prefix string, hi
flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("downlink-api-key", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("downlink-api-key", prefix), false), flagsplugin.WithHidden(hidden)))
flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("uplink-message", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("uplink-message", prefix), true), flagsplugin.WithHidden(hidden)))
AddSelectFlagsForApplicationWebhook_Message(flags, flagsplugin.Prefix("uplink-message", prefix), hidden)
+ flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("uplink-normalized", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("uplink-normalized", prefix), true), flagsplugin.WithHidden(hidden)))
+ AddSelectFlagsForApplicationWebhook_Message(flags, flagsplugin.Prefix("uplink-normalized", prefix), hidden)
flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("join-accept", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("join-accept", prefix), true), flagsplugin.WithHidden(hidden)))
AddSelectFlagsForApplicationWebhook_Message(flags, flagsplugin.Prefix("join-accept", prefix), hidden)
flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("downlink-ack", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("downlink-ack", prefix), true), flagsplugin.WithHidden(hidden)))
@@ -280,6 +282,16 @@ func PathsFromSelectFlagsForApplicationWebhook(flags *pflag.FlagSet, prefix stri
} else {
paths = append(paths, selectPaths...)
}
+ if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("uplink_normalized", prefix)); err != nil {
+ return nil, err
+ } else if selected && val {
+ paths = append(paths, flagsplugin.Prefix("uplink_normalized", prefix))
+ }
+ if selectPaths, err := PathsFromSelectFlagsForApplicationWebhook_Message(flags, flagsplugin.Prefix("uplink_normalized", prefix)); err != nil {
+ return nil, err
+ } else {
+ paths = append(paths, selectPaths...)
+ }
if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("join_accept", prefix)); err != nil {
return nil, err
} else if selected && val {
@@ -398,6 +410,7 @@ func AddSetFlagsForApplicationWebhook(flags *pflag.FlagSet, prefix string, hidde
flags.AddFlag(flagsplugin.NewStringStringMapFlag(flagsplugin.Prefix("template-fields", prefix), "", flagsplugin.WithHidden(hidden)))
flags.AddFlag(flagsplugin.NewStringFlag(flagsplugin.Prefix("downlink-api-key", prefix), "", flagsplugin.WithHidden(hidden)))
AddSetFlagsForApplicationWebhook_Message(flags, flagsplugin.Prefix("uplink-message", prefix), hidden)
+ AddSetFlagsForApplicationWebhook_Message(flags, flagsplugin.Prefix("uplink-normalized", prefix), hidden)
AddSetFlagsForApplicationWebhook_Message(flags, flagsplugin.Prefix("join-accept", prefix), hidden)
AddSetFlagsForApplicationWebhook_Message(flags, flagsplugin.Prefix("downlink-ack", prefix), hidden)
AddSetFlagsForApplicationWebhook_Message(flags, flagsplugin.Prefix("downlink-nack", prefix), hidden)
@@ -473,6 +486,16 @@ func (m *ApplicationWebhook) SetFromFlags(flags *pflag.FlagSet, prefix string) (
paths = append(paths, setPaths...)
}
}
+ if changed := flagsplugin.IsAnyPrefixSet(flags, flagsplugin.Prefix("uplink_normalized", prefix)); changed {
+ if m.UplinkNormalized == nil {
+ m.UplinkNormalized = &ApplicationWebhook_Message{}
+ }
+ if setPaths, err := m.UplinkNormalized.SetFromFlags(flags, flagsplugin.Prefix("uplink_normalized", prefix)); err != nil {
+ return nil, err
+ } else {
+ paths = append(paths, setPaths...)
+ }
+ }
if changed := flagsplugin.IsAnyPrefixSet(flags, flagsplugin.Prefix("join_accept", prefix)); changed {
if m.JoinAccept == nil {
m.JoinAccept = &ApplicationWebhook_Message{}
diff --git a/pkg/ttnpb/messages.pb.go b/pkg/ttnpb/messages.pb.go
index 0c91986f7f..56fa00904f 100644
--- a/pkg/ttnpb/messages.pb.go
+++ b/pkg/ttnpb/messages.pb.go
@@ -485,8 +485,10 @@ func (m *GatewayUplinkMessage) GetBandId() string {
type ApplicationUplink struct {
// Join Server issued identifier for the session keys used by this uplink.
SessionKeyId []byte `protobuf:"bytes,1,opt,name=session_key_id,json=sessionKeyId,proto3" json:"session_key_id,omitempty"`
- FPort uint32 `protobuf:"varint,2,opt,name=f_port,json=fPort,proto3" json:"f_port,omitempty"`
- FCnt uint32 `protobuf:"varint,3,opt,name=f_cnt,json=fCnt,proto3" json:"f_cnt,omitempty"`
+ // LoRaWAN FPort of the uplink message.
+ FPort uint32 `protobuf:"varint,2,opt,name=f_port,json=fPort,proto3" json:"f_port,omitempty"`
+ // LoRaWAN FCntUp of the uplink message.
+ FCnt uint32 `protobuf:"varint,3,opt,name=f_cnt,json=fCnt,proto3" json:"f_cnt,omitempty"`
// The frame payload of the uplink message.
// The payload is still encrypted if the skip_payload_crypto field of the EndDevice
// is true, which is indicated by the presence of the app_s_key field.
@@ -496,9 +498,16 @@ type ApplicationUplink struct {
DecodedPayload *types.Struct `protobuf:"bytes,5,opt,name=decoded_payload,json=decodedPayload,proto3" json:"decoded_payload,omitempty"`
// Warnings generated by the message processor while decoding the frm_payload.
DecodedPayloadWarnings []string `protobuf:"bytes,12,rep,name=decoded_payload_warnings,json=decodedPayloadWarnings,proto3" json:"decoded_payload_warnings,omitempty"`
+ // The normalized frame payload of the uplink message.
+ // This field is set by the message processor that is configured for the end device (see formatters) or application (see default_formatters).
+ // If the message processor is a custom script, there is no uplink normalizer script and the decoded output is valid
+ // normalized payload, this field contains the decoded payload.
+ NormalizedPayload []*types.Struct `protobuf:"bytes,17,rep,name=normalized_payload,json=normalizedPayload,proto3" json:"normalized_payload,omitempty"`
+ // Warnings generated by the message processor while normalizing the decoded payload.
+ NormalizedPayloadWarnings []string `protobuf:"bytes,18,rep,name=normalized_payload_warnings,json=normalizedPayloadWarnings,proto3" json:"normalized_payload_warnings,omitempty"`
// A list of metadata for each antenna of each gateway that received this message.
RxMetadata []*RxMetadata `protobuf:"bytes,6,rep,name=rx_metadata,json=rxMetadata,proto3" json:"rx_metadata,omitempty"`
- // Settings for the transmission.
+ // Transmission settings used by the end device.
Settings *TxSettings `protobuf:"bytes,7,opt,name=settings,proto3" json:"settings,omitempty"`
// Server time when the Network Server received the message.
ReceivedAt *types.Timestamp `protobuf:"bytes,8,opt,name=received_at,json=receivedAt,proto3" json:"received_at,omitempty"`
@@ -512,8 +521,9 @@ type ApplicationUplink struct {
// is true.
// Can be used with app_s_key to encrypt downlink payloads.
LastAFCntDown uint32 `protobuf:"varint,10,opt,name=last_a_f_cnt_down,json=lastAFCntDown,proto3" json:"last_a_f_cnt_down,omitempty"`
- Confirmed bool `protobuf:"varint,11,opt,name=confirmed,proto3" json:"confirmed,omitempty"`
- // Consumed airtime for the transmission of the uplink message. Calculated by Network Server using the RawPayload size and the transmission settings.
+ // Indicates whether the end device used confirmed data uplink.
+ Confirmed bool `protobuf:"varint,11,opt,name=confirmed,proto3" json:"confirmed,omitempty"`
+ // Consumed airtime for the transmission of the uplink message. Calculated by Network Server using the raw payload size and the transmission settings.
ConsumedAirtime *types.Duration `protobuf:"bytes,13,opt,name=consumed_airtime,json=consumedAirtime,proto3" json:"consumed_airtime,omitempty"`
// End device location metadata, set by the Application Server while handling the message.
Locations map[string]*Location `protobuf:"bytes,14,rep,name=locations,proto3" json:"locations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
@@ -592,6 +602,20 @@ func (m *ApplicationUplink) GetDecodedPayloadWarnings() []string {
return nil
}
+func (m *ApplicationUplink) GetNormalizedPayload() []*types.Struct {
+ if m != nil {
+ return m.NormalizedPayload
+ }
+ return nil
+}
+
+func (m *ApplicationUplink) GetNormalizedPayloadWarnings() []string {
+ if m != nil {
+ return m.NormalizedPayloadWarnings
+ }
+ return nil
+}
+
func (m *ApplicationUplink) GetRxMetadata() []*RxMetadata {
if m != nil {
return m.RxMetadata
@@ -662,6 +686,164 @@ func (m *ApplicationUplink) GetNetworkIds() *NetworkIdentifiers {
return nil
}
+type ApplicationUplinkNormalized struct {
+ // Join Server issued identifier for the session keys used by this uplink.
+ SessionKeyId []byte `protobuf:"bytes,1,opt,name=session_key_id,json=sessionKeyId,proto3" json:"session_key_id,omitempty"`
+ // LoRaWAN FPort of the uplink message.
+ FPort uint32 `protobuf:"varint,2,opt,name=f_port,json=fPort,proto3" json:"f_port,omitempty"`
+ // LoRaWAN FCntUp of the uplink message.
+ FCnt uint32 `protobuf:"varint,3,opt,name=f_cnt,json=fCnt,proto3" json:"f_cnt,omitempty"`
+ // The frame payload of the uplink message.
+ // This field is always decrypted with AppSKey.
+ FrmPayload []byte `protobuf:"bytes,4,opt,name=frm_payload,json=frmPayload,proto3" json:"frm_payload,omitempty"`
+ // The normalized frame payload of the uplink message.
+ // This field is set for each item in normalized_payload in the corresponding ApplicationUplink message.
+ NormalizedPayload *types.Struct `protobuf:"bytes,5,opt,name=normalized_payload,json=normalizedPayload,proto3" json:"normalized_payload,omitempty"`
+ // This field is set to normalized_payload_warnings in the corresponding ApplicationUplink message.
+ NormalizedPayloadWarnings []string `protobuf:"bytes,6,rep,name=normalized_payload_warnings,json=normalizedPayloadWarnings,proto3" json:"normalized_payload_warnings,omitempty"`
+ // A list of metadata for each antenna of each gateway that received this message.
+ RxMetadata []*RxMetadata `protobuf:"bytes,7,rep,name=rx_metadata,json=rxMetadata,proto3" json:"rx_metadata,omitempty"`
+ // Transmission settings used by the end device.
+ Settings *TxSettings `protobuf:"bytes,8,opt,name=settings,proto3" json:"settings,omitempty"`
+ // Server time when the Network Server received the message.
+ ReceivedAt *types.Timestamp `protobuf:"bytes,9,opt,name=received_at,json=receivedAt,proto3" json:"received_at,omitempty"`
+ // Indicates whether the end device used confirmed data uplink.
+ Confirmed bool `protobuf:"varint,10,opt,name=confirmed,proto3" json:"confirmed,omitempty"`
+ // Consumed airtime for the transmission of the uplink message. Calculated by Network Server using the raw payload size and the transmission settings.
+ ConsumedAirtime *types.Duration `protobuf:"bytes,11,opt,name=consumed_airtime,json=consumedAirtime,proto3" json:"consumed_airtime,omitempty"`
+ // End device location metadata, set by the Application Server while handling the message.
+ Locations map[string]*Location `protobuf:"bytes,12,rep,name=locations,proto3" json:"locations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+ // End device version identifiers, set by the Application Server while handling the message.
+ VersionIds *EndDeviceVersionIdentifiers `protobuf:"bytes,13,opt,name=version_ids,json=versionIds,proto3" json:"version_ids,omitempty"`
+ // Network identifiers, set by the Network Server that handles the message.
+ NetworkIds *NetworkIdentifiers `protobuf:"bytes,14,opt,name=network_ids,json=networkIds,proto3" json:"network_ids,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *ApplicationUplinkNormalized) Reset() { *m = ApplicationUplinkNormalized{} }
+func (m *ApplicationUplinkNormalized) String() string { return proto.CompactTextString(m) }
+func (*ApplicationUplinkNormalized) ProtoMessage() {}
+func (*ApplicationUplinkNormalized) Descriptor() ([]byte, []int) {
+ return fileDescriptor_bbc6bff5780bdc9d, []int{6}
+}
+func (m *ApplicationUplinkNormalized) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_ApplicationUplinkNormalized.Unmarshal(m, b)
+}
+func (m *ApplicationUplinkNormalized) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_ApplicationUplinkNormalized.Marshal(b, m, deterministic)
+}
+func (m *ApplicationUplinkNormalized) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ApplicationUplinkNormalized.Merge(m, src)
+}
+func (m *ApplicationUplinkNormalized) XXX_Size() int {
+ return xxx_messageInfo_ApplicationUplinkNormalized.Size(m)
+}
+func (m *ApplicationUplinkNormalized) XXX_DiscardUnknown() {
+ xxx_messageInfo_ApplicationUplinkNormalized.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ApplicationUplinkNormalized proto.InternalMessageInfo
+
+func (m *ApplicationUplinkNormalized) GetSessionKeyId() []byte {
+ if m != nil {
+ return m.SessionKeyId
+ }
+ return nil
+}
+
+func (m *ApplicationUplinkNormalized) GetFPort() uint32 {
+ if m != nil {
+ return m.FPort
+ }
+ return 0
+}
+
+func (m *ApplicationUplinkNormalized) GetFCnt() uint32 {
+ if m != nil {
+ return m.FCnt
+ }
+ return 0
+}
+
+func (m *ApplicationUplinkNormalized) GetFrmPayload() []byte {
+ if m != nil {
+ return m.FrmPayload
+ }
+ return nil
+}
+
+func (m *ApplicationUplinkNormalized) GetNormalizedPayload() *types.Struct {
+ if m != nil {
+ return m.NormalizedPayload
+ }
+ return nil
+}
+
+func (m *ApplicationUplinkNormalized) GetNormalizedPayloadWarnings() []string {
+ if m != nil {
+ return m.NormalizedPayloadWarnings
+ }
+ return nil
+}
+
+func (m *ApplicationUplinkNormalized) GetRxMetadata() []*RxMetadata {
+ if m != nil {
+ return m.RxMetadata
+ }
+ return nil
+}
+
+func (m *ApplicationUplinkNormalized) GetSettings() *TxSettings {
+ if m != nil {
+ return m.Settings
+ }
+ return nil
+}
+
+func (m *ApplicationUplinkNormalized) GetReceivedAt() *types.Timestamp {
+ if m != nil {
+ return m.ReceivedAt
+ }
+ return nil
+}
+
+func (m *ApplicationUplinkNormalized) GetConfirmed() bool {
+ if m != nil {
+ return m.Confirmed
+ }
+ return false
+}
+
+func (m *ApplicationUplinkNormalized) GetConsumedAirtime() *types.Duration {
+ if m != nil {
+ return m.ConsumedAirtime
+ }
+ return nil
+}
+
+func (m *ApplicationUplinkNormalized) GetLocations() map[string]*Location {
+ if m != nil {
+ return m.Locations
+ }
+ return nil
+}
+
+func (m *ApplicationUplinkNormalized) GetVersionIds() *EndDeviceVersionIdentifiers {
+ if m != nil {
+ return m.VersionIds
+ }
+ return nil
+}
+
+func (m *ApplicationUplinkNormalized) GetNetworkIds() *NetworkIdentifiers {
+ if m != nil {
+ return m.NetworkIds
+ }
+ return nil
+}
+
type ApplicationLocation struct {
Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"`
Location *Location `protobuf:"bytes,2,opt,name=location,proto3" json:"location,omitempty"`
@@ -675,7 +857,7 @@ func (m *ApplicationLocation) Reset() { *m = ApplicationLocation{} }
func (m *ApplicationLocation) String() string { return proto.CompactTextString(m) }
func (*ApplicationLocation) ProtoMessage() {}
func (*ApplicationLocation) Descriptor() ([]byte, []int) {
- return fileDescriptor_bbc6bff5780bdc9d, []int{6}
+ return fileDescriptor_bbc6bff5780bdc9d, []int{7}
}
func (m *ApplicationLocation) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ApplicationLocation.Unmarshal(m, b)
@@ -737,7 +919,7 @@ func (m *ApplicationJoinAccept) Reset() { *m = ApplicationJoinAccept{} }
func (m *ApplicationJoinAccept) String() string { return proto.CompactTextString(m) }
func (*ApplicationJoinAccept) ProtoMessage() {}
func (*ApplicationJoinAccept) Descriptor() ([]byte, []int) {
- return fileDescriptor_bbc6bff5780bdc9d, []int{7}
+ return fileDescriptor_bbc6bff5780bdc9d, []int{8}
}
func (m *ApplicationJoinAccept) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ApplicationJoinAccept.Unmarshal(m, b)
@@ -825,7 +1007,7 @@ func (m *ApplicationDownlink) Reset() { *m = ApplicationDownlink{} }
func (m *ApplicationDownlink) String() string { return proto.CompactTextString(m) }
func (*ApplicationDownlink) ProtoMessage() {}
func (*ApplicationDownlink) Descriptor() ([]byte, []int) {
- return fileDescriptor_bbc6bff5780bdc9d, []int{8}
+ return fileDescriptor_bbc6bff5780bdc9d, []int{9}
}
func (m *ApplicationDownlink) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ApplicationDownlink.Unmarshal(m, b)
@@ -936,7 +1118,7 @@ func (m *ApplicationDownlink_ClassBC) Reset() { *m = ApplicationDownlink
func (m *ApplicationDownlink_ClassBC) String() string { return proto.CompactTextString(m) }
func (*ApplicationDownlink_ClassBC) ProtoMessage() {}
func (*ApplicationDownlink_ClassBC) Descriptor() ([]byte, []int) {
- return fileDescriptor_bbc6bff5780bdc9d, []int{8, 0}
+ return fileDescriptor_bbc6bff5780bdc9d, []int{9, 0}
}
func (m *ApplicationDownlink_ClassBC) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ApplicationDownlink_ClassBC.Unmarshal(m, b)
@@ -981,7 +1163,7 @@ func (m *ApplicationDownlinks) Reset() { *m = ApplicationDownlinks{} }
func (m *ApplicationDownlinks) String() string { return proto.CompactTextString(m) }
func (*ApplicationDownlinks) ProtoMessage() {}
func (*ApplicationDownlinks) Descriptor() ([]byte, []int) {
- return fileDescriptor_bbc6bff5780bdc9d, []int{9}
+ return fileDescriptor_bbc6bff5780bdc9d, []int{10}
}
func (m *ApplicationDownlinks) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ApplicationDownlinks.Unmarshal(m, b)
@@ -1020,7 +1202,7 @@ func (m *ApplicationDownlinkFailed) Reset() { *m = ApplicationDownlinkFa
func (m *ApplicationDownlinkFailed) String() string { return proto.CompactTextString(m) }
func (*ApplicationDownlinkFailed) ProtoMessage() {}
func (*ApplicationDownlinkFailed) Descriptor() ([]byte, []int) {
- return fileDescriptor_bbc6bff5780bdc9d, []int{10}
+ return fileDescriptor_bbc6bff5780bdc9d, []int{11}
}
func (m *ApplicationDownlinkFailed) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ApplicationDownlinkFailed.Unmarshal(m, b)
@@ -1067,7 +1249,7 @@ func (m *ApplicationInvalidatedDownlinks) Reset() { *m = ApplicationInva
func (m *ApplicationInvalidatedDownlinks) String() string { return proto.CompactTextString(m) }
func (*ApplicationInvalidatedDownlinks) ProtoMessage() {}
func (*ApplicationInvalidatedDownlinks) Descriptor() ([]byte, []int) {
- return fileDescriptor_bbc6bff5780bdc9d, []int{11}
+ return fileDescriptor_bbc6bff5780bdc9d, []int{12}
}
func (m *ApplicationInvalidatedDownlinks) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ApplicationInvalidatedDownlinks.Unmarshal(m, b)
@@ -1124,7 +1306,7 @@ func (m *DownlinkQueueOperationErrorDetails) Reset() { *m = DownlinkQueu
func (m *DownlinkQueueOperationErrorDetails) String() string { return proto.CompactTextString(m) }
func (*DownlinkQueueOperationErrorDetails) ProtoMessage() {}
func (*DownlinkQueueOperationErrorDetails) Descriptor() ([]byte, []int) {
- return fileDescriptor_bbc6bff5780bdc9d, []int{12}
+ return fileDescriptor_bbc6bff5780bdc9d, []int{13}
}
func (m *DownlinkQueueOperationErrorDetails) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DownlinkQueueOperationErrorDetails.Unmarshal(m, b)
@@ -1198,7 +1380,7 @@ func (m *ApplicationServiceData) Reset() { *m = ApplicationServiceData{}
func (m *ApplicationServiceData) String() string { return proto.CompactTextString(m) }
func (*ApplicationServiceData) ProtoMessage() {}
func (*ApplicationServiceData) Descriptor() ([]byte, []int) {
- return fileDescriptor_bbc6bff5780bdc9d, []int{13}
+ return fileDescriptor_bbc6bff5780bdc9d, []int{14}
}
func (m *ApplicationServiceData) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ApplicationServiceData.Unmarshal(m, b)
@@ -1240,6 +1422,7 @@ type ApplicationUp struct {
ReceivedAt *types.Timestamp `protobuf:"bytes,12,opt,name=received_at,json=receivedAt,proto3" json:"received_at,omitempty"`
// Types that are valid to be assigned to Up:
// *ApplicationUp_UplinkMessage
+ // *ApplicationUp_UplinkNormalized
// *ApplicationUp_JoinAccept
// *ApplicationUp_DownlinkAck
// *ApplicationUp_DownlinkNack
@@ -1261,7 +1444,7 @@ func (m *ApplicationUp) Reset() { *m = ApplicationUp{} }
func (m *ApplicationUp) String() string { return proto.CompactTextString(m) }
func (*ApplicationUp) ProtoMessage() {}
func (*ApplicationUp) Descriptor() ([]byte, []int) {
- return fileDescriptor_bbc6bff5780bdc9d, []int{14}
+ return fileDescriptor_bbc6bff5780bdc9d, []int{15}
}
func (m *ApplicationUp) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ApplicationUp.Unmarshal(m, b)
@@ -1288,6 +1471,9 @@ type isApplicationUp_Up interface {
type ApplicationUp_UplinkMessage struct {
UplinkMessage *ApplicationUplink `protobuf:"bytes,3,opt,name=uplink_message,json=uplinkMessage,proto3,oneof" json:"uplink_message,omitempty"`
}
+type ApplicationUp_UplinkNormalized struct {
+ UplinkNormalized *ApplicationUplinkNormalized `protobuf:"bytes,15,opt,name=uplink_normalized,json=uplinkNormalized,proto3,oneof" json:"uplink_normalized,omitempty"`
+}
type ApplicationUp_JoinAccept struct {
JoinAccept *ApplicationJoinAccept `protobuf:"bytes,4,opt,name=join_accept,json=joinAccept,proto3,oneof" json:"join_accept,omitempty"`
}
@@ -1317,6 +1503,7 @@ type ApplicationUp_ServiceData struct {
}
func (*ApplicationUp_UplinkMessage) isApplicationUp_Up() {}
+func (*ApplicationUp_UplinkNormalized) isApplicationUp_Up() {}
func (*ApplicationUp_JoinAccept) isApplicationUp_Up() {}
func (*ApplicationUp_DownlinkAck) isApplicationUp_Up() {}
func (*ApplicationUp_DownlinkNack) isApplicationUp_Up() {}
@@ -1362,6 +1549,13 @@ func (m *ApplicationUp) GetUplinkMessage() *ApplicationUplink {
return nil
}
+func (m *ApplicationUp) GetUplinkNormalized() *ApplicationUplinkNormalized {
+ if x, ok := m.GetUp().(*ApplicationUp_UplinkNormalized); ok {
+ return x.UplinkNormalized
+ }
+ return nil
+}
+
func (m *ApplicationUp) GetJoinAccept() *ApplicationJoinAccept {
if x, ok := m.GetUp().(*ApplicationUp_JoinAccept); ok {
return x.JoinAccept
@@ -1436,6 +1630,7 @@ func (m *ApplicationUp) GetSimulated() bool {
func (*ApplicationUp) XXX_OneofWrappers() []interface{} {
return []interface{}{
(*ApplicationUp_UplinkMessage)(nil),
+ (*ApplicationUp_UplinkNormalized)(nil),
(*ApplicationUp_JoinAccept)(nil),
(*ApplicationUp_DownlinkAck)(nil),
(*ApplicationUp_DownlinkNack)(nil),
@@ -1466,7 +1661,7 @@ func (m *MessagePayloadFormatters) Reset() { *m = MessagePayloadFormatte
func (m *MessagePayloadFormatters) String() string { return proto.CompactTextString(m) }
func (*MessagePayloadFormatters) ProtoMessage() {}
func (*MessagePayloadFormatters) Descriptor() ([]byte, []int) {
- return fileDescriptor_bbc6bff5780bdc9d, []int{15}
+ return fileDescriptor_bbc6bff5780bdc9d, []int{16}
}
func (m *MessagePayloadFormatters) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_MessagePayloadFormatters.Unmarshal(m, b)
@@ -1526,7 +1721,7 @@ func (m *DownlinkQueueRequest) Reset() { *m = DownlinkQueueRequest{} }
func (m *DownlinkQueueRequest) String() string { return proto.CompactTextString(m) }
func (*DownlinkQueueRequest) ProtoMessage() {}
func (*DownlinkQueueRequest) Descriptor() ([]byte, []int) {
- return fileDescriptor_bbc6bff5780bdc9d, []int{16}
+ return fileDescriptor_bbc6bff5780bdc9d, []int{17}
}
func (m *DownlinkQueueRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DownlinkQueueRequest.Unmarshal(m, b)
@@ -1579,6 +1774,10 @@ func init() {
golang_proto.RegisterType((*ApplicationUplink)(nil), "ttn.lorawan.v3.ApplicationUplink")
proto.RegisterMapType((map[string]*Location)(nil), "ttn.lorawan.v3.ApplicationUplink.LocationsEntry")
golang_proto.RegisterMapType((map[string]*Location)(nil), "ttn.lorawan.v3.ApplicationUplink.LocationsEntry")
+ proto.RegisterType((*ApplicationUplinkNormalized)(nil), "ttn.lorawan.v3.ApplicationUplinkNormalized")
+ golang_proto.RegisterType((*ApplicationUplinkNormalized)(nil), "ttn.lorawan.v3.ApplicationUplinkNormalized")
+ proto.RegisterMapType((map[string]*Location)(nil), "ttn.lorawan.v3.ApplicationUplinkNormalized.LocationsEntry")
+ golang_proto.RegisterMapType((map[string]*Location)(nil), "ttn.lorawan.v3.ApplicationUplinkNormalized.LocationsEntry")
proto.RegisterType((*ApplicationLocation)(nil), "ttn.lorawan.v3.ApplicationLocation")
golang_proto.RegisterType((*ApplicationLocation)(nil), "ttn.lorawan.v3.ApplicationLocation")
proto.RegisterMapType((map[string]string)(nil), "ttn.lorawan.v3.ApplicationLocation.AttributesEntry")
@@ -1613,168 +1812,179 @@ func init() {
}
var fileDescriptor_bbc6bff5780bdc9d = []byte{
- // 2603 bytes of a gzipped FileDescriptorProto
- 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x59, 0x4d, 0x8c, 0x23, 0x47,
- 0xf5, 0x9f, 0xb6, 0x3d, 0xfe, 0x78, 0xfe, 0x98, 0xde, 0xca, 0x64, 0xd3, 0x33, 0xff, 0x4d, 0x76,
- 0xfe, 0xce, 0x86, 0x4c, 0x36, 0x8c, 0xbd, 0xcc, 0xb2, 0x24, 0x2c, 0x84, 0xc5, 0xf6, 0x78, 0x77,
- 0x3c, 0x1f, 0xb6, 0x53, 0xf6, 0x6e, 0x76, 0x89, 0x42, 0xab, 0xc7, 0x5d, 0xe3, 0xe9, 0x8c, 0xdd,
- 0xdd, 0xe9, 0x2e, 0x7b, 0xc6, 0x41, 0x48, 0x51, 0x6e, 0x44, 0xe2, 0x40, 0x2e, 0x88, 0x80, 0x10,
- 0x02, 0x29, 0x87, 0x15, 0xe2, 0x8c, 0xc4, 0x25, 0x17, 0xa4, 0x08, 0xc1, 0x99, 0x2b, 0x08, 0x4e,
- 0x91, 0xb8, 0xe4, 0x84, 0xe6, 0x12, 0xd4, 0xd5, 0xd5, 0x76, 0x77, 0xdb, 0x99, 0x9d, 0x49, 0x08,
- 0x82, 0xcb, 0xc8, 0x5d, 0xf5, 0xde, 0xaf, 0x5e, 0xbd, 0xf7, 0xea, 0xfd, 0x5e, 0xd5, 0xc0, 0x4a,
- 0xcf, 0xb0, 0x94, 0x23, 0x45, 0x5f, 0xb3, 0xa9, 0xd2, 0x39, 0x2c, 0x2a, 0xa6, 0x56, 0xec, 0x13,
- 0xdb, 0x56, 0xba, 0xc4, 0x2e, 0x98, 0x96, 0x41, 0x0d, 0x94, 0xa3, 0x54, 0x2f, 0x70, 0xa9, 0xc2,
- 0xf0, 0xfa, 0x72, 0xa9, 0xab, 0xd1, 0x83, 0xc1, 0x5e, 0xa1, 0x63, 0xf4, 0x8b, 0x44, 0x1f, 0x1a,
- 0x23, 0xd3, 0x32, 0x8e, 0x47, 0x45, 0x26, 0xdc, 0x59, 0xeb, 0x12, 0x7d, 0x6d, 0xa8, 0xf4, 0x34,
- 0x55, 0xa1, 0xa4, 0x38, 0xf5, 0xc3, 0x85, 0x5c, 0x5e, 0xf3, 0x41, 0x74, 0x8d, 0xae, 0xe1, 0x2a,
- 0xef, 0x0d, 0xf6, 0xd9, 0x17, 0xfb, 0x60, 0xbf, 0xb8, 0x78, 0xc5, 0x27, 0xde, 0x3e, 0x20, 0xed,
- 0x03, 0x4d, 0xef, 0xda, 0x35, 0x5d, 0x1d, 0xd8, 0xd4, 0xd2, 0x88, 0xed, 0x5f, 0xba, 0x6b, 0xac,
- 0xbd, 0x6e, 0x1b, 0x7a, 0x51, 0xd1, 0x75, 0x83, 0x2a, 0x54, 0x33, 0x74, 0xbe, 0x8d, 0xe5, 0x8d,
- 0x73, 0x81, 0xec, 0xf7, 0x94, 0xae, 0x3d, 0x03, 0xe5, 0x52, 0xd7, 0x30, 0xba, 0x3d, 0x32, 0x31,
- 0xd8, 0xa6, 0xd6, 0xa0, 0x43, 0xf9, 0xec, 0xe5, 0xf0, 0x2c, 0xd5, 0xfa, 0xc4, 0xa6, 0x4a, 0xdf,
- 0xe4, 0x02, 0x4f, 0x85, 0x05, 0xd4, 0x81, 0xc5, 0xf0, 0xf9, 0xfc, 0x93, 0xd3, 0xd1, 0x20, 0x96,
- 0x65, 0x58, 0x7c, 0xfa, 0xe9, 0xe9, 0x69, 0x4d, 0x25, 0x3a, 0xd5, 0xf6, 0x35, 0x62, 0x8d, 0x4d,
- 0x9c, 0x16, 0x3a, 0x24, 0x23, 0x6f, 0xf6, 0xf2, 0xf4, 0xac, 0x17, 0x5b, 0x57, 0x60, 0x66, 0x42,
- 0x50, 0x45, 0x55, 0xa8, 0xc2, 0x25, 0x0a, 0x3e, 0x57, 0x19, 0x26, 0xd1, 0x15, 0x53, 0x1b, 0xae,
- 0x17, 0x0d, 0x93, 0xf9, 0x69, 0xda, 0x67, 0xf9, 0xbf, 0x47, 0x21, 0x7b, 0xd7, 0xec, 0x69, 0xfa,
- 0xe1, 0xae, 0x9b, 0x59, 0xe8, 0x32, 0xa4, 0x2d, 0xe5, 0x48, 0x36, 0x95, 0x51, 0xcf, 0x50, 0x54,
- 0x49, 0x58, 0x11, 0x56, 0x33, 0x18, 0x2c, 0xe5, 0xa8, 0xe9, 0x8e, 0xa0, 0xaf, 0x40, 0xc2, 0x9b,
- 0x8c, 0xac, 0x08, 0xab, 0xe9, 0xf5, 0x27, 0x0a, 0xc1, 0x2c, 0x2c, 0x70, 0x28, 0xec, 0xc9, 0xa1,
- 0x6f, 0x43, 0xd2, 0x26, 0x94, 0x3a, 0x51, 0x95, 0x62, 0x4c, 0x67, 0x39, 0xac, 0xd3, 0x3e, 0x6e,
- 0x71, 0x89, 0x72, 0xf2, 0xa4, 0x3c, 0xff, 0x8e, 0x10, 0x11, 0x05, 0x3c, 0xd6, 0x42, 0xdf, 0x80,
- 0xb4, 0x75, 0x2c, 0x7b, 0x9b, 0x95, 0xe6, 0x57, 0xa2, 0xb3, 0x40, 0xf0, 0xf1, 0x2e, 0x97, 0xc0,
- 0x60, 0x8d, 0x7f, 0x33, 0x65, 0xd2, 0x21, 0xda, 0x90, 0xa8, 0xb2, 0x42, 0xa5, 0x38, 0xb7, 0xc0,
- 0x8d, 0x77, 0xc1, 0x8b, 0x77, 0xa1, 0xed, 0x25, 0x04, 0x06, 0x4f, 0xbc, 0x44, 0xd1, 0x0d, 0x58,
- 0xe8, 0x18, 0x96, 0x45, 0x7a, 0xcc, 0x6f, 0xb2, 0xa6, 0xda, 0x52, 0x62, 0x25, 0xba, 0x9a, 0x2a,
- 0x67, 0x4e, 0xca, 0xa9, 0x77, 0x85, 0x78, 0x3e, 0x66, 0x45, 0x24, 0x15, 0xe7, 0x7c, 0x42, 0x35,
- 0xd5, 0x46, 0x37, 0x61, 0x51, 0x25, 0x43, 0xad, 0x43, 0xe4, 0xce, 0x81, 0xa2, 0xeb, 0xa4, 0x27,
- 0x6b, 0xba, 0x4a, 0x8e, 0xa5, 0xd4, 0x8a, 0xb0, 0x9a, 0x65, 0x5b, 0xbc, 0x1a, 0x95, 0x3e, 0x11,
- 0x30, 0x72, 0xa5, 0x2a, 0xae, 0x50, 0xcd, 0x91, 0x41, 0x1b, 0x20, 0x76, 0x0c, 0xdd, 0x1e, 0xf4,
- 0x1d, 0x7b, 0x35, 0xcb, 0x49, 0x54, 0x09, 0x98, 0xd1, 0x4b, 0x53, 0x46, 0x6f, 0xf0, 0x24, 0xc5,
- 0x0b, 0x9e, 0x4a, 0xc9, 0xd5, 0xd8, 0x8a, 0x25, 0xa3, 0x62, 0x6c, 0x2b, 0x96, 0x4c, 0x8a, 0xa9,
- 0xfc, 0xcf, 0xa2, 0xb0, 0xb0, 0x61, 0x1c, 0xe9, 0x5f, 0x74, 0xa0, 0xb7, 0x20, 0x47, 0x74, 0x55,
- 0xe6, 0x3b, 0x77, 0x7c, 0x15, 0x65, 0x9a, 0x57, 0xc2, 0x9a, 0x55, 0x5d, 0xdd, 0x60, 0x42, 0xb5,
- 0xc9, 0x19, 0xc1, 0x19, 0x32, 0x19, 0xb5, 0xd1, 0x0d, 0x48, 0x58, 0xe4, 0x8d, 0x01, 0xb1, 0x29,
- 0xcf, 0x99, 0xa5, 0xe9, 0x9c, 0xc1, 0xae, 0xc0, 0xe6, 0x1c, 0xf6, 0x64, 0xd1, 0x4d, 0x48, 0xd9,
- 0x9d, 0x03, 0xa2, 0x0e, 0x7a, 0x44, 0x95, 0xe6, 0x1f, 0x95, 0x6c, 0x9b, 0x73, 0x78, 0x22, 0x3e,
- 0x2b, 0xd6, 0xf1, 0x33, 0xc4, 0xba, 0x00, 0x39, 0x9b, 0xd8, 0xb6, 0xa3, 0x72, 0x48, 0x46, 0xb2,
- 0xa6, 0x4a, 0x09, 0xc7, 0x99, 0x2c, 0xca, 0x6f, 0x46, 0xa5, 0xb7, 0x44, 0x9c, 0xe1, 0xf3, 0xdb,
- 0x64, 0x54, 0x53, 0xcb, 0x0b, 0x93, 0xe3, 0x80, 0xa2, 0xff, 0x2c, 0x0b, 0xf9, 0x1f, 0x44, 0x41,
- 0x6c, 0x1f, 0x97, 0x3a, 0x87, 0xba, 0x71, 0xd4, 0x23, 0x6a, 0xb7, 0x4f, 0xf4, 0x99, 0x89, 0x27,
- 0x9c, 0xc1, 0x98, 0x1a, 0xc4, 0x2d, 0x62, 0x0f, 0x7a, 0x94, 0x05, 0x2d, 0xb7, 0xfe, 0xec, 0xf4,
- 0xe6, 0x83, 0x0b, 0x15, 0x30, 0x13, 0x67, 0xd6, 0xbe, 0xcd, 0x8e, 0x1d, 0x07, 0x40, 0x5b, 0x20,
- 0xaa, 0x3c, 0x69, 0x64, 0x4e, 0x3c, 0x3c, 0x9e, 0x97, 0xc3, 0xa0, 0xa1, 0xe4, 0xc2, 0x0b, 0x6a,
- 0x70, 0x20, 0xff, 0xbe, 0x00, 0x71, 0x77, 0x21, 0x94, 0x86, 0x44, 0xeb, 0x6e, 0xa5, 0x52, 0x6d,
- 0xb5, 0xc4, 0x39, 0x74, 0x01, 0xb2, 0x77, 0xeb, 0xdb, 0xf5, 0xc6, 0x2b, 0x75, 0xb9, 0x8a, 0x71,
- 0x03, 0x8b, 0x02, 0xca, 0x40, 0xb2, 0xdd, 0x68, 0xc8, 0x3b, 0xa5, 0x76, 0x55, 0x8c, 0xa0, 0x2c,
- 0xa4, 0x9c, 0xaf, 0x6a, 0x09, 0xef, 0x3c, 0x10, 0xa3, 0x68, 0x11, 0xc4, 0x4a, 0x63, 0x67, 0xa7,
- 0xd6, 0xaa, 0x35, 0xea, 0x72, 0xb3, 0x54, 0xd9, 0xae, 0xb6, 0xc5, 0x58, 0x70, 0xb4, 0x5c, 0x2d,
- 0x55, 0x1a, 0x75, 0x71, 0xde, 0x59, 0xa8, 0x7d, 0x5f, 0xbe, 0x8d, 0xab, 0x2f, 0x8b, 0x71, 0x86,
- 0x7a, 0x5f, 0x6e, 0x36, 0x5e, 0xa9, 0x62, 0x31, 0x81, 0x44, 0xc8, 0xdc, 0x69, 0xb6, 0xe4, 0xbb,
- 0xf5, 0x9d, 0x46, 0x65, 0xbb, 0xba, 0x21, 0x26, 0x97, 0xe3, 0x1f, 0x3d, 0x5c, 0x8a, 0x48, 0x42,
- 0xfe, 0xc7, 0x02, 0x3c, 0x71, 0x47, 0xa1, 0xe4, 0x48, 0x19, 0x4d, 0x85, 0xa4, 0x02, 0xe9, 0xae,
- 0x3b, 0xc5, 0xc3, 0xe1, 0xf8, 0x22, 0x1f, 0xf6, 0x05, 0xd7, 0xf6, 0x67, 0x36, 0x74, 0xbd, 0x31,
- 0x1b, 0xbd, 0x00, 0x71, 0x7a, 0x2c, 0x2b, 0x9d, 0x43, 0x7e, 0xaa, 0x56, 0x1e, 0x15, 0x20, 0x3c,
- 0x4f, 0x9d, 0x91, 0xbc, 0x05, 0x8b, 0x1c, 0x3a, 0x58, 0xb1, 0x4b, 0x90, 0xf0, 0xa2, 0xe3, 0x5a,
- 0xf4, 0x64, 0x18, 0x31, 0x20, 0xef, 0xab, 0xaf, 0x9e, 0x1e, 0x7a, 0x02, 0x12, 0x7b, 0x8a, 0xae,
- 0x3a, 0xa9, 0xeb, 0x18, 0x95, 0xc2, 0x71, 0xe7, 0xb3, 0xa6, 0xe6, 0xff, 0x9a, 0x80, 0x0b, 0x25,
- 0xd3, 0xec, 0x69, 0x1d, 0x96, 0x60, 0x2e, 0xd0, 0x8c, 0x84, 0x17, 0x4e, 0x4b, 0x78, 0x94, 0x87,
- 0xf8, 0xbe, 0x6c, 0x1a, 0x96, 0x9b, 0x93, 0xd9, 0x72, 0xfa, 0xa4, 0x9c, 0xbc, 0x1a, 0x97, 0x3e,
- 0x11, 0x5e, 0xfc, 0x8b, 0x80, 0xe7, 0xf7, 0x9b, 0x86, 0x45, 0xd1, 0x63, 0x30, 0xbf, 0x2f, 0x77,
- 0x74, 0xca, 0x32, 0x2c, 0x8b, 0x63, 0xfb, 0x15, 0x9d, 0x3a, 0x35, 0x6a, 0xdf, 0xea, 0x8f, 0x6b,
- 0x54, 0xcc, 0xad, 0x51, 0xfb, 0x56, 0xbf, 0x39, 0x66, 0x96, 0x05, 0x95, 0x74, 0x0c, 0x95, 0xa8,
- 0x63, 0xa1, 0x79, 0x5e, 0xab, 0xc2, 0x95, 0xb2, 0xc5, 0xba, 0x01, 0x9c, 0xe3, 0xf2, 0x1e, 0xc2,
- 0x8b, 0x20, 0x85, 0x10, 0xe4, 0x23, 0xc5, 0xd2, 0x19, 0x57, 0x65, 0x9c, 0xf3, 0x86, 0x2f, 0x06,
- 0x35, 0x5e, 0xe1, 0xb3, 0x61, 0x4e, 0x8a, 0x9f, 0x8b, 0x93, 0xfc, 0x94, 0x98, 0xf8, 0xcc, 0x94,
- 0xe8, 0x63, 0xb5, 0xe4, 0xb9, 0x58, 0xed, 0x05, 0x48, 0x29, 0xa6, 0x29, 0xdb, 0x4e, 0xfc, 0x18,
- 0x27, 0xa5, 0xd7, 0xff, 0x2f, 0xbc, 0xfe, 0x36, 0x19, 0x55, 0xf5, 0x21, 0xe9, 0x19, 0x26, 0xc1,
- 0x09, 0xc5, 0x34, 0x5b, 0xdb, 0x64, 0x84, 0x56, 0xe1, 0x42, 0x4f, 0xb1, 0xa9, 0xac, 0xc8, 0x2c,
- 0x5a, 0xb2, 0x73, 0xce, 0x19, 0x39, 0x65, 0x71, 0xd6, 0x99, 0x28, 0xdd, 0xae, 0xe8, 0xd4, 0xa9,
- 0x06, 0xe8, 0x12, 0xa4, 0x3a, 0x86, 0xbe, 0xaf, 0x59, 0x7d, 0xa2, 0x4a, 0xe9, 0x15, 0x61, 0x35,
- 0x89, 0x27, 0x03, 0x33, 0x39, 0x2e, 0x7b, 0x5e, 0x8e, 0x43, 0x75, 0x48, 0xf5, 0x0c, 0x37, 0x35,
- 0x6d, 0x29, 0xc7, 0x02, 0x70, 0x2d, 0xbc, 0x8d, 0xa9, 0xf4, 0x2d, 0xec, 0x78, 0x2a, 0x55, 0x9d,
- 0x5a, 0x23, 0x3c, 0x81, 0x40, 0x3b, 0x90, 0x1e, 0x12, 0xcb, 0xf6, 0xea, 0xed, 0x02, 0x33, 0xe8,
- 0xf9, 0x4f, 0x25, 0xaf, 0x7b, 0xae, 0x6c, 0xe0, 0xa4, 0x0f, 0xbd, 0x31, 0xdb, 0x29, 0x17, 0x3a,
- 0xa1, 0x47, 0x86, 0x75, 0xc8, 0xd0, 0xc4, 0xd9, 0xe5, 0xa2, 0xee, 0x8a, 0x04, 0x40, 0x74, 0x6f,
- 0xcc, 0x5e, 0xbe, 0x07, 0xb9, 0xa0, 0xbd, 0x48, 0x84, 0xa8, 0x13, 0x35, 0x81, 0x1d, 0x54, 0xe7,
- 0x27, 0x2a, 0xc0, 0xfc, 0x50, 0xe9, 0x0d, 0x08, 0xaf, 0x28, 0x52, 0x78, 0x09, 0x0f, 0x00, 0xbb,
- 0x62, 0x37, 0x23, 0x2f, 0x0a, 0x37, 0x93, 0x1f, 0x3f, 0x5c, 0x8a, 0x25, 0x05, 0x71, 0x2e, 0xff,
- 0xfb, 0x08, 0x3c, 0xe6, 0x73, 0x92, 0x27, 0x8c, 0x24, 0x48, 0xd8, 0xc4, 0x72, 0xf6, 0xc9, 0xd7,
- 0xf2, 0x3e, 0xd1, 0xb7, 0x20, 0xe9, 0xf9, 0xec, 0x51, 0x4b, 0xfa, 0x53, 0xd7, 0xd3, 0x41, 0xef,
- 0x08, 0x00, 0x0a, 0xa5, 0x96, 0xb6, 0x37, 0xa0, 0xc4, 0xe9, 0x11, 0x9c, 0xc0, 0x5d, 0x3f, 0x25,
- 0x70, 0x1e, 0x5a, 0xa1, 0x34, 0xd6, 0x62, 0xbe, 0x28, 0xdf, 0x38, 0x29, 0xaf, 0xbf, 0x27, 0x14,
- 0x45, 0xc8, 0x5f, 0xb1, 0xf2, 0xd2, 0x95, 0xf5, 0xa7, 0xbe, 0xfb, 0xaa, 0xb2, 0xf6, 0xe6, 0xb5,
- 0xb5, 0xaf, 0xbf, 0xb6, 0x7a, 0xeb, 0xe6, 0xab, 0x6b, 0xaf, 0xdd, 0xf2, 0x3e, 0x9f, 0xfb, 0xde,
- 0xfa, 0x97, 0xbf, 0x7f, 0xe5, 0xea, 0xbc, 0x15, 0x95, 0x3e, 0x14, 0xb0, 0x6f, 0xf5, 0xe5, 0x97,
- 0x60, 0x21, 0x84, 0x3a, 0xc3, 0xc3, 0x8b, 0x7e, 0x0f, 0xa7, 0x66, 0xfb, 0xf1, 0x0f, 0x11, 0x78,
- 0xdc, 0x67, 0xf3, 0x96, 0xa1, 0xe9, 0xa5, 0x4e, 0x87, 0x98, 0xf4, 0xdc, 0xf5, 0x32, 0x70, 0x3a,
- 0x23, 0xe7, 0x38, 0x9d, 0xf7, 0xe1, 0x71, 0x4d, 0xf7, 0x2e, 0x74, 0xaa, 0xec, 0x91, 0xb0, 0xe7,
- 0xe2, 0xa7, 0x4f, 0x71, 0xb1, 0xc7, 0xe0, 0x78, 0xd1, 0x87, 0xe0, 0x0d, 0xda, 0xe8, 0x59, 0x58,
- 0x30, 0x89, 0xae, 0x6a, 0x7a, 0x57, 0xe6, 0xa6, 0xb2, 0x6a, 0x9c, 0xc4, 0x39, 0x3e, 0xdc, 0x72,
- 0x47, 0x3f, 0x57, 0x59, 0xf2, 0x39, 0xf3, 0x1f, 0x89, 0x40, 0x52, 0x7a, 0x86, 0xa0, 0x3f, 0x09,
- 0x9f, 0xe2, 0xcb, 0xf7, 0x05, 0xcf, 0x99, 0x1f, 0x3f, 0x5c, 0xfa, 0x89, 0xb0, 0x5c, 0xff, 0x0c,
- 0x77, 0x4a, 0xf6, 0xd7, 0xec, 0x0d, 0xba, 0x9a, 0x5e, 0xa8, 0x93, 0xa3, 0x4d, 0x72, 0x5c, 0x1e,
- 0x51, 0x62, 0xdf, 0xee, 0x29, 0xdd, 0xfc, 0x9d, 0xcf, 0x89, 0x77, 0x87, 0x50, 0x06, 0xf6, 0xef,
- 0xa2, 0xc6, 0x0f, 0x84, 0x19, 0xdc, 0x58, 0xfe, 0xb9, 0xf0, 0x5f, 0xbe, 0xfb, 0xff, 0x1c, 0x79,
- 0xc3, 0xa9, 0xe4, 0x1d, 0x60, 0xa7, 0x78, 0x98, 0x9d, 0xee, 0x40, 0xaa, 0xd3, 0x53, 0x6c, 0x5b,
- 0xde, 0x93, 0x3b, 0x9c, 0x9e, 0x9f, 0x3f, 0xc3, 0xd9, 0x29, 0x54, 0x1c, 0xa5, 0x72, 0x05, 0x27,
- 0x3a, 0xee, 0x0f, 0xb4, 0x09, 0x49, 0xd3, 0xd2, 0x0c, 0x4b, 0xa3, 0x23, 0x76, 0x14, 0x72, 0xd3,
- 0xf5, 0xbf, 0x7d, 0xdc, 0xe2, 0x17, 0x90, 0x26, 0x97, 0xf4, 0xb5, 0xe2, 0x63, 0xed, 0x59, 0xd7,
- 0x81, 0xd4, 0xa3, 0xaf, 0x03, 0xcb, 0x3f, 0x15, 0x20, 0xc1, 0xad, 0x42, 0x55, 0x48, 0xf2, 0x3e,
- 0xd4, 0xbd, 0xc3, 0xa6, 0xd7, 0x9f, 0x0b, 0x1b, 0xc3, 0x45, 0x67, 0xb4, 0xb0, 0x63, 0x55, 0x74,
- 0x0b, 0xb2, 0xca, 0x9e, 0x6d, 0xf4, 0x06, 0x94, 0xc8, 0x8c, 0xb7, 0x1f, 0x7d, 0xc6, 0x33, 0x9e,
- 0x82, 0x33, 0x34, 0x3e, 0xe5, 0x93, 0xe2, 0x29, 0xe4, 0x1f, 0xc0, 0xe2, 0x0c, 0x87, 0xda, 0xa8,
- 0x04, 0xa9, 0x49, 0x15, 0x13, 0xce, 0x5e, 0xc5, 0x26, 0x5a, 0xf9, 0xdf, 0x08, 0xb0, 0x34, 0x43,
- 0xe4, 0xb6, 0xa2, 0x39, 0x77, 0xbe, 0x1a, 0x24, 0x3d, 0x51, 0xde, 0x3e, 0x9f, 0x05, 0xdf, 0x4f,
- 0x6b, 0x9e, 0x3a, 0xfa, 0x26, 0xcc, 0xb3, 0x17, 0x21, 0x5e, 0xb2, 0x2f, 0x4d, 0xf5, 0x0d, 0xce,
- 0xe4, 0x06, 0xa1, 0x8a, 0xd6, 0xf3, 0xb7, 0x74, 0xae, 0x92, 0x9f, 0x90, 0x05, 0xb8, 0xec, 0x5b,
- 0xb3, 0x36, 0xab, 0x1e, 0x7f, 0x7e, 0xbf, 0xa0, 0x67, 0x60, 0x81, 0xb5, 0x72, 0xbe, 0x46, 0x8e,
- 0xd5, 0x20, 0x9c, 0x71, 0x86, 0xc7, 0x7d, 0xdc, 0x34, 0x79, 0x45, 0x4f, 0x23, 0x2f, 0xdf, 0x3e,
- 0x7e, 0x34, 0x0f, 0x79, 0x6f, 0xe1, 0x97, 0x07, 0x64, 0x40, 0x1a, 0x26, 0x71, 0x3b, 0x39, 0xbf,
- 0x27, 0xd0, 0x87, 0x02, 0x24, 0x55, 0x32, 0x94, 0x15, 0x55, 0xb5, 0x78, 0x31, 0xff, 0xb5, 0xf0,
- 0x6e, 0x69, 0x69, 0x0b, 0xf2, 0xeb, 0x5f, 0xbb, 0x76, 0xad, 0x54, 0xae, 0x6c, 0xe4, 0xdf, 0x8b,
- 0x08, 0x89, 0x5f, 0x46, 0xe2, 0x4e, 0xb5, 0xd1, 0xbb, 0x27, 0xe5, 0xf8, 0x9b, 0xb1, 0x83, 0x98,
- 0x29, 0x7c, 0xf4, 0x70, 0xe9, 0x6d, 0x01, 0x6e, 0x75, 0x8d, 0x02, 0x3d, 0x20, 0x94, 0xd5, 0xa4,
- 0x02, 0x6f, 0x9d, 0x8a, 0xc1, 0xa7, 0xb2, 0xe1, 0xf5, 0xa2, 0x79, 0xd8, 0x2d, 0xd2, 0x91, 0x49,
- 0xec, 0xc2, 0xae, 0x62, 0xd9, 0x07, 0x4a, 0x6f, 0xb3, 0x7a, 0x9f, 0xd5, 0x24, 0x74, 0x6e, 0x80,
- 0xbb, 0x7a, 0xdf, 0x85, 0xf8, 0xaa, 0x5b, 0xd4, 0x12, 0x2a, 0x19, 0x96, 0x54, 0xd5, 0x9a, 0xe1,
- 0xab, 0xc8, 0xa9, 0x44, 0xff, 0x34, 0xe4, 0xfa, 0x9a, 0xee, 0x8f, 0x80, 0x5b, 0xe2, 0xd3, 0x7d,
- 0x4d, 0x1f, 0x07, 0xe0, 0xcf, 0x02, 0x88, 0x1e, 0xf7, 0x8e, 0xfd, 0x14, 0xfb, 0x5f, 0xf4, 0x93,
- 0xd7, 0x2b, 0x6c, 0x70, 0x77, 0xbd, 0x04, 0x17, 0x43, 0x4d, 0x85, 0xe7, 0xb6, 0xf9, 0x90, 0xdb,
- 0x1e, 0x0b, 0x76, 0x19, 0xae, 0xf7, 0xd6, 0x27, 0xea, 0x21, 0x2f, 0xc6, 0x99, 0x17, 0x11, 0x9f,
- 0xdd, 0x9d, 0x38, 0x33, 0xaf, 0xc1, 0x45, 0xdf, 0xb1, 0x68, 0xb9, 0x0d, 0xed, 0x86, 0x73, 0x23,
- 0xfb, 0xf4, 0x76, 0xf7, 0x79, 0x88, 0xb1, 0x1b, 0x5e, 0xe4, 0x74, 0x72, 0x62, 0x42, 0xbe, 0xf4,
- 0xff, 0x6d, 0x12, 0xb2, 0x81, 0xcb, 0x07, 0x6a, 0x4f, 0x3d, 0x8f, 0x09, 0x67, 0x7f, 0x1e, 0xf3,
- 0x55, 0x8c, 0xf0, 0x43, 0xd9, 0x14, 0x33, 0x44, 0xce, 0xf0, 0x50, 0x14, 0x6a, 0xd4, 0x32, 0xe7,
- 0xba, 0x3f, 0x6e, 0x41, 0x6e, 0x60, 0xce, 0x78, 0x18, 0xfa, 0xff, 0x47, 0xde, 0xbe, 0x36, 0xe7,
- 0x70, 0x76, 0x10, 0x78, 0xbf, 0xd8, 0x84, 0xf4, 0xeb, 0x86, 0xa6, 0xcb, 0x0a, 0x6b, 0x96, 0xf9,
- 0x63, 0xdf, 0x33, 0xa7, 0x00, 0x4d, 0x3a, 0xeb, 0xcd, 0x39, 0x0c, 0xaf, 0x4f, 0xfa, 0xec, 0x4d,
- 0xc8, 0x8c, 0x1f, 0xac, 0x94, 0xce, 0x21, 0xef, 0x26, 0xce, 0x52, 0x17, 0x37, 0xe7, 0x70, 0xda,
- 0x53, 0x2d, 0x75, 0x0e, 0xd1, 0x16, 0x64, 0xc7, 0x48, 0xba, 0x03, 0x15, 0x3f, 0x0f, 0xd4, 0xd8,
- 0x8a, 0xba, 0x12, 0xc2, 0xb2, 0x89, 0x4e, 0x79, 0x43, 0x71, 0x5e, 0xac, 0x16, 0xd1, 0x29, 0x6a,
- 0xc3, 0xf8, 0x65, 0x4d, 0xde, 0x67, 0x04, 0xc6, 0xd9, 0xf7, 0xb9, 0x33, 0xa0, 0xb9, 0x8c, 0xb7,
- 0x39, 0x87, 0x73, 0x6a, 0x90, 0x03, 0xeb, 0x3e, 0xd4, 0x37, 0x9c, 0x42, 0xad, 0xf2, 0x37, 0x81,
- 0x33, 0xda, 0x38, 0xc6, 0x63, 0x55, 0x5e, 0x45, 0x06, 0x2c, 0x07, 0xf1, 0x64, 0xdf, 0x9d, 0x82,
- 0x3f, 0x65, 0x17, 0x4f, 0x81, 0x9e, 0xc5, 0x78, 0x9b, 0x73, 0x58, 0x0a, 0x2c, 0xe3, 0x13, 0x72,
- 0x36, 0xe0, 0x5d, 0x2e, 0x65, 0xdb, 0xe8, 0x0d, 0xf9, 0x8b, 0xc3, 0xe9, 0x1b, 0xf0, 0x2e, 0x95,
- 0xce, 0x06, 0x3c, 0xed, 0x16, 0x53, 0x46, 0xdb, 0x90, 0xe1, 0x87, 0x5f, 0x66, 0x27, 0xdf, 0x7d,
- 0x99, 0xf8, 0xd2, 0x29, 0x60, 0xbe, 0x4a, 0xe2, 0xe4, 0x92, 0xed, 0x2b, 0x2c, 0x97, 0x20, 0x65,
- 0x6b, 0xfd, 0x41, 0x8f, 0x6d, 0x3e, 0xe7, 0xb6, 0x9a, 0xe3, 0x81, 0x49, 0xbd, 0x28, 0xa7, 0x20,
- 0x32, 0x30, 0xdd, 0x07, 0xe1, 0x3f, 0x46, 0x40, 0xe2, 0xc7, 0x83, 0x37, 0xae, 0xb7, 0x0d, 0xab,
- 0xaf, 0x50, 0x4a, 0x2c, 0x1b, 0xed, 0x42, 0x66, 0x60, 0xca, 0xfb, 0xde, 0x00, 0xab, 0x21, 0xb9,
- 0xe9, 0x67, 0xc4, 0xb0, 0xa2, 0xaf, 0xab, 0x4c, 0x0f, 0xcc, 0xf1, 0x30, 0xba, 0x05, 0x17, 0xfd,
- 0x70, 0xb2, 0xa9, 0x58, 0x4a, 0x9f, 0x38, 0xc0, 0xec, 0xae, 0x5b, 0x4e, 0x9d, 0x94, 0xe3, 0x56,
- 0x4c, 0x7a, 0xeb, 0x83, 0x08, 0x5e, 0xf4, 0xe9, 0x35, 0x3d, 0x31, 0xf4, 0x32, 0xb0, 0xf8, 0xfb,
- 0x2c, 0x8a, 0x9e, 0xdb, 0x22, 0x76, 0x42, 0x26, 0x36, 0x55, 0x40, 0x0a, 0x42, 0xfa, 0xac, 0x8a,
- 0x85, 0xad, 0xba, 0x18, 0xd0, 0x1d, 0xdb, 0xe5, 0x6b, 0x2e, 0x7f, 0x27, 0xc0, 0x62, 0xa0, 0x11,
- 0xe1, 0xff, 0x37, 0xf8, 0x82, 0x0a, 0xf2, 0xae, 0xbf, 0x37, 0x8b, 0x9c, 0xb9, 0x37, 0x2b, 0xc3,
- 0x49, 0x39, 0xf1, 0xae, 0x10, 0x13, 0x7f, 0xf1, 0xc3, 0xb8, 0xaf, 0x4f, 0xbb, 0xfa, 0x2b, 0x01,
- 0xc4, 0xb0, 0xeb, 0x10, 0x82, 0xdc, 0xed, 0x06, 0xde, 0x2d, 0xb5, 0xdb, 0x55, 0x2c, 0xd7, 0x1b,
- 0xf5, 0xaa, 0x38, 0x87, 0x24, 0x58, 0x9c, 0x8c, 0xe1, 0x6a, 0xb3, 0xd1, 0xaa, 0xb5, 0x1b, 0xf8,
- 0x81, 0x28, 0xa0, 0x65, 0xb8, 0x38, 0x99, 0xb9, 0x83, 0x9b, 0x15, 0xb9, 0x55, 0xc5, 0xf7, 0x6a,
- 0x95, 0xaa, 0x18, 0x09, 0x6a, 0x6d, 0x95, 0xee, 0x95, 0x5a, 0x15, 0x5c, 0x6b, 0xb6, 0xc5, 0x68,
- 0x70, 0xa6, 0x52, 0x7a, 0x50, 0xad, 0xd7, 0xab, 0x3b, 0xcd, 0xa6, 0x18, 0x5b, 0xbe, 0xf0, 0xd1,
- 0xc3, 0xa5, 0xac, 0x24, 0x5c, 0x4d, 0x8d, 0xe7, 0xcb, 0x37, 0x3e, 0xf8, 0xdb, 0x53, 0xc2, 0x77,
- 0x8a, 0xe7, 0x68, 0x11, 0xa8, 0x6e, 0xee, 0xed, 0xc5, 0x19, 0xd1, 0x5c, 0xff, 0x57, 0x00, 0x00,
- 0x00, 0xff, 0xff, 0x72, 0x48, 0xaa, 0x27, 0xec, 0x1e, 0x00, 0x00,
+ // 2772 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x59, 0x4f, 0x6c, 0xe3, 0xc6,
+ 0xd5, 0x37, 0x25, 0x59, 0x7f, 0x9e, 0xfe, 0x98, 0x9e, 0x38, 0x1b, 0xda, 0xd9, 0x64, 0xfd, 0x29,
+ 0x9b, 0x2f, 0xce, 0xe6, 0xb3, 0xbc, 0x9f, 0xb7, 0xdb, 0xa4, 0xdb, 0x26, 0x5b, 0x49, 0x96, 0xd7,
+ 0xf2, 0x1f, 0x49, 0x19, 0x69, 0x37, 0xbb, 0x09, 0x52, 0x82, 0x16, 0xc7, 0x32, 0x63, 0x8a, 0x64,
+ 0x48, 0x4a, 0xb6, 0xb7, 0x28, 0x10, 0xe4, 0xd6, 0x00, 0x3d, 0x34, 0x28, 0x50, 0x34, 0x2d, 0x8a,
+ 0xa2, 0x05, 0x72, 0x58, 0x14, 0x3d, 0x14, 0xbd, 0xf5, 0x92, 0x4b, 0x81, 0x1c, 0xda, 0x5b, 0x81,
+ 0x5e, 0x0b, 0xb4, 0x97, 0x06, 0xe8, 0x25, 0xa7, 0xc2, 0x97, 0x14, 0x1c, 0x0e, 0x25, 0x92, 0xd2,
+ 0xda, 0xd6, 0x26, 0x29, 0xd2, 0x8b, 0x61, 0xce, 0xbc, 0xf7, 0x9b, 0x37, 0x6f, 0xde, 0xfb, 0xbd,
+ 0x37, 0x23, 0x58, 0x54, 0x75, 0x53, 0x3a, 0x94, 0xb4, 0x65, 0xcb, 0x96, 0xda, 0x07, 0x2b, 0x92,
+ 0xa1, 0xac, 0x74, 0x89, 0x65, 0x49, 0x1d, 0x62, 0x15, 0x0c, 0x53, 0xb7, 0x75, 0x94, 0xb3, 0x6d,
+ 0xad, 0xc0, 0xa4, 0x0a, 0xfd, 0x6b, 0x0b, 0xc5, 0x8e, 0x62, 0xef, 0xf7, 0x76, 0x0b, 0x6d, 0xbd,
+ 0xbb, 0x42, 0xb4, 0xbe, 0x7e, 0x6c, 0x98, 0xfa, 0xd1, 0xf1, 0x0a, 0x15, 0x6e, 0x2f, 0x77, 0x88,
+ 0xb6, 0xdc, 0x97, 0x54, 0x45, 0x96, 0x6c, 0xb2, 0x32, 0xf2, 0x8f, 0x0b, 0xb9, 0xb0, 0xec, 0x83,
+ 0xe8, 0xe8, 0x1d, 0xdd, 0x55, 0xde, 0xed, 0xed, 0xd1, 0x2f, 0xfa, 0x41, 0xff, 0x63, 0xe2, 0x65,
+ 0x9f, 0x78, 0x6b, 0x9f, 0xb4, 0xf6, 0x15, 0xad, 0x63, 0x55, 0x35, 0xb9, 0x67, 0xd9, 0xa6, 0x42,
+ 0x2c, 0xff, 0xd2, 0x1d, 0x7d, 0xf9, 0x2d, 0x4b, 0xd7, 0x56, 0x24, 0x4d, 0xd3, 0x6d, 0xc9, 0x56,
+ 0x74, 0x8d, 0x6d, 0x63, 0x61, 0x6d, 0x22, 0x90, 0x3d, 0x55, 0xea, 0x58, 0x63, 0x50, 0x2e, 0x76,
+ 0x74, 0xbd, 0xa3, 0x92, 0xa1, 0xc1, 0x96, 0x6d, 0xf6, 0xda, 0x36, 0x9b, 0xbd, 0x14, 0x9e, 0xb5,
+ 0x95, 0x2e, 0xb1, 0x6c, 0xa9, 0x6b, 0x30, 0x81, 0xa7, 0xc3, 0x02, 0x72, 0xcf, 0xa4, 0xf8, 0x6c,
+ 0xfe, 0xa9, 0xd1, 0xd3, 0x20, 0xa6, 0xa9, 0x9b, 0x6c, 0xfa, 0x99, 0xd1, 0x69, 0x45, 0x26, 0x9a,
+ 0xad, 0xec, 0x29, 0xc4, 0x1c, 0x98, 0x38, 0x2a, 0x74, 0x40, 0x8e, 0xbd, 0xd9, 0x4b, 0xa3, 0xb3,
+ 0xde, 0xd9, 0xba, 0x02, 0x63, 0x03, 0xc2, 0x96, 0x64, 0xc9, 0x96, 0x98, 0x44, 0xc1, 0xe7, 0x2a,
+ 0xdd, 0x20, 0x9a, 0x64, 0x28, 0xfd, 0xd5, 0x15, 0xdd, 0xa0, 0x7e, 0x1a, 0xf5, 0x59, 0xfe, 0xef,
+ 0x51, 0xc8, 0xde, 0x36, 0x54, 0x45, 0x3b, 0xd8, 0x71, 0x23, 0x0b, 0x5d, 0x82, 0xb4, 0x29, 0x1d,
+ 0x8a, 0x86, 0x74, 0xac, 0xea, 0x92, 0x2c, 0x70, 0x8b, 0xdc, 0x52, 0x06, 0x83, 0x29, 0x1d, 0x36,
+ 0xdc, 0x11, 0xf4, 0xff, 0x90, 0xf0, 0x26, 0x23, 0x8b, 0xdc, 0x52, 0x7a, 0xf5, 0x89, 0x42, 0x30,
+ 0x0a, 0x0b, 0x0c, 0x0a, 0x7b, 0x72, 0xe8, 0xdb, 0x90, 0xb4, 0x88, 0x6d, 0x3b, 0xa7, 0x2a, 0xc4,
+ 0xa8, 0xce, 0x42, 0x58, 0xa7, 0x75, 0xd4, 0x64, 0x12, 0xa5, 0xe4, 0x49, 0x69, 0xfa, 0x3d, 0x2e,
+ 0xc2, 0x73, 0x78, 0xa0, 0x85, 0xbe, 0x09, 0x69, 0xf3, 0x48, 0xf4, 0x36, 0x2b, 0x4c, 0x2f, 0x46,
+ 0xc7, 0x81, 0xe0, 0xa3, 0x1d, 0x26, 0x81, 0xc1, 0x1c, 0xfc, 0x4f, 0x95, 0x49, 0x9b, 0x28, 0x7d,
+ 0x22, 0x8b, 0x92, 0x2d, 0xc4, 0x99, 0x05, 0xee, 0x79, 0x17, 0xbc, 0xf3, 0x2e, 0xb4, 0xbc, 0x80,
+ 0xc0, 0xe0, 0x89, 0x17, 0x6d, 0x74, 0x1d, 0x66, 0xda, 0xba, 0x69, 0x12, 0x95, 0xfa, 0x4d, 0x54,
+ 0x64, 0x4b, 0x48, 0x2c, 0x46, 0x97, 0x52, 0xa5, 0xcc, 0x49, 0x29, 0xf5, 0x3e, 0x17, 0xcf, 0xc7,
+ 0xcc, 0x88, 0x20, 0xe3, 0x9c, 0x4f, 0xa8, 0x2a, 0x5b, 0xe8, 0x06, 0xcc, 0xc9, 0xa4, 0xaf, 0xb4,
+ 0x89, 0xd8, 0xde, 0x97, 0x34, 0x8d, 0xa8, 0xa2, 0xa2, 0xc9, 0xe4, 0x48, 0x48, 0x2d, 0x72, 0x4b,
+ 0x59, 0xba, 0xc5, 0x2b, 0x51, 0xe1, 0x33, 0x0e, 0x23, 0x57, 0xaa, 0xec, 0x0a, 0x55, 0x1d, 0x19,
+ 0xb4, 0x06, 0x7c, 0x5b, 0xd7, 0xac, 0x5e, 0xd7, 0xb1, 0x57, 0x31, 0x9d, 0x40, 0x15, 0x80, 0x1a,
+ 0x3d, 0x3f, 0x62, 0xf4, 0x1a, 0x0b, 0x52, 0x3c, 0xe3, 0xa9, 0x14, 0x5d, 0x8d, 0xcd, 0x58, 0x32,
+ 0xca, 0xc7, 0x36, 0x63, 0xc9, 0x24, 0x9f, 0xca, 0xff, 0x2c, 0x0a, 0x33, 0x6b, 0xfa, 0xa1, 0xf6,
+ 0x65, 0x1f, 0xf4, 0x26, 0xe4, 0x88, 0x26, 0x8b, 0x6c, 0xe7, 0x8e, 0xaf, 0xa2, 0x54, 0xf3, 0x72,
+ 0x58, 0xb3, 0xa2, 0xc9, 0x6b, 0x54, 0xa8, 0x3a, 0xcc, 0x11, 0x9c, 0x21, 0xc3, 0x51, 0x0b, 0x5d,
+ 0x87, 0x84, 0x49, 0xde, 0xee, 0x11, 0xcb, 0x66, 0x31, 0x33, 0x3f, 0x1a, 0x33, 0xd8, 0x15, 0xd8,
+ 0x98, 0xc2, 0x9e, 0x2c, 0xba, 0x01, 0x29, 0xab, 0xbd, 0x4f, 0xe4, 0x9e, 0x4a, 0x64, 0x61, 0xfa,
+ 0xac, 0x60, 0xdb, 0x98, 0xc2, 0x43, 0xf1, 0x71, 0x67, 0x1d, 0x3f, 0xc7, 0x59, 0x17, 0x20, 0x67,
+ 0x11, 0xcb, 0x72, 0x54, 0x0e, 0xc8, 0xb1, 0xa8, 0xc8, 0x42, 0xc2, 0x71, 0x26, 0x3d, 0xe5, 0xfb,
+ 0x51, 0xe1, 0x1d, 0x1e, 0x67, 0xd8, 0xfc, 0x16, 0x39, 0xae, 0xca, 0xa5, 0x99, 0x61, 0x3a, 0xa0,
+ 0xe8, 0xbf, 0x4a, 0x5c, 0xfe, 0xfb, 0x51, 0xe0, 0x5b, 0x47, 0xc5, 0xf6, 0x81, 0xa6, 0x1f, 0xaa,
+ 0x44, 0xee, 0x74, 0x89, 0x36, 0x36, 0xf0, 0xb8, 0x73, 0x18, 0x53, 0x85, 0xb8, 0x49, 0xac, 0x9e,
+ 0x6a, 0xd3, 0x43, 0xcb, 0xad, 0x3e, 0x37, 0xba, 0xf9, 0xe0, 0x42, 0x05, 0x4c, 0xc5, 0xa9, 0xb5,
+ 0xef, 0xd2, 0xb4, 0x63, 0x00, 0x68, 0x13, 0x78, 0x99, 0x05, 0x8d, 0xc8, 0x0a, 0x0f, 0x3b, 0xcf,
+ 0x4b, 0x61, 0xd0, 0x50, 0x70, 0xe1, 0x19, 0x39, 0x38, 0x90, 0xff, 0x90, 0x83, 0xb8, 0xbb, 0x10,
+ 0x4a, 0x43, 0xa2, 0x79, 0xbb, 0x5c, 0xae, 0x34, 0x9b, 0xfc, 0x14, 0x9a, 0x85, 0xec, 0xed, 0xda,
+ 0x56, 0xad, 0xfe, 0x5a, 0x4d, 0xac, 0x60, 0x5c, 0xc7, 0x3c, 0x87, 0x32, 0x90, 0x6c, 0xd5, 0xeb,
+ 0xe2, 0x76, 0xb1, 0x55, 0xe1, 0x23, 0x28, 0x0b, 0x29, 0xe7, 0xab, 0x52, 0xc4, 0xdb, 0xf7, 0xf8,
+ 0x28, 0x9a, 0x03, 0xbe, 0x5c, 0xdf, 0xde, 0xae, 0x36, 0xab, 0xf5, 0x9a, 0xd8, 0x28, 0x96, 0xb7,
+ 0x2a, 0x2d, 0x3e, 0x16, 0x1c, 0x2d, 0x55, 0x8a, 0xe5, 0x7a, 0x8d, 0x9f, 0x76, 0x16, 0x6a, 0xdd,
+ 0x15, 0xd7, 0x71, 0xe5, 0x55, 0x3e, 0x4e, 0x51, 0xef, 0x8a, 0x8d, 0xfa, 0x6b, 0x15, 0xcc, 0x27,
+ 0x10, 0x0f, 0x99, 0x5b, 0x8d, 0xa6, 0x78, 0xbb, 0xb6, 0x5d, 0x2f, 0x6f, 0x55, 0xd6, 0xf8, 0xe4,
+ 0x42, 0xfc, 0x93, 0x07, 0xf3, 0x11, 0x81, 0xcb, 0xff, 0x98, 0x83, 0x27, 0x6e, 0x49, 0x36, 0x39,
+ 0x94, 0x8e, 0x47, 0x8e, 0xa4, 0x0c, 0xe9, 0x8e, 0x3b, 0xc5, 0x8e, 0xc3, 0xf1, 0x45, 0x3e, 0xec,
+ 0x0b, 0xa6, 0xed, 0x8f, 0x6c, 0xe8, 0x78, 0x63, 0x16, 0x7a, 0x11, 0xe2, 0xf6, 0x91, 0x28, 0xb5,
+ 0x0f, 0x58, 0x56, 0x2d, 0x9e, 0x75, 0x40, 0x78, 0xda, 0x76, 0x46, 0xf2, 0x26, 0xcc, 0x31, 0xe8,
+ 0x20, 0x63, 0x17, 0x21, 0xe1, 0x9d, 0x8e, 0x6b, 0xd1, 0x53, 0x61, 0xc4, 0x80, 0xbc, 0x8f, 0x5f,
+ 0x3d, 0x3d, 0xf4, 0x04, 0x24, 0x76, 0x25, 0x4d, 0x76, 0x42, 0xd7, 0x31, 0x2a, 0x85, 0xe3, 0xce,
+ 0x67, 0x55, 0xce, 0xff, 0x23, 0x09, 0xb3, 0x45, 0xc3, 0x50, 0x95, 0x36, 0x0d, 0x30, 0x17, 0x68,
+ 0x4c, 0xc0, 0x73, 0xa7, 0x05, 0x3c, 0xca, 0x43, 0x7c, 0x4f, 0x34, 0x74, 0xd3, 0x8d, 0xc9, 0x6c,
+ 0x29, 0x7d, 0x52, 0x4a, 0x5e, 0x89, 0x0b, 0x9f, 0x71, 0x2f, 0xfd, 0x95, 0xc3, 0xd3, 0x7b, 0x0d,
+ 0xdd, 0xb4, 0xd1, 0x63, 0x30, 0xbd, 0x27, 0xb6, 0x35, 0x9b, 0x46, 0x58, 0x16, 0xc7, 0xf6, 0xca,
+ 0x9a, 0xed, 0x70, 0xd4, 0x9e, 0xd9, 0x1d, 0x70, 0x54, 0xcc, 0xe5, 0xa8, 0x3d, 0xb3, 0xdb, 0x18,
+ 0x54, 0x96, 0x19, 0x99, 0xb4, 0x75, 0x99, 0xc8, 0x03, 0xa1, 0x69, 0xc6, 0x55, 0x61, 0xa6, 0x6c,
+ 0xd2, 0x6e, 0x00, 0xe7, 0x98, 0xbc, 0x87, 0xf0, 0x12, 0x08, 0x21, 0x04, 0xf1, 0x50, 0x32, 0x35,
+ 0x5a, 0xab, 0x32, 0x4e, 0xbe, 0xe1, 0x0b, 0x41, 0x8d, 0xd7, 0xd8, 0x2c, 0x5a, 0x07, 0xa4, 0xe9,
+ 0x66, 0x57, 0x52, 0x95, 0xfb, 0xbe, 0xe5, 0x67, 0x69, 0x69, 0x7a, 0xe8, 0xf2, 0xb3, 0x43, 0x15,
+ 0xcf, 0x82, 0x57, 0xe0, 0xc9, 0x51, 0x9c, 0xa1, 0x11, 0x88, 0x1a, 0x31, 0x3f, 0xa2, 0x37, 0xb0,
+ 0x23, 0x54, 0x1b, 0xe3, 0x13, 0xd5, 0x46, 0x7f, 0x69, 0x4e, 0x3c, 0x72, 0x69, 0xf6, 0x55, 0xd7,
+ 0xe4, 0x44, 0xd5, 0xf5, 0x45, 0x48, 0x49, 0x86, 0x21, 0x5a, 0x4e, 0x1c, 0xd1, 0xda, 0x98, 0x5e,
+ 0x7d, 0x32, 0xbc, 0xfe, 0x16, 0x39, 0xae, 0x68, 0x7d, 0xa2, 0xea, 0x06, 0xc1, 0x09, 0xc9, 0x30,
+ 0x9a, 0x5b, 0xe4, 0x18, 0x2d, 0xc1, 0xac, 0x2a, 0x59, 0xb6, 0x28, 0x89, 0x34, 0x6a, 0x44, 0x87,
+ 0x6f, 0x68, 0x91, 0xcc, 0xe2, 0xac, 0x33, 0x51, 0x5c, 0x2f, 0x6b, 0xb6, 0xc3, 0x4a, 0xe8, 0x22,
+ 0xa4, 0xda, 0xba, 0xb6, 0xa7, 0x98, 0x5d, 0x22, 0x0b, 0xe9, 0x45, 0x6e, 0x29, 0x89, 0x87, 0x03,
+ 0x63, 0x6b, 0x6d, 0x76, 0xd2, 0x5a, 0x8b, 0x6a, 0x90, 0x52, 0x75, 0x37, 0x45, 0x2c, 0x21, 0x47,
+ 0x0f, 0xe0, 0x6a, 0x78, 0x1b, 0x23, 0x69, 0x54, 0xd8, 0xf6, 0x54, 0x2a, 0x9a, 0x6d, 0x1e, 0xe3,
+ 0x21, 0x04, 0xda, 0x86, 0x74, 0x9f, 0x98, 0x96, 0xc7, 0xfb, 0x33, 0xd4, 0xa0, 0x17, 0x1e, 0x5a,
+ 0x44, 0xef, 0xb8, 0xb2, 0x01, 0xc6, 0xe9, 0x7b, 0x63, 0x96, 0x43, 0x5b, 0x1a, 0xb1, 0x0f, 0x75,
+ 0xf3, 0x80, 0xa2, 0xf1, 0xe3, 0x69, 0xab, 0xe6, 0x8a, 0x04, 0x40, 0x34, 0x6f, 0xcc, 0x5a, 0xb8,
+ 0x03, 0xb9, 0xa0, 0xbd, 0x88, 0x87, 0xa8, 0x73, 0x6a, 0x1c, 0x25, 0x0c, 0xe7, 0x5f, 0x54, 0x80,
+ 0xe9, 0xbe, 0xa4, 0xf6, 0x08, 0x63, 0x36, 0x21, 0xbc, 0x84, 0x07, 0x80, 0x5d, 0xb1, 0x1b, 0x91,
+ 0x97, 0xb8, 0x1b, 0xc9, 0x4f, 0x1f, 0xcc, 0xc7, 0x92, 0x1c, 0x3f, 0x95, 0xff, 0x5d, 0x02, 0x9e,
+ 0x1c, 0x71, 0x52, 0x6d, 0x10, 0xf6, 0x13, 0xb3, 0xce, 0xe5, 0x10, 0xeb, 0x64, 0x4f, 0x4a, 0x70,
+ 0x25, 0x29, 0x7c, 0xc6, 0x2d, 0x7d, 0x7e, 0xde, 0x69, 0x8c, 0xcd, 0xfd, 0xd3, 0xa9, 0xc7, 0x97,
+ 0x3d, 0x93, 0xb3, 0x40, 0x7c, 0x42, 0x16, 0x48, 0x3c, 0x32, 0x0b, 0x24, 0x1f, 0x89, 0x05, 0x2a,
+ 0x41, 0x16, 0x48, 0x9d, 0xc5, 0x02, 0x14, 0xe4, 0xb7, 0x5c, 0x24, 0xc9, 0x05, 0xf8, 0x20, 0x90,
+ 0xac, 0x70, 0x9e, 0x64, 0x4d, 0x4f, 0x9c, 0xac, 0x77, 0xfd, 0xc9, 0x9a, 0xa1, 0x7e, 0xba, 0x71,
+ 0x66, 0xb2, 0x0e, 0xe3, 0xf0, 0xfc, 0x69, 0x9b, 0xfd, 0x42, 0xd3, 0x36, 0xf7, 0x15, 0x4d, 0xdb,
+ 0x3f, 0x44, 0xe0, 0x31, 0x9f, 0xbb, 0x3c, 0x61, 0x24, 0x40, 0xc2, 0x22, 0xa6, 0xb3, 0x4f, 0xb6,
+ 0x96, 0xf7, 0x89, 0x5e, 0x81, 0xa4, 0xe7, 0xb3, 0xb3, 0x96, 0xf4, 0xc7, 0x9a, 0xa7, 0x83, 0xde,
+ 0xe3, 0x00, 0x24, 0xdb, 0x36, 0x95, 0xdd, 0x9e, 0x4d, 0x9c, 0x2b, 0x86, 0x73, 0x84, 0xd7, 0x4e,
+ 0x39, 0x42, 0x0f, 0xad, 0x50, 0x1c, 0x68, 0x51, 0x5f, 0x94, 0xae, 0x9f, 0x94, 0x56, 0x3f, 0xe0,
+ 0x56, 0x78, 0xc8, 0x5f, 0x36, 0xf3, 0xc2, 0xe5, 0xd5, 0xa7, 0xbf, 0xf3, 0x86, 0xb4, 0x7c, 0xff,
+ 0xea, 0xf2, 0x37, 0xde, 0x5c, 0xba, 0x79, 0xe3, 0x8d, 0xe5, 0x37, 0x6f, 0x7a, 0x9f, 0xcf, 0x7f,
+ 0x77, 0xf5, 0xff, 0xbe, 0x77, 0xf9, 0xca, 0xb4, 0x19, 0x15, 0x3e, 0xe6, 0xb0, 0x6f, 0xf5, 0x85,
+ 0x97, 0x61, 0x26, 0x84, 0x3a, 0xc6, 0xc3, 0x73, 0x7e, 0x0f, 0xa7, 0xc6, 0xfb, 0xf1, 0xcf, 0x11,
+ 0x78, 0xdc, 0x67, 0xf3, 0xa6, 0xae, 0x68, 0xc5, 0x76, 0x9b, 0x18, 0xf6, 0xc4, 0xc4, 0x17, 0x28,
+ 0xaa, 0x91, 0x09, 0x8a, 0xea, 0x5d, 0x78, 0x5c, 0xd1, 0xbc, 0xf7, 0x20, 0x59, 0xf4, 0x7a, 0x78,
+ 0xcf, 0xc5, 0xcf, 0x9c, 0xe2, 0x62, 0xef, 0x02, 0x80, 0xe7, 0x7c, 0x08, 0xde, 0xa0, 0x85, 0x9e,
+ 0x83, 0x19, 0x83, 0x68, 0xb2, 0xa2, 0x75, 0x44, 0x66, 0x2a, 0x25, 0xd5, 0x24, 0xce, 0xb1, 0xe1,
+ 0xa6, 0x3b, 0x1a, 0xe6, 0x91, 0xe4, 0xa3, 0xf1, 0x88, 0xcf, 0xad, 0xff, 0x4c, 0x04, 0xc2, 0xd3,
+ 0x33, 0x09, 0xfd, 0x89, 0x7b, 0x88, 0x57, 0x3f, 0xe4, 0x3c, 0xb7, 0x7e, 0xfa, 0x60, 0xfe, 0x27,
+ 0xdc, 0x42, 0xed, 0x11, 0x1e, 0xa7, 0xe8, 0x5f, 0x43, 0xed, 0x75, 0x14, 0xad, 0x50, 0x23, 0x87,
+ 0x1b, 0xe4, 0xa8, 0x74, 0x6c, 0x13, 0x6b, 0x5d, 0x95, 0x3a, 0xf9, 0x5b, 0x9f, 0x13, 0xef, 0x16,
+ 0xb1, 0x29, 0xd8, 0x17, 0xd5, 0x63, 0x7f, 0xc4, 0x8d, 0x29, 0x76, 0xa5, 0x9f, 0x73, 0x5f, 0xf1,
+ 0xdd, 0xff, 0xe7, 0x6e, 0x01, 0x70, 0xea, 0x2d, 0x20, 0x50, 0xb1, 0xe2, 0xe1, 0x8a, 0x75, 0x0b,
+ 0x52, 0x6d, 0x55, 0xb2, 0x2c, 0x71, 0x57, 0x6c, 0xb3, 0xfe, 0xfa, 0x85, 0x73, 0x64, 0x51, 0xa1,
+ 0xec, 0x28, 0x95, 0xca, 0x38, 0xd1, 0x76, 0xff, 0x41, 0x1b, 0x90, 0x34, 0x4c, 0x45, 0x37, 0x15,
+ 0xfb, 0x98, 0x26, 0x45, 0x6e, 0xb4, 0x12, 0xb4, 0x8e, 0x9a, 0xec, 0x25, 0xa3, 0xc1, 0x24, 0x7d,
+ 0x77, 0xfa, 0x81, 0xf6, 0xb8, 0x77, 0x85, 0xd4, 0xd9, 0xef, 0x0a, 0x0b, 0x3f, 0xe5, 0x20, 0xc1,
+ 0xac, 0x42, 0x15, 0x48, 0xb2, 0x0b, 0xad, 0xc5, 0x1a, 0x8d, 0xe7, 0xc3, 0xc6, 0x30, 0xd1, 0x31,
+ 0x77, 0xe1, 0x81, 0x2a, 0xba, 0x09, 0x59, 0x69, 0xd7, 0xd2, 0xd5, 0x9e, 0x4d, 0x44, 0x5a, 0xcb,
+ 0xcf, 0xbe, 0x3b, 0x64, 0x3c, 0x05, 0x67, 0x68, 0x90, 0xe5, 0x43, 0x1a, 0xe5, 0xf2, 0xf7, 0x60,
+ 0x6e, 0x8c, 0x43, 0x2d, 0x54, 0x84, 0xd4, 0x90, 0xcf, 0xb8, 0xf3, 0xf3, 0xd9, 0x50, 0x2b, 0xff,
+ 0x1b, 0x0e, 0xe6, 0xc7, 0x88, 0xac, 0x4b, 0x8a, 0x4a, 0x64, 0x54, 0x85, 0xa4, 0x27, 0xca, 0xee,
+ 0xe1, 0xe7, 0xc1, 0xf7, 0x17, 0x38, 0x4f, 0x1d, 0x7d, 0x0b, 0xa6, 0xe9, 0xd3, 0x32, 0x23, 0xef,
+ 0x8b, 0x23, 0x1d, 0x84, 0x33, 0xb9, 0x46, 0x6c, 0x49, 0x51, 0xfd, 0xdd, 0x98, 0xab, 0xe4, 0x2f,
+ 0xcd, 0x1c, 0x5c, 0xf2, 0xad, 0x59, 0x1d, 0xc7, 0xcc, 0x9f, 0xdf, 0x2f, 0xe8, 0x59, 0x98, 0xa1,
+ 0x77, 0x31, 0xdf, 0x4d, 0x8c, 0x72, 0x10, 0xce, 0x38, 0xc3, 0x83, 0x8b, 0xd8, 0x68, 0x19, 0x8b,
+ 0x9e, 0x56, 0xc6, 0x7c, 0xfb, 0xf8, 0xe1, 0x34, 0xe4, 0xbd, 0x85, 0x5f, 0xed, 0x91, 0x1e, 0xa9,
+ 0x1b, 0xc4, 0xed, 0xee, 0xfc, 0x9e, 0x40, 0x1f, 0x73, 0x90, 0x94, 0x49, 0x5f, 0x94, 0x64, 0xd9,
+ 0x64, 0x64, 0xfe, 0x6b, 0xee, 0xfd, 0xe2, 0xfc, 0x26, 0xe4, 0x57, 0xbf, 0x7e, 0xf5, 0x6a, 0xb1,
+ 0x54, 0x5e, 0xcb, 0x7f, 0x10, 0xe1, 0x12, 0xbf, 0x8c, 0xc4, 0x1d, 0xb6, 0xd1, 0x3a, 0x27, 0xa5,
+ 0xf8, 0xfd, 0xd8, 0x7e, 0xcc, 0xe0, 0x3e, 0x79, 0x30, 0xff, 0x2e, 0x07, 0x37, 0x3b, 0x7a, 0xc1,
+ 0xde, 0x27, 0x36, 0xe5, 0xa4, 0x02, 0x6b, 0xa2, 0x56, 0x82, 0x6f, 0xee, 0xfd, 0x6b, 0x2b, 0xc6,
+ 0x41, 0x67, 0xc5, 0x3e, 0x36, 0x88, 0x55, 0xd8, 0x91, 0x4c, 0x6b, 0x5f, 0x52, 0x37, 0x2a, 0x77,
+ 0x29, 0x27, 0xa1, 0x89, 0x01, 0x6e, 0x6b, 0x5d, 0x17, 0xe2, 0x6b, 0x2e, 0xa9, 0x25, 0x64, 0xd2,
+ 0x2f, 0xca, 0xb2, 0x39, 0xc6, 0x57, 0x91, 0x53, 0x4b, 0xfe, 0x33, 0x90, 0xeb, 0x2a, 0x9a, 0xff,
+ 0x04, 0x5c, 0x8a, 0x4f, 0x77, 0x15, 0x6d, 0x70, 0x00, 0x7f, 0xe1, 0x80, 0xf7, 0xaa, 0xf0, 0xc0,
+ 0x4f, 0xb1, 0xff, 0x46, 0x3f, 0x79, 0x5d, 0xc3, 0x1a, 0x73, 0xd7, 0xcb, 0x70, 0x21, 0xd4, 0x5e,
+ 0x78, 0x6e, 0x9b, 0x0e, 0xb9, 0xed, 0xb1, 0x60, 0xbf, 0xe1, 0x7a, 0x6f, 0x75, 0xa8, 0x1e, 0xf2,
+ 0x62, 0x9c, 0x7a, 0x11, 0xb1, 0xd9, 0x9d, 0xa1, 0x33, 0xf3, 0x0a, 0x5c, 0xf0, 0xa5, 0x45, 0xd3,
+ 0x6d, 0x6d, 0xd7, 0x9c, 0xcb, 0xd4, 0xc3, 0x1b, 0xdf, 0x17, 0x20, 0x46, 0x2f, 0x67, 0x91, 0xd3,
+ 0x8b, 0x13, 0x15, 0xf2, 0x85, 0xff, 0x8f, 0x52, 0x90, 0x0d, 0x5c, 0x48, 0x50, 0x6b, 0xe4, 0x9d,
+ 0x9d, 0x3b, 0xff, 0x3b, 0xbb, 0x8f, 0x31, 0xc2, 0x2f, 0xee, 0x23, 0x95, 0x21, 0x72, 0x8e, 0x17,
+ 0xe7, 0xd0, 0x03, 0x50, 0x66, 0xa2, 0x07, 0xa0, 0x4d, 0xc8, 0xf5, 0x8c, 0x31, 0x2f, 0xcc, 0xff,
+ 0x73, 0xe6, 0x8d, 0x6c, 0x63, 0x0a, 0x67, 0x7b, 0x81, 0x87, 0xd0, 0xd7, 0x61, 0x96, 0x61, 0x0d,
+ 0xaf, 0xc9, 0x0f, 0x7b, 0x3b, 0x39, 0xe5, 0x82, 0xb7, 0x31, 0x85, 0xf9, 0x5e, 0xf8, 0xf1, 0x61,
+ 0x03, 0xd2, 0x6f, 0xe9, 0x8a, 0x26, 0x4a, 0xb4, 0x25, 0x67, 0xbf, 0x48, 0x3c, 0x7b, 0x0a, 0xea,
+ 0xb0, 0x7f, 0xdf, 0x98, 0xc2, 0xf0, 0xd6, 0xb0, 0x9b, 0xdf, 0x80, 0xcc, 0xe0, 0x55, 0x5d, 0x6a,
+ 0x1f, 0xb0, 0x4e, 0xe5, 0x3c, 0x9c, 0xbb, 0x31, 0x85, 0xd3, 0x9e, 0x6a, 0xb1, 0x7d, 0x80, 0x36,
+ 0x21, 0x3b, 0x40, 0xd2, 0x1c, 0xa8, 0xf8, 0x24, 0x50, 0x03, 0x2b, 0x6a, 0x52, 0x08, 0xcb, 0x22,
+ 0x9a, 0xcd, 0x9a, 0x95, 0x49, 0xb1, 0x9a, 0x44, 0xb3, 0x51, 0x0b, 0x06, 0xcf, 0xff, 0xe2, 0x1e,
+ 0x2d, 0x8e, 0xac, 0xb2, 0x3f, 0x7f, 0x0e, 0x34, 0xb7, 0x9a, 0x6e, 0x4c, 0xe1, 0x9c, 0x1c, 0xac,
+ 0xaf, 0x35, 0x1f, 0xea, 0xdb, 0x4e, 0x11, 0x90, 0xd9, 0x2b, 0xc3, 0x39, 0x6d, 0x1c, 0xe0, 0xd1,
+ 0x0a, 0x22, 0x23, 0x1d, 0x16, 0x82, 0x78, 0xa2, 0xef, 0xe6, 0xc2, 0x7e, 0x6f, 0x5b, 0x39, 0x05,
+ 0x7a, 0x5c, 0x35, 0xdd, 0x98, 0xc2, 0x42, 0x60, 0x19, 0x9f, 0x90, 0xb3, 0x01, 0xef, 0x0a, 0x2b,
+ 0x5a, 0xba, 0xda, 0x67, 0xcf, 0x91, 0xa7, 0x6f, 0xc0, 0xbb, 0xba, 0x3a, 0x1b, 0xf0, 0xb4, 0x9b,
+ 0x54, 0x19, 0x6d, 0x41, 0x86, 0x11, 0x8b, 0x48, 0x59, 0xc5, 0x7d, 0x6e, 0xf8, 0xdf, 0x53, 0xc0,
+ 0x7c, 0x2c, 0xe5, 0xc4, 0x92, 0xe5, 0x23, 0xad, 0x8b, 0x90, 0xb2, 0x94, 0x6e, 0x4f, 0xa5, 0x9b,
+ 0xcf, 0xb9, 0x6d, 0xec, 0x60, 0x60, 0xc8, 0x45, 0xa5, 0x14, 0x44, 0x7a, 0x86, 0xfb, 0xab, 0xd5,
+ 0x1f, 0x23, 0x20, 0xb0, 0xd4, 0x63, 0x4d, 0xf1, 0xba, 0x93, 0x2f, 0xb6, 0x4d, 0x4c, 0x0b, 0xed,
+ 0x40, 0xa6, 0x67, 0x88, 0x7b, 0xde, 0x00, 0xe5, 0xa7, 0xdc, 0xe8, 0x6f, 0x1d, 0x61, 0x45, 0x5f,
+ 0xc7, 0x9a, 0xee, 0x19, 0x83, 0x61, 0x74, 0x13, 0x2e, 0xf8, 0xe1, 0x44, 0x43, 0x32, 0xa5, 0x2e,
+ 0x71, 0x80, 0xe9, 0x8d, 0xba, 0x94, 0x3a, 0x29, 0xc5, 0xcd, 0x98, 0xf0, 0xce, 0x47, 0x11, 0x3c,
+ 0xe7, 0xd3, 0x6b, 0x78, 0x62, 0xe8, 0x55, 0xa0, 0xe7, 0xef, 0xb3, 0x28, 0x3a, 0xb1, 0x45, 0x34,
+ 0x43, 0x86, 0x36, 0x95, 0x41, 0x08, 0x42, 0xfa, 0xac, 0x8a, 0x85, 0xad, 0xba, 0x10, 0xd0, 0x1d,
+ 0xd8, 0xe5, 0x6b, 0x5c, 0x7f, 0xcf, 0xc1, 0x5c, 0xa0, 0xc9, 0x61, 0x3f, 0x6e, 0x7e, 0x49, 0x64,
+ 0xbf, 0xe3, 0xef, 0xfb, 0x22, 0xe7, 0xee, 0xfb, 0x4a, 0x70, 0x52, 0x4a, 0xbc, 0xcf, 0xc5, 0xf8,
+ 0x5f, 0xfc, 0x20, 0xee, 0xeb, 0x01, 0xaf, 0xfc, 0x8a, 0x03, 0x3e, 0xec, 0x3a, 0x84, 0x20, 0xb7,
+ 0x5e, 0xc7, 0x3b, 0xc5, 0x56, 0xab, 0x82, 0xc5, 0x5a, 0xbd, 0x56, 0xe1, 0xa7, 0x90, 0x00, 0x73,
+ 0xc3, 0x31, 0x5c, 0x69, 0xd4, 0x9b, 0xd5, 0x56, 0x1d, 0xdf, 0xe3, 0x39, 0xb4, 0x00, 0x17, 0x86,
+ 0x33, 0xb7, 0x70, 0xa3, 0x2c, 0x36, 0x2b, 0xf8, 0x4e, 0xb5, 0x5c, 0xe1, 0x23, 0x41, 0xad, 0xcd,
+ 0xe2, 0x9d, 0x62, 0xb3, 0x8c, 0xab, 0x8d, 0x16, 0x1f, 0x0d, 0xce, 0x94, 0x8b, 0xf7, 0x2a, 0xb5,
+ 0x5a, 0x65, 0xbb, 0xd1, 0xe0, 0x63, 0x0b, 0xb3, 0x9f, 0x3c, 0x98, 0xcf, 0x0a, 0xdc, 0x95, 0xd4,
+ 0x60, 0xbe, 0x74, 0xfd, 0xa3, 0xbf, 0x3d, 0xcd, 0xbd, 0xbe, 0x32, 0x41, 0xfb, 0x61, 0x6b, 0xc6,
+ 0xee, 0x6e, 0x9c, 0x16, 0xb1, 0x6b, 0xff, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x35, 0x53, 0xb3, 0xcc,
+ 0x91, 0x23, 0x00, 0x00,
}
diff --git a/pkg/ttnpb/messages.pb.paths.fm.go b/pkg/ttnpb/messages.pb.paths.fm.go
index 0332ed7dbd..8d29c93875 100644
--- a/pkg/ttnpb/messages.pb.paths.fm.go
+++ b/pkg/ttnpb/messages.pb.paths.fm.go
@@ -556,6 +556,8 @@ var ApplicationUplinkFieldPathsNested = []string{
"network_ids.net_id",
"network_ids.tenant_address",
"network_ids.tenant_id",
+ "normalized_payload",
+ "normalized_payload_warnings",
"received_at",
"rx_metadata",
"session_key_id",
@@ -604,6 +606,75 @@ var ApplicationUplinkFieldPathsTopLevel = []string{
"last_a_f_cnt_down",
"locations",
"network_ids",
+ "normalized_payload",
+ "normalized_payload_warnings",
+ "received_at",
+ "rx_metadata",
+ "session_key_id",
+ "settings",
+ "version_ids",
+}
+var ApplicationUplinkNormalizedFieldPathsNested = []string{
+ "confirmed",
+ "consumed_airtime",
+ "f_cnt",
+ "f_port",
+ "frm_payload",
+ "locations",
+ "network_ids",
+ "network_ids.cluster_address",
+ "network_ids.cluster_id",
+ "network_ids.net_id",
+ "network_ids.tenant_address",
+ "network_ids.tenant_id",
+ "normalized_payload",
+ "normalized_payload_warnings",
+ "received_at",
+ "rx_metadata",
+ "session_key_id",
+ "settings",
+ "settings.coding_rate",
+ "settings.concentrator_timestamp",
+ "settings.data_rate",
+ "settings.data_rate.modulation",
+ "settings.data_rate.modulation.fsk",
+ "settings.data_rate.modulation.fsk.bit_rate",
+ "settings.data_rate.modulation.lora",
+ "settings.data_rate.modulation.lora.bandwidth",
+ "settings.data_rate.modulation.lora.spreading_factor",
+ "settings.data_rate.modulation.lrfhss",
+ "settings.data_rate.modulation.lrfhss.coding_rate",
+ "settings.data_rate.modulation.lrfhss.modulation_type",
+ "settings.data_rate.modulation.lrfhss.operating_channel_width",
+ "settings.downlink",
+ "settings.downlink.antenna_index",
+ "settings.downlink.invert_polarization",
+ "settings.downlink.tx_power",
+ "settings.enable_crc",
+ "settings.frequency",
+ "settings.time",
+ "settings.timestamp",
+ "version_ids",
+ "version_ids.band_id",
+ "version_ids.brand_id",
+ "version_ids.firmware_version",
+ "version_ids.hardware_version",
+ "version_ids.model_id",
+ "version_ids.serial_number",
+ "version_ids.vendor_id",
+ "version_ids.vendor_profile_id",
+}
+
+var ApplicationUplinkNormalizedFieldPathsTopLevel = []string{
+ "confirmed",
+ "consumed_airtime",
+ "f_cnt",
+ "f_port",
+ "frm_payload",
+ "locations",
+ "network_ids",
+ "normalized_payload",
+ "normalized_payload_warnings",
"received_at",
"rx_metadata",
"session_key_id",
@@ -886,6 +957,8 @@ var ApplicationUpFieldPathsNested = []string{
"up.uplink_message.network_ids.net_id",
"up.uplink_message.network_ids.tenant_address",
"up.uplink_message.network_ids.tenant_id",
+ "up.uplink_message.normalized_payload",
+ "up.uplink_message.normalized_payload_warnings",
"up.uplink_message.received_at",
"up.uplink_message.rx_metadata",
"up.uplink_message.session_key_id",
@@ -920,6 +993,55 @@ var ApplicationUpFieldPathsNested = []string{
"up.uplink_message.version_ids.serial_number",
"up.uplink_message.version_ids.vendor_id",
"up.uplink_message.version_ids.vendor_profile_id",
+ "up.uplink_normalized",
+ "up.uplink_normalized.confirmed",
+ "up.uplink_normalized.consumed_airtime",
+ "up.uplink_normalized.f_cnt",
+ "up.uplink_normalized.f_port",
+ "up.uplink_normalized.frm_payload",
+ "up.uplink_normalized.locations",
+ "up.uplink_normalized.network_ids",
+ "up.uplink_normalized.network_ids.cluster_address",
+ "up.uplink_normalized.network_ids.cluster_id",
+ "up.uplink_normalized.network_ids.net_id",
+ "up.uplink_normalized.network_ids.tenant_address",
+ "up.uplink_normalized.network_ids.tenant_id",
+ "up.uplink_normalized.normalized_payload",
+ "up.uplink_normalized.normalized_payload_warnings",
+ "up.uplink_normalized.received_at",
+ "up.uplink_normalized.rx_metadata",
+ "up.uplink_normalized.session_key_id",
+ "up.uplink_normalized.settings",
+ "up.uplink_normalized.settings.coding_rate",
+ "up.uplink_normalized.settings.concentrator_timestamp",
+ "up.uplink_normalized.settings.data_rate",
+ "up.uplink_normalized.settings.data_rate.modulation",
+ "up.uplink_normalized.settings.data_rate.modulation.fsk",
+ "up.uplink_normalized.settings.data_rate.modulation.fsk.bit_rate",
+ "up.uplink_normalized.settings.data_rate.modulation.lora",
+ "up.uplink_normalized.settings.data_rate.modulation.lora.bandwidth",
+ "up.uplink_normalized.settings.data_rate.modulation.lora.spreading_factor",
+ "up.uplink_normalized.settings.data_rate.modulation.lrfhss",
+ "up.uplink_normalized.settings.data_rate.modulation.lrfhss.coding_rate",
+ "up.uplink_normalized.settings.data_rate.modulation.lrfhss.modulation_type",
+ "up.uplink_normalized.settings.data_rate.modulation.lrfhss.operating_channel_width",
+ "up.uplink_normalized.settings.downlink",
+ "up.uplink_normalized.settings.downlink.antenna_index",
+ "up.uplink_normalized.settings.downlink.invert_polarization",
+ "up.uplink_normalized.settings.downlink.tx_power",
+ "up.uplink_normalized.settings.enable_crc",
+ "up.uplink_normalized.settings.frequency",
+ "up.uplink_normalized.settings.time",
+ "up.uplink_normalized.settings.timestamp",
+ "up.uplink_normalized.version_ids",
+ "up.uplink_normalized.version_ids.band_id",
+ "up.uplink_normalized.version_ids.brand_id",
+ "up.uplink_normalized.version_ids.firmware_version",
+ "up.uplink_normalized.version_ids.hardware_version",
+ "up.uplink_normalized.version_ids.model_id",
+ "up.uplink_normalized.version_ids.serial_number",
+ "up.uplink_normalized.version_ids.vendor_id",
+ "up.uplink_normalized.version_ids.vendor_profile_id",
}
var ApplicationUpFieldPathsTopLevel = []string{
diff --git a/pkg/ttnpb/messages.pb.setters.fm.go b/pkg/ttnpb/messages.pb.setters.fm.go
index 67cde2d21c..a638bdeca5 100644
--- a/pkg/ttnpb/messages.pb.setters.fm.go
+++ b/pkg/ttnpb/messages.pb.setters.fm.go
@@ -522,6 +522,24 @@ func (dst *ApplicationUplink) SetFields(src *ApplicationUplink, paths ...string)
} else {
dst.DecodedPayloadWarnings = nil
}
+ case "normalized_payload":
+ if len(subs) > 0 {
+ return fmt.Errorf("'normalized_payload' has no subfields, but %s were specified", subs)
+ }
+ if src != nil {
+ dst.NormalizedPayload = src.NormalizedPayload
+ } else {
+ dst.NormalizedPayload = nil
+ }
+ case "normalized_payload_warnings":
+ if len(subs) > 0 {
+ return fmt.Errorf("'normalized_payload_warnings' has no subfields, but %s were specified", subs)
+ }
+ if src != nil {
+ dst.NormalizedPayloadWarnings = src.NormalizedPayloadWarnings
+ } else {
+ dst.NormalizedPayloadWarnings = nil
+ }
case "rx_metadata":
if len(subs) > 0 {
return fmt.Errorf("'rx_metadata' has no subfields, but %s were specified", subs)
@@ -686,6 +704,194 @@ func (dst *ApplicationUplink) SetFields(src *ApplicationUplink, paths ...string)
return nil
}
+func (dst *ApplicationUplinkNormalized) SetFields(src *ApplicationUplinkNormalized, paths ...string) error {
+ for name, subs := range _processPaths(paths) {
+ switch name {
+ case "session_key_id":
+ if len(subs) > 0 {
+ return fmt.Errorf("'session_key_id' has no subfields, but %s were specified", subs)
+ }
+ if src != nil {
+ dst.SessionKeyId = src.SessionKeyId
+ } else {
+ dst.SessionKeyId = nil
+ }
+ case "f_port":
+ if len(subs) > 0 {
+ return fmt.Errorf("'f_port' has no subfields, but %s were specified", subs)
+ }
+ if src != nil {
+ dst.FPort = src.FPort
+ } else {
+ var zero uint32
+ dst.FPort = zero
+ }
+ case "f_cnt":
+ if len(subs) > 0 {
+ return fmt.Errorf("'f_cnt' has no subfields, but %s were specified", subs)
+ }
+ if src != nil {
+ dst.FCnt = src.FCnt
+ } else {
+ var zero uint32
+ dst.FCnt = zero
+ }
+ case "frm_payload":
+ if len(subs) > 0 {
+ return fmt.Errorf("'frm_payload' has no subfields, but %s were specified", subs)
+ }
+ if src != nil {
+ dst.FrmPayload = src.FrmPayload
+ } else {
+ dst.FrmPayload = nil
+ }
+ case "normalized_payload":
+ if len(subs) > 0 {
+ return fmt.Errorf("'normalized_payload' has no subfields, but %s were specified", subs)
+ }
+ if src != nil {
+ dst.NormalizedPayload = src.NormalizedPayload
+ } else {
+ dst.NormalizedPayload = nil
+ }
+ case "normalized_payload_warnings":
+ if len(subs) > 0 {
+ return fmt.Errorf("'normalized_payload_warnings' has no subfields, but %s were specified", subs)
+ }
+ if src != nil {
+ dst.NormalizedPayloadWarnings = src.NormalizedPayloadWarnings
+ } else {
+ dst.NormalizedPayloadWarnings = nil
+ }
+ case "rx_metadata":
+ if len(subs) > 0 {
+ return fmt.Errorf("'rx_metadata' has no subfields, but %s were specified", subs)
+ }
+ if src != nil {
+ dst.RxMetadata = src.RxMetadata
+ } else {
+ dst.RxMetadata = nil
+ }
+ case "settings":
+ if len(subs) > 0 {
+ var newDst, newSrc *TxSettings
+ if (src == nil || src.Settings == nil) && dst.Settings == nil {
+ continue
+ }
+ if src != nil {
+ newSrc = src.Settings
+ }
+ if dst.Settings != nil {
+ newDst = dst.Settings
+ } else {
+ newDst = &TxSettings{}
+ dst.Settings = newDst
+ }
+ if err := newDst.SetFields(newSrc, subs...); err != nil {
+ return err
+ }
+ } else {
+ if src != nil {
+ dst.Settings = src.Settings
+ } else {
+ dst.Settings = nil
+ }
+ }
+ case "received_at":
+ if len(subs) > 0 {
+ return fmt.Errorf("'received_at' has no subfields, but %s were specified", subs)
+ }
+ if src != nil {
+ dst.ReceivedAt = src.ReceivedAt
+ } else {
+ dst.ReceivedAt = nil
+ }
+ case "confirmed":
+ if len(subs) > 0 {
+ return fmt.Errorf("'confirmed' has no subfields, but %s were specified", subs)
+ }
+ if src != nil {
+ dst.Confirmed = src.Confirmed
+ } else {
+ var zero bool
+ dst.Confirmed = zero
+ }
+ case "consumed_airtime":
+ if len(subs) > 0 {
+ return fmt.Errorf("'consumed_airtime' has no subfields, but %s were specified", subs)
+ }
+ if src != nil {
+ dst.ConsumedAirtime = src.ConsumedAirtime
+ } else {
+ dst.ConsumedAirtime = nil
+ }
+ case "locations":
+ if len(subs) > 0 {
+ return fmt.Errorf("'locations' has no subfields, but %s were specified", subs)
+ }
+ if src != nil {
+ dst.Locations = src.Locations
+ } else {
+ dst.Locations = nil
+ }
+ case "version_ids":
+ if len(subs) > 0 {
+ var newDst, newSrc *EndDeviceVersionIdentifiers
+ if (src == nil || src.VersionIds == nil) && dst.VersionIds == nil {
+ continue
+ }
+ if src != nil {
+ newSrc = src.VersionIds
+ }
+ if dst.VersionIds != nil {
+ newDst = dst.VersionIds
+ } else {
+ newDst = &EndDeviceVersionIdentifiers{}
+ dst.VersionIds = newDst
+ }
+ if err := newDst.SetFields(newSrc, subs...); err != nil {
+ return err
+ }
+ } else {
+ if src != nil {
+ dst.VersionIds = src.VersionIds
+ } else {
+ dst.VersionIds = nil
+ }
+ }
+ case "network_ids":
+ if len(subs) > 0 {
+ var newDst, newSrc *NetworkIdentifiers
+ if (src == nil || src.NetworkIds == nil) && dst.NetworkIds == nil {
+ continue
+ }
+ if src != nil {
+ newSrc = src.NetworkIds
+ }
+ if dst.NetworkIds != nil {
+ newDst = dst.NetworkIds
+ } else {
+ newDst = &NetworkIdentifiers{}
+ dst.NetworkIds = newDst
+ }
+ if err := newDst.SetFields(newSrc, subs...); err != nil {
+ return err
+ }
+ } else {
+ if src != nil {
+ dst.NetworkIds = src.NetworkIds
+ } else {
+ dst.NetworkIds = nil
+ }
+ }
+
+ default:
+ return fmt.Errorf("invalid field: '%s'", name)
+ }
+ }
+ return nil
+}
+
func (dst *ApplicationLocation) SetFields(src *ApplicationLocation, paths ...string) error {
for name, subs := range _processPaths(paths) {
switch name {
@@ -1260,6 +1466,42 @@ func (dst *ApplicationUp) SetFields(src *ApplicationUp, paths ...string) error {
dst.Up = nil
}
}
+ case "uplink_normalized":
+ var srcTypeOk bool
+ if src != nil {
+ _, srcTypeOk = src.Up.(*ApplicationUp_UplinkNormalized)
+ }
+ if srcValid := srcTypeOk || src == nil || src.Up == nil || len(oneofSubs) == 0; !srcValid {
+ return fmt.Errorf("attempt to set oneof 'uplink_normalized', while different oneof is set in source")
+ }
+ _, dstTypeOk := dst.Up.(*ApplicationUp_UplinkNormalized)
+ if dstValid := dstTypeOk || dst.Up == nil || len(oneofSubs) == 0; !dstValid {
+ return fmt.Errorf("attempt to set oneof 'uplink_normalized', while different oneof is set in destination")
+ }
+ if len(oneofSubs) > 0 {
+ var newDst, newSrc *ApplicationUplinkNormalized
+ if srcTypeOk {
+ newSrc = src.Up.(*ApplicationUp_UplinkNormalized).UplinkNormalized
+ }
+ if dstTypeOk {
+ newDst = dst.Up.(*ApplicationUp_UplinkNormalized).UplinkNormalized
+ } else if srcTypeOk {
+ newDst = &ApplicationUplinkNormalized{}
+ dst.Up = &ApplicationUp_UplinkNormalized{UplinkNormalized: newDst}
+ } else {
+ dst.Up = nil
+ continue
+ }
+ if err := newDst.SetFields(newSrc, oneofSubs...); err != nil {
+ return err
+ }
+ } else {
+ if srcTypeOk {
+ dst.Up = src.Up
+ } else {
+ dst.Up = nil
+ }
+ }
case "join_accept":
var srcTypeOk bool
if src != nil {
diff --git a/pkg/ttnpb/messages.pb.validate.go b/pkg/ttnpb/messages.pb.validate.go
index de738c0196..d61d839b60 100644
--- a/pkg/ttnpb/messages.pb.validate.go
+++ b/pkg/ttnpb/messages.pb.validate.go
@@ -767,6 +767,25 @@ func (m *ApplicationUplink) ValidateFields(paths ...string) error {
case "decoded_payload_warnings":
+ case "normalized_payload":
+
+ for idx, item := range m.GetNormalizedPayload() {
+ _, _ = idx, item
+
+ if v, ok := interface{}(item).(interface{ ValidateFields(...string) error }); ok {
+ if err := v.ValidateFields(subs...); err != nil {
+ return ApplicationUplinkValidationError{
+ field: fmt.Sprintf("normalized_payload[%v]", idx),
+ reason: "embedded message failed validation",
+ cause: err,
+ }
+ }
+ }
+
+ }
+
+ case "normalized_payload_warnings":
+
case "rx_metadata":
for idx, item := range m.GetRxMetadata() {
@@ -956,6 +975,244 @@ var _ApplicationUplink_FPort_NotInLookup = map[uint32]struct{}{
224: {},
}
+// ValidateFields checks the field values on ApplicationUplinkNormalized with
+// the rules defined in the proto definition for this message. If any rules
+// are violated, an error is returned.
+func (m *ApplicationUplinkNormalized) ValidateFields(paths ...string) error {
+ if m == nil {
+ return nil
+ }
+
+ if len(paths) == 0 {
+ paths = ApplicationUplinkNormalizedFieldPathsNested
+ }
+
+ for name, subs := range _processPaths(append(paths[:0:0], paths...)) {
+ _ = subs
+ switch name {
+ case "session_key_id":
+
+ if len(m.GetSessionKeyId()) > 2048 {
+ return ApplicationUplinkNormalizedValidationError{
+ field: "session_key_id",
+ reason: "value length must be at most 2048 bytes",
+ }
+ }
+
+ case "f_port":
+
+ if val := m.GetFPort(); val < 1 || val > 255 {
+ return ApplicationUplinkNormalizedValidationError{
+ field: "f_port",
+ reason: "value must be inside range [1, 255]",
+ }
+ }
+
+ if _, ok := _ApplicationUplinkNormalized_FPort_NotInLookup[m.GetFPort()]; ok {
+ return ApplicationUplinkNormalizedValidationError{
+ field: "f_port",
+ reason: "value must not be in list [224]",
+ }
+ }
+
+ case "f_cnt":
+ // no validation rules for FCnt
+ case "frm_payload":
+ // no validation rules for FrmPayload
+ case "normalized_payload":
+
+ if m.GetNormalizedPayload() == nil {
+ return ApplicationUplinkNormalizedValidationError{
+ field: "normalized_payload",
+ reason: "value is required",
+ }
+ }
+
+ if v, ok := interface{}(m.GetNormalizedPayload()).(interface{ ValidateFields(...string) error }); ok {
+ if err := v.ValidateFields(subs...); err != nil {
+ return ApplicationUplinkNormalizedValidationError{
+ field: "normalized_payload",
+ reason: "embedded message failed validation",
+ cause: err,
+ }
+ }
+ }
+
+ case "normalized_payload_warnings":
+
+ case "rx_metadata":
+
+ for idx, item := range m.GetRxMetadata() {
+ _, _ = idx, item
+
+ if v, ok := interface{}(item).(interface{ ValidateFields(...string) error }); ok {
+ if err := v.ValidateFields(subs...); err != nil {
+ return ApplicationUplinkNormalizedValidationError{
+ field: fmt.Sprintf("rx_metadata[%v]", idx),
+ reason: "embedded message failed validation",
+ cause: err,
+ }
+ }
+ }
+
+ }
+
+ case "settings":
+
+ if m.GetSettings() == nil {
+ return ApplicationUplinkNormalizedValidationError{
+ field: "settings",
+ reason: "value is required",
+ }
+ }
+
+ if v, ok := interface{}(m.GetSettings()).(interface{ ValidateFields(...string) error }); ok {
+ if err := v.ValidateFields(subs...); err != nil {
+ return ApplicationUplinkNormalizedValidationError{
+ field: "settings",
+ reason: "embedded message failed validation",
+ cause: err,
+ }
+ }
+ }
+
+ case "received_at":
+
+ if m.GetReceivedAt() == nil {
+ return ApplicationUplinkNormalizedValidationError{
+ field: "received_at",
+ reason: "value is required",
+ }
+ }
+
+ case "confirmed":
+ // no validation rules for Confirmed
+ case "consumed_airtime":
+
+ if v, ok := interface{}(m.GetConsumedAirtime()).(interface{ ValidateFields(...string) error }); ok {
+ if err := v.ValidateFields(subs...); err != nil {
+ return ApplicationUplinkNormalizedValidationError{
+ field: "consumed_airtime",
+ reason: "embedded message failed validation",
+ cause: err,
+ }
+ }
+ }
+
+ case "locations":
+
+ for key, val := range m.GetLocations() {
+ _ = val
+
+ // no validation rules for Locations[key]
+
+ if v, ok := interface{}(val).(interface{ ValidateFields(...string) error }); ok {
+ if err := v.ValidateFields(subs...); err != nil {
+ return ApplicationUplinkNormalizedValidationError{
+ field: fmt.Sprintf("locations[%v]", key),
+ reason: "embedded message failed validation",
+ cause: err,
+ }
+ }
+ }
+
+ }
+
+ case "version_ids":
+
+ if v, ok := interface{}(m.GetVersionIds()).(interface{ ValidateFields(...string) error }); ok {
+ if err := v.ValidateFields(subs...); err != nil {
+ return ApplicationUplinkNormalizedValidationError{
+ field: "version_ids",
+ reason: "embedded message failed validation",
+ cause: err,
+ }
+ }
+ }
+
+ case "network_ids":
+
+ if v, ok := interface{}(m.GetNetworkIds()).(interface{ ValidateFields(...string) error }); ok {
+ if err := v.ValidateFields(subs...); err != nil {
+ return ApplicationUplinkNormalizedValidationError{
+ field: "network_ids",
+ reason: "embedded message failed validation",
+ cause: err,
+ }
+ }
+ }
+
+ default:
+ return ApplicationUplinkNormalizedValidationError{
+ field: name,
+ reason: "invalid field path",
+ }
+ }
+ }
+ return nil
+}
+
+// ApplicationUplinkNormalizedValidationError is the validation error returned
+// by ApplicationUplinkNormalized.ValidateFields if the designated constraints
+// aren't met.
+type ApplicationUplinkNormalizedValidationError struct {
+ field string
+ reason string
+ cause error
+ key bool
+}
+
+// Field function returns field value.
+func (e ApplicationUplinkNormalizedValidationError) Field() string { return e.field }
+
+// Reason function returns reason value.
+func (e ApplicationUplinkNormalizedValidationError) Reason() string { return e.reason }
+
+// Cause function returns cause value.
+func (e ApplicationUplinkNormalizedValidationError) Cause() error { return e.cause }
+
+// Key function returns key value.
+func (e ApplicationUplinkNormalizedValidationError) Key() bool { return e.key }
+
+// ErrorName returns error name.
+func (e ApplicationUplinkNormalizedValidationError) ErrorName() string {
+ return "ApplicationUplinkNormalizedValidationError"
+}
+
+// Error satisfies the builtin error interface
+func (e ApplicationUplinkNormalizedValidationError) Error() string {
+ cause := ""
+ if e.cause != nil {
+ cause = fmt.Sprintf(" | caused by: %v", e.cause)
+ }
+
+ key := ""
+ if e.key {
+ key = "key for "
+ }
+
+ return fmt.Sprintf(
+ "invalid %sApplicationUplinkNormalized.%s: %s%s",
+ key,
+ e.field,
+ e.reason,
+ cause)
+}
+
+var _ error = ApplicationUplinkNormalizedValidationError{}
+
+var _ interface {
+ Field() string
+ Reason() string
+ Key() bool
+ Cause() error
+ ErrorName() string
+} = ApplicationUplinkNormalizedValidationError{}
+
+var _ApplicationUplinkNormalized_FPort_NotInLookup = map[uint32]struct{}{
+ 224: {},
+}
+
// ValidateFields checks the field values on ApplicationLocation with the rules
// defined in the proto definition for this message. If any rules are
// violated, an error is returned.
@@ -1152,13 +1409,10 @@ func (m *ApplicationJoinAccept) ValidateFields(paths ...string) error {
// no validation rules for PendingSession
case "received_at":
- if v, ok := interface{}(m.GetReceivedAt()).(interface{ ValidateFields(...string) error }); ok {
- if err := v.ValidateFields(subs...); err != nil {
- return ApplicationJoinAcceptValidationError{
- field: "received_at",
- reason: "embedded message failed validation",
- cause: err,
- }
+ if m.GetReceivedAt() == nil {
+ return ApplicationJoinAcceptValidationError{
+ field: "received_at",
+ reason: "value is required",
}
}
@@ -2017,7 +2271,7 @@ func (m *ApplicationUp) ValidateFields(paths ...string) error {
}
if len(subs) == 0 {
subs = []string{
- "uplink_message", "join_accept", "downlink_ack", "downlink_nack", "downlink_sent", "downlink_failed", "downlink_queued", "downlink_queue_invalidated", "location_solved", "service_data",
+ "uplink_message", "uplink_normalized", "join_accept", "downlink_ack", "downlink_nack", "downlink_sent", "downlink_failed", "downlink_queued", "downlink_queue_invalidated", "location_solved", "service_data",
}
}
for name, subs := range _processPaths(subs) {
@@ -2039,6 +2293,22 @@ func (m *ApplicationUp) ValidateFields(paths ...string) error {
}
}
+ case "uplink_normalized":
+ w, ok := m.Up.(*ApplicationUp_UplinkNormalized)
+ if !ok || w == nil {
+ continue
+ }
+
+ if v, ok := interface{}(m.GetUplinkNormalized()).(interface{ ValidateFields(...string) error }); ok {
+ if err := v.ValidateFields(subs...); err != nil {
+ return ApplicationUpValidationError{
+ field: "uplink_normalized",
+ reason: "embedded message failed validation",
+ cause: err,
+ }
+ }
+ }
+
case "join_accept":
w, ok := m.Up.(*ApplicationUp_JoinAccept)
if !ok || w == nil {
diff --git a/pkg/ttnpb/messages_flags.pb.go b/pkg/ttnpb/messages_flags.pb.go
index f1877f5600..02c8bfc3ae 100644
--- a/pkg/ttnpb/messages_flags.pb.go
+++ b/pkg/ttnpb/messages_flags.pb.go
@@ -20,6 +20,8 @@ func AddSelectFlagsForApplicationUplink(flags *pflag.FlagSet, prefix string, hid
flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("frm-payload", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("frm-payload", prefix), false), flagsplugin.WithHidden(hidden)))
flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("decoded-payload", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("decoded-payload", prefix), false), flagsplugin.WithHidden(hidden)))
flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("decoded-payload-warnings", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("decoded-payload-warnings", prefix), false), flagsplugin.WithHidden(hidden)))
+ flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("normalized-payload", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("normalized-payload", prefix), false), flagsplugin.WithHidden(hidden)))
+ flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("normalized-payload-warnings", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("normalized-payload-warnings", prefix), false), flagsplugin.WithHidden(hidden)))
flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("rx-metadata", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("rx-metadata", prefix), false), flagsplugin.WithHidden(hidden)))
flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("settings", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("settings", prefix), true), flagsplugin.WithHidden(hidden)))
AddSelectFlagsForTxSettings(flags, flagsplugin.Prefix("settings", prefix), hidden)
@@ -68,6 +70,16 @@ func PathsFromSelectFlagsForApplicationUplink(flags *pflag.FlagSet, prefix strin
} else if selected && val {
paths = append(paths, flagsplugin.Prefix("decoded_payload_warnings", prefix))
}
+ if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("normalized_payload", prefix)); err != nil {
+ return nil, err
+ } else if selected && val {
+ paths = append(paths, flagsplugin.Prefix("normalized_payload", prefix))
+ }
+ if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("normalized_payload_warnings", prefix)); err != nil {
+ return nil, err
+ } else if selected && val {
+ paths = append(paths, flagsplugin.Prefix("normalized_payload_warnings", prefix))
+ }
if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("rx_metadata", prefix)); err != nil {
return nil, err
} else if selected && val {
@@ -141,6 +153,117 @@ func PathsFromSelectFlagsForApplicationUplink(flags *pflag.FlagSet, prefix strin
return paths, nil
}
+// AddSelectFlagsForApplicationUplinkNormalized adds flags to select fields in ApplicationUplinkNormalized.
+func AddSelectFlagsForApplicationUplinkNormalized(flags *pflag.FlagSet, prefix string, hidden bool) {
+ flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("session-key-id", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("session-key-id", prefix), false), flagsplugin.WithHidden(hidden)))
+ flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("f-port", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("f-port", prefix), false), flagsplugin.WithHidden(hidden)))
+ flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("f-cnt", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("f-cnt", prefix), false), flagsplugin.WithHidden(hidden)))
+ flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("frm-payload", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("frm-payload", prefix), false), flagsplugin.WithHidden(hidden)))
+ flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("normalized-payload", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("normalized-payload", prefix), false), flagsplugin.WithHidden(hidden)))
+ flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("normalized-payload-warnings", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("normalized-payload-warnings", prefix), false), flagsplugin.WithHidden(hidden)))
+ flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("rx-metadata", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("rx-metadata", prefix), false), flagsplugin.WithHidden(hidden)))
+ flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("settings", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("settings", prefix), true), flagsplugin.WithHidden(hidden)))
+ AddSelectFlagsForTxSettings(flags, flagsplugin.Prefix("settings", prefix), hidden)
+ flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("received-at", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("received-at", prefix), false), flagsplugin.WithHidden(hidden)))
+ flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("confirmed", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("confirmed", prefix), false), flagsplugin.WithHidden(hidden)))
+ flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("consumed-airtime", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("consumed-airtime", prefix), false), flagsplugin.WithHidden(hidden)))
+ flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("locations", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("locations", prefix), false), flagsplugin.WithHidden(hidden)))
+ flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("version-ids", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("version-ids", prefix), true), flagsplugin.WithHidden(hidden)))
+ AddSelectFlagsForEndDeviceVersionIdentifiers(flags, flagsplugin.Prefix("version-ids", prefix), hidden)
+ flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("network-ids", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("network-ids", prefix), true), flagsplugin.WithHidden(hidden)))
+ AddSelectFlagsForNetworkIdentifiers(flags, flagsplugin.Prefix("network-ids", prefix), hidden)
+}
+
+// SelectFromFlags outputs the fieldmask paths forApplicationUplinkNormalized message from select flags.
+func PathsFromSelectFlagsForApplicationUplinkNormalized(flags *pflag.FlagSet, prefix string) (paths []string, err error) {
+ if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("session_key_id", prefix)); err != nil {
+ return nil, err
+ } else if selected && val {
+ paths = append(paths, flagsplugin.Prefix("session_key_id", prefix))
+ }
+ if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("f_port", prefix)); err != nil {
+ return nil, err
+ } else if selected && val {
+ paths = append(paths, flagsplugin.Prefix("f_port", prefix))
+ }
+ if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("f_cnt", prefix)); err != nil {
+ return nil, err
+ } else if selected && val {
+ paths = append(paths, flagsplugin.Prefix("f_cnt", prefix))
+ }
+ if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("frm_payload", prefix)); err != nil {
+ return nil, err
+ } else if selected && val {
+ paths = append(paths, flagsplugin.Prefix("frm_payload", prefix))
+ }
+ if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("normalized_payload", prefix)); err != nil {
+ return nil, err
+ } else if selected && val {
+ paths = append(paths, flagsplugin.Prefix("normalized_payload", prefix))
+ }
+ if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("normalized_payload_warnings", prefix)); err != nil {
+ return nil, err
+ } else if selected && val {
+ paths = append(paths, flagsplugin.Prefix("normalized_payload_warnings", prefix))
+ }
+ if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("rx_metadata", prefix)); err != nil {
+ return nil, err
+ } else if selected && val {
+ paths = append(paths, flagsplugin.Prefix("rx_metadata", prefix))
+ }
+ if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("settings", prefix)); err != nil {
+ return nil, err
+ } else if selected && val {
+ paths = append(paths, flagsplugin.Prefix("settings", prefix))
+ }
+ if selectPaths, err := PathsFromSelectFlagsForTxSettings(flags, flagsplugin.Prefix("settings", prefix)); err != nil {
+ return nil, err
+ } else {
+ paths = append(paths, selectPaths...)
+ }
+ if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("received_at", prefix)); err != nil {
+ return nil, err
+ } else if selected && val {
+ paths = append(paths, flagsplugin.Prefix("received_at", prefix))
+ }
+ if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("confirmed", prefix)); err != nil {
+ return nil, err
+ } else if selected && val {
+ paths = append(paths, flagsplugin.Prefix("confirmed", prefix))
+ }
+ if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("consumed_airtime", prefix)); err != nil {
+ return nil, err
+ } else if selected && val {
+ paths = append(paths, flagsplugin.Prefix("consumed_airtime", prefix))
+ }
+ if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("locations", prefix)); err != nil {
+ return nil, err
+ } else if selected && val {
+ paths = append(paths, flagsplugin.Prefix("locations", prefix))
+ }
+ if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("version_ids", prefix)); err != nil {
+ return nil, err
+ } else if selected && val {
+ paths = append(paths, flagsplugin.Prefix("version_ids", prefix))
+ }
+ if selectPaths, err := PathsFromSelectFlagsForEndDeviceVersionIdentifiers(flags, flagsplugin.Prefix("version_ids", prefix)); err != nil {
+ return nil, err
+ } else {
+ paths = append(paths, selectPaths...)
+ }
+ if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("network_ids", prefix)); err != nil {
+ return nil, err
+ } else if selected && val {
+ paths = append(paths, flagsplugin.Prefix("network_ids", prefix))
+ }
+ if selectPaths, err := PathsFromSelectFlagsForNetworkIdentifiers(flags, flagsplugin.Prefix("network_ids", prefix)); err != nil {
+ return nil, err
+ } else {
+ paths = append(paths, selectPaths...)
+ }
+ return paths, nil
+}
+
// AddSelectFlagsForApplicationLocation adds flags to select fields in ApplicationLocation.
func AddSelectFlagsForApplicationLocation(flags *pflag.FlagSet, prefix string, hidden bool) {
flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("service", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("service", prefix), false), flagsplugin.WithHidden(hidden)))
@@ -500,6 +623,8 @@ func AddSelectFlagsForApplicationUp(flags *pflag.FlagSet, prefix string, hidden
flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("received-at", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("received-at", prefix), false), flagsplugin.WithHidden(hidden)))
flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("up.uplink-message", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("up.uplink-message", prefix), true), flagsplugin.WithHidden(hidden)))
AddSelectFlagsForApplicationUplink(flags, flagsplugin.Prefix("up.uplink-message", prefix), hidden)
+ flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("up.uplink-normalized", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("up.uplink-normalized", prefix), true), flagsplugin.WithHidden(hidden)))
+ AddSelectFlagsForApplicationUplinkNormalized(flags, flagsplugin.Prefix("up.uplink-normalized", prefix), hidden)
flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("up.join-accept", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("up.join-accept", prefix), true), flagsplugin.WithHidden(hidden)))
AddSelectFlagsForApplicationJoinAccept(flags, flagsplugin.Prefix("up.join-accept", prefix), hidden)
flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("up.downlink-ack", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("up.downlink-ack", prefix), true), flagsplugin.WithHidden(hidden)))
@@ -553,6 +678,16 @@ func PathsFromSelectFlagsForApplicationUp(flags *pflag.FlagSet, prefix string) (
} else {
paths = append(paths, selectPaths...)
}
+ if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("up.uplink_normalized", prefix)); err != nil {
+ return nil, err
+ } else if selected && val {
+ paths = append(paths, flagsplugin.Prefix("up.uplink_normalized", prefix))
+ }
+ if selectPaths, err := PathsFromSelectFlagsForApplicationUplinkNormalized(flags, flagsplugin.Prefix("up.uplink_normalized", prefix)); err != nil {
+ return nil, err
+ } else {
+ paths = append(paths, selectPaths...)
+ }
if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("up.join_accept", prefix)); err != nil {
return nil, err
} else if selected && val {
diff --git a/pkg/ttnpb/messages_json.pb.go b/pkg/ttnpb/messages_json.pb.go
index 38b743ac1b..f993c85f89 100644
--- a/pkg/ttnpb/messages_json.pb.go
+++ b/pkg/ttnpb/messages_json.pb.go
@@ -600,6 +600,26 @@ func (x *ApplicationUplink) MarshalProtoJSON(s *jsonplugin.MarshalState) {
s.WriteObjectField("decoded_payload_warnings")
s.WriteStringArray(x.DecodedPayloadWarnings)
}
+ if len(x.NormalizedPayload) > 0 || s.HasField("normalized_payload") {
+ s.WriteMoreIf(&wroteField)
+ s.WriteObjectField("normalized_payload")
+ s.WriteArrayStart()
+ var wroteElement bool
+ for _, element := range x.NormalizedPayload {
+ s.WriteMoreIf(&wroteElement)
+ if element == nil {
+ s.WriteNil()
+ } else {
+ gogo.MarshalStruct(s, element)
+ }
+ }
+ s.WriteArrayEnd()
+ }
+ if len(x.NormalizedPayloadWarnings) > 0 || s.HasField("normalized_payload_warnings") {
+ s.WriteMoreIf(&wroteField)
+ s.WriteObjectField("normalized_payload_warnings")
+ s.WriteStringArray(x.NormalizedPayloadWarnings)
+ }
if len(x.RxMetadata) > 0 || s.HasField("rx_metadata") {
s.WriteMoreIf(&wroteField)
s.WriteObjectField("rx_metadata")
@@ -720,6 +740,26 @@ func (x *ApplicationUplink) UnmarshalProtoJSON(s *jsonplugin.UnmarshalState) {
return
}
x.DecodedPayloadWarnings = s.ReadStringArray()
+ case "normalized_payload", "normalizedPayload":
+ s.AddField("normalized_payload")
+ if s.ReadNil() {
+ x.NormalizedPayload = nil
+ return
+ }
+ s.ReadArray(func() {
+ v := gogo.UnmarshalStruct(s)
+ if s.Err() != nil {
+ return
+ }
+ x.NormalizedPayload = append(x.NormalizedPayload, v)
+ })
+ case "normalized_payload_warnings", "normalizedPayloadWarnings":
+ s.AddField("normalized_payload_warnings")
+ if s.ReadNil() {
+ x.NormalizedPayloadWarnings = nil
+ return
+ }
+ x.NormalizedPayloadWarnings = s.ReadStringArray()
case "rx_metadata", "rxMetadata":
s.AddField("rx_metadata")
if s.ReadNil() {
@@ -821,6 +861,249 @@ func (x *ApplicationUplink) UnmarshalJSON(b []byte) error {
return jsonplugin.DefaultUnmarshalerConfig.Unmarshal(b, x)
}
+// MarshalProtoJSON marshals the ApplicationUplinkNormalized message to JSON.
+func (x *ApplicationUplinkNormalized) MarshalProtoJSON(s *jsonplugin.MarshalState) {
+ if x == nil {
+ s.WriteNil()
+ return
+ }
+ s.WriteObjectStart()
+ var wroteField bool
+ if len(x.SessionKeyId) > 0 || s.HasField("session_key_id") {
+ s.WriteMoreIf(&wroteField)
+ s.WriteObjectField("session_key_id")
+ s.WriteBytes(x.SessionKeyId)
+ }
+ if x.FPort != 0 || s.HasField("f_port") {
+ s.WriteMoreIf(&wroteField)
+ s.WriteObjectField("f_port")
+ s.WriteUint32(x.FPort)
+ }
+ if x.FCnt != 0 || s.HasField("f_cnt") {
+ s.WriteMoreIf(&wroteField)
+ s.WriteObjectField("f_cnt")
+ s.WriteUint32(x.FCnt)
+ }
+ if len(x.FrmPayload) > 0 || s.HasField("frm_payload") {
+ s.WriteMoreIf(&wroteField)
+ s.WriteObjectField("frm_payload")
+ s.WriteBytes(x.FrmPayload)
+ }
+ if x.NormalizedPayload != nil || s.HasField("normalized_payload") {
+ s.WriteMoreIf(&wroteField)
+ s.WriteObjectField("normalized_payload")
+ if x.NormalizedPayload == nil {
+ s.WriteNil()
+ } else {
+ gogo.MarshalStruct(s, x.NormalizedPayload)
+ }
+ }
+ if len(x.NormalizedPayloadWarnings) > 0 || s.HasField("normalized_payload_warnings") {
+ s.WriteMoreIf(&wroteField)
+ s.WriteObjectField("normalized_payload_warnings")
+ s.WriteStringArray(x.NormalizedPayloadWarnings)
+ }
+ if len(x.RxMetadata) > 0 || s.HasField("rx_metadata") {
+ s.WriteMoreIf(&wroteField)
+ s.WriteObjectField("rx_metadata")
+ s.WriteArrayStart()
+ var wroteElement bool
+ for _, element := range x.RxMetadata {
+ s.WriteMoreIf(&wroteElement)
+ element.MarshalProtoJSON(s.WithField("rx_metadata"))
+ }
+ s.WriteArrayEnd()
+ }
+ if x.Settings != nil || s.HasField("settings") {
+ s.WriteMoreIf(&wroteField)
+ s.WriteObjectField("settings")
+ // NOTE: TxSettings does not seem to implement MarshalProtoJSON.
+ gogo.MarshalMessage(s, x.Settings)
+ }
+ if x.ReceivedAt != nil || s.HasField("received_at") {
+ s.WriteMoreIf(&wroteField)
+ s.WriteObjectField("received_at")
+ if x.ReceivedAt == nil {
+ s.WriteNil()
+ } else {
+ gogo.MarshalTimestamp(s, x.ReceivedAt)
+ }
+ }
+ if x.Confirmed || s.HasField("confirmed") {
+ s.WriteMoreIf(&wroteField)
+ s.WriteObjectField("confirmed")
+ s.WriteBool(x.Confirmed)
+ }
+ if x.ConsumedAirtime != nil || s.HasField("consumed_airtime") {
+ s.WriteMoreIf(&wroteField)
+ s.WriteObjectField("consumed_airtime")
+ if x.ConsumedAirtime == nil {
+ s.WriteNil()
+ } else {
+ gogo.MarshalDuration(s, x.ConsumedAirtime)
+ }
+ }
+ if x.Locations != nil || s.HasField("locations") {
+ s.WriteMoreIf(&wroteField)
+ s.WriteObjectField("locations")
+ s.WriteObjectStart()
+ var wroteElement bool
+ for k, v := range x.Locations {
+ s.WriteMoreIf(&wroteElement)
+ s.WriteObjectStringField(k)
+ v.MarshalProtoJSON(s.WithField("locations"))
+ }
+ s.WriteObjectEnd()
+ }
+ if x.VersionIds != nil || s.HasField("version_ids") {
+ s.WriteMoreIf(&wroteField)
+ s.WriteObjectField("version_ids")
+ // NOTE: EndDeviceVersionIdentifiers does not seem to implement MarshalProtoJSON.
+ gogo.MarshalMessage(s, x.VersionIds)
+ }
+ if x.NetworkIds != nil || s.HasField("network_ids") {
+ s.WriteMoreIf(&wroteField)
+ s.WriteObjectField("network_ids")
+ x.NetworkIds.MarshalProtoJSON(s.WithField("network_ids"))
+ }
+ s.WriteObjectEnd()
+}
+
+// MarshalJSON marshals the ApplicationUplinkNormalized to JSON.
+func (x *ApplicationUplinkNormalized) MarshalJSON() ([]byte, error) {
+ return jsonplugin.DefaultMarshalerConfig.Marshal(x)
+}
+
+// UnmarshalProtoJSON unmarshals the ApplicationUplinkNormalized message from JSON.
+func (x *ApplicationUplinkNormalized) UnmarshalProtoJSON(s *jsonplugin.UnmarshalState) {
+ if s.ReadNil() {
+ return
+ }
+ s.ReadObject(func(key string) {
+ switch key {
+ default:
+ s.ReadAny() // ignore unknown field
+ case "session_key_id", "sessionKeyId":
+ s.AddField("session_key_id")
+ x.SessionKeyId = s.ReadBytes()
+ case "f_port", "fPort":
+ s.AddField("f_port")
+ x.FPort = s.ReadUint32()
+ case "f_cnt", "fCnt":
+ s.AddField("f_cnt")
+ x.FCnt = s.ReadUint32()
+ case "frm_payload", "frmPayload":
+ s.AddField("frm_payload")
+ x.FrmPayload = s.ReadBytes()
+ case "normalized_payload", "normalizedPayload":
+ s.AddField("normalized_payload")
+ if s.ReadNil() {
+ x.NormalizedPayload = nil
+ return
+ }
+ v := gogo.UnmarshalStruct(s)
+ if s.Err() != nil {
+ return
+ }
+ x.NormalizedPayload = v
+ case "normalized_payload_warnings", "normalizedPayloadWarnings":
+ s.AddField("normalized_payload_warnings")
+ if s.ReadNil() {
+ x.NormalizedPayloadWarnings = nil
+ return
+ }
+ x.NormalizedPayloadWarnings = s.ReadStringArray()
+ case "rx_metadata", "rxMetadata":
+ s.AddField("rx_metadata")
+ if s.ReadNil() {
+ x.RxMetadata = nil
+ return
+ }
+ s.ReadArray(func() {
+ if s.ReadNil() {
+ x.RxMetadata = append(x.RxMetadata, nil)
+ return
+ }
+ v := &RxMetadata{}
+ v.UnmarshalProtoJSON(s.WithField("rx_metadata", false))
+ if s.Err() != nil {
+ return
+ }
+ x.RxMetadata = append(x.RxMetadata, v)
+ })
+ case "settings":
+ s.AddField("settings")
+ if s.ReadNil() {
+ x.Settings = nil
+ return
+ }
+ // NOTE: TxSettings does not seem to implement UnmarshalProtoJSON.
+ var v TxSettings
+ gogo.UnmarshalMessage(s, &v)
+ x.Settings = &v
+ case "received_at", "receivedAt":
+ s.AddField("received_at")
+ if s.ReadNil() {
+ x.ReceivedAt = nil
+ return
+ }
+ v := gogo.UnmarshalTimestamp(s)
+ if s.Err() != nil {
+ return
+ }
+ x.ReceivedAt = v
+ case "confirmed":
+ s.AddField("confirmed")
+ x.Confirmed = s.ReadBool()
+ case "consumed_airtime", "consumedAirtime":
+ s.AddField("consumed_airtime")
+ if s.ReadNil() {
+ x.ConsumedAirtime = nil
+ return
+ }
+ v := gogo.UnmarshalDuration(s)
+ if s.Err() != nil {
+ return
+ }
+ x.ConsumedAirtime = v
+ case "locations":
+ s.AddField("locations")
+ if s.ReadNil() {
+ x.Locations = nil
+ return
+ }
+ x.Locations = make(map[string]*Location)
+ s.ReadStringMap(func(key string) {
+ var v Location
+ v.UnmarshalProtoJSON(s)
+ x.Locations[key] = &v
+ })
+ case "version_ids", "versionIds":
+ s.AddField("version_ids")
+ if s.ReadNil() {
+ x.VersionIds = nil
+ return
+ }
+ // NOTE: EndDeviceVersionIdentifiers does not seem to implement UnmarshalProtoJSON.
+ var v EndDeviceVersionIdentifiers
+ gogo.UnmarshalMessage(s, &v)
+ x.VersionIds = &v
+ case "network_ids", "networkIds":
+ if s.ReadNil() {
+ x.NetworkIds = nil
+ return
+ }
+ x.NetworkIds = &NetworkIdentifiers{}
+ x.NetworkIds.UnmarshalProtoJSON(s.WithField("network_ids", true))
+ }
+ })
+}
+
+// UnmarshalJSON unmarshals the ApplicationUplinkNormalized from JSON.
+func (x *ApplicationUplinkNormalized) UnmarshalJSON(b []byte) error {
+ return jsonplugin.DefaultUnmarshalerConfig.Unmarshal(b, x)
+}
+
// MarshalProtoJSON marshals the ApplicationLocation message to JSON.
func (x *ApplicationLocation) MarshalProtoJSON(s *jsonplugin.MarshalState) {
if x == nil {
@@ -1548,6 +1831,10 @@ func (x *ApplicationUp) MarshalProtoJSON(s *jsonplugin.MarshalState) {
s.WriteMoreIf(&wroteField)
s.WriteObjectField("uplink_message")
ov.UplinkMessage.MarshalProtoJSON(s.WithField("uplink_message"))
+ case *ApplicationUp_UplinkNormalized:
+ s.WriteMoreIf(&wroteField)
+ s.WriteObjectField("uplink_normalized")
+ ov.UplinkNormalized.MarshalProtoJSON(s.WithField("uplink_normalized"))
case *ApplicationUp_JoinAccept:
s.WriteMoreIf(&wroteField)
s.WriteObjectField("join_accept")
@@ -1643,6 +1930,15 @@ func (x *ApplicationUp) UnmarshalProtoJSON(s *jsonplugin.UnmarshalState) {
}
ov.UplinkMessage = &ApplicationUplink{}
ov.UplinkMessage.UnmarshalProtoJSON(s.WithField("uplink_message", true))
+ case "uplink_normalized", "uplinkNormalized":
+ ov := &ApplicationUp_UplinkNormalized{}
+ x.Up = ov
+ if s.ReadNil() {
+ ov.UplinkNormalized = nil
+ return
+ }
+ ov.UplinkNormalized = &ApplicationUplinkNormalized{}
+ ov.UplinkNormalized.UnmarshalProtoJSON(s.WithField("uplink_normalized", true))
case "join_accept", "joinAccept":
ov := &ApplicationUp_JoinAccept{}
x.Up = ov
diff --git a/pkg/webui/console/components/events/previews/application-uplink-normalized.js b/pkg/webui/console/components/events/previews/application-uplink-normalized.js
new file mode 100644
index 0000000000..a390eb46a2
--- /dev/null
+++ b/pkg/webui/console/components/events/previews/application-uplink-normalized.js
@@ -0,0 +1,58 @@
+// Copyright © 2022 The Things Network Foundation, The Things Industries B.V.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import React from 'react'
+
+import { getDataRate, getSignalInformation } from '@console/components/events/utils'
+
+import PropTypes from '@ttn-lw/lib/prop-types'
+import sharedMessages from '@ttn-lw/lib/shared-messages'
+
+import messages from '../messages'
+
+import DescriptionList from './shared/description-list'
+import JSONPayload from './shared/json-payload'
+
+const ApplicationUplinkNormalizedPreview = React.memo(({ event }) => {
+ const { data, identifiers } = event
+ const deviceIds = identifiers[0].device_ids
+ const { snr, rssi } = getSignalInformation(data)
+ const dataRate = getDataRate(data)
+
+ return (
+
+
+ {data.normalized_payload.air && (
+
+
+
+ )}
+ {data.normalized_payload.wind && (
+
+
+
+ )}
+
+
+
+
+
+ )
+})
+
+ApplicationUplinkNormalizedPreview.propTypes = {
+ event: PropTypes.event.isRequired,
+}
+
+export default ApplicationUplinkNormalizedPreview
diff --git a/pkg/webui/console/components/events/utils/definitions.js b/pkg/webui/console/components/events/utils/definitions.js
index e6d5e1dd24..6870527bf0 100644
--- a/pkg/webui/console/components/events/utils/definitions.js
+++ b/pkg/webui/console/components/events/utils/definitions.js
@@ -16,6 +16,7 @@ import DownlinkMessage from '../previews/downlink-message'
import GatewayUplinkMessage from '../previews/gateway-uplink-message'
import ApplicationDownlink from '../previews/application-downlink'
import ApplicationUplink from '../previews/application-uplink'
+import ApplicationUplinkNormalized from '../previews/application-uplink-normalized'
import ApplicationUp from '../previews/application-up'
import ApplicationLocation from '../previews/application-location'
import JoinRequest from '../previews/join-request'
@@ -98,6 +99,7 @@ export const eventIconMap = [
export const dataTypeMap = {
ApplicationDownlink,
ApplicationUplink,
+ ApplicationUplinkNormalized,
ApplicationUp,
ApplicationLocation,
DownlinkMessage,
@@ -111,6 +113,7 @@ export const dataTypeMap = {
export const applicationUpMessages = [
'uplink_message',
+ 'uplink_normalized',
'join_accept',
'downlink_ack',
'downlink_nack',
diff --git a/pkg/webui/console/components/events/utils/index.js b/pkg/webui/console/components/events/utils/index.js
index e625467c6d..867019879f 100644
--- a/pkg/webui/console/components/events/utils/index.js
+++ b/pkg/webui/console/components/events/utils/index.js
@@ -68,6 +68,9 @@ export const getPreviewComponentByApplicationUpMessage = message => {
case 'uplink_message':
messageType = 'ApplicationUplink'
break
+ case 'uplink_normalized':
+ messageType = 'ApplicationUplinkNormalized'
+ break
case 'join_accept':
messageType = 'ApplicationJoinAccept'
break
@@ -86,6 +89,7 @@ export const getPreviewComponentByApplicationUpMessage = message => {
break
case 'service_data':
messageType = 'ApplicationServiceData'
+ break
}
return messageType in dataTypeMap ? dataTypeMap[messageType] : DefaultPreview
diff --git a/pkg/webui/console/components/pubsub-form/index.js b/pkg/webui/console/components/pubsub-form/index.js
index 5fa0a6175f..1db0e11d68 100644
--- a/pkg/webui/console/components/pubsub-form/index.js
+++ b/pkg/webui/console/components/pubsub-form/index.js
@@ -355,6 +355,14 @@ export default class PubsubForm extends Component {
component={Input.Toggled}
description={sharedMessages.eventUplinkMessageDesc}
/>
+
{
location_solved: mapPubsubMessageTypeToFormValue(pubsub.location_solved),
service_data: mapPubsubMessageTypeToFormValue(pubsub.service_data),
uplink_message: mapPubsubMessageTypeToFormValue(pubsub.uplink_message),
+ uplink_normalized: mapPubsubMessageTypeToFormValue(pubsub.uplink_normalized),
}
return result
@@ -137,6 +138,7 @@ export const mapFormValuesToPubsub = (values, appId) => {
location_solved: mapMessageTypeFormValueToPubsubMessageType(values.location_solved),
service_data: mapMessageTypeFormValueToPubsubMessageType(values.service_data),
uplink_message: mapMessageTypeFormValueToPubsubMessageType(values.uplink_message),
+ uplink_normalized: mapMessageTypeFormValueToPubsubMessageType(values.uplink_normalized),
}
switch (values._provider) {
@@ -172,4 +174,5 @@ export const blankValues = {
location_solved: { enabled: false, value: '' },
service_data: { enabled: false, value: '' },
uplink_message: { enabled: false, value: '' },
+ uplink_normalized: { enabled: false, value: '' },
}
diff --git a/pkg/webui/console/components/webhook-form/index.js b/pkg/webui/console/components/webhook-form/index.js
index 07b352203a..7b8d4def53 100644
--- a/pkg/webui/console/components/webhook-form/index.js
+++ b/pkg/webui/console/components/webhook-form/index.js
@@ -168,6 +168,12 @@ const validationSchema = Yup.object().shape({
})
.test('has path length at most 64 characters', m.messagePathValidateTooLong, messageCheck)
.nullable(),
+ uplink_normalized: Yup.object()
+ .shape({
+ path: Yup.string(),
+ })
+ .test('has path length at most 64 characters', m.messagePathValidateTooLong, messageCheck)
+ .nullable(),
join_accept: Yup.object()
.shape({
path: Yup.string(),
@@ -516,6 +522,16 @@ export default class WebhookForm extends Component {
component={Input.Toggled}
description={sharedMessages.eventUplinkMessageDesc}
/>
+
{
template_fields: fields,
base_url: urlTemplate.parse(template.base_url).expand(fields),
uplink_message: pathExpand(template.uplink_message, fields),
+ uplink_normalized: pathExpand(template.uplink_normalized, fields),
join_accept: pathExpand(template.join_accept, fields),
downlink_ack: pathExpand(template.downlink_ack, fields),
downlink_nack: pathExpand(template.downlink_nack, fields),
diff --git a/pkg/webui/console/views/application-integrations-pubsub-edit/connect.js b/pkg/webui/console/views/application-integrations-pubsub-edit/connect.js
index a52eff280a..6ea7824986 100644
--- a/pkg/webui/console/views/application-integrations-pubsub-edit/connect.js
+++ b/pkg/webui/console/views/application-integrations-pubsub-edit/connect.js
@@ -49,6 +49,7 @@ const pubsubEntitySelector = [
'location_solved',
'service_data',
'uplink_message',
+ 'uplink_normalized',
]
const mapStateToProps = state => ({
diff --git a/pkg/webui/console/views/application-integrations-webhook-edit/connect.js b/pkg/webui/console/views/application-integrations-webhook-edit/connect.js
index ffbed7df83..941ce5a9e9 100644
--- a/pkg/webui/console/views/application-integrations-webhook-edit/connect.js
+++ b/pkg/webui/console/views/application-integrations-webhook-edit/connect.js
@@ -35,6 +35,7 @@ const webhookEntitySelector = [
'headers',
'downlink_api_key',
'uplink_message',
+ 'uplink_normalized',
'join_accept',
'downlink_ack',
'downlink_nack',
diff --git a/pkg/webui/console/views/application-integrations-webhooks/connect.js b/pkg/webui/console/views/application-integrations-webhooks/connect.js
index ddad4abc1c..1a8bf91841 100644
--- a/pkg/webui/console/views/application-integrations-webhooks/connect.js
+++ b/pkg/webui/console/views/application-integrations-webhooks/connect.js
@@ -51,6 +51,7 @@ const selector = [
'name',
'service_data',
'uplink_message',
+ 'uplink_normalized',
]
const mapStateToProps = state => ({
diff --git a/pkg/webui/lib/shared-messages.js b/pkg/webui/lib/shared-messages.js
index 9cf785e3ad..934b7b2038 100644
--- a/pkg/webui/lib/shared-messages.js
+++ b/pkg/webui/lib/shared-messages.js
@@ -189,6 +189,7 @@ export default defineMessages({
eventLocationSolvedDesc: 'An integration succeeded locating the end device',
eventServiceDataDesc: 'An integration emits an event',
eventUplinkMessageDesc: 'An uplink message is received by the application',
+ eventUplinkNormalizedDesc: 'A normalized uplink payload',
eventsCannotShow: 'Cannot show events',
expiry: 'Expiry',
exportJson: 'Export as JSON',
@@ -295,6 +296,8 @@ export default defineMessages({
noLocation: 'No location information available',
noMatch: 'No items found',
none: 'None',
+ normalizedPayloadAir: 'Air',
+ normalizedPayloadWind: 'Wind',
notAvailable: 'n/a',
notLinked: 'Not linked',
notSet: 'Not set',
@@ -416,6 +419,7 @@ export default defineMessages({
uplink: 'Uplink',
uplinkFrameCount: 'Uplink frame count',
uplinkMessage: 'Uplink message',
+ uplinkNormalized: 'Normalized uplink',
uplinksReceived: 'Uplinks received',
unexposed: 'Unexposed',
used: '{currentValue}/{maxValue} used',
diff --git a/pkg/webui/locales/en.json b/pkg/webui/locales/en.json
index 834c4fc18a..dffbf31a5f 100644
--- a/pkg/webui/locales/en.json
+++ b/pkg/webui/locales/en.json
@@ -1219,6 +1219,7 @@
"lib.shared-messages.eventLocationSolvedDesc": "An integration succeeded locating the end device",
"lib.shared-messages.eventServiceDataDesc": "An integration emits an event",
"lib.shared-messages.eventUplinkMessageDesc": "An uplink message is received by the application",
+ "lib.shared-messages.eventUplinkNormalizedDesc": "A normalized uplink payload",
"lib.shared-messages.eventsCannotShow": "Cannot show events",
"lib.shared-messages.expiry": "Expiry",
"lib.shared-messages.exportJson": "Export as JSON",
@@ -1319,6 +1320,8 @@
"lib.shared-messages.noLocation": "No location information available",
"lib.shared-messages.noMatch": "No items found",
"lib.shared-messages.none": "None",
+ "lib.shared-messages.normalizedPayloadAir": "Air",
+ "lib.shared-messages.normalizedPayloadWind": "Wind",
"lib.shared-messages.notAvailable": "n/a",
"lib.shared-messages.notLinked": "Not linked",
"lib.shared-messages.notSet": "Not set",
@@ -1436,6 +1439,7 @@
"lib.shared-messages.uplink": "Uplink",
"lib.shared-messages.uplinkFrameCount": "Uplink frame count",
"lib.shared-messages.uplinkMessage": "Uplink message",
+ "lib.shared-messages.uplinkNormalized": "Normalized uplink",
"lib.shared-messages.uplinksReceived": "Uplinks received",
"lib.shared-messages.unexposed": "Unexposed",
"lib.shared-messages.used": "{currentValue}/{maxValue} used",
diff --git a/pkg/webui/locales/ja.json b/pkg/webui/locales/ja.json
index c7241aab95..489283c134 100644
--- a/pkg/webui/locales/ja.json
+++ b/pkg/webui/locales/ja.json
@@ -1791,6 +1791,7 @@
"event:as.up.join.forward": "ジョイン許可メッセージを転送",
"event:as.up.join.receive": "ジョイン許可メッセージを受信",
"event:as.up.location.forward": "ロケーション解明メッセージの転送",
+ "event:as.up.normalized.forward": "正規化されたアップリンクデータメッセージの転送",
"event:as.up.service.forward": "サービスデータメッセージの転送",
"event:client.collaborator.delete": "クライアントコラボレータの削除",
"event:client.collaborator.update": "クライアントコラボレータの更新",
diff --git a/sdk/js/generated/api-definition.json b/sdk/js/generated/api-definition.json
index 577a75bc8a..b7275cd545 100644
--- a/sdk/js/generated/api-definition.json
+++ b/sdk/js/generated/api-definition.json
@@ -810,6 +810,8 @@
"up.uplink_message.network_ids.net_id",
"up.uplink_message.network_ids.tenant_address",
"up.uplink_message.network_ids.tenant_id",
+ "up.uplink_message.normalized_payload",
+ "up.uplink_message.normalized_payload_warnings",
"up.uplink_message.received_at",
"up.uplink_message.rx_metadata",
"up.uplink_message.session_key_id",
@@ -844,6 +846,55 @@
"up.uplink_message.version_ids.serial_number",
"up.uplink_message.version_ids.vendor_id",
"up.uplink_message.version_ids.vendor_profile_id",
+ "up.uplink_normalized",
+ "up.uplink_normalized.confirmed",
+ "up.uplink_normalized.consumed_airtime",
+ "up.uplink_normalized.f_cnt",
+ "up.uplink_normalized.f_port",
+ "up.uplink_normalized.frm_payload",
+ "up.uplink_normalized.locations",
+ "up.uplink_normalized.network_ids",
+ "up.uplink_normalized.network_ids.cluster_address",
+ "up.uplink_normalized.network_ids.cluster_id",
+ "up.uplink_normalized.network_ids.net_id",
+ "up.uplink_normalized.network_ids.tenant_address",
+ "up.uplink_normalized.network_ids.tenant_id",
+ "up.uplink_normalized.normalized_payload",
+ "up.uplink_normalized.normalized_payload_warnings",
+ "up.uplink_normalized.received_at",
+ "up.uplink_normalized.rx_metadata",
+ "up.uplink_normalized.session_key_id",
+ "up.uplink_normalized.settings",
+ "up.uplink_normalized.settings.coding_rate",
+ "up.uplink_normalized.settings.concentrator_timestamp",
+ "up.uplink_normalized.settings.data_rate",
+ "up.uplink_normalized.settings.data_rate.modulation",
+ "up.uplink_normalized.settings.data_rate.modulation.fsk",
+ "up.uplink_normalized.settings.data_rate.modulation.fsk.bit_rate",
+ "up.uplink_normalized.settings.data_rate.modulation.lora",
+ "up.uplink_normalized.settings.data_rate.modulation.lora.bandwidth",
+ "up.uplink_normalized.settings.data_rate.modulation.lora.spreading_factor",
+ "up.uplink_normalized.settings.data_rate.modulation.lrfhss",
+ "up.uplink_normalized.settings.data_rate.modulation.lrfhss.coding_rate",
+ "up.uplink_normalized.settings.data_rate.modulation.lrfhss.modulation_type",
+ "up.uplink_normalized.settings.data_rate.modulation.lrfhss.operating_channel_width",
+ "up.uplink_normalized.settings.downlink",
+ "up.uplink_normalized.settings.downlink.antenna_index",
+ "up.uplink_normalized.settings.downlink.invert_polarization",
+ "up.uplink_normalized.settings.downlink.tx_power",
+ "up.uplink_normalized.settings.enable_crc",
+ "up.uplink_normalized.settings.frequency",
+ "up.uplink_normalized.settings.time",
+ "up.uplink_normalized.settings.timestamp",
+ "up.uplink_normalized.version_ids",
+ "up.uplink_normalized.version_ids.band_id",
+ "up.uplink_normalized.version_ids.brand_id",
+ "up.uplink_normalized.version_ids.firmware_version",
+ "up.uplink_normalized.version_ids.hardware_version",
+ "up.uplink_normalized.version_ids.model_id",
+ "up.uplink_normalized.version_ids.serial_number",
+ "up.uplink_normalized.version_ids.vendor_id",
+ "up.uplink_normalized.version_ids.vendor_profile_id",
"up.uplink_message.app_s_key",
"up.uplink_message.app_s_key.encrypted_key",
"up.uplink_message.app_s_key.kek_label",
@@ -863,6 +914,8 @@
"up.uplink_message.network_ids.net_id",
"up.uplink_message.network_ids.tenant_address",
"up.uplink_message.network_ids.tenant_id",
+ "up.uplink_message.normalized_payload",
+ "up.uplink_message.normalized_payload_warnings",
"up.uplink_message.received_at",
"up.uplink_message.rx_metadata",
"up.uplink_message.session_key_id",
@@ -1300,7 +1353,9 @@
"service_data.topic",
"updated_at",
"uplink_message",
- "uplink_message.topic"
+ "uplink_message.topic",
+ "uplink_normalized",
+ "uplink_normalized.topic"
]
},
"List": {
@@ -1375,7 +1430,9 @@
"service_data.topic",
"updated_at",
"uplink_message",
- "uplink_message.topic"
+ "uplink_message.topic",
+ "uplink_normalized",
+ "uplink_normalized.topic"
]
},
"Set": {
@@ -1460,7 +1517,9 @@
"service_data.topic",
"updated_at",
"uplink_message",
- "uplink_message.topic"
+ "uplink_message.topic",
+ "uplink_normalized",
+ "uplink_normalized.topic"
]
},
"Delete": {
@@ -1532,7 +1591,9 @@
"service_data",
"service_data.path",
"uplink_message",
- "uplink_message.path"
+ "uplink_message.path",
+ "uplink_normalized",
+ "uplink_normalized.path"
]
},
"ListTemplates": {
@@ -1577,7 +1638,9 @@
"service_data",
"service_data.path",
"uplink_message",
- "uplink_message.path"
+ "uplink_message.path",
+ "uplink_normalized",
+ "uplink_normalized.path"
]
},
"Get": {
@@ -1646,7 +1709,9 @@
"template_ids.template_id",
"updated_at",
"uplink_message",
- "uplink_message.path"
+ "uplink_message.path",
+ "uplink_normalized",
+ "uplink_normalized.path"
]
},
"List": {
@@ -1714,7 +1779,9 @@
"template_ids.template_id",
"updated_at",
"uplink_message",
- "uplink_message.path"
+ "uplink_message.path",
+ "uplink_normalized",
+ "uplink_normalized.path"
]
},
"Set": {
@@ -1792,7 +1859,9 @@
"template_ids.template_id",
"updated_at",
"uplink_message",
- "uplink_message.path"
+ "uplink_message.path",
+ "uplink_normalized",
+ "uplink_normalized.path"
]
},
"Delete": {
diff --git a/sdk/js/generated/api.json b/sdk/js/generated/api.json
index 1805613ac6..19e09639c6 100644
--- a/sdk/js/generated/api.json
+++ b/sdk/js/generated/api.json
@@ -4356,6 +4356,18 @@
"oneofdecl": "",
"defaultValue": ""
},
+ {
+ "name": "uplink_normalized",
+ "description": "",
+ "label": "",
+ "type": "Message",
+ "longType": "ApplicationPubSub.Message",
+ "fullType": "ttn.lorawan.v3.ApplicationPubSub.Message",
+ "ismap": false,
+ "isoneof": false,
+ "oneofdecl": "",
+ "defaultValue": ""
+ },
{
"name": "join_accept",
"description": "",
@@ -5656,6 +5668,18 @@
"oneofdecl": "",
"defaultValue": ""
},
+ {
+ "name": "uplink_normalized",
+ "description": "",
+ "label": "",
+ "type": "Message",
+ "longType": "ApplicationWebhook.Message",
+ "fullType": "ttn.lorawan.v3.ApplicationWebhook.Message",
+ "ismap": false,
+ "isoneof": false,
+ "oneofdecl": "",
+ "defaultValue": ""
+ },
{
"name": "join_accept",
"description": "",
@@ -6351,6 +6375,18 @@
"oneofdecl": "",
"defaultValue": ""
},
+ {
+ "name": "uplink_normalized",
+ "description": "",
+ "label": "",
+ "type": "Message",
+ "longType": "ApplicationWebhookTemplate.Message",
+ "fullType": "ttn.lorawan.v3.ApplicationWebhookTemplate.Message",
+ "ismap": false,
+ "isoneof": false,
+ "oneofdecl": "",
+ "defaultValue": ""
+ },
{
"name": "join_accept",
"description": "",
@@ -31500,7 +31536,15 @@
"ismap": false,
"isoneof": false,
"oneofdecl": "",
- "defaultValue": ""
+ "defaultValue": "",
+ "options": {
+ "validate.rules": [
+ {
+ "name": "timestamp.required",
+ "value": true
+ }
+ ]
+ }
}
]
},
@@ -31726,6 +31770,18 @@
"oneofdecl": "up",
"defaultValue": ""
},
+ {
+ "name": "uplink_normalized",
+ "description": "",
+ "label": "",
+ "type": "ApplicationUplinkNormalized",
+ "longType": "ApplicationUplinkNormalized",
+ "fullType": "ttn.lorawan.v3.ApplicationUplinkNormalized",
+ "ismap": false,
+ "isoneof": true,
+ "oneofdecl": "up",
+ "defaultValue": ""
+ },
{
"name": "join_accept",
"description": "",
@@ -31880,7 +31936,7 @@
},
{
"name": "f_port",
- "description": "",
+ "description": "LoRaWAN FPort of the uplink message.",
"label": "",
"type": "uint32",
"longType": "uint32",
@@ -31906,7 +31962,7 @@
},
{
"name": "f_cnt",
- "description": "",
+ "description": "LoRaWAN FCntUp of the uplink message.",
"label": "",
"type": "uint32",
"longType": "uint32",
@@ -31952,6 +32008,30 @@
"oneofdecl": "",
"defaultValue": ""
},
+ {
+ "name": "normalized_payload",
+ "description": "The normalized frame payload of the uplink message.\nThis field is set by the message processor that is configured for the end device (see formatters) or application (see default_formatters).\nIf the message processor is a custom script, there is no uplink normalizer script and the decoded output is valid\nnormalized payload, this field contains the decoded payload.",
+ "label": "repeated",
+ "type": "Struct",
+ "longType": "google.protobuf.Struct",
+ "fullType": "google.protobuf.Struct",
+ "ismap": false,
+ "isoneof": false,
+ "oneofdecl": "",
+ "defaultValue": ""
+ },
+ {
+ "name": "normalized_payload_warnings",
+ "description": "Warnings generated by the message processor while normalizing the decoded payload.",
+ "label": "repeated",
+ "type": "string",
+ "longType": "string",
+ "fullType": "string",
+ "ismap": false,
+ "isoneof": false,
+ "oneofdecl": "",
+ "defaultValue": ""
+ },
{
"name": "rx_metadata",
"description": "A list of metadata for each antenna of each gateway that received this message.",
@@ -31966,7 +32046,7 @@
},
{
"name": "settings",
- "description": "Settings for the transmission.",
+ "description": "Transmission settings used by the end device.",
"label": "",
"type": "TxSettings",
"longType": "TxSettings",
@@ -32022,7 +32102,7 @@
},
{
"name": "confirmed",
- "description": "",
+ "description": "Indicates whether the end device used confirmed data uplink.",
"label": "",
"type": "bool",
"longType": "bool",
@@ -32034,7 +32114,7 @@
},
{
"name": "consumed_airtime",
- "description": "Consumed airtime for the transmission of the uplink message. Calculated by Network Server using the RawPayload size and the transmission settings.",
+ "description": "Consumed airtime for the transmission of the uplink message. Calculated by Network Server using the raw payload size and the transmission settings.",
"label": "",
"type": "Duration",
"longType": "google.protobuf.Duration",
@@ -32118,6 +32198,272 @@
}
]
},
+ {
+ "name": "ApplicationUplinkNormalized",
+ "longName": "ApplicationUplinkNormalized",
+ "fullName": "ttn.lorawan.v3.ApplicationUplinkNormalized",
+ "description": "",
+ "hasExtensions": false,
+ "hasFields": true,
+ "hasOneofs": false,
+ "extensions": [],
+ "fields": [
+ {
+ "name": "session_key_id",
+ "description": "Join Server issued identifier for the session keys used by this uplink.",
+ "label": "",
+ "type": "bytes",
+ "longType": "bytes",
+ "fullType": "bytes",
+ "ismap": false,
+ "isoneof": false,
+ "oneofdecl": "",
+ "defaultValue": "",
+ "options": {
+ "validate.rules": [
+ {
+ "name": "bytes.max_len",
+ "value": 2048
+ }
+ ]
+ }
+ },
+ {
+ "name": "f_port",
+ "description": "LoRaWAN FPort of the uplink message.",
+ "label": "",
+ "type": "uint32",
+ "longType": "uint32",
+ "fullType": "uint32",
+ "ismap": false,
+ "isoneof": false,
+ "oneofdecl": "",
+ "defaultValue": "",
+ "options": {
+ "validate.rules": [
+ {
+ "name": "uint32.lte",
+ "value": 255
+ },
+ {
+ "name": "uint32.gte",
+ "value": 1
+ },
+ {
+ "name": "uint32.not_in",
+ "value": [
+ 224
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "name": "f_cnt",
+ "description": "LoRaWAN FCntUp of the uplink message.",
+ "label": "",
+ "type": "uint32",
+ "longType": "uint32",
+ "fullType": "uint32",
+ "ismap": false,
+ "isoneof": false,
+ "oneofdecl": "",
+ "defaultValue": ""
+ },
+ {
+ "name": "frm_payload",
+ "description": "The frame payload of the uplink message.\nThis field is always decrypted with AppSKey.",
+ "label": "",
+ "type": "bytes",
+ "longType": "bytes",
+ "fullType": "bytes",
+ "ismap": false,
+ "isoneof": false,
+ "oneofdecl": "",
+ "defaultValue": ""
+ },
+ {
+ "name": "normalized_payload",
+ "description": "The normalized frame payload of the uplink message.\nThis field is set for each item in normalized_payload in the corresponding ApplicationUplink message.",
+ "label": "",
+ "type": "Struct",
+ "longType": "google.protobuf.Struct",
+ "fullType": "google.protobuf.Struct",
+ "ismap": false,
+ "isoneof": false,
+ "oneofdecl": "",
+ "defaultValue": "",
+ "options": {
+ "validate.rules": [
+ {
+ "name": "message.required",
+ "value": true
+ }
+ ]
+ }
+ },
+ {
+ "name": "normalized_payload_warnings",
+ "description": "This field is set to normalized_payload_warnings in the corresponding ApplicationUplink message.",
+ "label": "repeated",
+ "type": "string",
+ "longType": "string",
+ "fullType": "string",
+ "ismap": false,
+ "isoneof": false,
+ "oneofdecl": "",
+ "defaultValue": ""
+ },
+ {
+ "name": "rx_metadata",
+ "description": "A list of metadata for each antenna of each gateway that received this message.",
+ "label": "repeated",
+ "type": "RxMetadata",
+ "longType": "RxMetadata",
+ "fullType": "ttn.lorawan.v3.RxMetadata",
+ "ismap": false,
+ "isoneof": false,
+ "oneofdecl": "",
+ "defaultValue": ""
+ },
+ {
+ "name": "settings",
+ "description": "Transmission settings used by the end device.",
+ "label": "",
+ "type": "TxSettings",
+ "longType": "TxSettings",
+ "fullType": "ttn.lorawan.v3.TxSettings",
+ "ismap": false,
+ "isoneof": false,
+ "oneofdecl": "",
+ "defaultValue": "",
+ "options": {
+ "validate.rules": [
+ {
+ "name": "message.required",
+ "value": true
+ }
+ ]
+ }
+ },
+ {
+ "name": "received_at",
+ "description": "Server time when the Network Server received the message.",
+ "label": "",
+ "type": "Timestamp",
+ "longType": "google.protobuf.Timestamp",
+ "fullType": "google.protobuf.Timestamp",
+ "ismap": false,
+ "isoneof": false,
+ "oneofdecl": "",
+ "defaultValue": "",
+ "options": {
+ "validate.rules": [
+ {
+ "name": "timestamp.required",
+ "value": true
+ }
+ ]
+ }
+ },
+ {
+ "name": "confirmed",
+ "description": "Indicates whether the end device used confirmed data uplink.",
+ "label": "",
+ "type": "bool",
+ "longType": "bool",
+ "fullType": "bool",
+ "ismap": false,
+ "isoneof": false,
+ "oneofdecl": "",
+ "defaultValue": ""
+ },
+ {
+ "name": "consumed_airtime",
+ "description": "Consumed airtime for the transmission of the uplink message. Calculated by Network Server using the raw payload size and the transmission settings.",
+ "label": "",
+ "type": "Duration",
+ "longType": "google.protobuf.Duration",
+ "fullType": "google.protobuf.Duration",
+ "ismap": false,
+ "isoneof": false,
+ "oneofdecl": "",
+ "defaultValue": ""
+ },
+ {
+ "name": "locations",
+ "description": "End device location metadata, set by the Application Server while handling the message.",
+ "label": "repeated",
+ "type": "LocationsEntry",
+ "longType": "ApplicationUplinkNormalized.LocationsEntry",
+ "fullType": "ttn.lorawan.v3.ApplicationUplinkNormalized.LocationsEntry",
+ "ismap": true,
+ "isoneof": false,
+ "oneofdecl": "",
+ "defaultValue": ""
+ },
+ {
+ "name": "version_ids",
+ "description": "End device version identifiers, set by the Application Server while handling the message.",
+ "label": "",
+ "type": "EndDeviceVersionIdentifiers",
+ "longType": "EndDeviceVersionIdentifiers",
+ "fullType": "ttn.lorawan.v3.EndDeviceVersionIdentifiers",
+ "ismap": false,
+ "isoneof": false,
+ "oneofdecl": "",
+ "defaultValue": ""
+ },
+ {
+ "name": "network_ids",
+ "description": "Network identifiers, set by the Network Server that handles the message.",
+ "label": "",
+ "type": "NetworkIdentifiers",
+ "longType": "NetworkIdentifiers",
+ "fullType": "ttn.lorawan.v3.NetworkIdentifiers",
+ "ismap": false,
+ "isoneof": false,
+ "oneofdecl": "",
+ "defaultValue": ""
+ }
+ ]
+ },
+ {
+ "name": "LocationsEntry",
+ "longName": "ApplicationUplinkNormalized.LocationsEntry",
+ "fullName": "ttn.lorawan.v3.ApplicationUplinkNormalized.LocationsEntry",
+ "description": "",
+ "hasExtensions": false,
+ "hasFields": true,
+ "hasOneofs": false,
+ "extensions": [],
+ "fields": [
+ {
+ "name": "key",
+ "description": "",
+ "label": "",
+ "type": "string",
+ "longType": "string",
+ "fullType": "string",
+ "ismap": false,
+ "isoneof": false,
+ "oneofdecl": "",
+ "defaultValue": ""
+ },
+ {
+ "name": "value",
+ "description": "",
+ "label": "",
+ "type": "Location",
+ "longType": "Location",
+ "fullType": "ttn.lorawan.v3.Location",
+ "ismap": false,
+ "isoneof": false,
+ "oneofdecl": "",
+ "defaultValue": ""
+ }
+ ]
+ },
{
"name": "DownlinkMessage",
"longName": "DownlinkMessage",
diff --git a/tools/go.mod b/tools/go.mod
index 7f50e6c012..aed96fcc3b 100644
--- a/tools/go.mod
+++ b/tools/go.mod
@@ -94,8 +94,8 @@ require (
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/disintegration/imaging v1.6.2 // indirect
- github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect
- github.com/dop251/goja v0.0.0-20220214123719-b09a6bfa842f // indirect
+ github.com/dlclark/regexp2 v1.7.0 // indirect
+ github.com/dop251/goja v0.0.0-20220815083517-0c74f9139fd6 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/eclipse/paho.mqtt.golang v1.3.5 // indirect
github.com/envoyproxy/protoc-gen-validate v0.6.3 // indirect
diff --git a/tools/go.sum b/tools/go.sum
index 6603410892..cbb6393308 100644
--- a/tools/go.sum
+++ b/tools/go.sum
@@ -199,11 +199,14 @@ github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
-github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E=
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
-github.com/dop251/goja v0.0.0-20220214123719-b09a6bfa842f h1:ztRywKO1rqqS8li0TDcnwi9AGsqAH0ky9NaND69/Ccc=
-github.com/dop251/goja v0.0.0-20220214123719-b09a6bfa842f/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
+github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
+github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
+github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
+github.com/dop251/goja v0.0.0-20220815083517-0c74f9139fd6 h1:xHdUVG+c8SWJnct16Z3QJOVlaYo3OwoJyamo6kR6OL0=
+github.com/dop251/goja v0.0.0-20220815083517-0c74f9139fd6/go.mod h1:yRkwfj0CBpOGre+TwBsqPV0IH0Pk73e4PXJOeNDboGs=
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
+github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eclipse/paho.mqtt.golang v1.3.5 h1:sWtmgNxYM9P2sP+xEItMozsR3w0cqZFlqnNN1bdl41Y=
@@ -534,6 +537,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
@@ -702,6 +706,7 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=