Skip to content

Commit

Permalink
Merge pull request #129 from lathoub/mallformed_packets
Browse files Browse the repository at this point in the history
Fix for Malformed packets and long session names
Thanks @folkertvanheusden and @hallvardkristiansen for reporting and fixing
  • Loading branch information
lathoub authored Aug 21, 2021
2 parents ee56ae8 + be0be95 commit 877b39f
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 82 deletions.
5 changes: 5 additions & 0 deletions src/AppleMIDI.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ void AppleMIDISession<UdpClass, Settings, Platform>::parseControlPackets()
#endif
controlBuffer.pop_front();
}
else if (retVal == parserReturn::SessionNameVeryLong)
{
// purge the rest of the data in controlPort
while (controlPort.read() >= 0) {}
}
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/AppleMIDI_Defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ enum parserReturn: uint8_t
Processed,
NotSureGiveMeMoreData,
NotEnoughData,
UnexpectedData,
UnexpectedData,
UnexpectedMidiData,
UnexpectedJournalData,
SessionNameVeryLong,
};

#if defined(__AVR__)
Expand Down
49 changes: 31 additions & 18 deletions src/AppleMIDI_Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,29 +78,35 @@ class AppleMIDIParser

#ifdef KEEP_SESSION_NAME
uint16_t bi = 0;
while ((i < buffer.size()) && (buffer[i] != 0x00))
while (i < buffer.size())
{
if (bi < DefaultSettings::MaxSessionNameLen)
invitation.sessionName[bi++] = buffer[i];
i++;
invitation.sessionName[bi++] = buffer[i++];
else
i++;
}
invitation.sessionName[bi++] = '\0';
#else
while ((i < buffer.size()) && (buffer[i] != 0x00))
while (i < buffer.size())
i++;
#endif
// session name is optional.
// If i > minimum size (16), then a sessionName was provided and must include 0x00
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[i++] != 0x00)
return parserReturn::NotEnoughData;
if (i == buffer.size() && buffer[i] != 0x00)
retVal = parserReturn::SessionNameVeryLong;

while (i--)
buffer.pop_front(); // consume all the bytes that made up this message

session->ReceivedInvitation(invitation, portType);

return parserReturn::Processed;
return retVal;
}
else if (0 == memcmp(command, amEndSession, sizeof(amEndSession)))
{
Expand Down Expand Up @@ -267,29 +273,36 @@ class AppleMIDIParser

#ifdef KEEP_SESSION_NAME
uint16_t bi = 0;
while ((i < buffer.size()) && (buffer[i] != 0x00))
while (i < buffer.size())
{
if (bi < DefaultSettings::MaxSessionNameLen)
invitationAccepted.sessionName[bi++] = buffer[i];
i++;
invitationAccepted.sessionName[bi++] = buffer[i++];
else
i++;
}
invitationAccepted.sessionName[bi++] = '\0';
#else
while ((i < buffer.size()) && (buffer[i] != 0x00))
while (i < buffer.size())
i++;
#endif
// session name is optional.
// If i > minimum size (16), then a sessionName was provided and must include 0x00

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[i++] != 0x00)
return parserReturn::NotEnoughData;
if (i == buffer.size() && buffer[i] != 0x00)
retVal = parserReturn::SessionNameVeryLong;

while (i--)
buffer.pop_front(); // consume all the bytes that made up this message

session->ReceivedInvitationAccepted(invitationAccepted, portType);

