Stümpern mit Niveau

Ab und zu – die Frequenz ist uneinheitlich, die Rahmenbedingungen unbekannt – neige ich dazu, irgendein IT-Thema mal genauer zu betrachten und ein paar Experimente durchzuführen. Quasi um immer wieder das “innere Komplexitätsgefühl” neu zu justieren. Ich stelle eine gewisse Häufung beim Thema “Protokolle” fest, vielleicht die masochistische Ader die mich schon zur Implementierung einer CD-/DVD-/BluRay-Brenner-Software gebracht hat. Ziel ist immer, bei einer Sache von Quasi-Null-Wissen auf mindestens 3 zusätzliche Seiten im persönlichen Handbuch des nutzlosen Wissens zu kommen. Quasi qualifiziertes Halbwissen, das jede Fachdiskussion unter einer Stunde locker überlebt.

Diesmal das Thema: IPP, das “Internet Printing Protocol”, das im Rahmen einer Diskussion im Kontext meines Lieblingsbetriebssystems RISC OS eher zufällig aufgekommen ist. Früher dachte ich, IPP sei quasi nur “Druckdatenstrom per HTTP an den Drucker übermitteln”. Sowas wie “JetDirect über HTTP”. Oh Mann, lag ich falsch. Oder besser: es ist eine sehr simplifizierte Beschreibung der Wirklichkeit, und qualifizierte mich damit als “Quasi-Null-Wissenden”.

In Wahrheit hat IPP interessantes Potenzial für die RISC OS-Welt, nämlich “driverless printing”. Man kann per standardisierten Requests den Drucker zu seinen Eigenschaften befragen (Papierzufuhroptionen, Nachbearbeitungsoptionen, Duplex oder nicht und wenn ja wie), zu seinem Status (Tonerstand/Tintenstand, Papierstau etc.) und es gibt ein plattformübergreifendes Rasterformat (“PWG Raster Format” – PWG ist die “Printer Working Group”, die hinter IPP steht), das zum Druck verwendet wird. Zusätzlich vermelden netzwerkverbundene Drucker ihre Anwesenheit über das Bonjour-Protokoll, aber auch direkt verbundene USB-Drucker werden unterstützt. Das Gesamtkunstwerk nennt sich IPP Everywhere. Also die Lösung aller Probleme der Art “klar gibt es da diesen Drucker XYZ, aber für Betriebssystem ABC gibt es leider keine Treiber” – heute behilft man sich da (zumindest im RISC OS-Markt) mit der oberen Preisklasse der PostScript- und PDF-fähigen Drucker, muss aber immer noch tricksen um mit speziellen Druckerfeatures arbeiten zu können.

Die IT wäre nicht die IT, wenn nicht zwei konkurrierende andere Lösungen gleichzeitig um die Gunst des Anwenders (interessiert den das eigentlich?) buhlen würden: AirPrint (von Apples Gnaden) und Wi-Fi Direct Print. So weit, so ärgerlich. Zurück zu IPP. Dessen Charme ist die Verfügbarkeit eines gut dokumentierten Standards, bei den beiden Konkurrenten sieht es da nicht ganz so gut aus, dafür waren diese früher am Markt und sind (deshalb?) deutlich weiter verbreitet – IPP Everywhere wird heute eigentlich nur von einer Reihe von HP-Druckern unterstützt.

Wie auch immer, ich fasste ein Ziel ins Auge: mit meinem IPP-fähigen (aber natürlich nicht IPP Everywhere-kompatiblen, weil Baujahr 2011) Oki C530 ein PDF zu drucken, indem über IPP der PDF-Datenstrom an den Drucker geschickt wird.

Wie anfangen? Der IPP-Guide erzählt von 40 Spezifikationen mit insgesamt fast 2000 Seiten, das schien jetzt etwas ambitioniert. Aber es gibt JIPP, eine Java-Implementierung von IPP von HP, verfügbar auf GitHub. Unter der guten MIT-Lizenz. Da waren Beispiele dabei. Wie schwer kann es also sein?

Erste Hürde: das Github-Projekt hostet nur Source-Releases. Kein Problem, man kann ja selber bauen. Stellt sich raus: ist Kotlin-Code, gebaut mit Gradle. Hm. Bin ich jetzt für beide Dinge nicht so der Experte, vielleicht ein Experiment für ein anderes Mal. Google führt mich zu einer sinnvollen Paketierung des Codes inklusive aller seiner Abhängigkeiten (wie der Kotlin-Runtime – auch nicht gerade schmal das Dingens). Also schnell ein IDE-Projekt aufgesetzt und los geht’s. Ich arbeite mit Version 0.6.16 – nur falls jemand ein paar Jahre später über diesen Artikel stolpert und bis hierher durchgehalten hat.

