1.1 --- a/DJMFix.cpp Tue Apr 15 22:44:31 2025 +0200
1.2 +++ b/DJMFix.cpp Tue Apr 15 22:45:25 2025 +0200
1.3 @@ -1,6 +1,6 @@
1.4 /**
1.5 * DJM-Fix
1.6 - * Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info)
1.7 + * Copyright © 2025 František Kučera (Frantovo.cz, GlobalCode.info)
1.8 *
1.9 * This program is free software: you can redistribute it and/or modify
1.10 * it under the terms of the GNU General Public License as published by
1.11 @@ -30,6 +30,7 @@
1.12
1.13 using L = djmfix::logging::Level;
1.14 using Bytes = std::vector<uint8_t>;
1.15 +namespace chro = std::chrono;
1.16
1.17 class DJMFixImpl : public DJMFix {
1.18 private:
1.19 @@ -47,10 +48,17 @@
1.20 void run() {
1.21 while (!stopped) {
1.22 logger->log(L::FINE, "DJMFixImpl::run()");
1.23 - if (sendKeepAlive) send({0xf0, 0x00, 0x40, 0x05, 0x00, 0x00, 0x00, 0x17, 0x00, 0x50, 0x01, 0xf7});
1.24 - std::this_thread::sleep_for(std::chrono::milliseconds(keepAliveInterval));
1.25 + if (sendKeepAlive) send({
1.26 + 0xf0, 0x00, 0x40, 0x05,
1.27 + 0x00, 0x00, 0x00, 0x17,
1.28 + 0x00, 0x50, 0x01, 0xf7
1.29 + });
1.30 + std::this_thread::sleep_for(chro::milliseconds(keepAliveInterval));
1.31 keepAliveCounter++;
1.32 - if (keepAliveCounter % (60 * 1000 / keepAliveInterval) == 0) logger->log(L::INFO, "Still sending periodic keep-alive messages (each " + std::to_string(keepAliveInterval) + " ms).");
1.33 + if (keepAliveCounter % (60 * 1000 / keepAliveInterval) == 0)
1.34 + logger->log(L::INFO,
1.35 + "Still sending periodic keep-alive messages "
1.36 + "(each " + std::to_string(keepAliveInterval) + " ms).");
1.37 }
1.38 }
1.39
1.40 @@ -61,15 +69,18 @@
1.41
1.42 std::string toString(const Bytes& midiMessage) {
1.43 std::stringstream result;
1.44 - for (uint8_t b : midiMessage) result << std::hex << std::setw(2) << std::setfill('0') << (int) b;
1.45 + for (uint8_t b : midiMessage)
1.46 + result << std::hex << std::setw(2) << std::setfill('0') << (int) b;
1.47 return result.str();
1.48 }
1.49
1.50 Bytes normalize(const Bytes& data) {
1.51 - if (data.size() % 2) throw std::invalid_argument("Data before normalization must have even number of bytes.");
1.52 + if (data.size() % 2) throw std::invalid_argument(
1.53 + "Data before normalization must have even number of bytes.");
1.54 Bytes result;
1.55 result.reserve(data.size() / 2);
1.56 - for (size_t i = 0; i < data.size() / 2; i++) result.push_back((data[i * 2] & 0x0F) << 4 | (data[i * 2 + 1] & 0x0F));
1.57 + for (size_t i = 0; i < data.size() / 2; i++) result.push_back(
1.58 + (data[i * 2] & 0x0F) << 4 | (data[i * 2 + 1] & 0x0F));
1.59 return result;
1.60 }
1.61
1.62 @@ -105,7 +116,11 @@
1.63 return true;
1.64 }
1.65
1.66 - template<typename T> std::vector<T> concat(const std::vector<T>& a, const std::vector<T>& b, const std::vector<T>& c = {}) {
1.67 + template<typename T> std::vector<T> concat(
1.68 + const std::vector<T>& a,
1.69 + const std::vector<T>& b,
1.70 + const std::vector<T>& c = {}) //
1.71 + {
1.72 std::vector<T> result;
1.73 result.reserve(a.size() + b.size() + c.size());
1.74 for (size_t i = 0; i < a.size(); i++) result.push_back(a[i]);
1.75 @@ -114,8 +129,10 @@
1.76 return result;
1.77 }
1.78
1.79 - template<typename T> std::vector<T> xOR(const std::vector<T>& a, const std::vector<T>& b) {
1.80 - if (a.size() != b.size()) throw std::invalid_argument("Both must be the same length when doing XOR.");
1.81 + template<typename T>
1.82 + std::vector<T> xOR(const std::vector<T>& a, const std::vector<T>& b) {
1.83 + if (a.size() != b.size()) throw std::invalid_argument(
1.84 + "Both must be the same length when doing XOR.");
1.85 std::vector<T> result;
1.86 result.reserve(a.size());
1.87 for (size_t i = 0; i < a.size(); i++) result.push_back(a[i] ^ b[i]);
1.88 @@ -124,7 +141,8 @@
1.89
1.90 public:
1.91
1.92 - DJMFixImpl(djmfix::logging::Logger* logger) : logger(logger ? logger : djmfix::logging::blackhole()) {
1.93 + DJMFixImpl(djmfix::logging::Logger* logger)
1.94 + : logger(logger ? logger : djmfix::logging::blackhole()) {
1.95 }
1.96
1.97 virtual ~DJMFixImpl() override {
1.98 @@ -137,37 +155,78 @@
1.99 this->midiSender = midiSender;
1.100 }
1.101
1.102 - virtual void receive(const MidiMessage& midiMessage) override {
1.103 - logger->log(L::FINE, "Received a message: size = " + std::to_string(midiMessage.size()) + " data = " + toString(midiMessage));
1.104 + virtual void receive(const MidiMessage& msg) override {
1.105 + logger->log(L::FINE, "Received a message: "
1.106 + "size = " + std::to_string(msg.size()) + " "
1.107 + "data = " + toString(msg));
1.108 std::lock_guard<std::recursive_mutex> lock(midiMutex);
1.109
1.110
1.111 - if (midiMessage.size() == 12 && midiMessage[9] == 0x11) {
1.112 + if (msg.size() == 12 && msg[9] == 0x11) {
1.113 logger->log(L::INFO, "Received greeting message.");
1.114 - send({0xf0, 0x00, 0x40, 0x05, 0x00, 0x00, 0x00, 0x17, 0x00, 0x12, 0x2a, 0x01, 0x0b, 0x50, 0x69, 0x6f, 0x6e, 0x65, 0x65, 0x72, 0x44, 0x4a, 0x02, 0x0b, 0x72, 0x65, 0x6b, 0x6f, 0x72, 0x64, 0x62, 0x6f, 0x78, 0x03, 0x12, 0x02, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0xf7});
1.115 + send({
1.116 + 0xf0, 0x00, 0x40, 0x05,
1.117 + 0x00, 0x00, 0x00, 0x17,
1.118 + 0x00, 0x12, 0x2a, 0x01,
1.119 + 0x0b, 0x50, 0x69, 0x6f,
1.120 + 0x6e, 0x65, 0x65, 0x72,
1.121 + 0x44, 0x4a, 0x02, 0x0b,
1.122 + 0x72, 0x65, 0x6b, 0x6f,
1.123 + 0x72, 0x64, 0x62, 0x6f,
1.124 + 0x78, 0x03, 0x12, 0x02,
1.125 + 0x09, 0x00, 0x00, 0x00,
1.126 + 0x00, 0x00, 0x00, 0x02,
1.127 + 0x03, 0x04, 0x08, 0x00,
1.128 + 0x00, 0x00, 0x00, 0xf7
1.129 + });
1.130 logger->log(L::INFO, "Sent message with seed1.");
1.131 - } else if (midiMessage.size() == 54 && midiMessage[9] == 0x13 && midiMessage[33] == 0x04 && midiMessage[43] == 0x03) {
1.132 - Bytes hash1(midiMessage.begin() + 35, midiMessage.begin() + 35 + 8);
1.133 - seed2 = Bytes(midiMessage.begin() + 45, midiMessage.begin() + 45 + 8);
1.134 + } else if (msg.size() == 54
1.135 + && msg[9] == 0x13
1.136 + && msg[33] == 0x04
1.137 + && msg[43] == 0x03) //
1.138 + {
1.139 + Bytes hash1(msg.begin() + 35, msg.begin() + 35 + 8);
1.140 + seed2 = Bytes(msg.begin() + 45, msg.begin() + 45 + 8);
1.141 hash1 = normalize(hash1);
1.142 seed2 = normalize(seed2);
1.143 - logger->log(L::INFO, "Received message with hash1 = " + toString(hash1) + " and seed2 = " + toString(seed2));
1.144 + logger->log(L::INFO, "Received message with "
1.145 + "hash1 = " + toString(hash1) + " and "
1.146 + "seed2 = " + toString(seed2));
1.147
1.148 Bytes seed0 = {0x68, 0x01, 0x31, 0xFB};
1.149 Bytes seed1 = {0x29, 0x00, 0x00, 0x00, 0x23, 0x48, 0x00, 0x00};
1.150
1.151 - Bytes hash1check = toBytes(fnv32hash(concat(seed1, xOR(seed0, seed2))));
1.152 + Bytes hash1check =
1.153 + toBytes(fnv32hash(concat(seed1, xOR(seed0, seed2))));
1.154
1.155 if (equals(hash1, hash1check)) {
1.156 logger->log(L::INFO, "Verification of hash1 was successful.");
1.157 - Bytes hash2 = toBytes(fnv32hash(concat(seed2, xOR(seed0, seed2))));
1.158 - send(concat({0xf0, 0x00, 0x40, 0x05, 0x00, 0x00, 0x00, 0x17, 0x00, 0x14, 0x38, 0x01, 0x0b, 0x50, 0x69, 0x6f, 0x6e, 0x65, 0x65, 0x72, 0x44, 0x4a, 0x02, 0x0b, 0x72, 0x65, 0x6b, 0x6f, 0x72, 0x64, 0x62, 0x6f, 0x78, 0x04, 0x0a}, concat(denormalize(hash2),{0x05, 0x16, 0x05, 0x09, 0x0b, 0x05, 0x04, 0x0b, 0x0f, 0x0e, 0x0e, 0x04, 0x04, 0x0a, 0x05, 0x0a, 0x0c, 0x08, 0x0e, 0x04, 0x0c, 0x05, 0xf7})));
1.159 + Bytes hash2 =
1.160 + toBytes(fnv32hash(concat(seed2, xOR(seed0, seed2))));
1.161 + send(concat({
1.162 + 0xf0, 0x00, 0x40, 0x05,
1.163 + 0x00, 0x00, 0x00, 0x17,
1.164 + 0x00, 0x14, 0x38, 0x01,
1.165 + 0x0b, 0x50, 0x69, 0x6f,
1.166 + 0x6e, 0x65, 0x65, 0x72,
1.167 + 0x44, 0x4a, 0x02, 0x0b,
1.168 + 0x72, 0x65, 0x6b, 0x6f,
1.169 + 0x72, 0x64, 0x62, 0x6f,
1.170 + 0x78, 0x04, 0x0a
1.171 + }, concat(denormalize(hash2),{
1.172 + 0x05, 0x16, 0x05, 0x09,
1.173 + 0x0b, 0x05, 0x04, 0x0b,
1.174 + 0x0f, 0x0e, 0x0e, 0x04,
1.175 + 0x04, 0x0a, 0x05, 0x0a,
1.176 + 0x0c, 0x08, 0x0e, 0x04,
1.177 + 0x0c, 0x05, 0xf7
1.178 + })));
1.179 logger->log(L::INFO, "Sent message with hash2.");
1.180 } else {
1.181 std::stringstream logMessage;
1.182 logMessage
1.183 << "Verification of hash1 failed: "
1.184 - << " midiMessage = " << toString(midiMessage)
1.185 + << " midiMessage = " << toString(msg)
1.186 << " seed0 = " << toString(seed0)
1.187 << " seed1 = " << toString(seed1)
1.188 << " seed2 = " << toString(seed2)
1.189 @@ -176,19 +235,26 @@
1.190 logger->log(L::SEVERE, logMessage.str());
1.191 // TODO: graceful death
1.192 }
1.193 - } else if (midiMessage.size() == 12 && midiMessage[9] == 0x15) {
1.194 + } else if (msg.size() == 12 && msg[9] == 0x15) {
1.195 sendKeepAlive = true;
1.196 - logger->log(L::INFO, "Received acknowledgment message. Started sending keep-alive messages. LINE/PHONO channels should work now.");
1.197 + logger->log(L::INFO, "Received acknowledgment message. "
1.198 + "Started sending keep-alive messages. "
1.199 + "LINE/PHONO channels should work now.");
1.200 }
1.201
1.202 }
1.203
1.204 void start() override {
1.205 logger->log(L::FINE, "DJMFixImpl::start()");
1.206 - if (midiSender == nullptr) throw std::logic_error("Need a midiSender when starting DJMFix");
1.207 + if (midiSender == nullptr)
1.208 + throw std::logic_error("Need a midiSender when starting DJMFix");
1.209
1.210 // TODO: methods for parsing and constructing messages from parts (TLV)
1.211 - send({0xf0, 0x00, 0x40, 0x05, 0x00, 0x00, 0x00, 0x17, 0x00, 0x50, 0x01, 0xf7});
1.212 + send({
1.213 + 0xf0, 0x00, 0x40, 0x05,
1.214 + 0x00, 0x00, 0x00, 0x17,
1.215 + 0x00, 0x50, 0x01, 0xf7
1.216 + });
1.217 logger->log(L::INFO, "Sent greeting message.");
1.218
1.219 keepAliveThread = std::thread(&DJMFixImpl::run, this);