0% found this document useful (0 votes)
55 views36 pages

TCP-IP For TI 89

This document describes the implementation of the TCP/IP protocol stack on Texas Instruments graphing calculators TI-89 and TI-92+. It discusses porting the TinyTCP implementation to run on the calculators' limited hardware. The physical layer uses a simplified RS-232 standard over the calculator's 2.5mm audio jack. Programming declarations and optimizations are discussed to fit the code within the 8KB size limit of the TI-89 OS. The goal is to connect the calculators to the Internet using simple protocols like FTP, Telnet and HTTP over a serial connection to a PC running KA9Q NOS.

Uploaded by

Samir Ribic
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
55 views36 pages

TCP-IP For TI 89

This document describes the implementation of the TCP/IP protocol stack on Texas Instruments graphing calculators TI-89 and TI-92+. It discusses porting the TinyTCP implementation to run on the calculators' limited hardware. The physical layer uses a simplified RS-232 standard over the calculator's 2.5mm audio jack. Programming declarations and optimizations are discussed to fit the code within the 8KB size limit of the TI-89 OS. The goal is to connect the calculators to the Internet using simple protocols like FTP, Telnet and HTTP over a serial connection to a PC running KA9Q NOS.

Uploaded by

Samir Ribic
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
You are on page 1/ 36

ELEKTROTEHNIČKI FAKULTET U SARAJEVU

ODSJEK ZA RAČUNARSTVO I INFORMATIKU

POSTDIPLOMSKI STUDIJ

SEMINARSKI RAD IZ PREDMETA

“RAČUNARSKE KOMUNIKACIJE I MREŽE”

Dipl. ing. Samir Ribić

IMPLEMENTACIJA PROTOKOLA

TCP/IP

NA GRAFIČKIM PROGRAMABILNIM KALKULATORIMA

TI89 I TI92+

Sarajevo, oktobar 2000


2
3

UVOD

Personalni računari su odavno prevazišli fazu memorijskih ograničenja iz


osamdesetih godina do te mjere da današnji softver gotovo zloupotrebljava veliku
količinu memorije, te forsira brzo zastarijevanje računara. Takođe, softver se sve više
shvata kao “crna kutija” i toliko je kompleksan da vrlo mali broj programera shvata
njegove detalje i prilika da se realizuje vlastiti, posebno sistemski, softver je sve rjeđa
zbog ogromne programske baze.

Svijet programabilnih kalkulatora je još uvijek


drugačiji. Nije rijetkost da se i danas proizvode
modeli kakvi su bili prije 20 godina (od svega 30
bajtova memorije), a čak i najjača klasa
programabilnih kalkulatora ima karakteristike kućnih
računara iz sredine osamdesetih.

Programabilni kalkulator TI-89, firme Texas


Instruments realizovan je s procesorom Motorola
68000. Riječ je o jednom od najboljih kalkulatora za
numeričku i simboličku matematiku. Na slici je dat
izgled kalkulatora prilikom rješavanja jednog
neodređenog integrala.

Pored izvanrednih mogućnosti ugrađenih u


ROM ovog kalkulatora, otvorena je i mogućnost
programiranja u asembleru ili njegovom internom
BASIC-u. Ubrzo je portiran GNU C kompajler kao
kros kompajler. To je otvorilo mogućnost razvoja
novih matematičkih i naučno-tehničkih programa,
kao i poslovnih aplikacija i igara.

RAM memorija kalkulatora TI-89 je oko 180


kilobajta slobodnih za korisnika, pri čemu
asemblerski program, zavisno od verzije operativnog sistema, može biti dug 8, 24 ili
64 kilobajta. Ova ograničenja predstavljaju izvjesan izazov za programera u smislu
odabiranja opcija koje će program imati i optimizacija.

TI-89 opremljen je specijalnim kablom koji se zove “TI GraphLink”. Ovaj


kabl je moguće povezati s PC-om ili modemom uz korištenje posebnog softvera sa
vlastitim protokolom.

Sa kalkulatorom TI-89 djelimično kompatibilan je ručni računar TI-92 Plus,


koji ima istu količinu memorije ali drugačiji rad s tastaturom (QWERTY) i veći ekran.
4

TCP/IP PROTOKOL NA TI-89

Ideja da se TI89 opremi TCP/IP protokolom stara je koliko i sam kalkulator (2


godine) ali još niko prije ovog seminarskog rada to nije realizovao. Stoga su prilikom
priprema za ovaj seminarski rad postavljeni sljedeći ciljevi:

 Realizovati minimalni funckionalni TCP/IP protokol između TI89 kalkulatora i


PC računara
 Omogućiti upotrebu najčešćih protokola višeg nivoa, FTP, POP3, Telnet, HTTP i
SMTP
 Obezbjediti protokol nižeg nivoa za serijsku liniju
 Paziti na ograničenje od 8 K za mašinski program prisutno u verziji OS 2.03

Posljednji uslov direktno ograničava temeljitost do koje se aplikacija može


realizovati, ali s druge strane omogućava da aplikacija ipak ne bude prevelika za ovaj
seminarski rad.

Projekat ima sljedeće svrhe:

 Na pojednostavljenom primjeru upoznati se sa protokolima koji čine Internet


 Povezati na Internet i mašinu koja to do sada nije mogla, makar i rudimentarno
 Podstaći razvoj specijalnih malih modema kako bi se dobila mašina manjih
dimenzija a veće upotrebljivosti od palmtop mašina
 Program treba da bude jezgro za daljnje širenje, prije svega u smijeru poboljšanja
korisničkog interfejsa i perfomansi

Zaključeno je da nema potrebe pisati kompletan kod iz početka, nego da je bolje


portirati neku već postojeću malu implementaciju TCP/IP protokola i prilagoditi je
specifičnom hardveru, a zatim proširiti još nekim protokolima.

Kao pogodan početak pokazala se aplikacija TinyTCP autora Geoffrey Coopera.


Prva verzija je pisana još 1986, a od 1997 je data u GPL licencu, što znači da su
dopuštene modifikacije i besplatna upotreba ali i onaj što je modificirao program
mora isporučiti izvorni kod.

Iako je izvorni kod dug oko 40 kilobajta, GNU C se pokazao kao izvanredan
optimizator, pa je prevedeni kod, uz isključene testne poruke dug nešto manje od 8
kilobajta.

FIZIČKI SLOJ

Radi smanjenja dimenzija priključka i potrošnje baterija, ulazno/izlazni port na


TI-89 je veoma pojednostavljen. Oblik priključka je 2,5 mm stereo džek. To daje
svega dva signala, nazvana po boji odgovarfajućih žica, bijeli i crveni, dok treća žica
5

predstavlja masu. Binarna 0 se generiše aktiviranjem bijelog pa crvenog signala, a


binarna 1 crvenog pa bijelog. Texas Instruments je razvio tri tipa kabla za
povezivanje računara s kalkulatorima, sivi, crni i USB, a nezavisni korisnici su razvili
još i paralelni. Crni, USB i paralelni kablovi zasnovani su na direktnom upravljanju
ovim signalima, pa su nepogodni za povezivanje s modemom. Sivi kabl, koji je i
najskuplji, koristi modifikovani RS232 standard. Potencionalno najinteresantniji USB
kabl još nije u prodaji.

U varijanti RS232 standarda primjenjenoj u sivom GraphLink kablu ulogu


imaju samo TX i RX signali, koji prenose podatke. Signali DTR i CTS su uvijek
postavljeni na 1 jer oni napajaju čipove koji se nalaze unutar kabla. Ovo rješenje
otežava kontrolu toka na fizičkom nivou, koja se realizuje upravo ovim signalima, pa
kolizije paketa neće biti rijetke. Komunikacija se obavlja standardno na 9600 bita u
sekundi, što je zadovoljavajuće.

Uprkos ovako ograničenom priključku, ulazno/izlazni portovi kalkulatora


TI89 su dobro realizovani, jer je omogućeno očitavanje cijelog primljenog bajta,
pojedine linije (crvene ili bijele) te generisanje interapta na primljenu vrijednost.

Port $60000C: Bit 6 omogućava direktni pristup portu, bitovi 3-0 omogućuaju
interapt respektivno u slučaju greške, bilo kakve aktivnosti linka, praznog bafera za
slanje i primljenog novog bajta

Port $60000D: Bit 7 indicira grešku, bit 6 indicira da je bafer za slanje prazan,
bit 5 indicira da imamo bajt u prijemnom baferu, bit 4 da je zahtijevan autointerapt

Port $60000E: Bit 3 čitanje s bijele linije, bit 2 čitanje s crvene linije, bit 1
upis u bijelu liniju ako je dopušten direktan prisup, bit 0 upis u crvenu liniju ako je
dopušten direktan prisup

Port $60000F: cijeli bajt koji šaljemo na link ili čitamo s njega

Sivi GraphLink se može preko Null modem kabla i gender changera povezati s
standardnim modemom, ali to još nije isprobano. Za sada je kao cilj postavljeno da
aplikacija radi sa direktnom vezom.

Prilikom razvoja ovog TCP/IP protokola pojavio se problem adekvatnog


programa sa PC strane. Windows, čak i u NT verziji, nije dobar kao server za Internet
preko stalne veze. Linux je izgledao kao pogodnije rješenje, ali nije bilo moguće
postići da linije DTR i CTS ostanu aktivne. Stoga je sa PC strane iskorišten
radioamaterski program KA9Q NOS, koji je zadovoljio većinu potreba (osim
protokola HTTP i POP3), a radi pod DOS-om. Program KA9Q NOS može uspješno
da radi u prozoru pod Windowsom, a može se preuzeti s adrese
ftp://ftp.cdrom.com/pub/simtelnet/msdos/tcpip/s951123.zip

PROGRAMSKE DEKLARACIJE

Kao i u svaki C program i TCP/IP paket za TI-89 počinje skupom deklaracija.


Većina od njih biće objašnjena kasnije u kodu.
6

Globalni simbol DEBUG_TCP, ako je definisan, omogućuje štampanje trenutnog


stanja TCP/IP protokola.
/*#define DEBUG_TCP*/

Header datoteke koje omogućavaju upotrebu rutina operativnog sistema djelo su


asistenta ETF u Sarajevu Željka Jurića, koji je istražio veliki broj nedokumentovanih
rutina operativnog sistema kalkulatora TI-89.
#include <nostub.h>
#include <alloc.h>
#include <string.h>
#include <link.h>
#include <kbd.h>
#include <graph.h>
#include <peekpoke.h>
#include <system.h>
#include <unknown.h>
#include <dialogs.h>
#include <menus.h>
#include <ctype.h>
#include <vat.h>
int _ti89;
Standardni tipovi su redefinisani radi veće čitljivosti.
#define True 1
#define False 0
#define Void void
#define Longword unsigned long /* 32 bits */
#define Word unsigned short /* 16 bits */
#define Byte unsigned char /* 8 bits */

Makro ADDR definiše IP adrese. Date su i vrijednosti podrazumijevanih IP adresa.


#define ADDR(a,b,c,d) (((long)a<<24L)|((long)b<<16L)|((long)c<<8L)|((long)d))
#define MY_ADDR ADDR( 192, 168, 0, 64 )
#define HOST_ADDR ADDR( 192, 168, 0, 1 )
#define IP_Address Longword

