Skip to content
오늘을살자
Go back

DoIP에서 UDS를 ISO-TP처럼 자르면 안 된다: Diagnostic Message 경계를 먼저 봐야 한다

Edit page

DoIP를 처음 붙일 때 CAN 진단 습관 때문에 자주 나오는 오해가 하나 있다.

그런데 DoIP에서는 이 그림으로 들어가면 구현이 금방 꼬인다.

오늘 메모는 DoIP에서 UDS를 ISO-TP처럼 자르면 왜 디버깅이 길어지는지다.

결론부터

DoIP에서 테스터가 직접 다뤄야 하는 메시지 경계는 보통 이쪽이다.

Generic Header(8 bytes)
+ Diagnostic Message payload
  + SA(2)
  + TA(2)
  + UDS bytes(N)

즉 애플리케이션 입장에서는
UDS 한 요청 = DoIP Diagnostic Message 하나로 보는 편이 안전하다.

CAN에서 보던 ISO-TP 조각 경계로 생각하면
송신 큐와 수신 파서가 불필요하게 복잡해진다.

왜 CAN 감각이 그대로 들어오나

CAN에서는 프레임 크기가 작다.
그래서 UDS payload가 길면 transport layer가 전면에 드러난다.

Single Frame
First Frame
Flow Control
Consecutive Frame

이 흐름을 늘 의식하게 된다.

반면 DoIP에서는 TCP가 이미 바이트 스트림 전달을 맡고 있고,
DoIP는 그 위에서 payload length로 메시지 경계만 정한다.

그래서 테스터 구현에서 먼저 신경 써야 하는 것은:

이 아니라

쪽이다.

자주 터지는 실수 1: UDS payload를 앱이 임의로 조각낸다

예를 들면 이런 구조다.

큰 UDS request 생성
-> 앱이 256바이트 단위로 나눔
-> 각 조각을 별도 DoIP Diagnostic Message로 전송
-> ECU 응답 이상

이건 ECU 입장에서는 같은 요청의 연속 조각이 아니라
서로 다른 UDS 요청으로 보일 수 있다.

그러면 흔히 이런 증상이 나온다.

즉 “크니까 나눠 보내야지”가 아니라
UDS service 자체가 요구하는 바이트열을 한 번에 담아야 한다고 보는 편이 맞다.

자주 터지는 실수 2: recv() 한 번을 한 메시지로 간주한다

반대로 수신 쪽에서는 이런 착각이 자주 나온다.

recv()
-> 받은 바이트를 바로 한 응답으로 처리

하지만 DoIP는 TCP stream 위에 있다.

그래서 CAN-TP처럼 “프레임 이벤트”를 기대하면 안 되고,
앞 8바이트를 읽어 payload length를 얻은 뒤
그 길이만큼 누적해서 한 Diagnostic Message를 완성해야 한다.

이 부분은 ISO-TP 조립과 비슷해 보일 수 있지만,
실제로 조립 기준은 DoIP header다.

자주 터지는 실수 3: ACK/NACK를 Flow Control처럼 해석한다

DoIP Diagnostic ACK/NACK가 있으니
이걸 CAN의 Flow Control 비슷하게 오해하는 경우도 있다.

그런데 역할이 다르다.

즉 ACK를 받았다고 해서
“다음 조각을 보내라”는 뜻이 아니다.

애초에 앱이 조각을 만들고 있다면
그 구조부터 다시 보는 편이 빠르다.

긴 UDS 요청은 어디서 다뤄야 하나

실무에서는 여기서 한 번 더 헷갈린다.

예를 들어 RequestDownload, TransferData, 대용량 DID 쓰기처럼
UDS payload 자체가 길어지는 서비스가 있다.

이때도 우선 기준은 같다.

UDS가 정의한 한 request PDU 생성
-> SA/TA 앞에 붙여 DoIP Diagnostic Message 구성
-> header payload length 계산
-> TCP stream으로 송신

즉 길이가 길다는 이유만으로
앱이 ISO-TP 같은 조각 계층을 새로 만들 필요는 없다.

물론 OEM 또는 ECU 구현에 따라
서비스 단위 block 분할 정책은 있을 수 있다.
하지만 그건 UDS 서비스 레벨의 block 설계이지,
DoIP transport를 ISO-TP처럼 다시 만드는 문제와는 다르다.

디버깅할 때 이렇게 보면 빨라진다

이 이슈는 패킷 덤프만 봐도 힌트가 바로 나온다.

  1. 한 UDS 요청이 DoIP Diagnostic Message 여러 개로 쪼개져 있는지 확인
  2. 각 DoIP message의 payload length가 SA/TA + UDS 길이와 맞는지 확인
  3. recv() 횟수와 message 개수를 같은 개념으로 보고 있지 않은지 확인
  4. ACK/NACK를 다음 조각 제어 신호처럼 해석하는 코드가 있는지 확인
  5. 큰 서비스 요청일수록 앱 내부에 “분할 송신 레이어”가 숨어 있지 않은지 확인

구현 쪽에서 무난한 패턴

애플리케이션 경계를 단순하게 두는 편이 덜 아프다.

build complete UDS request
-> wrap into one DoIP Diagnostic Message
-> send bytes

receive TCP bytes
-> accumulate by DoIP payload length
-> decode one Diagnostic Message
-> pass UDS payload upward

핵심은 두 가지다.

이렇게 잡아두면
Routing Activation 이후의 UDS 흐름도 훨씬 읽기 쉬워진다.

빠른 체크리스트

  1. 큰 UDS 요청을 앱이 임의 크기로 쪼개 별도 DoIP message로 보내지 않는지 확인
  2. recv() 한 번을 한 응답으로 처리하는 경로가 없는지 확인
  3. payload length 계산에 SA/TA 4바이트가 포함되는지 확인
  4. ACK/NACK를 transport-level flow control처럼 쓰는 코드가 없는지 확인
  5. 장비 로그에 UDS length, DoIP payload length, TCP rx chunk length를 분리 기록하는지 확인

한 줄 요약

DoIP에서 UDS를 ISO-TP처럼 다시 쪼개기보다, UDS 한 요청을 Diagnostic Message 하나로 만들고 TCP stream에서는 Generic Header의 payload length 기준으로 메시지 경계를 복원하는 편이 안전하다.

추천 키워드

DoIP, UDS, ISO-TP, Diagnostic Message, payload length, Routing Activation


DevBJ | 오늘을살자, Log Today


Edit page
Share this post on:

Previous Post
lwIP에서 RX는 되는데 payload가 가끔 깨진다: DMA 버퍼와 D-Cache 순서를 같이 봐야 한다
Next Post
lwIP TCP 재시도 루프에서 RTOS delay가 필요한 이유: busy loop를 막고 CPU를 양보하기