!=========================================================================
! Copyright (C) VMware, Inc. 1986-2011.  All Rights Reserved.
!
! $Id: gssocket.gs,v 1.14 2008-01-09 22:50:11 stever Exp $
!
! Superclass Hierarchy:
!   GsSocket, Object.
!
!=========================================================================

expectvalue %String
run
^ Object _newKernelSubclass: 'GsSocket'
    instVarNames: #()
    classVars: #( #SocketErrorSymbols )
    classInstVars: #()
    poolDictionaries: #()
    inDictionary: Globals
    constraints: #()
    instancesInvariant: false
    isModifiable: false 
    reservedOop: 883
%

removeallmethods GsSocket
removeallclassmethods GsSocket

category: 'For Documentation Installation only'
classmethod: GsSocket
installDocumentation

| doc txt |
doc := GsClassDocumentation newForClass: self.

txt := (GsDocText new) details:
'GsSocket provides the means for creating and binding TCP sockets through the
 operating system of the machine that is running the session''s Gem process, and
 for communicating across those sockets.  Methods that block GemStone Smalltalk
 until the socket operation completes are interruptable by a hard break.  (You
 can get a hard break in Topaz by pressing the control-C key twice.  You can get
 a hard break in GemBuilder for C by calling the GciHardBreak function, and in
 GemBuilder for Smalltalk by calling the corresponding hard break method.)

                              Warning:
    Do not retain an instance of GsSocket from one session to another.
    Instances of GsSocket are intended to exist only within a given GemStone
    session.  GsSockets that are used across sessions always generate an error'.
doc documentClassWith: txt.

txt := (GsDocText new) details:
'Methods in this category are obsolete and are provided only for compatibility
 with earlier releases of GemStone.  They will be removed in a future release.'.
doc documentCategory: #'Backward Compatibility' with: txt.

self description: doc.
%

! ------------------- Class methods for GsSocket
category: 'Drastic Measures'
classmethod: GsSocket
closeAll

"Close all instances of GsSocket that are open."

self _zeroArgClassPrim: 6 .
^ self
%

category: 'Garbage Collection'
classmethod: GsSocket
finalizeAll

"Reclaim all the low level system resources that are no longer being
 used by an instance of GsSocket. This is needed because if an instance
 of GsSocket is garbage collected before close is sent to it then the
 system resources are not reclaimed.
 The system resources that are reclaimed by this method
 are the sames ones freed by the close method.
 Returns the total number of GsSocket instances that were not finalized
 because they were still in use."

System _generationScavenge.
^ self _zeroArgClassPrim: 16
%

category: 'Backward Compatibility'
classmethod: GsSocket
getServByName: serviceName

"Obsolete in GemStone 5.0.  Use the getServiceByName: method instead."

"Returns the port number for the given service.  Returns nil if the service
 is undefined or an error occurs."

^ self getServicePortByName: serviceName
%

category: 'Queries'
classmethod: GsSocket
getServicePortByName: serviceName

"Returns the port number for the given service.  Returns nil if the service
 is undefined or an error occurs."

^ self _twoArgClassPrim: 1  with: serviceName with: nil
%

category: 'Queries'
classmethod: GsSocket
getServicePortByName: serviceName withProtocol: protocol

"Returns the port number for the given service and protocol.
 Returns nil if the service is undefined or an error occurs."

^ self _twoArgClassPrim: 1  with: serviceName with: protocol
%

category: 'Queries'
classmethod: GsSocket
getServiceNameByPort: servicePort withProtocol: protocol

"Returns the service name for the given port number and protocol.
 Returns nil if the service is undefined or an error occurs."

^ self _twoArgClassPrim: 8 with: servicePort with: protocol
%

category: 'Queries'
classmethod: GsSocket
getServiceNameByPort: servicePort

"Returns the service name for the given port number.
 Returns nil if the service is undefined or an error occurs."

^ self getServiceNameByPort: servicePort withProtocol: nil
%

category: 'Queries'
classmethod: GsSocket
getHostNameByAddress: address

"Returns the host name for the given ip address.
 Returns nil if the host is undefined or an error occurs.

 The address needs to be a String in a form acceptable to 
 the inet_addr() network function."

^ self _twoArgClassPrim: 7 with: address with: nil
%

category: 'Queries'
classmethod: GsSocket
getHostAddressByName: hostname

"Returns the ip address for the given hostname.
 Returns nil if the host is undefined or an error occurs.

 The resuling ip address is a String in a form acceptable to 
 the inet_addr() network function."

^ self _twoArgClassPrim: 12 with: hostname with: nil
%

category: 'Queries'
classmethod: GsSocket
getLocalHostName

"Returns the name of the local host or nil if an error occurs."

^ self _zeroArgClassPrim: 5
%

category: 'Queries'
classmethod: GsSocket
isAvailable

"Returns whether the supporting socket actions are available in the
 user's session."

^ true
%

category: 'Queries'
method: GsSocket
raiseExceptionOnError

"Returns true if a socket error on the receiver will raise an exception.
 Returns false if not. The initial value is false."

^ self _twoArgPrim: 18 with: nil with: nil
%

category: 'Configuration'
method: GsSocket
raiseExceptionOnError: bool

"Sets the value of raiseExceptionOnError to the Boolean bool for the receiver.
 Returns the receiver."

^ self _twoArgPrim: 18 with: bool with: nil
%

category: 'Instance Creation'
classmethod: GsSocket
new

"Returns a new socket or nil if unable to create a new socket."

^ super new _zeroArgPrim: 1
%

category: 'Instance Creation'
classmethod: GsSocket
newWithId: socketId

"Returns a new GsSocket that uses the low level socket id instead of
 creating one of its own. In most cases socketId comes from a C
 user action. The value of socketId must be representable as
 a 32 bit unsigned value.
 The instance created has disableChange set to true.
 Returns nil if unable to create a GsSocket instance.

 Note that if a GsSocket already exists whose id is socketId then
 this method will not create a new instance but instead returns
 the one that already exists."

| res |

res := self _twoArgClassPrim: 16 with: socketId with: nil.
(res == nil) ifTrue: [
  res := super new _twoArgPrim: 13 with: socketId with: nil.
].
^ res
%

