/* * Copyright (c) 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. */ #pragma implementation #include "X328Socket.h" #ifdef PROTOGEN # include "exception.h" # include "logfile.h" #else # include # define ERROR(fmt, args...) { fprintf(stderr, fmt "\n" , ## args); exit(1); } # define ERROS(fmt, args...) { fprintf(stderr, fmt "\n" , ## args); exit(1); } # define EPROTO(fmt, args...) { fprintf(stderr, fmt "\n" , ## args); exit(1); } # define FATAL(fmt, args...) { fprintf(stderr, fmt "\n" , ## args); abort(); } # define WARNING(fmt, args...) fprintf(stderr, fmt "\n" , ## args) #endif #include #include #include #include #ifdef USE_STD_ASSERT # include #else # define assert(s) if(! (s)) \ { \ WARNING("assertion failed: `" #s "'"); \ errno = protoErr; \ _state_plus |= error; \ return; \ } #endif /* * DEBUGGING FEATURES */ ostream &X328Socket::dumpstates(ostream &ds, int states) { ds.rdbuf()->form("0x%08x", states); switch(states&role) { case 0: ds << "(contention|"; break; case master: ds << "(master |"; break; case slave: ds << "(slave |"; break; default: ds << "(error |"; break; }; switch(states&matter) { case 0: ds << "nothing|"; break; case heading: ds << "heading|"; break; case text: ds << "text |"; break; default: ds << "error |"; break; }; switch(states&blockState) { case 0: ds << "none )"; break; case beforeBlock: ds << "beforeBlock)"; break; case inBlock: ds << "inBlock )"; break; case attention: ds << "attention )"; break; case wait: ds << "wait )"; break; case blockAbort: ds << "blockAbort )"; break; case pollReply: ds << "pollReply )"; break; default: ds << "error )"; break; }; if(active(states)) ds << "-> active "; else if(passive(states)) ds << "-> passive"; else ds << "-> medium "; if(states&force) ds << " (force)"; return ds; } ostream &X328Socket::dumptok(ostream &ds, int c) { switch(c) { case SOH : ds << "SOH "; break; case STX : ds << "STX "; break; case ETX : ds << "ETX "; break; case EOT : ds << "EOT "; break; case ENQ : ds << "ENQ "; break; case ACK : ds << "ACK "; break; case DLE : ds << "DLE "; break; case NAK : ds << "NAK "; break; case SYN : ds << "SYN "; break; case ETB : ds << "ETB "; break; case TSOH: ds << "TSOH"; break; case TSTX: ds << "TSTX"; break; case TETX: ds << "TETX"; break; case DEOT: ds << "DEOT"; break; case TENQ: ds << "TENQ"; break; case TDLE: ds << "TDLE"; break; case TSYN: ds << "TSYN"; break; case TETB: ds << "TETB"; break; case ACK0: ds << "ACK0"; break; case ACK1: ds << "ACK1"; break; case ACK2: ds << "ACK2"; break; case ACK3: ds << "ACK3"; break; case ACK4: ds << "ACK4"; break; case ACK5: ds << "ACK5"; break; case ACK6: ds << "ACK6"; break; case ACK7: ds << "ACK7"; break; case WACK: ds << "WACK"; break; case RINT: ds << "RINT"; break; case SOTB: ds << "SOTB"; break; case SOSS: ds << "SOSS"; break; default: if(c < 256 && isprint(c)) ds.form("'%c' ", c); else ds.form("%04x", c); } return ds; } /* * OPTIONS * * x328([[,[,{[+-][cr28bnotusmsep]}[,[,]]]]]) * * ET: 1.1, 2.1, 2.2, 2.3 * MT: A1, A2, A3, A4, B1, B2, C1, C2, D1 * CS: BCC, BCC2X, CRC16, CCITT (only if +c) * HW: highwater block size (only if +b) * * +: set a flag * -: clear a flag */ /* * OPTION FLAGS */ struct opts_t { char *name; int options; }; opts_t et_opts[] = { {"1.1", X328Socket::ET_1_1}, {"2.1", X328Socket::ET_2_1}, {"2.2", X328Socket::ET_2_2}, {"2.3", X328Socket::ET_2_3}, {NULL , 0}, }; opts_t mt_opts[] = { {"A1", X328Socket::MT_A1}, {"A2", X328Socket::MT_A2}, {"A3", X328Socket::MT_A3}, {"A4", X328Socket::MT_A4}, {"B1", X328Socket::MT_B1}, {"B2", X328Socket::MT_B2}, {"C1", X328Socket::MT_C1}, {"C2", X328Socket::MT_C2}, {"D1", X328Socket::MT_D1}, {NULL , 0}, }; static int lookup(const char *s, opts_t tab[]) { for(const char *name = tab->name; name !=NULL; name=(++tab)->name) { if(*name == *s && strcmp(name+1, s+1) == 0) return tab->options; } ERROR("unknown option `%s'", s); } // CHECKSUM ALGORITHMS typedef void (*cksum_func_t)(char *data, size_t ds, char *cksum); static void bcc_sum(char *data, size_t ds, char *cksum) { *cksum = 0; while(ds-- > 0) *cksum ^= *data++; } static void bcc2x_sum(char *data, size_t ds, char *cksum) { char c = 0; while(ds-- > 0) c ^= *data++; int x = c; char buf[3]; sprintf(buf, "%02X", x); cksum[0] = buf[0]; cksum[1] = buf[1]; } #define NCKSUM 2 static struct cksum_t { char *name; size_t len; cksum_func_t func; } cksum[NCKSUM] = { {"bcc", 1, bcc_sum }, // 0 standard one byte XOR-ed BCC {"bcc2x", 2, bcc2x_sum }, // 1 same as BCC but as two upper-case hex-digits //reserved, yet unimplemented names: //{"crc16", 2, crc16_sum }, // 2 - standard 16 bit CRC sum on 16+15+2+1 //{"ccitt", 2, ccitt_sum }, // 3 - standard 16 bit CRC sum on 16+12+5+1 }; static int plus(int o, int f) { return o | f; } static int minus(int o, int f) { return o & ~f; } void X328Socket::parse_args(const char *s, int l) { // parse the argument string char args[l+1]; memcpy(args,s,l); args[l] = 0; char *_argv[11]; char **argv = _argv; int argc = 0; { for(char *p = args; p != NULL; ) { char *val = strsep(&p, ","); *argv++ = val + strspn(val, " \t"); // delete spaces; argc++; // default strings are thus ""; } } argv = _argv; #define SETNUM(n,s) { char *e; long u=strtol(s,&e,0); if(e>s) n=u; } #define NEXTARG(d) argv++; argc--; if(argc <= 0 || **argv == 0) { d; } else // 1. subcategory of establishment and termination if(argc <= 0 || **argv == 0) { options = ET_2_3; } else { options = lookup(*argv, et_opts); } NEXTARG(options |= MT_B2) { options |= lookup(*argv, mt_opts); } NEXTARG(;) { int (*sign)(int, int) = plus; for(char *p = *argv; *p != 0; p++) { switch(*p) { case ' ': case '\t': break; case '+': sign = plus; break; case '-': sign = minus; break; case 'c': options = (*sign)(options, use_cksum); break; //o.k. case 'r': options = (*sign)(options, use_reply); break; case '2': options = (*sign)(options, alt_ack); break; case '8': options = (*sign)(options, mod8_ack); break; case 'b': options = (*sign)(options, use_etb); break; //o.k. case 'n': options = (*sign)(options, use_blk); break; //o.k. case 'o': options = (*sign)(options, use_sotb); break; case 't': options = (*sign)(options, transparent); break; // o.k. case 'u': options = (*sign)(options, one_way); break; case 's': options = (*sign)(options, switched); break; case 'm': options = (*sign)(options, master_init); break; case 'e': options = (*sign)(options, use_enq); break; case 'p': options = (*sign)(options, enq_prefix); break; default: ERROR("option `%c' not found", *p); } } } NEXTARG(cksum_num = 0) { for(cksum_num = 0; cksum_num < NCKSUM; cksum_num++) if(strcmp(*argv, cksum[cksum_num].name) == 0) break; if(cksum_num == NCKSUM) ERROR("unknown checksum algorithm `%s'", *argv); } NEXTARG(highwater = 240) SETNUM(highwater, *argv); return; } /* * STATES, TRANSITIONS */ void X328Socket::reset() // reset the states (initially and after EOT) { RintNo = 0; AckNo = 0; BlockNo = 1; LastReply = NAK; states = init_states; if(options & one_way || options & switched) if(master_init) states |= master; else states |= slave; end_of_heading = NULL; } /********************************************************************* Here is the function that implements the automat discussed above and shown in the petri net ansi-x3.28.net. It consists of two parts: generating or getting a token and performing the transition. The transition selection is the same for both, active and passive modes. However, while in passive mode, the next token is fetched with gettok(), in active states there is some strategy to be implemented. The strategy tries to influence the flow of the net such, that a goal state is approached. The goal state is expressed by two sets of state flags: goal_plus and goal_minus, the former holding the states that should be entered and the latter those to be exited. The strategy should work as follows: 1. exiting a state has precedence over entering 2. the `force' flag causes states to be exited forcefully using aborts or interrupts. 3. if no state is to be exited forcefully, and there is pending outgoing data, this is attempted to be delivered 3a. if the actual state does not allow delivering, we try to approach the desired state (i.e. master/(heading or text)/inBlock) 3b. if there is no data (left) and no goal state given, return --> delivery/reception of text/heading data does not change the state after transitioning, the new state is checked against the goal and if it does not match the goal we loop to the beginning. Otherwise, return. *********************************************************************/ void X328Socket::go(int goal_plus, int goal_minus) { if( _state_plus & error ) return; DBG( cerr << "go:" << endl; cerr << "state:" ; dumpstates(cerr, states) << endl; cerr << "goal+:" ; dumpstates(cerr, goal_plus) << endl; cerr << "goal-:" ; dumpstates(cerr, goal_minus) << endl; ) // ASSURE THAT THE GOAL IS CONSISTENT // 1. on and off switches must be disjunctive assert(! (goal_plus&goal_minus)); // 2. either master or slave assert(! ((goal_plus & master) && (goal_plus & slave))); // 3. either heading or text assert(! ((goal_plus & text) && (goal_plus & heading))); // 4. blockStates are mutually exclusive { int bs = goal_plus & blockState; assert((bs == 0) || (bs == beforeBlock) || (bs == inBlock) || (bs == attention) || (bs == wait) || (bs == pollReply) || (bs == blockAbort)); } // COMPLETE MINUS SWITCHES if(goal_plus & master) goal_minus |= states & slave; if(goal_plus & slave) goal_minus |= states & master; if(goal_plus & heading) goal_minus |= states & text; if(goal_plus & text) goal_minus |= states & heading; if(goal_minus & transmission) goal_minus |= states & message; if(goal_minus & message) goal_minus |= states & inBlock; DBG( cerr << "cgol+:" ; dumpstates(cerr, goal_plus) << endl; cerr << "cgol-:" ; dumpstates(cerr, goal_minus) << endl; ); // cycle as long as our goal is not reached do { int c; // DETERMINE THE EFFECTIVE GOAL // here we want to end up int goal = states & ~goal_minus | goal_plus; // determine the difference: goal_plus = goal & ~states; goal_minus = states & ~goal; DBG( cerr << "state:" ; dumpstates(cerr, states) << endl; cerr << "dgol+:" ; dumpstates(cerr, goal_plus) << endl; cerr << "dgol-:" ; dumpstates(cerr, goal_minus) << endl; ); if(! (states & transmission)) { // in contention if(! (states & attention)) { if(goal_plus & master) { c = ENQ; // ENQ1 sendtok(c); states |= ibid; } else if(goal_plus & slave) c = gettok(); else // nothing to do! return; } else { if(states & ibid) c = gettok(); else if(goal_plus & slave) { c = ACK; sendtok(c); } else { // FIXME! WACK1 - NAK1 - RINT? c = WACK; // WACK1 sendtok(c); } } } else if(passive(states)) c = gettok(); else // this is our chance! { // determine next token from goal if(states & master) { if(states & wait) { // ENQ2 c = ENQ; sendtok(c); goto transition; } if((goal_minus & ~force) && (goal_minus & force)) { // anything to leave forcefully? switch(goal_minus & blockState) { case inBlock: // block abort (ENQ3) c = ENQ; sendtok(c); goto transition; case beforeBlock: if(goal_minus & message) // sending station abort { c = EOT; // EOT3/2 sendtok(c); } goto transition; case wait: // already cought case attention: // is not active case blockAbort: // is not active case pollReply: // is not active FATAL("state error"); } } else // go ahead { // if there is pending heading data, deliver it if(heading_size > 0 || goal_plus & heading) { if(states & text) { FATAL("can't get back into heading state"); } else switch(states & blockState) { case beforeBlock: // SOH1/2 c = SOH; buftok(c); goto transition; case inBlock: if(deliver(&heading_data, &heading_size) || goal_minus & inBlock) { // means that the max block size is reached c = ETB; buftok(c); goto transition; } // else flow further! break; case wait: // already cought case attention: // is not active case blockAbort: // is not active case pollReply: // is not active FATAL("state error"); } } // no else! - flow further if(text_size > 0 || goal_plus & text) { switch(states & blockState) { case beforeBlock: // STX2,3,4 c = STX; buftok(c); goto transition; case inBlock: if(! (states & text)) { // enter text state in block STX1 c = STX; buftok(c); goto transition; } break; case wait: // already cought case attention: // is not active case blockAbort: // is not active case pollReply: // is not active FATAL("state error"); } if(deliver(&text_data, &text_size) || goal_minus & ( text | inBlock )) { if(goal_minus & text) c = ETX; else c = ETB; buftok(c); goto transition; } else { return; // need more text data } } if(goal_minus & inBlock) { if(goal_minus & text) c = ETX; else c = ETB; buftok(c); goto transition; } if(goal_minus & transmission) { // EOT1 - normal termination c = EOT; sendtok(c); goto transition; } return; // nothing to do! } } else if(states & slave) { if((goal_minus & ~force) && (goal_minus & force)) { // anything to leave forcefully? switch(goal_minus & blockState) { case inBlock: // is not active case beforeBlock: // is not active case wait: // is not active FATAL("state error"); case attention: // EOT4 termination interrupt case blockAbort: // EOT5 termination interrupt case pollReply: // (EOT6) termination interrupt c = EOT; sendtok(c); goto transition; } if(goal_minus & transmission) { // we're active, we can do a termination interrupt c = EOT; sendtok(c); goto transition; } } else { switch(states & blockState) { case inBlock: // is not active case beforeBlock: // is not active case wait: // is not active FATAL("state error"); case attention: // response, we are *not* in contention if(check_block()) { if(states & heading) { buffer.setend(buffer.ppoi()); if(collect(&heading_data, &heading_size)) return; // transfer buffer overflow } else // mixed, text, or after ETX { if(end_of_heading != NULL) // a mixed buffer { buffer.setend(end_of_heading); end_of_heading = NULL; if(collect(&heading_data, &heading_size)) return; // transfer buffer overflow } buffer.setend(buffer.ppoi()); if(collect(&text_data, &text_size)) return; // transfer buffer overflow } if(RintNo > 0) c = RINT; else if(goal_plus == wait) c = WACK; else c = ACK; } else c = NAK; sendtok(c); goto transition; case blockAbort: // NAK3 block abort response c = NAK; buffer.trunc(buffer.end()); sendtok(c); goto transition; case pollReply: // repeat last reply sendtok(LastReply); states &= ~pollReply; states |= beforeBlock; continue; default: FATAL("state error"); } } } } transition: DBG(cerr << "token:"; dumptok(cerr, c) << endl;) switch(c) { case ENQ: if(states & wait) // ENQ2 { DBG(cerr << "trans:ENQ2 - try" << endl;) if(! active(states) && !(states & ibid)) collect_prefix(); states &= ~wait; states |= attention; } else if(! (states & transmission)) // ENQ1 { DBG(cerr << "trans:ENQ1 - bid/select" << endl;) if(! active(states) && !(states & ibid)) collect_prefix(); states |= attention; } else if(states & inBlock) // ENQ3 { DBG(cerr << "trans:ENQ3 - block abort" << endl;) states &= ~inBlock; states |= blockAbort; } else if(states & beforeBlock) // ENQ4 -- repeat last reply { states &= ~beforeBlock; states |= pollReply; } else { ERROR("unexpected ENQ"); return; } break; case WACK: assert(states & attention); DBG(cerr << "trans:WACK1 - WACK" << endl;); if(! active(states)) collect_prefix(); states &= ~attention; states |= wait; break; case EOT: if(states & attention) // EOT4 - termination interrupt { DBG(cerr << "trans:EOT4 - termination interrupt" << endl;); if(! ( states & master )) { errno = termIntr; _state_plus |= error; return; } } else if(states & blockAbort) // EOT5 - termination interruopt { DBG(cerr << "trans:EOT5 - termination interrupt" << endl;); if(! ( states & master )) { errno = termIntr; _state_plus |= error; return; } } else { assert(states & beforeBlock); if(! (states & message)) // EOT1 - normal termination { DBG(cerr << "trans:EOT1 - normal termination" << endl;); if(states & slave) _state_plus |= eot_seen; else _state_plus |= eot_placed; } else // EOT2 & EOT3 - sending station abort { DBG(cerr << "trans:EOT2/3 - sending station abort" << endl;) if(states & slave) { _state_plus |= eot_seen; errno = sendAbrt; _state_plus |= error; return; } else _state_plus |= eot_placed; } } reset(); break; case NAK: if(! active(states)) collect_prefix(); if(states & attention) { if(! (states & transmission)) // NAK1 { DBG(cerr << "trans:NAK1 - bid/select failed" << endl;) states &= ~attention; } else // NAK2 { DBG(cerr << "trans:NAK2 - block failed" << endl;) if(states&slave) { buffer.discard(buffer.len()); states &= ~attention; states |= beforeBlock; } else if(states&master) { resend_buffer(); } else FATAL("state error"); } } else if(states & blockAbort) // NAK3 { DBG(cerr << "trans:NAK3 - block abort response" << endl;) states &= ~blockAbort; states |= beforeBlock; if(states&slave) buffer.discard(buffer.len()); } else { ERROR("unexpected NAK"); return; } break; case RINT: assert(attention); if(! active(states)) collect_prefix(); states &= ~attention; states |= beforeBlock; RintNo++; if(! (states & transmission)) // RINT1 { DBG(cerr << "trans:RINT1 - reverse inetrrupt" << endl;) if(states & ibid) { states |= master; states &= ~ibid; } else states |= slave; } else // RINT2 { DBG(cerr << "trans:RINT2 - reverse inetrrupt" << endl;) AckNo++; if(states&master) buffer.discard(buffer.len()); states |= beforeBlock; if(! (states & message)) // if this was the ETX block if(states & slave) _state_plus |= eom_seen; else _state_plus |= eom_placed; } break; case ACK: case ACK0: case ACK1: case ACK2: case ACK3: case ACK4: case ACK5: case ACK6: case ACK7: assert(states & attention); if(! active(states)) collect_prefix(); states &= ~attention; states |= beforeBlock; if(! (states & transmission)) // ACK1 { DBG(cerr << "trans:ACK1 - bid/select acknowledge" << endl;) AckNo++; if(states & ibid) { states |= master; states &= ~ibid; } else states |= slave; _state_plus &= ~eox_any; } else // ACK2 { DBG(cerr << "trans:ACK2 - block acknowledge" << endl;) AckNo++; if(states&master) buffer.discard(buffer.len()); if(! (states & message)) // if this was the ETX block if(states & slave) _state_plus |= eom_seen; else _state_plus |= eom_placed; } break; case SOH: assert(states & beforeBlock); states = states & ~beforeBlock | inBlock | heading; if(options & use_blk) if(states & master) buftok(BlockNo % 8); else recv_blk(); if(states & message) // SOH1 { DBG(cerr << "trans:SOH1 - start of heading" << endl;) } else // SOH2 { DBG(cerr << "trans:SOH2 - start of heading (new message)" << endl;) } break; case STX: if(states & inBlock) // STX1 { assert(states & heading); DBG(cerr << "trans:STX1 - start of text in block" << endl;) states &= ~heading; states |= text; if(states & slave) { end_of_heading = buffer.ppoi(); } } else { assert(states & beforeBlock); states &= ~beforeBlock; states |= inBlock; if(options & use_blk) if(states & master) buftok(BlockNo % 8); else recv_blk(); switch(states & matter) { case heading: // STX2 assert(states & message); DBG(cerr << "trans:STX2 - start of text" << endl;) states &= ~heading; states |= text; break; case text: // STX4 assert(states & message); DBG(cerr << "trans:STX4 - start of text" << endl;) break; case 0: // STX3 assert(! (states & message)); DBG(cerr << "trans:STX3 - start of text (new message)" << endl;) states |= text; break; default: FATAL("illegal state"); } } break; case ETX: assert(states & message); assert(states & inBlock); assert(states & text); DBG(cerr << "trans:ETX1 - end of text" << endl;) if(states & master) send_buffer(); states &= ~(message | inBlock | text); states |= attention; break; case ETB: assert(states & inBlock); DBG(cerr << "trans:ETB - end of block" << endl;) if(states & master) send_buffer(); states &= ~inBlock; states |= attention; break; case SOSS: case SOTB: case SYN: FATAL("unsupported control command"); break; case DLE: default: if(!active(states)) { buffer.put(c); } else { ERROR("protocol error on token: %x: `%c'", (int)c, c); return; } break; } DBG( cerr << "state:" ; dumpstates(cerr, states) << endl; cerr << "goal+:" ; dumpstates(cerr, goal_plus) << endl; cerr << "goal-:" ; dumpstates(cerr, goal_minus) << endl; ) } while( goal_plus // there's a + goal && ( (states & goal_plus) != goal_plus ) // which we have not reached || ( states & goal_minus ) // states not yet left || ( (states & heading) && heading_size > 0 ) // or data to move || ( (states & message) && text_size > 0 ) ); } bool X328Socket::deliver(char **buf, size_t *size) // deliver data (heading or text) into a buffer, returns true if // the highwater mark is reached and false if space is still left // after the data has been delivered. If not all data could be // delivered true is returned and buf/size parameters set to hold // the data not yet delivered. Take care to hold the original pointer // at some secure place in order to free the memory. { size_t len = buffer.len(); size_t delta = len + *size > highwater ? highwater - len: *size; buffer.put(*buf, delta); *buf += delta; *size -= delta; return *size > 0; } bool X328Socket::collect(char **buf, size_t *size) // collect data (heading or text) from the block buffer into a // transfer buffer. Return true if the transfer buffer overflows // and false otherwhise. // If all data could be collected false is returned // and buf/size parameters set to hold further data. Take care to // keep the original pointer at some secure place in order to free the // memory. // If no transfer buffer is specified (*buf == NULL), this means that we // aren't interested in the data, and it is just thrown away. Return // as if no buffer overflow had occured { if(*buf == NULL) { buffer.discard(buffer.len()); return false; } else { size_t n = buffer.get(*buf, *size); *size -= n; *buf += n; return *size == 0 && buffer.len() > 0; } } void X328Socket::collect_prefix() { buffer.setend(buffer.ppoi()); collect(&prefix_data, &prefix_size); buffer.discard(buffer.len()); } void X328Socket::send_prefix() { if(prefix_size > 0) _lower_level->send(prefix_data, prefix_size > 15 ? 15 : prefix_size); prefix_size = 0; prefix_data = NULL; } bool X328Socket::check_block() { if(options & use_cksum) { // include ETB/ETX in checksum // FIXME: ANSI says DLE must not be included in CRC! if(states & message) buftok(ETB); else buftok(ETX); size_t len = cksum[cksum_num].len; char bufa[len], bufc[len]; _lower_level->recv(bufa, len); (*(cksum[cksum_num].func)) (buffer.end(), buffer.ppoi() - buffer.end(), bufc); if(memcmp(bufa, bufc, len) != 0) { // block check failed! discard the block buffer.trunc(buffer.end()); return false; } // remove the end of block character buffer.trunc(buffer.ppoi() - (options & transparent ? 2 : 1)); } if(options & use_blk) { if(blk < BlockNo % 8) { // discard block but acknowledge correctly buffer.trunc(buffer.end()); return true; } else if(blk > BlockNo % 8) { // NAK buffer.trunc(buffer.end()); return false; } BlockNo++; } return true; } void X328Socket::send_buffer() { buffer.setend(buffer.ppoi()); if(options & use_cksum) { size_t len = cksum[cksum_num].len; char buf[len]; (*cksum[cksum_num].func) (buffer.poi() + ( options & transparent ? 2 : 1 ), buffer.len() - ( options & transparent ? 2 : 1 ), buf); buffer.put(buf, len); buffer.setend(buffer.ppoi()); } _lower_level->send(buffer.poi(), buffer.len()); } void X328Socket::resend_buffer() { _lower_level->send(buffer.poi(), buffer.len()); } int X328Socket::gettok() // returns char or token { DBG(cerr << "get>" << flush;) char c; if(! _lower_level->recv(&c, 1)) EPROTO("unexpected EOF"); int tok = c; switch(tok | 0x100) { case DLE : if(! _lower_level->recv(&c, 1)) EPROTO("unexpected EOF"); tok = c; switch(tok | 0x1000) { case TSOH: case TSTX: case TETX: case TSYN: case TETB: case TDLE: if( options & transparent ) return tok | 0x100; else return tok | 0x1000; case TENQ: if( options & transparent && states & inBlock ) return tok | 0x100; // else flow further case DEOT: case ACK0: case ACK1: case ACK2: case ACK3: case ACK4: case ACK5: case ACK6: case ACK7: case WACK: case RINT: case SOTB: case SOSS: if( states & inBlock ) { ERROR("illegal DLE-%o sequence in transparent block", c); return -1; } else return tok | 0x1000; default: ERROR("illegal DLE-%o sequence", c); return -1; } case SOH : case STX : case ETX : case EOT : case ENQ : case ACK : case NAK : case SYN : case ETB : if( options & transparent && states & inBlock ) return tok; else return tok | 0x100; default: return tok; } } void X328Socket::recv_blk() // receives blk { DBG(cerr << "blk>" << flush;) if(! _lower_level->recv(&blk, 1)) EPROTO("unexpected EOF"); } void X328Socket::buftok(int tok) // writes char or token into buffer { DBG(cerr << "buf<" << flush;) if(tok & ~0xff) { if(tok & 0x100) { if(options&transparent) { buffer.put((char)(DLE & 0xff)); buffer.put((char)(tok & 0xff)); } else buffer.put((char)(tok & 0xff)); } else if(tok & 0x1000) { buffer.put((char)(DLE & 0xff)); buffer.put((char)(tok & 0xff)); } else FATAL("illegal token 0x%x", tok); } else buffer.put((char)tok); } void X328Socket::sendtok(int tok) // writes char or token into buffer // remembers last reply (ACK, NAK, WACK, RINT) in LastReply { DBG(cerr << "snd<" << flush;) char buf[2]; int i = 0; if(tok & ~0xff) { if(tok & 0x100) { if(options&transparent && protectd(tok)) { buf[i++] = (char)(DLE & ~0x100); buf[i++] = (char)(tok & ~0x100); } else buf[i++] = (char)(tok & ~0x100); } else if(tok & 0x1000) { buf[i++] = (char)(DLE & ~0x100); buf[i++] = (char)(tok & ~0x1000); } else FATAL("illegal token 0x%x", tok); } else { buf[i++] = (char)tok; } _lower_level->send(buf, i); if(supervisory(tok) && tok != ENQ) LastReply=tok; } /********************************************************************** * ctor, dtor, and copy **********************************************************************/ X328Socket::X328Socket(const char *address, const char *param, size_t parlen) : Socket(address) { protogen_debug = 1; parse_args(param, parlen); // sets the options; if(! (options & use_etb)) // simple way of opting out blocking: highwater = (size_t)-1; // set highwater to 4 GByte reset(); prefix_data = NULL; prefix_size = 0; heading_data = NULL; heading_size = 0; text_data = NULL; text_size = 0; } X328Socket::X328Socket() : Socket() { options = 0; cksum_num = 0; highwater = 0; reset(); prefix_data = NULL; prefix_size = 0; heading_data = NULL; heading_size = 0; text_data = NULL; text_size = 0; } X328Socket::X328Socket(const X328Socket &s) : Socket() { options = s.options; cksum_num = s.cksum_num; highwater = s.highwater; reset(); prefix_data = NULL; prefix_size = 0; heading_data = NULL; heading_size = 0; text_data = NULL; text_size = 0; } X328Socket::~X328Socket() { if(state() & connected) close(); } Socket *X328Socket::ctor(const char *address, const char *param, size_t parlen) { return new X328Socket(address, param, parlen); } Socket *X328Socket::ctor() { // accept-less levels must copy their configuration return new X328Socket(*this); } /********************************************************************** * socket methods **********************************************************************/ size_t X328Socket::send(const char *buf, size_t len, int msgf) { DBG(cerr << "send(" << buf << ", " << len << ", " << hex << msgf << ")" << endl;) heading_data = NULL; heading_size = 0; prefix_data = NULL; prefix_size = 0; text_data = (char *)buf; text_size = len; go(master); _state_plus = _state_plus & ~receiving | sending; _state_minus = _state_minus & ~sending | receiving; while( text_size > 0 && ( ! ( _state_plus & error ) ) ) go(); if( msgf & msg_eom ) { go(0, message); _state_plus &= ~sending; _state_minus |= sending; } else if( msgf & msg_eor ) { if( options & use_etb ) go(0, inBlock); } if( msgf & msg_eot ) { go(0, transmission); _state_plus &= ~sending; _state_minus |= sending; } len -= text_size; text_data = NULL; text_size = 0; return len; } static int ABC = 10; size_t X328Socket::recv(char *buf, size_t max, int msgf) { DBG(cerr << "recv(" << buf << ", " << max << ", " << hex << msgf << ")" << endl;) if(_state_plus & ( eom_seen | eot_seen | error )) { if(! ABC--) abort(); return 0; } heading_data = NULL; heading_size = 0; prefix_data = NULL; prefix_size = 0; text_data = buf; text_size = max; go(slave); _state_plus = _state_plus & ~sending | receiving; _state_minus = _state_minus & ~receiving | sending; while( text_size == max && ! ( _state_plus & ( eot_seen | eom_seen | error ) ) ) go(); max -= text_size; text_data = NULL; text_size = 0; if(max == 0) _state_plus &= ~eom_seen; return max; } void X328Socket::shutdown(flags_t mode) { if(mode & send_mode) { go(0, master | force); _state_plus = _state_plus & ~sending; _state_minus = _state_minus | sending; } if(mode & recv_mode) { go(0, slave | force); _state_plus = _state_plus & ~receiving; _state_minus = _state_minus | receiving; } _lower_level->shutdown(mode); } void X328Socket::close(flags_t mode) { if(! (mode & silently)) go(0, transmission | force); _state_plus = _state_plus & ~(sending | receiving); _state_minus = _state_minus | receiving | sending; _lower_level->close(mode); } bool X328Socket::is_sendready(int sec, int usec) const { if(! (states & slave)) return _lower_level->is_sendready(sec, usec); else return false; } bool X328Socket::is_recvready(int sec, int usec) const { if( state() & eom_seen ) return false; else if(! (states & master)) return _lower_level->is_recvready(sec, usec); else return false; } bool X328Socket::is_exception(int sec, int usec) const { return _lower_level->is_exception(sec, usec); }