Skip to content

Commit 51ea7a8

Browse files
authored
Merge pull request #5060 from eisenhauer/xrootd-metadata-identity
BP5/XRootD: identity/version/byte-order checks on remote reads
2 parents a63748d + 8c10be6 commit 51ea7a8

7 files changed

Lines changed: 179 additions & 7 deletions

File tree

source/adios2/engine/bp5/BP5Reader.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,9 @@ void BP5Reader::PerformGets()
574574
params["SelectSteps"] = m_Parameters.SelectSteps;
575575
if (m_Parameters.IgnoreFlattenSteps)
576576
params["IgnoreFlattenSteps"] = "true";
577+
// Send our file id so the server can detect stale cached metadata (0 = none).
578+
if (m_FileUUID != 0)
579+
params["FileUUID"] = std::to_string(m_FileUUID);
577580
m_Remote->Open(std::get<0>(tup), std::get<1>(tup), m_RemoteName, m_OpenMode,
578581
RowMajorOrdering, params);
579582
}

source/adios2/toolkit/remote/XrootdHttpRemote.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include "XrootdHttpRemote.h"
88
#include "adios2/helper/adiosLog.h"
9+
#include "adios2/helper/adiosSystem.h" // IsLittleEndian
910

1011
#include <chrono>
1112
#include <cstdlib>
@@ -343,13 +344,18 @@ void XrootdHttpRemote::Open(const std::string hostname, const int32_t port,
343344
if (it != params.end())
344345
m_RequestTimeout = std::stol(it->second);
345346

347+
// File id for the server's identity check.
348+
it = params.find("FileUUID");
349+
if (it != params.end())
350+
m_FileUUID = static_cast<uint32_t>(std::stoul(it->second));
351+
346352
// Collect non-HTTP engine params (TarInfo, SelectSteps, IgnoreFlattenSteps)
347353
// and encode them as a TAB-separated string for transmission
348354
Params engineParams;
349355
for (const auto &p : params)
350356
{
351357
if (p.first != "UseHttps" && p.first != "CAPath" && p.first != "VerifySSL" &&
352-
p.first != "ConnectTimeout" && p.first != "RequestTimeout")
358+
p.first != "ConnectTimeout" && p.first != "RequestTimeout" && p.first != "FileUUID")
353359
{
354360
engineParams[p.first] = p.second;
355361
}
@@ -442,6 +448,12 @@ std::string XrootdHttpRemote::BuildFileConfigSegment()
442448
std::ostringstream s;
443449
// Always emit RMOrder so the server doesn't have to guess the default.
444450
s << "r" << (m_RowMajorOrdering ? "1" : "0");
451+
// File id for the identity check (omit when 0). Must precede the greedy `p`.
452+
if (m_FileUUID != 0)
453+
s << "u" << m_FileUUID;
454+
// Client byte order (omit when little-endian). Must precede the greedy `p`.
455+
if (!helper::IsLittleEndian())
456+
s << "e1";
445457
if (!m_EngineParams.empty())
446458
s << "p" << Base64urlEncode(m_EngineParams);
447459
std::string out = s.str();

source/adios2/toolkit/remote/XrootdHttpRemote.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ class XrootdHttpRemote : public Remote
147147
std::string m_BaseUrl;
148148
std::string m_Filename;
149149
std::string m_EngineParams;
150+
/** File id from the reader's metadata, sent for the server's staleness check;
151+
* 0 = none (legacy file). */
152+
uint32_t m_FileUUID = 0;
150153
/** Per-engine `<file-config>` path segment, computed once at Open()
151154
* from RMOrder + EngineParams. Reused on every request. */
152155
std::string m_FileConfigSegment;

source/utils/xrootd-plugin/AccessLog.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,13 @@ void AccessLog::Log(const Record &r)
153153
os << ",\"block\":" << r.blockID;
154154
if (r.accuracyError != 0.0)
155155
os << ",\"acc\":" << r.accuracyError;
156-
os << ",\"bytes\":" << r.bytes << ",\"batch\":" << r.batch << "}";
156+
os << ",\"bytes\":" << r.bytes << ",\"batch\":" << r.batch;
157+
if (r.reject)
158+
{
159+
os << ",\"reject\":";
160+
AppendJsonString(os, r.reject);
161+
}
162+
os << "}";
157163

158164
std::string line = os.str();
159165
{

source/utils/xrootd-plugin/AccessLog.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ class AccessLog
4848
uint64_t blockID = static_cast<uint64_t>(-1); // -1 => no block selection
4949
double accuracyError = 0.0;
5050
uint64_t bytes = 0;
51-
uint32_t batch = 1; // number of variables in the batch this belonged to
51+
uint32_t batch = 1; // number of variables in the batch this belonged to
52+
const char *reject = nullptr; // non-null => request was rejected; the reason string
5253
};
5354

5455
/* Record one request. No-op when disabled; may drop if the queue is full. */

source/utils/xrootd-plugin/XrdHttpSsiHandler.cpp

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,9 @@ std::string UrlEncode(const std::string &s)
7373
// c Count o Start a AccuracyError
7474
// N AccuracyNorm R AccuracyRelative
7575
//
76-
// File-config codes:
77-
// r RMOrder (single digit) p EngineParams (rest of segment)
76+
// File-config codes (in this order):
77+
// v WireVersion (digits) r RMOrder (single digit) u FileUUID (digits)
78+
// e ClientBigEndian (digits) p EngineParams (rest of segment)
7879
//
7980
// The decoder builds a legacy "verb Filename=...&key=val&..." string for
8081
// the inner SSI parser, which is unchanged.
@@ -192,14 +193,24 @@ bool DecodePerRequestParamString(const std::string &paramstring, std::ostringstr
192193
return true;
193194
}
194195

195-
// Decode the file-config segment. Strict order: optional `r<digit>`,
196-
// then optional `p<rest>`. `_` placeholder is empty.
196+
// Decode the file-config segment: optional v, r, u, e in that order, then p.
197+
// `_` placeholder is empty.
197198
bool DecodeFileConfigSegment(const std::string &fileConfig, std::ostringstream &out)
198199
{
199200
if (fileConfig == "_")
200201
return true;
201202
size_t i = 0;
202203
const size_t n = fileConfig.size();
204+
if (i < n && fileConfig[i] == 'v')
205+
{
206+
++i;
207+
const size_t start = i;
208+
while (i < n && fileConfig[i] >= '0' && fileConfig[i] <= '9')
209+
++i;
210+
if (i == start)
211+
return false; // 'v' with no digits
212+
out << "&WireVersion=" << fileConfig.substr(start, i - start);
213+
}
203214
if (i < n && fileConfig[i] == 'r')
204215
{
205216
++i;
@@ -210,6 +221,26 @@ bool DecodeFileConfigSegment(const std::string &fileConfig, std::ostringstream &
210221
return false;
211222
out << "&RMOrder=" << d;
212223
}
224+
if (i < n && fileConfig[i] == 'u')
225+
{
226+
++i;
227+
const size_t start = i;
228+
while (i < n && fileConfig[i] >= '0' && fileConfig[i] <= '9')
229+
++i;
230+
if (i == start)
231+
return false; // 'u' with no digits
232+
out << "&FileUUID=" << fileConfig.substr(start, i - start);
233+
}
234+
if (i < n && fileConfig[i] == 'e')
235+
{
236+
++i;
237+
const size_t start = i;
238+
while (i < n && fileConfig[i] >= '0' && fileConfig[i] <= '9')
239+
++i;
240+
if (i == start)
241+
return false; // 'e' with no digits
242+
out << "&ClientBigEndian=" << fileConfig.substr(start, i - start);
243+
}
213244
if (i < n && fileConfig[i] == 'p')
214245
{
215246
++i;

source/utils/xrootd-plugin/XrdSsiSvService.cpp

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,23 @@ void *SvAdiosGet(void *svP)
7878
sessP->doAdiosGet();
7979
return 0;
8080
}
81+
82+
// Highest request wire-format version we accept (clients emit none yet, absent ==
83+
// 0). Policing it gives a future newer client a clear error, not a parse failure.
84+
constexpr uint32_t kMaxWireVersion = 0;
85+
86+
// Record a rejected request in the access log (the normal entry is logged only
87+
// after a successful read, which a rejection never reaches).
88+
void LogReject(const std::string &file, const char *reason, uint32_t batch)
89+
{
90+
if (!AccessLog::Instance().Enabled())
91+
return;
92+
AccessLog::Record rec;
93+
rec.file = file.c_str();
94+
rec.reject = reason;
95+
rec.batch = batch;
96+
AccessLog::Instance().Log(rec);
97+
}
8198
}
8299

83100
/******************************************************************************/
@@ -499,6 +516,9 @@ void XrdSsiSvService::ProcessRequest4Me(XrdSsiRequest *rqstP)
499516
std::string Filename;
500517
std::string EngineParams;
501518
bool ArrayOrder = true;
519+
uint32_t RequestUUID = 0; // file id from client metadata; 0 = no check
520+
uint32_t WireVersion = 0; // request format version; absent/0 = original
521+
bool ClientBigEndian = false; // client native byte order; absent = little
502522
size_t NVars = 0;
503523

504524
struct VarRequest
@@ -619,23 +639,68 @@ void XrdSsiSvService::ProcessRequest4Me(XrdSsiRequest *rqstP)
619639
std::size_t pos = param.find("=") + 1;
620640
EngineParams = UrlDecode(param.substr(pos));
621641
}
642+
else if (HasPrefix(param, "FileUUID="))
643+
{
644+
std::size_t pos = param.find("=") + 1;
645+
std::stringstream sstream(param.substr(pos));
646+
sstream >> RequestUUID;
647+
}
648+
else if (HasPrefix(param, "WireVersion="))
649+
{
650+
std::size_t pos = param.find("=") + 1;
651+
std::stringstream sstream(param.substr(pos));
652+
sstream >> WireVersion;
653+
}
654+
else if (HasPrefix(param, "ClientBigEndian="))
655+
{
656+
std::size_t pos = param.find("=") + 1;
657+
std::stringstream sstream(param.substr(pos));
658+
int be = 0;
659+
sstream >> be;
660+
ClientBigEndian = (be != 0);
661+
}
622662
}
623663
}
624664

