DIRECTORY Allocator USING [NHeaderP, NormalHeader], Basics USING [BITAND, bytesPerWord, LowHalf], BasicTime USING [GetClockPulses], Booting USING [switches], Checksum USING [ComputeChecksum], CommBuffer USING [Direction, Encapsulation], CommBufferExtras USING [gapNoList], CommDriver USING [AllocBuffer, Buffer, BufferObject, FreeBuffer, GetNetworkChain, InsertReceiveProc, Network], DebuggerSwap USING [CallDebugger], Endian USING [bytesPerHWord, FFromCard, FWORD, HWORD], PrincOpsUtils USING [LongCopy], Process USING [Detach, DisableTimeout, EnableAborts, MsecToTicks, Pause, priorityBackground, SecondsToTicks, SetPriority, SetTimeout, Ticks], Pup USING [Address, allHosts, allNets, Host, nullAddress, nullHost, nullNet, nullSocket, Socket], PupBuffer USING [abortOverheadBytes, Buffer, BufferObject, ByteAlloc, ByteIndex, errorOverheadBytes, FWordIndex, HWordIndex, noChecksum, RoundUpForChecksum, WordsWithoutChecksum], PupInternal USING [Route], PupName USING [], PupSocket USING [dontWait, Milliseconds, waitForever], PupSocketBackdoor USING [ReceiveProc], PupType USING [bytesInPupHeader, bytesOfPupOverhead, CardFromSocket, ErrorCode, HeaderWithoutChecksum, SocketFromCard], PupWKS USING [rpc], RefText USING [ObtainScratch, ReleaseScratch], Rope USING [Fetch, FromRefText, Length, ROPE], SafeStorage USING [EnableFinalization, EstablishFinalization, FinalizationQueue, FQEmpty, FQNext, NewFQ]; PupSocketImpl: CEDAR MONITOR LOCKS socket USING socket: Socket IMPORTS Basics, BasicTime, Booting, Checksum, CommDriver, DebuggerSwap, Endian, PrincOpsUtils, Process, PupBuffer, PupInternal, PupType, RefText, Rope, SafeStorage EXPORTS PupInternal, PupName, PupSocket, PupSocketBackdoor = { Buffer: TYPE = PupBuffer.Buffer; Direction: TYPE = CommBuffer.Direction; ErrorCode: TYPE = PupType.ErrorCode; FWORD: TYPE = Endian.FWORD; HeaderWithoutChecksum: TYPE = PupType.HeaderWithoutChecksum; HWORD: TYPE = Endian.HWORD; Milliseconds: TYPE = PupSocket.Milliseconds; Network: TYPE = CommDriver.Network; ROPE: TYPE = Rope.ROPE; ReceiveProc: TYPE = PupSocketBackdoor.ReceiveProc; gapNoList: [0..16384) ~ CommBufferExtras.gapNoList; gapSocket: [0..16384) ~ 10; -- b.ovh.gap=gapSocket while b is on the socket queue bytesInPupHeader: NAT = PupType.bytesInPupHeader; bytesOfPupOverhead: NAT = PupType.bytesOfPupOverhead; bytesPerWord: NAT = Basics.bytesPerWord; bytesPerHWord: NAT = Endian.bytesPerHWord; outOfLineChecksum: BOOL _ TRUE; OutOfLineChecksum: PROC [ cs: WORD _ 0, nWords: CARDINAL, p: LONG POINTER] RETURNS [checksum: WORD] = TRUSTED { RETURN[Checksum.ComputeChecksum[cs, nWords, p]]; }; globalLock: Socket _ NEW[Object]; Socket: TYPE = REF Object; Object: PUBLIC TYPE = MONITORED RECORD [ local, remote: Pup.Address, sendBuffersInUse: CARDINAL _ 0, sendBuffersAlloc: CARDINAL, recvBuffersInUse: CARDINAL _ 0, recvBuffersAlloc: CARDINAL, sendChecksum: BOOL _ TRUE, recvChecksum: BOOL _ TRUE, dead, noErrors: BOOL _ FALSE, dontWait: BOOL _ FALSE, waitForInput: CONDITION, waitForSendBuffer: CONDITION, waitForRecvBuffer: CONDITION, routing: Routing _ NIL, -- NIL => no cached route firstInput, lastInput: Buffer _ NIL, next: Socket _ NIL, -- Only used when a server socket gets captured proc: ReceiveProc _ NIL, user: REF ANY ]; Routing: TYPE = REF RoutingInfo; RoutingInfo: TYPE = RECORD [ network: Network, immediate: Pup.Host, encap: CommBuffer.Encapsulation ]; RealDriverBuffer: PROC [b: Buffer] RETURNS [CommDriver.Buffer] = TRUSTED INLINE { nhp: Allocator.NHeaderP _ LOOPHOLE[b, Allocator.NHeaderP]-SIZE[Allocator.NormalHeader]; nhp.type _ CODE[CommDriver.BufferObject]; b.ovh.direction _ none; b.ovh.socket _ NIL; RETURN[LOOPHOLE[b]]; }; TempDriverBuffer: PROC [b: Buffer] RETURNS [CommDriver.Buffer] = TRUSTED INLINE { RETURN[LOOPHOLE[b]]; }; RealPupBuffer: PROC [b: CommDriver.Buffer, d: Direction] RETURNS [Buffer] = TRUSTED INLINE { nhp: Allocator.NHeaderP _ LOOPHOLE[b, Allocator.NHeaderP]-SIZE[Allocator.NormalHeader]; nhp.type _ CODE[PupBuffer.BufferObject]; b.ovh.next _ NIL; b.ovh.direction _ d; RETURN[LOOPHOLE[b]]; }; Next: PROC [b: Buffer] RETURNS [Buffer] = TRUSTED INLINE { RETURN[LOOPHOLE[b.ovh.next]]; }; SocketNotWellKnown: PUBLIC ERROR = CODE; IsWellKnown: PUBLIC PROC [local: Pup.Socket] RETURNS [yes: BOOL] = { socketNumber: LONG CARDINAL = PupType.CardFromSocket[local]; IF local = Pup.nullSocket THEN RETURN[FALSE]; IF socketNumber >= maxWellKnownSockets THEN RETURN[FALSE]; RETURN[TRUE]; }; CreateServer: PUBLIC PROC [ local: Pup.Socket, sendBuffers: NAT _ 1, recvBuffers: NAT _ 5, getTimeout: Milliseconds _ 10000 ] RETURNS [socket: Socket] = { socketNumber: LONG CARDINAL = PupType.CardFromSocket[local]; IF local = Pup.nullSocket THEN ERROR SocketNotWellKnown; IF socketNumber >= maxWellKnownSockets THEN ERROR SocketNotWellKnown; socket _ CreateCommon[local, Pup.nullAddress, sendBuffers, recvBuffers, getTimeout]; }; CreateEphemeral: PUBLIC PROC [ remote: Pup.Address, sendBuffers: NAT _ 1, recvBuffers: NAT _ 5, getTimeout: Milliseconds _ 10000 ] RETURNS [socket: Socket] = { socket _ CreateCommon[Pup.nullSocket, remote, sendBuffers, recvBuffers, getTimeout]; }; CreateCommon: PROC [ local: Pup.Socket, remote: Pup.Address, sendBuffers: NAT, recvBuffers: NAT, getTimeout: Milliseconds ] RETURNS [socket: Socket] = { network: Network; socket _ NEW [Object _ [ local: [Pup.nullNet, Pup.nullHost, local], remote: remote, sendBuffersAlloc: sendBuffers, recvBuffersAlloc: recvBuffers ] ]; network _ PupInternal.Route[remote].network; IF network = NIL THEN network _ CommDriver.GetNetworkChain[]; IF network # NIL THEN { socket.local.net _ network.pup.net; socket.local.host _ network.pup.host; }; TRUSTED { Process.EnableAborts[@socket.waitForInput]; Process.EnableAborts[@socket.waitForSendBuffer]; Process.EnableAborts[@socket.waitForRecvBuffer]; }; SetGetTimeout[socket, getTimeout]; CreateInner[globalLock, socket]; SafeStorage.EnableFinalization[socket]; }; socketsCreated: INT _ 0; CreateInner: ENTRY PROC [socket: Socket, new: Socket] = { ENABLE UNWIND => NULL; AddSocket[new]; socketsCreated _ socketsCreated.SUCC; }; SetRemoteAddress: PUBLIC PROC [socket: Socket, remote: Pup.Address] = { socket.remote _ remote; }; GetRemoteAddress: PUBLIC PROC [socket: Socket] RETURNS [Pup.Address] = { RETURN[socket.remote]; }; GetLocalAddress: PUBLIC PROC [socket: Socket] RETURNS [Pup.Address] = { RETURN[socket.local]; }; SetGetTimeout: PUBLIC PROC [socket: Socket, ms: Milliseconds] = { socket.dontWait _ FALSE; SELECT ms FROM PupSocket.dontWait => socket.dontWait _ TRUE; PupSocket.waitForever => TRUSTED { Process.DisableTimeout[@socket.waitForInput]; }; < CARDINAL.LAST => TRUSTED { Process.SetTimeout[@socket.waitForInput, Process.MsecToTicks[ms] ]; }; ENDCASE => TRUSTED { Process.SetTimeout[@socket.waitForInput, Process.SecondsToTicks[ms/1000] ]; }; }; SetSoftwareChecksumming: PUBLIC PROC [socket: Socket, send, recv: BOOL] = { socket.sendChecksum _ send; socket.recvChecksum _ recv; }; SetNoErrors: PUBLIC PROC [socket: Socket] = { socket.noErrors _ TRUE; }; Kick: PUBLIC ENTRY PROC [socket: Socket] = { BROADCAST socket.waitForInput; }; Destroy: PUBLIC ENTRY PROC [socket: Socket] = { BROADCAST socket.waitForInput; socket.dead _ TRUE; UNTIL socket.firstInput = NIL DO b: Buffer _ socket.firstInput; socket.firstInput _ Next[b]; b.ovh.next _ NIL; -- DKW: just to be careful ... IF b.ovh.gap#gapSocket THEN DebuggerSwap.CallDebugger["Clobbered buffer in socket queue!"]; b.ovh.gap _ gapNoList; -- DKW: b now removed from the queue socket.recvBuffersInUse _ socket.recvBuffersInUse - 1; CommDriver.FreeBuffer[RealDriverBuffer[b]]; ENDLOOP; socket.lastInput _ NIL; -- Help Buffer Finalization socket.routing _ NIL; }; DestroyInner: ENTRY PROC [socket: Socket, old: Socket] = { RemoveSocket[old]; }; AllocBuffer: PUBLIC PROC [socket: Socket] RETURNS [b: Buffer] = { InnerAllocBuffer[socket]; b _ RealPupBuffer[CommDriver.AllocBuffer[], send]; b.ovh.network _ NIL; b.ovh.socket _ socket; -- Needed for Finalization }; InnerAllocBuffer: ENTRY PROC [socket: Socket] = { ENABLE UNWIND => NULL; UNTIL socket.sendBuffersInUse < socket.sendBuffersAlloc DO WAIT socket.waitForSendBuffer; ENDLOOP; socket.sendBuffersInUse _ socket.sendBuffersInUse + 1; }; SetUserBytes: PUBLIC PROC [b: Buffer, bytes: PupBuffer.ByteIndex] = { boundsCheck: PupBuffer.ByteIndex _ bytes; -- In case somebody is using TRUSTED. b.byteLength _ bytes + bytesOfPupOverhead; }; SetUserHWords: PUBLIC PROC [b: Buffer, hWords: PupBuffer.HWordIndex] = { boundsCheck: PupBuffer.HWordIndex _ hWords; b.byteLength _ hWords*SIZE[HWORD, bytesPerWord] + bytesOfPupOverhead; }; SetUserFWords: PUBLIC PROC [b: Buffer, fWords: PupBuffer.FWordIndex] = { boundsCheck: PupBuffer.FWordIndex _ fWords; b.byteLength _ fWords*SIZE[FWORD, bytesPerWord] + bytesOfPupOverhead; }; SetUserSize: PUBLIC PROC [b: Buffer, sizeUnits: NAT] = { boundsCheck: PupBuffer.ByteIndex = sizeUnits*SIZE[WORD, bytesPerWord]; b.byteLength _ boundsCheck + bytesOfPupOverhead; }; Broadcast: PUBLIC PROC [socket: Socket, b: Buffer] = { BroadcastInner[socket, b, socket.remote.socket]; }; BroadcastInner: PROC [socket: Socket, b: Buffer, dest: Pup.Socket] = { bytes: NAT = PupBuffer.RoundUpForChecksum[b.byteLength]; b.hopCount _ 0; b.spares _ 0; b.dest.host _ Pup.allHosts; b.dest.socket _ dest; b.source.socket _ socket.local.socket; FOR network: Network _ CommDriver.GetNetworkChain[], network.next UNTIL network = NIL DO b.ovh.encap _ network.pup.getEncapsulation[network, Pup.allHosts]; b.dest.net _ network.pup.net; b.source.net _ network.pup.net; b.source.host _ network.pup.host; SetChecksum[b]; network.pup.send[network, TempDriverBuffer[b], bytes]; ENDLOOP; FreeBuffer[b]; }; Put: PUBLIC PROC [socket: Socket, b: Buffer] = { Send[socket, b, socket.remote]; }; Send: PUBLIC PROC [socket: Socket, b: Buffer, dest: Pup.Address] = { bytes: NAT = PupBuffer.RoundUpForChecksum[b.byteLength]; network: Network; immediate: Pup.Host; IF dest.net = Pup.allNets THEN { BroadcastInner[socket, b, dest.socket]; RETURN; }; [network, immediate] _ PupInternal.Route[dest]; IF network = NIL THEN { FreeBuffer[b]; RETURN; }; b.hopCount _ 0; b.spares _ 0; b.dest _ dest; b.source _ socket.local; b.ovh.encap _ network.pup.getEncapsulation[network, immediate]; TRUSTED { words: NAT = PupBuffer.WordsWithoutChecksum[b.byteLength]; checksumLoc: LONG POINTER TO HWORD _ @b.byteLength + words; IF socket.sendChecksum THEN { IF outOfLineChecksum THEN checksumLoc^ _ OutOfLineChecksum[0, words, @b.byteLength] ELSE checksumLoc^ _ Checksum.ComputeChecksum[0, words, @b.byteLength]; } ELSE checksumLoc^ _ PupBuffer.noChecksum; }; network.pup.send[network, TempDriverBuffer[b], bytes]; FreeBuffer[b]; }; Get: PUBLIC ENTRY PROC [socket: Socket] RETURNS [b: Buffer] = { ENABLE UNWIND => NULL; IF socket.firstInput = NIL THEN { IF socket.dontWait THEN RETURN[NIL]; IF socket.dead THEN RETURN[NIL]; WAIT socket.waitForInput; }; IF socket.firstInput = NIL THEN RETURN[NIL]; b _ socket.firstInput; socket.firstInput _ Next[b]; b.ovh.next _ NIL; -- DKW: just to be careful ... IF b.ovh.gap#gapSocket THEN DebuggerSwap.CallDebugger["Clobbered buffer in socket queue!"]; b.ovh.gap _ gapNoList; -- DKW: b now removed from the queue b.ovh.socket _ socket; -- Needed for Finalization }; GetUserBytes: PUBLIC PROC [b: Buffer] RETURNS [bytes: PupBuffer.ByteIndex] = { bytes _ b.byteLength - bytesOfPupOverhead; }; GetUserHWords: PUBLIC PROC [b: Buffer] RETURNS [hWords: PupBuffer.HWordIndex] = { bytes: PupBuffer.ByteIndex _ b.byteLength - bytesOfPupOverhead; hWords _ bytes/SIZE[HWORD]/bytesPerWord; }; GetUserFWords: PUBLIC PROC [b: Buffer] RETURNS [fWords: PupBuffer.FWordIndex] = { bytes: PupBuffer.ByteIndex _ b.byteLength - bytesOfPupOverhead; fWords _bytes/SIZE[FWORD]/bytesPerWord; }; GetUserSize: PUBLIC PROC [b: Buffer] RETURNS [sizeUnits: NAT] = { bytes: PupBuffer.ByteIndex _ b.byteLength - bytesOfPupOverhead; sizeUnits _ bytes/SIZE[WORD]/bytesPerWord; }; FreeBuffer: PUBLIC PROC [b: Buffer] = { socket: Socket _ NARROW[b.ovh.socket]; FreeBufferInner[socket, b]; }; FreeBufferInner: ENTRY PROC [socket: Socket, b: Buffer] = INLINE { ENABLE UNWIND => NULL; SELECT b.ovh.direction FROM send => { socket.sendBuffersInUse _ socket.sendBuffersInUse - 1; NOTIFY socket.waitForSendBuffer; }; recv => { socket.recvBuffersInUse _ socket.recvBuffersInUse - 1; NOTIFY socket.waitForRecvBuffer; }; ENDCASE => ERROR; CommDriver.FreeBuffer[RealDriverBuffer[b]]; }; FixupSourceAndDest: PUBLIC PROC [b: Buffer] = { network: Network _ NARROW[b.ovh.network]; IF b.source.net = Pup.nullNet THEN b.source.net _ network.pup.net; IF b.dest.net = Pup.nullNet THEN b.dest.net _ network.pup.net; IF b.dest.host = Pup.allHosts THEN b.dest.host _ network.pup.host; }; ReturnToSender: PUBLIC PROC [b: Buffer] = { ReturnToSenderNoFree[b]; FreeBuffer[b]; }; ReturnToSenderNoFree: PUBLIC PROC [b: Buffer] = { socket: Socket _ NARROW[b.ovh.socket]; network: Network _ NARROW[b.ovh.network]; bytes: NAT = PupBuffer.RoundUpForChecksum[b.byteLength]; temp: Pup.Address; FixupSourceAndDest[b]; b.hopCount _ 0; b.spares _ 0; temp _ b.source; b.source _ b.dest; b.dest _ temp; TRUSTED { words: NAT = PupBuffer.WordsWithoutChecksum[b.byteLength]; checksumLoc: LONG POINTER TO HWORD _ @b.byteLength + words; IF socket = NIL OR socket.sendChecksum THEN { IF outOfLineChecksum THEN checksumLoc^ _ OutOfLineChecksum[0, words, @b.byteLength] ELSE checksumLoc^ _ Checksum.ComputeChecksum[0, words, @b.byteLength]; } ELSE checksumLoc^ _ PupBuffer.noChecksum; }; IF b.source.host = Pup.allHosts OR network.toBroadcast[network, TempDriverBuffer[b]] THEN RETURN; network.pup.return[network, TempDriverBuffer[b], bytes]; }; SetChecksum: PUBLIC PROC [b: Buffer] = TRUSTED { words: NAT = PupBuffer.WordsWithoutChecksum[b.byteLength]; checksumLoc: LONG POINTER TO HWORD _ @b.byteLength + words; IF outOfLineChecksum THEN checksumLoc^ _ OutOfLineChecksum[0, words, @b.byteLength] ELSE checksumLoc^ _ Checksum.ComputeChecksum[0, words, @b.byteLength]; }; errorSize: NAT = SIZE[HeaderWithoutChecksum] + SIZE[ErrorCode] + SIZE[HWORD]; ReturnError: PUBLIC PROC [b: Buffer, code: ErrorCode, rope: ROPE] = { ReturnErrorNoFree[b, code, rope]; FreeBuffer[b]; }; ReturnErrorNoFree: PUBLIC PROC [b: Buffer, code: ErrorCode, rope: ROPE] = { socket: Socket _ NARROW[b.ovh.socket]; IF b.type = error THEN RETURN; TRUSTED { PrincOpsUtils.LongCopy[ from: @b.byteLength, nwords: SIZE[HeaderWithoutChecksum], to: @b.error.header]; }; b.type _ error; b.error.code _ code; b.error.options _ 0; SetUserSize[b, errorSize]; IF rope = NIL THEN SELECT code FROM badChecksum => rope _ "Bad Software Checksum"; noSocket => rope _ "No such Port"; resourceLimits => rope _ "Buffers full"; ENDCASE => NULL; AppendRope[b, rope]; IF code = iAmNotAGateway THEN { -- Fill in our return address network: Network = NARROW[b.ovh.network]; b.dest.net _ network.pup.net; b.dest.host _ network.pup.host; b.dest.socket _ Pup.nullSocket; }; ReturnToSenderNoFree[b]; }; newRoutingInfo: INT _ 0; PutFirst: PUBLIC PROC [socket: Socket, b: Buffer] = { routing: Routing _ socket.routing; -- ATOMIC new: RoutingInfo; IF socket.remote.socket = Pup.nullSocket THEN GOTO CantGetThere; [new.network, new.immediate] _ PupInternal.Route[socket.remote]; IF new.network = NIL THEN GOTO CantGetThere; new.encap _ new.network.pup.getEncapsulation[new.network, new.immediate]; IF routing = NIL OR routing^ # new THEN { routing _ NEW[RoutingInfo _ new]; socket.routing _ routing; -- ATOMIC newRoutingInfo _ newRoutingInfo.SUCC; }; PutAgain[socket, b]; EXITS CantGetThere => { socket.routing _ NIL; }; }; PutAgain: PUBLIC PROC [socket: Socket, b: Buffer] = { bytes: NAT = PupBuffer.RoundUpForChecksum[b.byteLength]; routing: Routing _ socket.routing; -- ATOMIC network: Network; IF routing = NIL THEN RETURN; network _ routing.network; IF network = NIL THEN RETURN; b.ovh.encap _ routing.encap; b.hopCount _ 0; b.spares _ 0; b.dest _ socket.remote; b.source _ socket.local; TRUSTED { words: NAT = PupBuffer.WordsWithoutChecksum[b.byteLength]; checksumLoc: LONG POINTER TO HWORD _ @b.byteLength + words; IF socket.sendChecksum THEN { IF outOfLineChecksum THEN checksumLoc^ _ OutOfLineChecksum[0, words, @b.byteLength] ELSE checksumLoc^ _ Checksum.ComputeChecksum[0, words, @b.byteLength]; } ELSE checksumLoc^ _ PupBuffer.noChecksum; }; IF network # NIL THEN network.pup.send[network, TempDriverBuffer[b], bytes]; }; Resend: PUBLIC PROC [b: Buffer, checksum: BOOL _ TRUE] = { bytes: NAT = PupBuffer.RoundUpForChecksum[b.byteLength]; network: Network _ NARROW[b.ovh.network]; IF network = NIL THEN RETURN; TRUSTED { words: NAT = PupBuffer.WordsWithoutChecksum[b.byteLength]; checksumLoc: LONG POINTER TO HWORD _ @b.byteLength + words; IF checksum THEN { IF outOfLineChecksum THEN checksumLoc^ _ OutOfLineChecksum[0, words, @b.byteLength] ELSE checksumLoc^ _ Checksum.ComputeChecksum[0, words, @b.byteLength]; } ELSE checksumLoc^ _ PupBuffer.noChecksum; }; network.pup.send[network, TempDriverBuffer[b], bytes]; }; SetDirectReceive: PUBLIC ENTRY PROC [socket: Socket, proc: ReceiveProc, user: REF] = { socket.proc _ proc; socket.user _ user; }; UseNormalPath: PUBLIC PROC [b: Buffer] = { socket: Socket _ NARROW[b.ovh.socket]; UseNormalPathInner[socket, b]; }; UseNormalPathInner: ENTRY PROC [socket: Socket, b: Buffer] = { b.ovh.socket _ NIL; IF b.ovh.gap#gapNoList THEN DebuggerSwap.CallDebugger["Buffer already in a list!"]; b.ovh.gap _ gapSocket; -- DKW: b is now in the socket queue IF socket.firstInput = NIL THEN socket.firstInput _ b ELSE socket.lastInput.ovh.next _ b; socket.lastInput _ b; NOTIFY socket.waitForInput; }; AllocRecvBuffer: PUBLIC PROC [socket: Socket] RETURNS [b: Buffer] = { InnerAllocRecvBuffer[socket]; b _ RealPupBuffer[CommDriver.AllocBuffer[], recv]; b.ovh.network _ NIL; b.ovh.socket _ socket; -- Needed for Finalization }; InnerAllocRecvBuffer: ENTRY PROC [socket: Socket] = { ENABLE UNWIND => NULL; UNTIL socket.recvBuffersInUse < socket.recvBuffersAlloc DO WAIT socket.waitForRecvBuffer; ENDLOOP; socket.recvBuffersInUse _ socket.recvBuffersInUse + 1; }; CopyRope: PUBLIC PROC [b: Buffer, rope: Rope.ROPE] = { chars: PupBuffer.ByteIndex _ MIN[Rope.Length[rope], PupBuffer.ByteAlloc.LAST]; FOR i: PupBuffer.ByteAlloc IN [0..chars) DO b.char[i] _ Rope.Fetch[rope, i]; ENDLOOP; SetUserBytes[b, chars]; }; AppendRope: PUBLIC PROC [b: Buffer, rope: Rope.ROPE] = { start: PupBuffer.ByteIndex _ GetUserBytes[b]; chars: PupBuffer.ByteIndex _ MIN[Rope.Length[rope], PupBuffer.ByteAlloc.LAST-start]; FOR i: PupBuffer.ByteAlloc IN [0..chars) DO b.char[start+i] _ Rope.Fetch[rope, i]; ENDLOOP; SetUserBytes[b, start+chars]; }; ExtractRope: PUBLIC PROC [b: Buffer] RETURNS [rope: Rope.ROPE] = { bytes: NAT _ GetUserBytes[b]; text: REF TEXT _ RefText.ObtainScratch[bytes]; FOR i: PupBuffer.ByteAlloc IN [0..bytes) DO text[i] _ b.char[i]; ENDLOOP; text.length _ bytes; rope _ Rope.FromRefText[text]; RefText.ReleaseScratch[text]; }; ExtractErrorRope: PUBLIC PROC [b: Buffer] RETURNS [rope: Rope.ROPE] = { bytes: NAT _ GetUserBytes[b]; text: REF TEXT _ RefText.ObtainScratch[bytes]; IF bytes < PupBuffer.errorOverheadBytes THEN RETURN[NIL]; bytes _ bytes - PupBuffer.errorOverheadBytes; FOR i: NAT IN [0..bytes) DO text[i] _ b.error.text[i]; ENDLOOP; text.length _ bytes; rope _ Rope.FromRefText[text]; RefText.ReleaseScratch[text]; }; ExtractAbortRope: PUBLIC PROC [b: Buffer] RETURNS [rope: Rope.ROPE] = { bytes: NAT _ GetUserBytes[b]; text: REF TEXT _ RefText.ObtainScratch[bytes]; IF bytes < PupBuffer.abortOverheadBytes THEN RETURN[NIL]; bytes _ bytes - PupBuffer.abortOverheadBytes; FOR i: NAT IN [0..bytes) DO text[i] _ b.abort.text[i]; ENDLOOP; text.length _ bytes; rope _ Rope.FromRefText[text]; RefText.ReleaseScratch[text]; }; unique: LONG CARDINAL _ BasicTime.GetClockPulses[]; GetUniqueID: PUBLIC PROC RETURNS [FWORD] = { new: LONG CARDINAL _ GetUniqueIDInner[globalLock]; RETURN[Endian.FFromCard[new]]; }; GetUniqueIDInner: ENTRY PROC [socket: Socket] RETURNS [LONG CARDINAL] = { unique _ unique.SUCC; RETURN[unique]; }; IsThisMe: PUBLIC PROC [address: Pup.Address] RETURNS [yes: BOOL] = { network: Network _ PupInternal.Route[address].network; IF network = NIL THEN RETURN[FALSE]; IF network.pup.net # address.net THEN RETURN[FALSE]; IF network.pup.host # address.host THEN RETURN[FALSE]; RETURN[TRUE]; }; GetMyAddress: PUBLIC PROC RETURNS [address: Pup.Address] = { network: Network _ CommDriver.GetNetworkChain[]; IF network = NIL THEN RETURN[Pup.nullAddress]; address.net _ network.pup.net; address.host _ network.pup.host; address.socket _ Pup.nullSocket; }; ClumpOfSockets: TYPE = RECORD [sockets: SEQUENCE count: CARDINAL OF Socket]; wellKnownSockets: REF ClumpOfSockets _ NEW[ClumpOfSockets[PupWKS.rpc.d]]; ephemeralSockets: REF ClumpOfSockets _ NEW[ClumpOfSockets[16]]; maxWellKnownSockets: CARDINAL = 512; -- Must be a power of 2 (for wraparound) maxEphemeralSockets: CARDINAL = 1024; -- Must be a power of 2 (for masking into table) ephemeralSocketMask: CARDINAL = maxEphemeralSockets - 1; wraparoundSocket: CARDINAL _ MAX[maxWellKnownSockets, maxEphemeralSockets]; nextEphemeralSocket: LONG CARDINAL _ BasicTime.GetClockPulses[]; IndexFromSocket: PROC [socket: Pup.Socket] RETURNS [index: CARDINAL] = { socketNumber: LONG CARDINAL = PupType.CardFromSocket[socket]; index _ IndexFromLC[socketNumber]; }; IndexFromLC: PROC [socketNumber: LONG CARDINAL] RETURNS [index: CARDINAL] = { index _ Basics.BITAND[Basics.LowHalf[socketNumber], ephemeralSocketMask]; }; CantSpecifyArbitraryLocalSocketNumber: PUBLIC ERROR = CODE; AddSocket: INTERNAL PROC [new: Socket] = { socketNumber: LONG CARDINAL = PupType.CardFromSocket[new.local.socket]; SELECT TRUE FROM new.local.socket = Pup.nullSocket => AddEphemeralSocket[new]; socketNumber < maxWellKnownSockets => AddWellKnownSocket[new]; ENDCASE => ERROR CantSpecifyArbitraryLocalSocketNumber; }; AddWellKnownSocket: INTERNAL PROC [new: Socket] = { socketNumber: CARDINAL = PupType.CardFromSocket[new.local.socket]; IF socketNumber >= wellKnownSockets.count THEN wellKnownSockets _ Expand[wellKnownSockets, socketNumber+1]; new.next _ wellKnownSockets[socketNumber]; wellKnownSockets[socketNumber] _ new; }; IndexAdjustmentConfusion: ERROR = CODE; -- Bug in this code SocketNumberWraparoundConfusion: ERROR = CODE; -- Bug in this code AddEphemeralSocket: INTERNAL PROC [new: Socket] = { localSocket: LONG CARDINAL _ nextEphemeralSocket; FOR i: CARDINAL IN [0..ephemeralSockets.count) DO index: CARDINAL _ IndexFromLC[localSocket]; IF ~(index < ephemeralSockets.count) THEN { localSocket _ localSocket + (maxEphemeralSockets-index); IF localSocket < maxWellKnownSockets THEN localSocket _ wraparoundSocket; index _ IndexFromLC[localSocket]; IF index # 0 THEN ERROR IndexAdjustmentConfusion; }; IF ephemeralSockets[index] = NIL THEN EXIT; localSocket _ localSocket + 1; IF localSocket < maxWellKnownSockets THEN localSocket _ wraparoundSocket; REPEAT FINISHED => { index: CARDINAL _ IndexFromLC[nextEphemeralSocket]; IF ephemeralSockets.count = maxEphemeralSockets THEN DebuggerSwap.CallDebugger["Socket table full"]; localSocket _ nextEphemeralSocket + (ephemeralSockets.count-index); IF localSocket < maxWellKnownSockets THEN localSocket _ wraparoundSocket; index _ IndexFromLC[localSocket]; IF index # ephemeralSockets.count THEN ERROR IndexAdjustmentConfusion; ephemeralSockets _ Expand[ephemeralSockets, ephemeralSockets.count+1]; }; ENDLOOP; IF localSocket < maxWellKnownSockets THEN ERROR SocketNumberWraparoundConfusion; new.local.socket _ PupType.SocketFromCard[localSocket]; nextEphemeralSocket _ localSocket + 1; ephemeralSockets[IndexFromSocket[new.local.socket]] _ new; }; Expand: INTERNAL PROC [old: REF ClumpOfSockets, size: CARDINAL] RETURNS [new: REF ClumpOfSockets] = { new _ NEW[ClumpOfSockets[size]]; FOR i: CARDINAL IN [0..old.count) DO new[i] _ old[i]; ENDLOOP; }; FindSocket: PROC [localSocket: Pup.Socket] RETURNS [h: Socket _ NIL] = { socketNumber: LONG CARDINAL = PupType.CardFromSocket[localSocket]; IF socketNumber < maxWellKnownSockets THEN h _ FindWellKnownSocket[socketNumber] ELSE h _ FindEphemeralSocket[localSocket]; }; FindWellKnownSocket: PROC [socketNumber: CARDINAL] RETURNS [h: Socket _ NIL] = { IF socketNumber < wellKnownSockets.count THEN h _ wellKnownSockets[socketNumber]; DO IF h = NIL THEN RETURN; IF ~h.dead THEN RETURN; h _ h.next; ENDLOOP; }; FindEphemeralSocket: PROC [localSocket: Pup.Socket] RETURNS [h: Socket _ NIL] = { index: CARDINAL = IndexFromSocket[localSocket]; IF index < ephemeralSockets.count THEN h _ ephemeralSockets[index]; IF h = NIL THEN RETURN; IF localSocket # h.local.socket THEN h _ NIL; }; RemoveSocket: INTERNAL PROC [old: Socket] = { socketNumber: LONG CARDINAL = PupType.CardFromSocket[old.local.socket]; IF socketNumber < maxWellKnownSockets THEN RemoveWellKnownSocket[old, socketNumber] ELSE RemoveEphemeralSocket[old]; old.next _ NIL; }; RemoveWellKnownSocket: INTERNAL PROC [old: Socket, socketNumber: CARDINAL] = { head: Socket _ wellKnownSockets[socketNumber]; IF head = old THEN { wellKnownSockets[socketNumber] _ head.next; RETURN; }; DO -- NIL fault if not on chain IF head.next = old THEN { head.next _ old.next; EXIT; }; head _ head.next; ENDLOOP; }; RemoveEphemeralSocket: INTERNAL PROC [old: Socket] = { index: CARDINAL = IndexFromSocket[old.local.socket]; IF ephemeralSockets[index] # old THEN ERROR; -- Or bounds fault if bogus ephemeralSockets[index] _ NIL; }; errorTooShort: INT _ 0; errorNoGateway: INT _ 0; errorNoSocket: INT _ 0; errorDeadSocket: INT _ 0; errorBadChecksum: INT _ 0; errorBuffersFull: INT _ 0; TakeThis: PUBLIC PROC [network: Network, buffer: CommDriver.Buffer, bytes: NAT] RETURNS [CommDriver.Buffer] = { b: Buffer = RealPupBuffer[buffer, recv]; bytesNeeded: NAT = PupBuffer.RoundUpForChecksum[b.byteLength]; dest: Pup.Address _ b.dest; localSocket: Pup.Socket = dest.socket; socket: Socket; proc: ReceiveProc; user: REF ANY; IF bytes < bytesNeeded OR b.byteLength < bytesOfPupOverhead THEN { errorTooShort _ errorTooShort.SUCC; RETURN[RealDriverBuffer[b]]; }; BEGIN SELECT TRUE FROM (dest.host = network.pup.host) => { SELECT TRUE FROM (dest.net = network.pup.net) => NULL; -- Main line case (network.pup.net = Pup.nullNet) => NULL; -- We don't know, he does (dest.net = Pup.nullNet) => NULL; -- He doesn't know, we do ENDCASE => GOTO TryBackDoor; }; (dest.host = Pup.allHosts) => { SELECT TRUE FROM (dest.net = network.pup.net) => NULL; -- Main line case (network.pup.net = Pup.nullNet) => NULL; -- We don't know, he does (dest.net = Pup.nullNet) => NULL; -- He doesn't know, we do ENDCASE => GOTO TryBackDoor; }; ENDCASE => GOTO TryBackDoor; EXITS TryBackDoor => { [network, ] _ PupInternal.Route[dest]; -- Back door fixup IF network = NIL THEN network _ NARROW[b.ovh.network]; -- Argh. Our Initialization or his confusion IF dest.net # network.pup.net OR dest.host # network.pup.host THEN RETURN[RealDriverBuffer[forwarder[b]]]; }; END; socket _ FindSocket[localSocket]; IF socket = NIL THEN { errorNoSocket _ errorNoSocket.SUCC; ReturnErrorNoFree[b, noSocket, "No socket at this machine"]; RETURN[RealDriverBuffer[b]]; }; IF socket.dead THEN { errorDeadSocket _ errorDeadSocket.SUCC; IF ~socket.noErrors THEN ReturnErrorNoFree[b, noSocket, "Socket has been deleted"]; RETURN[RealDriverBuffer[b]]; }; IF socket.recvChecksum THEN TRUSTED { words: NAT = PupBuffer.WordsWithoutChecksum[b.byteLength]; checksumLoc: LONG POINTER TO HWORD _ @b.byteLength + words; hisChecksum: HWORD _ checksumLoc^; IF hisChecksum # PupBuffer.noChecksum THEN { checksum: WORD; IF outOfLineChecksum THEN checksum _ OutOfLineChecksum[0, words, @b.byteLength] ELSE checksum _ Checksum.ComputeChecksum[0, words, @b.byteLength]; IF checksum # hisChecksum THEN { errorBadChecksum _ errorBadChecksum.SUCC; ReturnErrorNoFree[b, badChecksum, "Bad software checksum"]; RETURN[RealDriverBuffer[b]]; }; }; }; [proc, user] _ CheckDirect[socket]; IF proc # NIL THEN { new: Buffer; b.ovh.socket _ socket; new _ proc[socket, b, user]; IF new = NIL THEN RETURN[NIL]; UnbumpRecvCount[socket]; RETURN[RealDriverBuffer[new]]; }; IF TakeThisInner[socket, b] THEN RETURN[NIL] ELSE { errorBuffersFull _ errorBuffersFull.SUCC; IF ~socket.noErrors THEN ReturnErrorNoFree[b, resourceLimits, "Buffer allocation limit"]; RETURN[RealDriverBuffer[b]]; }; }; CheckDirect: ENTRY PROC [socket: Socket] RETURNS [proc: ReceiveProc, user: REF ANY] = { IF socket.proc = NIL THEN RETURN[NIL, NIL]; IF socket.recvBuffersInUse < socket.recvBuffersAlloc THEN { socket.recvBuffersInUse _ socket.recvBuffersInUse.SUCC; RETURN[socket.proc, socket.user]; }; RETURN[NIL, NIL]; }; UnbumpRecvCount: ENTRY PROC [socket: Socket] = INLINE { socket.recvBuffersInUse _ socket.recvBuffersInUse.PRED; }; TakeThisInner: ENTRY PROC [socket: Socket, b: Buffer] RETURNS [ok: BOOL] = { IF socket.dead THEN { CommDriver.FreeBuffer[RealDriverBuffer[b]]; RETURN[TRUE]; }; IF socket.recvBuffersInUse < socket.recvBuffersAlloc THEN { socket.recvBuffersInUse _ socket.recvBuffersInUse.SUCC; IF b.ovh.gap#gapNoList THEN DebuggerSwap.CallDebugger["Buffer already in a list!"]; b.ovh.gap _ gapSocket; -- DKW: b is now in the socket queue IF socket.firstInput = NIL THEN socket.firstInput _ b ELSE socket.lastInput.ovh.next _ b; socket.lastInput _ b; NOTIFY socket.waitForInput; RETURN[TRUE]; }; RETURN[FALSE]; }; forwarder: PROC [b: Buffer] RETURNS [Buffer] _ DummyForwarder; DummyForwarder: PROC [b: Buffer] RETURNS [Buffer] = { errorNoGateway _ errorNoGateway.SUCC; ReturnErrorNoFree[b, iAmNotAGateway, "I'm not a gateway (yet)"]; RETURN[b]; }; CaptureForwarding: PUBLIC PROC [proc: PROC [Buffer] RETURNS [Buffer] ] = { forwarder _ proc; }; sfqMaxCount: INT _ 0; -- DKW: see how full sfq can get in 10 seconds droppedSockets: INT _ 0; finishedSockets: INT _ 0; finishedErrors: INT _ 0; finishedDelayed: INT _ 0; finishedBatches: INT _ 0; droppedBuffers: INT _ 0; oneSecond: Process.Ticks = Process.SecondsToTicks[1]; SocketFinalizer: PROC = { Process.SetPriority[Process.priorityBackground]; DO sfqCount: INT _ 0; -- DKW: count the number of sockets in the queue each iteration list: LIST OF Socket _ NIL; -- Batch up sockets to be Destroyed, avoiding 10 seconds each DO socket: Socket _ NARROW[SafeStorage.FQNext[sfq]]; sfqCount _ sfqCount+1; IF ~socket.dead THEN { -- User forgot to call Destroy SafeStorage.EnableFinalization[socket]; Destroy[socket]; droppedSockets _ droppedSockets.SUCC; } ELSE { -- Normal end of life IF socket.noErrors THEN list _ CONS[socket, list] ELSE DestroyInner[globalLock, socket]; finishedSockets _ finishedSockets.SUCC; }; socket _ NIL; IF SafeStorage.FQEmpty[sfq] THEN EXIT; ENDLOOP; sfqMaxCount _ MAX[sfqMaxCount, sfqCount]; IF list = NIL THEN LOOP; Process.Pause[10*oneSecond]; UNTIL list = NIL DO DestroyInner[globalLock, list.first]; finishedDelayed _ finishedDelayed.SUCC; list _ list.rest; ENDLOOP; finishedBatches _ finishedBatches.SUCC; ENDLOOP; }; BufferFinalizer: PROC = { Process.SetPriority[Process.priorityBackground]; DO b: Buffer _ NARROW[SafeStorage.FQNext[bfq]]; SafeStorage.EnableFinalization[b]; FreeBuffer[b]; b _ NIL; droppedBuffers _ droppedBuffers.SUCC; ENDLOOP; }; DropTest: PROC [n: NAT] = { FOR i: NAT IN [0..n) DO socket: Socket _ CreateEphemeral[remote: Pup.nullAddress]; [] _ AllocBuffer[socket]; SetNoErrors[socket]; ENDLOOP; }; bfq: SafeStorage.FinalizationQueue _ SafeStorage.NewFQ[]; sfq: SafeStorage.FinalizationQueue _ SafeStorage.NewFQ[length: 300]; SafeStorage.EstablishFinalization[type: CODE[PupBuffer.BufferObject], npr: 0, fq: bfq]; SafeStorage.EstablishFinalization[type: CODE[Object], npr: 1, fq: sfq]; TRUSTED { Process.Detach[FORK SocketFinalizer[]]; }; TRUSTED { Process.Detach[FORK BufferFinalizer[]]; }; IF ~Booting.switches[c] THEN CommDriver.InsertReceiveProc[NIL, pup, TakeThis]; }. βPupSocketImpl.mesa Copyright c 1986 by Xerox Corporation. All rights reserved. Hal Murray, November 17, 1986 4:13:21 pm PST Doug Wyatt, June 10, 1986 2:54:21 pm PDT Hackery for performance tuning. "= FALSE" => no performance loss. While a buffer is on the input queue, b.ovh.socket is NIL so that dropped sockets will get finalized. While the client has the buffer (Alloc, Get, GetDirect) b.ovh.socket is filled in so we can fixup the socket counters if the buffer gets dropped. Beware: See the comments near Driver.Buffer Errors Creation and parameter setting AddSocket assigns a local socket number if socket.local.socket = nullSocket. Beware: socket is the global lock, new is the real socket Now just drop it on the floor Beware: socket is the global lock, old is the real socket Sending Needed so that the call to CommDriver.AllocBuffer is outside the ML. Receiving Back door - for speed Needed so that the call to CommDriver.AllocBuffer is outside the ML. Rope Utilities Utilities Keeping track of active sockets and assigning ephemeral socket numbers Once upon a time, this was a simple linked list. The current structure means that you can do a lookup without any chaining. There are two types of sockets: well known, and ephemeral. There is a separate lookup table for each type. Well known sockets have small values. In terms of binding, they are the constants "wired in" to the net. Actually, they are chained together if you create another instance of the same socket number. The idea is that you can capture a socket, say the echo server, without cooperation from the real server. Ephemeral socket numbers are assigned locally. Anything goes as long as they are unique. This module uses the low bits as an index into a table and increments the high bits when necessary to force uniqueness. Allocating 10 bits for "low" means that we can have up to 1000 ephemeral sockets active at the same time, which seems like enough for now. That leaves a 22 bit counter to guarantee uniqeness. I checked Bataan once. It had used 2^15 sockets in 4 hours. That's 2^18 per day. At that rate, things will wrap around every 16 days. That means we can't ignore the wraparound case. In practice, things will be much better than that since the algorithim reuses all the vacant slots before bumping the high bits. Note that even when things wrap around, we will never reuse an old socket number that is still in use since it will still be holding onto its slot. (We may reuse a socket number "right away" if it is used for a very long time, and deleted just before we check.) These tables are extended when needed. They never shrink. Assigns local socket number. Out of range. Bump high bits and set low bits back to 0. Hackery: look more if we encounter a dead one (echo server slot captured) Incoming packets from Drivers Counters for all the strange cases Don't be surprised if you find krocks lurking in here. /HGM, April 1, 1986 Stubs for gatewaying Finalization Beware: See the comments near CommDriver.Buffer I saw a NIL fault in FreeBuffer because b.ovh.socket was NIL. I think that was a missing check for socket.dead in TakeThisInner. /HGM, May 86 DKW: Because of the pause in SocketFinalizer, this queue must hold 10 seconds worth of discarded sockets; 100 (10 per second) is not enough. The TrickleCharger, for example, does FS file lookups as fast as it can; each lookup uses an ephemeral socket. When sfq had only 100 elements, the socket finalizer eventually fell behind and the socket table filled up. Κ&N˜codešœ™Kšœ Οmœ1™K˜Kšœžœ˜ Kšœ žœ˜'Kšœ žœ˜$Kšžœžœ žœ˜Kšœžœ!˜Kšžœžœ ˜BKšœ˜K™—šŸœžœžœ˜+K˜K˜K˜—K˜šŸœžœžœ˜1Kšœžœ˜&Kšœžœ˜)Kšœžœ.˜8Kšœ˜Kšœ˜Kšœ˜Kšœ ˜ K˜K˜K˜šžœ˜ Kšœžœ0˜:Kš œ žœžœžœžœ˜;šžœ žœžœžœ˜-šžœž˜Kšœ9˜9—KšžœD˜H—Kšžœ(˜,—Kšžœ˜Kšžœ3žœžœ˜AKšœ8˜8K˜K™—šŸ œžœžœžœ˜0Kšœžœ0˜:Kš œ žœžœžœžœ˜;šžœž˜Kšœ9˜9—KšžœB˜FK˜K™—Kš œ žœžœžœžœžœ˜MšŸ œž œ$žœ˜EKšœ!˜!Kšœ˜K˜J™—šŸœž œ$žœ˜KKšœžœ˜&Kšžœžœžœ˜šžœ˜ šœ˜Kšœ˜Kšœžœ˜$Kšœ˜——Kšœ˜Kšœ˜Kšœ˜Kšœ˜šžœžœž˜šžœž˜K˜.K˜"K˜(Kšžœžœ˜——K˜šžœžœ ˜=Kšœžœ˜)K˜K˜K˜"—Kšœ˜K˜J™——šŸ™Kšœžœ˜šŸœž œ ˜5Kšœ#  ˜,Kšœ˜Kšžœ'žœžœ˜@K˜@Kšžœžœžœžœ˜,KšœI˜Išžœ žœžœžœ˜)Kšœ žœ˜!Kšœ  ˜#Kšœ žœ˜(—Kšœ˜Kšžœ$žœ˜0Kšœ˜K™—šŸœž œ ˜5Kšœžœ.˜8Kšœ#  ˜,Kšœ˜Kšžœ žœž œ˜Kšœ˜Kšžœ žœž œ˜Kšœ˜Kšœ˜Kšœ ˜ Kšœ˜K˜šžœ˜ Kšœžœ0˜:Kš œ žœžœžœžœ˜;šžœž˜šžœž˜Kšœ9˜9—KšžœD˜H—Kšžœ(˜,—Kšžœ žœžœ8˜MKšœ˜K™—šŸœž œžœžœ˜:Kšœžœ.˜8Kšœžœ˜)Kšžœ žœž œ˜šžœ˜ Kšœžœ0˜:Kš œ žœžœžœžœ˜;šžœ ž˜šžœž˜Kšœ9˜9—KšžœD˜H—Kšžœ(˜,—Kšœ7˜7Kšœ˜K™—š Ÿœžœžœžœ+žœ˜VKšœ˜Kšœ˜Kšœ˜K™—šŸ œžœžœ˜*Kšœžœ˜&Kšœ˜Kšœ˜K˜—šŸœžœžœ ˜>Kšœžœ˜šžœžœ˜Kšœ7˜7—Kšœ $˜;Kšžœžœžœ˜5Kšžœ˜#Kšœ˜Kšžœ˜Kšœ˜K˜—šŸœžœžœžœ˜EKšœ˜Kšœ2˜2Kšœžœ˜Kšœ ˜1Kšœ˜K™—šŸœžœžœ˜5KšœAž™DKšž˜šžœ3ž˜:Kšžœ˜Kšžœ˜—Kšœ6˜6Kšœ˜K˜——šŸ™šŸœž œžœ˜6Kšœžœ(žœ˜Nšžœžœ ž˜+K˜ Kšžœ˜—K˜K˜K˜—šŸ œž œžœ˜8Kšœ-˜-Kšœžœ(žœ˜Tšžœžœ ž˜+K˜&Kšžœ˜—K˜K˜K™—š Ÿ œžœžœ žœ žœ˜BKšœžœ˜Kšœžœžœ ˜.šžœžœ ž˜+K˜Kšžœ˜—Kšœ˜K˜K˜K˜K™—š Ÿœžœžœ žœ žœ˜GKšœžœ˜Kšœžœžœ ˜.Kšžœ&žœžœžœ˜9Kšœ-˜-šžœžœžœ ž˜K˜Kšžœ˜—Kšœ˜K˜K˜K˜K™—š Ÿœžœžœ žœ žœ˜GKšœžœ˜Kšœžœžœ ˜.Kšžœ&žœžœžœ˜9Kšœ-˜-šžœžœžœ ž˜K˜Kšžœ˜—Kšœ˜K˜K˜K˜K˜——šŸ ™ Kšœžœžœ˜3š Ÿ œžœžœžœžœ˜,Kšœžœžœ ˜2Kšžœ˜Kšœ˜K˜—š Ÿœžœžœžœžœžœ˜IKšœžœ˜Kšžœ ˜Kšœ˜K˜—š Ÿœžœžœžœžœ˜DKšœ6˜6Kš žœ žœžœžœžœ˜$Kšžœžœžœžœ˜4Kšžœ!žœžœžœ˜6Kšžœžœ˜ K˜K™—šŸ œžœžœžœ˜˜>Kšžœžœ'˜7—Kšœ˜K˜—šŸœžœžœ˜4Kšœžœ,˜Bšžœ(ž˜.Kšœ<˜<—Kšœ*˜*Kšœ%˜%Kšœ˜K˜—KšŸœžœžœ ˜;KšŸœžœžœ ˜BšŸœžœžœ˜4Kšœ™Kšœ žœžœ˜1šžœžœžœž˜1Kšœžœ˜+šžœ#žœ˜+K™8Kšœ8˜8Kšžœ#žœ ˜IKšœ!˜!Kšžœ žœžœ˜4—Kšžœžœžœžœ˜+Kšœ˜Kšžœ#žœ ˜Išžœžœ˜Kšœžœ$˜3šžœ.ž˜4Kšœ/˜/—KšœC˜CKšžœ#žœ ˜IKšœ!˜!Kšžœ žœžœ˜FKšœI˜I—Kšžœ˜—Kšžœ#žœžœ!˜PKšœ7˜7Kšœ&˜&Kšœ:˜:Kšœ˜K˜—š Ÿœžœžœžœžœ˜?Kšžœžœ˜&Kšœžœ˜ Kš žœžœžœžœžœ˜>Kšœ˜K˜—šŸ œžœžœžœ˜IKšœžœžœ'˜Bšžœ$ž˜*Kšœ%˜%—Kšžœ&˜*Kšœ˜K˜—š Ÿœžœžœžœžœ˜QKšžœ'žœ$˜Qšž˜Kšžœžœžœžœ˜Kšžœ žœžœ˜K˜ KšœI™IKšžœ˜—Kšœ˜K˜—šŸœžœžœžœ˜RKšœžœ ˜/Kšžœ žœ˜CKšžœžœžœžœ˜Kšžœžœžœ˜-Kšœ˜K˜—šŸ œž œ˜.Kšœžœžœ,˜GKšžœ$žœ)˜SKšžœ˜ Kšœ žœ˜K˜K˜—šŸœžœžœžœ˜OKšœ.˜.šžœ žœ˜Kšœ+˜+Kšžœ˜ —šžœ ˜Kšžœžœžœ˜8K˜Kšžœ˜—K˜K˜—šŸœžœžœ˜7Kšœžœ%˜4Kšžœžœžœ ˜HKšœžœ˜K˜K˜——šœ™K˜™"Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜K˜—š Ÿœžœžœ6žœžœ˜oKšœ(˜(Kšœ žœ.˜>Kšœ˜Kšœ&˜&Kšœ˜Kšœ˜Kšœžœžœ˜šžœžœ"žœ˜BKšœžœ˜#Kšžœ˜—Kšž˜K™Jšžœžœž˜šœ#˜#šžœžœž˜Kšœ žœ ˜7Kšœ#žœ ˜BKšœžœ ˜;Kšžœžœ˜——šœ˜šžœžœž˜Kšœ žœ ˜7Kšœ#žœ ˜BKšœžœ ˜;Kšžœžœ˜——Kšžœžœ ˜—šžœ˜Kšœ' ˜9šžœ žœž˜Kšœ žœ ,˜M—šžœžœž˜BKšžœ$˜*——Kšžœ˜Kšœ!˜!šžœ žœžœ˜Kšœžœ˜#Jšœ<˜˜Bšžœžœ˜ Kšœ$žœ˜)Jšœ;˜;Kšžœ˜%———Kšœ#˜#šžœžœžœ˜K˜ Kšœ˜K˜Kš žœžœžœžœžœ˜Kšœ˜Kšžœ˜!—Kšžœžœžœžœ˜,šžœ˜Kšœ$žœ˜)JšžœžœA˜YKšžœ˜—K˜K™—š Ÿ œžœžœžœžœžœ˜WKš žœžœžœžœžœžœ˜+šžœ3ž˜;Kšœ2žœ˜7Kšžœ˜$—Kšžœžœžœ˜Kšœ˜K˜—šŸœžœžœžœ˜7Kšœ2žœ˜7Kšœ˜K˜—š Ÿ œžœžœžœžœ˜Lšžœ žœž˜Kšœ+˜+Kšžœžœ˜—šžœ3žœ˜;Kšœ2žœ˜7šžœžœ˜Kšœ7˜7—Kšœ $˜;Kšžœžœžœ˜5Kšžœ˜#Kšœ˜Kšžœ˜Kšžœžœ˜—Kšžœžœ˜Kšœ˜K˜——™Kšœ žœ žœ˜>K˜šŸœžœ žœ ˜5Kšœ žœ˜%Jšœ@˜@Jšžœ˜ Kšœ˜K˜—š Ÿœžœžœžœ žœ˜JJšœ˜Kšœ˜K˜——™ Kšœ žœ .˜DKšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜šœžœ˜K˜—Kšœ5˜5šŸœžœ˜Kšœ0˜0šž˜Kšœ žœ ?˜RKšœžœžœ žœ =˜Yšž˜Kšœžœ˜1Kšœ˜šžœžœ ˜5Kšœ'˜'Kšœ˜Kšœ žœ˜%Kšœ˜—šžœ ˜Kšžœžœžœ˜1Kšžœ"˜&Kšœ"žœ˜'Kšœ˜—Kšœ žœ˜ Kšžœžœžœ˜&Kšžœ˜—Kšœžœ˜)Kšžœžœžœžœ˜Kšœ˜šžœžœž˜Kšœ%˜%Kšœ"žœ˜'K˜Kšžœ˜—Kšœ"žœ˜'Kšžœ˜—Kšœ˜K˜—šŸœžœ˜Kšœ/™/Kšœ0˜0šž˜Kšœ žœ˜,Kšœ"˜"Kšœ˜Kšœžœ.žœFžœ™Kšœžœ˜Kšœ žœ˜%Kšžœ˜—Kšœ˜K˜—šŸœžœžœ˜šžœžœžœž˜Kšœ:˜:Kšœ˜K˜Kšžœ˜—Kšœ˜K˜—Kšœ9˜9šœD˜DK™ι—Kšœ(žœ+˜WKšœ(žœ˜GKšžœžœ˜4Kšžœžœ˜4K˜Kšžœžœžœ˜NK˜K˜—šœ˜K˜——…—|,°\