class MyProtocolCommModule(AbstractCommModule): def __init__(self, sim_env, ecu_id): ''' Constructor Input: ecu_id string id of the corresponding AbstractECU sim_env simpy.Environment environment of this component Output: - ''' AbstractCommModule.__init__(self, sim_env) # local parameters self._ecu_id = ecu_id self._jitter_in = 1 self.monitor_list = RefList() # initialize self._init_layers(self.sim_env, self.MessageClass) # add tags self._tags = ["AUTH_SEND_TIME_BEFORE_ENCRYPTION", "AUTH_SEND_TIME_AFTER_ENCRYPTION", "AUTH_RECEIVE_TIME_BEFORE_DECRYPTION", "AUTH_RECEIVE_TIME_AFTER_DECRYPTION"] # NEW: Add key pair - public and private assymetric_encryption_algorithm = AsymAuthMechEnum.RSA assymetric_encryption_key_length = AuKeyLengthEnum.bit_512 assymetric_encryption_option = 65537 self.priv_key, self.pub_key = encryption_tools.asy_get_key_pair(assymetric_encryption_algorithm, assymetric_encryption_key_length, assymetric_encryption_option) PublicKeyManager().add_key(self._ecu_id, self.pub_key) # make public key available to everybody self.first_message = False def receive_msg(self): while True: # receive from lower layer [message_id, message_data] = yield self.sim_env.process(self.transp_lay.receive_msg()) # receiver information print("\n\nRECEIVER\nTime: "+str(self.sim_env.now)+"--Communication Layer: \nI am ECU " + self._ecu_id + "\nReceived message:\n - ID: " + str(message_id)) # Assume it takes 0.5 seconds to e.g. decrypt this message uid = uuid.uuid4() # BEFORE PROCESSING print("\nECU "+ str(self._ecu_id) +"Time before message received: "+ str(self.sim_env.now)) G().mon(self.monitor_list, MonitorInput([], "AUTH_RECEIVE_TIME_BEFORE_DECRYPTION", self._ecu_id, self.sim_env.now, 123, message_id, message_data.get(), message_data.padded_size, 432, uid)) ''' Perform asymmetric decryption of the incoming message using its public key, e.g. lasting 0.5 seconds''' # get the public key of the sender senders_public_key = PublicKeyManager().get_key(message_data.sender_id) # use this key for decryption - also checks if this key is still valid received_cipher_message = message_data.get() clear_message = encryption_tools.asy_decrypt(received_cipher_message, senders_public_key, self.sim_env.now) [received_timestamp, received_hashed_message, received_message] = clear_message yield self.sim_env.timeout(0.5) ''' Perform symmetric decryption, e.g. takes 0.02 seconds''' [received_symmetric_key, received_symmetrically_encrypted_message] = received_message received_clear_message = encryption_tools.sym_decrypt(received_symmetrically_encrypted_message, received_symmetric_key) yield self.sim_env.timeout(0.02) ''' Verify received hash of the message, e.g. takes 0.01 second ''' hashed_message = HashedMessage(str(received_message), HashMechEnum.MD5) is_same_hash = hashed_message.same_hash(received_hashed_message) yield self.sim_env.timeout(0.01) # AFTER PROCESSING print("\nECU "+ str(self._ecu_id) +"Time after message received: "+ str(self.sim_env.now)) G().mon(self.monitor_list, MonitorInput([], "AUTH_RECEIVE_TIME_AFTER_DECRYPTION", self._ecu_id, self.sim_env.now, 123, message_id, message_data.get(), message_data.padded_size, 432, uid)) # push to higher layer return [message_id, received_clear_message] def send_msg(self, sender_id, message_id, message): # Sender information print("\n\nSENDER - \nTime: "+str(self.sim_env.now)+"--Communication Layer: \nI am ECU " + sender_id + "\nSending message:\n - ID: " + str(message_id)+"\n - Content: " + message.get()) # Message to be send print("\nSize of the message we want to send: "+ str(message.padded_size)) print("\nContent of the message: "+ str(message.get())) # Assume it takes 0.2 seconds to e.g. encrypt this message uid = uuid.uuid4() # BEFORE PROCESSING print("\nECU "+ str(self._ecu_id) +"Time before message sent: "+ str(self.sim_env.now)) G().mon(self.monitor_list, MonitorInput([], "AUTH_SEND_TIME_BEFORE_ENCRYPTION", self._ecu_id, self.sim_env.now, 123, message_id, message.get(), message.padded_size, 432, uid)) ''' Perform symmetric encryption (and key generation): send the message encrypted with a created key which e.g. takes 0.01 second''' algorithm = SymAuthMechEnum.AES key_length = AuKeyLengthEnum.bit_128 algorithm_mode = SymAuthMechEnum.CBC sym_key = encryption_tools.sym_get_key(algorithm, key_length, algorithm_mode) # encrypt the message with the symmetric key we just created clear_message = message.get() cipher = encryption_tools.sym_encrypt(clear_message, sym_key) sym_cipher_message = [sym_key, cipher] yield self.sim_env.timeout(0.01) ''' Hash the message we want to send and send the hash with the message, e.g. takes 0.01 second ''' hashed_message = HashedMessage(str(sym_cipher_message), HashMechEnum.MD5) yield self.sim_env.timeout(0.01) ''' Perform asymmetric encryption: Encrypt my private key which takes e.g. 0.2 seconds''' timestamp = self.sim_env.now encrypted_size = 50 # byte - usualy calculated from the size of the original message and the encryption algorithm clear_text_of_message = [timestamp, hashed_message, sym_cipher_message] cipher_message = encryption_tools.asy_encrypt(clear_text_of_message, self.priv_key) cipher_message.valid_till = timestamp + 5 # add optional validity (e.g. 5 seconds) wrapped_cipher_message = SegData(cipher_message, encrypted_size) yield self.sim_env.timeout(0.2) # AFTER PROCESSING G().mon(self.monitor_list, MonitorInput([], "AUTH_SEND_TIME_AFTER_ENCRYPTION", self._ecu_id, self.sim_env.now, 123, message_id, message.get(), message.padded_size, 432, uid)) print("\nECU "+ str(self._ecu_id) +"Time after message sent: "+ str(self.sim_env.now)) # Send message - here send your message with your message_id yield self.sim_env.process(self.transp_lay.send_msg(sender_id, message_id, wrapped_cipher_message)) def _init_layers(self, sim_env, MessageClass): ''' Initializes the software layers Input: sim_env simpy.Environment environment of this component MessageClass AbstractBusMessage class of the messages how they are sent on the CAN Bus Output: - ''' # create layers self.physical_lay = StdPhysicalLayer(sim_env) self.datalink_lay = StdDatalinkLayer(sim_env) self.transp_lay = SegmentTransportLayer(sim_env, MessageClass) # interconnect layers self.datalink_lay.physical_lay = self.physical_lay self.transp_lay.datalink_lay = self.datalink_lay @property def ecu_id(self): return self._ecu_id @ecu_id.setter def ecu_id(self, value): self._ecu_id = value def monitor_update(self): ''' updates the monitor connected to this ecu Input: - Output: monitor_list RefList list of MonitorInputs ''' # register Monitoring tags to track #G().register_eventline_tags(self._tags) items_1 = len(self.transp_lay.datalink_lay.controller.receive_buffer.items) items_2 = self.transp_lay.datalink_lay.transmit_buffer_size G().mon(self.monitor_list, MonitorInput(items_1, MonitorTags.BT_ECU_RECEIVE_BUFFER, self._ecu_id, self.sim_env.now)) G().mon(self.monitor_list, MonitorInput(items_2, MonitorTags.BT_ECU_TRANSMIT_BUFFER, self._ecu_id, self.sim_env.now)) self.monitor_list.clear_on_access() # on the next access the list will be cleared return self.monitor_list.get()
class MyProtocolCommModule(AbstractCommModule): def __init__(self, sim_env, ecu_id): ''' Constructor Input: ecu_id string id of the corresponding AbstractECU sim_env simpy.Environment environment of this component Output: - ''' AbstractCommModule.__init__(self, sim_env) # local parameters self._ecu_id = ecu_id self._jitter_in = 1 self.monitor_list = RefList() # initialize self._init_layers(self.sim_env, self.MessageClass) # add tags self._tags = [ "AUTH_SEND_TIME_BEFORE_ENCRYPTION", "AUTH_SEND_TIME_AFTER_ENCRYPTION", "AUTH_RECEIVE_TIME_BEFORE_DECRYPTION", "AUTH_RECEIVE_TIME_AFTER_DECRYPTION" ] def receive_msg(self): while True: # receive from lower layer [message_id, message_data ] = yield self.sim_env.process(self.transp_lay.receive_msg()) # receiver information print("\n\nRECEIVER\nTime: " + str(self.sim_env.now) + "--Communication Layer: \nI am ECU " + self._ecu_id + "\nReceived message:\n - ID: " + str(message_id) + "\n - Content: " + message_data.get()) # Assume it takes 0.5 seconds to e.g. decrypt this message uid = uuid.uuid4() # BEFORE PROCESSING print("\nECU " + str(self._ecu_id) + "Time before message received: " + str(self.sim_env.now)) G().mon( self.monitor_list, MonitorInput([], "AUTH_RECEIVE_TIME_BEFORE_DECRYPTION", self._ecu_id, self.sim_env.now, 123, message_id, message_data.get(), message_data.padded_size, 432, uid)) yield self.sim_env.timeout(0.5) # AFTER PROCESSING print("\nECU " + str(self._ecu_id) + "Time after message received: " + str(self.sim_env.now)) G().mon( self.monitor_list, MonitorInput([], "AUTH_RECEIVE_TIME_AFTER_DECRYPTION", self._ecu_id, self.sim_env.now, 123, message_id, message_data.get(), message_data.padded_size, 432, uid)) # push to higher layer return [message_id, message_data] def send_msg(self, sender_id, message_id, message): # Sender information print("\n\nSENDER - \nTime: " + str(self.sim_env.now) + "--Communication Layer: \nI am ECU " + sender_id + "\nSending message:\n - ID: " + str(message_id) + "\n - Content: " + message.get()) # Message to be send print("\nSize of the message we want to send: " + str(message.padded_size)) print("\nContent of the message: " + str(message.get())) # Assume it takes 0.2 seconds to e.g. encrypt this message uid = uuid.uuid4() # BEFORE PROCESSING print("\nECU " + str(self._ecu_id) + "Time before message sent: " + str(self.sim_env.now)) G().mon( self.monitor_list, MonitorInput([], "AUTH_SEND_TIME_BEFORE_ENCRYPTION", self._ecu_id, self.sim_env.now, 123, message_id, message.get(), message.padded_size, 432, uid)) yield self.sim_env.timeout(0.2) # AFTER PROCESSING G().mon( self.monitor_list, MonitorInput([], "AUTH_SEND_TIME_AFTER_ENCRYPTION", self._ecu_id, self.sim_env.now, 123, message_id, message.get(), message.padded_size, 432, uid)) print("\nECU " + str(self._ecu_id) + "Time after message sent: " + str(self.sim_env.now)) # Send message - here send your message with your message_id yield self.sim_env.process( self.transp_lay.send_msg(sender_id, message_id, message)) def _init_layers(self, sim_env, MessageClass): ''' Initializes the software layers Input: sim_env simpy.Environment environment of this component MessageClass AbstractBusMessage class of the messages how they are sent on the CAN Bus Output: - ''' # create layers self.physical_lay = StdPhysicalLayer(sim_env) self.datalink_lay = StdDatalinkLayer(sim_env) self.transp_lay = SegmentTransportLayer(sim_env, MessageClass) # interconnect layers self.datalink_lay.physical_lay = self.physical_lay self.transp_lay.datalink_lay = self.datalink_lay @property def ecu_id(self): return self._ecu_id @ecu_id.setter def ecu_id(self, value): self._ecu_id = value def monitor_update(self): ''' updates the monitor connected to this ecu Input: - Output: monitor_list RefList list of MonitorInputs ''' # register Monitoring tags to track #G().register_eventline_tags(self._tags) items_1 = len( self.transp_lay.datalink_lay.controller.receive_buffer.items) items_2 = self.transp_lay.datalink_lay.transmit_buffer_size G().mon( self.monitor_list, MonitorInput(items_1, MonitorTags.BT_ECU_RECEIVE_BUFFER, self._ecu_id, self.sim_env.now)) G().mon( self.monitor_list, MonitorInput(items_2, MonitorTags.BT_ECU_TRANSMIT_BUFFER, self._ecu_id, self.sim_env.now)) self.monitor_list.clear_on_access( ) # on the next access the list will be cleared return self.monitor_list.get()
class RapidCANBus(AbstractCANBus): ''' This class implements a CAN Bus that actively pulls messages from the ECUs buffers ''' def __init__(self, sim_env, bus_id, data_rate, avg_ecu_dist=2): ''' Constructor Input: sim_env simpy.Environment environment in which this Bus acts bus_id string id of this Bus object data_rate float datarate of this bus avg_ecu_dist float average distance between two connected ECUs Output: - ''' AbstractCANBus.__init__(self, sim_env, bus_id, data_rate, avg_ecu_dist) # bus objects self.current_message = None # current message on the bus [sender_ecu, message] self.set_settings() self.monitor_list = RefList() self._used_prop_times = {} self.gateways = [] self.first = True # synchronization objects self.pot_messages = [ ] # gathers all potential messages that want to be sent at a certain point in time self.sync_1 = simpy.Store( self.sim_env, capacity=1 ) # if the decision, who is allowed to sent is done this synchronizer starts the transmission self.sync_2 = simpy.Store( self.sim_env, capacity=1) # if store is empty then the channel is busy self.subscribers = 0 # number of ECUs waiting for the channel to be freed self.current_message_length_bit = 0 # project parameters self.SCB_GATHER_MSGS = time.SCB_GATHER_MSGS self.SCB_GRAB_PRIO_MSG = time.SCB_GRAB_PRIO_MSG self.SCB_PROPAGATION_DELAY = time.SCB_PROPAGATION_DELAY self.SCB_SENDING_TIME = time.SCB_SENDING_TIME self.SCB_WRITE_TO_TRANSCEIVER_BUFFER = time.SCB_WRITE_TO_TRANSCEIVER_BUFFER # ECUs Datalink layers willing to send self._willing_dll = [] def monitor_update(self): ''' returns the input for the monitor Input: - Output: monitor_list list List of MonitorInput objects ''' self.monitor_list.clear_on_access( ) # on the next access the list will be cleared return self.monitor_list.get() def release_willing(self, dll): ''' remove the ecus that are not willing to send anymore Input: dll AbstractDataLinkLayer Datalink layer of the ECU that is not willing to send anymore Output: - ''' self._willing_dll.remove(dll) def add_willing(self, dll): ''' add a Datalinklayer of an ECU that is willing to send Input: dll AbstractDataLinkLayer Datalink layer of the ECU that is willing to send Output: - ''' if dll not in self._willing_dll: self._willing_dll.append(dll) def notify_bus(self): ''' When the bus is empty it is set to a sleep mode that waits until the ecu notifies it. Using this method the ECU does so. Thus the Bus will only be active when any ecu sends. Input: - Output: - ''' if self.current_message != None: return else: self.sync_1.put(True) def process(self): ''' Constantly pull messages from all ECUs that are connected. Once the Bus is done with one message it pulls the next message from the connected ECUs. Input: - Output: - ''' stp = 0 while True: # print time t = self.sim_env.now if t > stp: # print(self.sim_env.now) stp += 0.5 # check which ECU sends current_minimum = float("inf") index = 0 for dll in self._willing_dll: val = dll.first_queue_identifier() if val < current_minimum and val != False: current_minimum = index index += 1 # no ECU wiling to send: wait for notify if current_minimum == float("inf"): yield self.sync_1.get() else: self.current_message = self._willing_dll[ current_minimum].controller.transmit_buffer.get().message self.current_message_length_bit = self.current_message.msg_length_in_bit # transmit message if self.current_message != None: # monitor start monitor_note = self._monitor_transmission_start() # write to buffer yield self.sim_env.process( self._wait_transmission_time_and_buffer()) self._reset_transmission() # monitor end self._monitor_transmission_end(monitor_note) # if ecus buffer is now empty remove from willing if (len(self._willing_dll[current_minimum].controller. transmit_buffer.queue) == 0): self.release_willing(self._willing_dll[current_minimum]) def set_settings(self): ''' sets the initial setting association between the settings variables and the actual parameter Input: - Output: - ''' self.settings = {} # parameter self.settings['t_gather_msg'] = 'SCB_GATHER_MSGS' self.settings['t_grab_prio_msg'] = 'SCB_GRAB_PRIO_MSG' self.settings['t_propagation_delay'] = 'SCB_PROPAGATION_DELAY' self.settings['t_sending_time'] = 'SCB_SENDING_TIME' self.settings[ 't_write_to_transceiver_buffer'] = 'SCB_WRITE_TO_TRANSCEIVER_BUFFER' def wait_until_free(self): ''' when the channel is busy some ECUs can start this method in a simpy process. Once the channel is free this process ends and the next ECU can start it's transmission technically: count number of waiting processes and notifies them all once the channel is free Input: - Output - ''' # add subscriber self.subscribers += 1 yield self.sync_2.get() # release all receivers while self.subscribers > 1: self.sync_2.put(True) self.subscribers -= 1 self.subscribers = 0 def _extract_transmission_times(self): ''' calculates the time the current transmission takes Input: - Output: t_propagation: float time it takes to propagate the message t_sending float time it takes to send the message ''' t_propagation = time.call( self.SCB_PROPAGATION_DELAY, self.avg_dist_between_ecus ) # either constant or calculated depending on config t_sending = time.call( self.SCB_SENDING_TIME, self.current_message_length_bit, proj.BUS_ECU_DATARATE ) # either constant or calculated depending on config return t_propagation, t_sending def _gateway_sends(self, ecu): ''' if gateway is the sender let it continue and reset the message state Input: ecu AbstractECU current ECU that sends the message Output: bool boolean True if the message was sent by this ECU ''' try: if ecu.ecu_id in self.current_message.gw_id: # send message back and forth send could lead to errors: need to reset gw_id list return True except: return False return False def _get_highest_priority_msg(self, message_list): ''' returns the message with the highest priority Input: message_list list list of messages Output: message object message with highest priority (lowest message id) ''' min_val = float("inf") message = None for cur_message in message_list: if min_val > cur_message.message_identifier: min_val = cur_message.message_identifier message = cur_message return message def _grab_highest_priority(self): ''' note the time it takes to select the message with the highest priority Input: - Output: - ''' if self.SCB_GRAB_PRIO_MSG != 0: G().to_t(self.sim_env, self.SCB_GRAB_PRIO_MSG, 'SCB_GRAB_PRIO_MSG', self.__class__.__name__, self) return True return False def _monitor_transmission_start(self): ''' notes the start time when this message was put on the bus Input: - Output: - ''' # extract information uid = uuid.uuid4() tag = MonitorTags.CB_PROCESSING_MESSAGE c_id = self.comp_id sender_id = self.current_message.sender_id msg_id = self.current_message.message_identifier msg_uid = self.current_message.data.unique_id data = self.current_message.data.get() # extract further information msg = self.current_message size = self.current_message_length_bit / 8 self.current_message.data.unique_id = msg_uid # send to monitor G().mon( self.monitor_list, MonitorInput(data, tag, c_id, self.sim_env.now, sender_id, msg_id, msg, size, msg_id, uid.hex)) return data, c_id, sender_id, msg_id, msg, size, uid def _monitor_transmission_end(self, mon_out): ''' notes the end time when this message was put on the bus Input: - Output: - ''' G().mon(self.monitor_list, MonitorInput(mon_out[0], MonitorTags.CB_DONE_PROCESSING_MESSAGE, \ mon_out[1], self.sim_env.now, mon_out[2], mon_out[3], \ mon_out[4], mon_out[5], -1, mon_out[6].hex)) def _push_to_receivers(self): ''' writes the current message to all ecus that are connected to this Bus Input: - Output: - ''' # get gateways if self.first: self.gateways = [ itm.ecu_id for itm in self.connected_ecus if isinstance(itm.ecu_id, UUID) ] self.first = False # send only to receivers if General( ).send_only_to_receivers and self.current_message.message_identifier not in can_registration.AUTH_MESSAGES: run_list = General().sender_receiver_map[ self.current_message.sender_id][ self.current_message.message_identifier] + self.gateways for ecu in self.connected_ecus: if ecu.ecu_id not in run_list: continue # Gateway:avoid sending to itself (loops) if self._gateway_sends(ecu): continue # ECU: avoid sending to itself if (ecu.ecu_id != self.current_message.sender_id): self.current_message.current_bus = self.comp_id ecu.ecuHW.transceiver.get(self.current_message) else: # iterate over receivers for ecu in self.connected_ecus: # Gateway:avoid sending to itself (loops) if self._gateway_sends(ecu): continue # ECU: avoid sending to itself if (ecu.ecu_id != self.current_message.sender_id): self.current_message.current_bus = self.comp_id ecu.ecuHW.transceiver.get(self.current_message) def _reset_transmission(self): ''' after one message was sent three things have to be reset the current message. The synchronizer for the selection of the next higher prioritized message to be sent and the list that gathered the selected potential messages Input: - Output: - ''' self.current_message = None # message is not on the line anymore self.sync_2.put(True) # channel is free again self.pot_messages = [] # reset def _sending_ok(self, t_propagation, t_sending): ''' checks if this message is sendable Input: t_propagation: float time it takes to propagate the message t_sending float time it takes to send the message Output: bool boolean true if the time is valid ''' try: G().to_t( self.sim_env, t_propagation + t_sending + self.SCB_WRITE_TO_TRANSCEIVER_BUFFER, 'SCB_PROPAGATION_DELAY+SCB_SENDING_TIME+SCB_WRITE_TO_TRANSCEIVER_BUFFER', self.__class__.__name__, self) if t_propagation + t_sending + self.SCB_WRITE_TO_TRANSCEIVER_BUFFER > 0: return True # logging.error("Error (skipped with time 0):t_propagation =%s, t_sending = %s, self.SCB_WRITE_TO_TRANSCEIVER_BUFFER = %s // msg_length_bit = %s" % \ # (t_propagation, t_sending, self.SCB_WRITE_TO_TRANSCEIVER_BUFFER, self.current_message_length_bit)) except: return False return False def _try_logging_transmission(self, t_propagation, t_sending): ''' notes the times that it takes to send the messages. In case an erroneous message is sent this method logs the exception Input: t_propagation: float time it takes to propagate the message t_sending float time it takes to send the message ''' try: # Log transmission L().log(300, self.sim_env.now, self.current_message.sender_id, float(self.current_message_length_bit) / 8.0, \ self.current_message_length_bit, self.comp_id, self.current_message.data.get(), t_propagation + t_sending) except: # Log traceback ECULogger().log_traceback() try: L().log(300, self.sim_env.now, self.current_message.sender_id, self.current_message.data, \ self.current_message_length_bit, self.comp_id, self.current_message.data, t_propagation + t_sending) except: pass def _wait_transmission_time_and_buffer(self): ''' this method times out for the duration of the transmission and then writes the sent messages to the receiving buffer of the ecu Input: - Output: - ''' if not self.current_message_length_bit in self._used_prop_times: # sending times t_propagation, t_sending = self._extract_transmission_times() # duration of transmission wait_time = t_propagation + t_sending + self.SCB_WRITE_TO_TRANSCEIVER_BUFFER if wait_time <= 0: wait_time = 0.000001 self._used_prop_times[self.current_message_length_bit] = wait_time else: wait_time = self._used_prop_times[self.current_message_length_bit] yield self.sim_env.timeout(wait_time) # put to connected ecus self._push_to_receivers()
class MyProtocolCommModule(AbstractCommModule): def __init__(self, sim_env, ecu_id): ''' Constructor Input: ecu_id string id of the corresponding AbstractECU sim_env simpy.Environment environment of this component Output: - ''' AbstractCommModule.__init__(self, sim_env) # local parameters self._ecu_id = ecu_id self._jitter_in = 1 self.monitor_list = RefList() # initialize self._init_layers(self.sim_env, self.MessageClass) # add tags self._tags = [ "AUTH_SEND_TIME_BEFORE_ENCRYPTION", "AUTH_SEND_TIME_AFTER_ENCRYPTION", "AUTH_RECEIVE_TIME_BEFORE_DECRYPTION", "AUTH_RECEIVE_TIME_AFTER_DECRYPTION" ] # NEW: Add key pair - public and private assymetric_encryption_algorithm = AsymAuthMechEnum.RSA assymetric_encryption_key_length = AuKeyLengthEnum.bit_512 assymetric_encryption_option = 65537 self.priv_key, self.pub_key = encryption_tools.asy_get_key_pair( assymetric_encryption_algorithm, assymetric_encryption_key_length, assymetric_encryption_option) PublicKeyManager().add_key( self._ecu_id, self.pub_key) # make public key available to everybody self.first_message = False def receive_msg(self): while True: # receive from lower layer [message_id, message_data ] = yield self.sim_env.process(self.transp_lay.receive_msg()) # receiver information print("\n\nRECEIVER\nTime: " + str(self.sim_env.now) + "--Communication Layer: \nI am ECU " + self._ecu_id + "\nReceived message:\n - ID: " + str(message_id)) # Assume it takes 0.5 seconds to e.g. decrypt this message uid = uuid.uuid4() # BEFORE PROCESSING print("\nECU " + str(self._ecu_id) + "Time before message received: " + str(self.sim_env.now)) G().mon( self.monitor_list, MonitorInput([], "AUTH_RECEIVE_TIME_BEFORE_DECRYPTION", self._ecu_id, self.sim_env.now, 123, message_id, message_data.get(), message_data.padded_size, 432, uid)) ''' Perform asymmetric decryption of the incoming message using its public key, e.g. lasting 0.5 seconds''' # get the public key of the sender senders_public_key = PublicKeyManager().get_key( message_data.sender_id) # use this key for decryption - also checks if this key is still valid received_cipher_message = message_data.get() clear_message = encryption_tools.asy_decrypt( received_cipher_message, senders_public_key, self.sim_env.now) [received_timestamp, received_hashed_message, received_message] = clear_message yield self.sim_env.timeout(0.5) ''' Perform symmetric decryption, e.g. takes 0.02 seconds''' [received_symmetric_key, received_symmetrically_encrypted_message] = received_message received_clear_message = encryption_tools.sym_decrypt( received_symmetrically_encrypted_message, received_symmetric_key) yield self.sim_env.timeout(0.02) ''' Verify received hash of the message, e.g. takes 0.01 second ''' hashed_message = HashedMessage(str(received_message), HashMechEnum.MD5) is_same_hash = hashed_message.same_hash(received_hashed_message) yield self.sim_env.timeout(0.01) # AFTER PROCESSING print("\nECU " + str(self._ecu_id) + "Time after message received: " + str(self.sim_env.now)) G().mon( self.monitor_list, MonitorInput([], "AUTH_RECEIVE_TIME_AFTER_DECRYPTION", self._ecu_id, self.sim_env.now, 123, message_id, message_data.get(), message_data.padded_size, 432, uid)) # push to higher layer return [message_id, received_clear_message] def send_msg(self, sender_id, message_id, message): # Sender information print("\n\nSENDER - \nTime: " + str(self.sim_env.now) + "--Communication Layer: \nI am ECU " + sender_id + "\nSending message:\n - ID: " + str(message_id) + "\n - Content: " + message.get()) # Message to be send print("\nSize of the message we want to send: " + str(message.padded_size)) print("\nContent of the message: " + str(message.get())) # Assume it takes 0.2 seconds to e.g. encrypt this message uid = uuid.uuid4() # BEFORE PROCESSING print("\nECU " + str(self._ecu_id) + "Time before message sent: " + str(self.sim_env.now)) G().mon( self.monitor_list, MonitorInput([], "AUTH_SEND_TIME_BEFORE_ENCRYPTION", self._ecu_id, self.sim_env.now, 123, message_id, message.get(), message.padded_size, 432, uid)) ''' Perform symmetric encryption (and key generation): send the message encrypted with a created key which e.g. takes 0.01 second''' algorithm = SymAuthMechEnum.AES key_length = AuKeyLengthEnum.bit_128 algorithm_mode = SymAuthMechEnum.CBC sym_key = encryption_tools.sym_get_key(algorithm, key_length, algorithm_mode) # encrypt the message with the symmetric key we just created clear_message = message.get() cipher = encryption_tools.sym_encrypt(clear_message, sym_key) sym_cipher_message = [sym_key, cipher] yield self.sim_env.timeout(0.01) ''' Hash the message we want to send and send the hash with the message, e.g. takes 0.01 second ''' hashed_message = HashedMessage(str(sym_cipher_message), HashMechEnum.MD5) yield self.sim_env.timeout(0.01) ''' Perform asymmetric encryption: Encrypt my private key which takes e.g. 0.2 seconds''' timestamp = self.sim_env.now encrypted_size = 50 # byte - usualy calculated from the size of the original message and the encryption algorithm clear_text_of_message = [timestamp, hashed_message, sym_cipher_message] cipher_message = encryption_tools.asy_encrypt(clear_text_of_message, self.priv_key) cipher_message.valid_till = timestamp + 5 # add optional validity (e.g. 5 seconds) wrapped_cipher_message = SegData(cipher_message, encrypted_size) yield self.sim_env.timeout(0.2) # AFTER PROCESSING G().mon( self.monitor_list, MonitorInput([], "AUTH_SEND_TIME_AFTER_ENCRYPTION", self._ecu_id, self.sim_env.now, 123, message_id, message.get(), message.padded_size, 432, uid)) print("\nECU " + str(self._ecu_id) + "Time after message sent: " + str(self.sim_env.now)) # Send message - here send your message with your message_id yield self.sim_env.process( self.transp_lay.send_msg(sender_id, message_id, wrapped_cipher_message)) def _init_layers(self, sim_env, MessageClass): ''' Initializes the software layers Input: sim_env simpy.Environment environment of this component MessageClass AbstractBusMessage class of the messages how they are sent on the CAN Bus Output: - ''' # create layers self.physical_lay = StdPhysicalLayer(sim_env) self.datalink_lay = StdDatalinkLayer(sim_env) self.transp_lay = SegmentTransportLayer(sim_env, MessageClass) # interconnect layers self.datalink_lay.physical_lay = self.physical_lay self.transp_lay.datalink_lay = self.datalink_lay @property def ecu_id(self): return self._ecu_id @ecu_id.setter def ecu_id(self, value): self._ecu_id = value def monitor_update(self): ''' updates the monitor connected to this ecu Input: - Output: monitor_list RefList list of MonitorInputs ''' # register Monitoring tags to track #G().register_eventline_tags(self._tags) items_1 = len( self.transp_lay.datalink_lay.controller.receive_buffer.items) items_2 = self.transp_lay.datalink_lay.transmit_buffer_size G().mon( self.monitor_list, MonitorInput(items_1, MonitorTags.BT_ECU_RECEIVE_BUFFER, self._ecu_id, self.sim_env.now)) G().mon( self.monitor_list, MonitorInput(items_2, MonitorTags.BT_ECU_TRANSMIT_BUFFER, self._ecu_id, self.sim_env.now)) self.monitor_list.clear_on_access( ) # on the next access the list will be cleared return self.monitor_list.get()
class MyProtocolCommModule(AbstractCommModule): def __init__(self, sim_env, ecu_id): ''' Constructor Input: ecu_id string id of the corresponding AbstractECU sim_env simpy.Environment environment of this component Output: - ''' AbstractCommModule.__init__(self, sim_env) # local parameters self._ecu_id = ecu_id self._jitter_in = 1 self.monitor_list = RefList() # initialize self._init_layers(self.sim_env, self.MessageClass) def receive_msg(self): ''' receives messages via the tesla mechanism after they where authenticated. Then messages that were authenticated are buffered in _messages_to_return and returned to higher layers sequentially on each call of this method Input: - Output: message_data object/string/... message that was received message_id integer id of received message NOTE: Accessing sender of a message -> USE: message_data.sender_id Accessing message_id of the received message -> USE: message_id Accessing message content -> USE: message_data.get() message_data is of type SegData ''' while True: # receive from lower layer [message_id, message_data ] = yield self.sim_env.process(self.transp_lay.receive_msg()) # do something here e.g. if message_id = Handshake request received here, # -> send something as response e.g. # PLACE CODE HERE print("\n\nRECEIVER\nTime: " + str(self.sim_env.now) + "--Communication Layer: \nI am ECU " + self._ecu_id + "\nReceived message:\n - ID: " + str(message_id) + "\n - Content: " + message_data.get()) # push to higher layer return [message_id, message_data] def send_msg(self, sender_id, message_id, message): ''' this method receives the message from the application layer and transmits it further to the transport layer or if required handles the security communication Input: sender_id string ID of the ECU that wants to send the message message_id integer identifier of the message that is to be sent message object message that will be sent Output: - ''' # do something with received message # PLACE CODE HERE print("\n\nSENDER - \nTime: " + str(self.sim_env.now) + "--Communication Layer: \nI am ECU " + sender_id + "\nSending message:\n - ID: " + str(message_id) + "\n - Content: " + message.get()) # Send message - here send your message with your message_id yield self.sim_env.process( self.transp_lay.send_msg(sender_id, message_id, message)) def _init_layers(self, sim_env, MessageClass): ''' Initializes the software layers Input: sim_env simpy.Environment environment of this component MessageClass AbstractBusMessage class of the messages how they are sent on the CAN Bus Output: - ''' # create layers self.physical_lay = StdPhysicalLayer(sim_env) self.datalink_lay = StdDatalinkLayer(sim_env) self.transp_lay = SegmentTransportLayer(sim_env, MessageClass) # interconnect layers self.datalink_lay.physical_lay = self.physical_lay self.transp_lay.datalink_lay = self.datalink_lay @property def ecu_id(self): return self._ecu_id @ecu_id.setter def ecu_id(self, value): self._ecu_id = value def monitor_update(self): ''' updates the monitor connected to this ecu Input: - Output: monitor_list RefList list of MonitorInputs ''' items_1 = len( self.transp_lay.datalink_lay.controller.receive_buffer.items) items_2 = self.transp_lay.datalink_lay.transmit_buffer_size G().mon( self.monitor_list, MonitorInput(items_1, MonitorTags.BT_ECU_RECEIVE_BUFFER, self._ecu_id, self.sim_env.now)) G().mon( self.monitor_list, MonitorInput(items_2, MonitorTags.BT_ECU_TRANSMIT_BUFFER, self._ecu_id, self.sim_env.now)) self.monitor_list.clear_on_access( ) # on the next access the list will be cleared return self.monitor_list.get()
class MyProtocolCommModule(AbstractCommModule): def __init__(self, sim_env, ecu_id): ''' Constructor Input: ecu_id string id of the corresponding AbstractECU sim_env simpy.Environment environment of this component Output: - ''' AbstractCommModule.__init__(self, sim_env) # local parameters self._ecu_id = ecu_id self._jitter_in = 1 self.monitor_list = RefList() # initialize self._init_layers(self.sim_env, self.MessageClass) # add tags self._tags = ["AUTH_SEND_TIME_BEFORE_ENCRYPTION", "AUTH_SEND_TIME_AFTER_ENCRYPTION", "AUTH_RECEIVE_TIME_BEFORE_DECRYPTION", "AUTH_RECEIVE_TIME_AFTER_DECRYPTION"] def receive_msg(self): while True: # receive from lower layer [message_id, message_data] = yield self.sim_env.process(self.transp_lay.receive_msg()) # receiver information print("\n\nRECEIVER\nTime: "+str(self.sim_env.now)+"--Communication Layer: \nI am ECU " + self._ecu_id + "\nReceived message:\n - ID: " + str(message_id) +"\n - Content: " + message_data.get()) # Assume it takes 0.5 seconds to e.g. decrypt this message uid = uuid.uuid4() # BEFORE PROCESSING print("\nECU "+ str(self._ecu_id) +"Time before message received: "+ str(self.sim_env.now)) G().mon(self.monitor_list, MonitorInput([], "AUTH_RECEIVE_TIME_BEFORE_DECRYPTION", self._ecu_id, self.sim_env.now, 123, message_id, message_data.get(), message_data.padded_size, 432, uid)) yield self.sim_env.timeout(0.5) # AFTER PROCESSING print("\nECU "+ str(self._ecu_id) +"Time after message received: "+ str(self.sim_env.now)) G().mon(self.monitor_list, MonitorInput([], "AUTH_RECEIVE_TIME_AFTER_DECRYPTION", self._ecu_id, self.sim_env.now, 123, message_id, message_data.get(), message_data.padded_size, 432, uid)) # push to higher layer return [message_id, message_data] def send_msg(self, sender_id, message_id, message): # Sender information print("\n\nSENDER - \nTime: "+str(self.sim_env.now)+"--Communication Layer: \nI am ECU " + sender_id + "\nSending message:\n - ID: " + str(message_id)+"\n - Content: " + message.get()) # Message to be send print("\nSize of the message we want to send: "+ str(message.padded_size)) print("\nContent of the message: "+ str(message.get())) # Assume it takes 0.2 seconds to e.g. encrypt this message uid = uuid.uuid4() # BEFORE PROCESSING print("\nECU "+ str(self._ecu_id) +"Time before message sent: "+ str(self.sim_env.now)) G().mon(self.monitor_list, MonitorInput([], "AUTH_SEND_TIME_BEFORE_ENCRYPTION", self._ecu_id, self.sim_env.now, 123, message_id, message.get(), message.padded_size, 432, uid)) yield self.sim_env.timeout(0.2) # AFTER PROCESSING G().mon(self.monitor_list, MonitorInput([], "AUTH_SEND_TIME_AFTER_ENCRYPTION", self._ecu_id, self.sim_env.now, 123, message_id, message.get(), message.padded_size, 432, uid)) print("\nECU "+ str(self._ecu_id) +"Time after message sent: "+ str(self.sim_env.now)) # Send message - here send your message with your message_id yield self.sim_env.process(self.transp_lay.send_msg(sender_id, message_id, message)) def _init_layers(self, sim_env, MessageClass): ''' Initializes the software layers Input: sim_env simpy.Environment environment of this component MessageClass AbstractBusMessage class of the messages how they are sent on the CAN Bus Output: - ''' # create layers self.physical_lay = StdPhysicalLayer(sim_env) self.datalink_lay = StdDatalinkLayer(sim_env) self.transp_lay = SegmentTransportLayer(sim_env, MessageClass) # interconnect layers self.datalink_lay.physical_lay = self.physical_lay self.transp_lay.datalink_lay = self.datalink_lay @property def ecu_id(self): return self._ecu_id @ecu_id.setter def ecu_id(self, value): self._ecu_id = value def monitor_update(self): ''' updates the monitor connected to this ecu Input: - Output: monitor_list RefList list of MonitorInputs ''' # register Monitoring tags to track #G().register_eventline_tags(self._tags) items_1 = len(self.transp_lay.datalink_lay.controller.receive_buffer.items) items_2 = self.transp_lay.datalink_lay.transmit_buffer_size G().mon(self.monitor_list, MonitorInput(items_1, MonitorTags.BT_ECU_RECEIVE_BUFFER, self._ecu_id, self.sim_env.now)) G().mon(self.monitor_list, MonitorInput(items_2, MonitorTags.BT_ECU_TRANSMIT_BUFFER, self._ecu_id, self.sim_env.now)) self.monitor_list.clear_on_access() # on the next access the list will be cleared return self.monitor_list.get()
class MyProtocolCommModule(AbstractCommModule): def __init__(self, sim_env, ecu_id): ''' Constructor Input: ecu_id string id of the corresponding AbstractECU sim_env simpy.Environment environment of this component Output: - ''' AbstractCommModule.__init__(self, sim_env) # local parameters self._ecu_id = ecu_id self._jitter_in = 1 self.monitor_list = RefList() self._have_session_key = False self._session_key = None # initialize self._init_layers(self.sim_env, self.MessageClass) # add tags self._tags = ["AUTH_SEND_TIME_BEFORE_ENCRYPTION", "AUTH_SEND_TIME_AFTER_ENCRYPTION", "AUTH_RECEIVE_TIME_BEFORE_DECRYPTION", "AUTH_RECEIVE_TIME_AFTER_DECRYPTION"] # NEW: Add key pair - public and private assymetric_encryption_algorithm = AsymAuthMechEnum.RSA assymetric_encryption_key_length = AuKeyLengthEnum.bit_512 assymetric_encryption_option = 65537 self.priv_key, self.pub_key = encryption_tools.asy_get_key_pair(assymetric_encryption_algorithm, assymetric_encryption_key_length, assymetric_encryption_option) PublicKeyManager().add_key(self._ecu_id, self.pub_key) # make public key available to everybody # Create a certificate from root L3 along L3 L31 L311 [certificate, root_certificates_to_verify_this_certificate, self.certificate_private_key] = GeneralSpecPreset().certificate_manager.generate_valid_ecu_cert(self._ecu_id, CAEnum.CA_L311, 0, float('inf')) self.my_certificate = certificate self.my_root_certificates = root_certificates_to_verify_this_certificate # verify certificate from root L311 with right root certificates #certificate_valid = encryption_tools.certificate_trustworthy(certificate, root_certificates_to_verify_this_certificate, self.sim_env.now) def _get_time_for_session_decryption(self, encrypted_message): """ This method computes the decryption time (as an example) based on the input message size :return: """ # decrypted size size_to_decrypt = 24 # decryption time algorithm = EnumTrafor().to_value(SymAuthMechEnum.AES) key_len = EnumTrafor().to_value(AuKeyLengthEnum.bit_128) library_tag = "Crypto_Lib_HW" # available tags: ['CyaSSL', 'Crypto_Lib_HW', 'Crypto_Lib_SW'] db_val = TimingDBMap().lookup_interpol(lib=library_tag, mode='DECRYPTION', \ keylen=key_len, alg=algorithm, data_size=size_to_decrypt, description='some_description') decrypted_size = encrypted_message.msg_unencrpyted._size # this is stored in the encrypted message decryption_time = db_val return decryption_time, decrypted_size def _get_time_for_session_encryption(self, encrypted_message): ''' This method computes the encryption time (as an example) based on the input message size Arguments for the lookup_interpol function are: --> Checkout the content of the database in ECUSimulation/config/data/measurements.db --> There you can also add your own measurements looks for a value in the database. If none is found looks for variables around it and tries to interpolate a value from the neighboring values Input: lib string value of library column in the DB mode string mode requested of library column in the DB e.g. ENCRYPTION, DECRYPTION,... alg string name of the algorithm of library column in the DB alg_mode string name of algorithm mode of library column in the DB (e.g. CTR, ...) keylen integer length of the key in bit of library column in the DB exp integer size of the exponent when RSA is used param_len integer length of the parameter whenn ECC is used (library column in the DB ) data_size integer size of the data of library column in the DB ret_all boolean if this value is true the values for all data_sizes will be returned Output: time float interpolated time from requested values in the database ''' # encrypted size size_to_encrypt = encrypted_message._size # encryption time algorithm = EnumTrafor().to_value(SymAuthMechEnum.AES) key_len = EnumTrafor().to_value(AuKeyLengthEnum.bit_128) library_tag = "Crypto_Lib_HW" # available tags: ['CyaSSL', 'Crypto_Lib_HW', 'Crypto_Lib_SW'] db_val = TimingDBMap().lookup_interpol(lib=library_tag, mode='ENCRYPTION', \ keylen=key_len, alg=algorithm, data_size=size_to_encrypt, description='t_ecu_auth_reg_msg_validate_cert') encrypted_size = 24 # something you need to compute yourself or use the examples provided in ECUSimulation/components/security/ecu/types/impl_sec_mod_lwa.py encryption_time = db_val return encryption_time, encrypted_size def receive_msg(self): while True: # receive from lower layer [message_id, message_data] = yield self.sim_env.process(self.transp_lay.receive_msg()) # receiver information print("\n\nRECEIVER\nTime: "+str(self.sim_env.now)+"--Communication Layer: \nI am ECU " + self._ecu_id + "\nReceived message:\n - ID: " + str(message_id)) # ALL PARTIES RECEIVED A SESSION KEY WE CAN DECRYPT THE MESSAGE HERE AND PASS IT TO THE # APPLICATION LAYER IF DECRYPTION WAS SUCCESSFUL if self._have_session_key and not message_id in [901, 902, 903]: # decryption decrypted_msg = sym_decrypt(message_data.get(), self._session_key) # compute decrypted time and size print("%s: Time before decryption %s" % (str(self._ecu_id), str(self.sim_env.now))) decryption_time, decrypted_size = self._get_time_for_session_decryption(message_data.get()) yield self.sim_env.timeout(decryption_time) # time for decryption print("%s: Time after decryption %s" % (str(self._ecu_id), str(self.sim_env.now))) # if successful pass to app layer if decrypted_msg is None: print("--- Decryption was not successful - probably this ECU did not get the session key") else: message_data = decrypted_msg.get() # push to higher layer return [message_id, message_data] # PART 1 OF OUR SIMPLE PROTOCOL, THE PROTOCOL IS RECEIVED AND A SESSION-KEY IS GENERATED AND SEND # TO THE OTHER ECUS, ALSO THIS SESSION KEY IS STORED AND USED FOR THE REMAINING COMMUNICATION if message_id == 901: #receive and verify certificate [encrypted_msg, received_certificate, received_cert_private_key] = message_data.get() # verify certificate - which works here as they all have the same CA authority that signed the certificate certificate_valid = encryption_tools.certificate_trustworthy(received_certificate, self.my_root_certificates, self.sim_env.now) print("Certificate is valid? %s" % str(certificate_valid)) # Encrypt the new message clear_message = asy_decrypt(encrypted_msg, received_cert_private_key, self.sim_env.now) print("Received CLEAR MESSAGE %s" % str(clear_message.get())) # Respond with a session key of size 30 self._session_key = sym_get_key(SymAuthMechEnum.AES, AuKeyLengthEnum.bit_128) self._have_session_key = True sec_message_id = 902 yield self.sim_env.process(self.transp_lay.send_msg(self._ecu_id, sec_message_id, SegData(self._session_key, 30))) # RECEIVE A SESSION KEY AND STORE IT if message_id == 902: # Read the message data -> which is session key self._session_key = message_data.get() self._have_session_key = True # return a all good message with id 903 of size 20 sec_message_id = 903 def send_msg(self, sender_id, message_id, message): # Sender information # INITIALLY SEND MY CERTIFICATE TO THE OTHER ECU if not self._have_session_key: print("Sending Certificate and my private key : ") # Encrypt message with the public key encrypted_msg = encryption_tools.asy_encrypt(message, self.my_certificate.pub_key_user) # Send the certificate and message and private key message_to_send = SegData([encrypted_msg, self.my_certificate, self.certificate_private_key], 50) sec_message_id = 901 print("\n\nSENDER - \nTime: " + str( self.sim_env.now) + "--Communication Layer: \nI am ECU " + sender_id + "\nSending message:\n - ID: " + str( sec_message_id) + "\n - Content: " + str(message_to_send.get())) yield self.sim_env.process(self.transp_lay.send_msg(sender_id, sec_message_id, message_to_send)) else: # IF THE RECEIVE MSG FUNCTION STORED A SESSION KEY THIS KEY IS NOW USED TO ENCRYPT A MESSAGE # AND TO SEND THE ENCRYPTED MESSAGE # Encrypt message encrypted_msg = sym_encrypt(message, self._session_key) print("\n\nSENDER - \nTime: "+str(self.sim_env.now)+"--Communication Layer: \nI am ECU " + sender_id + "\nSending message:\n - ID: " + str(message_id)+"\n - Content: " + message.get()) # Sending message now with the session key I have print("\nEncrypted the message") # Send message - here send your message with your message_id print("%s: Time before encryption %s" % (str(self._ecu_id), str(self.sim_env.now))) encryption_time, encrypted_size = self._get_time_for_session_encryption(message) yield self.sim_env.timeout(encryption_time) # apply time for encryption print("%s: Time after encryption %s" % (str(self._ecu_id), str(self.sim_env.now))) yield self.sim_env.process(self.transp_lay.send_msg(sender_id, message_id, SegData(encrypted_msg, encrypted_size))) def _init_layers(self, sim_env, MessageClass): ''' Initializes the software layers Input: sim_env simpy.Environment environment of this component MessageClass AbstractBusMessage class of the messages how they are sent on the CAN Bus Output: - ''' # create layers self.physical_lay = StdPhysicalLayer(sim_env) self.datalink_lay = StdDatalinkLayer(sim_env) self.transp_lay = SegmentTransportLayer(sim_env, MessageClass) # interconnect layers self.datalink_lay.physical_lay = self.physical_lay self.transp_lay.datalink_lay = self.datalink_lay @property def ecu_id(self): return self._ecu_id @ecu_id.setter def ecu_id(self, value): self._ecu_id = value def monitor_update(self): ''' updates the monitor connected to this ecu Input: - Output: monitor_list RefList list of MonitorInputs ''' # register Monitoring tags to track #G().register_eventline_tags(self._tags) items_1 = len(self.transp_lay.datalink_lay.controller.receive_buffer.items) items_2 = self.transp_lay.datalink_lay.transmit_buffer_size G().mon(self.monitor_list, MonitorInput(items_1, MonitorTags.BT_ECU_RECEIVE_BUFFER, self._ecu_id, self.sim_env.now)) G().mon(self.monitor_list, MonitorInput(items_2, MonitorTags.BT_ECU_TRANSMIT_BUFFER, self._ecu_id, self.sim_env.now)) self.monitor_list.clear_on_access() # on the next access the list will be cleared return self.monitor_list.get()
class RapidCANBus(AbstractCANBus): ''' This class implements a CAN Bus that actively pulls messages from the ECUs buffers ''' def __init__(self, sim_env, bus_id, data_rate, avg_ecu_dist=2): ''' Constructor Input: sim_env simpy.Environment environment in which this Bus acts bus_id string id of this Bus object data_rate float datarate of this bus avg_ecu_dist float average distance between two connected ECUs Output: - ''' AbstractCANBus.__init__(self, sim_env, bus_id, data_rate, avg_ecu_dist) # bus objects self.current_message = None # current message on the bus [sender_ecu, message] self.set_settings() self.monitor_list = RefList() self._used_prop_times = {} self.gateways = [] self.first = True # synchronization objects self.pot_messages = [] # gathers all potential messages that want to be sent at a certain point in time self.sync_1 = simpy.Store(self.sim_env, capacity=1) # if the decision, who is allowed to sent is done this synchronizer starts the transmission self.sync_2 = simpy.Store(self.sim_env, capacity=1) # if store is empty then the channel is busy self.subscribers = 0 # number of ECUs waiting for the channel to be freed self.current_message_length_bit = 0 # project parameters self.SCB_GATHER_MSGS = time.SCB_GATHER_MSGS self.SCB_GRAB_PRIO_MSG = time.SCB_GRAB_PRIO_MSG self.SCB_PROPAGATION_DELAY = time.SCB_PROPAGATION_DELAY self.SCB_SENDING_TIME = time.SCB_SENDING_TIME self.SCB_WRITE_TO_TRANSCEIVER_BUFFER = time.SCB_WRITE_TO_TRANSCEIVER_BUFFER # ECUs Datalink layers willing to send self._willing_dll = [] def monitor_update(self): ''' returns the input for the monitor Input: - Output: monitor_list list List of MonitorInput objects ''' self.monitor_list.clear_on_access() # on the next access the list will be cleared return self.monitor_list.get() def release_willing(self, dll): ''' remove the ecus that are not willing to send anymore Input: dll AbstractDataLinkLayer Datalink layer of the ECU that is not willing to send anymore Output: - ''' self._willing_dll.remove(dll) def add_willing(self, dll): ''' add a Datalinklayer of an ECU that is willing to send Input: dll AbstractDataLinkLayer Datalink layer of the ECU that is willing to send Output: - ''' if dll not in self._willing_dll: self._willing_dll.append(dll) def notify_bus(self): ''' When the bus is empty it is set to a sleep mode that waits until the ecu notifies it. Using this method the ECU does so. Thus the Bus will only be active when any ecu sends. Input: - Output: - ''' if self.current_message != None: return else: self.sync_1.put(True) def process(self): ''' Constantly pull messages from all ECUs that are connected. Once the Bus is done with one message it pulls the next message from the connected ECUs. Input: - Output: - ''' stp = 0 while True: # print time t = self.sim_env.now if t > stp: # print(self.sim_env.now) stp += 0.5 # check which ECU sends current_minimum = float("inf"); index = 0 for dll in self._willing_dll: val = dll.first_queue_identifier() if val < current_minimum and val != False: current_minimum = index index += 1 # no ECU wiling to send: wait for notify if current_minimum == float("inf"): yield self.sync_1.get() else: self.current_message = self._willing_dll[current_minimum].controller.transmit_buffer.get().message self.current_message_length_bit = self.current_message.msg_length_in_bit # transmit message if self.current_message != None: # monitor start monitor_note = self._monitor_transmission_start() # write to buffer yield self.sim_env.process(self._wait_transmission_time_and_buffer()) self._reset_transmission() # monitor end self._monitor_transmission_end(monitor_note) # if ecus buffer is now empty remove from willing if(len(self._willing_dll[current_minimum].controller.transmit_buffer.queue) == 0): self.release_willing(self._willing_dll[current_minimum]) def set_settings(self): ''' sets the initial setting association between the settings variables and the actual parameter Input: - Output: - ''' self.settings = {} # parameter self.settings['t_gather_msg'] = 'SCB_GATHER_MSGS' self.settings['t_grab_prio_msg'] = 'SCB_GRAB_PRIO_MSG' self.settings['t_propagation_delay'] = 'SCB_PROPAGATION_DELAY' self.settings['t_sending_time'] = 'SCB_SENDING_TIME' self.settings['t_write_to_transceiver_buffer'] = 'SCB_WRITE_TO_TRANSCEIVER_BUFFER' def wait_until_free(self): ''' when the channel is busy some ECUs can start this method in a simpy process. Once the channel is free this process ends and the next ECU can start it's transmission technically: count number of waiting processes and notifies them all once the channel is free Input: - Output - ''' # add subscriber self.subscribers += 1 yield self.sync_2.get() # release all receivers while self.subscribers > 1: self.sync_2.put(True) self.subscribers -= 1 self.subscribers = 0 def _extract_transmission_times(self): ''' calculates the time the current transmission takes Input: - Output: t_propagation: float time it takes to propagate the message t_sending float time it takes to send the message ''' t_propagation = time.call(self.SCB_PROPAGATION_DELAY, self.avg_dist_between_ecus) # either constant or calculated depending on config t_sending = time.call(self.SCB_SENDING_TIME, self.current_message_length_bit, proj.BUS_ECU_DATARATE) # either constant or calculated depending on config return t_propagation, t_sending def _gateway_sends(self, ecu): ''' if gateway is the sender let it continue and reset the message state Input: ecu AbstractECU current ECU that sends the message Output: bool boolean True if the message was sent by this ECU ''' try: if ecu.ecu_id in self.current_message.gw_id: # send message back and forth send could lead to errors: need to reset gw_id list return True except: return False return False def _get_highest_priority_msg(self, message_list): ''' returns the message with the highest priority Input: message_list list list of messages Output: message object message with highest priority (lowest message id) ''' min_val = float("inf") message = None for cur_message in message_list: if min_val > cur_message.message_identifier: min_val = cur_message.message_identifier message = cur_message return message def _grab_highest_priority(self): ''' note the time it takes to select the message with the highest priority Input: - Output: - ''' if self.SCB_GRAB_PRIO_MSG != 0: G().to_t(self.sim_env, self.SCB_GRAB_PRIO_MSG, 'SCB_GRAB_PRIO_MSG', self.__class__.__name__, self) return True return False def _monitor_transmission_start(self): ''' notes the start time when this message was put on the bus Input: - Output: - ''' # extract information uid = uuid.uuid4() tag = MonitorTags.CB_PROCESSING_MESSAGE c_id = self.comp_id sender_id = self.current_message.sender_id msg_id = self.current_message.message_identifier msg_uid = self.current_message.data.unique_id data = self.current_message.data.get(); # extract further information msg = self.current_message size = self.current_message_length_bit / 8 self.current_message.data.unique_id = msg_uid # send to monitor G().mon(self.monitor_list, MonitorInput(data, tag, c_id, self.sim_env.now, sender_id, msg_id, msg, size, msg_id, uid.hex)) return data, c_id, sender_id, msg_id, msg, size, uid def _monitor_transmission_end(self, mon_out): ''' notes the end time when this message was put on the bus Input: - Output: - ''' G().mon(self.monitor_list, MonitorInput(mon_out[0], MonitorTags.CB_DONE_PROCESSING_MESSAGE, \ mon_out[1], self.sim_env.now, mon_out[2], mon_out[3], \ mon_out[4], mon_out[5], -1, mon_out[6].hex)) def _push_to_receivers(self): ''' writes the current message to all ecus that are connected to this Bus Input: - Output: - ''' # get gateways if self.first: self.gateways = [itm.ecu_id for itm in self.connected_ecus if isinstance(itm.ecu_id, UUID)] self.first = False # send only to receivers if General().send_only_to_receivers and self.current_message.message_identifier not in can_registration.AUTH_MESSAGES: run_list = General().sender_receiver_map[self.current_message.sender_id][self.current_message.message_identifier] + self.gateways for ecu in self.connected_ecus: if ecu.ecu_id not in run_list: continue # Gateway:avoid sending to itself (loops) if self._gateway_sends(ecu): continue # ECU: avoid sending to itself if(ecu.ecu_id != self.current_message.sender_id): self.current_message.current_bus = self.comp_id ecu.ecuHW.transceiver.get(self.current_message) else: # iterate over receivers for ecu in self.connected_ecus: # Gateway:avoid sending to itself (loops) if self._gateway_sends(ecu): continue # ECU: avoid sending to itself if(ecu.ecu_id != self.current_message.sender_id): self.current_message.current_bus = self.comp_id ecu.ecuHW.transceiver.get(self.current_message) def _reset_transmission(self): ''' after one message was sent three things have to be reset the current message. The synchronizer for the selection of the next higher prioritized message to be sent and the list that gathered the selected potential messages Input: - Output: - ''' self.current_message = None # message is not on the line anymore self.sync_2.put(True) # channel is free again self.pot_messages = [] # reset def _sending_ok(self, t_propagation, t_sending): ''' checks if this message is sendable Input: t_propagation: float time it takes to propagate the message t_sending float time it takes to send the message Output: bool boolean true if the time is valid ''' try: G().to_t(self.sim_env, t_propagation + t_sending + self.SCB_WRITE_TO_TRANSCEIVER_BUFFER, 'SCB_PROPAGATION_DELAY+SCB_SENDING_TIME+SCB_WRITE_TO_TRANSCEIVER_BUFFER', self.__class__.__name__, self) if t_propagation + t_sending + self.SCB_WRITE_TO_TRANSCEIVER_BUFFER > 0: return True # logging.error("Error (skipped with time 0):t_propagation =%s, t_sending = %s, self.SCB_WRITE_TO_TRANSCEIVER_BUFFER = %s // msg_length_bit = %s" % \ # (t_propagation, t_sending, self.SCB_WRITE_TO_TRANSCEIVER_BUFFER, self.current_message_length_bit)) except: return False return False def _try_logging_transmission(self, t_propagation, t_sending): ''' notes the times that it takes to send the messages. In case an erroneous message is sent this method logs the exception Input: t_propagation: float time it takes to propagate the message t_sending float time it takes to send the message ''' try: # Log transmission L().log(300, self.sim_env.now, self.current_message.sender_id, float(self.current_message_length_bit) / 8.0, \ self.current_message_length_bit, self.comp_id, self.current_message.data.get(), t_propagation + t_sending) except: # Log traceback ECULogger().log_traceback() try: L().log(300, self.sim_env.now, self.current_message.sender_id, self.current_message.data, \ self.current_message_length_bit, self.comp_id, self.current_message.data, t_propagation + t_sending) except: pass def _wait_transmission_time_and_buffer(self): ''' this method times out for the duration of the transmission and then writes the sent messages to the receiving buffer of the ecu Input: - Output: - ''' if not self.current_message_length_bit in self._used_prop_times: # sending times t_propagation, t_sending = self._extract_transmission_times() # duration of transmission wait_time = t_propagation + t_sending + self.SCB_WRITE_TO_TRANSCEIVER_BUFFER if wait_time <= 0: wait_time = 0.000001 self._used_prop_times[self.current_message_length_bit] = wait_time else: wait_time = self._used_prop_times[self.current_message_length_bit] yield self.sim_env.timeout(wait_time) # put to connected ecus self._push_to_receivers()
class MyProtocolCommModule(AbstractCommModule): def __init__(self, sim_env, ecu_id): ''' Constructor Input: ecu_id string id of the corresponding AbstractECU sim_env simpy.Environment environment of this component Output: - ''' AbstractCommModule.__init__(self, sim_env) # local parameters self._ecu_id = ecu_id self._jitter_in = 1 self.monitor_list = RefList() # initialize self._init_layers(self.sim_env, self.MessageClass) # add tags self._tags = ["AUTH_SEND_TIME_BEFORE_ENCRYPTION", "AUTH_SEND_TIME_AFTER_ENCRYPTION", "AUTH_RECEIVE_TIME_BEFORE_DECRYPTION", "AUTH_RECEIVE_TIME_AFTER_DECRYPTION"] # NEW: Add key pair - public and private assymetric_encryption_algorithm = AsymAuthMechEnum.RSA assymetric_encryption_key_length = AuKeyLengthEnum.bit_512 assymetric_encryption_option = 65537 self.priv_key, self.pub_key = encryption_tools.asy_get_key_pair(assymetric_encryption_algorithm, assymetric_encryption_key_length, assymetric_encryption_option) PublicKeyManager().add_key(self._ecu_id, self.pub_key) # make public key available to everybody # Create a certificate from root L3 along L3 L31 L311 [certificate, root_certificates_to_verify_this_certificate, priv_key] = GeneralSpecPreset().certificate_manager.generate_valid_ecu_cert(self._ecu_id, CAEnum.CA_L311, 0, float('inf')) self.my_certificate = certificate self.my_root_certificates = root_certificates_to_verify_this_certificate # verify certificate from root L311 with right root certificates #certificate_valid = encryption_tools.certificate_trustworthy(certificate, root_certificates_to_verify_this_certificate, self.sim_env.now) def receive_msg(self): while True: # receive from lower layer [message_id, message_data] = yield self.sim_env.process(self.transp_lay.receive_msg()) # receiver information print("\n\nRECEIVER\nTime: "+str(self.sim_env.now)+"--Communication Layer: \nI am ECU " + self._ecu_id + "\nReceived message:\n - ID: " + str(message_id)) # Assume it takes 0.5 seconds to e.g. decrypt this message uid = uuid.uuid4() # BEFORE PROCESSING print("\nECU "+ str(self._ecu_id) +"Time before message received: "+ str(self.sim_env.now)) G().mon(self.monitor_list, MonitorInput([], "AUTH_RECEIVE_TIME_BEFORE_DECRYPTION", self._ecu_id, self.sim_env.now, 123, message_id, message_data.get(), message_data.padded_size, 432, uid)) ''' receive and verify certificate ''' [received_message, received_certificate] = message_data.get() # verify certificate - which works here as they all have the same CA authority that signed the certificate certificate_valid = encryption_tools.certificate_trustworthy(received_certificate, self.my_root_certificates, self.sim_env.now) # AFTER PROCESSING print("\nECU "+ str(self._ecu_id) +"Time after message received: "+ str(self.sim_env.now)) G().mon(self.monitor_list, MonitorInput([], "AUTH_RECEIVE_TIME_AFTER_DECRYPTION", self._ecu_id, self.sim_env.now, 123, message_id, message_data.get(), message_data.padded_size, 432, uid)) # push to higher layer return [message_id, message_data] def send_msg(self, sender_id, message_id, message): # Sender information print("\n\nSENDER - \nTime: "+str(self.sim_env.now)+"--Communication Layer: \nI am ECU " + sender_id + "\nSending message:\n - ID: " + str(message_id)+"\n - Content: " + message.get()) # Message to be send print("\nSize of the message we want to send: "+ str(message.padded_size)) print("\nContent of the message: "+ str(message.get())) # Assume it takes 0.2 seconds to e.g. encrypt this message uid = uuid.uuid4() # BEFORE PROCESSING print("\nECU "+ str(self._ecu_id) +"Time before message sent: "+ str(self.sim_env.now)) G().mon(self.monitor_list, MonitorInput([], "AUTH_SEND_TIME_BEFORE_ENCRYPTION", self._ecu_id, self.sim_env.now, 123, message_id, message.get(), message.padded_size, 432, uid)) '''Send my certificate to the other ECUs ''' message_to_send = SegData([message.get(), self.my_certificate], 50) # AFTER PROCESSING G().mon(self.monitor_list, MonitorInput([], "AUTH_SEND_TIME_AFTER_ENCRYPTION", self._ecu_id, self.sim_env.now, 123, message_id, message.get(), message.padded_size, 432, uid)) print("\nECU "+ str(self._ecu_id) +"Time after message sent: "+ str(self.sim_env.now)) # Send message - here send your message with your message_id yield self.sim_env.process(self.transp_lay.send_msg(sender_id, message_id, message_to_send)) def _init_layers(self, sim_env, MessageClass): ''' Initializes the software layers Input: sim_env simpy.Environment environment of this component MessageClass AbstractBusMessage class of the messages how they are sent on the CAN Bus Output: - ''' # create layers self.physical_lay = StdPhysicalLayer(sim_env) self.datalink_lay = StdDatalinkLayer(sim_env) self.transp_lay = SegmentTransportLayer(sim_env, MessageClass) # interconnect layers self.datalink_lay.physical_lay = self.physical_lay self.transp_lay.datalink_lay = self.datalink_lay @property def ecu_id(self): return self._ecu_id @ecu_id.setter def ecu_id(self, value): self._ecu_id = value def monitor_update(self): ''' updates the monitor connected to this ecu Input: - Output: monitor_list RefList list of MonitorInputs ''' # register Monitoring tags to track #G().register_eventline_tags(self._tags) items_1 = len(self.transp_lay.datalink_lay.controller.receive_buffer.items) items_2 = self.transp_lay.datalink_lay.transmit_buffer_size G().mon(self.monitor_list, MonitorInput(items_1, MonitorTags.BT_ECU_RECEIVE_BUFFER, self._ecu_id, self.sim_env.now)) G().mon(self.monitor_list, MonitorInput(items_2, MonitorTags.BT_ECU_TRANSMIT_BUFFER, self._ecu_id, self.sim_env.now)) self.monitor_list.clear_on_access() # on the next access the list will be cleared return self.monitor_list.get()
class MyProtocolCommModule(AbstractCommModule): def __init__(self, sim_env, ecu_id): ''' Constructor Input: ecu_id string id of the corresponding AbstractECU sim_env simpy.Environment environment of this component Output: - ''' AbstractCommModule.__init__(self, sim_env) # local parameters self._ecu_id = ecu_id self._jitter_in = 1 self.monitor_list = RefList() # initialize self._init_layers(self.sim_env, self.MessageClass) def receive_msg(self): ''' receives messages via the tesla mechanism after they where authenticated. Then messages that were authenticated are buffered in _messages_to_return and returned to higher layers sequentially on each call of this method Input: - Output: message_data object/string/... message that was received message_id integer id of received message NOTE: Accessing sender of a message -> USE: message_data.sender_id Accessing message_id of the received message -> USE: message_id Accessing message content -> USE: message_data.get() message_data is of type SegData ''' while True: # receive from lower layer [message_id, message_data] = yield self.sim_env.process(self.transp_lay.receive_msg()) # do something here e.g. if message_id = Handshake request received here, # -> send something as response e.g. # PLACE CODE HERE print("\n\nRECEIVER\nTime: "+str(self.sim_env.now)+"--Communication Layer: \nI am ECU " + self._ecu_id + "\nReceived message:\n - ID: " + str(message_id) +"\n - Content: " + message_data.get()) # push to higher layer return [message_id, message_data] def send_msg(self, sender_id, message_id, message): ''' this method receives the message from the application layer and transmits it further to the transport layer or if required handles the security communication Input: sender_id string ID of the ECU that wants to send the message message_id integer identifier of the message that is to be sent message object message that will be sent Output: - ''' # do something with received message # PLACE CODE HERE print("\n\nSENDER - \nTime: "+str(self.sim_env.now)+"--Communication Layer: \nI am ECU " + sender_id + "\nSending message:\n - ID: " + str(message_id)+"\n - Content: " + message.get()) # Send message - here send your message with your message_id yield self.sim_env.process(self.transp_lay.send_msg(sender_id, message_id, message)) def _init_layers(self, sim_env, MessageClass): ''' Initializes the software layers Input: sim_env simpy.Environment environment of this component MessageClass AbstractBusMessage class of the messages how they are sent on the CAN Bus Output: - ''' # create layers self.physical_lay = StdPhysicalLayer(sim_env) self.datalink_lay = StdDatalinkLayer(sim_env) self.transp_lay = SegmentTransportLayer(sim_env, MessageClass) # interconnect layers self.datalink_lay.physical_lay = self.physical_lay self.transp_lay.datalink_lay = self.datalink_lay @property def ecu_id(self): return self._ecu_id @ecu_id.setter def ecu_id(self, value): self._ecu_id = value def monitor_update(self): ''' updates the monitor connected to this ecu Input: - Output: monitor_list RefList list of MonitorInputs ''' items_1 = len(self.transp_lay.datalink_lay.controller.receive_buffer.items) items_2 = self.transp_lay.datalink_lay.transmit_buffer_size G().mon(self.monitor_list, MonitorInput(items_1, MonitorTags.BT_ECU_RECEIVE_BUFFER, self._ecu_id, self.sim_env.now)) G().mon(self.monitor_list, MonitorInput(items_2, MonitorTags.BT_ECU_TRANSMIT_BUFFER, self._ecu_id, self.sim_env.now)) self.monitor_list.clear_on_access() # on the next access the list will be cleared return self.monitor_list.get()