Radi olakšanja poziva procedura kao parametara procedura definisan je tip Procref.
#define P(x) x
typedef void ( *Procref ) P((void *s, Byte *dp, int len ));
typedef void ( *Procrefv ) P(( void ));

Iako se koristi isključivo serijska veza, IP zaglavlje sadrži i Eternet adresu, pa je i ona
deklarisana
struct Ethernet_Address {
Word w[ 3 ];
};
/* Ethernet zaglavlje */

struct eth_Header {
struct Ethernet_Address destination; /* 48 bits = 6 bytes */
struct Ethernet_Address source; /* 48 bits = 6 bytes */
Word type; /* 16 bits = 2 bytes */
}; /* total 14 bytes */

O strukturi IP zaglavlja biće još riječi kasnije.


/* Internet zaglavlje: */
struct in_Header {
Word vht; /* verzija, hdrlen, tos */
/* 2 bytes */
/* version = prvi nybble = 4. */
/* hdrlen = nizi nybble prvog bajta =
duzina zaglavlja u duplim rijecima = 5 */
Word length; /* 2 bytes */
Word identification;/* 2 bytes */
7

Word frag; /* 2 bytes */


Word ttlProtocol; /* 2 bytes */
/* prvi byte je TTL = 'time to lose' */
/* drugi byte je protocol */
Word checksum; /* 2 bytes */
IP_Address source; /* IP adresa - 4 bytes */
IP_Address destination; /* IP adresa - 4 bytes */
}; /* ukupno 20 bytes */

/* Makroi koji parsiraju dio IP poruke */

#define IP_VERSION(ip) ( ip->vht >> 12) & 0xf


#define IP_HLEN(ip) ( ip->vht >> 8) & 0xf
#define IP_HBYTES(ip) ( ip->vht >> 6) & 0x3c
#define IP_PROTOCOL(ip) ip->ttlProtocol & 0xff

O strukturi TCP zaglavlja biće još riječi kasnije.

struct tcp_Header {
Word srcPort;
Word dstPort;
Longword seqnum;
Longword acknum;
Word flags;
Word window;
Word checksum;
Word urgentPointer;
};

#define TCPF_FIN 0x0001


#define TCPF_SYN 0x0002
#define TCPF_RST 0x0004
#define TCPF_PUSH 0x0008
#define TCPF_ACK 0x0010
#define TCPF_URG 0x0020
#define TCPF_DO 0xF000
#define TCP_DATAOFFSET(tp) tp->flags >> 12

/* UDP zaglavlje je samo 8 bytes */

/* The TCP/UDP Pseudo Header. Used for computing checksum. */

struct tcp_Pseudoheader {
IP_Address src; /* 4 bytes */
IP_Address dst; /* 4 bytes */
Byte mbz; /* mora biti 0. 1 byte */
Byte protocol; /* 6 = tcp. 1 byte */
Word length; /* 2 bytes */
Word checksum; /* 2 bytes */
}; /* 14 bytes */

Stanja konačnog automata TCP/IP protokola.

#define TS_LISTEN 1 /* osluskivanje konekcije */


#define TS_SSYN 2 /* syn poslan, active otvoren */
#define TS_RSYN 3 /* syn primljen, synack+syn poslan. */
#define TS_ESTAB 4 /* uspostavljeno */
#define TS_SFIN 5 /* poslan FIN */
#define TS_AFIN 6 /* poslan FIN, primljen FINACK */
/* #define TS_CLOSEWT 7 */ /* primljen FIN cekanje na zatvaranje */
#define TS_RFIN 8 /* (zatvaranje) poslan FIN, primljen FIN */
/* (cekanje na FINACK) */
#define TS_LASTACK 9 /* fin primljen, finack+fin poslan */
#define TS_TIMEWT 10 /* cekanje nakon slanja zadnjeg FINACK */
#define TS_CLOSED 11 /* (zatvoreno) finack primljen */

Soket je u ovoj aplikaciji širi pojam od kombinacije porta i IP adrese. Uz njega su


uključeni i još neki parametri kao što su trenutni broj sekvence, timeout, broj
preostalih bajtova i slično.
8

/* ------ TCP Socket definicija ------------------------------------- */

#define TCP_MAXDATA 512 /* maximum bajtova za prikupljanje na izlazu */

struct tcp_Socket {
struct tcp_Socket * next; /* pokazivac na sljedeci soket */
short state; /* stanje veze */
Procref dataHandler; /* pozvano s podacima koji dolaze */
struct Ethernet_Address hisethaddr; /* ethernet adresa udaljenog */
IP_Address hisaddr; /* internet adresa udaljenog */
Word myport, hisport; /* tcp portovi za ovu konekciju */
Longword acknum, seqnum; /* potvrdjeni i sekvencni broj */
Longword timeout; /* timeout, u milisekundama */
short unhappy; /* flag, retransmisije semenata */
Word flags; /* flegovi zadnjeg paketa */
short dataSize; /* broj bajtova podataka koji se salju */
Byte data[ TCP_MAXDATA ]; /* podaci koji se salju */
};

Slijede prototipi svih funkcija.


int sed_Init P(( Void ));
int sed_Deinit P(( Void ));
Byte * sed_FormatPacket P(( Byte *destEAddr, int ethType ));
int sed_Send P(( int pkLengthInOctets ));
int sed_Receive P(( Byte *recBufLocation ));
Byte * sed_IsPacket P(( Void ));
int sed_CheckPacket P(( Word *recBufLocation, Word expectedType ));
Void tcp_Init P(( Void ));
Void tcp_Open P(( struct tcp_Socket *s, Word lport, IP_Address ina,
Word port, Procref datahandler ));
Void tcp_Listen P(( struct tcp_Socket *s, Word port, Procref datahandler,
Longword timeout ));
Void tcp_Close P(( struct tcp_Socket *s ));
Void tcp_Abort P(( struct tcp_Socket *s ));
int tcp P(( Procrefv application ));
int tcp_Write P(( struct tcp_Socket *s, Byte *dp, int len ));
Void tcp_Flush P(( struct tcp_Socket *s ));

/* redefinisati move kao memcpy */


#define Move(s,d,n) memcpy(d,s,n)
Void tcp_Unthread P(( struct tcp_Socket *ds ));
Void tcp_Retransmitter P(( Void ));
Void tcp_ProcessData P(( struct tcp_Socket *s,
struct tcp_Header *tp, int len ));
Void tcp_Send P(( struct tcp_Socket *s ));
Void tcp_DumpHeader P(( struct in_Header *ip,
struct tcp_Header *tp, char *mesg ));
Void tcp_Handler P(( struct in_Header *ip ));

Word checksum P(( Word *dp, int length ));


Longword lchecksum P(( Word *dp, int length ));

Void main P(( int argc, char **argv ));

Longword MsecClock P(( Void ));


void outp_char(char a);
void LineInput(void);
char inp_char();
void counters(void);
void puts (char *s);
void four_bytes(char *buf,unsigned char *n);

Vremenske konstante definisane su u milisekundama.

#define tcp_RETRANSMITTIME 1000 /* interval u kome se zove retransmisija, 1 sekunda */


#define tcp_LONGTIMEOUT 30000 /* timeout za otvorenu vezu */
#define tcp_TIMEOUT 20000 /* timeout tokom konekcije */

Pomalo je neuobičajeno deklarisati sve globalne varijable kao statičke, no pokazalo se


da zbog baga u linkeru isporučenom uz TIGCC kompajler, jedino na taj način moguće
je napraviti program neovisan od dodatnih kernela i biblioteka.
9

/* ----- staticni podaci ------------------------------------------------ */

static IP_Address local_IP_address=0, /* lokalna IP adresa */


remote_IP_address=0;
static int tcp_id=0; /* TCP ID, inkrementira se */

static struct tcp_Socket * tcp_allsocs=0;


static struct tcp_Socket * ftp_ctl=0;
static struct tcp_Socket * ftp_data=0;
static Byte * ftp_cmdbuf=0;
static int ftp_cmdbufi=0;

static Byte * ftp_outbuf=0;


static int ftp_outbufix=0,
ftp_outbuflen=0;

static short ftp_rcvState=0;


static Byte ftp_echoMode=0;
static unsigned char * p_begin_packet = NULL;
static unsigned char * p_recv_next = NULL;
static unsigned char *xmitbuffer=NULL;
static unsigned char *recvbuffer=NULL;
static void * queue=NULL;
static int EndProgram=0;
static char kbrdcycle=0;
static Word remoteport=0;
HANDLE hreturnfile=0;

Preostalo je još definisati statuse FTP klijenta.


#define ftp_StateGETCMD 0 /* uzimanje komande od korisnika */
#define ftp_StateIDLE 1 /* idle konekcija */
#define ftp_StateINCOMMAND 2 /* komanda poslana, ceka se odgovor */
#define ftp_StateRCVRESP 3 /* primljen odgovor, treba jos podataka */
#define SIZECMDBUF 120
#define OUTBUFSIZE 80

PODSLOJ SLOJA VEZE PODATAKA ZA PRISTUP MEDIJUMU

Operativni sistem kalkulatora TI89 zvani AMS ima potrebne rutine za


preuzimanje podataka sa linka koristeći prekide (interrapte). Nailazak novog bajta sa
linka generiše autointerapt 6. Rutina za obradu ovog prekida zatim smješta taj bajt u
bafer. Očitavanje se vrši funkcijom receive koja prenosi navedeni broj bajtova iz
bafera na datu adresu i kao rezultat vraća broj zaista prenesenih bajtova. Slanje se vrši
funkcijom transmit koja ima ista dva parametra. Za slanje jednog bajta napisana je
funkcija outp_char
void outp_char(char a)
{
transmit(&a,1);
}

Očitavanje bajtova je dosta jednostavno, ali je bafer jako ograničen, 128


bajtova. To se, nažalost, može riješiti samo prepisom odgovarajućih rutina iz ROM-a
što zahtijeva asemblerske podprograme i izvan je dosega ovog seminarskog rada.
Stoga je veličina MTU koji se prima postavljena na 64 bajta. Redundancija će biti
velika, ali gubljenje bajtova smanjeno.

SLOJ VEZE PODATAKA


10

U izboru između SLIP, PPP i HDLC protokola , uprkos superiornosti ostala


dva, izabran je SLIP zbog jednostavnosti implementacije. SLIP protokol prenosi
podatke uglavnom onakve kakvi su, s tim što na svom početku i kraju ima znak 0xC0.
Ukoliko je potrebno poslati upravo taj znak, on se kodira kao 0xDBDC, a ako je
potrbno poslati 0xDB šalje se 0xDBDD.
/* ----- interne definicije --------------------------------------- */

#define FR_END 0xC0 /* 0300 */


#define FR_ESC 0xDB /* 0333 */
#define T_FR_END 0xDC /* 0334 */
#define T_FR_ESC 0xDD /* 0335 */

#define BUFFERSIZE 8192

Funkcija sed_Send, slijedeći ova pravila šalje podatke SLIP protokolom.


/* ----- slanje SLIP protokolom ---------------------------------- */

