Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Btrieve ACS #298

Merged
merged 14 commits into from
Nov 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified MBBSEmu.Tests/Assets/MBBSEMU.DB
Binary file not shown.
109 changes: 107 additions & 2 deletions MBBSEmu.Tests/Btrieve/BtrieveFileProcessor_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ public class BtrieveFileProcessor_Tests : TestBase, IDisposable
{
const int RECORD_LENGTH = 74;

private const string EXPECTED_METADATA_T_SQL = "CREATE TABLE metadata_t(record_length INTEGER NOT NULL, physical_record_length INTEGER NOT NULL, page_length INTEGER NOT NULL, variable_length_records INTEGER NOT NULL)";
private const string EXPECTED_METADATA_T_SQL = "CREATE TABLE metadata_t(record_length INTEGER NOT NULL, physical_record_length INTEGER NOT NULL, page_length INTEGER NOT NULL, variable_length_records INTEGER NOT NULL, version INTEGER NOT NULL, acs_name STRING, acs BLOB)";
private const string EXPECTED_KEYS_T_SQL = "CREATE TABLE keys_t(id INTEGER PRIMARY KEY, number INTEGER NOT NULL, segment INTEGER NOT NULL, attributes INTEGER NOT NULL, data_type INTEGER NOT NULL, offset INTEGER NOT NULL, length INTEGER NOT NULL, null_value INTEGER NOT NULL, UNIQUE(number, segment))";
private const string EXPECTED_DATA_T_SQL = "CREATE TABLE data_t(id INTEGER PRIMARY KEY, data BLOB NOT NULL, key_0 TEXT, key_1 INTEGER NOT NULL UNIQUE, key_2 TEXT, key_3 INTEGER NOT NULL UNIQUE)";

private static readonly Random RANDOM = new Random();
private static readonly Random RANDOM = new Random(Guid.NewGuid().GetHashCode());

protected readonly string _modulePath = Path.Join(Path.GetTempPath(), $"mbbsemu{RANDOM.Next()}");

Expand Down Expand Up @@ -1062,6 +1062,111 @@ public void SeekByKeyLessOrEqualFound()
btrieve.PerformOperation(1, BitConverter.GetBytes(-2_000_000_000), EnumBtrieveOperationCodes.QueryLessOrEqual).Should().BeFalse();
}

private const int ACS_RECORD_LENGTH = 128;

private static byte[] CreateRecord(string username)
{
var usernameBytes = Encoding.ASCII.GetBytes(username);
var record = new byte[ACS_RECORD_LENGTH];

Array.Fill(record, (byte)0xFF);

Array.Copy(usernameBytes, 0, record, 2, usernameBytes.Length);
record[2 + usernameBytes.Length] = 0;

return record;
}

private static BtrieveFile CreateACSBtrieveFile()
{
// all upper case acs
var acs = new byte[256];
for (var i = 0; i < acs.Length; ++i)
acs[i] = (byte)i;
for (var i = 'a'; i <= 'z'; ++i)
acs[i] = (byte)char.ToUpper(i);

var btrieveFile = new BtrieveFile()
{
RecordLength = ACS_RECORD_LENGTH,
FileName = "TEST.DAT",
RecordCount = 0,
ACSName = "ALLCAPS",
ACS = acs,
};

var key = new BtrieveKey();
key.Segments.Add(new BtrieveKeyDefinition()
{
Number = 0,
Attributes = EnumKeyAttributeMask.NumberedACS | EnumKeyAttributeMask.UseExtendedDataType,
DataType = EnumKeyDataType.Zstring,
Offset = 2,
Length = 30,
Segment = false,
ACS = acs
});

btrieveFile.Keys.Add(0, key);

btrieveFile.Records.Add(new BtrieveRecord(1, CreateRecord("Sysop")));
btrieveFile.Records.Add(new BtrieveRecord(2, CreateRecord("Paladine")));
btrieveFile.Records.Add(new BtrieveRecord(3, CreateRecord("Testing")));
return btrieveFile;
}

[Fact]
public void CreatesACS()
{
var btrieve = new BtrieveFileProcessor();
var connectionString = "Data Source=acs.db;Mode=Memory";

btrieve.CreateSqliteDBWithConnectionString(connectionString, CreateACSBtrieveFile());

btrieve.GetRecordCount().Should().Be(3);
btrieve.GetKeyLength(0).Should().Be(30);

// validate acs
using var cmd = new SqliteCommand("SELECT acs_name, acs, LENGTH(acs) FROM metadata_t", btrieve.Connection);
using var reader = cmd.ExecuteReader();
reader.Read().Should().BeTrue();
reader.GetString(0).Should().Be("ALLCAPS");
reader.GetInt32(2).Should().Be(256);
}