return parserReturn::Processed;
return retVal;
}
else if (0 == memcmp(command, amInvitationRejected, sizeof(amInvitationRejected)))
{
Expand Down
18 changes: 16 additions & 2 deletions src/rtpMIDI_Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ class rtpMIDIParser
if (buffer.size() < minimumLen)
return parserReturn::NotSureGiveMeMoreData;

// 2.2. MIDI Payload (https://www.ietf.org/rfc/rfc4695.html#section-2.2)
// The payload MUST begin with the MIDI command section. The
// MIDI command section codes a (possibly empty) list of timestamped
// MIDI commands and provides the essential service of the payload
Expand Down Expand Up @@ -153,8 +154,15 @@ class rtpMIDIParser
if (midiCommandLength > 0)
{
auto retVal = decodeMidiSection(buffer);
if (retVal != parserReturn::Processed)
switch (retVal) {
case parserReturn::Processed:
break;
case parserReturn::UnexpectedMidiData:
// already processed MIDI data will be played
_rtpHeadersComplete = false;
default:
return retVal;
}
}

// The payload MAY also contain a journal section. The journal section
Expand All @@ -165,8 +173,14 @@ class rtpMIDIParser
if (rtpMidi_Flags & RTP_MIDI_CS_FLAG_J)
{
auto retVal = decodeJournalSection(buffer);
if (retVal != parserReturn::Processed)
switch (retVal) {
case parserReturn::Processed:
break;
case parserReturn::UnexpectedJournalData:
_rtpHeadersComplete = false;
default:
return retVal;
}
}

_rtpHeadersComplete = false;
Expand Down
43 changes: 43 additions & 0 deletions src/rtpMIDI_Parser_MidiCommandSection.hpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,45 @@
// https://www.ietf.org/rfc/rfc4695.html#section-3

parserReturn decodeMidiSection(RtpBuffer_t &buffer)
{
int cmdCount = 0;

// https://www.ietf.org/rfc/rfc4695.html#section-3.2
//
// The first MIDI channel command in the MIDI list MUST include a status
// octet.Running status coding, as defined in[MIDI], MAY be used for
// all subsequent MIDI channel commands in the list.As in[MIDI],
// System Commonand System Exclusive messages(0xF0 ... 0xF7) cancel
// the running status state, but System Real - time messages(0xF8 ...
// 0xFF) do not affect the running status state. All System commands in
// the MIDI list MUST include a status octet.

// As we note above, the first channel command in the MIDI list MUST
// include a status octet.However, the corresponding command in the
// original MIDI source data stream might not have a status octet(in
// this case, the source would be coding the command using running
// status). If the status octet of the first channel command in the
// MIDI list does not appear in the source data stream, the P(phantom)
// header bit MUST be set to 1. In all other cases, the P bit MUST be
// set to 0.
//
// Note that the P bit describes the MIDI source data stream, not the
// MIDI list encoding; regardless of the state of the P bit, the MIDI
// list MUST include the status octet.
//
// As receivers MUST be able to decode running status, sender
// implementors should feel free to use running status to improve
// bandwidth efficiency. However, senders SHOULD NOT introduce timing
// jitter into an existing MIDI command stream through an inappropriate
// use or removal of running status coding. This warning primarily
// applies to senders whose RTP MIDI streams may be transcoded onto a
// MIDI 1.0 DIN cable[MIDI] by the receiver : both the timestamps and
// the command coding (running status or not) must comply with the
// physical restrictions of implicit time coding over a slow serial
// line.

// (lathoub: RTP_MIDI_CS_FLAG_P((phantom) not implemented

uint8_t runningstatus = 0;

/* Multiple MIDI-commands might follow - the exact number can only be discovered by really decoding the commands! */
Expand Down Expand Up @@ -34,6 +72,11 @@ parserReturn decodeMidiSection(RtpBuffer_t &buffer)
return parserReturn::NotEnoughData;
}

if (consumed > midiCommandLength) {
buffer.clear();
return parserReturn::UnexpectedMidiData;
}

midiCommandLength -= consumed;

while (consumed--)
Expand Down
93 changes: 32 additions & 61 deletions test/Ethernet.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ class EthernetUDP
EthernetUDP()
{
_port = 0;




}

void begin(uint16_t port)
Expand All @@ -24,6 +28,21 @@ class EthernetUDP
if (port == DEFAULT_CONTROL_PORT && true)
{
// AppleMIDI messages
byte okSessionName[] = {
0xff, 0xff, 0x49, 0x4e, 0x00, 0x00, 0x00, 0x02, 0x4e, 0x27, 0x95, 0x9e, 0x00, 0x00, 0xec, 0xf9,
0x6c, 0x61, 0x70, 0x70, 0x69, 0x65, 0x6d, 0x63, 0x74, 0x6f, 0x70, 0x66, 0x61, 0x63, 0x65, 0x00
};

byte notOKSessionName[] = {
0xff, 0xff, 0x49, 0x4e, 0x00, 0x00, 0x00, 0x02, 0xcc, 0x0f, 0x6c, 0x49, 0x00, 0x00, 0xa4, 0x9b,
0x6c, 0x61, 0x70, 0x70, 0x69, 0x65, 0x6d, 0x63, 0x74, 0x6f, 0x70, 0x66, 0x61, 0x63, 0x65, 0x2f,
0x46, 0x4c, 0x55, 0x49, 0x44, 0x20, 0x53, 0x79, 0x6e, 0x74, 0x68, 0x20, 0x28, 0x36, 0x34, 0x37,
0x38, 0x29, 0x2d, 0x53, 0x79, 0x6e, 0x74, 0x68, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x70,
0x6f, 0x72, 0x74, 0x20, 0x28, 0x36, 0x34, 0x37, 0x38, 0x3a, 0x30, 0x29, 0x00
};

write(okSessionName, sizeof(okSessionName));

}

if (port == (DEFAULT_CONTROL_PORT + 1) && true)
Expand Down Expand Up @@ -309,7 +328,7 @@ class EthernetUDP

byte slecht[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};

write(rr2, sizeof(rr2));
// write(rr2, sizeof(rr2));
}

