RFC 1023

HEMS monitoring and control language

๐Ÿ“… published October 1987, from ietf

HEMS Monitoring and Control Language ๐Ÿ’–

RFC 1023 ๐ŸŽ€ from the Network Working Group, October 1987 ๐Ÿ“…

Written by G. Trewitt (Stanford) and C. Partridge (BBN/NNSC) โœ๏ธ

Status: experimental, as the Status of This Memo section explains ๐Ÿงช

Obsoletes or updates: nothing is listed in the header ๐Ÿคทโ€โ™€๏ธ

Okay so this RFC lays out a monitoring and control language that's general-purpose but still efficient ๐Ÿ’…. The data inside an entity is modeled as a hierarchy, and you name a specific item by giving its path from the root of the tree ๐ŸŒณ. Most items are read-only, but some can be "set" to perform control operations ๐Ÿ•น๏ธ. Both requests and responses are represented using the ISO ASN.1 data encoding rules ๐Ÿ”ฅ.


Status of This Memo ๐Ÿ“œ

The whole point of this RFC is to give a spec for monitoring and controlling network entities on the Internet ๐ŸŒ. It's an experimental spec meant for testing the ideas in here ๐Ÿงช. Nothing in this memo is intended as an Internet standard at this time ๐Ÿ™…โ€โ™€๏ธ. After enough experimentation and discussion, this RFC will get redrafted, perhaps as a standard โœจ. Distribution of this memo is unlimited ๐Ÿ“ฎ.

This language is a component of the High-Level Entity Monitoring System (HEMS), which is described in RFC-1021 and RFC-1022 ๐Ÿ“š. Readers might wanna check those out while reading this one. RFC-1024 has the detailed assignments of numbers and structures used in this system ๐Ÿ”ข. This memo assumes you already know the ISO data encoding standard, ASN.1 ๐Ÿง .


Overview and Scope ๐Ÿฆ‹

So the basic model here is that you send a query to a monitored entity, and the entity sends a response back ๐Ÿ“จ. "Query" is used in the database sense ๐Ÿ—„๏ธ. It might request info, modify stuff, or both ๐Ÿ’ซ. The examples are gateway-flavored, but this query-response mechanism can be applied to other entities besides just gateways ๐ŸŒ‰.

There's no interactive "conversation" like you'd get with SMTP [RFC-821] or FTP [RFC-959] ๐Ÿ™…โ€โ™€๏ธ. A query is a complete request that stands on its own and gets a complete response back ๐Ÿ’Œ.

The monitored entity doesn't have to store the whole query ๐Ÿง . It's totally possible for an implementation to process the query on the fly, producing portions of the response while the query is still being received โšก.

Other HEMS RFCs: RFC-1021 is the Overview, RFC-1022 covers the transport protocol and message encapsulation, and RFC-1024 has the precise data definitions ๐Ÿ“–. These issues aren't dealt with here ๐Ÿ™ˆ. It's assumed there's some mechanism to transport a sequence of octets to a query processor inside the monitored entity ๐Ÿšš, and some mechanism to return a sequence of octets to the entity making the query ๐Ÿ”.


Encoding of Queries and Responses ๐Ÿ’Œ

Both queries and responses are encoded using the representation defined in ISO Standard ASN.1 (Abstract Syntax Notation 1) ๐Ÿ“. ASN.1 represents data as sequences of <tag,length,contents> triples, encoded as a stream of octets ๐Ÿงฉ. The data tuples can be recursively nested to represent structured data like arrays or records ๐Ÿช†. For a full description of this notation, check out the ISO documents IS 8824 and IS 8825 ๐Ÿ“š. See the end of this memo for info about ordering these documents ๐Ÿ›’.


Notation Used in This Proposal ๐ŸŽ€

The notation in this memo is similar to ASN.1's, but less formal, smaller, and (hopefully) easier to read ๐Ÿฅฐ. The biggest difference is that, in this memo, we are not concerned with the length of the data items ๐Ÿ™…โ€โ™€๏ธ.

