singrdk/base/Applications/EmailServer/SmtpAgentNoNet/SmtpAgentNoNet.sg

1005 lines
36 KiB
Plaintext

///////////////////////////////////////////////////////////////////////////////
//
// Microsoft Research Singularity
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Note: SMTP Server.
//
//#define SMTPAGENT_VERBOSE
using Microsoft.Singularity.Applications;
using Microsoft.Singularity.Channels;
using Microsoft.Singularity.Diagnostics.Contracts;
using Microsoft.Singularity.Endpoint;
using Microsoft.Singularity.Directory;
using Microsoft.Singularity.V1.Services;
using Microsoft.Singularity.Io;
using Microsoft.Singularity.Configuration;
using Microsoft.Contracts;
using Microsoft.SingSharp.Reflection;
using System;
using System.IO;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Diagnostics;
using Microsoft.Singularity.Email.Contracts;
[assembly: Transform(typeof(ApplicationResourceTransform))]
namespace Microsoft.Singularity.Email_NoNet
{
[ConsoleCategory(HelpMessage="SMTP Mail Transfer Agent", DefaultAction=true)]
internal class Parameters
{
[InputEndpoint("data")]
public readonly TRef<UnicodePipeContract.Exp:READY> Stdin;
[OutputEndpoint("data")]
public readonly TRef<UnicodePipeContract.Imp:READY> Stdout;
[StringParameter("cnts", Mandatory=false, Default="/init/contents.txt",
HelpMessage="Contents of emails to be sent.")]
internal string emailCorpus;
[StringParameter("stl", Mandatory=false, Default="/init/stl.txt",
HelpMessage="file containing starting loc in corpus for each conn")]
internal string startPositionsFile;
[Endpoint]
public readonly TRef<DirectoryServiceContract.Imp:Start> nsRef;
[StringParameter("mailstore service", Mandatory=false, Default="/service/mailstore",
HelpMessage="Location of mail store service.")]
internal string mailstorePath;
[LongParameter("nts", Mandatory=false, Default= 1,
HelpMessage="Number of emails to send.")]
internal long numToSend;
[LongParameter("nc", Mandatory=false, Default=1,
HelpMessage="Number of concurrent connections.")]
internal long numConn;
[StringParameter("server", Mandatory=false, Default="0.0.0.0",
HelpMessage="Service IP Address to give to clients.")]
internal string server;
reflective internal Parameters();
internal int AppMain() {
return SmtpServer.AppMain(this);
}
}
public class Buffer
{
private byte[] data;
private int size;
public static Buffer Allocate(int bytes)
{
Buffer buffer = new Buffer(bytes);
return buffer;
}
private Buffer(int bytes)
{
this.data = new byte[bytes];
this.size = 0;
}
public byte[] Data
{
get { return data; }
}
public int Size
{
get { return size; }
}
public int Prepare(int bytes)
{
if (size + bytes > data.Length) {
byte[] dst = new byte[data.Length * 2];
Array.Copy(data, dst, size);
data = dst;
}
return size;
}
public void Save(int bytes)
{
if (size + bytes + 2 <= data.Length) {
size += bytes;
data[size++] = (byte)'\r';
data[size++] = (byte)'\n';
}
else {
throw new Exception("Overflow");
}
}
public void Release()
{
data = null;
size = 0;
}
}
public class EmailClientError : ApplicationException {}
//Baically, instantiating this class
//goes through the state machine with the server
public class EmailClient
{
[Conditional("SMTPAGENT_VERBOSE")]
public static void DebugWriteLine(string format)
{
DebugStub.WriteLine(format);
}
[Conditional("SMTPAGENT_VERBOSE")]
public static void DebugWriteLine(string format, __arglist)
{
DebugStub.WriteLine(format, new ArgIterator(__arglist));
}
private static void DumpEmail(string[] email)
{
DebugStub.WriteLine("Dumping email...\n");
foreach(string line in email) {
DebugStub.WriteLine("{0}", __arglist(line));
}
}
//client side state machine
private enum ReceivedState {
RECEIVED_NONE = 0,
RECEIVED_220 = 1,
RECEIVED_250 = 2,
RECEIVED_354 = 3,
}
private enum SentState {
SENT_NONE = 0,
SENT_EHLO = 1,
SENT_NOOP = 2,
SENT_MAIL = 3,
BEGAN_RCPT = 4,
FINISHED_RCPT = 5,
BEGAN_DATA = 6,
FINISHED_DATA = 7,
};
private string[][] corpus;
private int nxtEmail;
private string[] emailContents;
private int dataIndex;
private ReceivedState rcvState;
private SentState sentState;
private int rcptIndex;
private string[] rcpts;
private static string GetCommand(String line)
{
int len = line.Length;
int i;
for (i = 0; i < len && line[i] >= '0' && line[i] <= '9'; i++);
return line.Substring(0, i);
}
[NotDelayed]
public EmailClient(String[][] corpus, int startIndex)
{
this.corpus = corpus;
this.nxtEmail = startIndex;
DebugStub.WriteLine("Email client starting up startindex {0}\n", __arglist(startIndex));
DebugStub.WriteLine("corpus length is {0}\n", __arglist(corpus.Length));
rcvState = ReceivedState.RECEIVED_NONE;
sentState = SentState.SENT_NONE;
emailContents = corpus[nxtEmail];
nxtEmail++;
if (nxtEmail == corpus.Length) {
nxtEmail = 0;
}
}
//Command from SMTP server to client
//Advances state machine
public void SendLine(string line)
{
DebugWriteLine("EmailClient.SendLine received {0}\n", __arglist(line));
String command = GetCommand(line);
switch(command) {
case "220":
if (rcvState == ReceivedState.RECEIVED_NONE) {
DebugWriteLine("EmailClient received 220 in correct state\n");
rcvState = ReceivedState.RECEIVED_220;
return;
}
DebugStub.WriteLine("EmailClient recieved 220 while in state {0}\n",
__arglist(rcvState));
DebugStub.Break();
throw new EmailClientError();
break;
case "250":
DebugWriteLine("EmailClient received 250\n");
rcvState = ReceivedState.RECEIVED_250;
break;
case "354":
DebugWriteLine("EmailClient received 354\n");
rcvState = ReceivedState.RECEIVED_354;
break;
case "501":
DebugWriteLine("Uhoh! received 501\n");
DebugStub.Break();
break;
default:
DebugStub.WriteLine("Got unexpected command {0}\n", __arglist(command));
DebugStub.Break();
break;
}
}
//Get line from client to server
public string GetLine()
{
switch(sentState) {
case SentState.SENT_NONE:
DebugWriteLine("EmailClient GetLine while in state SENT_NONE\n");
string result = "EHLO Nobody\r\n";
sentState = SentState.SENT_EHLO;
rcvState = 0;
return result;
break;
case SentState.SENT_EHLO:
if (rcvState != ReceivedState.RECEIVED_250) {
break;
}
DebugWriteLine("GetLine while in state SENT_EHLO...sending MAIL\n");
sentState = SentState.SENT_MAIL;
rcvState = 0;
rcptIndex = 0;
string from = FindFrom(emailContents).ToLower();
return "MAIL FROM: <" + from + ">\r\n";
break;
case SentState.SENT_MAIL:
DebugWriteLine("GetLine while in state SENT_MAIL...sending RCPT\n");
if (rcvState != ReceivedState.RECEIVED_250) {
break;
}
string to = FindTo("To: ", emailContents);
string cc = FindTo("Cc: ", emailContents);
string bcc = FindTo("Bcc: ", emailContents);
if (to == null) {
to = cc;
cc = null;
}
if (to == null) {
to = bcc;
bcc = null;
}
if (cc != null) {
to = to + "," + cc;
}
if (bcc != null) {
to = to + "," + bcc;
}
if (to == null || to.Length == 0) {
//Sometimes the "to" is blank with no cc or bcc because of
//an interoffice dl?
to = "mail_forward@enron.com";
#if false
DebugStub.WriteLine(":: Couldn't find receipients.");
DumpEmail(emailContents);
DebugStub.Break();
throw new EmailClientError();
#endif
}
rcpts = to.Split(new Char[]{','});
for (int i = 0; i < rcpts.Length; i++) {
rcpts[i] = rcpts[i].Trim().ToLower();
if (rcpts[i].Length == 0) {
rcpts[i] = null;
}
}
for (int i = 0; i < rcpts.Length; i++) {
for (int j = i + 1; j < rcpts.Length; j++) {
if (rcpts[i] == rcpts[j]) {
rcpts[j] = null;
}
}
}
rcvState = 0;
string rcpt = "RCPT TO: <" + rcpts[rcptIndex] + ">\r\n";
rcptIndex++;
if (rcptIndex == rcpts.Length) {
sentState = SentState.FINISHED_RCPT;
}
else {
sentState = SentState.BEGAN_RCPT;
}
return rcpt;
break;
case SentState.BEGAN_RCPT:
DebugWriteLine("GetLine while in state BEGAN_RCPT...sending RCPT\n");
if (rcvState != ReceivedState.RECEIVED_250) {
break;
}
rcpt = "RCPT TO: <" + rcpts[rcptIndex] + ">\r\n";
rcptIndex++;
rcvState = 0;
if (rcptIndex == rcpts.Length) {
sentState = SentState.FINISHED_RCPT;
}
else {
sentState = SentState.BEGAN_RCPT;
}
return rcpt;
break;
case SentState.FINISHED_RCPT:
DebugWriteLine("GetLine while in state FINISHED_RCPT...sending DATA\n");
if (rcvState != ReceivedState.RECEIVED_250) {
break;
}
string dataCommand = "DATA \r\n";
dataIndex = 0;
rcvState = 0;
sentState = SentState.BEGAN_DATA;
return dataCommand;
break;
case SentState.BEGAN_DATA:
DebugWriteLine("GetLine while in state BEGAN_DATA...sending DATA\n");
if (rcvState != ReceivedState.RECEIVED_354) {
break;
}
string data;
if (dataIndex == emailContents.Length) {
DebugWriteLine("Completed reading email...sending .\n");
data = ".";
sentState = SentState.SENT_EHLO;
rcvState = 0;
emailContents = corpus[nxtEmail];
if(emailContents == null) {
DebugStub.WriteLine("null email! nxtEmail {0}\n", __arglist(nxtEmail));
DebugStub.Break();
}
nxtEmail++;
if (nxtEmail == corpus.Length) {
nxtEmail = 0;
}
}
else {
data = emailContents[dataIndex];
if ((data.Length == 1) && data[0] == '.') {
//prepend another . to avoid false positives that this is the last line
data = "." + data;
}
dataIndex++;
//DebugWriteLine("Got {0}\n", __arglist(data));
}
return data;
break;
default:
DebugStub.WriteLine("In unexpected state {0}\n", __arglist(sentState));
DebugStub.Break();
break;
}
DebugStub.WriteLine("EmailClient GetLine in state {0} never got 250\n", __arglist(sentState));
DumpEmail(emailContents);
throw new EmailClientError();
}
public static String FindFrom(String[] lines)
{
foreach (string line in lines) {
if (line.Length == 0) {
return null; // Header ended.
}
if (line.StartsWith("From: ")) {
return line.Substring(6);
}
}
return null;
}
public static string FindTo(String prefix, String[] lines)
{
for (int i = 0; i < lines.Length; i++) {
if (lines[i].Length == 0) {
return null; // Header ended.
}
if (lines[i].StartsWith(prefix)) {
string to = lines[i++].Substring(prefix.Length);
while (lines[i].Length > 0 && (lines[i][0] == ' ' || lines[i][0] == '\t')) {
to = to + lines[i++];
}
return to;
}
}
return null;
}
}
public class ServerSession
{
private static int idCount = 0;
private int id;
private string command;
private string server;
private EmailClient emailClient;
private long emailLimit;
[Conditional("SMTPAGENT_VERBOSE")]
public static void DebugWriteLine(string format)
{
DebugStub.WriteLine(format);
}
[Conditional("SMTPAGENT_VERBOSE")]
public static void DebugWriteLine(string format, __arglist)
{
DebugStub.WriteLine(format, new ArgIterator(__arglist));
}
public ServerSession(string[][] corpus, long limit, int startIndex)
{
this.emailClient = new EmailClient(corpus, startIndex);
this.id = idCount++;
this.server = "0.0.0.0";
this.emailLimit = limit;
}
public string GetCommand(String line)
{
int i = line.IndexOf(' ');
if (i < 0) {
command = line;
return null;
}
command = line.Substring(0, i).ToUpper();
return command;
}
private enum CurrentState {
HAD_NONE = 0,
HAD_HELO = 1,
HAD_MAIL = 2,
HAD_RCPT = 3,
};
public void Loop()
{
String client;
String line;
CurrentState cstate = CurrentState.HAD_NONE;
String from = null;
String to = null;
//Open a new connection to the mailstore for each smtp connection.
//Each client connection stays allive until all emails are sent.
DirectoryServiceContract.Imp! dsImp = SmtpServer.dsEp.Acquire();
MailStoreContract.Imp! msImp;
MailStoreContract.Exp! msExp;
MailStoreContract.NewChannel(out msImp, out msExp);
ErrorCode errorCode;
if (!SdsUtils.Bind(SmtpServer.mailstorePath, dsImp, msExp, out errorCode)) {
DebugWriteLine("Failed to bind to mail store...error {0}\n",
__arglist(SdsUtils.ErrorCodeToString(errorCode)));
Console.WriteLine("Failed to bind to mail store...error {0}\n",
SdsUtils.ErrorCodeToString(errorCode));
DebugStub.Break();
SmtpServer.dsEp.Release(dsImp);
delete msImp;
return;
}
SmtpServer.dsEp.Release(dsImp);
msImp.RecvMailStoreReady();
emailClient.SendLine("220 " + server + " Singularity Simple SMTP Service Ready");
long numReceived = 0;
bool abort = false;
DateTime beginEpoch = DateTime.Now;
DateTime endEpoch;
while (!abort) {
line = emailClient.GetLine();
if (line == null) {
break;
}
GetCommand(line);
switch (command) {
case "EHLO":
client = line.Split(new Char[] {' '})[1];
Console.WriteLine(":{0}: EHLO {1}", id, client);
emailClient.SendLine("250-" + server + " greets " + client);
emailClient.SendLine("250-8BITMIME");
emailClient.SendLine("250 HELP");
from = null;
to = null;
cstate = CurrentState.HAD_HELO;
DebugWriteLine("SMTPServer: Got EHLO\n");
break;
case "HELO":
client = line.Split(new Char[] {' '})[1];
Console.WriteLine(":{0}: HELO {1}", id, client);
emailClient.SendLine("250 " + server + " greets " + client);
from = null;
to = null;
cstate = CurrentState.HAD_HELO;
DebugWriteLine("SMTPServer: Got HELO\n");
break;
case "MAIL":
if (cstate != CurrentState.HAD_HELO) {
goto default;
}
if (line.StartsWith("MAIL FROM:")) {
int pos = line.IndexOf("<") + 1;
int beg = line.LastIndexOf(':');
if (beg < pos) {
beg = pos;
}
int end = line.IndexOf('>');
from = line.Substring(beg, end - beg).ToLower();
emailClient.SendLine("250 OK");
cstate = CurrentState.HAD_MAIL;
DebugWriteLine("SMTPServer: Got MAIL from: {0}\n", __arglist(from));
}
else {
emailClient.SendLine("501 Syntax error in parameters");
}
break;
case "RCPT":
if (cstate != CurrentState.HAD_MAIL && cstate != CurrentState.HAD_RCPT) {
goto default;
}
DebugWriteLine("Got RCPT\n");
if (line.StartsWith("RCPT TO:")) {
int pos = line.IndexOf("<") + 1;
int beg = line.LastIndexOf(':');
if (beg < pos) {
beg = pos;
}
int end = line.IndexOf('>');
string address = line.Substring(beg, end - beg).ToLower();
if (SmtpServer.addresses.ContainsKey(address)) {
emailClient.SendLine("250 OK");
if (to != null) {
to = to + ";" + address;
}
else {
to = address;
}
cstate = CurrentState.HAD_RCPT;
}
else {
emailClient.SendLine("250 Accepting for forwarding.");
cstate = CurrentState.HAD_RCPT;
if (to != null) {
to = to + ";" + "mail_forward@enron.com";
}
else {
to = "mail_forward@enron.com";
}
}
DebugWriteLine("Got address to: {0}\n", __arglist(to));
}
else {
emailClient.SendLine("501 Syntax error in parameters");
}
break;
case "DATA":
if (cstate != CurrentState.HAD_RCPT) {
goto default;
}
DebugWriteLine("Got DATA\n");
emailClient.SendLine("354 Start mail input; end with <CRLF>.<CRLF>");
Buffer buffer = Buffer.Allocate(65536);
bool good = false;
for (;;) {
int off = buffer.Prepare(1024);
string buffLine = emailClient.GetLine();
int len = buffLine.Length;
Encoding.ASCII.GetBytes(buffLine, 0, len, buffer.Data, off);
if (len < 0) {
break;
}
if (len == 1 && buffer.Data[off] == '.') {
DebugWriteLine("Got final . char\n");
good = true;
break;
}
else {
DebugWriteLine("S: {0}",
__arglist(Encoding.ASCII.GetString(buffer.Data, off, len)));
buffer.Save(len);
}
}
if (good) {
bool succeeded = false;
char[] in ExHeap! addresses = Bitter.FromString(to);
byte[] in ExHeap! data
= Bitter.FromByteArray(buffer.Data, 0, buffer.Size);
// Console.WriteLine("SmtpAgaint: Email from {0} to {1}", from, to);
msImp.SendSaveMessage(addresses, data);
switch receive
{
case msImp.SaveAck():
succeeded = true;
break;
case msImp.SaveNak(error):
Console.WriteLine("SmtpAgent: Server dropped email, "+
"error={0}", error);
break;
case msImp.ChannelClosed():
break;
}
if (succeeded) {
numReceived++;
if ((numReceived % 100) == 0) {
Console.Write(".");
}
emailClient.SendLine("250 OK");
}
else {
emailClient.SendLine("554 Transaction failed");
}
}
else {
emailClient.SendLine("554 Transaction failed");
// Dump();
throw new Exception("554 Transaction failed");
}
buffer.Release();
cstate = CurrentState.HAD_HELO;
from = null;
to = null;
if (numReceived == emailLimit) {
abort = true;
}
break;
case "NOOP":
emailClient.SendLine("250 OK");
break;
case "HELP":
emailClient.SendLine("250 OK");
break;
case "RSET":
emailClient.SendLine("250 OK");
from = null;
to = null;
cstate = CurrentState.HAD_HELO;
break;
case "QUIT":
emailClient.SendLine("221 " + server + " Service closing transmission channel");
abort = true;
break;
default:
emailClient.SendLine("503 Unrecognized command [" + command + "]");
abort = true;
break;
}
}
endEpoch = DateTime.Now;
Console.WriteLine(":{0}: Session closed", id);
TimeSpan delta = endEpoch - beginEpoch;
if (delta.TotalSeconds > 0) {
Console.WriteLine(
"\nDuration(s:ms) {0:f2} emails {1} rate() {2:e3} eps",
delta.TotalSeconds,
emailLimit,
emailLimit/ delta.TotalSeconds
);
}
if (delta.TotalMilliseconds == 0) {
Console.WriteLine("Too fast! < 1 milliseconds to run\n");
}
else {
Console.WriteLine("Duration ms {0:f2} rate {1:f2} epms {2:e3} eps\n",
delta.TotalMilliseconds,
(float) ((float)emailLimit / delta.TotalMilliseconds),
emailLimit / (delta.TotalMilliseconds / 1000)
);
}
delete msImp;
}
}
public class SmtpServer
{
public static String[][] corpus;
public static int numCorpusEmails = 12754;
public static int[] startPos;
public const int _port = 25;
public static bool verbose;
public static TRef<DirectoryServiceContract.Imp:Ready> dsEp;
public static SortedList addresses;
public static string mailstorePath;
[Conditional("SMTPAGENT_VERBOSE")]
public static void DebugWriteLine(string format)
{
DebugStub.WriteLine(format);
}
[Conditional("SMTPAGENT_VERBOSE")]
public static void DebugWriteLine(string format, __arglist)
{
DebugStub.WriteLine(format, new ArgIterator(__arglist));
}
//replicating c# Array.Resize that does not appear to be available
//in singularity.
private static void Resize(ref string[] oldString, int newSize)
{
if(newSize < 0) {
DebugStub.WriteLine("Resize size < 0!!\n");
DebugStub.Break();
throw new EmailClientError();
}
string[] newString = new string[newSize];
int length;
if (oldString.Length < newSize) {
length = oldString.Length;
}
else {
length = newSize;
}
for(int i = 0; i < length; i++) {
newString[i] = oldString[i];
}
oldString = newString;
}
public static void GenerateCorpus(string path, long numberEmails)
{
DebugWriteLine("Reading test corpus at path {0}\n", __arglist(path));
FileStream fsInput = new FileStream(path, FileMode.Open, FileAccess.Read);
StreamReader reader = new StreamReader(fsInput);
corpus = new string[numCorpusEmails][];
int arrSize;
int numLines;
string line;
string[] emailContents;
//XXX currently we hard code the size of the corpus into the app
//we do this because we have generated a list of random
//starting positions for each connection that is built against
//the corpus...
for(int i = 0; i < numCorpusEmails; i++) {
arrSize = 1024;
emailContents = new string[arrSize];
numLines = 0;
while((line = reader.ReadLine()) != null) {
if (line.Equals("DEADBEEFDEADBEEFDEADBEEF")) {
break;
}
if (numLines == arrSize) {
arrSize = arrSize * 2;
Resize(ref emailContents, arrSize);
}
emailContents[numLines] = line;
numLines++;
}
if(line == null) {
DebugStub.WriteLine("line null!\n");
if (numLines == 0 || numLines == 1) {
DebugStub.WriteLine("numlines {0}\n", __arglist(numLines));
DebugStub.Break();
}
}
Resize(ref emailContents, numLines);
corpus[i] = emailContents;
#if false
DebugStub.WriteLine("Parsed email {0} contents:\n", __arglist(i));
foreach (string newline in emailContents) {
DebugStub.WriteLine("line {0}\n", __arglist(newline));
}
#endif
}
reader.Close();
DebugStub.WriteLine("Generate corpus complete\n");
}
private static int StringToInt(string line)
{
int result = 0;
for (int i = 0; i < line.Length && line[i] >= '0' && line[i] <= '9'; i++) {
result = result * 10 + (line[i] - '0');
}
return result;
}
public static void GenerateStartPositions(string path, long numConn)
{
DebugWriteLine("Reading test corpus at path {0}\n", __arglist(path));
startPos = new int[100];
FileStream fsInput = new FileStream(path, FileMode.Open, FileAccess.Read);
StreamReader reader = new StreamReader(fsInput);
int cnt = 0;
string line;
while((line = reader.ReadLine()) != null) {
startPos[cnt] = StringToInt(line);
if (startPos[cnt] < 0) {
DebugStub.Break();
}
cnt++;
}
}
internal static int AppMain(Parameters! config)
{
string server = config.server;
if (server[0] >= '0' && server[0] <= '9') {
server = "[" + server + "]";
}
if (config.numConn < 1 || config.numConn > 100) {
Console.WriteLine("numConn must be between [1...100]\n");
return -1;
}
// Connect to the MailStore.
Console.WriteLine("SmtpAgent: Connecting to MailStore.");
DirectoryServiceContract.Imp! dsImp = config.nsRef.Acquire();
dsImp.RecvSuccess();
MailStoreContract.Imp! msImp;
MailStoreContract.Exp! msExp;
MailStoreContract.NewChannel(out msImp, out msExp);
mailstorePath = config.mailstorePath;
ErrorCode error;
if (!SdsUtils.Bind(config.mailstorePath, dsImp, msExp, out error)) {
DebugStub.WriteLine("Failed to bind to mail store...error {0}\n",
__arglist(SdsUtils.ErrorCodeToString(error)));
Console.WriteLine("Failed to bind to mail store...error {0}\n",
SdsUtils.ErrorCodeToString(error));
delete dsImp;
delete msImp;
return -1;
}
msImp.RecvMailStoreReady();
// Retrieve vaild address list.
char[] in ExHeap! buffer;
msImp.SendGetAddressList();
msImp.RecvGetAck(out buffer);
addresses = ReadUniqueList(Bitter.ToString(buffer));
delete buffer;
delete msImp;
// Save the endpoint.
dsEp = new TRef<DirectoryServiceContract.Imp:Ready>(dsImp);
GenerateCorpus(config.emailCorpus, config.numToSend);
GenerateStartPositions(config.startPositionsFile, config.numConn);
Console.WriteLine("SmtpAgent: Spawning {0} threads sending {1} emails per connection",
config.numConn, config.numToSend);
Thread[] workers = new Thread[config.numConn];
ServerSession[] conns = new ServerSession[config.numConn];
for(int i = 0; i < config.numConn; i++) {
conns[i] = new ServerSession(corpus, config.numToSend, startPos[i]);
workers[i] = new Thread(conns[i].Loop);
workers[i].Start();
}
for(int i = 0; i < config.numConn; i++) {
workers[i].Join();
}
// Close the Store engine.
dsImp = dsEp.Acquire();
if (dsImp != null) {
delete dsImp;
}
return 0;
}
public static string PopNextAddress(ref string content)
{
if (content.Length == 0) {
return null;
}
int i = content.IndexOf(';');
string ret = (i > 0) ? content.Substring(0, i) : content;
content = (i > 0) ? content.Substring(i + 1) : "";
return ret;
}
public static SortedList ReadUniqueList(string content)
{
SortedList list = new SortedList();
string address;
while ((address = PopNextAddress(ref content)) != null) {
if (!list.ContainsKey(address)) {
list.Add(address, address);
}
}
return list;
}
}
}