Das jprint-Beispiel (inzwischen übrigens – ohne meinen Input und erst vor wenigen Stunden! – signifikant verbessert, an einigen der Stolperfallen) erst mal als Basis genommen. Ein PDF an die bekannte IPP-URL des Druckers geschickt. Eine dubiose Exception war das Ergebnis (aus dem HTTP-OutputStream: java.io.IOException: Error writing request body to server) – wies darauf hin, das irgendwas mit der URL nicht stimmte. OK. Ein paar Varianten durchprobiert (offenbar gibt es typische Implementierungen der Form ipp://irgendneip/ipp und ipp://irgendneip/ipp/lp und http://irgendeineip/ipp und http://irgendeineip:631/ipp – an dieser Stelle die Nebenerkenntnis, dass IPP schlicht HTTP-mit-631-als-Default-Port ist). Manchmal den gleichen Fehler, manchmal einen anderen Fehler (java.net.SocketException: Software caused connection abort: recv failed). Dubios. Weiter rumprobiert. Stellt sich raus: auch bei derselben URL kommt manchmal die eine Exception, manchmal die andere. Aha. Dann doch kein Connection-/URL-Problem? Mal den Code genauer anschauen. Als Transportmechanismus wird eine Klasse namens “IppPacket” verwendet. Da gibt es einen alternativen Konstruktor, der auch eine “ippVersion” (ein int wie es sich gehört) entgegen nimmt. Könnte es sein, dass mein Drucker (aus der Zeit von IPP v1.1) mit dem Default von JIPP (IPP v2.x) einfach nicht klarkommt? Mehrfaches Ausführen des Codes mit stets derselben URL führt tatsächlich manchmal zu einer Exception-losen Verarbeitung des Kommandos mit der Fehler-Rückmeldung “server-error-version-not-supported”, was den Verdacht erhärtet.

Etwas später ist klar: dummerweise erlaubt der Konstruktor von IppPacket, der eine explizite Versionsangabe erlaubt, nicht dieselben Typen an Restparametern, insbesondere nicht den “Operation”-Enum, sondern auch nur einen int. OK, ein paar RFCs zum IPP gelesen und herausgefunden, dass das Kommando getPrinterAttributes 0x0B ist und printJob 0x02. Minuten später sehe ich, dass der Enum innen drin die ints eh auch definiert hat. Grmpf. Ist bestimmt so ein Kotlin-Automatismus. Bleibt die Frage: wie spezifiziert man jetzt in einem int eigentlich “1.1” als Version? Na per 8+8 Bit natürlich. Also 0x0101 für meine Zwecke. Prima. Erstmal getPrinterAttributes probieren statt gleich das PDF zu schicken. Das Kommando wird jetzt zuverlässig verschickt, aber JIPP kann das Resultat nicht unfallfrei parsen – ob das eine Schwäche von JIPP ist oder eine Abweichung von der Spezifikation seitens meines Druckers ist noch ungeklärt, aber Gott sei Dank hat der JIPP-Programmierer bei solchen Parse-Fehlern daran gedacht, die Rohdaten des Replys, der nicht parsebar waren, herauszudumpen. Also konnte ich einfach inspizieren, was mein Drucker so als Fähigkeiten zurückmeldet.

Nachdem das Kommando-Schicken nun völlig problemlos funktionierte, machte ich mich an die eigentliche Aufgabe, den Druck eines PDFs – der Oki unterstützt sowohl direktes Drucken per PostScript als auch per PDF, und PDFs finden sich auf der Platte entschieden leichter. Ein einseitiges Dokument identifiziert und per offiziellem PDF-MIME-Type “application/pdf” zum Drucker geschickt. Kommt zurück: “client-error-attributes-or-values-not-supported”. Eine sprechende Fehlermeldung! Sehr schön. Inspektion der bei getPrinterAttributes zurückgemeldeten Fähigkeiten zeigt: “application/pdf” wird auch gar nicht akzeptiert, nur “application/octet-stream”, “application/vnd.hp-PCL” oder “application/postscript”. Einfach mal “application/octet-stream” geraten, und siehe da: es wird gedruckt.

Na das war ja einfach.

Zu klärende offene Fragen: 1. Nützt mir das jetzt was für IPP unter RISC OS? 2. Wie erzeuge ich möglichst einfach das PWG-Raster-Format aus einem beliebigen Quellformat? 3. Warum drucke ich nicht einfach PDF über einen CUPS-Druckerserver, der ja auch IPP out-of-the-box kann?

Antworten: 1. Noch nicht. 2. Sieht nicht so schwierig aus, auch keine ultrakomplexe Kompression, sondern simples PackedBits-Format und anständig dokumentiert. 2a. Aber auch JPEG wird als Format im Standard zwingend vorausgesetzt – noch einfacher! 3. Weil das ja jeder kann.

Interessanter Beifang: AppImage als Paketiervariante für Linux-Anwendungen. Spannend, kannte ich noch nicht.