Showing the changes to interface/src/scripting/OSCScriptingInterface.cpp (1 of 2 files). Show all
Unified · Split
| @@ -12,18 +12,22 @@ · 11 unmodified lines | |||
| 12 | #include <QLoggingCategory> | 12 | #include <QLoggingCategory> |
| 13 | #include <QRegularExpression> | 13 | #include <QRegularExpression> |
| 14 | #include <QtEndian> | 14 | #include <QtEndian> |
| 15 | #include <QNetworkDatagram> | ||
| 15 | 16 | ||
| 16 | #include "ScriptEngine.h" | 17 | #include "ScriptEngine.h" |
| 17 | #include "OSCScriptingInterface.h" | 18 | #include "OSCScriptingInterface.h" |
| 18 | 19 | ||
| 19 | 20 | ||
| 20 | Q_LOGGING_CATEGORY(osc, "osc") | 21 | Q_LOGGING_CATEGORY(osc_cat, "osc") |
| 21 | 22 | ||
| 22 | enum OSCTag: char { | 23 | enum OSCTag: char { |
| 23 | Int = 'i', | 24 | Int = 'i', |
| 24 | Float = 'f', | 25 | Float = 'f', |
| 25 | String = 's', | 26 | String = 's', |
| 26 | Blob = 'b', | 27 | Blob = 'b', |
| 28 | False = 'F', | ||
| 29 | True = 'T', | ||
| 30 | Null = 'N', | ||
| 27 | }; | 31 | }; |
| 28 | 32 | ||
| 29 | static const QRegularExpression invalidCharacters = QRegularExpression("([ #*,?\\[\\]{}])"); | 33 | static const QRegularExpression invalidCharacters = QRegularExpression("([ #*,?\\[\\]{}])"); |
| @@ -32,6 +36,9 @@ static const QMap<QChar, QVariant::Type> typeNameMap = { · 2 unmodified lines | |||
| 32 | { OSCTag::Float, QVariant::Double }, | 36 | { OSCTag::Float, QVariant::Double }, |
| 33 | { OSCTag::String, QVariant::String }, | 37 | { OSCTag::String, QVariant::String }, |
| 34 | { OSCTag::Blob, QVariant::ByteArray }, | 38 | { OSCTag::Blob, QVariant::ByteArray }, |
| 39 | { OSCTag::False, QVariant::Bool }, | ||
| 40 | { OSCTag::True, QVariant::Bool }, | ||
| 41 | { OSCTag::Null, QVariant::Invalid }, | ||
| 35 | }; | 42 | }; |
| 36 | 43 | ||
| 37 | 44 | ||
| @@ -45,34 +52,146 @@ OSCScriptingInterface::OSCScriptingInterface(QObject* parent) : · 7 unmodified lines | |||
| 45 | { | 52 | { |
| 46 | _socket->bind(QHostAddress(_receiveHost.get()), _receivePort.get()); | 53 | _socket->bind(QHostAddress(_receiveHost.get()), _receivePort.get()); |
| 47 | 54 | ||
| 48 | connect(_socket.get(), &QUdpSocket::readyRead, this, &OSCScriptingInterface::receivePacket); | 55 | connect(_socket.get(), &QUdpSocket::readyRead, this, &OSCScriptingInterface::readPacket); |
| 49 | 56 | ||
| 50 | qCInfo(osc) << "Listening on" << _socket->localAddress(); | 57 | qCInfo(osc_cat) << "Listening on" << _socket->localAddress(); |
| 51 | } | 58 | } |
| 52 | 59 | ||
| 53 | OSCScriptingInterface::~OSCScriptingInterface() { | 60 | OSCScriptingInterface::~OSCScriptingInterface() { |
| 54 | _socket->close(); | 61 | _socket->close(); |
| 55 | qCInfo(osc) << "Closed listen socket"; | 62 | qCInfo(osc_cat) << "Closed listen socket"; |
| 56 | } | 63 | } |
| 57 | 64 | ||
| 58 | void OSCScriptingInterface::receivePacket() { | 65 | void OSCScriptingInterface::readPacket() { |
| 59 | // TODO | 66 | auto datagram = _socket->receiveDatagram(); |
| 60 | emit messageReceived("/test", "", QVariantList()); | 67 | |
| 68 | if (!datagram.isValid()) { return; } | ||
| 69 | |||
| 70 | auto next4 = [](int i) { return i + (4 - i % 4); }; | ||
| 71 | |||
| 72 | auto data = datagram.data(); | ||
| 73 | int cursor = 0; | ||
| 74 | |||
| 75 | QString addr; | ||
| 76 | QByteArray argTypes; | ||
| 77 | QVariantList args; | ||
| 78 | |||
| 79 | // address | ||
| 80 | for (int i = 0; (i + cursor) < data.length(); i++) { | ||
| 81 | if (data[i + cursor] == '\x00') { | ||
| 82 | addr = QString::fromUtf8(data.constData(), i); | ||
| 83 | cursor = next4(cursor + i); | ||
| 84 | break; | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | if (addr.isEmpty()) { return; } | ||
| 89 | |||
| 90 | // argument types | ||
| 91 | for (int i = 0; (i + cursor) < data.length(); i++) { | ||
| 92 | if (data[i + cursor] == '\x00') { | ||
| 93 | argTypes = QByteArray(data.constData() + cursor, i); | ||
| 94 | cursor = next4(cursor + i); | ||
| 95 | break; | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | // +1 to exclude the comma | ||
| 100 | for (int arg = 1; arg < argTypes.length(); arg++) { | ||
| 101 | switch (argTypes[arg]) { | ||
| 102 | case OSCTag::Int: { | ||
| 103 | const char* ptr = data.constData() + cursor; | ||
| 104 | qint32 tmp = qFromBigEndian<qint32>(ptr); | ||
| 105 | args.append(QVariantMap { | ||
| 106 | {"type", QString(OSCTag::Int)}, | ||
| 107 | {"value", tmp}, | ||
| 108 | }); | ||
| 109 | cursor += 4; | ||
| 110 | } break; | ||
| 111 | |||
| 112 | case OSCTag::Float: { | ||
| 113 | const char* ptr = data.constData() + cursor; | ||
| 114 | qint32 tmp = qFromBigEndian<qint32>(ptr); | ||
| 115 | args.append(QVariantMap { | ||
| 116 | {"type", QString(OSCTag::Float)}, | ||
| 117 | {"value", *reinterpret_cast<float*>(&tmp)}, | ||
| 118 | }); | ||
| 119 | cursor += 4; | ||
| 120 | } break; | ||
| 121 | |||
| 122 | case OSCTag::String: { | ||
| 123 | QString tmp; | ||
| 124 | |||
| 125 | for (int i = 0; i + cursor < data.length(); i++) { | ||
| 126 | if (data[i + cursor] == '\x00') { | ||
| 127 | tmp = QString::fromUtf8(data.constData() + cursor, i); | ||
| 128 | cursor = next4(cursor + i); | ||
| 129 | break; | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | args.append(QVariantMap { | ||
| 134 | {"type", QString(OSCTag::String)}, | ||
| 135 | {"value", tmp}, | ||
| 136 | }); | ||
| 137 | } break; | ||
| 138 | |||
| 139 | case OSCTag::Blob: { | ||
| 140 | const char* ptr = data.constData() + cursor; | ||
| 141 | auto len = qFromBigEndian<qint32>(ptr); | ||
| 142 | cursor += 4; | ||
| 143 | |||
| 144 | args.append(QVariantMap { | ||
| 145 | {"type", QString(OSCTag::Blob)}, | ||
| 146 | {"value", QByteArray(data.constData() + cursor, len)}, | ||
| 147 | }); | ||
| 148 | |||
| 149 | cursor = next4(cursor + len); | ||
| 150 | } break; | ||
| 151 | |||
| 152 | case OSCTag::False: | ||
| 153 | args.append(QVariantMap { | ||
| 154 | {"type", QString(OSCTag::False)}, | ||
| 155 | {"value", false}, | ||
| 156 | }); | ||
| 157 | break; | ||
| 158 | |||
| 159 | case OSCTag::True: | ||
| 160 | args.append(QVariantMap { | ||
| 161 | {"type", QString(OSCTag::True)}, | ||
| 162 | {"value", true}, | ||
| 163 | }); | ||
| 164 | break; | ||
| 165 | |||
| 166 | case OSCTag::Null: | ||
| 167 | args.append(QVariantMap { | ||
| 168 | {"type", QString(OSCTag::Null)}, | ||
| 169 | {"value", QVariant()}, | ||
| 170 | }); | ||
| 171 | break; | ||
| 172 | |||
| 173 | default: | ||
| 174 | qCCritical(osc_cat, "Unknown type '%c'", static_cast<char>(argTypes[arg])); | ||
| 175 | return; | ||
| 176 | } | ||
| 177 | } | ||
| 178 | |||
| 179 | emit packetReceived(addr, args); | ||
| 61 | } | 180 | } |
| 62 | 181 | ||
| 63 | void OSCScriptingInterface::sendMessage(const QString& address, const QString &argumentTypes, const QVariantList& arguments) { | 182 | void OSCScriptingInterface::sendPacket(const QString& address, const QVariantList& arguments) { |
| 64 | Q_ASSERT(_socket); | 183 | Q_ASSERT(_socket); |
| 65 | 184 | ||
| 66 | // FIXME: how do you get the script engine??? | 185 | // FIXME: how do you get the script engine??? |
| 67 | struct HackRemoveThis { | 186 | struct HackRemoveThis { |
| 68 | void raiseException(QString msg) { | 187 | void raiseException(QString msg) { |
| 69 | qCCritical(osc) << msg; | 188 | qCCritical(osc_cat) << msg; |
| 70 | } | 189 | } |
| 71 | }; | 190 | }; |
| 72 | 191 | ||
| 73 | auto scriptEngine = std::make_unique<HackRemoveThis>(); //_scriptManager->engine(); | 192 | auto scriptEngine = std::make_unique<HackRemoveThis>(); //_scriptManager->engine(); |
| 74 | 193 | ||
| 75 | if (address.length() < 2 || address[0] != '/') { | 194 | if (address.length() < 2 || !address.startsWith('/') || address.endsWith('/')) { |
| 76 | scriptEngine->raiseException("address must be at least two characters and start with '/'"); | 195 | scriptEngine->raiseException("address must be at least two characters and start with '/'"); |
| 77 | return; | 196 | return; |
| 78 | } | 197 | } |
| @@ -82,11 +201,6 @@ void OSCScriptingInterface::sendMessage(const QString& address, const QString &a · 3 unmodified lines | |||
| 82 | return; | 201 | return; |
| 83 | } | 202 | } |
| 84 | 203 | ||
| 85 | if (argumentTypes.length() != arguments.length()) { | ||
| 86 | scriptEngine->raiseException(QString("argumentTypes.length (%1) does not match arguments.length (%2)").arg(argumentTypes.length()).arg(arguments.length())); | ||
| 87 | return; | ||
| 88 | } | ||
| 89 | |||
| 90 | auto pad4 = [](QByteArray& array) { | 204 | auto pad4 = [](QByteArray& array) { |
| 91 | array.append((4 - array.length() % 4) % 4, 0); | 205 | array.append((4 - array.length() % 4) % 4, 0); |
| 92 | }; | 206 | }; |
| @@ -105,19 +219,26 @@ void OSCScriptingInterface::sendMessage(const QString& address, const QString &a · 12 unmodified lines | |||
| 105 | for (int i = 0; i < arguments.length(); i++) { | 219 | for (int i = 0; i < arguments.length(); i++) { |
| 106 | auto arg = arguments[i]; | 220 | auto arg = arguments[i]; |
| 107 | 221 | ||
| 108 | if (!typeNameMap.contains(argumentTypes[i])) { | 222 | QVariant::Type expectedType; |
| 109 | scriptEngine->raiseException(QString("Unknown type tag '%1'").arg(argumentTypes[i])); | 223 | QVariant value; |
| 110 | return; | 224 | |
| 225 | if (arg.type() == QVariant::Map) { | ||
| 226 | auto map = arg.toMap(); | ||
| 227 | auto typeName = map.value("type").toString(); | ||
| 228 | if (!typeNameMap.contains(typeName[0])) { | ||
| 229 | scriptEngine->raiseException(QString("Unknown type '%c' on argument %d").arg(typeName).arg(i)); | ||
| 230 | return; | ||
| 231 | } | ||
| 232 | expectedType = typeNameMap[typeName[0]]; | ||
| 233 | value = map.value("value"); | ||
| 234 | } else { | ||
| 235 | expectedType = arg.type(); | ||
| 236 | value = arg; | ||
| 111 | } | 237 | } |
| 112 | 238 | ||
| 113 | auto expectedType = typeNameMap[argumentTypes[i]]; | 239 | if (value.isNull()) { |
| 114 | if ( | 240 | bytes.append(OSCTag::Null); |
| 115 | expectedType != arg.type() && | 241 | continue; |
| 116 | // JS doesn't have ints, so allow doubles to be interpreted as ints | ||
| 117 | arg.type() != QVariant::Double | ||
| 118 | ) { | ||
| 119 | scriptEngine->raiseException(QString("Specified argument type '%1' does not match value of type '%2'").arg(QVariant::typeToName(expectedType)).arg(arg.typeName())); | ||
| 120 | return; | ||
| 121 | } | 242 | } |
| 122 | 243 | ||
| 123 | switch (expectedType) { | 244 | switch (expectedType) { |
| @@ -127,7 +248,7 @@ void OSCScriptingInterface::sendMessage(const QString& address, const QString &a · 3 unmodified lines | |||
| 127 | auto* ptr = bodyBytes.data() + bodyBytes.length(); | 248 | auto* ptr = bodyBytes.data() + bodyBytes.length(); |
| 128 | bodyBytes.append(4, 0); | 249 | bodyBytes.append(4, 0); |
| 129 | 250 | ||
| 130 | qToBigEndian(static_cast<qint32>(arg.toDouble()), ptr); | 251 | qToBigEndian(static_cast<qint32>(value.toDouble()), ptr); |
| 131 | } break; | 252 | } break; |
| 132 | 253 | ||
| 133 | case QVariant::Double: { | 254 | case QVariant::Double: { |
| @@ -137,14 +258,14 @@ void OSCScriptingInterface::sendMessage(const QString& address, const QString &a · 3 unmodified lines | |||
| 137 | bodyBytes.append(4, 0); | 258 | bodyBytes.append(4, 0); |
| 138 | 259 | ||
| 139 | // FIXME: use std::bit_cast instead of this hack once c++20 is in | 260 | // FIXME: use std::bit_cast instead of this hack once c++20 is in |
| 140 | auto tmp = static_cast<float>(arg.toDouble()); | 261 | auto tmp = static_cast<float>(value.toDouble()); |
| 141 | qToBigEndian(*reinterpret_cast<quint32*>(&tmp), ptr); | 262 | qToBigEndian(*reinterpret_cast<quint32*>(&tmp), ptr); |
| 142 | } break; | 263 | } break; |
| 143 | 264 | ||
| 144 | case QVariant::String: { | 265 | case QVariant::String: { |
| 145 | bytes.append(OSCTag::String); | 266 | bytes.append(OSCTag::String); |
| 146 | 267 | ||
| 147 | auto stringArg = arg.toString(); | 268 | auto stringArg = value.toString(); |
| 148 | 269 | ||
| 149 | if (stringArg.contains(QChar(0))) { | 270 | if (stringArg.contains(QChar(0))) { |
| 150 | scriptEngine->raiseException("String contains null (0x00) character"); | 271 | scriptEngine->raiseException("String contains null (0x00) character"); |
| @@ -161,7 +282,7 @@ void OSCScriptingInterface::sendMessage(const QString& address, const QString &a · 10 unmodified lines | |||
| 161 | case QVariant::ByteArray: { | 282 | case QVariant::ByteArray: { |
| 162 | bytes.append(OSCTag::Blob); | 283 | bytes.append(OSCTag::Blob); |
| 163 | 284 | ||
| 164 | auto bytesArg = arg.toByteArray(); | 285 | auto bytesArg = value.toByteArray(); |
| 165 | 286 | ||
| 166 | // write length prefix | 287 | // write length prefix |
| 167 | auto* ptr = bodyBytes.data() + bodyBytes.length(); | 288 | auto* ptr = bodyBytes.data() + bodyBytes.length(); |
| @@ -175,10 +296,13 @@ void OSCScriptingInterface::sendMessage(const QString& address, const QString &a · 7 unmodified lines | |||
| 175 | pad4(bodyBytes); | 296 | pad4(bodyBytes); |
| 176 | } break; | 297 | } break; |
| 177 | 298 | ||
| 178 | // unreachable | 299 | case QVariant::Bool: |
| 179 | default: | 300 | bytes.append(value.toBool() ? OSCTag::True : OSCTag::False); |
| 180 | Q_ASSERT(false); | ||
| 181 | break; | 301 | break; |
| 302 | |||
| 303 | default: | ||
| 304 | scriptEngine->raiseException(QString("Unserializable type %1").arg(value.typeName())); | ||
| 305 | return; | ||
| 182 | } | 306 | } |
| 183 | } | 307 | } |
| 184 | 308 | ||
| @@ -191,5 +315,5 @@ void OSCScriptingInterface::sendMessage(const QString& address, const QString &a · 6 unmodified lines | |||
| 191 | 315 | ||
| 192 | _socket->writeDatagram(bytes, QHostAddress(_sendHost.get()), _sendPort.get()); | 316 | _socket->writeDatagram(bytes, QHostAddress(_sendHost.get()), _sendPort.get()); |
| 193 | 317 | ||
| 194 | qCDebug(osc) << QHostAddress(_sendHost.get()) << _sendPort.get() << ":" << bytes.toHex(' '); | 318 | qCDebug(osc_cat) << QHostAddress(_sendHost.get()) << _sendPort.get() << ":" << bytes.toHex(' '); |
| 195 | } | 319 | } |