/*  Copyright (C) MOXA Inc. All rights reserved.

    This software is distributed under the terms of the
    MOXA License.  See the file COPYING-MOXA for details.
*/

/*
	rx_fd.c

	SocketCAN receive example for Classical CAN and CAN FD

    2025-12-09	Eric XH Lin
		new release
*/

#include <errno.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#define DEFAULT_IFACE	"can0"
#define RX_TARGET	10

static void dump_frame( const struct canfd_frame *cf, ssize_t nbytes )
{
	int is_fd = ( nbytes == CANFD_MTU );
	int is_eff = cf->can_id & CAN_EFF_FLAG;
	unsigned int can_id = is_eff ? ( cf->can_id & CAN_EFF_MASK ) : ( cf->can_id & CAN_SFF_MASK );
	unsigned int len = is_fd ? cf->len : ( (const struct can_frame *)cf )->can_dlc;
	int i;

	printf( "RX %s 0x%0*x [%u]%s ", is_fd ? "FD" : "Classic",
		is_eff ? 8 : 3, can_id, len,
		( is_fd && ( cf->flags & CANFD_BRS ) ) ? " BRS" : "" );

	for( i = 0; i < len; i++ )
		printf( "%02x ", cf->data[i] );

	if( cf->can_id & CAN_RTR_FLAG )
		printf( "RTR" );

	printf( "\n" );
}

int	main( int argc, char *argv[] )
{
	int s = -1;
	struct sockaddr_can addr;
	struct ifreq ifr;
	const char *ifname = ( argc > 1 ) ? argv[1] : DEFAULT_IFACE;
	int ret;
	int enable_canfd = 1;
	struct timeval tv = { .tv_sec = 2, .tv_usec = 0 };

	s = socket( PF_CAN, SOCK_RAW, CAN_RAW );
	if( s < 0 ){
		perror( "socket" );
		return -1;
	}

	if( setsockopt( s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &enable_canfd, sizeof(enable_canfd) ) < 0 ){
		perror( "setsockopt CAN_RAW_FD_FRAMES" );
		close( s );
		return -1;
	}

	setsockopt( s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv) );

	addr.can_family = PF_CAN;
	memset( &ifr, 0, sizeof(ifr) );
	strncpy( ifr.ifr_name, ifname, IFNAMSIZ - 1 );

	if( ioctl( s, SIOCGIFINDEX, &ifr ) ){
		perror( "ioctl" );
		close( s );
		return -1;
	}

	addr.can_ifindex = ifr.ifr_ifindex;

	if( bind( s, (struct sockaddr *)&addr, sizeof(addr) ) < 0 ){
		perror( "bind" );
		close( s );
		return -1;
	}

	printf( "Listening on %s (CTRL+C to exit)\n", ifname );

	{
		int rx_count = 0;
		struct canfd_frame rxf = {0};

		while( rx_count < RX_TARGET ){
			ssize_t nbytes = read( s, &rxf, sizeof(rxf) );

			if( nbytes < 0 ){
				if( errno == EWOULDBLOCK || errno == EAGAIN ){
					printf( "RX timeout waiting for traffic\n" );
					continue;
				}
				perror( "read" );
				close( s );
				return -1;
			}

			if( nbytes == 0 ){
				printf( "RX: no data\n" );
				continue;
			}

			dump_frame( &rxf, nbytes );
			rx_count++;
		}
	}

	close( s );
	return 0;
}