category: 'Low Level Access'
classmethod: GsSocket
getStandardId: index inband: bool
"Returns the socket id for the given index. Index must be >= 0.
 If bool is true then gets the inband socket id. Otherwise gets
 the outband socket id.

 If index is 0 then the socket id to the stone is returned. Starting with Gs64 v2.2,
 the socket id to the stone will be -1 if the session is using shared memory 
 communication to the stone.

 If index is 1 then the socket id to the rpc application is returned.

 Otherwise index represents the GCI session id (see GciGetSessionId) and
 the socket id to that session is returned.
 If a socket id does not exist for the given index then -1 is returned."

^ self _twoArgClassPrim: 15 with: index with: bool
%

category: 'Accessing'
method: GsSocket
changeable: aFlag
"If aBoolean is false then if a method is invoked on the receiver that
 would cause it to change state that method will fail with the error
 symbol set to #ChangeDisabled.
 If aBoolean is true then any methods can be invoked on the receiver.
 GsSockets created with new are changable.
 Those created with newWithId: are not changable.
 Methods that result in change are those in the following categories:
   Reading, Writing, Client Operations, Server Operations, and
   Socket Operations.
 If a close or closeAll is done on a GsSocket that is not changeable
 then the instance will no longer be usable but the low level socket
 id will not be closed.
 Returns self or nil if an error occurs."

^ self _twoArgPrim: 14 with: aFlag with: nil
%

category: 'Accessing'
method: GsSocket
changeable

"Returns true if methods that would change the state of the receiver
 are allowed. Returns false if the state of the receiver can not be
 changed."

^ self _twoArgPrim: 14 with: nil with: nil
%

category: 'Error Reporting'
classmethod: GsSocket
lastErrorString

"Returns a String containing information about the last error for 
 GsSocket class methods, or nil if there is no error.
 Clears the error information for the receiver."

^ self _zeroArgClassPrim: 7
%

category: 'Error Reporting'
classmethod: GsSocket
lastErrorCode

"Returns an integer representing that last operating system error
 for GsSocket class methods. Returns zero if there is no error.
 Does not clear the error information for the receiver."

^ self _zeroArgClassPrim: 8
%

category: 'Private'
classmethod: GsSocket
_getErrorSymbol: code

"Returns a symbol from SocketErrorSymbols for the given code.
 Returns nil if no error."

"Note: socketprim.c:raiseError does a recur from om to this method."

(code == 0) ifTrue: [^nil].
(code > (SocketErrorSymbols size)) ifTrue: [^SocketErrorSymbols at: 1].

^SocketErrorSymbols at: code
%

category: 'Error Reporting'
classmethod: GsSocket
lastErrorSymbol

"Returns a Symbol representing that last operating system error
 for GsSocket class methods. Returns nil if there is no error.
 Does not clear the error information for the receiver."

^ self _getErrorSymbol: (self _zeroArgClassPrim: 21)
%

category: 'Error Reporting'
method: GsSocket
lastErrorString

"Returns the string of the last error on the receiver, or nil if no error has
 occurred.  Clears the error information for the receiver."

^ self _zeroArgPrim: 10
%

category: 'Error Reporting'
method: GsSocket
lastErrorCode

"Returns an integer representing the last operating system error on the
 receiver. Returns zero if there is no error.
 Does not clear the error information for the receiver."

^ self _zeroArgPrim: 11
%

category: 'Error Reporting'
method: GsSocket
lastErrorSymbol

"Returns a Symbol representing the last operating system error on the
 receiver. Returns nil if there is no error.
 Does not clear the error information for the receiver."

^ (self class) _getErrorSymbol: (self _zeroArgPrim: 22)
%

category: 'Private'
classmethod: GsSocket
_zeroArgClassPrim: opcode

"See _zeroArgPrim for a description of the opcode."

<primitive: 323>

^ self _primitiveFailed: #_zeroArgClassPrim:
%

category: 'Private'
classmethod: GsSocket
_twoArgClassPrim: opcode with: arg1 with: arg2

"See _twoArgPrim for a description of legal opcodes"
<primitive: 322>

^ self _primitiveFailed: #_twoArgClassPrim:with:with:
%

category: 'Private'
classmethod: GsSocket
_threeArgClassPrim: opcode with: arg1 with: arg2 with: arg3

"See _threeArgPrim for a description of the opcode."

<primitive: 489>

^ self _primitiveFailed: #_threeArgClassPrim:with:with:with:
%


category: 'Private'
method: GsSocket
_zeroArgPrim: opcode

"opcode  function
   1     instance method: SocketCreate
   2     instance method: SocketClose
   3     instance method: SocketPort
   4     class method: SocketLocalHostName
   5     class method: GetLocalHostName
   6     class method: SocketCloseAll
   7     class method: SocketErrorString
   8     class method: SocketErrno
   9     instance method: SocketPeerAddress
  10     instance method: SocketErrorString
  11     instance method: SocketErrno
  12     instance method: SocketId
  13     instance method: SocketIsConnected
  14     instance method: SocketPeerPort
  15     instance method: SocketAddress
  16     class method: GsSocketFinalizeAll
  17     instance method: GsSocketGetEventFlags
  18     instance method: SocketReadWillNotBlock
  19     instance method: SocketWriteWillNotBlock
  20     instance method: GsSocketClearEventFlags
  21     class method: lastErrorSymbolCode
  22     instance method: lastErrorSymbolCode
"

<primitive: 323>

^ self _primitiveFailed: #_zeroArgPrim:
%

category: 'Private'
method: GsSocket
_twoArgPrim: opcode with: arg1 with: arg2

"opcode  function
   1     class method: GetServiceByName
   2     instance method: SocketConnect
   3     instance method: SocketLinger
   4     instance method: SocketAccept
   5     instance method: SocketKeepAlive
   6     instance method: SocketBind
   7     class method: GetHostNameByAddress
   8     class method: GetServiceNameByPort
   9     class method: GsSocketDoPoll
  10     unused
  11     instance method: SocketMakeListener
  12     class method: GetHostAddressByName
  13     instance method: newWithId
  14     instance method: disableChange
  15     class method: GetStandardId
  16     class method: GsSocketFindById
  17     instance method: GsSocketEnable
  18     instance method: GsSocketRaiseException
  19     instance method: GsSocketSetError
