Kea 1.5.0
rrparamregistry.cc
Go to the documentation of this file.
1
7
8// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC")
9//
10// This Source Code Form is subject to the terms of the Mozilla Public
11// License, v. 2.0. If a copy of the MPL was not distributed with this
12// file, You can obtain one at http://mozilla.org/MPL/2.0/.
13
14#include <config.h>
15
16#include <cassert>
17#include <algorithm>
18#include <cctype>
19#include <functional>
20#include <map>
21#include <string>
22#include <sstream>
23#include <utility>
24
25#include <stdint.h>
26
27#include <boost/shared_ptr.hpp>
28
30
31#include <dns/rrparamregistry.h>
32#include <dns/rrclass.h>
33#include <dns/rrtype.h>
34#include <dns/rdata.h>
35#include <dns/rdataclass.h>
36
37using namespace std;
38
39using namespace isc::util;
40using namespace isc::dns::rdata;
41
42namespace isc {
43namespace dns {
44
45namespace {
51bool
52CICharLess(char c1, char c2) {
53 return (tolower(static_cast<unsigned char>(c1)) <
54 tolower(static_cast<unsigned char>(c2)));
55}
56
57struct CIStringLess :
58 public binary_function<string, string, bool>
59{
60 bool operator()(const string& s1, const string& s2) const
61 {
62 return (lexicographical_compare(s1.begin(), s1.end(),
63 s2.begin(), s2.end(), CICharLess));
64 }
65};
66
67struct RRTypeParam {
68 RRTypeParam(const string& code_string, uint16_t code) :
69 code_string_(code_string), code_(code) {}
71 uint16_t code_;
72
74 static const unsigned int MAX_CODE = 0xffff;
75 static const string& UNKNOWN_PREFIX();
76 static size_t UNKNOWN_PREFIXLEN();
77 static const string& UNKNOWN_MAX();
78 static size_t UNKNOWN_MAXLEN();
79};
80
81typedef boost::shared_ptr<RRTypeParam> RRTypeParamPtr;
82typedef map<string, RRTypeParamPtr, CIStringLess> StrRRTypeMap;
83typedef map<uint16_t, RRTypeParamPtr> CodeRRTypeMap;
84
85inline const string&
86RRTypeParam::UNKNOWN_PREFIX() {
87 static const string p("TYPE");
88 return (p);
89}
90
91inline size_t
92RRTypeParam::UNKNOWN_PREFIXLEN() {
93 static size_t plen = UNKNOWN_PREFIX().size();
94 return (plen);
95}
96
97inline const string&
98RRTypeParam::UNKNOWN_MAX() {
99 static const string p("TYPE65535");
100 return (p);
101}
102
103inline size_t
104RRTypeParam::UNKNOWN_MAXLEN() {
105 static size_t plen = UNKNOWN_MAX().size();
106 return (plen);
107}
108
109struct RRClassParam {
110 RRClassParam(const string& code_string, uint16_t code) :
111 code_string_(code_string), code_(code) {}
112 string code_string_;
113 uint16_t code_;
114
116 static const unsigned int MAX_CODE = 0xffff;
117 static const string& UNKNOWN_PREFIX();
118 static size_t UNKNOWN_PREFIXLEN();
119 static const string& UNKNOWN_MAX();
120 static size_t UNKNOWN_MAXLEN();
121};
122
123typedef boost::shared_ptr<RRClassParam> RRClassParamPtr;
124typedef map<string, RRClassParamPtr, CIStringLess> StrRRClassMap;
125typedef map<uint16_t, RRClassParamPtr> CodeRRClassMap;
126
127inline const string&
128RRClassParam::UNKNOWN_PREFIX() {
129 static const string p("CLASS");
130 return (p);
131}
132
133inline size_t
134RRClassParam::UNKNOWN_PREFIXLEN() {
135 static size_t plen = UNKNOWN_PREFIX().size();
136 return (plen);
137}
138
139inline const string&
140RRClassParam::UNKNOWN_MAX() {
141 static const string p("CLASS65535");
142 return (p);
143}
144
145inline size_t
146RRClassParam::UNKNOWN_MAXLEN() {
147 static size_t plen = UNKNOWN_MAX().size();
148 return (plen);
149}
150} // end of anonymous namespace
151
160typedef pair<RRType, RRClass> RRTypeClass;
161typedef map<RRTypeClass, RdataFactoryPtr> RdataFactoryMap;
162typedef map<RRType, RdataFactoryPtr> GenericRdataFactoryMap;
163
164template <typename T>
166public:
167 virtual RdataPtr create(const string& rdata_str) const
168 {
169 return (RdataPtr(new T(rdata_str)));
170 }
171
172 virtual RdataPtr create(InputBuffer& buffer, size_t rdata_len) const
173 {
174 return (RdataPtr(new T(buffer, rdata_len)));
175 }
176
177 virtual RdataPtr create(const Rdata& source) const
178 {
179 return (RdataPtr(new T(dynamic_cast<const T&>(source))));
180 }
181
182 virtual RdataPtr create(MasterLexer& lexer, const Name* origin,
183 MasterLoader::Options options,
184 MasterLoaderCallbacks& callbacks) const
185 {
186 return (RdataPtr(new T(lexer, origin, options, callbacks)));
187 }
188};
189
199 StrRRTypeMap str2typemap;
201 CodeRRTypeMap code2typemap;
203 StrRRClassMap str2classmap;
205 CodeRRClassMap code2classmap;
208};
209
210RRParamRegistry::RRParamRegistry() {
211 impl_ = new RRParamRegistryImpl;
212
213 // set up parameters for well-known RRs
214 try {
215 // BEGIN_WELL_KNOWN_PARAMS
216 add("A", 1, "IN", 1, RdataFactoryPtr(new RdataFactory<in::A>()));
217 add("NS", 2, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::NS>()));
218 add("CNAME", 5, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::CNAME>()));
219 add("SOA", 6, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::SOA>()));
220 add("PTR", 12, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::PTR>()));
221 add("HINFO", 13, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::HINFO>()));
222 add("MINFO", 14, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::MINFO>()));
223 add("MX", 15, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::MX>()));
224 add("TXT", 16, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::TXT>()));
225 add("RP", 17, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::RP>()));
226 add("AFSDB", 18, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::AFSDB>()));
227 add("AAAA", 28, "IN", 1, RdataFactoryPtr(new RdataFactory<in::AAAA>()));
228 add("SRV", 33, "IN", 1, RdataFactoryPtr(new RdataFactory<in::SRV>()));
229 add("NAPTR", 35, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::NAPTR>()));
230 add("DNAME", 39, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::DNAME>()));
231 add("OPT", 41, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::OPT>()));
232 add("DS", 43, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::DS>()));
233 add("SSHFP", 44, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::SSHFP>()));
234 add("RRSIG", 46, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::RRSIG>()));
235 add("NSEC", 47, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::NSEC>()));
236 add("DNSKEY", 48, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::DNSKEY>()));
237 add("DHCID", 49, "IN", 1, RdataFactoryPtr(new RdataFactory<in::DHCID>()));
238 add("NSEC3", 50, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::NSEC3>()));
239 add("NSEC3PARAM", 51, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::NSEC3PARAM>()));
240 add("TLSA", 52, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::TLSA>()));
241 add("SPF", 99, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::SPF>()));
242 add("CAA", 257, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::CAA>()));
243 add("DLV", 32769, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::DLV>()));
244 add("A", 1, "CH", 3, RdataFactoryPtr(new RdataFactory<ch::A>()));
245 add("A", 1, "HS", 4, RdataFactoryPtr(new RdataFactory<hs::A>()));
246 add("TSIG", 250, "ANY", 255, RdataFactoryPtr(new RdataFactory<any::TSIG>()));
266 add("NSEC3PARAM", 51, RdataFactoryPtr(new RdataFactory<generic::NSEC3PARAM>()));
270 add("DLV", 32769, RdataFactoryPtr(new RdataFactory<generic::DLV>()));
271 // Meta and non-implemented RR types
272 addType("IPSECKEY", 45);
273 addType("L32", 105);
274 addType("NID", 104);
275 addType("LP", 107);
276 addType("L64", 106);
277 addType("KX", 36);
278 addType("UNSPEC", 103);
279 addType("MAILA", 254);
280 addType("MAILB", 253);
281 addType("HIP", 55);
282 addType("LOC", 29);
283 addType("URI", 256);
284 addType("APL", 42);
285 addType("GPOS", 27);
286 addType("TKEY", 249);
287 addType("MG", 8);
288 addType("MR", 9);
289 addType("IXFR", 251);
290 addType("MB", 7);
291 addType("MF", 4);
292 addType("AXFR", 252);
293 addType("ANY", 255);
294 addType("MD", 3);
295 addType("CERT", 37);
296 addType("KEY", 25);
297 addType("SIG", 24);
298 addType("A6", 38);
299 addType("PX", 26);
300 addType("RT", 21);
301 addType("X25", 19);
302 addType("NSAP-PTR", 23);
303 addType("NSAP", 22);
304 addType("NXT", 30);
305 addType("NULL", 10);
306 addType("WKS", 11);
307 addType("ISDN", 20);
308 // Meta classes
309 addClass("NONE", 254);
310 // END_WELL_KNOWN_PARAMS
311 } catch (...) {
312 delete impl_;
313 throw;
314 }
315}
316
317RRParamRegistry::~RRParamRegistry() {
318 delete impl_;
319}
320
323 static RRParamRegistry registry;
324
325 return (registry);
326}
327
328void
329RRParamRegistry::add(const std::string& typecode_string, uint16_t typecode,
330 RdataFactoryPtr rdata_factory)
331{
332 bool type_added = false;
333 try {
334 type_added = addType(typecode_string, typecode);
335 impl_->genericrdata_factories.insert(pair<RRType, RdataFactoryPtr>(
336 RRType(typecode),
337 rdata_factory));
338 } catch (...) {
339 if (type_added) {
340 removeType(typecode);
341 }
342 throw;
343 }
344}
345
346void
347RRParamRegistry::add(const std::string& typecode_string, uint16_t typecode,
348 const std::string& classcode_string, uint16_t classcode,
349 RdataFactoryPtr rdata_factory)
350{
351 // Rollback logic on failure is complicated. If adding the new type or
352 // class fails, we should revert to the original state, cleaning up
353 // intermediate state. But we need to make sure that we don't remove
354 // existing data. addType()/addClass() will simply ignore an attempt to
355 // add the same data, so the cleanup should be performed only when we add
356 // something new but we fail in other part of the process.
357 bool type_added = false;
358 bool class_added = false;
359
360 try {
361 type_added = addType(typecode_string, typecode);
362 class_added = addClass(classcode_string, classcode);
363 impl_->rdata_factories.insert(pair<RRTypeClass, RdataFactoryPtr>(
364 RRTypeClass(RRType(typecode),
365 RRClass(classcode)),
366 rdata_factory));
367 } catch (...) {
368 if (type_added) {
369 removeType(typecode);
370 }
371 if (class_added) {
372 removeClass(classcode);
373 }
374 throw;
375 }
376}
377
378bool
380 const RRClass& rrclass)
381{
382 RdataFactoryMap::iterator found =
383 impl_->rdata_factories.find(RRTypeClass(rrtype, rrclass));
384 if (found != impl_->rdata_factories.end()) {
385 impl_->rdata_factories.erase(found);
386 return (true);
387 }
388
389 return (false);
390}
391
392bool
394 GenericRdataFactoryMap::iterator found =
395 impl_->genericrdata_factories.find(rrtype);
396 if (found != impl_->genericrdata_factories.end()) {
397 impl_->genericrdata_factories.erase(found);
398 return (true);
399 }
400
401 return (false);
402}
403
404namespace {
411bool CICharEqual(char c1, char c2) {
412 return (tolower(static_cast<unsigned char>(c1)) ==
413 tolower(static_cast<unsigned char>(c2)));
414}
415
416bool
417caseStringEqual(const string& s1, const string& s2, size_t n) {
418 assert(s1.size() >= n && s2.size() >= n);
419
420 return (mismatch(s1.begin(), s1.begin() + n, s2.begin(), CICharEqual).first
421 == s1.begin() + n);
422}
423
434template <typename PT, typename MC, typename MS, typename ET>
435inline bool
436addParam(const string& code_string, uint16_t code, MC& codemap, MS& stringmap)
437{
438 // Duplicate type check
439 typename MC::const_iterator found = codemap.find(code);
440 if (found != codemap.end()) {
441 if (found->second->code_string_ != code_string) {
442 isc_throw(ET, "Duplicate RR parameter registration");
443 }
444 return (false);
445 }
446
447 typedef boost::shared_ptr<PT> ParamPtr;
448 typedef pair<string, ParamPtr> StrParamPair;
449 typedef pair<uint16_t, ParamPtr> CodeParamPair;
450 ParamPtr param = ParamPtr(new PT(code_string, code));
451 try {
452 stringmap.insert(StrParamPair(code_string, param));
453 codemap.insert(CodeParamPair(code, param));
454 } catch (...) {
455 // Rollback to the previous state: not all of the erase operations will
456 // find the entry, but we don't care.
457 stringmap.erase(code_string);
458 codemap.erase(code);
459 throw;
460 }
461
462 return (true);
463}
464
465template <typename MC, typename MS>
466inline bool
467removeParam(uint16_t code, MC& codemap, MS& stringmap) {
468 typename MC::iterator found = codemap.find(code);
469
470 if (found != codemap.end()) {
471 size_t erased = stringmap.erase(found->second->code_string_);
472 // We must have a corresponding entry of the str2 map exists
473 assert(erased == 1);
474
475 codemap.erase(found);
476
477 return (true);
478 }
479
480 return (false);
481}
482
483template <typename PT, typename MS>
484inline bool
485textToCode(const string& code_str, MS& stringmap, uint16_t& ret_code) {
486 typename MS::const_iterator found;
487
488 found = stringmap.find(code_str);
489 if (found != stringmap.end()) {
490 ret_code = found->second->code_;
491 return (true);
492 }
493
494 size_t l = code_str.size();
495 if (l > PT::UNKNOWN_PREFIXLEN() &&
496 l <= PT::UNKNOWN_MAXLEN() &&
497 caseStringEqual(code_str, PT::UNKNOWN_PREFIX(),
498 PT::UNKNOWN_PREFIXLEN())) {
499 unsigned int code;
500 istringstream iss(code_str.substr(PT::UNKNOWN_PREFIXLEN(),
501 l - PT::UNKNOWN_PREFIXLEN()));
502 iss >> dec >> code;
503 if (iss.rdstate() == ios::eofbit && code <= PT::MAX_CODE) {
504 ret_code = code;
505 return (true);
506 }
507 }
508
509 return (false);
510}
511
512template <typename PT, typename MC>
513inline string
514codeToText(uint16_t code, MC& codemap) {
515 typename MC::const_iterator found;
516
517 found = codemap.find(code);
518 if (found != codemap.end()) {
519 return (found->second->code_string_);
520 }
521
522 ostringstream ss;
523 ss << code;
524 return (PT::UNKNOWN_PREFIX() + ss.str());
525}
526}
527
528bool
529RRParamRegistry::addType(const string& type_string, uint16_t code) {
530 return (addParam<RRTypeParam, CodeRRTypeMap, StrRRTypeMap, RRTypeExists>
531 (type_string, code, impl_->code2typemap, impl_->str2typemap));
532}
533
534bool
536 return (removeParam<CodeRRTypeMap, StrRRTypeMap>(code, impl_->code2typemap,
537 impl_->str2typemap));
538}
539
540bool
541RRParamRegistry::textToTypeCode(const string& type_string,
542 uint16_t& type_code) const
543{
544 return (textToCode<RRTypeParam, StrRRTypeMap>
545 (type_string, impl_->str2typemap, type_code));
546}
547
548string
550 return (codeToText<RRTypeParam, CodeRRTypeMap>(code, impl_->code2typemap));
551}
552
553bool
554RRParamRegistry::addClass(const string& class_string, uint16_t code) {
555 return (addParam<RRClassParam, CodeRRClassMap, StrRRClassMap, RRClassExists>
556 (class_string, code, impl_->code2classmap, impl_->str2classmap));
557}
558
559bool
561 return (removeParam<CodeRRClassMap, StrRRClassMap>(code,
562 impl_->code2classmap,
563 impl_->str2classmap));
564}
565
566bool
567RRParamRegistry::textToClassCode(const string& class_string,
568 uint16_t& class_code) const
569{
570 return (textToCode<RRClassParam, StrRRClassMap>
571 (class_string, impl_->str2classmap, class_code));
572}
573
574string
576 return (codeToText<RRClassParam, CodeRRClassMap>(code,
577 impl_->code2classmap));
578}
579
580namespace {
581inline const AbstractRdataFactory*
582findRdataFactory(RRParamRegistryImpl* reg_impl,
583 const RRType& rrtype, const RRClass& rrclass)
584{
585 RdataFactoryMap::const_iterator found;
586 found = reg_impl->rdata_factories.find(RRTypeClass(rrtype, rrclass));
587 if (found != reg_impl->rdata_factories.end()) {
588 return (found->second.get());
589 }
590
591 GenericRdataFactoryMap::const_iterator genfound =
592 reg_impl->genericrdata_factories.find(rrtype);
593 if (genfound != reg_impl->genericrdata_factories.end()) {
594 return (genfound->second.get());
595 }
596
597 return (NULL);
598}
599}
600
602RRParamRegistry::createRdata(const RRType& rrtype, const RRClass& rrclass,
603 const std::string& rdata_string)
604{
605 // If the text indicates that it's rdata of an "unknown" type (beginning
606 // with '\# n'), parse it that way. (TBD)
607
608 const AbstractRdataFactory* factory =
609 findRdataFactory(impl_, rrtype, rrclass);
610 if (factory != NULL) {
611 return (factory->create(rdata_string));
612 }
613
614 return (RdataPtr(new generic::Generic(rdata_string)));
615}
616
618RRParamRegistry::createRdata(const RRType& rrtype, const RRClass& rrclass,
619 InputBuffer& buffer, size_t rdata_len)
620{
621 const AbstractRdataFactory* factory =
622 findRdataFactory(impl_, rrtype, rrclass);
623 if (factory != NULL) {
624 return (factory->create(buffer, rdata_len));
625 }
626
627 return (RdataPtr(new generic::Generic(buffer, rdata_len)));
628}
629
631RRParamRegistry::createRdata(const RRType& rrtype, const RRClass& rrclass,
632 const Rdata& source)
633{
634 const AbstractRdataFactory* factory =
635 findRdataFactory(impl_, rrtype, rrclass);
636 if (factory != NULL) {
637 return (factory->create(source));
638 }
639
641 dynamic_cast<const generic::Generic&>(source))));
642}
643
645RRParamRegistry::createRdata(const RRType& rrtype, const RRClass& rrclass,
646 MasterLexer& lexer, const Name* name,
647 MasterLoader::Options options,
648 MasterLoaderCallbacks& callbacks)
649{
650 const AbstractRdataFactory* factory =
651 findRdataFactory(impl_, rrtype, rrclass);
652 if (factory != NULL) {
653 return (factory->create(lexer, name, options, callbacks));
654 }
655
656 return (RdataPtr(new generic::Generic(lexer, name, options, callbacks)));
657}
658}
659}
Tokenizer for parsing DNS master files.
Definition: master_lexer.h:301
Set of issue callbacks for a loader.
Options
Options how the parsing should work.
Definition: master_loader.h:39
The Name class encapsulates DNS names.
Definition: name.h:223
The RRClass class encapsulates DNS resource record classes.
Definition: rrclass.h:98
The RRParamRegistry class represents a registry of parameters to manipulate DNS resource records (RRs...
rdata::RdataPtr createRdata(const RRType &rrtype, const RRClass &rrclass, const std::string &rdata_string)
Create RDATA of a given pair of RR type and class from a string.
bool removeType(uint16_t type_code)
Remove mappings between RR type code and textual representation for a given type.
bool textToTypeCode(const std::string &type_string, uint16_t &type_code) const
Convert a textual representation of an RR type to the corresponding 16-bit integer code.
bool textToClassCode(const std::string &class_string, uint16_t &class_code) const
Convert a textual representation of an RR class to the corresponding 16-bit integer code.
std::string codeToClassText(uint16_t class_code) const
Convert class code into its textual representation.
std::string codeToTypeText(uint16_t type_code) const
Convert type code into its textual representation.
bool addClass(const std::string &class_string, uint16_t class_code)
Add mappings between RR class code and textual representation.
static RRParamRegistry & getRegistry()
Return the singleton instance of RRParamRegistry.
bool addType(const std::string &type_string, uint16_t type_code)
Add mappings between RR type code and textual representation.
bool removeRdataFactory(const RRType &rrtype, const RRClass &rrclass)
Remove registered RDATA factory for the given pair of RRType and RRClass.
void add(const std::string &type_string, uint16_t type_code, const std::string &class_string, uint16_t class_code, rdata::RdataFactoryPtr rdata_factory)
Add a set of parameters for a pair of RR type and class.
bool removeClass(uint16_t class_code)
Remove mappings between RR class code and textual representation for a given class.
The RRType class encapsulates DNS resource record types.
Definition: rrtype.h:106
virtual RdataPtr create(const string &rdata_str) const
Create RDATA from a string.
virtual RdataPtr create(InputBuffer &buffer, size_t rdata_len) const
Create RDATA from wire-format data.
virtual RdataPtr create(MasterLexer &lexer, const Name *origin, MasterLoader::Options options, MasterLoaderCallbacks &callbacks) const
Create RDATA using MasterLexer.
virtual RdataPtr create(const Rdata &source) const
Create RDATA from another Rdata object of the same type.
The AbstractRdataFactory class is an abstract base class to encapsulate a set of Rdata factory method...
virtual RdataPtr create(const std::string &rdata_str) const =0
Create RDATA from a string.
The Rdata class is an abstract base class that provides a set of common interfaces to manipulate conc...
Definition: rdata.h:123
The generic::Generic class represents generic "unknown" RDATA.
Definition: rdata.h:249
The InputBuffer class is a buffer abstraction for manipulating read-only data.
Definition: buffer.h:81
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
boost::shared_ptr< AbstractRdataFactory > RdataFactoryPtr
The RdataFactoryPtr type is a pointer-like type, pointing to an object of some concrete derived class...
boost::shared_ptr< Rdata > RdataPtr
The RdataPtr type is a pointer-like type, pointing to an object of some concrete derived class of Rda...
pair< RRType, RRClass > RRTypeClass
Note: the element ordering in the type/class pair is intentional.
map< RRTypeClass, RdataFactoryPtr > RdataFactoryMap
map< RRType, RdataFactoryPtr > GenericRdataFactoryMap
Definition: edns.h:19
Defines the logger used by the top-level component of kea-dhcp-ddns.
string code_string_
uint16_t code_
The RRParamRegistryImpl class is the actual implementation of RRParamRegistry.
CodeRRTypeMap code2typemap
Mappings from textual representations of RR types to integer codes.
StrRRClassMap str2classmap
Mappings from RR class codes to textual representations.
CodeRRClassMap code2classmap
Mappings from textual representations of RR classes to integer codes.
GenericRdataFactoryMap genericrdata_factories
StrRRTypeMap str2typemap
Mappings from RR type codes to textual representations.