Rust: Stuck on Redis stage #SM4 (RDB Persistence - Read value with expiry)

I have no idea why this is not working. My tests pass the earlier expiry stage, but when reading the expiry from the rdb file the same code is failing. Can anyone suggest how to fix this?

pub fn is_key_expired(created_timestamp: SystemTime, expiry: u128) -> bool {
 SystemTime::now().duration_since(created_timestamp).unwrap_or(Duration::from_secs(0)) >= Duration::from_millis(expiry as u64)
}
//read expiry, if any
    let mut expiry_indicator = [0; 1];
    reader.read_exact(&mut expiry_indicator)?;

    match expiry_indicator[0] {
        0xFC => {
            let mut expiry = [0; 8];
            reader.read_exact(&mut expiry)?;

            let expiry = u64::from_le_bytes(expiry) as u128;
            //let expiry = reader.read_u64::<LittleEndian>()? as u128;
            kv.expiry = Some(expiry);
        }
        0xFD => {
            let mut expiry = [0; 4];
            reader.read_exact(&mut expiry)?;
            let expiry = u32::from_le_bytes(expiry) as u128;
            //let expiry = reader.read_u64::<LittleEndian>()? as u128;
            kv.expiry = Some(expiry * 1000);
        }
        _ => {
            let _ = reader.seek(std::io::SeekFrom::Current(-1));
        }
       
    }

Full disclosure: I can barely read Rust, so I will try to help as best as I can.

I ran your code, and the first thing I noticed is that in the Expiry #yz1 stage, expiry values were way smaller than they should be:

The correct expiry values should be much bigger, like those in Read value with expiry #sm4

@paebanks Could you try to check the parsing logic for expiry, making sure they look like the big number in #sm4 but still pass #yz1?

The expiry values in $yz1 are read from command line and are supplied as values in milliseconds. Those value are exactly as passed in, with the “px” option. The values in #sm4 are timestamp values and thats where I’m not sure my parsing is correct

@paebanks Got it! I can confirm you’re parsing the expiry values in #sm4 correctly, but they’re probably not handled in the way intended.

Let’s look at this line:

# `expired_at` might not be a fitting name for the `px` option
if let Some(expired_at) = value.expiry

and this function:

pub fn is_key_expired(created_timestamp: SystemTime, expiry: u128) -> bool {
    SystemTime::now().duration_since(created_timestamp).unwrap_or(Duration::from_secs(0)) >= Duration::from_millis(expiry as u64)
}

It can handle the case where px represents a duration (as a delta value, but not a timestamp).

So it won’t work for the expiry values in #sm4, which are timestamps and can be fittingly named expired_at.

Suggestion:

  • Either transform px durations into timestamps, and adjust is_key_expired accordingly,
  • Or transform the expiry timestamps in #sm4 into durations before storing them.

Thanks so much for your help. Still haven’t fixed it and I’m guessing it must be something simple. I converted the “px” values to timestamps with this function:

fn to_timestamp(expiry: u128) -> u128 {
    let exp_timestamp = SystemTime::now() + Duration::from_millis(expiry as u64);
    exp_timestamp.duration_since(UNIX_EPOCH).unwrap().as_millis() as u128
}

and my function to check expiry is this:

pub fn is_key_expired(expiry: u128) -> bool {
    let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u128;
    current_time >= expiry
}

Passes #yz1, but still fails #sm4

How do the logs look like now?

I grabbed that expiry value and converted it with an online tool. I can confirm it is indeed expired. The date it represents is Saturday January 1, 2022.

@paebanks There may be multiple issues at play here, but here’s one fix you can try first:

The expiry values (if any) are located at the beginning of the key-value pairs, not at the end.

Namely, the expiry for orange: raspberry shouldn’t be None, but instead should be 1956528000000, in the example above.


In our instructions, the examples are grouped like this:


Feel free to use this snippet to print out the hex dump:

Click me
    // Open the file
    let mut file = File::open(&path)?;
    let mut buffer = Vec::new();

    // Read the file content into the buffer
    file.read_to_end(&mut buffer)?;

    // Process the file buffer to mimic `xxd` output with extra newlines for readability
    for (i, chunk) in buffer.chunks(16).enumerate() {
        // Print the offset at the beginning of each line
        print!("{:08x}: ", i * 16);

        // Print hex values for each byte in the chunk
        for byte in chunk {
            print!("{:02x} ", byte);
        }

        // Pad hex column if the last chunk is less than 16 bytes
        if chunk.len() < 16 {
            for _ in 0..(16 - chunk.len()) {
                print!("   ");
            }
        }

        // Print ASCII representation for each byte in the chunk
        print!("|");
        for byte in chunk {
            let ch = if *byte == 0x0c || !byte.is_ascii_graphic() {
                '.'
            } else {
                *byte as char
            };
            print!("{}", ch);
        }
        print!("|\n"); // Newline after each chunk for readability
    }

This has been so helpful. Thanks! I was assuming expiry values came AFTER the kv, not before

2 Likes

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.