Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version: 1.25

- name: Build
run: go build -v ./...
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# go-lprlib
![Test](https://github.com/documatrix/go-lprlib/actions/workflows/go.yml/badge.svg)

This repository contains an implementation of the LPR protocol (send & receive) in go
This repository contains an implementation of the [LPR protocol](https://datatracker.ietf.org/doc/html/rfc1179) (send & receive) in go
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
module github.com/documatrix/go-lprlib

go 1.18
go 1.25

require (
github.com/stretchr/testify v1.8.0
golang.org/x/text v0.3.8
golang.org/x/text v0.31.0
)

require (
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
52 changes: 36 additions & 16 deletions lpr_daemon.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package lprlib

import (
"context"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -33,10 +32,6 @@ type QueueState func(queue string, list string, long bool) string

type ExternalIDCallbackFunc func() uint64

func init() {
rand.Seed(time.Now().UnixMicro())
}

// LprDaemon structure
type LprDaemon struct {
finishedConns chan *LprConnection
Expand Down Expand Up @@ -287,10 +282,6 @@ type LprConnection struct {
// SaveName The File name of the new file
SaveName string

// ctx is the lpr daemon's context.
// The connection must be closed once the context is canceled.
ctx context.Context

// daemon contains a reference to the LprDaemon
daemon *LprDaemon

Expand Down Expand Up @@ -374,22 +365,51 @@ func (lpr *LprConnection) RunConnection() {
if err != nil {
logErrorf("failed to create trace file: %v", err)
}
defer traceFile.Close()

defer func() {
err := traceFile.Close()
if err != nil {
logErrorf("failed to close trace file %s: %v", traceFile.Name(), err)
}
}()

logDebugf("Created trace file %s", traceFile.Name())
traceFile.WriteString(fmt.Sprintf("LPR connection trace %s\n", time.Now()))

_, err = fmt.Fprintf(traceFile, "LPR connection trace %s\n", time.Now())
if err != nil {
logErrorf("failed to write to trace file %s: %v", traceFile.Name(), err)
}
}

for lpr.Status != Error && lpr.Status != End {
command, err := lpr.ReadCommand()

if traceFile != nil {
traceFile.WriteString(fmt.Sprintf("received message %d:\n", len(command)))
_, traceErr := fmt.Fprintf(traceFile, "received message %d:\n", len(command))
if traceErr != nil {
logErrorf("failed to write to trace file %s: %v", traceFile.Name(), traceErr)
}

if err != nil {
traceFile.WriteString(fmt.Sprintf("error: %v\n", err))
_, traceErr = fmt.Fprintf(traceFile, "error: %v\n", err)
if traceErr != nil {
logErrorf("failed to write to trace file %s: %v", traceFile.Name(), traceErr)
}
} else {
traceFile.WriteString("-----\n")
traceFile.Write(command)
traceFile.WriteString("\n-----\n")
_, traceErr = traceFile.WriteString("-----\n")
if traceErr != nil {
logErrorf("failed to write to trace file %s: %v", traceFile.Name(), traceErr)
}

_, traceErr = traceFile.Write(command)
if traceErr != nil {
logErrorf("failed to write to trace file %s: %v", traceFile.Name(), traceErr)
}

_, traceErr = traceFile.WriteString("\n-----\n")
if traceErr != nil {
logErrorf("failed to write to trace file %s: %v", traceFile.Name(), traceErr)
}
}
}

Expand Down
13 changes: 8 additions & 5 deletions lpr_daemon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func TestDaemonLargeFileConnection(t *testing.T) {

conn := <-lprd.FinishedConnections()

out, err = ioutil.ReadFile(conn.SaveName)
out, err = os.ReadFile(conn.SaveName)
if err != nil {
t.Error(err)
} else {
Expand Down Expand Up @@ -243,7 +243,7 @@ func TestDaemonMultipleConnection(t *testing.T) {

i := 0
for conn := range lprd.FinishedConnections() {
out, err = ioutil.ReadFile(conn.SaveName)
out, err = os.ReadFile(conn.SaveName)
if err != nil {
t.Error(err)
} else {
Expand Down Expand Up @@ -288,7 +288,7 @@ func generateTempFile(dir, prefix, text string) (string, error) {
var err error
var file *os.File

file, err = ioutil.TempFile(dir, prefix)
file, err = os.CreateTemp(dir, prefix)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -413,6 +413,9 @@ func TestDaemonClose(t *testing.T) {
conn := <-lprd.FinishedConnections()
require.Equal(t, End, conn.Status)

err = os.Remove(conn.SaveName)
require.Nil(t, err)

// no new connection may be opened
lprs = LprSend{}
err = lprs.Init("127.0.0.1", name, port, "raw", "TestUser", time.Minute)
Expand Down Expand Up @@ -457,7 +460,7 @@ func TestDaemonFileSize(t *testing.T) {

con := <-lprd.FinishedConnections()
require.Equal(t, End, con.Status)
out, err = ioutil.ReadFile(con.SaveName)
out, err = os.ReadFile(con.SaveName)
require.Nil(t, err)
err = os.Remove(con.SaveName)
require.Nil(t, err)
Expand Down Expand Up @@ -501,7 +504,7 @@ func TestDaemonFileSize(t *testing.T) {

con = <-lprd.FinishedConnections()
require.Equal(t, End, con.Status)
out, err = ioutil.ReadFile(con.SaveName)
out, err = os.ReadFile(con.SaveName)
require.Nil(t, err)
err = os.Remove(con.SaveName)
require.Nil(t, err)
Expand Down
26 changes: 23 additions & 3 deletions lpr_status.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
package lprlib

import (
"errors"
"fmt"
"io"
"net"
"time"
)

// GetStatus Reads the Status from the printer
func GetStatus(hostname string, port uint16, queue string, long bool, timeout time.Duration) (string, error) {

// GetStatus Reads the status of the given queue on the given host (and port).
// The timeout parameter specifies the maximum time to wait for the connection
// and for each read/write operation. If timeout is 0, a default of 2 seconds
// is used. The long parameter specifies whether to request a long listing
// (true) or a short listing (false). The ignoreForcefulClose parameter controls
// if the read-status should ignore forceful connection closures by the server (which
// sometimes happens).
func GetStatus(hostname string, port uint16, queue string, long bool, timeout time.Duration, ignoreForcefulClose bool) (string, error) {
// Set default Port
if port == 0 {
port = 515
Expand Down Expand Up @@ -88,6 +94,20 @@ func GetStatus(hostname string, port uint16, queue string, long bool, timeout ti
if err == io.EOF {
break
} else {
if ignoreForcefulClose {
opErr := &net.OpError{}

if errors.As(err, &opErr) {
// Check for connection reset errors at the syscall level
// This works cross-platform: ECONNRESET on Unix-like systems,
// WSAECONNRESET on Windows
if isConnResetErr(opErr) {
logDebugf("Ignoring forceful connection closure by server")
break
}
}
}

return "", &LprError{"Error while reading status: " + err.Error()}
}
}
Expand Down
14 changes: 14 additions & 0 deletions lpr_status_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//go:build !windows
// +build !windows

package lprlib

import (
"errors"
"net"
"syscall"
)

func isConnResetErr(err *net.OpError) bool {
return errors.Is(err.Err, syscall.ECONNRESET)
}
37 changes: 34 additions & 3 deletions lpr_status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package lprlib

import (
"log"
"net"
"testing"
"time"

Expand Down Expand Up @@ -34,19 +35,19 @@ func TestGetStatus(t *testing.T) {

time.Sleep(1 * time.Second)

status, err := GetStatus("127.0.0.1", port, rawQueue, false, 2*time.Second)
status, err := GetStatus("127.0.0.1", port, rawQueue, false, 2*time.Second, false)
require.Nil(t, err)
require.NotEmpty(t, status)

lprd.GetQueueState = getShortQueueState

status, err = GetStatus("127.0.0.1", port, rawQueue, false, 2*time.Second)
status, err = GetStatus("127.0.0.1", port, rawQueue, false, 2*time.Second, false)
require.Nil(t, err)
require.Equal(t, shortQueueState, status)

lprd.GetQueueState = getLongQueueState

status, err = GetStatus("127.0.0.1", port, rawQueue, true, 2*time.Second)
status, err = GetStatus("127.0.0.1", port, rawQueue, true, 2*time.Second, false)
require.Nil(t, err)
require.Equal(t, longQueueState, status)

Expand All @@ -63,3 +64,33 @@ func TestGetStatus(t *testing.T) {

lprd.Close()
}

func TestGetStatus_ServerClosesImmediatelyAfterCommand(t *testing.T) {
listener, err := net.Listen("tcp", net.JoinHostPort("localhost", "0"))
require.NoError(t, err)

defer listener.Close()

go func() {
conn, err := listener.Accept()
require.NoError(t, err)

defer conn.Close()

// Read command byte
buf := make([]byte, 1024)
_, err = conn.Read(buf)
require.NoError(t, err)

// Close connection immediately
err = conn.(*net.TCPConn).SetLinger(0)
require.NoError(t, err)
}()

status, err := GetStatus("localhost", uint16(listener.Addr().(*net.TCPAddr).Port), "raw", false, 2*time.Second, true)
require.NoError(t, err)
require.Empty(t, status)

_, err = GetStatus("localhost", uint16(listener.Addr().(*net.TCPAddr).Port), "raw", false, 2*time.Second, false)
require.Error(t, err)
}
11 changes: 11 additions & 0 deletions lpr_status_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package lprlib

import (
"errors"
"net"
"syscall"
)

func isConnResetErr(err *net.OpError) bool {
return errors.Is(err.Err, syscall.ECONNRESET) || errors.Is(err.Err, syscall.WSAECONNRESET)
}