osrscache/
checksum.rs

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