JavaScript API design for generating a ASN.1 data object.

Greetings,

Here I am proposing below JavaScript API design for generating ASN.1 data structures.

JS Class definition will be dynamically provided based on supplied ASN.X data modules.

Consider below couple of examples:

1.. Supplied below ASN.1 Data Module for ticket.

<asnx:module>
   <namedType name="ticket">
     <type>
       <sequence>
         <element name="numero" type="asn:Integer"/>
         <element name="class">
           <type><enumerated>
             <enumeration name="normal" number="0"/>
             <enumeration name="gift" number="1"/>
             <enumeration name="VIP" number="2"/>
           </enumerated></type>
         </element>
         <optional>
           <element name="end" type="asn:UTCTime"/>
         </optional>
      </sequence>
    </type>
  </namedType>
</asn:module>

This will dynamically generate the below JS API calls.

There can be two options

a. Generate by calling constructor

	  let ticket1 = new Ticket(20, {'normal':20, 'gift': 21, 'vip':22}, '20200629');
	  ticket1.setSequence();
      let sequence_buffer = ticket1.encode();  

b. Generate by calling class methods

		let ticket2 = new Ticket();

        console.log(ticket2.isTicketFinalised()); //returns false
        ticket2.setNumero(21);
        ticket2.setStartTime(new Date());
        ticket2.setTicketClass({'normal':30, 'gift': 31, 'vip':32});
        console.log(ticket2.isTicketFinalised()); //returns true
        let ticket2_sequence_buffer = ticket2.encode();

As you can see here the endTime is optional, So the object can be encoded even if user does not set the optional elements and isTicketFinalised() will return true once we set all the required elements.

2. Supplied below ASN.1 Data Module for Time.

<namedType name="Time">
   <type>
   	<choice>
   		<element name="utcTime" type="asnx:UTCTime"/>
   		<element name="generalizedTime" type="asnx:GeneralizedTime"/>
   	 </choice>
    </type>
</namedType>

This will dynamically generate the below JS API calls. Again there will two options as indicated above. Here I am mentioning the constructor one.

	let time = new Time('20200704', '20200703');
	time. setTimeChoice();
	let time_sequence_buffer = time.encode();
	console.log("time_sequence_buffer:"+time_sequence_buffer);

Likewise to start with we can design the JS API library for the fixed Data Module and dynamic generation of API calls for the application of the provided library.

Thanks. Just commenting on the portion that I think might be confusing.

The Time type means you either use UTCTime or GeneralizedTime (the latter allows timezone information), typically because the developers only have one form of value at hand, so we can't let them supply both values in the constructor of Time.

I would imagine something like this:

A developer can do either this:

let time = new Time(new UTCTime("20200704")

Or

let time = new Time(new GeneralizedTime("20200704"))

But not both. The choice is made by the developer.

But, there is a problem in this example. Because Time is either UTCTime or GeneralizedTime, it's confusing to treat it the same way as we treat Sequence, because there is no container at work when it comes to Choice (@jot2re correct me if I am wrong).

Therefore the setter style might breaks here. That is, we can't do this:

let time = new Time()
time.setGeneralizedTime(new GeneralizedTime("20200704"))
// alternatively  time.setUtcTime(new UTCTime("20200704"))

As new GeneralizedTime("20200704") is a valid Time, not a property of an object called Time. In idiomatic Java, the following would look right for the namedType you supplied.

Time time = (Time)(new GeneralizedTime("20200704"))

You can observe that the name="utcTime" part is unused here (the name of the type that has a Choice, in this case Time, is useful). I think that's the reason the guy who made that ASN.1 module named it a silly name utcTime.


@hboon mentioned a learning style of "tab-complete learning", so I imagine that maybe if we make the developer type at setStateChannelExpiry and got offered two choices, then he will realise that he needs to make a choice there?

I would check again but a quick comment first:

ES5 supports getters and setters, so we can use them to expose a more idiomatic interface unless we expect the setters to return an error message.

e.g. for setters: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set

The example doesn’t use a class, but they work with classes too.

Yes, I believe this might be an issue since it will not be clearly defined from the coding whether the data represents UTCTime or GeneralizedTime. In fact it seems that GeneralizedTime also covers UTCTime implicitly https://www.obj-sys.com/asn1tutorial/node14.html

They differ. UTCTime has only 2 digits to represent the year, while GeneralizedTime has 4 digits, therefore UTCTime is not a subset of GeneralizedTime. I think this is an outdated example to use Choice because it was provided for y2k compatibility reason: the old schema required UTCTime, then at the turn of the century the schema gets updated so the old data signed with UTCTime is still valid and doesn't have to be changed and signed again. New developers should always choose to use GeneralizedTime. I recommend using a different example:

<namedType name="Subscription">
   …
   <namedType name="expiry">
     <type>
       <choice>
   		 <element name="blockHeight" type="asnx:Integer"/>
   		 <element name="generalizedTime" type="asnx:GeneralizedTime"/>
       </choice>
     </type>
   </namedType>
   …
</namedType>

This example is about a subscription that allows a vendor (e.g. a magazine) to take money from your account at a certain interval (once a week). It has an expiry that can be either the block height or a specific time. Contrast to the previous example, in this example the higher-level data structure is mentioned, so you have an object containing the Choice.

With @hboon's ES5 style taken in as well, the code is:

let tx = new Subscription()
tx.expiry = new UTCTime("20200704")

Or

let tx = new CrossChainTransaction()
tx.expiry = new BlockHeight(1212000)

And the choice is thus made in the set expiry function (generated from the ASN.X piece provided).


Is there any advantage in designing the API pretending that Choice is an object (which has as many attributes as the choices and only one is not null)? With the design I proposed, a developer may get:

let expiry = tx.expiry

Without knowing what type this expiry is. It may be a GeneralizedTime, or it may be an Integer, per choice made before. If the developer didn't check and mistaken an integer (blockHeight) to a timestamp. Would pretending Choice an object (instead of making the choice when setting the value) help in solving this issue?

Without knowing what type this expiry is. It may be a GeneralizedTime, or it may be an Integer, per choice made before. If the developer didn't check and mistaken an integer (blockHeight) to a timestamp. Would pretending Choice an object (instead of making the choice when setting the value) help in solving this issue?

It depends on whether we want developers to read the values (and what would they use it for?) or is it more of a write-only API. If we do, one way is to "tag" the return objects:

class C {
	get name() {
		return {
			type: 'String', //Or GeneralizedTime or UTCTime, etc
			value: this._name
		}
	}
	set name(value) {
		this._name = value
	}
}

let c = new C()
c.name = "Bob"
let {type: t, value: v} = c.name
console.log(t) //"String"
console.log(v) //"Bob"
console.log(c.name.value) //"Bob"

It introduces a bit more complexity in writing especially when it's not a choice, but still doesn't affect the read operations.

Or just add a "type" property to the class like:

class GeneralizedTime {
    get _type() {
        return "GeneralizedTime"
    }
}

And if they don't ever need to read it, we can remove the getters completely.