//////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // File: Services/Smb/Client/Transactor.sg // // Note: // // A "transactor" is anything that can issue requests (transactions) // against the transport mux. These requests are issued using the // SmbTransactor contract (in Services/Smb/PrivateContracts). The // TransportMux thread holds the export side of the contract, and // various other threads (DirectoryClient and FileExp service threads) // hold the import sides. // // The Transactor class contains state information for each SmbTransactor // channel, for the TransportMux class. The transport mux runs on a // single thread; only that thread creates and uses instances of the // Transactor class. So there is no need to synchronize access to these // instances. // // TODO: // // * Implement fragmentation for TRANSACTION2 requests. Reassembly // for inbound fragments is implemented, but not for outbound requests. // // // REQUESTS vs TRANSACTIONS // ------------------------ // // At its most basic, SMB provides a way to identify message boundaries (framing), // and a common header for all messages exchanged. Most of those messages are // request/response messages, where a single message sent from the client to the // server causes the server to response with exactly one message. We will refer // to these as "SMB requests", although that's a really horribly vague term. // // There are some SMB protocols that do not conform to this usage pattern. // Notably, SMB supports "transactions", which are identified by the SMB commands // SMB_TRANSACTION and SMB_TRANSACTION2. We will only deal with SMB_TRANSACTION2. // These transactions add several fields to the basic request/response header, // and they also allow the payload of the transaction to span more than one SMB // message. // // First, the client sends an SMB message to the server, identifying the SMB // transaction. There are two possibilities to consider: First, the client's // transaction message may be small enough to fit into a single SMB message; // this is the easy case. Otherwise, the client must fragment its transaction // request. In either case, the client sends its first SMB message, with // the Command field of the SMB header set to SMB_TRANSACTION2. // // Then the client waits for a response from the server. If the client needed // to fragment its transaction request, then it does NOT yet send any further // fragments. The server then decides whether or not it likes the request. // If the primary request indicates that the client needs to send more fragments, // then the server will respond with the "Interim Server Response". This is // just a "go ahead" message (which means one more useless round-trip). // The client then sends all of its fragments. // // The server then sends its transaction responses. If the response does not // need to be fragmented, then there will only be a single response message. // // using System; using System.Text; using System.Collections; using System.Threading; using System.Text; using System.Security.Protocols.Ntlm; using System.Runtime.InteropServices; using Microsoft.SingSharp; using Microsoft.Singularity; using Microsoft.Singularity.Channels; using NetStack.Contracts; using Smb.Protocol; using Smb.PublicChannels; using Smb.PrivateChannels; using Smb.Shared; namespace Smb.Client { /// /// This class represents the mux's view of a transactor. Only the mux thread creates or manipulates /// instances of this class. And because only a single thread ever touches these instances, locking /// is not required. /// internal class Transactor { public Transactor(MuxTuple tuple) { this.Tuple = tuple; this.State = TransactorState.Ready; this.DebugPrefix = "Tran" + tuple.ToString() + ": "; TrackedData data = new TrackedData(); this._datacon = new TContainer(data); } public class TrackedData : ITracked { // This buffer can be null. // This buffer is used to reassemble fragments of a TRANSACTION2 request. public byte[] in ExHeap TransactionResponseBuffer; public int TransactionResponseFragmentByteIndex; public int TransactionRequestFragmentByteIndex; public byte[] in ExHeap ResponseReassemblyBuffer; public int Transaction_ReceivedParameterBytes; public int Transaction_ReceivedDataBytes; public int Transaction_ReceivedFragmentCount; public int Transaction_DataOffset; public int Transaction_ParameterOffset; // In the "Ready" state, this field is null. // In all other states, this field is non-null. public SmbTransactor.Exp Exp; // Contains a reference to the byte[] in ExHeap buffer that contains a message that // needs to be transmitted to the remote SMB peer. A transactor client (a holder of // SmbTransactor.Imp) submits these buffers by sending the Request message. public byte[] in ExHeap RequestBuffer; // The length of the data in _buffer. Right now, this is always equal to the length // of the buffer itself, because the current TcpConnectionContract does not allow // for sends that are shorter than the length of the buffer. public int RequestLength; public byte[] in ExHeap ExtractResponseReassemblyBuffer() { expose(this) { byte[] in ExHeap buffer = this.ResponseReassemblyBuffer; this.ResponseReassemblyBuffer = null; return buffer; } } public SmbTransactor.Exp! AcquireEndpoint() { expose (this) { SmbTransactor.Exp exp = this.Exp; this.Exp = null; assert exp != null; return exp; } } #if false void ITracked.Acquire() {} void ITracked.Release() {} void ITracked.Expose() {} void ITracked.UnExpose() {} #else public void Acquire() {} public void Release() {} public void Expose() {} public void UnExpose() {} #endif public void Dispose() { delete TransactionResponseBuffer; delete RequestBuffer; delete Exp; // SelfReference.Dispose(); // delete SelfReference; delete ResponseReassemblyBuffer; } } readonly TContainer! _datacon; public TrackedData! AcquireData() { return _datacon.Acquire(); } public void ReleaseData([Claims]TrackedData! data) { _datacon.Release(data); } public SmbTransactor.Exp! AcquireEndpoint() { TrackedData! tracked = AcquireData(); SmbTransactor.Exp exp = tracked.AcquireEndpoint(); ReleaseData(tracked); assert exp != null; return exp; } public byte[] in ExHeap ExtractResponseReassemblyBuffer() { TrackedData! tracked = AcquireData(); byte[] in ExHeap buffer; expose(tracked) { buffer = tracked.ResponseReassemblyBuffer; tracked.ResponseReassemblyBuffer = null; ReleaseData(tracked); } return buffer; } public readonly string! DebugPrefix; public readonly MuxTuple Tuple; public TransactorState State; public bool InQueue; public SmbFlag1 _smbFlag1; public SmbFlag2 _smbFlag2; public SmbCommand Command; public void Dispose() { TrackedData data = AcquireData(); data.Dispose(); } override public string! ToString() { string! stateText = StateToString(this.State); return String.Format("[Transactor tuple {0} state {1}]", this.Tuple.ToString(), stateText); } public static string! StateToString(TransactorState tstate) { switch (tstate) { case TransactorState.Ready: return "Ready"; case TransactorState.WaitingSendRequest: return "WaitingSendRequest"; case TransactorState.WaitingSendPrimaryTransactionRequest: return "WaitingSendPrimaryTransactionRequest"; case TransactorState.WaitingSendSecondaryTransactionRequest: return "WaitingSendSecondaryTransactionRequest"; case TransactorState.WaitingReceiveResponse: return "WaitingReceiveResponse"; case TransactorState.WaitingReceivePrimaryTransactionResponse: return "WaitingReceivePrimaryTransactionResponse"; case TransactorState.WaitingReceiveSecondaryTransactionResponse: return "WaitingReceiveSecondaryTransactionResponse"; default: return "???" + ((int)tstate).ToString(); } } } enum TransactorState { // In this state, the ExpRef has been acquired (so don't try to acquire it again), and the exp is in the // endpoint map that is owned by the multiplexer thread. Two things can happen to this transactor: // // * The transactor can close its channel. In this case, the multiplexer thread removes the // transactor state from the _transactors hash table. // // * The transactor can start a new request. The multiplexer thread releases the ExpRef, and // sets Transactor.State to Queued. Ready, // In this state, the multiplexer has received a request from the transactor client (the imp side). // The multiplexer has not yet transmitted the packet to the remote peer. // // _waitingResponseExp contains a valid pointer. // _buffer contains a valid pointer. // // Events: // // * If the TCP connection closes or fails, then the multiplexer thread will: // acquire _waitingResponseExp, send its a NakRequest, and then delete it. // acquire _buffer and delete it. // remove the Transactor from the _transactors hash table. // // * When the multiplexer transmits the message, it removes the WaitingSendRequest, // In this state, the multiplexer has transmitted the request to the remote peer, and is now waiting // for a response. // // * If the TCP connection closes or fails, then the multiplexer thread will acquire the // waiting response endpoint, will send a NakRequest, will close the channel, and then // will remove the tuple entry from the table. // WaitingReceiveResponse, WaitingSendPrimaryTransactionRequest, WaitingReceiveInterimTransactionResponse, WaitingSendSecondaryTransactionRequest, WaitingReceivePrimaryTransactionResponse, WaitingReceiveSecondaryTransactionResponse, } }