Google
 

Trailing-Edge - PDP-10 Archives - BB-P363B-SM_1985 - mcb/nml/nmlctt.bli
There are 2 other files named nmlctt.bli in the archive. Click here to see a list.
!NET:<BRANDT.DEVELOPMENT>NMLCTT.BLI.1  9-Dec-82 18:00:03, Edit by BRANDT
!
! Ident 04.
! Change COUNTER_BUFFER_SIZE to 200 and allocation call as suggested
! by VOBA and GROSSMAN.
!
! NET:<PECKHAM.DEVELOPMENT>NMLCTT.BLI.4  6-Mar-82 21:19:03, Edit by PECKHAM
!
! Ident 03.
! Change input parameters for $NML$SHOZC.
!
! NET:<PECKHAM.DEVELOPMENT>NMLCTT.BLI.2  5-Feb-82 16:38:08, Edit by PECKHAM
!
! Ident 02.
! Fix range checking for 16 bit machines in NML$COUNTER_TIMER_SET.
!
! NET:<GROSSMAN>NMLCTT.BLI.2 27-Jan-82 08:44:55, Edit by GROSSMAN
!
! Ident 1.
! Don't allow counter timers to be set with 0, negative, or values greater
! than 65536. The routine NML$COUNTER_TIMER_SET will return a $false value
! if there is an attempt to do so.
!
! NET:<GROSSMAN>NMLCTT.BLI.34 26-Jan-82 06:48:16, Edit by GROSSMAN
!
! Fix bug in NML$CTR_CHECK_TIME. This caused the counter block given in
! the call to NMU$QUEUE_SCAN to never be changed (and this caused the task to
! always schedule the first counter block that ever got set). Also fix bugs
! related to being woken up on multiple events. We can now successfully wake
! up on counter timer queue changes, and on timeouts!
!
! NET:<GROSSMAN>NMLCTT.BLI.10 23-Jan-82 02:25:06, Edit by GROSSMAN
!
! Fix bug(s) with routines called by NMU$QUEUE_SCAN. They were all declared
! novalue, but it turns out to be much more valuable to return values than
! not to, because a non-zero value (which the novalue attribute does not
! ensure) terminates queue scanning immediately. This screws me up.
!
! NET:<GROSSMAN>NMLCTT.BLI.8 22-Jan-82 14:46:13, Edit by GROSSMAN
!
! Fix routine NML$CTR_CHECK_TIME so that it returns the counter block with
! the least date/time.
!
! NET:<GROSSMAN>NMLCTT.BLI.6 22-Jan-82 02:17:43, Edit by GROSSMAN
!
! Fix NML$CTR_REMOVE_ENTITY so that it knows how to remove the first item
! on the counter blocks entity list.
!
! NET:<GROSSMAN>NMLCTT.BLI.17 21-Jan-82 10:35:42, Edit by GROSSMAN
!
! Add the SHOW and ZERO function and event logging interface...
!
module NMLCTT (					! Counter Timer Task
		ident = 'X00.04'
		) =
begin
!
!                    COPYRIGHT (c) 1980, 1981, 1982
!                    DIGITAL EQUIPMENT CORPORATION
!                        Maynard, Massachusetts
!
!     This software is furnished under a license and may  be  used
!     and copied only in accordance with the terms of such license
!     and with the inclusion of the above copyright notice.   This
!     software  or any other copies thereof may not be provided or
!     otherwise made available to any other person.  No  title  to
!     and ownership of the software is hereby transferred.
!
!     The information  in  this  software  is  subject  to  change
!     without  notice  and should not be construed as a commitment
!     by DIGITAL EQUIPMENT CORPORATION.
!
!     DIGITAL assumes no responsibility for the use or reliability
!     of  its  software  on  equipment  which  is  not supplied by
!     DIGITAL.
!

!++
! Facility: LSG DECnet Network Management
!
! Abstract:
!
!	This module implements the Counter Timer functionality. This task wakes
!	up every time a Counter Timer goes off in order	to generate an
!	Automatic Counter event. The internal functioning of this module will
!	be to repeatedly do a Read-and-Zero function for each entity which has
!	a counter timer set. And then to sleep until the next counter timer
!	goes off. The actual counter timer event would seem to be an atomic
!	operation to the user.
!
! Environment: TOPS10 and TOPS20 user mode, MCB RSX task mode
!
! Author: Stu Grossman, Creation date: 6 January 1982
!
!--
!
! Include files
!

