diff options
| author | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-02-09 22:44:21 +0100 |
|---|---|---|
| committer | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-02-09 22:44:21 +0100 |
| commit | c402a8923d2e89607a007f46ee4cde15714d746d (patch) | |
| tree | 597f0e436cac0ec2dda2eb8437cfb57de6c95a82 /src/field/mod.rs | |
| parent | 48408b9e42eaeeaf0944f9a37f7882ba9ddc7f19 (diff) | |
Restart with fields module
Diffstat (limited to 'src/field/mod.rs')
| -rw-r--r-- | src/field/mod.rs | 145 |
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()), } } |