/* * Copyright (c) 1995, 1996 Gunther Schadow. All rights reserved. * * This program 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 2 of the License, or * (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "pg_config.h" IDENT("@(#) ansi-block.c (Gunther Schadow) 12/19/96"); #include "ansi.h" #include #include #include #include #include /* _as_send * * Send a buffer as one block. Receive acknowledge. If NAK or wrong * ACKN resend the block. If successful, reset the buffer pointer and * the buffer BCC and increase the ACK-N. * * TYPE FORMAT * * HEADING SOH ... ... ... ETB BCC * MIXED SOH ... STX ... ETB BCC * TEXT STX ... ... ... ETB BCC * * LAST STX ... ... ... ETX BCC */ result _as_send(ASTRM *asp) { int ctrl, ctrl_end, i; char bcc_end = 0; size_t len; int resend_cnt = AS_MAX_RESEND; /* * assemble end of block control compound and it's bcc */ if(asp->_flags & AS_LAST) ctrl_end = _A_C2T(A_ETX); else ctrl_end = _A_C2T(A_ETB); for(i = _A_NCCC(ctrl_end) - 1; i >= 0; i--) A_BCC(bcc_end, _A_DICC(ctrl_end,i)); /* * SEND BLOCK */ while(resend_cnt-- > 0) { /* SOH/STX */ switch(asp->_flags & AS_TYPE_MASK) { case AS_HEADING: ctrl = _A_C2T(A_SOH); len = asp->_poi - asp->_base; break; case AS_MIXED: ctrl = _A_C2T(A_SOH); len = asp->_stx - asp->_base; break; case AS_TEXT: ctrl = _A_C2T(A_STX); len = asp->_poi - asp->_base; break; } #ifdef WITH_BLK if(_as_send_ctrl(asp, asp->_pfx, ctrl, asp->_blk) == FAIL) #else if(_as_send_ctrl(asp, asp->_pfx, ctrl, 0) == FAIL) #endif return FAIL; asp->_flags |= AS_BLOCK; /* [SOH|STX] ... */ if(_as_opfx(asp, astimer_D) == FAIL) return FAIL; if(write(asp->_fd, asp->_base, len) == FAIL) { asp->_errno = errno; AS_ERROR(asp, AS_EIO); return FAIL; } if((asp->_flags & AS_TYPE_MASK) == AS_MIXED) { /* SOH ... STX */ if(_as_send_ctrl(asp, NULL, _A_C2T(A_STX), -1) == FAIL) return FAIL; /* SOH ... STX ... */ if(_as_opfx(asp, astimer_D) == FAIL) return FAIL; if(write(asp->_fd, asp->_stx, asp->_poi - asp->_stx) == FAIL) { asp->_errno = errno; AS_ERROR(asp, AS_EIO); return FAIL; } } /* (SOH|STX) ... [STX ...] (ETB|ETX) BCC */ DBG(syslog(LOG_DEBUG, "asp->bcc: 0x%02x; bcc_end: 0x%02x", asp->_bcc, bcc_end)); if(_as_send_ctrl(asp, NULL, ctrl_end, asp->_bcc ^ bcc_end) == FAIL) return FAIL; asp->_flags &= ~AS_BLOCK; /* * RECEIVE ACKNOWLEDGEMENT */ receive_ack: switch(_as_recv_ack(asp, NULL, A_ACKN(asp->_ackn))) { case SUCCESS: goto success; case FAIL: if(AS_ERRNO(asp) == AS_ETIMERA) { syslog(LOG_WARNING, "timer A timeout, try ENQ polling"); if(resend_cnt-- < 0) break; else /* try ENQ-ACK polling */ if(_as_send_ctrl(asp, NULL, A_ENQ, 0) == FAIL) return FAIL; else goto receive_ack; } else return FAIL; case A_NAK: case A_ACK: default: break; } } syslog(LOG_ERR, "_as_send: resend counter expired"); AS_ERROR(asp, AS_ERETRY); return FAIL; success: asp->_poi = asp->_base; if(asp->_flags & AS_TEXT) { asp->_flags &= ~AS_HEADING; asp->_stx = asp->_poi; } asp->_bcc = 0; A_ACKN_INC(asp->_ackn); A_BLK_INC(asp->_blk); return SUCCESS; } /* _as_recv * * Receive a buffer, reset buffer pointers and flags. If AS_TEXT is * not set in the flags, the block may start with SOH. Otherwise * only STX is allowed. If STX was seen in the block, AS_TEXT will be * set and _stx will point to the first character of the text part of * the block. When the block starts with STX, AS_TEXT will be set and * AS_HEADING cleared. */ result _as_recv(ASTRM *asp) { int ctrl; int resend_cnt = AS_MAX_RESEND; bool stx_seen; char *limit = asp->_base + asp->_size; char bcc, blk; DBG(syslog(LOG_DEBUG, "_as_recv begin")); if(asp->_flags == AS_LAST) { syslog(LOG_WARNING, "_as_recv: reading beyond end of message"); AS_ERROR(asp, AS_ERANGE); return FAIL; } start_block: if(resend_cnt-- <= 0) { syslog(LOG_ERR, "_as_recv: resend counter expired"); AS_ERROR(asp, AS_ERETRY); return FAIL; } asp->_bcc = 0; asp->_end = asp->_base; if((ctrl = _as_recv_ctrl(asp, asp->_pfx, &blk, astimer_D)) == FAIL) return FAIL; if(ctrl == _A_C2T(A_STX) || ctrl == _A_C2T(A_SOH)) { asp->_flags |= AS_BLOCK; #ifdef WITH_BLK if(blk >= 0 && blk != asp->_blk) { DBG(syslog(LOG_DEBUG, "ctrl = 0x%08x; blk = %d != %d", ctrl, blk, asp->_blk)); if(blk < asp->_blk) { syslog(LOG_WARNING, "BLK (%d) less than expected (%d)", blk, asp->_blk); if(_as_discard_block(asp, "LOW BLK", A_ACKN(asp->_ackn), TRUE) == FAIL) goto read_error; } else /* BLK greater than expected */ { syslog(LOG_WARNING, "BLK (%d) greater than expected (%d)", blk, asp->_blk); if(_as_discard_block(asp, "HIGH BLK", A_NAK, FALSE) == FAIL) goto read_error; } goto start_block; } #endif } switch(ctrl) { case _A_C2T(A_SOH): DBG(syslog(LOG_DEBUG, "start of heading `%s'", asp->_pfx)); if(asp->_flags & AS_TEXT) { syslog(LOG_ERR, "SOH after STX"); goto syntax_error; } else { asp->_flags |= AS_HEADING; stx_seen = FALSE; } break; case _A_C2T(A_STX): DBG(syslog(LOG_DEBUG, "start of text `%s'", asp->_pfx)); asp->_flags &= ~AS_HEADING; stx_seen = TRUE; asp->_stx = asp->_base; break; case A_EOT: syslog(LOG_WARNING, "got EOT: sending station abort"); AS_ERROR(asp, AS_EABORT); return FAIL; case A_ENQ: syslog(LOG_WARNING, "got another ENQ"); CHK(_as_send_ctrl(asp, NULL, asp->_resp, 0), FAIL, return FAIL); resend_cnt++; goto start_block; default: syslog(LOG_WARNING, "_as_recv: unexpected ctrl sequence `%s'-0x%04x", asp->_pfx, ctrl); goto syntax_error; } while(TRUE) { char c; if(_as_ipfx(asp, astimer_D) == FAIL) return FAIL; if(read(asp->_fd, &c, 1) < 1) goto read_error; A_BCC(asp->_bcc, c); #ifdef TRANSPARENT if(c == A_DLE) { if(_as_ipfx(asp, astimer_D) == FAIL) return FAIL; if(read(asp->_fd, &c, 1) < 1) goto read_error; A_BCC(asp->_bcc, c); #endif switch(c) { case A_STX: DBG(syslog(LOG_DEBUG, "start of text in block")); if(stx_seen) { syslog(LOG_ERR, "start of text in text block"); goto syntax_error; } if(asp->_end >= limit) goto buffer_exceeded; asp->_stx = asp->_end; stx_seen = TRUE; continue; case A_ENQ: asp->_flags &= ~AS_BLOCK; syslog(LOG_WARNING, "got ENQ: block abort"); if(_as_send_ack(asp, "BLOCK ABORT", A_NAK) == FAIL) return FAIL; goto start_block; case A_ETX: asp->_flags &= ~AS_BLOCK; DBG(syslog(LOG_DEBUG, "got ETX")); asp->_flags |= AS_LAST; case A_ETB: asp->_flags &= ~AS_BLOCK; DBG(syslog(LOG_DEBUG, "end of block (bcc = 0x%02x)", asp->_bcc)); if(_as_ipfx(asp, astimer_D) == FAIL) return FAIL; if(read(asp->_fd, &bcc, 1) < 1) goto read_error; if(bcc == asp->_bcc) { DBG(syslog(LOG_DEBUG, "bcc ok")); if(_as_send_ack(asp, NULL, A_ACKN(asp->_ackn)) == FAIL) return FAIL; A_ACKN_INC(asp->_ackn); A_BLK_INC(asp->_blk); goto end_block; } else { syslog(LOG_WARNING, "_as_recv: bcc error bcc = %d, got %d", asp->_bcc, bcc); if(_as_send_ack(asp, "BCC ERROR", A_NAK) == FAIL) return FAIL; goto start_block; } default: /* data */; } #ifdef TRANSPARENT } #endif if(asp->_end >= limit) goto buffer_exceeded; *asp->_end++ = c; } end_block: asp->_poi = asp->_base; if(stx_seen) asp->_flags |= AS_TEXT; else asp->_stx = asp->_end; DBG(syslog(LOG_DEBUG, "_as_recv successful")); return SUCCESS; buffer_exceeded: syslog(LOG_ERR, "_as_recv: buffer exceeded"); AS_ERROR(asp, AS_ENOBUF); return FAIL; read_error: asp->_errno = errno; syslog(LOG_ERR, "_as_recv: read: %m"); AS_ERROR(asp, AS_EIO); return FAIL; syntax_error: AS_ERROR(asp, AS_ESYNTAX); return FAIL; } /* * Discard a block */ result _as_discard_block(ASTRM *asp, const char *prefix, int ctrl, bool care_bcc) { if(!((asp->_flags & AS_IN) && (asp->_flags & AS_BLOCK))) { syslog(LOG_ERR, "discard a block that is not even begun"); AS_ERROR(asp, AS_EILL); return FAIL; } *asp->_end = 0; DBG(syslog(LOG_DEBUG, "discarding: `%s' bcc=0x%02x", asp->_base, asp->_bcc)); while(TRUE) { char c, bcc; if(_as_ipfx(asp, astimer_D) == FAIL) return FAIL; if(read(asp->_fd, &c, 1) < 1) goto read_error; A_BCC(asp->_bcc, c); #ifdef TRANSPARENT if(c == A_DLE) { if(_as_ipfx(asp, astimer_D) == FAIL) return FAIL; if(read(asp->_fd, &c, 1) < 1) goto read_error; A_BCC(asp->_bcc, c); #endif switch(c) { case A_ENQ: asp->_flags &= ~AS_BLOCK; syslog(LOG_INFO, "got ENQ: block abort"); if(_as_send_ack(asp, prefix, ctrl) == FAIL) return FAIL; goto end_block; case A_ETX: case A_ETB: asp->_flags &= ~AS_BLOCK; syslog(LOG_INFO, "end of block (bcc = 0x%02x)", asp->_bcc); if(_as_ipfx(asp, astimer_D) == FAIL) return FAIL; if(read(asp->_fd, &bcc, 1) < 1) goto read_error; if(care_bcc) if(bcc == asp->_bcc) { DBG(syslog(LOG_DEBUG, "bcc ok")); if(_as_send_ctrl(asp, prefix, ctrl, 0) == FAIL) return FAIL; goto end_block; } else { syslog(LOG_WARNING, "_as_recv: bcc error bcc = 0x%02x, got 0x%02x", asp->_bcc, bcc); if(_as_send_ack(asp, "BCC ERROR", A_NAK) == FAIL) return FAIL; goto end_block; } else { if(_as_send_ack(asp, prefix, ctrl) == FAIL) return FAIL; goto end_block; } default: /* data */; } #ifdef TRANSPARENT } #endif DBG(syslog(LOG_DEBUG, "'%c' 0x%02x bcc = 0x%02x", c,c, asp->_bcc)); } end_block: aspurge(asp); DBG(syslog(LOG_DEBUG, "discard_block successful")); return SUCCESS; read_error: asp->_errno = errno; syslog(LOG_ERR, "_as_recv: read: %m"); AS_ERROR(asp, AS_EIO); return FAIL; }