긴 UDS 요청을 붙이다 보면 이런 구간이 나온다.
- 요청은 정상적으로 들어갔다
- ECU는
0x78 Response Pending을 보낸다 - 세션 timeout이 걱정돼 Tester Present도 보내고 싶다
- 그런데 그 뒤부터 응답 매칭이 이상해진다
이때 “세션 유지를 잘한 것 같은데 왜 더 꼬이지?”라는 느낌을 자주 받는다.
오늘 메모는 DoIP에서 Response Pending 대기 중 Tester Present를 섞어 보낼 때 어디서 꼬이기 쉬운지다.
결론부터
0x78이 왔다는 것은 보통 이미 보낸 본 요청이 아직 진행 중이라는 뜻이다.
반면 Tester Present는 보통 세션을 유지하기 위한 별도 관리 요청이다.
둘 다 UDS 위에 있지만 역할은 다르다.
본 요청:
RoutineControl / ReadData / Download ...
-> 0x78
-> 최종 응답 대기
세션 유지 요청:
Tester Present
-> 세션 timeout 방지 목적
따라서 구현에서는
대기 중인 본 요청 흐름과 세션 유지 흐름을 분리해서 다루는 편이 안전하다.
왜 바로 섞고 싶어지나
현장에서는 이유가 충분히 있다.
- 긴 RoutineControl이 돈다
- Download 관련 서비스가 오래 걸린다
- ECU가 최종 응답을 늦게 준다
- 세션이 끊길까 불안하다
그래서 자연스럽게 이런 코드를 넣기 쉽다.
request 전송
-> 0x78 수신
-> 대기 타이머 시작
-> 중간에 Tester Present 주기 송신
-> 응답 처리 복잡도 증가
문제는 여기서부터다.
자주 터지는 실수 1: 응답 대기 슬롯이 하나뿐이다
테스터가 “지금 기다리는 응답”을 하나의 전역 상태로만 들고 있으면,
Tester Present 응답이 본 요청 응답 상태를 덮어쓸 수 있다.
예를 들면 이런 식이다.
ReadDataByIdentifier 전송
-> 0x78 수신
-> waiting_sid = 0x22
-> Tester Present 전송
-> Tester Present 응답 수신
-> waiting_sid 갱신 또는 초기화
-> 실제 0x22 최종 응답 도착
-> 응답 매칭 실패
패킷은 정상인데
앱 상태머신만 꼬이는 전형적인 패턴이다.
자주 터지는 실수 2: Tester Present를 같은 retry 흐름에 묶는다
0x78은 timeout이 아니다.
이미 받은 정상 응답의 한 종류다.
그런데 코드가 아래처럼 짜여 있으면 문제가 생긴다.
0x78 수신
-> "응답이 아직 안 끝남"
-> 같은 요청 큐에 Tester Present 삽입
-> 대기 타이머와 세션 유지 타이머가 같은 정책을 공유
그러면 이런 혼선이 나온다.
0x78대기 타이머가 Tester Present 응답으로 리셋됨- Tester Present 실패가 본 요청 실패처럼 처리됨
- 본 요청 timeout과 세션 keepalive 주기가 서로 덮어씀
즉 네트워크보다 먼저
타이머 책임을 분리했는지를 봐야 한다.
자주 터지는 실수 3: ECU가 다른 요청 끼어들기를 싫어한다
모든 ECU가 똑같이 동작하지는 않는다.
어떤 구현은 0x78을 보낸 뒤에도
같은 세션의 Tester Present를 잘 받아 준다.
하지만 어떤 구현은:
- 같은 연결에서 다른 요청이 끼어드는 것을 싫어하거나
- 특정 서비스 진행 중 병렬 요청을 제한하거나
- Tester Present에도 timing 제약을 둔다
이 경우 증상은 이런 식으로 보일 수 있다.
- 원래 요청의 최종 응답이 늦어진다
- Tester Present에 NRC가 온다
- 이후 본 요청이
0x21이나 timeout처럼 보인다
그래서 “세션 유지를 위해 넣은 패킷”이
오히려 디버깅 노이즈가 되기도 한다.
suppress positive response도 같이 보면 헷갈림이 줄어든다
Tester Present는 suppress positive response 비트를 함께 쓰는 경우가 많다.
이 옵션이 들어가면 로그 해석이 더 헷갈릴 수 있다.
Tester Present 송신
-> positive response 없음
-> 정상인지 누락인지 헷갈림
이 상태에서 본 요청의 최종 응답만 기다려야 하는데,
구현이 “왜 응답이 안 왔지?”로 오해하면
불필요한 재전송이나 재연결로 이어진다.
즉 Tester Present 자체도
응답이 반드시 오는 관리 요청인지, 무응답이 정상일 수 있는지를 코드가 알고 있어야 한다.
구현 쪽에서 무난한 패턴
가장 단순한 방향은 이렇다.
본 요청 상태머신:
request 전송
-> 0x78 수신
-> 같은 transaction 유지
-> 최종 응답 대기
세션 유지 상태머신:
idle일 때 주기 관리
-> 필요 시 Tester Present 전송
-> suppress 설정에 맞춰 별도 처리
그리고 긴 작업 중에는 정책을 명확히 두는 편이 좋다.
0x78대기 중에는 Tester Present를 잠시 멈출지- 특정 ECU에 대해서만 허용할지
- 허용하더라도 별도 큐와 별도 응답 매칭으로 처리할지
핵심은 본 요청 응답 대기 상태를 Tester Present가 건드리지 않게 하는 것이다.
로그를 이렇게 남기면 빨라진다
이 이슈는 패킷 캡처만 보면 “둘 다 정상 송수신”처럼 보일 수 있다.
나는 보통 아래 항목을 같이 남긴다.
- 현재 대기 중인 본 요청 SID
- 마지막
0x78수신 시각 - Tester Present 송신 시각과 suppress 사용 여부
- 응답 매칭 키가 SID인지, transaction 상태인지
- 본 요청 timeout과 세션 유지 타이머가 서로 다른지
이 다섯 줄이 있으면
“ECU가 이상한가”보다
“테스터 상태머신이 서로 섞였는가”를 빨리 볼 수 있다.
빠른 체크리스트
0x78대기 중인 본 요청 상태와 Tester Present 상태를 별도 변수로 관리하는지 확인- 기다리는 응답 슬롯이 하나뿐이라서 Tester Present 응답이 본 요청 상태를 덮어쓰지 않는지 확인
- Tester Present의 suppress positive response 사용 여부를 로그에 남기는지 확인
- 본 요청 timeout 정책과 세션 유지 주기를 같은 타이머로 돌리지 않는지 확인
- 특정 ECU가 긴 작업 중 병렬 요청을 허용하는지 실제 캡처로 확인했는지 점검
한 줄 요약
DoIP에서 0x78 Response Pending 대기 중 Tester Present를 섞어 보내야 한다면, 세션 유지 요청과 본 요청 응답 대기 상태를 같은 큐와 같은 타이머로 처리하지 말고 서로 분리해 관리하는 편이 안전하다.
추천 키워드
DoIP, UDS, Tester Present, Response Pending, NRC, Diagnostic Session
DevBJ | 오늘을살자, Log Today