library 'NMLLIB';			! NML definitions

!
! External routines
!

external routine
    NMU$TABLE_ROUTINES,			! Table manipulation stuff
    NMU$QUEUE_MANAGER,			! Queue management routines
    NMU$SCHED_MANAGER,			! Scheduler routines
    NMU$MEMORY_MANAGER,			! Memory management routines
    NML$DECLARE_EVENT;                  ! Interface to event generator

!
! Debugging definitions
!

external
    %debug_data_base;			! Debugging definitions

!
! Forward references
!

forward routine
    NML$CTR_CHECK_TIME,
    NML$CTR_COUNTER_TIMER_PREDICATE,
    NML$CTR_ENTITY_LIST_SCANNER,
    NML$CTR_REMOVE_ENTITY : novalue,
    NML$CTR_COUNTER_QUEUE_SCANNER;

!
! Counter timer block definitions
!

$field
    CTR_FIELDS =
	set
	    CTR_QUEUE = [$sub_block (Q_ENTRY_SIZE)],	! Queue of these blocks
	    CTR_TIME = [$sub_block (TIME_BLOCK_SIZE)],	! Abs time of next period
	    CTR_PERIOD = [$integer],			! Period in seconds
	    CTR_ENTITY_LIST = [$address]		! Pointers to entities on this timer
	tes;

literal
    CTR_SIZE = $field_set_size,
    CTR_ALLOCATION = $field_set_units;

macro
    CTR_BLOCK = block [CTR_SIZE] field (CTR_FIELDS) %;

!
! Entity block, pointed to by CTR_BLOCKs
!

$field
    ENTITY_FIELDS =
	set
	    ENTITY_NEXT = [$address],		! Points to next block
	    ENTITY_TYPE = [$tiny_integer],	! Entity type
	    ENTITY_ID_PTR = [$pointer]		! Pointer to string id
	tes;

literal
    ENTITY_SIZE = $field_set_size,
    ENTITY_ALLOCATION = $field_set_units;

macro
    ENTITY_BLOCK = block [ENTITY_SIZE] field (ENTITY_FIELDS) %;

!
! Storage
!

own
    COUNTER_TIMER_QUEUE : SQ_HEADER,
    OLD_ENTITY_ID : ref ENTITY_BLOCK,
    OLD_COUNTER_TIMER_BLOCK : ref CTR_BLOCK,
    COUNTER_TIMER_QUEUE_WAS_CHANGED;
%global_routine ('NML$COUNTER_TIMER_TASK') : novalue =

!++
! Functional description:
!
!	This routine will initialize the counter timer data base and
!	then loop forever waiting for counter timers to be set or for
!	counter timers to go off.
!
! Formal parameters:
!
!	None.
!
! Routine value:
!
!	None.
!
! Side effects:
!
!	None.
!
!--

begin
    local
	CURRENT_TIME : TIME_BLOCK,
	TEMP,
	CURRENT_COUNTER : ref CTR_BLOCK,
	COUNTER_BUFFER_PTR,			! Points to receiving buffer
	COUNTER_BUFFER_SIZE,			! Contains size of buffer
	EVENT : ref RAW_EVENT_BLOCK;	! Event block
!
! Reset the counter timer scheduler queue
!

    NMU$SQUEUE_RESET (COUNTER_TIMER_QUEUE);
    COUNTER_TIMER_QUEUE_WAS_CHANGED = $false;
!
! Set up a buffer for reading the counters into and a raw event block for
! queueing up the event.
!

    COUNTER_BUFFER_SIZE = 200;

    COUNTER_BUFFER_PTR =
      ch$ptr (NMU$MEMORY_GET (ch$allocation (.COUNTER_BUFFER_SIZE)),,8);

    EVENT = NMU$MEMORY_GET (RAW_EVENT_BLOCK_ALLOCATION);

!
! Loop forever doing this tasks' work
!

    while $true do
    begin

!
! Block, waiting for the counter timer queue to become non-empty.
! After receiving an item reinstall it on the queue, because we really don't
! want to remove anything from the queue.
!
	CURRENT_COUNTER = NMU$SQUEUE_REMOVE (COUNTER_TIMER_QUEUE);
	NMU$QUEUE_INSERT (COUNTER_TIMER_QUEUE [Q_QUEUE],
			  CURRENT_COUNTER [CTR_QUEUE]);

	COUNTER_TIMER_QUEUE_WAS_CHANGED = $false;
