Skip to content

Commit

Permalink
feat!: Refactored topic structure for more granular flow and access (#…
Browse files Browse the repository at this point in the history
…107)

* adding new topic v5 structure

* cleanup

* fix runtime issues

* update client rest urls for new account api

* fix permissions, add updated mqtt regex

* update user camera permissions, subscriptions

* update lasest topic schema

* temp add clientid

* update user presense fir last will

* update topics, ttl user

* update cam permissions by actual name

* always request user-specific context

* make realm accessible

* centralize sub logs, add priv remote render sub

* filter messages based on expected payload format

* add server publish to private user

* protect against other apps deleting objects

* update logger choices to switch on message topic

* fix logging issues

* add/fix subsciption failure warning

* move ttl to more reliable update check compoenent

* fixed ttl component
  • Loading branch information
mwfarb authored Oct 14, 2024
1 parent feff96a commit 16ff39f
Show file tree
Hide file tree
Showing 8 changed files with 348 additions and 69 deletions.
1 change: 1 addition & 0 deletions Runtime/ArenaCamera.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public bool PublishCreateUpdate()
object_id = camid,
action = created ? "update" : "create",
type = messageType,
ttl = 30,
};
if (string.IsNullOrWhiteSpace(displayName))
{ // provide default name if needed
Expand Down
191 changes: 150 additions & 41 deletions Runtime/ArenaClientScene.cs

Large diffs are not rendered by default.

72 changes: 57 additions & 15 deletions Runtime/ArenaMqttClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
Expand Down Expand Up @@ -142,7 +143,29 @@ public void Publish(string topic, byte[] payload)

public void Subscribe(string[] topics)
{
if (client != null) client.Subscribe(topics, new byte[] { MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE });
if (client != null)
{
var qosLevels = new byte[topics.Length];
Array.Fill(qosLevels, MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE);
client.Subscribe(topics, qosLevels);
ArenaMqttTokenClaimsJson perms = JsonConvert.DeserializeObject<ArenaMqttTokenClaimsJson>(permissions);
foreach (string topic in topics)
{
bool subscribePermission = false;
foreach (string subperm in perms.subs)
{
if (MqttTopicMatch(subperm, topic)) subscribePermission = true;
}
if (subscribePermission)
{
Debug.Log($"Subscribed to : {topic}");
}
else
{
Debug.LogWarning($"Subscribed FAILED to : {topic}");
}
}
}
}

public void Unsubscribe(string[] topics)
Expand Down Expand Up @@ -289,13 +312,13 @@ private IEnumerator Signin(string sceneName, string namespaceName, string realm,
}

// get arena CSRF token
yield return HttpRequestAuth($"https://{hostAddress}/user/login");
yield return HttpRequestAuth($"https://{hostAddress}/user/v2/login");

WWWForm form = new WWWForm();
if (idToken != null) form.AddField("id_token", idToken);

// get arena user account state
cd = new CoroutineWithData(this, HttpRequestAuth($"https://{hostAddress}/user/user_state", csrfToken, form));
cd = new CoroutineWithData(this, HttpRequestAuth($"https://{hostAddress}/user/v2/user_state", csrfToken, form));
yield return cd.coroutine;
if (!isCrdSuccess(cd.result)) yield break;
authState = JsonConvert.DeserializeObject<ArenaUserStateJson>(cd.result.ToString());
Expand All @@ -318,11 +341,9 @@ private IEnumerator Signin(string sceneName, string namespaceName, string realm,
// get arena user mqtt token
form.AddField("id_auth", tokenType);
form.AddField("username", userName);
if (camera)
{
form.AddField("userid", "true");
form.AddField("camid", "true");
}
// always request user-specific context, esp. for remote rendering
form.AddField("userid", "true");
form.AddField("camid", "true");
if (!string.IsNullOrWhiteSpace(realm))
{
form.AddField("realm", realm);
Expand All @@ -332,7 +353,7 @@ private IEnumerator Signin(string sceneName, string namespaceName, string realm,
{
form.AddField("scene", $"{namespaceName}/{sceneName}");
}
cd = new CoroutineWithData(this, HttpRequestAuth($"https://{hostAddress}/user/mqtt_auth", csrfToken, form));
cd = new CoroutineWithData(this, HttpRequestAuth($"https://{hostAddress}/user/v2/mqtt_auth", csrfToken, form));
yield return cd.coroutine;
if (!isCrdSuccess(cd.result)) yield break;
mqttToken = cd.result.ToString();
Expand All @@ -357,16 +378,22 @@ private IEnumerator Signin(string sceneName, string namespaceName, string realm,
userid = auth.ids.userid;
camid = auth.ids.camid;

// TODO: will message can only delete the primary camera object, need a solution for multiple cameras

// will message can only remove the primary user presence
var lwtTopic = new ArenaTopics(
realm: realm,
name_space: namespaceName,
scenename: sceneName,
idtag: userid
);
willFlag = camera;
willTopic = $"{realm}/s/{namespaceName}/{sceneName}/{camid}";
willTopic = lwtTopic.PUB_SCENE_PRESENCE;
ArenaObjectJson msg = new ArenaObjectJson
{
object_id = camid,
action = "delete",
object_id = userid,
action = "leave",
};
willMessage = JsonConvert.SerializeObject(msg);
Debug.Log($"MQTT Last will: {willTopic} {willMessage}");
}

string payloadJson = Base64UrlDecode(auth.token.Split('.')[1]);
Expand All @@ -391,7 +418,7 @@ protected IEnumerator GetFSLoginForUser()
WWWForm form = new WWWForm();
if (idToken != null) form.AddField("id_token", idToken);

CoroutineWithData cd = new CoroutineWithData(this, HttpRequestAuth($"https://{hostAddress}/user/storelogin", csrfToken, form));
CoroutineWithData cd = new CoroutineWithData(this, HttpRequestAuth($"https://{hostAddress}/user/v2/storelogin", csrfToken, form));
yield return cd.coroutine;
if (!isCrdSuccess(cd.result)) yield break;
if (string.IsNullOrWhiteSpace(fsToken))
Expand Down Expand Up @@ -506,5 +533,20 @@ private string GetCookie(string SetCookie, string cookieTag)
return csrfCookie;
}

public static bool MqttTopicMatch(string allowTopic, string attemptTopic)
{
var allowedRegex = allowTopic.Replace(@"/", @"\/").Replace("+", @"[a-zA-Z0-9 _+.-]*").Replace("#", @"[a-zA-Z0-9 \/_#+.-]*");
var re = new Regex(allowedRegex);
var matches = re.Matches(attemptTopic);
foreach (var match in matches.ToList())
{
if (match.Value == attemptTopic)
{
return true;
}
}
return false;
}

}
}
13 changes: 0 additions & 13 deletions Runtime/ArenaObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,19 +80,6 @@ void Start()
StartCoroutine(PublishTickThrottle());
}


