-- N.Wirth June 1, 1977 -- S.Andler August 24, 1977 10:49 PM -- C.Geschke August 31, 1977 9:36 AM -- E.Satterthwaite September 13, 1977 3:35 PM -- R.Johnsson September 19, 1977 8:18 AM -- J.Sandman October 18, 1977 12:40 PM -- E.Satterthwaite October 20, 1977 10:47 AM DIRECTORY InlineDefs: FROM "InlineDefs", IODefs: FROM "IODefs", PupDefs: FROM "PupDefs", SchedDefs: FROM "SchedDefs", StreamDefs: FROM "StreamDefs", StringDefs: FROM "StringDefs", SystemDefs: FROM "SystemDefs", TeleSilDefs: FROM "TeleSilDefs", TeleSilProcDefs: FROM "TeleSilProcDefs"; TeleSilPup: PROGRAM IMPORTS IODefs, PupDefs, SchedDefs, StreamDefs, StringDefs, SystemDefs, TeleSilProcDefs EXPORTS TeleSilProcDefs = BEGIN OPEN TeleSilProcDefs, -- TeleSilResident: -- BitMapDisplay, Mark, SetCursorIcon, SetMousePosition, TypeScriptWindow -- TeleSilDisplay: Confirm, Error -- TeleSilIO: InputFile, OutputFile -- TeleSilMain: InputStatus, NewPicture, OutputStatus, SetEtherOn TeleSilDefs; -- Constants -- BOF: PupDefs.Byte= 0; -- Mark byte to indicate beginning of file EOF: PupDefs.Byte= 1; -- Mark byte to indicate end of file -- Types -- Connection: TYPE= RECORD[ next: ConnPtr, id: ConnId, output: {active, closing, closed, finished}, input: {active, holding, closed, finished}, closeReason: PupDefs.CloseReason, outputWaiting: BOOLEAN, bs: PupDefs.ByteStream, name: STRING]; ConnPtr: TYPE= POINTER TO Connection; ConnSet: TYPE= WORD; -- Simulation of SET InQueueEl: TYPE= RECORD[ next: InQueuePtr, originator: ConnId, pCmd: CmdPtr]; OutQueueEl: TYPE= RECORD[ next: OutQueuePtr, recipients: ConnSet, pCmd: CmdPtr]; InQueuePtr: TYPE= POINTER TO InQueueEl; OutQueuePtr: TYPE= POINTER TO OutQueueEl; -- Variables -- pupVector: PupDefs.PupInterface _ NIL; --pointer to giant state vector listener: PupDefs.Listener; -- for listening to anybody WordsPerPup: CARDINAL = 28; listenSocket: PupDefs.Pair _ PupDefs.Pair[0,6]; sendHim: PupDefs.Address _ [PupDefs.ThisNet, PupDefs.ThisHost, listenSocket]; -- listenSocket is the agreed upon listening socket sendMe: PupDefs.Address _ [PupDefs.ThisNet, PupDefs.ThisHost, PupDefs.Pair[0,0]]; -- Socket Pair[0,0] gives unique socket sendConn: PupDefs.ConnObject _ [@sendMe, @sendHim]; firstConn: ConnPtr _ NIL; setConn: ConnSet _ emptySet; firstIn, lastIn: InQueuePtr _ NIL; -- List of CmdObj:s coming in firstOut, lastOut: OutQueuePtr _ NIL; -- List of CmdObj:s to be sent out buffer: STRING _ [80]; errorText: STRING _ buffer; -- sharing the same space! hostName, myName: STRING _ NIL; controlState: {passive, requested, granted} _ granted; -------------------------------------------------------------------- -- SETS -------------------------------------------------------------------- emptySet: WORD= 0; -- Simulation of the empty set fullSet: WORD= 177777B; -- Simulation of the full set Set: PROCEDURE[id: ConnId] RETURNS[ConnSet]= BEGIN RETURN[InlineDefs.BITSHIFT[1, id]] END; InSet: PROCEDURE[set: ConnSet, id: ConnId] RETURNS[BOOLEAN]= BEGIN RETURN[InlineDefs.BITAND[set, Set[id]]#emptySet] END; SetUnion: PROCEDURE[s1, s2: ConnSet] RETURNS[ConnSet]= BEGIN RETURN[InlineDefs.BITOR[s1, s2]] END; SetDiff: PROCEDURE[s1, s2: ConnSet] RETURNS[ConnSet]= BEGIN RETURN[InlineDefs.BITAND[s1, InlineDefs.BITNOT[s2]]] END; -------------------------------------------------------------------- -- STRINGS -------------------------------------------------------------------- GetString: PROCEDURE[bs: PupDefs.ByteStream, to: STRING]= BEGIN i: [0..80); to.length _ PupDefs.ByteStreamGet[bs]; -- puppackage should be fixed: FOR i IN [0..to.length) DO to[i] _ PupDefs.ByteStreamGetChar[bs] ENDLOOP; --FOR i IN [0..to.length) DO to[i] _ LOOPHOLE[PupDefs.ByteStreamGet[bs]] ENDLOOP; IF InlineDefs.BITAND[to.length,1]=0 THEN [] _ PupDefs.ByteStreamGet[bs] END; PutString: PROCEDURE[bs: PupDefs.ByteStream, from: STRING]= BEGIN i: [0..80); PupDefs.ByteStreamPut[bs, from.length]; FOR i IN [0..from.length) DO PupDefs.ByteStreamPutChar[bs, from[i]] ENDLOOP; IF InlineDefs.BITAND[from.length,1]=0 THEN PupDefs.ByteStreamPut[bs, 0] END; -------------------------------------------------------------------- SendCmd: PUBLIC PROCEDURE[pCmd: CmdPtr]= BEGIN SelectiveSendCmd[pCmd, setConn] END; SelectiveSendCmd: PROCEDURE[pCmd: CmdPtr, recipients: ConnSet]= BEGIN -- This procedure copies the command if it has recipients p: OutQueuePtr; pConn: ConnPtr; CursorCmdPtr: TYPE = POINTER TO cursor CmdObj; FOR pConn _ firstConn, pConn.next UNTIL pConn=NIL DO IF InSet[recipients, pConn.id] THEN IF pConn.output=active THEN pConn.outputWaiting _ TRUE ELSE recipients _ SetDiff[recipients, Set[pConn.id]] ENDLOOP; IF recipients#emptySet THEN IF firstOut#NIL AND pCmd.cmdType=cursor AND lastOut.pCmd.cmdType=cursor THEN BEGIN lastOut.recipients _ recipients; LOOPHOLE[lastOut.pCmd, CursorCmdPtr].point _ LOOPHOLE[pCmd, CursorCmdPtr].point END ELSE BEGIN p _ SystemDefs.AllocateHeapNode[SIZE[OutQueueEl]]; p^ _ OutQueueEl[NIL, recipients, CopyCmd[pCmd]]; IF firstOut=NIL THEN firstOut _ p ELSE lastOut.next _ p; lastOut _ p END END; Refresh: PUBLIC PROCEDURE = BEGIN SelectiveRefresh[setConn] END; SelectiveRefresh: PROCEDURE[recipients: ConnSet]= BEGIN OPEN IODefs; -- Write... pConn: ConnPtr; FOR pConn _ firstConn, pConn.next UNTIL pConn=NIL DO IF InSet[recipients, pConn.id] AND pConn.output=active THEN BEGIN WriteString["Refreshing "]; WriteLine[pConn.name]; WriteLine["Waiting for output process to finish"]; WHILE pConn.outputWaiting DO SchedDefs.ScheduleeYields ENDLOOP; PupDefs.ByteStreamPutMark[pConn.bs, BOF]; -- beginning of file EtherOutput[pConn.bs] -- send refresh END ENDLOOP END; RequestControl: PUBLIC PROCEDURE RETURNS[BOOLEAN]= BEGIN cmd: CmdObj; IF controlState=granted THEN RETURN[TRUE]; IF controlState=requested THEN RETURN[FALSE]; controlState _ requested; cmd _ [control[request, [pupVector.myNetwork, pupVector.myHost]]]; SendCmd[@cmd]; RETURN[FALSE] END; CmdWaiting: PUBLIC PROCEDURE RETURNS[BOOLEAN] = BEGIN RETURN[firstIn#NIL] END; NextCmd: PUBLIC PROCEDURE RETURNS[CmdPtr]= BEGIN tmp: InQueuePtr; pCmd: CmdPtr; originator: ConnId; thisGuy, allOthers: ConnSet; DO IF firstIn=NIL THEN RETURN[NIL]; pCmd _ firstIn.pCmd; tmp _ firstIn.next; originator _ firstIn.originator; thisGuy _ Set[originator]; allOthers _ SetDiff[setConn, thisGuy]; SystemDefs.FreeHeapNode[firstIn]; firstIn _ tmp; WITH pCmd SELECT FROM ether => EtherCmd[etherCmd, originator]; cursor => BEGIN SetMousePosition[point]; SelectiveSendCmd[pCmd, allOthers] END; mark => BEGIN Mark[point, icon]; SelectiveSendCmd[pCmd, allOthers] END; setCursorIcon => BEGIN [] _ SetCursorIcon[icon]; SelectiveSendCmd[pCmd, allOthers] END; control => SELECT action FROM request => SELECT controlState FROM passive => SelectiveSendCmd[pCmd, allOthers]; requested => BEGIN action _ reject; SelectiveSendCmd[pCmd, thisGuy] END; granted => BEGIN controlState _ passive; action _ grant; SelectiveSendCmd[pCmd, thisGuy] END; ENDCASE; grant => IF address=[pupVector.myNetwork, pupVector.myHost] OR address=[-1,0] THEN controlState _ granted ELSE SelectiveSendCmd[pCmd, allOthers]; reject => IF address=[pupVector.myNetwork, pupVector.myHost] THEN BEGIN IF controlState=requested THEN controlState _ passive END ELSE SelectiveSendCmd[pCmd, allOthers]; ENDCASE; ENDCASE => BEGIN SelectiveSendCmd[pCmd, allOthers]; RETURN[pCmd] END; FreeCmd[pCmd]; SchedDefs.ScheduleeYields -- let Pup Package do its thing ENDLOOP END; AllocateCmd: PUBLIC PROCEDURE RETURNS[CmdPtr]= BEGIN RETURN[SystemDefs.AllocateHeapNode[SIZE[CmdObj]]] END; FreeCmd: PUBLIC PROCEDURE[pCmd: CmdPtr]= BEGIN SystemDefs.FreeHeapNode[pCmd]; WITH pCmd SELECT FROM text => SystemDefs.FreeHeapString[s]; ENDCASE; END; CopyCmd: PROCEDURE[oldCmd: CmdPtr] RETURNS[newCmd: CmdPtr]= BEGIN newCmd _ AllocateCmd[]; newCmd^ _ oldCmd^ END; Connect: PUBLIC PROCEDURE= BEGIN ReadAddress; ConnectAndListen[connect]; IF setConn#emptySet THEN controlState _ passive END; Listen: PUBLIC PROCEDURE = BEGIN ConnectAndListen[listen] END; ConnectAndListen: PROCEDURE[action: {connect, listen}]= BEGIN IF myName=NIL THEN BEGIN IODefs.WriteString["Your name: "]; buffer.length _ 0; IODefs.ReadID[buffer]; IODefs.WriteChar[IODefs.CR]; myName _ SystemDefs.AllocateHeapString[buffer.length]; StringDefs.AppendString[myName,buffer]; END; IODefs.WriteLine["Making Pup Package"]; PupDefs.SetSandBarOK[sandBarOK]; pupVector _ PupDefs.GetPupVector[]; pupVector.dataWordsPerPup _ WordsPerPup; [] _ PupDefs.PupPackageReset[]; StreamDefs.SetIdleProc[SchedDefs.ScheduleeYields]; -- Idle process for keyboard input IF action=connect THEN WHILE CheckAddress[] DO OpenConnection; IF setConn = emptySet THEN ReadAddress ELSE EXIT ENDLOOP; IODefs.WriteLine["Making Listener"]; listener _ PupDefs.ListenerMakeByteStreams[listenSocket, Notify, NIL]; SetEtherOn[TRUE] END; Disconnect: PUBLIC PROCEDURE= BEGIN cmd: CmdObj; IF setConn#emptySet THEN BEGIN IF controlState = granted THEN BEGIN cmd _ [control[grant, [-1,0]]]; SendCmd[@cmd] END; CloseConnections END; IODefs.WriteLine["Destroying Listener"]; PupDefs.ListenerDestroy[listener]; IODefs.WriteLine["Destroying Pup Package"]; StreamDefs.ResetIdleProc; -- Reset idle process for keyboard input PupDefs.PupPackageDestroy; controlState _ granted; SetEtherOn[FALSE] END; ReadAddress: PROCEDURE= BEGIN OPEN IODefs; -- Write..., Read... WriteString["Connect to host: "]; buffer.length _ 0; IF hostName#NIL THEN BEGIN StringDefs.AppendString[buffer,hostName]; SystemDefs.FreeHeapString[hostName] END; IODefs.ReadID[buffer]; WriteChar[IODefs.CR]; hostName _ SystemDefs.AllocateHeapString[buffer.length]; StringDefs.AppendString[hostName,buffer] END; CheckAddress: PROCEDURE RETURNS[BOOLEAN]= BEGIN OPEN IODefs; -- Write..., Read... DO -- until a host hostName has been parsed or no name given IF hostName.length=0 THEN RETURN[FALSE]; IF PupDefs.GetAddress[@sendHim, hostName, errorText] THEN BEGIN -- hostName parsed correctly WriteString["Address parsed: "]; PupDefs.PrintAddress[@sendHim]; WriteChar[IODefs.CR]; RETURN[TRUE] END; Error[errorText]; ReadAddress ENDLOOP END; OpenConnection: PROCEDURE= BEGIN OPEN IODefs; -- Write..., Read... bs: PupDefs.ByteStream; WriteLine["Opening connection"]; bs _ PupDefs.ByteStreamMake[@sendConn]; BEGIN ENABLE PupDefs.StreamClosing => GOTO streamClosing; PupDefs.ByteStreamOpen[bs]; WriteString["Connected to "]; PupDefs.PrintAddress[@bs.myPacketStream.remote]; WriteChar[CR]; -- Exchange names PutString[bs,myName]; PupDefs.ByteStreamSendNow[bs]; GetString[bs,buffer]; WriteString["Connection accepted by "]; WriteString[buffer]; WriteChar[CR]; EtherInput[bs]; CreateIOProcesses[IdToConn[MakeConn[bs]]] EXITS streamClosing => BEGIN PupDefs.ByteStreamDestroy[bs]; Error["Remote end not responding"] END END END; CloseConnections: PROCEDURE= BEGIN -- This routine finishes output. The connections will be closed -- by OutputServer and destroyed by InputServer pConn: ConnPtr; IODefs.WriteLine["Closing connections"]; FOR pConn _ firstConn, pConn.next UNTIL pConn=NIL DO IF pConn.output=active THEN pConn.output _ closing ENDLOOP; UNTIL firstConn=NIL DO DestroyConn[firstConn] ENDLOOP END; Notify: PROCEDURE[unused: UNSPECIFIED, bs: PupDefs.ByteStream, remAddr: PupDefs.PAddress]= BEGIN IF setConn=fullSet THEN ERROR ELSE BEGIN ENABLE PupDefs.StreamClosing => GOTO streamClosing; GetString[bs,buffer]; -- Get name EtherReport[connecting, MakeConn[bs]] EXITS streamClosing => PupDefs.ByteStreamDestroy[bs] END END; MakeConn: PROCEDURE[bs: PupDefs.ByteStream] RETURNS[ConnId]= BEGIN pConn: ConnPtr _ SystemDefs.AllocateHeapNode[SIZE[Connection]]; id: ConnId; FOR id IN ConnId UNTIL NOT InSet[setConn, id] DO ENDLOOP; setConn _ SetUnion[setConn, Set[id]]; pConn^ _ [next: firstConn, id: id, output: finished, input: finished, closeReason:, outputWaiting: FALSE, bs: bs, name:]; pConn.name _ SystemDefs.AllocateHeapString[buffer.length]; StringDefs.AppendString[pConn.name,buffer]; firstConn _ pConn; RETURN[id] END; CreateIOProcesses: PROCEDURE[pConn: ConnPtr]= BEGIN pConn.input _ active; pConn.output _ active; SchedDefs.ScheduleeCreate[InputServer, pConn]; SchedDefs.ScheduleeCreate[OutputServer, pConn]; END; DestroyConn: PROCEDURE[pConn: ConnPtr]= BEGIN previousConn: ConnPtr; WHILE pConn.output#finished OR pConn.input#finished DO SchedDefs.ScheduleeYields ENDLOOP; IF pConn=firstConn THEN firstConn _ pConn.next ELSE FOR previousConn _ firstConn, previousConn.next DO IF previousConn.next=pConn THEN BEGIN previousConn.next _ pConn.next; EXIT END ENDLOOP; PupDefs.ByteStreamDestroy[pConn.bs]; SystemDefs.FreeHeapString[pConn.name]; setConn _ SetDiff[setConn, Set[pConn.id]]; SystemDefs.FreeHeapNode[pConn] END; EtherCmd: PROCEDURE[etherCmd: EtherCommand, sender: ConnId]= BEGIN OPEN IODefs; -- Write... pConn: ConnPtr _ IdToConn[sender]; TypeScriptWindow; SELECT etherCmd FROM input => BEGIN EtherInput[pConn.bs]; -- sender is sending picture pConn.input _ active; SelectiveRefresh[SetDiff[setConn, Set[sender]]] END; closing => BEGIN WriteString["Disconnected from "]; WriteString[pConn.name]; WriteString[" ... "]; SELECT pConn.closeReason FROM remoteClose => WriteString["remoteClose"]; transmissionTimeout => WriteString["transmissionTimeout"]; noRouteToNetwork => WriteString["noRouteToNetwork"]; ENDCASE => ERROR; DestroyConn[pConn]; IF controlState#granted THEN Disconnect END; connecting => BEGIN ENABLE PupDefs.StreamClosing => GOTO streamClosing; WriteString["Connection from "]; WriteString[pConn.name]; WriteString[" - "]; PupDefs.PrintAddress[@pConn.bs.myPacketStream.remote]; IF NOT Confirm[] THEN GOTO streamClosing; -- Send name PutString[pConn.bs,myName]; PupDefs.ByteStreamSendNow[pConn.bs]; -- Send picture EtherOutput[pConn.bs]; CreateIOProcesses[pConn] EXITS streamClosing => DestroyConn[pConn] END; ENDCASE; BitMapDisplay END; IdToConn: PROCEDURE[id: ConnId] RETURNS[pConn: ConnPtr]= BEGIN FOR pConn _ firstConn, pConn.next DO IF pConn=NIL THEN ERROR; IF pConn.id=id THEN EXIT ENDLOOP END; EtherInput: PROCEDURE[bs: PupDefs.ByteStream]= BEGIN OPEN IODefs; eof: BOOLEAN _ FALSE; EndOfFile: PROCEDURE RETURNS[BOOLEAN]= BEGIN RETURN[eof] END; GetWord: PROCEDURE RETURNS[WORD]= BEGIN bytePair: RECORD[left, right: PupDefs.Byte]; bytePair.left _ PupDefs.ByteStreamGet[bs]; bytePair.right _ PupDefs.ByteStreamGet[bs]; RETURN[LOOPHOLE[bytePair]] END; GetBlock: PROCEDURE[address: POINTER, words: INTEGER]= BEGIN mark: PupDefs.Byte; finger: PupDefs.BlockFinger _ DESCRIPTOR[address, words]; PupDefs.ByteStreamGetWholeBlock[bs, @finger !PupDefs.MarkInStream => BEGIN -- A mark=EOF in the stream means end of file. mark _ PupDefs.ByteStreamGet[bs]; IF mark=EOF THEN BEGIN eof _ TRUE; CONTINUE END END] END; WriteLine["Receiving picture"]; NewPicture; InputStatus[GetWord]; InputFile[EndOfFile, GetWord, GetBlock]; WriteLine["Finished receiving picture"] END; EtherOutput: PROCEDURE[bs: PupDefs.ByteStream]= BEGIN OPEN IODefs; PutWord: PROCEDURE[word: WORD]= BEGIN bytePair: RECORD[left, right: PupDefs.Byte] _ LOOPHOLE[word]; PupDefs.ByteStreamPut[bs, bytePair.left]; PupDefs.ByteStreamPut[bs, bytePair.right] END; PutBlock: PROCEDURE[address: POINTER, words: INTEGER]= BEGIN finger: PupDefs.BlockFinger _ DESCRIPTOR[address, words]; PupDefs.ByteStreamPutBlock[bs, @finger] END; WriteLine["Sending picture"]; OutputStatus[PutWord]; OutputFile[PutWord, PutBlock]; PupDefs.ByteStreamPutMark[bs, EOF]; -- indicate end of file WriteLine["Finished sending picture"]; END; OutputServer: PROCEDURE[pConn: ConnPtr]= BEGIN p:OutQueuePtr; finger: PupDefs.BlockFinger; DO IF pConn.outputWaiting THEN BEGIN DO -- delete messages that have no recipients left p _ firstOut; IF p.recipients#emptySet THEN EXIT; firstOut _ p.next; FreeCmd[p.pCmd]; SystemDefs.FreeHeapNode[p] ENDLOOP; DO -- send messages intended for this connection IF InSet[p.recipients, pConn.id] THEN BEGIN IF pConn.output=active OR pConn.output=closing THEN BEGIN finger _ DESCRIPTOR[p.pCmd, SIZE[CmdObj]]; PupDefs.ByteStreamPutBlock[pConn.bs, @finger -- potential Yield !PupDefs.StreamClosing => BEGIN pConn.output _ closed; CONTINUE END]; WITH p.pCmd SELECT FROM text => PutString[pConn.bs, s]; ENDCASE; END; p.recipients _ SetDiff[p.recipients, Set[pConn.id]] END; p _ p.next; IF p=NIL THEN EXIT ENDLOOP; pConn.outputWaiting _ FALSE; IF pConn.output=active OR pConn.output=closing THEN PupDefs.ByteStreamSendNow[pConn.bs !PupDefs.StreamClosing => BEGIN pConn.output _ closed; CONTINUE END] END; IF pConn.output#active THEN EXIT; -- no more messages for this connection SchedDefs.ScheduleeYields ENDLOOP; IF pConn.output=closing THEN PupDefs.ByteStreamClose[pConn.bs]; pConn.output _ finished END; InputServer: PROCEDURE[pConn: ConnPtr]= BEGIN cmd: CmdObj; mark: PupDefs.Byte; finger: PupDefs.BlockFinger _ DESCRIPTOR[@cmd, SIZE[CmdObj]]; DO PupDefs.ByteStreamGetWholeBlock[pConn.bs, @finger -- potential Yield !PupDefs.MarkInStream => BEGIN -- A mark=BOF in the stream means beginning of file. mark _ PupDefs.ByteStreamGet[pConn.bs]; IF mark=BOF THEN BEGIN pConn.input _ holding; cmd _ [ether[input]]; CONTINUE END END; PupDefs.StreamClosing => BEGIN pConn.closeReason _ why; GOTO closing END]; WITH cmd SELECT FROM text => BEGIN GetString[pConn.bs, buffer]; s _ SystemDefs.AllocateHeapString[buffer.length]; StringDefs.AppendString[s, buffer] END; ENDCASE; QueueCmd[@cmd, pConn.id]; WHILE pConn.input=holding DO SchedDefs.ScheduleeYields ENDLOOP; -- wait REPEAT closing => BEGIN pConn.input _ closed; IF pConn.output#finished THEN pConn.output _ closed; IF pConn.closeReason#localClose THEN EtherReport[closing, pConn.id] END ENDLOOP; pConn.input _ finished END; QueueCmd: PROCEDURE[pCmd: CmdPtr, id: ConnId]= BEGIN p: InQueuePtr; CursorCmdPtr: TYPE = POINTER TO cursor CmdObj; IF firstIn#NIL AND pCmd.cmdType=cursor AND lastIn.pCmd.cmdType=cursor THEN BEGIN lastIn.originator _ id; LOOPHOLE[lastIn.pCmd, CursorCmdPtr].point _ LOOPHOLE[pCmd, CursorCmdPtr].point END ELSE BEGIN p _ SystemDefs.AllocateHeapNode[SIZE[InQueueEl]]; p^ _ InQueueEl[NIL, id, CopyCmd[pCmd]]; IF firstIn=NIL THEN firstIn _ p ELSE lastIn.next _ p; lastIn _ p END END; EtherReport: PROCEDURE[etherCmd: EtherCommand, id: ConnId]= BEGIN cmd: CmdObj _ [ether[etherCmd]]; QueueCmd[@cmd, id] END; END.(1799)\613b10B189b1B4b17B1b5B1b14B1b17B1b17B24b7B2b5B18b10B2b10B20b12B2b10B2b12B2b10B33b3B62b3B69b10B245b7B31b7B35b9B75b10B77b10B30b11B48b9B62b8B10t10 8t0 30b11B18b12B36b7B125b6B119b8B45b9B17b7B22b7B2b6B50b8B2b7B56b6B17b9B46b8B2b6B17b12B190b8B42b7B48b3B91b5B119b8B94b7B263b9B383b9B345b7B80b16B916b7B59b16B58b8B478b14B291b10B70b7B1606b11B100b7B154b7B109b7B128b6B58b16B794t10 23t0 56b10B261t10 15t0 211b11B37b8B2b7B343b12B54b8B2b7B399b14B37b8B2b7B694b16B382b6B357b8B519b17B191b11B532b8B77b8B1271b8B174b10B949b11B655b12B1303b11B1064b8B523b11B