!
! Scan through the queue looking for the counter timer block that has the
! nearest time to now (or one that should have occurred already).
!
	TIME_CURRENT (0, CURRENT_TIME);
	NMU$QUEUE_SCAN (COUNTER_TIMER_QUEUE,
			CURRENT_COUNTER,
			NML$CTR_CHECK_TIME);
!
! See if the time has already passed, if it has, don't go to sleep, else
! sleep until we wake up at the right time (or if the COUNTER_TIMER_QUEUE
! has changed (so that we can reschedule)).
!

	TEMP = TIME_DIFFERENCE_SECONDS (CURRENT_COUNTER [CTR_TIME],
					      CURRENT_TIME);
	%debug (CTT_TRACE,
		(TRACE_INFO ('%D second counter selected in %D seconds',
			     .CURRENT_COUNTER [CTR_PERIOD],
			     .TEMP)));

!
! Now we have to put ourself on our own event queue so that when
! NML$COUNTER_TIMER_SET changes the counter timer queue, we get woken up.
! First, clear the event block, then put the current task (me) into the
! event queue for myself. This is just like doing an NMU$SCHED_WAIT with
! no wait.
!
	if .TEMP gtr 0 then
	begin
	    bind
		EB = COUNTER_TIMER_QUEUE [Q_EVENT] : EVENT_BLOCK;

	    NMU$TABLE_INSERT (EB [EB_TASKS], CURRENT_TASK);
	    NMU$SCHED_SLEEP (.TEMP);
	    NMU$TABLE_DELETE (EB [EB_TASKS], 1);
	end;
!
! Now, its time to see if we woke up because we timed out, or because we
! have to reschedule. This is done by examining the event queue. If the queue
! is still full, then we got here because of we woke up from our sleep, else
! the queue has been changed, and we have to reschedule.
!
	if not .COUNTER_TIMER_QUEUE_WAS_CHANGED then
	begin
	    local
		ENTITY : ref ENTITY_BLOCK;

	    ENTITY = .CURRENT_COUNTER [CTR_ENTITY_LIST];
	    while .ENTITY neq 0 do
	    begin
		%debug (CTT_TRACE,
			(TRACE_INFO ('Counter timer for entity %#A going off',
				     ch$rchar(.ENTITY[ENTITY_ID_PTR]),
				     ch$plus(.ENTITY[ENTITY_ID_PTR],1))));
		EVENT [REB_EVENT_CODE] = 0 ^ 6 + 8; ! Automatic Counters
		EVENT [REB_ENTITY_TYPE] = .ENTITY [ENTITY_TYPE];
		EVENT [REB_ENTITY_POINTER] = .ENTITY [ENTITY_ID_PTR];
		EVENT [REB_DATA_POINTER] = .COUNTER_BUFFER_PTR;
		EVENT [REB_DATA_LENGTH] = .COUNTER_BUFFER_SIZE;
		EVENT [REB_TIME_BLOCK] = 0;

		$NML$SHOZC(.ENTITY [ENTITY_TYPE],
			   .ENTITY [ENTITY_ID_PTR],
                           , %(no qualifier)%
			   EVENT [REB_DATA_LENGTH],
			   .COUNTER_BUFFER_PTR);

		NML$DECLARE_EVENT (.EVENT);
		ENTITY = .ENTITY [ENTITY_NEXT];
	    end;
	    TIME_PLUS (CURRENT_COUNTER [CTR_TIME],
		       .CURRENT_COUNTER [CTR_PERIOD]);
	end;


    end; ! End of task loop
end; ! End of NML$COUNTER_TIMER_TASK
%global_routine ('NML$COUNTER_TIMER_SET', PARM_NO, TYPE, EID_ADR, PARM_VAL_PTR) =

