Skip to content

Commit 14f2fd8

Browse files
committed
feat(mtproto): update layer to 230, update proxy logic and tests
1 parent 8573ffe commit 14f2fd8

24 files changed

Lines changed: 1954 additions & 2015 deletions

File tree

‎native/Cargo.lock‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎native/mtproto-rs/src/client/media_download.rs‎

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ mod peer_avatar;
1919

2020
use peer_avatar::reindex_peer_avatar;
2121

22+
#[cfg(test)]
23+
#[path = "../../tests/unit/cdn_transfer_tests.rs"]
24+
mod cdn_transfer_tests;
25+
2226
include!("media_lane.rs");
2327

2428

@@ -984,19 +988,15 @@ pub(crate) fn download_media_range_on_lane(
984988
Err(err) if media::is_cdn_redirect(&err) => {
985989
let job = media::take_cdn_redirect().ok_or(err)?;
986990
drop(admission);
987-
let cdn_result = {
991+
cdn_or_origin(|| {
988992
let _cdn_admission = client.policy.admit_download(job.dc_id, large)?;
989993
follow_cdn_redirect(handle, api_id, dest, &job)
990-
};
991-
match cdn_result {
992-
Ok(path) => Ok(path),
993-
Err(_) => media::with_cdn_supported(false, || {
994-
let _admission = client.policy.admit_download(target_dc, large)?;
995-
download_media_range_on_lane_dc(
996-
handle, client, home, api_id, media, dest, offset, target_dc,
997-
)
998-
}),
999-
}
994+
}, || {
995+
let _admission = client.policy.admit_download(target_dc, large)?;
996+
download_media_range_on_lane_dc(
997+
handle, client, home, api_id, media, dest, offset, target_dc,
998+
)
999+
})
10001000
}
10011001
other => other,
10021002
};

‎native/mtproto-rs/src/client/media_lane.rs‎

