Skip to content

Commit

Permalink
feat: Adding ignoreAuthority Option to Command (#1918)
Browse files Browse the repository at this point in the history
* Add ignoreAuthority to weaver

* Add ignoreAuthority to CommandAttribute

* Add NetworkConnection to handlers

* Pass conn parameter from OnCommandMessage

* Add ignoreAuthority to SendCommandInternal
Add NetworkConnection param to CmdDelegate
Add NetworkConnection param to InvokeCommand
Pass conn to InvokeHandlerDelegate
Pass conn to invoker.invokeFunction

* Update tests with optional NetworkConnection param

* adding commandInfo

* fixing test calls

* renaming arguments

* adding ignoreAuthority to register call

* weaver tests for ignore authority

* tests for command ignoreAuthority

* adding debug assert to make sure tests is running correct

* moving variables up

* removing un-needed code

* updating tests

Co-authored-by: James Frowen <jamesfrowendev@gmail.com>
  • Loading branch information
MrGadget and James-Frowen committed May 28, 2020
1 parent 8721d05 commit 3ace2c6
Show file tree
Hide file tree
Showing 17 changed files with 354 additions and 35 deletions.
7 changes: 6 additions & 1 deletion Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ public static MethodDefinition ProcessCommandCall(TypeDefinition td, MethodDefin
cmdName = cmdName.Substring(CmdPrefix.Length);
}

int channel = commandAttr.GetField("channel", 0);
bool ignoreAuthority = commandAttr.GetField("ignoreAuthority", false);


// invoke internal send and return
// load 'base.' to call the SendCommand function with
worker.Append(worker.Create(OpCodes.Ldarg_0));
Expand All @@ -70,7 +74,8 @@ public static MethodDefinition ProcessCommandCall(TypeDefinition td, MethodDefin
worker.Append(worker.Create(OpCodes.Ldstr, cmdName));
// writer
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Ldc_I4, commandAttr.GetField("channel", 0)));
worker.Append(worker.Create(OpCodes.Ldc_I4, channel));
worker.Append(worker.Create(ignoreAuthority ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0));
worker.Append(worker.Create(OpCodes.Call, Weaver.sendCommandInternal));

NetworkBehaviourProcessor.WriteRecycleWriter(worker);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class NetworkBehaviourProcessor
readonly List<FieldDefinition> syncObjects = new List<FieldDefinition>();
// <SyncVarField,NetIdField>
readonly Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds = new Dictionary<FieldDefinition, FieldDefinition>();
readonly List<MethodDefinition> commands = new List<MethodDefinition>();
readonly List<CmdResult> commands = new List<CmdResult>();
readonly List<MethodDefinition> clientRpcs = new List<MethodDefinition>();
readonly List<MethodDefinition> targetRpcs = new List<MethodDefinition>();
readonly List<EventDefinition> eventRpcs = new List<EventDefinition>();
Expand All @@ -24,6 +24,12 @@ class NetworkBehaviourProcessor

readonly TypeDefinition netBehaviourSubclass;

public struct CmdResult
{
public MethodDefinition method;
public bool ignoreAuthority;
}

