Essentially two translation tables. User command-to-OID and OID-to-machine-format. The actual translation details are highly dependent on the application presentation and the equipment command base protocol.
In this usage SNMP is used to replace a traditional RS232C communication. Since the format of communication between the user API and the equipment is already known the translation mearly consist of translating the old RES232C command to an SNMP payload (OID + data) and in the agent reverting the SNMP packet into a RS232C command again.
public static OID ConvertCommandToOID(Byte[] cmd, Byte cmd_length, Int16 cmd_part) { // Command format is // <stx><cmd......><cr> for equipment OID oid = null; if (cmd_length > 2) //Minimum command length is 3 { switch (cmd[1]) { case 0x39: // Configuration set oid = new OID("1.3.6.1.4.1.26637.127.5.12.0"); oid.Get = false; // Convert configuration to an encoded string and place in oid.DataString // The configuration is [3] to cr EncodeString(ref oid, cmd, 2); break; case 0x45: // Get/Set name if (cmd[2] == 0xAA) { // Get name oid = new OID("1.3.6.1.4.1.26637.127.5.1.0"); } else { // Set the equipment name oid = new OID("1.3.6.1.4.1.26637.127.5.19.2.0"); oid.Get = false; // Insert name String sn = "; // How long is the name? Byte lc = 0; for (lc = 0; (lc < cmd.Length) && (cmd[lc + 3] > 0x1F); lc++) { } // The length is in lc + 1 for the cr sn += (char)(lc + 0x30); for (Int16 iname = 3; cmd[iname] > 0x1F; iname++) sn += (char)cmd[iname]; sn += (char)0x0D; oid.DataString = sn; } break; case 0x5D: // Get Temperature oid = new OID("1.3.6.1.4.1.26637.127.5.7.0"); break; default: oid = new OID("£1.3.6.1.2.1.1"); // Default OID break; } } return oid;
In practice the switch will be enormous (in concrete, for the example given there were 94 command variants.) A GET is exampled and two types of SET, one uses a combined GET-SET command format, the other a dedicated SET command with a hefty payload. The STRING format is used for the SNMP data payload.
The OID is defined as a class, the information for creating the snmp packet payload is held here.
public class OID { private String sOID = "0"; private String sOID_reply = " "; private Byte[] data_buffer = new Byte[1024]; private Byte oid_size = 0; private Boolean bGet = true; private String data_string = "; private Int32 data_integer = 0; private Int32 data_buffer_size = 0; public OID() { } public OID(String _oid) { oid_size = (Byte)_oid.Length; bGet = true; sOID = _oid; } public OID(String _oid, Boolean _bGet, String _sData) { oid_size = (Byte)_oid.Length; bGet = _bGet; sOID = _oid; data_string = _sData; } public OID(String _oid, String _sData) { oid_size = (Byte)_oid.Length; bGet = false; sOID = _oid; data_string = _sData; } public OID(String _oid, Int32 _iData) { oid_size = (Byte)_oid.Length; bGet = false; sOID = _oid; data_integer = _iData; } public Int16 OIDSize { get { return oid_size; } } public OID(OID _oid) { // Copy all sOID = _oid.GetOIDString; sOID_reply = _oid.GetOIDReplyString; data_buffer = _oid.data_buffer; oid_size = _oid.oid_size; bGet = _oid.bGet; data_string = _oid.data_string; data_integer = _oid.data_integer; data_buffer_size = _oid.data_buffer_size; } public void Clone(OID _oid) { // Copy all sOID = _oid.GetOIDString; sOID_reply = _oid.GetOIDReplyString; data_buffer = _oid.data_buffer; oid_size = _oid.oid_size; bGet = _oid.bGet; data_string = _oid.data_string; data_integer = _oid.data_integer; data_buffer_size = _oid.data_buffer_size; } public Boolean Get { get { return bGet; } set { bGet = value; } } public String DataString { get { return data_string; } set { data_string = value; } } public Int32 DataInteger { get { return data_integer; } set { data_integer = value; } } public Int32 DataBufferSize { get { return data_buffer_size; } } public Byte[] DataBuffer { get { Byte[] a = new Byte[data_buffer_size]; for (Int16 i = 0; i < data_buffer_size; i++) a[i] = data_buffer[i]; return a; } set { for (Int16 i = 0; i < value.Length; i++) data_buffer[i] = value[i]; } } public String GetOIDString { get { return sOID; } } public String GetOIDReplyString { get { return sOID_reply; } set { sOID_reply = value; } } }
The snmp packet has been formed from the OID information, sent on, the agent generates a reply and sends back, The API will be listening on port 162 for incoming traffic, so the payload of the received packet will need to be translated back into a usable format. In this case it will be translated back into the equivalent RS232c reply.
public static void ConstructReplyFromOID(ref Byte[] reply_buffer, ref Byte reply_buffer_length, OID oid, Byte[] cmd) { // Reply depends upon what was sent // A reply received in the oid. A Single byte is expected for integer or string Byte _cmd = 0; Int16 ir = 0; Int16 i = 0; Int16 j = 0; String sr = "0"; Int32 vr = 0; // We only accept decoding the packet if the returned OID is the expected OID // The last OID sent is sLastObjectOID and the current reply sCurrentObjectOID // A string comparision sorts this out. problem this is a static method, so the oid have to be entered if (!Oid_Compare(oid.GetOIDString, oid.GetOIDReplyString)) { reply_buffer[0] = Usb.nak; reply_buffer[1] = 0x55; reply_buffer[2] = Usb.cr; reply_buffer_length = 3; // Do a re-synchronisation return; } reply_buffer[0] = Usb.stx; reply_buffer[1] = (Byte)(cmd[1] | 0x80); _cmd = (Byte)cmd[1]; switch (_cmd) { case 0x39: // This was a set configuration confirmation reply_buffer[2] = 0x0D; reply_buffer_length = 3; break; case 0x45: // A string reply from a single OID sr = oid.DataString; ir = 0; for (ir = 0; ir < sr.Length; ir++) reply_buffer[ir + 2] = (Byte)sr[ir]; reply_buffer[ir + 2] = 0x0D; reply_buffer_length = (Byte)(ir + 3); break; case 0x5D: // Temperature, 1, 2 // A string reply from a single OID sr = oid.DataString; // The first byte is the number of items in the string if (sr.Length > 2) ir = (Byte)(sr[0] - 0x30); else ir = 0; j = 2; if (sr.Length > ir) for (i = 0; i < ir; i++) { if (sr[i + 1] == 0x1F) { // The next byte is signed i++; reply_buffer[j++] = (Byte)(sr[i + 1] | 0x80); } else reply_buffer[j++] = (Byte)sr[i + 1]; } reply_buffer[j++] = 0x0D; reply_buffer_length = (Byte)j; break; default: break; } }
Since this is an RS232C eliminator information cannot come out of order and a simple blocking comms can be used. We kn ow the information and OID sent, we accept the new information if the expected OID has been received, so it is simple to reconstruct the desired reply. This example uses the same three possible commands of the send. We enter with the OID information, command (oid, cmd) that was used and return with reply (reply_buffer, reply_buffer_length)
public static Boolean Oid_Compare(String oid_a, String oid_b) { // Compare two OID strings if (oid_a.Length != oid_b.Length) return false; return oid_a.Contains(oid_b); }
Agents are usually run on a micro-controller (RTP), so the coding is highly dependent upon the TCP/IP stack used. The example given here is for a Coldfire microcontroller using a UNIX based operating system. In this case the actual MIB tree for the equipment is used to create a trigger response array. The snmp packets are passed through the array if a coincidence is found a handler function is invoked.
static const MIBVAR mibVar[] = { // MIB variable data fields // OID, options, type, sizeof data, pointer to data // (type, sizeof data, pointer to data) refer to the BER encoding // The first number is the OID length. The maximum OID length is 32 // The OID is the enterprise pre-fix + the private tree // Define the private Tree in 6 layers // Type of equipment eg Transmitter // Specific equipment number // Specific Module/Board // Type of measurement eg alarm, status, configuration etc... // Data subset // Specific data // All MIB sets are terminated in 0 // Equipment TX = 1, TX Series 1KW = 1, Control Board = 1, Status = 1, subset = 1, START_LED = 1 // MIB_INDEX_START_STATE (enterprise 8).1.1.1.1.1.1 // Can write, integer value(in reality a byte), // THE MIBVAR have to be in numerical order ie: 1.1.x, 1.2.x, 1.3.x // Index 0 {{15,{0x2b,6,1,4,1,0x82,0x44,0x5F,1,1,1,1,1,START_LED_MIB,0}}, MIBOPT_CAR, SNMP_Integer, sizeof(Start_Led), &Start_Led}, // Blaa.....etc // Index 20 // Equipment TX = 1, TX Series 1KW = 1, Control Board = 1, Status = 1, subset = 1, VACstr = 21 // MIB_INDEX_VAC (enterprise 8).1.1.1.1.1.21 // Cannot write, string value, A SET has to be ignored {{15,{0x2b,6,1,4,1,0x82,0x44,0x5F,1,1,1,1,1,VAC_MIB,0}}, MIBOPT_CAR, SNMP_String, sizeof(VACstr), &ptr_VACstr}, };
As can be seen the OID format is used to populate the array, 0x2b,6,1,4,1, is the initial ISO part, ,0x82,0x44,0x5F the ANS.1 encoded enterprise number and 1,1,1,1,1,VAC_MIB,0 the private OID part. VAC_MIB is a defined constant. When a SNMP GET is detected, the array is scanned, if coincidence with this OID found a string VACstr is returned.
The case of a SET is similar, except two extra functions are called when a SETable OID is detected, one to check the bind data validity and and the other to actually SET the data.
static int mibCheck2_misc(int varix, const uint8_t * index, uint32_t indexLen, const uint8_t * inp) { // This function checks the validity of a variable going to a set command // If index2_misc is used then use check2_misc, otherwise index goes with check // Parameters: // varix (in) current 'MIBVAR' index // index (in) encoded OID Table index // indexLen (in) length of the encoded OID Table index // inp (in) pointer to the BER encoded value to be set // Return values: // int an SNMP_RV code int return_code = SNMP_RV_ok; int iValue = 64; int reply = 0; int count = 0; int size = 0; int i = 0; char c; char sTemp[48]; bool bEnd = false; char sValue[48]; switch (varix) { case MISC_SNTP_ACTIVE_INDEX: // Values 0, 1 only reply = berReadInt ((uint32_t HUGE *) &iValue, sizeof(iValue), (const uint8_t HUGE **) &inp, SNMP_Integer ); if(reply == 1) { if(iValue == 1) { bNTPactivate = true; return_code = SNMP_RV_ok; } else if(iValue == 0) { bNTPactivate = false; return_code = SNMP_RV_ok; } else return_code = SNMP_RV_badValue; } else { return_code = SNMP_RV_badValue; } break; // Blaaa.... etc static int mibSet2_misc(int varix, const uint8_t *index, uint32_t indexLen, uint8_t *data) { int return_code = SNMP_RV_ok; switch (varix) { case MISC_SNTP_ADDRESS_INDEX: // Update flash data Add_Command_To_List(0xA1); if(bNTPactivate) Add_Command_To_List(0xB0); break; // Blaaa....etc
Here we have the initial part of a MIB check switch. Here MISC_SNTP_ACTIVE_INDEX referes to the index number of the GET reference to this variable in the MIB array.
This is then followed by the actual set function. Again the index reference to the GET MIB array entry is used in a switch to generate a command to use the the data received and validated data. In this particular case a FIFO accumulates the commands for execution on a different thread.