XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Buffers.Binary;
using System.Text;
using XFESystem.Core;
using XFESystem.Desktop;
using Xunit;

namespace XFESystem.Core.Tests;

public sealed class UsbWorkspaceTests
{
    private sealed class Disk : IWorkspaceBlockDevice
    {
        public const ulong Partition = 2048, Last = Partition+180000-1, Data = Partition+32+2800, Workspace = Data+1;
        private readonly Dictionary<ulong,byte[]> sectors;
        private ulong allowed, count;
        public ulong? FailAt;
        public int Flushes, Writes;
        public ulong SectorCount => 200000;
        public Disk(Dictionary<ulong,byte[]>? existing=null)
        {
            sectors=existing??new(); if(existing!=null)return;
            byte[] boot=new byte[512]; U16(boot,11,512);boot[13]=1;U16(boot,14,32);boot[16]=2;
            U32(boot,32,180000);U32(boot,36,1400);U32(boot,44,2);boot[510]=85;boot[511]=170;Raw(Partition,boot);
            byte[] fat=new byte[1400*512];U32(fat,0,0x0ffffff8);U32(fat,4,0x0fffffff);U32(fat,8,0x0fffffff);
            for(uint cluster=3;cluster<3+UsbWorkspaceFormat.ContainerSectors;cluster++)U32(fat,checked((int)cluster*4),cluster+1==3+UsbWorkspaceFormat.ContainerSectors?0x0fffffffu:cluster+1);
            U32(fat,131075*4,0x0fffffff);U32(fat,131076*4,0x0fffffff);
            Raw(Partition+32,fat);Raw(Partition+32+1400,fat);
            byte[] root=new byte[512];LongEntry(root,0,"Workspace.bin","WORKSPACBIN",false,3,UsbWorkspaceFormat.ContainerBytes);LongEntry(root,64,"UserFiles","USERFILE   ",true,131075,0);Raw(Data,root);
            byte[] folder=new byte[512];Entry(folder,0,"TEST    DLL",false,131076,4);Raw(Data+131073,folder);
            byte[] program=new byte[512];program[0]=1;program[1]=2;program[2]=3;program[3]=4;Raw(Data+131074,program);
            Raw(Workspace,UsbWorkspaceFormat.CreateDescriptor());
        }
        public Disk Reopen()=>new(sectors);
        public ReadOnlyFat32Volume Fat()=>new(this,Partition,Last);
        public UsbWorkspaceJournal Journal()=>new(this,Workspace);
        public void Read(ulong lba,Span<byte> target)
        { if(target.Length%512!=0||lba>=SectorCount||(ulong)target.Length/512>SectorCount-lba)throw new IOException(); for(int i=0;i<target.Length;i+=512){target.Slice(i,512).Clear();if(sectors.TryGetValue(lba+(uint)i/512,out var data))data.CopyTo(target.Slice(i,512));} }
        public void Write(ulong lba,ReadOnlySpan<byte> source)
        { if(source.Length%512!=0||lba<allowed||lba-allowed>=count||(ulong)source.Length/512>count-(lba-allowed)||FailAt==lba)throw new IOException("Simulated write failure or forbidden extent.");Writes++;Raw(lba,source); }
        private void Raw(ulong lba,ReadOnlySpan<byte> source)
        { for(int i=0;i<source.Length;i+=512)sectors[lba+(uint)i/512]=source.Slice(i,512).ToArray(); }
        public void Flush()=>Flushes++;
        public void AllowWorkspace(ulong lba,uint size) { if(lba!=Workspace||size!=UsbWorkspaceFormat.ContainerSectors)throw new IOException();allowed=lba+1;count=2*UsbWorkspaceFormat.BankSectors; }
        public void Corrupt(ulong lba) { if(!sectors.TryGetValue(lba,out var data))sectors[lba]=data=new byte[512];data[0]^=255; }
        public void ChangeFat(uint cluster,uint next) { ulong offset=(ulong)cluster*4;byte[] bytes=sectors[Partition+32+offset/512];U32(bytes,(int)(offset%512),next); }
        private static void Entry(byte[] data,int at,string name,bool directory,uint cluster,int length)
        { Encoding.ASCII.GetBytes(name.PadRight(11)).CopyTo(data,at);data[at+11]=directory?(byte)16:(byte)32;U16(data,at+20,cluster>>16);U16(data,at+26,cluster&65535);U32(data,at+28,(uint)length); }
        private static void LongEntry(byte[] data,int at,string name,string shortName,bool directory,uint cluster,int length)
        {
            Entry(data,at+32,shortName,directory,cluster,length);data[at]=0x41;data[at+11]=15;
            byte checksum=0;foreach(byte b in Encoding.ASCII.GetBytes(shortName.PadRight(11)))checksum=(byte)(((checksum&1)<<7)+(checksum>>1)+b);data[at+13]=checksum;
            int[] positions=[1,3,5,7,9,14,16,18,20,22,24,28,30];
            for(int i=0;i<13;i++)U16(data,at+positions[i],i<name.Length?name[i]:i==name.Length?0u:65535u);
        }
        private static void U16(byte[] b,int at,uint n)=>BinaryPrimitives.WriteUInt16LittleEndian(b.AsSpan(at),(ushort)n);
        private static void U32(byte[] b,int at,uint n)=>BinaryPrimitives.WriteUInt32LittleEndian(b.AsSpan(at),n);
    }

    [Fact] public void OrdinaryFatFileIsReadFromMediaAndWorkspaceExtentIsContiguous()
    {
        var disk=new Disk();var fat=disk.Fat();Assert.Equal(new byte[]{1,2,3,4},fat.ReadFile("UserFiles/TEST.DLL"));
        Assert.Equal((Disk.Workspace,(uint)UsbWorkspaceFormat.ContainerSectors),fat.ContiguousWorkspace());
    }
    [Fact] public void NewJournalIsEmptyAndSuccessfulSaveFlushesDataAndCommit()
    {
        var disk=new Disk();var journal=disk.Journal();Assert.Null(journal.Load());journal.Save("first"u8);
        Assert.Equal(2,disk.Flushes);Assert.Equal(1ul,journal.Generation);
        Assert.Equal("first",Encoding.UTF8.GetString(disk.Reopen().Journal().Load()!));
    }
    [Fact] public void InterruptedNewCommitKeepsThePreviousGeneration()
    {
        var disk=new Disk();var journal=disk.Journal();journal.Load();journal.Save("previous"u8);
        disk.FailAt=Disk.Workspace+1+UsbWorkspaceFormat.BankSectors;
        Assert.Throws<IOException>(()=>journal.Save("new data without a committed header"u8));
        Assert.Equal("previous",Encoding.UTF8.GetString(disk.Reopen().Journal().Load()!));
    }
    [Fact] public void DamagedNewestPayloadFallsBackToPreviousValidBank()
    {
        var disk=new Disk();var journal=disk.Journal();journal.Load();journal.Save("old"u8);journal.Save("new"u8);
        disk.Corrupt(Disk.Workspace+2+UsbWorkspaceFormat.BankSectors);
        var reopened=disk.Reopen().Journal();Assert.Equal("old",Encoding.UTF8.GetString(reopened.Load()!));Assert.Equal(1ul,reopened.Generation);
    }
    [Fact] public void TwoDamagedCommitRecordsAreNeverSilentlyReinitialized()
    {
        var disk=new Disk();disk.Corrupt(Disk.Workspace+1);disk.Corrupt(Disk.Workspace+1+UsbWorkspaceFormat.BankSectors);
        Assert.Throws<IOException>(()=>disk.Reopen().Journal().Load());Assert.Equal(0,disk.Writes);
    }
    [Fact] public void DescriptorAndBootSectorsCannotBeWrittenThroughWorkspaceWindow()
    {
        var disk=new Disk();disk.Journal();
        foreach(ulong lba in new[]{0ul,Disk.Partition,Disk.Workspace,Disk.Workspace+UsbWorkspaceFormat.ContainerSectors-1})
            Assert.Throws<IOException>(()=>disk.Write(lba,new byte[512]));
    }
    [Fact] public void BadDescriptorOrFatMirrorRefusesMountBeforeWriting()
    {
        var disk=new Disk();disk.Corrupt(Disk.Workspace);Assert.Throws<IOException>(()=>disk.Journal());Assert.Equal(0,disk.Writes);
        disk=new Disk();disk.ChangeFat(3,3);Assert.Throws<IOException>(()=>disk.Fat());Assert.Equal(0,disk.Writes);
    }
    [Fact] public void TextBinaryMoveDeleteAndAppearanceSurviveNewWorkspaceInstance()
    {
        var disk=new Disk();var workspace=new PersistentUsbWorkspace(disk.Journal(),disk.Fat());
        workspace.WriteText(SystemPaths.Combine(SystemPaths.Documents,"Keep.txt"),"saved on USB");
        workspace.WriteBytes(SystemPaths.Combine(SystemPaths.Documents,"Keep.dll"),new byte[]{0x4d,0x5a,3,4});
        workspace.Move(SystemPaths.Combine(SystemPaths.Documents,"Keep.txt"),SystemPaths.Combine(SystemPaths.Documents,"Moved.txt"));
        workspace.WriteText(SystemPaths.Combine(SystemPaths.Documents,"Delete.txt"),"gone");workspace.Delete(SystemPaths.Combine(SystemPaths.Documents,"Delete.txt"));
        new DesktopTheme(DesktopThemeMode.Dark,0xff0033).Save(workspace);
        disk=disk.Reopen();workspace=new PersistentUsbWorkspace(disk.Journal(),disk.Fat());
        Assert.Equal("saved on USB",workspace.ReadText(SystemPaths.Combine(SystemPaths.Documents,"Moved.txt")));
        Assert.Equal(new byte[]{0x4d,0x5a,3,4},workspace.ReadBytes(SystemPaths.Combine(SystemPaths.Documents,"Keep.dll")));
        Assert.False(workspace.Exists(SystemPaths.Combine(SystemPaths.Documents,"Delete.txt")));
        var theme=DesktopTheme.Load(workspace,out var error);Assert.Null(error);Assert.Equal(DesktopThemeMode.Dark,theme.Mode);Assert.True(workspace.IsPersistent);
    }
    [Fact] public void FailedSaveRollsBackTheLiveCacheAndDiagnosticsDoNotWriteMedia()
    {
        var disk=new Disk();var workspace=new PersistentUsbWorkspace(disk.Journal(),disk.Fat());string path=SystemPaths.Combine(SystemPaths.Documents,"A.txt");
        workspace.WriteText(path,"old");disk.FailAt=Disk.Workspace+1+UsbWorkspaceFormat.BankSectors;
        Assert.Throws<IOException>(()=>workspace.WriteText(path,"unsaved"));Assert.Equal("old",workspace.ReadText(path));
        int writes=disk.Writes;workspace.CreateDirectory("X:\\System\\Diagnostics");workspace.WriteText("X:\\System\\Diagnostics\\Usb.txt","temporary report");Assert.Equal(writes,disk.Writes);
    }
    [Fact] public void ImportFolderReadsFatBytesAndRejectsRuntimeFatMutation()
    {
        var disk=new Disk();var workspace=new PersistentUsbWorkspace(disk.Journal(),disk.Fat());
        Assert.Equal(new byte[]{1,2,3,4},workspace.ReadBytes("X:\\UsbFiles\\TEST.DLL"));
        Assert.Throws<IOException>(()=>workspace.WriteBytes("X:\\UsbFiles\\TEST.DLL",new byte[]{9}));
        Assert.Throws<IOException>(()=>workspace.Delete("X:\\UsbFiles\\TEST.DLL"));Assert.Equal(0,disk.Writes);
    }
}