ASN.1 Packed Encoding Rules (PER) UNALIGNED variant

NOTE: This article is no longer maintained. Please visit ASN.1 PER UNALIGNED variant summary.

Introduction

This article briefly summarizes the ASN.1 Packed Encoding Rules (PER) UNALIGNED variant used in the 3GPP specifications with examples.

Disclaimer

This is a simplified explanation and not a complete specification. Missing examples used in the 3GPP specifications will be updated as soon as possible.

Encoding rules

Length determinant

If length determinant len is to be encoded as Constrained whole number with lower bound lb and upper bound ub less than 64K (65,536):

  • If range (ub - lb + 1) is equal to 1, no bits are used for length determinant.
  • Otherwise, minimum number of bits to represent ub - lb are used and len - lb is encoded like ‘unsigned integer’ in other languages.
    1
    2
    3
    4
    bit n        1
    +----------+
    | len - lb |
    +----------+

If length determinant len is to be encoded as Normally small length, a single bit indicating whether the value is less than or equal to 64 is inserted and the value is encoded in two ways:

  • If the value is less than or equal to 64, the bit is set to 0 and len - 1 is encoded like 6-bit ‘unsigned integer’ in other languages.
    1
    2
    3
    4
    bit   6       1
    +-+---------+
    |0| len - 1 |
    +-+---------+
  • Otherwise, the bit is set to 1 and the value is encoded as described below.
    1
    2
    3
    +-+------+
    |1| len* |
    +-+------+

If length determinant len is unconstrained or semi-constrained and less than or equal to 127, a single bit 0 is inserted and the value is encoded as 7-bit ‘unsigned integer’ in other languages.

1
2
3
4
bit   7   1
+-+-----+
|0| len |
+-+-----+

If length determinant len is unconstrained or semi-constrained and larger than or equal to 127 and less than 16K (16,384), two bits 10 are inserted and the value is encoded as 14-bit ‘unsigned integer’ in other languages.

1
2
3
4
bit    14  1
+--+-----+
|10| len |
+--+-----+

If length determinant len is unconstrained or semi-constrained and larger than 16K, len is divided into A 64K + B 16K + C. Two bits 11, 000100 and 64K items are inserted and they are repeated A times. If 16K or more items remain, two bits 11, 000001/000010/000011 and corresponding number of items are inserted. Finally, unconstrained or semi-constrained length determinant is inserted and remaining items are inserted.

1
2
3
4
5
6
7
8
9
+--+--------+-------+--+---------+-------+-+-----+----------------+
|11| 000100 | items |11| len (B) | items |0| len | items (<= 127) |
+--+--------+-------+--+---------+-------+-+-----+----------------+
^-A repetition----^ ^-0/1 repetition---^

+--+--------+-------+--+---------+-------+--+-----+-------------------------+
|11| 000100 | items |11| len (B) | items |10| len | items (> 127 and < 16K) |
+--+--------+-------+--+---------+-------+--+-----+-------------------------+
^-A repetition----^ ^-0/1 repetition---^

Boolean type

BOOLEAN
A single bit is set to 1 for TRUE and 0 for FALSE

1
2
3
4
bit 1
+-+
|b|
+-+

Integer type

INTEGER (lb..ub)
Minimum number of bits to represent ub - lb are used and value - lb is encoded like ‘unsigned integer’ in other languages.

1
2
3
4
bit n          1
+------------+
| value - lb |
+------------+

INTEGER (lb..ub, ...)
A single bit indicating whether the value is not within range from lb to ub is inserted at the beginning.

  • If the bit is set to 0, the value is encoded like INTEGER (lb..ub).

    1
    2
    3
    4
    bit   n          1
    +-+------------+
    |0| value - lb |
    +-+------------+
  • If the bit is set to 1, Length determinant (unconstrained) is inserted to represent the number of octets used and the value is encoded like ‘signed integer’ in other languages.

    1
    2
    3
    4
    bit         len*8 1
    +-+-----+-------+
    |1| len | value |
    +-+-----+-------+

