asmap.cpp raw

   1  // Copyright (c) 2019-2022 The Limenka developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #include <util/asmap.h>
   6  
   7  #include <clientversion.h>
   8  #include <logging.h>
   9  #include <serialize.h>
  10  #include <streams.h>
  11  #include <util/fs.h>
  12  
  13  #include <algorithm>
  14  #include <bit>
  15  #include <cassert>
  16  #include <cstdio>
  17  #include <utility>
  18  #include <vector>
  19  
  20  namespace {
  21  
  22  constexpr uint32_t INVALID = 0xFFFFFFFF;
  23  
  24  uint32_t DecodeBits(std::vector<bool>::const_iterator& bitpos, const std::vector<bool>::const_iterator& endpos, uint8_t minval, const std::vector<uint8_t> &bit_sizes)
  25  {
  26      uint32_t val = minval;
  27      bool bit;
  28      for (std::vector<uint8_t>::const_iterator bit_sizes_it = bit_sizes.begin();
  29          bit_sizes_it != bit_sizes.end(); ++bit_sizes_it) {
  30          if (bit_sizes_it + 1 != bit_sizes.end()) {
  31              if (bitpos == endpos) break;
  32              bit = *bitpos;
  33              bitpos++;
  34          } else {
  35              bit = 0;
  36          }
  37          if (bit) {
  38              val += (1 << *bit_sizes_it);
  39          } else {
  40              for (int b = 0; b < *bit_sizes_it; b++) {
  41                  if (bitpos == endpos) return INVALID; // Reached EOF in mantissa
  42                  bit = *bitpos;
  43                  bitpos++;
  44                  val += bit << (*bit_sizes_it - 1 - b);
  45              }
  46              return val;
  47          }
  48      }
  49      return INVALID; // Reached EOF in exponent
  50  }
  51  
  52  enum class Instruction : uint32_t
  53  {
  54      RETURN = 0,
  55      JUMP = 1,
  56      MATCH = 2,
  57      DEFAULT = 3,
  58  };
  59  
  60  const std::vector<uint8_t> TYPE_BIT_SIZES{0, 0, 1};
  61  Instruction DecodeType(std::vector<bool>::const_iterator& bitpos, const std::vector<bool>::const_iterator& endpos)
  62  {
  63      return Instruction(DecodeBits(bitpos, endpos, 0, TYPE_BIT_SIZES));
  64  }
  65  
  66  const std::vector<uint8_t> ASN_BIT_SIZES{15, 16, 17, 18, 19, 20, 21, 22, 23, 24};
  67  uint32_t DecodeASN(std::vector<bool>::const_iterator& bitpos, const std::vector<bool>::const_iterator& endpos)
  68  {
  69      return DecodeBits(bitpos, endpos, 1, ASN_BIT_SIZES);
  70  }
  71  
  72  
  73  const std::vector<uint8_t> MATCH_BIT_SIZES{1, 2, 3, 4, 5, 6, 7, 8};
  74  uint32_t DecodeMatch(std::vector<bool>::const_iterator& bitpos, const std::vector<bool>::const_iterator& endpos)
  75  {
  76      return DecodeBits(bitpos, endpos, 2, MATCH_BIT_SIZES);
  77  }
  78  
  79  
  80  const std::vector<uint8_t> JUMP_BIT_SIZES{5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30};
  81  uint32_t DecodeJump(std::vector<bool>::const_iterator& bitpos, const std::vector<bool>::const_iterator& endpos)
  82  {
  83      return DecodeBits(bitpos, endpos, 17, JUMP_BIT_SIZES);
  84  }
  85  
  86  }
  87  
  88  uint32_t Interpret(const std::vector<bool> &asmap, const std::vector<bool> &ip)
  89  {
  90      std::vector<bool>::const_iterator pos = asmap.begin();
  91      const std::vector<bool>::const_iterator endpos = asmap.end();
  92      uint8_t bits = ip.size();
  93      uint32_t default_asn = 0;
  94      uint32_t jump, match, matchlen;
  95      Instruction opcode;
  96      while (pos != endpos) {
  97          opcode = DecodeType(pos, endpos);
  98          if (opcode == Instruction::RETURN) {
  99              default_asn = DecodeASN(pos, endpos);
 100              if (default_asn == INVALID) break; // ASN straddles EOF
 101              return default_asn;
 102          } else if (opcode == Instruction::JUMP) {
 103              jump = DecodeJump(pos, endpos);
 104              if (jump == INVALID) break; // Jump offset straddles EOF
 105              if (bits == 0) break; // No input bits left
 106              if (int64_t{jump} >= int64_t{endpos - pos}) break; // Jumping past EOF
 107              if (ip[ip.size() - bits]) {
 108                  pos += jump;
 109              }
 110              bits--;
 111          } else if (opcode == Instruction::MATCH) {
 112              match = DecodeMatch(pos, endpos);
 113              if (match == INVALID) break; // Match bits straddle EOF
 114              matchlen = std::bit_width(match) - 1;
 115              if (bits < matchlen) break; // Not enough input bits
 116              for (uint32_t bit = 0; bit < matchlen; bit++) {
 117                  if ((ip[ip.size() - bits]) != ((match >> (matchlen - 1 - bit)) & 1)) {
 118                      return default_asn;
 119                  }
 120                  bits--;
 121              }
 122          } else if (opcode == Instruction::DEFAULT) {
 123              default_asn = DecodeASN(pos, endpos);
 124              if (default_asn == INVALID) break; // ASN straddles EOF
 125          } else {
 126              break; // Instruction straddles EOF
 127          }
 128      }
 129      assert(false); // Reached EOF without RETURN, or aborted (see any of the breaks above) - should have been caught by SanityCheckASMap below
 130      return 0; // 0 is not a valid ASN
 131  }
 132  
 133  bool SanityCheckASMap(const std::vector<bool>& asmap, int bits)
 134  {
 135      const std::vector<bool>::const_iterator begin = asmap.begin(), endpos = asmap.end();
 136      std::vector<bool>::const_iterator pos = begin;
 137      std::vector<std::pair<uint32_t, int>> jumps; // All future positions we may jump to (bit offset in asmap -> bits to consume left)
 138      jumps.reserve(bits);
 139      Instruction prevopcode = Instruction::JUMP;
 140      bool had_incomplete_match = false;
 141      while (pos != endpos) {
 142          uint32_t offset = pos - begin;
 143          if (!jumps.empty() && offset >= jumps.back().first) return false; // There was a jump into the middle of the previous instruction
 144          Instruction opcode = DecodeType(pos, endpos);
 145          if (opcode == Instruction::RETURN) {
 146              if (prevopcode == Instruction::DEFAULT) return false; // There should not be any RETURN immediately after a DEFAULT (could be combined into just RETURN)
 147              uint32_t asn = DecodeASN(pos, endpos);
 148              if (asn == INVALID) return false; // ASN straddles EOF
 149              if (jumps.empty()) {
 150                  // Nothing to execute anymore
 151                  if (endpos - pos > 7) return false; // Excessive padding
 152                  while (pos != endpos) {
 153                      if (*pos) return false; // Nonzero padding bit
 154                      ++pos;
 155                  }
 156                  return true; // Sanely reached EOF
 157              } else {
 158                  // Continue by pretending we jumped to the next instruction
 159                  offset = pos - begin;
 160                  if (offset != jumps.back().first) return false; // Unreachable code
 161                  bits = jumps.back().second; // Restore the number of bits we would have had left after this jump
 162                  jumps.pop_back();
 163                  prevopcode = Instruction::JUMP;
 164              }
 165          } else if (opcode == Instruction::JUMP) {
 166              uint32_t jump = DecodeJump(pos, endpos);
 167              if (jump == INVALID) return false; // Jump offset straddles EOF
 168              if (int64_t{jump} > int64_t{endpos - pos}) return false; // Jump out of range
 169              if (bits == 0) return false; // Consuming bits past the end of the input
 170              --bits;
 171              uint32_t jump_offset = pos - begin + jump;
 172              if (!jumps.empty() && jump_offset >= jumps.back().first) return false; // Intersecting jumps
 173              jumps.emplace_back(jump_offset, bits);
 174              prevopcode = Instruction::JUMP;
 175          } else if (opcode == Instruction::MATCH) {
 176              uint32_t match = DecodeMatch(pos, endpos);
 177              if (match == INVALID) return false; // Match bits straddle EOF
 178              int matchlen = std::bit_width(match) - 1;
 179              if (prevopcode != Instruction::MATCH) had_incomplete_match = false;
 180              if (matchlen < 8 && had_incomplete_match) return false; // Within a sequence of matches only at most one should be incomplete
 181              had_incomplete_match = (matchlen < 8);
 182              if (bits < matchlen) return false; // Consuming bits past the end of the input
 183              bits -= matchlen;
 184              prevopcode = Instruction::MATCH;
 185          } else if (opcode == Instruction::DEFAULT) {
 186              if (prevopcode == Instruction::DEFAULT) return false; // There should not be two successive DEFAULTs (they could be combined into one)
 187              uint32_t asn = DecodeASN(pos, endpos);
 188              if (asn == INVALID) return false; // ASN straddles EOF
 189              prevopcode = Instruction::DEFAULT;
 190          } else {
 191              return false; // Instruction straddles EOF
 192          }
 193      }
 194      return false; // Reached EOF without RETURN instruction
 195  }
 196  
 197  std::vector<bool> DecodeAsmap(fs::path path)
 198  {
 199      std::vector<bool> bits;
 200      FILE *filestr = fsbridge::fopen(path, "rb");
 201      AutoFile file{filestr};
 202      if (file.IsNull()) {
 203          LogPrintf("Failed to open asmap file from disk\n");
 204          return bits;
 205      }
 206      file.seek(0, SEEK_END);
 207      int length = file.tell();
 208      LogPrintf("Opened asmap file %s (%d bytes) from disk\n", fs::quoted(fs::PathToString(path)), length);
 209      file.seek(0, SEEK_SET);
 210      uint8_t cur_byte;
 211      for (int i = 0; i < length; ++i) {
 212          file >> cur_byte;
 213          for (int bit = 0; bit < 8; ++bit) {
 214              bits.push_back((cur_byte >> bit) & 1);
 215          }
 216      }
 217      if (!SanityCheckASMap(bits, 128)) {
 218          LogPrintf("Sanity check of asmap file %s failed\n", fs::quoted(fs::PathToString(path)));
 219          return {};
 220      }
 221      return bits;
 222  }
 223  
 224