You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
995 lines
18 KiB
995 lines
18 KiB
//
|
|
// Test the quick return timeouts
|
|
//
|
|
// Assume that we are using a loopback connector.
|
|
//
|
|
// Assume that it isn't running on a stressed machine.
|
|
//
|
|
|
|
#include "windows.h"
|
|
#include "stdio.h"
|
|
|
|
#define FAILURE printf("FAIL: %d\n",__LINE__);exit(1)
|
|
|
|
int __cdecl main(int argc, char *argv[]) {
|
|
CHAR *myPort = "COM1";
|
|
DCB myDcb;
|
|
DWORD junk;
|
|
COMMTIMEOUTS myTimeOuts;
|
|
DWORD numberActuallyRead;
|
|
DWORD numberActuallyWritten;
|
|
UCHAR readBuff[1000];
|
|
HANDLE comHandle;
|
|
DWORD startingTicks;
|
|
OVERLAPPED readOl;
|
|
OVERLAPPED writeOl;
|
|
UCHAR writeBuff[5] = {0,1,2,3,4};
|
|
|
|
if (argc > 1) {
|
|
|
|
myPort = argv[1];
|
|
|
|
}
|
|
|
|
if ((comHandle = CreateFile(
|
|
myPort,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
|
|
NULL
|
|
)) == ((HANDLE)-1)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (!(readOl.hEvent = CreateEvent(
|
|
NULL,
|
|
TRUE,
|
|
FALSE,
|
|
NULL
|
|
))) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (!(writeOl.hEvent = CreateEvent(
|
|
NULL,
|
|
TRUE,
|
|
FALSE,
|
|
NULL
|
|
))) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (!GetCommState(
|
|
comHandle,
|
|
&myDcb
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
myDcb.BaudRate = 19200;
|
|
myDcb.ByteSize = 8;
|
|
myDcb.StopBits = ONESTOPBIT;
|
|
myDcb.Parity = NOPARITY;
|
|
myDcb.fOutxCtsFlow = FALSE;
|
|
myDcb.fOutxDsrFlow = FALSE;
|
|
myDcb.fDsrSensitivity = FALSE;
|
|
myDcb.fOutX = FALSE;
|
|
myDcb.fInX = FALSE;
|
|
myDcb.fRtsControl = RTS_CONTROL_ENABLE;
|
|
myDcb.fDtrControl = DTR_CONTROL_ENABLE;
|
|
if (!SetCommState(
|
|
comHandle,
|
|
&myDcb
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Test to make sure that all maxdword on read is illegal.
|
|
//
|
|
|
|
myTimeOuts.ReadIntervalTimeout = MAXDWORD;
|
|
myTimeOuts.ReadTotalTimeoutMultiplier = MAXDWORD;
|
|
myTimeOuts.ReadTotalTimeoutConstant = MAXDWORD;
|
|
myTimeOuts.WriteTotalTimeoutMultiplier = MAXDWORD;
|
|
myTimeOuts.WriteTotalTimeoutConstant = MAXDWORD;
|
|
|
|
if (SetCommTimeouts(
|
|
comHandle,
|
|
&myTimeOuts
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Test that MAXDWORD,0,0 will return immediately with whatever
|
|
// is there
|
|
//
|
|
|
|
myTimeOuts.ReadIntervalTimeout = MAXDWORD;
|
|
myTimeOuts.ReadTotalTimeoutMultiplier = 0;
|
|
myTimeOuts.ReadTotalTimeoutConstant = 0;
|
|
myTimeOuts.WriteTotalTimeoutMultiplier = MAXDWORD;
|
|
myTimeOuts.WriteTotalTimeoutConstant = MAXDWORD;
|
|
|
|
if (!SetCommTimeouts(
|
|
comHandle,
|
|
&myTimeOuts
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
startingTicks = GetTickCount();
|
|
if (!ReadFile(
|
|
comHandle,
|
|
&readBuff[0],
|
|
1000,
|
|
&numberActuallyRead,
|
|
&readOl
|
|
)) {
|
|
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (!GetOverlappedResult(
|
|
comHandle,
|
|
&readOl,
|
|
&numberActuallyRead,
|
|
TRUE
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// We certainly should have gotten back in less than a
|
|
// a half a second.
|
|
//
|
|
|
|
if ((GetTickCount() - startingTicks) > 500) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (numberActuallyRead) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Write out five bytes and make sure that is what we get back
|
|
//
|
|
|
|
if (!WriteFile(
|
|
comHandle,
|
|
&writeBuff[0],
|
|
5,
|
|
&numberActuallyWritten,
|
|
&writeOl
|
|
)) {
|
|
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (!GetOverlappedResult(
|
|
comHandle,
|
|
&writeOl,
|
|
&numberActuallyWritten,
|
|
TRUE
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (numberActuallyWritten != 5) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Give some time for the chars to get there.
|
|
//
|
|
|
|
Sleep (100);
|
|
|
|
startingTicks = GetTickCount();
|
|
if (!ReadFile(
|
|
comHandle,
|
|
&readBuff[0],
|
|
1000,
|
|
&numberActuallyRead,
|
|
&readOl
|
|
)) {
|
|
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (!GetOverlappedResult(
|
|
comHandle,
|
|
&readOl,
|
|
&numberActuallyRead,
|
|
TRUE
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// We certainly should have gotten back in less than a
|
|
// a half a second.
|
|
//
|
|
|
|
if ((GetTickCount() - startingTicks) > 500) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (numberActuallyRead != 5) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Test that the os2 wait for something works.
|
|
//
|
|
// First test that if there is something in the buffer
|
|
// it returns right away.
|
|
//
|
|
// Then test that if there isn't something, then if we
|
|
// put in the amount expected before the timeout expires
|
|
// that it returns.
|
|
//
|
|
// The test that if there isn't something and nothing
|
|
// happens before the timeout it returns after the timeout
|
|
// with nothing.
|
|
//
|
|
myTimeOuts.ReadIntervalTimeout = MAXDWORD;
|
|
myTimeOuts.ReadTotalTimeoutMultiplier = 0;
|
|
myTimeOuts.ReadTotalTimeoutConstant = 5000;
|
|
myTimeOuts.WriteTotalTimeoutMultiplier = MAXDWORD;
|
|
myTimeOuts.WriteTotalTimeoutConstant = MAXDWORD;
|
|
|
|
if (!SetCommTimeouts(
|
|
comHandle,
|
|
&myTimeOuts
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (!WriteFile(
|
|
comHandle,
|
|
&writeBuff[0],
|
|
5,
|
|
&numberActuallyWritten,
|
|
&writeOl
|
|
)) {
|
|
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (!GetOverlappedResult(
|
|
comHandle,
|
|
&writeOl,
|
|
&numberActuallyWritten,
|
|
TRUE
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (numberActuallyWritten != 5) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Give some time for the chars to get there.
|
|
//
|
|
|
|
Sleep (100);
|
|
startingTicks = GetTickCount();
|
|
if (!ReadFile(
|
|
comHandle,
|
|
&readBuff[0],
|
|
1000,
|
|
&numberActuallyRead,
|
|
&readOl
|
|
)) {
|
|
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Give it at most a 1/2 second to finish for
|
|
// the irp to complete immediately.
|
|
//
|
|
|
|
Sleep(500);
|
|
if (!GetOverlappedResult(
|
|
comHandle,
|
|
&readOl,
|
|
&numberActuallyRead,
|
|
FALSE
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ((GetTickCount() - startingTicks) > 1000) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (numberActuallyRead != 5) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Do the second os2 test
|
|
//
|
|
|
|
if (ReadFile(
|
|
comHandle,
|
|
&readBuff[0],
|
|
1000,
|
|
&numberActuallyRead,
|
|
&readOl
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Give it a second for the the read to complete
|
|
//
|
|
//
|
|
|
|
Sleep(1000);
|
|
|
|
//
|
|
// Call the GetOverlapped and make sure that it returns
|
|
// ERROR_IO_INCOMPLETE.
|
|
//
|
|
|
|
if (GetOverlappedResult(
|
|
comHandle,
|
|
&readOl,
|
|
&numberActuallyRead,
|
|
FALSE
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (GetLastError() != ERROR_IO_INCOMPLETE) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Do the write file and make sure that there is enough
|
|
// time for the chars to make it.
|
|
//
|
|
|
|
if (!WriteFile(
|
|
comHandle,
|
|
&writeBuff[0],
|
|
5,
|
|
&numberActuallyWritten,
|
|
&writeOl
|
|
)) {
|
|
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (!GetOverlappedResult(
|
|
comHandle,
|
|
&writeOl,
|
|
&numberActuallyWritten,
|
|
TRUE
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (numberActuallyWritten != 5) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Give some time for the chars to get there.
|
|
//
|
|
|
|
Sleep(100);
|
|
|
|
//
|
|
// Wait for no more than 6 seconds for the IO to complete
|
|
//
|
|
|
|
if (WaitForSingleObject(
|
|
readOl.hEvent,
|
|
6000
|
|
) != WAIT_OBJECT_0) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Make sure we got everything we wrote
|
|
//
|
|
|
|
if (!GetOverlappedResult(
|
|
comHandle,
|
|
&readOl,
|
|
&numberActuallyRead,
|
|
FALSE
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (numberActuallyRead != 5) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Do the third os2 wait for something test.
|
|
//
|
|
|
|
startingTicks = GetTickCount();
|
|
if (ReadFile(
|
|
comHandle,
|
|
&readBuff[0],
|
|
1000,
|
|
&numberActuallyRead,
|
|
&readOl
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Give it a second for the the read to complete
|
|
//
|
|
//
|
|
|
|
Sleep(1000);
|
|
|
|
//
|
|
// Call the GetOverlapped and make sure that it returns
|
|
// ERROR_IO_INCOMPLETE.
|
|
//
|
|
|
|
if (GetOverlappedResult(
|
|
comHandle,
|
|
&readOl,
|
|
&numberActuallyRead,
|
|
FALSE
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (GetLastError() != ERROR_IO_INCOMPLETE) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Wait for no more than 10 seconds for the IO to complete
|
|
//
|
|
|
|
if (WaitForSingleObject(
|
|
readOl.hEvent,
|
|
10000
|
|
) != WAIT_OBJECT_0) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// It shouldn't be more than 6 seconds for the Io to be done.
|
|
//
|
|
|
|
if ((GetTickCount() - startingTicks) > 6000) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Make sure we got everything we wrote, which in this case is zero.
|
|
//
|
|
|
|
if (!GetOverlappedResult(
|
|
comHandle,
|
|
&readOl,
|
|
&numberActuallyRead,
|
|
FALSE
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (numberActuallyRead) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Test the graphics mode quick return.
|
|
//
|
|
// First test that if there is something in the buffer
|
|
// it returns right away.
|
|
//
|
|
// Then test that if there isn't something, then if we
|
|
// put in 2 characters it returns right away with one
|
|
// and then the other read will return right away with
|
|
// 1.
|
|
//
|
|
// Then test that if there isn't something and nothing
|
|
// happens before the timeout it returns after the timeout
|
|
// with nothing.
|
|
//
|
|
myTimeOuts.ReadIntervalTimeout = MAXDWORD;
|
|
myTimeOuts.ReadTotalTimeoutMultiplier = MAXDWORD;
|
|
myTimeOuts.ReadTotalTimeoutConstant = 5000;
|
|
myTimeOuts.WriteTotalTimeoutMultiplier = MAXDWORD;
|
|
myTimeOuts.WriteTotalTimeoutConstant = MAXDWORD;
|
|
|
|
if (!SetCommTimeouts(
|
|
comHandle,
|
|
&myTimeOuts
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (!WriteFile(
|
|
comHandle,
|
|
&writeBuff[0],
|
|
5,
|
|
&numberActuallyWritten,
|
|
&writeOl
|
|
)) {
|
|
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (!GetOverlappedResult(
|
|
comHandle,
|
|
&writeOl,
|
|
&numberActuallyWritten,
|
|
TRUE
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (numberActuallyWritten != 5) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Give some time for the chars to get there.
|
|
//
|
|
|
|
Sleep (100);
|
|
startingTicks = GetTickCount();
|
|
if (!ReadFile(
|
|
comHandle,
|
|
&readBuff[0],
|
|
1000,
|
|
&numberActuallyRead,
|
|
&readOl
|
|
)) {
|
|
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Give it at most a 1/2 second to finish for
|
|
// the irp to complete immediately.
|
|
//
|
|
|
|
Sleep(500);
|
|
if (!GetOverlappedResult(
|
|
comHandle,
|
|
&readOl,
|
|
&numberActuallyRead,
|
|
FALSE
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ((GetTickCount() - startingTicks) > 1000) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (numberActuallyRead != 5) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Do the second graphics wait test.
|
|
//
|
|
if (ReadFile(
|
|
comHandle,
|
|
&readBuff[0],
|
|
1000,
|
|
&numberActuallyRead,
|
|
&readOl
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Give it a second for the the read to complete
|
|
//
|
|
//
|
|
|
|
Sleep(1000);
|
|
|
|
//
|
|
// Call the GetOverlapped and make sure that it returns
|
|
// ERROR_IO_INCOMPLETE.
|
|
//
|
|
|
|
if (GetOverlappedResult(
|
|
comHandle,
|
|
&readOl,
|
|
&numberActuallyRead,
|
|
FALSE
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (GetLastError() != ERROR_IO_INCOMPLETE) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Do the write file and make sure that there is enough
|
|
// time for the chars to make it.
|
|
//
|
|
|
|
if (!WriteFile(
|
|
comHandle,
|
|
&writeBuff[0],
|
|
5,
|
|
&numberActuallyWritten,
|
|
&writeOl
|
|
)) {
|
|
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (!GetOverlappedResult(
|
|
comHandle,
|
|
&writeOl,
|
|
&numberActuallyWritten,
|
|
TRUE
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (numberActuallyWritten != 5) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Give some time for the chars to get there.
|
|
//
|
|
|
|
Sleep(100);
|
|
|
|
//
|
|
// Wait for no more than 1 second for the IO to complete
|
|
//
|
|
|
|
if (WaitForSingleObject(
|
|
readOl.hEvent,
|
|
1000
|
|
) != WAIT_OBJECT_0) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Make sure we got everything we wrote
|
|
//
|
|
|
|
if (!GetOverlappedResult(
|
|
comHandle,
|
|
&readOl,
|
|
&numberActuallyRead,
|
|
FALSE
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (numberActuallyRead != 1) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
startingTicks = GetTickCount();
|
|
if (!ReadFile(
|
|
comHandle,
|
|
&readBuff[0],
|
|
1000,
|
|
&numberActuallyRead,
|
|
&readOl
|
|
)) {
|
|
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Give it at most a 1/2 second to finish for
|
|
// the irp to complete immediately.
|
|
//
|
|
|
|
Sleep(500);
|
|
if (!GetOverlappedResult(
|
|
comHandle,
|
|
&readOl,
|
|
&numberActuallyRead,
|
|
FALSE
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ((GetTickCount() - startingTicks) > 1000) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (numberActuallyRead != 4) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Do the third graphics wait test.
|
|
//
|
|
|
|
startingTicks = GetTickCount();
|
|
if (ReadFile(
|
|
comHandle,
|
|
&readBuff[0],
|
|
1000,
|
|
&numberActuallyRead,
|
|
&readOl
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Give it a second for the the read to complete
|
|
//
|
|
//
|
|
|
|
Sleep(1000);
|
|
|
|
//
|
|
// Call the GetOverlapped and make sure that it returns
|
|
// ERROR_IO_INCOMPLETE.
|
|
//
|
|
|
|
if (GetOverlappedResult(
|
|
comHandle,
|
|
&readOl,
|
|
&numberActuallyRead,
|
|
FALSE
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (GetLastError() != ERROR_IO_INCOMPLETE) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Wait for no more than 10 seconds for the IO to complete
|
|
//
|
|
|
|
if (WaitForSingleObject(
|
|
readOl.hEvent,
|
|
10000
|
|
) != WAIT_OBJECT_0) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// It shouldn't be more than 6 seconds for the Io to be done.
|
|
//
|
|
|
|
if ((GetTickCount() - startingTicks) > 6000) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
//
|
|
// Make sure we got everything we wrote, which in this case is zero.
|
|
//
|
|
|
|
if (!GetOverlappedResult(
|
|
comHandle,
|
|
&readOl,
|
|
&numberActuallyRead,
|
|
FALSE
|
|
)) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
if (numberActuallyRead) {
|
|
|
|
FAILURE;
|
|
|
|
}
|
|
|
|
|
|
return 1;
|
|
|
|
}
|