Navigation

    IO Ninja IO Ninja Forum
    • Register
    • Login
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    1. Home
    2. Vladimir
    3. Posts
    • Profile
    • More
      • Following
      • Followers
      • Topics
      • Posts
      • Best
      • Groups

    Posts made by Vladimir

    RE: writing modbus scan script

    @g0xran,

    TBH, I think it's better to provide the correct checksum. Depending on the implementation, Modbus slaves may ignore commands with incorrect checksums and not respond to such packets at all.

    In any case, the sample above fills in the checksum by calling ModbusRtuReadRequestPacket.updateChecksum(). The implementation is in scripts/packets/ModbusRtu.jnc in case you need it.

    Also, I recommend attaching the Modbus Analyzer layer plugin when you work with Modbus devices -- this plugin will decode data streams and provide a human-readable representation of Modbus packets.

    posted in Support & Troubleshooting •
    RE: Transmit section history buttons gone

    The history buttons were replaced with a much more powerful "Packet History" pane. There, you can see the whole history of transmitted packets, send any of those again with a double click, or drag a packet to the packet editor if you need to modify it before sending it.

    Also, you can assign a mnemonic name to any of those packets and add them to the "Packet Library" pane (makes it easier to navigate through the packet list and choose which packet to send).

    See the dedicated feature page below:

    https://ioninja.com/features/packet-library.html

    posted in Support & Troubleshooting •
    RE: writing modbus scan script

    If I understand correctly, you want to loop through all devices in an address range, and send a Modbus command to each one?

    The sample below sends a ReadInputRegisters command; feel free to modify it accordingly.

    import "../packets/ModbusRtu.jnc"
    
    void main() {
    	const int DeviceAddrFrom = 10;
    	const int DeviceAddrTo = 20;
    	const int ReadAddr = 30;
    
    	for (int addr = DeviceAddrFrom; addr <= DeviceAddrTo; addr++) {
    		ModbusRtuReadRequestPacket packet;
    		packet.m_adu.m_deviceAddress = addr;
    		packet.m_pdu.m_func = io.ModbusFunc.ReadInputRegisters;
    		packet.m_pdu.m_address = ReadAddr;
    		packet.m_pdu.m_count = 1;
    		packet.updateChecksum();
    
    		transmit(&packet, sizeof(packet));		
    		sys.sleep(1000); // give the device some time to respond
    	}	
    }
    
    posted in Support & Troubleshooting •
    RE: Dark Theme

    Thank you so much for the feedback! I'm really happy to hear IO Ninja is helping you in your line of work! 🙂

    There's currently no way to run IO Ninja in dark mode, though. Properly, that is -- for example, under the KDE Dark Mode IO Ninja (unintentionally) picks up the system settings -- but looks atrocious! On other platforms, we try to enforce the light mode because the "automatic" dark mode in IO Ninja is not really usable; porting and work of a designer are required. But we did receive quite a few requests for the dark mode in the past, so it's on our TODO list. We will do our best to add support for the dark mode in IO Ninja this year!

    posted in General Discussion •
    RE: tdevmon on MIPS architecture

    MIPS is not currently supported, sorry. It's on our TODO list, but not too high up, to be honest (people mostly run tdevmon on PCs and Raspberry Pis)

    However, the kernel module is open source, so if you have experience in Linux Kernel development, you can try porting the tdevmon LKM to MIPS yourself (yes, porting is required, bare recompilation won't cut it, unfortunately).

    If you're able to port the kernel module, I can provide you with the sources of the CLI app, too -- then you should be able to build the whole tdevmon on MIPS.

    posted in Support & Troubleshooting •
    RE: CLI use (ioninja - serial tap)

    Yes, we plan to add the .njlog output soon, so that would solve many problems.

    Running a remote X-server is an option, yes. It actually was the only option for running Serial Tap remotely before the release of ioninja-hwc. Now that we have ioninja-hwc, it should be the preferred approach in most cases.

    We also consider extending the Serial over SSH (and other remote tap plugins) with an option of keeping the ioninja-hwc process persistent across SSH reconnects (via screen or some other terminal manager).

    posted in Support & Troubleshooting •
    RE: CLI use (ioninja - serial tap)

    Hello Jose,

    Once again, apologies for the delayed reply 🙂

    Yes, in IO Ninja 5.1 we added a CLI tool (ioninja-hwc) for direct communication with Serial Tap and other IO Ninja hardware. The main purpose of this tool was to network-enable the IO Ninja Taps (i.e., to access those over SSH). As such, the output from this tool must satisfy certain requirements (e.g., reliably distinguish between ioninja-hwc messages and SSH server errors, like ioninja-hwc not found). The .njlog format is not very suitable for that, that's why we didn't use it by default (we actually used the protocol from our test fixture for Serial and other Taps).

    This said, we can of course add .njlog output as an extra feature -- just like we have .pcap output. We probably will do that in one of the upcoming releases.

    1. Regarding the format of the output -- since all the scripts in IO Ninja are open-source, you can check the sources of decoder in ioninja/scripts/common/io_HwcProto.jnc. This file contains file structure definitions, packet type code constants, and the decoder itself.

    2. You are right, there are no timestamps in this protocol. Serial Tap doesn't timestamp individual bytes (Serial Tap basically is a dual-channel USB-to-UART). We can, of course, add timestamps inside ioninja-hwc, but in case of SSH communications, it's not that much different from timestamping on the SSH client side (as we do now). And after we support the .njlog format for file output, adding timestamps to the protocol most likely will not be necessary.

    Let me know what you think!

    posted in Support & Troubleshooting •
    RE: RegEx examples for Colorize Log feature

    Thanks! That simple regex ([\x52\x57]....) does work somewhat, but it also highlights any data within the payload that has an ASCII R or W, instead of only at the start of the packet. But that's useful enough for now, I appreciate it.

    What defines the start of a packet in this protocol? Could you share a link to the description of the packet structure?

    posted in Support & Troubleshooting •
    RE: RegEx examples for Colorize Log feature

    Thanks for a quick reply!

    Indeed, a bug:

    			if (p > base)
    				writer.write(timestamp, recordCode, base, p - base - 1);
    

    should be:

    			if (p > base)
    				writer.write(timestamp, recordCode, base, p - base);
    

    BreakOnChar.7z

    posted in Support & Troubleshooting •
    RE: RegEx examples for Colorize Log feature

    The new Regex Colorizer feature has been just added in ioninja-5.1.0 and is not yet documented. It uses an FSM-based rather than a backtracking engine, and as such, it doesn't and will not ever support some features available in PCRE -- most notably, no backtracking or named groups. POSIX-style character classes (e.g., [:space:]) are currently not supported (but can be easily added). Until we have a dedicated documentation page, you can refer to the following Ragel definition as a reference for what's supported:

    #. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    #
    #  standard definitions
    #
    
    oct = [0-7];
    dec = [0-9];
    hex = [0-9a-fA-F];
    ws  = [ \t\r];
    
    utf8_1 = 0x00 .. 0x7f;
    utf8_2 = 0xc0 .. 0xdf;
    utf8_3 = 0xe0 .. 0xef;
    utf8_4 = 0xf0 .. 0xf7;
    utf8_c = 0x80 .. 0xbf;
    
    #. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    #
    #  main machine
    #
    
    main := |*
    
    '^'    { createToken(TokenKind_AnchorBeginLine); };
    '$'    { createToken(TokenKind_AnchorEndLine); };
    '\\A'  { createToken(TokenKind_AnchorBeginText); };
    '\\z'  { createToken(TokenKind_AnchorEndText); };
    '\\b'  { createToken(TokenKind_AnchorWordBoundary); };
    '\\B'  { createToken(TokenKind_AnchorNotWordBoundary); };
    
    '[^'   { createToken(TokenKind_NegatedCharClass); fgoto char_class; };
    '['    { createToken(TokenKind_CharClass); fgoto char_class; };
    '(?:'  { createToken(TokenKind_NonCapturingGroup); };
    '('    { createToken(TokenKind_Group); };
    ')'    { createToken(TokenKind_EndGroup); };
    '??'   { createToken(TokenKind_NonGreedyQuestion); };
    '?'    { createToken(TokenKind_Question); };
    '*?'   { createToken(TokenKind_NonGreedyStar); };
    '*'    { createToken(TokenKind_Star); };
    '+?'   { createToken(TokenKind_NonGreedyPlus); };
    '+'    { createToken(TokenKind_Plus); };
    '|'    { createToken(TokenKind_Pipe); };
    '{'    { createToken(TokenKind_Quantifier); fgoto quantifier; };
    '.'    { createToken(TokenKind_AnyChar); };
    
    '\\d'  { createToken(TokenKind_StdCharClassDigit); };
    '\\D'  { createToken(TokenKind_StdCharClassNonDigit); };
    '\\h'  { createToken(TokenKind_StdCharClassHex); };
    '\\H'  { createToken(TokenKind_StdCharClassNonHex); };
    '\\w'  { createToken(TokenKind_StdCharClassWord); };
    '\\W'  { createToken(TokenKind_StdCharClassNonWord); };
    '\\s'  { createToken(TokenKind_StdCharClassSpace); };
    '\\S'  { createToken(TokenKind_StdCharClassNonSpace); };
    
    '\\0'  { createCharToken(0); };
    '\\a'  { createCharToken('\a'); };
    '\\b'  { createCharToken('\b'); };
    '\\e'  { createCharToken('\x1b'); };
    '\\f'  { createCharToken('\f'); };
    '\\n'  { createCharToken('\n'); };
    '\\r'  { createCharToken('\r'); };
    '\\t'  { createCharToken('\t'); };
    '\\v'  { createCharToken('\v'); };
    
    '\\x' hex{2}   { createHexCharToken_2(ts + 2); };
    '\\u' hex{4}   { createHexCharToken_4(ts + 2); };
    '\\U' hex{8}   { createHexCharToken_8(ts + 2); };
    '\\'  oct{3}   { createOctCharToken(ts + 1); };
    '\\'  any      { createCharToken(ts[1]); };
    
    utf8_1            { createCharToken(ts[0]); };
    utf8_2 utf8_c     { createUtf8CharToken_2(ts); };
    utf8_3 utf8_c{2}  { createUtf8CharToken_3(ts); };
    utf8_4 utf8_c{3}  { createUtf8CharToken_4(ts); };
    
    any               { createCharToken(ts[0]); };
    
    *|;
    
    #. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    #
    #  char class machine
    #
    
    char_class := |*
    
    '-'    { createToken(TokenKind_Dash); };
    ']'    { createToken(TokenKind_EndCharClass); fgoto main; };
    
    # alas, Ragel doesn't allow injecting sub-scanners, hence, copy-paste...
    
    '\\d'  { createToken(TokenKind_StdCharClassDigit); };
    '\\D'  { createToken(TokenKind_StdCharClassNonDigit); };
    '\\h'  { createToken(TokenKind_StdCharClassHex); };
    '\\H'  { createToken(TokenKind_StdCharClassNonHex); };
    '\\w'  { createToken(TokenKind_StdCharClassWord); };
    '\\W'  { createToken(TokenKind_StdCharClassNonWord); };
    '\\s'  { createToken(TokenKind_StdCharClassSpace); };
    '\\S'  { createToken(TokenKind_StdCharClassNonSpace); };
    
    '\\0'  { createCharToken(0); };
    '\\a'  { createCharToken('\a'); };
    '\\b'  { createCharToken('\b'); };
    '\\e'  { createCharToken('\x1b'); };
    '\\f'  { createCharToken('\f'); };
    '\\n'  { createCharToken('\n'); };
    '\\r'  { createCharToken('\r'); };
    '\\t'  { createCharToken('\t'); };
    '\\v'  { createCharToken('\v'); };
    
    '\\x' hex{2}   { createHexCharToken_2(ts + 2); };
    '\\u' hex{4}   { createHexCharToken_4(ts + 2); };
    '\\U' hex{8}   { createHexCharToken_8(ts + 2); };
    '\\'  oct{3}   { createOctCharToken(ts + 1); };
    '\\'  any      { createCharToken(ts[1]); };
    
    utf8_1            { createCharToken(ts[0]); };
    utf8_2 utf8_c     { createUtf8CharToken_2(ts); };
    utf8_3 utf8_c{2}  { createUtf8CharToken_3(ts); };
    utf8_4 utf8_c{3}  { createUtf8CharToken_4(ts); };
    
    any               { createCharToken(ts[0]); };
    
    *|;
    
    #. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    #
    #  quantifier machine
    #
    
    quantifier := |*
    
    dec+  { createNumberToken(atoi(ts)); };
    ','   { createToken(TokenKind_Comma); };
    '}'   { createToken(TokenKind_EndQuantifier); fgoto main; };
    
    ws;
    
    *|;
    
    

    Just as you've noticed, not everything works as expected (e.g., there are issues with anchor processing and quantifiers); these issues will be polished over the upcoming service releases.

    For the time being, you can use this to colorize the command byte trigger (0x52 or 0x57) and the following four bytes:

    [\x52\x57]....
    

    I would also consider creating a simple protocol analyzer layer that would allow for more options when it comes to visual aids. For example, you can insert a line "break" before each occurrence of the command byte trigger with this code:

    class BreakOnCharLayer:
    	doc.Layer,
    	log.Converter {
    
    public:
    	construct(doc.PluginHost* pluginHost){
    		basetype.construct(pluginHost);
    		pluginHost.m_log.addConverter(this);
    	}
    
    	override bool convert(
    		log.Writer* writer,
    		uint64_t timestamp,
    		uint64_t recordCode,
    		void const* p0,
    		size_t size
    	) {
    		if (recordCode != log.StdRecordCode.Tx &&
    			recordCode != log.StdRecordCode.Rx)
    			return false;
    
    		char const* base = p0;
    		char const* p = p0;
    		char const* end = p + size;
    		for (; p < end; p++) {
    			char c = *p;
    			if (c != 0x52 && c != 0x57)
    				continue;
    
    			if (p > base)
    				writer.write(timestamp, recordCode, base, p - base - 1);
    
    			writer.write(timestamp, log.StdRecordCode.Break);
    			base = p;
    		}
    
    		if (p > base)
    			writer.write(timestamp, recordCode, base, p - base);
    
    		return true;
    	}
    }
    

    BreakOnChar.7z

    A layer like this could, of course, add more meaningful data to the log (i.e., some human-readable information about commands and replies extracted from the binary packets.

    Our tutorial for writing protocol analyzers covers the topic in more detail: https://ioninja.com/doc/developer-manual/tutorial-plugin-analyzer.html

    Of course, writing an in-depth protocol analyzer only makes sense if it is expected to work with a protocol at hand long enough (to justify the work on the analyzer). Otherwise, I would stop at inserting a break between the packet boundaries and maybe tagging packets with a few lines of human-readable text.

    posted in Support & Troubleshooting •
    RE: Two chips on one SPI

    Hello Bartosz,

    The script for a filter to do what you want is very simple. First, you deduce the state of the CS line (from the I2cSpiTapLogRecordCode.SpiStart and SpiStop log records); then, you use this state to either hide or show the MOSI/MISO data (the log.StdRecordCode.TxRx log records).

    The source code for such a filter might look something like this:

    import "doc_Layer.jnc"
    import "I2cSpiTap/I2cSpiTapLogRecordCode.jnc"
    
    class SpiCsFilterLayer:
    	doc.Layer,
    	log.Filter
    {
    protected:
    	ui.EnumProperty* m_csFilterProp; // a property to choose the filtering strategy
    	int m_cs; // the state of the CS line
    
    public:
    	construct(doc.PluginHost* pluginHost);
    
    	override bool filter(
    		uint64_t timestamp,
    		uint64_t recordCode,
    		void const* p,
    		size_t size
    		);
    }
    
    SpiCsFilterLayer.construct(doc.PluginHost* pluginHost)
    {
    	basetype.construct(pluginHost);
    
    	ui.EnumPropertyOption csFilterOptions[] = {
    		{ "Show always", -1 },
    		{ "Show when CS low", 0 },
    		{ "Show when CS high", 1 },
    	}
    
    	m_csFilterProp = m_pluginHost.m_propertyGrid.createEnumProperty(
    		"MOSI/MISO filter",
    		"Show MOSI/MISO filtering criteria",
    		csFilterOptions,
    		countof(csFilterOptions)
    	);
    
    	m_cs = -1; // -1 means unknown
    	pluginHost.m_log.addFilter(this);
    }
    
    bool SpiCsFilterLayer.filter(
    	uint64_t timestamp,
    	uint64_t recordCode,
    	void const* p,
    	size_t size
    	)
    {
    	bool isVisible = true;
    
    	switch (recordCode)
    	{
    	case log.StdRecordCode.SessionStarted:
    		m_cs = -1; // reset to unknown
    		break;
    
    	case I2cSpiTapLogRecordCode.SpiStart:
    		m_cs = 0;
    		break;
    
    	case I2cSpiTapLogRecordCode.SpiStop:
    		m_cs = 1;
    		break;
    
    	case log.StdRecordCode.TxRx:
    		isVisible =
    			m_csFilterProp.m_value == -1 || // show always
    			m_csFilterProp.m_value == m_cs; // matches the current state of CS
    		break;
    	}
    
    	return isVisible;
    }
    

    An archive with the complete filter plugin is attached (SpiCsFilter.7z)

    Usage:

    1. Start I2C/SPI session
    2. Open the SpiCsFilter.njplg file to attach this filter
    3. Go to "Settings"
    4. Select the appropriate filtering criteria
    5. Hit "Apply and rebuild log" to apply the filter to the whole log (clicking "Apply" or "OK" will apply the filter "from now on" (i.e., to the follow-up log records only)
    posted in Support & Troubleshooting •
    RE: Borrow capabilities

    Hello Maxim,

    To borrow a capability, you need:

    1. Be a part of a workgroup;
    2. Your workgroup must have at least one unused seat of this capability.

    Then, from the "Capabilities" page, click on this particular capability, and you should see a "BORROW" button at the bottom of the page (like on a screenshot below):

    ioninja-borrow-capability.png

    posted in Support & Troubleshooting •
    RE: CLI use (ioninja - serial tap)

    Apologies for the delayed reply... Still, better late than never 🙂

    Alas, currently, there's no support for remote monitoring via Serial Taps -- fiddling with ioninja-server won't cut it... However, it's on our immediate TODO list; we are working on a major update scheduled for the beginning of 2022 which -- among many other things -- will allow controlling Serial Taps and other IO Ninja hardware sniffers connected to remote ARM-based Linux boxes (such as Raspberry Pis).

    If waiting for the update is not an option, I suggest writing your own program which would read from a Serial Tap using libusb (or any other low-level USB framework available in the programming language of your choice) and dump the captured data into a file.

    The low-level details of USB communication with a Serial Tap (USB VIDs/PIDs, IN/OUT endpoints, packet formats, etc) can be checked in the open-source implementation of the official Serial Tap plugin (see file scripts/plugins/SerialTap/SerialTapSession.jnc). If you are familiar with C-family languages (C, Java, JavaScript, etc) you should be able to read and understand pretty much everything. And of course, feel free to ask any implementation-related questions here.

    Hope this clarifies the issue.

    posted in Support & Troubleshooting •
    RE: Sample Request for changing session properties within script

    Currently, IAS (in-app-script) can only control the underlying session via:

    • connect()
    • disconnect()
    • transmit(p, size)

    Fine-tuning of the sessions configuration is currently not possible from IAS. But we plan to add this capability in the near future.

    Each session class will export a public IAS interface (e.g., a serial session will have properties to query and control baud rate, parity, status lines, etc.) Then, IAS should be able to access this interface via a global constant g_session (or something like that).

    posted in General Discussion •
    RE: Existing user licenses

    Please read the introduction to the new licensing model here: https://ioninja.com/capabilities-intro.html

    In particular, the section "Have a License?" outlines how to reuse old licenses in IO Ninja 5.

    Also, Serial Taps (and all other IO Ninja hardware taps) do not require any paid capabilities and should work just like before, even with fewer restrictions. With IO Ninja 3, to use a Serial Tap, one had to possess a license. Now, it's not required -- for example, you can freely share Serial Taps with your friends.

    posted in General Discussion •
    RE: Reading Values of Properties of Serial Port in Script

    Sure. The basic idea is to listen for new log records and update your variables holding the state of RTS/CTS lines accordingly. You would need SerialLogRecordCode.PortOpened to get the initial line state on port open, SerialLogRecordCode.RtsChanged to track RTS changes, and SerialLogRecordCode.StatusLineChanged to track CTS changes.

    In the sample below, I simply print the states of those lines to the session system log (Menu -> View -> System Log). Feel free to change it according to the requirements of your script.

    import "io_base.jncx"
    import "io_Serial.jnc"
    import "Serial/SerialLogRecordCode.jnc"
    
    bool g_rts;
    bool g_cts;
    
    void onLogRecord(
    	uint64_t timestamp,
    	uint64_t recordCode,
    	void const* p,
    	size_t size
    ) {
    	switch (recordCode) {
    	case SerialLogRecordCode.PortOpened:
    		SerialOpenParams const* params = (SerialOpenParams const*)p;
    		g_rts = params.m_rts;
    		g_cts = (params.m_statusLines & io.SerialStatusLines.Cts) != 0;
    		printf("Initial RTS: %d CTS: %d\n", g_rts, g_cts);
    		break;
    
    	case SerialLogRecordCode.RtsChanged:
    		g_rts = *(bool const*) p;
    		printf("RTS changed: %d\n", g_rts);
    		break;
    
    	case SerialLogRecordCode.StatusLineChanged:
    		SerialStatusLineChangedParams const* params = (SerialStatusLineChangedParams const*)p;
    		if (params.m_mask & io.SerialStatusLines.Cts) {
    			g_cts = (params.m_lines & io.SerialStatusLines.Cts) != 0;
    			printf("CTS changed: %d\n", g_cts);
    		}
    		
    		break;
    	}
    }
    
    posted in General Discussion •
    RE: Trying to Snoop an RS485 link - Port problems

    Could you please share your wiring diagram? Or a photo of how you connect the tap?

    For RS485 half-duplex (two-wire), you need to connect either the (RX+/RX-) pair or the (TX+/TX-) pair. The only difference it's going to make is how the data will be called in the IO Ninja log -- RX or TX. For RS485 full-duplex (four-wire) -- which is far less common -- you need to connect both (RX+/RX-) and (TX+/TX-). But you definitely don't need two taps to sniff a single serial link.

    posted in Support & Troubleshooting •
    RE: Trying to Snoop an RS485 link - Port problems

    Oh, so you are using a Serial Tap for IO Ninja; not a generic dual-COM serial tap.

    Then you need to use a dedicated "Serial Tap" plugin (not "Generic Serial Tap").

    posted in Support & Troubleshooting •
    RE: Trying to Snoop an RS485 link - Port problems

    Generic serial tap reads from two ports at the same time -- one port obtains RX, CTS, DSR; the second one obtains TX, DTR, RTS. Oftentimes, only TX and RX are required -- it simplifies the wiring. There are various over-the-counter dual-COM taps available; you can easily solder (or build on a breadboard) your own one, too.

    The log error message says "ports A and B" while the toolbar refers to those ports as "DTE" and "DCE". This is confusing, I agree. We will change the port names in the log to "DTE" and "DCE" for consistency.

    posted in Support & Troubleshooting •
    RE: RS485 Modbus RTU - Comms snooping

    Hopefully 1 last questions - you are calculating the CRCs and reporting whether they are accurate/correct - do you just run through a bunch of different CRC algorithms at the start of a capture to try and work out which is the correct one ?

    Modbus RTU is using CRC16 (ANSI) with the seed value of 0xffff. You can see the Modbus checksum of any data block in the "Checksum calculator" of the Information pane on the right. Select a range of data, and it will be instantly calculated.

    The code should look something like this:

    uint16_t modbusChecksum = crc16_ansi(p, size, 0xffff);
    

    The implementation of crc16_ansi is in scripts/common/crc16.jnc

    posted in Support & Troubleshooting •