ASN.1 data items can be either a "simple type" like integer or octet string, or a "structured type," which is a collection of data items ๐Ÿงบ. The notation:

ID(value)

represents a simple data item whose tag is "ID" with the given value. A structured data item is represented as:

ID { ... contents ... }

where contents is a sequence of data items. Remember that the contents may include both simple and structured types, so the structure is fully recursive ๐ŸŒ€.

Sometimes you want to specify a type but give no value ๐Ÿซฅ. That happens when there's no meaningful value for a particular measured parameter, or when the entire contents of a structured type is being specified. In that case, the same notation is used, but with the value omitted:

ID()

or

ID{}

The representation of this is obvious. The data item has zero for the length and no contents ๐Ÿƒ.


Data Model ๐Ÿงš

Data in a monitored entity is modeled as a hierarchy ๐ŸŒณ. Implementations are not required to organize the data internally as a hierarchy, but they must provide this view of the data through the query language ๐Ÿซถ. A hierarchy offers useful structure for the following operations:

   Organization     A hierarchy allows related data to be grouped
                    together in a natural way.

   Naming           The name of a piece of data is just the path from
                    the root to the data of interest.

   Mapping onto ASN.1
                    ASN.1 can easily represent a hierarchy by using
                    "constructor" types as an envelope for an entire
                    subtree.

   Efficient Representation
                    Hierarchical structures are quite compact and can
                    be traversed very quickly.

Each node in the hierarchy must have names for its component parts ๐Ÿท๏ธ. We'd normally think of names as ASCII strings like "input errors," but the actual name is just an ASN.1 tag. Those tags are small integers (typically less than 100), so the monitored entity can easily map them onto its internal representation ๐Ÿ”ข.

We'll use the term "dictionary" to represent an internal node in the hierarchy ๐Ÿ“–. Here's a possible organization of the hierarchy in an entity that has several network interfaces and multiple processes. The exact organization of data in entities is specified in RFC-1024 ๐Ÿ’….

          system {
                  name                            -- host name
                  clock-msec                      -- msec since boot
                  interfaces                      -- # of interfaces
                  }
          interfaces {                    -- one per interface
                  interface { type, ip-addr, in-pkts, out-pkts, . . . }
                  interface { type, ip-addr, in-pkts, out-pkts, . . . }
                  interface { type, ip-addr, in-pkts, out-pkts, . . . }
                                  :
                  }
          processes {
                  process { name, stack, interrupts, . . . }
                  process { name, stack, interrupts, . . . }
                                  :
                  }
          route-table {
                  route-entry { dest, interface, nexthop, cost, . . . }
                  route-entry { dest, interface, nexthop, cost, . . . }
                                  :
                  }
          arp-table {
                  arp-entry { hard-addr, ip-addr, age }
                  arp-entry { hard-addr, ip-addr, age }
                                  :
                  }
          memory { }

The "name" of the clock in this entity would be:

system{ clock-msec }

and the name of a route-entry's IP address would be:

route-table{ route-entry{ ip-addr } }

Actually, this is the name of the IP addresses of ALL of the routing table entries ๐Ÿ˜ฑ. This ambiguity is a problem in any situation where there are several instances of an item being monitored ๐Ÿ™ƒ. If there was a meaningful index for such tabular data (e.g., "routing table entry #1"), there would be no problem. Unfortunately, there usually isn't such an index ๐Ÿ’€. The solution to this problem requires that the data be accessed on the basis of some of its content. More on this later ๐Ÿ‘€.

More than one piece of data can be named by a single ASN.1 object ๐ŸŽ. The entire collection of system information is named by:

system{ }

and the name of a routing table's IP address and cost would be:

route-table{ route-entry{ ip-addr, cost } }

Arrays ๐ŸŒˆ

