« Back to all posts

Christmas Eve and a battle to create a time capsule

How a friend and I hopped in on a challenge to create two tiny programs as our future mailboxes, and how they turned out.

Dec 30, 2025; #experiences #projects

pre-context

During the afternoon of 25th December, I was just casually hopping around Discord in search of what to do. I randomly joined a voice channel on my Minecraft team's server, and one of my friends also (luckily) hopped in. <@Itsmemonzu>, turns out, was also looking for something to do; and a second friend joined to aid him with his idea shortage.

Previously, his urge led him to install Zed on his beefy machine (after I told him about how it uses the GPU for rendering his code), and somehow all of a sudden he wanted to do all-things Zed only. The second friend, in the meantime, suggested him that he makes a time capsule program. I'll return to this in a bit.

Now as a matter of fact, the size-to-functionality ratio of his previous applications were pretty unrealistic - say, 64 megabytes for doing a GET request to the Minecraft Server Status API, and after he himself recognizing the problem, my futile attempts to motivate him back to use C# for his desktop apps were all turning to vain. He eventually turned to Python for this one.

Then suddenly he had asked - "how about we do a programming challenge over this?"

Hmm, not a bad idea!

I hadn't had a programming battle with someone ever since the prehistoric days of HackSquad 2022, so doing this was going to be fun, and Monzu's learning nature was also going to be interesting to analyze within the challenge. We eventually agreed on a few things:

I'll be attaching more context along the way with this entry. Till then, the game was officially on for both of us!

intermediate planning

We were connected to the same Podium VC we initialized with for the entirety of the challenge, and while talking, we both discussed some common frameworks for our programs:

His stackMy stack
Good old Python as usual.Rust for me.
The Rich frameworks. Great for TUI/UX. He wanted his CLI to be fully interactive.clap for me since I didn't want my iteration to be interactive at all for saving some time.
TinyDB as the database, since he was already fluent enough working with JSON tokens.I wanted to build my own database for the work, hence I'll be creating ChristmasDB.

Now let's discuss the initial concept of a time capsule. As our friend Rem had described it, a time capsule is:

"A program that saves a text and keeps it away, and finally returns it after a certain time."

Here's a bit of my thinking process:

My first thoughts after seeing his idea was - bootstrapping such a simple concept into a CLI and not making others bored would be a difficult task. I had to think of something which Monzu wasn't planning about. I'd have to ensure two things:

And, based on what I heard from Monzu:

And, based off of the sentence Rem had told us, we'd have to create a way to actually "put the text away from the user", so that it is not accessible before the time the user allocates, so we'd need a way to encrypt the text and make it undetectable without some sort of deciphering mechanism.

ignition!

We had started by discussing about the common stuff at first. He (and eventually I too) had decided to call each input of the user a "capsule", which had the following fields attached to it:

  1. The actual text, but ciphered.
  2. The datetime indicating the exact moment the text was ciphered away.
  3. The lifetime of the text. Things can't be permanent, just as in life.

He first thought of running a background job to give real-time insight of the datetime to the program, but I had a simpler solution in mind since spawning workers for a project like this was blatant overengineering. So, seeing him mess up his core analogy of the program we were about to make, we decided to share our algorithm. It was pretty simple:

  1. The program would only work when executed - not in the background, so that's one axed thing.
  2. Since we're already tracking two time-tracing fields (2 and 3 above), it'd be very easy to calculate future_time (in this case, the approximate time when a particular capsule should be unlocked). This would work by incrementing the user's given duration as a timedelta to the time when the capsule was created.
  3. If the user wanted to check whether a capsule was unlockable, it'd just check a condition: if (future_time < present_time) {... # do unlock stuff}

the route monzu took (algo)

After all of the yapping about the initial brainstorm phase, we were finally ready to start working. While I was entirely focused on the ChristmasDB layer, Monzu was honestly making some pretty good strides. He had started off by creating his input layer which was composed of numbers, and occasionally, some text. Primitive in nature, but works.

He was also streaming for me to help with some of his stuff so I had a sneak peek at his input strategy:

Create a capsule (1)
Check for unlockable capsules (2)
Unlock capsule (3); Your choice (1/2/3/exit):

Turns out he was going all-bland on the CLI, leaving the opportunity to simplify things a little. I didn't know whether this was going to change for the lifetime of the challenge, but we'd see shortly.

I also asked him regarding his backend logic and whatnot. He was busy handling timedelta and datetime objects with his TinyDB instance. Inspecting some of his "last" code that was uploaded to GitHub, I can see some remnants which I'm bound to explain in this blog:

# P.S.: My indentation got wrecked copy-pasting this snippet.

    elif action == "3":
        print("[white][bold]Which capsule do you want to unlock?[/][/]")
        num = int(input(":")) - 1
        # Check current time
        now_time = datetime.now().timestamp()

        # Accessing JSON Data
        parse = database.all()
        unlock_time = float(parse[num]["time_limit"])

