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
- Variables
- func CharsetFirst(msg *goipp.Message) bool
- func ClipDisplayName(s string) string
- func DecodeRequest(r io.Reader, maxHeader int64) (*goipp.Message, error)
- func FilterRequested(attrs goipp.Attributes, requested []string) goipp.Attributes
- func FormatAllowed(declared string, allowed []string) bool
- func Ignored(req *goipp.Message) goipp.Attributes
- func JobAttributes(j Job, printerURI string, upSince time.Time) goipp.Attributes
- func JobState(state string) (enum int, reason string)
- func NewJobID() string
- func OpBool(msg *goipp.Message, name string) (val, ok bool)
- func OpInt(msg *goipp.Message, name string) (int, bool)
- func OpString(msg *goipp.Message, name string) (string, bool)
- func OpStrings(msg *goipp.Message, name string) []string
- func OperationSupported(op goipp.Op) bool
- func Ordered(resp *goipp.Message) *goipp.Message
- func PrinterAttributes(p PrinterInfo, now time.Time) goipp.Attributes
- func PrinterUUID(tenant, printer string) string
- func Register(name string, c Constructor)
- func Resolve(declared string, head []byte, allowed []string) (format string, ok bool)
- func Response(req *goipp.Message, status goipp.Status) *goipp.Message
- func Sniff(head []byte) string
- func StatusMessage(resp *goipp.Message, text string)
- func ValidLabel(s string) bool
- type Config
- type Constructor
- type Job
- type NewJob
- type Printer
- type PrinterInfo
- type Store
- func (s *Store) Cancel(ctx context.Context, tenant, printer string, number int64) (Job, bool, error)
- func (s *Store) Close() error
- func (s *Store) CountQueued(ctx context.Context, tenant, printer string) (int, error)
- func (s *Store) CountReceiving(ctx context.Context, tenant string) (int, error)
- func (s *Store) CreateJob(ctx context.Context, nj NewJob) (Job, error)
- func (s *Store) DB() *sql.DB
- func (s *Store) DeletePrinter(ctx context.Context, tenant, label string) (bool, error)
- func (s *Store) EnsureSchema(ctx context.Context) error
- func (s *Store) FailExhausted(ctx context.Context, maxAttempts int) (int64, error)
- func (s *Store) GetJob(ctx context.Context, tenant, printer string, number int64) (Job, error)
- func (s *Store) GetJobByID(ctx context.Context, id string) (Job, error)
- func (s *Store) GetPrinter(ctx context.Context, tenant, label string) (Printer, error)
- func (s *Store) LeaseCommitted(ctx context.Context, node string, limit int) ([]Job, error)
- func (s *Store) ListJobs(ctx context.Context, tenant, printer string, completed bool, limit int) ([]Job, error)
- func (s *Store) ListPrinters(ctx context.Context, tenant string) ([]Printer, error)
- func (s *Store) MarkDelivered(ctx context.Context, id, rid string) error
- func (s *Store) MarkFailed(ctx context.Context, id, reason string) error
- func (s *Store) Purge(ctx context.Context, retention time.Duration) (int64, error)
- func (s *Store) ReapReceiving(ctx context.Context, olderThan time.Duration) (int64, error)
- func (s *Store) ReleaseLease(ctx context.Context, id string) error
- func (s *Store) ReleaseStaleLeases(ctx context.Context, staleAfter time.Duration) (int64, error)
- func (s *Store) SetClock(now func() time.Time)
- func (s *Store) SetCommitted(ctx context.Context, id, sha string, size int64, format, documentName string) (bool, error)
- func (s *Store) UpsertPrinter(ctx context.Context, p Printer) (Printer, bool, error)
Constants ¶
const ( PrinterActive = "active" PrinterDisabled = "disabled" )
Printer statuses.
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.
const ( StateReceiving = "receiving" StateCommitted = "committed" StateDelivered = "delivered" StateCanceled = "canceled" StateFailed = "failed" )
Job states. See the package doc for the lifecycle.
const ContentType = "application/ipp"
ContentType is the media type every IPP request and response carries.
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`.
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.
const SniffBytes = 1024
SniffBytes is how much of a document's head the inlet looks at.
Variables ¶
var ErrJobNotFound = errors.New("ipp: job not found")
ErrJobNotFound is returned when no job matches.
var ErrPrinterNotFound = errors.New("ipp: printer not found")
ErrPrinterNotFound is returned when no printer matches.
var ErrVersion = errors.New("ipp: unsupported protocol version")
ErrVersion is returned by DecodeRequest for an IPP major version this inlet does not speak.
var SupportedOperations = []goipp.Op{ goipp.OpPrintJob, goipp.OpValidateJob, goipp.OpCreateJob, goipp.OpSendDocument, goipp.OpCancelJob, goipp.OpGetJobAttributes, goipp.OpGetJobs, goipp.OpGetPrinterAttributes, }
SupportedOperations is the v1 operation set, in the order advertised.
Functions ¶
func CharsetFirst ¶
CharsetFirst reports whether the request opens with attributes-charset then attributes-natural-language, as RFC 8011 §4.1.4 requires.
func ClipDisplayName ¶
ClipDisplayName cuts a display name to MaxDisplayName bytes, on a rune boundary.
func DecodeRequest ¶
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 ¶
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 ¶
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 ¶
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 OperationSupported ¶
OperationSupported reports whether op is one this inlet answers.
func Ordered ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
StatusMessage attaches the human-readable status-message.
func ValidLabel ¶
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 ¶
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.
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 ¶
NewStore builds a Store over the opened DB and its dialect. A nil dialect defaults to SQLite (the in-tree default).
func Open ¶
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) CountQueued ¶
CountQueued is a printer's not-yet-delivered jobs (queued-job-count).
func (*Store) CountReceiving ¶
CountReceiving is the tenant's uploads in flight (the busy gate).
func (*Store) CreateJob ¶
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) DeletePrinter ¶
DeletePrinter removes a printer. It reports whether a row was there. Its jobs stay: a job already committed is still delivered.
func (*Store) EnsureSchema ¶
EnsureSchema creates the tables. Idempotent.
func (*Store) FailExhausted ¶
FailExhausted fails committed jobs the bus never accepted after maxAttempts leases — the one way delivery itself fails.
func (*Store) GetJobByID ¶
GetJobByID loads one job by its durable id.
func (*Store) GetPrinter ¶
GetPrinter returns one printer, whatever its status.
func (*Store) LeaseCommitted ¶
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 ¶
ListPrinters returns a tenant's printers, by label.
func (*Store) MarkDelivered ¶
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 ¶
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 ¶
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 ¶
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 ¶
ReleaseLease hands a committed job back for another attempt (the bus did not accept it in time).
func (*Store) ReleaseStaleLeases ¶
ReleaseStaleLeases clears leases older than staleAfter — crash recovery for a node that died between leasing and delivering. Returns the count.
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 ¶
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.