NanoOcp
Minimal AES70 / OCP.1 TCP client/server library for d&b Soundscape devices
Loading...
Searching...
No Matches
Ocp1Message.cpp
Go to the documentation of this file.
1/* Copyright (c) 2023, Bernardo Escalona
2 *
3 * This file is part of NanoOcp <https://github.com/ChristianAhrens/NanoOcp>
4 *
5 * This library is free software; you can redistribute it and/or modify it under
6 * the terms of the GNU Lesser General Public License version 3.0 as published
7 * by the Free Software Foundation.
8 *
9 * This library is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
12 * details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this library; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19#include "Ocp1Message.h"
20
21#ifdef JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED
22 #include <juce_core/juce_core.h>
23 #include <juce_events/juce_events.h>
24#else
25 #include <JuceHeader.h>
26#endif
27
28
29namespace NanoOcp1
30{
31
32//==============================================================================
33// Class Ocp1CommandDefinition
34//==============================================================================
35
37{
38 return Ocp1CommandDefinition(0x00000004, // ONO of OcaSubscriptionManager
40 3, // OcaSubscriptionManager level
41 1, // AddSubscription method
42 5, // 5 Params
44}
45
47{
48 return Ocp1CommandDefinition(0x00000004, // ONO of OcaSubscriptionManager
50 3, // OcaSubscriptionManager level
51 2, // RemoveSubscription method
52 2, // 2 Params
54}
55
57{
61 1, // Get method is usually MethodIdx 1
62 0, // 0 Param
63 ByteVector()); // Empty parameters
64}
65
67{
68 ByteVector newParamData = newValue.ToParamData(GetDataType());
69
73 2, // Set method is usually MethodIdx 2
74 1, // Set method usually takes one parameter
75 newParamData);
76}
77
82
83
84//==============================================================================
85// Class Ocp1Header
86//==============================================================================
87
89 : m_syncVal(static_cast<std::uint8_t>(0)),
90 m_protoVers(static_cast<std::uint16_t>(0)),
91 m_msgSize(static_cast<std::uint32_t>(0)),
92 m_msgType(static_cast<std::uint8_t>(0)),
93 m_msgCnt(static_cast<std::uint16_t>(0))
94{
95 jassert(memory.size() >= 10); // Not enough data to fit even an Ocp1Header.
96 if (memory.size() >= 10)
97 {
98 m_syncVal = memory.at(0);
99 jassert(m_syncVal == 0x3b); // Message does not start with the sync byte.
100
101 m_protoVers = ReadUint16(memory.data() + 1);
102 jassert(m_protoVers == 1); // Protocol version is expected to be 1.
103
104 m_msgSize = ReadUint32(memory.data() + 3);
105 jassert(m_msgSize >= Ocp1HeaderSize); // Message has unexpected size.
106
107 m_msgType = memory.at(7);
108 jassert(m_msgType <= Ocp1Message::KeepAlive); // Message type outside expected range.
109
110 m_msgCnt = ReadUint16(memory.data() + 8);
111 jassert(m_msgCnt > 0); // At least one message expected.
112 }
113}
114
116{
117 return ((m_syncVal == 0x3b) && (m_protoVers == 1) && (m_msgSize >= Ocp1HeaderSize) &&
119}
120
122{
123 ByteVector serializedData;
124
125 serializedData.push_back(m_syncVal);
126 serializedData.push_back(static_cast<std::uint8_t>(m_protoVers >> 8));
127 serializedData.push_back(static_cast<std::uint8_t>(m_protoVers));
128 serializedData.push_back(static_cast<std::uint8_t>(m_msgSize >> 24));
129 serializedData.push_back(static_cast<std::uint8_t>(m_msgSize >> 16));
130 serializedData.push_back(static_cast<std::uint8_t>(m_msgSize >> 8));
131 serializedData.push_back(static_cast<std::uint8_t>(m_msgSize));
132 serializedData.push_back(m_msgType);
133 serializedData.push_back(static_cast<std::uint8_t>(m_msgCnt >> 8));
134 serializedData.push_back(static_cast<std::uint8_t>(m_msgCnt));
135
136 return serializedData;
137}
138
139std::uint32_t Ocp1Header::CalculateMessageSize(std::uint8_t msgType, size_t parameterDataLength)
140{
141 std::uint32_t ret(0);
142
143 switch (msgType)
144 {
147 ret = static_cast<std::uint32_t>(26 + parameterDataLength);
148 break;
150 ret = static_cast<std::uint32_t>(37 + parameterDataLength);
151 break;
153 ret = static_cast<std::uint32_t>(19 + parameterDataLength);
154 break;
156 ret = static_cast<std::uint32_t>(9 + parameterDataLength);
157 break;
158 default:
159 break;
160 }
161
162 return ret;
163}
164
165
166//==============================================================================
167// Class Ocp1Message
168//==============================================================================
169
170// OCA_INVALID_SESSIONID == 0, OCA_LOCAL_SESSIONID == 1
171std::uint32_t Ocp1Message::m_nextHandle = 2;
172
173
174std::unique_ptr<Ocp1Message> Ocp1Message::UnmarshalOcp1Message(const ByteVector& receivedData)
175{
176 Ocp1Header header(receivedData);
177 if (!header.IsValid())
178 return nullptr;
179
180 switch (header.GetMessageType())
181 {
182 case Notification:
183 {
184 std::uint32_t notificationSize(ReadUint32(receivedData.data() + 10));
185 std::uint32_t newValueSize = notificationSize - 28;
186 if (newValueSize < 1)
187 return nullptr;
188
189 // Not a valid object number.
190 std::uint32_t targetOno(ReadUint32(receivedData.data() + 14));
191 if (targetOno == 0)
192 return nullptr;
193
194 // Method DefinitionLevel expected to be 3 (OcaSubscriptionManager)
195 std::uint16_t methodDefLevel(ReadUint16(receivedData.data() + 18));
196 if (methodDefLevel < 1)
197 return nullptr;
198
199 // Method index expected to be 1 (AddSubscription)
200 std::uint16_t methodIdx(ReadUint16(receivedData.data() + 20));
201 if (methodIdx < 1)
202 return nullptr;
203
204 // At least one parameter expected.
205 std::uint8_t paramCount = receivedData[22];
206 if (paramCount < 1)
207 return nullptr;
208
209 std::uint16_t contextSize(ReadUint16(receivedData.data() + 23));
210
211 // Not a valid object number.
212 std::uint32_t emitterOno(ReadUint32(receivedData.data() + 25 + contextSize));
213 if (emitterOno == 0)
214 return nullptr;
215
216 // Event definiton level expected to be 1 (OcaRoot).
217 std::uint16_t eventDefLevel(ReadUint16(receivedData.data() + 29 + contextSize));
218 if (eventDefLevel != 1)
219 return nullptr;
220
221 // Event index expected to be 1 (OCA_EVENT_PROPERTY_CHANGED).
222 std::uint16_t eventIdx(ReadUint16(receivedData.data() + 31 + contextSize));
223 if (eventIdx != 1)
224 return nullptr;
225
226 // Property definition level expected to be > 0.
227 std::uint16_t propDefLevel(ReadUint16(receivedData.data() + 33 + contextSize));
228 if (propDefLevel == 0)
229 return nullptr;
230
231 // Property index expected to be > 0.
232 std::uint16_t propIdx(ReadUint16(receivedData.data() + 35 + contextSize));
233 if (propIdx == 0)
234 return nullptr;
235
236 const auto parameterData = ByteVector(receivedData.begin() + 37 + contextSize,
237 receivedData.begin() + 37 + contextSize + newValueSize);
238
239 return std::make_unique<Ocp1Notification>(emitterOno, propDefLevel, propIdx, paramCount, parameterData);
240 }
241
242 case Response:
243 {
244 std::uint32_t responseSize(ReadUint32(receivedData.data() + 10));
245 std::uint32_t parameterDataLength = responseSize - 10;
246 if (responseSize < 10)
247 return nullptr;
248
249 // Not a valid handle.
250 std::uint32_t handle(ReadUint32(receivedData.data() + 14));
251 if (handle == 0)
252 return nullptr;
253
254 const std::uint8_t status = receivedData[18];
255 const std::uint8_t paramCount = receivedData[19];
256
257 const auto parameterData = [&receivedData, &parameterDataLength]() {
258 if (parameterDataLength == 0)
259 return ByteVector{};
260 else
261 return ByteVector(receivedData.begin() + 20, receivedData.end());
262 }();
263 jassert(parameterData.size() == parameterDataLength);
264
265 return std::make_unique<Ocp1Response>(handle, status, paramCount, parameterData);
266 }
267
268 case KeepAlive:
269 {
270 std::uint16_t heartbeat(ReadUint16(receivedData.data() + 10));
271
272 return std::make_unique<Ocp1KeepAlive>(heartbeat);
273 }
274
275 case Command:
276 {
277 // Not used in this implementation. See CommandResponseRequired instead.
278 return nullptr;
279 }
281 {
282 constexpr std::size_t commandSizeOffset = Ocp1Header::Ocp1HeaderSize;
283 constexpr std::size_t handleOffset = commandSizeOffset + 4;
284 constexpr std::size_t targetOnoOffset = handleOffset + 4;
285 constexpr std::size_t methodDefLevelOffset = targetOnoOffset + 4;
286 constexpr std::size_t methodIdxOffset = methodDefLevelOffset + 2;
287 constexpr std::size_t paramCountOffset = methodIdxOffset + 2;
288 constexpr std::uint32_t minimumCommandSize = 16; // Size without parameters
289 constexpr std::size_t parameterDataOffset = paramCountOffset + 1;
290
291 const std::uint32_t commandSize(ReadUint32(receivedData.data() + commandSizeOffset));
292 if (commandSize < minimumCommandSize)
293 return nullptr;
294
295 if(receivedData.size() < Ocp1Header::Ocp1HeaderSize + commandSize)
296 return nullptr;
297
298 std::uint32_t handle(ReadUint32(receivedData.data() + handleOffset));
299 const bool isInvalidHandle = (handle == 0);
300 if (isInvalidHandle)
301 return nullptr;
302
303 const std::uint32_t targetOno(ReadUint32(receivedData.data() + targetOnoOffset));
304 const bool isInvalidTargetOno = (targetOno == 0);
305 if (isInvalidTargetOno)
306 return nullptr;
307
308 const std::uint16_t methodDefLevel(ReadUint16(receivedData.data() + methodDefLevelOffset));
309 const bool isInvalidMethodDefLevel = (methodDefLevel < 1);
310 if (isInvalidMethodDefLevel)
311 return nullptr;
312
313 const std::uint16_t methodIdx(ReadUint16(receivedData.data() + methodIdxOffset));
314 const bool isInvalidMethodIdx = (methodIdx < 1);
315 if (isInvalidMethodIdx)
316 return nullptr;
317
318 const std::uint8_t paramCount = receivedData[paramCountOffset];
319
320 const auto parameterData = [&receivedData, &paramCount, &parameterDataOffset]() {
321 if (paramCount == 0)
322 return ByteVector{};
323 else if(receivedData.begin() + parameterDataOffset >= receivedData.end())
324 return ByteVector{};
325 else
326 return ByteVector(receivedData.begin() + parameterDataOffset, receivedData.end());
327 }();
328
329 auto result = std::make_unique<Ocp1CommandResponseRequired>(targetOno, methodDefLevel, methodIdx, paramCount, parameterData);
330 result->SetHandle(handle);
331 return result;
332 }
333 default:
334 return nullptr;
335 }
336}
337
338
339
340//==============================================================================
341// Class Ocp1CommandResponseRequired
342//==============================================================================
343
345{
346 ByteVector serializedData = m_header.GetSerializedData();
347
348 std::uint32_t commandSize(m_header.GetMessageSize() - 9); // Message size minus the header
349 serializedData.push_back(static_cast<std::uint8_t>(commandSize >> 24));
350 serializedData.push_back(static_cast<std::uint8_t>(commandSize >> 16));
351 serializedData.push_back(static_cast<std::uint8_t>(commandSize >> 8));
352 serializedData.push_back(static_cast<std::uint8_t>(commandSize));
353 serializedData.push_back(static_cast<std::uint8_t>(m_handle >> 24));
354 serializedData.push_back(static_cast<std::uint8_t>(m_handle >> 16));
355 serializedData.push_back(static_cast<std::uint8_t>(m_handle >> 8));
356 serializedData.push_back(static_cast<std::uint8_t>(m_handle));
357 serializedData.push_back(static_cast<std::uint8_t>(m_targetOno >> 24));
358 serializedData.push_back(static_cast<std::uint8_t>(m_targetOno >> 16));
359 serializedData.push_back(static_cast<std::uint8_t>(m_targetOno >> 8));
360 serializedData.push_back(static_cast<std::uint8_t>(m_targetOno));
361 serializedData.push_back(static_cast<std::uint8_t>(m_methodDefLevel >> 8));
362 serializedData.push_back(static_cast<std::uint8_t>(m_methodDefLevel));
363 serializedData.push_back(static_cast<std::uint8_t>(m_methodIndex >> 8));
364 serializedData.push_back(static_cast<std::uint8_t>(m_methodIndex));
365 serializedData.push_back(static_cast<std::uint8_t>(m_paramCount));
366 for (size_t i = 0; i < m_parameterData.size(); i++)
367 {
368 serializedData.push_back(m_parameterData[i]);
369 }
370
371 return serializedData;
372}
373
374
375
376//==============================================================================
377// Class Ocp1Response
378//==============================================================================
379
381{
382 ByteVector serializedData = m_header.GetSerializedData();
383
384 std::uint32_t responseSize(m_header.GetMessageSize() - 9); // Message size minus the header
385 serializedData.push_back(static_cast<std::uint8_t>(responseSize >> 24));
386 serializedData.push_back(static_cast<std::uint8_t>(responseSize >> 16));
387 serializedData.push_back(static_cast<std::uint8_t>(responseSize >> 8));
388 serializedData.push_back(static_cast<std::uint8_t>(responseSize));
389 serializedData.push_back(static_cast<std::uint8_t>(m_handle >> 24));
390 serializedData.push_back(static_cast<std::uint8_t>(m_handle >> 16));
391 serializedData.push_back(static_cast<std::uint8_t>(m_handle >> 8));
392 serializedData.push_back(static_cast<std::uint8_t>(m_handle));
393 serializedData.push_back(static_cast<std::uint8_t>(m_status));
394 serializedData.push_back(static_cast<std::uint8_t>(m_paramCount));
395 for (size_t i = 0; i < m_parameterData.size(); i++)
396 {
397 serializedData.push_back(m_parameterData[i]);
398 }
399
400 return serializedData;
401}
402
403
404
405//==============================================================================
406// Class Ocp1Notification
407//==============================================================================
408
410{
411 ByteVector serializedData = m_header.GetSerializedData();
412
413 std::uint32_t notificationSize(m_header.GetMessageSize() - 9); // Message size minus the header
414 serializedData.push_back(static_cast<std::uint8_t>(notificationSize >> 24));
415 serializedData.push_back(static_cast<std::uint8_t>(notificationSize >> 16));
416 serializedData.push_back(static_cast<std::uint8_t>(notificationSize >> 8));
417 serializedData.push_back(static_cast<std::uint8_t>(notificationSize));
418 serializedData.push_back(static_cast<std::uint8_t>(m_emitterOno >> 24)); // TargetOno
419 serializedData.push_back(static_cast<std::uint8_t>(m_emitterOno >> 16));
420 serializedData.push_back(static_cast<std::uint8_t>(m_emitterOno >> 8));
421 serializedData.push_back(static_cast<std::uint8_t>(m_emitterOno));
422 std::uint16_t methodDefLevel = 3; // OcaSubscriptionManager
423 serializedData.push_back(static_cast<std::uint8_t>(methodDefLevel >> 8));
424 serializedData.push_back(static_cast<std::uint8_t>(methodDefLevel));
425 std::uint16_t methodIdx = 1; // AddSubscription
426 serializedData.push_back(static_cast<std::uint8_t>(methodIdx >> 8));
427 serializedData.push_back(static_cast<std::uint8_t>(methodIdx));
428 std::uint16_t paramCount = 2;
429 serializedData.push_back(static_cast<std::uint8_t>(paramCount));
430 std::uint16_t contextLength = 0;
431 serializedData.push_back(static_cast<std::uint8_t>(contextLength >> 8));
432 serializedData.push_back(static_cast<std::uint8_t>(contextLength));
433 serializedData.push_back(static_cast<std::uint8_t>(m_emitterOno >> 24)); // EmitterOno
434 serializedData.push_back(static_cast<std::uint8_t>(m_emitterOno >> 16));
435 serializedData.push_back(static_cast<std::uint8_t>(m_emitterOno >> 8));
436 serializedData.push_back(static_cast<std::uint8_t>(m_emitterOno));
437 std::uint16_t eventDefLevel = 1; // OcaRoot level
438 serializedData.push_back(static_cast<std::uint8_t>(eventDefLevel >> 8));
439 serializedData.push_back(static_cast<std::uint8_t>(eventDefLevel));
440 std::uint16_t eventIdx = 1; // PropertyChanged event
441 serializedData.push_back(static_cast<std::uint8_t>(eventIdx >> 8));
442 serializedData.push_back(static_cast<std::uint8_t>(eventIdx));
443 serializedData.push_back(static_cast<std::uint8_t>(m_emitterPropertyDefLevel >> 8));
444 serializedData.push_back(static_cast<std::uint8_t>(m_emitterPropertyDefLevel));
445 serializedData.push_back(static_cast<std::uint8_t>(m_emitterPropertyIndex >> 8));
446 serializedData.push_back(static_cast<std::uint8_t>(m_emitterPropertyIndex));
447 for (size_t i = 0; i < m_parameterData.size(); i++)
448 {
449 serializedData.push_back(m_parameterData[i]);
450 }
451 serializedData.push_back(static_cast<std::uint8_t>(1)); // Ending byte
452
453 return serializedData;
454}
455
456
457
458//==============================================================================
459// Class Ocp1KeepAlive
460//==============================================================================
461
462Ocp1KeepAlive::Ocp1KeepAlive(std::uint16_t heartBeatSeconds)
463 : Ocp1Message(static_cast<std::uint8_t>(KeepAlive),
464 DataFromUint16(heartBeatSeconds))
465{
466}
467
468Ocp1KeepAlive::Ocp1KeepAlive(std::uint32_t heartBeatMilliseconds)
469 : Ocp1Message(static_cast<std::uint8_t>(KeepAlive),
470 DataFromUint32(heartBeatMilliseconds))
471{
472}
473
475{
476 if (m_parameterData.size() == sizeof(std::uint16_t))
477 {
479 }
480
481 return 0;
482}
483
485{
486 if (m_parameterData.size() == sizeof(std::uint32_t))
487 {
489 }
490
491 return 0;
492}
493
495{
496 ByteVector serializedData = m_header.GetSerializedData();
497 serializedData.insert(serializedData.end(), m_parameterData.begin(), m_parameterData.end());
498
499 return serializedData;
500}
501
502}
ByteVector GetSerializedData() override
std::uint16_t m_msgCnt
std::uint32_t m_msgSize
static std::uint32_t CalculateMessageSize(std::uint8_t msgType, size_t parameterDataLength)
std::uint8_t m_syncVal
ByteVector GetSerializedData() const
static constexpr std::uint32_t Ocp1HeaderSize
std::uint8_t m_msgType
std::uint8_t GetMessageType() const
std::uint32_t GetMessageSize() const
Ocp1Header(std::uint8_t msgType, std::size_t parameterDataLength)
std::uint16_t m_protoVers
Ocp1KeepAlive(std::uint16_t heartBeatSeconds)
std::uint16_t GetHeartBeatSeconds() const
std::uint32_t GetHeartBeatMilliseconds() const
ByteVector GetSerializedData() override
Abstract base class for all OCP.1 protocol messages.
static std::uint32_t m_nextHandle
@ CommandResponseRequired
Command that expects a Response with a matching handle.
@ Response
Device reply to a CommandResponseRequired.
@ Command
Fire-and-forget command; no response expected.
@ KeepAlive
Heartbeat for connection supervision.
@ Notification
Unsolicited property change from device to client.
static std::unique_ptr< Ocp1Message > UnmarshalOcp1Message(const ByteVector &receivedData)
ByteVector m_parameterData
ByteVector GetSerializedData() override
std::uint16_t m_emitterPropertyDefLevel
std::uint16_t m_emitterPropertyIndex
std::uint32_t m_handle
ByteVector GetSerializedData() override
std::uint8_t m_paramCount
Type-erased OCA parameter value with built-in marshal/unmarshal support.
Definition Variant.h:102
std::vector< std::uint8_t > ToParamData(Ocp1DataType type=OCP1DATATYPE_NONE, bool *pOk=nullptr) const
Definition Variant.cpp:570
Minimal AES70 / OCP.1 TCP client/server library built on JUCE.
Definition NanoOcp1.cpp:23
ByteVector DataFromOnoForSubscription(std::uint32_t ono, bool add)
std::vector< std::uint8_t > ByteVector
Binary buffer type used throughout NanoOcp for all serialized OCP.1 data.
ByteVector DataFromUint32(std::uint32_t intValue)
std::uint32_t DataToUint32(const ByteVector &parameterData, bool *pOk)
ByteVector DataFromUint16(std::uint16_t value)
std::uint16_t DataToUint16(const ByteVector &parameterData, bool *pOk)
std::uint32_t ReadUint32(const char *buffer)
std::uint16_t ReadUint16(const char *buffer)
Parameter bundle that fully describes one OCA controllable property.
Definition Ocp1Message.h:72
virtual Ocp1CommandDefinition GetValueCommand() const
virtual Ocp1CommandDefinition * Clone() const
virtual Ocp1CommandDefinition AddSubscriptionCommand() const
virtual Ocp1CommandDefinition RemoveSubscriptionCommand() const
virtual Ocp1CommandDefinition SetValueCommand(const Variant &newValue) const
Ocp1DataType GetDataType() const