There is one sub-type of a dictionary that is used as the basis for tables of objects with identical types. We call these dictionaries arrays ๐Ÿ’Ž. In the example above, the dictionaries for interfaces, processes, routing tables, and ARP tables are all arrays. In fact, we expect that most of the interesting data in an entity will be contained in arrays โœจ.

The primary difference between arrays and plain dictionaries is that arrays may contain only one type of item, while dictionaries, in general, will contain many different types of items ๐Ÿงƒ. Arrays are usually accessed associatively using special operators in the language ๐Ÿ”.

The fact that these objects are viewed externally as arrays does not mean they're represented in an implementation as linear lists of objects ๐Ÿ™…โ€โ™€๏ธ. Any collection of same-typed objects is viewed as an array, even though it might be represented as, for example, a hash table ๐Ÿ—‚๏ธ.


Representation of a Reply ๐Ÿ’œ

The data returned to the monitoring entity is a sequence of ASN.1 data items ๐Ÿง. Each of these corresponds to one of the top-level dictionaries maintained by the monitored entity. The tags for these data items will be in the "application-specific" class. So if an entity has the above structure for its data, the only top-level data items that will be returned will have tags corresponding to these groups ๐Ÿท๏ธ. If a query returned data from two of these, the representation might look like:

interfaces{ . . . }  route-table{ . . . }

which is just a stream of two ASN.1 objects (each of which may consist of many sub-objects) ๐Ÿงต.

Data not in the root dictionary will have tags from the context-specific class. Therefore, data must always be fully qualified ๐ŸŽฏ. For example, the name of the entity would always be returned encapsulated inside an ASN.1 object for "system." If it weren't, there would be no way to tell if the object returned was "name" inside the "system" dictionary or "dest" inside the "interfaces" dictionary (assuming in this case that "name" and "dest" were assigned the same ASN.1 tag) ๐Ÿ˜ต.

Having fully-qualified data simplifies decoding at the receiving end and allows the tags to be locally chosen. For example, definitions for tags dealing with ARP tables can't conflict with definitions for tags dealing with interfaces ๐Ÿงฉ. Therefore, the people doing the name assignments are less constrained ๐Ÿ™Œ. In addition, most of the identifiers will be fairly small integers ๐Ÿ”ข.

