527 lines
39 KiB
MQL5
527 lines
39 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| CBitBuffer_Test.mq5 |
|
|
//| Copyright © 2025, Amr Ali |
|
|
//| https://www.mql5.com/en/users/amrali |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Amr Ali"
|
|
#property link "https://www.mql5.com/en/users/amrali"
|
|
#property version "1.02"
|
|
|
|
// Include the CBitBuffer class definition.
|
|
// Assuming CBitBuffer.mqh is in the same directory or MQL5/Include.
|
|
#include "CBitBuffer.mqh"
|
|
|
|
// Define a simple structure for testing WriteStruct/ReadStruct.
|
|
struct MyTestStruct
|
|
{
|
|
int id;
|
|
double price;
|
|
long volume;
|
|
bool isActive;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Script program start function |
|
|
//+------------------------------------------------------------------+
|
|
void OnStart()
|
|
{
|
|
Print("--- CBitBuffer Capabilities Test ---");
|
|
|
|
// --- 1. Bit-Packing Operations ---
|
|
TestBitPacking();
|
|
|
|
// --- 2. Basic Write and Read Operations ---
|
|
TestBasicOperations();
|
|
|
|
// --- 3. Mixed Read/Write Operations ---
|
|
TestMixedOperations();
|
|
|
|
// --- 4. Variable-Length Quantity (VLQ) Encoding Test ---
|
|
TestVLQEncoding();
|
|
|
|
// --- 5. String and Struct Serialization Test ---
|
|
TestStringAndStruct();
|
|
|
|
// --- 6. File Save and Load Test ---
|
|
TestFileOperations();
|
|
|
|
// --- 7. Error Handling Test ---
|
|
TestErrorHandling();
|
|
|
|
Print("--- CBitBuffer Test Complete ---");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Test Function: Bit-Packing Operations |
|
|
//+------------------------------------------------------------------+
|
|
void TestBitPacking()
|
|
{
|
|
Print("\n--- Test: Bit-Packing Operations ---");
|
|
CBitBuffer buffer;
|
|
|
|
// Write bits
|
|
Print("Writing bits...");
|
|
buffer.WriteBit(true); // 1
|
|
buffer.WriteBit(false); // 0
|
|
buffer.WriteBit(true); // 1
|
|
// Writing a small integer (e.g., 5 bits for a value up to 31)
|
|
buffer.WriteBits(27, 5); // 27 = 11011 (binary)
|
|
// Writing a larger integer (e.g., 10 bits for a value up to 1023)
|
|
buffer.WriteBits(789, 10); // 789 = 1100010101 (binary)
|
|
// Writing an 8-bit value (byte)
|
|
buffer.WriteBits(0xA5, 8); // 0xA5 = 10100101 (binary)
|
|
|
|
PrintFormat("Total bits written: %d", buffer.GetTotalWrittenBits());
|
|
buffer.PrintHex(); // Debug print
|
|
|
|
// Reset read position to start
|
|
buffer.ResetReadPosition();
|
|
Print("Reading bits...");
|
|
|
|
// Read and verify
|
|
PrintFormat("Read bit 1: %s (Expected: true)", buffer.ReadBit() ? "true" : "false");
|
|
PrintFormat("Read bit 2: %s (Expected: false)", buffer.ReadBit() ? "true" : "false");
|
|
PrintFormat("Read bit 3: %s (Expected: true)", buffer.ReadBit() ? "true" : "false");
|
|
PrintFormat("Read 5-bit integer: %d (Expected: 27)", (int)buffer.ReadBits(5));
|
|
PrintFormat("Read 10-bit integer: %d (Expected: 789)", (int)buffer.ReadBits(10));
|
|
PrintFormat("Read 8-bit value: 0x%X (Expected: 0xA5)", (int)buffer.ReadBits(8));
|
|
|
|
if (buffer.GetLastError() == BIT_BUFFER_NO_ERROR)
|
|
{
|
|
Print("Bit-Packing Operations test: PASSED");
|
|
}
|
|
else
|
|
{
|
|
PrintFormat("Bit-Packing Operations test: FAILED! Error: %s", buffer.GetLastErrorString());
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Test Function: Basic Write and Read Operations |
|
|
//+------------------------------------------------------------------+
|
|
void TestBasicOperations()
|
|
{
|
|
Print("\n--- Test: Basic Write and Read Operations ---");
|
|
CBitBuffer buffer;
|
|
|
|
// Write various data types
|
|
Print("Writing data...");
|
|
buffer.WriteBool(true);
|
|
buffer.WriteBit(false); // Write a single bit
|
|
buffer.WriteChar('A');
|
|
buffer.WriteUChar(255);
|
|
buffer.WriteShort(-12345);
|
|
buffer.WriteUShort(65530);
|
|
buffer.WriteInt(123456789);
|
|
buffer.WriteUInt(4294967295U); // Max uint
|
|
buffer.WriteLong(9223372036854775807LL);
|
|
buffer.WriteULong(18446744073709551615ULL); // Max ulong
|
|
buffer.WriteFloat(3.14159f);
|
|
buffer.WriteDouble(2.718281828459045);
|
|
datetime now = TimeCurrent();
|
|
buffer.WriteDatetime(now);
|
|
|
|
PrintFormat("Total bits written: %d", buffer.GetTotalWrittenBits());
|
|
buffer.PrintHex(); // Debug print
|
|
|
|
// Reset read position to start
|
|
buffer.ResetReadPosition();
|
|
Print("Reading data...");
|
|
|
|
// Read and verify
|
|
PrintFormat("Read Bool: %s (Expected: true)", buffer.ReadBool() ? "true" : "false");
|
|
PrintFormat("Read Bit: %s (Expected: false)", buffer.ReadBit() ? "true" : "false");
|
|
PrintFormat("Read Char: %c (Expected: A)", buffer.ReadChar());
|
|
PrintFormat("Read UChar: %d (Expected: 255)", buffer.ReadUChar());
|
|
PrintFormat("Read Short: %d (Expected: -12345)", buffer.ReadShort());
|
|
PrintFormat("Read UShort: %d (Expected: 65530)", buffer.ReadUShort());
|
|
PrintFormat("Read Int: %d (Expected: 123456789)", buffer.ReadInt());
|
|
PrintFormat("Read UInt: %u (Expected: 4294967295)", buffer.ReadUInt());
|
|
PrintFormat("Read Long: %lli (Expected: 9223372036854775807)", buffer.ReadLong());
|
|
PrintFormat("Read ULong: %llu (Expected: 18446744073709551615)", buffer.ReadULong());
|
|
PrintFormat("Read Float: %.5f (Expected: 3.14159)", buffer.ReadFloat());
|
|
PrintFormat("Read Double: %.15f (Expected: 2.718281828459045)", buffer.ReadDouble());
|
|
PrintFormat("Read Datetime: %s (Expected: %s)", TimeToString(buffer.ReadDatetime()), TimeToString(now));
|
|
|
|
if (buffer.GetLastError() == BIT_BUFFER_NO_ERROR)
|
|
{
|
|
Print("Basic operations test: PASSED");
|
|
}
|
|
else
|
|
{
|
|
PrintFormat("Basic operations test: FAILED! Error: %s", buffer.GetLastErrorString());
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Test Function: Mixed Read/Write Operations |
|
|
//+------------------------------------------------------------------+
|
|
void TestMixedOperations()
|
|
{
|
|
Print("\n--- Test: Mixed Read/Write Operations ---");
|
|
CBitBuffer buffer;
|
|
|
|
// Write some data
|
|
buffer.WriteInt(100);
|
|
buffer.WriteShort(200);
|
|
PrintFormat("After initial writes: Total bits written: %d", buffer.GetTotalWrittenBits());
|
|
buffer.PrintHex();
|
|
|
|
// Read some data (this should trigger a flush)
|
|
int val1 = buffer.ReadInt();
|
|
PrintFormat("Read Int: %d (Expected: 100)", val1);
|
|
PrintFormat("Read position after first read: %d", buffer.GetReadPosition());
|
|
|
|
// Write more data (this should invalidate read cache)
|
|
buffer.WriteLong(987654321L);
|
|
buffer.WriteBool(true);
|
|
PrintFormat("After mixed writes: Total bits written: %d", buffer.GetTotalWrittenBits());
|
|
buffer.PrintHex();
|
|
|
|
// Read remaining data
|
|
short val2 = buffer.ReadShort();
|
|
long val3 = buffer.ReadLong();
|
|
bool val4 = buffer.ReadBool();
|
|
|
|
PrintFormat("Read Short: %d (Expected: 200)", val2);
|
|
PrintFormat("Read Long: %lli (Expected: 987654321)", val3);
|
|
PrintFormat("Read Bool: %s (Expected: true)", val4 ? "true" : "false");
|
|
|
|
if (buffer.GetLastError() == BIT_BUFFER_NO_ERROR)
|
|
{
|
|
Print("Mixed operations test: PASSED");
|
|
}
|
|
else
|
|
{
|
|
PrintFormat("Mixed operations test: FAILED! Error: %s", buffer.GetLastErrorString());
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Test Function: Variable-Length Quantity (VLQ) Encoding Test |
|
|
//+------------------------------------------------------------------+
|
|
void TestVLQEncoding()
|
|
{
|
|
Print("\n--- Test: Variable-Length Quantity (VLQ) Encoding ---");
|
|
CBitBuffer buffer;
|
|
|
|
// Test values: small, medium, large, zero, negative
|
|
int intValues[] = {0, 1, -1, 127, -128, 12345, -67890, 2147483647, -2147483648};
|
|
uint uintValues[] = {0, 1, 127, 12345, 4294967295U};
|
|
long longValues[] = {0L, 1L, -1L, 1234567890123L, -9876543210987L, 9223372036854775807L, -9223372036854775808L};
|
|
ulong ulongValues[] = {0ULL, 1ULL, 1234567890123ULL, 18446744073709551615ULL};
|
|
|
|
Print("Writing VLQ integers...");
|
|
for (int i = 0; i < ArraySize(intValues); i++)
|
|
{
|
|
buffer.WriteVarInt(intValues[i]);
|
|
}
|
|
for (int i = 0; i < ArraySize(uintValues); i++)
|
|
{
|
|
buffer.WriteVarUInt(uintValues[i]);
|
|
}
|
|
for (int i = 0; i < ArraySize(longValues); i++)
|
|
{
|
|
buffer.WriteVarLong(longValues[i]);
|
|
}
|
|
for (int i = 0; i < ArraySize(ulongValues); i++)
|
|
{
|
|
buffer.WriteVarULong(ulongValues[i]);
|
|
}
|
|
|
|
PrintFormat("Total bits written (VLQ): %d", buffer.GetTotalWrittenBits());
|
|
buffer.PrintHex();
|
|
|
|
buffer.ResetReadPosition();
|
|
Print("Reading VLQ integers...");
|
|
|
|
bool vlqPassed = true;
|
|
for (int i = 0; i < ArraySize(intValues); i++)
|
|
{
|
|
int readVal = buffer.ReadVarInt();
|
|
if (readVal != intValues[i])
|
|
{
|
|
PrintFormat("VLQ Int Mismatch: Expected %d, Got %d", intValues[i], readVal);
|
|
vlqPassed = false;
|
|
}
|
|
}
|
|
for (int i = 0; i < ArraySize(uintValues); i++)
|
|
{
|
|
uint readVal = buffer.ReadVarUInt();
|
|
if (readVal != uintValues[i])
|
|
{
|
|
PrintFormat("VLQ UInt Mismatch: Expected %u, Got %u", uintValues[i], readVal);
|
|
vlqPassed = false;
|
|
}
|
|
}
|
|
for (int i = 0; i < ArraySize(longValues); i++)
|
|
{
|
|
long readVal = buffer.ReadVarLong();
|
|
if (readVal != longValues[i])
|
|
{
|
|
PrintFormat("VLQ Long Mismatch: Expected %Ld, Got %Ld", longValues[i], readVal);
|
|
vlqPassed = false;
|
|
}
|
|
}
|
|
for (int i = 0; i < ArraySize(ulongValues); i++)
|
|
{
|
|
ulong readVal = buffer.ReadVarULong();
|
|
if (readVal != ulongValues[i])
|
|
{
|
|
PrintFormat("VLQ ULong Mismatch: Expected %lu, Got %lu", ulongValues[i], readVal);
|
|
vlqPassed = false;
|
|
}
|
|
}
|
|
|
|
if (vlqPassed && buffer.GetLastError() == BIT_BUFFER_NO_ERROR)
|
|
{
|
|
Print("VLQ encoding test: PASSED");
|
|
}
|
|
else
|
|
{
|
|
PrintFormat("VLQ encoding test: FAILED! Error: %s", buffer.GetLastErrorString());
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Test Function: String and Struct Serialization |
|
|
//+------------------------------------------------------------------+
|
|
void TestStringAndStruct()
|
|
{
|
|
Print("\n--- Test: String and Struct Serialization ---");
|
|
CBitBuffer buffer;
|
|
|
|
string testString1 = "Hello, MQL5 BitBuffer!";
|
|
string testString2 = "Another string with ümlauts and special chars: éàç";
|
|
string testString3 = ""; // Empty string
|
|
string testString4 = NULL; // Null string
|
|
|
|
MyTestStruct myStruct1;
|
|
myStruct1.id = 123;
|
|
myStruct1.price = 123.456;
|
|
myStruct1.volume = 14432;
|
|
myStruct1.isActive = true;
|
|
|
|
MyTestStruct myStruct2;
|
|
myStruct2.id = 789;
|
|
myStruct2.price = 987.654;
|
|
myStruct2.volume = 747;
|
|
myStruct2.isActive = false;
|
|
|
|
Print("Writing strings and structs...");
|
|
buffer.WriteString(testString1);
|
|
buffer.WriteString(testString2);
|
|
buffer.WriteString(testString3);
|
|
buffer.WriteString(testString4);
|
|
buffer.WriteStruct(myStruct1);
|
|
buffer.WriteStruct(myStruct2);
|
|
|
|
PrintFormat("Total bits written (Strings/Structs): %d", buffer.GetTotalWrittenBits());
|
|
buffer.PrintHex();
|
|
|
|
buffer.ResetReadPosition();
|
|
Print("Reading strings and structs...");
|
|
|
|
string readString1 = buffer.ReadString();
|
|
string readString2 = buffer.ReadString();
|
|
string readString3 = buffer.ReadString();
|
|
string readString4 = buffer.ReadString();
|
|
MyTestStruct readStruct1 = buffer.ReadStruct<MyTestStruct>();
|
|
MyTestStruct readStruct2 = buffer.ReadStruct<MyTestStruct>();
|
|
|
|
bool structStringPassed = true;
|
|
if (readString1 != testString1) { PrintFormat("String1 Mismatch: '%s' != '%s'", readString1, testString1); structStringPassed = false; }
|
|
if (readString2 != testString2) { PrintFormat("String2 Mismatch: '%s' != '%s'", readString2, testString2); structStringPassed = false; }
|
|
if (readString3 != testString3) { PrintFormat("String3 Mismatch: '%s' != '%s'", readString3, testString3); structStringPassed = false; }
|
|
if (readString4 != testString4) { PrintFormat("String4 Mismatch: '%s' != '%s'", readString4, testString4); structStringPassed = false; }
|
|
|
|
if (readStruct1.id != myStruct1.id || readStruct1.price != myStruct1.price ||
|
|
readStruct1.volume != myStruct1.volume || readStruct1.isActive != myStruct1.isActive)
|
|
{
|
|
Print("Struct1 Mismatch!");
|
|
PrintFormat(" Expected: ID=%d, Price=%.3f, volume='%lli', Active=%s", myStruct1.id, myStruct1.price, myStruct1.volume, myStruct1.isActive ? "true" : "false");
|
|
PrintFormat(" Got: ID=%d, Price=%.3f, volume='%lli', Active=%s", readStruct1.id, readStruct1.price, readStruct1.volume, readStruct1.isActive ? "true" : "false");
|
|
structStringPassed = false;
|
|
}
|
|
|
|
if (readStruct2.id != myStruct2.id || readStruct2.price != myStruct2.price ||
|
|
readStruct2.volume != myStruct2.volume || readStruct2.isActive != myStruct2.isActive)
|
|
{
|
|
Print("Struct2 Mismatch!");
|
|
PrintFormat(" Expected: ID=%d, Price=%.3f, volume='%lli', Active=%s", myStruct2.id, myStruct2.price, myStruct2.volume, myStruct2.isActive ? "true" : "false");
|
|
PrintFormat(" Got: ID=%d, Price=%.3f, volume='%lli', Active=%s", readStruct2.id, readStruct2.price, readStruct2.volume, readStruct2.isActive ? "true" : "false");
|
|
structStringPassed = false;
|
|
}
|
|
|
|
if (structStringPassed && buffer.GetLastError() == BIT_BUFFER_NO_ERROR)
|
|
{
|
|
Print("String and Struct serialization test: PASSED");
|
|
}
|
|
else
|
|
{
|
|
PrintFormat("String and Struct serialization test: FAILED! Error: %s", buffer.GetLastErrorString());
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Test Function: File Save and Load Operations |
|
|
//+------------------------------------------------------------------+
|
|
void TestFileOperations()
|
|
{
|
|
Print("\n--- Test: File Save and Load Operations ---");
|
|
CBitBuffer bufferWrite;
|
|
CBitBuffer bufferRead;
|
|
string filename = "bitbuffer_test_data.bin"; // File in MQL5/Files/
|
|
|
|
// Write some data to bufferWrite
|
|
bufferWrite.WriteInt(12345);
|
|
bufferWrite.WriteString("Data for file storage.");
|
|
bufferWrite.WriteDouble(987.654321);
|
|
bufferWrite.WriteBool(true);
|
|
bufferWrite.WriteVarInt(-500);
|
|
|
|
PrintFormat("Buffer to save (bits): %d", bufferWrite.GetTotalWrittenBits());
|
|
bufferWrite.PrintHex();
|
|
|
|
// Save to file
|
|
if (bufferWrite.Save(filename))
|
|
{
|
|
PrintFormat("Buffer successfully saved to '%s'", filename);
|
|
}
|
|
else
|
|
{
|
|
PrintFormat("Failed to save buffer to file! Error: %s", bufferWrite.GetLastErrorString());
|
|
return;
|
|
}
|
|
|
|
// Load from file into bufferRead
|
|
if (bufferRead.Load(filename))
|
|
{
|
|
PrintFormat("Buffer successfully loaded from '%s'", filename);
|
|
PrintFormat("Loaded buffer (bits): %d", bufferRead.GetTotalWrittenBits());
|
|
bufferRead.PrintHex();
|
|
|
|
// Verify loaded data
|
|
int readInt = bufferRead.ReadInt();
|
|
string readString = bufferRead.ReadString();
|
|
double readDouble = bufferRead.ReadDouble();
|
|
bool readBool = bufferRead.ReadBool();
|
|
int readVarInt = bufferRead.ReadVarInt();
|
|
|
|
bool fileTestPassed = true;
|
|
if (readInt != 12345) { PrintFormat("File Load Mismatch: Int. Expected 12345, Got %d", readInt); fileTestPassed = false; }
|
|
if (readString != "Data for file storage.") { PrintFormat("File Load Mismatch: String. Expected 'Data for file storage.', Got '%s'", readString); fileTestPassed = false; }
|
|
if (readDouble != 987.654321) { PrintFormat("File Load Mismatch: Double. Expected 987.654321, Got %.6f", readDouble); fileTestPassed = false; }
|
|
if (readBool != true) { PrintFormat("File Load Mismatch: Bool. Expected true, Got %s", readBool ? "true" : "false"); fileTestPassed = false; }
|
|
if (readVarInt != -500) { PrintFormat("File Load Mismatch: VarInt. Expected -500, Got %d", readVarInt); fileTestPassed = false; }
|
|
|
|
if (fileTestPassed && bufferRead.GetLastError() == BIT_BUFFER_NO_ERROR)
|
|
{
|
|
Print("File operations test: PASSED");
|
|
}
|
|
else
|
|
{
|
|
PrintFormat("File operations test: FAILED! Error: %s", bufferRead.GetLastErrorString());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PrintFormat("Failed to load buffer from file! Error: %s", bufferRead.GetLastErrorString());
|
|
return;
|
|
}
|
|
|
|
// Clean up: Delete the test file
|
|
if (FileDelete(filename))
|
|
{
|
|
PrintFormat("Test file '%s' deleted successfully.", filename);
|
|
}
|
|
else
|
|
{
|
|
PrintFormat("Failed to delete test file '%s'. MQL5 Error: %d", filename, ::GetLastError());
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Test Function: Error Handling |
|
|
//+------------------------------------------------------------------+
|
|
void TestErrorHandling()
|
|
{
|
|
Print("\n--- Test: Error Handling ---");
|
|
CBitBuffer buffer;
|
|
bool errorHandled = true;
|
|
|
|
// Test 1: Read out of bounds
|
|
Print("Attempting to read out of bounds...");
|
|
buffer.Clear();
|
|
buffer.WriteInt(10); // Write 32 bits
|
|
buffer.ResetReadPosition();
|
|
buffer.ReadLong(); // Try to read 64 bits
|
|
if (buffer.GetLastError() == BIT_BUFFER_READ_OUT_OF_BOUNDS)
|
|
{
|
|
PrintFormat(" Caught expected error: %s", buffer.GetLastErrorString());
|
|
}
|
|
else
|
|
{
|
|
PrintFormat(" Failed to catch expected READ_OUT_OF_BOUNDS error. Got: %s", buffer.GetLastErrorString());
|
|
errorHandled = false;
|
|
}
|
|
buffer.ClearLastError();
|
|
|
|
// Test 2: Invalid bit count for WriteBits
|
|
Print("Attempting to write with invalid bit count...");
|
|
buffer.WriteBits(1, 0); // 0 bits
|
|
if (buffer.GetLastError() == BIT_BUFFER_INVALID_BIT_COUNT)
|
|
{
|
|
PrintFormat(" Caught expected error: %s", buffer.GetLastErrorString());
|
|
}
|
|
else
|
|
{
|
|
PrintFormat(" Failed to catch expected INVALID_BIT_COUNT error. Got: %s", buffer.GetLastErrorString());
|
|
errorHandled = false;
|
|
}
|
|
buffer.ClearLastError();
|
|
|
|
// Test 3: Set read position out of bounds
|
|
Print("Attempting to set read position out of bounds...");
|
|
buffer.Clear();
|
|
buffer.WriteInt(10);
|
|
buffer.SetReadPosition(100); // Beyond total written bits
|
|
if (buffer.GetLastError() == BIT_BUFFER_INVALID_READ_POSITION)
|
|
{
|
|
PrintFormat(" Caught expected error: %s", buffer.GetLastErrorString());
|
|
}
|
|
else
|
|
{
|
|
PrintFormat(" Failed to catch expected INVALID_READ_POSITION error. Got: %s", buffer.GetLastErrorString());
|
|
errorHandled = false;
|
|
}
|
|
buffer.ClearLastError();
|
|
|
|
// Test 4: String too large
|
|
Print("Attempting to write string too large for USHORT_MAX...");
|
|
string veryLongString = "";
|
|
for (int i = 0; i < 70000; i++) // Create a string longer than USHORT_MAX (65535)
|
|
{
|
|
veryLongString += "a";
|
|
}
|
|
buffer.WriteString(veryLongString);
|
|
if (buffer.GetLastError() == BIT_BUFFER_DATA_TOO_LARGE)
|
|
{
|
|
PrintFormat(" Caught expected error: %s", buffer.GetLastErrorString());
|
|
}
|
|
else
|
|
{
|
|
PrintFormat(" Failed to catch expected DATA_TOO_LARGE error. Got: %s", buffer.GetLastErrorString());
|
|
errorHandled = false;
|
|
}
|
|
buffer.ClearLastError();
|
|
|
|
if (errorHandled)
|
|
{
|
|
Print("Error handling test: PASSED");
|
|
}
|
|
else
|
|
{
|
|
Print("Error handling test: FAILED!");
|
|
}
|
|
}
|