'===========================================================================
' Subject: PB NETBIOS                         Date: 09-01-96 (22:19)       
'  Author: Andrew Coggins                     Code: PB                     
'  Origin: rjohnso6@ix.netcom.com           Packet: NETWORK.ABC
'===========================================================================
'==============================PBNETBIO.BAS================================
'This is public domain stuff, use at your own risk, etc. To test the use
'of the NetBios commands used to receive and send broadcast messages over
'a network. This sends ONE 80 character message to the other machine each
'time you hit "S" You must have registered an new name (and know its
'number) in the local name table before you can use the broadcast stuff.
'This is true for NetBIOS over NetBEUI in a DOS VDM in OS2 PEER. You may
'can avoid having to create a name in true DOS/NOVELL stuff. I don't have
'that so I can't test it. You only have to add a name once for your process
'to use it many times. (You should probably delete it when you finish your
'process because the name table is finite , but OS2 deletes it for me
'when I close the VDM so I don't worry about it. (I can't say enough good
'about OS2's accurate simulation of DOS (unlike Windoze XX)))

'This does not check for the presence of a
'network. PB BBS has an example already that does that (PBLANT.ZIP).
'Another usefull document is Tom Thompson's NetBios Programmers Reference
'(1988 is the latest version I have) also on the PB BBS. This is meant
'mostly as an example of how to set up and talk to the NetBIOS (and as a
'result, the other computers). It wasn't meant to do anything useful, but
'if you run it on 2 computers when you hit the "S" key on one you'll see
'a string pop up on the other. I recommend single stepping if you want to
'see what is going on.

'The basic operation of the program is:
'  1. Establish a name and get its number in the local name table
'  2. Go into Receive (no wait) mode (listen for broadcast from others)
'  3. Wait on local user (You) to hit "S" or ESC (to quit)
'  4. Upon S-key, cancel Receive, issue Send Broadcast, reissue Receive
'  5. Upon any receive, print what was received, and reissue receive.

'OBTW - my name is Andy Coggins - I can be reached at
'rjohnso6@netcom.com. I am in the discovery phase w/ this stuff myself.
'But I could find NOTHING on this on ANY board/net/ftp/www so I decided
'to send this up because it worked as intended and maybe it will help
'somebody out. If you have anything that expands on this please let me
'know or post to the PowerBASIC site (support@powerpasic.com).
'======================================================================
%FLAGS = 0          'constants for register names
%AX    = 1
%BX    = 2
%CX    = 3
%DX    = 4
%SI    = 5
%DI    = 6
%BP    = 7
%DS    = 8
%ES    = 9

'establish the Network Control block Type (64 bytes)
TYPE NCBType
  cmd AS BYTE             ' 1-command code
  rcode AS BYTE           ' 1-return code
  lsn AS BYTE             ' 1-Local Session Number
  locnum AS BYTE          ' 1-my name # in local name table (1)
  iobuff AS DWORD         ' 4-FAR Pointer to I/O Buffer
  bufflen AS WORD         ' 2-length of data in buffer
  callname AS STRING * 16 ' 16-remote system to call
  locname AS STRING * 16  ' 16-network name of local machine
  rectimout AS BYTE       ' 1-Recieve Time Out in 1/2 Seconds
  sentimout AS BYTE       ' 1-send Timeout in 1/2 second
  inthandl AS DWORD       ' 4-FAR Pointer to Post routine handler (0:0)
  cardnum AS BYTE         ' 1-Network Adapter number to execute command
  cmd_ccode AS BYTE       ' 1-Command Completion code
  dum AS STRING * 14      ' 14-reserved for use by Network Card Only
END TYPE

'Initialize the NCB to all 0's (I assume they mean nulls in strings)
'You are supposed to clear the NCB each time you issue a new command
'(or use a different NCB)
'this should happen upon creation of the variable, so just copy
'what was created and that is my init value
DIM ncb AS SHARED NCBType               'working variable
DIM ncbinit AS SHARED NCBType           'initiazation value

netname$ = "DELL 486-50"     'a unique name to add to the name table

MAP iobuff$$ * 512         'use 512 buffer for broadcast messages
DIM buffaddr AS SHARED BYTE PTR          'pointer for string data
buffaddr = STRPTR32(iobuff$$)    '32bit address of data itself


