To begin a session, you must first connect to the manager. You can do that with the ast_connect statement below. Almost every function returns an integer error code. The codes can be found in the header file. AST_SUCCESS means the action succeeded. Any other code is a failure. You can get a string specifying the error by passing the code to ast_error. It would have been just as efficient to return a pointer to the error message. However, this makes error comparison a little more straightforward in the source code.

struct connect_params {
    int mem;               //buffer size, must be pow of 2
    int sleep_milli;       //sleep time between read attempts
    int tries;             //number of read tries
    int wait_for_resp;     //response method
    int port;              //AMI port
    char *host;            //AMI host
    /* This will be called after ever read.  That does not mean every
    message.  There may be no new useful information in the buffer, or
    there may be several new messages.  The logic is dumb.  Set it to
    NULL if you don't need it (it's NULL by default).  */
    void (*read_callback)(struct ast_connection *);

struct ast_connection {
    volatile int socket;          //connection to AMI (set to -1 when disconnected)
    int cnt;                      //id, incremented after each write
    int ret_msg_len;              //max size of return messages (auto-resized)
    struct connect_params params; //stores setup parameters
    char *response;               //message buffer
    volatile int start;           //start of last message read
    volatile int end;             //end of oldest message in buffer
    int pstart;                   //start of partial message
    int pend;                     //end of partial message
    /* bit: meaning
       ---  -------
         0  buffer overrun, 1
         1  response fail, 1
    volatile int flags;
    pthread_mutex_t mutex;        //to lock this struct
    pthread_t reader;             //reader thread
    char *ret_msg;                //stores last message read

code = ast_connect(&conn,¶ms);

The ast_connection structure is allocated inside the connection function. This object is always passed first to each function. You can think of this as the connection object, and the functions that accept it as an argument are its member functions. The connection object is opaque to you (the user). Although there is a definition of it in the header file, there is no reason you should need to access any of its members. There are accessor/mutators for anything you might need to do. The connection can be freed with ast_close.

The second parameter is optional. You can pass NULL, and the connection function will use the default values. The mem member is the buffer size (power of 2). Host and port refer to the AMI machine name and port. The callback function, if non-NULL, will be invoked after every read. This doesn't mean after every message; so, you have to check yourself. You might use the callback for an event driven application, such as a GUI manager.

The callback mechanism isn't really much help for a web application. The driving force is the browser, not the Asterisk server. For an application like this, we either need to wait for responses, or poll for them periodically. Since I don't want to risk the browser hanging while waiting for the Asterisk server to send back a response, I made a compromise. The read doesn't block, but it will still wait a certain amount of time before giving up waiting for a response. You can set the number of attempts with the tries member. In between each read attempt, it will sleep for sleep_milli milliseconds. With this design, you can effectively wait for as long or as short as you like. The last connect_params member is wait_for_resp. By setting this to AST_NOSEARCH, requests will return immediately without waiting at all. If it's AST_SEARCHFORWARD, requests will wait for responses according to tries and sleep_milli. The buffer will be searched starting from the oldest messages. If it's AST_SEARCHREVERSE, the buffer is searched starting from the newest messages (towards the oldest or the start of the buffer).

After each function, you should check the error code. The next thing you need to do is log in. The following statement passes the connection object, which is fully initialized at this point (and a reader thread is running in the background). The next two parameters are the username and password.

code = ast_login(conn,"todd","todd");

This is actually a macro which can be found in ast_man.h. All the commands are, in fact, macros which invoke a variadic parameter list function, ast_call. This makes the library easy to extend. All you would need to do is define a new macro. The reason this is possible is because I have made the underlying implementation as simplistic as possible. The code makes few assumptions about what you might want it to do. The request, forwards the command to Asterisk and does little more. Reading the request from the buffer does essentially no postprocessing on the message. There are some support functions, such as ast_stripresponse, which will operate on the response message. However, you need to call this extra function if you wish to use it. This is a design tradeoff. By making the basic functionality as simple as possible, the code is faster and doesn't do anything you don't need it to do. But if you need the extra support code, you have to do all the work yourself (which means more code and time).

As you peruse the list of functions that comprise the interface, you'll see peek and get. Peek means that the message will be copied into another buffer which you can then read by calling ast_get_response. Get will do the same thing, but it will also remove the message from the read buffer. The only other difference is that you can peek at any message but only get the first or last message. You will also see _r and _f. If you see _r, that means the search starts from the end of the buffer (reverse). The _f means it starts from the front. Generally, it's faster to read from the front of the buffer because there are no partial messages to skip over (and no memory to copy). When I first designed the interface, I only allowed for reading from the end of the buffer where the messages are written to. But I later discovered that some requests can have multiple responses. Since the responses can be returned at any time, there is no way to preserve response order when reading from the end. But you can preserve order if you always read from the front. So, I added the _f functions. I decided to keep the _r functions. Conceivably they're still useful. However, you can choose to build the library without them.

Any function that has by_id as part of its name will search for a message by the id you specify. Every request/response pair automatically has an id associated with it. You can get the id of the last response by calling ast_get_last_id.

If you see wait in the name, it means the function will block according to the members of connect_params. Otherwise, it will check the buffer once immediately and then return. The functions ast_overrun, ast_success, and ast_connected tell you whether the buffer has been overrun, if the last command succeeded, and if you are connected to an Asterisk server, respectively.

The remaining function is ast_strip_response. This will move the 'ActionID: #' and Response: Success' lines in the results buffer to just after the NULL character. The function will return a pointer to these lines, in case you still need them. Ast_strip_response2 does the same thing, but it lets you specify which lines you wish to remove from the response. The headers parameter is an array of pointers to strings, terminated by a NULL pointer. Any action name you place in here will be cut from the response message and moved to the end of the string. See the diagram below. As mentioned before, these are support functions. You may not need them, but they're there to help.

Response: Success\r\nAction: Ping\r\nActionID: 3\r\n\r\n\0


Action: Ping\r\n\r\n\0Response: Success\r\nActionID: 3\r\n\0
                      ^---return pointer

One thing to notice about the interface is that most of the functions take only one or two parameters (the first is always ast_connection). Wherever possible, I have tried to remove parameter options to keep the parameter lists short. That keeps the code short and makes the interface easy to understand. In some cases, where specifying options was necessary, I added the options to connect_params. This way they can be set once at the beginning. If absolutely necessary, they can be changed by operating on the ast_connection structure.

prev next