profesor@wpi.WPI.EDU (Matthew E Cross) (11/12/90)
At one time, I had in my hands the 6510 assembly source code for the punter protocol. Does anyone know where I can obtain a copy of this? I believe it was on the mail archive server, something like 'acliu@skat.usc.edu' (is that still up?). If you have a copy of this, could you mail it to me? I would really appreciate it! I do have description of the protocol. Now that I know a decent amount of C/Unix programming, I'm trying to implement the protocol on UNIX. BUT, the protocol differs subtlely from the description I have. (or so it seems.) If I could find that source, I could see what the 64 end is doing, and maybe get the UNIX end to work... -- +-------------------------------------+--------------+------------------------+ | "The letter U has a lot of uses ... | Looking for | profesor@wpi.wpi.edu | | I like to play it like a guitar!" | suggestions +------------------------+ | -Sesame Street | for new gweepco programs... |
root@zswamp.fidonet.org (Geoffrey Welsh) (11/16/90)
Matthew E Cross (profesor@wpi.WPI.EDU ) wrote: >At one time, I had in my hands the 6510 assembly source code >for the >punter protocol. Does anyone know where I can obtain a copy >of this? At the risk of incurring much wrath, here it is: 10 rem *** new protocol *** 20 rem release 2: oct 27 1984 30 rem by steve punter 40 ifpeek(49152)<>169thenload"term.c1 v2",8,1 50 open5,2,0,chr$(8):get#5,a$:bs=255:open1,8,15:dimt$(3):sys49176 60 ty$="psp":t$(1)="Program":t$(2)="SEQ":t$(3)="WordPro":cs=1.02273e6 70 br$="300":gosub6000 80 print"Option: ("+br$+" Baud) Block Size ="bs 90 print" 1 - Terminal Mode" 100 print" 2 - Transmit a File" 110 print" 3 - Receive a File" 120 print" 4 - Change Block Size" 130 print" 5 - Toggle Baud Rate (300-1200)" 140 geta$:ifa$=""then140 150 ifa$="2"then1000 160 ifa$="3"then2000 170 ifa$="4"then3000 180 ifa$="1"thenprint"Terminal Mode:":goto4000 190 ifa$="5"then6700 200 goto140 1000 print"Program Name? ";:gosub5000:ifi$=""orfl=1then80 1010 print"File Type (P, S, or W)?"; 1020 geta$:ifa$=""then1020 1030 ifa$=" "then80 1040 ifa$="p"thensa=0:t=1:goto1080 1050 ifa$="s"thensa=2:t=2:goto1080 1060 ifa$="w"thensa=0:t=3:goto1080 1070 goto1020 1080 printt$(t):open2,8,sa,i$:input#1,e$,em$,t$,s$ 1090 ifval(e$)>0thenprinte$","em$","t$","s$:close2:goto1010 1120 sys49173:poke51227,t:sys49164:ifpeek(512)=1thenclose2:goto4000 1130 sys49173:poke51224,bs:sys49158:close2:goto4000 2000 print"Save As? ";:gosub5000:ifi$=""orfl=1then80 2010 sys49173:sys49161:ifpeek(512)=1then4000 2020 t$=","+mid$(ty$,peek(51227),1)+",w" 2025 print"File Type: "t$(peek(51227)) 2030 open2,8,2,"@0:"+i$+t$:forx=1to1300:nextx:print:sys61310 2040 sys49173:sys49155:close2:forx=1to1500:nextx:poke668,peek(667):goto4000 3000 print"Block Size? ";:gosub5000:ifi$=""then80 3010 bs=val(i$):ifbs<40thenbs=40 3020 ifbs>255thenbs=255 3030 goto80 4000 print 4010 sys49173:sys49167:goto80 5000 i$="":fl=0 5010 print" "; 5020 geta$:ifa$=""then5020 5030 ifa$=chr$(13)then5100 5040 ifa$=chr$(20)then5080 5045 ifa$="?"thenfl=1:goto5100 5050 iflen(a$)>20then5020 5060 if(asc(a$)and127)<32then5020 5070 printa$;:i$=i$+a$:goto5010 5080 iflen(i$)=0then5020 5090 print" ??";:i$=left$(i$,len(i$)-1):goto5010 5100 print" ":return 6000 s=val(br$):poke659,6:ifs=1200thenpoke659,8 6010 rc=cs/s:gosub6500:poke51968,lo:poke51969,hi 6020 ifs=1200thenrc=cs/s*.966:gosub6500 6030 poke665,lo:poke666,hi 6040 rc=cs/s/2-100:gosub6500:poke661,lo:poke662,hi:return 6500 hi=int(rc/256):lo=int(rc-hi*256):return 6700 ifbr$="300"thenbr$="1200":goto6720 6710 br$="300" 6720 gosub6000:goto80 1000 REM SAVE "@0:NEWPROT.SRC.1200",8 1010 : 1020 OPEN2,8,2,"@0:NEWPROT1200,P,W" 1030 : 1040 SYS700 1050 ; 1060 .OPT O2 1070 ; 1080 DIFFER = $0000 1090 STARTLOC = $C000 1100 C64 = 1 1110 PNTA = $62 1120 PNTB = $64 1130 STAT = $96 1140 DEFTO = $009A ;DEFAULT OUTPUT DEVICE (KERNAL) 1150 PTR1 = $009E ;TAPE PASS1 ERROR LOG (KERNAL) 1160 BUFPNTR = $A6 ;POINTER TO TAPE I/O BUFFER (KERNAL) [2] 1170 TAPE1 = $B2 ;POINTER TO START OF TAPE BUFFER (KERNAL) [2] 1180 ROBUF = $00F9 ;POINTER TO RS232 OUTPUT BUFFER (KERNAL) 1190 LASTCH = $0200 ;LAST USED CHARACTER 1200 RIDBE = $029B 1210 RIDBS = $029C 1220 RODBS = $029D ;START POSITION OF RS232 OUTPUT BUFFER (KERNAL) 1230 RODBE = $029E ;END POSITION OF RS232 OUTPUT BUFFER (KERNAL) 1240 RS232ENB = $02A1 ;RS232 ENABLE=128, DISABLE=255 1250 IBSOUT = $0326 ;CHROUT ROUTINE VECTOR (KERNAL) [2] 1260 CODEBUF = $C800-DIFFER ;BUFFER FOR INCOMING 3 CHR CODES 1270 BITPNT = $C803-DIFFER ;BIT POINTER FOR ALLOWABLE MATCHES 1280 BITCNT = $C804-DIFFER ;BIT COUNTER (0 TO 4) 1290 BITPAT = $C805-DIFFER ;BIT PATTERN FOR SEARCHES 1300 TIMER1 = $C806-DIFFER ;TIMER FOR NON-RECEIVED CHARACTERS (2) 1310 GBSAVE = $C808-DIFFER ;LOCATION TO SAVE GOOD BAD SIGNAL NEEDED 1320 BUFCOUNT = $C809-DIFFER ;NUMBER OF CHRS TO BUFFER INTO BLOCK 1330 DELAY = $C80B-DIFFER ;DELAY FOR WAIT PERIOD 1340 SKPDELAY = $C80C-DIFFER ;DELAY SKIP COUNTER 1350 ENDFLAG = $C80D-DIFFER ;FLAG TO INDICATE LAST BLOCK 1360 CHECK = $C80E-DIFFER ;SAVE PLACE FOR CHECKSUM (4) 1370 CHECK1 = $C812-DIFFER ;SECONDARY CHECKSUM HOLDING PLACE (4) 1380 BUFPNT = $C816-DIFFER ;POINTER TO CURRENT BUFFER 1390 RECSIZE = $C817-DIFFER ;SIZE OF RECEIVED BUFFER 1400 MAXSIZE = $C818-DIFFER ;MAXIMUM BLOCK SIZE 1410 BLOCKNUM = $C819-DIFFER ;BLOCK NUMBER (2) 1420 FILETYPE = $C81B-DIFFER ;FILE TYPE (FROM BASIC) 1430 STACK = $C81C-DIFFER ;STACK POINTER AT ENTRY 1440 DONTDASH = $C81D-DIFFER ;FLAG TO SUPPRESS DASHES AND COLONS 1450 SPECMODE = $C81E-DIFFER ;FLAG TO SEND SPECIAL START CODE 1460 BUFFER = $C900-DIFFER ;BUFFER FOR BLOCK 1470 ; 1480 ;BUFFER POSITIONS 1490 ; 1500 SIZEPOS = 4 1510 NUMPOS = 5 1520 DATAPOS = 7 1530 ; 1540 XMIT = $CB00 1550 OLDOUT = $CB02 1560 BASIC4 = $EF06 ;BASIC CALL FROM CHROUT 1570 BASIC3 = $EF3B ;BASIC CALL FROM CHROUT 1580 SETUP = $EF7E ;SET UP RS232 TO RECEIVE AGAIN 1590 ; 1600 ;KERNAL LOCATIONS 1610 ; 1620 BASIC1 = $F80D ;BASIC CALL FROM CHROUT 1630 BASIC2 = $F864 ;BASIC CALL FROM CHROUT 1640 READST = $FFB7 1650 CHKIN = $FFC6 ;OPEN CHANNEL FOR INPUT 1660 CHKOUT = $FFC9 ;OPEN CHANNEL FOR OUTPUT 1670 CLRCHN = $FFCC ;CLOSE INPUT AND OUTPUT CHANNELS 1680 CHRIN = $FFCF ;INPUT CHARACTER FROM CHANNEL 1690 CHROUT = $FFD2 ;OUTPUT CHARACTER TO CHANNEL 1700 GETIN = $FFE4 ;GET A CHARACTER FROM KEYBOARD QUEUE 1710 ZFFFE = $FFFE 1720 ; 1730 *=STARTLOC 1740 ; 1750 LDA #00 ;SYS 49152 1760 .BYT $2C 1770 LDA #03 ;SYS 49155 1780 .BYT $2C 1790 LDA #06 ;SYS 49158 1800 .BYT $2C 1810 LDA #09 ;SYS 49161 1820 .BYT $2C 1830 LDA #12 ;SYS 49164 1840 .BYT $2C 1850 LDA #15 ;SYS 49167 1860 NOP 1870 JMP OVER 1880 JMP RESET 1890 JMP INIT 1900 ; 1910 OVER STA PNTA 1920 TSX 1930 STX STACK 1940 LDA #<TABLE 1950 CLC 1960 ADC PNTA 1970 STA JMPPOINT+1 1980 LDA #>TABLE 1990 ADC #$00 2000 STA JMPPOINT+2 2010 JMPPOINT JMP TABLE 2020 ; 2030 TABLE JMP ACCEPT 2040 JMP RECEIVE 2050 JMP TRANSMIT 2060 JMP RECTYPE 2070 JMP TRANTYPE 2080 JMP TERMINAL 2090 JMP INIT 2100 ; 2110 CODES .ASC "GOO" 2120 .ASC "BAD" 2130 .ASC "ACK" 2140 .ASC "S/B" 2150 .ASC "SYN" 2160 ; 2170 ;ACCEPT CHARACTERS AND CHECK FOR CODES 2180 ; 2190 ACCEPT STA BITPAT ;SAVE REQUIRED BIT PATTERN 2200 LDA #$00 2210 STA CODEBUF 2220 STA CODEBUF+1 2230 STA CODEBUF+2 2240 CD1 LDA #$00 2250 STA TIMER1 ;CLEAR TIMER 2260 STA TIMER1+1 2270 CD2 JSR EXIT 2280 JSR GETNUM ;GET#5,A$ 2290 LDA STAT 2300 BNE CD3 ;IF NO CHR, DO TIMER CHECK 2310 LDA CODEBUF+1 2320 STA CODEBUF 2330 LDA CODEBUF+2 2340 STA CODEBUF+1 2350 LDA LASTCH 2360 STA CODEBUF+2 2370 LDA #$00 2380 STA BITCNT ;CLEAR BIT COUNTER 2390 LDA #$01 2400 STA BITPNT ;INITIALIZE BIT POINTER 2410 CD4 LDA BITPAT ;LOOK AT BIT PATTERN 2420 BIT BITPNT ;IS BIT SET 2430 BEQ CD5 ;NO, DON'T CHECK THIS CODE WORD 2440 LDY BITCNT 2450 LDX #$00 2460 CD6 LDA CODEBUF,X 2470 CMP CODES,Y 2480 BNE CD5 2490 INY 2500 INX 2510 CPX #$03 2520 BNE CD6 2530 JMP CD7 2540 ; 2550 CD5 ASL BITPNT ;SHIFT BIT POINTER 2560 LDA BITCNT 2570 CLC 2580 ADC #$03 2590 STA BITCNT 2600 CMP #15 2610 BNE CD4 2620 JMP CD1 2630 ; 2640 CD7 LDA #255 2650 STA TIMER1 2660 STA TIMER1+1 2670 JMP CD2 2680 ; 2690 CD3 INC TIMER1 2700 BNE CD9 2710 INC TIMER1+1 2720 CD9 LDA TIMER1+1 2730 ORA TIMER1 2740 BEQ CD8 2750 LDA TIMER1 2760 CMP #$07 2770 .IF C64:LDA TIMER1+1 2780 .IF C64:CMP #20 2790 BCC CD2 2800 LDA #$01 2810 STA STAT 2820 JMP DODELAY 2830 ; 2840 CD8 LDA #$00 2850 STA STAT 2860 RTS 2870 ; 2880 ; 2890 .IF C64:.GOTO 3210 2900 ; 2910 ;DO A GET# FOR PET 2920 ; 2930 GETNUM LDX #5 2940 JSR CHKIN 2950 JSR CHRIN 2960 STA LASTCH 2970 JSR CLRCHN 2980 RTS 2990 ; 3000 ;DO A GET# FOR PET TERMINAL MODE 3010 ; 3020 GETNUM1 LDA $E823 3030 BPL GT1 3040 LDX #5 3050 JSR CHKIN 3060 JSR CHRIN 3070 STA LASTCH 3080 JSR CLRCHN 3090 LDA $E822 3100 LDA #0 3110 STA STAT 3120 RTS 3130 ; 3140 GT1 LDA #0 3150 STA LASTCH 3160 LDA #2 3170 STA STAT 3180 RTS 3190 ; 3200 .GOTO 3540 3210 ; 3220 ;GET# FOR C64 3230 ; 3240 GETNUM1 NOP 3250 GETNUM TYA 3260 PHA 3270 LDA RIDBE 3280 CMP RIDBS 3290 BEQ GET1 3300 LDY RIDBS 3310 LDA ($F7),Y 3320 PHA 3330 INC RIDBS 3340 LDA #$00 3350 STA STAT 3360 PLA 3370 STA LASTCH 3380 PLA 3390 TAY 3400 JMP DORTS 3410 ; 3420 GET1 LDA #$02 3430 STA STAT 3440 LDA #$00 3450 STA LASTCH 3460 PLA 3470 TAY 3480 ; 3490 DORTS PHA 3500 LDA #$03 3510 STA $BA 3520 PLA 3530 RTS 3540 ; 3550 ;SEND A CODE 3560 ; 3570 SENDCODE LDX #$05 3580 JSR CHKOUT 3590 LDX #$00 3600 SN1 LDA CODES,Y 3610 JSR CHROUT 3620 INY 3630 INX 3640 CPX #$03 3650 BNE SN1 3660 JMP CLRCHN 3670 ; 3680 ;DO HANDSHAKING FOR RECEPTION END 3690 ; 3700 RECHAND STA GBSAVE ;SAVE GOOD OR BAD SIGNAL AS NEEDED 3710 LDA #$00 3720 STA DELAY ;NO DELAY 3730 RC1 LDA #$02 3740 STA PNTA 3750 LDY GBSAVE 3760 JSR SENDCODE ;SEND G/B SIGNAL 3770 RC9 LDA #%00100 ;ALLOW "ACK" SIGNALS 3780 JSR ACCEPT ;WAIT FOR CODE 3790 LDA STAT 3800 BEQ RC2 ;IF OK, SEND G/B SIGNAL AGAIN 3810 DEC PNTA 3820 BNE RC9 3830 JMP RC1 3840 ; 3850 RC2 LDY #$09 3860 JSR SENDCODE ;SEND "S/B" CODE 3870 LDA ENDFLAG 3880 BEQ RC5 3890 LDA GBSAVE 3900 BEQ RC6 3910 RC5 LDA BUFFER+SIZEPOS 3920 STA BUFCOUNT 3930 STA RECSIZE 3940 JSR RECMODEM ;WAIT FOR BLOCK 3950 LDA STAT 3960 CMP #%0001 ;CHECK FOR GOOD BLOCK 3970 BEQ RC4 3980 CMP #%0010 ;CHECK FOR BLANK INPUT 3990 BEQ RC2 4000 CMP #%0100 ;CHECK FOR LOSS OF SIGNAL 4010 BEQ RC4 4020 CMP #%1000 ;CHECK FOR "ACK" SIGNAL 4030 BEQ RC2 4040 RC4 RTS 4050 ; 4060 RC6 LDA #%10000 ;WAIT FOR "SYN" SIGNAL 4070 JSR ACCEPT 4080 LDA STAT 4090 BNE RC2 ;IF NOT, SEND "S/B" AGAIN 4100 LDA #10 4110 STA BUFCOUNT 4120 RC8 LDY #12 ;SEND "SYN" SIGNAL 4130 JSR SENDCODE 4140 LDA #%01000 ;WAIT FOR "S/B" SIGNAL 4150 JSR ACCEPT 4160 LDA STAT 4170 BEQ RC7 4180 DEC BUFCOUNT 4190 BNE RC8 4200 RC7 RTS 4210 ; 4220 ;DO HANDSHAKING FOR TRANSMISSION END 4230 ; 4240 TRANHAND LDA #$01 4250 STA DELAY ;USE DELAY 4260 TX2 LDA SPECMODE 4270 BEQ TX20 4280 LDY #$00 4290 JSR SENDCODE ;SEND A "GOO" SIGNAL 4300 TX20 LDA #%01011 ;ALLOW "GOO", "BAD", AND "S/B" 4310 JSR ACCEPT ;WAIT FOR CODES 4320 LDA STAT 4330 BNE TX2 ;IF NO SIGNAL, WAIT AGAIN 4340 LDA #$00 4350 STA SPECMODE 4360 LDA BITCNT 4370 CMP #$00 ;"GOOD" SIGNAL 4380 BNE TX10 ;NO, RESEND OLD BLOCK 4390 LDA ENDFLAG 4400 BNE TX4 4410 INC BLOCKNUM 4420 BNE TX7 4430 INC BLOCKNUM+1 4440 TX7 JSR THISBUF 4450 LDY #NUMPOS ;BLOCK NUMBER HIGH ORDER PART 4460 INY 4470 LDA (PNTB),Y 4480 CMP #255 4490 BNE TX3 4500 LDA #$01 4510 STA ENDFLAG 4520 LDA BUFPNT 4530 EOR #$01 4540 STA BUFPNT 4550 JSR THISBUF 4560 JSR DUMMYBL1 4570 JMP TX1 4580 ; 4590 TX3 JSR DUMMYBLK ;YES, GET NEW BLOCK 4600 TX1 LDA #"-" 4610 .BYT $2C 4620 TX10 LDA #":" 4630 JSR PRTDASH 4640 LDY #$06 4650 JSR SENDCODE ;SEND "ACK" CODE 4660 LDA #%01000 ;ALLOW ONLY "S/B" CODE 4670 JSR ACCEPT ;WAIT FOR CODE 4680 LDA STAT 4690 BNE TX1 4700 JSR THISBUF 4710 LDY #SIZEPOS ;BLOCK SIZE 4720 LDA (PNTB),Y 4730 STA BUFCOUNT 4740 JSR ALTBUF 4750 LDX #$05 4760 JSR CHKOUT 4770 LDY #$00 4780 TX6 LDA (PNTB),Y ;TRANSMIT ALTERNATE BUFFER 4790 JSR CHROUT 4800 INY 4810 CPY BUFCOUNT 4820 BNE TX6 4830 JSR CLRCHN 4840 LDA #$00 4850 RTS 4860 ; 4870 TX4 LDA #"*" 4880 JSR PRTDASH 4890 LDY #$06 4900 JSR SENDCODE ;SEND "ACK" SIGNAL 4910 LDA #%01000 4920 JSR ACCEPT ;WAIT FOR "S/B" SIGNAL 4930 LDA STAT 4940 BNE TX4 ;IF NOT, RESEND "ACK" SIGNAL 4950 LDA #10 4960 STA BUFCOUNT 4970 TX5 LDY #12 4980 JSR SENDCODE ;SEND "SYN" SIGNAL 4990 LDA #%10000 5000 JSR ACCEPT ;WAIT FOR "SYN" SIGNAL BACK 5010 LDA STAT 5020 BEQ TX8 5030 DEC BUFCOUNT 5040 BNE TX5 5050 TX8 LDA #$03 5060 STA BUFCOUNT 5070 TX9 LDY #$09 5080 JSR SENDCODE ;SEND "S/B" SIGNAL 5090 LDA #$00000 5100 JSR ACCEPT ;JUST WAIT 5110 DEC BUFCOUNT 5120 BNE TX9 5130 LDA #$01 5140 RTS 5150 ; 5160 ;RECEIVE A BLOCK FROM THE MODEM 5170 ; 5180 ; STAT RETURNS WITH: 5190 ; 5200 ; BIT 0 - BUFFERED ALL CHARACTERS SUCCESSFULLY 5210 ; BIT 1 - NO CHARACTERS RECEIVED AT ALL 5220 ; BIT 2 - INSUFFICIENT CHARACTERS RECEIVED 5230 ; BIT 3 - "ACK" SIGNAL RECEIVED 5240 ; 5250 RECMODEM LDY #$00 ;START INDEX 5260 RCM5 LDA #$00 ;CLEAR TIMERS 5270 STA TIMER1 5280 STA TIMER1+1 5290 RCM1 JSR EXIT 5300 JSR GETNUM ;GET A CHR FROM THE MODEM 5310 LDA STAT 5320 BNE RCM2 ;NO CHARACTER RECEIVED 5330 LDA LASTCH 5340 STA BUFFER,Y ;SAVE CHR IN BUFFER 5350 CPY #$03 ;CHR ONE OF THE FIRST 3 5360 BCS RCM3 ;NO, SKIP CODE CHECK 5370 STA CODEBUF,Y ;SAVE CHR IN CODE BUFFER 5380 CPY #$02 ;ON THE 3RD CHR 5390 BNE RCM3 ;NO, DON'T LOOK AT CHRS YET 5400 LDA CODEBUF ;CHECK FOR A "ACK" SIGNAL 5410 CMP #"A" 5420 BNE RCM3 5430 LDA CODEBUF+1 5440 CMP #"C" 5450 BNE RCM3 5460 LDA CODEBUF+2 5470 CMP #"K" 5480 BEQ RCM4 ;"ACK" FOUND 5490 RCM3 INY ;INC INDEX 5500 CPY BUFCOUNT ;BUFFERED ALL CHRS 5510 BNE RCM5 ;NO, BUFFER NEXT 5520 LDA #%0001 ;YES, RETURN BIT 0 SET 5530 STA STAT 5540 RTS 5550 ; 5560 RCM4 LDA #$FF ;"SYN" FOUND, SET TIMER TO -1 5570 STA TIMER1 5580 STA TIMER1+1 5590 JMP RCM1 ;SEE IF THERE IS ANOTHER CHR 5600 ; 5610 RCM2 INC TIMER1 ;INC TIMER 5620 BNE RCM6 5630 INC TIMER1+1 5640 RCM6 LDA TIMER1 5650 ORA TIMER1+1 ;TIMER NOW AT ZERO 5660 BEQ RCM7 ;"SYN" FOUND WITH NO FOLLOWING CHRS 5670 LDA TIMER1 5680 CMP #$06 5690 .IF C64:LDA TIMER1+1 5700 .IF C64:CMP #16 ;TIME OUT YET 5710 BNE RCM1 ;NO, GET NEXT CHR 5720 LDA #%0010 ;YES, SET BIT 1 5730 STA STAT 5740 CPY #$00 5750 BEQ RCM9 5760 LDA #%0100 ;BUT IF CHRS RECEIVED, SET BIT 2 5770 STA STAT 5780 RCM9 JMP DODELAY 5790 ; 5800 RCM7 LDA #%1000 ;"ACK" FOUND, SET BIT 2 5810 STA STAT 5820 RTS 5830 ; 5840 ;CREATE DUMMY BLOCK FOR TRANSMISSION 5850 ; 5860 DUMMYBLK LDA BUFPNT 5870 EOR #$01 5880 STA BUFPNT 5890 JSR THISBUF ;READ BLOCK INTO "THIS" BUFFER 5900 LDY #NUMPOS ;BLOCK NUMBER 5910 LDA BLOCKNUM 5920 CLC 5930 ADC #$01 5940 STA (PNTB),Y ;SET BLOCK NUMBER LOW PART 5950 INY 5960 LDA BLOCKNUM+1 5970 ADC #$00 5980 STA (PNTB),Y ;SET BLOCK NUMBER HIGH PART 5990 LDX #$02 6000 JSR CHKIN 6010 LDY #DATAPOS ;ACTUAL BLOCK 6020 DB1 JSR CHRIN 6030 STA (PNTB),Y 6040 INY 6050 JSR READST 6060 BNE DB4 6070 CPY MAXSIZE 6080 BNE DB1 6090 TYA 6100 PHA 6110 JMP DB5 6120 ; 6130 DB4 TYA 6140 PHA 6150 LDY #NUMPOS ;BLOCK NUMBER 6160 INY ;HIGH PART 6170 LDA #255 6180 STA (PNTB),Y 6190 JMP DB5 6200 ; 6210 DUMMYBL1 PHA ;SAVE SIZE OF JUST READ BLOCK 6220 DB5 JSR CLRCHN 6230 .IF C64:JSR RESET 6240 .IF C64:JSR DOD2 6250 .IF C64:JSR RESET 6260 LDY #SIZEPOS ;BLOCK SIZE 6270 LDA (PNTB),Y 6280 STA BUFCOUNT ;SET BUFCOUNT FOR CHECKSUM 6290 JSR ALTBUF 6300 PLA 6310 LDY #SIZEPOS ;BLOCK SIZE 6320 STA (PNTB),Y 6330 JSR CHECKSUM 6340 RTS 6350 ; 6360 ;SET POINTERS FOR CURRENT BUFFER 6370 ; 6380 THISBUF LDA #<BUFFER 6390 STA PNTB 6400 LDA BUFPNT 6410 CLC 6420 ADC #>BUFFER 6430 STA PNTB+1 6440 RTS 6450 ; 6460 ;SET POINTER B FOR ALTERNATE BUFFER 6470 ; 6480 ALTBUF LDA #<BUFFER 6490 STA PNTB 6500 LDA BUFPNT 6510 EOR #$01 6520 CLC 6530 ADC #>BUFFER 6540 STA PNTB+1 6550 RTS 6560 ; 6570 ;CALCULATE CHECKSUM 6580 ; 6590 CHECKSUM LDA #$00 6600 STA CHECK1 6610 STA CHECK1+1 6620 STA CHECK1+2 6630 STA CHECK1+3 6640 LDY #SIZEPOS 6650 CKS1 LDA CHECK1 6660 CLC 6670 ADC (PNTB),Y 6680 STA CHECK1 6690 BCC CKS2 6700 INC CHECK1+1 6710 CKS2 LDA CHECK1+2 6720 EOR (PNTB),Y 6730 STA CHECK1+2 6740 LDA CHECK1+3 6750 ROL A ;SET OR CLEAR CARRY FLAG 6760 ROL CHECK1+2 6770 ROL CHECK1+3 6780 INY 6790 CPY BUFCOUNT 6800 BNE CKS1 6810 LDY #$00 6820 LDA CHECK1 6830 STA (PNTB),Y 6840 INY 6850 LDA CHECK1+1 6860 STA (PNTB),Y 6870 INY 6880 LDA CHECK1+2 6890 STA (PNTB),Y 6900 INY 6910 LDA CHECK1+3 6920 STA (PNTB),Y 6930 RTS 6940 ; 6950 ;TRANSMIT A PROGRAM 6960 ; 6970 TRANSMIT LDA #$00 6980 STA ENDFLAG 6990 STA SKPDELAY 7000 STA DONTDASH 7010 LDA #$01 7020 STA BUFPNT 7030 LDA #$FF 7040 STA BLOCKNUM 7050 STA BLOCKNUM+1 7060 JSR ALTBUF 7070 LDY #SIZEPOS ;BLOCK SIZE 7080 LDA #DATAPOS 7090 STA (PNTB),Y 7100 JSR THISBUF 7110 LDY #NUMPOS ;BLOCK NUMBER 7120 LDA #$00 7130 STA (PNTB),Y 7140 INY 7150 STA (PNTB),Y 7160 TRM1 JSR TRANHAND 7170 BEQ TRM1 7180 REC3 LDA #$00 7190 STA LASTCH 7200 RTS 7210 ; 7220 ;RECEIVE A FILE 7230 ; 7240 RECEIVE LDA #$01 7250 STA BLOCKNUM 7260 LDA #$00 7270 STA BLOCKNUM+1 7280 STA ENDFLAG 7290 STA BUFPNT 7300 STA BUFFER+NUMPOS ;BLOCK NUMBER 7310 STA BUFFER+NUMPOS+1 7320 STA SKPDELAY 7330 LDA #DATAPOS 7340 STA BUFFER+SIZEPOS ;BLOCK SIZE 7350 LDA #$00 7360 REC1 JSR RECHAND 7370 LDA ENDFLAG 7380 BNE REC3 7390 JSR MATCH ;DO CHECKSUMS MATCH 7400 BNE REC2 ;NO 7410 JSR CLRCHN 7420 LDA BUFCOUNT 7430 CMP #DATAPOS 7440 BEQ REC7 7450 LDX #$02 7460 JSR CHKOUT 7470 LDY #DATAPOS 7480 REC6 LDA BUFFER,Y 7490 JSR CHROUT 7500 INY 7510 CPY BUFCOUNT 7520 BNE REC6 7530 JSR CLRCHN 7540 REC7 LDA BUFFER+NUMPOS+1 ;BLOCK NUMBER HIGH ORDER PART 7550 CMP #$FF 7560 BNE REC4 7570 LDA #$01 7580 STA ENDFLAG 7590 LDA #"*" 7600 .BYT $2C 7610 REC4 LDA #"-" 7620 JSR CHROUT 7630 .IF C64:JSR RESET 7640 LDA #$00 7650 JMP REC1 7660 ; 7670 REC2 JSR CLRCHN 7680 LDA #":" 7690 JSR CHROUT 7700 LDA RECSIZE 7710 STA BUFFER+SIZEPOS 7720 LDA #$03 7730 JMP REC1 7740 ; 7750 ;SEE IF CHECKSUMS MATCH 7760 ; 7770 MATCH LDA BUFFER 7780 STA CHECK 7790 LDA BUFFER+1 7800 STA CHECK+1 7810 LDA BUFFER+2 7820 STA CHECK+2 7830 LDA BUFFER+3 7840 STA CHECK+3 7850 JSR THISBUF 7860 LDA RECSIZE 7870 STA BUFCOUNT 7880 JSR CHECKSUM 7890 LDA BUFFER 7900 CMP CHECK 7910 BNE MTC1 7920 LDA BUFFER+1 7930 CMP CHECK+1 7940 BNE MTC1 7950 LDA BUFFER+2 7960 CMP CHECK+2 7970 BNE MTC1 7980 LDA BUFFER+3 7990 CMP CHECK+3 8000 BNE MTC1 8010 LDA #$00 8020 RTS 8030 ; 8040 MTC1 LDA #$01 8050 RTS 8060 ; 8070 ;RECEIVE FILE TYPE BLOCK 8080 ; 8090 RECTYPE LDA #$00 8100 STA BLOCKNUM 8110 STA BLOCKNUM+1 8120 STA ENDFLAG 8130 STA BUFPNT 8140 STA SKPDELAY 8150 LDA #DATAPOS 8160 CLC 8170 ADC #$01 8180 STA BUFFER+SIZEPOS 8190 LDA #$00 8200 RCT3 JSR RECHAND 8210 LDA ENDFLAG 8220 BNE RCT1 8230 JSR MATCH 8240 BNE RCT2 8250 LDA BUFFER+DATAPOS 8260 STA FILETYPE 8270 LDA #$01 8280 STA ENDFLAG 8290 LDA #$00 8300 JMP RCT3 8310 ; 8320 RCT2 LDA RECSIZE 8330 STA BUFFER+SIZEPOS 8340 LDA #$03 8350 JMP RCT3 8360 ; 8370 RCT1 LDA #$00 8380 STA LASTCH 8390 RTS 8400 ; 8410 ;TRANSMIT FILE TYPE 8420 ; 8430 TRANTYPE LDA #$00 8440 STA ENDFLAG 8450 STA SKPDELAY 8460 LDA #$01 8470 STA BUFPNT 8480 STA DONTDASH 8490 LDA #255 8500 STA BLOCKNUM 8510 STA BLOCKNUM+1 8520 JSR ALTBUF 8530 LDY #SIZEPOS ;BLOCK SIZE 8540 LDA #DATAPOS 8550 CLC 8560 ADC #$01 8570 STA (PNTB),Y 8580 JSR THISBUF 8590 LDY #NUMPOS ;BLOCK NUMBER 8600 LDA #255 8610 STA (PNTB),Y 8620 INY 8630 STA (PNTB),Y 8640 LDY #DATAPOS 8650 LDA FILETYPE 8660 STA (PNTB),Y 8670 LDA #$01 8680 STA SPECMODE 8690 TRF1 JSR TRANHAND 8700 BEQ TRF1 8710 LDA #$00 8720 STA LASTCH 8730 RTS 8740 ; 8750 ;DO DELAY FOR TIMING 8760 ; 8770 DODELAY INC SKPDELAY 8780 LDA SKPDELAY 8790 CMP #$03 8800 BCC DOD1 8810 LDA #$00 8820 STA SKPDELAY 8830 LDA DELAY 8840 BEQ DOD2 8850 BNE DOD3 8860 ; 8870 DOD1 LDA DELAY 8880 BEQ DOD3 8890 ; 8900 DOD2 LDX #$00 8910 LP1 LDY #$00 8920 LP2 INY 8930 BNE LP2 8940 INX 8950 CPX #120 8960 BNE LP1 8970 DOD3 RTS 8980 ; 8990 ;PRINT DASH, COLON, OR STAR 9000 ; 9010 PRTDASH PHA 9020 LDA BLOCKNUM 9030 ORA BLOCKNUM+1 9040 BEQ PRTD1 9050 LDA DONTDASH 9060 BNE PRTD1 9070 PLA 9080 JSR CHROUT 9090 PHA 9100 PRTD1 PLA 9110 RTS 9120 ; 9130 ;RESET RS232 PORT 9140 ; 9150 RESET JSR SETUP 9160 LDA RS232ENB 9170 CMP #$80 9180 BEQ RESET 9190 CMP #$92 9200 BEQ RESET 9210 RTS 9220 ; 9230 ;TERMINAL EMULATION ROUTINE 9240 ; 9250 TERMINAL JSR CURSOR 9260 TERM JSR GETNUM1 9270 LDA STAT 9280 BNE KEYBJ 9290 LDA LASTCH 9300 AND #$7F 9310 STA LASTCH 9320 CMP #$08 9330 BEQ OK1 9340 CMP #$0D 9350 BEQ OK1 9360 CMP #$20 9370 BPL OK1 9380 KEYBJ JMP KEYBOARD 9390 ; 9400 OK1 CMP #"A"+$20 9410 BCC OK2 9420 CMP #"Z"+$21 9430 BCS OK2 9440 SEC 9450 SBC #$20 9460 STA LASTCH 9470 JMP OK3 9480 ; 9490 OK2 CMP #$41 9500 BCC OK3 9510 CMP #"Z"+1 9520 BCS OK3 9530 CLC 9540 ADC #$80 9550 STA LASTCH 9560 ; 9570 OK3 CMP #$08 9580 BNE OK4 9590 LDA #$14 9600 STA LASTCH 9610 OK4 CMP #34 ;QUOTE 9620 BNE OK5 9630 JSR CHROUT 9640 LDA #20 9650 JSR CHROUT 9660 LDA #34 9670 OK5 LDA LASTCH 9680 CMP #$0D 9690 BNE OK6 9700 LDA #$20 9710 JSR CHROUT 9720 LDA #$0D 9730 OK6 JSR CHROUT 9740 JSR CURSOR 9750 ; 9760 KEYBOARD JSR GETIN 9770 BEQ TERM 9780 STA LASTCH 9790 CMP #$13 ;CLR/HOME KEY 9800 BEQ TERMOUT 9810 CMP #"A" 9820 BCC OK7 ;<"A" 9830 CMP #"Z"+1 9840 BCS OK7 ;>"Z" 9850 CLC 9860 ADC #$20 ;TO LOWERCASE ASCII 9870 STA LASTCH 9880 JMP OK8 9890 ; 9900 OK7 LDA LASTCH 9910 CMP #"A"+$80 9920 BCC OK8 ;<"A" 9930 CMP #"Z"+$81 9940 BCS OK8 ;>"Z" 9950 SEC 9960 SBC #$80 ;TO UPPERCASE ASCII 9970 STA LASTCH 9980 ; 9990 OK8 CMP #20 ;BACKSPACE 10000 BNE OK9 10010 LDA #$08 10020 STA LASTCH 10030 OK9 CMP #$83 ;SHIFT R/S 10040 BNE OKA 10050 LDA #$10 ;CTRL P 10060 STA LASTCH 10070 OKA LDX #$05 10080 JSR CHKOUT 10090 LDA LASTCH 10100 JSR CHROUT 10110 JSR CLRCHN 10120 JMP TERMINAL 10130 ; 10140 TERMOUT RTS ;WITH CLR/HOME 10150 ; 10160 CURSOR LDA #$12 10170 JSR CHROUT 10180 LDA #$20 10190 JSR CHROUT 10200 LDA #$9D 10210 JSR CHROUT 10220 LDA #$92 10230 JSR CHROUT 10240 ; 10250 ;CHECK FOR COMMODORE KEY 10260 ; 10270 EXIT LDA $028D ;IS COMMODORE 10280 CMP #$02 ;KEY DOWN 10290 BNE EXIT1 10300 EXIT2 PLA 10310 TSX 10320 CPX STACK 10330 BNE EXIT2 10340 EXIT1 LDA #$01 10350 STA LASTCH 10360 RTS 10370 ; 10380 ;MOVE CHROUT VECTOR IF NECESSARY 10390 ; 10400 INIT LDA IBSOUT ;BEEN MOVED YET 10410 CMP #<NEWOUT 10420 BNE INIT1 ;NO, CHANGE IT 10430 LDA IBSOUT+1 10440 CMP #>NEWOUT 10450 BEQ INIT2 ;YES, LEAVE IT 10460 INIT1 LDA IBSOUT ;STORE OLD CHROUT VECTOR 10470 STA OLDOUT 10480 LDA IBSOUT+1 10490 STA OLDOUT+1 10500 LDA #<NEWOUT ;SET NEW CHROUT VECTOR 10510 STA IBSOUT 10520 LDA #>NEWOUT 10530 STA IBSOUT+1 10540 INIT2 RTS 10550 ; 10560 ;NEW CHROUT ROUTINE TO CORRECT FOR 1200 BAUD SPEED PROBLEMS 10570 ; 10580 NEWOUT PHA ;DUPLICIATON OF ORIGINAL KERNAL ROUTINES 10590 LDA DEFTO ;TEST DFAULT OUTPUT DEVICE FOR 10600 CMP #$03 ;SCREEN, AND... 10610 BNE NEWOUT1 10620 PLA ;IF SO, GO BACK TO ORIGINAL ROM ROUTINES 10630 JMP (OLDOUT) 10640 ; 10650 NEWOUT1 BCC NEWOUT2 ;IF DEVICE NUMBER LESS THAN 3, 10660 PLA ;ALSO GO BACK TO ORIGINAL KERNAL ROUTINES 10670 JMP (OLDOUT) 10680 ; 10690 NEWOUT2 LSR A 10700 PLA 10710 STA PTR1 10720 TXA 10730 PHA 10740 TYA 10750 PHA 10760 BCC NEWOUT9 10770 JSR BASIC1 10780 BNE NEWOUT5 10790 JSR BASIC2 10800 BCS NEWOUT7 10810 LDA #$02 10820 LDY #$00 10830 STA (TAPE1),Y 10840 INY 10850 STY BUFPNTR 10860 NEWOUT5 LDA PTR1 10870 STA (TAPE1),Y 10880 NEWOUT6 CLC 10890 NEWOUT7 PLA 10900 TAY 10910 PLA 10920 TAX 10930 LDA PTR1 10940 BCC NEWOUT8 10950 LDA #$00 10960 NEWOUT8 RTS 10970 ; 10980 NEWOUT9 JSR NEWOUT10 10990 JMP NEWOUT6 11000 ; 11010 NEWOUT11 JSR NEWOUT12 11020 NEWOUT10 LDY RODBE 11030 INY 11040 CPY RODBS 11050 BEQ NEWOUT11 11060 STY RODBE 11070 DEY 11080 LDA PTR1 11090 STA (ROBUF),Y 11100 ; 11110 NEWOUT12 LDA RS232ENB 11120 LSR A 11130 BCS NEWOUT13 11140 LDA #$10 11150 STA $DD0E 11160 LDA XMIT 11170 STA $DD04 11180 LDA XMIT+1 11190 STA $DD05 11200 LDA #$81 11210 JSR BASIC3 11220 JSR BASIC4 11230 LDA #$11 11240 STA $DD0E 11250 NEWOUT13 RTS 11260 ; 11270 .END -- UUCP: watmath!xenitec!zswamp!root | 602-66 Mooregate Crescent Internet: root@zswamp.fidonet.org | Kitchener, Ontario FidoNet: SYSOP, 1:221/171 | N2M 5E6 CANADA Data: (519) 742-8939 | (519) 741-9553 MC Hammer, n. Device used to ensure firm seating of MicroChannel boards Try our new Bud 'C' compiler... it specializes in 'case' statements!
root@zswamp.fidonet.org (Geoffrey Welsh) (11/16/90)
Matthew E Cross (profesor@wpi.WPI.EDU ) wrote:
>I do have description of the protocol.
I'll include that, while I'm at it. Please read the next message for
comments about the errors in the description and for the batch hack.
The C1 Protocol
by Steve Punter
Inception
---------
During the summer of 1981, when I first got the idea of putting up a BBS, I
started work on a simple protocol for transfering programs to and from the
BBS. This protocol was similar in structure to XMODEM, and had about the same
reliability. Under good line conditions, it would give error free transfers
(this was to be expected). Under moderate noise conditions, the protocol would
hold up, and would still give error free transmissions. It was under poor line
conditions that it, and XMODEM, would fall apart.
In the summer of 1984, I started work on a very ambitious project; to produce
a protocol that was both fast, and extremely reliable, even under the worst of
line conditions. From this work came the "C1" protocol; not a simple
block/checksum affair, but a complete communication system for the computer.
Be warned, therefore, that understanding the ins and outs of "C1" will not be
easy, but with enough patience, there's no reason why even the least skilled
programmer cannot be comfortable with it.
Concepts
--------
The concept behind the "C1" protocol was simple; to allow two computers to
"talk" with one another (while transferring data) in such a way that nothing
short of a complete distortion of the transmission line could result in a
misunderstanding. If this concept could be realized, then files could be
transferred between computers without fear of line noise causing a breakdown
in the protocol, or that the received data would differ, in any way, from that
which was sent. Nothing is perfect though, and I don't, for a minute, claim
that "C1" is completely infallible, but I can say, with reasonable comfort,
that "C1" can deliver bad line accuracy not found in any other microcomputer
transfer protocols. For this accuracy though, there is a price to pay, and it
is complexity; the protocol is extremely difficult to duplicate without a
complete and utter understanding of the intricate workings of "C1". This
document will attempt to give you that required understanding.
A Simple Conversation
---------------------
In first deciding how the protocol would function, I thought of how two people
could carry on a conversation under high noise conditions, where
misunderstanding would be the norm. The senario I'm going to give differs from
the protocol in that the people talking have no way of verifying the accuracy
of what they believe they have heard. What it is meant to demonstrate is how
the the two computers "talk" with one another, and discuss the neccessary
repetition, or non-repetition, of each block of data (the cornerstone of a
checksum based transfer protocol).
Ken and John are attempting to assemble a machine in the middle of a very
noisy machine shop. Ken reads the instructions to John, who carries them out.
Even at close proximity, the two have difficulty hearing one another, so they
adopt of form banter which allows each instuction to be verified and
acknowledged. Here is how the conversation might go:
John: Put part "A" in hole "D".
Ken: Understood, putting part "A" in hole "D".
John: Acknowledged, let me know when you are ready for the next instruction.
Ken: Go ahead, what do I do next?
John: Put screw "E" through slot "T".
Ken: I didn't understand that, could you please repeat.
John: Oh, ok, tell me when you're ready for that instruction again.
Ken: Ready now.
The conversation continues on in this fashion, guaranteeing that both John and
Ken are fully aware of what the other is doing. In real life, people wouldn't
have the patience to keep up that sort of banter, but that's why they make
more mistakes than a computer. It is just this sort of "conversation" that the
two computers have between each other, only the language is different; the
instruction is replaced by the block of data, and all other statements by
special codes.
Communication Codes
-------------------
One of the areas where simple protocols fall apart is in the transmission of
"handshaking codes". It's called handshaking because is implies that the two
computers are having a dialogue, rather than a monologue. These other
protocols rely on single byte (8 bit) words for their communication codes, and
that could spell trouble, since the likelihood of any one 8 bit code being
transposed into another is greater than for multiple byte codes. For this
reason, "C1" uses 3 byte (24 bit) codes which are sufficiently different that
the likelihood of a transposition is extremely low. Not only that, but as you
will soon learn, the method of receiving 3 byte codes is designed such that if
there is sufficient line noise to make the neccessary transpositions, there
would most likely be extra characters sent; "C1" can avoid this situation.
Five distinct codes are used in the protocol; "GOO", "BAD", "ACK", "S/B", and
"SYN". Each has it's own meaning, just like any English word, and all are used
in a specific sequence such that synchronization difficulties would be
automatically identified and corrected.
Checksums
---------
When a block of data is sent, we must have a way of determining if it is
correctly received or not. This is accomplished by using what is known as a
checksum. Quite simply, a checksum is a number which is mathematically derived
from all the bytes within the block. The receiving computer recalculates the
sum and compares it with the sum it received along with the block.
Theoretically, any fault in the transmitted data will result in the two
checksums not matching; but that's theory. In reality, the accuracy of the
checksum is based on the type of mathematical operation used to calculate it,
and what kind of noise it encounters.
The simplest way to create a checksum is to add up all the ASCII values of the
bytes contained in the block. This is fine for many types of errors, but not
the type which inverts a particular bit. Should two identical inversions occur
on two opposite bits, the sum will remain the same. For example, take the
following two bytes:
11010011 = 211
Plus 01101101 = 109
-------- ---
320
Now assume that the forth bit from the right of both of these bytes becomes
inverted by line noise:
11011011 = 219
Plus 01100101 = 101
-------- ---
320
As you can see, the sum remains 320, even though line noise has made obvious
changes to the bytes. A better system is one called "Cyclic Redundancy", which
works on a somewhat different principle. The checksum is 16 bits long, and is
created in the following fashion; each byte from the block is Exclusive OR'ed
with the low order part of the checksum. The checksum is then ROTATED one bit
to the left, and the procedure repeated with the next byte. Even this highly
superior method can be tripped up, so I have combined BOTH an additive
checksum and Cyclic Redundancy checksum to create one very hard to beat 32 bit
"super" checksum.
Listening For Code Words
------------------------
Although 3 byte code words are more reliable than 1 byte code words, nothing
is perfect. It was once said that if you let an infinite number of monkeys
bash away at typewriters for an infinite amount of time, one of them would
eventually type "To be or not to be, that is the question". Although this
stretches statistical probability to it's limit, this kind of thing can easily
happen on a smaller scale; the letters "GOO" could quite conceivably be
produced by purely random line noise.
To try and eliminate ALL possible errors isn't feasible, but "C1" makes an
attempt at trying to eliminate as many as possible. One reasonably probable
fact is that any noise capable of randomly producing "GOO", would not stop
there; more likely, it would produce a string of characters, something like
"HGOOEK". Were we to allow the protocol to listen exclusively for three letter
combinations, it would most assuredly pick out the "GOO" in that string.
My specifications for "C1" call for a code recognition routine which will ONLY
make code word comparisons on the LAST 3 RECEIVED bytes. This is accomplished
in my coding by going back and testing for further characters after I have
identified a three byte code word. Should another byte be present, the
identified code word is thrown away, and the search will continue.
Statement and Listen Loops
--------------------------
One immediate drawback to the system described above is that a REAL code word,
masked within some random noise, would be rejected by the receiving computer.
This would also be true of a code word simply damaged by noise (like "GOE").
For a protocol to be impervious to this sort of corruption, it must be capable
of restating code words over and over until the receiving computer can
understand, yet it must also have a way of knowing whether the receiving
computer got the code word or not. This was a fact that eluded me when I wrote
the original protocol. When we talk to other people, the cornerstone of
understanding is recognition. If we ask "What do you think?", yet get no
reply, we ask again. Only when we receive a reply from the person to whom we
are talking do we continue on with our next statement. It would be pointless
wasting our breath on someone who isn't listening.
Within "C1", communication between computers is handled through a similar
system which I call the "Statement and Listen Loop". It's quite simple really;
when one computer has to "say" something to the other, it does so, then waits
for a predetermined time for a known response. Should it fail to receive a
response within that period of time, the code word is said again, and the
computer listens for the reply. This continues until the required response is
heard. The system is further enhanced by the fact that both computers are
ALWAYS engaged in a "Statement and Listen Loop".
Synchronization Lock
--------------------
That rather ominous sounding title is actually rather simple; it refers to a
condition whereby the "Statement and Listen Loops" of each computer become
locked together. This is analogous to two people speaking at the same time,
over and over, such that no effective communication takes place. In order to
guarantee that the two computers never get into this state, the wait times of
the loops are altered slightly.
Assume that the fixed wait loop time was 0.5 seconds; this is called a "Short"
wait. We also have a "Long" wait, which would be slightly longer, say 0.6
seconds (actually, the delay within a "Statement and Listen Loop" is not
particually critical, but should be somewhere in the neighbourhood of one half
second). Each time the computer goes through an SLL, a counter would determine
which type of wait to use; Long or Short. The sequence is broken into three;
the transmitting computer will use a Long-Long-Short, while the receiving
computer will use a Short-Short-Long.
Block Structure
---------------
Each block of data contains somewhat more than just a collection of characters
taken from disk, it also contains a "header". The header is 7 bytes long, and
contains the following information:
Byte 1: Low part of ADDITIVE checksum
Byte 2: High part of ADDITIVE checksum
Byte 3: Low part of CLC checksum
Byte 4: High part of CLC checksum
Byte 5: Size of NEXT block
Byte 6: Low part of Block Number
Byte 7: High part of Block Number
As you remember from the section on "checksums", there are two distinctly
different, 16 bit (2 byte) checksums. One is an additive checksum, composed of
the mathematical sum of the ASCII values of all the DATA bytes (and bytes 5
through 7 of the header). The other checksum is calculated using Cyclic (CLC)
Redundancy (on the same bytes). These 32 checksum bits are placed in the first
4 bytes of the header.
The 5th byte is the length of the NEXT block. This may seem odd to some, but
consider the difficulties in sending the size of the current block in that
self same block. You need to know the block size to calculate the checksum,
but you can't know for sure that the block size is correct unless you have
verified the checksum. We call this a Catch-22. By sending the size of any
given block in the PREVIOUS block, the size is known for a fact BEFORE the
checksum is calculated.
In the 6th and 7th byte are the block number. This was added quite early on in
the development of "C1" under the assumption that it would be necessary (as it
is in XMODEM). As it turned out, "C1" uses a method of handshaking which makes
this unnecessary. None the less, my specifications call for it's inclusion, as
certain uses of the block number could be made. Also, the high order part of
the block number (byte 7 of the header) is used to flag the last block.
Varying Block Size
------------------
The reason that block size was included in the header was originally to allow
the last block only to vary in size (one can never guarantee that the amount
of data to be sent will divide nicely into a preset block size). It quickly
dawned on me that "C1" was set up in such a way that ANY block size could be
used for ANY block in the transmission. Varying block size has it's
advantages; under reasonably clean line conditions, large blocks transmit the
most data with the least handshaking (which is mildly time consuming). Smaller
blocks are superior under bad noise conditions, since smaller blocks run a
higher chance of making it through the noise unscathed; and should it still
fail to make it, less time is required to repeat a smaller block.
My current implementation of "C1" allows the user to pick a fixed block size
between 40 and 255 bytes, but in other implementations, there is no reason why
block size couldn't be varied DURING transmission to adapt to CHANGING line
conditions.
One final thing concerning block structure is how would one presume to know
the size of the FIRST BLOCK if that is revealed only in the block that came
before it (quite a paradox). "C1" requires that the first block contain ONLY a
header, which would make that block 7 bytes long. This header would do little
more than supply the receiving computer with the size of first REAL block.
Accuracy of this first "dummy" block is guaranteed since it must still pass
the checksum tests. You must make the block number for this dummy block "0".
Communication Syntax
--------------------
Now that you understand block structure, handshaking methods, and code word
vocabulary, it comes time to find out how this all comes together. Most
procotols have very simple handshaking between blocks which is easy to trip
up, given sufficiently noisy conditions. Usually, the transmitting computer
sends the block, then waits for a response from the receiving computer; either
"good" or "bad". The transmitting computer then proceeds to send the next
block (if "good") or resend the last block (if "bad"). This system falls apart
the moment the transmitting computer receives a false indication of "good" or
"bad" and goes on to transmit the wrong block (and whether the receiving
computer likes it or not, it has to tackle with another block). Should things
get out of sync, and the transmitting computer sends the next block when it
should have sent the last one again, XMODEM attempts to make corrections by
use of the block number encoded within each block.
"C1" does nothing so crude; it's very communication syntax guarantees that
neither computer will get out of phase with the other. Whereas XMODEM uses a
single statement monologue between each block, "C1" uses a multiple part
dialogue. This makes "C1" about 3% slower than XMODEM, but this small
trade-off in speed for accuracy will be well worth it the first time you run
into trouble with XMODEM.
XMODEM commincations would look something like this:
Xmit: Transmits Block
Rec : "Good"
Xmit: Transmits Next Block
Rec : "Bad"
Xmit: Transmits Same Block Again
In "C1", the transmission would look something like this:
Xmit: Transmits Block
Rec : "Good"
Xmit: Good block acknowledged
Rec : Send next block for me
Xmit: Transmits Next Block
Rec : "Bad"
Xmit: Bad block acknowledged
Rec : Send that block again
Xmit: Transmits Same Block Again
In this type of transmission dialogue, neither computer can get out of sync,
since should it receive the opposite response than it expects, it goes back to
give the correct code word for the response it DID RECEIVE, thus regaining
proper synchronization. Couple this with the "Statement and Listen Loops", and
you can readily see than communication would be hard to break down.
Syntax Description
------------------
The following diagram should give you an understanding of the flow of
information between blocks:
For a Good Block:
Xmit: [Block] "ACK" [Next Block]
Rec : "GOO" "S/B"
For a Bad Block:
Xmit: [Block] "ACK" [Same Block]
Rec : "BAD" "S/B"
Actually, the two are identical; the only difference is the substitution of
either "GOO" or "BAD" as the response to the received block. Immediately after
receiving the block, the receiving computer recalculats the checksum to
determine validity of the data. In the meantime, the transmitting computer
starts to wait for a "GOO" or "BAD" signal. Since it can "say" nothing until
it receives one of these codes, it merely waits. That may sound suspiciously
like a good place to "hang up" the protocol, but the receiving end is
eventually going to finish receiving the block, either because it timed out
waiting, or it finished collecting the correct number of bytes from the
transmitting computer.
At that time, the receiving computer sends the appropriate code word ("GOO" or
"BAD") and begins to wait for an acknowledgement ("ACK"). If it doesn't
receive the "ACK" in about one half second, it sends the "GOO" or "BAD" code
word once again. Meanwhile, the transmitting computer has been patiently
awaiting the reception of the "GOO" or "BAD" code. Once it receives it, it
transmits an "ACK" and starts to wait for an "send block" signal ("S/B"). If
it doesn't get the "S/B" within about one half second, it sends "ACK" again.
Back at the receiving computer, which is waiting for this "ACK" signal, it
receives it and sends the "S/B" signal and begins to wait for the block.
Should it receive an "ACK" while waiting for the block, or receives nothing at
all for approximately .5 seconds, it assumes that the transmitting computer
hasn't heard the "S/B" and transmits it again. In the meantime, the
transmitting computer is waiting for the "S/B", and upon reception, starts
sending the block. The process has now started all over again.
A quick analysis of this system will reveal that it's damned near impossible
to get any type of noise which could possibly mimick the code sequences
required. Also, no noise could stop the eventual completion of the above
sequence, since each computer is aways "sending and waiting". If two people
keep repeating their sentences over and over, and continue to listen to the
other person, even a noisy room couldn't stop them from hearing one another
EVENTUALLY. Of course, some line noise is just so horrendous, that even this
method of communication could fail. Then again, this type of noise would make
it damned near impossible for the user to be online in the first place, so it
can be considered an unlikely event. But, should one of the computers go
offline for any reason, we wouldn't want the other computer to keep looping
and looping until it died of old age.
Although I haven't built in such protection into the terminal program I
distribute in the public domain, my BBS program does have abortion code.
Should the protocol on the BBS have to go through the "Statement and Listen
Loop" more than 24 times in row (which is hightly unlikely if the other
computer is still online), it will abort the transfer. Similar code could be
used in your implementation.
The End-Off Situation
---------------------
When the final block is transmitted, the high order part of the block number
should be made HEX "FF" (255 decimal). This will inform the receiving computer
that this is the last block of data, and to expect no more. The question now
arises; how can both computers be 100% sure that the other is fully aware of
the file completion? A fair question, but not one with a simple answer.
When the transmitting computer receives the "GOO" for the last block, it can
be fairly certain that the receiving computer has received the final block,
but it must inform the receiving computer that it knows this. It does so by
sending an "ACK", but cannot be sure the receiving computer has received the
"ACK" unless it gets the "S/B" signal back. Now, the transmitting computer
must acknowledge the reception of the "S/B", but under the normal
communications syntax, it would now have send a block. This is where the
"End-Off" syntax comes into play; after receiving the "S/B", the transmitting
computer sends back a "SYN" signal. In response to that receiving computer
sends it's own "S/B" signal, then waits for the final "S/B" from the
transmitting computer. Since it will not be responding to this code, it simply
goes into a wait cycle for approximately 5 seconds.
If it does get the "S/B" within that 5 seconds, it ends immediately, but
otherwise doesn't really care if it receives the code or not since at this
stage, there is a 100% assurance of both computers knowing things are Ok. The
transmitting computer need only send three copies of the "S/B" code at this
point, since, as stated above, there is full assurance that both computers are
finished. NOTE that the code words chosen for the End-Off situation are not
necessarily related to their appearant function.
Transfering File Type
---------------------
When transfering files from one computer to another it is often necessary to
also transfer the file type, but this must be known BEFORE the file is opened,
and, therefore, before the protocol begins. "C1" does not impose any strict
rules on what sort of information you transfer about the files, if any, but
when writing a terminal program to communicate with one of my bulletin boards,
the following should be done:
Using a full implementation of the "C1" procotol (first dummy block, data
block, and End-Off), transmit a single byte of data corresponding to the
following file types:
1 = Program File
2 = SEQ File
3 = WordPro File
Transmitting this single piece of data would require that TWO blocks be sent;
the initial dummy block to set up the size of the first data block (of which
there will be only one, size 8), and the data block itself, consisting of 7
header bytes and the single file type byte. For other applications, one could
conceivable transfer much more information, including file name, file type,
computer type, etc. It could even be possible to transfer multiple files,
specifying the number and name of each file in this first transmission.
Alternately, no one said you HAVE to use this first separate transmission; if
no information other the file needs to be transmitted, you just send the file
and nothing more.
--
UUCP: watmath!xenitec!zswamp!root | 602-66 Mooregate Crescent
Internet: root@zswamp.fidonet.org | Kitchener, Ontario
FidoNet: SYSOP, 1:221/171 | N2M 5E6 CANADA
Data: (519) 742-8939 | (519) 741-9553
MC Hammer, n. Device used to ensure firm seating of MicroChannel boards
Try our new Bud 'C' compiler... it specializes in 'case' statements!
root@zswamp.fidonet.org (Geoffrey Welsh) (11/16/90)
Matthew E Cross (profesor@wpi.WPI.EDU ) wrote:
>I do have description of the protocol.
... and, if you implement according to that standard, it won't work.
Notes on Implementing the C1 Protocol
by Geoffrey Welsh
with Matthew Desmond
November 13th, 1988
When Steve Punter realized that his original PET Transfer Protocol (also
known as "old Punter protocol") accepted too many compromises (its checksum
mode was unreliable; its 7-bit approach meant that it ran at half speed,
etc.), he decided to design a new protocol that circumvented the problems
of performing file transfers on a Commodore 64 and some of the problems he had
seen with his old protocol. At the time, many superior protocols existed that
could have been adapted to use on the C64, but Steve was not aware of them or
their strengths. He could only compare his new design to XMODEM.
Steve's document describing the C1 protocol does not accurately reflect
much of the real information needed to implement C1 and, in some cases, is
outright misleading. Hence this file to help you understand what's going on.
"CRC" Calculation
-----------------
Steve's "CRC" is calculated by exclusive-ORing each successive byte in the
block (with the exception of the bytes in the header where the checksums go,
for which one should substitute zero bytes) and then ROTATING the CRC left
through 16 bits. Note that the bit that "falls out" of bit 15 must be placed
back into bit 0 immediately.
Synchronization Lock
--------------------
Steve's "Long-Long-Short" and "Short-Short-Long" solution to
synchronization lock is downright silly. All will be well (and completely
compatible) if the delay in the statement-and-listen loop is always set to
long when transmitting and short when receiving.
Varying Block Size
------------------
I recommend that block size variation be implemented such that maximum
block size - 255 bytes total - is used on clean lines, but size is
automatically reduced on noisy lines. In no circumstance should the block size
exceed 255 bytes total (7 header and 248 data bytes); this is implied by the
one-byte next block size indicator. Although block size may fall as low as 8
bytes, it would be rediculous to spend time on-line getting only one data byte
accross per block; a more reasonable lower limit might be 40 (as suggested by
Steve), or 31 (start with block size = 255; shift right every time two
consecutive blocks are bad and shift left with 1 in the carry every time three
consecutive blocks are good).
Transfering File Type
---------------------
This protocol was developed for use with Commodore computers, whose DOS
treats executable, sequential, and random-access file types differently. As a
result, existing implementations were designed to transmit the file type
before the file itself. Non-Commodore machines may or may not preserve the
file type transmitted by the uploader to be sent out when sending the file to
a downloader using C1, but it is essential that SOME file type be identified
when sending ANY file via the C1 protocol. Any file other than Commodore BASIC
or 6502 executable binaries (with one exception: see note on "WordPro Files",
next paragraph) should be sent under the file type "Sequential", so the type
"Sequential" should be assumed if the exact type is not known. If a file is
received under the wrong file type, it is easy enough to change Commodore DOS'
file type flag. Nonetheless, it is obviously preferrable to preserve the file
type as uploaded.
By "Wordpro File", Steve means a file compatible with the text format he
used in his commercial package WordPro for Professional Software, Inc.; this
file type is seldom used and really means that the file is written as a
"Program File" under Commodore DOS even though it contains WordPro format
text. This file type may be translated to "Program".
Steve's paragraph that begins "Transmitting this single piece of data would
require that TWO blocks be sent; the initial dummy block to inform the
receiver that the first (and only) data block is of size 8, and the data block
itself, consisting of 7 header bytes and the file type byte." is blatantly
inaccurate. Existing C1 implementations send the file type in a single, 8-byte
(7 bytes header plus one data byte) block before the transfer of the file
itself. First you must transmit (or expect to receive) the 8-byte file-type
block and then proceed through the end-off sequence, then start the file
transfer with a 7-byte block zero.
No matter what, a close examination of Steve's implementation is absolutely
necessary to obtain the finer details of the protocol. An ASCII dump of his
Commodore BASIC and 6502 implementation is available on my BBS under the name
C1IMP.TXT.
Also, here is all that I could find about Alan Peters' "Multi-Punter" hack;
this is the third of three C1 batch standards, and not necessarily the most
common. Apologies for the all-caps, it's a side-effect of a quick PETSCII to
ASCII conversion.
FROM : ALAN PETERS
TO : DEREK JAGDEO
POSTED : 1251H ON 21-JAN-87 * TECH *
SUBJECT: MULTI=TRANSFER
CAT : GENERAL
YOU CAN GO AHEAD AND DO IT, BUT I'LL
GIVE YOU SOME SPECIFICS:
UPLOADING. FOR EACH TIME THROUGH A
MULTI-SEND, THE FIRST BLOCK MUST
CONTAIN 27 BYTES OF DATA, RATHER THEN
THE FILE TYPE. THIS IS STORED AS:
BYTES 0-15 : FILE NAME, PADDED WITH
SHIFTED SPACES TO LENGTH 16.
BYTE 16 :I COMMA (,)
BYTE 17 : FILE TYPE (LOWER CASE P,S,
OR U)
BYTES 18-20 : FILES LEFT TO SEND, IE
045 FOR 45 FILES LEFT.
BYTES 21-23 : BLOCKS LEFT, TOTAL OF
ALL FILES LEFT
BYTES 24-26 : CURRENT FILE SIZE.
YOU THEN MODIFY THE PUNTER
PROTOCOL FOR MULTI LIKE THIS:
OPEN CURRENT FILE FOR READ.
CALL READY RS232 REC'V ROUTINE.
CALL ROUTINE TO SWITCH PROTOCOL TO
MULTI
CALL TRANSMIT 1 ROUTINE
CALL ROUTINE TO SWITCH PROTOCOL
BACK TO NORMAL.
CHECK FOR ABORT ($200)
IF NOT, RESUME LIKE A NORMAL
SINGLE FILE TRANSFER : CALL RS232
REC'V ROUTINE AGAIN, THEN TRANSMIT 2.
CLOSE THE FILE, CLEAR THE BUFFER
POINTERS @ $29B/$29C, WAIT A LITTLE,
THEN DO THE NEXT FILE.
IF YOU ARE DONE ALL FILES, THEN
YOU MUST SEND ON END-OF-MULTI NULL
STRING, WHICH IS 27 "-" CHARACTERS.
PUT THIS IN THE 27 BYTE BUFFER, THEN
CALL THE ABOVE ROUTINES FOR NORMAL
FILE MULTI UPLOAD. DO NOT CALL
TRANSMIT 2. DON'T OPEN A FILE, SINCE
THERE IS NONE. THIS LAST PART IS
IMPORTANT, SO THAT THE RECEIVER WILL
KNOW WHEN TO STOP RECEIVING FILES.
THE CHANGES TO THE PUNTER PROTOCOL
ARE TRIVIAL. THE ROUTINE THAT CONVERTS
IT TO MULTI LOOKS LIKE THIS:
TOMULTI LDA #27
STA L1+1:STA L2+1
LDA #128:STA MMODE
BRN LDA HSHAKE:PHA
LDA HSHAKE+3:PHA
LDA HSHAKE+2:STA HSHAKE
LDA HSHAKE+5:STA HSHAKE+3
PLA:STA HSHAKE+5
PLA:STA HSHAKE+2\
RTS
FROMMULT LDA #1
STA L1+1:STA L2+1
LDA #0:STA MMODE
BEQ BRN
THE FIRST LABEL, L1 OCCURS IN THE
REC'V ROUTINE @ $C4CE. YOU WILL BE
CHANGING THE ADC #1 INSTRUCTION TO ADC
#27 TO MAKE THE FIRST BLOCK LENGTH 27
INSTEAD OF 1. IT MUST BE CHANGED BACK
TO 1 BEFORE YOU CALL THE TRANSMIT 2 OR
RECEIVE 2 ROUTINES.
THE MMODE VALUE IS TO ALLOW THE
BUFFERING ROUTINE IN THE PROTOCOL TO
MOVE THE 27 BYTES FROM THE PUNTER
BUFFERS TO THE 27 BYTE BUFFER. INSERT
THE FOLLOWING IN THE CODE JUST AFTER
THE FILE TYPE IS STORED IN RECEIVE1:
BIT MMODE:BPL RESUME
LDY #0
J1 LDA PUNTERB+7,Y
STA BUFFER,Y
INY:CPY #27:BNE J1
RESUME.....
YOU MUST CHANGE THE TRANSMIT
ROUTINE AT $C517 AS WELL. THE ADC #1
WILL CHANGE TO ADC #27, THEN BACK
AFTER THE FIRST BLOCK.
IN THIS TRANSMIT 1 ROUTINE,
LOCATIONS $64/65 INDIRECTLY ADDRESS
THE PUNTER XMIT BUFFER. SO, THE SMALL
MULTI INSERTION CODE LOOKS LIKE:
BIT MMODE:BPL RESUME
LDY #7
J1 LDA BUFFER-7,Y
STA ($64),Y
INY:CPY #34:BNE J1
RESUME ...
THIS GOES JUST AFTER THE CODE WHERE
THE FILE TYPE IS PUT INTO THE BUFFER.
THE HANDSHAKE REVERSAL DOES THIS:
GOO TO OOG AND BAD TO DAB. I FOUND
THAT IT IS NOT NECESSARY TO CHANGE THE
OTHER 3 SIGNALS.
FOR THE RECEIVER, HE MUST DO THE
SAME AS WITH TRANSMIT, BUT USE THE
BUFFER DATA TO WRITE THE FILE TO DISK.
HOPE THIS HELPS.
ALAN PETERS * TECH *
--
UUCP: watmath!xenitec!zswamp!root | 602-66 Mooregate Crescent
Internet: root@zswamp.fidonet.org | Kitchener, Ontario
FidoNet: SYSOP, 1:221/171 | N2M 5E6 CANADA
Data: (519) 742-8939 | (519) 741-9553
MC Hammer, n. Device used to ensure firm seating of MicroChannel boards
Try our new Bud 'C' compiler... it specializes in 'case' statements!
root@zswamp.fidonet.org (Geoffrey Welsh) (11/16/90)
Matthew E Cross (profesor@wpi.WPI.EDU ) wrote: >Now that I know a decent amount of C/Unix programming, I'm >trying to implement the protocol on UNIX. Here is the 'C' source, written for Steve Punter's MS-DOS BBS software by Ben Pedersen: Docs for the 'C' source code the the C1 protocol By Steve Punter The following source code for my "C1" protocol was written by BEN PEDERSEN, and donated to the public domain. Specifically, it is written to compile under MicroSoft's "Quick-C", and be called from a higher level language ("Quick- BASIC"). However, it should compile with most PC compilers, and you don't HAVE to call it from a higher language. The two important routines are "C1Rec" and "C1Send". Both these routines are passed THREE parameters: File Name POINTER File Type Flag Base Location of Screen The file name pointer is merely a pointer to a NULL TERMINATED string which contains the file name to be written to, or read from, the disk. The File Type Flag is an INTEGER value of 0 or 1, which determines whether the routines go through the initial transfer of a file type byte. This is NECESSARY when uploading or downloading from a Punter type BBS, but is not required by the protocol per se. The Base Location of Screen is a LONG value telling the protocol at what SEGMENT the screen is located (usually $B000 for monochrome, or $B800 for CGA). As the transfer proceeds, the number of successful bytes, and number of bad blocks, are put on the top line of your screen. This is to accommodate the STATUS LINE of the BBS, but will most likely not suit your applications. It should be changed appropriately. RS-232 is handled via three routines: GETSER(), PUTSER(), and CHECK(). All of the above are EXTERNAL routines which sould be supplied at compile time. GETSER() should return an INT value of the byte received from the RS-232 input port, or input buffer. PUTSER() should accept an INT value to put out on the RS-232 port. CHECK() should return an INT value of how many characters are waiting in the RS-232 input buffer. extern void setser(unsigned,unsigned,unsigned); extern int getser(); extern int check(); #include <dos.h> #include <stdio.h> #include <fcntl.h> #include <time.h> #include <stdlib.h> #include <io.h> #include <bios.h> static unsigned buffer; static unsigned ds; static unsigned tab2; static int tfh; static int badblocks; static long xfertotal; static int nocount; static long scnloc; unsigned _dos_findfirst(char *,unsigned,struct find_t *); unsigned _dos_findnext(struct find_t *); void dosc(int *,int *); typedef struct C1STUFF { int fp; /* pointer to open file for send & receive */ char *BuffPtr; /* pointer to data buffer */ char BuffOne[256]; /* data buffer no. one */ char BuffTwo[256]; /* data buffer no. two */ char TempBuff[500]; /* general purpose buffer */ char *ReplyStr[5]; /* pointers to reply code strings */ char *CharPtr; /* general purpose char pointer */ int BlkSize; /* size of block being sent or received */ int NextBsize; /* size of next block to be sent or received */ int FullBsize; /* selected size, 40-255 bytes, of a full block */ int BlkNum; /* block number count */ int FileFlag; /* file type being sent or received */ int ReplyOut; /* array index flags currently-transmitted reply code */ int ReplyWant; /* array index flags currently-wanted reply code */ int ErrCodes; /* count of erroneous reply codes received */ int ErrBlocks; /* count of erroneous blocks received */ int Chksum; /* additive checksum for current block */ int CLC; /* cyclic checksum for current block */ int EndFlag; /* local last block flag */ int EndOff; /* signal end of transfer */ int EndXfer; /* signal to terminate transfer */ int TransFlag; /* flag text file for ASCII-PETSCII-ASCII translate */ int Wait; /* index to Wait Period array for reply codes */ long Period[3]; /* array of reply code Wait Period values */ }; struct C1STUFF C1Stuff, *StuffPtr; /* C1 variables structure & pointer */ void prtstat (gd,bb) long gd; int bb; { char far *screen; char buf[20]; int i; int z; FP_SEG(screen)=scnloc; FP_OFF(screen)=160; sprintf(buf,"%lu",gd); for (i=0,z=94;*(buf+i)!=0;i++,z=z+2) *(screen+z)=*(buf+i); sprintf(buf,"%u",bb); for (i=0,z=120;*(buf+i)!=0;i++,z=z+2) *(screen+z)=*(buf+i); } /*********************************** * c1.c * * * * C language implementation * * of Steve Punter's * * C1 file transfer protocol * * written for MS-DOS 2.xx * * * * Ben Pedersen Jun 88 * ************************************ /* constants for external assembler functions */ #define END 0x4f /* exit program */ #define PGUP 0x49 /* upload a file */ #define PGDN 0x51 /* download a file */ #define F1 0x3b /* DOS shell */ #define BELL 0x07 /* tweak speaker */ /* response code string array index */ #define CODE_ACK 0x00 #define CODE_GOO 0x01 #define CODE_BAD 0x02 #define CODE_SYN 0x03 #define CODE_SBK 0x04 /* used to clear a reply code flag */ #define NONE 0x05 /* short and long periods to wait for a response */ #define SHORT 0x400L #define LONG 0x460L #define ERROR 0xffff /* flag error condition on function return */ #define NOERR 0x00 /* flag no error on function return */ #define MAXCODERR 0x18 /* maximum number of reply code errors */ #define MAXBLKERR 0x05 /* maximum number of data block errors */ #define EOT_FLAG 0xff /* end of transfer flag in BLKNUM_H */ #define HEADER 0x07 /* no. of bytes in block header */ #define CHKSUM_L 0x00 /* byte no. of additive checksum low byte */ #define CHKSUM_H 0x01 /* byte no. of additive checksum high byte */ #define CLC_L 0x02 /* byte no. of cyclic checksum low byte */ #define CLC_H 0x03 /* byte no. of cyclic checksum high byte */ #define NEXT_BSIZE 0x04 /* byte no. of next blocksize byte */ #define BLKNUM_L 0x05 /* byte no. of block number low byte */ #define BLKNUM_H 0x06 /* byte no. of block number high byte */ #define FILE_TYPE 0x07 /* byte no. of file type byte */ #define FALSE 0 /* false response */ #define TRUE 1 /* true response */ /***************************************************************************/ /* C1 protocol functions for receiving data */ int HandleRec(); /* handle reply codes for receiving data blocks */ int BlockRec(); /* get a block of data from serial buffer */ int ChksmRec(); /* performs checksum tests on received block */ int EndReceive(); /* handle reply codes for ending data receive */ /* C1 protocol functions for sending data */ int FtypeSend(int); /* handle sending file type block */ int DataSend(); /* controls block sending sequence */ int DataBuild(); /* builds both the next and the current block */ void ChksmSend(); /* generates checksums for current block */ int EndSend(); /* handle reply codes for data send */ int SLBlocks(); /* statement & listen loop for block send */ /* C1 protocol functions common to sending and receiving data */ int SLCodes(); /* statement & listen loop for reply codes */ int GetBytes(); /* loop for getting reply chars */ int CodeOut(); /* send out a reply code */ /************************************************************************** * * functions to receive blocks * **************************************************************************/ int fortran C1Rec(pathname,fflg,loc) char *pathname; int fflg; long loc; { int i; int filetype; scnloc=loc; C1Stuff.ReplyStr[0] = "ACK"; /* signal a good or bad block */ C1Stuff.ReplyStr[1] = "GOO"; /* signal a good block */ C1Stuff.ReplyStr[2] = "BAD"; /* signal a bad block */ C1Stuff.ReplyStr[3] = "SYN"; /* signal end of transfer sequence */ C1Stuff.ReplyStr[4] = "S/B"; /* request block or signal transfer done */ /* sequence of reply code wait periods */ C1Stuff.Period[0] = SHORT; C1Stuff.Period[1] = SHORT; C1Stuff.Period[2] = LONG; StuffPtr = &C1Stuff; /* set pointer to struct of C1 variables */ C1Stuff.BuffPtr = C1Stuff.BuffOne; /* set pointer to a data buffer */ C1Stuff.ErrCodes = 0; /* count of reply code errors */ C1Stuff.ErrBlocks = 0; /* count of data block errors */ C1Stuff.Wait = 0; /* index variable of reply code wait periods */ C1Stuff.TransFlag = FALSE; /* PETSCII-ASCII translation flag */ xfertotal = 0; badblocks = 0; _dos_creat(pathname,_A_NORMAL,&tfh); C1Stuff.FileFlag=1; if(fflg != 0) { nocount = 1; C1Stuff.BlkSize = HEADER+1; /* size of file type block */ if(HandleRec() == ERROR) /* receive file type block */ { _dos_close(tfh); return(1); } StuffPtr->CharPtr = StuffPtr->BuffPtr+7; filetype = *StuffPtr->CharPtr & 255; if(EndReceive() == ERROR) /* end off file type receive */ { _dos_close(tfh); return(2); } } nocount = 0; C1Stuff.BlkSize = HEADER; /* size of dummy data block */ if(HandleRec() == ERROR) /* receive data blocks */ { _dos_close(tfh); return(1); } if(EndReceive() == ERROR) /* end off data receive */ { _dos_close(tfh); return(3); } _dos_close(tfh); if (fflg==0) return(0); else return (-filetype); } int HandleRec() /* handle reply codes for receiving data blocks */ { int flag; int bw; StuffPtr->BlkNum = 0; /* local block number count */ StuffPtr->ReplyOut = CODE_GOO; /* reply code to send to sender */ StuffPtr->ReplyWant = CODE_ACK; /* reply code wanted from sender */ StuffPtr->EndOff = 0; StuffPtr->EndFlag = 0; StuffPtr->EndXfer = 0; if(SLCodes() == ERROR) return(ERROR); /* send & get reply */ do { do { StuffPtr->ReplyOut = CODE_SBK; /* code to request a block */ CodeOut(); /* request a block */ } while ((flag = BlockRec()) == -2); if(flag == NOERR) /* receive a block */ { if (StuffPtr->BlkNum > 0) { if (nocount==0) xfertotal += ((StuffPtr->BlkSize & 0xff) - HEADER); prtstat(xfertotal,badblocks); } ++StuffPtr->BlkNum; /* increment local block count */ StuffPtr->ErrBlocks = NOERR; /* zero block error count */ StuffPtr->ReplyOut = CODE_GOO; /* reply code signals good block */ if(StuffPtr->BlkNum > 1) /* block number > 1 written to disk */ { _dos_write(tfh,StuffPtr->BuffPtr+HEADER, (StuffPtr->BlkSize & 0xff)-HEADER,&bw); } /* last block received? - goto end off */ if((*(StuffPtr->BuffPtr + BLKNUM_H) & 0xff) == EOT_FLAG) return(NOERR); /* get size of the next block from fifth byte of received block */ StuffPtr->BlkSize = (unsigned)*(StuffPtr->BuffPtr+NEXT_BSIZE); } if(flag == ERROR) { /* transmitter still sending ACK? */ if(GetBytes() != ERROR && strncmp(StuffPtr->CharPtr-3, StuffPtr->ReplyStr[CODE_ACK], 3) == NULL) continue; else /* otherwise reply BAD */ { badblocks++; prtstat(xfertotal,badblocks); StuffPtr->ReplyOut = CODE_BAD; /* signal a bad block received */ ++StuffPtr->ErrBlocks; /* increment local block error count */ } } if(SLCodes() == ERROR) return(ERROR); /* send & get reply */ } while(StuffPtr->ErrBlocks < MAXBLKERR); return(ERROR); } int BlockRec() /* get a block of data from serial buffer */ { long t1, t2; StuffPtr->CharPtr = StuffPtr->BuffPtr; /* cycle through wait periods */ if(++StuffPtr->Wait > 2) StuffPtr->Wait = 0; t2 = 0L; /* set to current time plus the wait period */ t1 = clock() + StuffPtr->Period[StuffPtr->Wait]; while(check() == NULL) { if(t2 > t1) return(ERROR); t2 = clock(); } do { t2 = 0L; t1 = clock() + 150L; /* max of .15 sec without receiving a byte */ while(check() == NULL) { if(t2 > t1) return(-2); t2 = clock(); } *StuffPtr->CharPtr = getser(); ++StuffPtr->CharPtr; } while(StuffPtr->CharPtr < StuffPtr->BuffPtr+(StuffPtr->BlkSize & 0xff)); return(ChksmRec()); /* return result of checksum verification */ } int ChksmRec() /* performs checksum tests on received block */ { /* do both additive and cyclical checksum calculation on block */ for(StuffPtr->Chksum = StuffPtr->CLC = 0,StuffPtr->CharPtr = StuffPtr->BuffPtr+4; StuffPtr->CharPtr < StuffPtr->BuffPtr + (StuffPtr->BlkSize & 0xff); StuffPtr->CharPtr++) { StuffPtr->Chksum += (*StuffPtr->CharPtr & 0xff); StuffPtr->CLC ^= (*StuffPtr->CharPtr & 0xff); if(StuffPtr->CLC & 0x8000) StuffPtr->CLC = (StuffPtr->CLC << 1) + 1; else StuffPtr->CLC <<= 1; } /* test against checksums received */ if(*(StuffPtr->BuffPtr+CHKSUM_L) == (char)StuffPtr->Chksum & 0xff && *(StuffPtr->BuffPtr+CHKSUM_H) == (char)(StuffPtr->Chksum >> 8 & 0xff) && *(StuffPtr->BuffPtr+CLC_L) == (char)StuffPtr->CLC & 0xff && *(StuffPtr->BuffPtr+CLC_H) == (char)(StuffPtr->CLC >> 8 & 0xff)) return(NOERR); /* signal good block */ else return(ERROR); /* signal bad block */ } int EndReceive() /* handle reply codes for ending data receive */ { if(SLCodes() == ERROR) return(ERROR); /* send & get reply */ StuffPtr->ReplyOut = CODE_SBK; /* send out 'S/B' */ StuffPtr->ReplyWant = CODE_SYN; /* want 'SYN' */ if(SLCodes() == ERROR) return(ERROR); /* send & get reply */ StuffPtr->ReplyOut = CODE_SYN; /* send 'SYN' */ StuffPtr->ReplyWant = CODE_SBK; /* want 'S/B' */ if(SLCodes() == ERROR) return(ERROR); /* send & get reply */ StuffPtr->ReplyOut = CODE_SBK; /* receiver terminates */ CodeOut(); return(NOERR); } /************************************************************************** * * functions to send blocks * **************************************************************************/ int fortran C1Send(pathname,bsize,fflg,loc) char *pathname; int bsize; int fflg; long loc; { struct find_t find, *f_ptr; int i; int x; scnloc=loc; C1Stuff.ReplyStr[0] = "ACK"; /* signal a good or bad block */ C1Stuff.ReplyStr[1] = "GOO"; /* signal a good block */ C1Stuff.ReplyStr[2] = "BAD"; /* signal a bad block */ C1Stuff.ReplyStr[3] = "SYN"; /* signal end of transfer sequence */ C1Stuff.ReplyStr[4] = "S/B"; /* request block or signal transfer done */ /* reply code Wait Period sequence */ C1Stuff.Period[0] = LONG; C1Stuff.Period[1] = LONG; C1Stuff.Period[2] = SHORT; StuffPtr = &C1Stuff; /* set pointer to C1 variables struct */ f_ptr = &find; C1Stuff.BuffPtr = C1Stuff.BuffOne; /* set pointer to a data buffer */ C1Stuff.FullBsize = 0; /* zero size of a full block */ C1Stuff.Wait = 0; /* zero reply code Wait Period index */ C1Stuff.TransFlag = FALSE; /* set ASCII-PETSCII TransFlag flag */ xfertotal = 0; badblocks = 0; _dos_open(pathname,O_RDONLY,&tfh); C1Stuff.FileFlag=fflg; C1Stuff.FullBsize=bsize; x=0; do { GetBytes(); if (x++>12 || c1cancel()==ERROR) return; } while (strncmp(StuffPtr->CharPtr-3,StuffPtr->ReplyStr[CODE_GOO],3) != NULL && x++<80); if(fflg != 0) { nocount = 1; if(FtypeSend(fflg) == ERROR) /* send the file type block */ { _dos_close(tfh); return(4); } } nocount = 0; if(DataSend() == ERROR) /* send data blocks */ { _dos_close(tfh); return(1); } _dos_close(tfh); return(0); } int FtypeSend(ftype) /* handle sending file type block */ int ftype; { StuffPtr->ErrCodes = NOERR; /* zero reply code error count */ StuffPtr->ErrBlocks = NOERR; /* zero data block error count */ StuffPtr->EndOff = FALSE; /* end off sequence flag */ StuffPtr->BlkSize = HEADER+1; /* set size of this block */ StuffPtr->BlkNum = 0; /* set number of this block */ /* build the file type block */ *(StuffPtr->BuffPtr+BLKNUM_L) = EOT_FLAG; /* sixth byte = 255 */ *(StuffPtr->BuffPtr+BLKNUM_H) = EOT_FLAG; /* seventh byte = 255 */ /* eighth byte contains file type: 1=SEQ(text),2=PROG(binary),3=WORDPRO */ *(StuffPtr->BuffPtr+FILE_TYPE) = ftype; ChksmSend(); /* get checksum into first four bytes */ /* start of a file type transfer */ if(SLBlocks() == ERROR) return(ERROR); /* send file type block */ return(EndSend()); /* return result of file type xfer end off */ } int DataSend() /* controls block sending sequence */ { StuffPtr->EndFlag = FALSE; /* local last block flag */ StuffPtr->EndOff = FALSE; /* end off sequence flag */ StuffPtr->ErrCodes = NOERR; /* zero reply code error count */ StuffPtr->ErrBlocks = NOERR; /* zero data block error count */ StuffPtr->BlkSize = HEADER; /* set up block zero */ *(StuffPtr->BuffPtr+NEXT_BSIZE) = HEADER; /* size of block zero */ /* build block zero */ *(StuffPtr->BuffPtr+BLKNUM_L) = 0x00; *(StuffPtr->BuffPtr+BLKNUM_H) = 0x00; StuffPtr->BlkNum = -1; ChksmSend(); /* get checksums into first four bytes */ if(SLBlocks() == ERROR) return(ERROR); /* send block zero */ ++StuffPtr->BlkNum; /* increment local block number */ /* set up block two so its size can be sent in block one */ if(DataBuild() == ERROR) return(ERROR); StuffPtr->BlkSize = HEADER; /* size of block one */ /* build block number one */ *(StuffPtr->BuffPtr+BLKNUM_L) = 0x01; *(StuffPtr->BuffPtr+BLKNUM_H) = 0x00; ChksmSend(); /* get checksums into first four bytes */ if(SLBlocks() == ERROR) return(ERROR); /* send block one */ do { ++StuffPtr->BlkNum; /* increment local block number */ /* current block obtained previously so its size is already known */ StuffPtr->BlkSize = StuffPtr->NextBsize; /* build next block and do checksums for current block */ if(DataBuild() == ERROR) return(ERROR); if(SLBlocks() == ERROR) return(ERROR); /* send current block */ /* if final block was sent do end off and return result */ if(StuffPtr->EndOff == TRUE) return(EndSend()); } while(TRUE); } int DataBuild() /* builds both the next and the current block */ { int BlkSize; /* holds actual no. of bytes read from disk */ /* read a block from disk if last block not yet obtained */ /* NOTE: block read now will be the following one sent, not current one */ if(StuffPtr->EndFlag == FALSE) { _dos_read(tfh,StuffPtr->BuffPtr+HEADER,StuffPtr->FullBsize-HEADER,&BlkSize); if(eof(tfh) != NULL) /* check for end-of-file */ { /* signal last block to receiver */ *(StuffPtr->BuffPtr+BLKNUM_L) = EOT_FLAG; *(StuffPtr->BuffPtr+BLKNUM_H) = EOT_FLAG; StuffPtr->EndFlag = TRUE; /* set local last block flag */ } else /* put block number in sixth & seventh bytes */ { *(StuffPtr->BuffPtr+BLKNUM_L) = (StuffPtr->BlkNum+1) & 0xff; *(StuffPtr->BuffPtr+BLKNUM_H) = (StuffPtr->BlkNum+1) >> 8 & 0xff; } /* store size of this block for when it is sent after current one */ StuffPtr->NextBsize = BlkSize+HEADER; } /* swap buffer pointer to other buffer which contains current block */ StuffPtr->BuffPtr = (StuffPtr->BuffPtr == StuffPtr->BuffOne) ? StuffPtr->BuffTwo : StuffPtr->BuffOne; /* set fifth byte of current block to size of next block */ *(StuffPtr->BuffPtr+NEXT_BSIZE) = StuffPtr->NextBsize & 0xff; /* put additive & cyclical checksums into first four bytes */ ChksmSend(); return(NOERR); } int SLBlocks() /* statement & listen loop for block send */ { int resend; char *charptr; do { resend = FALSE; /* set resend flag */ StuffPtr->ReplyOut = CODE_ACK; /* send 'ACK' to receiver */ StuffPtr->ReplyWant = CODE_SBK; /* want 'S/B' from receiver */ if(SLCodes() == ERROR) /* send & get reply */ { return(ERROR); } for(charptr = StuffPtr->BuffPtr; /* send out block */ charptr < StuffPtr->BuffPtr + (unsigned)StuffPtr->BlkSize; charptr++) putser(*charptr); do { GetBytes(); /* search for string match for 'GOO' or 'BAD' */ if(strncmp(StuffPtr->CharPtr-3,StuffPtr->ReplyStr[CODE_GOO],3) == NULL) { if (*(StuffPtr->BuffPtr + BLKNUM_H) !=0 || *(StuffPtr->BuffPtr + BLKNUM_L) !=0) { if (nocount==0) xfertotal += ((StuffPtr->BlkSize & 0xff) - HEADER); prtstat(xfertotal,badblocks); } StuffPtr->ErrBlocks = NOERR; /* clear block error count */ StuffPtr->ErrCodes = NOERR; /* clear reply error count */ if((*(StuffPtr->BuffPtr + BLKNUM_H) & 0xff) == EOT_FLAG) StuffPtr->EndOff = TRUE; /* final block sent ok */ return(NOERR); } else if(strncmp(StuffPtr->CharPtr-3,StuffPtr->ReplyStr[CODE_BAD],3) == NULL) { badblocks++; prtstat(xfertotal,badblocks); ++StuffPtr->ErrBlocks; /* increment block error count */ StuffPtr->ErrCodes = NOERR; /* clear reply error count */ resend = TRUE; /* flag no end off */ } else ++StuffPtr->ErrCodes; if(StuffPtr->ErrCodes > MAXCODERR) return(ERROR); } while(resend == FALSE); } while(StuffPtr->ErrBlocks < MAXBLKERR); return(ERROR); } void ChksmSend() /* generates checksums for current block */ { int x; /* calculate additive and cyclical checksums */ for(StuffPtr->Chksum=StuffPtr->CLC=0,StuffPtr->CharPtr=StuffPtr->BuffPtr+4; StuffPtr->CharPtr < StuffPtr->BuffPtr + (StuffPtr->BlkSize & 0xff); StuffPtr->CharPtr++) { StuffPtr->Chksum += (*StuffPtr->CharPtr & 0xff); StuffPtr->CLC ^= (*StuffPtr->CharPtr & 0xff); if(StuffPtr->CLC & 0x8000) StuffPtr->CLC = (StuffPtr->CLC << 1) + 1; else StuffPtr->CLC <<= 1; } /* put checksums into first four bytes */ *(StuffPtr->BuffPtr+CHKSUM_L) = StuffPtr->Chksum & 0xff; *(StuffPtr->BuffPtr+CHKSUM_H) = StuffPtr->Chksum >> 8 & 0xff; *(StuffPtr->BuffPtr+CLC_L) = StuffPtr->CLC & 0xff; *(StuffPtr->BuffPtr+CLC_H) = StuffPtr->CLC >> 8 & 0xff; } int EndSend() /* handle reply codes for data send */ { StuffPtr->EndXfer = FALSE; StuffPtr->ReplyOut = CODE_ACK; /* send 'ACK' */ StuffPtr->ReplyWant = CODE_SBK; /* want 'S/B' */ if(SLCodes() == ERROR) return(ERROR); /* send & get reply */ StuffPtr->ReplyOut = CODE_SYN; /* send 'SYN' */ StuffPtr->ReplyWant = CODE_SYN; /* want 'SYN' */ if(SLCodes() == ERROR) return(ERROR); /* send & get reply */ StuffPtr->EndXfer = TRUE; StuffPtr->ReplyOut = CODE_SBK; /* send 'S/B' */ StuffPtr->ReplyWant = CODE_SBK; /* want 'S/B' */ if(SLCodes() == ERROR) return(ERROR); /* send & get reply */ return(NOERR); } /************************************************************************** * * functions common to both receiving and sending * **************************************************************************/ int SLCodes() /* statement & listen loop for reply codes */ { do { if (c1cancel()==ERROR) return (ERROR); CodeOut(); /* send reply code to receiver */ /* on final end off only send 'S/B' three times */ if(StuffPtr->EndXfer == TRUE && StuffPtr->ErrCodes > 1) return(NOERR); if(GetBytes() == ERROR) continue; /* search for string match */ if(strncmp(StuffPtr->CharPtr-3, StuffPtr->ReplyStr[StuffPtr->ReplyWant],3) == NULL) { StuffPtr->ErrCodes = NOERR; /* clear reply code error count */ return(NOERR); } else ++StuffPtr->ErrCodes; } while(StuffPtr->ErrCodes < MAXCODERR); return(ERROR); } int GetBytes() { long t1, t2; StuffPtr->CharPtr = StuffPtr->TempBuff; *(StuffPtr->CharPtr+0)=0; *(StuffPtr->CharPtr+1)=0; *(StuffPtr->CharPtr+2)=0; /* cycle through wait periods */ if(++StuffPtr->Wait > 2) StuffPtr->Wait = 0; t2 = 0L; /* set to current time plus the wait period */ t1 = clock() + StuffPtr->Period[StuffPtr->Wait]; while(check() == NULL) { if(t2 > t1) { ++StuffPtr->ErrCodes; return(ERROR); } t2 = clock(); } do { t2 = 0L; t1 = clock() + 50L; /* max of .05 sec without receiving a byte */ while(check() == NULL) { if(t2 > t1) { StuffPtr->CharPtr += 3; return(NOERR); } t2 = clock(); } *(StuffPtr->CharPtr+0)=*(StuffPtr->CharPtr+1); *(StuffPtr->CharPtr+1)=*(StuffPtr->CharPtr+2); *(StuffPtr->CharPtr+2)=getser(); } while(1); } int CodeOut() /* send out a reply code */ { if (StuffPtr->ReplyOut < 5) { StuffPtr->CharPtr = StuffPtr->ReplyStr[StuffPtr->ReplyOut]; while(*StuffPtr->CharPtr != NULL) putser(*StuffPtr->CharPtr++); } } int c1cancel () { int x; if ((inp(1022) & 128)==0) return (ERROR); x=_bios_keybrd(_KEYBRD_SHIFTSTATUS) & 15; if (x==14 || x==13) return (ERROR); return (NOERR); } -- UUCP: watmath!xenitec!zswamp!root | 602-66 Mooregate Crescent Internet: root@zswamp.fidonet.org | Kitchener, Ontario FidoNet: SYSOP, 1:221/171 | N2M 5E6 CANADA Data: (519) 742-8939 | (519) 741-9553 MC Hammer, n. Device used to ensure firm seating of MicroChannel boards Try our new Bud 'C' compiler... it specializes in 'case' statements!