"======================================================================
|
|   Smalltalk TCP/IP sockets - IPAddress class
|
|   $Revision: 1.7.5$
|   $Date: 2000/05/28 16:56:52$
|   $Author: pb$
|
 ======================================================================"


"======================================================================
|
| Copyright 1988-92, 1994-95, 1999, 2000 Free Software Foundation, Inc.
| Written by Steve Byrne and Paolo Bonzini.
|
| This file is part of the GNU Smalltalk class library.
|
| The GNU Smalltalk class library is free software; you can redistribute it
| and/or modify it under the terms of the GNU Lesser General Public License
| as published by the Free Software Foundation; either version 2.1, or (at
| your option) any later version.
| 
| The GNU Smalltalk class library is distributed in the hope that it will be
| useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
| General Public License for more details.
| 
| You should have received a copy of the GNU Lesser General Public License
| along with the GNU Smalltalk class library; see the file COPYING.LESSER.
| If not, write to the Free Software Foundation, 59 Temple Place - Suite
| 330, Boston, MA 02111-1307, USA.  
|
 ======================================================================"


Object subclass: #IPAddress
       instanceVariableNames: 'name address'
       classVariableNames: 'AnyLocalAddress LocalHostName LoopbackHost UnknownAddress Cache'
       poolDictionaries: ''
       category: 'Sockets-Protocols'
! 

CStruct newStruct: #CSockAddrStruct declaration: #( 
	(sinFamily short)
	(sinPort (array byte 2))
	(sinAddr (array byte 4))
	(sinZero (array byte 8))
)!


!IPAddress class methodsFor: 'initialization'!

flush
    | localAddrBytes |
    LocalHostName := self primLocalName.

    self
	primAnyLocalAddress: LocalHostName
	in: (localAddrBytes := ByteArray new: 4).
    AnyLocalAddress := IPAddress fromBytes: localAddrBytes.

    LoopbackHost := IPAddress fromBytes: #[127 0 0 1].
    LoopbackHost name: LocalHostName.

    UnknownAddress := IPAddress fromBytes: #[0 0 0 0].
    UnknownAddress name: '0.0.0.0'.

    Cache := Dictionary new.
!

onStartup
    self flush
! !

!IPAddress class methodsFor: 'constants'!

addressSize
    ^4
!

anyLocalAddress
    ^AnyLocalAddress
!

localHostName
    ^LocalHostName
!

loopbackHost
    ^LoopbackHost		"127.0.0.1"
!

unknownAddress
    ^UnknownAddress		"0.0.0.0"
!

version
    ^4
! !

!IPAddress class methodsFor: 'host name lookup'!

allByName: aString
    | host |
    host := aString asLowercase.
    (self isDigitAddress: host) ifTrue: [
	^Cache at: host ifAbsentPut: [ Array with: (self fromString: host) ]
    ].

    ^Cache at: host ifAbsentPut: [
	| result index addresses addr |
	result := self lookupAllHostAddr: host.

	result address = 0 ifTrue: [
	    addresses := WriteStream on: (Array new: 1).
	    result := result castTo: CByteType.
	    index := 0.
	    [   addr := self
		    with: (result at: index) value
		    with: (result at: index + 1) value
		    with: (result at: index + 2) value
		    with: (result at: index + 3) value.

		index := index + 4.
		addr = UnknownAddress
	    ]   whileFalse: [ addresses nextPut: addr ].
	    result free.
	    addresses := addresses contents.
	].
	addresses
    ]
!

byName: aString
    | all |
    aString isEmpty ifTrue: [ ^LoopbackHost ].
    all := self allByName: aString.
    ^all isNil ifTrue: [ nil ] ifFalse: [ all anyOne ]
! !

!IPAddress class methodsFor: 'instance creation'!

fromBytes: aByteArray
    ^self basicNew
	address: (aByteArray copy makeReadOnly: true)
!

fromSockAddr: aByteArray port: portAdaptor
    | s |
    s := CSockAddrStruct sizeof.
    portAdaptor value:
	(aByteArray at: s - 13) * 256 + (aByteArray at: s - 12).
    
    ^self fromBytes: (aByteArray copyFrom: s - 11 to: s - 8)
!

fromString: aString
    | substrings |
    substrings := aString substrings: $. .
    substrings := substrings collect: [ :each | each asInteger ].
    ^self fromArray: substrings
!

