Skip to content

Commit 379d0a1

Browse files
committed
Remove dependency on crc_any
The crc_any is not currently imported in Chromium, and since the dcsctp library only uses a very small part of it, reimplement this ourselves. In contrast to the crc_any crate, we generate the tables by the compiler, but similarly to it, we process 8 bytes at a time, so performance should be identical. The correctness of this algorithm is guaranteed by test vectors and with the recorded packet captures that exist in other unit tests. We could add an optional feature to add a dependency on the crc32c crate, which adds hardware accelerated support for some platforms, but that library is not imported in Chromium and other monorepos. Note that the crc32c checksum becomes less relevant with support for https://datatracker.ietf.org/doc/rfc9653/, which his library has, and will use if configured and when both sides support it.
1 parent cb05466 commit 379d0a1

File tree

6 files changed

+172
-23
lines changed

6 files changed

+172
-23
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ and this project adheres to
1010

1111
## 0.1.7 - 2026-01-21
1212

13+
### Changed
14+
15+
- Removed dependency on `crc_any` crate.
16+
1317
### Fixed
1418

1519
- Fixed a socket handover bug.

Cargo.lock

Lines changed: 0 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ license = "Apache-2.0"
88
repository = "https://github.com/webrtc/dcsctp"
99

1010
[workspace.dependencies]
11-
crc-any = "2.5.0"
1211
cxx = "1.0.192"
1312
cxx-build = "1.0.192"
1413
dcsctp = { path = "." }
@@ -49,7 +48,6 @@ license = { workspace = true }
4948
repository = { workspace = true }
5049

5150
[dependencies]
52-
crc-any = { workspace = true }
5351
cxx = { workspace = true, optional = true }
5452
log = { workspace = true }
5553
rand = { workspace = true }

src/packet/crc32c.rs

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// Copyright 2026 The dcSCTP Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// See <https://datatracker.ietf.org/doc/html/rfc9260#appendix-A>.
16+
17+
// CRC-32C (iSCSI) polynomial in reversed bit order.
18+
const CRC32C_POLY: u32 = 0x82F63B78;
19+
20+
const fn generate_tables() -> [[u32; 256]; 8] {
21+
let mut tables = [[0u32; 256]; 8];
22+
let mut i = 0;
23+
24+
// Generate the first table (for single byte processing).
25+
while i < 256 {
26+
let mut crc = i as u32;
27+
let mut j = 0;
28+
while j < 8 {
29+
if (crc & 1) != 0 {
30+
crc = (crc >> 1) ^ CRC32C_POLY;
31+
} else {
32+
crc >>= 1;
33+
}
34+
j += 1;
35+
}
36+
tables[0][i] = crc;
37+
i += 1;
38+
}
39+
40+
// Generate subsequent tables (for 8 bytes at a time).
41+
let mut k = 1;
42+
while k < 8 {
43+
let mut i = 0;
44+
while i < 256 {
45+
let prev = tables[k - 1][i];
46+
tables[k][i] = (prev >> 8) ^ tables[0][(prev & 0xff) as usize];
47+
i += 1;
48+
}
49+
k += 1;
50+
}
51+
tables
52+
}
53+
54+
const CRC32C_LOOKUP_TABLE: [[u32; 256]; 8] = generate_tables();
55+
56+
pub struct Crc32c(u32);
57+
58+
impl Crc32c {
59+
pub fn new() -> Self {
60+
Self(0xffffffff)
61+
}
62+
63+
pub fn digest(&mut self, data: &[u8]) {
64+
let mut crc = self.0;
65+
let mut iter = data.chunks_exact(8);
66+
67+
// Process 8 bytes at a time.
68+
for chunk in iter.by_ref() {
69+
let current_chunk = u64::from_le_bytes(chunk.try_into().unwrap());
70+
let idx = (crc as u64) ^ current_chunk;
71+
72+
crc = CRC32C_LOOKUP_TABLE[7][(idx as u8) as usize]
73+
^ CRC32C_LOOKUP_TABLE[6][((idx >> 8) as u8) as usize]
74+
^ CRC32C_LOOKUP_TABLE[5][((idx >> 16) as u8) as usize]
75+
^ CRC32C_LOOKUP_TABLE[4][((idx >> 24) as u8) as usize]
76+
^ CRC32C_LOOKUP_TABLE[3][((idx >> 32) as u8) as usize]
77+
^ CRC32C_LOOKUP_TABLE[2][((idx >> 40) as u8) as usize]
78+
^ CRC32C_LOOKUP_TABLE[1][((idx >> 48) as u8) as usize]
79+
^ CRC32C_LOOKUP_TABLE[0][((idx >> 56) as u8) as usize];
80+
}
81+
82+
// Process remaining bytes.
83+
for &byte in iter.remainder() {
84+
crc = (crc >> 8) ^ CRC32C_LOOKUP_TABLE[0][((crc ^ byte as u32) & 0xff) as usize];
85+
}
86+
87+
self.0 = crc;
88+
}
89+
90+
pub fn value(&self) -> u32 {
91+
!self.0
92+
}
93+
}
94+
95+
#[cfg(test)]
96+
mod tests {
97+
use super::*;
98+
99+
#[test]
100+
fn test_crc32c_empty() {
101+
let mut crc = Crc32c::new();
102+
crc.digest(&[]);
103+
assert_eq!(crc.value(), 0x00000000);
104+
}
105+
106+
#[test]
107+
fn test_table_0() {
108+
// See the table at <https://datatracker.ietf.org/doc/html/rfc9260#appendix-A>.
109+
assert_eq!(CRC32C_LOOKUP_TABLE[0][0], 0x00000000);
110+
assert_eq!(CRC32C_LOOKUP_TABLE[0][1], 0xf26b8303);
111+
assert_eq!(CRC32C_LOOKUP_TABLE[0][255], 0xad7d5351);
112+
}
113+
114+
#[test]
115+
fn test_crc32c_vectors() {
116+
// Standard test vectors for CRC32c (Castagnoli)
117+
// Check "123456789"
118+
let mut crc = Crc32c::new();
119+
crc.digest(b"123456789");
120+
assert_eq!(crc.value(), 0xe3069283);
121+
122+
// 4 bytes (less than 8)
123+
let mut crc = Crc32c::new();
124+
crc.digest(b"1234");
125+
assert_eq!(crc.value(), 0xf63af4ee);
126+
127+
// 8 bytes (exactly one chunk)
128+
let mut crc = Crc32c::new();
129+
crc.digest(b"12345678");
130+
assert_eq!(crc.value(), 0x6087809a);
131+
}
132+
133+
#[test]
134+
fn test_vs_reference() {
135+
// See <https://datatracker.ietf.org/doc/html/rfc9260#appendix-A>.
136+
fn reference_crc32c(data: &[u8]) -> u32 {
137+
let mut crc: u32 = 0xffffffff;
138+
for &byte in data {
139+
crc = (crc >> 8) ^ CRC32C_LOOKUP_TABLE[0][((crc ^ byte as u32) & 0xff) as usize];
140+
}
141+
!crc
142+
}
143+
144+
// Test various lengths from 0 to 100
145+
for len in 0..100 {
146+
// Create determinstic "random" data
147+
let data: Vec<u8> = (0..len).map(|i| (i * 17) as u8).collect();
148+
149+
let mut crc = Crc32c::new();
150+
crc.digest(&data);
151+
152+
let ref_val = reference_crc32c(&data);
153+
assert_eq!(crc.value(), ref_val, "Failed for length {}", len);
154+
}
155+
156+
// Test a larger buffer to ensure no issues with multiple chunks
157+
let data: Vec<u8> = (0..1024).map(|i| (i * 13) as u8).collect();
158+
let mut crc = Crc32c::new();
159+
crc.digest(&data);
160+
assert_eq!(crc.value(), reference_crc32c(&data));
161+
}
162+
}

