diff options
| author | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-02-09 23:17:22 +0100 |
|---|---|---|
| committer | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-02-09 23:17:22 +0100 |
| commit | cf94c0cc512994ca847f1c376e86735c54a6ee59 (patch) | |
| tree | 414c46e79fef9ec56ca4424995b51b8369d5ffeb | |
| parent | c402a8923d2e89607a007f46ee4cde15714d746d (diff) | |
Use a result for the parser
| -rw-r--r-- | Cargo.lock | 58 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | src/field/id.rs | 16 | ||||
| -rw-r--r-- | src/field/mod.rs | 53 | ||||
| -rw-r--r-- | src/field/utils/access_privileges.rs | 1 | ||||
| -rw-r--r-- | src/field/utils/user_flags.rs | 6 |
6 files changed, 116 insertions, 19 deletions
@@ -5,3 +5,61 @@ version = 4 [[package]] name = "linea_caliente" version = "0.1.0" +dependencies = [ + "thiserror", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" @@ -8,6 +8,7 @@ homepage = "https://r.bdr.sh/hotline.html" authors = ["Rubén Beltrán del Río <linea_caliente@r.bdr.sh>"] [dependencies] +thiserror = "2.0.11" [profile.release] strip = true diff --git a/src/field/id.rs b/src/field/id.rs index 07e02b2..fa854de 100644 --- a/src/field/id.rs +++ b/src/field/id.rs @@ -76,8 +76,22 @@ pub enum ID { } impl ID { + #[must_use] pub fn from_u16(id: u16) -> Self { - use ID::*; + use ID::{ + AutomaticResponse, ChatID, ChatOptions, ChatSubject, CommunityBannerID, Data, + ErrorText, FileComment, FileCreateDate, FileCreatorString, FileModifyDate, FileName, + FileNameWithInfo, FileNewName, FileNewPath, FilePath, FileResumeData, FileSize, + FileType, FileTypeString, FileXferOptions, FolderItemCount, LegacyNewsCategoryListData, + NewsArticle1stChildArticle, NewsArticleData, NewsArticleDataFlavor, NewsArticleDate, + NewsArticleFlags, NewsArticleID, NewsArticleListData, NewsArticleNextArticle, + NewsArticleParentArticle, NewsArticlePoster, NewsArticlePrevArticle, + NewsArticleRecurseDel, NewsArticleTitle, NewsCategoryGUID, NewsCategoryListData, + NewsCategoryName, NewsPath, NoServerAgreement, Options, QuotingMessage, + ReferenceNumber, ServerAgreement, ServerBanner, ServerBannerType, ServerBannerUrl, + ServerName, TransferSize, Unknown, UserAccess, UserAlias, UserFlags, UserID, + UserIconID, UserLogin, UserName, UserNameWithInfo, UserPassword, Version, WaitingCount, + }; match id { 100 => ErrorText, 101 => Data, diff --git a/src/field/mod.rs b/src/field/mod.rs index 277e7c4..a746ece 100644 --- a/src/field/mod.rs +++ b/src/field/mod.rs @@ -4,6 +4,13 @@ pub mod utils; pub use id::ID; use std::convert::TryInto; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Data could not be parsed as an integer.")] + InvalidInteger, +} /// Hotline defines 3 data types in the wire format: /// 1) "integer" (16-bit or 32-bit, determined by magnitude) @@ -33,9 +40,23 @@ pub struct Field { /// - Some field IDs are definitely “integer” (e.g. user ID). /// - Some are definitely “string” (e.g. user name). /// - Others are “binary” (which might contain sub-structures). -pub fn parse_field_value(id: ID, data: &[u8]) -> Value { - use Value::*; - use ID::*; +#[must_use] +pub fn parse_field_value(id: ID, data: &[u8]) -> Result<Value, Error> { + use Value::{Binary, Integer, Text}; + use ID::{ + AutomaticResponse, ChatID, ChatOptions, ChatSubject, CommunityBannerID, Data, ErrorText, + FileComment, FileCreateDate, FileCreatorString, FileModifyDate, FileName, FileNameWithInfo, + FileNewName, FileNewPath, FilePath, FileResumeData, FileSize, FileType, FileTypeString, + FileXferOptions, FolderItemCount, LegacyNewsCategoryListData, NewsArticle1stChildArticle, + NewsArticleData, NewsArticleDataFlavor, NewsArticleDate, NewsArticleFlags, NewsArticleID, + NewsArticleListData, NewsArticleNextArticle, NewsArticleParentArticle, NewsArticlePoster, + NewsArticlePrevArticle, NewsArticleRecurseDel, NewsArticleTitle, NewsCategoryGUID, + NewsCategoryListData, NewsCategoryName, NewsPath, NoServerAgreement, Options, + QuotingMessage, ReferenceNumber, ServerAgreement, ServerBanner, ServerBannerType, + ServerBannerUrl, ServerName, TransferSize, Unknown, UserAccess, UserAlias, UserFlags, + UserID, UserIconID, UserLogin, UserName, UserNameWithInfo, UserPassword, Version, + WaitingCount, + }; // By default, assume "binary" // Then override to integer or string if the spec clearly says so. @@ -71,20 +92,18 @@ pub fn parse_field_value(id: ID, data: &[u8]) -> Value { let len = data.len(); if len == 2 { - Integer(u16::from_be_bytes(data.try_into().unwrap()) as u32) + data.try_into() + .map(u16::from_be_bytes) + .map(u32::from) + .map(Integer) + .map_err(|_| Error::InvalidInteger) } else if len == 4 { - Integer(u32::from_be_bytes(data.try_into().unwrap())) - } else if len == 0 { - // no data => 0 - Integer(0) + data.try_into() + .map(u32::from_be_bytes) + .map(Integer) + .map_err(|_| Error::InvalidInteger) } else { - // fallback - // interpret first 4 bytes, or everything as 32-bit - let mut buf = [0u8; 4]; - for (i, b) in data.iter().enumerate().take(4) { - buf[i] = *b; - } - Integer(u32::from_be_bytes(buf)) + Err(Error::InvalidInteger) } } @@ -106,7 +125,7 @@ pub fn parse_field_value(id: ID, data: &[u8]) -> Value { | NewsArticleTitle | NewsArticlePoster => { let s = String::from_utf8_lossy(data).to_string(); - Text(s) + Ok(Text(s)) } // Some are definitely “binary,” but might happen to contain text: @@ -133,6 +152,6 @@ pub fn parse_field_value(id: ID, data: &[u8]) -> Value { | NewsPath | NewsArticleDate | NewsArticleData - | Unknown(_) => Binary(data.to_vec()), + | Unknown(_) => Ok(Binary(data.to_vec())), } } diff --git a/src/field/utils/access_privileges.rs b/src/field/utils/access_privileges.rs index eea9557..fabaef4 100644 --- a/src/field/utils/access_privileges.rs +++ b/src/field/utils/access_privileges.rs @@ -42,6 +42,7 @@ pub const CAN_UPLOAD_FOLDERS: u64 = 2_u64.pow(38); pub const CAN_DOWNLOAD_FOLDERS: u64 = 2_u64.pow(39); pub const CAN_SEND_MESSAGES: u64 = 2_u64.pow(40); +#[must_use] pub fn can(permission: u64, bits: u64) -> bool { bits & permission != 0 } diff --git a/src/field/utils/user_flags.rs b/src/field/utils/user_flags.rs index 6c79e8b..06ac5db 100644 --- a/src/field/utils/user_flags.rs +++ b/src/field/utils/user_flags.rs @@ -1,22 +1,26 @@ -//! This module contains utilities to read the data in a UserFlags bitmap. +//! This module contains utilities to read the data in a `UserFlags` bitmap. const AWAY: u32 = 0b0001; const ADMIN_OR_DISCONNECTED: u32 = 0b0010; const REFUSES_PRIVATE_MESSAGES: u32 = 0b0100; const REFUSES_PRIVATE_CHAT: u32 = 0b1000; +#[must_use] pub fn is_away(bits: u32) -> bool { bits & AWAY != 0 } +#[must_use] pub fn is_admin_or_disconnected(bits: u32) -> bool { bits & ADMIN_OR_DISCONNECTED != 0 } +#[must_use] pub fn refuses_private_messages(bits: u32) -> bool { bits & REFUSES_PRIVATE_MESSAGES != 0 } +#[must_use] pub fn refuses_private_chat(bits: u32) -> bool { bits & REFUSES_PRIVATE_CHAT != 0 } |