aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRuben Beltran del Rio <git@r.bdr.sh>2025-02-09 23:17:22 +0100
committerRuben Beltran del Rio <git@r.bdr.sh>2025-02-09 23:17:22 +0100
commitcf94c0cc512994ca847f1c376e86735c54a6ee59 (patch)
tree414c46e79fef9ec56ca4424995b51b8369d5ffeb /src
parentc402a8923d2e89607a007f46ee4cde15714d746d (diff)
Use a result for the parser
Diffstat (limited to 'src')
-rw-r--r--src/field/id.rs16
-rw-r--r--src/field/mod.rs53
-rw-r--r--src/field/utils/access_privileges.rs1
-rw-r--r--src/field/utils/user_flags.rs6
4 files changed, 57 insertions, 19 deletions
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
}