"

<primitive: 322>

^ self _primitiveFailed: #_twoArgPrim:with:with:
%

category: 'Private'
method: GsSocket
_threeArgPrim: opcode with: arg1 with: arg2 with: arg3

"opcode  function
   1     instance method: SocketRead
   2     instance method: SocketWrite
"

<primitive: 489>

^ self _primitiveFailed: #_threeArgPrim:with:with:with:
%

category: 'Private'
classmethod: GsSocket
_initSocketErrorSymbols

"Initializes the array of socket error symbols."

SocketErrorSymbols := #(
  "SYSERR_UNKNOWN," #UnknownSystemError
  "SYSERR_HOST_NOT_FOUND," #HostNotFound
  "SYSERR_TRY_AGAIN," #TryAgain
  "SYSERR_NO_RECOVERY," #NoRecovery
  "SYSERR_NO_DATA," #NoData
  "SYSERR_NO_ADDRESS," #NoAddress
  "SYSERR_NETDOWN," #NetworkDown
  "SYSERR_NETUNREACH," #NetworkUnreachable
  "SYSERR_NETRESET," #NetworkReset
  "SYSERR_CONNABORTED," #ConnAborted
  "SYSERR_CONNRESET," #ConnReset
  "SYSERR_NOBUFS," #NoBuffers
  "SYSERR_ISCONN," #AlreadyConnected
  "SYSERR_NOTCONN," #NotConnected
  "SYSERR_SHUTDOWN," #Shutdown
  "SYSERR_TIMEDOUT," #TimedOut
  "SYSERR_CONNREFUSED," #ConnectionRefused
  "SYSERR_NAMETOOLONG," #NameTooLong
  "SYSERR_INTR," #Interrupted
  "SYSERR_INPROGRESS," #InProgress
  "SYSERR_INVAL," #InvalidInput
  "SYSERR_ALREADY," #AlreadyDone
  "SYSERR_NOTSOCK," #NotSocket
  "SYSERR_DESTADDRREQ," #DestinationAddressRequired
  "SYSERR_MSGSIZE," #MsgSize
  "SYSERR_PROTOTYPE," #WrongProtocol
  "SYSERR_NOPROTOOPT," #OptionNotSupported
  "SYSERR_PROTONOSUPPORT," #ProtocolNotSupported
  "SYSERR_SOCKTNOSUPPORT," #TypeNotSupported
  "SYSERR_OPNOTSUPP," #OperationNotSupported
  "SYSERR_NOTSUP," #OperationNotSupported
  "SYSERR_PFNOSUPPORT," #ProtocolFamilyNotSupported
  "SYSERR_AFNOSUPPORT," #FamilyNotSupported
  "SYSERR_ADDRINUSE," #AddressInUse
  "SYSERR_ADDRNOTAVAIL," #AddressNotAvailable
  "SYSERR_WOULDBLOCK," #WouldBlock
  "SYSERR_BADF," #NotSocket
  "SYSERR_AGAIN,"  #WouldBlock
  "SYSERR_NOMEM," #NoMemory
  "SYSERR_ACCES," #AccessDenied
  "SYSERR_FAULT," #Fault
  "SYSERR_PIPE," #BrokenPipe
  "SYSERR_HOSTDOWN," #HostDown
  "SYSERR_HOSTUNREACH," #HostUnreachable
  "SYSERR_NONET," #NoNetwork
  "SYSERR_WSANOTINITIALISED," #NotInitialized
  "SYSERR_NOSR," #NoStreams
  "SYSERR_PROTO," #ProtocolError
  "SYSERR_NXIO," #ServerExited
  "SYSERR_WSAEINTR," #BlockingCallCanceled
  "SYSERR_MFILE,"    #ProcessOutOfDescriptors
  "SYSERR_NFILE,"    #SystemOutOfDescriptors
  "GSSOCKETERR_NOHOSTNAME," #GsSocketNoHostName
).
%

category: 'Private'
method: GsSocket
_isReadable

"Returns true if the receiver is marked as being ready to read."

^(((self _zeroArgPrim: 17) bitAnd: 1) ~~ 0)
%

category: 'Private'
method: GsSocket
_enableRead

"Enables read detection by _doPoll:"

^self _twoArgPrim: 17 with: 1 with: nil
%

category: 'Private'
method: GsSocket
_enableWrite

"Enables write detection by _doPoll:"

^self _twoArgPrim: 17 with: 2 with: nil
%

category: 'Private'
method: GsSocket
_isReadableOrException

"Returns true if the receiver is marked as being ready to read
 or has an exception."

^(((self _zeroArgPrim: 17) bitAnd: 17) ~~ 0)
%

category: 'Private'
method: GsSocket
_isWritable

"Returns true if the receiver is marked as being ready to write."

^(((self _zeroArgPrim: 17) bitAnd: 2) ~~ 0)
%

category: 'Private'
method: GsSocket
_isWritableOrException

"Returns true if the receiver is marked as being ready to write
 or has an exception."

^(((self _zeroArgPrim: 17) bitAnd: 18) ~~ 0)
%

category: 'Private'
method: GsSocket
_hadException

"Returns true if the receiver is marked as having had an exception."

^(((self _zeroArgPrim: 17) bitAnd: 16) ~~ 0)
%

category: 'Private'
method: GsSocket
_clearEventFlags

  self _zeroArgPrim: 20
%

category: 'Private'
method: GsSocket
_setError: str

^self _twoArgPrim: 19 with: str with: nil
%

category: 'Private'
classmethod: GsSocket
_doPoll: resultArray timeout: msToWait

"Waits for up to msToWait milliseconds for an event to be detected
 on a GsSocket. If msToWait is -1 then waits forever.
 If msToWait is 0 then do not wait at all.
 resultArray must be an Array. It will have added to its end each
 instance of GsSocket that had an event detected on it.
 Returns true if it times out. Returns nil on an error.
 Otherwise returns resultArray."

