// Sun, 18 Jun 2000
// Y.Takagi

#ifdef WIN32
#include <winsock.h>
#else
#include <net/socket.h>
#endif

#include <fstream>
#include <list>
#include <cstring>

#include "IppContent.h"

/*----------------------------------------------------------------------*/

short readLength(istream &is)
{
	short len = 0;
	is.read((char *)&len, sizeof(short));
	len = ntohs(len);
	return len;
}

void writeLength(ostream &os, short len)
{
	len = htons(len);
	os.write((char *)&len, sizeof(short));
}

/*----------------------------------------------------------------------*/

DATETIME::DATETIME()
{
	memset(this, 0, sizeof(DATETIME));
}

DATETIME::DATETIME(const DATETIME &dt)
{
	memcpy(this, &dt.datetime, sizeof(DATETIME));
}

DATETIME & DATETIME::operator = (const DATETIME &dt)
{
	memcpy(this, &dt.datetime, sizeof(DATETIME));
	return *this;
}

istream& operator >> (istream &is, DATETIME &attr)
{
	return is;
}

ostream& operator << (ostream &os, const DATETIME &attr)
{
	return os;
}


/*----------------------------------------------------------------------*/

IppAttribute::IppAttribute(IPP_TAG t)
	: tag(t)
{
}

int IppAttribute::length() const
{
	return 1;
}

istream &IppAttribute::input(istream &is)
{
	return is;
}

ostream &IppAttribute::output(ostream &os) const
{
	os << (unsigned char)tag;
	return os;
}

ostream &IppAttribute::print(ostream &os) const
{
	os << "Tag: " << hex << (int)tag << '\n';
	return os;
}

/*----------------------------------------------------------------------*/

IppNamedAttribute::IppNamedAttribute(IPP_TAG t)
	: IppAttribute(t)
{
}

IppNamedAttribute::IppNamedAttribute(IPP_TAG t, const char *s)
	: IppAttribute(t), name(s ? s : "")
{
}

int IppNamedAttribute::length() const
{
	return IppAttribute::length() + 2 + name.length();
}

istream &IppNamedAttribute::input(istream &is)
{
	short len = readLength(is);

	if (0 < len) {
		char *buffer = new char[len + 1];
		is.read(buffer, len);
		buffer[len] = '\0';
		name = buffer;
		delete [] buffer;
	}

	return is;
}

ostream &IppNamedAttribute::output(ostream &os) const
{
	IppAttribute::output(os);

	writeLength(os, name.length());
	os << name;

	return os;
}

ostream &IppNamedAttribute::print(ostream &os) const
{
	IppAttribute::print(os);
	os << '\t' << "Name: " << name << '\n';
	return os;
}

/*----------------------------------------------------------------------*/

IppNoValueAttribute::IppNoValueAttribute(IPP_TAG t)
	: IppNamedAttribute(t)
{
}

IppNoValueAttribute::IppNoValueAttribute(IPP_TAG t, const char *n)
	: IppNamedAttribute(t, n)
{
}

int IppNoValueAttribute::length() const
{
	return IppAttribute::length() + 2;
}

istream &IppNoValueAttribute::input(istream &is)
{
	IppNamedAttribute::input(is);

	short len = readLength(is);

	if (0 < len) {
		is.seekg(len, ios::cur);
	}

	return is;
}

ostream &IppNoValueAttribute::output(ostream &os) const
{
	IppAttribute::output(os);

	writeLength(os, 0);

	return os;
}

ostream &IppNoValueAttribute::print(ostream &os) const
{
	return IppNamedAttribute::print(os);
}

/*----------------------------------------------------------------------*/

IppIntegerAttribute::IppIntegerAttribute(IPP_TAG t)
	: IppNamedAttribute(t), value(0)
{
}

IppIntegerAttribute::IppIntegerAttribute(IPP_TAG t, const char *n, int v)
	: IppNamedAttribute(t, n), value(v)
{
}

int IppIntegerAttribute::length() const
{
	return IppNamedAttribute::length() + 2 + 4;
}