CLS
'add unique name to name table and get the number returned by
'NetBIOS.
ncb = ncbinit               'clear the Network Control Block
PRINT "Adding Name will take approx 10 seconds (why, I don't know)"
CALL NCBAddName (ncb, netname$, xcode%)
IF xcode% THEN
  hexxcode$ = RIGHT$("00"+HEX$(xcode%),2)
  PRINT "We have a NetBIOS error - AL = ";hexxcode$;" (hex)  ";xcode%;" decimal"
  GOTO End.NTTest
ELSE
  PRINT "ADD Name was sucessful"
  PRINT "The Name ADDED was - ";ncb.locname
  PRINT "The Name NUMBER for this name is - ";ncb.locnum
  namenum? = ncb.locnum            'this is # to use for broadcast cmds
END IF


ncb = ncbinit               'must do to enter a command
rxcnt% = 0: txcnt% = 0      'just some status counters

'set up receive mode on each computer
CALL NCBRcvBroadcast (ncb, buffaddr, namenum?, xcode%)
'CALL PrintNCB(ncb)              'just for testing
IF xcode% THEN
  hexxcode$ = RIGHT$("00"+HEX$(xcode%),2)
  PRINT "We have a NetBIOS error - AL = ";hexxcode$;" (hex)  ";xcode%;" decimal"
  CALL PrintNCB(ncb)            'just to see what changed in the NCB
  GOTO End.NTTest
END IF                          'error code on exit

PRINT "Hit (S)end to send a broadcast, ESC to stop the program"
'This is the main program loop
choice$ = ""
WHILE choice$ <> CHR$(27)
  choice$ = INKEY$
  choice$ = UCASE$(choice$)
'  choice$ = "S"              'for single step testing
  IF choice$ = "S" THEN
    'cancel receive command
    CALL NCBCancel (ncb, xcode%)
'    CALL PrintNCB (ncb)
    IF xcode% THEN
      hexxcode$ = RIGHT$("00"+HEX$(xcode%),2)
      PRINT "We have a NetBIOS error - AL = ";hexxcode$;" (hex)  ";xcode%;" decimal"
      CALL PrintNCB(ncb)            'just to see what changed in the NCB
      GOTO End.NTTest
    END IF                        'xcode%

  'initiate send command
    INCR txcnt%
    ncb = ncbinit                      'reinitialize ncb
    'use a dummy dynamic string (so you will know its length)
    'change the string each time so you can see some progress
    iobuff$ = STRING$(80, LTRIM$(STR$(txcnt% MOD 10)))
    CALL NCBSendBroadcast (ncb, buffaddr, namenum?, iobuff$, xcode%)
'    CALL PrintNCB (ncb)
    IF xcode% THEN
      hexxcode$ = RIGHT$("00"+HEX$(xcode%),2)
      PRINT "We have a NetBIOS error - AL = ";hexxcode$;" (hex)  ";xcode%;" decimal"
      CALL PrintNCB(ncb)            'just to see what changed in the NCB
      GOTO End.NTTest
    ELSE
      PRINT "Broadcast packet number - ";txcnt%
      PRINT iobuff$                'print what you sent
      'reissue receive command
      ncb = ncbinit
      CALL NCBRcvBroadcast (ncb, buffaddr, namenum?, xcode%)
      IF xcode% THEN
        hexxcode$ = RIGHT$("00"+HEX$(xcode%),2)
        PRINT "We have a NetBIOS error - AL = ";hexxcode$;" (hex)  ";xcode%;" decimal"
        CALL PrintNCB(ncb)            'just to see what changed in the NCB
        GOTO End.NTTest
      END IF                          'error code on exit
    END IF

  ELSE                                'choice$ <> "s"
    'check for received message in no wait mode
    'print received message
    IF ncb.cmd_ccode = 255 THEN              '&HFF until completed
      LOCATE 24,1
      PRINT "Waiting on Send Broadcast from other computer - ";waitcnt&;
      INCR waitcnt&
    ELSEIF ncb.cmd_ccode = 0 THEN        'receive was successful
      INCR rxcnt%
      LOCATE 10,1
      PRINT "Receive packet number - ";USING$("#######",rxcnt%);" From ";ncb.callname
      msg$ = LEFT$(iobuff$$, ncb.bufflen)
      PRINT msg$
      msg$ = ""
      'reissue receive command
      ncb = ncbinit
      CALL NCBRcvBroadcast (ncb, buffaddr, namenum?, xcode%)
      IF xcode% THEN
        hexxcode$ = RIGHT$("00"+HEX$(xcode%),2)
        PRINT "We have a NetBIOS error - AL = ";hexxcode$;" (hex)  ";xcode%;" decimal"
        CALL PrintNCB(ncb)            'just to see what changed in the NCB
        GOTO End.NTTest
      END IF                          'error code on exit
    ELSE             'we had some netbios error - never happened in my test
      hexxcode$ = RIGHT$("00"+HEX$(ncb.cmd_ccode),2)
      PRINT "We have a NetBIOS error - AL = ";hexxcode$;" (hex)  ";xcode%;" decimal"
      CALL PrintNCB(ncb)            'just to see what changed in the NCB
      GOTO End.NTTest
    END IF                         'ncb.cmd_ccode
  END IF                         'choice$ = "s" or not
