ipp

package
v0.2.36 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 2, 2026 License: MPL-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package ipp is the chassis side of the print inlet: the durable job store the `ipp` personality writes to, plus the protocol helpers (codec, printer attributes, document sniffing) it answers with. The personality itself — HTTP, authentication, spooling, dispatch — lives in chassis/server/personality/ipp.

The store owns exactly one concern: an IPP job's life from the first byte of its document to the moment TxCo accepts responsibility for it.

receiving  the IPP body is in progress
committed  the document is in the CAS, the tenant owns it, this row is durable
delivered  the envelope was accepted onto the TxCo execution path
canceled   the client canceled before delivery           (terminal)
failed     IPP infrastructure could not deliver it       (terminal)

`delivered` is where IPP's responsibility ENDS. What the stack then does with the document — create a task, wait three days for a human, fail — is not part of a print job's lifecycle, and nothing here observes it.

Index

Constants

View Source
const (
	PrinterActive   = "active"
	PrinterDisabled = "disabled"
)

Printer statuses.

View Source
const (
	FormatPDF       = "application/pdf"
	FormatJPEG      = "image/jpeg"
	FormatPWGRaster = "image/pwg-raster"
	FormatURF       = "image/urf"
	FormatText      = "text/plain"
	FormatOctet     = "application/octet-stream" // "the printer should auto-sense"
)

Document formats this package can recognise. application/postscript is deliberately absent: PostScript is a programming language, not a passive page format, and the inlet never accepts it.

View Source
const (
	StateReceiving = "receiving"
	StateCommitted = "committed"
	StateDelivered = "delivered"
	StateCanceled  = "canceled"
	StateFailed    = "failed"
)

Job states. See the package doc for the lifecycle.

View Source
const ContentType = "application/ipp"

ContentType is the media type every IPP request and response carries.

View Source
const MaxDisplayName = 63

MaxDisplayName is the longest display name a printer keeps, in bytes: the DNS-SD instance-name limit, because the name is also `printer-dns-sd-name`.

View Source
const MaxHeaderBytes = 64 << 10

MaxHeaderBytes bounds the IPP attribute block of one request. A real client's Print-Job header is a few hundred bytes; the cap only stops a hostile peer from making the decoder build an unbounded message.

View Source
const SniffBytes = 1024

SniffBytes is how much of a document's head the inlet looks at.

Variables

View Source
var ErrJobNotFound = errors.New("ipp: job not found")

ErrJobNotFound is returned when no job matches.

View Source
var ErrPrinterNotFound = errors.New("ipp: printer not found")

ErrPrinterNotFound is returned when no printer matches.

View Source
var ErrVersion = errors.New("ipp: unsupported protocol version")

ErrVersion is returned by DecodeRequest for an IPP major version this inlet does not speak.

SupportedOperations is the v1 operation set, in the order advertised.

Functions

func CharsetFirst

func CharsetFirst(msg *goipp.Message) bool

CharsetFirst reports whether the request opens with attributes-charset then attributes-natural-language, as RFC 8011 §4.1.4 requires.

func ClipDisplayName

func ClipDisplayName(s string) string

ClipDisplayName cuts a display name to MaxDisplayName bytes, on a rune boundary.

func DecodeRequest

func DecodeRequest(r io.Reader, maxHeader int64) (*goipp.Message, error)

DecodeRequest reads ONE IPP message header (the attribute groups, up to and including the end-of-attributes tag) from r and stops there: goipp reads exactly the bytes it needs, so whatever follows — the document of a Print-Job or Send-Document — is still unread in r for the caller to stream. That property is what lets the inlet hash a 200 MB document into the CAS without ever holding it; wire_test.go pins it.

The header read is bounded by maxHeader (<= 0 → MaxHeaderBytes). The document is NOT bounded here.

func FilterRequested

func FilterRequested(attrs goipp.Attributes, requested []string) goipp.Attributes

FilterRequested narrows attrs to the client's requested-attributes.

func FormatAllowed

func FormatAllowed(declared string, allowed []string) bool

FormatAllowed is the pre-read gate: may a job declaring this document-format be accepted at all? An absent declaration means "the printer's default" (allowed[0]) and is always acceptable.

func Ignored

func Ignored(req *goipp.Message) goipp.Attributes

Ignored builds the unsupported-attributes group: every job-template attribute a client sent that asks for something this printer does not have. The printer has exactly one of everything (fixedJobTemplate), so `copies=1` is simply honoured, while `copies=17` — or duplex, or A4, or any attribute it has never heard of — comes back here with the out-of-band `unsupported` value and the response status becomes successful-ok-ignored-or-substituted-attributes: IPP's own way of saying "accepted; that setting meant nothing". A job is never refused over one.

