Refactor caching logic and enhance hash generation in steamcache

- Replaced SHA1 hash calculations with SHA256 for improved security and consistency in cache key generation.
- Introduced a new TestURLHashing function to validate the new cache key generation logic.
- Removed outdated hash calculation tests and streamlined the caching process to focus on URL-based hashing.
- Implemented lightweight validation methods in ServeHTTP to enhance performance and reliability of cached responses.
- Added batched time updates in VFS implementations for better performance during access time tracking.
This commit is contained in:
2025-09-02 05:45:44 -05:00
parent b9358a0e8d
commit 4a4579b0f3
6 changed files with 621 additions and 324 deletions

View File

@@ -3,9 +3,9 @@ package steamcache
import (
"io"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
)
@@ -110,99 +110,60 @@ func TestCacheMissAndHit(t *testing.T) {
}
}
func TestHashCalculation(t *testing.T) {
// Test data
testData := []byte("Hello, World!")
func TestURLHashing(t *testing.T) {
// Test the new SHA256-based cache key generation
// Calculate hash
hash := calculateFileHash(testData)
// Expected SHA1 hash of "Hello, World!"
expectedHash := "0a0a9f2a6772942557ab5355d76af442f8f65e01"
if hash != expectedHash {
t.Errorf("Hash calculation failed: expected %s, got %s", expectedHash, hash)
}
// Test verification
if !verifyFileHash(testData, expectedHash) {
t.Error("Hash verification failed for correct hash")
}
if verifyFileHash(testData, "wronghash") {
t.Error("Hash verification passed for wrong hash")
}
}
func TestHashVerificationWithRealData(t *testing.T) {
// Test with some real data to ensure our hash calculation is correct
testCases := []struct {
data string
expected string
input string
desc string
shouldCache bool
}{
{"", "da39a3ee5e6b4b0d3255bfef95601890afd80709"}, // SHA1 of empty string
{"test", "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"}, // SHA1 of "test"
{"Hello, World!", "0a0a9f2a6772942557ab5355d76af442f8f65e01"}, // SHA1 of "Hello, World!"
{
input: "/depot/1684171/chunk/abcdef1234567890",
desc: "chunk file URL",
shouldCache: true,
},
{
input: "/depot/1684171/manifest/944076726177422892/5/abcdef1234567890",
desc: "manifest file URL",
shouldCache: true,
},
{
input: "/depot/invalid/path",
desc: "invalid depot URL format",
shouldCache: true, // Still gets hashed, just not a proper Steam format
},
{
input: "/some/other/path",
desc: "non-Steam URL",
shouldCache: false, // Not cached
},
}
for _, tc := range testCases {
data := []byte(tc.data)
hash := calculateFileHash(data)
if hash != tc.expected {
t.Errorf("Hash calculation failed for '%s': expected %s, got %s", tc.data, tc.expected, hash)
}
t.Run(tc.desc, func(t *testing.T) {
result := generateSteamCacheKey(tc.input)
if !verifyFileHash(data, tc.expected) {
t.Errorf("Hash verification failed for '%s'", tc.data)
}
if tc.shouldCache {
// Should return a cache key with "steam/" prefix
if !strings.HasPrefix(result, "steam/") {
t.Errorf("generateSteamCacheKey(%s) = %s, expected steam/ prefix", tc.input, result)
}
// Should be exactly 70 characters (6 for "steam/" + 64 for SHA256 hex)
if len(result) != 70 {
t.Errorf("generateSteamCacheKey(%s) length = %d, expected 70", tc.input, len(result))
}
} else {
// Should return empty string for non-Steam URLs
if result != "" {
t.Errorf("generateSteamCacheKey(%s) = %s, expected empty string", tc.input, result)
}
}
})
}
}
func TestResponseHashCalculation(t *testing.T) {
// Create a mock HTTP response
resp := &http.Response{
StatusCode: 200,
Status: "200 OK",
Header: http.Header{
"Content-Type": []string{"application/octet-stream"},
"Content-Length": []string{"13"},
"Cache-Control": []string{"public, max-age=3600"},
},
}
bodyData := []byte("Hello, World!")
// Calculate response hash
responseHash := calculateResponseHash(resp, bodyData)
// The hash should be different from just the body hash
bodyHash := calculateFileHash(bodyData)
if responseHash == bodyHash {
t.Error("Response hash should be different from body hash when headers are present")
}
// Test that the same response produces the same hash
responseHash2 := calculateResponseHash(resp, bodyData)
if responseHash != responseHash2 {
t.Error("Response hash should be consistent for the same response")
}
// Test with different headers
resp2 := &http.Response{
StatusCode: 200,
Status: "200 OK",
Header: http.Header{
"Content-Type": []string{"text/plain"},
"Content-Length": []string{"13"},
},
}
responseHash3 := calculateResponseHash(resp2, bodyData)
if responseHash == responseHash3 {
t.Error("Response hash should be different for different headers")
}
}
// Removed hash calculation tests since we switched to lightweight validation
func TestSteamKeySharding(t *testing.T) {
sc := New("localhost:8080", "0", "1G", t.TempDir(), "", "lru", "lru")
@@ -236,35 +197,4 @@ func TestSteamKeySharding(t *testing.T) {
// and be readable, whereas without sharding it might not work correctly
}
func TestKeyGeneration(t *testing.T) {
testCases := []struct {
input string
expected string
desc string
}{
{
input: "/depot/1684171/chunk/0016cfc5019b8baa6026aa1cce93e685d6e06c6e",
expected: "steam/0016cfc5019b8baa6026aa1cce93e685d6e06c6e",
desc: "chunk file URL",
},
{
input: "/depot/1684171/manifest/944076726177422892/5/12001286503415372840",
expected: "steam/12001286503415372840",
desc: "manifest file URL",
},
{
input: "/depot/invalid/path",
expected: "",
desc: "invalid depot URL format",
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
result := generateSteamCacheKey(tc.input)
if result != tc.expected {
t.Errorf("generateSteamCacheKey(%s) = %s, expected %s", tc.input, result, tc.expected)
}
})
}
}
// Removed old TestKeyGeneration - replaced with TestURLHashing that uses SHA256