istream &IppIntegerAttribute::input(istream &is)
{
	IppNamedAttribute::input(is);

	short len = readLength(is);

	if (0 < len && len <= 4) {
		is.read((char *)&value, sizeof(value));
		value = ntohl(value);
	} else {
		is.seekg(len, ios::cur);
	}

	return is;
}

ostream &IppIntegerAttribute::output(ostream &os) const
{
	IppNamedAttribute::output(os);

	writeLength(os, 4);
	unsigned long val = htonl(value);
	os.write((char *)&val, sizeof(val));
	return os;
}

ostream &IppIntegerAttribute::print(ostream &os) const
{
	IppNamedAttribute::print(os);
	os << '\t' << "Value: " << dec << value << '\n';
	return os;
}

/*----------------------------------------------------------------------*/

IppBooleanAttribute::IppBooleanAttribute(IPP_TAG t)
	: IppNamedAttribute(t), value(false)
{
}

IppBooleanAttribute::IppBooleanAttribute(IPP_TAG t, const char *n, bool f)
	: IppNamedAttribute(t, n), value(f)
{
}

int IppBooleanAttribute::length() const
{
	return IppNamedAttribute::length() + 2 + 1;
}


istream &IppBooleanAttribute::input(istream &is)
{
	IppNamedAttribute::input(is);

	short len = readLength(is);

	if (0 < len && len <= 1) {
		char c;
		is.read((char *)&c, sizeof(c));
		value = c ? true : false;
	} else {
		is.seekg(len, ios::cur);
	}

	return is;
}

ostream &IppBooleanAttribute::output(ostream &os) const
{
	IppNamedAttribute::output(os);

	writeLength(os, 1);
	char c = (char)value;
	os.write((char *)&c, sizeof(c));

	return os;
}

ostream &IppBooleanAttribute::print(ostream &os) const
{
	IppNamedAttribute::print(os);
	os << '\t' << "Value: " << value << '\n';
	return os;
}

/*----------------------------------------------------------------------*/

IppDatetimeAttribute::IppDatetimeAttribute(IPP_TAG t)
	: IppNamedAttribute(t)
{
}

IppDatetimeAttribute::IppDatetimeAttribute(IPP_TAG t, const char *n, const DATETIME *dt)
	: IppNamedAttribute(t, n), datetime(*dt)
{
}

int IppDatetimeAttribute::length() const
{
	return IppNamedAttribute::length() + 2 + 11;
}

istream &IppDatetimeAttribute::input(istream &is)
{
	IppNamedAttribute::input(is);

	short len = readLength(is);

	if (0 < len) {
		if (len == 11) {
			is >> datetime;
		} else {
			is.seekg(len, ios::cur);
		}
	}

	return is;
}

ostream &IppDatetimeAttribute::output(ostream &os) const
{
	IppNamedAttribute::output(os);

	writeLength(os, 11);
	os << datetime;

	return os;
}

ostream &IppDatetimeAttribute::print(ostream &os) const
{
	IppNamedAttribute::print(os);
	os << '\t' << "Value(DateTime): " << datetime << '\n';
	return os;
}

/*----------------------------------------------------------------------*/

IppStringAttribute::IppStringAttribute(IPP_TAG t)
	: IppNamedAttribute(t)
{
}

IppStringAttribute::IppStringAttribute(IPP_TAG t, const char *n, const char *s)
: IppNamedAttribute(t, n), text(s ? s : "")
{
}

int IppStringAttribute::length() const
{
	return IppNamedAttribute::length() + 2 + text.length();
}

istream &IppStringAttribute::input(istream &is)
{
	IppNamedAttribute::input(is);

	short len = readLength(is);

	if (0 < len) {
		char *buffer = new char[len + 1];
		is.read(buffer, len);
		buffer[len] = '\0';
		text = buffer;
		delete [] buffer;
	}

	return is;
}

ostream &IppStringAttribute::output(ostream &os) const
{
	IppNamedAttribute::output(os);

	writeLength(os, text.length());
	os << text;

	return os;
}

ostream &IppStringAttribute::print(ostream &os) const
{
	IppNamedAttribute::print(os);
	os << '\t' << "Value: " << text << '\n';
	return os;
}