Enumerated type

ENUMERATED {item1, item2}
The selected index is encoded like INTEGER (0..ub), where ub is equal to the number of enumeration items - 1.

1
2
3
4
bit n     1
+-------+
| index |
+-------+

ENUMERATED {item1, item2, ..., item3, item4}
A single bit indicating whether the selected enumeration index is not within the enumeration root.

  • If the bit is set to 0, the enumeration index is encoded like ENUMERATED {item1, item2}.
    1
    2
    3
    4
    bit   n     1
    +-+-------+
    |0| index |
    +-+-------+
  • If the bit is set to 1, a single bit indicating whether the selected index is larger than or equal to 64 is inserted and the value is encoded in two ways.
    • If the bit is set to 0, the value is encoded like 6-bit ‘unsigned integer’ in other languages.
      1
      2
      3
      4
      bit     6            1
      +-+-+--------------+
      |1|0| index (< 64) |
      +-+-+--------------+
    • If the bit is set to 1, Length determinant (semi-constrained with lower bound of 0) to represent the number of octets used is inserted and the value is encoded like ‘unsigned integer’ in other languages.
      1
      2
      3
      4
      bit           len*8         1
      +-+-+-----+---------------+
      |1|1| len | index (>= 64) |
      +-+-+-----+---------------+

Bit string type

BIT STRING
Length determinant (unconstrained) to represent the number of bits is inserted and the value is inserted.

1
2
3
4
bit       len   1
+-----+-------+
| len | value |
+-----+-------+

BIT STRING (SIZE(n))
The value is inserted without length determinant.

1
2
3
4
bit n     1
+-------+
| value |
+-------+

BIT STRING (SIZE(lb..ub))
Length determinant is inserted to represent the number of bits used and the value is inserted.

1
2
3
4
bit       n     1
+-----+-------+
| len | value |
+-----+-------+

Octet string type

OCTET STRING
Length determinant (unconstrained) is inserted to represent the number of octets used and the value is inserted.

1
2
3
4
bit       len*8 1
+-----+-------+
| len | value |
+-----+-------+

OCTET STRING (SIZE(n))
If n is less than 64K, the value is inserted without length determinant.

1
2
3
4
bit n*8   1
+-------+
| value |
+-------+

If n is larger than 64K, Length determinant (constrained) to represent the number of octets used is inserted and the value is inserted.

1
2
3
4
bit       len*8 1
+-----+-------+
| len | value |
+-----+-------+

OCTET STRING (SIZE(v1..v2))
Length determinant (constrained) to represent the number of octets used is inserted and the value is inserted.

1
2
3
4
bit       len*8 1
+-----+-------+
| len | value |
+-----+-------+

OCTET STRING (CONTAINING x)
Same as OCTET STRING.

Null type

It is just a placeholder. Not a single bit is used to encode NULL

Sequence type

1
2
3
4
5
SEQUENCE {
item1 Item1
item2 Item2 OPTIONAL,
item3 Item3 DEFAULT value
}

n bits are inserted where n is equal to the number of OPTIONAL and DEFAULT components. Each bit represents presence (1) and absence (0) of a component of a corresponding position. If n is larger than 64K, Length determinant (constrained) is inserted at the beginning.

1
2
3
4
5
6
7
8
9
bit n                1
+------------------+--------+-----+--------+
| bit mask (< 64K) | value1 | ... | valueN |
+------------------+--------+-----+--------+

bit n 1
+-----+-------------------+--------+-----+--------+
| len | bit mask (>= 64K) | value1 | ... | valueN |
+-----+-------------------+--------+-----+--------+
1
2
3
4
5
6
7
8
9
10
SEQUENCE {
item1 Item1,
item2 Item2 OPTIONAL,
...,
item3 Item4,
[[
item5 Item5,
item6 Item,
]]
}