public NetworkBehaviourProcessor(TypeDefinition td)
{
Weaver.DLog(td, "NetworkBehaviourProcessor");
Expand Down Expand Up @@ -228,22 +234,23 @@ void GenerateConstants()

for (int i = 0; i < commands.Count; ++i)
{
GenerateRegisterCommandDelegate(cctorWorker, Weaver.registerCommandDelegateReference, commandInvocationFuncs[i], commands[i].Name);
CmdResult cmdResult = commands[i];
GenerateRegisterCommandDelegate(cctorWorker, Weaver.registerCommandDelegateReference, commandInvocationFuncs[i], cmdResult);
}

for (int i = 0; i < clientRpcs.Count; ++i)
{
GenerateRegisterCommandDelegate(cctorWorker, Weaver.registerRpcDelegateReference, clientRpcInvocationFuncs[i], clientRpcs[i].Name);
GenerateRegisterRemoteDelegate(cctorWorker, Weaver.registerRpcDelegateReference, clientRpcInvocationFuncs[i], clientRpcs[i].Name);
}

for (int i = 0; i < targetRpcs.Count; ++i)
{
GenerateRegisterCommandDelegate(cctorWorker, Weaver.registerRpcDelegateReference, targetRpcInvocationFuncs[i], targetRpcs[i].Name);
GenerateRegisterRemoteDelegate(cctorWorker, Weaver.registerRpcDelegateReference, targetRpcInvocationFuncs[i], targetRpcs[i].Name);
}

for (int i = 0; i < eventRpcs.Count; ++i)
{
GenerateRegisterCommandDelegate(cctorWorker, Weaver.registerEventDelegateReference, eventRpcInvocationFuncs[i], eventRpcs[i].Name);
GenerateRegisterRemoteDelegate(cctorWorker, Weaver.registerEventDelegateReference, eventRpcInvocationFuncs[i], eventRpcs[i].Name);
}

foreach (FieldDefinition fd in syncObjects)
Expand All @@ -268,7 +275,7 @@ void GenerateConstants()
// This generates code like:
NetworkBehaviour.RegisterCommandDelegate(base.GetType(), "CmdThrust", new NetworkBehaviour.CmdDelegate(ShipControl.InvokeCmdCmdThrust));
*/
void GenerateRegisterCommandDelegate(ILProcessor worker, MethodReference registerMethod, MethodDefinition func, string cmdName)
void GenerateRegisterRemoteDelegate(ILProcessor worker, MethodReference registerMethod, MethodDefinition func, string cmdName)
{
worker.Append(worker.Create(OpCodes.Ldtoken, netBehaviourSubclass));
worker.Append(worker.Create(OpCodes.Call, Weaver.getTypeFromHandleReference));
Expand All @@ -280,6 +287,25 @@ void GenerateRegisterCommandDelegate(ILProcessor worker, MethodReference registe
//
worker.Append(worker.Create(OpCodes.Call, registerMethod));
}

void GenerateRegisterCommandDelegate(ILProcessor awakeWorker, MethodReference registerMethod, MethodDefinition func, CmdResult cmdResult)
{
string cmdName = cmdResult.method.Name;
bool ignoreAuthority = cmdResult.ignoreAuthority;

awakeWorker.Append(awakeWorker.Create(OpCodes.Ldtoken, netBehaviourSubclass));
awakeWorker.Append(awakeWorker.Create(OpCodes.Call, Weaver.getTypeFromHandleReference));
awakeWorker.Append(awakeWorker.Create(OpCodes.Ldstr, cmdName));
awakeWorker.Append(awakeWorker.Create(OpCodes.Ldnull));
awakeWorker.Append(awakeWorker.Create(OpCodes.Ldftn, func));

awakeWorker.Append(awakeWorker.Create(OpCodes.Newobj, Weaver.CmdDelegateConstructor));

awakeWorker.Append(awakeWorker.Create(ignoreAuthority ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0));

//
awakeWorker.Append(awakeWorker.Create(OpCodes.Call, registerMethod));
}

void GenerateSerialization()
{
Expand Down Expand Up @@ -915,8 +941,14 @@ void ProcessCommand(HashSet<string> names, MethodDefinition md, CustomAttribute
return;
}

bool ignoreAuthority = commandAttr.GetField("ignoreAuthority", false);

names.Add(md.Name);
commands.Add(md);
commands.Add(new CmdResult
{
method = md,
ignoreAuthority = ignoreAuthority
});

MethodDefinition cmdCallFunc = CommandProcessor.ProcessCommandCall(netBehaviourSubclass, md, commandAttr);

Expand Down
1 change: 1 addition & 0 deletions Assets/Mirror/Runtime/CustomAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class CommandAttribute : Attribute
{
// this is zero
public int channel = Channels.DefaultReliable;
public bool ignoreAuthority = false;
}

/// <summary>
Expand Down
38 changes: 27 additions & 11 deletions Assets/Mirror/Runtime/NetworkBehaviour.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ internal static int GetMethodHash(Type invokeClass, string methodName)
}

[EditorBrowsable(EditorBrowsableState.Never)]
protected void SendCommandInternal(Type invokeClass, string cmdName, NetworkWriter writer, int channelId)
protected void SendCommandInternal(Type invokeClass, string cmdName, NetworkWriter writer, int channelId, bool ignoreAuthority = false)
{
// this was in Weaver before
// NOTE: we could remove this later to allow calling Cmds on Server
Expand All @@ -191,7 +191,7 @@ protected void SendCommandInternal(Type invokeClass, string cmdName, NetworkWrit
return;
}
// local players can always send commands, regardless of authority, other objects must have authority.
if (!(isLocalPlayer || hasAuthority))
if (!(ignoreAuthority || isLocalPlayer || hasAuthority))
{
logger.LogWarning($"Trying to send command for object without authority. {invokeClass.ToString()}.{cmdName}");
return;
Expand Down Expand Up @@ -365,13 +365,18 @@ protected class Invoker
public MirrorInvokeType invokeType;
public Type invokeClass;
public CmdDelegate invokeFunction;
public bool cmdIgnoreAuthority;
}
public struct CommandInfo
{
public bool ignoreAuthority;
}

static readonly Dictionary<int, Invoker> cmdHandlerDelegates = new Dictionary<int, Invoker>();

// helper function register a Command/Rpc/SyncEvent delegate
[EditorBrowsable(EditorBrowsableState.Never)]
protected static void RegisterDelegate(Type invokeClass, string cmdName, MirrorInvokeType invokerType, CmdDelegate func)
protected static void RegisterDelegate(Type invokeClass, string cmdName, MirrorInvokeType invokerType, CmdDelegate func, bool cmdIgnoreAuthority = false)
{
// type+func so Inventory.RpcUse != Equipment.RpcUse
int cmdHash = GetMethodHash(invokeClass, cmdName);
Expand All @@ -394,16 +399,17 @@ protected static void RegisterDelegate(Type invokeClass, string cmdName, MirrorI
{
invokeType = invokerType,
invokeClass = invokeClass,
invokeFunction = func
invokeFunction = func,
cmdIgnoreAuthority = cmdIgnoreAuthority,
};
cmdHandlerDelegates[cmdHash] = invoker;
if (logger.LogEnabled()) logger.Log("RegisterDelegate hash:" + cmdHash + " invokerType: " + invokerType + " method:" + func.GetMethodName());
}

[EditorBrowsable(EditorBrowsableState.Never)]
public static void RegisterCommandDelegate(Type invokeClass, string cmdName, CmdDelegate func)
public static void RegisterCommandDelegate(Type invokeClass, string cmdName, CmdDelegate func, bool ignoreAuthority)
{
RegisterDelegate(invokeClass, cmdName, MirrorInvokeType.Command, func);
RegisterDelegate(invokeClass, cmdName, MirrorInvokeType.Command, func, ignoreAuthority);
}

[EditorBrowsable(EditorBrowsableState.Never)]
Expand All @@ -427,9 +433,7 @@ internal static void ClearDelegates()

static bool GetInvokerForHash(int cmdHash, MirrorInvokeType invokeType, out Invoker invoker)
{
if (cmdHandlerDelegates.TryGetValue(cmdHash, out invoker) &&
invoker != null &&
invoker.invokeType == invokeType)
if (cmdHandlerDelegates.TryGetValue(cmdHash, out invoker) && invoker != null && invoker.invokeType == invokeType)
{
return true;
}
Expand All @@ -438,21 +442,33 @@ static bool GetInvokerForHash(int cmdHash, MirrorInvokeType invokeType, out Invo
// (no need to throw an error, an attacker might just be trying to
// call an cmd with an rpc's hash)
if (logger.LogEnabled()) logger.Log("GetInvokerForHash hash:" + cmdHash + " not found");

return false;
}

// InvokeCmd/Rpc/SyncEventDelegate can all use the same function here
internal bool InvokeHandlerDelegate(int cmdHash, MirrorInvokeType invokeType, NetworkReader reader)
{
if (GetInvokerForHash(cmdHash, invokeType, out Invoker invoker) &&
invoker.invokeClass.IsInstanceOfType(this))
if (GetInvokerForHash(cmdHash, invokeType, out Invoker invoker) && invoker.invokeClass.IsInstanceOfType(this))
{
invoker.invokeFunction(this, reader);
return true;
}
return false;
}

internal CommandInfo GetCommandInfo(int cmdHash)
{
if (GetInvokerForHash(cmdHash, MirrorInvokeType.Command, out Invoker invoker) && invoker.invokeClass.IsInstanceOfType(this))
{
return new CommandInfo
{
ignoreAuthority = invoker.cmdIgnoreAuthority
};
}
return default;
}

[Obsolete("Use NetworkBehaviour.GetDelegate instead.")]
public static CmdDelegate GetRpcHandler(int cmdHash) => GetDelegate(cmdHash);

Expand Down
22 changes: 22 additions & 0 deletions Assets/Mirror/Runtime/NetworkIdentity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,28 @@ internal void HandleCommand(int componentIndex, int cmdHash, NetworkReader reade
HandleRemoteCall(componentIndex, cmdHash, MirrorInvokeType.Command, reader);
}

// happens on server
internal NetworkBehaviour.CommandInfo GetCommandInfo(int componentIndex, int cmdHash)
{
if (gameObject == null)
{
// error can be logged later
return default;
}

// find the right component to invoke the function on
if (0 <= componentIndex && componentIndex < NetworkBehaviours.Length)
{
NetworkBehaviour invokeComponent = NetworkBehaviours[componentIndex];
return invokeComponent.GetCommandInfo(cmdHash);
}
else
{
// error can be logged later
return default;
}
}

// happens on client
internal void HandleRPC(int componentIndex, int rpcHash, NetworkReader reader)
{
Expand Down
5 changes: 4 additions & 1 deletion Assets/Mirror/Runtime/NetworkServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -993,10 +993,13 @@ static void OnCommandMessage(NetworkConnection conn, CommandMessage msg)
return;
}

NetworkBehaviour.CommandInfo commandInfo = identity.GetCommandInfo(msg.componentIndex, msg.functionHash);

// Commands can be for player objects, OR other objects with client-authority
// -> so if this connection's controller has a different netId then
// only allow the command if clientAuthorityOwner
if (identity.connectionToClient != conn)
bool needAuthority = !commandInfo.ignoreAuthority;
if (needAuthority && identity.connectionToClient != conn)
{
logger.LogWarning("Command for object without authority [netId=" + msg.netId + "]");
return;
Expand Down

0 comments on commit 3ace2c6

Please sign in to comment.