Skip to content

Commit

Permalink
Merge pull request #61 from kohanis/json_parsing_improvements
Browse files Browse the repository at this point in the history
Json parsing improvements
  • Loading branch information
staskobzar authored Mar 9, 2023
2 parents 7ce1dc7 + 8ed4dbf commit 3b1db13
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 23 deletions.
43 changes: 28 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## amiws - Asterisk Manager Iterface (AMI) to web-socket proxy
## amiws - Asterisk Manager Interface (AMI) to web-socket proxy

[![Build Status](https://travis-ci.org/staskobzar/amiws.svg?branch=master)](https://travis-ci.org/staskobzar/amiws)
![](https://img.shields.io/badge/license-GPL_3-green.svg "License")
Expand All @@ -7,15 +7,15 @@


### Introduction
*amiws* is simple proxy from AMI to WEB. It can connect to one or more Asterisk PBXs via AMI (Asterisk Manager Interface), read messages from AMI stream and send actions/commands to it. Received messages are parsed and converted to JSON.
*amiws* is simple proxy from AMI to WEB. It can connect to one or more Asterisk PBXs via AMI (Asterisk Manager Interface), read messages from AMI stream and send actions/commands to it. Received messages are parsed and converted to JSON.
*amiws* also provides HTTP/WebSocket interface and sends JSON messages to all connected users via HTTP.


Here is simple workflow scheme:
![amiws workflow](https://github.com/staskobzar/amiws/blob/master/doc/amiws.workflaw.png)


*amiws* proxy can help to build interctive, realtime dashboards for Asterisk PBX single or multiple servers. The example of a simple dashboard can be found in directory "web_root". Here is how it would look like when connecting two Asterisk PBXs:
*amiws* proxy can help to build interactive, real-time dashboards for Asterisk PBX single or multiple servers. The example of a simple dashboard can be found in directory "web_root". Here is how it would look like when connecting two Asterisk PBXs:
![web interface](https://github.com/staskobzar/amiws/blob/master/doc/amiws.user.screen.png)


Expand All @@ -24,8 +24,8 @@ Here is simple workflow scheme:
* Plain TCP or SSL/TLS connection to AMI
* Logging with syslog
* HTTP and WebSocket server
* SSL/TLS encypted connection for HTTP and WebSocket
* WWW Digest authenticatation with username/password for HTTP(s)
* SSL/TLS encrypted connection for HTTP and WebSocket
* WWW Digest authentication with username/password for HTTP(s)
* YAML configuration file


Expand All @@ -47,12 +47,12 @@ Options:

*amiws* relies on several greate projects:
* [mongoose](https://github.com/cesanta/mongoose) - awesome embedded networking library from [Cesanta](https://www.cesanta.com/).
* [fozen](https://github.com/cesanta/frozen) - awesome JSON parser and emitter from [Cesanta](https://www.cesanta.com/).
* [re2c](http://re2c.org/) - awesome lexer generator for AMI protocol implementation.
* [frozen](https://github.com/cesanta/frozen) - awesome JSON parser and emitter from [Cesanta](https://www.cesanta.com/).
* [re2c](http://re2c.org/) - awesome lexer generator for AMI protocol implementation.
* [cmocka](https://cmocka.org/) - awesome unit testing framework for C.
* [lemon](http://www.hwaci.com/sw/lemon/lemon.html) - parser generator to process YAML tokens

They do not need to me installed. *mongoose* and *frozen* are already included. *re2c* and *cmocka* are only needed for developers.
They do not need to be installed. *mongoose* and *frozen* are already included. *re2c* and *cmocka* are only needed for developers.

### Building and install
```
Expand All @@ -79,12 +79,12 @@ To run unit tests (requires cmocka):
make check
```

This repo also provides init scripts for [System V](https://github.com/staskobzar/amiws/blob/master/etc/amiws.sysv.init)
This repo also provides init scripts for [System V](https://github.com/staskobzar/amiws/blob/master/etc/amiws.sysv.init)
and [systemctl](https://github.com/staskobzar/amiws/blob/master/etc/amiws.service) in "etc/" directory.

### Configuration

Program behaviour is controlled by configuration file. Configuration parameters are described in sample file ["amiws.annotated.yaml"](https://github.com/staskobzar/amiws/blob/master/etc/amiws.annotated.yaml) in directory "etc" of this repository.
Program behaviour is controlled by configuration file. Configuration parameters are described in sample file ["amiws.annotated.yaml"](https://github.com/staskobzar/amiws/blob/master/etc/amiws.annotated.yaml) in directory "etc" of this repository.

### JSON message

Expand Down Expand Up @@ -137,33 +137,46 @@ _AMI message types_:

AMI description in details can be found in [Asterisk wiki](https://wiki.asterisk.org/wiki/display/AST/Home).

*amiws* also accepts JSON messages and send them back to all Asterisk servers:
*amiws* also accepts JSON messages and sends them back to all Asterisk servers:
```javascript
sock.send(JSON.stringify({"Action": "CoreStatus", "ActionID": "12345"}));
```
Beware that this will send same Action to all AMI servers!

Keep this in mind when you deploy *amiws* within public Internet and protect access to it.
Keep this in mind when you deploy *amiws* on public Internet and protect access to it.

#### Special header AMIServerID
To send message to specified AMI server you can use header ```AMIServerID```.
The is should correspond to the ```server_id``` in requests.
This should correspond to the ```server_id``` in requests.

Example:
```javascript
sock.send(JSON.stringify({"Action": "CoreStatus", "AMIServerID": 1}));
```

#### Sending multiple variables
In some cases, you will need to send multiple variables. This can be done with a list:

Example:
```javascript
sock.send(JSON.stringify({"Action": "CoreStatus", "Variable": ["Trunk=provider07", "Hop=hop315"]}));
```

List is unnecessary when there is only one variable:
```javascript
sock.send(JSON.stringify({"Action": "CoreStatus", "Variable": "Trunk=provider07"}));
```

### SSL/TLS transport
SSL/TLS transport usage is described in this blog article:
http://staskobzar.blogspot.ca/2017/05/amiws-asterisk-manager-iterface-to-web.html

Some more information can be found in annotated configuration file.

### Digest Authentication
HTTP content can be protected with username/password. When this option is enabled, then anyone trying access web-page, will have to provide username and password to proceed.
HTTP content can be protected with username/password. When this option is enabled, anyone trying to access web-page will have to provide username and password to proceed.

Configuration paramers to set are:
Configuration parameters to set are:
```
auth_domain : example.com
auth_file : /var/www/.htaccess
Expand Down
2 changes: 1 addition & 1 deletion src/lib/Makefile.am
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
noinst_LIBRARIES = libmongoose.a libfrozen.a libamipack.a libamiws.a libamiwscfg.a

libamiwscfg_a_SOURCES = config.c config_parser.c config_parser.h amiws.h
libamiws_a_SOURCES = amiws_lib.c amiws.h
libamiws_a_SOURCES = amiws_lib.c amiws.h linked_str_stack.c linked_str_stack.h
#libmongoose_a_CPPFLAGS = -DMG_ENABLE_DEBUG
libmongoose_a_SOURCES= mongoose.c mongoose.h
libfrozen_a_SOURCES = frozen.c frozen.h
Expand Down
2 changes: 1 addition & 1 deletion src/lib/amipack.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
*/

/**
* @file amiws.h
* @file amipack.h
* @brief AMI (Asterisk Management Interface) messages
* read/create functions interface.
* AMI packet is implemented as linked list of headers.
Expand Down
48 changes: 42 additions & 6 deletions src/lib/amiws_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
*/

#include "amiws.h"
#include "linked_str_stack.h"

static struct mg_mgr mgr;
static struct mg_serve_http_opts s_http_server_opts;
Expand Down Expand Up @@ -384,21 +385,32 @@ int scan_amipack( const char *p,
return found ? i + 3 : 0;
}

typedef struct CallbackData_ {
AMIPacket *pack;
LinkedStrStack *stack;
} CallbackData;

static void send_ami_action(struct websocket_message *wm,
struct mg_connection *nc)
{
char *p_str = NULL;
size_t len;
struct mg_connection *c;
AMIPacket *pack = (AMIPacket *) amipack_init ();
AMIPacket *pack = amipack_init();

CallbackData callback_data;
callback_data.pack = pack;
callback_data.stack = linked_str_stack_create();

if(json_walk((const char*)wm->data, wm->size, json_scan_cb, (void*)pack) < (int)wm->size ){
syslog (LOG_ERR, "Invalid JSON string: %.*s", (int)wm->size, wm->data);
if (json_walk((const char *) wm->data, wm->size, json_scan_cb, (void *) &callback_data) < (int) wm->size) {
syslog(LOG_ERR, "Invalid JSON string: %.*s", (int) wm->size, wm->data);
amipack_destroy(pack);
linked_str_stack_destroy(callback_data.stack);
return;
}

linked_str_stack_destroy(callback_data.stack);

len = amipack_to_str(pack, &p_str);
syslog (LOG_DEBUG, "Converted to AMI packet from JSON:\n %.*s", (int)len, p_str);

Expand Down Expand Up @@ -428,7 +440,13 @@ static void json_scan_cb( void *callback_data,
const char *path,
const struct json_token *token)
{
AMIPacket *pack = (AMIPacket*) callback_data;
char *hdr_name;
size_t name_size;

CallbackData *data = (CallbackData *) callback_data;
AMIPacket *pack = data->pack;
LinkedStrStack *stack = data->stack;

switch(token->type) {
case JSON_TYPE_STRING:
case JSON_TYPE_NUMBER:
Expand All @@ -439,10 +457,28 @@ static void json_scan_cb( void *callback_data,
pack->sid = atoi(token->ptr);
syslog (LOG_DEBUG, "AMI server header AMIServerID: %d", pack->sid);
} else {
amipack_append(pack, strndup(name, name_len), name_len,
strndup(token->ptr, token->len), token->len);
name_size = linked_str_stack_peek(stack, &hdr_name);
if(hdr_name != NULL)
hdr_name = strndup(hdr_name, name_size);
else {
hdr_name = strndup(name, name_len);
name_size = name_len;
}

amipack_append(pack, hdr_name, name_size,
strndup(token->ptr, token->len), token->len);
}
break;
case JSON_TYPE_ARRAY_START:
linked_str_stack_push(stack, strndup(name, name_len), name_len);
break;
case JSON_TYPE_OBJECT_START:
linked_str_stack_push(stack, NULL, 0);
break;
case JSON_TYPE_OBJECT_END:
case JSON_TYPE_ARRAY_END:
linked_str_stack_pull(stack);
break;
default: break;
}
}
Expand Down
104 changes: 104 additions & 0 deletions src/lib/linked_str_stack.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* amiws -- Library with functions for read/create AMI packets
* Copyright (C) 2017, Stas Kobzar <[email protected]>
*
* This file is part of amiws.
*
* amiws 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 3 of the License, or
* (at your option) any later version.
*
* amiws 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 amiws. If not, see <http://www.gnu.org/licenses/>.
*/

/**
* @file linked_str_stack.c
* @brief Linked stack of strings implementation
*
* @author Kohanis
*/

#include <malloc.h>
#include <assert.h>

#include "linked_str_stack.h"

LinkedStrStackNode *linked_str_stack_node_create(char *str, size_t len, LinkedStrStackNode* prev)
{
LinkedStrStackNode *node = (LinkedStrStackNode *)malloc(sizeof(LinkedStrStackNode));
assert ( node != NULL && "Failed to allocate memory for linked stack node");

node->str = str;
node->len = len;
node->prev = prev;

return node;
}

void linked_str_stack_node_destroy(LinkedStrStackNode* node)
{
if (node == NULL)
return;

if(node->str)
free(node->str);

free(node);
node = NULL;
}

LinkedStrStack *linked_str_stack_create()
{
LinkedStrStack *stack = (LinkedStrStack *)malloc(sizeof(LinkedStrStack));
assert (stack != NULL && "Failed to allocate memory for linked stack");

stack->top = NULL;
return stack;
}

void linked_str_stack_destroy(LinkedStrStack *stack)
{
if(stack == NULL)
return;

LinkedStrStackNode *prev, *current = stack->top;
while(current != NULL) {
prev = current->prev;
linked_str_stack_node_destroy(current);
current = prev;
}

free(stack);
stack = NULL;
}

void linked_str_stack_push(LinkedStrStack *stack, char *str, size_t len)
{
stack->top = linked_str_stack_node_create(str, len, stack->top);
}

size_t linked_str_stack_peek(LinkedStrStack *stack, char **str)
{
if(stack->top == NULL) {
*str = NULL;
return 0;
}
*str = stack->top->str;
return stack->top->len;
}

void linked_str_stack_pull(LinkedStrStack *stack)
{
if(stack->top == NULL)
return;
LinkedStrStackNode *prev = stack->top->prev;
linked_str_stack_node_destroy(stack->top);
stack->top = prev;
}
55 changes: 55 additions & 0 deletions src/lib/linked_str_stack.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* amiws -- Library with functions for read/create AMI packets
* Copyright (C) 2017, Stas Kobzar <[email protected]>
*
* This file is part of amiws.
*
* amiws 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 3 of the License, or
* (at your option) any later version.
*
* amiws 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 amiws. If not, see <http://www.gnu.org/licenses/>.
*/

/**
* @file linked_str_stack.h
* @brief Linked stack of strings implementation interface
*
* @author Kohanis
*/

#ifndef __AMIWS_linked_str_stack_H
#define __AMIWS_linked_str_stack_H

#include <stddef.h>

typedef struct LinkedStrStackNode_ {
char *str;
size_t len;

struct LinkedStrStackNode_ *prev;
} LinkedStrStackNode;

typedef struct LinkedStrStack_ {
LinkedStrStackNode *top;
} LinkedStrStack;


LinkedStrStack *linked_str_stack_create();

void linked_str_stack_destroy(LinkedStrStack *stack);

void linked_str_stack_push(LinkedStrStack *stack, char *str, size_t len);

size_t linked_str_stack_peek(LinkedStrStack *stack, char **str);

void linked_str_stack_pull(LinkedStrStack *stack);

#endif

0 comments on commit 3b1db13

Please sign in to comment.