mirror of
https://github.com/freemkv/libfreemkv.git
synced 2026-10-07 18:13:10 +00:00
halt: Stop-aware waits across channels, drive, io, flush and the macOS shim
Waits are halt-aware on a stall timer; every CDB carries the op token; prefetchers and pipeline joins honour Stop; flush is durable with measured progress; the macOS shim open is cancellable. Also: CPI is cleared on every decrypted unit, FMTS phase verification, the HD DVD declared CPS count, AES work hoisted out of AACS hot paths, and the last production unsafe removed.
This commit is contained in:
@@ -147,3 +147,98 @@ fn libaacs_quotes_match_source() {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const SITES: &str = include_str!("spec_sites.txt");
|
||||
|
||||
// The body of the one `fn <name>` in `src`, braces matched outside `//` comments and
|
||||
// string/char literals. Err when the fn is missing or defined twice.
|
||||
fn fn_body<'a>(src: &'a str, name: &str) -> Result<&'a str, String> {
|
||||
let starts: Vec<usize> = [format!("fn {name}("), format!("fn {name}<")]
|
||||
.iter()
|
||||
.flat_map(|pat| src.match_indices(pat.as_str()).map(|(i, _)| i))
|
||||
.collect();
|
||||
let [start] = starts[..] else {
|
||||
return Err(format!("fn {name}: {} definitions", starts.len()));
|
||||
};
|
||||
let bytes = src.as_bytes();
|
||||
let (mut i, mut depth, mut open) = (start, 0usize, None);
|
||||
while i < bytes.len() {
|
||||
match bytes[i] {
|
||||
b'/' if bytes.get(i + 1) == Some(&b'/') => {
|
||||
i += src[i..].find('\n').unwrap_or(src.len() - i);
|
||||
}
|
||||
b'"' => {
|
||||
i += 1;
|
||||
while i < bytes.len() && bytes[i] != b'"' {
|
||||
i += if bytes[i] == b'\\' { 2 } else { 1 };
|
||||
}
|
||||
}
|
||||
b'\'' if bytes.get(i + 2) == Some(&b'\'') => i += 2,
|
||||
b'{' => {
|
||||
open.get_or_insert(i);
|
||||
depth += 1;
|
||||
}
|
||||
b'}' if open.is_some() => {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
return Ok(&src[open.unwrap()..=i]);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
Err(format!("fn {name}: unbalanced body"))
|
||||
}
|
||||
|
||||
/// KU design §3.6.1 rule 11: every listed spec-governed site names its quote ID.
|
||||
#[test]
|
||||
fn spec_governed_sites_cite_the_spec() {
|
||||
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
let ids: HashSet<&str> = spec::quotes().map(|q| q.id).collect();
|
||||
let mut rows = 0;
|
||||
for line in SITES
|
||||
.lines()
|
||||
.filter(|l| !l.starts_with('#') && !l.is_empty())
|
||||
{
|
||||
let cols: Vec<&str> = line.split('\t').collect();
|
||||
let [file, name, id] = cols[..] else {
|
||||
panic!("site row is `<file>\\t<fn>\\t<id>`: {line:?}");
|
||||
};
|
||||
assert!(ids.contains(id), "{file} {name}: {id} is not a spec quote");
|
||||
let src = std::fs::read_to_string(root.join(file)).expect(file);
|
||||
let body = fn_body(&src, name).unwrap_or_else(|e| panic!("{file}: {e}"));
|
||||
assert!(cites(body, id), "{file} fn {name} does not cite {id}");
|
||||
rows += 1;
|
||||
}
|
||||
assert!(rows > 0, "no spec-governed sites listed");
|
||||
}
|
||||
|
||||
// Does `body` name `id` as a whole ID? `KS-2` must not match inside `KS-22` or `XKS-2`.
|
||||
fn cites(body: &str, id: &str) -> bool {
|
||||
body.match_indices(id).any(|(at, _)| {
|
||||
let before = body[..at].chars().next_back();
|
||||
let after = body[at + id.len()..].chars().next();
|
||||
!before.is_some_and(|c| c.is_alphanumeric() || c == '_')
|
||||
&& !after.is_some_and(|c| c.is_ascii_digit())
|
||||
})
|
||||
}
|
||||
|
||||
/// The site checker itself: a cited ID passes; a missing ID, a missing fn, a braced
|
||||
/// comment or string, and a twice-defined fn are all caught.
|
||||
#[test]
|
||||
fn spec_site_check_finds_the_body() {
|
||||
let src = "fn a() { // KS-1 { unbalanced in a comment\n let s = \"}\"; let c = '{'; }\n\
|
||||
fn b() { if x { KS-2 } }\nfn b2() {}\n";
|
||||
assert!(fn_body(src, "a").unwrap().contains("KS-1"));
|
||||
assert!(fn_body(src, "b").unwrap().contains("KS-2"));
|
||||
assert!(!fn_body(src, "b2").unwrap().contains("KS-2"));
|
||||
assert!(fn_body(src, "c").is_err());
|
||||
assert!(fn_body("fn d() {}\nfn d() {}", "d").is_err());
|
||||
// Word boundaries: `KS-2` is not cited by `KS-22`, and is by `KS-2,` / `(KS-2)`.
|
||||
assert!(!cites("fn e() { // KS-22 only }", "KS-2"));
|
||||
assert!(!cites("fn e() { // XKS-2 }", "KS-2"));
|
||||
assert!(cites("fn e() { // KS-22, KS-2, KS-3 }", "KS-2"));
|
||||
assert!(cites("fn e() { // (KS-2) }", "KS-2"));
|
||||
assert!(cites(fn_body(src, "b").unwrap(), "KS-2"));
|
||||
}
|
||||
|
||||
+14
-2
@@ -10,7 +10,7 @@ KS-2 An Aligned Unit consists of 32 MPEG source packets: Each MPEG source packet
|
||||
KS-3 The final 6128 bytes of each Aligned Unit is encrypted using the Block Key and AES-128CBCE. A new CBC cipher chain is started for each Aligned Unit (see Figure 3-7).
|
||||
KS-4 The first 16 bytes of each Aligned Unit is used as the seed for calculating the Block Key. Calculation method for the Block key is described in Figure 3-8.
|
||||
KS-5 Copy_permission_indicator shall be set to 11₂ if the data is encrypted, or shall be set to 00₂ if the data is not encrypted. If the Licensed Player encounters the packet with Copy_permission_indicator set to 10₂ or 01₂, the data shall be considered encrypted.
|
||||
KS-6 Table 3-34 TP_extra_header: Syntax No. of bits Mnemonic TP_extra_header { Copy_permission_indicator 2 uimsbf Arrival_time_stamp 30 uimsbf } // cited later by SG9 (KU-L1) and the §3.6.2 CPI-clear site
|
||||
KS-6 Table 3-34 TP_extra_header: Syntax No. of bits Mnemonic TP_extra_header { Copy_permission_indicator 2 uimsbf Arrival_time_stamp 30 uimsbf }
|
||||
KS-7 Each physical sector in an Aligned Unit shall be allocated contiguously on the BD-ROM disc.
|
||||
KS-8 All the extents of each Clip AV stream file shall be allocated with ascending order in physical layer.
|
||||
KS-9 Within the Stereoscopic Interleaved file, segments of the two Clip AV stream files are recorded alternately. The boundary of these segments shall be always aligned to Aligned Unit boundary, so that there is no impact on the encryption scheme of the Stereoscopic Interleaved file.
|
||||
@@ -26,7 +26,7 @@ KS-18 If the Bus Encryption Enabled (BEE) flag in the Content Certificate is set
|
||||
KS-19 Unless otherwise specified, the Initialization Vector used at the beginning of a CBC encryption or decryption chain is a constant, iv0, which is: 0BA0F8DDFEA61FB3D8DF9F566A050F78₁₆
|
||||
KS-20 The 30 least significant bits of this field shall be interpreted as a 30-bit unsigned binary number specifying the length of the extent in bytes. Unless otherwise specified, the length shall be an integral multiple of the logical block size. The 2 most significant bits shall be interpreted as a 2-bit unsigned binary number specifying the type of the extent as described in figure 4/42. If the 30 least significant bits are set to ZERO, the two most significant bits shall also be set to ZERO.
|
||||
KS-21 Value Interpretation / 0 Extent recorded and allocated / 1 Extent not recorded but allocated / 2 Extent not recorded and not allocated / 3 The extent is the next extent of allocation descriptors (see 4/12) / Figure 42 - Extent interpretation
|
||||
KS-22 static int _verify_ts(uint8_t *buf) { int i; for (i = 0; i < ALIGNED_UNIT_LEN; i += 192) { if (BD_UNLIKELY(buf[i + 4] != 0x47)) { return 0; } /* Clear copy_permission_indicator bits */ buf[i] &= ~0xc0; } return 1; } // cited later by SG9 (KU-L1) and the §3.6.2 CPI-clear site
|
||||
KS-22 static int _verify_ts(uint8_t *buf) { int i; for (i = 0; i < ALIGNED_UNIT_LEN; i += 192) { if (BD_UNLIKELY(buf[i + 4] != 0x47)) { return 0; } /* Clear copy_permission_indicator bits */ buf[i] &= ~0xc0; } return 1; }
|
||||
KS-23 crypto_error = crypto_aes128e(aacs->uk->uk[curr_uk].key, out_buf, key); if (crypto_error) { LOG_CRYPTO_ERROR(DBG_AACS, "unit key derivation failed", crypto_error); return 1; } for (a = 0; a < 16; a++) { key[a] ^= out_buf[a]; /* here out_buf is plain data from in_buf */ } // test-only reference
|
||||
KS-24 /* plain ? */ if (!(buf[0] & 0xc0)) { // TP_extra_header Copy_permission_indicator == 0, unit is not encrypted return 1; } // test-only reference
|
||||
KS-25 AACS 2.x (UHD): the 64-byte Unit_Key_RO.inf stride and the FMTS IndividualSegment.tbl / .fmts model have no public book. They are pinned by the qa UHD fixture and aacs-unit-grid.md §2.
|
||||
@@ -34,3 +34,15 @@ KS-26 FMTS anchor contract: the decode server returns the whole set only for an
|
||||
KS-27 Please note: The AACS Specifications books for HD DVD and CBHD have been removed from this AACS website due to inactivity.
|
||||
KS-28 File-anchored grid on real discs: 32/32 sync on the file grid vs 0–1/32 on the disc-LBA grid (aacs-unit-grid.md §2 table). This corroborates KS-1.
|
||||
KS-29 The Licensed Drive reads Volume ID (Volume_ID) from the media and calculates a message authentication code (Dm) from the Volume ID and the Bus Key (BK) calculated in step 26 in Section 4.3. // test-only reference
|
||||
SS-1 Table 18 Sense key specific field definitions Sense Key Sense Key Specific Field Definition Reference ILLEGAL REQUEST Field pointer 2.4.1.1.4.2 HARDWARE ERROR, MEDIUM ERROR, or RECOVERED ERROR Actual retry count 2.4.1.1.4.3 NO SENSE or NOT READY Progress indication 2.4.1.1.4.4 … 2.4.1.1.4.4 Progress indication sense key specific data If the sense key is NO SENSE or NOT READY, the SENSE KEY SPECIFIC field shall be as shown in table 21. … PROGRESS INDICATION field The PROGRESS INDICATION field is a percent complete indication in which the returned value is a numerator that has 65 536 (10000h) as its denominator. The progress indication shall be based upon the total operation. … SKSV (Sense-key Specific Valid) bit 1 A sense-key specific valid (SKSV) bit set to one indicates the SENSE KEY SPECIFIC field contains valid information as defined in this manual.
|
||||
SS-2 The descriptor format sense data for response codes 72h (current errors) and 73h (deferred errors) is defined in table 12. … The sense key specific sense data descriptor (see table 17) provides additional information about the exception condition. The format and content of the sense-key specific data depends on the value in the SENSE KEY field (see 2.4.1.1.1).
|
||||
SS-3 In the event that a command requires a level of readiness that does not currently exist, the Drive should be terminated with CHECK CONDITION status and sense bytes SK/ASC/ASCQ should be selected from those shown in Table F.3. Table F.3 — Readiness Errors SK ASC ASCQ Description 2 04 00 LOGICAL UNIT NOT READY, CAUSE NOT REPORTABLE 2 04 01 LOGICAL UNIT IS IN PROCESS OF BECOMING READY 2 04 02 LOGICAL UNIT NOT READY, INITIALIZING CMD. REQUIRED 2 04 03 LOGICAL UNIT NOT READY, MANUAL INTERVENTION REQUIRED 2 04 04 LOGICAL UNIT NOT READY, FORMAT IN PROGRESS 2 04 07 LOGICAL UNIT NOT READY, OPERATION IN PROGRESS 2 04 08 LOGICAL UNIT NOT READY, LONG WRITE IN PROGRESS 2 0C 07 WRITE ERROR RECOVERY NEEDED 2 0C 0F DEFECTS IN ERROR WINDOW 2 30 00 INCOMPATIBLE MEDIUM INSTALLED 2 30 01 CANNOT READ MEDIUM – UNKNOWN FORMAT 2 or 5 30 02 CANNOT READ MEDIUM – INCOMPATIBLE FORMAT 2 30 03 CLEANING CARTRIDGE INSTALLED 2 or 5 30 04 CANNOT WRITE MEDIUM – UNKNOWN FORMAT 2 or 5 30 05 CANNOT WRITE MEDIUM – INCOMPATIBLE FORMAT 2 or 5 30 06 CANNOT FORMAT MEDIUM – INCOMPATIBLE MEDIUM 2 30 07 CLEANING FAILURE 2 or 5 30 11 CANNOT WRITE MEDIUM – UNSUPPORTED MEDIUM VERSION 2 3A 00 MEDIUM NOT PRESENT 2 3A 01 MEDIUM NOT PRESENT – TRAY CLOSED 2 3A 02 MEDIUM NOT PRESENT – TRAY OPEN
|
||||
SS-4 The TEST UNIT READY command (see table 202) provides a means to check if the logical unit is ready. This is not a request for a self-test. If the logical unit is able to accept an appropriate medium-access command without returning CHECK CONDITION status, this command shall return a GOOD status. If the logical unit is unable to become operational or is in a state such that an application client action (e.g., START UNIT command) is required to make the logical unit ready, the command shall be terminated with CHECK CONDITION status, with the sense key set to NOT READY.
|
||||
SS-5 The Drive maintains two separate Prevent states: Prevent and Persistent Prevent as described in 4.1.7. The Persistent and Prevent bits are used to independently select values for these states. See Table 329. Table 329 — State Selection Persistent Prevent Meaning 0 0 Prevent State shall be cleared (Unlocked) 0 1 Prevent State shall be set (Locked) 1 0 Persistent Prevent State shall be cleared (Persistent Allow) 1 1 Persistent Prevent State shall be set (Persistent Prevent)
|
||||
SS-6 When Power Conditions field is zero and FL is zero, the meanings of LoEj and Start are defined in Table 633. Table 633 — LoEj and Start Meanings when Power Conditions = 0 LoEj Start Operation 0 0 Stop the disc 0 1 Start the disc and make ready for access 1 0 Eject the disc if permitted. It is not an error If no media is present. See 6.13, PREVENT ALLOW MEDIUM REMOVAL Command 1 1 Load the disc, Start the disc and make ready for access. It is not an error if no media is present.
|
||||
SS-7 static int _mmc_report_key(MMC *mmc, uint8_t agid, uint32_t addr, uint8_t blocks, uint8_t format, uint8_t *buf, uint16_t len) { uint8_t cmd[MMC_CDB_SIZE]; memset(cmd, 0, sizeof(cmd)); BD_DEBUG(DBG_MMC, "MMC report key...\n"); cmd[0] = MMC_REPORT_KEY; cmd[2] = (addr >> 24) & 0xff; cmd[3] = (addr >> 16) & 0xff; cmd[4] = (addr >> 8) & 0xff; cmd[5] = addr & 0xff; cmd[6] = blocks; cmd[7] = MMC_KEY_CLASS_AACS; cmd[8] = (len >> 8) & 0xff; cmd[9] = len & 0xff; cmd[10] = (agid << 6) | (format & 0x3f); return device_send_cmd(mmc->dev, cmd, buf, 0, len); } … static int _mmc_invalidate_agid(MMC *mmc, uint8_t agid) { uint8_t buf[2]; memset(buf, 0, sizeof(buf)); return _mmc_report_key(mmc, agid, 0, 0, MMC_REPORT_KEY_AACS_INVALIDATE_AGID, buf, sizeof(buf)); } static void _mmc_invalidate_agids(MMC *mmc) { int agid; /* invalidate all agids */ for (agid = 0; agid < 4; agid++) { _mmc_invalidate_agid(mmc, agid); } } static int _mmc_report_agid(MMC *mmc, uint8_t *agid) { uint8_t buf[8]; memset(buf, 0, sizeof(buf)); int result = _mmc_report_key(mmc, 0, 0, 0, MMC_REPORT_KEY_AACS_AGID, buf, sizeof(buf)); if (result) { *agid = (buf[7] & 0xff) >> 6; } return result; }
|
||||
SS-12 The fsync() function shall request that all data for the open file descriptor named by fildes is to be transferred to the storage device associated with the file described by fildes. The nature of the transfer is implementation-defined. The fsync() function shall not return until the system has completed that action or until an error is detected. The fdatasync() function shall force all currently queued I/O operations associated with the file indicated by file descriptor fildes to the synchronized I/O completion state. The functionality shall be equivalent to fsync() with the symbol _POSIX_SYNCHRONIZED_IO defined, with the exception that all I/O operations shall be completed as defined for synchronized I/O data integrity completion.
|
||||
SS-13 This system call is extremely dangerous and should not be used in portable programs. None of these operations writes out the file's metadata. Therefore, unless the application is strictly performing overwrites of already-instantiated disk blocks, there are no guarantees that the data will be available after a crash. There is no user interface to know if a write is purely an overwrite. On filesystems using copy-on-write semantics (e.g., btrfs) an overwrite of existing allocated blocks is impossible. When writing into preallocated space, many filesystems also require calls into the block allocator, which this system call does not sync out to disk. This system call does not flush disk write caches and thus does not provide any data integrity on systems with volatile disk write caches. Some details SYNC_FILE_RANGE_WAIT_BEFORE and SYNC_FILE_RANGE_WAIT_AFTER will detect any I/O errors or ENOSPC conditions and will return these to the caller. Useful combinations of the flags bits are: SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE Ensures that all pages in the specified range which were dirty when sync_file_range() was called are placed under write-out. This is a start-write-for-data-integrity operation. SYNC_FILE_RANGE_WRITE Start write-out of all dirty pages in the specified range which are not presently under write-out. This is an asynchronous flush-to-disk operation. This is not suitable for data integrity operations. SYNC_FILE_RANGE_WAIT_BEFORE (or SYNC_FILE_RANGE_WAIT_AFTER) Wait for completion of write-out of all pages in the specified range. This can be used after an earlier SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE operation to wait for completion of that operation, and obtain its result. SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER This is a write-for-data-integrity operation that will ensure that all pages in the specified range which were dirty when sync_file_range() was called are committed to disk.
|
||||
SS-14 Does the same thing as fsync(2) then asks the drive to flush all buffered data to the permanent storage device (arg is ignored). As this drains the entire queue of the device and acts as a barrier, data that had been fsync'd on the same device before is guaranteed to be persisted when this call returns. This is currently implemented on HFS, MS-DOS (FAT), Universal Disk Format (UDF) and APFS file systems. The operation may take quite a while to complete. Certain FireWire drives have also been known to ignore the request to flush their buffered data.
|
||||
SS-15 Typically the WriteFile and WriteFileEx functions write data to an internal buffer that the operating system writes to a disk or communication pipe on a regular basis. The FlushFileBuffers function writes all the buffered information for a specified file to the device or pipe.
|
||||
SS-23 When coupled with a store, all previous operations become ordered before any load of this value with Acquire (or stronger) ordering. In particular, all previous writes become visible to all threads that perform an Acquire (or stronger) load of this value. When coupled with a load, if the loaded value was written by a store operation with Release (or stronger) ordering, then all subsequent operations become ordered after that store. In particular, all subsequent loads will see data written before the store.
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Spec-governed code sites (KU design §3.6.1 rule 11): `<file>\t<fn>\t<id>`, one row per
|
||||
# (site, quote). `spec_governed_sites_cite_the_spec` checks that the body of `fn <fn>` in
|
||||
# `<file>` names `<id>`. KU rows are KS-n; Stop adds its SS-n rows here.
|
||||
src/aacs/content.rs clear_copy_permission_indicator KS-5
|
||||
src/aacs/content.rs clear_copy_permission_indicator KS-6
|
||||
src/aacs/content.rs clear_copy_permission_indicator KS-22
|
||||
src/decrypt.rs apply_aacs_map KS-3
|
||||
src/decrypt.rs apply_aacs_map KS-5
|
||||
src/decrypt.rs apply_aacs_map KS-25
|
||||
src/decrypt.rs apply_aacs_map KS-26
|
||||
src/mux/resolve.rs probe_fmts_index_keys KS-25
|
||||
src/mux/resolve.rs probe_fmts_index_keys KS-26
|
||||
src/disc/mod.rs declared_cps_units KS-14
|
||||
src/whole_disc.rs single_cps_key_slot KS-14
|
||||
src/whole_disc.rs single_cps_key_slot KS-27
|
||||
src/sector/bus_removal.rs debus_read KS-18
|
||||
src/scsi/mod.rs parse_sense_progress SS-1
|
||||
src/scsi/mod.rs parse_sense_progress SS-2
|
||||
src/drive/mod.rs wait_ready_with SS-1
|
||||
src/drive/mod.rs wait_ready_with SS-3
|
||||
src/drive/mod.rs wait_ready_with SS-4
|
||||
src/drive/allow.rs is_allow SS-5
|
||||
src/drive/mod.rs lock_tray SS-5
|
||||
src/drive/mod.rs unlock_tray SS-5
|
||||
src/drive/allow.rs is_eject SS-6
|
||||
src/drive/mod.rs eject SS-6
|
||||
src/drive/mod.rs start_unit SS-6
|
||||
src/drive/mod.rs note_ledger SS-7
|
||||
Reference in New Issue
Block a user