Internet-Draft S-Expressions June 2023
Rivest & Eastlake Expires 27 December 2023 [Page]
Workgroup:
Network Working Group
Internet-Draft:
draft-rivest-sexp-01
Published:
Intended Status:
Informational
Expires:
Authors:
R. Rivest
MIT CSAIL
D. Eastlake
Futurewei Technologies

S-Expressions

Abstract

This memo describes a data structure called "S-expressions" that are suitable for representing arbitrary, complex data structures. We make precise the encodings of S-expressions: we give a "canonical form" for S-expressions, described two "transport" representations, and also describe an "advanced" format for display to people.

Status of This Memo

This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.

Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.

Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."

This Internet-Draft will expire on 27 December 2023.

Table of Contents

1. Introduction

S-expressions are data structures for representing complex data. They are either byte-strings ("octet-strings") or lists of simpler S-expressions. Here is a sample S-expression:

    (snicker "abc" (#03# |YWJj|))

It is a list of length three:

This note gives a specific proposal for constructing and utilizing S-expressions. The proposal is independent of any particular application.

Here are the design goals for S-expressions:
generality:
S-expressions should be good at representing arbitrary data.
readability:
it should be easy for someone to examine and understand the structure of an S-expression.
economy:
S-expressions should represent data compactly.
tranportability:
S-expressions should be easy to transport over communication media (such as email) that are known to be less than perfect.
flexibility:
S-expressions should make it relatively simple to modify and extend data structures.
canonicalization:
it should be easy to produce a unique "canonical" form of an S-expression, for digital signature purposes.
efficiency:
S-expressions should admit in-memory representations that allow efficient processing.

The Sections of this document cover material as follows:

1.1. Historical Notes

The S-expression technology described here was originally developed for "SDSI" (the Simple Distributed Security Infrastructure by Lampson and Rivest [SDSI]) in 1996, although the origins clearly date back to McCarthy's [LISP] programming language. It was further refined and improved during the merger of SDSI and SPKI [SPKI] [RFC2692] [RFC2693] during the first half of 1997. S-expressions are similar to, but more readable and flexible than, Bernstein's "net-strings" [BERN].

Although made publicly available as a file named draft-rivest-sexp-00.txt on 4 May 1997, this document was not actually submitted to the IETF as that time. Version -00 of this Internet Draft was a modernized version of that file. Version -01 has some further polishing and corrections.

1.2. Conventions Used in This Document

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.

2. S-expressions -- informal introduction

Informally, an S-expression is either:

An octet-string is a finite sequence of eight-bit octets. There may be many different but equivalent ways of representing an octet-string

    abc         -- as a token
    "abc"       -- as a quoted string
    #616263#    -- as a hexadecimal string
    3:abc       -- as a length-prefixed "verbatim" encoding
    {MzphYmM=}  -- as a base-64 encoding of the verbatim
                     encoding (that is, an encoding of "3:abc")
    |YWJj|      -- as a base-64 encoding of the octet-string
                     "abc"

The above encodings are all equivalent; they all denote the same octet string.

We will give details of these encodings later on, and also describe how to give a "display type" to a simple string.

A list is a finite sequence of zero or more simpler S-expressions. A list is represented by using parentheses to surround the sequence of encodings of its elements, as in:

    (abc (de #6667#) "ghi jkl")

As we see, there is variability possible in the encoding of an S-expression. In some cases, it is desirable to standardize or restrict the encodings; in other cases it is desirable to have no restrictions. The following are the target cases we aim to handle:

These need not be different; in this proposal the canonical encoding is the same as the transport encoding, for example. In this note we propose (related) encoding techniques for each of these uses.

3. Character set

We will be describing encodings of S-expressions. Except when giving "verbatim" encodings, the character set used is limited to the following characters in US-ASCII:

Alphabetic:
    A B ... Z a b ... z
numeric:
    0 1 ... 9
whitespace:
    space, horizontal tab, vertical tab, form-feed
    carriage-return, line-feed
The following graphics characters, which we call "pseudo-alphabetic":
    - hyphen or minus
    . period
    / slash
    _ underscore
    : colon
    * asterisk
    + plus
    = equal
The following graphics characters, which are "reserved punctuation":
    ( left parenthesis
    ) right parenthesis
    [ left bracket
    ] right bracket
    { left brace
    } right brace
    | vertical bar
    # number sign
    " double quote
    & ampersand
    \ backslash
The following characters are unused and unavailable, except in "verbatim" and "quoted string" encodings:
    ! exclamation point
    % percent
    ^ circumflex
    ~ tilde
    ; semicolon
    ' apostrophe
    , comma
    < less than
    > greater than
    ? question mark

4. Octet string representations

This section describes in detail the ways in which an octet-string may be represented.

We recall that an octet-string is any finite sequence of octets, and that the octet-string may have length zero.

4.1. Verbatim representation

A verbatim encoding of an octet string consists of three parts:

There are no blanks or whitespace separating the parts. No "escape sequences" are interpreted in the octet string. This encoding is also called a "binary" or "raw" encoding.

Here are some sample verbatim encodings:

    3:abc
    7:subject
    4:::::
    12:hello world!
    10:abcdefghij
    0:

4.2. Quoted-string representation

The quoted-string representation of an octet-string consists of:

The specified length is the length of the resulting string after any escape sequences have been handled. The string does not have any "terminating NULL" that C includes, and the length does not count such a character.

The length is optional.

The escape conventions within the quoted string are as follows (these follow the "C" programming language conventions, with an extension for ignoring line terminators of just LF or CRLF and more restrictive octal and hexadecimal value formats):

    \a     -- audible alert (bell)
    \b     -- backspace
    \t     -- horizontal tab
    \v     -- vertical tab
    \n     -- new-line
    \f     -- form-feed
    \r     -- carriage-return
    \"     -- double-quote
    \'     -- single-quote
    \?     -- question mark
    \\     -- back-slash
    \ooo   -- character with octal value ooo (all three
              digits MUST be present)
    \xhh   -- character with hexadecimal value hh (both
              digits MUST be present)
    \<carriage-return>   -- causes carriage-return
              to be ignored.
    \<line-feed>         -- causes linefeed to be
              ignored.
    \<carriage-return><line-feed>   -- causes
              CRLF to be ignored.
    \<line-feed><carriage-return>   -- causes
              LFCR to be ignored.

Here are some examples of quoted-string encodings:

    "subject"
    "hi there"
    7"subject"
    3"\n\n\n"
    "This has\n two lines."
    "This has \
     one."
    ""

4.3. Token representation

An octet string that meets the following conditions may be given directly as a "token".

        -   .   /   _   :  *  +  =

(Note: upper and lower case are not equivalent.)

(Note: A token may begin with punctuation, including ":").

Here are some examples of token representations:

    subject
    not-before
    class-of-1997
    //microsoft.com/names/smith
    *

4.4. Hexadecimal representation

An octet-string may be represented with a hexadecimal encoding consisting of:

There may be whitespace inserted in the midst of the hexadecimal encoding arbitrarily; it is ignored. It is an error to have characters other than whitespace and hexadecimal digits.

Here are some examples of hexadecimal encodings:

    #616263#    -- represents "abc"
    3#616263#   -- also represents "abc"
    # 616
      263 #     -- also represents "abc"

4.5. Base-64 representation

An octet-string may be represented in a base-64 coding [RFC4648] consisting of:

The base-64 encoding uses only the characters

    A-Z  a-z  0-9  +  /  =

It produces four characters of output for each three octets of input. If the input has one or two left-over octets of input, it produces an output block of length four ending in two or one equals signs, respectively. Output routines compliant with this standard MUST output the equals signs as specified. Input routines MAY accept inputs where the equals signs are dropped.

There may be whitespace inserted in the midst of the base-64 encoding arbitrarily; it is ignored. It is an error to have characters other than whitespace and base-64 characters.

Here are some examples of base-64 encodings:

    |YWJj|       -- represents "abc"
    | Y W
      J j |      -- also represents "abc"
    3|YWJj|      -- also represents "abc"
    |YWJjZA==|   -- represents "abcd"
    |YWJjZA|     -- also represents "abcd"

4.6. Display hint

Any octet string may be preceded by a single "display hint".

The purposes of the display hint is to provide information on how to display the octet string to a user. It has no other function. Many of the MIME types work here.

A display-hint is an octet string surrounded by square brackets. There may be whitespace separating the octet string from the surrounding brackets. Any of the legal formats may be used for the octet string.

Here are some examples of display-hints:

    [image/gif]
    [URI]
    [charset=unicode-1-1]
    [text/richtext]
    [application/postscript]
    [audio/basic]
    ["http://abc.com/display-types/funky.html"]

In applications an octet-string that is untyped may be considered to have a pre-specified "default" mime type. The mime type

    "text/plain; charset=iso-8859-1"

is the standard default.

4.7. Equality of octet-strings

Two octet strings are considered to be "equal" if and only if they have the same display hint and the same data octet strings.

Note that octet-strings are "case-sensitive"; the octet-string "abc" is not equal to the octet-string "ABC".

An untyped octet-string can be compared to another octet-string (typed or not) by considering it as a typed octet-string with the default mime-type.

5. Lists

Just as with octet-strings, there are several ways to represent an S-expression. Whitespace may be used to separate list elements, but they are only required to separate two octet strings when otherwise the two octet strings might be interpreted as one, as when one token follows another. Also, whitespace may follow the initial left parenthesis, or precede the final right parenthesis.

Here are some examples of encodings of lists:

    (a b c)

    ( a ( b c ) ( ( d e ) ( e f ) )  )

    (11:certificate(6:issuer3:bob)(7:subject5:alice))

    ({ODpFeGFtcGxlIQ==} "1997" murphy 3:XC+)

6. Representation types

There are three "types" of representations:

The first two MUST be supported by any implementation; the last is OPTIONAL.

6.1. Canonical representation

This canonical representation is used for digital signature purposes, transmission, etc. It is uniquely defined for each S-expression. It is not particularly readable, but that is not the point. It is intended to be very easy to parse, to be reasonably economical, and to be unique for any S-expression.

The "canonical" form of an S-expression represents each octet-string in verbatim mode, and represents each list with no blanks separating elements from each other or from the surrounding parentheses.

Here are some examples of canonical representations of S-expressions:

    (6:issuer3:bob)

    (4:icon[12:image/bitmap]9:xxxxxxxxx)

    (7:subject(3:ref5:alice6:mother))

6.2. Basic transport representation

There are two forms of the "basic transport" representation:

The transport mechanism is intended to provide a universal means of representing S-expressions for transport from one machine to another.

Here are some examples of an S-expression represented in basic transport mode:

    (1:a1:b1:c)

    {KDE6YTE6YjE6YykK}

The second example above is the same S-expression as the first encoded in base-64.

There is a difference between the brace notation for base-64 used here and the || notation for base-64'd octet-strings described above. Here the base-64 contents are converted to octets, and then re-scanned as if they were given originally as octets. With the || notation, the contents are just turned into an octet-string.

6.3. Advanced transport representation

The "advanced transport" representation is intended to provide more flexible and readable notations for documentation, design, debugging, and (in some cases) user interface.

The advanced transport representation allows all of the representation forms described above in Section 4, include quoted strings, base-64 and hexadecimal representation of strings, tokens, representations of strings with omitted lengths, and so on.

7. BNF for syntax

We give separate BNF's for canonical and advanced forms of S-expressions. We use the following notation:

    <x>*          means 0 or more occurrences of <x>
    <x>+          means 1 or more occurrences of <x>
    <x>?          means 0 or 1 occurrences of <x>
    parentheses   are used for grouping, as in (<x> | <y>)*

For canonical transport:

<sexpr>         :: <string> | <list>
<string>        :: <display>? <simple-string>
<simple-string> :: <raw>
<display>       :: "[" <simple-string> "]"
<raw>           :: <decimal> ":" <bytes>
<decimal>       :: "0" | ("1"..."9")("0"..."9")*
<bytes>         -- any string of bytes, of the indicated length
<list>          :: "(" <sexp>* ")"

For basic transport:

<sexpr>         :: <canonical> | <base-64>
<canonical>     :: <string> | <list>
<string>        :: <display>? <simple-string>
<simple-string> :: <raw>
<display>       :: "[" <simple-string> "]"
<raw>           :: <decimal> ":" <bytes>
<decimal>       :: "0" | ("1"..."9")("0"..."9")*
<bytes>         -- any string of bytes, of the indicated length
<list>          :: "(" <canonical>* ")"
<base-64>       :: "{" <base-64-char>* "}"
<base-64-char>  :: <alpha> | <decimal-digit> |
                       "+" | "/" | "=" ;
<alpha>         :: <upper-case> | <lower-case>
<lower-case>    :: "a" | ... | "z" ;
<upper-case>    :: "A" | ... | "Z" ;

For advanced transport:

<sexpr>         :: <string> | <list>
<string>        :: <display>? <simple-string>
<simple-string> :: <raw> | <token> | <base-64> |
                       <hexadecimal> | <quoted-string>
<display>       :: "[" <whitespce> <simple-string>
                       <whitespace> "]"
<raw>           :: <decimal> ":" <bytes>
<decimal>       :: "0" | ("1"..."9")("0"..."9")*
<bytes>         -- any string of bytes, of the indicated length
<token>         :: (<alpha> | <simple-punct>) (<alpha> |
                       <decimal-digit> | <simple-punct> )*
<base-64>       :: <decimal>? "|"( <base-64-char> |
                       <whitespace> )* "|"
<hexadecimal>   :: "#" ( <hex-digit> | <white-space> )* "#"
<quoted-string> :: <decimal>? <quoted-string-body>
<quoted-string-body> :: "\"" <bytes> "\""
<list>          :: "(" <whitespace> <sexp>* <whitespace> ")"
<whitespace>    :: <whitespace-char>*
<alpha>         :: <upper-case> | <lower-case>
<lower-case>    :: "a" | ... | "z" ;
<upper-case>    :: "A" | ... | "Z" ;
<decimal-digit> :: "0" | ... | "9" ;
<hex-digit>     :: <decimal-digit> |
                       "A" | ... | "F" | "a" | ... | "f"
<simple-punc>   :: "-" | "." | "/" | "_" |
                       ":" | "*" | "+" | "="
<whitespace-char> :: " " | "\t" | "\r" | "\n" | "\v" | "\f"
<base-64-char>  :: <alpha> | <decimal-digit> |
                       "+" | "/" | "="

8. In-memory representations

For processing, the S-expression would typically be parsed and represented in memory in a way that is more amenable to efficient processing. We suggest two alternatives:

We only sketch these here, as they are only suggestive. The code referenced below illustrates these styles in more detail.

8.1. List-structure memory representation

Here there are separate records for simple-strings, strings, and lists. An S-expression of the form ("abc" "de") would require two records for the simple strings, two for the strings, and two for the list elements. This is a fairly conventional representation, and details are omitted here.

8.2. Array-layout memory representation

Here each S-expression is represented as a contiguous array of bytes. The first byte codes the "type" of the S-expression:

    01   octet-string

    02   octet-string with display-hint

    03   beginning of list (and 00 is used for "end of list")

Each of the three types is immediately followed by a k-byte integer indicating the size (in bytes) of the following representation. Here k is an integer that depends on the implementation, it might be anywhere from 2 to 8, but would be fixed for a given implementation; it determines the size of the objects that can be handled. The transport and canonical representations are independent of the choice of k made by the implementation.

Although the length of lists are not given in the usual S-expression notations, it is easy to fill them in when parsing; when you reach a right-parenthesis you know how long the list representation was, and where to go back to fill in the missing length.

8.2.1. Octet string

This is represented as follows:

    01 <length> <octet-string>

For example (here k = 2)

    01 0003 a b c

8.2.2. Octet-string with display-hint

This is represented as follows:

    02 <length>
      01 <length> <octet-string>    /* for display-type */
      01 <length> <octet-string>    /* for octet-string */

For example, the S-expression

    [gif] #61626364#

would be represented as (with k = 2)

    02 000d
      01 0003  g  i  f
      01 0004 61 62 63 64

8.2.3. List

This is represented as

    03 <length> <item1> <item2> <item3> ... <itemn> 00

For example, the list (abc [d]ef (g)) is represented in memory as (with k=2)

    03 001b
      01 0003 a b c
      02 0009
        01 0001 d
        01 0002 e f
      03 0005
        01 0001 g
      00
    00

9. Code

At this time there is code available that is intended to read and parse some or all of the various S-expression formats specified here. In particular, see the following likely incomplete list:

10. Utilization of S-expressions

S-expressions are used in [SPKI] specified in [RFC2693].

This note has described S-expressions in general form. Application writers may wish to restrict their use of S-expressions in various ways. Here are some possible restrictions that might be considered:

11. IANA Considerations

This document requires no IANA actions.

12. Security Considerations

As a pure data representation format, there are few security considerations to S-expressions. A canonical form is required for the reliable verification of digital signatures and this is provided in Section 6.1.

13. Normative References

[RFC2119]
Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/info/rfc2119>.
[RFC4648]
Josefsson, S., "The Base16, Base32, and Base64 Data Encodings", RFC 4648, DOI 10.17487/RFC4648, , <https://www.rfc-editor.org/info/rfc4648>.
[RFC8174]
Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, , <https://www.rfc-editor.org/info/rfc8174>.

14. Informative References

[BERN]
Bernstein, D., "Netstrings", Work in progress, , <https://www.ietf.org/archive/id/draft-bernstein-netstrings-02.txt>.
[Libgcrypt]
GnuPG, "The Libgcrypt Library", Libgcrypt version 1.10.2, , <https://www.gnupg.org/documentation/manuals/gcrypt/>.
[LISP]
Levin, M. and J. McCarthy, "LISP 1.5 Programmer's Manual", ISBN-13 978-0-262-12011-0, ISBN-10 0262130114, .
[RFC2692]
Ellison, C., "SPKI Requirements", RFC 2692, DOI 10.17487/RFC2692, , <https://www.rfc-editor.org/info/rfc2692>.
[RFC2693]
Ellison, C., Frantz, B., Lampson, B., Rivest, R., Thomas, B., and T. Ylonen, "SPKI Certificate Theory", RFC 2693, DOI 10.17487/RFC2693, , <https://www.rfc-editor.org/info/rfc2693>.
[SDSI]
Rivest, R. and B. Lampson, "A Simple Distributed Security Architecture", working document, SDSI version 1.1, , <https://people.csail.mit.edu/rivest/pubs/RL96.ver-1.1.html>.
[SexpCode]
Malkiewicz, J., "SEXP---(S-expressions)", , <https://github.com/jpmalkiewicz/rivest-sexp>.
[SPKI]
"SPKI--A Simple Public Key Infrastructure", <http://www.clark.net/pub/cme/html/spki.html>.

Appendix A. Change History

RFC Editor Note: Please delete this section before publication.

A.1. -00 Changes

This sub-section summarizes significant changes between the original 1997 -00 version of this document and the 2023 version submited to the IETF.

  1. Convert to XML v3.
  2. Update Ron Rivest author information and, with his permission, add Donald Eastlake as an author.
  3. Add minimal "IANA Considerations" and "Security Considerations" sections.
  4. Since implementation requirements terminology is used, add the usual paragraph about it as a sub-section of Section 1 and add references to [RFC2119] and [RFC8174].
  5. Divide references into Normative and Informational and update base-64 reference to be to [RFC4648].
  6. Add a couple of sentence to the "Historical note" section about the history of -00 versions of the draft.

A.2. Changes from -00 to -01

  1. Fix glitches and errors in the BNF.
  2. Add Acknowledgements section to list Marc Petit-Huguenin (who provided BNF improvements) and John Klensin.
  3. Update code references in Section 9 and add to Informative References section. Note: The code in the Malkiewicz github repository may be the code that was originally at http://theory.lcs.mit.edu/~rivest/sexp.html
  4. Add this Change History Appendix.
  5. Move "Historical Notes" which were formerly a separate section at the end of the document up to be a sub-section of Section 1.
  6. Add references to [LISP], [RFC2692], and [RFC2693].
  7. Add simple security considerations.
  8. Minor editorial fixes/improvements.

Acknowledgements

The comments and suggestions of the following are gratefully acknowledged: Marc Petit-Huguenin, John Klensin.

Authors' Addresses

Ronald L. Rivest
MIT CSAIL
32 Vassar Street, Room 32-G692
Cambridge, Massachusetts 02139
United States of America
Donald E. Eastlake 3rd
Futurewei Technologies
2386 Panoramic Circle
Apopka, Florida 32703
United States of America