-- Transport Mechanism Mail Server - server for MTP --
-- [Indigo]<Grapevine>MS>MTPServer.mesa
-- HGM, 13-Nov-84 1:51:24
-- Randy Gobbel 29-May-81 14:46:16 --
-- Andrew Birrell 29-Dec-81 15:00:20 --
-- Mark Johnson May 28, 1981 2:39 PM --
-- Brenda Hankins 10-Aug-84 15:41:12 Klamath update (replace STOP with 2 init procs)
DIRECTORY
BodyDefs USING [ItemLength, maxRNameLength, RName, Timestamp],
FTPDefs,
HeapDefs USING [
HeapAbandonWrite, HeapEndRead, HeapReadData, HeapReadRName, HeapStartRead,
HeapStartWrite, HeapWriteData, HeapWriteRName, ObjectNumber, objectStart,
ReaderHandle, SetReaderOffset, WriterHandle],
LocalNameDefs USING [ReadMSName],
LogDefs USING [WriteLogEntry],
NameInfoDefs USING [
CheckStamp, Close, Enumerate, GetMembers, GetRemark, MemberInfo, NameType,
RListHandle],
PolicyDefs USING [CheckOperation, EndOperation, WaitOperation],
Process USING [Detach],
ProtocolDefs USING [AppendTimestamp, maxRemarkLength, Remark],
PupDefs USING [ParsePupAddressConstant, PupAddress],
ReturnDefs USING [CopyItem, ParseBody, RejectedByMTP],
RestartDefs USING [] --EXPORT only-- ,
ServerDefs USING [DownServer, ServerUp],
SiteCacheDefs USING [SingleFlush, ValidateRName],
SLDefs USING [
GetCount, SLHeader, SLEndRead, SLStartRead, SLReadHandle, SLTransfer,
SLWrite],
Storage USING [Free, FreeString, Node, String],
String USING [AppendDecimal, AppendString, EquivalentStrings],
Time USING [Current];
MTPServer: MONITOR
IMPORTS
FTPDefs, HeapDefs, LocalNameDefs, LogDefs, NameInfoDefs, PolicyDefs, Process,
ProtocolDefs, PupDefs, ReturnDefs, ServerDefs, SiteCacheDefs, SLDefs, Storage,
String, Time
EXPORTS RestartDefs =
BEGIN
RNameFromString: PROCEDURE [s: STRING] RETURNS [BOOLEAN] =
BEGIN RETURN[s.length <= BodyDefs.maxRNameLength] END;
MailSystemObject: TYPE = RECORD [
net, host: [0..256), -- FTPServerMail assumes these are first --
credentialsOK: BOOLEAN];
WhoIsHe: SIGNAL RETURNS [net, host: [0..256)] = CODE;
--communication between CreateMailSystem and Backstop --
CreateMailSystem: PROCEDURE [
filePrimitives: FTPDefs.FilePrimitives, bufferSize: CARDINAL]
RETURNS [mailSystem: FTPDefs.MailSystem, forwardingProvided: BOOLEAN] =
BEGIN
real: POINTER TO MailSystemObject = Storage.Node[SIZE[MailSystemObject]];
real.credentialsOK ← FALSE;
[real.net, real.host] ← SIGNAL WhoIsHe[];
RETURN[LOOPHOLE[real, FTPDefs.MailSystem], FALSE]
END;
DestroyMailSystem: PROCEDURE [mailSystem: FTPDefs.MailSystem] =
BEGIN Storage.Free[mailSystem]; END;
InspectCredentials: PROCEDURE [
mailSystem: FTPDefs.MailSystem, status: FTPDefs.Status,
user, password: STRING] = BEGIN END;
LocateMailboxes: PROCEDURE [
mailSystem: FTPDefs.MailSystem, localMailboxList: FTPDefs.Mailbox] =
BEGIN
mbx: FTPDefs.Mailbox;
FOR mbx ← localMailboxList, mbx.nextMailbox WHILE mbx # NIL DO
IF mbx.located THEN LOOP;
mbx.located ← RNameFromString[mbx.mailbox]
AND SiteCacheDefs.ValidateRName[mbx.mailbox];
ENDLOOP;
END;
StageMessage: PROCEDURE [
mailSystem: FTPDefs.MailSystem,
receiveBlock: PROCEDURE [UNSPECIFIED, POINTER, CARDINAL] RETURNS [CARDINAL],
receiveBlockData: UNSPECIFIED] = BEGIN ERROR; END;
DeliverMessage: PROCEDURE [
mailSystem: FTPDefs.MailSystem, localMailboxList: FTPDefs.Mailbox] =
BEGIN ERROR; END;
DummyForward: PROCEDURE [
mailSystem: FTPDefs.MailSystem, remoteMailboxList: FTPDefs.Mailbox] =
BEGIN ERROR; END;
RetrieveMessages: PROCEDURE [
mailSystem: FTPDefs.MailSystem, localMailbox: FTPDefs.Mailbox,
processMessage: PROCEDURE [FTPDefs.MessageInfo],
sendBlock: PROCEDURE [UNSPECIFIED, POINTER, CARDINAL],
sendBlockData: UNSPECIFIED] =
BEGIN
ERROR FTPDefs.FTPError[
unidentifiedPermanentError, "MTP retrieval not supported"L];
END;
myMailPrimitives: FTPDefs.MailPrimitivesObject ← [
CreateMailSystem, DestroyMailSystem, InspectCredentials, LocateMailboxes,
StageMessage, DeliverMessage, DummyForward, RetrieveMessages];
-- DL expansion for MTP socket --
CreateDL: PROC [bufferSize: CARDINAL] RETURNS [fileSystem: FTPDefs.FileSystem] =
BEGIN RETURN[LOOPHOLE[NIL]]; END;
DestroyDL: PROC [fileSystem: FTPDefs.FileSystem] = {};
DecomposeDL: PROC [
fileSystem: FTPDefs.FileSystem, absoluteFilename: STRING,
virtualFilename: FTPDefs.VirtualFilename] =
BEGIN
virtualFilename.device.length ← 0;
virtualFilename.directory.length ← 0;
virtualFilename.name.length ← 0;
String.AppendString[virtualFilename.name, absoluteFilename];
virtualFilename.version.length ← 0;
END;
ComposeDL: PROC [
fileSystem: FTPDefs.FileSystem, absoluteFilename: STRING,
virtualFilename: FTPDefs.VirtualFilename] =
BEGIN
IF virtualFilename.device.length = 0 AND virtualFilename.directory.length = 0
AND virtualFilename.name.length = 0 AND virtualFilename.version.length = 0
THEN NULL -- that's what the spec says! --
ELSE
BEGIN
absoluteFilename.length ← 0;
String.AppendString[absoluteFilename, virtualFilename.name];
END;
END;
InspectCredentialsDL: PROC [
fileSystem: FTPDefs.FileSystem, status: FTPDefs.Status,
user, password: STRING] = {};
EnumerateDL: PROCEDURE [
fileSystem: FTPDefs.FileSystem, files: STRING,
intent: FTPDefs.EnumerateFilesIntent,
processFile: PROC [UNSPECIFIED, STRING, FTPDefs.FileInfo],
processFileData: UNSPECIFIED] =
BEGIN
fileInfoObject: FTPDefs.FileInfoObject ← [
fileType: text, byteSize: 8, byteCount: 0, creationDate: NIL,
writeDate: NIL, readDate: NIL, author: NIL];
processFile[processFileData, files, @fileInfoObject];
END;
MyDLHandle: TYPE = RECORD [name: STRING, members: NameInfoDefs.RListHandle];
OpenDL: PROCEDURE [
fileSystem: FTPDefs.FileSystem, file: STRING, mode: FTPDefs.Mode,
fileTypePlease: BOOLEAN, info: FTPDefs.FileInfo]
RETURNS [fileHandle: FTPDefs.FileHandle, fileType: FTPDefs.FileType] =
BEGIN
IF mode = read THEN
BEGIN
myHandle: POINTER TO MyDLHandle = Storage.Node[SIZE[MyDLHandle]];
info: NameInfoDefs.MemberInfo = NameInfoDefs.GetMembers[file];
WITH info SELECT FROM
allDown =>
FTPDefs.FTPError[fileBusy, "Registration server not available"L];
notFound => FTPDefs.FTPError[noSuchFile, "Distribution list not found"L];
individual => FTPDefs.FTPError[noSuchFile, "Not a distribution list"L];
group => myHandle.members ← members;
ENDCASE => ERROR;
myHandle.name ← file;
fileHandle ← LOOPHOLE[myHandle];
fileType ← text;
END
ELSE
FTPDefs.FTPError[
requestedAccessDenied, "Distribution lists are read-only"L];
END;
ReadDL: PROC [
fileSystem: FTPDefs.FileSystem, fileHandle: FTPDefs.FileHandle,
sendBlock: PROC [UNSPECIFIED, POINTER, CARDINAL],
sendBlockData: UNSPECIFIED] =
BEGIN
myHandle: POINTER TO MyDLHandle = LOOPHOLE[fileHandle];
head: STRING = ": "L;
pad: STRING = ", "L;
tail: STRING = ";"L;
first: BOOLEAN ← TRUE;
SendDL: PROC [
memberList: NameInfoDefs.RListHandle,
testRecursion: PROC [new: BodyDefs.RName] RETURNS [BOOLEAN]] =
BEGIN
Work: PROC [member: BodyDefs.RName] RETURNS [done: BOOLEAN] =
BEGIN
DoneThis: PROC [new: BodyDefs.RName] RETURNS [BOOLEAN] =
BEGIN
-- Mechanism for eliminating recursive loops --
RETURN[
IF String.EquivalentStrings[member, new] THEN TRUE
ELSE testRecursion[new]]
END;
info: NameInfoDefs.MemberInfo;
skip: BOOLEAN ← FALSE;
FOR index: CARDINAL IN [0..member.length) DO
IF member[index] = '↑ THEN
BEGIN -- consider group --
IF testRecursion[member] THEN skip ← TRUE
ELSE info ← NameInfoDefs.GetMembers[member];
EXIT
END;
REPEAT FINISHED => info ← [individual[]];
ENDLOOP;
done ← FALSE;
IF NOT skip THEN
WITH info SELECT FROM
allDown, notFound, individual =>
BEGIN
IF first THEN first ← FALSE
ELSE sendBlock[sendBlockData, @(pad.text), pad.length];
sendBlock[sendBlockData, @(member.text), member.length];
END;
group =>
BEGIN
SendDL[members, DoneThis ! UNWIND => NameInfoDefs.Close[members]];
NameInfoDefs.Close[members];
END;
ENDCASE => ERROR;
END;
NameInfoDefs.Enumerate[memberList, Work];
END;
DoneTopLevel: PROC [new: BodyDefs.RName] RETURNS [BOOLEAN] =
-- top level of recursive loop elimination --
{RETURN[String.EquivalentStrings[myHandle.name, new]]};
BEGIN
remark: ProtocolDefs.Remark = [ProtocolDefs.maxRemarkLength];
info: NameInfoDefs.NameType = NameInfoDefs.GetRemark[myHandle.name, remark];
IF info # group THEN String.AppendString[remark, myHandle.name];
IF remark.length > 0 THEN
BEGIN
sendBlock[sendBlockData, @(remark.text), remark.length];
sendBlock[sendBlockData, @(head.text), head.length];
END;
SendDL[myHandle.members, DoneTopLevel];
IF remark.length > 0 THEN sendBlock[sendBlockData, @(tail.text), tail.length];
END;
END;
WriteDL: PROC [
fileSystem: FTPDefs.FileSystem, fileHandle: FTPDefs.FileHandle,
receiveBlock: PROC [UNSPECIFIED, POINTER, CARDINAL] RETURNS [CARDINAL],
receiveBlockData: UNSPECIFIED] = {
ERROR FTPDefs.FTPError[requestedAccessDenied, "Don't be silly!"L]};
DeleteDL: PROC [fileSystem: FTPDefs.FileSystem, file: STRING] = {
ERROR FTPDefs.FTPError[requestedAccessDenied, "Don't be silly!"L]};
RenameDL: PROC [fileSystem: FTPDefs.FileSystem, currentFile, newFile: STRING] =
{ERROR FTPDefs.FTPError[requestedAccessDenied, "Don't be silly!"L]};
CloseDL: PROC [
fileSystem: FTPDefs.FileSystem, fileHandle: FTPDefs.FileHandle,
aborted: BOOLEAN] =
BEGIN
myHandle: POINTER TO MyDLHandle = LOOPHOLE[fileHandle];
NameInfoDefs.Close[myHandle.members];
Storage.Free[myHandle];
END;
myDLPrimitives: FTPDefs.FilePrimitivesObject ← [
CreateFileSystem: CreateDL, DestroyFileSystem: DestroyDL,
DecomposeFilename: DecomposeDL, ComposeFilename: ComposeDL,
InspectCredentials: InspectCredentialsDL, EnumerateFiles: EnumerateDL,
OpenFile: OpenDL, ReadFile: ReadDL, WriteFile: WriteDL, CloseFile: CloseDL,
DeleteFile: DeleteDL, RenameFile: RenameDL];
-- Forwarding to foreign servers --
ftpUser: FTPDefs.FTPUser = FTPDefs.FTPCreateUser[
filePrimitives: NIL,
communicationPrimitives: FTPDefs.PupCommunicationPrimitives[]];
ForwardOutcome: TYPE = {ok, bad, tempFailure, totalFailure};
ForwardMessage: ENTRY PROCEDURE [
host: STRING, SLhandle: SLDefs.SLReadHandle, SLobj: HeapDefs.ReaderHandle,
body: HeapDefs.ObjectNumber, slHeader: POINTER TO SLDefs.SLHeader] =
BEGIN
bodyReader: HeapDefs.ReaderHandle ← HeapDefs.HeapStartRead[body];
ended: BOOLEAN;
outcome: ForwardOutcome ← ok;
goodCount: CARDINAL ← 0;
badCount: CARDINAL ← 0;
wrongCount: CARDINAL ← 0;
badList: HeapDefs.WriterHandle ← NIL;
badString: STRING ← NIL;
wrongList: HeapDefs.WriterHandle ← NIL;
MakeBad: PROC [bad: BodyDefs.RName] =
BEGIN
SiteCacheDefs.SingleFlush[bad];
IF NameInfoDefs.CheckStamp[bad] = notFound
OR slHeader.created.time + 24 * LONG[60 * 60] < Time.Current[] THEN -- Either name went bad, or name is foreign and bad,
-- or GV disagrees with MTP and we've waited long enough --
BEGIN
IF badList = NIL THEN badList ← HeapDefs.HeapStartWrite[temp];
HeapDefs.HeapWriteRName[badList, bad];
badCount ← badCount + 1;
END
ELSE -- GV database disagrees with MTP host: wait until they converge,
-- or until the long-term timeout on the message. --
BEGIN
IF wrongList = NIL THEN
BEGIN
wrongList ← HeapDefs.HeapStartWrite[SLpending];
HeapDefs.HeapWriteData[wrongList, [slHeader, SIZE[SLDefs.SLHeader]]];
END;
HeapDefs.HeapWriteRName[wrongList, bad];
wrongCount ← wrongCount + 1;
END;
END;
CheckRecipients: PROCEDURE =
BEGIN
msg: STRING = [128];
badRecipient: BodyDefs.RName = [BodyDefs.maxRNameLength];
error: FTPDefs.RecipientError;
number: CARDINAL;
ended: BOOLEAN;
DO
[number, error] ← FTPDefs.FTPIdentifyNextRejectedRecipient[ftpUser, msg];
IF number = 0 THEN EXIT;
goodCount ← goodCount - 1;
-- search SL for recipient --
HeapDefs.SetReaderOffset[SLobj, SIZE[SLDefs.SLHeader]];
ended ← FALSE;
UNTIL ended OR number = 0 DO
ended ← HeapDefs.HeapReadRName[SLobj, badRecipient];
number ← number - 1;
ENDLOOP;
IF number = 0 THEN
BEGIN
outcome ← bad;
IF badString = NIL THEN
BEGIN
badString ← Storage.String[msg.length];
String.AppendString[badString, msg];
END;
MakeBad[badRecipient];
END;
ENDLOOP;
END;
IF NOT ServerDefs.ServerUp[slHeader.server] THEN outcome ← tempFailure
ELSE
BEGIN
ENABLE
FTPDefs.FTPError =>
SELECT ftpError FROM
noNameLookupResponse, connectionTimedOut, connectionClosed,
connectionRejected, noRouteToNetwork, unidentifiedTransientError =>
GOTO tempFailure;
noValidRecipients => GOTO nobody;
ENDCASE -- includes: noSuchHost, unidentifiedPermanentError -- =>
BEGIN OPEN String;
IF ftpError = noSuchHost THEN message ← "server does not exist"L;
IF message = NIL OR message.length = 0 THEN
message ← "No message given"L;
IF badString # NIL THEN Storage.FreeString[badString];
badString ← Storage.String[message.length];
AppendString[badString, message];
GOTO totalFailure
END;
bodyLength: BodyDefs.ItemLength;
BEGIN
sender: BodyDefs.RName = [BodyDefs.maxRNameLength];
bodyLength ← ReturnDefs.ParseBody[reader: bodyReader, sender: sender];
FTPDefs.FTPOpenConnection[ftpUser, host, mail, NIL];
FTPDefs.FTPSetCredentials[ftpUser, primary, sender, NIL];
END;
FTPDefs.FTPBeginDeliveryOfMessage[ftpUser];
BEGIN
recipient: BodyDefs.RName = [BodyDefs.maxRNameLength];
[ended, ] ← HeapDefs.HeapReadData[SLobj, [recipient, 0]];
UNTIL ended DO
ended ← HeapDefs.HeapReadRName[SLobj, recipient];
FTPDefs.FTPSendRecipientOfMessage[ftpUser, recipient];
goodCount ← goodCount + 1;
ENDLOOP;
END;
CheckRecipients[];
ReturnDefs.CopyItem[
bodyReader, bodyLength, FTPDefs.FTPSendBlockOfMessage, ftpUser];
FTPDefs.FTPSendBlockOfMessage[ftpUser, NIL, 0]; --end of message--
CheckRecipients[];
FTPDefs.FTPEndDeliveryOfMessage[ftpUser];
EXITS
tempFailure => outcome ← tempFailure;
totalFailure =>
BEGIN
recipient: BodyDefs.RName = [BodyDefs.maxRNameLength];
ended: BOOLEAN;
HeapDefs.SetReaderOffset[
SLobj, HeapDefs.objectStart + SIZE[SLDefs.SLHeader]];
IF badList # NIL THEN {
HeapDefs.HeapAbandonWrite[badList]; badList ← NIL};
IF wrongList # NIL THEN {
HeapDefs.HeapAbandonWrite[wrongList]; wrongList ← NIL};
badCount ← wrongCount ← goodCount ← 0;
[ended, ] ← HeapDefs.HeapReadData[SLobj, [recipient, 0]];
UNTIL ended DO
ended ← HeapDefs.HeapReadRName[SLobj, recipient];
MakeBad[recipient];
ENDLOOP;
outcome ← totalFailure;
END;
nobody => BEGIN badCount ← badCount + goodCount; goodCount ← 0; END;
END;
FTPDefs.FTPCloseConnection[ftpUser];
LogForwarding[
outcome, host, slHeader.created, goodCount, badCount, wrongCount];
SELECT outcome FROM
ok, bad, totalFailure =>
BEGIN
IF badList # NIL THEN
ReturnDefs.RejectedByMTP[badList, body, host, badString];
IF wrongList # NIL THEN SLDefs.SLWrite[body, wrongList, pending];
SLDefs.SLEndRead[SLhandle];
END;
tempFailure =>
BEGIN
IF badList # NIL THEN HeapDefs.HeapAbandonWrite[badList];
IF wrongList # NIL THEN HeapDefs.HeapAbandonWrite[wrongList];
SLDefs.SLTransfer[SLhandle, input];
ServerDefs.DownServer[slHeader.server];
END;
ENDCASE => ERROR;
IF badString # NIL THEN Storage.FreeString[badString];
HeapDefs.HeapEndRead[bodyReader];
HeapDefs.HeapEndRead[SLobj];
END;
LogForwarding: PROC [
outcome: ForwardOutcome, host: STRING, postmark: BodyDefs.Timestamp,
goodCount, badCount, wrongCount: CARDINAL] =
BEGIN
log: STRING = [140];
log.length ← 0;
String.AppendString[log, "Forwarded "L];
ProtocolDefs.AppendTimestamp[log, postmark];
String.AppendString[log, " to "L];
String.AppendString[log, host];
String.AppendString[log, ": "];
SELECT outcome FROM
ok, bad, totalFailure =>
BEGIN
String.AppendString[log, "good="L];
String.AppendDecimal[log, goodCount];
IF badCount # 0 THEN
BEGIN
String.AppendString[log, ", bad="L];
String.AppendDecimal[log, badCount];
END;
IF wrongCount # 0 THEN
BEGIN
String.AppendString[log, ", wrong="L];
String.AppendDecimal[log, wrongCount];
END;
END;
tempFailure => BEGIN String.AppendString[log, "failed temporarily"L]; END;
ENDCASE => ERROR;
LogDefs.WriteLogEntry[log];
END;
NoRecipients: ERROR = CODE; --not caught; should not occur--
NotForeignSite: ERROR = CODE; --not caught; should not occur--
BadForeignSite: ERROR = CODE; --not caught; should not occur--
ForwardMain: PROCEDURE =
BEGIN
-- multiple instantiations of this procedure are allowed --
DO
SLobj: HeapDefs.ReaderHandle ← NIL;
SLhandle: SLDefs.SLReadHandle;
bodyObj: HeapDefs.ObjectNumber;
slHeader: SLDefs.SLHeader;
[SLhandle, bodyObj, SLobj] ← SLDefs.SLStartRead[foreign];
PolicyDefs.WaitOperation[readForward];
BEGIN
-- read SL header --
ended: BOOLEAN;
used: CARDINAL;
[ended, used] ← HeapDefs.HeapReadData[
SLobj, [@slHeader, SIZE[SLDefs.SLHeader]]];
IF ended THEN ERROR NoRecipients[];
END;
IF slHeader.server.type # foreign THEN ERROR NotForeignSite[];
WITH slHeader.server.name SELECT FROM
connect =>
ForwardMessage[
host: value, SLhandle: SLhandle, SLobj: SLobj, body: bodyObj,
slHeader: @slHeader];
ENDCASE => ERROR BadForeignSite[];
-- reader was closed by ForwardMessage --
PolicyDefs.EndOperation[readForward];
ENDLOOP;
END;
ForwardRestart: PROCEDURE =
BEGIN
-- on restart, must transfer everything to input, since ServerHandles
-- are no longer valid --
THROUGH [1..SLDefs.GetCount[foreign]] DO
BEGIN
handle: SLDefs.SLReadHandle;
body: HeapDefs.ObjectNumber;
SL: HeapDefs.ReaderHandle;
[handle, body, SL] ← SLDefs.SLStartRead[foreign];
HeapDefs.HeapEndRead[SL];
SLDefs.SLTransfer[handle, input];
END;
ENDLOOP;
END;
-- Backstop and Filter for listeners --
Backstop: FTPDefs.BackstopServer ←
BEGIN
addr: PupDefs.PupAddress;
IF NOT PupDefs.ParsePupAddressConstant[@addr, originOfRequest] THEN
BEGIN addr.net ← [0]; addr.host ← [0]; END;
localInsignia.length ← 0;
String.AppendString[localInsignia, "Grapevine MTP server "L];
String.AppendString[localInsignia, LocalNameDefs.ReadMSName[].name];
server[
!
FTPDefs.FTPError =>
SELECT ftpError FROM
IN FTPDefs.CommunicationError, IN FTPDefs.ProtocolError => CONTINUE;
IN FTPDefs.UnidentifiedError => CONTINUE;
ENDCASE => RESUME ; WhoIsHe => RESUME [addr.net, addr.host]];
PolicyDefs.EndOperation[MTP];
END;
Filter: PROCEDURE [from: STRING, purpose: FTPDefs.Purpose] =
BEGIN
IF NOT PolicyDefs.CheckOperation[MTP] THEN
BEGIN
LogDefs.WriteLogEntry["Rejected MTP connection"L];
ERROR FTPDefs.RejectThisConnection["Server full"L];
END;
END;
-- Initialization --
InitMTPServer1: PUBLIC PROCEDURE =
BEGIN
FTPDefs.FTPInitialize[];
FTPDefs.FTPCatchUnidentifiedErrors[FALSE];
ForwardRestart[];
Process.Detach[FORK ForwardMain[]];
END;
InitMTPServer2: PUBLIC PROCEDURE =
BEGIN
[] ← FTPDefs.FTPCreateListener[
--purpose-- mail,
--DL kludge-- @myDLPrimitives,
--mail system-- @myMailPrimitives,
--comm system-- FTPDefs.PupCommunicationPrimitives[],
--backstop-- @Backstop,
--backstopData-- 0,
--filter-- Filter];
END;
END.