DJMFix.cpp
author František Kučera <franta-hg@frantovo.cz>
Mon, 04 Jan 2021 15:45:12 +0100
branchv_0
changeset 12 15d87fdd6e6c
parent 8 87dfa7c89294
child 13 334b727f7516
permissions -rw-r--r--
use Logger instead of messing with STDIO directly
franta-hg@0
     1
/**
franta-hg@0
     2
 * DJM-Fix
franta-hg@0
     3
 * Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info)
franta-hg@0
     4
 *
franta-hg@0
     5
 * This program is free software: you can redistribute it and/or modify
franta-hg@0
     6
 * it under the terms of the GNU General Public License as published by
franta-hg@0
     7
 * the Free Software Foundation, version 3 of the License.
franta-hg@0
     8
 *
franta-hg@0
     9
 * This program is distributed in the hope that it will be useful,
franta-hg@0
    10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
franta-hg@0
    11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
franta-hg@0
    12
 * GNU General Public License for more details.
franta-hg@0
    13
 *
franta-hg@0
    14
 * You should have received a copy of the GNU General Public License
franta-hg@0
    15
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
franta-hg@0
    16
 */
franta-hg@1
    17
#include <iostream>
franta-hg@12
    18
#include <sstream>
franta-hg@5
    19
#include <iomanip>
franta-hg@2
    20
#include <thread>
franta-hg@6
    21
#include <mutex>
franta-hg@5
    22
#include <atomic>
franta-hg@2
    23
#include <chrono>
franta-hg@2
    24
#include <stdexcept>
franta-hg@6
    25
#include <vector>
franta-hg@1
    26
franta-hg@1
    27
#include "DJMFix.h"
franta-hg@1
    28
franta-hg@1
    29
