rscache/checksum.rs
1//! Validator for the cache.
2//!
3//! # Example
4//!
5//! ```
6//! # use rscache::{Cache, error::Error};
7//! use rscache::checksum::Checksum;
8//!
9//! # fn main() -> Result<(), Error> {
10//! # let cache = Cache::new("./data/osrs_cache")?;
11//! # let client_crcs = vec![1593884597, 1029608590, 16840364, 4209099954, 3716821437, 165713182, 686540367,
12//! # 4262755489, 2208636505, 3047082366, 586413816, 2890424900, 3411535427, 3178880569,
13//! # 153718440, 3849392898, 3628627685, 2813112885, 1461700456, 2751169400, 2927815226];
14//! // Either one works
15//! let checksum = cache.checksum()?;
16//! let checksum = Checksum::new(&cache)?;
17//!
18//! checksum.validate(&client_crcs)?;
19//!
20//! // Encode the checksum with the OSRS protocol.
21//! let buffer = checksum.encode()?;
22//! # Ok(())
23//! # }
24//! ```
25
26use std::{borrow::Borrow, iter::IntoIterator};
27use std::slice::Iter;
28
29use crate::{error::ValidateError, Cache};
30use nom::{combinator::cond, number::complete::be_u32, Parser};
31use runefs::{
32 codec::{Buffer, Encoded},
33 REFERENCE_TABLE_ID,
34};
35
36#[cfg(feature = "rs3")]
37use num_bigint::{BigInt, Sign};
38#[cfg(feature = "serde")]
39use serde::{Deserialize, Serialize};
40#[cfg(feature = "rs3")]
41use whirlpool::{Digest, Whirlpool};
42
43/// Each entry in the checksum is mapped to an [`Index`](runefs::Index).
44#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
45#[cfg_attr(not(feature = "rs3"), derive(Default))]
46#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
47pub struct Entry {
48 pub(crate) crc: u32,
49 pub(crate) version: u32,
50 #[cfg(feature = "rs3")]
51 pub(crate) hash: Vec<u8>,
52}
53
54/// Validator for the `Cache`.
55///
56/// Used to validate cache index files. It contains a list of entries, one entry for each index file.
57///
58/// In order to create a `Checksum` you can either use the [`checksum`](crate::Cache::checksum) function on `Cache` or
59/// use [`new`](Checksum::new) and pass in a reference to an exisiting cache. They both achieve the same result.
60#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
61#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
62pub struct Checksum {
63 index_count: usize,
64 entries: Vec<Entry>,
65}
66
67impl Checksum {
68 /// Generate a checksum based on the given cache.
69 ///
70 /// # Errors
71 ///
72 /// Decoding of a index buffer fails, this is considered a bug.
73 pub fn new(cache: &Cache) -> crate::Result<Self> {
74 Ok(Self {
75 index_count: cache.indices.count(),
76 entries: Self::entries(cache)?,
77 })
78 }
79
80 fn entries(cache: &Cache) -> crate::Result<Vec<Entry>> {
81 let entries: Vec<Entry> = (0..cache.indices.count())
82 .filter_map(|idx_id| cache.read(REFERENCE_TABLE_ID, idx_id as u32).ok())
83 .enumerate()
84 .map(|(idx_id, buffer)| -> crate::Result<Entry> {
85 if buffer.is_empty() || idx_id == 47 {
86 Ok(Entry::default())
87 } else {
88 // let (buffer, size) = if with_rsa {
89 // be_u8(buffer.as_slice())?
90 // } else {
91 // (buffer.as_slice(), (buffer.len() / 8) as u8)
92 // };
93
94 #[cfg(feature = "rs3")]
95 let hash = {
96 let mut hasher = Whirlpool::new();
97 hasher.update(&buffer);
98 hasher.finalize().as_slice().to_vec()
99 };
100
101 let checksum = crc32fast::hash(&buffer);
102
103 let data = buffer.decode()?;
104 let (_, version) = cond(data[0] >= 6, be_u32).parse(&data[1..5])?;
105 let version = version.unwrap_or(0);
106
107 Ok(Entry {
108 crc: checksum,
109 version,
110 #[cfg(feature = "rs3")]
111 hash,
112 })
113 }
114 })
115 .filter_map(crate::Result::ok)
116 .collect();
117
118 Ok(entries)
119 }
120
121 /// Consumes the `Checksum` and encodes it into a byte buffer.
122 ///
123 ///
124 /// Note: It defaults to OSRS. RS3 clients use RSA to encrypt
125 /// network traffic, which includes the checksum. When encoding for RS3 clients
126 /// use [`RsaChecksum`] instead.
127 ///
128 /// After encoding the checksum it can be sent to the client.
129 ///
130 /// # Errors
131 ///
132 /// Encoding of the formatted buffer fails, this is considered a bug.
133 pub fn encode(self) -> crate::Result<Buffer<Encoded>> {
134 let mut buffer = Vec::with_capacity(self.entries.len() * 8);
135
136 for entry in self.entries {
137 buffer.extend(u32::to_be_bytes(entry.crc));
138 buffer.extend(u32::to_be_bytes(entry.version));
139 }
140
141 // let mut buffer = codec::encode(Compression::None, &buffer, None)?;
142
143 // #[cfg(feature = "whirlpool")]
144 // {
145 // let mut hasher = Whirlpool::new();
146 // hasher.update(&buffer);
147 // let mut hash = hasher.finalize().as_slice().to_vec();
148 // hash.insert(0, 0);
149
150 // let rsa_keys = self.rsa_keys.as_ref().unwrap();
151 // let exp = BigInt::parse_bytes(rsa_keys.exponent, 10).unwrap_or_default();
152 // let mud = BigInt::parse_bytes(rsa_keys.modulus, 10).unwrap_or_default();
153 // let rsa = BigInt::from_bytes_be(Sign::Plus, &hash)
154 // .modpow(&exp, &mud)
155 // .to_bytes_be()
156 // .1;
157
158 // buffer.extend(rsa);
159 // }
160
161 // Ok(buffer)
162 // Ok(codec::encode(Compression::None, &buffer, None)?)
163 Ok(Buffer::from(buffer).encode()?)
164 }
165
166 /// Validates the given crcs from the client with the internal crcs of this cache.
167 ///
168 /// ```
169 /// # use rscache::{Cache, error::Error};
170 /// # use rscache::checksum::Checksum;
171 /// # fn main() -> Result<(), Error> {
172 /// # let cache = Cache::new("./data/osrs_cache")?;
173 /// let checksum = Checksum::new(&cache)?;
174 ///
175 /// let crcs = [
176 /// 1593884597, 1029608590, 16840364, 4209099954, 3716821437, 165713182, 686540367, 4262755489,
177 /// 2208636505, 3047082366, 586413816, 2890424900, 3411535427, 3178880569, 153718440,
178 /// 3849392898, 3628627685, 2813112885, 1461700456, 2751169400, 2927815226,
179 /// ];
180 ///
181 /// assert!(checksum.validate(crcs).is_ok());
182 /// # Ok(())
183 /// # }
184 /// ```
185 ///
186 /// # Errors
187 ///
188 /// When the lengths of the crc iterators don't match up because too many or too few indices
189 /// were shared between the client and the server, or if a crc value mismatches.
190 pub fn validate<I>(&self, crcs: I) -> Result<(), ValidateError>
191 where
192 I: IntoIterator,
193 I::Item: Borrow<u32>,
194 <I as IntoIterator>::IntoIter: ExactSizeIterator,
195 {
196 let crcs = crcs.into_iter();
197
198 if self.entries.len() != crcs.len() {
199 return Err(ValidateError::InvalidLength {
200 expected: self.entries.len(),
201 actual: crcs.len(),
202 });
203 }
204 for (index, (internal, external)) in self
205 .entries
206 .iter()
207 .map(|entry| &entry.crc)
208 .zip(crcs)
209 .enumerate()
210 {
211 if internal != external.borrow() {
212 return Err(ValidateError::InvalidCrc {
213 idx: index,
214 internal: *internal,
215 external: *external.borrow(),
216 });
217 }
218 }
219
220 Ok(())
221 }
222
223 #[allow(missing_docs)]
224 #[inline]
225 pub const fn index_count(&self) -> usize {
226 self.index_count
227 }
228
229 #[allow(missing_docs)]
230 #[inline]
231 pub fn iter(&self) -> Iter<'_, Entry> {
232 self.entries.iter()
233 }
234}
235
236/// A struct that holds both keys for RSA encryption.
237#[cfg(feature = "rs3")]
238#[cfg_attr(docsrs, doc(cfg(feature = "rs3")))]
239#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
240#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
241pub struct RsaKeys<'a> {
242 pub(crate) exponent: &'a [u8],
243 pub(crate) modulus: &'a [u8],
244}
245
246#[cfg(feature = "rs3")]
247#[cfg_attr(docsrs, doc(cfg(feature = "rs3")))]
248impl<'a> RsaKeys<'a> {
249 /// Generate a RSA key set with the given keys.
250 pub const fn new(exponent: &'a [u8], modulus: &'a [u8]) -> Self {
251 Self { exponent, modulus }
252 }
253
254 /// Encrypts the given hash.
255 // TODO: maybe make this panic if the exponent or modulus not line up
256 pub fn encrypt(&self, hash: &[u8]) -> Vec<u8> {
257 let exp = BigInt::parse_bytes(self.exponent, 10).unwrap_or_default();
258 let mud = BigInt::parse_bytes(self.modulus, 10).unwrap_or_default();
259 BigInt::from_bytes_be(Sign::Plus, hash)
260 .modpow(&exp, &mud)
261 .to_bytes_be()
262 .1
263 }
264}
265
266/// Wraps a general `Checksum` with the added benefit of encrypting
267/// the whirlpool hash into the checksum buffer.
268///
269/// # Example
270///
271/// ```
272/// # use rscache::{Cache, error::Error};
273/// use rscache::checksum::{RsaChecksum, RsaKeys};
274///
275/// # fn main() -> Result<(), Error> {
276/// # let cache = Cache::new("./data/osrs_cache")?;
277/// # const EXPONENT: &'static [u8] = b"5206580307236375668350588432916871591810765290737810323990754121164270399789630501436083337726278206128394461017374810549461689174118305784406140446740993";
278/// # const MODULUS: &'static [u8] = b"6950273013450460376345707589939362735767433035117300645755821424559380572176824658371246045200577956729474374073582306250298535718024104420271215590565201";
279/// let keys = RsaKeys::new(EXPONENT, MODULUS);
280///
281/// // Either one works
282/// let checksum = cache.checksum_with(keys)?;
283/// // let checksum = RsaChecksum::with_keys(&cache, keys)?;
284/// # Ok(())
285/// # }
286/// ```
287#[cfg(feature = "rs3")]
288#[cfg_attr(docsrs, doc(cfg(feature = "rs3")))]
289#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
290#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
291pub struct RsaChecksum<'a> {
292 checksum: Checksum,
293 #[cfg_attr(feature = "serde", serde(borrow))]
294 rsa_keys: RsaKeys<'a>,
295}
296
297#[cfg(feature = "rs3")]
298impl<'a> RsaChecksum<'a> {
299 /// Generate a checksum with RSA encryption support.
300 pub fn with_keys(cache: &Cache, rsa_keys: RsaKeys<'a>) -> crate::Result<Self> {
301 Ok(Self {
302 checksum: Checksum::new(cache)?,
303 rsa_keys,
304 })
305 }
306
307 /// Same as [`Checksum::encode`](Checksum::encode) but for RS3.
308 pub fn encode(self) -> crate::Result<Buffer<Encoded>> {
309 let index_count = self.checksum.index_count - 1;
310 let mut buffer = vec![0; 81 * index_count];
311
312 buffer[0] = index_count as u8;
313 for (index, entry) in self.checksum.entries.iter().enumerate() {
314 let offset = index * 80;
315 buffer[offset + 1..=offset + 4].copy_from_slice(&u32::to_be_bytes(entry.crc));
316 buffer[offset + 5..=offset + 8].copy_from_slice(&u32::to_be_bytes(entry.version));
317 buffer[offset + 9..=offset + 12].copy_from_slice(&u32::to_be_bytes(0));
318 buffer[offset + 13..=offset + 16].copy_from_slice(&u32::to_be_bytes(0));
319 buffer[offset + 17..=offset + 80].copy_from_slice(&entry.hash);
320 }
321
322 let mut hasher = Whirlpool::new();
323 hasher.update(&buffer);
324 let mut hash = hasher.finalize().as_slice().to_vec();
325 hash.insert(0, 0);
326
327 buffer.extend(self.rsa_keys.encrypt(&hash));
328
329 Ok(Buffer::from(buffer))
330 }
331}
332
333#[cfg(feature = "rs3")]
334impl<'a> From<(&'a [u8], &'a [u8])> for RsaKeys<'a> {
335 fn from(keys: (&'a [u8], &'a [u8])) -> Self {
336 RsaKeys::new(keys.0, keys.1)
337 }
338}
339
340impl IntoIterator for Checksum {
341 type Item = Entry;
342 type IntoIter = std::vec::IntoIter<Entry>;
343
344 #[inline]
345 fn into_iter(self) -> Self::IntoIter {
346 self.entries.into_iter()
347 }
348}
349
350impl<'a> IntoIterator for &'a Checksum {
351 type Item = &'a Entry;
352 type IntoIter = Iter<'a, Entry>;
353
354 #[inline]
355 fn into_iter(self) -> Self::IntoIter {
356 self.entries.iter()
357 }
358}
359
360#[cfg(feature = "rs3")]
361impl<'a> IntoIterator for RsaChecksum<'a> {
362 type Item = Entry;
363 type IntoIter = std::vec::IntoIter<Entry>;
364
365 #[inline]
366 fn into_iter(self) -> Self::IntoIter {
367 self.checksum.entries.into_iter()
368 }
369}
370
371#[cfg(feature = "rs3")]
372impl<'a> IntoIterator for &'a RsaChecksum<'a> {
373 type Item = &'a Entry;
374 type IntoIter = Iter<'a, Entry>;
375
376 #[inline]
377 fn into_iter(self) -> Self::IntoIter {
378 self.checksum.entries.iter()
379 }
380}
381
382#[cfg(feature = "rs3")]
383impl Default for Entry {
384 #[inline]
385 fn default() -> Self {
386 Self {
387 crc: 0,
388 version: 0,
389 hash: vec![0; 64],
390 }
391 }
392}