Repository: FactbirdHQ/atat (language: rust)

Issue:
Parsing issues
I discovered that the parser cannot handle a newline followed by a valid URC, which is needed to be able to recover from garbage data being received. 

Here is the failing (new) testcase (`digest.rs`)

```rust
    #[test]
    fn newline_garbage_followed_by_urc() {
        let mut digester = AtDigester::<UrcTestParser>::new();
        let mut buf = heapless::Vec::<u8, TEST_RX_BUF_LEN>::new();

        buf.extend_from_slice(
            b"\r\naa\r\n+UUSORD: 0,5\r\n", // 20 bytes
        )
            .unwrap();
        let (res, bytes) = digester.digest(&buf);
        assert_eq!((res, bytes), (DigestResult::Urc(b"+UUSORD: 0,5"), 20)); // fails with (None, 0)
        buf.rotate_left(bytes);
        buf.truncate(buf.len() - bytes);
        assert!(buf.is_empty());
    }
```

I'm happy to investigate this a bit further and possibly provide a fix, but one question first:
Shouldn't `digest` return the number of consumed bytes? Or what is the 2nd part of the return tuple? The trait has no documentation...  
Here is a reproducer:

```rust
    #[test]
    fn consumed_bytes_are_correct_with_prefixed_garbage() {
        let mut digester = AtDigester::<UrcTestParser>::new();
        let mut buf = heapless::Vec::<u8, TEST_RX_BUF_LEN>::new(
        buf.extend_from_slice(
            b"aaaa\r\n+UUSORD: 0,5\r\n", // 20 bytes
        )
            .unwrap();
        let (res, bytes) = digester.digest(&buf);

        assert_eq!((res, bytes), (DigestResult::Urc(b"+UUSORD: 0,5"), 20)); // fails. only 16 are consumed
        buf.rotate_left(bytes);
        buf.truncate(buf.len() - bytes);
        assert!(buf.is_empty()); // followup error
    }
```

EDIT: Ok the 2nd issue is probably fixed by:
```diff
diff --git a/atat/src/digest.rs b/atat/src/digest.rs
--- a/atat/src/digest.rs	(revision 242628d2ad2add452c909a036af036d83ea2fb5a)
+++ b/atat/src/digest.rs	(date 1727770394036)
@@ -128,7 +128,7 @@
 
         // 2. Match for URC's
         match P::parse(buf) {
-            Ok((urc, len)) => return (DigestResult::Urc(urc), len),
+            Ok((urc, len)) => return (DigestResult::Urc(urc), len + space_and_echo_bytes),
             Err(ParseError::Incomplete) => return incomplete,
             _ => {}
         }
```

EDIT: the 1st issue seems to be more tricky though






Discussion / hints:

Fix patch:
diff --git a/atat/src/digest.rs b/atat/src/digest.rs
index c1257fa..9e10dd3 100644
--- a/atat/src/digest.rs
+++ b/atat/src/digest.rs
@@ -26,6 +26,7 @@ impl From<nom::Err<nom::error::Error<&[u8]>>> for ParseError {
 }
 
 pub trait Digester {
+    /// Digest the input buffer and return the result and the number of bytes consumed.
     fn digest<'a>(&mut self, buf: &'a [u8]) -> (DigestResult<'a>, usize);
 }
 
@@ -128,7 +129,7 @@ impl<P: Parser> Digester for AtDigester<P> {
 
         // 2. Match for URC's
         match P::parse(buf) {
-            Ok((urc, len)) => return (DigestResult::Urc(urc), len),
+            Ok((urc, len)) => return (DigestResult::Urc(urc), len + space_and_echo_bytes),
             Err(ParseError::Incomplete) => return incomplete,
             _ => {}
         }
@@ -185,6 +186,14 @@ impl<P: Parser> Digester for AtDigester<P> {
             return (result, len + space_and_echo_bytes);
         }
 
+        // Handle '\r\n <Garbage> \r\n <Valid URC> \r\n' as parser::echo will only consume garbage BEFORE a \r\n
+        if buf.starts_with(b"\r\n") && buf.len() > 4 {
+            let (res, consumed) = self.digest(&buf[2..]);
+            if res != DigestResult::None {
+                return (res, space_and_echo_bytes + 2 + consumed);
+            }
+        }
+
         // No matches at all.
         incomplete
     }
@@ -827,6 +836,36 @@ mod test {
         assert!(buf.is_empty());
     }
 
+    #[test]
+    fn urc_prefixed_with_garbage() {
+        let mut digester = AtDigester::<UrcTestParser>::new();
+        let mut buf = heapless::Vec::<u8, TEST_RX_BUF_LEN>::new();
+
+        buf.extend_from_slice(b"aaaa\r\n+UUSORD: 0,5\r\n").unwrap();
+        let (res, bytes) = digester.digest(&buf);
+
+        assert_eq!((res, bytes), (DigestResult::Urc(b"+UUSORD: 0,5"), 20));
+        buf.rotate_left(bytes);
+        buf.truncate(buf.len() - bytes);
+        assert!(buf.is_empty());
+    }
+
+    #[test]
+    fn urc_prefixed_with_garbage_including_newline() {
+        let mut digester = AtDigester::<UrcTestParser>::new();
+        let mut buf = heapless::Vec::<u8, TEST_RX_BUF_LEN>::new();
+
+        buf.extend_from_slice(
+            b"a\r\na\r\n+UUSORD: 0,5\r\n", // 20 bytes
+        )
+        .unwrap();
+        let (res, bytes) = digester.digest(&buf);
+        assert_eq!((res, bytes), (DigestResult::Urc(b"+UUSORD: 0,5"), 20));
+        buf.rotate_left(bytes);
+        buf.truncate(buf.len() - bytes);
+        assert!(buf.is_empty());
+    }
+
     #[test]
     fn response_no_echo() {
         let mut digester = AtDigester::<UrcTestParser>::new();


Test patch: