|
|
//================================================================================
// Copyright (C) 1997 Microsoft Corporation
// Author: RameshV
// Description: implements the basic structures for a server object
// ThreadSafe: no
// Locks: none
// Please read stdinfo.txt for programming style.
//================================================================================
#include <mm.h>
#include <winbase.h>
#include <array.h>
#include <opt.h>
#include <optl.h>
#include <optclass.h>
#include <bitmask.h>
#include <range.h>
#include <reserve.h>
#include <subnet.h>
#include <optdefl.h>
#include <classdefl.h>
#include <oclassdl.h>
#include <sscope.h>
#include "server.h"
#include "server\uniqid.h"
//BeginExport(function)
DWORD MemServerInit( OUT PM_SERVER *Server, IN DWORD Address, IN DWORD State, IN DWORD Policy, IN LPWSTR Name, IN LPWSTR Comment ) //EndExport(function)
{ DWORD Error; DWORD Size; PM_SERVER Srv;
AssertRet(Server, ERROR_INVALID_PARAMETER );
Size = ROUND_UP_COUNT(sizeof(M_SERVER), ALIGN_WORST); if(Name) Size += sizeof(WCHAR)*(1+wcslen(Name)); if(Comment) Size += sizeof(WCHAR)*(1+wcslen(Comment));
Srv = MemAlloc(Size); if( NULL == Srv ) return ERROR_NOT_ENOUGH_MEMORY;
Srv->LastUniqId = 0; Srv->Address = Address; Srv->State = State; Srv->Policy = Policy; Error = MemArrayInit(&Srv->Subnets); if( ERROR_SUCCESS != Error ) { MemFree(Srv); return Error; }
Error = MemArrayInitLoc(&Srv->Subnets, &Srv->Loc); // Require(ERROR_SUCCESS == Error); // guaranteed failure as the array is empty now..
Error = MemArrayInit(&Srv->MScopes); if( ERROR_SUCCESS != Error ) { MemFree(Srv); return Error; }
Error = MemArrayInit(&Srv->SuperScopes); if( ERROR_SUCCESS != Error ) { MemFree(Srv); return Error; }
Error = MemOptClassInit(&Srv->Options); if( ERROR_SUCCESS != Error ) { MemFree(Srv); return Error; }
Error = MemOptClassDefListInit(&Srv->OptDefs); if( ERROR_SUCCESS != Error ) { MemFree(Srv); return Error; }
Error = MemClassDefListInit(&Srv->ClassDefs); if( ERROR_SUCCESS != Error ) { MemFree(Srv); return Error; }
Size = ROUND_UP_COUNT(sizeof(M_SERVER), ALIGN_WORST);
if( Name ) { Srv->Name = (LPWSTR)(Size + (LPBYTE)Srv); Size += sizeof(WCHAR)*(1 + wcslen(Name)); wcscpy(Srv->Name, Name); } if( Comment ) { Srv->Comment = (LPWSTR)(Size + (LPBYTE)Srv); wcscpy(Srv->Comment, Comment); }
*Server = Srv; return ERROR_SUCCESS; }
//BeginExport(function)
DWORD MemServerCleanup( IN OUT PM_SERVER Server ) //EndExport(function)
{ DWORD Error;
AssertRet(Server, ERROR_INVALID_PARAMETER);
Error = MemArrayCleanup(&Server->Subnets); Require( ERROR_SUCCESS == Error);
Error = MemArrayCleanup(&Server->MScopes); Require( ERROR_SUCCESS == Error);
Error = MemArrayCleanup(&Server->SuperScopes); Require( ERROR_SUCCESS == Error);
Error = MemOptClassCleanup(&Server->Options); Require( ERROR_SUCCESS == Error);
Error = MemOptClassDefListCleanup(&Server->OptDefs); Require( ERROR_SUCCESS == Error);
Error = MemClassDefListCleanup(&Server->ClassDefs); Require( ERROR_SUCCESS == Error);
MemFree(Server);
return ERROR_SUCCESS; }
//================================================================================
// subnet related functions on the server
//================================================================================
//BeginExport(function)
DWORD MemServerGetUAddressInfo( IN PM_SERVER Server, IN DWORD Address, OUT PM_SUBNET *Subnet, // OPTIONAL
OUT PM_RANGE *Range, // OPTIONAL
OUT PM_EXCL *Excl, // OPTIONAL
OUT PM_RESERVATION *Reservation // OPTIONAL
) //EndExport(function)
{ ARRAY_LOCATION Loc; PM_SUBNET ThisSubnet; DWORD Error; LONG Start, End, Mid;
AssertRet(Server && (Subnet || Range || Excl || Reservation ), ERROR_INVALID_PARAMETER); Require( !CLASSD_HOST_ADDR( Address ) );
//
// more efficient binary search
//
if( Subnet ) { *Subnet = NULL; }
Start = 0; End = MemArraySize(&Server->Subnets) - 1;
while( Start <= End ) { // still got an element to go by..
Mid = (Start + End) /2 ;
Error = MemArrayGetElement(&Server->Subnets, &Mid, &ThisSubnet); Require( ERROR_SUCCESS == Error );
Require(ThisSubnet->fSubnet); // can't work if something inbetween aint a subnet
if( Address < ThisSubnet->Address) { // not in this subnet ..
End = Mid - 1; } else if( ThisSubnet->Address == (ThisSubnet->Mask & Address) ) {
//
// We got the subnet we're looking for..
//
if( Range || Excl || Reservation ) { Error = MemSubnetGetAddressInfo( ThisSubnet, Address, Range, Excl, Reservation ); } // if
if( Subnet ) { *Subnet = ThisSubnet; }
//
// if we got a subnet, but didn't suceed above.. still we got something
// so return success... otherwise return whatever above returned..
//
return ( Subnet && (*Subnet) ) ? ERROR_SUCCESS: Error; } else {
//
// Has to be one of the furhter down subnets..
//
Start = Mid + 1; } } // while
//
// couldn't find it unfortunately..
//
return ERROR_FILE_NOT_FOUND; }
//BeginExport(function)
DWORD MemServerGetMAddressInfo( IN PM_SERVER Server, IN DWORD Address, OUT PM_SUBNET *Subnet, // OPTIONAL
OUT PM_RANGE *Range, // OPTIONAL
OUT PM_EXCL *Excl, // OPTIONAL
OUT PM_RESERVATION *Reservation // OPTIONAL
) //EndExport(function)
{ ARRAY_LOCATION Loc; PM_SUBNET ThisMScope; DWORD Error,Error2; PM_RANGE RangeTmp;
AssertRet(Server && (Subnet || Range || Excl || Reservation ), ERROR_INVALID_PARAMETER);
if( NULL == Range ) Range = &RangeTmp;
Error = MemArrayInitLoc(&Server->MScopes, &Loc); while ( ERROR_FILE_NOT_FOUND != Error ) { Require(ERROR_SUCCESS == Error);
Error = MemArrayGetElement(&Server->MScopes, &Loc, (LPVOID*)&ThisMScope); Require(ERROR_SUCCESS == Error);
Error2 = MemSubnetGetAddressInfo( ThisMScope, Address, Range, Excl, Reservation );
if (ERROR_SUCCESS == Error2) { if( Subnet ) *Subnet = ThisMScope; return ERROR_SUCCESS; }
Error = MemArrayNextLoc(&Server->MScopes, &Loc); continue; }
return ERROR_FILE_NOT_FOUND; }
//BeginExport(function)
DWORD MemServerAddSubnet( IN OUT PM_SERVER Server, IN PM_SUBNET Subnet, // completely created subnet, must not be
IN ULONG UniqId // in Server's list tho'
) //EndExport(function)
{ DWORD Error; PM_SUBNET OldSubnet; ARRAY_LOCATION Loc;
AssertRet(Server && Subnet, ERROR_INVALID_PARAMETER); AssertRet((Subnet->Mask & Subnet->Address), ERROR_INVALID_PARAMETER);
Subnet->ServerPtr = Server; // set the backptr for future use
//
// First check if subnet duplicates exist and avoid that
//
//
for( Error = MemArrayInitLoc(&Server->Subnets, &Loc); NO_ERROR == Error ; Error = MemArrayNextLoc(&Server->Subnets, &Loc) ) {
Error = MemArrayGetElement(&Server->Subnets, &Loc, &OldSubnet); Require(ERROR_SUCCESS == Error);
if( (Subnet->Address & OldSubnet->Mask) == OldSubnet->Address || (OldSubnet->Address & Subnet->Mask) == Subnet->Address ) { return ERROR_OBJECT_ALREADY_EXISTS; } } // for
//
// Subnets are stored in ascending order of IP addresses.. so insert
// at the right location
//
Subnet->UniqId = UniqId;
for( Error = MemArrayInitLoc(&Server->Subnets, &Loc) ; ERROR_SUCCESS == Error ; Error = MemArrayNextLoc(&Server->Subnets, &Loc) ) { Error = MemArrayGetElement(&Server->Subnets, &Loc, &OldSubnet); Require(ERROR_SUCCESS == Error);
if( Subnet->Address == OldSubnet->Address ) { //
// Subnet already present?
//
return ERROR_OBJECT_ALREADY_EXISTS; }
if( Subnet->Address < OldSubnet->Address ) { //
// right place to insert the new subnet..
//
Error = MemArrayInitLoc(&Server->Subnets, &Server->Loc); Require(ERROR_SUCCESS == Error);
Error = MemArrayInsElement(&Server->Subnets, &Loc, Subnet); Require(ERROR_SUCCESS == Error);
return Error; } } // for
//
// This subnet's address is greater than all others.. so add it at end
//
Error = MemArrayAddElement(&Server->Subnets, Subnet); if( ERROR_SUCCESS != Error) return Error;
Error = MemArrayInitLoc(&Server->Subnets, &Server->Loc); Require(ERROR_SUCCESS == Error);
return ERROR_SUCCESS; } // MemServerAddSubnet()
//
// Delete all the elements attached to this subnet.
//
DWORD MemServerDelSubnetElements( IN PM_SUBNET Subnet ) { DWORD Error; ARRAY_LOCATION Loc, Loc2; PM_EXCL Excl; PM_RANGE Range; PM_RESERVATION Resrv; PM_ONECLASS_OPTLIST OptList; PM_OPTION Option; // Delete Ranges
Error = MemArrayInitLoc( &Subnet->Ranges, &Loc ); while (( MemArraySize( &Subnet->Ranges ) > 0 ) && ( ERROR_FILE_NOT_FOUND != Error )) { Error = MemArrayGetElement( &Subnet->Ranges, &Loc, ( LPVOID * ) &Range ); Require( ERROR_SUCCESS == Error ); Error = DeleteRecord( Range->UniqId ); if ( ERROR_SUCCESS != Error ) { MemFree( Range ); return Error; } Error = MemArrayDelElement( &Subnet->Ranges, &Loc, ( LPVOID * ) &Range ); Require( ERROR_SUCCESS == Error ); MemFree( Range );
} // while
// Delete all exclusions
Error = MemArrayInitLoc( &Subnet->Exclusions, &Loc ); while (( MemArraySize( &Subnet->Exclusions ) > 0 ) && ( ERROR_FILE_NOT_FOUND != Error )) { Error = MemArrayGetElement( &Subnet->Exclusions, &Loc, ( LPVOID * ) &Excl ); Require( ERROR_SUCCESS == Error ); Error = DeleteRecord( Excl->UniqId ); if ( ERROR_SUCCESS != Error ) { MemFree( Excl ); return Error; } Error = MemArrayDelElement( &Subnet->Exclusions, &Loc, ( LPVOID * ) &Excl ); Require( ERROR_SUCCESS == Error ); MemFree( Excl );
} // while
// Delete all Reservations
Error = MemArrayInitLoc( &Subnet->Reservations, &Loc ); while (( MemArraySize( &Subnet->Reservations ) > 0 ) && ( ERROR_FILE_NOT_FOUND != Error )) { Error = MemArrayGetElement( &Subnet->Reservations, &Loc, ( LPVOID * ) &Resrv ); Require( ERROR_SUCCESS == Error ); Error = MemReserveDel( &Subnet->Reservations, Resrv->Address ); // MemFree( Resrv );
} // while
// Delete all option values
Error = MemOptClassDelClass( &Subnet->Options );
return Error; } // MemServerDelSubnetElements()
//BeginExport(function)
DWORD MemServerDelSubnet( IN OUT PM_SERVER Server, IN DWORD SubnetAddress, OUT PM_SUBNET *Subnet ) //EndExport(function)
{ DWORD Error; PM_SUBNET DeletedSubnet; ARRAY_LOCATION Loc;
AssertRet(Server && Subnet, ERROR_INVALID_PARAMETER);
Error = MemArrayInitLoc(&Server->Subnets, &Loc); while( ERROR_FILE_NOT_FOUND != Error ) { Require(ERROR_SUCCESS == Error);
Error = MemArrayGetElement(&Server->Subnets, &Loc, (LPVOID*)&DeletedSubnet); Require(ERROR_SUCCESS == Error && DeletedSubnet);
if( SubnetAddress == DeletedSubnet->Address) { Error = MemServerDelSubnetElements( DeletedSubnet ); if ( ERROR_SUCCESS != Error ) { return Error; }
Error = DeleteRecord( DeletedSubnet->UniqId ); if ( ERROR_SUCCESS != Error ) { return Error; }
Error = MemArrayDelElement(&Server->Subnets, &Loc, (LPVOID*)Subnet); Require(ERROR_SUCCESS == Error && Subnet); Require(*Subnet == DeletedSubnet);
Error = MemArrayInitLoc(&Server->Subnets, &Server->Loc); // Require(ERROR_SUCCESS == Error); // this may fail if this is the last subnet being deleted
return ERROR_SUCCESS; }
Error = MemArrayNextLoc(&Server->Subnets, &Loc); } // while
return ERROR_FILE_NOT_FOUND; } // MemServerDelSubnet()
//BeginExport(function)
DWORD MemServerFindSubnetByName( IN PM_SERVER Server, IN LPWSTR Name, OUT PM_SUBNET *Subnet ) //EndExport(function)
{ DWORD Error; PM_SUBNET ThisSubnet; ARRAY_LOCATION Loc;
AssertRet(Server && Name && Subnet, ERROR_INVALID_PARAMETER);
Error = MemArrayInitLoc(&Server->Subnets, &Loc); while( ERROR_FILE_NOT_FOUND != Error ) { Require(ERROR_SUCCESS == Error);
Error = MemArrayGetElement(&Server->Subnets, &Loc, (LPVOID*)&ThisSubnet); Require(ERROR_SUCCESS == Error && ThisSubnet);
if( 0 == wcscmp(Name, ThisSubnet->Name) ) { *Subnet = ThisSubnet; return ERROR_SUCCESS; }
Error = MemArrayNextLoc(&Server->Subnets, &Loc); }
return ERROR_FILE_NOT_FOUND; }
//================================================================================
// superscope functionality
//================================================================================
//BeginExport(constant)
#define INVALID_SSCOPE_ID 0xFFFFFFFF
#define INVALID_SSCOPE_NAME NULL
//EndExport(constant)
//BeginExport(function)
DWORD MemServerFindSScope( // find matching with EITHER scopeid ir sscopename
IN OUT PM_SERVER Server, IN DWORD SScopeId, // 0xFFFFFFFF == invalid scope id, dont use for search
IN LPWSTR SScopeName, // NULL == invalid scope name
OUT PM_SSCOPE *SScope ) //EndExport(function)
{ ARRAY_LOCATION Loc; DWORD Error; PM_SSCOPE ThisSScope;
AssertRet(Server && SScope, ERROR_INVALID_PARAMETER); AssertRet(SScopeId != INVALID_SSCOPE_ID || SScopeName != INVALID_SSCOPE_NAME, ERROR_INVALID_PARAMETER);
Error = MemArrayInitLoc(&Server->SuperScopes, &Loc); while( ERROR_FILE_NOT_FOUND != Error ) { Require(ERROR_SUCCESS == Error);
Error = MemArrayGetElement(&Server->SuperScopes, &Loc, &ThisSScope); Require(ERROR_SUCCESS == Error && ThisSScope);
if( ThisSScope->SScopeId == SScopeId || (INVALID_SSCOPE_NAME != SScopeName && 0 == wcscmp(ThisSScope->Name, SScopeName) )) { *SScope = ThisSScope; return ERROR_SUCCESS; }
Error = MemArrayNextLoc(&Server->SuperScopes, &Loc); } return ERROR_FILE_NOT_FOUND; }
//
// Each scope has a field that specifies a superscope. The list of super scopes in
// the memory do not contain any individual physical record in the database. The
// superscope name associated with the scope is the real physical entry.
//
//BeginExport(function)
DWORD MemServerAddSScope( IN OUT PM_SERVER Server, IN PM_SSCOPE SScope ) //EndExport(function)
{ DWORD Error; PM_SSCOPE OldSScope;
AssertRet( Server && SScope && INVALID_SSCOPE_ID != SScope->SScopeId && INVALID_SSCOPE_NAME != SScope->Name, ERROR_INVALID_PARAMETER );
Error = MemServerFindSScope( Server, SScope->SScopeId, SScope->Name, &OldSScope ); if( ERROR_SUCCESS == Error && OldSScope ) return ERROR_OBJECT_ALREADY_EXISTS;
Error = MemArrayAddElement(&Server->SuperScopes, SScope); return Error; } // MemServerAddSScope()
//BeginExport(function)
DWORD MemServerDelSScope( IN OUT PM_SERVER Server, IN DWORD SScopeId, OUT PM_SSCOPE *SScope ) //EndExport(function)
{ DWORD Error; ARRAY_LOCATION Loc; PM_SSCOPE ThisSScope;
AssertRet(Server && SScope && INVALID_SSCOPE_ID != SScopeId, ERROR_INVALID_PARAMETER);
Error = MemArrayInitLoc(&Server->SuperScopes, &Loc); while( ERROR_FILE_NOT_FOUND != Error ) { Require(ERROR_SUCCESS == Error);
Error = MemArrayGetElement(&Server->SuperScopes, &Loc, (LPVOID *)&ThisSScope); Require(ERROR_SUCCESS == Error && ThisSScope );
if( ThisSScope->SScopeId == SScopeId ) { Error = MemArrayDelElement(&Server->SuperScopes, &Loc, (LPVOID *)SScope); Require(ERROR_SUCCESS == Error && *SScope == ThisSScope);
return Error; } // if
Error = MemArrayNextLoc(&Server->SuperScopes, &Loc); } // while
return ERROR_FILE_NOT_FOUND; } // MemServerDelSScope()
//================================================================================
// MCAST scope functionality
//================================================================================
//BeginExport(constants)
#define INVALID_MSCOPE_ID 0x0
#define INVALID_MSCOPE_NAME NULL
//EndExport(constants)
//BeginExport(function)
DWORD MemServerFindMScope( // search either based on ScopeId or ScopeName
IN PM_SERVER Server, IN DWORD MScopeId, // Multicast scope id, or 0 if this is not the key to search on
IN LPWSTR Name, // Multicast scope name or NULL if this is not the key to search on
OUT PM_MSCOPE *MScope ) //EndExport(function)
{ ARRAY_LOCATION Loc; DWORD Error; PM_MSCOPE ThisMScope;
AssertRet(Server && MScope, ERROR_INVALID_PARAMETER);
Error = MemArrayInitLoc(&Server->MScopes, &Loc); while (ERROR_FILE_NOT_FOUND != Error) { Require(ERROR_SUCCESS == Error);
Error = MemArrayGetElement(&Server->MScopes, &Loc, &ThisMScope); Require(ERROR_SUCCESS == Error && ThisMScope );
if( MScopeId == ThisMScope->MScopeId || (Name != INVALID_MSCOPE_NAME && 0 == wcscmp(Name, ThisMScope->Name)) ) { *MScope = ThisMScope; return ERROR_SUCCESS; }
Error = MemArrayNextLoc(&Server->MScopes, &Loc); } return ERROR_FILE_NOT_FOUND; }
//BeginExport(function)
DWORD MemServerAddMScope( IN OUT PM_SERVER Server, IN OUT PM_MSCOPE MScope, IN ULONG UniqId ) //EndExport(function)
{ DWORD Error; PM_MSCOPE OldMScope;
AssertRet(Server && MScope, ERROR_INVALID_PARAMETER); AssertRet(MScope->MScopeId != INVALID_MSCOPE_ID && MScope->Name != INVALID_MSCOPE_NAME, ERROR_INVALID_PARAMETER);
MScope->ServerPtr = Server; // set the backptr for future use
Error = MemServerFindMScope( Server, MScope->Address, MScope->Name, &OldMScope );
if( ERROR_SUCCESS == Error && OldMScope ) return ERROR_OBJECT_ALREADY_EXISTS;
MScope->UniqId = UniqId; Error = MemArrayAddElement(&Server->MScopes, (LPVOID)MScope); Require(ERROR_SUCCESS == Error);
return Error; }
//BeginExport(function)
DWORD MemServerDelMScope( IN OUT PM_SERVER Server, IN DWORD MScopeId, IN LPWSTR MScopeName, OUT PM_MSCOPE *MScope ) //EndExport(function)
{ DWORD Error; ARRAY_LOCATION Loc; PM_MSCOPE ThisMScope;
AssertRet(Server && MScope && MScopeName != INVALID_MSCOPE_NAME, ERROR_INVALID_PARAMETER);
Error = MemArrayInitLoc(&Server->MScopes, &Loc); while (ERROR_FILE_NOT_FOUND != Error) { Require(ERROR_SUCCESS == Error);
Error = MemArrayGetElement(&Server->MScopes, &Loc, &ThisMScope); Require(ERROR_SUCCESS == Error && ThisMScope );
if ( INVALID_MSCOPE_ID != MScopeId ) { if( MScopeId == ThisMScope->MScopeId ) {
Error = MemServerDelSubnetElements( ThisMScope ); if ( ERROR_SUCCESS != Error ) { return Error; }
Error = DeleteRecord( ThisMScope->UniqId ); if ( ERROR_SUCCESS != Error ) { return Error; }
Error = MemArrayDelElement(&Server->MScopes, &Loc, MScope); Require(ERROR_SUCCESS == Error && *MScope == ThisMScope);
return Error; } // if
} // if
if ( INVALID_MSCOPE_NAME != MScopeName ) { if( !wcscmp(MScopeName, ThisMScope->Name ) ) { Error = MemServerDelSubnetElements( ThisMScope ); if ( ERROR_SUCCESS != Error ) { return Error; }
Error = DeleteRecord( ThisMScope->UniqId ); if ( ERROR_SUCCESS != Error ) { return Error; }
Error = MemArrayDelElement(&Server->MScopes, &Loc, MScope); Require(ERROR_SUCCESS == Error && *MScope == ThisMScope);
return Error; } } // if
Error = MemArrayNextLoc(&Server->MScopes, &Loc); } // while
return ERROR_FILE_NOT_FOUND; } // MemServerDelMScope()
//================================================================================
// ClassId related stuff
//================================================================================
//BeginExport(function)
DWORD MemServerGetOptDef( IN OUT PM_SERVER Server, IN DWORD ClassId, // required, strict search, no defaulting class to zero
IN DWORD VendorId, // required, strict search, no defaulting vendor to zero
IN DWORD OptId, // OPTIONAL - search by this or following param
IN LPWSTR OptName, // OPTIONAL - search by name or above param
OUT PM_OPTDEF *OptDef // if found return the opt def here
) //EndExport(function)
{ DWORD Error; PM_OPTDEFLIST OptDefList;
Require(OptDef);
Error = MemOptClassDefListFindOptDefList( &Server->OptDefs, ClassId, VendorId, &OptDefList ); if( ERROR_SUCCESS != Error ) return Error;
Require(OptDefList);
return MemOptDefListFindOptDef( OptDefList, OptId, OptName, OptDef ); }
//BeginExport(function)
DWORD MemServerAddOptDef( IN OUT PM_SERVER Server, IN DWORD ClassId, IN DWORD VendorId, IN DWORD OptId, IN LPWSTR OptName, IN LPWSTR OptComment, IN DWORD Type, IN LPBYTE OptVal, IN DWORD OptValLen, IN ULONG UniqId ) //EndExport(function)
{ DWORD Error; PM_OPTDEF OptDef;
return MemOptClassDefListAddOptDef( &Server->OptDefs, ClassId, VendorId, OptId, Type, OptName, OptComment, OptVal, OptValLen, UniqId ); } // MemOptClassDefListAddOptDef()
//BeginExport(function)
DWORD MemServerDelOptDef( IN OUT PM_SERVER Server, IN DWORD ClassId, IN DWORD VendorId, IN DWORD OptId ) //EndExport(function)
{ DWORD Error; PM_OPTDEFLIST OptDefList;
Error = MemOptClassDefListFindOptDefList( &Server->OptDefs, ClassId, VendorId, &OptDefList ); if( ERROR_SUCCESS != Error ) return Error;
return MemOptDefListDelOptDef( OptDefList, OptId ); }
//================================================================================
// end of File
//================================================================================
|