A single bit indicating whether extension additions after the extension marker are present.

  • If the bit is set to 0, the rest of encoding is the same as Sequence type without extension marker and extension additions.
    1
    2
    3
    4
    bit   n        1
    +-+----------+--------+-----+--------+
    |0| bit mask | value1 | ... | valueN |
    +-+----------+--------+-----+--------+
  • If the bit is set to 1, Length determinant (normally small length) to represent the number of extension additions after the extension marker and the same number of bits are inserted. Each bit represents presence (1) and absence (0) of an extension addition of a corresponding position. Each extension addition is encoded as Open type field. An extension addition group enclosed with [[ and ]] is encoded as a Sequence type.
    1
    2
    3
    4
    bit   n        1                               len      1
    +-+----------+--------+-----+--------+-----+----------+--------+-----+--------+
    |0| bit mask | value1 | ... | valueN | len | bit mask | value1 | ... | valueN |
    +-+----------+--------+-----+--------+-----+----------+--------+-----+--------+

Sequence-of type

SEQUENCE (SIZE(v1..v2)) OF
Length determinant (constrained) to represent the number of sequence-of values included is inserted and the values are inserted.

1
2
3
+-----+--------+--------+-----+--------+
| len | value1 | value2 | ... | valueN |
+-----+--------+--------+-----+--------+

Choice type

1
2
3
4
CHOICE {
item1 Item1,
item2 Item2
}

The choice index is encoded like INTEGER (0..ub) where ub is the largest index and the actual value is inserted.

1
2
3
+-------+-------+
| index | value |
+-------+-------+
1
2
3
4
5
6
7
CHOICE {
item1 Item1,
item2 Item2,
...,
item3 Item3,
item4 Item4
}

A single bit indicating whether the selected item is within the the alternative root.

  • If the bit is set to 0, the choice index is encoded like INTEGER (0..ub) where ub is ther largest index and the actual value is inserted.
    1
    2
    3
    +-+-------+-------+
    |0| index | value |
    +-+-------+-------+
  • If the bit is set to 1, a single bit indicating whether the choice index is larger than or equal to 64 is inserted and the value is encoded in two ways.
    • If the bit is set to 0, the choice index is encoded like 6-bit ‘unsigned integer’ in other languages and the actual value is inserted.
      1
      2
      3
      4
      bit     6            1
      +-+-+---------------+-------+
      |1|0| index (< 64) | value |
      +-+-+---------------+-------+
    • If the bit is set to 1, Length determinant (semi-constrained with lower bound of 0) to represent the number of octets used is inserted and the choice index is encoded like ‘unsigned integer’ in other languages. And the actual value is inserted.
      1
      2
      3
      4
      bit           len           1
      +-+-+-----+---------------+-------+
      |1|1| len | index (>= 64) | value |
      +-+-+-----+---------------+-------+

References

  1. ITU-T X.691 ASN.1 encoding rules: Specification of Packed Encoding Rules (PER)

Tower of Hanoi with recursion

Problem statement

Assume there are 3 towers and \(D\) disks. And all disks are place at the first tower in a descending order in terms of disk number. It can be illustrated as follows:

1
2
3
4
5
6
7
     [1]             |              |
[ 2 ] | |
: | |
[ D-1 ] | |
[ D ] | |
-----------------------------------------
Tower 1 Tower 2 Tower 3

The objective is to find out the order of movement to move the entire disks to another tower with constrains:

  • One only disk can be moved at a time
  • Disk \(X\) cannot be placed under disk \(Y\), where \(X\) is greater than \(Y\)

Approach

To move disk \(X\) from tower \(A\) to \(C\), three steps as follows:

  1. Move disks 1 to \(X-1\) from tower \(A\) to \(B\)
  2. Move disk \(X\) from tower \(A\) to \(C\)
  3. Move disks 1 to \(X-1\) from tower \(B\) to \(C\)

Steps (1) and (3) are not a single atomic operation and they can be performed recursively:

  1. Move disks 1 to \(X-2\) from tower \(A\) to \(C\)
  2. Move disk \(X-1\) from tower \(A\) to \(B\)
  3. Move disks 1 to \(X-2\) from tower \(C\) to \(B\)

