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}