UNPKG

sphero-pwn

Version:
175 lines (157 loc) 5.66 kB
# The tokenizer is a finite state machine that consumes characters and # transitions between the states below. START_OF_PACKET = 0 # Looking for the start-of-packet byte, 0xFF PACKET_TYPE = 1 # Reading the packet type RESPONSE_CODE = 2 # Reading the response code byte RESPONSE_SEQUENCE = 3 # Reading the response sequence byte RESPONSE_DATA_LENGTH = 4 # Reading the response data length RESPONSE_DATA = 5 # Reading response data bytes RESPONSE_CHECKSUM = 6 # Reading the response checksum ASYNC_ID_CODE = 7 # Reading the async message ID code (type) ASYNC_DATA_LENGTH_MSB = 8 # Reading the MSB of the async message's data length ASYNC_DATA_LENGTH_LSB = 9 # Reading the LSB of the async message's data length ASYNC_DATA = 10 # Reading async message data bytes ASYNC_CHECKSUM = 11 # Reading the async message's checksum # Splits incoming data bytes into packets. # # This does not implement the EventEmitter interface because the tokenizer is # expected to have a single object (a Session instance) listening to all its # events. module.exports = class Tokenizer # Creates a tokenizer with no stored data. constructor: -> @_state = START_OF_PACKET # The response code or async message ID code. @_code = null @_sequence = null @_dataLeft = null @_dataOffset = null @_data = null @_checksum = 0 # Called when a data stream error is encountered. # # @param {Error} error the error that was encountered, with a descriptive # message onError: (error) -> return # Called when a byte is encountered outside a binary packet. # # This should only happen when the robot's shell is used. Returned characters # should be displayed to the user as they are. # # @param {String} char a one-character string onText: (char) -> return # Called when a command response is decoded from the stream. # # @param {Response} response a fully decoded response onResponse: (response) -> return # Called when an asynchronous message is decoded from the stream. # # @param {Async} a fully decoded asynchronous message onAsync: (async) -> return # Tokenizes a new chunk of data. # # @param {Buffer} data the chunk of data to be tokenized consume: (data) -> i = 0 while i < data.length byte = data[i] i += 1 switch @_state # Packet header. when START_OF_PACKET if byte is 0xFF @_state = PACKET_TYPE else @onText String.fromCharCode(byte) when PACKET_TYPE @_checksum = 0 if byte is 0xFF @_state = RESPONSE_CODE else if byte is 0xFE @_state = ASYNC_ID_CODE else @_state = START_OF_PACKET @onError new Error("Invalid packet type #{byte}") # Response header. when RESPONSE_CODE @_checksum = (@_checksum + byte) & 0xFF @_code = byte @_state = RESPONSE_SEQUENCE when RESPONSE_SEQUENCE @_checksum = (@_checksum + byte) & 0xFF @_sequence = byte @_state = RESPONSE_DATA_LENGTH when RESPONSE_DATA_LENGTH @_checksum = (@_checksum + byte) & 0xFF @_newDataBuffer byte - 1 @_state = RESPONSE_DATA @_checkDoneReadingData() # Async message header. when ASYNC_ID_CODE @_checksum = (@_checksum + byte) & 0xFF @_code = byte @_state = ASYNC_DATA_LENGTH_MSB when ASYNC_DATA_LENGTH_MSB @_checksum = (@_checksum + byte) & 0xFF @_dataLeft = (byte << 8) @_state = ASYNC_DATA_LENGTH_LSB when ASYNC_DATA_LENGTH_LSB @_checksum = (@_checksum + byte) & 0xFF @_newDataBuffer((@_dataLeft | byte) - 1) @_state = ASYNC_DATA @_checkDoneReadingData() # Data. when RESPONSE_DATA, ASYNC_DATA # TODO(pwnall): This can be optimized by grabbing all the data left # in the buffer at once. @_checksum = (@_checksum + byte) & 0xFF @_data[@_dataOffset] = byte @_dataOffset += 1 @_dataLeft -= 1 @_checkDoneReadingData() # Response checksum and construction. when RESPONSE_CHECKSUM expected = @_checksum ^ 0xFF if expected != byte @onError new Error( "Invalid response checksum #{byte}, expected #{expected}") else response = code: @_code, sequence: @_sequence, data: @_data @onResponse response @_state = START_OF_PACKET @_data = null @_dataLeft = null # Async message checksum and construction. when ASYNC_CHECKSUM expected = @_checksum ^ 0xFF if expected != byte @onError new Error( "Invalid async message checksum #{byte}, expected #{expected}") else async = idCode: @_code, data: @_data @onAsync async @_state = START_OF_PACKET @_data = null @_dataLeft = null # Create a new data buffer for a response or async message. # # @param {Number} dataSize the number of data bytes expected _newDataBuffer: (dataSize) -> @_data = new Buffer dataSize @_dataLeft = dataSize @_dataOffset = 0 return # Transition the FSM to the next state if we're done reading all the data. _checkDoneReadingData: -> return unless @_dataLeft is 0 if @_state == RESPONSE_DATA @_state = RESPONSE_CHECKSUM else @_state = ASYNC_CHECKSUM return