datastore refactor
Some checks failed
Build and Test / wii-sports-club (push) Failing after 20m22s
Build and Test / puyopuyo (push) Failing after 20m41s
Build and Test / minecraft-wiiu (push) Failing after 20m54s
Build and Test / sonic-transformed (push) Failing after 21m17s
Build and Test / mario-tennis (push) Failing after 21m34s
Build and Test / fast-racing-neo (push) Failing after 21m37s
Build and Test / splatoon-testfire (push) Failing after 22m11s
Build and Test / wii-u-chat (push) Failing after 23m6s
Build and Test / splatoon (push) Failing after 23m23s
Build and Test / friends (push) Failing after 3m51s
Build and Test / super-mario-maker (push) Failing after 3m50s

This commit is contained in:
Maple Nebel 2026-07-13 19:38:43 +02:00
commit 3f07e45f08
11 changed files with 1030 additions and 2105 deletions

1035
Cargo.lock generated

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,116 +0,0 @@
use base64::{engine::general_purpose, Engine as _};
use hmac::{Hmac, Mac};
use sha2::{Sha256, Digest};
use chrono::{Utc, Duration};
use serde_json::json;
use rnex_core::executables::common::RNEX_DATASTORE_S3_ENDPOINT;
pub struct S3Presigner {
endpoint: String,
bucket: String,
}
impl S3Presigner {
pub async fn new(endpoint: &str, bucket: String) -> Self {
Self {
endpoint: endpoint.trim_end_matches('/').to_string(),
bucket,
}
}
pub async fn generate_presigned_post(&self, key: &str) -> (String, Vec<(String, String)>) {
let access_key = std::env::var("AWS_ACCESS_KEY_ID").expect("Missing Access Key");
let secret_key = std::env::var("AWS_SECRET_ACCESS_KEY").expect("Missing Secret Key");
let region = "us-east-1"; // hardcoded because its the default region for most s3 clones
let date_short = Utc::now().format("%Y%m%d").to_string();
let date_full = Utc::now().format("%Y%m%dT%H%M%SZ").to_string();
let expiration = (Utc::now() + Duration::minutes(15)).format("%Y-%m-%dT%H:%M:%SZ").to_string();
let credential = format!("{}/{}/{}/s3/aws4_request", access_key, date_short, region);
let policy_json = json!({
"expiration": expiration,
"conditions": [
{"bucket": self.bucket},
["starts-with", "$key", key],
{"x-amz-credential": credential},
{"x-amz-algorithm": "AWS4-HMAC-SHA256"},
{"x-amz-date": date_full}
]
});
let policy_base64 = general_purpose::STANDARD.encode(policy_json.to_string());
let signature = self.calculate_signature(&secret_key, &date_short, region, &policy_base64);
let fields = vec![
("key".to_string(), key.to_string()),
("X-Amz-Algorithm".to_string(), "AWS4-HMAC-SHA256".to_string()),
("X-Amz-Credential".to_string(), credential),
("X-Amz-Date".to_string(), date_full),
("Policy".to_string(), policy_base64),
("X-Amz-Signature".to_string(), signature),
];
let url = format!("https://{}/{}", *RNEX_DATASTORE_S3_ENDPOINT, self.bucket);
(url, fields)
}
pub fn generate_presigned_get(&self, key: &str) -> String {
let access_key = std::env::var("AWS_ACCESS_KEY_ID").expect("Missing Access Key");
let secret_key = std::env::var("AWS_SECRET_ACCESS_KEY").expect("Missing Secret Key");
let region = "us-east-1";
let date_short = Utc::now().format("%Y%m%d").to_string();
let date_full = Utc::now().format("%Y%m%dT%H%M%SZ").to_string();
let credential_scope = format!("{}/{}/s3/aws4_request", date_short, region);
let query_string = format!(
"X-Amz-Algorithm=AWS4-HMAC-SHA256&\
X-Amz-Credential={}%2F{}&\
X-Amz-Date={}&\
X-Amz-Expires=900&\
X-Amz-SignedHeaders=host",
access_key,
urlencoding::encode(&credential_scope),
date_full
);
let canonical_request = format!(
"GET\n/{}/{}\n{}\nhost:{}\n\nhost\nUNSIGNED-PAYLOAD",
self.bucket, key, query_string, *RNEX_DATASTORE_S3_ENDPOINT
);
let hashed_request = hex::encode(Sha256::digest(canonical_request.as_bytes()));
let string_to_sign = format!(
"AWS4-HMAC-SHA256\n{}\n{}\n{}",
date_full, credential_scope, hashed_request
);
let k_date = self.hmac_sha256(format!("AWS4{}", secret_key).as_bytes(), &date_short);
let k_region = self.hmac_sha256(&k_date, region);
let k_service = self.hmac_sha256(&k_region, "s3");
let k_signing = self.hmac_sha256(&k_service, "aws4_request");
let signature = hex::encode(self.hmac_sha256(&k_signing, &string_to_sign));
format!(
"https://{}/{}/{}?{}&X-Amz-Signature={}",
*RNEX_DATASTORE_S3_ENDPOINT, self.bucket, key, query_string, signature
)
}
fn calculate_signature(&self, secret: &str, date: &str, region: &str, policy: &str) -> String {
let k_date = self.hmac_sha256(format!("AWS4{}", secret).as_bytes(), date);
let k_region = self.hmac_sha256(&k_date, region);
let k_service = self.hmac_sha256(&k_region, "s3");
let k_signing = self.hmac_sha256(&k_service, "aws4_request");
hex::encode(self.hmac_sha256(&k_signing, policy))
}
fn hmac_sha256(&self, key: &[u8], data: &str) -> Vec<u8> {
let mut mac = Hmac::<Sha256>::new_from_slice(key).expect("HMAC can take key of any size");
mac.update(data.as_bytes());
mac.finalize().into_bytes().to_vec()
}
}