WEND

End.Nttest:
PRINT "The program is finished - I hope you enjoyed this as much as I did"
END
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Below are the Network generic routines.
'A routine to dump the Network Control Block to the Screen
SUB PrintNcb (ncb AS NCBType) LOCAL
  PRINT "This is the value in each NCB field"

  PRINT "cmd = ";         ncb.cmd
  PRINT "rcode = ";       ncb.rcode
  PRINT "lsn = ";         ncb.lsn
  PRINT "locnum = ";      ncb.locnum
  PRINT "iobuff = ";      ncb.iobuff
  PRINT "bufflen = ";     ncb.bufflen
  PRINT "callname = ";    ncb.callname
  PRINT "locname = ";    ncb.locname
  PRINT "rectimout = ";  ncb.rectimout
  PRINT "sentimout = ";  ncb.sentimout
  PRINT "inthandl = ";   ncb.inthandl
  PRINT "cardnum = ";    ncb.cardnum
  PRINT "cmd_ccode = ";  ncb.cmd_ccode
  PRINT "dum = ";        ncb.dum

  IF ncb.bufflen <> 0 THEN
    DIM x AS BYTE PTR                 'handle string 1 byte at a time
    x = ncb.iobuff             'transfer 32bit address of message buffer
    FOR count% = 1 TO ncb.bufflen  'read msg buffer 1 byte at a time
      inchar$ = CHR$(@x)           '@z points to an ASCII byte
      msg$ = msg$ + inchar$      'build a string for viewing in IDE
      INCR x                       ' increment the pointer itself
    NEXT count%
    PRINT "|" + msg$ + "|"
  END IF                          'anything in the io buffer?