!++
! Functional description:
!
!	This routine is called by the Volatile Data Base manager each time a
!	parameter is set.  If the parameter is a counter timer, then it will
!	either create a new counter timer block or just	string the entity onto
!	an already existing counter timer block of the same period. It will
!	also notify the counter timer task if a new counter timer block has
!	been created.
!
! Formal parameters:
!
!	PARM_NO			The Network Management parameter number
!	TYPE			The entity type
!	EID_ADR			Address of entity id string
!	PARM_VAL_PTR		Character pointer to byte swapped parameter value
!
! Routine value:
!
!	$true			The value of the counter is between 1 and 65536
!	$false			Counter timer value is out of range
!
!--

begin
    local
	ENTITY_ID : ref ENTITY_BLOCK,
	PERIOD;

!
! First check to see if a counter timer is being set. If not, just return.
!

    if not NML$CTR_COUNTER_TIMER_PREDICATE (.PARM_NO, .TYPE) then return $true;

!
! Ok, we got a counter timer, now lets see if it has been set already.
! First set up an entity id block
!

    PERIOD = GETW (PARM_VAL_PTR);

    if .PERIOD eqlu 0 or (%bpval gtr 16 and .PERIOD gtr 65535) then return $false;

    ENTITY_ID = NMU$MEMORY_GET (ENTITY_ALLOCATION);

    ENTITY_ID [ENTITY_TYPE] = .TYPE;
    ENTITY_ID [ENTITY_ID_PTR] = ch$ptr(.EID_ADR,,8);
    ENTITY_ID [ENTITY_NEXT] = 0;

!
! Now scan through the data base looking for another block similar to the one
! we now have.
!

    OLD_ENTITY_ID = OLD_COUNTER_TIMER_BLOCK = 0;
    NMU$QUEUE_SCAN (COUNTER_TIMER_QUEUE,
		    .ENTITY_ID,
		    NML$CTR_ENTITY_LIST_SCANNER);

!
! If a counter timer has already been set for this entity, clear it, otherwise
! just proceed.
!

    if .OLD_ENTITY_ID neq 0
     then NML$CTR_REMOVE_ENTITY (.OLD_COUNTER_TIMER_BLOCK, .OLD_ENTITY_ID);

!
! Now see if there is an existing counter block with the same period as that of
! the counter we want to set.
!

    OLD_COUNTER_TIMER_BLOCK = 0;
    NMU$QUEUE_SCAN (COUNTER_TIMER_QUEUE,
		    .PERIOD,
		    NML$CTR_COUNTER_QUEUE_SCANNER);

!
! If there is no counter block with that period, create one, insert it into
! the queue of counter blocks (and wake up the counter timer task), and set
! its time and period.
!

    if .OLD_COUNTER_TIMER_BLOCK eql 0 then
    begin
	OLD_COUNTER_TIMER_BLOCK = NMU$MEMORY_GET (CTR_ALLOCATION);
	NMU$SQUEUE_INSERT (COUNTER_TIMER_QUEUE, .OLD_COUNTER_TIMER_BLOCK);
	COUNTER_TIMER_QUEUE_WAS_CHANGED = $true;
	OLD_COUNTER_TIMER_BLOCK [CTR_PERIOD] = .PERIOD;
	TIME_CURRENT (0, OLD_COUNTER_TIMER_BLOCK [CTR_TIME]);
	OLD_COUNTER_TIMER_BLOCK [CTR_ENTITY_LIST] = .ENTITY_ID;
	return $true
    end;

    OLD_ENTITY_ID = .OLD_COUNTER_TIMER_BLOCK [CTR_ENTITY_LIST];
    while .OLD_ENTITY_ID [ENTITY_NEXT] neq 0 do
     OLD_ENTITY_ID = .OLD_ENTITY_ID [ENTITY_NEXT];

    OLD_ENTITY_ID [ENTITY_NEXT] = .ENTITY_ID;
    $true
end; ! End of NML$COUNTER_TIMER_SET
%global_routine ('NML$COUNTER_TIMER_CLEAR', PARM_NO, TYPE, EID_ADR) : novalue =

!++
! Functional description:
!
!	This routine is called by the Volatile Data Base manager each time a
!	parameter is cleared.  If the parameter is a counter timer, then it
!	will remove that counter timer from the data base.
!
! Formal parameters:
!
!	PARM_NO			The Network Management parameter number
!	TYPE			The entity type
!	EID_ADR			Address of entity id string
!
!--

begin
    local
	ENTITY_ID : ref ENTITY_BLOCK;

