History

Mostly working send/recv, numeric argument 2 is always zero?

Ada committed 10 months ago

Commit a88e62b1 parent 81c80e72 files · +166 −46Browse files

Showing the changes to interface/src/scripting/OSCScriptingInterface.cpp (1 of 2 files). Show all

Unified · Split

interface/src/scripting/OSCScriptingInterface.cpp+158−34
@@ -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
20Q_LOGGING_CATEGORY(osc, "osc")21Q_LOGGING_CATEGORY(osc_cat, "osc")
21 22
22enum OSCTag: char {23enum 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
29static const QRegularExpression invalidCharacters = QRegularExpression("([ #*,?\\[\\]{}])");33static 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
53OSCScriptingInterface::~OSCScriptingInterface() {60OSCScriptingInterface::~OSCScriptingInterface() {
54 _socket->close();61 _socket->close();
55 qCInfo(osc) << "Closed listen socket";62 qCInfo(osc_cat) << "Closed listen socket";
56}63}
57 64
58void OSCScriptingInterface::receivePacket() {65void OSCScriptingInterface::readPacket() {
59 // TODO66 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
63void OSCScriptingInterface::sendMessage(const QString& address, const QString &argumentTypes, const QVariantList& arguments) {182void 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 in260 // 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 prefix287 // 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 // unreachable299 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}