Lines changed: 79 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,11 @@ fn follow_cdn_redirect(
464464
dest: &Path,
465465
job: &media::CdnRedirect,
466466
) -> Result<String, MtprotoError> {
467+
if job.offset != 0 {
468+
return Err(MtprotoError::Message("CDN resume requires origin download".into()));
469+
}
470+
let client = get_client(handle)?;
471+
let session_id = client.data.lock().home_session_id;
467472
let pem = with_client_mut(handle, |state| {
468473
let cfg: CdnConfig =
469474
api_invoke::invoke_api(&mut state.snapshot, api_id, media::get_cdn_config_request())?;
@@ -503,63 +508,85 @@ fn follow_cdn_redirect(
503508
last_io: std::time::Instant::now(),
504509
});
505510
let staged = media::StagedDownload::new(dest)?;
506-
let path = crate::rpc::with_live_transport(&mut slot, || {
511+
crate::rpc::with_live_transport(&mut slot, || {
507512
crate::rpc::with_rpc_timeout_secs(45, || {
508-
use std::io::{Seek, SeekFrom, Write};
509-
let mut out = std::fs::File::create(staged.path())
510-
.map_err(|e| MtprotoError::Message(e.to_string()))?;
511-
let mut offset = job.offset;
512-
loop {
513-
if media::download_cancelled(dest) || crate::request_control::check().is_err() {
514-
return Err(MtprotoError::Message("cancelled".into()));
515-
}
516-
let mut part = loop {
517-
let response: UploadCdnFile = api_invoke::invoke_api_without_updates(
518-
&mut snap,
519-
api_id,
520-
media::get_cdn_file_request(job.file_token.clone(), offset, media::CHUNK),
521-
)?;
522-
match media::cdn_file_bytes(response)? {
523-
Ok(bytes) => break bytes,
524-
Err(request_token) => {
525-
let _ = with_client_mut(handle, |state| {
526-
api_invoke::invoke_api::<_, Vector<Box<FileHash>>>(
527-
&mut state.snapshot,
528-
api_id,
529-
media::reupload_cdn_file_request(
530-
job.file_token.clone(),
531-
request_token,
532-
),
533-
)
534-
})?;
535-
}
536-
}
537-
};
538-
media::decrypt_cdn_part(
539-
&job.encryption_key,
540-
&job.encryption_iv,
541-
offset,
542-
&mut part,
513+
receive_cdn_parts(job, dest, &staged, |offset| {
514+
let response: UploadCdnFile = api_invoke::invoke_api_without_updates(
515+
&mut snap, api_id,
516+
media::get_cdn_file_request(job.file_token.clone(), offset, media::CHUNK),
543517
)?;
544-
out.seek(SeekFrom::Start(offset as u64))
545-
.map_err(|e| MtprotoError::Message(e.to_string()))?;
546-
out.write_all(&part)
547-
.map_err(|e| MtprotoError::Message(e.to_string()))?;
548-
if (part.len() as i32) < media::CHUNK {
549-
break;
550-
}
551-
offset += part.len() as i64;
552-
media::notify_progress(&dest.display().to_string(), offset, 0);
553-
}
554-
out.flush()
555-
.map_err(|e| MtprotoError::Message(e.to_string()))?;
556-
Ok(staged.path().display().to_string())
518+
media::cdn_file_bytes(response)
519+
}, |request_token| {
520+
with_client_mut(handle, |state| {
521+
api_invoke::invoke_api::<_, Vector<Box<FileHash>>>(
522+
&mut state.snapshot, api_id,
523+
media::reupload_cdn_file_request(job.file_token.clone(), request_token),
524+
)
525+
}).map(|_| ())
526+
})
557527
})
558528
})?;
559-
let client = get_client(handle)?;
560-
let session_id = client.data.lock().home_session_id;
561529
publish_media(&client, session_id, staged, dest)?;
562-
let _ = path;
563530
Ok(dest.display().to_string())
564531
}
565532

533+
fn cdn_or_origin<T>(
534+
cdn: impl FnOnce() -> Result<T, MtprotoError>,
535+
origin: impl FnOnce() -> Result<T, MtprotoError>,
536+
) -> Result<T, MtprotoError> {
537+
match cdn() {
538+
Ok(result) => Ok(result),
539+
Err(_) => media::with_cdn_supported(false, origin),
540+
}
541+
}
542+
543+
fn receive_cdn_parts(
544+
job: &media::CdnRedirect,
545+
dest: &Path,
546+
staged: &media::StagedDownload,
547+
mut fetch: impl FnMut(i64) -> Result<Result<Vec<u8>, Vec<u8>>, MtprotoError>,
548+
mut reupload: impl FnMut(Vec<u8>) -> Result<(), MtprotoError>,
549+
) -> Result<(), MtprotoError> {
550+
use std::io::{Seek, SeekFrom, Write};
551+
let mut out = std::fs::File::create(staged.path())
552+
.map_err(|e| MtprotoError::Message(e.to_string()))?;
553+
let mut offset = job.offset;
554+
loop {
555+
if media::download_cancelled(dest) || crate::request_control::check().is_err() {
556+
return Err(MtprotoError::Message("cancelled".into()));
557+
}
558+
let mut reuploads = 0;
559+
let mut part = loop {
560+
if media::download_cancelled(dest) || crate::request_control::check().is_err() {
561+
return Err(MtprotoError::Message("cancelled".into()));
562+
}
563+
match fetch(offset)? {
564+
Ok(bytes) => break bytes,
565+
Err(token) => {
566+
if reuploads >= 3 {
567+
return Err(MtprotoError::Message("CDN reupload limit exceeded".into()));
568+
}
569+
reuploads += 1;
570+
reupload(token)?;
571+
}
572+
}
573+
};
574+
if media::download_cancelled(dest) || crate::request_control::check().is_err() {
575+
return Err(MtprotoError::Message("cancelled".into()));
576+
}
577+
media::decrypt_cdn_part(&job.encryption_key, &job.encryption_iv, offset, &mut part)?;
578+
if part.len() > media::CHUNK as usize
579+
|| !media::verify_cdn_part(&job.file_hashes, offset, &part)
580+
{
581+
return Err(MtprotoError::Message("CDN hash verification failed".into()));
582+
}
583+
out.seek(SeekFrom::Start(offset as u64))
584+
.map_err(|e| MtprotoError::Message(e.to_string()))?;
585+
out.write_all(&part).map_err(|e| MtprotoError::Message(e.to_string()))?;
586+
if part.len() < media::CHUNK as usize { break; }
587+
offset += part.len() as i64;
588+
media::notify_progress(&dest.display().to_string(), offset, 0);
589+
}
590+
out.flush().map_err(|e| MtprotoError::Message(e.to_string()))
591+
}
592+

‎native/mtproto-rs/src/instant_view/rich.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ pub(crate) fn append_rich(text: &RichText, out: &mut media::FormattedText) {
7272
append_rich(&t.text, out);
7373
mark(out, start, "code", None);
7474
}
75+
RichText::TextTonAddress(t) => {
76+
append_rich(&t.text, out);
77+
mark(out, start, "code", None);
78+
}
7579
RichText::TextBankCard(t) => {
7680
append_rich(&t.text, out);
7781
mark(out, start, "code", None);

‎native/mtproto-rs/src/media/cdn.rs‎

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub struct CdnRedirect {
2828
pub encryption_key: Vec<u8>,
2929
pub encryption_iv: Vec<u8>,
3030
pub offset: i64,
31+
pub file_hashes: Vec<FileHash>,
3132
}
3233

3334
pub fn cdn_supported() -> bool {
@@ -59,6 +60,9 @@ pub fn store_cdn_redirect(redirect: &UploadFileCdnRedirectConstructor, offset: i
5960
encryption_key: redirect.encryption_key.clone(),
6061
encryption_iv: redirect.encryption_iv.clone(),
6162
offset,
63+
file_hashes: crate::peers::vector_boxed_items(redirect.file_hashes.as_ref())
64+
.cloned()
65+
.collect(),
6266
});
6367
});
6468
}
@@ -152,8 +156,29 @@ pub fn hash_matches(file_hash: &FileHash, offset: i64, data: &[u8]) -> bool {
152156
if hash.offset != offset {
153157
return false;
154158
}
155-
let take = hash.limit.max(0) as usize;
156-
verify_cdn_hash(&data[..data.len().min(take)], &hash.hash)
159+
let Ok(take) = usize::try_from(hash.limit) else {
160+
return false;
161+
};
162+
take > 0 && data.len() >= take && verify_cdn_hash(&data[..take], &hash.hash)
163+
}
164+
165+
pub(crate) fn verify_cdn_part(hashes: &[FileHash], offset: i64, data: &[u8]) -> bool {
166+
if offset < 0 || data.is_empty() {
167+
return false;
168+
}
169+
let mut consumed = 0;
170+
while consumed < data.len() {
171+
let Some(at) = offset.checked_add(consumed as i64) else { return false };
172+
let Some(hash) = hashes.iter().find(|entry| matches!(entry,
173+
FileHash::FileHash(hash) if hash.offset == at
174+
)) else { return false };
175+
if !hash_matches(hash, at, &data[consumed..]) {
176+
return false;
177+
}
178+
let FileHash::FileHash(hash) = hash else { return false };
179+
consumed += hash.limit as usize;
180+
}
181+
true
157182
}
158183

159184
#[cfg(test)]

‎native/mtproto-rs/src/media/page_plain.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,10 @@ fn push_rich(text: &RichText, out: &mut FormattedText) {
522522
push_rich(&t.text, out);
523523
mark(out, start, "code", None);
524524
}
525+
RichText::TextTonAddress(t) => {
526+
push_rich(&t.text, out);
527+
mark(out, start, "code", None);
528+
}
525529
RichText::TextBankCard(t) => {
526530
push_rich(&t.text, out);
527531
mark(out, start, "code", None);

‎native/mtproto-rs/src/rpc.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ mod gzip_tests;
9797
#[cfg(test)]
9898
#[path = "../tests/unit/rpc_push_tests.rs"]
9999
mod push_tests;
100+
#[cfg(test)]
101+
#[path = "../tests/unit/rpc_wire_tests.rs"]
102+
mod wire_tests;
103+
100104
#[cfg(test)]
101105
#[path = "../tests/unit/rpc_session_tests.rs"]
102106
mod session_tests;

0 commit comments

Comments
 (0)