From 2306b470f1753e3645de3be6766c9efe5c9f01a0 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 13 Jul 2026 22:31:56 +0100 Subject: [PATCH] Enforce advertised body producer authority --- .../EveUnityCultMeshLiveProviderTransport.cs | 23 +++++--- .../EveUnitySceneSurfaceLowererTests.cs | 52 ++++++++++++++----- .../org.gamecult.eve.unity-scene/package.json | 4 +- 3 files changed, 55 insertions(+), 24 deletions(-) diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs b/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs index 762ff17..0d57da3 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs @@ -286,7 +286,7 @@ namespace GameCult.Eve.UnityScene private CultMeshBodyPublicationResolver CreateBodyResolver() { - var producerId = RequireAdvertisedBodyProducerId(_advertisement); + var producerIds = RequireAdvertisedBodyProducerIds(_advertisement); var mappedRoot = Path.GetDirectoryName(_replicaPath) ?? "."; return new CultMeshBodyPublicationResolver(new CultMeshBodyTransportService( new ICultMeshBodyTransportAdapter[] @@ -295,20 +295,27 @@ namespace GameCult.Eve.UnityScene new CultMeshMappedBodyAdapter(mappedRoot), new CultMeshNetworkBodyAdapter(ReadNetworkBody) }, - (candidateProducerId, _) => IsAdvertisedBodyProducer(producerId, candidateProducerId))); + (candidateProducerId, _) => IsAdvertisedBodyProducer(producerIds, candidateProducerId))); } - private static bool IsAdvertisedBodyProducer(string advertisedProducerId, string candidateProducerId) => - string.Equals(candidateProducerId, advertisedProducerId, StringComparison.Ordinal); + private static bool IsAdvertisedBodyProducer( + IReadOnlyList advertisedProducerIds, + string candidateProducerId) => + advertisedProducerIds.Any(producerId => + string.Equals(candidateProducerId, producerId, StringComparison.Ordinal)); - private static string RequireAdvertisedBodyProducerId(EveProviderAdvertisementDocument? advertisement) + private static IReadOnlyList RequireAdvertisedBodyProducerIds(EveProviderAdvertisementDocument? advertisement) { if (advertisement == null) throw new InvalidOperationException("The Eve provider advertisement must be resolved before body transport authorization."); - if (string.IsNullOrWhiteSpace(advertisement.ServiceId)) + var producerIds = advertisement.AuthorizedBodyProducerIds + .Where(producerId => !string.IsNullOrWhiteSpace(producerId)) + .Distinct(StringComparer.Ordinal) + .ToArray(); + if (producerIds.Length == 0) throw new InvalidOperationException( - $"Eve provider '{advertisement.ProviderId}' does not advertise a service identity for body production."); - return advertisement.ServiceId; + $"Eve provider '{advertisement.ProviderId}' does not advertise an authorized body producer."); + return producerIds; } private byte[] ReadNetworkBody(CultMeshBodyDescriptor descriptor) diff --git a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs index 14cd65e..b202b93 100644 --- a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs +++ b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs @@ -103,36 +103,56 @@ namespace GameCult.Eve.UnityScene.Tests } [Test] - public void LiveTransportAuthorizesBodiesWithAdvertisedServiceIdentity() + public void LiveTransportAuthorizesOnlyExplicitlyAdvertisedBodyProducers() { - var advertisement = ProviderAdvertisement("aetheria", "aetheria.daemon"); + var advertisement = ProviderAdvertisement("aetheria", "aetheria-daemon-17", "aetheria.daemon"); var method = typeof(EveUnityCultMeshLiveProviderTransport) - .GetMethod("RequireAdvertisedBodyProducerId", BindingFlags.NonPublic | BindingFlags.Static)!; + .GetMethod("RequireAdvertisedBodyProducerIds", BindingFlags.NonPublic | BindingFlags.Static)!; - var producerId = (string)method.Invoke(null, new object?[] { advertisement })!; + var producerIds = (IReadOnlyList)method.Invoke(null, new object?[] { advertisement })!; - Assert.That(producerId, Is.EqualTo("aetheria.daemon")); - Assert.That(producerId, Is.Not.EqualTo(advertisement.ProviderId)); + Assert.That(producerIds, Is.EqualTo(new[] { "aetheria.daemon" })); + Assert.That(producerIds, Does.Not.Contain(advertisement.ProviderId)); + Assert.That(producerIds, Does.Not.Contain(advertisement.ServiceId)); var authorizes = typeof(EveUnityCultMeshLiveProviderTransport) .GetMethod("IsAdvertisedBodyProducer", BindingFlags.NonPublic | BindingFlags.Static)!; - Assert.That(authorizes.Invoke(null, new object[] { producerId, "aetheria.daemon" }), Is.True); - Assert.That(authorizes.Invoke(null, new object[] { producerId, "aetheria" }), Is.False); - Assert.That(authorizes.Invoke(null, new object[] { producerId, "impostor" }), Is.False); + Assert.That(authorizes.Invoke(null, new object[] { producerIds, "aetheria.daemon" }), Is.True); + Assert.That(authorizes.Invoke(null, new object[] { producerIds, "aetheria" }), Is.False); + Assert.That(authorizes.Invoke(null, new object[] { producerIds, "aetheria-daemon-17" }), Is.False); + Assert.That(authorizes.Invoke(null, new object[] { producerIds, "impostor" }), Is.False); } [Test] public void LiveTransportRejectsAdvertisementWithoutBodyProducerIdentity() { - var advertisement = ProviderAdvertisement("aetheria", ""); + var advertisement = ProviderAdvertisement("aetheria", "aetheria-daemon-17"); var method = typeof(EveUnityCultMeshLiveProviderTransport) - .GetMethod("RequireAdvertisedBodyProducerId", BindingFlags.NonPublic | BindingFlags.Static)!; + .GetMethod("RequireAdvertisedBodyProducerIds", BindingFlags.NonPublic | BindingFlags.Static)!; var error = Assert.Throws(() => method.Invoke(null, new object?[] { advertisement })); Assert.That(error!.InnerException, Is.TypeOf()); - StringAssert.Contains("does not advertise a service identity", error.InnerException!.Message); + StringAssert.Contains("does not advertise an authorized body producer", error.InnerException!.Message); + } + + [Test] + public void ProviderAdvertisementRoundTripPreservesBodyProducerAuthority() + { + var advertisement = ProviderAdvertisement( + "aetheria", + "aetheria-daemon-17", + "aetheria.daemon", + "aetheria.asset-publisher"); + + var bytes = MessagePack.MessagePackSerializer.Serialize(advertisement); + var decoded = MessagePack.MessagePackSerializer.Deserialize(bytes); + + Assert.That(decoded.ProviderId, Is.EqualTo("aetheria")); + Assert.That(decoded.ServiceId, Is.EqualTo("aetheria-daemon-17")); + Assert.That(decoded.AuthorizedBodyProducerIds, + Is.EqualTo(new[] { "aetheria.daemon", "aetheria.asset-publisher" })); } [Test] @@ -2055,7 +2075,10 @@ namespace GameCult.Eve.UnityScene.Tests } }; - private static EveProviderAdvertisementDocument ProviderAdvertisement(string providerId, string serviceId) => + private static EveProviderAdvertisementDocument ProviderAdvertisement( + string providerId, + string serviceId, + params string[] authorizedBodyProducerIds) => new EveProviderAdvertisementDocument( providerId, serviceId, @@ -2068,7 +2091,8 @@ namespace GameCult.Eve.UnityScene.Tests Array.Empty(), Array.Empty(), Array.Empty(), - Array.Empty()); + Array.Empty(), + authorizedBodyProducerIds); private static CultMeshBodyPublicationDocument BodyPublication( EveEntitySoaViewDocument view, diff --git a/packages/org.gamecult.eve.unity-scene/package.json b/packages/org.gamecult.eve.unity-scene/package.json index eb3c847..a5e4d6c 100644 --- a/packages/org.gamecult.eve.unity-scene/package.json +++ b/packages/org.gamecult.eve.unity-scene/package.json @@ -1,13 +1,13 @@ { "name": "org.gamecult.eve.unity-scene", "displayName": "Eve Unity Scene Lowering", - "version": "0.3.4", + "version": "0.3.5", "unity": "2021.3", "description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.", "dependencies": { "org.gamecult.cultlib": "1.0.10", "org.gamecult.eve.plugin-fields": "0.1.1", - "org.gamecult.eve.surface": "0.2.0", + "org.gamecult.eve.surface": "0.2.1", "com.unity.mathematics": "1.3.2" }, "keywords": [