^ self _twoArgClassPrim: 9 with: msToWait with: resultArray.
%

! ------------------- Instance methods for GsSocket
category: 'Backward Compatibility'
method: GsSocket
bind

"Obsolete in GemStone 5.0.  Use the bindTo: method instead."

^self bindTo: nil 
%

category: 'Client Operations'
method: GsSocket
bindTo: portNumber

"Binds the receiver to the specified port number.  Use the makeServer
 methods to do the bind when creating a server socket.
 If portNumber is nil then a random port is selected.
 This method is provided to bind a client socket to a specific port
 before it is connected.  Returns the port number actually
 bound to (should be the same as the argument unless argument is nil), 
 or nil if not successful."

^ self _twoArgPrim: 6 with: portNumber with: nil
%

category: 'Client Operations'
method: GsSocket
bindTo: portNumber toAddress: address

"Binds the receiver to the specified port number and address. 
 Use the makeServer methods to do the bind when creating a server socket.
 This method is provided to bind a client socket to a specific port
 and/or address before it is connected. 
 If portNumber is nil then a random port is selected.
 If address is nil then any appropriate network interface is used.
 Returns the port number actually
 bound to (should be the same as the argument unless argument is nil), 
 or nil if not successful."

^ self _twoArgPrim: 6 with: portNumber with: address
%

category: 'Socket Operations'
method: GsSocket
close

"Release any temporary system resources used by the receiver.  This includes
 closing the low level socket.  Returns self if the socket is closes
 successfully or the socket is already closed.  Returns nil if socket cannot
 be closed."
  
^ self _zeroArgPrim: 2
%

category: 'Client Operations'
method: GsSocket
connectTo: portNumber

"Connect the receiver to the server socket on the local machine
 identified by portNumber.
 Returns true if the connection succeeded and false if not."

^ self connectTo: portNumber on: (self class getLocalHostName)
%

! changes to fix 34574 , 36607
category: 'Client Operations'
method: GsSocket
connectTo: portNumber on: host

"Connect the receiver to the server socket identified by portNumber and
 host. host can be the name of the host or its address.
 Returns true if the connection succeeded and false if not."

| status |
status := self _twoArgPrim: 2 with: host with: portNumber.
status == self ifTrue:[ ^ true ].
(status == false) ifTrue: [
  "connect call has blocked, wait for it to complete"
  self writeWillNotBlockWithin: -1 .
  ^ self peerName ~~ nil .
].
^ false
%

category: 'Accessing'
method: GsSocket
option: optionName

"Returns the current value of the specified option.
 Returns nil if an error occurred.
 optionName can be any of the following:
   'KEEPALIVE': value is Boolean; detect broken connections.
   'NODELAY': value is Boolean; disables nagle algorithm for send coalescing.
   'REUSEADDR': value is Boolean; allows local address reuse.
   'RCVBUF': value is SmallInt; buffer size for input.
   'SNDBUF': value is SmallInt; buffer size for output.
   'OOBINLINE': value is Boolean; reception of out-of-band data inband.
   'DEBUG': value is Boolean; records debugging information.
   'DONTROUTE': value is Boolean; routing bypass for outgoing messages.
   'USELOOPBACK': value is Boolean; bypass network card if possible.
   'BROADCAST': value is Boolean; permission to transmit broadcast messages.
   'TYPE': value is SmallInt; type of the socket (tcp or udp).
   'ERROR': value is SmallInt; error code of the socket.

  If an option name is not supported on a particular platform the
  error string will be 'The requested socket option does not exist'."

^ self _twoArgPrim: 5 with: optionName with: nil
%

category: 'Socket Operations'
method: GsSocket
option: optionName put: value

"Sets the value of the specified option.
 Returns the receiver or nil if an error occurred.
 optionName can be any of the following:
   'CLOSEONEXEC': value is Boolean; close when system exec() is done.
   'KEEPALIVE': value is Boolean; detect broken connections.
   'NODELAY': value is Boolean; disables nagle algorithm for send coalescing.
   'REUSEADDR': value is Boolean; allows local address reuse.
   'RCVBUF': value is SmallInt; buffer size for input.
   'SNDBUF': value is SmallInt; buffer size for output.
   'OOBINLINE': value is Boolean; reception of out-of-band data inband.
   'DEBUG': value is Boolean; records debugging information.
   'DONTROUTE': value is Boolean; routing bypass for outgoing messages.
   'USELOOPBACK': value is Boolean; bypass network card if possible.
   'BROADCAST': value is Boolean; permission to transmit broadcast messages.

  If an option name is not supported on a particular platform the
  error string will be 'The requested socket option does not exist'."

^ self _twoArgPrim: 5 with: optionName with: value
%
   
category: 'Socket Operations'
method: GsSocket
keepAlive: bool

"Sets the receiver to periodically broadcast messages to clients.  If
 a client does not respond to a broadcast, its connection is severed.
 If bool is false, keepAlive is turned off.
 Returns the receiver or nil if an error occurred."

^ self option: 'KEEPALIVE' put: bool
%

category: 'Socket Operations'
method: GsSocket
linger: bool length: timeOut

"Sets up the receiver so that if unsent data is waiting to be transmitted
 at the time the receiver is closed, the current process will block until
 either the data is transmitted, or the given timeOut expires.  timeOut is
 in units of seconds.

 Returns the receiver or nil if an error occurred."

^ self _twoArgPrim: 3 with: bool with: timeOut
%

category: 'Server Operations'
method: GsSocket
makeListener: queueLength

"Turns the receiver into a listening socket.
 The queueLength argument specifies the size of the listen backlog queue for
 incoming connections.
 Returns the receiver or nil if an error occurred."

^ self _twoArgPrim: 11 with: queueLength with: nil
%

category: 'Server Operations'
method: GsSocket
makeServer

"Turns the receiver into a server socket. Binds the receiver to a port and
 makes it a listening socket.  Returns the receiver or nil if an error
 occurred."

^ self makeServer: 5 atPort: nil atAddress: nil
%

category: 'Server Operations'
method: GsSocket
makeServer: queueLength

