{"record":{"id":"58b587b66345cf70","repo":"tonhowtf/omniget","slug":"http-fetching-aes-key","errorCode":null,"errorMessage":"HTTP {} fetching AES key","messagePattern":"HTTP (.+?) fetching AES key","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/hls_downloader.rs","lineNumber":476,"sourceCode":"            }\n        }\n        Ok(None)\n    }\n\n    async fn fetch_key_with_retry(\n        &self,\n        url: &str,\n        referer: &str,\n        max_retries: u32,\n    ) -> anyhow::Result<Vec<u8>> {\n        let mut last_err = None;\n        for attempt in 0..max_retries {\n            let req = apply_referer_headers(self.client.get(url), referer)\n                .header(\"User-Agent\", self.effective_user_agent());\n            match req.send().await {\n                Ok(resp) => {\n                    if !resp.status().is_success() {\n                        last_err = Some(anyhow::anyhow!(\"HTTP {} fetching AES key\", resp.status()));\n                    } else {\n                        match resp.bytes().await {\n                            Ok(bytes) => return Ok(bytes.to_vec()),\n                            Err(e) => last_err = Some(anyhow::anyhow!(e)),\n                        }\n                    }\n                }\n                Err(e) => last_err = Some(anyhow::anyhow!(e)),\n            }\n            if attempt < max_retries - 1 {\n                let base = 500 * (attempt as u64 + 1);\n                let jitter = rand::random::<u64>() % (base / 2 + 1);\n                tokio::time::sleep(Duration::from_millis(base + jitter)).await;\n            }\n        }\n        Err(last_err.unwrap_or_else(|| {\n            anyhow::anyhow!(\"Failed to fetch AES key after {} attempts\", max_retries)\n        }))","sourceCodeStart":458,"sourceCodeEnd":494,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/hls_downloader.rs#L458-L494","documentation":"fetch_key_with_retry downloads the AES-128 encryption key referenced by EXT-X-KEY from its URI. Non-success HTTP statuses are recorded as this error and retried with backoff; the key is required to decrypt segments, so this failure aborts decryption.","triggerScenarios":"Key server returns 403 (missing Referer/cookies), 404 (key URI expired or session-bound), 429, or 5xx while fetching the AES key URL.","commonSituations":"Key URIs tied to the playlist request's session/IP/cookies; expired one-time key tokens; geo-blocking of the key endpoint while segments are served openly.","solutions":["Send the same Referer/User-Agent/Cookie headers used for the playlist when fetching the key.","Re-fetch the playlist (and its key URI) if the key URL is session-bound or expired.","Check status: 403 -> headers/geo; 404 -> regenerate playlist URL; 429 -> back off and retry later.","Fetch the key once and cache it (EXT-X-KEY usually reuses the same URI across segments) to reduce rate-limit risk."],"exampleFix":"// before\nlet key = fetch_key_with_retry(client, key_url, referer, 3).await?;\n// after\nlet key = fetch_key_with_retry(client, key_url, referer, 5)\n    .await\n    .with_context(|| format!(\"fetching AES key {key_url}\"))?;","handlingStrategy":"retry","validationCode":"// Only attempt decryption if the key looks like a 16-byte key\nlet key = fetch_key_with_retry(client, key_url, referer, 5).await?;\nif key.len() != 16 { anyhow::bail!(\"key server returned {} bytes\", key.len()); }","typeGuard":null,"tryCatchPattern":"match fetch_key_with_retry(client, &key_uri, referer, 5).await {\n    Ok(k) if k.len() == 16 => k,\n    Ok(k) => bail!(\"AES key wrong size: {} bytes\", k.len()),\n    Err(e) if e.to_string().contains(\"HTTP 40\") =>\n        bail!(\"key denied: mirror cookies/referer from playlist request\"),\n    Err(e) => return Err(e.into()),\n};","preventionTips":["Send identical headers/cookies to the key endpoint as to the playlist.","Cache the key per EXT-X-KEY URI instead of refetching per segment.","Refresh the playlist when key URLs are session-bound or one-time.","Back off on 429 and honor Retry-After."],"tags":["hls","aes","http","encryption-key"],"backgroundTag":"http-error-response","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}