public void SetTtlDeleteTimer(float seconds)
{
StartCoroutine(TtlUpdater(seconds));
}

IEnumerator TtlUpdater(float seconds)
{
yield return new WaitForSeconds(seconds);
externalDelete = true;
Destroy(gameObject);
}

IEnumerator PublishTickThrottle()
{
// TODO (mwfarb): prevent child objects of parent.transform.hasChanged = true from publishing unnecessarily
Expand Down
83 changes: 83 additions & 0 deletions Runtime/ArenaTopics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
namespace ArenaUnity
{

public readonly struct ArenaTopics
{
/**
* ARENA pubsub topic variables
* - nameSpace - namespace of the scene
* - sceneName - name of the scene
* - userName - name of the user per arena-auth (e.g. jdoe)
* - idTag - username prefixed with a uuid (e.g. 1448081341_jdoe)
* - camName - idTag prefixed with camera_ (e.g. camera_1448081341_jdoe)
*/
public ArenaTopics(
string realm = "",
string name_space = "",
string scenename = "",
string idtag = "",
string uuId = "",
string userobj = "",
string objectid = "",
string touid = "",
string devicename = ""
)
{
REALM = realm;
nameSpace = name_space;
sceneName = scenename;
idTag = idtag;
uuid = uuId;
userObj = userobj;
objectId = objectid;
toUid = touid;
deviceName = devicename;
}
public readonly string REALM { get; }
public readonly string nameSpace { get; }
public readonly string sceneName { get; }
public readonly string idTag { get; }
public readonly string uuid { get; }
public readonly string userObj { get; }
public readonly string objectId { get; }
public readonly string toUid { get; }
public readonly string deviceName { get; }

#pragma warning disable format
// Disable auto-format to keep alignment for readability

// SUBSCRIBE
public string SUB_NETWORK { get { return "$NETWORK"; } }
public string SUB_DEVICE { get { return $"{REALM}/d/{deviceName}/#"; } } // All client placeholder
public string SUB_PROC_REG { get { return $"{REALM}/proc/reg"; } }
public string SUB_PROC_CTL { get { return $"{REALM}/proc/control/{uuid}/#"; } }
public string SUB_PROC_DBG { get { return $"{REALM}/proc/debug/{uuid}"; } }
public string SUB_SCENE_PUBLIC { get { return $"{REALM}/s/{nameSpace}/{sceneName}/+/+"; } }
public string SUB_SCENE_PRIVATE { get { return $"{REALM}/s/{nameSpace}/{sceneName}/+/+/{idTag}/#"; } }
public string SUB_SCENE_RENDER_PRIVATE { get { return $"{REALM}/s/{nameSpace}/{sceneName}/r/+/{idTag}/#"; } }

// PUBLISH
public string PUB_NETWORK_LATENCY { get { return "$NETWORK/latency"; } }
public string PUB_DEVICE { get { return $"{REALM}/d/{deviceName}/{idTag}"; } }
public string PUB_PROC_REG { get { return $"{REALM}/proc/reg"; } }
public string PUB_PROC_CTL { get { return $"{REALM}/proc/control"; } }
public string PUB_PROC_DBG { get { return $"{REALM}/proc/debug/{uuid}"; } }
public string PUB_SCENE_PRESENCE { get { return $"{REALM}/s/{nameSpace}/{sceneName}/x/{idTag}"; } }
public string PUB_SCENE_PRESENCE_PRIVATE{ get { return $"{REALM}/s/{nameSpace}/{sceneName}/x/{idTag}/{toUid}"; } }
public string PUB_SCENE_CHAT { get { return $"{REALM}/s/{nameSpace}/{sceneName}/c/{idTag}"; } }
public string PUB_SCENE_CHAT_PRIVATE { get { return $"{REALM}/s/{nameSpace}/{sceneName}/c/{idTag}/{toUid}"; } }
public string PUB_SCENE_USER { get { return $"{REALM}/s/{nameSpace}/{sceneName}/u/{userObj}"; } }
public string PUB_SCENE_USER_PRIVATE { get { return $"{REALM}/s/{nameSpace}/{sceneName}/u/{userObj}/{toUid}"; } } // Need to add face_ privs
public string PUB_SCENE_OBJECTS { get { return $"{REALM}/s/{nameSpace}/{sceneName}/o/{objectId}"; } } // All client placeholder
public string PUB_SCENE_OBJECTS_PRIVATE { get { return $"{REALM}/s/{nameSpace}/{sceneName}/o/{objectId}/{toUid}"; } }
public string PUB_SCENE_RENDER { get { return $"{REALM}/s/{nameSpace}/{sceneName}/r/{idTag}"; } }
public string PUB_SCENE_RENDER_PRIVATE { get { return $"{REALM}/s/{nameSpace}/{sceneName}/r/{idTag}/-"; } } // To avoid unpriv sub
public string PUB_SCENE_RENDER_PRI_SERV { get { return $"{REALM}/s/{nameSpace}/{sceneName}/r/-/{toUid}"; } }
public string PUB_SCENE_ENV { get { return $"{REALM}/s/{nameSpace}/{sceneName}/e/{idTag}"; } }
public string PUB_SCENE_ENV_PRIVATE { get { return $"{REALM}/s/{nameSpace}/{sceneName}/e/{idTag}/-"; } } // To avoid unpriv sub
public string PUB_SCENE_PROGRAM { get { return $"{REALM}/s/{nameSpace}/{sceneName}/p/{idTag}"; } }
public string PUB_SCENE_PROGRAM_PRIVATE { get { return $"{REALM}/s/{nameSpace}/{sceneName}/p/{idTag}/{toUid}"; } }
public string PUB_SCENE_DEBUG { get { return $"{REALM}/s/{nameSpace}/{sceneName}/d/{idTag}/-"; } } // To avoid unpriv sub
#pragma warning restore format
}
}
11 changes: 11 additions & 0 deletions Runtime/ArenaTopics.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions Runtime/ArenaTtl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using UnityEngine;

namespace ArenaUnity
{
[DisallowMultipleComponent]
[RequireComponent(typeof(ArenaObject))]
public class ArenaTtl : MonoBehaviour
{
long? expiration = null;

private void Start()
{
}

public void SetTtlDeleteTimer(float seconds)
{
expiration = DateTimeOffset.Now.ToUnixTimeMilliseconds() + (long)(seconds * 1000);
}

private void Update()
{
if (expiration != null && DateTimeOffset.Now.ToUnixTimeMilliseconds() > expiration)
{
var aobj = GetComponent<ArenaObject>();
if (aobj != null)
{
aobj.externalDelete = true;
Destroy(gameObject);
}
}
}

}
}
11 changes: 11 additions & 0 deletions Runtime/ArenaTtl.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 16ff39f

Please sign in to comment.