1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

MidiFile: Make file-reading more robust

This commit adds tests and fixes some potential crashes caused by
out-of-bounds reads.
This commit is contained in:
reuk 2020-10-20 12:25:11 +01:00
parent a98dc7553b
commit 0943291990
4 changed files with 669 additions and 103 deletions

View file

@ -60,9 +60,8 @@ namespace MidiBufferHelpers
if (maxBytes == 1)
return 1;
int n;
auto bytesLeft = MidiMessage::readVariableLengthVal (data + 1, n);
return jmin (maxBytes, n + 2 + bytesLeft);
const auto var = MidiMessage::readVariableLengthValue (data + 1, maxBytes - 1);
return jmin (maxBytes, var.value + 2 + var.bytesUsed);
}
if (byte >= 0x80)

View file

@ -46,23 +46,77 @@ namespace MidiFileHelpers
}
}
static bool parseMidiHeader (const uint8* &data, short& timeFormat, short& fileType, short& numberOfTracks) noexcept
template <typename Value>
struct Optional
{
auto ch = ByteOrder::bigEndianInt (data);
data += 4;
Optional() = default;
if (ch != ByteOrder::bigEndianInt ("MThd"))
Optional (const Value& v)
: value (v), valid (true) {}
Value value = Value();
bool valid = false;
};
template <typename Integral>
struct ReadTrait;
template <>
struct ReadTrait<uint32> { static constexpr auto read = ByteOrder::bigEndianInt; };
template <>
struct ReadTrait<uint16> { static constexpr auto read = ByteOrder::bigEndianShort; };
template <typename Integral>
Optional<Integral> tryRead (const uint8*& data, size_t& remaining)
{
using Trait = ReadTrait<Integral>;
constexpr auto size = sizeof (Integral);
if (remaining < size)
return {};
const Optional<Integral> result { Trait::read (data) };
data += size;
remaining -= size;
return result;
}
struct HeaderDetails
{
size_t bytesRead = 0;
short timeFormat = 0;
short fileType = 0;
short numberOfTracks = 0;
};
static Optional<HeaderDetails> parseMidiHeader (const uint8* const initialData,
const size_t maxSize)
{
auto* data = initialData;
auto remaining = maxSize;
auto ch = tryRead<uint32> (data, remaining);
if (! ch.valid)
return {};
if (ch.value != ByteOrder::bigEndianInt ("MThd"))
{
bool ok = false;
auto ok = false;
if (ch == ByteOrder::bigEndianInt ("RIFF"))
if (ch.value == ByteOrder::bigEndianInt ("RIFF"))
{
for (int i = 0; i < 8; ++i)
{
ch = ByteOrder::bigEndianInt (data);
data += 4;
ch = tryRead<uint32> (data, remaining);
if (ch == ByteOrder::bigEndianInt ("MThd"))
if (! ch.valid)
return {};
if (ch.value == ByteOrder::bigEndianInt ("MThd"))
{
ok = true;
break;
@ -71,21 +125,37 @@ namespace MidiFileHelpers
}
if (! ok)
return false;
return {};
}
auto bytesRemaining = ByteOrder::bigEndianInt (data);
data += 4;
fileType = (short) ByteOrder::bigEndianShort (data);
data += 2;
numberOfTracks = (short) ByteOrder::bigEndianShort (data);
data += 2;
timeFormat = (short) ByteOrder::bigEndianShort (data);
data += 2;
bytesRemaining -= 6;
data += bytesRemaining;
const auto bytesRemaining = tryRead<uint32> (data, remaining);
return true;
if (! bytesRemaining.valid || bytesRemaining.value > remaining)
return {};
const auto optFileType = tryRead<uint16> (data, remaining);
if (! optFileType.valid || 2 < optFileType.value)
return {};
const auto optNumTracks = tryRead<uint16> (data, remaining);
if (! optNumTracks.valid || (optFileType.value == 0 && optNumTracks.value != 1))
return {};
const auto optTimeFormat = tryRead<uint16> (data, remaining);
if (! optTimeFormat.valid)
return {};
HeaderDetails result;
result.fileType = (short) optFileType.value;
result.timeFormat = (short) optTimeFormat.value;
result.numberOfTracks = (short) optNumTracks.value;
result.bytesRead = maxSize - remaining;
return { result };
}
static double convertTicksToSeconds (double time,
@ -149,6 +219,47 @@ namespace MidiFileHelpers
}
}
}
static MidiMessageSequence readTrack (const uint8* data, int size)
{
double time = 0;
uint8 lastStatusByte = 0;
MidiMessageSequence result;
while (size > 0)
{
const auto delay = MidiMessage::readVariableLengthValue (data, (int) size);
if (delay.bytesUsed == 0)
break;
data += delay.bytesUsed;
size -= delay.bytesUsed;
time += delay.value;
if (size <= 0)
break;
int messSize = 0;
const MidiMessage mm (data, size, messSize, lastStatusByte, time);
if (messSize <= 0)
break;
size -= messSize;
data += messSize;
result.addEvent (mm);
auto firstByte = *(mm.getRawData());
if ((firstByte & 0xf0) != 0xf0)
lastStatusByte = firstByte;
}
return result;
}
}
//==============================================================================
@ -253,78 +364,56 @@ bool MidiFile::readFrom (InputStream& sourceStream, bool createMatchingNoteOffs)
const int maxSensibleMidiFileSize = 200 * 1024 * 1024;
// (put a sanity-check on the file size, as midi files are generally small)
if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize))
if (! sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize))
return false;
auto size = data.getSize();
auto d = static_cast<const uint8*> (data.getData());
const auto optHeader = MidiFileHelpers::parseMidiHeader (d, size);
if (! optHeader.valid)
return false;
const auto header = optHeader.value;
timeFormat = header.timeFormat;
d += header.bytesRead;
size -= (size_t) header.bytesRead;
for (int track = 0; track < header.numberOfTracks; ++track)
{
auto size = data.getSize();
auto d = static_cast<const uint8*> (data.getData());
short fileType, expectedTracks;
const auto optChunkType = MidiFileHelpers::tryRead<uint32> (d, size);
if (size > 16 && MidiFileHelpers::parseMidiHeader (d, timeFormat, fileType, expectedTracks))
{
size -= (size_t) (d - static_cast<const uint8*> (data.getData()));
int track = 0;
if (! optChunkType.valid)
return false;
for (;;)
{
auto chunkType = (int) ByteOrder::bigEndianInt (d);
d += 4;
auto chunkSize = (int) ByteOrder::bigEndianInt (d);
d += 4;
const auto optChunkSize = MidiFileHelpers::tryRead<uint32> (d, size);
if (chunkSize <= 0 || (size_t) chunkSize > size)
break;
if (! optChunkSize.valid)
return false;
if (chunkType == (int) ByteOrder::bigEndianInt ("MTrk"))
readNextTrack (d, chunkSize, createMatchingNoteOffs);
const auto chunkSize = optChunkSize.value;
if (++track >= expectedTracks)
break;
if (size < chunkSize)
return false;
size -= (size_t) chunkSize + 8;
d += chunkSize;
}
if (optChunkType.value == ByteOrder::bigEndianInt ("MTrk"))
readNextTrack (d, (int) chunkSize, createMatchingNoteOffs);
return true;
}
size -= chunkSize;
d += chunkSize;
}
return false;
return size == 0;
}
void MidiFile::readNextTrack (const uint8* data, int size, bool createMatchingNoteOffs)
{
double time = 0;
uint8 lastStatusByte = 0;
MidiMessageSequence result;
while (size > 0)
{
int bytesUsed;
auto delay = MidiMessage::readVariableLengthVal (data, bytesUsed);
data += bytesUsed;
size -= bytesUsed;
time += delay;
int messSize = 0;
const MidiMessage mm (data, size, messSize, lastStatusByte, time);
if (messSize <= 0)
break;
size -= messSize;
data += messSize;
result.addEvent (mm);
auto firstByte = *(mm.getRawData());
if ((firstByte & 0xf0) != 0xf0)
lastStatusByte = firstByte;
}
auto sequence = MidiFileHelpers::readTrack (data, size);
// sort so that we put all the note-offs before note-ons that have the same time
std::stable_sort (result.list.begin(), result.list.end(),
std::stable_sort (sequence.list.begin(), sequence.list.end(),
[] (const MidiMessageSequence::MidiEventHolder* a,
const MidiMessageSequence::MidiEventHolder* b)
{
@ -337,10 +426,10 @@ void MidiFile::readNextTrack (const uint8* data, int size, bool createMatchingNo
return a->message.isNoteOff() && b->message.isNoteOn();
});
addTrack (result);
if (createMatchingNoteOffs)
tracks.getLast()->updateMatchedPairs();
sequence.updateMatchedPairs();
addTrack (sequence);
}
//==============================================================================
@ -443,4 +532,267 @@ bool MidiFile::writeTrack (OutputStream& mainOut, const MidiMessageSequence& ms)
return true;
}
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
struct MidiFileTest : public UnitTest
{
MidiFileTest()
: UnitTest ("MidiFile", UnitTestCategories::midi)
{}
void runTest() override
{
beginTest ("ReadTrack respects running status");
{
const auto sequence = parseSequence ([] (OutputStream& os)
{
MidiFileHelpers::writeVariableLengthInt (os, 100);
writeBytes (os, { 0x90, 0x40, 0x40 });
MidiFileHelpers::writeVariableLengthInt (os, 200);
writeBytes (os, { 0x40, 0x40 });
MidiFileHelpers::writeVariableLengthInt (os, 300);
writeBytes (os, { 0xff, 0x2f, 0x00 });
});
expectEquals (sequence.getNumEvents(), 3);
expect (sequence.getEventPointer (0)->message.isNoteOn());
expect (sequence.getEventPointer (1)->message.isNoteOn());
expect (sequence.getEventPointer (2)->message.isEndOfTrackMetaEvent());
}
beginTest ("ReadTrack returns available messages if input is truncated");
{
{
const auto sequence = parseSequence ([] (OutputStream& os)
{
// Incomplete delta time
writeBytes (os, { 0xff });
});
expectEquals (sequence.getNumEvents(), 0);
}
{
const auto sequence = parseSequence ([] (OutputStream& os)
{
// Complete delta with no following event
MidiFileHelpers::writeVariableLengthInt (os, 0xffff);
});
expectEquals (sequence.getNumEvents(), 0);
}
{
const auto sequence = parseSequence ([] (OutputStream& os)
{
// Complete delta with malformed following event
MidiFileHelpers::writeVariableLengthInt (os, 0xffff);
writeBytes (os, { 0x90, 0x40 });
});
expectEquals (sequence.getNumEvents(), 1);
expect (sequence.getEventPointer (0)->message.isNoteOff());
expectEquals (sequence.getEventPointer (0)->message.getNoteNumber(), 0x40);
expectEquals (sequence.getEventPointer (0)->message.getVelocity(), (uint8) 0x00);
}
}
beginTest ("Header parsing works");
{
{
// No data
const auto header = parseHeader ([] (OutputStream&) {});
expect (! header.valid);
}
{
// Invalid initial byte
const auto header = parseHeader ([] (OutputStream& os)
{
writeBytes (os, { 0xff });
});
expect (! header.valid);
}
{
// Type block, but no header data
const auto header = parseHeader ([] (OutputStream& os)
{
writeBytes (os, { 'M', 'T', 'h', 'd' });
});
expect (! header.valid);
}
{
// We (ll-formed header, but track type is 0 and channels != 1
const auto header = parseHeader ([] (OutputStream& os)
{
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 0, 0, 16, 0, 1 });
});
expect (! header.valid);
}
{
// Well-formed header, but track type is 5
const auto header = parseHeader ([] (OutputStream& os)
{
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 5, 0, 16, 0, 1 });
});
expect (! header.valid);
}
{
// Well-formed header
const auto header = parseHeader ([] (OutputStream& os)
{
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 16, 0, 1 });
});
expect (header.valid);
expectEquals (header.value.fileType, (short) 1);
expectEquals (header.value.numberOfTracks, (short) 16);
expectEquals (header.value.timeFormat, (short) 1);
expectEquals ((int) header.value.bytesRead, 14);
}
}
beginTest ("Read from stream");
{
{
// Empty input
const auto file = parseFile ([] (OutputStream&) {});
expect (! file.valid);
}
{
// Malformed header
const auto file = parseFile ([] (OutputStream& os)
{
writeBytes (os, { 'M', 'T', 'h', 'd' });
});
expect (! file.valid);
}
{
// Header, no channels
const auto file = parseFile ([] (OutputStream& os)
{
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 0, 0, 1 });
});
expect (file.valid);
expectEquals (file.value.getNumTracks(), 0);
}
{
// Header, one malformed channel
const auto file = parseFile ([] (OutputStream& os)
{
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 });
writeBytes (os, { 'M', 'T', 'r', '?' });
});
expect (! file.valid);
}
{
// Header, one channel with malformed message
const auto file = parseFile ([] (OutputStream& os)
{
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 });
writeBytes (os, { 'M', 'T', 'r', 'k', 0, 0, 0, 1, 0xff });
});
expect (file.valid);
expectEquals (file.value.getNumTracks(), 1);
expectEquals (file.value.getTrack (0)->getNumEvents(), 0);
}
{
// Header, one channel with incorrect length message
const auto file = parseFile ([] (OutputStream& os)
{
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 });
writeBytes (os, { 'M', 'T', 'r', 'k', 0x0f, 0, 0, 0, 0xff });
});
expect (! file.valid);
}
{
// Header, one channel, all well-formed
const auto file = parseFile ([] (OutputStream& os)
{
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 });
writeBytes (os, { 'M', 'T', 'r', 'k', 0, 0, 0, 4 });
MidiFileHelpers::writeVariableLengthInt (os, 0x0f);
writeBytes (os, { 0x80, 0x00, 0x00 });
});
expect (file.valid);
expectEquals (file.value.getNumTracks(), 1);
auto& track = *file.value.getTrack (0);
expectEquals (track.getNumEvents(), 1);
expect (track.getEventPointer (0)->message.isNoteOff());
expectEquals (track.getEventPointer (0)->message.getTimeStamp(), (double) 0x0f);
}
}
}
template <typename Fn>
static MidiMessageSequence parseSequence (Fn&& fn)
{
MemoryOutputStream os;
fn (os);
return MidiFileHelpers::readTrack (reinterpret_cast<const uint8*> (os.getData()),
(int) os.getDataSize());
}
template <typename Fn>
static MidiFileHelpers::Optional<MidiFileHelpers::HeaderDetails> parseHeader (Fn&& fn)
{
MemoryOutputStream os;
fn (os);
return MidiFileHelpers::parseMidiHeader (reinterpret_cast<const uint8*> (os.getData()),
os.getDataSize());
}
template <typename Fn>
static MidiFileHelpers::Optional<MidiFile> parseFile (Fn&& fn)
{
MemoryOutputStream os;
fn (os);
MemoryInputStream is (os.getData(), os.getDataSize(), false);
MidiFile mf;
if (mf.readFrom (is))
return mf;
return {};
}
static void writeBytes (OutputStream& os, const std::vector<uint8>& bytes)
{
for (const auto& byte : bytes)
os.writeByte ((char) byte);
}
};
static MidiFileTest midiFileTests;
#endif
} // namespace juce

