Skip to main content

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