diff --git a/config.json b/config.json index 6e034b6..e25550d 100644 --- a/config.json +++ b/config.json @@ -569,6 +569,14 @@ "prerequisites": [], "difficulty": 9 }, + { + "slug": "intergalactic-transmission", + "name": "Intergalactic Transmission", + "uuid": "1fc2f497-ddd8-4826-bffe-01023efe6bd7", + "practices": [], + "prerequisites": [], + "difficulty": 9 + }, { "slug": "zebra-puzzle", "name": "Zebra Puzzle", diff --git a/exercises/practice/intergalactic-transmission/.docs/instructions.md b/exercises/practice/intergalactic-transmission/.docs/instructions.md new file mode 100644 index 0000000..5497088 --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.docs/instructions.md @@ -0,0 +1,54 @@ +# Instructions + +Your job is to help implement + +- the transmitter, which calculates the transmission sequence, and +- the receiver, which decodes it. + +A parity bit is simple way of detecting transmission errors. +The transmitters and receivers can only transmit and receive _exactly_ eight bits at a time (including the parity bit). +The parity bit is set so that there is an _even_ number of 1 bits in each transmission, and the parity bit is always the first bit from the right. +So if the receiver receives `11000001`, `01110101` or `01000000` (i.e. a transmission with an odd number of 1 bits), it knows there is an error. + +However, messages are rarely this short, and need to be transmitted in a sequence when they are longer. + +For example, consider the message `11000000 00000001 11000000 11011110` (or `C0 01 C0 DE` in hex). + +Since each transmission contains exactly eight bits, it can only contain seven bits of data and the parity bit. +A parity bit must then be inserted after every seven bits of data: + +```text +11000000 00000001 11000000 11011110 + ↑ ↑ ↑ ↑ (7th bits) +``` + +The transmission sequence for this message looks like this: + +```text +1100000_ 0000000_ 0111000_ 0001101_ 1110 + ↑ ↑ ↑ ↑ (parity bits) +``` + +The data in the first transmission in the sequence (`1100000`) has two 1 bits (an even number), so the parity bit is 0. +The first transmission becomes `11000000` (or `C0` in hex). + +The data in the next transmission (`0000000`) has zero 1 bits (an even number again), so the parity bit is 0 again. +The second transmission thus becomes `00000000` (or `00` in hex). + +The data for the next two transmissions (`0111000` and `0001101`) have three 1 bits. +Their parity bits are set to 1 so that they have an even number of 1 bits in the transmission. +They are transmitted as `01110001` and `00011011` (or `71` and `1B` in hex). + +The last transmission (`1110`) has only four bits of data. +Since exactly eight bits are transmitted at a time and the parity bit is the rightmost bit, three 0 bits and then the parity bit are added to make up eight bits. +It now looks like this (where `_` is the parity bit): + +```text +1110 000_ + ↑↑↑ (added 0 bits) +``` + +There is an odd number of 1 bits again, so the parity bit is 1. +The last transmission in the sequence becomes `11100001` (or `E1` in hex). + +The entire transmission sequence for this message is `11000000 00000000 01110001 00011011 11100001` (or `C0 00 71 1B E1` in hex). diff --git a/exercises/practice/intergalactic-transmission/.docs/introduction.md b/exercises/practice/intergalactic-transmission/.docs/introduction.md new file mode 100644 index 0000000..f19dffb --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.docs/introduction.md @@ -0,0 +1,23 @@ +# Introduction + +Trillions upon trillions of messages zip between Earth and neighboring galaxies every millisecond. +But transmitting over such long distances is tricky. +Pesky solar flares, temporal distortions, stray forces, and even the flap of a space butterfly's wing can cause a random bit to change during transmission. + +Now imagine the consequences: + +- Crashing the Intergalactic Share Market when "buy low" turns to "sell now". +- Losing contact with the Kepler Whirl system when "save new worm hole" becomes "cave new worm hole". +- Or plunging the universe into existential horror by replacing a cowboy emoji 🤠 with a clown emoji 🤡. + +Detecting corrupted messages isn't just important — it's critical. +The receiver _must_ know when something has gone wrong before disaster strikes. + +But how? +Scientists and engineers from across the universe have been battling this problem for eons. +Entire cosmic AI superclusters churn through the data. +And then, one day, a legend resurfaces — an ancient, powerful method, whispered in debugging forums, muttered by engineers who've seen too much... + +The Parity Bit! + +A method so simple, so powerful, that it might just save interstellar communication. diff --git a/exercises/practice/intergalactic-transmission/.meta/config.json b/exercises/practice/intergalactic-transmission/.meta/config.json new file mode 100644 index 0000000..70c6f4b --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "rmonnet" + ], + "files": { + "solution": [ + "intergalactic_transmission.odin" + ], + "test": [ + "intergalactic_transmission_test.odin" + ], + "example": [ + ".meta/example.odin" + ] + }, + "blurb": "Add parity bits to a message for transmission", + "source": "Kah Goh", + "source_url": "https://github.com/exercism/problem-specifications/pull/2543" +} diff --git a/exercises/practice/intergalactic-transmission/.meta/example.odin b/exercises/practice/intergalactic-transmission/.meta/example.odin new file mode 100644 index 0000000..fa51229 --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.meta/example.odin @@ -0,0 +1,160 @@ +package intergalactic_transmission + +transmit_sequence :: proc(msg: []u8) -> (seq: []u8) { + + if len(msg) == 0 { return nil } + + buf: comm_buffer + for chunk in msg { + transmit_chunk(&buf, chunk) + } + // There may be some remaining bit in the buffer, flush them. + if buf.step != 0 { + transmit_overflow(&buf) + } + return buf.data[:] +} + +decode_message :: proc(seq: []u8) -> (msg: []u8, okay: bool) { + + if len(seq) == 0 { return nil, true } + + buf: comm_buffer + for chunk in seq { + if !has_even_ones(chunk) { + // We got a chunk with invalid parity, abort! + delete(buf.data) + return nil, false + } + decode_chunk(&buf, chunk) + } + // Because the original message is a set of 8bit words, + // once we got through all of them, what's left in the overflow register + // can only be some zeros (the reminder of a last padded chunk) so there is no need + // to flush the receive buffer. + return buf.data[:], true +} + +comm_buffer :: struct { + step: u8, + overflow: u8, + data: [dynamic]u8, +} + +// Since we only use 7 bits out of each 8bit word, we get leftovers that +// we store in an overflow register. After the 1st word, the leftover is 1 bit, +// after the 2nd word, the leftover is 2 bits, ..., after each 7th word, +// we end up with enough data in the overflow register to send an extra 8bit +// chunk (including parity bit) herefore the need to flush the buffer every 7 +// messages. +// +// For the 1st word, we use the leftmost 7 bits to send and keep the +// 1 rightmost bit in the overflow register. +// For the 2nd word of the message, we will use the 6 leftmost bits +// to build the next tr_chunk and add the 1 bit that was in the overflow +// register to the right of the 6bit block (that bit came first in the +// transmission). We will then be left with 2 bits to store in the +// overflow buffer. +// We repeat the process with, at each step, one less bit of the +// message word going into the chunk and one more bit going into +// the overflow register, until we are left with 7 bits in the +// overflow register, at which point we can emit two chunks +// and go back to the situation we were for the 1st message +// (empty overflow register) +// The tr_chunk_masks and tr_overflow_masks split the current word +// into the rigthmost part (going into the tr_chunk) to be sent +// and the leftmost part (going into the overflow register). +// At each step that split move by 1 bit to the left. +transmit_chunk :: proc(buf: ^comm_buffer, chunk: u8) { + + // Use the leftmost (7 - seq)th bits of chunk and store in position (7-seq)..1. + tr_chunk := (chunk & tr_chunk_masks[buf.step]) >> buf.step + // Add seq-th bits of overflow at position 7..(7-seq+1) + if buf.step > 0 { + tr_chunk |= (buf.overflow << (8 - buf.step)) + } + // Store the remaining rightmost bits in the overflow register. + buf.overflow = chunk & tr_overflow_masks[buf.step] + package_chunk_for_transmission(buf, tr_chunk) + buf.step += 1 + // If we have 7 bits in the overflow, we should flush it. + if buf.step == 7 { + transmit_overflow(buf) + } + +} + +// Every seven steps (when the overflow register has 7 bits) +// and at the end of the message, we need to flush the +// overflow register and send what's in there. +transmit_overflow :: proc(buf: ^comm_buffer) { + + last_chunk := buf.overflow << (8 - buf.step) + package_chunk_for_transmission(buf, last_chunk) + // Reset the sequence since we emptied the overflow register. + buf.step = 0 +} + +// Add the parity bit and put the chunk in the data buffer for transmission. +package_chunk_for_transmission :: proc(buf: ^comm_buffer, chunk: u8) { + + tr_chunk := chunk + // Compute and set the parity bit. + if !has_even_ones(tr_chunk) { + tr_chunk |= 1 + } + append(&buf.data, tr_chunk) +} + +// Each chunk we receive has 7bits usable. We need to +// collect them in sets of 8bits. +// For the 1st word received, we store the 7 bits in the overflow +// register (in the 7 leftmost bits). +// For the 2nd word, we add the 1 leftmost bit to the +// content of the overflow (to the right of the register), +// queue the now full overflow register and then reuse it +// to store the 6 rightmost remaining bits of the 2nd word. +// For each successive word, we keep doing this but with +// one more bit going to complete the previous overflow +// and one less bit going to the new overflow, until there is +// no more bits to go in the new overflow. At which point +// we are back to the same situation as for the first word. +decode_chunk :: proc(buf: ^comm_buffer, chunk: u8) { + + // Remove the parity bit + rc_chunk := (chunk & 0xFE) >> 1 + if buf.step == 0 { + // Store the chunk in the top of the overflow register. + buf.overflow = rc_chunk << 1 + } else { + // Store the rightmost bits at the bottom of the overflow register. + buf.overflow |= ((rc_chunk & rc_chunk_masks[buf.step - 1]) >> (7 - buf.step)) + // We have a full overflow register, output it. + append(&buf.data, buf.overflow) + // Store the leftmost remaining bits at the top of the overflow register. + buf.overflow = (rc_chunk & rc_overflow_masks[buf.step - 1]) << (buf.step + 1) + } + buf.step = (buf.step + 1) % 8 +} + +// Check if the word has an even number of bits set to 1. +has_even_ones :: proc(n: u8) -> bool { + + ones := 0 + num := n + for _ in 0 ..= 7 { + if num & 1 == 1 { + ones += 1 + } + num = num >> 1 + } + return ones & 1 == 0 +} + +// Since these masks splits 8 bits into two parts: tr_chunk_masks[i] + tr_overflow_masks[i] = 0xFF +tr_chunk_masks := [?]u8{0xFE, 0xFC, 0xF8, 0xF0, 0xE0, 0xC0, 0x80} +tr_overflow_masks := [?]u8{0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F} + +// Since these masks splits 7 bits into two parts: rc_chunk_masks[i] + rc_overflow_masks[i] = 0x7F +rc_chunk_masks := [?]u8{0x40, 0x60, 0x70, 0x78, 0x7C, 0x7E, 0x7F} +rc_overflow_masks := [?]u8{0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01, 0x00} diff --git a/exercises/practice/intergalactic-transmission/.meta/tests.toml b/exercises/practice/intergalactic-transmission/.meta/tests.toml new file mode 100644 index 0000000..64a8aac --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.meta/tests.toml @@ -0,0 +1,88 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[f99d4046-b429-4582-9324-f0bcac7ab51c] +description = "calculate transmit sequences -> empty message" + +[ee27ea2d-8999-4f23-9275-8f6879545f86] +description = "calculate transmit sequences -> 0x00 is transmitted as 0x0000" + +[97f27f98-8020-402d-be85-f21ba54a6df0] +description = "calculate transmit sequences -> 0x02 is transmitted as 0x0300" + +[24712fb9-0336-4e2f-835e-d2350f29c420] +description = "calculate transmit sequences -> 0x06 is transmitted as 0x0600" + +[7630b5a9-dba1-4178-b2a0-4a376f7414e0] +description = "calculate transmit sequences -> 0x05 is transmitted as 0x0581" + +[ab4fe80b-ef8e-4a99-b4fb-001937af415d] +description = "calculate transmit sequences -> 0x29 is transmitted as 0x2881" + +[4e200d84-593b-4449-b7c0-4de1b6a0955e] +description = "calculate transmit sequences -> 0xc001c0de is transmitted as 0xc000711be1" + +[fbc537e9-6b21-4f4a-8c2b-9cf9b702a9b7] +description = "calculate transmit sequences -> six byte message" + +[d5b75adf-b5fc-4f77-b4ab-77653e30f07c] +description = "calculate transmit sequences -> seven byte message" + +[6d8b297b-da1d-435e-bcd7-55fbb1400e73] +description = "calculate transmit sequences -> eight byte message" + +[54a0642a-d5aa-490c-be89-8e171a0cab6f] +description = "calculate transmit sequences -> twenty byte message" + +[9a8084dd-3336-474c-90cb-8a852524604d] +description = "decode received messages -> empty message" + +[879af739-0094-4736-9127-bd441b1ddbbf] +description = "decode received messages -> zero message" + +[7a89eeef-96c5-4329-a246-ec181a8e959a] +description = "decode received messages -> 0x0300 is decoded to 0x02" + +[3e515af7-8b62-417f-960c-3454bca7f806] +description = "decode received messages -> 0x0581 is decoded to 0x05" + +[a1b4a3f7-9f05-4b7a-b86e-d7c6fc3f16a9] +description = "decode received messages -> 0x2881 is decoded to 0x29" + +[2e99d617-4c91-4ad5-9217-e4b2447d6e4a] +description = "decode received messages -> first byte has wrong parity" + +[507e212d-3dae-42e8-88b4-2223838ff8d2] +description = "decode received messages -> second byte has wrong parity" + +[b985692e-6338-46c7-8cea-bc38996d4dfd] +description = "decode received messages -> 0xcf4b00 is decoded to 0xce94" + +[7a1f4d48-696d-4679-917c-21b7da3ff3fd] +description = "decode received messages -> 0xe2566500 is decoded to 0xe2ad90" + +[467549dc-a558-443b-80c5-ff3d4eb305d4] +description = "decode received messages -> six byte message" + +[1f3be5fb-093a-4661-9951-c1c4781c71ea] +description = "decode received messages -> seven byte message" + +[6065b8b3-9dcd-45c9-918c-b427cfdb28c1] +description = "decode received messages -> last byte has wrong parity" + +[98af97b7-9cca-4c4c-9de3-f70e227a4cb1] +description = "decode received messages -> eight byte message" + +[aa7d4785-2bb9-43a4-a38a-203325c464fb] +description = "decode received messages -> twenty byte message" + +[4c86e034-b066-42ac-8497-48f9bc1723c1] +description = "decode received messages -> wrong parity on 16th byte" diff --git a/exercises/practice/intergalactic-transmission/intergalactic_transmission.odin b/exercises/practice/intergalactic-transmission/intergalactic_transmission.odin new file mode 100644 index 0000000..d9da857 --- /dev/null +++ b/exercises/practice/intergalactic-transmission/intergalactic_transmission.odin @@ -0,0 +1,11 @@ +package intergalactic_transmission + +transmit_sequence :: proc(msg: []u8) -> (seq: []u8) { + // Implement this procedure. + return nil +} + +decode_message :: proc(seq: []u8) -> (msg: []u8, okay: bool) { + // Implement this procedure. + return nil, false +} diff --git a/exercises/practice/intergalactic-transmission/intergalactic_transmission_test.odin b/exercises/practice/intergalactic-transmission/intergalactic_transmission_test.odin new file mode 100644 index 0000000..cdaa1f7 --- /dev/null +++ b/exercises/practice/intergalactic-transmission/intergalactic_transmission_test.odin @@ -0,0 +1,487 @@ +package intergalactic_transmission + +import "core:fmt" +import "core:strings" +import "core:testing" + +@(test) +/// description = calculate transmit sequences -> empty message +test_calculate_transmit_sequences___empty_message :: proc(t: ^testing.T) { + + input := [?]u8{} + output := transmit_sequence(input[:]) + result := to_hex_sequence(output) + defer { + delete(output) + delete(result) + } + expected := "[]" + + testing.expect_value(t, result, expected) +} + +@(test) +/// description = calculate transmit sequences -> 0x00 is transmitted as 0x0000 +test_calculate_transmit_sequences___0x00_is_transmitted_as_0x0000 :: proc(t: ^testing.T) { + + input := [?]u8{0x00} + output := transmit_sequence(input[:]) + result := to_hex_sequence(output) + defer { + delete(output) + delete(result) + } + expected := "[0x00, 0x00]" + + testing.expect_value(t, result, expected) +} + +@(test) +/// description = calculate transmit sequences -> 0x02 is transmitted as 0x0300 +test_calculate_transmit_sequences___0x02_is_transmitted_as_0x0300 :: proc(t: ^testing.T) { + + input := [?]u8{0x02} + output := transmit_sequence(input[:]) + result := to_hex_sequence(output) + defer { + delete(output) + delete(result) + } + expected := "[0x03, 0x00]" + + testing.expect_value(t, result, expected) +} + +@(test) +/// description = calculate transmit sequences -> 0x06 is transmitted as 0x0600 +test_calculate_transmit_sequences___0x06_is_transmitted_as_0x0600 :: proc(t: ^testing.T) { + + input := [?]u8{0x06} + output := transmit_sequence(input[:]) + result := to_hex_sequence(output) + defer { + delete(output) + delete(result) + } + expected := "[0x06, 0x00]" + + testing.expect_value(t, result, expected) +} + +@(test) +/// description = calculate transmit sequences -> 0x05 is transmitted as 0x0581 +test_calculate_transmit_sequences___0x05_is_transmitted_as_0x0581 :: proc(t: ^testing.T) { + + input := [?]u8{0x05} + output := transmit_sequence(input[:]) + result := to_hex_sequence(output) + defer { + delete(output) + delete(result) + } + expected := "[0x05, 0x81]" + + testing.expect_value(t, result, expected) +} + +@(test) +/// description = calculate transmit sequences -> 0x29 is transmitted as 0x2881 +test_calculate_transmit_sequences___0x29_is_transmitted_as_0x2881 :: proc(t: ^testing.T) { + + input := [?]u8{0x29} + output := transmit_sequence(input[:]) + result := to_hex_sequence(output) + defer { + delete(output) + delete(result) + } + expected := "[0x28, 0x81]" + + testing.expect_value(t, result, expected) +} + +@(test) +/// description = calculate transmit sequences -> 0xc001c0de is transmitted as 0xc000711be1 +test_calculate_transmit_sequences___0xc001c0de_is_transmitted_as_0xc000711be1 :: proc( + t: ^testing.T, +) { + + input := [?]u8{0xc0, 0x01, 0xc0, 0xde} + output := transmit_sequence(input[:]) + result := to_hex_sequence(output) + defer { + delete(output) + delete(result) + } + expected := "[0xC0, 0x00, 0x71, 0x1B, 0xE1]" + + testing.expect_value(t, result, expected) +} + +@(test) +/// description = calculate transmit sequences -> six byte message +test_calculate_transmit_sequences___six_byte_message :: proc(t: ^testing.T) { + + input := [?]u8{0x47, 0x72, 0x65, 0x61, 0x74, 0x21} + output := transmit_sequence(input[:]) + result := to_hex_sequence(output) + defer { + delete(output) + delete(result) + } + expected := "[0x47, 0xB8, 0x99, 0xAC, 0x17, 0xA0, 0x84]" + + testing.expect_value(t, result, expected) +} + +@(test) +/// description = calculate transmit sequences -> seven byte message +test_calculate_transmit_sequences___seven_byte_message :: proc(t: ^testing.T) { + + input := [?]u8{0x47, 0x72, 0x65, 0x61, 0x74, 0x31, 0x21} + output := transmit_sequence(input[:]) + result := to_hex_sequence(output) + defer { + delete(output) + delete(result) + } + expected := "[0x47, 0xB8, 0x99, 0xAC, 0x17, 0xA0, 0xC5, 0x42]" + + testing.expect_value(t, result, expected) +} + +@(test) +/// description = calculate transmit sequences -> eight byte message +test_calculate_transmit_sequences___eight_byte_message :: proc(t: ^testing.T) { + + input := [?]u8{0xc0, 0x01, 0x13, 0x37, 0xc0, 0xde, 0x21, 0x21} + output := transmit_sequence(input[:]) + result := to_hex_sequence(output) + defer { + delete(output) + delete(result) + } + expected := "[0xC0, 0x00, 0x44, 0x66, 0x7D, 0x06, 0x78, 0x42, 0x21, 0x81]" + + testing.expect_value(t, result, expected) +} + +@(test) +/// description = calculate transmit sequences -> twenty byte message +test_calculate_transmit_sequences___twenty_byte_message :: proc(t: ^testing.T) { + + input := [?]u8 { + 0x45, + 0x78, + 0x65, + 0x72, + 0x63, + 0x69, + 0x73, + 0x6d, + 0x20, + 0x69, + 0x73, + 0x20, + 0x61, + 0x77, + 0x65, + 0x73, + 0x6f, + 0x6d, + 0x65, + 0x21, + } + output := transmit_sequence(input[:]) + result := to_hex_sequence(output) + defer { + delete(output) + delete(result) + } + expected := "[0x44, 0xBD, 0x18, 0xAF, 0x27, 0x1B, 0xA5, 0xE7, 0x6C, 0x90, 0x1B, 0x2E, 0x33, 0x03, 0x84, 0xEE, 0x65, 0xB8, 0xDB, 0xED, 0xD7, 0x28, 0x84]" + + testing.expect_value(t, result, expected) +} + +@(test) +/// description = decode received messages -> empty message +test_decode_received_messages___empty_message :: proc(t: ^testing.T) { + + input := [?]u8{} + output, okay := decode_message(input[:]) + result := to_hex_sequence(output) + defer { + delete(output) + delete(result) + } + expected := "[]" + testing.expectf(t, okay, "Expected the message to be valid but got okay = false") + testing.expect_value(t, result, expected) +} + +@(test) +/// description = decode received messages -> zero message +test_decode_received_messages___zero_message :: proc(t: ^testing.T) { + + input := [?]u8{0x00, 0x00} + output, okay := decode_message(input[:]) + result := to_hex_sequence(output) + defer { + delete(output) + delete(result) + } + expected := "[0x00]" + testing.expectf(t, okay, "Expected the message to be valid but got okay = false") + testing.expect_value(t, result, expected) +} + +@(test) +/// description = decode received messages -> 0x0300 is decoded to 0x02 +test_decode_received_messages___0x0300_is_decoded_to_0x02 :: proc(t: ^testing.T) { + + input := [?]u8{0x03, 0x00} + output, okay := decode_message(input[:]) + result := to_hex_sequence(output) + defer { + delete(output) + delete(result) + } + expected := "[0x02]" + testing.expectf(t, okay, "Expected the message to be valid but got okay = false") + testing.expect_value(t, result, expected) +} + +@(test) +/// description = decode received messages -> 0x0581 is decoded to 0x05 +test_decode_received_messages___0x0581_is_decoded_to_0x05 :: proc(t: ^testing.T) { + + input := [?]u8{0x05, 0x81} + output, okay := decode_message(input[:]) + result := to_hex_sequence(output) + defer { + delete(output) + delete(result) + } + expected := "[0x05]" + testing.expectf(t, okay, "Expected the message to be valid but got okay = false") + testing.expect_value(t, result, expected) +} + +@(test) +/// description = decode received messages -> 0x2881 is decoded to 0x29 +test_decode_received_messages___0x2881_is_decoded_to_0x29 :: proc(t: ^testing.T) { + + input := [?]u8{0x28, 0x81} + output, okay := decode_message(input[:]) + result := to_hex_sequence(output) + defer { + delete(output) + delete(result) + } + expected := "[0x29]" + testing.expectf(t, okay, "Expected the message to be valid but got okay = false") + testing.expect_value(t, result, expected) +} + +@(test) +/// description = decode received messages -> first byte has wrong parity +test_decode_received_messages___first_byte_has_wrong_parity :: proc(t: ^testing.T) { + + input := [?]u8{0x07, 0x00} + output, okay := decode_message(input[:]) + defer delete(output) + testing.expectf(t, !okay, "Expected the message to be invalid but got okay = true") +} + +@(test) +/// description = decode received messages -> second byte has wrong parity +test_decode_received_messages___second_byte_has_wrong_parity :: proc(t: ^testing.T) { + + input := [?]u8{0x03, 0x68} + output, okay := decode_message(input[:]) + defer delete(output) + testing.expectf(t, !okay, "Expected the message to be invalid but got okay = true") +} + +@(test) +/// description = decode received messages -> 0xcf4b00 is decoded to 0xce94 +test_decode_received_messages___0xcf4b00_is_decoded_to_0xce94 :: proc(t: ^testing.T) { + + input := [?]u8{0xcf, 0x4b, 0x00} + output, okay := decode_message(input[:]) + result := to_hex_sequence(output) + defer { + delete(output) + delete(result) + } + expected := "[0xCE, 0x94]" + testing.expectf(t, okay, "Expected the message to be valid but got okay = false") + testing.expect_value(t, result, expected) +} + +@(test) +/// description = decode received messages -> 0xe2566500 is decoded to 0xe2ad90 +test_decode_received_messages___0xe2566500_is_decoded_to_0xe2ad90 :: proc(t: ^testing.T) { + + input := [?]u8{0xe2, 0x56, 0x65, 0x00} + output, okay := decode_message(input[:]) + result := to_hex_sequence(output) + defer { + delete(output) + delete(result) + } + expected := "[0xE2, 0xAD, 0x90]" + testing.expectf(t, okay, "Expected the message to be valid but got okay = false") + testing.expect_value(t, result, expected) +} + +@(test) +/// description = decode received messages -> six byte message +test_decode_received_messages___six_byte_message :: proc(t: ^testing.T) { + + input := [?]u8{0x47, 0xb8, 0x99, 0xac, 0x17, 0xa0, 0x84} + output, okay := decode_message(input[:]) + result := to_hex_sequence(output) + defer { + delete(output) + delete(result) + } + expected := "[0x47, 0x72, 0x65, 0x61, 0x74, 0x21]" + testing.expectf(t, okay, "Expected the message to be valid but got okay = false") + testing.expect_value(t, result, expected) +} + +@(test) +/// description = decode received messages -> seven byte message +test_decode_received_messages___seven_byte_message :: proc(t: ^testing.T) { + + input := [?]u8{0x47, 0xb8, 0x99, 0xac, 0x17, 0xa0, 0xc5, 0x42} + output, okay := decode_message(input[:]) + result := to_hex_sequence(output) + defer { + delete(output) + delete(result) + } + expected := "[0x47, 0x72, 0x65, 0x61, 0x74, 0x31, 0x21]" + testing.expectf(t, okay, "Expected the message to be valid but got okay = false") + testing.expect_value(t, result, expected) +} + +@(test) +/// description = decode received messages -> last byte has wrong parity +test_decode_received_messages___last_byte_has_wrong_parity :: proc(t: ^testing.T) { + + input := [?]u8{0x47, 0xb8, 0x99, 0xac, 0x17, 0xa0, 0xc5, 0x43} + output, okay := decode_message(input[:]) + defer delete(output) + testing.expectf(t, !okay, "Expected the message to be invalid but got okay = true") +} + +@(test) +/// description = decode received messages -> eight byte message +test_decode_received_messages___eight_byte_message :: proc(t: ^testing.T) { + + input := [?]u8{0xc0, 0x00, 0x44, 0x66, 0x7d, 0x06, 0x78, 0x42, 0x21, 0x81} + output, okay := decode_message(input[:]) + result := to_hex_sequence(output) + defer { + delete(output) + delete(result) + } + expected := "[0xC0, 0x01, 0x13, 0x37, 0xC0, 0xDE, 0x21, 0x21]" + testing.expectf(t, okay, "Expected the message to be valid but got okay = false") + testing.expect_value(t, result, expected) +} + +@(test) +/// description = decode received messages -> twenty byte message +test_decode_received_messages___twenty_byte_message :: proc(t: ^testing.T) { + + input := [?]u8 { + 0x44, + 0xbd, + 0x18, + 0xaf, + 0x27, + 0x1b, + 0xa5, + 0xe7, + 0x6c, + 0x90, + 0x1b, + 0x2e, + 0x33, + 0x03, + 0x84, + 0xee, + 0x65, + 0xb8, + 0xdb, + 0xed, + 0xd7, + 0x28, + 0x84, + } + output, okay := decode_message(input[:]) + result := to_hex_sequence(output) + defer { + delete(output) + delete(result) + } + expected := "[0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x6D, 0x20, 0x69, 0x73, 0x20, 0x61, 0x77, 0x65, 0x73, 0x6F, 0x6D, 0x65, 0x21]" + testing.expectf(t, okay, "Expected the message to be valid but got okay = false") + testing.expect_value(t, result, expected) +} + +@(test) +/// description = decode received messages -> wrong parity on 16th byte +test_decode_received_messages___wrong_parity_on_16th_byte :: proc(t: ^testing.T) { + + input := [?]u8 { + 0x44, + 0xbd, + 0x18, + 0xaf, + 0x27, + 0x1b, + 0xa5, + 0xe7, + 0x6c, + 0x90, + 0x1b, + 0x2e, + 0x33, + 0x03, + 0x84, + 0xef, + 0x65, + 0xb8, + 0xdb, + 0xed, + 0xd7, + 0x28, + 0x84, + } + output, okay := decode_message(input[:]) + defer delete(output) + testing.expectf(t, !okay, "Expected the message to be invalid but got okay = true") +} + +// Helper function to comvert a slice of u8 to its hexadecimal representation +// Note: we don't use `expect_slices()` because the resulting values would be displayed in +// decimal, not hexadecimal as we need to be compatible with the content of the tests. +to_hex_sequence :: proc(sequence: []u8) -> string { + + buf := strings.builder_make() + strings.write_rune(&buf, '[') + for value, i in sequence { + if i > 0 { + strings.write_string(&buf, ", ") + } + hex_value := fmt.aprintf("%#02X", value) + defer delete(hex_value) + strings.write_string(&buf, hex_value) + } + strings.write_rune(&buf, ']') + return strings.to_string(buf) +}