@@ -52,6 +52,14 @@ type Service struct {
5252 onSessionActivity func (* Session )
5353}
5454
55+ // Spec-to-implementation mapping notes:
56+ // - No separate SPGetSession method: session acceptance is handled inside
57+ // handleOpenSession.
58+ // - No separate SPGetRequest/SPCmdReply/SPWrtReply/SPWrtContinue methods:
59+ // these are represented by handleCommand/handleASPWrite/completeWrite.
60+ // - No separate SPNewStatus method: status is sourced from
61+ // commandHandler.GetStatus() when servicing SPGetStatus.
62+
5563// requestContext is what the host service threads through atp.HandleInbound
5664// so the Sender bridge can use router.Reply on the way out.
5765type requestContext struct {
@@ -91,7 +99,13 @@ func (s *Service) SetCommandHandler(handler afp.CommandHandler) {
9199// Socket returns the socket number this service listens on.
92100func (s * Service ) Socket () uint8 { return ServerSocket }
93101
94- // Start performs SPGetParms/SPInit, registers NBP, and stands up the engine.
102+ // Start performs server-side initialization corresponding to:
103+ // - SPGetParms (server end; server ASP client -> ASP)
104+ // - SPInit (server end; server ASP client -> ASP)
105+ //
106+ // In this implementation, SPInit is represented by wiring the SLS endpoint and
107+ // validating ServiceStatusBlock size against QuantumSize before accepting
108+ // traffic.
95109func (s * Service ) Start (router service.Router ) error {
96110 s .router = router
97111
@@ -140,7 +154,14 @@ func (s *Service) registerInZone(zone []byte) {
140154}
141155
142156// Stop unregisters NBP and shuts everything down.
157+ // Before teardown, it sends a best-effort SPAttention(ServerGoingDown) to
158+ // active sessions so workstation clients can terminate cleanly.
143159func (s * Service ) Stop () error {
160+ for _ , sessID := range s .sm .SessionIDs () {
161+ if err := s .SendAttention (sessID , AspAttnServerGoingDown ); err != nil {
162+ netlog .Debug ("[ASP] Stop: SendAttention failed for sess=%d: %v" , sessID , err )
163+ }
164+ }
144165 for _ , z := range s .registeredZones {
145166 s .nbp .UnregisterName ([]byte (s .serverName ), []byte (nbpType ), z )
146167 }
@@ -188,7 +209,11 @@ func (s *Service) sendBridge(src, dst atp.Address, payload []byte, hint any) err
188209 return s .router .Route (dg , true )
189210}
190211
191- // handleATPRequest is the atp.RequestHandler dispatched for every TReq.
212+ // handleATPRequest is the server-side dispatcher for ASP network requests.
213+ // Direction by SPFunction per spec:
214+ // - workstation -> server: OpenSess, GetStatus, Command, Write, CloseSess
215+ // - both directions: Tickle
216+ //
192217// It demultiplexes on the ASP function code in the user-data MSB.
193218func (s * Service ) handleATPRequest (in atp.IncomingRequest , reply atp.Replier ) {
194219 aspCmd := uint8 ((in .UserBytes >> 24 ) & 0xFF )
@@ -258,20 +283,43 @@ func (s *Service) chunkResponse(data []byte, bitmap uint8) [][]byte {
258283 return bufs
259284}
260285
286+ // handleGetStatus implements SPGetStatus servicing on the server side
287+ // (workstation ASP client -> server SLS).
288+ //
289+ // Related server-end calls from the spec:
290+ // - SPInit provides initial ServiceStatusBlock.
291+ // - SPNewStatus updates status for later SPGetStatus calls.
292+ //
293+ // In this code, status comes from commandHandler.GetStatus() at request time.
261294func (s * Service ) handleGetStatus (in atp.IncomingRequest , reply atp.Replier ) {
262295 var status []byte
263296 if s .commandHandler != nil {
264297 status = s .commandHandler .GetStatus ()
265298 }
299+ if len (status ) > s .effectiveQuantumSize () {
300+ netlog .Info ("[ASP] GetStatus: ServiceStatusBlockSize=%d exceeds QuantumSize=%d (SPErrorSizeErr)" ,
301+ len (status ), s .effectiveQuantumSize ())
302+ reply (atp.ResponseMessage {
303+ Buffers : [][]byte {nil },
304+ UserBytes : []uint32 {errToUserBytes (SPErrorSizeErr )},
305+ })
306+ return
307+ }
266308 reply (atp.ResponseMessage {Buffers : s .chunkResponse (status , in .Bitmap )})
267309}
268310
311+ // handleOpenSession implements SPOpenSession handling at the server side
312+ // (workstation ASP client -> server SLS).
313+ //
314+ // Spec note: classic ASP may gate acceptance on pending SPGetSession calls.
315+ // This implementation models SPGetSession implicitly by accepting while session
316+ // capacity is available.
269317func (s * Service ) handleOpenSession (in atp.IncomingRequest , reply atp.Replier ) {
270318 pkt := ParseOpenSessPacket (in .UserBytes )
271319
272320 if pkt .VersionNum != ASPVersion {
273321 netlog .Info ("[ASP] OpenSess: bad version 0x%04X from %s" , pkt .VersionNum , in .Src )
274- r := OpenSessReplyPacket {SSSSocket : ServerSocket , ErrorCode : aspBadVersNum }
322+ r := OpenSessReplyPacket {SSSSocket : ServerSocket , ErrorCode : SPErrorBadVersNum }
275323 reply (atp.ResponseMessage {
276324 Buffers : [][]byte {nil },
277325 UserBytes : []uint32 {r .MarshalUserData ()},
@@ -281,7 +329,7 @@ func (s *Service) handleOpenSession(in atp.IncomingRequest, reply atp.Replier) {
281329
282330 sess := s .sm .Open (in .Src .Net , in .Src .Node , pkt .WSSSocket , in .Local .Net , in .Local .Node )
283331 if sess == nil {
284- r := OpenSessReplyPacket {SSSSocket : ServerSocket , ErrorCode : aspTooManyClients }
332+ r := OpenSessReplyPacket {SSSSocket : ServerSocket , ErrorCode : SPErrorTooManyClients }
285333 reply (atp.ResponseMessage {
286334 Buffers : [][]byte {nil },
287335 UserBytes : []uint32 {r .MarshalUserData ()},
@@ -292,29 +340,57 @@ func (s *Service) handleOpenSession(in atp.IncomingRequest, reply atp.Replier) {
292340 if s .onSessionOpen != nil {
293341 s .onSessionOpen (sess )
294342 }
295- r := OpenSessReplyPacket {SSSSocket : ServerSocket , SessionID : sess .ID , ErrorCode : aspNoErr }
343+ r := OpenSessReplyPacket {SSSSocket : ServerSocket , SessionID : sess .ID , ErrorCode : SPErrorNoError }
296344 reply (atp.ResponseMessage {
297345 Buffers : [][]byte {nil },
298346 UserBytes : []uint32 {r .MarshalUserData ()},
299347 })
300348}
301349
350+ // handleCloseSession handles CloseSess packets from workstation -> server and
351+ // maps them to server-side SPCloseSession semantics.
302352func (s * Service ) handleCloseSession (in atp.IncomingRequest , reply atp.Replier ) {
303353 pkt := ParseCloseSessPacket (in .UserBytes )
354+ if s .sm .Get (pkt .SessionID ) == nil {
355+ netlog .Debug ("[ASP] CloseSess: unknown SessRefNum=%d" , pkt .SessionID )
356+ reply (atp.ResponseMessage {
357+ Buffers : [][]byte {nil },
358+ UserBytes : []uint32 {errToUserBytes (SPErrorParamErr )},
359+ })
360+ return
361+ }
304362 s .sm .Close (pkt .SessionID )
305363 reply (atp.ResponseMessage {
306364 Buffers : [][]byte {nil },
307365 UserBytes : []uint32 {CloseSessReplyUserData ()},
308366 })
309367}
310368
369+ // handleCommand implements the SPCommand/SPCmdReply transaction path:
370+ // 1. workstation -> server Command request
371+ // 2. server -> workstation CmdReply result
372+ //
373+ // In classic server-end API terms, this combines SPGetRequest (Command type)
374+ // and SPCmdReply.
311375func (s * Service ) handleCommand (in atp.IncomingRequest , reply atp.Replier ) {
312376 receivedAt := time .Now ()
313377 pkt := ParseCommandPacket (in .UserBytes , in .Data )
378+ if len (pkt .CmdBlock ) > s .effectiveMaxCmdSize () {
379+ netlog .Debug ("[ASP] Command: CmdBlockSize=%d exceeds MaxCmdSize=%d (SPErrorSizeErr)" ,
380+ len (pkt .CmdBlock ), s .effectiveMaxCmdSize ())
381+ reply (atp.ResponseMessage {
382+ Buffers : [][]byte {nil },
383+ UserBytes : []uint32 {errToUserBytes (SPErrorSizeErr )},
384+ })
385+ return
386+ }
314387 sess := s .sm .Get (pkt .SessionID )
315388 if sess == nil {
316- netlog .Debug ("[ASP] Command: unknown session %d" , pkt .SessionID )
317- reply (atp.ResponseMessage {Buffers : [][]byte {nil }})
389+ netlog .Debug ("[ASP] Command: unknown SessRefNum=%d" , pkt .SessionID )
390+ reply (atp.ResponseMessage {
391+ Buffers : [][]byte {nil },
392+ UserBytes : []uint32 {errToUserBytes (SPErrorParamErr )},
393+ })
318394 return
319395 }
320396 sess .touchActivity ()
@@ -336,10 +412,29 @@ func (s *Service) handleCommand(in atp.IncomingRequest, reply atp.Replier) {
336412 if s .commandHandler != nil {
337413 replyData , errCode = s .commandHandler .HandleCommand (pkt .CmdBlock )
338414 }
415+ if len (replyData ) > s .effectiveQuantumSize () {
416+ netlog .Debug ("[ASP] Command: SessRefNum=%d CmdReplyDataSize=%d exceeds QuantumSize=%d (SPErrorSizeErr)" ,
417+ pkt .SessionID , len (replyData ), s .effectiveQuantumSize ())
418+ reply (atp.ResponseMessage {
419+ Buffers : [][]byte {nil },
420+ UserBytes : []uint32 {errToUserBytes (SPErrorSizeErr )},
421+ })
422+ return
423+ }
424+ if wsCap := bitmapMaxBytes (in .Bitmap ); wsCap > 0 && len (replyData ) > wsCap {
425+ netlog .Debug ("[ASP] Command: reply %d exceeds workstation capacity %d (SPErrorBufTooSmall)" ,
426+ len (replyData ), wsCap )
427+ bufs := s .chunkResponse (replyData , in .Bitmap )
428+ reply (atp.ResponseMessage {
429+ Buffers : bufs ,
430+ UserBytes : []uint32 {errToUserBytes (SPErrorBufTooSmall )},
431+ })
432+ return
433+ }
339434 bufs := s .chunkResponse (replyData , in .Bitmap )
340435 reply (atp.ResponseMessage {
341436 Buffers : bufs ,
342- UserBytes : []uint32 {uint32 (errCode )},
437+ UserBytes : []uint32 {errToUserBytes (errCode )},
343438 })
344439 elapsed := time .Since (receivedAt )
345440 replyBytes := 0
@@ -353,24 +448,36 @@ func (s *Service) handleCommand(in atp.IncomingRequest, reply atp.Replier) {
353448 }
354449}
355450
356- // handleASPWrite implements phase 1 of the two-phase ASP write protocol :
451+ // handleASPWrite implements SPWrite handling ( phase 1 of 2) on the server side :
357452//
358- // 1. Workstation → server: Write TReq with the AFP command block.
359- // 2. Server → workstation WSS: WriteContinue TReq carrying the buffer size.
360- // 3. Workstation → server (TResp to WriteContinue): the actual write data.
361- // 4. Server → workstation: TResp to the original Write TReq with the AFP result.
453+ // 1. workstation -> server: Write TReq with command block
454+ // 2. server -> workstation: SPWrtContinue ( WriteContinue TReq)
455+ // 3. workstation -> server: WriteContinue TResp with write data
456+ // 4. server -> workstation: SPWrtReply for the original Write TReq
362457//
363458// We capture `reply` from step 1 and invoke it in step 4 once the
364459// WriteContinue Pending resolves with the data.
460+ //
461+ // In classic server-end API terms, this combines SPGetRequest (Write type)
462+ // with SPWrtContinue and SPWrtReply.
365463func (s * Service ) handleASPWrite (in atp.IncomingRequest , reply atp.Replier ) {
366464 receivedAt := time .Now ()
367465 pkt := ParseWritePacket (in .UserBytes , in .Data )
466+ if len (pkt .CmdBlock ) > s .effectiveMaxCmdSize () {
467+ netlog .Debug ("[ASP] Write: CmdBlockSize=%d exceeds MaxCmdSize=%d (SPErrorSizeErr)" ,
468+ len (pkt .CmdBlock ), s .effectiveMaxCmdSize ())
469+ reply (atp.ResponseMessage {
470+ Buffers : [][]byte {nil },
471+ UserBytes : []uint32 {errToUserBytes (SPErrorSizeErr )},
472+ })
473+ return
474+ }
368475 sess := s .sm .Get (pkt .SessionID )
369476 if sess == nil {
370- netlog .Debug ("[ASP] Write: unknown session %d" , pkt .SessionID )
477+ netlog .Debug ("[ASP] Write: unknown SessRefNum= %d" , pkt .SessionID )
371478 reply (atp.ResponseMessage {
372479 Buffers : [][]byte {nil },
373- UserBytes : []uint32 {errToUserBytes (aspParamErr )},
480+ UserBytes : []uint32 {errToUserBytes (SPErrorParamErr )},
374481 })
375482 return
376483 }
@@ -387,7 +494,17 @@ func (s *Service) handleASPWrite(in atp.IncomingRequest, reply atp.Replier) {
387494
388495 var wantBytes uint32
389496 if len (pkt .CmdBlock ) >= 12 {
390- wantBytes = binary .BigEndian .Uint32 (pkt .CmdBlock [8 :12 ])
497+ rawWantBytes := int32 (binary .BigEndian .Uint32 (pkt .CmdBlock [8 :12 ]))
498+ if rawWantBytes < 0 {
499+ netlog .Debug ("[ASP] Write: negative BufferSize=%d in SPWrtContinue request metadata (SPErrorParamErr)" ,
500+ rawWantBytes )
501+ reply (atp.ResponseMessage {
502+ Buffers : [][]byte {nil },
503+ UserBytes : []uint32 {errToUserBytes (SPErrorParamErr )},
504+ })
505+ return
506+ }
507+ wantBytes = uint32 (rawWantBytes )
391508 }
392509 if max := uint32 (s .quantumSize ); wantBytes > max {
393510 netlog .Info ("[ASP] Write sess=%d: clamping wantBytes %d→%d" ,
@@ -431,7 +548,7 @@ func (s *Service) handleASPWrite(in atp.IncomingRequest, reply atp.Replier) {
431548 netlog .Debug ("[ASP] Write sess=%d: WriteContinue SendRequest failed: %v" , pkt .SessionID , err )
432549 reply (atp.ResponseMessage {
433550 Buffers : [][]byte {nil },
434- UserBytes : []uint32 {errToUserBytes (aspParamErr )},
551+ UserBytes : []uint32 {errToUserBytes (SPErrorParamErr )},
435552 })
436553 return
437554 }
@@ -455,6 +572,8 @@ func (s *Service) handleASPWrite(in atp.IncomingRequest, reply atp.Replier) {
455572 go s .completeWrite (sess , pkt .CmdBlock , wantBytes , pending , reply , in .Bitmap , receivedAt , wcSentAt )
456573}
457574
575+ // completeWrite finalizes the server-side SPWrite flow after SPWrtContinue has
576+ // returned write data, then sends the SPWrtReply-equivalent result.
458577func (s * Service ) completeWrite (sess * Session , cmdBlock []byte , wantBytes uint32 ,
459578 pending * atp.Pending , reply atp.Replier , bitmap uint8 , receivedAt , wcSentAt time.Time ) {
460579 resp , err := pending .Wait (context .Background ())
@@ -468,7 +587,7 @@ func (s *Service) completeWrite(sess *Session, cmdBlock []byte, wantBytes uint32
468587 netlog .Debug ("[ASP] Write sess=%d: WriteContinue failed after %v: %v" , sess .ID , wcRTT .Round (time .Millisecond ), err )
469588 reply (atp.ResponseMessage {
470589 Buffers : [][]byte {nil },
471- UserBytes : []uint32 {errToUserBytes (aspParamErr )},
590+ UserBytes : []uint32 {errToUserBytes (SPErrorParamErr )},
472591 })
473592 return
474593 }
@@ -493,10 +612,29 @@ func (s *Service) completeWrite(sess *Session, cmdBlock []byte, wantBytes uint32
493612 if s .commandHandler != nil {
494613 replyData , errCode = s .commandHandler .HandleCommand (full )
495614 }
615+ if len (replyData ) > s .effectiveQuantumSize () {
616+ netlog .Debug ("[ASP] Write: SessRefNum=%d WrtReplyDataSize=%d exceeds QuantumSize=%d (SPErrorSizeErr)" ,
617+ sess .ID , len (replyData ), s .effectiveQuantumSize ())
618+ reply (atp.ResponseMessage {
619+ Buffers : [][]byte {nil },
620+ UserBytes : []uint32 {errToUserBytes (SPErrorSizeErr )},
621+ })
622+ return
623+ }
624+ if wsCap := bitmapMaxBytes (bitmap ); wsCap > 0 && len (replyData ) > wsCap {
625+ netlog .Debug ("[ASP] Write: reply %d exceeds workstation capacity %d (SPErrorBufTooSmall)" ,
626+ len (replyData ), wsCap )
627+ bufs := s .chunkResponse (replyData , bitmap )
628+ reply (atp.ResponseMessage {
629+ Buffers : bufs ,
630+ UserBytes : []uint32 {errToUserBytes (SPErrorBufTooSmall )},
631+ })
632+ return
633+ }
496634 bufs := s .chunkResponse (replyData , bitmap )
497635 reply (atp.ResponseMessage {
498636 Buffers : bufs ,
499- UserBytes : []uint32 {uint32 (errCode )},
637+ UserBytes : []uint32 {errToUserBytes (errCode )},
500638 })
501639 totalElapsed := time .Since (receivedAt )
502640 replyBytes := 0
@@ -536,19 +674,36 @@ func (s *Service) sendTickle(sess *Session) {
536674// uint32 wire encoding without tripping Go's constant-overflow check.
537675func errToUserBytes (code int32 ) uint32 { return uint32 (code ) }
538676
539- // SPGetParms returns the maximum command block size and quantum size.
677+ func (s * Service ) effectiveQuantumSize () int {
678+ if s .quantumSize > 0 {
679+ return s .quantumSize
680+ }
681+ return QuantumSize
682+ }
683+
684+ func (s * Service ) effectiveMaxCmdSize () int {
685+ if s .maxCmdSize > 0 {
686+ return s .maxCmdSize
687+ }
688+ return ATPMaxData
689+ }
690+
691+ // SPGetParms implements SPGetParms (both ends): ASP client -> ASP local query
692+ // for MaxCmdSize and QuantumSize.
540693func (s * Service ) SPGetParms () GetParmsResult {
541694 return GetParmsResult {MaxCmdSize : ATPMaxData , QuantumSize : QuantumSize }
542695}
543696
544- // SendAttention sends an ASP Attention to the workstation end of a session.
697+ // SendAttention implements server-side SPAttention
698+ // (server ASP client -> workstation end of an open session).
545699func (s * Service ) SendAttention (sessID uint8 , code uint16 ) error {
546700 if code == 0 {
547701 return fmt .Errorf ("ASP: attention code must be non-zero" )
548702 }
549703 sess := s .sm .Get (sessID )
550704 if sess == nil {
551- return fmt .Errorf ("ASP: unknown session %d" , sessID )
705+ netlog .Debug ("[ASP] Attention: unknown SessRefNum=%d" , sessID )
706+ return fmt .Errorf ("ASP SPAttention: unknown SessRefNum=%d (SPErrorParamErr=%d)" , sessID , SPErrorParamErr )
552707 }
553708 if s .endpoint == nil {
554709 return fmt .Errorf ("ASP: not started" )
0 commit comments