1use crate::integrity::{self, integrity_algorithm};
6use crate::key::exchange::Key;
7use crate::keywrap::{self, keywrap_algorithm};
8
9use crate::{Error, ProtectionInfo, rsn_ensure};
10use anyhow::{anyhow, ensure};
11use fidl_fuchsia_wlan_mlme::SaeFrame;
12use wlan_common::ie::RsnxeOctet1;
13use wlan_common::ie::rsn::akm::Akm;
14use wlan_common::ie::rsn::cipher::{CIPHER_BIP_CMAC_128, Cipher, GROUP_CIPHER_SUITE, TKIP};
15use wlan_common::ie::rsn::rsne::{RsnCapabilities, Rsne};
16use wlan_common::ie::wpa::WpaIe;
17
18use zerocopy::SplitByteSlice;
19
20pub mod esssa;
21#[cfg(test)]
22pub mod test_util;
23
24#[derive(Debug, Clone, PartialEq)]
25pub enum ProtectionType {
26 LegacyWpa1,
27 Rsne,
28}
29
30#[derive(Debug)]
31pub enum IgtkSupport {
32 Unsupported,
33 Capable,
34 Required,
35}
36
37#[derive(Debug, Clone, PartialEq)]
38pub struct NegotiatedProtection {
39 pub group_data: Cipher,
40 pub pairwise: Cipher,
41 pub group_mgmt: Option<Cipher>,
42 pub akm: Akm,
43 pub mic_size: u16,
44 pub protection_type: ProtectionType,
45 caps: Option<RsnCapabilities>,
48 pub rsnxe: Option<RsnxeOctet1>,
49}
50
51impl NegotiatedProtection {
52 pub fn from_protection(protection: &ProtectionInfo) -> Result<Self, anyhow::Error> {
53 match protection {
54 ProtectionInfo::Rsne { rsne, rsnxe } => Self::from_rsne(rsne, *rsnxe),
55 ProtectionInfo::LegacyWpa(wpa) => Self::from_legacy_wpa(wpa),
56 }
57 }
58
59 fn key_descriptor_version(&self) -> u16 {
60 let key_descriptor_type = match self.protection_type {
61 ProtectionType::LegacyWpa1 => eapol::KeyDescriptor::LEGACY_WPA1,
62 ProtectionType::Rsne => eapol::KeyDescriptor::IEEE802DOT11,
63 };
64 derive_key_descriptor_version(key_descriptor_type, self)
65 }
66
67 #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
68 pub fn integrity_algorithm(&self) -> Result<Box<dyn integrity::Algorithm>, Error> {
69 integrity_algorithm(self.key_descriptor_version(), &self.akm)
70 .ok_or(Error::UnknownIntegrityAlgorithm)
71 }
72
73 #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
74 pub fn keywrap_algorithm(&self) -> Result<Box<dyn keywrap::Algorithm>, Error> {
75 keywrap_algorithm(self.key_descriptor_version(), &self.akm)
76 .ok_or(Error::UnknownKeywrapAlgorithm)
77 }
78
79 pub fn from_rsne(rsne: &Rsne, rsnxe: Option<RsnxeOctet1>) -> Result<Self, anyhow::Error> {
82 rsne.ensure_valid_s_rsne()
83 .map_err(|e| anyhow!(e).context(Error::InvalidNegotiatedProtection))?;
84
85 let group_data = rsne.group_data_cipher_suite.as_ref().unwrap();
88 let pairwise = &rsne.pairwise_cipher_suites[0];
89 let akm = &rsne.akm_suites[0];
90 let mic_size = akm.mic_bytes();
91 let mic_size = mic_size.unwrap();
92
93 Ok(Self {
94 group_data: group_data.clone(),
95 pairwise: pairwise.clone(),
96 group_mgmt: rsne.group_mgmt_cipher_suite.clone(),
97 akm: akm.clone(),
98 mic_size,
99 protection_type: ProtectionType::Rsne,
100 caps: rsne.rsn_capabilities.clone(),
101 rsnxe,
102 })
103 }
104
105 pub fn from_legacy_wpa(wpa: &WpaIe) -> Result<Self, anyhow::Error> {
108 ensure!(wpa.unicast_cipher_list.len() == 1, Error::InvalidNegotiatedProtection);
109 ensure!(wpa.akm_list.len() == 1, Error::InvalidNegotiatedProtection);
110 let akm = wpa.akm_list[0].clone();
111 let mic_size = akm.mic_bytes().ok_or(Error::InvalidNegotiatedProtection)?;
112 let group_data = wpa.multicast_cipher.clone();
113 let pairwise = wpa.unicast_cipher_list[0].clone();
114 Ok(Self {
115 group_data,
116 pairwise,
117 group_mgmt: None,
118 akm,
119 mic_size,
120 protection_type: ProtectionType::LegacyWpa1,
121 caps: None,
122 rsnxe: None,
123 })
124 }
125
126 pub fn to_full_protection(&self) -> ProtectionInfo {
129 match self.protection_type {
130 ProtectionType::Rsne => ProtectionInfo::Rsne {
131 rsne: Rsne {
132 group_data_cipher_suite: Some(self.group_data.clone()),
133 pairwise_cipher_suites: vec![self.pairwise.clone()],
134 group_mgmt_cipher_suite: self.group_mgmt.clone(),
135 akm_suites: vec![self.akm.clone()],
136 rsn_capabilities: self.caps.clone(),
137 ..Default::default()
138 },
139 rsnxe: self.rsnxe,
140 },
141 ProtectionType::LegacyWpa1 => ProtectionInfo::LegacyWpa(WpaIe {
142 multicast_cipher: self.group_data.clone(),
143 unicast_cipher_list: vec![self.pairwise.clone()],
144 akm_list: vec![self.akm.clone()],
145 }),
146 }
147 }
148
149 pub fn igtk_support(&self) -> IgtkSupport {
150 match &self.caps {
151 Some(caps) => {
152 if caps.mgmt_frame_protection_req() {
153 IgtkSupport::Required
154 } else if caps.mgmt_frame_protection_cap() {
155 IgtkSupport::Capable
156 } else {
157 IgtkSupport::Unsupported
158 }
159 }
160 None => IgtkSupport::Unsupported,
161 }
162 }
163
164 pub fn group_mgmt_cipher(&self) -> Cipher {
165 self.group_mgmt.clone().unwrap_or(CIPHER_BIP_CMAC_128)
167 }
168}
169
170pub struct EncryptedKeyData<B: SplitByteSlice>(eapol::KeyFrameRx<B>);
172
173impl<B: SplitByteSlice> EncryptedKeyData<B> {
174 #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
175 pub fn decrypt(
178 self,
179 kek: &[u8],
180 protection: &NegotiatedProtection,
181 ) -> Result<(eapol::KeyFrameRx<B>, Vec<u8>), Error> {
182 let key_data = protection.keywrap_algorithm()?.unwrap_key(
183 kek,
184 &self.0.key_frame_fields.key_iv,
185 &self.0.key_data[..],
186 )?;
187 Ok((self.0, key_data))
188 }
189}
190
191#[derive(Debug)]
193pub struct WithUnverifiedMic<B: SplitByteSlice>(eapol::KeyFrameRx<B>);
194
195impl<B: SplitByteSlice> WithUnverifiedMic<B> {
196 #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
197 pub fn verify_mic(
201 self,
202 kck: &[u8],
203 protection: &NegotiatedProtection,
204 ) -> Result<UnverifiedKeyData<B>, Error> {
205 let mic_bytes = protection.akm.mic_bytes().ok_or(Error::UnsupportedAkmSuite)?;
208 rsn_ensure!(self.0.key_mic.len() == mic_bytes as usize, Error::InvalidMicSize);
209
210 let buf = self.0.to_bytes(true);
212 let valid_mic =
213 protection.integrity_algorithm()?.verify(kck, &buf[..], &self.0.key_mic[..]);
214 rsn_ensure!(valid_mic, Error::InvalidMic);
215
216 if self.0.key_frame_fields.key_info().encrypted_key_data() {
217 Ok(UnverifiedKeyData::Encrypted(EncryptedKeyData(self.0)))
218 } else {
219 Ok(UnverifiedKeyData::NotEncrypted(self.0))
220 }
221 }
222}
223
224pub enum UnverifiedKeyData<B: SplitByteSlice> {
227 Encrypted(EncryptedKeyData<B>),
228 NotEncrypted(eapol::KeyFrameRx<B>),
229}
230
231#[derive(Debug)]
234pub enum Dot11VerifiedKeyFrame<B: SplitByteSlice> {
235 WithUnverifiedMic(WithUnverifiedMic<B>),
236 WithoutMic(eapol::KeyFrameRx<B>),
237}
238
239impl<B: SplitByteSlice> Dot11VerifiedKeyFrame<B> {
240 #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
248 pub fn from_frame(
249 frame: eapol::KeyFrameRx<B>,
250 role: &Role,
251 protection: &NegotiatedProtection,
252 key_replay_counter: u64,
253 ) -> Result<Dot11VerifiedKeyFrame<B>, Error> {
254 let sender = match role {
255 Role::Supplicant => Role::Authenticator,
256 Role::Authenticator => Role::Supplicant,
257 };
258
259 let key_descriptor = match frame.key_frame_fields.descriptor_type {
262 eapol::KeyDescriptor::IEEE802DOT11 => eapol::KeyDescriptor::IEEE802DOT11,
263 eapol::KeyDescriptor::LEGACY_WPA1
264 if protection.protection_type == ProtectionType::LegacyWpa1 =>
265 {
266 eapol::KeyDescriptor::LEGACY_WPA1
267 }
268 eapol::KeyDescriptor::RC4 => {
269 return Err(Error::InvalidKeyDescriptor(
270 frame.key_frame_fields.descriptor_type,
271 eapol::KeyDescriptor::IEEE802DOT11,
272 )
273 .into());
274 }
275 _ => {
277 return Err(Error::UnsupportedKeyDescriptor(
278 frame.key_frame_fields.descriptor_type,
279 )
280 .into());
281 }
282 };
283
284 let frame_key_descriptor_version =
286 frame.key_frame_fields.key_info().key_descriptor_version();
287 let expected_version = derive_key_descriptor_version(key_descriptor, protection);
288 rsn_ensure!(
289 frame_key_descriptor_version == expected_version,
290 Error::UnsupportedKeyDescriptorVersion(frame_key_descriptor_version)
291 );
292
293 match frame.key_frame_fields.key_info().key_type() {
296 eapol::KeyType::PAIRWISE => {}
297 eapol::KeyType::GROUP_SMK => {
298 rsn_ensure!(
300 !frame.key_frame_fields.key_info().install(),
301 Error::InvalidInstallBitGroupSmkHandshake
302 );
303 }
304 };
305
306 if let Role::Supplicant = sender {
308 rsn_ensure!(
309 !frame.key_frame_fields.key_info().key_ack(),
310 Error::InvalidKeyAckBitSupplicant
311 );
312 }
313
314 if let Role::Authenticator = sender {
322 rsn_ensure!(
323 !frame.key_frame_fields.key_info().error(),
324 Error::InvalidErrorBitAuthenticator
325 );
326 }
327
328 if let Role::Authenticator = sender {
330 rsn_ensure!(
331 !frame.key_frame_fields.key_info().request(),
332 Error::InvalidRequestBitAuthenticator
333 );
334 }
335
336 rsn_ensure!(
341 !frame.key_frame_fields.key_info().smk_message(),
342 Error::SmkHandshakeNotSupported
343 );
344
345 match frame.key_frame_fields.key_info().key_type() {
347 eapol::KeyType::PAIRWISE => match sender {
348 Role::Supplicant if frame.key_frame_fields.key_len.get() != 0 => {
357 let tk_len =
358 protection.pairwise.tk_bytes().ok_or(Error::UnsupportedCipherSuite)?;
359 rsn_ensure!(
360 frame.key_frame_fields.key_len.get() == u16::from(tk_len),
361 Error::InvalidKeyLength(
362 frame.key_frame_fields.key_len.get().into(),
363 tk_len.into()
364 )
365 );
366 }
367 Role::Authenticator => {
369 let tk_len: usize =
370 protection.pairwise.tk_bytes().ok_or(Error::UnsupportedCipherSuite)?.into();
371 rsn_ensure!(
372 usize::from(frame.key_frame_fields.key_len.get()) == tk_len,
373 Error::InvalidKeyLength(
374 frame.key_frame_fields.key_len.get().into(),
375 tk_len
376 )
377 );
378 }
379 _ => {}
380 },
381 eapol::KeyType::GROUP_SMK => {}
385 };
386
387 if key_replay_counter > 0 {
388 match sender {
389 Role::Supplicant => {
392 rsn_ensure!(
393 frame.key_frame_fields.key_replay_counter.get() >= key_replay_counter,
394 Error::InvalidKeyReplayCounter(
395 frame.key_frame_fields.key_replay_counter.get(),
396 key_replay_counter
397 )
398 );
399 }
400 Role::Authenticator => {
406 rsn_ensure!(
407 frame.key_frame_fields.key_replay_counter.get() > key_replay_counter,
408 Error::InvalidKeyReplayCounter(
409 frame.key_frame_fields.key_replay_counter.get(),
410 key_replay_counter
411 )
412 );
413 }
414 }
415 }
416
417 if frame.key_frame_fields.key_info().encrypted_key_data() {
420 rsn_ensure!(
421 frame.key_frame_fields.key_info().key_mic(),
422 Error::InvalidMicBitForEncryptedKeyData
423 );
424 }
425
426 if frame.key_frame_fields.key_info().key_mic() {
445 Ok(Dot11VerifiedKeyFrame::WithUnverifiedMic(WithUnverifiedMic(frame)))
446 } else {
447 Ok(Dot11VerifiedKeyFrame::WithoutMic(frame))
448 }
449 }
450
451 pub fn unsafe_get_raw(&self) -> &eapol::KeyFrameRx<B> {
455 match self {
456 Dot11VerifiedKeyFrame::WithUnverifiedMic(WithUnverifiedMic(frame)) => frame,
457 Dot11VerifiedKeyFrame::WithoutMic(frame) => frame,
458 }
459 }
460}
461
462pub fn derive_key_descriptor_version(
465 key_descriptor_type: eapol::KeyDescriptor,
466 protection: &NegotiatedProtection,
467) -> u16 {
468 let akm = &protection.akm;
469 let pairwise = &protection.pairwise;
470
471 if !akm.has_known_algorithm() || !pairwise.has_known_usage() {
472 return 0;
473 }
474
475 match akm.suite_type {
476 1 | 2 => match key_descriptor_type {
477 eapol::KeyDescriptor::RC4 => match pairwise.suite_type {
478 TKIP | GROUP_CIPHER_SUITE => 1,
479 _ => 0,
480 },
481 eapol::KeyDescriptor::IEEE802DOT11 | eapol::KeyDescriptor::LEGACY_WPA1 => {
482 if pairwise.suite_type == TKIP || pairwise.suite_type == GROUP_CIPHER_SUITE {
483 1
484 } else if pairwise.is_enhanced() || protection.group_data.is_enhanced() {
485 2
486 } else {
487 0
488 }
489 }
490 _ => 0,
491 },
492 3..=6 => 3,
495 _ => 0,
496 }
497}
498
499#[derive(Debug, Clone, Copy, PartialEq)]
500pub enum Role {
501 Authenticator,
502 Supplicant,
503}
504
505#[derive(Debug, PartialEq, Clone, Copy)]
506pub enum SecAssocStatus {
507 WrongPassword,
508 PmkSaEstablished,
509 EssSaEstablished,
510}
511
512#[derive(Debug, PartialEq, Clone, Copy)]
513pub enum AuthRejectedReason {
514 AuthFailed,
516 TooManyRetries,
518 PmksaExpired,
520}
521
522#[derive(Debug, PartialEq, Clone, Copy)]
523pub enum AuthStatus {
524 Success,
525 Rejected(AuthRejectedReason),
526 InternalError,
527}
528
529#[derive(Debug, PartialEq, Clone)]
530pub enum SecAssocUpdate {
531 TxEapolKeyFrame {
532 frame: eapol::KeyFrameBuf,
533 expect_response: bool,
536 },
537 Key(Key),
538 Status(SecAssocStatus),
539 TxSaeFrame(SaeFrame),
541 SaeAuthStatus(AuthStatus),
542 ScheduleSaeTimeout(u64),
543 TxOwePublicKey {
545 group_id: u16,
546 key: Vec<u8>,
547 },
548}
549
550pub type UpdateSink = Vec<SecAssocUpdate>;
551
552#[cfg(test)]
553mod tests {
554 use super::*;
555 use assert_matches::assert_matches;
556 use wlan_common::ie::rsn::akm::{self, AKM_PSK};
557 use wlan_common::ie::rsn::cipher::{self, CIPHER_CCMP_128, CIPHER_GCMP_256};
558 use wlan_common::ie::rsn::fake_wpa2_s_rsne;
559 use zerocopy::byteorder::big_endian::U16;
560
561 #[test]
562 fn test_negotiated_protection_from_rsne() {
563 let rsne = Rsne {
564 group_data_cipher_suite: Some(CIPHER_GCMP_256),
565 pairwise_cipher_suites: vec![CIPHER_CCMP_128],
566 akm_suites: vec![AKM_PSK],
567 ..Default::default()
568 };
569 NegotiatedProtection::from_rsne(&rsne, None)
570 .expect("error, could not create negotiated RSNE");
571
572 let rsne = Rsne::wpa3_rsne();
573 NegotiatedProtection::from_rsne(&rsne, None)
574 .expect("error, could not create negotiated RSNE");
575
576 let rsne = Rsne {
577 pairwise_cipher_suites: vec![CIPHER_CCMP_128],
578 akm_suites: vec![AKM_PSK],
579 ..Default::default()
580 };
581 NegotiatedProtection::from_rsne(&rsne, None).expect_err("error, created negotiated RSNE");
582
583 let rsne = Rsne {
584 group_data_cipher_suite: Some(CIPHER_CCMP_128),
585 akm_suites: vec![AKM_PSK],
586 ..Default::default()
587 };
588 NegotiatedProtection::from_rsne(&rsne, None).expect_err("error, created negotiated RSNE");
589
590 let rsne = Rsne {
591 group_data_cipher_suite: Some(CIPHER_CCMP_128),
592 pairwise_cipher_suites: vec![CIPHER_CCMP_128],
593 ..Default::default()
594 };
595 NegotiatedProtection::from_rsne(&rsne, None).expect_err("error, created negotiated RSNE");
596 }
597
598 #[test]
602 fn test_supplicant_sends_zeroed_and_non_zeroed_key_length() {
603 let protection = NegotiatedProtection::from_rsne(&fake_wpa2_s_rsne(), None)
604 .expect("could not derive negotiated RSNE");
605 let mut env = test_util::FourwayTestEnv::new(test_util::HandshakeKind::Wpa2, 1, 3);
606
607 let msg1 = env.initiate(11.into());
609 let (msg2_base, ptk) = env.send_msg1_to_supplicant(msg1.keyframe(), 11.into());
610
611 let mut buf = vec![];
613 let mut msg2 = msg2_base.copy_keyframe_mut(&mut buf);
614 msg2.key_frame_fields.key_len = U16::new(0);
615 env.finalize_key_frame(&mut msg2, Some(ptk.kck()));
616 let result = Dot11VerifiedKeyFrame::from_frame(msg2, &Role::Authenticator, &protection, 12);
617 assert!(result.is_ok(), "failed verifying message: {}", result.unwrap_err());
618
619 let mut buf = vec![];
622 let mut msg2 = msg2_base.copy_keyframe_mut(&mut buf);
623 msg2.key_frame_fields.key_len = U16::new(16);
624 env.finalize_key_frame(&mut msg2, Some(ptk.kck()));
625 let result = Dot11VerifiedKeyFrame::from_frame(msg2, &Role::Authenticator, &protection, 12);
626 assert!(result.is_ok(), "failed verifying message: {}", result.unwrap_err());
627 }
628
629 #[test]
632 fn test_supplicant_sends_random_key_length() {
633 let mut env = test_util::FourwayTestEnv::new(test_util::HandshakeKind::Wpa2, 1, 3);
634
635 let msg1 = env.initiate(12.into());
637 let (msg2, ptk) = env.send_msg1_to_supplicant(msg1.keyframe(), 12.into());
638 let mut buf = vec![];
639 let mut msg2 = msg2.copy_keyframe_mut(&mut buf);
640
641 msg2.key_frame_fields.key_len = U16::new(29);
642 env.finalize_key_frame(&mut msg2, Some(ptk.kck()));
643
644 let protection = NegotiatedProtection::from_rsne(&fake_wpa2_s_rsne(), None)
645 .expect("could not derive negotiated RSNE");
646 let result = Dot11VerifiedKeyFrame::from_frame(msg2, &Role::Authenticator, &protection, 12);
647 assert!(result.is_err(), "successfully verified illegal message");
648 }
649
650 #[test]
651 fn test_to_rsne() {
652 let rsne = Rsne::wpa2_rsne();
653 let negotiated_protection = NegotiatedProtection::from_rsne(&rsne, None)
654 .expect("error, could not create negotiated RSNE")
655 .to_full_protection();
656 assert_matches!(negotiated_protection, ProtectionInfo::Rsne { rsne: actual_protection, rsnxe: None } => {
657 assert_eq!(actual_protection, rsne);
658 });
659 }
660
661 #[test]
662 fn test_to_rsne_with_rsnxe() {
663 let rsne = Rsne::wpa3_rsne();
664 let rsnxe = Some(RsnxeOctet1(0x20));
665 let protection = ProtectionInfo::Rsne { rsne: rsne.clone(), rsnxe };
666 let negotiated_protection = NegotiatedProtection::from_protection(&protection)
667 .expect("error, could not create negotiated RSNE")
668 .to_full_protection();
669 assert_eq!(negotiated_protection, protection);
670 }
671
672 #[test]
673 fn test_to_legacy_wpa() {
674 let wpa_ie = make_wpa(Some(cipher::TKIP), vec![cipher::TKIP], vec![akm::PSK]);
675 let negotiated_protection = NegotiatedProtection::from_legacy_wpa(&wpa_ie)
676 .expect("error, could not create negotiated WPA")
677 .to_full_protection();
678 assert_matches!(negotiated_protection, ProtectionInfo::LegacyWpa(actual_protection) => {
679 assert_eq!(actual_protection, wpa_ie);
680 });
681 }
682
683 #[test]
684 fn test_igtk_support() {
685 let rsne = Rsne::wpa3_rsne();
687 let negotiated_protection =
688 NegotiatedProtection::from_rsne(&rsne, None).expect("Could not create negotiated RSNE");
689 assert_matches!(negotiated_protection.igtk_support(), IgtkSupport::Required);
690 assert_eq!(negotiated_protection.group_mgmt_cipher(), CIPHER_BIP_CMAC_128);
691
692 let mut rsne = Rsne::wpa3_rsne();
694 rsne.rsn_capabilities.replace(RsnCapabilities(0).with_mgmt_frame_protection_cap(true));
695 let negotiated_protection =
696 NegotiatedProtection::from_rsne(&rsne, None).expect("Could not create negotiated RSNE");
697 assert_matches!(negotiated_protection.igtk_support(), IgtkSupport::Capable);
698
699 let rsne = Rsne::wpa2_rsne();
701 let negotiated_protection =
702 NegotiatedProtection::from_rsne(&rsne, None).expect("Could not create negotiated RSNE");
703 assert_matches!(negotiated_protection.igtk_support(), IgtkSupport::Unsupported);
704 }
705
706 #[test]
707 fn test_default_igtk_cipher() {
708 let mut rsne = Rsne::wpa3_rsne();
709 rsne.group_mgmt_cipher_suite.take(); let negotiated_protection =
711 NegotiatedProtection::from_rsne(&rsne, None).expect("Could not create negotiated RSNE");
712 assert_matches!(negotiated_protection.igtk_support(), IgtkSupport::Required);
713 assert_eq!(negotiated_protection.group_mgmt_cipher(), CIPHER_BIP_CMAC_128);
714 }
715
716 fn make_wpa(unicast: Option<u8>, multicast: Vec<u8>, akms: Vec<u8>) -> WpaIe {
717 WpaIe {
718 multicast_cipher: unicast
719 .map(cipher::Cipher::new_dot11)
720 .expect("failed to make wpa ie!"),
721 unicast_cipher_list: multicast.into_iter().map(cipher::Cipher::new_dot11).collect(),
722 akm_list: akms.into_iter().map(akm::Akm::new_dot11).collect(),
723 }
724 }
725}