this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

day 4 part 1

+130 -6
+9 -1
shared/challenges_markdown/four/part_one.md
··· 1 - Day four is the first one is create a tid from a given clock id and date time (random id and time the event started?) 1 + You may have seen some record keys that look a bit like this `3mhhb2bgzs22p`, these are 2 + called [TIDs](https://atproto.com/specs/tid), or Timestamp Identifiers. These are the most common record keys. They 3 + are 4 + made up of a `clock id` and a `timestamp` then encoded in a way that you can sort and order by time. The clock id helps 5 + with uniqueness if you are running multiple nodes, and the timestamps are in nanoseconds as well to help with this. 6 + 7 + Today you will be making a `tid` withe clock id of `{{clock_id}}` and a timestamp of `{{tid_time}}`. 8 + 9 + Hint: May check out [@atcute/tid](https://github.com/mary-ext/atcute/tree/trunk/packages/utilities/tid)
+121 -5
shared/src/advent/challenges/day_four.rs
··· 1 1 use crate::OAuthAgentType; 2 2 use crate::advent::day::Day; 3 - use crate::advent::{AdventChallenge, AdventError, ChallengeCheckResponse}; 3 + use crate::advent::{AdventChallenge, AdventError, AdventPart, ChallengeCheckResponse}; 4 4 use async_trait::async_trait; 5 - use sqlx::PgPool; 5 + use atrium_api::types::LimitedU32; 6 + use atrium_api::types::string::Tid; 7 + use rand::{Rng, rng}; 8 + use serde_json::json; 9 + use sqlx::{PgPool, types::chrono, types::chrono::DateTime}; 10 + use std::str::FromStr; 11 + use std::time::{SystemTime, UNIX_EPOCH}; 6 12 7 13 pub struct DayFour { 8 14 pub pool: PgPool, ··· 27 33 true 28 34 } 29 35 36 + async fn build_additional_context( 37 + &self, 38 + _did: &str, 39 + part: &AdventPart, 40 + _code: &str, 41 + ) -> Result<Option<serde_json::Value>, AdventError> { 42 + match part { 43 + AdventPart::One => { 44 + let mut rng = rng(); 45 + let clock_id: LimitedU32<1023> = rng 46 + .random_range(1..=1023) 47 + .try_into() 48 + .unwrap_or(LimitedU32::<1023>::MAX); 49 + //Creating the datetime a bit oddly so we can zero out nano seconds to make tid creations a bit nicer 50 + let now = SystemTime::now() 51 + .duration_since(UNIX_EPOCH) 52 + .expect("system time before Unix epoch"); 53 + let time_now = DateTime::from_timestamp(now.as_secs() as i64, 0).unwrap(); 54 + let tid = Tid::from_datetime(clock_id, time_now); 55 + 56 + Ok(Some( 57 + json!({ "clock_id": clock_id, "tid_time": time_now, "tid": tid }), 58 + )) 59 + } 60 + AdventPart::Two => Ok(None), 61 + } 62 + } 63 + 30 64 async fn check_part_one( 31 65 &self, 32 - _did: String, 33 - _verification_code: Option<String>, 66 + did: String, 67 + verification_code: Option<String>, 34 68 ) -> Result<ChallengeCheckResponse, AdventError> { 35 - todo!() 69 + let verification_code = verification_code.unwrap_or_default(); 70 + let tid = match Tid::from_str(verification_code.as_str()) { 71 + Ok(tid) => tid, 72 + Err(err) => { 73 + log::error!("Error parsing TID: {}", err); 74 + return Ok(ChallengeCheckResponse::Incorrect( 75 + "Could not parse the TID, please try again.".to_string(), 76 + )); 77 + } 78 + }; 79 + 80 + let days_challenge = match self.get_days_challenge(did.as_str()).await? { 81 + None => { 82 + log::error!("Could not find a challenge record for day: 4 for the user: {did:?}"); 83 + return Err(AdventError::ShouldNotHappen( 84 + "Could not find challenge record".to_string(), 85 + )); 86 + } 87 + Some(challenge) => challenge, 88 + }; 89 + 90 + let additional_context = match days_challenge.additional_context { 91 + Some(ctx) => ctx, 92 + None => { 93 + log::error!("No additional_context found for day 4 challenge for user: {did:?}"); 94 + return Err(AdventError::ShouldNotHappen( 95 + "Could not find challenge context".to_string(), 96 + )); 97 + } 98 + }; 99 + 100 + let expected_clock_id = additional_context["clock_id"].as_u64().unwrap_or(0) as u32; 101 + let expected_time_str = additional_context["tid_time"].as_str().unwrap_or_default(); 102 + 103 + let expected_time = match chrono::DateTime::parse_from_rfc3339(expected_time_str) { 104 + Ok(dt) => dt.timestamp(), 105 + Err(err) => { 106 + log::error!("Failed to parse stored tid_time: {}", err); 107 + return Err(AdventError::ShouldNotHappen( 108 + "Could not parse stored challenge time".to_string(), 109 + )); 110 + } 111 + }; 112 + 113 + let (submitted_timestamp_micros, submitted_clock_id) = match decode_tid(tid.as_str()) { 114 + Some(values) => values, 115 + None => { 116 + return Ok(ChallengeCheckResponse::Incorrect( 117 + "Could not decode the TID, please try again.".to_string(), 118 + )); 119 + } 120 + }; 121 + 122 + if submitted_clock_id != expected_clock_id { 123 + return Ok(ChallengeCheckResponse::Incorrect( 124 + "The clock ID in your TID does not match the expected clock ID.".to_string(), 125 + )); 126 + } 127 + 128 + let submitted_timestamp_secs = submitted_timestamp_micros / 1_000_000; 129 + let time_diff = (submitted_timestamp_secs - expected_time).abs(); 130 + 131 + if time_diff > 300 { 132 + return Ok(ChallengeCheckResponse::Incorrect( 133 + "The timestamp in your TID is not within 5 minutes of the expected time." 134 + .to_string(), 135 + )); 136 + } 137 + 138 + Ok(ChallengeCheckResponse::Correct) 36 139 } 37 140 } 141 + 142 + /// Decodes a TID string and gets the timestamp and clock id 143 + fn decode_tid(tid_str: &str) -> Option<(i64, u32)> { 144 + const CHARSET: &[u8] = b"234567abcdefghijklmnopqrstuvwxyz"; 145 + let mut n: u64 = 0; 146 + for c in tid_str.bytes() { 147 + let val = CHARSET.iter().position(|&b| b == c)? as u64; 148 + n = (n << 5) | val; 149 + } 150 + let timestamp_micros = (n >> 10) as i64; 151 + let clock_id = (n & 0x3FF) as u32; 152 + Some((timestamp_micros, clock_id)) 153 + }