if (port == 5005 && true)
Expand Down Expand Up @@ -339,6 +358,17 @@ class EthernetUDP
return _buffer.size();
};

int read()
{
if (_buffer.size() == 0)
return -1;

byte value = _buffer.front();
_buffer.pop_front();

return value;
}

size_t read(byte* buffer, size_t size)
{
size = min(size, _buffer.size());
Expand All @@ -363,68 +393,9 @@ class EthernetUDP
};

void endPacket() { };

void flush()
{
if (_port == 5004)
{
if (_buffer[0] == 0xff && _buffer[1] == 0xff && _buffer[2] == 'I' &&_buffer[3] == 'N')
{
_buffer.clear();


byte u[] = {
0xff, 0xff,
0x4f, 0x4b,
0x00, 0x00, 0x00, 0x02,
0xb7, 0x06, 0x20, 0x30,
0xda, 0x8d, 0xc5, 0x8a,
0x4d, 0x61, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x20, 0x50, 0x72, 0x6f, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x53, 0x61, 0x6e, 0x64, 0x72, 0x61, 0x20, 0x56, 0x65, 0x72, 0x62, 0x65, 0x6b, 0x65, 0x6e, 0x20, 0x28, 0x32, 0x29, 0x00 };





byte r[] = { 0xff, 0xff,
0x4f, 0x4b,
0x00, 0x0, 0x00, 0x02,
0xb7, 0x06, 0x20, 0x30,
0xda, 0x8d, 0xc5, 0x8a,
0x53, 0x65, 0x73, 0x73, 0x69, 0x6, 0x6e, 0x31, 0x2d, 0x42, 0x00 };
write(u, sizeof(u));
}
}
if (_port == 5005)
{
if (_buffer[0] == 0xff && _buffer[1] == 0xff && _buffer[2] == 'I' &&_buffer[3] == 'N')
{
_buffer.clear();
byte r[] = { 0xff, 0xff,
0x4f, 0x4b,
0x00, 0x0, 0x00, 0x02,
0xb7, 0x06, 0x20, 0x30,
0xda, 0x8d, 0xc5, 0x8a,
0x53, 0x65, 0x73, 0x73, 0x69, 0x6, 0x6e, 0x31, 0x2d, 0x42, 0x00 };
write(r, sizeof(r));
}
else if (_buffer[0] == 0xff && _buffer[1] == 0xff && _buffer[2] == 'C' &&_buffer[3] == 'K')
{
if (_buffer[8] == 0x00)
{
_buffer.clear();
byte r[] = { 0xff, 0xff,
0x43, 0x4b,
0xda, 0x8d, 0xc5, 0x8a,
0x01,
0x65, 0x73, 0x73,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x34,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x6c, 0x83,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
write(r, sizeof(r));
}
else
_buffer.clear();
}
}
};

void stop() { _buffer.clear(); };
Expand Down

0 comments on commit 877b39f

Please sign in to comment.