rusty_tip/nanonis/
protocol.rs

1use crate::error::NanonisError;
2use crate::types::NanonisValue;
3use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
4use log::debug;
5use std::io::Read;
6
7// Protocol constants
8pub const COMMAND_SIZE: usize = 32;
9pub const HEADER_SIZE: usize = 40;
10pub const ERROR_INFO_SIZE: usize = 8;
11pub const MAX_RETRY_COUNT: usize = 1000;
12pub const MAX_RESPONSE_SIZE: usize = 100 * 1024 * 1024; // 100MB
13pub const RESPONSE_FLAG: u16 = 1;
14pub const ZERO_BUFFER: u16 = 0;
15
16#[derive(Debug, Clone)]
17struct MessageHeader {
18    command: [u8; COMMAND_SIZE],
19    body_size: u32,
20    send_response: u16,
21    _padding: u16,
22}
23
24impl MessageHeader {
25    fn new(command: &str, body_size: u32) -> Self {
26        let mut cmd_bytes = [0u8; COMMAND_SIZE];
27        let cmd_str = command.as_bytes();
28        let len = cmd_str.len().min(COMMAND_SIZE);
29        cmd_bytes[..len].copy_from_slice(&cmd_str[..len]);
30
31        Self {
32            command: cmd_bytes,
33            body_size,
34            send_response: RESPONSE_FLAG, // Always request response for error info
35            _padding: ZERO_BUFFER,
36        }
37    }
38
39    // Safe serialization without unsafe code
40    fn to_bytes(&self) -> [u8; HEADER_SIZE] {
41        let mut buf = [0u8; HEADER_SIZE];
42        buf[0..32].copy_from_slice(&self.command);
43        buf[32..36].copy_from_slice(&self.body_size.to_be_bytes());
44        buf[36..38].copy_from_slice(&self.send_response.to_be_bytes());
45        buf[38..40].copy_from_slice(&self._padding.to_be_bytes());
46        buf
47    }
48}
49
50/// Low-level protocol handling
51pub struct Protocol;
52
53impl Protocol {
54    /// Parse error information from the end of a response body using safe slice operations
55    pub fn parse_error_info(
56        body: &[u8],
57        data_end_cursor: usize,
58    ) -> Result<(), NanonisError> {
59        // Get error section safely
60        let error_section = match body.get(data_end_cursor..) {
61            Some(section) if section.len() >= ERROR_INFO_SIZE => section,
62            _ => return Ok(()), // No error info available
63        };
64
65        // Use safe slice splitting instead of manual indexing
66        let (status_bytes, rest) = error_section.split_at(4);
67        let (size_bytes, message_bytes) = rest.split_at(4);
68
69        let error_status =
70            i32::from_be_bytes(status_bytes.try_into().map_err(|_| {
71                NanonisError::Protocol("Invalid error status format".into())
72            })?);
73
74        let error_desc_size =
75            i32::from_be_bytes(size_bytes.try_into().map_err(|_| {
76                NanonisError::Protocol("Invalid error size format".into())
77            })?) as usize;
78
79        if error_desc_size > 0 {
80            // Safe message extraction with bounds checking
81            let message_slice =
82                message_bytes.get(..error_desc_size).ok_or_else(|| {
83                    NanonisError::Protocol("Error message truncated".into())
84                })?;
85
86            // Use from_utf8 for better error handling
87            let error_msg = std::str::from_utf8(message_slice).map_err(|_| {
88                NanonisError::Protocol("Invalid UTF-8 in error message".into())
89            })?;
90
91            let trimmed_msg = error_msg.trim();
92            if !trimmed_msg.is_empty() {
93                return Err(NanonisError::ServerError {
94                    code: error_status,
95                    message: trimmed_msg.to_string(),
96                });
97            }
98        }
99
100        Ok(())
101    }
102
103    /// Helper for reading exact byte counts with better error messages
104    pub fn read_exact_bytes<const N: usize>(
105        reader: &mut dyn Read,
106    ) -> Result<[u8; N], NanonisError> {
107        debug!("Attempting to read exactly {} bytes", N);
108        let mut buf = [0u8; N];
109
110        match reader.read_exact(&mut buf) {
111            Ok(()) => {
112                debug!(
113                    "Successfully read {} bytes: {:02x?}",
114                    N,
115                    if N <= 20 { &buf[..] } else { &buf[..20] }
116                );
117                Ok(buf)
118            }
119            Err(e) => {
120                debug!("Failed to read {} bytes: {} (kind: {:?})", N, e, e.kind());
121                Err(NanonisError::Io {
122                    source: e,
123                    context: format!("Failed to read {} bytes from Nanonis", N),
124                })
125            }
126        }
127    }
128
129    /// Helper for reading variable-length data with size validation
130    pub fn read_variable_bytes(
131        reader: &mut dyn Read,
132        size: usize,
133    ) -> Result<Vec<u8>, NanonisError> {
134        debug!("Attempting to read {} variable bytes", size);
135
136        // Reasonable size limit to prevent memory attacks
137        if size > MAX_RESPONSE_SIZE {
138            debug!("Size {} exceeds maximum {}", size, MAX_RESPONSE_SIZE);
139            return Err(NanonisError::Protocol(format!(
140                "Response size {} exceeds maximum {}",
141                size, MAX_RESPONSE_SIZE
142            )));
143        }
144
145        let mut body = vec![0u8; size];
146        match reader.read_exact(&mut body) {
147            Ok(()) => {
148                debug!(
149                    "Successfully read {} variable bytes: {:02x?}",
150                    size,
151                    if size <= 50 { &body[..] } else { &body[..50] }
152                );
153                Ok(body)
154            }
155            Err(e) => {
156                debug!(
157                    "Failed to read {} variable bytes: {} (kind: {:?})",
158                    size,
159                    e,
160                    e.kind()
161                );
162                // Try to read whatever we can to diagnose the issue
163                let mut partial_buf = Vec::new();
164                if let Ok(bytes_read) = reader.read_to_end(&mut partial_buf) {
165                    debug!(
166                        "Partial read got {} bytes: {:02x?}",
167                        bytes_read,
168                        if bytes_read <= 50 {
169                            &partial_buf[..]
170                        } else {
171                            &partial_buf[..50]
172                        }
173                    );
174                }
175                Err(NanonisError::Io {
176                    source: e,
177                    context: format!("Failed to read {} byte response body", size),
178                })
179            }
180        }
181    }
182
183    /// Parse response with error checking - returns (values, cursor_position)
184    pub fn parse_response_with_error_check(
185        response: &[u8],
186        response_types: &[&str],
187    ) -> Result<Vec<NanonisValue>, NanonisError> {
188        // Parse normal response data first
189        let values = Self::parse_response(response, response_types)?;
190
191        // Calculate cursor position after parsing all response data
192        let cursor = Self::calculate_cursor_position(response, response_types)?;
193
194        // Check for errors at the end
195        Self::parse_error_info(response, cursor)?;
196
197        Ok(values)
198    }
199
200    /// Calculate cursor position after parsing response data
201    fn calculate_cursor_position(
202        response: &[u8],
203        response_types: &[&str],
204    ) -> Result<usize, NanonisError> {
205        let mut cursor = std::io::Cursor::new(response);
206        let mut result = Vec::with_capacity(response_types.len());
207
208        // This is essentially the same parsing logic as parse_response,
209        // but we only track the cursor position without storing values
210        for &response_type in response_types {
211            match response_type {
212                "H" => {
213                    cursor.read_u16::<BigEndian>()?;
214                }
215                "h" => {
216                    cursor.read_i16::<BigEndian>()?;
217                }
218                "I" => {
219                    let val = cursor.read_u32::<BigEndian>()?;
220                    result.push(val);
221                }
222                "i" => {
223                    let val = cursor.read_i32::<BigEndian>()? as u32;
224                    result.push(val);
225                }
226                "f" => {
227                    cursor.read_f32::<BigEndian>()?;
228                }
229                "d" => {
230                    cursor.read_f64::<BigEndian>()?;
231                }
232
233                t if t.contains("*f") => {
234                    let len = if t.starts_with("+") {
235                        cursor.read_u32::<BigEndian>()? as usize
236                    } else if let Some(&prev_val) = result.last() {
237                        prev_val as usize
238                    } else {
239                        return Err(NanonisError::Protocol(
240                            "Array length not specified".to_string(),
241                        ));
242                    };
243
244                    for _ in 0..len {
245                        cursor.read_f32::<BigEndian>()?;
246                    }
247                }
248
249                t if t.contains("*d") => {
250                    let len = if t.starts_with("+") {
251                        cursor.read_u32::<BigEndian>()? as usize
252                    } else if let Some(&prev_val) = result.last() {
253                        prev_val as usize
254                    } else {
255                        return Err(NanonisError::Protocol(
256                            "Array length not specified".to_string(),
257                        ));
258                    };
259
260                    for _ in 0..len {
261                        cursor.read_f64::<BigEndian>()?;
262                    }
263                }
264
265                t if t.contains("*i") => {
266                    let len = if t.starts_with("+") {
267                        cursor.read_u32::<BigEndian>()? as usize
268                    } else if let Some(&prev_val) = result.last() {
269                        prev_val as usize
270                    } else {
271                        return Err(NanonisError::Protocol(
272                            "Array length not specified".to_string(),
273                        ));
274                    };
275
276                    for _ in 0..len {
277                        cursor.read_i32::<BigEndian>()?;
278                    }
279                }
280
281                "+*c" => {
282                    let _total_size = cursor.read_u32::<BigEndian>()?;
283                    let num_strings = cursor.read_u32::<BigEndian>()? as usize;
284
285                    for _ in 0..num_strings {
286                        let string_len = cursor.read_u32::<BigEndian>()? as usize;
287                        let mut string_bytes = vec![0u8; string_len];
288                        cursor.read_exact(&mut string_bytes)?;
289                    }
290                }
291
292                "*-c" => {
293                    let string_length = result.last().ok_or_else(|| {
294                        NanonisError::Protocol(
295                            "String length not found for *-c type".to_string(),
296                        )
297                    })?;
298
299                    let mut string_bytes = vec![0u8; *string_length as usize];
300                    cursor.read_exact(&mut string_bytes)?;
301                }
302
303                "2f" => {
304                    if result.len() < 2 {
305                        return Err(NanonisError::Protocol(
306                            "2D array dimensions not found".to_string(),
307                        ));
308                    }
309
310                    let rows = result[result.len() - 2] as usize;
311                    let cols = result[result.len() - 1] as usize;
312
313                    for _ in 0..(rows * cols) {
314                        cursor.read_f32::<BigEndian>()?;
315                    }
316                }
317
318                _ => {
319                    return Err(NanonisError::Type(format!(
320                        "Unsupported response type: {response_type}"
321                    )));
322                }
323            };
324        }
325
326        Ok(cursor.position() as usize)
327    }
328
329    /// Serialize a value according to its type specification
330    pub fn serialize_value(
331        value: &NanonisValue,
332        body_type: &str,
333        buffer: &mut Vec<u8>,
334    ) -> Result<(), NanonisError> {
335        match (value, body_type) {
336            (NanonisValue::U16(v), "H") => buffer.write_u16::<BigEndian>(*v)?,
337            (NanonisValue::I16(v), "h") => buffer.write_i16::<BigEndian>(*v)?,
338            (NanonisValue::U32(v), "I") => buffer.write_u32::<BigEndian>(*v)?,
339            (NanonisValue::I32(v), "i") => buffer.write_i32::<BigEndian>(*v)?,
340            (NanonisValue::F32(v), "f") => buffer.write_f32::<BigEndian>(*v)?,
341            (NanonisValue::F64(v), "d") => buffer.write_f64::<BigEndian>(*v)?,
342
343            (NanonisValue::String(s), t) if t.contains("*c") => {
344                let bytes = s.as_bytes();
345                if t.starts_with("+") {
346                    buffer.write_u32::<BigEndian>(bytes.len() as u32)?;
347                }
348                buffer.extend_from_slice(bytes);
349            }
350
351            (NanonisValue::ArrayI32(arr), t) if t.contains("*i") => {
352                if t.starts_with("+") {
353                    buffer.write_u32::<BigEndian>(arr.len() as u32)?;
354                }
355                for &val in arr {
356                    buffer.write_i32::<BigEndian>(val)?;
357                }
358            }
359
360            (NanonisValue::ArrayU32(arr), t) if t.contains("*I") => {
361                if t.starts_with("+") {
362                    buffer.write_u32::<BigEndian>(arr.len() as u32)?;
363                }
364                for &val in arr {
365                    buffer.write_u32::<BigEndian>(val)?;
366                }
367            }
368
369            (NanonisValue::ArrayF32(arr), t) if t.contains("*f") => {
370                if t.starts_with("+") {
371                    buffer.write_u32::<BigEndian>(arr.len() as u32)?;
372                }
373                for &val in arr {
374                    buffer.write_f32::<BigEndian>(val)?;
375                }
376            }
377
378            (NanonisValue::ArrayF64(arr), t) if t.contains("*d") => {
379                if t.starts_with("+") {
380                    buffer.write_u32::<BigEndian>(arr.len() as u32)?;
381                }
382                for &val in arr {
383                    buffer.write_f64::<BigEndian>(val)?;
384                }
385            }
386
387            _ => {
388                return Err(NanonisError::Type(format!(
389                    "Unsupported type combination: {value:?} with {body_type}"
390                )))
391            }
392        }
393        Ok(())
394    }
395
396    /// Parse response data according to type specifications
397    pub fn parse_response(
398        response: &[u8],
399        response_types: &[&str],
400    ) -> Result<Vec<NanonisValue>, NanonisError> {
401        let mut cursor = std::io::Cursor::new(response);
402        let mut result = Vec::with_capacity(response_types.len());
403
404        for &response_type in response_types {
405            let value = match response_type {
406                "H" => NanonisValue::U16(cursor.read_u16::<BigEndian>()?),
407                "h" => NanonisValue::I16(cursor.read_i16::<BigEndian>()?),
408                "I" => NanonisValue::U32(cursor.read_u32::<BigEndian>()?),
409                "i" => NanonisValue::I32(cursor.read_i32::<BigEndian>()?),
410                "f" => NanonisValue::F32(cursor.read_f32::<BigEndian>()?),
411                "d" => NanonisValue::F64(cursor.read_f64::<BigEndian>()?),
412
413                t if t.contains("*f") => {
414                    let len = if t.starts_with("+") {
415                        cursor.read_u32::<BigEndian>()? as usize
416                    } else if let Some(prev_val) = result.last() {
417                        match prev_val {
418                            NanonisValue::U32(len) => *len as usize,
419                            NanonisValue::I32(len) => *len as usize,
420                            _ => {
421                                return Err(NanonisError::Protocol(
422                                    "Array length not found".to_string(),
423                                ))
424                            }
425                        }
426                    } else {
427                        return Err(NanonisError::Protocol(
428                            "Array length not specified".to_string(),
429                        ));
430                    };
431
432                    let mut arr = Vec::with_capacity(len);
433                    for _ in 0..len {
434                        arr.push(cursor.read_f32::<BigEndian>()?);
435                    }
436                    NanonisValue::ArrayF32(arr)
437                }
438
439                t if t.contains("*d") => {
440                    let len = if t.starts_with("+") {
441                        cursor.read_u32::<BigEndian>()? as usize
442                    } else if let Some(prev_val) = result.last() {
443                        match prev_val {
444                            NanonisValue::U32(len) => *len as usize,
445                            NanonisValue::I32(len) => *len as usize,
446                            _ => {
447                                return Err(NanonisError::Protocol(
448                                    "Array length not found".to_string(),
449                                ))
450                            }
451                        }
452                    } else {
453                        return Err(NanonisError::Protocol(
454                            "Array length not specified".to_string(),
455                        ));
456                    };
457
458                    let mut arr = Vec::with_capacity(len);
459                    for _ in 0..len {
460                        arr.push(cursor.read_f64::<BigEndian>()?);
461                    }
462                    NanonisValue::ArrayF64(arr)
463                }
464
465                t if t.contains("*i") => {
466                    let len = if t.starts_with("+") {
467                        cursor.read_u32::<BigEndian>()? as usize
468                    } else if let Some(prev_val) = result.last() {
469                        match prev_val {
470                            NanonisValue::U32(len) => *len as usize,
471                            NanonisValue::I32(len) => *len as usize,
472                            _ => {
473                                return Err(NanonisError::Protocol(
474                                    "Array length not found".to_string(),
475                                ))
476                            }
477                        }
478                    } else {
479                        return Err(NanonisError::Protocol(
480                            "Array length not specified".to_string(),
481                        ));
482                    };
483
484                    let mut arr = Vec::with_capacity(len);
485                    for _ in 0..len {
486                        arr.push(cursor.read_i32::<BigEndian>()?);
487                    }
488                    NanonisValue::ArrayI32(arr)
489                }
490
491                // Handle string arrays with prepended length
492                "+*c" => {
493                    // First read total byte size (we don't use this, but it's in the protocol)
494                    let _total_size = cursor.read_u32::<BigEndian>()?;
495                    // Then read number of strings
496                    let num_strings = cursor.read_u32::<BigEndian>()? as usize;
497                    let mut strings = Vec::with_capacity(num_strings);
498
499                    for _ in 0..num_strings {
500                        let string_len = cursor.read_u32::<BigEndian>()? as usize;
501                        let mut string_bytes = vec![0u8; string_len];
502                        cursor.read_exact(&mut string_bytes)?;
503                        let string =
504                            String::from_utf8_lossy(&string_bytes).to_string();
505                        strings.push(string);
506                    }
507
508                    NanonisValue::ArrayString(strings)
509                }
510
511                // Handle dynamic strings (*-c) where length comes from previous variable
512                "*-c" => {
513                    // Get string length from previous variable (should be an integer)
514                    let string_length = match result.last() {
515                        Some(NanonisValue::I32(len)) => *len as usize,
516                        Some(NanonisValue::U32(len)) => *len as usize,
517                        _ => {
518                            return Err(NanonisError::Protocol(
519                                "String length not found for *-c type".to_string(),
520                            ))
521                        }
522                    };
523
524                    // Read string bytes
525                    let mut string_bytes = vec![0u8; string_length];
526                    cursor.read_exact(&mut string_bytes)?;
527                    let string = String::from_utf8_lossy(&string_bytes).to_string();
528
529                    NanonisValue::String(string)
530                }
531
532                "2f" => {
533                    // 2D float array - dimensions should be in the two preceding i32 values
534                    if result.len() < 2 {
535                        return Err(NanonisError::Protocol(
536                            "2D array dimensions not found".to_string(),
537                        ));
538                    }
539
540                    let rows = match result[result.len() - 2] {
541                        NanonisValue::I32(r) => r as usize,
542                        _ => {
543                            return Err(NanonisError::Protocol(
544                                "Invalid row count for 2D array".to_string(),
545                            ))
546                        }
547                    };
548
549                    let cols = match result[result.len() - 1] {
550                        NanonisValue::I32(c) => c as usize,
551                        _ => {
552                            return Err(NanonisError::Protocol(
553                                "Invalid column count for 2D array".to_string(),
554                            ))
555                        }
556                    };
557
558                    // Read the flat array data
559                    let mut data_2d = Vec::with_capacity(rows);
560
561                    for _ in 0..rows {
562                        let mut row_data = Vec::with_capacity(cols);
563                        for _ in 0..cols {
564                            row_data.push(cursor.read_f32::<BigEndian>()?);
565                        }
566                        data_2d.push(row_data);
567                    }
568
569                    NanonisValue::Array2DF32(data_2d)
570                }
571
572                _ => {
573                    return Err(NanonisError::Type(format!(
574                        "Unsupported response type: {response_type}"
575                    )))
576                }
577            };
578
579            result.push(value);
580        }
581
582        Ok(result)
583    }
584
585    /// Create command header with proper padding using safe serialization
586    pub fn create_command_header(command: &str, body_size: u32) -> Vec<u8> {
587        let header = MessageHeader::new(command, body_size);
588        header.to_bytes().to_vec()
589    }
590
591    /// Validate command response header
592    pub fn validate_response_header(
593        header: &[u8; HEADER_SIZE],
594        expected_command: &str,
595    ) -> Result<u32, NanonisError> {
596        // Extract body size from header (bytes 32-36)
597        let response_body_size =
598            u32::from_be_bytes([header[32], header[33], header[34], header[35]]);
599
600        // Verify command matches (bytes 0-32 of header)
601        let received_command = String::from_utf8_lossy(&header[0..COMMAND_SIZE])
602            .trim_end_matches('\0')
603            .trim_end_matches('0')
604            .to_string();
605
606        if received_command == expected_command {
607            Ok(response_body_size)
608        } else {
609            Err(NanonisError::CommandMismatch {
610                expected: expected_command.to_string(),
611                actual: received_command,
612            })
613        }
614    }
615}