1pub type Result<T> = std::result::Result<T, Error>;
5
6#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8#[non_exhaustive]
9pub enum MagicContext {
10 File {
12 file: String,
14 magic: Vec<u8>,
16 },
17 MtpDataBlock {
19 block_id: u32,
21 },
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26#[non_exhaustive]
27pub enum MapReadFailure {
28 Open,
30 BadSalt {
32 size: usize,
34 },
35 Decrypt,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42#[non_exhaustive]
43pub enum KeyDataFailure {
44 UnusableWrapParameters {
47 kind: u32,
49 memory: u32,
51 time: u32,
53 parallelism: u32,
55 },
56 BadSalt {
58 size: usize,
60 },
61 BadTrailerMagic,
63 BadWrapCount {
65 count: u32,
67 },
68 BadKdfFamily {
70 kind: u32,
72 },
73 BadWrapSaltSize {
75 size: usize,
77 },
78 DuplicateWrapGeneration {
80 generation: u32,
82 },
83 AccountsInfoDecrypt,
85 BadAccountsCount {
88 count: i32,
90 },
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
97#[non_exhaustive]
98pub enum ConfigFailure {
99 BadIpSize {
102 size: i32,
104 },
105 BadSecretSize {
108 size: i32,
110 },
111 NonAsciiIp {
114 byte: u8,
116 position: usize,
118 },
119 UnencodableIp,
121 BadVersion {
124 version: i32,
126 },
127 BadEnvironment {
130 code: i32,
132 },
133 DcNotFound {
135 dc_id: i32,
137 },
138 NoTcpEndpoint,
140 BadEndpointIp,
142 DcIdOutOfRange {
144 dc_id: i64,
146 },
147 PortOutOfRange {
149 port: i64,
151 },
152 NullSessionColumn {
154 column: &'static str,
156 },
157 NoSelfUser,
159 ConflictingSelfUsers,
161 BadUserId {
163 user_id: i64,
165 },
166}
167
168#[derive(Debug, thiserror::Error)]
178#[non_exhaustive]
179pub enum Error {
180 #[error("tdata file not found")]
182 FileNotFound {
183 file: String,
186 },
187
188 #[error("tdata file has an invalid magic")]
191 InvalidMagic {
192 context: MagicContext,
194 },
195
196 #[error("tdata file has an invalid checksum")]
199 InvalidCheckSum {
200 file: String,
203 computed: [u8; 16],
206 },
207
208 #[error("tdata decrypt key is bad")]
210 BadDecryptKey,
211
212 #[error("tdata passcode is wrong")]
214 WrongPasscode,
215
216 #[error("tdata encrypted data has a bad size")]
219 BadEncryptedDataSize {
220 size: usize,
222 },
223
224 #[error("tdata decrypted data has a bad size")]
227 BadDecryptedDataSize {
228 encrypted_size: usize,
230 full_len: usize,
232 decrypted_size: usize,
234 },
235
236 #[error("tdata config data is bad")]
238 BadConfigData {
239 context: ConfigFailure,
241 },
242
243 #[error("QDataStream operation failed")]
245 QDataStreamFailed {
246 context: Option<&'static str>,
249 },
250
251 #[error("failed to read tdata map data")]
253 ReadMapDataFailed {
254 context: MapReadFailure,
256 },
257
258 #[error("tdata map data passcode is incorrect")]
261 ReadMapDataIncorrectPasscode,
262
263 #[error("tdata auth key not found")]
265 AuthKeyNotFound,
266
267 #[error("maximum number of accounts reached")]
269 MaxAccountLimit,
270
271 #[error("failed to save tdata")]
273 SaveFailed,
274
275 #[error("tdata key data is invalid")]
278 KeyDataInvalid {
279 context: KeyDataFailure,
281 },
282
283 #[error("{what} has a bad length: expected {expected}, got {actual}")]
285 BadLength {
286 what: &'static str,
288 expected: usize,
290 actual: usize,
292 },
293
294 #[error("Invalid {what}: {reason}")]
297 InvalidInput {
298 what: &'static str,
300 reason: String,
302 },
303
304 #[error("I/O error: {0}")]
306 Io(#[from] std::io::Error),
307}
308
309impl Error {
310 #[must_use]
314 pub fn code(&self) -> &'static str {
315 match self {
316 Self::FileNotFound { .. } => "TFILE_NOT_FOUND",
317 Self::InvalidMagic { .. } => "TDATA_INVALID_MAGIC",
318 Self::InvalidCheckSum { .. } => "TDATA_INVALID_CHECKSUM",
319 Self::BadDecryptKey => "TDATA_BAD_DECRYPT_KEY",
320 Self::WrongPasscode => "TDATA_WRONG_PASSCODE",
321 Self::BadEncryptedDataSize { .. } => "TDATA_BAD_ENCRYPTED_DATA_SIZE",
322 Self::BadDecryptedDataSize { .. } => "TDATA_BAD_DECRYPTED_DATA_SIZE",
323 Self::BadConfigData { .. } => "TDATA_BAD_CONFIG_DATA",
324 Self::QDataStreamFailed { .. } => "QDATASTREAM_FAILED",
325 Self::ReadMapDataFailed { .. } => "TDATA_READ_MAP_DATA_FAILED",
326 Self::ReadMapDataIncorrectPasscode => "TDATA_READ_MAP_DATA_INCORRECT_PASSCODE",
327 Self::AuthKeyNotFound => "TDATA_AUTH_KEY_NOT_FOUND",
328 Self::MaxAccountLimit => "MAX_ACCOUNT_LIMIT",
329 Self::SaveFailed => "TDATA_SAVE_FAILED",
330 Self::KeyDataInvalid { .. } => "TDATA_KEY_DATA_INVALID",
331 Self::BadLength { .. } => "BAD_LENGTH",
332 Self::InvalidInput { .. } => "INVALID_INPUT",
333 Self::Io(_) => "IO",
334 }
335 }
336
337 #[must_use]
347 pub fn detail_code(&self) -> Option<&'static str> {
348 match self {
349 Self::KeyDataInvalid {
350 context: KeyDataFailure::BadAccountsCount { .. },
351 } => Some("BAD_ACCOUNTS_COUNT"),
352 _ => None,
353 }
354 }
355
356 #[must_use]
390 pub fn legacy_message(&self) -> String {
391 match self {
392 Self::FileNotFound { file } => format!("Could not open {file}"),
393 Self::InvalidMagic {
394 context: MagicContext::File { file, magic },
395 } => format!("Invalid magic {} in file {file}", py_bytes_repr(magic)),
396 Self::InvalidMagic {
397 context: MagicContext::MtpDataBlock { block_id },
398 } => format!("Unsupported block id in MtpData: {block_id}"),
399 Self::InvalidCheckSum { file, computed } => {
400 let mut hex = String::with_capacity(2 * computed.len());
401 for &byte in computed {
402 push_hex(&mut hex, byte);
403 }
404 format!("Invalid checksum {hex} in file {file}")
405 }
406 Self::BadDecryptKey => {
407 "Bad decrypt key, data not decrypted - incorrect password?".to_owned()
408 }
409 Self::BadEncryptedDataSize { size } => format!("Bad encrypted part size: {size}"),
410 Self::BadDecryptedDataSize {
411 encrypted_size,
412 full_len,
413 decrypted_size,
414 } => format!(
415 "Bad decrypted part size: {encrypted_size}, fullLen: {full_len}, \
416 decrypted size: {decrypted_size}"
417 ),
418 Self::QDataStreamFailed { context } => {
419 context.unwrap_or("Could not stream data").to_owned()
420 }
421 Self::ReadMapDataFailed { context } => match context {
422 MapReadFailure::Open => {
423 "Could not read map data, file not found or couldn't be opened".to_owned()
424 }
425 MapReadFailure::BadSalt { size } => {
426 format!("Bad salt in map file, size: {size}")
427 }
428 MapReadFailure::Decrypt => "Could not decrypt map data".to_owned(),
429 },
430 Self::ReadMapDataIncorrectPasscode => {
431 "Could not decrypt pass-protected key from map file, maybe bad password..."
432 .to_owned()
433 }
434 Self::AuthKeyNotFound => "Could not find the main authKey".to_owned(),
435 Self::MaxAccountLimit => {
436 "You can't have more than 6 accounts in one TDesktop client".to_owned()
437 }
438 Self::KeyDataInvalid { context } => key_data_message(*context),
439 Self::SaveFailed => "Could not save tdata, something went wrong".to_owned(),
440 Self::BadConfigData { context } => config_message(*context),
441 Self::WrongPasscode
442 | Self::BadLength { .. }
443 | Self::InvalidInput { .. }
444 | Self::Io(_) => self.to_string(),
445 }
446 }
447
448 #[must_use]
461 pub fn legacy_class(&self) -> &'static str {
462 match self {
463 Self::FileNotFound { .. } => "TFileNotFound",
464 Self::InvalidMagic { .. } => "TDataInvalidMagic",
465 Self::InvalidCheckSum { .. } => "TDataInvalidCheckSum",
466 Self::BadDecryptKey => "TDataBadDecryptKey",
467 Self::WrongPasscode => "TDataWrongPasscode",
468 Self::BadEncryptedDataSize { .. } => "TDataBadEncryptedDataSize",
469 Self::BadDecryptedDataSize { .. } => "TDataBadDecryptedDataSize",
470 Self::BadConfigData { .. } => "TDataBadConfigData",
471 Self::QDataStreamFailed { .. } => "QDataStreamFailed",
472 Self::ReadMapDataFailed { .. } => "TDataReadMapDataFailed",
473 Self::ReadMapDataIncorrectPasscode => "TDataReadMapDataIncorrectPasscode",
474 Self::AuthKeyNotFound => "TDataAuthKeyNotFound",
475 Self::MaxAccountLimit => "MaxAccountLimit",
476 Self::SaveFailed => "TDataSaveFailed",
477 Self::KeyDataInvalid {
478 context: KeyDataFailure::BadAccountsCount { .. },
479 } => "OpenTeleException",
480 Self::KeyDataInvalid { .. } => "TDataKeyDataInvalid",
481 Self::BadLength { .. } | Self::InvalidInput { .. } => "ValueError",
482 Self::Io(_) => "OSError",
483 }
484 }
485}
486
487fn config_message(context: ConfigFailure) -> String {
489 match context {
490 ConfigFailure::BadIpSize { .. } => "Bad ipSize data".to_owned(),
491 ConfigFailure::BadSecretSize { .. } => "Bad secretSize data".to_owned(),
492 ConfigFailure::NonAsciiIp { byte, position } => {
493 let mut hex = String::with_capacity(2);
494 push_hex(&mut hex, byte);
495 format!(
496 "'ascii' codec can't decode byte 0x{hex} in position {position}: \
497 ordinal not in range(128)"
498 )
499 }
500 ConfigFailure::UnencodableIp => "Endpoint IP is not ASCII".to_owned(),
501 ConfigFailure::BadVersion { .. } => "version != kVersion, something went wrong".to_owned(),
502 ConfigFailure::BadEnvironment { code } => format!("{code} is not a valid Environment"),
503 ConfigFailure::DcNotFound { dc_id } => format!("No endpoints for dc id {dc_id}"),
504 ConfigFailure::NoTcpEndpoint => {
505 "Couldn't find endpoint for this account, something went wrong?".to_owned()
506 }
507 ConfigFailure::BadEndpointIp => "Endpoint IP is not an IPv4 or IPv6 address".to_owned(),
508 ConfigFailure::DcIdOutOfRange { dc_id } => format!("DC id out of range: {dc_id}"),
509 ConfigFailure::PortOutOfRange { port } => format!("Port out of range: {port}"),
510 ConfigFailure::NullSessionColumn { column } => format!("Session column {column} is NULL"),
511 ConfigFailure::NoSelfUser => "No cached user is the self user".to_owned(),
512 ConfigFailure::ConflictingSelfUsers => {
513 "Cached users with different ids are the self user".to_owned()
514 }
515 ConfigFailure::BadUserId { user_id } => format!("Bad user id: {user_id}"),
516 }
517}
518
519fn key_data_message(context: KeyDataFailure) -> String {
521 match context {
522 KeyDataFailure::UnusableWrapParameters {
523 kind,
524 memory,
525 time,
526 parallelism,
527 } => format!(
528 "Unusable passcode wrap parameters: PasscodeKdf(kind={kind}, memory={memory}, \
529 time={time}, parallel={parallelism})"
530 ),
531 KeyDataFailure::BadSalt { size } => format!("Bad salt in key data, size: {size}"),
532 KeyDataFailure::BadTrailerMagic => "Bad key data trailer magic".to_owned(),
533 KeyDataFailure::BadWrapCount { count } => format!("Bad key data wrap count: {count}"),
534 KeyDataFailure::BadKdfFamily { kind } => format!("Bad key data KDF family: {kind}"),
535 KeyDataFailure::BadWrapSaltSize { size } => {
536 format!("Bad key data wrap salt size: {size}")
537 }
538 KeyDataFailure::DuplicateWrapGeneration { generation } => {
539 format!("Duplicate key data wrap generation: {generation}")
540 }
541 KeyDataFailure::AccountsInfoDecrypt => "Could not decrypt the accounts info".to_owned(),
542 KeyDataFailure::BadAccountsCount { count } => {
543 format!("Bad accounts count {count}, the data might have been corrupted")
544 }
545 }
546}
547
548fn py_bytes_repr(bytes: &[u8]) -> String {
552 let quote = if bytes.contains(&b'\'') && !bytes.contains(&b'"') {
553 '"'
554 } else {
555 '\''
556 };
557 let mut out = format!("b{quote}");
558 for &byte in bytes {
559 match byte {
560 b'\\' => out.push_str("\\\\"),
561 b'\t' => out.push_str("\\t"),
562 b'\n' => out.push_str("\\n"),
563 b'\r' => out.push_str("\\r"),
564 _ if char::from(byte) == quote => {
565 out.push('\\');
566 out.push(quote);
567 }
568 0x20..=0x7e => out.push(char::from(byte)),
569 _ => {
570 out.push_str("\\x");
571 push_hex(&mut out, byte);
572 }
573 }
574 }
575 out.push(quote);
576 out
577}
578
579fn push_hex(out: &mut String, byte: u8) {
581 const DIGITS: &[u8; 16] = b"0123456789abcdef";
582 out.push(char::from(DIGITS[usize::from(byte >> 4)]));
583 out.push(char::from(DIGITS[usize::from(byte & 0x0f)]));
584}
585
586#[cfg(test)]
587mod tests {
588 use super::{ConfigFailure, Error, KeyDataFailure, MagicContext, MapReadFailure};
589 use std::collections::HashSet;
590
591 fn every_variant() -> Vec<Error> {
594 let all = vec![
595 Error::FileNotFound {
596 file: "map".to_owned(),
597 },
598 Error::InvalidMagic {
599 context: MagicContext::File {
600 file: "maps".to_owned(),
601 magic: b"ABCD".to_vec(),
602 },
603 },
604 Error::InvalidCheckSum {
605 file: "ck".to_owned(),
606 computed: [0; 16],
607 },
608 Error::BadDecryptKey,
609 Error::WrongPasscode,
610 Error::BadEncryptedDataSize { size: 16 },
611 Error::BadDecryptedDataSize {
612 encrypted_size: 48,
613 full_len: 32,
614 decrypted_size: 32,
615 },
616 Error::BadConfigData {
617 context: ConfigFailure::BadIpSize { size: 0 },
618 },
619 Error::QDataStreamFailed { context: None },
620 Error::ReadMapDataFailed {
621 context: MapReadFailure::Open,
622 },
623 Error::ReadMapDataIncorrectPasscode,
624 Error::AuthKeyNotFound,
625 Error::MaxAccountLimit,
626 Error::SaveFailed,
627 Error::KeyDataInvalid {
628 context: KeyDataFailure::BadTrailerMagic,
629 },
630 Error::BadLength {
631 what: "key",
632 expected: 32,
633 actual: 16,
634 },
635 Error::InvalidInput {
636 what: "PBKDF2 iterations",
637 reason: "must be at least 1".to_owned(),
638 },
639 Error::Io(std::io::Error::other("disk gone")),
640 ];
641 for error in &all {
642 match error {
643 Error::FileNotFound { .. }
644 | Error::InvalidMagic { .. }
645 | Error::InvalidCheckSum { .. }
646 | Error::BadDecryptKey
647 | Error::WrongPasscode
648 | Error::BadEncryptedDataSize { .. }
649 | Error::BadDecryptedDataSize { .. }
650 | Error::BadConfigData { .. }
651 | Error::QDataStreamFailed { .. }
652 | Error::ReadMapDataFailed { .. }
653 | Error::ReadMapDataIncorrectPasscode
654 | Error::AuthKeyNotFound
655 | Error::MaxAccountLimit
656 | Error::SaveFailed
657 | Error::KeyDataInvalid { .. }
658 | Error::BadLength { .. }
659 | Error::InvalidInput { .. }
660 | Error::Io(_) => {}
661 }
662 }
663 all
664 }
665
666 #[test]
667 fn codes_are_unique() {
668 let all = every_variant();
669 let codes: HashSet<&str> = all.iter().map(Error::code).collect();
670 assert_eq!(codes.len(), all.len(), "duplicate code in {codes:?}");
671 }
672
673 #[test]
674 fn codes_are_non_empty_screaming_snake() {
675 for error in every_variant() {
676 let code = error.code();
677 assert!(!code.is_empty(), "{error:?} has an empty code");
678 assert!(
679 code.bytes()
680 .all(|b| b.is_ascii_uppercase() || b.is_ascii_digit() || b == b'_'),
681 "{code} is not SCREAMING_SNAKE"
682 );
683 }
684 }
685
686 #[test]
687 fn only_bad_accounts_count_has_a_detail_code() {
688 for error in every_variant() {
689 assert_eq!(error.detail_code(), None, "{error:?}");
690 }
691 let error = Error::KeyDataInvalid {
692 context: KeyDataFailure::BadAccountsCount { count: 0 },
693 };
694 assert_eq!(error.detail_code(), Some("BAD_ACCOUNTS_COUNT"));
695 assert_eq!(error.code(), "TDATA_KEY_DATA_INVALID");
696 }
697
698 #[test]
699 fn wrong_passcode_code_is_stable() {
700 assert_eq!(Error::WrongPasscode.code(), "TDATA_WRONG_PASSCODE");
701 }
702
703 #[test]
706 fn codes_and_display_texts_are_fixed() {
707 let expected = [
708 ("TFILE_NOT_FOUND", "tdata file not found"),
709 ("TDATA_INVALID_MAGIC", "tdata file has an invalid magic"),
710 (
711 "TDATA_INVALID_CHECKSUM",
712 "tdata file has an invalid checksum",
713 ),
714 ("TDATA_BAD_DECRYPT_KEY", "tdata decrypt key is bad"),
715 ("TDATA_WRONG_PASSCODE", "tdata passcode is wrong"),
716 (
717 "TDATA_BAD_ENCRYPTED_DATA_SIZE",
718 "tdata encrypted data has a bad size",
719 ),
720 (
721 "TDATA_BAD_DECRYPTED_DATA_SIZE",
722 "tdata decrypted data has a bad size",
723 ),
724 ("TDATA_BAD_CONFIG_DATA", "tdata config data is bad"),
725 ("QDATASTREAM_FAILED", "QDataStream operation failed"),
726 (
727 "TDATA_READ_MAP_DATA_FAILED",
728 "failed to read tdata map data",
729 ),
730 (
731 "TDATA_READ_MAP_DATA_INCORRECT_PASSCODE",
732 "tdata map data passcode is incorrect",
733 ),
734 ("TDATA_AUTH_KEY_NOT_FOUND", "tdata auth key not found"),
735 ("MAX_ACCOUNT_LIMIT", "maximum number of accounts reached"),
736 ("TDATA_SAVE_FAILED", "failed to save tdata"),
737 ("TDATA_KEY_DATA_INVALID", "tdata key data is invalid"),
738 ("BAD_LENGTH", "key has a bad length: expected 32, got 16"),
739 (
740 "INVALID_INPUT",
741 "Invalid PBKDF2 iterations: must be at least 1",
742 ),
743 ("IO", "I/O error: disk gone"),
744 ];
745 let all = every_variant();
746 assert_eq!(all.len(), expected.len());
747 for (error, (code, display)) in all.iter().zip(expected) {
748 assert_eq!(error.code(), code);
749 assert_eq!(error.to_string(), display);
750 }
751 }
752
753 #[test]
755 fn legacy_message_falls_back_to_display() {
756 let error = Error::WrongPasscode;
757 assert_eq!(error.legacy_message(), error.to_string());
758 }
759
760 #[test]
763 fn config_contexts_share_code_and_display() {
764 let contexts = [
765 ConfigFailure::BadIpSize { size: 46 },
766 ConfigFailure::BadSecretSize { size: -1 },
767 ConfigFailure::NonAsciiIp {
768 byte: 0x80,
769 position: 3,
770 },
771 ConfigFailure::UnencodableIp,
772 ConfigFailure::BadVersion { version: 2 },
773 ConfigFailure::BadEnvironment { code: 2 },
774 ConfigFailure::DcNotFound { dc_id: 9 },
775 ConfigFailure::NoTcpEndpoint,
776 ConfigFailure::BadEndpointIp,
777 ConfigFailure::DcIdOutOfRange { dc_id: 256 },
778 ConfigFailure::PortOutOfRange { port: 65536 },
779 ConfigFailure::NullSessionColumn { column: "port" },
780 ConfigFailure::NoSelfUser,
781 ConfigFailure::ConflictingSelfUsers,
782 ConfigFailure::BadUserId { user_id: -5 },
783 ];
784 let mut messages = HashSet::new();
785 for context in contexts {
786 let error = Error::BadConfigData { context };
787 assert_eq!(error.code(), "TDATA_BAD_CONFIG_DATA");
788 assert_eq!(error.to_string(), "tdata config data is bad");
789 let message = error.legacy_message();
790 assert_ne!(message, error.to_string(), "{context:?}");
791 assert!(messages.insert(message), "{context:?} repeats a message");
792 }
793 }
794
795 #[test]
796 fn io_error_converts_and_displays() {
797 let error: Error = std::io::Error::other("disk gone").into();
798 assert_eq!(error.code(), "IO");
799 assert_eq!(error.to_string(), "I/O error: disk gone");
800 }
801
802 #[test]
803 fn invalid_input_message_is_its_display() {
804 let error = Error::InvalidInput {
805 what: "pq",
806 reason: "7 is not a product of two factors greater than 1".to_owned(),
807 };
808 assert_eq!(error.code(), "INVALID_INPUT");
809 assert_eq!(
810 error.legacy_message(),
811 "Invalid pq: 7 is not a product of two factors greater than 1"
812 );
813 assert_eq!(error.legacy_message(), error.to_string());
814 }
815
816 #[test]
817 fn bad_length_displays_its_fields() {
818 let error = Error::BadLength {
819 what: "iv",
820 expected: 32,
821 actual: 31,
822 };
823 assert_eq!(
824 error.to_string(),
825 "iv has a bad length: expected 32, got 31"
826 );
827 }
828}