282 lines
11 KiB
Plaintext
282 lines
11 KiB
Plaintext
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Microsoft Research Singularity
|
||
|
//
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
//
|
||
|
// File: TcpConnectionExpThread.sg
|
||
|
// Note: Provider-side helper for the IP Channel Contract
|
||
|
//
|
||
|
|
||
|
using System.Threading;
|
||
|
using System.Net.IP;
|
||
|
using Microsoft.SingSharp;
|
||
|
using Microsoft.Singularity;
|
||
|
using Microsoft.Singularity.Channels;
|
||
|
using NetStack.Contracts;
|
||
|
using NetStack.Runtime;
|
||
|
using System.Collections;
|
||
|
using System;
|
||
|
|
||
|
namespace NetStack.Channels.Private
|
||
|
{
|
||
|
public class TcpConnectionExpThread
|
||
|
{
|
||
|
private TRef<TcpConnectionContract.Exp:ReadyState>! chanEP;
|
||
|
private TcpSession! session;
|
||
|
|
||
|
public void Run()
|
||
|
{
|
||
|
try {
|
||
|
RunInternal();
|
||
|
}
|
||
|
finally {
|
||
|
// This breaks the
|
||
|
// NetStack's abstraction - we should be calling
|
||
|
// a new Core method to recycle the session
|
||
|
TcpSessionPool.Recycle(session);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void RunInternal()
|
||
|
{
|
||
|
TcpConnectionContract.Exp! ep = chanEP.Acquire();
|
||
|
|
||
|
while(true) {
|
||
|
switch receive {
|
||
|
//
|
||
|
// ======================= Start state =======================
|
||
|
//
|
||
|
case ep.Connect(uint dstIP, ushort dstPort) :
|
||
|
{
|
||
|
TcpError error;
|
||
|
bool success = session.Connect(new IPv4(dstIP), dstPort, out error);
|
||
|
|
||
|
if (success)
|
||
|
{ ep.SendOK(); }
|
||
|
else
|
||
|
{ ep.SendCouldNotConnect(error); }
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case ep.BindLocalEndPoint(uint localIP, ushort localPort) :
|
||
|
{
|
||
|
bool success = session.BindLocalEndPoint(new IPv4(localIP), localPort);
|
||
|
|
||
|
if (success)
|
||
|
{ ep.SendOK(); }
|
||
|
else
|
||
|
{ ep.SendInvalidEndPoint(); }
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
//
|
||
|
// ======================= Bound state =======================
|
||
|
//
|
||
|
|
||
|
case ep.Listen(int backlog) :
|
||
|
{
|
||
|
bool success = session.Listen(backlog);
|
||
|
|
||
|
if (success)
|
||
|
{ ep.SendOK(); }
|
||
|
else
|
||
|
{ ep.SendCouldNotListen(); }
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
//
|
||
|
// ======================= Listening state =======================
|
||
|
//
|
||
|
|
||
|
case ep.IsSessionAvailable() :
|
||
|
{ ep.SendSessionIsAvailable(session.GetNumWaitingListenSessions() > 0); }
|
||
|
break;
|
||
|
|
||
|
case ep.Accept(TcpConnectionContract.Exp:PreConnected! newEP) :
|
||
|
{
|
||
|
TcpSession newSession = (TcpSession)session.Accept();
|
||
|
|
||
|
if (newSession != null) {
|
||
|
// Transition the newEP to the Connected state
|
||
|
newEP.SendReady();
|
||
|
// Wrap it up in a new thread
|
||
|
TcpConnectionExpThread newConnection = new TcpConnectionExpThread(newEP, newSession);
|
||
|
newConnection.Start();
|
||
|
ep.SendOK();
|
||
|
}
|
||
|
else {
|
||
|
// This is not expected; Accept() is supposed
|
||
|
// to block until there is a new, established
|
||
|
// connection.
|
||
|
delete newEP;
|
||
|
throw new Exception("Unexpected null return value from TcpSession.Accept");
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
//
|
||
|
// ======================= Connected state =======================
|
||
|
//
|
||
|
|
||
|
case ep.Read() :
|
||
|
{
|
||
|
byte[] in ExHeap data = session.ReadData();
|
||
|
|
||
|
if (data == null) {
|
||
|
if (ep.InState(TcpConnectionContract.ReadResult.Value)) {
|
||
|
if (session.ValidForWrite) {
|
||
|
ep.SendNoMoreData(); // -> SendOnly
|
||
|
}
|
||
|
else {
|
||
|
ep.SendConnectionClosed(); // -> Zombie
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// Must be in the ROReadResult state. Here, there is
|
||
|
// no ConnectionClosed message.
|
||
|
ep.SendNoMoreData(); // -> Zombie
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// Note the unfortunate data copy.
|
||
|
ep.SendData(data);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case ep.PollRead(int timeout) :
|
||
|
{
|
||
|
byte[] in ExHeap data = session.PollData(timeout);
|
||
|
|
||
|
if (data == null) {
|
||
|
if (ep.InState(TcpConnectionContract.PollReadResult.Value)) {
|
||
|
if (session.ValidForRead) {
|
||
|
ep.SendNoData(); // Just no data for now. -> Connected
|
||
|
}
|
||
|
else if (session.ValidForWrite) {
|
||
|
ep.SendNoMoreData(); // Can't read anymore, but can write -> SendOnly
|
||
|
}
|
||
|
else {
|
||
|
ep.SendConnectionClosed(); // Can't read or write -> Zombie
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// Must be in the ROPollReadResult state. Here, there is
|
||
|
// no ConnectionClised message.
|
||
|
if (session.ValidForRead) {
|
||
|
ep.SendNoData(); // -> ReceiveOnly
|
||
|
}
|
||
|
else {
|
||
|
ep.SendNoMoreData(); // -> Zombie
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
ep.SendData(data);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case ep.Write(byte[]! in ExHeap data) :
|
||
|
{
|
||
|
if (!session.ValidForWrite) {
|
||
|
delete data;
|
||
|
ep.SendCantSend(); // -> Zombie
|
||
|
}
|
||
|
else {
|
||
|
int bytesToWrite = data.Length;
|
||
|
int written = session.WriteData(data);
|
||
|
|
||
|
if (written == -1) {
|
||
|
// A -1 return value indicates the connection has been closed
|
||
|
// underneath us while we were trying to write.
|
||
|
ep.SendCantSend();
|
||
|
}
|
||
|
else if (written != bytesToWrite) {
|
||
|
// This is unexpected; the current implementation always
|
||
|
// blocks and writes as much data as is provided.
|
||
|
throw new Exception("Unexpected partial write in TcpSession.WriteData");
|
||
|
}
|
||
|
else {
|
||
|
ep.SendOK();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case ep.IsDataAvailable() :
|
||
|
{ ep.SendDataIsAvailable(session.IsDataAvailable()); }
|
||
|
break;
|
||
|
|
||
|
case ep.DoneSending() :
|
||
|
{ session.DoneSending(); }
|
||
|
break;
|
||
|
|
||
|
case ep.DoneReceiving() :
|
||
|
{ session.DoneReceiving(); }
|
||
|
break;
|
||
|
|
||
|
case ep.Abort() :
|
||
|
{ session.Abort(); }
|
||
|
break;
|
||
|
|
||
|
//
|
||
|
// ======================= Messages from multiple states =======================
|
||
|
//
|
||
|
|
||
|
case ep.GetLocalAddress() :
|
||
|
{ ep.SendIPAddress((uint)session.LocalAddress); }
|
||
|
break;
|
||
|
|
||
|
case ep.GetLocalPort() :
|
||
|
{ ep.SendPort(session.LocalPort); }
|
||
|
break;
|
||
|
|
||
|
case ep.GetRemoteAddress() :
|
||
|
{ ep.SendIPAddress((uint)session.RemoteAddress); }
|
||
|
break;
|
||
|
|
||
|
case ep.GetRemotePort() :
|
||
|
{ ep.SendPort(session.RemotePort); }
|
||
|
break;
|
||
|
|
||
|
case ep.Close() :
|
||
|
{ session.Close(); }
|
||
|
break;
|
||
|
|
||
|
case unsatisfiable :
|
||
|
session.Dispose();
|
||
|
delete ep;
|
||
|
// Exit this thread
|
||
|
return;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
public TcpConnectionExpThread([Claims]TcpConnectionContract.Exp:ReadyState! ep)
|
||
|
{
|
||
|
chanEP = new TRef<TcpConnectionContract.Exp:ReadyState>(ep);
|
||
|
session = (!)((TcpSession)Core.Instance().CreateSession("TCP"));
|
||
|
}
|
||
|
|
||
|
private TcpConnectionExpThread([Claims]TcpConnectionContract.Exp:Connected! ep, TcpSession! newSession)
|
||
|
{
|
||
|
chanEP = new TRef<TcpConnectionContract.Exp:ReadyState>(ep);
|
||
|
session = (!)newSession;
|
||
|
}
|
||
|
|
||
|
public void Start()
|
||
|
{
|
||
|
#if THREAD_POOL
|
||
|
Core.Instance().ThreadPool.QueueUserWorkItem(new ThreadStart(Run));
|
||
|
#else
|
||
|
Thread newThread = new Thread(new ThreadStart(Run));
|
||
|
newThread.Start();
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
}
|