/*++

Copyright (c) 1996  Microsoft Corporation

Module Name:

	util.c	- Generic Debugger Extension Utilities

Abstract:

	Taken from AliD's ndiskd(ndiskd.c).

Revision History:

	Who         When        What
	--------    --------    ----------------------------------------------
	josephj     03-30-98    Created (taken fron AliD's ndiskd (ndiskd.c).

Notes:

--*/
#include "common.h"


WINDBG_EXTENSION_APIS ExtensionApis;
EXT_API_VERSION ApiVersion = { 5, 0, EXT_API_VERSION_NUMBER, 0 };

#define    ERRPRT     dprintf

#define    NL      1
#define    NONL    0

USHORT SavedMajorVersion;
USHORT SavedMinorVersion;
BOOL   ChkTarget;            // is debuggee a CHK build?




/*
 * Print out an optional message, an ANSI_STRING, and maybe a new-line
 */
BOOL
PrintStringA( IN LPSTR msg OPTIONAL, IN PANSI_STRING pStr, IN BOOL nl )
{
    PCHAR    StringData;
    ULONG    BytesRead;

    if( msg )
        dprintf( msg );

    if( pStr->Length == 0 ) {
        if( nl )
            dprintf( "\n" );
        return TRUE;
    }

    StringData = (PCHAR)LocalAlloc( LPTR, pStr->Length + 1 );

    if( StringData == NULL ) {
        ERRPRT( "Out of memory!\n" );
        return FALSE;
    }

    ReadMemory((ULONG_PTR) pStr->Buffer,
               StringData,
               pStr->Length,
               &BytesRead );

    if ( BytesRead ) {
        StringData[ pStr->Length ] = '\0';
        dprintf("%s%s", StringData, nl ? "\n" : "" );
    }

    LocalFree((HLOCAL)StringData);

    return BytesRead;
}

/*
 * Get 'size' bytes from the debuggee program at 'dwAddress' and place it
 * in our address space at 'ptr'.  Use 'type' in an error printout if necessary
 */
BOOL
GetData( IN LPVOID ptr, IN UINT_PTR dwAddress, IN ULONG size, IN PCSTR type )
{
    BOOL b;
    ULONG BytesRead;
    ULONG count = size;

    while( size > 0 ) {

    if (count >= 3000)
        count = 3000;

        b = ReadMemory(dwAddress, ptr, count, &BytesRead );

        if (!b || BytesRead != count ) {
            ERRPRT( "Unable to read %u bytes at %X, for %s\n", size, dwAddress, type );
            return FALSE;
        }

        dwAddress += count;
        size -= count;
        ptr = (LPVOID)((ULONG_PTR)ptr + count);
    }

    return TRUE;
}


/*
 * Print out a single HEX character
 */
VOID
PrintHexChar( IN UCHAR c )
{
    dprintf( "%c%c", "0123456789abcdef"[ (c>>4)&0xf ], "0123456789abcdef"[ c&0xf ] );
}

/*
 * Print out 'buf' of 'cbuf' bytes as HEX characters
 */
VOID
PrintHexBuf( IN PUCHAR buf, IN ULONG cbuf )
{
    while( cbuf-- ) {
        PrintHexChar( *buf++ );
        dprintf( " " );
    }
}


/*
 * Fetch the null terminated UNICODE string at dwAddress into buf
 */
BOOL
GetString( IN UINT_PTR dwAddress, IN LPWSTR buf, IN ULONG MaxChars )
{
    do {
        if( !GetData( buf, dwAddress, sizeof( *buf ), "Character" ) )
            return FALSE;

        dwAddress += sizeof( *buf );

    } while( --MaxChars && *buf++ != '\0' );

    return TRUE;
}

char *mystrtok ( char *string, char * control )
{
    static UCHAR *str;
    char *p, *s;

    if( string )
        str = string;

    if( str == NULL || *str == '\0' )
        return NULL;

    //
    // Skip leading delimiters...
    //
    for( ; *str; str++ ) {
        for( s=control; *s; s++ ) {
            if( *str == *s )
                break;
        }
        if( *s == '\0' )
            break;
    }

    //
    // Was it was all delimiters?
    //
    if( *str == '\0' ) {
        str = NULL;
        return NULL;
    }

    //
    // We've got a string, terminate it at first delimeter
    //
    for( p = str+1; *p; p++ ) {
        for( s = control; *s; s++ ) {
            if( *p == *s ) {
                s = str;
                *p = '\0';
                str = p+1;
                return s;
            }
        }
    }

    //
    // We've got a string that ends with the NULL
    //
    s = str;
    str = NULL;
    return s;
}

        
VOID
WinDbgExtensionDllInit(
    PWINDBG_EXTENSION_APIS lpExtensionApis,
    USHORT MajorVersion,
    USHORT MinorVersion
    )
{
    ExtensionApis = *lpExtensionApis;
    g_pfnDbgPrintf = dprintf;

    SavedMajorVersion = MajorVersion;
    SavedMinorVersion = MinorVersion;
    ChkTarget = SavedMajorVersion == 0x0c ? TRUE : FALSE;
}

DECLARE_API( version )
{
#if    DBG
    PCSTR kind = "Checked";
#else
    PCSTR kind = "Free";
#endif

    dprintf(
        "%s IPATM Extension dll for Build %d debugging %s kernel for Build %d\n",
        kind,
        VER_PRODUCTBUILD,
        SavedMajorVersion == 0x0c ? "Checked" : "Free",
        SavedMinorVersion
    );
}

VOID
CheckVersion(
    VOID
    )
{

	//
	// for now don't bother to version check
	//
	return;
#if DBG
    if ((SavedMajorVersion != 0x0c) || (SavedMinorVersion != VER_PRODUCTBUILD)) {
        dprintf("\r\n*** Extension DLL(%d Checked) does not match target system(%d %s)\r\n\r\n",
                VER_PRODUCTBUILD, SavedMinorVersion, (SavedMajorVersion==0x0f) ? "Free" : "Checked" );
    }
#else
    if ((SavedMajorVersion != 0x0f) || (SavedMinorVersion != VER_PRODUCTBUILD)) {
        dprintf("\r\n*** Extension DLL(%d Free) does not match target system(%d %s)\r\n\r\n",
                VER_PRODUCTBUILD, SavedMinorVersion, (SavedMajorVersion==0x0f) ? "Free" : "Checked" );
    }
#endif
}

LPEXT_API_VERSION
ExtensionApiVersion(
    VOID
    )
{
    return &ApiVersion;
}

//
//	VOID
//	PrintName(
//		PUNICODE_STRING Name
//		);
// print a unicode string
// Note: the Buffer field in unicode string is unmapped
//
VOID
PrintName(
	PUNICODE_STRING Name
	)
{
	USHORT i;
	WCHAR ubuf[256];
	UCHAR abuf[256];
	
	if (!GetString((UINT_PTR)Name->Buffer, ubuf, (ULONG)Name->Length))
	{
		return;
	}

	for (i = 0; i < Name->Length/2; i++)
	{
		abuf[i] = (UCHAR)ubuf[i];
	}
	abuf[i] = 0;

	dprintf("%s",abuf);
}

MYPWINDBG_OUTPUT_ROUTINE g_pfnDbgPrintf = NULL;




bool
dbgextReadMemory(
    UINT_PTR uOffset,
    void * pvBuffer,
    UINT cb,
    char *pszDescription
    )
{
    UINT cbBytesRead=0;

    bool fRet = ReadMemory(
                    uOffset,
                    pvBuffer,
                    cb,
                    &cbBytesRead
                    );
    if (!fRet || cbBytesRead != cb)
    {
        ERRPRT("Read  failed: 0x%X(%s, %u bytes)\n",uOffset,pszDescription,cb);
        fRet = FALSE;
    }

    return fRet;
}

bool
dbgextWriteMemory(
    UINT_PTR uOffset,
    void * pvBuffer,
    UINT cb,
    char *pszDescription
    )
{
    UINT cbBytesWritten=0;
    bool fRet = WriteMemory(
                    uOffset,
                    pvBuffer,
                    cb,
                    &cbBytesWritten
                    );
    if (!fRet || cbBytesWritten != cb)
    {
        ERRPRT("Write failed: 0x%X(%s, %u bytes)\n",uOffset,pszDescription,cb);
        fRet = FALSE;
    }
    return 0;
}


bool
dbgextReadUINT_PTR(
    UINT_PTR uOffset,
    UINT_PTR *pu,
    char *pszDescription
    )
{
    UINT cbBytesRead=0;

    bool fRet = ReadMemory(
                    uOffset,
                    pu,
                    sizeof(*pu),
                    &cbBytesRead
                    );
    if (!fRet || cbBytesRead != sizeof(*pu))
    {
        ERRPRT("Read  failed: 0x%X(%s, UINT_PTR)\n",uOffset,pszDescription);
        fRet = FALSE;
    }

    return fRet;
}

bool
dbgextReadUINT(
    UINT_PTR uOffset,
    UINT *pu,
    char *pszDescription
    )
{
    UINT cbBytesRead=0;

    bool fRet = ReadMemory(
                    uOffset,
                    pu,
                    sizeof(*pu),
                    &cbBytesRead
                    );
    if (!fRet || cbBytesRead != sizeof(*pu))
    {
        ERRPRT("Read  failed: 0x%X(%s, UINT)\n",uOffset,pszDescription);
        fRet = FALSE;
    }

    return fRet;
}

bool
dbgextReadSZ(
    UINT_PTR uOffset,
    char *szBuf,
    UINT	cbMax,
    char *pszDescription
    )
// Read a NULL-terminated string (upto cbMax)
//
{
    UINT cbBytesRead=0;

    bool fRet = ReadMemory(
                    uOffset,
                    szBuf,
                    cbMax,
                    &cbBytesRead
                    );
    if (!fRet)
    {
        ERRPRT("Read  failed: 0x%p(%s, SZ)\n",uOffset,pszDescription);
        fRet = FALSE;
    }
    else
    {
    	if (cbBytesRead)
    	{
    		szBuf[cbBytesRead-1] = 0;
		}
		else
		{
			*szBuf = 0;
		}
    }
    return fRet;
}

bool
dbgextWriteUINT_PTR(
    UINT_PTR uOffset,
    UINT_PTR u,
    char *pszDescription
    )
{
    UINT cbBytesWritten=0;
    bool fRet = WriteMemory(
                    uOffset,
                    &u,
                    sizeof(uOffset),
                    &cbBytesWritten
                    );
    if (!fRet || cbBytesWritten != sizeof(u))
    {
        ERRPRT("Write failed: 0x%X(%s, UINT_PTR)\n",uOffset,pszDescription);
        fRet = FALSE;
    }
    return fRet;
}

UINT_PTR
dbgextGetExpression(
    const char *pcszExpression
    )
{
    UINT_PTR uRet =  GetExpression(pcszExpression);
    
    //
    // At such a point we use this for something besides pointers,
    // we will remove the check below.
    //

    if (!uRet)
    {
        ERRPRT("Eval  failed: \"%s\"\n", pcszExpression);
    }

    return uRet;
}

void
dbgextDumpDLlist(
	UINT_PTR uOffset,
	UINT	uContainingOffset,
	char 	*pszDescription
	)
/*++
	Print the pointers to the containing records of
	all the items in the doubly-linked list.
--*/
{
	bool fRet;
	LIST_ENTRY Link;
	LIST_ENTRY *pHead;
	LIST_ENTRY *pFlink;
	UINT uCount = 0;
	UINT uMax = 16;

	do
	{
		char *szPrefix;
		char *szSuffix;

		// Read the list header.
		//
		fRet = dbgextReadMemory(
				uOffset,
				&Link,
				sizeof(Link),
				pszDescription
				);

		if (!fRet) break;

		pHead = (LIST_ENTRY *) uOffset;
		pFlink = Link.Flink;

		if (pFlink == pHead)
		{
			MyDbgPrintf("        <empty>\n");
			break;
		}


		for(
			;
			(pFlink != pHead);
			pFlink = Link.Flink, uCount++)
		{
			char *pContainingRecord = ((char *)pFlink) -  uContainingOffset;

			szPrefix = "        ";
			szSuffix = "";
			if (uCount%4)
			{
				szPrefix = " ";
				if ((uCount%4)==3)
				{
					szSuffix = "\n";
				}
			}

			if (uCount >= uMax) break;
			

			// Read the next link.
			//
			fRet = dbgextReadMemory(
					(UINT_PTR) pFlink,
					&Link,
					sizeof(Link),
					pszDescription
					);
			if (!fRet) break;

			MyDbgPrintf("%s0x%p%s", szPrefix, pContainingRecord, szSuffix);
		}

		if (uCount%4)
		{
			MyDbgPrintf("\n");
		}
		if (pFlink != pHead && uCount >= uMax)
		{
			MyDbgPrintf("        ...\n");
		}

	} while (FALSE);
}

