1 // Copyright (C) 2023 Huawei Device Co., Ltd. 2 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // you may not use this file except in compliance with the License. 4 // You may obtain a copy of the License at 5 // 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 use std::collections::{HashMap, HashSet}; 15 use std::fs::File; 16 use std::os::fd::{FromRawFd, IntoRawFd, RawFd}; 17 18 pub use ffi::{Action, Mode}; 19 use ipc::IpcStatusCode; 20 21 cfg_oh! { 22 use ipc::parcel::Serialize; 23 use ipc::parcel::Deserialize; 24 } 25 26 use super::reason::Reason; 27 use super::ATOMIC_SERVICE; 28 use crate::manage::account::GetOhosAccountUid; 29 use crate::manage::network::{NetworkState, NetworkType}; 30 use crate::utils::c_wrapper::{CFileSpec, CFormItem, CStringWrapper}; 31 use crate::utils::form_item::{FileSpec, FormItem}; 32 use crate::utils::{hashmap_to_string, query_calling_bundle}; 33 34 #[cxx::bridge(namespace = "OHOS::Request")] 35 mod ffi { 36 /// Action 37 #[derive(Clone, Copy, PartialEq, Debug)] 38 #[repr(u8)] 39 pub enum Action { 40 /// Download 41 Download = 0, 42 /// Upload 43 Upload, 44 /// Any 45 Any, 46 } 47 48 /// Mode 49 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] 50 #[repr(u8)] 51 pub enum Mode { 52 /// BackGround 53 BackGround = 0, 54 /// ForeGround 55 FrontEnd, 56 /// Any 57 Any, 58 } 59 } 60 61 #[derive(Clone, Copy, PartialEq, Debug)] 62 #[repr(u8)] 63 pub(crate) enum Version { 64 API9 = 1, 65 API10, 66 } 67 68 /// NetworkConfig 69 #[derive(Clone, Copy, PartialEq, Debug)] 70 #[repr(u8)] 71 pub enum NetworkConfig { 72 /// Any 73 Any = 0, 74 /// Wifi 75 Wifi, 76 /// Cellular 77 Cellular, 78 } 79 80 /// task min speed 81 #[derive(Copy, Clone, Debug, Default)] 82 pub struct MinSpeed { 83 pub(crate) speed: i64, 84 pub(crate) duration: i64, 85 } 86 87 /// task Timeout 88 #[derive(Copy, Clone, Debug, Default)] 89 pub struct Timeout { 90 pub(crate) connection_timeout: u64, 91 pub(crate) total_timeout: u64, 92 } 93 94 #[repr(C)] 95 #[derive(Copy, Clone, Debug)] 96 pub(crate) struct CommonTaskConfig { 97 pub(crate) task_id: u32, 98 pub(crate) uid: u64, 99 pub(crate) token_id: u64, 100 pub(crate) action: Action, 101 pub(crate) mode: Mode, 102 pub(crate) cover: bool, 103 pub(crate) network_config: NetworkConfig, 104 pub(crate) metered: bool, 105 pub(crate) roaming: bool, 106 pub(crate) retry: bool, 107 pub(crate) redirect: bool, 108 pub(crate) index: u32, 109 pub(crate) begins: u64, 110 pub(crate) ends: i64, 111 pub(crate) gauge: bool, 112 pub(crate) precise: bool, 113 pub(crate) priority: u32, 114 pub(crate) background: bool, 115 pub(crate) multipart: bool, 116 pub(crate) min_speed: MinSpeed, 117 pub(crate) timeout: Timeout, 118 } 119 120 /// task config 121 #[derive(Clone, Debug)] 122 pub struct TaskConfig { 123 pub(crate) bundle: String, 124 pub(crate) bundle_type: u32, 125 pub(crate) atomic_account: String, 126 pub(crate) url: String, 127 pub(crate) title: String, 128 pub(crate) description: String, 129 pub(crate) method: String, 130 pub(crate) headers: HashMap<String, String>, 131 pub(crate) data: String, 132 pub(crate) token: String, 133 pub(crate) proxy: String, 134 pub(crate) certificate_pins: String, 135 pub(crate) extras: HashMap<String, String>, 136 pub(crate) version: Version, 137 pub(crate) form_items: Vec<FormItem>, 138 pub(crate) file_specs: Vec<FileSpec>, 139 pub(crate) body_file_paths: Vec<String>, 140 pub(crate) certs_path: Vec<String>, 141 pub(crate) common_data: CommonTaskConfig, 142 } 143 144 impl TaskConfig { satisfy_network(&self, network: &NetworkState) -> Result<(), Reason>145 pub(crate) fn satisfy_network(&self, network: &NetworkState) -> Result<(), Reason> { 146 // NetworkConfig::Cellular with NetworkType::Wifi is allowed 147 match network { 148 NetworkState::Offline => Err(Reason::NetworkOffline), 149 NetworkState::Online(info) => match self.common_data.network_config { 150 NetworkConfig::Any => Ok(()), 151 NetworkConfig::Wifi if info.network_type == NetworkType::Cellular => { 152 Err(Reason::UnsupportedNetworkType) 153 } 154 _ => { 155 if (self.common_data.roaming || !info.is_roaming) 156 && (self.common_data.metered || !info.is_metered) 157 { 158 Ok(()) 159 } else { 160 Err(Reason::UnsupportedNetworkType) 161 } 162 } 163 }, 164 } 165 } 166 satisfy_foreground(&self, foreground_abilities: &HashSet<u64>) -> bool167 pub(crate) fn satisfy_foreground(&self, foreground_abilities: &HashSet<u64>) -> bool { 168 self.common_data.mode == Mode::BackGround 169 || foreground_abilities.contains(&self.common_data.uid) 170 } 171 } 172 173 pub(crate) struct ConfigSet { 174 pub(crate) headers: String, 175 pub(crate) extras: String, 176 pub(crate) form_items: Vec<CFormItem>, 177 pub(crate) file_specs: Vec<CFileSpec>, 178 pub(crate) body_file_names: Vec<CStringWrapper>, 179 pub(crate) certs_path: Vec<CStringWrapper>, 180 } 181 182 impl PartialOrd for Mode { partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering>183 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { 184 Some(self.cmp(other)) 185 } 186 } 187 188 impl Ord for Mode { cmp(&self, other: &Self) -> std::cmp::Ordering189 fn cmp(&self, other: &Self) -> std::cmp::Ordering { 190 self.to_usize().cmp(&other.to_usize()) 191 } 192 } 193 194 impl Mode { to_usize(self) -> usize195 fn to_usize(self) -> usize { 196 match self { 197 Mode::FrontEnd => 0, 198 Mode::Any => 1, 199 Mode::BackGround => 2, 200 _ => unreachable!(), 201 } 202 } 203 } 204 205 impl From<u8> for Mode { from(value: u8) -> Self206 fn from(value: u8) -> Self { 207 match value { 208 0 => Mode::BackGround, 209 1 => Mode::FrontEnd, 210 _ => Mode::Any, 211 } 212 } 213 } 214 215 impl From<u8> for Action { from(value: u8) -> Self216 fn from(value: u8) -> Self { 217 match value { 218 0 => Action::Download, 219 1 => Action::Upload, 220 _ => Action::Any, 221 } 222 } 223 } 224 225 impl From<u8> for Version { from(value: u8) -> Self226 fn from(value: u8) -> Self { 227 match value { 228 2 => Version::API10, 229 _ => Version::API9, 230 } 231 } 232 } 233 234 impl From<u8> for NetworkConfig { from(value: u8) -> Self235 fn from(value: u8) -> Self { 236 match value { 237 0 => NetworkConfig::Any, 238 2 => NetworkConfig::Cellular, 239 _ => NetworkConfig::Wifi, 240 } 241 } 242 } 243 244 impl TaskConfig { build_config_set(&self) -> ConfigSet245 pub(crate) fn build_config_set(&self) -> ConfigSet { 246 ConfigSet { 247 headers: hashmap_to_string(&self.headers), 248 extras: hashmap_to_string(&self.extras), 249 form_items: self.form_items.iter().map(|x| x.to_c_struct()).collect(), 250 file_specs: self.file_specs.iter().map(|x| x.to_c_struct()).collect(), 251 body_file_names: self 252 .body_file_paths 253 .iter() 254 .map(CStringWrapper::from) 255 .collect(), 256 certs_path: self.certs_path.iter().map(CStringWrapper::from).collect(), 257 } 258 } 259 contains_user_file(&self) -> bool260 pub(crate) fn contains_user_file(&self) -> bool { 261 for specs in self.file_specs.iter() { 262 if specs.is_user_file { 263 return true; 264 } 265 } 266 false 267 } 268 } 269 270 impl Default for TaskConfig { default() -> Self271 fn default() -> Self { 272 Self { 273 bundle_type: 0, 274 atomic_account: "ohosAnonymousUid".to_string(), 275 bundle: "xxx".to_string(), 276 url: "".to_string(), 277 title: "xxx".to_string(), 278 description: "xxx".to_string(), 279 method: "GET".to_string(), 280 headers: Default::default(), 281 data: "".to_string(), 282 token: "xxx".to_string(), 283 proxy: "".to_string(), 284 extras: Default::default(), 285 version: Version::API10, 286 form_items: vec![], 287 file_specs: vec![], 288 body_file_paths: vec![], 289 certs_path: vec![], 290 certificate_pins: "".to_string(), 291 common_data: CommonTaskConfig { 292 task_id: 0, 293 uid: 0, 294 token_id: 0, 295 action: Action::Download, 296 mode: Mode::BackGround, 297 cover: false, 298 network_config: NetworkConfig::Any, 299 metered: false, 300 roaming: false, 301 retry: false, 302 redirect: true, 303 index: 0, 304 begins: 0, 305 ends: -1, 306 gauge: false, 307 precise: false, 308 priority: 0, 309 background: false, 310 multipart: false, 311 min_speed: MinSpeed::default(), 312 timeout: Timeout::default(), 313 }, 314 } 315 } 316 } 317 318 /// ConfigBuilder 319 pub struct ConfigBuilder { 320 inner: TaskConfig, 321 } 322 323 impl ConfigBuilder { 324 /// Create a new ConfigBuilder new() -> Self325 pub fn new() -> Self { 326 Self { 327 inner: TaskConfig::default(), 328 } 329 } 330 /// Set url url(&mut self, url: &str) -> &mut Self331 pub fn url(&mut self, url: &str) -> &mut Self { 332 self.inner.url = url.to_string(); 333 self 334 } 335 336 /// set version version(&mut self, version: u8) -> &mut Self337 pub fn version(&mut self, version: u8) -> &mut Self { 338 self.inner.version = version.into(); 339 self 340 } 341 342 /// Set title file_spec(&mut self, file: File) -> &mut Self343 pub fn file_spec(&mut self, file: File) -> &mut Self { 344 self.inner.file_specs.push(FileSpec::user_file(file)); 345 self 346 } 347 /// Set action action(&mut self, action: Action) -> &mut Self348 pub fn action(&mut self, action: Action) -> &mut Self { 349 self.inner.common_data.action = action; 350 self 351 } 352 353 /// Set mode mode(&mut self, mode: Mode) -> &mut Self354 pub fn mode(&mut self, mode: Mode) -> &mut Self { 355 self.inner.common_data.mode = mode; 356 self 357 } 358 359 /// Set bundle name bundle_name(&mut self, bundle_name: &str) -> &mut Self360 pub fn bundle_name(&mut self, bundle_name: &str) -> &mut Self { 361 self.inner.bundle = bundle_name.to_string(); 362 self 363 } 364 365 /// Set uid uid(&mut self, uid: u64) -> &mut Self366 pub fn uid(&mut self, uid: u64) -> &mut Self { 367 self.inner.common_data.uid = uid; 368 self 369 } 370 371 /// set network network(&mut self, network: NetworkConfig) -> &mut Self372 pub fn network(&mut self, network: NetworkConfig) -> &mut Self { 373 self.inner.common_data.network_config = network; 374 self 375 } 376 377 /// Set metered roaming(&mut self, roaming: bool) -> &mut Self378 pub fn roaming(&mut self, roaming: bool) -> &mut Self { 379 self.inner.common_data.roaming = roaming; 380 self 381 } 382 383 /// set metered metered(&mut self, metered: bool) -> &mut Self384 pub fn metered(&mut self, metered: bool) -> &mut Self { 385 self.inner.common_data.metered = metered; 386 self 387 } 388 389 /// build build(&mut self) -> TaskConfig390 pub fn build(&mut self) -> TaskConfig { 391 self.inner.clone() 392 } 393 394 /// redirect redirect(&mut self, redirect: bool) -> &mut Self395 pub fn redirect(&mut self, redirect: bool) -> &mut Self { 396 self.inner.common_data.redirect = redirect; 397 self 398 } 399 400 /// begins begins(&mut self, begins: u64) -> &mut Self401 pub fn begins(&mut self, begins: u64) -> &mut Self { 402 self.inner.common_data.begins = begins; 403 self 404 } 405 406 /// ends ends(&mut self, ends: u64) -> &mut Self407 pub fn ends(&mut self, ends: u64) -> &mut Self { 408 self.inner.common_data.ends = ends as i64; 409 self 410 } 411 412 /// method method(&mut self, metered: &str) -> &mut Self413 pub fn method(&mut self, metered: &str) -> &mut Self { 414 self.inner.method = metered.to_string(); 415 self 416 } 417 418 /// retry retry(&mut self, retry: bool) -> &mut Self419 pub fn retry(&mut self, retry: bool) -> &mut Self { 420 self.inner.common_data.retry = retry; 421 self 422 } 423 } 424 425 #[cfg(feature = "oh")] 426 impl Serialize for TaskConfig { serialize(&self, parcel: &mut ipc::parcel::MsgParcel) -> ipc::IpcResult<()>427 fn serialize(&self, parcel: &mut ipc::parcel::MsgParcel) -> ipc::IpcResult<()> { 428 parcel.write(&(self.common_data.action.repr as u32))?; 429 parcel.write(&(self.version as u32))?; 430 parcel.write(&(self.common_data.mode.repr as u32))?; 431 parcel.write(&self.bundle_type)?; 432 parcel.write(&self.common_data.cover)?; 433 parcel.write(&(self.common_data.network_config as u32))?; 434 parcel.write(&(self.common_data.metered))?; 435 parcel.write(&self.common_data.roaming)?; 436 parcel.write(&(self.common_data.retry))?; 437 parcel.write(&(self.common_data.redirect))?; 438 parcel.write(&(self.common_data.background))?; 439 parcel.write(&(self.common_data.multipart))?; 440 parcel.write(&self.common_data.index)?; 441 parcel.write(&(self.common_data.begins as i64))?; 442 parcel.write(&self.common_data.ends)?; 443 parcel.write(&self.common_data.gauge)?; 444 parcel.write(&self.common_data.precise)?; 445 parcel.write(&self.common_data.priority)?; 446 parcel.write(&self.common_data.min_speed.speed)?; 447 parcel.write(&self.common_data.min_speed.duration)?; 448 parcel.write(&self.common_data.timeout.connection_timeout)?; 449 parcel.write(&self.common_data.timeout.total_timeout)?; 450 parcel.write(&self.url)?; 451 parcel.write(&self.title)?; 452 parcel.write(&self.method)?; 453 parcel.write(&self.token)?; 454 parcel.write(&self.description)?; 455 parcel.write(&self.data)?; 456 parcel.write(&self.proxy)?; 457 parcel.write(&self.certificate_pins)?; 458 459 parcel.write(&(self.certs_path.len() as u32))?; 460 for cert_path in &self.certs_path { 461 parcel.write(cert_path)?; 462 } 463 464 parcel.write(&(self.form_items.len() as u32))?; 465 for form_item in &self.form_items { 466 parcel.write(&form_item.name)?; 467 parcel.write(&form_item.value)?; 468 } 469 parcel.write(&(self.file_specs.len() as u32))?; 470 for file_spec in &self.file_specs { 471 parcel.write(&file_spec.name)?; 472 parcel.write(&file_spec.path)?; 473 parcel.write(&file_spec.file_name)?; 474 parcel.write(&file_spec.mime_type)?; 475 parcel.write(&file_spec.is_user_file)?; 476 if file_spec.is_user_file { 477 let file = unsafe { File::from_raw_fd(file_spec.fd.unwrap()) }; 478 parcel.write_file(file)?; 479 } 480 } 481 482 parcel.write(&(self.body_file_paths.len() as u32))?; 483 for body_file_paths in self.body_file_paths.iter() { 484 parcel.write(body_file_paths)?; 485 } 486 parcel.write(&(self.headers.len() as u32))?; 487 for header in self.headers.iter() { 488 parcel.write(header.0)?; 489 parcel.write(header.1)?; 490 } 491 492 parcel.write(&(self.extras.len() as u32))?; 493 for extra in self.extras.iter() { 494 parcel.write(extra.0)?; 495 parcel.write(extra.1)?; 496 } 497 498 Ok(()) 499 } 500 } 501 502 #[cfg(feature = "oh")] 503 impl Deserialize for TaskConfig { deserialize(parcel: &mut ipc::parcel::MsgParcel) -> ipc::IpcResult<Self>504 fn deserialize(parcel: &mut ipc::parcel::MsgParcel) -> ipc::IpcResult<Self> { 505 let action: u32 = parcel.read()?; 506 let action: Action = Action::from(action as u8); 507 let version: u32 = parcel.read()?; 508 let version: Version = Version::from(version as u8); 509 let mode: u32 = parcel.read()?; 510 let mode: Mode = Mode::from(mode as u8); 511 let bundle_type: u32 = parcel.read()?; 512 let cover: bool = parcel.read()?; 513 let network: u32 = parcel.read()?; 514 let network_config = NetworkConfig::from(network as u8); 515 let metered: bool = parcel.read()?; 516 let roaming: bool = parcel.read()?; 517 let retry: bool = parcel.read()?; 518 let redirect: bool = parcel.read()?; 519 let background: bool = parcel.read()?; 520 let multipart: bool = parcel.read()?; 521 let index: u32 = parcel.read()?; 522 let begins: i64 = parcel.read()?; 523 let ends: i64 = parcel.read()?; 524 let gauge: bool = parcel.read()?; 525 let precise: bool = parcel.read()?; 526 let priority: u32 = parcel.read()?; 527 let min_speed: i64 = parcel.read()?; 528 let min_duration: i64 = parcel.read()?; 529 let connection_timeout: u64 = parcel.read()?; 530 let total_timeout: u64 = parcel.read()?; 531 let url: String = parcel.read()?; 532 let title: String = parcel.read()?; 533 let method: String = parcel.read()?; 534 let token: String = parcel.read()?; 535 let description: String = parcel.read()?; 536 let data_base: String = parcel.read()?; 537 let proxy: String = parcel.read()?; 538 let certificate_pins: String = parcel.read()?; 539 let bundle = query_calling_bundle(); 540 let uid = ipc::Skeleton::calling_uid(); 541 let token_id = ipc::Skeleton::calling_full_token_id(); 542 let certs_path_size: u32 = parcel.read()?; 543 if certs_path_size > parcel.readable() as u32 { 544 error!("deserialize failed: certs_path_size too large"); 545 sys_event!( 546 ExecFault, 547 DfxCode::INVALID_IPC_MESSAGE_A00, 548 "deserialize failed: certs_path_size too large" 549 ); 550 return Err(IpcStatusCode::Failed); 551 } 552 let mut certs_path = Vec::new(); 553 for _ in 0..certs_path_size { 554 let cert_path: String = parcel.read()?; 555 certs_path.push(cert_path); 556 } 557 558 let form_size: u32 = parcel.read()?; 559 if form_size > parcel.readable() as u32 { 560 error!("deserialize failed: form_size too large"); 561 sys_event!( 562 ExecFault, 563 DfxCode::INVALID_IPC_MESSAGE_A00, 564 "deserialize failed: form_size too large" 565 ); 566 return Err(IpcStatusCode::Failed); 567 } 568 let mut form_items = Vec::new(); 569 for _ in 0..form_size { 570 let name: String = parcel.read()?; 571 let value: String = parcel.read()?; 572 form_items.push(FormItem { name, value }); 573 } 574 575 let file_size: u32 = parcel.read()?; 576 if file_size > parcel.readable() as u32 { 577 error!("deserialize failed: file_specs size too large"); 578 sys_event!( 579 ExecFault, 580 DfxCode::INVALID_IPC_MESSAGE_A00, 581 "deserialize failed: file_specs size too large" 582 ); 583 return Err(IpcStatusCode::Failed); 584 } 585 let mut file_specs: Vec<FileSpec> = Vec::new(); 586 for _ in 0..file_size { 587 let name: String = parcel.read()?; 588 let path: String = parcel.read()?; 589 let file_name: String = parcel.read()?; 590 let mime_type: String = parcel.read()?; 591 let is_user_file: bool = parcel.read()?; 592 let mut fd: Option<RawFd> = None; 593 if is_user_file { 594 let raw_fd = unsafe { parcel.read_raw_fd() }; 595 if raw_fd < 0 { 596 error!("Failed to open user file, fd: {}", raw_fd); 597 sys_event!( 598 ExecFault, 599 DfxCode::INVALID_IPC_MESSAGE_A00, 600 "deserialize failed: failed to open user file" 601 ); 602 return Err(IpcStatusCode::Failed); 603 } 604 let ipc_fd = unsafe { File::from_raw_fd(raw_fd) }; 605 fd = Some(ipc_fd.into_raw_fd()); 606 } 607 file_specs.push(FileSpec { 608 name, 609 path, 610 file_name, 611 mime_type, 612 is_user_file, 613 fd, 614 }); 615 } 616 617 // Response bodies fd. 618 let body_file_size: u32 = parcel.read()?; 619 if body_file_size > parcel.readable() as u32 { 620 error!("deserialize failed: body_file size too large"); 621 sys_event!( 622 ExecFault, 623 DfxCode::INVALID_IPC_MESSAGE_A00, 624 "deserialize failed: body_file size too large" 625 ); 626 return Err(IpcStatusCode::Failed); 627 } 628 629 let mut body_file_paths: Vec<String> = Vec::new(); 630 for _ in 0..body_file_size { 631 let file_name: String = parcel.read()?; 632 body_file_paths.push(file_name); 633 } 634 635 let header_size: u32 = parcel.read()?; 636 if header_size > parcel.readable() as u32 { 637 error!("deserialize failed: header size too large"); 638 sys_event!( 639 ExecFault, 640 DfxCode::INVALID_IPC_MESSAGE_A00, 641 "deserialize failed: header size too large" 642 ); 643 return Err(IpcStatusCode::Failed); 644 } 645 let mut headers: HashMap<String, String> = HashMap::new(); 646 for _ in 0..header_size { 647 let key: String = parcel.read()?; 648 let value: String = parcel.read()?; 649 headers.insert(key, value); 650 } 651 652 let extras_size: u32 = parcel.read()?; 653 if extras_size > parcel.readable() as u32 { 654 error!("deserialize failed: extras size too large"); 655 sys_event!( 656 ExecFault, 657 DfxCode::INVALID_IPC_MESSAGE_A00, 658 "deserialize failed: extras size too large" 659 ); 660 return Err(IpcStatusCode::Failed); 661 } 662 let mut extras: HashMap<String, String> = HashMap::new(); 663 for _ in 0..extras_size { 664 let key: String = parcel.read()?; 665 let value: String = parcel.read()?; 666 extras.insert(key, value); 667 } 668 669 let atomic_account = if bundle_type == ATOMIC_SERVICE { 670 GetOhosAccountUid() 671 } else { 672 "".to_string() 673 }; 674 675 let task_config = TaskConfig { 676 bundle, 677 bundle_type, 678 atomic_account, 679 url, 680 title, 681 description, 682 method, 683 headers, 684 data: data_base, 685 token, 686 proxy, 687 certificate_pins, 688 extras, 689 version, 690 form_items, 691 file_specs, 692 body_file_paths, 693 certs_path, 694 common_data: CommonTaskConfig { 695 task_id: 0, 696 uid, 697 token_id, 698 action, 699 mode, 700 cover, 701 network_config, 702 metered, 703 roaming, 704 retry, 705 redirect, 706 index, 707 begins: begins as u64, 708 ends, 709 gauge, 710 precise, 711 priority, 712 background, 713 multipart, 714 min_speed: MinSpeed { 715 speed: min_speed, 716 duration: min_duration, 717 }, 718 timeout: Timeout { 719 connection_timeout, 720 total_timeout, 721 }, 722 }, 723 }; 724 Ok(task_config) 725 } 726 } 727 728 #[cfg(test)] 729 mod ut_config { 730 include!("../../tests/ut/task/ut_config.rs"); 731 } 732