!
! First check to see if a counter timer is being set. If not, just return.
!

    if not NML$CTR_COUNTER_TIMER_PREDICATE (.PARM_NO, .TYPE) then return ;

!
! Ok, we got a counter timer, now lets see if it has been set already.
! First set up an entity id block
!

    ENTITY_ID = NMU$MEMORY_GET (ENTITY_ALLOCATION);

    ENTITY_ID [ENTITY_TYPE] = .TYPE;
    ENTITY_ID [ENTITY_ID_PTR] = ch$ptr(.EID_ADR,,8);
    ENTITY_ID [ENTITY_NEXT] = 0;

!
! Now scan through the data base looking for another block similar to the one
! we now have.
!

    OLD_ENTITY_ID = OLD_COUNTER_TIMER_BLOCK = 0;
    NMU$QUEUE_SCAN (COUNTER_TIMER_QUEUE,
		    .ENTITY_ID,
		    NML$CTR_ENTITY_LIST_SCANNER);

!
! If a counter timer has already been set for this entity, clear it, otherwise
! just return.
!

    if .OLD_ENTITY_ID neq 0 then
    begin
	NML$CTR_REMOVE_ENTITY (.OLD_COUNTER_TIMER_BLOCK, .OLD_ENTITY_ID);
	return
    end;

    NMU$MEMORY_RELEASE (.ENTITY_ID, ENTITY_ALLOCATION);

end; ! End of NML$COUNTER_TIMER_CLEAR
%routine ('NML$CTR_CHECK_TIME', COUNTER_BLOCK : ref CTR_BLOCK, CURRENT_COUNTER) =

!++
! Functional description:
!
!	This routine is called from NML$QUEUE_SCAN with the address of a
!	CTR_BLOCK and a time block. It will update CURRENT_COUNTER to reflect
!	the counter block found thus far that has the smallest time.
!
! Inputs:
!
!	COUNTER_BLOCK		The address of a counter block
!	CURRENT_COUNTER		The address of the address of a counter block
!				used as an initial comparison for this routine.
!
! Outputs:
!
!	.CURRENT_COUNTER	The address of the latest (thus far) CTR_BLOCK
!
!--

begin
    bind
	TEMP = .CURRENT_COUNTER : ref CTR_BLOCK;

    if TIME_TEST (COUNTER_BLOCK [CTR_TIME], lss, TEMP [CTR_TIME])
    then TEMP = .COUNTER_BLOCK;
    0  ! Ensure that NMU$QUEUE_SCAN does not terminate prematurely
end; ! End of NML$CTR_CHECK_TIME
%routine ('NML$CTR_ENTITY_LIST_SCANNER', COUNTER_BLOCK : ref CTR_BLOCK, ENTITY_ID : ref ENTITY_BLOCK) =

!++
! Functional description:
!
!	This routine will facilitate the search for an entity in the counter
!	timer data base. It is called for each counter timer block, and will
!	scan through that blocks entity list looking for the desired entity.
!	When it finds a match, it will set up OLD_ENTITY_ID and
!	OLD_COUNTER_TIMER_BLOCK.
!
! Formal parameters:
!
!	COUNTER_BLOCK		The address of the current counter block
!	ENTITY_ID		The address of the entity being sought after.
!
! Implicit outputs:
!
!	OLD_ENTITY_ID		The address of the found entity or 0
!	OLD_COUNTER_TIMER_BLOCK	 "     "    "   "    "   counter " "
!
! Routine value:
!
!	none
!--

begin
    local
	TEMP : ref ENTITY_BLOCK;

    TEMP = .COUNTER_BLOCK [CTR_ENTITY_LIST];
    while .TEMP neq 0 do
    begin
	if .TEMP [ENTITY_ID_PTR] eql .ENTITY_ID [ENTITY_ID_PTR] then
	begin
	    OLD_ENTITY_ID = .TEMP;
	    OLD_COUNTER_TIMER_BLOCK = .COUNTER_BLOCK;
	    return 1		! Tell NMU$QUEUE_SCAN to stop scanning
	end;
	TEMP = .TEMP [ENTITY_NEXT];
    end;
    0				! Tell NMU$QUEUE_SCAN to keep on scanning
end; ! End of NML$CTR_ENTITY_LIST_SCANNER
%routine ('NML$CTR_COUNTER_QUEUE_SCANNER', COUNTER_BLOCK : ref CTR_BLOCK, PERIOD) =

