| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563 |
- #include "external_entity_codec.h"
- #include <array>
- namespace sunrise::middleware::gameplay::external {
- namespace {
- /** Presence, count, shortcut, and trailing fields use one bit. */
- constexpr std::uint8_t kFlagWidth = 1;
- /** The wire token carries a 13-bit slot. */
- constexpr std::uint8_t kEntitySlotWidth = 13;
- /** The wire token carries a four-bit incarnation. */
- constexpr std::uint8_t kEntityIncarnationWidth = 4;
- /** A raw bubble that fits on wire uses one byte. */
- constexpr std::uint8_t kRawBubbleWidth = 8;
- /** The optional raw-bubble value is limited to one unsigned byte. */
- constexpr std::uint16_t kMaximumRawBubble = 0xFF;
- /** A strict remove declares its one-bit body in a signed 16-bit field. */
- constexpr std::uint8_t kSubrecordLengthWidth = 16;
- /** A create carries one lifecycle byte. */
- constexpr std::uint8_t kLifecycleRevisionWidth = 8;
- /** A create carries one two-bit entity type. */
- constexpr std::uint8_t kEntityTypeWidth = 2;
- /** A strict administrative remove contains only its trailing false bit. */
- constexpr std::uint16_t kStrictRemoveBodyBits = 1;
- /** All flags represented by the explicit five-bit envelope. */
- constexpr std::uint16_t kAllowedFlags =
- entityCreate | entityUpdate | entityRemove | entityLifecycle | entityAnchor;
- /** Explicit flags are sent in this fixed order. */
- constexpr std::array<std::uint16_t, 5> kExplicitFlags{
- entityCreate, entityUpdate, entityRemove, entityLifecycle, entityAnchor};
- /** Measured callback lengths used to reject a changed second pass. */
- struct PayloadPlan {
- std::size_t baselineBits{};
- std::size_t updateBits{};
- };
- /** Reads one boolean field. */
- [[nodiscard]] bool read_flag(encoding::bits::Reader& reader, bool& output) noexcept {
- std::uint64_t value = 0;
- if (!reader.read(kFlagWidth, value)) {
- return false;
- }
- output = value != 0;
- return true;
- }
- /** Writes one boolean field. */
- [[nodiscard]] bool write_flag(encoding::bits::Writer& writer, bool value) noexcept {
- return writer.write(value ? 1U : 0U, kFlagWidth);
- }
- /** @return True when a token fits its 17-bit wire identity. */
- [[nodiscard]] bool valid_token(const EntityToken& token) noexcept {
- return token.slot <= kMaximumEntitySlot && token.incarnation <= kMaximumEntityIncarnation;
- }
- /** @return True when a raw bubble is absent or fits the optional byte. */
- [[nodiscard]] bool valid_raw_bubble(std::uint16_t rawBubble) noexcept {
- return rawBubble == kNoRawBubble || rawBubble <= kMaximumRawBubble;
- }
- /** @return True when a value is one of the four wire entity types. */
- [[nodiscard]] bool valid_type(EntityType type) noexcept {
- return type < EntityType::count;
- }
- /** @return True for create/update records or the exact administrative remove form. */
- [[nodiscard]] bool valid_flags(std::uint16_t flags) noexcept {
- if ((flags & ~kAllowedFlags) != 0) {
- return false;
- }
- if (flags == entityRemove) {
- return true;
- }
- return (flags & entityRemove) == 0 && (flags & (entityCreate | entityUpdate)) != 0;
- }
- /** @return The declared callback limit for one type-payload part. */
- [[nodiscard]] std::size_t payload_limit(const TypePayloadCodec& codec,
- TypePayloadPart part) noexcept {
- return part == TypePayloadPart::baseline ? codec.maximumBaselineBits : codec.maximumUpdateBits;
- }
- /** @return True when callback state and its declared bit limit are bounded. */
- [[nodiscard]] bool valid_payload(const TypePayloadCodec& codec,
- TypePayloadPart part,
- const TypePayload& payload) noexcept {
- return payload.byteCount <= payload.state.size()
- && payload_limit(codec, part) <= kMaximumTypePayloadBits;
- }
- /** Validates the canonical record forms accepted by the generic envelope. */
- [[nodiscard]] bool valid_record(const EntityRecord& record) noexcept {
- if (!valid_token(record.token) || !valid_raw_bubble(record.rawBubble)
- || !valid_flags(record.flags)) {
- return false;
- }
- if (record.flags == entityRemove) {
- return !record.anchorPresent && !record.trailingState && record.lifecycleRevision == 0
- && record.baseline.byteCount == 0 && record.update.byteCount == 0;
- }
- if (!valid_type(record.type) || (record.anchorPresent && (record.flags & entityAnchor) == 0)
- || ((record.flags & entityAnchor) != 0 && record.anchorPresent
- && !valid_token(record.anchor))
- || record.trailingState) {
- return false;
- }
- if ((record.flags & entityCreate) == 0
- && (record.lifecycleRevision != 0 || record.baseline.byteCount != 0)) {
- return false;
- }
- return (record.flags & entityUpdate) != 0 || record.update.byteCount == 0;
- }
- /** Writes one token in slot-then-incarnation order. */
- [[nodiscard]] bool write_token(encoding::bits::Writer& writer, const EntityToken& token) noexcept {
- return valid_token(token) && writer.write(token.slot, kEntitySlotWidth)
- && writer.write(token.incarnation, kEntityIncarnationWidth);
- }
- /** Reads one token without changing output on failure. */
- [[nodiscard]] bool read_token(encoding::bits::Reader& reader, EntityToken& output) noexcept {
- std::uint64_t slot = 0;
- std::uint64_t incarnation = 0;
- if (!reader.read(kEntitySlotWidth, slot)
- || !reader.read(kEntityIncarnationWidth, incarnation)) {
- return false;
- }
- output.slot = static_cast<std::uint16_t>(slot);
- output.incarnation = static_cast<std::uint8_t>(incarnation);
- return true;
- }
- /** Measures one pure payload callback before any envelope bits are written. */
- [[nodiscard]] bool measure_payload(const TypePayloadCodec& codec,
- const EntityRecord& record,
- TypePayloadPart part,
- std::size_t& bitCount) noexcept {
- const TypePayload& payload =
- part == TypePayloadPart::baseline ? record.baseline : record.update;
- if (codec.write == nullptr || !valid_payload(codec, part, payload)) {
- return false;
- }
- encoding::bits::Writer writer = encoding::bits::Writer::measuring();
- std::size_t ignored = 0;
- if (!codec.write(codec.context, record.token, record.type, part, payload, writer)
- || !writer.finish(ignored) || writer.bit_count() > payload_limit(codec, part)) {
- return false;
- }
- bitCount = writer.bit_count();
- return true;
- }
- /** Runs one payload writer and requires the preflight length to remain exact. */
- [[nodiscard]] bool write_payload(const TypePayloadCodec& codec,
- const EntityRecord& record,
- TypePayloadPart part,
- std::size_t expectedBits,
- encoding::bits::Writer& writer) noexcept {
- const TypePayload& payload =
- part == TypePayloadPart::baseline ? record.baseline : record.update;
- const std::size_t before = writer.bit_count();
- std::size_t ignored = 0;
- return codec.write != nullptr
- && codec.write(codec.context, record.token, record.type, part, payload, writer)
- && writer.finish(ignored) && writer.bit_count() >= before
- && writer.bit_count() - before == expectedBits;
- }
- /** Reads one payload through a bounded callback into temporary state. */
- [[nodiscard]] bool read_payload(const TypePayloadCodec& codec,
- const EntityToken& token,
- EntityType type,
- TypePayloadPart part,
- encoding::bits::Reader& reader,
- TypePayload& output) noexcept {
- const std::size_t limit = payload_limit(codec, part);
- if (codec.read == nullptr || limit > kMaximumTypePayloadBits) {
- return false;
- }
- const std::size_t before = reader.remaining_bits();
- TypePayload candidate{};
- std::uint64_t ignored = 0;
- if (!codec.read(codec.context, token, type, part, reader, candidate) || !reader.read(0, ignored)
- || candidate.byteCount > candidate.state.size()) {
- return false;
- }
- const std::size_t after = reader.remaining_bits();
- if (after > before || before - after > limit) {
- return false;
- }
- output = candidate;
- return true;
- }
- /** Resolves the type for an update-only record. */
- [[nodiscard]] bool
- resolve_type(const TypePayloadCodec& codec, const EntityToken& token, EntityType& output) noexcept {
- EntityType candidate = EntityType::count;
- if (codec.resolveType == nullptr || !codec.resolveType(codec.context, token, candidate)
- || !valid_type(candidate)) {
- return false;
- }
- output = candidate;
- return true;
- }
- /** Builds a complete payload plan before the channel-2 header is touched. */
- [[nodiscard]] bool
- prepare_batch(const TypePayloadCodec& codec, const EntityBatch& batch, PayloadPlan& plan) noexcept {
- plan = {};
- if (!valid_raw_bubble(batch.defaultRawBubble)) {
- return false;
- }
- if (!batch.recordPresent) {
- return true;
- }
- if (!valid_record(batch.record)) {
- return false;
- }
- if ((batch.record.flags & entityCreate) != 0
- && !measure_payload(codec, batch.record, TypePayloadPart::baseline, plan.baselineBits)) {
- return false;
- }
- return (batch.record.flags & entityUpdate) == 0
- || measure_payload(codec, batch.record, TypePayloadPart::update, plan.updateBits);
- }
- /** Writes the five explicit flag bits or the update shortcut. */
- [[nodiscard]] bool write_record_flags(encoding::bits::Writer& writer,
- std::uint16_t flags) noexcept {
- const bool shortcut = flags == entityUpdate;
- if (!write_flag(writer, shortcut)) {
- return false;
- }
- if (shortcut) {
- return true;
- }
- for (const std::uint16_t flag : kExplicitFlags) {
- if (!write_flag(writer, (flags & flag) != 0)) {
- return false;
- }
- }
- return true;
- }
- /** Reads the shortcut or five explicit flag bits. */
- [[nodiscard]] bool read_record_flags(encoding::bits::Reader& reader,
- std::uint16_t& output) noexcept {
- bool shortcut = false;
- if (!read_flag(reader, shortcut)) {
- return false;
- }
- if (shortcut) {
- output = entityUpdate;
- return true;
- }
- std::uint16_t flags = 0;
- for (const std::uint16_t flag : kExplicitFlags) {
- bool present = false;
- if (!read_flag(reader, present)) {
- return false;
- }
- if (present) {
- flags |= flag;
- }
- }
- output = flags;
- return true;
- }
- /** Writes one inherited, byte-sized, or reset raw bubble. */
- [[nodiscard]] bool write_record_bubble(encoding::bits::Writer& writer,
- std::uint16_t defaultRawBubble,
- std::uint16_t rawBubble) noexcept {
- const bool changed = rawBubble != defaultRawBubble;
- if (!write_flag(writer, changed)) {
- return false;
- }
- if (!changed) {
- return true;
- }
- const bool fits = rawBubble <= kMaximumRawBubble;
- return write_flag(writer, fits) && (!fits || writer.write(rawBubble, kRawBubbleWidth));
- }
- /** Reads one inherited, byte-sized, or reset raw bubble. */
- [[nodiscard]] bool read_record_bubble(encoding::bits::Reader& reader,
- std::uint16_t defaultRawBubble,
- std::uint16_t& output) noexcept {
- bool changed = false;
- if (!read_flag(reader, changed)) {
- return false;
- }
- if (!changed) {
- output = defaultRawBubble;
- return true;
- }
- bool fits = false;
- if (!read_flag(reader, fits)) {
- return false;
- }
- if (!fits) {
- output = kNoRawBubble;
- return true;
- }
- std::uint64_t bubble = 0;
- if (!reader.read(kRawBubbleWidth, bubble)) {
- return false;
- }
- output = static_cast<std::uint16_t>(bubble);
- return true;
- }
- /** Writes one validated record body after its token and batch default. */
- [[nodiscard]] bool write_record(encoding::bits::Writer& writer,
- const TypePayloadCodec& codec,
- const EntityRecord& record,
- std::uint16_t defaultRawBubble,
- const PayloadPlan& plan) noexcept {
- if (!write_record_flags(writer, record.flags)) {
- return false;
- }
- if ((record.flags & entityAnchor) != 0
- && (!write_flag(writer, record.anchorPresent)
- || (record.anchorPresent && !write_token(writer, record.anchor)))) {
- return false;
- }
- if (!write_record_bubble(writer, defaultRawBubble, record.rawBubble)) {
- return false;
- }
- if (record.flags == entityRemove
- && !writer.write(kStrictRemoveBodyBits, kSubrecordLengthWidth)) {
- return false;
- }
- if ((record.flags & entityCreate) != 0
- && (!writer.write(record.lifecycleRevision, kLifecycleRevisionWidth)
- || !writer.write(static_cast<std::uint8_t>(record.type), kEntityTypeWidth)
- || !write_payload(
- codec, record, TypePayloadPart::baseline, plan.baselineBits, writer))) {
- return false;
- }
- if ((record.flags & entityUpdate) != 0
- && !write_payload(codec, record, TypePayloadPart::update, plan.updateBits, writer)) {
- return false;
- }
- return (record.flags & entityRemove) == 0 || write_flag(writer, record.trailingState);
- }
- /** Reads one record body into temporary state. */
- [[nodiscard]] bool read_record(encoding::bits::Reader& reader,
- const TypePayloadCodec& codec,
- std::uint16_t defaultRawBubble,
- EntityRecord& output) noexcept {
- EntityRecord candidate{};
- candidate.token = output.token;
- if (!read_record_flags(reader, candidate.flags) || !valid_flags(candidate.flags)) {
- return false;
- }
- if ((candidate.flags & entityAnchor) != 0
- && (!read_flag(reader, candidate.anchorPresent)
- || (candidate.anchorPresent && !read_token(reader, candidate.anchor)))) {
- return false;
- }
- if (!read_record_bubble(reader, defaultRawBubble, candidate.rawBubble)) {
- return false;
- }
- if ((candidate.flags & (entityCreate | entityRemove)) == entityRemove) {
- std::uint64_t bitLength = 0;
- if (!reader.read(kSubrecordLengthWidth, bitLength) || bitLength != kStrictRemoveBodyBits) {
- return false;
- }
- }
- if ((candidate.flags & entityCreate) != 0) {
- std::uint64_t revision = 0;
- std::uint64_t type = 0;
- if (!reader.read(kLifecycleRevisionWidth, revision)
- || !reader.read(kEntityTypeWidth, type)) {
- return false;
- }
- candidate.lifecycleRevision = static_cast<std::uint8_t>(revision);
- candidate.type = static_cast<EntityType>(type);
- if (!valid_type(candidate.type)
- || !read_payload(codec,
- candidate.token,
- candidate.type,
- TypePayloadPart::baseline,
- reader,
- candidate.baseline)) {
- return false;
- }
- } else if ((candidate.flags & entityUpdate) != 0
- && !resolve_type(codec, candidate.token, candidate.type)) {
- return false;
- }
- if ((candidate.flags & entityUpdate) != 0
- && !read_payload(codec,
- candidate.token,
- candidate.type,
- TypePayloadPart::update,
- reader,
- candidate.update)) {
- return false;
- }
- if ((candidate.flags & entityRemove) != 0
- && (!read_flag(reader, candidate.trailingState) || candidate.trailingState)) {
- return false;
- }
- if (!valid_record(candidate)) {
- return false;
- }
- output = candidate;
- return true;
- }
- /** Writes a prepared channel-2 batch without repeating callback preflight. */
- [[nodiscard]] bool write_batch_fields(encoding::bits::Writer& writer,
- const TypePayloadCodec& codec,
- const EntityBatch& batch,
- const PayloadPlan& plan) noexcept {
- if (!write_flag(writer, batch.recordPresent)
- || (batch.recordPresent && !write_token(writer, batch.record.token))) {
- return false;
- }
- const bool defaultPresent = batch.defaultRawBubble != kNoRawBubble;
- if (!write_flag(writer, defaultPresent)
- || (defaultPresent && !writer.write(batch.defaultRawBubble, kRawBubbleWidth))) {
- return false;
- }
- return !batch.recordPresent
- || write_record(writer, codec, batch.record, batch.defaultRawBubble, plan);
- }
- /** Reads a channel-2 batch into temporary state. */
- [[nodiscard]] bool read_batch_fields(encoding::bits::Reader& reader,
- const TypePayloadCodec& codec,
- EntityBatch& output) noexcept {
- EntityBatch candidate{};
- if (!read_flag(reader, candidate.recordPresent)
- || (candidate.recordPresent && !read_token(reader, candidate.record.token))) {
- return false;
- }
- bool defaultPresent = false;
- if (!read_flag(reader, defaultPresent)) {
- return false;
- }
- if (defaultPresent) {
- std::uint64_t bubble = 0;
- if (!reader.read(kRawBubbleWidth, bubble)) {
- return false;
- }
- candidate.defaultRawBubble = static_cast<std::uint16_t>(bubble);
- }
- if (candidate.recordPresent
- && !read_record(reader, codec, candidate.defaultRawBubble, candidate.record)) {
- return false;
- }
- output = candidate;
- return true;
- }
- /** Writes common and all four fixed channels using one prepared entity plan. */
- [[nodiscard]] bool write_frame_fields(encoding::bits::Writer& writer,
- const TypePayloadCodec& codec,
- const ExternalEntityFrame& frame,
- const PayloadPlan& plan) noexcept {
- if (!write_flag(writer, frame.commonPresent)
- || (frame.commonPresent && !write_common_state(writer, frame.common))) {
- return false;
- }
- // Channels 0 and 1 are empty until their type registries have server producers.
- if (!write_flag(writer, false) || !write_flag(writer, false)
- || !write_batch_fields(writer, codec, frame.entities, plan)) {
- return false;
- }
- // Channel 3 and its enclosing list stay absent in the generic fallback.
- return write_flag(writer, false) && write_flag(writer, false);
- }
- /** Reads common and all four fixed channels into temporary state. */
- [[nodiscard]] bool read_frame_fields(encoding::bits::Reader& reader,
- const TypePayloadCodec& codec,
- ExternalEntityFrame& output) noexcept {
- ExternalEntityFrame candidate{};
- if (!read_flag(reader, candidate.commonPresent)
- || (candidate.commonPresent && !read_common_state(reader, candidate.common))) {
- return false;
- }
- bool present = false;
- if (!read_flag(reader, present) || present || !read_flag(reader, present) || present
- || !read_batch_fields(reader, codec, candidate.entities) || !read_flag(reader, present)
- || present || !read_flag(reader, present) || present) {
- return false;
- }
- output = candidate;
- return true;
- }
- } // namespace
- /** Reads one channel-2 batch and commits the reader and output only on success. */
- bool read_entity_batch(encoding::bits::Reader& reader,
- const TypePayloadCodec& codec,
- EntityBatch& output) noexcept {
- encoding::bits::Reader candidateReader = reader;
- EntityBatch candidate{};
- if (!read_batch_fields(candidateReader, codec, candidate)) {
- return false;
- }
- reader = candidateReader;
- output = candidate;
- return true;
- }
- /** Writes one channel-2 batch after a complete fail-closed preflight. */
- bool write_entity_batch(encoding::bits::Writer& writer,
- const TypePayloadCodec& codec,
- const EntityBatch& batch) noexcept {
- PayloadPlan plan{};
- if (!prepare_batch(codec, batch, plan)) {
- return false;
- }
- encoding::bits::Writer measuring = encoding::bits::Writer::measuring();
- std::size_t ignored = 0;
- if (!write_batch_fields(measuring, codec, batch, plan) || !measuring.finish(ignored)) {
- return false;
- }
- return write_batch_fields(writer, codec, batch, plan);
- }
- /** Reads the fixed four-channel wrapper and commits no state on failure. */
- bool read_external_entity_frame(encoding::bits::Reader& reader,
- const TypePayloadCodec& codec,
- ExternalEntityFrame& output) noexcept {
- encoding::bits::Reader candidateReader = reader;
- ExternalEntityFrame candidate{};
- if (!read_frame_fields(candidateReader, codec, candidate)) {
- return false;
- }
- reader = candidateReader;
- output = candidate;
- return true;
- }
- /** Writes the fixed four-channel wrapper after a complete fail-closed preflight. */
- bool write_external_entity_frame(encoding::bits::Writer& writer,
- const TypePayloadCodec& codec,
- const ExternalEntityFrame& frame) noexcept {
- PayloadPlan plan{};
- if (!prepare_batch(codec, frame.entities, plan)) {
- return false;
- }
- encoding::bits::Writer measuring = encoding::bits::Writer::measuring();
- std::size_t ignored = 0;
- if (!write_frame_fields(measuring, codec, frame, plan) || !measuring.finish(ignored)) {
- return false;
- }
- return write_frame_fields(writer, codec, frame, plan);
- }
- } // namespace sunrise::middleware::gameplay::external
|