fromArray: parts
    | result last |
    result := ByteArray new: 4.

    "e.g. 2 parts (a.b): byte 1 are taken from a and b; byte 
     4 and 3 are bits 0-7 and 8-15 of c respectively; byte 2 is
     whatever remains (bits 16-23 is the string is well-formed).
     Handling (result at: parts size) specially simplifies
     error checking."

    1 to: parts size - 1 do: [ :i |
	result at: i put: (parts at: i) asInteger
    ].
    last := (parts at: parts size) asInteger.
    result size to: parts size + 1 by: -1 do: [ :i |
	result at: i put: last \\ 256.
	last := last // 256.
    ].
    result at: parts size put: last.
    ^self fromBytes: result
!

new
    self shouldNotImplement
!

with: b1 with: b2 with: b3 with: b4
    ^self basicNew address:
	((ByteArray with: b1 with: b2 with: b3 with: b4) makeReadOnly: true)
! !

!IPAddress class methodsFor: 'private'!

isDigitAddress: aString
    | dots |
    dots := 0.
    (aString substrings: $.) do: [ :part |
	dots := dots + 1.
	(part allSatisfy: [ :each | each isDigit ])
	    ifFalse: [ ^false ].

	part asInteger > 255 ifTrue: [ ^false ].
    ].
    ^dots = 4
! !


!IPAddress methodsFor: 'accessing'!

= anIPAddress
    "The host name is not checked. See name to understand why!"
    ^(self class == anIPAddress class) and: [
	self asByteArray = anIPAddress asByteArray
    ]
!

asByteArray
    ^address
!

addressClass
    | net |
    net := address at: 1.
    net < 128 ifTrue: [ ^$A ].
    net < 192 ifTrue: [ ^$B ].
    net < 224 ifTrue: [ ^$C ].
    ^net < 240 ifTrue: [ $D ] ifFalse: [ $E ]
!

hash
    ^address hash
!

host
    | net |
    net := address at: 1.
    net < 128 ifTrue: [
	^(address at: 4) + ((address at: 3) * 256) + ((address at: 2) * 65536)
    ].
    net < 192 ifTrue: [
	^(address at: 4) + ((address at: 3) * 256)
    ].
    ^address at: 4
!

network
    | net |
    net := address at: 1.
    net < 128 ifTrue: [ ^net ].
    net < 192 ifTrue: [	^net * 256 + (address at: 2) ].
    ^net * 65536 + ((address at: 2) * 256) + (address at: 2)
!

subnet
    | net |
    net := address at: 1.
    net < 128 ifTrue: [ ^(address at: 2) ].
    net < 192 ifTrue: [	^(address at: 3) ].
    ^0
!

subnetHost
    ^(address at: 1) < 128
        ifTrue: [ ((address at: 3) * 256) + (address at: 4) ]
        ifFalse: [ address at: 4 ]
!

isMulticast
    ^(address at: 1) between: 224 and: 239
    "^self addressClass == $D"
!

name
    | addresses |
    name isNil ifFalse: [ ^name ].
    name := self class primName: self asByteArray.

    "No DNS active..."
    name isNil ifTrue: [ ^name := self printString ].

    addresses := Cache at: name ifAbsentPut: [ Array with: self ].

    addresses do: [ :each |
	each getName isNil ifTrue: [ each name: name ].
	(each = self and: [ each getName ~= name ]) ifTrue: [
	    "Seems like someone's joking with the DNS server
	     and changed this host's IP address even though the
	     name stays the same. Don't cache the name and don't
	     even give away an alphanumeric name"
	    ^name := self printString
	].
    ].
    ^name
!

port: port
    "Return a ByteArray containing a struct sockaddr for the given port
     on the IP address represented by the receiver. Family = AF_INET."

    ^(ByteArray new: CSockAddrStruct sizeof)

	"Write sin_addr"
	replaceFrom: CSockAddrStruct sizeof - 11
	to: CSockAddrStruct sizeof - 8
	with: address
	startingAt: 1;

	"Write sin_family = AF_INET in host order"
	shortAt: 1 put: self class afInet;

	"Write sin_port in network order (big endian)"
	at: CSockAddrStruct sizeof - 13 put: port // 256;
	at: CSockAddrStruct sizeof - 12 put: (port bitAnd: 255);

	"ouf..."
	yourself
! !

!IPAddress methodsFor: 'printing'!

printOn: aStream

    (address at: 1) printOn: aStream.
    2 to: address size do: [ :i |
	aStream nextPut: $.; print: (address at: i) 
    ].
! !

!IPAddress methodsFor: 'private'!

address: aByteArray
    address := aByteArray
!

getName
    ^name
!

name: newName
    name := newName
! !

