TIP: Click on subject to list as thread! ANSI
echo: net_dev
to: All
from: Rene Herman
date: 1997-11-27 23:47:26
subject: MSGID.ASM

Hello All ...

=== Cut ===
;--------------------------------------------------------------------------
;
; MSGID.ASM, assembly language implementation of the procedures in the
; MSGID Pascal unit. Written and placed into the public domain, 1997-11-28,
; Rene Herman (2:282/1.11).
;
; Provides a function for generating (the number part of) MSGIDs (message
; identification strings) used to implement duplicate message detection in
; FidoNet Technology Networks (FTNs).
;
; According to FTS-0009, a MSGID (again, the number part of) must be an
; eight character hexadecimal number, uniquely identifying a message from a
; particular system for a period of at least three years. We comply (using
; 4 years for easy math) by constructing "base" MSGIDs according to:
;
;  MSGID := ((Year mod 4) shl 30) or (Month shl 26) or (Day shl 21) or
;           (Hour shl 16) or (Min shl 10) or (Sec shl 4)
;
; and using the right 4 bits as a continous counter. That is, we generate
; a base MSGID per the formula and continue adding 1 to it until we
; overflow the right 4 bits, at which time we start anew (at least delaying
; until the next second, in case we used up all our MSGIDs in one second).
; This method allows 16 MSGIDs to be generated per second, which should be
; enough. One extra free bit could be obtained by calculating the base
; MSGID as the number of seconds in this "Year mod 4" (or 3) period,
; providing for 32 possible MSGIDs per second, but this would require a lot
; of extra work, and probably isn't worth the trouble.
;
; Note that it is important to obtain the year, month, day, hour, minute
; and second in one indivisible operation. Most language libraries provide
; "GetTime" and "GetDate" functions, but using these
functions could cause
; trouble. Consider, for example, the situation that we start reading the
; date on 1997-28-11 / 23:59:59. Now, before we can read the time, the date
; changes, and when we get around to reading the time, we read 00:00:00.
; The program now believes it's 1997-28-11 / 00:00:00, a day ago, which
; could cause duplicate MSGIDs to be generated (if at that very moment a
; day ago we were also running). If we read the time first, we could wind
; up a day in the future, possibly causing the next-day run to generate
; duplicates. For this reason the code below gets the time and date values
; directly from CMOS.
;
; Of course, this approach has one major flaw, one that is shared by all
; methods that are based on the date and time, and that is the fact that
; there is no way to guard against someone setting back the clock. With
; the inaccuracy of PC clocks, and not to mention daylight savings time,
; this isn't a rare event.
;
; But unfortunately, short of introducing some concept like "network time",
; (or further elaborating on it?) with all Fido participants required to
; tranx their clocks to it on every connect with their uplink, I don't
; really see a solution for this problem anyway. Keeping special files
; around with the last date read or last MSGID generated certainly isn't
; the way to go. Files are bound to get corrupted or deleted, harddisks may
; crash, users may neglect to save the file when reinstalling software, and
; so on.
;
; Ignoring the problem seems to be the most intelligent solution to it, but
; if anyone has some comments on this, or any of the following, I'd be
; pleased to hear about them.
;
;--------------------------------------------------------------------------

.MODEL                  LARGE, PASCAL       ; interfacing with a unit

PUBLIC                  GetMSGID            ; publish functions
PUBLIC                  GetMSGIDStr

;--------------------------------------------------------------------------
;
; Equates for accessing the RTC CMOS registers

NMI_Disable = 080h;

RTC_Status  = NMI_Disable or 10;
RTC_Year    = NMI_Disable or  9;
RTC_Month   = NMI_Disable or  8;
RTC_Day     = NMI_Disable or  7;
RTC_Hour    = NMI_Disable or  4;
RTC_Min     = NMI_Disable or  2;
RTC_Sec     = NMI_Disable or  0;

;--------------------------------------------------------------------------
;
; Private module data. The PrevMSGID and an array of hex characters for
; GetMSGIDStr.

.DATA

PrevMSGID               DD -1
HexDigits               DB '0123456789abcdef'

.CODE

;--------------------------------------------------------------------------
;
; GetRTC helper procedure for GetMSGID. Reads the Real Time Clock Values
; from CMOS (which are stored as packed-BCD). AL holds index (and NMI bit)
; and receives the (binary) result. Destroys CL.

