linux-can / can-utils

Linux-CAN / SocketCAN user space applications
2.39k stars 711 forks source link

CAN J19939 Address Claim (Protocol error) #183

Closed josemic closed 4 years ago

josemic commented 4 years ago

I am doing an address claim as described in j1939.rst

However I get an protocol error. The TTCN code looks as following:

testcase tc_can_f_j1939_address_claim() runs on MTC {
  var PTC v_ptc_j1939AddressClaimInitiator := PTC.create("PTC1_ptc_j1939AddressClaimInitiator") alive

  f_addSyncSlaveSet(v_ptc_j1939AddressClaimInitiator, v_PTCSet)

  connect(mtc:pt_sync, v_ptc_j1939AddressClaimInitiator:pt_sync)

  const octetstring c_j1939_name_source  := '1122334455667788'O

  v_ptc_j1939AddressClaimInitiator.start(f_j1939_address_claim(
    e_testbody1, 
    c_j1939_name_source))

  var e_Phase v_phase

  for(v_phase := c_firstPhase; v_phase < e_testcase_complete;v_phase := f_incMTCPhase(v_phase)) {
    f_startPhase(v_phase)
    log("MTC: ", v_phase)
    f_awaitEndPhase(v_phase)
  }

  all component.done;
  log("MTC done")

  disconnect(mtc:pt_sync, v_ptc_j1939AddressClaimInitiator:pt_sync)

  all component.kill;      
}
function f_j1939_address_claim(in e_Phase p_phase, 
  in   J1939_NAME p_j1939_name_source) runs on PTC {
  map(self:pt_socketCAN, system:pt_socketCAN)
  var SocketCAN_socketid v_socket_id
  var SocketCAN_ifr v_ifr

  alt_awaitPhaseStartReq(e_open_socket)
  var SocketCAN_open_j1939_result res
  res := f_open_j1939()
  v_socket_id := res.socket_id
  v_ifr := res.ifr
  var SocketCAN_bind_result v_bind_result
  v_bind_result := f_bind(v_socket_id, 
    v_ifr.if_index, 
    p_j1939_name_source,
    J1939_NO_PGN,
    J1939_IDLE_ADDR)
  f_sendPhaseEndInd()

  var SocketCAN_setsockopt_result            v_setsockopt_result

  v_setsockopt_result := f_setsockopt(v_socket_id, {j1939_broadcast:=Enable})

  // configure filters:
  const J1939_filter c_j1939_filter0 := {
    name_filter:= omit,
    addr_filter:= omit,
    pgn_filter :={
      pgn :=J1939_PGN_ADDRESS_CLAIMED,
      pgn_mask := J1939_PGN_PDU1_MAX}};

  const J1939_filter c_j1939_filter1 := {
    name_filter:= omit,
    addr_filter:= omit,
    pgn_filter :={
      pgn      := J1939_PGN_REQUEST, //J1939_PGN_ADDRESS_REQUEST
      pgn_mask := J1939_PGN_PDU1_MAX}};

  const J1939_filter c_j1939_filter2 := {
    name_filter:= omit,
    addr_filter:= omit,
    pgn_filter :={
      pgn      := J1939_PGN_ADDRESS_COMMANDED,
      pgn_mask := J1939_PGN_MAX}};

  const SocketCAN_setsockopt_commandu c_commandu_activate_j1939_filters := 
  {j1939_filter:={c_j1939_filter0, c_j1939_filter1, c_j1939_filter2}}

  v_setsockopt_result := f_setsockopt(v_socket_id, c_commandu_activate_j1939_filters)

  alt_awaitPhaseStartReq(p_phase)

  var SocketCAN_j1939_send_data_to_result send_data_to_result
  var J1939_hdr v_j1939_destination := {
    name := omit, 
    pgn := J1939_PGN_ADDRESS_CLAIMED, 
    addr:= J1939_NO_ADDR}

  send_data_to_result := f_j1939_send_data_to(
    v_socket_id, 
    v_ifr.if_index,
    v_j1939_destination,
    p_j1939_name_source /*p_pdu_send*/)

  f_sendPhaseEndInd()

  alt_awaitPhaseStartReq(e_close_socket)
  f_close_socket (v_socket_id)
  unmap(self:pt_socketCAN, system:pt_socketCAN)
  setverdict(pass)
  f_sendPhaseEndInd()
}
mo$ ttcn3_start SocketCAN J1939.cfg SocketCAN_J1939_test.tc_can_f_j1939_address_claim
ttcn3_start: Starting the test suite
spawn /home/michael/Programming/TTCN/titan.core/Install/bin/mctr_cli J1939.cfg

