mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-11-15 10:14:21 +01:00
Merge pull request #5980 from BryanCrotazGivEnergy/packet-diagram-bit-counts
Feature: Packet diagram can use bit counts - implements #5978
This commit is contained in:
6
.changeset/quiet-hotels-shine.md
Normal file
6
.changeset/quiet-hotels-shine.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
'mermaid': minor
|
||||||
|
'@mermaid-js/parser': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
feat: Add shorter `+<count>: Label` syntax in packet diagram
|
||||||
@@ -16,13 +16,25 @@ This diagram type is particularly useful for developers, network engineers, educ
|
|||||||
|
|
||||||
## Syntax
|
## Syntax
|
||||||
|
|
||||||
```md
|
```
|
||||||
packet-beta
|
packet-beta
|
||||||
start: "Block name" %% Single-bit block
|
start: "Block name" %% Single-bit block
|
||||||
start-end: "Block name" %% Multi-bit blocks
|
start-end: "Block name" %% Multi-bit blocks
|
||||||
... More Fields ...
|
... More Fields ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Bits Syntax (v\<MERMAID_RELEASE_VERSION>+)
|
||||||
|
|
||||||
|
Using start and end bit counts can be difficult, especially when modifying a design. For this we add a bit count field, which starts from the end of the previous field automagically. Use `+<count>` to set the number of bits, thus:
|
||||||
|
|
||||||
|
```
|
||||||
|
packet-beta
|
||||||
|
+1: "Block name" %% Single-bit block
|
||||||
|
+8: "Block name" %% 8-bit block
|
||||||
|
9-15: "Manually set start and end, it's fine to mix and match"
|
||||||
|
... More Fields ...
|
||||||
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
```mermaid-example
|
```mermaid-example
|
||||||
@@ -76,8 +88,8 @@ packet-beta
|
|||||||
```mermaid-example
|
```mermaid-example
|
||||||
packet-beta
|
packet-beta
|
||||||
title UDP Packet
|
title UDP Packet
|
||||||
0-15: "Source Port"
|
+16: "Source Port"
|
||||||
16-31: "Destination Port"
|
+16: "Destination Port"
|
||||||
32-47: "Length"
|
32-47: "Length"
|
||||||
48-63: "Checksum"
|
48-63: "Checksum"
|
||||||
64-95: "Data (variable length)"
|
64-95: "Data (variable length)"
|
||||||
@@ -86,8 +98,8 @@ title UDP Packet
|
|||||||
```mermaid
|
```mermaid
|
||||||
packet-beta
|
packet-beta
|
||||||
title UDP Packet
|
title UDP Packet
|
||||||
0-15: "Source Port"
|
+16: "Source Port"
|
||||||
16-31: "Destination Port"
|
+16: "Destination Port"
|
||||||
32-47: "Length"
|
32-47: "Length"
|
||||||
48-63: "Checksum"
|
48-63: "Checksum"
|
||||||
64-95: "Data (variable length)"
|
64-95: "Data (variable length)"
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ describe('packet diagrams', () => {
|
|||||||
[
|
[
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
"bits": 11,
|
||||||
"end": 10,
|
"end": 10,
|
||||||
"label": "test",
|
"label": "test",
|
||||||
"start": 0,
|
"start": 0,
|
||||||
@@ -49,11 +50,13 @@ describe('packet diagrams', () => {
|
|||||||
[
|
[
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
"bits": 11,
|
||||||
"end": 10,
|
"end": 10,
|
||||||
"label": "test",
|
"label": "test",
|
||||||
"start": 0,
|
"start": 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"bits": 1,
|
||||||
"end": 11,
|
"end": 11,
|
||||||
"label": "single",
|
"label": "single",
|
||||||
"start": 11,
|
"start": 11,
|
||||||
@@ -63,6 +66,58 @@ describe('packet diagrams', () => {
|
|||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle bit counts', async () => {
|
||||||
|
const str = `packet-beta
|
||||||
|
+8: "byte"
|
||||||
|
+16: "word"
|
||||||
|
`;
|
||||||
|
await expect(parser.parse(str)).resolves.not.toThrow();
|
||||||
|
expect(getPacket()).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"bits": 8,
|
||||||
|
"end": 7,
|
||||||
|
"label": "byte",
|
||||||
|
"start": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bits": 16,
|
||||||
|
"end": 23,
|
||||||
|
"label": "word",
|
||||||
|
"start": 8,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle bit counts with bit or bits', async () => {
|
||||||
|
const str = `packet-beta
|
||||||
|
+8: "byte"
|
||||||
|
+16: "word"
|
||||||
|
`;
|
||||||
|
await expect(parser.parse(str)).resolves.not.toThrow();
|
||||||
|
expect(getPacket()).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"bits": 8,
|
||||||
|
"end": 7,
|
||||||
|
"label": "byte",
|
||||||
|
"start": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bits": 16,
|
||||||
|
"end": 23,
|
||||||
|
"label": "word",
|
||||||
|
"start": 8,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
it('should split into multiple rows', async () => {
|
it('should split into multiple rows', async () => {
|
||||||
const str = `packet-beta
|
const str = `packet-beta
|
||||||
0-10: "test"
|
0-10: "test"
|
||||||
@@ -73,11 +128,13 @@ describe('packet diagrams', () => {
|
|||||||
[
|
[
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
"bits": 11,
|
||||||
"end": 10,
|
"end": 10,
|
||||||
"label": "test",
|
"label": "test",
|
||||||
"start": 0,
|
"start": 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"bits": 20,
|
||||||
"end": 31,
|
"end": 31,
|
||||||
"label": "multiple",
|
"label": "multiple",
|
||||||
"start": 11,
|
"start": 11,
|
||||||
@@ -85,6 +142,7 @@ describe('packet diagrams', () => {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
"bits": 31,
|
||||||
"end": 63,
|
"end": 63,
|
||||||
"label": "multiple",
|
"label": "multiple",
|
||||||
"start": 32,
|
"start": 32,
|
||||||
@@ -92,6 +150,7 @@ describe('packet diagrams', () => {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
"bits": 26,
|
||||||
"end": 90,
|
"end": 90,
|
||||||
"label": "multiple",
|
"label": "multiple",
|
||||||
"start": 64,
|
"start": 64,
|
||||||
@@ -111,11 +170,13 @@ describe('packet diagrams', () => {
|
|||||||
[
|
[
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
"bits": 17,
|
||||||
"end": 16,
|
"end": 16,
|
||||||
"label": "test",
|
"label": "test",
|
||||||
"start": 0,
|
"start": 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"bits": 14,
|
||||||
"end": 31,
|
"end": 31,
|
||||||
"label": "multiple",
|
"label": "multiple",
|
||||||
"start": 17,
|
"start": 17,
|
||||||
@@ -123,6 +184,7 @@ describe('packet diagrams', () => {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
"bits": 31,
|
||||||
"end": 63,
|
"end": 63,
|
||||||
"label": "multiple",
|
"label": "multiple",
|
||||||
"start": 32,
|
"start": 32,
|
||||||
@@ -142,6 +204,16 @@ describe('packet diagrams', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw error if numbers are not continuous with bit counts', async () => {
|
||||||
|
const str = `packet-beta
|
||||||
|
+16: "test"
|
||||||
|
18-20: "error"
|
||||||
|
`;
|
||||||
|
await expect(parser.parse(str)).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`[Error: Packet block 18 - 20 is not contiguous. It should start from 16.]`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should throw error if numbers are not continuous for single packets', async () => {
|
it('should throw error if numbers are not continuous for single packets', async () => {
|
||||||
const str = `packet-beta
|
const str = `packet-beta
|
||||||
0-16: "test"
|
0-16: "test"
|
||||||
@@ -152,6 +224,16 @@ describe('packet diagrams', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw error if numbers are not continuous for single packets with bit counts', async () => {
|
||||||
|
const str = `packet-beta
|
||||||
|
+16: "test"
|
||||||
|
18: "error"
|
||||||
|
`;
|
||||||
|
await expect(parser.parse(str)).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`[Error: Packet block 18 - 18 is not contiguous. It should start from 16.]`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should throw error if numbers are not continuous for single packets - 2', async () => {
|
it('should throw error if numbers are not continuous for single packets - 2', async () => {
|
||||||
const str = `packet-beta
|
const str = `packet-beta
|
||||||
0-16: "test"
|
0-16: "test"
|
||||||
@@ -172,4 +254,13 @@ describe('packet diagrams', () => {
|
|||||||
`[Error: Packet block 25 - 20 is invalid. End must be greater than start.]`
|
`[Error: Packet block 25 - 20 is invalid. End must be greater than start.]`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw error if bit count is 0', async () => {
|
||||||
|
const str = `packet-beta
|
||||||
|
+0: "test"
|
||||||
|
`;
|
||||||
|
await expect(parser.parse(str)).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`[Error: Packet block 0 is invalid. Cannot have a zero bit field.]`
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,26 +10,33 @@ const maxPacketSize = 10_000;
|
|||||||
|
|
||||||
const populate = (ast: Packet) => {
|
const populate = (ast: Packet) => {
|
||||||
populateCommonDb(ast, db);
|
populateCommonDb(ast, db);
|
||||||
let lastByte = -1;
|
let lastBit = -1;
|
||||||
let word: PacketWord = [];
|
let word: PacketWord = [];
|
||||||
let row = 1;
|
let row = 1;
|
||||||
const { bitsPerRow } = db.getConfig();
|
const { bitsPerRow } = db.getConfig();
|
||||||
for (let { start, end, label } of ast.blocks) {
|
|
||||||
if (end && end < start) {
|
for (let { start, end, bits, label } of ast.blocks) {
|
||||||
|
if (start !== undefined && end !== undefined && end < start) {
|
||||||
throw new Error(`Packet block ${start} - ${end} is invalid. End must be greater than start.`);
|
throw new Error(`Packet block ${start} - ${end} is invalid. End must be greater than start.`);
|
||||||
}
|
}
|
||||||
if (start !== lastByte + 1) {
|
start ??= lastBit + 1;
|
||||||
|
if (start !== lastBit + 1) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Packet block ${start} - ${end ?? start} is not contiguous. It should start from ${
|
`Packet block ${start} - ${end ?? start} is not contiguous. It should start from ${
|
||||||
lastByte + 1
|
lastBit + 1
|
||||||
}.`
|
}.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
lastByte = end ?? start;
|
if (bits === 0) {
|
||||||
log.debug(`Packet block ${start} - ${lastByte} with label ${label}`);
|
throw new Error(`Packet block ${start} is invalid. Cannot have a zero bit field.`);
|
||||||
|
}
|
||||||
|
end ??= start + (bits ?? 1) - 1;
|
||||||
|
bits ??= end - start + 1;
|
||||||
|
lastBit = end;
|
||||||
|
log.debug(`Packet block ${start} - ${lastBit} with label ${label}`);
|
||||||
|
|
||||||
while (word.length <= bitsPerRow + 1 && db.getPacket().length < maxPacketSize) {
|
while (word.length <= bitsPerRow + 1 && db.getPacket().length < maxPacketSize) {
|
||||||
const [block, nextBlock] = getNextFittingBlock({ start, end, label }, row, bitsPerRow);
|
const [block, nextBlock] = getNextFittingBlock({ start, end, bits, label }, row, bitsPerRow);
|
||||||
word.push(block);
|
word.push(block);
|
||||||
if (block.end + 1 === row * bitsPerRow) {
|
if (block.end + 1 === row * bitsPerRow) {
|
||||||
db.pushWord(word);
|
db.pushWord(word);
|
||||||
@@ -39,7 +46,7 @@ const populate = (ast: Packet) => {
|
|||||||
if (!nextBlock) {
|
if (!nextBlock) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
({ start, end, label } = nextBlock);
|
({ start, end, bits, label } = nextBlock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db.pushWord(word);
|
db.pushWord(word);
|
||||||
@@ -50,8 +57,11 @@ const getNextFittingBlock = (
|
|||||||
row: number,
|
row: number,
|
||||||
bitsPerRow: number
|
bitsPerRow: number
|
||||||
): [Required<PacketBlock>, PacketBlock | undefined] => {
|
): [Required<PacketBlock>, PacketBlock | undefined] => {
|
||||||
|
if (block.start === undefined) {
|
||||||
|
throw new Error('start should have been set during first phase');
|
||||||
|
}
|
||||||
if (block.end === undefined) {
|
if (block.end === undefined) {
|
||||||
block.end = block.start;
|
throw new Error('end should have been set during first phase');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (block.start > block.end) {
|
if (block.start > block.end) {
|
||||||
@@ -62,16 +72,20 @@ const getNextFittingBlock = (
|
|||||||
return [block as Required<PacketBlock>, undefined];
|
return [block as Required<PacketBlock>, undefined];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rowEnd = row * bitsPerRow - 1;
|
||||||
|
const rowStart = row * bitsPerRow;
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
start: block.start,
|
start: block.start,
|
||||||
end: row * bitsPerRow - 1,
|
end: rowEnd,
|
||||||
label: block.label,
|
label: block.label,
|
||||||
|
bits: rowEnd - block.start,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: row * bitsPerRow,
|
start: rowStart,
|
||||||
end: block.end,
|
end: block.end,
|
||||||
label: block.label,
|
label: block.label,
|
||||||
|
bits: block.end - rowStart,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,13 +10,25 @@ This diagram type is particularly useful for developers, network engineers, educ
|
|||||||
|
|
||||||
## Syntax
|
## Syntax
|
||||||
|
|
||||||
```md
|
```
|
||||||
packet-beta
|
packet-beta
|
||||||
start: "Block name" %% Single-bit block
|
start: "Block name" %% Single-bit block
|
||||||
start-end: "Block name" %% Multi-bit blocks
|
start-end: "Block name" %% Multi-bit blocks
|
||||||
... More Fields ...
|
... More Fields ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Bits Syntax (v<MERMAID_RELEASE_VERSION>+)
|
||||||
|
|
||||||
|
Using start and end bit counts can be difficult, especially when modifying a design. For this we add a bit count field, which starts from the end of the previous field automagically. Use `+<count>` to set the number of bits, thus:
|
||||||
|
|
||||||
|
```
|
||||||
|
packet-beta
|
||||||
|
+1: "Block name" %% Single-bit block
|
||||||
|
+8: "Block name" %% 8-bit block
|
||||||
|
9-15: "Manually set start and end, it's fine to mix and match"
|
||||||
|
... More Fields ...
|
||||||
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
```mermaid-example
|
```mermaid-example
|
||||||
@@ -46,8 +58,8 @@ packet-beta
|
|||||||
```mermaid-example
|
```mermaid-example
|
||||||
packet-beta
|
packet-beta
|
||||||
title UDP Packet
|
title UDP Packet
|
||||||
0-15: "Source Port"
|
+16: "Source Port"
|
||||||
16-31: "Destination Port"
|
+16: "Destination Port"
|
||||||
32-47: "Length"
|
32-47: "Length"
|
||||||
48-63: "Checksum"
|
48-63: "Checksum"
|
||||||
64-95: "Data (variable length)"
|
64-95: "Data (variable length)"
|
||||||
|
|||||||
@@ -12,5 +12,10 @@ entry Packet:
|
|||||||
;
|
;
|
||||||
|
|
||||||
PacketBlock:
|
PacketBlock:
|
||||||
start=INT('-' end=INT)? ':' label=STRING EOL
|
(
|
||||||
|
start=INT('-' end=INT)?
|
||||||
|
| '+' bits=INT
|
||||||
|
)
|
||||||
|
':' label=STRING
|
||||||
|
EOL
|
||||||
;
|
;
|
||||||
Reference in New Issue
Block a user