//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ namespace System.ServiceModel.Channels { using System.Collections.Generic; using System.Runtime; using System.ServiceModel; using System.ServiceModel.Security.Tokens; using System.Transactions; using SR = System.ServiceModel.SR; sealed class MsmqOutputSessionChannel : TransportOutputChannel, IOutputSessionChannel { MsmqQueue msmqQueue; List> buffers; Transaction associatedTx; IOutputSession session; MsmqChannelFactory factory; MessageEncoder encoder; SecurityTokenProviderContainer certificateTokenProvider; public MsmqOutputSessionChannel(MsmqChannelFactory factory, EndpointAddress to, Uri via, bool manualAddressing) : base(factory, to, via, manualAddressing, factory.MessageVersion) { this.factory = factory; this.encoder = this.factory.MessageEncoderFactory.CreateSessionEncoder(); this.buffers = new List>(); this.buffers.Add(EncodeSessionPreamble()); if (factory.IsMsmqX509SecurityConfigured) { this.certificateTokenProvider = factory.CreateX509TokenProvider(to, via); } this.session = new OutputSession(); } int CalcSessionGramSize() { long sessionGramSize = 0; for (int i = 0; i < this.buffers.Count; i++) { ArraySegment buffer = this.buffers[i]; sessionGramSize += buffer.Count; } if (sessionGramSize > int.MaxValue) throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new InvalidOperationException(SR.GetString(SR.MsmqSessionGramSizeMustBeInIntegerRange))); return (int)sessionGramSize; } void CopySessionGramToBuffer(byte[] sessionGramBuffer) { int sessionGramOffset = 0; for (int i = 0; i < this.buffers.Count; i++) { ArraySegment buffer = this.buffers[i]; Buffer.BlockCopy(buffer.Array, buffer.Offset, sessionGramBuffer, sessionGramOffset, buffer.Count); sessionGramOffset += buffer.Count; } } void ReturnSessionGramBuffers() { // Don't return that fancy/schmancy end buffer for (int i = 0; i < this.buffers.Count - 1; i++) { this.Factory.BufferManager.ReturnBuffer(this.buffers[i].Array); } } public IOutputSession Session { get { return this.session; } } void OnCloseCore(bool isAborting, TimeSpan timeout) { // Dump the messages into the queue as a big bag. // no MSMQ send if aborting // no MSMQ send if the channel has only a preamble (no actual messages sent) if (!isAborting && this.buffers.Count > 1) { lock (ThisLock) { VerifyTransaction(); buffers.Add(EncodeEndMarker()); } int size = CalcSessionGramSize(); using (MsmqOutputMessage msmqMessage = new MsmqOutputMessage(this.Factory, size, this.RemoteAddress)) { msmqMessage.ApplyCertificateIfNeeded(this.certificateTokenProvider, this.factory.MsmqTransportSecurity.MsmqAuthenticationMode, timeout); msmqMessage.Body.EnsureBufferLength(size); msmqMessage.Body.BufferLength = size; CopySessionGramToBuffer(msmqMessage.Body.Buffer); bool lockHeld = false; try { Msmq.EnterXPSendLock(out lockHeld, this.factory.MsmqTransportSecurity.MsmqProtectionLevel); this.msmqQueue.Send(msmqMessage, MsmqTransactionMode.CurrentOrSingle); MsmqDiagnostics.SessiongramSent(this.Session.Id, msmqMessage.MessageId, this.buffers.Count); } catch (MsmqException ex) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(ex.Normalized); } finally { if (lockHeld) { Msmq.LeaveXPSendLock(); } ReturnSessionGramBuffers(); } } } if (null != this.msmqQueue) this.msmqQueue.Dispose(); this.msmqQueue = null; if (certificateTokenProvider != null) { if (isAborting) certificateTokenProvider.Abort(); else certificateTokenProvider.Close(timeout); } } protected override void OnAbort() { this.OnCloseCore(true, TimeSpan.Zero); } protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state) { this.OnCloseCore(false, timeout); return new CompletedAsyncResult(callback, state); } protected override void OnEndClose(IAsyncResult result) { CompletedAsyncResult.End(result); } protected override void OnClose(TimeSpan timeout) { this.OnCloseCore(false, timeout); } void OnOpenCore(TimeSpan timeout) { if (null == Transaction.Current) throw DiagnosticUtility.ExceptionUtility.ThrowHelperCritical(new InvalidOperationException(SR.GetString(SR.MsmqTransactionCurrentRequired))); this.associatedTx = Transaction.Current; this.associatedTx.EnlistVolatile(new TransactionEnlistment(this, this.associatedTx), EnlistmentOptions.None); this.msmqQueue = new MsmqQueue(this.Factory.AddressTranslator.UriToFormatName(this.RemoteAddress.Uri), UnsafeNativeMethods.MQ_SEND_ACCESS); if (certificateTokenProvider != null) { certificateTokenProvider.Open(timeout); } } protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state) { OnOpenCore(timeout); return new CompletedAsyncResult(callback, state); } protected override void OnEndOpen(IAsyncResult result) { CompletedAsyncResult.End(result); } protected override void OnOpen(TimeSpan timeout) { OnOpenCore(timeout); } protected override IAsyncResult OnBeginSend(Message message, TimeSpan timeout, AsyncCallback callback, object state) { OnSend(message, timeout); return new CompletedAsyncResult(callback, state); } protected override void OnEndSend(IAsyncResult result) { CompletedAsyncResult.End(result); } protected override void OnSend(Message message, TimeSpan timeout) { lock (ThisLock) { ThrowIfDisposed(); VerifyTransaction(); // serialize the indigo message to byte array and save... this.buffers.Add(EncodeMessage(message)); } } void VerifyTransaction() { if (this.associatedTx != Transaction.Current) { this.Fault(); throw DiagnosticUtility.ExceptionUtility.ThrowHelperCritical(new InvalidOperationException(SR.GetString(SR.MsmqSameTransactionExpected))); } if (TransactionStatus.Active != Transaction.Current.TransactionInformation.Status) { this.Fault(); throw DiagnosticUtility.ExceptionUtility.ThrowHelperCritical(new InvalidOperationException(SR.GetString(SR.MsmqTransactionNotActive))); } } ArraySegment EncodeSessionPreamble() { EncodedVia encodedVia = new EncodedVia(this.Via.AbsoluteUri); EncodedContentType encodedContentType = EncodedContentType.Create(this.encoder.ContentType); int startSize = ClientSimplexEncoder.ModeBytes.Length + SessionEncoder.CalcStartSize(encodedVia, encodedContentType) + ClientSimplexEncoder.PreambleEndBytes.Length; byte[] startBytes = this.Factory.BufferManager.TakeBuffer(startSize); Buffer.BlockCopy(ClientSimplexEncoder.ModeBytes, 0, startBytes, 0, ClientSimplexEncoder.ModeBytes.Length); SessionEncoder.EncodeStart(startBytes, ClientSimplexEncoder.ModeBytes.Length, encodedVia, encodedContentType); Buffer.BlockCopy(ClientSimplexEncoder.PreambleEndBytes, 0, startBytes, startSize - ClientSimplexEncoder.PreambleEndBytes.Length, ClientSimplexEncoder.PreambleEndBytes.Length); return new ArraySegment(startBytes, 0, startSize); } ArraySegment EncodeEndMarker() { return new ArraySegment(SessionEncoder.EndBytes, 0, SessionEncoder.EndBytes.Length); } // Stick a message into a buffer ArraySegment EncodeMessage(Message message) { ArraySegment messageData = this.encoder.WriteMessage(message, int.MaxValue, this.Factory.BufferManager, SessionEncoder.MaxMessageFrameSize); return SessionEncoder.EncodeMessageFrame(messageData); } MsmqChannelFactory Factory { get { return this.factory; } } class OutputSession : IOutputSession { string id = "uuid:/session-gram/" + Guid.NewGuid(); public string Id { get { return this.id; } } } class TransactionEnlistment : IEnlistmentNotification { MsmqOutputSessionChannel channel; Transaction transaction; public TransactionEnlistment(MsmqOutputSessionChannel channel, Transaction transaction) { this.channel = channel; this.transaction = transaction; } public void Prepare(PreparingEnlistment preparingEnlistment) { // Abort if this happens before the channel is closed... if (this.channel.State != CommunicationState.Closed) { channel.Fault(); Exception e = DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.MsmqSessionChannelsMustBeClosed))); preparingEnlistment.ForceRollback(e); } else preparingEnlistment.Prepared(); } public void Commit(Enlistment enlistment) { enlistment.Done(); } public void Rollback(Enlistment enlistment) { this.channel.Fault(); enlistment.Done(); } public void InDoubt(Enlistment enlistment) { enlistment.Done(); } } } }