*************************************************************************
* TTCN-3 Test Executor - Main Controller 2                              *
* Version: CRL 113 200/6 R6A                                            *
* Copyright (c) 2000-2019 Ericsson Telecom AB                           *
* All rights reserved. This program and the accompanying materials      *
* are made available under the terms of the Eclipse Public License v2.0 *
* which accompanies this distribution, and is available at              *
* https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html            *
*************************************************************************

Using configuration file: J1939.cfg
MC@michael-ThinkPad-T430: Unix server socket created successfully.
MC@michael-ThinkPad-T430: Listening on TCP port 41013.
MC2> michael-ThinkPad-T430 is the default
spawn ./SocketCAN michael-ThinkPad-T430 41013
TTCN-3 Host Controller (parallel mode), version CRL 113 200/6 R6A
MC@michael-ThinkPad-T430: New HC connected from localhost [127.0.0.1]. michael-ThinkPad-T430: Linux 5.4.0-050400rc8-lowlatency on x86_64.
cmtc
MC@michael-ThinkPad-T430: Downloading configuration file to all HCs.
MC@michael-ThinkPad-T430: Configuration file was processed on all HCs.
MC@michael-ThinkPad-T430: Creating MTC on host localhost.
MC@michael-ThinkPad-T430: MTC is created.
MC2> smtc SocketCAN_J1939_test.tc_can_f_j1939_address_claim
MC2> MTC@michael-ThinkPad-T430: Test case tc_can_f_j1939_address_claim started.
PTC1_ptc_j1939AddressClaimInitiator(3)@michael-ThinkPad-T430: Dynamic test case error: SocketCAN j1939 sendto() error while trying to send J1939 message of 8 bytes  to addr ff, name 0, pgn ee00 (Protocol error)

The log file is attached. log_merged.txt

olerem commented 4 years ago

I assume your first call should be:

  var SocketCAN_j1939_send_data_to_result send_data_to_result
  var J1939_hdr v_j1939_destination := {
    name := omit, 
    pgn := J1939_PGN_REQUEST,
    addr:= J1939_NO_ADDR}

  send_data_to_result := f_j1939_send_data_to(
    v_socket_id, 
    v_ifr.if_index,
    v_j1939_destination,
    { 0, 0xee, 0, })
olerem commented 4 years ago

Did you get some kernel log on this request? It would say some thing starting with "tx address claim wit ...", it should be a hint, what exactly went wrong.

josemic commented 4 years ago

Thanks for the hint. The kernel log shows: Dec 11 18:42:56 michael-ThinkPad-T430 kernel: [ 2221.739290] vcan0: tx address claim with different name

When now turning the order of bytes p_j1939_name_source in the sendto function around to '8877665544332211'O compared to how it was used in the bind command '1122334455667788'O. It works and candump shows: vcan0 18EEFFFE [8] 88 77 66 55 44 33 22 11

Which is according to my understanding the Cannot Claim Source Address command.

marckleinebudde commented 4 years ago

The type of your c_j1939_name_source source is probably wrong. It's not an octet string it's the name in little endian.

josemic commented 4 years ago

Not sure if I understand you right. For address claim, the name is used in the bind command as big endian and in the sentto command as little endian. However the sentto command is usually used to then the PDU, which is an octetstring. That is why I have in later versions changed the sentto as following:

  var J1939_NAME v_j1939_name_source_reverse := 
  p_j1939_name_source[7]& p_j1939_name_source[6]& p_j1939_name_source[5]&
  p_j1939_name_source[4]& p_j1939_name_source[3]& p_j1939_name_source[2]& 
  p_j1939_name_source[1]& p_j1939_name_source[0]

  send_data_to_result := f_j1939_send_data_to(
    v_socket_id, 
    v_ifr.if_index,
    v_j1939_destination,
    v_j1939_name_source_reverse /*p_pdu_send*/) 
marckleinebudde commented 4 years ago

Not sure if I understand you right. For address claim, the name is used in the bind() command as big endian

No, the name in the bind() is host native endianess. It's addressing information you pass to the kernel. So it's native.

and in the sentto() command as little endian.

It's data you send over the network. Look into the j1939 specs for address claiming how the message should look like.

However the sentto() command is usually used to then the PDU, which is an octetstring. That is why I have in later versions changed the sentto as following:

On the lower level it's an octet string you send over the net, but how do you encode a 8 byte value into a series of 8 single bytes? It's defined by the j1939 standard to be little endian for the address claiming message.

Regarding your example code, what you call reverse is usually called little endian. I'm sure that TTCN-3 has a proper way to do this.

josemic commented 4 years ago

No the name in the bind() is host native endianess. It's addressing information you pass to the kernel. So it's native.

