420 lines
14 KiB
C++
420 lines
14 KiB
C++
#pragma once
|
|
|
|
#include "utility/Deque.h"
|
|
|
|
#include "AppleMIDI_Defs.h"
|
|
#include "AppleMIDI_Settings.h"
|
|
#include "AppleMIDI_Namespace.h"
|
|
|
|
BEGIN_APPLEMIDI_NAMESPACE
|
|
|
|
template <class UdpClass, class Settings, class Platform>
|
|
class AppleMIDISession;
|
|
|
|
template <class UdpClass, class Settings, class Platform>
|
|
class AppleMIDIParser
|
|
{
|
|
public:
|
|
AppleMIDISession<UdpClass, Settings, Platform> *session;
|
|
|
|
parserReturn parse(RtpBuffer_t &buffer, const amPortType &portType)
|
|
{
|
|
conversionBuffer cb;
|
|
|
|
uint8_t signature[2]; // Signature "Magic Value" for Apple network MIDI session establishment
|
|
uint8_t command[2]; // 16-bit command identifier (two ASCII characters, first in high 8 bits, second in low 8 bits)
|
|
|
|
size_t minimumLen = (sizeof(signature) + sizeof(command)); // Signature + Command
|
|
if (buffer.size() < minimumLen)
|
|
return parserReturn::NotSureGiveMeMoreData;
|
|
|
|
size_t i = 0;
|
|
|
|
signature[0] = buffer[i++];
|
|
signature[1] = buffer[i++];
|
|
if (0 != memcmp(signature, amSignature, sizeof(amSignature)))
|
|
{
|
|
return parserReturn::UnexpectedData;
|
|
}
|
|
|
|
command[0] = buffer[i++];
|
|
command[1] = buffer[i++];
|
|
|
|
if (0 == memcmp(command, amInvitation, sizeof(amInvitation)))
|
|
{
|
|
uint8_t protocolVersion[4];
|
|
|
|
minimumLen += (sizeof(protocolVersion) + sizeof(initiatorToken_t) + sizeof(ssrc_t));
|
|
if (buffer.size() < minimumLen)
|
|
{
|
|
return parserReturn::NotEnoughData;
|
|
}
|
|
|
|
// 2 (stored in network byte order (big-endian))
|
|
protocolVersion[0] = buffer[i++];
|
|
protocolVersion[1] = buffer[i++];
|
|
protocolVersion[2] = buffer[i++];
|
|
protocolVersion[3] = buffer[i++];
|
|
if (0 != memcmp(protocolVersion, amProtocolVersion, sizeof(amProtocolVersion)))
|
|
{
|
|
return parserReturn::UnexpectedData;
|
|
}
|
|
|
|
AppleMIDI_Invitation invitation;
|
|
|
|
// A random number generated by the session's APPLEMIDI_INITIATOR.
|
|
cb.buffer[0] = buffer[i++];
|
|
cb.buffer[1] = buffer[i++];
|
|
cb.buffer[2] = buffer[i++];
|
|
cb.buffer[3] = buffer[i++];
|
|
invitation.initiatorToken = __ntohl(cb.value32);
|
|
|
|
// The sender's synchronization source identifier.
|
|
cb.buffer[0] = buffer[i++];
|
|
cb.buffer[1] = buffer[i++];
|
|
cb.buffer[2] = buffer[i++];
|
|
cb.buffer[3] = buffer[i++];
|
|
invitation.ssrc = __ntohl(cb.value32);
|
|
|
|
#ifdef KEEP_SESSION_NAME
|
|
uint16_t bi = 0;
|
|
while (i < buffer.size())
|
|
{
|
|
if (bi < Settings::MaxSessionNameLen)
|
|
invitation.sessionName[bi++] = buffer[i++];
|
|
else
|
|
i++;
|
|
}
|
|
invitation.sessionName[bi++] = '\0';
|
|
#else
|
|
while (i < buffer.size())
|
|
i++;
|
|
#endif
|
|
auto retVal = parserReturn::Processed;
|
|
|
|
// when given a Session Name and the buffer has been fully processed and the
|
|
// last character is not 'endl', then we got a very long sessionName. It will
|
|
// continue in the next memory chunk of the packet. We don't care, so indicated
|
|
// flush the remainder of the packet.
|
|
// First part if the session name is kept, processing continues
|
|
if (i > minimumLen)
|
|
if (i == buffer.size() && buffer[buffer.size() - 1] != 0x00)
|
|
retVal = parserReturn::SessionNameVeryLong;
|
|
|
|
while (i > 0)
|
|
{
|
|
buffer.pop_front(); // consume all the bytes that made up this message
|
|
--i;
|
|
}
|
|
|
|
session->ReceivedInvitation(invitation, portType);
|
|
|
|
return retVal;
|
|
}
|
|
else if (0 == memcmp(command, amEndSession, sizeof(amEndSession)))
|
|
{
|
|
uint8_t protocolVersion[4];
|
|
|
|
minimumLen += (sizeof(protocolVersion) + sizeof(initiatorToken_t) + sizeof(ssrc_t));
|
|
if (buffer.size() < minimumLen)
|
|
return parserReturn::NotEnoughData;
|
|
|
|
// 2 (stored in network byte order (big-endian))
|
|
protocolVersion[0] = buffer[i++];
|
|
protocolVersion[1] = buffer[i++];
|
|
protocolVersion[2] = buffer[i++];
|
|
protocolVersion[3] = buffer[i++];
|
|
if (0 != memcmp(protocolVersion, amProtocolVersion, sizeof(amProtocolVersion)))
|
|
{
|
|
return parserReturn::UnexpectedData;
|
|
}
|
|
|
|
AppleMIDI_EndSession_t endSession;
|
|
|
|
// A random number generated by the session's APPLEMIDI_INITIATOR.
|
|
cb.buffer[0] = buffer[i++];
|
|
cb.buffer[1] = buffer[i++];
|
|
cb.buffer[2] = buffer[i++];
|
|
cb.buffer[3] = buffer[i++];
|
|
endSession.initiatorToken = __ntohl(cb.value32);
|
|
|
|
// The sender's synchronization source identifier.
|
|
cb.buffer[0] = buffer[i++];
|
|
cb.buffer[1] = buffer[i++];
|
|
cb.buffer[2] = buffer[i++];
|
|
cb.buffer[3] = buffer[i++];
|
|
endSession.ssrc = __ntohl(cb.value32);
|
|
|
|
while (i > 0)
|
|
{
|
|
buffer.pop_front(); // consume all the bytes that made up this message
|
|
--i;
|
|
}
|
|
|
|
session->ReceivedEndSession(endSession);
|
|
|
|
return parserReturn::Processed;
|
|
}
|
|
else if (0 == memcmp(command, amSynchronization, sizeof(amSynchronization)))
|
|
{
|
|
AppleMIDI_Synchronization_t synchronization;
|
|
|
|
// minimum amount : 4 bytes for sender SSRC, 1 byte for count, 3 bytes padding and 3 times 8 bytes
|
|
minimumLen += (4 + 1 + 3 + (3 * 8));
|
|
if (buffer.size() < minimumLen)
|
|
return parserReturn::NotEnoughData;
|
|
|
|
// The sender's synchronization source identifier.
|
|
cb.buffer[0] = buffer[i++];
|
|
cb.buffer[1] = buffer[i++];
|
|
cb.buffer[2] = buffer[i++];
|
|
cb.buffer[3] = buffer[i++];
|
|
synchronization.ssrc = __ntohl(cb.value32);
|
|
|
|
synchronization.count = buffer[i++];
|
|
buffer[i++];
|
|
buffer[i++];
|
|
buffer[i++]; // padding, unused
|
|
cb.buffer[0] = buffer[i++];
|
|
cb.buffer[1] = buffer[i++];
|
|
cb.buffer[2] = buffer[i++];
|
|
cb.buffer[3] = buffer[i++];
|
|
cb.buffer[4] = buffer[i++];
|
|
cb.buffer[5] = buffer[i++];
|
|
cb.buffer[6] = buffer[i++];
|
|
cb.buffer[7] = buffer[i++];
|
|
synchronization.timestamps[0] = __ntohll(cb.value64);
|
|
|
|
cb.buffer[0] = buffer[i++];
|
|
cb.buffer[1] = buffer[i++];
|
|
cb.buffer[2] = buffer[i++];
|
|
cb.buffer[3] = buffer[i++];
|
|
cb.buffer[4] = buffer[i++];
|
|
cb.buffer[5] = buffer[i++];
|
|
cb.buffer[6] = buffer[i++];
|
|
cb.buffer[7] = buffer[i++];
|
|
synchronization.timestamps[1] = __ntohll(cb.value64);
|
|
|
|
cb.buffer[0] = buffer[i++];
|
|
cb.buffer[1] = buffer[i++];
|
|
cb.buffer[2] = buffer[i++];
|
|
cb.buffer[3] = buffer[i++];
|
|
cb.buffer[4] = buffer[i++];
|
|
cb.buffer[5] = buffer[i++];
|
|
cb.buffer[6] = buffer[i++];
|
|
cb.buffer[7] = buffer[i++];
|
|
synchronization.timestamps[2] = __ntohll(cb.value64);
|
|
|
|
while (i > 0)
|
|
{
|
|
buffer.pop_front(); // consume all the bytes that made up this message
|
|
--i;
|
|
}
|
|
|
|
session->ReceivedSynchronization(synchronization);
|
|
|
|
return parserReturn::Processed;
|
|
}
|
|
else if (0 == memcmp(command, amReceiverFeedback, sizeof(amReceiverFeedback)))
|
|
{
|
|
AppleMIDI_ReceiverFeedback_t receiverFeedback;
|
|
|
|
minimumLen += (sizeof(receiverFeedback.ssrc) + sizeof(receiverFeedback.sequenceNr) + sizeof(receiverFeedback.dummy));
|
|
if (buffer.size() < minimumLen)
|
|
return parserReturn::NotEnoughData;
|
|
|
|
cb.buffer[0] = buffer[i++];
|
|
cb.buffer[1] = buffer[i++];
|
|
cb.buffer[2] = buffer[i++];
|
|
cb.buffer[3] = buffer[i++];
|
|
receiverFeedback.ssrc = __ntohl(cb.value32);
|
|
|
|
cb.buffer[0] = buffer[i++];
|
|
cb.buffer[1] = buffer[i++];
|
|
receiverFeedback.sequenceNr = __ntohs(cb.value16);
|
|
cb.buffer[0] = buffer[i++];
|
|
cb.buffer[1] = buffer[i++];
|
|
receiverFeedback.dummy = __ntohs(cb.value16);
|
|
|
|
while (i--)
|
|
buffer.pop_front(); // consume all the bytes that made up this message
|
|
|
|
session->ReceivedReceiverFeedback(receiverFeedback);
|
|
|
|
return parserReturn::Processed;
|
|
}
|
|
#ifdef APPLEMIDI_INITIATOR
|
|
else if (0 == memcmp(command, amInvitationAccepted, sizeof(amInvitationAccepted)))
|
|
{
|
|
uint8_t protocolVersion[4];
|
|
|
|
minimumLen += (sizeof(protocolVersion) + sizeof(initiatorToken_t) + sizeof(ssrc_t));
|
|
if (buffer.size() < minimumLen)
|
|
{
|
|
return parserReturn::NotEnoughData;
|
|
}
|
|
|
|
// 2 (stored in network byte order (big-endian))
|
|
protocolVersion[0] = buffer[i++];
|
|
protocolVersion[1] = buffer[i++];
|
|
protocolVersion[2] = buffer[i++];
|
|
protocolVersion[3] = buffer[i++];
|
|
if (0 != memcmp(protocolVersion, amProtocolVersion, sizeof(amProtocolVersion)))
|
|
{
|
|
return parserReturn::UnexpectedData;
|
|
}
|
|
|
|
AppleMIDI_InvitationAccepted_t invitationAccepted;
|
|
|
|
// A random number generated by the session's APPLEMIDI_INITIATOR.
|
|
cb.buffer[0] = buffer[i++];
|
|
cb.buffer[1] = buffer[i++];
|
|
cb.buffer[2] = buffer[i++];
|
|
cb.buffer[3] = buffer[i++];
|
|
invitationAccepted.initiatorToken = __ntohl(cb.value32);
|
|
|
|
// The sender's synchronization source identifier.
|
|
cb.buffer[0] = buffer[i++];
|
|
cb.buffer[1] = buffer[i++];
|
|
cb.buffer[2] = buffer[i++];
|
|
cb.buffer[3] = buffer[i++];
|
|
invitationAccepted.ssrc = __ntohl(cb.value32);
|
|
|
|
#ifdef KEEP_SESSION_NAME
|
|
uint16_t bi = 0;
|
|
while (i < buffer.size())
|
|
{
|
|
if (bi < Settings::MaxSessionNameLen)
|
|
invitationAccepted.sessionName[bi++] = buffer[i++];
|
|
else
|
|
i++;
|
|
}
|
|
invitationAccepted.sessionName[bi++] = '\0';
|
|
#else
|
|
while (i < buffer.size())
|
|
i++;
|
|
#endif
|
|
|
|
auto retVal = parserReturn::Processed;
|
|
|
|
// when given a Session Name and the buffer has been fully processed and the
|
|
// last character is not 'endl', then we got a very long sessionName. It will
|
|
// continue in the next memory chunk of the packet. We don't care, so indicated
|
|
// flush the remainder of the packet.
|
|
// First part if the session name is kept, processing continues
|
|
if (i > minimumLen)
|
|
if (i == buffer.size() && buffer[buffer.size() - 1] != 0x00)
|
|
retVal = parserReturn::SessionNameVeryLong;
|
|
|
|
while (i > 0)
|
|
{
|
|
buffer.pop_front(); // consume all the bytes that made up this message
|
|
--i;
|
|
}
|
|
|
|
session->ReceivedInvitationAccepted(invitationAccepted, portType);
|
|
|
|
return retVal;
|
|
}
|
|
else if (0 == memcmp(command, amInvitationRejected, sizeof(amInvitationRejected)))
|
|
{
|
|
uint8_t protocolVersion[4];
|
|
|
|
minimumLen += (sizeof(protocolVersion) + sizeof(initiatorToken_t) + sizeof(ssrc_t));
|
|
if (buffer.size() < minimumLen)
|
|
{
|
|
return parserReturn::NotEnoughData;
|
|
}
|
|
|
|
// 2 (stored in network byte order (big-endian))
|
|
protocolVersion[0] = buffer[i++];
|
|
protocolVersion[1] = buffer[i++];
|
|
protocolVersion[2] = buffer[i++];
|
|
protocolVersion[3] = buffer[i++];
|
|
if (0 != memcmp(protocolVersion, amProtocolVersion, sizeof(amProtocolVersion)))
|
|
{
|
|
return parserReturn::UnexpectedData;
|
|
}
|
|
|
|
AppleMIDI_InvitationRejected_t invitationRejected;
|
|
|
|
// A random number generated by the session's APPLEMIDI_INITIATOR.
|
|
cb.buffer[0] = buffer[i++];
|
|
cb.buffer[1] = buffer[i++];
|
|
cb.buffer[2] = buffer[i++];
|
|
cb.buffer[3] = buffer[i++];
|
|
invitationRejected.initiatorToken = __ntohl(cb.value32);
|
|
|
|
// The sender's synchronization source identifier.
|
|
cb.buffer[0] = buffer[i++];
|
|
cb.buffer[1] = buffer[i++];
|
|
cb.buffer[2] = buffer[i++];
|
|
cb.buffer[3] = buffer[i++];
|
|
invitationRejected.ssrc = __ntohl(cb.value32);
|
|
|
|
#ifdef KEEP_SESSION_NAME
|
|
uint16_t bi = 0;
|
|
while ((i < buffer.size()) && (buffer[i] != 0x00))
|
|
{
|
|
if (bi < Settings::MaxSessionNameLen)
|
|
invitationRejected.sessionName[bi++] = buffer[i];
|
|
i++;
|
|
}
|
|
invitationRejected.sessionName[bi++] = '\0';
|
|
#else
|
|
while ((i < buffer.size()) && (buffer[i] != 0x00))
|
|
i++;
|
|
#endif
|
|
// session name is optional.
|
|
// If i > minimum size (16), then a sessionName was provided and must include 0x00
|
|
if (i > minimumLen)
|
|
if (i == buffer.size() || buffer[i++] != 0x00)
|
|
return parserReturn::NotEnoughData;
|
|
|
|
while (i > 0)
|
|
{
|
|
buffer.pop_front(); // consume all the bytes that made up this message
|
|
--i;
|
|
}
|
|
|
|
session->ReceivedInvitationRejected(invitationRejected);
|
|
|
|
return parserReturn::Processed;
|
|
}
|
|
else if (0 == memcmp(command, amBitrateReceiveLimit, sizeof(amBitrateReceiveLimit)))
|
|
{
|
|
AppleMIDI_BitrateReceiveLimit bitrateReceiveLimit;
|
|
|
|
minimumLen += (sizeof(bitrateReceiveLimit.ssrc) + sizeof(bitrateReceiveLimit.bitratelimit));
|
|
if (buffer.size() < minimumLen)
|
|
return parserReturn::NotEnoughData;
|
|
|
|
cb.buffer[0] = buffer[i++];
|
|
cb.buffer[1] = buffer[i++];
|
|
cb.buffer[2] = buffer[i++];
|
|
cb.buffer[3] = buffer[i++];
|
|
bitrateReceiveLimit.ssrc = __ntohl(cb.value32);
|
|
|
|
cb.buffer[0] = buffer[i++];
|
|
cb.buffer[1] = buffer[i++];
|
|
cb.buffer[2] = buffer[i++];
|
|
cb.buffer[3] = buffer[i++];
|
|
bitrateReceiveLimit.bitratelimit = __ntohl(cb.value32);
|
|
|
|
while (i > 0)
|
|
{
|
|
buffer.pop_front(); // consume all the bytes that made up this message
|
|
--i;
|
|
}
|
|
|
|
session->ReceivedBitrateReceiveLimit(bitrateReceiveLimit);
|
|
|
|
return parserReturn::Processed;
|
|
}
|
|
#endif
|
|
return parserReturn::UnexpectedData;
|
|
}
|
|
};
|
|
|
|
END_APPLEMIDI_NAMESPACE
|