View file

@ -57,6 +57,32 @@ uint16 MidiMessage::pitchbendToPitchwheelPos (const float pitchbend,
}
//==============================================================================
MidiMessage::VariableLengthValue MidiMessage::readVariableLengthValue (const uint8* data, int maxBytesToUse) noexcept
{
uint32 v = 0;
// The largest allowable variable-length value is 0x0f'ff'ff'ff which is
// represented by the 4-byte stream 0xff 0xff 0xff 0x7f.
// Longer bytestreams risk overflowing a 32-bit signed int.
const auto limit = jmin (maxBytesToUse, 4);
for (int numBytesUsed = 0; numBytesUsed < limit; ++numBytesUsed)
{
const auto i = data[numBytesUsed];
v = (v << 7) + (i & 0x7f);
if (! (i & 0x80))
return { (int) v, numBytesUsed + 1 };
}
// If this is hit, the input was malformed. Either there were not enough
// bytes of input to construct a full value, or no terminating byte was
// found. This implementation only supports variable-length values of up
// to four bytes.
jassertfalse;
return {};
}
int MidiMessage::readVariableLengthVal (const uint8* data, int& numBytesUsed) noexcept
{
numBytesUsed = 0;
@ -224,16 +250,8 @@ MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const
}
else if (byte == 0xff)
{
if (sz == 1)
{
size = 1;
}
else
{
int n;
const int bytesLeft = readVariableLengthVal (src + 1, n);
size = jmin (sz + 1, n + 2 + bytesLeft);
}
const auto bytesLeft = readVariableLengthValue (src + 1, sz - 1);
size = jmin (sz + 1, bytesLeft.bytesUsed + 2 + bytesLeft.value);
auto dest = allocateSpace (size);
*dest = (uint8) byte;
@ -682,7 +700,7 @@ bool MidiMessage::isActiveSense() const noexcept { return *getRawData() == 0x
int MidiMessage::getMetaEventType() const noexcept
{
auto data = getRawData();
return *data != 0xff ? -1 : data[1];
return (size < 2 || *data != 0xff) ? -1 : data[1];
}
int MidiMessage::getMetaEventLength() const noexcept
@ -691,8 +709,8 @@ int MidiMessage::getMetaEventLength() const noexcept
if (*data == 0xff)
{
int n;
return jmin (size - 2, readVariableLengthVal (data + 2, n));
const auto var = readVariableLengthValue (data + 2, size - 2);
return jmax (0, jmin (size - 2 - var.bytesUsed, var.value));
}
return 0;
@ -702,10 +720,9 @@ const uint8* MidiMessage::getMetaEventData() const noexcept
{
jassert (isMetaEvent());
int n;
auto d = getRawData() + 2;
readVariableLengthVal (d, n);
return d + n;
const auto var = readVariableLengthValue (d, size - 2);
return d + var.bytesUsed;
}
bool MidiMessage::isTrackMetaEvent() const noexcept { return getMetaEventType() == 0; }
@ -1141,4 +1158,170 @@ const char* MidiMessage::getControllerName (const int n)
return isPositiveAndBelow (n, numElementsInArray (names)) ? names[n] : nullptr;
}
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
struct MidiMessageTest : public UnitTest
{
MidiMessageTest()
: UnitTest ("MidiMessage", UnitTestCategories::midi)
{}
void runTest() override
{
using std::begin;
using std::end;
beginTest ("ReadVariableLengthValue should return valid, backward-compatible results");
{
const std::vector<uint8> inputs[]
{
{ 0x00 },
{ 0x40 },
{ 0x7f },
{ 0x81, 0x00 },
{ 0xc0, 0x00 },
{ 0xff, 0x7f },
{ 0x81, 0x80, 0x00 },
{ 0xc0, 0x80, 0x00 },
{ 0xff, 0xff, 0x7f },
{ 0x81, 0x80, 0x80, 0x00 },
{ 0xc0, 0x80, 0x80, 0x00 },
{ 0xff, 0xff, 0xff, 0x7f }
};
const int outputs[]
{
0x00,
0x40,
0x7f,
0x80,
0x2000,
0x3fff,
0x4000,
0x100000,
0x1fffff,
0x200000,
0x8000000,
0xfffffff,
};
expectEquals (std::distance (begin (inputs), end (inputs)),
std::distance (begin (outputs), end (outputs)));
size_t index = 0;
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996)
for (const auto& input : inputs)
{
auto copy = input;
while (copy.size() < 16)
copy.push_back (0);
const auto result = MidiMessage::readVariableLengthValue (copy.data(),
(int) copy.size());
expectEquals (result.value, outputs[index]);
expectEquals (result.bytesUsed, (int) inputs[index].size());
int legacyNumUsed = 0;
const auto legacyResult = MidiMessage::readVariableLengthVal (copy.data(),
legacyNumUsed);
expectEquals (result.value, legacyResult);
expectEquals (result.bytesUsed, legacyNumUsed);
++index;
}
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
JUCE_END_IGNORE_WARNINGS_MSVC
}
beginTest ("ReadVariableLengthVal should return 0 if input is truncated");
{
for (size_t i = 0; i != 16; ++i)
{
std::vector<uint8> input;
input.resize (i, 0xFF);
const auto result = MidiMessage::readVariableLengthValue (input.data(),
(int) input.size());
expectEquals (result.value, 0);
expectEquals (result.bytesUsed, 0);
}
}
const std::vector<uint8> metaEvents[]
{
// Format is 0xff, followed by a 'kind' byte, followed by a variable-length
// 'data-length' value, followed by that many data bytes
{ 0xff, 0x00, 0x02, 0x00, 0x00 }, // Sequence number
{ 0xff, 0x01, 0x00 }, // Text event
{ 0xff, 0x02, 0x00 }, // Copyright notice
{ 0xff, 0x03, 0x00 }, // Track name
{ 0xff, 0x04, 0x00 }, // Instrument name
{ 0xff, 0x05, 0x00 }, // Lyric
{ 0xff, 0x06, 0x00 }, // Marker
{ 0xff, 0x07, 0x00 }, // Cue point
{ 0xff, 0x20, 0x01, 0x00 }, // Channel prefix
{ 0xff, 0x2f, 0x00 }, // End of track
{ 0xff, 0x51, 0x03, 0x01, 0x02, 0x03 }, // Set tempo
{ 0xff, 0x54, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05 }, // SMPTE offset
{ 0xff, 0x58, 0x04, 0x01, 0x02, 0x03, 0x04 }, // Time signature
{ 0xff, 0x59, 0x02, 0x01, 0x02 }, // Key signature
{ 0xff, 0x7f, 0x00 }, // Sequencer-specific
};
beginTest ("MidiMessage data constructor works for well-formed meta-events");
{
const auto status = (uint8) 0x90;
for (const auto& input : metaEvents)
{
int bytesUsed = 0;
const MidiMessage msg (input.data(), (int) input.size(), bytesUsed, status);
expect (msg.isMetaEvent());
expectEquals (msg.getMetaEventLength(), (int) input.size() - 3);
expectEquals (msg.getMetaEventType(), (int) input[1]);
}
}
beginTest ("MidiMessage data constructor works for malformed meta-events");
{
const auto status = (uint8) 0x90;
const auto runTest = [&] (const std::vector<uint8>& input)
{
int bytesUsed = 0;
const MidiMessage msg (input.data(), (int) input.size(), bytesUsed, status);
expect (msg.isMetaEvent());
expectEquals (msg.getMetaEventLength(), jmax (0, (int) input.size() - 3));
expectEquals (msg.getMetaEventType(), input.size() >= 2 ? input[1] : -1);
};
runTest ({ 0xff });
for (const auto& input : metaEvents)
{
auto copy = input;
copy[2] = 0x40; // Set the size of the message to more bytes than are present
runTest (copy);
}
}
}
};
static MidiMessageTest midiMessageTests;
#endif
} // namespace juce