"Turns the receiver into a server socket.  The queueLength argument specifies
 the size of the listen backlog queue for incoming connections.  Binds the
 receiver to a random port.  Returns the receiver or nil if an error occurred."

^ self makeServer: queueLength atPort: nil atAddress: nil
%

category: 'Server Operations'
method: GsSocket
makeServerAtPort: portNum

"Turns the receiver into a server socket.  Binds the receiver to portNum and
 makes it a listening socket.  Returns the receiver or nil if an error
 occurred."

^ self makeServer: 5 atPort: portNum atAddress: nil
%

category: 'Server Operations'
method: GsSocket
makeServer: queueLength atPort: portNum

"Turns the receiver into a server socket.  The queueLength argument specifies
 the size of the listen backlog queue for incoming connections.  Binds the
 receiver to portNum.  If portNum is nil then a random port is selected.
 Returns the receiver, or nil if an error occurred."

^ self makeServer: queueLength atPort: portNum atAddress: nil
%

category: 'Server Operations'
method: GsSocket
makeServer: queueLength atPort: portNum atAddress: address

"Turns the receiver into a server socket.  The queueLength argument specifies
 the size of the listen backlog queue for incoming connections.  Binds the
 receiver to portNum and address.
 If portNum is nil then a random port is selected.
 If address is nil then any appropriate network interface is used.
 Returns the receiver, or nil if an error occurred."

((self bindTo: portNum toAddress: address) == nil) ifTrue: [^nil].
^ self makeListener: queueLength
%

category: 'Server Operations'
method: GsSocket
speciesForAccept
"Returns a class, an instance of which should be used as the result
 of the accept method."

^self class
%

category: 'Server Operations'
method: GsSocket
accept

"Accept a client request for a connection on the receiver.  Returns the socket
 created for a new connection, or nil if there was some problem.
 The result is an instance of speciesForAccept.
 For example, the following code does not return until there is a connection:
   
 sock := GsSocket new.
 sock makeServer.
 newsock := sock accept.
 msg := newsock read: 512."

^ self listen: nil acceptingWith: (self speciesForAccept basicNew)
%

category: 'Backward Compatibility'
method: GsSocket
listen: queueLength

"Obsolete in GemStone 5.0."

"Listens for a connection, with the given queue limit.  Returns the socket
 created for a new connection, or nil if there was some problem.  For example,
 the following code does not return until there is a connection:
   
 sock := GsSocket new.
 sock bind.
 newsock := sock listen: 5.
 msg := newsock read: 512."

^ self listen: queueLength acceptingWith: (self speciesForAccept basicNew)
%

category: 'Backward Compatibility'
method: GsSocket
listen: queueLength  acceptingWith: aSocket

"Obsolete in GemStone 5.0."

"Listens for a connection, with the given queue limit.  Once a request
 for connection is received, acceptance is automatically performed, and
 the socket is attached to the given socket object, aSocket.  The socket
 object, aSocket, should be a closed socket, or uninitialized socket object.
   
 Does not return until there is a connection.
   
 Returns aSocket if a connection is made, or nil if an error occurs.
 If an error occurs, the error state of the receiver is updated."
  
| res |

res := true.
[res isKindOf: Boolean] whileTrue: [
  res := self _twoArgPrim: 4 with: aSocket with: queueLength.
  (res == false) ifTrue: [
    "would have blocked"
    res := self readWillNotBlockWithin: -1.
  ].
].
(res == nil) ifFalse: [
  res := aSocket.
].
^res
%

category: 'Accessing'
method: GsSocket
peerName

"For a bound socket, returns the hostname of the machine on which the 
 process at the other end of the connection is running.

 If the socket is not bound, or an error occurs, returns nil."

| addr |  

addr := self peerAddress.
(addr == nil) ifTrue: [^nil].
^ self class getHostNameByAddress: addr
%

category: 'Accessing'
method: GsSocket
peerAddress

"For a bound socket, returns the address of the machine on which the 
 process at the other end of the connection is running.

 If the socket is not bound, or an error occurs, returns nil."
  
^ self _zeroArgPrim: 9 
%

category: 'Accessing'
method: GsSocket
peerPort

"For a bound socket, returns the port being used by the
 the other end of the connection.

 If the socket is not bound, or an error occurs, returns nil."

^ self _zeroArgPrim: 14
%

category: 'Accessing'
method: GsSocket
port

"Returns the local port number of a socket, or nil if an error occurs.
 On some platforms an error is raised if the receiver is not bound.
 On others the port 0 is returned and no error is raised."
  
^ self _zeroArgPrim: 3 
%

category: 'Accessing'
method: GsSocket
address

"Returns the local ip address of a socket, or nil if an error occurs.
 On some platforms an error is raised if the receiver is not bound.
 On others the address 0.0.0.0 is returned and no error is raised."
  
^ self _zeroArgPrim: 15
%

!"Provided for compatibility with Geode."
category: 'Reading'
method: GsSocket
read: maxBytes

"This method is equivalent to readString:."

^ self readString: maxBytes
%

category: 'Reading'
method: GsSocket
read: maxBytes into: byteObj

"Reads up to the given number of bytes into the given byte object (for
 example, a String) starting at index 1.
 Returns the number of bytes read, or nil if an error occurs.

 If no data is available for reading, this blocks until data arrives.
 The readWillNotBlock or readWillNotBlockWithin: methods may be used to
 determine whether or not data is ready for reading before calling this method."

^ self read: maxBytes into: byteObj startingAt: 1  
%

category: 'Reading'
method: GsSocket
read: maxBytes into: byteObj startingAt: index

"Reads up to the given number of bytes into the given byte object (for
 example, a String).  The first byte read will go into the position
 indicated by index. Returns the number of bytes read, or nil if an error
 occurs.

 An error will be raised if index is greater than the size of byteObj.
 Note that index can be equal to the size of byteObj plus 1 in which
 case any bytes read will be appended to byteObj.

 If no data is available for reading, this blocks until data arrives.
 The readWillNotBlock or readWillNotBlockWithin: methods may be used to
 determine whether or not data is ready for reading before calling this method."

| res |

