M3 API protocol dissector for Wireshark

Have you ever needed to troubleshoot M3 API calls in Wireshark? Unfortunately, the M3 API protocol is a proprietary protocol. Consequently, Wireshark does not understand it, and it just gives us raw TCP data as a stream of bytes.


I implemented a simple protocol dissector for Wireshark that understands the M3 API protocol, i.e. it parses the TCP stream to show the M3 API bytes in a human-readable format, with company (CONO), division (DIVI), user id (USID), and MI program (e.g. CRS610MI). The dissector is currently not complete and only parses the MvxInit request phase of the protocol.

Reverse engineering

I reverse engineered the M3 API protocol thanks to MvxLib, a free and open source client implementation of the protocol in C# by Mattias Bengtsson (now deprecated), and thanks to MvxSockJ, the official and closed-source client implementation of the protocol in Java (up-to-date).

3 2


The MvxInit phase of the protocol is the first phase of the protocol for connection and authentication, and it has the following structure:

struct MvxInit
   struct request {
      int size;
      char Command[5]; // PWLOG
      char CONO_DIVI[32];
      char USID[16];
      char PasswordCiphertext[16]; // password ^ key
      char MIProgram[32]; // e.g. CRS610MI
      char ApplicationName[32]; // e.g. MI-TEST
      char LocalIPAddress[16];
   struct response {
      int size_;
      char message[15];

Wireshark Generic Dissector

I used the Wireshark Generic Dissector (wsgd) to create a simple dissector. It requires two files: a data format description, and a file description.

The data format description m3api.fdesc is very similar to the C struct above, where the header is required by wsgd:

struct header
   byte_order big_endian;
   uint16 id;
   uint16 size;
struct body
      uint32 size;
      string(5) Command;
      string(32) CONO_DIVI;
      string(16) USID;
      string(16) PasswordCiphertext;
      string(32) MIProgram;
      string(32) ApplicationName;
      string(16) LocalIPAddress;
   } MvxInit;

Given the limitations of wsgd and its message identifier, I could not solve how to parse more than one type of message, so I chose the MvxInit request, and the rest will throw errors.

I made a test M3 API call to M3BE, I captured it in Wireshark, and I saved the TCP stream as a binary file (I anonymized it so I can publish it here):

Then, I used wsgd’s byte_interpret.exe (available on the wsgd downloads), using that test binary file, to fine tune my data format description until it was correct:
byte_interpret.exe m3api.fdesc -frame_bin Test.bin

Then, here is the wsgd file description m3api.wsgd; note how I listed the TCP port numbers of my M3 API servers (DEV, TST, PRD):




include m3api.fdesc;

To activate the new dissector in Wireshark, simply drop the wsgd generic.dll file into Wireshark’s plugins folder, and drop the two above files into Wireshark’s profiles folder:

Then, restart Wireshark, and start capturing M3 API calls. Wireshark will automatically parse the TCP stream as M3 API protocol for the port numbers you specified in the data format description.


Here is a resulting capture between MI-Test and M3BE. Note how you can filter the displayed packets by m3api. Note how the protocol dissector understands the phase of the M3 API protocol (MvxInit), the company (CONO), division (DIVI), user id (USID), and MIProgram (CRS610MI). Also, I wrote C code to decrypt the password ciphertext, but I could not solve where to put that code in wsgd, so the dissector does not decrypt the password.

Limitations and future work

  • I am using M3BE 15.1.2. The M3 API protocol may be different for previous or future versions of M3.
  • I am doing user/password authentication. The M3 API protocol supports other methods of authentication.
  • Given the limitations of wsgd and its message identifier, I will most likely discontinue using wsgd.
  • Instead, I would write a protocol dissector in LUA.
  • Ideally, I should write a protocol dissector in C, but that is over-kill for my needs.


That was a simple M3 API protocol dissector for Wireshark that parses and displays M3 API bytes into a human readable format to help troubleshoot M3 API calls between client applications and M3 Business Engine.

About the M3 API protocol

The M3 API protocol is a proprietary client/server protocol based on TCP/IP for third-party applications to make API calls to Infor M3 Business Engine (M3BE). It was created a long time ago when Movex was on AS/400. It is a very simple protocol, lean, efficient, with good performance, it is available in major platforms (IBM System i, Microsoft Windows Intel/AMD, SUN Solaris, Linux, 32-bit, 64-bit, etc.), it is available in major programming languages (C/C++, Win32, Java, .NET, etc.), it supports Unicode, it supports multiple authentication methods, and it has withstood the test of time (since the nineties). It has been maintained primarily by Björn P.

The data transits in clear text. The protocol had an optional encryption available with the Blowfish cipher, but that feature was removed. Now, only the password is encoded with a XOR cipher during MvxInit. If you need to make secure calls, use the M3 API SOAP or REST secure endpoints of the Infor Grid.

For more information about M3 API, refer to the documentation in the M3 API Toolkit:

More interfaces

If you need to integrate Infor M3 with any of the following interfaces, here are most of the interfaces with which I have worked in the past two years for which I have not posted anything on this blog yet, and for which I may be able to help you if you have questions (contact me here). For that, I used a variety of Enterprise Collaborator, Smart Office scripts, and other code.

Also, check-out the other interfaces I have worked with.

Authorization hierarchies for approval flows in M3

Today I will illustrate authorization hierarchies for approval flows in Infor M3.

Approval flows

A common requirement in M3 projects is to implement approval flows, for example for purchase orders; where a buyer creates a purchase order of a certain amount; where one or more approvers must review the order, and either approve it, one approver after the other, either reject it for some reason; and where the approvers are selected from a hierarchy of managers and their maximum order amount.

There are many variations of these approval flows, each being specific to the requirements of the M3 project. Here is a simple approval flow with a single approver:
Approval flows get complex quickly, with many decisions to take, many levels of approval to have, many design trade-offs to consider, and all sorts of scenarios to support.

Infor Process Automation

To implement the approval flows we use Infor Process Automation (IPA). We can also use the former Lawson ProcessFlow Integrator (PFI). As for Infor ION, it is the new standard for implementing M3 approval flows, but its usage for M3 is still young, and it does not yet have as many features as IPA does.

M3 Purchase Authority – PPS235

To store the hierarchy of approvers and their maximum order amounts, we use the program M3 Purchase Authority – PPS235. It stores the user (AURE), the maximum order amount this user is authorized to approve (MPOA), and their manager who is the next level for authorization (MNGR). For orders that exceed this amount, authorization is required by a user with authorization rights for a higher amount.



I am not an expert on PPS235, but it looks like it does not enforce integrity, and it is not in normal form, and that can result in logical inconsistencies.

Indeed, the hierarchy of managers and maximum order amount may contradict each other. For instance, PPS235 allowed me to set a user whose maximum order amount was higher than that of its manager, in which case routing the approval to that manager will result in a logical anomaly.

Also, there is a field to set the user’s authorization level (AUTL) which results in an alternate hierarchy of approvers, and PPS235 allowed me to enter illogical values there too.

That results in several possible hierarchies: either based on the managers, either based on the maximum order amounts, either based on the authorization levels, all possibly contradicting each other.

Also, PPS235 erroneously allows cycles in the hierarchy. For instance, it allowed me to set a user to be the manager of its manager. This will cause an infinite loop in the graph traversal.

Also, users can be unreachable if there does not exist a connection to that user in the hierarchy.

There is probably a logical explanation for these design decisions. Meanwhile, you must ensure the integrity of your data before proceeding.


The data of PPS235 is stored in table MPAUTD – Authorization distribution as an adjacency list of users and their managers, for example:

Marie Eric
Keith Daniel
Eric Daniel
Charles Daniel
John Daniel
Daniel Joe
Joe Jeff

The resulting tree looks something like this (I use Graphviz to visualize the hierarchy and ensure it is correct):

To retrieve the hierarchy of managers for a certain user we traverse the adjacency list recursively using SQL’s common table expressions (CTE), for example for user Marie and for a purchase order in company 100:


That results in the hierarchy of approvers and their maximum order amount starting at the specified buyer:

You can now create a loop of UserAction activity nodes in IPA to iterate thru that hierarchy. Here is an excerpt (you will need to add the SQL activity node and everything else that may be needed; you can also use the new ForEach activity node instead of my loop with an if-then-else Branch):


That was a quick illustration of authorization hierarchies for approval flows for purchase orders in M3, more specifically how to store the hierarchy of approvers in PPS235, how to recursively query it from MPAUTD, and how to do a loop of UserAction activity nodes.

(I meant to write about this many years ago. I finally got around to doing it after I learned how to do the recursive SQL portion for a customer last week. I hope it helps you.)

Custom message process in MEC

Here is an unofficial guide on how to create a custom message process in Infor M3 Enterprise Collaborator (MEC).

What is a message process?

A message process in MEC is one of the steps in a process flow. Technically speaking, it is a Java class that reads a stream of bytes as an input, does some processing on it, and writes a stream of bytes as an output, for example transform a flat file to XML, apply XSLT to an XML, remove an envelope, archive the message, or make a SOAP request. Message processes are chained together in a partner agreement in the Partner Admin Tool.



The Partner Admin Tool User Guide has some information about message processes:

Java classes

The message processes are Java classes located in MEC’s core library:


Each message process is a Java class in package com.intentia.ec.server.process:4

Each message process may have a configuration dialog box in package com.intentia.ec.partneradmin.swt.agreement: 3


The message processes are declared in the MEC database in table PR_Process:

Java code

To create your own message process follow these steps:

  1. Use the following skeleton Java code, fill with your code, set the file extension for the output message (in my example it is .something), use the in input and out output streams to process the message as you need, and eventually use the cat logger to write debug info in the log file:
    package somewhere;
    import java.io.InputStream;
    import java.io.OutputStream;
    import org.apache.log4j.Category;
    import com.intentia.ec.server.process.AbstractProcess;
    import com.intentia.ec.server.process.ProcessException;
    public class SomeProcess extends AbstractProcess {
      private static Category cat = Category.getInstance(SomeProcess.class.getName());
      public String getState() {
        return "SomeProcess";
      public boolean hasOutput() {
        return true;
      public String getFileExtension() {
        return ".something";
      public void process(InputStream in, OutputStream out) throws ProcessException {
        // your code here

    Note: I do not have a sample code for the dialog box, but you can get inspiration from one of the existing classes in package com.intentia.ec.partneradmin.swt.agreement.

  2. Compile the Java code with:
    javac -extdirs D:\Infor\LifeCycle\host\grid\M3\grids\M3\applications\MECSRV\MecServer\lib\ SomeProcess.java
  3. Copy the resulting Java class to the classpath of the MEC server and Partner Admin Tool, in the folder corresponding to the package (in my case it was package somewhere):
    D:\Infor\MECTOOLS\Partner Admin\classes\somewhere\SomeProcess.class

    Note: You can probably also put the Java class in a JAR file; to be tested.

  4. In the MEC database, add the process to the PR_Process table, where ?? is a new ID, for example 27:
    INSERT INTO MECDBDEV.dbo.PR_Process (ID, Name, Description, ConfigurationClass, WorkClass, Standard) VALUES (??, 'Thibaud Process', 'My custom message process', null, 'somewhere.SomeProcess', 1)
  5. In the Infor Grid, restart the MECSRV application to pick up the new Java class:
  6. In the Partner Admin Tool, create a partner agreement and add the message process:
  7. Reload the MEC server to pick up the new agreement:
  8. Run the partner agreement, for example I have a channel detection with a HTTPIn receive channel listening on port 8084, and I make an HTTP request to that port number to trigger the partner agreement.
  9. Check the message received (.rcv) and the message produced (in my case it was extension .something); for that, you will need one Archive process in your partner agreement, before and after your custom process:
  10. You can also open the files directly in the folder specified:
  11. And if you used the logger, you can check your logs in the Event tab:

Real-world example

In my case, I needed a custom message process for the following real-world scenario. My current customer does Procurement PunchOut with its partners using cXML, an old protocol from 1999. In that protocol, there is a step (PunchOutOrderMessage) that sends an XML document in a hidden field cxml-urlencoded of an HTML form. That results in a POST HTTP request (to MEC) with Content-Type: application/x-www-form-urlencoded, with the XML document that is URL-encoded as a value of parameter cxml-urlencoded in the request body. Unfortunately, MEC does not have a message process to extract a specific parameter value of a message, and URL-decode it. So I developed my custom message process as explained above, to take the request body, extract the desired parameter value, URL-decode it, and output the resulting XML. I may write a detailed post about it some day, maybe not.


That was a guide on how to create a custom message process in MEC, doing Java development, to take an input message in a partner agreement, do some custom processing on it, and produce an output message. This is an unofficial solution that I figured out by de-compiling and hacking MEC. There may be a simpler solution, I do not know.

That’s it! Thank you for supporting this blog, please like, subscribe, share around you, and come author the next blog post with us.

Procurement PunchOut with cXML

Hi colleagues. It has been a while since I posted anything. Today I will write a quick post as part of an interface I am currently developing to do procurement PunchOut using cXML, an old protocol from 1999, for my customer and its suppliers. This will eventually end up in their Infor M3 and M3 Enterprise Collaborator implementation.

I only needed to test the Message Authentication Code (MAC) so I wrote a quick prototype in Python.

The cXML User’s Guide describes the MAC algorithm using HMAC-SHA1-96:

Here is my implementation in Python:

# Normalize the values
data = [fromDomain.lower(),

# Concatenate the UTF-8-encoded byte representation of the strings, each followed by a null byte (0x00)
data = b''.join([(bytes(x, "utf-8") + b'\x00') for x in data])

# Calculate the Message Authentication Code (MAC)
digest = hmac.new(password.encode("utf-8"), data, hashlib.sha1).digest()

# Truncate to 96 bits (12 bytes)
truncated = digest[0:12]

# Base-64 encode, and convert bytearray to string
mac = str(base64.b64encode(truncated), "utf-8")

# Set the CredentialMac in the XML document
credentialMac = xml.find("Header/Sender/Credential").find("CredentialMac")
credentialMac.attrib["creationDate"] = creationDate
credentialMac.attrib["expirationDate"] = expirationDate
credentialMac.text = mac

Here is my resulting MAC, and it matches that of the cXML User’s Guide, good:

I posted the full source code in my GitHub repository at https://github.com/M3OpenSource/cXML/blob/master/Test.py .

That’s it!

Thank you for continuing to support this blog.

Experimenting with middle-side modifications

With Infor M3, there are server-side modifications, client-side modifications, and the unexplored middle-side modifications. I will experiment with servlet filters for the M3 UI Adapter.

Modification tiers

There are several options to modify M3 functionality:

  • Server-side modifications are M3 Java mods developed with MAK; they propagate to all tiers, including M3 API, but they are often avoided due to the maintenance nightmare during M3 upgrades, and they are banned altogether from Infor CloudSuite multi-tenant. There are also Custom lists made with CMS010 which are great, they are simply configured by power users without requiring any programming, and they survive M3 upgrades.
  • Client-side modifications for Smart Office are Smart Office scripts in JScript.NET, Smart Office SDK features, applications and MForms extensions in C#, Smart Office Mashups in XAML, and Personalizations with wizards. They do not affect M3 upgrades but they apply only to Smart Office. And client-side modifications for H5 Client are H5 Client scripts in JavaScript, and web mashups converted from XAML to HTML5. Likewise, they do not affect M3 upgrades but they apply only to H5 Client.
  • Middle-side modifications are servlet filters for the M3 UI Adapter. They propagate to all user interfaces – Smart Office AND H5 Client – but this is unexplored and perilous. In the old days, IBrix lived in this tier.

M3 UI Adapter

The M3 UI Adapter (MUA), formerly known as M3 Net Extension (MNE), is the J2EE middleware that talks the M3 Business Engine protocol (MEX?) and serves the user interfaces. It was written mostly single-handedly by norpe. It is a simple and elegant architecture that runs As Fast As Fucking Possible (TM) and that is as robust as The Crazy Nastyass Honey Badger [1]. The facade servlet is MvxMCSvt. All the userids/passwords, all the commands, for all interactive programs, for all users, go thru here. It produces an XML response that Smart Office and H5 Client utilize to render the panels. The XML includes the options, the lists, the columns, the rows, the textboxes, the buttons, the positioning, the panel sequence, the keys, the captions, the help, the group boxes, the data, etc.

For example, starting CRS610/B involves:

The following creates a list with columns:

import com.intentia.mc.metadata.view.ListColumn;
import com.intentia.mc.metadata.view.ListView;

ListView listView = new ListView();
ListColumn listColumn = new ListColumn();
listColumn.setCaption(new Caption());
listColumn.setName("col" + Integer.toString(columnCount));
listView.addFilterField(posField, listColumn);
listView.setListColumns((ListColumn[])listColumns.toArray(new ListColumn[0]));

Here is an excerpt of the XML response for CRS610/B that shows the list columns and a row of data:


This experiment involves adding a servlet filter to MvxMCSvt to transform the XML response. Unfortunately, MNE is a one-way function that produces XML in a StringBuffer, but that cannot conversely parse the XML back into its data structures. Thus, we have to transform the XML ourselves. I will not make any technical recommendations for this because it is an experiment. You can refer to the existing MNE filters for examples on how to use the XML Pull Parser (xpp3- that is included in MNE. And you can use com.intentia.mc.util.CstXMLNames for the XML tag names.

To create a servlet filter:

javac -cp servlet-api-2.5.jar;mne-app- TestFilter.java

package net.company.your;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import com.intentia.mc.util.Logger;

public class TestFilter implements Filter {

    private static final Logger logger = Logger.getLogger(TestFilter.class);

    public void init(FilterConfig filterConfig) throws ServletException {}

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Hello, World");
        chain.doFilter(request, response);

    public void destroy() {}


Add the servlet filter to the MNE deployment descriptor at D:\Infor\LifeCycle\host\grid\XYZ\grids\XYZ\applications\M3_UI_Adapter\webapps\mne\WEB-INF\web.xml:


Then, reload the M3UIAdapterModule in the Infor Grid. This will destroy the UI sessions, and users will have to logout and logon M3 again.

Optionally, set the log level of the servlet filter to DEBUG.


MvxMCSvt is the single point of entry. If you fuck it up, it will affect all users, all programs, on all user interfaces. So this experiment is currently a Frankenstein idea that would require a valid business case, a solid design, and great software developers to make it into production.

Also, changes to the web.xml file will be overriden with a next software update.


Is this idea worth pursuing? Is this another crazy idea? What do you think?

Call M3 API from Event Analytics rules

Here is how to call M3 API from a Drools rule in Infor Event Analytics; this is a common requirement.

Sample scenario

Here is my sample business case.

When a user changes the status of an approval line in OIS115 (OOAPRO), I have to find the order type (ORTP) of the order to determine which approval flow to trigger in Infor Process Automation (IPA), but ORTP is not part of the table OOAPRO, for that reason I must previously make a call to OIS100MI.GetHead.

I could call M3 in the approval flow, but false positives would generate noise in the WorkUnits.

Is it possible?

I asked Nichlas Karlsson, Senior Architect – Business Integration at Infor, if it was possible to call M3 API directly in the Drools rule. He is one of the original developers of Event Hub and Event Analytics and very helpful with my projects (thank you) although he does not work with these products any longer. He responded that Event Analytics is a generic software with no specific connection to M3, so unfortunately this is not possible out of the box, however it is a common requirement. He said I could solve it using MvxSockJ to call M3 APIs in my own Java class, included in a jar that I put in the lib folder. He added to not forget that the execution time for all rules within a session must be less than the proxy timeout, i.e. 30s. And I would also need to manage host, port, user, password and other properties in some way.

Instead of MvxSockJ I will use the MI-WS proxy of the Grid as illustrated in my previous post.

Sample Drools rule

Here is my sample Drools rule that works:

package com.lawson.eventhub.analytics.drools;

import java.util.List;
import com.lawson.eventhub.analytics.drools.model.Event;
import com.lawson.eventhub.analytics.drools.model.HubEvent;
import com.lawson.eventhub.EventOperation;
import com.lawson.grid.node.Node;
import com.lawson.grid.proxy.access.SessionId;
import com.lawson.grid.proxy.access.SessionProvider;
import com.lawson.grid.proxy.access.SessionUtils;
import com.lawson.grid.proxy.ProxyClient;
import com.lawson.grid.registry.Registry;
import com.lawson.miws.api.data.MIParameters;
import com.lawson.miws.api.data.MIRecord;
import com.lawson.miws.api.data.MIResult;
import com.lawson.miws.api.data.NameValue;
import com.lawson.miws.proxy.MIAccessProxy;

declare HubEvent

rule "TestSubscription"

rule "TestRule"
		event: HubEvent(publisher == "M3", documentName == "OOAPRO", operation == EventOperation.UPDATE, elementOldValues["STAT"] == 10, elementValues["STAT"] == "20")
		// connect to MI-WS
		Registry registry = Node.getRegistry();
		SessionUtils su = SessionUtils.getInstance(registry);
		SessionProvider sp = su.getProvider(SessionProvider.TYPE_USER_PASSWORD);
		SessionId sid = sp.logon("Thibaud", "******".toCharArray());
		MIAccessProxy proxy = (MIAccessProxy)registry.getProxy(MIAccessProxy.class);
		ProxyClient.setSessionId(proxy, sid);

		// prepare input parameters
		MIParameters p = new MIParameters();
		MIRecord r = new MIRecord();
		r.add("CONO", event.getElementValue("CONO"));
		r.add("ORNO", event.getElementValue("ORNO"));

		// execute and get output
		MIResult s = proxy.execute(p);
		List<MIRecord> records = s.getResult(); // all records
		if (records.isEmpty()) return;
		MIRecord record = records.get(0); // zeroth record
		List<NameValue> nameValues = record.getValues(); // all output parameters
		String ORTP = nameValues.get(4).getValue(); // PROBLEM: somehow nameValues.indexOf("X") returns -1

		// make decision
		if (ORTP.equals("100")) event.postEvent("ApprovalFlowA");
		if (ORTP.equals("200")) event.postEvent("ApprovalFlowB");
		if (ORTP.equals("300")) event.postEvent("ApprovalFlowC");

Note: You will need to drop foundation-client- in the lib folder of Event Analytics, and restart the application


There are some limitations with this code:

  • The execution time must be less than the 30s proxy timeout
  • Limit the number of return columns; there is currently a bug with Serializable in ColumnList, see Infor Xtreme incident 8629267
  • If the M3 API returns an error message it will throw the bug with Serializable in MITransactionException, see Infor Xtreme incident 8629267
  • Somehow NameValue.indexOf(name) always returned -1 during my tests, it is probably a bug in the class, so I had to hard-code the index value of the output field (yikes)
  • I do not know how to avoid the logon to M3 with user and password to get a SessionId; I wish there was a generic SYSTEM account that Event Analytics could use
  • For simplicity of illustration I did not verify all the null pointers; you should do the proper verifications
  • The code may throw MITransactionException, ProxyException and IndexOutOfBoundsException
  • You can move the Java code to a separate class in the lib folder; for that refer to my previous post

Related articles

That’s it. Let me know what you think in the comments below.