/*----------------------------------------------------------------------*/

IppDoubleStringAttribute::IppDoubleStringAttribute(IPP_TAG t)
	: IppNamedAttribute(t)
{
}

IppDoubleStringAttribute::IppDoubleStringAttribute(IPP_TAG t, const char *n, const char *s1, const char *s2)
: IppNamedAttribute(t, n), text1(s1 ? s1 : ""), text2(s2 ? s2 : "")
{
}

int IppDoubleStringAttribute::length() const
{
	return IppNamedAttribute::length() + 2 + text1.length() + 2  + text2.length();
}

istream &IppDoubleStringAttribute::input(istream &is)
{
	IppNamedAttribute::input(is);

	short len = readLength(is);

	if (0 < len) {
		char *buffer = new char[len + 1];
		is.read(buffer, len);
		buffer[len] = '\0';
		text1 = buffer;
		delete [] buffer;
	}

	len = readLength(is);

	if (0 < len) {
		char *buffer = new char[len + 1];
		is.read(buffer, len);
		buffer[len] = '\0';
		text2 = buffer;
		delete [] buffer;
	}

	return is;
}

ostream &IppDoubleStringAttribute::output(ostream &os) const
{
	IppNamedAttribute::output(os);

	writeLength(os, text1.length());
	os << text1;

	writeLength(os, text2.length());
	os << text2;

	return os;
}

ostream &IppDoubleStringAttribute::print(ostream &os) const
{
	IppNamedAttribute::print(os);
	os << '\t' << "Value1: " << text1 << '\n';
	os << '\t' << "Value2: " << text2 << '\n';
	return os;
}

/*----------------------------------------------------------------------*/

IppResolutionAttribute::IppResolutionAttribute(IPP_TAG t)
	: IppNamedAttribute(t), xres(0), yres(0), resolution_units((IPP_RESOLUTION_UNITS)0)
{
}

IppResolutionAttribute::IppResolutionAttribute(IPP_TAG t, const char *n, int x, int y, IPP_RESOLUTION_UNITS u)
	: IppNamedAttribute(t, n), xres(x), yres(y), resolution_units(u)
{
}

int IppResolutionAttribute::length() const
{
	return IppNamedAttribute::length() + 2 + 4 + 2 + 4 + 2 + 1;
}

istream &IppResolutionAttribute::input(istream &is)
{
	IppNamedAttribute::input(is);

	short len = readLength(is);

	if (0 < len && len <= 4) {
		is.read((char *)&xres, sizeof(xres));
		xres = ntohl(xres);
	} else {
		is.seekg(len, ios::cur);
	}

	len = readLength(is);

	if (0 < len && len <= 4) {
		is.read((char *)&yres, sizeof(yres));
		yres = ntohl(yres);
	} else {
		is.seekg(len, ios::cur);
	}

	len = readLength(is);

	if (len == 1) {
		char c;
		is.read((char *)&c, sizeof(c));
		resolution_units = (IPP_RESOLUTION_UNITS)c;
	} else {
		is.seekg(len, ios::cur);
	}

	return is;
}

ostream &IppResolutionAttribute::output(ostream &os) const
{
	IppNamedAttribute::output(os);

	writeLength(os, 4);
	unsigned long val = htonl(xres);
	os.write((char *)&val, sizeof(val));

	writeLength(os, 4);
	val = htonl(yres);
	os.write((char *)&val, sizeof(val));

	writeLength(os, 1);
	unsigned char c = (unsigned char)resolution_units;
	os.write((char *)&c, sizeof(c));

	return os;
}

ostream &IppResolutionAttribute::print(ostream &os) const
{
	IppNamedAttribute::print(os);
	os << '\t' << "Value(xres): " << dec << xres << '\n';
	os << '\t' << "Value(yres): " << dec << yres << '\n';
	os << '\t' << "Value(unit): " << dec << resolution_units << '\n';
	return os;
}

/*----------------------------------------------------------------------*/

IppRangeOfIntegerAttribute::IppRangeOfIntegerAttribute(IPP_TAG t)
	: IppNamedAttribute(t), lower(0), upper(0)
{
}

