aboutsummaryrefslogtreecommitdiff
path: root/src/field/mod.rs
diff options
context:
space:
mode:
authorRuben Beltran del Rio <git@r.bdr.sh>2025-02-09 22:44:21 +0100
committerRuben Beltran del Rio <git@r.bdr.sh>2025-02-09 22:44:21 +0100
commitc402a8923d2e89607a007f46ee4cde15714d746d (patch)
tree597f0e436cac0ec2dda2eb8437cfb57de6c95a82 /src/field/mod.rs
parent48408b9e42eaeeaf0944f9a37f7882ba9ddc7f19 (diff)
Restart with fields module
Diffstat (limited to 'src/field/mod.rs')
-rw-r--r--src/field/mod.rs145
1 files changed, 131 insertions, 14 deletions
diff --git a/src/field/mod.rs b/src/field/mod.rs
index 82e8abd..277e7c4 100644
--- a/src/field/mod.rs
+++ b/src/field/mod.rs
@@ -1,21 +1,138 @@
-use std::io::Result;
-mod data;
+mod id;
+pub mod utils;
-pub use data::Data;
+pub use id::ID;
-pub trait Field: Sized {
- const ID: u16;
+use std::convert::TryInto;
- fn size(&self) -> [u8; 2];
- fn serialize_data(&self) -> &Vec<u8>;
- fn deserialize(data: &[u8]) -> Result<Self>;
- fn serialize(&self) -> Vec<u8> {
- let mut result = Vec::new();
+/// Hotline defines 3 data types in the wire format:
+/// 1) "integer" (16-bit or 32-bit, determined by magnitude)
+/// 2) "string" (ASCII)
+/// 3) "binary"
+///
+/// Internally, we can unify them into:
+/// - Integer(u32)
+/// - String(String)
+/// - Binary(Vec<u8>)
+#[derive(Debug, Clone)]
+pub enum Value {
+ Integer(u32),
+ Text(String),
+ Binary(Vec<u8>),
+}
+
+/// A single Hotline field: an ID plus a typed value.
+#[derive(Debug, Clone)]
+pub struct Field {
+ pub id: ID,
+ pub value: Value,
+}
+
+/// Parse the field data into the correct `Value` type based on the `field_id`.
+/// The spec says:
+/// - 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::*;
+
+ // By default, assume "binary"
+ // Then override to integer or string if the spec clearly says so.
+ match id {
+ // Integers
+ UserID
+ | UserIconID
+ | ReferenceNumber
+ | TransferSize
+ | ChatOptions
+ | UserFlags
+ | Options
+ | ChatID
+ | WaitingCount
+ | ServerBannerType
+ | NoServerAgreement
+ | Version
+ | CommunityBannerID
+ | FileSize
+ | NewsArticleID
+ | NewsArticlePrevArticle
+ | NewsArticleNextArticle
+ | NewsArticleFlags
+ | NewsArticleParentArticle
+ | NewsArticle1stChildArticle
+ | NewsArticleRecurseDel
+ | FolderItemCount => {
+ // Convert the data to an integer (u32).
+ // Hotline protocol can store 16-bit or 32-bit depending on magnitude,
+ // but on the wire we need to detect it. For simplicity here,
+ // we'll parse up to 4 bytes. If data is 2 bytes, parse as 16-bit.
+ // If 4 bytes, parse as 32-bit. If it's not 2 or 4, we do best-effort.
+
+ let len = data.len();
+ if len == 2 {
+ Integer(u16::from_be_bytes(data.try_into().unwrap()) as u32)
+ } else if len == 4 {
+ Integer(u32::from_be_bytes(data.try_into().unwrap()))
+ } else if len == 0 {
+ // no data => 0
+ Integer(0)
+ } 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))
+ }
+ }
- result.extend_from_slice(&Self::ID.to_be_bytes());
- result.extend_from_slice(&self.size());
- result.extend_from_slice(self.serialize_data());
+ // Strings
+ UserName
+ | UserLogin
+ | UserPassword
+ | ChatSubject
+ | ServerAgreement
+ | FileName
+ | FileTypeString
+ | FileCreatorString
+ | FileComment
+ | FileNewName
+ | AutomaticResponse
+ | ServerName
+ | NewsCategoryName
+ | NewsArticleDataFlavor
+ | NewsArticleTitle
+ | NewsArticlePoster => {
+ let s = String::from_utf8_lossy(data).to_string();
+ Text(s)
+ }
- result
+ // Some are definitely “binary,” but might happen to contain text:
+ ErrorText
+ | Data
+ | ServerBanner
+ | ServerBannerUrl
+ | FileNameWithInfo
+ | FilePath
+ | FileResumeData
+ | FileXferOptions
+ | FileCreateDate
+ | FileModifyDate
+ | FileNewPath
+ | FileType
+ | QuotingMessage
+ | UserAccess
+ | UserAlias
+ | UserNameWithInfo
+ | NewsCategoryGUID
+ | LegacyNewsCategoryListData
+ | NewsArticleListData
+ | NewsCategoryListData
+ | NewsPath
+ | NewsArticleDate
+ | NewsArticleData
+ | Unknown(_) => Binary(data.to_vec()),
}
}