If there is only one disk to move, step (1) and step (3) can be skipped.

Implementation

We can define a function moveDisks() whose inputs are the number of disks to be moved, tower number that the disks are currently located and tower number that the disks to be moved to:

1
void moveDisks(int nDisks, int from, int to);

It can recursively call the function itself:

1
2
3
4
5
6
7
8
9
10
void moveDisks(int nDisks, int from, int to) {
int intermediate = 6 - from - to;
if (nDisks > 1) {
moveDisks(nDisks - 1, from, intermediate);
}
cout << "Move disk " << nDisks << " from tower " << from << " to tower " << to << endl;
if (nDisks > 1) {
moveDisks(nDisks - 1, intermediate, to);
}
}

Here int intermediate = 6 - from - to; is a tricky part. This variable represents the number of intermediate tower to move nDisks - 1 disks. If each tower is numbered with 1, 2, and 3 then relationships between from, to and intermediate can be determined as follows and the forementioned equation reflects these relationships:

From To Intermediate
1 2 3
1 3 2
2 1 3
2 3 2
3 1 2
3 2 3

Test result

1
2
3
4
5
6
7
8
3
Move disk 1 from tower 1 to tower 3
Move disk 2 from tower 1 to tower 2
Move disk 1 from tower 3 to tower 2
Move disk 3 from tower 1 to tower 3
Move disk 1 from tower 2 to tower 1
Move disk 2 from tower 2 to tower 3
Move disk 1 from tower 1 to tower 3

webpack raw-loader with TypeScript

Another struggle of a day. I had difficulties on importing a GraphQL schema definition in TypeScript project with webpack raw-loader.

Declare in webpack.config.js to use raw-loader to import a GraphQL schema definition:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module.exports = {
module: {
rules: [
{
test: /\.graphql$/i, // Extension which you want to load via raw-loader
use: 'raw-loader',
},
// omitted
],
},
resolve: {
extensions: ['.js', '.ts'], // Add extension if you want to raw-load without typing extension
},
// omitted
};

Declare in src/raw-loader.d.ts that an imported GraphQL schema definition is a type of string:

1
2
3
4
declare module "*.graphql" {
const content: string;
export default content;
};

Declare in tsconfig.json so that tsc does not try to compile *.graphql files:

1
2
3
{
"exclude": ["*.graphql"]
}

axios with proxy and custom SSL certificate

This article summarizes what made me struggle days.

Fix https-proxy-agent to support CA

It was originally proposed here by maslakov.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { HttpsProxyAgent, HttpsProxyAgentOptions } from 'https-proxy-agent';
import { ClientRequest, RequestOptions } from 'agent-base';
import { Socket } from 'net';

export class PatchedHttpsProxyAgent extends HttpsProxyAgent {
private ca: any;

constructor(opts: HttpsProxyAgentOptions) {
super(opts);
this.ca = opts.ca;
}

async callback(req: ClientRequest, opts: RequestOptions): Promise<Socket> {
return super.callback(req, Object.assign(opts, { ca: this.ca }));
}
}

Working prototype

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const axios = require('axios');
const HttpsProxyAgent = require('https-proxy-agent');
const ca = require('ssl-root-cas/latest').create();

ca.addFile(certificateFilePath);

const httpsAgent = new HttpsProxyAgent({
protocol: 'http',
host: 'proxy_host',
port: 8080,
ca,
});

axios.defaults.httpsAgent = httpsAgent;

References

  1. PatchedHttpsProxyAgent

Building GitHub Action to publish Hexo post from GitHub Issue

I wanted to write and publish a Hexo post without tedious commands such as git clone, git pull, hexo new, hexo generate, git commit and git push. I saw the feasibility to build an automated publishing system using GitHub Issue and GitHub Actions for this purpose.

Here’s workflow what I thought:

  • A user makes an issue, a draft of a post on GitHub Issue
  • GitHub Workflow is triggered against the issue
  • The workflow converts the issue to a Hexo post

GitHub Issue

