feat: database-backed message model with multi-account key cache
- Add Windows 4.1.x Config.Cipher pointer-chain key extraction with cross-chunk
MemoryPatternScanner and bounded blob XOR decoding; keys still require per-DB
page-1 HMAC verification before caching.
- Cache keys by account fingerprint + database relative path; db scan --save
skips memory scanning entirely once all discovered databases are cached.
- Add DbMessage model (localId, serverId, type, timestamp, sender, display name,
avatar, isSelf) read read-only from Msg_{md5(chat)} via SQLCipher with hex/zstd
content decoding and contact.db name resolution.
- CLI: db messages / db contacts / db schema with masked defaults; include the
earlier chat send --session wiring. 97 Core tests pass; real-machine validated.
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
using WxAgent.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace WxAgent.Core.Tests;
|
||||
|
||||
public sealed class KeyCachePlannerTests
|
||||
{
|
||||
private static AccountKeySet CachedAccount(string fingerprint, params string[] paths) => new(
|
||||
fingerprint, "C:\\xwechat_files\\" + fingerprint, "4.1.13.63", DateTimeOffset.UtcNow,
|
||||
paths.Select(path => new DatabaseKeyEvidence(path, "salt", "key", 42, DateTimeOffset.UtcNow, KeyBindingConfidence.PageHmacVerified)).ToArray());
|
||||
|
||||
private static AccountDatabaseEntry Entry(string fingerprint, string path) => new(fingerprint, path);
|
||||
|
||||
[Fact]
|
||||
public void VerifiedKeysCoversEveryCachedDatabasePath()
|
||||
{
|
||||
var cached = new[] { CachedAccount("a", "contact.db", "message_0.db"), CachedAccount("b", "message_0.db") };
|
||||
var verified = KeyCachePlanner.VerifiedKeys(cached);
|
||||
Assert.Contains(KeyCachePlanner.Key("a", "contact.db"), verified);
|
||||
Assert.Contains(KeyCachePlanner.Key("a", "message_0.db"), verified);
|
||||
Assert.Contains(KeyCachePlanner.Key("b", "message_0.db"), verified);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HasPendingIsFalseWhenAllDiscoveredDatabasesAreCached()
|
||||
{
|
||||
var cached = new[] { CachedAccount("a", "contact.db", "message_0.db") };
|
||||
var verified = KeyCachePlanner.VerifiedKeys(cached);
|
||||
var discovered = new[] { Entry("a", "contact.db"), Entry("a", "message_0.db") };
|
||||
Assert.False(KeyCachePlanner.HasPending(discovered, verified));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HasPendingIsTrueForNewDatabaseOrNewAccount()
|
||||
{
|
||||
var cached = new[] { CachedAccount("a", "contact.db") };
|
||||
var verified = KeyCachePlanner.VerifiedKeys(cached);
|
||||
Assert.True(KeyCachePlanner.HasPending([Entry("a", "message_0.db")], verified));
|
||||
Assert.True(KeyCachePlanner.HasPending([Entry("b", "contact.db")], verified));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CacheKeyIsCaseInsensitiveForWindowsPaths()
|
||||
{
|
||||
var verified = KeyCachePlanner.VerifiedKeys([CachedAccount("a", "Contact.DB")]);
|
||||
Assert.True(verified.Contains(KeyCachePlanner.Key("a", "contact.db")));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user