428 lines
12 KiB
C++
428 lines
12 KiB
C++
#include "ippmsg.h"
|
|
|
|
quint32 IppMsg::_reqid=1;
|
|
|
|
IppMsg::IppMsg()
|
|
{
|
|
}
|
|
|
|
IppMsg::IppMsg(QJsonObject opAttrs, QJsonObject jobAttrs, quint8 majVsn, quint8 minVsn)
|
|
{
|
|
_majVsn = majVsn;
|
|
_minVsn = minVsn;
|
|
_opAttrs = opAttrs;
|
|
_jobAttrs = QJsonArray {jobAttrs};
|
|
}
|
|
|
|
|
|
IppMsg::~IppMsg()
|
|
{
|
|
}
|
|
|
|
IppMsg::IppMsg(QNetworkReply* resp)
|
|
{
|
|
QByteArray tmp = resp->readAll();
|
|
Bytestream bts(tmp.constData(), tmp.length());
|
|
|
|
quint32 reqId;
|
|
|
|
bts >> _majVsn >> _minVsn >> _status >> reqId;
|
|
|
|
QJsonObject attrs;
|
|
IppMsg::IppTag currentAttrType = IppTag::EndAttrs;
|
|
|
|
while(!bts.atEnd())
|
|
{
|
|
if(bts.peekU8() <= IppTag::UnsupportedAttrs) {
|
|
|
|
if(currentAttrType == IppTag::OpAttrs) {
|
|
_opAttrs = attrs;
|
|
}
|
|
else if (currentAttrType == IppTag::JobAttrs) {
|
|
_jobAttrs.append(attrs);
|
|
}
|
|
else if (currentAttrType == IppTag::PrinterAttrs) {
|
|
_printerAttrs = attrs;
|
|
}
|
|
else if (currentAttrType == IppTag::UnsupportedAttrs) {
|
|
qDebug() << "WARNING: unsupported attrs reported:" << attrs;
|
|
}
|
|
|
|
if(bts >>= (uint8_t)IppTag::EndAttrs) {
|
|
break;
|
|
}
|
|
|
|
currentAttrType = (IppTag)bts.getU8();
|
|
attrs = QJsonObject();
|
|
|
|
}
|
|
else {
|
|
consume_attribute(attrs, bts);
|
|
}
|
|
}
|
|
}
|
|
|
|
QJsonValue IppMsg::consume_value(quint8 tag, Bytestream& data)
|
|
{
|
|
QJsonValue value;
|
|
quint16 tmp_len;
|
|
|
|
switch (tag) {
|
|
case OpAttrs:
|
|
case JobAttrs:
|
|
case EndAttrs:
|
|
case PrinterAttrs:
|
|
case UnsupportedAttrs:
|
|
Q_ASSERT(false);
|
|
case Integer:
|
|
case Enum:
|
|
quint32 tmp_u32;
|
|
data >> tmp_len >> tmp_u32;
|
|
value = (int)tmp_u32;
|
|
break;
|
|
case Boolean:
|
|
quint8 tmp_bool;
|
|
data >> tmp_len >> tmp_bool;
|
|
value = (bool)tmp_bool;
|
|
break;
|
|
case DateTime:
|
|
{
|
|
quint16 year;
|
|
quint8 month, day, hour, minutes, seconds, deci_seconds,
|
|
plus_minus, utc_h_offset, utc_m_offset;
|
|
data >> tmp_len >> year >> month >> day >> hour >> minutes >> seconds >> deci_seconds
|
|
>> plus_minus >> utc_h_offset >> utc_m_offset;
|
|
QDate date(year, month, day);
|
|
QTime time(hour, minutes, seconds, deci_seconds*100);
|
|
int offset_seconds = (plus_minus == '+' ? 1 : -1)*(utc_h_offset*60*60+utc_m_offset*60);
|
|
value = QDateTime(date, time, Qt::OffsetFromUTC, offset_seconds).toString(Qt::ISODate);
|
|
break;
|
|
}
|
|
case Resolution:
|
|
{
|
|
qint32 x, y;
|
|
qint8 units;
|
|
QJsonObject tmp_res;
|
|
data >> tmp_len >> x >> y >> units;
|
|
tmp_res.insert("x", x);
|
|
tmp_res.insert("y", y);
|
|
tmp_res.insert("units", units);
|
|
value = tmp_res;
|
|
break;
|
|
}
|
|
case IntegerRange:
|
|
{
|
|
qint32 low, high;
|
|
data >> tmp_len >> low >> high;
|
|
QJsonObject tmp_range;
|
|
tmp_range.insert("low", low);
|
|
tmp_range.insert("high", high);
|
|
value = tmp_range;
|
|
break;
|
|
}
|
|
case OctetStringUnknown:
|
|
case TextWithLanguage:
|
|
case NameWithLanguage:
|
|
case TextWithoutLanguage:
|
|
case NameWithoutLanguage:
|
|
case Keyword:
|
|
case Uri:
|
|
case UriScheme:
|
|
case Charset:
|
|
case NaturalLanguage:
|
|
case MimeMediaType:
|
|
default:
|
|
{
|
|
std::string tmp_str = "";
|
|
data >> tmp_len;
|
|
data/tmp_len >> tmp_str;
|
|
value = tmp_str.c_str();
|
|
break;
|
|
}
|
|
};
|
|
return QJsonObject {{"tag", tag}, {"value", value}};
|
|
}
|
|
|
|
QJsonArray IppMsg::get_unnamed_attributes(Bytestream& data)
|
|
{
|
|
quint8 tag;
|
|
QJsonArray attrs;
|
|
while(data.remaining())
|
|
{
|
|
data >> tag;
|
|
if(data >>= (quint16)0)
|
|
{
|
|
attrs.append(consume_value(tag, data));
|
|
}
|
|
else
|
|
{
|
|
data -= 1;
|
|
break;
|
|
}
|
|
}
|
|
return attrs;
|
|
}
|
|
|
|
QJsonArray untag_values(QJsonArray taggedValues)
|
|
{
|
|
QJsonArray res;
|
|
foreach(QJsonValue it, taggedValues)
|
|
{
|
|
res.append(it.toObject()["value"]);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
QJsonValue IppMsg::collect_attributes(QJsonArray& attrs)
|
|
{
|
|
QJsonArray resArr;
|
|
QJsonObject resObj = QJsonObject();
|
|
while(!attrs.empty())
|
|
{
|
|
QJsonObject tmpobj = attrs.takeAt(0).toObject();
|
|
quint8 tag = tmpobj["tag"].toInt();
|
|
if(tag == MemberName)
|
|
{
|
|
QString key = tmpobj["value"].toString();
|
|
tmpobj = attrs.takeAt(0).toObject();
|
|
if(tmpobj["tag"] == BeginCollection)
|
|
{
|
|
resObj[key] = QJsonObject {{"tag", BeginCollection}, {"value", collect_attributes(attrs)}};
|
|
}
|
|
else
|
|
{ // This should be general data attributes
|
|
QJsonArray restOfSet;
|
|
while(attrs.begin()->toObject()["tag"] == tmpobj["tag"])
|
|
{
|
|
restOfSet.append(attrs.takeAt(0));
|
|
}
|
|
|
|
if(restOfSet.empty())
|
|
{
|
|
resObj[key] = tmpobj;
|
|
}
|
|
else
|
|
{
|
|
restOfSet.prepend(tmpobj);
|
|
tmpobj["value"] = untag_values(restOfSet);
|
|
resObj[key] = tmpobj;
|
|
}
|
|
}
|
|
}
|
|
else if(tag == EndCollection)
|
|
{
|
|
resArr.append(resObj);
|
|
resObj = QJsonObject();
|
|
if(attrs.empty())
|
|
{ // end of collection
|
|
break;
|
|
}
|
|
else if(attrs.begin()->toObject()["tag"] == BeginCollection)
|
|
{ // this is a 1setOf
|
|
attrs.pop_front();
|
|
continue;
|
|
}
|
|
else
|
|
{ // the following attribute(s) belong to an outer collection
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "out of sync with collection" << tmpobj;
|
|
}
|
|
}
|
|
|
|
if(resArr.size()==1)
|
|
{ // The code above unconditionally produces arrays, collapse if they are just a single object
|
|
return resArr.first();
|
|
}
|
|
else
|
|
{
|
|
return resArr;
|
|
}
|
|
}
|
|
|
|
QString IppMsg::consume_attribute(QJsonObject& attrs, Bytestream& data)
|
|
{
|
|
quint8 tag;
|
|
quint16 tmp_len;
|
|
QString name;
|
|
QJsonValue taggedValue;
|
|
std::string tmp_str = "";
|
|
|
|
data >> tag >> tmp_len;
|
|
|
|
data/tmp_len >> tmp_str;
|
|
QString name0 = tmp_str.c_str();
|
|
name = tmp_str.c_str();
|
|
|
|
taggedValue = consume_value(tag, data);
|
|
|
|
QJsonArray unnamed = get_unnamed_attributes(data);
|
|
|
|
// qDebug() << name0 << tag << tmp_len << value << unnamed;
|
|
|
|
if(tag == BeginCollection)
|
|
{
|
|
// qDebug() << "Unnamed attrs for collection" << unnamed;
|
|
|
|
QJsonValue collected = collect_attributes(unnamed);
|
|
// qDebug() << "collected" << collected;
|
|
taggedValue = QJsonObject {{"tag", tag}, {"value", collected}};
|
|
}
|
|
|
|
bool noList = (tag == Boolean || tag == IntegerRange);
|
|
bool forceArray = ((name.endsWith("-supported") || name == "printer-icons") && !noList);
|
|
|
|
if(!unnamed.empty() || forceArray)
|
|
{
|
|
unnamed.prepend(taggedValue);
|
|
attrs.insert(name, QJsonObject {{"tag", tag}, {"value", untag_values(unnamed)}});
|
|
|
|
}
|
|
else
|
|
{
|
|
attrs.insert(name, taggedValue);
|
|
}
|
|
return name;
|
|
}
|
|
|
|
QByteArray IppMsg::encode(Operation op)
|
|
{
|
|
Bytestream ipp;
|
|
|
|
ipp << _majVsn << _minVsn;
|
|
|
|
ipp << quint16(op);
|
|
ipp << _reqid++;
|
|
|
|
|
|
ipp << quint8(OpAttrs);
|
|
// attributes-charset and attributes-natural-language are required to be first
|
|
// some printers fail if the other mandatory parameters are not in this specific order
|
|
QStringList InitialAttrs = {"attributes-charset",
|
|
"attributes-natural-language",
|
|
"printer-uri",
|
|
"requesting-user-name"};
|
|
foreach(QString key, InitialAttrs)
|
|
{
|
|
QJsonObject val = _opAttrs.take(key).toObject();
|
|
encode_attr(ipp, val["tag"].toInt(), key, val["value"]);
|
|
}
|
|
for(QJsonObject::iterator it = _opAttrs.begin(); it != _opAttrs.end(); it++)
|
|
{ // encode any remaining op-attrs
|
|
QJsonObject val = it.value().toObject();
|
|
encode_attr(ipp, val["tag"].toInt(), it.key(), val["value"]);
|
|
}
|
|
for(QJsonArray::iterator ait = _jobAttrs.begin(); ait != _jobAttrs.end(); ait++)
|
|
{
|
|
QJsonObject tmpObj = ait->toObject();
|
|
if (!tmpObj.isEmpty()) {
|
|
ipp << quint8(JobAttrs);
|
|
for(QJsonObject::iterator it = tmpObj.begin(); it != tmpObj.end(); it++)
|
|
{
|
|
QJsonObject val = it.value().toObject();
|
|
encode_attr(ipp, val["tag"].toInt(), it.key(), val["value"]);
|
|
}
|
|
}
|
|
}
|
|
|
|
ipp << quint8(EndAttrs);
|
|
|
|
return QByteArray((char*)(ipp.raw()), ipp.size());
|
|
}
|
|
|
|
void IppMsg::encode_attr(Bytestream& msg, quint8 tag, QString name, QJsonValue value, bool inCollection)
|
|
{
|
|
|
|
if(inCollection)
|
|
{
|
|
msg << (quint8)MemberName << (quint16)0 << (quint16)name.length() << name.toStdString();
|
|
name = "";
|
|
}
|
|
msg << tag << quint16(name.length()) << name.toStdString();
|
|
|
|
|
|
switch (tag) {
|
|
case OpAttrs:
|
|
case JobAttrs:
|
|
case EndAttrs:
|
|
case PrinterAttrs:
|
|
Q_ASSERT(false);
|
|
case Integer:
|
|
case Enum:
|
|
{
|
|
quint32 tmp_u32 = value.toInt();
|
|
msg << (quint16)4 << tmp_u32;
|
|
break;
|
|
}
|
|
case Boolean:
|
|
{
|
|
quint32 tmp_u8 = value.toBool();
|
|
msg << (quint16)1 << tmp_u8;
|
|
break;
|
|
}
|
|
case DateTime:
|
|
{
|
|
Q_ASSERT("fixme");
|
|
break;
|
|
}
|
|
case Resolution:
|
|
{
|
|
qint32 x = value.toObject()["x"].toInt();
|
|
qint32 y = value.toObject()["y"].toInt();
|
|
qint8 units = value.toObject()["units"].toInt();
|
|
msg << (quint16)9 << x << y << units;
|
|
break;
|
|
}
|
|
case IntegerRange:
|
|
{
|
|
qint32 low = value.toObject()["low"].toInt();
|
|
qint32 high = value.toObject()["high"].toInt();
|
|
msg << (quint16)8 << low << high;
|
|
break;
|
|
}
|
|
case BeginCollection:
|
|
{
|
|
msg << (quint16)0; // length of value
|
|
if(value.isObject())
|
|
{
|
|
QJsonObject collection = value.toObject();
|
|
foreach(QString key, collection.keys())
|
|
{
|
|
encode_attr(msg, collection[key].toObject()["tag"].toInt(), key,
|
|
collection[key].toObject()["value"], true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO add support for 1-setOf in collections
|
|
Q_ASSERT("FIXME-array");
|
|
}
|
|
msg << (quint8)EndCollection << (quint16)0 << (quint16)0;
|
|
break;
|
|
}
|
|
case OctetStringUnknown:
|
|
case TextWithLanguage:
|
|
case NameWithLanguage:
|
|
case TextWithoutLanguage:
|
|
case NameWithoutLanguage:
|
|
case Keyword:
|
|
case Uri:
|
|
case UriScheme:
|
|
case Charset:
|
|
case NaturalLanguage:
|
|
case MimeMediaType:
|
|
{
|
|
QByteArray tmpstr = value.toString().toUtf8();
|
|
msg << quint16(tmpstr.length());
|
|
msg.putBytes(tmpstr.data(), tmpstr.length());
|
|
break;
|
|
}
|
|
default:
|
|
qDebug() << "uncaught tag" << tag;
|
|
Q_ASSERT(false);
|
|
break;
|
|
}
|
|
}
|