use std::time::{Duration, Instant, SystemTime};
use anyhow::Result;
use clap::*;
use clap_num::maybe_hex;
use connection::Connection;
use slcan::Slcan;
pub mod connection;
pub mod j1939;
pub mod packet;
pub mod pushbus;
pub mod sim;
pub mod slcan;
pub mod uds;
use j1939::J1939;
use uds::Uds;
#[cfg(windows)]
pub mod rp1210;
#[cfg(windows)]
use rp1210::Rp1210;
#[cfg(target_os = "linux")]
pub mod socketcanconnection;
#[cfg(target_os = "linux")]
use socketcanconnection::SocketCanConnection;
use crate::{j1939::j1939_packet::J1939Packet, packet::Packet};
#[derive(Parser)] #[command(name = "cancan")]
#[command(version,about = "CAN tool", long_about = None)]
pub struct CanCan {
pub connection: String,
#[arg(long="sa", short('s'), default_value = "0xF9",value_parser=maybe_hex::<u8>)]
pub source_address: u8,
#[arg(long="da", short('d'), default_value = "0xFF",value_parser=maybe_hex::<u8>)]
pub destination_address: u8,
#[arg(long)]
pub j1939_tp: bool,
#[arg(long = "timeout", short('t'), default_value = "2000")]
pub timeout: u64,
#[arg(long, short('v'), default_value = "false")]
pub verbose: bool,
#[clap(subcommand)]
command: CanCommand,
}
impl CanCan {
fn timeout(&self) -> Duration {
Duration::from_millis(self.timeout)
}
}
pub struct CanContext {
pub can_can: CanCan,
pub connection: Box<dyn Connection>,
}
#[derive(Subcommand, Debug, Clone)]
enum CanCommand {
Log,
Server,
Ping,
Bandwidth,
Send {
#[arg( value_parser=maybe_hex::<u32>)]
id: u32,
#[arg( value_parser=maybe_hex::<u64>)]
payload: u64,
},
Vin,
Uds {
#[command(subcommand)]
uds: Uds,
},
J1939 {
#[arg(long, short = 't')]
transport_protocol: bool,
#[command(subcommand)]
j1939: J1939,
},
}
fn hex_array(arg: &str) -> Result<Box<[u8]>, std::num::ParseIntError> {
todo!()
}
#[derive(Parser, Debug, Clone)]
pub enum ConnectionDescriptor {
List {},
Sim {},
J2534 {},
#[cfg(target_os = "linux")]
SocketCan {
dev: String,
#[arg(long, short('s'), default_value = "500000")]
speed: u64,
},
SLCAN {
#[arg(long, short('v'), default_value = "false")]
verbose: bool,
port: String,
speed: u32,
},
#[cfg(windows)]
RP1210 {
id: String,
device: i16,
#[arg(long, short('C'), default_value = "J1939:Baud=Auto")]
connection_string: String,
#[arg(long, default_value = "false")]
app_packetize: bool,
#[arg(long, short('a'), default_value = "0xF9",value_parser=maybe_hex::<u8>)]
address: u8,
},
}
impl ConnectionDescriptor {
pub fn connect(&self) -> Result<Box<dyn Connection>> {
let connection = self;
match &connection {
ConnectionDescriptor::List {} => list_all(),
ConnectionDescriptor::Sim {} => todo!(),
ConnectionDescriptor::J2534 {} => todo!(),
#[cfg(target_os = "linux")]
ConnectionDescriptor::SocketCan { dev, speed } => {
Ok(Box::new(SocketCanConnection::new(dev, *speed)?) as Box<dyn Connection>)
}
ConnectionDescriptor::SLCAN {
verbose,
port,
speed,
} => Ok(Box::new(Slcan::new(*verbose, port, *speed)?)),
#[cfg(windows)]
ConnectionDescriptor::RP1210 {
id,
device,
connection_string,
app_packetize,
address: source_address,
} => {
{
let mut cs = rp1210::CONNECTION_STRING.write().unwrap();
*cs = connection_string.to_string();
let mut ap = rp1210::APP_PACKETIZATION.write().unwrap();
*ap = *app_packetize;
}
Ok(Box::new(Rp1210::new(id, *device, *source_address)?) as Box<dyn Connection>)
}
}
}
}
pub fn list_all() -> ! {
for pd in connection::enumerate_connections().unwrap() {
eprintln!("{}", pd.name);
for dd in pd.devices {
eprintln!(" {}", dd.name);
for c in dd.connections {
eprintln!(" {}: {}", c.name(), c.command_line());
}
}
}
std::process::exit(0);
}
pub fn main() -> Result<()> {
let can_can = CanCan::parse();
let connection =
ConnectionDescriptor::parse_from(std::iter::once("").chain(can_can.connection.split(" ")))
.connect()?;
let cli = &mut CanContext {
can_can,
connection,
};
match cli.can_can.command.clone() {
CanCommand::Server => {
server(cli)?;
}
CanCommand::Ping => {
ping(cli)?;
}
CanCommand::Send { id, payload } => {
send(cli, id, &payload.to_be_bytes())?;
}
CanCommand::Bandwidth => {
bandwidth(cli)?;
}
CanCommand::Vin => {
vin(cli)?;
}
CanCommand::Log => {
log(cli)?;
}
CanCommand::Uds { uds } => {
uds.execute_and_report(cli)?;
}
CanCommand::J1939 {
j1939,
transport_protocol,
} => {
j1939.execute(cli, transport_protocol)?;
}
}
Ok(())
}
fn send(can_can: &mut CanContext, id: u32, payload: &[u8]) -> Result<()> {
let packet = Packet::new(id, payload);
can_can.connection.send(&packet)?;
Ok(())
}
const PING_PGN: u32 = 0xFF00;
const SEND_PGN: u32 = 0xFF01;
fn bandwidth(can_can: &mut CanContext) -> Result<()> {
let source_address = can_can.can_can.source_address;
let destination_address = can_can.can_can.destination_address;
let connection = can_can.connection.as_mut();
let mut sequence: u64 = 0;
loop {
let p = J1939Packet::new_packet(
None,
1,
6,
SEND_PGN,
destination_address,
source_address,
&sequence.to_be_bytes(),
);
connection.send(&p.into())?;
sequence += 1;
}
}
fn ping(cli: &mut CanContext) -> Result<()> {
let source_address = cli.can_can.source_address;
let destination_address = cli.can_can.destination_address;
let connection = cli.connection.as_mut();
let mut sequence: u64 = 0;
let mut complete: u64 = 0;
let mut last_report = Instant::now();
let start = last_report;
loop {
let p = J1939Packet::new_packet(
None,
1,
6,
PING_PGN,
destination_address,
source_address,
&sequence.to_be_bytes(),
)
.into();
let mut iter = connection
.iter_for(Duration::from_secs(1))
.map(|p| p.into());
connection.send(&p)?;
if iter.any(|p: J1939Packet| p.pgn() == PING_PGN && p.source() == destination_address) {
complete += 1;
} else {
eprintln!("FAIL: {p}");
}
sequence += 1;
let now = Instant::now();
if now - last_report > Duration::from_secs(1) {
eprintln!(
"{} {complete}/{sequence}",
now.duration_since(start).as_millis()
);
last_report = now;
}
}
}
fn server(cli: &mut CanContext) -> Result<()> {
let sa = cli.can_can.source_address;
let mut count: i64 = 0;
let mut prev: i64 = 0;
let mut prev_time: SystemTime = SystemTime::now();
let connection = cli.connection.as_mut();
let stream = connection
.iter()
.filter_map(|o| o.map(|p| p.into()))
.filter(|p: &J1939Packet| p.source() != sa);
for p in stream {
if p.pgn() == PING_PGN {
count += 1;
let pong = &J1939Packet::new_packet(None, 1, 6, PING_PGN, p.source(), sa, {
let this = &p;
&this.payload
});
if count % 10_000 == 0 {
eprintln!("pong: {p} -> {pong}");
}
connection.send(&pong.into())?;
} else if p.pgn() == SEND_PGN {
count += 1;
let this = {
let mut arr = [0u8; 8];
arr.copy_from_slice({
let this = &p;
&this.payload
});
i64::from_be_bytes(arr)
};
if prev + 1 != this {
let diff = this - prev;
eprintln!("skipped: {diff} prev: {prev:X} this: {this:X} packet: {p}");
}
prev = this;
if count % 1_000 == 0 {
let now = SystemTime::now();
let rate = 1000.0 / now.duration_since(prev_time)?.as_secs_f64();
eprintln!("send count: {count} rate: {rate} packet/s");
prev_time = now;
connection.send(
&J1939Packet::new_packet(
None,
1,
6,
SEND_PGN,
p.source(),
sa,
&count.to_be_bytes(),
)
.into(),
)?;
}
}
}
Ok(())
}
fn vin(can_can: &mut CanContext) -> Result<()> {
let connection = can_can.connection.as_mut();
{
eprintln!("request VIN from ECM");
let mut iter = connection
.iter_for(Duration::from_secs(5))
.map(|p| p.into());
let packets = J1939::receive_tp(connection, 0xF9, false, &mut iter);
J1939::request(connection, Duration::from_secs(3), true, 0xF9, 0x00, 0xFEEC)?;
if let Some(p) = packets
.filter(|p| {
p.pgn() == 0xFEEC || [0xEA00, 0xEB00, 0xEC00, 0xE800].contains(&(p.pgn() & 0xFF00))
})
.map(|p| {
eprintln!(" {p}");
p
})
.find(|p| p.pgn() == 0xFEEC && p.source() == 0)
{
println!(
"ECM {:02X} VIN: {}\n{}",
p.source(),
String::from_utf8(p.data().into()).unwrap(),
p
);
};
}
{
eprintln!("\nrequest VIN from Broadcast");
let mut packets = connection
.iter_for(Duration::from_secs(5))
.map(|p| p.into());
let packets = J1939::receive_tp(connection, 0xF9, false, &mut packets);
J1939::request(connection, Duration::from_secs(3), true, 0xF9, 0xFF, 0xFEEC)?;
packets.filter(|p| p.pgn() == 0xFEEC).for_each(|p| {
println!(
"SA: {:02X} VIN: {}",
p.source(),
String::from_utf8(p.data().into()).unwrap()
)
});
}
Ok(())
}
fn log(can_can: &mut CanContext) -> Result<()> {
let connection = can_can.connection.as_mut();
let mut iter = connection.iter().flatten().map(|p| p.into());
let j1939_tp = can_can.can_can.j1939_tp;
eprintln!("\n\nlog everything for the next 30 days tp:{j1939_tp}");
if j1939_tp {
J1939::receive_tp(connection, can_can.can_can.source_address, false, &mut iter)
.for_each(|p| println!("{p}"));
} else {
iter.for_each(|p| println!("{p}"));
}
Ok(())
}