func JobAttributes

func JobAttributes(j Job, printerURI string, upSince time.Time) goipp.Attributes

JobAttributes is the job-description set for one job. printerURI is the URI the client used, so job-uri stays under the name it knows us by.

func JobState

func JobState(state string) (enum int, reason string)

JobState maps a store state onto what an IPP client sees. `delivered` is IPP's `completed`: the printer's work is done the moment TxCo accepts responsibility for the document. The stack's run is not a print job.

func NewJobID

func NewJobID() string

NewJobID mints a durable job identity.

func OpBool

func OpBool(msg *goipp.Message, name string) (val, ok bool)

OpBool returns a boolean operation attribute.

func OpInt

func OpInt(msg *goipp.Message, name string) (int, bool)

OpInt returns an integer/enum operation attribute.

func OpString

func OpString(msg *goipp.Message, name string) (string, bool)

OpString returns an operation attribute's first value as a string.

func OpStrings

func OpStrings(msg *goipp.Message, name string) []string

OpStrings returns every value of a multi-valued operation attribute.

func OperationSupported

func OperationSupported(op goipp.Op) bool

OperationSupported reports whether op is one this inlet answers.

func Ordered

func Ordered(resp *goipp.Message) *goipp.Message

Ordered fixes the group order of a response built through the named fields. RFC 8011 §4.1.3: operation attributes, then UNSUPPORTED attributes, then the object's (job / printer) attributes. goipp's own field order puts the job group ahead of the unsupported group, which CUPS's ipptool rejects ("Attribute groups out of order"). A response that already carries explicit Groups (Get-Jobs: one job group per job) is left alone.

func PrinterAttributes

func PrinterAttributes(p PrinterInfo, now time.Time) goipp.Attributes

PrinterAttributes is the printer-description + job-template set.

THE LEAST FICTIONAL PRINTER. A print client needs a printer to have paper, a resolution and a copy count before it will talk to it, so this one has them — exactly ONE of each. Nothing is offered as a choice, because TxCo would ignore the choice: "17 copies" and "duplex, A4" mean nothing to a stack receiving a document, and a capability advertised is a promise. If a client needs a further attribute before it accepts the printer, add it here as one more boring fixed value; never as a range or a list the chassis then disregards. (Job-template attributes a client sends anyway come back in the unsupported group — see Ignored.)

func PrinterUUID

func PrinterUUID(tenant, printer string) string

PrinterUUID is a stable identity for (tenant, printer): macOS keys its queue on printer-uuid, so it must not change between requests or nodes. A name-based (v5-shaped) UUID over a fixed namespace string.

func Register

func Register(name string, c Constructor)

Register adds a store backend constructor. Called from a backend package's init().

func Resolve

func Resolve(declared string, head []byte, allowed []string) (format string, ok bool)

Resolve decides the format a job is recorded under once the document's head has been read. ok=false means the payload obviously is not what was declared (IPP client-error-document-format-error).

declared ""              → the printer default (allowed[0]); head must agree
declared octet-stream    → auto-sense: whatever the head is, if THAT is allowed
declared anything else   → head must agree

Callers gate on FormatAllowed first; Resolve re-checks so it is safe alone.

func Response

func Response(req *goipp.Message, status goipp.Status) *goipp.Message

Response starts a response to req: same request-id, the request's version (capped at 2.0, the newest this inlet claims), and the two operation attributes RFC 8011 requires first in every response.

func Sniff

func Sniff(head []byte) string

Sniff names the format a document head plausibly is, or "" when it is none this package knows. Shallow on purpose: it answers "does the payload resemble what the client claimed", not "is this a safe PDF" — deep parsing belongs downstream, and the stack must still treat the bytes as hostile.

func StatusMessage

func StatusMessage(resp *goipp.Message, text string)

StatusMessage attaches the human-readable status-message.

func ValidLabel

func ValidLabel(s string) bool

ValidLabel reports whether s can name a printer. The head applies it to the URL and the op to its `printer` param, so a row no URL reaches cannot be registered.

Types

type Config

type Config struct {
	// DBPath is the bundled SQLite backend's file path (--ipp-db-path).
	DBPath string
}

Config carries backend-selecting options resolved from chassis config. The bundled "sqlite" backend uses DBPath. Adding a field here doesn't affect existing backends (same posture as drive.Config).

type Constructor

type Constructor func(Config) (*Store, error)

Constructor builds a Store from resolved config. It is expected to open its backing DB and call Store.EnsureSchema before returning.

type Job

