aboutsummaryrefslogtreecommitdiff
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
parentc402a8923d2e89607a007f46ee4cde15714d746d (diff)
Use a result for the parser
-rw-r--r--Cargo.lock58
-rw-r--r--Cargo.toml1
-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
6 files changed, 116 insertions, 19 deletions
diff --git a/Cargo.lock b/Cargo.lock
index fb61b8c..7b8ebee 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 5b31dc0..8aa104a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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
}