mirror of
https://github.com/linux-can/can-utils.git
synced 2026-09-21 15:29:56 +02:00
Compare commits
65
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e1cc9a9d3 | ||
|
|
8ea9dff919 | ||
|
|
4855df9af7 | ||
|
|
cdc00bacf0 | ||
|
|
4feccd5889 | ||
|
|
03cf00cbf3 | ||
|
|
1f038b3cd8 | ||
|
|
392ccc970b | ||
|
|
7939678070 | ||
|
|
88a0417a6b | ||
|
|
65e715d56d | ||
|
|
374fecde09 | ||
|
|
bfbf0c851f | ||
|
|
ed7822c9a2 | ||
|
|
e9ea9d1168 | ||
|
|
2d793d38d2 | ||
|
|
340a3b8c92 | ||
|
|
8838ce80fc | ||
|
|
d6c57240ed | ||
|
|
3e0e6ae3a4 | ||
|
|
8bf5f8873f | ||
|
|
0094905376 | ||
|
|
987bc8aac2 | ||
|
|
6b46063eee | ||
|
|
93a7b2dfd3 | ||
|
|
af09afb734 | ||
|
|
69efecc428 | ||
|
|
3698814740 | ||
|
|
6ed0f0ea7f | ||
|
|
287469245c | ||
|
|
7a318636e7 | ||
|
|
7fb81e6779 | ||
|
|
a904183b4e | ||
|
|
e2172546aa | ||
|
|
705b3202a9 | ||
|
|
31a59be02f | ||
|
|
4d908bd7cf | ||
|
|
837e2bb343 | ||
|
|
ec16ef97ff | ||
|
|
651c8818dd | ||
|
|
130e0dced2 | ||
|
|
ff90f4ec21 | ||
|
|
e3ee283443 | ||
|
|
e8130a3575 | ||
|
|
1250c12a30 | ||
|
|
9b5b030877 | ||
|
|
da3914c491 | ||
|
|
18b1e7970a | ||
|
|
491cabea07 | ||
|
|
f1cea504a4 | ||
|
|
900c7e9f6e | ||
|
|
b208876e65 | ||
|
|
72a9fd8ccd | ||
|
|
e448d542e8 | ||
|
|
71b2aec834 | ||
|
|
a24bff8b08 | ||
|
|
fe9ea67814 | ||
|
|
e8559479fb | ||
|
|
6050aa155d | ||
|
|
302184f383 | ||
|
|
c542c9ada7 | ||
|
|
2b8c7c5f4b | ||
|
|
4577316bd6 | ||
|
|
4364d655b8 | ||
|
|
2e71e396c5 |
@@ -30,7 +30,45 @@ jobs:
|
||||
podman run --name stable -di --userns=keep-id:uid=1000,gid=1000 -v "$PWD":/home -w /home ${{ matrix.release }} bash
|
||||
podman exec -i stable uname -a
|
||||
podman exec -i stable id
|
||||
|
||||
- name: Update APT Sources List (Ubuntu Only)
|
||||
if:
|
||||
startsWith(matrix.release, 'ubuntu:') && matrix.release != 'ubuntu:20.04'
|
||||
run: |
|
||||
podman exec -i -u root stable apt update
|
||||
podman exec -e DEBIAN_FRONTEND='noninteractive' -i -u root stable apt install -o APT::Install-Suggests=false -qy \
|
||||
lsb-release
|
||||
|
||||
podman exec -i -u root stable \
|
||||
test -e /etc/apt/sources.list &&
|
||||
podman exec -i -u root stable \
|
||||
sed -i -e 's|\(http.*:\)|[arch=amd64] \1|g' /etc/apt/sources.list
|
||||
|
||||
podman exec -i -u root stable \
|
||||
test -e /etc/apt/sources.list.d/ubuntu.sources &&
|
||||
podman exec -i -u root stable \
|
||||
sed -i -e '/^Components:/a Architectures: amd64' /etc/apt/sources.list.d/ubuntu.sources
|
||||
|
||||
echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports/ $(podman exec -i stable lsb_release -cs) main restricted universe multiverse" | \
|
||||
podman exec -i -u root stable tee -a /etc/apt/sources.list.d/cross.list
|
||||
echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports/ $(podman exec -i stable lsb_release -cs)-updates main restricted universe multiverse" | \
|
||||
podman exec -i -u root stable tee -a /etc/apt/sources.list.d/cross.list
|
||||
echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports/ $(podman exec -i stable lsb_release -cs)-backports main restricted universe multiverse" | \
|
||||
podman exec -i -u root stable tee -a /etc/apt/sources.list.d/cross.list
|
||||
|
||||
- name: Add Architecture
|
||||
if:
|
||||
matrix.release != 'ubuntu:20.04'
|
||||
run: |
|
||||
podman exec -i -u root stable dpkg --add-architecture arm64
|
||||
podman exec -i -u root stable dpkg --add-architecture armhf
|
||||
|
||||
- name: Install Development Packages
|
||||
env:
|
||||
release: ${{ matrix.release == 'debian:experimental' && '-t experimental' || '' }}
|
||||
run: |
|
||||
podman exec -i -u root stable apt update
|
||||
podman exec -e DEBIAN_FRONTEND='noninteractive' -i -u root stable apt upgrade -o APT::Install-Suggests=false -qy
|
||||
podman exec -e DEBIAN_FRONTEND='noninteractive' -i -u root stable apt install -o APT::Install-Suggests=false -qy ${release} \
|
||||
clang \
|
||||
cmake \
|
||||
@@ -38,52 +76,67 @@ jobs:
|
||||
gcc-aarch64-linux-gnu \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
gcc-mips-linux-gnu \
|
||||
libgps-dev \
|
||||
make
|
||||
|
||||
- name: Install Cross Libs
|
||||
env:
|
||||
release: ${{ matrix.release == 'debian:experimental' && '-t experimental' || '' }}
|
||||
if:
|
||||
matrix.release != 'ubuntu:20.04'
|
||||
run: |
|
||||
podman exec -e DEBIAN_FRONTEND='noninteractive' -i -u root stable apt install -o APT::Install-Suggests=false -qy ${release} \
|
||||
libgps-dev:arm64 \
|
||||
libgps-dev:armhf
|
||||
|
||||
- name: Configure & Build with gcc
|
||||
env:
|
||||
cc: gcc
|
||||
run: |
|
||||
podman exec -i stable cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=${cc} -DENABLE_WERROR=ON -B build-${cc}
|
||||
podman exec -i stable cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=${cc} -DENABLE_WERROR=ON -DENABLE_GPS=ON -B build-${cc}
|
||||
podman exec -i stable cmake --build build-${cc}
|
||||
|
||||
- name: Configure & Build with clang
|
||||
env:
|
||||
cc: clang
|
||||
run: |
|
||||
podman exec -i stable cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=${cc} -DENABLE_WERROR=ON -B build-${cc}
|
||||
podman exec -i stable cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=${cc} -DENABLE_WERROR=ON -DENABLE_GPS=ON -B build-${cc}
|
||||
podman exec -i stable cmake --build build-${cc}
|
||||
|
||||
- name: Configure & Build with arm-linux-gnueabihf-gcc
|
||||
env:
|
||||
toolchain: arm-linux-gnueabihf-gcc
|
||||
gps: ${{ matrix.release == 'ubuntu:20.04' && 'OFF' || 'ON' }}
|
||||
run: |
|
||||
podman exec -i stable cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=cmake/${toolchain}.cmake -DENABLE_WERROR=ON -B build-${toolchain}
|
||||
podman exec -i stable cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=cmake/${toolchain}.cmake -DENABLE_WERROR=ON -DENABLE_GPS=${gps} -B build-${toolchain}
|
||||
podman exec -i stable cmake --build build-${toolchain}
|
||||
|
||||
- name: Configure & Build with arm-linux-gnueabihf-clang
|
||||
if:
|
||||
${{ matrix.release != 'ubuntu:20.04' && matrix.release != 'debian:oldstable-slim' }}
|
||||
matrix.release != 'ubuntu:20.04' && matrix.release != 'debian:oldstable-slim'
|
||||
env:
|
||||
toolchain: arm-linux-gnueabihf-clang
|
||||
gps: ${{ matrix.release == 'ubuntu:20.04' && 'OFF' || 'ON' }}
|
||||
run: |
|
||||
podman exec -i stable cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=cmake/${toolchain}.cmake -DENABLE_WERROR=ON -B build-${toolchain}
|
||||
podman exec -i stable cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=cmake/${toolchain}.cmake -DENABLE_WERROR=ON -DENABLE_GPS=${gps} -B build-${toolchain}
|
||||
podman exec -i stable cmake --build build-${toolchain}
|
||||
|
||||
- name: Configure & Build with aarch64-linux-gnu-gcc
|
||||
env:
|
||||
toolchain: aarch64-linux-gnu-gcc
|
||||
gps: ${{ matrix.release == 'ubuntu:20.04' && 'OFF' || 'ON' }}
|
||||
run: |
|
||||
podman exec -i stable cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=cmake/${toolchain}.cmake -DENABLE_WERROR=ON -B build-${toolchain}
|
||||
podman exec -i stable cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=cmake/${toolchain}.cmake -DENABLE_WERROR=ON -DENABLE_GPS=${gps} -B build-${toolchain}
|
||||
podman exec -i stable cmake --build build-${toolchain}
|
||||
|
||||
- name: Configure & Build with aarch64-linux-gnu-clang
|
||||
if:
|
||||
${{ matrix.release != 'ubuntu:20.04' && matrix.release != 'debian:oldstable-slim' }}
|
||||
matrix.release != 'ubuntu:20.04' && matrix.release != 'debian:oldstable-slim'
|
||||
env:
|
||||
toolchain: aarch64-linux-gnu-clang
|
||||
gps: ${{ matrix.release == 'ubuntu:20.04' && 'OFF' || 'ON' }}
|
||||
run: |
|
||||
podman exec -i stable cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=cmake/${toolchain}.cmake -DENABLE_WERROR=ON -B build-${toolchain}
|
||||
podman exec -i stable cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=cmake/${toolchain}.cmake -DENABLE_WERROR=ON -DENABLE_GPS=${gps} -B build-${toolchain}
|
||||
podman exec -i stable cmake --build build-${toolchain}
|
||||
|
||||
- name: Configure & Build with mips-linux-gnu-gcc
|
||||
@@ -93,6 +146,20 @@ jobs:
|
||||
podman exec -i stable cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=cmake/${toolchain}.cmake -DENABLE_WERROR=ON -B build-${toolchain}
|
||||
podman exec -i stable cmake --build build-${toolchain}
|
||||
|
||||
- name: Configure & Build with gcc (Makefile)
|
||||
env:
|
||||
cc: gcc
|
||||
run: |
|
||||
podman exec -i stable make CC=${cc}
|
||||
podman exec -i stable make clean
|
||||
|
||||
- name: Configure & Build with clang (Makefile)
|
||||
env:
|
||||
cc: clang
|
||||
run: |
|
||||
podman exec -i stable make CC=${cc}
|
||||
podman exec -i stable make clean
|
||||
|
||||
- name: Show logs
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
*~
|
||||
*.a
|
||||
*.so
|
||||
*.so.*
|
||||
*.o
|
||||
.ccls-cache
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
cmake_install.cmake
|
||||
compile_commands.json
|
||||
tags
|
||||
/build
|
||||
@@ -10,6 +16,7 @@ tags
|
||||
/can-calc-bit-timing
|
||||
/canbusload
|
||||
/candump
|
||||
/canerrsim
|
||||
/canfdtest
|
||||
/cangen
|
||||
/cangw
|
||||
@@ -33,6 +40,7 @@ tags
|
||||
/j1939sr
|
||||
/j1939-timedate-cli
|
||||
/j1939-timedate-srv
|
||||
/j1939-vehicle-position-srv
|
||||
/log2asc
|
||||
/log2long
|
||||
/mcp251xfd-dump
|
||||
|
||||
+159
-114
@@ -4,16 +4,46 @@ project(can-utils LANGUAGES C)
|
||||
|
||||
message(STATUS "CMake version: ${CMAKE_VERSION}")
|
||||
|
||||
include (CheckFunctionExists)
|
||||
include (CheckSymbolExists)
|
||||
include (GNUInstallDirs)
|
||||
include(CheckFunctionExists)
|
||||
include(CheckSymbolExists)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Release)
|
||||
endif()
|
||||
|
||||
if(CMAKE_EXPORT_COMPILE_COMMANDS STREQUAL "")
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE BOOL "project default" FORCE)
|
||||
endif()
|
||||
|
||||
# Add an option to enable treating warnings as errors
|
||||
option(ENABLE_WERROR "Treat all compiler warnings as errors" OFF)
|
||||
option(ENABLE_GPS "Enable GPS support" OFF)
|
||||
option(BUILD_SHARED_LIBS "Build shared libraries" ON)
|
||||
|
||||
# Options for controlling targets. These groups correspond to the README.
|
||||
option(ENABLE_BASIC_TOOLS "Build basic tools" ON)
|
||||
option(ENABLE_IP_SERVER "Build tools providing CAN access via IP sockets" ON)
|
||||
option(ENABLE_GATEWAY "Build in-kernel gateway configuration tools" ON)
|
||||
option(ENABLE_MEASUREMENT "Build measurement tools" ON)
|
||||
option(ENABLE_ISOTP "Build isotp tools" ON)
|
||||
option(ENABLE_LOG_FILE "Build log file converter tools" ON)
|
||||
option(ENABLE_SLCAN "Build slcan tools" ON)
|
||||
option(ENABLE_MCP251XFD "Build MCP251XFD tools" ON)
|
||||
|
||||
# cmake_dependent_option is only available in CMake 3.22 and later.
|
||||
if(ANDROID)
|
||||
set(ENABLE_J1939 OFF)
|
||||
set(ENABLE_ISOBUSFS OFF)
|
||||
else()
|
||||
option(ENABLE_J1939 "Build J1939 tools" ON)
|
||||
option(ENABLE_ISOBUSFS "Build ISOBUS tools" ON)
|
||||
endif()
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
if(ENABLE_GPS)
|
||||
pkg_check_modules(GPS REQUIRED libgps)
|
||||
endif()
|
||||
|
||||
if(ENABLE_WERROR)
|
||||
add_compile_options(-Werror)
|
||||
@@ -32,102 +62,116 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DCLOCK_TAI=11")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DSO_TXTIME=61")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DSCM_TXTIME=SO_TXTIME")
|
||||
|
||||
include_directories (.)
|
||||
include_directories (./include)
|
||||
include_directories(.)
|
||||
include_directories(./include)
|
||||
|
||||
check_function_exists(fork HAVE_FORK)
|
||||
|
||||
set(PROGRAMS_CANLIB
|
||||
asc2log
|
||||
canbusload
|
||||
candump
|
||||
cangen
|
||||
canplayer
|
||||
cansend
|
||||
cansequence
|
||||
log2asc
|
||||
log2long
|
||||
slcanpty
|
||||
)
|
||||
# List of all programs to be built.
|
||||
# Each program is expected to have a corresponding source file with the same name.
|
||||
set(PROGRAMS)
|
||||
# List of programs to link against can.
|
||||
set(PROGRAMS_CANLIB)
|
||||
# List of programs to link against j1939 and can.
|
||||
set(PROGRAMS_J1939)
|
||||
|
||||
if(HAVE_FORK)
|
||||
if(ENABLE_BASIC_TOOLS)
|
||||
list(APPEND PROGRAMS cansniffer)
|
||||
list(APPEND PROGRAMS_CANLIB
|
||||
candump
|
||||
canplayer
|
||||
cansend
|
||||
cangen
|
||||
cansequence
|
||||
)
|
||||
endif()
|
||||
|
||||
if(ENABLE_IP_SERVER AND HAVE_FORK)
|
||||
list(APPEND PROGRAMS bcmserver)
|
||||
list(APPEND PROGRAMS_CANLIB canlogserver)
|
||||
endif()
|
||||
|
||||
set(PROGRAMS_J1939
|
||||
j1939acd
|
||||
j1939cat
|
||||
j1939spy
|
||||
j1939sr
|
||||
testj1939
|
||||
)
|
||||
|
||||
set(PROGRAMS_J1939_TIMEDATE
|
||||
j1939-timedate-srv
|
||||
j1939-timedate-cli
|
||||
)
|
||||
|
||||
set(PROGRAMS_ISOBUSFS
|
||||
isobusfs-srv
|
||||
isobusfs-cli
|
||||
)
|
||||
|
||||
set(PROGRAMS
|
||||
${PROGRAMS_CANLIB}
|
||||
canfdtest
|
||||
cangw
|
||||
cansniffer
|
||||
isotpdump
|
||||
isotpperf
|
||||
isotprecv
|
||||
isotpsend
|
||||
isotpsniffer
|
||||
isotptun
|
||||
slcan_attach
|
||||
slcand
|
||||
)
|
||||
|
||||
if(HAVE_FORK)
|
||||
list(APPEND PROGRAMS bcmserver)
|
||||
list(APPEND PROGRAMS isotpserver)
|
||||
if(ENABLE_GATEWAY)
|
||||
list(APPEND PROGRAMS cangw)
|
||||
endif()
|
||||
|
||||
add_executable(can-calc-bit-timing
|
||||
calc-bit-timing/can-calc-bit-timing.c
|
||||
)
|
||||
if(ENABLE_MEASUREMENT)
|
||||
list(APPEND PROGRAMS
|
||||
canfdtest
|
||||
canerrsim
|
||||
)
|
||||
list(APPEND PROGRAMS_CANLIB canbusload)
|
||||
|
||||
add_executable(mcp251xfd-dump
|
||||
mcp251xfd/mcp251xfd-dev-coredump.c
|
||||
mcp251xfd/mcp251xfd-dump.c
|
||||
mcp251xfd/mcp251xfd-main.c
|
||||
mcp251xfd/mcp251xfd-regmap.c
|
||||
)
|
||||
add_executable(can-calc-bit-timing
|
||||
calc-bit-timing/can-calc-bit-timing.c
|
||||
)
|
||||
install(TARGETS can-calc-bit-timing DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
endif()
|
||||
|
||||
if(NOT ANDROID)
|
||||
list(APPEND PROGRAMS ${PROGRAMS_J1939})
|
||||
|
||||
add_library(j1939 STATIC
|
||||
libj1939.c
|
||||
if(ENABLE_ISOTP)
|
||||
list(APPEND PROGRAMS
|
||||
isotpdump
|
||||
isotpperf
|
||||
isotprecv
|
||||
isotpsend
|
||||
isotpsniffer
|
||||
isotptun
|
||||
)
|
||||
|
||||
target_link_libraries(j1939
|
||||
PRIVATE can
|
||||
if(HAVE_FORK)
|
||||
list(APPEND PROGRAMS isotpserver)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(ENABLE_J1939)
|
||||
list(APPEND PROGRAMS_J1939
|
||||
j1939acd
|
||||
j1939cat
|
||||
j1939spy
|
||||
j1939sr
|
||||
testj1939
|
||||
)
|
||||
|
||||
add_library(isobusfs SHARED
|
||||
add_executable(j1939-timedate-cli
|
||||
j1939_timedate/j1939_timedate_cli.c
|
||||
)
|
||||
target_link_libraries(j1939-timedate-cli
|
||||
PRIVATE can j1939
|
||||
)
|
||||
install(TARGETS j1939-timedate-cli DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
add_executable(j1939-timedate-srv
|
||||
j1939_timedate/j1939_timedate_srv.c
|
||||
)
|
||||
target_link_libraries(j1939-timedate-srv
|
||||
PRIVATE can j1939
|
||||
)
|
||||
install(TARGETS j1939-timedate-srv DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
endif()
|
||||
|
||||
if(ENABLE_J1939 AND ENABLE_GPS)
|
||||
add_executable(j1939-vehicle-position-srv
|
||||
j1939_vehicle_position/j1939_vehicle_position_srv.c
|
||||
)
|
||||
target_link_libraries(j1939-vehicle-position-srv
|
||||
PRIVATE can j1939 ${GPS_LIBRARIES}
|
||||
)
|
||||
install(TARGETS j1939-vehicle-position-srv DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
endif()
|
||||
|
||||
if(ENABLE_ISOBUSFS)
|
||||
add_library(isobusfs EXCLUDE_FROM_ALL
|
||||
isobusfs/isobusfs_cmn.c
|
||||
isobusfs/isobusfs_cmn_dh.c
|
||||
)
|
||||
|
||||
set(PUBLIC_HEADER_ISOBUSFS
|
||||
isobusfs/isobusfs_cmn.h
|
||||
isobusfs/isobusfs_cmn_cm.h
|
||||
)
|
||||
|
||||
set_target_properties(isobusfs PROPERTIES
|
||||
PUBLIC_HEADER "${PUBLIC_HEADER_ISOBUSFS}"
|
||||
SOVERSION 0
|
||||
)
|
||||
|
||||
install(TARGETS isobusfs
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||
@@ -141,10 +185,10 @@ if(NOT ANDROID)
|
||||
isobusfs/isobusfs_cli_selftests.c
|
||||
isobusfs/isobusfs_cli_int.c
|
||||
)
|
||||
|
||||
target_link_libraries(isobusfs-cli
|
||||
PRIVATE isobusfs can j1939
|
||||
)
|
||||
install(TARGETS isobusfs-cli DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
add_executable(isobusfs-srv
|
||||
isobusfs/isobusfs_srv.c
|
||||
@@ -155,48 +199,55 @@ if(NOT ANDROID)
|
||||
isobusfs/isobusfs_srv_fh.c
|
||||
isobusfs/isobusfs_srv_vh.c
|
||||
)
|
||||
|
||||
target_link_libraries(isobusfs-srv
|
||||
PRIVATE isobusfs can j1939
|
||||
)
|
||||
|
||||
install(TARGETS
|
||||
isobusfs-cli
|
||||
isobusfs-srv
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
set(PUBLIC_HEADER_j1939_TIMEDATE
|
||||
j1939_timedate/j1939_timedate_cmn.h
|
||||
)
|
||||
|
||||
add_executable(j1939-timedate-cli
|
||||
j1939_timedate/j1939_timedate_cli.c
|
||||
)
|
||||
|
||||
target_link_libraries(j1939-timedate-cli
|
||||
PRIVATE can j1939
|
||||
)
|
||||
|
||||
add_executable(j1939-timedate-srv
|
||||
j1939_timedate/j1939_timedate_srv.c
|
||||
)
|
||||
|
||||
target_link_libraries(j1939-timedate-srv
|
||||
PRIVATE can j1939
|
||||
)
|
||||
|
||||
install(TARGETS
|
||||
j1939-timedate-cli
|
||||
j1939-timedate-srv
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
install(TARGETS isobusfs-srv DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
endif()
|
||||
|
||||
add_library(can STATIC
|
||||
if(ENABLE_LOG_FILE)
|
||||
list(APPEND PROGRAMS_CANLIB
|
||||
asc2log
|
||||
log2asc
|
||||
log2long
|
||||
)
|
||||
endif()
|
||||
|
||||
if(ENABLE_SLCAN)
|
||||
list(APPEND PROGRAMS
|
||||
slcan_attach
|
||||
slcand
|
||||
)
|
||||
list(APPEND PROGRAMS_CANLIB slcanpty)
|
||||
endif()
|
||||
|
||||
if(ENABLE_MCP251XFD)
|
||||
add_executable(mcp251xfd-dump
|
||||
mcp251xfd/mcp251xfd-dev-coredump.c
|
||||
mcp251xfd/mcp251xfd-dump.c
|
||||
mcp251xfd/mcp251xfd-main.c
|
||||
mcp251xfd/mcp251xfd-regmap.c
|
||||
)
|
||||
install(TARGETS mcp251xfd-dump DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
endif()
|
||||
|
||||
list(APPEND PROGRAMS
|
||||
${PROGRAMS_CANLIB}
|
||||
${PROGRAMS_J1939}
|
||||
)
|
||||
|
||||
add_library(can STATIC EXCLUDE_FROM_ALL
|
||||
lib.c
|
||||
canframelen.c
|
||||
)
|
||||
|
||||
add_library(j1939 STATIC EXCLUDE_FROM_ALL
|
||||
libj1939.c
|
||||
)
|
||||
target_link_libraries(j1939
|
||||
PRIVATE can
|
||||
)
|
||||
|
||||
foreach(name ${PROGRAMS})
|
||||
add_executable(${name} ${name}.c)
|
||||
|
||||
@@ -213,12 +264,6 @@ foreach(name ${PROGRAMS})
|
||||
install(TARGETS ${name} DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
endforeach()
|
||||
|
||||
install(TARGETS
|
||||
can-calc-bit-timing
|
||||
mcp251xfd-dump
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
|
||||
add_custom_target(uninstall
|
||||
"${CMAKE_COMMAND}" -P "${CMAKE_SOURCE_DIR}/cmake/make_uninstall.cmake"
|
||||
COMMENT "Add uninstall target"
|
||||
|
||||
@@ -46,6 +46,7 @@ MAKEFLAGS := -k
|
||||
CFLAGS := -O2 -Wall -Wno-parentheses -Wsign-compare
|
||||
|
||||
HAVE_FORK := $(shell ./check_cc.sh "$(CC)" fork_test.c)
|
||||
HAVE_LIBGPS := $(shell test -x "`which pkg-config`" && pkg-config --exists libgps && echo 1 || echo 0)
|
||||
|
||||
CPPFLAGS += \
|
||||
-I. \
|
||||
@@ -67,6 +68,11 @@ PROGRAMS_J1939_TIMEDATE := \
|
||||
j1939-timedate-srv \
|
||||
j1939-timedate-cli
|
||||
|
||||
ifeq ($(HAVE_LIBGPS),1)
|
||||
PROGRAMS_J1939_VEHICLE_POSITION := \
|
||||
j1939-vehicle-position-srv
|
||||
endif
|
||||
|
||||
PROGRAMS_ISOBUSFS := \
|
||||
isobusfs-srv \
|
||||
isobusfs-cli
|
||||
@@ -98,6 +104,7 @@ PROGRAMS_SLCAN := \
|
||||
PROGRAMS := \
|
||||
$(PROGRAMS_CANGW) \
|
||||
$(PROGRAMS_J1939_TIMEDATE) \
|
||||
$(PROGRAMS_J1939_VEHICLE_POSITION) \
|
||||
$(PROGRAMS_ISOBUSFS) \
|
||||
$(PROGRAMS_ISOTP) \
|
||||
$(PROGRAMS_J1939) \
|
||||
@@ -106,6 +113,7 @@ PROGRAMS := \
|
||||
can-calc-bit-timing \
|
||||
canbusload \
|
||||
candump \
|
||||
canerrsim \
|
||||
canfdtest \
|
||||
cangen \
|
||||
cansequence \
|
||||
@@ -126,7 +134,8 @@ endif
|
||||
all: $(PROGRAMS)
|
||||
|
||||
clean:
|
||||
rm -f $(PROGRAMS) *.o mcp251xfd/*.o isobusfs/*.o j1939_timedate/*.o
|
||||
rm -f $(PROGRAMS) *.o mcp251xfd/*.o isobusfs/*.o j1939_timedate/*.o \
|
||||
j1939_vehicle_position/*.o
|
||||
|
||||
install:
|
||||
mkdir -p $(DESTDIR)$(PREFIX)/bin
|
||||
@@ -153,6 +162,8 @@ isobusfs_srv.o: lib.h libj1939.h
|
||||
isobusfs_c.o: lib.h libj1939.h
|
||||
j1939_timedate_srv.o: lib.h libj1939.h
|
||||
j1939_timedate_cli.o: lib.h libj1939.h
|
||||
j1939_vehicle_position_srv.o: lib.h libj1939.h
|
||||
|
||||
canframelen.o: canframelen.h
|
||||
|
||||
asc2log: asc2log.o lib.o
|
||||
@@ -182,6 +193,12 @@ j1939-timedate-cli: lib.o \
|
||||
j1939_timedate/j1939_timedate_cli.o
|
||||
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
|
||||
|
||||
j1939-vehicle-position-srv: \
|
||||
lib.o \
|
||||
libj1939.o \
|
||||
j1939_vehicle_position/j1939_vehicle_position_srv.o
|
||||
$(CC) $(LDFLAGS) $^ $(LDLIBS) $(shell pkg-config --libs libgps) -o $@
|
||||
|
||||
isobusfs-srv: lib.o \
|
||||
libj1939.o \
|
||||
isobusfs/isobusfs_cmn.o \
|
||||
|
||||
@@ -29,6 +29,7 @@ subsystem (aka SocketCAN):
|
||||
* canbusload : calculate and display the CAN busload
|
||||
* can-calc-bit-timing : userspace version of in-kernel bitrate calculation
|
||||
* canfdtest : Full-duplex test program (DUT and host part)
|
||||
* canerrsim : CAN error message simulator
|
||||
|
||||
#### ISO-TP tools [ISO15765-2:2016 for Linux](https://github.com/hartkopp/can-isotp)
|
||||
* isotpsend : send a single ISO-TP PDU
|
||||
@@ -49,6 +50,10 @@ subsystem (aka SocketCAN):
|
||||
Follow the link to see examples on how this tools can be used:
|
||||
[Kickstart guide to can-j1939 on linux](https://github.com/linux-can/can-utils/blob/master/can-j1939-kickstart.md)
|
||||
|
||||
#### ISOBus File server tools
|
||||
* isobusfs-cli : ISOBus file client
|
||||
* isobusfs-srv : ISOBus file server
|
||||
|
||||
#### Log file converters
|
||||
* asc2log : convert ASC logfile to compact CAN frame logfile
|
||||
* log2asc : convert compact CAN frame logfile to ASC logfile
|
||||
@@ -75,4 +80,4 @@ Follow the link to see examples on how this tools can be used:
|
||||
* [SocketCAN Documentation (Linux Kernel)](https://www.kernel.org/doc/html/latest/networking/can.html)
|
||||
* [Elinux.org CAN Bus Page](http://elinux.org/CAN_Bus)
|
||||
* [Debian Package Description](https://packages.debian.org/sid/can-utils)
|
||||
|
||||
* [J1939 kernel module installation on Debian](can-j1939-install-kernel-module.md)
|
||||
|
||||
@@ -84,7 +84,7 @@ static void print_usage(char *prg)
|
||||
}
|
||||
|
||||
static void prframe(FILE *file, struct timeval *tv, int dev,
|
||||
cu_t *cf, char dir)
|
||||
union cfu *cf, char dir)
|
||||
{
|
||||
static char abuf[BUFLEN];
|
||||
|
||||
@@ -177,7 +177,7 @@ static void eval_can(char* buf, struct timeval *date_tvp, char timestamps,
|
||||
cf.len = CAN_ERR_DLC;
|
||||
|
||||
calc_tv(&tv, &read_tv, date_tvp, timestamps, dplace);
|
||||
prframe(outfile, &tv, interface, (cu_t *)&cf, NO_DIR);
|
||||
prframe(outfile, &tv, interface, (union cfu *)&cf, NO_DIR);
|
||||
fflush(outfile);
|
||||
return;
|
||||
}
|
||||
@@ -249,7 +249,7 @@ static void eval_can(char* buf, struct timeval *date_tvp, char timestamps,
|
||||
cf.data[i] = data[i] & 0xFFU;
|
||||
|
||||
calc_tv(&tv, &read_tv, date_tvp, timestamps, dplace);
|
||||
prframe(outfile, &tv, interface, (cu_t *)&cf, dir[0]);
|
||||
prframe(outfile, &tv, interface, (union cfu *)&cf, dir[0]);
|
||||
fflush(outfile);
|
||||
}
|
||||
}
|
||||
@@ -374,7 +374,7 @@ static void eval_canfd(char* buf, struct timeval *date_tvp, char timestamps,
|
||||
}
|
||||
|
||||
calc_tv(&tv, &read_tv, date_tvp, timestamps, dplace);
|
||||
prframe(outfile, &tv, interface, (cu_t *)&cf, dir[0]);
|
||||
prframe(outfile, &tv, interface, (union cfu *)&cf, dir[0]);
|
||||
fflush(outfile);
|
||||
|
||||
/* No support for really strange CANFD ErrorFrames format m( */
|
||||
@@ -487,7 +487,7 @@ static void eval_canxl_cc(char* buf, struct timeval *date_tvp, char timestamps,
|
||||
cf.len8_dlc = dlc;
|
||||
|
||||
calc_tv(&tv, &read_tv, date_tvp, timestamps, dplace);
|
||||
prframe(outfile, &tv, interface, (cu_t *)&cf, dir[0]);
|
||||
prframe(outfile, &tv, interface, (union cfu *)&cf, dir[0]);
|
||||
fflush(outfile);
|
||||
}
|
||||
|
||||
@@ -584,7 +584,7 @@ static void eval_canxl_fd(char* buf, struct timeval *date_tvp, char timestamps,
|
||||
return;
|
||||
|
||||
if (!(flags & ASC_F_FDF))
|
||||
return;
|
||||
return;
|
||||
|
||||
cf.flags = CANFD_FDF;
|
||||
|
||||
@@ -594,7 +594,7 @@ static void eval_canxl_fd(char* buf, struct timeval *date_tvp, char timestamps,
|
||||
cf.flags |= CANFD_ESI;
|
||||
|
||||
calc_tv(&tv, &read_tv, date_tvp, timestamps, dplace);
|
||||
prframe(outfile, &tv, interface, (cu_t *)&cf, dir[0]);
|
||||
prframe(outfile, &tv, interface, (union cfu *)&cf, dir[0]);
|
||||
fflush(outfile);
|
||||
}
|
||||
|
||||
@@ -723,7 +723,7 @@ static void eval_canxl_xl(char* buf, struct timeval *date_tvp, char timestamps,
|
||||
cf.flags |= CANXL_RRS;
|
||||
|
||||
calc_tv(&tv, &read_tv, date_tvp, timestamps, dplace);
|
||||
prframe(outfile, &tv, interface, (cu_t *)&cf, dir[0]);
|
||||
prframe(outfile, &tv, interface, (union cfu *)&cf, dir[0]);
|
||||
fflush(outfile);
|
||||
|
||||
/* No support for CAN XL ErrorFrames */
|
||||
|
||||
@@ -1121,6 +1121,11 @@ static const unsigned int common_data_bitrates[] = {
|
||||
4000000,
|
||||
2000000,
|
||||
1000000,
|
||||
800000,
|
||||
500000,
|
||||
250000,
|
||||
125000,
|
||||
100000,
|
||||
0
|
||||
};
|
||||
|
||||
|
||||
@@ -72,6 +72,9 @@
|
||||
#ifndef SO_TIMESTAMPING
|
||||
#define SO_TIMESTAMPING 37
|
||||
#endif
|
||||
#ifndef SCM_TIMESTAMPING
|
||||
#define SCM_TIMESTAMPING SO_TIMESTAMPING
|
||||
#endif
|
||||
|
||||
#define TIMESTAMPSZ 50 /* string 'absolute with date' requires max 49 bytes */
|
||||
|
||||
@@ -113,7 +116,8 @@ static const int canfx_on = 1;
|
||||
|
||||
#define MAXANI 4
|
||||
static const char anichar[MAXANI] = { '|', '/', '-', '\\' };
|
||||
static const char extra_m_info[4][4] = { "- -", "B -", "- E", "B E" };
|
||||
static const char extra_fd_info[4][4] = { "- -", "B -", "- E", "B E" };
|
||||
static const char extra_xl_info[4][4] = { "- -", "S -", "- R", "S R" };
|
||||
|
||||
extern int optind, opterr, optopt;
|
||||
|
||||
@@ -128,6 +132,7 @@ static void print_usage(void)
|
||||
fprintf(stderr, "Options:\n");
|
||||
fprintf(stderr, " -t <type> (timestamp: (a)bsolute/(d)elta/(z)ero/(A)bsolute w date)\n");
|
||||
fprintf(stderr, " -H (read hardware timestamps instead of system timestamps)\n");
|
||||
fprintf(stderr, " -N (log nanosecond timestamps instead of microseconds)\n");
|
||||
fprintf(stderr, " -c (increment color mode level)\n");
|
||||
fprintf(stderr, " -i (binary output - may exceed 80 chars/line)\n");
|
||||
fprintf(stderr, " -a (enable additional ASCII output)\n");
|
||||
@@ -142,7 +147,7 @@ static void print_usage(void)
|
||||
fprintf(stderr, " -d (monitor dropped CAN frames)\n");
|
||||
fprintf(stderr, " -e (dump CAN error frames in human-readable format)\n");
|
||||
fprintf(stderr, " -8 (display raw DLC values in {} for Classical CAN)\n");
|
||||
fprintf(stderr, " -x (print extra message infos, rx/tx brs esi)\n");
|
||||
fprintf(stderr, " -x (print extra message infos, rx/tx [brs esi|sec rrs])\n");
|
||||
fprintf(stderr, " -T <msecs> (terminate after <msecs> if no frames were received)\n");
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "Up to %d CAN interfaces with optional filter sets can be specified\n", MAXSOCK);
|
||||
@@ -220,16 +225,22 @@ static int idx2dindex(int ifidx, int socket)
|
||||
return i;
|
||||
}
|
||||
|
||||
static int sprint_timestamp(char *ts_buffer, const char timestamp,
|
||||
const struct timeval *tv, struct timeval *const last_tv)
|
||||
static int sprint_timestamp(char *ts_buffer, const char timestamp, const char use_ns,
|
||||
const struct timespec *ts, struct timespec *const last_ts)
|
||||
{
|
||||
int numchars = 0;
|
||||
|
||||
switch (timestamp) {
|
||||
case 'a': /* absolute with timestamp */
|
||||
numchars = sprintf(ts_buffer, "(%010llu.%06llu) ",
|
||||
(unsigned long long)tv->tv_sec,
|
||||
(unsigned long long)tv->tv_usec);
|
||||
if (use_ns) {
|
||||
numchars = sprintf(ts_buffer, "(%010llu.%09llu) ",
|
||||
(unsigned long long)ts->tv_sec,
|
||||
(unsigned long long)ts->tv_nsec);
|
||||
} else {
|
||||
numchars = sprintf(ts_buffer, "(%010llu.%06llu) ",
|
||||
(unsigned long long)ts->tv_sec,
|
||||
(unsigned long long)ts->tv_nsec / 1000);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'A': /* absolute with date */
|
||||
@@ -237,32 +248,43 @@ static int sprint_timestamp(char *ts_buffer, const char timestamp,
|
||||
struct tm tm;
|
||||
char timestring[25];
|
||||
|
||||
tm = *localtime(&tv->tv_sec);
|
||||
tm = *localtime(&ts->tv_sec);
|
||||
strftime(timestring, 24, "%Y-%m-%d %H:%M:%S", &tm);
|
||||
numchars = sprintf(ts_buffer, "(%s.%06llu) ", timestring,
|
||||
(unsigned long long)tv->tv_usec);
|
||||
if (use_ns) {
|
||||
numchars = sprintf(ts_buffer, "(%s.%09llu) ", timestring,
|
||||
(unsigned long long)ts->tv_nsec);
|
||||
} else {
|
||||
numchars = sprintf(ts_buffer, "(%s.%06llu) ", timestring,
|
||||
(unsigned long long)ts->tv_nsec / 1000);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'd': /* delta */
|
||||
case 'z': /* starting with zero */
|
||||
{
|
||||
struct timeval diff;
|
||||
struct timespec diff;
|
||||
|
||||
if (last_tv->tv_sec == 0) /* first init */
|
||||
*last_tv = *tv;
|
||||
diff.tv_sec = tv->tv_sec - last_tv->tv_sec;
|
||||
diff.tv_usec = tv->tv_usec - last_tv->tv_usec;
|
||||
if (diff.tv_usec < 0)
|
||||
diff.tv_sec--, diff.tv_usec += 1000000;
|
||||
if (last_ts->tv_sec == 0) /* first init */
|
||||
*last_ts = *ts;
|
||||
diff.tv_sec = ts->tv_sec - last_ts->tv_sec;
|
||||
diff.tv_nsec = ts->tv_nsec - last_ts->tv_nsec;
|
||||
if (diff.tv_nsec < 0)
|
||||
diff.tv_sec--, diff.tv_nsec += 1000000000;
|
||||
if (diff.tv_sec < 0)
|
||||
diff.tv_sec = diff.tv_usec = 0;
|
||||
numchars = sprintf(ts_buffer, "(%03llu.%06llu) ",
|
||||
(unsigned long long)diff.tv_sec,
|
||||
(unsigned long long)diff.tv_usec);
|
||||
diff.tv_sec = diff.tv_nsec = 0;
|
||||
if (use_ns) {
|
||||
numchars = sprintf(ts_buffer, "(%03llu.%09llu) ",
|
||||
(unsigned long long)diff.tv_sec,
|
||||
(unsigned long long)diff.tv_nsec);
|
||||
} else {
|
||||
numchars = sprintf(ts_buffer, "(%03llu.%06llu) ",
|
||||
(unsigned long long)diff.tv_sec,
|
||||
(unsigned long long)diff.tv_nsec / 1000);
|
||||
}
|
||||
|
||||
if (timestamp == 'd')
|
||||
*last_tv = *tv; /* update for delta calculation */
|
||||
*last_ts = *ts; /* update for delta calculation */
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -288,6 +310,7 @@ int main(int argc, char **argv)
|
||||
unsigned char timestamp = 0;
|
||||
unsigned char logtimestamp = 'a';
|
||||
unsigned char hwtimestamp = 0;
|
||||
unsigned char use_ns = 0;
|
||||
unsigned char down_causes_exit = 1;
|
||||
unsigned char dropmonitor = 0;
|
||||
unsigned char extra_msg_info = 0;
|
||||
@@ -319,10 +342,10 @@ int main(int argc, char **argv)
|
||||
struct cmsghdr *cmsg;
|
||||
struct can_filter *rfilter;
|
||||
can_err_mask_t err_mask;
|
||||
static cu_t cu; /* union for CAN CC/FD/XL frames */
|
||||
static union cfu cu; /* union for CAN CC/FD/XL frames */
|
||||
int nbytes, i;
|
||||
struct ifreq ifr;
|
||||
struct timeval tv, last_tv;
|
||||
struct timespec ts, last_ts;
|
||||
int timeout_ms = -1; /* default to no timeout */
|
||||
FILE *logfile = NULL;
|
||||
char fname[83]; /* suggested by -Wformat-overflow= */
|
||||
@@ -334,12 +357,12 @@ int main(int argc, char **argv)
|
||||
signal(SIGHUP, sigterm);
|
||||
signal(SIGINT, sigterm);
|
||||
|
||||
last_tv.tv_sec = 0;
|
||||
last_tv.tv_usec = 0;
|
||||
last_ts.tv_sec = 0;
|
||||
last_ts.tv_nsec = 0;
|
||||
|
||||
progname = basename(argv[0]);
|
||||
|
||||
while ((opt = getopt(argc, argv, "t:HciaSs:lf:Ln:r:Dde8xT:h?")) != -1) {
|
||||
while ((opt = getopt(argc, argv, "t:HNciaSs:lf:Ln:r:Dde8xT:h?")) != -1) {
|
||||
switch (opt) {
|
||||
case 't':
|
||||
timestamp = optarg[0];
|
||||
@@ -359,6 +382,10 @@ int main(int argc, char **argv)
|
||||
hwtimestamp = 1;
|
||||
break;
|
||||
|
||||
case 'N':
|
||||
use_ns = 1;
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
color++;
|
||||
break;
|
||||
@@ -688,12 +715,12 @@ int main(int argc, char **argv)
|
||||
localtime_r(&currtime, &now);
|
||||
|
||||
snprintf(fname, sizeof(fname), "candump-%04d-%02d-%02d_%02d%02d%02d.log",
|
||||
now.tm_year + 1900,
|
||||
now.tm_mon + 1,
|
||||
now.tm_mday,
|
||||
now.tm_hour,
|
||||
now.tm_min,
|
||||
now.tm_sec);
|
||||
now.tm_year + 1900,
|
||||
now.tm_mon + 1,
|
||||
now.tm_mday,
|
||||
now.tm_hour,
|
||||
now.tm_min,
|
||||
now.tm_sec);
|
||||
|
||||
logname = fname;
|
||||
}
|
||||
@@ -783,9 +810,13 @@ int main(int argc, char **argv)
|
||||
for (cmsg = CMSG_FIRSTHDR(&msg);
|
||||
cmsg && (cmsg->cmsg_level == SOL_SOCKET);
|
||||
cmsg = CMSG_NXTHDR(&msg,cmsg)) {
|
||||
if (cmsg->cmsg_type == SO_TIMESTAMP) {
|
||||
if (cmsg->cmsg_type == SCM_TIMESTAMP) {
|
||||
struct timeval tv;
|
||||
memcpy(&tv, CMSG_DATA(cmsg), sizeof(tv));
|
||||
} else if (cmsg->cmsg_type == SO_TIMESTAMPING) {
|
||||
ts.tv_sec = tv.tv_sec;
|
||||
ts.tv_nsec = tv.tv_usec;
|
||||
ts.tv_nsec *= 1000;
|
||||
} else if (cmsg->cmsg_type == SCM_TIMESTAMPING) {
|
||||
struct timespec *stamp = (struct timespec *)CMSG_DATA(cmsg);
|
||||
|
||||
/*
|
||||
@@ -795,8 +826,7 @@ int main(int argc, char **argv)
|
||||
* See chapter 2.1.2 Receive timestamps in
|
||||
* linux/Documentation/networking/timestamping.txt
|
||||
*/
|
||||
tv.tv_sec = stamp[2].tv_sec;
|
||||
tv.tv_usec = stamp[2].tv_nsec / 1000;
|
||||
ts = stamp[2];
|
||||
} else if (cmsg->cmsg_type == SO_RXQ_OVFL) {
|
||||
memcpy(&obj->dropcnt, CMSG_DATA(cmsg), sizeof(__u32));
|
||||
}
|
||||
@@ -831,11 +861,9 @@ int main(int argc, char **argv)
|
||||
/* build common log format output */
|
||||
if ((log) || ((logfrmt) && (silent == SILENT_OFF))) {
|
||||
|
||||
alen = sprint_timestamp(afrbuf, logtimestamp,
|
||||
&tv, &last_tv);
|
||||
alen = sprint_timestamp(afrbuf, logtimestamp, use_ns, &ts, &last_ts);
|
||||
|
||||
alen += sprintf(afrbuf + alen, "%*s ",
|
||||
max_devname_len, devname[idx]);
|
||||
alen += sprintf(afrbuf + alen, "%*s ", max_devname_len, devname[idx]);
|
||||
|
||||
alen += snprintf_canframe(afrbuf + alen, sizeof(afrbuf) - alen, &cu, 0);
|
||||
}
|
||||
@@ -861,18 +889,23 @@ int main(int argc, char **argv)
|
||||
|
||||
/* print (colored) long CAN frame style to stdout */
|
||||
alen = sprintf(afrbuf, " %s", (color > 2) ? col_on[idx % MAXCOL] : "");
|
||||
alen += sprint_timestamp(afrbuf + alen, timestamp, &tv, &last_tv);
|
||||
alen += sprint_timestamp(afrbuf + alen, timestamp, use_ns, &ts, &last_ts);
|
||||
alen += sprintf(afrbuf + alen, " %s%*s",
|
||||
(color && (color < 3)) ? col_on[idx % MAXCOL] : "",
|
||||
max_devname_len, devname[idx]);
|
||||
(color && (color < 3)) ? col_on[idx % MAXCOL] : "",
|
||||
max_devname_len, devname[idx]);
|
||||
|
||||
if (extra_msg_info) {
|
||||
if (msg.msg_flags & MSG_DONTROUTE)
|
||||
alen += sprintf(afrbuf + alen, " TX %s",
|
||||
extra_m_info[cu.fd.flags & 3]);
|
||||
alen += sprintf(afrbuf + alen, " TX");
|
||||
else
|
||||
alen += sprintf(afrbuf + alen, " RX %s",
|
||||
extra_m_info[cu.fd.flags & 3]);
|
||||
alen += sprintf(afrbuf + alen, " RX");
|
||||
|
||||
if (cu.xl.flags & CANXL_XLF)
|
||||
alen += sprintf(afrbuf + alen, " %s",
|
||||
extra_xl_info[cu.xl.flags & 3]);
|
||||
else
|
||||
alen += sprintf(afrbuf + alen, " %s",
|
||||
extra_fd_info[cu.fd.flags & 3]);
|
||||
}
|
||||
|
||||
alen += sprintf(afrbuf + alen, "%s ", (color == 1) ? col_off : "");
|
||||
|
||||
+564
@@ -0,0 +1,564 @@
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// canerrsim - utility to simulate SocketCAN error messages, by Zeljko Avramovic (c) 2024 //
|
||||
// //
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later OR BSD-3-Clause //
|
||||
// //
|
||||
// Virtual CAN adapter vcan0 is hard coded and you can bring it up like this: //
|
||||
// sudo modprobe vcan //
|
||||
// sudo ip link add dev vcan0 type vcan //
|
||||
// sudo ip link set vcan0 mtu 72 # needed for CAN FD //
|
||||
// sudo ip link set vcan0 up //
|
||||
// //
|
||||
// To simulate error messages use canerrsim utility like this: //
|
||||
// ./canerrsim vcan0 LostArBit=09 Data4=AA TX BusOff NoAck ShowBits //
|
||||
// //
|
||||
// That should show in canerrdump utility as: //
|
||||
// 0x06A [8] 09 00 80 00 AA 00 00 00 ERR=LostArBit09,NoAck,BusOff,Prot(Type(TX),Loc(Unspec)) //
|
||||
// //
|
||||
// Alternatively, you could use candump from can-utils to check only error messages like this: //
|
||||
// candump -tA -e -c -a any,0~0,#FFFFFFFF //
|
||||
// //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/error.h>
|
||||
#include <linux/can/raw.h>
|
||||
#include <net/if.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define STR_EQUAL 0
|
||||
|
||||
void show_help_and_exit()
|
||||
{
|
||||
printf("\n");
|
||||
printf("Usage: canerrsim <CAN interface> <options>\n");
|
||||
printf("\n");
|
||||
printf("CAN interface: ( CAN interface is case sensitive )\n");
|
||||
printf(" can0 ( or can1, can2 or virtual ones like vcan0, vcan1...\n");
|
||||
printf("\n");
|
||||
printf("Options: ( options are not case sensitive )\n");
|
||||
printf(" ( ERROR CLASS (MASK) IN CAN ID: )\n");
|
||||
printf(" TxTimeout ( TX timeout by netdevice driver )\n");
|
||||
printf(" NoAck ( received no ACK on transmission )\n");
|
||||
printf(" BusOff ( bus off )\n");
|
||||
printf(" BusError ( bus error, may flood! )\n");
|
||||
printf(" Restarted ( controller restarted )\n");
|
||||
printf(" TxCount=<00..FF> ( TX error counter )\n");
|
||||
printf(" RxCount=<00..FF> ( RX error counter )\n");
|
||||
printf(" ( ARBITRATIONLOST IN CAN ID + BIT NUMBER IN DATA[0]: )\n");
|
||||
printf(" LostArBit=<00..29> ( decimal lost arbitration bit number in bitstream )\n");
|
||||
printf(" ( CONTROLLER IN CAN ID + ERROR STATUS IN DATA[1]: )\n");
|
||||
printf(" OverflowRX ( RX buffer overflow )\n");
|
||||
printf(" OverflowTX ( TX buffer overflow )\n");
|
||||
printf(" WarningRX ( reached warning level for RX errors )\n");
|
||||
printf(" WarningTX ( reached warning level for TX errors )\n");
|
||||
printf(" PassiveRX ( reached error passive status RX, errors > 127 )\n");
|
||||
printf(" PassiveTX ( reached error passive status TX, errors > 127 )\n");
|
||||
printf(" Active ( recovered to error active state )\n");
|
||||
printf(" ( PROTOCOL ERROR IN CAN ID + TYPE IN DATA[2]: )\n");
|
||||
printf(" SingleBit ( single bit error )\n");
|
||||
printf(" FrameFormat ( frame format error )\n");
|
||||
printf(" BitStuffing ( bit stuffing error )\n");
|
||||
printf(" Bit0 ( unable to send dominant bit )\n");
|
||||
printf(" Bit1 ( unable to send recessive bit )\n");
|
||||
printf(" BusOverload ( bus overload )\n");
|
||||
printf(" ActiveAnnouncement ( active error announcement )\n");
|
||||
printf(" TX ( error occurred on transmission )\n");
|
||||
printf(" ( PROTOCOL ERROR IN CAN ID + LOCATION IN DATA[3]: )\n");
|
||||
printf(" SOF ( start of frame )\n");
|
||||
printf(" ID28_21 ( ID bits 21..28, SFF: 3..10 )\n");
|
||||
printf(" ID20_18 ( ID bits 18..20, SFF: 0..2 )\n");
|
||||
printf(" SRTR ( substitute RTR, SFF: RTR )\n");
|
||||
printf(" IDE ( identifier extension )\n");
|
||||
printf(" ID17_13 ( ID bits 13..17 )\n");
|
||||
printf(" ID12_05 ( ID bits 5..12 )\n");
|
||||
printf(" ID04_00 ( ID bits 0..4 )\n");
|
||||
printf(" RTR ( RTR )\n");
|
||||
printf(" RES1 ( reserved bit 1 )\n");
|
||||
printf(" RES0 ( reserved bit 0 )\n");
|
||||
printf(" DLC ( data length code )\n");
|
||||
printf(" DATA ( data section )\n");
|
||||
printf(" CRC_SEQ ( CRC sequence )\n");
|
||||
printf(" CRC_DEL ( CRC delimiter )\n");
|
||||
printf(" ACK ( ACK slot )\n");
|
||||
printf(" ACK_DEL ( ACK delimiter )\n");
|
||||
printf(" EOF ( end of frame )\n");
|
||||
printf(" INTERM ( intermission )\n");
|
||||
printf(" ( TRANSCEIVER ERROR IN CAN ID + STATUS IN DATA[4]: )\n");
|
||||
printf(" ( CANH CANL )\n");
|
||||
printf(" TransUnspec ( 0000 0000 )\n");
|
||||
printf(" CanHiNoWire ( 0000 0100 )\n");
|
||||
printf(" CanHiShortToBAT ( 0000 0101 )\n");
|
||||
printf(" CanHiShortToVCC ( 0000 0110 )\n");
|
||||
printf(" CanHiShortToGND ( 0000 0111 )\n");
|
||||
printf(" CanLoNoWire ( 0100 0000 )\n");
|
||||
printf(" CanLoShortToBAT ( 0101 0000 )\n");
|
||||
printf(" CanLoShortToVCC ( 0110 0000 )\n");
|
||||
printf(" CanLoShortToGND ( 0111 0000 )\n");
|
||||
printf(" CanLoShortToCanHi ( 1000 0000 )\n");
|
||||
printf(" ( CUSTOM BYTE TO DATA[0..7]: )\n");
|
||||
printf(" Data<0..7>=<00..FF> ( write hex number to one of 8 payload bytes )\n");
|
||||
printf(" ( DEBUG HELPERS: )\n");
|
||||
printf(" ShowBits ( display all frame bits )\n");
|
||||
printf("\n");
|
||||
printf("Examples:\n");
|
||||
printf("\n");
|
||||
printf(" ./canerrsim can1 LostArBit=09 Data3=AA Data4=BB ShowBits\n");
|
||||
printf(" ( can1: 9th arb. bit lost, custom bytes in Data[3] and Data[4], show debug frame bits )\n");
|
||||
printf("\n");
|
||||
printf(" ./canerrsim vcan0 NoAck TxTimeout Active\n");
|
||||
printf(" ( vcan0: received no ACK on transmission, driver timeout, protocol type active error announcement )\n");
|
||||
printf("\n");
|
||||
printf(" ./canerrsim vcan0 BusError CanHiNoWire Restarted INTERM\n");
|
||||
printf(" ( vcan0: bus error, lost CANH wiring, controller restarted, protocol location intermission )\n");
|
||||
printf("\n");
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
void err_exit(const char *msg)
|
||||
{
|
||||
printf("%s", msg);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
void show_custom_format_and_exit(const char *param, const char *format)
|
||||
{
|
||||
char str_buf[80];
|
||||
sprintf(str_buf, format, param);
|
||||
err_exit(str_buf);
|
||||
}
|
||||
|
||||
void show_invalid_option(const char *option)
|
||||
{
|
||||
show_custom_format_and_exit(option, "Error: Invalid option %s\n");
|
||||
}
|
||||
|
||||
void show_err_and_exit(const char *err_type)
|
||||
{
|
||||
show_custom_format_and_exit(err_type, "Error: You can only have one %s parameter!\n");
|
||||
}
|
||||
|
||||
void show_loc_err_and_exit()
|
||||
{
|
||||
show_err_and_exit("protocol location");
|
||||
}
|
||||
|
||||
void show_arb_err_and_exit()
|
||||
{
|
||||
show_err_and_exit("arbitration bit");
|
||||
}
|
||||
|
||||
void show_transc_err_and_exit()
|
||||
{
|
||||
show_err_and_exit("transceiver");
|
||||
}
|
||||
|
||||
void print_binary(uint32_t number)
|
||||
{
|
||||
uint32_t mask = 0x80000000; // start with the most significant bit
|
||||
for (int i = 0; i < 32; i++) {
|
||||
putchar((number & mask) ? '1' : '0');
|
||||
mask >>= 1; // shift the mask to the right
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int sock;
|
||||
struct sockaddr_can addr;
|
||||
struct ifreq ifr;
|
||||
struct can_frame frame;
|
||||
bool show_bits = false, location_processed = false, transceiver_processed = false, arbitration_processed = false;
|
||||
char tmp_str[256];
|
||||
|
||||
printf("CAN Sockets Error Messages Simulator\n");
|
||||
if (argc < 3)
|
||||
show_help_and_exit();
|
||||
|
||||
// initialize CAN frame
|
||||
memset(&frame, 0, sizeof(frame));
|
||||
frame.can_id = CAN_ERR_FLAG;
|
||||
frame.can_dlc = CAN_ERR_DLC;
|
||||
|
||||
// Parse command line parameters
|
||||
for (int i = 2; i < argc; i++) {
|
||||
//printf("strlen(argv[%d]) = %d\n", i, strlen(argv[i]));
|
||||
|
||||
// error class (mask) in can_id
|
||||
if (strcasecmp(argv[i], "TxTimeout") == STR_EQUAL)
|
||||
frame.can_id |= CAN_ERR_TX_TIMEOUT; // generate TxTimeout error
|
||||
else if (strcasecmp(argv[i], "NoAck") == STR_EQUAL)
|
||||
frame.can_id |= CAN_ERR_ACK; // generate NoAck error
|
||||
else if (strcasecmp(argv[i], "BusOff") == STR_EQUAL)
|
||||
frame.can_id |= CAN_ERR_BUSOFF; // generate BusOff error
|
||||
else if (strcasecmp(argv[i], "BusError") == STR_EQUAL)
|
||||
frame.can_id |= CAN_ERR_BUSERROR; // generate BusError error
|
||||
else if (strcasecmp(argv[i], "Restarted") == STR_EQUAL)
|
||||
frame.can_id |= CAN_ERR_RESTARTED; // generate Restarted error
|
||||
// error status of CAN controller / data[1]
|
||||
else if (strcasecmp(argv[i], "OverflowRX") == STR_EQUAL) {
|
||||
frame.can_id |= CAN_ERR_CRTL; // generate Controller error
|
||||
frame.data[1] |= CAN_ERR_CRTL_RX_OVERFLOW; // generate RX Overflow suberror
|
||||
} else if (strcasecmp(argv[i], "OverflowTX") == STR_EQUAL) {
|
||||
frame.can_id |= CAN_ERR_CRTL; // generate Controller error
|
||||
frame.data[1] |= CAN_ERR_CRTL_TX_OVERFLOW; // generate TX Overflow suberror
|
||||
} else if (strcasecmp(argv[i], "WarningRX") == STR_EQUAL) {
|
||||
frame.can_id |= CAN_ERR_CRTL; // generate Controller error
|
||||
frame.data[1] |= CAN_ERR_CRTL_RX_WARNING; // generate RX Warning suberror
|
||||
} else if (strcasecmp(argv[i], "WarningTX") == STR_EQUAL) {
|
||||
frame.can_id |= CAN_ERR_CRTL; // generate Controller error
|
||||
frame.data[1] |= CAN_ERR_CRTL_TX_WARNING; // generate TX Warning suberror
|
||||
} else if (strcasecmp(argv[i], "PassiveRX") == STR_EQUAL) {
|
||||
frame.can_id |= CAN_ERR_CRTL; // generate Controller error
|
||||
frame.data[1] |= CAN_ERR_CRTL_RX_PASSIVE; // generate RX Passive suberror
|
||||
} else if (strcasecmp(argv[i], "PassiveTX") == STR_EQUAL) {
|
||||
frame.can_id |= CAN_ERR_CRTL; // generate Controller error
|
||||
frame.data[1] |= CAN_ERR_CRTL_TX_PASSIVE; // generate TX Passive suberror
|
||||
} else if (strcasecmp(argv[i], "Active") == STR_EQUAL) {
|
||||
frame.can_id |= CAN_ERR_CRTL; // generate Controller error
|
||||
frame.data[1] |= CAN_ERR_CRTL_ACTIVE; // generate Active suberror
|
||||
} else if (strcasecmp(argv[i], "CtrlUnspec") == STR_EQUAL) {
|
||||
frame.can_id |= CAN_ERR_CRTL; // generate Controller error
|
||||
frame.data[1] = CAN_ERR_CRTL_UNSPEC; // generate Unspec suberror
|
||||
}
|
||||
// error in CAN protocol (type) / data[2]
|
||||
else if (strcasecmp(argv[i], "SingleBit") == STR_EQUAL) {
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Type error
|
||||
frame.data[2] = CAN_ERR_PROT_BIT; // generate SingleBit suberror
|
||||
} else if (strcasecmp(argv[i], "FrameFormat") == STR_EQUAL) {
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Type error
|
||||
frame.data[2] = CAN_ERR_PROT_FORM; // generate FrameFormat suberror
|
||||
} else if (strcasecmp(argv[i], "BitStuffing") == STR_EQUAL) {
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Type error
|
||||
frame.data[2] = CAN_ERR_PROT_STUFF; // generate BitStuffing suberror
|
||||
} else if (strcasecmp(argv[i], "Bit0") == STR_EQUAL) {
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Type error
|
||||
frame.data[2] = CAN_ERR_PROT_BIT0; // generate Bit0 suberror
|
||||
} else if (strcasecmp(argv[i], "Bit1") == STR_EQUAL) {
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Type error
|
||||
frame.data[2] = CAN_ERR_PROT_BIT1; // generate Bit1 suberror
|
||||
} else if (strcasecmp(argv[i], "BusOverload") == STR_EQUAL) {
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Type error
|
||||
frame.data[2] = CAN_ERR_PROT_OVERLOAD; // generate BusOverload suberror
|
||||
} else if (strcasecmp(argv[i], "ActiveAnnouncement") == STR_EQUAL) {
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Type error
|
||||
frame.data[2] = CAN_ERR_PROT_ACTIVE; // generate ActiveAnnouncement suberror
|
||||
} else if (strcasecmp(argv[i], "TX") == STR_EQUAL) {
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Type error
|
||||
frame.data[2] = CAN_ERR_PROT_TX; // generate TX suberror
|
||||
} else if (strcasecmp(argv[i], "ProtUnspec") == STR_EQUAL) {
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Type error
|
||||
frame.data[2] = CAN_ERR_PROT_UNSPEC; // generate Unspec suberror
|
||||
}
|
||||
// error in CAN protocol (location) / data[3]
|
||||
else if (strcasecmp(argv[i], "LocUnspec") == STR_EQUAL) {
|
||||
if (location_processed)
|
||||
show_loc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Location error
|
||||
frame.data[3] = CAN_ERR_PROT_LOC_UNSPEC; // generate Unspec suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "SOF") == STR_EQUAL) {
|
||||
if (location_processed)
|
||||
show_loc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Location error
|
||||
frame.data[3] = CAN_ERR_PROT_LOC_SOF; // generate SOF suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "SOF") == STR_EQUAL) {
|
||||
if (location_processed)
|
||||
show_loc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Location error
|
||||
frame.data[3] = CAN_ERR_PROT_LOC_SOF; // generate SOF suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "ID28_21") == STR_EQUAL) {
|
||||
if (location_processed)
|
||||
show_loc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Location error
|
||||
frame.data[3] = CAN_ERR_PROT_LOC_ID28_21; // generate ID28_21 suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "ID20_18") == STR_EQUAL) {
|
||||
if (location_processed)
|
||||
show_loc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Location error
|
||||
frame.data[3] = CAN_ERR_PROT_LOC_ID20_18; // generate ID20_18 suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "SRTR") == STR_EQUAL) {
|
||||
if (location_processed)
|
||||
show_loc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Location error
|
||||
frame.data[3] = CAN_ERR_PROT_LOC_SRTR; // generate SRTR suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "IDE") == STR_EQUAL) {
|
||||
if (location_processed)
|
||||
show_loc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Location error
|
||||
frame.data[3] = CAN_ERR_PROT_LOC_IDE; // generate IDE suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "ID17_13") == STR_EQUAL) {
|
||||
if (location_processed)
|
||||
show_loc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Location error
|
||||
frame.data[3] = CAN_ERR_PROT_LOC_ID17_13; // generate ID17_13 suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "ID12_05") == STR_EQUAL) {
|
||||
if (location_processed)
|
||||
show_loc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Location error
|
||||
frame.data[3] = CAN_ERR_PROT_LOC_ID12_05; // generate ID12_05 suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "ID04_00") == STR_EQUAL) {
|
||||
if (location_processed)
|
||||
show_loc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Location error
|
||||
frame.data[3] = CAN_ERR_PROT_LOC_ID04_00; // generate ID04_00 suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "RTR") == STR_EQUAL) {
|
||||
if (location_processed)
|
||||
show_loc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Location error
|
||||
frame.data[3] = CAN_ERR_PROT_LOC_RTR; // generate RTR suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "RES1") == STR_EQUAL) {
|
||||
if (location_processed)
|
||||
show_loc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Location error
|
||||
frame.data[3] = CAN_ERR_PROT_LOC_RES1; // generate RES1 suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "RES0") == STR_EQUAL) {
|
||||
if (location_processed)
|
||||
show_loc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Location error
|
||||
frame.data[3] = CAN_ERR_PROT_LOC_RES0; // generate RES0 suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "DLC") == STR_EQUAL) {
|
||||
if (location_processed)
|
||||
show_loc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Location error
|
||||
frame.data[3] = CAN_ERR_PROT_LOC_DLC; // generate DLC suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "DATA") == STR_EQUAL) {
|
||||
if (location_processed)
|
||||
show_loc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Location error
|
||||
frame.data[3] = CAN_ERR_PROT_LOC_DATA; // generate DATA suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "CRC_SEQ") == STR_EQUAL) {
|
||||
if (location_processed)
|
||||
show_loc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Location error
|
||||
frame.data[3] = CAN_ERR_PROT_LOC_CRC_SEQ; // generate CRC_SEQ suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "CRC_DEL") == STR_EQUAL) {
|
||||
if (location_processed)
|
||||
show_loc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Location error
|
||||
frame.data[3] = CAN_ERR_PROT_LOC_CRC_DEL; // generate CRC_DEL suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "ACK") == STR_EQUAL) {
|
||||
if (location_processed)
|
||||
show_loc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Location error
|
||||
frame.data[3] = CAN_ERR_PROT_LOC_ACK; // generate ACK suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "ACK_DEL") == STR_EQUAL) {
|
||||
if (location_processed)
|
||||
show_loc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Location error
|
||||
frame.data[3] = CAN_ERR_PROT_LOC_ACK_DEL; // generate ACK_DEL suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "EOF") == STR_EQUAL) {
|
||||
if (location_processed)
|
||||
show_loc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Location error
|
||||
frame.data[3] = CAN_ERR_PROT_LOC_EOF; // generate EOF suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "INTERM") == STR_EQUAL) {
|
||||
if (location_processed)
|
||||
show_loc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_PROT; // generate Protocol Location error
|
||||
frame.data[3] = CAN_ERR_PROT_LOC_INTERM; // generate INTERM suberror
|
||||
location_processed = true;
|
||||
}
|
||||
// error status of CAN transceiver / data[4]
|
||||
else if (strcasecmp(argv[i], "TransUnspec") == STR_EQUAL) {
|
||||
if (transceiver_processed)
|
||||
show_transc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_TRX; // generate Transceiver error
|
||||
frame.data[4] = CAN_ERR_TRX_UNSPEC; // generate EOF suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "CanHiNoWire") == STR_EQUAL) {
|
||||
if (transceiver_processed)
|
||||
show_transc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_TRX; // generate Transceiver error
|
||||
frame.data[4] = CAN_ERR_TRX_CANH_NO_WIRE; // generate CanHiNoWire suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "CanHiShortToBAT") == STR_EQUAL) {
|
||||
if (transceiver_processed)
|
||||
show_transc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_TRX; // generate Transceiver error
|
||||
frame.data[4] = CAN_ERR_TRX_CANH_SHORT_TO_BAT; // generate CanHiShortToBAT suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "CanHiShortToVCC") == STR_EQUAL) {
|
||||
if (transceiver_processed)
|
||||
show_transc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_TRX; // generate Transceiver error
|
||||
frame.data[4] = CAN_ERR_TRX_CANH_SHORT_TO_VCC; // generate CanHiShortToVCC suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "CanHiShortToGND") == STR_EQUAL) {
|
||||
if (transceiver_processed)
|
||||
show_transc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_TRX; // generate Transceiver error
|
||||
frame.data[4] = CAN_ERR_TRX_CANH_SHORT_TO_GND; // generate CanHiShortToGND suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "CanLoNoWire") == STR_EQUAL) {
|
||||
if (transceiver_processed)
|
||||
show_transc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_TRX; // generate Transceiver error
|
||||
frame.data[4] = CAN_ERR_TRX_CANL_NO_WIRE; // generate CanLoNoWire suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "CanLoShortToBAT") == STR_EQUAL) {
|
||||
if (transceiver_processed)
|
||||
show_transc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_TRX; // generate Transceiver error
|
||||
frame.data[4] = CAN_ERR_TRX_CANL_SHORT_TO_BAT; // generate CanLoShortToBAT suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "CanLoShortToVCC") == STR_EQUAL) {
|
||||
if (transceiver_processed)
|
||||
show_transc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_TRX; // generate Transceiver error
|
||||
frame.data[4] = CAN_ERR_TRX_CANL_SHORT_TO_VCC; // generate CanLoShortToVCC suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "CanLoShortToGND") == STR_EQUAL) {
|
||||
if (transceiver_processed)
|
||||
show_transc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_TRX; // generate Transceiver error
|
||||
frame.data[4] = CAN_ERR_TRX_CANL_SHORT_TO_GND; // generate CanLoShortToGND suberror
|
||||
location_processed = true;
|
||||
} else if (strcasecmp(argv[i], "CanLoShortToCanHi") == STR_EQUAL) {
|
||||
if (transceiver_processed)
|
||||
show_transc_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_TRX; // generate Transceiver error
|
||||
frame.data[4] = CAN_ERR_TRX_CANL_SHORT_TO_CANH; // generate CanLoShortToCanHi suberror
|
||||
location_processed = true;
|
||||
}
|
||||
// LostArBit=29 (Totallength=12)
|
||||
else if ((strlen(argv[i]) == 12) && // 'LostArBit=29'
|
||||
(argv[i][9] == '=') && // '='
|
||||
(argv[i][10] >= '0' && argv[i][10] <= '2') && // valid bits are from 00 to 29 (in decimal)
|
||||
(argv[i][11] >= '0' && argv[i][11] <= '9')) { // valid bits are from 00 to 29 (in decimal)
|
||||
unsigned char arb_bit_num = (argv[i][10] - '0') * 10 + argv[i][11] - '0'; // convert decimal bitnumber to byte
|
||||
argv[i][9] = 0; // terminate string for comparison
|
||||
if (strcasecmp(argv[i], "LostArBit") == STR_EQUAL) {
|
||||
if (arbitration_processed)
|
||||
show_arb_err_and_exit();
|
||||
frame.can_id |= CAN_ERR_LOSTARB; // generate LostArbitartionBit error
|
||||
frame.data[0] = arb_bit_num; // bitnumber
|
||||
arbitration_processed = true;
|
||||
} else {
|
||||
argv[i][9] = '='; // undo string termination
|
||||
show_invalid_option(argv[i]);
|
||||
}
|
||||
}
|
||||
// Data1=F4 (Totallength=8) // since this does not set any error bit, has to be combined with other errors
|
||||
else if ((strlen(argv[i]) == 8) && // 'Data1=F4'
|
||||
(argv[i][4] >= '0' && argv[i][4] <= '7') && // valid data bytes are from 0 to 7 (in decimal)
|
||||
(argv[i][5] == '=') && // '='
|
||||
((argv[i][6] >= '0' && argv[i][6] <= '9') || (argv[i][6] >= 'A' && argv[i][6] <= 'F')) && // first hexadecimal digit
|
||||
((argv[i][7] >= '0' && argv[i][7] <= '9') || (argv[i][6] >= 'A' && argv[i][6] <= 'F'))) { // second hexadecimal digit
|
||||
unsigned char data_byte_value, data_byte_no = 0;
|
||||
data_byte_no = argv[i][4] - '0'; // convert order number of data byte (Data1 to 1, Data2 to 2...)
|
||||
data_byte_value = 0;
|
||||
if (argv[i][6] >= 'A') // convert higher digit hexadecimal char to byte
|
||||
data_byte_value += (argv[i][6] - 'A' + 10) * 16;
|
||||
else
|
||||
data_byte_value += (argv[i][6] - '0') * 16;
|
||||
if (argv[i][7] >= 'A') // convert lower digit hexadecimal char to byte
|
||||
data_byte_value += (argv[i][7] - 'A' + 10);
|
||||
else
|
||||
data_byte_value += (argv[i][7] - '0');
|
||||
argv[i][4] = 0; // terminate string for comparison
|
||||
if (strcasecmp(argv[i], "Data") == STR_EQUAL) {
|
||||
if (transceiver_processed)
|
||||
show_transc_err_and_exit();
|
||||
frame.data[data_byte_no] = data_byte_value; // populate proper data byte
|
||||
arbitration_processed = true;
|
||||
} else {
|
||||
argv[i][4] = data_byte_no + '0'; // undo string termination
|
||||
show_invalid_option(argv[i]);
|
||||
}
|
||||
}
|
||||
// RxCount=F4 or TxCount=3A (Totallength=10)
|
||||
else if ((strlen(argv[i]) == 10) && // 'RxCounter=F4' or 'TxCounter=3A'
|
||||
(argv[i][7] == '=') && // '='
|
||||
((argv[i][8] >= '0' && argv[i][8] <= '9') || (argv[i][8] >= 'A' && argv[i][8] <= 'F')) && // first hexadecimal digit
|
||||
((argv[i][9] >= '0' && argv[i][9] <= '9') || (argv[i][9] >= 'A' && argv[i][9] <= 'F'))) { // second hexadecimal digit
|
||||
unsigned char counter_value = 0;
|
||||
if (argv[i][8] >= 'A') // convert higher digit hexadecimal char to byte
|
||||
counter_value += (argv[i][8] - 'A' + 10) * 16;
|
||||
else
|
||||
counter_value += (argv[i][8] - '0') * 16;
|
||||
if (argv[i][9] >= 'A') // convert lower digit hexadecimal char to byte
|
||||
counter_value += (argv[i][9] - 'A' + 10);
|
||||
else
|
||||
counter_value += (argv[i][9] - '0');
|
||||
argv[i][7] = 0; // terminate string for comparison
|
||||
if (strcasecmp(argv[i], "TxCount") == STR_EQUAL) {
|
||||
frame.can_id |= CAN_ERR_CNT; // generate TxCounter error
|
||||
frame.data[6] = counter_value; // populate proper data byte
|
||||
} else if (strcasecmp(argv[i], "RxCount") == STR_EQUAL) {
|
||||
frame.can_id |= CAN_ERR_CNT; // generate RxCounter error
|
||||
frame.data[7] = counter_value; // populate proper data byte
|
||||
} else {
|
||||
argv[i][7] = '='; // undo string termination
|
||||
show_invalid_option(argv[i]);
|
||||
}
|
||||
} else if (strcasecmp(argv[i], "ShowBits") == STR_EQUAL) // DEBUG helper
|
||||
show_bits = true; // Display frame as bits
|
||||
else
|
||||
show_invalid_option(argv[i]);
|
||||
}
|
||||
|
||||
if (show_bits == true) {
|
||||
printf("CAN ID = ");
|
||||
print_binary(frame.can_id);
|
||||
printf("\n");
|
||||
// printf("frame.can_dlc = %d\n", frame.can_dlc);
|
||||
printf("CAN Data = ");
|
||||
for (size_t i = 0; i < frame.can_dlc; i++)
|
||||
printf("%02X ", frame.data[i]);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// create socket
|
||||
if ((sock = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0)
|
||||
err_exit("Error while opening socket");
|
||||
|
||||
// set interface name
|
||||
strcpy(ifr.ifr_name, argv[1]); // can0, vcan0...
|
||||
if (ioctl(sock, SIOCGIFINDEX, &ifr) < 0) {
|
||||
sprintf(tmp_str, "Error setting CAN interface name %s", argv[1]);
|
||||
err_exit(tmp_str);
|
||||
}
|
||||
|
||||
// bind socket to the CAN interface
|
||||
addr.can_family = AF_CAN;
|
||||
addr.can_ifindex = ifr.ifr_ifindex;
|
||||
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
|
||||
err_exit("Error in socket bind");
|
||||
|
||||
// Send CAN error frame
|
||||
if (write(sock, &frame, sizeof(frame)) < 0)
|
||||
err_exit("Error writing to socket");
|
||||
else
|
||||
printf("CAN error frame sent\n");
|
||||
|
||||
close(sock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -180,7 +180,7 @@ static void print_usage(char *prg)
|
||||
fprintf(stderr, " -X (generate CAN XL CAN frames)\n");
|
||||
fprintf(stderr, " -R (generate RTR frames)\n");
|
||||
fprintf(stderr, " -8 (allow DLC values greater then 8 for Classic CAN frames)\n");
|
||||
fprintf(stderr, " -m (mix -e -f -b -E -R -X frames)\n");
|
||||
fprintf(stderr, " -m (mix CC [-e -R] FD [-f -b -E] XL [-X] on capable devices)\n");
|
||||
fprintf(stderr, " -I <mode> (CAN ID generation mode - see below)\n");
|
||||
fprintf(stderr, " -L <mode> (CAN data length code (dlc) generation mode - see below)\n");
|
||||
fprintf(stderr, " -D <mode> (CAN data (payload) generation mode - see below)\n");
|
||||
@@ -299,7 +299,7 @@ static int setsockopt_txtime(int fd)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_send_one(int fd, cu_t *cu, size_t len, int timeout)
|
||||
static int do_send_one(int fd, union cfu *cu, size_t len, int timeout)
|
||||
{
|
||||
uint8_t control[CMSG_SPACE(sizeof(uint64_t))] = { 0 };
|
||||
struct iovec iov = {
|
||||
@@ -339,10 +339,9 @@ resend:
|
||||
nbytes = sendmsg(fd, &msg, 0);
|
||||
if (nbytes < 0) {
|
||||
ret = -errno;
|
||||
if (ret != -ENOBUFS) {
|
||||
perror("write");
|
||||
if (ret != -ENOBUFS)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!ignore_enobufs && !timeout) {
|
||||
perror("write");
|
||||
return ret;
|
||||
@@ -455,6 +454,9 @@ int main(int argc, char **argv)
|
||||
unsigned char extended = 0;
|
||||
unsigned char canfd = 0;
|
||||
unsigned char canxl = 0;
|
||||
unsigned char mixcc = 1; /* mix default */
|
||||
unsigned char mixfd = 1; /* mix default */
|
||||
unsigned char mixxl = 1; /* mix default */
|
||||
unsigned char brs = 0;
|
||||
unsigned char esi = 0;
|
||||
unsigned char mix = 0;
|
||||
@@ -492,7 +494,7 @@ int main(int argc, char **argv)
|
||||
struct can_raw_vcid_options vcid_opts = {
|
||||
.flags = CAN_RAW_XL_VCID_TX_PASS,
|
||||
};
|
||||
static cu_t cu;
|
||||
static union cfu cu;
|
||||
int i;
|
||||
struct ifreq ifr = { 0 };
|
||||
const int enable_canfx = 1;
|
||||
@@ -574,7 +576,6 @@ int main(int argc, char **argv)
|
||||
|
||||
case 'm':
|
||||
mix = 1;
|
||||
canfd = 1; /* to switch the socket into CAN FD mode */
|
||||
view |= CANLIB_VIEW_INDENT_SFF;
|
||||
break;
|
||||
|
||||
@@ -777,14 +778,58 @@ int main(int argc, char **argv)
|
||||
&loopback, sizeof(loopback));
|
||||
}
|
||||
|
||||
if (canfd || canxl) {
|
||||
/* get CAN netdevice MTU for frame type capabilities */
|
||||
if (ioctl(s, SIOCGIFMTU, &ifr) < 0) {
|
||||
perror("SIOCGIFMTU");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* check if the frame fits into the CAN netdevice */
|
||||
if (ioctl(s, SIOCGIFMTU, &ifr) < 0) {
|
||||
perror("SIOCGIFMTU");
|
||||
/* check CAN XL support */
|
||||
if (ifr.ifr_mtu < (int)CANXL_MIN_MTU) {
|
||||
mixxl = 0;
|
||||
if (canxl) {
|
||||
printf("CAN interface not CAN XL capable - sorry.\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* check CAN FD support */
|
||||
if (ifr.ifr_mtu < (int)CANFD_MTU) {
|
||||
mixfd = 0;
|
||||
if (canfd) {
|
||||
printf("CAN interface not CAN FD capable - sorry.\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* enable CAN FD on the socket */
|
||||
if (mixfd) {
|
||||
/* interface is ok - try to switch the socket into CAN FD mode */
|
||||
if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES,
|
||||
&enable_canfx, sizeof(enable_canfx))){
|
||||
printf("error when enabling CAN FD support\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* enable CAN XL on the socket */
|
||||
if (mixxl) {
|
||||
/* interface is ok - try to switch the socket into CAN XL mode */
|
||||
if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_XL_FRAMES,
|
||||
&enable_canfx, sizeof(enable_canfx))){
|
||||
printf("error when enabling CAN XL support\n");
|
||||
return 1;
|
||||
}
|
||||
/* try to enable the CAN XL VCID pass through mode */
|
||||
if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_XL_VCID_OPTS,
|
||||
&vcid_opts, sizeof(vcid_opts))) {
|
||||
printf("error when enabling CAN XL VCID pass through\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* sanitize given values */
|
||||
if (canfd || canxl) {
|
||||
if (canfd) {
|
||||
/* ensure discrete CAN FD length values 0..8, 12, 16, 20, 24, 32, 64 */
|
||||
cu.fd.len = can_fd_dlc2len(can_fd_len2dlc(cu.fd.len));
|
||||
@@ -793,41 +838,6 @@ int main(int argc, char **argv)
|
||||
if (cu.fd.len > CANFD_MAX_DLEN)
|
||||
cu.fd.len = CANFD_MAX_DLEN;
|
||||
}
|
||||
|
||||
if (canxl && (ifr.ifr_mtu < (int)CANXL_MIN_MTU)) {
|
||||
printf("CAN interface not CAN XL capable - sorry.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (canfd && (ifr.ifr_mtu < (int)CANFD_MTU)) {
|
||||
printf("CAN interface not CAN FD capable - sorry.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (ifr.ifr_mtu == (int)CANFD_MTU) {
|
||||
/* interface is ok - try to switch the socket into CAN FD mode */
|
||||
if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES,
|
||||
&enable_canfx, sizeof(enable_canfx))){
|
||||
printf("error when enabling CAN FD support\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (ifr.ifr_mtu >= (int)CANXL_MIN_MTU) {
|
||||
/* interface is ok - try to switch the socket into CAN XL mode */
|
||||
if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_XL_FRAMES,
|
||||
&enable_canfx, sizeof(enable_canfx))){
|
||||
printf("error when enabling CAN XL support\n");
|
||||
return 1;
|
||||
}
|
||||
/* try to enable the CAN XL VCID pass through mode */
|
||||
if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_XL_VCID_OPTS,
|
||||
&vcid_opts, sizeof(vcid_opts))) {
|
||||
printf("error when enabling CAN XL VCID pass through\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
/* sanitize Classical CAN 2.0 frame length */
|
||||
if (len8_dlc) {
|
||||
@@ -987,12 +997,12 @@ int main(int argc, char **argv)
|
||||
rnd = random();
|
||||
|
||||
if (xl_flags_mode == MODE_RANDOM) {
|
||||
cu.xl.flags = rnd & CANXL_SEC;
|
||||
cu.xl.flags = rnd & (CANXL_SEC | CANXL_RRS);
|
||||
} else if (xl_flags_mode == MODE_FIX) {
|
||||
cu.xl.flags = xl_flags;
|
||||
} else if (xl_flags_mode == MODE_INCREMENT) {
|
||||
xl_flags ^= CANXL_SEC;
|
||||
cu.xl.flags = (xl_flags & CANXL_SEC);
|
||||
xl_flags++;
|
||||
cu.xl.flags = (xl_flags & (CANXL_SEC | CANXL_RRS));
|
||||
}
|
||||
|
||||
/* mark CAN XL frame */
|
||||
@@ -1040,8 +1050,31 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
ret = do_send_one(s, &cu, mtu, polltimeout);
|
||||
if (ret)
|
||||
if ((ret == -EINVAL) && mix) {
|
||||
/* mix mode: disable unsupported CAN frame type */
|
||||
switch (mtu) {
|
||||
case CAN_MTU:
|
||||
mixcc = 0;
|
||||
break;
|
||||
case CANFD_MTU:
|
||||
mixfd = 0;
|
||||
break;
|
||||
case CANXL_MTU:
|
||||
mixxl = 0;
|
||||
break;
|
||||
default:
|
||||
printf ("mix mode: unknown MTU");
|
||||
return 1;
|
||||
}
|
||||
if (!mixcc && !mixfd && !mixxl) {
|
||||
printf ("mix mode: no valid CAN frame types\n");
|
||||
return 1;
|
||||
}
|
||||
} else if (ret) {
|
||||
/* other error than -ENOBUFS and -EINVAL */
|
||||
perror("write");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (burst_sent_count >= burst_count)
|
||||
burst_sent_count = 0;
|
||||
@@ -1082,18 +1115,41 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
if (mix) {
|
||||
canfd = 0;
|
||||
canxl = 0;
|
||||
i = random();
|
||||
extended = i & 1;
|
||||
canfd = i & 2;
|
||||
if (canfd) {
|
||||
brs = i & 4;
|
||||
esi = i & 8;
|
||||
if (mixfd) {
|
||||
canfd = i & 2;
|
||||
if (canfd) {
|
||||
brs = i & 4;
|
||||
esi = i & 8;
|
||||
}
|
||||
}
|
||||
if (mixxl) {
|
||||
if (mixfd)
|
||||
canxl = ((i & 96) == 96); /* 1/4 */
|
||||
else
|
||||
canxl = ((i & 32) == 32); /* 1/2 */
|
||||
}
|
||||
if (mixcc) {
|
||||
rtr_frame = ((i & 24) == 24); /* reduce RTR to 1/4 */
|
||||
} else {
|
||||
/* no CC frames allowed - CAN XL-only mode? */
|
||||
if (!canxl && !canfd) {
|
||||
/* force XL or FD frames */
|
||||
if (mixxl)
|
||||
canxl = 1;
|
||||
else if (mixfd) {
|
||||
canfd = 1;
|
||||
brs = i & 4;
|
||||
esi = i & 8;
|
||||
} else {
|
||||
printf ("mix mode: no valid CAN frame types\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* generate CAN XL traffic if the interface is capable */
|
||||
if (ifr.ifr_mtu >= (int)CANXL_MIN_MTU)
|
||||
canxl = ((i & 96) == 96);
|
||||
|
||||
rtr_frame = ((i & 24) == 24); /* reduce RTR frames to 1/4 */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-6
@@ -177,7 +177,8 @@ int main(int argc, char **argv)
|
||||
sigset_t sigset;
|
||||
fd_set rdfs;
|
||||
int s[MAXDEV];
|
||||
int socki, accsocket;
|
||||
int socki;
|
||||
int accsocket = -1;
|
||||
canid_t mask[MAXDEV] = {0};
|
||||
canid_t value[MAXDEV] = {0};
|
||||
int inv_filter[MAXDEV] = {0};
|
||||
@@ -191,7 +192,7 @@ int main(int argc, char **argv)
|
||||
.rx_vcid_mask = 0,
|
||||
};
|
||||
struct can_filter rfilter;
|
||||
static cu_t cu; /* union for CAN CC/FD/XL frames */
|
||||
static union cfu cu; /* union for CAN CC/FD/XL frames */
|
||||
const int canfx_on = 1;
|
||||
int nbytes, i, j;
|
||||
struct ifreq ifr;
|
||||
@@ -286,7 +287,7 @@ int main(int argc, char **argv)
|
||||
inaddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
inaddr.sin_port = htons(port);
|
||||
|
||||
while(bind(socki, (struct sockaddr*)&inaddr, sizeof(inaddr)) < 0) {
|
||||
while(running && bind(socki, (struct sockaddr*)&inaddr, sizeof(inaddr)) < 0) {
|
||||
struct timespec f = {
|
||||
.tv_nsec = 100 * 1000 * 1000,
|
||||
};
|
||||
@@ -295,12 +296,17 @@ int main(int argc, char **argv)
|
||||
nanosleep(&f, NULL);
|
||||
}
|
||||
|
||||
if (!running) {
|
||||
close(socki);
|
||||
return 128 + signal_num;
|
||||
}
|
||||
|
||||
if (listen(socki, 3) != 0) {
|
||||
perror("listen");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
while(1) {
|
||||
while(running) {
|
||||
accsocket = accept(socki, (struct sockaddr*)&clientaddr, &sin_size);
|
||||
if (accsocket > 0) {
|
||||
//printf("accepted\n");
|
||||
@@ -314,11 +320,16 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
if (!running) {
|
||||
close(socki);
|
||||
return 128 + signal_num;
|
||||
}
|
||||
|
||||
for (i=0; i<currmax; i++) {
|
||||
|
||||
pr_debug("open %d '%s' m%08X v%08X i%d e%d.\n",
|
||||
i, argv[optind+i], mask[i], value[i],
|
||||
inv_filter[i], err_mask[i]);
|
||||
i, argv[optind+i], mask[i], value[i],
|
||||
inv_filter[i], err_mask[i]);
|
||||
|
||||
if ((s[i] = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
|
||||
perror("socket");
|
||||
|
||||
+23
-11
@@ -68,7 +68,7 @@
|
||||
#endif
|
||||
|
||||
#define DEVSZ 22 /* IFNAMSZ + 6 */
|
||||
#define TIMESZ sizeof("(1345212884.318850) ")
|
||||
#define TIMESZ sizeof("(1345212884.318850123) ")
|
||||
#define BUFSZ (TIMESZ + DEVSZ + AFRSZ)
|
||||
|
||||
/* adapt sscanf() functions below on error */
|
||||
@@ -261,7 +261,7 @@ int main(int argc, char **argv)
|
||||
struct can_raw_vcid_options vcid_opts = {
|
||||
.flags = CAN_RAW_XL_VCID_TX_PASS,
|
||||
};
|
||||
static cu_t cu;
|
||||
static union cfu cu;
|
||||
static struct timeval today_tv, log_tv, last_log_tv, diff_tv;
|
||||
struct timespec sleep_ts;
|
||||
int s; /* CAN_RAW socket */
|
||||
@@ -452,14 +452,20 @@ int main(int argc, char **argv)
|
||||
return 1;
|
||||
}
|
||||
log_tv.tv_sec = sec;
|
||||
log_tv.tv_usec = usec;
|
||||
|
||||
/*
|
||||
* ensure the fractions of seconds are 6 decimal places long to catch
|
||||
* ensure the fractions of seconds are 6 or 9 decimal places long to catch
|
||||
* 3rd party or handcrafted logfiles that treat the timestamp as float
|
||||
*/
|
||||
if (strchr(buf, ')') - strchr(buf, '.') != 7) {
|
||||
fprintf(stderr, "timestamp format in logfile requires 6 decimal places\n");
|
||||
switch (strchr(buf, ')') - strchr(buf, '.')) {
|
||||
case 7: //6
|
||||
log_tv.tv_usec = usec;
|
||||
break;
|
||||
case 10: //9
|
||||
log_tv.tv_usec = usec / 1000;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "timestamp format in logfile requires 6 or 9 decimal places\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -541,19 +547,25 @@ int main(int argc, char **argv)
|
||||
break;
|
||||
}
|
||||
|
||||
if (sscanf(buf, "(%llu.%llu) %s %s", &sec, &usec, device, afrbuf) != 4) {
|
||||
if (sscanf(buf, "(%llu.%llu) %21s %6299s", &sec, &usec, device, afrbuf) != 4) {
|
||||
fprintf(stderr, "incorrect line format in logfile\n");
|
||||
return 1;
|
||||
}
|
||||
log_tv.tv_sec = sec;
|
||||
log_tv.tv_usec = usec;
|
||||
|
||||
/*
|
||||
* ensure the fractions of seconds are 6 decimal places long to catch
|
||||
* ensure the fractions of seconds are 6 or 9 decimal places long to catch
|
||||
* 3rd party or handcrafted logfiles that treat the timestamp as float
|
||||
*/
|
||||
if (strchr(buf, ')') - strchr(buf, '.') != 7) {
|
||||
fprintf(stderr, "timestamp format in logfile requires 6 decimal places\n");
|
||||
switch (strchr(buf, ')') - strchr(buf, '.')) {
|
||||
case 7: //6
|
||||
log_tv.tv_usec = usec;
|
||||
break;
|
||||
case 10: //9
|
||||
log_tv.tv_usec = usec / 1000;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "timestamp format in logfile requires 6 or 9 decimal places\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,10 @@ static void print_usage(char *prg)
|
||||
"_{dlc}:\n"
|
||||
" an optional 9..F data length code value when payload length is 8\n"
|
||||
"<flags>:\n"
|
||||
" a single ASCII Hex value (0 .. F) which defines canfd_frame.flags\n"
|
||||
" a single ASCII Hex value (0 .. F) which defines canfd_frame.flags:\n"
|
||||
" %x CANFD_BRS\n"
|
||||
" %x CANFD_ESI\n"
|
||||
" %x CANFD_FDF\n"
|
||||
"\n"
|
||||
"<vcid>:\n"
|
||||
" 2 hex chars - virtual CAN network identifier (00 .. FF)\n"
|
||||
@@ -100,7 +103,8 @@ static void print_usage(char *prg)
|
||||
" 1F334455#1122334455667788_B / 123#R / 00000123#R3 / 333#R8_E /\n"
|
||||
" 45123#81:00:12345678#11223344.556677 / 00242#81:07:40000123#112233\n"
|
||||
"\n",
|
||||
prg, prg);
|
||||
prg, prg,
|
||||
CANFD_BRS, CANFD_ESI, CANFD_FDF);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
@@ -113,7 +117,7 @@ int main(int argc, char **argv)
|
||||
struct can_raw_vcid_options vcid_opts = {
|
||||
.flags = CAN_RAW_XL_VCID_TX_PASS,
|
||||
};
|
||||
static cu_t cu;
|
||||
static union cfu cu;
|
||||
struct ifreq ifr;
|
||||
|
||||
/* check command line options */
|
||||
|
||||
+11
-11
@@ -161,15 +161,15 @@ void switchvdl(char *delim)
|
||||
|
||||
int comp(const void *elem1, const void *elem2)
|
||||
{
|
||||
unsigned long f = ((struct snif*)elem1)->current.can_id;
|
||||
unsigned long s = ((struct snif*)elem2)->current.can_id;
|
||||
unsigned long f = ((struct snif*)elem1)->current.can_id;
|
||||
unsigned long s = ((struct snif*)elem2)->current.can_id;
|
||||
|
||||
if (f > s)
|
||||
return 1;
|
||||
if (f < s)
|
||||
return -1;
|
||||
if (f > s)
|
||||
return 1;
|
||||
if (f < s)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void print_usage(char *prg)
|
||||
@@ -741,10 +741,10 @@ int handle_timeo(long currcms)
|
||||
do_clr(i, UPDATE);
|
||||
}
|
||||
else if ((sniftab[i].hold) && (sniftab[i].hold < currcms)) {
|
||||
memset(&sniftab[i].marker.data, 0, max_dlen);
|
||||
print_snifline(i);
|
||||
sniftab[i].hold = 0; /* disable update by hold */
|
||||
}
|
||||
memset(&sniftab[i].marker.data, 0, max_dlen);
|
||||
print_snifline(i);
|
||||
sniftab[i].hold = 0; /* disable update by hold */
|
||||
}
|
||||
else
|
||||
printf("%s", CSR_DOWN); /* skip my line */
|
||||
|
||||
|
||||
@@ -671,6 +671,10 @@ int main(int argc, char *argv[])
|
||||
|
||||
while (1) {
|
||||
ret = isobusfs_cli_process_events_and_tasks(priv);
|
||||
if (ret == ISOBUSFS_CLI_RET_EXIT) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
#define ISOBUSFS_CLI_MAX_EPOLL_EVENTS 10
|
||||
#define ISOBUSFS_CLI_DEFAULT_WAIT_TIMEOUT_MS 1000 /* ms */
|
||||
|
||||
/* internel return codes, not errno values */
|
||||
#define ISOBUSFS_CLI_RET_EXIT 1
|
||||
|
||||
enum isobusfs_cli_state {
|
||||
ISOBUSFS_CLI_STATE_CONNECTING,
|
||||
ISOBUSFS_CLI_STATE_IDLE,
|
||||
|
||||
+70
-30
@@ -16,6 +16,12 @@
|
||||
#define MAX_COMMAND_LENGTH 256
|
||||
|
||||
#define MAX_DISPLAY_FILENAME_LENGTH 100
|
||||
/*
|
||||
* ISO 11783-13:2021 B.21 minimal directory entry payload size in bytes:
|
||||
* 1 (name length) + 1 (min name byte) + 1 (attributes) +
|
||||
* 2 (date) + 2 (time) + 4 (size).
|
||||
*/
|
||||
#define ISOBUSFS_MIN_DIR_ENTRY_SIZE (1 + 1 + 1 + 2 + 2 + 4)
|
||||
|
||||
struct command_mapping {
|
||||
const char *command;
|
||||
@@ -66,8 +72,8 @@ static int cmd_help(struct isobusfs_priv *priv, const char *options)
|
||||
static int cmd_exit(struct isobusfs_priv *priv, const char *options)
|
||||
{
|
||||
pr_int("exit interactive mode\n");
|
||||
/* Return -EINTR to indicate the program should exit */
|
||||
return -EINTR;
|
||||
|
||||
return ISOBUSFS_CLI_RET_EXIT;
|
||||
}
|
||||
|
||||
static int cmd_dmesg(struct isobusfs_priv *priv, const char *options)
|
||||
@@ -510,8 +516,12 @@ isobusfs_cli_ls_handle_open_dir_sent(struct isobusfs_priv *priv,
|
||||
|
||||
ctx->handle = res->handle;
|
||||
|
||||
ctx->offset = 0;
|
||||
ctx->entry_count = 0;
|
||||
|
||||
ret = isobusfs_cli_send_and_register_fa_sf_event(priv, ctx->handle,
|
||||
0, ctx->entry_count,
|
||||
ISOBUSFS_FA_SEEK_SET,
|
||||
ctx->offset,
|
||||
cb, ctx);
|
||||
if (ret)
|
||||
pr_int("Failed to send seek file request: %i\n", ret);
|
||||
@@ -530,7 +540,7 @@ isobusfs_cli_ls_handle_seek_dir_sent(struct isobusfs_priv *priv,
|
||||
{
|
||||
isobusfs_event_callback cb = isobusfs_cli_ls_event_callback;
|
||||
struct isobusfs_fa_seekf_res *res =
|
||||
(struct isobusfs_fa_seekf_res *)msg;
|
||||
(struct isobusfs_fa_seekf_res *)msg->buf;
|
||||
uint16_t count;
|
||||
int ret;
|
||||
|
||||
@@ -543,8 +553,10 @@ isobusfs_cli_ls_handle_seek_dir_sent(struct isobusfs_priv *priv,
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* set max possible number fitting in to 16bits */
|
||||
count = UINT16_MAX;
|
||||
/* ISO 11783-13:2021 C.3.5.2: count is number of directory entries. */
|
||||
count = ISOBUSFS_MAX_DATA_LENGH / ISOBUSFS_MIN_DIR_ENTRY_SIZE;
|
||||
if (!count)
|
||||
count = 1;
|
||||
ctx->request_count = count;
|
||||
|
||||
ret = isobusfs_cli_send_and_register_fa_rf_event(priv, ctx->handle,
|
||||
@@ -616,8 +628,8 @@ static bool isobusfs_cli_extract_directory_entry(const uint8_t *buffer,
|
||||
uint16_t *file_time,
|
||||
uint32_t *file_size)
|
||||
{
|
||||
size_t entry_total_len, copy_len;
|
||||
uint8_t filename_length;
|
||||
size_t entry_total_len;
|
||||
|
||||
if (*pos + 2 > buffer_length) {
|
||||
pr_int("Error: Incomplete data in buffer\n");
|
||||
@@ -633,7 +645,13 @@ static bool isobusfs_cli_extract_directory_entry(const uint8_t *buffer,
|
||||
}
|
||||
|
||||
(*pos)++;
|
||||
strncpy(filename, (const char *)buffer + *pos, filename_length);
|
||||
|
||||
if (filename_length > ISOBUSFS_MAX_DIR_ENTRY_NAME_LENGTH)
|
||||
copy_len = ISOBUSFS_MAX_DIR_ENTRY_NAME_LENGTH;
|
||||
else
|
||||
copy_len = filename_length;
|
||||
|
||||
strncpy(filename, (const char *)buffer + *pos, copy_len);
|
||||
filename[filename_length] = '\0';
|
||||
*pos += filename_length;
|
||||
if (filename_length > MAX_DISPLAY_FILENAME_LENGTH) {
|
||||
@@ -688,15 +706,17 @@ isobusfs_cli_print_directory_entry(struct isobusfs_cli_ls_context *ctx,
|
||||
static void
|
||||
isobusfs_cli_print_directory_entries(struct isobusfs_cli_ls_context *ctx,
|
||||
const uint8_t *buffer,
|
||||
size_t buffer_length)
|
||||
size_t buffer_length,
|
||||
uint16_t max_entries)
|
||||
{
|
||||
char filename[ISOBUSFS_MAX_DIR_ENTRY_NAME_LENGTH + 1];
|
||||
uint16_t file_date, file_time;
|
||||
uint32_t file_size;
|
||||
uint8_t attributes;
|
||||
size_t pos = 0;
|
||||
uint16_t entries = 0;
|
||||
|
||||
while (pos < buffer_length) {
|
||||
while (pos < buffer_length && entries < max_entries) {
|
||||
if (!isobusfs_cli_extract_directory_entry(buffer, buffer_length,
|
||||
&pos, filename,
|
||||
&attributes,
|
||||
@@ -709,6 +729,7 @@ isobusfs_cli_print_directory_entries(struct isobusfs_cli_ls_context *ctx,
|
||||
file_date, file_time,
|
||||
file_size);
|
||||
ctx->entry_count++;
|
||||
entries++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -721,26 +742,35 @@ isobusfs_cli_ls_handle_read_dir_sent(struct isobusfs_priv *priv,
|
||||
(struct isobusfs_read_file_response *)msg->buf;
|
||||
size_t buffer_length = msg->len - sizeof(*res);
|
||||
isobusfs_event_callback cb;
|
||||
size_t entries_before;
|
||||
size_t entries_in_message;
|
||||
uint16_t count;
|
||||
int ret;
|
||||
|
||||
pr_debug("< rx: Read File Response. Error code: %i", res->error_code);
|
||||
|
||||
if (isobusfs_cli_int_is_error(priv, 0, res->error_code, res->tan))
|
||||
goto error;
|
||||
|
||||
count = le16toh(res->count);
|
||||
if (count && count != buffer_length) {
|
||||
pr_int("Buffer length mismatch: %u != %zu\n", count,
|
||||
buffer_length);
|
||||
if (count && buffer_length) {
|
||||
entries_before = ctx->entry_count;
|
||||
isobusfs_cli_print_directory_entries(ctx, res->data,
|
||||
buffer_length, count);
|
||||
entries_in_message = ctx->entry_count - entries_before;
|
||||
} else {
|
||||
entries_in_message = 0;
|
||||
}
|
||||
|
||||
if (count != entries_in_message) {
|
||||
pr_int("Directory entry count mismatch: %u != %zu\n", count,
|
||||
entries_in_message);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (count)
|
||||
isobusfs_cli_print_directory_entries(ctx, res->data,
|
||||
buffer_length);
|
||||
|
||||
cb = isobusfs_cli_ls_event_callback;
|
||||
if (count) {
|
||||
|
||||
if (res->error_code == ISOBUSFS_ERR_END_OF_FILE) {
|
||||
ret = isobusfs_cli_send_and_register_fa_cf_event(priv,
|
||||
ctx->handle,
|
||||
cb, ctx);
|
||||
@@ -750,21 +780,31 @@ isobusfs_cli_ls_handle_read_dir_sent(struct isobusfs_priv *priv,
|
||||
}
|
||||
|
||||
ctx->state = ISOBUSFS_CLI_LS_STATE_CLOSE_DIR_SENT;
|
||||
} else {
|
||||
ctx->offset = ctx->entry_count;
|
||||
ret = isobusfs_cli_send_and_register_fa_sf_event(priv,
|
||||
ctx->handle, 0,
|
||||
ctx->offset,
|
||||
cb, ctx);
|
||||
if (ret)
|
||||
pr_int("Failed to send seek file request: %i\n", ret);
|
||||
|
||||
ctx->state = ISOBUSFS_CLI_LS_STATE_SEEK_DIR_SENT;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!count) {
|
||||
pr_int("Error: zero-length read without EOF\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/*
|
||||
* Directory seek offset is entry index, not byte offset.
|
||||
* Server side seek rewinds and skips "offset" entries.
|
||||
*/
|
||||
ctx->offset = ctx->entry_count;
|
||||
|
||||
ret = isobusfs_cli_send_and_register_fa_sf_event(priv,
|
||||
ctx->handle, 0,
|
||||
ctx->offset,
|
||||
cb, ctx);
|
||||
if (ret)
|
||||
pr_int("Failed to send seek file request: %i\n", ret);
|
||||
|
||||
ctx->state = ISOBUSFS_CLI_LS_STATE_SEEK_DIR_SENT;
|
||||
|
||||
return;
|
||||
error:
|
||||
|
||||
ctx->state = ISOBUSFS_CLI_LS_STATE_ERROR;
|
||||
}
|
||||
|
||||
@@ -1077,7 +1117,7 @@ int isobusfs_cli_interactive(struct isobusfs_priv *priv)
|
||||
}
|
||||
}
|
||||
|
||||
pr_int("unknown comand\n");
|
||||
pr_int("unknown command\n");
|
||||
isobusfs_cli_promt(priv);
|
||||
} else {
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK)
|
||||
|
||||
@@ -211,7 +211,7 @@ static struct isobusfs_cli_test_dir_path test_dir_patterns[] = {
|
||||
{ "~tilde_dir", true },
|
||||
/* expected result \\vol1\dir1\~\ */
|
||||
{ "\\\\vol1\\dir1\\~", true },
|
||||
/* expected result \\vol1\~\ not clear if it is manufacture speficic dir */
|
||||
/* expected result \\vol1\~\ not clear if it is manufacture specific dir */
|
||||
{ "\\~\\", true },
|
||||
/* expected result \\~\ */
|
||||
{ "\\\\~\\", false },
|
||||
@@ -603,23 +603,24 @@ struct isobusfs_cli_test_rf_path {
|
||||
uint8_t flags;
|
||||
uint32_t offset;
|
||||
uint32_t read_size;
|
||||
uint32_t expected_size;
|
||||
bool expect_pass;
|
||||
};
|
||||
|
||||
static struct isobusfs_cli_test_rf_path test_rf_patterns[] = {
|
||||
/* expected result \\vol1\dir1\dir2\ */
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 0, 0, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 0, 1, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 1, 1, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 2, 1, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 3, 1, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, 8, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, 8 * 100, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 100, 8 * 100, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, ISOBUSFS_MAX_DATA_LENGH, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, (ISOBUSFS_MAX_DATA_LENGH & ~3) + 16, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, ISOBUSFS_MAX_DATA_LENGH + 1, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, -1, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 0, 0, 0, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 0, 1, 1, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 1, 1, 1, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 2, 1, 1, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 3, 1, 1, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, 8, 8, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, 8 * 100, 8 * 100, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 100, 8 * 100, 8 * 100, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, ISOBUSFS_MAX_DATA_LENGH, ISOBUSFS_MAX_DATA_LENGH, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, (ISOBUSFS_MAX_DATA_LENGH & ~3) + 16, (ISOBUSFS_MAX_DATA_LENGH & ~3) + 16, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, ISOBUSFS_MAX_DATA_LENGH + 1, ISOBUSFS_MAX_DATA_LENGH + 1, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, UINT32_MAX, 1024 * 1024, true },
|
||||
};
|
||||
|
||||
size_t current_rf_pattern_test;
|
||||
@@ -825,11 +826,20 @@ static int isobusfs_cli_test_rf_req(struct isobusfs_priv *priv, bool *complete)
|
||||
goto test_fail;
|
||||
test_start_time = current_time;
|
||||
break;
|
||||
} else if (remaining_size > 0 && priv->read_data_len == 0 && tp->expect_pass) {
|
||||
pr_err("read test failed: %s. Read size is zero, but expected more data: %zd",
|
||||
tp->path_name, remaining_size);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
} else if (remaining_size > 0 && priv->read_data_len == 0) {
|
||||
if (tp->read_size > tp->expected_size &&
|
||||
tp->read_size - remaining_size == tp->expected_size) {
|
||||
/* this is acceptable case when read size
|
||||
* is larger than actual file size
|
||||
*/
|
||||
pr_info("read test passed: %s. Reached end of file as expected.",
|
||||
tp->path_name);
|
||||
} else if (tp->expect_pass) {
|
||||
pr_err("read test failed: %s. Read size is too small, but expected more data: %zd",
|
||||
tp->path_name, remaining_size);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* fall troth */
|
||||
|
||||
@@ -611,7 +611,7 @@ int isobusfs_cmn_connect_socket(int sock, struct sockaddr_can *addr)
|
||||
}
|
||||
|
||||
/* FIXME: linger is currently not supported by the kernel J1939 stack
|
||||
* but it would be nice to have it. Especially if we wont to stop sending
|
||||
* but it would be nice to have it. Especially if we won't to stop sending
|
||||
* messages on a socket when the connection is lost.
|
||||
*/
|
||||
int isobusfs_cmn_set_linger(int sock)
|
||||
|
||||
@@ -23,7 +23,7 @@ int isobusfs_cmn_dh_validate_dir_path(const char *path, bool writable)
|
||||
ret = access(path, mode);
|
||||
if (ret == -1) {
|
||||
ret = -errno;
|
||||
pr_err("failed to acces path %s, for read %s. %s", path,
|
||||
pr_err("failed to access path %s, for read %s. %s", path,
|
||||
writable ? "and write" : "", strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ static int isobusfs_srv_rx_fs(struct isobusfs_srv_priv *priv,
|
||||
cg);
|
||||
|
||||
/* ISO 11783-13:2021 - Annex C.1.1 Overview:
|
||||
* If a client sends a command, which is not defined withing this
|
||||
* If a client sends a command, which is not defined within this
|
||||
* documentation, the file server shall respond with a
|
||||
* NACK (ISO 11783-3:2018 Chapter 5.4.5)
|
||||
*/
|
||||
@@ -279,7 +279,7 @@ static int isobusfs_srv_sock_fss_prepare(struct isobusfs_srv_priv *priv)
|
||||
return ret;
|
||||
|
||||
/* keep address and name and overwrite PGN */
|
||||
/* TOOD: actually, this is PGN input filter. Should we use different
|
||||
/* TODO: actually, this is PGN input filter. Should we use different
|
||||
* PGN?
|
||||
*/
|
||||
addr.can_addr.j1939.pgn = ISOBUSFS_PGN_CL_TO_FS;
|
||||
@@ -720,7 +720,7 @@ static int isobusfs_srv_parse_args(struct isobusfs_srv_priv *priv, int argc,
|
||||
}
|
||||
|
||||
if (!local_name_set)
|
||||
pr_warn("local name is not set. Wont be able to generate proper manufacturer-specific directory name. Falling mack to MCMC0000");
|
||||
pr_warn("local name is not set. Won't be able to generate proper manufacturer-specific directory name. Falling mack to MCMC0000");
|
||||
isobusfs_srv_generate_mfs_dir_name(priv);
|
||||
|
||||
pr_debug("Server configuration:");
|
||||
|
||||
@@ -662,8 +662,8 @@ static int isobusfs_srv_dh_ccd_res(struct isobusfs_srv_priv *priv,
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* We assime, the relative path stored in res->name is not longer
|
||||
* than absolue path
|
||||
* We assume, the relative path stored in res->name is not longer
|
||||
* than absolute path
|
||||
*/
|
||||
if (req->name_len > ISOBUSFS_SRV_MAX_PATH_LEN) {
|
||||
pr_warn("path too long");
|
||||
@@ -697,7 +697,7 @@ static int isobusfs_srv_dh_ccd_res(struct isobusfs_srv_priv *priv,
|
||||
process_error:
|
||||
if (ret < 0) {
|
||||
/* linux_error_to_isobusfs_error() can't distinguish between
|
||||
* -EINVAL vor SRC and DST, so we have to do it manually.
|
||||
* -EINVAL for SRC and DST, so we have to do it manually.
|
||||
*/
|
||||
if (ret == -EINVAL)
|
||||
error_code = ISOBUSFS_ERR_INVALID_DST_NAME;
|
||||
|
||||
+181
-47
@@ -495,15 +495,119 @@ static int check_access_with_base(const char *base_dir,
|
||||
return access(full_path, mode);
|
||||
}
|
||||
|
||||
/*
|
||||
* ISO 11783-13:2021 B.21 and B.15:
|
||||
* Filters directory entries that can be returned in a Read File response
|
||||
* for directory handles while collecting the attributes and name length.
|
||||
*/
|
||||
/**
|
||||
* isobusfs_srv_dir_entry_visible() - Filter visible directory entries.
|
||||
* @handle: Directory handle for access checks.
|
||||
* @entry: Directory entry to inspect.
|
||||
* @file_stat: Stat buffer to fill for the entry.
|
||||
* @entry_name_len: Returns entry name length on success.
|
||||
* @attributes: Returns computed attributes on success.
|
||||
*
|
||||
* ISO 11783-13:2021 B.21 and B.15 define the directory entry layout and
|
||||
* attributes. This helper skips entries that are not readable or too long
|
||||
* and returns attributes for the entry that will be serialized.
|
||||
*
|
||||
* Return: true when the entry should be emitted, false otherwise.
|
||||
*/
|
||||
static bool isobusfs_srv_dir_entry_visible(struct isobusfs_srv_handles *handle,
|
||||
const struct dirent *entry,
|
||||
struct stat *file_stat,
|
||||
size_t *entry_name_len,
|
||||
uint8_t *attributes)
|
||||
{
|
||||
if (check_access_with_base(handle->path, entry->d_name, R_OK) != 0)
|
||||
return false;
|
||||
|
||||
if (fstatat(handle->fd, entry->d_name, file_stat, 0) < 0)
|
||||
return false;
|
||||
|
||||
*entry_name_len = strlen(entry->d_name);
|
||||
if (*entry_name_len > ISOBUSFS_MAX_DIR_ENTRY_NAME_LENGTH)
|
||||
return false;
|
||||
|
||||
if (S_ISDIR(file_stat->st_mode))
|
||||
*attributes |= ISOBUSFS_ATTR_DIRECTORY;
|
||||
if (check_access_with_base(handle->path, entry->d_name, W_OK) != 0)
|
||||
*attributes |= ISOBUSFS_ATTR_READ_ONLY;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* ISO 11783-13:2021 C.3.4.2 and C.3.5.2:
|
||||
* Directory offsets/counts are in entries, so advance the directory stream
|
||||
* by visible entries only and report EOF if the entry offset is past the end.
|
||||
*/
|
||||
/**
|
||||
* isobusfs_srv_dir_skip_entries() - Advance directory stream by entries.
|
||||
* @handle: Directory handle to reposition.
|
||||
* @offset: Entry offset to seek to.
|
||||
*
|
||||
* ISO 11783-13:2021 C.3.4.2 and C.3.5.2 state directory offsets/counts are in
|
||||
* entries. This helper advances by visible entries only.
|
||||
*
|
||||
* Return: ISOBUSFS_ERR_SUCCESS or a protocol error code.
|
||||
*/
|
||||
static int isobusfs_srv_dir_skip_entries(struct isobusfs_srv_handles *handle,
|
||||
int32_t offset)
|
||||
{
|
||||
struct dirent *entry;
|
||||
int32_t skipped = 0;
|
||||
|
||||
rewinddir(handle->dir);
|
||||
|
||||
while (skipped < offset && (entry = readdir(handle->dir)) != NULL) {
|
||||
struct stat file_stat;
|
||||
size_t entry_name_len = 0;
|
||||
uint8_t attributes = 0;
|
||||
|
||||
if (!isobusfs_srv_dir_entry_visible(handle, entry, &file_stat,
|
||||
&entry_name_len,
|
||||
&attributes))
|
||||
continue;
|
||||
|
||||
skipped++;
|
||||
}
|
||||
|
||||
if (offset > 0 && skipped < offset)
|
||||
return ISOBUSFS_ERR_END_OF_FILE;
|
||||
|
||||
return ISOBUSFS_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* isobusfs_srv_read_directory() - Read directory entries into a response buffer.
|
||||
* @handle: Directory handle to read.
|
||||
* @buffer: Output buffer for directory entries.
|
||||
* @max_bytes: Maximum payload bytes allowed in the response.
|
||||
* @max_entries: Maximum number of entries to return.
|
||||
* @readed_size: Returns payload size in bytes.
|
||||
* @entries_read: Returns number of entries serialized.
|
||||
*
|
||||
* ISO 11783-13:2021 C.3.5.2: directory Count is entry based. This function
|
||||
* encodes up to @max_entries entries while respecting @max_bytes.
|
||||
*
|
||||
* Return: ISOBUSFS_ERR_SUCCESS or a protocol error code.
|
||||
*/
|
||||
static int isobusfs_srv_read_directory(struct isobusfs_srv_handles *handle,
|
||||
uint8_t *buffer, size_t count,
|
||||
ssize_t *readed_size)
|
||||
uint8_t *buffer, size_t max_bytes,
|
||||
uint16_t max_entries,
|
||||
ssize_t *readed_size,
|
||||
uint16_t *entries_read)
|
||||
{
|
||||
DIR *dir = handle->dir;
|
||||
struct dirent *entry;
|
||||
size_t pos = 0;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* ISO 11783-13:2021 C.3.5.2:
|
||||
* Directory offsets are entry indices, not byte positions.
|
||||
* Position the directory stream to the previously stored offset (handle->dir_pos).
|
||||
*
|
||||
* Handling Changes in Directory Contents:
|
||||
@@ -519,13 +623,12 @@ static int isobusfs_srv_read_directory(struct isobusfs_srv_handles *handle,
|
||||
* either returning an error or restarting from the beginning of the directory, depending
|
||||
* on the application's requirements.
|
||||
*/
|
||||
for (int i = 0; i < handle->dir_pos &&
|
||||
(entry = readdir(dir)) != NULL; i++) {
|
||||
/* Iterating to the desired position */
|
||||
}
|
||||
ret = isobusfs_srv_dir_skip_entries(handle, handle->dir_pos);
|
||||
if (ret != ISOBUSFS_ERR_SUCCESS)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Directory Entry Layout:
|
||||
* Directory Entry Layout (ISO 11783-13:2021 B.21):
|
||||
* This loop reads directory entries and encodes them into a buffer.
|
||||
* Each entry in the buffer follows the format specified in ISO 11783-13:2021.
|
||||
*
|
||||
@@ -550,26 +653,23 @@ static int isobusfs_srv_read_directory(struct isobusfs_srv_handles *handle,
|
||||
* The handle->dir_pos is incremented after processing each entry, marking
|
||||
* the current position in the directory stream for subsequent reads.
|
||||
*/
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
*entries_read = 0;
|
||||
while ((entry = readdir(dir)) != NULL &&
|
||||
(*entries_read) < max_entries) {
|
||||
size_t entry_name_len, entry_total_len;
|
||||
__le16 file_date, file_time;
|
||||
uint8_t attributes = 0;
|
||||
struct stat file_stat;
|
||||
uint8_t attributes = 0;
|
||||
__le32 size;
|
||||
|
||||
if (check_access_with_base(handle->path, entry->d_name, R_OK) != 0)
|
||||
continue; /* Skip this entry if it's not readable */
|
||||
|
||||
if (fstatat(handle->fd, entry->d_name, &file_stat, 0) < 0)
|
||||
continue; /* Skip this entry on error */
|
||||
|
||||
entry_name_len = strlen(entry->d_name);
|
||||
if (entry_name_len > ISOBUSFS_MAX_DIR_ENTRY_NAME_LENGTH)
|
||||
if (!isobusfs_srv_dir_entry_visible(handle, entry, &file_stat,
|
||||
&entry_name_len,
|
||||
&attributes))
|
||||
continue;
|
||||
|
||||
entry_total_len = 1 + entry_name_len + 1 + 2 + 2 + 4;
|
||||
|
||||
if (pos + entry_total_len > count)
|
||||
if (pos + entry_total_len > max_bytes)
|
||||
break;
|
||||
|
||||
buffer[pos++] = (uint8_t)entry_name_len;
|
||||
@@ -577,10 +677,6 @@ static int isobusfs_srv_read_directory(struct isobusfs_srv_handles *handle,
|
||||
memcpy(buffer + pos, entry->d_name, entry_name_len);
|
||||
pos += entry_name_len;
|
||||
|
||||
if (S_ISDIR(file_stat.st_mode))
|
||||
attributes |= ISOBUSFS_ATTR_DIRECTORY;
|
||||
if (check_access_with_base(handle->path, entry->d_name, W_OK) != 0)
|
||||
attributes |= ISOBUSFS_ATTR_READ_ONLY;
|
||||
buffer[pos++] = attributes;
|
||||
|
||||
file_date = htole16(convert_to_file_date(file_stat.st_mtime));
|
||||
@@ -594,9 +690,11 @@ static int isobusfs_srv_read_directory(struct isobusfs_srv_handles *handle,
|
||||
size = htole32(file_stat.st_size);
|
||||
memcpy(buffer + pos, &size, sizeof(size));
|
||||
pos += sizeof(size);
|
||||
(*entries_read)++;
|
||||
}
|
||||
|
||||
*readed_size = pos;
|
||||
handle->dir_pos += *entries_read;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -609,13 +707,18 @@ static int isobusfs_srv_fa_rf_req(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_srv_client *client;
|
||||
struct isobusfs_fa_readf_req *req;
|
||||
ssize_t readed_size = 0;
|
||||
uint16_t entries_read = 0;
|
||||
uint8_t error_code = 0;
|
||||
ssize_t send_size;
|
||||
bool res_allocated = false;
|
||||
size_t max_bytes = 0;
|
||||
uint16_t count = 0;
|
||||
int ret = 0;
|
||||
int count;
|
||||
bool is_dir = false;
|
||||
|
||||
req = (struct isobusfs_fa_readf_req *)msg->buf;
|
||||
count = le16toh(req->count);
|
||||
res = (struct isobusfs_read_file_response *)&res_fail[0];
|
||||
|
||||
pr_debug("< rx: Read File Request. tan: %d, handle: %d, count: %d",
|
||||
req->tan, req->handle, count);
|
||||
@@ -627,17 +730,6 @@ static int isobusfs_srv_fa_rf_req(struct isobusfs_srv_priv *priv,
|
||||
* TODO: currently we are not able to detect support transport mode,
|
||||
* so ETP is assumed.
|
||||
*/
|
||||
if (count > ISOBUSFS_MAX_DATA_LENGH)
|
||||
count = ISOBUSFS_MAX_DATA_LENGH;
|
||||
|
||||
res = malloc(sizeof(*res) + count);
|
||||
if (!res) {
|
||||
pr_warn("failed to allocate memory");
|
||||
res = (struct isobusfs_read_file_response *)&res_fail[0];
|
||||
error_code = ISOBUSFS_ERR_OUT_OF_MEM;
|
||||
goto send_response;
|
||||
}
|
||||
|
||||
client = isobusfs_srv_get_client_by_msg(priv, msg);
|
||||
if (!client) {
|
||||
pr_warn("client not found");
|
||||
@@ -649,12 +741,33 @@ static int isobusfs_srv_fa_rf_req(struct isobusfs_srv_priv *priv,
|
||||
if (!handle) {
|
||||
pr_warn("failed to find file with handle: %x", req->handle);
|
||||
error_code = ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
|
||||
goto send_response;
|
||||
}
|
||||
|
||||
/* Determine whether to read a file or a directory */
|
||||
if (handle->dir) {
|
||||
ret = isobusfs_srv_read_directory(handle, res->data, count,
|
||||
&readed_size);
|
||||
/* ISO 11783-13:2021 C.3.5.2: count is entry count for directories. */
|
||||
is_dir = true;
|
||||
max_bytes = ISOBUSFS_MAX_DATA_LENGH;
|
||||
} else {
|
||||
if (count > ISOBUSFS_MAX_DATA_LENGH)
|
||||
count = ISOBUSFS_MAX_DATA_LENGH;
|
||||
max_bytes = count;
|
||||
}
|
||||
|
||||
res = malloc(sizeof(*res) + max_bytes);
|
||||
if (!res) {
|
||||
pr_warn("failed to allocate memory");
|
||||
res = (struct isobusfs_read_file_response *)&res_fail[0];
|
||||
error_code = ISOBUSFS_ERR_OUT_OF_MEM;
|
||||
goto send_response;
|
||||
}
|
||||
res_allocated = true;
|
||||
|
||||
if (is_dir) {
|
||||
ret = isobusfs_srv_read_directory(handle, res->data, max_bytes,
|
||||
count, &readed_size,
|
||||
&entries_read);
|
||||
} else {
|
||||
ret = isobusfs_srv_read_file(handle, res->data, count,
|
||||
&readed_size);
|
||||
@@ -663,7 +776,10 @@ static int isobusfs_srv_fa_rf_req(struct isobusfs_srv_priv *priv,
|
||||
if (ret < 0) {
|
||||
error_code = ret;
|
||||
readed_size = 0;
|
||||
} else if (count != 0 && readed_size == 0) {
|
||||
entries_read = 0;
|
||||
} else if (count != 0 &&
|
||||
((is_dir && entries_read == 0) ||
|
||||
(!is_dir && readed_size == 0))) {
|
||||
error_code = ISOBUSFS_ERR_END_OF_FILE;
|
||||
}
|
||||
|
||||
@@ -673,7 +789,10 @@ send_response:
|
||||
ISOBUSFS_FA_F_READ_FILE_RES);
|
||||
res->tan = req->tan;
|
||||
res->error_code = error_code;
|
||||
res->count = htole16(readed_size);
|
||||
if (is_dir)
|
||||
res->count = htole16(entries_read);
|
||||
else
|
||||
res->count = htole16(readed_size);
|
||||
|
||||
send_size = sizeof(*res) + readed_size;
|
||||
if (send_size < ISOBUSFS_MIN_TRANSFER_LENGH)
|
||||
@@ -686,11 +805,12 @@ send_response:
|
||||
goto free_res;
|
||||
}
|
||||
|
||||
pr_debug("> tx: Read File Response. Error code: %d (%s), readed size: %d",
|
||||
pr_debug("> tx: Read File Response. Error code: %d (%s), read size: %d",
|
||||
error_code, isobusfs_error_to_str(error_code), readed_size);
|
||||
|
||||
free_res:
|
||||
free(res);
|
||||
if (res_allocated)
|
||||
free(res);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -754,19 +874,33 @@ static int isobusfs_srv_seek(struct isobusfs_srv_priv *priv,
|
||||
return ISOBUSFS_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* isobusfs_srv_seek_directory() - Seek a directory by entry index.
|
||||
* @handle: Directory handle to seek.
|
||||
* @offset: Entry index to seek to.
|
||||
*
|
||||
* ISO 11783-13:2021 C.3.4.2 defines directory offsets as entry indices.
|
||||
*
|
||||
* Return: ISOBUSFS_ERR_SUCCESS or a protocol error code.
|
||||
*/
|
||||
static int isobusfs_srv_seek_directory(struct isobusfs_srv_handles *handle,
|
||||
int32_t offset)
|
||||
{
|
||||
DIR *dir = fdopendir(handle->fd);
|
||||
int32_t current_pos = handle->dir_pos;
|
||||
int ret;
|
||||
|
||||
if (!dir)
|
||||
if (!handle->dir)
|
||||
return ISOBUSFS_ERR_OTHER;
|
||||
|
||||
rewinddir(dir);
|
||||
|
||||
for (int32_t i = 0; i < offset; i++) {
|
||||
if (readdir(dir) == NULL)
|
||||
return ISOBUSFS_ERR_END_OF_FILE;
|
||||
/*
|
||||
* ISO 11783-13:2021 C.3.4.2:
|
||||
* Directory offsets are entry indices. If we fail to seek, restore
|
||||
* the previous entry position since the position shall not change on error.
|
||||
*/
|
||||
ret = isobusfs_srv_dir_skip_entries(handle, offset);
|
||||
if (ret != ISOBUSFS_ERR_SUCCESS) {
|
||||
isobusfs_srv_dir_skip_entries(handle, current_pos);
|
||||
return ret;
|
||||
}
|
||||
|
||||
handle->dir_pos = offset;
|
||||
|
||||
+88
-88
@@ -426,112 +426,112 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
if (frame.can_id & CAN_EFF_FLAG)
|
||||
printf(" %s %8X", argv[optind], frame.can_id & CAN_EFF_MASK);
|
||||
else
|
||||
printf(" %s %3X", argv[optind], frame.can_id & CAN_SFF_MASK);
|
||||
if (frame.can_id & CAN_EFF_FLAG)
|
||||
printf(" %s %8X", argv[optind], frame.can_id & CAN_EFF_MASK);
|
||||
else
|
||||
printf(" %s %3X", argv[optind], frame.can_id & CAN_SFF_MASK);
|
||||
|
||||
if (ext)
|
||||
printf("{%02X}", frame.data[0]);
|
||||
if (ext)
|
||||
printf("{%02X}", frame.data[0]);
|
||||
|
||||
if (nbytes == CAN_MTU)
|
||||
printf(" [%d] ", frame.len);
|
||||
else
|
||||
printf(" [%02d] ", frame.len);
|
||||
if (nbytes == CAN_MTU)
|
||||
printf(" [%d] ", frame.len);
|
||||
else
|
||||
printf(" [%02d] ", frame.len);
|
||||
|
||||
datidx = 0;
|
||||
n_pci = frame.data[ext];
|
||||
datidx = 0;
|
||||
n_pci = frame.data[ext];
|
||||
|
||||
switch (n_pci & 0xF0) {
|
||||
case 0x00:
|
||||
is_ff = 1;
|
||||
if (n_pci & 0xF) {
|
||||
printf("[SF] ln: %-4d data:", n_pci & 0xF);
|
||||
datidx = ext+1;
|
||||
} else {
|
||||
printf("[SF] ln: %-4d data:", frame.data[ext + 1]);
|
||||
datidx = ext+2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x10:
|
||||
is_ff = 1;
|
||||
fflen = ((n_pci & 0x0F)<<8) + frame.data[ext+1];
|
||||
if (fflen)
|
||||
datidx = ext+2;
|
||||
else {
|
||||
fflen = (frame.data[ext+2]<<24) +
|
||||
(frame.data[ext+3]<<16) +
|
||||
(frame.data[ext+4]<<8) +
|
||||
frame.data[ext+5];
|
||||
datidx = ext+6;
|
||||
}
|
||||
printf("[FF] ln: %-4lu data:", fflen);
|
||||
break;
|
||||
|
||||
case 0x20:
|
||||
printf("[CF] sn: %X data:", n_pci & 0x0F);
|
||||
switch (n_pci & 0xF0) {
|
||||
case 0x00:
|
||||
is_ff = 1;
|
||||
if (n_pci & 0xF) {
|
||||
printf("[SF] ln: %-4d data:", n_pci & 0xF);
|
||||
datidx = ext+1;
|
||||
break;
|
||||
} else {
|
||||
printf("[SF] ln: %-4d data:", frame.data[ext + 1]);
|
||||
datidx = ext+2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x30:
|
||||
n_pci &= 0x0F;
|
||||
printf("[FC] FC: %d ", n_pci);
|
||||
case 0x10:
|
||||
is_ff = 1;
|
||||
fflen = ((n_pci & 0x0F)<<8) + frame.data[ext+1];
|
||||
if (fflen)
|
||||
datidx = ext+2;
|
||||
else {
|
||||
fflen = (frame.data[ext+2]<<24) +
|
||||
(frame.data[ext+3]<<16) +
|
||||
(frame.data[ext+4]<<8) +
|
||||
frame.data[ext+5];
|
||||
datidx = ext+6;
|
||||
}
|
||||
printf("[FF] ln: %-4lu data:", fflen);
|
||||
break;
|
||||
|
||||
if (n_pci > 3)
|
||||
n_pci = 3;
|
||||
case 0x20:
|
||||
printf("[CF] sn: %X data:", n_pci & 0x0F);
|
||||
datidx = ext+1;
|
||||
break;
|
||||
|
||||
printf("= %s # ", fc_info[n_pci]);
|
||||
case 0x30:
|
||||
n_pci &= 0x0F;
|
||||
printf("[FC] FC: %d ", n_pci);
|
||||
|
||||
printf("BS: %d %s# ", frame.data[ext+1],
|
||||
(frame.data[ext+1])? "":"= off ");
|
||||
if (n_pci > 3)
|
||||
n_pci = 3;
|
||||
|
||||
i = frame.data[ext+2];
|
||||
printf("STmin: 0x%02X = ", i);
|
||||
printf("= %s # ", fc_info[n_pci]);
|
||||
|
||||
if (i < 0x80)
|
||||
printf("%d ms", i);
|
||||
else if (i > 0xF0 && i < 0xFA)
|
||||
printf("%d us", (i & 0x0F) * 100);
|
||||
else
|
||||
printf("reserved");
|
||||
break;
|
||||
printf("BS: %d %s# ", frame.data[ext+1],
|
||||
(frame.data[ext+1])? "":"= off ");
|
||||
|
||||
default:
|
||||
printf("[??]");
|
||||
i = frame.data[ext+2];
|
||||
printf("STmin: 0x%02X = ", i);
|
||||
|
||||
if (i < 0x80)
|
||||
printf("%d ms", i);
|
||||
else if (i > 0xF0 && i < 0xFA)
|
||||
printf("%d us", (i & 0x0F) * 100);
|
||||
else
|
||||
printf("reserved");
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("[??]");
|
||||
}
|
||||
|
||||
if (datidx && frame.len > datidx) {
|
||||
printf(" ");
|
||||
for (i = datidx; i < frame.len; i++) {
|
||||
printf("%02X ", frame.data[i]);
|
||||
}
|
||||
|
||||
if (datidx && frame.len > datidx) {
|
||||
printf(" ");
|
||||
if (asc) {
|
||||
printf("%*s", ((7-ext) - (frame.len-datidx))*3 + 5 ,
|
||||
"- '");
|
||||
for (i = datidx; i < frame.len; i++) {
|
||||
printf("%02X ", frame.data[i]);
|
||||
}
|
||||
|
||||
if (asc) {
|
||||
printf("%*s", ((7-ext) - (frame.len-datidx))*3 + 5 ,
|
||||
"- '");
|
||||
for (i = datidx; i < frame.len; i++) {
|
||||
printf("%c",((frame.data[i] > 0x1F) &&
|
||||
(frame.data[i] < 0x7F))?
|
||||
frame.data[i] : '.');
|
||||
}
|
||||
printf("'");
|
||||
}
|
||||
if (uds_output && is_ff) {
|
||||
int offset = 3;
|
||||
if (asc)
|
||||
offset = 1;
|
||||
printf("%*s", ((7-ext) - (frame.len-datidx))*offset + 3,
|
||||
" - ");
|
||||
print_uds_message(frame.data[datidx], frame.data[datidx+2]);
|
||||
is_ff = 0;
|
||||
printf("%c",((frame.data[i] > 0x1F) &&
|
||||
(frame.data[i] < 0x7F))?
|
||||
frame.data[i] : '.');
|
||||
}
|
||||
printf("'");
|
||||
}
|
||||
if (uds_output && is_ff) {
|
||||
int offset = 3;
|
||||
if (asc)
|
||||
offset = 1;
|
||||
printf("%*s", ((7-ext) - (frame.len-datidx))*offset + 3,
|
||||
" - ");
|
||||
print_uds_message(frame.data[datidx], frame.data[datidx+2]);
|
||||
is_ff = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (color)
|
||||
printf("%s", ATTRESET);
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
if (color)
|
||||
printf("%s", ATTRESET);
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
close(s);
|
||||
|
||||
+162
-162
@@ -243,177 +243,177 @@ int main(int argc, char **argv)
|
||||
continue;
|
||||
}
|
||||
|
||||
/* check extended address if provided */
|
||||
if (ext && extaddr != frame.data[0])
|
||||
continue;
|
||||
|
||||
/* only get flow control information from dst CAN ID */
|
||||
if (frame.can_id == dst) {
|
||||
/* check extended address if provided */
|
||||
if (ext && extaddr != frame.data[0])
|
||||
if (rx_ext && frame.data[0] != rx_extaddr)
|
||||
continue;
|
||||
|
||||
/* only get flow control information from dst CAN ID */
|
||||
if (frame.can_id == dst) {
|
||||
/* check extended address if provided */
|
||||
if (rx_ext && frame.data[0] != rx_extaddr)
|
||||
continue;
|
||||
n_pci = frame.data[rx_ext];
|
||||
/* check flow control PCI only */
|
||||
if ((n_pci & 0xF0) != 0x30)
|
||||
continue;
|
||||
|
||||
n_pci = frame.data[rx_ext];
|
||||
/* check flow control PCI only */
|
||||
if ((n_pci & 0xF0) != 0x30)
|
||||
continue;
|
||||
bs = frame.data[rx_ext + 1];
|
||||
stmin = frame.data[rx_ext + 2];
|
||||
}
|
||||
|
||||
bs = frame.data[rx_ext + 1];
|
||||
stmin = frame.data[rx_ext + 2];
|
||||
/* data content starts and index datidx */
|
||||
datidx = 0;
|
||||
|
||||
n_pci = frame.data[ext];
|
||||
switch (n_pci & 0xF0) {
|
||||
|
||||
case 0x00:
|
||||
/* SF */
|
||||
if (n_pci & 0xF) {
|
||||
fflen = rcvlen = n_pci & 0xF;
|
||||
datidx = ext+1;
|
||||
} else {
|
||||
fflen = rcvlen = frame.data[ext + 1];
|
||||
datidx = ext+2;
|
||||
}
|
||||
|
||||
/* data content starts and index datidx */
|
||||
datidx = 0;
|
||||
|
||||
n_pci = frame.data[ext];
|
||||
switch (n_pci & 0xF0) {
|
||||
|
||||
case 0x00:
|
||||
/* SF */
|
||||
if (n_pci & 0xF) {
|
||||
fflen = rcvlen = n_pci & 0xF;
|
||||
datidx = ext+1;
|
||||
} else {
|
||||
fflen = rcvlen = frame.data[ext + 1];
|
||||
datidx = ext+2;
|
||||
}
|
||||
|
||||
/* ignore incorrect SF PDUs */
|
||||
if (frame.len < rcvlen + datidx)
|
||||
fflen = rcvlen = 0;
|
||||
|
||||
/* get number of digits for printing */
|
||||
fflen_digits = getdigits(fflen);
|
||||
|
||||
/* get CAN FD bitrate & LL_DL setting information */
|
||||
brs = frame.flags & CANFD_BRS;
|
||||
ll_dl = frame.len;
|
||||
if (ll_dl < 8)
|
||||
ll_dl = 8;
|
||||
|
||||
ioctl(s, SIOCGSTAMP, &start_tv);
|
||||
|
||||
/* determine CAN frame mode for this PDU */
|
||||
if (nbytes == CAN_MTU)
|
||||
canfd_on = 0;
|
||||
else
|
||||
canfd_on = 1;
|
||||
|
||||
break;
|
||||
|
||||
case 0x10:
|
||||
/* FF */
|
||||
fflen = ((n_pci & 0x0F)<<8) + frame.data[ext+1];
|
||||
if (fflen)
|
||||
datidx = ext+2;
|
||||
else {
|
||||
fflen = (frame.data[ext+2]<<24) +
|
||||
(frame.data[ext+3]<<16) +
|
||||
(frame.data[ext+4]<<8) +
|
||||
frame.data[ext+5];
|
||||
datidx = ext+6;
|
||||
}
|
||||
|
||||
/* to increase the time resolution we multiply fflen with 1000 later */
|
||||
if (fflen >= (UINT32_MAX / 1000)) {
|
||||
printf("fflen %lu is more than ~4.2 MB - ignoring PDU\n", fflen);
|
||||
fflush(stdout);
|
||||
fflen = rcvlen = 0;
|
||||
continue;
|
||||
}
|
||||
rcvlen = frame.len - datidx;
|
||||
last_sn = 0;
|
||||
|
||||
/* get number of digits for printing */
|
||||
fflen_digits = getdigits(fflen);
|
||||
|
||||
/* get CAN FD bitrate & LL_DL setting information */
|
||||
brs = frame.flags & CANFD_BRS;
|
||||
ll_dl = frame.len;
|
||||
|
||||
ioctl(s, SIOCGSTAMP, &start_tv);
|
||||
|
||||
/* determine CAN frame mode for this PDU */
|
||||
if (nbytes == CAN_MTU)
|
||||
canfd_on = 0;
|
||||
else
|
||||
canfd_on = 1;
|
||||
|
||||
break;
|
||||
|
||||
case 0x20:
|
||||
/* CF */
|
||||
if (rcvlen) {
|
||||
sn = n_pci & 0x0F;
|
||||
if (sn == ((last_sn + 1) & 0xF)) {
|
||||
last_sn = sn;
|
||||
datidx = ext+1;
|
||||
rcvlen += frame.len - datidx;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* PDU reception in process */
|
||||
if (rcvlen) {
|
||||
if (rcvlen > fflen)
|
||||
rcvlen = fflen;
|
||||
|
||||
percent = (rcvlen * 100 / fflen);
|
||||
printf("\r %3lu%% ", percent);
|
||||
|
||||
printf("|");
|
||||
|
||||
if (percent > 100)
|
||||
percent = 100;
|
||||
|
||||
for (i=0; i < NUMBAR; i++){
|
||||
if (i < (int)(percent/PERCENTRES))
|
||||
printf("X");
|
||||
else
|
||||
printf(".");
|
||||
}
|
||||
printf("| %*lu/%lu ", fflen_digits, rcvlen, fflen);
|
||||
}
|
||||
|
||||
/* PDU complete */
|
||||
if (rcvlen && rcvlen >= fflen) {
|
||||
|
||||
printf("\r%s %02d%c (BS:%2hhu # ", canfd_on?"CAN-FD":"CAN2.0", ll_dl, brs?'*':' ', bs);
|
||||
if (stmin < 0x80)
|
||||
printf("STmin:%3hhu msec)", stmin);
|
||||
else if (stmin > 0xF0 && stmin < 0xFA)
|
||||
printf("STmin:%3u usec)", (stmin & 0xF) * 100);
|
||||
else
|
||||
printf("STmin: invalid )");
|
||||
|
||||
printf(" : %lu byte in ", fflen);
|
||||
|
||||
/* calculate time */
|
||||
ioctl(s, SIOCGSTAMP, &end_tv);
|
||||
diff_tv.tv_sec = end_tv.tv_sec - start_tv.tv_sec;
|
||||
diff_tv.tv_usec = end_tv.tv_usec - start_tv.tv_usec;
|
||||
if (diff_tv.tv_usec < 0)
|
||||
diff_tv.tv_sec--, diff_tv.tv_usec += 1000000;
|
||||
if (diff_tv.tv_sec < 0)
|
||||
diff_tv.tv_sec = diff_tv.tv_usec = 0;
|
||||
|
||||
/* check devisor to be not zero */
|
||||
if (diff_tv.tv_sec * 1000 + diff_tv.tv_usec / 1000){
|
||||
printf("%llu.%06llus ", (unsigned long long)diff_tv.tv_sec, (unsigned long long)diff_tv.tv_usec);
|
||||
printf("=> %lu byte/s", (fflen * 1000) /
|
||||
(unsigned long)(diff_tv.tv_sec * 1000 + diff_tv.tv_usec / 1000));
|
||||
} else
|
||||
printf("(no time available) ");
|
||||
|
||||
printf("\n");
|
||||
/* wait for next PDU */
|
||||
/* ignore incorrect SF PDUs */
|
||||
if (frame.len < rcvlen + datidx)
|
||||
fflen = rcvlen = 0;
|
||||
|
||||
/* get number of digits for printing */
|
||||
fflen_digits = getdigits(fflen);
|
||||
|
||||
/* get CAN FD bitrate & LL_DL setting information */
|
||||
brs = frame.flags & CANFD_BRS;
|
||||
ll_dl = frame.len;
|
||||
if (ll_dl < 8)
|
||||
ll_dl = 8;
|
||||
|
||||
ioctl(s, SIOCGSTAMP, &start_tv);
|
||||
|
||||
/* determine CAN frame mode for this PDU */
|
||||
if (nbytes == CAN_MTU)
|
||||
canfd_on = 0;
|
||||
else
|
||||
canfd_on = 1;
|
||||
|
||||
break;
|
||||
|
||||
case 0x10:
|
||||
/* FF */
|
||||
fflen = ((n_pci & 0x0F)<<8) + frame.data[ext+1];
|
||||
if (fflen)
|
||||
datidx = ext+2;
|
||||
else {
|
||||
fflen = (frame.data[ext+2]<<24) +
|
||||
(frame.data[ext+3]<<16) +
|
||||
(frame.data[ext+4]<<8) +
|
||||
frame.data[ext+5];
|
||||
datidx = ext+6;
|
||||
}
|
||||
fflush(stdout);
|
||||
|
||||
/* to increase the time resolution we multiply fflen with 1000 later */
|
||||
if (fflen >= (UINT32_MAX / 1000)) {
|
||||
printf("fflen %lu is more than ~4.2 MB - ignoring PDU\n", fflen);
|
||||
fflush(stdout);
|
||||
fflen = rcvlen = 0;
|
||||
continue;
|
||||
}
|
||||
rcvlen = frame.len - datidx;
|
||||
last_sn = 0;
|
||||
|
||||
/* get number of digits for printing */
|
||||
fflen_digits = getdigits(fflen);
|
||||
|
||||
/* get CAN FD bitrate & LL_DL setting information */
|
||||
brs = frame.flags & CANFD_BRS;
|
||||
ll_dl = frame.len;
|
||||
|
||||
ioctl(s, SIOCGSTAMP, &start_tv);
|
||||
|
||||
/* determine CAN frame mode for this PDU */
|
||||
if (nbytes == CAN_MTU)
|
||||
canfd_on = 0;
|
||||
else
|
||||
canfd_on = 1;
|
||||
|
||||
break;
|
||||
|
||||
case 0x20:
|
||||
/* CF */
|
||||
if (rcvlen) {
|
||||
sn = n_pci & 0x0F;
|
||||
if (sn == ((last_sn + 1) & 0xF)) {
|
||||
last_sn = sn;
|
||||
datidx = ext+1;
|
||||
rcvlen += frame.len - datidx;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* PDU reception in process */
|
||||
if (rcvlen) {
|
||||
if (rcvlen > fflen)
|
||||
rcvlen = fflen;
|
||||
|
||||
percent = (rcvlen * 100 / fflen);
|
||||
printf("\r %3lu%% ", percent);
|
||||
|
||||
printf("|");
|
||||
|
||||
if (percent > 100)
|
||||
percent = 100;
|
||||
|
||||
for (i=0; i < NUMBAR; i++){
|
||||
if (i < (int)(percent/PERCENTRES))
|
||||
printf("X");
|
||||
else
|
||||
printf(".");
|
||||
}
|
||||
printf("| %*lu/%lu ", fflen_digits, rcvlen, fflen);
|
||||
}
|
||||
|
||||
/* PDU complete */
|
||||
if (rcvlen && rcvlen >= fflen) {
|
||||
|
||||
printf("\r%s %02d%c (BS:%2hhu # ", canfd_on?"CAN-FD":"CAN2.0", ll_dl, brs?'*':' ', bs);
|
||||
if (stmin < 0x80)
|
||||
printf("STmin:%3hhu msec)", stmin);
|
||||
else if (stmin > 0xF0 && stmin < 0xFA)
|
||||
printf("STmin:%3u usec)", (stmin & 0xF) * 100);
|
||||
else
|
||||
printf("STmin: invalid )");
|
||||
|
||||
printf(" : %lu byte in ", fflen);
|
||||
|
||||
/* calculate time */
|
||||
ioctl(s, SIOCGSTAMP, &end_tv);
|
||||
diff_tv.tv_sec = end_tv.tv_sec - start_tv.tv_sec;
|
||||
diff_tv.tv_usec = end_tv.tv_usec - start_tv.tv_usec;
|
||||
if (diff_tv.tv_usec < 0)
|
||||
diff_tv.tv_sec--, diff_tv.tv_usec += 1000000;
|
||||
if (diff_tv.tv_sec < 0)
|
||||
diff_tv.tv_sec = diff_tv.tv_usec = 0;
|
||||
|
||||
/* check devisor to be not zero */
|
||||
if (diff_tv.tv_sec * 1000 + diff_tv.tv_usec / 1000){
|
||||
printf("%llu.%06llus ", (unsigned long long)diff_tv.tv_sec, (unsigned long long)diff_tv.tv_usec);
|
||||
printf("=> %lu byte/s", (fflen * 1000) /
|
||||
(unsigned long)(diff_tv.tv_sec * 1000 + diff_tv.tv_usec / 1000));
|
||||
} else
|
||||
printf("(no time available) ");
|
||||
|
||||
printf("\n");
|
||||
/* wait for next PDU */
|
||||
fflen = rcvlen = 0;
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
close(s);
|
||||
|
||||
+155
-155
@@ -82,183 +82,183 @@ void print_usage(char *prg)
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int s;
|
||||
struct sockaddr_can addr;
|
||||
static struct can_isotp_options opts;
|
||||
static struct can_isotp_fc_options fcopts;
|
||||
static struct can_isotp_ll_options llopts;
|
||||
int opt, i;
|
||||
extern int optind, opterr, optopt;
|
||||
__u32 force_rx_stmin = 0;
|
||||
int loop = 0;
|
||||
int s;
|
||||
struct sockaddr_can addr;
|
||||
static struct can_isotp_options opts;
|
||||
static struct can_isotp_fc_options fcopts;
|
||||
static struct can_isotp_ll_options llopts;
|
||||
int opt, i;
|
||||
extern int optind, opterr, optopt;
|
||||
__u32 force_rx_stmin = 0;
|
||||
int loop = 0;
|
||||
|
||||
unsigned char msg[BUFSIZE];
|
||||
int nbytes;
|
||||
unsigned char msg[BUFSIZE];
|
||||
int nbytes;
|
||||
|
||||
addr.can_addr.tp.tx_id = addr.can_addr.tp.rx_id = NO_CAN_ID;
|
||||
addr.can_addr.tp.tx_id = addr.can_addr.tp.rx_id = NO_CAN_ID;
|
||||
|
||||
while ((opt = getopt(argc, argv, "s:d:x:p:P:b:m:w:f:lFL:?")) != -1) {
|
||||
switch (opt) {
|
||||
case 's':
|
||||
addr.can_addr.tp.tx_id = strtoul(optarg, NULL, 16);
|
||||
if (strlen(optarg) > 7)
|
||||
addr.can_addr.tp.tx_id |= CAN_EFF_FLAG;
|
||||
break;
|
||||
while ((opt = getopt(argc, argv, "s:d:x:p:P:b:m:w:f:lFL:?")) != -1) {
|
||||
switch (opt) {
|
||||
case 's':
|
||||
addr.can_addr.tp.tx_id = strtoul(optarg, NULL, 16);
|
||||
if (strlen(optarg) > 7)
|
||||
addr.can_addr.tp.tx_id |= CAN_EFF_FLAG;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
addr.can_addr.tp.rx_id = strtoul(optarg, NULL, 16);
|
||||
if (strlen(optarg) > 7)
|
||||
addr.can_addr.tp.rx_id |= CAN_EFF_FLAG;
|
||||
break;
|
||||
case 'd':
|
||||
addr.can_addr.tp.rx_id = strtoul(optarg, NULL, 16);
|
||||
if (strlen(optarg) > 7)
|
||||
addr.can_addr.tp.rx_id |= CAN_EFF_FLAG;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
{
|
||||
int elements = sscanf(optarg, "%hhx:%hhx",
|
||||
&opts.ext_address,
|
||||
&opts.rx_ext_address);
|
||||
case 'x':
|
||||
{
|
||||
int elements = sscanf(optarg, "%hhx:%hhx",
|
||||
&opts.ext_address,
|
||||
&opts.rx_ext_address);
|
||||
|
||||
if (elements == 1)
|
||||
opts.flags |= CAN_ISOTP_EXTEND_ADDR;
|
||||
else if (elements == 2)
|
||||
opts.flags |= (CAN_ISOTP_EXTEND_ADDR | CAN_ISOTP_RX_EXT_ADDR);
|
||||
else {
|
||||
printf("incorrect extended addr values '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (elements == 1)
|
||||
opts.flags |= CAN_ISOTP_EXTEND_ADDR;
|
||||
else if (elements == 2)
|
||||
opts.flags |= (CAN_ISOTP_EXTEND_ADDR | CAN_ISOTP_RX_EXT_ADDR);
|
||||
else {
|
||||
printf("incorrect extended addr values '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'p':
|
||||
{
|
||||
int elements = sscanf(optarg, "%hhx:%hhx",
|
||||
&opts.txpad_content,
|
||||
&opts.rxpad_content);
|
||||
case 'p':
|
||||
{
|
||||
int elements = sscanf(optarg, "%hhx:%hhx",
|
||||
&opts.txpad_content,
|
||||
&opts.rxpad_content);
|
||||
|
||||
if (elements == 1)
|
||||
opts.flags |= CAN_ISOTP_TX_PADDING;
|
||||
else if (elements == 2)
|
||||
opts.flags |= (CAN_ISOTP_TX_PADDING | CAN_ISOTP_RX_PADDING);
|
||||
else if (sscanf(optarg, ":%hhx", &opts.rxpad_content) == 1)
|
||||
opts.flags |= CAN_ISOTP_RX_PADDING;
|
||||
else {
|
||||
printf("incorrect padding values '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (elements == 1)
|
||||
opts.flags |= CAN_ISOTP_TX_PADDING;
|
||||
else if (elements == 2)
|
||||
opts.flags |= (CAN_ISOTP_TX_PADDING | CAN_ISOTP_RX_PADDING);
|
||||
else if (sscanf(optarg, ":%hhx", &opts.rxpad_content) == 1)
|
||||
opts.flags |= CAN_ISOTP_RX_PADDING;
|
||||
else {
|
||||
printf("incorrect padding values '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'P':
|
||||
if (optarg[0] == 'l')
|
||||
opts.flags |= CAN_ISOTP_CHK_PAD_LEN;
|
||||
else if (optarg[0] == 'c')
|
||||
opts.flags |= CAN_ISOTP_CHK_PAD_DATA;
|
||||
else if (optarg[0] == 'a')
|
||||
opts.flags |= (CAN_ISOTP_CHK_PAD_LEN | CAN_ISOTP_CHK_PAD_DATA);
|
||||
else {
|
||||
printf("unknown padding check option '%c'.\n", optarg[0]);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
case 'P':
|
||||
if (optarg[0] == 'l')
|
||||
opts.flags |= CAN_ISOTP_CHK_PAD_LEN;
|
||||
else if (optarg[0] == 'c')
|
||||
opts.flags |= CAN_ISOTP_CHK_PAD_DATA;
|
||||
else if (optarg[0] == 'a')
|
||||
opts.flags |= (CAN_ISOTP_CHK_PAD_LEN | CAN_ISOTP_CHK_PAD_DATA);
|
||||
else {
|
||||
printf("unknown padding check option '%c'.\n", optarg[0]);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
fcopts.bs = strtoul(optarg, NULL, 16) & 0xFF;
|
||||
break;
|
||||
case 'b':
|
||||
fcopts.bs = strtoul(optarg, NULL, 16) & 0xFF;
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
fcopts.stmin = strtoul(optarg, NULL, 16) & 0xFF;
|
||||
break;
|
||||
case 'm':
|
||||
fcopts.stmin = strtoul(optarg, NULL, 16) & 0xFF;
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
fcopts.wftmax = strtoul(optarg, NULL, 16) & 0xFF;
|
||||
break;
|
||||
case 'w':
|
||||
fcopts.wftmax = strtoul(optarg, NULL, 16) & 0xFF;
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
opts.flags |= CAN_ISOTP_FORCE_RXSTMIN;
|
||||
force_rx_stmin = strtoul(optarg, NULL, 10);
|
||||
break;
|
||||
case 'f':
|
||||
opts.flags |= CAN_ISOTP_FORCE_RXSTMIN;
|
||||
force_rx_stmin = strtoul(optarg, NULL, 10);
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
loop = 1;
|
||||
break;
|
||||
case 'l':
|
||||
loop = 1;
|
||||
break;
|
||||
|
||||
case 'F':
|
||||
opts.flags |= CAN_ISOTP_DYN_FC_PARMS;
|
||||
break;
|
||||
case 'F':
|
||||
opts.flags |= CAN_ISOTP_DYN_FC_PARMS;
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
if (sscanf(optarg, "%hhu:%hhu:%hhu",
|
||||
&llopts.mtu,
|
||||
&llopts.tx_dl,
|
||||
&llopts.tx_flags) != 3) {
|
||||
printf("unknown link layer options '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
case 'L':
|
||||
if (sscanf(optarg, "%hhu:%hhu:%hhu",
|
||||
&llopts.mtu,
|
||||
&llopts.tx_dl,
|
||||
&llopts.tx_flags) != 3) {
|
||||
printf("unknown link layer options '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
|
||||
case '?':
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
break;
|
||||
case '?':
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unknown option %c\n", opt);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((argc - optind != 1) ||
|
||||
(addr.can_addr.tp.tx_id == NO_CAN_ID) ||
|
||||
(addr.can_addr.tp.rx_id == NO_CAN_ID)) {
|
||||
print_usage(basename(argv[0]));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((s = socket(PF_CAN, SOCK_DGRAM, CAN_ISOTP)) < 0) {
|
||||
perror("socket");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_OPTS, &opts, sizeof(opts));
|
||||
setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_RECV_FC, &fcopts, sizeof(fcopts));
|
||||
|
||||
if (llopts.tx_dl) {
|
||||
if (setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_LL_OPTS, &llopts, sizeof(llopts)) < 0) {
|
||||
perror("link layer sockopt");
|
||||
exit(1);
|
||||
default:
|
||||
fprintf(stderr, "Unknown option %c\n", opt);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.flags & CAN_ISOTP_FORCE_RXSTMIN)
|
||||
setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_RX_STMIN, &force_rx_stmin, sizeof(force_rx_stmin));
|
||||
if ((argc - optind != 1) ||
|
||||
(addr.can_addr.tp.tx_id == NO_CAN_ID) ||
|
||||
(addr.can_addr.tp.rx_id == NO_CAN_ID)) {
|
||||
print_usage(basename(argv[0]));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((s = socket(PF_CAN, SOCK_DGRAM, CAN_ISOTP)) < 0) {
|
||||
perror("socket");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
addr.can_family = AF_CAN;
|
||||
addr.can_ifindex = if_nametoindex(argv[optind]);
|
||||
if (!addr.can_ifindex) {
|
||||
perror("if_nametoindex");
|
||||
exit(1);
|
||||
}
|
||||
setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_OPTS, &opts, sizeof(opts));
|
||||
setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_RECV_FC, &fcopts, sizeof(fcopts));
|
||||
|
||||
if (llopts.tx_dl) {
|
||||
if (setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_LL_OPTS, &llopts, sizeof(llopts)) < 0) {
|
||||
perror("link layer sockopt");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.flags & CAN_ISOTP_FORCE_RXSTMIN)
|
||||
setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_RX_STMIN, &force_rx_stmin, sizeof(force_rx_stmin));
|
||||
|
||||
addr.can_family = AF_CAN;
|
||||
addr.can_ifindex = if_nametoindex(argv[optind]);
|
||||
if (!addr.can_ifindex) {
|
||||
perror("if_nametoindex");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
perror("bind");
|
||||
close(s);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
do {
|
||||
nbytes = read(s, msg, BUFSIZE);
|
||||
if (nbytes > 0 && nbytes < BUFSIZE)
|
||||
for (i=0; i < nbytes; i++)
|
||||
printf("%02X ", msg[i]);
|
||||
printf("\n");
|
||||
} while (loop);
|
||||
|
||||
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
perror("bind");
|
||||
close(s);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
do {
|
||||
nbytes = read(s, msg, BUFSIZE);
|
||||
if (nbytes > 0 && nbytes < BUFSIZE)
|
||||
for (i=0; i < nbytes; i++)
|
||||
printf("%02X ", msg[i]);
|
||||
printf("\n");
|
||||
} while (loop);
|
||||
|
||||
close(s);
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
+200
-200
@@ -86,238 +86,238 @@ void print_usage(char *prg)
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int s;
|
||||
struct sockaddr_can addr;
|
||||
static struct can_isotp_options opts;
|
||||
static struct can_isotp_ll_options llopts;
|
||||
int opt;
|
||||
extern int optind, opterr, optopt;
|
||||
unsigned int loops = 1; /* one (== no) loop by default */
|
||||
useconds_t usecs = 0; /* wait before sending the PDU */
|
||||
__u32 force_tx_stmin = 0;
|
||||
unsigned char buf[BUFSIZE];
|
||||
int buflen = 0;
|
||||
int datalen = 0;
|
||||
int retval = 0;
|
||||
int s;
|
||||
struct sockaddr_can addr;
|
||||
static struct can_isotp_options opts;
|
||||
static struct can_isotp_ll_options llopts;
|
||||
int opt;
|
||||
extern int optind, opterr, optopt;
|
||||
unsigned int loops = 1; /* one (== no) loop by default */
|
||||
useconds_t usecs = 0; /* wait before sending the PDU */
|
||||
__u32 force_tx_stmin = 0;
|
||||
unsigned char buf[BUFSIZE];
|
||||
int buflen = 0;
|
||||
int datalen = 0;
|
||||
int retval = 0;
|
||||
|
||||
addr.can_addr.tp.tx_id = addr.can_addr.tp.rx_id = NO_CAN_ID;
|
||||
addr.can_addr.tp.tx_id = addr.can_addr.tp.rx_id = NO_CAN_ID;
|
||||
|
||||
while ((opt = getopt(argc, argv, "s:d:x:p:P:t:f:D:l:g:bSCL:?")) != -1) {
|
||||
switch (opt) {
|
||||
case 's':
|
||||
addr.can_addr.tp.tx_id = strtoul(optarg, NULL, 16);
|
||||
if (strlen(optarg) > 7)
|
||||
addr.can_addr.tp.tx_id |= CAN_EFF_FLAG;
|
||||
break;
|
||||
while ((opt = getopt(argc, argv, "s:d:x:p:P:t:f:D:l:g:bSCL:?")) != -1) {
|
||||
switch (opt) {
|
||||
case 's':
|
||||
addr.can_addr.tp.tx_id = strtoul(optarg, NULL, 16);
|
||||
if (strlen(optarg) > 7)
|
||||
addr.can_addr.tp.tx_id |= CAN_EFF_FLAG;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
addr.can_addr.tp.rx_id = strtoul(optarg, NULL, 16);
|
||||
if (strlen(optarg) > 7)
|
||||
addr.can_addr.tp.rx_id |= CAN_EFF_FLAG;
|
||||
break;
|
||||
case 'd':
|
||||
addr.can_addr.tp.rx_id = strtoul(optarg, NULL, 16);
|
||||
if (strlen(optarg) > 7)
|
||||
addr.can_addr.tp.rx_id |= CAN_EFF_FLAG;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
{
|
||||
int elements = sscanf(optarg, "%hhx:%hhx",
|
||||
&opts.ext_address,
|
||||
&opts.rx_ext_address);
|
||||
case 'x':
|
||||
{
|
||||
int elements = sscanf(optarg, "%hhx:%hhx",
|
||||
&opts.ext_address,
|
||||
&opts.rx_ext_address);
|
||||
|
||||
if (elements == 1)
|
||||
opts.flags |= CAN_ISOTP_EXTEND_ADDR;
|
||||
else if (elements == 2)
|
||||
opts.flags |= (CAN_ISOTP_EXTEND_ADDR | CAN_ISOTP_RX_EXT_ADDR);
|
||||
else {
|
||||
printf("incorrect extended addr values '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (elements == 1)
|
||||
opts.flags |= CAN_ISOTP_EXTEND_ADDR;
|
||||
else if (elements == 2)
|
||||
opts.flags |= (CAN_ISOTP_EXTEND_ADDR | CAN_ISOTP_RX_EXT_ADDR);
|
||||
else {
|
||||
printf("incorrect extended addr values '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'p':
|
||||
{
|
||||
int elements = sscanf(optarg, "%hhx:%hhx",
|
||||
&opts.txpad_content,
|
||||
&opts.rxpad_content);
|
||||
case 'p':
|
||||
{
|
||||
int elements = sscanf(optarg, "%hhx:%hhx",
|
||||
&opts.txpad_content,
|
||||
&opts.rxpad_content);
|
||||
|
||||
if (elements == 1)
|
||||
opts.flags |= CAN_ISOTP_TX_PADDING;
|
||||
else if (elements == 2)
|
||||
opts.flags |= (CAN_ISOTP_TX_PADDING | CAN_ISOTP_RX_PADDING);
|
||||
else if (sscanf(optarg, ":%hhx", &opts.rxpad_content) == 1)
|
||||
opts.flags |= CAN_ISOTP_RX_PADDING;
|
||||
else {
|
||||
printf("incorrect padding values '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (elements == 1)
|
||||
opts.flags |= CAN_ISOTP_TX_PADDING;
|
||||
else if (elements == 2)
|
||||
opts.flags |= (CAN_ISOTP_TX_PADDING | CAN_ISOTP_RX_PADDING);
|
||||
else if (sscanf(optarg, ":%hhx", &opts.rxpad_content) == 1)
|
||||
opts.flags |= CAN_ISOTP_RX_PADDING;
|
||||
else {
|
||||
printf("incorrect padding values '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'P':
|
||||
if (optarg[0] == 'l')
|
||||
opts.flags |= CAN_ISOTP_CHK_PAD_LEN;
|
||||
else if (optarg[0] == 'c')
|
||||
opts.flags |= CAN_ISOTP_CHK_PAD_DATA;
|
||||
else if (optarg[0] == 'a')
|
||||
opts.flags |= (CAN_ISOTP_CHK_PAD_LEN | CAN_ISOTP_CHK_PAD_DATA);
|
||||
else {
|
||||
printf("unknown padding check option '%c'.\n", optarg[0]);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
case 'P':
|
||||
if (optarg[0] == 'l')
|
||||
opts.flags |= CAN_ISOTP_CHK_PAD_LEN;
|
||||
else if (optarg[0] == 'c')
|
||||
opts.flags |= CAN_ISOTP_CHK_PAD_DATA;
|
||||
else if (optarg[0] == 'a')
|
||||
opts.flags |= (CAN_ISOTP_CHK_PAD_LEN | CAN_ISOTP_CHK_PAD_DATA);
|
||||
else {
|
||||
printf("unknown padding check option '%c'.\n", optarg[0]);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
|
||||
case 't':
|
||||
if (!strncmp(optarg, ZERO_STRING, strlen(ZERO_STRING)))
|
||||
opts.frame_txtime = CAN_ISOTP_FRAME_TXTIME_ZERO;
|
||||
else
|
||||
opts.frame_txtime = strtoul(optarg, NULL, 10);
|
||||
break;
|
||||
case 't':
|
||||
if (!strncmp(optarg, ZERO_STRING, strlen(ZERO_STRING)))
|
||||
opts.frame_txtime = CAN_ISOTP_FRAME_TXTIME_ZERO;
|
||||
else
|
||||
opts.frame_txtime = strtoul(optarg, NULL, 10);
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
opts.flags |= CAN_ISOTP_FORCE_TXSTMIN;
|
||||
force_tx_stmin = strtoul(optarg, NULL, 10);
|
||||
break;
|
||||
case 'f':
|
||||
opts.flags |= CAN_ISOTP_FORCE_TXSTMIN;
|
||||
force_tx_stmin = strtoul(optarg, NULL, 10);
|
||||
break;
|
||||
|
||||
case 'D':
|
||||
datalen = strtoul(optarg, NULL, 10);
|
||||
if (!datalen || datalen >= BUFSIZE) {
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
case 'D':
|
||||
datalen = strtoul(optarg, NULL, 10);
|
||||
if (!datalen || datalen >= BUFSIZE) {
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
if (optarg[0] == 'i') {
|
||||
loops = 0; /* infinite loop */
|
||||
} else {
|
||||
loops = strtoul(optarg, NULL, 10);
|
||||
if (!loops) {
|
||||
fprintf(stderr, "Invalid argument for option -l!\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'l':
|
||||
if (optarg[0] == 'i') {
|
||||
loops = 0; /* infinite loop */
|
||||
} else {
|
||||
loops = strtoul(optarg, NULL, 10);
|
||||
if (!loops) {
|
||||
fprintf(stderr, "Invalid argument for option -l!\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
usecs = strtoul(optarg, NULL, 10);
|
||||
break;
|
||||
case 'g':
|
||||
usecs = strtoul(optarg, NULL, 10);
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
opts.flags |= CAN_ISOTP_WAIT_TX_DONE;
|
||||
break;
|
||||
case 'b':
|
||||
opts.flags |= CAN_ISOTP_WAIT_TX_DONE;
|
||||
break;
|
||||
|
||||
case 'S':
|
||||
opts.flags |= CAN_ISOTP_SF_BROADCAST;
|
||||
break;
|
||||
case 'S':
|
||||
opts.flags |= CAN_ISOTP_SF_BROADCAST;
|
||||
break;
|
||||
|
||||
case 'C':
|
||||
opts.flags |= CAN_ISOTP_CF_BROADCAST;
|
||||
break;
|
||||
case 'C':
|
||||
opts.flags |= CAN_ISOTP_CF_BROADCAST;
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
if (sscanf(optarg, "%hhu:%hhu:%hhu",
|
||||
&llopts.mtu,
|
||||
&llopts.tx_dl,
|
||||
&llopts.tx_flags) != 3) {
|
||||
printf("unknown link layer options '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
case 'L':
|
||||
if (sscanf(optarg, "%hhu:%hhu:%hhu",
|
||||
&llopts.mtu,
|
||||
&llopts.tx_dl,
|
||||
&llopts.tx_flags) != 3) {
|
||||
printf("unknown link layer options '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
|
||||
case '?':
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
break;
|
||||
case '?':
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unknown option %c\n", opt);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
default:
|
||||
fprintf(stderr, "Unknown option %c\n", opt);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#define BC_FLAGS (CAN_ISOTP_SF_BROADCAST | CAN_ISOTP_CF_BROADCAST)
|
||||
|
||||
if ((argc - optind != 1) ||
|
||||
(addr.can_addr.tp.tx_id == NO_CAN_ID) ||
|
||||
((opts.flags & BC_FLAGS) == BC_FLAGS) ||
|
||||
((addr.can_addr.tp.rx_id == NO_CAN_ID) &&
|
||||
(!(opts.flags & BC_FLAGS)))) {
|
||||
print_usage(basename(argv[0]));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((s = socket(PF_CAN, SOCK_DGRAM, CAN_ISOTP)) < 0) {
|
||||
perror("socket");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_OPTS, &opts, sizeof(opts)) < 0) {
|
||||
perror("sockopt");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
if (llopts.tx_dl) {
|
||||
if (setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_LL_OPTS, &llopts, sizeof(llopts)) < 0) {
|
||||
perror("link layer sockopt");
|
||||
exit(1);
|
||||
if ((argc - optind != 1) ||
|
||||
(addr.can_addr.tp.tx_id == NO_CAN_ID) ||
|
||||
((opts.flags & BC_FLAGS) == BC_FLAGS) ||
|
||||
((addr.can_addr.tp.rx_id == NO_CAN_ID) &&
|
||||
(!(opts.flags & BC_FLAGS)))) {
|
||||
print_usage(basename(argv[0]));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.flags & CAN_ISOTP_FORCE_TXSTMIN)
|
||||
setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_TX_STMIN, &force_tx_stmin, sizeof(force_tx_stmin));
|
||||
if ((s = socket(PF_CAN, SOCK_DGRAM, CAN_ISOTP)) < 0) {
|
||||
perror("socket");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
addr.can_family = AF_CAN;
|
||||
addr.can_ifindex = if_nametoindex(argv[optind]);
|
||||
if (!addr.can_ifindex) {
|
||||
perror("if_nametoindex");
|
||||
exit(1);
|
||||
}
|
||||
if (setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_OPTS, &opts, sizeof(opts)) < 0) {
|
||||
perror("sockopt");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
perror("bind");
|
||||
close(s);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!datalen) {
|
||||
while (buflen < BUFSIZE && scanf("%hhx", &buf[buflen]) == 1)
|
||||
buflen++;
|
||||
} else {
|
||||
for (buflen = 0; buflen < datalen; buflen++)
|
||||
buf[buflen] = ((buflen % 0xFF) + 1) & 0xFF;
|
||||
}
|
||||
if (llopts.tx_dl) {
|
||||
if (setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_LL_OPTS, &llopts, sizeof(llopts)) < 0) {
|
||||
perror("link layer sockopt");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.flags & CAN_ISOTP_FORCE_TXSTMIN)
|
||||
setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_TX_STMIN, &force_tx_stmin, sizeof(force_tx_stmin));
|
||||
|
||||
addr.can_family = AF_CAN;
|
||||
addr.can_ifindex = if_nametoindex(argv[optind]);
|
||||
if (!addr.can_ifindex) {
|
||||
perror("if_nametoindex");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
perror("bind");
|
||||
close(s);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!datalen) {
|
||||
while (buflen < BUFSIZE && scanf("%hhx", &buf[buflen]) == 1)
|
||||
buflen++;
|
||||
} else {
|
||||
for (buflen = 0; buflen < datalen; buflen++)
|
||||
buf[buflen] = ((buflen % 0xFF) + 1) & 0xFF;
|
||||
}
|
||||
|
||||
loop:
|
||||
if (usecs)
|
||||
usleep(usecs);
|
||||
if (usecs)
|
||||
usleep(usecs);
|
||||
|
||||
retval = write(s, buf, buflen);
|
||||
if (retval < 0) {
|
||||
perror("write");
|
||||
return retval;
|
||||
}
|
||||
retval = write(s, buf, buflen);
|
||||
if (retval < 0) {
|
||||
perror("write");
|
||||
return retval;
|
||||
}
|
||||
|
||||
if (retval != buflen)
|
||||
fprintf(stderr, "wrote only %d from %d byte\n", retval, buflen);
|
||||
if (retval != buflen)
|
||||
fprintf(stderr, "wrote only %d from %d byte\n", retval, buflen);
|
||||
|
||||
if (loops) {
|
||||
if (--loops)
|
||||
goto loop;
|
||||
} else {
|
||||
goto loop;
|
||||
}
|
||||
if (loops) {
|
||||
if (--loops)
|
||||
goto loop;
|
||||
} else {
|
||||
goto loop;
|
||||
}
|
||||
|
||||
/*
|
||||
* due to a Kernel internal wait queue the PDU is sent completely
|
||||
* before close() returns.
|
||||
*/
|
||||
close(s);
|
||||
/*
|
||||
* due to a Kernel internal wait queue the PDU is sent completely
|
||||
* before close() returns.
|
||||
*/
|
||||
close(s);
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+8
-8
@@ -112,10 +112,10 @@ static struct {
|
||||
int sig_alrm;
|
||||
int sig_usr1;
|
||||
int state;
|
||||
#define STATE_INITIAL 0
|
||||
#define STATE_REQ_SENT 1
|
||||
#define STATE_REQ_PENDING 2 /* wait 1250 msec for first claim */
|
||||
#define STATE_OPERATIONAL 3
|
||||
#define STATE_INITIAL 0
|
||||
#define STATE_REQ_SENT 1
|
||||
#define STATE_REQ_PENDING 2 /* wait 1250 msec for first claim */
|
||||
#define STATE_OPERATIONAL 3
|
||||
} s = {
|
||||
.intf = default_intf,
|
||||
.ranges = default_range,
|
||||
@@ -126,8 +126,8 @@ static struct {
|
||||
struct {
|
||||
uint64_t name;
|
||||
int flags;
|
||||
#define F_USE 0x01
|
||||
#define F_SEEN 0x02
|
||||
#define F_USE 0x01
|
||||
#define F_SEEN 0x02
|
||||
} addr[J1939_IDLE_ADDR /* =254 */];
|
||||
|
||||
/* lookup by name */
|
||||
@@ -209,7 +209,7 @@ static int open_socket(const char *device, uint64_t name)
|
||||
if (s.verbose)
|
||||
fprintf(stderr, "- setsockopt(, SOL_CAN_J1939, SO_J1939_FILTER, <filter>, %zd);\n", sizeof(filt));
|
||||
ret = setsockopt(sock, SOL_CAN_J1939, SO_J1939_FILTER,
|
||||
&filt, sizeof(filt));
|
||||
&filt, sizeof(filt));
|
||||
if (ret < 0)
|
||||
err(1, "setsockopt filter");
|
||||
|
||||
@@ -217,7 +217,7 @@ static int open_socket(const char *device, uint64_t name)
|
||||
if (s.verbose)
|
||||
fprintf(stderr, "- setsockopt(, SOL_SOCKET, SO_BROADCAST, %d, %zd);\n", value, sizeof(value));
|
||||
ret = setsockopt(sock, SOL_SOCKET, SO_BROADCAST,
|
||||
&value, sizeof(value));
|
||||
&value, sizeof(value));
|
||||
if (ret < 0)
|
||||
err(1, "setsockopt set broadcast");
|
||||
|
||||
|
||||
+18
-18
@@ -31,11 +31,11 @@
|
||||
* strict type-checking.. See the
|
||||
* "unnecessary" pointer comparison.
|
||||
*/
|
||||
#define min(x, y) ({ \
|
||||
typeof(x) _min1 = (x); \
|
||||
typeof(y) _min2 = (y); \
|
||||
(void) (&_min1 == &_min2); \
|
||||
_min1 < _min2 ? _min1 : _min2; })
|
||||
#define min(x, y) ({ \
|
||||
typeof(x) _min1 = (x); \
|
||||
typeof(y) _min2 = (y); \
|
||||
(void) (&_min1 == &_min2); \
|
||||
_min1 < _min2 ? _min1 : _min2; })
|
||||
|
||||
|
||||
struct j1939cat_stats {
|
||||
@@ -98,7 +98,7 @@ static const char help_msg[] =
|
||||
static const char optstring[] = "?hi:vs:rp:P:R:B";
|
||||
|
||||
static ssize_t j1939cat_send_one(struct j1939cat_priv *priv, int out_fd,
|
||||
const void *buf, size_t buf_size)
|
||||
const void *buf, size_t buf_size)
|
||||
{
|
||||
ssize_t num_sent;
|
||||
int flags = 0;
|
||||
@@ -132,7 +132,7 @@ static ssize_t j1939cat_send_one(struct j1939cat_priv *priv, int out_fd,
|
||||
}
|
||||
|
||||
static void j1939cat_print_timestamp(struct j1939cat_priv *priv, const char *name,
|
||||
struct timespec *cur)
|
||||
struct timespec *cur)
|
||||
{
|
||||
struct j1939cat_stats *stats = &priv->stats;
|
||||
|
||||
@@ -140,8 +140,8 @@ static void j1939cat_print_timestamp(struct j1939cat_priv *priv, const char *nam
|
||||
return;
|
||||
|
||||
fprintf(stderr, " %s: %llu s %llu us (seq=%03u, send=%07u)",
|
||||
name, (unsigned long long)cur->tv_sec, (unsigned long long)cur->tv_nsec / 1000,
|
||||
stats->tskey, stats->send);
|
||||
name, (unsigned long long)cur->tv_sec, (unsigned long long)cur->tv_nsec / 1000,
|
||||
stats->tskey, stats->send);
|
||||
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
@@ -226,7 +226,7 @@ static int j1939cat_extract_serr(struct j1939cat_priv *priv)
|
||||
stats->tskey = serr->ee_data;
|
||||
|
||||
j1939cat_print_timestamp(priv, j1939cat_tstype_to_str(serr->ee_info),
|
||||
&tss->ts[0]);
|
||||
&tss->ts[0]);
|
||||
|
||||
if (serr->ee_info == SCM_TSTAMP_SCHED)
|
||||
return -EINTR;
|
||||
@@ -338,7 +338,7 @@ static int j1939cat_recv_err(struct j1939cat_priv *priv)
|
||||
}
|
||||
|
||||
static int j1939cat_send_loop(struct j1939cat_priv *priv, int out_fd, char *buf,
|
||||
size_t buf_size)
|
||||
size_t buf_size)
|
||||
{
|
||||
struct j1939cat_stats *stats = &priv->stats;
|
||||
ssize_t count;
|
||||
@@ -411,7 +411,7 @@ static int j1939cat_send_loop(struct j1939cat_priv *priv, int out_fd, char *buf,
|
||||
}
|
||||
|
||||
static int j1939cat_sendfile(struct j1939cat_priv *priv, int out_fd, int in_fd,
|
||||
off_t *offset, size_t count)
|
||||
off_t *offset, size_t count)
|
||||
{
|
||||
int ret = EXIT_SUCCESS;
|
||||
off_t orig = 0;
|
||||
@@ -617,7 +617,7 @@ static int j1939cat_sock_prepare(struct j1939cat_priv *priv)
|
||||
|
||||
if (priv->todo_prio >= 0) {
|
||||
ret = setsockopt(priv->sock, SOL_CAN_J1939, SO_J1939_SEND_PRIO,
|
||||
&priv->todo_prio, sizeof(priv->todo_prio));
|
||||
&priv->todo_prio, sizeof(priv->todo_prio));
|
||||
if (ret < 0) {
|
||||
warn("set priority %i", priv->todo_prio);
|
||||
return EXIT_FAILURE;
|
||||
@@ -633,11 +633,11 @@ static int j1939cat_sock_prepare(struct j1939cat_priv *priv)
|
||||
}
|
||||
|
||||
sock_opt = SOF_TIMESTAMPING_SOFTWARE |
|
||||
SOF_TIMESTAMPING_OPT_CMSG |
|
||||
SOF_TIMESTAMPING_TX_ACK |
|
||||
SOF_TIMESTAMPING_TX_SCHED |
|
||||
SOF_TIMESTAMPING_OPT_STATS | SOF_TIMESTAMPING_OPT_TSONLY |
|
||||
SOF_TIMESTAMPING_OPT_ID | SOF_TIMESTAMPING_RX_SOFTWARE;
|
||||
SOF_TIMESTAMPING_OPT_CMSG |
|
||||
SOF_TIMESTAMPING_TX_ACK |
|
||||
SOF_TIMESTAMPING_TX_SCHED |
|
||||
SOF_TIMESTAMPING_OPT_STATS | SOF_TIMESTAMPING_OPT_TSONLY |
|
||||
SOF_TIMESTAMPING_OPT_ID | SOF_TIMESTAMPING_RX_SOFTWARE;
|
||||
|
||||
if (setsockopt(priv->sock, SOL_SOCKET, SO_TIMESTAMPING,
|
||||
(char *) &sock_opt, sizeof(sock_opt)))
|
||||
|
||||
+4
-4
@@ -76,7 +76,7 @@ static struct {
|
||||
static const int ival_1 = 1;
|
||||
|
||||
static char ctrlmsg[
|
||||
CMSG_SPACE(sizeof(struct timeval))
|
||||
CMSG_SPACE(sizeof(struct timeval))
|
||||
+ CMSG_SPACE(sizeof(uint8_t)) /* dest addr */
|
||||
+ CMSG_SPACE(sizeof(uint64_t)) /* dest name */
|
||||
+ CMSG_SPACE(sizeof(uint8_t)) /* priority */
|
||||
@@ -264,9 +264,9 @@ abs_time:
|
||||
struct tm tm;
|
||||
tm = *localtime(&tdut.tv_sec);
|
||||
printf("(%04u%02u%02uT%02u%02u%02u.%04llu)",
|
||||
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
||||
tm.tm_hour, tm.tm_min, tm.tm_sec,
|
||||
(unsigned long long)tdut.tv_usec/100);
|
||||
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
||||
tm.tm_hour, tm.tm_min, tm.tm_sec,
|
||||
(unsigned long long)tdut.tv_usec/100);
|
||||
}
|
||||
}
|
||||
printf(" %s ", libj1939_addr2str(&src));
|
||||
|
||||
@@ -68,9 +68,9 @@ static struct {
|
||||
int pkt_len;
|
||||
int priority;
|
||||
int defined;
|
||||
#define DEF_SRC 1
|
||||
#define DEF_DST 2
|
||||
#define DEF_PRIO 4
|
||||
#define DEF_SRC 1
|
||||
#define DEF_DST 2
|
||||
#define DEF_PRIO 4
|
||||
struct sockaddr_can src, dst;
|
||||
} s = {
|
||||
.priority = 6,
|
||||
|
||||
@@ -83,8 +83,8 @@ static inline void _put_id(char *buf, int end_offset, canid_t id)
|
||||
|
||||
/* CAN DLC to real data length conversion helpers */
|
||||
|
||||
static const unsigned char dlc2len[] = {0, 1, 2, 3, 4, 5, 6, 7,
|
||||
8, 12, 16, 20, 24, 32, 48, 64};
|
||||
static const unsigned char dlc2len[] = {
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64};
|
||||
|
||||
/* get data length from raw data length code (DLC) */
|
||||
unsigned char can_fd_dlc2len(unsigned char dlc)
|
||||
@@ -92,16 +92,17 @@ unsigned char can_fd_dlc2len(unsigned char dlc)
|
||||
return dlc2len[dlc & 0x0F];
|
||||
}
|
||||
|
||||
static const unsigned char len2dlc[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, /* 0 - 8 */
|
||||
9, 9, 9, 9, /* 9 - 12 */
|
||||
10, 10, 10, 10, /* 13 - 16 */
|
||||
11, 11, 11, 11, /* 17 - 20 */
|
||||
12, 12, 12, 12, /* 21 - 24 */
|
||||
13, 13, 13, 13, 13, 13, 13, 13, /* 25 - 32 */
|
||||
14, 14, 14, 14, 14, 14, 14, 14, /* 33 - 40 */
|
||||
14, 14, 14, 14, 14, 14, 14, 14, /* 41 - 48 */
|
||||
15, 15, 15, 15, 15, 15, 15, 15, /* 49 - 56 */
|
||||
15, 15, 15, 15, 15, 15, 15, 15}; /* 57 - 64 */
|
||||
static const unsigned char len2dlc[] = {
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, /* 0 - 8 */
|
||||
9, 9, 9, 9, /* 9 - 12 */
|
||||
10, 10, 10, 10, /* 13 - 16 */
|
||||
11, 11, 11, 11, /* 17 - 20 */
|
||||
12, 12, 12, 12, /* 21 - 24 */
|
||||
13, 13, 13, 13, 13, 13, 13, 13, /* 25 - 32 */
|
||||
14, 14, 14, 14, 14, 14, 14, 14, /* 33 - 40 */
|
||||
14, 14, 14, 14, 14, 14, 14, 14, /* 41 - 48 */
|
||||
15, 15, 15, 15, 15, 15, 15, 15, /* 49 - 56 */
|
||||
15, 15, 15, 15, 15, 15, 15, 15}; /* 57 - 64 */
|
||||
|
||||
/* map the sanitized data length to an appropriate data length code */
|
||||
unsigned char can_fd_len2dlc(unsigned char len)
|
||||
@@ -154,7 +155,7 @@ int hexstring2data(char *arg, unsigned char *data, int maxdlen)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int parse_canframe(char *cs, cu_t *cu)
|
||||
int parse_canframe(char *cs, union cfu *cu)
|
||||
{
|
||||
/* documentation see lib.h */
|
||||
|
||||
@@ -308,7 +309,7 @@ int parse_canframe(char *cs, cu_t *cu)
|
||||
return mtu;
|
||||
}
|
||||
|
||||
int snprintf_canframe(char *buf, size_t size, cu_t *cu, int sep)
|
||||
int snprintf_canframe(char *buf, size_t size, union cfu *cu, int sep)
|
||||
{
|
||||
/* documentation see lib.h */
|
||||
|
||||
@@ -432,7 +433,7 @@ int snprintf_canframe(char *buf, size_t size, cu_t *cu, int sep)
|
||||
return offset;
|
||||
}
|
||||
|
||||
int snprintf_long_canframe(char *buf, size_t size, cu_t *cu, int view)
|
||||
int snprintf_long_canframe(char *buf, size_t size, union cfu *cu, int view)
|
||||
{
|
||||
/* documentation see lib.h */
|
||||
|
||||
@@ -777,8 +778,8 @@ static int snprintf_error_ctrl(char *buf, size_t len, const struct canfd_frame *
|
||||
|
||||
n += snprintf(buf + n, len - n, "{");
|
||||
n += snprintf_error_data(buf + n, len - n, cf->data[1],
|
||||
controller_problems,
|
||||
ARRAY_SIZE(controller_problems));
|
||||
controller_problems,
|
||||
ARRAY_SIZE(controller_problems));
|
||||
n += snprintf(buf + n, len - n, "}");
|
||||
|
||||
return n;
|
||||
@@ -793,8 +794,8 @@ static int snprintf_error_prot(char *buf, size_t len, const struct canfd_frame *
|
||||
|
||||
n += snprintf(buf + n, len - n, "{{");
|
||||
n += snprintf_error_data(buf + n, len - n, cf->data[2],
|
||||
protocol_violation_types,
|
||||
ARRAY_SIZE(protocol_violation_types));
|
||||
protocol_violation_types,
|
||||
ARRAY_SIZE(protocol_violation_types));
|
||||
n += snprintf(buf + n, len - n, "}{");
|
||||
if (cf->data[3] > 0 &&
|
||||
cf->data[3] < ARRAY_SIZE(protocol_violation_locations))
|
||||
@@ -819,7 +820,7 @@ static int snprintf_error_cnt(char *buf, size_t len, const struct canfd_frame *c
|
||||
}
|
||||
|
||||
int snprintf_can_error_frame(char *buf, size_t len, const struct canfd_frame *cf,
|
||||
const char* sep)
|
||||
const char* sep)
|
||||
{
|
||||
canid_t class, mask;
|
||||
int i, n = 0, classes = 0;
|
||||
@@ -858,7 +859,7 @@ int snprintf_can_error_frame(char *buf, size_t len, const struct canfd_frame *cf
|
||||
n += tmp_n;
|
||||
if (mask == CAN_ERR_LOSTARB)
|
||||
n += snprintf_error_lostarb(buf + n, len - n,
|
||||
cf);
|
||||
cf);
|
||||
if (mask == CAN_ERR_CRTL)
|
||||
n += snprintf_error_ctrl(buf + n, len - n, cf);
|
||||
if (mask == CAN_ERR_PROT)
|
||||
@@ -878,7 +879,7 @@ int snprintf_can_error_frame(char *buf, size_t len, const struct canfd_frame *cf
|
||||
}
|
||||
|
||||
int64_t timespec_diff_ms(struct timespec *ts1,
|
||||
struct timespec *ts2)
|
||||
struct timespec *ts2)
|
||||
{
|
||||
int64_t diff = (ts1->tv_sec - ts2->tv_sec) * 1000;
|
||||
|
||||
|
||||
@@ -49,6 +49,8 @@
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
|
||||
#ifdef DEBUG
|
||||
#define pr_debug(fmt, args...) printf(fmt, ##args)
|
||||
#else
|
||||
@@ -57,11 +59,11 @@ static inline int pr_debug(const char* fmt, ...) {return 0;}
|
||||
#endif
|
||||
|
||||
/* CAN CC/FD/XL frame union */
|
||||
typedef union {
|
||||
union cfu {
|
||||
struct can_frame cc;
|
||||
struct canfd_frame fd;
|
||||
struct canxl_frame xl;
|
||||
} cu_t;
|
||||
};
|
||||
|
||||
/*
|
||||
* The buffer size for ASCII CAN frame string representations
|
||||
@@ -108,7 +110,7 @@ int hexstring2data(char *arg, unsigned char *data, int maxdlen);
|
||||
*
|
||||
*/
|
||||
|
||||
int parse_canframe(char *cs, cu_t *cu);
|
||||
int parse_canframe(char *cs, union cfu *cu);
|
||||
/*
|
||||
* Transfers a valid ASCII string describing a CAN frame into the CAN union
|
||||
* containing CAN CC/FD/XL structs.
|
||||
@@ -178,7 +180,7 @@ int parse_canframe(char *cs, cu_t *cu);
|
||||
* - CAN FD frames do not have a RTR bit
|
||||
*/
|
||||
|
||||
int snprintf_canframe(char *buf, size_t size, cu_t *cu, int sep);
|
||||
int snprintf_canframe(char *buf, size_t size, union cfu *cu, int sep);
|
||||
/*
|
||||
* Creates a CAN frame hexadecimal output in compact format.
|
||||
* The CAN data[] is separated by '.' when sep != 0.
|
||||
@@ -211,7 +213,7 @@ int snprintf_canframe(char *buf, size_t size, cu_t *cu, int sep);
|
||||
|
||||
#define SWAP_DELIMITER '`'
|
||||
|
||||
int snprintf_long_canframe(char *buf, size_t size, cu_t *cu, int view);
|
||||
int snprintf_long_canframe(char *buf, size_t size, union cfu *cu, int view);
|
||||
/*
|
||||
* Creates a CAN frame hexadecimal output in user readable format.
|
||||
*
|
||||
|
||||
+25
@@ -253,6 +253,31 @@ int libj1939_bind_socket(int sock, struct sockaddr_can *addr)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* libj1939_connect_socket - Connects a socket to a CAN address.
|
||||
* @sock: The socket file descriptor.
|
||||
* @addr: The CAN address to connect to.
|
||||
*
|
||||
* This function attempts to establish a connection between the given socket
|
||||
* and the specified CAN address. If the connection fails, it logs an error
|
||||
* message with the error code and a description of the error.
|
||||
*
|
||||
* Return: 0 on success, or a negative error code on failure.
|
||||
*/
|
||||
int libj1939_connect_socket(int sock, struct sockaddr_can *addr)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = connect(sock, (void *)addr, sizeof(*addr));
|
||||
if (ret < 0) {
|
||||
ret = -errno;
|
||||
pr_err("failed to connect socket: %d (%s)", ret, strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* libj1939_socket_prio - Set the priority of a J1939 socket
|
||||
* @sock: The file descriptor of the socket
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <linux/can/j1939.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#ifndef J1939_LIB_H
|
||||
@@ -42,6 +43,7 @@ void libj1939_init_sockaddr_can(struct sockaddr_can *sac, uint32_t pgn);
|
||||
|
||||
int libj1939_open_socket(void);
|
||||
int libj1939_bind_socket(int sock, struct sockaddr_can *addr);
|
||||
int libj1939_connect_socket(int sock, struct sockaddr_can *addr);
|
||||
int libj1939_socket_prio(int sock, int prio);
|
||||
int libj1939_set_broadcast(int sock);
|
||||
int libj1939_add_socket_to_epoll(int epoll_fd, int sock, uint32_t events);
|
||||
|
||||
@@ -183,7 +183,7 @@ static void canfd_asc(struct canfd_frame *cf, int devno, int mtu,
|
||||
fprintf(outfile, " %8d %4d %8X 0 0 0 0 0", 130000, 130, flags);
|
||||
}
|
||||
|
||||
static void canxl_asc(cu_t *cu, int devno, int mtu,
|
||||
static void canxl_asc(union cfu *cu, int devno, int mtu,
|
||||
char *extra_info, FILE *outfile)
|
||||
{
|
||||
char id[10];
|
||||
@@ -296,7 +296,7 @@ static void canxl_asc(cu_t *cu, int devno, int mtu,
|
||||
|
||||
#define DEVSZ 22
|
||||
#define EXTRASZ 20
|
||||
#define TIMESZ sizeof("(1345212884.318850) ")
|
||||
#define TIMESZ sizeof("(1345212884.318850123) ")
|
||||
#define BUFSZ (DEVSZ + AFRSZ + EXTRASZ + TIMESZ)
|
||||
|
||||
/* adapt sscanf() functions below on error */
|
||||
@@ -314,7 +314,7 @@ int main(int argc, char **argv)
|
||||
{
|
||||
static char buf[BUFSZ], device[DEVSZ], afrbuf[AFRSZ], extra_info[EXTRASZ];
|
||||
|
||||
static cu_t cu;
|
||||
static union cfu cu;
|
||||
static struct timeval tv, start_tv;
|
||||
FILE *infile = stdin;
|
||||
FILE *outfile = stdout;
|
||||
@@ -407,7 +407,22 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
tv.tv_sec = sec;
|
||||
tv.tv_usec = usec;
|
||||
|
||||
/*
|
||||
* ensure the fractions of seconds are 6 or 9 decimal places long to catch
|
||||
* 3rd party or handcrafted logfiles that treat the timestamp as float
|
||||
*/
|
||||
switch (strchr(buf, ')') - strchr(buf, '.')) {
|
||||
case 7: //6
|
||||
tv.tv_usec = usec;
|
||||
break;
|
||||
case 10: //9
|
||||
tv.tv_usec = usec / 1000;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "timestamp format in logfile requires 6 or 9 decimal places\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (print_banner) { /* print banner */
|
||||
print_banner = 0;
|
||||
|
||||
+4
-4
@@ -51,7 +51,7 @@
|
||||
#include "lib.h"
|
||||
|
||||
#define DEVSZ 22
|
||||
#define TIMESZ 22 /* sizeof("(1345212884.318850) ") */
|
||||
#define TIMESZ 25 /* sizeof("(1345212884.318850123) ") */
|
||||
#define BUFSZ (DEVSZ + AFRSZ + TIMESZ)
|
||||
|
||||
/* adapt sscanf() functions below on error */
|
||||
@@ -61,14 +61,14 @@
|
||||
#if (DEVSZ != 22)
|
||||
#error "DEVSZ value does not fit sscanf restrictions!"
|
||||
#endif
|
||||
#if (TIMESZ != 22)
|
||||
#if (TIMESZ != 25)
|
||||
#error "TIMESZ value does not fit sscanf restrictions!"
|
||||
#endif
|
||||
|
||||
int main(void)
|
||||
{
|
||||
static char buf[BUFSZ], timestamp[TIMESZ], device[DEVSZ], afrbuf[AFRSZ];
|
||||
static cu_t cu;
|
||||
static union cfu cu;
|
||||
int mtu;
|
||||
|
||||
while (fgets(buf, BUFSZ-1, stdin)) {
|
||||
@@ -78,7 +78,7 @@ int main(void)
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (sscanf(buf, "%21s %21s %6299s", timestamp, device, afrbuf) != 3)
|
||||
if (sscanf(buf, "%24s %21s %6299s", timestamp, device, afrbuf) != 3)
|
||||
return 1;
|
||||
|
||||
mtu = parse_canframe(afrbuf, &cu);
|
||||
|
||||
@@ -160,7 +160,7 @@ static int look_up_uart_speed(long int s)
|
||||
return B3500000;
|
||||
#endif
|
||||
#ifdef B3710000
|
||||
case 3710000
|
||||
case 3710000:
|
||||
return B3710000;
|
||||
#endif
|
||||
#ifdef B4000000
|
||||
@@ -370,8 +370,13 @@ int main(int argc, char *argv[])
|
||||
|
||||
/* retrieve the name of the created CAN netdevice */
|
||||
if (ioctl(fd, SIOCGIFNAME, ifr.ifr_name) < 0) {
|
||||
perror("ioctl SIOCGIFNAME");
|
||||
exit(EXIT_FAILURE);
|
||||
if (name) {
|
||||
perror("ioctl SIOCGIFNAME");
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
/* Graceful degradation: we only needed the name for display. */
|
||||
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "<unknown>");
|
||||
}
|
||||
}
|
||||
|
||||
syslogger(LOG_NOTICE, "attached TTY %s to netdevice %s\n", ttypath, ifr.ifr_name);
|
||||
|
||||
+6
-6
@@ -197,7 +197,7 @@ int main(int argc, char *argv[])
|
||||
if (verbose)
|
||||
fprintf(stderr, "- setsockopt(, SOL_CAN_J1939, SO_J1939_SEND_PRIO, &%i);\n", todo_prio);
|
||||
ret = setsockopt(sock, SOL_CAN_J1939, SO_J1939_SEND_PRIO,
|
||||
&todo_prio, sizeof(todo_prio));
|
||||
&todo_prio, sizeof(todo_prio));
|
||||
if (ret < 0)
|
||||
err(1, "set priority %i", todo_prio);
|
||||
}
|
||||
@@ -246,7 +246,7 @@ int main(int argc, char *argv[])
|
||||
if (verbose)
|
||||
fprintf(stderr, "- sendto(, <dat>, %i, 0, %s, %zi);\n", todo_send, libj1939_addr2str(&peername), sizeof(peername));
|
||||
ret = sendto(sock, dat, todo_send, 0,
|
||||
(void *)&peername, sizeof(peername));
|
||||
(void *)&peername, sizeof(peername));
|
||||
} else {
|
||||
/*
|
||||
* we may do sendto(sock, dat, todo_send, 0, NULL, 0)
|
||||
@@ -266,14 +266,14 @@ int main(int argc, char *argv[])
|
||||
fprintf(stderr, "- while (1)\n");
|
||||
while (todo_echo || todo_recv) {
|
||||
/*
|
||||
* re-use peername for storing the sender's peername of
|
||||
* reuse peername for storing the sender's peername of
|
||||
* received packets
|
||||
*/
|
||||
if (verbose)
|
||||
fprintf(stderr, "- recvfrom(, <dat>, %zi, 0, &<peername>, %zi);\n", sizeof(peername), sizeof(peername));
|
||||
peernamelen = sizeof(peername);
|
||||
ret = recvfrom(sock, dat, sizeof(dat), 0,
|
||||
(void *)&peername, &peernamelen);
|
||||
(void *)&peername, &peernamelen);
|
||||
if (ret < 0) {
|
||||
if (EINTR == errno) {
|
||||
if (verbose)
|
||||
@@ -287,7 +287,7 @@ int main(int argc, char *argv[])
|
||||
if (verbose)
|
||||
fprintf(stderr, "- sendto(, <dat>, %i, 0, %s, %i);\n", ret, libj1939_addr2str(&peername), peernamelen);
|
||||
ret = sendto(sock, dat, ret, 0,
|
||||
(void *)&peername, peernamelen);
|
||||
(void *)&peername, peernamelen);
|
||||
if (ret < 0)
|
||||
err(1, "sendto");
|
||||
}
|
||||
@@ -297,7 +297,7 @@ int main(int argc, char *argv[])
|
||||
if (todo_names && peername.can_addr.j1939.name)
|
||||
printf("%016llx ", peername.can_addr.j1939.name);
|
||||
printf("%02x %05x:", peername.can_addr.j1939.addr,
|
||||
peername.can_addr.j1939.pgn);
|
||||
peername.can_addr.j1939.pgn);
|
||||
for (i = 0, j = 0; i < ret; ++i, j++) {
|
||||
if (j == 8) {
|
||||
printf("\n%05x ", i);
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Copyright (c) 2025 Linux CAN project
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
import pytest
|
||||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Add option to select the interface (default: vcan0)
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption(
|
||||
"--can-iface",
|
||||
action="store",
|
||||
default="vcan0",
|
||||
help="The CAN interface for tests (e.g., vcan0)"
|
||||
)
|
||||
parser.addoption(
|
||||
"--bin-path",
|
||||
action="store",
|
||||
default=".",
|
||||
help="Path to the compiled can-utils binaries"
|
||||
)
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def can_interface(request):
|
||||
"""
|
||||
Checks if the specified interface exists.
|
||||
If not, the tests are skipped.
|
||||
"""
|
||||
iface = request.config.getoption("--can-iface")
|
||||
|
||||
try:
|
||||
# 'ip link show <iface>' returns 0 if it exists, otherwise an error
|
||||
subprocess.check_call(
|
||||
["ip", "link", "show", iface],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL
|
||||
)
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
pytest.skip(f"Prerequisite not met: Interface '{iface}' not found.")
|
||||
|
||||
return iface
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def bin_path(request):
|
||||
"""
|
||||
Returns the path to the binaries and checks if they exist.
|
||||
"""
|
||||
path = request.config.getoption("--bin-path")
|
||||
# Exemplarily check if 'cansend' is located there
|
||||
cansend_path = os.path.join(path, "cansend")
|
||||
if not os.path.isfile(cansend_path) and not os.path.isfile(cansend_path + ".exe"):
|
||||
pytest.skip(f"Compiled tools not found in '{path}'. Please run 'make' first.")
|
||||
return path
|
||||
@@ -0,0 +1,114 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Copyright (c) 2023 Linux CAN project
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import os
|
||||
import pytest
|
||||
import signal
|
||||
|
||||
def run_tool(bin_path, tool_name, args):
|
||||
"""Helper function to run a tool"""
|
||||
full_path = os.path.join(bin_path, tool_name)
|
||||
cmd = [full_path] + args
|
||||
return subprocess.run(cmd, capture_output=True, text=True)
|
||||
|
||||
def test_tools_executable(bin_path):
|
||||
"""Simply checks if the tools show help (existence & executability)"""
|
||||
for tool in ["cansend", "candump", "cangen"]:
|
||||
result = run_tool(bin_path, tool, ["-?"]) # -? is often help in can-utils, otherwise invalid args
|
||||
# We do not expect a crash (Segfault), Exit code may vary depending on arg parsing
|
||||
# It is important that stderr or stdout contains something meaningful or the process exits cleanly
|
||||
assert result.returncode != -11, f"{tool} crashed (Segfault)"
|
||||
|
||||
def test_send_and_receive(bin_path, can_interface):
|
||||
"""
|
||||
Integration test:
|
||||
1. Starts candump in the background on the interface.
|
||||
2. Sends a CAN frame with cansend.
|
||||
3. Checks if candump saw the frame.
|
||||
"""
|
||||
candump_path = os.path.join(bin_path, "candump")
|
||||
cansend_path = os.path.join(bin_path, "cansend")
|
||||
|
||||
# Unique ID and data for this test
|
||||
can_id = "123"
|
||||
can_data = "11223344"
|
||||
can_frame = f"{can_id}#{can_data}"
|
||||
|
||||
# 1. Start candump in the background
|
||||
# -L for Raw output, -x for extra details (optional), interface
|
||||
with subprocess.Popen(
|
||||
[candump_path, "-L", can_interface],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
preexec_fn=os.setsid # Process group for clean killing
|
||||
) as dumpproc:
|
||||
|
||||
time.sleep(0.5) # Wait briefly until candump is ready
|
||||
|
||||
# 2. Send frame
|
||||
# cansend <device> <can_frame>
|
||||
send_result = subprocess.run(
|
||||
[cansend_path, can_interface, can_frame],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
assert send_result.returncode == 0, f"cansend failed: {send_result.stderr}"
|
||||
|
||||
time.sleep(0.5) # Wait until frame is processed
|
||||
|
||||
# Terminate candump
|
||||
os.killpg(os.getpgid(dumpproc.pid), signal.SIGTERM)
|
||||
stdout, stderr = dumpproc.communicate()
|
||||
|
||||
# 3. Analysis
|
||||
# candump -L Output Format (approx): (timestamp) vcan0 123#11223344
|
||||
print(f"Candump Output: {stdout}")
|
||||
|
||||
assert can_id in stdout, "CAN-ID was not received"
|
||||
assert can_data in stdout, "CAN data were not received"
|
||||
|
||||
def test_cangen_generation(bin_path, can_interface):
|
||||
"""
|
||||
Checks if cangen generates traffic.
|
||||
"""
|
||||
cangen_path = os.path.join(bin_path, "cangen")
|
||||
candump_path = os.path.join(bin_path, "candump")
|
||||
|
||||
# Start candump
|
||||
with subprocess.Popen(
|
||||
[candump_path, "-L", can_interface],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
preexec_fn=os.setsid
|
||||
) as dumpproc:
|
||||
|
||||
# Start cangen: Send 5 frames (-n 5) and then terminate
|
||||
subprocess.run(
|
||||
[cangen_path, "-n", "5", can_interface],
|
||||
check=True
|
||||
)
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
os.killpg(os.getpgid(dumpproc.pid), signal.SIGTERM)
|
||||
stdout, _ = dumpproc.communicate()
|
||||
|
||||
# We expect 5 lines of output (or more, if other traffic is present)
|
||||
lines = [l for l in stdout.splitlines() if can_interface in l]
|
||||
assert len(lines) >= 5, "cangen did not generate enough frames/candump did not see them"
|
||||
@@ -0,0 +1,171 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Copyright (c) 2025 Linux CAN project
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
import pytest
|
||||
import subprocess
|
||||
import os
|
||||
import re
|
||||
|
||||
# --- Helper Functions ---
|
||||
|
||||
def run_ccbt(bin_path, args):
|
||||
"""
|
||||
Helper to run can-calc-bit-timing.
|
||||
Returns the subprocess.CompletedProcess object.
|
||||
"""
|
||||
cmd = [os.path.join(bin_path, "can-calc-bit-timing")] + args
|
||||
return subprocess.run(cmd, capture_output=True, text=True)
|
||||
|
||||
# --- Tests ---
|
||||
|
||||
def test_usage_on_invalid_option(bin_path):
|
||||
"""
|
||||
Description:
|
||||
Tests that providing an invalid option (like -h) results in an error
|
||||
message and prints the usage information.
|
||||
Note: The tool does not support -h explicitly, it treats it as invalid.
|
||||
|
||||
Manual reproduction:
|
||||
$ can-calc-bit-timing -h
|
||||
> invalid option -- 'h'
|
||||
> Usage: can-calc-bit-timing [options] ...
|
||||
"""
|
||||
result = run_ccbt(bin_path, ["-h"])
|
||||
|
||||
# Expect failure code usually for invalid options
|
||||
assert result.returncode != 0
|
||||
assert "Usage: can-calc-bit-timing" in result.stderr or "Usage: can-calc-bit-timing" in result.stdout
|
||||
|
||||
def test_list_controllers(bin_path):
|
||||
"""
|
||||
Description:
|
||||
Tests the -l option to list all supported CAN controller names.
|
||||
|
||||
Manual reproduction:
|
||||
$ can-calc-bit-timing -l
|
||||
> ...
|
||||
> mcp251x
|
||||
> ...
|
||||
"""
|
||||
result = run_ccbt(bin_path, ["-l"])
|
||||
|
||||
assert result.returncode == 0
|
||||
# Check for a common controller known to be in the list (e.g., mcp251x or sja1000)
|
||||
# The list is usually quite long.
|
||||
assert "mcp251x" in result.stdout or "sja1000" in result.stdout
|
||||
|
||||
def test_basic_calculation_stdout(bin_path):
|
||||
"""
|
||||
Description:
|
||||
Tests a basic bit timing calculation for a standard bitrate (500k)
|
||||
and clock (8MHz). Verifies that a table is produced.
|
||||
|
||||
Manual reproduction:
|
||||
$ can-calc-bit-timing -c 8000000 -b 500000
|
||||
> Bit rate : 500000 Rate error : 0.00% ...
|
||||
"""
|
||||
# 8MHz clock, 500kbit/s
|
||||
result = run_ccbt(bin_path, ["-c", "8000000", "-b", "500000"])
|
||||
|
||||
assert result.returncode == 0
|
||||
# Output should contain column headers like "SampP" (Sample Point) and the bitrate
|
||||
assert "SampP" in result.stdout
|
||||
assert "500000" in result.stdout
|
||||
|
||||
def test_quiet_mode(bin_path):
|
||||
"""
|
||||
Description:
|
||||
Tests the -q option (quiet mode), which should suppress the header line.
|
||||
|
||||
Manual reproduction:
|
||||
$ can-calc-bit-timing -q -c 8000000 -b 500000
|
||||
> (Output should start with data, not the 'Bit timing parameters...' header)
|
||||
"""
|
||||
# Run without -q first to confirm header exists
|
||||
res_std = run_ccbt(bin_path, ["-c", "8000000", "-b", "500000"])
|
||||
assert "Bit timing parameters" in res_std.stdout
|
||||
|
||||
# Run with -q
|
||||
res_quiet = run_ccbt(bin_path, ["-q", "-c", "8000000", "-b", "500000"])
|
||||
assert res_quiet.returncode == 0
|
||||
assert "Bit timing parameters" not in res_quiet.stdout
|
||||
# Data should still be there
|
||||
assert "500000" in res_quiet.stdout
|
||||
|
||||
def test_verbose_output(bin_path):
|
||||
"""
|
||||
Description:
|
||||
Tests the -v option for verbose output.
|
||||
|
||||
Manual reproduction:
|
||||
$ can-calc-bit-timing -v -c 8000000 -b 500000
|
||||
"""
|
||||
result = run_ccbt(bin_path, ["-v", "-c", "8000000", "-b", "500000"])
|
||||
assert result.returncode == 0
|
||||
# Verbose mode usually prints more details, but the table should definitely be there.
|
||||
# We check for "SampP" to ensure valid calculation output.
|
||||
assert "SampP" in result.stdout
|
||||
|
||||
def test_data_bitrate_fd(bin_path):
|
||||
"""
|
||||
Description:
|
||||
Tests the -d option to specify a data bitrate (CAN FD).
|
||||
|
||||
Manual reproduction:
|
||||
$ can-calc-bit-timing -c 40000000 -b 1000000 -d 2000000
|
||||
"""
|
||||
# 40MHz clock, 1M nominal, 2M data
|
||||
result = run_ccbt(bin_path, ["-c", "40000000", "-b", "1000000", "-d", "2000000"])
|
||||
|
||||
assert result.returncode == 0
|
||||
# Should calculate for both
|
||||
assert "SampP" in result.stdout
|
||||
assert "1000000" in result.stdout
|
||||
assert "2000000" in result.stdout
|
||||
|
||||
def test_specific_sample_point(bin_path):
|
||||
"""
|
||||
Description:
|
||||
Tests the -s option to define a specific sample point (e.g., 875 for 87.5%).
|
||||
|
||||
Manual reproduction:
|
||||
$ can-calc-bit-timing -c 8000000 -b 500000 -s 875
|
||||
> ... Sample Point : 87.5% ...
|
||||
"""
|
||||
# 875 means 87.5%
|
||||
result = run_ccbt(bin_path, ["-c", "8000000", "-b", "500000", "-s", "875"])
|
||||
|
||||
assert result.returncode == 0
|
||||
assert "87.5%" in result.stdout
|
||||
|
||||
def test_specific_algorithm(bin_path):
|
||||
"""
|
||||
Description:
|
||||
Tests the --alg option to select a different algorithm.
|
||||
Assuming 'v4_8' is a valid algorithm key based on source files
|
||||
(can-calc-bit-timing-v4_8.c). Note: Valid keys depend on compilation.
|
||||
We check if it accepts the flag without crashing, even if default is used.
|
||||
|
||||
Manual reproduction:
|
||||
$ can-calc-bit-timing -c 8000000 -b 500000 --alg v4.8
|
||||
(Note: algorithm naming convention might vary, using a safe check)
|
||||
"""
|
||||
# Trying to list supported algorithms first would be ideal if possible,
|
||||
# but based on file list, let's try a standard calculation with a potentially valid alg string.
|
||||
# If the alg string is invalid, it usually warns or errors.
|
||||
|
||||
# We simply check that the flag is parsed.
|
||||
# "default" should always be valid.
|
||||
result = run_ccbt(bin_path, ["-c", "8000000", "-b", "500000", "--alg", "default"])
|
||||
assert result.returncode == 0
|
||||
@@ -0,0 +1,284 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Copyright (c) 2025 Linux CAN project
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
import pytest
|
||||
import subprocess
|
||||
import os
|
||||
import time
|
||||
import signal
|
||||
import re
|
||||
|
||||
# --- Helper Functions & Classes ---
|
||||
|
||||
def run_cangen(bin_path, interface, args):
|
||||
"""
|
||||
Runs cangen to generate traffic.
|
||||
Waits for it to finish (assumes -n is used in args).
|
||||
"""
|
||||
cmd = [os.path.join(bin_path, "cangen"), interface] + args
|
||||
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
class CanbusloadMonitor:
|
||||
"""
|
||||
Context manager to run canbusload.
|
||||
Captures stdout/stderr.
|
||||
Ensures process is killed to prevent stalls.
|
||||
"""
|
||||
def __init__(self, bin_path, args):
|
||||
self.cmd = [os.path.join(bin_path, "canbusload")] + args
|
||||
self.process = None
|
||||
self.output = ""
|
||||
self.error = ""
|
||||
|
||||
def __enter__(self):
|
||||
# run with setsid to allow killing the whole process group later
|
||||
self.process = subprocess.Popen(
|
||||
self.cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
preexec_fn=os.setsid
|
||||
)
|
||||
time.sleep(0.5) # Wait for tool to start and initialize
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self.process:
|
||||
if self.process.poll() is None:
|
||||
# canbusload mentions using CTRL-C (SIGINT) to terminate.
|
||||
# SIGTERM might be ignored or handled differently.
|
||||
os.killpg(os.getpgid(self.process.pid), signal.SIGINT)
|
||||
try:
|
||||
# Wait a bit for it to exit gracefully
|
||||
self.process.wait(timeout=1.0)
|
||||
except subprocess.TimeoutExpired:
|
||||
# Force kill if it's stuck (e.g. buffer deadlock)
|
||||
os.killpg(os.getpgid(self.process.pid), signal.SIGKILL)
|
||||
|
||||
# Read remaining output
|
||||
self.output, self.error = self.process.communicate()
|
||||
|
||||
# --- Tests for canbusload ---
|
||||
|
||||
def test_help_option(bin_path):
|
||||
"""
|
||||
Description:
|
||||
Tests the -h option to ensure the usage message is displayed.
|
||||
|
||||
Manual reproduction:
|
||||
$ canbusload -h
|
||||
> monitor CAN bus load.
|
||||
> Usage: canbusload [options] <CAN interface>+
|
||||
"""
|
||||
result = subprocess.run(
|
||||
[os.path.join(bin_path, "canbusload"), "-h"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
assert "Usage: canbusload" in result.stdout or "Usage: canbusload" in result.stderr
|
||||
assert "monitor CAN bus load" in result.stdout or "monitor CAN bus load" in result.stderr
|
||||
|
||||
def test_basic_monitoring(bin_path, can_interface):
|
||||
"""
|
||||
Description:
|
||||
Tests basic monitoring of a CAN interface with a specified bitrate.
|
||||
Verifies that the interface name appears in the output and frames are counted.
|
||||
|
||||
Manual reproduction:
|
||||
1. Open a terminal and run:
|
||||
$ canbusload vcan0@500000
|
||||
2. In another terminal, generate traffic:
|
||||
$ cangen vcan0 -n 10
|
||||
3. Observe the output updating with frame counts.
|
||||
"""
|
||||
bitrate = "500000"
|
||||
target = f"{can_interface}@{bitrate}"
|
||||
|
||||
with CanbusloadMonitor(bin_path, [target]) as monitor:
|
||||
# Generate some traffic so canbusload has something to report
|
||||
# Use -g to add a small gap, preventing potential buffer flooding issues on very fast systems
|
||||
run_cangen(bin_path, can_interface, ["-n", "10", "-g", "10"])
|
||||
time.sleep(1.0) # Wait for next update cycle
|
||||
|
||||
assert can_interface in monitor.output
|
||||
# Check if we saw some frames (regex for at least 1 frame)
|
||||
# Output format roughly: can0@500k 10 ...
|
||||
assert re.search(r'\s+[1-9][0-9]*\s+', monitor.output), "No frames detected in output"
|
||||
|
||||
def test_time_option_t(bin_path, can_interface):
|
||||
"""
|
||||
Description:
|
||||
Tests the -t option which shows the current time on the first line.
|
||||
|
||||
Manual reproduction:
|
||||
$ canbusload -t vcan0@500000
|
||||
> canbusload 2025-01-01 12:00:00 ...
|
||||
"""
|
||||
target = f"{can_interface}@500000"
|
||||
|
||||
with CanbusloadMonitor(bin_path, ["-t", target]) as monitor:
|
||||
time.sleep(0.5)
|
||||
|
||||
# Check for YYYY-MM-DD format in the output header
|
||||
assert re.search(r'\d{4}-\d{2}-\d{2}', monitor.output), "Timestamp not found with -t option"
|
||||
|
||||
def test_bargraph_option_b(bin_path, can_interface):
|
||||
"""
|
||||
Description:
|
||||
Tests the -b option which displays a bargraph of the bus load.
|
||||
|
||||
Manual reproduction:
|
||||
1. Run:
|
||||
$ canbusload -b vcan0@500000
|
||||
2. Generate heavy traffic:
|
||||
$ cangen vcan0 -g 0 -n 100
|
||||
3. Observe the ASCII bar graph (e.g., |XXXX....|).
|
||||
"""
|
||||
target = f"{can_interface}@500000"
|
||||
|
||||
with CanbusloadMonitor(bin_path, ["-b", target]) as monitor:
|
||||
# Generate burst of traffic to spike load
|
||||
run_cangen(bin_path, can_interface, ["-n", "50", "-g", "0"])
|
||||
time.sleep(1.0)
|
||||
|
||||
# Check for bargraph delimiters
|
||||
assert "|" in monitor.output
|
||||
# Check for bargraph content (X for data, . for empty)
|
||||
# Note: With low load it might just be dots, but the bar structure |...| should exist
|
||||
assert re.search(r'\|[XRT\.]+\|', monitor.output), "Bargraph structure not found"
|
||||
|
||||
def test_colorize_option_c(bin_path, can_interface):
|
||||
"""
|
||||
Description:
|
||||
Tests the -c option for colorized output.
|
||||
Checks for the presence of ANSI escape codes in the output.
|
||||
|
||||
Manual reproduction:
|
||||
$ canbusload -c vcan0@500000
|
||||
> Output should be colored (visible in compatible terminal).
|
||||
"""
|
||||
target = f"{can_interface}@500000"
|
||||
|
||||
with CanbusloadMonitor(bin_path, ["-c", target]) as monitor:
|
||||
run_cangen(bin_path, can_interface, ["-n", "1"])
|
||||
time.sleep(0.5)
|
||||
|
||||
# Check for ANSI escape character (ASCII 27 / \x1b)
|
||||
assert "\x1b[" in monitor.output, "ANSI escape codes for color not found"
|
||||
|
||||
def test_redraw_option_r(bin_path, can_interface):
|
||||
"""
|
||||
Description:
|
||||
Tests the -r option which redraws the terminal (like 'top').
|
||||
Checks for clear screen/cursor movement escape sequences.
|
||||
|
||||
Manual reproduction:
|
||||
$ canbusload -r vcan0@500000
|
||||
> Screen should refresh instead of scrolling.
|
||||
"""
|
||||
target = f"{can_interface}@500000"
|
||||
|
||||
with CanbusloadMonitor(bin_path, ["-r", target]) as monitor:
|
||||
time.sleep(0.5)
|
||||
|
||||
# Usually implies clearing screen or moving cursor home: \x1b[H or \x1b[2J
|
||||
assert "\x1b[" in monitor.output, "Cursor movement/redraw codes not found"
|
||||
|
||||
def test_exact_calculation_e(bin_path, can_interface):
|
||||
"""
|
||||
Description:
|
||||
Tests the -e option for exact calculation of stuffed bits.
|
||||
Ensures the tool runs without error and outputs the 'exact' indicator if applicable
|
||||
or simply produces valid output.
|
||||
|
||||
Manual reproduction:
|
||||
$ canbusload -e vcan0@500000
|
||||
> Header might indicate different mode or output values change slightly.
|
||||
"""
|
||||
target = f"{can_interface}@500000"
|
||||
|
||||
with CanbusloadMonitor(bin_path, ["-e", target]) as monitor:
|
||||
run_cangen(bin_path, can_interface, ["-n", "10"])
|
||||
time.sleep(0.5)
|
||||
|
||||
# Primarily checking that it runs successfully and produces output
|
||||
assert can_interface in monitor.output
|
||||
assert re.search(r'\s+[1-9][0-9]*\s+', monitor.output)
|
||||
|
||||
def test_ignore_bitstuffing_i(bin_path, can_interface):
|
||||
"""
|
||||
Description:
|
||||
Tests the -i option to ignore bitstuffing in bandwidth calculation.
|
||||
|
||||
Manual reproduction:
|
||||
$ canbusload -i vcan0@500000
|
||||
"""
|
||||
target = f"{can_interface}@500000"
|
||||
|
||||
with CanbusloadMonitor(bin_path, ["-i", target]) as monitor:
|
||||
run_cangen(bin_path, can_interface, ["-n", "10"])
|
||||
time.sleep(0.5)
|
||||
|
||||
assert can_interface in monitor.output
|
||||
|
||||
def test_multiple_interfaces(bin_path, can_interface):
|
||||
"""
|
||||
Description:
|
||||
Tests monitoring multiple interfaces simultaneously.
|
||||
Note: Since we usually only have one 'can_interface' fixture,
|
||||
we will simulate the syntax. If the second interface doesn't exist,
|
||||
canbusload might still run or show error for that line, but we check
|
||||
parsing.
|
||||
|
||||
To robustly test, we use the same interface twice with different bitrates
|
||||
(if supported by tool) or just check that the command line is accepted.
|
||||
|
||||
Manual reproduction:
|
||||
$ canbusload vcan0@500000 vcan0@250000
|
||||
> Should list vcan0 twice.
|
||||
"""
|
||||
# Using the same interface twice to ensure they exist.
|
||||
target1 = f"{can_interface}@500000"
|
||||
target2 = f"{can_interface}@250000"
|
||||
|
||||
with CanbusloadMonitor(bin_path, [target1, target2]) as monitor:
|
||||
run_cangen(bin_path, can_interface, ["-n", "10"])
|
||||
time.sleep(1.0)
|
||||
|
||||
# Check that the interface appears at least twice in the output lines
|
||||
# (ignoring the header line)
|
||||
count = monitor.output.count(can_interface)
|
||||
# 1 in command echo (if any), 2 in status lines.
|
||||
# Since we are capturing output, it likely appears multiple times across updates.
|
||||
# We just ensure it's there.
|
||||
assert count >= 2, "Multiple interfaces did not appear in output"
|
||||
|
||||
def test_missing_bitrate_error(bin_path, can_interface):
|
||||
"""
|
||||
Description:
|
||||
Tests that omitting the bitrate results in an error/usage message.
|
||||
The bitrate is mandatory.
|
||||
|
||||
Manual reproduction:
|
||||
$ canbusload vcan0
|
||||
> Should print usage or error.
|
||||
"""
|
||||
# Running without @bitrate
|
||||
result = subprocess.run(
|
||||
[os.path.join(bin_path, "canbusload"), can_interface],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
# Expect failure code or error message
|
||||
assert result.returncode != 0 or "Usage" in result.stdout or "Usage" in result.stderr
|
||||
@@ -0,0 +1,260 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Copyright (c) 2023 Linux CAN project
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License for more details.
|
||||
|
||||
import pytest
|
||||
import subprocess
|
||||
import os
|
||||
import time
|
||||
import signal
|
||||
import re
|
||||
import shutil
|
||||
|
||||
# --- Helper Functions ---
|
||||
|
||||
def run_cangen(bin_path, interface, args):
|
||||
"""
|
||||
Runs cangen to generate traffic.
|
||||
Waits for it to finish (assumes -n is used in args).
|
||||
"""
|
||||
cmd = [os.path.join(bin_path, "cangen"), interface] + args
|
||||
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
class CandumpMonitor:
|
||||
"""
|
||||
Context manager to run candump.
|
||||
Captures stdout/stderr.
|
||||
"""
|
||||
def __init__(self, bin_path, interface, args=None):
|
||||
self.cmd = [os.path.join(bin_path, "candump"), interface]
|
||||
if args:
|
||||
# Insert args before interface if they are options
|
||||
# candump usage: candump [options] <interface>
|
||||
self.cmd = [os.path.join(bin_path, "candump")] + args + [interface]
|
||||
else:
|
||||
self.cmd = [os.path.join(bin_path, "candump"), interface]
|
||||
|
||||
self.process = None
|
||||
self.output = ""
|
||||
self.error = ""
|
||||
|
||||
def __enter__(self):
|
||||
self.process = subprocess.Popen(
|
||||
self.cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
preexec_fn=os.setsid
|
||||
)
|
||||
time.sleep(0.1) # Wait for start
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self.process:
|
||||
if self.process.poll() is None:
|
||||
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
|
||||
self.output, self.error = self.process.communicate()
|
||||
|
||||
# --- Tests for candump ---
|
||||
|
||||
def test_help_option(bin_path):
|
||||
"""Test -h option: Should print usage."""
|
||||
# candump often returns 1 or 0 on help, we check output mainly
|
||||
result = subprocess.run([os.path.join(bin_path, "candump"), "-h"], capture_output=True, text=True)
|
||||
assert "Usage: candump" in result.stdout or "Usage: candump" in result.stderr
|
||||
|
||||
def test_count_n(bin_path, can_interface):
|
||||
"""Test -n <count>: Terminate after reception of <count> CAN frames."""
|
||||
count = 3
|
||||
# Start candump expecting 3 frames
|
||||
with CandumpMonitor(bin_path, can_interface, ["-n", str(count)]) as monitor:
|
||||
# Generate 3 frames
|
||||
run_cangen(bin_path, can_interface, ["-n", str(count)])
|
||||
|
||||
# Wait a bit to ensure candump finishes and exits on its own
|
||||
time.sleep(0.5)
|
||||
|
||||
# Check if process exited
|
||||
assert monitor.process.poll() is not None, "candump did not exit after receiving count frames"
|
||||
|
||||
# Count lines (ignoring empty lines)
|
||||
lines = [l for l in monitor.output.splitlines() if l.strip()]
|
||||
assert len(lines) == count
|
||||
|
||||
def test_timeout_T(bin_path, can_interface):
|
||||
"""Test -T <msecs>: Terminate after timeout if no frames received."""
|
||||
start = time.time()
|
||||
# Run with 500ms timeout
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "candump"), can_interface, "-T", "500"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL
|
||||
)
|
||||
duration = time.time() - start
|
||||
# Should be around 0.5s, definitely less than 2s (safety margin) and more than 0.4s
|
||||
assert 0.4 <= duration <= 1.0
|
||||
|
||||
def test_timestamp_formats(bin_path, can_interface):
|
||||
"""Test -t <type>: timestamp formats."""
|
||||
|
||||
# (a)bsolute
|
||||
with CandumpMonitor(bin_path, can_interface, ["-t", "a"]) as monitor:
|
||||
run_cangen(bin_path, can_interface, ["-n", "1"])
|
||||
assert re.search(r'\(\d+\.\d+\)', monitor.output), "Absolute timestamp missing"
|
||||
|
||||
# (d)elta
|
||||
with CandumpMonitor(bin_path, can_interface, ["-t", "d"]) as monitor:
|
||||
run_cangen(bin_path, can_interface, ["-n", "2", "-g", "10"])
|
||||
# Second line should have a small delta
|
||||
assert re.search(r'\(\d+\.\d+\)', monitor.output), "Delta timestamp missing"
|
||||
|
||||
# (z)ero
|
||||
with CandumpMonitor(bin_path, can_interface, ["-t", "z"]) as monitor:
|
||||
run_cangen(bin_path, can_interface, ["-n", "1"])
|
||||
# Starts with (0.000000) roughly. Note: candump often uses 3 digits for seconds (e.g. 000.000000).
|
||||
# Regex adjusted to accept one or more leading zeros.
|
||||
assert re.search(r'\(0+\.\d+\)', monitor.output), f"Zero timestamp format mismatch: {monitor.output}"
|
||||
|
||||
# (A)bsolute w date
|
||||
with CandumpMonitor(bin_path, can_interface, ["-t", "A"]) as monitor:
|
||||
run_cangen(bin_path, can_interface, ["-n", "1"])
|
||||
# Check for YYYY-MM-DD format
|
||||
assert re.search(r'\(\d{4}-\d{2}-\d{2}', monitor.output), "Date timestamp missing"
|
||||
|
||||
def test_ascii_output_a(bin_path, can_interface):
|
||||
"""Test -a: Additional ASCII output."""
|
||||
# Send known data that is printable ASCII
|
||||
# 0x41 = 'A', 0x42 = 'B'
|
||||
with CandumpMonitor(bin_path, can_interface, ["-a"]) as monitor:
|
||||
# Force DLC to 4 to ensure we get all 4 bytes ("ABCD")
|
||||
run_cangen(bin_path, can_interface, ["-n", "1", "-D", "41424344", "-L", "4"])
|
||||
|
||||
assert "ABCD" in monitor.output or " .ABCD" in monitor.output
|
||||
|
||||
def test_silent_mode_s(bin_path, can_interface):
|
||||
"""Test -s <level>: Silent mode."""
|
||||
|
||||
# Level 0 (Default, not silent)
|
||||
with CandumpMonitor(bin_path, can_interface, ["-s", "0"]) as monitor:
|
||||
run_cangen(bin_path, can_interface, ["-n", "1"])
|
||||
assert len(monitor.output) > 0
|
||||
|
||||
# Level 2 (Silent)
|
||||
with CandumpMonitor(bin_path, can_interface, ["-s", "2"]) as monitor:
|
||||
run_cangen(bin_path, can_interface, ["-n", "1"])
|
||||
assert len(monitor.output.strip()) == 0
|
||||
|
||||
def test_log_file_l(bin_path, can_interface, tmp_path):
|
||||
"""Test -l: Log to file (default name)."""
|
||||
# Resolve bin_path to absolute because we change CWD below
|
||||
bin_path = os.path.abspath(bin_path)
|
||||
|
||||
# Change cwd to tmp_path to avoid littering
|
||||
cwd = os.getcwd()
|
||||
os.chdir(tmp_path)
|
||||
try:
|
||||
# -l implies -s 2 (silent stdout)
|
||||
with CandumpMonitor(bin_path, can_interface, ["-l"]) as monitor:
|
||||
run_cangen(bin_path, can_interface, ["-n", "1"])
|
||||
|
||||
# Check if file starting with candump- exists
|
||||
files = [f for f in os.listdir('.') if f.startswith('candump-')]
|
||||
assert len(files) > 0, "Log file not created"
|
||||
|
||||
# Check content
|
||||
with open(files[0], 'r') as f:
|
||||
content = f.read()
|
||||
assert can_interface in content
|
||||
finally:
|
||||
os.chdir(cwd)
|
||||
|
||||
def test_specific_log_file_f(bin_path, can_interface, tmp_path):
|
||||
"""Test -f <fname>: Log to specific file."""
|
||||
logfile = os.path.join(tmp_path, "test.log")
|
||||
|
||||
with CandumpMonitor(bin_path, can_interface, ["-f", logfile]) as monitor:
|
||||
run_cangen(bin_path, can_interface, ["-n", "1"])
|
||||
|
||||
assert os.path.exists(logfile)
|
||||
with open(logfile, 'r') as f:
|
||||
content = f.read()
|
||||
assert can_interface in content
|
||||
|
||||
def test_log_format_stdout_L(bin_path, can_interface):
|
||||
"""Test -L: Log file format on stdout."""
|
||||
with CandumpMonitor(bin_path, can_interface, ["-L"]) as monitor:
|
||||
run_cangen(bin_path, can_interface, ["-n", "1"])
|
||||
|
||||
# Format: (timestamp) interface ID#DATA
|
||||
assert re.search(r'\(\d+\.\d+\)\s+' + can_interface + r'\s+[0-9A-F]+#', monitor.output)
|
||||
|
||||
def test_raw_dlc_8(bin_path, can_interface):
|
||||
"""Test -8: Display raw DLC in {} for Classical CAN."""
|
||||
# Send a classic CAN frame
|
||||
with CandumpMonitor(bin_path, can_interface, ["-8"]) as monitor:
|
||||
run_cangen(bin_path, can_interface, ["-n", "1", "-L", "4"])
|
||||
|
||||
# Look for [4] or {4} depending on output format with -8
|
||||
# Standard candump: "vcan0 123 [4] 11 22 33 44"
|
||||
# With -8: "vcan0 123 {4} [4] 11 22 33 44" (Wait, man page says "display raw DLC in {}")
|
||||
# Let's check for the curly braces
|
||||
assert "{" in monitor.output and "}" in monitor.output
|
||||
|
||||
def test_extra_info_x(bin_path, can_interface):
|
||||
"""Test -x: Print extra message info (RX/TX, etc.)."""
|
||||
with CandumpMonitor(bin_path, can_interface, ["-x"]) as monitor:
|
||||
run_cangen(bin_path, can_interface, ["-n", "1"])
|
||||
|
||||
# Usually adds "RX - - " or similar at the end
|
||||
assert "RX" in monitor.output or "TX" in monitor.output
|
||||
|
||||
def test_filters(bin_path, can_interface):
|
||||
"""Test CAN filters on command line."""
|
||||
|
||||
# Filter for ID 123
|
||||
# Command: candump vcan0,123:7FF
|
||||
|
||||
# 1. Send ID 123 (Should receive)
|
||||
cmd_match = [can_interface + ",123:7FF"] # Argument is "interface,filter"
|
||||
with CandumpMonitor(bin_path, cmd_match[0], args=[]) as monitor:
|
||||
# Note: CandumpMonitor logic needs slightly adjustment if interface arg contains comma
|
||||
# But here we pass it as the "interface" argument to the class which works for valid invocation
|
||||
run_cangen(bin_path, can_interface, ["-n", "1", "-I", "123"])
|
||||
assert "123" in monitor.output
|
||||
|
||||
# 2. Send ID 456 (Should NOT receive)
|
||||
with CandumpMonitor(bin_path, cmd_match[0], args=[]) as monitor:
|
||||
run_cangen(bin_path, can_interface, ["-n", "1", "-I", "456"])
|
||||
assert "456" not in monitor.output
|
||||
|
||||
def test_inverse_filter(bin_path, can_interface):
|
||||
"""Test inverse filter (~)."""
|
||||
# Filter: Everything EXCEPT ID 123
|
||||
# Command: candump vcan0,123~7FF
|
||||
|
||||
arg = can_interface + ",123~7FF"
|
||||
|
||||
# Send 123 (Should NOT receive)
|
||||
with CandumpMonitor(bin_path, arg) as monitor:
|
||||
run_cangen(bin_path, can_interface, ["-n", "1", "-I", "123"])
|
||||
assert "123" not in monitor.output
|
||||
|
||||
# Send 456 (Should receive)
|
||||
with CandumpMonitor(bin_path, arg) as monitor:
|
||||
run_cangen(bin_path, can_interface, ["-n", "1", "-I", "456"])
|
||||
assert "456" in monitor.output
|
||||
|
||||
def test_swap_byte_order_S(bin_path, can_interface):
|
||||
"""Test -S: Swap byte order."""
|
||||
# Send 11223344
|
||||
# -S should display it swapped/marked.
|
||||
# Warning: -S only affects printed output, usually prints big-endian vs little-endian representation
|
||||
|
||||
with CandumpMonitor(bin_path, can_interface, ["-S"]) as monitor:
|
||||
run_cangen(bin_path, can_interface, ["-n", "1", "-D", "11223344", "-L", "4"])
|
||||
|
||||
# The help says "marked with '`'".
|
||||
assert "`" in monitor.output
|
||||
@@ -0,0 +1,164 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import os
|
||||
import pytest
|
||||
import signal
|
||||
import tempfile
|
||||
|
||||
# Note: bin_path and interface fixtures are provided implicitly by conftest.py
|
||||
|
||||
def test_usage_error(bin_path):
|
||||
"""
|
||||
Test the behavior when no arguments are provided.
|
||||
|
||||
Description:
|
||||
The tool requires at least an interface name. Running without arguments
|
||||
displays the usage text.
|
||||
Note: The tool exits with code 0 even on missing arguments.
|
||||
|
||||
Manual Reproduction:
|
||||
Run: ./canerrsim
|
||||
Expect: Output showing Usage info and exit code 0.
|
||||
"""
|
||||
canerrsim = os.path.join(bin_path, "canerrsim")
|
||||
|
||||
if not os.path.exists(canerrsim):
|
||||
pytest.skip(f"Binary {canerrsim} not found. Please build it first.")
|
||||
|
||||
result = subprocess.run(
|
||||
[canerrsim],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
# Expect success code (0) as per tool implementation
|
||||
assert result.returncode == 0
|
||||
# Expect usage hint in output
|
||||
assert "Usage: canerrsim" in result.stderr or "Usage: canerrsim" in result.stdout
|
||||
|
||||
def test_basic_can_traffic(bin_path, can_interface):
|
||||
"""
|
||||
Sanity check: Verify that standard CAN frames are looped back correctly.
|
||||
If this fails, vcan is broken or not up.
|
||||
"""
|
||||
cansend = os.path.join(bin_path, "cansend")
|
||||
candump = os.path.join(bin_path, "candump")
|
||||
|
||||
if not os.path.exists(cansend) or not os.path.exists(candump):
|
||||
pytest.skip("cansend or candump not found.")
|
||||
|
||||
with tempfile.TemporaryFile(mode='w+') as tmp_out:
|
||||
candump_proc = subprocess.Popen(
|
||||
[candump, can_interface],
|
||||
stdout=tmp_out,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
time.sleep(0.2)
|
||||
|
||||
# Send standard frame 123#112233
|
||||
subprocess.run([cansend, can_interface, "123#112233"], check=True)
|
||||
time.sleep(0.2)
|
||||
|
||||
candump_proc.terminate()
|
||||
candump_proc.wait()
|
||||
|
||||
tmp_out.seek(0)
|
||||
output = tmp_out.read()
|
||||
|
||||
assert "123" in output and "11 22 33" in output, "Standard CAN frame loopback failed on vcan0"
|
||||
|
||||
@pytest.mark.parametrize("args, grep_for", [
|
||||
(["NoAck"], "ERRORFRAME"),
|
||||
(["Data0=AA", "Data1=BB"], "AA BB"),
|
||||
(["TxTimeout"], "ERRORFRAME"),
|
||||
])
|
||||
def test_canerrsim_generate_errors(bin_path, can_interface, args, grep_for):
|
||||
"""
|
||||
Test generating specific error frames using canerrsim.
|
||||
|
||||
Description:
|
||||
1. Start candump in background with error frames enabled (-e).
|
||||
2. Run canerrsim with specific error options.
|
||||
3. Verify candump received the error frame matching the criteria.
|
||||
|
||||
Feature Detection:
|
||||
Checks if the environment supports error frame loopback on vcan.
|
||||
Skips if not supported.
|
||||
|
||||
Manual Reproduction:
|
||||
1. Terminal 1: candump -e vcan0
|
||||
2. Terminal 2: ./canerrsim vcan0 <args>
|
||||
"""
|
||||
canerrsim = os.path.join(bin_path, "canerrsim")
|
||||
candump = os.path.join(bin_path, "candump")
|
||||
|
||||
if not os.path.exists(canerrsim):
|
||||
pytest.skip(f"Binary {canerrsim} not found.")
|
||||
|
||||
# --- Feature Detection Step ---
|
||||
# Try to verify if we can receive ANY error frame before running specific tests
|
||||
with tempfile.TemporaryFile(mode='w+') as probe_out:
|
||||
probe_proc = subprocess.Popen(
|
||||
[candump, "-e", can_interface],
|
||||
stdout=probe_out,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
time.sleep(0.2)
|
||||
# Send a simple error frame
|
||||
subprocess.run([canerrsim, can_interface, "NoAck"],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
time.sleep(0.2)
|
||||
probe_proc.terminate()
|
||||
probe_proc.wait()
|
||||
|
||||
probe_out.seek(0)
|
||||
if "ERRORFRAME" not in probe_out.read():
|
||||
pytest.skip("Environment does not support loopback of user-space generated CAN error frames on vcan.")
|
||||
|
||||
# --- Actual Test ---
|
||||
with tempfile.TemporaryFile(mode='w+') as tmp_out:
|
||||
# Start candump to capture the error frame
|
||||
candump_proc = subprocess.Popen(
|
||||
[candump, "-e", can_interface],
|
||||
stdout=tmp_out,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
try:
|
||||
time.sleep(0.5)
|
||||
# Run canerrsim
|
||||
cmd = [canerrsim, can_interface] + args
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
timeout=2
|
||||
)
|
||||
|
||||
assert result.returncode == 0, f"canerrsim failed with args: {args}"
|
||||
time.sleep(0.5)
|
||||
|
||||
finally:
|
||||
candump_proc.terminate()
|
||||
try:
|
||||
candump_proc.wait(timeout=1)
|
||||
except subprocess.TimeoutExpired:
|
||||
candump_proc.kill()
|
||||
candump_proc.wait()
|
||||
|
||||
# Rewind file to read captured output
|
||||
tmp_out.seek(0)
|
||||
stdout = tmp_out.read()
|
||||
|
||||
# Validation
|
||||
print(f"--- Candump Output ---\n{stdout}")
|
||||
|
||||
found = grep_for in stdout or grep_for.lower() in stdout or grep_for.upper() in stdout
|
||||
assert found, f"Expected '{grep_for}' in candump output for args {args}"
|
||||
@@ -0,0 +1,105 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import signal
|
||||
import os
|
||||
import pytest
|
||||
|
||||
# Note: bin_path and interface fixtures are provided implicitly by conftest.py
|
||||
|
||||
def test_usage_error(bin_path):
|
||||
"""
|
||||
Test the behavior when an invalid option is provided.
|
||||
|
||||
Description:
|
||||
The tool should reject invalid flags (like '-h' which is not implemented
|
||||
as a success flag in this tool) and return a non-zero exit code.
|
||||
|
||||
Manual Reproduction:
|
||||
Run: ./canfdtest -h
|
||||
Expect: Output showing Usage info and exit code != 0.
|
||||
"""
|
||||
canfdtest = os.path.join(bin_path, "canfdtest")
|
||||
|
||||
if not os.path.exists(canfdtest):
|
||||
pytest.skip(f"Binary {canfdtest} not found. Please build it first.")
|
||||
|
||||
result = subprocess.run(
|
||||
[canfdtest, "-h"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
# Expect failure code
|
||||
assert result.returncode != 0
|
||||
# Expect usage hint in output
|
||||
assert "Usage: canfdtest" in result.stderr or "Usage: canfdtest" in result.stdout
|
||||
|
||||
@pytest.mark.parametrize("extra_args", [
|
||||
[], # Standard CAN
|
||||
["-d"], # CAN FD
|
||||
["-e"], # Extended Frames (29-bit)
|
||||
["-b", "-d"], # CAN FD with Bit Rate Switch
|
||||
])
|
||||
def test_full_duplex_communication(bin_path, can_interface, extra_args):
|
||||
"""
|
||||
Test full-duplex communication (ping-pong) between two instances.
|
||||
|
||||
Description:
|
||||
Simulates a DUT (Device Under Test) and a Host on the same interface.
|
||||
1. DUT: Runs in background (`canfdtest -v <iface>`), echoing frames.
|
||||
2. Host: Runs in foreground (`canfdtest -g -v <iface>`), generating frames.
|
||||
3. Checks if the Host process exits successfully (0).
|
||||
|
||||
Manual Reproduction:
|
||||
1. Terminal 1: ./canfdtest -v vcan0 (MUST use same flags -d/-e as Host)
|
||||
2. Terminal 2: ./canfdtest -g -v -l 10 vcan0 (add -d or -e as needed)
|
||||
"""
|
||||
canfdtest = os.path.join(bin_path, "canfdtest")
|
||||
|
||||
if not os.path.exists(canfdtest):
|
||||
pytest.skip(f"Binary {canfdtest} not found. Please build it first.")
|
||||
|
||||
# 1. Start DUT (echo server)
|
||||
# DUT must also receive flags like -d (FD) or -e (Extended) to open the socket correctly
|
||||
# We use setsid to ensure we can kill the process group later
|
||||
dut_cmd = [canfdtest, "-v"] + extra_args + [can_interface]
|
||||
dut_proc = subprocess.Popen(
|
||||
dut_cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
preexec_fn=os.setsid
|
||||
)
|
||||
|
||||
try:
|
||||
# Give DUT time to initialize socket
|
||||
time.sleep(0.2)
|
||||
|
||||
# 2. Start Host (generator)
|
||||
# -g: generate, -l 10: 10 loops
|
||||
host_cmd = [canfdtest, "-g", "-v", "-l", "10"] + extra_args + [can_interface]
|
||||
|
||||
host_result = subprocess.run(
|
||||
host_cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
timeout=5 # Test should be very fast
|
||||
)
|
||||
|
||||
# 3. Validation
|
||||
if host_result.returncode != 0:
|
||||
print(f"--- Host Stdout ---\n{host_result.stdout}")
|
||||
print(f"--- Host Stderr ---\n{host_result.stderr}")
|
||||
|
||||
assert host_result.returncode == 0, f"Host failed with args: {extra_args}"
|
||||
# The tool outputs "Test messages sent and received: N" on success, not "Test successful"
|
||||
assert "Test messages sent and received" in host_result.stdout
|
||||
|
||||
finally:
|
||||
# 4. Teardown: Kill DUT
|
||||
if dut_proc.poll() is None:
|
||||
os.killpg(os.getpgid(dut_proc.pid), signal.SIGTERM)
|
||||
dut_proc.wait()
|
||||
@@ -0,0 +1,364 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Copyright (c) 2023 Linux CAN project
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License for more details.
|
||||
|
||||
import pytest
|
||||
import subprocess
|
||||
import os
|
||||
import time
|
||||
import signal
|
||||
import re
|
||||
|
||||
# --- Helper Functions ---
|
||||
|
||||
def parse_candump_line(line):
|
||||
"""
|
||||
Parses a line from 'candump -L' (logging format).
|
||||
Format: (timestamp) interface ID#DATA or ID##FLAGS_DATA (FD)
|
||||
Returns a dict with 'id', 'data', 'flags' (for FD) or None if parsing fails.
|
||||
"""
|
||||
# Regex for standard CAN: (123.456) vcan0 123#112233
|
||||
match_std = re.search(r'\(\d+\.\d+\)\s+\S+\s+([0-9A-Fa-f]+)#([0-9A-Fa-f]*)', line)
|
||||
if match_std:
|
||||
return {'id': match_std.group(1), 'data': match_std.group(2), 'type': 'classic'}
|
||||
|
||||
# Regex for CAN FD: (123.456) vcan0 123##<flags><data>
|
||||
match_fd = re.search(r'\(\d+\.\d+\)\s+\S+\s+([0-9A-Fa-f]+)##([0-9A-Fa-f]+)', line)
|
||||
if match_fd:
|
||||
# In log format, data usually follows flags. Not splitting strictly here,
|
||||
# just capturing the payload block for verification.
|
||||
return {'id': match_fd.group(1), 'payload_raw': match_fd.group(2), 'type': 'fd'}
|
||||
|
||||
return None
|
||||
|
||||
class TrafficMonitor:
|
||||
"""Context manager to run candump in the background."""
|
||||
def __init__(self, bin_path, interface, args=None):
|
||||
self.cmd = [os.path.join(bin_path, "candump"), "-L", interface]
|
||||
if args:
|
||||
self.cmd.extend(args)
|
||||
self.process = None
|
||||
self.output = ""
|
||||
|
||||
def __enter__(self):
|
||||
self.process = subprocess.Popen(
|
||||
self.cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
preexec_fn=os.setsid
|
||||
)
|
||||
time.sleep(0.2) # Wait for socket bind
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self.process:
|
||||
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
|
||||
out, _ = self.process.communicate()
|
||||
self.output = out
|
||||
|
||||
# --- Tests for cangen ---
|
||||
|
||||
def test_help_option(bin_path):
|
||||
"""Test -h option: Should print usage and exit with 1."""
|
||||
result = subprocess.run([os.path.join(bin_path, "cangen"), "-h"], capture_output=True, text=True)
|
||||
assert result.returncode == 1
|
||||
assert "Usage: cangen" in result.stderr
|
||||
|
||||
def test_count_n(bin_path, can_interface):
|
||||
"""Test -n <count>: Generate exact number of frames."""
|
||||
count = 5
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", str(count)],
|
||||
check=True
|
||||
)
|
||||
|
||||
lines = [l for l in monitor.output.splitlines() if can_interface in l]
|
||||
assert len(lines) == count, f"Expected {count} frames, got {len(lines)}"
|
||||
|
||||
def test_gap_g(bin_path, can_interface):
|
||||
"""Test -g <ms>: Gap generation (timing check)."""
|
||||
# Send 5 frames with 100ms gap -> should take approx 400-500ms
|
||||
start = time.time()
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "5", "-g", "100"],
|
||||
check=True
|
||||
)
|
||||
duration = time.time() - start
|
||||
# Allow some tolerance (e.g., at least 0.4s for 4 gaps)
|
||||
assert duration >= 0.4, f"Gap logic too fast: {duration}s"
|
||||
|
||||
def test_absolute_time_a(bin_path, can_interface):
|
||||
"""Test -a: Use absolute time for gap (Functional check)."""
|
||||
# Difficult to verify timing strictly without real-time analysis, checking for successful execution
|
||||
result = subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "2", "-g", "10", "-a"],
|
||||
check=True
|
||||
)
|
||||
assert result.returncode == 0
|
||||
|
||||
def test_txtime_t(bin_path, can_interface):
|
||||
"""Test -t: Use SO_TXTIME (Functional check)."""
|
||||
# Requires kernel support, checking for no crash
|
||||
result = subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "1", "-t"],
|
||||
capture_output=True
|
||||
)
|
||||
# Don't assert 0 explicitly if kernel might not support it, but vcan usually works.
|
||||
if result.returncode != 0:
|
||||
pytest.skip("Kernel might not support SO_TXTIME or config issue")
|
||||
|
||||
def test_start_time_option(bin_path, can_interface):
|
||||
"""Test --start <ns>: Start time (Functional check)."""
|
||||
# Using a timestamp in the past to ensure immediate execution or future for delay
|
||||
# Just checking argument parsing valid
|
||||
result = subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "1", "--start", "0"],
|
||||
check=True
|
||||
)
|
||||
assert result.returncode == 0
|
||||
|
||||
def test_extended_frames_e(bin_path, can_interface):
|
||||
"""Test -e: Extended frame mode (EFF)."""
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "1", "-e"],
|
||||
check=True
|
||||
)
|
||||
|
||||
parsed = parse_candump_line(monitor.output.strip())
|
||||
assert parsed, "No frame captured"
|
||||
# Extended IDs are usually shown with 8 chars in candump or check length > 3
|
||||
# Standard: 3 chars (e.g., 123), Extended: 8 chars (e.g., 12345678)
|
||||
# Note: cangen generates random IDs.
|
||||
assert len(parsed['id']) == 8, f"Expected 8-char hex ID for EFF, got {parsed['id']}"
|
||||
|
||||
def test_can_fd_f(bin_path, can_interface):
|
||||
"""Test -f: CAN FD frames."""
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "1", "-f"],
|
||||
check=True
|
||||
)
|
||||
|
||||
# candump -L for FD uses '##' separator
|
||||
assert "##" in monitor.output, "CAN FD separator '##' not found in candump output"
|
||||
|
||||
def test_can_fd_brs_b(bin_path, can_interface):
|
||||
"""Test -b: CAN FD with Bitrate Switch (BRS)."""
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "1", "-b"],
|
||||
check=True
|
||||
)
|
||||
assert "##" in monitor.output
|
||||
|
||||
def test_can_fd_esi_E(bin_path, can_interface):
|
||||
"""Test -E: CAN FD with Error State Indicator (ESI)."""
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "1", "-E"],
|
||||
check=True
|
||||
)
|
||||
assert "##" in monitor.output
|
||||
|
||||
def test_can_xl_X(bin_path, can_interface):
|
||||
"""Test -X: CAN XL frames."""
|
||||
# This might fail on older kernels/interfaces. We skip if return code is error.
|
||||
result = subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "1", "-X"],
|
||||
capture_output=True
|
||||
)
|
||||
if result.returncode != 0:
|
||||
pytest.skip("CAN XL not supported by this environment/kernel")
|
||||
|
||||
def test_rtr_frames_R(bin_path, can_interface):
|
||||
"""Test -R: Remote Transmission Request (RTR)."""
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "1", "-R"],
|
||||
check=True
|
||||
)
|
||||
|
||||
# candump -L often represents RTR with 'R' in the data part or 'remote' flag
|
||||
# For candump -L: "vcan0 123#R"
|
||||
assert "#R" in monitor.output, f"RTR flag not found in: {monitor.output}"
|
||||
|
||||
def test_dlc_greater_8_option_8(bin_path, can_interface):
|
||||
"""Test -8: Allow DLC > 8 for Classic CAN."""
|
||||
# This generates frames with DLC > 8 but len 8.
|
||||
# It's a specific protocol violation test.
|
||||
result = subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "1", "-8"],
|
||||
check=True
|
||||
)
|
||||
assert result.returncode == 0
|
||||
|
||||
def test_mix_modes_m(bin_path, can_interface):
|
||||
"""Test -m: Mix CC, FD, XL."""
|
||||
result = subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "5", "-m"],
|
||||
check=True
|
||||
)
|
||||
assert result.returncode == 0
|
||||
|
||||
def test_id_generation_mode_I(bin_path, can_interface):
|
||||
"""Test -I <mode>: ID generation."""
|
||||
|
||||
# Fixed ID
|
||||
target_id = "123"
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "1", "-I", target_id],
|
||||
check=True
|
||||
)
|
||||
parsed = parse_candump_line(monitor.output)
|
||||
assert parsed['id'] == target_id
|
||||
|
||||
# Increment ID ('i')
|
||||
# Generate 3 frames, IDs should be consecutive (or incrementing)
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "3", "-I", "i"],
|
||||
check=True
|
||||
)
|
||||
lines = monitor.output.strip().splitlines()
|
||||
ids = [int(parse_candump_line(l)['id'], 16) for l in lines]
|
||||
assert len(ids) == 3
|
||||
assert ids[1] == ids[0] + 1
|
||||
assert ids[2] == ids[1] + 1
|
||||
|
||||
def test_dlc_generation_mode_L(bin_path, can_interface):
|
||||
"""Test -L <mode>: DLC/Length generation."""
|
||||
|
||||
# Fixed Length 4
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "1", "-L", "4"],
|
||||
check=True
|
||||
)
|
||||
parsed = parse_candump_line(monitor.output)
|
||||
# Data hex string length should be 2 * DLC (e.g. 4 bytes = 8 chars)
|
||||
assert len(parsed['data']) == 8
|
||||
|
||||
def test_data_generation_mode_D(bin_path, can_interface):
|
||||
"""Test -D <mode>: Data content generation."""
|
||||
|
||||
# Fixed Payload
|
||||
payload = "11223344"
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "1", "-D", payload, "-L", "4"],
|
||||
check=True
|
||||
)
|
||||
parsed = parse_candump_line(monitor.output)
|
||||
assert parsed['data'] == payload
|
||||
|
||||
# Increment Payload ('i')
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "2", "-D", "i", "-L", "1"],
|
||||
check=True
|
||||
)
|
||||
lines = monitor.output.strip().splitlines()
|
||||
data_bytes = [int(parse_candump_line(l)['data'], 16) for l in lines]
|
||||
# The first byte should increment (modulo 256)
|
||||
assert (data_bytes[1] - data_bytes[0]) % 256 == 1
|
||||
|
||||
def test_disable_loopback_x(bin_path, can_interface):
|
||||
"""Test -x: Disable loopback."""
|
||||
# If loopback is disabled, candump on the SAME interface (same socket namespace)
|
||||
# should NOT receive the frames if it's running on the same host usually.
|
||||
# Note: On vcan, local loopback is handled by the driver.
|
||||
# cangen -x sets CAN_RAW_LOOPBACK = 0.
|
||||
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "3", "-x"],
|
||||
check=True
|
||||
)
|
||||
|
||||
# We expect output to be empty because candump is a local socket on the same node
|
||||
# and we disabled loopback for the sender.
|
||||
assert monitor.output.strip() == "", "candump received frames despite -x (disable loopback)"
|
||||
|
||||
def test_poll_p(bin_path, can_interface):
|
||||
"""Test -p <timeout>: Poll on ENOBUFS."""
|
||||
# Functional test, hard to provoke ENOBUFS on empty vcan
|
||||
result = subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "1", "-p", "10"],
|
||||
check=True
|
||||
)
|
||||
assert result.returncode == 0
|
||||
|
||||
def test_priority_P(bin_path, can_interface):
|
||||
"""Test -P <priority>: Set socket priority."""
|
||||
result = subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "1", "-P", "5"],
|
||||
check=True
|
||||
)
|
||||
assert result.returncode == 0
|
||||
|
||||
def test_ignore_enobufs_i(bin_path, can_interface):
|
||||
"""Test -i: Ignore ENOBUFS."""
|
||||
result = subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "1", "-i"],
|
||||
check=True
|
||||
)
|
||||
assert result.returncode == 0
|
||||
|
||||
def test_burst_c(bin_path, can_interface):
|
||||
"""Test -c <count>: Burst count."""
|
||||
count = 6
|
||||
burst = 3
|
||||
# Sending 6 frames in bursts of 3. Total should still be 6.
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", str(count), "-c", str(burst)],
|
||||
check=True
|
||||
)
|
||||
lines = [l for l in monitor.output.splitlines() if can_interface in l]
|
||||
assert len(lines) == count
|
||||
|
||||
def test_verbose_v(bin_path, can_interface):
|
||||
"""Test -v: Verbose output to stdout."""
|
||||
# -v prints ascii art/dots, -v -v prints details
|
||||
result = subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "1", "-v", "-v"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
# Output should contain the Interface and ID
|
||||
assert can_interface in result.stdout
|
||||
# The output format is like: " vcan0 737 [3] EF 32 23"
|
||||
# It does not contain labels like "ID:", "data:", "DLC:".
|
||||
# Check for the presence of brackets which indicate DLC in this format.
|
||||
assert "[" in result.stdout and "]" in result.stdout
|
||||
|
||||
def test_random_id_flags(bin_path, can_interface):
|
||||
"""Test ID generation flags: r (random), e (even), o (odd)."""
|
||||
# Test Even
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "5", "-I", "e"],
|
||||
check=True
|
||||
)
|
||||
lines = monitor.output.strip().splitlines()
|
||||
for l in lines:
|
||||
pid = int(parse_candump_line(l)['id'], 16)
|
||||
assert pid % 2 == 0, f"ID {pid} is not even"
|
||||
|
||||
# Test Odd
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "cangen"), can_interface, "-n", "5", "-I", "o"],
|
||||
check=True
|
||||
)
|
||||
lines = monitor.output.strip().splitlines()
|
||||
for l in lines:
|
||||
pid = int(parse_candump_line(l)['id'], 16)
|
||||
assert pid % 2 != 0, f"ID {pid} is not odd"
|
||||
@@ -0,0 +1,286 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Copyright (c) 2023 Linux CAN project
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License for more details.
|
||||
|
||||
import pytest
|
||||
import subprocess
|
||||
import os
|
||||
import time
|
||||
import signal
|
||||
import socket
|
||||
import select
|
||||
|
||||
# --- Helper Functions ---
|
||||
|
||||
def get_free_port():
|
||||
"""Finds a free TCP port on localhost."""
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
# Bind to port 0 lets the OS choose an available port
|
||||
s.bind(('localhost', 0))
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
# Get the port chosen by the OS
|
||||
return s.getsockname()[1]
|
||||
|
||||
class CanLogServerMonitor:
|
||||
"""
|
||||
Context manager to run canlogserver.
|
||||
"""
|
||||
def __init__(self, bin_path, interface, args=None, port=None):
|
||||
self.cmd = [os.path.join(bin_path, "canlogserver")]
|
||||
|
||||
# Determine port: use provided one or find a free one
|
||||
if port is None:
|
||||
self.port = get_free_port()
|
||||
else:
|
||||
self.port = port
|
||||
|
||||
# Explicitly add port argument
|
||||
self.cmd.extend(["-p", str(self.port)])
|
||||
|
||||
if args:
|
||||
self.cmd.extend(args)
|
||||
|
||||
self.cmd.append(interface)
|
||||
|
||||
self.process = None
|
||||
self.client_socket = None
|
||||
|
||||
def __enter__(self):
|
||||
# Redirect stderr to DEVNULL to prevent buffer filling deadlocks
|
||||
# If the server writes too much to stderr and we don't read it, it hangs.
|
||||
self.process = subprocess.Popen(
|
||||
self.cmd,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
text=True,
|
||||
preexec_fn=os.setsid
|
||||
)
|
||||
time.sleep(0.5) # Wait for server startup
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self.client_socket:
|
||||
try:
|
||||
self.client_socket.close()
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
if self.process:
|
||||
# Check if process is still running before attempting to kill
|
||||
if self.process.poll() is None:
|
||||
try:
|
||||
# Send SIGTERM to the process group
|
||||
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
|
||||
# Wait with timeout to avoid infinite hang
|
||||
self.process.wait(timeout=2)
|
||||
except (ProcessLookupError, subprocess.TimeoutExpired):
|
||||
# Force kill if it doesn't exit nicely
|
||||
try:
|
||||
os.killpg(os.getpgid(self.process.pid), signal.SIGKILL)
|
||||
self.process.wait(timeout=1)
|
||||
except (ProcessLookupError, subprocess.TimeoutExpired):
|
||||
pass
|
||||
|
||||
def connect(self):
|
||||
"""Connects to the canlogserver via TCP."""
|
||||
retries = 10
|
||||
for i in range(retries):
|
||||
try:
|
||||
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.client_socket.connect(('localhost', self.port))
|
||||
return True
|
||||
except ConnectionRefusedError:
|
||||
time.sleep(0.2)
|
||||
return False
|
||||
|
||||
def read_data(self, timeout=2.0):
|
||||
"""Reads data from the TCP socket until data arrives or timeout."""
|
||||
if not self.client_socket:
|
||||
return ""
|
||||
|
||||
start_time = time.time()
|
||||
received_data = ""
|
||||
|
||||
while time.time() - start_time < timeout:
|
||||
try:
|
||||
ready = select.select([self.client_socket], [], [], 0.1)
|
||||
if ready[0]:
|
||||
chunk = self.client_socket.recv(4096).decode('utf-8', errors='ignore')
|
||||
if chunk:
|
||||
received_data += chunk
|
||||
if len(chunk) > 0:
|
||||
time.sleep(0.1)
|
||||
ready_more = select.select([self.client_socket], [], [], 0)
|
||||
if ready_more[0]:
|
||||
received_data += self.client_socket.recv(4096).decode('utf-8', errors='ignore')
|
||||
break
|
||||
else:
|
||||
# Connection closed
|
||||
break
|
||||
except OSError:
|
||||
break
|
||||
|
||||
return received_data
|
||||
|
||||
def run_cansend(bin_path, interface, frame):
|
||||
"""Executes cansend."""
|
||||
subprocess.run([os.path.join(bin_path, "cansend"), interface, frame], check=True)
|
||||
|
||||
# --- Tests for canlogserver ---
|
||||
|
||||
def test_help_option(bin_path):
|
||||
"""
|
||||
Tests the help option '-h'.
|
||||
Manual reproduction: $ ./canlogserver -h
|
||||
"""
|
||||
result = subprocess.run([os.path.join(bin_path, "canlogserver"), "-h"], capture_output=True, text=True)
|
||||
assert "Usage: canlogserver" in result.stderr or "Usage: canlogserver" in result.stdout
|
||||
|
||||
def test_basic_logging(bin_path, can_interface):
|
||||
"""
|
||||
Tests basic logging functionality.
|
||||
Manual reproduction:
|
||||
$ ./canlogserver vcan0 &
|
||||
$ nc localhost 28700
|
||||
$ ./cansend vcan0 123#11
|
||||
"""
|
||||
with CanLogServerMonitor(bin_path, can_interface) as server:
|
||||
assert server.connect(), f"Could not connect to canlogserver on port {server.port}"
|
||||
run_cansend(bin_path, can_interface, "123#112233")
|
||||
data = server.read_data(timeout=2.0)
|
||||
assert "123" in data
|
||||
assert "112233" in data
|
||||
|
||||
def test_custom_port(bin_path, can_interface):
|
||||
"""
|
||||
Tests the custom port option '-p'.
|
||||
Manual reproduction: ./canlogserver -p 28701 vcan0
|
||||
"""
|
||||
custom_port = get_free_port()
|
||||
with CanLogServerMonitor(bin_path, can_interface, port=custom_port) as server:
|
||||
assert server.connect(), f"Could not connect to canlogserver on port {custom_port}"
|
||||
run_cansend(bin_path, can_interface, "456#AA")
|
||||
data = server.read_data(timeout=2.0)
|
||||
assert "456" in data
|
||||
|
||||
def test_id_filter_mask_value(bin_path, can_interface):
|
||||
"""
|
||||
Tests ID filtering with mask (-m) and value (-v).
|
||||
Manual reproduction: ./canlogserver -m 0x7FF -v 0x123 vcan0
|
||||
"""
|
||||
args = ["-m", "0x7FF", "-v", "0x123"]
|
||||
with CanLogServerMonitor(bin_path, can_interface, args=args) as server:
|
||||
assert server.connect()
|
||||
run_cansend(bin_path, can_interface, "123#11")
|
||||
data = server.read_data(timeout=2.0)
|
||||
assert "123" in data
|
||||
|
||||
run_cansend(bin_path, can_interface, "456#22")
|
||||
data = server.read_data(timeout=1.0)
|
||||
assert "456" not in data
|
||||
|
||||
def test_id_filter_invert(bin_path, can_interface):
|
||||
"""
|
||||
Tests the inverted filter option '-i'.
|
||||
Manual reproduction: ./canlogserver -m 0x7FF -v 0x123 -i 1 vcan0
|
||||
"""
|
||||
args = ["-m", "0x7FF", "-v", "0x123", "-i", "1"]
|
||||
with CanLogServerMonitor(bin_path, can_interface, args=args) as server:
|
||||
assert server.connect()
|
||||
run_cansend(bin_path, can_interface, "123#11")
|
||||
data = server.read_data(timeout=1.0)
|
||||
assert "123" not in data
|
||||
|
||||
run_cansend(bin_path, can_interface, "456#22")
|
||||
data = server.read_data(timeout=2.0)
|
||||
assert "456" in data
|
||||
|
||||
def test_multiple_interfaces(bin_path, can_interface):
|
||||
"""
|
||||
Tests passing multiple interfaces.
|
||||
Manual reproduction: ./canlogserver vcan0 vcan0
|
||||
"""
|
||||
with CanLogServerMonitor(bin_path, can_interface, args=[can_interface]) as server:
|
||||
assert server.connect(), "Could not connect to canlogserver with multiple interfaces"
|
||||
|
||||
def test_error_frame_mask(bin_path, can_interface):
|
||||
"""
|
||||
Tests the error frame mask option '-e'.
|
||||
Manual reproduction: ./canlogserver -e 0xFFFFFFFF vcan0
|
||||
"""
|
||||
args = ["-e", "0xFFFFFFFF"]
|
||||
with CanLogServerMonitor(bin_path, can_interface, args=args) as server:
|
||||
assert server.connect()
|
||||
run_cansend(bin_path, can_interface, "123#11")
|
||||
data = server.read_data(timeout=2.0)
|
||||
assert "123" in data
|
||||
|
||||
def test_signal_sigint_shutdown(bin_path, can_interface):
|
||||
"""
|
||||
Tests the server shutdown on SIGINT (Ctrl-C) during 'accept()'.
|
||||
|
||||
Verifies that the server terminates correctly when receiving SIGINT while
|
||||
waiting for a client connection.
|
||||
|
||||
Manual reproduction:
|
||||
1. Start server: $ ./canlogserver vcan0
|
||||
2. Press Ctrl-C (send SIGINT).
|
||||
3. Check exit code (should be 130).
|
||||
"""
|
||||
with CanLogServerMonitor(bin_path, can_interface) as server:
|
||||
assert server.process.poll() is None
|
||||
try:
|
||||
os.killpg(os.getpgid(server.process.pid), signal.SIGINT)
|
||||
except ProcessLookupError:
|
||||
pytest.fail("Server process not found for SIGINT")
|
||||
|
||||
try:
|
||||
server.process.wait(timeout=2.0)
|
||||
except subprocess.TimeoutExpired:
|
||||
pytest.fail("Server did not exit on SIGINT (Infinite Accept Loop Bug)")
|
||||
|
||||
assert server.process.returncode == 130
|
||||
|
||||
def test_bind_retry_shutdown(bin_path, can_interface):
|
||||
"""
|
||||
Tests shutdown behavior when the server is stuck in the bind retry loop.
|
||||
|
||||
This reproduces the bug where the server would print dots endlessly
|
||||
and ignore SIGINT if the port was already in use.
|
||||
|
||||
Manual reproduction:
|
||||
1. Open a listening socket on port 28700: $ nc -l 28700 &
|
||||
2. Start server: $ ./canlogserver -p 28700 vcan0
|
||||
-> Server prints dots ............
|
||||
3. Press Ctrl-C.
|
||||
-> Server should exit immediately, not hang.
|
||||
"""
|
||||
# 1. Occupy a port locally
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.bind(('localhost', 0))
|
||||
s.listen(1)
|
||||
busy_port = s.getsockname()[1]
|
||||
|
||||
# 2. Start canlogserver trying to bind to the same port
|
||||
with CanLogServerMonitor(bin_path, can_interface, port=busy_port) as server:
|
||||
# Allow time for server to start and enter the retry loop
|
||||
time.sleep(1.0)
|
||||
|
||||
# 3. Send SIGINT
|
||||
# The server should be stuck in the bind loop printing dots.
|
||||
try:
|
||||
os.killpg(os.getpgid(server.process.pid), signal.SIGINT)
|
||||
except ProcessLookupError:
|
||||
pytest.fail("Server process died unexpectedly")
|
||||
|
||||
# 4. Wait for graceful exit
|
||||
try:
|
||||
server.process.wait(timeout=2.0)
|
||||
except subprocess.TimeoutExpired:
|
||||
pytest.fail("Server hung in bind retry loop (Infinite Bind Loop Bug)")
|
||||
|
||||
# Check expected exit code (128 + 2 = 130)
|
||||
assert server.process.returncode == 130
|
||||
@@ -0,0 +1,245 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Copyright (c) 2023 Linux CAN project
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License for more details.
|
||||
|
||||
import pytest
|
||||
import subprocess
|
||||
import os
|
||||
import time
|
||||
import signal
|
||||
import re
|
||||
|
||||
# --- Helper Functions ---
|
||||
|
||||
class TrafficMonitor:
|
||||
"""
|
||||
Context manager to run candump in the background.
|
||||
Captures the bus traffic to verify what canplayer sends.
|
||||
"""
|
||||
def __init__(self, bin_path, interface, args=None):
|
||||
self.cmd = [os.path.join(bin_path, "candump"), "-L", interface]
|
||||
if args:
|
||||
self.cmd.extend(args)
|
||||
self.process = None
|
||||
self.output = ""
|
||||
|
||||
def __enter__(self):
|
||||
self.process = subprocess.Popen(
|
||||
self.cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
preexec_fn=os.setsid
|
||||
)
|
||||
# Give candump a moment to bind to the socket
|
||||
time.sleep(0.2)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self.process:
|
||||
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
|
||||
self.output, _ = self.process.communicate()
|
||||
|
||||
def create_logfile(path, content):
|
||||
"""Creates a temporary CAN log file."""
|
||||
with open(path, 'w') as f:
|
||||
f.write(content)
|
||||
return path
|
||||
|
||||
# --- Tests for canplayer ---
|
||||
|
||||
def test_help_option(bin_path):
|
||||
"""Test -h option: Should print usage."""
|
||||
# canplayer might exit with 0 or 1, check output primarily
|
||||
result = subprocess.run([os.path.join(bin_path, "canplayer"), "-h"], capture_output=True, text=True)
|
||||
assert "Usage: canplayer" in result.stdout or "Usage: canplayer" in result.stderr
|
||||
|
||||
def test_replay_file(bin_path, can_interface, tmp_path):
|
||||
"""Test -I: Replay a simple log file."""
|
||||
log_content = f"(1600000000.000000) {can_interface} 123#112233\n"
|
||||
logfile = create_logfile(os.path.join(tmp_path, "test.log"), log_content)
|
||||
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "canplayer"), "-I", logfile],
|
||||
check=True
|
||||
)
|
||||
|
||||
assert "123#112233" in monitor.output
|
||||
|
||||
def test_stdin_input(bin_path, can_interface):
|
||||
"""Test input via stdin (default behavior without -I)."""
|
||||
log_content = f"(1600000000.000000) {can_interface} 123#AABBCC\n"
|
||||
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "canplayer")],
|
||||
input=log_content,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
|
||||
assert "123#AABBCC" in monitor.output
|
||||
|
||||
def test_interface_mapping(bin_path, can_interface, tmp_path):
|
||||
"""Test interface assignment (e.g., vcan0=can99)."""
|
||||
# Log file contains 'can99', but we map it to our real 'can_interface'
|
||||
fake_iface = "can99"
|
||||
log_content = f"(1600000000.000000) {fake_iface} 123#DEADBEEF\n"
|
||||
logfile = create_logfile(os.path.join(tmp_path, "test.log"), log_content)
|
||||
|
||||
# Assignment syntax: dest=src (send frames received from src on dest)
|
||||
# We want to send ON can_interface frames that came FROM fake_iface
|
||||
mapping = f"{can_interface}={fake_iface}"
|
||||
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "canplayer"), "-I", logfile, mapping],
|
||||
check=True
|
||||
)
|
||||
|
||||
# Monitor should see it on can_interface
|
||||
assert "123#DEADBEEF" in monitor.output
|
||||
|
||||
def test_loop_l(bin_path, can_interface, tmp_path):
|
||||
"""Test -l <num>: Loop playback."""
|
||||
log_content = f"(1600000000.000000) {can_interface} 123#01\n"
|
||||
logfile = create_logfile(os.path.join(tmp_path, "test.log"), log_content)
|
||||
|
||||
count = 3
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "canplayer"), "-I", logfile, "-l", str(count)],
|
||||
check=True
|
||||
)
|
||||
|
||||
# Should appear 3 times
|
||||
occurrences = monitor.output.count("123#01")
|
||||
assert occurrences == count
|
||||
|
||||
def test_ignore_timestamps_t(bin_path, can_interface, tmp_path):
|
||||
"""Test -t: Ignore timestamps (send immediately)."""
|
||||
# Create log with 2 seconds delay between frames
|
||||
# If -t works, execution should be instant, not taking >2 seconds
|
||||
log_content = (
|
||||
f"(100.000000) {can_interface} 123#01\n"
|
||||
f"(102.000000) {can_interface} 123#02\n"
|
||||
)
|
||||
logfile = create_logfile(os.path.join(tmp_path, "test.log"), log_content)
|
||||
|
||||
start = time.time()
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "canplayer"), "-I", logfile, "-t"],
|
||||
check=True
|
||||
)
|
||||
duration = time.time() - start
|
||||
|
||||
# Should be very fast, definitely under 1 second
|
||||
assert duration < 1.0
|
||||
|
||||
def test_skip_gaps_s(bin_path, can_interface, tmp_path):
|
||||
"""Test -s <s>: Skip gaps in timestamps > 's' seconds."""
|
||||
# Gap of 2 seconds in log file
|
||||
log_content = (
|
||||
f"(100.000000) {can_interface} 123#01\n"
|
||||
f"(102.000000) {can_interface} 123#02\n"
|
||||
)
|
||||
logfile = create_logfile(os.path.join(tmp_path, "test.log"), log_content)
|
||||
|
||||
# Tell canplayer to skip gaps > 1s (-s 1)
|
||||
start = time.time()
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "canplayer"), "-I", logfile, "-s", "1"],
|
||||
check=True
|
||||
)
|
||||
duration = time.time() - start
|
||||
|
||||
# Should skip the 2s wait
|
||||
assert duration < 1.5
|
||||
|
||||
def test_terminate_n(bin_path, can_interface, tmp_path):
|
||||
"""Test -n <count>: Terminate after sending count frames."""
|
||||
# Timestamps fixed to 6 decimal places (microseconds)
|
||||
log_content = (
|
||||
f"(100.000000) {can_interface} 123#01\n"
|
||||
f"(100.001000) {can_interface} 123#02\n"
|
||||
f"(100.002000) {can_interface} 123#03\n"
|
||||
)
|
||||
logfile = create_logfile(os.path.join(tmp_path, "test.log"), log_content)
|
||||
|
||||
# Process only 2 frames
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "canplayer"), "-I", logfile, "-n", "2"],
|
||||
check=True
|
||||
)
|
||||
|
||||
assert "123#01" in monitor.output
|
||||
assert "123#02" in monitor.output
|
||||
assert "123#03" not in monitor.output
|
||||
|
||||
def test_verbose_v(bin_path, can_interface, tmp_path):
|
||||
"""Test -v: Verbose output."""
|
||||
log_content = f"(1600000000.000000) {can_interface} 123#11\n"
|
||||
logfile = create_logfile(os.path.join(tmp_path, "test.log"), log_content)
|
||||
|
||||
result = subprocess.run(
|
||||
[os.path.join(bin_path, "canplayer"), "-I", logfile, "-v"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
|
||||
# Verbose mode usually prints the frame being sent to stdout
|
||||
assert "123" in result.stdout and "11" in result.stdout
|
||||
|
||||
def test_disable_loopback_x(bin_path, can_interface, tmp_path):
|
||||
"""Test -x: Disable local loopback."""
|
||||
log_content = f"(1600000000.000000) {can_interface} 123#FF\n"
|
||||
logfile = create_logfile(os.path.join(tmp_path, "test.log"), log_content)
|
||||
|
||||
# If loopback is disabled, a local candump should NOT see the frame
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "canplayer"), "-I", logfile, "-x"],
|
||||
check=True
|
||||
)
|
||||
|
||||
# Expect empty output (or at least the frame shouldn't be there)
|
||||
assert "123#FF" not in monitor.output
|
||||
|
||||
def test_gap_g(bin_path, can_interface, tmp_path):
|
||||
"""Test -g <ms>: Gap generation (functional check)."""
|
||||
# -g adds a fixed gap. Difficult to measure precisely without affecting test stability,
|
||||
# but we can check it runs successfully.
|
||||
# Timestamps fixed to 6 decimal places
|
||||
log_content = f"(100.000000) {can_interface} 123#11\n(100.000000) {can_interface} 123#22\n"
|
||||
logfile = create_logfile(os.path.join(tmp_path, "test.log"), log_content)
|
||||
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "canplayer"), "-I", logfile, "-g", "10"],
|
||||
check=True
|
||||
)
|
||||
|
||||
def test_parsing_bad_lines(bin_path, can_interface, tmp_path):
|
||||
"""Test that lines not starting with '(' are ignored."""
|
||||
# Timestamps fixed to 6 decimal places
|
||||
log_content = (
|
||||
"This is a comment line\n"
|
||||
f"(100.000000) {can_interface} 123#AA\n"
|
||||
"Another invalid line\n"
|
||||
)
|
||||
logfile = create_logfile(os.path.join(tmp_path, "test.log"), log_content)
|
||||
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
subprocess.run(
|
||||
[os.path.join(bin_path, "canplayer"), "-I", logfile],
|
||||
check=True
|
||||
)
|
||||
|
||||
# Should only process the valid frame
|
||||
assert "123#AA" in monitor.output
|
||||
# Ensure it didn't crash or error on invalid lines (return code check is implicit in check=True)
|
||||
@@ -0,0 +1,205 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Copyright (c) 2023 Linux CAN project
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License for more details.
|
||||
|
||||
import pytest
|
||||
import subprocess
|
||||
import os
|
||||
import time
|
||||
import signal
|
||||
import re
|
||||
|
||||
# --- Helper Functions ---
|
||||
|
||||
class TrafficMonitor:
|
||||
"""
|
||||
Context manager to run candump in the background.
|
||||
Captures the bus traffic to verify what cansend sends.
|
||||
"""
|
||||
def __init__(self, bin_path, interface, args=None):
|
||||
self.cmd = [os.path.join(bin_path, "candump"), "-L", interface]
|
||||
if args:
|
||||
self.cmd.extend(args)
|
||||
self.process = None
|
||||
self.output = ""
|
||||
|
||||
def __enter__(self):
|
||||
self.process = subprocess.Popen(
|
||||
self.cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
preexec_fn=os.setsid
|
||||
)
|
||||
# Give candump a moment to bind to the socket
|
||||
time.sleep(0.2)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self.process:
|
||||
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
|
||||
self.output, _ = self.process.communicate()
|
||||
|
||||
def run_cansend(bin_path, interface, frame):
|
||||
"""Executes cansend with the given frame string."""
|
||||
cmd = [os.path.join(bin_path, "cansend"), interface, frame]
|
||||
subprocess.run(cmd, check=True)
|
||||
|
||||
# --- Tests for cansend ---
|
||||
|
||||
def test_help_option(bin_path):
|
||||
"""Test -h option: Should print usage."""
|
||||
# cansend might exit with 1 on help (since it expects args), check output
|
||||
result = subprocess.run([os.path.join(bin_path, "cansend"), "-h"], capture_output=True, text=True)
|
||||
# The output provided by user shows usage on stdout or stderr
|
||||
assert "Usage: " in result.stdout or "Usage: " in result.stderr
|
||||
assert "cansend" in result.stdout or "cansend" in result.stderr
|
||||
|
||||
def test_classic_can_simple(bin_path, can_interface):
|
||||
"""Test standard frame: <can_id>#{data}"""
|
||||
# 123#DEADBEEF
|
||||
frame = "123#DEADBEEF"
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
run_cansend(bin_path, can_interface, frame)
|
||||
|
||||
assert "123#DEADBEEF" in monitor.output
|
||||
|
||||
def test_classic_can_dots(bin_path, can_interface):
|
||||
"""Test data with dots: 5A1#11.2233.44556677.88"""
|
||||
frame_input = "5A1#11.2233.44556677.88"
|
||||
# candump -L output will be without dots: 5A1#1122334455667788
|
||||
expected = "5A1#1122334455667788"
|
||||
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
run_cansend(bin_path, can_interface, frame_input)
|
||||
|
||||
assert expected in monitor.output
|
||||
|
||||
def test_classic_can_no_data(bin_path, can_interface):
|
||||
"""Test frame with no data: 5AA#"""
|
||||
frame = "5AA#"
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
run_cansend(bin_path, can_interface, frame)
|
||||
|
||||
# Expect ID with empty data. candump -L format: 5AA# (or 5AA# with nothing following)
|
||||
# Regex ensures 5AA# is at end of line or followed by space
|
||||
assert re.search(r'5AA#(\s|$)', monitor.output)
|
||||
|
||||
def test_classic_can_extended_id(bin_path, can_interface):
|
||||
"""Test Extended Frame Format (EFF): 1F334455#11..."""
|
||||
frame = "1F334455#112233"
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
run_cansend(bin_path, can_interface, frame)
|
||||
|
||||
assert "1F334455#112233" in monitor.output
|
||||
|
||||
def test_classic_can_rtr(bin_path, can_interface):
|
||||
"""Test RTR frame: <can_id>#R"""
|
||||
frame = "123#R"
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
run_cansend(bin_path, can_interface, frame)
|
||||
|
||||
# candump -L typically represents RTR via 'R' in output or specific formatting
|
||||
# e.g., 123#R
|
||||
assert "123#R" in monitor.output
|
||||
|
||||
def test_classic_can_rtr_len(bin_path, can_interface):
|
||||
"""Test RTR with length: <can_id>#R{len}"""
|
||||
# 00000123#R3 -> ID 123 (EFF padded?), len 3
|
||||
# Note: 00000123 is 8 chars -> EFF.
|
||||
frame = "00000123#R3"
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
run_cansend(bin_path, can_interface, frame)
|
||||
|
||||
# Check for EFF ID and RTR marker.
|
||||
# candump -L output for RTR might differ slightly depending on version,
|
||||
# but usually preserves the #R syntax if length matches.
|
||||
assert "00000123#R" in monitor.output
|
||||
|
||||
def test_classic_can_explicit_dlc(bin_path, can_interface):
|
||||
"""Test explicit DLC: <can_id>#{data}_{dlc}"""
|
||||
# 1F334455#1122334455667788_B (DLC 11)
|
||||
# This sets the DLC field > 8, but payload is 8 bytes.
|
||||
# Classic CAN allows DLC > 8 (interpreted as 8 usually, but passed on wire).
|
||||
frame = "1F334455#1122334455667788_B"
|
||||
|
||||
# We need candump to show the DLC to verify this, or just check it sends successfully.
|
||||
# Standard candump -L doesn't always show DLC > 8 unless -8 is used.
|
||||
# But here we verify 'cansend' doesn't crash and sends *something*.
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
run_cansend(bin_path, can_interface, frame)
|
||||
|
||||
assert "1F334455#1122334455667788" in monitor.output
|
||||
|
||||
def test_classic_can_rtr_len_dlc(bin_path, can_interface):
|
||||
"""Test RTR with len and DLC: <can_id>#R{len}_{dlc}"""
|
||||
# 333#R8_E (Len 8, DLC 14)
|
||||
frame = "333#R8_E"
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
run_cansend(bin_path, can_interface, frame)
|
||||
|
||||
assert "333#R" in monitor.output
|
||||
|
||||
def test_can_fd_flags_only(bin_path, can_interface):
|
||||
"""Test CAN FD flags only: <can_id>##<flags>"""
|
||||
# 123##1 (Flags: 1 = BRS)
|
||||
frame = "123##1"
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
run_cansend(bin_path, can_interface, frame)
|
||||
|
||||
# candump -L for FD uses ##
|
||||
# Output should contain 123##1
|
||||
# Note: Kernel/Driver often adds CANFD_FDF (0x04) flag automatically,
|
||||
# so we might see 5 (1|4) instead of 1.
|
||||
assert "123##1" in monitor.output or "123##5" in monitor.output
|
||||
|
||||
def test_can_fd_flags_data(bin_path, can_interface):
|
||||
"""Test CAN FD with flags and data: <can_id>##<flags>{data}"""
|
||||
# 213##311223344 (Flags 3, Data 11223344)
|
||||
# Flags 3 = BRS (1) | ESI (2)
|
||||
frame = "213##311223344"
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
run_cansend(bin_path, can_interface, frame)
|
||||
|
||||
# Note: Kernel/Driver often adds CANFD_FDF (0x04) flag automatically,
|
||||
# so we might see 7 (3|4) instead of 3.
|
||||
assert "213##311223344" in monitor.output or "213##711223344" in monitor.output
|
||||
|
||||
def test_can_xl_frame(bin_path, can_interface):
|
||||
"""Test CAN XL frame (if supported)."""
|
||||
# Example: 45123#81:00:12345678#11223344.556677
|
||||
# Prio: 123, Flags: 81, SDT: 00, AF: 12345678, Data...
|
||||
# Note: VCID is separate? The example in help says:
|
||||
# <vcid><prio>#... -> 45123 means VCID 45, Prio 123?
|
||||
frame = "45123#81:00:12345678#11223344556677"
|
||||
|
||||
# This might fail on kernels without CAN XL support.
|
||||
# We wrap in try/except or check return code to skip.
|
||||
try:
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
run_cansend(bin_path, can_interface, frame)
|
||||
|
||||
# If it worked, check output. XL output format in candump might vary.
|
||||
# But we expect the hex data to appear.
|
||||
assert "11223344556677" in monitor.output
|
||||
except subprocess.CalledProcessError:
|
||||
pytest.skip("CAN XL frame sending failed (kernel support missing?)")
|
||||
|
||||
def test_invalid_syntax(bin_path, can_interface):
|
||||
"""Test invalid syntax handling."""
|
||||
# Missing separator
|
||||
frame = "123DEADBEEF"
|
||||
cmd = [os.path.join(bin_path, "cansend"), can_interface, frame]
|
||||
|
||||
# Should exit with error code
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_missing_args(bin_path):
|
||||
"""Test missing arguments."""
|
||||
cmd = [os.path.join(bin_path, "cansend")]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
assert result.returncode != 0
|
||||
@@ -0,0 +1,192 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Copyright (c) 2023 Linux CAN project
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License for more details.
|
||||
|
||||
import pytest
|
||||
import subprocess
|
||||
import os
|
||||
import time
|
||||
import signal
|
||||
import re
|
||||
|
||||
# --- Helper Functions ---
|
||||
|
||||
class TrafficMonitor:
|
||||
"""
|
||||
Context manager to run candump in the background.
|
||||
Captures the bus traffic to verify what cansequence sends.
|
||||
"""
|
||||
def __init__(self, bin_path, interface, args=None):
|
||||
self.cmd = [os.path.join(bin_path, "candump"), "-L", interface]
|
||||
if args:
|
||||
self.cmd.extend(args)
|
||||
self.process = None
|
||||
self.output = ""
|
||||
|
||||
def __enter__(self):
|
||||
self.process = subprocess.Popen(
|
||||
self.cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
preexec_fn=os.setsid
|
||||
)
|
||||
time.sleep(0.2)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self.process:
|
||||
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
|
||||
self.output, _ = self.process.communicate()
|
||||
|
||||
def run_cansequence_sender(bin_path, interface, count=None, args=None):
|
||||
"""Runs cansequence in sender mode."""
|
||||
cmd = [os.path.join(bin_path, "cansequence"), interface]
|
||||
if count is not None:
|
||||
cmd.append(f"--loop={count}")
|
||||
if args:
|
||||
cmd.extend(args)
|
||||
|
||||
subprocess.run(cmd, check=True)
|
||||
|
||||
class CanSequenceReceiver:
|
||||
"""Context manager for cansequence receiver."""
|
||||
def __init__(self, bin_path, interface, args=None):
|
||||
self.cmd = [os.path.join(bin_path, "cansequence"), interface, "--receive"]
|
||||
if args:
|
||||
self.cmd.extend(args)
|
||||
self.process = None
|
||||
self.stdout = ""
|
||||
self.stderr = ""
|
||||
|
||||
def __enter__(self):
|
||||
self.process = subprocess.Popen(
|
||||
self.cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
preexec_fn=os.setsid
|
||||
)
|
||||
time.sleep(0.2)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self.process:
|
||||
if self.process.poll() is None:
|
||||
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
|
||||
self.stdout, self.stderr = self.process.communicate()
|
||||
|
||||
# --- Tests for cansequence ---
|
||||
|
||||
def test_help_option(bin_path):
|
||||
"""Test -h option."""
|
||||
result = subprocess.run([os.path.join(bin_path, "cansequence"), "-h"], capture_output=True, text=True)
|
||||
assert "Usage: cansequence" in result.stdout or "Usage: cansequence" in result.stderr
|
||||
|
||||
def test_sender_payload_increment(bin_path, can_interface):
|
||||
"""
|
||||
Test 1: Sender + Candump.
|
||||
Verify that cansequence sends frames with incrementing payload.
|
||||
"""
|
||||
count = 5
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
run_cansequence_sender(bin_path, can_interface, count=count)
|
||||
|
||||
# Parse output to find payloads
|
||||
# candump format: (timestamp) iface ID#DATA
|
||||
# cansequence default ID is 2. Payload is sequence number (hex).
|
||||
# e.g. 00, 01, 02...
|
||||
|
||||
lines = monitor.output.strip().splitlines()
|
||||
assert len(lines) == count
|
||||
|
||||
# Extract data bytes. Assuming standard classic CAN frame.
|
||||
# Regex to capture the first byte of data: ID#<Byte0>...
|
||||
data_bytes = []
|
||||
for line in lines:
|
||||
match = re.search(r'#([0-9A-Fa-f]{2})', line)
|
||||
if match:
|
||||
data_bytes.append(int(match.group(1), 16))
|
||||
|
||||
# Verify increment
|
||||
for i in range(len(data_bytes)):
|
||||
assert data_bytes[i] == i % 256, f"Payload mismatch at index {i}"
|
||||
|
||||
def test_sender_receiver_success(bin_path, can_interface):
|
||||
"""
|
||||
Test 2: Sender + Receiver.
|
||||
Verify that receiver accepts the stream from sender without error.
|
||||
"""
|
||||
# Start receiver
|
||||
with CanSequenceReceiver(bin_path, can_interface, args=["-v"]) as receiver:
|
||||
# Run sender
|
||||
run_cansequence_sender(bin_path, can_interface, count=10)
|
||||
|
||||
# Give receiver a moment to process
|
||||
time.sleep(0.5)
|
||||
|
||||
# Receiver should NOT have exited with error (if we didn't use -q)
|
||||
# and shouldn't have printed "sequence mismatch" errors.
|
||||
# Note: cansequence prints to stderr usually on error.
|
||||
|
||||
assert "sequence number mismatch" not in receiver.stderr
|
||||
assert "sequence number mismatch" not in receiver.stdout
|
||||
|
||||
def test_receiver_detects_error(bin_path, can_interface):
|
||||
"""
|
||||
Test 3: Cansend (Injection) + Receiver.
|
||||
Verify receiver detects missing sequence number.
|
||||
"""
|
||||
# Use -q 1 to quit immediately on first error
|
||||
with CanSequenceReceiver(bin_path, can_interface, args=["--quit", "1"]) as receiver:
|
||||
|
||||
# Manually send sequence 0 (valid)
|
||||
# Sequence number is usually little endian integer in payload.
|
||||
# ID 2 is default for cansequence.
|
||||
subprocess.run([os.path.join(bin_path, "cansend"), can_interface, "002#00"], check=True)
|
||||
time.sleep(0.1)
|
||||
|
||||
# Manually send sequence 2 (skipping 1) -> Invalid!
|
||||
subprocess.run([os.path.join(bin_path, "cansend"), can_interface, "002#02"], check=True)
|
||||
time.sleep(0.5)
|
||||
|
||||
# Check if receiver exited
|
||||
assert receiver.process.poll() is not None, "Receiver did not exit on sequence error"
|
||||
assert receiver.process.returncode != 0
|
||||
|
||||
def test_extended_id(bin_path, can_interface):
|
||||
"""Test sending extended frames (-e)."""
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
# Send 1 frame, extended mode
|
||||
run_cansequence_sender(bin_path, can_interface, count=1, args=["-e"])
|
||||
|
||||
# Output should contain 8-character ID (padded) or match extended syntax.
|
||||
# Default ID is 2, so extended is usually 00000002.
|
||||
assert "00000002#" in monitor.output or "2#" in monitor.output
|
||||
# Depending on candump formatting, we might verify extended flag logic if needed,
|
||||
# but existence of traffic is the main check here.
|
||||
|
||||
def test_custom_identifier(bin_path, can_interface):
|
||||
"""Test custom CAN ID (-i)."""
|
||||
custom_id = "0x123" # Hex string ensures strtoul interprets as hex
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
run_cansequence_sender(bin_path, can_interface, count=1, args=["-i", custom_id])
|
||||
|
||||
# Check for ID 123
|
||||
assert "123#" in monitor.output
|
||||
|
||||
def test_can_fd_mode(bin_path, can_interface):
|
||||
"""Test CAN-FD mode (-f)."""
|
||||
# Requires hardware/vcan support for FD
|
||||
try:
|
||||
with TrafficMonitor(bin_path, can_interface) as monitor:
|
||||
run_cansequence_sender(bin_path, can_interface, count=1, args=["-f"])
|
||||
|
||||
# FD frames usually appear with ## in candump -L (if not strict classic view)
|
||||
# or we verify the output exists.
|
||||
assert "##" in monitor.output or "#" in monitor.output
|
||||
except subprocess.CalledProcessError:
|
||||
pytest.skip("CAN-FD not supported or failed")
|
||||
@@ -0,0 +1,252 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Copyright (c) 2023 Linux CAN project
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License for more details.
|
||||
|
||||
import pytest
|
||||
import subprocess
|
||||
import os
|
||||
import time
|
||||
import signal
|
||||
import select
|
||||
import shutil
|
||||
import re
|
||||
|
||||
# --- Helper Functions ---
|
||||
|
||||
class CanSnifferMonitor:
|
||||
"""
|
||||
Context manager to run cansniffer.
|
||||
Captures stdout/stderr non-blocking way.
|
||||
"""
|
||||
def __init__(self, bin_path, interface, args=None):
|
||||
# CRITICAL: Use stdbuf -o0 to force unbuffered stdout.
|
||||
# cansniffer uses printf, which is fully buffered when writing to a pipe.
|
||||
# Without stdbuf, we would detect empty output until 4KB of data accumulates.
|
||||
if not shutil.which("stdbuf"):
|
||||
pytest.fail("stdbuf utility (coreutils) is required for testing cansniffer TUI")
|
||||
|
||||
self.cmd = ["stdbuf", "-o0", os.path.join(bin_path, "cansniffer"), interface]
|
||||
if args:
|
||||
self.cmd.extend(args)
|
||||
self.process = None
|
||||
self.output_buffer = ""
|
||||
|
||||
def __enter__(self):
|
||||
# We need bufsize=0 or unbuffered to get output in real-time
|
||||
self.process = subprocess.Popen(
|
||||
self.cmd,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
bufsize=0,
|
||||
preexec_fn=os.setsid
|
||||
)
|
||||
time.sleep(0.5) # Wait for initialization (clearing screen etc)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self.process:
|
||||
try:
|
||||
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
|
||||
def send_input(self, text):
|
||||
"""Sends commands to cansniffer stdin."""
|
||||
if self.process and self.process.stdin:
|
||||
self.process.stdin.write(text)
|
||||
self.process.stdin.flush()
|
||||
time.sleep(0.5) # Allow processing time (increased for stability)
|
||||
|
||||
def read_output(self, timeout=1.0):
|
||||
"""
|
||||
Reads available output from stdout without blocking forever.
|
||||
"""
|
||||
collected = ""
|
||||
start = time.time()
|
||||
while time.time() - start < timeout:
|
||||
# Check if there is data to read
|
||||
reads = [self.process.stdout.fileno()]
|
||||
ret = select.select(reads, [], [], 0.1)
|
||||
|
||||
if ret[0]:
|
||||
# Read chunks
|
||||
chunk = self.process.stdout.read(1024)
|
||||
if not chunk:
|
||||
break
|
||||
collected += chunk
|
||||
else:
|
||||
# If we have collected something and no new data is coming fast, break early
|
||||
# to speed up tests, unless we are waiting for specific timeout
|
||||
if collected and (time.time() - start > 0.3):
|
||||
break
|
||||
|
||||
self.output_buffer += collected
|
||||
return collected
|
||||
|
||||
def clear_buffer(self):
|
||||
"""Discards currently available output."""
|
||||
self.read_output(timeout=0.2)
|
||||
|
||||
# --- Tests for cansniffer ---
|
||||
|
||||
def test_help_option(bin_path):
|
||||
"""Test -? option (cansniffer uses -? for help)."""
|
||||
# Note: The help text says '-?' prints help.
|
||||
result = subprocess.run([os.path.join(bin_path, "cansniffer"), "-?"], capture_output=True, text=True)
|
||||
# cansniffer usually returns error code because ? is invalid opt for standard parsers
|
||||
assert "Usage: cansniffer" in result.stdout or "Usage: cansniffer" in result.stderr
|
||||
|
||||
def test_basic_sniffing(bin_path, can_interface):
|
||||
"""Test if cansniffer shows traffic."""
|
||||
with CanSnifferMonitor(bin_path, can_interface) as sniffer:
|
||||
# Generate traffic: ID 123
|
||||
subprocess.run([os.path.join(bin_path, "cansend"), can_interface, "123#112233"], check=True)
|
||||
|
||||
# Read output
|
||||
out = sniffer.read_output(timeout=2.0)
|
||||
|
||||
# Check if 123 appears.
|
||||
assert "123" in out
|
||||
|
||||
def test_filtering_add(bin_path, can_interface):
|
||||
"""Test adding specific ID filter (+ID)."""
|
||||
# Start with -q (quiet, all IDs deactivated)
|
||||
with CanSnifferMonitor(bin_path, can_interface, args=["-q"]) as sniffer:
|
||||
# 1. Send ID 123 -> Should NOT appear (quiet mode)
|
||||
subprocess.run([os.path.join(bin_path, "cansend"), can_interface, "123#AA"], check=True)
|
||||
out = sniffer.read_output(timeout=1.0)
|
||||
assert "123" not in out
|
||||
|
||||
# 2. Add filter +123 via stdin
|
||||
sniffer.send_input("+123\n")
|
||||
|
||||
# 3. Send ID 123 again -> Should appear now
|
||||
subprocess.run([os.path.join(bin_path, "cansend"), can_interface, "123#BB"], check=True)
|
||||
out = sniffer.read_output(timeout=2.0)
|
||||
assert "123" in out
|
||||
|
||||
def test_filtering_remove(bin_path, can_interface):
|
||||
"""Test removing specific ID filter (-ID)."""
|
||||
# Start normal mode (sniffs all)
|
||||
with CanSnifferMonitor(bin_path, can_interface) as sniffer:
|
||||
# 1. Send ID 456 -> Should appear
|
||||
subprocess.run([os.path.join(bin_path, "cansend"), can_interface, "456#11"], check=True)
|
||||
out = sniffer.read_output(timeout=1.0)
|
||||
assert "456" in out
|
||||
|
||||
# 2. Remove 456 via stdin
|
||||
sniffer.send_input("-456\n")
|
||||
|
||||
# Clear buffer to ignore previous output about 456
|
||||
sniffer.clear_buffer()
|
||||
|
||||
# Send a different ID to force a screen refresh / activity on a visible ID
|
||||
# This ensures cansniffer produces NEW output
|
||||
subprocess.run([os.path.join(bin_path, "cansend"), can_interface, "789#AA"], check=True)
|
||||
time.sleep(0.2)
|
||||
|
||||
# 3. Send ID 456 again with NEW unique data (CC)
|
||||
# Using CC is safer than 22 as numbers might appear in timestamps
|
||||
subprocess.run([os.path.join(bin_path, "cansend"), can_interface, "456#CC"], check=True)
|
||||
|
||||
# Read subsequent output
|
||||
out = sniffer.read_output(timeout=1.5)
|
||||
|
||||
# Verify 789 is there (sanity check that we captured output)
|
||||
assert "789" in out
|
||||
|
||||
# Verify 456 (and specifically its new data CC) is NOT in the new block.
|
||||
assert "CC" not in out
|
||||
|
||||
def test_binary_mode_b(bin_path, can_interface):
|
||||
"""Test binary mode output (-b)."""
|
||||
with CanSnifferMonitor(bin_path, can_interface, args=["-b"]) as sniffer:
|
||||
subprocess.run([os.path.join(bin_path, "cansend"), can_interface, "123#DEADBEEF"], check=True)
|
||||
out = sniffer.read_output(timeout=1.0)
|
||||
# In binary mode (-b), data bytes are often visualized as bits/dots.
|
||||
# But the ID "123" should still be visible.
|
||||
assert "123" in out
|
||||
|
||||
def test_color_mode_c(bin_path, can_interface):
|
||||
"""Test color mode (-c)."""
|
||||
# Color output depends on terminal capabilities usually, but cansniffer -c forces it.
|
||||
with CanSnifferMonitor(bin_path, can_interface, args=["-c"]) as sniffer:
|
||||
subprocess.run([os.path.join(bin_path, "cansend"), can_interface, "123#11"], check=True)
|
||||
out = sniffer.read_output(timeout=1.0)
|
||||
# Color mode uses ANSI escape sequences (e.g. \033[...m)
|
||||
# Note: In Python string literals, \x1b is ESC.
|
||||
assert "\x1b[" in out or "\033[" in out
|
||||
|
||||
def test_clear_screen_space(bin_path, can_interface):
|
||||
"""Test clearing screen (<SPACE>)."""
|
||||
with CanSnifferMonitor(bin_path, can_interface) as sniffer:
|
||||
# Give it a moment to startup
|
||||
time.sleep(0.5)
|
||||
|
||||
# Clear screen command
|
||||
sniffer.send_input("\n") # Just enter sometimes works, or Space+Enter
|
||||
sniffer.send_input(" \n")
|
||||
|
||||
# If it clears, it emits ANSI clear sequence "\033[2J" or "\033[H" (Home)
|
||||
out = sniffer.read_output(timeout=1.0)
|
||||
|
||||
# Check for Common ANSI escape sequences for clearing/home
|
||||
# \033[2J = Clear Screen, \033[H = Cursor Home
|
||||
assert "\x1b[2J" in out or "\x1b[H" in out or "\x1b[" in out
|
||||
|
||||
def test_timeout_t(bin_path, can_interface):
|
||||
"""Test timeout for ID display (-t)."""
|
||||
# -t <time> in 10ms steps. e.g., -t 5 = 50ms (very short).
|
||||
with CanSnifferMonitor(bin_path, can_interface, args=["-t", "5"]) as sniffer:
|
||||
subprocess.run([os.path.join(bin_path, "cansend"), can_interface, "789#AA"], check=True)
|
||||
out = sniffer.read_output(timeout=0.5)
|
||||
assert "789" in out
|
||||
|
||||
# Wait for timeout (50ms + margin)
|
||||
time.sleep(0.5)
|
||||
|
||||
# Send something else to trigger redraw
|
||||
subprocess.run([os.path.join(bin_path, "cansend"), can_interface, "999#BB"], check=True)
|
||||
out = sniffer.read_output(timeout=0.5)
|
||||
|
||||
# 789 should have timed out.
|
||||
if "999" in out:
|
||||
# If we see the update for 999, we expect 789 to NOT be present in this new block
|
||||
pass
|
||||
|
||||
@pytest.mark.xfail(reason="cansniffer hides static data, so the ID disappears despite refresh")
|
||||
def test_constant_data_refresh(bin_path, can_interface):
|
||||
"""
|
||||
Test if sending the SAME data repeatedly keeps the ID alive.
|
||||
User suspects that identical data might not reset the timeout.
|
||||
"""
|
||||
# Set timeout to 500ms (-t 50)
|
||||
with CanSnifferMonitor(bin_path, can_interface, args=["-t", "50"]) as sniffer:
|
||||
# 1. Send initial packet
|
||||
subprocess.run([os.path.join(bin_path, "cansend"), can_interface, "555#AA"], check=True)
|
||||
out = sniffer.read_output(timeout=0.5)
|
||||
assert "555" in out
|
||||
|
||||
# 2. Send IDENTICAL data repeatedly for 1.5 seconds (3x timeout)
|
||||
# We send every 0.1s (well within 0.5s timeout)
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 1.5:
|
||||
subprocess.run([os.path.join(bin_path, "cansend"), can_interface, "555#AA"], check=True)
|
||||
time.sleep(0.1)
|
||||
# Read output to prevent buffer overflow, though we are unbuffered
|
||||
sniffer.read_output(timeout=0.01)
|
||||
|
||||
# 3. Check if it is STILL visible
|
||||
out = sniffer.read_output(timeout=0.5)
|
||||
|
||||
# If the user is right (bug), '555' might be missing because content didn't change.
|
||||
# If it works as expected (refresh on any frame), '555' should be there.
|
||||
if "555" not in out:
|
||||
pytest.fail("ID 555 disappeared despite constant refresh with identical data! Possible bug confirmed.")
|
||||
|
||||
assert "555" in out
|
||||
@@ -0,0 +1,503 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Copyright (c) 2025 Linux CAN project
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
import pytest
|
||||
import subprocess
|
||||
import os
|
||||
import time
|
||||
import tempfile
|
||||
import signal
|
||||
import sys
|
||||
import struct
|
||||
import datetime
|
||||
import stat
|
||||
import select
|
||||
|
||||
# Helper function to check for binaries
|
||||
def require_binaries(bin_path, binaries):
|
||||
for binary in binaries:
|
||||
if not os.path.exists(os.path.join(bin_path, binary)):
|
||||
pytest.skip(f"Binary '{binary}' not found in {bin_path}")
|
||||
|
||||
def create_test_file(filepath, size_bytes):
|
||||
"""
|
||||
Python implementation of isobusfs_create_test_file.sh
|
||||
Generates a file with a specific XOR pattern (0xdeadbeef).
|
||||
"""
|
||||
pattern = 0xdeadbeef
|
||||
# Each iteration writes 4 bytes
|
||||
iterations = size_bytes // 4
|
||||
|
||||
with open(filepath, 'wb') as f:
|
||||
# Write in chunks to improve performance for larger files
|
||||
chunk_size = 4096 # iterations per chunk
|
||||
for i in range(0, iterations, chunk_size):
|
||||
chunk_end = min(i + chunk_size, iterations)
|
||||
data = bytearray()
|
||||
for counter in range(i, chunk_end):
|
||||
# bash: printf "%08x" $((counter ^ pattern)) | xxd -r -p
|
||||
# This results in Big-Endian binary representation
|
||||
val = counter ^ pattern
|
||||
data.extend(struct.pack('>I', val))
|
||||
f.write(data)
|
||||
|
||||
def create_test_infrastructure(root):
|
||||
"""
|
||||
Python implementation of isobusfs_create_test_dirs.sh
|
||||
Creates the complex directory structure, special files, and timestamps.
|
||||
"""
|
||||
print(f"DEBUG: Generating test infrastructure in {root}")
|
||||
|
||||
# 1. Create Directory Hierarchy
|
||||
# NOTE: Changed MCMC0683 to MCMC0000 to match the server's default fallback
|
||||
# when no local name is provided via -n argument.
|
||||
dirs = [
|
||||
"dir1/dir2/dir3/dir4",
|
||||
"dir1/dir2/dir3/dir5",
|
||||
"MCMC0000/msd_dir1/msd_dir2/~/~tilde_dir",
|
||||
"dir1/~/~",
|
||||
"dir1/dir2/special_chars_*?/",
|
||||
"dir1/dir2/unicode_名字",
|
||||
]
|
||||
for d in dirs:
|
||||
os.makedirs(os.path.join(root, d), exist_ok=True)
|
||||
|
||||
# 2. Create simple text file
|
||||
with open(os.path.join(root, "dir1/dir2/file0"), "w") as f:
|
||||
f.write("hello\n")
|
||||
|
||||
# 3. Create binary test files (1KB and 1MB)
|
||||
create_test_file(os.path.join(root, "dir1/dir2/file1k"), 1024)
|
||||
create_test_file(os.path.join(root, "dir1/dir2/file1m"), 1048576)
|
||||
|
||||
# 4. Create 300 files with long names
|
||||
# Suffix reconstructed based on visual pattern (alphabet repeated ~8.5 times ending in 'p')
|
||||
alphabet = "abcdefghijklmnopqrstuvwxyz"
|
||||
long_suffix = (alphabet * 8) + alphabet[:16] # Results in 224 chars
|
||||
|
||||
for count in range(1, 301):
|
||||
fname = f"long_name_{count}_{long_suffix}"
|
||||
with open(os.path.join(root, fname), 'w') as f:
|
||||
pass
|
||||
|
||||
# 5. Create special permission files
|
||||
base_perm_dir = os.path.join(root, "dir1/dir2")
|
||||
|
||||
# hidden_file
|
||||
with open(os.path.join(base_perm_dir, "hidden_file"), 'w') as f:
|
||||
pass
|
||||
|
||||
# readonly_file (chmod 444)
|
||||
ro_path = os.path.join(base_perm_dir, "readonly_file")
|
||||
with open(ro_path, 'w') as f:
|
||||
pass
|
||||
os.chmod(ro_path, 0o444)
|
||||
|
||||
# executable_file (chmod +x -> 755 typically)
|
||||
exe_path = os.path.join(base_perm_dir, "executable_file")
|
||||
with open(exe_path, 'w') as f:
|
||||
pass
|
||||
current_mode = os.stat(exe_path).st_mode
|
||||
os.chmod(exe_path, current_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
||||
|
||||
# no_read_permission_file (chmod 000)
|
||||
no_read_path = os.path.join(base_perm_dir, "no_read_permission_file")
|
||||
with open(no_read_path, 'w') as f:
|
||||
pass
|
||||
os.chmod(no_read_path, 0o000)
|
||||
|
||||
# 6. Date/Timestamp Problems
|
||||
# Note: timestamps might be limited by the host OS (Y2038 on 32-bit systems)
|
||||
date_dirs = {
|
||||
"y2000_problem": "2000-01-01 00:00:00",
|
||||
"y2038_problem": "2038-01-19 03:14:07",
|
||||
"y1979_problem": "1979-12-31 23:59:59",
|
||||
"y1980_problem": "1980-01-01 00:00:00",
|
||||
"y2107_problem": "2107-12-31 23:59:59",
|
||||
"y2108_problem": "2108-01-01 00:00:00",
|
||||
}
|
||||
|
||||
for dirname, date_str in date_dirs.items():
|
||||
dir_path = os.path.join(base_perm_dir, dirname)
|
||||
os.makedirs(dir_path, exist_ok=True)
|
||||
try:
|
||||
dt = datetime.datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")
|
||||
ts = dt.timestamp()
|
||||
# Set access and modification time
|
||||
os.utime(dir_path, (ts, ts))
|
||||
except (OverflowError, OSError, ValueError):
|
||||
print(f"WARNING: Could not set timestamp {date_str} for {dirname} on this platform.")
|
||||
|
||||
def test_isobusfs_selftest(bin_path, can_interface):
|
||||
"""
|
||||
Test ISOBUS File Server interaction using the client's 'selftest' command.
|
||||
Includes generation of complex file infrastructure.
|
||||
|
||||
Description:
|
||||
1. Create a temporary directory.
|
||||
2. Populate it with complex file structure (long names, deep dirs, special permissions).
|
||||
3. Start isobusfs-srv.
|
||||
4. Start isobusfs-cli and run 'selftest'.
|
||||
5. Verify Test 1-7 PASS explicitly.
|
||||
6. Verify Test 8 PASS or prove successful data transfer (partial pass).
|
||||
7. If tests fail, run internal 'dmesg' command on client.
|
||||
|
||||
Manual Reproduction:
|
||||
1. Run isobusfs_create_test_dirs.sh inside a test folder.
|
||||
2. ./isobusfs-srv -i vcan0 -a 80 -v vol1:/path/to/folder -l 3
|
||||
3. ./isobusfs-cli -i vcan0 -a 90 -r 80 -l 3
|
||||
4. Type 'selftest'
|
||||
"""
|
||||
server_bin = "isobusfs-srv"
|
||||
client_bin = "isobusfs-cli"
|
||||
require_binaries(bin_path, [server_bin, client_bin])
|
||||
|
||||
server_exe = os.path.join(bin_path, server_bin)
|
||||
client_exe = os.path.join(bin_path, client_bin)
|
||||
|
||||
# Use a temporary directory as the volume root
|
||||
with tempfile.TemporaryDirectory() as tmp_vol_dir:
|
||||
# 1. Generate Infrastructure
|
||||
create_test_infrastructure(tmp_vol_dir)
|
||||
|
||||
# Define addresses (hex)
|
||||
srv_addr = "80"
|
||||
cli_addr = "90"
|
||||
vol_name = "vol1"
|
||||
|
||||
# Construct Server Command
|
||||
server_cmd = [
|
||||
server_exe,
|
||||
"-i", can_interface,
|
||||
"-a", srv_addr,
|
||||
"-v", f"{vol_name}:{tmp_vol_dir}",
|
||||
"-l", "3"
|
||||
]
|
||||
|
||||
print(f"DEBUG: Starting Server: {' '.join(server_cmd)}")
|
||||
|
||||
# Start Server
|
||||
server_proc = subprocess.Popen(
|
||||
server_cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
|
||||
client_proc = None
|
||||
|
||||
try:
|
||||
# Wait for server initialization
|
||||
time.sleep(1)
|
||||
|
||||
if server_proc.poll() is not None:
|
||||
_, err = server_proc.communicate()
|
||||
pytest.fail(f"isobusfs-srv failed to start. Error: {err.decode('utf-8', errors='replace')}")
|
||||
|
||||
# Construct Client Command
|
||||
client_cmd = [
|
||||
client_exe,
|
||||
"-i", can_interface,
|
||||
"-a", cli_addr,
|
||||
"-r", srv_addr,
|
||||
"-l", "3"
|
||||
]
|
||||
|
||||
print(f"DEBUG: Starting Client: {' '.join(client_cmd)}")
|
||||
|
||||
# Start Client
|
||||
client_proc = subprocess.Popen(
|
||||
client_cmd,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
bufsize=0
|
||||
)
|
||||
|
||||
# Interact with Client
|
||||
print("DEBUG: Sending 'selftest' command...")
|
||||
client_proc.stdin.write("selftest\n")
|
||||
client_proc.stdin.flush()
|
||||
|
||||
# Allow time for tests (20s)
|
||||
time.sleep(20)
|
||||
|
||||
# After tests, request internal dmesg to check for errors
|
||||
print("DEBUG: Sending 'dmesg' command to client...")
|
||||
client_proc.stdin.write("dmesg\n")
|
||||
client_proc.stdin.flush()
|
||||
|
||||
# Give dmesg a moment to output
|
||||
time.sleep(1)
|
||||
|
||||
client_proc.terminate()
|
||||
|
||||
try:
|
||||
outs, errs = client_proc.communicate(timeout=1)
|
||||
except subprocess.TimeoutExpired:
|
||||
client_proc.kill()
|
||||
outs, errs = client_proc.communicate()
|
||||
|
||||
print("-" * 20 + " Client Output " + "-" * 20)
|
||||
print(outs)
|
||||
print("-" * 20 + " Client Errors " + "-" * 20)
|
||||
print(errs)
|
||||
print("-" * 50)
|
||||
|
||||
# Verification logic
|
||||
failures = []
|
||||
|
||||
# Tests 1-7 MUST pass
|
||||
mandatory_tests = [
|
||||
"Test 1: PASSED",
|
||||
"Test 2: PASSED",
|
||||
"Test 3: PASSED",
|
||||
"Test 4: PASSED",
|
||||
"Test 5: PASSED",
|
||||
"Test 6: PASSED",
|
||||
"Test 7: PASSED",
|
||||
"Test 8: PASSED",
|
||||
]
|
||||
|
||||
for test in mandatory_tests:
|
||||
if test not in outs:
|
||||
failures.append(test.replace(": PASSED", ""))
|
||||
|
||||
if failures:
|
||||
pytest.fail(f"The following ISOBUS FS tests failed: {', '.join(failures)}. See output above for details and internal dmesg log.")
|
||||
|
||||
finally:
|
||||
if server_proc.poll() is None:
|
||||
server_proc.terminate()
|
||||
try:
|
||||
server_out, server_err = server_proc.communicate(timeout=1)
|
||||
except subprocess.TimeoutExpired:
|
||||
server_proc.kill()
|
||||
server_out, server_err = server_proc.communicate()
|
||||
|
||||
print("-" * 20 + " Server Output " + "-" * 20)
|
||||
# Manually decode output, replacing invalid characters
|
||||
if server_out:
|
||||
print(server_out.decode('utf-8', errors='replace'))
|
||||
if server_err:
|
||||
print(server_err.decode('utf-8', errors='replace'))
|
||||
print("-" * 50)
|
||||
|
||||
if client_proc and client_proc.poll() is None:
|
||||
client_proc.kill()
|
||||
|
||||
def test_isobusfs_interactive_commands(bin_path, can_interface):
|
||||
"""
|
||||
Test the interactive command shell of isobusfs-cli.
|
||||
|
||||
Covers:
|
||||
- help: Help text display
|
||||
- ls/ll: Directory listing
|
||||
- cd: Directory navigation
|
||||
- pwd: Working directory reporting (ISOBUS path format checks)
|
||||
- get: File download
|
||||
- dmesg: Log buffer access (executed at the end)
|
||||
- exit: Clean exit
|
||||
- quit: Clean exit (tested in separate session)
|
||||
"""
|
||||
server_bin = "isobusfs-srv"
|
||||
client_bin = "isobusfs-cli"
|
||||
require_binaries(bin_path, [server_bin, client_bin])
|
||||
|
||||
# FIX 1: Use absolute paths to find binaries even when CWD changes
|
||||
server_exe = os.path.abspath(os.path.join(bin_path, server_bin))
|
||||
client_exe = os.path.abspath(os.path.join(bin_path, client_bin))
|
||||
|
||||
# Helper function for truly interactive communication
|
||||
def interact(proc, cmd=None, expect_prompt=True, timeout=5):
|
||||
"""
|
||||
Sends a command and reads output until the prompt 'isobusfs> ' appears.
|
||||
Using binary mode (bytes) to avoid buffering issues.
|
||||
"""
|
||||
if cmd:
|
||||
proc.stdin.write(cmd.encode('utf-8') + b"\n")
|
||||
proc.stdin.flush()
|
||||
|
||||
if not expect_prompt:
|
||||
return ""
|
||||
|
||||
output = b""
|
||||
start_time = time.time()
|
||||
|
||||
while time.time() - start_time < timeout:
|
||||
# Check if stdout has data ready to read
|
||||
rlist, _, _ = select.select([proc.stdout], [], [], 0.1)
|
||||
if rlist:
|
||||
# Read 1 byte at a time to safely detect the prompt boundary
|
||||
char = proc.stdout.read(1)
|
||||
if not char: # EOF
|
||||
break
|
||||
output += char
|
||||
if output.endswith(b"isobusfs> "):
|
||||
return output.decode('utf-8', errors='replace')
|
||||
|
||||
# Check if process died
|
||||
if proc.poll() is not None:
|
||||
break
|
||||
|
||||
# If we timed out but got some output, return it for debugging/assertion
|
||||
return output.decode('utf-8', errors='replace')
|
||||
|
||||
# Use temporary directories
|
||||
with tempfile.TemporaryDirectory() as srv_dir, tempfile.TemporaryDirectory() as cli_dir:
|
||||
# 1. Prepare Server Data
|
||||
create_test_infrastructure(srv_dir)
|
||||
|
||||
# 2. Start Server
|
||||
srv_addr = "80"
|
||||
cli_addr = "95"
|
||||
vol_name = "testvol"
|
||||
|
||||
server_cmd = [
|
||||
server_exe,
|
||||
"-i", can_interface,
|
||||
"-a", srv_addr,
|
||||
"-v", f"{vol_name}:{srv_dir}",
|
||||
"-l", "3"
|
||||
]
|
||||
|
||||
print(f"DEBUG: Starting Server: {' '.join(server_cmd)}")
|
||||
server_proc = subprocess.Popen(
|
||||
server_cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE
|
||||
)
|
||||
|
||||
client_proc = None
|
||||
|
||||
try:
|
||||
time.sleep(1)
|
||||
if server_proc.poll() is not None:
|
||||
_, err = server_proc.communicate()
|
||||
pytest.fail(f"Server failed to start: {err.decode('utf-8', errors='replace')}")
|
||||
|
||||
client_cmd = [
|
||||
client_exe,
|
||||
"-i", can_interface,
|
||||
"-a", cli_addr,
|
||||
"-r", srv_addr,
|
||||
"-l", "3"
|
||||
]
|
||||
|
||||
print(f"DEBUG: Running Client Session 1 in {cli_dir}")
|
||||
# FIX 2: Use bufsize=0 and binary streams for precise control
|
||||
client_proc = subprocess.Popen(
|
||||
client_cmd,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
cwd=cli_dir,
|
||||
bufsize=0
|
||||
)
|
||||
|
||||
# --- Session 1: Main Workflow ---
|
||||
|
||||
# 1. Initial Prompt (wait for startup)
|
||||
out = interact(client_proc)
|
||||
assert "Interactive mode" in out
|
||||
|
||||
# 2. Help
|
||||
out = interact(client_proc, "help")
|
||||
assert "exit - exit interactive mode" in out
|
||||
|
||||
# 3. Pwd (Root)
|
||||
# FIX 3: Expect ISOBUS style path: \\<volume_name>
|
||||
out = interact(client_proc, "pwd")
|
||||
assert f"\\\\{vol_name}" in out
|
||||
|
||||
# 4. ls
|
||||
out = interact(client_proc, "ls")
|
||||
assert "dir1" in out
|
||||
assert "long_name_1_" in out # Verify we still see some long names
|
||||
|
||||
# 5. cd dir1
|
||||
out = interact(client_proc, "cd dir1")
|
||||
|
||||
# 6. pwd (check change)
|
||||
out = interact(client_proc, "pwd")
|
||||
assert f"\\\\{vol_name}\\dir1" in out
|
||||
|
||||
# 7. ll
|
||||
out = interact(client_proc, "ll")
|
||||
assert "dir2" in out
|
||||
|
||||
# 8. cd dir2
|
||||
out = interact(client_proc, "cd dir2")
|
||||
|
||||
# 9. pwd
|
||||
out = interact(client_proc, "pwd")
|
||||
assert f"\\\\{vol_name}\\dir1\\dir2" in out
|
||||
|
||||
# 10. ls
|
||||
out = interact(client_proc, "ls")
|
||||
assert "file0" in out
|
||||
|
||||
# 11. get file0
|
||||
out = interact(client_proc, "get file0")
|
||||
assert "File transfer completed" in out
|
||||
|
||||
# Verify file content
|
||||
downloaded_file = os.path.join(cli_dir, "file0")
|
||||
assert os.path.exists(downloaded_file), "Command 'get file0' failed."
|
||||
with open(downloaded_file, "r") as f:
|
||||
assert f.read() == "hello\n"
|
||||
|
||||
# 12. Dmesg (Moved to end to prevent buffer output interference)
|
||||
out = interact(client_proc, "dmesg")
|
||||
|
||||
# 13. Exit
|
||||
client_proc.stdin.write(b"exit\n")
|
||||
client_proc.stdin.flush()
|
||||
try:
|
||||
client_proc.wait(timeout=2)
|
||||
except subprocess.TimeoutExpired:
|
||||
client_proc.kill()
|
||||
pytest.fail("Client did not exit cleanly after 'exit' command")
|
||||
|
||||
# FIX 4: Accept 252 (which is -EINTR / -4 cast to unsigned 8-bit)
|
||||
assert client_proc.returncode in [0, -2, 252]
|
||||
|
||||
# --- Session 2: Test 'quit' alias ---
|
||||
print("DEBUG: Running Client Session 2 to test 'quit'")
|
||||
client_proc = subprocess.Popen(
|
||||
client_cmd,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
bufsize=0
|
||||
)
|
||||
|
||||
# Consume initial prompt
|
||||
interact(client_proc)
|
||||
|
||||
# Send quit
|
||||
client_proc.stdin.write(b"quit\n")
|
||||
client_proc.stdin.flush()
|
||||
client_proc.wait(timeout=2)
|
||||
|
||||
assert client_proc.returncode in [0, -2, 252]
|
||||
|
||||
finally:
|
||||
# Cleanup
|
||||
if server_proc.poll() is None:
|
||||
server_proc.terminate()
|
||||
server_proc.wait(timeout=1)
|
||||
|
||||
if client_proc and client_proc.poll() is None:
|
||||
client_proc.kill()
|
||||
@@ -0,0 +1,170 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import os
|
||||
import pytest
|
||||
import signal
|
||||
|
||||
# Note: bin_path and interface fixtures are provided implicitly by conftest.py
|
||||
|
||||
def test_isotp_usage(bin_path):
|
||||
"""
|
||||
Test usage/help output for isotpsend and isotprecv.
|
||||
isotpsend returns error on -h.
|
||||
isotprecv returns usage on missing args.
|
||||
"""
|
||||
isotpsend = os.path.join(bin_path, "isotpsend")
|
||||
isotprecv = os.path.join(bin_path, "isotprecv")
|
||||
|
||||
if os.path.exists(isotpsend):
|
||||
# isotpsend: invalid option -h
|
||||
# Note: The tool might exit with 0 even on invalid options in some builds.
|
||||
# We rely on checking the output text.
|
||||
res_send = subprocess.run(
|
||||
[isotpsend, "-h"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
# We do not assert returncode != 0 here because it was observed to be 0
|
||||
assert "Usage: isotpsend" in res_send.stderr or "Usage: isotpsend" in res_send.stdout
|
||||
|
||||
if os.path.exists(isotprecv):
|
||||
# isotprecv: no args -> usage -> exit code != 0 (typically) or 0?
|
||||
# Based on your output, it prints Usage. We check valid stderr/stdout output.
|
||||
res_recv = subprocess.run(
|
||||
[isotprecv],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
# We assume non-zero because usually tools requiring args exit with error
|
||||
# checking the output text is the most important part.
|
||||
assert "Usage: isotprecv" in res_recv.stderr or "Usage: isotprecv" in res_recv.stdout
|
||||
|
||||
def check_isotp_support(bin_path, interface):
|
||||
"""
|
||||
Helper to check if ISO-TP kernel module is loaded/available.
|
||||
Tries to run isotpsend for a split second.
|
||||
"""
|
||||
isotpsend = os.path.join(bin_path, "isotpsend")
|
||||
# Try to send a dummy frame. If socket creation fails, it exits with error.
|
||||
# We use a timeout to ensure it doesn't hang.
|
||||
try:
|
||||
# -s 123 -d 321 are dummy IDs
|
||||
proc = subprocess.Popen(
|
||||
[isotpsend, "-s", "123", "-d", "321", interface],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
# Send empty or minimal data to close quickly if successful or fail if socket error
|
||||
try:
|
||||
outs, errs = proc.communicate(input="00", timeout=0.5)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
outs, errs = proc.communicate()
|
||||
|
||||
if "socket: Protocol not supported" in errs or "socket: Protocol not supported" in outs:
|
||||
return False
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@pytest.mark.parametrize("payload_hex", [
|
||||
"AA BB CC", # Single Frame (< 8 bytes)
|
||||
"00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE" # Multi Frame (> 8 bytes, triggers segmentation)
|
||||
])
|
||||
def test_isotp_transmission(bin_path, can_interface, payload_hex):
|
||||
"""
|
||||
Test ISO-TP transmission between isotpsend and isotprecv.
|
||||
|
||||
Description:
|
||||
1. Start isotprecv (Receiver) in background.
|
||||
It listens on RX_ID (e.g., 321) and sends Flow Control on TX_ID (e.g., 123).
|
||||
2. Run isotpsend (Sender).
|
||||
It sends on TX_ID (e.g., 123) and listens for Flow Control on RX_ID (e.g., 321).
|
||||
3. Verify that isotprecv outputs exactly the data sent by isotpsend.
|
||||
|
||||
This verifies:
|
||||
- Kernel ISO-TP stack (socket creation).
|
||||
- Addressing (Source/Dest IDs).
|
||||
- Segmentation/Reassembly (for long payloads).
|
||||
"""
|
||||
isotpsend = os.path.join(bin_path, "isotpsend")
|
||||
isotprecv = os.path.join(bin_path, "isotprecv")
|
||||
|
||||
if not os.path.exists(isotpsend) or not os.path.exists(isotprecv):
|
||||
pytest.skip("isotpsend or isotprecv binary not found.")
|
||||
|
||||
if not check_isotp_support(bin_path, can_interface):
|
||||
pytest.skip("ISO-TP kernel support missing (socket failed). Load can-isotp module.")
|
||||
|
||||
# IDs for the connection
|
||||
# Sender transmits with ID_A, Receiver listens to ID_A
|
||||
ID_A = "123"
|
||||
ID_B = "321"
|
||||
|
||||
# 1. Start Receiver (isotprecv)
|
||||
# -s <src> -d <dst>
|
||||
# In isotprecv context: -s is usually the ID it sends FC on, -d is ID it listens to?
|
||||
# Actually, can-utils convention is often:
|
||||
# isotprecv -s <tx_id> -d <rx_id>
|
||||
# To listen to what isotpsend (-s 123 -d 321) sends:
|
||||
# We need a socket that listens on 123 and writes on 321.
|
||||
# So isotprecv should be: -s 321 -d 123
|
||||
recv_cmd = [isotprecv, "-s", ID_B, "-d", ID_A, can_interface]
|
||||
|
||||
recv_proc = subprocess.Popen(
|
||||
recv_cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
try:
|
||||
time.sleep(0.5) # Wait for receiver to bind socket
|
||||
|
||||
# 2. Run Sender (isotpsend)
|
||||
# -s 123 -d 321
|
||||
send_cmd = [isotpsend, "-s", ID_A, "-d", ID_B, can_interface]
|
||||
|
||||
# Pass payload via stdin
|
||||
send_proc = subprocess.run(
|
||||
send_cmd,
|
||||
input=payload_hex,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
timeout=2
|
||||
)
|
||||
|
||||
assert send_proc.returncode == 0, f"isotpsend failed: {send_proc.stderr}"
|
||||
|
||||
# 3. Verify Receiver Output
|
||||
# isotprecv prints the received PDU. It might not exit automatically unless we kill it
|
||||
# OR if we used -l (loop). Without -l, it typically exits after one PDU.
|
||||
# We wait briefly for it to finish.
|
||||
try:
|
||||
recv_stdout, recv_stderr = recv_proc.communicate(timeout=2)
|
||||
except subprocess.TimeoutExpired:
|
||||
recv_proc.terminate()
|
||||
recv_stdout, recv_stderr = recv_proc.communicate()
|
||||
|
||||
# Check if the payload is in the output
|
||||
# Output format is usually "AA BB CC ..."
|
||||
# We normalize spaces for comparison
|
||||
expected = " ".join(payload_hex.split())
|
||||
actual = " ".join(recv_stdout.split())
|
||||
|
||||
print(f"--- Sender Stderr: ---\n{send_proc.stderr}")
|
||||
print(f"--- Receiver Stdout: ---\n{recv_stdout}")
|
||||
|
||||
assert expected in actual, f"Receiver did not receive correct data. Expected '{expected}', got '{actual}'"
|
||||
|
||||
finally:
|
||||
if recv_proc.poll() is None:
|
||||
recv_proc.terminate()
|
||||
recv_proc.wait()
|
||||
@@ -0,0 +1,218 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import os
|
||||
import pytest
|
||||
import signal
|
||||
import pty
|
||||
import select
|
||||
|
||||
# Note: bin_path and interface fixtures are provided implicitly by conftest.py
|
||||
|
||||
def check_isotp_support(bin_path, interface):
|
||||
"""
|
||||
Helper to check if ISO-TP kernel module is loaded/available.
|
||||
Reused logic to prevent failing tests on systems without can-isotp.
|
||||
"""
|
||||
isotpsend = os.path.join(bin_path, "isotpsend")
|
||||
if not os.path.exists(isotpsend):
|
||||
return False
|
||||
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
[isotpsend, "-s", "123", "-d", "321", interface],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
try:
|
||||
outs, errs = proc.communicate(input="00", timeout=0.5)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
outs, errs = proc.communicate()
|
||||
|
||||
if "socket: Protocol not supported" in errs or "socket: Protocol not supported" in outs:
|
||||
return False
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def test_isotpdump_usage(bin_path):
|
||||
"""
|
||||
Test usage/help output for isotpdump.
|
||||
|
||||
Manual Reproduction:
|
||||
Run: ./isotpdump -h
|
||||
Expect: Output containing "Usage: isotpdump".
|
||||
"""
|
||||
isotpdump = os.path.join(bin_path, "isotpdump")
|
||||
|
||||
if not os.path.exists(isotpdump):
|
||||
pytest.skip("isotpdump binary not found")
|
||||
|
||||
# Running with -h usually triggers error output about invalid option 'h'
|
||||
# or just prints usage.
|
||||
result = subprocess.run(
|
||||
[isotpdump, "-h"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
assert "Usage: isotpdump" in result.stderr or "Usage: isotpdump" in result.stdout
|
||||
|
||||
@pytest.mark.parametrize("payload_hex, desc", [
|
||||
("11 22 33", "Single Frame"),
|
||||
("00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE", "Multi Frame (Segmentation)")
|
||||
])
|
||||
def test_isotpdump_traffic(bin_path, can_interface, payload_hex, desc):
|
||||
"""
|
||||
Test that isotpdump correctly captures and decodes ISO-TP traffic.
|
||||
|
||||
Description:
|
||||
1. Start isotpdump in background using a PTY.
|
||||
It uses a raw CAN socket to monitor traffic and interprets ISO-TP headers.
|
||||
2. Start isotprecv (Background) to handle Flow Control (FC).
|
||||
3. Run isotpsend to generate traffic.
|
||||
4. Read dump output continuously.
|
||||
5. Stop dump with SIGINT.
|
||||
6. Verify dump output contains the payload bytes.
|
||||
|
||||
Manual Reproduction:
|
||||
1. Terminal 1: ./isotpdump -s 123 -d 321 vcan0
|
||||
2. Terminal 2: ./isotprecv -s 321 -d 123 vcan0
|
||||
3. Terminal 3: echo "11 22 33" | ./isotpsend -s 123 -d 321 vcan0
|
||||
"""
|
||||
isotpdump = os.path.join(bin_path, "isotpdump")
|
||||
isotpsend = os.path.join(bin_path, "isotpsend")
|
||||
isotprecv = os.path.join(bin_path, "isotprecv")
|
||||
|
||||
for tool in [isotpdump, isotpsend, isotprecv]:
|
||||
if not os.path.exists(tool):
|
||||
pytest.skip(f"{tool} not found")
|
||||
|
||||
if not check_isotp_support(bin_path, can_interface):
|
||||
pytest.skip("ISO-TP kernel support missing")
|
||||
|
||||
SRC_ID = "123"
|
||||
DST_ID = "321"
|
||||
|
||||
print(f"\nDEBUG: Starting test '{desc}'")
|
||||
|
||||
# 1. Start Dump with PTY
|
||||
master_fd, slave_fd = pty.openpty()
|
||||
|
||||
# -s SourceID, -d DestID
|
||||
dump_cmd = [isotpdump, "-s", SRC_ID, "-d", DST_ID, can_interface]
|
||||
print(f"DEBUG: Starting dump: {' '.join(dump_cmd)}")
|
||||
|
||||
dump_proc = subprocess.Popen(
|
||||
dump_cmd,
|
||||
stdin=slave_fd,
|
||||
stdout=slave_fd,
|
||||
stderr=slave_fd,
|
||||
close_fds=True
|
||||
)
|
||||
os.close(slave_fd)
|
||||
|
||||
output_bytes = b""
|
||||
|
||||
try:
|
||||
time.sleep(1.0) # Wait for startup
|
||||
|
||||
if dump_proc.poll() is not None:
|
||||
pytest.fail(f"isotpdump process died during startup. RC={dump_proc.returncode}")
|
||||
|
||||
# 2. Start Receiver (isotprecv)
|
||||
# Required for Multi-Frame Flow Control
|
||||
recv_cmd = [isotprecv, "-s", DST_ID, "-d", SRC_ID, can_interface]
|
||||
recv_proc = subprocess.Popen(
|
||||
recv_cmd,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
try:
|
||||
time.sleep(1.0)
|
||||
|
||||
if recv_proc.poll() is not None:
|
||||
_, err = recv_proc.communicate()
|
||||
pytest.fail(f"isotprecv failed to start. RC={recv_proc.returncode}. Stderr: {err}")
|
||||
|
||||
# 3. Send Data (isotpsend)
|
||||
send_cmd = [isotpsend, "-s", SRC_ID, "-d", DST_ID, can_interface]
|
||||
print(f"DEBUG: Sending data: {' '.join(send_cmd)} with payload '{payload_hex}'")
|
||||
|
||||
subprocess.run(
|
||||
send_cmd,
|
||||
input=payload_hex,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
check=True,
|
||||
timeout=2
|
||||
)
|
||||
|
||||
# 4. Capture dump output
|
||||
start_wait = time.time()
|
||||
# isotpdump output format is raw-ish but includes data bytes.
|
||||
# Example: "vcan0 123 [3] 11 22 33"
|
||||
expected_parts = payload_hex.split()
|
||||
print("DEBUG: Waiting for data in dump output...")
|
||||
|
||||
while time.time() - start_wait < 4:
|
||||
r, _, _ = select.select([master_fd], [], [], 0.1)
|
||||
if master_fd in r:
|
||||
try:
|
||||
chunk = os.read(master_fd, 4096)
|
||||
if chunk:
|
||||
output_bytes += chunk
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
current_out = output_bytes.decode('utf-8', errors='replace')
|
||||
# Check if we have the data
|
||||
if all(part in current_out for part in expected_parts):
|
||||
print("DEBUG: Found all expected parts in output.")
|
||||
break
|
||||
|
||||
if dump_proc.poll() is not None:
|
||||
break
|
||||
|
||||
finally:
|
||||
if recv_proc.poll() is None:
|
||||
recv_proc.terminate()
|
||||
recv_proc.wait()
|
||||
|
||||
finally:
|
||||
if dump_proc.poll() is None:
|
||||
print("DEBUG: Sending SIGINT to dump.")
|
||||
dump_proc.send_signal(signal.SIGINT)
|
||||
try:
|
||||
end_time = time.time() + 1
|
||||
while time.time() < end_time:
|
||||
r, _, _ = select.select([master_fd], [], [], 0.1)
|
||||
if master_fd in r:
|
||||
try:
|
||||
chunk = os.read(master_fd, 4096)
|
||||
except OSError:
|
||||
break
|
||||
if dump_proc.poll() is not None:
|
||||
break
|
||||
finally:
|
||||
if dump_proc.poll() is None:
|
||||
dump_proc.kill()
|
||||
dump_proc.wait()
|
||||
|
||||
os.close(master_fd)
|
||||
|
||||
output = output_bytes.decode('utf-8', errors='replace')
|
||||
print(f"--- Dump Output ({desc}) ---\n{output}")
|
||||
|
||||
expected_parts = payload_hex.split()
|
||||
missing_bytes = [b for b in expected_parts if b not in output]
|
||||
|
||||
assert not missing_bytes, f"Missing bytes in dump output: {missing_bytes}. Output:\n{output}"
|
||||
@@ -0,0 +1,193 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import os
|
||||
import pytest
|
||||
import signal
|
||||
import pty
|
||||
import select
|
||||
|
||||
# Note: bin_path and interface fixtures are provided implicitly by conftest.py
|
||||
|
||||
def check_isotp_support(bin_path, interface):
|
||||
"""
|
||||
Helper to check if ISO-TP kernel module is loaded/available.
|
||||
Reused logic to prevent failing tests on systems without can-isotp.
|
||||
"""
|
||||
isotpsend = os.path.join(bin_path, "isotpsend")
|
||||
if not os.path.exists(isotpsend):
|
||||
return False
|
||||
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
[isotpsend, "-s", "123", "-d", "321", interface],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
try:
|
||||
outs, errs = proc.communicate(input="00", timeout=0.5)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
outs, errs = proc.communicate()
|
||||
|
||||
if "socket: Protocol not supported" in errs or "socket: Protocol not supported" in outs:
|
||||
return False
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def test_isotpperf_usage(bin_path):
|
||||
"""
|
||||
Test usage/help output for isotpperf.
|
||||
|
||||
Manual Reproduction:
|
||||
1. Run: ./isotpperf -h
|
||||
2. Expect: Output containing "Usage: isotpperf".
|
||||
"""
|
||||
isotpperf = os.path.join(bin_path, "isotpperf")
|
||||
|
||||
if not os.path.exists(isotpperf):
|
||||
pytest.skip("isotpperf binary not found")
|
||||
|
||||
result = subprocess.run(
|
||||
[isotpperf, "-h"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
# Check for usage info in stdout or stderr (some tools print help to stderr)
|
||||
assert "Usage: isotpperf" in result.stderr or "Usage: isotpperf" in result.stdout
|
||||
|
||||
def test_isotpperf_measurement(bin_path, can_interface):
|
||||
"""
|
||||
Test isotpperf measurement functionality with a separate receiver.
|
||||
|
||||
Description:
|
||||
1. Start isotpperf to monitor/measure the transfer.
|
||||
2. Start isotprecv (in background) to act as the active receiver (providing Flow Control).
|
||||
3. Send data (25 bytes) using isotpsend.
|
||||
4. Verify that isotpperf correctly reports "25 byte in".
|
||||
|
||||
Manual Reproduction:
|
||||
1. ./isotpperf -s 123 -d 321 vcan0
|
||||
2. ./isotprecv -s 321 -d 123 vcan0 (Required for Flow Control on multi-frame)
|
||||
3. echo "00 ... 11" | ./isotpsend -s 123 -d 321 vcan0
|
||||
4. Check isotpperf output for "25 byte in".
|
||||
"""
|
||||
isotpperf = os.path.join(bin_path, "isotpperf")
|
||||
isotprecv = os.path.join(bin_path, "isotprecv")
|
||||
isotpsend = os.path.join(bin_path, "isotpsend")
|
||||
|
||||
for tool in [isotpperf, isotprecv, isotpsend]:
|
||||
if not os.path.exists(tool):
|
||||
pytest.skip(f"{tool} not found")
|
||||
|
||||
if not check_isotp_support(bin_path, can_interface):
|
||||
pytest.skip("ISO-TP kernel support missing")
|
||||
|
||||
perf_src = "123"
|
||||
perf_dst = "321"
|
||||
|
||||
# 1. Start isotpperf
|
||||
# Arguments: -s <source_id> -d <dest_id> <interface>
|
||||
cmd_perf = [isotpperf, "-s", perf_src, "-d", perf_dst, can_interface]
|
||||
print(f"DEBUG: Starting isotpperf: {' '.join(cmd_perf)}")
|
||||
|
||||
# Use PTY for isotpperf to force unbuffered output behavior
|
||||
master_fd, slave_fd = pty.openpty()
|
||||
|
||||
perf_proc = subprocess.Popen(
|
||||
cmd_perf,
|
||||
stdout=slave_fd,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
os.close(slave_fd) # Close slave in parent
|
||||
|
||||
recv_proc = None
|
||||
|
||||
try:
|
||||
time.sleep(0.5)
|
||||
if perf_proc.poll() is not None:
|
||||
_, err = perf_proc.communicate()
|
||||
pytest.fail(f"isotpperf failed to start. Stderr: {err}")
|
||||
|
||||
# 2. Start isotprecv (The Receiver / Flow Control provider)
|
||||
# It needs reversed IDs relative to the sender to acknowledge frames.
|
||||
# Sender (isotpsend): -s 123 -d 321
|
||||
# Receiver (isotprecv): -s 321 -d 123
|
||||
cmd_recv = [isotprecv, "-s", perf_dst, "-d", perf_src, can_interface]
|
||||
print(f"DEBUG: Starting isotprecv: {' '.join(cmd_recv)}")
|
||||
|
||||
# We don't strictly need isotprecv's output, but we need it running.
|
||||
# Pipe output to DEVNULL to avoid buffer blocking.
|
||||
recv_proc = subprocess.Popen(
|
||||
cmd_recv,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL
|
||||
)
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
# 3. Send data (25 bytes)
|
||||
# "00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE 11 11 11 11 11 11 11 11 11 11"
|
||||
payload_bytes = "00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE 11 11 11 11 11 11 11 11 11 11"
|
||||
cmd_send = [isotpsend, "-s", perf_src, "-d", perf_dst, can_interface]
|
||||
|
||||
print(f"DEBUG: Sending data via isotpsend: {' '.join(cmd_send)}")
|
||||
subprocess.run(
|
||||
cmd_send,
|
||||
input=payload_bytes,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
|
||||
# Wait a bit for isotpperf to receive and print
|
||||
# Reading from PTY
|
||||
output = ""
|
||||
start_time = time.time()
|
||||
found = False
|
||||
expected_msg = "25 byte in"
|
||||
|
||||
while time.time() - start_time < 3.0:
|
||||
r, _, _ = select.select([master_fd], [], [], 0.1)
|
||||
if master_fd in r:
|
||||
try:
|
||||
chunk = os.read(master_fd, 1024).decode('utf-8', errors='replace')
|
||||
if chunk:
|
||||
output += chunk
|
||||
# Check continuously
|
||||
if expected_msg in output:
|
||||
found = True
|
||||
break
|
||||
except OSError:
|
||||
break
|
||||
|
||||
if perf_proc.poll() is not None:
|
||||
break
|
||||
|
||||
print(f"DEBUG: isotpperf output (PTY):\n{output}")
|
||||
|
||||
assert found, f"isotpperf did not report '{expected_msg}'. Captured: {output}"
|
||||
|
||||
finally:
|
||||
# Cleanup
|
||||
if recv_proc:
|
||||
recv_proc.terminate()
|
||||
recv_proc.wait()
|
||||
|
||||
if perf_proc.poll() is None:
|
||||
perf_proc.terminate()
|
||||
try:
|
||||
perf_proc.wait(timeout=1)
|
||||
except subprocess.TimeoutExpired:
|
||||
perf_proc.kill()
|
||||
|
||||
if master_fd:
|
||||
os.close(master_fd)
|
||||
@@ -0,0 +1,251 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import os
|
||||
import pytest
|
||||
import socket
|
||||
import signal
|
||||
import pty
|
||||
import select
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
# Note: bin_path and interface fixtures are provided implicitly by conftest.py
|
||||
|
||||
def check_isotp_support(bin_path, interface):
|
||||
"""
|
||||
Helper to check if ISO-TP kernel module is loaded/available.
|
||||
Reused logic to prevent failing tests on systems without can-isotp.
|
||||
"""
|
||||
isotpsend = os.path.join(bin_path, "isotpsend")
|
||||
if not os.path.exists(isotpsend):
|
||||
return False
|
||||
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
[isotpsend, "-s", "123", "-d", "321", interface],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
try:
|
||||
outs, errs = proc.communicate(input="00", timeout=0.5)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
outs, errs = proc.communicate()
|
||||
|
||||
if "socket: Protocol not supported" in errs or "socket: Protocol not supported" in outs:
|
||||
return False
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def get_free_port():
|
||||
"""Find a free port on localhost."""
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.bind(('127.0.0.1', 0))
|
||||
return s.getsockname()[1]
|
||||
|
||||
def test_isotpserver_usage(bin_path):
|
||||
"""
|
||||
Test usage/help output for isotpserver.
|
||||
|
||||
Manual Reproduction:
|
||||
1. Run: ./isotpserver -h
|
||||
2. Expect: Output containing "Usage: isotpserver".
|
||||
"""
|
||||
isotpserver = os.path.join(bin_path, "isotpserver")
|
||||
|
||||
if not os.path.exists(isotpserver):
|
||||
pytest.skip("isotpserver binary not found")
|
||||
|
||||
result = subprocess.run(
|
||||
[isotpserver, "-h"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
assert "Usage: isotpserver" in result.stderr or "Usage: isotpserver" in result.stdout
|
||||
|
||||
def test_isotpserver_bridging(bin_path, can_interface):
|
||||
"""
|
||||
Test TCP <-> CAN bridging using isotpserver with specific format <HEX>.
|
||||
|
||||
Description:
|
||||
1. Start candump to monitor bus traffic (diagnostics).
|
||||
2. Start isotpserver on a local port.
|
||||
3. Start isotprecv to capture CAN frames sent by server.
|
||||
4. Connect a TCP client to the server.
|
||||
5. Send ASCII HEX via TCP in format <112233> -> Verify reception on CAN (via isotprecv as '11 22 33').
|
||||
6. Send ASCII HEX via CAN (isotpsend '44 55 66') -> Verify reception on TCP as <445566>.
|
||||
|
||||
Manual Reproduction:
|
||||
1. ./isotpserver -l 12345 -s 123 -d 321 vcan0
|
||||
2. ./isotprecv -s 321 -d 123 vcan0
|
||||
3. telnet localhost 12345
|
||||
4. Type "<112233>" in telnet -> Check isotprecv output for "11 22 33".
|
||||
5. echo "44 55 66" | ./isotpsend -s 321 -d 123 vcan0
|
||||
6. Check telnet output for "<445566>".
|
||||
"""
|
||||
isotpserver = os.path.join(bin_path, "isotpserver")
|
||||
isotprecv = os.path.join(bin_path, "isotprecv")
|
||||
isotpsend = os.path.join(bin_path, "isotpsend")
|
||||
candump = os.path.join(bin_path, "candump")
|
||||
|
||||
for tool in [isotpserver, isotprecv, isotpsend, candump]:
|
||||
if not os.path.exists(tool):
|
||||
pytest.skip(f"{tool} not found")
|
||||
|
||||
if not check_isotp_support(bin_path, can_interface):
|
||||
pytest.skip("ISO-TP kernel support missing")
|
||||
|
||||
port = get_free_port()
|
||||
SRC_ID = "123"
|
||||
DST_ID = "321"
|
||||
|
||||
# 0. Start candump for diagnostics
|
||||
dump_out = tempfile.TemporaryFile(mode='w+')
|
||||
print(f"DEBUG: Starting candump on {can_interface}")
|
||||
dump_proc = subprocess.Popen(
|
||||
[candump, "-L", can_interface],
|
||||
stdout=dump_out,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
# 1. Start isotpserver
|
||||
# We use PIPE for stderr to catch errors
|
||||
server_cmd = [isotpserver, "-l", str(port), "-s", SRC_ID, "-d", DST_ID, can_interface]
|
||||
print(f"DEBUG: Starting isotpserver: {' '.join(server_cmd)}")
|
||||
server_proc = subprocess.Popen(
|
||||
server_cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
recv_master_fd = None
|
||||
recv_slave_fd = None
|
||||
recv_proc = None
|
||||
sock = None
|
||||
|
||||
try:
|
||||
time.sleep(0.5)
|
||||
if server_proc.poll() is not None:
|
||||
_, err = server_proc.communicate()
|
||||
pytest.fail(f"isotpserver failed to start. Stderr: {err}")
|
||||
|
||||
# 2. Start isotprecv (to receive what server sends to CAN)
|
||||
# Use PTY to avoid buffering issues
|
||||
recv_master_fd, recv_slave_fd = pty.openpty()
|
||||
|
||||
recv_cmd = [isotprecv, "-s", DST_ID, "-d", SRC_ID, can_interface]
|
||||
print(f"DEBUG: Starting isotprecv: {' '.join(recv_cmd)}")
|
||||
recv_proc = subprocess.Popen(
|
||||
recv_cmd,
|
||||
stdout=recv_slave_fd,
|
||||
stderr=subprocess.PIPE, # Capture errors normally
|
||||
close_fds=True
|
||||
)
|
||||
os.close(recv_slave_fd) # Close slave in parent
|
||||
recv_slave_fd = None # Mark as closed
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
# 3. Connect TCP Client
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(2.0)
|
||||
sock.connect(('127.0.0.1', port))
|
||||
except ConnectionRefusedError:
|
||||
pytest.fail(f"Could not connect to isotpserver on port {port}")
|
||||
|
||||
# --- TCP -> CAN ---
|
||||
# The test requirement is to use <HEX> format.
|
||||
# Sending <112233> to the server.
|
||||
payload_tcp = "<112233>"
|
||||
print(f"DEBUG: Sending via TCP: {payload_tcp}")
|
||||
sock.sendall(payload_tcp.encode('ascii'))
|
||||
|
||||
# Give it a moment to process and forward to CAN
|
||||
time.sleep(1.0)
|
||||
|
||||
# Read isotprecv output via PTY
|
||||
recv_out = ""
|
||||
try:
|
||||
# Simple non-blocking read loop
|
||||
while True:
|
||||
r, _, _ = select.select([recv_master_fd], [], [], 0.1)
|
||||
if recv_master_fd in r:
|
||||
chunk = os.read(recv_master_fd, 1024)
|
||||
if not chunk:
|
||||
break
|
||||
recv_out += chunk.decode('utf-8', errors='replace')
|
||||
else:
|
||||
break
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
print(f"DEBUG: isotprecv output: {recv_out}")
|
||||
|
||||
# Check success
|
||||
# isotprecv prints standard space-separated hex (e.g., "11 22 33")
|
||||
# even if the input was compact <112233>.
|
||||
if "11 22 33" not in recv_out:
|
||||
print("DEBUG: Failure detected in TCP->CAN. Checking candump...")
|
||||
time.sleep(0.5)
|
||||
|
||||
assert "11 22 33" in recv_out, "TCP -> CAN failed: Data not received on CAN bus (isotprecv empty or mismatch)"
|
||||
|
||||
# --- CAN -> TCP ---
|
||||
# Sending standard space-separated hex on CAN
|
||||
payload_can = "44 55 66"
|
||||
print(f"DEBUG: Sending via CAN (isotpsend): {payload_can}")
|
||||
|
||||
send_cmd = [isotpsend, "-s", DST_ID, "-d", SRC_ID, can_interface]
|
||||
subprocess.run(
|
||||
send_cmd,
|
||||
input=payload_can,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
|
||||
# Receive on TCP
|
||||
# Expecting the server to format the output as <445566> based on the requirement.
|
||||
try:
|
||||
tcp_data = sock.recv(1024).decode('ascii')
|
||||
print(f"DEBUG: Received via TCP: {tcp_data}")
|
||||
# The expectation is <445566> (compact hex inside brackets)
|
||||
assert "<445566>" in tcp_data, "CAN -> TCP failed: Data format mismatch or not received on TCP socket"
|
||||
except socket.timeout:
|
||||
pytest.fail("CAN -> TCP failed: Timeout waiting for data on TCP socket")
|
||||
|
||||
finally:
|
||||
if sock:
|
||||
sock.close()
|
||||
|
||||
server_proc.terminate()
|
||||
server_out, server_err = server_proc.communicate()
|
||||
if server_out or server_err:
|
||||
print(f"DEBUG: isotpserver stdout: {server_out}")
|
||||
print(f"DEBUG: isotpserver stderr: {server_err}")
|
||||
|
||||
if recv_proc:
|
||||
recv_proc.terminate()
|
||||
recv_proc.wait()
|
||||
|
||||
if recv_master_fd:
|
||||
os.close(recv_master_fd)
|
||||
if recv_slave_fd: # Should be closed already, but for safety
|
||||
os.close(recv_slave_fd)
|
||||
|
||||
dump_proc.terminate()
|
||||
dump_proc.wait()
|
||||
dump_out.seek(0)
|
||||
print(f"DEBUG: candump output:\n{dump_out.read()}")
|
||||
dump_out.close()
|
||||
@@ -0,0 +1,352 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import os
|
||||
import pytest
|
||||
import tempfile
|
||||
import signal
|
||||
import pty
|
||||
import sys
|
||||
import select
|
||||
|
||||
# Note: bin_path and interface fixtures are provided implicitly by conftest.py
|
||||
|
||||
def check_isotp_support(bin_path, interface):
|
||||
"""
|
||||
Helper to check if ISO-TP kernel module is loaded/available.
|
||||
Reused logic to prevent failing tests on systems without can-isotp.
|
||||
"""
|
||||
isotpsend = os.path.join(bin_path, "isotpsend")
|
||||
if not os.path.exists(isotpsend):
|
||||
return False
|
||||
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
[isotpsend, "-s", "123", "-d", "321", interface],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
try:
|
||||
outs, errs = proc.communicate(input="00", timeout=0.5)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
outs, errs = proc.communicate()
|
||||
|
||||
if "socket: Protocol not supported" in errs or "socket: Protocol not supported" in outs:
|
||||
return False
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def test_isotpsniffer_usage(bin_path):
|
||||
"""
|
||||
Test usage/help output for isotpsniffer.
|
||||
|
||||
Manual Reproduction:
|
||||
Run: ./isotpsniffer -h
|
||||
Expect: Output containing "Usage: isotpsniffer".
|
||||
"""
|
||||
isotpsniffer = os.path.join(bin_path, "isotpsniffer")
|
||||
|
||||
if not os.path.exists(isotpsniffer):
|
||||
pytest.skip("isotpsniffer binary not found")
|
||||
|
||||
# Running with -h usually triggers error output about missing argument for option 'h'
|
||||
# or just prints usage.
|
||||
result = subprocess.run(
|
||||
[isotpsniffer, "-h"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
assert "Usage: isotpsniffer" in result.stderr or "Usage: isotpsniffer" in result.stdout
|
||||
|
||||
@pytest.mark.parametrize("payload_hex, desc", [
|
||||
("11 22 33", "Single Frame"),
|
||||
("00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE", "Multi Frame (Segmentation)")
|
||||
])
|
||||
def test_isotpsniffer_traffic(bin_path, can_interface, payload_hex, desc):
|
||||
"""
|
||||
Test that isotpsniffer correctly captures ISO-TP traffic.
|
||||
|
||||
Description:
|
||||
1. Start isotpsniffer in background using a PTY (pseudo-terminal).
|
||||
It sniffs raw CAN traffic and decodes ISO-TP.
|
||||
2. Start isotprecv (Background).
|
||||
It acts as the ISO-TP destination node, handling Flow Control (FC).
|
||||
This matches the manual reproduction where isotprecv is running.
|
||||
3. Run isotpsend to generate traffic.
|
||||
4. Read sniffer output continuously.
|
||||
5. Stop sniffer with SIGINT.
|
||||
6. Verify sniffer output contains the payload bytes.
|
||||
|
||||
Manual Reproduction (Single Frame):
|
||||
1. Terminal 1: ./isotpsniffer -s 123 -d 321 vcan0
|
||||
2. Terminal 2: ./isotprecv -s 321 -d 123 vcan0
|
||||
3. Terminal 3: echo "11 22 33" | ./isotpsend -s 123 -d 321 vcan0
|
||||
4. Observe Terminal 1 output.
|
||||
|
||||
Manual Reproduction (Multi Frame):
|
||||
1. Terminal 1: ./isotpsniffer -s 123 -d 321 vcan0
|
||||
2. Terminal 2: ./isotprecv -s 321 -d 123 vcan0
|
||||
3. Terminal 3: echo "00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE" | ./isotpsend -s 123 -d 321 vcan0
|
||||
4. Observe Terminal 1 output.
|
||||
"""
|
||||
isotpsniffer = os.path.join(bin_path, "isotpsniffer")
|
||||
isotpsend = os.path.join(bin_path, "isotpsend")
|
||||
isotprecv = os.path.join(bin_path, "isotprecv")
|
||||
|
||||
for tool in [isotpsniffer, isotpsend, isotprecv]:
|
||||
if not os.path.exists(tool):
|
||||
pytest.skip(f"{tool} not found")
|
||||
|
||||
if not check_isotp_support(bin_path, can_interface):
|
||||
pytest.skip("ISO-TP kernel support missing")
|
||||
|
||||
SRC_ID = "123"
|
||||
DST_ID = "321"
|
||||
|
||||
print(f"\nDEBUG: Starting test '{desc}'")
|
||||
|
||||
# 1. Start Sniffer with PTY
|
||||
# We use PTY to force line buffering and ensure captured output
|
||||
master_fd, slave_fd = pty.openpty()
|
||||
|
||||
sniff_cmd = [isotpsniffer, "-s", SRC_ID, "-d", DST_ID, can_interface]
|
||||
print(f"DEBUG: Starting sniffer: {' '.join(sniff_cmd)}")
|
||||
sniff_proc = subprocess.Popen(
|
||||
sniff_cmd,
|
||||
stdin=slave_fd,
|
||||
stdout=slave_fd,
|
||||
stderr=slave_fd,
|
||||
close_fds=True
|
||||
)
|
||||
os.close(slave_fd)
|
||||
|
||||
output_bytes = b""
|
||||
|
||||
try:
|
||||
time.sleep(1.0)
|
||||
|
||||
if sniff_proc.poll() is not None:
|
||||
pytest.fail(f"isotpsniffer process died during startup. RC={sniff_proc.returncode}")
|
||||
|
||||
# 2. Start Receiver (isotprecv)
|
||||
recv_cmd = [isotprecv, "-s", DST_ID, "-d", SRC_ID, can_interface]
|
||||
print(f"DEBUG: Starting receiver: {' '.join(recv_cmd)}")
|
||||
recv_proc = subprocess.Popen(
|
||||
recv_cmd,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
try:
|
||||
time.sleep(1.0)
|
||||
|
||||
if recv_proc.poll() is not None:
|
||||
_, err = recv_proc.communicate()
|
||||
pytest.fail(f"isotprecv failed to start. RC={recv_proc.returncode}. Stderr: {err}")
|
||||
|
||||
# 3. Send Data (isotpsend)
|
||||
send_cmd = [isotpsend, "-s", SRC_ID, "-d", DST_ID, can_interface]
|
||||
print(f"DEBUG: Sending data: {' '.join(send_cmd)} with payload '{payload_hex}'")
|
||||
|
||||
subprocess.run(
|
||||
send_cmd,
|
||||
input=payload_hex,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
check=True,
|
||||
timeout=2
|
||||
)
|
||||
|
||||
# 4. Capture sniffer output
|
||||
start_wait = time.time()
|
||||
expected_parts = payload_hex.split()
|
||||
print("DEBUG: Waiting for data in sniffer output...")
|
||||
|
||||
while time.time() - start_wait < 4:
|
||||
r, _, _ = select.select([master_fd], [], [], 0.1)
|
||||
if master_fd in r:
|
||||
try:
|
||||
chunk = os.read(master_fd, 4096)
|
||||
if chunk:
|
||||
output_bytes += chunk
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
current_out = output_bytes.decode('utf-8', errors='replace')
|
||||
if all(part in current_out for part in expected_parts):
|
||||
print("DEBUG: Found all expected parts in output.")
|
||||
break
|
||||
|
||||
if sniff_proc.poll() is not None:
|
||||
break
|
||||
|
||||
finally:
|
||||
if recv_proc.poll() is None:
|
||||
recv_proc.terminate()
|
||||
recv_proc.wait()
|
||||
|
||||
finally:
|
||||
if sniff_proc.poll() is None:
|
||||
print("DEBUG: Sending SIGINT to sniffer.")
|
||||
sniff_proc.send_signal(signal.SIGINT)
|
||||
try:
|
||||
end_time = time.time() + 1
|
||||
while time.time() < end_time:
|
||||
r, _, _ = select.select([master_fd], [], [], 0.1)
|
||||
if master_fd in r:
|
||||
try:
|
||||
chunk = os.read(master_fd, 4096)
|
||||
except OSError:
|
||||
break
|
||||
if sniff_proc.poll() is not None:
|
||||
break
|
||||
finally:
|
||||
if sniff_proc.poll() is None:
|
||||
sniff_proc.kill()
|
||||
sniff_proc.wait()
|
||||
|
||||
os.close(master_fd)
|
||||
|
||||
output = output_bytes.decode('utf-8', errors='replace')
|
||||
print(f"--- Sniffer Output ({desc}) ---\n{output}")
|
||||
|
||||
expected_parts = payload_hex.split()
|
||||
missing_bytes = [b for b in expected_parts if b not in output]
|
||||
|
||||
assert not missing_bytes, f"Missing bytes in sniffer output: {missing_bytes}. Output:\n{output}"
|
||||
|
||||
def test_isotp_protocol_compliance(bin_path, can_interface):
|
||||
"""
|
||||
Verify raw ISO-TP protocol behavior on the bus using candump.
|
||||
This ensures that segmentation and flow control are actually happening.
|
||||
|
||||
Scenario: Multi-Frame Transfer (>7 bytes)
|
||||
|
||||
Manual Reproduction:
|
||||
1. Terminal 1: candump vcan0
|
||||
2. Terminal 2: ./isotprecv -s 321 -d 123 vcan0
|
||||
3. Terminal 3: echo "00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE" | ./isotpsend -s 123 -d 321 vcan0
|
||||
|
||||
Expect in Terminal 1 (Raw Frames):
|
||||
- First Frame (FF) from Sender: ID=123, PCI=0x1...
|
||||
- Flow Control (FC) from Receiver: ID=321, PCI=0x3...
|
||||
- Consecutive Frames (CF) from Sender: ID=123, PCI=0x2...
|
||||
"""
|
||||
isotpsend = os.path.join(bin_path, "isotpsend")
|
||||
isotprecv = os.path.join(bin_path, "isotprecv")
|
||||
candump = os.path.join(bin_path, "candump")
|
||||
|
||||
for tool in [isotpsend, isotprecv, candump]:
|
||||
if not os.path.exists(tool):
|
||||
pytest.skip(f"{tool} not found")
|
||||
|
||||
if not check_isotp_support(bin_path, can_interface):
|
||||
pytest.skip("ISO-TP kernel support missing")
|
||||
|
||||
SRC_ID = "123" # 0x07B
|
||||
DST_ID = "321" # 0x141
|
||||
# 14 bytes payload -> requires segmentation (FF + CF)
|
||||
payload_hex = "00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE"
|
||||
|
||||
# 1. Start candump (Raw Monitor)
|
||||
# We use a temp file to avoid pipe buffering issues
|
||||
with tempfile.TemporaryFile(mode='w+') as dump_out:
|
||||
dump_proc = subprocess.Popen(
|
||||
[candump, can_interface],
|
||||
stdout=dump_out,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
# 2. Start Receiver
|
||||
recv_proc = subprocess.Popen(
|
||||
[isotprecv, "-s", DST_ID, "-d", SRC_ID, can_interface],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE
|
||||
)
|
||||
|
||||
try:
|
||||
time.sleep(1.0) # Wait for startup
|
||||
|
||||
# 3. Start Sender
|
||||
subprocess.run(
|
||||
[isotpsend, "-s", SRC_ID, "-d", DST_ID, can_interface],
|
||||
input=payload_hex,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
check=True,
|
||||
timeout=2
|
||||
)
|
||||
|
||||
time.sleep(1.0) # Allow transmission to complete
|
||||
|
||||
finally:
|
||||
recv_proc.terminate()
|
||||
recv_proc.wait()
|
||||
dump_proc.terminate()
|
||||
dump_proc.wait()
|
||||
|
||||
# 4. Analyze Raw Dump
|
||||
dump_out.seek(0)
|
||||
raw_traffic = dump_out.read()
|
||||
print(f"--- Raw CAN Traffic ---\n{raw_traffic}")
|
||||
|
||||
# Check for ISO-TP Protocol Elements
|
||||
# Note: candump format is usually "interface ID [len] data..."
|
||||
|
||||
# 1. Check for First Frame (FF) sent by SRC (123)
|
||||
# FF PCI starts with nibble 1 (e.g., 10 0E ...)
|
||||
# We look for ID 123 and data starting with 1
|
||||
ff_found = False
|
||||
|
||||
# 2. Check for Flow Control (FC) sent by DST (321)
|
||||
# FC PCI starts with nibble 3 (e.g., 30 00 00)
|
||||
fc_found = False
|
||||
|
||||
# 3. Check for Consecutive Frame (CF) sent by SRC (123)
|
||||
# CF PCI starts with nibble 2 (e.g., 21 ...)
|
||||
cf_found = False
|
||||
|
||||
for line in raw_traffic.splitlines():
|
||||
if SRC_ID in line:
|
||||
# Check data bytes for Sender
|
||||
# Typical line: "vcan0 123 [8] 10 0E 00 11 22 33 44 55"
|
||||
parts = line.split()
|
||||
if "[" in parts[2]: # Format with [len]
|
||||
data_idx = 3
|
||||
else: # Format without [len]
|
||||
data_idx = 2
|
||||
|
||||
if len(parts) > data_idx:
|
||||
first_byte = int(parts[data_idx], 16)
|
||||
if (first_byte & 0xF0) == 0x10:
|
||||
ff_found = True
|
||||
elif (first_byte & 0xF0) == 0x20:
|
||||
cf_found = True
|
||||
|
||||
elif DST_ID in line:
|
||||
# Check data bytes for Receiver (FC)
|
||||
parts = line.split()
|
||||
if "[" in parts[2]:
|
||||
data_idx = 3
|
||||
else:
|
||||
data_idx = 2
|
||||
|
||||
if len(parts) > data_idx:
|
||||
first_byte = int(parts[data_idx], 16)
|
||||
if (first_byte & 0xF0) == 0x30:
|
||||
fc_found = True
|
||||
|
||||
assert ff_found, "Missing ISO-TP First Frame (FF - 0x1...) in raw traffic"
|
||||
assert fc_found, "Missing ISO-TP Flow Control (FC - 0x3...) in raw traffic. Kernel stack might not be responding."
|
||||
assert cf_found, "Missing ISO-TP Consecutive Frame (CF - 0x2...) in raw traffic"
|
||||
@@ -0,0 +1,256 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import os
|
||||
import pytest
|
||||
import signal
|
||||
import tempfile
|
||||
|
||||
# Note: bin_path and interface fixtures are provided implicitly by conftest.py
|
||||
|
||||
def wait_for_can_msg(file_obj, pattern, timeout=5.0):
|
||||
"""
|
||||
Helper to poll the log file for a specific pattern.
|
||||
Returns True if found within timeout, False otherwise.
|
||||
"""
|
||||
start = time.time()
|
||||
while time.time() - start < timeout:
|
||||
file_obj.seek(0)
|
||||
content = file_obj.read()
|
||||
if pattern in content:
|
||||
return True
|
||||
time.sleep(0.1)
|
||||
return False
|
||||
|
||||
def test_j1939acd_usage(bin_path):
|
||||
"""
|
||||
Test usage/help output for j1939acd.
|
||||
"""
|
||||
j1939acd = os.path.join(bin_path, "j1939acd")
|
||||
if not os.path.exists(j1939acd):
|
||||
pytest.skip("j1939acd binary not found")
|
||||
|
||||
result = subprocess.run(
|
||||
[j1939acd, "-h"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
assert "Usage: j1939acd" in result.stderr or "Usage: j1939acd" in result.stdout
|
||||
|
||||
def test_j1939acd_claiming_lifecycle(bin_path, can_interface):
|
||||
"""
|
||||
Test J1939 Address Claiming procedure (Startup & Shutdown).
|
||||
Corresponds to Test Scenario 1 (Happy Flow) and Cleanup.
|
||||
"""
|
||||
j1939acd = os.path.join(bin_path, "j1939acd")
|
||||
candump = os.path.join(bin_path, "candump")
|
||||
|
||||
if not os.path.exists(j1939acd):
|
||||
pytest.skip("j1939acd binary not found")
|
||||
|
||||
nodename_hex = "1122334455667788"
|
||||
# candump -L format uses compact hex strings (no spaces)
|
||||
nodename_payload = "8877665544332211"
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix=".jacd", delete=False) as tmp_cache:
|
||||
cache_file = tmp_cache.name
|
||||
|
||||
# Start candump
|
||||
dump_out = tempfile.TemporaryFile(mode='w+')
|
||||
dump_proc = subprocess.Popen([candump, "-L", can_interface], stdout=dump_out, stderr=subprocess.PIPE, text=True)
|
||||
time.sleep(0.5) # Allow candump to initialize
|
||||
|
||||
daemon_proc = None
|
||||
try:
|
||||
# Start j1939acd with range starting at 80 (0x50).
|
||||
# We rely on -r because -a is reportedly not working properly.
|
||||
# Range: 80 to 128 (Decimal) -> 0x50 to 0x80.
|
||||
cmd_daemon = [j1939acd, "-r", "80-128", "-c", cache_file, nodename_hex, can_interface]
|
||||
print(f"DEBUG: Starting j1939acd: {' '.join(cmd_daemon)}")
|
||||
daemon_proc = subprocess.Popen(cmd_daemon, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
|
||||
# Wait for initial claim of 0x50 (which is 80 decimal)
|
||||
if not wait_for_can_msg(dump_out, f"18EEFF50#{nodename_payload}", timeout=3.0):
|
||||
dump_out.seek(0)
|
||||
print(f"DEBUG: Timeout waiting for start. Log:\n{dump_out.read()}")
|
||||
pytest.fail("j1939acd did not send Initial Address Claim (0x50) within timeout")
|
||||
|
||||
# Stop
|
||||
daemon_proc.send_signal(signal.SIGINT)
|
||||
daemon_proc.wait(timeout=2.0)
|
||||
|
||||
# Analyze shutdown
|
||||
if not wait_for_can_msg(dump_out, f"18EEFFFE#{nodename_payload}", timeout=3.0):
|
||||
dump_out.seek(0)
|
||||
print(f"DEBUG: Timeout waiting for stop. Log:\n{dump_out.read()}")
|
||||
pytest.fail("j1939acd did not send Address Release (0xFE) after SIGINT")
|
||||
|
||||
finally:
|
||||
if daemon_proc and daemon_proc.poll() is None: daemon_proc.kill()
|
||||
if dump_proc.poll() is None: dump_proc.terminate()
|
||||
dump_out.close()
|
||||
if os.path.exists(cache_file): os.remove(cache_file)
|
||||
|
||||
def test_j1939acd_conflict_defense(bin_path, can_interface):
|
||||
"""
|
||||
Test Scenario 2: Conflict Resolution - Defense (Winning).
|
||||
The DUT (Name: 11...) should defend address 0x50 against a lower priority challenger (Name: FF...).
|
||||
"""
|
||||
j1939acd = os.path.join(bin_path, "j1939acd")
|
||||
candump = os.path.join(bin_path, "candump")
|
||||
cansend = os.path.join(bin_path, "cansend")
|
||||
|
||||
if not os.path.exists(j1939acd): pytest.skip("j1939acd missing")
|
||||
|
||||
nodename_hex = "1122334455667788"
|
||||
dut_payload = "8877665544332211" # Little Endian of DUT Name, compact for candump -L
|
||||
|
||||
# Challenger: Name FFFFFFFFFFFFFFFF (Lower Priority)
|
||||
challenger_payload_send = "FFFFFFFFFFFFFFFF"
|
||||
challenger_payload_dump = "FFFFFFFFFFFFFFFF"
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix=".jacd", delete=False) as tmp_cache:
|
||||
cache_file = tmp_cache.name
|
||||
|
||||
dump_out = tempfile.TemporaryFile(mode='w+')
|
||||
dump_proc = subprocess.Popen([candump, "-L", can_interface], stdout=dump_out, stderr=subprocess.PIPE, text=True)
|
||||
time.sleep(0.5)
|
||||
|
||||
daemon_proc = None
|
||||
try:
|
||||
# 1. Start DUT (Claims 0x50)
|
||||
# Using range starting at 80 (decimal) for 0x50
|
||||
cmd_daemon = [j1939acd, "-r", "80-128", "-c", cache_file, nodename_hex, can_interface]
|
||||
daemon_proc = subprocess.Popen(cmd_daemon, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
|
||||
# Wait for DUT to actually claim the address BEFORE injecting conflict
|
||||
print("DEBUG: Waiting for DUT to claim 0x50...")
|
||||
if not wait_for_can_msg(dump_out, f"18EEFF50#{dut_payload}", timeout=3.0):
|
||||
dump_out.seek(0)
|
||||
print(f"DEBUG: DUT failed to claim 0x50 initially. Log:\n{dump_out.read()}")
|
||||
pytest.fail("DUT did not claim preferred address 0x50 on startup")
|
||||
|
||||
# 2. Inject Conflict (Lower Priority)
|
||||
# ID 18EEFF50 (Claim for 0x50), Payload F...
|
||||
conflict_cmd = [cansend, can_interface, f"18EEFF50#{challenger_payload_send}"]
|
||||
print(f"DEBUG: Injecting Conflict (Defense): {' '.join(conflict_cmd)}")
|
||||
subprocess.run(conflict_cmd, check=True)
|
||||
|
||||
# 3. Wait for Defense (DUT should re-send claim)
|
||||
time.sleep(1.0)
|
||||
|
||||
# Analyze
|
||||
dump_proc.terminate()
|
||||
dump_proc.wait()
|
||||
dump_out.seek(0)
|
||||
log = dump_out.read()
|
||||
|
||||
lines = log.splitlines()
|
||||
injection_index = -1
|
||||
defense_index = -1
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
# Check for Challenger Frame
|
||||
if "18EEFF50" in line and challenger_payload_dump in line:
|
||||
injection_index = i
|
||||
# Check for DUT Frame
|
||||
elif "18EEFF50" in line and dut_payload in line:
|
||||
# We want a DUT claim that appears AFTER the injection
|
||||
if injection_index != -1 and i > injection_index:
|
||||
defense_index = i
|
||||
|
||||
if injection_index == -1:
|
||||
print(f"DEBUG: Log content:\n{log}")
|
||||
pytest.fail("Injected conflict frame not found in candump")
|
||||
|
||||
if defense_index == -1:
|
||||
print(f"DEBUG: Log content:\n{log}")
|
||||
pytest.fail("DUT did not defend address (no Claim 0x50 sent after injection)")
|
||||
|
||||
finally:
|
||||
if daemon_proc: daemon_proc.send_signal(signal.SIGINT); daemon_proc.wait()
|
||||
if dump_proc.poll() is None: dump_proc.terminate()
|
||||
dump_out.close()
|
||||
if os.path.exists(cache_file): os.remove(cache_file)
|
||||
|
||||
def test_j1939acd_conflict_yielding(bin_path, can_interface):
|
||||
"""
|
||||
Test Scenario 3: Conflict Resolution - Yielding (Losing).
|
||||
The DUT (Name: 11...) should yield address 0x50 to a higher priority challenger (Name: 01...)
|
||||
and claim a new address (e.g., 0x51).
|
||||
"""
|
||||
j1939acd = os.path.join(bin_path, "j1939acd")
|
||||
candump = os.path.join(bin_path, "candump")
|
||||
cansend = os.path.join(bin_path, "cansend")
|
||||
|
||||
if not os.path.exists(j1939acd): pytest.skip("j1939acd missing")
|
||||
|
||||
nodename_hex = "1122334455667788"
|
||||
dut_payload = "8877665544332211"
|
||||
|
||||
# Challenger: Name 0000000000000001 (Higher Priority)
|
||||
challenger_payload_send = "0100000000000000"
|
||||
challenger_payload_dump = "0100000000000000"
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix=".jacd", delete=False) as tmp_cache:
|
||||
cache_file = tmp_cache.name
|
||||
|
||||
dump_out = tempfile.TemporaryFile(mode='w+')
|
||||
dump_proc = subprocess.Popen([candump, "-L", can_interface], stdout=dump_out, stderr=subprocess.PIPE, text=True)
|
||||
time.sleep(0.5)
|
||||
|
||||
daemon_proc = None
|
||||
try:
|
||||
# 1. Start DUT (Claims 0x50)
|
||||
# Using decimal 80 for 0x50. Range 80-96.
|
||||
cmd_daemon = [j1939acd, "-r", "80-96", "-c", cache_file, nodename_hex, can_interface]
|
||||
daemon_proc = subprocess.Popen(cmd_daemon, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
|
||||
# Wait for DUT to actually claim 0x50 BEFORE injecting
|
||||
print("DEBUG: Waiting for DUT to claim 0x50...")
|
||||
if not wait_for_can_msg(dump_out, f"18EEFF50#{dut_payload}", timeout=3.0):
|
||||
dump_out.seek(0)
|
||||
print(f"DEBUG: DUT failed to claim 0x50 initially. Log:\n{dump_out.read()}")
|
||||
pytest.fail("DUT did not claim preferred address 0x50 on startup")
|
||||
|
||||
# 2. Inject Conflict (Higher Priority)
|
||||
conflict_cmd = [cansend, can_interface, f"18EEFF50#{challenger_payload_send}"]
|
||||
print(f"DEBUG: Injecting Conflict (Yielding): {' '.join(conflict_cmd)}")
|
||||
subprocess.run(conflict_cmd, check=True)
|
||||
|
||||
time.sleep(1.0) # Wait for DUT response (New Claim)
|
||||
|
||||
# Analyze
|
||||
dump_proc.terminate()
|
||||
dump_proc.wait()
|
||||
dump_out.seek(0)
|
||||
log = dump_out.read()
|
||||
|
||||
lines = log.splitlines()
|
||||
injection_index = -1
|
||||
new_claim_index = -1
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
if "18EEFF50" in line and challenger_payload_dump in line:
|
||||
injection_index = i
|
||||
# Look for DUT claiming a NEW address (e.g. 51 hex / 81 dec)
|
||||
# Frame 18EEFF51
|
||||
elif "18EEFF51" in line and dut_payload in line:
|
||||
if injection_index != -1 and i > injection_index:
|
||||
new_claim_index = i
|
||||
|
||||
if injection_index == -1:
|
||||
print(f"DEBUG: Log content:\n{log}")
|
||||
pytest.fail("Injected conflict frame not found in candump")
|
||||
|
||||
if new_claim_index == -1:
|
||||
print(f"DEBUG: Log content:\n{log}")
|
||||
pytest.fail("DUT did not claim new address (0x51) after yielding 0x50")
|
||||
|
||||
finally:
|
||||
if daemon_proc: daemon_proc.send_signal(signal.SIGINT); daemon_proc.wait()
|
||||
if dump_proc.poll() is None: dump_proc.terminate()
|
||||
dump_out.close()
|
||||
if os.path.exists(cache_file): os.remove(cache_file)
|
||||
@@ -0,0 +1,127 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import os
|
||||
import pytest
|
||||
import signal
|
||||
import tempfile
|
||||
|
||||
# Note: bin_path and interface fixtures are provided implicitly by conftest.py
|
||||
|
||||
def test_j1939cat_usage(bin_path):
|
||||
"""
|
||||
Test usage/help output for j1939cat.
|
||||
|
||||
Manual Reproduction:
|
||||
1. Run: ./j1939cat -h
|
||||
2. Expect: Output containing "Usage: j1939cat".
|
||||
"""
|
||||
j1939cat = os.path.join(bin_path, "j1939cat")
|
||||
if not os.path.exists(j1939cat):
|
||||
pytest.skip("j1939cat binary not found")
|
||||
|
||||
# Some tools return 0 on -h, others non-zero. We just check output.
|
||||
result = subprocess.run(
|
||||
[j1939cat, "-h"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
assert "Usage: j1939cat" in result.stderr or "Usage: j1939cat" in result.stdout
|
||||
|
||||
def test_j1939cat_transfer_p2p(bin_path, can_interface):
|
||||
"""
|
||||
Test Point-to-Point data transfer using j1939cat.
|
||||
|
||||
Description:
|
||||
1. Start a receiver instance of j1939cat listening on SA 0x90.
|
||||
2. Start a sender instance sending data from SA 0x80 to SA 0x90 (PGN 0x12300).
|
||||
3. Verify that the receiver prints the sent data.
|
||||
|
||||
Manual Reproduction:
|
||||
1. Receiver: ./j1939cat vcan0:0x90 -r
|
||||
2. Sender: echo "Hello J1939" | ./j1939cat vcan0:0x80 :0x90,0x12300
|
||||
3. Check receiver output for "Hello J1939".
|
||||
"""
|
||||
j1939cat = os.path.join(bin_path, "j1939cat")
|
||||
if not os.path.exists(j1939cat):
|
||||
pytest.skip("j1939cat binary not found")
|
||||
|
||||
# Define Addresses (Hex strings as per example usage)
|
||||
# Receiver Address (SA=0x90)
|
||||
recv_addr = f"{can_interface}:0x90"
|
||||
|
||||
# Sender Address (SA=0x80)
|
||||
send_addr_from = f"{can_interface}:0x80"
|
||||
# Destination (SA=0x90, PGN=0x12300)
|
||||
# Note: The example usage ":0x90,0x12300" implies destination address 0x90 and PGN 0x12300.
|
||||
# The leading colon implies reusing the interface or defaults.
|
||||
send_addr_to = ":0x90,0x12300"
|
||||
|
||||
payload = "Hello J1939 World"
|
||||
|
||||
# Create a temp file for input
|
||||
with tempfile.NamedTemporaryFile(mode='w+', delete=False) as tmp_in:
|
||||
tmp_in.write(payload)
|
||||
tmp_in_path = tmp_in.name
|
||||
|
||||
# 1. Start Receiver
|
||||
# Command: j1939cat <IFACE>:0x90 -r
|
||||
cmd_recv = [j1939cat, recv_addr, "-r"]
|
||||
print(f"DEBUG: Receiver cmd: {' '.join(cmd_recv)}")
|
||||
|
||||
recv_proc = subprocess.Popen(
|
||||
cmd_recv,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
try:
|
||||
# Allow receiver to bind
|
||||
time.sleep(1.0)
|
||||
|
||||
if recv_proc.poll() is not None:
|
||||
_, err = recv_proc.communicate()
|
||||
pytest.fail(f"Receiver failed to start. Stderr: {err}")
|
||||
|
||||
# 2. Start Sender
|
||||
# Command: j1939cat -i <file> <IFACE>:0x80 :0x90,0x12300
|
||||
cmd_send = [j1939cat, "-i", tmp_in_path, send_addr_from, send_addr_to]
|
||||
print(f"DEBUG: Sender cmd: {' '.join(cmd_send)}")
|
||||
|
||||
send_proc = subprocess.run(
|
||||
cmd_send,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
if send_proc.returncode != 0:
|
||||
pytest.fail(f"Sender failed with exit code {send_proc.returncode}. Stderr: {send_proc.stderr}")
|
||||
|
||||
# 3. Verify Reception
|
||||
# Wait a brief moment for processing
|
||||
time.sleep(1.0)
|
||||
|
||||
# Terminate receiver to read remaining output
|
||||
recv_proc.terminate()
|
||||
try:
|
||||
recv_proc.wait(timeout=1.0)
|
||||
except subprocess.TimeoutExpired:
|
||||
recv_proc.kill()
|
||||
|
||||
stdout, stderr = recv_proc.communicate()
|
||||
|
||||
print(f"DEBUG: Receiver Output:\n{stdout}")
|
||||
print(f"DEBUG: Receiver Stderr:\n{stderr}")
|
||||
|
||||
assert payload in stdout, "Receiver did not output the sent payload"
|
||||
|
||||
finally:
|
||||
# Cleanup
|
||||
if recv_proc.poll() is None:
|
||||
recv_proc.kill()
|
||||
if os.path.exists(tmp_in_path):
|
||||
os.remove(tmp_in_path)
|
||||
@@ -0,0 +1,136 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import os
|
||||
import pytest
|
||||
import signal
|
||||
import pty
|
||||
|
||||
# Note: bin_path and interface fixtures are provided implicitly by conftest.py
|
||||
|
||||
def test_j1939spy_usage(bin_path):
|
||||
"""
|
||||
Test usage/help output for j1939spy.
|
||||
|
||||
Manual Reproduction:
|
||||
1. Run: ./j1939spy -h
|
||||
2. Expect: Output containing "Usage: j1939spy".
|
||||
"""
|
||||
j1939spy = os.path.join(bin_path, "j1939spy")
|
||||
if not os.path.exists(j1939spy):
|
||||
pytest.skip("j1939spy binary not found")
|
||||
|
||||
result = subprocess.run(
|
||||
[j1939spy, "-h"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
assert "Usage: j1939spy" in result.stderr or "Usage: j1939spy" in result.stdout
|
||||
|
||||
def test_j1939spy_sniffing(bin_path, can_interface):
|
||||
"""
|
||||
Test j1939spy sniffing functionality.
|
||||
|
||||
Description:
|
||||
1. Start j1939spy in promiscuous mode (-P) on the interface using PTY to avoid buffering.
|
||||
2. Generate J1939 traffic using j1939sr (Source 0x90 -> Dest 0x80).
|
||||
3. Verify that j1939spy captures and displays the traffic.
|
||||
|
||||
Manual Reproduction:
|
||||
1. Spy: ./j1939spy -P vcan0
|
||||
2. Transfer: echo "test" | ./j1939sr vcan0:90 vcan0:80
|
||||
3. Check j1939spy output for "test" (hex: 74657374).
|
||||
"""
|
||||
j1939spy = os.path.join(bin_path, "j1939spy")
|
||||
j1939sr = os.path.join(bin_path, "j1939sr")
|
||||
|
||||
for tool in [j1939spy, j1939sr]:
|
||||
if not os.path.exists(tool):
|
||||
pytest.skip(f"{tool} not found")
|
||||
|
||||
# 1. Start j1939spy
|
||||
# Command: ./j1939spy -P vcan0
|
||||
cmd_spy = [j1939spy, "-P", can_interface]
|
||||
print(f"DEBUG: Starting spy: {' '.join(cmd_spy)}")
|
||||
|
||||
# Use PTY to force line buffering (simulates terminal behavior)
|
||||
master_fd, slave_fd = pty.openpty()
|
||||
|
||||
spy_proc = subprocess.Popen(
|
||||
cmd_spy,
|
||||
stdout=slave_fd,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
os.close(slave_fd) # Close slave in parent
|
||||
|
||||
try:
|
||||
# Allow spy to start
|
||||
time.sleep(0.5)
|
||||
if spy_proc.poll() is not None:
|
||||
_, err = spy_proc.communicate()
|
||||
pytest.fail(f"j1939spy failed to start. Stderr: {err}")
|
||||
|
||||
# 2. Generate Traffic
|
||||
# Using j1939sr from SA 0x90 to SA 0x80
|
||||
src_addr = f"{can_interface}:90"
|
||||
dst_addr = f"{can_interface}:80"
|
||||
payload_str = "test"
|
||||
# Hex for "test" is 74 65 73 74. j1939spy usually prints hex.
|
||||
|
||||
cmd_sr = [j1939sr, src_addr, dst_addr]
|
||||
print(f"DEBUG: Generating traffic: echo '{payload_str}' | {' '.join(cmd_sr)}")
|
||||
|
||||
sr_proc = subprocess.run(
|
||||
cmd_sr,
|
||||
input=payload_str + "\n",
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
if sr_proc.returncode != 0:
|
||||
print(f"DEBUG: j1939sr stderr: {sr_proc.stderr}")
|
||||
|
||||
# 3. Verify capture
|
||||
# Read from PTY
|
||||
time.sleep(1.0)
|
||||
|
||||
output = ""
|
||||
try:
|
||||
# Read non-blocking or simple read since we know data should be there
|
||||
# Using os.read on master_fd
|
||||
while True:
|
||||
# Basic non-blocking read approach or just one big read if buffer has data
|
||||
# For test stability, just read a chunk.
|
||||
chunk = os.read(master_fd, 4096).decode('utf-8', errors='replace')
|
||||
if not chunk:
|
||||
break
|
||||
output += chunk
|
||||
if "74657374" in output:
|
||||
break
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
print(f"DEBUG: j1939spy output:\n{output}")
|
||||
|
||||
# Expected output format from user description:
|
||||
# vcan0:90,00000 80 !6 [5] 74657374 0a
|
||||
# We look for the payload hex "74657374" (test)
|
||||
expected_hex = "74657374"
|
||||
|
||||
assert expected_hex in output, f"j1939spy did not capture the payload hex '{expected_hex}'"
|
||||
|
||||
finally:
|
||||
if spy_proc.poll() is None:
|
||||
spy_proc.terminate()
|
||||
try:
|
||||
spy_proc.wait(timeout=1.0)
|
||||
except subprocess.TimeoutExpired:
|
||||
spy_proc.kill()
|
||||
|
||||
if master_fd:
|
||||
os.close(master_fd)
|
||||
@@ -0,0 +1,130 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import os
|
||||
import pytest
|
||||
import signal
|
||||
|
||||
# Note: bin_path and interface fixtures are provided implicitly by conftest.py
|
||||
|
||||
def test_j1939sr_usage(bin_path):
|
||||
"""
|
||||
Test usage/help output for j1939sr.
|
||||
|
||||
Manual Reproduction:
|
||||
1. Run: ./j1939sr -h
|
||||
2. Expect: Output containing "Usage: j1939sr".
|
||||
"""
|
||||
j1939sr = os.path.join(bin_path, "j1939sr")
|
||||
if not os.path.exists(j1939sr):
|
||||
pytest.skip("j1939sr binary not found")
|
||||
|
||||
# j1939sr -h typically returns error exit code because -h is invalid option
|
||||
result = subprocess.run(
|
||||
[j1939sr, "-h"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
assert "Usage: j1939sr" in result.stderr or "Usage: j1939sr" in result.stdout
|
||||
|
||||
def test_j1939sr_transfer(bin_path, can_interface):
|
||||
"""
|
||||
Test basic data transfer using j1939sr.
|
||||
|
||||
Description:
|
||||
1. Start a receiver (j1939sr) listening on a specific SA (Source Address).
|
||||
2. Start a sender (j1939sr) sending from another SA to the receiver's SA.
|
||||
3. Verify data integrity.
|
||||
|
||||
Manual Reproduction:
|
||||
1. Receiver: ./j1939sr vcan0:80
|
||||
2. Sender: echo "TestPayload" | ./j1939sr vcan0:90 vcan0:80
|
||||
3. Check output of Receiver.
|
||||
"""
|
||||
j1939sr = os.path.join(bin_path, "j1939sr")
|
||||
if not os.path.exists(j1939sr):
|
||||
pytest.skip("j1939sr binary not found")
|
||||
|
||||
# Addresses (Hex without 0x prefix based on user feedback)
|
||||
recv_sa_hex = "80"
|
||||
send_sa_hex = "90"
|
||||
|
||||
# Receiver Command: ./j1939sr vcan0:80
|
||||
# Listens on vcan0, address 0x80
|
||||
recv_arg = f"{can_interface}:{recv_sa_hex}"
|
||||
cmd_recv = [j1939sr, recv_arg]
|
||||
print(f"DEBUG: Receiver cmd: {' '.join(cmd_recv)}")
|
||||
|
||||
# We set stdin=subprocess.PIPE to prevent the tool from detecting EOF immediately
|
||||
# and exiting. This simulates a running terminal session.
|
||||
recv_proc = subprocess.Popen(
|
||||
cmd_recv,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
try:
|
||||
# Allow receiver to bind socket
|
||||
time.sleep(1.0)
|
||||
|
||||
# Check if it is still running. A listener SHOULD still be running.
|
||||
if recv_proc.poll() is not None:
|
||||
stdout, stderr = recv_proc.communicate()
|
||||
print(f"DEBUG: Receiver Stdout:\n{stdout}")
|
||||
# If it exited with 0, it might mean it finished 'nothing' or errored gracefully.
|
||||
# For a server, early exit is a failure.
|
||||
pytest.fail(f"Receiver exited prematurely (rc={recv_proc.returncode}). Stderr: {stderr}")
|
||||
|
||||
# Sender Command: ./j1939sr vcan0:90 vcan0:80
|
||||
# SOURCE: vcan0:90 (Sender Address)
|
||||
# DEST: vcan0:80 (Receiver Address)
|
||||
send_arg_src = f"{can_interface}:{send_sa_hex}"
|
||||
send_arg_dst = f"{can_interface}:{recv_sa_hex}"
|
||||
|
||||
cmd_send = [j1939sr, send_arg_src, send_arg_dst]
|
||||
print(f"DEBUG: Sender cmd: {' '.join(cmd_send)}")
|
||||
|
||||
payload = "J1939TestMessage\n" # newline is important for line buffering tools
|
||||
|
||||
# Send data via stdin to sender process
|
||||
send_proc = subprocess.run(
|
||||
cmd_send,
|
||||
input=payload,
|
||||
stdout=subprocess.PIPE, # Capture to avoid clutter
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
if send_proc.returncode != 0:
|
||||
pytest.fail(f"Sender failed with exit code {send_proc.returncode}. Stderr: {send_proc.stderr}")
|
||||
|
||||
# Verify reception
|
||||
# We give it a moment to process
|
||||
time.sleep(0.5)
|
||||
|
||||
# Terminate receiver to finish reading
|
||||
if recv_proc.poll() is None:
|
||||
recv_proc.terminate()
|
||||
try:
|
||||
recv_proc.wait(timeout=1.0)
|
||||
except subprocess.TimeoutExpired:
|
||||
recv_proc.kill()
|
||||
|
||||
stdout, stderr = recv_proc.communicate()
|
||||
|
||||
print(f"DEBUG: Receiver Output:\n{stdout}")
|
||||
|
||||
if stderr:
|
||||
print(f"DEBUG: Receiver Stderr:\n{stderr}")
|
||||
|
||||
# Check if payload exists in output
|
||||
assert payload.strip() in stdout, "Receiver did not output the sent payload"
|
||||
|
||||
finally:
|
||||
if recv_proc.poll() is None:
|
||||
recv_proc.kill()
|
||||
@@ -0,0 +1,143 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import os
|
||||
import pytest
|
||||
import signal
|
||||
import pty
|
||||
import select # Import select here or at top level
|
||||
|
||||
# Note: bin_path and interface fixtures are provided implicitly by conftest.py
|
||||
|
||||
def test_testj1939_usage(bin_path):
|
||||
"""
|
||||
Test usage/help output for testj1939.
|
||||
|
||||
Manual Reproduction:
|
||||
1. Run: ./testj1939 -h
|
||||
2. Expect: Output containing "Usage: testj1939".
|
||||
"""
|
||||
testj1939 = os.path.join(bin_path, "testj1939")
|
||||
if not os.path.exists(testj1939):
|
||||
pytest.skip("testj1939 binary not found")
|
||||
|
||||
result = subprocess.run(
|
||||
[testj1939, "-h"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
assert "Usage: testj1939" in result.stderr or "Usage: testj1939" in result.stdout
|
||||
|
||||
def test_testj1939_send(bin_path, can_interface):
|
||||
"""
|
||||
Test sending functionality of testj1939.
|
||||
|
||||
Description:
|
||||
1. Start j1939spy (as receiver) in promiscuous mode on the interface using PTY.
|
||||
2. Run testj1939 to send 10 bytes of dummy data (-s 10) from Source 0x90 to Dest 0x80.
|
||||
3. Verify that j1939spy captures the payload.
|
||||
|
||||
Manual Reproduction:
|
||||
1. Receiver: ./j1939spy -P vcan0
|
||||
2. Sender: ./testj1939 vcan0:90 vcan0:80 -s 10
|
||||
3. Check j1939spy output for payload "01234567 89abcdef" (standard dummy pattern of testj1939).
|
||||
"""
|
||||
testj1939 = os.path.join(bin_path, "testj1939")
|
||||
j1939spy = os.path.join(bin_path, "j1939spy")
|
||||
|
||||
for tool in [testj1939, j1939spy]:
|
||||
if not os.path.exists(tool):
|
||||
pytest.skip(f"{tool} not found")
|
||||
|
||||
# 1. Start Receiver (j1939spy)
|
||||
# Command: ./j1939spy -P vcan0
|
||||
cmd_spy = [j1939spy, "-P", can_interface]
|
||||
print(f"DEBUG: Starting spy: {' '.join(cmd_spy)}")
|
||||
|
||||
# Use PTY to ensure line buffering for the spy tool
|
||||
master_fd, slave_fd = pty.openpty()
|
||||
|
||||
spy_proc = subprocess.Popen(
|
||||
cmd_spy,
|
||||
stdout=slave_fd,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
os.close(slave_fd) # Close slave in parent
|
||||
|
||||
try:
|
||||
# Allow spy to start
|
||||
time.sleep(0.5)
|
||||
if spy_proc.poll() is not None:
|
||||
_, err = spy_proc.communicate()
|
||||
pytest.fail(f"j1939spy failed to start. Stderr: {err}")
|
||||
|
||||
# 2. Run Sender (testj1939)
|
||||
# Command: ./testj1939 vcan0:90 vcan0:80 -s 10
|
||||
# Source SA: 0x90, Dest SA: 0x80, Size: 10 bytes
|
||||
# testj1939 generates a dummy pattern. Based on logs: "01234567 89abcdef"
|
||||
src_arg = f"{can_interface}:90"
|
||||
dst_arg = f"{can_interface}:80"
|
||||
|
||||
cmd_send = [testj1939, src_arg, dst_arg, "-s", "10"]
|
||||
print(f"DEBUG: Sending data: {' '.join(cmd_send)}")
|
||||
|
||||
send_proc = subprocess.run(
|
||||
cmd_send,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
if send_proc.returncode != 0:
|
||||
print(f"DEBUG: testj1939 stderr: {send_proc.stderr}")
|
||||
# Note: Depending on kernel and socket state, send might fail or succeed.
|
||||
# Usually sendto() succeeds on CAN even without receiver.
|
||||
|
||||
# 3. Verify capture
|
||||
# Read from PTY
|
||||
time.sleep(1.0)
|
||||
|
||||
output = ""
|
||||
found = False
|
||||
|
||||
# Expected Payload: Based on error log "01234567 89abcdef"
|
||||
# We search for "01234567" which is the start of the payload
|
||||
expected_pattern_hex = "01234567"
|
||||
|
||||
try:
|
||||
start_read = time.time()
|
||||
# Try reading for up to 3 seconds
|
||||
while time.time() - start_read < 3.0:
|
||||
# Use select to check if data is available to read
|
||||
r, _, _ = select.select([master_fd], [], [], 0.5)
|
||||
if master_fd in r:
|
||||
chunk = os.read(master_fd, 4096).decode('utf-8', errors='replace')
|
||||
if chunk:
|
||||
output += chunk
|
||||
if expected_pattern_hex in output:
|
||||
found = True
|
||||
break
|
||||
# Check if process died
|
||||
if spy_proc.poll() is not None:
|
||||
break
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
print(f"DEBUG: j1939spy output:\n{output}")
|
||||
|
||||
assert found, f"j1939spy did not capture the expected payload pattern '{expected_pattern_hex}'. Received output:\n{output}"
|
||||
|
||||
finally:
|
||||
if spy_proc.poll() is None:
|
||||
spy_proc.terminate()
|
||||
try:
|
||||
spy_proc.wait(timeout=1.0)
|
||||
except subprocess.TimeoutExpired:
|
||||
spy_proc.kill()
|
||||
|
||||
if master_fd:
|
||||
os.close(master_fd)
|
||||
Reference in New Issue
Block a user