type Job struct {
	ID             string
	Tenant         string
	Printer        string
	Number         int64
	RequestingUser string // client-claimed requesting-user-name: untrusted
	JobName        string
	DocumentName   string
	DocumentFormat string
	SHA256         string // "" until committed; bare 64-hex
	Size           int64
	State          string
	StateReason    string
	Attempts       int
	Rid            string
	Host           string // the ipp.<zone> host the job arrived on
	// URIPath is the printer's path on that host: "/p/<printer>", or
	// "/p/<handle>/<printer>" through the platform's shared front door.
	// Host + URIPath is the printer URI the client configured.
	URIPath  string
	ClientIP string
	// PrincipalID and CredentialID are who signed in to create the job: the
	// principal the printer is granted to and the credential it presented.
	// The dispatcher pins the delivered run to them. Both are "" on a row an
	// older build wrote; such a job is delivered with no principal.
	PrincipalID  string
	CredentialID string
	CreatedAt    time.Time
	CommittedAt  time.Time
	DeliveredAt  time.Time
}

Job is one print job. Number is the int32 `job-id` the IPP client sees (a per-tenant sequence); ID is the opaque durable identity the stack sees as `@ipp.job_id` and may key idempotence on.

type NewJob

type NewJob struct {
	Tenant         string
	Printer        string
	RequestingUser string
	JobName        string
	DocumentName   string
	DocumentFormat string
	Host           string
	URIPath        string
	ClientIP       string
	PrincipalID    string
	CredentialID   string
}

NewJob is what a Print-Job / Create-Job request knows before any document byte has been read.

type Printer

type Printer struct {
	Tenant      string
	Label       string
	DisplayName string // "" = show the label
	PrincipalID string // `<kind>:<name>`: who may print here
	Status      string
	CreatedBy   string // the base stack that registered it
	CreatedAt   time.Time
	UpdatedAt   time.Time
}

Printer is one registered printer.

func (Printer) Active

func (p Printer) Active() bool

Active reports whether the printer accepts requests.

func (Printer) Name

func (p Printer) Name() string

Name is what a client shows for the printer: its display name, else its label.

type PrinterInfo

type PrinterInfo struct {
	Tenant string // identity only (printer-uuid); never sent
	Name   string // the path label: /p/<Name>
	// DisplayName is the registered printer's name for people ("Paris"), ""
	// when it has none. It is what a client that adds the printer by URI
	// (Add Printer, an `ipps://` link) names the queue: without it the queue
	// is named after the HOST, which every printer on the host shares.
	DisplayName string
	URI         string // the URI the client reached us at
	// Location is where this printer "is": the ipp host it answers on. The
	// one honest answer a virtual printer has to "which room?".
	Location string
	// MoreInfo is a web page about the printer: the zone's own site.
	MoreInfo string
	Secure   bool // TLS in front (uri-security-supported)
	Formats  []string
	Queued   int       // not-yet-delivered jobs
	UpSince  time.Time // process start (printer-up-time)
	// OperationTimeout is how long a Create-Job waits for its document
	// before the job is failed (multiple-operation-time-out) — a real
	// behaviour of this printer, so it is advertised.
	OperationTimeout time.Duration
}

PrinterInfo is everything that varies between two printers' answers to Get-Printer-Attributes. Note what is NOT here: copies, media, sides, quality, resolution. Those are fixed below.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store is the façade over the job tables. It carries the dialect (for `?`→`$n` rebinding and the skip-locked lease) and a clock seam for tests.

func NewStore

func NewStore(db *sql.DB, d registry.Dialect) *Store

NewStore builds a Store over the opened DB and its dialect. A nil dialect defaults to SQLite (the in-tree default).

func Open

func Open(name string, cfg Config) (*Store, error)

Open constructs the named backend. Unknown name is a startup error listing what is available (so a misconfigured --ipp-store fails loudly).

func (*Store) Cancel

func (s *Store) Cancel(ctx context.Context, tenant, printer string, number int64) (Job, bool, error)

Cancel cancels a job the client no longer wants. Only a job that has not been delivered (and is not mid-handoff under a lease) can be canceled; the returned Job tells the caller why when it could not.

func (*Store) Close

func (s *Store) Close() error

Close releases the underlying DB handle.

func (*Store) CountQueued

func (s *Store) CountQueued(ctx context.Context, tenant, printer string) (int, error)

CountQueued is a printer's not-yet-delivered jobs (queued-job-count).

func (*Store) CountReceiving

func (s *Store) CountReceiving(ctx context.Context, tenant string) (int, error)

CountReceiving is the tenant's uploads in flight (the busy gate).

func (*Store) CreateJob

func (s *Store) CreateJob(ctx context.Context, nj NewJob) (Job, error)

