Skip to content

Commit 29ee373

Browse files
authored
Merge pull request #5 from ObsoleteMadness/fix-asp-reply
Better ASP Error handling.
2 parents 8e00271 + f55a0b5 commit 29ee373

5 files changed

Lines changed: 378 additions & 35 deletions

File tree

server.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ bridge_host_mac = ; optional host adapter MAC for Wi-Fi bridge sh
2525

2626
[MacIP]
2727
; MacIP Gateway Settings. Allows TCP over DDP.
28-
enabled = false ; true to enable MacIP Gateway, false to disable
28+
enabled = true ; true to enable MacIP Gateway, false to disable
2929
mode = pcap ; modes are pcap or nat.
3030
zone = ; MacIP Gateway Zone, defaults to EtherTalk zone, otherwise the first zone detected.
3131
nat_subnet = ; in NAT mode, the subnet to use (eg 192.168.100.0/24)

service/asp/asp.go

Lines changed: 177 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
5765
type requestContext struct {
@@ -91,7 +99,13 @@ func (s *Service) SetCommandHandler(handler afp.CommandHandler) {
9199
// Socket returns the socket number this service listens on.
92100
func (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.
95109
func (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.
143159
func (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.
193218
func (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.
261294
func (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.
269317
func (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.
302352
func (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.
311375
func (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.
365463
func (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.
458577
func (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.
537675
func 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.
540693
func (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).
545699
func (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

Comments
 (0)