IppRangeOfIntegerAttribute::IppRangeOfIntegerAttribute(IPP_TAG t, const char *n, int l, int u)
	: IppNamedAttribute(t, n), lower(l), upper(u)
{
}

int IppRangeOfIntegerAttribute::length() const
{
	return IppNamedAttribute::length() + 2 + 4 + 2 + 4;
}

istream &IppRangeOfIntegerAttribute::input(istream &is)
{
	IppNamedAttribute::input(is);

	short len = readLength(is);

	if (0 < len && len <= 4) {
		is.read((char *)&lower, sizeof(lower));
		lower = ntohl(lower);
	} else {
		is.seekg(len, ios::cur);
	}

	len = readLength(is);

	if (0 < len && len <= 4) {
		is.read((char *)&upper, sizeof(upper));
		upper = ntohl(upper);
	} else {
		is.seekg(len, ios::cur);
	}

	return is;
}

ostream &IppRangeOfIntegerAttribute::output(ostream &os) const
{
	IppNamedAttribute::output(os);

	writeLength(os, 4);
	unsigned long val = htonl(lower);
	os.write((char *)&val, sizeof(val));

	writeLength(os, 4);
	val = htonl(upper);
	os.write((char *)&val, sizeof(val));

	return os;
}

ostream &IppRangeOfIntegerAttribute::print(ostream &os) const
{
	IppNamedAttribute::print(os);
	os << '\t' << "Value(lower): " << dec << lower << '\n';
	os << '\t' << "Value(upper): " << dec << upper << '\n';
	return os;
}

/*----------------------------------------------------------------------*/

IppContent::IppContent()
{
	version      = 0x0100;
	operation_id = IPP_GET_PRINTER_ATTRIBUTES;
	request_id   = 0x00000001;

	is = NULL;
	size = -1;
}

IppContent::~IppContent()
{
	for (list<IppAttribute *>::const_iterator it = attrs.begin(); it != attrs.end(); it++) {
		delete (*it);
	}
}

unsigned short IppContent::getVersion() const
{
	return version;
}

void IppContent::setVersion(unsigned short i)
{
	version = i;
}


IPP_OPERATION_ID IppContent::getOperationId() const
{
	return (IPP_OPERATION_ID)operation_id;
}

void IppContent::setOperationId(IPP_OPERATION_ID i)
{
	operation_id = i;
}

IPP_STATUS_CODE IppContent::getStatusCode() const
{
	return (IPP_STATUS_CODE)operation_id;
}

unsigned long IppContent::getRequestId() const
{
	return request_id;
}

void IppContent::setRequestId(unsigned long i)
{
	request_id = i;
}