void
WalkDLlist(
	UINT_PTR uOffsetHeadList,
	UINT_PTR uOffsetStartLink,	OPTIONAL
	void *pvContext,
	PFNNODEFUNC pFunc,
	UINT	MaxToWalk,
	char *pszDescription
	)
/*++
	Print the pointers to the containing records of
	all the items in the doubly-linked list.
--*/
{
	bool fRet;
	LIST_ENTRY Link;
	LIST_ENTRY *pHead;
	LIST_ENTRY *pFlink;
	UINT uCount = 0;
	UINT uMax = MaxToWalk;

	do
	{
		// Read the list header.
		//
		fRet = dbgextReadMemory(
				uOffsetHeadList,
				&Link,
				sizeof(Link),
				pszDescription
				);

		if (!fRet) break;

		pHead = (LIST_ENTRY *) uOffsetHeadList;

		if (uOffsetStartLink == 0)
		{
			pFlink = Link.Flink;
		}
		else
		{
			pFlink = (LIST_ENTRY*) uOffsetStartLink;
		}

		if (pFlink == pHead)
		{
			// MyDbgPrintf("        <end-of-list>\n");
			break;
		}


		for(
			;
			(pFlink != pHead);
			pFlink = Link.Flink, uCount++)
		{
			if (uCount >= uMax) break;
			
			// Read the next link.
			//
			fRet = dbgextReadMemory(
					(UINT_PTR) pFlink,
					&Link,
					sizeof(Link),
					pszDescription
					);
			if (!fRet) break;

			// Call the nodefunc..
			//
			pFunc((UINT_PTR)pFlink, 0 /*uIndex*/, pvContext);
		}
	} while (FALSE);
}

void
DumpObjects(TYPE_INFO *pType, UINT_PTR uAddr, UINT cObjects, UINT uFlags)
{
    //
    // Print object's type and size
    //
    dprintf(
        "%s@0x%X (%lu Bytes)\n",
        pType->szName,
        uAddr,
        pType->cbSize
        );


    DumpMemory(
        uAddr,
        pType->cbSize,
        0,
        pType->szName
        );
    
    //
    // Dump bytes...
    //

    return;
}

BYTE rgbScratchBuffer[100000];

bool
DumpMemory(
    UINT_PTR uAddr,
    UINT cb,
    UINT uFlags,
    const char *pszDescription
    )
{
    bool fTruncated = FALSE;
    bool fRet = FALSE;
    UINT cbLeft = cb;
    char *pbSrc = rgbScratchBuffer;

    if (cbLeft>1024)
    {
        cbLeft = 1024;
        fTruncated = TRUE;
    }
    
    fRet = dbgextReadMemory(
            uAddr,
            rgbScratchBuffer,
            cbLeft,
            (char*)pszDescription
            );

    if (!fRet) goto end;

    #define ROWSIZE 16 // bytes
    //
    // Dump away...
    //
    while (cbLeft)
    {
        char rgTmp_dwords[ROWSIZE];
        char rgTmp_bytes[ROWSIZE];
        char *pb=NULL;
        UINT cbRow = ROWSIZE;
        if (cbRow > cbLeft)
        {
            cbRow = cbLeft;
        }
    
        
        memset(rgTmp_dwords, 0xff, sizeof(rgTmp_dwords));
        memset(rgTmp_bytes,  ' ', sizeof(rgTmp_bytes));

        memcpy(rgTmp_dwords, pbSrc, cbRow);
        memcpy(rgTmp_bytes,  pbSrc, cbRow);
        
        // sanitize bytes
        for (pb=rgTmp_bytes; pb<(rgTmp_bytes+sizeof(rgTmp_bytes)); pb++)
        {
            char c = *pb;
            if (c>=0x20 && c<0x7f) // isprint is too permissive.
            {
                if (*pb=='\t')
                {
                    *pb=' ';
                }
            }
            else
            {
                *pb='.';
            }
        }

        dprintf(
            "    %08lx: %08lx %08lx %08lx %08lx |%4.4s|%4.4s|%4.4s|%4.4s|\n",
            uAddr,
            ((DWORD*) rgTmp_dwords)[0],
            ((DWORD*) rgTmp_dwords)[1],
            ((DWORD*) rgTmp_dwords)[2],
            ((DWORD*) rgTmp_dwords)[3],
        #if 1
            rgTmp_bytes+0,
            rgTmp_bytes+4,
            rgTmp_bytes+8,
            rgTmp_bytes+12
        #else
            "aaaabbbbccccdddd",
            "bbbb",
            "cccc",
            "dddd"
        #endif
            );

        cbLeft -= cbRow;
        pbSrc += cbRow;
        uAddr += cbRow;
    }

#if 0
0x00000000: 00000000 00000000 00000000 00000000 |xxxx|xxxx|xxxx|xxxx|
0x00000000: 00000000 00000000 00000000 00000000 |xxxx|xxxx|xxxx|xxxx|
0x00000000: 00000000 00000000 00000000 00000000 |xxxx|xxxx|xxxx|xxxx|
#endif // 

end:

    return fRet;
}


bool
MatchPrefix(const char *szPattern, const char *szString)
{
    BOOL fRet = FALSE;
    ULONG uP = lstrlenA(szPattern);
    ULONG uS = lstrlenA(szString);

    if (uP<=uS)
    {
        fRet = (_memicmp(szPattern, szString, uP)==0);
    }

    return fRet;
}

bool
MatchSuffix(const char *szPattern, const char *szString)
{
    BOOL fRet = FALSE;
    ULONG uP = lstrlenA(szPattern);
    ULONG uS = lstrlenA(szString);

    if (uP<=uS)
    {
        szString += (uS-uP);
        fRet = (_memicmp(szPattern, szString, uP)==0);
    }
    return fRet;
}

bool
MatchSubstring(const char *szPattern, const char *szString)
{
    BOOL fRet = FALSE;
    ULONG uP = lstrlenA(szPattern);
    ULONG uS = lstrlenA(szString);

    if (uP<=uS)
    {
        const char *szLast =  szString + (uS-uP);
        do
        {
            fRet = (_memicmp(szPattern, szString, uP)==0);

        } while (!fRet && szString++ < szLast);
    }

    return fRet;
}

bool
MatchExactly(const char *szPattern, const char *szString)
{
    BOOL fRet = FALSE;
    ULONG uP = lstrlenA(szPattern);
    ULONG uS = lstrlenA(szString);

    if (uP==uS)
    {
        fRet = (_memicmp(szPattern, szString, uP)==0);
    }

    return fRet;
}


bool
MatchAlways(const char *szPattern, const char *szString)
{
    return TRUE;
}

void
DumpBitFields(
		ULONG  			Flags,
    	BITFIELD_INFO	rgBitFieldInfo[]
		)
{
	BITFIELD_INFO *pbf = rgBitFieldInfo;

	for(;pbf->szName; pbf++)
	{
		if ((Flags & pbf->Mask) == pbf->Value)
		{
			MyDbgPrintf(" %s", pbf->szName);
		}
	}
}