[Fact]
public void ACSSeekByKey()
{
var btrieve = new BtrieveFileProcessor();
var connectionString = "Data Source=acs.db;Mode=Memory";

btrieve.CreateSqliteDBWithConnectionString(connectionString, CreateACSBtrieveFile());

var key = new byte[30];
Array.Copy(Encoding.ASCII.GetBytes("paladine"), key, 8);

btrieve.PerformOperation(0, key, EnumBtrieveOperationCodes.QueryEqual).Should().BeTrue();
var record = btrieve.GetRecord(btrieve.Position);
record.Should().NotBeNull();
record.Offset.Should().Be(2);
// we searched by paladine but the actual data is Paladine
record.Data[2].Should().Be((byte)'P');
}

[Fact]
public void ACSInsertDuplicateFails()
{
var btrieve = new BtrieveFileProcessor();
var connectionString = "Data Source=acs.db;Mode=Memory";

btrieve.CreateSqliteDBWithConnectionString(connectionString, CreateACSBtrieveFile());

var record = new byte[ACS_RECORD_LENGTH];
Array.Copy(Encoding.ASCII.GetBytes("paladine"), 0, record, 2, 8);

btrieve.Insert(record).Should().Be(0);
}

/// <summary>Creates a copy of data shrunk by cutOff bytes at the end</summary>
private static byte[] MakeSmaller(byte[] data, int cutOff)
{
Expand Down
2 changes: 1 addition & 1 deletion MBBSEmu.Tests/Btrieve/BtrieveFile_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace MBBSEmu.Tests.Btrieve
{
public class BtrieveFile_Tests : TestBase, IDisposable
{
private static readonly Random RANDOM = new Random();
private static readonly Random RANDOM = new Random(Guid.NewGuid().GetHashCode());

private readonly string[] _btrieveFiles = { "MBBSEMU.DAT" };

Expand Down
168 changes: 148 additions & 20 deletions MBBSEmu.Tests/Btrieve/BtrieveKey_Tests.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
using FluentAssertions;
using MBBSEmu.Btrieve;
using MBBSEmu.Btrieve.Enums;
using MBBSEmu.DependencyInjection;
using MBBSEmu.Resources;
using NLog;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Xunit;

Expand Down Expand Up @@ -48,9 +44,9 @@ private static byte[] CreateNullPaddedString(string s, int length)
[InlineData(8, EnumKeyDataType.OldBinary, 0xF8F7F6F5F4F3F2F1)]
public void NegativeIntegerTypeConversion(ushort length, EnumKeyDataType type, object expected)
{
var key = new BtrieveKey() {
Segments = new List<BtrieveKeyDefinition>() {
new BtrieveKeyDefinition() {
var key = new BtrieveKey {
Segments = new List<BtrieveKeyDefinition> {
new BtrieveKeyDefinition {
Number = 0,
Offset = 0,
Length = length,
Expand Down Expand Up @@ -89,9 +85,9 @@ public void NegativeIntegerTypeConversion(ushort length, EnumKeyDataType type, o
[InlineData(8, EnumKeyDataType.OldBinary, 0x807060504030201)]
public void PositiveIntegerTypeConversion(ushort length, EnumKeyDataType type, object expected)
{
var key = new BtrieveKey() {
Segments = new List<BtrieveKeyDefinition>() {
new BtrieveKeyDefinition() {
var key = new BtrieveKey {
Segments = new List<BtrieveKeyDefinition> {
new BtrieveKeyDefinition {
Number = 0,
Offset = 0,
Length = length,
Expand Down Expand Up @@ -134,9 +130,9 @@ public void PositiveIntegerTypeConversion(ushort length, EnumKeyDataType type, o
[InlineData(1, EnumKeyDataType.OldAscii, "T")]
public void StringTypeConversion(ushort length, EnumKeyDataType type, object expected)
{
var key = new BtrieveKey() {
Segments = new List<BtrieveKeyDefinition>() {
new BtrieveKeyDefinition() {
var key = new BtrieveKey {
Segments = new List<BtrieveKeyDefinition> {
new BtrieveKeyDefinition {
Number = 0,
Offset = 0,
Length = length,
Expand All @@ -155,9 +151,9 @@ public void StringTypeConversion(ushort length, EnumKeyDataType type, object exp
[Fact]
public void CompositeKeyConcatentation()
{
var key = new BtrieveKey() {
Segments = new List<BtrieveKeyDefinition>() {
new BtrieveKeyDefinition() {
var key = new BtrieveKey {
Segments = new List<BtrieveKeyDefinition> {
new BtrieveKeyDefinition {
Number = 0,
Offset = 2,
Length = 8,
Expand All @@ -167,7 +163,7 @@ public void CompositeKeyConcatentation()
SegmentIndex = 0,
NullValue = 0,
},
new BtrieveKeyDefinition() {
new BtrieveKeyDefinition {
Number = 0,
Offset = 20,
Length = 4,
Expand Down Expand Up @@ -201,9 +197,9 @@ public void CompositeKeyConcatentation()
[InlineData(EnumKeyDataType.OldBinary)]
public void NullValueString(EnumKeyDataType dataType)
{
var key = new BtrieveKey() {
Segments = new List<BtrieveKeyDefinition>() {
new BtrieveKeyDefinition() {
var key = new BtrieveKey {
Segments = new List<BtrieveKeyDefinition> {
new BtrieveKeyDefinition {
Number = 0,
Offset = 2,
Length = 8,
Expand All @@ -223,5 +219,137 @@ public void NullValueString(EnumKeyDataType dataType)
var sqlLiteObject = key.ExtractKeyInRecordToSqliteObject(record);
sqlLiteObject.Should().Be(DBNull.Value);
}

private static byte[] UpperACS()
{
var acs = new byte[256];
for (var i = 0; i < acs.Length; ++i)
acs[i] = (byte)i;
// make uppercase
for (var i = 'a'; i <= 'z'; ++i)
acs[i] = (byte)char.ToUpper(i);

return acs;
}

[Fact]
public void ACSReplacement_SingleKey()
{
var acs = UpperACS();

var key = new BtrieveKey {
Segments = new List<BtrieveKeyDefinition> {
new BtrieveKeyDefinition {
Number = 0,
Offset = 2,
Length = 8,
DataType = EnumKeyDataType.Zstring,
Attributes = EnumKeyAttributeMask.UseExtendedDataType | EnumKeyAttributeMask.NumberedACS,
ACS = acs,
Segment = true,
SegmentIndex = 0,
NullValue = 0,
}}
};

var record = new byte[128];
Array.Fill(record, (byte)0xFF, 0, record.Length);
// first segment is all spaces i.e. null
record[2] = (byte)'a';
record[3] = (byte)'B';
record[4] = (byte)'t';
record[5] = (byte)'Z';
record[6] = (byte)'%';
record[7] = 0;

var sqlLiteObject = key.ExtractKeyInRecordToSqliteObject(record);
sqlLiteObject.Should().Be("ABTZ%");
}

[Theory]
[InlineData("b", "B")]
[InlineData("test", "TEST")]
[InlineData("1234567890", "1234567890")]
[InlineData("test1234test4321", "TEST1234TEST4321")]
public void ACSReplacement_MultiKey(string input, string expected)
{
var acs = UpperACS();

var key = new BtrieveKey {
Segments = new List<BtrieveKeyDefinition> {
new BtrieveKeyDefinition {
Number = 0,
Offset = 2,
Length = 8,
DataType = EnumKeyDataType.Zstring,
Attributes = EnumKeyAttributeMask.UseExtendedDataType | EnumKeyAttributeMask.NumberedACS,
ACS = acs,
Segment = true,
SegmentIndex = 0,
NullValue = 0,
},
new BtrieveKeyDefinition {
Number = 0,
Offset = 10,
Length = 8,
DataType = EnumKeyDataType.Zstring,
Attributes = EnumKeyAttributeMask.UseExtendedDataType | EnumKeyAttributeMask.NumberedACS,
ACS = acs,
Segment = false,
SegmentIndex = 1,
NullValue = 0,
}}
};

var record = new byte[128];
Array.Fill(record, (byte)0x0, 0, record.Length);
Array.Copy(Encoding.ASCII.GetBytes(input), 0, record, 2, input.Length);

var sqlLiteObject = Encoding.ASCII.GetString((byte[])key.ExtractKeyInRecordToSqliteObject(record)).TrimEnd((char)0);
sqlLiteObject.Should().Be(expected);
}

[Theory]
[InlineData("b", "b")]
[InlineData("test", "test")]
[InlineData("1234567890", "1234567890")]
[InlineData("test1234test4321", "test1234TEST4321")]
public void ACSReplacement_ACSOnlyOnSecondKey(string input, string expected)
{
var acs = UpperACS();

var key = new BtrieveKey()
{
Segments = new List<BtrieveKeyDefinition> {
new BtrieveKeyDefinition {
Number = 0,
Offset = 2,
Length = 8,
DataType = EnumKeyDataType.Zstring,
Attributes = EnumKeyAttributeMask.UseExtendedDataType,
Segment = true,
SegmentIndex = 0,
NullValue = 0,
},
new BtrieveKeyDefinition {
Number = 0,
Offset = 10,
Length = 8,
DataType = EnumKeyDataType.Zstring,
Attributes = EnumKeyAttributeMask.UseExtendedDataType | EnumKeyAttributeMask.NumberedACS,
ACS = acs,
Segment = false,
SegmentIndex = 1,
NullValue = 0,
}}
};

var record = new byte[128];
Array.Fill(record, (byte)0x0, 0, record.Length);
Array.Copy(Encoding.ASCII.GetBytes(input), 0, record, 2, input.Length);

var sqlLiteObject = Encoding.ASCII.GetString((byte[])key.ExtractKeyInRecordToSqliteObject(record)).TrimEnd((char)0);
sqlLiteObject.Should().Be(expected);
}
}
}
Loading