View file

@ -858,11 +858,43 @@ public:
//==============================================================================
/** Reads a midi variable-length integer.
@param data the data to read the number from
@param numBytesUsed on return, this will be set to the number of bytes that were read
This signature has been deprecated in favour of the safer
readVariableLengthValue.
The `data` argument indicates the data to read the number from,
and `numBytesUsed` is used as an out-parameter to indicate the number
of bytes that were read.
*/
static int readVariableLengthVal (const uint8* data,
int& numBytesUsed) noexcept;
JUCE_DEPRECATED (static int readVariableLengthVal (const uint8* data,
int& numBytesUsed) noexcept);
/** Holds information about a variable-length value which was parsed
from a stream of bytes.
A valid value requires that `bytesUsed` is greater than 0.
If `bytesUsed <= 0` this object should be considered invalid.
*/
struct VariableLengthValue
{
VariableLengthValue() = default;
VariableLengthValue (int valueIn, int bytesUsedIn)
: value (valueIn), bytesUsed (bytesUsedIn) {}
int value = 0;
int bytesUsed = 0;
};
/** Reads a midi variable-length integer, with protection against buffer overflow.
@param data the data to read the number from
@param maxBytesToUse the number of bytes in the region following `data`
@returns a struct containing the parsed value, and the number
of bytes that were read. If parsing fails, both the
`value` and `bytesUsed` fields will be set to 0.
*/
static VariableLengthValue readVariableLengthValue (const uint8* data,
int maxBytesToUse) noexcept;
/** Based on the first byte of a short midi message, this uses a lookup table
to return the message length (either 1, 2, or 3 bytes).