CreateJob allocates the tenant's next job number and inserts the row in `receiving`, in one transaction. The number exists BEFORE the document is read so a client polling Get-Jobs (or canceling) sees an upload in flight.

func (*Store) DB

func (s *Store) DB() *sql.DB

DB exposes the handle for tests and backends. Not for request paths.

func (*Store) DeletePrinter

func (s *Store) DeletePrinter(ctx context.Context, tenant, label string) (bool, error)

DeletePrinter removes a printer. It reports whether a row was there. Its jobs stay: a job already committed is still delivered.

func (*Store) EnsureSchema

func (s *Store) EnsureSchema(ctx context.Context) error

EnsureSchema creates the tables. Idempotent.

func (*Store) FailExhausted

func (s *Store) FailExhausted(ctx context.Context, maxAttempts int) (int64, error)

FailExhausted fails committed jobs the bus never accepted after maxAttempts leases — the one way delivery itself fails.

func (*Store) GetJob

func (s *Store) GetJob(ctx context.Context, tenant, printer string, number int64) (Job, error)

GetJob loads one job by the number the client knows it by.

func (*Store) GetJobByID

func (s *Store) GetJobByID(ctx context.Context, id string) (Job, error)

GetJobByID loads one job by its durable id.

func (*Store) GetPrinter

func (s *Store) GetPrinter(ctx context.Context, tenant, label string) (Printer, error)

GetPrinter returns one printer, whatever its status.

func (*Store) LeaseCommitted

func (s *Store) LeaseCommitted(ctx context.Context, node string, limit int) ([]Job, error)

LeaseCommitted leases up to limit committed, unleased jobs to node in ONE statement and returns the rows this call won (the scheduled store's claim shape: on Postgres the subselect takes FOR UPDATE SKIP LOCKED so competing dispatchers partition the set; on SQLite the single statement is atomic under the single writer). The state stays `committed` — a lease is a dispatcher's business, not the client's.

func (*Store) ListJobs

func (s *Store) ListJobs(ctx context.Context, tenant, printer string, completed bool, limit int) ([]Job, error)

ListJobs returns a printer's jobs, newest first. completed selects the terminal states (IPP which-jobs=completed); otherwise the live ones.

func (*Store) ListPrinters

func (s *Store) ListPrinters(ctx context.Context, tenant string) ([]Printer, error)

ListPrinters returns a tenant's printers, by label.

func (*Store) MarkDelivered

func (s *Store) MarkDelivered(ctx context.Context, id, rid string) error

MarkDelivered is the END of a print job's lifecycle: the envelope was accepted onto the TxCo execution path. rid names the run for tracing.

func (*Store) MarkFailed

func (s *Store) MarkFailed(ctx context.Context, id, reason string) error

MarkFailed moves a not-yet-delivered job to the terminal `failed` state: IPP infrastructure could not receive or deliver it. Never used for anything the stack did.

func (*Store) Purge

func (s *Store) Purge(ctx context.Context, retention time.Duration) (int64, error)

Purge deletes terminal rows older than retention. The documents they point at stay in the CAS (there is no blob GC); only the job history goes.

func (*Store) ReapReceiving

func (s *Store) ReapReceiving(ctx context.Context, olderThan time.Duration) (int64, error)

ReapReceiving fails `receiving` rows older than olderThan: a Create-Job whose document never came, or an upload whose node died mid-body.

func (*Store) ReleaseLease

func (s *Store) ReleaseLease(ctx context.Context, id string) error

ReleaseLease hands a committed job back for another attempt (the bus did not accept it in time).

func (*Store) ReleaseStaleLeases

func (s *Store) ReleaseStaleLeases(ctx context.Context, staleAfter time.Duration) (int64, error)

ReleaseStaleLeases clears leases older than staleAfter — crash recovery for a node that died between leasing and delivering. Returns the count.

func (*Store) SetClock

func (s *Store) SetClock(now func() time.Time)

SetClock pins the store's clock (tests).

func (*Store) SetCommitted

func (s *Store) SetCommitted(ctx context.Context, id, sha string, size int64, format, documentName string) (bool, error)

SetCommitted moves a receiving job to committed: the document is in the CAS under sha and the tenant owns it. Returns false when the row was no longer `receiving` — canceled (or reaped) while the body streamed.

func (*Store) UpsertPrinter

func (s *Store) UpsertPrinter(ctx context.Context, p Printer) (Printer, bool, error)

UpsertPrinter registers a printer, or changes the one that has its label. A "" DisplayName or Status keeps what an existing row has (a new row defaults to no display name, and active); CreatedBy is set on insert only. It returns the row as stored, and whether this call created it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL