ASN.1 — стандарт описания протоколов передачи данных. Вот уже 8 лет не дает покоя тот факт, что существуют либо слабые попытки его использовать, практически не прибегая к его полному функционалу, либо же это монстры за большие деньги, используемые отнюдь не рядовым Васей.
Причем и те и другие наследуют одни и те же черты — неудобство, перегруженность дополнительными действиями и генерацией совсем бесполезных заглушек.
На хабре появлялось уже достаточно много статей с описанием всех прелестей ASN.1, рекомендую почитать их.


Вашему вниманию предлагаю альфа-версию библиотеки, позволяющей в стиле hibernate или jackson-databind сохранить ваши данные в бинарном виде. Пока поддерживаются только нотации BER(DER).


Примеры использования


ASN.1 S уже сейчас позволяет сериализовать классы с примитивами (и их упакованной версией):


@Sequence( name = "Message", tagNumber = 2 )
public class Message
{
    @Property( optional = true )
    private Integer id;
    @Property( typeName = "GeneralizedTime" )
    private Instant stamp;
    @Property
    private String text;
    @Constructor
    public Message( @ConstructorParam( value = "manager", global = true ) ObjectManager manager, @ConstructorParam( "id" ) Integer id, @ConstructorParam( "stamp" ) Instant stamp, @ConstructorParam( "userId" ) int userId, @ConstructorParam( "text" ) String text )
    {
        this.id = id;
        this.stamp = stamp;
        user = manager.getUser( userId );
        this.text = text;
    }
       //...
    private User user;
    @Property
    public int getUserId()
    {
        return user.getId();
    }

Разумеется задавать поля в конструкторе не обязательно. Достаточно сделать все поля доступными для записи через сеттеры и не забыть про конструктор по умолчанию (jackson вас тоже по рукам бить будет):


@Sequence( name = "User" )
public class User
{
    @Property
    private int id;
    @Property
    private String name;
    @Property
    private String accessKey;

    public User(){  }
    public User( int id, String name, String accessKey ) { ... }

    public int getId(){return id;}
    public void setId( int id ){this.id = id;}

    public String getName(){return name;}
    public void setName( String name ){this.name = name;}

    public String getAccessKey() { return accessKey; }
    public void setAccessKey( String accessKey ) { this.accessKey = accessKey; }

В данный момент наличие аннотации обязательно, но ничто не мешает сделать вариант полностью аналогичный jackson — данные можно сериализовать как есть, без всяких метаданных, написанных вами, основываясь на reflection. Сейчас этого нет в целях отладки — жесткие системы всегда проще отладить.


Так же поддерживаются массивы (тип указывается, что бы библиотека не генерировала свое — T-Java-Bind-org-asn1s-databind-tests-persons-Person-Array):


    @Property( optional = true, typeName = "Person-Array" )
    private Person[] family;

И списки, если класс элемента один:


    @Property( typeName = "NoteList" )
    private List<Note> notes;

В будущем можно будет задавать набор классов, которые могут появится в коллекции (в духе
@ItemClass({Note.class, Book.class, Paper.class}) ). Все необходимое в библиотеке есть, а ASN.1 поддерживает несколько механизмов реализации (CHOICE, INSTANCE OF, можно даже поиграться с SEQUENCE и SET, используя необязательные поля).


И наконец пример в сериализованном виде:
image


Быстродействие


Серьезных исследований не проводилось, но в сравнении с jackson ASN.1 S работает на порядок хуже.


PersonsTest.testPersonsJsonRead: [measured 100000 out of 110000 rounds, threads: 1 (sequential)]
 ... time.total: 0.33, time.warmup: 0.10, time.bench: 0.22
PersonsTest.testPersonsRead: [measured 100000 out of 110000 rounds, threads: 1 (sequential)]
 ... time.total: 1.05, time.warmup: 0.26, time.bench: 0.79
PersonsTest.testPersonsJsonWrite: [measured 100000 out of 110000 rounds, threads: 1 (sequential)]
 ... time.total: 0.21, time.warmup: 0.05, time.bench: 0.16
PersonsTest.testPersonsWrite: [measured 100000 out of 110000 rounds, threads: 1 (sequential)]
 ... time.total: 1.17, time.warmup: 0.22, time.bench: 0.95

Использование NIO позволит повысить быстродействие, в данный момент буферизация делается через стек из ByteArrayOutputStream — самый простой способ, приводящий к огромному числу операций копирования и выделения памяти.


Заключение


Представлена библиотека с открытым исходными кодом под лицензией MIT. Несмотря на версию альфа — уже можно использовать для простых случаев.

Поделиться с друзьями
-->

Комментарии (4)


  1. evnp
    18.07.2017 10:20

    Пару лет назад было прям очень нужно и выбрали http://www.unigone.com/en/asn1-solutions/asn1compiler/ как самое в тестах адекватное решение (ничего подходящего свободного не нашлось), но потом проект все равно провалился :)

    В любом случае спасибо, может еще кому это пригодится. Нам правда актуальнее были не аннотации, а генерация бинов по существующей схеме.


    1. lastrix
      18.07.2017 12:15

      Основной упор был на поддержку спецификаций (X.680-X.683) для чтения и представления схем в модель. Иначе что-либо делать далее не имеет смысла.
      Аннотации в данный момент — рудимент, который нужно будет переписать с нуля. На это дело потрачено часов 10. Для сравнения, суммарно (включая предыдущую попытку, которая так же на гитхабе у меня валяется) на проект ушло более 300 часов.

      Что же до генерации бинов — это не такая сложная задача. И ставить себе такую задачу не было желания, т.к. проект мой ничем не отличался бы от уже существующих опенсорсных аналогов.

      Проект писался и пишется исключительно по фану и ради удовольствия.


  1. bearded_guy
    18.07.2017 12:15

    Делал подобную шутку, до для C# и для PER сериализации. Очень раздражало, что в спецификации о том как и где лежат биты нет ни одной картинки, а все написано словами.


    1. lastrix
      18.07.2017 12:19

      Рекомендую заглянуть сюда: http://asn1-playground.oss.com/
      Так и проверял, правильно ли я понял доки. Все интеграционные тесты "а" в бинарном виде получены как раз из этого сервиса.