def test_qmd5_hash(self): """ Test MD5 hash from salt+data pass (Qt) """ # WHEN: Given a known salt+data hash_ = qmd5_hash(salt=salt.encode('utf-8'), data=pin.encode('utf-8')) # THEN: Validate return has is same self.assertEquals(hash_, test_hash, 'Qt-MD5 should have returned a good hash')
def test_qmd5_hash_bad(self): """ Test MD5 hash from salt+hash fail (Qt) """ # WHEN: Given a different salt+hash hash_ = qmd5_hash(salt=pin.encode('utf-8'), data=salt.encode('utf-8')) # THEN: return data is different self.assertNotEquals(hash_, test_hash, 'Qt-MD5 should have returned a bad hash')
def test_qmd5_hash_bad(self): """ Test MD5 hash from salt+hash fail (Qt) """ # WHEN: Given a different salt+hash hash_ = qmd5_hash(salt=pin.encode('utf-8'), data=salt.encode('utf-8')) # THEN: return data is different assert hash_ is not test_hash, 'Qt-MD5 should have returned a bad hash'
def test_qmd5_hash(self): """ Test MD5 hash from salt+data pass (Qt) """ # WHEN: Given a known salt+data hash_ = qmd5_hash(salt=salt.encode('utf-8'), data=pin.encode('utf-8')) # THEN: Validate return has is same assert hash_ == test_hash, 'Qt-MD5 should have returned a good hash'
def test_qmd5_hash_bad(self): """ Test MD5 hash from salt+hash fail (Qt) """ # WHEN: Given a different salt+hash hash_ = qmd5_hash(salt=pin.encode('ascii'), data=salt.encode('ascii')) # THEN: return data is different self.assertNotEquals(hash_.decode('ascii'), test_hash, 'Qt-MD5 should have returned a bad hash')
def test_qmd5_hash(self): """ Test MD5 hash from salt+data pass (Qt) """ # WHEN: Given a known salt+data hash_ = qmd5_hash(salt=salt.encode('ascii'), data=pin.encode('ascii')) # THEN: Validate return has is same self.assertEquals(hash_.decode('ascii'), test_hash, 'Qt-MD5 should have returned a good hash')
def check_login(self, data=None): """ Processes the initial connection and authentication (if needed). Starts poll timer if connection is established. NOTE: Qt md5 hash function doesn't work with projector authentication. Use the python md5 hash function. :param data: Optional data if called from another routine """ log.debug('({ip}) check_login(data="{data}")'.format(ip=self.ip, data=data)) if data is None: # Reconnected setup? if not self.waitForReadyRead(2000): # Possible timeout issue log.error('({ip}) Socket timeout waiting for login'.format(ip=self.ip)) self.change_status(E_SOCKET_TIMEOUT) return read = self.readLine(self.max_size) dontcare = self.readLine(self.max_size) # Clean out the trailing \r\n if read is None: log.warning('({ip}) read is None - socket error?'.format(ip=self.ip)) return elif len(read) < 8: log.warning('({ip}) Not enough data read)'.format(ip=self.ip)) return data = decode(read, 'ascii') # Possibility of extraneous data on input when reading. # Clean out extraneous characters in buffer. dontcare = self.readLine(self.max_size) log.debug('({ip}) check_login() read "{data}"'.format(ip=self.ip, data=data.strip())) # At this point, we should only have the initial login prompt with # possible authentication # PJLink initial login will be: # 'PJLink 0' - Unauthenticated login - no extra steps required. # 'PJLink 1 XXXXXX' Authenticated login - extra processing required. if not data.upper().startswith('PJLINK'): # Invalid response return self.disconnect_from_host() if '=' in data: # Processing a login reply data_check = data.strip().split('=') else: # Process initial connection data_check = data.strip().split(' ') log.debug('({ip}) data_check="{data}"'.format(ip=self.ip, data=data_check)) # Check for projector reporting an error if data_check[1].upper() == 'ERRA': # Authentication error self.disconnect_from_host() self.change_status(E_AUTHENTICATION) log.debug('({ip}) emitting projectorAuthentication() signal'.format(ip=self.name)) return elif data_check[1] == '0' and self.pin is not None: # Pin set and no authentication needed log.warning('({ip}) Regular connection but PIN set'.format(ip=self.name)) self.disconnect_from_host() self.change_status(E_AUTHENTICATION) log.debug('({ip}) Emitting projectorNoAuthentication() signal'.format(ip=self.name)) self.projectorNoAuthentication.emit(self.name) return elif data_check[1] == '1': # Authenticated login with salt if self.pin is None: log.warning('({ip}) Authenticated connection but no pin set'.format(ip=self.name)) self.disconnect_from_host() self.change_status(E_AUTHENTICATION) log.debug('({ip}) Emitting projectorAuthentication() signal'.format(ip=self.name)) self.projectorAuthentication.emit(self.name) return else: log.debug('({ip}) Setting hash with salt="{data}"'.format(ip=self.ip, data=data_check[2])) log.debug('({ip}) pin="{data}"'.format(ip=self.ip, data=self.pin)) data_hash = str(qmd5_hash(salt=data_check[2].encode('utf-8'), data=self.pin.encode('utf-8')), encoding='ascii') else: data_hash = None # We're connected at this point, so go ahead and setup regular I/O self.readyRead.connect(self.get_data) self.projectorReceivedData.connect(self._send_command) # Initial data we should know about self.send_command(cmd='CLSS', salt=data_hash) self.waitForReadyRead() if (not self.no_poll) and (self.state() == self.ConnectedState): log.debug('({ip}) Starting timer'.format(ip=self.ip)) self.timer.setInterval(2000) # Set 2 seconds for initial information self.timer.start()
def get_data(self, buff, *args, **kwargs): """ Process received data :param buff: Data to process. """ log.debug('({ip}) get_data(buffer="{buff}"'.format(ip=self.entry.name, buff=buff)) ignore_class = 'ignore_class' in kwargs # NOTE: Class2 has changed to some values being UTF-8 data_in = decode(buff, 'utf-8') if isinstance(buff, bytes) else buff data = data_in.strip() self.receive_data_signal() # Initial packet checks if len(data) < 7: self._trash_buffer(msg='get_data(): Invalid packet - length') return elif len(data) > self.max_size: self._trash_buffer(msg='get_data(): Invalid packet - too long ({length} bytes)'.format(length=len(data))) return elif not data.startswith(PJLINK_PREFIX): self._trash_buffer(msg='get_data(): Invalid packet - PJLink prefix missing') return elif data[6] != '=' and data[8] != '=': # data[6] = standard command packet # data[8] = initial PJLink connection (after mangling) self._trash_buffer(msg='get_data(): Invalid reply - Does not have "="') return log.debug('({ip}) get_data(): Checking new data "{data}"'.format(ip=self.entry.name, data=data)) header, data = data.split('=') log.debug('({ip}) get_data() header="{header}" data="{data}"'.format(ip=self.entry.name, header=header, data=data)) # At this point, the header should contain: # "PVCCCC" # Where: # P = PJLINK_PREFIX # V = PJLink class or version # C = PJLink command version, cmd = header[1], header[2:].upper() log.debug('({ip}) get_data() version="{version}" cmd="{cmd}" data="{data}"'.format(ip=self.entry.name, version=version, cmd=cmd, data=data)) if cmd not in PJLINK_VALID_CMD: self._trash_buffer('get_data(): Invalid packet - unknown command "{data}"'.format(data=cmd)) return elif version not in PJLINK_VALID_CMD[cmd]['version']: self._trash_buffer(msg='get_data() Command reply version does not match a valid command version') return elif int(self.pjlink_class) < int(version): if not ignore_class: log.warning('({ip}) get_data(): Projector returned class reply higher ' 'than projector stated class'.format(ip=self.entry.name)) return chk = process_command(self, cmd, data) if chk is None: # Command processed normally and not initial connection, so skip other checks return # PJLink initial connection checks elif chk == S_DATA_OK: # Previous command returned OK log.debug('({ip}) OK returned - resending command'.format(ip=self.entry.name)) self.send_command(cmd=cmd, priority=True) elif chk == S_CONNECT: # Normal connection log.debug('({ip}) Connecting normal'.format(ip=self.entry.name)) self.change_status(S_CONNECTED) self.send_command(cmd='CLSS', priority=True) self.readyRead.connect(self.get_socket) elif chk == S_AUTHENTICATE: # Connection with pin log.debug('({ip}) Connecting with pin'.format(ip=self.entry.name)) data_hash = str(qmd5_hash(salt=chk[1].encode('utf-8'), data=self.pin.encode('utf-8')), encoding='ascii') self.change_status(S_CONNECTED) self.readyRead.connect(self.get_socket) self.send_command(cmd='CLSS', salt=data_hash, priority=True) elif chk == E_AUTHENTICATION: # Projector did not like our pin log.warning('({ip}) Failed authentication - disconnecting'.format(ip=self.entry.name)) self.disconnect_from_host() self.projectorAuthentication.emit(self.entry.name) self.change_status(status=E_AUTHENTICATION) return