View file

@ -22,7 +22,7 @@ impl PRUDPSockAddr {
}
pub fn calculate_connection_signature(&self) -> [u8; 16] {
let mut hmac = Md5Hmac::new_from_slice(&[0; 16]).expect("?");
let mut hmac = Md5Hmac::new_from_slice(&[0; 16]).expect("incorrect slice size");
let data = match self.regular_socket_addr.ip() {
IpAddr::V4(v) => v.octets().to_vec(),
@ -33,7 +33,7 @@ impl PRUDPSockAddr {
hmac.update(&data);
let result: [u8; 16] = hmac.finalize().into_bytes()[0..16]
.try_into()
.expect("fuck");
.expect("incorrect result size");
result
}
}

View file

@ -73,7 +73,7 @@ impl RmcSerialize for SocketAddrV6 {
))
}
fn serialize_write_size(&self) -> Result<u32> {
Ok(6)
Ok(26)
}
}
/*

View file

@ -124,7 +124,7 @@ impl RmcSerialize for u128 {
}
#[inline(always)]
fn serialize_write_size(&self) -> Result<u32> {
Ok(8)
Ok(16)
}
}

View file

@ -465,6 +465,6 @@ impl From<Error> for ErrorCode {
impl Into<u32> for ErrorCode {
fn into(self) -> u32 {
unsafe { transmute(self) }
self as u32
}
}

View file

@ -77,7 +77,7 @@ impl<'a, T: Read + ?Sized> SubRead<'a, T> {
impl<T: Read + ?Sized> Read for SubRead<'_, T> {
#[inline(always)]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let max_read = usize::max(self.left_to_read, buf.len());
let max_read = usize::min(self.left_to_read, buf.len());
let read = self.origin.read(&mut buf[..max_read])?;
self.left_to_read -= read;
Ok(read)

View file

@ -1,5 +1,6 @@
use std::fmt::Write;
use std::io::{self, Read};
use std::str::FromStr;
use rnex_util::station_url::StationUrl;
@ -12,7 +13,7 @@ impl RmcSerialize for StationUrl {
fn deserialize(reader: &mut (impl Read + ?Sized)) -> Result<Self> {
let str = String::deserialize(reader)?;
Self::try_from(str.as_str()).map_err(|_| StationUrlInvalid)
Self::from_str(str.as_str()).map_err(|_| StationUrlInvalid)
}
fn serialize(&self, writer: &mut (impl io::Write + ?Sized)) -> Result<()> {
let str: String = self.into();

View file

@ -9,6 +9,7 @@ tracing = "0.1.44"
chrono = "0.4.39"
bytemuck = { version = "1.25.0", features = ["derive"] }
md-5 = "0.11.0"
thiserror = "2.0.18"
[features]
nx = []

View file

@ -1,8 +1,10 @@
use std::{
fmt::{Debug, Display, Formatter},
net::IpAddr,
str::FromStr,
};
use thiserror::Error;
use tracing::error;
use crate::PID;
@ -86,11 +88,15 @@ impl StationUrl {
}
}
impl TryFrom<&str> for StationUrl {
type Error = ();
// todo: add more specific error messages to parsing
#[derive(Error, Debug)]
#[error("failed to parse station url")]
pub struct StationUrlParseError;
fn try_from(value: &str) -> Result<Self, ()> {
let (url_type, options) = value.split_at(value.find(":/").ok_or(())?);
impl FromStr for StationUrl {
type Err = StationUrlParseError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let (url_type, options) = value.split_at(value.find(":/").ok_or(StationUrlParseError)?);
let options = &options[2..];
@ -100,10 +106,10 @@ impl TryFrom<&str> for StationUrl {
"udp" => UDP,
"prudp" => PRUDP,
"prudps" => PRUDPS,
_ => return Err(()),
_ => return Err(StationUrlParseError),
};
let options = Self::read_options(options).ok_or(())?;
let options = Self::read_options(options).ok_or(StationUrlParseError)?;
Ok(Self { url_type, options })
}