commit 5af59f1e225d927862d1bc7badb9b8b214abaf8a
parent 92387e4c1132f7909a3a965649b29e9b359f6965
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date: Wed, 23 Mar 2022 21:44:06 +0100
add additional validation for demo packets including packet ordering and checksum
Diffstat:
4 files changed, 179 insertions(+), 39 deletions(-)
diff --git a/source/virusLib/CMakeLists.txt b/source/virusLib/CMakeLists.txt
@@ -4,6 +4,7 @@ project(virusLib)
add_library(virusLib STATIC)
set(SOURCES
+ demopacketvalidator.cpp demopacketvalidator.h
demoplayback.cpp demoplayback.h
device.cpp device.h
midiOutParser.cpp midiOutParser.h
diff --git a/source/virusLib/demopacketvalidator.cpp b/source/virusLib/demopacketvalidator.cpp
@@ -0,0 +1,134 @@
+#include "demopacketvalidator.h"
+
+#include <cassert>
+
+#include "dsp56kEmu/logging.h"
+
+namespace virusLib
+{
+ bool DemoPacketValidator::add(const Packet& _packet)
+ {
+ if(isComplete())
+ return isValid();
+
+ if(_packet.size() < 11)
+ return isValid();
+
+ const auto cmd = _packet[5];
+
+ switch (cmd)
+ {
+ case 0x50: // Virus A second
+ case 0x55: // Virus B second
+ case 0x57: // Virus C second
+ {
+ const auto msb = _packet[6]; // packet number MSB
+ const auto lsb = _packet[7]; // packet number LSB
+
+ uint8_t checksum = 0;
+ for(size_t i=5; i<_packet.size()-2; ++i)
+ checksum += _packet[i];
+ checksum &= 0x7f;
+
+ if(checksum != _packet[_packet.size()-2])
+ {
+ LOG("Packet MSB " << static_cast<int>(msb) << " LSB " << static_cast<int>(lsb) << " is invalid, wrong checksum");
+ m_valid = false;
+ return false;
+ }
+
+ auto packetInvalid = [&]()
+ {
+ LOG("Packet invalid, expected packet index " << static_cast<int>(m_expectedMSB) << " " << static_cast<int>(m_expectedLSB) << " but got " << static_cast<int>(msb) << " " << static_cast<int>(lsb));
+ m_valid = false;
+ return false;
+ };
+
+ auto matchExpected = [&]()
+ {
+ return msb == m_expectedMSB && lsb == m_expectedLSB;
+ };
+
+ if(msb == 127 && lsb >= 3)
+ {
+ if(lsb == 3)
+ {
+ if(!matchExpected())
+ return packetInvalid();
+
+ m_packets.push_back(_packet);
+
+ m_expectedLSB = ++m_offset;
+ }
+ else if(lsb < 126)
+ {
+ if(!matchExpected())
+ return packetInvalid();
+ m_expectedLSB = 0;
+ m_expectedMSB = 0;
+ }
+ else if(lsb == 127)
+ {
+ if(m_expectedMSB != msb)
+ {
+ LOG("Terminating too soon, missing packets");
+ m_valid = false;
+ return false;
+ }
+
+ m_complete = true;
+ if(!toBinary(m_data))
+ m_valid = false;
+ return isComplete();
+ }
+ }
+ else
+ {
+ if(!matchExpected())
+ return packetInvalid();
+
+ m_packets.push_back(_packet);
+
+ if(lsb == 3)
+ {
+ ++m_expectedMSB;
+ m_expectedLSB = 0;
+ }
+ else
+ ++m_expectedLSB;
+ }
+ }
+ break;
+ default:
+ // skip unknown packets
+ return true;
+ }
+
+ return false;
+ }
+
+ bool DemoPacketValidator::toBinary(std::vector<uint8_t>& _binary) const
+ {
+ if(!isComplete())
+ return false;
+
+ for (const auto& p : m_packets)
+ {
+ // midi bytes in a sysex frame can only carry 7 bit, not 8. They've chosen the easy way that costs more storage
+ // They transfer only one nibble of a ROM byte in one midi byte to ensure that the most significant nibble is
+ // always zero. By concating two nibbles together we get one ROM byte
+ for(size_t s=8; s<p.size()-2; s += 2)
+ {
+ const uint8_t a = p[s];
+ const uint8_t b = p[s+1];
+ if(a > 0xf || b > 0xf)
+ {
+ LOG("Invalid data, high nibble must be 0");
+ return false;
+ }
+ _binary.push_back(static_cast<uint8_t>(b << 4) | a);
+ }
+ }
+ return true;
+ }
+}
diff --git a/source/virusLib/demopacketvalidator.h b/source/virusLib/demopacketvalidator.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <cstdint>
+#include <vector>
+
+namespace virusLib
+{
+ class DemoPacketValidator
+ {
+ public:
+ using Packet = std::vector<uint8_t>;
+
+ bool add(const Packet& _packet);
+
+ bool isValid() const { return m_valid; }
+ bool isComplete() const { return isValid() && m_complete; }
+
+ const std::vector<uint8_t>& getData() const { return m_data; }
+
+ private:
+ bool toBinary(std::vector<uint8_t>& _binary) const;
+
+ std::vector<Packet> m_packets;
+ std::vector<uint8_t> m_data;
+
+ uint8_t m_expectedMSB = 127;
+ uint8_t m_expectedLSB = 8;
+ uint8_t m_offset = 8;
+
+ bool m_valid = true;
+ bool m_complete = false;
+ };
+}
diff --git a/source/virusLib/demoplayback.cpp b/source/virusLib/demoplayback.cpp
@@ -12,6 +12,8 @@
#include <cstring> // memcpy
+#include "demopacketvalidator.h"
+
namespace virusLib
{
constexpr auto g_timeScale_C = 57; // C OS 6.6
@@ -32,50 +34,20 @@ namespace virusLib
std::vector<std::vector<uint8_t>> packets;
synthLib::MidiToSysex::splitMultipleSysex(packets, sysex);
- std::vector<uint8_t> temp;
-
- for(size_t i=0; i<packets.size(); ++i)
- {
- const auto& p = packets[i];
-
- const auto cmd = p[5];
-
- const uint32_t indexA = p[6]; // packet number MSB
- const uint32_t indexB = p[7]; // packet number LSB
+ DemoPacketValidator validator;
- switch(cmd)
- {
- case 0x50: // Virus A second
- case 0x55: // Virus B second
- case 0x57: // Virus C second
- {
- LOG("Packet " << i << " indexA = " << indexA << " indexB = " << indexB);
-
- if(indexB > 3)
- {
- continue;
- }
+ for (const auto& packet : packets)
+ validator.add(packet);
- // midi bytes in a sysex fram can only carry 7 bit, not 8. They've chosen the easy way that costs more storage
- // They transfer one nibble of a ROM byte in each midi byte to ensure that the most significant nibble is always zero
- // By concating two nibbles together we get one ROM byte
- for(size_t s=8; s<p.size()-2; s += 2)
- {
- const uint8_t a = p[s];
- const uint8_t b = p[s+1];
- assert(a <= 0xf);
- assert(b <= 0xf);
- temp.push_back(static_cast<uint8_t>(b << 4) | a);
- }
- }
- break;
- }
+ if(!validator.isValid())
+ {
+ LOG("Packet validation failed, packets missing or invalid");
+ return false;
}
- if(temp.empty())
- return false;
+ const auto& data = validator.getData();
- return loadBinData(temp);
+ return loadBinData(data);
}
bool DemoPlayback::loadBinData(const std::vector<uint8_t>& _data)