Compare commits
2 Commits
72cf2f4091
...
8a1f037d5a
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a1f037d5a | |||
| e328758c53 |
@@ -2,7 +2,7 @@
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: galaxy_repository.proto
|
||||
# Protobuf Python Version: 6.33.5
|
||||
# Protobuf Python Version: 6.31.1
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
@@ -12,8 +12,8 @@ from google.protobuf.internal import builder as _builder
|
||||
_runtime_version.ValidateProtobufRuntimeVersion(
|
||||
_runtime_version.Domain.PUBLIC,
|
||||
6,
|
||||
33,
|
||||
5,
|
||||
31,
|
||||
1,
|
||||
'',
|
||||
'galaxy_repository.proto'
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import warnings
|
||||
|
||||
import galaxy_repository_pb2 as galaxy__repository__pb2
|
||||
|
||||
GRPC_GENERATED_VERSION = '1.81.1'
|
||||
GRPC_GENERATED_VERSION = '1.80.0'
|
||||
GRPC_VERSION = grpc.__version__
|
||||
_version_not_supported = False
|
||||
|
||||
@@ -25,7 +25,7 @@ if _version_not_supported:
|
||||
)
|
||||
|
||||
|
||||
class GalaxyRepositoryStub:
|
||||
class GalaxyRepositoryStub(object):
|
||||
"""Wire-compatibility policy (ProtobufStyleGuide): this contract evolves
|
||||
additively only. Never renumber or repurpose an existing field number or
|
||||
enum value. When a field or enum value is removed, add a `reserved` range
|
||||
@@ -72,7 +72,7 @@ class GalaxyRepositoryStub:
|
||||
_registered_method=True)
|
||||
|
||||
|
||||
class GalaxyRepositoryServicer:
|
||||
class GalaxyRepositoryServicer(object):
|
||||
"""Wire-compatibility policy (ProtobufStyleGuide): this contract evolves
|
||||
additively only. Never renumber or repurpose an existing field number or
|
||||
enum value. When a field or enum value is removed, add a `reserved` range
|
||||
@@ -162,7 +162,7 @@ def add_GalaxyRepositoryServicer_to_server(servicer, server):
|
||||
|
||||
|
||||
# This class is part of an EXPERIMENTAL API.
|
||||
class GalaxyRepository:
|
||||
class GalaxyRepository(object):
|
||||
"""Wire-compatibility policy (ProtobufStyleGuide): this contract evolves
|
||||
additively only. Never renumber or repurpose an existing field number or
|
||||
enum value. When a field or enum value is removed, add a `reserved` range
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: mxaccess_gateway.proto
|
||||
# Protobuf Python Version: 6.33.5
|
||||
# Protobuf Python Version: 6.31.1
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
@@ -12,8 +12,8 @@ from google.protobuf.internal import builder as _builder
|
||||
_runtime_version.ValidateProtobufRuntimeVersion(
|
||||
_runtime_version.Domain.PUBLIC,
|
||||
6,
|
||||
33,
|
||||
5,
|
||||
31,
|
||||
1,
|
||||
'',
|
||||
'mxaccess_gateway.proto'
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import warnings
|
||||
|
||||
import mxaccess_gateway_pb2 as mxaccess__gateway__pb2
|
||||
|
||||
GRPC_GENERATED_VERSION = '1.81.1'
|
||||
GRPC_GENERATED_VERSION = '1.80.0'
|
||||
GRPC_VERSION = grpc.__version__
|
||||
_version_not_supported = False
|
||||
|
||||
@@ -25,7 +25,7 @@ if _version_not_supported:
|
||||
)
|
||||
|
||||
|
||||
class MxAccessGatewayStub:
|
||||
class MxAccessGatewayStub(object):
|
||||
"""Wire-compatibility policy (ProtobufStyleGuide): this contract evolves
|
||||
additively only. Never renumber or repurpose an existing field number or
|
||||
enum value. When a field or enum value is removed, add a `reserved` range
|
||||
@@ -78,7 +78,7 @@ class MxAccessGatewayStub:
|
||||
_registered_method=True)
|
||||
|
||||
|
||||
class MxAccessGatewayServicer:
|
||||
class MxAccessGatewayServicer(object):
|
||||
"""Wire-compatibility policy (ProtobufStyleGuide): this contract evolves
|
||||
additively only. Never renumber or repurpose an existing field number or
|
||||
enum value. When a field or enum value is removed, add a `reserved` range
|
||||
@@ -189,7 +189,7 @@ def add_MxAccessGatewayServicer_to_server(servicer, server):
|
||||
|
||||
|
||||
# This class is part of an EXPERIMENTAL API.
|
||||
class MxAccessGateway:
|
||||
class MxAccessGateway(object):
|
||||
"""Wire-compatibility policy (ProtobufStyleGuide): this contract evolves
|
||||
additively only. Never renumber or repurpose an existing field number or
|
||||
enum value. When a field or enum value is removed, add a `reserved` range
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: mxaccess_worker.proto
|
||||
# Protobuf Python Version: 6.33.5
|
||||
# Protobuf Python Version: 6.31.1
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
@@ -12,8 +12,8 @@ from google.protobuf.internal import builder as _builder
|
||||
_runtime_version.ValidateProtobufRuntimeVersion(
|
||||
_runtime_version.Domain.PUBLIC,
|
||||
6,
|
||||
33,
|
||||
5,
|
||||
31,
|
||||
1,
|
||||
'',
|
||||
'mxaccess_worker.proto'
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ import grpc
|
||||
import warnings
|
||||
|
||||
|
||||
GRPC_GENERATED_VERSION = '1.81.1'
|
||||
GRPC_GENERATED_VERSION = '1.80.0'
|
||||
GRPC_VERSION = grpc.__version__
|
||||
_version_not_supported = False
|
||||
|
||||
|
||||
@@ -196,11 +196,30 @@ public sealed class ConstraintEnforcer(
|
||||
|
||||
private GalaxyTagLookup? ResolveTarget(string tagAddress)
|
||||
{
|
||||
GalaxyHierarchyCacheEntry entry = cache.Current;
|
||||
return !string.IsNullOrWhiteSpace(tagAddress)
|
||||
&& entry.Index.TagsByAddress.TryGetValue(tagAddress, out GalaxyTagLookup? lookup)
|
||||
? lookup
|
||||
: null;
|
||||
if (string.IsNullOrWhiteSpace(tagAddress))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
IReadOnlyDictionary<string, GalaxyTagLookup> tagsByAddress = cache.Current.Index.TagsByAddress;
|
||||
if (tagsByAddress.TryGetValue(tagAddress, out GalaxyTagLookup? lookup))
|
||||
{
|
||||
return lookup;
|
||||
}
|
||||
|
||||
// Galaxy SQL keys array attributes by their suffixed FullTagReference (e.g. "Obj.Arr[]"),
|
||||
// but callers pass the bare address ("Obj.Arr") before the worker-boundary normalization
|
||||
// runs. Probe the suffixed form so a bare array name resolves to its array attribute,
|
||||
// consistent with ArrayAddressNormalizer. Only build the suffixed string on a direct miss
|
||||
// when the address is not already suffixed, and only accept it when it is truly an array.
|
||||
if (!tagAddress.EndsWith("[]", StringComparison.Ordinal)
|
||||
&& tagsByAddress.TryGetValue(tagAddress + "[]", out GalaxyTagLookup? arrayLookup)
|
||||
&& arrayLookup.Attribute?.IsArray == true)
|
||||
{
|
||||
return arrayLookup;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool MatchesPathOrTag(
|
||||
|
||||
@@ -141,6 +141,112 @@ public sealed class GatewayArrayWriteWiringTests
|
||||
Assert.Equal(new[] { 0, 7, 0, 0 }, forwarded.ArrayValue.Int32Values.Values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A bare array <c>AddItem2</c> address is normalized to its writable array form on the wire,
|
||||
/// and the normalized address lands in the tracked <see cref="SessionItemRegistration"/>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task AddItem2_BareArrayAddress_NormalizedOnWireAndInRegistration()
|
||||
{
|
||||
CapturingWorkerClient worker = new();
|
||||
GatewaySession session = CreateReadySession(worker);
|
||||
|
||||
WorkerCommand command = new()
|
||||
{
|
||||
Command = new MxCommand
|
||||
{
|
||||
Kind = MxCommandKind.AddItem2,
|
||||
AddItem2 = new AddItem2Command
|
||||
{
|
||||
ServerHandle = 1,
|
||||
ItemDefinition = "Obj.Arr",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
worker.NextReply = new WorkerCommandReply
|
||||
{
|
||||
Reply = new MxCommandReply
|
||||
{
|
||||
Kind = MxCommandKind.AddItem2,
|
||||
ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.Ok },
|
||||
AddItem2 = new AddItem2Reply { ItemHandle = 43 },
|
||||
},
|
||||
};
|
||||
|
||||
await session.InvokeAsync(command, CancellationToken.None);
|
||||
|
||||
Assert.NotNull(worker.LastCommand);
|
||||
Assert.Equal("Obj.Arr[]", worker.LastCommand!.Command.AddItem2.ItemDefinition);
|
||||
|
||||
MxCommand trackingCopy = new()
|
||||
{
|
||||
Kind = MxCommandKind.AddItem2,
|
||||
AddItem2 = new AddItem2Command
|
||||
{
|
||||
ServerHandle = 1,
|
||||
ItemDefinition = "Obj.Arr",
|
||||
},
|
||||
};
|
||||
session.TrackCommandReply(trackingCopy, worker.NextReply.Reply);
|
||||
|
||||
Assert.True(session.TryGetItemRegistration(1, 43, out SessionItemRegistration registration));
|
||||
Assert.Equal("Obj.Arr[]", registration.TagAddress);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A sparse-array entry in a <see cref="WriteBulkCommand"/> is expanded to a full,
|
||||
/// default-filled <see cref="MxArray"/> before reaching the worker; no sparse value is ever
|
||||
/// forwarded inside a bulk write.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task WriteBulk_SparseArrayEntryValue_ExpandedBeforeReachingWorker()
|
||||
{
|
||||
CapturingWorkerClient worker = new();
|
||||
GatewaySession session = CreateReadySession(worker);
|
||||
|
||||
WorkerCommand command = new()
|
||||
{
|
||||
Command = new MxCommand
|
||||
{
|
||||
Kind = MxCommandKind.WriteBulk,
|
||||
WriteBulk = new WriteBulkCommand
|
||||
{
|
||||
ServerHandle = 1,
|
||||
Entries =
|
||||
{
|
||||
new WriteBulkEntry
|
||||
{
|
||||
ItemHandle = 42,
|
||||
Value = new MxValue
|
||||
{
|
||||
SparseArrayValue = new MxSparseArray
|
||||
{
|
||||
ElementDataType = MxDataType.Integer,
|
||||
TotalLength = 4,
|
||||
Elements =
|
||||
{
|
||||
new MxSparseElement
|
||||
{
|
||||
Index = 1,
|
||||
Value = new MxValue { Int32Value = 7 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await session.InvokeAsync(command, CancellationToken.None);
|
||||
|
||||
MxValue forwarded = worker.LastCommand!.Command.WriteBulk.Entries[0].Value;
|
||||
Assert.Equal(MxValue.KindOneofCase.ArrayValue, forwarded.KindCase);
|
||||
Assert.Equal(new[] { 0, 7, 0, 0 }, forwarded.ArrayValue.Int32Values.Values);
|
||||
}
|
||||
|
||||
private static GatewaySession CreateReadySession(IWorkerClient workerClient)
|
||||
{
|
||||
GatewaySession session = new(
|
||||
|
||||
@@ -207,6 +207,62 @@ public sealed class ConstraintEnforcerTests
|
||||
Assert.Equal("read_historized_only", failure.ConstraintName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A bare array attribute address (no trailing <c>[]</c>) resolves through the Galaxy index
|
||||
/// even though arrays are keyed by their suffixed FullTagReference (e.g. "Pump_001.Levels[]").
|
||||
/// Without the <c>[]</c> fallback in ResolveTarget the bare name misses the index and a
|
||||
/// read-constrained key gets a spurious tag_metadata denial for an AddItem it should allow.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task CheckReadTagAsync_WithBareArrayName_ResolvesViaArraySuffixFallback()
|
||||
{
|
||||
ConstraintEnforcer enforcer = CreateEnforcer(out _);
|
||||
ApiKeyIdentity identity = CreateIdentity(ApiKeyConstraints.Empty with
|
||||
{
|
||||
// A read constraint that covers the Pump_001 subtree; the array attribute is inside it.
|
||||
ReadTagGlobs = ["Pump_001.*"],
|
||||
});
|
||||
|
||||
ConstraintFailure? failure = await enforcer.CheckReadTagAsync(
|
||||
identity,
|
||||
"Pump_001.Levels",
|
||||
CancellationToken.None);
|
||||
|
||||
// Before the fix: bare "Pump_001.Levels" misses the index (keyed "Pump_001.Levels[]") and
|
||||
// returns a tag_metadata failure. After the fix: it resolves and is within scope -> null.
|
||||
Assert.Null(failure);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A bare non-array name that is genuinely absent from the index still resolves to null:
|
||||
/// the <c>[]</c> probe must not manufacture a false positive for a scalar/missing tag.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task CheckReadTagAsync_WithMissingNonArrayName_StillFailsToResolve()
|
||||
{
|
||||
ConstraintEnforcer enforcer = CreateEnforcer(out _);
|
||||
ApiKeyIdentity identity = CreateIdentity(ApiKeyConstraints.Empty with
|
||||
{
|
||||
ReadTagGlobs = ["Pump_001.*"],
|
||||
});
|
||||
|
||||
// "Pump_001.Scalar" is not in the index, and "Pump_001.Scalar[]" is not either, so the
|
||||
// suffix probe must not resolve it. A genuinely-unknown name behaves the same.
|
||||
ConstraintFailure? missingScalar = await enforcer.CheckReadTagAsync(
|
||||
identity,
|
||||
"Pump_001.Scalar",
|
||||
CancellationToken.None);
|
||||
Assert.NotNull(missingScalar);
|
||||
Assert.Equal("tag_metadata", missingScalar.ConstraintName);
|
||||
|
||||
ConstraintFailure? unknown = await enforcer.CheckReadTagAsync(
|
||||
identity,
|
||||
"DoesNotExist_999.Whatever",
|
||||
CancellationToken.None);
|
||||
Assert.NotNull(unknown);
|
||||
Assert.Equal("tag_metadata", unknown.ConstraintName);
|
||||
}
|
||||
|
||||
private static ConstraintEnforcer CreateEnforcer(out FakeAuditWriter auditWriter)
|
||||
{
|
||||
auditWriter = new FakeAuditWriter();
|
||||
@@ -276,6 +332,14 @@ public sealed class ConstraintEnforcerTests
|
||||
AttributeName = "NonHistorized",
|
||||
FullTagReference = "Pump_001.NonHistorized",
|
||||
},
|
||||
new GalaxyAttribute
|
||||
{
|
||||
// Galaxy SQL keys array attributes by their suffixed FullTagReference,
|
||||
// so the index entry is "Pump_001.Levels[]", not the bare name.
|
||||
AttributeName = "Levels",
|
||||
FullTagReference = "Pump_001.Levels[]",
|
||||
IsArray = true,
|
||||
},
|
||||
},
|
||||
},
|
||||
new GalaxyObject
|
||||
|
||||
Reference in New Issue
Block a user