def raw_write_file(device, buf, applet_id, file_index, raw): logger.debug('Preparing to write file') size = len(buf) command = MessageConst.REQUEST_WRITE_RAW_FILE if raw else MessageConst.REQUEST_WRITE_FILE message = Message(command, [(file_index, 1, 1), (size, 2, 3), (applet_id, 5, 2)]) send_message(device, message, MessageConst.RESPONSE_WRITE_FILE) logger.debug('Writing block file data') write_extended_data(device, buf) message = Message(MessageConst.REQUEST_CONFIRM_WRITE_FILE) send_message(device, message, MessageConst.RESPONSE_CONFIRM_WRITE_FILE) logger.info('Writing file complete')
def read_extended_data(device, size): """ Read binary data blocks in response to some other command, handling segmentation and checksum validation. The command sequence is: While data left to read OUT: 0x10 ASMESSAGE_REQUEST_BLOCK_READ IN: 0x4d ASMESSAGE_RESPONSE_BLOCK_READ OUT: data """ logger.debug('Reading extended data') remaining = size message = Message(MessageConst.REQUEST_BLOCK_READ, []) result = util.create_buffer(0) while remaining > 0: response = send_message(device, message) if response.command() == MessageConst.RESPONSE_BLOCK_READ_EMPTY: break if response.command() == MessageConst.RESPONSE_BLOCK_READ: blocksize = response.argument(1, 4) checksum = response.argument(5, 2) buf = device.read(blocksize) assert calculate_data_checksum(buf) == checksum result.extend(buf) remaining = remaining - len(buf) else: raise NeotoolsError('Unexpected response %s' % response) return result.tobytes()
def set_settings(device, applet_id, settings): settings_buf = settings.to_raw() checksum = calculate_data_checksum(settings_buf) device.dialogue_start() logger.info('Requesting to write settings for applet_id=%s', applet_id) message = Message(MessageConst.REQUEST_SET_SETTINGS, [(len(settings_buf), 1, 4), (checksum, 5, 2)]) send_message(device, message, MessageConst.RESPONSE_BLOCK_WRITE) logger.info('Writing settings') device.write(settings_buf) receive_message(device, MessageConst.RESPONSE_BLOCK_WRITE_DONE) message = Message(MessageConst.REQUEST_SET_APPLET, [(0, 1, 4), (applet_id, 5, 2)]) send_message(device, message, MessageConst.RESPONSE_SET_APPLET) device.dialogue_end()
def flip_to_keyboard_mode(self): logger.info('Switching Neo to keyboard mode') self.dialogue_start() message = Message(MessageConst.REQUEST_RESTART, []) response = send_message(self, message) assert_success(response, MessageConst.RESPONSE_RESTART) self.dialogue_end()
def get_system_memory(device): device.dialogue_start() message = Message(MessageConst.REQUEST_GET_AVAIL_SPACE, []) response = send_message(device, message) assert response.command() == MessageConst.RESPONSE_GET_AVAIL_SPACE result = { 'free_rom': response.argument(1, 4), 'free_ram': response.argument(5, 2) * 256 } device.dialogue_end() return result
def create_file(device, filename, password, data, applet_id): """ Create a new file. The sequence for creating a new file is a little counter intuitive, starting with the file attributes: --> REQUEST_SET_FILE_ATTRIBUTES ; set up the attributes (see rawWriteAttributes()) <-- RESPONSE_SET_FILE_ATTRIBUTES --> REQUEST_BLOCK_WRITE --> Attribute data <-- RESPONSE_BLOCK_WRITE_DONE --> REQUEST_COMMIT ; create the file <-- RESPONSE_COMMIT --> REQUEST_WRITE_RAW_FILE ; the following sequence is as for writing an existing file (see rawWriteFile()) <-- RESPONSE_WRITE_FILE --> REQUEST_BLOCK_WRITE --> File data <-- RESPONSE_BLOCK_WRITE_DONE --> REQUEST_CONFIRM_WRITE_FILE <-- RESPONSE_CONFIRM_WRITE_FILE :param device: :param filename: :param password: :param data: The buffer to write. :param applet_id: :return: The new FileAttributes. """ usage = get_applet_resource_usage(device, applet_id) system_memory = get_system_memory(device) if isinstance(data, str): data = data.encode('utf-8') size = len(data) if size + 1024 > system_memory['free_ram']: # REVIEW: arbitrarily choosing to keep at least 1k unused on the device raise NeotoolsError('The device does not have enough RAM') device.dialogue_start() file_index = usage['file_count'] + 1 # The space is unbound, it is not index attrs = FileAttributes(file_index, filename, 0, password, size, size, 0) raw_set_file_attributes(device, attrs, applet_id, file_index) # Sending this message appears to bind the attributes to a new file - # not sending it will still result in a new file, but the attributes will not be correct. message = Message(MessageConst.REQUEST_COMMIT, [(file_index, 4, 1), (applet_id, 5, 2)]) response = send_message(device, message, MessageConst.RESPONSE_COMMIT) raw_write_file(device, data, applet_id, file_index, True) device.dialogue_end()
def get_applet_resource_usage(device, applet_id): device.dialogue_start() message = Message(MessageConst.REQUEST_GET_USED_SPACE, [(0x00000001, 1, 4), (applet_id, 5, 2)]) response = send_message(device, message, MessageConst.RESPONSE_GET_USED_SPACE) result = { 'ram': response.argument(1, 4), 'file_count': response.argument(5, 2) } device.dialogue_end() return result
def clear_file(device, applet_id, file_index): attrs = get_file_attributes(device, applet_id, file_index) if attrs is None: return None attrs.alloc_size = attrs.min_size = 0 device.dialogue_start() raw_set_file_attributes(device, attrs, applet_id, file_index) message = Message(MessageConst.REQUEST_COMMIT, [(file_index, 4, 1), (applet_id, 5, 2)]) send_message(device, message, MessageConst.RESPONSE_COMMIT) raw_write_file(device, b'', applet_id, file_index, True) device.dialogue_end()
def raw_set_file_attributes(device, attrs, applet_id, file_index): """ OUT: 0x1d ASMESSAGE_REQUEST_SET_FILE_ATTRIBUTES IN: 0x5b ASMESSAGE_RESPONSE_SET_FILE_ATTRIBUTES OUT: 0x02 ASMESSAGE_REQUEST_BLOCK_WRITE IN: 0x42 ASMESSAGE_RESPONSE_BLOCK_WRITE OUT: data IN: 0x43 ASMESSAGE_RESPONSE_BLOCK_WRITE_DONE """ assert file_index < 256 logger.debug('Setting file attributes applet_id=%s file_index=%s attrs=%s', applet_id, file_index, attrs) message = Message(MessageConst.REQUEST_SET_FILE_ATTRIBUTES, [(file_index, 1, 4), (applet_id, 5, 2)]) send_message(device, message, MessageConst.RESPONSE_SET_FILE_ATTRIBUTES) write_extended_data(device, attrs.to_raw())
def raw_read_file(device, applet_id, file_attrs, raw): """ Transfer sequence: OUT: 0x12|0x1c ASMESSAGE_REQUEST_READ_FILE | ASMESSAGE_REQUEST_READ_RAW_FILE IN: 0x53 ASMESSAGE_RESPONSE_READ_FILE [block read sequence] """ size = file_attrs.alloc_size index = file_attrs.file_index logger.info('Requesting to read a file at applet_id=%s, file_index=%s', applet_id, index) command = MessageConst.REQUEST_READ_RAW_FILE if raw else MessageConst.REQUEST_READ_FILE message = Message(command, [(size, 1, 3), (index, 4, 1), (applet_id, 5, 2)]) send_message(device, message) return read_extended_data(device, size)
def get_settings(device, applet_id, flags): device.dialogue_start() logger.info('Requesting settings for applet_id=%s, flags=%s', applet_id, flags) message = Message(MessageConst.REQUEST_GET_SETTINGS, [(flags, 1, 4), (applet_id, 5, 2)]) response = send_message(device, message, MessageConst.RESPONSE_GET_SETTINGS) response_size = response.argument(1, 4) expected_checksum = response.argument(5, 2) logger.info('Retrieving settings data') result = device.read(response_size) assert calculate_data_checksum(result) == expected_checksum device.dialogue_end() settings_list = AppletSettingsItem.list_from_raw(result) return AppletSettings(settings_list)
def get_file_attributes(device, applet_id, index): logger.info('Getting file attributes applet_id=%s index=%s', applet_id, index) device.dialogue_start() message = Message(MessageConst.REQUEST_GET_FILE_ATTRIBUTES, [(index, 4, 1), (applet_id, 5, 2)]) response = send_message(device, message) if response.command() == MessageConst.ERROR_PARAMETER: # Entry not found. This probably just means that the iteration has exceeded the number of files available. return None assert_success(response, MessageConst.RESPONSE_GET_FILE_ATTRIBUTES) length = response.argument(1, 4) checksum = response.argument(5, 2) assert length == FILE_ATTRIBUTES_FORMAT['size'] buf = device.read(FILE_ATTRIBUTES_FORMAT['size']) assert checksum == calculate_data_checksum(buf) device.dialogue_end() return FileAttributes.from_raw(index, buf)
def write_extended_data(device, buf): remaining = len(buf) offset = 0 while remaining > 0: blocksize = min(1024, remaining) block = buf[offset:offset + blocksize] checksum = calculate_data_checksum(block) message = Message(MessageConst.REQUEST_BLOCK_WRITE, [(blocksize, 1, 4), (checksum, 5, 2)]) response = send_message(device, message, MessageConst.RESPONSE_BLOCK_WRITE) device.write(block) response = receive_message(device, MessageConst.RESPONSE_BLOCK_WRITE_DONE) offset = offset + blocksize remaining = remaining - blocksize
def raw_read_applet_headers(device, index): header_size = APPLET_HEADER_FORMAT['size'] logger.info('Requesting to read list of applets with index=%s', index) message = Message(MessageConst.REQUEST_LIST_APPLETS, [(index, 1, 4), (LIST_APPLETS_REQUEST_COUNT, 5, 2)]) response = send_message(device, message) size = response.argument(1, 4) expected_checksum = response.argument(5, 2) if size > LIST_APPLETS_REQUEST_COUNT * header_size: raise NeotoolsError( 'rawReadAppletHeaders: reply will return too much data!') if size == 0: return [] buf = device.read(size) if len(buf) % header_size != 0: logger.warning( 'rawReadAppletHeaders: read returned a partial header (expected header size %s, bytes read %s', header_size, len(buf)) if calculate_data_checksum(buf) != expected_checksum: raise NeotoolsError('rawReadAppletHeaders: data checksum error') return buf