src/packet/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub(crate) mod chunk_validators;
2020
pub(crate) mod cookie_ack_chunk;
2121
pub(crate) mod cookie_echo_chunk;
2222
pub(crate) mod cookie_received_while_shutting_down;
23+
pub(crate) mod crc32c;
2324
pub(crate) mod data;
2425
pub(crate) mod data_chunk;
2526
pub(crate) mod error_causes;

src/packet/sctp_packet.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ use crate::packet::AsSerializableTlv;
2121
use crate::packet::ChunkParseError;
2222
use crate::packet::chunk::Chunk;
2323
use crate::packet::chunk::RawChunk;
24+
use crate::packet::crc32c::Crc32c;
2425
use crate::packet::ensure;
2526
use crate::packet::read_u16_be;
2627
use crate::packet::read_u32_be;
2728
use crate::packet::write_u16_be;
2829
use crate::packet::write_u32_be;
29-
use crc_any::CRCu32;
3030
use thiserror::Error;
3131

3232
pub const COMMON_HEADER_SIZE: usize = 12;
@@ -111,11 +111,11 @@ impl SctpPacket {
111111
} else {
112112
const FOUR_ZEROES: &[u8] = &[0, 0, 0, 0];
113113
// Verify the checksum. The checksum field must be zero when that's done.
114-
let mut crc = CRCu32::crc32c();
114+
let mut crc = Crc32c::new();
115115
crc.digest(&data[0..8]);
116116
crc.digest(FOUR_ZEROES);
117117
crc.digest(&data[12..]);
118-
let checksum = crc.get_crc().to_be();
118+
let checksum = crc.value().to_be();
119119
ensure!(checksum == common_header.checksum, PacketParseError::InvalidChecksum);
120120
}
121121

@@ -202,9 +202,9 @@ impl SctpPacketBuilder {
202202
pub fn build(&mut self) -> Vec<u8> {
203203
let mut out = Vec::<u8>::new();
204204
if self.write_checksum && !self.data.is_empty() {
205-
let mut crc = CRCu32::crc32c();
205+
let mut crc = Crc32c::new();
206206
crc.digest(&self.data);
207-
let checksum = crc.get_crc().to_be();
207+
let checksum = crc.value().to_be();
208208
write_u32_be!(&mut self.data[8..12], checksum);
209209
}
210210
std::mem::swap(&mut self.data, &mut out);

0 commit comments

Comments
 (0)