istream &IppContent::input(istream &is)
{
	if (!is.read((char *)&version, sizeof(version))) {
		return is;
	}

	version = ntohs(version);

	if (!is.read((char *)&operation_id, sizeof(operation_id))) {
		return is;
	}

	operation_id = ntohs(operation_id);

	if (!is.read((char *)&request_id, sizeof(request_id))) {
		return is;
	}

	request_id = ntohl(request_id);
	char tag;

	while (1) {

		if (!is.read((char *)&tag, sizeof(tag))) {
			return is;
		}

		if (tag <= 0x0F) {	// delimiter

//			case IPP_OPERATION_ATTRIBUTES_TAG:
//			case IPP_JOB_ATTRIBUTES_TAG:
//			case IPP_END_OF_ATTRIBUTES_TAG:
//			case IPP_PRINTER_ATTRIBUTES_TAG:
//			case IPP_UNSUPPORTED_ATTRIBUTES_TAG:

			attrs.push_back(new IppAttribute((IPP_TAG)tag));
			if (tag == IPP_END_OF_ATTRIBUTES_TAG) {
				break;
			}

		} else if (tag <= 0x1F) {

			IppNoValueAttribute *attr = new IppNoValueAttribute((IPP_TAG)tag);
			is >> *attr;
			attrs.push_back(attr);

		} else if (tag <= 0x2F) {	// integer values

			switch (tag) {
			case IPP_INTEGER:
			case IPP_ENUM:
				{
					IppIntegerAttribute *attr = new IppIntegerAttribute((IPP_TAG)tag);
					is >> *attr;
					attrs.push_back(attr);
				}
				break;
			case IPP_BOOLEAN:
				{
					IppBooleanAttribute *attr = new IppBooleanAttribute((IPP_TAG)tag);
					is >> *attr;
					attrs.push_back(attr);
				}
				break;
			default:
				{
					short len = readLength(is);
					is.seekg(len, ios::cur);
					len = readLength(is);
					is.seekg(len, ios::cur);
				}
				break;
			}

		} else if (tag <= 0x3F) {	// octetString values

			switch (tag) {
			case IPP_STRING:
				{
					IppStringAttribute *attr = new IppStringAttribute((IPP_TAG)tag);
					is >> *attr;
					attrs.push_back(attr);
				}
				break;
			case IPP_DATETIME:
				{
					IppDatetimeAttribute *attr = new IppDatetimeAttribute((IPP_TAG)tag);
					is >> *attr;
					attrs.push_back(attr);
				}
				break;
			case IPP_RESOLUTION:
				{
					IppResolutionAttribute *attr = new IppResolutionAttribute((IPP_TAG)tag);
					is >> *attr;
					attrs.push_back(attr);
				}
				break;
			case IPP_RANGE_OF_INTEGER:
				{
					IppRangeOfIntegerAttribute *attr = new IppRangeOfIntegerAttribute((IPP_TAG)tag);
					is >> *attr;
					attrs.push_back(attr);
				}
				break;
			case IPP_TEXT_WITH_LANGUAGE:
			case IPP_NAME_WITH_LANGUAGE:
				{
					IppDoubleStringAttribute *attr = new IppDoubleStringAttribute((IPP_TAG)tag);
					is >> *attr;
					attrs.push_back(attr);
				}
				break;
			default:
				{
					short len = readLength(is);
					is.seekg(len, ios::cur);
					len = readLength(is);
					is.seekg(len, ios::cur);
				}
				break;
			}

		} else if (tag <= 0x5F) {	// character-string values

//			case IPP_TEXT_WITHOUT_LANGUAGE:
//			case IPP_NAME_WITHOUT_LANGUAGE:
//			case IPP_KEYWORD:
//			case IPP_URI:
//			case IPP_URISCHEME:
//			case IPP_CHARSET:
//			case IPP_NATURAL_LANGUAGE:
//			case IPP_MIME_MEDIA_TYPE:

			IppStringAttribute *attr = new IppStringAttribute((IPP_TAG)tag);
			is >> *attr;
			attrs.push_back(attr);
		}
	}
	return is;
}

ostream &IppContent::output(ostream &os) const
{
	unsigned short ns_version = htons(version);						// version-number
	os.write((char *)&ns_version, sizeof(ns_version));				// version-number

	unsigned short ns_operation_id = htons(operation_id);			// operation-id
	os.write((char *)&ns_operation_id, sizeof(ns_operation_id));	// operation-id

	unsigned long ns_request_id = htonl(request_id);				// request-id
	os.write((char *)&ns_request_id, sizeof(ns_request_id));		// request-id

	for (list<IppAttribute *>::const_iterator it = attrs.begin(); it != attrs.end(); it++) {
		os << *(*it);
	}

	ifstream ifs;
	istream *iss = is;
	if (iss == NULL) {
		if (!file_path.empty()) {
			ifs.open(file_path.c_str(), ios::in | ios::binary);
			iss = &ifs;
		}
	}
	if (iss && iss->good()) {
		if (iss->good()) {
			char c;
			while (iss->get(c)) {
				os.put(c);
			}
		}
	}

	return os;
}

void IppContent::setDelimiter(IPP_TAG tag)
{
	attrs.push_back(new IppAttribute(tag));
}

void IppContent::setInteger(const char *name, int value)
{
	attrs.push_back(new IppIntegerAttribute(IPP_INTEGER, name, value));
}

void IppContent::setBoolean(const char *name, bool value)
{
	attrs.push_back(new IppBooleanAttribute(IPP_BOOLEAN, name, value));
}

