3 * Copyright © 2025 František Kučera (Frantovo.cz, GlobalCode.info)
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License.
9 * This program 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.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 using L = djmfix::logging::Level;
32 using Bytes = std::vector<uint8_t>;
33 namespace chro = std::chrono;
35 class DJMFixImpl : public DJMFix {
37 MidiSender* midiSender;
38 djmfix::logging::Logger* logger;
39 const int keepAliveInterval = 200;
40 int keepAliveCounter = 0;
41 std::thread keepAliveThread;
42 std::recursive_mutex midiMutex;
43 std::atomic<bool> running{false};
44 std::atomic<bool> stopped{false};
45 std::atomic<bool> sendKeepAlive{false};
50 logger->log(L::FINE, "DJMFixImpl::run()");
51 if (sendKeepAlive) send({
52 0xf0, 0x00, 0x40, 0x05,
53 0x00, 0x00, 0x00, 0x17,
54 0x00, 0x50, 0x01, 0xf7
56 std::this_thread::sleep_for(chro::milliseconds(keepAliveInterval));
58 if (keepAliveCounter % (60 * 1000 / keepAliveInterval) == 0)
60 "Still sending periodic keep-alive messages "
61 "(each " + std::to_string(keepAliveInterval) + " ms).");
65 void send(const MidiMessage& midiMessage) {
66 std::lock_guard<std::recursive_mutex> lock(midiMutex);
67 midiSender->send(midiMessage);
70 std::string toString(const Bytes& midiMessage) {
71 std::stringstream result;
72 for (uint8_t b : midiMessage)
73 result << std::hex << std::setw(2) << std::setfill('0') << (int) b;
77 Bytes normalize(const Bytes& data) {
78 if (data.size() % 2) throw std::invalid_argument(
79 "Data before normalization must have even number of bytes.");
81 result.reserve(data.size() / 2);
82 for (size_t i = 0; i < data.size() / 2; i++) result.push_back(
83 (data[i * 2] & 0x0F) << 4 | (data[i * 2 + 1] & 0x0F));
87 Bytes denormalize(const Bytes& data) {
89 result.reserve(data.size()*2);
90 for (size_t i = 0; i < data.size(); i++) {
91 result.push_back(data[i] >> 4);
92 result.push_back(data[i] & 0x0F);
97 uint32_t fnv32hash(const Bytes& buff) {
98 uint32_t hash = 0x811c9dc5;
99 for (uint8_t b : buff) hash = ((b^hash) * 0x1000193);
103 Bytes toBytes(const uint32_t value) {
106 result.push_back(value >> 24);
107 result.push_back(value >> 16);
108 result.push_back(value >> 8);
109 result.push_back(value >> 0);
113 bool equals(Bytes a, Bytes b) {
114 if (a.size() != b.size()) return false;
115 for (size_t i = 0; i < a.size(); i++) if (a[i] != b[i]) return false;
119 template<typename T> std::vector<T> concat(
120 const std::vector<T>& a,
121 const std::vector<T>& b,
122 const std::vector<T>& c = {}) //
124 std::vector<T> result;
125 result.reserve(a.size() + b.size() + c.size());
126 for (size_t i = 0; i < a.size(); i++) result.push_back(a[i]);
127 for (size_t i = 0; i < b.size(); i++) result.push_back(b[i]);
128 for (size_t i = 0; i < c.size(); i++) result.push_back(c[i]);
133 std::vector<T> xOR(const std::vector<T>& a, const std::vector<T>& b) {
134 if (a.size() != b.size()) throw std::invalid_argument(
135 "Both must be the same length when doing XOR.");
136 std::vector<T> result;
137 result.reserve(a.size());
138 for (size_t i = 0; i < a.size(); i++) result.push_back(a[i] ^ b[i]);
144 DJMFixImpl(djmfix::logging::Logger* logger)
145 : logger(logger ? logger : djmfix::logging::blackhole()) {
148 virtual ~DJMFixImpl() override {
149 logger->log(L::FINER, "~DJMFixImpl()");
153 void setMidiSender(MidiSender* midiSender) {
154 logger->log(L::FINER, "DJMFixImpl::setMidiSender()");
155 this->midiSender = midiSender;
158 virtual void receive(const MidiMessage& msg) override {
159 logger->log(L::FINE, "Received a message: "
160 "size = " + std::to_string(msg.size()) + " "
161 "data = " + toString(msg));
162 std::lock_guard<std::recursive_mutex> lock(midiMutex);
165 if (msg.size() == 12 && msg[9] == 0x11) {
166 logger->log(L::INFO, "Received greeting message.");
168 0xf0, 0x00, 0x40, 0x05,
169 0x00, 0x00, 0x00, 0x17,
170 0x00, 0x12, 0x2a, 0x01,
171 0x0b, 0x50, 0x69, 0x6f,
172 0x6e, 0x65, 0x65, 0x72,
173 0x44, 0x4a, 0x02, 0x0b,
174 0x72, 0x65, 0x6b, 0x6f,
175 0x72, 0x64, 0x62, 0x6f,
176 0x78, 0x03, 0x12, 0x02,
177 0x09, 0x00, 0x00, 0x00,
178 0x00, 0x00, 0x00, 0x02,
179 0x03, 0x04, 0x08, 0x00,
180 0x00, 0x00, 0x00, 0xf7
182 logger->log(L::INFO, "Sent message with seed1.");
183 } else if (msg.size() == 54
186 && msg[43] == 0x03) //
188 Bytes hash1(msg.begin() + 35, msg.begin() + 35 + 8);
189 seed2 = Bytes(msg.begin() + 45, msg.begin() + 45 + 8);
190 hash1 = normalize(hash1);
191 seed2 = normalize(seed2);
192 logger->log(L::INFO, "Received message with "
193 "hash1 = " + toString(hash1) + " and "
194 "seed2 = " + toString(seed2));
196 Bytes seed0 = {0x68, 0x01, 0x31, 0xFB};
197 Bytes seed1 = {0x29, 0x00, 0x00, 0x00, 0x23, 0x48, 0x00, 0x00};
200 toBytes(fnv32hash(concat(seed1, xOR(seed0, seed2))));
202 if (equals(hash1, hash1check)) {
203 logger->log(L::INFO, "Verification of hash1 was successful.");
205 toBytes(fnv32hash(concat(seed2, xOR(seed0, seed2))));
207 0xf0, 0x00, 0x40, 0x05,
208 0x00, 0x00, 0x00, 0x17,
209 0x00, 0x14, 0x38, 0x01,
210 0x0b, 0x50, 0x69, 0x6f,
211 0x6e, 0x65, 0x65, 0x72,
212 0x44, 0x4a, 0x02, 0x0b,
213 0x72, 0x65, 0x6b, 0x6f,
214 0x72, 0x64, 0x62, 0x6f,
216 }, concat(denormalize(hash2),{
217 0x05, 0x16, 0x05, 0x09,
218 0x0b, 0x05, 0x04, 0x0b,
219 0x0f, 0x0e, 0x0e, 0x04,
220 0x04, 0x0a, 0x05, 0x0a,
221 0x0c, 0x08, 0x0e, 0x04,
224 logger->log(L::INFO, "Sent message with hash2.");
226 std::stringstream logMessage;
228 << "Verification of hash1 failed: "
229 << " midiMessage = " << toString(msg)
230 << " seed0 = " << toString(seed0)
231 << " seed1 = " << toString(seed1)
232 << " seed2 = " << toString(seed2)
233 << " hash1 = " << toString(hash1)
234 << " hash1check = " << toString(hash1check);
235 logger->log(L::SEVERE, logMessage.str());
236 // TODO: graceful death
238 } else if (msg.size() == 12 && msg[9] == 0x15) {
239 sendKeepAlive = true;
240 logger->log(L::INFO, "Received acknowledgment message. "
241 "Started sending keep-alive messages. "
242 "LINE/PHONO channels should work now.");
247 void start() override {
248 logger->log(L::FINE, "DJMFixImpl::start()");
249 if (midiSender == nullptr)
250 throw std::logic_error("Need a midiSender when starting DJMFix");
252 // TODO: methods for parsing and constructing messages from parts (TLV)
254 0xf0, 0x00, 0x40, 0x05,
255 0x00, 0x00, 0x00, 0x17,
256 0x00, 0x50, 0x01, 0xf7
258 logger->log(L::INFO, "Sent greeting message.");
260 keepAliveThread = std::thread(&DJMFixImpl::run, this);
265 void stop() override {
267 keepAliveThread.join();
269 logger->log(L::FINE, "DJMFixImpl::stop()");
273 DJMFix* create(djmfix::logging::Logger* logger) {
274 return new DJMFixImpl(logger);