int sed_Send( pkLengthInBytes ) int pkLengthInBytes; {


int i;
unsigned char * p_uc;

if( pkLengthInBytes <= 0 ) return 1; /* ne radi nista */

/* Pri slanju procesira se escape


Ako nadjemo FR_END, mijenja se u FR_ESC + T_FR_END.
Ako nadjemo FR_ESC, mijenja se u FR_ESC + T_FR_ESC. */

outp_char( FR_END ); /* salji FR_END. */


p_uc = &xmitbuffer[ 0 ];

for( i = 0; i < pkLengthInBytes; ++i ) {


switch( *p_uc ) {
case FR_END:
outp_char( FR_ESC );
outp_char( T_FR_END );
break;
case FR_ESC:
outp_char( FR_ESC );
outp_char( T_FR_ESC );
break;
default:
outp_char( *p_uc );
}
++p_uc;
}
outp_char( FR_END );
return 1;
}

Provjera da li je neki frejm stigao vrši se s vremena na vrijeme i primljeni


bajtovi čuvaju u 8K dugom cirkularnom baferu. Sada je onako poslan okvir potrebno
dekodirati iz SLIP formata u izvorni. Tu je ključna uloga varijable b_esc_flag koja
indicira da li je aktivan ESCAPE kod (0xDB). Takođe, broji se koliko je bilo END
markera (0xDB) brojačem end_count. Kada ovaj brojač dostigne 2, frejm je stigao i
prepušta se višim slojevima. Ali, ako su END markeri susjedni bajtovi, a brojač je 2,
znači da je usljed neke kolizije kraj frejma shvaćen kao njegov početak i taj se frejm
mora odbiti.

/* ----- Provjera primljenog frejma ---------------------------- */

/* Provjerava se primljeni frejm i vraca njegova lokacija, ako nije primljen


vraca se nula. */