I thought that GitHub Issue is a perfect system to draft a post. It has a title, a content and a date/time of update and those information can be a title, a content and a date/time of a post. One thing missing is tags of a post. To achieve this, I got an idea to use labels of an issue as tags of a post.

GitHub Action

I did a research whether there exists GitHub Actions converting an issue to a Hexo post, but I found nothing. So I decided to build a GitHub Action for it.

First of all, it takes two parameters, issue_url and token and extracts endpoint from issue_url:

1
2
3
4
5
6
import * as core from '@actions/core';

const issueUrl = core.getInput('issue_url');
const index = issueUrl.indexOf('/repos');
const endpoint = issueUrl.substring(index);
const token = core.getInput('token');

And then, it initializes Hexo:

1
2
3
4
5
6
7
8
import Hexo from 'hexo';

const hexo = new Hexo(process.cwd(), {});
hexo.init().then(() => {
// Described below
}).catch((reason) => {
core.setFailed(reason);
});

After Hexo is initialized, it retrieves information of an issue:

1
2
3
4
5
6
7
8
9
10
11
12
import { Octokit } from 'octokit';

// After resolving `hexo.init()`
const gh = new Octokit({ auth: token });

console.log(`Converting issue ${endpoint} to Hexo post...`);
gh.request(`GET ${endpoint}`).then((response) => {
const { title, updated_at: date, labels, milestone, body: content } = response.data;
// Described below
}).catch((reason) => {
core.setFailed(reason);
});

If a draft is set to be published, it derives tags and creates a Hexo post:

1
2
3
4
5
6
7
8
9
10
11
12
13
const MILESTONE_PUBLISH = 'publish';

if (milestone.title !== MILESTONE_PUBLISH) {
console.log(`Issue does not have milestone ${MILESTONE_PUBLISH}`);
} else {
const tags = labels.map((label: any) => label.name);
hexo.post.create({
title,
date,
tags,
content,
} as any);
}

At this point, a markdown file is generated and further actions to be prepared to complete publishing a post. The complete implementation of this action can be found here.

Workflow

Now a complete workflow needs to be setup. The workflow below converts an issue to a post and pushes it to the repository:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
name: Issue to Hexo
on:
issues:
# Sufficient to trigger this workflow when an issue is milestoned
types: [ milestoned ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
persist-credentials: 'false'
- uses: internalstability/[email protected]
with:
issue_url: ${{ github.event.issue.url }}
# Personal access token used to get information of Issue
token: ${{ secrets.token }}
# At this point, a markdown file is generated and untracked
# Take further action, e.g. generate (`hexo generate`), commit and push
- name: Commit post
run: |
git add .
git config user.name "issue-to-hexo bot"
git config.user.email "<>"
git commit -m "Add a post"
- name: Push
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.token }}

I have another workflow which generates (hexo generate) a blog from the pushed code. I made two separate workflows because I both use GitHub Issue to draft a post and manually write and push a markdown draft on my PC. Here’s the workflow:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
name: Publish
on:
push:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
persist-credentials: 'false'
submodules: 'true'
- uses: actions/setup-node@v2
with:
node-version: '14'
# This submodule is a directory that contains generated Hexo blog
- name: Checkout submodule
run: |
cd public
git checkout master
- name: Build
run: |
npm install
npm run build
- name: Commit submodule
run: |
cd public
git add .
git config user.name "internalstability/hexo-src bot"
git config user.email "<>"
git commit -m "Publish"
- name: Push submodule
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.token }}
directory: 'public'
repository: 'internalstability/internalstability.github.io'
# These actions commits and pushes the submodule to the main module
- name: Commit main module
run: |
git add .
git config user.name "internalstability/hexo-src bot"
git config user.email "<>"
git commit -m "Publish"
- name: Push main module
uses: ad-m/github-push-action@master

Drag sorting Ant Design Table with dnd kit

Introduction

I was trying to implement Drag sorting with handler for Ant Design Table. I found that react-sortable-hoc is not going to be enhancement further and the author encourages to use dnd kit. This article summarizes what I did to implement it with dnd kit.

