//! This is a simple QR encoder for DRM panic. //! //! It is called from a panic handler, so it should't allocate memory and //! does all the work on the stack or on the provided buffers. For //! simplification, it only supports low error correction, and applies the //! first mask (checkerboard). It will draw the smallest QR code that can //! contain the string passed as parameter. To get the most compact //! QR code, the start of the URL is encoded as binary, and the //! compressed kmsg is encoded as numeric. //! //! 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 7 bytes of input into 17 decimal digits, and then uses the //! efficient numeric encoding, that encode 3 decimal digits into //! 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. This is the same algorithm used by Fido v2.2 QR-initiated //! authentication specification. //! //! Inspired by these 3 projects, all under MIT license: //! //! * <https://github.com/kennytm/qrcode-rust> //! * <https://github.com/erwanvivien/fast_qr> //! * <https://github.com/bjguillot/qr>
/// Format info for low quality ECC. const FORMAT_INFOS_QR_L: [u16; 8] = [ 0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976,
];
impl Version { /// Returns the smallest QR version than can hold these segments. fn from_segments(segments: &[&Segment<'_>]) -> Option<Version> {
(1..=40)
.map(Version)
.find(|&v| v.max_data() * 8 >= segments.iter().map(|s| s.total_size_bits(v)).sum())
}
/// Number of bits to encode characters in numeric mode. const NUM_CHARS_BITS: [usize; 4] = [0, 4, 7, 10]; /// 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];
/// Returns the size of the length field in bits, depending on QR Version. fn length_bits_count(&self, version: Version) -> usize { let Version(v) = version; matchself {
Segment::Binary(_) => match v { 1..=9 => 8,
_ => 16,
},
Segment::Numeric(_) => match v { 1..=9 => 10, 10..=26 => 12,
_ => 14,
},
}
}
/// Number of characters in the segment. fn character_count(&self) -> usize { matchself {
Segment::Binary(data) => data.len(),
Segment::Numeric(data) => { let last_chars = BYTES_TO_DIGITS[data.len() % 7]; // 17 decimal numbers per 7bytes + remainder. 17 * (data.len() / 7) + last_chars
}
}
}
// On arm32 architecture, dividing an `u64` by a constant will generate a call // to `__aeabi_uldivmod` which is not present in the kernel. // So use the multiply by inverse method for this architecture. fn div10(val: u64) -> u64 { if cfg!(target_arch = "arm") { let val_h = val >> 32; let val_l = val & 0xFFFFFFFF; let b_h: u64 = 0x66666666; let b_l: u64 = 0x66666667;
impl Iterator for SegmentIterator<'_> { type Item = (u16, usize);
fn next(&mutself) -> Option<Self::Item> { matchself.segment {
Segment::Binary(data) => { ifself.offset < data.len() { let byte = u16::from(data[self.offset]); self.offset += 1;
Some((byte, 8))
} else {
None
}
}
Segment::Numeric(data) => { ifself.decfifo.len < 3 && self.offset < data.len() { // If there are less than 3 decimal digits in the fifo, // take the next 7 bytes of input, and push them to the fifo. letmut 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); self.decfifo.push(chunk, BYTES_TO_DIGITS[len]); self.offset += len;
} self.decfifo.pop3()
}
}
}
}
/// Data to be put in the QR code, with correct segment encoding, padding, and /// Error Code Correction. impl EncodedMsg<'_> { fn new<'a>(segments: &[&Segment<'_>], data: &'a mut [u8]) -> Option<EncodedMsg<'a>> { let version = Version::from_segments(segments)?; let ec_size = version.ec_size(); let g1_blocks = version.g1_blocks(); let g2_blocks = version.g2_blocks(); let g1_blk_size = version.g1_blk_size(); let g2_blk_size = g1_blk_size + 1; let poly = version.poly();
/// Push bits of data at an offset (in bits). fn push(&mutself, offset: &mut usize, bits: (u16, usize)) { let (number, len_bits) = bits; let byte_off = *offset / 8; let bit_off = *offset % 8; let b = bit_off + len_bits;
for s in segments.iter() { self.push(&mut offset, s.get_header()); self.push(&mut offset, s.get_length_field(self.version)); for bits in s.iter() { self.push(&mut offset, bits);
}
} self.push(&mut offset, (MODE_STOP, 4));
let pad_offset = offset.div_ceil(8); for i in pad_offset..self.version.max_data() { self.data[i] = PADDING[(i & 1) ^ (pad_offset & 1)];
}
}
/// Iterator, to retrieve the data in the interleaved order needed by QR code. struct EncodedMsgIterator<'a> {
em: &'a EncodedMsg<'a>,
offset: usize,
}
impl Iterator for EncodedMsgIterator<'_> { type Item = u8;
/// Send the bytes in interleaved mode, first byte of first block of group1, /// then first byte of second block of group1, ... fn next(&mutself) -> Option<Self::Item> { let em = self.em; let blocks = em.g1_blocks + em.g2_blocks; let g1_end = em.g1_blocks * em.g1_blk_size; let g2_end = g1_end + em.g2_blocks * em.g2_blk_size; let ec_end = g2_end + em.ec_size * blocks;
ifself.offset >= ec_end { return None;
}
let offset = ifself.offset < em.g1_blk_size * blocks { // group1 and group2 interleaved let blk = self.offset % blocks; let blk_off = self.offset / blocks; if blk < em.g1_blocks {
blk * em.g1_blk_size + blk_off
} else {
g1_end + em.g2_blk_size * (blk - em.g1_blocks) + blk_off
}
} elseifself.offset < g2_end { // last byte of group2 blocks let blk2 = self.offset - blocks * em.g1_blk_size;
em.g1_blk_size * em.g1_blocks + blk2 * em.g2_blk_size + em.g2_blk_size - 1
} else { // EC blocks let ec_offset = self.offset - g2_end; let blk = ec_offset % blocks; let blk_off = ec_offset / blocks;
/// A QR code image, encoded as a linear binary framebuffer. /// 1 bit per module (pixel), each new line start at next byte boundary. /// Max width is 177 for V40 QR code, so `u8` is enough for coordinate. struct QrImage<'a> {
data: &'a mut [u8],
width: u8,
stride: u8,
version: Version,
}
impl QrImage<'_> { fn new<'a, 'b>(em: &'b EncodedMsg<'b>, qrdata: &'a mut [u8]) -> QrImage<'a> { let width = em.version.width(); let stride = width.div_ceil(8); let data = qrdata;
/// Set pixel to light color. fn set(&mutself, x: u8, y: u8) { let off = y as usize * self.stride as usize + x as usize / 8; letmut v = self.data[off];
v |= 0x80 >> (x % 8); self.data[off] = v;
}
/// Invert a module color. fn xor(&mutself, x: u8, y: u8) { let off = y as usize * self.stride as usize + x as usize / 8; self.data[off] ^= 0x80 >> (x % 8);
}
/// Draw a light square at (x, y) top left corner. fn draw_square(&mutself, x: u8, y: u8, size: u8) { for k in0..size { self.set(x + k, y); self.set(x, y + k + 1); self.set(x + size, y + k); self.set(x + k + 1, y + size);
}
}
// Finder pattern: 3 8x8 square at the corners. fn draw_finders(&mutself) { self.draw_square(1, 1, 4); self.draw_square(self.width - 6, 1, 4); self.draw_square(1, self.width - 6, 4); for k in0..8 { self.set(k, 7); self.set(self.width - k - 1, 7); self.set(k, self.width - 8);
} for k in0..7 { self.set(7, k); self.set(self.width - 8, k); self.set(7, self.width - 1 - k);
}
}
fn is_finder(&self, x: u8, y: u8) -> bool { let end = self.width - 8; #[expect(clippy::nonminimal_bool)]
{
(x < 8 && y < 8) || (x < 8 && y >= end) || (x >= end && y < 8)
}
}
// Alignment pattern: 5x5 squares in a grid. fn draw_alignments(&mutself) { let positions = self.version.alignment_pattern(); for &x in positions.iter() { for &y in positions.iter() { if !self.is_finder(x, y) { self.draw_square(x - 1, y - 1, 2);
}
}
}
}
fn is_alignment(&self, x: u8, y: u8) -> bool { let positions = self.version.alignment_pattern(); for &ax in positions.iter() { for &ay in positions.iter() { ifself.is_finder(ax, ay) { continue;
} if x >= ax - 2 && x <= ax + 2 && y >= ay - 2 && y <= ay + 2 { returntrue;
}
}
} false
}
// Timing pattern: 2 dotted line between the finder patterns. fn draw_timing_patterns(&mutself) { let end = self.width - 8;
for x in (9..end).step_by(2) { self.set(x, 6); self.set(6, x);
}
}
fn is_timing(&self, x: u8, y: u8) -> bool {
x == 6 || y == 6
}
// Mask info: 15 bits around the finders, written twice for redundancy. fn draw_maskinfo(&mutself) { let info: u16 = FORMAT_INFOS_QR_L[0]; letmut skip = 0;
for k in0..7 { if k == 6 {
skip = 1;
} if info & (1 << (14 - k)) == 0 { self.set(k + skip, 8); self.set(8, self.width - 1 - k);
}
}
skip = 0; for k in0..8 { if k == 2 {
skip = 1;
} if info & (1 << (7 - k)) == 0 { self.set(8, 8 - skip - k); self.set(self.width - 8 + k, 8);
}
}
}
fn is_maskinfo(&self, x: u8, y: u8) -> bool { let end = self.width - 8; // Count the dark module as mask info.
(x <= 8 && y == 8) || (y <= 8 && x == 8) || (x == 8 && y >= end) || (x >= end && y == 8)
}
// Version info: 18bits written twice, close to the finders. fn draw_version_info(&mutself) { let vinfo = self.version.version_info(); let pos = self.width - 11;
if vinfo != 0 { for x in0..3 { for y in0..6 { if vinfo & (1 << (x + y * 3)) == 0 { self.set(x + pos, y); self.set(y, x + pos);
}
}
}
}
}
fn is_version_info(&self, x: u8, y: u8) -> bool { let vinfo = self.version.version_info(); let pos = self.width - 11;
vinfo != 0 && ((x >= pos && x < pos + 3 && y < 6) || (y >= pos && y < pos + 3 && x < 6))
}
/// Returns true if the module is reserved (Not usable for data and EC). fn is_reserved(&self, x: u8, y: u8) -> bool { self.is_alignment(x, y)
|| self.is_finder(x, y)
|| self.is_timing(x, y)
|| self.is_maskinfo(x, y)
|| self.is_version_info(x, y)
}
/// Last module to draw, at bottom left corner. fn is_last(&self, x: u8, y: u8) -> bool {
x == 0 && y == self.width - 1
}
/// Move to the next module according to QR code order. /// /// From bottom right corner, to bottom left corner. fn next(&self, x: u8, y: u8) -> (u8, u8) { let x_adj = if x <= 6 { x + 1 } else { x }; let column_type = (self.width - x_adj) % 4;
match column_type { 2if y > 0 => (x + 1, y - 1), 0if y < self.width - 1 => (x + 1, y + 1), 0 | 2if x == 7 => (x - 2, y),
_ => (x - 1, y),
}
}
/// Find next module that can hold data. fn next_available(&self, x: u8, y: u8) -> (u8, u8) { let (mut x, mut y) = self.next(x, y); whileself.is_reserved(x, y) && !self.is_last(x, y) {
(x, y) = self.next(x, y);
}
(x, y)
}
fn draw_data(&mutself, data: impl Iterator<Item = u8>) { let (mut x, mut y) = (self.width - 1, self.width - 1); for byte in data { for s in0..8 { if byte & (0x80 >> s) == 0 { self.set(x, y);
}
(x, y) = self.next_available(x, y);
}
} // Set the remaining modules (0, 3 or 7 depending on version). // because 0 correspond to a light module. while !self.is_last(x, y) { if !self.is_reserved(x, y) { self.set(x, y);
}
(x, y) = self.next(x, y);
}
}
/// Apply checkerboard mask to all non-reserved modules. fn apply_mask(&mutself) { for x in0..self.width { for y in0..self.width { if (x ^ y) % 2 == 0 && !self.is_reserved(x, y) { self.xor(x, y);
}
}
}
}
/// Draw the QR code with the provided data iterator. fn draw_all(&mutself, data: impl Iterator<Item = u8>) { // First clear the table, as it may have already some data. self.clear(); self.draw_finders(); self.draw_alignments(); self.draw_timing_patterns(); self.draw_version_info(); self.draw_data(data); self.draw_maskinfo(); self.apply_mask();
}
}
/// C entry point for the rust QR Code generator. /// /// Write the QR code image in the data buffer, and return the QR code width, /// or 0, if the data doesn't fit in a QR code. /// /// * `url`: The base URL of the QR code. It will be encoded as Binary segment. /// * `data`: A pointer to the binary data, to be encoded. if URL is NULL, it /// will be encoded as binary segment, otherwise it will be encoded /// efficiently as a numeric segment, and appended to the URL. /// * `data_len`: Length of the data, that needs to be encoded, must be less /// than `data_size`. /// * `data_size`: Size of data buffer, it should be at least 4071 bytes to hold /// a V40 QR code. It will then be overwritten with the QR code image. /// * `tmp`: A temporary buffer that the QR code encoder will use, to write the /// segments and ECC. /// * `tmp_size`: Size of the temporary buffer, it must be at least 3706 bytes /// long for V40. /// /// # Safety /// /// * `url` must be null or point at a nul-terminated string. /// * `data` must be valid for reading and writing for `data_size` bytes. /// * `tmp` must be valid for reading and writing for `tmp_size` bytes. /// /// They must remain valid for the duration of the function call. #[export] pubunsafeextern"C"fn drm_panic_qr_generate(
url: *const kernel::ffi::c_char,
data: *mut u8,
data_len: usize,
data_size: usize,
tmp: *mut u8,
tmp_size: usize,
) -> u8 { if data_size < 4071 || tmp_size < 3706 || data_len > data_size { return0;
} // SAFETY: The caller ensures that `data` is a valid pointer for reading and // writing `data_size` bytes. let data_slice: &mut [u8] = unsafe { core::slice::from_raw_parts_mut(data, data_size) }; // SAFETY: The caller ensures that `tmp` is a valid pointer for reading and // writing `tmp_size` bytes. let tmp_slice: &mut [u8] = unsafe { core::slice::from_raw_parts_mut(tmp, tmp_size) }; if url.is_null() { match EncodedMsg::new(&[&Segment::Binary(&data_slice[0..data_len])], tmp_slice) {
None => 0,
Some(em) => { let qr_image = QrImage::new(&em, data_slice);
qr_image.width
}
}
} else { // SAFETY: The caller ensures that `url` is a valid pointer to a // nul-terminated string. let url_cstr: &CStr = unsafe { CStr::from_char_ptr(url) }; let segments = &[
&Segment::Binary(url_cstr.as_bytes()),
&Segment::Numeric(&data_slice[0..data_len]),
]; match EncodedMsg::new(segments, tmp_slice) {
None => 0,
Some(em) => { let qr_image = QrImage::new(&em, data_slice);
qr_image.width
}
}
}
}
/// Returns the maximum data size that can fit in a QR code of this version. /// * `version`: QR code version, between 1-40. /// * `url_len`: Length of the URL. /// /// * If `url_len` > 0, remove the 2 segments header/length and also count the /// conversion to numeric segments. /// * If `url_len` = 0, only removes 3 bytes for 1 binary segment. /// /// # Safety /// /// Always safe to call. // Required to be unsafe due to the `#[export]` annotation. #[export] pubunsafeextern"C"fn drm_panic_qr_max_data_size(version: u8, url_len: usize) -> usize { #[expect(clippy::manual_range_contains)] if version < 1 || version > 40 { return0;
} let max_data = Version(version as usize).max_data();
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.