cloudpub_sdk/lib.rs
1//! # CloudPub Rust SDK
2//!
3//! CloudPub Rust SDK предоставляет простой интерфейс для взаимодействия с
4//! платформой CloudPub. CloudPub обеспечивает безопасное туннелирование и публикацию сервисов,
5//! позволяя безопасно открывать локальные сервисы в интернет.
6//!
7//! ## Возможности
8//!
9//! - **Публикация сервисов**: Открытие локальных сервисов (HTTP, HTTPS, TCP, UDP, WebSocket, RTSP) в интернет
10//! - **Безопасное туннелирование**: Все соединения шифруются и аутентифицируются
11//! - **Несколько протоколов**: Поддержка различных протоколов, включая HTTP/HTTPS, TCP/UDP, WebSocket и RTSP
12//! - **Аутентификация**: Встроенная поддержка аутентификации на основе токенов и учетных данных
13//! - **Управление сервисами**: Запуск, остановка, список и управление опубликованными сервисами
14//! - **Событийно-ориентированная архитектура**: Поддержка async/await с управлением состоянием на основе событий
15//!
16//! ## Быстрый старт
17//!
18//! Добавьте SDK в ваш `Cargo.toml`:
19//!
20//! ```toml
21//! [dependencies]
22//! cloudpub-sdk = "2.4.2"
23//! ```
24//!
25//! ## Базовое использование
26//!
27//! ### Простая публикация сервиса
28//!
29//! ```no_run
30//! use cloudpub_sdk::Connection;
31//! use cloudpub_sdk::protocol::{Protocol, Auth, Endpoint};
32//!
33//! #[tokio::main]
34//! async fn main() -> anyhow::Result<()> {
35//! // Создание соединения с учетными данными
36//! let mut conn = Connection::builder()
37//! .credentials("user@example.com", "password")
38//! .build()
39//! .await?;
40//!
41//! // Публикация локального веб-сервиса
42//! let endpoint = conn.publish(
43//! Protocol::Http,
44//! "localhost:3000".to_string(),
45//! Some("My Web App".to_string()),
46//! Some(Auth::None),
47//! None, // acl
48//! None, // headers
49//! None, // rules
50//! ).await?;
51//!
52//! println!("Service published at: {}", endpoint.as_url());
53//!
54//! // Ожидание сигнала Ctrl+C для остановки
55//! tokio::signal::ctrl_c().await?;
56//!
57//! // Очистка
58//! conn.unpublish(endpoint.guid).await?;
59//! Ok(())
60//! }
61//! ```
62//!
63//! ### Расширенная публикация сервиса с ACL, заголовками и правилами фильтрации
64//!
65//! ```no_run
66//! use cloudpub_sdk::Connection;
67//! use cloudpub_sdk::protocol::{
68//! Protocol, Auth, Endpoint, Acl, Header, FilterRule,
69//! Role, FilterAction
70//! };
71//!
72//! #[tokio::main]
73//! async fn main() -> anyhow::Result<()> {
74//! let mut conn = Connection::builder()
75//! .credentials("admin@example.com", "password")
76//! .build()
77//! .await?;
78//!
79//! // Настройка списка контроля доступа
80//! let acl = vec![
81//! Acl { user: "admin@example.com".to_string(), role: Role::Admin as i32 },
82//! Acl { user: "user@example.com".to_string(), role: Role::Reader as i32 },
83//! Acl { user: "writer@example.com".to_string(), role: Role::Writer as i32 },
84//! ];
85//!
86//! // Добавление пользовательских HTTP заголовков
87//! let headers = vec![
88//! Header { name: "X-API-Version".to_string(), value: "2.0".to_string() },
89//! Header { name: "Access-Control-Allow-Origin".to_string(), value: "*".to_string() },
90//! Header { name: "X-Custom-Header".to_string(), value: "CloudPub".to_string() },
91//! ];
92//!
93//! // Определение правил фильтрации для обработки запросов
94//! let rules = vec![
95//! FilterRule {
96//! order: 0,
97//! action_type: FilterAction::FilterAllow as i32,
98//! action_value: None,
99//! data: "http.path starts_with \"/api/\" and http.headers[\"X-API-Key\"] contains \"valid\"".to_string(),
100//! },
101//! FilterRule {
102//! order: 1,
103//! action_type: FilterAction::FilterDeny as i32,
104//! action_value: None,
105//! data: "http.path matches \"^/admin.*\" and not (ip.src >= 192.168.1.0 and ip.src <= 192.168.1.255)".to_string(),
106//! },
107//! FilterRule {
108//! order: 2,
109//! action_type: FilterAction::FilterRedirect as i32,
110//! action_value: Some("https://api.example.com/v2/".to_string()), // URL перенаправления
111//! data: "http.path starts_with \"/api/v1/\"".to_string(),
112//! },
113//! ];
114//!
115//! // Публикация сервиса со всеми расширенными возможностями
116//! let endpoint = conn.publish(
117//! Protocol::Https,
118//! "localhost:8443".to_string(),
119//! Some("Secured API Server".to_string()),
120//! Some(Auth::Basic),
121//! Some(acl),
122//! Some(headers),
123//! Some(rules),
124//! ).await?;
125//!
126//! println!("Advanced service published at: {}", endpoint.as_url());
127//!
128//! Ok(())
129//! }
130//! ```
131//!
132//! ### Расширенная конфигурация
133//!
134//! ```no_run
135//! # async fn example() -> anyhow::Result<()> {
136//! use cloudpub_sdk::Connection;
137//! use std::time::Duration;
138//!
139//! // Создание соединения с расширенной конфигурацией
140//! let conn = Connection::builder()
141//! .config_path("/custom/path/config.toml") // Пользовательский файл конфигурации
142//! .log_level("debug") // Включить отладочное логирование
143//! .verbose(true) // Вывод логов в консоль
144//! .timeout(Duration::from_secs(60)) // Установить таймаут операции
145//! .token("existing-auth-token") // Использовать существующий токен
146//! .build()
147//! .await?;
148//! # Ok(())
149//! # }
150//! ```
151//!
152//! ## Расширенные возможности
153//!
154//! ### Списки контроля доступа (ACL)
155//!
156//! Контроль доступа к вашим сервисам на основе ролей пользователей:
157//!
158//! ```no_run
159//! # use cloudpub_sdk::protocol::{Acl, Role};
160//! let acl = vec![
161//! Acl {
162//! user: "admin@example.com".to_string(),
163//! role: Role::Admin as i32 // Полный доступ
164//! },
165//! Acl {
166//! user: "reader@example.com".to_string(),
167//! role: Role::Reader as i32 // Доступ только на чтение
168//! },
169//! Acl {
170//! user: "writer@example.com".to_string(),
171//! role: Role::Writer as i32 // Доступ на чтение-запись
172//! },
173//! Acl {
174//! user: "guest@example.com".to_string(),
175//! role: Role::Nobody as i32 // Нет доступа
176//! },
177//! ];
178//! ```
179//!
180//! ### Пользовательские HTTP заголовки
181//!
182//! Добавление пользовательских заголовков в HTTP/HTTPS ответы:
183//!
184//! ```no_run
185//! # use cloudpub_sdk::protocol::Header;
186//! let headers = vec![
187//! Header {
188//! name: "X-API-Version".to_string(),
189//! value: "2.0".to_string()
190//! },
191//! Header {
192//! name: "Access-Control-Allow-Origin".to_string(),
193//! value: "*".to_string()
194//! },
195//! Header {
196//! name: "Cache-Control".to_string(),
197//! value: "no-cache, no-store, must-revalidate".to_string()
198//! },
199//! ];
200//! ```
201//!
202//! ### Правила фильтрации
203//!
204//! Определение правил для управления маршрутизацией запросов и доступом на основе различных параметров соединения.
205//! Правила обрабатываются по порядку (по полю `order`), и первое подходящее правило применяется.
206//!
207//! ```no_run
208//! # use cloudpub_sdk::protocol::{FilterRule, FilterAction};
209//! let rules = vec![
210//! // Разрешить доступ к API только из локальной сети
211//! FilterRule {
212//! order: 0,
213//! action_type: FilterAction::FilterAllow as i32,
214//! action_value: None,
215//! data: "ip.src >= 192.168.1.0 and ip.src <= 192.168.1.255 and http.path starts_with \"/api/\"".to_string(),
216//! },
217//!
218//! // Блокировать доступ к админ-панели снаружи
219//! FilterRule {
220//! order: 1,
221//! action_type: FilterAction::FilterDeny as i32,
222//! action_value: None,
223//! data: "http.path matches \"^/admin.*\"".to_string(),
224//! },
225//!
226//! // Перенаправление старых API эндпоинтов на новую версию
227//! // action_value содержит URL перенаправления для FilterRedirect
228//! FilterRule {
229//! order: 2,
230//! action_type: FilterAction::FilterRedirect as i32,
231//! action_value: Some("https://api.example.com/v2/".to_string()), // Redirect URL
232//! data: "http.path starts_with \"/api/v1/\"".to_string(),
233//! },
234//!
235//! // Блокировать запросы из определенных стран
236//! FilterRule {
237//! order: 3,
238//! action_type: FilterAction::FilterDeny as i32,
239//! action_value: None,
240//! data: "geo.country != \"Россия\"".to_string(),
241//! },
242//!
243//! // Разрешить только аутентифицированные API запросы
244//! FilterRule {
245//! order: 4,
246//! action_type: FilterAction::FilterAllow as i32,
247//! action_value: None,
248//! data: "http.path starts_with \"/api/\" and http.headers[\"Authorization\"] contains \"Bearer\"".to_string(),
249//! },
250//! ];
251//! ```
252//!
253//! **Доступные переменные для фильтр выражений:**
254//! - `ip.src`, `ip.dst` - IP адреса источника и назначения
255//! - `port.src`, `port.dst` - Порты источника и назначения
256//! - `protocol` - Протокол соединения ("tcp", "udp", "http")
257//! - `http.host`, `http.path`, `http.method` - HTTP-специфичные поля
258//! - `http.headers["Header-Name"]` - Доступ к HTTP заголовкам
259//! - `geo.country`, `geo.region`, `geo.city`, `geo.code` - Данные геолокации
260//!
261//! **Операторы:**
262//! - Сравнение: `==`, `!=`, `>`, `<`, `>=`, `<=`
263//! - Строковые: `matches` (regex), `contains`, `starts_with`, `ends_with`
264//! - Логические: `and`, `or`, `not`
265//!
266//! ## Типы сервисов
267//!
268//! CloudPub поддерживает несколько протоколов сервисов:
269//!
270//! ### HTTP/HTTPS сервисы
271//!
272//! ```no_run
273//! # async fn http_example(conn: &mut cloudpub_sdk::Connection) -> anyhow::Result<()> {
274//! use cloudpub_sdk::protocol::{Protocol, Auth, Endpoint};
275//!
276//! // Публикация HTTP сервиса с базовой аутентификацией
277//! let http_endpoint = conn.publish(
278//! Protocol::Http,
279//! "localhost:8080".to_string(),
280//! Some("API Server".to_string()),
281//! Some(Auth::Basic), // Требовать пароль для доступа
282//! None, // acl
283//! None, // headers
284//! None, // rules
285//! ).await?;
286//!
287//! // Публикация HTTPS сервиса
288//! let https_endpoint = conn.publish(
289//! Protocol::Https,
290//! "localhost:8443".to_string(),
291//! Some("Secure API".to_string()),
292//! Some(Auth::Basic), // Требовать токен для доступа
293//! None, // acl
294//! None, // headers
295//! None, // rules
296//! ).await?;
297//! # Ok(())
298//! # }
299//! ```
300//!
301//! ### TCP/UDP сервисы
302//!
303//! ```no_run
304//! # async fn tcp_example(conn: &mut cloudpub_sdk::Connection) -> anyhow::Result<()> {
305//! use cloudpub_sdk::protocol::{Protocol, Auth};
306//!
307//! // Публикация TCP сервиса (например, SSH)
308//! let tcp_endpoint = conn.publish(
309//! Protocol::Tcp,
310//! "localhost:22".to_string(),
311//! Some("SSH Server".to_string()),
312//! Some(Auth::None),
313//! None, // acl
314//! None, // headers
315//! None, // rules
316//! ).await?;
317//!
318//! // Публикация UDP сервиса (например, DNS)
319//! let udp_endpoint = conn.publish(
320//! Protocol::Udp,
321//! "localhost:53".to_string(),
322//! Some("DNS Server".to_string()),
323//! Some(Auth::None),
324//! None, // acl
325//! None, // headers
326//! None, // rules
327//! ).await?;
328//! # Ok(())
329//! # }
330//! ```
331//!
332//! ### WebSocket сервисы
333//!
334//! ```no_run
335//! # async fn ws_example(conn: &mut cloudpub_sdk::Connection) -> anyhow::Result<()> {
336//! use cloudpub_sdk::protocol::{Protocol, Auth};
337//!
338//! // Публикация TCP сервиса
339//! let tcp_endpoint = conn.publish(
340//! Protocol::Tcp,
341//! "localhost:8080".to_string(),
342//! Some("TCP Server".to_string()),
343//! Some(Auth::None),
344//! None, // acl
345//! None, // headers
346//! None, // rules
347//! ).await?;
348//!
349//! // Публикация UDP сервиса
350//! let udp_endpoint = conn.publish(
351//! Protocol::Udp,
352//! "localhost:8443".to_string(),
353//! Some("UDP Server".to_string()),
354//! Some(Auth::Basic),
355//! None, // acl
356//! None, // headers
357//! None, // rules
358//! ).await?;
359//! # Ok(())
360//! # }
361//! ```
362//!
363//! ### RTSP стриминговые сервисы
364//!
365//! ```no_run
366//! # async fn rtsp_example(conn: &mut cloudpub_sdk::Connection) -> anyhow::Result<()> {
367//! use cloudpub_sdk::protocol::{Protocol, Auth};
368//!
369//! // Публикация RTSP потока с учетными данными в URL
370//! // Формат: rtsp://username:password@host:port/path
371//! let rtsp_endpoint = conn.publish(
372//! Protocol::Rtsp,
373//! "rtsp://camera:secret123@192.168.1.100:554/live/stream1".to_string(),
374//! Some("Security Camera".to_string()),
375//! Some(Auth::Basic), // Аутентификация доступа в CloudPub
376//! None, // acl
377//! None, // headers
378//! None, // rules
379//! ).await?;
380//!
381//! // RTSP без учетных данных (публичный поток)
382//! let public_rtsp = conn.publish(
383//! Protocol::Rtsp,
384//! "rtsp://localhost:554/public".to_string(),
385//! Some("Public Stream".to_string()),
386//! Some(Auth::None),
387//! None, // acl
388//! None, // headers
389//! None, // rules
390//! ).await?;
391//! # Ok(())
392//! # }
393//! ```
394//!
395//! ## Управление сервисами
396//!
397//! ### Список сервисов
398//!
399//! ```no_run
400//! # async fn list_example(conn: &mut cloudpub_sdk::Connection) -> anyhow::Result<()> {
401//! use cloudpub_sdk::protocol::Endpoint;
402//! // Получить все зарегистрированные сервисы
403//! let services = conn.ls().await?;
404//!
405//! for service in services {
406//! let name = service.client.as_ref()
407//! .and_then(|c| c.description.clone())
408//! .unwrap_or_else(|| "Без имени".to_string());
409//! println!("Service: {}", name);
410//! println!(" GUID: {}", service.guid);
411//! println!(" URL: {}", service.as_url());
412//! println!(" Status: {}", service.status.unwrap_or_else(|| "Unknown".to_string()));
413//! println!(" Protocol: {:?}", service.remote_proto);
414//! }
415//! # Ok(())
416//! # }
417//! ```
418//!
419//! ### Запуск и остановка сервисов
420//!
421//! ```no_run
422//! # async fn control_example(conn: &mut cloudpub_sdk::Connection) -> anyhow::Result<()> {
423//! // Временно остановить сервис
424//! conn.stop("service-guid-123".to_string()).await?;
425//!
426//! // Перезапустить сервис
427//! conn.start("service-guid-123".to_string()).await?;
428//!
429//! // Постоянно удалить сервис
430//! conn.unpublish("service-guid-123".to_string()).await?;
431//!
432//! // Удалить все сервисы
433//! conn.clean().await?;
434//! # Ok(())
435//! # }
436//! ```
437//!
438//! ## Управление конфигурацией
439//!
440//! ### Чтение и запись опций
441//!
442//! ```no_run
443//! # fn config_example(conn: &cloudpub_sdk::Connection) -> anyhow::Result<()> {
444//! // Установка значений конфигурации
445//! conn.set("server", "api.cloudpub.com")?;
446//! conn.set("port", "443")?;
447//! conn.set("ssl", "true")?;
448//!
449//! // Получение значений конфигурации
450//! let server = conn.get("server")?;
451//! println!("Server: {}", server);
452//!
453//! // Получение всех опций конфигурации
454//! let options = conn.options();
455//! for (key, value) in options {
456//! println!("{}: {}", key, value);
457//! }
458//! # Ok(())
459//! # }
460//! ```
461//!
462//! ### Управление аутентификацией
463//!
464//! ```no_run
465//! # async fn auth_example() -> anyhow::Result<()> {
466//! use cloudpub_sdk::Connection;
467//!
468//! // Аутентификация с учетными данными
469//! let mut conn = Connection::builder()
470//! .credentials("user@example.com", "password")
471//! .build()
472//! .await?;
473//!
474//! // Позже, выход для очистки токена
475//! conn.logout()?;
476//!
477//! // Повторная аутентификация с сохраненным токеном
478//! let conn = Connection::builder()
479//! .token("saved-auth-token")
480//! .build()
481//! .await?;
482//! # Ok(())
483//! # }
484//! ```
485//!
486//! ## Вспомогательные функции
487//!
488//! ### Проверка состояния сервера
489//!
490//! ```no_run
491//! # async fn ping_example(conn: &mut cloudpub_sdk::Connection) -> anyhow::Result<()> {
492//! // Измерение задержки сервера (возвращает микросекунды)
493//! let latency_us = conn.ping().await?;
494//! let latency_ms = latency_us as f64 / 1000.0;
495//! println!("Server latency: {}μs ({:.2}ms)", latency_us, latency_ms);
496//!
497//! if latency_ms > 100.0 {
498//! println!("Warning: High latency detected!");
499//! }
500//! # Ok(())
501//! # }
502//! ```
503//!
504//! ### Управление кэшем
505//!
506//! ```no_run
507//! # fn cache_example(conn: &cloudpub_sdk::Connection) -> anyhow::Result<()> {
508//! // Очистка локального кэша
509//! conn.purge()?;
510//! println!("Cache cleared");
511//! # Ok(())
512//! # }
513//! ```
514//!
515//! ## Обработка ошибок
516//!
517//! Все методы SDK возвращают `Result<T>` для правильной обработки ошибок:
518//!
519//! ```no_run
520//! # async fn error_example() -> anyhow::Result<()> {
521//! use cloudpub_sdk::Connection;
522//!
523//! match Connection::builder()
524//! .credentials("user@example.com", "wrong-password")
525//! .build()
526//! .await
527//! {
528//! Ok(conn) => {
529//! println!("Connected successfully");
530//! },
531//! Err(e) => {
532//! eprintln!("Connection failed: {}", e);
533//! // Обработка специфичных типов ошибок
534//! if e.to_string().contains("authentication") {
535//! eprintln!("Authentication failed. Please check your credentials.");
536//! }
537//! }
538//! }
539//! # Ok(())
540//! # }
541//! ```
542//!
543//! ## Полный пример
544//!
545//! Вот полный пример, демонстрирующий полный жизненный цикл с расширенными возможностями:
546//!
547//! ```no_run
548//! use cloudpub_sdk::Connection;
549//! use cloudpub_sdk::protocol::{
550//! Protocol, Auth, Endpoint, Acl, Header, FilterRule,
551//! Role, FilterAction
552//! };
553//! use std::time::Duration;
554//! use tokio::time::sleep;
555//!
556//! #[tokio::main]
557//! async fn main() -> anyhow::Result<()> {
558//! // Инициализация соединения с конфигурацией
559//! println!("Connecting to CloudPub...");
560//! let mut conn = Connection::builder()
561//! .credentials("admin@example.com", "secure-password")
562//! .log_level("info")
563//! .verbose(true)
564//! .timeout_secs(30)
565//! .build()
566//! .await?;
567//!
568//! println!("Connected successfully!");
569//!
570//! // Публикация нескольких сервисов
571//! println!("\nPublishing services...");
572//!
573//! // Простой веб-сервис без расширенных возможностей
574//! let web_service = conn.publish(
575//! Protocol::Http,
576//! "localhost:3000".to_string(),
577//! Some("Web Application".to_string()),
578//! Some(Auth::None),
579//! None, // Нет ACL ограничений
580//! None, // Нет пользовательских заголовков
581//! None, // Нет правил фильтрации
582//! ).await?;
583//! println!("✓ Web app: {}", web_service.as_url());
584//!
585//! // API сервер с ACL и пользовательскими заголовками
586//! let api_acl = vec![
587//! Acl { user: "api_admin@example.com".to_string(), role: Role::Admin as i32 },
588//! Acl { user: "api_user@example.com".to_string(), role: Role::Reader as i32 },
589//! ];
590//! let api_headers = vec![
591//! Header { name: "X-API-Version".to_string(), value: "2.0".to_string() },
592//! Header { name: "Access-Control-Allow-Origin".to_string(), value: "*".to_string() },
593//! ];
594//! let api_service = conn.publish(
595//! Protocol::Https,
596//! "localhost:8443".to_string(),
597//! Some("API Server".to_string()),
598//! Some(Auth::Basic),
599//! Some(api_acl),
600//! Some(api_headers),
601//! None, // Нет правил фильтрации
602//! ).await?;
603//! println!("✓ API server: {}", api_service.as_url());
604//!
605//! // SSH сервис с правилами фильтрации для безопасности
606//! let ssh_rules = vec![
607//! FilterRule {
608//! order: 0,
609//! action_type: FilterAction::FilterAllow as i32,
610//! action_value: None,
611//! data: "ip.src == 192.168.1.10 or ip.src == 10.0.0.5".to_string(), // Разрешить только определенные IP
612//! },
613//! FilterRule {
614//! order: 1,
615//! action_type: FilterAction::FilterDeny as i32,
616//! action_value: None,
617//! data: "ip.src matches \".*\"".to_string(), // Заблокировать все остальные IP
618//! },
619//! ];
620//! let ssh_service = conn.publish(
621//! Protocol::Tcp,
622//! "localhost:22".to_string(),
623//! Some("SSH Access".to_string()),
624//! Some(Auth::Basic),
625//! None, // Нет ACL
626//! None, // Нет заголовков (TCP не использует HTTP заголовки)
627//! Some(ssh_rules),
628//! ).await?;
629//! println!("✓ SSH: {}", ssh_service.as_url());
630//!
631//! // Проверка состояния сервера
632//! println!("\nChecking server health...");
633//! let latency_us = conn.ping().await?;
634//! println!("Server latency: {}μs ({:.2}ms)", latency_us, latency_us as f64 / 1000.0);
635//!
636//! // Список всех сервисов
637//! println!("\nActive services:");
638//! let services = conn.ls().await?;
639//! for (i, service) in services.iter().enumerate() {
640//! let name = service.client.as_ref()
641//! .and_then(|c| c.description.clone())
642//! .unwrap_or_else(|| "Без имени".to_string());
643//! println!("{}. {} - {}",
644//! i + 1,
645//! name,
646//! service.as_url()
647//! );
648//! }
649//!
650//! // Работать некоторое время
651//! println!("\nServices are running. Press Ctrl+C to stop...");
652//! tokio::select! {
653//! _ = tokio::signal::ctrl_c() => {
654//! println!("\nShutting down...");
655//! }
656//! _ = sleep(Duration::from_secs(3600)) => {
657//! println!("\nTimeout reached");
658//! }
659//! }
660//!
661//! // Очистка
662//! println!("Очистка сервисов...");
663//! for service in services {
664//! conn.unpublish(service.guid).await?;
665//! let name = service.client.as_ref()
666//! .and_then(|c| c.description.clone())
667//! .unwrap_or_else(|| "Без имени".to_string());
668//! println!("✓ Removed: {}", name);
669//! }
670//!
671//! println!("All services stopped. Goodbye!");
672//! Ok(())
673//! }
674//! ```
675//! ## Потокобезопасность
676//!
677//! Структура `Connection` использует внутреннюю синхронизацию и может безопасно использоваться
678//! между потоками через `Arc<Mutex<Connection>>` при необходимости.
679//!
680//! ## Лицензия
681//!
682//! Этот SDK лицензирован под лицензией Apache 2.0.
683
684pub use cloudpub_client::config::ClientOpts;
685pub use cloudpub_common::protocol;
686
687mod builder;
688mod connection;
689
690pub use builder::ConnectionBuilder;
691pub use connection::{CheckSignalFn, Connection, ConnectionEvent};