(online snippet's here)

I'll provide some entirely personal comments from here onwards (and some sweet, sweet lore).

Going into the snippet, I could do nothing but get flabbergasted by the inconventional naming conventions (even though the code's working perfectly). The unreliable parsing from parse[num]["time_limit"] might also seem a little vague at first, since there isn't a strict schema being enforced due to Python's core json implementation being dynamic in nature.

As a matter of fact, during the primary (and late-game) stages, Monzu himself ended up parsing the time_limit field as integers rather than datetime instances, and even when he did end up parsing correctly, he passed datetime objects directly into the schema for reflecting back. *confused Monzu noises in the background wondering why it ain't behaving like Rust*

As of the shared algorithm we were talking about implementing, his way of implementation was, again, using the time_limit field. The snippet is this:

def checkCapsule():
    now_time = datetime.now().timestamp()

    # full database init here
    # I removed all comments except this one
    parse = database.all()

    for i, unlock in enumerate(parse, start=0):
        unlock_time = float(parse[i]["time_limit"])
        if now_time >= unlock_time:
            print("[green][bold]You can now unlock your Capsule No." + f" {i + 1}")
        if now_time < unlock_time:
            print(
                "[red][bold]Your capsule No.[/][/]"
                + f" {i + 1} [red][bold]is not unlockable yet.[/][/]"
            )

(online snippet's here)

For some weird reason, he ended up implementing the same algorithm twice in two different places. I mean, okay, that's why functions exist. But, have a look yourself:

    elif action == "2":
        checkCapsule()  # implements check
        time.sleep(5)
    elif action == "3":
        print("[white][bold]Which capsule do you want to unlock?[/][/]")
        num = int(input(":")) - 1  # de-facto inputs

        now_time = datetime.now().timestamp()

        parse = database.all()
        unlock_time = float(parse[num]["time_limit"])  # recall here
        if now_time >= unlock_time:  # also recall here

(online snippet's here)

I hope you clearly see the minor details I've pointed out in front of you. The code works nicely, no comments regarding that. But, it'd just break with one algorithmic change (saying just for the name of explanation).

the route I took (algo + monzu's entry)

Now, not to brag or anything about code - in fact I actually write pretty bad Rust, and considering the fact that other, more knowledgable people use the stack with lifetime specifiers, generics and whatnot for simple data parsing like this, I truly am new to this. However, I took a different approach to the same problem he took upon.

He still hadn't handled the encryption layer, but I picked the good old AES encryption method as my staple. I was also learning about its implementation at the same time since I had never implemented it to date on a CLI project. Within the ChristmasDB layer, I also implemented a few functions which did the following:

The code is too long for me to include here, so I'll just include the primary algorithms here. Rest will be linked.

For checking whether a Capsule should be deciphered, the is_awaiting_decryption() method had been implemented by me as such:

// yes I used serde for the whole store
// that too in JSON
#[derive(Deserialize, Serialize, Clone, PartialEq)]
pub struct Capsule {
    data: Vec<u8>,
    nonce: [u8; 16],
    should_be_kept_for: Duration,
    time_added: NaiveDateTime,
}

impl Capsule {
    pub fn is_awaiting_decryption(&self) -> Result<bool> {
        // This uses NaiveDateTime from the `chrono` crate.
        if let Some(future) = self.time_added.checked_add_signed(self.should_be_kept_for) {
            Ok(future < Local::now().naive_local())
        } else {
            bail!("Duration overflow when computing unlock time.")
        }
    }
}

I will also include the decrypt() function for you to have a sneak peek:

    pub fn decrypt(&self, cap: &Capsule) -> Result<(String, usize)> {
        let mut data = cap.data.clone();

        // AES 256 in CTR mode; this is just a type-override from the `ctr` crate.
        let mut cipher = Aes256Ctr::new(&self.key.into(), &cap.nonce.into());
        // Apparently applying keystreams twice reverses the encryption.
        cipher.apply_keystream(&mut data);

        // Improper parsing handled since keystreams can be improperly applied,
        // given that the user has provided a wrong password.
        let text = String::from_utf8(data).context("Invalid UTF-8, possibly a faulty password?")?;

        let idx = self
            .capsules
            .iter()
            .position(|x| x == cap)
            .context("Capsule not found! Did you delete it manually? :suspicious_eyes:")?;

        Ok((text, idx))
    }

(get the full ChristmasDB snippet here)

I had a lot of fun making this work.

Returning to the original lore, I also implemented CORNELLI_PASS as an environment variable through which the user would apply to set a password. I looked at Monzu's iteration, and I couldn't find a password. Seems like he stole the password side of things from the user and entirely put it on the hands of his own program; which is, I don't know whether to call clever, but the solution kind of works, but then again it becomes pointless.

Oh and, I'd only create two commands:

two interfaces

I was kind of envious of his interface, that he managed to at least get a primitive solution up and running that fast. I was being very lazy to pass a mutable string variable to an io::stdin() call, so much so that I had to compensate with even harder clap arguments.

Anyhow, I had previously created rust-cli-template for my personal projects, so that came in handy. I had just cloned and deleted the .git folder to gain a headstart. This was before all of the algorithmic lore described above. By now, Monzu was getting into the encryption part of things, and I was just starting off with the UX.

I implemented the storymode-like interface for mailbox. Also, something different had also started happening while I was doing that.

By the time they had joined, it was almost 6 hours that Monzu and I were talking in the channel for, and they found us in completely different states. I had spend a ludicrous amount of time optimizing the database, so I was pretty much clear for UX. I had also yoinked the duration-parsing (1h2m1s-style) from trimsec to get some boost. On the other hand, Monzu was only just getting into duration parsing. Frantically enough, he started seeking help from both furti and wolverton regarding some other topics.

Wait, aren't they supposed to be judges?

Anyways, he had also asked me about duration-parsing beforehand. At first we concluded that only doing it by the day would be cheap enough for the project, but upon looking at the actual problem, neither programs would've been able to track the correct decryption time if we had done that, so I skipped over the simplification idea. Turns out, Monzu didn't get the clarification and he had implemented a days-only parser.

I was seeing an unreasonable amount of panic within - I bet he could've hardened his runtime for comfort (no pun intended).

He ended up adding a positional parsing method for the duration (x hours, x minutes, x seconds, non-comma-separated). Having a look at his source, we can see exactly that alongside his self-password mechanism:

          # Parse key from key.txt
           with open(keyFile, "rb") as keyFile:
               readKey = keyFile.read()
               f = Fernet(readKey)
           message = f.encrypt(input().encode())
           user_input = input("Set time limit: (i.e 2 50 30 (day/hour/minute)) ")
           days, hours, minutes = map(int, user_input.split())
           time_limit = datetime.now() + timedelta(
               days=days, hours=hours, minutes=minutes
           )
           if isinstance(time_limit, datetime):
               database.insert(
                   {
                       "message": str(message),
                       "time_limit": time_limit.timestamp(),
                       "current_time": str(datetime.now()),
                   }
               )
               print("[red][bold]Your message has been capsulized!")

(original snippet's here)

release timer

Seeing Monzu's unreasonable panic, furti gave him an additional 10 minutes (which ended up scaling to 30 minutes since Monzu was ignoring that). We also saw some late-game failures in his code. Remember the datetime and timedelta objects we were talking about? Turns out he was parsing them wrong again, and this time he was fully yelling in the voice channel.

This isn't supposed to be a Monzu-rant blog entry, but he quite literally realized that datetime and timedelta were distinct data types and not derivations of language primitives. Quite fun.

We had another bit of coding leftover. Monzu was closing in on the finish line, and I was already finished, looking for further polishing ideas. I even tried downloading the Stardew Valley Overture soundtrack just for the demo for presenting the mailbox command, but ultimately that didn't turn out well, so I scrapped the idea.

Once Monzu was finished, we were counting down for the CI builds. He was pretty much off with the Python interpreter and the pyinstaller library for packaging. My builds actually failed once since I had kept the old bin names from where I had gotten the workflows (one of my previous projects), so I had to restart it with freshly. After that, we both submitted.

the release

At this point of writing the blog entry, I'm pretty burnt out from the 3AM fatigue, but to summarize, the release was a lot more entertaining.

I released the repository with the name "cornelli", and Monzu released his by the name "Shomoy", which means time in Bengali.

Both furti and wolverton were checking out our repositories. My project was first tried out by wolverton, which he cleared without hesitation (and also gave me a nice rating, thanks wolverton if you're reading this). While reading the documentation, he had also asked for some alignment with the setup procedure, since I didn't include the particular setup procedure for PowerShell/cmd, and setting CORNELLI_PASS failed inside that particular Windows environment. I'm pretty glad I could fix that by just changing export to set inside the command.

As of Monzu, his road was a little bit rocky. The primary issue being him not including a sort of "dependency lockfile" in his project, ultimately leading to furti and wolverton running the following command to install the three dependencies:

$ pip install rich cryptography tinydb

After some iterations, wolverton (watching my stream) and I managed to get his application running.

I won't take the courtesy of the overall review. That goes to the judges.

Wolverton had left a massive review for both of the projects. He rated in four categories: documentation, installation, the actual program and overall. On the contrary, furti left a huge almost-40-minute review on both of the projects, which is private so I won't be sharing it here.

verdict by the judges

Even though the overall result was biasing towards different things, both Monzu and I faced criticism and questions for our design choices. For example, the CORNELLI_PASS pattern I was using got questioned by furti since in the v1 release, it wasn't behaving well at all for some mailbox entries. We ended up fixing it later on.

On the flip side, Monzu faced criticism for the following things:

verdict, overall

And with that, we got ourselves two new projects.

I'm very thankful that I had agreed to do this challenge, as this was close to a teeny-tiny hackathon for both of us. Hmm, maybe I could continue this with my friends?

Till then, thanks for reading this entry <3 signing off.



and finally, a section for you ^w^