The TTCN-3 has message encoding in little and big endian (encoding (msb/lsb)), however this applies only to messages. If there is a native encoding, we have to do it in the test port. This is the way the test port for bind looks like: Note sure what you mean. This is the way the bind code looks like.

                addr.can_ifindex = bindu.j1939().if__index();
                if (bindu.j1939().j1939__source().addr().is_present()) {
                    const OPTIONAL<J1939::J1939__ADDR> &addr_ =
                            bindu.j1939().j1939__source().addr();
                    addr.can_addr.j1939.addr = oct2int(addr_);
                }
                if (bindu.j1939().j1939__source().name().is_present()) {
                    const OPTIONAL<J1939::J1939__NAME> &name =
                            bindu.j1939().j1939__source().name();
                    addr.can_addr.j1939.name = (__u64) oct2int(name).get_long_long_val();
                }
                if (bindu.j1939().j1939__source().pgn().is_present()) {
                    const OPTIONAL<J1939::J1939__PGN>& pgn =
                    bindu.j1939().j1939__source().pgn();
                    addr.can_addr.j1939.pgn = oct2int(pgn);
                }

                log("SocketCAN: Binding socket J1939 to addr %x, name %llx, pgn %x",
                        addr.can_addr.j1939.addr, addr.can_addr.j1939.name, addr.can_addr.j1939.pgn);

One question that comes to my mind is that test suites have to be rewritten if the endianess of the kernel changes (intel vs. arm processor used)?

and in the sentto() command as little endian.

It's data you send over the network. Look into the j1939 specs for address claiming how the message should look like.

I do not have access to the j1939 specification. I have only the book from Wilfied Voss "A comprehensible Guide to J1939".

