The current encoding, is done by converting 13bits of input into 4 decimal digits, that are then encoded efficiently using the numeric encoding of the QR code specification. The Fido v2.2 specification [1] uses a similar approach for its QR-initiated authentication. The only difference is that it converts 7 bytes (56bits) of input into 17 decimal digits. The benefit is that the algorithm doesn't require to split input bytes into 13bits chunk, and the ratio is a bit better. This improvement was proposed by Jó Ágila Bitsch in [2]. drm_panic is still young, and the QR code feature is not widely used, so it's still time to switch to a common algorithm, shared with a widely used standard. I also changed the name of the url parameter, from zl= to z=, so the website can keep backward compatibility if needed. [1] https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#hybrid-qr-initiated [2] https://github.com/kdj0c/panic_report/issues/2 Signed-off-by: Jocelyn Falempe <jfalempe@xxxxxxxxxx> --- drivers/gpu/drm/drm_panic.c | 2 +- drivers/gpu/drm/drm_panic_qr.rs | 104 ++++++++++---------------------- 2 files changed, 32 insertions(+), 74 deletions(-) diff --git a/drivers/gpu/drm/drm_panic.c b/drivers/gpu/drm/drm_panic.c index 0a9ecc1380d2..d88fd86263f6 100644 --- a/drivers/gpu/drm/drm_panic.c +++ b/drivers/gpu/drm/drm_panic.c @@ -712,7 +712,7 @@ static int drm_panic_get_qr_code_url(u8 **qr_image) char *kmsg; int max_qr_data_size, url_len; - url_len = snprintf(url, sizeof(url), CONFIG_DRM_PANIC_SCREEN_QR_CODE_URL "?a=%s&v=%s&zl=", + url_len = snprintf(url, sizeof(url), CONFIG_DRM_PANIC_SCREEN_QR_CODE_URL "?a=%s&v=%s&z=", utsname()->machine, utsname()->release); max_qr_data_size = drm_panic_qr_max_data_size(panic_qr_version, url_len); diff --git a/drivers/gpu/drm/drm_panic_qr.rs b/drivers/gpu/drm/drm_panic_qr.rs index 09500cddc009..939240ca8619 100644 --- a/drivers/gpu/drm/drm_panic_qr.rs +++ b/drivers/gpu/drm/drm_panic_qr.rs @@ -13,12 +13,13 @@ //! The binary data must be a valid URL parameter, so the easiest way is //! to use base64 encoding. But this wastes 25% of data space, so the //! whole stack trace won't fit in the QR code. So instead it encodes -//! every 13bits of input into 4 decimal digits, and then uses the +//! every 7 bytes of input into 17 decimal digits, and then uses the //! efficient numeric encoding, that encode 3 decimal digits into -//! 10bits. This makes 39bits of compressed data into 12 decimal digits, -//! into 40bits in the QR code, so wasting only 2.5%. And the numbers are +//! 10bits. This makes 168bits of compressed data into 51 decimal digits, +//! into 170bits in the QR code, so wasting only 1.17%. And the numbers are //! valid URL parameter, so the website can do the reverse, to get the -//! binary data. +//! binary data. This is the same algorithm used by Fido v2.2 QR-initiated +//! authentication specification. //! //! Inspired by these 3 projects, all under MIT license: //! @@ -26,7 +27,6 @@ //! * <https://github.com/erwanvivien/fast_qr> //! * <https://github.com/bjguillot/qr> -use core::cmp; use kernel::str::CStr; #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)] @@ -296,35 +296,11 @@ fn version_info(&self) -> u32 { /// Padding bytes. const PADDING: [u8; 2] = [236, 17]; -/// Get the next 13 bits of data, starting at specified offset (in bits). -fn get_next_13b(data: &[u8], offset: usize) -> Option<(u16, usize)> { - if offset < data.len() * 8 { - let size = cmp::min(13, data.len() * 8 - offset); - let byte_off = offset / 8; - let bit_off = offset % 8; - // `b` is 20 at max (`bit_off` <= 7 and `size` <= 13). - let b = (bit_off + size) as u16; - - let first_byte = (data[byte_off] << bit_off >> bit_off) as u16; - - let number = match b { - 0..=8 => first_byte >> (8 - b), - 9..=16 => (first_byte << (b - 8)) + (data[byte_off + 1] >> (16 - b)) as u16, - _ => { - (first_byte << (b - 8)) - + ((data[byte_off + 1] as u16) << (b - 16)) - + (data[byte_off + 2] >> (24 - b)) as u16 - } - }; - Some((number, size)) - } else { - None - } -} - /// Number of bits to encode characters in numeric mode. const NUM_CHARS_BITS: [usize; 4] = [0, 4, 7, 10]; -const POW10: [u16; 4] = [1, 10, 100, 1000]; +/// Number of decimal digits required to encode n bytes of binary data. +/// eg: you need 15 decimal digits to fit 6 bytes of binary data. +const BYTES_TO_DIGITS: [usize; 8] = [0, 3, 5, 8, 10, 13, 15, 17]; enum Segment<'a> { Numeric(&'a [u8]), @@ -360,13 +336,9 @@ fn character_count(&self) -> usize { match self { Segment::Binary(data) => data.len(), Segment::Numeric(data) => { - let data_bits = data.len() * 8; - let last_chars = match data_bits % 13 { - 1 => 1, - k => (k + 1) / 3, - }; - // 4 decimal numbers per 13bits + remainder. - 4 * (data_bits / 13) + last_chars + let last_chars = BYTES_TO_DIGITS[data.len() % 7]; + // 17 decimal numbers per 7bytes + remainder. + 17 * (data.len() / 7) + last_chars } } } @@ -403,7 +375,7 @@ fn iter(&self) -> SegmentIterator<'_> { struct SegmentIterator<'a> { segment: &'a Segment<'a>, offset: usize, - carry: u16, + carry: u64, carry_len: usize, } @@ -422,40 +394,26 @@ fn next(&mut self) -> Option<Self::Item> { } } Segment::Numeric(data) => { - if self.carry_len == 3 { - let out = (self.carry, NUM_CHARS_BITS[self.carry_len]); - self.carry_len = 0; - self.carry = 0; - Some(out) - } else if let Some((bits, size)) = get_next_13b(data, self.offset) { - self.offset += size; - let new_chars = match size { - 1 => 1, - k => (k + 1) / 3, - }; - if self.carry_len + new_chars > 3 { - self.carry_len = new_chars + self.carry_len - 3; - let out = ( - self.carry * POW10[new_chars - self.carry_len] - + bits / POW10[self.carry_len], - NUM_CHARS_BITS[3], - ); - self.carry = bits % POW10[self.carry_len]; - Some(out) - } else { - let out = ( - self.carry * POW10[new_chars] + bits, - NUM_CHARS_BITS[self.carry_len + new_chars], - ); - self.carry_len = 0; - Some(out) + if self.carry_len < 3 && self.offset < data.len() { + let mut buf = [0u8; 8]; + let len = 7.min(data.len() - self.offset); + buf[..len].copy_from_slice(&data[self.offset..self.offset + len]); + let chunk = u64::from_le_bytes(buf); + let pow = u64::pow(10, BYTES_TO_DIGITS[len] as u32); + self.carry = chunk + self.carry * pow; + self.offset += len; + self.carry_len += BYTES_TO_DIGITS[len]; + } + match self.carry_len { + 0 => None, + len => { + let out_len = 3.min(len); + self.carry_len -= out_len; + let pow = u64::pow(10, self.carry_len as u32); + let out = (self.carry / pow) as u16; + self.carry = self.carry % pow; + Some((out, NUM_CHARS_BITS[out_len])) } - } else if self.carry_len > 0 { - let out = (self.carry, NUM_CHARS_BITS[self.carry_len]); - self.carry_len = 0; - Some(out) - } else { - None } } } base-commit: c6eabbab359c156669e10d5dec3e71e80ff09bd2 -- 2.47.1