diff --git a/Cargo.lock b/Cargo.lock index b1b724b..6f0520a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3200,12 +3200,14 @@ name = "tora" version = "0.1.0" dependencies = [ "anyhow", + "chrono", "clap", "colored", "hyper-util", "nix", "serde_json", "tokio", + "tokio-stream", "tonic", "tonic-health", "tora-proto", diff --git a/crates/proto/src/generated/torrent/torrent.rs b/crates/proto/src/generated/torrent/torrent.rs index a8dbfb9..75f2971 100644 --- a/crates/proto/src/generated/torrent/torrent.rs +++ b/crates/proto/src/generated/torrent/torrent.rs @@ -159,6 +159,136 @@ impl ::prost::Name for ResumeResponse { } } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct NotificationsRequest { + /// If non-empty, only events for these torrent ids (or id prefixes, as + /// accepted by `Status`/`Remove`) are delivered. + #[prost(string, repeated, tag = "1")] + pub ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// If non-empty, only these event kinds are delivered. An empty list means + /// all kinds. + #[prost(enumeration = "NotificationKind", repeated, tag = "2")] + pub kinds: ::prost::alloc::vec::Vec, +} +impl ::prost::Name for NotificationsRequest { + const NAME: &'static str = "NotificationsRequest"; + const PACKAGE: &'static str = "torrent"; + fn full_name() -> ::prost::alloc::string::String { + "torrent.NotificationsRequest".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/torrent.NotificationsRequest".into() + } +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct Notification { + /// Wall-clock time the daemon observed the change, in milliseconds since the + /// Unix epoch. + #[prost(uint64, tag = "1")] + pub observed_at_unix_millis: u64, + /// What changed. Use this to dispatch; use the `oneof detail` for + /// kind-specific payload. + #[prost(enumeration = "NotificationKind", tag = "2")] + pub kind: i32, + /// Full snapshot of the torrent *after* the change. Always populated so + /// consumers don't need to fan out to `Status` for context. + #[prost(message, optional, tag = "3")] + pub torrent: ::core::option::Option, + #[prost(oneof = "notification::Detail", tags = "10, 11, 12, 13")] + pub detail: ::core::option::Option, +} +/// Nested message and enum types in `Notification`. +pub mod notification { + #[derive(Clone, PartialEq, Eq, Hash, ::prost::Oneof)] + pub enum Detail { + #[prost(message, tag = "10")] + StateChanged(super::StateChanged), + #[prost(message, tag = "11")] + Progress(super::ProgressMark), + #[prost(message, tag = "12")] + Added(super::AddedInfo), + #[prost(message, tag = "13")] + Removed(super::RemovedInfo), + } +} +impl ::prost::Name for Notification { + const NAME: &'static str = "Notification"; + const PACKAGE: &'static str = "torrent"; + fn full_name() -> ::prost::alloc::string::String { + "torrent.Notification".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/torrent.Notification".into() + } +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct StateChanged { + #[prost(enumeration = "State", tag = "1")] + pub previous: i32, + #[prost(enumeration = "State", tag = "2")] + pub current: i32, +} +impl ::prost::Name for StateChanged { + const NAME: &'static str = "StateChanged"; + const PACKAGE: &'static str = "torrent"; + fn full_name() -> ::prost::alloc::string::String { + "torrent.StateChanged".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/torrent.StateChanged".into() + } +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ProgressMark { + /// Current integer percentage 0..=100. + #[prost(uint32, tag = "1")] + pub percent: u32, + /// Bytes downloaded since the previous PROGRESS event for this torrent. + #[prost(uint64, tag = "2")] + pub bytes_delta: u64, +} +impl ::prost::Name for ProgressMark { + const NAME: &'static str = "ProgressMark"; + const PACKAGE: &'static str = "torrent"; + fn full_name() -> ::prost::alloc::string::String { + "torrent.ProgressMark".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/torrent.ProgressMark".into() + } +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct AddedInfo { + /// Source string as provided to `Add` (magnet, URL, or file path). + #[prost(string, tag = "1")] + pub source: ::prost::alloc::string::String, +} +impl ::prost::Name for AddedInfo { + const NAME: &'static str = "AddedInfo"; + const PACKAGE: &'static str = "torrent"; + fn full_name() -> ::prost::alloc::string::String { + "torrent.AddedInfo".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/torrent.AddedInfo".into() + } +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct RemovedInfo { + /// True if `Remove` was called with `delete_files = true`. + #[prost(bool, tag = "1")] + pub files_deleted: bool, +} +impl ::prost::Name for RemovedInfo { + const NAME: &'static str = "RemovedInfo"; + const PACKAGE: &'static str = "torrent"; + fn full_name() -> ::prost::alloc::string::String { + "torrent.RemovedInfo".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/torrent.RemovedInfo".into() + } +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct TorrentStatus { #[prost(string, tag = "1")] pub id: ::prost::alloc::string::String, @@ -203,6 +333,47 @@ impl ::prost::Name for TorrentStatus { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] +pub enum NotificationKind { + Unspecified = 0, + /// Emitted once when a torrent is added via `Add`. + TorrentAdded = 1, + /// Emitted once when a torrent is removed via `Remove`. + TorrentRemoved = 2, + /// Emitted on any `State` transition, including PAUSED <-> DOWNLOADING, + /// DOWNLOADING <-> STALE, * -> FINISHED, * -> ERROR. + StateChanged = 3, + /// Emitted when the integer download percentage changes (rate-limited). + /// No event is sent if the percentage did not advance since the last tick. + Progress = 4, +} +impl NotificationKind { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Unspecified => "NOTIFICATION_KIND_UNSPECIFIED", + Self::TorrentAdded => "TORRENT_ADDED", + Self::TorrentRemoved => "TORRENT_REMOVED", + Self::StateChanged => "STATE_CHANGED", + Self::Progress => "PROGRESS", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "NOTIFICATION_KIND_UNSPECIFIED" => Some(Self::Unspecified), + "TORRENT_ADDED" => Some(Self::TorrentAdded), + "TORRENT_REMOVED" => Some(Self::TorrentRemoved), + "STATE_CHANGED" => Some(Self::StateChanged), + "PROGRESS" => Some(Self::Progress), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] pub enum State { Unspecified = 0, Pending = 1, @@ -210,6 +381,12 @@ pub enum State { Paused = 3, Finished = 4, Error = 5, + /// Live per librqbit, but the daemon has detected a stall: no byte progress + /// for several ticks AND zero connected peers. The torrent is still + /// considered live by the underlying engine; this state is an application- + /// level overlay. Recovers to DOWNLOADING automatically when bytes advance + /// or a peer connects. + Stale = 6, } impl State { /// String value of the enum field names used in the ProtoBuf definition. @@ -224,6 +401,7 @@ impl State { Self::Paused => "PAUSED", Self::Finished => "FINISHED", Self::Error => "ERROR", + Self::Stale => "STALE", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -235,13 +413,14 @@ impl State { "PAUSED" => Some(Self::Paused), "FINISHED" => Some(Self::Finished), "ERROR" => Some(Self::Error), + "STALE" => Some(Self::Stale), _ => None, } } } /// Encoded file descriptor set for the `torrent` package pub const FILE_DESCRIPTOR_SET: &[u8] = &[ - 0x0a, 0x90, 0x1f, 0x0a, 0x0d, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x0a, 0xe0, 0x3e, 0x0a, 0x0d, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x22, 0x43, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x61, 0x67, 0x6e, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x61, 0x67, 0x6e, 0x65, @@ -267,229 +446,482 @@ pub const FILE_DESCRIPTOR_SET: &[u8] = &[ 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x10, 0x0a, 0x0e, 0x52, - 0x65, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xec, 0x03, - 0x0a, 0x0d, 0x54, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, - 0x1b, 0x0a, 0x09, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x69, 0x6e, 0x66, 0x6f, 0x48, 0x61, 0x73, 0x68, 0x12, 0x12, 0x0a, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, - 0x75, 0x74, 0x70, 0x75, 0x74, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, - 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x6f, - 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, - 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x65, - 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x12, 0x2c, 0x0a, 0x12, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x73, 0x70, 0x65, - 0x65, 0x64, 0x5f, 0x62, 0x70, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x64, 0x6f, - 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x70, 0x65, 0x65, 0x64, 0x42, 0x70, 0x73, 0x12, 0x1f, - 0x0a, 0x0b, 0x65, 0x74, 0x61, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x0b, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x0a, 0x65, 0x74, 0x61, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, - 0x25, 0x0a, 0x0e, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, - 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, - 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, - 0x5f, 0x73, 0x70, 0x65, 0x65, 0x64, 0x5f, 0x62, 0x70, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0e, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x70, 0x65, 0x65, 0x64, 0x42, 0x70, 0x73, - 0x12, 0x14, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x65, 0x65, 0x64, 0x73, 0x18, - 0x0f, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x73, 0x65, 0x65, 0x64, 0x73, 0x2a, 0x61, 0x0a, 0x05, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, - 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, - 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x44, 0x4f, 0x57, - 0x4e, 0x4c, 0x4f, 0x41, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x41, - 0x55, 0x53, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x46, 0x49, 0x4e, 0x49, 0x53, 0x48, - 0x45, 0x44, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x32, - 0xd9, 0x02, 0x0a, 0x08, 0x54, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x30, 0x0a, 0x03, - 0x41, 0x64, 0x64, 0x12, 0x13, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x64, - 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, - 0x6e, 0x74, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, - 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, - 0x6e, 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x16, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x54, 0x6f, 0x72, 0x72, 0x65, - 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x33, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, - 0x12, 0x14, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, - 0x06, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x12, 0x16, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, - 0x74, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x17, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x50, 0x61, 0x75, 0x73, - 0x65, 0x12, 0x15, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x75, 0x73, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, - 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x75, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x39, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x12, 0x16, 0x2e, 0x74, 0x6f, 0x72, - 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, - 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4a, 0xad, 0x14, 0x0a, 0x06, - 0x12, 0x04, 0x00, 0x00, 0x53, 0x01, 0x0a, 0x08, 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x00, 0x00, 0x12, - 0x0a, 0x08, 0x0a, 0x01, 0x02, 0x12, 0x03, 0x02, 0x00, 0x10, 0x0a, 0x0a, 0x0a, 0x02, 0x06, 0x00, - 0x12, 0x04, 0x04, 0x00, 0x11, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x06, 0x00, 0x01, 0x12, 0x03, 0x04, - 0x08, 0x10, 0x0a, 0x5a, 0x0a, 0x04, 0x06, 0x00, 0x02, 0x00, 0x12, 0x03, 0x06, 0x02, 0x2c, 0x1a, - 0x4d, 0x20, 0x41, 0x64, 0x64, 0x20, 0x61, 0x20, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, - 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x61, 0x20, 0x6d, 0x61, 0x67, 0x6e, 0x65, 0x74, 0x20, 0x6c, 0x69, - 0x6e, 0x6b, 0x20, 0x6f, 0x72, 0x20, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x66, - 0x69, 0x6c, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x20, 0x64, 0x6f, - 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x74, 0x2e, 0x0a, 0x0a, 0x0c, - 0x0a, 0x05, 0x06, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x06, 0x06, 0x09, 0x0a, 0x0c, 0x0a, 0x05, - 0x06, 0x00, 0x02, 0x00, 0x02, 0x12, 0x03, 0x06, 0x0a, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, - 0x02, 0x00, 0x03, 0x12, 0x03, 0x06, 0x1f, 0x2a, 0x0a, 0x3a, 0x0a, 0x04, 0x06, 0x00, 0x02, 0x01, - 0x12, 0x03, 0x08, 0x02, 0x34, 0x1a, 0x2d, 0x20, 0x47, 0x65, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x20, 0x6f, - 0x66, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x74, 0x6f, 0x72, 0x72, 0x65, - 0x6e, 0x74, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x08, - 0x06, 0x0c, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x01, 0x02, 0x12, 0x03, 0x08, 0x0d, 0x1a, - 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, 0x08, 0x25, 0x32, 0x0a, 0x35, - 0x0a, 0x04, 0x06, 0x00, 0x02, 0x02, 0x12, 0x03, 0x0a, 0x02, 0x2f, 0x1a, 0x28, 0x20, 0x4c, 0x69, - 0x73, 0x74, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x20, - 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x61, 0x65, - 0x6d, 0x6f, 0x6e, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, - 0x0a, 0x06, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x02, 0x02, 0x12, 0x03, 0x0a, 0x0b, - 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x02, 0x03, 0x12, 0x03, 0x0a, 0x21, 0x2d, 0x0a, - 0x2e, 0x0a, 0x04, 0x06, 0x00, 0x02, 0x03, 0x12, 0x03, 0x0c, 0x02, 0x35, 0x1a, 0x21, 0x20, 0x52, - 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x20, 0x61, 0x20, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, - 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x0a, 0x0a, - 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x03, 0x01, 0x12, 0x03, 0x0c, 0x06, 0x0c, 0x0a, 0x0c, 0x0a, - 0x05, 0x06, 0x00, 0x02, 0x03, 0x02, 0x12, 0x03, 0x0c, 0x0d, 0x1a, 0x0a, 0x0c, 0x0a, 0x05, 0x06, - 0x00, 0x02, 0x03, 0x03, 0x12, 0x03, 0x0c, 0x25, 0x33, 0x0a, 0x2b, 0x0a, 0x04, 0x06, 0x00, 0x02, - 0x04, 0x12, 0x03, 0x0e, 0x02, 0x32, 0x1a, 0x1e, 0x20, 0x50, 0x61, 0x75, 0x73, 0x65, 0x20, 0x61, - 0x20, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x6f, 0x72, - 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x04, 0x01, 0x12, - 0x03, 0x0e, 0x06, 0x0b, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x04, 0x02, 0x12, 0x03, 0x0e, - 0x0c, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x04, 0x03, 0x12, 0x03, 0x0e, 0x23, 0x30, - 0x0a, 0x27, 0x0a, 0x04, 0x06, 0x00, 0x02, 0x05, 0x12, 0x03, 0x10, 0x02, 0x35, 0x1a, 0x1a, 0x20, - 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x20, 0x61, 0x20, 0x70, 0x61, 0x75, 0x73, 0x65, 0x64, 0x20, - 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, - 0x05, 0x01, 0x12, 0x03, 0x10, 0x06, 0x0c, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x05, 0x02, - 0x12, 0x03, 0x10, 0x0d, 0x1a, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x05, 0x03, 0x12, 0x03, - 0x10, 0x25, 0x33, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x04, 0x13, 0x00, 0x17, 0x01, 0x0a, - 0x0a, 0x0a, 0x03, 0x04, 0x00, 0x01, 0x12, 0x03, 0x13, 0x08, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, - 0x00, 0x02, 0x00, 0x05, 0x12, 0x03, 0x14, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, - 0x00, 0x12, 0x03, 0x14, 0x02, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, - 0x03, 0x14, 0x09, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x14, - 0x12, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x05, 0x12, 0x03, 0x16, 0x02, 0x08, - 0x0a, 0x53, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x01, 0x12, 0x03, 0x16, 0x02, 0x18, 0x1a, 0x46, 0x20, - 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x20, - 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, - 0x64, 0x65, 0x2e, 0x20, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x20, 0x6d, 0x65, 0x61, 0x6e, 0x73, 0x20, - 0x75, 0x73, 0x65, 0x20, 0x74, 0x6f, 0x72, 0x61, 0x64, 0x27, 0x73, 0x20, 0x64, 0x65, 0x66, 0x61, - 0x75, 0x6c, 0x74, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, - 0x16, 0x09, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, 0x16, 0x16, - 0x17, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x01, 0x12, 0x04, 0x19, 0x00, 0x1b, 0x01, 0x0a, 0x0a, 0x0a, - 0x03, 0x04, 0x01, 0x01, 0x12, 0x03, 0x19, 0x08, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, - 0x00, 0x05, 0x12, 0x03, 0x1a, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x00, 0x12, - 0x03, 0x1a, 0x02, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x1a, - 0x09, 0x0b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x1a, 0x0e, 0x0f, - 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x02, 0x12, 0x04, 0x1d, 0x00, 0x1f, 0x01, 0x0a, 0x0a, 0x0a, 0x03, - 0x04, 0x02, 0x01, 0x12, 0x03, 0x1d, 0x08, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, - 0x05, 0x12, 0x03, 0x1e, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x00, 0x12, 0x03, - 0x1e, 0x02, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x01, 0x12, 0x03, 0x1e, 0x09, - 0x0b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x03, 0x12, 0x03, 0x1e, 0x0e, 0x0f, 0x0a, - 0x09, 0x0a, 0x02, 0x04, 0x03, 0x12, 0x03, 0x21, 0x00, 0x16, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x03, - 0x01, 0x12, 0x03, 0x21, 0x08, 0x13, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x04, 0x12, 0x04, 0x23, 0x00, - 0x25, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x04, 0x01, 0x12, 0x03, 0x23, 0x08, 0x14, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x04, 0x02, 0x00, 0x04, 0x12, 0x03, 0x24, 0x02, 0x0a, 0x0a, 0x0b, 0x0a, 0x04, - 0x04, 0x04, 0x02, 0x00, 0x12, 0x03, 0x24, 0x02, 0x26, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, - 0x00, 0x06, 0x12, 0x03, 0x24, 0x0b, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x00, 0x01, - 0x12, 0x03, 0x24, 0x19, 0x21, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x00, 0x03, 0x12, 0x03, - 0x24, 0x24, 0x25, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x05, 0x12, 0x04, 0x27, 0x00, 0x2a, 0x01, 0x0a, - 0x0a, 0x0a, 0x03, 0x04, 0x05, 0x01, 0x12, 0x03, 0x27, 0x08, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, - 0x05, 0x02, 0x00, 0x05, 0x12, 0x03, 0x28, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x05, 0x02, - 0x00, 0x12, 0x03, 0x28, 0x02, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x00, 0x01, 0x12, - 0x03, 0x28, 0x09, 0x0b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x00, 0x03, 0x12, 0x03, 0x28, - 0x0e, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x01, 0x05, 0x12, 0x03, 0x29, 0x02, 0x06, - 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x05, 0x02, 0x01, 0x12, 0x03, 0x29, 0x02, 0x18, 0x0a, 0x0c, 0x0a, - 0x05, 0x04, 0x05, 0x02, 0x01, 0x01, 0x12, 0x03, 0x29, 0x07, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, - 0x05, 0x02, 0x01, 0x03, 0x12, 0x03, 0x29, 0x16, 0x17, 0x0a, 0x09, 0x0a, 0x02, 0x04, 0x06, 0x12, - 0x03, 0x2c, 0x00, 0x19, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x06, 0x01, 0x12, 0x03, 0x2c, 0x08, 0x16, - 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x07, 0x12, 0x04, 0x2e, 0x00, 0x30, 0x01, 0x0a, 0x0a, 0x0a, 0x03, - 0x04, 0x07, 0x01, 0x12, 0x03, 0x2e, 0x08, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x00, - 0x05, 0x12, 0x03, 0x2f, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x07, 0x02, 0x00, 0x12, 0x03, - 0x2f, 0x02, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x00, 0x01, 0x12, 0x03, 0x2f, 0x09, - 0x0b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x00, 0x03, 0x12, 0x03, 0x2f, 0x0e, 0x0f, 0x0a, - 0x09, 0x0a, 0x02, 0x04, 0x08, 0x12, 0x03, 0x32, 0x00, 0x18, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x08, - 0x01, 0x12, 0x03, 0x32, 0x08, 0x15, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x09, 0x12, 0x04, 0x34, 0x00, - 0x36, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x09, 0x01, 0x12, 0x03, 0x34, 0x08, 0x15, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x09, 0x02, 0x00, 0x05, 0x12, 0x03, 0x35, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, - 0x04, 0x09, 0x02, 0x00, 0x12, 0x03, 0x35, 0x02, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x09, 0x02, - 0x00, 0x01, 0x12, 0x03, 0x35, 0x09, 0x0b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x00, 0x03, - 0x12, 0x03, 0x35, 0x0e, 0x0f, 0x0a, 0x09, 0x0a, 0x02, 0x04, 0x0a, 0x12, 0x03, 0x38, 0x00, 0x19, - 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x0a, 0x01, 0x12, 0x03, 0x38, 0x08, 0x16, 0x0a, 0x0a, 0x0a, 0x02, - 0x05, 0x00, 0x12, 0x04, 0x3a, 0x00, 0x41, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x05, 0x00, 0x01, 0x12, - 0x03, 0x3a, 0x05, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x3b, - 0x02, 0x13, 0x0a, 0x0b, 0x0a, 0x04, 0x05, 0x00, 0x02, 0x00, 0x12, 0x03, 0x3b, 0x02, 0x18, 0x0a, - 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x00, 0x02, 0x12, 0x03, 0x3b, 0x16, 0x17, 0x0a, 0x0c, 0x0a, - 0x05, 0x05, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x3c, 0x02, 0x09, 0x0a, 0x0b, 0x0a, 0x04, 0x05, - 0x00, 0x02, 0x01, 0x12, 0x03, 0x3c, 0x02, 0x0e, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x01, - 0x02, 0x12, 0x03, 0x3c, 0x0c, 0x0d, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x02, 0x01, 0x12, - 0x03, 0x3d, 0x02, 0x0d, 0x0a, 0x0b, 0x0a, 0x04, 0x05, 0x00, 0x02, 0x02, 0x12, 0x03, 0x3d, 0x02, - 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x02, 0x02, 0x12, 0x03, 0x3d, 0x10, 0x11, 0x0a, - 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x03, 0x01, 0x12, 0x03, 0x3e, 0x02, 0x08, 0x0a, 0x0b, 0x0a, - 0x04, 0x05, 0x00, 0x02, 0x03, 0x12, 0x03, 0x3e, 0x02, 0x0d, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, - 0x02, 0x03, 0x02, 0x12, 0x03, 0x3e, 0x0b, 0x0c, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x04, - 0x01, 0x12, 0x03, 0x3f, 0x02, 0x0a, 0x0a, 0x0b, 0x0a, 0x04, 0x05, 0x00, 0x02, 0x04, 0x12, 0x03, - 0x3f, 0x02, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x04, 0x02, 0x12, 0x03, 0x3f, 0x0d, - 0x0e, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x05, 0x01, 0x12, 0x03, 0x40, 0x02, 0x07, 0x0a, - 0x0b, 0x0a, 0x04, 0x05, 0x00, 0x02, 0x05, 0x12, 0x03, 0x40, 0x02, 0x0c, 0x0a, 0x0c, 0x0a, 0x05, - 0x05, 0x00, 0x02, 0x05, 0x02, 0x12, 0x03, 0x40, 0x0a, 0x0b, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x0b, - 0x12, 0x04, 0x43, 0x00, 0x53, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x0b, 0x01, 0x12, 0x03, 0x43, - 0x08, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x00, 0x05, 0x12, 0x03, 0x44, 0x02, 0x08, - 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0b, 0x02, 0x00, 0x12, 0x03, 0x44, 0x02, 0x10, 0x0a, 0x0c, 0x0a, - 0x05, 0x04, 0x0b, 0x02, 0x00, 0x01, 0x12, 0x03, 0x44, 0x09, 0x0b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, - 0x0b, 0x02, 0x00, 0x03, 0x12, 0x03, 0x44, 0x0e, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, - 0x01, 0x05, 0x12, 0x03, 0x45, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0b, 0x02, 0x01, 0x12, - 0x03, 0x45, 0x02, 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x01, 0x01, 0x12, 0x03, 0x45, - 0x09, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x01, 0x03, 0x12, 0x03, 0x45, 0x15, 0x16, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x02, 0x05, 0x12, 0x03, 0x46, 0x02, 0x08, 0x0a, 0x0b, - 0x0a, 0x04, 0x04, 0x0b, 0x02, 0x02, 0x12, 0x03, 0x46, 0x02, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, - 0x0b, 0x02, 0x02, 0x01, 0x12, 0x03, 0x46, 0x09, 0x0d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, - 0x02, 0x03, 0x12, 0x03, 0x46, 0x10, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x03, 0x05, - 0x12, 0x03, 0x47, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0b, 0x02, 0x03, 0x12, 0x03, 0x47, - 0x02, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x03, 0x01, 0x12, 0x03, 0x47, 0x09, 0x0f, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x03, 0x03, 0x12, 0x03, 0x47, 0x12, 0x13, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x04, 0x05, 0x12, 0x03, 0x48, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, - 0x04, 0x0b, 0x02, 0x04, 0x12, 0x03, 0x48, 0x02, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, - 0x04, 0x01, 0x12, 0x03, 0x48, 0x09, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x04, 0x03, - 0x12, 0x03, 0x48, 0x17, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x05, 0x05, 0x12, 0x03, - 0x49, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0b, 0x02, 0x05, 0x12, 0x03, 0x49, 0x02, 0x19, - 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x05, 0x01, 0x12, 0x03, 0x49, 0x09, 0x14, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x05, 0x03, 0x12, 0x03, 0x49, 0x17, 0x18, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x0b, 0x02, 0x06, 0x05, 0x12, 0x03, 0x4a, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0b, - 0x02, 0x06, 0x12, 0x03, 0x4a, 0x02, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x06, 0x01, - 0x12, 0x03, 0x4a, 0x09, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x06, 0x03, 0x12, 0x03, - 0x4a, 0x1c, 0x1d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x07, 0x06, 0x12, 0x03, 0x4b, 0x02, - 0x07, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0b, 0x02, 0x07, 0x12, 0x03, 0x4b, 0x02, 0x12, 0x0a, 0x0c, - 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x07, 0x01, 0x12, 0x03, 0x4b, 0x08, 0x0d, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x0b, 0x02, 0x07, 0x03, 0x12, 0x03, 0x4b, 0x10, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, - 0x02, 0x08, 0x05, 0x12, 0x03, 0x4c, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0b, 0x02, 0x08, - 0x12, 0x03, 0x4c, 0x02, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x08, 0x01, 0x12, 0x03, - 0x4c, 0x09, 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x08, 0x03, 0x12, 0x03, 0x4c, 0x19, - 0x1a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x09, 0x05, 0x12, 0x03, 0x4d, 0x02, 0x08, 0x0a, - 0x0b, 0x0a, 0x04, 0x04, 0x0b, 0x02, 0x09, 0x12, 0x03, 0x4d, 0x02, 0x21, 0x0a, 0x0c, 0x0a, 0x05, - 0x04, 0x0b, 0x02, 0x09, 0x01, 0x12, 0x03, 0x4d, 0x09, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, - 0x02, 0x09, 0x03, 0x12, 0x03, 0x4d, 0x1e, 0x20, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x0a, - 0x05, 0x12, 0x03, 0x4e, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0b, 0x02, 0x0a, 0x12, 0x03, - 0x4e, 0x02, 0x1a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x0a, 0x01, 0x12, 0x03, 0x4e, 0x09, - 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x0a, 0x03, 0x12, 0x03, 0x4e, 0x17, 0x19, 0x0a, - 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x0b, 0x05, 0x12, 0x03, 0x4f, 0x02, 0x08, 0x0a, 0x0b, 0x0a, - 0x04, 0x04, 0x0b, 0x02, 0x0b, 0x12, 0x03, 0x4f, 0x02, 0x1d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, - 0x02, 0x0b, 0x01, 0x12, 0x03, 0x4f, 0x09, 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x0b, - 0x03, 0x12, 0x03, 0x4f, 0x1a, 0x1c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x0c, 0x05, 0x12, - 0x03, 0x50, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0b, 0x02, 0x0c, 0x12, 0x03, 0x50, 0x02, - 0x1f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x0c, 0x01, 0x12, 0x03, 0x50, 0x09, 0x19, 0x0a, - 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x0c, 0x03, 0x12, 0x03, 0x50, 0x1c, 0x1e, 0x0a, 0x0c, 0x0a, - 0x05, 0x04, 0x0b, 0x02, 0x0d, 0x05, 0x12, 0x03, 0x51, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, - 0x0b, 0x02, 0x0d, 0x12, 0x03, 0x51, 0x02, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x0d, - 0x01, 0x12, 0x03, 0x51, 0x09, 0x0e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x0d, 0x03, 0x12, - 0x03, 0x51, 0x11, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x0e, 0x05, 0x12, 0x03, 0x52, - 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0b, 0x02, 0x0e, 0x12, 0x03, 0x52, 0x02, 0x14, 0x0a, - 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x0e, 0x01, 0x12, 0x03, 0x52, 0x09, 0x0e, 0x0a, 0x0c, 0x0a, - 0x05, 0x04, 0x0b, 0x02, 0x0e, 0x03, 0x12, 0x03, 0x52, 0x11, 0x13, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x65, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x59, 0x0a, + 0x14, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x03, 0x69, 0x64, 0x73, 0x12, 0x2f, 0x0a, 0x05, 0x6b, 0x69, 0x6e, 0x64, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, + 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x69, 0x6e, + 0x64, 0x52, 0x05, 0x6b, 0x69, 0x6e, 0x64, 0x73, 0x22, 0x81, 0x03, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x35, 0x0a, 0x17, 0x6f, 0x62, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x6d, 0x69, + 0x6c, 0x6c, 0x69, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x6f, 0x62, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x64, 0x41, 0x74, 0x55, 0x6e, 0x69, 0x78, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x73, + 0x12, 0x2d, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, + 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, + 0x30, 0x0a, 0x07, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x16, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x54, 0x6f, 0x72, 0x72, 0x65, + 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x07, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, + 0x74, 0x12, 0x3c, 0x0a, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, + 0x6e, 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x48, + 0x00, 0x52, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, + 0x33, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x67, + 0x72, 0x65, 0x73, 0x73, 0x4d, 0x61, 0x72, 0x6b, 0x48, 0x00, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x67, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x2a, 0x0a, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x0c, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x64, + 0x64, 0x65, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, + 0x12, 0x30, 0x0a, 0x07, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x6d, 0x6f, + 0x76, 0x65, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x07, 0x72, 0x65, 0x6d, 0x6f, 0x76, + 0x65, 0x64, 0x42, 0x08, 0x0a, 0x06, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x22, 0x64, 0x0a, 0x0c, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x2a, 0x0a, 0x08, + 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, + 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x08, + 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x12, 0x28, 0x0a, 0x07, 0x63, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x74, 0x6f, 0x72, 0x72, + 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x07, 0x63, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x74, 0x22, 0x49, 0x0a, 0x0c, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x4d, 0x61, + 0x72, 0x6b, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x07, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, + 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x22, 0x23, 0x0a, + 0x09, 0x41, 0x64, 0x64, 0x65, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x22, 0x32, 0x0a, 0x0b, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x49, 0x6e, 0x66, + 0x6f, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0xec, 0x03, 0x0a, 0x0d, 0x54, 0x6f, 0x72, 0x72, 0x65, + 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x6e, 0x66, 0x6f, + 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x6e, 0x66, + 0x6f, 0x48, 0x61, 0x73, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x50, 0x61, + 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, + 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x79, + 0x74, 0x65, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x65, + 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x64, + 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x24, + 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, + 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x64, 0x6f, 0x77, + 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x73, 0x70, 0x65, 0x65, 0x64, 0x5f, 0x62, 0x70, 0x73, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x53, + 0x70, 0x65, 0x65, 0x64, 0x42, 0x70, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x74, 0x61, 0x5f, 0x73, + 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x65, 0x74, + 0x61, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x75, 0x70, 0x6c, 0x6f, + 0x61, 0x64, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x0d, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, + 0x28, 0x0a, 0x10, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x73, 0x70, 0x65, 0x65, 0x64, 0x5f, + 0x62, 0x70, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x75, 0x70, 0x6c, 0x6f, 0x61, + 0x64, 0x53, 0x70, 0x65, 0x65, 0x64, 0x42, 0x70, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x65, 0x65, + 0x72, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x12, + 0x14, 0x0a, 0x05, 0x73, 0x65, 0x65, 0x64, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, + 0x73, 0x65, 0x65, 0x64, 0x73, 0x2a, 0x7e, 0x0a, 0x10, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x21, 0x0a, 0x1d, 0x4e, 0x4f, 0x54, + 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, + 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, + 0x54, 0x4f, 0x52, 0x52, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x44, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, + 0x13, 0x0a, 0x0f, 0x54, 0x4f, 0x52, 0x52, 0x45, 0x4e, 0x54, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x56, + 0x45, 0x44, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x43, 0x48, + 0x41, 0x4e, 0x47, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x52, 0x4f, 0x47, 0x52, + 0x45, 0x53, 0x53, 0x10, 0x04, 0x2a, 0x6c, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x15, + 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, + 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, + 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x44, 0x4f, 0x57, 0x4e, 0x4c, 0x4f, 0x41, 0x44, 0x49, 0x4e, + 0x47, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x41, 0x55, 0x53, 0x45, 0x44, 0x10, 0x03, 0x12, + 0x0c, 0x0a, 0x08, 0x46, 0x49, 0x4e, 0x49, 0x53, 0x48, 0x45, 0x44, 0x10, 0x04, 0x12, 0x09, 0x0a, + 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x4c, + 0x45, 0x10, 0x06, 0x32, 0xa2, 0x03, 0x0a, 0x08, 0x54, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x73, + 0x12, 0x30, 0x0a, 0x03, 0x41, 0x64, 0x64, 0x12, 0x13, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, + 0x74, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x74, + 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x38, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x2e, 0x74, + 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x54, + 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x33, 0x0a, 0x04, + 0x4c, 0x69, 0x73, 0x74, 0x12, 0x14, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x74, 0x6f, 0x72, + 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x39, 0x0a, 0x06, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x12, 0x16, 0x2e, 0x74, 0x6f, + 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, + 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x05, + 0x50, 0x61, 0x75, 0x73, 0x65, 0x12, 0x15, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, + 0x50, 0x61, 0x75, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x74, + 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x75, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x12, 0x16, + 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, + 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x47, 0x0a, 0x0d, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x12, 0x1d, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x15, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x01, 0x4a, 0xc0, 0x2c, 0x0a, 0x07, 0x12, 0x05, 0x00, + 0x00, 0x9c, 0x01, 0x01, 0x0a, 0x08, 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x00, 0x00, 0x12, 0x0a, 0x08, + 0x0a, 0x01, 0x02, 0x12, 0x03, 0x02, 0x00, 0x10, 0x0a, 0x0a, 0x0a, 0x02, 0x06, 0x00, 0x12, 0x04, + 0x04, 0x00, 0x15, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x06, 0x00, 0x01, 0x12, 0x03, 0x04, 0x08, 0x10, + 0x0a, 0x5a, 0x0a, 0x04, 0x06, 0x00, 0x02, 0x00, 0x12, 0x03, 0x06, 0x02, 0x2c, 0x1a, 0x4d, 0x20, + 0x41, 0x64, 0x64, 0x20, 0x61, 0x20, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x66, 0x72, + 0x6f, 0x6d, 0x20, 0x61, 0x20, 0x6d, 0x61, 0x67, 0x6e, 0x65, 0x74, 0x20, 0x6c, 0x69, 0x6e, 0x6b, + 0x20, 0x6f, 0x72, 0x20, 0x2e, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x66, 0x69, 0x6c, + 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x20, 0x64, 0x6f, 0x77, 0x6e, + 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x74, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, + 0x06, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x06, 0x06, 0x09, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, + 0x02, 0x00, 0x02, 0x12, 0x03, 0x06, 0x0a, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x00, + 0x03, 0x12, 0x03, 0x06, 0x1f, 0x2a, 0x0a, 0x3a, 0x0a, 0x04, 0x06, 0x00, 0x02, 0x01, 0x12, 0x03, + 0x08, 0x02, 0x34, 0x1a, 0x2d, 0x20, 0x47, 0x65, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x20, 0x6f, 0x66, 0x20, + 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, + 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x08, 0x06, 0x0c, + 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x01, 0x02, 0x12, 0x03, 0x08, 0x0d, 0x1a, 0x0a, 0x0c, + 0x0a, 0x05, 0x06, 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, 0x08, 0x25, 0x32, 0x0a, 0x35, 0x0a, 0x04, + 0x06, 0x00, 0x02, 0x02, 0x12, 0x03, 0x0a, 0x02, 0x2f, 0x1a, 0x28, 0x20, 0x4c, 0x69, 0x73, 0x74, + 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x6b, 0x6e, + 0x6f, 0x77, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x61, 0x65, 0x6d, 0x6f, + 0x6e, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x0a, 0x06, + 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x02, 0x02, 0x12, 0x03, 0x0a, 0x0b, 0x16, 0x0a, + 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x02, 0x03, 0x12, 0x03, 0x0a, 0x21, 0x2d, 0x0a, 0x2e, 0x0a, + 0x04, 0x06, 0x00, 0x02, 0x03, 0x12, 0x03, 0x0c, 0x02, 0x35, 0x1a, 0x21, 0x20, 0x52, 0x65, 0x6d, + 0x6f, 0x76, 0x65, 0x20, 0x61, 0x20, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x66, 0x72, + 0x6f, 0x6d, 0x20, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, + 0x05, 0x06, 0x00, 0x02, 0x03, 0x01, 0x12, 0x03, 0x0c, 0x06, 0x0c, 0x0a, 0x0c, 0x0a, 0x05, 0x06, + 0x00, 0x02, 0x03, 0x02, 0x12, 0x03, 0x0c, 0x0d, 0x1a, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, + 0x03, 0x03, 0x12, 0x03, 0x0c, 0x25, 0x33, 0x0a, 0x2b, 0x0a, 0x04, 0x06, 0x00, 0x02, 0x04, 0x12, + 0x03, 0x0e, 0x02, 0x32, 0x1a, 0x1e, 0x20, 0x50, 0x61, 0x75, 0x73, 0x65, 0x20, 0x61, 0x20, 0x64, + 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x6f, 0x72, 0x72, 0x65, + 0x6e, 0x74, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x04, 0x01, 0x12, 0x03, 0x0e, + 0x06, 0x0b, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x04, 0x02, 0x12, 0x03, 0x0e, 0x0c, 0x18, + 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x04, 0x03, 0x12, 0x03, 0x0e, 0x23, 0x30, 0x0a, 0x27, + 0x0a, 0x04, 0x06, 0x00, 0x02, 0x05, 0x12, 0x03, 0x10, 0x02, 0x35, 0x1a, 0x1a, 0x20, 0x52, 0x65, + 0x73, 0x75, 0x6d, 0x65, 0x20, 0x61, 0x20, 0x70, 0x61, 0x75, 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f, + 0x72, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x05, 0x01, + 0x12, 0x03, 0x10, 0x06, 0x0c, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x05, 0x02, 0x12, 0x03, + 0x10, 0x0d, 0x1a, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x05, 0x03, 0x12, 0x03, 0x10, 0x25, + 0x33, 0x0a, 0xd5, 0x01, 0x0a, 0x04, 0x06, 0x00, 0x02, 0x06, 0x12, 0x03, 0x14, 0x02, 0x48, 0x1a, + 0xc7, 0x01, 0x20, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x20, 0x74, 0x6f, 0x20, + 0x61, 0x20, 0x6c, 0x69, 0x76, 0x65, 0x20, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x6f, 0x66, + 0x20, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x20, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x61, + 0x72, 0x65, 0x20, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x65, 0x64, 0x0a, 0x20, 0x66, 0x72, + 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, + 0x20, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x6f, 0x6e, 0x77, 0x61, 0x72, 0x64, 0x20, 0xe2, 0x80, + 0x94, 0x20, 0x6e, 0x6f, 0x20, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x69, 0x73, 0x20, + 0x72, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x2e, 0x20, 0x4e, 0x65, 0x77, 0x20, 0x73, 0x75, + 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x73, 0x0a, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, + 0x73, 0x65, 0x65, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, + 0x66, 0x69, 0x72, 0x65, 0x20, 0x61, 0x66, 0x74, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x79, 0x20, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, + 0x06, 0x01, 0x12, 0x03, 0x14, 0x06, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x06, 0x02, + 0x12, 0x03, 0x14, 0x14, 0x28, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x06, 0x06, 0x12, 0x03, + 0x14, 0x33, 0x39, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x06, 0x03, 0x12, 0x03, 0x14, 0x3a, + 0x46, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x04, 0x17, 0x00, 0x1b, 0x01, 0x0a, 0x0a, 0x0a, + 0x03, 0x04, 0x00, 0x01, 0x12, 0x03, 0x17, 0x08, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, + 0x00, 0x05, 0x12, 0x03, 0x18, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, + 0x03, 0x18, 0x02, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x18, + 0x09, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x18, 0x12, 0x13, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x05, 0x12, 0x03, 0x1a, 0x02, 0x08, 0x0a, 0x53, + 0x0a, 0x04, 0x04, 0x00, 0x02, 0x01, 0x12, 0x03, 0x1a, 0x02, 0x18, 0x1a, 0x46, 0x20, 0x4f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x20, 0x64, 0x69, + 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, + 0x2e, 0x20, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x20, 0x6d, 0x65, 0x61, 0x6e, 0x73, 0x20, 0x75, 0x73, + 0x65, 0x20, 0x74, 0x6f, 0x72, 0x61, 0x64, 0x27, 0x73, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, + 0x74, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x1a, 0x09, + 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, 0x1a, 0x16, 0x17, 0x0a, + 0x0a, 0x0a, 0x02, 0x04, 0x01, 0x12, 0x04, 0x1d, 0x00, 0x1f, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, + 0x01, 0x01, 0x12, 0x03, 0x1d, 0x08, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x05, + 0x12, 0x03, 0x1e, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x00, 0x12, 0x03, 0x1e, + 0x02, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x1e, 0x09, 0x0b, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x1e, 0x0e, 0x0f, 0x0a, 0x0a, + 0x0a, 0x02, 0x04, 0x02, 0x12, 0x04, 0x21, 0x00, 0x23, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x02, + 0x01, 0x12, 0x03, 0x21, 0x08, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x05, 0x12, + 0x03, 0x22, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x00, 0x12, 0x03, 0x22, 0x02, + 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x01, 0x12, 0x03, 0x22, 0x09, 0x0b, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x03, 0x12, 0x03, 0x22, 0x0e, 0x0f, 0x0a, 0x09, 0x0a, + 0x02, 0x04, 0x03, 0x12, 0x03, 0x25, 0x00, 0x16, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x03, 0x01, 0x12, + 0x03, 0x25, 0x08, 0x13, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x04, 0x12, 0x04, 0x27, 0x00, 0x29, 0x01, + 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x04, 0x01, 0x12, 0x03, 0x27, 0x08, 0x14, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x04, 0x02, 0x00, 0x04, 0x12, 0x03, 0x28, 0x02, 0x0a, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x04, + 0x02, 0x00, 0x12, 0x03, 0x28, 0x02, 0x26, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x00, 0x06, + 0x12, 0x03, 0x28, 0x0b, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x00, 0x01, 0x12, 0x03, + 0x28, 0x19, 0x21, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x00, 0x03, 0x12, 0x03, 0x28, 0x24, + 0x25, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x05, 0x12, 0x04, 0x2b, 0x00, 0x2e, 0x01, 0x0a, 0x0a, 0x0a, + 0x03, 0x04, 0x05, 0x01, 0x12, 0x03, 0x2b, 0x08, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, + 0x00, 0x05, 0x12, 0x03, 0x2c, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x05, 0x02, 0x00, 0x12, + 0x03, 0x2c, 0x02, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x00, 0x01, 0x12, 0x03, 0x2c, + 0x09, 0x0b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x00, 0x03, 0x12, 0x03, 0x2c, 0x0e, 0x0f, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x01, 0x05, 0x12, 0x03, 0x2d, 0x02, 0x06, 0x0a, 0x0b, + 0x0a, 0x04, 0x04, 0x05, 0x02, 0x01, 0x12, 0x03, 0x2d, 0x02, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x05, 0x02, 0x01, 0x01, 0x12, 0x03, 0x2d, 0x07, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, + 0x01, 0x03, 0x12, 0x03, 0x2d, 0x16, 0x17, 0x0a, 0x09, 0x0a, 0x02, 0x04, 0x06, 0x12, 0x03, 0x30, + 0x00, 0x19, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x06, 0x01, 0x12, 0x03, 0x30, 0x08, 0x16, 0x0a, 0x0a, + 0x0a, 0x02, 0x04, 0x07, 0x12, 0x04, 0x32, 0x00, 0x34, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x07, + 0x01, 0x12, 0x03, 0x32, 0x08, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x00, 0x05, 0x12, + 0x03, 0x33, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x07, 0x02, 0x00, 0x12, 0x03, 0x33, 0x02, + 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x00, 0x01, 0x12, 0x03, 0x33, 0x09, 0x0b, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x00, 0x03, 0x12, 0x03, 0x33, 0x0e, 0x0f, 0x0a, 0x09, 0x0a, + 0x02, 0x04, 0x08, 0x12, 0x03, 0x36, 0x00, 0x18, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x08, 0x01, 0x12, + 0x03, 0x36, 0x08, 0x15, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x09, 0x12, 0x04, 0x38, 0x00, 0x3a, 0x01, + 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x09, 0x01, 0x12, 0x03, 0x38, 0x08, 0x15, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x09, 0x02, 0x00, 0x05, 0x12, 0x03, 0x39, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x09, + 0x02, 0x00, 0x12, 0x03, 0x39, 0x02, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x00, 0x01, + 0x12, 0x03, 0x39, 0x09, 0x0b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x00, 0x03, 0x12, 0x03, + 0x39, 0x0e, 0x0f, 0x0a, 0x09, 0x0a, 0x02, 0x04, 0x0a, 0x12, 0x03, 0x3c, 0x00, 0x19, 0x0a, 0x0a, + 0x0a, 0x03, 0x04, 0x0a, 0x01, 0x12, 0x03, 0x3c, 0x08, 0x16, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x0b, + 0x12, 0x04, 0x3e, 0x00, 0x45, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x0b, 0x01, 0x12, 0x03, 0x3e, + 0x08, 0x1c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x00, 0x04, 0x12, 0x03, 0x41, 0x02, 0x0a, + 0x0a, 0x81, 0x01, 0x0a, 0x04, 0x04, 0x0b, 0x02, 0x00, 0x12, 0x03, 0x41, 0x02, 0x1a, 0x1a, 0x74, + 0x20, 0x49, 0x66, 0x20, 0x6e, 0x6f, 0x6e, 0x2d, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2c, 0x20, 0x6f, + 0x6e, 0x6c, 0x79, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, + 0x68, 0x65, 0x73, 0x65, 0x20, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x64, 0x73, + 0x20, 0x28, 0x6f, 0x72, 0x20, 0x69, 0x64, 0x20, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x65, 0x73, + 0x2c, 0x20, 0x61, 0x73, 0x0a, 0x20, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x20, 0x62, + 0x79, 0x20, 0x60, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x60, 0x2f, 0x60, 0x52, 0x65, 0x6d, 0x6f, + 0x76, 0x65, 0x60, 0x29, 0x20, 0x61, 0x72, 0x65, 0x20, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, + 0x65, 0x64, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x00, 0x05, 0x12, 0x03, 0x41, + 0x0b, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x00, 0x01, 0x12, 0x03, 0x41, 0x12, 0x15, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x00, 0x03, 0x12, 0x03, 0x41, 0x18, 0x19, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x01, 0x04, 0x12, 0x03, 0x44, 0x02, 0x0a, 0x0a, 0x62, 0x0a, 0x04, + 0x04, 0x0b, 0x02, 0x01, 0x12, 0x03, 0x44, 0x02, 0x26, 0x1a, 0x55, 0x20, 0x49, 0x66, 0x20, 0x6e, + 0x6f, 0x6e, 0x2d, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2c, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x74, + 0x68, 0x65, 0x73, 0x65, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x20, 0x6b, 0x69, 0x6e, 0x64, 0x73, + 0x20, 0x61, 0x72, 0x65, 0x20, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x65, 0x64, 0x2e, 0x20, + 0x41, 0x6e, 0x20, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x6d, 0x65, + 0x61, 0x6e, 0x73, 0x0a, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x6b, 0x69, 0x6e, 0x64, 0x73, 0x2e, 0x0a, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x01, 0x06, 0x12, 0x03, 0x44, 0x0b, 0x1b, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x01, 0x01, 0x12, 0x03, 0x44, 0x1c, 0x21, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x0b, 0x02, 0x01, 0x03, 0x12, 0x03, 0x44, 0x24, 0x25, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x0c, + 0x12, 0x04, 0x47, 0x00, 0x57, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x0c, 0x01, 0x12, 0x03, 0x47, + 0x08, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x00, 0x05, 0x12, 0x03, 0x4a, 0x02, 0x08, + 0x0a, 0x65, 0x0a, 0x04, 0x04, 0x0c, 0x02, 0x00, 0x12, 0x03, 0x4a, 0x02, 0x25, 0x1a, 0x58, 0x20, + 0x57, 0x61, 0x6c, 0x6c, 0x2d, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x20, 0x6f, 0x62, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2c, 0x20, + 0x69, 0x6e, 0x20, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x20, + 0x73, 0x69, 0x6e, 0x63, 0x65, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x55, 0x6e, 0x69, 0x78, 0x20, + 0x65, 0x70, 0x6f, 0x63, 0x68, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x00, 0x01, + 0x12, 0x03, 0x4a, 0x09, 0x20, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x00, 0x03, 0x12, 0x03, + 0x4a, 0x23, 0x24, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x01, 0x06, 0x12, 0x03, 0x4d, 0x02, + 0x12, 0x0a, 0x65, 0x0a, 0x04, 0x04, 0x0c, 0x02, 0x01, 0x12, 0x03, 0x4d, 0x02, 0x1c, 0x1a, 0x58, + 0x20, 0x57, 0x68, 0x61, 0x74, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x2e, 0x20, 0x55, + 0x73, 0x65, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x64, 0x69, 0x73, 0x70, 0x61, + 0x74, 0x63, 0x68, 0x3b, 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x60, 0x6f, 0x6e, + 0x65, 0x6f, 0x66, 0x20, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x60, 0x20, 0x66, 0x6f, 0x72, 0x0a, + 0x20, 0x6b, 0x69, 0x6e, 0x64, 0x2d, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x20, 0x70, + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x01, + 0x01, 0x12, 0x03, 0x4d, 0x13, 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x01, 0x03, 0x12, + 0x03, 0x4d, 0x1a, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x02, 0x06, 0x12, 0x03, 0x50, + 0x02, 0x0f, 0x0a, 0x8e, 0x01, 0x0a, 0x04, 0x04, 0x0c, 0x02, 0x02, 0x12, 0x03, 0x50, 0x02, 0x1c, + 0x1a, 0x80, 0x01, 0x20, 0x46, 0x75, 0x6c, 0x6c, 0x20, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, + 0x74, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, + 0x20, 0x2a, 0x61, 0x66, 0x74, 0x65, 0x72, 0x2a, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x2e, 0x20, 0x41, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x20, 0x70, 0x6f, 0x70, 0x75, + 0x6c, 0x61, 0x74, 0x65, 0x64, 0x20, 0x73, 0x6f, 0x0a, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, + 0x65, 0x72, 0x73, 0x20, 0x64, 0x6f, 0x6e, 0x27, 0x74, 0x20, 0x6e, 0x65, 0x65, 0x64, 0x20, 0x74, + 0x6f, 0x20, 0x66, 0x61, 0x6e, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x60, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x60, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, + 0x74, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x02, 0x01, 0x12, 0x03, 0x50, 0x10, + 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x02, 0x03, 0x12, 0x03, 0x50, 0x1a, 0x1b, 0x0a, + 0x0c, 0x0a, 0x04, 0x04, 0x0c, 0x08, 0x00, 0x12, 0x04, 0x51, 0x02, 0x56, 0x03, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x0c, 0x08, 0x00, 0x01, 0x12, 0x03, 0x51, 0x08, 0x0e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x0c, 0x02, 0x03, 0x06, 0x12, 0x03, 0x52, 0x04, 0x10, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0c, 0x02, + 0x03, 0x12, 0x03, 0x52, 0x04, 0x24, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x03, 0x01, 0x12, + 0x03, 0x52, 0x11, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x03, 0x03, 0x12, 0x03, 0x52, + 0x21, 0x23, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x04, 0x06, 0x12, 0x03, 0x53, 0x04, 0x10, + 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0c, 0x02, 0x04, 0x12, 0x03, 0x53, 0x04, 0x1f, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x0c, 0x02, 0x04, 0x01, 0x12, 0x03, 0x53, 0x11, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x0c, 0x02, 0x04, 0x03, 0x12, 0x03, 0x53, 0x1c, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0c, 0x02, + 0x05, 0x06, 0x12, 0x03, 0x54, 0x04, 0x0d, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0c, 0x02, 0x05, 0x12, + 0x03, 0x54, 0x04, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x05, 0x01, 0x12, 0x03, 0x54, + 0x0e, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x05, 0x03, 0x12, 0x03, 0x54, 0x16, 0x18, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x06, 0x06, 0x12, 0x03, 0x55, 0x04, 0x0f, 0x0a, 0x0b, + 0x0a, 0x04, 0x04, 0x0c, 0x02, 0x06, 0x12, 0x03, 0x55, 0x04, 0x1d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x0c, 0x02, 0x06, 0x01, 0x12, 0x03, 0x55, 0x10, 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0c, 0x02, + 0x06, 0x03, 0x12, 0x03, 0x55, 0x1a, 0x1c, 0x0a, 0x0a, 0x0a, 0x02, 0x05, 0x00, 0x12, 0x04, 0x59, + 0x00, 0x65, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x05, 0x00, 0x01, 0x12, 0x03, 0x59, 0x05, 0x15, 0x0a, + 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x5a, 0x02, 0x1f, 0x0a, 0x0b, 0x0a, + 0x04, 0x05, 0x00, 0x02, 0x00, 0x12, 0x03, 0x5a, 0x02, 0x24, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, + 0x02, 0x00, 0x02, 0x12, 0x03, 0x5a, 0x22, 0x23, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x01, + 0x01, 0x12, 0x03, 0x5c, 0x02, 0x0f, 0x0a, 0x3e, 0x0a, 0x04, 0x05, 0x00, 0x02, 0x01, 0x12, 0x03, + 0x5c, 0x02, 0x14, 0x1a, 0x31, 0x20, 0x45, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x20, 0x6f, 0x6e, + 0x63, 0x65, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x61, 0x20, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, + 0x74, 0x20, 0x69, 0x73, 0x20, 0x61, 0x64, 0x64, 0x65, 0x64, 0x20, 0x76, 0x69, 0x61, 0x20, 0x60, + 0x41, 0x64, 0x64, 0x60, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x01, 0x02, 0x12, + 0x03, 0x5c, 0x12, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x5e, + 0x02, 0x11, 0x0a, 0x43, 0x0a, 0x04, 0x05, 0x00, 0x02, 0x02, 0x12, 0x03, 0x5e, 0x02, 0x16, 0x1a, + 0x36, 0x20, 0x45, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x63, 0x65, 0x20, 0x77, + 0x68, 0x65, 0x6e, 0x20, 0x61, 0x20, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x73, + 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20, 0x76, 0x69, 0x61, 0x20, 0x60, 0x52, 0x65, + 0x6d, 0x6f, 0x76, 0x65, 0x60, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x02, 0x02, + 0x12, 0x03, 0x5e, 0x14, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x03, 0x01, 0x12, 0x03, + 0x61, 0x02, 0x0f, 0x0a, 0x86, 0x01, 0x0a, 0x04, 0x05, 0x00, 0x02, 0x03, 0x12, 0x03, 0x61, 0x02, + 0x14, 0x1a, 0x79, 0x20, 0x45, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x20, 0x61, + 0x6e, 0x79, 0x20, 0x60, 0x53, 0x74, 0x61, 0x74, 0x65, 0x60, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x69, 0x6e, 0x67, + 0x20, 0x50, 0x41, 0x55, 0x53, 0x45, 0x44, 0x20, 0x3c, 0x2d, 0x3e, 0x20, 0x44, 0x4f, 0x57, 0x4e, + 0x4c, 0x4f, 0x41, 0x44, 0x49, 0x4e, 0x47, 0x2c, 0x0a, 0x20, 0x44, 0x4f, 0x57, 0x4e, 0x4c, 0x4f, + 0x41, 0x44, 0x49, 0x4e, 0x47, 0x20, 0x3c, 0x2d, 0x3e, 0x20, 0x53, 0x54, 0x41, 0x4c, 0x45, 0x2c, + 0x20, 0x2a, 0x20, 0x2d, 0x3e, 0x20, 0x46, 0x49, 0x4e, 0x49, 0x53, 0x48, 0x45, 0x44, 0x2c, 0x20, + 0x2a, 0x20, 0x2d, 0x3e, 0x20, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, + 0x05, 0x00, 0x02, 0x03, 0x02, 0x12, 0x03, 0x61, 0x12, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, + 0x02, 0x04, 0x01, 0x12, 0x03, 0x64, 0x02, 0x0a, 0x0a, 0x9d, 0x01, 0x0a, 0x04, 0x05, 0x00, 0x02, + 0x04, 0x12, 0x03, 0x64, 0x02, 0x0f, 0x1a, 0x8f, 0x01, 0x20, 0x45, 0x6d, 0x69, 0x74, 0x74, 0x65, + 0x64, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x67, + 0x65, 0x72, 0x20, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x70, 0x65, 0x72, 0x63, + 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x20, 0x28, + 0x72, 0x61, 0x74, 0x65, 0x2d, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x29, 0x2e, 0x0a, 0x20, + 0x4e, 0x6f, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x73, 0x20, 0x73, 0x65, 0x6e, 0x74, + 0x20, 0x69, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, + 0x67, 0x65, 0x20, 0x64, 0x69, 0x64, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x61, 0x64, 0x76, 0x61, 0x6e, + 0x63, 0x65, 0x20, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x61, 0x73, + 0x74, 0x20, 0x74, 0x69, 0x63, 0x6b, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x04, + 0x02, 0x12, 0x03, 0x64, 0x0d, 0x0e, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x0d, 0x12, 0x04, 0x67, 0x00, + 0x6a, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x0d, 0x01, 0x12, 0x03, 0x67, 0x08, 0x14, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x0d, 0x02, 0x00, 0x06, 0x12, 0x03, 0x68, 0x02, 0x07, 0x0a, 0x0b, 0x0a, 0x04, + 0x04, 0x0d, 0x02, 0x00, 0x12, 0x03, 0x68, 0x02, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0d, 0x02, + 0x00, 0x01, 0x12, 0x03, 0x68, 0x08, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0d, 0x02, 0x00, 0x03, + 0x12, 0x03, 0x68, 0x13, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0d, 0x02, 0x01, 0x06, 0x12, 0x03, + 0x69, 0x02, 0x07, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0d, 0x02, 0x01, 0x12, 0x03, 0x69, 0x02, 0x14, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0d, 0x02, 0x01, 0x01, 0x12, 0x03, 0x69, 0x08, 0x0f, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x0d, 0x02, 0x01, 0x03, 0x12, 0x03, 0x69, 0x12, 0x13, 0x0a, 0x0a, 0x0a, 0x02, + 0x04, 0x0e, 0x12, 0x04, 0x6c, 0x00, 0x71, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x0e, 0x01, 0x12, + 0x03, 0x6c, 0x08, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0e, 0x02, 0x00, 0x05, 0x12, 0x03, 0x6e, + 0x02, 0x08, 0x0a, 0x32, 0x0a, 0x04, 0x04, 0x0e, 0x02, 0x00, 0x12, 0x03, 0x6e, 0x02, 0x15, 0x1a, + 0x25, 0x20, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, + 0x72, 0x20, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x20, 0x30, 0x2e, 0x2e, + 0x3d, 0x31, 0x30, 0x30, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0e, 0x02, 0x00, 0x01, 0x12, + 0x03, 0x6e, 0x09, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0e, 0x02, 0x00, 0x03, 0x12, 0x03, 0x6e, + 0x13, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0e, 0x02, 0x01, 0x05, 0x12, 0x03, 0x70, 0x02, 0x08, + 0x0a, 0x53, 0x0a, 0x04, 0x04, 0x0e, 0x02, 0x01, 0x12, 0x03, 0x70, 0x02, 0x19, 0x1a, 0x46, 0x20, + 0x42, 0x79, 0x74, 0x65, 0x73, 0x20, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, + 0x20, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x65, 0x76, 0x69, + 0x6f, 0x75, 0x73, 0x20, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53, 0x53, 0x20, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x74, 0x6f, 0x72, 0x72, + 0x65, 0x6e, 0x74, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0e, 0x02, 0x01, 0x01, 0x12, 0x03, + 0x70, 0x09, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0e, 0x02, 0x01, 0x03, 0x12, 0x03, 0x70, 0x17, + 0x18, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x0f, 0x12, 0x04, 0x73, 0x00, 0x76, 0x01, 0x0a, 0x0a, 0x0a, + 0x03, 0x04, 0x0f, 0x01, 0x12, 0x03, 0x73, 0x08, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0f, 0x02, + 0x00, 0x05, 0x12, 0x03, 0x75, 0x02, 0x08, 0x0a, 0x4e, 0x0a, 0x04, 0x04, 0x0f, 0x02, 0x00, 0x12, + 0x03, 0x75, 0x02, 0x14, 0x1a, 0x41, 0x20, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x73, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, + 0x20, 0x74, 0x6f, 0x20, 0x60, 0x41, 0x64, 0x64, 0x60, 0x20, 0x28, 0x6d, 0x61, 0x67, 0x6e, 0x65, + 0x74, 0x2c, 0x20, 0x55, 0x52, 0x4c, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, + 0x70, 0x61, 0x74, 0x68, 0x29, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0f, 0x02, 0x00, 0x01, + 0x12, 0x03, 0x75, 0x09, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0f, 0x02, 0x00, 0x03, 0x12, 0x03, + 0x75, 0x12, 0x13, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x10, 0x12, 0x04, 0x78, 0x00, 0x7b, 0x01, 0x0a, + 0x0a, 0x0a, 0x03, 0x04, 0x10, 0x01, 0x12, 0x03, 0x78, 0x08, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x10, 0x02, 0x00, 0x05, 0x12, 0x03, 0x7a, 0x02, 0x06, 0x0a, 0x46, 0x0a, 0x04, 0x04, 0x10, 0x02, + 0x00, 0x12, 0x03, 0x7a, 0x02, 0x19, 0x1a, 0x39, 0x20, 0x54, 0x72, 0x75, 0x65, 0x20, 0x69, 0x66, + 0x20, 0x60, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x60, 0x20, 0x77, 0x61, 0x73, 0x20, 0x63, 0x61, + 0x6c, 0x6c, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x60, 0x64, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x20, 0x3d, 0x20, 0x74, 0x72, 0x75, 0x65, 0x60, 0x2e, + 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x00, 0x01, 0x12, 0x03, 0x7a, 0x07, 0x14, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x00, 0x03, 0x12, 0x03, 0x7a, 0x17, 0x18, 0x0a, 0x0b, 0x0a, + 0x02, 0x05, 0x01, 0x12, 0x05, 0x7d, 0x00, 0x8a, 0x01, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x05, 0x01, + 0x01, 0x12, 0x03, 0x7d, 0x05, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x01, 0x02, 0x00, 0x01, 0x12, + 0x03, 0x7e, 0x02, 0x13, 0x0a, 0x0b, 0x0a, 0x04, 0x05, 0x01, 0x02, 0x00, 0x12, 0x03, 0x7e, 0x02, + 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x01, 0x02, 0x00, 0x02, 0x12, 0x03, 0x7e, 0x16, 0x17, 0x0a, + 0x0c, 0x0a, 0x05, 0x05, 0x01, 0x02, 0x01, 0x01, 0x12, 0x03, 0x7f, 0x02, 0x09, 0x0a, 0x0b, 0x0a, + 0x04, 0x05, 0x01, 0x02, 0x01, 0x12, 0x03, 0x7f, 0x02, 0x0e, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x01, + 0x02, 0x01, 0x02, 0x12, 0x03, 0x7f, 0x0c, 0x0d, 0x0a, 0x0d, 0x0a, 0x05, 0x05, 0x01, 0x02, 0x02, + 0x01, 0x12, 0x04, 0x80, 0x01, 0x02, 0x0d, 0x0a, 0x0c, 0x0a, 0x04, 0x05, 0x01, 0x02, 0x02, 0x12, + 0x04, 0x80, 0x01, 0x02, 0x12, 0x0a, 0x0d, 0x0a, 0x05, 0x05, 0x01, 0x02, 0x02, 0x02, 0x12, 0x04, + 0x80, 0x01, 0x10, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x05, 0x01, 0x02, 0x03, 0x01, 0x12, 0x04, 0x81, + 0x01, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x04, 0x05, 0x01, 0x02, 0x03, 0x12, 0x04, 0x81, 0x01, 0x02, + 0x0d, 0x0a, 0x0d, 0x0a, 0x05, 0x05, 0x01, 0x02, 0x03, 0x02, 0x12, 0x04, 0x81, 0x01, 0x0b, 0x0c, + 0x0a, 0x0d, 0x0a, 0x05, 0x05, 0x01, 0x02, 0x04, 0x01, 0x12, 0x04, 0x82, 0x01, 0x02, 0x0a, 0x0a, + 0x0c, 0x0a, 0x04, 0x05, 0x01, 0x02, 0x04, 0x12, 0x04, 0x82, 0x01, 0x02, 0x0f, 0x0a, 0x0d, 0x0a, + 0x05, 0x05, 0x01, 0x02, 0x04, 0x02, 0x12, 0x04, 0x82, 0x01, 0x0d, 0x0e, 0x0a, 0x0d, 0x0a, 0x05, + 0x05, 0x01, 0x02, 0x05, 0x01, 0x12, 0x04, 0x83, 0x01, 0x02, 0x07, 0x0a, 0x0c, 0x0a, 0x04, 0x05, + 0x01, 0x02, 0x05, 0x12, 0x04, 0x83, 0x01, 0x02, 0x0c, 0x0a, 0x0d, 0x0a, 0x05, 0x05, 0x01, 0x02, + 0x05, 0x02, 0x12, 0x04, 0x83, 0x01, 0x0a, 0x0b, 0x0a, 0x0d, 0x0a, 0x05, 0x05, 0x01, 0x02, 0x06, + 0x01, 0x12, 0x04, 0x89, 0x01, 0x02, 0x07, 0x0a, 0xc2, 0x02, 0x0a, 0x04, 0x05, 0x01, 0x02, 0x06, + 0x12, 0x04, 0x89, 0x01, 0x02, 0x0c, 0x1a, 0xb3, 0x02, 0x20, 0x4c, 0x69, 0x76, 0x65, 0x20, 0x70, + 0x65, 0x72, 0x20, 0x6c, 0x69, 0x62, 0x72, 0x71, 0x62, 0x69, 0x74, 0x2c, 0x20, 0x62, 0x75, 0x74, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x20, 0x68, 0x61, 0x73, 0x20, + 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x61, 0x20, 0x73, 0x74, 0x61, 0x6c, 0x6c, + 0x3a, 0x20, 0x6e, 0x6f, 0x20, 0x62, 0x79, 0x74, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, + 0x73, 0x73, 0x0a, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x73, 0x65, 0x76, 0x65, 0x72, 0x61, 0x6c, 0x20, + 0x74, 0x69, 0x63, 0x6b, 0x73, 0x20, 0x41, 0x4e, 0x44, 0x20, 0x7a, 0x65, 0x72, 0x6f, 0x20, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x70, 0x65, 0x65, 0x72, 0x73, 0x2e, 0x20, + 0x54, 0x68, 0x65, 0x20, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x73, 0x20, 0x73, + 0x74, 0x69, 0x6c, 0x6c, 0x0a, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x72, 0x65, 0x64, + 0x20, 0x6c, 0x69, 0x76, 0x65, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x75, 0x6e, 0x64, + 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x3b, 0x20, + 0x74, 0x68, 0x69, 0x73, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6e, + 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x0a, 0x20, 0x6c, + 0x65, 0x76, 0x65, 0x6c, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x2e, 0x20, 0x52, 0x65, + 0x63, 0x6f, 0x76, 0x65, 0x72, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x44, 0x4f, 0x57, 0x4e, 0x4c, 0x4f, + 0x41, 0x44, 0x49, 0x4e, 0x47, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x61, + 0x6c, 0x6c, 0x79, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x20, 0x61, + 0x64, 0x76, 0x61, 0x6e, 0x63, 0x65, 0x0a, 0x20, 0x6f, 0x72, 0x20, 0x61, 0x20, 0x70, 0x65, 0x65, + 0x72, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x73, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, + 0x05, 0x01, 0x02, 0x06, 0x02, 0x12, 0x04, 0x89, 0x01, 0x0a, 0x0b, 0x0a, 0x0c, 0x0a, 0x02, 0x04, + 0x11, 0x12, 0x06, 0x8c, 0x01, 0x00, 0x9c, 0x01, 0x01, 0x0a, 0x0b, 0x0a, 0x03, 0x04, 0x11, 0x01, + 0x12, 0x04, 0x8c, 0x01, 0x08, 0x15, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x00, 0x05, 0x12, + 0x04, 0x8d, 0x01, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x11, 0x02, 0x00, 0x12, 0x04, 0x8d, + 0x01, 0x02, 0x10, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x00, 0x01, 0x12, 0x04, 0x8d, 0x01, + 0x09, 0x0b, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x00, 0x03, 0x12, 0x04, 0x8d, 0x01, 0x0e, + 0x0f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x01, 0x05, 0x12, 0x04, 0x8e, 0x01, 0x02, 0x08, + 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x11, 0x02, 0x01, 0x12, 0x04, 0x8e, 0x01, 0x02, 0x17, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x11, 0x02, 0x01, 0x01, 0x12, 0x04, 0x8e, 0x01, 0x09, 0x12, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x11, 0x02, 0x01, 0x03, 0x12, 0x04, 0x8e, 0x01, 0x15, 0x16, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x11, 0x02, 0x02, 0x05, 0x12, 0x04, 0x8f, 0x01, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x04, 0x04, + 0x11, 0x02, 0x02, 0x12, 0x04, 0x8f, 0x01, 0x02, 0x12, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, + 0x02, 0x01, 0x12, 0x04, 0x8f, 0x01, 0x09, 0x0d, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x02, + 0x03, 0x12, 0x04, 0x8f, 0x01, 0x10, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x03, 0x05, + 0x12, 0x04, 0x90, 0x01, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x11, 0x02, 0x03, 0x12, 0x04, + 0x90, 0x01, 0x02, 0x14, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x03, 0x01, 0x12, 0x04, 0x90, + 0x01, 0x09, 0x0f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x03, 0x03, 0x12, 0x04, 0x90, 0x01, + 0x12, 0x13, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x04, 0x05, 0x12, 0x04, 0x91, 0x01, 0x02, + 0x08, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x11, 0x02, 0x04, 0x12, 0x04, 0x91, 0x01, 0x02, 0x19, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x04, 0x01, 0x12, 0x04, 0x91, 0x01, 0x09, 0x14, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x11, 0x02, 0x04, 0x03, 0x12, 0x04, 0x91, 0x01, 0x17, 0x18, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x11, 0x02, 0x05, 0x05, 0x12, 0x04, 0x92, 0x01, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x04, + 0x04, 0x11, 0x02, 0x05, 0x12, 0x04, 0x92, 0x01, 0x02, 0x19, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, + 0x02, 0x05, 0x01, 0x12, 0x04, 0x92, 0x01, 0x09, 0x14, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, + 0x05, 0x03, 0x12, 0x04, 0x92, 0x01, 0x17, 0x18, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x06, + 0x05, 0x12, 0x04, 0x93, 0x01, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x11, 0x02, 0x06, 0x12, + 0x04, 0x93, 0x01, 0x02, 0x1e, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x06, 0x01, 0x12, 0x04, + 0x93, 0x01, 0x09, 0x19, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x06, 0x03, 0x12, 0x04, 0x93, + 0x01, 0x1c, 0x1d, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x07, 0x06, 0x12, 0x04, 0x94, 0x01, + 0x02, 0x07, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x11, 0x02, 0x07, 0x12, 0x04, 0x94, 0x01, 0x02, 0x12, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x07, 0x01, 0x12, 0x04, 0x94, 0x01, 0x08, 0x0d, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x07, 0x03, 0x12, 0x04, 0x94, 0x01, 0x10, 0x11, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x11, 0x02, 0x08, 0x05, 0x12, 0x04, 0x95, 0x01, 0x02, 0x08, 0x0a, 0x0c, 0x0a, + 0x04, 0x04, 0x11, 0x02, 0x08, 0x12, 0x04, 0x95, 0x01, 0x02, 0x1b, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x11, 0x02, 0x08, 0x01, 0x12, 0x04, 0x95, 0x01, 0x09, 0x16, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, + 0x02, 0x08, 0x03, 0x12, 0x04, 0x95, 0x01, 0x19, 0x1a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, + 0x09, 0x05, 0x12, 0x04, 0x96, 0x01, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x11, 0x02, 0x09, + 0x12, 0x04, 0x96, 0x01, 0x02, 0x21, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x09, 0x01, 0x12, + 0x04, 0x96, 0x01, 0x09, 0x1b, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x09, 0x03, 0x12, 0x04, + 0x96, 0x01, 0x1e, 0x20, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x0a, 0x05, 0x12, 0x04, 0x97, + 0x01, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x11, 0x02, 0x0a, 0x12, 0x04, 0x97, 0x01, 0x02, + 0x1a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x0a, 0x01, 0x12, 0x04, 0x97, 0x01, 0x09, 0x14, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x0a, 0x03, 0x12, 0x04, 0x97, 0x01, 0x17, 0x19, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x0b, 0x05, 0x12, 0x04, 0x98, 0x01, 0x02, 0x08, 0x0a, 0x0c, + 0x0a, 0x04, 0x04, 0x11, 0x02, 0x0b, 0x12, 0x04, 0x98, 0x01, 0x02, 0x1d, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x11, 0x02, 0x0b, 0x01, 0x12, 0x04, 0x98, 0x01, 0x09, 0x17, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x11, 0x02, 0x0b, 0x03, 0x12, 0x04, 0x98, 0x01, 0x1a, 0x1c, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, + 0x02, 0x0c, 0x05, 0x12, 0x04, 0x99, 0x01, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x11, 0x02, + 0x0c, 0x12, 0x04, 0x99, 0x01, 0x02, 0x1f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x0c, 0x01, + 0x12, 0x04, 0x99, 0x01, 0x09, 0x19, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x0c, 0x03, 0x12, + 0x04, 0x99, 0x01, 0x1c, 0x1e, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x0d, 0x05, 0x12, 0x04, + 0x9a, 0x01, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x11, 0x02, 0x0d, 0x12, 0x04, 0x9a, 0x01, + 0x02, 0x14, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x0d, 0x01, 0x12, 0x04, 0x9a, 0x01, 0x09, + 0x0e, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x0d, 0x03, 0x12, 0x04, 0x9a, 0x01, 0x11, 0x13, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x0e, 0x05, 0x12, 0x04, 0x9b, 0x01, 0x02, 0x08, 0x0a, + 0x0c, 0x0a, 0x04, 0x04, 0x11, 0x02, 0x0e, 0x12, 0x04, 0x9b, 0x01, 0x02, 0x14, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x11, 0x02, 0x0e, 0x01, 0x12, 0x04, 0x9b, 0x01, 0x09, 0x0e, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x11, 0x02, 0x0e, 0x03, 0x12, 0x04, 0x9b, 0x01, 0x11, 0x13, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, ]; include!("torrent.serde.rs"); diff --git a/crates/proto/src/generated/torrent/torrent.serde.rs b/crates/proto/src/generated/torrent/torrent.serde.rs index c626f55..f80a62a 100644 --- a/crates/proto/src/generated/torrent/torrent.serde.rs +++ b/crates/proto/src/generated/torrent/torrent.serde.rs @@ -199,6 +199,98 @@ impl<'de> serde::Deserialize<'de> for AddResponse { deserializer.deserialize_struct("torrent.AddResponse", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for AddedInfo { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.source.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("torrent.AddedInfo", len)?; + if !self.source.is_empty() { + struct_ser.serialize_field("source", &self.source)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for AddedInfo { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &["source"]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Source, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "source" => Ok(GeneratedField::Source), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = AddedInfo; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct torrent.AddedInfo") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut source__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Source => { + if source__.is_some() { + return Err(serde::de::Error::duplicate_field("source")); + } + source__ = Some(map_.next_value()?); + } + } + } + Ok(AddedInfo { + source: source__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("torrent.AddedInfo", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for ListRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -362,6 +454,420 @@ impl<'de> serde::Deserialize<'de> for ListResponse { deserializer.deserialize_struct("torrent.ListResponse", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for Notification { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.observed_at_unix_millis != 0 { + len += 1; + } + if self.kind != 0 { + len += 1; + } + if self.torrent.is_some() { + len += 1; + } + if self.detail.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("torrent.Notification", len)?; + if self.observed_at_unix_millis != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field( + "observedAtUnixMillis", + ToString::to_string(&self.observed_at_unix_millis).as_str(), + )?; + } + if self.kind != 0 { + let v = NotificationKind::try_from(self.kind) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.kind)))?; + struct_ser.serialize_field("kind", &v)?; + } + if let Some(v) = self.torrent.as_ref() { + struct_ser.serialize_field("torrent", v)?; + } + if let Some(v) = self.detail.as_ref() { + match v { + notification::Detail::StateChanged(v) => { + struct_ser.serialize_field("stateChanged", v)?; + } + notification::Detail::Progress(v) => { + struct_ser.serialize_field("progress", v)?; + } + notification::Detail::Added(v) => { + struct_ser.serialize_field("added", v)?; + } + notification::Detail::Removed(v) => { + struct_ser.serialize_field("removed", v)?; + } + } + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for Notification { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "observed_at_unix_millis", + "observedAtUnixMillis", + "kind", + "torrent", + "state_changed", + "stateChanged", + "progress", + "added", + "removed", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + ObservedAtUnixMillis, + Kind, + Torrent, + StateChanged, + Progress, + Added, + Removed, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "observedAtUnixMillis" | "observed_at_unix_millis" => { + Ok(GeneratedField::ObservedAtUnixMillis) + } + "kind" => Ok(GeneratedField::Kind), + "torrent" => Ok(GeneratedField::Torrent), + "stateChanged" | "state_changed" => Ok(GeneratedField::StateChanged), + "progress" => Ok(GeneratedField::Progress), + "added" => Ok(GeneratedField::Added), + "removed" => Ok(GeneratedField::Removed), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = Notification; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct torrent.Notification") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut observed_at_unix_millis__ = None; + let mut kind__ = None; + let mut torrent__ = None; + let mut detail__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::ObservedAtUnixMillis => { + if observed_at_unix_millis__.is_some() { + return Err(serde::de::Error::duplicate_field( + "observedAtUnixMillis", + )); + } + observed_at_unix_millis__ = Some( + map_.next_value::<::pbjson::private::NumberDeserialize<_>>()? + .0, + ); + } + GeneratedField::Kind => { + if kind__.is_some() { + return Err(serde::de::Error::duplicate_field("kind")); + } + kind__ = Some(map_.next_value::()? as i32); + } + GeneratedField::Torrent => { + if torrent__.is_some() { + return Err(serde::de::Error::duplicate_field("torrent")); + } + torrent__ = map_.next_value()?; + } + GeneratedField::StateChanged => { + if detail__.is_some() { + return Err(serde::de::Error::duplicate_field("stateChanged")); + } + detail__ = map_ + .next_value::<::std::option::Option<_>>()? + .map(notification::Detail::StateChanged); + } + GeneratedField::Progress => { + if detail__.is_some() { + return Err(serde::de::Error::duplicate_field("progress")); + } + detail__ = map_ + .next_value::<::std::option::Option<_>>()? + .map(notification::Detail::Progress); + } + GeneratedField::Added => { + if detail__.is_some() { + return Err(serde::de::Error::duplicate_field("added")); + } + detail__ = map_ + .next_value::<::std::option::Option<_>>()? + .map(notification::Detail::Added); + } + GeneratedField::Removed => { + if detail__.is_some() { + return Err(serde::de::Error::duplicate_field("removed")); + } + detail__ = map_ + .next_value::<::std::option::Option<_>>()? + .map(notification::Detail::Removed); + } + } + } + Ok(Notification { + observed_at_unix_millis: observed_at_unix_millis__.unwrap_or_default(), + kind: kind__.unwrap_or_default(), + torrent: torrent__, + detail: detail__, + }) + } + } + deserializer.deserialize_struct("torrent.Notification", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for NotificationKind { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::Unspecified => "NOTIFICATION_KIND_UNSPECIFIED", + Self::TorrentAdded => "TORRENT_ADDED", + Self::TorrentRemoved => "TORRENT_REMOVED", + Self::StateChanged => "STATE_CHANGED", + Self::Progress => "PROGRESS", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for NotificationKind { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "NOTIFICATION_KIND_UNSPECIFIED", + "TORRENT_ADDED", + "TORRENT_REMOVED", + "STATE_CHANGED", + "PROGRESS", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = NotificationKind; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "NOTIFICATION_KIND_UNSPECIFIED" => Ok(NotificationKind::Unspecified), + "TORRENT_ADDED" => Ok(NotificationKind::TorrentAdded), + "TORRENT_REMOVED" => Ok(NotificationKind::TorrentRemoved), + "STATE_CHANGED" => Ok(NotificationKind::StateChanged), + "PROGRESS" => Ok(NotificationKind::Progress), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} +impl serde::Serialize for NotificationsRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.ids.is_empty() { + len += 1; + } + if !self.kinds.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("torrent.NotificationsRequest", len)?; + if !self.ids.is_empty() { + struct_ser.serialize_field("ids", &self.ids)?; + } + if !self.kinds.is_empty() { + let v = self + .kinds + .iter() + .cloned() + .map(|v| { + NotificationKind::try_from(v) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", v))) + }) + .collect::, _>>()?; + struct_ser.serialize_field("kinds", &v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for NotificationsRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &["ids", "kinds"]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Ids, + Kinds, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "ids" => Ok(GeneratedField::Ids), + "kinds" => Ok(GeneratedField::Kinds), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = NotificationsRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct torrent.NotificationsRequest") + } + + fn visit_map( + self, + mut map_: V, + ) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut ids__ = None; + let mut kinds__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Ids => { + if ids__.is_some() { + return Err(serde::de::Error::duplicate_field("ids")); + } + ids__ = Some(map_.next_value()?); + } + GeneratedField::Kinds => { + if kinds__.is_some() { + return Err(serde::de::Error::duplicate_field("kinds")); + } + kinds__ = Some( + map_.next_value::>()? + .into_iter() + .map(|x| x as i32) + .collect(), + ); + } + } + } + Ok(NotificationsRequest { + ids: ids__.unwrap_or_default(), + kinds: kinds__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("torrent.NotificationsRequest", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for PauseRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -525,6 +1031,125 @@ impl<'de> serde::Deserialize<'de> for PauseResponse { deserializer.deserialize_struct("torrent.PauseResponse", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for ProgressMark { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.percent != 0 { + len += 1; + } + if self.bytes_delta != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("torrent.ProgressMark", len)?; + if self.percent != 0 { + struct_ser.serialize_field("percent", &self.percent)?; + } + if self.bytes_delta != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field( + "bytesDelta", + ToString::to_string(&self.bytes_delta).as_str(), + )?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for ProgressMark { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &["percent", "bytes_delta", "bytesDelta"]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Percent, + BytesDelta, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "percent" => Ok(GeneratedField::Percent), + "bytesDelta" | "bytes_delta" => Ok(GeneratedField::BytesDelta), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = ProgressMark; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct torrent.ProgressMark") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut percent__ = None; + let mut bytes_delta__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Percent => { + if percent__.is_some() { + return Err(serde::de::Error::duplicate_field("percent")); + } + percent__ = Some( + map_.next_value::<::pbjson::private::NumberDeserialize<_>>()? + .0, + ); + } + GeneratedField::BytesDelta => { + if bytes_delta__.is_some() { + return Err(serde::de::Error::duplicate_field("bytesDelta")); + } + bytes_delta__ = Some( + map_.next_value::<::pbjson::private::NumberDeserialize<_>>()? + .0, + ); + } + } + } + Ok(ProgressMark { + percent: percent__.unwrap_or_default(), + bytes_delta: bytes_delta__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("torrent.ProgressMark", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for RemoveRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -704,6 +1329,98 @@ impl<'de> serde::Deserialize<'de> for RemoveResponse { deserializer.deserialize_struct("torrent.RemoveResponse", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for RemovedInfo { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.files_deleted { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("torrent.RemovedInfo", len)?; + if self.files_deleted { + struct_ser.serialize_field("filesDeleted", &self.files_deleted)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for RemovedInfo { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &["files_deleted", "filesDeleted"]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + FilesDeleted, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "filesDeleted" | "files_deleted" => Ok(GeneratedField::FilesDeleted), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = RemovedInfo; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct torrent.RemovedInfo") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut files_deleted__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::FilesDeleted => { + if files_deleted__.is_some() { + return Err(serde::de::Error::duplicate_field("filesDeleted")); + } + files_deleted__ = Some(map_.next_value()?); + } + } + } + Ok(RemovedInfo { + files_deleted: files_deleted__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("torrent.RemovedInfo", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for ResumeRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -880,6 +1597,7 @@ impl serde::Serialize for State { Self::Paused => "PAUSED", Self::Finished => "FINISHED", Self::Error => "ERROR", + Self::Stale => "STALE", }; serializer.serialize_str(variant) } @@ -897,6 +1615,7 @@ impl<'de> serde::Deserialize<'de> for State { "PAUSED", "FINISHED", "ERROR", + "STALE", ]; struct GeneratedVisitor; @@ -943,6 +1662,7 @@ impl<'de> serde::Deserialize<'de> for State { "PAUSED" => Ok(State::Paused), "FINISHED" => Ok(State::Finished), "ERROR" => Ok(State::Error), + "STALE" => Ok(State::Stale), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -950,6 +1670,120 @@ impl<'de> serde::Deserialize<'de> for State { deserializer.deserialize_any(GeneratedVisitor) } } +impl serde::Serialize for StateChanged { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.previous != 0 { + len += 1; + } + if self.current != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("torrent.StateChanged", len)?; + if self.previous != 0 { + let v = State::try_from(self.previous).map_err(|_| { + serde::ser::Error::custom(format!("Invalid variant {}", self.previous)) + })?; + struct_ser.serialize_field("previous", &v)?; + } + if self.current != 0 { + let v = State::try_from(self.current).map_err(|_| { + serde::ser::Error::custom(format!("Invalid variant {}", self.current)) + })?; + struct_ser.serialize_field("current", &v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for StateChanged { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &["previous", "current"]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Previous, + Current, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "previous" => Ok(GeneratedField::Previous), + "current" => Ok(GeneratedField::Current), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = StateChanged; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct torrent.StateChanged") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut previous__ = None; + let mut current__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Previous => { + if previous__.is_some() { + return Err(serde::de::Error::duplicate_field("previous")); + } + previous__ = Some(map_.next_value::()? as i32); + } + GeneratedField::Current => { + if current__.is_some() { + return Err(serde::de::Error::duplicate_field("current")); + } + current__ = Some(map_.next_value::()? as i32); + } + } + } + Ok(StateChanged { + previous: previous__.unwrap_or_default(), + current: current__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("torrent.StateChanged", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for StatusRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result diff --git a/crates/proto/src/generated/torrent/torrent.tonic.rs b/crates/proto/src/generated/torrent/torrent.tonic.rs index 2f75817..4b32189 100644 --- a/crates/proto/src/generated/torrent/torrent.tonic.rs +++ b/crates/proto/src/generated/torrent/torrent.tonic.rs @@ -173,6 +173,23 @@ pub mod torrents_client { .insert(GrpcMethod::new("torrent.Torrents", "Resume")); self.inner.unary(req, path, codec).await } + pub async fn notifications( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response>, + tonic::Status, + > { + self.inner.ready().await.map_err(|e| { + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static("/torrent.Torrents/Notifications"); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("torrent.Torrents", "Notifications")); + self.inner.server_streaming(req, path, codec).await + } } } /// Generated server implementations. @@ -212,6 +229,15 @@ pub mod torrents_server { &self, request: tonic::Request, ) -> std::result::Result, tonic::Status>; + /// Server streaming response type for the Notifications method. + type NotificationsStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + std::marker::Send + + 'static; + async fn notifications( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; } #[derive(Debug)] pub struct TorrentsServer { @@ -508,6 +534,50 @@ pub mod torrents_server { }; Box::pin(fut) } + "/torrent.Torrents/Notifications" => { + #[allow(non_camel_case_types)] + struct NotificationsSvc(pub Arc); + impl + tonic::server::ServerStreamingService + for NotificationsSvc + { + type Response = super::Notification; + type ResponseStream = T::NotificationsStream; + type Future = + BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::notifications(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = NotificationsSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } _ => Box::pin(async move { let mut response = http::Response::new(tonic::body::Body::default()); let headers = response.headers_mut(); diff --git a/crates/proto/src/lib.rs b/crates/proto/src/lib.rs index e56e4f6..10ce80d 100644 --- a/crates/proto/src/lib.rs +++ b/crates/proto/src/lib.rs @@ -2,9 +2,11 @@ pub mod torrent { include!("generated/torrent/torrent.rs"); } +pub use torrent::notification as notification_detail; pub use torrent::{ - AddRequest, AddResponse, ListRequest, ListResponse, PauseRequest, PauseResponse, RemoveRequest, - RemoveResponse, ResumeRequest, ResumeResponse, State, StatusRequest, TorrentStatus, + AddRequest, AddResponse, AddedInfo, ListRequest, ListResponse, Notification, NotificationKind, + NotificationsRequest, PauseRequest, PauseResponse, ProgressMark, RemoveRequest, RemoveResponse, + RemovedInfo, ResumeRequest, ResumeResponse, State, StateChanged, StatusRequest, TorrentStatus, torrents_client::TorrentsClient, torrents_server::{Torrents, TorrentsServer}, }; diff --git a/crates/tora/Cargo.toml b/crates/tora/Cargo.toml index 8900d96..b727cb5 100644 --- a/crates/tora/Cargo.toml +++ b/crates/tora/Cargo.toml @@ -5,12 +5,14 @@ edition = "2024" [dependencies] anyhow = "1.0.103" +chrono = { version = "0.4", default-features = false, features = ["clock"] } clap = { version = "4.6.1", features = ["derive", "env"] } colored = "3" hyper-util = { version = "0.1.20", features = ["tokio"] } nix = { version = "0.31", features = ["signal"] } serde_json = "1.0.140" -tokio = { version = "1.52.3", features = ["rt-multi-thread", "macros", "net"] } +tokio = { version = "1.52.3", features = ["rt-multi-thread", "macros", "net", "signal"] } +tokio-stream = "0.1.18" tonic = "0.14.6" tonic-health = "0.14.6" tora-proto = { version = "0.1.0", path = "../proto" } diff --git a/crates/tora/src/cli.rs b/crates/tora/src/cli.rs index 4f4b76c..8572a86 100644 --- a/crates/tora/src/cli.rs +++ b/crates/tora/src/cli.rs @@ -1,6 +1,7 @@ use std::path::PathBuf; use clap::{Parser, Subcommand, ValueEnum}; +use tora_proto::NotificationKind; #[derive(Parser)] #[command( @@ -43,6 +44,31 @@ pub enum SortKey { Added, } +/// CLI-facing mirror of `NotificationKind` so `clap`'s `ValueEnum` derive can +/// enumerate the values. Mapped back to the proto enum via `to_proto`. +#[derive(Copy, Clone, Debug, ValueEnum)] +pub enum KindFilter { + /// `TORRENT_ADDED` + Added, + /// `TORRENT_REMOVED` + Removed, + /// `STATE_CHANGED` (includes DOWNLOADING <-> STALE, * -> FINISHED, etc.) + StateChanged, + /// `PROGRESS` (one event per integer percent advance) + Progress, +} + +impl KindFilter { + pub fn to_proto(self) -> i32 { + match self { + KindFilter::Added => NotificationKind::TorrentAdded as i32, + KindFilter::Removed => NotificationKind::TorrentRemoved as i32, + KindFilter::StateChanged => NotificationKind::StateChanged as i32, + KindFilter::Progress => NotificationKind::Progress as i32, + } + } +} + #[derive(Subcommand)] pub enum Command { /// Check whether torad is reachable and healthy. @@ -95,4 +121,15 @@ pub enum Command { }, /// Stop the torad daemon. Stop, + /// Stream torrent notifications live from torad. Runs until interrupted + /// (Ctrl-C) or the daemon disconnects. + Watch { + /// Filter to these torrent ids (UUIDs or unique prefixes). Repeatable. + /// Omit to observe all torrents. + #[arg(long = "id", value_name = "ID")] + ids: Vec, + /// Only emit these event kinds. Repeatable. Omit for all kinds. + #[arg(long = "kind", value_enum)] + kinds: Vec, + }, } diff --git a/crates/tora/src/main.rs b/crates/tora/src/main.rs index e80e620..3d8bd36 100644 --- a/crates/tora/src/main.rs +++ b/crates/tora/src/main.rs @@ -6,17 +6,18 @@ use std::time::Duration; use anyhow::{Context, Result}; use clap::Parser; -use cli::{Cli, Command, SortKey}; +use cli::{Cli, Command, KindFilter, SortKey}; use hyper_util::rt::TokioIo; use output::Formatter; use tokio::net::UnixStream; +use tokio_stream::StreamExt; use tonic::transport::{Channel, Endpoint, Uri}; use tonic_health::pb::HealthCheckRequest; use tonic_health::pb::health_check_response::ServingStatus; use tonic_health::pb::health_client::HealthClient; use tora_proto::{ - AddRequest, ListRequest, PauseRequest, RemoveRequest, ResumeRequest, StatusRequest, - TorrentsClient, + AddRequest, ListRequest, NotificationsRequest, PauseRequest, RemoveRequest, ResumeRequest, + StatusRequest, TorrentsClient, }; use tower::service_fn; @@ -51,6 +52,7 @@ async fn main() -> Result<()> { download_dir, } => start_daemon(&socket, &pid_file, &database_url, download_dir, f).await, Command::Stop => stop_daemon(&socket, &pid_file, f).await, + Command::Watch { ids, kinds } => watch(&socket, ids, kinds, f).await, } } @@ -269,3 +271,44 @@ async fn stop_daemon(socket: &Path, pid_file: &Path, f: &dyn Formatter) -> Resul Err(err) => Err(err).context("failed to send SIGTERM to torad"), } } + +async fn watch( + socket: &Path, + ids: Vec, + kinds: Vec, + f: &dyn Formatter, +) -> Result<()> { + let channel = connect(socket).await?; + let mut client = TorrentsClient::new(channel); + + let request = NotificationsRequest { + ids, + kinds: kinds.iter().map(|k| k.to_proto()).collect(), + }; + + let stream = client + .notifications(request) + .await + .context("Notifications RPC failed")? + .into_inner(); + + tokio::pin!(stream); + + let stdout = std::io::stdout(); + while let Some(message) = stream.next().await { + match message { + Ok(notification) => { + let line = f.watch(¬ification)?; + let mut guard = stdout.lock(); + use std::io::Write; + writeln!(guard, "{line}")?; + guard.flush()?; + } + Err(status) => { + anyhow::bail!("stream error: {status}"); + } + } + } + + Ok(()) +} diff --git a/crates/tora/src/output.rs b/crates/tora/src/output.rs index cfbd08a..f2ef8b6 100644 --- a/crates/tora/src/output.rs +++ b/crates/tora/src/output.rs @@ -1,8 +1,12 @@ use std::fmt::Write; use anyhow::Result; +use chrono::{Local, TimeZone}; use colored::Colorize; -use tora_proto::{State, TorrentStatus}; +use tora_proto::{ + Notification, NotificationKind, ProgressMark, State, StateChanged, TorrentStatus, + notification_detail::Detail, +}; use crate::cli::OutputFormat; @@ -18,6 +22,7 @@ pub trait Formatter { fn resume(&self) -> Result; fn start(&self) -> Result; fn stop(&self) -> Result; + fn watch(&self, event: &Notification) -> Result; } pub fn from_format(format: OutputFormat) -> Box { @@ -77,6 +82,10 @@ impl Formatter for LongFormatter { fn stop(&self) -> Result { Ok(format!(" {} torad stopped", "●".yellow())) } + + fn watch(&self, event: &Notification) -> Result { + Ok(format_event_long(event)) + } } struct JsonFormatter; @@ -130,6 +139,10 @@ impl Formatter for JsonFormatter { &serde_json::json!({ "stopped": true }), )?) } + + fn watch(&self, event: &Notification) -> Result { + Ok(serde_json::to_string(event)?) + } } fn format_status_long(status: &TorrentStatus) -> String { @@ -216,6 +229,99 @@ fn format_status_long(status: &TorrentStatus) -> String { out } +fn format_event_long(event: &Notification) -> String { + let time = Local + .timestamp_millis_opt(event.observed_at_unix_millis as i64) + .single() + .map(|t| t.format("%H:%M:%S").to_string()) + .unwrap_or_else(|| "??".to_string()); + + let kind = NotificationKind::try_from(event.kind).unwrap_or(NotificationKind::Unspecified); + let torrent = event.torrent.as_ref(); + let id = torrent.map(|t| short_id(&t.id)).unwrap_or_default(); + let name = torrent + .map(|t| display_name(&t.name)) + .unwrap_or_else(|| "(unknown)".to_string()); + let state = torrent + .and_then(|t| State::try_from(t.state).ok()) + .unwrap_or(State::Unspecified); + + let id_w = 8usize; + let name_w = 30usize; + + let (icon, suffix) = match (&kind, event.detail.as_ref()) { + (NotificationKind::TorrentAdded, _) => ("+".green(), String::new()), + (NotificationKind::TorrentRemoved, Some(Detail::Removed(r))) => { + let note = if r.files_deleted { + " (files deleted)" + } else { + "" + }; + ("−".red(), format!("{}{}", colored_state(state), note.red())) + } + (NotificationKind::TorrentRemoved, _) => ("−".red(), colored_state(state).to_string()), + ( + NotificationKind::StateChanged, + Some(Detail::StateChanged(StateChanged { current, .. })), + ) => { + let cur = State::try_from(*current).unwrap_or(State::Unspecified); + let icon = state_icon(cur); + (icon, colored_state(cur).to_string()) + } + ( + NotificationKind::Progress, + Some(Detail::Progress(ProgressMark { + percent, + bytes_delta, + })), + ) => { + let delta = humanize_bytes(*bytes_delta); + let pct_bar = format_pct_inline(*percent); + ( + "↑".yellow(), + format!( + "{} {} {}", + pct_bar.yellow(), + format!("{}%", percent).bold(), + format!("(Δ {delta})").dimmed() + ), + ) + } + (_, _) => ("·".dimmed(), colored_state(state).to_string()), + }; + + format!( + " {} {} {} {} {}", + time.dimmed(), + icon, + pad(&id, id_w).dimmed(), + pad(&name, name_w), + suffix, + ) +} + +fn state_icon(state: State) -> colored::ColoredString { + match state { + State::Finished => "✓".green(), + State::Downloading => "▶".yellow(), + State::Paused => "⏸".cyan(), + State::Error => "✗".red(), + State::Stale => "⚠".red(), + State::Pending | State::Unspecified => "·".dimmed(), + } +} + +fn format_pct_inline(percent: u32) -> String { + let filled = (percent as usize * BAR_WIDTH) / 100; + let empty = BAR_WIDTH - filled; + format!( + "{}{} {:>3}%", + "█".repeat(filled), + "░".repeat(empty), + percent + ) +} + fn format_list_long(torrents: &[TorrentStatus]) -> String { if torrents.is_empty() { return " no torrents".dimmed().to_string(); @@ -301,6 +407,7 @@ fn colored_state(state: State) -> colored::ColoredString { State::Downloading => s.yellow(), State::Paused => s.cyan(), State::Error => s.red().bold(), + State::Stale => s.red(), State::Pending | State::Unspecified => s.dimmed(), } } @@ -312,6 +419,7 @@ fn pad_colored(s: &str, width: usize, state: State) -> colored::ColoredString { State::Downloading => padded.yellow(), State::Paused => padded.cyan(), State::Error => padded.red().bold(), + State::Stale => padded.red(), State::Pending | State::Unspecified => padded.dimmed(), } } diff --git a/crates/torad/src/db.rs b/crates/torad/src/db.rs index 166f86c..e06c0e6 100644 --- a/crates/torad/src/db.rs +++ b/crates/torad/src/db.rs @@ -23,6 +23,7 @@ pub enum TorrentState { Paused, Finished, Error, + Stale, } #[derive(Debug, Clone, sqlx::FromRow)] @@ -126,8 +127,12 @@ pub struct Progress<'a> { pub error_message: Option<&'a str>, } -pub async fn update_progress(pool: &PgPool, id: Uuid, progress: Progress<'_>) -> Result<()> { - sqlx::query( +pub async fn update_progress( + pool: &PgPool, + id: Uuid, + progress: Progress<'_>, +) -> Result { + let row = sqlx::query_as::<_, TorrentRow>( "UPDATE torrents SET name = COALESCE($2, name), total_bytes = $3, @@ -136,7 +141,8 @@ pub async fn update_progress(pool: &PgPool, id: Uuid, progress: Progress<'_>) -> error_message = $6, updated_at = now(), completed_at = CASE WHEN $5 = 'finished' THEN now() ELSE completed_at END - WHERE id = $1", + WHERE id = $1 + RETURNING id, info_hash, name, source, output_path, total_bytes, downloaded_bytes, state, error_message", ) .bind(id) .bind(progress.name) @@ -144,7 +150,7 @@ pub async fn update_progress(pool: &PgPool, id: Uuid, progress: Progress<'_>) -> .bind(progress.downloaded_bytes) .bind(progress.state) .bind(progress.error_message) - .execute(pool) + .fetch_one(pool) .await?; - Ok(()) + Ok(row) } diff --git a/crates/torad/src/torrents.rs b/crates/torad/src/torrents.rs index 800f82f..994b93e 100644 --- a/crates/torad/src/torrents.rs +++ b/crates/torad/src/torrents.rs @@ -1,19 +1,20 @@ use std::collections::{HashMap, HashSet}; use std::path::PathBuf; use std::sync::Arc; -use std::time::Duration; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use anyhow::{Context, Result}; use librqbit::api::TorrentIdOrHash; use librqbit::{AddTorrent, AddTorrentOptions, ManagedTorrent, Session, TorrentStatsState}; use librqbit_core::Id20; use sqlx::PgPool; -use tokio::sync::Mutex; +use tokio::sync::{Mutex, broadcast}; use tonic::{Request, Response, Status}; use tora_proto::{ - AddRequest, AddResponse, ListRequest, ListResponse, PauseRequest, PauseResponse, RemoveRequest, - RemoveResponse, ResumeRequest, ResumeResponse, State as ProtoState, StatusRequest, - TorrentStatus, Torrents, + AddRequest, AddResponse, AddedInfo, ListRequest, ListResponse, Notification, NotificationKind, + NotificationsRequest, PauseRequest, PauseResponse, ProgressMark, RemoveRequest, RemoveResponse, + ResumeRequest, ResumeResponse, State as ProtoState, StateChanged, StatusRequest, TorrentStatus, + Torrents, }; use tracing::{error, info, warn}; use uuid::Uuid; @@ -24,6 +25,14 @@ use crate::source::SourceResolver; const POLL_INTERVAL: Duration = Duration::from_secs(2); const ADD_TIMEOUT: Duration = Duration::from_secs(60); const BYTES_PER_MIB: f64 = 1_048_576.0; +/// `broadcast` channel capacity. Subscribers that fall more than this many +/// events behind receive `Lagged` errors and continue. Picked for ~5 s of +/// slack at the maximum realistic emit rate (~50 events/sec). +const EVENT_CHANNEL_CAPACITY: usize = 256; +/// Consecutive poller ticks a `Live && !finished` torrent must show no byte +/// progress AND zero connected peers before being flipped to `Stale`. +/// At a 2 s `POLL_INTERVAL`, this is 6 s of inactivity. +const STALE_TICKS_THRESHOLD: u32 = 3; pub enum ResolveError { NotFound, @@ -38,6 +47,8 @@ pub struct TorrentManager { source: SourceResolver, tracked: Mutex>>, adding: Mutex>, + events: broadcast::Sender, + poll_cache: Mutex>, } impl TorrentManager { @@ -52,6 +63,7 @@ impl TorrentManager { .await .context("failed to create librqbit session")?; let source = SourceResolver::new().context("failed to build source resolver")?; + let (events, _) = broadcast::channel(EVENT_CHANNEL_CAPACITY); Ok(Arc::new(Self { pool, session, @@ -59,9 +71,22 @@ impl TorrentManager { source, tracked: Mutex::new(HashMap::new()), adding: Mutex::new(HashSet::new()), + events, + poll_cache: Mutex::new(HashMap::new()), })) } + pub fn subscribe(&self) -> broadcast::Receiver { + self.events.subscribe() + } + + pub(crate) fn notify(&self, mut notification: Notification) { + if notification.observed_at_unix_millis == 0 { + notification.observed_at_unix_millis = now_millis(); + } + let _ = self.events.send(notification); + } + pub async fn add(&self, source: &str, output_dir: Option<&str>) -> Result { let resolved = self.source.resolve(source).await?; let stored_source = resolved.rewritten_source.as_deref().unwrap_or(source); @@ -263,30 +288,158 @@ impl TorrentManager { } async fn report_progress(&self) { - let tracked = self.tracked.lock().await; - for (id, handle) in tracked.iter() { - let stats = handle.stats(); - let name = handle.name(); - let state = if stats.finished { - TorrentState::Finished - } else { - match stats.state { - TorrentStatsState::Initializing => TorrentState::Pending, - TorrentStatsState::Live => TorrentState::Downloading, - TorrentStatsState::Paused => TorrentState::Paused, - TorrentStatsState::Error => TorrentState::Error, - } - }; + // Snapshot stats under a brief lock; do all DB work and emission outside. + let snapshots: Vec<(Uuid, librqbit::TorrentStats, Option)> = { + let tracked = self.tracked.lock().await; + tracked + .iter() + .map(|(id, handle)| (*id, handle.stats(), handle.name())) + .collect() + }; + + for (id, stats, name) in snapshots { + let raw_state = raw_state_from_stats(&stats); + let live = live_stats_from_stats(&stats); + + let (final_state, prev_state_proto, percent, bytes_delta, state_changed) = self + .diff_and_update_cache( + id, + raw_state, + stats.progress_bytes, + live.peers, + stats.total_bytes, + ) + .await; + let progress = db::Progress { name: name.as_deref(), total_bytes: i64::try_from(stats.total_bytes).unwrap_or(i64::MAX), downloaded_bytes: i64::try_from(stats.progress_bytes).unwrap_or(i64::MAX), - state, + state: final_state, error_message: stats.error.as_deref(), }; - if let Err(err) = db::update_progress(&self.pool, *id, progress).await { - error!(id = %id, error = %err, "failed to persist torrent progress"); + let row = match db::update_progress(&self.pool, id, progress).await { + Ok(row) => row, + Err(err) => { + error!(id = %id, error = %err, "failed to persist torrent progress"); + continue; + } + }; + + let status = row_to_status(row, live); + + if state_changed { + self.notify(Notification { + observed_at_unix_millis: 0, + kind: NotificationKind::StateChanged as i32, + torrent: Some(status.clone()), + detail: Some(tora_proto::notification_detail::Detail::StateChanged( + StateChanged { + previous: prev_state_proto as i32, + current: status.state, + }, + )), + }); } + + if let Some(delta) = bytes_delta { + self.notify(Notification { + observed_at_unix_millis: 0, + kind: NotificationKind::Progress as i32, + torrent: Some(status), + detail: Some(tora_proto::notification_detail::Detail::Progress( + ProgressMark { + percent, + bytes_delta: delta, + }, + )), + }); + } + } + } + + /// Updates `poll_cache` for `id` from this tick's observation and returns + /// the data needed to emit notifications. Returns + /// `(final_state, previous_proto_state, percent, Option, state_changed)`. + async fn diff_and_update_cache( + &self, + id: Uuid, + raw_state: TorrentState, + progress_bytes: u64, + live_peers: u32, + total_bytes: u64, + ) -> (TorrentState, ProtoState, u32, Option, bool) { + let mut poll_cache = self.poll_cache.lock().await; + let cache = poll_cache + .entry(id) + .or_insert_with(|| PollCache::new(proto_state(raw_state))); + compute_diff(cache, raw_state, progress_bytes, live_peers, total_bytes) + } +} + +/// Pure diff/staleness logic. Updates `cache` in place and returns +/// `(final_state, previous_proto_state, percent, Option, state_changed)`. +/// Extracted from `diff_and_update_cache` so it can be unit-tested without a +/// live TorrentManager (which needs Postgres + librqbit session). +fn compute_diff( + cache: &mut PollCache, + raw_state: TorrentState, + progress_bytes: u64, + live_peers: u32, + total_bytes: u64, +) -> (TorrentState, ProtoState, u32, Option, bool) { + if raw_state == TorrentState::Downloading { + if progress_bytes == cache.last_bytes { + cache.stale_ticks = cache.stale_ticks.saturating_add(1); + } else { + cache.stale_ticks = 0; + } + } else { + cache.stale_ticks = 0; + } + cache.last_bytes = progress_bytes; + + let final_state = if raw_state == TorrentState::Downloading + && cache.stale_ticks >= STALE_TICKS_THRESHOLD + && live_peers == 0 + { + TorrentState::Stale + } else { + raw_state + }; + + let prev_proto = cache.last_state; + let final_proto = proto_state(final_state); + let state_changed = prev_proto != final_proto; + cache.last_state = final_proto; + + // u128 mul to avoid overflow on multi-exabyte totals; cap at 100. + let percent = if total_bytes == 0 { + 0 + } else { + ((progress_bytes as u128 * 100 / total_bytes as u128) as u32).min(100) + }; + let bytes_delta = if percent != cache.last_percent { + let delta = progress_bytes.saturating_sub(cache.bytes_at_last_progress); + cache.last_percent = percent; + cache.bytes_at_last_progress = progress_bytes; + Some(delta) + } else { + None + }; + + (final_state, prev_proto, percent, bytes_delta, state_changed) +} + +fn raw_state_from_stats(stats: &librqbit::TorrentStats) -> TorrentState { + if stats.finished { + TorrentState::Finished + } else { + match stats.state { + TorrentStatsState::Initializing => TorrentState::Pending, + TorrentStatsState::Live => TorrentState::Downloading, + TorrentStatsState::Paused => TorrentState::Paused, + TorrentStatsState::Error => TorrentState::Error, } } } @@ -301,6 +454,34 @@ struct LiveTorrentStats { seeds: u32, } +#[derive(Clone)] +struct PollCache { + last_state: ProtoState, + last_percent: u32, + last_bytes: u64, + bytes_at_last_progress: u64, + stale_ticks: u32, +} + +impl PollCache { + fn new(state: ProtoState) -> Self { + Self { + last_state: state, + last_percent: 0, + last_bytes: 0, + bytes_at_last_progress: 0, + stale_ticks: 0, + } + } +} + +fn now_millis() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|d| d.as_millis() as u64) + .unwrap_or(0) +} + async fn persist_error(pool: &PgPool, id: Uuid, message: &str) { let progress = db::Progress { name: None, @@ -318,10 +499,13 @@ fn extract_live_stats(tracked: &HashMap>, id: &Uuid) - let Some(handle) = tracked.get(id) else { return LiveTorrentStats::default(); }; - let stats = handle.stats(); + live_stats_from_stats(&handle.stats()) +} + +fn live_stats_from_stats(stats: &librqbit::TorrentStats) -> LiveTorrentStats { let uploaded_bytes = stats.uploaded_bytes; - let Some(live) = stats.live else { + let Some(live) = stats.live.as_ref() else { return LiveTorrentStats { uploaded_bytes, ..Default::default() @@ -364,6 +548,7 @@ fn proto_state(state: TorrentState) -> ProtoState { TorrentState::Paused => ProtoState::Paused, TorrentState::Finished => ProtoState::Finished, TorrentState::Error => ProtoState::Error, + TorrentState::Stale => ProtoState::Stale, } } @@ -419,9 +604,18 @@ impl Torrents for GrpcTorrents { .add(&req.magnet, output_dir) .await .map_err(|err| Status::internal(err.to_string()))?; - Ok(Response::new(AddResponse { - id: row.id.to_string(), - })) + let id_str = row.id.to_string(); + let status = row_to_status(row, LiveTorrentStats::default()); + let source = status.source.clone(); + self.manager.notify(Notification { + observed_at_unix_millis: 0, + kind: NotificationKind::TorrentAdded as i32, + torrent: Some(status), + detail: Some(tora_proto::notification_detail::Detail::Added(AddedInfo { + source, + })), + }); + Ok(Response::new(AddResponse { id: id_str })) } async fn status( @@ -468,6 +662,17 @@ impl Torrents for GrpcTorrents { .map_err(|err| Status::internal(err.to_string()))? .ok_or_else(|| Status::not_found("torrent not found"))?; info!(id = %id, name = ?row.name, "torrent removed"); + let status = row_to_status(row, LiveTorrentStats::default()); + self.manager.notify(Notification { + observed_at_unix_millis: 0, + kind: NotificationKind::TorrentRemoved as i32, + torrent: Some(status), + detail: Some(tora_proto::notification_detail::Detail::Removed( + tora_proto::RemovedInfo { + files_deleted: req.delete_files, + }, + )), + }); Ok(Response::new(RemoveResponse {})) } @@ -508,4 +713,263 @@ impl Torrents for GrpcTorrents { info!(id = %id, name = ?row.name, "torrent resumed"); Ok(Response::new(ResumeResponse {})) } + + type NotificationsStream = + tokio_stream::wrappers::ReceiverStream>; + + async fn notifications( + &self, + request: Request, + ) -> Result, Status> { + use tokio::sync::mpsc; + use tokio_stream::wrappers::ReceiverStream; + + let req = request.into_inner(); + + // Resolve id-prefix filter once at subscribe time. If any prefix fails + // to resolve, the whole subscription is rejected — partial filters + // would surprise the client. + let allowed_ids: Option> = if req.ids.is_empty() { + None + } else { + let mut set = HashSet::new(); + for id_str in &req.ids { + let uuid = self.manager.resolve_id(id_str).await.map_err(resolve_err)?; + set.insert(uuid); + } + Some(set) + }; + + let allowed_kinds: Option> = if req.kinds.is_empty() { + None + } else { + Some(req.kinds.iter().copied().collect()) + }; + + let mut rx = self.manager.subscribe(); + let (tx, rx_stream) = mpsc::channel::>(64); + + tokio::spawn(async move { + loop { + match rx.recv().await { + Ok(notification) => { + if let Some(ref allowed) = allowed_ids + && let Some(ref torrent) = notification.torrent + && Uuid::parse_str(&torrent.id) + .ok() + .filter(|id| allowed.contains(id)) + .is_none() + { + continue; + } + if let Some(ref kinds) = allowed_kinds + && !kinds.contains(¬ification.kind) + { + continue; + } + if tx.send(Ok(notification)).await.is_err() { + break; + } + } + Err(broadcast::error::RecvError::Lagged(n)) => { + warn!(skipped = n, "notifications subscriber lagged"); + continue; + } + Err(broadcast::error::RecvError::Closed) => break, + } + } + }); + + Ok(Response::new(ReceiverStream::new(rx_stream))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// A `PollCache` primed as though a prior tick already recorded + /// `bytes` at `total` size, in the Downloading state. The first + /// `compute_diff` call from a test then represents the first + /// *observation* after that baseline (not an artificial advance from 0). + fn primed_cache(bytes: u64, total: u64) -> PollCache { + let percent = if total == 0 { + 0 + } else { + ((bytes as u128 * 100 / total as u128) as u32).min(100) + }; + PollCache { + last_state: ProtoState::Downloading, + last_percent: percent, + last_bytes: bytes, + bytes_at_last_progress: bytes, + stale_ticks: 0, + } + } + + /// `(final_state, state_changed, percent, bytes_delta)` + fn simplify( + out: (TorrentState, ProtoState, u32, Option, bool), + ) -> (TorrentState, bool, u32, Option) { + (out.0, out.4, out.2, out.3) + } + + #[test] + fn below_stale_threshold_stays_downloading() { + let mut cache = primed_cache(100_000, 1_000_000); + // Tick 1: stale_ticks=1. + let out = compute_diff(&mut cache, TorrentState::Downloading, 100_000, 0, 1_000_000); + assert_eq!(simplify(out), (TorrentState::Downloading, false, 10, None)); + // Tick 2: stale_ticks=2. + let out = compute_diff(&mut cache, TorrentState::Downloading, 100_000, 0, 1_000_000); + assert_eq!(simplify(out), (TorrentState::Downloading, false, 10, None)); + } + + #[test] + fn stale_triggers_at_threshold_with_zero_peers() { + let mut cache = primed_cache(100_000, 1_000_000); + // Tick 1: stale_ticks=1. + compute_diff(&mut cache, TorrentState::Downloading, 100_000, 0, 1_000_000); + // Tick 2: stale_ticks=2. + compute_diff(&mut cache, TorrentState::Downloading, 100_000, 0, 1_000_000); + // Tick 3: stale_ticks=3 → STALE. + let out = compute_diff(&mut cache, TorrentState::Downloading, 100_000, 0, 1_000_000); + assert_eq!(out.0, TorrentState::Stale); + assert_eq!(out.1, ProtoState::Downloading); + assert!(out.4); + } + + #[test] + fn peers_above_zero_prevents_stale() { + let mut cache = primed_cache(100_000, 1_000_000); + for _ in 0..10 { + let out = compute_diff(&mut cache, TorrentState::Downloading, 100_000, 1, 1_000_000); + assert_eq!(out.0, TorrentState::Downloading); + } + } + + #[test] + fn bytes_advance_resets_stale_counter() { + let mut cache = primed_cache(100_000, 1_000_000); + // Two no-progress ticks bring stale_ticks to 2. + compute_diff(&mut cache, TorrentState::Downloading, 100_000, 0, 1_000_000); + compute_diff(&mut cache, TorrentState::Downloading, 100_000, 0, 1_000_000); + // Tick 3: bytes advance to 200 — stale_ticks resets to 0, stays Downloading. + let out = compute_diff(&mut cache, TorrentState::Downloading, 200_000, 0, 1_000_000); + assert_eq!(out.0, TorrentState::Downloading); + assert!(!out.4); + // Tick 4: no progress again, stale_ticks=1 (not threshold). + let out = compute_diff(&mut cache, TorrentState::Downloading, 200_000, 0, 1_000_000); + assert_eq!(out.0, TorrentState::Downloading); + } + + #[test] + fn stale_recovers_on_bytes_advance() { + let mut cache = primed_cache(100_000, 1_000_000); + // Reach STALE state. + for _ in 0..3 { + compute_diff(&mut cache, TorrentState::Downloading, 100_000, 0, 1_000_000); + } + assert_eq!(cache.last_state, ProtoState::Stale); + // Bytes advance — should recover to Downloading and emit state_changed. + let out = compute_diff(&mut cache, TorrentState::Downloading, 200_000, 0, 1_000_000); + assert_eq!(out.0, TorrentState::Downloading); + assert_eq!(out.1, ProtoState::Stale); + assert!(out.4); + } + + #[test] + fn stale_recovers_on_peer_connect() { + let mut cache = primed_cache(100_000, 1_000_000); + // Reach STALE state. + for _ in 0..3 { + compute_diff(&mut cache, TorrentState::Downloading, 100_000, 0, 1_000_000); + } + assert_eq!(cache.last_state, ProtoState::Stale); + // Peer connects (bytes unchanged). + let out = compute_diff(&mut cache, TorrentState::Downloading, 100_000, 1, 1_000_000); + assert_eq!(out.0, TorrentState::Downloading); + assert_eq!(out.1, ProtoState::Stale); + assert!(out.4); + } + + #[test] + fn stale_persists_when_nothing_changes() { + let mut cache = primed_cache(100_000, 1_000_000); + for _ in 0..3 { + compute_diff(&mut cache, TorrentState::Downloading, 100_000, 0, 1_000_000); + } + assert_eq!(cache.last_state, ProtoState::Stale); + // Subsequent tick: still no progress, still no peers. + let out = compute_diff(&mut cache, TorrentState::Downloading, 100_000, 0, 1_000_000); + assert_eq!(out.0, TorrentState::Stale); + assert!(!out.4); + } + + #[test] + fn pause_and_resume_emit_state_change() { + let mut cache = primed_cache(100_000, 1_000_000); + // Pause: raw_state changes from Downloading to Paused. + let out = compute_diff(&mut cache, TorrentState::Paused, 100_000, 0, 1_000_000); + assert_eq!(out.0, TorrentState::Paused); + assert!(out.4); + // Resume: back to Downloading. + let out = compute_diff(&mut cache, TorrentState::Downloading, 100_000, 0, 1_000_000); + assert_eq!(out.0, TorrentState::Downloading); + assert!(out.4); + } + + #[test] + fn finished_takes_precedence_over_stale() { + let mut cache = primed_cache(100_000, 1_000_000); + let out = compute_diff(&mut cache, TorrentState::Finished, 1_000_000, 0, 1_000_000); + assert_eq!(out.0, TorrentState::Finished); + assert!(out.4); + } + + #[test] + fn percent_change_emits_progress_with_delta() { + let mut cache = primed_cache(0, 1000); + // 0% → 10% (bytes from 0 to 100 of 1000). + let out = compute_diff(&mut cache, TorrentState::Downloading, 100, 1, 1000); + assert_eq!(out.2, 10); + assert_eq!(out.3, Some(100)); + // 10% → 50% (bytes from 100 to 500 of 1000). + let out = compute_diff(&mut cache, TorrentState::Downloading, 500, 1, 1000); + assert_eq!(out.2, 50); + assert_eq!(out.3, Some(400)); + } + + #[test] + fn no_progress_emit_when_percent_unchanged() { + let mut cache = primed_cache(0, 1000); + // 0% → 10%. + compute_diff(&mut cache, TorrentState::Downloading, 100, 1, 1000); + // 10% again (bytes from 100 to 109, still 10%). + let out = compute_diff(&mut cache, TorrentState::Downloading, 109, 1, 1000); + assert_eq!(out.2, 10); + assert_eq!(out.3, None); + } + + #[test] + fn zero_total_bytes_gives_zero_percent() { + let mut cache = primed_cache(0, 0); + let out = compute_diff(&mut cache, TorrentState::Downloading, 0, 0, 0); + assert_eq!(out.2, 0); + assert_eq!(out.3, None); + } + + #[test] + fn first_tick_initializes_state_without_false_event() { + let mut cache = PollCache::new(ProtoState::Pending); + let out = compute_diff(&mut cache, TorrentState::Pending, 0, 0, 0); + assert!(!out.4); + } + + #[test] + fn percent_caps_at_100_on_full_download() { + let mut cache = primed_cache(0, 1000); + let out = compute_diff(&mut cache, TorrentState::Downloading, 1000, 1, 1000); + assert_eq!(out.2, 100); + } } diff --git a/db/schema.sql b/db/schema.sql index d6d36ab..bd961c6 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -3,7 +3,8 @@ CREATE TYPE torrent_state AS ENUM ( 'downloading', 'paused', 'finished', - 'error' + 'error', + 'stale' ); CREATE TABLE torrents ( diff --git a/proto/torrent.proto b/proto/torrent.proto index e865975..754d903 100644 --- a/proto/torrent.proto +++ b/proto/torrent.proto @@ -15,6 +15,10 @@ service Torrents { rpc Pause(PauseRequest) returns (PauseResponse); // Resume a paused torrent. rpc Resume(ResumeRequest) returns (ResumeResponse); + // Subscribe to a live stream of torrent notifications. Events are delivered + // from the subscribe point onward — no history is replayed. New subscribers + // only see events that fire after they connect. + rpc Notifications(NotificationsRequest) returns (stream Notification); } message AddRequest { @@ -56,6 +60,69 @@ message ResumeRequest { message ResumeResponse {} +message NotificationsRequest { + // If non-empty, only events for these torrent ids (or id prefixes, as + // accepted by `Status`/`Remove`) are delivered. + repeated string ids = 1; + // If non-empty, only these event kinds are delivered. An empty list means + // all kinds. + repeated NotificationKind kinds = 2; +} + +message Notification { + // Wall-clock time the daemon observed the change, in milliseconds since the + // Unix epoch. + uint64 observed_at_unix_millis = 1; + // What changed. Use this to dispatch; use the `oneof detail` for + // kind-specific payload. + NotificationKind kind = 2; + // Full snapshot of the torrent *after* the change. Always populated so + // consumers don't need to fan out to `Status` for context. + TorrentStatus torrent = 3; + oneof detail { + StateChanged state_changed = 10; + ProgressMark progress = 11; + AddedInfo added = 12; + RemovedInfo removed = 13; + } +} + +enum NotificationKind { + NOTIFICATION_KIND_UNSPECIFIED = 0; + // Emitted once when a torrent is added via `Add`. + TORRENT_ADDED = 1; + // Emitted once when a torrent is removed via `Remove`. + TORRENT_REMOVED = 2; + // Emitted on any `State` transition, including PAUSED <-> DOWNLOADING, + // DOWNLOADING <-> STALE, * -> FINISHED, * -> ERROR. + STATE_CHANGED = 3; + // Emitted when the integer download percentage changes (rate-limited). + // No event is sent if the percentage did not advance since the last tick. + PROGRESS = 4; +} + +message StateChanged { + State previous = 1; + State current = 2; +} + +message ProgressMark { + // Current integer percentage 0..=100. + uint32 percent = 1; + // Bytes downloaded since the previous PROGRESS event for this torrent. + uint64 bytes_delta = 2; +} + +message AddedInfo { + // Source string as provided to `Add` (magnet, URL, or file path). + string source = 1; +} + +message RemovedInfo { + // True if `Remove` was called with `delete_files = true`. + bool files_deleted = 1; +} + enum State { STATE_UNSPECIFIED = 0; PENDING = 1; @@ -63,6 +130,12 @@ enum State { PAUSED = 3; FINISHED = 4; ERROR = 5; + // Live per librqbit, but the daemon has detected a stall: no byte progress + // for several ticks AND zero connected peers. The torrent is still + // considered live by the underlying engine; this state is an application- + // level overlay. Recovers to DOWNLOADING automatically when bytes advance + // or a peer connects. + STALE = 6; } message TorrentStatus {