LCOV - code coverage report
Current view: top level - source/simulation2/serialization - ISerializer.h (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 35 36 97.2 %
Date: 2023-01-19 00:18:29 Functions: 15 21 71.4 %

          Line data    Source code
       1             : /* Copyright (C) 2020 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #ifndef INCLUDED_ISERIALIZER
      19             : #define INCLUDED_ISERIALIZER
      20             : 
      21             : #include "maths/Fixed.h"
      22             : #include "ps/Errors.h"
      23             : #include "scriptinterface/ScriptTypes.h"
      24             : 
      25           5 : ERROR_GROUP(Serialize);
      26           2 : ERROR_TYPE(Serialize, OutOfBounds);
      27           0 : ERROR_TYPE(Serialize, InvalidCharInString);
      28           1 : ERROR_TYPE(Serialize, ScriptError);
      29           2 : ERROR_TYPE(Serialize, InvalidScriptValue);
      30             : 
      31             : /**
      32             :  * @page serialization Component serialization
      33             :  *
      34             :  * This page gives a high-level description of ISerializer and IDeserializer.
      35             :  *
      36             :  * @section design Rough design notes
      37             :  *
      38             :  * We want to handle the following serialization-related features:
      39             :  * - Out-of-sync detection: Based on hash of simulation state.
      40             :  *   Must be fast.
      41             :  * - Debugging OOS failures: Human-readable output of simulation state,
      42             :  *   that can be diffed against other states.
      43             :  * - Saving and loading games to disk: Machine-readable output of state.
      44             :  *   Should remain usable after patches to the game.
      45             :  * - Joining in-progress network games: Machine-readable output of state.
      46             :  *   Must be compact (minimise network traffic).
      47             :  *   Must be secure (no remote code execution from invalid network data).
      48             :  *
      49             :  * All cases must be portable across OSes, CPU architectures (endianness, bitness),
      50             :  * compilers, compiler settings, etc.
      51             :  *
      52             :  * This is designed for serializing components, which we consider to have three main kinds of state:
      53             :  * - Basic deterministic state: Stuff that's critical to the simulation state,
      54             :  *   and is identical between all players in a network game.
      55             :  *   Always needs to be saved and loaded and hashed and dumped etc.
      56             :  * - Derived deterministic state: Stuff that can be identically recomputed from
      57             :  *   the basic deterministic state,
      58             :  *   but typically is stored in memory as an optimisation (e.g. caches).
      59             :  *   Shouldn't be saved to disk/network (to save space), and should be recomputed on loading.
      60             :  *   Should be included in hashes and debug dumps, to detect errors sooner.
      61             :  * - Non-deterministic state: Stuff that's not part of the simulation state,
      62             :  *   and may vary between players in a network game,
      63             :  *   but is kept for graphics or for optimisations etc.
      64             :  *   Shouldn't be saved or hashed or dumped or anything.
      65             :  *
      66             :  * (TODO: Versioning (for saved games to survive patches) is not yet implemented.)
      67             :  *
      68             :  * We have separate serialization and deserialization APIs (thus requiring components
      69             :  * to implement separate serialize/deserialize functions), rather than a single unified
      70             :  * API (that can either serialize or deserialize depending on the particular instance).
      71             :  * This means more work for simple components, but it simplifies complex components (those
      72             :  * which have versioning, or recompute derived state) since they don't need lots of
      73             :  * "if (IsDeserializing()) ..." checks.
      74             :  *
      75             :  * Callers must use the same sequence of IDeserializer calls as they used
      76             :  * ISerializer calls. For efficiency, serializations typically don't encode
      77             :  * the data type, and so mismatches will make them read bogus values and
      78             :  * hopefully hit an out-of-bounds exception.
      79             :  *
      80             :  * The ISerializable interface used by some code in the engine is irrelevant here. Serialization
      81             :  * of interesting classes is implemented in this module, not in the classes themselves, so that
      82             :  * the serialization system has greater control (e.g. the human-readable debug output can handle
      83             :  * classes very differently to binary output).
      84             :  *
      85             :  * To encourage portability, only fixed-size types are exposed in the API (e.g. int32_t,
      86             :  * not int). Components should use fixed-size types internally, to ensure deterministic
      87             :  * simulation across 32-bit and 64-bit architectures.
      88             :  *
      89             :  * TODO: At least some compound types like lists ought to be supported somehow.
      90             :  *
      91             :  * To encourage security, the API accepts bounds on numbers and string lengths etc,
      92             :  * and will fail if the data exceeds the bounds. Callers should provide sensible bounds
      93             :  * (they must never be exceeded, but the caller should be able to safely cope with any values
      94             :  * within the bounds and not crash or run out of memory etc).
      95             :  *
      96             :  * For the OOS debugging output, the API requires field names for all values. These are
      97             :  * only used for human-readable output, so they should be readable but don't have to
      98             :  * be unique or follow any particular pattern.
      99             :  *
     100             :  * The APIs throw exceptions whenever something fails (all errors are fatal).
     101             :  * Component (de)serialization functions must be exception-safe.
     102             :  */
     103             : 
     104             : /*
     105             :  * Implementation details:
     106             :  *
     107             :  * The current implementation has lots of nested virtual function calls,
     108             :  * to maximise flexibility. If this turns out to be too inefficient,
     109             :  * it'll need to be rewritten somehow with less virtuals (probably using
     110             :  * templates instead). (This is a more generic design than will be needed
     111             :  * in practice.)
     112             :  *
     113             :  * The public API functions do bounds checking, then pass the data
     114             :  * to the Put* methods, which subclasses must handle.
     115             :  */
     116             : 
     117             : /**
     118             :  * Serialization interface; see @ref serialization "serialization overview".
     119             :  */
     120         141 : class ISerializer
     121             : {
     122             : public:
     123             :     virtual ~ISerializer();
     124             : 
     125             :     /**
     126             :      * Serialize a number, which must be lower <= value <= upper.
     127             :      * The same bounds must be used when deserializing the number.
     128             :      * This should be used in preference to the Unbounded functions, to
     129             :      * detect erroneous or malicious values that might cause problems when used.
     130             :      * @throw PSERROR_Serialize_OutOfBounds if value is out of bounds
     131             :      * @throw PSERROR_Serialize for any other errors
     132             :      * @param name informative name for debug output
     133             :      * @param value value to serialize
     134             :      * @param lower inclusive lower bound
     135             :      * @param upper inclusive upper bound
     136             :      */
     137             :     void NumberU8(const char* name, uint8_t value, uint8_t lower, uint8_t upper);
     138             :     void NumberI8(const char* name, int8_t value, int8_t lower, int8_t upper);
     139             :     void NumberU16(const char* name, uint16_t value, uint16_t lower, uint16_t upper); ///< @copydoc NumberU8
     140             :     void NumberI16(const char* name, int16_t value, int16_t lower, int16_t upper); ///< @copydoc NumberU8
     141             :     void NumberU32(const char* name, uint32_t value, uint32_t lower, uint32_t upper); ///< @copydoc NumberU8
     142             :     void NumberI32(const char* name, int32_t value, int32_t lower, int32_t upper); ///< @copydoc NumberU8
     143             : 
     144             :     /**
     145             :      * Serialize a number.
     146             :      * @throw PSERROR_Serialize for any errors
     147             :      * @param name informative name for debug output
     148             :      * @param value value to serialize
     149             :      */
     150         391 :     void NumberU8_Unbounded(const char* name, uint8_t value)
     151             :     {
     152             :         // (These functions are defined inline for efficiency)
     153         391 :         PutNumber(name, value);
     154         391 :     }
     155             : 
     156           2 :     void NumberI8_Unbounded(const char* name, int8_t value) ///@copydoc NumberU8_Unbounded()
     157             :     {
     158           2 :         PutNumber(name, value);
     159           2 :     }
     160             : 
     161          10 :     void NumberU16_Unbounded(const char* name, uint16_t value) ///@copydoc NumberU8_Unbounded()
     162             :     {
     163          10 :         PutNumber(name, value);
     164          10 :     }
     165             : 
     166           2 :     void NumberI16_Unbounded(const char* name, int16_t value) ///@copydoc NumberU8_Unbounded()
     167             :     {
     168           2 :         PutNumber(name, value);
     169           2 :     }
     170             : 
     171         550 :     void NumberU32_Unbounded(const char* name, uint32_t value) ///@copydoc NumberU8_Unbounded()
     172             :     {
     173         550 :         PutNumber(name, value);
     174         550 :     }
     175             : 
     176         151 :     void NumberI32_Unbounded(const char* name, int32_t value) ///@copydoc NumberU8_Unbounded()
     177             :     {
     178         151 :         PutNumber(name, value);
     179         151 :     }
     180             : 
     181          10 :     void NumberFloat_Unbounded(const char* name, float value) ///@copydoc NumberU8_Unbounded()
     182             :     {
     183          10 :         PutNumber(name, value);
     184          10 :     }
     185             : 
     186          30 :     void NumberDouble_Unbounded(const char* name, double value) ///@copydoc NumberU8_Unbounded()
     187             :     {
     188          30 :         PutNumber(name, value);
     189          30 :     }
     190             : 
     191         165 :     void NumberFixed_Unbounded(const char* name, fixed value) ///@copydoc NumberU8_Unbounded()
     192             :     {
     193         165 :         PutNumber(name, value);
     194         165 :     }
     195             : 
     196             :     /**
     197             :      * Serialize a boolean.
     198             :      */
     199         271 :     void Bool(const char* name, bool value)
     200             :     {
     201         271 :         PutBool(name, value);
     202         271 :     }
     203             : 
     204             :     /**
     205             :      * Serialize an ASCII string.
     206             :      * Characters must be in the range U+0001 .. U+00FF (inclusive).
     207             :      * The string must be between minlength .. maxlength (inclusive) characters.
     208             :      */
     209             :     void StringASCII(const char* name, const std::string& value, uint32_t minlength, uint32_t maxlength);
     210             : 
     211             :     /**
     212             :      * Serialize a Unicode string.
     213             :      * Characters must be in the range U+0000..U+D7FF, U+E000..U+FDCF, U+FDF0..U+FFFD (inclusive).
     214             :      * The string must be between minlength .. maxlength (inclusive) characters.
     215             :      */
     216             :     void String(const char* name, const std::wstring& value, uint32_t minlength, uint32_t maxlength);
     217             : 
     218             :     /**
     219             :      * Serialize a JS::MutableHandleValue.
     220             :      * The value must not contain any unserializable values (like functions).
     221             :      * NOTE: We have to use a mutable handle because JS_Stringify requires that for unknown reasons.
     222             :      */
     223             :     void ScriptVal(const char* name, JS::MutableHandleValue value);
     224             : 
     225             :     /**
     226             :      * Serialize a stream of bytes.
     227             :      * It is the caller's responsibility to deal with portability (padding, endianness, etc);
     228             :      * the typed serialize methods should usually be used instead of this.
     229             :      */
     230             :     void RawBytes(const char* name, const u8* data, size_t len);
     231             : 
     232             :     /**
     233             :      * Returns true if the serializer is being used in debug mode.
     234             :      * Components should serialize non-critical data (e.g. data that is unchanged
     235             :      * from the template) only if this is true. (It must still only be used
     236             :      * for synchronised, deterministic data.)
     237             :      */
     238             :     virtual bool IsDebug() const;
     239             : 
     240             :     /**
     241             :      * Returns a stream which can be used to serialize data directly.
     242             :      * (This is particularly useful for chaining multiple serializers
     243             :      * together.)
     244             :      */
     245             :     virtual std::ostream& GetStream() = 0;
     246             : 
     247             : protected:
     248             :     virtual void PutNumber(const char* name, uint8_t value) = 0;
     249             :     virtual void PutNumber(const char* name, int8_t value) = 0;
     250             :     virtual void PutNumber(const char* name, uint16_t value) = 0;
     251             :     virtual void PutNumber(const char* name, int16_t value) = 0;
     252             :     virtual void PutNumber(const char* name, uint32_t value) = 0;
     253             :     virtual void PutNumber(const char* name, int32_t value) = 0;
     254             :     virtual void PutNumber(const char* name, float value) = 0;
     255             :     virtual void PutNumber(const char* name, double value) = 0;
     256             :     virtual void PutNumber(const char* name, fixed value) = 0;
     257             :     virtual void PutBool(const char* name, bool value) = 0;
     258             :     virtual void PutString(const char* name, const std::string& value) = 0;
     259             :     // We have to use a mutable handle because JS_Stringify requires that for unknown reasons.
     260             :     virtual void PutScriptVal(const char* name, JS::MutableHandleValue value) = 0;
     261             :     virtual void PutRaw(const char* name, const u8* data, size_t len) = 0;
     262             : };
     263             : 
     264             : #endif // INCLUDED_ISERIALIZER

Generated by: LCOV version 1.13