• 0 Posts
  • 131 Comments
Joined 5 months ago
cake
Cake day: March 12th, 2025

help-circle




  • After a quick look, looks like it tries to split the (unencrypted) hostname into multiple packets, or at least scramble it slightly. I’m not sure how much it helps in practice, but it might help against naïve filtering/scanning, as the hostname is either sent in different packets, or split and sent unordered in the same packet. It probably only helps if encrypted client hello isn’t supported.

    TL;DR: If I’ve understood everything correctly, it just moves chunks of the plaintext hostname around & tries to split it into multiple packets.

    Note: Mostly based on comments, as it’s late & I’m too tired to parse too much cryptography code.

    Full source of the limit_chunks function, formatted with Rustfmt:

    const fn limit_chunks<'a>(
        left: (u64, &'a [u8]),
        right: (u64, &'a [u8]),
        limit: usize,
    ) -> ((u64, &'a [u8]), (u64, &'a [u8])) {
        let (left_offset, mut left) = left;
        let (mut right_offset, mut right) = right;
        if left.len() + right.len() <= limit {
            // Nothing to do. Both chunks will fit into one packet, meaning the SNI isn't spread
            // over multiple packets. But at least it's in two unordered CRYPTO frames.
        } else if left.len() <= limit {
            // `left` is short enough to fit into this packet. So send from the *end*
            // of `right`, so that the second half of the SNI is in another packet.
            let right_len = right.len() + left.len() - limit;
            right_offset += right_len as u64;
            (_, right) = right.split_at(right_len);
        } else if right.len() <= limit {
            // `right` is short enough to fit into this packet. So only send a part of `left`.
            // The SNI begins at the end of `left`, so send the beginnig of it in this packet.
            (left, _) = left.split_at(limit - right.len());
        } else {
            // Both chunks are too long to fit into one packet. Just send a part of each.
            (left, _) = left.split_at(limit / 2);
            (right, _) = right.split_at(limit / 2);
        }
        ((left_offset, left), (right_offset, right))
    }
    

    Same, but for write_chunk:

    fn write_chunk<B: Buffer>(
        offset: u64,
        data: &[u8],
        builder: &mut packet::Builder<B>,
    ) -> Option<(u64, usize)> {
        let mut header_len = 1 + Encoder::varint_len(offset) + 1;
    
        // Don't bother if there isn't room for the header and some data.
        if builder.remaining() < header_len + 1 {
            return None;
        }
        // Calculate length of data based on the minimum of:
        // - available data
        // - remaining space, less the header, which counts only one byte for the length at
        //   first to avoid underestimating length
        let length = min(data.len(), builder.remaining() - header_len);
        header_len += Encoder::varint_len(u64::try_from(length).expect("usize fits in u64")) - 1;
        let length = min(data.len(), builder.remaining() - header_len);
    
        builder.encode_varint(FrameType::Crypto);
        builder.encode_varint(offset);
        builder.encode_vvec(&data[..length]);
        Some((offset, length))
    }
    

    Link to the MIT license file







  • So it works now? If so, then glad to be of help.

    Just remember that if your shebang points to sh, you can’t rely on bash-specific features. The shebang line basically tells the kernel to run your file with the specified program. So, for example, a file with #!/bin/cat will print the full contents of the file (including the shebang) and #!/bin/echo will print the command line. (something like ./script arg1 arg2) As the echo command does not try to interpret arguments as paths, the content of the script would be ignored in that case.








  • What’s disgusting about it? The only thing I can think of is the implicit return, which felt a bit icky at first.

    Also, as the if expression is an expression, you can call methods on it like so:

    if 1 > 2 {
        3
    } else {
        4
    }.min(5)
    

    (the above is still an expression, so it could be used, for example, as part of a condition for another if)

    Of course, you can write horrible code in any language, but the ability to use blocks where expressions are expected can be great sometimes.



  • It’s way more common than you may realize. Intel & AMD (and other x86 CPU manufacturers of the time) did it before the first Crusoe CPU launched. (2000 according to Wikipedia)

    CISC architectures are now seen as inefficient, so all the new ones are RISC and new CISC CPUs just translate the instructions to their intenal RISCier microarchitecture. The CPU’s microcode specifies what an instruction translates to.