res := true.
[res isKindOf: Boolean] whileTrue: [
  res := self _threeArgPrim: 1 with: byteObj with: index with: maxBytes.
  (res == false) ifTrue: [
    "would have blocked"
    res := self readWillNotBlockWithin: -1.
  ].
].
^res
%

category: 'Reading'
method: GsSocket
readString: amount untilFalse: aBlock

"Reads bytes from the receiver into a String which it creates and returns.
 Will keep reading until amount bytes are read or 
 the one argument Block  aBlock  returns false.
 Each time aBlock is evaluated it is given the number of bytes read so far.
 aBlock is only evaluated after a partial read.
 Returns the String or nil if an error occurs."

| bytes |

bytes := String new.
((self read: amount into: bytes untilFalse: aBlock) == nil) ifTrue: [
  ^nil
].

^bytes
%

category: 'Reading'
method: GsSocket
read: amount untilFalse: aBlock

"Same as readString:untilFalse:."

^ self readString: amount untilFalse: aBlock.
%

category: 'Reading'
method: GsSocket
read: amount into: byteObj untilFalse: aBlock

"Reads at most amount bytes from the receiver into the given byte object
 starting at index 1.
 Returns when amount bytes have been read, an error occurs, or 
 the one argument Block  aBlock   evaluates to false.

 See read:into:startingAt:untilFalse: for details.

 If aBlock is evaluated its argument is the total number of bytes read
 so far. Returns the number of bytes read, or nil if an error occurs."

^ self read: amount into: byteObj startingAt: 1 untilFalse: aBlock.
%

category: 'Reading'
method: GsSocket
read: amount into: byteObj startingAt: index untilFalse: aBlock

"Reads at most amount bytes from the receiver into byteObj.
 The first byte read is put in byteObj at position specified by index.
 Returns when amount bytes have been read, an error occurs, or 
 the one argument Block   aBlock   evaluates to false.

 An error will be raised if index is greater than the size of byteObj.
 Note that index can be equal to the size of byteObj plus 1 in which
 case any bytes read will be appended to byteObj.

 Repeatedly does a read followed by an evaluation of aBlock if the
 read returned without amount bytes having been read.

 If the receiver is not ready for reading, this blocks until it is ready.
 The readWillNotBlock or readWillNotBlockWithin: methods may be used to
 determine whether or not data is ready for reading before calling this method.
 They can also be used in aBlock to determine when it should return true
 so that another read will be done without blocking.

 If aBlock is evaluated its argument is the total number of bytes read
 so far. Returns the number of bytes read, or nil if an error occurs."

| total result offset numToRead |

total := 0.
offset := index.
numToRead := amount.

[result := self read: numToRead into: byteObj startingAt: offset.
 (result == nil) ifTrue: [^nil]. "Quit due to error"
 total := total + result.
 (total == amount) ifTrue: [^total]. "All done"
 numToRead := numToRead - result.
 offset := offset + result.
 aBlock value: total.
] untilFalse.

^total
%

category: 'Testing'
method: GsSocket
isActive
"Returns true if the receiver's socket is in a usable state.
 Returns false if the receiver's socket is not usable.
 Sockets become unusable when they are closed."

((self id) == -1) ifTrue: [^false].
^ true
%

category: 'Testing'
method: GsSocket
isConnected
"Returns true if the socket is connected to a peer.
 Returns false if the socket never was connected or has lost its connection
 to the peer and had no more data to read.
 Returns nil if an error occurs."

^ self _zeroArgPrim: 13.
%

category: 'Testing'
method: GsSocket
readWillNotBlock

"Returns true if the socket is currently ready to receive input without
 blocking.  Returns false if it is not currently ready.  Returns nil if an error
 occurs.

 The receiver must already be connected for this method to work properly.  If it
 is not connected, then the value that this method returns is indeterminate.
 Use the peerName method to determine if the receiver is connected.

 Call this method to prevent subsequent read or accept operations from hanging.
 If it returns true for a connected socket, then the input operation will not
 hang.  However, a return value of true is no guarantee that the operation
 itself will succeed."
  
^ self _zeroArgPrim: 18
%

category: 'Testing'
method: GsSocket
readWillNotBlockWithin: msToWait

"Returns true if the socket is ready to receive input without blocking within
 msToWait milliseconds from the time that this method is called.  Returns false
 if it is not ready after msToWait milliseconds.  Returns nil if an error
 occurs.

 If msToWait is 0, then this method reports the current readiness of the
 receiver.  If msToWait is -1, then this method never returns false, but waits
 until the receiver is ready to receive input without blocking, and then returns
 true.

 The receiver must already be connected for this method to work properly.  If it
 is not connected, then the value that this method returns is indeterminate.
 Use the peerName method to determine if the receiver is connected.

 Call this method to prevent subsequent read or accept operations from hanging.
 If it returns true for a connected socket, then the input operation will not
 hang.  However, a return value of true is no guarantee that the operation
 itself will succeed."

(msToWait == 0) ifTrue: [^ self readWillNotBlock].

(self _isReadableOrException) ifTrue: [^true].
^ ProcessorScheduler scheduler _waitUntilReadable: self timeout: msToWait
%

category: 'Backward Compatibility'
method: GsSocket
readReady

"Obsolete in GemStone 5.0.  Use the readWillNotBlock or readWillNotBlockWithin:
 method instead."

^ self readWillNotBlockWithin: 0
%

category: 'Reading'
method: GsSocket
readString: maxBytes

"Reads up to the given number of bytes, returning them in a String whose size is
 between 0 and maxBytes inclusive.  If an error occurs, nil is returned instead.

 If maxBytes is greater than the size of the operating system's buffer for 
 the socket, the size of the result string may be a function of this
 buffer size, even if more data is available from the sender.  Repeated
 invocation of this method may be necessary to obtain all of the data.

 For optimum performance, maxBytes should be <= 8192, and should typically be
 4096.
   
 If no data is available for reading, this blocks until data arrives.   
 The readWillNotBlock or readWillNotBlockWithin: methods may be used to
 determine whether or not data is ready for reading before calling this method."
 
| bytes amount |
bytes := String new.

amount := self read: maxBytes into: bytes startingAt: 1.
amount == nil ifTrue: [
  ^nil
].
^bytes
%