namespace djmfix {
franta-hg@1
    30
franta-hg@12
    31
using L = djmfix::logging::Level;
franta-hg@6
    32
using Bytes = std::vector<uint8_t>;
franta-hg@6
    33
franta-hg@1
    34
class DJMFixImpl : public DJMFix {
franta-hg@1
    35
private:
franta-hg@2
    36
	MidiSender* midiSender;
franta-hg@12
    37
	djmfix::logging::Logger* logger;
franta-hg@2
    38
	std::thread keepAliveThread;
franta-hg@6
    39
	std::recursive_mutex midiMutex;
franta-hg@5
    40
	std::atomic<bool> running{false};
franta-hg@5
    41
	std::atomic<bool> stopped{false};
franta-hg@8
    42
	std::atomic<bool> sendKeepAlive{false};
franta-hg@8
    43
	Bytes seed2;
franta-hg@2
    44
franta-hg@2
    45
	void run() {
franta-hg@2
    46
		while (!stopped) {
franta-hg@12
    47
			logger->log(L::FINE, "DJMFixImpl::run()");
franta-hg@8
    48
			if (sendKeepAlive) send({0xf0, 0x00, 0x40, 0x05, 0x00, 0x00, 0x00, 0x17, 0x00, 0x50, 0x01, 0xf7});
franta-hg@2
    49
			std::this_thread::sleep_for(std::chrono::milliseconds(200));
franta-hg@2
    50
		}
franta-hg@2
    51
	}
franta-hg@2
    52
franta-hg@6
    53
	void send(const MidiMessage& midiMessage) {
franta-hg@6
    54
		std::lock_guard<std::recursive_mutex> lock(midiMutex);
franta-hg@6
    55
		midiSender->send(midiMessage);
franta-hg@6
    56
	}
franta-hg@6
    57
franta-hg@6
    58
	std::string toString(const Bytes& midiMessage) {
franta-hg@5
    59
		std::stringstream result;
franta-hg@5
    60
		for (uint8_t b : midiMessage) result << std::hex << std::setw(2) << std::setfill('0') << (int) b;
franta-hg@5
    61
		return result.str();
franta-hg@5
    62
	}
franta-hg@5
    63
franta-hg@6
    64
	Bytes normalize(const Bytes& data) {
franta-hg@6
    65
		if (data.size() % 2) throw std::invalid_argument("data before normalization must have even number of bytes");
franta-hg@6
    66
		Bytes result;
franta-hg@6
    67
		result.reserve(data.size() / 2);
franta-hg@6
    68
		for (size_t i = 0; i < data.size() / 2; i++) result.push_back((data[i * 2] & 0x0F) << 4 | (data[i * 2 + 1] & 0x0F));
franta-hg@6
    69
		return result;
franta-hg@6
    70
	}
franta-hg@6
    71
franta-hg@8
    72
	Bytes denormalize(const Bytes& data) {
franta-hg@8
    73
		Bytes result;
franta-hg@8
    74
		result.reserve(data.size()*2);
franta-hg@8
    75
		for (size_t i = 0; i < data.size(); i++) {
franta-hg@8
    76
			result.push_back(data[i] >> 4);
franta-hg@8
    77
			result.push_back(data[i] & 0x0F);
franta-hg@8
    78
		}
franta-hg@8
    79
		return result;
franta-hg@8
    80
	}
franta-hg@8
    81
franta-hg@6
    82
	uint32_t fnv32hash(const Bytes& buff) {
franta-hg@6
    83
		uint32_t hash = 0x811c9dc5;
franta-hg@6
    84
		for (uint8_t b : buff) hash = ((b^hash) * 0x1000193);
franta-hg@6
    85
		return hash;
franta-hg@6
    86
	}
franta-hg@6
    87
franta-hg@6
    88
	Bytes toBytes(const uint32_t value) {
franta-hg@6
    89
		Bytes result;
franta-hg@6
    90
		result.reserve(4);
franta-hg@6
    91
		result.push_back(value >> 24);
franta-hg@6
    92
		result.push_back(value >> 16);
franta-hg@6
    93
		result.push_back(value >> 8);
franta-hg@6
    94
		result.push_back(value >> 0);
franta-hg@6
    95
		return result;
franta-hg@6
    96
	}
franta-hg@6
    97
franta-hg@6
    98
	bool equals(Bytes a, Bytes b) {
franta-hg@6
    99
		if (a.size() != b.size()) return false;
franta-hg@6
   100
		for (size_t i = 0; i < a.size(); i++) if (a[i] != b[i]) return false;
franta-hg@6
   101
		return true;
franta-hg@6
   102
	}
franta-hg@6
   103
franta-hg@6
   104
	template<typename T> std::vector<T> concat(const std::vector<T>& a, const std::vector<T>& b, const std::vector<T>& c = {}) {
franta-hg@6
   105
		std::vector<T> result;
franta-hg@6
   106
		result.reserve(a.size() + b.size() + c.size());
franta-hg@6
   107
		for (size_t i = 0; i < a.size(); i++) result.push_back(a[i]);
franta-hg@6
   108
		for (size_t i = 0; i < b.size(); i++) result.push_back(b[i]);
franta-hg@6
   109
		for (size_t i = 0; i < c.size(); i++) result.push_back(c[i]);
franta-hg@6
   110
		return result;
franta-hg@6
   111
	}
franta-hg@6
   112
franta-hg@6
   113
	template<typename T> std::vector<T> xOR(const std::vector<T>& a, const std::vector<T>& b) {
franta-hg@6
   114
		if (a.size() != b.size()) throw std::invalid_argument("xor: both must be the same length");
franta-hg@6
   115
		std::vector<T> result;
franta-hg@6
   116
		result.reserve(a.size());
franta-hg@6
   117
		for (size_t i = 0; i < a.size(); i++) result.push_back(a[i] ^ b[i]);
franta-hg@6
   118
		return result;
franta-hg@6
   119
	}
franta-hg@6
   120
franta-hg@1
   121
public:
franta-hg@1
   122
franta-hg@12
   123
	DJMFixImpl(djmfix::logging::Logger* logger) : logger(logger ? logger : djmfix::logging::blackhole()) {
franta-hg@12
   124
	}
franta-hg@12
   125
franta-hg@1
   126
	virtual ~DJMFixImpl() override {
franta-hg@12
   127
		logger->log(L::FINE, "~DJMFixImpl()");
franta-hg@2
   128
		if (running) stop();
franta-hg@2
   129
	}
franta-hg@2
   130
franta-hg@2
   131
	void setMidiSender(MidiSender* midiSender) {
franta-hg@12
   132
		logger->log(L::FINE, "DJMFixImpl::setMidiSender()");
franta-hg@2
   133
		this->midiSender = midiSender;
franta-hg@1
   134
	}
franta-hg@1
   135
franta-hg@6
   136
	virtual void receive(const MidiMessage& midiMessage) override {
franta-hg@12
   137
		logger->log(L::INFO, "received message: size = " + std::to_string(midiMessage.size()) + " data = " + toString(midiMessage));
franta-hg@6
   138
		std::lock_guard<std::recursive_mutex> lock(midiMutex);
franta-hg@5
   139
franta-hg@7
   140
franta-hg@7
   141
		if (midiMessage.size() == 12 && midiMessage[9] == 0x11) {
franta-hg@7
   142
			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});
franta-hg@7
   143
		} else if (midiMessage.size() == 54 && midiMessage[9] == 0x13 && midiMessage[33] == 0x04 && midiMessage[43] == 0x03) {
franta-hg@6
   144
			Bytes hash1(midiMessage.begin() + 35, midiMessage.begin() + 35 + 8);
franta-hg@8
   145
			seed2 = Bytes(midiMessage.begin() + 45, midiMessage.begin() + 45 + 8);
franta-hg@6
   146
			hash1 = normalize(hash1);
franta-hg@6
   147
			seed2 = normalize(seed2);
franta-hg@12
   148
			logger->log(L::INFO, "got message with hash1 = " + toString(hash1) + " and seed2 = " + toString(seed2));
franta-hg@6
   149
franta-hg@6
   150
			Bytes seed0 = {0x68, 0x01, 0x31, 0xFB};
franta-hg@6
   151
			Bytes seed1 = {0x29, 0x00, 0x00, 0x00, 0x23, 0x48, 0x00, 0x00};
franta-hg@6
   152
franta-hg@6
   153
			Bytes hash1check = toBytes(fnv32hash(concat(seed1, xOR(seed0, seed2))));
franta-hg@6
   154
franta-hg@6
   155
			if (equals(hash1, hash1check)) {
franta-hg@12
   156
				logger->log(L::INFO, "hash1 verification: OK");
franta-hg@8
   157
				Bytes hash2 = toBytes(fnv32hash(concat(seed2, xOR(seed0, seed2))));
franta-hg@8
   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})));
