diff --git a/definitions.go b/definitions.go index 7ea4524..d321e30 100644 --- a/definitions.go +++ b/definitions.go @@ -13,7 +13,7 @@ const ( IPProtoGGP IPProto = 3 // Gateway-to-Gateway [RFC823] IPProtoIPv4 IPProto = 4 // IPv4 encapsulation [RFC2003] IPProtoST IPProto = 5 // Stream [RFC1190, RFC1819] - IPProtoTCP IPProto = 6 // Transmission Control [RFC9293] + IPProtoTCP IPProto = 6 // TCP [RFC9293] IPProtoCBT IPProto = 7 // CBT [Ballardie] IPProtoEGP IPProto = 8 // Exterior Gateway Protocol [RFC888] IPProtoIGP IPProto = 9 // any private interior gateway (used by Cisco for their IGRP) @@ -24,7 +24,7 @@ const ( IPProtoEMCON IPProto = 14 // EMCON IPProtoXNET IPProto = 15 // Cross Net Debugger IPProtoCHAOS IPProto = 16 // Chaos - IPProtoUDP IPProto = 17 // User Datagram [RFC768] + IPProtoUDP IPProto = 17 // UDP [RFC768] IPProtoMUX IPProto = 18 // Multiplexing IPProtoDCNMEAS IPProto = 19 // DCN Measurement Subsystems IPProtoHMP IPProto = 20 // Host Monitoring [RFC869] diff --git a/examples/stackbasic/main.go b/examples/stackbasic/main.go index a299568..fec7a14 100644 --- a/examples/stackbasic/main.go +++ b/examples/stackbasic/main.go @@ -19,6 +19,7 @@ import ( "github.com/soypat/lneto/internal" "github.com/soypat/lneto/internal/ltesto" "github.com/soypat/lneto/internet" + "github.com/soypat/lneto/internet/pcap" "github.com/soypat/lneto/ipv4" "github.com/soypat/lneto/tcp" ) @@ -65,14 +66,22 @@ func main() { hdr.Reset(make([]byte, 0, 1024)) const standbyDuration = 5 * time.Second lastHit := time.Now().Add(-standbyDuration) + var cap pcap.PacketBreakdown for { nread, err := tap.Read(buf[:]) if err != nil { slogger.error("tap-err", slog.String("err", err.Error())) log.Fatal(err) } else if nread > 0 { - // debugEthPacket(nil, "IN ", buf[:nread]) - // fmt.Println("INHEX ", debugHex(buf[:nread])) + frames, err := cap.CaptureEthernet(nil, buf[:nread], 0) + if err == nil { + flags := getTCPFlags(frames, buf[:nread]) + if flags == 0 { + fmt.Println("IN", time.Now().Format("15:04:05.000"), frames) + } else { + fmt.Println("IN", time.Now().Format("15:04:05.000"), frames, flags.String()) + } + } err = lStack.RecvEth(buf[:nread]) if err != nil { slogger.error("recv", slog.String("err", err.Error()), slog.Int("plen", nread)) @@ -80,11 +89,18 @@ func main() { } doHTTP(handler, &hdr) nw, err := lStack.HandleEth(buf[:]) - debugEthPacket(nil, "OUT", buf[:nw]) if err != nil { slogger.error("handle", slog.String("err", err.Error())) } else if nw > 0 { - // fmt.Println("OUTHEX ", debugHex(buf[:nread])) + frames, err := cap.CaptureEthernet(nil, buf[:nread], 0) + if err == nil { + flags := getTCPFlags(frames, buf[:nread]) + if flags == 0 { + fmt.Println("OU", time.Now().Format("15:04:05.000"), frames) + } else { + fmt.Println("OU", time.Now().Format("15:04:05.000"), frames, flags.String()) + } + } _, err = tap.Write(buf[:nw]) if err != nil { log.Fatal(err) @@ -92,7 +108,7 @@ func main() { } hit := nread > 0 || nw > 0 if hit { - slogger.info("exchange", slog.Int("read", nread), slog.Int("nwrite", nw)) + // slogger.info("exchange", slog.Int("read", nread), slog.Int("nwrite", nw)) lastHit = time.Now() } else { if time.Since(lastHit) > standbyDuration { @@ -379,3 +395,21 @@ func debugHex(b []byte) string { } const tblhex = "0123456789abcdef" + +func getTCPFlags(frames []pcap.Frame, pkt []byte) (flags tcp.Flags) { + for i := range frames { + if frames[i].Protocol != lneto.IPProtoTCP { + continue + } + iflags, err := frames[i].FieldByClass(pcap.FieldClassFlags) + if err != nil { + return 0 + } + v, err := frames[i].FieldAsUint(iflags, pkt) + if err != nil { + return 0 + } + return tcp.Flags(v) + } + return 0 +} diff --git a/examples/tap/main.go b/examples/tap/main.go index 61a096a..a1e68f4 100644 --- a/examples/tap/main.go +++ b/examples/tap/main.go @@ -11,8 +11,10 @@ import ( "runtime" "time" + "github.com/soypat/lneto" "github.com/soypat/lneto/internal/ltesto" "github.com/soypat/lneto/internet/pcap" + "github.com/soypat/lneto/tcp" ) func main() { @@ -45,7 +47,14 @@ func run() error { captime := time.Now() frames, err := cap.CaptureEthernet(nil, pkt, 0) if err == nil { - fmt.Println(channel, captime.Format("15:04:05.000"), frames) + flags, src, dst := getTCPData(frames, pkt) + if flags != 0 { + fmt.Println(channel, captime.Format("15:04:05.000"), frames, flags.String(), src, "->", dst) + } else { + fmt.Println(channel, captime.Format("15:04:05.000"), frames) + } + } else { + fmt.Println(channel, captime.Format("15:04:05.000"), "ERR", frames, err.Error()) } }) hwaddr, err := sv.HardwareAddress6() @@ -74,3 +83,27 @@ func run() error { } } } + +func getTCPData(frames []pcap.Frame, pkt []byte) (flags tcp.Flags, src, dst uint16) { + for i := range frames { + if frames[i].Protocol != lneto.IPProtoTCP { + continue + } + return tcp.Flags(getFrameClassUint(frames[i], pkt, pcap.FieldClassFlags)), + uint16(getFrameClassUint(frames[i], pkt, pcap.FieldClassSrc)), + uint16(getFrameClassUint(frames[i], pkt, pcap.FieldClassDst)) + } + return 0, 0, 0 +} + +func getFrameClassUint(frame pcap.Frame, pkt []byte, class pcap.FieldClass) uint64 { + iflags, err := frame.FieldByClass(class) + if err != nil { + return 0 + } + v, err := frame.FieldAsUint(iflags, pkt) + if err != nil { + return 0 + } + return v +} diff --git a/examples/tcpclient/main.go b/examples/tcpclient/main.go index 24e4afc..66f0459 100644 --- a/examples/tcpclient/main.go +++ b/examples/tcpclient/main.go @@ -7,6 +7,7 @@ import ( "os" "os/signal" "syscall" + "time" "github.com/soypat/lneto/http/httpraw" ) @@ -33,7 +34,7 @@ func run() error { return err } - fmt.Println("dialing...") + fmt.Println(time.Now().Format("15:04:05.000"), "dialing...") conn, err := net.DialTCP("tcp4", &net.TCPAddr{IP: []byte{192, 168, 10, 1}, Port: port}, &net.TCPAddr{IP: []byte{192, 168, 10, 2}, Port: 80}) if err != nil { return err @@ -47,9 +48,8 @@ func run() error { conn.Close() os.Exit(0) }() - fmt.Println("reading...") + fmt.Println(time.Now().Format("15:04:05.000"), "writing...") conn.Write(req) - hdr.Reset(nil) var needMore bool = true for needMore { @@ -63,6 +63,6 @@ func run() error { if err != nil { return err } - fmt.Println("got HTTP:\n", hdr.String()) + fmt.Println(time.Now().Format("15:04:05.000"), "got HTTP:\n", hdr.String()) return nil } diff --git a/internet/basicstack.go b/internet/basicstack.go index b5495af..efd4b5e 100644 --- a/internet/basicstack.go +++ b/internet/basicstack.go @@ -4,7 +4,9 @@ import ( "errors" "io" "log/slog" + "net" "net/netip" + "slices" "github.com/soypat/lneto" "github.com/soypat/lneto/internal" @@ -66,7 +68,12 @@ func (sb *StackBasic) Recv(frame []byte) error { proto := ifrm.Protocol() if h.proto == proto { sb.info("iprecv", slog.String("ipproto", proto.String()), slog.Int("plen", int(totalLen))) - return h.recv(frame[:totalLen], off) + err = h.recv(frame[:totalLen], off) + if err == net.ErrClosed { + sb.info("ipclose", slog.String("proto", proto.String())) + sb.handlers = slices.Delete(sb.handlers, i, i+1) + } + return err } } } diff --git a/internet/pcap/capture.go b/internet/pcap/capture.go index 50ddbee..8eab9be 100644 --- a/internet/pcap/capture.go +++ b/internet/pcap/capture.go @@ -12,9 +12,13 @@ import ( "github.com/soypat/lneto/ethernet" "github.com/soypat/lneto/http/httpraw" "github.com/soypat/lneto/ipv4" + "github.com/soypat/lneto/ipv6" "github.com/soypat/lneto/tcp" + "github.com/soypat/lneto/udp" ) +const unknownPayloadProto = "payload?" + type PacketBreakdown struct { hdr httpraw.Header vld lneto.Validator @@ -115,6 +119,29 @@ func (pc *PacketBreakdown) CaptureARP(dst []Frame, pkt []byte, bitOffset int) ([ return dst, nil } +func (pc *PacketBreakdown) CaptureIPv6(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) { + if bitOffset%8 != 0 { + return dst, errors.New("IPv6 must be parsed at byte boundary") + } + ifrm6, err := ipv6.NewFrame(pkt[bitOffset/8:]) + if err != nil { + return dst, err + } + ifrm6.ValidateSize(pc.validator()) + if pc.validator().HasError() { + return dst, pc.validator().Err() + } + finfo := Frame{ + Protocol: ethernet.TypeIPv6, + PacketBitOffset: bitOffset, + } + finfo.Fields = append(finfo.Fields, baseIPv6Fields[:]...) + dst = append(dst, finfo) + proto := ifrm6.NextHeader() + end := 40 * octet + return pc.captureIPProto(proto, dst, pkt, end) +} + func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) { if bitOffset%8 != 0 { return dst, errors.New("IPv4 must be parsed at byte boundary") @@ -143,11 +170,17 @@ func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) ( proto := ifrm4.Protocol() dst = append(dst, finfo) end := bitOffset + octet*ifrm4.HeaderLength() + return pc.captureIPProto(proto, dst, pkt, end) +} + +func (pc *PacketBreakdown) captureIPProto(proto lneto.IPProto, dst []Frame, pkt []byte, bitOffset int) (_ []Frame, err error) { switch proto { case lneto.IPProtoTCP: - dst, err = pc.CaptureTCP(dst, pkt, end) + dst, err = pc.CaptureTCP(dst, pkt, bitOffset) + case lneto.IPProtoUDP: + dst, err = pc.CaptureUDP(dst, pkt, bitOffset) default: - dst = append(dst, remainingFrameInfo(proto, 0, end, octet*len(pkt))) + dst = append(dst, remainingFrameInfo(proto, 0, bitOffset, octet*len(pkt))) } return dst, err } @@ -183,12 +216,35 @@ func (pc *PacketBreakdown) CaptureTCP(dst []Frame, pkt []byte, bitOffset int) ([ if len(payload) > 0 { dst, err = pc.CaptureHTTP(dst, pkt, end) if err != nil { - dst = append(dst, remainingFrameInfo(nil, FieldClassPayload, end, len(pkt))) + dst = append(dst, remainingFrameInfo(unknownPayloadProto, FieldClassPayload, end, octet*len(pkt))) } } return dst, nil } +func (pc *PacketBreakdown) CaptureUDP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) { + if bitOffset%8 != 0 { + return dst, errors.New("UDP must be parsed at byte boundary") + } + ufrm, err := udp.NewFrame(pkt[bitOffset/8:]) + if err != nil { + return dst, err + } + ufrm.ValidateSize(pc.validator()) + if pc.validator().HasError() { + return dst, pc.validator().Err() + } + finfo := Frame{ + Protocol: lneto.IPProtoUDP, + PacketBitOffset: bitOffset, + } + finfo.Fields = append(finfo.Fields, baseUDPFields[:]...) + dst = append(dst, finfo) + end := bitOffset + 8*octet + dst = append(dst, remainingFrameInfo(unknownPayloadProto, FieldClassPayload, end, octet*len(pkt))) + return dst, nil +} + func (pc *PacketBreakdown) CaptureHTTP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) { const httpProtocol = "HTTP" if bitOffset%8 != 0 { @@ -357,7 +413,6 @@ func (frm Frame) AppendField(dst []byte, fieldIdx int, pkt []byte) ([]byte, erro func (frm Frame) String() string { iopt, err := frm.FieldByClass(FieldClassOptions) - hasOpts := "" if err == nil { hasOpts = fmt.Sprintf(" optlen=%d", (frm.Fields[iopt].BitLength+7)/8) @@ -457,6 +512,56 @@ var baseARPFields = [...]FrameField{ }, } +var baseIPv6Fields = [...]FrameField{ + { + Class: FieldClassVersion, + FrameBitOffset: 0, + BitLength: 4, + }, + { + Name: "Type of Service", + Class: FieldClassFlags, + FrameBitOffset: 4, + BitLength: 1 * octet, + RightAligned: true, + }, + { + Name: "Flow Label", + Class: FieldClassID, + FrameBitOffset: 12, + BitLength: 20, + RightAligned: true, + }, + { + Name: "Total Length", + Class: FieldClassSize, + FrameBitOffset: 4 * octet, + BitLength: 2 * octet, + }, + { + Name: "Next Header", + Class: 0, + FrameBitOffset: 6 * octet, + BitLength: 1 * octet, + }, + { + Name: "Hop Limit", + Class: 0, + FrameBitOffset: 7 * octet, + BitLength: 1 * octet, + }, + { + Class: FieldClassSrc, + FrameBitOffset: 8 * octet, + BitLength: 16 * octet, + }, + { + Class: FieldClassSrc, + FrameBitOffset: 24 * octet, + BitLength: 16 * octet, + }, +} + var baseIPv4Fields = [...]FrameField{ { Class: FieldClassVersion, @@ -532,7 +637,7 @@ var baseTCPFields = [...]FrameField{ }, { Name: "Destination port", - Class: FieldClassSrc, + Class: FieldClassDst, FrameBitOffset: 2 * octet, BitLength: 2 * octet, }, @@ -579,6 +684,31 @@ var baseTCPFields = [...]FrameField{ }, } +var baseUDPFields = [...]FrameField{ + { + Name: "Source port", + Class: FieldClassSrc, + FrameBitOffset: 0, + BitLength: 2 * octet, + }, + { + Name: "Destination port", + Class: FieldClassDst, + FrameBitOffset: 2 * octet, + BitLength: 2 * octet, + }, + { + Class: FieldClassSize, + FrameBitOffset: 4 * octet, + BitLength: 2 * octet, + }, + { + Class: FieldClassChecksum, + FrameBitOffset: 6 * octet, + BitLength: 2 * octet, + }, +} + func remainingFrameInfo(proto any, class FieldClass, pktBitOffset, pktBitLen int) Frame { return Frame{ Protocol: proto, diff --git a/internet/pcap/capture_test.go b/internet/pcap/capture_test.go index 6d99a22..4ab1386 100644 --- a/internet/pcap/capture_test.go +++ b/internet/pcap/capture_test.go @@ -102,6 +102,16 @@ func TestCap(t *testing.T) { if gotTCPFlags != wantTCPflags { t.Errorf("want %s TCP flags, got %s", wantTCPflags.String(), gotTCPFlags.String()) } + wanDstPort := gen.DstTCP + wantSrcPort := gen.SrcTCP + gotSrcPort := uint16(getClass(ptfrm, FieldClassSrc)) + gotDstPort := uint16(getClass(ptfrm, FieldClassDst)) + if wantSrcPort != gotSrcPort { + t.Errorf("want %d TCP src port, got %d", wantSrcPort, gotSrcPort) + } + if wanDstPort != gotDstPort { + t.Errorf("want %d TCP dst port, got %d", wanDstPort, gotDstPort) + } gotHeaderLen := getClass(ptfrm, FieldClassSize) if gotHeaderLen != uint64(wantHeaderLen) { t.Errorf("want %d TCP header length, got %d", wantHeaderLen, gotHeaderLen) diff --git a/internet/portstack.go b/internet/portstack.go new file mode 100644 index 0000000..a77e373 --- /dev/null +++ b/internet/portstack.go @@ -0,0 +1,14 @@ +package internet + +import "github.com/soypat/lneto" + +type PortStack struct { + handlers []porthandler + proto lneto.IPProto +} + +type porthandler struct { + recv func([]byte, int) error + handle func([]byte, int) (int, error) + port uint16 +} diff --git a/stringers.go b/stringers.go index c605d6b..36ad85d 100644 --- a/stringers.go +++ b/stringers.go @@ -152,7 +152,7 @@ func _() { } const ( - _IPProto_name_0 = "IPv6 Hop-by-Hop Option [RFC8200]Internet Control Message [RFC792]Internet Group Management [RFC1112]Gateway-to-Gateway [RFC823]IPv4 encapsulation [RFC2003]Stream [RFC1190, RFC1819]Transmission Control [RFC9293]CBT [Ballardie]Exterior Gateway Protocol [RFC888]any private interior gateway (used by Cisco for their IGRP)BBN RCC MonitoringNetwork Voice Protocol [RFC741]PUPARGUSEMCONCross Net DebuggerChaosUser Datagram [RFC768]MultiplexingDCN Measurement SubsystemsHost Monitoring [RFC869]Packet Radio MeasurementXEROX NS IDPTrunk-1Trunk-2Leaf-1Leaf-2Reliable Data Protocol [RFC908]Internet Reliable Transaction [RFC938]ISO Transport Protocol Class 4 [RFC905]Bulk Data Transfer Protocol [RFC998]MFE Network Services ProtocolMERIT Internodal ProtocolDatagram Congestion Control Protocol [RFC4340]Third Party Connect ProtocolInter-Domain Policy Routing ProtocolXTPDatagram Delivery ProtocolIDPR Control Message Transport ProtoTP++ Transport ProtocolIL Transport ProtocolIPv6 encapsulation [RFC2473]Source Demand Routing ProtocolRouting Header for IPv6 [RFC8200]Fragment Header for IPv6 [RFC8200]Inter-Domain Routing ProtocolReservation Protocol [RFC2205]Generic Routing Encapsulation [RFC2784]Dynamic Source Routing ProtocolBNAEncap Security Payload [RFC4303]Authentication Header [RFC4302]Integrated Net Layer Security TUBAIP with EncryptionNBMA Address Resolution ProtocolIP MobilityTransport Layer Security Protocol using Kryptonet key managementSKIPICMP for IPv6 [RFC8200]No Next Header for IPv6 [RFC8200]Destination Options for IPv6 [RFC8200]" + _IPProto_name_0 = "IPv6 Hop-by-Hop Option [RFC8200]Internet Control Message [RFC792]Internet Group Management [RFC1112]Gateway-to-Gateway [RFC823]IPv4 encapsulation [RFC2003]Stream [RFC1190, RFC1819]TCP [RFC9293]CBT [Ballardie]Exterior Gateway Protocol [RFC888]any private interior gateway (used by Cisco for their IGRP)BBN RCC MonitoringNetwork Voice Protocol [RFC741]PUPARGUSEMCONCross Net DebuggerChaosUDP [RFC768]MultiplexingDCN Measurement SubsystemsHost Monitoring [RFC869]Packet Radio MeasurementXEROX NS IDPTrunk-1Trunk-2Leaf-1Leaf-2Reliable Data Protocol [RFC908]Internet Reliable Transaction [RFC938]ISO Transport Protocol Class 4 [RFC905]Bulk Data Transfer Protocol [RFC998]MFE Network Services ProtocolMERIT Internodal ProtocolDatagram Congestion Control Protocol [RFC4340]Third Party Connect ProtocolInter-Domain Policy Routing ProtocolXTPDatagram Delivery ProtocolIDPR Control Message Transport ProtoTP++ Transport ProtocolIL Transport ProtocolIPv6 encapsulation [RFC2473]Source Demand Routing ProtocolRouting Header for IPv6 [RFC8200]Fragment Header for IPv6 [RFC8200]Inter-Domain Routing ProtocolReservation Protocol [RFC2205]Generic Routing Encapsulation [RFC2784]Dynamic Source Routing ProtocolBNAEncap Security Payload [RFC4303]Authentication Header [RFC4302]Integrated Net Layer Security TUBAIP with EncryptionNBMA Address Resolution ProtocolIP MobilityTransport Layer Security Protocol using Kryptonet key managementSKIPICMP for IPv6 [RFC8200]No Next Header for IPv6 [RFC8200]Destination Options for IPv6 [RFC8200]" _IPProto_name_1 = "CFTP" _IPProto_name_2 = "SATNET and Backroom EXPAKKryptolanMIT Remote Virtual Disk ProtocolInternet Pluribus Packet Core" _IPProto_name_3 = "SATNET MonitoringVISA ProtocolInternet Packet Core UtilityComputer Protocol Network ExecutiveComputer Protocol Heart BeatWang Span NetworkPacket Video ProtocolBackroom SATNET MonitoringSUN ND PROTOCOL-TemporaryWIDEBAND MonitoringWIDEBAND EXPAKISO Internet ProtocolVMTPSECURE-VMTPVINESTTPNSFNET-IGPDissimilar Gateway ProtocolTCFEIGRPOSPFIGPSprite RPC ProtocolLocus Address Resolution ProtocolMulticast Transport ProtocolAX.25 FramesIP-within-IP Encapsulation ProtocolMobile Internetworking Control Pro.Semaphore Communications Sec. Pro.Ethernet-within-IP EncapsulationEncapsulation Header" @@ -161,7 +161,7 @@ const ( ) var ( - _IPProto_index_0 = [...]uint16{0, 32, 65, 100, 127, 155, 180, 210, 225, 259, 318, 336, 367, 370, 375, 380, 398, 403, 425, 437, 463, 487, 511, 523, 530, 537, 543, 549, 580, 618, 657, 693, 722, 747, 793, 821, 857, 860, 886, 922, 945, 966, 994, 1024, 1057, 1091, 1120, 1150, 1189, 1220, 1223, 1255, 1286, 1320, 1338, 1370, 1381, 1445, 1449, 1472, 1505, 1543} + _IPProto_index_0 = [...]uint16{0, 32, 65, 100, 127, 155, 180, 193, 208, 242, 301, 319, 350, 353, 358, 363, 381, 386, 398, 410, 436, 460, 484, 496, 503, 510, 516, 522, 553, 591, 630, 666, 695, 720, 766, 794, 830, 833, 859, 895, 918, 939, 967, 997, 1030, 1064, 1093, 1123, 1162, 1193, 1196, 1228, 1259, 1293, 1311, 1343, 1354, 1418, 1422, 1445, 1478, 1516} _IPProto_index_2 = [...]uint8{0, 25, 34, 66, 95} _IPProto_index_3 = [...]uint16{0, 17, 30, 58, 93, 121, 138, 159, 185, 210, 229, 243, 264, 268, 279, 284, 287, 297, 324, 327, 332, 339, 358, 391, 419, 431, 466, 501, 535, 567, 587} _IPProto_index_4 = [...]uint8{0, 4, 36, 48, 78, 82, 86, 89, 104, 135, 159, 179, 188, 222, 253} diff --git a/tcp/control.go b/tcp/control.go index b5a076f..9f5e856 100644 --- a/tcp/control.go +++ b/tcp/control.go @@ -57,7 +57,7 @@ type ControlBlock struct { challengeAck bool } -// State returns the current state of the TCP connection. +// State returns the current state of the TCP connection. See [State]. func (tcb *ControlBlock) State() State { return tcb._state } // RecvNext returns the next sequence number expected to be received from remote. diff --git a/tcp/definitions.go b/tcp/definitions.go index dba63b0..7805845 100644 --- a/tcp/definitions.go +++ b/tcp/definitions.go @@ -308,15 +308,17 @@ func (s State) IsSynchronized() bool { return s >= StateEstablished && !s.IsClosed() } -// txOpen returns true if the TCP state machine allows data to be sent by user. -func (s State) txOpen() bool { +// TxDataOpen returns true if the state allows for outgoing data segments to be sent. +// Combine with [State.IsPreestablished] to know whether there is no more data to be sent over the network. +func (s State) TxDataOpen() bool { // In CloseWait state the remote endpoint has closed // our receive hald of the connection but we can still transmit indefinitely. return s == StateEstablished || s == StateCloseWait } -// rxOpen returns true if the TCP state machine allows data to be received from remote endpoint. -func (s State) rxOpen() bool { +// RxDataOpen returns true if the state allows the receiving of incoming data segments. +// Combine with [State.IsPreestablished] to know whether there is no more data to be received over the network. +func (s State) RxDataOpen() bool { return s == StateEstablished || s == StateFinWait1 || s == StateFinWait2 } diff --git a/validation.go b/validation.go index 8a302ec..5b5412d 100644 --- a/validation.go +++ b/validation.go @@ -65,7 +65,7 @@ func (v *Validator) AddBitPosErr(bitStart, bitLen int, err error) { if err == nil { panic("err argument to bitPosErr cannot be nil") } else if bitLen <= 0 { - panic("") + panic("zero bitlen") } v.accumBitpos = append(v.accumBitpos, BitPosErr{BitStart: bitStart, BitLen: bitLen, Err: err}) v.accum = append(v.accum, &v.accumBitpos[len(v.accumBitpos)-1])