Prepare dummy data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function App() {
const columns = [
{
key: "dragHandle", dataIndex: "dragHandle", title: "Drag",
width: 30,
render: () => <MenuOutlined />,
},
{
key: "key", dataIndex: "key", title: "Key",
},
];

const dataSourceRaw = new Array(5).fill({}).map((item, index) => ({
// This will be transformed into `data-row-key` of props.
// Shall be truthy to be draggable. I don't know why.
// To this end, index of number type is converted into string.
key: index.toString(),
}));
const [dataSource, setDataSource] = useState(dataSourceRaw);

return (
<Table
columns={columns}
dataSource={dataSource}
/>
);

The important thing here is that the key used to identify an item must be truthy value. I struggled with a situation that the first item is not draggable. After some minutes of debugging, I found that if the key is falsy value, it is not draggable. But I couldn’t find the root cause for this.

Define a state variable for drag overlay

1
2
// ID to render overlay.
const [activeId, setActiveId] = useState(null);

activeId will be used for determining whether to render drag overlay or not.

Convert Table into sortable preset of dnd kit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
);

return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<Table
columns={columns}
dataSource={dataSource}
components={{
body: {
wrapper: DraggableWrapper,
row: DraggableRow,
},
}}
/>
{/* Render overlay component. */}
<DragOverlay>{activeId ? activeId : null}</DragOverlay>
</DndContext>
);

According to sortable single container, enclose Table in DndContext, wrap body (tbody) with SortableContext and implement useSortable in row (tr). In this example, DraggableWrapper implements SortableContext and DraggableRow implements tr with useSortrable.

DragOverlay is rendered only when activeId has a valid ID value.

DraggableWrapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function DraggableWrapper(props: any) {
const { children, ...restProps } = props;
/**
* 'children[1]` is `dataSource`
* Check if `children[1]` is an array
* because antd gives 'No Data' element when `dataSource` is an empty array
*/
return (
<SortableContext
items={children[1] instanceof Array ? children[1].map((child: any) => child.key) : []}
strategy={verticalListSortingStrategy}
{...restProps}
>
<tbody {...restProps}>
{
// This invokes `Table.components.body.row` for each element of `children`.
children
}
</tbody>
</SortableContext>
);
}

DraggableWrapper implements SortableContext. items shall be a list of keys to identify items, not items themselves. Inside tbody, children is a list of rows and each item will invoke Table.components.body.row, which is DraggableRow.

DraggableRow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function DraggableRow(props: any) {
const { attributes, listeners, setNodeRef } = useSortable({
id: props["data-row-key"],
});
const { children, ...restProps } = props;
/**
* 'children[1]` is a row of `dataSource`
* Check if `children[1]` is an array
* because antd gives 'No Data' element when `dataSource` is an empty array
*/
return (
<tr
ref={setNodeRef}
{...attributes}
{...restProps}
>
{
children instanceof Array ? (
children.map((child: any) => {
const { children, key, ...restProps } = child;
return key === "dragHandle" ? (
<td {...listeners} {...restProps}>
{child}
</td>
) : (
<td {...restProps}>{child}</td>
);
})
) : (
children
)
}
</tr>
);
}

DraggableRow implements tr with useSortable, where id must be the same with a key of each item. We want to make a row draggable, so assign setNodRef to tr. And we want to enable dragging only when a user grabs a drag handle, so assign listeners to td containing a drag handle.

handleDragStart

1
2
3
4
function handleDragStart(event: any) {
const { active } = event;
setActiveId(active.id);
}

handleDragEnd

1
2
3
4
5
6
7
8
9
10
11
12
13
function handleDragEnd(event: any) {
const { active, over } = event;
if (active.id !== over.id) {
setDataSource((items) => {
// In this example, find an item, where `item.key` === `useSortable.id`.
const oldIndex = items.findIndex((item) => item.key === active.id);
const newIndex = items.findIndex((item) => item.key === over.id);
return arrayMove(items, oldIndex, newIndex);
});
}
// Stop overlay.
setActiveId(null);
}

