iothub/device/src/Common/ActionItem.cs (233 lines of code) (raw):
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Security;
using System.Threading;
namespace Microsoft.Azure.Devices.Client
{
internal abstract class ActionItem
{
#if NET451
[Fx.Tag.SecurityNote(Critical = "Stores the security context, used later in binding back into")]
[SecurityCritical]
private SecurityContext _context;
#endif
private bool _isScheduled;
protected ActionItem()
{
}
public bool LowPriority { get; protected set; }
public static void Schedule(Action<object> callback, object state)
{
Schedule(callback, state, false);
}
[Fx.Tag.SecurityNote(Critical = "Calls into critical method ScheduleCallback",
Safe = "Schedule invoke of the given delegate under the current context")]
public static void Schedule(Action<object> callback, object state, bool lowPriority)
{
Fx.Assert(callback != null, "A null callback was passed for Schedule!");
if (PartialTrustHelpers.ShouldFlowSecurityContext || WaitCallbackActionItem.ShouldUseActivity)
{
new DefaultActionItem(callback, state, lowPriority).Schedule();
}
else
{
ScheduleCallback(callback, state, lowPriority);
}
}
[Fx.Tag.SecurityNote(Critical = "Called after applying the user context on the stack or (potentially) " +
"without any user context on the stack")]
[SecurityCritical]
protected abstract void Invoke();
[Fx.Tag.SecurityNote(Critical = "Access critical field context and critical property " +
"CallbackHelper.InvokeWithContextCallback, calls into critical method " +
"PartialTrustHelpers.CaptureSecurityContextNoIdentityFlow, calls into critical method ScheduleCallback; " +
"since the invoked method and the capturing of the security contex are de-coupled, can't " +
"be treated as safe")]
[SecurityCritical]
protected void Schedule()
{
if (_isScheduled)
{
throw Fx.Exception.AsError(new InvalidOperationException(CommonResources.ActionItemIsAlreadyScheduled));
}
_isScheduled = true;
#if NET451
if (PartialTrustHelpers.ShouldFlowSecurityContext)
{
_context = PartialTrustHelpers.CaptureSecurityContextNoIdentityFlow();
}
if (_context != null)
{
ScheduleCallback(CallbackHelper.InvokeWithContextCallback);
}
else
#endif
{
ScheduleCallback(CallbackHelper.InvokeWithoutContextCallback);
}
}
#if NET451
[Fx.Tag.SecurityNote(Critical = "Access critical field context and critical property " +
"CallbackHelper.InvokeWithContextCallback, calls into critical method ScheduleCallback; " +
"since nothing is known about the given context, can't be treated as safe")]
[SecurityCritical]
protected void ScheduleWithContext(SecurityContext contextToSchedule)
{
if (contextToSchedule == null)
{
throw Fx.Exception.ArgumentNull("context");
}
if (_isScheduled)
{
throw Fx.Exception.AsError(new InvalidOperationException(CommonResources.ActionItemIsAlreadyScheduled));
}
_isScheduled = true;
_context = contextToSchedule.CreateCopy();
ScheduleCallback(CallbackHelper.InvokeWithContextCallback);
}
#endif
[Fx.Tag.SecurityNote(Critical = "Access critical property CallbackHelper.InvokeWithoutContextCallback, " +
"Calls into critical method ScheduleCallback; not bound to a security context")]
[SecurityCritical]
protected void ScheduleWithoutContext()
{
if (_isScheduled)
{
throw Fx.Exception.AsError(new InvalidOperationException(CommonResources.ActionItemIsAlreadyScheduled));
}
_isScheduled = true;
ScheduleCallback(CallbackHelper.InvokeWithoutContextCallback);
}
[Fx.Tag.SecurityNote(Critical = "Calls into critical methods IOThreadScheduler.ScheduleCallbackNoFlow, " +
"IOThreadScheduler.ScheduleCallbackLowPriNoFlow")]
[SecurityCritical]
private static void ScheduleCallback(Action<object> callback, object state, bool lowPriority)
{
Fx.Assert(callback != null, "Cannot schedule a null callback");
if (lowPriority)
{
IoThreadScheduler.ScheduleCallbackLowPriNoFlow(callback, state);
}
else
{
IoThreadScheduler.ScheduleCallbackNoFlow(callback, state);
}
}
#if NET451
[Fx.Tag.SecurityNote(Critical = "Extract the security context stored and reset the critical field")]
[SecurityCritical]
private SecurityContext ExtractContext()
{
Fx.Assert(_context != null, "Cannot bind to a null context; context should have been set by now");
Fx.Assert(_isScheduled, "Context is extracted only while the object is scheduled");
SecurityContext result = _context;
_context = null;
return result;
}
#endif
[Fx.Tag.SecurityNote(Critical = "Calls into critical static method ScheduleCallback")]
[SecurityCritical]
private void ScheduleCallback(Action<object> callback)
{
ScheduleCallback(callback, this, LowPriority);
}
[SecurityCritical]
private static class CallbackHelper
{
[Fx.Tag.SecurityNote(Critical = "Stores a delegate to a critical method")]
private static Action<object> s_invokeWithoutContextCallback;
[Fx.Tag.SecurityNote(Critical = "Stores a delegate to a critical method")]
private static ContextCallback s_onContextAppliedCallback;
#if NET451
[Fx.Tag.SecurityNote(Critical = "Stores a delegate to a critical method")]
private static Action<object> s_invokeWithContextCallback;
[Fx.Tag.SecurityNote(Critical = "Provides access to a critical field; Initialize it with " +
"a delegate to a critical method")]
public static Action<object> InvokeWithContextCallback
{
get
{
if (s_invokeWithContextCallback == null)
{
s_invokeWithContextCallback = new Action<object>(InvokeWithContext);
}
return s_invokeWithContextCallback;
}
}
#endif
[Fx.Tag.SecurityNote(Critical = "Provides access to a critical field; Initialize it with " +
"a delegate to a critical method")]
public static Action<object> InvokeWithoutContextCallback
{
get
{
if (s_invokeWithoutContextCallback == null)
{
s_invokeWithoutContextCallback = new Action<object>(InvokeWithoutContext);
}
return s_invokeWithoutContextCallback;
}
}
[Fx.Tag.SecurityNote(Critical = "Provides access to a critical field; Initialize it with " +
"a delegate to a critical method")]
public static ContextCallback OnContextAppliedCallback
{
get
{
if (s_onContextAppliedCallback == null)
{
s_onContextAppliedCallback = new ContextCallback(OnContextApplied);
}
return s_onContextAppliedCallback;
}
}
#if NET451
[Fx.Tag.SecurityNote(Critical = "Called by the scheduler without any user context on the stack")]
private static void InvokeWithContext(object state)
{
SecurityContext context = ((ActionItem)state).ExtractContext();
SecurityContext.Run(context, OnContextAppliedCallback, state);
}
#endif
[Fx.Tag.SecurityNote(Critical = "Called by the scheduler without any user context on the stack")]
private static void InvokeWithoutContext(object state)
{
var tempState = (ActionItem)state;
tempState.Invoke();
tempState._isScheduled = false;
}
[Fx.Tag.SecurityNote(Critical = "Called after applying the user context on the stack")]
private static void OnContextApplied(object o)
{
var tempState = (ActionItem)o;
tempState.Invoke();
tempState._isScheduled = false;
}
}
private class DefaultActionItem : ActionItem
{
[Fx.Tag.SecurityNote(Critical = "Stores a delegate that will be called later, at a particular context")]
[SecurityCritical]
private readonly Action<object> _callback;
[Fx.Tag.SecurityNote(Critical = "Stores an object that will be passed to the delegate that will be " +
"called later, at a particular context")]
[SecurityCritical]
private readonly object _state;
[Fx.Tag.SecurityNote(Critical = "Access critical fields callback and state",
Safe = "Doesn't leak information or resources")]
public DefaultActionItem(Action<object> callback, object state, bool isLowPriority)
{
Fx.Assert(callback != null, "Shouldn't instantiate an object to wrap a null callback");
LowPriority = isLowPriority;
_callback = callback;
_state = state;
}
[Fx.Tag.SecurityNote(Critical = "Implements a the critical abstract ActionItem.Invoke method, " +
"Access critical fields callback and state")]
[SecurityCritical]
protected override void Invoke()
{
_callback(_state);
}
}
}
}