category: 'Writing'
method: GsSocket
write: byteObj

"Write out the given byte object.  Returns the number of bytes written,
 or nil if an error occurs.
  
 If the receiver is not ready for writing, this blocks until it is ready.
 The writeWillNotBlock or writeWillNotBlockWithin: methods may be used to
 determine whether or not data is ready for writing before calling this method."

^ self write: (byteObj size) from: byteObj startingAt: 1
%

category: 'Writing'
method: GsSocket
write: byteObj startingAt: index

"Write bytes from byteObj to the receiver.
 The first byte written will be from the position indicated by index.
 All of the bytes after it in byteObj will be written.
 Returns the number of bytes written, or nil if an error occurs.
  
 If the receiver is not ready for writing, this blocks until it is ready.
 The writeWillNotBlock or writeWillNotBlockWithin: methods may be used to
 determine whether or not data is ready for writing before calling this method."

^ self write: ((byteObj size) - (index - 1)) from: byteObj startingAt: index
%

category: 'Writing'
method: GsSocket
write: byteObj untilFalse: aBlock

"Writes the entire contents of byteObj to the receiver unless
 an error occurs or aBlock returns false.
 See write:from:startingAt:untilFalse: for details.
 Returns the number of bytes actually written."

^ self write: (byteObj size) from: byteObj startingAt: 1 untilFalse: aBlock.
%

category: 'Writing'
method: GsSocket
write: byteObj startingAt: index untilFalse: aBlock

"Writes the contents of byteObj to the receiver unless
 an error occurs or aBlock returns false.
 The first byte written from byteObj is at the position specified by index.
 All of the bytes after it in byteObj will be written.
 See write:from:startingAt:untilFalse: for details.
 Returns the number of bytes actually written."

^ self write: ((byteObj size) - (index -1)) from: byteObj startingAt: index
       untilFalse: aBlock.
%

category: 'Writing'
method: GsSocket
write: amount from: byteObj

"Write the given number of bytes from the given byte object.  Returns the
 number of bytes written, or nil if an error occurs.
  
 If the receiver is not ready for writing, this blocks until it is ready.
 The writeWillNotBlock or writeWillNotBlockWithin: methods may be used to
 determine whether or not data is ready for writing before calling this method."

^ self write: amount from: byteObj startingAt: 1
%

category: 'Writing'
method: GsSocket
write: amount from: byteObj startingAt: index

"Write bytes from byteObj to the receiver.
 The first byte written will be from the position indicated by index.
 The number of bytes written is specified by amount.
 It is in an error if amount bytes are not available.
 Returns the number of bytes written, or nil if an error occurs.
  
 If the receiver is not ready for writing, this blocks until it is ready.
 The writeWillNotBlock or writeWillNotBlockWithin: methods may be used to
 determine whether or not data is ready for writing before calling this method."
  
^self write: amount from: byteObj startingAt: index 
      untilFalse: [ :total |
  	  self writeWillNotBlockWithin: -1.
          true
        ]
%

category: 'Writing'
method: GsSocket
nbwrite: amount from: byteObj startingAt: index

"Write at most amount bytes from byteObj to the receiver without blocking.
 The first byte written will be from the position indicated by index.
 The maximum number of bytes to write is specified by amount.
 It is an error if amount bytes are not available.
 Returns the number of bytes written, or nil if an error occurs."

| res |

res := true.
[res == true] whileTrue: [
  res := self _threeArgPrim: 2 with: byteObj with: index with: amount.
  (res == false) ifTrue: [
    "would have blocked"
    res := 0.
  ].
].
^res
%

category: 'Writing'
method: GsSocket
write: amount from: byteObj startingAt: index untilFalse: aBlock

"Write bytes from byteObj to the receiver until amount bytes have
 been written, an error occurs, or aBlock's value is false.
 The first byte written will be from the position indicated by index.
 The number of bytes written is specified by amount.
 It is in an error if amount bytes are not available.
 Returns the number of bytes written, or nil if an error occurs.
 This method repeatedly tries to write as many bytes as it can
 and then, if more bytes still need to be written, evaluates aBlock.

 If the receiver is not ready for writing the one argument Block
 aBlock is called.
 The writeWillNotBlock or writeWillNotBlockWithin: methods may be used
 in aBlock to determine when it should return true so that another write
 will be done without blocking.
 Every time aBlock is evaluated its argument is the total number of bytes
 written so far.

 Implementation uses nbwrite, so unless aBlock uses 
 writeWillNotBlockWithin:-1 or other logic based on writeWillNotBlock ,
 to wait for the socket to be ready before returning false from
 aBlock, this method will run hot.  "

| total offset numToWrite |

total := 0.
offset := index.
numToWrite := amount.

[ | result |
 result := self nbwrite: numToWrite from: byteObj startingAt: offset.
 (result == nil) ifTrue: [^nil]. "Quit due to error"
 total := total + result.
 (total == amount) ifTrue: [^total]. "All done"
 numToWrite := numToWrite - result.
 offset := offset + result.
 aBlock value: total.
] untilFalse.

^total
%

category: 'Testing'
method: GsSocket
writeWillNotBlock

"Returns true if the socket is currently ready to take output without blocking.
 Returns false if it is not currently ready.  Returns nil if an error occurs.

 The receiver must already be connected for this method to work properly.  If it
 is not connected, then the value that this method returns is indeterminate.
 Use the peerName method to determine if the receiver is connected.

 Call this method to prevent subsequent write operations from hanging.  If it
 returns true for a connected socket, then a subsequent write will not hang.
 However, a return value of true is no guarantee that the write operation itself
 will succeed."
  
^ self _zeroArgPrim: 19
%

category: 'Testing'
method: GsSocket
writeWillNotBlockWithin: msToWait

"Returns true if the socket is ready to take output without blocking within
 msToWait milliseconds from the time that this method is called.  Returns false
 if it is not ready after msToWait milliseconds.  Returns nil if an error
 occurs.

 If msToWait is 0, then this method reports the current readiness of the
 receiver.  If msToWait is -1, then this method never returns false, but waits
 until the receiver is ready to take output without blocking, and then returns
 true.

 The receiver must already be connected for this method to work properly.  If it
 is not connected, then the value that this method returns is indeterminate.
 Use the peerName method to determine if the receiver is connected.

 Call this method to prevent subsequent write operations from hanging.  If it
 returns true for a connected socket, then a subsequent write will not hang.
 However, a return value of true is no guarantee that the write operation itself
 will succeed."
  