Here is the way the sendto looks like in the test port:


    SocketCAN__Types::SocketCAN__j1939__send__data__to__result result;
    int sock;
    int cn = send_par.id();

    if ((cn < sock_list_length)) {
        struct sockaddr_can destaddr;
        long nrOfBytesSent, nrOfBytestoSend, nrOfBytesRemaining;
        sock = sock_list[cn].fd;

        destaddr.can_ifindex = send_par.if__index();
        destaddr.can_family = AF_CAN;
        destaddr.can_addr.j1939.addr = J1939_NO_ADDR;
        destaddr.can_addr.j1939.name = J1939_NO_NAME;
        destaddr.can_addr.j1939.pgn = J1939_NO_PGN;

        if (send_par.j1939__destination().addr().is_present()) {
            const OPTIONAL<J1939::J1939__ADDR> &addr =
                    send_par.j1939__destination().addr();
            destaddr.can_addr.j1939.addr = oct2int(addr);
        }
        if (send_par.j1939__destination().name().is_present()) {
            const OPTIONAL<J1939::J1939__NAME> &name =
                    send_par.j1939__destination().name();
            //destaddr.can_addr.j1939.name = oct2int(name);
            destaddr.can_addr.j1939.name = (__u64) oct2int(name).get_long_long_val();
        }
        if (send_par.j1939__destination().pgn().is_present()) {
            const OPTIONAL<J1939::J1939__PGN> &pgn =
                    send_par.j1939__destination().pgn();
            destaddr.can_addr.j1939.pgn = oct2int(pgn);
        }

        log("SocketCAN: Sending J1939 message to addr %x, name %llx, pgn %x",
                destaddr.can_addr.j1939.addr, destaddr.can_addr.j1939.name,
                destaddr.can_addr.j1939.pgn);
        //logOctet("containing data: ", send_par.pdu());

        nrOfBytestoSend = send_par.pdu().lengthof();
        nrOfBytesRemaining = nrOfBytestoSend;
        do {
            nrOfBytesSent = sendto(sock, (const void*)((const unsigned char*)send_par.pdu()+nrOfBytestoSend-nrOfBytesRemaining),
                    nrOfBytesRemaining, 0, (struct sockaddr*) &destaddr, sizeof(destaddr));
            nrOfBytesRemaining = nrOfBytesRemaining - nrOfBytesSent;
        } while ((nrOfBytesSent >= 0) and (nrOfBytesRemaining != 0));

        log(
                "Sent J1939 message with send of size %d to addr %x, name %llx, pgn %x",
                nrOfBytesSent, destaddr.can_addr.j1939.addr,
                destaddr.can_addr.j1939.name, destaddr.can_addr.j1939.pgn);

Do you mean that I have to turn around all bytes from the octetstring before before sending in the test port? (const void*)((const unsigned char*)send_par.pdu()+nrOfBytestoSend-nrOfBytesRemaining)

So if I understand you right I do not have to reverse in TTCN-3 code, but I have always to send the pdu in little endian in TTCN-3 test port?

marckleinebudde commented 4 years ago

addr.can_addr.j1939.name = (__u64) oct2int(name).get_long_long_val();

I'm not familiar with TTCN-3, but I assume this produces a native uint64_t. So the tests don't have to be re-written for BE system. Although we haven't tested the j1939 stack on BE yet. However this looks wrong. What's the data type of name here? Is it possible to define a uint64_t equivalent in TTCN-3?

BTW: Your write loop misses error handling.

Do you mean that I have to turn around all bytes from the octetstring before before sending in the test port?

No.

So if I understand you right I do not have to reverse in TTCN-3 code, but I have always to send the pdu in little endian in TTCN-3 test port?

No.

The sendto() takes an octet string. According to the standard the name is send as a little endian encoded over the network.

See Table 3. Structure of the Name. Compare to this graphical representation of the j1939 name, where the MSB is on the left and the LSB on the right.

This means first byte of the data in the address claim message is the LSB of the identity number, which is the LSB of the name.

When you write a uint64_t in C (uint64_t foo = 0x1122334455667788;) the LSB is 0x88 and the MSB is 0x11.

When you send an octet string, e.g. '1122334455667788'O byte wise it's: 11 22 33 44 55 66 77 88. An octet string is just an arbitrary number of bytes (octets) grouped together. If you are on a little endian system and have a uint64_t foo=0x1122334455667788 and send it byte wise it's: 88 77 66 55 44 33 22 11 contrary the same uint64_t on a big endian system send byte wise: 11 22 33 44 55 66 77 88.

This means your data type for name is wrong. It must be something similar to an uint64_t not a group of 8 single bytes, which is called octet string in TTCN-3. When generating the address claim message you have to convert the native name into LE representation and treat this as an octet string.

josemic commented 4 years ago

BTW: Your write loop misses error handling. The error handling for sentto with the return value nrOfBytesSent < 0 is written below the code shown (not shown here).

addr.can_addr.j1939.name = (__u64) oct2int(name).get_long_long_val(); However this looks wrong. What's the data type of name here?

name is of data type __u64 addr is defined as following: struct sockaddr_can addr = { }; where sockaddr_can is defined in can.h:

struct sockaddr_can {
    __kernel_sa_family_t can_family;
    int         can_ifindex;
    union {
        /* transport protocol class address information (e.g. ISOTP) */
        struct { canid_t rx_id, tx_id; } tp;
        /* J1939 address information */
        struct {
            /* 8 byte name when using dynamic addressing */
            __u64 name;
            /* pgn:
             * 8 bit: PS in PDU2 case, else 0
             * 8 bit: PF
             * 1 bit: DP
             * 1 bit: reserved
             */
            __u32 pgn;
            /* 1 byte address */
            __u8 addr;
        } j1939;
        /* reserved for future CAN protocols address information */
    } can_addr;
};

I made a test: In Terminal 1: Sending with TTCN (were the with sendto the reveresed c_j1939_name_source is send: const J1939_NAME c_j1939_name_source := '1122334455667788'O

In Terminal 2:

./candump -t d vcan0
 (000.000000)  vcan0  18EEFF80   [8]  88 77 66 55 44 33 22 11

and with jacd: In Terminal 3: ./jacd -r 100,80-120 -c /tmp/1122334455667780.jacd 1122334455667788 vcan0

In Termianl 4:

./candump -t d vcan0
 (000.000000)  vcan0  18EAFFFE   [3]  00 EE 00
 (001.250174)  vcan0  18EEFF50   [8]  88 77 66 55 44 33 22 11

Thus the bind looks for intel PCs ok (I do not completly understand why), however the sendto needs to convert name of type J1939_NAMEto little endian before sending as octetstring. This should be handled seamlessly by TTCN after declaring the J1939_NAME type as little endian.

marckleinebudde commented 4 years ago

The question is, what's the datatype of the name in the TTCN-3 universe here? oct2int(name).get_long_long_val()

As said earlier, the name in bind() is in native endianess, while the name in the data of the sendto() has to be converted to little endian.

josemic commented 4 years ago

j1939.hh:

namespace J1939 {
typedef OCTETSTRING J1939__NAME;
..
}

It is the C++ OCTETSTRING class.

marckleinebudde commented 4 years ago

Octetstring for the j1939 name is probably wrong. It should be a native unsigned 64 bit value.

josemic commented 4 years ago

Ericsson confirmed that oct2int(name).get_long_long_val() is a native 64-bit unsigned integer.

josemic commented 4 years ago

The titan TTCN-3 test port titan.TestPorts.SocketCANasp with support for J1939 had been released here: Titan test ports

For J1939 support see the directory doc/Readme.md

josemic commented 4 years ago

This issue is solved. It can be closed.

josemic commented 4 years ago

@marckleinebudde: The Titan SocketCAN test port had made much progress. Here is found an introduction: Getting Started with the SocketCAN J1939 Test Port on the example of Isobus https://www.eclipse.org/forums/index.php/t/1104980/