void IppContent::setString(const char *name, const char *value)
{
	attrs.push_back(new IppStringAttribute(IPP_STRING, name, value));
}

void IppContent::setDateTime(const char *name, const DATETIME *dt)
{
	attrs.push_back(new IppDatetimeAttribute(IPP_DATETIME, name, dt));
}

void IppContent::setResolution(const char *name, int x, int y, IPP_RESOLUTION_UNITS u)
{
	attrs.push_back(new IppResolutionAttribute(IPP_RESOLUTION, name, x, y, u));
}

void IppContent::setRangeOfInteger(const char *name, int lower, int upper)
{
	attrs.push_back(new IppRangeOfIntegerAttribute(IPP_RANGE_OF_INTEGER, name, lower, upper));
}

void IppContent::setTextWithLanguage(const char *name, const char *s1, const char *s2)
{
	attrs.push_back(new IppDoubleStringAttribute(IPP_TEXT_WITH_LANGUAGE, name, s1, s2));
}

void IppContent::setNameWithLanguage(const char *name, const char *s1, const char *s2)
{
	attrs.push_back(new IppDoubleStringAttribute(IPP_NAME_WITH_LANGUAGE, name, s1, s2));
}

void IppContent::setTextWithoutLanguage(const char *name, const char *value)
{
	attrs.push_back(new IppStringAttribute(IPP_TEXT_WITHOUT_LANGUAGE, name, value));
}

void IppContent::setNameWithoutLanguage(const char *name, const char *value)
{
	attrs.push_back(new IppStringAttribute(IPP_NAME_WITHOUT_LANGUAGE, name, value));
}

void IppContent::setKeyword(const char *name, const char *value)
{
	attrs.push_back(new IppStringAttribute(IPP_KEYWORD, name, value));
}

void IppContent::setURI(const char *name, const char *value)
{
	attrs.push_back(new IppStringAttribute(IPP_URI, name, value));
}

void IppContent::setURIScheme(const char *name, const char *value)
{
	attrs.push_back(new IppStringAttribute(IPP_URISCHEME, name, value));
}

void IppContent::setCharset(const char *name, const char *value)
{
	attrs.push_back(new IppStringAttribute(IPP_CHARSET, name, value));
}

void IppContent::setNaturalLanguage(const char *name, const char *value)
{
	attrs.push_back(new IppStringAttribute(IPP_NATURAL_LANGUAGE, name, value));
}

void IppContent::setMimeMediaType(const char *name, const char *value)
{
	attrs.push_back(new IppStringAttribute(IPP_MIME_MEDIA_TYPE, name, value));
}

int IppContent::length() const
{
	int length = 8;	// sizeof(version-number + operation-id + request-id)

	for (list<IppAttribute *>::const_iterator it = attrs.begin(); it != attrs.end(); it++) {
		length += (*it)->length();
	}

	ifstream ifs;
	istream *iss = is;
	if (iss == NULL) {
		if (!file_path.empty()) {
			ifs.open(file_path.c_str(), ios::in | ios::binary);
			iss = &ifs;
		}
	}
	if (iss && iss->good()) {
		int fsize = size;
		if (fsize < 0) {
			streampos pos = iss->tellg();
			iss->seekg(0, ios::end);
			fsize = iss->tellg();
			iss->seekg(pos, ios::beg);
		}
		if (fsize > 0) {
			length += fsize;
		}
	}

	return length;
}

void IppContent::setRawData(const char *file, int n)
{
	file_path = file;
	size = n;
}

void IppContent::setRawData(istream &ifs, int n)
{
	is = &ifs;
	size = n;
}

ostream &IppContent::print(ostream &os) const
{
	os << "version:      " << hex << version << '\n';
	os << "operation_id: " << hex << operation_id << '\n';
	os << "request_id:   " << hex << request_id << '\n';

	for (list<IppAttribute *>::const_iterator it = attrs.begin(); it != attrs.end(); it++) {
		(*it)->print(os);
	}

	return os;
}

bool IppContent::fail() const
{
	return !good();
}