Byte * sed_IsPacket() {
unsigned char uc;

#ifdef DEBUG_SED
11

unsigned char * p;
#endif
static int end_count = 0;
static int b_esc_flag = 0;
static unsigned char lastuc=0;
Longword delay;
if (p_begin_packet==NULL) {
p_begin_packet = &recvbuffer[0];
end_count=0;
}
if (p_recv_next==NULL) {
p_recv_next = &recvbuffer[0];
end_count=0;
}
if ((Longword)p_recv_next>
(Longword)&recvbuffer[BUFFERSIZE-1]) {
p_begin_packet=&recvbuffer[0];
p_recv_next=&recvbuffer[0];
puts("Full buffer");
OSLinkReset();

/* Ima li jos podataka na ulazu */


while( receive(&uc,1) != 0 ) { /* ocitaj znak*/

if( b_esc_flag ) {
switch( uc ) {
case T_FR_ESC:
*p_recv_next++ = FR_ESC;
break;
case T_FR_END:
*p_recv_next++ = FR_END;
break;
default:
*p_recv_next++ = uc;
}
b_esc_flag = 0;
} else {
switch( uc ) {
case FR_ESC:
b_esc_flag = 1;
break;
case FR_END:
++end_count;
if ((end_count==2)&&
(p_begin_packet==p_recv_next)) {
end_count=1;
OSLinkReset();

if( (end_count == 1)) {


p_begin_packet = p_recv_next;
if (((Longword)p_begin_packet)&1){
*p_begin_packet++;
}
/*Izbjeci neparne adrese*/
}
break;
default:
*p_recv_next++ = uc;
}
}
if( end_count == 2 ) break;
}
/* Imamo li frejm u baferu? */

if( end_count == 2 ) {
p_recv_next = &recvbuffer[ 0 ];
end_count = 0;
return p_begin_packet;

} else {
return ( Byte * ) 0;
}
}
12

Funkcijom ResetBuffer postavljamo pokazivače bafera za prijem SLIP


frejmova na nulu, što se koristi u slučaju neispravnih datagrama.

int ResetBuffer()
{
p_begin_packet=NULL;
p_recv_next==NULL;

/* ----- Formatiraj ethernet zaglavlje u izlaznom baferu ----------- */


/* vrati pointer gdje se formira IP poruka */

Byte * sed_FormatPacket( destEAddr, ethType )


Byte *destEAddr; int ethType; {

return ( Byte * ) &xmitbuffer[ 0 ]; /* vrati pointer na prvi bajt */


}

MREŽNI SLOJ

Kao protokol mrežnog sloja izabran je IP, uz izvjesna pojednostavljenja. Pošto


je riječ o uređaju koji ima samo jedan izlazni port, nema potrebe za rutiranjem jer se
sva slanja na druge IP adrese jednostavno prosljeđuju na izlaz. Računar s druge strane
RS232 veze će obaviti rutiranje. Ulazni datagrami, s druge strane, će biti obrađeni ako
kao odredište imaju IP adresu kalkulatora.

IP zaglavlje izgleda kao na sljedećoj slici:

<------------------------------------------32 bits------------------------------------->
Version IHL Type of service Total length
Identification DF/MF/Fragment offset
Time to live Protocol Header checksum
Source address
Destination address
Options

Ono je mapirano strukturom IP_header (gore deklarisanom) koja kada se


grafički predstavi izgleda ovako:

<------------------------------------------32 bits------------------------------------->
vht Length
identification Frag
ttlProtocol Checksum
Source
Destination
Neki podaci su dakle pakovani u 16-bitne riječi, a opcije ne postoje. Neki od
podataka se ignorišu.
13

Prijem datagrama se vrši u glavnoj petlji funkcije tcp. Ona kao parametar ima
pokazivač na funkciju application koja će se pozivati u stanju kada se ne vrši obrada
primljenih datagrama.

Funkcija se vrti u “beskonačnoj” for (;;) petlji iz koje se izlazi kada varijabla
EndProgram ima vrijednost različitu od nule (a to će se postići kada se pritisne ESC
taster). Pozivom gore opisane funckije sed_IsPacket se dobije početna adresa SLIP
frejma. Pošto SLIP dopušta da se njim prenosi jedino IP protokol, to će ujedno značiti
i početnu adresu IP datagrama. Ako je ta adresa jednaka nuli, to je idle stanje i
iskoristiti će se za unos korisničke komande i retransmisiju datagrama.

Primljeni datagram se provjerava na ispravnost IP adrese i čeksuma zaglavlja.


Provjera na ovom sloju je bitna jer SLIP nema mehanizma provjere. Ako su ovi
parametri neispravni datagram se odbacuje. Ukoliko su ispravni, provjeri se da li je tip
IP datagrama 6, što predstavlja TCP protokol i tako poziva odgovarajući handler koji
se izvršava u funkciji tcp_Handler. Buduće verzije ovog programa (koje će zanemariti
8 K limit) će uključiti i UDP i ICMP protokole.

/* ----- TCP petlja koja proziva aplikacijski program --- */

int tcp( application )

Procrefv application;
{
struct in_Header * ip;
Longword timeout, start;

timeout = 0L;
for(;;) {
if (EndProgram) break;
start = ( Longword ) MsecClock();
/* Imamo li paket? */
ip = ( struct in_Header * ) sed_IsPacket();
if( ip == ( struct in_Header * ) 0 ) {
/* Nemamo ga. */
if( MsecClock() > timeout ) {
/* Anything to retransmit? */
tcp_Retransmitter();
/* Set next transmit time */
timeout = MsecClock() + tcp_RETRANSMITTIME;
}
/* Nema posla, pa neka korisnik unese komandu. */
application();
continue;
}

/* Paket primljen, procesirajmo ga */


if( ip -> destination != local_IP_address ) {
#ifdef DEBUG_TCP
puts( "Wrong IP" );
#endif
OSLinkReset();
ResetBuffer();
goto recycle;
}
if( checksum( ( Word * ) ip, IP_HBYTES( ip )) != 0xFFFF ) {
#ifdef DEBUG_TCP
puts("Bad IP checksum!" );
#endif
OSLinkReset();
ResetBuffer();
goto recycle;
}

if ( (IP_PROTOCOL( ip )) == 6) tcp_Handler(ip);
14

/* reciklaza */
recycle:
}

return 1;
}

Čeksum IP datagrama se vrši funkcijama lchecksum i checksum. Funkcija


lchecksum sabira riječi (16 bitne) while petljom, a predzadnja linija dopunjuje sumu
zadnjim bajtom nula ako je dužina bila neparna.
/* ----- racuna Longword sumu rijeci u poruci ----------------- */
/* (parcialni checksum) */

Longword lchecksum( dp, length ) Word *dp; int length; {


int len;
Longword sum;

len = length >> 1;


sum = 0L;

while ( len-- > 0 ) sum += ( *dp++ );


if( length & 1 ) sum += ( ( *dp ) & 0xFF00 );

return sum;
}

Funckija checksum poziva funkciju lchecksum a zatim sabira višu i nižu riječ
ovako dobijene sume po modulu 65536 (0xFFFF).
/* ----- racuna Word checksum ------------------------------------ */

Word checksum( dp, length ) Word *dp; int length; {


Longword sum;

sum = lchecksum( dp, length );

while( sum & 0xFFFF0000L )


sum = ( sum & 0xFFFFL ) + (( sum >> 16L ) & 0xFFFFL );

return ( Word )( sum & 0xFFFF );


}

Datagrami se šalju funkcijom tcp_Send. Kod ove funkcije su djelomično


pomiješane opcije koje pripadaju transportnom sloju i mrežnom sloju, jer ona kao
parametar ima kompletan soket. Datagram se neće slati ako je navedeni soket u stanju
TS_CLOSED, tj. da veza nije uspostavljena, dok se za sinhronizacione datagrame ne
šalju podaci, nego samo zaglavlja. Ovom funkcijom se pripreme inicijalne vrijednosti
polja u IP i TCP zaglavljima, te izračuna čeksum istih. Nakon toga se SLIP
protokolom pošalje frame, funkcija sed_Send.

Void tcp_Send( s ) struct tcp_Socket *s; {


struct tcp_Pseudoheader ph;
struct _pkt {
struct in_Header in;
struct tcp_Header tcp;
Longword maxsegopt;
} * pkt;
Byte * dp;
Longword lw;

/* ne radi to ako je u Closed stanju ili socket nije u listi */

if(( s -> state == 0 ) || ( s -> state == TS_CLOSED ))


return;
pkt = ( struct _pkt * ) sed_FormatPacket(
15

( Byte * ) &( s -> hisethaddr.w[ 0 ] ), 0x800 );


dp = ( Byte * ) &( pkt -> maxsegopt );
if( s -> flags & TCPF_SYN ) {
/* Ne treba slati podatke u SYN */
pkt -> in.length = ( sizeof( struct in_Header )
+ sizeof( struct tcp_Header ));
} else {
pkt -> in.length = ( sizeof( struct in_Header )
+ sizeof( struct tcp_Header ) + s -> dataSize );
}
/* tcp zaglavlje */
pkt -> tcp.srcPort = ( s -> myport );
pkt -> tcp.dstPort = ( s -> hisport );
pkt -> tcp.seqnum = ( s -> seqnum );
pkt -> tcp.acknum = ( s -> acknum );
pkt -> tcp.window = ( 64 );
/* Ovo 64 je velicina podataka koji se mogu primiti */
pkt -> tcp.flags = ( s -> flags | 0x5000 );
pkt -> tcp.checksum = 0;
pkt -> tcp.urgentPointer = 0;
if( s -> flags & TCPF_SYN ) {
/* Ako se salju opcije, dodaje se 1 DWORD na ofset podataka */
pkt -> tcp.flags = ( ( pkt -> tcp.flags ) + 0x1000 );
/* Dodati 4 na duzinu */
pkt -> in.length = ( ( pkt -> in.length ) + 4 );
/* Opcije. Vrsta 02, duzina 04, vrijednost 1400.*/
pkt -> maxsegopt = ( 0x02040578 ); /* 1400 bytes */
dp += 4;
} else {
/* Salji posatke samo ako nije SYN. */
Move( s -> data, dp, s -> dataSize );
}

/* internet zaglavlje */
pkt -> in.vht = ( 0x4500 ); /* verzija 4, duzina 5, tos 0 */
pkt -> in.identification = ( tcp_id++ );
pkt -> in.frag = 0;
pkt -> in.ttlProtocol = (( 250 << 8 ) + 6 );
pkt -> in.checksum = 0;
pkt -> in.source = ( local_IP_address );
pkt -> in.destination = ( s -> hisaddr );
pkt -> in.checksum = ((Word ) ~ checksum( ( Word * ) &pkt -> in,
sizeof( struct in_Header )));
/* racunaj tcp checksum */
ph.src = pkt -> in.source;
ph.dst = pkt -> in.destination;
ph.mbz = 0;
ph.protocol = 6;
ph.length = (( pkt -> in.length ) - sizeof( struct in_Header ));
lw = lchecksum( ( Word * ) &ph, sizeof( ph ) - 2 );
while( lw & 0xFFFF0000L )
lw = ( lw & 0xFFFFL ) + (( lw >> 16L ) & 0xFFFFL );
lw += lchecksum( ( Word * ) &pkt -> tcp,
( ph.length ));
while( lw & 0xFFFF0000L )
lw = ( lw & 0xFFFFL ) + (( lw >> 16L ) & 0xFFFFL );
pkt -> tcp.checksum = ( ~ ( Word )( lw & 0xFFFFL ));
#ifdef DEBUG_TCP
tcp_DumpHeader( &pkt -> in, &pkt -> tcp, "Sending" );
#endif
sed_Send( ( pkt -> in.length ));
}

Svi nepotvrđeni datagrami se čuvaju u listi takozvanih soketa. Početni


pokazivač na ovu listu je tcp_allsocs. S vremena na vrijeme se vrši njihova
retransmisija. Retransmisija se vrši tako da se prođe kroz cijelu ovu listu i izvrši
tcp_Send nad svim ovim datagramima. Svakom soketu se umanji predviđeno vrijeme
za tcp_RETRANSMITTIME i kada to vrijeme dostigne nulu ili negativnu vrijednost,
provjeri se da li je soket u stanju TS_TIMEWT. Ako jeste, to znači da je veza
regularno završena i briše se handler ako ga je bilo, te se soket izbacuje iz liste. Ako
nije, to znači da je došlo do neregularnog prekida veze i poziva se tcp_Abort.
16

/* ----- retransmisija ---------------------------------------------- */


/* Periodicno se poziva radi TCP retrasmisije */
Void tcp_Retransmitter( Void ) {
struct tcp_Socket * s;
short x;
/* Pogledaj sve sokete ako se ima nesto slati */
for( s = tcp_allsocs; s; s = s -> next ) {
x = False;
if( s -> dataSize > 0 || s -> unhappy ) {
/* salji podatke */
tcp_Send( s );
x = True;
}
/* ista poslano? */
if( x || s -> state != TS_ESTAB )
s -> timeout -= tcp_RETRANSMITTIME;
if( s -> timeout <= 0 ) {
if( s -> state == TS_TIMEWT ) {
#ifdef DEBUG_TCP
puts("Closed." );
#endif
s -> state = TS_CLOSED;
if( s -> dataHandler != 0 )
( s -> dataHandler )(( void * ) s,
( Byte * ) 0, 0 );
#ifdef DEBUG_TCP
else puts("got close no handler" );
#endif

/* Izbaci socket iz ulancane liste */


tcp_Unthread( s );
} else {
puts( "Timeout\n" );
tcp_Abort( s );
}
}
}
}

Vrijeme na osnovu kojeg se određuje retransmisija generiše se na bazi funkcije


MsecClock.
Longword MsecClock P(( Void ))
{ Longword value;
if (OSTimerExpired(6)) OSTimerRestart(6);
value=5*(32000-OSTimerCurVal(6));
return (value);
}

Brisanje soketa iz liste vrši se funkcijom tcp_Unthread. Ona prođe kroz cijelu
listu i poveže pokazivače ostalih soketa da više ne pokazuju na dati soket.
/* ----- Izbaci soket iz liste ako je tu ------ */

Void tcp_Unthread( ds ) struct tcp_Socket *ds; {


struct tcp_Socket *s, **sp;
if( ds == ( struct tcp_Socket * ) 0 ) return;
sp = &tcp_allsocs; /* -> -> socket */
for(;;) {
s = *sp; /* Na koji on pointer pokazuje */
if( s == ( struct tcp_Socket * ) 0 ) break; /* kraj liste? */
if( s == ds ) { /* slaze se s onim koji brisemo? */
*sp = s -> next; /* odvezi ga iz liste */
break; /* zavrseno */
}
sp = &s -> next; /* idemo na sljedeci */
}
ds -> next = ( struct tcp_Socket * ) 0; /* ocisti naredni pointer */
}
17

TRANSPORTNI SLOJ

U ovom trenutku jedini protokol transportnog sloja koji je implementiran je


TCP. Ostali protokoli su svakako lakši za implementaciju.

Prvi zadatak transportnog sloja je sjeckanje podataka na datagrame do veličine


definisane sa tzv. MTU, maximum transfer unit. Funkcija tcp_Write vraća 0 ako
konekcija nije uspostavljena. Njen parametar je soketska struktura koja predstavlja
trenutno stanje određene TCP/IP konekcije. Ako je dužina segmenta veća od
maksimalne dopuštene za slanje (default TCP_MAXDATA=512 bajtova) Move
funkcijom se u transmisioni bafer prebaci onoliko bajtova koliko je moguće, a
funkcija vraća broj prenesenih bajtova. Po izlazu iz funkcije tcp_Write poziva se
tcp_Flush koja dalje zove funkciju tcp_Send, već opisanu u mrežnom sloju.
/* ----- Upis podataka u konekciju. -------------------------------- */

/* Vraca broj upisanih bajtova, == 0 ako konekcija nije uspostavljena. */

int tcp_Write( s, dp, len )


struct tcp_Socket *s;
Byte *dp;
int len;
{
int x;

/* ako konekcija nije uspostavljena ne salji nista */

if( s -> state != TS_ESTAB ) return 0;

/* koliko se moze pomjeriti */

if( len > ( x = TCP_MAXDATA - s -> dataSize ) ) len = x;


if( len > 0 ) {
Move( dp, &s -> data[ s -> dataSize ], len );
s -> dataSize += len;
tcp_Flush( s );
}
return len; /* vrati broj poslanih bajtova */
}

/* ----- Slanje podataka koji cekaju na to---------------------------------- */


Void tcp_Flush( s ) struct tcp_Socket *s; {
if(( s -> state == 0 ) || ( s -> state == TS_CLOSED ))
return;
if( s -> dataSize > 0 ) {
s -> flags |= TCPF_PUSH;
tcp_Send( s );
}
}
18

TCP paket izgleda ovako:

<----------------------------------------- 32 bits ------------------------------------------------>


Source port Destination port
Sequence number
Acknowledgement number
TCP U A P R S F Window size
Header R C S S YI
length G K H T NN
Checksum Urgent pointer
Options (0 or more 32bit words)
Data (optional)

Polja u gore opisanoj strukturi se poklapaju sa strukturom tcp_Header.

<----------------------------------------- 32 bits ------------------------------------------------>


srcPort dstPort
seqnum
acknum
flags window
checksum urgentPointer
Za lakše praćenje primljenih paketa napravljena je funkcija tcp_DumpHeader.

/* ----- Stampanje zaglavlja TCP protokola ----------------------- */


#ifdef DEBUG_TCP
Void tcp_DumpHeader( ip, tp, mesg )
struct in_Header *ip;
struct tcp_Header *tp;
char *mesg;
{
static char *flags[] = { "FIN", "SYN", "RST", "PUSH", "ACK", "URG" };
int len;
Word f;

len = ( ip -> length )


- (( TCP_DATAOFFSET( tp ) + IP_HLEN( ip )) << 2 );

printf("TCP: %s pkt", mesg);


printf("SP: %x; DP: %x; ",
( tp -> srcPort ),
( tp -> dstPort ));
printf("Seq=%lx AckN%lx Wnd=%d DLen=%d",
( tp -> seqnum ),
( tp -> acknum ),
( tp -> window ),
len );
printf("DO=%d, C=%x U=%d",
TCP_DATAOFFSET( tp ),
( tp -> checksum ),
( tp -> urgentPointer ));

f = ( tp -> flags );
printf("%x",f);

}
#endif
19

Primljeni paketi se obrađuju odgovarajućim handlerom za TCP protokol. Ova


rutina, tcp_Handler, je najsloženija rutina u cijelom programu. Nakon primljenog
paketa, pregledaju se svi do sada aktivni soketi, koji su smješteni u spregnutu listu.
Provjerava se ima li koji sa istim izvorišnim i odredišnim portovima kao i primljeni
paket. Zatim se obavi slična provjera sa pasivnim soketima, s tim što je kod njih
udaljeni port jednak nuli, jer nije naša aplikacija inicirala paket. Ukoliko ni nakon ove
provjere ne dođemo do željenog paketa u listi, paket se odbacuje.

Sljedeći korak je proračun čeksuma zaglavlja. Za to se po TCP standardu


definiše pseudozaglavlje, u varijabli ph. U pseudozaglavlje se prenesu odgovarajući
podaci iz samog zaglavlja i nad njim izračuna čeksum. Ako čeksum nije valjan, paket
se odbacuje.

active OPEN
CLOSED -----------
create TCB
snd SYN
passive OPEN CLOSE
------------ ----------
create TCB delete TCB

CLOSE
LISTEN ----------
delete TCB
rcv SYN SEND
----------- -------
snd SYN,ACK snd SYN

SYN rcv SYN SYN


RCVD SENT
snd ACK

rcv ACK of SYN rcv SYN,ACK


-------------- -----------
x snd ACK

CLOSE
------- ESTAB
snd FIN
CLOSE rcv FIN
------- -------
snd FIN snd ACK
FIN CLOSE
WAIT-1 WAIT
rcv FIN
rcv ACK of FIN ------- CLOSE
-------------- snd ACK -------
x snd FIN

FINWAIT-2 CLOSING LAST-ACK

rcv ACK of FIN rcv ACK of FIN


rcv FIN -------------- Timeout=2MSL --------------
------- x V ------------ x V
snd ACK delete TCB
TIME WAIT CLOSED

TCP radi na bazi konačnog automata, definisanog u RFC 793. Konačni


automat ima jedanaest stanja, prikazanih na slici iznad. Ako je u zaglavlju paketa
setovan bit RST, automat prelazi u stanje CLOSED. Prije toga se poziva rutina koja
obrađuje paket za dati soket. Ove rutine pripadaju aplikativnom sloju, a pointer na
funkcije koji ih obrađuju je sastavni dio svakog soketa.
20

Potvrda prijema paketa se vrši poljima seqnum i acknum. Po TCP/IP


standardu, prijemna mašina uvijek gleda seqnum sadržaj i u paketu koji šalje kao
odgovor polje acknum će sadržati primljenu vrijednost iz seqnum uvećanu za veličinu
podataka paketa+1.

Ukoliko u stanju LISTEN (čekanje na poziv, u kodu case TS_LISTEN) naiđe


paket sa setovanim bitom SYN, pravi se novi paket sličnog sadržaja kao i ulazni.
Vrijednost polja acknum u tom paketu je jednaka vrijednosti seqnum iz ulaznog
paketa uvećana za 1. U paketu se setuju bitovi SYN i ACK, te se paket šalje. Prelazi
se u stanje SYN RCVD, a brojač isteklog vremena postavi na početnu vrijednost.

Iz stanja SYN SENT (aplikacija je startovana da bi otvorila konekciju, u kodu


case TS_SSYN) se prelazi u stanje SYN RCVD ako ulazni paket ima setovan bit
SYN, a ako je setovan i SYN i ACK prelazi se u stanje ESTAB uz adekvatno
ažuriranje polja acknum. Ukoliko SYN nije setovan, znači da prijemna strana nije
odgovorila na zahtjev za sinhronizaciju i ovaj paket se ponovo šalje.

U stanju SYN RCVD (zahtjev za konekciju je stigao, čeka se na ACK, u kodu


case TS_RSYN), ako je setovan bit SYN, znači da iz nekog razloga potvrda ACK nije
stigla pa se primljeni paket pošiljaocu vraća s setovanim bitovima SYN i ACK, pod
pretpostavkom da on nije primio odgovarajući paket. Ukoliko je setovan bit ACK,
prelazi se u ESTAB stanje uz adekvatno ažuriranje polja acknum.

U stanju ESTAB (normalno stanje prijenosa podataka, u kodu označeno kao


case TS_ESTAB)bit ACK mora biti setovan, inače se ne vrši dalja obrada. Gleda se
acknum iz paketa i seqnum iz soketa. Udaljeni uređaj, koji je poslao paket, potvrđuje
naše poslane podatke. Razlika ove dvije vrijednosti mora biti pozitivna, što znači da
su naši podaci potvrđeni. Neka je ta razlika diff. Na početak bafera prikačenog uz
soket s izlaznim podacima postavi se blok sa pozicije diff u tom baferu. Ovo takođe
omoguććuje sjeckanje podataka u datagrame, što je zadatak transportnog sloja. U
soketu se promijene broj prebačenih bajtova (dataSize) i broj sekvence (seqnum) za
veličinu diff, te setuje ACK. Nakon ovoga se obrađuju primljeni podaci, funkcijom
tcp_ProcessData, o kojoj će biti riječi kasnije.

U stanju FIN_WAIT1 (aplikacija kaže da je završila, u kodu case TS_SFIN),


se zavisno od razlike sekvencnog i potvrđenog broja setuju ACK i FIN ili samo ACK
uz prelazak u stanje CLOSING. Primljeni podaci se obrađuju funckijom
tcp_ProcessData.

U stanju CLOSING (obije strane pokušale da simultano zatvore komunikaciju,


što je u kodu case TS_AFIN), setuje se bit ACK u soketu i obrade primljeni podaci
pomoću tcp_ProcessData.

Iz stanja FIN_WAIT2 (druga strana se složila za prekid, što je u kodu dato kao
case TS_RFIN) ako su brojevi sekvenci u redu se prelazi u stanje TIMED_WAIT.

U stanju LAST_ACK (čekanje kraja svih paketa, u kodu case TS_LASTACK)


se provjere brojevi sekvenci. Ako su u redu, prelazi se u CLOSED, izvrši rutina za
obradu primljenih podataka na tom portu još jednom ako je ima, i zbaci soket iz liste.
U protivnom se ponovo šalje primljeni paket s setovanim bitom FIN.
21

U stanju TIME_WAIT (čekanje na kraj svih paketa, case TS_TIMEWT) se


vraća paket pošiljaocu s bitom ACK setovanim, sve dok brojač retransmisija ne
postigne maksimum, i time istekne vrijeme.

/* ----- Handler za ulazne pakete. ------------------------------ */

Void tcp_Handler( ip ) struct in_Header *ip; {


struct tcp_Header * tp;
struct tcp_Pseudoheader ph;
int len;
int diff;
struct tcp_Socket * s;
Word flags;
Longword lw;
len = IP_HBYTES( ip );
tp = ( struct tcp_Header * )(( Byte * ) ip + len );
len = ( ip -> length ) - len;
/* pogledaj u aktivnim soketima */
for( s = tcp_allsocs; s; s = s -> next )
if(( s -> hisport != 0 )
&& ( ( tp -> dstPort ) == s -> myport )
&& ( ( tp -> srcPort ) == s -> hisport )
&& ( ( ip -> source )
== s -> hisaddr ))
break;
if( s == 0 ) { /* nismo nasli */
/* pogledaj u pasivnim soketima */
for ( s = tcp_allsocs; s; s = s -> next )
if(( s -> hisport == 0 )
&& ( ( tp -> dstPort )
== s -> myport ))
break;
}
if( s == 0 ) { /* Nismo nasli soket */
#ifdef DEBUG_TCP
tcp_DumpHeader( ip, tp, "Discarding" );
#endif
return;
}
#ifdef DEBUG_TCP
tcp_DumpHeader( ip, tp, "Received" );
#endif
ph.src = ip -> source;
ph.dst = ip -> destination;
ph.mbz = 0;
ph.protocol = 6;
ph.length = ( len );
lw = lchecksum( ( Word * ) &ph, sizeof ph - 2 );
while( lw & 0xFFFF0000L )
lw = ( lw & 0xFFFFL ) + (( lw >> 16L ) & 0xFFFFL );
lw += lchecksum( ( Word * ) tp, len );
while( lw & 0xFFFF0000L )
lw = ( lw & 0xFFFFL ) + (( lw >> 16L ) & 0xFFFFL );
if( lw != 0x0000FFFFL ) {
#ifdef DEBUG_TCP
printf( "bad tcp checksum (%lx)", lw );
#endif
OSLinkReset();
ResetBuffer();
return;
}
flags = ( tp -> flags );
if( flags & TCPF_RST ) {
#ifdef DEBUG_TCP
puts("reset\n");
#endif
s -> state = TS_CLOSED;
if( s -> dataHandler != 0 )
( s -> dataHandler )(( void * ) s, ( Byte * ) 0, -1);
22

#ifdef DEBUG_TCP
else puts( "got close" );
#endif
tcp_Unthread( s );
return;
}
switch( s -> state ) {

case TS_LISTEN:
/* Inicialno stanje of a Listen porta */
if( flags & TCPF_SYN ) {
/* Ocekujemo nazad SYN */
s -> acknum = ( tp -> seqnum ) + 1;
s -> hisport = ( tp -> srcPort );
s -> hisaddr = ( ip -> source );
s -> flags = TCPF_SYN | TCPF_ACK;
tcp_Send( s );
s -> state = TS_RSYN;
s -> unhappy = True;
s -> timeout = tcp_TIMEOUT;
#ifdef DEBUG_TCP
puts("Syn");
#endif
}
break;

case TS_SSYN:
/* Inicialno stanje Open porta */
if( flags & TCPF_SYN ) {
s -> acknum++;
s -> flags = TCPF_ACK;
s -> timeout = tcp_TIMEOUT;
if(( flags & TCPF_ACK )
&& ( tp -> acknum )
== ( s -> seqnum + 1 ) ) {
puts( "--- Open! ---\n" );
s -> state = TS_ESTAB;
s -> seqnum++;
s -> acknum = ( tp -> seqnum )+ 1;
s -> unhappy = False;
} else {
s -> state = TS_RSYN;
}
} else {
#ifdef DEBUG_TCP
puts( "Sent Syn, didn't get Syn back\n" );
#endif
/* Poluotvorena konekcija. */
s -> flags = TCPF_RST;
s -> seqnum = ( tp -> acknum );
tcp_Send( s );
#ifdef DEBUG_TCP
puts("Sent RST!\n" );
#endif
/* Ne ocekujemo odgovor. Spremi se da posaljes SYN kad se desi
timeout. */
s -> flags = TCPF_SYN;
s -> timeout = tcp_TIMEOUT;
s -> seqnum = 0; /* Pocni */
/* Ostani u SYNSENT cekaj timeout. */
}
break;

case TS_RSYN:
if( flags & TCPF_SYN ) {
s -> flags = TCPF_SYN | TCPF_ACK;
tcp_Send(s);
s -> timeout = tcp_TIMEOUT;
#ifdef DEBUG_TCP
puts(" retransmit of original syn\n");
#endif
}
if(( flags & TCPF_ACK )
&& ( tp -> acknum )
== ( s -> seqnum + 1L ) ) {
s -> flags = TCPF_ACK;
tcp_Send( s );
23

s -> seqnum++;
s -> unhappy = False;
s -> state = TS_ESTAB;
s -> timeout = tcp_TIMEOUT;
#ifdef DEBUG_TCP
puts( "Synack received - connection established\n" );
#endif

} else {
#ifdef DEBUG_TCP
puts( "Wrong syn. ");
#endif
}
break;

case TS_ESTAB:
if(( flags & TCPF_ACK ) == 0 ) return;
/* procesiraj ack vrijednost u paketu */
diff = ( int )( ( tp -> acknum )- s -> seqnum );
if( diff > 0 ) {
/* diff vrijednost je broj bajtova MOJIH podataka koje
udaljeni racunar potvrdjuje. */
if( diff > TCP_MAXDATA ) diff = TCP_MAXDATA;
else {
Move( &s -> data[ diff ],
&s -> data[ 0 ],
TCP_MAXDATA - diff );
}

s -> dataSize -= diff; /* preostalo bajtova za slanje */


s -> seqnum += diff; /* moj naredni sekvencni broj */
}
s -> flags = ( Word ) TCPF_ACK;
tcp_ProcessData( s, tp, len );
break;

case TS_SFIN:
if(( flags & TCPF_ACK ) == 0 ) return;
diff = ( int )( ( tp -> acknum )
- s -> seqnum - 1 );
s -> flags = ( Word )( TCPF_ACK | TCPF_FIN );
if( diff == 0 ) {
s -> state = TS_AFIN;
s -> flags = TCPF_ACK;
#ifdef DEBUG_TCP
puts("finack received.\n");
#endif
}
tcp_ProcessData(s, tp, len);
break;

case TS_AFIN:
s -> flags = TCPF_ACK;
tcp_ProcessData( s, tp, len );
break;

case TS_RFIN:
if( ( tp -> acknum ) == (s -> seqnum + 1) ) {
s -> state = TS_TIMEWT;
s -> timeout = tcp_TIMEOUT;
}
break;

case TS_LASTACK:
if( ( tp -> acknum ) == (s -> seqnum + 1) ) {
s -> state = TS_CLOSED;
s -> unhappy = False;
s -> dataSize = 0;
if( s -> dataHandler != 0 )
( s -> dataHandler )(( void * ) s,
( Byte * ) 0, 0 );
#ifdef DEBUG_TCP
else puts( "got close, no handler\n" );
#endif
tcp_Unthread( s );
#ifdef DEBUG_TCP
puts( "Closed. [626]\n" );
24

#endif
} else {
s -> flags = TCPF_ACK | TCPF_FIN;
tcp_Send( s );
s -> timeout = tcp_TIMEOUT;
#ifdef DEBUG_TCP
puts("retransmitting FIN\n" );
#endif
}
break;

case TS_TIMEWT:
s -> flags = TCPF_ACK;
tcp_Send(s);
}
}

U stanjima ESTAB, FIN-WAIT1, FIN-WAIT2 u paketima pored kontrolnih


bajtova dolaze i podaci. Obrada primljenih podataka se vrši funkcijom
tcp_ProcessData. Nakon računanja razlike između seqnum i acknum, koja se dobija u
varijabli diff, uz pomoć makroa TCP_DATAOFFSET izračunamo dužinu zaglavlja i
na bazi nje dobijemo mjesto u memoriji gdje se nalaze podaci i dodijeli se ta lokacija
pointeru dp. Podaci se zatim obrađuju handlerom ako on postoji. Po završetku obrade
primljenih podataka se provjeri da li je setovan fleg FIN, što znači da je riječ o
posljednjem paketu i da se traži prekid veze. Tada se iz stanja ESTAB prelazi u stanje
LASTACK, iz stanja FINWAIT1 u FINWAIT2, a iz stanja CLOSING u stanje
TIME_WAIT. Ovako promijenjen paket se šalje nazad pošiljaocu.
/* ----- Procesiraj podatke u primljenom paketu. -------------------- */

/* Pozvano iz svih stanja gdje mogu doci podaci: established,fin-wait-1, fin-wait-2 */

Void tcp_ProcessData( s, tp, len )


struct tcp_Socket *s;
struct tcp_Header *tp;
int len;
{
int diff, x;
Word flags;
Byte * dp;
flags = ( Word ) ( tp -> flags );
/* Razlika izmedju vrijednosti koju potvrdjujem i sekvencnog broja koju on
salje jeDuzina bajtova u baferu koju je on vec vidio. */
diff = ( int )( s -> acknum - ( tp -> seqnum ));
/* Iako nema podataka u SYN paketu, ovo je kompenzacija za opcije. */
if( flags & TCPF_SYN ) diff--;
x = TCP_DATAOFFSET( tp ) << 2; /* mnozenje s 4 */
dp = (( Byte * ) tp ) + x; /* pokazuje na podatke */
len -= x; /* oduzmi ofset */
if( diff >= 0 ) {
dp += diff; /* Ignorisi ono sto smo vec vidjeli */
len -= diff; /* Oduzmi duzinu podataka koje smo vec vidjeli */
s -> acknum += len; /* Dodaj duzinu na acknum da potvrdite podatke */
if( s -> dataHandler != 0 ) /* cast removed */
( s -> dataHandler )(( void * ) s, dp, len );
#ifdef DEBUG_TCP
else printf( "got data, %d bytes\n", len );
#endif
if( flags & TCPF_FIN ) {
s -> acknum++;
#ifdef DEBUG_TCP
puts( "consumed fin.\n" );
#endif
switch(s -> state) {
case TS_ESTAB:
/* CLOSEWT se preskace */
x = TS_LASTACK;
s -> flags |= TCPF_FIN;
s -> unhappy = True;
#ifdef DEBUG_TCP
puts( "sending fin.\n" );
25

#endif
break;
case TS_SFIN:
x = TS_RFIN;
break;
case TS_AFIN:
x = TS_TIMEWT;
break;
}
s -> state = x;
}
} else {
#ifdef DEBUG_TCP
printf( "diff was negative, %d\n", diff );
#endif
}
s -> timeout = tcp_TIMEOUT;
tcp_Send( s );
}

SESIJSKI SLOJ

Premda TCP/IP protokol nema specijalnih zaglavlja za sesijski sloj, njegove


funkcije su izdvojene u posebne podprograme. Otvaranje TCP konekcije se vrši
funkcijom TCP_OPEN. Soket prelazi u stanje SYN SENT i inicijaliziraju se portovi u
soketu i ostala polja soketa, soket se stavlja u spregnutu listu, a zatim se taj soket šalje
sa setovanim SYN bitom. Ova funkcija je predviđena za akcije gdje TI89 služi kao
klijent.

/* ----- tcp otvaranje --------------------------------------------------- */

/* Otvori TCP konekciju na odrediste. */

Void tcp_Open( s, lport, ina, port, datahandler )


struct tcp_Socket * s;
IP_Address ina;
Word lport, port;

Procref datahandler;
{

s -> state = TS_SSYN;


s -> timeout = tcp_LONGTIMEOUT;
if( lport == 0 ) lport = ( Word ) MsecClock();
s -> myport = lport;
s -> hisaddr = ina;
s -> hisport = port;
s -> seqnum = 0;
s -> dataSize = 0;
s -> flags = TCPF_SYN; /* Ocekujemo SYN */
s -> unhappy = True; /* Oznaci kao unhappy */
s -> dataHandler = datahandler;
/* soket dodajemo u listu */
s -> next = tcp_allsocs;
tcp_allsocs = s;
tcp_Send( s );
}

Tamo gdje TI89 radi kao server, predviđeno je otvaranje veze s tcp_Listen.
Soket se prebacuje u stanje LISTEN i inicijaliziraju njegova polja. Sada se soket ne
šalje, jer je ovo pasivna konekcija, očekuje se otvaranje s druge strane.

/* ----- tcp osluskivanje ------------------------------------------------- */


26

/* Pasivno otvaranje, osluskuje se odgovarajuci port */

Void tcp_Listen( s, port, datahandler, timeout )


struct tcp_Socket * s;
Word port;

Procref datahandler;
Longword timeout;
{
s -> state = TS_LISTEN;

if( timeout == 0L ) s -> timeout = 0x7FFFFFFL; /* zauvijek... */


else s -> timeout = timeout;

s -> myport = port;


s -> hisport = 0;
s -> seqnum = 0;
s -> dataSize = 0;
s -> flags = 0;
s -> unhappy = 0;
s -> dataHandler = datahandler;

/* Dodaj ga u listu */

s -> next = tcp_allsocs;


tcp_allsocs = s;
}

Procedura za zatvaranje konekcije tcp_Close iz stanja ESTAB ili SYN RCVD


prevodi u stanje FINWAIT-1, a setuju se flegovi ACK i FIN.
/* ----- tcp regularno zatvaranje--------------------------------------------- */

/* Salji FIN na port – radi samo ako je otvorena konekcija */

Void tcp_Close( s ) struct tcp_Socket *s; {


if( s -> state == TS_ESTAB || s -> state == TS_RSYN ) {
s -> flags = TCPF_ACK | TCPF_FIN;
s -> state = TS_SFIN;
s -> unhappy = True;
}
}
Procedura za prekid konekcije tcp_Abort ukoliko nije u stanjima LISTEN ili
CLOSED šalje paket s flegovima RST i ACK, prelazi u stanje CLOSED i izvršava
handler.
/* ----- Prekid TCP konekcije ------------------------------------- */

Void tcp_Abort( s ) struct tcp_Socket *s; {

if( s -> state != TS_LISTEN && s -> state != TS_CLOSED ) {


s -> flags = TCPF_RST | TCPF_ACK;
tcp_Send( s );
}

s -> unhappy = 0;
s -> dataSize = 0;
s -> state = TS_CLOSED;

if( s -> dataHandler != 0 )


( s -> dataHandler )(( void * ) s, ( Byte * ) 0, -1 );
#ifdef DEBUG_TCP
else puts( "got abort, no handler\n" );
#endif
tcp_Unthread( s );
}

PREZENTACIONI SLOJ
27

Kao i u slučaju sesijskog, i prezentacioni sloj u TCP/IP protokolu nema


specijalno, standardno zaglavlje. Ipak, uobičajeno je da se na ovom mjestu raspravlja
o načinu predstavljanja podataka.

Svi protokoli višeg nivoa koji će biti implementirani koriste ASCII kod. TI89
koristi modifikovanu verziju ASCII koda koja se uglavnom razlikuje po karakterima
sa kodom ispod 32. TI89 ima znatno više grafičkih znakova u odnosu na ASCII, jer su
mu potrebni za prikaz matematičkih operacija. Nažalost, u ROM-u nisu
implementirane standardne C funkcije putc, puts i printf, esencijalne za prikaz ASCII
podataka na ekranu, pa su napisane posebno. U tome pomaže činjenica da je sprintf
implementiran. Funkcija putc brine da se karakter prikaže na pravom mjestu iza
prethodnog, a u slučaju potrebe i skrolira ekran. Puts štampa string, a printf
formatizovano štampa više različitih argumenata.
static int __x = 0, __y = 0;

void putc (char c)


{
int h = 6 + 2 * FontGetSys ();
if (c == '\n' || __x + FontCharWidth (c) > ScrRect->xy.x1) __x = 0, __y += h;
if ( __y + h > ScrRect->xy.y1) ScrRectScroll (ScrRect, ScrRect, h, A_REVERSE), __y
-= h;
if (c==127) {
__x -= FontCharWidth (c);
if (__x<0) { __y -=h;
__x=ScrRect->xy.x1;
if (__y<0) __y=0;
}
} else
if (c != '\n') DrawChar (__x, __y, c, A_REPLACE), __x += FontCharWidth (c);
}

void puts (char *s)


{
int i;
for (i = 0; i < strlen(s); i++) putc (s[i]);
}
#define printf(format,args...) ({char buffer[200]; sprintf (buffer, format, ##args);
puts (buffer);})
#define clrscr() (__x = __y = 0, ClrScr ())

Pored ASCII podataka, FTP može da prenosi i binarne datoteke. TI89 nema
datoteke u klasičnom smislu te riječi, na spoljnjem, magnetnom mediju. Umjesto toga
postoje varijable koje se čuvaju u osnovnoj ili Flash memoriji. Njihov sadržaj se čuva
baterijama. Funkcijom CreateFile se alocira maksimalni dopušteni prostor u RAM-u
za varijablu. Na početku njenog sadržaja nalazi se dužina varijable. Sa Hlock
zabranjujemo premještanje te varijable iz memorije. Funkcijom AppendCharToFile se
karakter stavlja na kraj iskorištenog prostora te varijable i ažurira dužina.
AppendBlockToFile obavlja isto sa više karaktera. CloseFile na kraj iskorištenog
prostora stavlja signaturu koja znači da je varijabla string tipa , s HeapUnlock
dopustimo pomjeranje bloka i promijenimo njegovu dužinu da oslobodimo
neiskorišteni prostor.
28

HANDLE CreateFile(char *FileName) // Vraca handle, H_NULL u slucaju greske


{
HANDLE h;
SYM_ENTRY *sym_entry; /* O strukturi varijabli vidi TIGCC library */
char str[30],*sptr=str;
*sptr=0; while(*++sptr=*FileName++);
if(!(h=HeapAlloc(HeapMax()))) return H_NULL;
if(!(sym_entry=DerefSym(SymAdd(sptr)))) return H_NULL;
*(long*)HLock(sym_entry->handle=h)=0x00010000;
return h;
}

void AppendCharToFile(HANDLE h,unsigned char c)


{
char *base=HeapDeref(h);
unsigned len=*(unsigned*)base;
if(len>HeapSize(h)-10) return;
*(unsigned*)base=len+1;
base[len+2]=c;
}

void AppendBlockToFile(HANDLE h,void *addr,unsigned int len)


{
unsigned int i;
for(i=len;i;i--) AppendCharToFile(h,*((char*)addr)++);
}

void CloseFile(HANDLE h)
{
AppendCharToFile(h,0); AppendCharToFile(h,0x2D);
HeapUnlock(h);
HeapRealloc(h,*(unsigned*)HeapDeref(h)+3);
}

APLIKATIVNI SLOJ

Da bi program zadovoljio uslov dužine programa odabrani su protokoli


aplikativnog sloja koji imaju jednostavne komande sastavljene od ASCII znakova za
komunikaciju između korisnika i Internet providera. To znači da se neće razvijati
poseban korisnički interfejs za svaku od funkcija. Protokoli aplikativnog sloja koji su
mogući su FTP (portovi 20,21), HTTP (port 80), SMTP (port 23), POP3 (port 110) i
Telnet (port 21). Iako je riječ o degradiranim funkcijama ovih protokola u odnosu na
moderne aplikacije koje obavljaju ovu funkciju, osnovne mogućnosti su ipak
podržane. Tako, recimo, za prijem fajla FTP protokolom sa servera možemo otkucati:

USER anonymous
PASS [email protected]
LIST
RETR program.zip
QUIT

a ove komande će prijaviti korisnika anonymous sa lozinkom koja je njegova E-mail


adresa, prikazati spisak direktorija te pokupiti fajl program.zip u varijablu
RECEIVED. Izuzev slanja fajla, podržane su praktično sve funkcije FTP klijenata.
29

Pregled Web dokumenata je veoma rudimentalan. Stranicu možemo čitati


komandom

GET /index.html HTTP/1.0

koja prikazuje stranu sa svim HTML tagovima u izvornom obliku.

Čitanje pošte POP3 protokolom se vrši komandama

USER johnsmith
PASS idontknow
LIST
RETR 2
QUIT

Ove komande čitaju poruku korisnika johnsmith, s lozinkom idontknow, prikazavši


spisak svih poruka, a zatim poruku broj 2.

Slanje pošte SMTP protokolom se vrši sljedećim komandama:

MAIL FROM: <[email protected]>


RCPT TO: <[email protected]>
DATA
From: John Ew Smith
To: Ivan Kerenski
Subject: Visit
I plan to visit you in Vladivostok
after I finished my job in Tokio
.
QUIT

Ove komande navedu E-mail adrese pošiljaoca i primaoca, a zatim iza komande
DATA slijede redovi poruke, pri čemu i oni mogu imati specijalni sadržaj. Poruka se
završava redom koji sadrži tačku. Komanda QUIT šalje sve poruke i prekida vezu.

Telnet je pojednostavljen u tom smislu da se primljeni paketi prikazuju na


ekranu, a šalje svaki pritisnuti taster.

Završetak FTP konekcije zahtijeva zatvaranje dva TCP porta, onog za


komunikaciju s korisnikom i onog za podatke, funkcija ftp_Abort.
30

ftp_Abort()
{
tcp_Abort(ftp_ctl);
tcp_Abort(ftp_data);
}

Uprkos imenu, ftp_ctlHandler se koristi ne samo za FTP nego i HTTP, SMTP


te POP3. Poziva se za svaki primljeni paket, a parametri su joj soket, pointer na
podatke primljenog paketa i njegova duzina. Primljeni podaci se provjeravaju da li se
završavaju sa kodovima za novi red CR/LF. U slučaju FTP protokola (port 21) poziva
se funkcija ftp_commandLine za obradu, a ostali protokoli prikazuju poruku na
ekranu.
void ftp_ctlHandler(s, dp, len)
struct tcp_Socket *s;
Byte *dp;
int len;
{
Byte c, *bp, data[80];
int i;

if ( dp == 0 ) {
tcp_Abort(ftp_data);
return;
}

do {
i = len;
if ( i > sizeof data ) i = sizeof data;
Move(dp, data, i);
len -= i;
bp = data;
while ( i-- > 0 ) {
c = *bp++;
if ( c != '\r' ) {
if ( c == '\n' ) {
ftp_cmdbuf[ftp_cmdbufi] = 0;
if (remoteport==21)
ftp_commandLine();
else
printf("> %s\n", ftp_cmdbuf);
ftp_cmdbufi = 0;
} else if ( ftp_cmdbufi < (SIZECMDBUF)-1 ) {
ftp_cmdbuf[ftp_cmdbufi++] = c;
}
}
}
} while ( len > 0 );
}

Primljeni red FTP odgovora se obrađuje funckijom ftp_commandLine. Ako je


riječ o prvom redu u višelinijskom odgovoru to se indicira (po FTP protokolu)
minusom na četvrtom znaku, a ako je zadnji red, blankom na četvrtom znaku. Kada se
detektuje minus na navedenom mjestu, prelazi se u ftp_StateRCVRESP stanje, što
znači da se prikuplja primljeni tekst. Iz navedenog stanja se prelazi u ftp_StateIdle
kada je na navedenom mjestu blanko.
ftp_commandLine()
{
printf("> %s\n", ftp_cmdbuf);
switch(ftp_rcvState) {
case ftp_StateIDLE:
if ( ftp_cmdbuf[3] == '-' )
ftp_rcvState = ftp_StateRCVRESP;
break;

case ftp_StateINCOMMAND:
if ( ftp_cmdbuf[3] == '-' )
ftp_rcvState = ftp_StateRCVRESP;
case ftp_StateRCVRESP:
31

if ( ftp_cmdbuf[3] == ' ' )


ftp_rcvState = ftp_StateIDLE;
break;
}
}

Ova aplikacija nije multitasking, ali ipak korisnička akcija ne blokira prijem i
obradu paketa. Obrada svih protokola koji rade po principu naredba-Enter-odgovor
(dakle osim Telneta) se vrši u funkciji ftp_application. Komanda Osdequeue čita
pritisnuti taster bez usporavanja aplikacije. Ako je taj taster ESC (kod 264) veza se
zatvara i ako ima fajla u koji upisujemo zatvaramo i njega, te TCP/IP konekcije.
Indicira se varijabla EndProgram koja znači kraj programa.

U stanju ftp_StateGetCmd se očitavaju uneseni kodovi tastature. Kodovi kod


TI89 su malo različiti od ASCII, a neki za Internet bitni znakovi kao što su # i @ se ne
mogu lako dobiti na tastaturi, pa se ako korisnik unese na tastaturi prisutne znakove
ugaoni stepen i korijen (176 i 168), oni se konvertuju u potrebne. Obrada koda 257 je
obrada tastera backspace. Znakovi \r i \n predstavljaju reakciju na taster ENTER. Pri
pritisku na ovaj taster, prelazi se u obradu komande.

U stanju ftp_StateIdle prikaže se prompt “Command>” i pređe u stanje


ftp_StateGetCmd.

Unesena komanda se na labeli docmd terminira znakovima CR/LF, u slučaju


komande RETR kreira fajl, a u slučaju komande LIST hendlu fajla dodijeli null
vrijednost. Pređe se u stanje ftp_StateInCommand u kome se unesena komanda
pošalje u mrežu funkcijom tcp_Write.
void ftp_application(void)
{
char *s;
char *dp;
char recv_filename[8];
int i,j,putsx,putsy;
i = -1;
if ((kbrdcycle++ == 0 )||(ftp_rcvState==ftp_StateGETCMD))
if ( !OSdequeue(&i,queue) ) {
if ( i == 264 ) {
printf("Closing...\n");
if ( hreturnfile)
CloseFile(hreturnfile);

tcp_Close(ftp_ctl);
EndProgram=1;
}
}

switch (ftp_rcvState) {
case ftp_StateGETCMD:
getcmd:
if ( i != -1 ) {
ftp_outbuf[ftp_outbuflen] = 0;
if (i==176) i=64;
if (i==168) i=35;
switch (i) {
case 257:
if ( ftp_outbuflen > 0 ) {
ftp_outbuflen--;
putc(127);
putc(' ');

}
break;
case '\r':
32

case '\n':
puts("\n");
dp = &ftp_outbuf[ftp_outbuflen];
goto docmd;

default:
if ( i >= ' ' && ftp_outbuflen < OUTBUFSIZE ) {
ftp_outbuf[ftp_outbuflen++] = i;
putc(i);
}
}
}
break;
case ftp_StateIDLE:
ftp_rcvState = ftp_StateGETCMD;
ftp_outbuflen = 0;
puts("Command> ");
goto getcmd;
docmd:
*dp++ = '\r';
*dp++ = '\n';
if(( strncmp( ftp_outbuf, "RETR ", 5 ) == 0 )
|| ( strncmp( ftp_outbuf,"retr ", 5 ) == 0 )){
if (! hreturnfile)
hreturnfile=CreateFile("received");
}
if(( strncmp( ftp_outbuf, "LIST ", 5 ) == 0 )
|| ( strncmp( ftp_outbuf,"list ", 5 ) == 0 )){
hreturnfile=0;
}
ftp_outbuflen = (unsigned long)dp - (unsigned long)ftp_outbuf;
ftp_outbufix = 0;
ftp_rcvState = ftp_StateINCOMMAND;
/* nastavlja */
case ftp_StateINCOMMAND:
i = ftp_outbuflen - ftp_outbufix;
if ( i > 0 ) {
i = tcp_Write(ftp_ctl, &ftp_outbuf[ftp_outbufix], i);
ftp_outbufix += i;
tcp_Flush(ftp_ctl);
}
/* fall through */
case ftp_StateRCVRESP:
break;
}
}

FTP protokol ima i poseban port za podatke, 20. On je pasivno otvoren,


očekujući da FTP server počne sa njim da komunicira. Ukoliko je otvorena izlazna
datoteka (tj. TI89 globalna varijabla) upisuje se u nju, inače na ekran.
void ftp_dataHandler(s, dp, len)
struct tcp_Socket *s;
Byte *dp;
int len;
{
if (!(hreturnfile))
while (len>0) { /* Prikaz na ekranu primljenih podataka */
if((*dp<32) || (*dp>126))
printf (" %02x ",*dp);
else putc( *dp );
++dp;
--len;
}
else {
AppendBlockToFile(hreturnfile,dp,len); /* Upis na disk */
dp+=len;
len=0;
putc('#');
}
}

Handler za Telnet za sada prikazuje primljene podatke na ekranu.


void TelnetHandler(s, dp, len)
33

struct tcp_Socket *s;


Byte *dp;
int len;
{
Byte c, *bp, data[80];
int i;

do {
i = len;
if ( i > sizeof data ) i = sizeof data;
Move(dp, data, i);
len -= i;
bp = data;
while ( i-- > 0 ) {
c = *bp++;
if (c=='\n') printf("\n");
else if (c>=' ') putc(c);
}
} while ( len > 0 );

Telnetska aplikacija ASCII kod svakog pritisnutog tastera šalje u mrežu, a u


slučaju pritiska na ESC zatvara konekciju.
void telnet_application(void)
{ int i,n;
i = -1;
if (kbrdcycle++ == 0 )
if ( !OSdequeue(&i,queue) ) {
if ( i == 264 ) {
printf("Closing...\n");
tcp_Close(ftp_ctl);
EndProgram=1;
}
}
if ( i != -1 ) {
if (i==176) i=64;
if (i==168) i=35;
ftp_outbuf[1]=i;
ftp_outbuflen=1;
putc(i);
n = tcp_Write(ftp_ctl, &ftp_outbuf[1],1);
tcp_Flush(ftp_ctl);
}
}

KORISNIČKA APLIKACIJA

Preostalo je još inicijalizirati sve protokole i napisati korisnički interfejs za


omogućivanje izbora protokola.

Funkcija applayer zavisno od parametara dodjeljuje odgovarajuće handlere i


portove jednom od pet aplikativnih protokola.
applayer(host, dataHandler,protocol)
IP_Address host;
Procref dataHandler;
int protocol;
{
Word port;
char filecmd[80];

port = 0x4200;
/* postavi varijable stanja */
ftp_rcvState = ftp_StateRCVRESP;
ftp_cmdbufi = 0;
switch (protocol) {
case 1:
34

tcp_Listen(ftp_data, 20, dataHandler, 0); /* FTP */


remoteport=21;
break;
case 2: remoteport=25;break; /* SMTP */
case 3: remoteport=110;break; /* POP3 */
case 4: remoteport=80;break; /* HTTP */
case 5: remoteport=23; /* Telnet */
tcp_Open(ftp_ctl, port, host, remoteport, TelnetHandler);
break;
}
if (remoteport !=23)
tcp_Open(ftp_ctl, port, host, remoteport, ftp_ctlHandler);
if (remoteport !=23) tcp(ftp_application);
else tcp(telnet_application);
}

Funkcija parse_ip prevodi string koji sadrži IP adresu u tridesetdvobitni broj.


unsigned long parse_ip(char *s)
{
unsigned char c;
unsigned long b,r=0;
unsigned int i;
for(i=0;i<=3;i++)
{
c=*s;
if(!isdigit(c)) return 0;
b=0;
while(isdigit(c))
{
b=10*b+c-'0'; /* Znak u broj */
c=*++s;
}
if(b>255) return 0;
if(!((c=='.' && i<3)||(c==0 && i==3))) return 0; /* Pogresan format */
s++;
r=256*r+b; /* U b smo dobili bajt i dodajemo ga u rezultat */
}
return r;
}

Funkcija connect_dialog iscrta dijalog za izbor protokola, lokalne i udaljene IP


adrese te vlastite E-MAIL adrese (ovaj posljednji
parametar se ne koristi). Sistemska funkcija DialogNew
kreira dijalog, kome se elementi dodaju sistemskim
funkcijama DialogAddRequest, DialogAddPulldown i
slično. Funkcija DialogDo izvrši dijalog, tražeći unos. U
varijablama reqbuffer i combobuffer se nalazi naš izbor.
Na slici je dat izgled dijaloga, preuzet iz programa.

int connect_dialog(unsigned long *local,unsigned long *remote,


unsigned char *email,unsigned int *protocol)
{
HANDLE dhandle,chandle;
char reqbuffer[200];
int combobuffer[1];
int i,key;
static char *opc[5]={"FTP","SMTP","POP3","HTTP","TELNET"}; /* Opcije */
static char *edt[3]={"Local:","Remote:","My email:"}; /* Promptovi */
four_bytes(reqbuffer,(char*)local); /* IP adresa u string */
four_bytes(reqbuffer+70,(char*)remote);
strcpy(reqbuffer+140,email); /* E-mail adresa u memoriju */
combobuffer[0]=*protocol;
dhandle=DialogNew(150,70,NoCallBack); /* Kreiraj dijalog */
DialogAddTitle(dhandle,"CONNECT TO REMOTE HOST",BT_OK,BT_CANCEL); /*Naslov*/
for(i=0;i<3;i++) DialogAddRequest(dhandle,5,15+10*i,edt[i],70*i,60,15); /*3 polja*/
chandle=PopupNew(NULL,44);
for(i=0;i<5;i++) PopupAddText(chandle,-1,opc[i],0);
35

DialogAddPulldown(dhandle,5,45,"Protocol:",chandle,0); /* Dodaj combo s menijem*/


key=DialogDo(dhandle,CENTER,CENTER,reqbuffer,combobuffer); /* Izvrsi dijalog */
HeapFree(dhandle); HeapFree(chandle); /* Oslobodi memoriju */
if(key!=13) return 0;
*local=parse_ip(reqbuffer); /* Napuni rezultantne podatke */
*remote=parse_ip(reqbuffer+70);
if(*local==0 || *remote==0) return 0;
strcpy(email,reqbuffer+140);
*protocol=combobuffer[0];
return 1; /* Nije prekinuto ili s greskom */
}

void four_bytes(char *buf,unsigned char *n)


{
sprintf(buf,"%d.%d.%d.%d",peek(n),peek(n+1),peek(n+2),peek(n+3));
}

Neke varijable je potrebno inicijalizirati na početne vrijednosti. Pošto su sve


globalne varijable statičke, inicijaliziramo samo one varijable kod kojih je prethodno
stanje bitno za sljedeću konekciju.
/* ----- Inicializiraj tcp implementaciju -------------------------- */

Void tcp_Init( Void ) {


tcp_allsocs = ( struct tcp_Socket * ) 0;
tcp_id = 0;
p_begin_packet = NULL;
p_recv_next = NULL;

Glavni program označen funkcijom _main (ovo je malo odstupanje od


standarda C jezika, vjerovatno zbog nepostojanja standardnih datoteka), inicijalizira
dinamičke strukture i tajmer i zatim u glavnoj petlji poziva dijalog, te odlazi u
aplikativni sloj po izboru protokola.
_main(){
unsigned int protocol=3;
char *email="[email protected]";

LCD_BUFFER savescr;
LCD_save(savescr); /* Sacuvaj ekran */
EndProgram=0;
queue=kbd_queue();
clrscr();
OSLinkReset(); /* Pripremi Graphlink */
OSFreeTimer(6); /* Pripremi tajmer */
OSRegisterTimer(6,32000);
xmitbuffer=malloc(BUFFERSIZE); /* Alociraj memoriju */
recvbuffer=malloc(BUFFERSIZE);
ftp_ctl=malloc(sizeof(struct tcp_Socket));
ftp_data=malloc(sizeof(struct tcp_Socket));
ftp_cmdbuf=malloc(SIZECMDBUF);
ftp_outbuf=malloc(OUTBUFSIZE); /* Prozivaj dijalog dok se ne odabere ESC */
while (connect_dialog(&local_IP_address, &remote_IP_address,email,&protocol)==1)
{ EndProgram=0;
hreturnfile=0;
FontSetSys(F_4x6); /* Najmanji font */
tcp_Init(); /* Inicijaliziraj TCP */
applayer(remote_IP_address,ftp_dataHandler,protocol); /* Odredi protokol*/
}
free(xmitbuffer); /* Oslobodi memoriju */
free(recvbuffer);
free(ftp_ctl);
free(ftp_data);
free(ftp_cmdbuf);
free(ftp_outbuf);

LCD_restore(savescr); /* Obnovi ekran */


OSFreeTimer(6);
}
36

MOGUĆA PROŠIRENJA

Kada verzija 2.05 operativnog sistema ovog kalkulatora koja povećava limit
za mašinske/C programe na 24 K, potpuno zamijeni verziju 2.03 program će se moći
proširiti. Potrebno je
 Dodati UDP i DNS protokol i hosts tabele da se ne mora raditi isključivo sa IP
adresama
 Dodati miniterminal za uspostavljanje SLIP veze
 Modemski povezati TI89 za provajderom
 Poboljšati prikaz HTML
 U Telnetu emulirati neki terminal poput VT100
 Pisati Front end aplikacije za navedene portove
 Implementirati ICMP i PING kao server i kao klijent
 Realizovati FTP slanje

Pošto će se program prema GPL licenci isporučivati sa izvornim kodom, te


izmjene će možda raditi i korisnici.

LITERATURA

 Andrew S. Tannenbaum, Computer Networks Third Edition, Prientice Hall International, 1996
 Geoffrey Cooper, IMAGEN Corporation tinytcp.c - Tiny Implementation of the Transmission
Control Protocol, written March 28, 1986
 Željko Jurić, TIGCC Library, Sarajevo 2000
 Network Working Group J. RomkeyRequest for Comments: 1055 June l988 A NONSTANDARD
FOR TRANSMISSION OF IP DATAGRAMS OVER SERIAL LINES: SLIP
 Network Working Group T. SocolofskyRequest for Comments: 1180 C. Kale Spider Systems
Limited January 1991 A TCP/IP Tutorial
 Network Working Group T. Berners-LeeRequest for Comments: 1866 MIT/W3CCategory:
Standards Track D. Connolly November 1995 Hypertext Markup Language - 2.0
 Network Working Group J. MyersRequest for Comments: 1939 Carnegie MellonSTD: 53 M.
RoseObsoletes: 1725 Dover Beach Consulting, Inc.Category: Standards Track May 1996 Post
Office Protocol - Version 3
 Network Working Group T. Berners-LeeRequest for Comments: 1945 MIT/LCSCategory:
Informational R. Fielding UC Irvine H. Frystyk MIT/LCS May 1996 Hypertext Transfer Protocol
-- HTTP/1.0
 Network Working Group A. ContaRequest for Comments: 2463 LucentObsoletes: 1885 S.
DeeringCategory: Standards Track Cisco Systems December 1998 Internet Control Message
Protocol (ICMPv6) for the Internet Protocol Version 6 (IPv6) Specification
 Network Working Group A. GulbrandsenRequest for Comments: 2782 Troll
TechnologiesObsoletes: 2052 P. VixieCategory: Standards Track Internet Software Consortium L.
Esibov Microsoft Corp. February 2000 A DNS RR for specifying the location of services (DNS
SRV)
 RFC 768 J. Postel ISI 28 August 1980 User Datagram Protocol
 RFC: 793 TRANSMISSION CONTROL PROTOCOL DARPA INTERNET PROGRAM
PROTOCOL SPECIFICATION
 Jonathan B. Postel RFC 821 SIMPLE MAIL TRANSFER PROTOCOL
 Network Working Group J. PostelRequest for Comments: 854 J. Reynolds ISIObsoletes: NIC
18639 May 1983 TELNET PROTOCOL SPECIFICATION
 Network Working Group J. PostelRequest for Comments: 959 J. Reynolds ISIObsoletes RFC: 765
(IEN 149) October 1985 FILE TRANSFER PROTOCOL (FTP)

You might also like