1
votes

My question is about the Win32-Ada bindings provided by the packages:

I would like to use the following function defined in the package Win32.Mgmtapi:

function SnmpMgrOidToStr
  (oid    : access Win32.Snmp.AsnObjectIdentifier;
   string : access Win32.LPSTR)
  return Win32.BOOL;

I have access to a variable Variable_Binding of the type Win32.Snmp.a_RFC1157VarBind_t. The following four types are defined in the package Win32.Snmp:

type AsnObjectIdentifier is record
   idLength : Win32.UINT;
   ids      : Win32.PUINT;
end record;

subtype AsnObjectName is AsnObjectIdentifier;

type RFC1157VarBind is record
   name  : AsnObjectName;
   value : AsnObjectSyntax;
end record;

type a_RFC1157VarBind_t is access all RFC1157VarBind;

Shortened example code:

with Win32;
with Win32.Mgmtapi;
with Win32.Snmp;

procedure Test is
begin
   -- [...]

   declare
      Variable_Binding : aliased Win32.Snmp.a_RFC1157VarBind_t := (...);

      OID_String : access Win32.LPSTR;

      Return_Value : Win32.BOOL;
   begin
      Return_Value := Win32.Mgmtapi.SnmpMgrOidToStr
        (oid    => Variable_Binding.name, -- type is not compatible
         string => OID_String);
   end;
end Test;

My three questions:

  1. The function SnmpMgrOidToStr requires an access type for the parameter name. The component name defined in the type RFC1157VarBind is not compatible to this access type. What are my options to convert the component name of the variable Variable_Binding to an access type?
  2. I assume that the function SnmpMgrOidToStr allocates the required memory for the variable OID_String of the type Win32.LPSTR. How can I convert the type Win32.LPSTR to an Ada string (String/Unbounded_String)?
  3. How can I free the allocated memory after a converstion to an Ada string?

Update:

I added a full SNMP example. This requires an enabled and configured Windows SNMP service. The program output should be:

Opened session.
Converted string to object identifier.
Copied object identifier.
Sent request.
Type is: 4
WORKSTATION
system.sysName.0
Closed session.

The documentation of the function SnmpMgrRequest states:

Note The SnmpVarBind array pointed to by the SnmpVarBindList structure must be allocated using the SnmpUtilMemAlloc function.

And according to the documentation of the function SnmpUtilMemAlloc the function SnmpUtilMemFree should be used to free the allocated memory.

I think that I am allocating the memory correctly using ths procedure call:

-- Allocate memory
-- Allocated memory is not freed properly
SnmpUtilMemAlloc (Win32.UINT (Win32.Snmp.RFC1157VarBind'Size * System.Storage_Unit));

New questions:

  1. Is this procedure call correct?
  2. How can I use the function SnmpUtilMemFree to free the allocated memory?

At the moment the memory is not freed properly according to the tool Dr. Memory (Version 2.2.0-1):

Error #1: LEAK 1536 direct bytes 0x0418b850-0x0418be50 + 0 indirect bytes
# 0 replace_RtlAllocateHeap               [d:\drmemory_package\common\alloc_replace.c:3771]
# 1 KERNELBASE.dll!GlobalAlloc           +0x6d     (0x75404015 <KERNELBASE.dll+0x14015>)
# 2 snmpapi.dll!SnmpUtilMemAlloc         +0xf      (0x73db1ee8 <snmpapi.dll+0x1ee8>)
# 3 _ada_snmp_example                     [C:/Users/username/Desktop/SNMPExample/src/snmp_example.adb:123]
# 4 main                                  [C:\Users\username\Desktop\SNMPExample\obj/b__snmp_example.adb:259]

Full source code:

with Ada.Text_IO;
with Ada.Unchecked_Conversion;
with Interfaces.C.Strings;
with System;

with Win32;
with Win32.Mgmtapi;
with Win32.Snmp;

procedure SNMP_Example
is
   -- Imported functions and procedures
   procedure SnmpUtilMemAlloc (nBytes : Win32.UINT);

   pragma Import (Stdcall, SnmpUtilMemAlloc, "SnmpUtilMemAlloc");

   procedure SnmpUtilMemFree (pMem : Win32.LPVOID);

   pragma Import (Stdcall, SnmpUtilMemFree, "SnmpUtilMemFree");

   function SnmpUtilOidCpy
     (DestObjId : access Win32.Snmp.AsnObjectIdentifier;
      SrcObjId  : access Win32.Snmp.AsnObjectIdentifier)
      return Win32.INT;

   pragma Import (Stdcall, SnmpUtilOidCpy, "SnmpUtilOidCpy");

   procedure SnmpUtilOidFree (Obj : access Win32.Snmp.AsnObjectIdentifier);

   pragma Import (Stdcall, SnmpUtilOidFree, "SnmpUtilOidFree");

   procedure SnmpUtilVarBindFree (VarBind : access Win32.Snmp.RFC1157VarBind);

   pragma Import (Stdcall, SnmpUtilVarBindFree, "SnmpUtilVarBindFree");

   procedure SnmpUtilVarBindListFree (VarBindList : access Win32.Snmp.RFC1157VarBindList);

   pragma Import (Stdcall, SnmpUtilVarBindListFree, "SnmpUtilVarBindListFree");

   -- Conversion related
   function To_LPVOID is new Ada.Unchecked_Conversion (Win32.LPSTR, Win32.LPVOID);

   function To_PCSTR is new Ada.Unchecked_Conversion (Win32.PBYTE, Win32.PCSTR);

   -- Linker options
   pragma Linker_Options ("-lmgmtapi");
   pragma Linker_Options ("-lsnmpapi");

   -- Connection related (Ada)
   IP_Address        : constant String  := "127.0.0.1";
   Community_String  : constant String  := "public";
   Timeout_MS        : constant Integer := 500;
   Number_Of_Retries : constant Integer := 1;

   -- Connection related (C)
   IP_Address_C       : Interfaces.C.Strings.chars_ptr := Interfaces.C.Strings.New_String (IP_Address);
   Community_String_C : Interfaces.C.Strings.chars_ptr := Interfaces.C.Strings.New_String (Community_String);

   -- Connection related (Win32)
   IP_Address_Win32        : constant Win32.LPSTR := Win32.To_PSTR (IP_Address_C);
   Community_String_Win32  : constant Win32.LPSTR := Win32.To_PSTR (Community_String_C);
   Timeout_MS_Win32        : constant Win32.INT := Win32.INT (Timeout_MS);
   Number_Of_Retries_Win32 : constant Win32.INT := Win32.INT (Number_Of_Retries);

   -- Comparison of types related
   use type Interfaces.C.int;
   use type Win32.BOOL;
   use type Win32.Mgmtapi.LPSNMP_MGR_SESSION;

   -- Session
   SNMP_Session : Win32.Mgmtapi.LPSNMP_MGR_SESSION;

   -- Custom types
   type Counter_Type is mod 2**32 - 1;
   type Gauge_Type is mod 2**32 - 1;
begin
   -- Open session
   SNMP_Session := Win32.Mgmtapi.SnmpMgrOpen
     (lpAgentAddress   => IP_Address_Win32,
      lpAgentCommunity => Community_String_Win32,
      nTimeOut         => Timeout_MS_Win32,
      nRetries         => Number_Of_Retries_Win32);

   if SNMP_Session = null then
      Ada.Text_IO.Put_Line ("Failed to open session.");
   else
      Ada.Text_IO.Put_Line ("Opened session.");

      Send_Request : declare
         -- Request related (Ada)
         SNMP_Object_Identifier_Ada : constant String := ".1.3.6.1.2.1.1.5.0";

         -- Request related (C)
         SNMP_Object_Identifier_C : Interfaces.C.Strings.chars_ptr := Interfaces.C.Strings.New_String (SNMP_Object_Identifier_Ada);

         -- Request related (Win32)
         SNMP_Object_Identifier_Win32 : constant Win32.LPSTR := Win32.To_PSTR (SNMP_Object_Identifier_C);

         SNMP_Object_Identifier : aliased Win32.Snmp.AsnObjectIdentifier;

         Variable_Bindings       : aliased Win32.Snmp.RFC1157VarBindList;
         Variable_Bindings_Entry : aliased Win32.Snmp.RFC1157VarBind;

         Error_Status : aliased Win32.Snmp.AsnInteger;
         Error_Index  : aliased Win32.Snmp.AsnInteger;

         Return_Value_String_To_OID_Conversion : Win32.BOOL;
         Return_Value_OID_Copy                 : Win32.INT;
         Return_Value_Request                  : Win32.INT;
         Return_Value_OID_To_String_Conversion : Win32.BOOL;
      begin
         Return_Value_String_To_OID_Conversion := Win32.Mgmtapi.SnmpMgrStrToOid
           (string => SNMP_Object_Identifier_Win32,
            oid    => SNMP_Object_Identifier'Access);

         if Return_Value_String_To_OID_Conversion = 0 then
            Ada.Text_IO.Put_Line ("Failed to convert string to object identifier.");
         else
            Ada.Text_IO.Put_Line ("Converted string to object identifier.");

            -- Allocate memory
            -- Allocated memory is not freed properly
            SnmpUtilMemAlloc (Win32.UINT (Win32.Snmp.RFC1157VarBind'Size * System.Storage_Unit));

            Return_Value_OID_Copy := SnmpUtilOidCpy
              (DestObjId => Variable_Bindings_Entry.name'Unrestricted_Access,
               SrcObjId  => SNMP_Object_Identifier'Access);

            if Return_Value_OID_Copy = 0 then
               Ada.Text_IO.Put_Line ("Failed to copy object identifier.");
            else
               Ada.Text_IO.Put_Line ("Copied object identifier.");

               -- Construct variable bindings entry
               Variable_Bindings_Entry.value.asnType := Win32.Snmp.ASN_NULL;

               -- Construct variable bindings
               Variable_Bindings.len  := 1;
               Variable_Bindings.list := Variable_Bindings_Entry'Unchecked_Access;

               Return_Value_Request := Win32.Mgmtapi.SnmpMgrRequest
                 (session          => SNMP_Session,
                  requestType      => Win32.Snmp.ASN_RFC1157_GETREQUEST,
                  variableBindings => Variable_Bindings'Access,
                  errorStatus      => Error_Status'Access,
                  errorIndex       => Error_Index'Access);

               if Return_Value_Request = 0 then
                  Ada.Text_IO.Put_Line ("Failed to send request.");
               else
                  Ada.Text_IO.Put_Line ("Sent request.");

                  Ada.Text_IO.Put_Line ("Type is:" & Win32.BYTE'Image (Variable_Bindings.list.value.asnType));

                  case Variable_Bindings.list.value.asnType is
                     when Win32.Snmp.ASN_INTEGER =>
                        Ada.Text_IO.Put_Line (Integer'Image (Integer (Variable_Bindings.list.value.asnValue.number)));
                     when Win32.Snmp.ASN_OCTETSTRING =>
                        Ada.Text_IO.Put_Line (Interfaces.C.Strings.Value (Win32.To_Chars_Ptr (To_PCSTR (Variable_Bindings.list.value.asnValue.string.stream))));
                     when Win32.Snmp.ASN_RFC1155_COUNTER =>
                        Ada.Text_IO.Put_Line (Counter_Type'Image (Counter_Type (Variable_Bindings.list.value.asnValue.counter)));
                     when Win32.Snmp.ASN_RFC1155_GAUGE =>
                        Ada.Text_IO.Put_Line (Gauge_Type'Image (Gauge_Type (Variable_Bindings.list.value.asnValue.counter)));
                     when Win32.Snmp.ASN_RFC1155_TIMETICKS =>
                        Ada.Text_IO.Put_Line (Duration'Image (Duration (Variable_Bindings.list.value.asnValue.ticks) / 100));
                     when others =>
                        Ada.Text_IO.Put_Line ("Unsupported type.");
                  end case;

                  -- Convert object identifier to string
                  Convert_OID_To_String : declare
                     OID_String_Win32 : aliased Win32.LPSTR;
                  begin
                     Return_Value_OID_To_String_Conversion := Win32.Mgmtapi.SnmpMgrOidToStr
                       (oid    => Variable_Bindings.list.name'Unrestricted_Access,
                        string => OID_String_Win32'Unchecked_Access);

                     if Return_Value_OID_To_String_Conversion = 0 then
                        Ada.Text_IO.Put_Line ("Failed to convert object identifier to string.");
                     else
                        Ada.Text_IO.Put_Line (Interfaces.C.Strings.Value (Win32.To_Chars_Ptr (OID_String_Win32)));
                     end if;

                     SnmpUtilMemFree (To_LPVOID (OID_String_Win32));
                  end Convert_OID_To_String;

                  -- Free memory
                  SnmpUtilOidFree (SNMP_Object_Identifier'Access);

                  SnmpUtilVarBindFree (Variable_Bindings_Entry'Access);

                  SnmpUtilVarBindListFree (Variable_Bindings'Access);

                  Interfaces.C.Strings.Free (IP_Address_C);

                  Interfaces.C.Strings.Free (Community_String_C);

                  Interfaces.C.Strings.Free (SNMP_Object_Identifier_C);
               end if;
            end if;
         end if;
      end Send_Request;

      Close_Session : declare
         Return_Value : constant Win32.BOOL := Win32.Mgmtapi.SnmpMgrClose (SNMP_Session);
      begin
         if Return_Value = 0 then
            Ada.Text_IO.Put_Line ("Failed to close session.");
         else
            Ada.Text_IO.Put_Line ("Closed session.");
         end if;
      end Close_Session;
   end if;
end SNMP_Example;
1

1 Answers

2
votes

Unfortunately, I cannot create a minimal working example to test the code, so I cannot guarantee that the code below will actually work:

test.adb

with Ada.Text_IO;
with Ada.Unchecked_Conversion;
with Interfaces.C.Strings;

with Win32;
with Win32.Mgmtapi;
with Win32.Snmp;

procedure Test is

   pragma Linker_Options ("-lMgmtapi");    --  for SnmpMgrOidToStr
   pragma Linker_Options ("-lSnmpapi");    --  for SnmpUtilMemFree

begin

   -- [...]

   declare

      --  Field "name" in type "RFC1157VarBind" is not aliased so you cannot
      --  use 'Unchecked_Access. I see two options here:
      --
      --    (1) Use 'Unrestricted_Access on the field "name".
      --    (2) Copy "VarBind_Ptr.name" into a new aliased variable and use 'Unchecked_Access.
      --
      --  I *assume* that the library behind "SnmpMgrOidToStr" will not "store"
      --  our pointer and dereferece it after we left this block of code (resulting
      --  in an invalid dereference). If this assumption holds, then we
      --  can use 'Unchecked_Access.
      --
      --  As "VarBind_Ptr" is already an access type, its field must also have a
      --  memory address (depite it not being defined as aliased). Hence, I
      --  think it's safe to use the very permissive 'Unrestricted_Access
      --  attribute here to obtain a reference.

      VarBind_Ptr  : Win32.Snmp.a_RFC1157VarBind_t;

      OID            : aliased Win32.Snmp.AsnObjectIdentifier := VarBind_Ptr.name;
      OID_String_Ptr : aliased Win32.LPSTR;

      Return_Value : Win32.BOOL;

   begin

      --  Option 1
      Return_Value := Win32.Mgmtapi.SnmpMgrOidToStr
        (oid    => VarBind_Ptr.name'Unrestricted_Access,
         string => OID_String_Ptr'Unchecked_Access);

      --  Option 2
      Return_Value := Win32.Mgmtapi.SnmpMgrOidToStr
        (oid    => OID'Unchecked_Access,
         string => OID_String_Ptr'Unchecked_Access);


      declare

         --  Win32.ads contains a convenience function "To_Chars_Ptr" to
         --  convert "Win32.LPSTR" to "Interfaces.C.Strings.chars_ptr". Hence,
         --  we can convert "OID_String_Ptr" to an Ada string via "chars_ptr":
         --
         --     Win32.LPSTR ---> chars_ptr ---> String
         --
         --  Note that the character array to which "Win32.LPSTR" points is, in
         --  this particular case, null terminated (see [1]). This might not
         --  always be the case [3].

         use Ada.Text_IO;
         use Interfaces.C.Strings;

      begin
         Put_Line (Value (Win32.To_Chars_Ptr (OID_String_Ptr)));
      end;


      declare

         --  The documentation [1] states that the string returned by
         --  "SnmpMgrOidToStr" should be freed using "SnmpUtilMemFree" [2]. This
         --  function seems to be missing in the win32ada library (?), so we
         --  need to import it ourselves.

         procedure SnmpUtilMemFree (pMem : Win32.LPVOID)
           with Import, Convention => Stdcall, Link_Name => "SnmpUtilMemFree";           

         function To_LPVOID is
            new Ada.Unchecked_Conversion (Win32.LPSTR, Win32.LPVOID);

      begin
         SnmpUtilMemFree (To_LPVOID (OID_String_Ptr));
      end;

   end;

end Test;

References:

  • MS Docs: SnmpMgrOidToStr [1]
  • MS Docs: SnmpUtilMemFree [2]
  • MS Docs: LPSTR [3]