-
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
345
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 40 commits
Commits
Show all changes
345 commits
Select commit
Hold shift + click to select a range
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 ebc5055
test(audio): require durable publication commit
seonghobae d794555
fix(audio): add platform publication durability barrier
seonghobae 4e2bccc
test(audio): compile publication durability tests
seonghobae 94086ed
fix(audio): durably commit local source publication
seonghobae e3f6151
docs(audio): record crash-durable source publication
seonghobae 00f720c
docs(audio): doctor publication durability boundary
seonghobae 60816b1
test(audio): preserve existing publication on commit failure
seonghobae 6011294
fix(audio): preserve existing source on publication collision
seonghobae 53747bd
test(audio): bind decoded memory ceiling to canonical float32
seonghobae 379d4a1
fix(audio): cap canonical decoded buffer at float32 footprint
seonghobae 6b16e3c
docs(audio): doctor resource policy v2 memory contract
seonghobae e96b6f0
test(audio): require canonical float32 decoded PCM
seonghobae b24a6ff
fix(audio): enforce canonical float32 decoded PCM
seonghobae 3ee3f64
docs(audio): record canonical PCM dtype policy
seonghobae 623294d
docs(audio): doctor canonical float32 admission
seonghobae 6a329fe
style(audio): restore transcription EOF formatting
seonghobae 44a9087
style(audio): restore temporal EOF formatting
seonghobae 31a7b79
style(audio): restore separator EOF formatting
seonghobae 8adcec8
test(audio): align resource policy version regression
seonghobae f294100
test(audio): bind metadata errors to policy version
seonghobae 370a3d5
test(runtime): require native analysis cancellation
seonghobae 0238c67
fix(runtime): add typed analysis cancellation error
seonghobae 7aa8055
fix(runtime): expose cancelled job error contract
seonghobae 4b85582
fix(runtime): cancel active analysis children
seonghobae c4e2f30
docs(runtime): record native cancellation boundary
seonghobae 0a54d60
docs(runtime): record analysis cancellation behavior
seonghobae 12cc6bd
test(runtime): bind cancellation error wire value
seonghobae 36c5605
fix(runtime): declare cancellation command permission
seonghobae a90a7ea
fix(runtime): grant analysis cancellation capability
seonghobae b746f60
fix(runtime): add generated cancellation permission contract
seonghobae 9e5d1d5
test(runtime): bind cancellation command capability
seonghobae 2dac5b6
docs(audio): bind cancellation to Tauri runtime authority
seonghobae 2f254ac
docs(security): bind analysis cancellation IPC authority
seonghobae 1756759
docs(architecture): record Tauri command authority boundary
seonghobae d3f6b53
test(tauri): require cancellation permission in generated schemas
seonghobae a4c1d71
fix(tauri): refresh generated cancellation capability snapshot
seonghobae ae4d4d3
fix(tauri): regenerate cancellation permission schema
seonghobae 3bfe659
fix(tauri): restore complete generated desktop schema
seonghobae a9a7e27
test(tauri): fail on tracked generated schema drift
seonghobae ea97fc0
fix(tauri): avoid build-mutated schema worktree assertion
seonghobae 5e0180e
docs(architecture): align resource admission and cancellation truth
seonghobae 2d21b6e
docs(architecture): sync local-first resource boundary
seonghobae 1161252
docs(doctoring): trace process-tree containment constraints
seonghobae 6b416c0
test(audio): enforce encoded size at decode boundary
seonghobae 6afb2d8
fix(audio): enforce encoded bytes in decode port
seonghobae 959891e
test(audio): reject non-mono decoder output before normalization
seonghobae 65c9b6a
fix(audio): fail closed on non-mono decoder shapes
seonghobae ec6c41e
style(audio): restore canonical Python file terminator
seonghobae b34be05
test(audio): require renderer cancellation bridge
seonghobae aa07f08
fix(audio): expose cancellation through renderer bridge
seonghobae 4fed274
test(audio): cover cancellation bridge races
seonghobae d3a50f2
test(audio): expose terminal cancellation race
seonghobae 7991631
test(audio): pin cancellation acceptance lock
seonghobae 2fba61c
fix(audio): serialize terminal cancellation
seonghobae fced227
docs(audio): record cancellation ordering invariant
seonghobae 23445c1
test(audio): expose queued cancellation cleanup race
seonghobae 4956aac
fix(audio): serialize queued cancellation cleanup
seonghobae c8a4869
fix(score): restore project score root call
seonghobae f402f54
docs: establish product technical gap baseline
seonghobae 3dd62bb
test(tauri): gate cancellation on runtime authority
seonghobae aa1c938
test(tauri): match generic cancellation finalizer
seonghobae db1a18f
test(tauri): parse generated cancellation schemas
seonghobae a8bd64a
test(tauri): validate cancellation schema structure
seonghobae 8f0d89e
style(tauri): format schema contract helper
seonghobae b0384cb
test(audio): reject post-admission decode growth
seonghobae c0b9c2f
fix(audio): bound decoder reads to admitted bytes
seonghobae f995307
test(audio): cover bounded readinto EOF semantics
seonghobae 7152b92
test(audio): cover bounded seek branches
seonghobae 7413f92
docs(audio): doctor bounded decode authority
seonghobae a7282aa
test(audio): reject invalid publication identity JSON
seonghobae 43b889e
fix(audio): validate publication identity deserialization
seonghobae 1789da7
docs(product): refresh audio admission gap baseline
seonghobae 593d44e
test(audio): expose pre-normalization decoder budget gap
seonghobae f606236
fix(audio): reject over-budget decoder buffers before normalization
seonghobae 2299b21
docs(product): record decoder pre-normalization resource guard
seonghobae c040eed
docs(audio): distinguish decoder allocation from canonical artifact b…
seonghobae 24465db
docs(changelog): clarify decoder allocation admission
seonghobae dcfa90b
test(audio): cap finiteness validation temporary memory
seonghobae a98165a
fix(audio): bound finiteness scan temporary memory
seonghobae bfeefe8
docs(audio): record bounded finiteness validation memory
seonghobae f7f5be0
docs(audio): trace bounded finiteness scan memory
seonghobae 6c559ca
docs(audio): record bounded finiteness scan fix
seonghobae 9b5d1e9
test(audio): assert finiteness rejection provenance
seonghobae 8a4d6f0
test(audio): freeze decoder dtype and resampler contract
seonghobae 2d98bfb
fix(audio): pin canonical decode dtype and resampler
seonghobae bcb533f
test(audio): align decode-port contract with pinned resampler
seonghobae 3761c75
fix(audio): satisfy reproducibility regression lint
seonghobae e24d7ce
fix(audio): apply pinned Ruff formatter output
seonghobae 5e303e0
docs(audio): pin canonical decoder reproducibility evidence
seonghobae 815841d
docs(changelog): record canonical decoder pin
seonghobae c60fb7c
docs(audio): narrow decoder reproducibility claim
seonghobae fb000fb
docs(changelog): narrow decoder reproducibility claim
seonghobae da43fea
test(audio): reject decoder views retaining oversized backing memory
seonghobae e2d39f9
fix(audio): reject decoder views with hidden backing memory
seonghobae f80b962
docs(audio): record hidden decoder backing-memory admission gap
seonghobae ab94470
test(audio): require owned PCM for decoder views
seonghobae 1a35277
fix(audio): detach decoder views into owned PCM
seonghobae 44fbdcf
docs(audio): distinguish retained artifact memory from decoder peak RSS
seonghobae eab2c68
docs(audio): record owned canonicalization of decoder views
seonghobae 8fc4da8
docs(audio): doctor decoder view ownership boundary
seonghobae 574e86d
test(audio): reject malformed decoder dtypes
seonghobae 8f7e1a5
fix(audio): fail closed on malformed decoder dtypes
seonghobae 45b1727
docs(audio): record decoder dtype admission boundary
seonghobae bc8aa65
test(audio): prove dtype rejection precedes PCM allocation
seonghobae 25ff54f
test(audio): reject canonicalization budget expansion
seonghobae 2216a1d
fix(audio): budget canonical PCM before allocation
seonghobae 43ce16f
docs(audio): record canonicalization budget boundary
seonghobae 0510b57
test(audio): resolve duplicate resource-policy import
seonghobae 0467043
test(audio): pin allocation rejection provenance
seonghobae 4861e78
test(audio): reject canonical allocation exhaustion
seonghobae c19f60b
fix(audio): normalize canonical allocation exhaustion
seonghobae 664efd0
docs(audio): record allocator-failure claim boundary
seonghobae ba53156
docs(audio): record canonical allocation exhaustion
seonghobae fb94f21
test(audio): distinguish pre-canonical allocation failure
seonghobae cacc982
fix(audio): scope allocator budget mapping to canonical copy
seonghobae 69adec3
docs(audio): distinguish canonical allocation exhaustion
seonghobae 95d9cc7
test(audio): pin allocator exception provenance
seonghobae cd3e0d5
test(audio): reject empty YouTube artifact with correct reason
seonghobae f925e23
fix(audio): preserve YouTube admission failure reason
seonghobae eed3f8e
fix(audio): lease YouTube artifact ownership per video
seonghobae d04d1c1
test(audio): create malformed YouTube output under active lease
seonghobae ef95475
test(audio): isolate same-ID YouTube cache ownership
seonghobae ab7a4c1
fix(audio): bind YouTube cleanup to leased video identity
seonghobae 8facfa0
test(audio): reject noncanonical YouTube completed filename
seonghobae c4c2c9e
fix(audio): require canonical YouTube completed artifact name
seonghobae ef5b223
docs(audio): bind YouTube completion to canonical filename
seonghobae f23ba9f
test(audio): preserve nontransient same-ID cache files
seonghobae 3dc1bb4
test(audio): bound transient sibling sweep
seonghobae b82bfdf
fix(audio): restrict YouTube abort cleanup authority
seonghobae d2b72b9
test(audio): preserve yt-dlp part-fragment cleanup
seonghobae 09bb020
fix(audio): normalize yt-dlp part-fragment cleanup stems
seonghobae be97c73
test(audio): preserve YouTube IDs containing fragment tokens
seonghobae 98366b6
fix(audio): preserve fragment tokens in YouTube video IDs
seonghobae 0e11700
style(audio): restore youtube module terminator
seonghobae 3c39fa7
docs(audio): record YouTube fragment identity boundary
seonghobae 7664e34
docs(audio): record fragment identity cleanup fix
seonghobae c09ec58
test(audio): constrain yt-dlp fragment cleanup to ASCII indices
seonghobae 668d726
fix(audio): match yt-dlp ASCII fragment indices exactly
seonghobae ab569ca
docs(audio): narrow yt-dlp fragment cleanup authority
seonghobae 740c357
docs(audio): trace yt-dlp fragment index grammar
seonghobae 21458ae
docs(audio): record ASCII fragment cleanup contract
seonghobae f551694
test(audio): reject fragment lookalikes ending in part
seonghobae 7a0387f
fix(audio): reject fragment suffix lookalikes before part cleanup
seonghobae af94694
test(audio): require Unix analysis process-group containment
seonghobae 4e0aa5d
fix(audio): contain analysis descendants on Unix
seonghobae 3ece757
docs(audio): record Unix cancellation containment
seonghobae e6f5d36
docs(audio): trace Unix process-group cancellation
seonghobae 55764a1
docs(audio): log Unix descendant cancellation
seonghobae a07be5c
docs(architecture): record Unix analysis containment
seonghobae 143f04b
docs(security): narrow Unix cancellation claim
seonghobae 65c94f7
test(audio): expose YouTube descendant timeout leak
seonghobae 81a4890
fix(audio): contain YouTube import descendants on Unix
seonghobae 9c36cf0
test(audio): verify YouTube containment in shell gate
seonghobae e8b887c
docs(audio): record YouTube descendant containment gap repair
seonghobae 5f7692f
docs(audio): record YouTube subprocess containment
seonghobae 066d891
docs(architecture): include YouTube process containment
seonghobae 76ca2c0
docs(security): include YouTube descendant containment
seonghobae 91b64cf
docs(doctoring): trace YouTube process containment
seonghobae 04e4d5c
test(process): require shared containment owner
seonghobae 25dd7a5
refactor(process): share containment primitives
seonghobae e772af6
docs(process): record shared containment owner
seonghobae e395dc4
docs(gap): mark containment ownership consolidated
seonghobae 650d93f
docs(changelog): record containment consolidation
seonghobae ce96055
docs(architecture): point process containment to shared owner
seonghobae 6619840
docs(security): consolidate subprocess containment owner
seonghobae 5771e9b
test(process): align containment platform guards
seonghobae bb14420
fix(process): align containment platform guards
seonghobae 3b2df5d
test(process): scope core containment regression
seonghobae c1c2106
test(process): scope Tauri containment regression
seonghobae 9b6340a
test(process): reject success-path descendant pipe hang
seonghobae 650ff2f
fix(process): drain residual descendants after parent exit
seonghobae 04ebdf1
docs(product): cover terminal descendant containment
seonghobae 5e1903d
docs(changelog): record terminal descendant cleanup
seonghobae 6d3af83
docs(security): doctor subprocess terminal containment
seonghobae 1c2f9b9
docs(architecture): align subprocess containment truth
seonghobae 26b16ae
docs(security): cover terminal descendant pipe cleanup
seonghobae fb04b95
test(process): reject unbounded import output capture
seonghobae 90cbe99
fix(process): bound captured helper output
seonghobae 6abb294
refactor(process): expose bounded output owner
seonghobae 2b99124
docs(process): bound helper output evidence
seonghobae 0fbe2cf
docs(gap): include bounded subprocess output admission
seonghobae c7334e5
docs(security): bound subprocess capture memory
seonghobae d766304
docs(changelog): record bounded helper output
seonghobae a0d7f13
fix(process): compile Unix group termination
seonghobae 11acaf5
test(audio): reject slow helper after output overflow
seonghobae ab9b8a1
fix(audio): terminate helper on output admission failure
seonghobae 0dc2faf
docs(audio): record prompt output-admission termination
seonghobae 8770768
docs(product): align helper-output admission baseline
seonghobae 170a2e8
docs(changelog): record helper overflow termination
seonghobae 0e27839
test(audio): bound helper timeout polling latency
seonghobae da1652c
fix(audio): clamp process polling to deadline
seonghobae 3dac5d8
docs(audio): record deadline-bounded process polling
seonghobae 06618e0
docs(audio): make process deadline contract code-current
seonghobae fe3c593
docs(audio): record bounded helper deadline polling
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,22 @@ | ||
| /// 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_TOO_LARGE_ERROR: &str = | ||
| "Selected audio file exceeds the 100 MiB analysis limit."; | ||
|
|
||
| /// 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) | ||
| } |
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,15 @@ | ||
| //! Pure, GUI-independent logic for the BandScope desktop application. | ||
| //! | ||
| //! The historical desktop-core implementation remains in `lib.rs` as the | ||
| //! compatibility module while bounded resource boundaries are isolated in | ||
| //! auditable modules. Public symbols are re-exported so downstream callers keep | ||
| //! the same crate-root API. | ||
|
|
||
| #[path = "lib.rs"] | ||
| mod runtime_core; | ||
| mod audio_resource; | ||
| mod score_pdf; | ||
|
|
||
| pub use audio_resource::{validate_local_audio_file_size, MAX_LOCAL_AUDIO_FILE_BYTES}; | ||
| pub use runtime_core::*; | ||
| pub use score_pdf::read_validated_score_pdf; |
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,82 @@ | ||
| use crate::{MAX_SCORE_PDF_BYTES, PDF_MAGIC}; | ||
| use std::{fs::File, io::Read, path::Path}; | ||
|
|
||
| const SCORE_READ_ERROR: &str = "Could not read the score PDF."; | ||
| const SCORE_TOO_LARGE_ERROR: &str = "Score PDF is too large (exceeds 25MB limit)."; | ||
| const SCORE_INVALID_PDF_ERROR: &str = "Stored score is not a valid PDF."; | ||
|
|
||
| fn read_validated_pdf_stream( | ||
| reader: &mut impl Read, | ||
| expected_len: u64, | ||
| ) -> Result<Vec<u8>, String> { | ||
| if expected_len > MAX_SCORE_PDF_BYTES { | ||
| return Err(SCORE_TOO_LARGE_ERROR.to_string()); | ||
| } | ||
|
|
||
| // MAX_SCORE_PDF_BYTES is 25 MiB, which fits every supported Rust `usize`. | ||
| let mut bytes = vec![0_u8; expected_len as usize]; | ||
| reader | ||
| .read_exact(&mut bytes) | ||
| .map_err(|_| SCORE_READ_ERROR.to_string())?; | ||
|
|
||
| let mut growth_probe = [0_u8; 1]; | ||
| if reader | ||
| .read(&mut growth_probe) | ||
| .map_err(|_| SCORE_READ_ERROR.to_string())? | ||
| != 0 | ||
| { | ||
| return Err(SCORE_TOO_LARGE_ERROR.to_string()); | ||
| } | ||
|
|
||
| if !bytes.starts_with(PDF_MAGIC) { | ||
| return Err(SCORE_INVALID_PDF_ERROR.to_string()); | ||
| } | ||
|
|
||
| Ok(bytes) | ||
| } | ||
|
|
||
| /// Read one already-authorized stored score without allocating beyond the PDF limit. | ||
| /// | ||
| /// The caller remains responsible for path authority and containment. This helper | ||
| /// opens that resolved path once, snapshots the descriptor length, allocates only | ||
| /// that bounded size, reads exactly that many bytes, and then probes one additional | ||
| /// byte on the same descriptor. A file that was already oversized is rejected | ||
| /// before heap allocation; a file that grows after metadata inspection is rejected | ||
| /// by the one-byte probe without extending the heap buffer beyond the product cap. | ||
| /// Errors intentionally omit the local path and file content. | ||
| pub fn read_validated_score_pdf(path: &Path) -> Result<Vec<u8>, String> { | ||
| let mut file = File::open(path).map_err(|_| SCORE_READ_ERROR.to_string())?; | ||
| let metadata = file | ||
| .metadata() | ||
| .map_err(|_| SCORE_READ_ERROR.to_string())?; | ||
| if !metadata.is_file() { | ||
| return Err(SCORE_READ_ERROR.to_string()); | ||
| } | ||
| read_validated_pdf_stream(&mut file, metadata.len()) | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| use std::io::Cursor; | ||
|
|
||
| #[test] | ||
| fn stream_rejects_growth_after_the_metadata_length_snapshot() { | ||
| let mut reader = Cursor::new(b"%PDF-extra".to_vec()); | ||
|
|
||
| let error = read_validated_pdf_stream(&mut reader, PDF_MAGIC.len() as u64) | ||
| .expect_err("bytes beyond the metadata snapshot must fail closed"); | ||
|
|
||
| assert_eq!(error, SCORE_TOO_LARGE_ERROR); | ||
| } | ||
|
|
||
| #[test] | ||
| fn stream_rejects_truncation_after_the_metadata_length_snapshot() { | ||
| let mut reader = Cursor::new(PDF_MAGIC.to_vec()); | ||
|
|
||
| let error = read_validated_pdf_stream(&mut reader, (PDF_MAGIC.len() + 1) as u64) | ||
| .expect_err("truncation after the metadata snapshot must fail closed"); | ||
|
|
||
| assert_eq!(error, SCORE_READ_ERROR); | ||
| } | ||
| } |
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,25 @@ | ||
| use bandscope_desktop_core::{validate_local_audio_file_size, MAX_LOCAL_AUDIO_FILE_BYTES}; | ||
|
|
||
| #[test] | ||
| fn local_audio_size_policy_accepts_the_exact_native_bootstrap_ceiling() { | ||
| assert_eq!( | ||
| validate_local_audio_file_size(MAX_LOCAL_AUDIO_FILE_BYTES), | ||
| Ok(MAX_LOCAL_AUDIO_FILE_BYTES) | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn local_audio_size_policy_rejects_an_empty_native_bootstrap_source() { | ||
| assert_eq!( | ||
| validate_local_audio_file_size(0), | ||
| Err("Could not read the selected audio file.".to_string()) | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn local_audio_size_policy_rejects_a_native_source_above_the_canonical_ceiling() { | ||
| assert_eq!( | ||
| validate_local_audio_file_size(MAX_LOCAL_AUDIO_FILE_BYTES + 1), | ||
| Err("Selected audio file exceeds the 100 MiB analysis limit.".to_string()) | ||
| ); | ||
| } |
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,91 @@ | ||
| use bandscope_desktop_core::{read_validated_score_pdf, MAX_SCORE_PDF_BYTES}; | ||
| use std::io::Write; | ||
| use std::path::PathBuf; | ||
| use std::time::{SystemTime, UNIX_EPOCH}; | ||
|
|
||
| fn unique_test_dir(name: &str) -> PathBuf { | ||
| let suffix = SystemTime::now() | ||
| .duration_since(UNIX_EPOCH) | ||
| .expect("system clock should be after epoch") | ||
| .as_nanos(); | ||
| std::env::temp_dir().join(format!("bandscope-{name}-{suffix}")) | ||
| } | ||
|
|
||
| #[test] | ||
| fn score_pdf_read_returns_only_valid_bounded_pdf_bytes() { | ||
| let root = unique_test_dir("score-read-valid"); | ||
| std::fs::create_dir_all(&root).expect("test directory should be created"); | ||
| let path = root.join("score.pdf"); | ||
| let expected = b"%PDF-1.7\nvalidated body"; | ||
| std::fs::write(&path, expected).expect("valid PDF fixture should be written"); | ||
|
|
||
| let actual = read_validated_score_pdf(&path).expect("valid stored PDF should be readable"); | ||
|
|
||
| assert_eq!(actual, expected); | ||
| let _ = std::fs::remove_dir_all(root); | ||
| } | ||
|
|
||
| #[test] | ||
| fn score_pdf_read_rejects_empty_short_and_wrong_magic_content() { | ||
| let root = unique_test_dir("score-read-invalid"); | ||
| std::fs::create_dir_all(&root).expect("test directory should be created"); | ||
|
|
||
| for (name, content) in [ | ||
| ("empty.pdf", b"".as_slice()), | ||
| ("short.pdf", b"%PD".as_slice()), | ||
| ("wrong.pdf", b"PK\x03\x04 not a PDF".as_slice()), | ||
| ] { | ||
| let path = root.join(name); | ||
| std::fs::write(&path, content).expect("invalid PDF fixture should be written"); | ||
| let error = read_validated_score_pdf(&path).expect_err("invalid PDF must fail closed"); | ||
| assert!( | ||
| error == "Could not read the score PDF." || error == "Stored score is not a valid PDF.", | ||
| "unexpected payload-safe error: {error}" | ||
| ); | ||
| assert!(!error.contains(root.to_string_lossy().as_ref())); | ||
| } | ||
|
|
||
| let _ = std::fs::remove_dir_all(root); | ||
| } | ||
|
|
||
| #[test] | ||
| fn score_pdf_read_rejects_oversized_sparse_file_before_heap_allocation() { | ||
| let root = unique_test_dir("score-read-oversized"); | ||
| std::fs::create_dir_all(&root).expect("test directory should be created"); | ||
| let path = root.join("oversized.pdf"); | ||
| let mut file = std::fs::File::create(&path).expect("oversized PDF fixture should be created"); | ||
| file.write_all(b"%PDF-") | ||
| .expect("PDF magic should be written before extending sparse file"); | ||
| file.set_len(MAX_SCORE_PDF_BYTES + 1) | ||
| .expect("sparse PDF fixture should exceed the product limit"); | ||
| drop(file); | ||
|
|
||
| let error = read_validated_score_pdf(&path).expect_err("oversized PDF must fail closed"); | ||
|
|
||
| assert_eq!(error, "Score PDF is too large (exceeds 25MB limit)."); | ||
| let _ = std::fs::remove_dir_all(root); | ||
| } | ||
|
|
||
| #[cfg(unix)] | ||
| #[test] | ||
| fn score_pdf_read_rejects_non_file_descriptor() { | ||
| let root = unique_test_dir("score-read-directory"); | ||
| std::fs::create_dir_all(&root).expect("test directory should be created"); | ||
|
|
||
| let error = read_validated_score_pdf(&root).expect_err("directory must fail closed"); | ||
|
|
||
| assert_eq!(error, "Could not read the score PDF."); | ||
| assert!(!error.contains(root.to_string_lossy().as_ref())); | ||
| let _ = std::fs::remove_dir_all(root); | ||
| } | ||
|
|
||
| #[test] | ||
| fn score_pdf_read_rejects_missing_file_without_exposing_path() { | ||
| let root = unique_test_dir("score-read-missing"); | ||
| let path = root.join("private-score.pdf"); | ||
|
|
||
| let error = read_validated_score_pdf(&path).expect_err("missing PDF must fail closed"); | ||
|
|
||
| assert_eq!(error, "Could not read the score PDF."); | ||
| assert!(!error.contains("private-score.pdf")); | ||
| } |
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,58 @@ | ||
| import { beforeEach, describe, expect, it, vi } from "vitest"; | ||
| import { importYoutubeUrl, selectLocalAudioSource } from "./analysis"; | ||
|
|
||
| type TauriWindow = Window & { | ||
| __TAURI_INTERNALS__?: unknown; | ||
| __TAURI_INVOKE__?: unknown; | ||
| }; | ||
|
|
||
| const tauriWindow = window as TauriWindow; | ||
| const INVALID_RESOURCE_POLICY_MESSAGE = | ||
| "Selected audio file metadata violates the analysis resource policy."; | ||
|
|
||
| function fractionalBootstrap(projectId: string) { | ||
| return { | ||
| projectId, | ||
| sourceMode: "reference", | ||
| projectRoot: `/tmp/bandscope/projects/${projectId}`, | ||
| cacheRoot: `/tmp/bandscope/cache/${projectId}`, | ||
| tempRoot: `/tmp/bandscope/temp/${projectId}`, | ||
| source: { | ||
| sourcePath: `/tmp/bandscope/${projectId}/input.wav`, | ||
| fileName: "input.wav", | ||
| extension: "wav", | ||
| fileSizeBytes: 1.5 | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| describe("analysis encoded-byte policy parity", () => { | ||
| beforeEach(() => { | ||
| delete tauriWindow.__TAURI_INTERNALS__; | ||
| delete tauriWindow.__TAURI_INVOKE__; | ||
| }); | ||
|
|
||
| it("rejects fractional local-file metadata before project state", async () => { | ||
| tauriWindow.__TAURI_INVOKE__ = vi.fn().mockResolvedValue(fractionalBootstrap("local-project")); | ||
|
|
||
| await expect(selectLocalAudioSource()).resolves.toEqual({ | ||
| ok: false, | ||
| error: { | ||
| code: "invalid_request", | ||
| message: INVALID_RESOURCE_POLICY_MESSAGE | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| it("rejects fractional imported-file metadata before project state", async () => { | ||
| tauriWindow.__TAURI_INVOKE__ = vi.fn().mockResolvedValue(fractionalBootstrap("youtube-project")); | ||
|
|
||
| await expect(importYoutubeUrl("https://youtu.be/4ozX4yFUC34")).resolves.toEqual({ | ||
| ok: false, | ||
| error: { | ||
| code: "invalid_request", | ||
| message: INVALID_RESOURCE_POLICY_MESSAGE | ||
| } | ||
| }); | ||
| }); | ||
| }); |
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.