Skip to content

Commit 34e9da5

Browse files
committed
chore: update version to 0.5.6 and add memchr usage in parser and stream modules
1 parent bf905bd commit 34e9da5

4 files changed

Lines changed: 275 additions & 23 deletions

File tree

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rsipstack"
3-
version = "0.5.5"
3+
version = "0.5.6"
44
edition = "2021"
55
description = "SIP Stack Rust library for building SIP applications"
66
license = "MIT"
@@ -34,6 +34,7 @@ hickory-resolver = { version = "0.25.2", features = [
3434
"tokio",
3535
], optional = true }
3636
bytes = "1.11.1"
37+
memchr = "2"
3738
futures-util = "0.3.32"
3839
parking_lot = "0.12"
3940
dashmap = "6"
@@ -59,6 +60,7 @@ tokio = { version = "1.50.0", features = ["time", "sync", "macros", "io-util"] }
5960
tokio = { version = "1.50.0", features = ["full"] }
6061

6162
[dev-dependencies]
63+
criterion = { version = "0.5", features = ["html_reports"] }
6264
dotenv = "0.15"
6365
tempfile = "3"
6466
sdp-rs = "0.2.1"

src/sip/headers/typed/parse_helpers.rs

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::sip::{
22
uri::{parse_params, parse_uri, Param, Uri},
33
Error,
44
};
5+
use memchr::memchr;
56
pub fn parse_display_uri_params_str(s: &str) -> Result<(Option<String>, Uri, Vec<Param>), Error> {
67
let s = s.trim();
78
if let Some(lt) = s.find('<') {
@@ -23,24 +24,28 @@ pub fn parse_display_uri_params_str(s: &str) -> Result<(Option<String>, Uri, Vec
2324
}
2425

2526
let mut uri_end = None;
27+
let bytes = s.as_bytes();
28+
// Pre-locate '@' once with memchr; used below to decide whether a ':'
29+
// introduces a scheme/port or just a userinfo separator.
30+
let at_pos = memchr(b'@', bytes);
2631
let mut in_userinfo = false;
27-
let mut chars = s.char_indices().peekable();
2832

29-
while let Some((i, c)) = chars.next() {
30-
match c {
31-
'@' if in_userinfo => {
33+
for (i, &b) in bytes.iter().enumerate() {
34+
match b {
35+
b'@' => {
3236
in_userinfo = false;
3337
}
34-
':' if !in_userinfo => {
35-
if i > 0 && !s[..i].contains('@') {
38+
b':' if !in_userinfo => {
39+
// A ':' before the '@' (or when there is no '@') means we are
40+
// in the userinfo section (scheme colon is already consumed by
41+
// the caller via parse_uri, so here it signals user:password).
42+
if at_pos.map_or(false, |at| i < at) {
3643
in_userinfo = true;
3744
}
3845
}
39-
';' => {
40-
if !in_userinfo {
41-
uri_end = Some(i);
42-
break;
43-
}
46+
b';' if !in_userinfo => {
47+
uri_end = Some(i);
48+
break;
4449
}
4550
_ => {}
4651
}
@@ -116,4 +121,68 @@ mod tests {
116121
assert_eq!(result.2.len(), 1);
117122
assert!(matches!(&result.2[0], Param::Tag(t) if t.value() == "12345"));
118123
}
124+
125+
// ── 无括号、有端口、无 @ ──────────────────────────────────────────────────
126+
127+
#[test]
128+
fn test_no_userinfo_with_port_and_params() {
129+
// sip:host:5060;transport=tcp — ':' before ';' but no '@', so
130+
// in_userinfo must remain false and ';' triggers uri_end.
131+
let result =
132+
parse_display_uri_params_str("sip:example.com:5060;transport=tcp").unwrap();
133+
assert_eq!(result.1.host_with_port.to_string(), "example.com:5060");
134+
assert_eq!(result.2.len(), 1);
135+
}
136+
137+
// ── 无括号、user:password@host ────────────────────────────────────────────
138+
139+
#[test]
140+
fn test_userinfo_password_no_brackets() {
141+
let result =
142+
parse_display_uri_params_str("sip:alice:secret@example.com;tag=abc").unwrap();
143+
let auth = result.1.auth.unwrap();
144+
assert_eq!(auth.user, "alice");
145+
assert_eq!(auth.password, Some("secret".into()));
146+
assert_eq!(result.2.len(), 1);
147+
}
148+
149+
// ── 无括号、纯 host(无 scheme、无 @、无 ';') ────────────────────────────
150+
151+
#[test]
152+
fn test_no_brackets_no_params_host_only() {
153+
let result = parse_display_uri_params_str("sip:example.com").unwrap();
154+
assert!(result.1.auth.is_none());
155+
assert_eq!(result.1.host_with_port.to_string(), "example.com");
156+
assert!(result.2.is_empty());
157+
}
158+
159+
// ── 有括号、display name 含引号 ──────────────────────────────────────────
160+
161+
#[test]
162+
fn test_display_name_with_quotes() {
163+
let result =
164+
parse_display_uri_params_str("\"Alice Smith\" <sip:alice@example.com>").unwrap();
165+
assert_eq!(result.0, Some("Alice Smith".to_string()));
166+
assert_eq!(result.1.to_string(), "sip:alice@example.com");
167+
assert!(result.2.is_empty());
168+
}
169+
170+
// ── 有括号、display name 为空 ────────────────────────────────────────────
171+
172+
#[test]
173+
fn test_empty_display_name_with_brackets() {
174+
let result = parse_display_uri_params_str("<sip:alice@example.com>;tag=xyz").unwrap();
175+
assert!(result.0.is_none());
176+
assert_eq!(result.2.len(), 1);
177+
}
178+
179+
// ── 多 ';' param(无括号) ────────────────────────────────────────────────
180+
181+
#[test]
182+
fn test_no_brackets_multiple_params_with_at() {
183+
let result =
184+
parse_display_uri_params_str("sip:bob@example.com;tag=1;expires=60;lr").unwrap();
185+
assert_eq!(result.1.auth.as_ref().unwrap().user, "bob");
186+
assert_eq!(result.2.len(), 3);
187+
}
119188
}

src/sip/parser.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::sip::{
44
uri::parse_uri,
55
Error, Method, StatusCode, Uri, Version,
66
};
7+
use memchr::{memchr, memmem};
78
pub fn parse_message(data: &[u8]) -> Result<SipMessage, Error> {
89
let sep = find_double_crlf(data)
910
.ok_or_else(|| Error::ParseError("SIP message: missing \\r\\n\\r\\n separator".into()))?;
@@ -123,8 +124,7 @@ fn parse_response_line(line: &str, headers: Headers, body: Vec<u8>) -> Result<Si
123124
}
124125

125126
fn find_double_crlf(data: &[u8]) -> Option<usize> {
126-
let needle = b"\r\n\r\n";
127-
data.windows(4).position(|w| w == needle)
127+
memmem::find(data, b"\r\n\r\n")
128128
}
129129

130130
fn split_crlf_lines(data: &[u8]) -> impl Iterator<Item = &str> {
@@ -146,8 +146,14 @@ impl<'a> Iterator for SplitCrLf<'a> {
146146
let start = self.pos;
147147
let rest = &self.data[start..];
148148

149-
let (end, next_pos) = if let Some(p) = rest.windows(2).position(|w| w == b"\r\n") {
150-
(start + p, start + p + 2)
149+
// Find '\n' with memchr, then check the preceding '\r'.
150+
let (end, next_pos) = if let Some(lf) = memchr(b'\n', rest) {
151+
let line_end = if lf > 0 && rest[lf - 1] == b'\r' {
152+
start + lf - 1
153+
} else {
154+
start + lf
155+
};
156+
(line_end, start + lf + 1)
151157
} else {
152158
(self.data.len(), self.data.len())
153159
};

src/transport/stream.rs

Lines changed: 182 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::{
77
Result,
88
};
99
use bytes::{Buf, BytesMut};
10+
use memchr::{memchr, memmem};
1011
use tokio::{
1112
io::{AsyncRead, AsyncWrite, AsyncWriteExt},
1213
sync::Mutex,
@@ -64,25 +65,24 @@ impl Decoder for SipCodec {
6465
return Ok(Some(SipCodecType::KeepaliveResponse));
6566
}
6667

67-
if let Some(headers_end) = src.windows(4).position(|w| w == b"\r\n\r\n") {
68+
if let Some(headers_end) = memmem::find(src, b"\r\n\r\n") {
6869
let headers = &src[..headers_end + 4]; // include CRLFCRLF
6970

7071
// Parse Content-Length as u32 without UTF-8 conversion
7172
let mut content_length: usize = 0;
7273
let mut start = 0;
7374
while start < headers.len() {
74-
// find end of line
75-
let mut end = start;
76-
while end < headers.len() && headers[end] != b'\n' {
77-
end += 1;
78-
}
75+
// find end of line with memchr
76+
let end = memchr(b'\n', &headers[start..])
77+
.map(|p| start + p)
78+
.unwrap_or(headers.len());
7979

8080
let mut line = &headers[start..end];
8181
if let Some(&b'\r') = line.last() {
8282
line = &line[..line.len().saturating_sub(1)];
8383
}
8484

85-
if let Some(colon) = line.iter().position(|&b| b == b':') {
85+
if let Some(colon) = memchr(b':', line) {
8686
let header = &line[..colon];
8787
let is_cl = if header.len() == CL_FULL_NAME.len()
8888
&& header
@@ -284,3 +284,178 @@ where
284284
lock.flush().await?;
285285
Ok(())
286286
}
287+
288+
#[cfg(test)]
289+
mod tests {
290+
use super::*;
291+
use crate::transport::connection::{KEEPALIVE_REQUEST, KEEPALIVE_RESPONSE};
292+
use bytes::BytesMut;
293+
use tokio_util::codec::Decoder;
294+
295+
fn make_codec() -> SipCodec {
296+
SipCodec::new()
297+
}
298+
299+
// Minimal valid INVITE with the given body.
300+
fn invite_bytes(body: &str) -> Vec<u8> {
301+
let msg = format!(
302+
concat!(
303+
"INVITE sip:bob@biloxi.com SIP/2.0\r\n",
304+
"Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776\r\n",
305+
"To: Bob <sip:bob@biloxi.com>\r\n",
306+
"From: Alice <sip:alice@atlanta.com>;tag=123\r\n",
307+
"Call-ID: abc@test\r\n",
308+
"CSeq: 1 INVITE\r\n",
309+
"Content-Type: application/sdp\r\n",
310+
"Content-Length: {}\r\n",
311+
"\r\n",
312+
"{}"
313+
),
314+
body.len(),
315+
body
316+
);
317+
msg.into_bytes()
318+
}
319+
320+
fn register_bytes() -> Vec<u8> {
321+
concat!(
322+
"REGISTER sip:registrar.example.com SIP/2.0\r\n",
323+
"Via: SIP/2.0/UDP bob:5060;branch=z9hG4bKnashds8\r\n",
324+
"To: Bob <sip:bob@example.com>\r\n",
325+
"From: Bob <sip:bob@example.com>;tag=456\r\n",
326+
"Call-ID: 843817637@998sdasdh09\r\n",
327+
"CSeq: 1 REGISTER\r\n",
328+
"Content-Length: 0\r\n",
329+
"\r\n",
330+
)
331+
.as_bytes()
332+
.to_vec()
333+
}
334+
335+
// ── 基本解码 ──────────────────────────────────────────────────────────────
336+
337+
#[test]
338+
fn decode_complete_message_no_body() {
339+
let mut codec = make_codec();
340+
let mut buf = BytesMut::from(register_bytes().as_slice());
341+
let result = codec.decode(&mut buf).unwrap();
342+
assert!(matches!(result, Some(SipCodecType::Message(_))));
343+
assert_eq!(buf.len(), 0, "buffer should be fully consumed");
344+
}
345+
346+
#[test]
347+
fn decode_complete_message_with_body() {
348+
let body = "v=0\r\no=- 0 0 IN IP4 127.0.0.1\r\n";
349+
let mut codec = make_codec();
350+
let mut buf = BytesMut::from(invite_bytes(body).as_slice());
351+
let result = codec.decode(&mut buf).unwrap();
352+
assert!(matches!(result, Some(SipCodecType::Message(_))));
353+
assert_eq!(buf.len(), 0);
354+
}
355+
356+
// ── 分包:header 尚未完整 ─────────────────────────────────────────────────
357+
358+
#[test]
359+
fn decode_returns_none_when_headers_incomplete() {
360+
let mut codec = make_codec();
361+
let full = register_bytes();
362+
// Feed only the first half of the message.
363+
let half = &full[..full.len() / 2];
364+
let mut buf = BytesMut::from(half);
365+
let result = codec.decode(&mut buf).unwrap();
366+
assert!(result.is_none(), "should wait for more data");
367+
// Buffer must be untouched.
368+
assert_eq!(buf.as_ref(), half);
369+
}
370+
371+
// ── 分包:body 尚未完整 ───────────────────────────────────────────────────
372+
373+
#[test]
374+
fn decode_returns_none_when_body_incomplete() {
375+
let body = "v=0\r\no=- 0 0 IN IP4 127.0.0.1\r\n";
376+
let mut codec = make_codec();
377+
let full = invite_bytes(body);
378+
// Feed headers + CRLFCRLF but only half the body.
379+
let headers_end = memmem::find(&full, b"\r\n\r\n").unwrap() + 4;
380+
let partial_body = body.len() / 2;
381+
let partial = &full[..headers_end + partial_body];
382+
let mut buf = BytesMut::from(partial);
383+
let result = codec.decode(&mut buf).unwrap();
384+
assert!(result.is_none());
385+
assert_eq!(buf.len(), partial.len());
386+
}
387+
388+
// ── 粘包:两条消息连在一起 ────────────────────────────────────────────────
389+
390+
#[test]
391+
fn decode_two_back_to_back_messages() {
392+
let mut codec = make_codec();
393+
let mut buf = BytesMut::new();
394+
buf.extend_from_slice(&register_bytes());
395+
buf.extend_from_slice(&register_bytes());
396+
397+
let first = codec.decode(&mut buf).unwrap();
398+
assert!(matches!(first, Some(SipCodecType::Message(_))));
399+
400+
let second = codec.decode(&mut buf).unwrap();
401+
assert!(matches!(second, Some(SipCodecType::Message(_))));
402+
403+
assert_eq!(buf.len(), 0);
404+
}
405+
406+
// ── Content-Length 短格式 'l' ─────────────────────────────────────────────
407+
408+
#[test]
409+
fn decode_short_content_length_header() {
410+
let body = "hello";
411+
let raw = format!(
412+
concat!(
413+
"INVITE sip:bob@example.com SIP/2.0\r\n",
414+
"Via: SIP/2.0/UDP pc;branch=z9hG4bK1\r\n",
415+
"To: <sip:bob@example.com>\r\n",
416+
"From: <sip:alice@example.com>;tag=1\r\n",
417+
"Call-ID: x@y\r\n",
418+
"CSeq: 1 INVITE\r\n",
419+
"l: {}\r\n",
420+
"\r\n",
421+
"{}"
422+
),
423+
body.len(),
424+
body
425+
);
426+
let mut codec = make_codec();
427+
let mut buf = BytesMut::from(raw.as_bytes());
428+
let result = codec.decode(&mut buf).unwrap();
429+
assert!(matches!(result, Some(SipCodecType::Message(_))));
430+
}
431+
432+
// ── Keepalive 帧 ──────────────────────────────────────────────────────────
433+
434+
#[test]
435+
fn decode_keepalive_request() {
436+
let mut codec = make_codec();
437+
let mut buf = BytesMut::from(KEEPALIVE_REQUEST.as_ref());
438+
let result = codec.decode(&mut buf).unwrap();
439+
assert!(matches!(result, Some(SipCodecType::KeepaliveRequest)));
440+
assert_eq!(buf.len(), 0);
441+
}
442+
443+
#[test]
444+
fn decode_keepalive_response() {
445+
let mut codec = make_codec();
446+
let mut buf = BytesMut::from(KEEPALIVE_RESPONSE.as_ref());
447+
let result = codec.decode(&mut buf).unwrap();
448+
assert!(matches!(result, Some(SipCodecType::KeepaliveResponse)));
449+
assert_eq!(buf.len(), 0);
450+
}
451+
452+
// ── 消息过大 ─────────────────────────────────────────────────────────────
453+
454+
#[test]
455+
fn decode_rejects_oversized_buffer() {
456+
let mut codec = make_codec();
457+
let mut buf = BytesMut::from(vec![b'X'; MAX_SIP_MESSAGE_SIZE + 1].as_slice());
458+
let result = codec.decode(&mut buf);
459+
assert!(result.is_err());
460+
}
461+
}

0 commit comments

Comments
 (0)