2008-03-05 09:52:00 -05:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// 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;
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
// NOTE: not sure what error to specify when an operation
|
2008-03-05 09:52:00 -05:00
|
|
|
// 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;
|
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
if (conn.InState(TcpConnectionContract.ReceiveOnly.Value)) {
|
2008-03-05 09:52:00 -05:00
|
|
|
// DebugStub.Break();
|
|
|
|
return false;
|
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
if (conn.InState(TcpConnectionContract.ReadyState.Value)) {
|
2008-03-05 09:52:00 -05:00
|
|
|
DebugStub.Break();
|
|
|
|
return false;
|
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
if (conn.InState(TcpConnectionContract.Listening.Value)) {
|
2008-03-05 09:52:00 -05:00
|
|
|
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();
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
try {
|
2008-03-05 09:52:00 -05:00
|
|
|
// 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;
|
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
finally {
|
2008-03-05 09:52:00 -05:00
|
|
|
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();
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
try {
|
2008-03-05 09:52:00 -05:00
|
|
|
// Need to be connected for the remote endpoint to be defined
|
2008-11-17 18:29:00 -05:00
|
|
|
if (!IsConnected(conn)) {
|
2008-03-05 09:52:00 -05:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
conn.SendGetRemoteAddress();
|
|
|
|
conn.RecvIPAddress(out localIP);
|
|
|
|
conn.SendGetRemotePort();
|
|
|
|
conn.RecvPort(out localPort);
|
|
|
|
return true;
|
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
finally {
|
2008-03-05 09:52:00 -05:00
|
|
|
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;
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
while (keepTrying) {
|
2008-03-05 09:52:00 -05:00
|
|
|
keepTrying = false; // defensive
|
|
|
|
conn.SendPollRead(timeToSleep);
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
switch receive {
|
2008-03-05 09:52:00 -05:00
|
|
|
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();
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
try {
|
|
|
|
if (CanRead(conn)) {
|
2008-03-05 09:52:00 -05:00
|
|
|
// Drain as much data from the netstack as possible
|
|
|
|
DrainDataNonBlock(conn, m_DataBuffer, microSeconds);
|
|
|
|
}
|
|
|
|
|
|
|
|
return m_DataBuffer.Count;
|
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
finally {
|
2008-03-05 09:52:00 -05:00
|
|
|
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();
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
switch receive {
|
2008-03-05 09:52:00 -05:00
|
|
|
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();
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
try {
|
2008-03-05 09:52:00 -05:00
|
|
|
epNS.SendBind(Bitter.FromString2(TcpContract.ModuleName), expConn);
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
switch receive {
|
2008-03-05 09:52:00 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
finally {
|
2008-03-05 09:52:00 -05:00
|
|
|
delete epNS;
|
|
|
|
}
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
try {
|
2008-03-05 09:52:00 -05:00
|
|
|
// 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);
|
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
finally {
|
2008-03-05 09:52:00 -05:00
|
|
|
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();
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
try {
|
|
|
|
if (!conn.InState(TcpConnectionContract.Listening.Value)) {
|
2008-03-05 09:52:00 -05:00
|
|
|
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);
|
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
finally {
|
2008-03-05 09:52:00 -05:00
|
|
|
ReleaseChannel(conn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal override void Bind(EndPoint localEP)
|
|
|
|
{
|
2008-11-17 18:29:00 -05:00
|
|
|
if (localEP == null) {
|
2008-03-05 09:52:00 -05:00
|
|
|
throw new ArgumentNullException("localEP");
|
|
|
|
}
|
|
|
|
|
|
|
|
IPEndPoint ep = localEP as IPEndPoint;
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
if (ep == null) {
|
2008-03-05 09:52:00 -05:00
|
|
|
// Illegal argument
|
|
|
|
throw new SocketException(SocketErrors.WSAEINVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure we're in the ReadyState state
|
|
|
|
TcpConnectionContract.Imp conn = GetChannelOrThrow();
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
try {
|
|
|
|
if (!conn.InState(TcpConnectionContract.ReadyState.Value)) {
|
2008-03-05 09:52:00 -05:00
|
|
|
throw new SocketException(BAD_SOCKET_STATE_ERR);
|
|
|
|
}
|
|
|
|
|
|
|
|
conn.SendBindLocalEndPoint((uint) ep.Address.m_addr,
|
|
|
|
(ushort) ep.Port);
|
2008-11-17 18:29:00 -05:00
|
|
|
switch receive {
|
2008-03-05 09:52:00 -05:00
|
|
|
case conn.OK() :
|
|
|
|
// success!
|
|
|
|
break;
|
|
|
|
case conn.InvalidEndPoint() :
|
|
|
|
throw new SocketException(SocketErrors.WSAEADDRNOTAVAIL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
finally {
|
2008-03-05 09:52:00 -05:00
|
|
|
ReleaseChannel(conn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal override void Close()
|
|
|
|
{
|
2008-11-17 18:29:00 -05:00
|
|
|
// NOTE: we should add support for the SO_DONTLINGER and SO_LINGER
|
2008-03-05 09:52:00 -05:00
|
|
|
// 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);
|
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
else {
|
2008-03-05 09:52:00 -05:00
|
|
|
conn.SendClose();
|
|
|
|
ReleaseChannel(conn);
|
|
|
|
Dispose(); // This discards the channel
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal override void Connect(EndPoint remoteEP)
|
|
|
|
{
|
2008-11-17 18:29:00 -05:00
|
|
|
if (remoteEP == null) {
|
2008-03-05 09:52:00 -05:00
|
|
|
throw new ArgumentNullException("remoteEP");
|
|
|
|
}
|
|
|
|
|
|
|
|
IPEndPoint ep = remoteEP as IPEndPoint;
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
if (ep == null) {
|
2008-03-05 09:52:00 -05:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
try {
|
2008-03-05 09:52:00 -05:00
|
|
|
conn.SendConnect((uint)ep.Address.m_addr,
|
|
|
|
unchecked((ushort)ep.Port));
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
switch receive {
|
2008-03-05 09:52:00 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
finally {
|
2008-03-05 09:52:00 -05:00
|
|
|
ReleaseChannel(conn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
//
|
|
|
|
//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();
|
|
|
|
//}
|
|
|
|
//
|
2008-03-05 09:52:00 -05:00
|
|
|
|
|
|
|
internal override void Listen(int backlog)
|
|
|
|
{
|
|
|
|
// We have to be in the Bound state to Listen
|
|
|
|
TcpConnectionContract.Imp conn = GetChannelOrThrow();
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
if (!conn.InState(TcpConnectionContract.Bound.Value)) {
|
2008-03-05 09:52:00 -05:00
|
|
|
throw new SocketException(BAD_SOCKET_STATE_ERR);
|
|
|
|
}
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
try {
|
2008-03-05 09:52:00 -05:00
|
|
|
conn.SendListen(backlog);
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
switch receive {
|
2008-03-05 09:52:00 -05:00
|
|
|
case conn.CouldNotListen() :
|
|
|
|
throw new SocketException(SocketErrors.SocketError);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case conn.OK() :
|
|
|
|
// Success
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
finally {
|
2008-03-05 09:52:00 -05:00
|
|
|
ReleaseChannel(conn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal override bool Poll(int microSeconds, SelectMode mode)
|
|
|
|
{
|
2008-11-17 18:29:00 -05:00
|
|
|
switch (mode) {
|
2008-03-05 09:52:00 -05:00
|
|
|
//
|
|
|
|
// 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;
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
try {
|
|
|
|
// First check if our channel is in the Closed state
|
2008-03-05 09:52:00 -05:00
|
|
|
isClosed = conn.InState(TcpConnectionContract.Closed.Value);
|
|
|
|
|
|
|
|
// Now check if there are sessions available for Accept()
|
2008-11-17 18:29:00 -05:00
|
|
|
if (conn.InState(TcpConnectionContract.Listening.Value)) {
|
2008-03-05 09:52:00 -05:00
|
|
|
conn.SendIsSessionAvailable();
|
|
|
|
conn.RecvSessionIsAvailable(out sessionsWaiting);
|
|
|
|
}
|
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
finally {
|
2008-03-05 09:52:00 -05:00
|
|
|
ReleaseChannel(conn);
|
|
|
|
}
|
|
|
|
|
|
|
|
int dataAvailable = 0;
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
try {
|
2008-03-05 09:52:00 -05:00
|
|
|
dataAvailable = GetAvailableBytes(microSeconds);
|
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
catch (Exception) {
|
2008-03-05 09:52:00 -05:00
|
|
|
// 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;
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
try {
|
2008-03-05 09:52:00 -05:00
|
|
|
isConnected = Connected;
|
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
catch (Exception) {
|
2008-03-05 09:52:00 -05:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
if (buffer == null) {
|
2008-03-05 09:52:00 -05:00
|
|
|
throw new ArgumentNullException("buffer");
|
|
|
|
}
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
if (offset < 0 || offset > buffer.Length) {
|
2008-03-05 09:52:00 -05:00
|
|
|
throw new ArgumentOutOfRangeException("offset");
|
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
if (size < 0 || size > buffer.Length - offset) {
|
2008-03-05 09:52:00 -05:00
|
|
|
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.
|
2008-11-17 18:29:00 -05:00
|
|
|
if (size <= m_DataBuffer.Count) {
|
2008-03-05 09:52:00 -05:00
|
|
|
int copied = m_DataBuffer.CopyFromHead(buffer, offset, size, fPeek);
|
|
|
|
Debug.Assert(copied == size);
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
TcpConnectionContract.Imp conn = GetChannelOrThrow();
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
try {
|
2008-03-05 09:52:00 -05:00
|
|
|
int dataCount = m_DataBuffer.Count;
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
if (!CanRead(conn)) {
|
2008-03-05 09:52:00 -05:00
|
|
|
// The channel doesn't support reading anymore.
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
if (dataCount > 0) {
|
2008-03-05 09:52:00 -05:00
|
|
|
// 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;
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
if (!canReadAgain) {
|
2008-03-05 09:52:00 -05:00
|
|
|
// The connection went away!
|
2008-11-17 18:29:00 -05:00
|
|
|
if (dataCount == 0) {
|
2008-03-05 09:52:00 -05:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
if (dataCount > 0) {
|
2008-03-05 09:52:00 -05:00
|
|
|
// 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;
|
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
else {
|
2008-03-05 09:52:00 -05:00
|
|
|
// There's no data at all, but we can still read. Block.
|
|
|
|
canReadAgain = BlockingRead(conn, m_DataBuffer);
|
|
|
|
dataCount = m_DataBuffer.Count;
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
if (!canReadAgain) {
|
2008-03-05 09:52:00 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
finally {
|
2008-03-05 09:52:00 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
//
|
|
|
|
//public static void Select(IList checkRead, IList checkWrite, IList checkError, int microSeconds)
|
|
|
|
//{
|
|
|
|
// // TODO
|
|
|
|
// throw new NotSupportedException();
|
|
|
|
//}
|
|
|
|
//
|
2008-03-05 09:52:00 -05:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
if (socketFlags != SocketFlags.None) {
|
2008-03-05 09:52:00 -05:00
|
|
|
// Invalid flags for this operation
|
|
|
|
throw new SocketException(SocketErrors.WSAEINVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
TcpConnectionContract.Imp conn = GetChannelOrThrow();
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
try {
|
|
|
|
if (!CanWrite(conn)) {
|
2008-03-05 09:52:00 -05:00
|
|
|
throw new SocketException(BAD_SOCKET_STATE_ERR);
|
|
|
|
}
|
|
|
|
|
|
|
|
conn.SendWrite(data); // Give away data
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
switch receive {
|
2008-03-05 09:52:00 -05:00
|
|
|
case conn.OK() :
|
|
|
|
// Success
|
|
|
|
return byteCount;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case conn.CantSend() :
|
|
|
|
// uh oh
|
|
|
|
throw new SocketException(SocketErrors.WSAENOTCONN);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
finally {
|
2008-03-05 09:52:00 -05:00
|
|
|
ReleaseChannel(conn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal override int Send(byte[]! buffer,
|
|
|
|
int offset,
|
|
|
|
int size,
|
|
|
|
SocketFlags socketFlags)
|
|
|
|
{
|
2008-11-17 18:29:00 -05:00
|
|
|
if (buffer == null) {
|
2008-03-05 09:52:00 -05:00
|
|
|
throw new ArgumentNullException("buffer");
|
|
|
|
}
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
if (offset < 0 || offset > buffer.Length) {
|
2008-03-05 09:52:00 -05:00
|
|
|
throw new ArgumentOutOfRangeException("offset");
|
|
|
|
}
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
if (size < 0 || size > buffer.Length - offset) {
|
2008-03-05 09:52:00 -05:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
//
|
|
|
|
//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));
|
|
|
|
//}
|
|
|
|
//
|
2008-03-05 09:52:00 -05:00
|
|
|
|
|
|
|
internal override void Shutdown(SocketShutdown how)
|
|
|
|
{
|
|
|
|
TcpConnectionContract.Imp conn = GetChannelOrThrow();
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
try {
|
2008-03-05 09:52:00 -05:00
|
|
|
if ((how == SocketShutdown.Both) ||
|
|
|
|
(how == SocketShutdown.Receive))
|
|
|
|
{
|
2008-11-17 18:29:00 -05:00
|
|
|
if (!CanRead(conn)) {
|
2008-03-05 09:52:00 -05:00
|
|
|
// 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))
|
|
|
|
{
|
2008-11-17 18:29:00 -05:00
|
|
|
if (!CanWrite(conn)) {
|
2008-03-05 09:52:00 -05:00
|
|
|
// Can't shut down writing if it's not currently allowed
|
|
|
|
throw new SocketException(BAD_SOCKET_STATE_ERR);
|
|
|
|
}
|
|
|
|
|
|
|
|
conn.SendDoneSending();
|
|
|
|
}
|
|
|
|
}
|
2008-11-17 18:29:00 -05:00
|
|
|
finally {
|
2008-03-05 09:52:00 -05:00
|
|
|
ReleaseChannel(conn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ================== Properties ==================
|
|
|
|
|
|
|
|
//
|
2008-11-17 18:29:00 -05:00
|
|
|
// NOTE: Many of these are hard-coded for now since we only support
|
2008-03-05 09:52:00 -05:00
|
|
|
// IPv4 Tcp streams.
|
|
|
|
//
|
|
|
|
|
|
|
|
internal override int Available {
|
|
|
|
get {
|
|
|
|
return GetAvailableBytes(0); // Don't sleep at all
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal override EndPoint LocalEndPoint {
|
|
|
|
get {
|
2008-11-17 18:29:00 -05:00
|
|
|
if (m_LocalEndPoint == null) {
|
2008-03-05 09:52:00 -05:00
|
|
|
uint ipAddr;
|
|
|
|
ushort port;
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
if (!GetLocalEndPoint(out ipAddr, out port)) {
|
2008-03-05 09:52:00 -05:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_LocalEndPoint = new IPEndPoint(ipAddr, port);
|
|
|
|
}
|
|
|
|
|
|
|
|
return m_LocalEndPoint;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal override EndPoint RemoteEndPoint {
|
|
|
|
get {
|
2008-11-17 18:29:00 -05:00
|
|
|
if (m_RemoteEndPoint == null) {
|
2008-03-05 09:52:00 -05:00
|
|
|
uint ipAddr;
|
|
|
|
ushort port;
|
|
|
|
|
2008-11-17 18:29:00 -05:00
|
|
|
if (!GetRemoteEndPoint(out ipAddr, out port)) {
|
2008-03-05 09:52:00 -05:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_RemoteEndPoint = new IPEndPoint(ipAddr, port);
|
|
|
|
}
|
|
|
|
|
|
|
|
return m_RemoteEndPoint;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal override bool Blocking {
|
|
|
|
get {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
set {
|
2008-11-17 18:29:00 -05:00
|
|
|
if (value != true) {
|
2008-03-05 09:52:00 -05:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|