bool IppContent::good() const
{
	return (IPP_SUCCESSFUL_OK_S <= operation_id) && (operation_id <= IPP_SUCCESSFUL_OK_E);
}

bool IppContent::operator !() const
{
	return fail();
}

const char *IppContent::getStatusMessage() const
{
	if (good()) {
		switch (operation_id) {
		case IPP_SUCCESSFUL_OK:
			return "successful-ok";
		case IPP_SUCCESSFUL_OK_IGNORED_OR_SUBSTITUTED_ATTRIBUTES:
			return "successful-ok-ignored-or-substituted-attributes";
		case IPP_SUCCESSFUL_OK_CONFLICTING_ATTRIBUTES:
			return "successful-ok-conflicting-attributes";
		default:
			return "successful-ok-???";
		}
	} else if (IPP_CLIENT_ERROR_S <= operation_id && operation_id <= IPP_CLIENT_ERROR_E) {
		switch (operation_id) {
		case IPP_CLIENT_ERROR_BAD_REQUEST:
			return "client-error-bad-request";
		case IPP_CLIENT_ERROR_FORBIDDEN:
			return "client-error-forbidden";
		case IPP_CLIENT_ERROR_NOT_AUTHENTICATED:
			return "client-error-not-authenticated";
		case IPP_CLIENT_ERROR_NOT_AUTHORIZED:
			return "client-error-not-authorized";
		case IPP_CLIENT_ERROR_NOT_POSSIBLE:
			return "client-error-not-possible";
		case IPP_CLIENT_ERROR_TIMEOUT:
			return "client-error-timeout";
		case IPP_CLIENT_ERROR_NOT_FOUND:
			return "client-error-not-found";
		case IPP_CLIENT_ERROR_GONE:
			return "client-error-gone";
		case IPP_CLIENT_ERROR_REQUEST_ENTITY_TOO_LARGE:
			return "client-error-request-entity-too-large";
		case IPP_CLIENT_ERROR_REQUEST_VALUE_TOO_LONG:
			return "client-error-request-value-too-long";
		case IPP_CLIENT_ERROR_DOCUMENT_FORMAT_NOT_SUPPORTED:
			return "client-error-document-format-not-supported";
		case IPP_CLIENT_ERROR_ATTRIBUTES_OR_VALUES_NOT_SUPPORTED:
			return "client-error-attributes-or-values-not-supported";
		case IPP_CLIENT_ERROR_URI_SCHEME_NOT_SUPPORTED:
			return "client-error-uri-scheme-not-supported";
		case IPP_CLIENT_ERROR_CHARSET_NOT_SUPPORTED:
			return "client-error-charset-not-supported";
		case IPP_CLIENT_ERROR_CONFLICTING_ATTRIBUTES:
			return "client-error-conflicting-attributes";
		default:
			return "client-error-???";
		}
	} else if (IPP_SERVER_ERROR_S <= operation_id && operation_id <= IPP_SERVER_ERROR_E) {
		switch (operation_id) {
		case IPP_SERVER_ERROR_INTERNAL_ERROR:
			return "server-error-internal-error";
		case IPP_SERVER_ERROR_OPERATION_NOT_SUPPORTED:
			return "server-error-operation-not-supported";
		case IPP_SERVER_ERROR_SERVICE_UNAVAILABLE:
			return "server-error-service-unavailable";
		case IPP_SERVER_ERROR_VERSION_NOT_SUPPORTED:
			return "server-error-version-not-supported";
		case IPP_SERVER_ERROR_DEVICE_ERROR:
			return "server-error-device-error";
		case IPP_SERVER_ERROR_TEMPORARY_ERROR:
			return "server-error-temporary-error";
		case IPP_SERVER_ERROR_NOT_ACCEPTING_JOBS:
			return "server-error-not-accepting-jobs";
		case IPP_SERVER_ERROR_BUSY:
			return "server-error-busy";
		case IPP_SERVER_ERROR_JOB_CANCELED:
			return "server-error-job-canceled";
		default:
			return "server-error-???";
		}
	} else {
		return "unknown error.";
	}
}
