Feature request: expose the Called AET to the incoming C-STORE instance filter

We run a multi-tenant Orthanc as a public DIMSE ingress (1.12.11, Python plugin 7.1). Each tenant is identified by a Called AE Title we issue, because our senders’ Calling AEs are vendor factory defaults (OEM_StoreSCU, STORESCU, VXVUE) that are neither unique across clinics nor freely changeable on the device.

Today our filter accepts everything and we authorize at STABLE_STUDY by reading CalledAET from instance metadata, per Python Auto-route and called AET . That works, but it means unauthenticated bytes hit storage before any authorization decision, and the sender always sees C-STORE success, so a clinic that typos its Called AE gets a green light and no error. We end up delivering that rejection by email and phone instead of in-protocol.

The filter can already reject (it receives uint16_t* dimseStatus, so we would return 0x0124), it
just cannot see the field the decision depends on. Orthanc does hand the Called AET to plugins
elsewhere: OnFind(answers, query, issuerAet, calledAet) and the move callback both get it. Only
the incoming C-STORE path lacks it, at both levels: no called-AET parameter on OrthancPluginIncomingCStoreInstanceFilter, and no counterpart to OrthancPluginGetInstanceRemoteAet in the DicomInstance group. Verified on Python plugin 7.1: none of the 15 methods on the DicomInstance handed to the filter yields it, though it appears in instance metadata immediately after storage.

Smallest thing that would fix it: add OrthancPluginGetInstanceCalledAet(context, instance), mirroring OrthancPluginGetInstanceRemoteAet, returning the Called AET when the origin is DicomProtocol and an empty string otherwise. No callback signature changes, and the Python plugin would surface it as DicomInstance.GetInstanceCalledAet(). A filter variant carrying const char* calledAet would also work, but the accessor additionally covers RegisterIncomingDicomInstanceFilter and the stored-instance callback.

I did not find this in the TODO or on the forum, apologies if I missed it. Happy to test a patch
against a staging gateway that takes real traffic from a range of vendor consoles.

I was trying to understand the implications of this as a way into the current Orthanc codebase, and noticed that there is an example of adding a C-STORE filter in the python module documentation

Could you apply the security filter at that point, and then do the routing in STABLE_STUDY?

Thanks for digging into it. That is the hook we already use, and the routing does happen in
STABLE_STUDY. The catch is the discriminator: the example filters on GetInstanceRemoteAet(), the Calling AE, and across our senders that is a shared vendor factory default (STORESCU, OEM_StoreSCU, VXVUE). It tells us the make of the console, not which tenant is sending.

I re-probed today to be sure I was not just missing an accessor. Orthanc 1.12.11, Python plugin
7.1, one C-STORE with Calling AE SOUNDDR01 and Called AE CRX_FAKECRED99. Inside the filter:

GetInstanceRemoteAet()           = 'SOUNDDR01'
HasInstanceMetadata('CalledAET') = 0
GetInstanceMetadata('CalledAET') = None

The same instance in RegisterOnStoredInstanceCallback:

{"CalledAET": "CRX_FAKECRED99", "RemoteIP": "...", "Origin": "DicomProtocol", ...}

So Orthanc has the value, it is just not reachable until after the instance is stored. RemoteIP is
in the same boat, so a source-IP check at filter time is out as well, and none of the 15 methods on
the DicomInstance yields the Called AE. (multitenant-dicom does not help either: it is an extra
DICOM server and TCP port per tenant.)

If our modalities had distinct Calling AEs, the documented hook would be all we need. The ask is narrowly that the field which does identify our tenants be readable where the example already reads the Calling AE.