// MobileGL - MobileGL/MG_Remote/CapsCodec.h // Copyright (c) 2025-2026 MobileGL-Dev // Licensed under the GNU Lesser General Public License v3.0: // https://www.gnu.org/licenses/gpl-3.0.txt // https://www.gnu.org/licenses/lgpl-3.0.txt // SPDX-License-Identifier: LGPL-3.0-only // End of Source File Header // The MGPCaps snapshot's serializers, and the CallMask encoding. Owner: package w1 (the two // blob codecs) and s1 (the handshake asserts). Signatures by c0. // // MGPCaps (MG_Pipe/MGPipeTypes.h:126-139) has four members. Two are flat and cross by memcpy // (DynamicBackendParameters Dynamic, Uint64 CallMask); two are MGPBlobRefs over containers // and HAVE NO SERIALIZER IN THE TREE - the header says so itself at MGPipeTypes.h:134-136, // "Their serializers land with the transport (P5)". They are: // // FormatCapabilities -> FormatCapabilityCache (MG_Backend/BackendObject.h:93-98): // FullCaps + CaveatCaps (bitfield tables) + SampleCounts, a Vector per // (target, format) pair. The Vector is why this cannot be a memcpy. // RendererInfo -> RendererInfo (MG_Util/Types.h:317): three Strings, an // Optional, and a Vector inside GLInfo. // // THE ABI RULING (table 0). MGPCaps has only a COMPOSITIONAL size assertion // (MGPipeTypes.h:145-146) because DynamicBackendParameters still carries SizeT and GLenum // members - P0.5's fixed-width rewrite did not happen. P5 does NOT rewrite it. Instead the // handshake asserts that both peers agree on sizeof(DynamicBackendParameters), // sizeof(MGPCaps) and the build fingerprint, and Fatal{AbiMismatch} otherwise. P6's spawn is // same-machine and same-binary, so it inherits this unchanged; the fixed-width rewrite is on // P7's account. #pragma once #include #include #include #include "Transport/SessionRings.h" // AbiFingerprintInputs / MixAbiFingerprint namespace MobileGL::MG_Remote { // ---- CallMask's layout (c0's ruling, extending R-8) --------------------------------- // // R-8 requires the client's liveness gates - MGPipeResourceSubsystemEnabled() and // P4aFamilyHasItsConsumer() - to answer from MGPCaps::CallMask instead of from // MGPipeGetResourceOps(), because that op table is the SERVER's registration: under // inproc a client reading it is right by accident, and under spawn it is null and five // whole record families silently emit nothing. // // But CallMask as declared carries only the nine MGPCapBit FEATURE bits // (MGPipeTypes.h:108-124) and has no per-family bit at all, so "read the CallMask" was // not yet an implementable instruction. It is now: // // bits 0..8 MGPCapBit, unchanged. kCapNeedsHostIndexBytes and kCapNeedsHostUboBytes // are BOTH ZERO in P5 by ruling (table 0), which is what keeps every // MGHostSpan out of the first IPC frame. // bits 9..31 reserved for further MGPCapBits. // bits 32..47 THE CONSUMER MASK: bit (32 + n) means "the server has a consumer for // MGPipe subsystem bit n" - i.e. the server's own subsystem mask, shifted. // Sixteen bits covers bits 0..12 allocated through P4a with room to P8. // bits 48..63 reserved. // // protocol.fbs:94's `tableSlotMask: ulong` is DELETED rather than renamed (R-8 offered // either). Two reasons, and the second is decisive: ARCHITECTURE.md:114 says CallMask // REPLACES "is this table slot null" as the capability probe, so a field whose comment is // "which GLFunctionsTable slots the peer registered" re-introduces exactly what it // replaced; and GLFunctionsTable has SIXTY-NINE function-pointer slots // (BackendObject.h:117-292), so a 64-bit mask cannot address it and never could. inline constexpr Uint32 kMGCapsConsumerBitShift = 32; inline constexpr Uint64 kMGCapsConsumerMask = 0xFFFFull << kMGCapsConsumerBitShift; // Server side: fold the subsystems this server actually consumes into a CallMask. inline constexpr Uint64 MGCapsConsumerBits(Uint64 subsystemMask) { return (subsystemMask & 0xFFFFull) << kMGCapsConsumerBitShift; } // Client side: the ONE legal spelling of "does the server consume this family". // `subsystemBit` is a kMGPipeSubsystem* constant (MG_Pipe/MGPipe.h), not an index. inline constexpr Bool MGCapsServerConsumes(Uint64 callMask, Uint64 subsystemBit) { return (callMask & MGCapsConsumerBits(subsystemBit)) != 0; } // ---- the two blob codecs ------------------------------------------------------------ // // Byte-stable within one build; the handshake's fingerprint is what makes that enough. // Both decoders must tolerate a truncated or over-long buffer by returning false, never // by reading past `size`: these bytes arrive over the wire. Bool EncodeFormatCapabilities(const MG_Backend::FormatCapabilityCache& cache, Vector& out); Bool DecodeFormatCapabilities(const void* bytes, Uint64 size, MG_Backend::FormatCapabilityCache& out); Bool EncodeRendererInfo(const RendererInfo& info, Vector& out); Bool DecodeRendererInfo(const void* bytes, Uint64 size, RendererInfo& out); // ---- the ABI assertion the handshake carries ---------------------------------------- // // The inputs, as this build sees them: sizeof(DynamicBackendParameters), sizeof(MGPCaps), // sizeof(GLFunctionsTable), the format-capability table's extents, the two caps-blob codec // versions, MGPWireOp::kOpCount, the protocol ABI version and the compile-time git stamp. // Public so that the sensitivity control can pin every one of them to the real value AND // perturb them one at a time through the same mixer the handshake uses. Transport::AbiFingerprintInputs CapsAbiFingerprintInputs(); // What Hello/Welcome carry and compare: EXACTLY Transport::MixAbiFingerprint( // CapsAbiFingerprintInputs()) - one implementation, no second hash (ID-46 finding 6). A // mismatch is Fatal{AbiMismatch} and never a downgrade, because every alternative silently // reads one struct as another. Uint64 CapsAbiFingerprint(); } // namespace MobileGL::MG_Remote