!++
! Functional description:
!
!	This routine will facilitate the search for a counter block with a
!	certain period. If such a block is not found a zero will be returned.
!
! Formal parameters:
!
!	COUNTER_BLOCK		The address of the current counter block
!	PERIOD			The period be being searched for.
!
! Implicit outputs:
!
!	OLD_COUNTER_TIMER_BLOCK	The address of a counter block that has the
!				proper period.
!
! Routine value:
!
!	none
!--

begin
    if .COUNTER_BLOCK [CTR_PERIOD] neq .PERIOD then return 0
     else OLD_COUNTER_TIMER_BLOCK = .COUNTER_BLOCK

end; ! End of NML$CTR_COUNTER_QUEUE_SCANNER
%routine ('NML$CTR_COUNTER_TIMER_PREDICATE', PARM_NO, TYPE) =

!++
! Functional description:
!
!	This routine will determine whether or not the entity type and
!	parameter number constitute a counter timer parameter for that entity
!	type. This routine is needed because the fucking NML specification is
!	so inane.
!
! Formal parameters:
!
!	PARM_NO			Parameter number
!	TYPE			Network management type number
!
! Routine value:
!
!	$false			The parameter is not a counter timer
!	$true			The parameter is a counter timer
!--

begin
    if	(  .TYPE eql NODE_E   and .PARM_NO eql 160
	or .TYPE eql LINE_    and .PARM_NO eql 110
	or .TYPE eql CIRCUIT_ and .PARM_NO eql 110
	or .TYPE eql MODULE_  and .PARM_NO eql 100)

	then $true else $false

end; ! End of NML$CTR_COUNTER_TIMER_PREDICATE
%routine ('NML$CTR_REMOVE_ENTITY') : novalue =

!++
! Functional description:
!
!	This routine will remove an entity from the counter timer data base.
!	It will unlink and deallocate its entity block. It will also unlink
!	and deallocate empty counter timer blocks (and wake up the counter
!	timer task if so).
!
! Implicit inputs:
!
!	OLD_ENTITY_ID		The address of the entity to be killed
!	OLD_COUNTER_TIMER_BLOCK	The address of the counter block that contains
!				the aforementioned entity.
!
! Side effects:
!
!	The counter timer task may be invoked if a counter timer block gets
!	destroyed.  If all the counter timer blocks get destroyed, the
!	counter timer task will sleep forever.
!
! Routine value:
!
!	none
!
!--
begin
    local
	TEMP : ref ENTITY_BLOCK;

!
! First see if the entity is hooked up directly to the counter block (ie: is
! it the first item?). If so, update the pointer in the counter block, else
! update the pointer in the appropriate entity block in the counters entity
! block list.
!

    if .OLD_COUNTER_TIMER_BLOCK [CTR_ENTITY_LIST] eql .OLD_ENTITY_ID
    then
    OLD_COUNTER_TIMER_BLOCK [CTR_ENTITY_LIST] = .OLD_ENTITY_ID [ENTITY_NEXT]
    else
    begin
	TEMP = .OLD_COUNTER_TIMER_BLOCK [CTR_ENTITY_LIST];
	while (.TEMP [ENTITY_NEXT] neq .OLD_ENTITY_ID) do
	TEMP = .TEMP [ENTITY_NEXT];

	TEMP [ENTITY_NEXT] = .OLD_ENTITY_ID [ENTITY_NEXT];
    end;

    NMU$MEMORY_RELEASE (.OLD_ENTITY_ID, ENTITY_ALLOCATION);

    if .OLD_COUNTER_TIMER_BLOCK [CTR_ENTITY_LIST] eql 0 then
    begin
	NMU$QUEUE_EXTRACT (COUNTER_TIMER_QUEUE [Q_QUEUE],
			   OLD_COUNTER_TIMER_BLOCK [CTR_QUEUE]);
	COUNTER_TIMER_QUEUE_WAS_CHANGED = $true;
	NMU$MEMORY_RELEASE (.OLD_COUNTER_TIMER_BLOCK, CTR_ALLOCATION);
	NMU$SCHED_FLAG (COUNTER_TIMER_QUEUE [Q_EVENT]);
    end;

end; ! NML$CTR_REMOVE_ENTITY

end ! End of NMLCTT
eludom