void
DumpStructure(
    TYPE_INFO *pType,
    UINT_PTR uAddr,
    char *szFieldSpec,
    UINT uFlags
    )
{
    //
    // Determine field comparision function ...
    //
    PFNMATCHINGFUNCTION pfnMatchingFunction = MatchAlways;

	if (pType->pfnSpecializedDump)
	{
		// Call the specialized function to handle this...
		//
		pType->pfnSpecializedDump(
				pType,
				uAddr,
				szFieldSpec,
				uFlags
				);
		return;	
	}

    //
    // Pick a selection function ...
    //
    if (szFieldSpec)
    {
        if (uFlags & fMATCH_SUBSTRING)
        {
            pfnMatchingFunction = MatchSubstring;
        }
        else if (uFlags & fMATCH_SUFFIX)
        {
            pfnMatchingFunction = MatchSuffix;
        }
        else if (uFlags & fMATCH_PREFIX)
        {
            pfnMatchingFunction = MatchPrefix;
        }
        else
        {
            pfnMatchingFunction = MatchExactly;
        }
    }

    //
    // Print object's type and size
    //
    dprintf(
        "%s@0x%X (%lu Bytes)\n",
        pType->szName,
        uAddr,
        pType->cbSize
        );

    //
    // Run through all the fields in this type, and if the entry is selected,
    // we will display it.
    //
    {
        STRUCT_FIELD_INFO *pField = pType->rgFields;
        for (;pField->szFieldName; pField++)
        {
            bool fMatch  = !szFieldSpec
                           || pfnMatchingFunction(szFieldSpec, pField->szFieldName);
            if (fMatch)
            {
                UINT_PTR uFieldAddr = uAddr + pField->uFieldOffset;

                // special-case small fields...
                if (pField->uFieldSize<=sizeof(ULONG_PTR))
                {

					ULONG_PTR Buf=0;
    				BOOL fRet = dbgextReadMemory(
										uFieldAddr,
										&Buf,
										pField->uFieldSize,
                        				(char*)pField->szFieldName
										);
					if (fRet)
					{
						// print it as a hex number

						MyDbgPrintf(
							"\n%s\t[%lx,%lx]: 0x%lx",
							pField->szFieldName,
							pField->uFieldOffset,
							pField->uFieldSize,
							Buf
							);

						//
						// If it's an embedded object and it's a bitfield,
						// print the bitfields...
						//
						if (	FIELD_IS_EMBEDDED_TYPE(pField)
							&&  TYPEISBITFIELD(pField->pBaseType) )
						{
							DumpBitFields(
									(ULONG)Buf,
								    pField->pBaseType->rgBitFieldInfo
								    );
							
						}
						
						MyDbgPrintf("\n");
	
					}
					continue;
				}

            #if 0
                MyDbgPrintf(
                    "%s\ndc 0x%08lx L %03lx %s\n",
                    pField->szSourceText,
                    uFieldAddr,
                    pField->uFieldSize,
                    pField->szFieldName
                    );
            #else // 1
                MyDbgPrintf(
                    "\n%s\t[%lx,%lx]\n",
                    pField->szFieldName,
                    pField->uFieldOffset,
                    pField->uFieldSize
                    );
            #endif // 1

                // if (szFieldSpec)
                {
                #if 0
                    MyDumpObjects(
                        pCmd,
                        pgi->pBaseType,
                        pgi->uAddr,
                        pgi->cbSize,
                        pgi->szName
                        );
                #endif // 0
                    DumpMemory(
                        uFieldAddr,
                        pField->uFieldSize,
                        0,
                        pField->szFieldName
                        );
                }
            }
        }
    }

    return;
}


DECLARE_API( help )
{
    do_help(args);
}


DECLARE_API( rm )
{
    do_rm(args);
}

DECLARE_API( arp )
{
    do_arp(args);
}


ULONG
NodeFunc_DumpAddress (
	UINT_PTR uNodeAddr,
	UINT uIndex,
	void *pvContext
	)
{
	MyDbgPrintf("[%lu] 0x%08lx\n", uIndex, uNodeAddr);
	return 0;
}


UINT
WalkList(
	UINT_PTR uStartAddress,
	UINT uNextOffset,
	UINT uStartIndex,
	UINT uEndIndex,
	void *pvContext,
	PFNNODEFUNC pFunc,
	char *pszDescription
	)
//
// Visit each node in the list in turn,
// reading just the next pointers. It calls pFunc for each list node
// between uStartIndex and uEndIndex. It terminates under the first of
// the following conditions:
// 	* Null pointer
// 	* ReadMemoryError
// 	* Read past uEndIndex
// 	* pFunc returns FALSE
//
{
	UINT uIndex = 0;
	UINT_PTR uAddress = uStartAddress;
	BOOL fRet = TRUE;
	UINT uRet = 0;


	//
	// First skip until we get to uStart Index
	//
	for (;fRet && uAddress && uIndex < uStartIndex; uIndex++)
	{
		fRet =  dbgextReadUINT_PTR(
							uAddress+uNextOffset,
							&uAddress,
							pszDescription
							);
	}


	//
	// Now call pFunc with each node
	//
	for (;fRet && uAddress && uIndex <= uEndIndex; uIndex++)
	{
		uRet = pFunc(uAddress, uIndex, pvContext);

		fRet =  dbgextReadUINT_PTR(
							uAddress+uNextOffset,
							&uAddress,
							pszDescription
							);
	}

	pFunc = NodeFunc_DumpAddress;
	return uRet;
}