Skip to main content

soup_sdk/chat/parser/
raw.rs

1use chrono::{DateTime, Utc};
2use std::sync::Arc;
3use thiserror::Error;
4
5use crate::chat::constants::{SEPARATOR_U8, message_codes::MessageCode};
6
7#[derive(Debug, Clone)]
8pub struct RawMessage {
9    pub code: MessageCode,
10    pub ret_code: u32,
11    pub body: Vec<String>,
12    pub received_time: DateTime<Utc>,
13    pub raw: Arc<[u8]>,
14}
15
16struct MessageHeader {
17    code: MessageCode,
18    ret_code: u32,
19}
20
21#[derive(Debug, Clone, Error)]
22pub enum ParseError {
23    #[error("프레임이 너무 짧습니다: {len} bytes")]
24    FrameTooShort { len: usize },
25
26    #[error("헤더 길이가 잘못되었습니다: {len} bytes")]
27    InvalidHeaderLength { len: usize },
28
29    #[error("필드가 없습니다: code={code}, index={index}, len={len}")]
30    MissingField {
31        code: MessageCode,
32        index: usize,
33        len: usize,
34    },
35
36    #[error("숫자 파싱 실패: code={code}, index={index}, value={value:?}, target={target}")]
37    InvalidNumber {
38        code: MessageCode,
39        index: usize,
40        value: String,
41        target: &'static str,
42    },
43
44    #[error("JSON 파싱 실패: code={code}, index={index}, reason={reason}")]
45    InvalidJson {
46        code: MessageCode,
47        index: usize,
48        reason: String,
49    },
50
51    #[error("프로토콜 파싱 실패: {0}")]
52    Protocol(String),
53}
54
55pub type ParseResult<T> = std::result::Result<T, ParseError>;
56
57impl RawMessage {
58    pub fn field(&self, index: usize) -> ParseResult<&str> {
59        self.body
60            .get(index)
61            .map(String::as_str)
62            .ok_or(ParseError::MissingField {
63                code: self.code,
64                index,
65                len: self.body.len(),
66            })
67    }
68
69    pub fn field_string(&self, index: usize) -> ParseResult<String> {
70        Ok(self.field(index)?.to_string())
71    }
72
73    pub fn parse_u32(&self, index: usize) -> ParseResult<u32> {
74        let value = self.field(index)?;
75        value.parse::<u32>().map_err(|_| ParseError::InvalidNumber {
76            code: self.code,
77            index,
78            value: value.to_string(),
79            target: "u32",
80        })
81    }
82
83    pub fn parse_usize_or_default(&self, index: usize, default: usize) -> usize {
84        self.field(index)
85            .ok()
86            .and_then(|value| value.parse::<usize>().ok())
87            .unwrap_or(default)
88    }
89
90    pub fn parse_u32_or_default(&self, index: usize, default: u32) -> u32 {
91        self.field(index)
92            .ok()
93            .and_then(|value| value.parse::<u32>().ok())
94            .unwrap_or(default)
95    }
96}
97
98pub fn parse_message(data: Arc<[u8]>) -> ParseResult<RawMessage> {
99    let now = Utc::now();
100
101    if data.len() < 14 {
102        return Err(ParseError::FrameTooShort { len: data.len() });
103    }
104
105    let header_bytes = &data[0..14];
106
107    let header = parse_header(header_bytes)?;
108
109    let body = &data[14..];
110
111    Ok(RawMessage {
112        code: header.code,
113        ret_code: header.ret_code,
114        body: parse_body(body),
115        received_time: now,
116        raw: data,
117    })
118}
119
120fn parse_header(header: &[u8]) -> ParseResult<MessageHeader> {
121    if header.len() != 14 {
122        return Err(ParseError::InvalidHeaderLength { len: header.len() });
123    }
124
125    Ok(MessageHeader {
126        code: parse_bytes_to_u32(&header[2..6]),
127        ret_code: parse_bytes_to_u32(&header[12..14]),
128    })
129}
130
131fn parse_body(body: &[u8]) -> Vec<String> {
132    // 입력 데이터가 너무 짧으면(헤더만 있거나 비어있으면) 빈 벡터를 반환합니다.
133    if body.len() < 2 {
134        return Vec::new();
135    }
136
137    // 첫 번째 바이트를 건너뛴 슬라이스를 가져옵니다.
138    let data_to_process = &body[1..];
139
140    // 구분자 개수를 미리 세어서 Vec 용량을 할당합니다.
141    let separator_count = data_to_process
142        .iter()
143        .filter(|&&b| b == SEPARATOR_U8)
144        .count();
145    let mut result = Vec::with_capacity(separator_count + 1);
146
147    // 데이터를 구분자로 분할하고, 각 조각을 문자열로 변환한 뒤, 벡터로 수집합니다.
148    for byte_part in data_to_process.split(|&byte| byte == SEPARATOR_U8) {
149        // UTF-8 유효성을 먼저 확인하여 불필요한 할당을 피합니다.
150        match std::str::from_utf8(byte_part) {
151            Ok(s) => result.push(s.to_string()),
152            Err(_) => {
153                // 유효하지 않은 UTF-8인 경우에만 lossy conversion 사용
154                let cow_str = String::from_utf8_lossy(byte_part);
155                result.push(cow_str.into_owned());
156            }
157        }
158    }
159
160    result
161}
162
163fn parse_bytes_to_u32(bytes: &[u8]) -> u32 {
164    // UTF-8 바이트를 직접 파싱하여 불필요한 String 할당을 피합니다.
165    match std::str::from_utf8(bytes) {
166        Ok(s) => s.parse::<u32>().unwrap_or(0),
167        Err(_) => {
168            // 유효하지 않은 UTF-8인 경우에만 lossy conversion 사용
169            String::from_utf8_lossy(bytes).parse::<u32>().unwrap_or(0)
170        }
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use std::sync::Arc;
177
178    use super::*;
179
180    fn frame(code: u32, body: &[u8]) -> Arc<[u8]> {
181        let mut bytes = Vec::new();
182        bytes.extend_from_slice(&[27, 9]);
183        bytes.extend_from_slice(format!("{code:04}").as_bytes());
184        bytes.extend_from_slice(format!("{:06}", body.len()).as_bytes());
185        bytes.extend_from_slice(b"00");
186        bytes.extend_from_slice(body);
187        bytes.into()
188    }
189
190    #[test]
191    fn decodes_header_and_body_fields() {
192        let raw = parse_message(frame(5, b"\x0chello\x0cuser")).unwrap();
193
194        assert_eq!(raw.code, 5);
195        assert_eq!(raw.ret_code, 0);
196        assert_eq!(raw.body, vec!["hello".to_string(), "user".to_string()]);
197    }
198
199    #[test]
200    fn rejects_short_header() {
201        let short: Arc<[u8]> = vec![1_u8, 2, 3].into();
202        let err = parse_message(short).unwrap_err();
203
204        assert!(matches!(err, ParseError::FrameTooShort { len: 3 }));
205    }
206
207    #[test]
208    fn keeps_unknown_code_decodable() {
209        let raw = parse_message(frame(9999, b"\x0cnew-payload")).unwrap();
210
211        assert_eq!(raw.code, 9999);
212        assert_eq!(raw.body, vec!["new-payload".to_string()]);
213    }
214
215    #[test]
216    fn invalid_utf8_body_is_lossy_not_fatal() {
217        let raw = parse_message(frame(5, b"\x0c\xff")).unwrap();
218
219        assert_eq!(raw.body.len(), 1);
220        assert!(raw.body[0].contains('\u{fffd}'));
221    }
222}