(msToWait == 0) ifTrue: [^ self writeWillNotBlock].

(self _isWritableOrException) ifTrue: [^true].
^ ProcessorScheduler scheduler _waitUntilWritable: self timeout: msToWait
%

category: 'Backward Compatibility'
method: GsSocket
writeReady

"Obsolete in GemStone 5.0.  Use the writeWillNotBlock or
 writeWillNotBlockWithin: method instead."

^ self writeWillNotBlockWithin: 0
%

category: 'Comparing'
method: GsSocket
hash

"Returns a SmallInteger related to the value of the receiver.  If two instances
 of GsSocket are equal (as compared by the = method), then they must have the
 same hash value."

^ self id hash
%

category: 'Comparing'
method: GsSocket
= aSocket

"Returns true if the receiver and aSocket represent the same operating system
 socket.  Returns false otherwise."

| idSelf |
(self == aSocket) ifTrue: [^true].
aSocket == nil ifTrue: [^false].
(aSocket isKindOf: self class) ifFalse: [^false].
idSelf := self id.
(idSelf == -1) ifTrue: [^false].
^idSelf == (aSocket id)
%

category: 'Accessing'
method: GsSocket
id

"Returns the value of the low level socket.  If no low level socket exists,
 returns -1."

^ self _zeroArgPrim: 12
%

category: 'Comparing'
method: GsSocket
~= aSocket

"Returns false if the receiver and aSocket represent the same operating system
 socket.  Returns true otherwise."

^ (self = aSocket) not
%

category: 'Examples'
classmethod: GsSocket
clientExample 

^ self clientExample: true usingPort: 57785
%

category: 'Examples'
classmethod: GsSocket
clientExample: logToGciClient usingPort: portNum

"logToGciClient is a Boolean, true if GsFile-based logging is to be done to
  the RPC Gci client, false if to the server process's log file .
  Use false if executing via topaz 'nbrun'  .

 This client will connect to a server created with serverExample, and read
 the string object that the server sends.
   
 Creates a socket, connect it to port portNum, read a string, close the
 socket, and check the resulting object.  Returns true if successful.
   
 The server should already be listening for connections when this method is
 invoked."

| socket dataString dataObj chunk firstChunk |

socket := GsSocket new.
(socket connectTo: portNum) ifFalse:[ 
  socket close .
  self halt: 'Unable to connect to port ' , portNum asString , '. Wait 30 seconds or so, then ensure serverExample is started first.' 
  ].
dataString := String new .

firstChunk := true .
[socket readWillNotBlockWithin: 5000] whileFalse: [ 
  GsFile gciLog: 'Waiting for server to write...' onClient: logToGciClient .
  ].
GsFile gciLog: 'Got something from the server to read.' onClient: logToGciClient .

[
  chunk := socket readString: 4000 "max bytes".
  firstChunk ifTrue:[
    firstChunk := false .
    chunk size <= 0 ifTrue:[ self halt: 'readWillNotBlock disagrees with read'].
    ] .
  chunk == nil ifTrue:[ self halt: 'Error in reading from socket' ].
  dataString addAll: chunk .
  (chunk at: chunk size) == (Character withValue: 0)  "until null terminator"
  ] untilTrue .
  
socket close.
dataString size: (dataString size - 1) "strip null terminator" .
dataObj := (PassiveObject newWithContents: dataString) activate.
dataObj size == 5000 ifFalse:[ self halt: 'bad size of dataObj' ].
1 to: 5000 do:[:j | 
  (dataObj at:j) == j ifFalse:[ self halt: 'invalid contents in dataObj' ].
  ] .
^ true. 
%

category: 'Examples'
classmethod: GsSocket
serverExample

^ self serverExample: true usingPort: 57785
%

category: 'Examples'
classmethod: GsSocket
serverExample: logToGciClient usingPort: portNum

"logToGciClient is a Boolean, true if GsFile-based logging is to be done to
  the RPC Gci client, false if to the server process's log file . 
  Use false if executing via topaz 'nbrun'  .

 Creates a socket, binds it to port portNum, and waits for a connection.
 When a connection is established, sends some data to the client, and
 closes the connection.  Returns true if successful.

 You will need two GemStone sessions running from two independent
 interface processes to run both this and the clientExample.  The Gem
 processes for the two sessions must be on the same machine. (For
 example two Topaz sessions.)
   
 Warning: This method will cause your current session to hang until a 
 connection is established."

| server client dataObj dataString numWritten dataStream errStr |

server := GsSocket new.

(server makeServerAtPort: portNum) == nil ifTrue: [
  errStr := server lastErrorString.
  server close.
  self halt: errStr.
  ].

server port == portNum ifFalse:[ self halt: 'bad port number' ] .

[server readWillNotBlockWithin: 5000] whileFalse: [
  GsFile gciLog: 'Waiting for GsSocket clientExample to connect...' 
	onClient: logToGciClient
  ].
GsFile gciLog: 'Client connected.' onClient: logToGciClient .

client := server accept.
client == nil ifTrue: [
  errStr := server lastErrorString.
  server close.
  self halt: errStr.
].
client linger: true length: 10.  "wait after closing until data is processed"

dataObj := Array new:5000 .
1 to: 5000 do:[:j | dataObj at:j put: j ].
dataString := String new .
dataStream := WriteStream on: dataString . 
PassiveObject passivate: dataObj toStream: dataStream .
dataString add: (Character withValue: 0). "null terminate so client knows
                                           when to stop reading"
client writeWillNotBlock ifFalse:[ self halt: 'socket write will block' ].
numWritten := client write: dataString .
numWritten == dataString size ifFalse:[ self halt: 'error writing to socket'].
client close.
server close .
				"deleted   'reduce garbage' code"
^ true
%

expectvalue true
run
GsSocket _initSocketErrorSymbols.
^true
%