625665
NVars = varRequests.size(); // trust actual count over NVars param
626666

627667
if (Filename.empty() || NVars == 0)
628668
{
669+
LogReject(Filename, "badparams", static_cast<uint32_t>(NVars));
629670
RespondErr("batchget: invalid parameters", EINVAL);
630671
return;
631672
}
673+
if (WireVersion > kMaxWireVersion)
674+
{
675+
LogReject(Filename, "version", static_cast<uint32_t>(NVars));
676+
std::string msg = "batchget: unsupported request protocol version " +
677+
std::to_string(WireVersion) + "; this server supports up to " +
678+
std::to_string(kMaxWireVersion);
679+
RespondErr(msg.c_str(), EINVAL);
680+
return;
681+
}
682+
if (ClientBigEndian != !adios2::helper::IsLittleEndian())
683+
{
684+
LogReject(Filename, "byteorder", static_cast<uint32_t>(NVars));
685+
RespondErr("batchget: cross-endian remote reads not supported", EINVAL);
686+
return;
687+
}
632688

633689
try
634690
{
635691
auto poolEntry = m_FilePoolPtr->GetFree(Filename, ArrayOrder, EngineParams);
636692
auto engine = poolEntry.file->m_engine;
637693
auto io = poolEntry.file->m_io;
638694

695+
// Reject if the client's file id doesn't match the file we opened
696+
// (stale cached metadata); 0 = client has no id, no check.
697+
if (RequestUUID != 0 && engine.FileUUID() != RequestUUID)
698+
{
699+
LogReject(Filename, "identity", static_cast<uint32_t>(NVars));
700+
RespondErr("batchget: file identity mismatch; remote metadata is stale", EINVAL);
701+
return;
702+
}
703+
639704
// Compute sizes for each variable and total response size
640705
// Response format: [uint64_t NVars][uint64_t size_0]...[size_N-1][data_0]...[data_N-1]
641706
size_t headerSize = sizeof(uint64_t) + NVars * sizeof(uint64_t);
@@ -648,6 +713,7 @@ void XrdSsiSvService::ProcessRequest4Me(XrdSsiRequest *rqstP)
648713
adios2::DataType TypeOfVar = io.InquireVariableType(vr.VarName);
649714
if (TypeOfVar == adios2::DataType::None)
650715
{
716+
LogReject(Filename, "novar", static_cast<uint32_t>(NVars));
651717
RespondErr("batchget: unknown variable", EINVAL);
652718
return;
653719
}
@@ -678,6 +744,7 @@ void XrdSsiSvService::ProcessRequest4Me(XrdSsiRequest *rqstP)
678744
m_responseBuffer = (char *)malloc(m_responseBufferSize);
679745
if (!m_responseBuffer)
680746
{
747+
LogReject(Filename, "alloc", static_cast<uint32_t>(NVars));
681748
RespondErr("batchget: allocation failed", ENOMEM);
682749
return;
683750
}
@@ -773,6 +840,7 @@ void XrdSsiSvService::ProcessRequest4Me(XrdSsiRequest *rqstP)
773840
}
774841
catch (const std::exception &exc)
775842
{
843+
LogReject(Filename, "exception", static_cast<uint32_t>(NVars));
776844
std::string msg = std::string("batchget: exception during processing: ") + exc.what();
777845
RespondErr(msg.c_str(), EINVAL);
778846
}
@@ -787,6 +855,9 @@ void XrdSsiSvService::ProcessRequest4Me(XrdSsiRequest *rqstP)
787855
size_t BlockID = (size_t)-1, DimCount = 0, StepStart = 0;
788856
size_t StepCount = 1;
789857
bool ArrayOrder = true;
858+
uint32_t RequestUUID = 0; // file id from client metadata; 0 = no check
859+
uint32_t WireVersion = 0; // request format version; absent/0 = original
860+
bool ClientBigEndian = false; // client native byte order; absent = little
790861
// Accuracy parameters
791862
double AccuracyError = 0.0;
792863
double AccuracyNorm = 0.0;
@@ -885,6 +956,41 @@ void XrdSsiSvService::ProcessRequest4Me(XrdSsiRequest *rqstP)
885956
std::size_t pos = param.find("=") + 1;
886957
EngineParams = UrlDecode(param.substr(pos));
887958
}
959+
else if (HasPrefix(param, "FileUUID="))
960+
{
961+
std::size_t pos = param.find("=") + 1;
962+
std::stringstream sstream(param.substr(pos));
963+
sstream >> RequestUUID;
964+
}
965+
else if (HasPrefix(param, "WireVersion="))
966+
{
967+
std::size_t pos = param.find("=") + 1;
968+
std::stringstream sstream(param.substr(pos));
969+
sstream >> WireVersion;
970+
}
971+
else if (HasPrefix(param, "ClientBigEndian="))
972+
{
973+
std::size_t pos = param.find("=") + 1;
974+
std::stringstream sstream(param.substr(pos));
975+
int be = 0;
976+
sstream >> be;
977+
ClientBigEndian = (be != 0);
978+
}
979+
}
980+
if (WireVersion > kMaxWireVersion)
981+
{
982+
LogReject(Filename, "version", 1);
983+
std::string msg = "get: unsupported request protocol version " +
984+
std::to_string(WireVersion) + "; this server supports up to " +
985+
std::to_string(kMaxWireVersion);
986+
RespondErr(msg.c_str(), EINVAL);
987+
return;
988+
}
989+
if (ClientBigEndian != !adios2::helper::IsLittleEndian())
990+
{
991+
LogReject(Filename, "byteorder", 1);
992+
RespondErr("get: cross-endian remote reads not supported", EINVAL);
993+
return;
888994
}
889995
// Get a "anonymous" engine with this file open with this array order.
890996
// (any other differentiating characteristics of an ADIOS Open should be included in these
@@ -901,10 +1007,19 @@ void XrdSsiSvService::ProcessRequest4Me(XrdSsiRequest *rqstP)
9011007
auto poolEntry = m_FilePoolPtr->GetFree(Filename, ArrayOrder, EngineParams);
9021008
auto engine = poolEntry.file->m_engine;
9031009
auto io = poolEntry.file->m_io;
1010+
1011+
// Metadata-identity check (0 = client has no id, enforces nothing).
1012+
if (RequestUUID != 0 && engine.FileUUID() != RequestUUID)
1013+
{
1014+
LogReject(Filename, "identity", 1);
1015+
RespondErr("get: file identity mismatch; remote metadata is stale", EINVAL);
1016+
return;
1017+
}
9041018
adios2::Box<adios2::Dims> varSel(Start, Count);
9051019
adios2::DataType TypeOfVar = io.InquireVariableType(VarName);
9061020
if (TypeOfVar == adios2::DataType::None)
9071021
{
1022+
LogReject(Filename, "novar", 1);
9081023
RespondErr("get: unknown variable", EINVAL);
9091024
return;
9101025
}
@@ -975,6 +1090,7 @@ void XrdSsiSvService::ProcessRequest4Me(XrdSsiRequest *rqstP)
9751090
}
9761091
catch (const std::exception &exc)
9771092
{
1093+
LogReject(Filename, "exception", 1);
9781094
std::string errMsg = std::string("Exception in Get: ") + exc.what();
9791095
RespondErr(errMsg.c_str(), EINVAL);
9801096
}

0 commit comments

Comments
 (0)