diff --git a/GPL-2.0 b/GPL-2.0 new file mode 100644 index 0000000..0a98ec0 --- /dev/null +++ b/GPL-2.0 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..fd44a62 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# XCP dissector + +This repository provides a dissector that enables [Wireshark](https://www.wireshark.org/) to decode [XCP](https://www.asam.net/standards/detail/mcd-1-xcp/) packets. + +## Purpose and disclaimer + +This dissector is mainly intended as diagnostic aid for developing and troubleshooting one's own XCP implementations. + +It does not claim to be 100% complete or 100% correct, and it is no replacement for studying the specification documents of the XCP standard itself. + +## Installation + +To install the dissector, copy the contents of the `lua` directory to your Wireshark plugins directory. +On Windows, this is typically located at `%AppData%\Wireshark\plugins`, on Linux at `~/.config/wireshark`. + +## Limitations + +The supported transport layers are *XCP on Ethernet* and *XCP on CAN*. +There are no plans to add other transport layers. + +The Multicast commands `GET_SLAVE_ID` and `GET_DAQ_CLOCK_MULTICAST` are not supported. + +Dissecting recordings of incomplete XCP sessions may not work reliably, due to parameters negotiated early in the session influencing the structure and meaning of certain packets exchanged later on. + +If DLC is set to MAX_DLC for XCP on CAN, the tail is also read as DATA for DOWNLOAD_MAX, PROGRAM_MAX and DAQ lists. + +## XCP on Ethernet + +If you are using a different port than 5555, you may need to right-click an XCP packet, click "decode as", optionally select the destination port number instead of the automatically chosen source port number, and choose "XCP_ETH". + +In that case, the dissector assumes that the slave uses the lower port number. You may need to change the `xcp_dir` assignments in `XCP_ETH.lua` if this isn't the case for you. + +## XCP on CAN +### Convert to a supported format +Some popular formats for recording CAN bus traffic are not directly supported by Wireshark. You can convert many formats e.g. with [python-can](https://github.com/hardbyte/python-can): + +```cmd +pip install python-can +python -m can.logconvert input.asc output.blf +``` + +### Enable Dissection +* Enable the protocol (Analyze -> Enabled Protocols -> XCP_CAN) +* Set your Master and Slave CAN IDs (Edit -> Preferences -> Protocols -> XCP_CAN) +* Right click on your CAN Frame -> Decode As -> Current -> XCP_CAN + +Only CAN frames whose CAN IDs match the CAN IDs configured in the protocol preferences are dissected. + +## License + +Copyright (c) 2024 TraceTronic GmbH + +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; version 2. + +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. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +## Feedback and contact + +If you have any questions, comments, requests, suggestions, etc. regarding this dissector, please contact support@tracetronic.com diff --git a/lua/XCP.lua b/lua/XCP.lua new file mode 100644 index 0000000..23bfab1 --- /dev/null +++ b/lua/XCP.lua @@ -0,0 +1,2627 @@ +-- XCP.lua +-- XCP dissector for Wireshark, protocol layer +-- Version 0.1 +-- +-- Copyright (c) 2023-2024 tracetronic GmbH +-- +-- SPDX-License-Identifier: GPL-2.0 + + +xcpProto = Proto("xcp", "Universal Measurement and Calibration Protocol") + +--[[ +conversations: request-response tracking +for each conversation ID (e.g. tcp.stream), for each packet, record +* for commands, the PID (so the response can be interpreted accordingly) +* for commands whose parameters influence the repsonse format, these parameters +* for commands and responses whose parameters influence events or behavior of other commands, these parameters +* for commands and responses that describe DAQ properties, the information needed later to decode DAQ packets + +find_stored_data can then retrieve the latest (but before the current packet) value for a specific recorded datum (such as PID) + +current limitations: the interleaved communication model is not supported +]] +-- +conversations = {} + +COMMAND_NAMES = { + -- Standard commands + [0xFF] = "CONNECT", + [0xFE] = "DISCONNECT", + [0xFD] = "GET_STATUS", + [0xFC] = "SYNCH", + [0xFB] = "GET_COMM_MODE_INFO", + [0xFA] = "GET_ID", + [0xF9] = "SET_REQUEST", + [0xF8] = "GET_SEED", + [0xF7] = "UNLOCK", + [0xF6] = "SET_MTA", + [0xF5] = "UPLOAD", + [0xF4] = "SHORT_UPLOAD", + [0xF3] = "BUILD_CHECKSUM", + [0xF2] = "TRANSPORT_LAYER_CMD", + [0xF1] = "USER_CMD", + -- Calibration commands + [0xF0] = "DOWNLOAD", + [0xEF] = "DOWNLOAD_NEXT", + [0xEE] = "DOWNLOAD_MAX", + [0xED] = "SHORT_DOWNLOAD", + [0xEC] = "MODIFY_BITS", + -- Page switching commands + [0xEB] = "SET_CAL_PAGE", + [0xEA] = "GET_CAL_PAGE", + [0xE9] = "GET_PAG_PROCESSOR_INFO", + [0xE8] = "GET_SEGMENT_INFO", + [0xE7] = "GET_PAGE_INFO", + [0xE6] = "SET_SEGMENT_MODE", + [0xE5] = "GET_SEGMENT_MODE", + [0xE4] = "COPY_CAL_PAGE", + -- Basic data acquisition and stimulation commands + [0xE2] = "SET_DAQ_PTR", + [0xE1] = "WRITE_DAQ", + [0xE0] = "SET_DAQ_LIST_MODE", + [0xDE] = "START_STOP_DAQ_LIST", + [0xDD] = "START_STOP_SYNCH", + [0xC7] = "WRITE_DAQ_MULTIPLE", + [0xDB] = "READ_DAQ", + [0xDC] = "GET_DAQ_CLOCK", + [0xDA] = "GET_DAQ_PROCESSOR_INFO", + [0xD9] = "GET_DAQ_RESOLUTION_INFO", + [0xDF] = "GET_DAQ_LIST_MODE", + [0xD7] = "GET_DAQ_EVENT_INFO", + [0xC5] = "DTO_CTR_PROPERTIES", + -- Static data acquisition and stimulation commands + [0xE3] = "CLEAR_DAQ_LIST", + [0xD8] = "GET_DAQ_LIST_INFO", + -- Dynamic data acquisition and stimulation commands + [0xD6] = "FREE_DAQ", + [0xD5] = "ALLOC_DAQ", + [0xD4] = "ALLOC_ODT", + [0xD3] = "ALLOC_ODT_ENTRY", + -- Non-volatile memory programming commands + [0xD2] = "PROGRAM_START", + [0xD1] = "PROGRAM_CLEAR", + [0xD0] = "PROGRAM", + [0xCF] = "PROGRAM_RESET", + [0xCE] = "GET_PGM_PROCESSOR_INFO", + [0xCD] = "GET_SECTOR_INFO", + [0xCC] = "PROGRAM_PREPARE", + [0xCB] = "PROGRAM_FORMAT", + [0xCA] = "PROGRAM_NEXT", + [0xC9] = "PROGRAM_MAX", + [0xC8] = "PROGRAM_VERIFY", + -- Time synchronization commands + [0xC6] = "TIME_CORRELATION_PROPERTIES", +} + +ERR_CODES = { + [0x00] = "ERR_CMD_SYNCH", + [0x10] = "ERR_CMD_BUSY", + [0x11] = "ERR_DAQ_ACTIVE", + [0x12] = "ERR_PGM_ACTIVE", + [0x20] = "ERR_CMD_UNKNOWN", + [0x21] = "ERR_CMD_SYNTAX", + [0x22] = "ERR_OUT_OF_RANGE", + [0x23] = "ERR_WRITE_PROTECTED", + [0x24] = "ERR_ACCESS_DENIED", + [0x25] = "ERR_ACCESS_LOCKED", + [0x26] = "ERR_PAGE_NOT_VALID", + [0x27] = "ERR_MODE_NOT_VALID", + [0x28] = "ERR_SEGMENT_NOT_VALID", + [0x29] = "ERR_SEQUENCE", + [0x2A] = "ERR_DAQ_CONFIG", + [0x30] = "ERR_MEMORY_OVERFLOW", + [0x31] = "ERR_GENERIC", + [0x32] = "ERR_VERIFY", + [0x33] = "ERR_RESOURCE_TEMPORARY_NOT_ACCESSIBLE", + [0x34] = "ERR_SUBCMD_UNKNOWN", +} + +EV_CODES = { + [0x00] = "EV_RESUME_MODE", + [0x01] = "EV_CLEAR_DAQ", + [0x02] = "EV_STORE_DAQ", + [0x03] = "EV_STORE_CAL", + [0x05] = "EV_CMD_PENDING", + [0x06] = "EV_DAQ_OVERLOAD", + [0x07] = "EV_SESSION_TERMINATED", + [0x08] = "EV_TIME_SYNC", + [0x09] = "EV_STIM_TIMEOUT", + [0x0A] = "EV_SLEEP", + [0x0B] = "EV_WAKE_UP", + [0x0C] = "EV_ECU_STATE_CHANGE", + [0xFE] = "EV_USER", + [0xFF] = "EV_TRANSPORT", +} + +SERV_CODES = { + [0x00] = "SERV_RESET", + [0x01] = "SERV_TEXT", +} + +AG_TYPES = { + [0] = "bytes", + [1] = "words", + [2] = "dwords", + [3] = "elements (of unknown size)" +} + +xcp_type = ProtoField.string("xcp.type", "TYPE", base.ASCII) +xcp_cmd = ProtoField.uint8("xcp.cmd", "CMD", base.HEX, COMMAND_NAMES) +xcp_err = ProtoField.uint8("xcp.err", "ERR", base.HEX, ERR_CODES) +xcp_ev = ProtoField.uint8("xcp.ev", "EV", base.HEX, EV_CODES) +xcp_serv = ProtoField.uint8("xcp.serv", "SERV", base.HEX, SERV_CODES) +xcp_info = ProtoField.string("xcp.info", "INFO", base.ASCII) +xcp_data = ProtoField.bytes("xcp.data", "DATA", base.SPACE) +xcp_request = ProtoField.framenum("xcp.request", "REQUEST", base.NONE, frametype.REQUEST, 0, "Associated request") +xcp_reserved = ProtoField.bytes("xcp.reserved", "RESERVED", base.SPACE) + +xcpProto.fields = { xcp_type, xcp_cmd, xcp_err, xcp_ev, xcp_serv, xcp_info, xcp_data, xcp_request, xcp_reserved } + +local band = bit.band +local rshift = bit.rshift +local lshift = bit.lshift +local format = string.format + +function hex(number, digits) + if type(number) == "table" then + return "0x" .. number:tohex(digits / 2) + end + return format("0x%0" .. digits .. "x", number) +end + +function parse_number(bufferslice, comm_mode_basic) + -- convert a buffer slice into a number, using the correct endianness. Use uint64 only if needed + if bufferslice:len() == 8 then + if band(comm_mode_basic, 1) == 0 then + return bufferslice:le_uint64() + else + return bufferslice:uint64() + end + end + if band(comm_mode_basic, 1) == 0 then + return bufferslice:le_uint() + else + return bufferslice:uint() + end +end + +function find_stored_data(conversation_id, max_pkt_num, key, default) + -- find the stored data from the highest-numbered packet of the current conversation that has a lower number than the current one + local found_pktnum = -1 + local found_data = default + for pkt_num, pkt_data in pairs(conversations[conversation_id]) do + if pkt_num <= max_pkt_num and pkt_num >= found_pktnum then + if pkt_data ~= nil and pkt_data[key] ~= nil then + found_pktnum = pkt_num + found_data = pkt_data[key] + end + end + end + return found_data +end + +function decode_cmd_connect(buffer, pinfo, tree, pkt_data, comm_mode_basic) + pkt_data.TIME_SYNCHRONIZATION_PROPERTIES__extended = false + local mode_field = buffer(1, 1) + local mode = mode_field:uint() + if mode == 0 then + tree:add(xcp_info, mode_field, "mode: Normal (0)") + elseif mode == 1 then + tree:add(xcp_info, mode_field, "mode: user-defined (1)") + else + tree:add(xcp_info, mode_field, "mode: " .. mode .. " (unknown)") + end +end + +function decode_cmd_get_id(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local id_type_field = buffer(1, 1) + local id_type = id_type_field:uint() + if id_type == 0 then + tree:add(xcp_info, id_type_field, "type: ASCII text (0)") + elseif id_type == 1 then + tree:add(xcp_info, id_type_field, "type: ASAM-MC2 filename without path and extension (1)") + elseif id_type == 2 then + tree:add(xcp_info, id_type_field, "type: ASAM-MC2 filename with path and extension (2)") + elseif id_type == 3 then + tree:add(xcp_info, id_type_field, "type: URL where the ASAM-MC2 file can be found (3)") + elseif id_type == 4 then + tree:add(xcp_info, id_type_field, "type: ASAM-MC2 file to upload (4)") + elseif 128 <= id_type and id_type <= 255 then + tree:add(xcp_info, id_type_field, "type: user defined (" .. hex(id_type, 2) .. ")") + else + tree:add(xcp_info, id_type_field, "type: unknown (" .. hex(id_type, 2) .. ")") + end +end + +function decode_cmd_set_request(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local mode_field = buffer(1, 1) + local mode = mode_field:uint() + local sess_field = buffer(2, 2) + local session_configuration_id = parse_number(sess_field, comm_mode_basic) + local infotext = "mode: " .. hex(mode, 2) .. " -- " + if band(mode, 0x1) ~= 0 then + infotext = infotext .. "STORE_CAL_REQ|" + end + if band(mode, 0x2) ~= 0 then + infotext = infotext .. "STORE_DAQ_REQ_NO_RESUME|" + end + if band(mode, 0x4) ~= 0 then + infotext = infotext .. "STORE_DAQ_REQ_RESUME|" + end + if band(mode, 0x8) ~= 0 then + infotext = infotext .. "CLEAR_DAQ_REQ|" + end + if mode == 0 then + infotext = "mode: 0|" + end + tree:add(xcp_info, mode_field, infotext:sub(1, -2)) + tree:add(xcp_info, sess_field, "session configuration id: " .. session_configuration_id) +end + +function decode_cmd_get_seed(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local mode_field = buffer(1, 1) + local mode = mode_field:uint() + local resource_field = buffer(2, 1) + local resource = resource_field:uint() + pkt_data.GET_SEED__MODE = mode + if mode == 0 then + tree:add(xcp_info, mode_field, "Mode: 0 -- first part of seed") + tree:add(xcp_info, resource_field, "resource: " .. resource) + elseif mode == 1 then + tree:add(xcp_info, mode_field, "Mode: 1 -- remaining part of seed") + tree:add(xcp_reserved, resource_field, "don't care") + else + tree:add(xcp_info, mode_field, "Mode: unknown (" .. hex(mode, 2) .. ")") + tree:add(xcp_info, resource_field, "resource: " .. resource) + end +end + +function decode_cmd_unlock(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local length_field = buffer(1, 1) + local length = length_field:uint() + local key = buffer(2) + tree:add(xcp_info, length_field, "remaining key length: " .. length) + tree:add(xcp_data, key, key:bytes():raw()) +end + +function decode_cmd_set_mta(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local addrExt_field = buffer(3, 1) + local addrExt = addrExt_field:uint() + local address_field = buffer(4, 4) + local address = parse_number(address_field, comm_mode_basic) + tree:add(xcp_reserved, buffer(1, 2)) + tree:add(xcp_info, addrExt_field, "Address Extension: " .. hex(addrExt, 2)) + tree:add(xcp_info, address_field, "Address: " .. hex(address, 8)) +end + +function decode_cmd_upload(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local address_granularity = band(rshift(comm_mode_basic, 1), 0x3) + local nElem_field = buffer(1, 1) + local nElem = nElem_field:uint() + -- save nElem to conversations to retrieve data field length in next pos upload response + pkt_data["N_ELEM"] = nElem + tree:add(xcp_info, nElem_field, nElem .. " " .. AG_TYPES[address_granularity]) +end + +function decode_cmd_short_upload(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local address_granularity = band(rshift(comm_mode_basic, 1), 0x3) + local nElem_field = buffer(1, 1) + local nElem = nElem_field:uint() + -- save nElem to conversations to retrieve data field length in next pos upload response + pkt_data["N_ELEM"] = nElem + local addrExt_field = buffer(3, 1) + local addrExt = addrExt_field:uint() + local address_field = buffer(4, 4) + local address = parse_number(address_field, comm_mode_basic) + tree:add(xcp_info, nElem_field, nElem .. " " .. AG_TYPES[address_granularity]) + tree:add(xcp_reserved, buffer(2, 1)) + tree:add(xcp_info, addrExt_field, "Address Extension: " .. hex(addrExt, 2)) + tree:add(xcp_info, address_field, "Address: " .. hex(address, 8)) +end + +function decode_cmd_build_checksum(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local address_granularity = band(rshift(comm_mode_basic, 1), 0x3) + local blockSize_field = buffer(4, 4) + local blockSize = parse_number(blockSize_field, comm_mode_basic) + tree:add(xcp_reserved, buffer(1, 3)) + tree:add(xcp_info, blockSize_field, blockSize .. " " .. AG_TYPES[address_granularity]) +end + +function decode_cmd_transport_layer_cmd__user_cmd(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local subCmd_field = buffer(1, 1) + local subCmd = subCmd_field:uint() + local params_field = buffer(2) + local params = params_field:bytes():raw() + tree:add(xcp_info, subCmd_field, "Subcommand " .. hex(subCmd, 2)) + tree:add(xcp_data, params_field, params) +end + +function decode_cmd_download__program(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local address_granularity = band(rshift(comm_mode_basic, 1), 0x3) + local nElem_field = buffer(1, 1) + local nElem = nElem_field:uint() + local data_field = buffer(2, nElem*2^address_granularity) + local data = data_field:bytes():raw() + tree:add(xcp_info, nElem_field, nElem .. " " .. AG_TYPES[address_granularity]) + if address_granularity == 2 then + tree:add(xcp_reserved, buffer(2, 2), "DWORD alignment") + data_field = data_field(2) + data = data_field:bytes():raw() + end + tree:add(xcp_data, data_field, data) +end + +function decode_cmd_download__program__max(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local address_granularity = band(rshift(comm_mode_basic, 1), 0x3) + local offset = 1 + if address_granularity == 0 then + -- no alignment + elseif address_granularity == 1 then + offset = 2 + tree:add(xcp_reserved, buffer(1, 1), "WORD alignment") + elseif address_granularity == 2 then + offset = 4 + tree:add(xcp_reserved, buffer(1, 3), "DWORD alignment") + end + local data_field = buffer(offset) + local data = data_field:bytes():raw() + tree:add(xcp_data, data_field, data) +end + +function decode_cmd_short_download(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local address_granularity = band(rshift(comm_mode_basic, 1), 0x3) + local nElem_field = buffer(1, 1) + local nElem = nElem_field:uint() + local addrExt_field = buffer(3, 1) + local addrExt = addrExt_field:uint() + local address_field = buffer(4, 4) + local address = parse_number(address_field, comm_mode_basic) + tree:add(xcp_info, nElem_field, nElem .. " " .. AG_TYPES[address_granularity]) + tree:add(xcp_reserved, buffer(2, 1)) + tree:add(xcp_info, addrExt_field, "Address Extension: " .. hex(addrExt, 2)) + tree:add(xcp_info, address_field, "Address: " .. hex(address, 8)) + local data_field = buffer(8, nElem*2^address_granularity) + local data = data_field:bytes():raw() + tree:add(xcp_data, data_field, data) +end + +function decode_cmd_modify_bits(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local shiftValue_field = buffer(1, 1) + local shiftValue = shiftValue_field:uint() + local andMask_field = buffer(2, 2) + local xorMask_field = buffer(4, 2) + tree:add(xcp_info, shiftValue_field, "Shift value: " .. hex(shiftValue, 2)) + tree:add(xcp_info, andMask_field, "AND mask: " .. andMask_field:bytes():raw():tohex(false, ":")) + tree:add(xcp_info, xorMask_field, "XOR mask: " .. xorMask_field:bytes():raw():tohex(false, ":")) +end + +function decode_cmd_set_cal_page(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local mode_field = buffer(1, 1) + local mode = mode_field:uint() + local segNum_field = buffer(2, 1) + local segNum = segNum_field:uint() + local pageNum_field = buffer(3, 1) + local pageNum = pageNum_field:uint() + if band(mode, 0x3) == 0x3 then + tree:add(xcp_info, mode_field, "Mode: ECU | XCP (3)") + elseif band(mode, 0x3) == 0x2 then + tree:add(xcp_info, mode_field, "Mode: XCP (2)") + elseif band(mode, 0x3) == 0x1 then + tree:add(xcp_info, mode_field, "Mode: ECU (1)") + else + tree:add(xcp_info, mode_field, "Mode: No access (0)") + end + if band(mode, 0x80) ~= 0 then + tree:add(xcp_info, mode_field, "All segments (0x80)") + tree:add(xcp_reserved, segNum_field, "Segment number ignored") + else + tree:add(xcp_info, segNum_field, "Segment " .. segNum) + end + if band(mode, 0x7c) ~= 0 then + tree:add(xcp_info, mode_field, "Mode: unknown flags (" .. hex(band(mode, 0x7c), 2) .. ")") + end + tree:add(xcp_info, pageNum_field, "Page " .. pageNum) +end + +function decode_cmd_get_cal_page(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local mode_field = buffer(1, 1) + local mode = mode_field:uint() + local segNum_field = buffer(2, 1) + local segNum = segNum_field:uint() + + if mode == 2 then + tree:add(xcp_info, mode_field, "Mode: XCP (2)") + elseif mode == 1 then + tree:add(xcp_info, mode_field, "Mode: ECU (1)") + else + tree:add(xcp_info, mode_field, "Mode: invalid (" .. hex(mode, 2) .. ")") + end + tree:add(xcp_info, segNum_field, "Segment " .. segNum) +end + +function decode_cmd_get_segment_info(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local mode_field = buffer(1, 1) + local mode = mode_field:uint() + local segNum_field = buffer(2, 1) + local segNum = segNum_field:uint() + local segInfo_field = buffer(3, 1) + local segInfo = segInfo_field:uint() + local mapIdx_field = buffer(4, 1) + local mapIdx = mapIdx_field:uint() + if mode == 0 then + tree:add(xcp_info, mode_field, "Mode: basic address info (0)") + elseif mode == 1 then + tree:add(xcp_info, mode_field, "Mode: standard info (1)") + elseif mode == 2 then + tree:add(xcp_info, mode_field, "Mode: address mapping info (2)") + else + tree:add(xcp_info, mode_field, "Mode: unknown (" .. hex(mode, 2) .. ")") + end + tree:add(xcp_info, segNum_field, "Segment number: " .. segNum) + if mode == 0 then + if segInfo == 0 then + tree:add(xcp_info, segInfo_field, "Segment Info: address (0)") + elseif segInfo == 1 then + tree:add(xcp_info, segInfo_field, "Segment Info: length (1)") + else + tree:add(xcp_info, segInfo_field, "Segment Info: unknown (" .. hex(segInfo, 2) .. ")") + end + tree:add(xcp_reserved, mapIdx_field, "Mapping Index: ignored") + elseif mode == 1 then + tree:add(xcp_reserved, segInfo_field, "Segment Info: ignored") + tree:add(xcp_reserved, mapIdx_field, "Mapping Index: ignored") + elseif mode == 2 then + if segInfo == 0 then + tree:add(xcp_info, segInfo_field, "Segment Info: source address (0)") + elseif segInfo == 1 then + tree:add(xcp_info, segInfo_field, "Segment Info: destination address (1)") + elseif segInfo == 2 then + tree:add(xcp_info, segInfo_field, "Segment Info: length address (2)") + else + tree:add(xcp_info, segInfo_field, "Segment Info: unknown (" .. hex(segInfo, 2) .. ")") + end + tree:add(xcp_info, mapIdx_field, "Mapping Index: " .. mapIdx) + end + pkt_data.GET_SEGMENT_INFO__MODE = mode + pkt_data.GET_SEGMENT_INFO__SEGMENT_INFO = segInfo +end + +function decode_cmd_get_page_info(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local segNum_field = buffer(2, 1) + local segNum = segNum_field:uint() + local pageNum_field = buffer(3, 1) + local pageNum = pageNum_field:uint() + tree:add(xcp_reserved, buffer(1, 1), "Reserved") + tree:add(xcp_info, segNum_field, "Segment number: " .. segNum) + tree:add(xcp_info, pageNum_field, "Page number: " .. pageNum) +end + +function decode_cmd_set_segment_mode(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local mode_field = buffer(1, 1) + local mode = mode_field:uint() + local segNum_field = buffer(2, 1) + local segNum = segNum_field:uint() + if band(mode, 1) == 1 then + tree:add(xcp_info, mode_field, "Mode: enable FREEZE (bit 0 set)") + else + tree:add(xcp_info, mode_field, "Mode: disable FREEZE (bit 0 not set)") + end + if band(mode, 0xfe) ~= 0 then + tree:add(xcp_info, mode_field, "Mode: unknown flags set (" .. hex(mode, 2) .. ")") + end + tree:add(xcp_info, segNum_field, "Segment number: " .. segNum) +end + +function decode_cmd_get_segment_mode(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local segNum_field = buffer(2, 1) + local segNum = segNum_field:uint() + tree:add(xcp_reserved, buffer(1, 1), "Reserved") + tree:add(xcp_info, segNum_field, "Segment number: " .. segNum) +end + +function decode_cmd_copy_cal_page(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local segSrc_field = buffer(1, 1) + local segSrc = segSrc_field:uint() + local pageSrc_field = buffer(2, 1) + local pageSrc = pageSrc_field:uint() + local segDst_field = buffer(3, 1) + local segDst = segDst_field:uint() + local pageDst_field = buffer(4, 1) + local pageDst = pageDst_field:uint() + tree:add(xcp_info, segSrc_field, "Source segment: " .. segSrc) + tree:add(xcp_info, pageSrc_field, "Source page: " .. pageSrc) + tree:add(xcp_info, segDst_field, "Destination segment: " .. segDst) + tree:add(xcp_info, pageDst_field, "Destination page: " .. pageDst) +end + +function decode_cmd_clear_daq_list__get_daq_list_mode__info(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local daqListNr_field = buffer(2, 2) + local daqListNr = parse_number(daqListNr_field, comm_mode_basic) + tree:add(xcp_reserved, buffer(1, 1)) + tree:add(xcp_info, daqListNr_field, "DAQ List number: " .. daqListNr) +end + +function decode_cmd_set_daq_ptr(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local daqListNr_field = buffer(2, 2) + local daqListNr = parse_number(daqListNr_field, comm_mode_basic) + local odtNr_field = buffer(4, 1) + local odtNr = odtNr_field:uint() + local odtEntryNr_field = buffer(5, 1) + local odtEntryNr = odtEntryNr_field:uint() + tree:add(xcp_reserved, buffer(1, 1), "Reserved") + tree:add(xcp_info, daqListNr_field, "DAQ list number: " .. daqListNr) + tree:add(xcp_info, odtNr_field, "ODT number (relative): " .. odtNr) + tree:add(xcp_info, odtEntryNr_field, "ODT entry number (relative): " .. odtEntryNr) +end + +function decode_cmd_write_daq(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local address_granularity = band(rshift(comm_mode_basic, 1), 0x3) + local bitOffset_field = buffer(1, 1) + local bitOffset = bitOffset_field:uint() + local size_field = buffer(2, 1) + local size = size_field:uint() + local addrExt_field = buffer(3, 1) + local addrExt = addrExt_field:uint() + local addr_field = buffer(4, 4) + local addr = parse_number(addr_field, comm_mode_basic) + + if bitOffset == 0xff then + tree:add(xcp_info, bitOffset_field, "whole data element (0xff)") + else + tree:add(xcp_info, bitOffset_field, "Bit offset: " .. hex(bitOffset, 2)) + if bitOffset <= 0x1f then + local bitMask = lshift(1, bitOffset) + tree:add(xcp_info, bitOffset_field, "Bit mask: " .. hex(bitMask, 8)) + end + end + + tree:add(xcp_info, size_field, "Size: " .. size .. " " .. AG_TYPES[address_granularity]) + + tree:add(xcp_info, addrExt_field, "Address extension: " .. hex(addrExt, 2)) + tree:add(xcp_info, addr_field, "Address: " .. hex(addr, 8)) +end + +function decode_cmd_set_daq_list_mode(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local mode_field = buffer(1, 1) + local mode = mode_field:uint() + local daqListNr_field = buffer(2, 2) + local daqListNr = parse_number(daqListNr_field, comm_mode_basic) + local eventChannelNr_field = buffer(4, 2) + local eventChannelNr = parse_number(eventChannelNr_field, comm_mode_basic) + local prescaler_field = buffer(6, 1) + local prescaler = prescaler_field:uint() + local prio_field = buffer(7, 1) + local prio = prio_field:uint() + + local mode_string = hex(mode, 2) .. " -- " + if band(mode, 0x1) == 0 then + mode_string = mode_string .. "Alternating: off, " + else + mode_string = mode_string .. "Alternating: on, " + end + if band(mode, 0x2) == 0 then + mode_string = mode_string .. "Direction: DAQ, " + else + mode_string = mode_string .. "Direction: STIM, " + end + if band(mode, 0x8) == 0 then + mode_string = mode_string .. "DTO_CTR: not used, " + pkt_data.DTO_CTR = { table.unpack(find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "DTO_CTR", {})) } + pkt_data.DTO_CTR[daqListNr] = false + else + mode_string = mode_string .. "DTO_CTR: in use, " + pkt_data.DTO_CTR = { table.unpack(find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "DTO_CTR", {})) } + pkt_data.DTO_CTR[daqListNr] = true + end + if band(mode, 0x10) == 0 then + mode_string = mode_string .. "Timestamp: disabled, " + pkt_data.TIMESTAMP_ENABLED = false + else + mode_string = mode_string .. "Timestamp: enabled, " + pkt_data.TIMESTAMP_ENABLED = true + end + if band(mode, 0x20) == 0 then + mode_string = mode_string .. "PID_OFF: disabled - DTO with identification field" + else + mode_string = mode_string .. "PID_OFF: enabled - DTO without identification field" + end + + tree:add(xcp_info, mode_field, mode_string) + tree:add(xcp_info, daqListNr_field, "DAQ List #" .. daqListNr) + tree:add(xcp_info, eventChannelNr_field, "Event Channel #" .. eventChannelNr) + tree:add(xcp_info, prescaler_field, "Prescaler: " .. prescaler) + tree:add(xcp_info, prio_field, "Priority: " .. prio) +end + +function decode_cmd_start_stop_daq_list(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local mode_field = buffer(1, 1) + local mode = mode_field:uint() + local daqListNr_field = buffer(2, 2) + local daqListNr = parse_number(daqListNr_field, comm_mode_basic) + pkt_data.DAQ_LIST_NR = daqListNr + pkt_data.DAQ_RUNNING = { table.unpack(find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "DAQ_RUNNING", {})) } + pkt_data.DAQ_SELECTED = { table.unpack(find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "DAQ_RUNNING", {})) } + if mode == 0 then + tree:add(xcp_info, mode_field, "Mode: stop (0)") + pkt_data.DAQ_RUNNING[daqListNr] = false + elseif mode == 1 then + tree:add(xcp_info, mode_field, "Mode: start (1)") + pkt_data.DAQ_RUNNING[daqListNr] = true + elseif mode == 2 then + tree:add(xcp_info, mode_field, "Mode: select (2)") + pkt_data.DAQ_SELECTED[daqListNr] = true + else + tree:add(xcp_info, mode_field, "Mode: unknown (" .. hex(mode, 2) .. ")") + end + tree:add(xcp_info, daqListNr_field, "DAQ List #" .. daqListNr) +end + +function decode_cmd_start_stop_synch(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local mode_field = buffer(1, 1) + local mode = mode_field:uint() + pkt_data.DAQ_RUNNING = { table.unpack(find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "DAQ_RUNNING", {})) } + pkt_data.DAQ_SELECTED = { table.unpack(find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "DAQ_RUNNING", {})) } + if mode == 0 then + tree:add(xcp_info, mode_field, "Mode: stop all (0)") + pkt_data.DAQ_RUNNING = {} + pkt_data.DAQ_SELECTED = {} + elseif mode == 1 then + tree:add(xcp_info, mode_field, "Mode: start selected (1)") + pkt_data.DAQ_RUNNING = pkt_data.DAQ_SELECTED + pkt_data.DAQ_SELECTED = {} + elseif mode == 2 then + tree:add(xcp_info, mode_field, "Mode: stop selected (2)") + for daq, flag in pairs(pkt_data.DAQ_SELECTED) do + pkt_data.DAQ_RUNNING[daq] = false + end + pkt_data.DAQ_SELECTED = {} + else + tree:add(xcp_info, mode_field, "Mode: unknown (" .. hex(mode, 2) .. ")") + end +end + +function decode_cmd_get_daq_event_info(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local eventChannelNr_field = buffer(2, 2) + local eventChannelNr = parse_number(eventChannelNr_field, comm_mode_basic) + tree:add(xcp_reserved, buffer(1, 1)) + tree:add(xcp_info, eventChannelNr_field, "Event channel number: " .. eventChannelNr) +end + +function decode_cmd_alloc_daq(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local daqCount_field = buffer(2, 2) + local daqCount = parse_number(daqCount_field, comm_mode_basic) + tree:add(xcp_reserved, buffer(1, 1)) + tree:add(xcp_info, daqCount_field, "DAQ_COUNT: " .. daqCount) +end + +function decode_cmd_alloc_odt(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local daqListNr_field = buffer(2, 2) + local daqListNr = parse_number(daqListNr_field, comm_mode_basic) + local odtCount_field = buffer(4, 1) + local odtCount = odtCount_field:uint() + tree:add(xcp_reserved, buffer(1, 1)) + tree:add(xcp_info, daqListNr_field, "DAQ list number: " .. daqListNr) + tree:add(xcp_info, odtCount_field, "ODT_COUNT: " .. odtCount) +end + +function decode_cmd_alloc_odt_entry(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local daqListNr_field = buffer(2, 2) + local daqListNr = parse_number(daqListNr_field, comm_mode_basic) + local odtNumber_field = buffer(4, 1) + local odtNumber = odtNumber_field:uint() + local odtEntriesCount_field = buffer(5, 1) + local odtEntriesCount = odtEntriesCount_field:uint() + tree:add(xcp_reserved, buffer(1, 1)) + tree:add(xcp_info, daqListNr_field, "DAQ list number: " .. daqListNr) + tree:add(xcp_info, odtNumber_field, "ODT number: " .. odtNumber) + tree:add(xcp_info, odtEntriesCount_field, "ODT_ENTRIES_COUNT: " .. odtEntriesCount) +end + +function decode_cmd_program_clear(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local mode_field = buffer(1, 1) + local mode = mode_field:uint() + local clearRange_field = buffer(4, 4) + local clearRange = parse_number(clearRange_field, comm_mode_basic) + if mode == 0 then + tree:add(xcp_info, mode_field, "Mode: absolute (0)") + tree:add(xcp_reserved, buffer(2, 2)) + tree:add(xcp_info, clearRange_field, "Range: " .. clearRange .. " Bytes starting at MTA") + elseif mode == 1 then + tree:add(xcp_info, mode_field, "Mode: functional (1)") + tree:add(xcp_reserved, buffer(2, 2)) + local clearString = "" + if band(clearRange, 1) ~= 0 then + clearString = clearString + "CALIB (1), " + end + if band(clearRange, 2) ~= 0 then + clearString = clearString + "CODE excluding BOOT (2), " + end + if band(clearRange, 4) ~= 0 then + clearString = clearString + "NVRAM (4), " + end + if band(clearRange, 0xf8) ~= 0 then + clearString = clearString + "RESERVED (" .. hex(band(clearRange, 0xf8), 2) .. "), " + end + if clearRange > 0x100 then + clearString = clearString + "user defined (" .. hex(band(clearRange, 0xffffff00), 8) .. "), " + end + if clearRange == 0 then + clearString = "NONE (0)" + else + clearString = clearString:sub(1, -3) + end + tree:add(xcp_info, clearRange_field, "Range: " .. clearString) + else + tree:add(xcp_info, mode_field, "Mode: unknown (" .. hex(mode, 2) .. ")") + tree:add(xcp_reserved, buffer(2, 2)) + tree:add(xcp_info, clearRange_field, "Range: " .. hex(clearRange, 8)) + end +end + +function decode_cmd_get_sector_info(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local mode_field = buffer(1, 1) + local mode = mode_field:uint() + local secNum_field = buffer(2, 1) + local secNum = secNum_field:uint() + local infoType = "unknown information about this sector" + if mode == 0 then + infoType = "start address of this sector" + elseif mode == 1 then + infoType = "length of this sector in bytes" + elseif mode == 2 then + infoType = "name length of this sector" + end + tree:add(xcp_info, mode_field, "Mode: Get " .. infoType) + tree:add(xcp_info, secNum_field, "Sector number: " .. secNum) + pkt_data.GET_SECTOR_INFO__MODE = mode +end + +function decode_cmd_program_prepare(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local address_granularity = band(rshift(comm_mode_basic, 1), 0x3) + local codesize_field = buffer(2, 2) + local codesize = parse_number(codesize_field, comm_mode_basic) + tree:add(xcp_reserved, buffer(1, 1), "Not used") + tree:add(xcp_info, codesize_field, "Code size: " .. codesize .. " " .. AG_TYPES[address_granularity]) +end + +function decode_cmd_program_format(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local compressionMethod_field = buffer(1, 1) + local compressionMethod = compressionMethod_field:uint() + local encryptionMethod_field = buffer(2, 1) + local encryptionMethod = encryptionMethod_field:uint() + local programmingMethod_field = buffer(3, 1) + local programmingMethod = programmingMethod_field:uint() + local accessMethod_field = buffer(4, 1) + local accessMethod = accessMethod_field:uint() + if compressionMethod == 0 then + tree:add(xcp_info, compressionMethod_field, "Uncompressed (0)") + elseif compressionMethod >= 0x80 then + tree:add(xcp_info, compressionMethod_field, + "User defined compression (" .. hex(compressionMethod, 2) + ")") + else + tree:add(xcp_info, compressionMethod_field, + "Invalid compression (" .. hex(compressionMethod, 2) + ")") + end + if encryptionMethod == 0 then + tree:add(xcp_info, encryptionMethod_field, "Not encrypted (0)") + elseif encryptionMethod >= 0x80 then + tree:add(xcp_info, encryptionMethod_field, + "User defined encryption (" .. hex(encryptionMethod, 2) + ")") + else + tree:add(xcp_info, encryptionMethod_field, + "Invalid encryption (" .. hex(encryptionMethod, 2) + ")") + end + if programmingMethod == 0 then + tree:add(xcp_info, programmingMethod_field, "Sequential programming (0)") + elseif programmingMethod >= 0x80 then + tree:add(xcp_info, programmingMethod_field, + "User defined programming mode (" .. hex(programmingMethod, 2) + ")") + else + tree:add(xcp_info, programmingMethod_field, + "Invalid programming mode (" .. hex(programmingMethod, 2) + ")") + end + if accessMethod == 0 then + tree:add(xcp_info, accessMethod_field, "Absolute access (0)") + elseif accessMethod == 1 then + tree:add(xcp_info, accessMethod_field, "Functional access (1)") + elseif accessMethod >= 0x80 then + tree:add(xcp_info, accessMethod_field, "User defined access mode (" .. hex(accessMethod, 2) + ")") + else + tree:add(xcp_info, accessMethod_field, "Invalid access mode (" .. hex(accessMethod, 2) + ")") + end +end + +function decode_cmd_program_verify(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local mode_field = buffer(1, 1) + local mode = mode_field:uint() + local type_field = buffer(2, 2) + local type = parse_number(type_field, comm_mode_basic) + local value_field = buffer(4, 4) + local value = parse_number(value_field, comm_mode_basic) + if mode == 0 then + tree:add(xcp_info, mode_field, "Verification mode: 0 (internal)") + tree:add(xcp_info, type_field, "Verification type: " .. hex(type, 4) .. " (ignored)") + tree:add(xcp_info, type_field, "Verification value: " .. hex(value, 8) .. " (ignored)") + elseif mode == 1 then + local typeString = hex(type, 4) .. " -- " + if band(type, 1) ~= 0 then + typeString = typeString .. "CALIB, " + end + if band(type, 2) ~= 0 then + typeString = typeString .. "CODE, " + end + if band(type, 4) ~= 0 then + typeString = typeString .. "FLASH, " + end + if band(type, 0xf8) ~= 0 then + typeString = typeString .. "reserved (" .. hex(band(type, 0xf8), 2) .. "), " + end + if band(type, 0xff00) ~= 0 then + typeString = typeString .. "user defined (" .. hex(band(type, 0xff00), 4) .. "), " + end + tree:add(xcp_info, mode_field, "Verification mode: 1 (value-based)") + tree:add(xcp_info, type_field, "Verification type: " .. string.sub(typeString, 1, -3)) + tree:add(xcp_info, type_field, "Verification value: " .. hex(value, 8)) + end +end + +function decode_cmd_write_daq_multiple(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local noDAQ_field = buffer(1, 1) + local noDAQ = noDAQ_field:uint() + tree:add(xcp_info, noDAQ_field, "Number of DAQ elements: " .. noDAQ) + for i = 1, noDAQ + 1, 1 do + local bitOffset_field = buffer((i - 1) * 8 + 2, 1) + local bitOffset = bitOffset_field:uint() + local size_field = buffer((i - 1) * 8 + 3, 1) + local size = size_field:uint() + local address_field = buffer((i - 1) * 8 + 4, 4) + local address = parse_number(address_field, comm_mode_basic) + local addrExt_field = buffer((i - 1) * 8 + 8, 1) + local addrExt = addrExt_field:uint() + local dummy_field = buffer((i - 1) * 8 + 9, 1) + local elem_tree = tree:add(xcp_info, buffer((i - 1) * 8 + 2, 8), "Element #" .. i) + elem_tree:add(xcp_info, bitOffset_field, "Bit offset: " .. bitOffset) + elem_tree:add(xcp_info, size_field, "Size: " .. size) + elem_tree:add(xcp_info, address_field, "Address: " .. hex(address, 8)) + elem_tree:add(xcp_info, addrExt_field, "Address Extension: " .. hex(addrExt, 2)) + elem_tree:add(xcp_reserved, dummy_field, "Dummy for alignment") + end +end + +function decode_cmd_time_correlation_properties(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local set_properties_field = buffer(1, 1) + local set_properties = set_properties_field:uint() + local get_properties_request_field = buffer(2, 1) + local get_properties_request = get_properties_request_field:uint() + local cluster_id_field = buffer(4, 2) + local cluster_id = parse_number(cluster_id_field, comm_mode_basic) + + local response_fmt = band(set_properties, 0x03) + local time_sync_bridge = rshift(band(set_properties, 0x0c), 2) + local set_cluster_id = band(set_properties, 0x10) ~= 0 + local unknown_set_properties_flags = band(set_properties, 0xe0) + + local get_clk_info = band(get_properties_request, 1) == 1 + local unknown_get_properties_flags = band(get_properties_request, 0xfe) + + if response_fmt ~= 0 then + pkt_data["TIME_SYNCHRONIZATION_PROPERTIES__extended"] = true + end + + if response_fmt == 0 then + tree:add(xcp_info, set_properties_field, "Response format: 0 (unchanged)") + elseif response_fmt == 1 then + tree:add(xcp_info, set_properties_field, "Response format: 1 (EV_TIME_SYNC on trigger initiator 0, 2, and 3 only)") + elseif response_fmt == 2 then + tree:add(xcp_info, set_properties_field, "Response format: 2 (EV_TIME_SYNC on all triggers)") + else + tree:add(xcp_info, set_properties_field, "Response format: 3 (reserved)") + end + + if time_sync_bridge == 0 then + tree:add(xcp_info, set_properties_field, "Time sync bridge: 0 (unchanged)") + elseif time_sync_bridge == 1 then + tree:add(xcp_info, set_properties_field, "Time sync bridge: 1 (enable)") + elseif time_sync_bridge == 2 then + tree:add(xcp_info, set_properties_field, "Time sync bridge: 2 (disable)") + else + tree:add(xcp_info, set_properties_field, "Time sync bridge: 3 (reserved)") + end + + if set_cluster_id then + tree:add(xcp_info, set_properties_field, "Assign to cluster") + else + tree:add(xcp_info, set_properties_field, "Do not change cluster assignment") + end + + if unknown_set_properties_flags ~= 0 then + tree:add(xcp_info, set_properties_field, + "Unknown extra set_properties flags: " .. hex(unknown_set_properties_flags, 2)) + end + + if get_clk_info then + tree:add(xcp_info, get_properties_request_field, "Clock info requested") + else + tree:add(xcp_info, get_properties_request_field, "Clock info not requested") + end + + if unknown_get_properties_flags ~= 0 then + tree:add(xcp_info, get_properties_request_field, + "Unknown extra get_properties flags: " .. hex(unknown_get_properties_flags, 2)) + end + + tree:add(xcp_reserved, buffer(3, 1), "Reserved") + + if set_cluster_id then + tree:add(xcp_info, cluster_id_field, "Cluster ID: " .. cluster_id) + else + tree:add(xcp_info, cluster_id_field, "Cluster ID: " .. cluster_id .. " (ignored)") + end +end + +function decode_cmd_dto_ctr_properties(buffer, pinfo, tree, pkt_data, comm_mode_basic) + local modifier_field = buffer(1, 1) + local modifier = modifier_field:uint() + local eventChannelNr_field = buffer(2, 2) + local eventChannelNr = parse_number(eventChannelNr_field, comm_mode_basic) + local relatedEventChannelNr_field = buffer(4, 2) + local relatedEventChannelNr = parse_number(relatedEventChannelNr_field, comm_mode_basic) + local mode_field = buffer(6, 1) + local mode = mode_field:uint() + local modify_relatedEvent = band(modifier, 1) ~= 0 + local modify_daqMode = band(modifier, 2) ~= 0 + local modify_stimMode = band(modifier, 4) ~= 0 + local unknown_modify_flags = band(modifier, 0xf8) + local daqMode = band(mode, 1) ~= 0 + local stimMode = band(mode, 2) ~= 0 + local unknown_mode_flags = band(mode, 0xfc) + local modifier_string = "" + if modify_relatedEvent then + modifier_string = modifier_string .. "modify related event (1), " + end + if modify_daqMode then + modifier_string = modifier_string .. "modify DAQ mode (2), " + end + if modify_stimMode then + modifier_string = modifier_string .. "modify STIM mode (4), " + end + if unknown_modify_flags ~= 0 then + modifier_string = modifier_string .. "unknown modifier (" .. hex(unknown_modify_flags, 2) .. "), " + end + modifier_string = string.sub(modifier_string, 1, -3) + local mode_string = "" + if daqMode then + mode_string = mode_string .. "Insert STIM counter copy (bit 0 set)" + else + mode_string = mode_string .. "Insert counter (bit 0 not set)" + end + if not modify_daqMode then + mode_string = mode_string .. " (ignored), " + else + mode_string = mode_string .. ", " + end + if stimMode then + mode_string = mode_string .. "check counter (bit 1 set)" + else + mode_string = mode_string .. "don't check counter (bit 1 not set)" + end + if not modify_stimMode then + mode_string = mode_string .. " (ignored), " + else + mode_string = mode_string .. ", " + end + if unknown_mode_flags ~= 0 then + mode_string = mode_string .. "unknown mode (" .. hex(unknown_mode_flags, 2) .. "), " + end + mode_string = string.sub(mode_string, 1, -3) + tree:add(xcp_info, modifier_field, modifier_string) + tree:add(xcp_info, eventChannelNr_field, "Event channel: " + eventChannelNr) + if modify_relatedEvent then + tree:add(xcp_info, relatedEventChannelNr_field, "Related event channel: " .. relatedEventChannelNr) + else + tree:add(xcp_info, relatedEventChannelNr_field, "Related event channel: " .. relatedEventChannelNr .. " (ignored)") + end + tree:add(xcp_info, mode_field, mode_string) +end + +CMD_DECODERS = { + [0xFF] = decode_cmd_connect, + [0xFA] = decode_cmd_get_id, + [0xF9] = decode_cmd_set_request, + [0xF8] = decode_cmd_get_seed, + [0xF7] = decode_cmd_unlock, + [0xF6] = decode_cmd_set_mta, + [0xF5] = decode_cmd_upload, + [0xF4] = decode_cmd_short_upload, + [0xF3] = decode_cmd_build_checksum, + [0xF2] = decode_cmd_transport_layer_cmd__user_cmd, + [0xF1] = decode_cmd_transport_layer_cmd__user_cmd, + [0xF0] = decode_cmd_download__program, + [0xEF] = decode_cmd_download__program, + [0xEE] = decode_cmd_download__program__max, + [0xED] = decode_cmd_short_download, + [0xEC] = decode_cmd_modify_bits, + [0xEB] = decode_cmd_set_cal_page, + [0xEA] = decode_cmd_get_cal_page, + [0xE8] = decode_cmd_get_segment_info, + [0xE7] = decode_cmd_get_page_info, + [0xE6] = decode_cmd_set_segment_mode, + [0xE5] = decode_cmd_get_segment_mode, + [0xE4] = decode_cmd_copy_cal_page, + [0xE3] = decode_cmd_clear_daq_list__get_daq_list_mode__info, + [0xE2] = decode_cmd_set_daq_ptr, + [0xE1] = decode_cmd_write_daq, + [0xE0] = decode_cmd_set_daq_list_mode, + [0xDF] = decode_cmd_clear_daq_list__get_daq_list_mode__info, + [0xDE] = decode_cmd_start_stop_daq_list, + [0xDD] = decode_cmd_start_stop_synch, + [0xD8] = decode_cmd_clear_daq_list__get_daq_list_mode__info, + [0xD7] = decode_cmd_get_daq_event_info, + [0xD5] = decode_cmd_alloc_daq, + [0xD4] = decode_cmd_alloc_odt, + [0xD3] = decode_cmd_alloc_odt_entry, + [0xD1] = decode_cmd_program_clear, + [0xD0] = decode_cmd_download__program, + [0xCD] = decode_cmd_get_sector_info, + [0xCC] = decode_cmd_program_prepare, + [0xCB] = decode_cmd_program_format, + [0xCA] = decode_cmd_download__program, + [0xC9] = decode_cmd_download__program__max, + [0xC8] = decode_cmd_program_verify, + [0xC7] = decode_cmd_write_daq_multiple, + [0xC6] = decode_cmd_time_correlation_properties, + [0xC5] = decode_cmd_dto_ctr_properties, +} + +function decode_cmd(buffer, pinfo, tree) + local comm_mode_basic = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "COMM_MODE_BASIC", 0) + local pkt_data = {} + conversations[pinfo.private.xcp_conversation][pinfo.number] = pkt_data + local pid_field = buffer(0, 1) + local pid = pid_field:uint() + local cmd_name = COMMAND_NAMES[pid] + if cmd_name then + pinfo.cols.info:set("XCP command: " .. cmd_name) + else + pinfo.cols.info:set("Unknown XCP command: " .. hex(pid, 2)) + end + + + -- store the PID so decode_res and decode_err can find it + pkt_data["CMD"] = pid + pkt_data["pinfo.number"] = pinfo.number + tree:add(xcp_type, pid_field, "Command") + tree:add(xcp_cmd, pid_field, pid) + + local decoder = CMD_DECODERS[pid] + if decoder then + decoder(buffer, pinfo, tree, pkt_data, comm_mode_basic) + end +end + +function decode_res_connect(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local resource_field = buffer(1, 1) + local resource = resource_field:uint() + local comm_mode_basic_field = buffer(2, 1) + comm_mode_basic = comm_mode_basic_field:uint() + local max_cto_field = buffer(3, 1) + local max_cto = max_cto_field:uint() + local max_dto_field = buffer(4, 2) + local max_dto = parse_number(max_dto_field, comm_mode_basic) + local proto_version_field = buffer(6, 1) + local proto_version = proto_version_field:uint() + local transport_version_field = buffer(7, 1) + local transport_version = proto_version_field:uint() + cmd_pkt["RESOURCE"] = resource + cmd_pkt["COMM_MODE_BASIC"] = comm_mode_basic + cmd_pkt["MAX_CTO"] = max_cto + cmd_pkt["MAX_DTO"] = max_dto + local info_string = "resource: " + if band(resource, 0x1) == 0 then + info_string = info_string .. "CAL/PAG unavailable (bit 0 not set), " + else + info_string = info_string .. "CAL/PAG available (bit 0 set), " + end + if band(resource, 0x4) == 0 then + info_string = info_string .. "DAQ unavailable (bit 2 not set), " + else + info_string = info_string .. "DAQ available (bit 2 set), " + end + if band(resource, 0x8) == 0 then + info_string = info_string .. "STIM unavailable (bit 3 not set), " + else + info_string = info_string .. "STIM available (bit 3 set), " + end + if band(resource, 0x10) == 0 then + info_string = info_string .. "PGM unavailable (bit 4 not set)" + else + info_string = info_string .. "PGM available (bit 4 set)" + end + if band(resource, 0xe2) ~= 0 then + info_string = info_string .. ", unknown RESOURCE flags: (" .. hex(resource, 2) .. ")" + end + tree:add(xcp_info, resource_field, info_string) + info_string = "comm_mode_basic: " + if band(comm_mode_basic, 0x01) == 0 then + info_string = info_string .. "little endian, " + else + info_string = info_string .. "big endian, " + end + local address_granularity = band(rshift(comm_mode_basic, 1), 0x3) + if address_granularity == 0 then + info_string = info_string .. "address granularity BYTE (0)" + elseif address_granularity == 1 then + info_string = info_string .. "address granularity WORD (1)" + elseif address_granularity == 2 then + info_string = info_string .. "address granularity DWORD (2)" + else + info_string = info_string .. "address granularity 3 (reserved)" + end + if band(comm_mode_basic, 0x40) == 0x40 then + info_string = info_string .. ", blockMode available" + end + if band(comm_mode_basic, 0x80) == 0x80 then + info_string = info_string .. ", GET_COMM_MODE_INFO available" + end + if band(comm_mode_basic, 0x38) ~= 0 then + info_string = info_string .. ", unknown COMM_MODE_BASIC flags (" .. hex(resource, 2) .. ")" + end + tree:add(xcp_info, comm_mode_basic_field, info_string) + tree:add(xcp_info, max_cto_field, "max_cto: " .. max_cto) + tree:add(xcp_info, max_dto_field, "max_dto: " .. max_dto) + tree:add(xcp_info, proto_version_field, "proto_version: " .. proto_version) + tree:add(xcp_info, transport_version_field, "transport_version: " .. transport_version) +end + +function decode_res_get_status(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local status_field = buffer(1, 1) + local status = status_field:uint() + local protection_field = buffer(2, 1) + local protection = protection_field:uint() + local state_number_field = buffer(3, 1) + local state_number = state_number_field:uint() + local session_configuration_id_field = buffer(4, 2) + local session_configuration_id = parse_number(session_configuration_id_field, comm_mode_basic) + local info_string = "Session status: " + if band(status, 0xcd) == 0 then + info_string = info_string .. "0 " + end + if band(status, 0x1) ~= 0 then + info_string = info_string .. "STORE_CAL_REQ, " + end + if band(status, 0x4) ~= 0 then + info_string = info_string .. "STORE_DAQ_REQ, " + end + if band(status, 0x8) ~= 0 then + info_string = info_string .. "CLEAR_DAQ_REQ, " + end + if band(status, 0x40) ~= 0 then + info_string = info_string .. "DAQ_RUNNING, " + end + if band(status, 0x80) ~= 0 then + info_string = info_string .. "RESUME, " + end + if band(status, 0x32) ~= 0 then + info_string = info_string .. "unknown status flags (" .. hex(band(status, 0x32), 2) .. "), " + end + tree:add(xcp_info, status_field, info_string:sub(1, -3)) + info_string = "Protection status: " + if band(protection, 0x1d) == 0 then + info_string = info_string .. "0 " + end + if band(protection, 0x1) ~= 0 then + info_string = info_string .. "CAL/PAG, " + end + if band(protection, 0x4) ~= 0 then + info_string = info_string .. "DAQ, " + end + if band(protection, 0x8) ~= 0 then + info_string = info_string .. "STIM, " + end + if band(protection, 0x10) ~= 0 then + info_string = info_string .. "PGM, " + end + if band(protection, 0xe2) ~= 0 then + info_string = info_string .. + "unknown protection flags (" .. hex(band(protection, 0xe2), 2) .. "), " + end + tree:add(xcp_info, protection_field, info_string:sub(0, -3)) + tree:add(xcp_info, state_number_field, "state number: " .. state_number) + tree:add(xcp_info, session_configuration_id_field, "session configuration id: " .. session_configuration_id) +end + +function decode_res_get_comm_mode_info(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local optional_field = buffer(2, 1) + local optional = optional_field:uint() + local max_bs_field = buffer(4, 1) + local max_bs = max_bs_field:uint() + local min_st_field = buffer(5, 1) + local min_st = min_st_field:uint() + local queue_size_field = buffer(6, 1) + local queue_size = queue_size_field:uint() + local drv_version_field = buffer(7, 1) + local drv_version = drv_version_field:uint() + local info_string = "optional: " + if band(optional, 0x03) == 0 then + info_string = info_string .. "0 " + end + if band(optional, 0x01) ~= 0 then + info_string = info_string .. "MASTER_BLOCK_MODE, " + end + if band(optional, 0x02) ~= 0 then + info_string = info_string .. "INTERLEAVED_MODE, " + end + if band(optional, 0xfc) ~= 0 then + info_string = info_string .. "unknown OPTIONAL flags (" .. hex(band(optional, 0xfc), 2) .. "), " + end + tree:add(xcp_reserved, buffer(1, 1)) + tree:add(xcp_info, optional_field, info_string:sub(0, -3)) + tree:add(xcp_reserved, buffer(3, 1)) + tree:add(xcp_info, max_bs_field, "MAX_BS: " .. max_bs) + tree:add(xcp_info, min_st_field, "MIN_ST: " .. min_st) + tree:add(xcp_info, queue_size_field, "queue size: " .. queue_size) + tree:add(xcp_info, drv_version_field, "XCP driver version: " .. drv_version) +end + +function decode_res_get_id(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local mode_field = buffer(1, 1) + local mode = mode_field:uint() + local length_field = buffer(4, 4) + local length = parse_number(length_field, comm_mode_basic) + local transfer_mode = band(mode, 1) ~= 0 + local compressed_encrypted = band(mode, 2) ~= 0 + local unknown_mode_flags = band(mode, 0xfc) + if transfer_mode then + tree:add(xcp_info, mode_field, "Transfer mode: immediate") + else + tree:add(xcp_info, mode_field, "Transfer mode: UPLOAD") + end + if compressed_encrypted then + tree:add(xcp_info, mode_field, "Compressed and/or encrypted") + else + tree:add(xcp_info, mode_field, "Plain text") + end + if unknown_mode_flags ~= 0 then + tree:add(xcp_info, mode_field, "Unknown mode flags (" .. hex(unknown_mode_flags, 2) .. ")") + end + tree:add(xcp_reserved, buffer(2, 2)) + tree:add(xcp_info, length_field, "Length in Bytes: " .. length) + if transfer_mode and length > 0 then + local data_field = buffer(8) + local data = data_field:bytes():raw() + tree:add(xcp_data, data_field, data) + end +end + +function decode_res_set_request(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + cmd_pkt.DAQ_SELECTED = {} +end + +function decode_res_get_seed(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local mode = cmd_pkt.GET_SEED__MODE + local length_field = buffer(1, 1) + local length = length_field:uint() + local seed_field = buffer(2) + local seed = seed_field:bytes():raw() + if length == 0 then + tree:add(xcp_info, length_field, "Resource is unprotected") + else + if mode == 0 then + tree:add(xcp_info, length_field, "Total length: " .. length) + elseif mode == 1 then + tree:add(xcp_info, length_field, "Remaining length: " .. length) + else + tree:add(xcp_info, "Unknown mode: " .. hex(mode, 2)):set_generated() + end + tree:add(xcp_data, seed_field, seed) + end +end + +function decode_res_unlock(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local protection_field = buffer(1, 1) + local protection = protection_field:uint() + local info_string = "Protection status: " + if band(protection, 0x1d) == 0 then + info_string = info_string .. "0 " + end + if band(protection, 0x1) ~= 0 then + info_string = info_string .. "CAL/PAG, " + end + if band(protection, 0x4) ~= 0 then + info_string = info_string .. "DAQ, " + end + if band(protection, 0x8) ~= 0 then + info_string = info_string .. "STIM, " + end + if band(protection, 0x10) ~= 0 then + info_string = info_string .. "PGM, " + end + if band(protection, 0xe2) ~= 0 then + info_string = info_string .. + "unknown protection flags (" .. hex(band(protection, 0xe2), 2) .. "), " + end + tree:add(xcp_info, protection_field, info_string:sub(0, -3)) +end + +function decode_res_upload(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local address_granularity = band(rshift(comm_mode_basic, 1), 0x3) + local offset = 1 + if address_granularity == 1 then + offset = 2 + tree:add(xcp_reserved, buffer(1, 1), "WORD Alignment") + elseif address_granularity == 3 then + offset = 4 + tree:add(xcp_reserved, buffer(1, 3), "DWORD Alignment") + end + local nElem = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "N_ELEM", nil) + local data_field + if nElem == nil then + -- Interpret the rest of the entire packet as data field if N_ELEM was not found + -- in the previous cmd_upload packet + data_field = buffer(offset) + else + data_field = buffer(offset, nElem * 2^address_granularity) + end + local data = data_field:bytes():raw() + tree:add(xcp_data, data_field, data) +end + +function decode_res_build_checksum(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local checksumType_field = buffer(1, 1) + local checksumType = checksumType_field:uint() + local checksum_field = buffer(4, 4) + local checksum = parse_number(checksum_field, comm_mode_basic) + local checksumType_string = "Unknown (" .. hex(checksumType, 2) .. ")" + if checksumType <= 9 and checksumType > 0 then + local checksumTypes = { "ADD_11", "ADD_12", "ADD_14", "ADD_22", "ADD_24", "ADD_44", "CRC_16", "CRC_16_CITT", + "CRC_32" } + checksumType_string = checksumTypes[checksumType] + elseif checksumType == 0xff then + checksumType_string = "User defined (0xff)" + end + tree:add(xcp_info, checksumType_field, "Checksum type: " .. checksumType_string) + tree:add(xcp_reserved, buffer(2, 2)) + tree.add(xcp_info, checksum_field, "Checksum: " .. hex(checksum, 8)) +end + +function decode_res_transport_layer_cmd__user_cmd(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local data_field = buffer(1) + local data = data_field:bytes():raw() + tree:add(xcp_data, data_field, data) +end + +function decode_res_get_cal_page(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local pageNum_field = buffer(3, 1) + local pageNum = pageNum_field:uint() + tree:add(xcp_reserved, buffer(1, 1)) + tree:add(xcp_reserved, buffer(2, 1)) + tree:add(xcp_info, pageNum_field, "Logical data page number: " .. pageNum) +end + +function decode_res_get_pag_processor_info(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local maxSegment_field = buffer(1, 1) + local maxSegment = maxSegment_field:uint() + local pagProperties_field = buffer(2, 1) + local pagProperties = pagProperties_field:uint() + tree:add(xcp_info, maxSegment_field, "Number of segments: " .. maxSegment) + if band(pagProperties, 1) == 1 then + tree:add(xcp_info, pagProperties_field, "FREEZE supported") + else + tree:add(xcp_info, pagProperties_field, "FREEZE not supported") + end + if band(pagProperties, 0xfe) ~= 0 then + tree:add(xcp_info, pagProperties_field, "Unknown PAG_PROPERTIES flags: " .. hex(band(pagProperties, 0xfe), 2)) + end +end + +function decode_res_get_segment_info(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local mode = cmd_pkt.GET_SEGMENT_INFO__MODE + local segInfo = cmd_pkt.GET_SEGMENT_INFO__SEGMENT_INFO + tree:add(xcp_info, "Mode: " .. mode):set_generated() + tree:add(xcp_info, "Segment Info: " .. segInfo):set_generated() + if mode == 0 then + local basicInfo_field = buffer(4, 4) + local basicInfo = parse_number(basicInfo_field, comm_mode_basic) + tree:add(xcp_reserved, buffer(1, 1)) + tree:add(xcp_reserved, buffer(2, 2)) + if segInfo == 0 then + tree:add(xcp_info, basicInfo_field, "Address: " .. hex(basicInfo, 8)) + elseif segInfo == 1 then + tree:add(xcp_info, basicInfo_field, "Length: " .. basicInfo) + else + tree:add(xcp_info, basicInfo_field, "Unknown info (type " .. hex(segInfo) .. "): " .. hex(basicInfo, 8)) + end + elseif mode == 1 then + local maxPages_field = buffer(1, 1) + local maxPages = maxPages_field:uint() + local addrExt_field = buffer(2, 1) + local addrExt = addrExt_field:uint() + local maxMapping_field = buffer(3, 1) + local maxMapping = maxMapping_field:uint() + local compressionMethod_field = buffer(4, 1) + local compressionMethod = compressionMethod_field:uint() + local encryptionMethod_field = buffer(5, 1) + local encryptionMethod = encryptionMethod_field:uint() + tree:add(xcp_info, maxPages_field, "Max pages: " .. maxPages) + tree:add(xcp_info, addrExt_field, "Address extension: " .. addrExt) + tree:add(xcp_info, maxMapping_field, "Max mapped ranges: " .. maxMapping) + tree:add(xcp_info, compressionMethod_field, "Compression method: " .. hex(compressionMethod, 2)) + tree:add(xcp_info, encryptionMethod_field, "Encryption method: " .. hex(encryptionMethod, 2)) + elseif mode == 2 then + local mappingInfo_field = buffer(4, 4) + local mappingInfo = parse_number(mappingInfo_field, comm_mode_basic) + tree:add(xcp_reserved, buffer(1, 1)) + tree:add(xcp_reserved, buffer(2, 2)) + if segInfo == 0 then + tree:add(xcp_info, mappingInfo_field, "Source address: " .. hex(mappingInfo, 8)) + elseif segInfo == 1 then + tree:add(xcp_info, mappingInfo_field, "Destination address: " .. hex(mappingInfo, 8)) + elseif segInfo == 2 then + tree:add(xcp_info, mappingInfo_field, "Length: " .. mappingInfo) + else + tree:add(xcp_info, mappingInfo_field, + "Unknown info (type " .. hex(segInfo) .. "): " .. hex(mappingInfo_field, 8)) + end + else + tree:add(xcp_info, "Unknown Mode: " .. hex(mode, 2)):set_generated() + tree:add(xcp_data, buffer(1), buffer(1):bytes():raw()) + end +end + +function decode_res_get_page_info(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local pageProperties_field = buffer(1, 1) + local pageProperties = pageProperties_field:uint() + local initSegment_field = buffer(2, 1) + local initSegment = initSegment_field:uint() + local unknown_props = band(pageProperties, 0xc0) + local propsString = "Page properties: " + if band(pageProperties, 1) ~= 0 then + propsString = propsString .. "ECU_ACCESS_WITHOUT_XCP, " + end + if band(pageProperties, 2) ~= 0 then + propsString = propsString .. "ECU_ACCESS_WITH_XCP, " + end + if band(pageProperties, 4) ~= 0 then + propsString = propsString .. "XCP_READ_ACCESS_WITHOUT_ECU, " + end + if band(pageProperties, 8) ~= 0 then + propsString = propsString .. "XCP_READ_ACCESS_WITH_ECU, " + end + if band(pageProperties, 16) ~= 0 then + propsString = propsString .. "XCP_WRITE_ACCESS_WITHOUT_ECU, " + end + if band(pageProperties, 32) ~= 0 then + propsString = propsString .. "XCP_WRITE_ACCESS_WITH_ECU, " + end + if unknown_props ~= 0 then + propsString = propsString .. "UNKNOWN (" .. hex(unknown_props, 2) .. "), " + end + propsString = string.sub(propsString, 1, -3) + tree:add(xcp_info, pageProperties_field, propsString) + tree:add(xcp_info, initSegment_field, "Init Segment: " .. initSegment) +end + +function decode_res_get_segment_mode(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local mode_field = buffer(2, 1) + local mode = mode_field:uint() + local freeze = band(mode, 1) ~= 0 + local unknown_mode_flags = band(mode, 0xfe) + tree:add(xcp_reserved, buffer(1, 1)) + if freeze then + buffer:add(xcp_info, mode_field, "Mode: FREEZE") + elseif unknown_mode_flags == 0 then + buffer:add(xcp_info, mode_field, "Mode: 0") + end + if unknown_mode_flags ~= 0 then + buffer:add(xcp_info, mode_field, "Mode: unknown (" .. hex(unknown_mode_flags, 2) .. ")") + end +end + +function decode_res_get_daq_list_mode(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local mode_field = buffer(1, 1) + local mode = mode_field:uint() + local eventChannelNr_field = buffer(4, 2) + local eventChannelNr = parse_number(eventChannelNr_field, comm_mode_basic) + local prescaler_field = buffer(6, 1) + local prescaler = prescaler_field:uint() + local prio_field = buffer(7, 1) + local prio = prio_field:uint() + local modeString = "Current mode: " + if band(mode, 1) ~= 0 then + modeString = modeString .. "SELECTED, " + end + if band(mode, 2) ~= 0 then + modeString = modeString .. "direction=STIM, " + else + modeString = modeString .. "direction=DAQ, " + end + if band(mode, 4) ~= 0 then + modeString = modeString .. "UNKNOWN (0x04), " + end + if band(mode, 8) ~= 0 then + modeString = modeString .. "DTO_CTR, " + end + if band(mode, 16) ~= 0 then + modeString = modeString .. "TIMESTAMP, " + cmd_pkt.TIMESTAMP_ENABLED = true + else + cmd_pkt.TIMESTAMP_ENABLED = false + end + if band(mode, 32) ~= 0 then + modeString = modeString .. "PID_OFF, " + end + if band(mode, 64) ~= 0 then + modeString = modeString .. "RUNNING, " + end + if band(mode, 128) ~= 0 then + modeString = modeString .. "RESUME, " + end + modeString = string.sub(modeString, 1, -3) + tree:add(xcp_info, mode_field, modeString) + tree:add(xcp_reserved, buffer(2, 2)) + tree:add(xcp_info, eventChannelNr_field, "Current event channel number: " .. eventChannelNr) + tree:add(xcp_info, prescaler_field, "Current prescaler: " .. prescaler) + tree:add(xcp_info, prio_field, "Current DAQ list priority: " .. prio) +end + +function decode_res_start_stop_daq_list(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local firstPid_field = buffer(1, 1) + local firstPid = firstPid_field:uint() + tree:add(xcp_info, firstPid_field, "First PID: " .. firstPid) + cmd_pkt.FIRST_PID = { table.unpack(find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "FIRST_PID", {})) } + cmd_pkt.FIRST_PID[cmd_pkt.DAQ_LIST_NR] = firstPid +end + +function decode_res_get_daq_clock__ev_time_sync(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local extended_format = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, + "TIME_SYNCHRONIZATION_PROPERTIES__extended") + local max_cto = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "MAX_CTO") + + local isResponse = buffer(0, 1).uint() == + 0xff -- pos. response for 0xdc GET_DAQ_CLOCK; otherwise event 0x08 EV_TIME_SYNC + if isResponse then + if max_cto == 8 then + extended_format = false -- unlike the event, the response does not have a special format for extended=true, max_cto=8 + end + tree:add(xcp_reserved, buffer(1, 1)) + end + + local triggerInfo_field = buffer(2, 1) + local triggerInfo = triggerInfo_field:uint() + local triggerInfo_string = "Trigger info: " + local timeOfSampling = band(rshift(triggerInfo, 3), 0x3) + local unknown_triggerInfo = band(triggerInfo, 0xe0) + local triggerInitiator = band(triggerInfo, 0x7) + local initiators = { + [0] = "HW trigger", + "external time sync event", + "GET_DAQ_CLOCK_MULTICAST (direct)", + "GET_DAQ_CLOCK_MULTICAST (bridged)", + "SYNC_STATE changed", + "leap second", + "ECU reset", + "reserved (7)" + } + local initiatorString = "Trigger initiator: " .. initiators[triggerInitiator] + if isResponse then + if triggerInitiator ~= 0 then + tree:add(xcp_info, triggerInfo_field, initiatorString .. " (ignored)") + end + else + if extended_format then + tree:add(xcp_info, triggerInfo_field, initiatorString) + else + tree:add(xcp_reserved, triggerInfo_field) + end + end + if extended_format or isResponse then + local samplingPoints = { [0] = "processing", "low jitter", "TX", "RX" } + local samplingPointString = "Time of sampling: " .. samplingPoints[timeOfSampling] + tree:add(xcp_info, triggerInfo_field, samplingPointString) + if unknown_triggerInfo ~= 0 then + tree:add(xcp_info, triggerInfo_field, "Unknown flags in TRIGGER_INFO: " .. hex(unknown_triggerInfo, 2)) + end + end + + local payloadFmt_mcastClkCounter_field = buffer(3, 1) + local payloadFmt = payloadFmt_mcastClkCounter_field:uint() + local fmt_xcp_slv = band(payloadFmt, 3) + local fmt_grandm = band(rshift(payloadFmt, 2), 3) + local fmt_ecu = band(rshift(payloadFmt, 4), 3) + local cluster_identifier_present = band(rshift(payloadFmt, 6), 1) + local unknownFmtFlag = rshift(payloadFmt, 7) ~= 0 + local daqClockMulticastCounter = payloadFmt_mcastClkCounter_field:uint() + + if extended_format then + if max_cto == 8 then + local timestamp_field = buffer(4, 4) + local timestamp = parse_number(timestamp_field, comm_mode_basic) + tree:add(xcp_info, payloadFmt_mcastClkCounter_field, "DAQ clock multicast counter: " .. daqClockMulticastCounter) + tree:add(xcp_info, timestamp_field, "Timestamp: " .. timestamp) + else + local offset = 4 + local bad_format = 0 + local formats = { "DWORD", "DLONG" } + local slaveClock_field = nil + local slaveClock = nil + local gramdmasterClock_field = nil + local gramdmasterClock = nil + local ecuClock_field = nil + local ecuClock = nil + + if fmt_xcp_slv == 3 then + tree:add(xcp_info, payloadFmt_mcastClkCounter_field, "Unknown XCP slave clock timestamp format: 3") + bad_format = offset + elseif fmt_xcp_slv > 0 then + slaveClock_field = buffer(offset, fmt_xcp_slv * 4) + slaveClock = parse_number(slaveClock_field) + offset = offset + fmt_xcp_slv * 4 + tree:add(xcp_info, payloadFmt_mcastClkCounter_field, "XCP slave clock timestamp format: " .. formats + [fmt_xcp_slv]) + else + tree:add(xcp_info, payloadFmt_mcastClkCounter_field, "XCP slave clock timestamp format: absent") + end + if fmt_grandm == 3 then + tree:add(xcp_info, payloadFmt_mcastClkCounter_field, "Unknown Grandmaster-synced clock timestamp format: 3") + bad_format = offset + elseif fmt_grandm > 0 then + gramdmasterClock_field = buffer(offset, fmt_grandm * 4) + gramdmasterClock = parse_number(gramdmasterClock_field) + offset = offset + fmt_grandm * 4 + tree:add(xcp_info, payloadFmt_mcastClkCounter_field, + "Grandmaster-synced clock timestamp format: " .. formats[fmt_grandm]) + else + tree:add(xcp_info, payloadFmt_mcastClkCounter_field, "Grandmaster-synced clock format: timestamp absent") + end + if fmt_ecu == 3 then + tree:add(xcp_info, payloadFmt_mcastClkCounter_field, "Unknown ECU clock timestamp format: 3") + bad_format = offset + elseif fmt_ecu > 0 then + ecuClock_field = buffer(offset, fmt_ecu * 4) + ecuClock = parse_number(ecuClock_field) + offset = offset + fmt_ecu * 4 + tree:add(xcp_info, payloadFmt_mcastClkCounter_field, "ECU clock timestamp format: " .. formats[fmt_ecu]) + else + tree:add(xcp_info, payloadFmt_mcastClkCounter_field, "ECU clock timestamp format: absent") + end + if cluster_identifier_present then + tree:add(xcp_info, payloadFmt_mcastClkCounter_field, "Cluster identifier present") + else + tree:add(xcp_info, payloadFmt_mcastClkCounter_field, "Cluster identifier absent") + end + if unknownFmtFlag then + tree:add(xcp_info, payloadFmt_mcastClkCounter_field, "Unknown payload format flag: 0x80") + end + if slaveClock_field then + tree:add(xcp_info, slaveClock_field, "XCP slave clock timestamp: " .. tostring(slaveClock)) + end + if gramdmasterClock_field then + tree:add(xcp_info, gramdmasterClock_field, "Grandmaster-synced clock timestamp: " .. tostring(gramdmasterClock)) + end + if ecuClock_field then + tree:add(xcp_info, ecuClock_field, "ECU clock timestamp: " .. tostring(ecuClock)) + end + if bad_format == offset then + tree:add(xcp_info, buffer(bad_format), + "Bad payload format specifier (see above), can't parse the rest of the payload") + return + end + if cluster_identifier_present then + local clusterIdentifier_field = buffer(offset, 2) + local clusterIdentifier = parse_number(clusterIdentifier_field, comm_mode_basic) + offset = offset + 2 + local daqClockMulticastCounter_field = buffer(offset, 1) + daqClockMulticastCounter = daqClockMulticastCounter_field:uint() + offset = offset + 1 + tree:add(xcp_info, clusterIdentifier_field, "Cluster identifier: " .. hex(clusterIdentifier, 4)) + tree:add(xcp_info, daqClockMulticastCounter_field, "DAQ clock multicast counter: " .. daqClockMulticastCounter) + end + if buffer:len() > offset then + local syncState_field = buffer(offset, 1) + local syncState = syncState_field:uint() + local slaveSyncState = band(syncState, 7) + local slaveSyncStates = { + [0] = "synchronizing", + "synchronized", + "syntonizing", + "syntonized", + "reserved (4)", + "reserved (5)", + "reserved (6)", + "not supported" + } + local gmSyncState = band(rshift(syncState, 3), 1) + local gmSyncStates = { [0] = "not yet synchronized", "synchronized" } + local ecuSyncState = band(rshift(syncState, 4), 3) + local ecuSyncStates = { [0] = "not synchronized", "synchronized", "unknown", "reserved (3)" } + local unknownSyncBits = band(syncState, 0xc0) + local slaveClockObservability = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, + "TIME_CORRELATION_PROPERTIES__slaveClock", 0) + local gramdmasterClockObservability = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, + "TIME_CORRELATION_PROPERTIES__gmClock", 0) + local ecuClockObservability = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, + "TIME_CORRELATION_PROPERTIES__ecuClock", 0) + local node = tree:add(xcp_info, syncState_field, "Slave clock sync state: " .. slaveSyncStates[slaveSyncState]) + if slaveClockObservability ~= 1 then + node:append_text(" (ignored)") + end + node = tree:add(xcp_info, syncState_field, "Gramdmaster-synced clock sync state: " .. gmSyncStates[gmSyncState]) + if gramdmasterClockObservability == 0 then + node:append_text(" (ignored)") + end + node = tree:add(xcp_info, syncState_field, "ECU clock sync state: " .. ecuSyncStates[ecuSyncState]) + if ecuClockObservability == 0 then + node:append_text(" (ignored)") + end + if unknownSyncBits ~= 0 then + tree:add(xcp_info, syncState_field, "Unknown bits in Sync state: " .. hex(unknownSyncBits, 2)) + end + end + end + else + -- legacy format + if isResponse then + tree:add(xcp_info, triggerInfo_field, triggerInfo_string) + tree:add(xcp_info, payloadFmt_mcastClkCounter_field, + "Payload format: " .. hex(payloadFmt, 2) .. " (ignored in legacy or max_cto=8 format)") + else + tree:add(xcp_reserved, payloadFmt_mcastClkCounter_field) + end + local timestamp_field = buffer(4, 4) + local timestamp = parse_number(timestamp_field, comm_mode_basic) + tree:add(xcp_info, timestamp_field, "timestamp: " .. timestamp) + end +end + +function decode_res_read_daq(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local address_granularity = band(rshift(comm_mode_basic, 1), 0x3) + local bitOffset_field = buffer(1, 1) + local bitOffset = bitOffset_field:uint() + local elementSize_field = buffer(2, 1) + local elementSize = elementSize_field:uint() + local addrExt_field = buffer(3, 1) + local addrExt = addrExt_field:uint() + local address_field = buffer(4, 4) + local address = parse_number(address_field, comm_mode_basic) + tree:add(xcp_info, bitOffset_field, "Bit offset: " .. bitOffset) + tree:add(xcp_info, elementSize_field, "Element size: " .. elementSize .. AG_TYPES[address_granularity]) + tree:add(xcp_info, addrExt_field, "Address extension: " .. hex(addrExt, 2)) + tree:add(xcp_info, address_field, "Address: " .. hex(address, 8)) +end + +function decode_res_get_daq_processor_info(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local daqProps_field = buffer(1, 1) + local daqProps = daqProps_field:uint() + local maxDaq_field = buffer(2, 2) + local maxDaq = parse_number(maxDaq_field, comm_mode_basic) + local maxEvtChannel_field = buffer(4, 2) + local maxEvtChannel = parse_number(maxEvtChannel_field, comm_mode_basic) + local minDaq_field = buffer(6, 1) + local minDaq = minDaq_field:uint() + local daqKeyByte_field = buffer(7, 1) + local daqKeyByte = daqKeyByte_field:uint() + local infoString = "DAQ properties: " + if band(daqProps, 1) ~= 0 then + infoString = infoString .. "dynamic, " + else + infoString = infoString .. "static, " + end + if band(daqProps, 2) ~= 0 then + infoString = infoString .. "prescaler supported, " + else + infoString = infoString .. "prescaler not supported, " + end + if band(daqProps, 4) ~= 0 then + infoString = infoString .. "RESUME supported, " + else + infoString = infoString .. "RESUME not supported, " + end + if band(daqProps, 8) ~= 0 then + infoString = infoString .. "bitwise stimulation supported, " + else + infoString = infoString .. "bitwise stimulation not supported, " + end + if band(daqProps, 16) ~= 0 then + infoString = infoString .. "time-stamped mode supported, " + cmd_pkt.TIMESTAMP_SUPPORTED = true + else + infoString = infoString .. "time-stamped mode not supported, " + cmd_pkt.TIMESTAMP_SUPPORTED = false + end + if band(daqProps, 32) ~= 0 then + infoString = infoString .. "PID_OFF supported, " + else + infoString = infoString .. "PID_OFF not supported, " + end + if band(daqProps, 192) == 0 then + infoString = infoString .. "no overload indication" + elseif band(daqProps, 192) == 64 then + infoString = infoString .. "overload indication in MSB of PID" + elseif band(daqProps, 192) == 128 then + infoString = infoString .. "overload indication by event packet" + else + infoString = infoString .. "invalid overload indication type (both event and MSB)" + end + tree:add(xcp_info, daqProps_field, infoString) + tree:add(xcp_info, maxDaq_field, "Max DAQ: " .. maxDaq) + if maxEvtChannel == 0 then + tree:add(xcp_info, maxEvtChannel_field, "Max event channel: 0 (unknown)") + else + tree:add(xcp_info, maxEvtChannel_field, "Max event channel: " .. maxEvtChannel) + end + tree:add(xcp_info, minDaq_field, "Min DAQ (#predefined): " .. minDaq) + local optimisationType = band(daqKeyByte, 0xf) + local optimisationTypes = { + [0] = "OM_DEFAULT", + "OM_ODT_TYPE_16", + "OM_ODT_TYPE_32", + "OM_ODT_TYPE_64", + "OM_ODT_TYPE_ALIGNMENT", + "OM_MAX_ENTRY_SIZE" + } + if optimisationType > 5 then + tree:add(xcp_info, daqKeyByte_field, "Unknown optimisation type: " .. hex(optimisationType, 1)) + else + tree:add(xcp_info, daqKeyByte_field, "Optimisation type: " .. optimisationTypes[optimisationType]) + end + local addrExtConstraint = band(rshift(daqKeyByte, 4), 3) + local addrExtConstraints = { + [0] = "can be different within one ODT", + "must be the same within one ODT", + "(invalid constraint)", + "must be the same within one DAQ" + } + tree:add(xcp_info, daqKeyByte_field, "Address extensions " .. addrExtConstraints[addrExtConstraint]) + local idType = rshift(daqKeyByte, 6) + local idTypes = { + [0] = "Absolute", + "Relative (DAQ as BYTE)", + "Relative (DAQ as WORD, unaligned)", + "Relative (DAQ as WORD, aligned)" + } + tree:add(xcp_info, daqKeyByte_field, "ID type: " .. idTypes[idType]) + cmd_pkt.DAQ_IDTYPE = idType +end + +function decode_res_get_daq_resolution_info(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local odtGranularityDaq_field = buffer(1, 1) + local odtGranularityDaq = odtGranularityDaq_field:uint() + local odtEntrySizeDaq_field = buffer(2, 1) + local odtEntrySizeDaq = odtEntrySizeDaq_field:uint() + local odtGranularityStim_field = buffer(3, 1) + local odtGranularityStim = odtGranularityStim_field:uint() + local odtEntrySizeStim_field = buffer(4, 1) + local odtEntrySizeStim = odtEntrySizeStim_field:uint() + local timestampMode_field = buffer(5, 1) + local timestampMode = timestampMode_field:uint() + local timestampTicks_field = buffer(6, 2) + local timestampTicks = parse_number(timestampTicks_field, comm_mode_basic) + tree:add(xcp_info, odtGranularityDaq_field, "ODT granularity for DAQ: " .. odtGranularityDaq) + tree:add(xcp_info, odtEntrySizeDaq_field, "ODT entry size for DAQ: " .. odtEntrySizeDaq) + tree:add(xcp_info, odtGranularityStim_field, "ODT granularity for STIM: " .. odtGranularityStim) + tree:add(xcp_info, odtEntrySizeStim_field, "ODT entry size for STIM: " .. odtEntrySizeStim) + + local suffix = "" + if not find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "TIMESTAMP_SUPPORTED", true) then + suffix = " (ignored)" + end + local timestampSize = band(timestampMode, 7) + local timestampFixed = band(timestampMode, 8) ~= 0 + cmd_pkt.TIMESTAMP_FIXED = timestampFixed + local timestampUnit = rshift(timestampMode, 4) + local timestampUnits = { + [0] = "1ns", + "10ns", + "100ns", + "1µs", + "10µs", + "100µs", + "1ms", + "10ms", + "100ms", + "1s", + "1ps", + "10ps", + "100ps", + "unknown (0xd)", + "unknown (0xe)", + "unknown (0xf)" + } + cmd_pkt.TIMESTAMP_SIZE = timestampSize + if timestampSize == 3 then + tree:add(xcp_info, timestampMode_field, "Timestamp size: 3 (not allowed)" .. suffix) + else + tree:add(xcp_info, timestampMode_field, "Timestamp size: " .. timestampSize .. suffix) + end + if timestampFixed then + tree:add(xcp_info, timestampMode_field, "Timestamp fixed" .. suffix) + else + tree:add(xcp_info, timestampMode_field, "Timestamp not fixed" .. suffix) + end + tree:add(xcp_info, timestampMode_field, "Timestamp unit: " .. timestampUnits[timestampUnit] .. suffix) + tree:add(xcp_info, timestampTicks_field, "Ticks per unit: " .. timestampTicks .. suffix) +end + +function decode_res_get_daq_list_info(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local daqListProperties_field = buffer(1, 1) + local daqListProperties = daqListProperties_field:uint() + local maxOdt_field = buffer(2, 1) + local maxOdt = maxOdt_field:uint() + local maxOdtEntries_field = buffer(3, 1) + local maxOdtEntries = maxOdtEntries_field:uint() + local fixedEvent_field = buffer(4, 2) + local fixedEvent = parse_number(fixedEvent_field, comm_mode_basic) + if band(daqListProperties, 1) ~= 0 then + tree:add(xcp_info, daqListProperties_field, "DAQ list configurations is fixed") + else + tree:add(xcp_info, daqListProperties_field, "DAQ list configurations can be changed") + end + if band(daqListProperties, 2) ~= 0 then + tree:add(xcp_info, daqListProperties_field, "Event Channel is fixed") + else + tree:add(xcp_info, daqListProperties_field, "Event Channel can be changed") + end + local daqListType = band(rshift(daqListProperties, 2), 3) + if daqListType == 0 then + tree:add(xcp_info, daqListProperties_field, "Invalid DAQ list type: 0") + elseif daqListType == 1 then + tree:add(xcp_info, daqListProperties_field, "DAQ list type: DAQ only") + elseif daqListType == 2 then + tree:add(xcp_info, daqListProperties_field, "DAQ list type: STIM only") + else + tree:add(xcp_info, daqListProperties_field, "DAQ list type: both") + end + local unknownBits = band(daqListProperties, 0xf0) + if unknownBits ~= 0 then + tree:add(xcp_info, daqListProperties_field, "Unknown bits in DAQ list properties:" .. hex(unknownBits, 2)) + end + tree:add(xcp_info, maxOdt_field, "Number of ODTs: " .. maxOdt) + tree:add(xcp_info, maxOdtEntries_field, "Number of entries per ODT: " .. maxOdtEntries) + tree:add(xcp_info, fixedEvent_field, "Fixed event channel: " .. fixedEvent) +end + +function decode_res_get_daq_event_info(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local daqEventProperties_field = buffer(1, 1) + local daqEventProperties = daqEventProperties_field:uint() + local maxDaqList_field = buffer(2, 1) + local maxDaqList = maxDaqList_field:uint() + local evtChanNameLen_field = buffer(3, 1) + local evtChanNameLen = evtChanNameLen_field:uint() + local evtChanTimeCycle_field = buffer(4, 1) + local evtChanTimeCycle = evtChanTimeCycle_field:uint() + local evtChanTimeUnit_field = buffer(5, 1) + local evtChanTimeUnit = evtChanTimeUnit_field:uint() + local evtChanPrio_field = buffer(6, 1) + local evtChanPrio = evtChanPrio_field:uint() + local evtChanType = band(rshift(daqEventProperties, 2), 3) + local consistencyLevel = rshift(daqEventProperties, 6) + local unknownBits = band(daqEventProperties, 0x33) + if evtChanType == 0 then + tree:add(xcp_info, daqEventProperties_field, "Invalid Event channel type: 0") + elseif evtChanType == 1 then + tree:add(xcp_info, daqEventProperties_field, "Event channel type: DAQ only") + elseif evtChanType == 2 then + tree:add(xcp_info, daqEventProperties_field, "Event channel type: STIM only") + else + tree:add(xcp_info, daqEventProperties_field, "Event channel type: both") + end + if consistencyLevel == 0 then + tree:add(xcp_info, daqEventProperties_field, "Consistency on ODT level") + elseif consistencyLevel == 1 then + tree:add(xcp_info, daqEventProperties_field, "Consistency on DAQ level") + elseif consistencyLevel == 2 then + tree:add(xcp_info, daqEventProperties_field, "Consistency on Event Channel level") + else + tree:add(xcp_info, daqEventProperties_field, "Unknown consistency specification: DAQ|Event (0xc0)") + end + if unknownBits ~= 0 then + tree:add(xcp_info, daqEventProperties_field, "Unknown bits in Event properties: " .. hex(unknownBits, 2)) + end + tree:add(xcp_info, maxDaqList_field, "Max. number of DAQ lists: " .. maxDaqList) + tree:add(xcp_info, evtChanNameLen_field, "Name length: " .. evtChanNameLen) + tree:add(xcp_info, evtChanTimeCycle_field, "Time cycle: " .. evtChanTimeCycle) + local timeUnits = { + [0] = "1ns", + "10ns", + "100ns", + "1µs", + "10µs", + "100µs", + "1ms", + "10ms", + "100ms", + "1s", + "1ps", + "10ps", + "100ps" + } + tree:add(xcp_info, evtChanTimeUnit_field, "Time unit: " .. timeUnits[band(evtChanTimeUnit, 0x0f)]) + local highNibble = band(evtChanTimeUnit, 0xf0) + if highNibble ~= 0 then + tree:add(xcp_info, evtChanTimeUnit_field, "Unknown bits in time unit field: " .. hex(highNibble)) + end + tree:add(xcp_info, evtChanPrio_field, "Priority: " .. evtChanPrio) +end + +function decode_res_program_start(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local commModePgm_field = buffer(2, 1) + local commModePgm = commModePgm_field:uint() + local maxCtoPgm_field = buffer(3, 1) + local maxCtoPgm = maxCtoPgm_field:uint() + local maxBsPgm_field = buffer(4, 1) + local maxBsPgm = maxBsPgm_field:uint() + local minStPgm_field = buffer(5, 1) + local minStPgm = minStPgm_field:uint() + local qSizePgm_field = buffer(6, 1) + local qSizePgm = qSizePgm_field:uint() + + tree:add(xcp_reserved, buffer(1, 1)) + if band(commModePgm, 1) ~= 0 then + tree:add(xcp_info, commModePgm_field, "Master block mode available") + else + tree:add(xcp_info, commModePgm_field, "Master block mode not available") + end + if band(commModePgm, 2) ~= 0 then + tree:add(xcp_info, commModePgm_field, "Interleaved mode available") + else + tree:add(xcp_info, commModePgm_field, "Interleaved mode not available") + end + if band(commModePgm, 0x40) ~= 0 then + tree:add(xcp_info, commModePgm_field, "Slave block mode available") + else + tree:add(xcp_info, commModePgm_field, "Slave block mode not available") + end + local unknownBits = band(commModePgm, 0xbc) + if unknownBits ~= 0 then + tree:add(xcp_info, commModePgm_field, "Unknown bits in COMM_MODE_PGM: " .. hex(unknownBits, 2)) + end + tree:add(xcp_info, maxCtoPgm_field, "MAX_CTO: " .. maxCtoPgm) + tree:add(xcp_info, maxBsPgm_field, "MAX_BS: " .. maxBsPgm) + tree:add(xcp_info, minStPgm_field, "MIN_ST: " .. minStPgm) + tree:add(xcp_info, qSizePgm_field, "QUEUE_SIZE: " .. qSizePgm) +end + +function decode_res_get_pgm_processor_info(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local pgmProps_field = buffer(1, 1) + local pgmProps = pgmProps_field:uint() + local maxSector_field = buffer(2, 1) + local maxSector = maxSector_field:uint() + local mode = band(pgmProps, 3) + local compression = band(rshift(pgmProps, 2), 3) + local encryption = band(rshift(pgmProps, 4), 3) + local nonSeq = rshift(pgmProps, 6) + if mode == 0 then + tree:add(xcp_info, pgmProps_field, "Invalid clear/programming mode: 0") + elseif mode == 1 then + tree:add(xcp_info, pgmProps_field, "Clear/programming mode supported: absolute") + elseif mode == 2 then + tree:add(xcp_info, pgmProps_field, "Clear/programming mode supported: functional") + elseif mode == 3 then + tree:add(xcp_info, pgmProps_field, "Clear/programming mode supported: both") + end + if compression == 0 then + tree:add(xcp_info, pgmProps_field, "Compression not supported") + elseif compression == 1 then + tree:add(xcp_info, pgmProps_field, "Compression supported but not required") + elseif compression == 2 then + tree:add(xcp_info, pgmProps_field, "Compression not supported but still required") + elseif compression == 3 then + tree:add(xcp_info, pgmProps_field, "Compression supported and required") + end + if encryption == 0 then + tree:add(xcp_info, pgmProps_field, "Encryption not supported") + elseif encryption == 1 then + tree:add(xcp_info, pgmProps_field, "Encryption supported but not required") + elseif encryption == 2 then + tree:add(xcp_info, pgmProps_field, "Encryption not supported but still required") + elseif encryption == 3 then + tree:add(xcp_info, pgmProps_field, "Encryption supported and required") + end + if nonSeq == 0 then + tree:add(xcp_info, pgmProps_field, "Non-sequential programming not supported") + elseif nonSeq == 1 then + tree:add(xcp_info, pgmProps_field, "Non-sequential programming supported but not required") + elseif nonSeq == 2 then + tree:add(xcp_info, pgmProps_field, "Non-sequential programming not supported but still required") + elseif nonSeq == 3 then + tree:add(xcp_info, pgmProps_field, "Non-sequential programming supported and required") + end + tree:add(xcp_info, maxSector_field, "Total number of sectors: " .. maxSector) +end + +function decode_res_get_sector_info(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local csn_field = buffer(1, 1) + local csn = csn_field:uint() + tree:add(xcp_info, csn_field, "Clear Sequence Number: " .. csn) + if cmd_pkt.GET_SECTOR_INFO__MODE == 2 then + local nameLength_field = buffer(2, 1) + local nameLength = nameLength_field:uint() + tree:add(xcp_info, nameLength_field, "Name length: " .. nameLength) + else + local psn_field = buffer(2, 1) + local psn = psn_field:uint() + local pm_field = buffer(3, 1) + local pm = pm_field:uint() + local secInfo_field = buffer(4, 4) + local secInfo = parse_number(secInfo_field, comm_mode_basic) + tree:add(xcp_info, psn_field, "Program Sequence Number: " .. psn) + if pm == 0 then + tree:add(xcp_info, pm_field, "Programming method: absolute access mode") + elseif pm >= 0x80 then + tree:add(xcp_info, pm_field, "Programming method: user defined (" .. hex(pm, 2) .. ")") + else + tree:add(xcp_info, pm_field, "Programming method: invalid (" .. hex(pm, 2) .. ")") + end + if cmd_pkt.GET_SECTOR_INFO__MODE == 0 then + tree:add(xcp_info, secInfo_field, "Start address: " .. hex(secInfo, 8)) + elseif cmd_pkt.GET_SECTOR_INFO__MODE == 1 then + tree:add(xcp_info, secInfo_field, "Length: " .. secInfo) + else + tree:add(xcp_info, "Unknown mode: " .. cmd_pkt.GET_SECTOR_INFO__MODE):set_generated() + tree:add(xcp_info, secInfo_field, "Sector info: " .. secInfo) + end + end +end + +function decode_res_time_correlation_properties(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + local slaveConfig_field = buffer(1, 1) + local slaveConfig = slaveConfig_field:uint() + local observableClocks_field = buffer(2, 1) + local observableClocks = observableClocks_field:uint() + local syncState_field = buffer(3, 1) + local syncState = syncState_field:uint() + local clockInfo_field = buffer(4, 1) + local clockInfo = clockInfo_field:uint() + local clusterId_field = buffer(6, 2) + local clusterId = parse_number(clusterId_field, comm_mode_basic) + + local responseFmt = band(slaveConfig, 3) + local daqTsRelation = band(slaveConfig, 4) ~= 0 + local timeSyncBridge = band(rshift(slaveConfig, 3), 3) + local unknownSlvCfgBits = band(slaveConfig, 0xe0) + if responseFmt == 0 then + tree:add(xcp_info, slaveConfig_field, "Response format: legacy") + elseif responseFmt == 1 then + tree:add(xcp_info, slaveConfig_field, "Response format: extended; event only for TRIGGER_INITIATOR 2 and 3") + elseif responseFmt == 2 then + tree:add(xcp_info, slaveConfig_field, "Response format: extended; event for all TRIGGER_INITIATOR values") + else + tree:add(xcp_info, slaveConfig_field, "Unknown response format 3") + end + if daqTsRelation then + tree:add(xcp_info, slaveConfig_field, "Timestamps related to ECU clock") + else + tree:add(xcp_info, slaveConfig_field, "Timestamps related to XCP slave clock") + end + if timeSyncBridge == 0 then + tree:add(xcp_info, slaveConfig_field, "Time Sync Bridge: not available") + elseif timeSyncBridge == 1 then + tree:add(xcp_info, slaveConfig_field, "Time Sync Bridge: disabled") + elseif timeSyncBridge == 2 then + tree:add(xcp_info, slaveConfig_field, "Time Sync Bridge: enabled") + else + tree:add(xcp_info, slaveConfig_field, "Time Sync Bridge: reserved (3)") + end + if unknownSlvCfgBits ~= 0 then + tree:add(xcp_info, slaveConfig_field, "Unknown bits in Slave configuration: " .. hex(unknownSlvCfgBits, 2)) + end + + local slaveClock = band(observableClocks, 3) + local gmClock = band(rshift(observableClocks, 2), 3) + local ecuClock = band(rshift(observableClocks, 4), 3) + local unknownObsClkBits = band(observableClocks, 0xc0) + + if slaveClock == 0 then + tree:add(xcp_info, observableClocks_field, "Slave clock: free running") + elseif slaveClock == 1 then + tree:add(xcp_info, observableClocks_field, "Slave clock: syntonizable/synchronizable") + elseif slaveClock == 2 then + tree:add(xcp_info, observableClocks_field, "Slave clock: not available") + else + tree:add(xcp_info, observableClocks_field, "Slave clock: reserved (3)") + end + if gmClock == 0 then + tree:add(xcp_info, observableClocks_field, "Grandmaster-synced clock: not available") + elseif gmClock == 1 then + tree:add(xcp_info, observableClocks_field, "Grandmaster-synced clock: random-read possible") + elseif gmClock == 2 then + tree:add(xcp_info, observableClocks_field, "Grandmaster-synced clock: no random read, only EV_TIME_SYNC") + else + tree:add(xcp_info, observableClocks_field, "Grandmaster-synced clock: reserved (3)") + end + if ecuClock == 0 then + tree:add(xcp_info, observableClocks_field, "ECU clock: not available") + elseif ecuClock == 1 then + tree:add(xcp_info, observableClocks_field, "ECU clock: random-read possible") + elseif ecuClock == 2 then + tree:add(xcp_info, observableClocks_field, "ECU clock: no random read, only EV_TIME_SYNC") + else + tree:add(xcp_info, observableClocks_field, "ECU clock: used for timestamps but not explicitly readable") + end + if unknownObsClkBits ~= 0 then + tree:add(xcp_info, observableClocks_field, "Unknown bits in OBSERVABLE_CLOCKS: " .. hex(unknownObsClkBits, 2)) + end + cmd_pkt.TIME_CORRELATION_PROPERTIES__slaveClock = slaveClock + cmd_pkt.TIME_CORRELATION_PROPERTIES__gmClock = gmClock + cmd_pkt.TIME_CORRELATION_PROPERTIES__ecuClock = ecuClock + + local slaveSyncState = band(syncState, 7) + local slaveSyncStates = { + [0] = "synchronizing", + "synchronized", + "syntonizing", + "syntonized", + "reserved (4)", + "reserved (5)", + "reserved (6)", + "not supported" + } + local node = tree:add(xcp_info, syncState_field, "Slave clock sync state: " .. slaveSyncStates[slaveSyncState]) + if slaveClock ~= 1 then + node:append_text(" (ignored)") + end + local gmSyncState = band(rshift(syncState, 3), 1) + local gmSyncStates = { [0] = "not yet synchronized", "synchronized" } + node = tree:add(xcp_info, syncState_field, "Gramdmaster-synced clock sync state: " .. gmSyncStates[gmSyncState]) + if gmClock == 0 then + node:append_text(" (ignored)") + end + local ecuSyncState = band(rshift(syncState, 4), 3) + local ecuSyncStates = { [0] = "not synchronized", "synchronized", "unknown", "reserved (3)" } + node = tree:add(xcp_info, syncState_field, "ECU clock sync state: " .. ecuSyncStates[ecuSyncState]) + if ecuClock == 0 then + node:append_text(" (ignored)") + end + local unknownSyncBits = band(syncState, 0xc0) + if unknownSyncBits ~= 0 then + tree:add(xcp_info, syncState_field, "Unknown bits in Sync state: " .. hex(unknownSyncBits, 2)) + end + + local unknownInfoBits = band(clockInfo, 0xe0) + local infoString = "" + if band(clockInfo, 1) ~= 0 then + infoString = infoString .. "Slave clock, " + end + if band(clockInfo, 2) ~= 0 then + infoString = infoString .. "Grandmaster-synced clock, " + end + if band(clockInfo, 4) ~= 0 then + infoString = infoString .. "Clock relation, " + end + if band(clockInfo, 8) ~= 0 then + infoString = infoString .. "ECU clock, " + end + if band(clockInfo, 16) ~= 0 then + infoString = infoString .. "ECU grandmaster clock, " + end + if unknownInfoBits ~= 0 then + infoString = infoString .. "Unknown bits (" .. hex(unknownInfoBits, 2) .. "), " + end + infoString = string.sub(infoString, 1, -3) + tree:add(xcp_info, clockInfo_field, "Information available at MTA: " .. infoString) + tree:add(xcp_reserved, buffer(5, 1)) + tree:add(xcp_info, clusterId_field, "Cluster ID: " .. hex(clusterId, 4)) + +end + +RES_DECODERS = { + [0xFF]=decode_res_connect, + [0xFD]=decode_res_get_status, + [0xFB]=decode_res_get_comm_mode_info, + [0xFA]=decode_res_get_id, + [0xF9]=decode_res_set_request, + [0xF8]=decode_res_get_seed, + [0xF7]=decode_res_unlock, + [0xF5]=decode_res_upload, + [0xF3]=decode_res_build_checksum, + [0xF2]=decode_res_transport_layer_cmd__user_cmd, + [0xF1]=decode_res_transport_layer_cmd__user_cmd, + [0xEA]=decode_res_get_cal_page, + [0xE9]=decode_res_get_pag_processor_info, + [0xE8]=decode_res_get_segment_info, + [0xE7]=decode_res_get_page_info, + [0xE5]=decode_res_get_segment_mode, + [0xDF]=decode_res_get_daq_list_mode, + [0xDE]=decode_res_start_stop_daq_list, + [0xDC]=decode_res_get_daq_clock__ev_time_sync, + [0xDB]=decode_res_read_daq, + [0xDA]=decode_res_get_daq_processor_info, + [0xD9]=decode_res_get_daq_resolution_info, + [0xD8]=decode_res_get_daq_list_info, + [0xD7]=decode_res_get_daq_event_info, + [0xD2]=decode_res_program_start, + [0xCE]=decode_res_get_pgm_processor_info, + [0xCD]=decode_res_get_sector_info, + [0xC6]=decode_res_time_correlation_properties, +} + +function decode_res(buffer, pinfo, tree) + local comm_mode_basic = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "COMM_MODE_BASIC", 0) + local cmd_pid = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "CMD") + local cmd_pktnum = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "pinfo.number") + tree:append_text(" Response to " .. COMMAND_NAMES[cmd_pid]) + tree:add(xcp_type, buffer(0, 1), "Command Response") + local cmd_pkt = {} + if cmd_pktnum ~= nil then + tree:add(xcp_request, cmd_pktnum):set_generated() + cmd_pkt = conversations[pinfo.private.xcp_conversation][cmd_pktnum] + end + if cmd_pid ~= nil then + tree:add(xcp_cmd, cmd_pid):set_generated() + local cmd_name = COMMAND_NAMES[cmd_pid] + if cmd_name then + pinfo.cols.info:set("XCP pos. response to " .. cmd_name) + else + pinfo.cols.info:set("XCP pos. response to unknown command " .. hex(cmd_pid, 2)) + end + end + + local decoder = RES_DECODERS[cmd_pid] + if decoder then + decoder(buffer, pinfo, tree, cmd_pkt, comm_mode_basic) + end +end + +function decode_err(buffer, pinfo, tree) + local err_code = buffer(1, 1):le_uint() + local comm_mode_basic = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "COMM_MODE_BASIC", 0) + local address_granularity = band(rshift(comm_mode_basic, 1), 0x3) + if err_code == 0 then + pinfo.cols.info:set("SYNCH response") + tree:append_text(" Response to SYNCH") + tree:add(xcp_type, buffer(0, 2), "SYNCH") + return + end + local cmd_pid = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "CMD") + local cmd_pktnum = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "pinfo.number") + local cmd_name = COMMAND_NAMES[cmd_pid] + local err_name = ERR_CODES[err_code] + local info_string = "XCP Error: " + if err_name then + info_string = info_string .. err_name + else + info_string = "Unknown " .. info_string .. hex(err_code, 2) + end + if cmd_name then + info_string = info_string .. " (in response to " .. cmd_name .. ")" + elseif cmd_pid then + info_string = info_string .. " (in response to unknown command" .. hex(cmd_pid, 2) .. ")" + end + pinfo.cols.info:set(info_string) + + if cmd_pid == 0xc6 then + -- if the command was not successful, it did not change whether extended or legacy format is used + local cmd_pktnum = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "pinfo.number") + conversations[pinfo.private.xcp_conversation][cmd_pktnum]["TIME_SYNCHRONIZATION_PROPERTIES__extended"] = nil + end + tree:add(xcp_type, buffer(0, 1), "Error") + tree:add(xcp_request, cmd_pktnum):set_generated() + tree:add(xcp_cmd, cmd_pid):set_generated() + tree:add(xcp_err, buffer(1, 1), err_code) + if err_code == 0x22 then + -- ERR_OUT_OF_RANGE + if cmd_pid == 0xF3 then + -- BUILD_CHECKSUM + local mtaBlockSizeAlign_field = buffer(2, 2) + local mtaBlockSizeAlign = parse_number(mtaBlockSizeAlign_field, comm_mode_basic) + tree:add(xcp_info, mtaBlockSizeAlign_field, "MTA_BLOCK_SIZE_ALIGN: " .. mtaBlockSizeAlign) + local maxAllowedBlockSize_field = buffer(4, 4) + local maxAllowedBlockSize = parse_number(maxAllowedBlockSize_field, comm_mode_basic) + tree:add(xcp_info, maxAllowedBlockSize_field, + "Maximum allowed block size: " .. maxAllowedBlockSize .. AG_TYPES[address_granularity]) + end + elseif err_code == 0x31 then + -- ERR_GENERIC + local implementationSpecificDeviceErrorCode_field = buffer(2, 2) + local implementationSpecificDeviceErrorCode = parse_number(implementationSpecificDeviceErrorCode_field, + comm_mode_basic) + tree:add(xcp_info, implementationSpecificDeviceErrorCode_field, + "implementation-specific device error code: " .. implementationSpecificDeviceErrorCode) + elseif err_code == 0x29 then + -- ERR_SEQUENCE + if cmd_pid == 0xef or cmd_pid == 0xca then + -- DOWNLOAD_NEXT, PROGRAM_NEXT + local numExpected_field = buffer(2, 1) + local numExpected = numExpected_field:uint() + tree:add(xcp_info, numExpected_field, "Number of expected data Elements: " .. numExpected) + end + end +end + +function decode_ev(buffer, pinfo, tree) + local event_code = buffer(1, 1):le_uint() + local event_name = EV_CODES[EV_CODES] + if not event_name then + event_name = "Unknown event " .. hex(event_code, 2) + end + tree:append_text(" " .. event_name) + pinfo.cols.info:set("XCP " .. event_name) + local comm_mode_basic = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "COMM_MODE_BASIC", 0) + + tree:add(xcp_type, buffer(0, 1), "Event") + tree:add(xcp_ev, buffer(1, 1), event_code) + if event_code == 0x00 then + -- EV_RESUME_MODE + local sessionConfigurationId_field = buffer(2, 2) + local sessionConfigurationId = parse_number(sessionConfigurationId_field, comm_mode_basic) + tree:add(xcp_info, sessionConfigurationId_field, "sessionConfigurationId: " .. sessionConfigurationId) + if buffer:len() >= 8 then + local timestamp_field = buffer(4, 4) + local timestamp = parse_number(timestamp_field, comm_mode_basic) + tree:add(xcp_info, timestamp_field, "timestamp: " .. timestamp) + end + -- events 1 thru 7 have no payload + elseif event_code == 0x08 then + -- EV_TIME_SYNC + decode_res_get_daq_clock__ev_time_sync(buffer, pinfo, tree, {}, comm_mode_basic) + elseif event_code == 0x09 then + -- EV_STIM_TIMEOUT + local infoType_field = buffer(2, 1) + local infoType = infoType_field:uint() + local failureType_field = buffer(3, 1) + local failureType = failureType_field:uint() + local number_Field = buffer(4, 2) + local number = parse_number(number_Field, comm_mode_basic) + + local failureType_string = "" + if failureType == 0 then + failureType_string = "Timeout" + elseif failureType == 1 then + failureType_string = "DTO_CTR check failed" + elseif failureType <= 127 then + failureType_string = "Reserved (" .. hex(failureType) .. ")" + else + failureType_string = "User defined (" .. hex(failureType) .. ")" + end + + local infoType_string = "unknown" + if infoType == 0 then + infoType_string = "Event channel" + elseif infoType == 1 then + infoType_string = "DAQ list" + else + infoType_string = "Unknown" + end + tree:add(xcp_info, infoType_field, "Info type: " .. infoType_string) + tree:add(xcp_info, failureType_field, "Failure type: " .. failureType_string) + tree:add(xcp_info, number_Field, infoType_string .. " number: " .. number) + + -- events 0x0a and 0x0b have no payload + elseif event_code == 0x0c then + -- EV_ECU_STATE_CHANGE + local state_field = buffer(2, 1) + local state = state_field:uint() + tree:add(xcp_info, state_field, "state: " .. state) + -- events 0x0d thru 0xfd are not defined + elseif event_code == 0xfe or event_code == 0xff then + -- EV_USER, EV_TRANSPORT + tree:add(xcp_data, buffer(2)) + end +end + +function decode_serv(buffer, pinfo, tree) + tree:add(xcp_type, buffer(0, 1), "Service Request") + local requestCode_field = buffer(1, 1) + local requestCode = requestCode_field:uint() + tree:add(xcp_serv, requestCode_field, requestCode) + pinfo.cols.info:set("XCP Service Request") + if requestCode == 1 then + tree:add(xcp_info, buffer(2):string()) + end +end + +function decode_daq_stim(buffer, pinfo, tree, mode) + pinfo.cols.info:set("XCP " .. mode) + tree:append_text(" " .. mode) + local pid_field = buffer(0, 1) + tree:add(xcp_type, pid_field, mode) + + local pkt_data = {} + conversations[pinfo.private.xcp_conversation][pinfo.number] = pkt_data + local comm_mode_basic = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "COMM_MODE_BASIC", 0) + local timestamp_size = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "TIMESTAMP_SIZE", 0) + local timestamp_fixed = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "TIMESTAMP_FIXED", false) + local timestamp_supported = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "TIMESTAMP_SUPPORTED", false) + local timestamp_enabled = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "TIMESTAMP_ENABLED", false) + local dto_ctr = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "DTO_CTR", {}) + local first_pid = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "FIRST_PID", {}) + local daq_running = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "DAQ_RUNNING", {}) + local prev_odt = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "ODT", 255) + + -- TODO: when introducing new transport layers, support for PID_OFF might be needed + local idType = find_stored_data(pinfo.private.xcp_conversation, pinfo.number, "DAQ_IDTYPE", 99) + if idType > 3 then + tree:add(xcp_info, "DAQ ID type not known, can't interpret DAQ header"):set_generated() + tree:add(xcp_data, buffer) + return + end + local pid = pid_field:uint() + local offset = idType + 1 + local daq = -1 + if idType == 0 then + tree:add(xcp_info, pid_field, "absolute ODT: " .. pid) + for daq_, hasCounter in pairs(dto_ctr) do + if hasCounter and daq_running[daq_] and pid == first_pid[daq_] then + local ctr_field = buffer(offset, 1) + offset = offset + 1 + local ctr = ctr_field:uint() + tree:add(xcp_info, ctr_field, "Counter: " .. ctr) + end + end + else + tree:add(xcp_info, pid_field, "relative ODT: " .. pid) + local daq_field = nil + if idType == 1 then + daq_field = buffer(1, 1) + elseif idType == 2 then + daq_field = buffer(1, 2) + else + -- 3 + tree:add(xcp_reserved, buffer(1, 1)) + daq_field = buffer(2, 2) + end + daq = parse_number(daq_field, comm_mode_basic) + tree:add(xcp_info, daq_field, "DAQ list: " .. daq) + if pid <= prev_odt and dto_ctr[daq] then + -- assumption: ODTs per DAQ list are transferred/captured in ascending order + local ctr_field = buffer(offset, 1) + if idType == 3 then + ctr_field = buffer(1, 1) + else + offset = offset + 1 + end + local ctr = ctr_field:uint() + tree:add(xcp_info, ctr_field, "Counter: " .. ctr) + end + if timestamp_fixed or (timestamp_supported and timestamp_enabled) then + if pid <= prev_odt then + -- assumption: ODTs per DAQ list are transferred/captured in ascending order + local timestamp_field = buffer(offset, timestamp_size) + offset = offset + timestamp_size + local timestamp = parse_number(timestamp_field, comm_mode_basic) + tree:add(xcp_info, timestamp_field, "Timestamp: " .. timestamp) + end + end + end + pkt_data.DAQ = daq + pkt_data.ODT = pid + tree:add(xcp_data, buffer(offset)) +end + +function xcpProto.dissector(buffer, pinfo, tree) + length = buffer:len() + if length == 0 then + return + end + + pinfo.cols.protocol = xcpProto.name + local pid = buffer(0, 1):le_uint() + if conversations[pinfo.private.xcp_conversation] == nil then + conversations[pinfo.private.xcp_conversation] = {} + end + + if pinfo.private.xcp_dir == "CMD" + then + if pid >= 0xc5 then + decode_cmd(buffer, pinfo, tree) + else + decode_daq_stim(buffer, pinfo, tree, "STIM") + end + elseif pinfo.private.xcp_dir == "RES" + then + if pid == 0xFF then + decode_res(buffer, pinfo, tree) + elseif pid == 0xFE then + decode_err(buffer, pinfo, tree) + elseif pid == 0xFD then + decode_ev(buffer, pinfo, tree) + elseif pid == 0xFC then + decode_serv(buffer, pinfo, tree) + else + decode_daq_stim(buffer, pinfo, tree, "DAQ") + end + end +end + +return xcpProto diff --git a/lua/XCP_CAN.lua b/lua/XCP_CAN.lua new file mode 100644 index 0000000..bbdea85 --- /dev/null +++ b/lua/XCP_CAN.lua @@ -0,0 +1,76 @@ +-- XCP_CAN.lua +-- XCP dissector for Wireshark, transport layer "XCP on CAN (FD)" +-- Version 0.1 +-- +-- Copyright (c) 2023-2024 tracetronic GmbH +-- +-- SPDX-License-Identifier: GPL-2.0 + + + +xcp_can = Proto("xcp_can", "XCP on CAN") +xcp_can.fields = {} +can_id_field = Field.new("can.id") + +xcp_can.prefs.info = Pref.statictext("Only CAN frames whose CAN IDs match the CAN IDs configured in fields below are dissected") +xcp_can.prefs.master_can_ids = Pref.range("Master CAN IDs:", "", "CAN IDs which a master \z +uses to send frames to a slave\n\nMultiple comma separated values (both decimal and hex) \z +are supported e.g.: '21,0x42,0xab'.\n\z Ranges like '21-42' are NOT supported.", 2^29) +xcp_can.prefs.slave_can_ids = Pref.range("Slave CAN IDs: ", "", "CAN IDs which slaves \z +are using to send frames to the master\n\nMultiple comma separated values (both decimal and hex) \z +are supported e.g.: '21,0x42,0xab'.\nRanges like '21-42' are NOT supported.", 2^29) + + +local function contains(table, value) + for _, table_value in ipairs(table) do + if table_value == value then + return true + end + end + return false +end + + +local function parse_ids(str) + local ids = {} + for id in string.gmatch(str, '([^,]+)') do + table.insert(ids, tonumber(id)) + end + return ids +end + + +function xcp_can.dissector(buffer, pinfo, tree) + local bufferLength = buffer:len() + if bufferLength == 0 then + return + end + + local master_ids = parse_ids(xcp_can.prefs.master_can_ids) + local slave_ids = parse_ids(xcp_can.prefs.slave_can_ids) + local can_id = can_id_field().value + + if not contains(master_ids, can_id) and not contains(slave_ids, can_id) then + return + end + + if contains(master_ids, can_id) then + pinfo.private.xcp_dir = "CMD" -- CMD/STIM + pinfo.cols.src = string.format("Master 0x%02x", can_id) + end + + if contains(slave_ids, can_id) then + pinfo.private.xcp_dir = "RES" -- RES/ERR/EV/SERV/DAQ + pinfo.cols.src = string.format("Slave 0x%02x", can_id) + end + + pinfo.private.xcp_conversation = "CAN" + + pinfo.cols.protocol = xcp_can.name + local subtree = tree:add(xcp_can, buffer(), "XCP on CAN") + Dissector.get("xcp"):call(buffer():tvb(), pinfo, subtree) +end + + +local sd = DissectorTable.get("can.subdissector") +sd:add_for_decode_as(xcp_can) \ No newline at end of file diff --git a/lua/XCP_ETH.lua b/lua/XCP_ETH.lua new file mode 100644 index 0000000..39a1672 --- /dev/null +++ b/lua/XCP_ETH.lua @@ -0,0 +1,78 @@ +-- XCP_ETH.lua +-- XCP dissector for Wireshark, transport layer "XCP on Ethernet" +-- Version 0.1 +-- +-- Copyright (c) 2023-2024 tracetronic GmbH +-- +-- SPDX-License-Identifier: GPL-2.0 + + +xcp_eth = Proto("xcp_eth", "XCP on Ethernet") + +message_length = ProtoField.uint16("xcp_eth.len", "LEN", base.DEC) +message_counter = ProtoField.uint16("xcp_eth.ctr", "CTR", base.DEC) + +xcp_eth.fields = { message_length, message_counter } +tcp_stream = Field.new("tcp.stream") +udp_stream = Field.new("udp.stream") +tcp_sport = Field.new("tcp.srcport") +tcp_dport = Field.new("tcp.dstport") +udp_sport = Field.new("udp.srcport") +udp_dport = Field.new("udp.dstport") + +function xcp_eth.dissector(buffer, pinfo, tree) + local bufferLength = buffer:len() + if bufferLength == 0 then + return + end + + pinfo.cols.protocol = xcp_eth.name + + local offset = 0 + + while offset + 4 < bufferLength do + + + local len = buffer(offset, 2):le_uint() + + -- assumption: if port not 5555, lower port number is ECU, higher is MC software + if pinfo.port_type == 2 then + pinfo.private.xcp_conversation = "TCP" .. tostring(tcp_stream()) + if tcp_sport()() == 5555 then + pinfo.private.xcp_dir = "RES" -- RES/ERR/EV/SERV/DAQ + elseif tcp_dport()() == 5555 then + pinfo.private.xcp_dir = "CMD" -- CMD/STIM + elseif tcp_sport()() > tcp_dport()() then + pinfo.private.xcp_dir = "CMD" -- CMD/STIM + else + pinfo.private.xcp_dir = "RES" -- RES/ERR/EV/SERV/DAQ + end + elseif pinfo.port_type == 3 then + pinfo.private.xcp_conversation = "UDP" .. tostring(udp_stream()) + if udp_sport()() == 5555 then + pinfo.private.xcp_dir = "RES" -- RES/ERR/EV/SERV/DAQ + elseif udp_dport()() == 5555 then + pinfo.private.xcp_dir = "CMD" -- CMD/STIM + elseif udp_sport()() > udp_dport()() then + pinfo.private.xcp_dir = "CMD" -- CMD/STIM + else + pinfo.private.xcp_dir = "RES" -- RES/ERR/EV/SERV/DAQ + end + end + local subtree = tree:add(xcp_eth, buffer(offset, len + 4), "XCP") + local ethSubtree = subtree:add(xcp_eth, buffer(offset, 4)) + ethSubtree:add(message_length, buffer(offset, 2), len) + ethSubtree:add_le(message_counter, buffer(offset + 2, 2)) + Dissector.get("xcp"):call(buffer(offset + 4, len):tvb(), pinfo, subtree) + offset = offset + len + 4 + + end +end + +local tcp_port = DissectorTable.get("tcp.port") +local udp_port = DissectorTable.get("udp.port") + +tcp_port:add(5555, xcp_eth) +udp_port:add(5555, xcp_eth) +-- udp_port:add(5556, xcp_eth) GET_SLAVE_ID +-- udp_port:add(5557, xcp_eth) GET_DAQ_CLOCK_MULTICAST