-
Notifications
You must be signed in to change notification settings - Fork 1
fix(audio): establish canonical local-audio resource policy #866
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
seonghobae
wants to merge
151
commits into
develop
Choose a base branch
from
fix/audio-resource-policy-781
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
151 commits
Select commit
Hold shift + click to select a range
cb251f7
test(audio): define canonical resource policy contract
seonghobae 3e96ed6
feat(audio): add canonical resource policy
seonghobae f83a1ba
test(score): require bounded validated PDF reads
seonghobae d659d9d
refactor(core): expose bounded score reader module
seonghobae fc1af87
refactor(core): preserve public API through root module
seonghobae 1df5fe0
fix(score): bound stored PDF reads before allocation
seonghobae 521fe12
fix(score): use bounded native PDF reader
seonghobae ca20dc5
style(score): keep bounded reader rustfmt-clean
seonghobae e6d31ee
docs(changelog): record bounded stored-score reads
seonghobae 051e39d
fix(core): preserve desktop-core package contract
seonghobae c11af77
test(score): cover non-file stored score reads
seonghobae f86e266
test(score): prove same-descriptor growth detection
seonghobae 6aa0098
test(audio): require policy parity at orchestration and decode bounda…
seonghobae ee3b48c
fix(audio): add one-sample decode probe to resource policy
seonghobae a982e7f
fix(audio): bind temporal decode to canonical resource policy
seonghobae 38b3fb6
fix(audio): enforce canonical policy before stem inference
seonghobae ed6e4f7
fix(audio): apply canonical byte policy at request preflight
seonghobae e7671de
docs(audio): record resource-boundary evidence
seonghobae 9549647
docs(changelog): record canonical audio resource bounds
seonghobae b35bc12
docs(security): bind local audio to canonical resource policy
seonghobae 710ed16
test(audio): reject resource-policy arithmetic overflow
seonghobae 6e80527
fix(audio): fail closed on extreme sample-rate arithmetic
seonghobae 572333e
style(audio): keep policy bounds formatter-clean
seonghobae adbfad0
test(audio): reject unrepresentable policy and decoder values
seonghobae 428db0a
fix(audio): make resource policy arithmetic fully fail closed
seonghobae 84f60b3
docs(audio): record checked policy arithmetic
seonghobae e42ff5c
test(audio): reject oversized desktop selection at the bridge
seonghobae 05fbbb3
test(audio): enforce encoded-byte parity for imported sources
seonghobae b23e51a
fix(audio): enforce encoded-byte parity at desktop bridge
seonghobae 509c18b
docs(changelog): record desktop audio-policy parity
seonghobae 9823e0e
fix(changelog): restore full release history after parity note drift
seonghobae f631049
fix(ci): format audio resource policy regressions
seonghobae 0ba0593
test(audio): reject fractional encoded byte metadata
seonghobae ceb7f71
fix(audio): require integral encoded byte metadata
seonghobae 84f7691
test(audio): require native encoded-byte admission
seonghobae da676cb
chore(audio): stack native resource policy on bounded core root
seonghobae 0488c8f
fix(audio): enforce native encoded-byte ceiling
seonghobae fbc7d7d
fix(audio): export native resource policy
seonghobae e61e858
fix(audio): enforce native bootstrap byte ceiling
seonghobae 3f976e5
docs(audio): record native intake enforcement
seonghobae 2b9d5e3
test(audio): require YouTube download to use canonical 100 MiB policy
cursoragent 19064f4
fix(audio): abort YouTube downloads at the canonical encoded-byte cei…
cursoragent 7b3b1e7
docs(audio): record YouTube download-time policy evidence
cursoragent d1a75c9
test(audio): cover fail-closed resource admission branches
seonghobae 1f3fdb8
merge(audio): consolidate YouTube admission repair into canonical #866
seonghobae 5e8fa77
fix(audio): delete owned YouTube partials on in-flight abort
cursoragent 6df13ae
style(audio): wrap zero-byte decoder regression
seonghobae 6d3b9b2
test(audio): reject malformed YouTube duration metadata
seonghobae 86f7272
fix(audio): reject malformed YouTube duration metadata
seonghobae bc0fc98
docs(changelog): record YouTube duration metadata guard
seonghobae ec64aa7
test(audio): bound decoded buffer memory
seonghobae 104573a
fix(audio): enforce decoded memory budget
seonghobae c6d9368
docs(audio): record decoded memory admission
seonghobae f60eb17
docs(audio): record decoded memory budget
seonghobae aeb42da
test(youtube): cover fail-closed duration conversion
seonghobae f35f1d4
test(youtube): avoid exceptional float subclasses
seonghobae f5f0c3d
test(youtube): reject noncanonical numeric metadata
seonghobae f4cee9e
fix(youtube): reject non-canonical duration numerics
seonghobae 69cdf8b
docs(changelog): record strict duration metadata type gate
seonghobae 3ea49d3
fix(youtube): remove unreachable duration conversion branch
seonghobae 97490c7
test(youtube): require post-download duration revalidation
seonghobae d58c24e
test(youtube): model owned cleanup on duration drift
seonghobae d3e2792
fix(youtube): revalidate duration after download
seonghobae 1c85058
test(privacy): fail on temporal path disclosure
seonghobae 4a6e269
fix(privacy): redact temporal analysis failure diagnostics
seonghobae 6d3c388
test(privacy): align temporal diagnostics with redaction contract
seonghobae aa0191c
test(security): reject foreign YouTube download paths
seonghobae 48d06cf
fix(security): bind completed YouTube path to import cache
seonghobae 85424c9
docs(security): record YouTube completed-path authority
seonghobae 7eca596
docs(changelog): record YouTube completed-path containment
seonghobae 6a728fd
test(audio): exercise module entrypoint with owned path
seonghobae 2b83681
style(tests): restore Ruff formatting
seonghobae 2909bc8
test(audio): reject malformed model stem output
seonghobae e5293a9
fix(audio): reject malformed model stem output
seonghobae 2464ff9
test(audio): use explicit raw error pattern
seonghobae c35d55d
test(audio): cover model conversion failures
seonghobae 15a9edb
test(audio): reproduce GPU tensor NumPy boundary
seonghobae c5519a4
fix(audio): move accelerator stems to CPU before NumPy
seonghobae 389f057
fix(audio): preserve CPU tests while bridging accelerator stems
seonghobae 2ea9e07
test(security): reproduce stem worker log disclosure
seonghobae b7dd015
test(security): cover parent stem failure logs
seonghobae cf8b62e
fix(security): redact analysis API tracebacks
seonghobae 2f281be
fix(security): keep API privacy init lint-safe
seonghobae a8bbb77
test(security): scope analysis log redaction
seonghobae 223dd78
fix(security): scope stem diagnostic redaction
seonghobae c2cc5bb
fix(audio): preflight source metadata before decode
seonghobae b4a3513
Merge remote-tracking branch 'origin/develop' into HEAD
seonghobae 505a595
fix(chords): handle zero-element layouts
seonghobae ecc2279
test(audio): require actionable oversize rejection copy
seonghobae f88d3a4
fix(audio): name the next action for oversized sources
seonghobae 3ac09a6
test(audio): preserve structured resource rejection contract
seonghobae 336195a
fix(audio): preserve resource rejection provenance
seonghobae 7862ad2
test(audio): fail closed on parser ValueError leakage
seonghobae 1f33cde
fix(audio): contain metadata parser ValueError details
seonghobae 2ca91e0
test(audio): require one canonical PCM decode port
seonghobae 5cc67e1
test(audio): keep decode-port RED compatible with frozen policy
seonghobae 4b3009c
fix(audio): own one bounded PCM decode port
seonghobae c2e6509
fix(audio): route MIR consumers through owned decode port
seonghobae 3a76907
test(audio): follow the owned decode-port preflight seam
seonghobae c5cc94f
fix(audio): document decode-port regressions
seonghobae 609de8d
Merge protected develop into Resource Admission owner
seonghobae 9852265
test(audio): bind native intake diagnostics to resource owner
seonghobae dbeee9c
merge: adopt protected develop in resource admission lane
seonghobae 804a286
test(audio): reject growth while materializing admitted source
seonghobae 0beee45
fix(audio): bound admitted source materialization
seonghobae a2b1bd9
fix(audio): materialize selected source into project storage
seonghobae 0ee15f0
docs(audio): trace app-owned source materialization
seonghobae 323a7fa
fix(audio): export bounded source materialization port
seonghobae dcb3b25
docs(audio): record native port export repair
seonghobae 131d6d7
test(audio): expose bounded-copy destination error
seonghobae ac4adfd
fix(audio): distinguish bounded-copy destination failure
seonghobae e2257d9
docs(audio): record bounded-copy diagnostics repair
seonghobae dc41379
test(audio): require native content identity receipt
seonghobae 566cd1f
fix(audio): emit streaming content identity receipt
seonghobae f955661
docs(audio): trace native content identity receipt
seonghobae 373824c
test(core): require reusable SHA-256 reader boundary
seonghobae d1ba406
fix(core): expose reusable SHA-256 reader boundary
seonghobae 8a4f50c
docs(core): record reusable SHA-256 consolidation port
seonghobae fdfdd70
test(audio): require publication-bound source identity
seonghobae a1c85cb
fix(audio): verify published source identity
seonghobae 20e7faa
fix(audio): expose publication receipt verifier
seonghobae 505d214
docs(audio): trace publication receipt verification
seonghobae 6a0692e
test(audio): bound publication verification to receipt size
seonghobae c65a9fd
fix(audio): stop publication verification at expected bytes
seonghobae 92f436a
docs(audio): bound publication verification read evidence
seonghobae dedaab7
test(audio): reject invalid publication receipt lengths
seonghobae ed9fe7e
test(audio): require publication-bound materializer receipt
seonghobae bdf8f87
fix(audio): bind published source to native receipt
seonghobae 539bd57
docs(audio): align publication identity traceability
seonghobae 51734ce
test(audio): align native oversize policy expectation
seonghobae dd78dee
test(audio): keep zero-byte guard on decode port
seonghobae 6ef0096
test(audio): patch canonical decode boundary
seonghobae e5726df
test(audio): preserve unrelated separation test wording
seonghobae 46ca91a
docs(security): align local-audio publication authority
seonghobae 45b1f72
test(audio): require no-clobber source publication
seonghobae eb972e9
fix(audio): publish local source without clobber race
seonghobae 55b0da5
docs(audio): record no-clobber publication decision
seonghobae bad908c
test(audio): require path-free publication identity handoff
seonghobae 87bdeea
fix(audio): expose path-free publication identity
seonghobae 344a9a3
fix(audio): export publication identity handoff
seonghobae 681675d
docs(audio): record path-free publication identity boundary
seonghobae 645457e
test(audio): require native publication identity retention
seonghobae f89996b
test(audio): keep Tauri retention as next production slice
seonghobae cbfa967
test(audio): require native retention of publication identity
seonghobae d8c57ce
test(audio): keep native-retention RED off canonical head
seonghobae 106ae75
test(audio): require native retention of publication identity
seonghobae e4e2ba7
fix(audio): retain verified publication identity natively
seonghobae 9a13d2b
docs(audio): record native publication identity retention
seonghobae 06092be
fix(ci): format audio decode regressions
seonghobae 839f5a0
fix(ci): format audio metadata regression
seonghobae 841e1c9
chore(test): normalize decode docstring wrap
seonghobae File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,325 @@ | ||
| use crate::content_sha256::StreamingSha256; | ||
| use std::io::{ErrorKind, Read, Write}; | ||
|
|
||
| /// Maximum encoded local-audio file size accepted by the desktop bootstrap boundary. | ||
| pub const MAX_LOCAL_AUDIO_FILE_BYTES: u64 = 100 * 1024 * 1024; | ||
|
|
||
| const LOCAL_AUDIO_READ_ERROR: &str = "Could not read the selected audio file."; | ||
| const LOCAL_AUDIO_WRITE_ERROR: &str = "Could not prepare the local project workspace."; | ||
| const LOCAL_AUDIO_TOO_LARGE_ERROR: &str = | ||
| "Choose a shorter or smaller song file to start analysis."; | ||
|
|
||
| /// Immutable identity evidence for one successfully staged local-audio byte stream. | ||
| #[derive(Clone, Debug, Eq, PartialEq)] | ||
| pub struct LocalAudioCopyReceipt { | ||
| /// Exact number of bytes written successfully to the staging writer. | ||
| pub file_size_bytes: u64, | ||
| /// SHA-256 of exactly the bytes written successfully, encoded as lowercase hexadecimal. | ||
| pub content_sha256: String, | ||
| } | ||
|
|
||
| /// Validate a native local-audio file length before storing bootstrap metadata. | ||
| /// | ||
| /// The caller must obtain this length from the native filesystem descriptor or | ||
| /// metadata boundary rather than from renderer-controlled JSON. The function | ||
| /// intentionally returns only bounded product messages and never includes a | ||
| /// local path or payload content. | ||
| pub fn validate_local_audio_file_size(file_size_bytes: u64) -> Result<u64, String> { | ||
| if file_size_bytes == 0 { | ||
| return Err(LOCAL_AUDIO_READ_ERROR.to_string()); | ||
| } | ||
| if file_size_bytes > MAX_LOCAL_AUDIO_FILE_BYTES { | ||
| return Err(LOCAL_AUDIO_TOO_LARGE_ERROR.to_string()); | ||
| } | ||
| Ok(file_size_bytes) | ||
| } | ||
|
|
||
| fn read_retrying_interrupted(reader: &mut impl Read, buffer: &mut [u8]) -> Result<usize, String> { | ||
| loop { | ||
| match reader.read(buffer) { | ||
| Ok(read) => return Ok(read), | ||
| Err(error) if error.kind() == ErrorKind::Interrupted => continue, | ||
| Err(_) => return Err(LOCAL_AUDIO_READ_ERROR.to_string()), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn copy_bounded_local_audio_with_limit<R: Read, W: Write>( | ||
| mut reader: R, | ||
| writer: &mut W, | ||
| max_bytes: u64, | ||
| ) -> Result<LocalAudioCopyReceipt, String> { | ||
| let mut copied = 0_u64; | ||
| let mut buffer = [0_u8; 64 * 1024]; | ||
| let mut content_digest = StreamingSha256::default(); | ||
|
|
||
| loop { | ||
| if copied == max_bytes { | ||
| let mut overflow_probe = [0_u8; 1]; | ||
| let read = read_retrying_interrupted(&mut reader, &mut overflow_probe)?; | ||
| if read == 0 { | ||
| break; | ||
| } | ||
| return Err(LOCAL_AUDIO_TOO_LARGE_ERROR.to_string()); | ||
| } | ||
|
|
||
| let remaining = (max_bytes - copied).min(buffer.len() as u64) as usize; | ||
| let read = read_retrying_interrupted(&mut reader, &mut buffer[..remaining])?; | ||
| if read == 0 { | ||
| break; | ||
| } | ||
| writer | ||
| .write_all(&buffer[..read]) | ||
| .map_err(|_| LOCAL_AUDIO_WRITE_ERROR.to_string())?; | ||
| content_digest | ||
| .update(&buffer[..read]) | ||
| .map_err(|_| LOCAL_AUDIO_READ_ERROR.to_string())?; | ||
| copied += read as u64; | ||
| } | ||
|
|
||
| if copied == 0 { | ||
| return Err(LOCAL_AUDIO_READ_ERROR.to_string()); | ||
| } | ||
| let content_sha256 = content_digest | ||
| .finalize_hex() | ||
| .map_err(|_| LOCAL_AUDIO_READ_ERROR.to_string())?; | ||
| Ok(LocalAudioCopyReceipt { | ||
| file_size_bytes: copied, | ||
| content_sha256, | ||
| }) | ||
| } | ||
|
|
||
| /// Copy one admitted local-audio stream into a staging writer and return native content identity. | ||
| /// | ||
| /// Security Notes: callers must pass an already-open, OS-authorized source | ||
| /// descriptor and a private app-owned staging writer. The helper writes no more | ||
| /// than the 100 MiB ceiling, hashes exactly the bytes whose writes succeeded, | ||
| /// and, after reaching the ceiling exactly, reads only one probe byte to detect | ||
| /// source growth. Source-read and destination-write failures use distinct | ||
| /// bounded product errors so storage failures are not misdiagnosed as bad media. | ||
| /// The caller must discard the staging artifact on error, synchronize it before | ||
| /// publication, and bind the returned receipt only to the artifact that was | ||
| /// actually published. | ||
| pub fn copy_bounded_local_audio_with_receipt<R: Read, W: Write>( | ||
| reader: R, | ||
| writer: &mut W, | ||
| ) -> Result<LocalAudioCopyReceipt, String> { | ||
| copy_bounded_local_audio_with_limit(reader, writer, MAX_LOCAL_AUDIO_FILE_BYTES) | ||
| } | ||
|
|
||
| /// Re-read a published app-owned source and prove that it matches its staging receipt. | ||
| /// | ||
| /// Security Notes: the caller must pass an already-open descriptor for the | ||
| /// synchronized, published `source.<extension>` object. This helper opens no | ||
| /// path and grants no filesystem authority. The staging receipt is native | ||
| /// evidence from the prior bounded copy, so its byte length becomes the tighter | ||
| /// publication-read ceiling: the verifier hashes at most that many bytes and | ||
| /// reads one additional probe byte to reject growth. It then requires both size | ||
| /// and digest to equal the staging receipt. Any invalid expected length, read, | ||
| /// growth, truncation, or content mismatch is reported as a bounded | ||
| /// project-workspace failure because the selected source already passed | ||
| /// admission before publication. | ||
| pub fn verify_local_audio_publication_receipt<R: Read>( | ||
| reader: R, | ||
| expected: &LocalAudioCopyReceipt, | ||
| ) -> Result<LocalAudioCopyReceipt, String> { | ||
| if expected.file_size_bytes == 0 || expected.file_size_bytes > MAX_LOCAL_AUDIO_FILE_BYTES { | ||
| return Err(LOCAL_AUDIO_WRITE_ERROR.to_string()); | ||
| } | ||
|
|
||
| let mut sink = std::io::sink(); | ||
| let actual = copy_bounded_local_audio_with_limit(reader, &mut sink, expected.file_size_bytes) | ||
| .map_err(|_| LOCAL_AUDIO_WRITE_ERROR.to_string())?; | ||
| if actual != *expected { | ||
| return Err(LOCAL_AUDIO_WRITE_ERROR.to_string()); | ||
| } | ||
| Ok(actual) | ||
| } | ||
|
|
||
| /// Copy one admitted local-audio stream into a staging writer and return its byte count. | ||
| /// | ||
| /// This compatibility adapter preserves the existing desktop call boundary while | ||
| /// callers migrate to `copy_bounded_local_audio_with_receipt`. It uses the same | ||
| /// bounded copy and content-hash path and discards only the returned digest. | ||
| pub fn copy_bounded_local_audio<R: Read, W: Write>(reader: R, writer: &mut W) -> Result<u64, String> { | ||
| copy_bounded_local_audio_with_receipt(reader, writer).map(|receipt| receipt.file_size_bytes) | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| use std::io::{Cursor, Error}; | ||
|
|
||
| struct FailingWriter; | ||
|
|
||
| impl Write for FailingWriter { | ||
| fn write(&mut self, _buffer: &[u8]) -> std::io::Result<usize> { | ||
| Err(Error::new(ErrorKind::Other, "simulated destination failure")) | ||
| } | ||
|
|
||
| fn flush(&mut self) -> std::io::Result<()> { | ||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| struct FailingReader; | ||
|
|
||
| impl Read for FailingReader { | ||
| fn read(&mut self, _buffer: &mut [u8]) -> std::io::Result<usize> { | ||
| Err(Error::new(ErrorKind::Other, "simulated source failure")) | ||
| } | ||
| } | ||
|
|
||
| struct InterruptedThenReader { | ||
| bytes: Cursor<Vec<u8>>, | ||
| interrupted: bool, | ||
| } | ||
|
|
||
| impl Read for InterruptedThenReader { | ||
| fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> { | ||
| if !self.interrupted { | ||
| self.interrupted = true; | ||
| return Err(Error::from(ErrorKind::Interrupted)); | ||
| } | ||
| self.bytes.read(buffer) | ||
| } | ||
| } | ||
|
|
||
| struct CountingReader { | ||
| bytes: Cursor<Vec<u8>>, | ||
| bytes_read: usize, | ||
| } | ||
|
|
||
| impl Read for CountingReader { | ||
| fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> { | ||
| let read = self.bytes.read(buffer)?; | ||
| self.bytes_read += read; | ||
| Ok(read) | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn bounded_copy_rejects_stream_growth_without_staging_bytes_past_the_limit() { | ||
| let input = Cursor::new(vec![1_u8, 2, 3, 4, 5]); | ||
| let mut staged = Vec::new(); | ||
|
|
||
| let error = copy_bounded_local_audio_with_limit(input, &mut staged, 4) | ||
| .expect_err("a source that grows beyond the admitted byte limit must fail closed"); | ||
|
|
||
| assert_eq!(error, LOCAL_AUDIO_TOO_LARGE_ERROR); | ||
| assert_eq!(staged, vec![1_u8, 2, 3, 4]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn bounded_copy_accepts_the_exact_limit_and_reports_content_identity() { | ||
| let input = Cursor::new(vec![1_u8, 2, 3, 4]); | ||
| let mut staged = Vec::new(); | ||
|
|
||
| let receipt = copy_bounded_local_audio_with_limit(input, &mut staged, 4) | ||
| .expect("the exact encoded-byte limit remains admissible"); | ||
|
|
||
| assert_eq!(receipt.file_size_bytes, 4); | ||
| assert_eq!( | ||
| receipt.content_sha256, | ||
| "9f64a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a" | ||
| ); | ||
| assert_eq!(staged, vec![1_u8, 2, 3, 4]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn bounded_copy_reports_destination_failure_as_workspace_failure() { | ||
| let input = Cursor::new(vec![1_u8, 2, 3, 4]); | ||
| let mut staged = FailingWriter; | ||
|
|
||
| let error = copy_bounded_local_audio_with_limit(input, &mut staged, 4) | ||
| .expect_err("a staging write failure must not be reported as a source read failure"); | ||
|
|
||
| assert_eq!(error, LOCAL_AUDIO_WRITE_ERROR); | ||
| } | ||
|
|
||
| #[test] | ||
| fn bounded_copy_keeps_source_failure_distinct_from_workspace_failure() { | ||
| let input = FailingReader; | ||
| let mut staged = Vec::new(); | ||
|
|
||
| let error = copy_bounded_local_audio_with_limit(input, &mut staged, 4) | ||
| .expect_err("a source read failure must retain the media-read diagnosis"); | ||
|
|
||
| assert_eq!(error, LOCAL_AUDIO_READ_ERROR); | ||
| assert!(staged.is_empty()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn bounded_copy_retries_interrupted_source_reads_without_changing_identity() { | ||
| let input = InterruptedThenReader { | ||
| bytes: Cursor::new(vec![1_u8, 2, 3, 4]), | ||
| interrupted: false, | ||
| }; | ||
| let mut staged = Vec::new(); | ||
|
|
||
| let receipt = copy_bounded_local_audio_with_limit(input, &mut staged, 4) | ||
| .expect("an interrupted source read should be retried"); | ||
|
|
||
| assert_eq!(receipt.file_size_bytes, 4); | ||
| assert_eq!( | ||
| receipt.content_sha256, | ||
| "9f64a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a" | ||
| ); | ||
| assert_eq!(staged, vec![1_u8, 2, 3, 4]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn publication_verification_maps_read_failure_to_workspace_failure() { | ||
| let expected = LocalAudioCopyReceipt { | ||
| file_size_bytes: 4, | ||
| content_sha256: | ||
| "9f64a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a" | ||
| .to_string(), | ||
| }; | ||
|
|
||
| let error = verify_local_audio_publication_receipt(FailingReader, &expected) | ||
| .expect_err("published artifact read failure must be a workspace failure"); | ||
|
|
||
| assert_eq!(error, LOCAL_AUDIO_WRITE_ERROR); | ||
| } | ||
|
|
||
| #[test] | ||
| fn publication_verification_stops_after_expected_size_plus_one_probe_byte() { | ||
| let expected = LocalAudioCopyReceipt { | ||
| file_size_bytes: 4, | ||
| content_sha256: | ||
| "9f64a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a" | ||
| .to_string(), | ||
| }; | ||
| let mut published = CountingReader { | ||
| bytes: Cursor::new(vec![1_u8, 2, 3, 4, 5, 6, 7, 8]), | ||
| bytes_read: 0, | ||
| }; | ||
|
|
||
| let error = verify_local_audio_publication_receipt(&mut published, &expected) | ||
| .expect_err("a grown published artifact must fail without scanning unrelated tail bytes"); | ||
|
|
||
| assert_eq!(error, LOCAL_AUDIO_WRITE_ERROR); | ||
| assert_eq!(published.bytes_read, 5); | ||
| } | ||
|
|
||
| #[test] | ||
| fn publication_verification_rejects_impossible_expected_lengths_without_reading() { | ||
| for file_size_bytes in [0, MAX_LOCAL_AUDIO_FILE_BYTES + 1] { | ||
| let expected = LocalAudioCopyReceipt { | ||
| file_size_bytes, | ||
| content_sha256: "00".repeat(32), | ||
| }; | ||
| let mut published = CountingReader { | ||
| bytes: Cursor::new(vec![1_u8, 2, 3, 4]), | ||
| bytes_read: 0, | ||
| }; | ||
|
|
||
| let error = verify_local_audio_publication_receipt(&mut published, &expected) | ||
| .expect_err("an impossible native receipt length must fail before reading"); | ||
|
|
||
| assert_eq!(error, LOCAL_AUDIO_WRITE_ERROR); | ||
| assert_eq!(published.bytes_read, 0); | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.