It will often be the case that requested data may not be available. That can happen because the request was badly formed (asked for data that couldn't exist), or because the particular data item wasn't defined in a particular situation (time since last error, when there hasn't been an error) ๐Ÿซ . In this situation, the returned data item will have the same tag as in the request, but will have zero-length data. Therefore, there can NEVER be an "undefined data" error ๐Ÿ’ฅ.

This allows completely generic queries to be composed without regard to whether the data is defined at all of the entities that will receive the request ๐ŸŒ. All of the available data will be returned, without generating errors that might otherwise terminate the processing of the query ๐Ÿฅณ.

REPRESENTATION OF A REQUEST โœจ

okay so a request to a monitored entity is ALSO just a sequence of ASN.1 data items ๐ŸŽ€ and each one fits into one of these categories, bestie:

  • Template ๐ŸŒธ these are objects with the same types as the objects that get returned by a request. the difference is that a template only describes the shape of the data, with no values in it. templates are how you pick out exactly which data you want back. the order you list things in a template doesn't decide the order of what comes back, and a template MAY be either simple or structured depending on what it's naming. the representations of the simple data items in a template all have a length of zero ๐Ÿ™ˆ
  • Tag ๐Ÿฆ‹ a tag is a special case of a template that's a simple (non-structured) type. it names exactly one node in the dictionary tree, so it's giving precision.
  • Opcodes ๐Ÿ’… these tell the query interpreter to actually DO something (we'll get into the details later). they're an application-specific type, and their value decides which operation runs. those values are defined in RFC-1024.
  • Data ๐Ÿงš these are the same objects used to represent info coming back from an entity. sometimes you also have to send data as part of a request. like if you're asking about the interface with IP address 10.0.0.51, that address gets sent in the same format in the request as it would show up in a reply. ate

Data, Tags, and Templates are usually in the context-specific class, except for items in the root dictionary and a few special cases, which are in the application-specific class. ๐Ÿซถ


QUERY LANGUAGE ๐Ÿ’…

okay so queries are formed in a flexible way using what we call a "language" ๐Ÿ’– but it is lowkey not a programming language. there are operations that work on data, but most of the other features you'd find in a programming language are missing. specifically:

  • programs are not stored in the query processor.
  • the only form of temporary storage is a stack.

in the current version:

  • there are no subroutines.
  • there are no control structures defined in the language.
  • there are no arithmetic or conditional operators.

these features could be added to the language if needed โœจ

the whole goal here is a language that's expressive enough to write useful queries with, but still super simple to execute and to implement. simplicity is the whole vibe ๐Ÿ‘‘

the stack is the main character here. it MAY contain templates (and therefore tags), data, or dictionaries (and therefore arrays) from the entity being monitored. at the start, it has exactly one item in it: the root dictionary ๐ŸŒˆ

here's how it goes. the query processor reads ASN.1 objects from the input stream. every object that isn't an opcode gets pushed onto the stack the second it's read. each opcode runs immediately, and it MAY remove things from the stack, and it MAY generate ASN.1 objects and send them to the output stream. portions of the response MAY be generated while the query is still being received, which is so iconic ๐Ÿ”ฅ

the list of opcodes below is provisional, so changes may need to be made to deal with additional needs ๐Ÿงš

in the descriptions, opcode names are in capital letters. each one is preceded by the arguments it pulls off the stack and followed by the results it leaves on the stack. for example:

OP          a b    OP    t

that means the OP operator takes <a> and <b> off of the stack and leaves <t> on the stack. a lot of the operators leave the first operand (<a> in this example) on the stack for future use, which is giving reuse energy ๐Ÿฆ‹

here are the operators defined in the query language ๐Ÿ’œ

GET ๐ŸŒธ

GET         dict template    GET    dict

emit an ASN.1 object with the same "shape" as the given template. any items in the template that aren't in <dictionary> (or its components) come out as objects with a length of zero. that's how requests for data that isn't available get handled, whether it's because the data isn't defined or because it doesn't apply in this situation ๐Ÿ’…

or ๐Ÿ“

or          dict    GET    dict

if there is no template, you get all of the items in the dictionary. that's the same as providing a template that lists every single item in the dictionary ๐Ÿซถ

BEGIN ๐Ÿงš

BEGIN       dict1 tag    BEGIN     dict1 dict

pushes the value for dict{ tag } onto the stack, which SHOULD be another dictionary. at the same time, it produces the beginning octets of an ASN.1 object corresponding to that dictionary. it's up to the implementation to choose between using the "indefinite length" representation or going back and filling the length in later ๐Ÿ’–

END ๐ŸŽ€

END         dict    END    --

pops the dictionary off of the stack and terminates the currently open ASN.1 object. it MUST be paired with a BEGIN โญ


Getting Items Based on Their Values

okay so there's one problem we kinda skipped over earlier ๐Ÿ™ˆ when you're dealing with array data, how do you specify one or more entries based on some value sitting inside those entries? picture a situation with several interfaces, organized like this:

       interfaces {
               interface { type, ip-addr, in-pkts, out-pkts, ...}
               interface { type, ip-addr, in-pkts, out-pkts, ...}
                               :
                               :
               }

if you only want info about one interface (maybe because there's an enormous amount of data about each one), you still need a way to name it. one option is to just number the interfaces and refer to the one you want like this:

       interfaces(3)

...which means the third one ๐Ÿ”ข

but this is probably not enough, since interface numbers may change over time, maybe from one reboot to the next. this method is totally not sufficient for arrays with lots of elements, like processes, routing tables, etc. honestly, large, changing arrays are probably the more common case in fact ๐Ÿ˜ญ

because indexing isn't really useful in this context, there's no general mechanism for indexing in the language ๐Ÿ’€

a better scheme is to select objects based on some value contained in them, like the IP address or the process name โœจ that's what the GET-MATCH operator is for. it gives you this functionality in a fairly general way ๐Ÿ‘‘

GET-MATCH ๐Ÿฆ‹

GET-MATCH   array value template    GET-MATCH    array

<array> SHOULD be an array (a dictionary containing only one type of item). the first tag in <value> and <template> MUST match this type. for each entry in <array>, match the <value> against the contents of that entry. if there is a match, emit the entry based upon <template>, just as in a GET operation ๐ŸŒˆ

if there are several leaf items in the value to be matched against, like this:

       route-entry{ interface(1), cost(3) }

then all of them MUST match an array entry for it to be emitted ๐Ÿ”ฅ

here's how you'd use this operator to get the input and output packet counts for the interface with ip-address 10.0.0.51 ๐ŸŽ€

       interfaces BEGIN                    -- get dictionary
       interface{ ip-addr(10.0.0.51) }     -- value to match
       interface{ in-pkts out-pkts }       -- data template to get
       GET-MATCH
       END                                 -- finished with dict

the exact meaning of a "match" depends on the characteristics of the entities being compared. in almost all cases it's a comparison for exact equality. BUT it is totally reasonable to define values that let matches do interesting things ๐Ÿงš for example, you could define three different flavors of ip-addr: one that has only the IP net number, one with the net+subnet, and one with the whole IP address. another option is to allow wildcards in IP addresses (e.g., if the "host" part of an IP address is all ones, then it would match against any IP address with the same net number) ๐Ÿ’œ

so, for all data items defined, the behavior of the match operation MUST be defined if it is not simple equality ๐Ÿซถ

implementations don't have to provide the ability to use all items in an object to match against. it's expected that some data structures that are great for efficient lookup on one item may be super inefficient for matching against others. (for instance, routing tables are designed for lookup with IP addresses. it may be very difficult to search the routing table by matching against costs ๐Ÿ™ˆ)

NOTE: it would be desirable to provide a general-purpose filtering capability, rather than just "equality" as provided by GET-MATCH. however, because of the potential complexity of such a facility, the lack of a widely-accepted representation for filter expressions, and time pressure, we are not defining this mechanism now ๐Ÿฅบ

however, if a generalized filtering mechanism is devised, the GET-MATCH operator will disappear ๐ŸŒธ

Data Attributes ๐Ÿ’–

okay bestie, ASN.1 data is self-describing as far as the structure goes ๐Ÿ’… but it has zero clue what the data actually means. you can look at the raw bytes and figure out that an item is of type [context 5] and 4 octets long ๐Ÿ™ˆ but that doesn't tell you if it's an integer, an IP address, or a 4-character string, or what it's an IP address of ๐Ÿ’€

most of the time the answers come from RFC-1024 โœจ, which defines all of the ASN.1 tags and their precise meaning. when extensions have been made, it MAY not be possible to get documentation on them ๐Ÿ˜ญ (See the section about extensions, page 15.)

the query language has a set of operators that are parallel to GET and GET-MATCH ๐Ÿฆ‹ and they return a set of attributes describing the data. this information SHOULD be enough to let a human understand what the data means and let a sophisticated application treat it appropriately. it's giving enough to let an application format stuff on a display and decide whether or not to subtract one sample from another ๐Ÿ”ฅ

some of the attributes are textual descriptions that help a human get the nature of the data and give it meaningful labels ๐ŸŽ€. extensive descriptions of standard data are OPTIONAL, since they're already defined in RFC-1024. complete descriptions of extensions MUST be available, even if they're documented in a user's manual. network firefighters ๐Ÿงฏ MAY not have the manual handy when the network is broken, so no excuses ๐Ÿ˜ญ

the format of the attributes is not as simple as the format of the data itself ๐Ÿงš. you can't use the data's tag, since that would just look exactly like the data itself. the format is:

          Attributes ::= [APPLICATION 2] IMPLICIT SEQUENCE {
                  tagASN1       [0] IMPLICIT INTEGER,
                  valueFormat   [1] IMPLICIT INTEGER,
                  longDesc      [2] IMPLICIT IA5String OPTIONAL,
                  shortDesc     [3] IMPLICIT IA5String OPTIONAL,
                  unitsDesc     [4] IMPLICIT IA5String OPTIONAL,
                  precision     [5] IMPLICIT INTEGER OPTIONAL,
                  properties    [6] IMPLICIT BITSTRING OPTIONAL,
          }

for example, the attributes for

       system{ name, clock-msec }

might be:

       system{
               Attributes{
                       tagASN1(name), valueFormat(IA5String),
                       longDesc("The name of the host"),
                       shortDesc("hostname")
               },
               Attributes{
                       tagASN1(clock-msec), valueFormat(Integer),
                       longDesc("milliseconds since boot"),
                       shortDesc("uptime"), unitsDesc("ms")
                       precision(4294967296),
                       properties(1)

               }

note that in this example <name> and <clock-msec> are integer values for the ASN.1 tags for the two data items ๐Ÿ“. a complete definition of the contents of the Attributes type is in RFC-1024.

note that there will be exactly as many Attributes items in the result as there are objects in the template โœจ. Attributes objects for items which do not exist in the entity will have a valueFormat of NULL, and none of the OPTIONAL elements will appear ๐Ÿซถ.

GET-ATTRIBUTES ๐Ÿ’…

   GET-ATTRIBUTES
               dict template    GET-ATTRIBUTES    dict

emit ASN.1 Attributes objects for the objects named in <template>. any items in the template that are not in <dictionary> (or its components) elicit an Attributes object with no.

   or          dict    GET-ATTRIBUTES    dict

if there is no template, emit Attribute objects for all of the items in the dictionary. this is equivalent to providing a template that lists all of the items in the dictionary, so you can get a complete list of a dictionary's contents ๐Ÿ“‹

GET-ATTRIBUTES-MATCH ๐Ÿ‘‘

   GET-ATTRIBUTES-MATCH
               dict value template GET-ATTRIBUTES-MATCH dict <array>

<array> SHOULD be an array (a dictionary containing only one type of item). the first tag in <value> and <template> MUST match this type. for each entry in <array>, match the <value> against the contents of the entry. if there is a match, emit the attributes based upon <template>, just as in a GET-ATTRIBUTES operation.

GET-ATTRIBUTES-MATCH is necessary because there will be situations where the contents of the elements of an array MAY differ, even though the array elements themselves are the same type. the most obvious example is when several network interfaces exist and they're different types, with different data collected for each type ๐ŸŒˆ

NOTE: The GET-ATTRIBUTES-MATCH operator will disappear if a generalized filtering mechanism is devised.

ADDITIONAL NOTE: a much cleaner method would be to store the attributes as sub-components of the data item of interest ๐Ÿง  for example, requesting:

          system{ clock-msec() }  GET

would normally just get the value of the data. asking for an additional layer down the tree would now get its attributes:

          system{ clock-msec{ shortDesc, unitsDesc }  GET

would get the named attributes. (the attributes would be named with application-specific tags.) unfortunately, ASN.1 doesn't provide an obvious notation to describe this type of organization, so we're stuck with the GET-ATTRIBUTES operator ๐Ÿ˜ค. however, if this cleaner organization becomes possible, this decision MAY be re-thought ๐Ÿฆ‹


Examining Memory ๐Ÿงธ

even with the ability to symbolically access all of this information in an entity, there will still be times when you have to get to very low levels and examine memory, like in remote debugging operations ๐Ÿ”. the building blocks we've covered so far can easily be extended to let memory be examined.

memory is modeled as an array, with an ASN.1 representation of OctetString. because there's a huge variety of addressing architectures out there, the conversion between the OctetString and "memory" is very machine-dependent ๐Ÿ’€. the only simple case is for byte-addressed machines with 8 bits per byte.

each address space in an entity is represented by one dictionary. in a one-address-space situation, this dictionary will be at the top level. if each process has its own address space, then one "memory" dictionary MAY exist for each process ๐Ÿงš

the GET-RANGE operator is provided for the primary purpose of retrieving the contents of memory, but it can be used on any array. it's only useful in these other contexts if the array index is meaningful ๐ŸŽ€

   GET-RANGE   array start length    GET-RANGE    dict

get <length> elements of <array> starting at <start>. <start> and <length> are both ASN.1 INTEGER type ๐Ÿ”ข

the returned data MAY not be <length> octets long, since it MAY take more than one octet to represent one memory location ๐Ÿ™ˆ

memory is special in that it will not automatically be returned as part of a request for an entire dictionary. for example, if memory is part of the "system" dictionary, then requesting:

system{}

will emit the entire contents of the system dictionary, but not the memory item ๐Ÿซฃ

NOTE: The GET-RANGE operator MAY disappear if a generalized filtering mechanism is devised.

Controlling Things ๐ŸŽ›๏ธ

all of the operators defined so far only let data in an entity be retrieved. by replacing the "template" arguments used in the GET operators with values, you can change data in the entity ๐Ÿ’–

there are many control operations that don't correspond to simply changing the value of a piece of data, like bringing an interface "down" or "up" ๐Ÿ”Œ. in these cases, a special data item associated with the component being controlled (e.g., each interface) would be defined. control operations then consist of "setting" this item to an appropriate command code ๐ŸŽฏ

   SET         dict value    SET    dict

set the value(s) of data in the entity to the value(s) given in <value>.

   SET-MATCH   array mvalue svalue    SET-MATCH    dict

<array> SHOULD be an array (a dictionary containing only one type of item). the first tag in <mvalue> and <svalue> MUST match this type. for each entry in <array>, match the <mvalue> against the contents of the entry. if there is a match, set value(s) in the entity to the value(s) in <svalue>, just as in SET.

   CREATE      array value    SET    dict

insert a new entry into <array>. depending upon the context, there MAY be severe restrictions about what constitutes a valid <value> ๐Ÿšง

   DELETE      array value    SET    dict

delete the entry(s) in <array> that have values that match <value> ๐Ÿ—‘๏ธ

if there are several leaf items in the matched value, as in

          route-entry{ interface(1), cost(3) }

all of them MUST match an array entry for any values to be changed ๐Ÿซถ

here is an example of how this operator would be used to shut down the interface with ip-address 10.0.0.51, changing its status to "down" ๐Ÿ“‰

          interfaces BEGIN                    -- get dictionary
          interface{ ip-addr(10.0.0.51) }     -- value to match
          interface{ status(down) }           -- value to set
          SET-MATCH
          END                                 -- finished with dict

delete the routing table entry for 36.0.0.0 ๐Ÿงน

          route-table BEGIN                   -- get dictionary
          route-entry{ ip-addr(36.0.0.0) }    -- value to match
          DELETE
          END                                 -- finished with dict

note that this BEGIN/END pair ends up sending an empty ASN.1 item. we don't regard this as a problem, as it is likely that there will be some get operations executed in the same context. in addition, the "open" ASN.1 item provides the correct context for reporting errors. (See page 14.) ๐Ÿ™Œ

NOTE: The SET-MATCH operator will disappear and the DELETE operator will change if a generalized filtering mechanism is devised.

Atomic Operations โš›๏ธ

atomic operations can be provided if you want them, by allowing the stack to contain a fragment of a query. a new operation would take a query fragment, verify that it's executable, and execute it, atomically ๐Ÿงฉ

this is mentioned as a possibility, but it MAY be difficult to implement. more study is needed ๐Ÿ“š

ERRORS ๐Ÿšจ

okay bestie, so if some info gets requested but isn't available (maybe it doesn't apply to this implementation, maybe nobody collects it, whatever) it can always come back as "no-value" by giving the ASN.1 length as 0. no drama, just an empty slot ๐Ÿซถ

when something else goes wrong, like having improper arguments on the top of the stack or trying to run BEGIN when the tag doesn't point to a dictionary, an ERROR object gets emitted. โœจ the contents of that object spell out exactly what went wrong, and RFC-1024 is where you'll find the details ๐Ÿ’…

now here's the iconic part ๐Ÿ‘‘ there might be several unterminated ASN.1 objects in progress when the error hits, and every single one has to get terminated. each unterminated object gets closed with a copy of the ERROR object. how that looks depends on the length encoding in use: you fill in the value for the length (definite length form) or you emit two zero octets (indefinite length form). after all the objects are closed, one final copy of the ERROR object gets emitted. this structure means the error gets noticed at every level of interpretation on the receiving end. no level gets to be like "huh, what error?" ๐Ÿ’€

if there was an error before any ASN.1 objects were generated, the result is just:

error(details)

and if a couple of ASN.1 objects were left unterminated, it might look like this:

interfaces{
  interface { name(...) type(...) error(details) }
  error(details)
  }
error{details}

EXTENDING THE SET OF VALUES ๐Ÿงš

there are two ways to extend the set of values the query language understands. ๐ŸŽ€

the first is to register your data and its meaning and get an ASN.1 tag assigned for it. this is the preferred method, because then that data specification is available for everyone to use. it's giving community energy ๐ŸŒˆ

the second method is to use the VendorSpecific application type to "wrap" the vendor-specific data. whenever an implementation defines data that isn't in RFC-1024, the "VendorSpecific" tag should be used to label a dictionary holding that vendor-specific data. say a vendor had some data about interfaces that was too strange to get standard numbers assigned for. they could represent it like this instead:

interfaces {
  interface {
    in-pkts, out-pkts, ...
    VendorSpecific { ephemeris, declination }
    }
  }

in this case, ephemeris and declination are two context-dependent tags the vendor assigned for its non-standard data. ๐Ÿฆ‹

if the vendor-specific method is chosen, the private data MUST have descriptions available through the GET-ATTRIBUTES and GET-ATTRIBUTESMATCH operators. ๐Ÿšจ this is non-negotiable, bestie. even with that descriptive ability, the preferred method is still to get standard numbers assigned if possible ๐Ÿซถ


IMPLEMENTATION ๐Ÿ› ๏ธ

ngl, it's not really the vibe of RFCs to define an implementation, but the authors feel some suggestions will help early implementors of the query language. this list isn't meant to be complete. it's just hints about how the authors imagine the query processor could be implemented efficiently โœจ

  • the stack is an abstraction. it should be implemented with pointers, not by copying dictionaries, etc. ๐Ÿฅž
  • an object-oriented approach should make the initial implementation fairly easy. changes to the "shape" of the data items (which will certainly happen early on) will also be easier to make. ๐ŸŒธ
  • only a few "messages" need to be understood by objects. ๐Ÿ’…
  • most interesting objects are dictionaries, and each one can be implemented using pointers to the data and procedure "hooks" that perform specific operations such as GET, MATCH, SET, etc. ๐Ÿงš
  • the hardest part is actually extracting data from existing TCP/IP implementations that weren't designed with detailed monitoring in mind. this should be less of a problem if a system is designed with easy monitoring as a goal. ๐Ÿ˜ญ

OBTAINING A COPY OF THE ASN.1 SPECIFICATION ๐Ÿ“š

copies of ISO Standard ASN.1 (Abstract Syntax Notation 1) are available from the following source. it comes in two parts, and you need both ๐ŸŽ€

IS 8824 -- Specification (meaning, notation)
IS 8825 -- Encoding Rules (representation)

they're available from:

Omnicom Inc.
115 Park St, S.E.          (new address as of March, 1987)
Vienna, VA  22180
(703) 281-1135
๐Ÿ“ฆ where this page comes from