Raw File
#include <unistd.h>
#include <stdlib.h> // for atoi
#include "getcmd.h"
#include "debug.h"
#include "modem_core.h"
char* mdm_responses[MDM_RESP_END_OF_LIST];
void mdm_init(void) {
mdm_responses[MDM_RESP_OK] = "OK";
mdm_responses[MDM_RESP_RING] = "RING";
mdm_responses[MDM_RESP_ERROR] = "ERROR";
mdm_responses[MDM_RESP_CONNECT] = "CONNECT";
mdm_responses[MDM_RESP_NO_CARRIER] = "NO CARRIER";
mdm_responses[MDM_RESP_CONNECT_1200] = "CONNECT 1200";
mdm_responses[MDM_RESP_NO_DIALTONE] = "NO DIALTONE";
mdm_responses[MDM_RESP_BUSY] = "BUSY";
mdm_responses[MDM_RESP_NO_ANSWER] = "NO ANSWER";
mdm_responses[MDM_RESP_CONNECT_0600] = "CONNECT 0600";
mdm_responses[MDM_RESP_CONNECT_2400] = "CONNECT 2400";
mdm_responses[MDM_RESP_CONNECT_4800] = "CONNECT 4800";
mdm_responses[MDM_RESP_CONNECT_9600] = "CONNECT 9600";
mdm_responses[MDM_RESP_CONNECT_7200] = "CONNECT 7200";
mdm_responses[MDM_RESP_CONNECT_12000] = "CONNECT 12000";
mdm_responses[MDM_RESP_CONNECT_14400] = "CONNECT 14400";
mdm_responses[MDM_RESP_CONNECT_19200] = "CONNECT 19200";
mdm_responses[MDM_RESP_CONNECT_38400] = "CONNECT 38400";
mdm_responses[MDM_RESP_CONNECT_57600] = "CONNECT 57600";
mdm_responses[MDM_RESP_CONNECT_115200] = "CONNECT 115200";
mdm_responses[MDM_RESP_CONNECT_230400]= "CONNECT 230400";
mdm_responses[MDM_RESP_CONNECT_460800] = "CONNECT 460800";
mdm_responses[MDM_RESP_CONNECT_921600] = "CONNECT 921600";
}
modem_response get_connect_response(int speed, int level) {
if(level == 0) {
return MDM_RESP_CONNECT;
}
switch (speed) {
case 921600:
return MDM_RESP_CONNECT_921600;
case 460800:
return MDM_RESP_CONNECT_460800;
case 230400:
return MDM_RESP_CONNECT_230400;
case 115200:
return MDM_RESP_CONNECT_115200;
case 57600:
return MDM_RESP_CONNECT_57600;
case 38400:
return MDM_RESP_CONNECT_38400;
case 19200:
return MDM_RESP_CONNECT_19200;
case 9600:
return MDM_RESP_CONNECT_9600;
case 4800:
return MDM_RESP_CONNECT_4800;
case 2400:
return MDM_RESP_CONNECT_2400;
case 1200:
return MDM_RESP_CONNECT_1200;
case 600:
return MDM_RESP_CONNECT_0600;
default:
return MDM_RESP_CONNECT;
}
}
void mdm_init_config(modem_config *cfg) {
int i = 0;
cfg->local_connect[0] = 0;
cfg->remote_connect[0] = 0;
cfg->local_answer[0] = 0;
cfg->remote_answer[0] = 0;
cfg->no_answer[0] = 0;
cfg->inactive[0] = 0;
cfg->direct_conn = FALSE;
cfg->direct_conn_num[0] = 0;
cfg->is_binary_negotiated = FALSE;
cfg->send_responses = TRUE;
cfg->connect_response = 0;
cfg->response_code_level = 4;
cfg->text_responses = TRUE;
cfg->is_echo = TRUE;
cfg->is_cmd_mode = TRUE;
cfg->conn_type = MDM_CONN_NONE;
cfg->is_off_hook = FALSE;
cfg->is_ringing = FALSE;
cfg->cur_line_idx = 0;
cfg->ring_ctr = 0;
for(i = 0; i < sizeof(cfg->s) / sizeof(cfg->s[0]); i++) {
cfg->s[i] = 0;
}
cfg->s[S_REG_BREAK] = 43;
cfg->s[S_REG_CR] = 13;
cfg->s[S_REG_LF] = 10;
cfg->s[S_REG_BS] = 8;
cfg->s[S_REG_BLIND_WAIT] = 2;
cfg->s[S_REG_CARRIER_WAIT] = 50;
cfg->s[S_REG_COMMA_PAUSE] = 2;
cfg->s[S_REG_CARRIER_TIME] = 6;
cfg->s[S_REG_CARRIER_LOSS] = 14;
cfg->s[S_REG_DTMF_TIME] = 95;
cfg->s[S_REG_GUARD_TIME] = 50;
cfg->crlf[0] = cfg->s[S_REG_CR];
cfg->crlf[1] = cfg->s[S_REG_LF];
cfg->crlf[2] = 0;
cfg->dial_type = 0;
cfg->last_dial_type = 0;
cfg->disconnect_delay = 0;
cfg->pre_break_delay = FALSE;
cfg->break_len = 0;
cfg->memory_dial = FALSE;
cfg->dsr_active = FALSE;
cfg->force_dsr = TRUE;
cfg->force_dcd = FALSE;
cfg->first_ch = 0;
cfg->is_cmd_started = FALSE;
cfg->allow_transmit = TRUE;
cfg->invert_dsr = FALSE;
cfg->invert_dcd = FALSE;
dce_init_config(&cfg->dce_data);
line_init_config(&cfg->line_data);
}
static int get_new_cts_state(modem_config *cfg) {
return DCE_CL_CTS;
}
static int get_new_dsr_state(modem_config *cfg) {
if(cfg->force_dsr || (cfg->conn_type != MDM_CONN_NONE)) {
return (cfg->invert_dsr ? 0 : DCE_CL_DSR);
}
return (cfg->invert_dsr ? DCE_CL_DSR : 0);
}
static int get_new_dcd_state(modem_config *cfg) {
if(cfg->force_dcd || (cfg->conn_type != MDM_CONN_NONE)) {
return (cfg->invert_dcd ? 0 : DCE_CL_DCD);
}
return (cfg->invert_dcd ? DCE_CL_DCD : 0);
}
static int get_new_ri_state(modem_config *cfg) {
return (cfg->is_ringing && (cfg->ring_ctr == 0) ? DCE_CL_RI : 0);
}
int mdm_set_control_lines(modem_config *cfg) {
int state = 0;
state |= get_new_cts_state(cfg);
state |= get_new_dsr_state(cfg);
state |= get_new_dcd_state(cfg);
state |= get_new_ri_state(cfg);
LOG(LOG_INFO,
"Control Lines: DSR:%d DCD:%d CTS:%d RI:%d",
((state & DCE_CL_DSR) != 0 ? 1 : 0),
((state & DCE_CL_DCD) != 0 ? 1 : 0),
((state & DCE_CL_CTS) != 0 ? 1 : 0),
((state & DCE_CL_RI) != 0 ? 1 : 0)
);
dce_set_control_lines(&cfg->dce_data, state);
return 0;
}
void mdm_write_char(modem_config *cfg, unsigned char data) {
unsigned char str[1];
str[0] = data;
mdm_write(cfg, str, 1);
}
void mdm_write(modem_config *cfg, unsigned char data[], int len) {
if(cfg->allow_transmit == TRUE) {
dce_write(&cfg->dce_data, data, len);
}
}
void mdm_send_response(int msg, modem_config *cfg) {
char msgID[17];
LOG(LOG_DEBUG, "Sending %s response to modem", mdm_responses[msg]);
if(cfg->send_responses == TRUE) {
mdm_write(cfg, (unsigned char *)cfg->crlf, 2);
if(cfg->text_responses == TRUE) {
LOG(LOG_ALL, "Sending text response");
mdm_write(cfg, (unsigned char *)mdm_responses[msg], strlen(mdm_responses[msg]));
} else {
LOG(LOG_ALL, "Sending numeric response");
sprintf(msgID, "%d", msg);
mdm_write(cfg, (unsigned char *)msgID, strlen((char *)msgID));
}
mdm_write(cfg, (unsigned char *)cfg->crlf, 2);
}
}
int mdm_print_speed(modem_config *cfg) {
int speed;
switch(cfg->connect_response) {
case 2:
speed = cfg->dce_data.port_speed;
break;
default:
speed = cfg->line_speed;
break;
}
mdm_send_response(get_connect_response(speed, cfg->response_code_level), cfg);
return 0;
}
static void off_hook(modem_config *cfg) {
LOG(LOG_INFO, "taking modem off hook");
cfg->is_off_hook = TRUE;
line_off_hook(&cfg->line_data);
}
int mdm_off_hook(modem_config *cfg) {
off_hook(cfg);
if(cfg->is_ringing == TRUE) {
cfg->conn_type = MDM_CONN_INCOMING;
cfg->is_ringing = FALSE;
}
if(cfg->conn_type == MDM_CONN_INCOMING) {
mdm_set_control_lines(cfg);
}
return 0;
}
int mdm_answer(modem_config *cfg) {
if(cfg->is_ringing == TRUE) {
cfg->is_ringing = FALSE;
cfg->conn_type = MDM_CONN_INCOMING;
off_hook(cfg);
cfg->is_cmd_mode = FALSE;
mdm_set_control_lines(cfg);
mdm_print_speed(cfg);
} else if(cfg->conn_type != MDM_CONN_NONE) {
// we are connected, just go off hook.
off_hook(cfg);
cfg->is_cmd_mode = FALSE;
mdm_set_control_lines(cfg);
} else {
mdm_send_response(MDM_RESP_NO_CARRIER, cfg);
usleep(cfg->disconnect_delay * 1000);
//mdm_disconnect(cfg, FALSE);
}
return 0;
}
int mdm_connect(modem_config *cfg) {
off_hook(cfg);
cfg->is_cmd_mode = FALSE;
if(cfg->conn_type == MDM_CONN_NONE) {
if(line_connect(&cfg->line_data, cfg->dialno) == 0) {
cfg->conn_type = MDM_CONN_OUTGOING;
mdm_set_control_lines(cfg);
mdm_print_speed(cfg);
} else {
mdm_send_response(MDM_RESP_NO_CARRIER, cfg);
usleep(cfg->disconnect_delay * 1000);
}
}
return 0;
}
int mdm_listen(modem_config *cfg) {
return line_listen(&cfg->line_data);
}
int mdm_disconnect(modem_config* cfg, unsigned char force) {
int type;
LOG_ENTER();
LOG(LOG_INFO, "Disconnecting modem");
cfg->is_cmd_mode = TRUE;
cfg->is_off_hook = FALSE;
cfg->break_len = 0;
cfg->is_ringing = FALSE;
cfg->pre_break_delay = FALSE;
cfg->is_binary_negotiated = FALSE;
if(cfg->direct_conn && !force) {
LOG(LOG_INFO, "Direct connection active, maintaining link");
} else {
line_disconnect(&cfg->line_data);
type = cfg->conn_type;
cfg->conn_type = MDM_CONN_NONE;
mdm_set_control_lines(cfg);
if(type != MDM_CONN_NONE) {
mdm_send_response(MDM_RESP_NO_CARRIER, cfg);
usleep(cfg->disconnect_delay * 1000);
} else {
// ath0 after just off hook
mdm_send_response(MDM_RESP_OK, cfg);
usleep(cfg->disconnect_delay * 1000);
}
cfg->s[S_REG_RING_COUNT] = 0;
mdm_listen(cfg);
}
LOG_EXIT();
return 0;
}
int mdm_parse_cmd(modem_config* cfg) {
int done = FALSE;
int index = 0;
int num = 0;
int start = 0;
int end = 0;
int cmd = AT_CMD_NONE;
char *command = cfg->cur_line;
int len = cfg->cur_line_idx;
char tmp[256];
LOG_ENTER();
LOG(LOG_DEBUG, "Evaluating AT%s", command);
while(TRUE != done ) {
if(cmd != AT_CMD_ERR) {
cmd = getcmd(command, len, &index, &num, &start, &end);
LOG(LOG_DEBUG,
"Command: %c (%d), Flags: %d, index=%d, num=%d, data=%d-%d",
(cmd > -1 ? cmd & 0xff : ' '),
cmd,
cmd >> 8,
index,
num,
start,
end
);
}
switch(cmd) {
case AT_CMD_ERR:
mdm_send_response(MDM_RESP_ERROR, cfg);
done = TRUE;
break;
case AT_CMD_END:
if(cfg->is_cmd_mode == TRUE)
mdm_send_response(MDM_RESP_OK, cfg);
done = TRUE;
break;
case AT_CMD_NONE:
done = TRUE;
break;
case 'O':
case 'A':
mdm_answer(cfg);
cmd = AT_CMD_END;
done = TRUE;
break;
case 'B': // 212A versus V.22 connection
if(num > 1) {
cmd = AT_CMD_ERR;
} else {
//cfg->connect_1200 = num;
}
break;
case 'D':
if(end > start) {
strncpy(cfg->dialno, (char *)command + start, end - start);
cfg->dialno[end - start] = '\0';
cfg->dial_type = (unsigned char)num;
cfg->last_dial_type = (unsigned char)num;
strncpy((char *)cfg->last_dialno, (char *)command+start, end - start);
cfg->last_dialno[end - start] = '\0';
cfg->memory_dial = FALSE;
} else if (num == 'L') {
strncpy(cfg->dialno, cfg->last_dialno, strlen(cfg->last_dialno));
cfg->dial_type = cfg->last_dial_type;
cfg->memory_dial = TRUE;
mdm_write(cfg, (unsigned char *)cfg->crlf, 2);
mdm_write(cfg, (unsigned char *)cfg->dialno, strlen(cfg->dialno));
} else {
cfg->dialno[0] = 0;
cfg->last_dialno[0] = 0;
cfg->dial_type = 0;
cfg->last_dial_type = 0;
}
if (strlen(cfg->dialno) > 0) {
mdm_connect(cfg);
} else {
mdm_off_hook(cfg);
cfg->is_cmd_mode = FALSE;
}
done = TRUE;
break;
case 'E': // still need to define #2
if(num == 0)
cfg->is_echo = FALSE;
else if(num == 1)
cfg->is_echo = TRUE;
else {
cmd = AT_CMD_ERR;
}
break;
case 'H':
if(num == 0) {
mdm_disconnect(cfg, FALSE);
} else if(num == 1) {
mdm_off_hook(cfg);
} else
cmd = AT_CMD_ERR;
break;
case 'I': // Information.
break;
case 'L': // Speaker volume
if(num < 1 || num > 3)
cmd = AT_CMD_ERR;
else {
//cfg->volume = num;
}
break;
case 'M': // speaker settings
if(num > 3)
cmd=AT_CMD_ERR;
else {
//cfg->speaker_setting = num;
}
break;
case 'N': // automode negotiate
if(num > 1)
cmd=AT_CMD_ERR;
else {
//cfg->auto_mode=num;
}
break;
case 'P': // defaut to pulse dialing
//cfg->default_dial_type=MDM_DT_PULSE;
break;
case 'Q': // still need to define #2
if(num == 0)
cfg->send_responses = TRUE;
else if(num == 1)
cfg->send_responses = FALSE;
else if(num == 2) // this should be yes orig/no answer.
cfg->send_responses = TRUE;
else {
cmd = AT_CMD_ERR;
}
break;
case 'S':
strncpy(tmp, command + start, end - start);
tmp[end - start] = '\0';
if(num < sizeof(cfg->s)) {
cfg->s[num] = atoi(tmp); // TODO do not assume this is always a number...
switch(num) {
case S_REG_CR:
cfg->crlf[0] = cfg->s[S_REG_CR];
break;
case S_REG_LF:
cfg->crlf[1] = cfg->s[S_REG_LF];
break;
}
} else {
LOG(LOG_DEBUG, "Ignoring S register %d=%s", num, tmp);
}
break;
case AT_CMD_FLAG_QUERY | 'S':
sprintf(tmp, "%s%3.3d", cfg->crlf, cfg->s[num]);
mdm_write(cfg, (unsigned char *)tmp, strlen(tmp));
break;
case 'T': // defaut to tone dialing
//cfg->default_dial_type = MDM_DT_TONE;
break;
case 'V': // done
if(num == 0)
cfg->text_responses = FALSE;
else if(num == 1)
cfg->text_responses = TRUE;
else {
cmd=AT_CMD_ERR;
}
break;
case 'W':
if(num > -1 && num < 3)
cfg->connect_response = num;
else
cmd = AT_CMD_ERR;
break;
case 'X':
if(num > -1 && num < 5)
cfg->response_code_level = num;
else
cmd = AT_CMD_ERR;
break;
case 'Y': // long space disconnect.
if(num > 1)
cmd = AT_CMD_ERR;
else {
//cfg->long_disconnect = num;
}
break;
case 'Z': // long space disconnect.
if(num > 1)
cmd = AT_CMD_ERR;
else {
// set config0 to cur_line and go.
}
break;
case AT_CMD_FLAG_EXT + 'C':
switch(num) {
case 0:
cfg->force_dcd = TRUE;
mdm_set_control_lines(cfg);
break;
case 1:
cfg->force_dcd = FALSE;
mdm_set_control_lines(cfg);
break;
default:
cmd = AT_CMD_ERR;
break;
}
break;
case AT_CMD_FLAG_EXT + 'K':
// flow control.
switch (num) {
case 0:
dce_set_flow_control(&cfg->dce_data, 0);
break;
case 3:
dce_set_flow_control(&cfg->dce_data, MDM_FC_RTS);
break;
case 4:
dce_set_flow_control(&cfg->dce_data, MDM_FC_XON);
break;
case 5:
dce_set_flow_control(&cfg->dce_data, MDM_FC_XON);
// need to add passthrough.. Not sure how.
break;
case 6:
dce_set_flow_control(&cfg->dce_data, MDM_FC_XON | MDM_FC_RTS);
break;
default:
cmd=AT_CMD_ERR;
break;
}
break;
default:
break;
}
}
cfg->last_line_idx = cfg->cur_line_idx;
cfg->cur_line_idx = 0;
return cmd;
}
/*
* interestingly, at least the hayes modem I have has the following behavior
* concerning the AT command:
*
* AaaaaaT works (rule is that the first A found is considered the A char, and the
* trailing T has the same case, so this works
*
* abcdefat also works (first 'a' starts the sequence, next letters starts over,
* and then the last 'a' is paired up with the 't'
*
* At and aT will not work (the modem uses AT to determine parity, but mixed case
* messes with the algorithm
*
* If a sequence is started (AT sent) but then the user backspaces, the backspace
* is echoed to the sender, but then a 'T' is sent if the user has erased all of the
* command
*
* A real modem will complain about incorrect data in the command string. TCPSER
* will silently ignore such issues, as it creates more compatibility to ignore
* unknown commands.
*/
int mdm_handle_char(modem_config *cfg, unsigned char ch) {
char ch_raw = ch & 0x7f;
if(cfg->is_echo == TRUE)
dce_write_char_raw(&cfg->dce_data, ch);
if(cfg->is_cmd_started == TRUE) { // we previously got an 'AT'
if(ch_raw == (cfg->s[S_REG_BS])) {
if(cfg->cur_line_idx == 0 && cfg->is_echo == TRUE) {
mdm_write_char(cfg, 'T');
} else {
cfg->cur_line_idx--;
}
} else if(ch_raw == (unsigned char)(cfg->s[S_REG_CR])) {
// we have a line, process.
cfg->cur_line[cfg->cur_line_idx] = 0;
mdm_parse_cmd(cfg);
cfg->first_ch = 0;
cfg->is_cmd_started = FALSE;
} else {
cfg->cur_line[cfg->cur_line_idx++ % sizeof(cfg->cur_line)] = ch_raw;
}
} else if(cfg->first_ch) { // we already got our first char
// if we received a 't' and the case of both chars is the same, start
if(((ch_raw & 0x5f) == 'T') && ((cfg->first_ch & 0x20) == (ch_raw & 0x20))) {
cfg->is_cmd_started = TRUE;
dce_detect_parity(&cfg->dce_data, cfg->first_ch, ch);
LOG(LOG_ALL,"'T' parsed in serial stream, switching to command parse mode");
} else if(ch_raw == '/') {
LOG(LOG_ALL,"'/' parsed in the serial stream, replaying last command");
cfg->cur_line_idx = cfg->last_line_idx;
mdm_parse_cmd(cfg);
cfg->is_cmd_started = FALSE;
} else if((ch_raw & 0x5f) != 'A') {
cfg->first_ch = 0;
}
} else if((ch_raw & 0x5f) == 'A') {
LOG(LOG_ALL, "'A' parsed in serial stream");
cfg->first_ch = ch;
}
return 0;
}
int mdm_clear_break(modem_config* cfg) {
cfg->break_len = 0;
cfg->pre_break_delay = FALSE;
return 0;
}
int mdm_handle_timeout(modem_config *cfg) {
if(cfg->pre_break_delay == TRUE && cfg->break_len == 3) {
// pre and post break.
LOG(LOG_INFO, "Break condition detected");
cfg->is_cmd_mode = TRUE;
mdm_send_response(MDM_RESP_OK, cfg);
mdm_clear_break(cfg);
} else if(cfg->pre_break_delay == FALSE) {
// pre break wait over.
LOG(LOG_DEBUG, "Initial Break Delay detected");
cfg->pre_break_delay = TRUE;
} else if(cfg->pre_break_delay == TRUE && cfg->break_len > 0) {
LOG(LOG_ALL, "Inter-break-char delay time exceeded");
mdm_clear_break(cfg);
} else if(cfg->s[S_REG_INACTIVITY_TIME] != 0) {
// timeout...
LOG(LOG_INFO, "DTE communication inactivity timeout");
mdm_disconnect(cfg, FALSE);
}
return 0;
}
int mdm_send_ring(modem_config *cfg) {
LOG(LOG_DEBUG, "Sending 'RING' to modem");
cfg->is_ringing = TRUE;
mdm_set_control_lines(cfg);
mdm_send_response(MDM_RESP_RING, cfg);
cfg->s[S_REG_RING_COUNT]++;
LOG(LOG_ALL,"Sent #%d ring", cfg->s[S_REG_RING_COUNT]);
if(cfg->is_cmd_mode == FALSE || (cfg->s[S_REG_RINGS] != 0 && cfg->s[S_REG_RING_COUNT] >= cfg->s[S_REG_RINGS])) {
mdm_answer(cfg);
}
return 0;
}
int mdm_parse_data(modem_config *cfg, unsigned char *data, int len) {
int i;
if(cfg->is_cmd_mode == TRUE) {
for(i = 0; i < len; i++) {
mdm_handle_char(cfg, data[i]);
}
} else {
line_write(&cfg->line_data, data, len);
if(cfg->pre_break_delay == TRUE) {
for(i = 0; i < len; i++) {
if(dce_strip_parity(&cfg->dce_data, data[i]) == (unsigned char)cfg->s[S_REG_BREAK]) {
LOG(LOG_DEBUG, "Break character received");
cfg->break_len++;
if(cfg->break_len > 3) { // more than 3, considered invalid
cfg->pre_break_delay = FALSE;
cfg->break_len = 0;
}
} else {
LOG(LOG_ALL, "Found non-break character, cancelling break");
// chars past +++
mdm_clear_break(cfg);
}
}
}
}
return 0;
}
int mdm_read(modem_config *cfg, unsigned char *data, int len) {
int res;
if(cfg->is_cmd_mode == TRUE) {
// read one char in raw mode
res = dce_read_char_raw(&cfg->dce_data);
if(res > 0) { // we have a character
data[0] = (unsigned char)res; // fixup
res = 1;
}
} else {
res = dce_read(&cfg->dce_data, data, sizeof(data));
}
return res;
}
Generated by GNU Enscript 1.6.6, and GophHub 1.3.