载入中...
搜索中...
未找到
Snapshot.cpp
浏览该文件的文档.
1#include "common/Snapshot.h"
2
3#include <charconv>
4#include <cstdint>
5#include <limits>
6#include <set>
7#include <utility>
8
9namespace eve {
10namespace {
11
12bool hasOnlyEnvelopeFields(const Value::Object& object) {
13 static const std::set<std::string> fields = {"contentHash", "instanceId", "payload", "revision",
14 "schema", "schemaVersion", "tick", "type"};
15 for (const auto& entry : object)
16 if (!fields.contains(entry.first)) return false;
17 return true;
18}
19
20Result<std::uint64_t> readUint64(const Value& value, std::string_view field) {
21 const auto* text = value.getIf<std::string>();
22 if (!text || text->empty())
24 DiagnosticCode::ParseError, "snapshot integer must be a decimal string", std::string(field)));
25 std::uint64_t output = 0;
26 const auto [end, error] = std::from_chars(text->data(), text->data() + text->size(), output);
27 if (error != std::errc{} || end != text->data() + text->size())
29 Diagnostic::error(DiagnosticCode::ParseError, "snapshot integer is out of range", std::string(field)));
31}
32
33Result<std::string> canonicalHashInputFor(const SnapshotEnvelope& snapshot) {
35 object.emplace("instanceId", Value(snapshot.instanceId.format()));
36 object.emplace("payload", snapshot.payload);
37 object.emplace("revision", Value(std::to_string(snapshot.revision.value())));
38 object.emplace("schema", Value(snapshot.schema.format()));
39 object.emplace("schemaVersion", Value(std::to_string(snapshot.schemaVersion.value())));
40 object.emplace("tick", Value(std::to_string(snapshot.tick.value())));
41 object.emplace("type", Value(snapshot.type));
42 return Value(std::move(object)).toJson();
43}
44
45Result<void> validateHeader(const SnapshotEnvelope& snapshot) {
46 if (snapshot.type.empty())
48 Diagnostic::error(DiagnosticCode::InvalidArgument, "snapshot type must not be empty", "type"));
49 if (!snapshot.schema.isValid())
51 Diagnostic::error(DiagnosticCode::InvalidArgument, "snapshot schema must be a valid logical ID", "schema"));
52 return Result<void>::success();
53}
54
55} // namespace
56
58 auto valid = validateHeader(snapshot);
59 if (!valid.ok()) return Result<std::string>::failure(valid.status());
60 return canonicalHashInputFor(snapshot);
61}
62
64 auto valid = validateHeader(snapshot);
65 if (!valid.ok()) return valid;
66 if (!hashProvider)
68 Diagnostic::error(DiagnosticCode::Unsupported, "snapshot hash provider is required", "contentHash"));
69
70 auto input = snapshotHashInput(snapshot);
71 if (!input.ok()) return Result<void>::failure(input.status());
72 auto computed = hashProvider(input.value());
73 if (!computed.ok()) return Result<void>::failure(computed.status());
74 if (std::move(computed).takeValue() != snapshot.contentHash)
76 Diagnostic::error(DiagnosticCode::HashMismatch, "snapshot content hash does not match", "contentHash"));
77 return Result<void>::success();
78}
79
81 const auto* object = payload.getIf<Value::Object>();
82 if (!object) return Result<void>::success();
83
84 const auto validate = [&](std::string_view field, std::uint64_t expected) -> Result<void> {
85 const auto found = object->find(std::string(field));
86 if (found == object->end()) return Result<void>::success();
87 auto parsed = readUint64(found->second, field);
88 if (!parsed.ok()) return Result<void>::failure(parsed.status());
89 if (parsed.value() != expected)
91 DiagnosticCode::Conflict, "snapshot payload " + std::string(field) + " disagrees with the envelope",
92 "payload." + std::string(field)));
93 return Result<void>::success();
94 };
95
96 auto revisionResult = validate("revision", revision.value());
97 if (!revisionResult.ok()) return revisionResult;
98 auto tickResult = validate("tick", tick.value());
99 if (!tickResult.ok()) return tickResult;
100 return Result<void>::success();
101}
102
105 Value payload, const SnapshotHashProvider& hashProvider) {
106 SnapshotEnvelope snapshot{std::move(type), std::move(schema), schemaVersion, instanceId, revision, tick,
107 ContentId::nil(), std::move(payload)};
108 auto valid = validateHeader(snapshot);
109 if (!valid.ok()) return Result<SnapshotEnvelope>::failure(valid.status());
110 if (!hashProvider)
112 Diagnostic::error(DiagnosticCode::Unsupported, "snapshot hash provider is required", "contentHash"));
113 auto input = snapshotHashInput(snapshot);
114 if (!input.ok()) return Result<SnapshotEnvelope>::failure(input.status());
115 auto hash = hashProvider(input.value());
116 if (!hash.ok()) return Result<SnapshotEnvelope>::failure(hash.status());
117 snapshot.contentHash = std::move(hash).takeValue();
118 return Result<SnapshotEnvelope>::success(std::move(snapshot));
119}
120
122 auto valid = validateHeader(snapshot);
123 if (!valid.ok()) return Result<Value>::failure(valid.status());
125 object.emplace("contentHash", Value(snapshot.contentHash.format()));
126 object.emplace("instanceId", Value(snapshot.instanceId.format()));
127 object.emplace("payload", snapshot.payload);
128 object.emplace("revision", Value(std::to_string(snapshot.revision.value())));
129 object.emplace("schema", Value(snapshot.schema.format()));
130 object.emplace("schemaVersion", Value(std::to_string(snapshot.schemaVersion.value())));
131 object.emplace("tick", Value(std::to_string(snapshot.tick.value())));
132 object.emplace("type", Value(snapshot.type));
133 return Result<Value>::success(Value(std::move(object)));
134}
135
137 const auto* object = value.getIf<Value::Object>();
138 if (!object || !hasOnlyEnvelopeFields(*object))
140 DiagnosticCode::ParseError, "snapshot envelope must be an object with the exact public fields", {}));
141
142 const auto type = value.getIf<Value::Object>()->find("type");
143 const auto schemaText = object->find("schema");
144 const auto schemaVersion = object->find("schemaVersion");
145 const auto instanceId = object->find("instanceId");
146 const auto revision = object->find("revision");
147 const auto tick = object->find("tick");
148 const auto contentHash = object->find("contentHash");
149 const auto payload = object->find("payload");
150 if (type == object->end() || schemaText == object->end() || schemaVersion == object->end() ||
151 instanceId == object->end() || revision == object->end() || tick == object->end() ||
152 contentHash == object->end() || payload == object->end() || !type->second.isString() ||
153 !schemaText->second.isString() || !instanceId->second.isString() || !contentHash->second.isString())
155 Diagnostic::error(DiagnosticCode::ParseError, "snapshot envelope has invalid fields", {}));
156
157 const auto schema = LogicalId::parse(*schemaText->second.getIf<std::string>());
158 const auto persistentId = PersistentId::parse(*instanceId->second.getIf<std::string>());
159 const auto contentId = ContentId::parse(*contentHash->second.getIf<std::string>());
160 if (!schema || !persistentId || !contentId)
162 Diagnostic::error(DiagnosticCode::ParseError, "snapshot envelope has invalid identity fields", {}));
163
164 auto version = readUint64(schemaVersion->second, "schemaVersion");
165 if (!version.ok()) return Result<SnapshotEnvelope>::failure(version.status());
166 auto revisionValue = readUint64(revision->second, "revision");
167 if (!revisionValue.ok()) return Result<SnapshotEnvelope>::failure(revisionValue.status());
168 auto tickValue = readUint64(tick->second, "tick");
169 if (!tickValue.ok()) return Result<SnapshotEnvelope>::failure(tickValue.status());
170
171 SnapshotEnvelope snapshot{*type->second.getIf<std::string>(),
172 *schema,
173 SchemaVersion(std::move(version).takeValue()),
174 *persistentId,
175 Revision(std::move(revisionValue).takeValue()),
176 SimulationTick(std::move(tickValue).takeValue()),
177 *contentId,
178 payload->second};
179 auto verified = verifySnapshotEnvelope(snapshot, hashProvider);
180 if (!verified.ok()) return Result<SnapshotEnvelope>::failure(verified.status());
181 return Result<SnapshotEnvelope>::success(std::move(snapshot));
182}
183
185 auto value = snapshotEnvelopeValue(snapshot);
186 if (!value.ok()) return Result<std::string>::failure(value.status());
187 return std::move(value).andThen([](Value&& owned) { return owned.toJson(); });
188}
189
190Result<SnapshotEnvelope> parseSnapshotEnvelope(std::string_view json, const SnapshotHashProvider& hashProvider) {
191 auto value = Value::fromJson(json);
192 if (!value.ok()) return Result<SnapshotEnvelope>::failure(value.status());
193 return parseSnapshotEnvelopeValue(value.value(), hashProvider);
194}
195
197 if (!schema.isValid() || from.value() >= to.value() || !migration)
199 Diagnostic::error(DiagnosticCode::InvalidArgument, "invalid snapshot migration edge", {}));
200 const Key key{schema.format(), from.value()};
201 if (steps_.contains(key))
203 Diagnostic::error(DiagnosticCode::Conflict, "snapshot migration edge already exists", {}));
204 steps_.emplace(key, Step{to, std::move(migration)});
205 return Result<void>::success();
206}
207
209 const SnapshotHashProvider& hashProvider) const {
210 auto verified = verifySnapshotEnvelope(snapshot, hashProvider);
211 if (!verified.ok()) return Result<SnapshotEnvelope>::failure(verified.status());
212 if (snapshot.schemaVersion.value() > targetVersion.value())
215 "snapshot schema version is newer than the supported target", "schemaVersion"));
216 if (snapshot.schemaVersion == targetVersion) return Result<SnapshotEnvelope>::success(std::move(snapshot));
217
218 while (snapshot.schemaVersion.value() < targetVersion.value()) {
219 const Key key{snapshot.schema.format(), snapshot.schemaVersion.value()};
220 const auto step = steps_.find(key);
221 if (step == steps_.end())
224 "no snapshot migration step reaches the requested version", "schemaVersion"));
225 if (step->second.to.value() > targetVersion.value())
228 "snapshot migration step overshoots the requested version", "schemaVersion"));
229 auto payload = step->second.migration(snapshot.payload);
230 if (!payload.ok()) return Result<SnapshotEnvelope>::failure(payload.status());
231 snapshot.payload = std::move(payload).takeValue();
232 snapshot.schemaVersion = step->second.to;
233 }
234
235 return makeSnapshotEnvelope(snapshot.type, snapshot.schema, snapshot.schemaVersion, snapshot.instanceId,
236 snapshot.revision, snapshot.tick, std::move(snapshot.payload), hashProvider);
237}
238
239} // namespace eve
double value
Value::Object payload
std::string output
std::string from
ActiveSource owned
std::array< std::uint8_t, 32 > hash
Definition Evpack.cpp:172
EvpackChunkInput input
Definition Evpack.cpp:170
std::uint32_t key
std::int32_t first
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
std::string text
bool valid
std::string error
Definition Package.cpp:60
std::uint64_t revision
std::vector< std::string > fields
Definition PlayHost.cpp:111
bool found
Versioned, integrity-checked persistence envelopes.
SimulationTick tick
Json object
float step
Definition TreeMesh.cpp:314
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Human-readable, scoped identifier in the form namespace:name.
Definition Identity.h:411
const std::string & format() const noexcept
Returns the canonical namespace:name representation.
Definition Identity.h:436
static std::optional< LogicalId > parse(std::string_view text)
Parses a scoped logical name.
Definition Identity.cpp:36
bool isValid() const noexcept
Returns whether this value contains a valid namespace and name.
Definition Identity.h:433
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
std::function< Result< Value >(const Value &payload)> Migration
Definition Snapshot.h:147
Result< SnapshotEnvelope > migrate(SnapshotEnvelope snapshot, SchemaVersion targetVersion, const SnapshotHashProvider &hashProvider) const
Migrate and reseal an envelope to an exact target version.
Definition Snapshot.cpp:208
Result< void > add(LogicalId schema, SchemaVersion from, SchemaVersion to, Migration migration)
Register one schema-local migration edge.
Definition Snapshot.cpp:196
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
static Result< Value > fromJson(std::string_view json)
Parse one strict JSON value into an owning Value.
Definition Value.cpp:57
std::map< std::string, Value > Object
Definition Value.h:34
Id128 public API.
Definition Identity.h:112
std::string format() const
Formats the ID in lower-case canonical UUID text.
Definition Identity.h:173
static constexpr Id128 nil() noexcept
Returns the all-zero nil value.
Definition Identity.h:142
static std::optional< Id128 > parse(std::string_view text) noexcept
Parses canonical UUID text.
Definition Identity.h:151
constexpr std::uint64_t value() const noexcept
Returns the underlying value at an explicit protocol boundary.
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16
Result< Value > snapshotEnvelopeValue(const SnapshotEnvelope &snapshot)
Convert an envelope to its strict canonical value representation.
Definition Snapshot.cpp:121
Result< void > validateSnapshotPayloadMetadata(const Value &payload, Revision revision, SimulationTick tick)
Validate optional payload copies of envelope revision and tick.
Definition Snapshot.cpp:80
Result< SnapshotEnvelope > parseSnapshotEnvelope(std::string_view json, const SnapshotHashProvider &hashProvider)
Parse and verify an envelope from canonical or compatible JSON text.
Definition Snapshot.cpp:190
detail::StrongUint64< detail::SchemaVersionTag > SchemaVersion
Persistent data-format version; not a runtime replacement generation.
Result< std::string > snapshotHashInput(const SnapshotEnvelope &snapshot)
Produce the canonical JSON input used for a snapshot content hash.
Definition Snapshot.cpp:57
Result< SnapshotEnvelope > parseSnapshotEnvelopeValue(const Value &value, const SnapshotHashProvider &hashProvider)
Parse and verify an envelope from an owning Value.
Definition Snapshot.cpp:136
Result< std::string > serializeSnapshotEnvelope(const SnapshotEnvelope &snapshot)
Serialize an envelope as deterministic compact JSON.
Definition Snapshot.cpp:184
detail::StrongUint64< detail::SimulationTickTag > SimulationTick
Deterministic simulation time step; it is not wall-clock time.
Definition Time.h:31
Result< SnapshotEnvelope > makeSnapshotEnvelope(std::string type, LogicalId schema, SchemaVersion schemaVersion, PersistentId instanceId, Revision revision, SimulationTick tick, Value payload, const SnapshotHashProvider &hashProvider)
Construct and seal a snapshot envelope.
Definition Snapshot.cpp:103
detail::StrongUint64< detail::RevisionTag > Revision
Monotonic content/state revision used for optimistic-concurrency checks.
Definition Revision.h:13
Result< void > verifySnapshotEnvelope(const SnapshotEnvelope &snapshot, const SnapshotHashProvider &hashProvider)
Verify an envelope's content hash without modifying it.
Definition Snapshot.cpp:63
std::function< Result< ContentId >(std::string_view canonicalInput)> SnapshotHashProvider
Injected content-digest implementation used by snapshots.
Definition Snapshot.h:36
Stable outer format shared by persistence and cross-process snapshots.
Definition Snapshot.h:46
SchemaVersion schemaVersion
Definition Snapshot.h:49
std::string type
Definition Snapshot.h:47
ContentId contentHash
Definition Snapshot.h:53
PersistentId instanceId
Definition Snapshot.h:50
SimulationTick tick
Definition Snapshot.h:52