GetRTC                  PROC    NEAR

          out     070h, al                  ; set index
          out     0EBh, al                  ; i/o delay
          in      al, 071h                  ; get data

; Next 7 lines convert the retrieved packed-BCD byte into its binary form.
; Based on the observation that 10 * H + L = 16 * H + L - 6 * H. That is,
; subtract 6 times the high nibble from a packed-BCD byte to convert it to
; binary.

          mov     cl, al                    ; cl = 16H + L
          shr     cl, 1                     ; cl = 8H + (L shr 1)
          shr     cl, 1                     ; cl = 4H + (L shr 2)
          and     cl, 0FCh                  ; cl = 4H
          sub     al, cl                    ; org - 4H
          shr     cl, 1                     ; cl = 2H
          sub     al, cl                    ; org - 4H - 2H = org - 6H
          ret

GetRTC                  ENDP

;--------------------------------------------------------------------------
;
; GetMSGID function. Takes no parameters and returns a unique MSGID in
; DX:AX. Interface to it from Pascal as:
;
; function GetMSGID: LongInt; far;

GetMSGID                PROC

; Get the previously saved (inited to -1) PrevMSGID and see if we can
; return it. That is, see if we haven't yet used up all sixteen MSGIDs
; we can construct with the clock value we retrieved earlier. If still
; some left, immediately exit with PrevMSGID + 1.

        inc     WORD PTR [PrevMSGID]
        mov     dx, WORD PTR [PrevMSGID + WORD]
        mov     ax, WORD PTR [PrevMSGID]
        test    al, 00Fh
        jnz     {at}{at}End                       ; exit if still some left.

; Our previous IDs all used up, need to get some new ones. See start of
; source for a description of our MSGID format, and the reason for getting
; the time and date values directly from CMOS. Now for some RTC pitfalls.
;
; Quoting The Undocumented PC - Frank van Gilluwe - ISBN 0-201-62277-7
;
; " Unfortunately reading the clock values is a bit more tricky than
;   changing them. Why make our lives as programmers easy? The RTC chip
;   has an internal update cycle once every second which takes about
;   2ms. During this internal update, reads from the time, date and
;   alarm registers will get unpredictable data! If you just hope for
;   the best, about 1 out of 500 tries will have bad data. Badd odds in
;   my book, if you like stable, repeatable operation.
;
;   O.K., so you really would like to get a valid value when you read the
;   clock. To do so, disable interrupts, go into a tight loop, reading
;   register A's 'update in progress' bit 7. When this transitions from
;   high to low, you have all of 244ms to read the clock register you
;   want. "
;
; Note the "when this transitions from high to low". That means we must
; wait for it to go high first, and then wait for it to go low again. If
; we we're to just check for it to be low, it might go high just after we
; checked, invalidating our data. Fortunately, we want to do this anyway,
; as it guarantees that we don't read the same second as the last time
; through. Using this approach, even another task in a multitasking
; environment won't be able to get the same second as we did. It is
; unfortunate that we need to keep interrupts disabled while waiting,
; since this means interrupts may be disabled for (almost) a full second,
; but it really can't be helped.
;
; NMI (Non Maskable Interrupt) is also controlled through the CMOS index
; register, and the book advices to disable it as well.
;
; There is the possibility that the clock is set to use AM/PM time keeping,
; in stead (sp?) of the more sane 24hour notation. If it is, we need to
; adjust to avoid generating duplicate MSGIDs at the same time AM and PM on
; the same day. We don't go through the trouble of converting to 24hour
; notation though, but just let the high bit of the 5bit hour value reflect
; PM. The fact that 12am is earlier in the day than 1am kills the monotonic
; increasing nature of the MSGID, but we really don't care, MSGIDs need be
; unique, not monotonic increasing. When AM/PM mode is in effect, the high
; bit (sign bit) of the value in CMOS will be set for PM, and clear for AM.

        mov     al, RTC_Status              ; index status and disable NMI

        cli                                 ; disable interrupts
        out     070h, al                    ; disable NMI and index reg. A
        out     0EBh, al                    ; i/o delay

{at}{at}WaitRTCUpdate:                            ; wait for an update
        in      al, 071h
        test    al, 080h                    ; RTC update occuring?
        jz      {at}{at}WaitRTCUpdate             ; > n