END SUB                           'PrintNcb
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'display the NCB Status stuff. I got junk for the most of these, but that
'could be because of OS2's virtualization (or it could be me taking it
'apart wrong) I only wanted to look at the name table and I got that part
'right. If you want to get the rest of the status fields correct you
'need better documentation than I have. If you correct it, feel free to
'just replace this post w/ the correct stuff.
'This routine is not called in this program.
SUB DispNcbStatus (ncb AS NCBType, a$$) LOCAL
'a$$ is the global message buffer iobuff$$
'take apart the message buffer returned by the &H33 command to NetBios
'ie. show me satus of things
DIM y AS BYTE PTR        'a local byte pointer to walk down the string with
y = ncb.iobuff           'same as STRPTR32(a$$)

namerows% = (ncb.bufflen - 60)\18
DIM Locname$(1:namerows%, 1:3)        'table for local names

'there is probably an easier way to do this, but I don't need it,so
'I didn't spend time thinking about it.
nodename$ =               GimmeHex$(a$$, 1, 6)
jumpstat$ =               GimmeHex$(a$$, 7, 1)
post$ =                   GimmeHex$(a$$, 8, 1)
version$ =                GimmeHex$(a$$, 9, 2)
minrun$ =                 GimmeHex$(a$$, 11,2)
crcerr$ =                 GimmeHex$(a$$, 13,2)
alignerr$ =               GimmeHex$(a$$, 15,2)
coliderr$ =               GimmeHex$(a$$, 17,2)
aborterr$ =               GimmeHex$(a$$, 19,2)
txpacket$ =               GimmeHex$(a$$, 21,4)
rcpacket$ =               GimmeHex$(a$$, 25,4)
txretry$ =                GimmeHex$(a$$, 29,2)
rxnobuf$ =                GimmeHex$(a$$, 31,2)
dum1$ =                   GimmeHex$(a$$, 33,8)
freencb$ =                GimmeHex$(a$$, 41,2)
lastresetncb$ =           GimmeHex$(a$$, 43,2)
maxresetncb$ =            GimmeHex$(a$$, 45,2)
dum2$ =                   GimmeHex$(a$$, 47,4)
numsess$ =                GimmeHex$(a$$, 51,2)
lastresetsess$ =          GimmeHex$(a$$, 53,2)
maxresetsess$ =           GimmeHex$(a$$, 55,2)
maxpacketsize$ =          GimmeHex$(a$$, 57,2)
numnames$ =               GimmeHex$(a$$, 59,2)
'below are the local names
FOR i% = 0 TO namerows%-1
  Locname$(i%+1, 1) = MID$(a$$, 61+(18*i%),16)                    'local name
  Locname$(i%+1, 2) = LTRIM$(STR$(ASCII(MID$(a$$, 77+(18*i%),1)))) 'name #
  Locname$(i%+1, 3) = BinFmt$(ASCII(MID$(a$$, 78+(18*i%),1)),8)    'status
NEXT i%


PRINT "nodename$ = ";            nodename$;                          " ";
PRINT "jumpstat$ = ";            BimFmt$(VAL("&H"+jumpstat$),8);     " ";
PRINT "post$ = ";                VAL("&H"+post$);                    " ";
PRINT "version$ = ";             version$;                           " "
PRINT "minrun$ = ";              VAL("&H"+minrun$);                  " ";
PRINT "crcerr$ = ";              VAL("&H"+crcerr$);                  " ";
PRINT "alignerr$ = ";            VAL("&H"+alignerr$);                " ";
PRINT "coliderr$ = ";            VAL("&H"+coliderr$);                " ";
PRINT "aborterr$ = ";            VAL("&H"+aborterr$);                " "
PRINT "txpacket$ = ";            VAL("&H"+txpacket$);                " ";
PRINT "rcpacket$ = ";            VAL("&H"+rcpacket$);                " ";
PRINT "txretry$ = ";             VAL("&H"+txretry$);                 " ";
PRINT "rxnobuf$ = ";             VAL("&H"+rxnobuf$);                 " "
'PRINT "dum1$ = ";                dum1$
PRINT "freencb$ = ";             VAL("&H"+freencb$);                 " ";
PRINT "lastresetncb$ = ";        VAL("&H"+lastresetncb$);            " ";
PRINT "maxresetncb$ = ";         VAL("&H"+maxresetncb$);             " "
'PRINT "dum2$ = ";                dum2$
PRINT "numsess$ = ";             VAL("&H"+numsess$);                 " ";
PRINT "lastresetsess$ = ";       VAL("&H"+lastresetsess$);           " ";
PRINT "maxresetsess$ = ";        VAL("&H"+maxresetsess$);            " ";
PRINT "maxpacketsize$ = ";       VAL("&H"+maxpacketsize$);           " ";
PRINT "numnames$ = ";            VAL("&H"+numnames$);                " "
PRINT
PRINT " LOCAL Name Table follows"
'below are the local names
FOR i% = 0 TO namerows%-1
  PRINT "Name = ";Locname$(i%+1, 1);                                 " ";
  PRINT "NameNum = ";Locname$(i%+1, 2);                              " ";
  PRINT "Status = ";Locname$(i%+1, 3);                               " "
NEXT i%

END SUB                                    'DispNcbStatus
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'to look at the binary representation of a value (from PB3 help)
FUNCTION BinFmt$(value&,size%) ' returns a binary string of size% digits
  BinFmt$ = RIGHT$("000000000000000"+BIN$(value&),size%)
END FUNCTION                         'BinFmt$
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'not used in this program (used by the DispNcbStatus routine)
FUNCTION GimmeHex$(a$$, start%, length%)
  'since y is pointing to 1st byte already, we want to hand it a start% = 1
  '(like MID$) but we don't want to add 1 to it. If we want it to start at
  'position 7 we want to add 6 to it.
  DIM y AS BYTE PTR
  y = STRPTR32(a$$)

  INCR y, start%-1
  FOR i% = 1 TO length%
    a$ = a$ + RIGHT$("00"+HEX$(@y),2)
    INCR y
  NEXT i%
  FUNCTION = a$
END FUNCTION                          'GimmeHex$
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'just to consolidate code the call and the associated return value
'are passed to and from this routine
SUB CallNcb (ncb AS NCBType, rcode%) LOCAL
ncbseg?? = VARSEG(ncb)                  'find our typed var in memory
'ncbseghex$ = HEX$(ncbseg??)
ncbofs?? = VARPTR(ncb)
'ncbofshex$ = HEX$(ncbofs??)

REG %ES, ncbseg??
REG %BX, ncbofs??
CALL INTERRUPT &H5C
AL?? = REG(%AX) AND &H00FF
rcode% = AL??                  'AL = 0 means no error

END SUB                                'Call Ncb
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'not used in this routine
SUB CheckAddapterStatus (ncb AS NCBType, buffaddr AS DWORD, rcode%) LOCAL

ncb.cmd = &h033                        'send a broadcast in wait mode
ncb.cardnum = 0             'send message from 1st network card in machine
ncb.callname = "*"                     'use my local call name
ncb.iobuff = buffaddr                  '32 bit address of the msg buffer
ncb.bufflen = 512                      'length of the message buffer

CALL CallNcb (ncb, rcode%)

END SUB                                  ' CheckAddapterStatus
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'add a name to the local name table
SUB NCBAddName(ncb AS NCBType, addname$, rcode%) LOCAL
ncb.cmd = &H030            'add name command - wait mode

ncb.cardnum = 0             'use 1st network card in machine
ncb.locname = addname$     'name to add

CALL CallNcb (ncb, rcode%)

END SUB                                  'NCBAddName
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'delete a name from the local name table
SUB NCBDeleteName(ncb AS NCBType, delname$, rcode%) LOCAL
ncb.cmd = &H031            'delete name command - wait mode

ncb.cardnum = 0             'use 1st network card in machine
ncb.locname = delname$

CALL CallNcb (ncb, rcode%)

END SUB                                  'NCBDeleteName
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'set up to receive, use no wait mode so you can do other things
'like get operator input. (you can keep checking for
'ncb.cmd_ccode <> &HFF to see if you received anything)
SUB NCBRcvBroadcast(ncb AS NCBType, buffaddr AS DWORD, namenum?, rcode%) LOCAL
'ncb.cmd = &H023            'receive command - wait mode
ncb.cmd = &H0A3            'receive command - nowait mode

ncb.cardnum = 0             'use 1st network card in machine
ncb.locnum = namenum?       'my number in local name table
ncb.iobuff = buffaddr      '32 bit address of the msg buffer
ncb.bufflen = 512          'default (max) length of the message buffer

CALL CallNcb (ncb, rcode%)

END SUB                                  'Receive Broadcast
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'send a broadcast to EVERYBODY that has any kind of receive broadcast
'issued (I'm sure this could screw up some complex programs)
SUB NCBSendBroadcast(ncb AS NCBType, buffaddr AS DWORD, namenum?, iobuff$, rcode%) LOCAL
SHARED iobuff$$
ncb.cmd = &H022            'send command - wait mode

ncb.cardnum = 0             'use 1st network card in machine
ncb.locnum = namenum?      'my position in local name table
ncb.iobuff = buffaddr      '32 bit address of the msg buffer
iobuff$$ = iobuff$
ncb.bufflen = LEN(iobuff$)    'length of the message to send


CALL CallNcb (ncb, rcode%)

END SUB                                  'Send Broadcast
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'must cancel an unfinished command issued by a ncb before using
'THAT ncb for another command (you wouldn't believe how things bombed
'when I didn't cancel receive before I tried to send)
'I used wait mode (it was instantaneous)
SUB NCBCancel(ncb AS NCBType, rcode%) LOCAL
ncb.cmd = &H035            'cancel command for this ncb - wait mode

ncb.cardnum = 0             'use 1st network card in machine

CALL CallNcb (ncb, rcode%)
'I think OS/2 returns a 24h (36) (command competed before could cancel) to
'cancel a receive broadcast command, so I will treat this as no error.
IF rcode% = 36 THEN rcode% = 0
END SUB                                  'NCBCancel
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
