pylessard / python-udsoncan

Python implementation of UDS (ISO-14229) standard.
MIT License
575 stars 199 forks source link

Unexpected UDS Authentication message byte order(Subfunction 0x05,0x6) #190

Closed ichbinbork closed 9 months ago

ichbinbork commented 9 months ago

Hi,

I guess I discover oddness about Authentication service 0x05 and 0x06 subfunction. First of all I'm not expert of UDS protocol so that please do not judge me :)

I was testing your UDS services for a while and I observed sending messages scheme like [PCI, SID, Sub Func, DATA,DATA,DATA,DATA] like general UDS protocol. So far everything is normal and as it should be. Then I tried to use authentication service with subfunction 0x05 0x06 I encounter little bit differ UDS request byte order.

Here is the message that sent from the client side while using subfunction 0x05 '10 18 29 05 19 00 00 00'. I sent this message using client.authentication(5,communication_configuration=255,algorithm_indicator=b'\x06' + bytes(15)) that line of code. Its same for the 0x06

In this request '18' value is nonsense for me and I couldn't get what caused it to send. I suspected about parameters (algoritm indicator and communication configuration) might cause this problem then I checked your test code and in the test code parameters used in the same way.

Thank again for this great project. Best regards.

pylessard commented 9 months ago

Hi, 1018 is the isotp header. 1 means FirstFrame and 018 means 22 bytes. Looking at the description you make of the payload, I think you may misunderstand the isotp header part. When a payload first in a single frame, the header will be 0x0n where 0 means SingleFrame and n is the payload length. When the payload is longer than a single CAN message, it uses the scheme above.

Note also that bytes(15) vreates a payload of 15 bytes with value 0. I'm mentioning in case you wanted bytes([15]) which creates payload of 1 byte with value 15.

Let me know if that helps or not. I have not written the support for that service, but if the question is unanswered, I will dive into the code.

Cheers

ichbinbork commented 9 months ago

Hi again,

After some research finally got some points about isotp header part. Thank you for informative comment. Come to the point about byte(15), algorithm indicator is mandatory parameter field which should send with 0x05 0x06 subfunctions. And according to ISO14229-1 it has to be exact 16 byte (like in code so its okay).

pylessard commented 9 months ago

One last note. The header of the first message has 3 possibilities.

  1. Payload fist in a single message. First byte is 0x0n where n is the length of the payload
  2. Payload bigger than a single message and less than 4096. Header is 0x1nnn, where nnn is the length of the payload (12bits)
  3. Payload is bigger than 4095 bytes: Header is 0x1000nnnnnnnn. Where nnnnnnnn is the 32 bits length
ichbinbork commented 8 months ago

Thank you for your effort about explaining ISO-TP. At this point I will little bit tackle your isotp python module. I have also one extra question. I know that this is not belongs to this issue topic but :( . I want to set client server environment for UDS messages. In client side udsoncan works great but server side I'm sending packets manually(with can library). Is there any way to use udsoncan in the server side?. Lets suppose that we want implement Authentication challenge-response steps on both sides(Sending request challenge subfunction first then creating challenge then verify proof of ownership). I tried to mock this communication but I failed cuz I don't have any implemented UDS on server side. When I send the manually UDS responses communication somehow does not continue and is interrupted. Probably this thing happens due to not sending proper or expected message from server side. Could you have better ideas about that?

Best regards.

pylessard commented 8 months ago

Hi, I have not implemented any logic for server side handling. Generally, the server is an ECU that is programmed in C/C++, therefore I figured there would be very few benefits form all the effort it would requires to implement that in Python.

Request/Response objects both have to_bytes and from_bytes method (which can be useful for server side). But above that, nothing has been done. Services have make_request and interpret_response (clients) and no interpret_request/make_response (server). And obviously, there is not server object, just a client.

Your best bet probably is to manually craft request/responses and send that to the isotp layer. You can use one of the UDS connection to make it simpler

frame = conn.wait_frame()
req = Request.from_bytes(frame)

if req.service == ...:
    ....

resp = Response(MyService, code=Response.Code.Positive, data=bytes([1,2,3,4])
conn.send(resp.to_bytes())

cheers

ichbinbork commented 8 months ago

Hi,

It looks like it will do the job. Then, I have a further question about it. As you may know, the TransferData (0x36) service sends many CAN frames containing data. To maintain the challenge-response flow between server and client, the server sends a flow control frame when multiple frames are sent from the client side. I've realized that even if I don't send flow control messages from the server side, the client side continues sending frames. Is there any way to control this communication? I suspect it must be in your 'isotp' Python module, but I haven't delved into it yet.

Best regards.

pylessard commented 8 months ago

Hello, You are correct, it is a designed choice that I made early in the development. As far as I could tell, the ISO-15765 standard does not specifies any requirement when a sender fails to send a flow control and keep going on. I decided to make the design resilient and still accept the data. I am still questioning that choice today.

My rationale was based on some previous experience where I saw some tester hard-code a series of CAN frame in a unit test suite to send UDS payload, no isotp management. I figured I could accomodate those who does something like that

Nevertheless, I agree that it might be confusing for someone who wants to test his isotp layer. I was thinking of adding a parameter to control that. like "strict_flow_control_handling" or something along those lines.

I'd be happy to have your opinion about this

ichbinbork commented 8 months ago

Hello,

Further to our previous conversation, possibly not addressing flow control issues will cause problems on the server side. Let's say we are trying to update the software of an ECU on the vehicle and the Gateway memory is somehow overloaded. In this case it would be a good option to receive and evaluate the message "FS = 2: Overload" to avoid data loss. I know we can send DTC when overload occurs but it still does not prevent data loss during update. I will try to study your project and make PL. If you have time, I would appreciate if you share the path of the relevant code block. If not, I will spend some more time and find it.

Best regards.

Edit: After little research I found the code block that handles the flow control messages in your project

if flow_control_frame is not None:
            if flow_control_frame.flow_status == PDU.FlowStatus.Overflow:   # Needs to stop sending.
                self._stop_sending(success=False)
                self._trigger_error(isotp.errors.OverflowError('Received a FlowControl PDU indicating an Overflow. Stopping transmission.'))
                return self.ProcessTxReport(msg=None, immediate_rx_required=False)

It seems like you handled the flow control codes. But in practice, it did not work as intended.

pylessard commented 8 months ago

I handle flow controls. I just don't raise an error if I receive a consecutive frame instead of a flow control.

I will address that. What needs to be done is add a WAIT_FC state in the rx state machine and drop consecutive frame there

pylessard commented 8 months ago

Do you have an issue with flow control not veing handled or not being enforced? Not the same thing

ichbinbork commented 8 months ago

I realized what was going on. When I try to send less data chunk like

      client.change_session(2)
      client.routine_control(0x3A,1)   ## Start Routine
      client.routine_control(0X3A,2)   ## Stop Routine
      client.routine_control(0X3A,3)   ## Request Routine Results
      memloc1 = MemoryLocation(address=0x1234, memorysize=0x10, address_format=16)
      client.request_download(memloc1)
      our_software= b'\x18p8\x9a\x14[\xfa\n\xfa\x82\xaf\xc8\x02\xfc\xc1\xd8]y\xa59'
      client.transfer_data(0x3B,our_software)
      client.request_transfer_exit()
      client.ecu_reset(1)

It transmitted perfectly to the server side as seen on the CAN log image

But when I tried to send large data compare to previous like this

our_software= b'\x18p8\x9a\x14[\xfa\n\xfa\x82\xaf\xc8\x02\xfc\xc1\xd8]y\xa59\xeb\x10k\x07\tk\x07\xda\x03\x01.9\x08\xfc\xd7\xbd\x84\xa2\xe4;\x0b\xd4\x92h\xdd\x9c\x7f\xa4\xf7\xcd=\xe6\xcc\x15T\xb3\x8a\x0bP\x0byJ\x91\xe5\xd3%\xba:y\xd9\xa5)\x8e\x13\xca\x99:P\xc3\x95c\xadnj\xda\xa2\x1fv~2\x0cm\xbfTW\xa37\xce\x12\xdd\xd5\xef6\x17\xc2\x80wHp\xc3\xf9K>\n\xabz`v\xd4\x80\xdf3b\x8b\t{\x89\x83\x1f\x14 X\x9a\xeb\x1d\\P\xf5h@\xe2Y\x98\xd0\xedD\x08G\x1b\xe8~\xa5\xc8\xc2L5[\xf4LS\x9d\xccY\xfd\x9bh\xa5\xdf\x8d\xd8\xc0A\xf2"\x9f\xd1\xccs,\xf0_u\x84\xd0\xa7\xbf\xf8\x9f\x85h\xe8\x9b\xfd\xab5\x17j\x1f\x0f\xfb!\xc6\\\xcb\xd8\x02\xb9\xa3\xc9O\x1e\xb4\xdc\xe1\x16\xd5e\x98\xbcF"\xc6o\x7f\x18\x18ln,\xccC\r\x03\x1d#qc\xf7\xdd\xa3\xcb\xa5\xa4\x06\x98V\xbbXs\'\xe94N\x89\xf0\xed'
      client.transfer_data(0x00, our_software)
      client.request_transfer_exit()

The communication stuck as seen in the screenshot below and I couldn't transmit the request transfer exit service from client side. image

I'm thinking that the client wait more than one FlowControl frame for keep stay on communication when I send large payloads. So I think you have already applied proper handled and enforced requirements of iso-tp. As I see when I sent the Flow Control from server side, the client side sends exactly 10 data frame as shown in CAN log and wait for the Flow Control frame for server side. What is the reason of expecting flow control after got 10 data frame? After a short period of time (5 seconds as specified in the iso-tp parameter) the session ends as I expected because I do not get a response from the server

Editted: "0x30,0x0A,0x14,0x00,0x00,0x00,0x00,0x00" Here is my response payload. As you see I'm sending "0x0A" as block size that's why sending 10 data frame. In short all problems has been solved.

I appreciate that.

pylessard commented 8 months ago

Glad the repo helped you. Cheers