{at}{at}WaitNoRTCUpdate:                          ; wait till update past
        in      al, 071h
        test    al, 080h                    ; RTC update occuring?
        jnz     {at}{at}WaitNoRTCUpdate           ; > y

; RTC just updated, now have approx 244ms (thats looong!) to get the time
; and date.

        mov     al, RTC_Day                 ; get day
        call    GetRTC
        mov     ah, al                      ; save

        mov     al, RTC_Year                ; get year
        call    GetRTC
        shr     ax, 1                       ; put year and days into place
        rcr     ax, 1
        rcr     ax, 1
        mov     dx, ax                      ; save away

        mov     al, RTC_Month               ; get month
        call    GetRTC
        shl     al, 1
        shl     al, 1
        or      dh, al                      ; or the month into place

        mov     al, RTC_Hour                ; get hours
        call    GetRTC
        jns     {at}{at}24HourFormatOrAM          ; PM (if RTC in AM/PM mode) ?
        xor     al, 090h                    ; yes, clear sign, set bit 4
{at}{at}24HourFormatOrAM:
        and     dl, 0E0h                    ; zero out left over year bits
        or      dl, al                      ; or the hour into place

        mov     al, RTC_Min                 ; get minutes
        call    GetRTC
        mov     ah, al                      ; save

        mov     al, RTC_Sec                 ; get seconds
        call    GetRTC

        out     070h, al                    ; reenable NMI (bit 7 clear)
        sti                                 ; reenable interrupts

        shl     al, 1                       ; shift seconds into high al
        shl     al, 1
        shl     ax, 1                       ; shift min/sec into high ax
        shl     ax, 1

; DX:AX now contains our MSGID. Note that the 4 shl's above have zeroed out
; the low 4 bits (our private counter). Store into PrevMSGID and return it.

        mov     WORD PTR [PrevMSGID + WORD], dx
        mov     WORD PTR [PrevMSGID], ax

{at}{at}End:
        RET

GetMSGID                ENDP

;--------------------------------------------------------------------------
;
; StoreHexWord helper procedure for GetMSGIDStr. Expects es:di to point to
; the last character of a 4 character string which it will fill with the
; ASCII-HEX representation of ax, and bx to point to a 16 char array of
; hex digits. Destroys al, cl. di decremented by 4.

StoreHexWord            PROC    NEAR

        call    {at}{at}StoreHexByte
        mov     al, ah

{at}{at}StoreHexByte:
        mov     cl, al
        and     al, 00Fh
        xlat
        mov     es:[di], al
        dec     di
        mov     al, cl
        shr     al, 1
        shr     al, 1
        shr     al, 1
        shr     al, 1
        xlat
        mov     es:[di], al
        dec     di
        ret

StoreHexWord            ENDP

;--------------------------------------------------------------------------
;
; GetMSGIDStr function. Simple wrapper around a GetMSGID call, using the
; above StoreHexWord helper to translate the retrieved MSGID to a hex
; string (a string[8] is required and sufficient). Takes no parameters and
; expects Pascal to provide it with the address of a return string. Use as:
;
; function GetMSGIDStr: string; far;

GetMSGIDStr             PROC

        call    GetMSGID
        mov     bx, sp
        les     di, ss:[bx + CODEPTR]
        add     di, DWORD * 2
        mov     bx, OFFSET HexDigits
        call    StoreHexWord
        mov     ax, dx
        call    StoreHexWord
        mov     BYTE PTR es:[di], DWORD * 2
        RET

GetMSGIDStr             ENDP

END

;--------------------------------------------------------------------------
=== Cut ===

Rene

---
* Origin: rene.herman{at}tip.nl (2:282/1.11)
SEEN-BY: 20/10 200/0 201/0 100 200 209 300 400 505 600 203/600 204/450 205/0
SEEN-BY: 206/0 270/101 490/21 633/267 270
@PATH: 282/1 280/801 270/101 201/505 633/267

SOURCE: echomail via fidonet.ozzmosis.com

Email questions or comments to sysop@ipingthereforeiam.com
All parts of this website painstakingly hand-crafted in the U.S.A.!
IPTIA BBS/MUD/Terminal/Game Server List, © 2025 IPTIA Consulting™.