singrdk/base/Libraries/System.Net/Sockets/TcpSocket.cs

936 lines
33 KiB
C#

////////////////////////////////////////////////////////////////////////////////
//
// Microsoft Research Singularity
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Note:
// This is a port of the System.Net.Sockets.Socket class to Singularity.
// Currently, it only supports TCP sockets.
//
using Microsoft.SingSharp;
using Microsoft.SingSharp.Runtime;
using Microsoft.Singularity;
using Microsoft.Singularity.Channels;
using Microsoft.Singularity.Directory;
using NetStack.Contracts;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Contracts;
namespace System.Net.Sockets
{
internal class TcpSocket : InternalSocket
{
// This can be NULL if the channel has been discarded.
private TRef<TcpConnectionContract.Imp:ReadyState> m_Conn;
private IPEndPoint m_LocalEndPoint; // Strictly a cache; may be null
private IPEndPoint m_RemoteEndPoint; // Strictly a cache; may be null
// Data arrives from the netstack in discrete packets; in order to
// adapt this to the socket interface, we keep a buffer of data
// that has been read from the NetStack but not passed off to
// the user.
private TrackedBytesBuffer m_DataBuffer;
// NOTE: not sure what error to specify when an operation
// is not allowed by the current socket state; this is the value
// I use. Change it if it's inappropriate
private const int BAD_SOCKET_STATE_ERR = (int)SocketErrors.WSAEOPNOTSUPP;
// ================== Private Methods ==================
// Returns true if the channel is in one of the three connected states
// (connected, ReceiveOnly, SendOnly)
private bool IsConnected(TcpConnectionContract.Imp! conn)
{
return (conn.InState(TcpConnectionContract.Connected.Value) ||
conn.InState(TcpConnectionContract.ReceiveOnly.Value) ||
conn.InState(TcpConnectionContract.SendOnly.Value));
}
#if !SPL
// Returns true if the channel allows writing
private bool CanWrite(TcpConnectionContract.Imp! conn) {
if (conn.InState(TcpConnectionContract.Connected.Value)) {
return true;
}
else if(conn.InState(TcpConnectionContract.SendOnly.Value)) {
return true;
}
if (conn.InState(TcpConnectionContract.ReceiveOnly.Value)) {
// DebugStub.Break();
return false;
}
if (conn.InState(TcpConnectionContract.ReadyState.Value)) {
DebugStub.Break();
return false;
}
if (conn.InState(TcpConnectionContract.Listening.Value)) {
DebugStub.Break();
return false;
}
return false;
}
#else
// Returns true if the channel allows writing
private bool CanWrite(TcpConnectionContract.Imp! conn)
{
return (conn.InState(TcpConnectionContract.Connected.Value) ||
conn.InState(TcpConnectionContract.SendOnly.Value));
}
#endif
// Returns true if the channel allows reading
private bool CanRead(TcpConnectionContract.Imp! conn)
{
return (conn.InState(TcpConnectionContract.Connected.Value) ||
conn.InState(TcpConnectionContract.ReceiveOnly.Value));
}
// Returns the channel or throws a BAD_SOCKET_STATE_ERR error
private TcpConnectionContract.Imp! GetChannelOrThrow()
{
if (m_Conn == null) {
throw new SocketException(BAD_SOCKET_STATE_ERR);
}
else {
return m_Conn.Acquire();
}
}
// Release the previously-acquired channel
private void ReleaseChannel([Claims]TcpConnectionContract.Imp! conn)
{
m_Conn.Release(conn);
}
//
// Returns false if we are in an inappropriate state to retrieve the
// local endpoint
//
private bool GetLocalEndPoint(out uint localIP, out ushort localPort)
{
localIP = 0;
localPort = 0;
TcpConnectionContract.Imp! conn = GetChannelOrThrow();
try {
// We need to be connected or listening to retrieve the local
// endpoint
if ((!conn.InState(TcpConnectionContract.Listening.Value)) &&
(!IsConnected(conn)))
{
return false;
}
conn.SendGetLocalAddress();
conn.RecvIPAddress(out localIP);
conn.SendGetLocalPort();
conn.RecvPort(out localPort);
return true;
}
finally {
ReleaseChannel(conn);
}
}
//
// Returns false if we are in an inappropriate state to retrieve the
// remote endpoint
//
private bool GetRemoteEndPoint(out uint localIP, out ushort localPort)
{
localIP = 0;
localPort = 0;
TcpConnectionContract.Imp! conn = GetChannelOrThrow();
try {
// Need to be connected for the remote endpoint to be defined
if (!IsConnected(conn)) {
return false;
}
conn.SendGetRemoteAddress();
conn.RecvIPAddress(out localIP);
conn.SendGetRemotePort();
conn.RecvPort(out localPort);
return true;
}
finally {
ReleaseChannel(conn);
}
}
// Reads as much as possible without blocking, tack it onto dataBuffer.
// We will block for a MAXIMUM of the time specified by "microSeconds".
// Returns true if future read operations are permissible, false if
// the data stream has ended
private bool DrainDataNonBlock(TcpConnectionContract.Imp! conn,
TrackedBytesBuffer! dataBuffer,
int microSeconds)
{
bool keepTrying = true;
int timeToSleep = microSeconds;
while (keepTrying) {
keepTrying = false; // defensive
conn.SendPollRead(timeToSleep);
switch receive {
case conn.Data(byte[]! in ExHeap readData) :
// Tack this onto the data buffer. Data is consumed by AddToTail()
dataBuffer.AddToTail(readData);
// Keep trying, but don't sleep. We do this strictly to
// drain off any additional data, since currently the
// netStack hands data to us in packet-sized chunks,
// which means it may have additional data ready if you
// ask it again.
keepTrying = true;
timeToSleep = 0;
break;
case conn.NoData() :
// done
return true;
break;
case conn.NoMoreData() :
return false;
break;
case conn.ConnectionClosed() :
return false;
break;
}
}
// Should never get here
return true;
}
// Makes a round-trip to the NetStack to pull in as much data as
// possible, only blocking as long as is specified by the
// microSeconds argument (this can be zero). Reports the new
// amount of data in our internal buffer.
private int GetAvailableBytes(int microSeconds)
{
TcpConnectionContract.Imp conn = GetChannelOrThrow();
try {
if (CanRead(conn)) {
// Drain as much data from the netstack as possible
DrainDataNonBlock(conn, m_DataBuffer, microSeconds);
}
return m_DataBuffer.Count;
}
finally {
ReleaseChannel(conn);
}
}
// Performs a single blocking read to the netstack, and adds data
// to dataBuffer.
// Returns true if future read operations are permissible; false
// if the data stream has ended.
private bool BlockingRead(TcpConnectionContract.Imp! conn, TrackedBytesBuffer! dataBuffer)
{
conn.SendRead();
switch receive {
case conn.Data(byte[]! in ExHeap readData) :
// Tack this onto the data buffer. Data is consumed by AddToTail.
dataBuffer.AddToTail(readData);
return true;
break;
case conn.NoMoreData() :
return false;
break;
case conn.ConnectionClosed() :
return false;
break;
}
}
private void Initialize([Claims] TcpConnectionContract.Imp:ReadyState! conn)
{
// Stash the endpoint into our field
m_Conn = new TRef<TcpConnectionContract.Imp:ReadyState>(conn);
// Create an empty data buffer...
m_DataBuffer = new TrackedBytesBuffer();
}
[NotDelayed]
private TcpSocket([Claims] TcpConnectionContract.Imp:ReadyState! conn)
{
Initialize(conn);
}
// ================== Internal Methods ==================
[NotDelayed]
internal TcpSocket()
{
// Connect to the Tcp module
TcpContract.Imp! impConn;
TcpContract.Exp! expConn;
TcpContract.NewChannel(out impConn, out expConn);
DirectoryServiceContract.Imp! epNS = DirectoryService.NewClientEndpoint();
try {
epNS.SendBind(Bitter.FromString2(TcpContract.ModuleName), expConn);
switch receive {
case epNS.NakBind(ServiceContract.Exp:Start rejectedEP, error) :
// failure
if (rejectedEP != null) {
delete rejectedEP;
}
throw new SocketException(SocketErrors.WSASYSNOTREADY);
break;
case epNS.AckBind() :
impConn.RecvReady();
// success
break;
}
}
finally {
delete epNS;
}
try {
// Create a new Tcp connection
TcpConnectionContract.Exp! tcpExpConn;
TcpConnectionContract.Imp! tcpImpConn;
TcpConnectionContract.NewChannel(out tcpImpConn, out tcpExpConn);
impConn.SendCreateTcpSession(tcpExpConn);
impConn.RecvAck();
// Confirm that the channel is wired up
tcpImpConn.RecvReady();
Initialize(tcpImpConn);
}
finally {
delete impConn;
}
}
internal override void Dispose()
{
if (m_Conn != null) {
TcpConnectionContract.Imp conn = m_Conn.Acquire();
delete conn;
m_Conn = null;
}
}
internal override InternalSocket Accept()
{
// We need to be in the listening state
TcpConnectionContract.Imp conn = GetChannelOrThrow();
try {
if (!conn.InState(TcpConnectionContract.Listening.Value)) {
throw new SocketException(BAD_SOCKET_STATE_ERR);
}
TcpConnectionContract.Exp! newExpConn;
TcpConnectionContract.Imp! newImpConn;
// Make sure to create the new channel in the connected
// state!
TcpConnectionContract.NewChannel(out newImpConn, out newExpConn,
TcpConnectionContract.PreConnected.Value);
conn.SendAccept(newExpConn);
conn.RecvOK(); // BLOCKS
// Receive the confirmation message that the new channel
// is established
newImpConn.RecvReady();
return new TcpSocket(newImpConn);
}
finally {
ReleaseChannel(conn);
}
}
internal override void Bind(EndPoint localEP)
{
if (localEP == null) {
throw new ArgumentNullException("localEP");
}
IPEndPoint ep = localEP as IPEndPoint;
if (ep == null) {
// Illegal argument
throw new SocketException(SocketErrors.WSAEINVAL);
}
// Make sure we're in the ReadyState state
TcpConnectionContract.Imp conn = GetChannelOrThrow();
try {
if (!conn.InState(TcpConnectionContract.ReadyState.Value)) {
throw new SocketException(BAD_SOCKET_STATE_ERR);
}
conn.SendBindLocalEndPoint((uint) ep.Address.m_addr,
(ushort) ep.Port);
switch receive {
case conn.OK() :
// success!
break;
case conn.InvalidEndPoint() :
throw new SocketException(SocketErrors.WSAEADDRNOTAVAIL);
break;
}
}
finally {
ReleaseChannel(conn);
}
}
internal override void Close()
{
// NOTE: we should add support for the SO_DONTLINGER and SO_LINGER
// socket options, and then call either Close() or Abort() on our underlying
// channel. We will also need to add support for sleeping-close to the
// NetStack to support nonzero SO_LINGER values.
TcpConnectionContract.Imp conn = GetChannelOrThrow();
// Need to be listening, connected or zombied to close
if ((!conn.InState(TcpConnectionContract.ReadyState.Value)) &&
(!conn.InState(TcpConnectionContract.Listening.Value)) &&
(!IsConnected(conn)) &&
(!conn.InState(TcpConnectionContract.Zombie.Value)))
{
ReleaseChannel(conn);
throw new SocketException(BAD_SOCKET_STATE_ERR);
}
else {
conn.SendClose();
ReleaseChannel(conn);
Dispose(); // This discards the channel
}
}
internal override void Connect(EndPoint remoteEP)
{
if (remoteEP == null) {
throw new ArgumentNullException("remoteEP");
}
IPEndPoint ep = remoteEP as IPEndPoint;
if (ep == null) {
// Illegal argument
throw new SocketException(SocketErrors.WSAEINVAL);
}
TcpConnectionContract.Imp conn = GetChannelOrThrow();
if ((!conn.InState(TcpConnectionContract.ReadyState.Value)) &&
(!conn.InState(TcpConnectionContract.Bound.Value)))
{
// Need to be in the ReadyState or Bound state to issue
// a Connect
throw new SocketException(BAD_SOCKET_STATE_ERR);
}
try {
conn.SendConnect((uint)ep.Address.m_addr,
unchecked((ushort)ep.Port));
switch receive {
case conn.CouldNotConnect(TcpError error) :
string message = TcpException.GetMessageForTcpError(error);
throw new TcpException(error, String.Format("A TCP connection could not be established to endpoint {0}. {1}",
remoteEP.ToString(), message));
case conn.OK() :
// Success
break;
}
}
finally {
ReleaseChannel(conn);
}
}
//
//public object GetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName)
//{
// // TODO
// throw new NotSupportedException();
//}
//
//public void GetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, byte[] optionValue)
//{
// // TODO
// throw new NotSupportedException();
//}
//
//public byte[] GetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, int optionLength)
//{
// // TODO
// throw new NotSupportedException();
//}
//
internal override void Listen(int backlog)
{
// We have to be in the Bound state to Listen
TcpConnectionContract.Imp conn = GetChannelOrThrow();
if (!conn.InState(TcpConnectionContract.Bound.Value)) {
throw new SocketException(BAD_SOCKET_STATE_ERR);
}
try {
conn.SendListen(backlog);
switch receive {
case conn.CouldNotListen() :
throw new SocketException(SocketErrors.SocketError);
break;
case conn.OK() :
// Success
break;
}
}
finally {
ReleaseChannel(conn);
}
}
internal override bool Poll(int microSeconds, SelectMode mode)
{
switch (mode) {
//
// For SelectRead, return true iff:
// Listen has been called and a connection is pending, OR
// Data is available for reading, OR
// The connection has been closed, reset, or terminated
//
case SelectMode.SelectRead :
{
TcpConnectionContract.Imp conn = GetChannelOrThrow();
bool isClosed, sessionsWaiting = false;
try {
// First check if our channel is in the Closed state
isClosed = conn.InState(TcpConnectionContract.Closed.Value);
// Now check if there are sessions available for Accept()
if (conn.InState(TcpConnectionContract.Listening.Value)) {
conn.SendIsSessionAvailable();
conn.RecvSessionIsAvailable(out sessionsWaiting);
}
}
finally {
ReleaseChannel(conn);
}
int dataAvailable = 0;
try {
dataAvailable = GetAvailableBytes(microSeconds);
}
catch (Exception) {
// Swallow any exceptions here
}
return ((dataAvailable > 0) || isClosed || sessionsWaiting);
}
break;
//
// For SelectWrite, return true iff:
// An asynchronous Connect has succeeded, OR
// Data can be sent
//
case SelectMode.SelectWrite :
{
bool isConnected = false;
try {
isConnected = Connected;
}
catch (Exception) {
// swallow everything
}
// We don't currently support async operations,
// so just indicate true if we are connected (meaning
// we can send data)
return isConnected;
}
break;
//
// For SelectError, return true iff:
// An asynchronous Connect has failed
// OutOfBandInline is not set, and out-of-band data is available.
//
case SelectMode.SelectError :
// We don't support any of the requisite operations
return false;
break;
}
// never get here
Debug.Assert(false);
return false;
}
internal override int Receive(byte[]! buffer,
int offset,
int size,
SocketFlags socketFlags)
{
if ((socketFlags != SocketFlags.None) &&
(socketFlags != SocketFlags.Peek))
{
// Invalid flags for this operation
throw new SocketException(SocketErrors.WSAEINVAL);
}
if (buffer == null) {
throw new ArgumentNullException("buffer");
}
if (offset < 0 || offset > buffer.Length) {
throw new ArgumentOutOfRangeException("offset");
}
if (size < 0 || size > buffer.Length - offset) {
throw new ArgumentOutOfRangeException("size");
}
bool fPeek = ((int)socketFlags & (int)SocketFlags.Peek) != 0;
// If we can satisfy the read request from our internal buffer, just
// do that and we're done.
if (size <= m_DataBuffer.Count) {
int copied = m_DataBuffer.CopyFromHead(buffer, offset, size, fPeek);
Debug.Assert(copied == size);
return size;
}
TcpConnectionContract.Imp conn = GetChannelOrThrow();
try {
int dataCount = m_DataBuffer.Count;
if (!CanRead(conn)) {
// The channel doesn't support reading anymore.
if (dataCount > 0) {
// There's some residual data in our buffer.
m_DataBuffer.CopyFromHead(buffer, offset, dataCount, fPeek);
}
return dataCount;
}
// We're in the connected state, but we don't have as much
// data as the reader would like already available. Take
// this opportunity to drain off as much as possible from
// the Netstack below us.
bool canReadAgain = DrainDataNonBlock(conn, m_DataBuffer, 0);
dataCount = m_DataBuffer.Count;
if (!canReadAgain) {
// The connection went away!
if (dataCount == 0) {
// At the beginning of this call, the socket was
// readable, but now it's not. So the data
// stream has just ended. Signal this to the caller
// with a zero-size completion.
// If the caller tries to Receive() again, they'll
// get an exception.
return 0;
}
// else return some data below
}
if (dataCount > 0) {
// Return as much as possible, even if the data stream
// has ended
int countToReturn = (size > dataCount) ? dataCount : size;
m_DataBuffer.CopyFromHead(buffer, offset, countToReturn, fPeek);
return countToReturn;
}
else {
// There's no data at all, but we can still read. Block.
canReadAgain = BlockingRead(conn, m_DataBuffer);
dataCount = m_DataBuffer.Count;
if (!canReadAgain) {
if (dataCount == 0)
// See comment above
{ return 0; }
// else return some data below
}
// We did a blocking read and were told that future reads
// are permissible. It wouldn't make sense to not have
// received some data at this point.
Debug.Assert(dataCount > 0);
// Return as much as possible.
int countToReturn = (size > dataCount) ? dataCount : size;
m_DataBuffer.CopyFromHead(buffer, offset, countToReturn, fPeek);
return countToReturn;
}
}
finally {
ReleaseChannel(conn);
}
}
internal override int ReceiveFrom(byte[]! buffer,
int offset,
int size,
SocketFlags socketFlags,
ref EndPoint remoteEP)
{
int recvBytes = Receive(buffer, offset, size, socketFlags);
remoteEP = RemoteEndPoint;
return recvBytes;
}
//
//public static void Select(IList checkRead, IList checkWrite, IList checkError, int microSeconds)
//{
// // TODO
// throw new NotSupportedException();
//}
//
internal override int Send([Claims] byte[]! in ExHeap data,
SocketFlags socketFlags)
{
int byteCount = data.Length;
if (byteCount == 0) {
// This shoudn't really happen, obviously
delete data;
return 0;
}
if (socketFlags != SocketFlags.None) {
// Invalid flags for this operation
throw new SocketException(SocketErrors.WSAEINVAL);
}
TcpConnectionContract.Imp conn = GetChannelOrThrow();
try {
if (!CanWrite(conn)) {
throw new SocketException(BAD_SOCKET_STATE_ERR);
}
conn.SendWrite(data); // Give away data
switch receive {
case conn.OK() :
// Success
return byteCount;
break;
case conn.CantSend() :
// uh oh
throw new SocketException(SocketErrors.WSAENOTCONN);
break;
}
}
finally {
ReleaseChannel(conn);
}
}
internal override int Send(byte[]! buffer,
int offset,
int size,
SocketFlags socketFlags)
{
if (buffer == null) {
throw new ArgumentNullException("buffer");
}
if (offset < 0 || offset > buffer.Length) {
throw new ArgumentOutOfRangeException("offset");
}
if (size < 0 || size > buffer.Length - offset) {
throw new ArgumentOutOfRangeException("size");
}
// Copy the data into the shared heap
byte[]! in ExHeap dataToWrite = Bitter.FromByteArray(buffer, offset, size);
return Send(dataToWrite, socketFlags);
}
internal override int SendTo(byte[]! buffer,
int offset,
int size,
SocketFlags socketFlags,
EndPoint remoteEP)
{
return Send(buffer, offset, size, socketFlags);
}
//
//public void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, int optionValue)
//{
// // TODO
// throw new NotSupportedException();
//}
//
//public void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, byte[] optionValue)
//{
// // TODO
// throw new NotSupportedException();
//}
//
//public void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, bool optionValue)
//{
// SetSocketOption(optionLevel, optionName, (optionValue?1:0));
//}
//
internal override void Shutdown(SocketShutdown how)
{
TcpConnectionContract.Imp conn = GetChannelOrThrow();
try {
if ((how == SocketShutdown.Both) ||
(how == SocketShutdown.Receive))
{
if (!CanRead(conn)) {
// Can't shut down reading if it's not currently allowed
throw new SocketException(BAD_SOCKET_STATE_ERR);
}
conn.SendDoneReceiving();
}
if ((how == SocketShutdown.Both) ||
(how == SocketShutdown.Send))
{
if (!CanWrite(conn)) {
// Can't shut down writing if it's not currently allowed
throw new SocketException(BAD_SOCKET_STATE_ERR);
}
conn.SendDoneSending();
}
}
finally {
ReleaseChannel(conn);
}
}
// ================== Properties ==================
//
// NOTE: Many of these are hard-coded for now since we only support
// IPv4 Tcp streams.
//
internal override int Available {
get {
return GetAvailableBytes(0); // Don't sleep at all
}
}
internal override EndPoint LocalEndPoint {
get {
if (m_LocalEndPoint == null) {
uint ipAddr;
ushort port;
if (!GetLocalEndPoint(out ipAddr, out port)) {
return null;
}
m_LocalEndPoint = new IPEndPoint(ipAddr, port);
}
return m_LocalEndPoint;
}
}
internal override EndPoint RemoteEndPoint {
get {
if (m_RemoteEndPoint == null) {
uint ipAddr;
ushort port;
if (!GetRemoteEndPoint(out ipAddr, out port)) {
return null;
}
m_RemoteEndPoint = new IPEndPoint(ipAddr, port);
}
return m_RemoteEndPoint;
}
}
internal override bool Blocking {
get {
return true;
}
set {
if (value != true) {
// TODO: non-blocking mode!
throw new NotSupportedException();
}
}
}
internal override bool Connected
{
get {
TcpConnectionContract.Imp conn = GetChannelOrThrow();
bool connected = IsConnected(conn);
ReleaseChannel(conn);
return connected;
}
}
internal override AddressFamily AddressFamily {
get {
return AddressFamily.InterNetwork;
}
}
internal override SocketType SocketType {
get {
return SocketType.Stream;
}
}
internal override ProtocolType ProtocolType {
get {
return ProtocolType.Tcp;
}
}
}
}