handleDragEnd performs swapping two items. Here, to find indexes of two items in data source, we need to compare key of an item and id of useSortable.

You can find demo here.

UML and OpenAPI for full stack development

There are many good articles introducing how to become a full stack developer. Lots of technologies — HTML, CSS, JavaScript, Express, React are mentioned. They are really good resources. However, I think they miss some importatnt things and here are two importatnt things.

UML: Data Model of Service

Before implementing a service, it is important to construct concrete a data model, definitions and relationships between entities. Without a concrete data model, design and implementation of service are highly likely to be changed during development, resulting in an unstable product and risk of missing feature.

To design a class diagram, you can use one of software (or others):

OpenAPI: Protocol between Backend and Frontend

After designing data model, a developer needs to implement backend service provider and frontend service request. OpenAPI enables a developer to define a protocol: which paths, parameters and body contents to use, how responses will be.

To design OpenAPI, you can use one of software (or others):

Embedding worker in Electron app using Webpack and electron-builder

Disclaimer: as per electron-boilerplate

Make webpack bundle your worker code. I’ll skip the entire code here because it’s too long.

Remeber the output path

1
2
3
4
5
6
{
output: {
path: path.join(__dirname, '..'),
filename: './app/dist/worker.js',
}
}

Make electron-builder copy your worker bundle file .

1
2
3
4
5
6
7
{
"build": {
"files": [
"dist/"
]
}
}

This will place your worker bunlde file inside resources/app.asar.

Adjust worker path and working directory for fork in main.dev.ts.

1
2
3
4
5
6
7
8
9
const workerPath =
process.env.NODE_ENV === 'development'
? 'app/worker.js'
: 'app.asar/dist/worker.js';
const workerCwd =
process.env.NODE_ENV === 'development'
? undefined
: join(__dirname, '..');
const worker = fork(workerPath, { cwd: workerCwd });

References

Installing Electron manually for electron-builder

This article tries to resolve an uncommon issue when you encounter the following error and it cannot be resolved automatically, e.g. behind proxy, etc.:

1
Error: Electron failed to install correctly, please delete node_modules/electron and try installing again

Download the right version (with consideration of package.json) of Electron from https://github.com/electron/electron/releases

Unpack the downloaded Electron and put all files under node_modules/electron/dist.

Create path.txt file under node_modules/electron with the following content:

  • Windows: electron.exe
  • Linux: electron
  • macOS: Electron.app/Contents/MacOS/Electron

Note: Make sure there is no whitespace characters (including a newline character) after the filename. This is a problem that I struggled for minutes and hours.

References

Quick start on ANTLR4 in Rust - part3

This is my note in adopting and learning ANTLR4Rust

Series


In the previous article, we implemented a parser with internal state variables. However, if a grammar is huge, it is practically impossible to manage a huge number of state variables. Alternatively, a visitor-like approach can be used.

In an exit method of the root rule in the Listener, we can grab the current context and its children and call custom parser methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
impl Listener {
fn hdr(&self, ctx: &HdrContextAll) -> Row {
let row_ctx = ctx.row().unwrap();
self.row(&row_ctx)
} fn row(&self, ctx: &RowContextAll) -> Row {
let mut row = Row::new();
let field_ctx_list = ctx.field_all();
for (_i, field_ctx) in field_ctx_list.iter().enumerate() {
let field = self.field(&field_ctx);
row.push(field);
}
row
} fn field(&self, ctx: &FieldContextAll) -> String {
ctx.get_text()
}
}
impl CSVListener for Listener {
fn exit_csvFile(&mut self, ctx: &CsvFileContext) {
let hdr_ctx = ctx.hdr().unwrap();
let header = self.hdr(&hdr_ctx);
self.csv.header = header;
let row_ctx_list = ctx.row_all();
for (_i, row_ctx) in row_ctx_list.iter().enumerate() {
let row = self.row(&row_ctx);
self.csv.rows.push(row);
}
}
}

The minimal working example can be found here.