Skip to content

Commit 0eb25f2

Browse files
Merge pull request firmata#332 from zfields/memory
Abstract FirmataParser memory allocation scheme
2 parents 62e39c4 + 6463281 commit 0eb25f2

File tree

4 files changed

+175
-79
lines changed

4 files changed

+175
-79
lines changed

Firmata.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ void FirmataClass::endSysex(void)
7575
* An instance named "Firmata" is created automatically for the user.
7676
*/
7777
FirmataClass::FirmataClass()
78+
:
79+
parser(FirmataParser(parserBuffer, MAX_DATA_BYTES))
7880
{
7981
firmwareVersionCount = 0;
8082
firmwareVersionVector = 0;

Firmata.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ class FirmataClass
9797
void endSysex(void);
9898

9999
private:
100+
uint8_t parserBuffer[MAX_DATA_BYTES];
100101
FirmataMarshaller marshaller;
101102
FirmataParser parser;
102103
Stream *FirmataStream;

FirmataParser.cpp

Lines changed: 159 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -17,33 +17,41 @@
1717

1818
#include "FirmataParser.h"
1919

20+
#include "FirmataConstants.h"
21+
2022
//******************************************************************************
2123
//* Constructors
2224
//******************************************************************************
2325

2426
/**
25-
* The Firmata class.
26-
* An instance named "Firmata" is created automatically for the user.
27+
* The FirmataParser class.
28+
* @param dataBuffer A pointer to an external buffer used to store parsed data
29+
* @param dataBufferSize The size of the external buffer
2730
*/
28-
FirmataParser::FirmataParser()
31+
FirmataParser::FirmataParser(uint8_t * const dataBuffer, size_t dataBufferSize)
2932
:
30-
executeMultiByteCommand(0),
31-
multiByteChannel(0),
32-
waitForData(0),
33-
parsingSysex(false),
34-
sysexBytesRead(0),
35-
currentAnalogCallback((callbackFunction)NULL),
36-
currentDigitalCallback((callbackFunction)NULL),
37-
currentReportAnalogCallback((callbackFunction)NULL),
38-
currentReportDigitalCallback((callbackFunction)NULL),
39-
currentPinModeCallback((callbackFunction)NULL),
40-
currentPinValueCallback((callbackFunction)NULL),
41-
currentReportFirmwareCallback((systemCallbackFunction)NULL),
42-
currentReportVersionCallback((systemCallbackFunction)NULL),
43-
currentSystemResetCallback((systemCallbackFunction)NULL),
44-
currentStringCallback((stringCallbackFunction)NULL),
45-
currentSysexCallback((sysexCallbackFunction)NULL)
33+
dataBuffer(dataBuffer),
34+
dataBufferSize(dataBufferSize),
35+
executeMultiByteCommand(0),
36+
multiByteChannel(0),
37+
waitForData(0),
38+
parsingSysex(false),
39+
sysexBytesRead(0),
40+
currentDataBufferOverflowCallbackContext((void *)NULL),
41+
currentAnalogCallback((callbackFunction)NULL),
42+
currentDigitalCallback((callbackFunction)NULL),
43+
currentReportAnalogCallback((callbackFunction)NULL),
44+
currentReportDigitalCallback((callbackFunction)NULL),
45+
currentPinModeCallback((callbackFunction)NULL),
46+
currentPinValueCallback((callbackFunction)NULL),
47+
currentReportFirmwareCallback((systemCallbackFunction)NULL),
48+
currentReportVersionCallback((systemCallbackFunction)NULL),
49+
currentSystemResetCallback((systemCallbackFunction)NULL),
50+
currentStringCallback((stringCallbackFunction)NULL),
51+
currentSysexCallback((sysexCallbackFunction)NULL),
52+
currentDataBufferOverflowCallback((dataBufferOverflowCallbackFunction)NULL)
4653
{
54+
allowBufferUpdate = ((uint8_t *)NULL == dataBuffer);
4755
}
4856

4957
//******************************************************************************
@@ -53,47 +61,6 @@ FirmataParser::FirmataParser()
5361
//------------------------------------------------------------------------------
5462
// Serial Receive Handling
5563

56-
/**
57-
* Process incoming sysex messages. Handles REPORT_FIRMWARE and STRING_DATA internally.
58-
* Calls callback function for STRING_DATA and all other sysex messages.
59-
* @private
60-
*/
61-
void FirmataParser::processSysexMessage(void)
62-
{
63-
switch (storedInputData[0]) { //first byte in buffer is command
64-
case REPORT_FIRMWARE:
65-
if (currentReportFirmwareCallback)
66-
(*currentReportFirmwareCallback)();
67-
break;
68-
case STRING_DATA:
69-
if (currentStringCallback) {
70-
size_t bufferLength = (sysexBytesRead - 1) / 2;
71-
size_t i = 1;
72-
size_t j = 0;
73-
while (j < bufferLength) {
74-
// The string length will only be at most half the size of the
75-
// stored input buffer so we can decode the string within the buffer.
76-
storedInputData[j] = storedInputData[i];
77-
i++;
78-
storedInputData[j] += (storedInputData[i] << 7);
79-
i++;
80-
j++;
81-
}
82-
// Make sure string is null terminated. This may be the case for data
83-
// coming from client libraries in languages that don't null terminate
84-
// strings.
85-
if (storedInputData[j - 1] != '\0') {
86-
storedInputData[j] = '\0';
87-
}
88-
(*currentStringCallback)((char *)&storedInputData[0]);
89-
}
90-
break;
91-
default:
92-
if (currentSysexCallback)
93-
(*currentSysexCallback)(storedInputData[0], sysexBytesRead - 1, storedInputData + 1);
94-
}
95-
}
96-
9764
/**
9865
* Parse data from the input stream.
9966
* @param inputData A single byte to be added to the parser.
@@ -110,43 +77,43 @@ void FirmataParser::parse(uint8_t inputData)
11077
processSysexMessage();
11178
} else {
11279
//normal data byte - add to buffer
113-
storedInputData[sysexBytesRead] = inputData;
114-
sysexBytesRead++;
80+
bufferDataAtPosition(inputData, sysexBytesRead);
81+
++sysexBytesRead;
11582
}
11683
} else if ( (waitForData > 0) && (inputData < 128) ) {
117-
waitForData--;
118-
storedInputData[waitForData] = inputData;
84+
--waitForData;
85+
bufferDataAtPosition(inputData, waitForData);
11986
if ( (waitForData == 0) && executeMultiByteCommand ) { // got the whole message
12087
switch (executeMultiByteCommand) {
12188
case ANALOG_MESSAGE:
12289
if (currentAnalogCallback) {
12390
(*currentAnalogCallback)(multiByteChannel,
124-
(storedInputData[0] << 7)
125-
+ storedInputData[1]);
91+
(dataBuffer[0] << 7)
92+
+ dataBuffer[1]);
12693
}
12794
break;
12895
case DIGITAL_MESSAGE:
12996
if (currentDigitalCallback) {
13097
(*currentDigitalCallback)(multiByteChannel,
131-
(storedInputData[0] << 7)
132-
+ storedInputData[1]);
98+
(dataBuffer[0] << 7)
99+
+ dataBuffer[1]);
133100
}
134101
break;
135102
case SET_PIN_MODE:
136103
if (currentPinModeCallback)
137-
(*currentPinModeCallback)(storedInputData[1], storedInputData[0]);
104+
(*currentPinModeCallback)(dataBuffer[1], dataBuffer[0]);
138105
break;
139106
case SET_DIGITAL_PIN_VALUE:
140107
if (currentPinValueCallback)
141-
(*currentPinValueCallback)(storedInputData[1], storedInputData[0]);
108+
(*currentPinValueCallback)(dataBuffer[1], dataBuffer[0]);
142109
break;
143110
case REPORT_ANALOG:
144111
if (currentReportAnalogCallback)
145-
(*currentReportAnalogCallback)(multiByteChannel, storedInputData[0]);
112+
(*currentReportAnalogCallback)(multiByteChannel, dataBuffer[0]);
146113
break;
147114
case REPORT_DIGITAL:
148115
if (currentReportDigitalCallback)
149-
(*currentReportDigitalCallback)(multiByteChannel, storedInputData[0]);
116+
(*currentReportDigitalCallback)(multiByteChannel, dataBuffer[0]);
150117
break;
151118
}
152119
executeMultiByteCommand = 0;
@@ -197,6 +164,31 @@ const
197164
return (waitForData > 0 || parsingSysex);
198165
}
199166

167+
/**
168+
* Provides a mechanism to either set or update the working buffer of the parser.
169+
* The method will be enabled when no buffer has been provided, or an overflow
170+
* condition exists.
171+
* @param dataBuffer A pointer to an external buffer used to store parsed data
172+
* @param dataBufferSize The size of the external buffer
173+
*/
174+
int FirmataParser::setDataBufferOfSize(uint8_t * dataBuffer, size_t dataBufferSize)
175+
{
176+
int result;
177+
178+
if ( !allowBufferUpdate ) {
179+
result = __LINE__;
180+
} else if ((uint8_t *)NULL == dataBuffer) {
181+
result = __LINE__;
182+
} else {
183+
this->dataBuffer = dataBuffer;
184+
this->dataBufferSize = dataBufferSize;
185+
allowBufferUpdate = false;
186+
result = 0;
187+
}
188+
189+
return result;
190+
}
191+
200192
/**
201193
* Attach a generic sysex callback function to a command (options are: ANALOG_MESSAGE,
202194
* DIGITAL_MESSAGE, REPORT_ANALOG, REPORT DIGITAL, SET_PIN_MODE and SET_DIGITAL_PIN_VALUE).
@@ -249,9 +241,21 @@ void FirmataParser::attach(uint8_t command, stringCallbackFunction newFunction)
249241
*/
250242
void FirmataParser::attach(uint8_t command, sysexCallbackFunction newFunction)
251243
{
244+
(void)command;
252245
currentSysexCallback = newFunction;
253246
}
254247

248+
/**
249+
* Attach a buffer overflow callback
250+
* @param newFunction A reference to the buffer overflow callback function to attach.
251+
* @param context The context supplied by the end-user, and provided during the execution of the callback
252+
*/
253+
void FirmataParser::attach(dataBufferOverflowCallbackFunction newFunction, void * context)
254+
{
255+
currentDataBufferOverflowCallback = newFunction;
256+
currentDataBufferOverflowCallbackContext = context;
257+
}
258+
255259
/**
256260
* Detach a callback function for a specified command (such as SYSTEM_RESET, STRING_DATA,
257261
* ANALOG_MESSAGE, DIGITAL_MESSAGE, etc).
@@ -272,10 +276,91 @@ void FirmataParser::detach(uint8_t command)
272276
}
273277
}
274278

279+
/**
280+
* Detach the buffer overflow callback
281+
* @param <unused> Any pointer of type dataBufferOverflowCallbackFunction.
282+
*/
283+
void FirmataParser::detach(dataBufferOverflowCallbackFunction)
284+
{
285+
currentDataBufferOverflowCallback = (dataBufferOverflowCallbackFunction)NULL;
286+
currentDataBufferOverflowCallbackContext = (void *)NULL;
287+
}
288+
275289
//******************************************************************************
276290
//* Private Methods
277291
//******************************************************************************
278292

293+
/**
294+
* Buffer abstraction to prevent memory corruption
295+
* @param data The byte to put into the buffer
296+
* @param pos The position to insert the byte into the buffer
297+
* @return writeError A boolean to indicate if an error occured
298+
* @private
299+
*/
300+
bool FirmataParser::bufferDataAtPosition(const uint8_t data, const size_t pos)
301+
{
302+
bool bufferOverflow = (pos >= dataBufferSize);
303+
304+
// Notify of overflow condition
305+
if ( bufferOverflow
306+
&& ((dataBufferOverflowCallbackFunction)NULL != currentDataBufferOverflowCallback) )
307+
{
308+
allowBufferUpdate = true;
309+
currentDataBufferOverflowCallback(currentDataBufferOverflowCallbackContext);
310+
// Check if overflow was resolved during callback
311+
bufferOverflow = (pos >= dataBufferSize);
312+
}
313+
314+
// Write data to buffer if no overflow condition persist
315+
if ( !bufferOverflow )
316+
{
317+
dataBuffer[pos] = data;
318+
}
319+
320+
return bufferOverflow;
321+
}
322+
323+
/**
324+
* Process incoming sysex messages. Handles REPORT_FIRMWARE and STRING_DATA internally.
325+
* Calls callback function for STRING_DATA and all other sysex messages.
326+
* @private
327+
*/
328+
void FirmataParser::processSysexMessage(void)
329+
{
330+
switch (dataBuffer[0]) { //first byte in buffer is command
331+
case REPORT_FIRMWARE:
332+
if (currentReportFirmwareCallback)
333+
(*currentReportFirmwareCallback)();
334+
break;
335+
case STRING_DATA:
336+
if (currentStringCallback) {
337+
size_t bufferLength = (sysexBytesRead - 1) / 2;
338+
size_t i = 1;
339+
size_t j = 0;
340+
while (j < bufferLength) {
341+
// The string length will only be at most half the size of the
342+
// stored input buffer so we can decode the string within the buffer.
343+
bufferDataAtPosition(dataBuffer[i], j);
344+
++i;
345+
bufferDataAtPosition((dataBuffer[j] + (dataBuffer[i] << 7)), j);
346+
++i;
347+
++j;
348+
}
349+
// Make sure string is null terminated. This may be the case for data
350+
// coming from client libraries in languages that don't null terminate
351+
// strings.
352+
if (dataBuffer[j - 1] != '\0') {
353+
bufferDataAtPosition('\0', j);
354+
}
355+
(*currentStringCallback)((char *)&dataBuffer[0]);
356+
}
357+
break;
358+
default:
359+
if (currentSysexCallback)
360+
(*currentSysexCallback)(dataBuffer[0], sysexBytesRead - 1, dataBuffer + 1);
361+
}
362+
}
363+
279364
/**
280365
* Resets the system state upon a SYSTEM_RESET message from the host software.
281366
* @private
@@ -288,8 +373,8 @@ void FirmataParser::systemReset(void)
288373
executeMultiByteCommand = 0; // execute this after getting multi-byte data
289374
multiByteChannel = 0; // channel data for multiByteCommands
290375

291-
for (i = 0; i < MAX_DATA_BYTES; i++) {
292-
storedInputData[i] = 0;
376+
for (i = 0; i < dataBufferSize; ++i) {
377+
dataBuffer[i] = 0;
293378
}
294379

295380
parsingSysex = false;
@@ -298,4 +383,3 @@ void FirmataParser::systemReset(void)
298383
if (currentSystemResetCallback)
299384
(*currentSystemResetCallback)();
300385
}
301-

0 commit comments

Comments
 (0)