franta-hg@6
   159
			} else {
franta-hg@12
   160
				std::stringstream logMessage;
franta-hg@12
   161
				logMessage
franta-hg@12
   162
						<< "hash1 verification failed: "
franta-hg@8
   163
						<< " midiMessage = " << toString(midiMessage)
franta-hg@8
   164
						<< " seed0 = " << toString(seed0)
franta-hg@8
   165
						<< " seed1 = " << toString(seed1)
franta-hg@8
   166
						<< " seed2 = " << toString(seed2)
franta-hg@8
   167
						<< " hash1 = " << toString(hash1)
franta-hg@12
   168
						<< " hash1check = " << toString(hash1check);
franta-hg@12
   169
				logger->log(L::SEVERE, logMessage.str());
franta-hg@8
   170
				// TODO: graceful death
franta-hg@6
   171
			}
franta-hg@8
   172
		} else if (midiMessage.size() == 12 && midiMessage[9] == 0x15) {
franta-hg@8
   173
			sendKeepAlive = true;
franta-hg@5
   174
		}
franta-hg@5
   175
franta-hg@1
   176
	}
franta-hg@1
   177
franta-hg@1
   178
	void start() override {
franta-hg@12
   179
		logger->log(L::FINE, "DJMFixImpl::start()");
franta-hg@5
   180
		if (midiSender == nullptr) throw std::logic_error("need a midiSender when starting DJMFix");
franta-hg@5
   181
franta-hg@5
   182
		// TODO: methods for parsing and constructing messages from parts (TLV)
franta-hg@6
   183
		send({0xf0, 0x00, 0x40, 0x05, 0x00, 0x00, 0x00, 0x17, 0x00, 0x50, 0x01, 0xf7});
franta-hg@2
   184
franta-hg@2
   185
		keepAliveThread = std::thread(&DJMFixImpl::run, this);
franta-hg@2
   186
		running = true;
franta-hg@2
   187
franta-hg@1
   188
	}
franta-hg@1
   189
franta-hg@1
   190
	void stop() override {
franta-hg@2
   191
		stopped = true;
franta-hg@2
   192
		keepAliveThread.join();
franta-hg@2
   193
		running = false;
franta-hg@12
   194
		logger->log(L::FINE, "DJMFixImpl::stop()");
franta-hg@1
   195
	}
franta-hg@1
   196
};
franta-hg@1
   197
franta-hg@12
   198
DJMFix* create(djmfix::logging::Logger* logger) {
franta-hg@12
   199
	return new DJMFixImpl(logger);
franta-hg@1
   200
}
franta-hg@1
   201
franta-hg@1
   202
}