├── docs ├── asm_qrcode.png ├── python │ ├── qrcode.png │ ├── .flake8 │ ├── galois.py │ └── qrcode.py ├── jupyter │ ├── mask-0.png │ ├── mask-1.png │ ├── mask-2.png │ ├── mask-3.png │ ├── mask-4.png │ ├── mask-5.png │ ├── mask-6.png │ ├── mask-7.png │ ├── qrcode.png │ └── lunch-and-learn.ipynb └── 3Q-test.md ├── .gitignore ├── const.inc ├── Makefile ├── LICENSE ├── qrcode.pbm ├── README.md ├── ascii.s ├── pbm.s ├── main.s ├── qrcode.s └── reed-solomon.s /docs/asm_qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barrettotte/qr-asm/HEAD/docs/asm_qrcode.png -------------------------------------------------------------------------------- /docs/python/qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barrettotte/qr-asm/HEAD/docs/python/qrcode.png -------------------------------------------------------------------------------- /docs/jupyter/mask-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barrettotte/qr-asm/HEAD/docs/jupyter/mask-0.png -------------------------------------------------------------------------------- /docs/jupyter/mask-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barrettotte/qr-asm/HEAD/docs/jupyter/mask-1.png -------------------------------------------------------------------------------- /docs/jupyter/mask-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barrettotte/qr-asm/HEAD/docs/jupyter/mask-2.png -------------------------------------------------------------------------------- /docs/jupyter/mask-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barrettotte/qr-asm/HEAD/docs/jupyter/mask-3.png -------------------------------------------------------------------------------- /docs/jupyter/mask-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barrettotte/qr-asm/HEAD/docs/jupyter/mask-4.png -------------------------------------------------------------------------------- /docs/jupyter/mask-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barrettotte/qr-asm/HEAD/docs/jupyter/mask-5.png -------------------------------------------------------------------------------- /docs/jupyter/mask-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barrettotte/qr-asm/HEAD/docs/jupyter/mask-6.png -------------------------------------------------------------------------------- /docs/jupyter/mask-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barrettotte/qr-asm/HEAD/docs/jupyter/mask-7.png -------------------------------------------------------------------------------- /docs/jupyter/qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barrettotte/qr-asm/HEAD/docs/jupyter/qrcode.png -------------------------------------------------------------------------------- /docs/python/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E501, # line too long 3 | E701 # multiple statements on one line -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.wakatime-project 2 | __pycache__ 3 | .vscode/ 4 | 5 | asm-tests/bin/ 6 | .ipynb_checkpoints/ 7 | 8 | bin/ 9 | *.o 10 | 11 | test.py -------------------------------------------------------------------------------- /const.inc: -------------------------------------------------------------------------------- 1 | // OS 2 | .equ STDOUT, 0 // 3 | 4 | // Syscall 5 | .equ EXIT, 1 // 6 | .equ READ, 3 // 7 | .equ WRITE, 4 // 8 | .equ OPEN, 5 // 9 | .equ CLOSE, 6 // 10 | .equ CREATE, 8 // 11 | 12 | // ASCII 13 | .equ ASCII_LF, 0x0A // '\n' in ASCII 14 | .equ ASCII_SPACE, 0x20 // ' ' in ASCII 15 | .equ ASCII_ZERO, 0x30 // '0' in ASCII 16 | .equ ASCII_ONE, 0x31 // '1' in ASCII 17 | .equ ASCII_TWO, 0x32 // '2' in ASCII 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # qr-asm build file 2 | 3 | OUT = bin 4 | BIN = qrcode 5 | BINARGS = "https://github.com/barrettotte" 3 6 | 7 | SRC := $(patsubst %.s,%.o,$(wildcard *.s)) 8 | 9 | AS = arm-none-eabi-as 10 | ASFLAGS = -g 11 | 12 | LD = arm-none-eabi-ld 13 | LDFLAGS = 14 | # LDS = link.ld 15 | 16 | GDB = arm-none-eabi-gdb 17 | DBGARGS = -ex 'file $(OUT)/$(BIN)' -ex 'target remote localhost:1234' -ex 'layout regs' 18 | 19 | .PHONY: .FORCE 20 | .FORCE: 21 | 22 | all: clean build link 23 | 24 | rebuild: all 25 | 26 | build: $(SRC) 27 | @echo $(SRC) 28 | 29 | %.o : %.s 30 | $(AS) $(ASFLAGS) $< -o $@ 31 | 32 | link: 33 | $(LD) *.o $(LDFLAGS) -o $(OUT)/$(BIN) 34 | 35 | clean: 36 | @mkdir -p $(OUT) 37 | rm -f *.o $(OUT)/$(BIN) 38 | rm -f qrcode.pbm 39 | 40 | qemu: .FORCE 41 | qemu-arm -singlestep -g 1234 $(OUT)/$(BIN) $(BINARGS) 42 | 43 | debug: .FORCE 44 | $(GDB) $(DBGARGS) 45 | 46 | test: 47 | ./$(OUT)/$(BIN) $(BINARGS) 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Barrett Otte 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /qrcode.pbm: -------------------------------------------------------------------------------- 1 | P1 2 | 37 37 3 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 7 | 0 0 0 0 1 1 1 1 1 1 1 0 1 1 0 0 0 0 0 1 1 0 0 1 0 0 1 1 1 1 1 1 1 0 0 0 0 8 | 0 0 0 0 1 0 0 0 0 0 1 0 1 0 1 1 1 1 0 0 0 1 1 1 1 0 1 0 0 0 0 0 1 0 0 0 0 9 | 0 0 0 0 1 0 1 1 1 0 1 0 1 1 1 1 0 0 0 1 0 0 1 1 0 0 1 0 1 1 1 0 1 0 0 0 0 10 | 0 0 0 0 1 0 1 1 1 0 1 0 1 1 0 1 0 1 0 1 1 0 1 0 1 0 1 0 1 1 1 0 1 0 0 0 0 11 | 0 0 0 0 1 0 1 1 1 0 1 0 1 1 0 1 0 0 0 1 1 0 1 1 0 0 1 0 1 1 1 0 1 0 0 0 0 12 | 0 0 0 0 1 0 0 0 0 0 1 0 0 1 0 0 0 1 0 1 1 1 1 1 1 0 1 0 0 0 0 0 1 0 0 0 0 13 | 0 0 0 0 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1 0 0 0 0 14 | 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 15 | 0 0 0 0 0 1 1 0 1 0 1 1 0 1 1 1 0 1 0 0 0 1 0 1 1 0 1 0 1 1 1 1 1 0 0 0 0 16 | 0 0 0 0 1 1 0 1 1 0 0 0 1 1 1 0 1 0 0 1 0 0 1 0 1 0 1 0 0 0 1 0 1 0 0 0 0 17 | 0 0 0 0 1 1 0 0 1 0 1 1 1 0 1 1 1 1 1 1 1 0 1 0 1 0 0 0 0 1 1 1 1 0 0 0 0 18 | 0 0 0 0 1 1 0 0 0 1 0 1 1 0 1 1 0 0 0 1 0 1 0 1 1 1 1 1 0 1 0 1 0 0 0 0 0 19 | 0 0 0 0 0 0 0 0 0 0 1 0 0 1 1 0 1 0 1 0 0 1 0 0 1 0 1 1 0 0 0 0 0 0 0 0 0 20 | 0 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 1 0 1 1 1 1 0 1 1 1 1 0 0 0 1 1 0 0 0 0 21 | 0 0 0 0 0 0 1 1 1 0 1 0 1 0 1 1 1 1 0 1 0 0 0 0 0 0 1 1 0 0 1 1 1 0 0 0 0 22 | 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 1 1 1 1 0 1 1 0 0 1 1 1 1 0 0 0 0 1 0 0 0 0 23 | 0 0 0 0 1 0 0 0 0 1 1 0 1 1 0 1 0 1 0 1 0 1 0 0 0 1 1 1 0 1 0 1 0 0 0 0 0 24 | 0 0 0 0 0 0 1 1 0 0 0 0 1 0 1 0 0 1 0 1 0 1 0 0 1 1 1 0 0 0 1 1 1 0 0 0 0 25 | 0 0 0 0 1 0 1 0 1 0 1 1 1 0 0 0 1 1 1 0 0 0 0 0 1 1 0 1 1 1 0 1 1 0 0 0 0 26 | 0 0 0 0 0 1 1 1 0 1 0 1 0 1 0 0 1 1 1 1 0 1 1 1 1 0 1 1 0 0 0 1 1 0 0 0 0 27 | 0 0 0 0 1 0 0 0 1 0 1 1 1 1 0 1 0 1 0 0 1 1 0 0 1 1 1 1 1 0 0 1 0 0 0 0 0 28 | 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 1 1 0 0 1 1 0 1 1 0 0 0 1 0 0 1 1 0 0 0 0 29 | 0 0 0 0 1 1 1 1 1 1 1 0 1 1 1 0 0 0 1 0 1 0 0 1 1 0 1 0 1 0 1 1 1 0 0 0 0 30 | 0 0 0 0 1 0 0 0 0 0 1 0 0 1 1 0 1 1 1 0 1 1 1 0 1 0 0 0 1 0 0 0 1 0 0 0 0 31 | 0 0 0 0 1 0 1 1 1 0 1 0 1 1 1 1 1 0 0 1 1 0 1 0 1 1 1 1 1 1 0 0 1 0 0 0 0 32 | 0 0 0 0 1 0 1 1 1 0 1 0 0 1 0 1 1 1 1 1 1 1 0 0 1 1 0 0 1 1 0 0 0 0 0 0 0 33 | 0 0 0 0 1 0 1 1 1 0 1 0 1 0 0 1 1 1 1 1 1 1 1 1 0 0 0 1 1 0 0 0 1 0 0 0 0 34 | 0 0 0 0 1 0 0 0 0 0 1 0 1 1 1 0 1 0 0 0 1 1 0 0 1 0 0 1 0 0 0 1 0 0 0 0 0 35 | 0 0 0 0 1 1 1 1 1 1 1 0 0 0 1 0 1 1 1 1 1 1 1 1 1 1 1 0 1 0 0 1 1 0 0 0 0 36 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 37 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 38 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 39 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qr-asm 2 | 3 | Generate a QR code from scratch with only ARM assembly. 4 | 5 |
6 |
7 | QR code to my GitHub profile. 8 |

A byte mode QR code of https://github.com/barrettotte using Q error correction level. 9 |
See qrcode.pbm for the raw image file.

10 |
11 | 12 | ## Why? 13 | 14 | At first, I learned how QR codes worked with an ugly python implementation; See [docs/python](docs/python). 15 | Midway through the python implementation, I started to learn ARM assembly. But, I had no idea what to build 16 | to practice my knowledge. 17 | 18 | So, this project turned into a giant coding challenge just to see if I could do it. 19 | In a world where C exists, you should obviously never use assembly for this type of project/application. 20 | 21 | ## Usage 22 | 23 | `Usage: qrcode msg err_lvl` 24 | 25 | valid error levels: `L=1, M=0, Q=3, H=2` 26 | 27 | My primary test - `./bin/qrcode "https://github.com/barrettotte" 3` 28 | 29 | Quick build and test - `make && make test` 30 | 31 | ## Limitations 32 | 33 | I constrained the QR code generation a lot. I just wanted to encode a url, not build a whole library. 34 | 35 | - Byte mode encoding only 36 | - QR version 4 and below (up to 80 characters with v4-L) 37 | - Mask evaluation not implemented, hardcoded to mask 0 (I think masks only effect scan efficiency) 38 | - Instead of implementing an entire image file format, I used the [PBM](https://en.wikipedia.org/wiki/Netpbm) file format to create my QR code image. 39 | 40 | Additionally, I'm still a novice with assembly so there is probably a ton of unoptimized code. 41 | I also didn't use all the features of the ARM assembler/architecture. 42 | I wanted to write this so a year from now a dummy like me could understand what I wrote. 43 | 44 | ### Debugging with GDB 45 | 46 | I'm still new to GDB, but this worked for me. 47 | 48 | - `make && make qemu` 49 | - `make debug` 50 | 51 | For sanity checking this ugly thing, see my notes in [docs/3Q-test.md](docs/3Q-test.md) 52 | 53 | ## References 54 | 55 | - [ARM A32 Calling Convention](https://en.wikipedia.org/wiki/Calling_convention#ARM_(A32)) 56 | - [ARM 32-bit EABI Syscall Reference](https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md#arm-32_bit_EABI) 57 | - [GNU ARM Assembler Quick Reference](https://www.ic.unicamp.br/~celio/mc404-2014/docs/gnu-arm-directives.pdf) 58 | - [QR Code Design Wiki](https://en.wikipedia.org/wiki/QR_code#Design) 59 | - [QR Code Tutorial](https://www.thonky.com/qr-code-tutorial/) 60 | - [Reed Solomon Codes for Coders](https://en.wikiversity.org/wiki/Reed%E2%80%93Solomon_codes_for_coders) 61 | - [GDB Command Reference](https://visualgdb.com/gdbreference/commands/x) 62 | - [PBM File Description](https://oceancolor.gsfc.nasa.gov/staff/norman/seawifs_image_cookbook/faux_shuttle/pbm.html) 63 | - [Netpbm Wiki](https://en.wikipedia.org/wiki/Netpbm) 64 | -------------------------------------------------------------------------------- /ascii.s: -------------------------------------------------------------------------------- 1 | // subroutines for various ASCII utilities 2 | 3 | .include "const.inc" 4 | 5 | // exports 6 | .global ascii_uint2dec // convert uint to ASCII decimal string 7 | .global ascii_ubyte2bin // convert ubyte to ASCII binary string 8 | 9 | .data 10 | 11 | aui2d_buf: .space 10+1 // 2^32 capacity; 10 digits (+1 for terminator) 12 | one_tenth: .word 0x1999999A // ~((2^32)/10)+1; for quick div by 10 13 | 14 | .text 15 | 16 | ascii_ubyte2bin: // ***** Convert ubyte to ASCII binary string ***** 17 | // r0 - pointer to ASCII conversion 18 | // r1 - unused 19 | // r2 - ubyte input 20 | // r3 - unused 21 | push {r4-r11, lr} // save caller's vars + return address 22 | 23 | mov r4, #8 // size = 8 24 | mov r5, #1 // mask = 1 25 | mov r8, #0 // i = 0 26 | _aub2b_digit: // loop over each binary digit 27 | and r6, r2, r5 // mask ubyte to get single digit 28 | cmp r6, r5 // compare mask with 29 | beq _aub2b_one // if bit is set, skip over next 2 lines 30 | mov r7, #ASCII_ZERO // c = '0' 31 | b _aub2b_next // iterate 32 | _aub2b_one: 33 | mov r7, #ASCII_ONE // c = '1' 34 | _aub2b_next: 35 | sub r6, r4, r8 // x = 8 - i 36 | sub r6, r6, #1 // offset 1 (null terminator) 37 | strb r7, [r0, r6] // ascii[8-i-1] = c 38 | lsl r5, r5, #1 // shift mask bit 39 | 40 | add r8, r8, #1 // i++ 41 | cmp r8, r4 // check loop condition 42 | blt _aub2b_digit // while (i < 8) 43 | 44 | _aub2b_done: 45 | pop {r4-r11, lr} // restore caller's vars + return address 46 | bx lr // return from subroutine 47 | 48 | ascii_uint2dec: // ***** Convert uint to ASCII decimal string ***** 49 | // r0 - pointer to ASCII conversion 50 | // r1 - length of ASCII conversion (output only) 51 | // r2 - unsigned integer input 52 | // r3 - unused 53 | push {r4-r11, lr} // save caller's vars + return address 54 | 55 | ldr r0, =aui2d_buf // pointer to ASCII convert buffer 56 | ldr r4, =one_tenth // pointer to magic constant 57 | ldr r4, [r4] // load 1/10 58 | mov r9, #10 // 2^32 has up to ten digits 59 | mov r11, #ASCII_ZERO // offset in ASCII table for digits 60 | mov r8, #0 // i = 1 61 | _aui2d_digit: // loop over each decimal digit 62 | umull r7, r5, r2, r4 // a = r5 = r2 / 10 63 | cmp r5, #0 // check loop condition 64 | beq _aui2d_done // while ((r2 / 10) != 0) 65 | 66 | umull r6, r10, r5, r9 // (a / b) * 10 67 | sub r6, r2, r6 // r6 = r2 % 10 = a - ((a / 10) * 10)) 68 | orr r10, r11, r6 // convert digit to ASCII; c 69 | 70 | strb r10, [r0, r8] // s[i] = c 71 | mov r2, r5 // go to next digit 72 | add r8, r8, #1 // i++ 73 | b _aui2d_digit // while(1) 74 | _aui2d_done: 75 | orr r10, r11, r2 // convert digit to ASCII; c 76 | strb r10, [r0, r8] // s[i] = c; highest digit 77 | add r8, r8, #1 // i++ 78 | 79 | mov r7, #0 // i = 0 80 | _aui2d_rev_loop: // reverse byte order of ASCII buffer 81 | ldrb r10, [r0, r7] // ascii[i] 82 | cmp r7, r8 // compare i with digits 83 | blt _aui2d_rev_next // 84 | mov r10, #ASCII_ZERO // set to ASCII zero if not set already 85 | _aui2d_rev_next: 86 | sub r11, r9, r7 // x = 10-i 87 | sub r11, r11, #1 // x= 10-i-1 88 | strb r10, [r0, r11] // ascii[x] = ascii[i] 89 | 90 | add r7, r7, #1 // i++ 91 | cmp r7, r9 // check loop condition 92 | blt _aui2d_rev_loop // while (i < 10) 93 | 94 | mov r10, #0x00 // null terminator 95 | strb r10, [r0, #10] // ascii[-1] = '\0' 96 | mov r1, r8 // return ASCII conversion length 97 | 98 | pop {r4-r11, lr} // restore caller's vars + return address 99 | bx lr // return from subroutine 100 | 101 | .end // end of source 102 | -------------------------------------------------------------------------------- /pbm.s: -------------------------------------------------------------------------------- 1 | // subroutines for creating a Portable Bitmap File (PBM) 2 | 3 | .include "const.inc" 4 | 5 | // exports 6 | .global pbm_write // write array to a new PBM file 7 | 8 | // constants 9 | .equ FILE_MODE, 0666 // file permissions; R/W everyone 10 | .equ MAX_PBM_WIDTH, 70 // max line width of PBM file 11 | 12 | .data 13 | 14 | magic_num: .byte 0x50, 0x31, 0x0A // PBM header; P1\n 15 | line_buff: .space MAX_PBM_WIDTH // line buffer for PBM output 16 | 17 | .text 18 | 19 | pbm_write: // ***** Write memory to new PBM file ***** 20 | // r0 - pointer to memory block (of ASCII bytes) 21 | // r1 - pointer to output file name 22 | // r2 - width 23 | // r3 - height 24 | push {r4-r11, lr} // save caller's vars + return address 25 | 26 | push {r1} // save output file name pointer 27 | push {r0} // save memory block pointer 28 | mov r10, r2 // retain width 29 | mov r11, r3 // retain length 30 | 31 | mov r7, #CREATE // load syscall number 32 | mov r0, r1 // load file name 33 | mov r1, #FILE_MODE // load file mode 34 | svc #0 // invoke syscall 35 | 36 | mov r6, r0 // retain file descriptor 37 | ldr r8, =line_buff // pointer to line buffer 38 | mov r5, #0 // line_idx = 0 39 | _pbm_header: 40 | mov r7, #WRITE // load syscall number 41 | mov r0, r6 // load file descriptor 42 | ldr r1, =magic_num // pointer to PBM header line 1 43 | mov r2, #3 // length of buffer 44 | svc #0 // invoke syscall 45 | _pbm_h_width: 46 | mov r1, #0 // output only 47 | mov r2, r10 // PBM width 48 | bl ascii_uint2dec // convert uint to ASCII string. r0,r1 output 49 | 50 | mov r7, #0 // j = 0 51 | mov r9, #10 // i = 10 52 | sub r9, r9, r1 // i = 10 - ASCII length 53 | _pbm_hw_loop: 54 | add r2, r9, r7 // x = (10 - ASCII length) + j 55 | ldrb r2, [r0, r2] // ascii[x] 56 | strb r2, [r8, r5] // line[line_idx] = ascii[x] 57 | add r5, r5, #1 // line_idx++ 58 | 59 | add r7, r7, #1 // i++ 60 | cmp r7, r1 // check loop condition 61 | blt _pbm_hw_loop // while (j < ascii digits) 62 | 63 | mov r2, #ASCII_SPACE // load space 64 | strb r2, [r8, r5] // line[line_idx] = ' ' 65 | add r5, r5, #1 // line_idx++ 66 | 67 | _pbm_h_length: 68 | mov r1, #0 // output only 69 | mov r2, r11 // PBM length 70 | bl ascii_uint2dec // convert unsigned int to ASCII. r0,r1 output 71 | 72 | mov r7, #0 // j = 0 73 | mov r9, #10 // i = 10 74 | sub r9, r9, r1 // i = 10 - ASCII length 75 | _pbm_hl_loop: 76 | add r2, r9, r7 // x = (10 - ASCII length) + j 77 | ldrb r2, [r0, r2] // ascii[x] 78 | strb r2, [r8, r5] // line[line_idx] = ascii[x] 79 | add r5, r5, #1 // line_idx++ 80 | 81 | add r7, r7, #1 // i++ 82 | cmp r7, r1 // check loop condition 83 | blt _pbm_hl_loop // while (j < ascii digits) 84 | _pbm_header_done: 85 | mov r2, #ASCII_LF // load line feed 86 | strb r2, [r8, r5] // line[line_idx] = '\n' 87 | add r5, r5, #1 // line_idx++ 88 | 89 | mov r7, #WRITE // load syscall number 90 | mov r0, r6 // load file descriptor 91 | mov r1, r8 // load pointer to line buffer 92 | mov r2, r5 // load line buffer length 93 | svc #0 // invoke syscall 94 | _pbm_body: 95 | pop {r0} // restore memory block pointer 96 | mov r9, r0 // retain pointer 97 | push {r0} // save memory block pointer again 98 | mov r1, #0 // block_idx = 0 99 | mov r2, #0 // i = 0 100 | _pbm_loop_x: // loop over rows 101 | mov r4, #0 // line_idx = 0 102 | mov r3, #0 // j = 0 103 | _pbm_loop_y: // loop over cols 104 | 105 | ldrb r5, [r9, r1] // block[block_idx] 106 | 107 | strb r5, [r8, r4] // line[line_idx] = block[block_idx] 108 | add r4, r4, #1 // line_idx++ 109 | add r1, r1, #1 // block_idx++ 110 | 111 | mov r5, #ASCII_SPACE // load space 112 | strb r5, [r8, r4] // line[line_idx] = ' ' 113 | add r4, r4, #1 // line_idx++ 114 | _pbm_next_y: // next col 115 | add r3, r3, #1 // j++ 116 | cmp r3, r10 // check loop condition 117 | blt _pbm_loop_y // while (j < width) 118 | _pbm_next_x: // next row 119 | mov r5, #ASCII_LF // load line feed 120 | strb r5, [r8, r4] // line[line_idx] = '\n' 121 | add r4, r4, #1 // line_idx++ 122 | 123 | push {r1-r2} // save indices 124 | mov r7, #WRITE // load syscall number 125 | mov r0, r6 // load file descriptor 126 | mov r1, r8 // load pointer to line buffer 127 | mov r2, r4 // load buffer size 128 | svc #0 // invoke syscall 129 | pop {r1-r2} // restore indices 130 | 131 | add r2, r2, #1 // i++ 132 | cmp r2, r11 // check loop condition 133 | blt _pbm_loop_x // while (i < length) 134 | _pbm_done: 135 | mov r7, #CLOSE // load syscall number 136 | mov r0, r6 // load file descriptor 137 | svc #0 // invoke syscall 138 | 139 | pop {r0} // restore memory block pointer 140 | pop {r1} // restore file name pointer 141 | 142 | pop {r4-r11, lr} // restore caller's vars + return address 143 | bx lr // return from subroutine 144 | 145 | .end // end of source 146 | -------------------------------------------------------------------------------- /docs/python/galois.py: -------------------------------------------------------------------------------- 1 | # all the mess of dealing with galois field arithmetic and polynomials 2 | 3 | GF256_ANTILOG = [ 4 | 1, 2, 4, 8, 16, 32, 64, 128, 29, 58, # 0 - 9 5 | 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, # 10 - 19 6 | 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, # 20 - 29 7 | 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, # 30 - 39 8 | 106, 212, 181, 119, 238, 193, 159, 35, 70, 140, # 40 - 49 9 | 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, # 50 - 59 10 | 185, 111, 222, 161, 95, 190, 97, 194, 153, 47, # 60 - 69 11 | 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, # 70 - 79 12 | 253, 231, 211, 187, 107, 214, 177, 127, 254, 225, # 80 - 89 13 | 223, 163, 91, 182, 113, 226, 217, 175, 67, 134, # 90 - 99 14 | 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, # 100 - 109 15 | 103, 206, 129, 31, 62, 124, 248, 237, 199, 147, # 110 - 119 16 | 59, 118, 236, 197, 151, 51, 102, 204, 133, 23, # 120 - 129 17 | 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, # 130 - 139 18 | 132, 21, 42, 84, 168, 77, 154, 41, 82, 164, # 140 - 149 19 | 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, # 150 - 159 20 | 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, # 160 - 169 21 | 215, 179, 123, 246, 241, 255, 227, 219, 171, 75, # 170 - 179 22 | 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, # 180 - 189 23 | 174, 65, 130, 25, 50, 100, 200, 141, 7, 14, # 190 - 199 24 | 28, 56, 112, 224, 221, 167, 83, 166, 81, 162, # 200 - 209 25 | 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, # 210 - 219 26 | 172, 69, 138, 9, 18, 36, 72, 144, 61, 122, # 220 - 229 27 | 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, # 230 - 239 28 | 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, # 240 - 249 29 | 108, 216, 173, 71, 142, 1 # 250 - 255 30 | ] 31 | 32 | GF256_LOG = [ 33 | -1, 0, 1, 25, 2, 50, 26, 198, 3, 223, # 0 - 9 34 | 51, 238, 27, 104, 199, 75, 4, 100, 224, 14, # 10 - 19 35 | 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, # 20 - 29 36 | 76, 113, 5, 138, 101, 47, 225, 36, 15, 33, # 30 - 39 37 | 53, 147, 142, 218, 240, 18, 130, 69, 29, 181, # 40 - 49 38 | 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, # 50 - 59 39 | 77, 228, 114, 166, 6, 191, 139, 98, 102, 221, # 60 - 69 40 | 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, # 70 - 79 41 | 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, # 80 - 89 42 | 19, 92, 131, 56, 70, 64, 30, 66, 182, 163, # 90 - 99 43 | 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, # 100 - 109 44 | 186, 61, 202, 94, 155, 159, 10, 21, 121, 43, # 110 - 119 45 | 78, 212, 229, 172, 115, 243, 167, 87, 7, 112, # 120 - 129 46 | 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, # 130 - 139 47 | 49, 197, 254, 24, 227, 165, 153, 119, 38, 184, # 140 - 149 48 | 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, # 150 - 159 49 | 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, # 160 - 169 50 | 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, # 170 - 179 51 | 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, # 180 - 189 52 | 65, 162, 31, 45, 67, 216, 183, 123, 164, 118, # 190 - 199 53 | 196, 23, 73, 236, 127, 12, 111, 246, 108, 161, # 200 - 209 54 | 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, # 210 - 219 55 | 187, 204, 62, 90, 203, 89, 95, 176, 156, 169, # 220 - 229 56 | 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, # 230 - 239 57 | 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, # 240 - 249 58 | 244, 234, 168, 80, 88, 175 # 250 - 255 59 | ] 60 | 61 | GF256_SIZE = 256 62 | PRIMITIVE_POLYNOMIAL = 285 63 | 64 | 65 | # addition in galois field 256 66 | def gf256_add(a: int, b: int): 67 | return a ^ b 68 | 69 | 70 | # subtraction in galois field 256 (same as addition in this field) 71 | def gf256_sub(a: int, b: int): 72 | return a ^ b 73 | 74 | 75 | # multiplication in galois field 256 using lookup table 76 | def gf256_mul(a: int, b: int): 77 | if a == 0 or b == 0: 78 | return 0 79 | return GF256_ANTILOG[(GF256_LOG[a] + GF256_LOG[b]) % (GF256_SIZE - 1)] 80 | 81 | 82 | # division in galois field 256 (using lookup table) 83 | def gf256_div(a: int, b: int): 84 | if a == 0: 85 | return 0 86 | elif b == 0: 87 | raise Exception("div by zero in GF") 88 | return gf256_mul(a, gf256_inv(b)) 89 | 90 | 91 | # multiplicative inverse of a -> $a^{-1}$ 92 | def gf256_inv(a: int): 93 | if a == 0: 94 | raise Exception("Zero has no inverse") 95 | return GF256_ANTILOG[(GF256_SIZE - 1) - GF256_LOG[a]] 96 | 97 | 98 | class Polynomial(): 99 | 100 | def __init__(self, terms: list): 101 | self.terms = terms 102 | 103 | def __str__(self): 104 | return ' + '.join([f"{t}x^{len(self.terms) - i - 1}" for i, t in enumerate(self.terms[::-1]) if t > 0]) 105 | 106 | # return __str__ in alpha notation 107 | def str_alpha(self): 108 | return ' + '.join([f"α^{GF256_LOG[t]}x^{len(self.terms) - i - 1}" for i, t in enumerate(self.terms[::-1]) if t > 0]) 109 | 110 | # return degree of polynomial 111 | def get_degree(self): 112 | return len(self.terms) - 1 113 | 114 | # determine if two polynomials are equivalent 115 | def equals(self, other): 116 | if len(self.terms) > len(other.terms): 117 | min_poly = other 118 | max_poly = self 119 | else: 120 | min_poly = self 121 | max_poly = other 122 | for i in range(len(min_poly.terms)): 123 | if self.terms[i] != other.terms[i]: 124 | return False 125 | for i in range(len(min_poly.terms), len(max_poly.terms)): 126 | if max_poly.terms[i] != 0: 127 | return False 128 | return True 129 | 130 | 131 | # create new polynomial from a block of words 132 | # each word becomes the coefficient of an x term 133 | def block_to_poly(block: list): 134 | terms = ([int(w, 2) for w in block])[::-1] 135 | return Polynomial(terms) 136 | 137 | 138 | # multiply polynomial by alpha value 139 | def poly_alpha_mul(p: Polynomial, alpha: int): 140 | for i, t in enumerate(p.terms): 141 | # print(GF256_ANTILOG[GF256_LOG[t]]) 142 | t_alpha = (GF256_LOG[t] + alpha) % (GF256_SIZE - 1) 143 | p.terms[i] = GF256_ANTILOG[t_alpha] 144 | return p 145 | 146 | 147 | # normalize polynomial 148 | def poly_normalize(p: Polynomial): 149 | max_nz = len(p.terms) - 1 # max nonzero term 150 | for i in range(len(p.terms) - 1, 0, -1): 151 | if p.terms[i] != 0: 152 | break 153 | max_nz = i - 1 154 | if max_nz < 0: 155 | return Polynomial([0]) 156 | elif max_nz < (len(p.terms) - 1): 157 | p.terms = p.terms[0: max_nz + 1] 158 | return p 159 | 160 | 161 | # add two polynomials 162 | def poly_add(a: Polynomial, b: Polynomial): 163 | term_len = len(a.terms) 164 | if len(b.terms) > term_len: 165 | term_len = len(b.terms) 166 | p = Polynomial([0] * term_len) 167 | 168 | for i in range(term_len): 169 | if len(a.terms) > i and len(b.terms) > i: 170 | p.terms[i] = gf256_add(a.terms[i], b.terms[i]) 171 | elif len(a.terms) > i: 172 | p.terms[i] = a.terms[i] 173 | else: 174 | p.terms[i] = b.terms[i] 175 | return poly_normalize(p) 176 | 177 | 178 | # multiply two polynomials 179 | def poly_mul(a: Polynomial, b: Polynomial): 180 | p = Polynomial([0] * (len(a.terms) + len(b.terms))) 181 | for i in range(len(a.terms)): 182 | for j in range(len(b.terms)): 183 | if a.terms[i] != 0 and b.terms[j] != 0: 184 | monomial = Polynomial([0] * (i + j + 1)) 185 | monomial.terms[i + j] = gf256_mul(a.terms[i], b.terms[j]) 186 | p = poly_add(p, monomial) 187 | return poly_normalize(p) 188 | 189 | 190 | # perform polynomial long division and return remainder polynomial 191 | def poly_remainder(numerator: Polynomial, denominator: Polynomial): 192 | if numerator.equals(denominator): 193 | raise Exception("Remainder is zero") 194 | remainder = numerator 195 | 196 | while len(remainder.terms) >= len(denominator.terms): 197 | degree = len(remainder.terms) - len(denominator.terms) 198 | coefficient = gf256_div(remainder.terms[-1], denominator.terms[-1]) 199 | 200 | divisor = poly_mul(denominator, new_monomial(coefficient, degree)) 201 | remainder = poly_add(remainder, divisor) 202 | return poly_normalize(remainder) 203 | 204 | 205 | # create a monomial (single term polynomial) with given term and degree 206 | def new_monomial(term: int, degree: int): 207 | if term == 0: 208 | return Polynomial([0]) 209 | mono = Polynomial([0] * (degree + 1)) 210 | mono.terms[degree] = term 211 | return mono 212 | 213 | 214 | # create generator polynomial for GF256 215 | def get_gen_poly(degree: int): 216 | if degree < 2: 217 | raise Exception('generator polynomial degree must be greater than 2') 218 | gp = Polynomial([1]) 219 | for i in range(degree): 220 | np = Polynomial([GF256_ANTILOG[i], 1]) 221 | gp = poly_mul(gp, np) 222 | return gp 223 | 224 | 225 | # create a polynomial from bit string 226 | def bits_to_poly(bits: str): 227 | return Polynomial([int(bits[i]) for i in range(len(bits))]) 228 | 229 | 230 | # create bit string from polynomial 231 | def poly_to_bits(p: Polynomial): 232 | return ''.join(['1' if t > 0 else '0' for t in p.terms]) 233 | -------------------------------------------------------------------------------- /docs/3Q-test.md: -------------------------------------------------------------------------------- 1 | # Debugging My 3Q Test 2 | 3 | Useful GDB commands for my `3Q` test on message `https://github.com/barrettotte` 4 | 5 | ## Input Message 6 | 7 | `x/30ub &msg` 8 | 9 | ``` 10 | 30 characters representing `https://github.com/barrettotte` 11 | 12 | 0x?????: 104 116 116 112 115 58 47 47 13 | 0x?????: 103 105 116 104 117 98 46 99 14 | 0x?????: 111 109 47 98 97 114 114 101 15 | 0x?????: 116 116 111 116 116 101 16 | ``` 17 | 18 | ## Padding the Message 19 | 20 | `x/34ub &data_words` 21 | 22 | ``` 23 | 34 bytes = mode + char count indicator + encoded message 24 | 25 | 0x?????: 65 230 135 71 71 7 51 162 26 | 0x?????: 242 246 118 151 70 135 86 34 27 | 0x?????: 230 54 246 210 246 38 23 39 28 | 0x?????: 38 87 71 70 247 71 70 80 29 | 0x?????: 236 17 30 | ``` 31 | 32 | ## Create the Message Polynomial 33 | 34 | Message length = 34 / 2 blocks = 17 words per block 35 | 36 | `x/17ub &dw_block` 37 | 38 | ``` 39 | group 1, block 1 = 17 bytes 40 | 41 | 0x?????: 65 230 135 71 71 7 51 162 42 | 0x?????: 242 246 118 151 70 135 86 34 43 | 0x?????: 230 44 | ``` 45 | 46 | `x/18ub &msg_poly` 47 | 48 | ``` 49 | byte 1 = 17 terms, bytes 2-18 = terms array 50 | 51 | 230x^0 + 34x^1 + 86x^2 + 135x^3 + 70x^4 + 151x^5 + 118x^6 52 | + 246x^7 + 242x^8 + 162x^9 + 51x^10 + 7x^11 + 71x^12 + 71x^13 53 | + 135x^14 + 230x^15 + 65x^16 54 | 55 | 0x?????: 17 230 34 86 135 70 151 118 56 | 0x?????: 246 242 162 51 7 71 71 135 57 | 0x?????: 230 65 58 | ``` 59 | 60 | ## Create the Generator Polynomial 61 | 62 | Verifying debug outputs from [../jupyter/lunch-and-learn.ipynb](../jupyter/lunch-and-learn.ipynb) 63 | 64 | - `x/20ub &gen_poly` 65 | - `x/24ub >mpA_poly` 66 | - `x/24ub >mpB_poly` 67 | - `x/24ub &prdA_poly` 68 | - `x/24ub &prdB_poly` 69 | 70 | set breakpoint to `blt _gpoly_loop` and repeat `c` and `x/20ub &gen_poly` in GDB 71 | 72 | ``` 73 | - [x] 00: 2 1 1 74 | - [x] 01: 3 2 3 1 75 | - [x] 02: 4 8 14 7 1 76 | - [x] 03: 5 64 120 54 15 1 77 | - [x] 04: 6 116 147 63 198 31 1 78 | - [x] 05: 7 38 227 32 218 1 63 1 79 | - [x] 06: 8 117 68 11 164 154 122 127 1 80 | - [x] 07: 9 24 200 173 239 54 81 11 255 1 81 | - [x] 08: 10 37 197 232 164 235 245 158 207 226 1 82 | - [x] 09: 11 193 157 113 95 94 199 111 159 194 216 1 83 | - [x] 10: 12 160 116 144 248 162 219 123 50 163 130 172 1 84 | - [x] 11: 13 97 213 127 92 84 7 31 220 118 67 119 68 1 85 | - [x] 12: 14 120 132 83 43 46 13 52 17 177 17 227 73 137 1 86 | - [x] 13: 15 163 234 210 166 127 195 158 43 151 174 70 114 54 14 1 87 | - [x] 14: 16 26 134 32 151 132 139 105 105 10 74 112 163 111 196 29 1 88 | - [x] 15: 17 59 36 50 98 229 41 65 163 8 30 209 68 189 104 13 59 1 89 | - [x] 16: 18 79 99 125 53 85 134 143 41 249 83 197 22 119 120 83 66 119 1 90 | - [x] 17: 19 146 217 67 32 75 173 82 73 220 240 215 199 175 149 113 183 251 239 1 91 | 92 | Wooo! I literally struggled on this for over a week ... :^) 93 | ``` 94 | 95 | `x/20ub &gen_poly` 96 | 97 | ``` 98 | byte 1 = 19 terms, bytes 2-20 = terms array 99 | 100 | 146x^0 + 217x^1 + 67x^2 + 32x^3 + 75x^4 + 173x^5 + 82x^6 + 73x^7 101 | + 220x^8 + 240x^9 + 215x^10 + 199x^11 + 175x^12 + 149x^13 + 113x^14 102 | + 183x^15 + 251x^16 + 239x^17 + 1x^18 103 | 104 | 0x?????: 19 146 217 67 32 75 173 82 105 | 0x?????: 73 220 240 215 199 175 149 113 106 | 0x?????: 183 251 239 1 107 | ``` 108 | 109 | ## Reed-Solomon Error Correction 110 | 111 | Verifying the rest of the Reed-Solomon error correction steps. 112 | 113 | - `x/18ub &msg_poly` 114 | - `x/20ub &gen_poly` 115 | 116 | ### Message Polynomial Prepare 117 | 118 | Ensure lead term doesn't become too small during division. 119 | 120 | - `x/35ub &tmpA_poly` 121 | - `x/64ub &tmpB_poly` 122 | - `x/18ub &msg_poly` 123 | 124 | ``` 125 | (1x^18) * msg_poly 126 | 127 | byte 1 = 35 terms, bytes 2-35 = terms array 128 | 129 | 0x^0 + 0x^1 + 0x^2 + 0x^3 + 0x^4 + 0x^5 + 0x^6 + 0x^7 + 0x^8 + 0x^9 + 0x^10 130 | + 0x^11 + 0x^12 + 0x^13 + 0x^14 + 0x^15 + 0x^16 + 0x^17 + 230x^18 + 34x^19 131 | + 86x^20 + 135x^21 + 70x^22 + 151x^23 + 118x^24 + 246x^25 + 242x^26 + 162x^27 132 | + 51x^28 + 7x290 + 71x^30 + 71x^31 + 135x^32 + 230x^33 + 65x^34 133 | 134 | 0x?????: 35 0 0 0 0 0 0 0 135 | 0x?????: 0 0 0 0 0 0 0 0 136 | 0x?????: 0 0 0 230 34 86 135 70 137 | 0x?????: 151 118 246 242 162 51 7 71 138 | 0x?????: 71 135 230 65 0 0 0 0 139 | 0x?????: 0 0 0 0 0 0 0 0 140 | 0x?????: 0 0 0 0 0 0 0 0 141 | 0x?????: 0 0 0 0 0 0 0 0 142 | ``` 143 | 144 | ### Polynomial Remainder 145 | 146 | poly_remainder(msg_poly, gen_poly) 147 | 148 | - `x/18ub &msg_poly` 149 | - `x/40ub &tmpA_poly` 150 | - `x/40ub &tmpB_poly` 151 | 152 | check `divisor = denominator * mono` => `&tmpC_poly = &gen_poly * &tmp_mono` 153 | 154 | - `x/40ub &tmp_mono` 155 | - `x/20ub &gen_poly` 156 | - `x/35ub &tmpC_poly` 157 | 158 | check `remainder = remainder + divisor` => `&rem_poly = &rem_poly + &tmpC_poly` 159 | 160 | - Byte one should go down each iteration 161 | - `x/35ub &rem_poly` 162 | - `x/35ub &tmpC_poly` 163 | 164 | Verify polynomial remainder calculated correctly: 165 | 166 | `x/19ub &rem_poly` 167 | 168 | **Note: This is for Block 0!** 169 | 170 | ``` 171 | rem_poly = (msg_poly * 1x^18) / (gen_poly) 172 | 173 | byte 1 = 18 terms, bytes 2-18 = terms array 174 | 175 | 202x^0 + 242x^1 + 0x^2 + 131x^3 + 35x^4 + 80x^5 + 198x^6 + 27x^7 + 233x^8 176 | + 174x^9 + 204x^10 + 245x^11 + 42x^12 + 54x^13 + 168x^14 + 17x^15 177 | + 51x^16 + 253x^17 178 | 179 | 0x?????: 18 202 242 0 131 35 80 198 180 | 0x?????: 27 233 174 204 245 42 54 168 181 | 0x?????: 17 51 253 182 | ``` 183 | 184 | ### ECW Block 0 185 | 186 | Verify error correction word block filled correctly. 187 | 188 | - `x/18ub &ecw_block` 189 | - `x/19ub &rem_poly` 190 | 191 | ``` 192 | 18 error correction words (max of 28). 193 | 194 | 0x?????: 253 51 17 168 54 42 245 204 195 | 0x?????: 174 233 27 198 80 35 131 0 196 | 0x?????: 242 202 197 | ``` 198 | 199 | ### ECW Block Loop 200 | 201 | Verify ECW block is copied correctly to ECW array. 202 | 203 | - `x/18ub &ecw_block` 204 | - `x/36ub &ecw_blocks` 205 | 206 | 207 | ``` 208 | ECW block 0 copied; 18 words 209 | 210 | 0x?????: 253 51 17 168 54 42 245 204 211 | 0x?????: 174 233 27 198 80 35 131 0 212 | 0x?????: 242 202 0 0 0 0 0 0 213 | 0x?????: 0 0 0 0 0 0 0 0 214 | 0x?????: 0 0 0 0 215 | ``` 216 | 217 | ``` 218 | ECW block 1 copied; 36 words 219 | 220 | 0x?????: 253 51 17 168 54 42 245 204 221 | 0x?????: 174 233 27 198 80 35 131 0 222 | 0x?????: 242 202 9 107 30 118 21 108 223 | 0x?????: 227 15 117 139 15 178 142 79 224 | 0x?????: 151 162 200 57 225 | ``` 226 | 227 | ### Interleave Data and Error Correction Blocks 228 | 229 | - `x/70ub &payload` 230 | - `x/36ub &ecw_blocks` 231 | - `x/34ub &data_words` 232 | 233 | ``` 234 | interleaved data; 70 words = 2(18 + 17) 235 | 236 | 0x?????: 65 54 230 246 135 210 71 246 237 | 0x?????: 71 38 7 23 51 39 162 38 238 | 0x?????: 242 87 246 71 118 70 151 247 239 | 0x?????: 70 71 135 70 86 80 34 236 240 | 0x?????: 230 17 253 9 51 107 17 30 241 | 0x?????: 168 118 54 21 42 108 245 227 242 | 0x?????: 204 15 174 117 233 139 27 15 243 | 0x?????: 198 178 80 142 35 79 131 151 244 | 0x?????: 0 162 242 200 202 57 245 | ``` 246 | 247 | Add remainder, verify bit size - `x/1uh &pyld_bits` = 567 = (70 * 8) + 7 248 | 249 | ## Build QR Code Matrix 250 | 251 | ### PBM File Header 252 | 253 | `x/1ub &qr_width` = 29 ... used for 29x29 QR code matrix 254 | 255 | Verify PBM width converted to ASCII - `x/11ub &ascii_buff` 256 | 257 | ``` 258 | 10 bytes representing a ten digit ASCII string, 11th byte for null terminator. 259 | 260 | 50 = '2'; 57 = '9'; 48='0' => "0000000029" 261 | 262 | 0x?????: 48 48 48 48 48 48 48 48 263 | 0x?????: 50 57 0 264 | ``` 265 | 266 | Verify PBM length converted to ASCII - `x/11ub &ascii_buff` 267 | ``` 268 | 10 bytes representing a ten digit ASCII string, 11th byte for null terminator. 269 | 270 | 50 = '2'; 57 = '9'; 48='0' => "0000000029" 271 | 272 | 0x?????: 48 48 48 48 48 48 48 48 273 | 0x?????: 50 57 0 274 | ``` 275 | 276 | Verify PBM header 2nd line - `x/6ub &line_buff` 277 | ``` 278 | 6 bytes representing "29 29\n" 279 | 280 | 0x?????: 50 57 32 50 57 10 281 | ``` 282 | 283 | ### PBM Body 284 | 285 | Verify ubyte to ASCII binary string worked - `x/9ub &bin_string`, `x/9ub &aub2b_buf` 286 | 287 | ## Misc 288 | 289 | From this point on its a lot of visual checking. 290 | 291 | `x/567ub &data_bin` 292 | 293 | `x/9ub &fmt_hi` 294 | 295 | ``` 296 | "00110101" 297 | 298 | 0x?????: 48 48 49 49 48 49 48 49 299 | 0x?????: 0 300 | ``` 301 | 302 | `x/9ub &fmt_lo` 303 | 304 | ``` 305 | "01011111" 306 | 307 | 0x?????: 48 49 48 49 49 49 49 49 308 | 0x?????: 0 309 | ``` -------------------------------------------------------------------------------- /docs/python/qrcode.py: -------------------------------------------------------------------------------- 1 | # scratchpad program to make a QR code from scratch. 2 | # 3 | # this looks really bad because I'm trying to keep it as simple as possible 4 | # for when I port this to ARM assembly. 5 | # 6 | # reference: 7 | # - https://www.thonky.com/qr-code-tutorial/ 8 | # - Reed Solomon Encoding (Computerphile) https://www.youtube.com/watch?v=fBRMaEAFLE0 9 | # - https://www.youtube.com/watch?v=Ct2fyigNgPY 10 | 11 | from PIL import Image 12 | import math 13 | import galois 14 | 15 | # mode indicators 16 | MODE_NUMERIC = 1 # 0001 17 | MODE_ALPHANUM = 2 # 0010 18 | MODE_BYTE = 4 # 0100 19 | MODE_KANJI = 8 # 1000 20 | MODE_ECI = 7 # 0111 21 | 22 | # error correction level bits 23 | ERROR_L = 1 # 01; low 24 | ERROR_M = 0 # 00; medium 25 | ERROR_Q = 3 # 11; quartile 26 | ERROR_H = 2 # 10; high 27 | 28 | # https://www.thonky.com/qr-code-tutorial/character-capacities 29 | BYTE_MODE_CAPACITY_LOOKUP = [ 30 | # L, M, Q, H 31 | [0, 0, 0, 0], # (one-indexing) 32 | [17, 14, 11, 7], # 1 33 | [32, 26, 20, 14], # 2 34 | [53, 42, 32, 24], # 3 35 | [78, 62, 46, 34], # 4 36 | [106, 84, 60, 44], # 5 37 | # and so on...to 40 38 | ] 39 | 40 | # https://www.thonky.com/qr-code-tutorial/error-correction-table 41 | EC_CONFIG_LOOKUP = [ 42 | [], # L M Q H 43 | [[19, 7, 1, 19, 0, 0], [16, 10, 1, 16, 0, 0], [13, 13, 1, 13, 0, 0], [9, 17, 1, 9, 0, 0]], # 1 44 | [[34, 10, 1, 34, 0, 0], [28, 16, 1, 28, 0, 0], [22, 22, 1, 22, 0, 0], [16, 28, 1, 16, 0, 0]], # 2 45 | [[55, 15, 1, 55, 0, 0], [44, 26, 1, 44, 0, 0], [34, 18, 2, 17, 0, 0], [26, 22, 2, 13, 0, 0]], # 3 46 | [[80, 20, 1, 80, 0, 0], [64, 18, 2, 32, 0, 0], [48, 26, 2, 24, 0, 0], [36, 16, 4, 9, 0, 0]], # 4 47 | [[108, 26, 1, 108, 0, 0], [86, 24, 2, 43, 0, 0], [62, 18, 2, 15, 2, 16], [46, 22, 2, 11, 2, 12]], # 5 48 | # and so on...to 40 49 | ] 50 | 51 | # https://www.thonky.com/qr-code-tutorial/format-version-tables 52 | VERSION_INFO_LOOKUP = [ 53 | '', '', '', '', '', '', # starts at version 7 w/one indexing 54 | '000111110010010100', '001000010110111100', '001001101010011001', '001010010011010011', # 7-10 55 | '001011101111110110', '001100011101100010', '001101100001000111', '001110011000001101', # 11-14 56 | '001111100100101000', '010000101101111000', '010001010001011101', '010010101000010111', # 15-18 57 | '010011010100110010', '010100100110100110', '010101011010000011', '010110100011001001', # 19-22 58 | '010111011111101100', '011000111011000100', '011001000111100001', '011010111110101011', # 23-26 59 | '011011000010001110', '011100110000011010', '011101001100111111', '011110110101110101', # 27-30 60 | '011111001001010000', '100000100111010101', '100001011011110000', '100010100010111010', # 31-34 61 | '100011011110011111', '100100101100001011', '100101010000101110', '100110101001100100', # 35-38 62 | '100111010101000001', '101000110001101001' # 39-40 63 | ] 64 | 65 | # https://www.thonky.com/qr-code-tutorial/format-version-tables 66 | FMT_INFO_LOOKUP = [ 67 | [ # L 68 | '111011111000100', '111001011110011', '111110110101010', '111100010011101', 69 | '110011000101111', '110001100011000', '110110001000001', '110100101110110' 70 | ], 71 | [ # M 72 | '101010000010010', '101000100100101', '101111001111100', '101101101001011', 73 | '100010111111001', '100000011001110', '100111110010111', '100101010100000' 74 | ], 75 | [ 76 | # Q 77 | '011010101011111', '011000001101000', '011111100110001', '011101000000110', 78 | '010010010110100', '010000110000011', '010111011011010', '010101111101101' 79 | ], 80 | [ # H 81 | '001011010001001', '001001110111110', '001110011100111', '001100111010000', 82 | '000011101100010', '000001001010101', '000110100001100', '000100000111011' 83 | ] 84 | ] 85 | 86 | REMAINDER_LOOKUP = [ 87 | 0, # one indexing 88 | 0, 7, 7, 7, 7, 7, 0, 0, 0, 0, 89 | 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 90 | 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 91 | 3, 3, 3, 3, 0, 0, 0, 0, 0, 0 92 | ] 93 | 94 | # https://www.thonky.com/qr-code-tutorial/alignment-pattern-locations 95 | ALIGNMENT_PATTERN_LOOK = [ 96 | [], # one indexing 97 | [], # version 1 has no alignment 98 | [6, 18, 0, 0, 0, 0, 0], [6, 22, 0, 0, 0, 0, 0], # 2, 3 99 | [6, 26, 0, 0, 0, 0, 0], [6, 30, 0, 0, 0, 0, 0], # 4, 5 100 | [6, 34, 0, 0, 0, 0, 0], [6, 22, 38, 0, 0, 0, 0], # 6, 7 101 | [6, 24, 42, 0, 0, 0, 0], [6, 26, 46, 0, 0, 0, 0], # 8, 9 102 | [6, 28, 50, 0, 0, 0, 0], [6, 30, 54, 0, 0, 0, 0], # 10, 11 103 | [6, 32, 58, 0, 0, 0, 0], [6, 34, 62, 0, 0, 0, 0], # 12, 13 104 | [6, 26, 46, 66, 0, 0, 0], [6, 26, 48, 70, 0, 0, 0], # 14, 15 105 | # and so on to 40... 106 | ] 107 | 108 | # adjust indices for lookup tables based on error level 109 | # fixes issue with LMQH ordering 110 | ERROR_IDX_TO_LOOKUP = [1, 0, 3, 2] 111 | 112 | 113 | # utility to build string of byte/bit size 114 | def byte_size_str(d): 115 | size = len(d) 116 | return f"{size} bit(s) => {size // 8} byte(s), {size % 8} bit(s)" 117 | 118 | 119 | # is test between low and high (inclusive)? 120 | def is_between(low, high, test): 121 | return test >= low and test <= high 122 | 123 | 124 | # get error correction config from lookup table 125 | def get_ec_config(version, err_lvl): 126 | return EC_CONFIG_LOOKUP[version][ERROR_IDX_TO_LOOKUP[err_lvl]] 127 | 128 | 129 | # find version to use based on payload size and error correction 130 | def get_version(size, err_lvl): 131 | err_idx = ERROR_IDX_TO_LOOKUP[err_lvl] 132 | for col, row in enumerate(BYTE_MODE_CAPACITY_LOOKUP): 133 | if row[err_idx] > size: 134 | return col 135 | raise Exception("couldn't find version") 136 | 137 | 138 | # determine character count indicator 139 | def get_count(size, version, mode): 140 | if int(mode, 2) == MODE_BYTE: 141 | if is_between(1, 9, version): 142 | word_size = 8 143 | elif is_between(10, 26, version): 144 | word_size = 16 145 | elif is_between(27, 40, version): 146 | word_size = 16 147 | else: 148 | raise Exception("Invalid version") 149 | else: 150 | raise Exception("Only byte mode implemented!") 151 | return int_to_bits(size, word_size) 152 | 153 | 154 | # convert integer to bits 155 | def int_to_bits(i, word_size): 156 | return bin(int(hex(i), 16))[2:].zfill(word_size) 157 | 158 | 159 | # encode string to byte mode format - https://www.thonky.com/qr-code-tutorial/byte-mode-encoding 160 | # UTF-8 encode -> hex bytes -> 8-bit binary 161 | def encode_byte_mode(s): 162 | as_hex = [c.encode('utf-8').hex() for c in s] 163 | return [bin(int(byte, 16))[2:].zfill(8) for byte in as_hex] 164 | 165 | 166 | # draw square in qr matrix 167 | def draw_square(qr_mat, qr_size, x, y, n, c): 168 | for i in range(n): 169 | for j in range(n): 170 | dx = (x + i) 171 | dy = (y + j) 172 | if dx < qr_size and dy < qr_size and dx >= 0 and dy >= 0: 173 | qr_mat[(dy * qr_size) + dx] = c 174 | return qr_mat 175 | 176 | 177 | # place finder pattern in QR code matrix with left corner at (x,y) 178 | def place_finder(qr_mat, qr_size, x, y): 179 | qr_mat = draw_square(qr_mat, qr_size, x - 1, y - 1, 9, 3) # separator 180 | qr_mat = draw_square(qr_mat, qr_size, x, y, 7, 4) # outer 181 | qr_mat = draw_square(qr_mat, qr_size, x + 1, y + 1, 5, 3) # inner 182 | qr_mat = draw_square(qr_mat, qr_size, x + 2, y + 2, 3, 4) # center 183 | return qr_mat 184 | 185 | 186 | # traverse data and place using zigzag pattern 187 | def zigzag_data(qr_mat, qr_size, data): 188 | x = qr_size - 1 189 | y = qr_size - 1 190 | data_idx = 0 191 | zig = True 192 | up = True 193 | 194 | while data_idx < len(data): 195 | # reached edge, bounce back 196 | if y == qr_size: 197 | up = not up 198 | x -= 2 199 | zig = True 200 | y = qr_size - 1 201 | elif y < 0: 202 | up = not up 203 | x -= 2 204 | zig = True 205 | y = 0 206 | next_mod = qr_mat[(y * qr_size) + x] 207 | 208 | # zig zag past existing structure 209 | if next_mod == 2: 210 | qr_mat[(y * qr_size) + x] = int(data[data_idx]) 211 | data_idx += 1 212 | 213 | # zig or zag 214 | if zig: 215 | x -= 1 216 | else: 217 | y += 1 if not up else -1 218 | x += 1 219 | zig = not zig 220 | 221 | # skip over timing patterns 222 | if x == 6: 223 | y -= 1 224 | x -= 1 225 | return qr_mat 226 | 227 | 228 | 229 | # print matrix to console 230 | def print_matrix(qr_mat, qr_size): 231 | icons = ['⬜', '⬛', '❓', '❌', '❌'] 232 | for i in range(qr_size): 233 | for j in range(qr_size): 234 | module = abs(qr_mat[i * qr_size + j]) 235 | if module >= 0 and module <= len(icons) - 1: 236 | print(icons[module], end='') 237 | else: 238 | raise Exception(f"Unknown value '{module}'") 239 | print('') 240 | print('') 241 | 242 | 243 | # generate a matrix for each mask 244 | # https://www.thonky.com/qr-code-tutorial/mask-patterns 245 | def get_masks(qr_size): 246 | masks = [] 247 | 248 | # mask 0 249 | mask = [0] * (qr_size ** 2) 250 | for y in range(qr_size): 251 | for x in range(qr_size): 252 | mask[(y * qr_size) + x] = 1 if ((x + y) % 2) == 0 else 0 253 | masks.append(mask) 254 | 255 | # mask 1 256 | mask = [0] * (qr_size ** 2) 257 | for y in range(qr_size): 258 | for x in range(qr_size): 259 | mask[(y * qr_size) + x] = 1 if (y % 2) == 0 else 0 260 | masks.append(mask) 261 | 262 | # mask 2 263 | mask = [0] * (qr_size ** 2) 264 | for y in range(qr_size): 265 | for x in range(qr_size): 266 | mask[(y * qr_size) + x] = 1 if (x % 3) == 0 else 0 267 | masks.append(mask) 268 | 269 | # mask 3 270 | mask = [0] * (qr_size ** 2) 271 | for y in range(qr_size): 272 | for x in range(qr_size): 273 | mask[(y * qr_size) + x] = 1 if ((x + y) % 3) == 0 else 0 274 | masks.append(mask) 275 | 276 | # mask 4 277 | mask = [0] * (qr_size ** 2) 278 | for y in range(qr_size): 279 | for x in range(qr_size): 280 | mask[(y * qr_size) + x] = 1 if ((x // 3 + y // 2) % 2) == 0 else 0 281 | masks.append(mask) 282 | 283 | # mask 5 284 | mask = [0] * (qr_size ** 2) 285 | for y in range(qr_size): 286 | for x in range(qr_size): 287 | mask[(y * qr_size) + x] = 1 if ((x * y % 2) + (x * y % 3)) == 0 else 0 288 | masks.append(mask) 289 | 290 | # mask 6 291 | mask = [0] * (qr_size ** 2) 292 | for y in range(qr_size): 293 | for x in range(qr_size): 294 | mask[(y * qr_size) + x] = 1 if (((x * y) % 2 + x * y % 3) % 2) == 0 else 0 295 | masks.append(mask) 296 | 297 | # mask 7 298 | mask = [0] * (qr_size ** 2) 299 | for y in range(qr_size): 300 | for x in range(qr_size): 301 | mask[(y * qr_size) + x] = 1 if (((x + y) % 2 + x * y % 3) % 2) == 0 else 0 302 | masks.append(mask) 303 | 304 | return masks 305 | 306 | 307 | # apply mask to QR matrix (not affecting non-function modules) 308 | def apply_mask(mask, qr_mat, qr_size): 309 | masked = [0] * (qr_size ** 2) 310 | for y in range(qr_size): 311 | for x in range(qr_size): 312 | idx = (y * qr_size) + x 313 | module = qr_mat[idx] 314 | 315 | # 3-4 are reserved 316 | if module < 2: 317 | masked[idx] = module ^ mask[idx] 318 | elif module == 3: 319 | masked[idx] = 0 # swap out reserved '0' 320 | elif module == 4: 321 | masked[idx] = 1 # swap out reserved '1' 322 | return masked 323 | 324 | 325 | # calculate format bits 326 | def calc_fmt_bits(err_lvl, mask_idx): 327 | fmt_bits = int_to_bits(err_lvl, 2) + int_to_bits(mask_idx, 3) 328 | err_bits = (fmt_bits + ('0' * 10)).lstrip('0') 329 | 330 | # calculate error correction bits 331 | while len(err_bits) >= 11: 332 | # build generator polynomial 333 | res = '' 334 | gen_bits = '10100110111' # $x^{10}+x^8+x^5+x^4+x^2+x+1$ 335 | 336 | # pad generator polynomial to match length of format bits 337 | while len(gen_bits) != len(err_bits): 338 | gen_bits += '0' 339 | 340 | # XOR generator bits with format string 341 | for i in range(len(gen_bits)): 342 | res += str(int(gen_bits[i]) ^ int(err_bits[i])) 343 | err_bits = res.lstrip('0') 344 | 345 | # repad to 10-bits 346 | while len(err_bits) < 10: 347 | err_bits = '0' + err_bits 348 | 349 | # combine format and error correction bits 350 | fmt_bits += err_bits 351 | final_fmt_bits = '' 352 | for i in range(len(fmt_bits)): 353 | final_fmt_bits += str(int(fmt_bits[i]) ^ int('101010000010010'[i])) 354 | 355 | lookup_fmt = FMT_INFO_LOOKUP[ERROR_IDX_TO_LOOKUP[err_lvl]][mask_idx] 356 | assert final_fmt_bits == lookup_fmt 357 | 358 | return final_fmt_bits 359 | 360 | 361 | # add format bits adjacent to finders 362 | def add_format_bits(qr_mat, qr_size, fmt_bits): 363 | # break up format bits to place near finder patterns 364 | high_bits = fmt_bits[0:7] # MSB=0 365 | low_bits = fmt_bits[8:15] # LSB=14 366 | 367 | # top left format bits 368 | x = 0 369 | y = 8 370 | for i in range(len(high_bits)): 371 | if i == 6: 372 | x += 1 # skip vertical timing 373 | qr_mat[(y * qr_size) + x] = int(high_bits[i]) 374 | x += 1 375 | x = 8 376 | y = 7 377 | for j in range(len(low_bits)): 378 | if j == 1: 379 | y -= 1 # skip horizontal timing 380 | qr_mat[(y * qr_size) + x] = int(low_bits[j]) 381 | y -= 1 382 | 383 | # top right format bits 384 | x = qr_size - 7 385 | y = 8 386 | for i in range(len(low_bits)): 387 | qr_mat[(y * qr_size) + x + i] = int(low_bits[i]) 388 | 389 | # bottom left format bits 390 | x = 8 391 | y = qr_size - 1 392 | for i in range(len(low_bits)): 393 | qr_mat[((y - i) * qr_size) + x] = int(high_bits[i]) 394 | 395 | return qr_mat 396 | 397 | 398 | # Evaluate penalty for rule 1: group of 5 or more same-colored modules in a row or col 399 | def eval_rule_1(masked, qr_size): 400 | row_count = 0 401 | col_count = 0 402 | prev_row = 0 403 | prev_col = 0 404 | penalty_horizontal = 0 405 | penalty_vertical = 0 406 | module = -1 407 | 408 | for y in range(qr_size): 409 | if module == prev_col: 410 | col_count += 1 411 | else: 412 | col_count = 0 413 | 414 | if col_count == 5: 415 | penalty_vertical += 3 416 | elif col_count > 5: 417 | penalty_vertical += 1 418 | 419 | for x in range(qr_size): 420 | module = masked[(y * qr_size) + x] 421 | if module == prev_row: 422 | row_count += 1 423 | else: 424 | row_count = 0 425 | 426 | if row_count == 5: 427 | penalty_horizontal += 3 428 | elif row_count > 5: 429 | penalty_horizontal += 1 430 | prev_row = module 431 | row_count = 0 432 | prev_col = 0 433 | return penalty_horizontal + penalty_vertical 434 | 435 | 436 | # Evaluate penalty for rule 2: 2x2 area of same colored modules 437 | def eval_rule_2(masked, qr_size): 438 | penalty = 0 439 | for x in range(qr_size): 440 | for y in range(qr_size): 441 | idx = (y * qr_size) + x 442 | if (x < qr_size - 1) and (y < qr_size - 1) and (y > 0): 443 | is_square = True 444 | test = masked[idx] # top left 445 | 446 | if test != masked[idx + 1]: 447 | is_square = False # top right 448 | elif test != masked[idx + qr_size]: 449 | is_square = False # bottom left 450 | elif test != masked[idx + qr_size + 1]: 451 | is_square = False # bottom right 452 | 453 | if is_square: 454 | penalty += 3 455 | return penalty 456 | 457 | 458 | # Evaluate penalty for rule 3: occurrences of 10111010000 and 00001011101 in rows/cols 459 | def eval_rule_3(masked, qr_size): 460 | return 0 # skipping this...could not get it working for some reason... 461 | 462 | 463 | # Evaluate penalty for rule 4: ratio of light to dark modules 464 | def eval_rule_4(masked, qr_size): 465 | white = 0 466 | black = 0 467 | for x in range(qr_size): 468 | for y in range(qr_size): 469 | idx = (y * qr_size) + x 470 | if masked[idx] == 1: 471 | black += 1 472 | else: 473 | white += 1 474 | total = white + black 475 | return ((abs(black * 20 - total * 10) + total - 1) // (total - 1)) * 10 476 | 477 | 478 | # apply each mask and use penalty to determine most ideal 479 | def apply_ideal_mask(qr_mat, qr_size, err_lvl): 480 | masks = get_masks(qr_size) 481 | min_penalty = 99999999 482 | ideal_mask_idx = -1 483 | 484 | for mask_idx, mask in enumerate(masks): 485 | penalty = 0 486 | fmt_bits = calc_fmt_bits(err_lvl, mask_idx) 487 | masked = add_format_bits(qr_mat, qr_size, fmt_bits) 488 | masked = apply_mask(mask, masked, qr_size) 489 | 490 | penalty_1 = eval_rule_1(masked, qr_size) 491 | penalty_2 = eval_rule_2(masked, qr_size) 492 | penalty_3 = eval_rule_3(masked, qr_size) 493 | penalty_4 = eval_rule_4(masked, qr_size) 494 | penalty += penalty_1 + penalty_2 + penalty_3 + penalty_4 495 | 496 | # print(f"mask {mask_idx} rule 1 penalty: {penalty_1}") 497 | # print(f"mask {mask_idx} rule 2 penalty: {penalty_2}") 498 | # print(f"mask {mask_idx} rule 3 penalty: {penalty_3}") 499 | # print(f"mask {mask_idx} rule 4 penalty: {penalty_4}") 500 | 501 | if penalty < min_penalty: 502 | min_penalty = penalty 503 | ideal_mask_idx = mask_idx 504 | print(f"mask {mask_idx} has penalty {penalty}") 505 | print(f"ideal mask is mask {ideal_mask_idx}") 506 | 507 | # apply ideal mask 508 | fmt_bits = calc_fmt_bits(err_lvl, ideal_mask_idx) 509 | masked = apply_mask(masks[ideal_mask_idx], qr_mat, qr_size) 510 | final_mat = add_format_bits(masked, qr_size, fmt_bits) 511 | return final_mat 512 | 513 | 514 | # add 4 module wide area of light modules (quiet zone) 515 | def add_quiet_zone(qr_mat, qr_size): 516 | quieted = [0] * ((qr_size + 8) ** 2) 517 | for x in range(0, qr_size): 518 | for y in range(0, qr_size): 519 | module = qr_mat[(y * qr_size) + x] 520 | quieted[((y + 4) * (qr_size + 8)) + (x + 4)] = module 521 | return quieted 522 | 523 | 524 | # save matrix to file 525 | def mat_to_file(qr_mat, qr_size, file_name): 526 | img = Image.new(mode='1', size=(qr_size, qr_size)) 527 | # ohhh...bits should be reversed... 528 | qr_mat = [not b for b in qr_mat] 529 | 530 | for x in range(qr_size): 531 | for y in range(qr_size): 532 | pixel = qr_mat[(y * qr_size) + x] 533 | img.putpixel((x, y), pixel) 534 | img.save(file_name) 535 | 536 | def main(): 537 | # encode payload 538 | payload = 'https://github.com/barrettotte' 539 | print(f"encoding payload '{payload}'") 540 | encoded = encode_byte_mode(payload) 541 | encoded_len = len(encoded) 542 | 543 | # build segment 0 544 | err_lvl = ERROR_Q 545 | version = get_version(encoded_len, err_lvl) 546 | ec_config = get_ec_config(version, err_lvl) 547 | 548 | mode = int_to_bits(MODE_BYTE, 4) 549 | count = get_count(encoded_len, version, mode) 550 | capacity = ec_config[0] 551 | capacity_bits = capacity * 8 552 | 553 | print(f"size: {encoded_len} byte(s) - char count: {count}") 554 | print(f"version {version} with max capacity of {capacity} byte(s)", end='') 555 | print(f" or {capacity_bits} bit(s)") 556 | 557 | # raw with no padding 558 | seg_0 = mode + count + ''.join(encoded) 559 | print("before padding: " + byte_size_str(seg_0)) 560 | 561 | # add terminator of 0's up to four bits if there's room 562 | terminal_bits = 0 563 | while terminal_bits < 4 and len(seg_0) < capacity_bits: 564 | seg_0 += '0' 565 | terminal_bits += 1 566 | 567 | # pad bits to nearest byte 568 | while len(seg_0) % 8 != 0 and len(seg_0 < capacity_bits): 569 | seg_0 += '0' 570 | 571 | # pad bytes to full capacity (alternating 0xEC and 0x11) 572 | use_EC = True 573 | while len(seg_0) < capacity_bits: 574 | seg_0 += int_to_bits(int(0xEC), 8) if use_EC else int_to_bits(int(0x11), 8) 575 | use_EC = not use_EC 576 | 577 | print(f'after padding: {byte_size_str(seg_0)}') 578 | print("seg_0: {0:0>4X}".format(int(seg_0, 2))) 579 | 580 | # https://www.thonky.com/qr-code-tutorial/error-correction-coding 581 | code_words = [seg_0[i: i + 8] for i in range(0, len(seg_0), 8)] # (bytes) 582 | print(f'total word(s) = {len(code_words)}') 583 | 584 | # split into up to two groups with various blocks of EC words 585 | # https://www.thonky.com/qr-code-tutorial/error-correction-table 586 | g1_blocks = [] # only two groups 587 | g2_blocks = [] # so we can be lazy 588 | 589 | # map error correction confi 590 | ecw_per_block = ec_config[1] 591 | g1_block_count = ec_config[2] 592 | g1_data_block_size = ec_config[3] 593 | g2_block_count = ec_config[4] 594 | g2_data_block_size = ec_config[5] 595 | 596 | print(ec_config) 597 | print(f"error correction code words per block: {ecw_per_block}") 598 | print(f"data code words per group 1 block: {g1_data_block_size}") 599 | print(f"data code words per group 2 block: {g2_data_block_size}") 600 | 601 | # build group 1 602 | cw_idx = 0 603 | while len(g1_blocks) < g1_block_count: 604 | to_idx = g1_data_block_size * (len(g1_blocks) + 1) 605 | g1_blocks.append(code_words[cw_idx: to_idx]) 606 | cw_idx += g1_data_block_size 607 | assert len(g1_blocks) == g1_block_count 608 | 609 | # build group 2 610 | g2_offset = cw_idx 611 | while len(g2_blocks) < g2_block_count: 612 | to_idx = (g2_data_block_size * (len(g2_blocks) + 1)) + g2_offset 613 | g2_blocks.append(code_words[cw_idx:to_idx]) 614 | cw_idx += g2_data_block_size 615 | assert len(g2_blocks) == g2_block_count 616 | 617 | # error correction 618 | # 619 | # bit-wise modulo 2 arithmetic 620 | # byte-wise 100011101 (285) arithmetic 621 | # -> Galois field 2^8 == GF(256) 622 | # -> x^8 + x^4 + x^3 + x^2 + 1 623 | # 624 | # log/antilog and GF(256) 625 | # 2**2 * 2**8 = 2**(2+8) = 2**10 626 | # if exponent > 256 add modulo 255 627 | # -> 2**170 * 2**164 = 2**(170+164) = 2**334 == 2**(334%255)=2**79 628 | # 629 | # message polynomial - data codewords made in encoding step 630 | # used as coefficients. Ex: 25,218,35 => $25x^2+218x+35$ 631 | # 632 | # generator polynomial - $(x-\alpha^0)\ldots(x-\alpha^{n-1})$ 633 | # where n is the number of error correction keywords that must be generated 634 | # 635 | # https://www.thonky.com/qr-code-tutorial/error-correction-table 636 | # https://www.thonky.com/qr-code-tutorial/show-division-steps 637 | # https://www.thonky.com/qr-code-tutorial/generator-polynomial-tool?degree=18 638 | 639 | print('') 640 | ec_blocks = [] 641 | 642 | # group 1 error correction 643 | for i, block in enumerate(g1_blocks): 644 | print(f'block {i}') 645 | print([int(word, 2) for word in block]) 646 | 647 | # translate block of data to message polynomial 648 | msg_poly = galois.block_to_poly(block) 649 | print(f"\nmsg = {msg_poly}\n") 650 | 651 | # build generator polynomial 652 | gen_poly = galois.get_gen_poly(ecw_per_block) 653 | print(f"gen = {gen_poly}\n") 654 | 655 | # ensure lead term doesn't become too small during division 656 | mono = galois.new_monomial(1, ecw_per_block) 657 | msg_poly = galois.poly_mul(msg_poly, mono) 658 | print(f"msg * {mono} = {msg_poly}\n") 659 | 660 | # find error correction words via polynomial long division 661 | rem_poly = galois.poly_remainder(msg_poly, gen_poly) 662 | print(f"msg / gen = {rem_poly}\n") 663 | ec_block = [int_to_bits(word, 8) for word in rem_poly.terms[::-1]] 664 | print(f"{len(ec_block)} error correction words:\n{[hex(int(word, 2)) for word in ec_block]}") 665 | assert len(ec_block) == ecw_per_block 666 | ec_blocks.append(ec_block) 667 | print('') 668 | 669 | # TODO: group 2 670 | 671 | # interleave data and error correction blocks 672 | data = [] 673 | for i in range(g1_data_block_size): 674 | for j in range(len(g1_blocks)): 675 | data.append(g1_blocks[j][i]) 676 | for i in range(ecw_per_block): 677 | for j in range(len(ec_blocks)): 678 | data.append(ec_blocks[j][i]) 679 | print(f"interleaved data - {len(data)} word(s):\n{[hex(int(x, 2)) for x in data]}") 680 | 681 | # add remainder bits 682 | remainder_bits = REMAINDER_LOOKUP[version] 683 | print(f"Adding {remainder_bits} remainder bit(s)") 684 | data = ''.join([x for x in data]) + ('0' * remainder_bits) 685 | print(f"data - ({len(data)}) bit(s)") 686 | print(data + '\n\n') 687 | 688 | # populate QR matrix 689 | qr_size = ((version - 1) * 4) + 21 690 | qr_mat = [2] * (qr_size ** 2) # flat list (so asm port is easier) 691 | print(f"QR matrix : {qr_size} x {qr_size} - {len(qr_mat)} module(s)\n") 692 | 693 | # place reserved areas - finders overlay on top of these 694 | # version 7 and greater requires more reserved area...lets just skip it 695 | if version >= 7: 696 | raise Exception("QR versions greater than 6 are not supported") 697 | qr_mat = draw_square(qr_mat, qr_size, 0, 0, 9, 3) # top left 698 | qr_mat = draw_square(qr_mat, qr_size, (qr_size - 7) - 1, 0, 9, 3) # top right 699 | qr_mat = draw_square(qr_mat, qr_size, 0, (qr_size - 7), 9, 3) # bottom left 700 | 701 | # place timing patterns 702 | is_fill = True 703 | for i in range(qr_size): 704 | c = 4 if is_fill else 3 705 | qr_mat[(i) * qr_size + (6)] = c 706 | is_fill = not is_fill 707 | is_fill = True 708 | for j in range(qr_size): 709 | c = 4 if is_fill else 3 710 | qr_mat[(6) * qr_size + (j)] = c 711 | is_fill = not is_fill 712 | 713 | # place finders 714 | qr_mat = place_finder(qr_mat, qr_size, 0, 0) # top left 715 | qr_mat = place_finder(qr_mat, qr_size, 0, (qr_size - 7)) # top right 716 | qr_mat = place_finder(qr_mat, qr_size, (qr_size - 7), 0) # bottom left 717 | 718 | # place alignment pattern 719 | if version > 1: 720 | pat = ALIGNMENT_PATTERN_LOOK[version] 721 | qr_mat = draw_square(qr_mat, qr_size, pat[1] - 2, pat[1] - 2, 5, 4) 722 | qr_mat = draw_square(qr_mat, qr_size, pat[1] - 1, pat[1] - 1, 3, 3) 723 | qr_mat = draw_square(qr_mat, qr_size, pat[1], pat[1], 1, 4) 724 | 725 | # place dark module 726 | qr_mat[((4 * version) + 9) * qr_size + (8)] = 4 727 | 728 | # place data zigzag pattern 729 | qr_mat = zigzag_data(qr_mat, qr_size, data) 730 | print_matrix(qr_mat, qr_size) 731 | 732 | # determine and apply ideal mask 733 | qr_mat = apply_ideal_mask(qr_mat, qr_size, err_lvl) 734 | print('') 735 | print_matrix(qr_mat, qr_size) 736 | 737 | # add quiet zone and save to file 738 | qr_mat = add_quiet_zone(qr_mat, qr_size) 739 | mat_to_file(qr_mat, qr_size + 8, './qrcode.png') 740 | 741 | if __name__ == '__main__': main() 742 | -------------------------------------------------------------------------------- /main.s: -------------------------------------------------------------------------------- 1 | // Generate a byte-mode QR Code (v1-4) 2 | // 3 | // Program Summary: 4 | // - Get message, message length, and error correction level from stdin 5 | // - Setup QR version, capacities, etc from program arguments 6 | // - Pad message and split into groups/blocks based on config 7 | // - Perform Reed-Solomon error correction on each block of data 8 | // - Interleave data and error correction into a payload block 9 | // - Build QR code matrix using payload block and QR code specifications 10 | // - Output QR code matrix to image file 11 | 12 | .include "const.inc" 13 | 14 | .global _start 15 | 16 | // constants 17 | .equ MODE, 0b0100 // byte encoding mode 18 | .equ MAX_VERSION, 3 // max version supported (1-4); zero indexed 19 | .equ MAX_DATA_CAP, 80 // max data capacity (message) (v4-L) 20 | .equ MAX_G1B, 4 // max blocks in group 1 (v4-H) 21 | .equ MAX_DWB, 80 // max data words per block (v4-L) 22 | .equ MAX_ECWB, 28 // max error correction words per block (v2-H) 23 | .equ MAX_PAYLOAD, 255 // max size of payload to transform into QR code 24 | .equ MAX_QR_SIZE, 1696 // max modules in QR matrix; ((V*4)+21+8)^2, to next word 25 | 26 | .data 27 | 28 | // error Messages 29 | err_01: .string "Invalid error correction level.\n" 30 | .equ err_01_len, (.-err_01) 31 | err_02: .string "Only QR versions 1-4 are supported.\n" 32 | .equ err_02_len, (.-err_02) 33 | err_03: .string "Usage: qrcode msg err_lvl\n" 34 | .equ err_03_len, (.-err_03) 35 | err_04: .string "Message size too large.\n" 36 | .equ err_04_len, (.-err_04) 37 | 38 | // lookup tables 39 | tbl_eclvl: // error correction level lookup 40 | .byte 1, 0, 3, 2 // L, M, Q, H 41 | 42 | tbl_version: // L, M, Q, H // version lookup 43 | .byte 17, 14, 11, 7 // v1 44 | .byte 32, 26, 20, 14 // v2 45 | .byte 53, 42, 32, 24 // v3 46 | .byte 78, 62, 46, 34 // v4 47 | 48 | tbl_ecprops: // error correction config lookup 49 | // 0: data capacity in bytes 50 | // 1: error correction words per block 51 | // 2: blocks in group 1 52 | // 3: data words in each group 1 block 53 | // ********* 54 | .byte 19, 7, 1, 19 // 0: v1-L 55 | .byte 16, 10, 1, 16 // 1: v1-M 56 | .byte 13, 13, 1, 13 // 2: v1-Q 57 | .byte 9, 17, 1, 9 // 3: v1-H 58 | .byte 34, 10, 1, 34 // 4: v2-L 59 | .byte 28, 16, 1, 28 // 5: v2-M 60 | .byte 22, 22, 1, 22 // 6: v2-Q 61 | .byte 16, 28, 1, 16 // 7: v2-H 62 | .byte 55, 15, 1, 55 // 8: v3-L 63 | .byte 44, 26, 1, 44 // 9: v3-M 64 | .byte 34, 18, 2, 17 // 10: v3-Q 65 | .byte 26, 22, 2, 13 // 11: v3-H 66 | .byte 80, 20, 1, 80 // 12: v4-L 67 | .byte 64, 18, 2, 32 // 13: v4-M 68 | .byte 48, 26, 2, 24 // 14: v4-Q 69 | .byte 36, 16, 4, 9 // 15: v4-H 70 | 71 | tbl_rem: .byte 0, 7, 7, 7 // remainder lookup (v1-4) 72 | 73 | // variables 74 | msg: .space MAX_DATA_CAP // message (from stdin) 75 | msg_len: .space 1 // message length 76 | out_file: .asciz "qrcode.pbm" // (10 bytes) 77 | 78 | version: .space 1 // QR code version (zero indexed) 79 | eclvl_idx: .space 1 // error correction level index (L,M,Q,H) 80 | eclvl: .space 1 // error correction level value (1,0,3,2) 81 | ecprop_idx: .space 1 // error correction properties index 82 | data_cap: .space 1 // max capacity for data words 83 | ecwb_cap: .space 1 // error correction words per block 84 | g1b_cap: .space 1 // number of blocks in group 1 85 | g1bw_cap: .space 1 // data words in each group 1 block 86 | count_ind: .space 1 // character count indicator byte 87 | 88 | pyld_size: .space 1 // calculated size of payload 89 | pyld_bits: .space 2 // payload size in bits 90 | qr_width: .space 1 // width of QR matrix 91 | bin_str: .space 8+1 // temp for byte to binary ASCII string convert 92 | 93 | data_words: .space MAX_DATA_CAP // all data words 94 | dw_block: .space MAX_DWB // data word block 95 | ecw_blocks: .space MAX_ECWB*MAX_G1B // all error correction blocks 96 | ecw_block: .space MAX_ECWB // error correction words block 97 | payload: .space MAX_PAYLOAD // payload of data and error correction blocks 98 | qr_mat: .space MAX_QR_SIZE, ASCII_TWO // QR code matrix; ASCII bytes 99 | data_bin: .space MAX_PAYLOAD*8 // payload converted to binary ASCII string 100 | 101 | .text 102 | 103 | _start: // ***** program entry point ***** 104 | ldr r0, [sp] // get argv[0] = argc 105 | cmp r0, #3 // check argc 106 | blt bad_argc // if (argc < 3) 107 | 108 | ldr r1, [sp, #8] // get argv[1]; message 109 | ldr r2, =msg // pointer to message 110 | mov r3, #0 // i = 0 111 | _msg_loop: 112 | ldrb r4, [r1, r3] // c = argv[i] 113 | cmp r4, #0x00 // check loop condition 114 | beq _msg_done // done copy 115 | strb r4, [r2, r3] // msg[i] = c 116 | add r3, r3, #1 // i++ 117 | b _msg_loop // while (1) 118 | _msg_done: 119 | cmp r3, #MAX_DATA_CAP // check if message too large 120 | bgt bad_msglen // if (msg_len > MAX) 121 | 122 | ldr r4, =msg_len // pointer to message length 123 | strb r3, [r4] // save message length 124 | 125 | set_eclvl: // ***** set error correction level ***** 126 | ldr r5, [sp, #12] // get argv[2] 127 | ldrb r5, [r5] // error level ASCII 128 | cmp r5, #51 // check '3' 129 | bgt bad_eclvl // if (argv[2] > 51) 130 | cmp r5, #48 // check '0' 131 | blt bad_eclvl // if (argv[2] < 48) 132 | 133 | ldr r1, =eclvl_idx // pointer to error correction index 134 | sub r5, r5, #48 // convert ASCII number to decimal 135 | sub r5, r5, #1 // zero index error correction level 136 | strb r5, [r1] // save error correction index 137 | 138 | ldr r1, =tbl_eclvl // pointer to error correction table 139 | ldrb r0, [r1, r5] // lookup error correction value 140 | ldr r1, =eclvl // pointer to error correction level 141 | strb r0, [r1] // save error correction level 142 | 143 | find_version: // ***** find QR version ***** 144 | mov r0, #0 // version = 0 (v1) 145 | ldr r1, =eclvl_idx // pointer to error correction index 146 | ldrb r1, [r1] // i = eclvl 147 | ldr r2, =tbl_version // pointer to version table 148 | ldr r3, =msg_len // pointer to message length 149 | ldrb r3, [r3] // load message length 150 | ldr r4, =count_ind // pointer to char count indicator 151 | strb r3, [r4] // save msg_len as char count indicator byte 152 | mov r4, #MAX_VERSION // load max QR version supported 153 | 154 | version_loop: // ***** search version lookup table ***** 155 | ldrb r5, [r2, r1] // msg capacity = tbl_version[(i * 4) + eclvl] 156 | cmp r5, r3 // compare msg capacity to msg_len 157 | bgt set_version // if (msg capacity > msg_len) 158 | 159 | add r0, r0, #1 // version += 1 160 | add r1, r1, #4 // i += 4 161 | cmp r0, r4 // check loop condition 162 | blt version_loop // while (version < MAX_VERSION) 163 | b bad_version // unsupported version encountered 164 | 165 | set_version: // ***** set QR version (zero indexed) ***** 166 | ldr r1, =version // pointer to version 167 | strb r0, [r1] // save version to memory 168 | 169 | set_ec_props: // ***** set error correction properties ***** 170 | lsl r0, #4 // i = version * 16 171 | ldr r1, =eclvl_idx // pointer to error correction level index 172 | ldrb r1, [r1] // load error correction level index 173 | lsl r1, #2 // j = eclvl_idx * 4 174 | add r0, r0, r1 // i += j == (version * 16) + (eclvl_idx * 4) 175 | 176 | ldr r1, =ecprop_idx // pointer to error correction properties index 177 | strb r0, [r1] // save error correction properties index 178 | ldr r1, =tbl_ecprops // pointer to error correction properties 179 | ldrb r2, [r1, r0] // load data word capacity from EC properties 180 | ldr r3, =data_cap // pointer to data word capacity 181 | strb r2, [r3] // save data word capacity 182 | 183 | add r0, r0, #1 // increment index to ECW per block 184 | ldr r3, =ecwb_cap // pointer to max ECWs per block 185 | ldrb r2, [r1, r0] // load ECW per block from EC properties 186 | strb r2, [r3] // save ECW per block 187 | 188 | add r0, r0, #1 // increment index to group 1 blocks 189 | ldr r3, =g1b_cap // pointer to max blocks in group 1 190 | ldrb r2, [r1, r0] // load group 1 blocks from EC properties 191 | strb r2, [r3] // save group 1 blocks 192 | 193 | add r0, r0, #1 // increment index to group 1 words per block 194 | ldr r3, =g1bw_cap // pointer to max data words in each group 1 block 195 | ldrb r2, [r1, r0] // load group 1 words per block from EC properties 196 | strb r2, [r3] // save group 1 words per block 197 | 198 | init_dw: // ***** init data words ***** 199 | mov r1, #MODE // load mode nibble 200 | lsl r1, r1, #4 // shift nibble from low to high 201 | ldr r2, =count_ind // pointer to char count indicator 202 | ldrb r3, [r2] // load char count indicator nibble 203 | lsr r4, r3, #4 // shift char indicator high nibble to low nibble 204 | eor r0, r1, r4 // combine mode nibble with count indicator high nibble 205 | 206 | mov r6, #0 // dw_idx = 0 207 | ldr r7, =data_words // pointer to data words array 208 | strb r0, [r7, r6] // data_words[dw_idx] = mode nibble + count_ind[LOW] 209 | add r6, r6, #1 // dw_idx++ 210 | 211 | ldr r2, =msg // pointer to message 212 | ldr r5, =msg_len // pointer to message length 213 | ldrb r5, [r5] // load msg_len for loop exit 214 | mov r8, #0 // msg_idx = 0 215 | msg_loop: // ***** load message into data words array ***** 216 | eor r0, r0, r0 // reset scratch register for low nibble 217 | eor r1, r1, r1 // reset scratch register for high nibble 218 | 219 | mov r1, #0xF // load bitmask 0b00001111 220 | and r1, r3, r1 // mask low nibble out of msg[msg_idx-1] 221 | lsl r1, r1, #4 // shift low nibble to high nibble 222 | 223 | ldrb r3, [r2, r8] // load msg[msg_idx] 224 | lsr r4, r3, #4 // shift high nibble to low nibble 225 | eor r0, r1, r4 // combine high nibble with low nibble 226 | strb r0, [r7, r6] // store combined byte at data_words[dw_idx] 227 | 228 | add r8, r8, #1 // msg_idx++ 229 | add r6, r6, #1 // dw_idx++ 230 | cmp r8, r5 // check loop condition 231 | blt msg_loop // while (msg_idx < msg_len) 232 | 233 | mov r1, #0xF // load bitmask 0b00001111 234 | and r1, r3, r1 // mask low nibble out of msg[msg_idx-1] 235 | lsl r1, r1, #4 // shift low nibble to high nibble 236 | strb r1, [r7, r6] // store last char low nibble and zero nibble padding 237 | add r6, r6, #1 // dw_idx++ 238 | 239 | ldr r2, =data_cap // pointer to capacity 240 | ldrb r0, [r2] // load max capacity 241 | pad_loop: // ***** pad data with alternating bytes ***** 242 | cmp r6, r0 // check loop condition 243 | bge pad_done // dw_idx >= data capacity, pad finished 244 | mov r2, #0xEC // set pad byte 245 | strb r2, [r7, r6] // data_words[dw_idx] = 0xEC 246 | add r6, r6, #1 // dw_idx++ 247 | 248 | cmp r6, r0 // check loop condition 249 | bge pad_done // msg_idx >= data capacity, pad finished 250 | mov r2, #0x11 // set pad byte 251 | strb r2, [r7, r6] // data_words[dw_idx] = 0x11 252 | add r6, r6, #1 // msg_idx++ 253 | b pad_loop // while (msg_idx < capacity) 254 | 255 | pad_done: // ***** data padding finished ***** 256 | ldr r5, =g1b_cap // pointer to group 1 blocks capacity (3Q = 2) 257 | ldrb r5, [r5] // load g1b_cap 258 | ldr r6, =g1bw_cap // pointer to data words per block 259 | ldrb r6, [r6] // load g1bw_cap 260 | 261 | mov r9, #0 // data word offset 262 | mov r10, #0 // ECW offset 263 | mov r4, #0 // i = 0 264 | block_loop: // ***** loop over data blocks in group ***** 265 | ldr r0, =data_words // pointer to data words array 266 | ldr r7, =dw_block // pointer to data block 267 | mov r8, #0 // j = 0; 268 | dwb_loop: // fill data block with subset of data words 269 | add r3, r9, r8 // x = j + dw offset 270 | ldrb r2, [r0, r3] // get byte at offset 271 | strb r2, [r7, r8] // block[j] = data_words[x] 272 | dwb_next: // iterate to next word 273 | add r8, r8, #1 // j++ 274 | cmp r8, r6 // check loop condition 275 | blt dwb_loop // while (j < words per block) 276 | 277 | add r9, r9, r8 // dw offset += data words per block 278 | 279 | reed_sol: // ***** Reed-Solomon error correction ***** 280 | ldr r0, =ecw_block // pointer to error correction words block 281 | mov r1, r7 // pointer to data block 282 | mov r2, r6 // data words in each block 283 | ldr r3, =ecwb_cap // pointer to error correction words per block 284 | ldrb r3, [r3] // load ECW per block 285 | push {r3} // save ECW per block; r3 gets clobbered 286 | bl reed_solomon // perform Reed-Solomon error correction 287 | pop {r3} // restore ECW per block 288 | 289 | ldr r7, =ecw_blocks // pointer to all error correction words 290 | mov r8, #0 // j = 0 291 | ecw_loop: // copy ECW block to main ECW block 292 | ldrb r11, [r0, r8] // ecw_block[j] 293 | add r2, r8, r10 // x = j + ECW offset 294 | strb r11, [r7, r2] // ecw_blocks[x] = ecw_block[j] 295 | ecw_next: // iterate to next word 296 | add r8, r8, #1 // j++ 297 | cmp r8, r3 // check loop condition 298 | blt ecw_loop // while (j < ECW block capacity) 299 | 300 | add r10, r10, r3 // ECW offset += ECW block capacity 301 | 302 | block_next: // iterate to next data block 303 | add r4, r4, #1 // i++ 304 | cmp r4, r5 // check loop condition 305 | blt block_loop // while (i < blocks in group) 306 | 307 | interleave_dw: // ***** interleave data blocks into payload ***** 308 | ldr r0, =payload // pointer to payload 309 | mov r4, #0 // payload_idx 310 | 311 | ldr r11, =data_words // pointer to data words array 312 | mov r1, #0 // i = 0 313 | _pay_dw_loop: 314 | mov r7, #0 // dw_offset = 0 315 | mov r2, #0 // j = 0 316 | _pay_dwb_loop: 317 | add r10, r1, r7 // i + dw_offset 318 | ldrb r8, [r11, r10] // data_words[i + dw_offset] 319 | strb r8, [r0, r4] // payload[payload_idx] = data_words[i + dw_offset] 320 | add r4, r4, #1 // payload_idx++ 321 | _pay_dwb_next: 322 | add r7, r7, r6 // dw_offset += data words per block 323 | add r2, r2, #1 // j++ 324 | cmp r2, r5 // check loop condition 325 | blt _pay_dwb_loop // while (j < blocks) 326 | _pay_dw_next: 327 | add r1, r1, #1 // i++ 328 | cmp r1, r6 // check loop condition 329 | blt _pay_dw_loop // while (i < data words per block) 330 | 331 | interleave_ecw: // ***** interleave ECW blocks into payload ***** 332 | ldr r11, =ecw_blocks // pointer to ECW array 333 | mov r1, #0 // i = 0 334 | _pay_ecw_loop: 335 | mov r7, #0 // ecw_offset = 0 336 | mov r2, #0 // j = 0 337 | _pay_ecwb_loop: 338 | add r10, r1, r7 // i + ecw_offset 339 | ldrb r8, [r11, r10] // ecw_blocks[i + ecw_offset] 340 | strb r8, [r0, r4] // payload[payload_idx] = ecw[i + ecw_offset] 341 | add r4, r4, #1 // payload_idx++ 342 | _pay_ecwb_next: 343 | add r7, r7, r3 // ecw_offset += ECW per block 344 | add r2, r2, #1 // j++ 345 | cmp r2, r5 // check loop condition 346 | blt _pay_ecwb_loop // while (j < blocks) 347 | _pay_ecw_next: 348 | add r1, r1, #1 // i++ 349 | cmp r1, r3 // check loop condition 350 | blt _pay_ecw_loop // while (i < ECW per block) 351 | 352 | ldr r9, =pyld_size // pointer to payload size 353 | strb r4, [r9] // store size of payload 354 | 355 | qr_init: // ***** QR matrix init ***** 356 | ldr r5, =version // pointer to version 357 | ldrb r5, [r5] // load version 358 | lsl r2, r5, #2 // version * 4 359 | add r2, r2, #21 // (version * 4) + 21 360 | ldr r3, =qr_width // pointer to QR code width 361 | strb r2, [r3] // save QR width 362 | mov r6, r0 // retain pointer to payload 363 | mov r11, r2 // retain QR width 364 | 365 | ldr r0, =bin_str // pointer to temp buffer 366 | ldr r5, =data_bin // pointer to full binary string 367 | mov r8, #0 // bit_idx = 0 368 | mov r7, #0 // i = 0 369 | qr_data_loop: 370 | ldr r9, [r6, r7] // payload[i] 371 | mov r2, r9 // ubyte to convert 372 | bl ascii_ubyte2bin // convert ubyte to bin string 373 | 374 | mov r9, #0 // j = 0 375 | qr_bin_loop: 376 | ldrb r1, [r0, r9] // bin_str[j] 377 | add r2, r8, r9 // x = bit_idx + j 378 | strb r1, [r5, r2] // data_bin[bit_idx] = bin_str[j] 379 | 380 | add r9, r9, #1 // j++ 381 | cmp r9, #8 // check loop condition 382 | blt qr_bin_loop // while (j < 8) 383 | 384 | add r7, r7, #1 // i++ 385 | add r8, r8, #8 // bit_idx += 8 386 | cmp r7, r4 // check loop condition 387 | blt qr_data_loop // while (i < payload_bytes) 388 | 389 | qr_remainder: // ***** Calculate remainder bits ***** 390 | lsl r4, r4, #3 // convert size to bits; 2^3 = 8 391 | ldr r7, =tbl_rem // pointer to remainder table 392 | ldr r6, =version // pointer to version 393 | ldrb r6, [r6] // load version 394 | ldrb r6, [r7, r6] // tbl_rem[version] = remainder 395 | 396 | mov r9, #0 // i = 0 397 | qr_rem_loop: 398 | add r2, r8, r9 // x = bit_idx + i 399 | mov r7, #ASCII_ZERO // load '0' 400 | strb r7, [r5, r2] // data[x] = '0' 401 | 402 | add r9, r9, #1 // i++ 403 | cmp r9, r6 // check loop condition 404 | blt qr_rem_loop // while (i < remainder_bits) 405 | 406 | add r4, r4, r6 // bits = (payload bytes * 8) + remainder 407 | ldr r7, =pyld_bits // pointer to payload size in bits 408 | strh r4, [r7] // store calculated payload size 409 | 410 | qr_fill: // ****** Populate QR matrix ***** 411 | ldr r0, =qr_mat // pointer to QR matrix 412 | mov r2, r11 // pass qr_width 413 | ldr r3, =version // pointer to version 414 | ldrb r3, [r3] // load version 415 | bl qr_reserved // add reserved areas to QR matrix 416 | 417 | mov r1, r5 // pointer to binary string 418 | mov r2, r11 // pass qr_width 419 | mov r3, r4 // load payload size in bits 420 | bl qr_zigzag // add payload to QR matrix 421 | 422 | mov r1, #0 // hardcoded mask index 423 | ldr r3, =eclvl_idx // pointer to error level index 424 | ldrb r3, [r3] // pass error level 425 | bl qr_fmtbits // add format bits to QR matrix 426 | 427 | bl qr_mask0 // apply mask 0 to QR matrix 428 | bl qr_quiet // add quiet zone to QR matrix 429 | add r2, r2, #8 // add quiet zone width 430 | 431 | qr_pbm: // ***** Output QR matrix to PBM file ***** 432 | bl qr_normalize // normalize QR matrix to ['0','1'] 433 | ldr r1, =out_file // pointer to PBM file name 434 | mov r3, r2 // use width for PBM width + length (square) 435 | bl pbm_write // create new PBM file from QR matrix 436 | 437 | b _end // end of main program flow 438 | 439 | bad_eclvl: // ***** invalid error correction level ***** 440 | ldr r1, =err_01 // pointer to buffer address 441 | mov r2, #err_01_len // string length 442 | b error_exit // exit program with error 443 | 444 | bad_version: // ***** invalid version ***** 445 | ldr r1, =err_02 // pointer to buffer address 446 | mov r2, #err_02_len // string length 447 | b error_exit // exit program with error 448 | 449 | bad_argc: // ***** invalid arguments ***** 450 | ldr r1, =err_03 // pointer to buffer address 451 | mov r2, #err_03_len // string length 452 | b error_exit // exit program with error 453 | 454 | bad_msglen: // ***** invalid message length ***** 455 | ldr r1, =err_04 // pointer to buffer address 456 | mov r2, #err_04_len // string length 457 | b error_exit // exit program with error 458 | 459 | error_exit: // ***** exit with error ***** 460 | mov r7, #WRITE // syscall number 461 | mov r0, #STDOUT // file descriptor 462 | svc #0 // invoke syscall (r1,r2 = msg,len) 463 | mov r0, #1 // set status code as error 464 | mov r7, #EXIT // syscall number 465 | mov r1, #1 // error status 466 | svc #0 // invoke syscall 467 | 468 | _end: // ***** terminate program ***** 469 | mov r7, #EXIT // syscall number 470 | mov r0, #0 // successful exit 471 | svc #0 // invoke syscall 472 | 473 | .end // end of source 474 | -------------------------------------------------------------------------------- /qrcode.s: -------------------------------------------------------------------------------- 1 | // subroutines for building QR code matrix 2 | 3 | .include "const.inc" 4 | 5 | // exports 6 | .global qr_reserved // add reserved areas to QR matrix 7 | .global qr_normalize // normalize QR matrix to ['0','1'] 8 | .global qr_zigzag // "zigzag" payload into QR matrix 9 | .global qr_fmtbits // add format bits to QR matrix 10 | .global qr_mask0 // apply mask 0 to QR matrix 11 | .global qr_quiet // add quiet zone to QR matrix 12 | 13 | // internal subroutines 14 | // add_square - add a square to QR matrix 15 | // add_timing - add horizontal and vertical timing patterns to QR matrix 16 | // add_finder - add a finder to QR matrix 17 | // add_align - add alignment pattern to QR matrix 18 | 19 | // constants 20 | .equ MOD_DLT, 0x30 // Data light module; ASCII '0' 21 | .equ MOD_DDK, 0x31 // Data dark module; ASCII '1' 22 | .equ MOD_EMP, 0x32 // Empty module; ASCII '2' 23 | .equ MOD_RLT, 0x33 // Reserved light module; ASCII '3' 24 | .equ MOD_RDK, 0x34 // Reserved dark module; ASCII '4' 25 | 26 | .equ MASK_B0, 0x000000ff // 32-bit mask for byte 0 27 | .equ MASK_B1, 0x0000ff00 // 32-bit mask for byte 1 28 | .equ MASK_B2, 0x00ff0000 // 32-bit mask for byte 2 29 | .equ MASK_B3, 0xff000000 // 32-bit mask for byte 3 30 | 31 | .data 32 | 33 | tbl_align: .byte 0, 18, 22, 26 // alignment pattern; v1-v4 34 | fmt_lo: .space 8+1 // temp string buffer 35 | fmt_hi: .space 8+1 // temp string buffer 36 | 37 | tbl_fmt: // table of format information strings 38 | // {err_lvl}-{mask_idx} 39 | .hword 0b111011111000100 // L-0 40 | .hword 0b101010000010010 // M-0 41 | .hword 0b011010101011111 // Q-0 42 | .hword 0b001011010001001 // H-0 43 | .hword 0b111001011110011 // L-1 44 | .hword 0b101000100100101 // M-1 45 | .hword 0b011000001101000 // Q-1 46 | .hword 0b001001110111110 // H-1 47 | .hword 0b111110110101010 // L-2 48 | .hword 0b101111001111100 // M-2 49 | .hword 0b011111100110001 // Q-2 50 | .hword 0b001110011100111 // H-2 51 | .hword 0b111100010011101 // L-3 52 | .hword 0b101101101001011 // M-3 53 | .hword 0b011101000000110 // Q-3 54 | .hword 0b001100111010000 // H-3 55 | .hword 0b110011000101111 // L-4 56 | .hword 0b100010111111001 // M-4 57 | .hword 0b010010010110100 // Q-4 58 | .hword 0b000011101100010 // H-4 59 | .hword 0b110001100011000 // L-5 60 | .hword 0b100000011001110 // M-5 61 | .hword 0b010000110000011 // Q-5 62 | .hword 0b000001001010101 // H-5 63 | .hword 0b110110001000001 // L-6 64 | .hword 0b100111110010111 // M-6 65 | .hword 0b010111011011010 // Q-6 66 | .hword 0b000110100001100 // H-6 67 | .hword 0b110100101110110 // L-7 68 | .hword 0b100101010100000 // M-7 69 | .hword 0b010101111101101 // Q-7 70 | .hword 0b000100000111011 // H-7 71 | 72 | .text 73 | 74 | add_square: // ***** Add square to QR matrix ***** 75 | // r0 - pointer to QR matrix 76 | // r1 - sq_args = [x pos, y pos, square width, fill byte] 77 | // r2 - QR width 78 | // r3 - unused 79 | push {r4-r11, lr} // save caller's vars + return address 80 | 81 | and r11, r1, #MASK_B0 // get byte 0 from sq_args; fill byte 82 | and r4, r1, #MASK_B1 // get byte 1 from sq_args; square width 83 | lsr r4, r4, #8 // shift 1 byte; set square width 84 | 85 | and r8, r1, #MASK_B2 // get byte 2 from sq_args; y position 86 | lsr r8, r8, #16 // shift 2 bytes; set y position 87 | and r7, r1, #MASK_B3 // get byte 3 from sq_args; x position 88 | lsr r7, r7, #24 // shift 3 bytes; set x position 89 | 90 | mov r5, #0 // i = 0 91 | _asq_x_loop: 92 | mov r6, #0 // j = 0 93 | _asq_y_loop: 94 | push {r7,r8} // save x and y position 95 | add r7, r7, r5 // dx = x + i 96 | add r8, r8, r6 // dy = y + j 97 | 98 | cmp r7, r2 // if 99 | bge _asq_y_next // (dx >= qr_size) 100 | cmp r8, r2 // && 101 | bge _asq_y_next // (dy >= qr_size) 102 | cmp r7, #0 // && 103 | blt _asq_y_next // (dx < 0) 104 | cmp r8, #0 // && 105 | blt _asq_y_next // (dy < 0) 106 | 107 | umull r9, r10, r8, r2 // dy * qr_size 108 | add r9, r9, r7 // (dy * qr_size) + dx 109 | strb r11, [r0, r9] // qr_mat[(dy * qr_size) + dx] = sq_args[0] 110 | _asq_y_next: 111 | pop {r7, r8} // restore original x and y position 112 | add r6, r6, #1 // j++ 113 | cmp r6, r4 // check loop condition 114 | blt _asq_y_loop // while (j < sq_args[1]) 115 | _asq_x_next: 116 | add r5, r5, #1 // i++ 117 | cmp r5, r4 // check loop condition 118 | blt _asq_x_loop // while (i < sq_args[1]) 119 | 120 | pop {r4-r11, lr} // restore caller's vars + return address 121 | bx lr // return from subroutine 122 | 123 | add_timing: // ***** Add timing patterns to QR matrix ***** 124 | // r0 - pointer to QR matrix 125 | // r1 - unused 126 | // r2 - QR width 127 | // r3 - unused 128 | push {r4-r11, lr} // save caller's vars + return address 129 | 130 | mov r4, #1 // is_dark = true 131 | mov r5, #0 // i = 0 132 | mov r7, #6 // timing offset 133 | _at_loop: 134 | cmp r4, #1 // is_dark? 135 | beq _at_dark // use dark module 136 | mov r6, $MOD_RLT // set light module ASCII 137 | b _at_set // skip over next 2 lines 138 | _at_dark: 139 | mov r6, $MOD_RDK // set dark module ASCII 140 | _at_set: 141 | umull r9, r10, r2, r5 // x = i * qr_size 142 | add r9, r9, r7 // x = (i * qr_size) + offset 143 | strb r6, [r0, r9] // qr_mat[x] = r6; horizontal timing 144 | 145 | umull r9, r10, r7, r2 // x = offset * qr_size 146 | add r9, r9, r5 // x = (offset * qr_size) + i 147 | strb r6, [r0, r9] // qr_mat[x] = r6; vertical timing 148 | 149 | eor r4, r4, #1 // is_dark = !is_dark 150 | add r5, r5, #1 // i++ 151 | cmp r5, r2 // check loop condition 152 | blt _at_loop // while (i < qr_width) 153 | 154 | pop {r4-r11, lr} // restore caller's vars + return address 155 | bx lr // return from subroutine 156 | 157 | add_finder: // ***** Add a finder pattern to QR matrix ***** 158 | // r0 - pointer to QR matrix 159 | // r1 - unused 160 | // r2 - QR width 161 | // r3 - pos = [?, ?, x, y] 162 | push {r4-r11, lr} // save caller's vars + return address 163 | 164 | and r4, r3, #MASK_B1 // get x position 165 | lsr r4, r4, #8 // shift over 1 byte 166 | and r5, r3, #MASK_B0 // get y position 167 | 168 | sub r4, r4, #1 // init x position 169 | sub r5, r5, #1 // init y position 170 | mov r8, #9 // init width constant 171 | mov r7, #0 // init is_dark = false 172 | mov r6, #0 // i = 0 173 | _af_loop: 174 | eor r1, r1, r1 // reset sq_args 175 | orr r1, r1, r4, lsl #24 // set x position 176 | orr r1, r1, r5, lsl #16 // set y position 177 | orr r1, r1, r8, lsl #8 // set square width 178 | 179 | cmp r7, #1 // is_dark? 180 | beq _af_dkmod // 181 | orr r1, r1, #MOD_RLT // set fill character 182 | b _af_square // skip next two lines 183 | _af_dkmod: 184 | orr r1, r1, #MOD_RDK // set fill character 185 | _af_square: 186 | bl add_square // add square to QR matrix 187 | add r4, r4, #1 // x++ 188 | add r5, r5, #1 // y++ 189 | sub r8, r8, #2 // width -= 2 190 | eor r7, r7, #1 // is_dark = !is_dark 191 | 192 | add r6, r6, #1 // i++ 193 | cmp r6, #4 // check loop condition 194 | blt _af_loop // while (i < 4) 195 | 196 | pop {r4-r11, lr} // restore caller's vars + return address 197 | bx lr // return from subroutine 198 | 199 | add_align: // ***** add alignment patterns to QR matrix ***** 200 | // r0 - pointer to QR matrix 201 | // r1 - unused; clobbered 202 | // r2 - QR width 203 | // r3 - QR version 204 | push {r4-r11, lr} // save caller's vars + return address 205 | 206 | cmp r3, #0 // check version 207 | beq _aa_done // version 1 has no alignment patterns 208 | ldr r6, =tbl_align // pointer to alignment pattern table 209 | ldrb r6, [r6, r3] // load alignment constant 210 | 211 | mov r5, #1 // is_dark = true 212 | mov r7, #5 // init width 213 | sub r8, r6, #2 // init offset 214 | mov r6, #0 // i = 0 215 | _aa_loop: 216 | eor r1, r1, r1 // reset sq_args 217 | orr r1, r1, r8, lsl #24 // set x position 218 | orr r1, r1, r8, lsl #16 // set y position 219 | orr r1, r1, r7, lsl #8 // set square width 220 | 221 | cmp r5, #1 // is_dark? 222 | beq _aa_dkmod // use dark module 223 | orr r1, r1, #MOD_RLT // set fill byte to light 224 | b _aa_square // skip next two lines 225 | _aa_dkmod: 226 | orr r1, r1, #MOD_RDK // set fill byte 227 | _aa_square: 228 | bl add_square // add alignment at x,y 229 | add r8, r8, #1 // offset++ 230 | sub r7, r7, #2 // width -= 2 231 | eor r5, r5, #1 // is_dark = !is_dark 232 | 233 | add r6, r6, #1 // i++ 234 | cmp r6, #3 // check loop condition 235 | blt _aa_loop // while (i < 3) 236 | 237 | _aa_done: 238 | pop {r4-r11, lr} // restore caller's vars + return address 239 | bx lr // return from subroutine 240 | 241 | qr_reserved: // ***** Add reserved areas to QR matrix ***** 242 | // r0 - pointer to QR matrix 243 | // r1 - unused; clobbered 244 | // r2 - QR width 245 | // r3 - QR version 246 | push {r4-r11, lr} // save caller's vars + return address 247 | 248 | mov r11, r3 // retain QR version 249 | mov r5, #9 // set square width 250 | mov r6, #MOD_RLT // set fill byte 251 | mov r1, #0 // reset unused arg 252 | 253 | _qrr_sq: // add reserved squares to QR matrix 254 | mov r4, #0 // set x,y; sq_args[3] = 0, sq_args[2] = 0 255 | orr r1, r1, r5, lsl #8 // set square width; sq_args[1] 256 | orr r1, r1, r6 // set fill byte; sq_args[0] 257 | bl add_square // add top left square to QR matrix 258 | eor r1, r1, r1 // reset sq_args 259 | 260 | sub r4, r2, r5 // x = qr_size - 9 261 | add r4, r4, #1 // x += 1 262 | orr r1, r1, r4, lsl #24 // set x position 263 | orr r1, r1, r5, lsl #8 // set square width 264 | orr r1, r1, r6 // set fill byte 265 | bl add_square // add top right square to QR matrix 266 | eor r1, r1, r1 // reset sq_args 267 | 268 | sub r4, r2, r5 // y = qr_size - 9 269 | add r4, r4, #1 // y += 1 270 | orr r1, r1, r4, lsl #16 // set y position 271 | orr r1, r1, r5, lsl #8 // set square width 272 | orr r1, r1, r6 // set fill byte 273 | bl add_square // add bottom left square to QR matrix 274 | 275 | _qrr_time: // add timing patterns to QR matrix 276 | bl add_timing // add horizontal and vertical timing patterns 277 | 278 | _qrr_find: // add finding patterns to QR matrix 279 | mov r3, #0 // pos = (0, 0) 280 | bl add_finder // add finder pattern at x,y 281 | 282 | sub r6, r2, #7 // qr_size - 7 283 | orr r3, r3, r6 // pos = (0, qr_size-7) 284 | bl add_finder // add finder pattern at x,y 285 | 286 | eor r3, r3, r3 // reset pos 287 | orr r3, r3, r6, lsl #8 // pos = (qr_size-7, 0) 288 | bl add_finder // add finder pattern at x,y 289 | _qrr_align: 290 | mov r3, r11 // load QR version 291 | bl add_align // add alignment patterns to QR matrix 292 | 293 | _qrr_darkmod: // add dark module to QR matrix 294 | mov r4, #4 // 295 | add r5, r11, #1 // version + 1 296 | umull r8, r7, r4, r5 // 4 * (version + 1) 297 | add r8, r8, #9 // (4 * (v + 1)) + 9 298 | umull r4, r7, r8, r2 // ((4 * (v + 1)) + 9) * qr_width 299 | add r4, r4, #8 // (((4 * (v + 1)) + 9) * qr_width) + 8 300 | mov r5, #MOD_RDK // load dark module 301 | strb r5, [r0, r4] // set dark module at calculated index 302 | 303 | pop {r4-r11, lr} // restore caller's vars + return address 304 | bx lr // return from subroutine 305 | 306 | qr_normalize: // ***** Normalize QR matrix to ['0','1'] ***** 307 | // r0 - pointer to QR matrix 308 | // r1 - unused 309 | // r2 - QR width 310 | // r3 - unused 311 | push {r4-r11, lr} // save caller's vars + return address 312 | 313 | mov r9, #0 // qr_idx = 0 314 | mov r4, #0 // i = 0 315 | _qrn_x_loop: 316 | mov r5, #0 // j = 0 317 | _qrn_y_loop: 318 | ldrb r6, [r0, r9] // qr_mat[qr_idx] 319 | 320 | cmp r6, #MOD_EMP // check if empty module 321 | beq _qrn_ltmod // normalize to data light module 322 | cmp r6, #MOD_RLT // check if reserved light module 323 | beq _qrn_ltmod // normalize to data light module 324 | cmp r6, #MOD_RDK // check if reserved dark module 325 | beq _qrn_dkmod // normalize to data dark module 326 | 327 | b _qrn_y_next // already normalized; iterate 328 | _qrn_dkmod: 329 | mov r6, #MOD_DDK // dark module 330 | strb r6, [r0, r9] // store normalized module 331 | b _qrn_y_next // iterate 332 | _qrn_ltmod: 333 | mov r6, #MOD_DLT // light module 334 | strb r6, [r0, r9] // store normalized module 335 | _qrn_y_next: 336 | add r9, r9, #1 // qr_idx++ 337 | add r5, r5, #1 // j++ 338 | cmp r5, r2 // check loop condition 339 | blt _qrn_y_loop // while (j < qr_width) 340 | _qrn_x_next: 341 | add r4, r4, #1 // i++ 342 | cmp r4, r2 // check loop condition 343 | blt _qrn_x_loop // while (i < qr_width) 344 | 345 | pop {r4-r11, lr} // restore caller's vars + return address 346 | bx lr // return from subroutine 347 | 348 | qr_zigzag: // ***** "zigzag" payload into QR matrix ***** 349 | // r0 - pointer to QR matrix 350 | // r1 - pointer to payload 351 | // r2 - QR matrix width 352 | // r3 - payload size in bits 353 | push {r4-r11, lr} // save caller's vars + return address 354 | 355 | sub r10, r2, #1 // x = qr_width - 1 356 | mov r11, r10 // y = qr_width - 1 357 | mov r8, #0b11 // flags = [is_zig, is_up] 358 | mov r9, #0 // i = 0 359 | _qrz_loop: 360 | cmp r11, r2 // check if at bottom edge 361 | beq _qrz_edge_b // if (y == qr_width) 362 | cmp r11, #0 // check if at upper edge 363 | blt _qrz_edge_t // if (y < 0) 364 | b _qrz_edge_no // not at edge 365 | 366 | _qrz_edge_b: // at bottom edge; bounce off 367 | eor r8, r8, #0b01 // is_up = !is_up 368 | sub r10, r10, #2 // x -= 2 369 | orr r8, r8, #0b10 // is_zig = true 370 | sub r11, r2, #1 // y = qr_width - 1 371 | b _qrz_edge_no // not at edge, anymore 372 | 373 | _qrz_edge_t: // at top edge; bounce off 374 | eor r8, r8, #0b01 // is_up = !is_up 375 | sub r10, r10, #2 // x -= 2 376 | orr r8, r8, #0b10 // is_zig = true 377 | mov r11, #0 // y = 0 378 | 379 | _qrz_edge_no: // not at edge 380 | umull r4, r5, r11, r2 // y * qr_width 381 | add r4, r4, r10 // (y * qr_width) + x 382 | ldrb r5, [r0, r4] // next_mod = qr_mat[((y * qr_width) + x)] 383 | cmp r5, #MOD_EMP // check if next_mod is empty 384 | bne _qrz_no_set // skip module set 385 | 386 | ldrb r5, [r1, r9] // payload[i] 387 | strb r5, [r0, r4] // qr_mat[((y * qr_width) + x)] = payload[i] 388 | add r9, r9, #1 // i++ 389 | _qrz_no_set: 390 | and r5, r8, #0b10 // get is_zig flag 391 | cmp r5, #0b10 // check is_zig flag 392 | bne _qrz_zag // if (!is_zig) 393 | sub r10, r10, #1 // x--; zig 394 | b _qrz_znext // skip rest of zigzag branches 395 | _qrz_zag: 396 | add r10, r10, #1 // x++; zag 397 | and r5, r8, #0b01 // get is_up flag 398 | cmp r5, #0b01 // check is_up flag 399 | beq _qrz_zag_up // if (is_up) 400 | add r11, r11, #1 // y++; zag down 401 | b _qrz_znext // skip rest of zigzag branches 402 | _qrz_zag_up: 403 | sub r11, r11, #1 // y--; zag up 404 | _qrz_znext: 405 | eor r8, r8, #0b10 // is_zig = !is_zig 406 | cmp r10, #6 // check timing pattern position 407 | bne _qrz_next // don't skip anything 408 | 409 | _qrz_skip_time: // skip over timing patterns 410 | sub r10, r10, #1 // x-- 411 | sub r11, r11, #1 // y-- 412 | _qrz_next: 413 | cmp r9, r3 // check loop condition 414 | blt _qrz_loop // while (i < payload_bits) 415 | 416 | pop {r4-r11, lr} // restore caller's vars + return address 417 | bx lr // return from subroutine 418 | 419 | qr_fmtbits: // ***** Add format bits to QR matrix ***** 420 | // r0 - pointer to QR matrix 421 | // r1 - mask index 422 | // r2 - QR matrix width 423 | // r3 - error level index 424 | push {r4-r11, lr} // save caller's vars + return address 425 | 426 | ldr r4, =tbl_fmt // pointer to table format table 427 | lsl r6, r3, #1 // err_offset = err_lvl * 2 428 | lsl r7, r1, #3 // mask_offset = mask_idx * (2^3) 429 | add r6, r6, r7 // fmt_idx = err_offset + mask_offset 430 | ldrh r5, [r4, r6] // tbl_fmt[fmt_idx] 431 | 432 | push {r0,r2} // save args 433 | ldr r9, =fmt_hi // pointer to temp string buffer 434 | mov r0, r9 // use high byte buffer 435 | and r2, r5, #0xFF00 // mask high byte 436 | lsr r2, r2, #7 // shift one byte over; only 7 fmt bits 437 | bl ascii_ubyte2bin // convert byte to bit string 438 | 439 | ldr r10, =fmt_lo // pointer to temp string buffer 440 | mov r0, r10 // use low byte buffer 441 | and r2, r5, #0x00FF // mask low byte 442 | lsl r2, r2, #1 // only 7 fmt bits 443 | bl ascii_ubyte2bin // convert byte to bit string 444 | pop {r0,r2} // restore args 445 | 446 | mov r4, #0 // x = 0 447 | mov r5, #8 // y = 8 448 | mov r6, #0 // i = 0 449 | _qrf_tlh_loop: // top left 7 format bits (high byte) 450 | cmp r6, #6 // check for timing index 451 | bne _qrf_tlh_next // if (i != 6) 452 | add r4, r4, #1 // x++; skip vertical timing 453 | _qrf_tlh_next: 454 | umull r7, r8, r5, r2 // q = y * qr_width 455 | add r7, r7, r4 // q += x 456 | ldrb r8, [r9, r6] // hi_bits[i] 457 | add r8, r8, #3 // convert data to reserved 458 | strb r8, [r0, r7] // qr_mat[q] = hi_bits[i] 459 | add r4, r4, #1 // x++ 460 | 461 | add r6, r6, #1 // i++ 462 | cmp r6, #7 // check loop condition 463 | blt _qrf_tlh_loop // while (i < 7) 464 | 465 | mov r4, #8 // x = 8 466 | mov r5, #7 // y = 7 467 | mov r6, #0 // i = 0 468 | _qrf_tll_loop: // top left 7 format bits (low byte) 469 | cmp r6, #1 // check for timing index 470 | bne _qrf_tll_next // if (i != 1) 471 | sub r5, r5, #1 // y--; skip horizontal timing 472 | _qrf_tll_next: 473 | umull r7, r8, r5, r2 // q = y * qr_width 474 | add r7, r7, r4 // q += x 475 | ldrb r8, [r10, r6] // lo_bits[i] 476 | add r8, r8, #3 // convert data to reserved 477 | strb r8, [r0, r7] // qr_mat[q] = lo_bits[i] 478 | sub r5, r5, #1 // y-- 479 | 480 | add r6, r6, #1 // i++ 481 | cmp r6, #7 // check loop condition 482 | blt _qrf_tll_loop // while (i < 7) 483 | 484 | sub r4, r2, #7 // x = qr_width - 7 485 | mov r5, #8 // y = 8 486 | mov r6, #0 // i = 0 487 | _qrf_trl_loop: // top right 7 format bits (low byte) 488 | umull r7, r8, r5, r2 // q = y * qr_width 489 | add r7, r7, r4 // q += x 490 | add r7, r7, r6 // q += i 491 | ldrb r8, [r10, r6] // lo_bits[i] 492 | add r8, r8, #3 // convert data to reserved 493 | strb r8, [r0, r7] // qr_mat[q] = lo_bits[i] 494 | 495 | add r6, r6, #1 // i++ 496 | cmp r6, #7 // check loop condition 497 | blt _qrf_trl_loop // while (i < 7) 498 | 499 | mov r4, #8 // x = 8 500 | sub r5, r2, #1 // y = qr_width - 1 501 | mov r6, #0 // i = 0; skip 8th bit 502 | _qrf_blh_loop: // bottom left 7 format bits (high byte) 503 | sub r11, r5, r6 // q = (y - i) 504 | umull r7, r8, r11, r2 // q *= qr_width 505 | add r7, r7, r4 // q += x 506 | ldrb r8, [r9, r6] // hi_bits[i] 507 | add r8, r8, #3 // convert data to reserved 508 | strb r8, [r0, r7] // qr_mat[q] = hi_bits[i] 509 | 510 | add r6, r6, #1 // i++ 511 | cmp r6, #7 // check loop condition 512 | blt _qrf_blh_loop // while (i < 7) 513 | 514 | pop {r4-r11, lr} // restore caller's vars + return address 515 | bx lr // return from subroutine 516 | 517 | qr_mask0: // ***** Apply mask 0 to QR matrix ***** 518 | // r0 - pointer to QR matrix 519 | // r1 - unused 520 | // r2 - QR width 521 | // r3 - unused 522 | push {r4-r11, lr} // save caller's vars + return address 523 | 524 | mov r4, #0 // y = 0 525 | _qrm0_y_loop: 526 | mov r5, #0 // x = 0 527 | _qrm0_x_loop: 528 | add r10, r5, r4 // x + y 529 | and r10, r10, #0b01 // check if odd 530 | eor r10, r10, #0b01 // flip bit; is even 531 | 532 | umull r6, r7, r4, r2 // q = y * qr_width 533 | add r6, r6, r5 // q += x 534 | ldrb r8, [r0, r6] // mod = qr_mat[q] 535 | 536 | cmp r8, #MOD_RLT // check for reserved light module 537 | beq _qrm0_rlt // if (mod == '3') 538 | cmp r8, #MOD_RDK // check for reserved dark module 539 | beq _qrm0_rdk // if (mod == '4') 540 | 541 | sub r9, r8, #48 // convert ASCII to bit 542 | eor r9, r9, r10 // qr_mat[q] XOR mask 543 | add r9, r9, #48 // convert bit back to ASCII 544 | strb r9, [r0, r6] // apply mask 545 | b _qrm0_x_next // iterate 546 | _qrm0_rlt: 547 | mov r9, #MOD_DLT // convert reserved to data 548 | strb r9, [r0, r6] // qr_mat[q] = '0' 549 | b _qrm0_x_next // iterate 550 | _qrm0_rdk: 551 | mov r9, #MOD_DDK // convert reserved to data 552 | strb r9, [r0, r6] // qr_mat[q] = '1' 553 | _qrm0_x_next: 554 | add r5, r5, #1 // x++ 555 | cmp r5, r2 // check loop condition 556 | blt _qrm0_x_loop // while (x < qr_width) 557 | 558 | _qrm0_y_next: 559 | add r4, r4, #1 // y++ 560 | cmp r4, r2 // check loop condition 561 | blt _qrm0_y_loop // while (y < qr_width) 562 | 563 | pop {r4-r11, lr} // restore caller's vars + return address 564 | bx lr // return from subroutine 565 | 566 | qr_quiet: // ***** Add quiet zone to QR matrix ***** 567 | // r0 - pointer to QR matrix 568 | // r1 - unused 569 | // r2 - QR width 570 | // r3 - unused 571 | push {r4-r11, lr} // save caller's vars + return address 572 | 573 | mov r4, r2 // y = qr_width 574 | sub r4, r4, #1 575 | _qrq_y_loop: 576 | mov r5, r2 // x = qr_width 577 | sub r5, r5, #1 578 | _qrq_x_loop: 579 | umull r6, r7, r4, r2 // q = y * qr_width 580 | add r6, r6, r5 // q += x 581 | ldrb r10, [r0, r6] // mod = qr_mat[q] 582 | _qrq_transform: 583 | push {r10} // save original module 584 | add r7, r2, #8 // qr_width + 8 585 | add r8, r4, #4 // y + (8/2) 586 | umull r10, r11, r8, r7 // t = (y + (8/2)) * (qr_width + 8) 587 | add r11, r10, r5 // t += x 588 | add r11, r11, #4 // t += (8/2) 589 | pop {r10} // restore original module 590 | strb r10, [r0, r11] // qr_mat[t] = qr_mat[q] 591 | _qrq_reset: 592 | mov r10, #MOD_DLT // light module '0' 593 | strb r10, [r0, r6] // reset moved module 594 | 595 | _qrq_x_next: 596 | sub r5, r5, #1 // x-- 597 | cmp r5, #0 // check loop condition 598 | bge _qrq_x_loop // while (x > 0) 599 | _qrq_y_next: 600 | sub r4, r4, #1 // y-- 601 | cmp r4, #0 // check loop condition 602 | bge _qrq_y_loop // while (y > 0) 603 | 604 | pop {r4-r11, lr} // restore caller's vars + return address 605 | bx lr // return from subroutine 606 | 607 | .end // end of source 608 | -------------------------------------------------------------------------------- /reed-solomon.s: -------------------------------------------------------------------------------- 1 | // Subroutines for performing Reed-Solomon error correction 2 | 3 | // exports 4 | .global reed_solomon // Reed-Solomon error correction 5 | 6 | // internal subroutines 7 | // new_msg_poly - build polynomial from message 8 | // new_gen_poly - build generator polynomial 9 | // gf256_mul - multiplication in Galois Field 2^8 10 | // gf256_inv - inversion in Galois Field 2^8 11 | // gf256_div - division in Galoi field 2^8 12 | // poly_clr - reset a polynomial's data 13 | // poly_norm - polynomial normalization 14 | // poly_add - polynomial addition 15 | // poly_mul - polynomial multiplication 16 | // poly_rem - find remainder of polynomial long division 17 | 18 | // constants 19 | .equ POLY_SIZE, 128 // max terms in a polynomial 20 | 21 | .data 22 | 23 | gf256_anti: // Galois field 256 anti-logarithm table 24 | .byte 1, 2, 4, 8, 16, 32, 64, 128 // 0 - 7 25 | .byte 29, 58, 116, 232, 205, 135, 19, 38 // 8 - 15 26 | .byte 76, 152, 45, 90, 180, 117, 234, 201 // 16 - 23 27 | .byte 143, 3, 6, 12, 24, 48, 96, 192 // 24 - 31 28 | .byte 157, 39, 78, 156, 37, 74, 148, 53 // 32 - 39 29 | .byte 106, 212, 181, 119, 238, 193, 159, 35 // 40 - 47 30 | .byte 70, 140, 5, 10, 20, 40, 80, 160 // 48 - 55 31 | .byte 93, 186, 105, 210, 185, 111, 222, 161 // 56 - 63 32 | .byte 95, 190, 97, 194, 153, 47, 94, 188 // 64 - 71 33 | .byte 101, 202, 137, 15, 30, 60, 120, 240 // 72 - 79 34 | .byte 253, 231, 211, 187, 107, 214, 177, 127 // 80 - 87 35 | .byte 254, 225, 223, 163, 91, 182, 113, 226 // 88 - 95 36 | .byte 217, 175, 67, 134, 17, 34, 68, 136 // 96 - 103 37 | .byte 13, 26, 52, 104, 208, 189, 103, 206 // 104 - 111 38 | .byte 129, 31, 62, 124, 248, 237, 199, 147 // 112 - 119 39 | .byte 59, 118, 236, 197, 151, 51, 102, 204 // 120 - 127 40 | .byte 133, 23, 46, 92, 184, 109, 218, 169 // 128 - 135 41 | .byte 79, 158, 33, 66, 132, 21, 42, 84 // 136 - 143 42 | .byte 168, 77, 154, 41, 82, 164, 85, 170 // 144 - 151 43 | .byte 73, 146, 57, 114, 228, 213, 183, 115 // 152 - 159 44 | .byte 230, 209, 191, 99, 198, 145, 63, 126 // 160 - 167 45 | .byte 252, 229, 215, 179, 123, 246, 241, 255 // 168 - 175 46 | .byte 227, 219, 171, 75, 150, 49, 98, 196 // 176 - 183 47 | .byte 149, 55, 110, 220, 165, 87, 174, 65 // 184 - 191 48 | .byte 130, 25, 50, 100, 200, 141, 7, 14 // 192 - 199 49 | .byte 28, 56, 112, 224, 221, 167, 83, 166 // 200 - 207 50 | .byte 81, 162, 89, 178, 121, 242, 249, 239 // 208 - 215 51 | .byte 195, 155, 43, 86, 172, 69, 138, 9 // 216 - 223 52 | .byte 18, 36, 72, 144, 61, 122, 244, 245 // 224 - 231 53 | .byte 247, 243, 251, 235, 203, 139, 11, 22 // 232 - 239 54 | .byte 44, 88, 176, 125, 250, 233, 207, 131 // 240 - 247 55 | .byte 27, 54, 108, 216, 173, 71, 142, 1 // 248 - 255 56 | 57 | gf256_log: // Galois field 256 logarithm table 58 | .byte -1, 0, 1, 25, 2, 50, 26, 198 // 0 - 7 59 | .byte 3, 223, 51, 238, 27, 104, 199, 75 // 8 - 15 60 | .byte 4, 100, 224, 14, 52, 141, 239, 129 // 16 - 23 61 | .byte 28, 193, 105, 248, 200, 8, 76, 113 // 24 - 31 62 | .byte 5, 138, 101, 47, 225, 36, 15, 33 // 32 - 39 63 | .byte 53, 147, 142, 218, 240, 18, 130, 69 // 40 - 47 64 | .byte 29, 181, 194, 125, 106, 39, 249, 185 // 48 - 55 65 | .byte 201, 154, 9, 120, 77, 228, 114, 166 // 56 - 63 66 | .byte 6, 191, 139, 98, 102, 221, 48, 253 // 64 - 71 67 | .byte 226, 152, 37, 179, 16, 145, 34, 136 // 72 - 79 68 | .byte 54, 208, 148, 206, 143, 150, 219, 189 // 80 - 87 69 | .byte 241, 210, 19, 92, 131, 56, 70, 64 // 88 - 95 70 | .byte 30, 66, 182, 163, 195, 72, 126, 110 // 96 - 103 71 | .byte 107, 58, 40, 84, 250, 133, 186, 61 // 104 - 111 72 | .byte 202, 94, 155, 159, 10, 21, 121, 43 // 112 - 119 73 | .byte 78, 212, 229, 172, 115, 243, 167, 87 // 120 - 127 74 | .byte 7, 112, 192, 247, 140, 128, 99, 13 // 128 - 135 75 | .byte 103, 74, 222, 237, 49, 197, 254, 24 // 136 - 143 76 | .byte 227, 165, 153, 119, 38, 184, 180, 124 // 144 - 151 77 | .byte 17, 68, 146, 217, 35, 32, 137, 46 // 152 - 159 78 | .byte 55, 63, 209, 91, 149, 188, 207, 205 // 160 - 167 79 | .byte 144, 135, 151, 178, 220, 252, 190, 97 // 168 - 175 80 | .byte 242, 86, 211, 171, 20, 42, 93, 158 // 176 - 183 81 | .byte 132, 60, 57, 83, 71, 109, 65, 162 // 184 - 191 82 | .byte 31, 45, 67, 216, 183, 123, 164, 118 // 192 - 199 83 | .byte 196, 23, 73, 236, 127, 12, 111, 246 // 200 - 207 84 | .byte 108, 161, 59, 82, 41, 157, 85, 170 // 208 - 215 85 | .byte 251, 96, 134, 177, 187, 204, 62, 90 // 216 - 223 86 | .byte 203, 89, 95, 176, 156, 169, 160, 81 // 224 - 231 87 | .byte 11, 245, 22, 235, 122, 117, 44, 215 // 232 - 239 88 | .byte 79, 174, 213, 233, 230, 231, 173, 232 // 240 - 247 89 | .byte 116, 214, 244, 234, 168, 80, 88, 175 // 248 - 255 90 | 91 | // polynomials: 92 | msg_poly: .space POLY_SIZE // message polynomial 93 | gen_poly: .space POLY_SIZE // generator polynomial 94 | rem_poly: .space POLY_SIZE // remainder polynomial used in reed-solomon subroutine 95 | tmp_mono: .space POLY_SIZE // scratch monomial 96 | tmpA_poly: .space POLY_SIZE // scratch polynomial 97 | tmpB_poly: .space POLY_SIZE // scratch polynomial 98 | tmpC_poly: .space POLY_SIZE // scratch polynomial 99 | prdA_poly: .space POLY_SIZE // scratch polynomial for polynomial multiplication (operand A) 100 | prdB_poly: .space POLY_SIZE // scratch polynomial for polynomial multiplication (operand B) 101 | sum_poly: .space POLY_SIZE // scratch polynomial for polynomial addition (sum) 102 | // 103 | // struct polynomial { 104 | // byte length; // number of terms 105 | // byte terms[]; // array of terms 106 | // } 107 | // 108 | // example: [5, 3, 2, 0, 4, 9] 109 | // = 3x^0 + 2x^1 + 0x^2 + 4x^3 + 9x^4 (5 terms) 110 | // 111 | .text 112 | 113 | gf256_mul: // ***** multiplication in GF(256) ***** 114 | // r0 - product 115 | // r1 - unused 116 | // r2 - operand A 117 | // r3 - operand B 118 | push {r4-r11, lr} // save caller's vars + return address 119 | 120 | mov r0, #0 // init product 121 | orr r4, r2, r3 // if either A or B are zero, then r4 = 0 122 | cmp r4, r0 // check if r4 = 0 123 | beq _gf256m_done // leave routine; 0 * n = 0 124 | 125 | ldr r4, =gf256_log // pointer to logarithm table 126 | ldr r5, =gf256_anti // pointer to anti-logarithm table 127 | ldrb r6, [r4, r2] // gf256_log[r2] 128 | ldrb r7, [r4, r3] // gf256_log[r3] 129 | add r6, r6, r7 // gf256_log[r2] + gf256_log[r3]; modulo operand A 130 | 131 | mov r7, #255 // load modulo operand B = Galois field size - 1 132 | udiv r8, r6, r7 // (a / b) 133 | umull r10, r9, r8, r7 // (a / b) * b (throw away rdhi=r9) 134 | sub r8, r6, r10 // a - ((a / b) * b) 135 | ldrb r0, [r5, r8] // gf256_anti[r6 % 255] 136 | _gf256m_done: 137 | pop {r4-r11, lr} // restore caller's vars + return address 138 | bx lr // return from subroutine 139 | 140 | gf256_inv: // ***** inverse in GF(256) ***** 141 | // r0 - inverse of R2 142 | // r1 - error status; error if non-zero 143 | // r2 - number to invert in GF(256) 144 | // r3 - unused 145 | push {r4-r11, lr} // save caller's vars + return address 146 | 147 | mov r1, #0 // init error status 148 | cmp r2, #0 // assert no error 149 | beq _gf256i_err // if (r2 != 0) then error occurred 150 | ldr r5, =gf256_log // pointer to logarithm table 151 | ldr r6, =gf256_anti // pointer to anti-logarithm table 152 | ldrb r7, [r5, r2] // gf256_log[r2] 153 | 154 | mov r4, #255 // Galois field size - 1 155 | sub r4, r4, r7 // 255 - gf256_log[r2] 156 | ldrb r0, [r6, r4] // gf256_anti[255 - gf256_log[r2]] 157 | b _gf256i_done // leave subroutine 158 | _gf256i_err: 159 | mov r1, #1 // return error code 160 | _gf256i_done: 161 | pop {r4-r11, lr} // restore caller's vars + return address 162 | bx lr // return from subroutine 163 | 164 | gf256_div: // ***** division in GF(256) ***** 165 | // r0 - quotient 166 | // r1 - error status; error if non-zero 167 | // r2 - operand A 168 | // r3 - operand B 169 | push {r4-r11, lr} // save caller's vars + return address 170 | 171 | mov r0, #0 // init quotient 172 | mov r1, #0 // init error 173 | cmp r2, #0 // check if we can early exit 174 | beq _gf256d_done // 0 / x = 0 175 | 176 | cmp r3, #0 // assert no error 177 | beq _gf256d_err1 // if r3 == 0, error (div by zero) 178 | 179 | mov r4, r2 // retain operand A 180 | mov r2, r3 // load operand B 181 | bl gf256_inv // invert operand B 182 | 183 | cmp r1, #0 // assert no error 184 | bne _gf256d_err2 // if r0 != 0, then error occurred 185 | 186 | mov r2, r4 // load operand A 187 | mov r3, r0 // load inverted operand B 188 | bl gf256_mul // GF(256) multiply; return product in r0 189 | 190 | b _gf256d_done // return quotient 191 | _gf256d_err1: 192 | mov r1, #1 // divide by zero error 193 | _gf256d_err2: 194 | mov r1, #2 // GF(256) invert error 195 | _gf256d_done: 196 | pop {r4-r11, lr} // restore caller's vars + return address 197 | bx lr // return from subroutine 198 | 199 | poly_clr: // ***** polynomial clear ***** 200 | // r0 - pointer to polynomial to clear 201 | // r1 - unused 202 | // r2 - unused 203 | // r3 - unused 204 | push {r4-r11, lr} // save caller's vars + return address 205 | 206 | ldrb r4, [r0] // p.length 207 | mov r5, #0 // empty term 208 | mov r6, r5 // i = 0 209 | _pclr_loop: 210 | strb r5, [r0, r6] // p.terms[i] = 0 211 | add r6, r6, #1 // i++ 212 | cmp r6, r4 // check loop condition 213 | ble _pclr_loop // while (i <= p.length) 214 | 215 | pop {r4-r11, lr} // restore caller's vars + return address 216 | bx lr // return from subroutine 217 | 218 | poly_norm: // ***** polynomial normalization ***** 219 | // r0 - (n) pointer to store normalized polynomial 220 | // r1 - unused 221 | // r2 - (p) pointer to polynomial to normalize 222 | // r3 - unused 223 | push {r4-r11, lr} // save caller's vars + return address 224 | 225 | ldrb r4, [r2] // load p.length 226 | mov r7, r4 // max_nz = p.length - 1 227 | mov r5, r4 // i = p.length - 1 228 | _pnorm_nzloop: 229 | ldrb r6, [r2, r4] // p.terms[i] 230 | cmp r6, #0 // if (p.terms[i] != 0): 231 | bne _pnorm_maxnz // break 232 | 233 | sub r4, r4, #1 // i-- 234 | mov r7, r4 // max_nz = i - 1 235 | cmp r5, r4 // check loop condition 236 | bne _pnorm_nzloop // while (i >= 0) 237 | _pnorm_maxnz: 238 | cmp r7, #0 // leave if negative, shouldn't happen... 239 | blt _pnorm_done // if (max_nz < 0) 240 | 241 | strb r7, [r0] // n.length = max_nz 242 | add r7, #1 // max_nz += 1 243 | mov r5, #0 // j = 0 244 | _pnorm_norm_loop: 245 | add r6, r5, #1 // y = j + 1 246 | ldrb r8, [r2, r6] // r8 = p[y] 247 | strb r8, [r0, r6] // n[y] = p[y] 248 | add r5, r5, #1 // j++ 249 | cmp r5, r7 // check loop condition 250 | ble _pnorm_norm_loop // while (j < max_nz+1) 251 | b _pnorm_done // return 252 | _pnorm_skip: // already normalized 253 | mov r0, r2 // move pointers 254 | _pnorm_done: 255 | pop {r4-r11, lr} // restore caller's vars + return address 256 | bx lr // return from subroutine 257 | 258 | poly_add: // ***** polynomial addition ***** 259 | // r0 - pointer to store sum polynomial 260 | // r1 - unused 261 | // r2 - pointer to operand A polynomial 262 | // r3 - pointer to operand B polynomial 263 | push {r4-r11, lr} // save caller's vars + return address 264 | 265 | ldrb r5, [r2] // A.length 266 | ldrb r6, [r3] // B.length 267 | mov r4, r5 // default to A.length 268 | cmp r6, r4 // check loop condition 269 | ble _padd_init // if (B.length <= A.length) 270 | mov r4, r6 // set to B.Length 271 | _padd_init: // initialize temp sum polynomial 272 | ldr r8, =sum_poly // pointer to temp sum polynomial 273 | push {r0} // save pointer to output polynomial 274 | mov r0, r8 // 275 | bl poly_clr // reset data in temp sum polynomial 276 | pop {r0} // restore pointer to output polynomial 277 | strb r4, [r8] // store sum length 278 | mov r7, #0 // i = 0 279 | _padd_loop: // loop over sum 280 | add r1, r7, #1 // x = i + 1 281 | ldrb r10, [r2, r1] // A.terms[x] 282 | ldrb r11, [r3, r1] // B.terms[x] 283 | cmp r5, r7 // if ( 284 | ble _padd_A // A.length <= i or 285 | cmp r6, r7 // B.length <= i 286 | ble _padd_A // ) 287 | _padd_AB: 288 | eor r9, r10, r11 // use GF(256) addition its just XOR 289 | b _padd_next // iterate 290 | _padd_A: 291 | cmp r5, r7 // 292 | ble _padd_B // elif (A.length <= i) 293 | ldrb r9, [r2, r1] // use A.terms[x] 294 | b _padd_next // iterate 295 | _padd_B: 296 | ldrb r9, [r3, r1] // else, use B.terms[x] 297 | 298 | _padd_next: // setup next iteration of loop 299 | strb r9, [r8, r1] // set sum.terms[x] 300 | add r7, r7, #1 // i++ 301 | cmp r7, r4 // check loop condition 302 | blt _padd_loop // while (i < sum.length) 303 | _padd_done: 304 | mov r2, r8 // normalize temp sum into output polynomial 305 | bl poly_norm // normalize polynomial 306 | 307 | pop {r4-r11, lr} // restore caller's vars + return address 308 | bx lr // return from subroutine 309 | 310 | poly_mul: // ***** polynomial multiplication ***** 311 | // r0 - pointer to product polynomial 312 | // r1 - unused 313 | // r2 - pointer to operand A polynomial 314 | // r3 - pointer to operand B polynomial 315 | push {r4-r11, lr} // save caller's vars + return address 316 | 317 | push {r0} // save output pointer for later 318 | ldr r0, =prdA_poly // pointer to temp A polynomial 319 | bl poly_clr // clear temp A polynomial 320 | ldrb r5, [r2] // A.length 321 | ldrb r6, [r3] // B.length 322 | add r7, r5, r6 // calculate temp A polynomial size 323 | strb r7, [r0] // tempA.length = A.length + B.length 324 | 325 | mov r8, #0 // i = 0 326 | _pmul_loop_a: // outer loop over operand A 327 | mov r9, #0 // j = 0 328 | _pmul_loop_b: // inner loop over operand B 329 | add r0, r8, #1 // x = i + 1 330 | add r1, r9, #1 // y = j + 1 331 | ldrb r10, [r2, r0] // A.terms[x] 332 | ldrb r11, [r3, r1] // B.terms[y] 333 | 334 | cmp r10, #0 // if ( 335 | beq _pmul_next_b // A.terms[x] == 0 or 336 | cmp r11, #0 // B.terms[y] == 0 337 | beq _pmul_next_b // ) then skip this iteration 338 | 339 | push {r2, r3} // store pointers to polynomial operands 340 | add r4, r0, r1 // x + y 341 | 342 | mov r2, r10 // operand A = A.terms[x] 343 | mov r3, r11 // operand B = B.terms[y] 344 | bl gf256_mul // perform GF(256) multiplication 345 | push {r0} // store GF(256) product 346 | 347 | ldr r0, =prdB_poly // pointer to tempB polynomial 348 | bl poly_clr // clear tempB polynomial for current iteration 349 | mov r3, r0 // use tempB polynomial as operand B in polynomial addition 350 | add r7, r8, r9 // i + j 351 | add r7, r7, #1 // adjust to one-indexing 352 | strb r7, [r3] // tempB.length = i + j + 1 353 | pop {r0} // restore GF(256) product 354 | sub r4, r4, #1 // adjust indexing to (x+y)-1 355 | strb r0, [r3, r4] // tempB.terms[x+y] = GF(256) product of A[x] & B[y] 356 | 357 | ldr r0, =prdA_poly // pointer to tempA polynomial 358 | mov r2, r0 // use tempA polynomial as operand and output 359 | bl poly_add // perform polynomial addition 360 | 361 | pop {r2, r3} // restore polynomial operand pointers 362 | _pmul_next_b: 363 | add r9, r9, #1 // j++ 364 | cmp r9, r6 // check loop condition 365 | blt _pmul_loop_b // while (j < B.length) 366 | _pmul_next_a: 367 | add r8, r8, #1 // i++ 368 | cmp r8, r5 // check loop condition 369 | blt _pmul_loop_a // while (i < A.length) 370 | 371 | pop {r0} // restore output pointer 372 | ldr r2, =prdA_poly // pointer to tempA polynomial 373 | bl poly_norm // normalize polynomial 374 | 375 | pop {r4-r11, lr} // restore caller's vars + return address 376 | bx lr // return from subroutine 377 | 378 | poly_rem: // ***** Remainder of Polynomial Long Division ***** 379 | // r0 - pointer to remainder polynomial 380 | // r1 - error status; error if non-zero 381 | // r2 - pointer to numerator polynomial 382 | // r3 - pointer denominator polynomial 383 | push {r4-r11, lr} // save caller's vars + return address 384 | 385 | mov r1, #0 // init error 386 | mov r10, r3 // retain pointer to denominator 387 | mov r11, r0 // retain pointer to remainder polynomial 388 | ldrb r4, [r2] // numerator terms 389 | ldrb r5, [r10] // denominator terms 390 | mov r6, #0 // i = 0 391 | _prem_eq_loop: // check equality of operands 392 | ldrb r7, [r2] // numerator[i] 393 | ldrb r8, [r10] // denominator[i] 394 | cmp r8, r7 // compare ith terms 395 | bne _prem_neq // if (numerator[i] != denominator[i]); no error 396 | 397 | add r6, r6, #1 // i++ 398 | cmp r6, r4 // check loop condition 399 | ble _prem_eq_loop // while (i <= numerator.length) 400 | 401 | b _prem_err1 // error: numerator and denominator are equal 402 | _prem_neq: // numerator != denominator, all good 403 | mov r6, #0 // i = 0 404 | 405 | _prem_init_loop: // initialize remainder to numerator 406 | ldrb r7, [r2, r6] // numerator[i] 407 | strb r7, [r11, r6] // remainder[i] = numerator[i] 408 | add r6, r6, #1 // i++ 409 | cmp r6, r4 // check loop condition 410 | ble _prem_init_loop // while (i <= numerator.length) 411 | 412 | ldrb r7, [r10] // denominator.length 413 | ldr r5, =tmp_mono // pointer to temporary monomial 414 | _prem_loop: 415 | ldrb r6, [r11] // remainder.length 416 | ldrb r2, [r11, r6] // remainder.terms[-1]; operand A 417 | ldrb r3, [r10, r7] // denominator.terms[-1]; operand B 418 | bl gf256_div // GF(256) divide; A / B 419 | 420 | mov r9, r0 // retain GF(256) quotient 421 | cmp r1, #0 // assert no error 422 | bne _prem_err2 // if (r1 != 0) then error occurred 423 | 424 | mov r0, r5 // pointer to tmp_mono 425 | bl poly_clr // reset tmp_mono data 426 | sub r8, r6, r7 // calc size of temp monomial 427 | add r8, r8, #1 // adjust to zero-indexing 428 | strb r8, [r5] // tmp_mono.length = rem.length - denom.length 429 | strb r9, [r5, r8] // set tmp_mono coefficient as GF(256) quotient 430 | 431 | ldr r8, =tmpC_poly // pointer to temporary polynomial; divisor 432 | mov r0, r8 // store polynomial product in temporary polynomial 433 | bl poly_clr // reset tmpC_poly 434 | mov r2, r10 // pointer to denominator; operand A 435 | mov r3, r5 // pointer to temp monomial; operand B 436 | bl poly_mul // polynomial multiply; A * B 437 | 438 | mov r0, r11 // pointer to remainder polynomial; sum 439 | mov r2, r11 // pointer to remainder polynomial; operand A 440 | mov r3, r8 // pointer to temporary polynomial; operand B 441 | bl poly_add // polynomial add; A + B 442 | 443 | cmp r6, r7 // check loop condition 444 | bge _prem_loop // while (remainder.length >= denominator.length) 445 | mov r1, #0 // no error occurred, if we get here 446 | 447 | bl poly_norm // normalize remainder polynomial 448 | b _prem_done // return 449 | _prem_err1: 450 | mov r1, #1 // numerator == denominator 451 | _prem_err2: 452 | mov r1, #2 // error in gf256_div 453 | _prem_done: 454 | pop {r4-r11, lr} // restore caller's vars + return address 455 | bx lr // return from subroutine 456 | 457 | new_gen_poly: // ***** create generator polynomial ***** 458 | // r0 - pointer to store generator polynomial 459 | // r1 - unused 460 | // r2 - ECW per block = polynomial length 461 | // r3 - unused 462 | push {r4-r11, lr} // save caller's vars + return address 463 | 464 | mov r4, #1 // init g_poly to 1x^0 465 | strb r4, [r0] // g_poly[0] = length 466 | strb r4, [r0, #1] // g_poly[1] = 1 467 | 468 | ldr r10, =tmpA_poly // pointer to scratch polynomial A 469 | ldr r5, =tmpB_poly // pointer to scratch polynomial B 470 | ldr r7, =gf256_anti // pointer to gf256_anti table 471 | mov r9, r2 // retain ECW per block 472 | 473 | mov r8, #0 // i = 0 474 | _gpoly_loop: // build generator polynomial 475 | mov r6, #2 // load tmp_poly length 476 | strb r6, [r5] // tmpB_poly[0] = length 477 | 478 | ldrb r6, [r7, r8] // load first term from anti-logarithm table 479 | strb r6, [r5, #1] // tmpB_poly[1] = (gf256_anti[i])x^0 480 | mov r6, #1 // load second term 481 | strb r6, [r5, #2] // tmpB_poly[2] = 1x^1 482 | 483 | mov r6, #0 // j = 0 484 | _gpoly_copy: // copy current generator polynomial to scratch poly A 485 | ldrb r4, [r0, r6] // r4 = g_poly[j] 486 | strb r4, [r10, r6] // tmpA_poly[j] = g_poly[j] 487 | add r6, r6, #1 // j++ 488 | cmp r6, r9 // compare index and ECW per block 489 | ble _gpoly_copy // while (j <= g_poly.length) 490 | _gpoly_iter: 491 | mov r2, r10 // pointer to scratch polynomial A; operand A 492 | mov r3, r5 // pointer to scratch polynomial B; operand B 493 | bl poly_mul // polynomial multiply; r0 = generator polynomial 494 | 495 | add r8, r8, #1 // i++ 496 | cmp r8, r9 // compare i and ECW per block 497 | blt _gpoly_loop // while (i < g_poly.length) 498 | 499 | pop {r4-r11, lr} // restore caller's vars + return address 500 | bx lr // return from subroutine 501 | 502 | new_msg_poly: // ***** create message polynomial ***** 503 | // r0 - pointer to store message polynomial 504 | // r1 - unused 505 | // r2 - pointer to message 506 | // r3 - message length = polynomial length 507 | push {r4-r11, lr} // save caller's vars + return address 508 | 509 | mov r4, r3 // i = msg_len 510 | strb r4, [r0] // m_poly[0] = length 511 | sub r4, r4, #1 // i-- (zero index) 512 | mov r5, #1 // j = 1 ; m_poly idx 513 | _mpoly_loop: // loop over message 514 | ldrb r6, [r2, r4] // r5 = msg[i] 515 | strb r6, [r0, r5] // m_poly[j] = msg[i] 516 | sub r4, r4, #1 // i-- 517 | add r5, r5, #1 // j++ 518 | cmp r5, r3 // compare m_poly index with message length 519 | ble _mpoly_loop // while (j <= msg_len) 520 | 521 | pop {r4-r11, lr} // restore caller's vars + return address 522 | bx lr // return from subroutine 523 | 524 | reed_solomon: // ***** Reed-Solomon Error Correction ***** 525 | // r0 - pointer to error correction block 526 | // r1 - pointer to data block 527 | // r2 - data block capacity 528 | // r3 - error correction words (ECW) capacity 529 | push {r4-r11, lr} // save caller's vars + return address 530 | 531 | ldr r4, =msg_poly // pointer to message polynomial 532 | ldr r5, =gen_poly // pointer to generator polynomial 533 | mov r6, r2 // retain block capacity 534 | mov r7, r3 // retain ECW capacity 535 | push {r0} // store output pointer for later 536 | 537 | mov r0, r4 // pointer to message polynomial 538 | bl poly_clr // reset message polynomial data 539 | mov r0, r5 // pointer to generator polynomial 540 | bl poly_clr // reset generator polynomial data 541 | 542 | mov r0, r4 // pointer to message polynomial 543 | mov r2, r1 // pointer to data block 544 | mov r3, r6 // block capacity 545 | bl new_msg_poly // create message polynomial 546 | 547 | mov r0, r5 // pointer to generator polynomial 548 | mov r2, r7 // use ECW capacity 549 | bl new_gen_poly // create generator polynomial 550 | push {r1} // don't clobber pointer to data block 551 | 552 | ldr r8, =tmpB_poly // pointer to temporary polynomial 553 | mov r0, r8 // 554 | bl poly_clr // reset temp monomial data 555 | add r9, r7, #1 // terms = degree + 1 556 | strb r9, [r8] // tmpB_poly = ECW capacity + 1 557 | mov r10, #1 // coefficient 558 | strb r10, [r8, r9] // tmpB_poly = 1x^(ECW capacity) 559 | 560 | ldr r9, =tmpA_poly // pointer to temp polynomial 561 | mov r0, r9 // 562 | bl poly_clr // reset temp polynomial data 563 | mov r2, r4 // pointer to message polynomial 564 | mov r3, r8 // pointer to temp monomial 565 | bl poly_mul // perform polynomial multiplication 566 | 567 | ldr r0, =rem_poly // output to remainder polynomial 568 | bl poly_clr // reset remainder polynomial data 569 | mov r2, r9 // operand A; pointer to (msg_poly * monomial) 570 | mov r3, r5 // operand B; pointer to generator polynomial 571 | bl poly_rem // find remainder polynomial of A / B 572 | 573 | mov r5, r0 // retain pointer to remainder polynomial 574 | mov r4, r1 // retain error status of poly_rem 575 | pop {r1} // restore data block pointer 576 | pop {r0} // restore output pointer 577 | cmp r4, #0 // assert no error 578 | bne _rs_err1 // if (r4 != 0) then error occurred in poly_rem 579 | 580 | ldrb r4, [r5] // poly_rem.length 581 | mov r6, #0 // i = 0 582 | _rs_copy: // copy remainder polynomial terms into ECW block (reversed) 583 | add r7, r6, #1 // x = i + 1; one-indexing 584 | sub r9, r4, r6 // y = poly_rem.length - i 585 | sub r9, r9, #1 // account for poly offset (length byte) 586 | ldrb r8, [r5, r7] // rem_poly[x] 587 | strb r8, [r0, r9] // ECW[y] = rem_poly[x] 588 | 589 | add r6, r6, #1 // i++ 590 | cmp r6, r4 // check loop condition 591 | blt _rs_copy // while (i < poly_rem.length) 592 | 593 | b _rs_done // return 594 | _rs_err1: 595 | nop // error occurred: don't write to ECW block 596 | _rs_done: 597 | pop {r4-r11, lr} // restore caller's vars + return address 598 | bx lr // return from subroutine 599 | 600 | .end // end of source 601 | -------------------------------------------------------------------------------- /docs/jupyter/lunch-and-learn.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "source": [ 6 | "# Useless Information: QR Codes\n", 7 | "\n", 8 | "A brief walkthrough on how QR codes are generated.\n", 9 | "\n", 10 | "" 21 | ], 22 | "metadata": {} 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "source": [ 27 | "## Mathematics Refresher\n", 28 | "\n", 29 | "
\n", 30 | "\n", 31 | "**Monomial**: $3x^2$ (single term)\n", 32 | "\n", 33 | "**Polynomial**: $4x^6+16x^4+2x^2+5x+1$ (multiple terms or monomials)\n", 34 | "\n", 35 | "
\n", 36 | "\n", 37 | "### Logarithms \n", 38 | "\n", 39 | "$y = \\log_b x \\implies b^y=x$\n", 40 | "\n", 41 | "Example: $\\log_2(8) = 3 \\implies 2^3=8$\n", 42 | "\n", 43 | "
\n", 44 | "\n", 45 | "#### Multiplication via logarithms - A trivial example with base 2\n", 46 | "\n", 47 | "$128 \\times 512 = ?$\n", 48 | "\n", 49 | "$\\log_2(128) = 7,\\; \\log_2(512) = 9$\n", 50 | "\n", 51 | "$128 \\times 512 = 2^7 \\times 2^9 = 2^{16} = 65536$\n", 52 | "\n", 53 | "
\n", 54 | "\"A\n", 55 | "\n", 56 | "This is how you multiply on a slide rule!" 57 | ], 58 | "metadata": {} 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "source": [ 63 | "## Some Fun Facts About QR Codes\n", 64 | "\n", 65 | "(Also known as, I skimmed the wikipedia page - https://en.wikipedia.org/wiki/QR_code)\n", 66 | "\n", 67 | "- QR code -> Quick Response code\n", 68 | "- 1994 Masahiro Hara at Denso Wave (Japanese automotive company)\n", 69 | "- A matrix barcode (2D barcode) -> faster to read and stores more data\n", 70 | "- Error correction (Reed-Solomon) allows damaged QR Codes to still be read\n", 71 | "\n", 72 | "
\n", 73 | "\n", 74 | "\"Hello\n", 75 | "   \n", 76 | "\"Damaged" 77 | ], 78 | "metadata": {} 79 | }, 80 | { 81 | "cell_type": "markdown", 82 | "source": [ 83 | "
" 84 | ], 85 | "metadata": {} 86 | }, 87 | { 88 | "cell_type": "markdown", 89 | "source": [ 90 | "## Let's Make a QR Code" 91 | ], 92 | "metadata": {} 93 | }, 94 | { 95 | "cell_type": "markdown", 96 | "source": [ 97 | "### Choose an Encoding Mode\n", 98 | "\n", 99 | "| Mode | Maximum Character (40-L) | Mode Indicator |\n", 100 | "| ------------ | ------------------------ | -------------- |\n", 101 | "| Numeric | 7089 characters | 0001 |\n", 102 | "| Alphanumeric | 4296 characters | 0010 |\n", 103 | "| Byte | 2953 characters | 0100 |\n", 104 | "| Kanji | 1817 characters | 0111 |" 105 | ], 106 | "metadata": {} 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": null, 111 | "source": [ 112 | "MODE_NUMERIC = 1 # 0001\n", 113 | "MODE_ALPHANUM = 2 # 0010\n", 114 | "MODE_BYTE = 4 # 0100\n", 115 | "MODE_KANJI = 8 # 1000" 116 | ], 117 | "outputs": [], 118 | "metadata": {} 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": null, 123 | "source": [ 124 | "# Declare payload and encode it (byte mode)\n", 125 | "\n", 126 | "# UTF-8 encode -> hex bytes -> 8-bit binary\n", 127 | "def encode_byte_mode(s):\n", 128 | " as_hex = [c.encode('utf-8').hex() for c in s]\n", 129 | " return [bin(int(byte, 16))[2:].zfill(8) for byte in as_hex]\n", 130 | "\n", 131 | "# convert integer to bits\n", 132 | "def int_to_bits(i, word_size):\n", 133 | " return bin(int(hex(i), 16))[2:].zfill(word_size)\n", 134 | "\n", 135 | "payload = 'https://github.com/barrettotte'\n", 136 | "encoded = encode_byte_mode(payload)\n", 137 | "encoded_len = len(encoded)\n", 138 | "mode = int_to_bits(MODE_BYTE, 4)\n", 139 | "\n", 140 | "print(f\"encoded '{payload}' to\\n\\n{encoded}\\n\\nsize: {encoded_len} byte(s)\")\n", 141 | "print([hex(ord(b)) for b in payload])\n", 142 | "print(f'\\nmode: {mode}')" 143 | ], 144 | "outputs": [], 145 | "metadata": {} 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "source": [ 150 | "### Choose an Error Correction Level\n", 151 | "\n", 152 | "Higher error correction, less character capacity\n", 153 | "\n", 154 | "| Level | Name | Recovery | Level Indicator |\n", 155 | "| ----- | -------- | -------- | --------------- |\n", 156 | "| L | Low | 7% | 01 |\n", 157 | "| M | Medium | 15% | 00 |\n", 158 | "| Q | Quartile | 25% | 11 |\n", 159 | "| H | High | 30% | 10 |" 160 | ], 161 | "metadata": {} 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": null, 166 | "source": [ 167 | "ERROR_L = 1 # 01\n", 168 | "ERROR_M = 0 # 00\n", 169 | "ERROR_Q = 3 # 11\n", 170 | "ERROR_H = 2 # 10\n", 171 | "err_lvl = ERROR_Q" 172 | ], 173 | "outputs": [], 174 | "metadata": {} 175 | }, 176 | { 177 | "cell_type": "markdown", 178 | "source": [ 179 | "### Find the Version\n", 180 | "\n", 181 | "Versions 1-40, select smallest possible!\n", 182 | "\n", 183 | "| Version + Error Correction Level | Numeric | Alphanumeric | Byte | Kanji |\n", 184 | "| -------------------------------- | ------- | ------------ | ---- | ----- |\n", 185 | "| 1-L | 41 | 25 | 17 | 10 |\n", 186 | "| 1-M | 34 | 20 | 14 | 8 |\n", 187 | "| 1-Q | 27 | 16 | 11 | 7 |\n", 188 | "| 1-H | 17 | 10 | 7 | 4 |\n", 189 | "| ... | ... | ... | ... | ... |\n", 190 | "| 3-L | 127 | 77 | 53 | 32 |\n", 191 | "| 3-M | 101 | 61 | 42 | 26 |\n", 192 | "| 3-Q | 77 | 47 | 32 | 20 |\n", 193 | "| 3-H | 58 | 35 | 24 | 15 |\n", 194 | "| ... | ... | ... | ... | ... |\n", 195 | "| 40-L | 7089 | 4296 | 2953 | 1817 |\n", 196 | "| 40-M | 5596 | 3391 | 2331 | 1435 |\n", 197 | "| 40-Q | 3993 | 2420 | 1663 | 1024 |\n", 198 | "| 40-H | 3057 | 1852 | 1273 | 784 |\n" 199 | ], 200 | "metadata": {} 201 | }, 202 | { 203 | "cell_type": "code", 204 | "execution_count": null, 205 | "source": [ 206 | "# trimmed down version of capacity table\n", 207 | "BYTE_MODE_CAPACITY_LOOKUP = [\n", 208 | " # L, M, Q, H\n", 209 | " [0, 0, 0, 0], # (one-indexing)\n", 210 | " [17, 14, 11, 7], # 1\n", 211 | " [32, 26, 20, 14], # 2\n", 212 | " [53, 42, 32, 24], # 3\n", 213 | " [78, 62, 46, 34], # 4\n", 214 | " [106, 84, 60, 44], # 5\n", 215 | " # and so on...to 40\n", 216 | "]\n", 217 | "\n", 218 | "# fixes issue with LMQH ordering\n", 219 | "ERROR_IDX_TO_LOOKUP = [1, 0, 3, 2]\n", 220 | "\n", 221 | "# find version to use based on payload size and error correction\n", 222 | "def get_version(size, err_lvl):\n", 223 | " err_idx = ERROR_IDX_TO_LOOKUP[err_lvl]\n", 224 | " for col, row in enumerate(BYTE_MODE_CAPACITY_LOOKUP):\n", 225 | " if row[err_idx] > size:\n", 226 | " return col\n", 227 | " raise Exception(\"couldn't find version\")\n", 228 | "\n", 229 | "# find smallest version for our payload\n", 230 | "version = get_version(encoded_len, err_lvl)\n", 231 | "assert version == 3 # should be using a 3-Q configuration\n", 232 | "\n", 233 | "print(f'payload is {encoded_len} character(s)')\n", 234 | "print(f'selected version {version}')" 235 | ], 236 | "outputs": [], 237 | "metadata": {} 238 | }, 239 | { 240 | "cell_type": "markdown", 241 | "source": [ 242 | "### Fetch Error Correction Configuration\n", 243 | "\n", 244 | "The scheme used to break up our encoded bytes into groups/blocks to run through error correction." 245 | ], 246 | "metadata": {} 247 | }, 248 | { 249 | "cell_type": "code", 250 | "execution_count": null, 251 | "source": [ 252 | "EC_CONFIG_LOOKUP = [\n", 253 | " [], # L M Q H\n", 254 | " [[19, 7, 1, 19, 0, 0], [16, 10, 1, 16, 0, 0], [13, 13, 1, 13, 0, 0], [9, 17, 1, 9, 0, 0]], # 1\n", 255 | " [[34, 10, 1, 34, 0, 0], [28, 16, 1, 28, 0, 0], [22, 22, 1, 22, 0, 0], [16, 28, 1, 16, 0, 0]], # 2\n", 256 | " [[55, 15, 1, 55, 0, 0], [44, 26, 1, 44, 0, 0], [34, 18, 2, 17, 0, 0], [26, 22, 2, 13, 0, 0]], # 3\n", 257 | " [[80, 20, 1, 80, 0, 0], [64, 18, 2, 32, 0, 0], [48, 26, 2, 24, 0, 0], [36, 16, 4, 9, 0, 0]], # 4\n", 258 | " [[108, 26, 1, 108, 0, 0], [86, 24, 2, 43, 0, 0], [62, 18, 2, 15, 2, 16], [46, 22, 2, 11, 2, 12]], # 5\n", 259 | " # and so on...to 40\n", 260 | "]\n", 261 | "\n", 262 | "def get_ec_config(version, err_lvl):\n", 263 | " return EC_CONFIG_LOOKUP[version][ERROR_IDX_TO_LOOKUP[err_lvl]]\n", 264 | "\n", 265 | "# fetch error correction configuration\n", 266 | "ec_config = get_ec_config(version, err_lvl)\n", 267 | "capacity = ec_config[0]\n", 268 | "capacity_bits = capacity * 8\n", 269 | "\n", 270 | "print('error correction config:')\n", 271 | "print(f' Total data words = {capacity}')\n", 272 | "print(f' Error correction words per block = {ec_config[1]}')\n", 273 | "print(f' Number of blocks in group 1 = {ec_config[2]}')\n", 274 | "print(f' Number of data words in each group 1 block = {ec_config[3]}')\n", 275 | "print(f' Number of blocks in group 2 = {ec_config[4]}')\n", 276 | "print(f' Number of data words in each group 2 block = {ec_config[5]}')" 277 | ], 278 | "outputs": [], 279 | "metadata": {} 280 | }, 281 | { 282 | "cell_type": "markdown", 283 | "source": [ 284 | "## Fetch Character Count Indicator\n", 285 | "\n", 286 | "Depending on version and encoding mode, the payload size will need to take up more bits.\n", 287 | "\n", 288 | "| Version Range | Numeric | Alphanumeric | Byte | Kanji |\n", 289 | "| ------------- | ------- | ------------ | ------- | ------- |\n", 290 | "| 1-9 | 10 bits | 9 bits | 8 bits | 8 bits |\n", 291 | "| 10-26 | 12 bits | 11 bits | 16 bits | 10 bits |\n", 292 | "| 27-40 | 14 bits | 13 bits | 16 bits | 12 bits |" 293 | ], 294 | "metadata": {} 295 | }, 296 | { 297 | "cell_type": "code", 298 | "execution_count": null, 299 | "source": [ 300 | "# is test between low and high (inclusive)?\n", 301 | "def is_between(low, high, test):\n", 302 | " return test >= low and test <= high\n", 303 | "\n", 304 | "# determine character count indicator\n", 305 | "def get_count(size, version, mode):\n", 306 | " if int(mode, 2) == MODE_BYTE:\n", 307 | " if is_between(1, 9, version):\n", 308 | " word_size = 8\n", 309 | " elif is_between(10, 40, version):\n", 310 | " word_size = 16\n", 311 | " else:\n", 312 | " raise Exception(\"Invalid version\")\n", 313 | " else:\n", 314 | " raise Exception(\"Only byte mode implemented!\")\n", 315 | " return int_to_bits(size, word_size)\n", 316 | "\n", 317 | "count = get_count(encoded_len, version, mode)\n", 318 | "\n", 319 | "print(f\"size: {encoded_len} byte(s) - == { hex(int(count, 2))} char count: {count}\")\n", 320 | "print(f\"version {version} with max capacity of {capacity} byte(s)\", end='')\n", 321 | "print(f\" or {capacity_bits} bit(s)\")" 322 | ], 323 | "outputs": [], 324 | "metadata": {} 325 | }, 326 | { 327 | "cell_type": "markdown", 328 | "source": [ 329 | "## Pad the Payload\n", 330 | "\n", 331 | "Before feeding the encoded payload into the error correction algorithm, it needs to be byte/bit padded." 332 | ], 333 | "metadata": {} 334 | }, 335 | { 336 | "cell_type": "code", 337 | "execution_count": null, 338 | "source": [ 339 | "# utility to build string of byte/bit size\n", 340 | "def byte_size_str(d):\n", 341 | " size = len(d)\n", 342 | " return f\"{size} bit(s) => {size // 8} byte(s), {size % 8} bit(s)\"\n", 343 | "\n", 344 | "seg = mode + count + ''.join(encoded)\n", 345 | "print(\"seg: {0:0>4X}\".format(int(seg, 2)))\n", 346 | "print('before padding: ' + byte_size_str(seg))\n", 347 | "\n", 348 | "# Add terminator of zeros up to four bits (if there is room)\n", 349 | "terminal_bits = 0\n", 350 | "while terminal_bits < 4 and len(seg) < capacity_bits:\n", 351 | " seg += '0'\n", 352 | " terminal_bits += 1\n", 353 | "\n", 354 | "# pad bits to nearest byte\n", 355 | "while len(seg) % 8 != 0 and len(seg) < capacity_bits:\n", 356 | " seg += '0'\n", 357 | "\n", 358 | "# pad bytes to full capacity (alternating 0xEC and 0x11)\n", 359 | "use_EC = True\n", 360 | "while len(seg) < capacity_bits:\n", 361 | " seg += int_to_bits(int(0xEC), 8) if use_EC else int_to_bits(int(0x11), 8)\n", 362 | " use_EC = not use_EC\n", 363 | "\n", 364 | "print(\"seg: {0:0>4X}\".format(int(seg, 2)))\n", 365 | "print(f'\\nafter padding: {byte_size_str(seg)}')\n", 366 | "assert len(seg) == capacity_bits" 367 | ], 368 | "outputs": [], 369 | "metadata": {} 370 | }, 371 | { 372 | "cell_type": "markdown", 373 | "source": [ 374 | "## Split the Payload\n", 375 | "\n", 376 | "Using error correction configuration, split the payload into groups and blocks" 377 | ], 378 | "metadata": {} 379 | }, 380 | { 381 | "cell_type": "code", 382 | "execution_count": null, 383 | "source": [ 384 | "# split segment into words (bytes)\n", 385 | "code_words = [seg[i: i + 8] for i in range(0, len(seg), 8)]\n", 386 | "print(f'total word(s) = {len(code_words)}')\n", 387 | "\n", 388 | "g1_blocks = [] # only two groups\n", 389 | "g2_blocks = [] # so we can be lazy\n", 390 | "\n", 391 | "ecw_per_block = ec_config[1]\n", 392 | "g1_block_count = ec_config[2]\n", 393 | "g1_data_block_size = ec_config[3]\n", 394 | "g2_block_count = ec_config[4]\n", 395 | "g2_data_block_size = ec_config[5]\n", 396 | "\n", 397 | "print('\\nerror correction config:')\n", 398 | "print(f' Total data words = {capacity}')\n", 399 | "print(f' Error correction words per block = {ecw_per_block}')\n", 400 | "print(f' Number of blocks in group 1 = {g1_block_count}')\n", 401 | "print(f' Number of data words in each group 1 block = {g1_data_block_size}')\n", 402 | "print(f' Number of blocks in group 2 = {g2_block_count}')\n", 403 | "print(f' Number of data words in each group 2 block = {g2_data_block_size}\\n')\n", 404 | "\n", 405 | "# build group 1\n", 406 | "cw_idx = 0\n", 407 | "while len(g1_blocks) < g1_block_count:\n", 408 | " to_idx = g1_data_block_size * (len(g1_blocks) + 1)\n", 409 | " g1_blocks.append(code_words[cw_idx: to_idx])\n", 410 | " cw_idx += g1_data_block_size\n", 411 | "assert len(g1_blocks) == g1_block_count\n", 412 | "\n", 413 | "print(f'group 1 blocks:')\n", 414 | "for i, b in enumerate(g1_blocks):\n", 415 | " print(f'\\nblock {i}: {b}')\n", 416 | "\n", 417 | "# build group 2\n", 418 | "g2_offset = cw_idx\n", 419 | "while len(g2_blocks) < g2_block_count:\n", 420 | " to_idx = (g2_data_block_size * (len(g2_blocks) + 1)) + g2_offset\n", 421 | " g2_blocks.append(code_words[cw_idx:to_idx])\n", 422 | " cw_idx += g2_data_block_size\n", 423 | "assert len(g2_blocks) == g2_block_count\n", 424 | "\n", 425 | "print(f'\\ngroup 2 blocks:')\n", 426 | "for i, b in enumerate(g2_blocks):\n", 427 | " print(f'\\nblock {i}: {b}')" 428 | ], 429 | "outputs": [], 430 | "metadata": {} 431 | }, 432 | { 433 | "cell_type": "markdown", 434 | "source": [ 435 | "## Reed-Solomon Error Correction\n", 436 | "\n", 437 | "An error correction algorithm to allow a damaged payload to still get read correctly." 438 | ], 439 | "metadata": {} 440 | }, 441 | { 442 | "cell_type": "markdown", 443 | "source": [ 444 | "#### A Very High Level Overview of Galois Fields\n", 445 | "\n", 446 | "A Galois or finite field is a field consisting of a finite amount of elements.\n", 447 | "\n", 448 | "A finite field with $p^n$ elements is given by $\\text{GF}(p^n)$, where $p$ is a prime number.\n", 449 | "\n", 450 | "Think of a wagon wheel with $p^n$ spokes.\n", 451 | "\n", 452 | "When $p$ is 2, we can start thinking in binary.\n", 453 | "$\\text{GF}(2^3) = (0,1,2,3,4,5,6,7)$\n", 454 | "\n", 455 | "- $\\text{GF}(8)[0] = 0(2^2) + 0(2^1) + 0(2^0) = 000$\n", 456 | "- $\\text{GF}(8)[1] = 0(2^2) + 0(2^1) + 1(2^0) = 001$\n", 457 | "- $\\text{GF}(8)[2] = 0(2^2) + 1(2^1) + 0(2^0) = 010$\n", 458 | "- $\\text{GF}(8)[3] = 0(2^2) + 1(2^1) + 1(2^0) = 011$\n", 459 | "- $\\text{GF}(8)[4] = 1(2^2) + 0(2^1) + 0(2^0) = 100$\n", 460 | "- $\\text{GF}(8)[5] = 1(2^2) + 0(2^1) + 1(2^0) = 101$\n", 461 | "- $\\text{GF}(8)[6] = 1(2^2) + 1(2^1) + 0(2^0) = 110$\n", 462 | "- $\\text{GF}(8)[7] = 1(2^2) + 1(2^1) + 1(2^0) = 111$\n", 463 | "\n", 464 | "So, any binary number can be represented as a polynomial and vice versa.\n", 465 | "\n", 466 | "$156=10011100 = 1x^7 + 0x^6 + 0x^5 + 1x^4 + 1x^3 + 1x^2 + 0x^1 + 0x^0 = x^7+x^4+x^3+x^2$\n", 467 | "\n", 468 | "
\n", 469 | "\n", 470 | "Finite fields are used in cryptography algorithms since they allow bytes to be easily \n", 471 | "and rapidly scrambled using polynomial arithmetic.\n", 472 | "\n", 473 | "Reed-Solomon error correction uses $\\text{GF}(2^8) = \\text{GF}(256)$." 474 | ], 475 | "metadata": {} 476 | }, 477 | { 478 | "cell_type": "markdown", 479 | "source": [ 480 | "#### Finite Field Arithmetic in $\\text{GF}(256)$\n", 481 | "\n", 482 | "**Addition and subtraction**:\n", 483 | "\n", 484 | "$(x^6+x^4+x+1) + (x^7+x^6+x^3+x) = x^7+x^4+x^3+1\\;\\;(\\text{Coefficients are in GF(2))}$\n", 485 | "\n", 486 | "$01010011 + 11001010 = 10011001 \\implies \\text{XOR}$\n", 487 | "\n", 488 | "**Multiplication**:\n", 489 | "\n", 490 | "I cheat and use a lookup table. But, the algorithm is called [Russian peasant multiplication](https://en.wikipedia.org/wiki/Ancient_Egyptian_multiplication#Russian_peasant_multiplication) which is actually a special case of the algorithm\n", 491 | "used in ancient Egyptian multiplication." 492 | ], 493 | "metadata": {} 494 | }, 495 | { 496 | "cell_type": "code", 497 | "execution_count": null, 498 | "source": [ 499 | "# Add Galois functions\n", 500 | "GF256_SIZE = 256\n", 501 | "\n", 502 | "GF256_ANTILOG = [\n", 503 | " 1, 2, 4, 8, 16, 32, 64, 128, 29, 58, # 0 - 9\n", 504 | " 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, # 10 - 19\n", 505 | " 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, # 20 - 29\n", 506 | " 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, # 30 - 39\n", 507 | " 106, 212, 181, 119, 238, 193, 159, 35, 70, 140, # 40 - 49\n", 508 | " 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, # 50 - 59\n", 509 | " 185, 111, 222, 161, 95, 190, 97, 194, 153, 47, # 60 - 69\n", 510 | " 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, # 70 - 79\n", 511 | " 253, 231, 211, 187, 107, 214, 177, 127, 254, 225, # 80 - 89\n", 512 | " 223, 163, 91, 182, 113, 226, 217, 175, 67, 134, # 90 - 99\n", 513 | " 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, # 100 - 109\n", 514 | " 103, 206, 129, 31, 62, 124, 248, 237, 199, 147, # 110 - 119\n", 515 | " 59, 118, 236, 197, 151, 51, 102, 204, 133, 23, # 120 - 129\n", 516 | " 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, # 130 - 139\n", 517 | " 132, 21, 42, 84, 168, 77, 154, 41, 82, 164, # 140 - 149\n", 518 | " 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, # 150 - 159\n", 519 | " 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, # 160 - 169\n", 520 | " 215, 179, 123, 246, 241, 255, 227, 219, 171, 75, # 170 - 179\n", 521 | " 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, # 180 - 189\n", 522 | " 174, 65, 130, 25, 50, 100, 200, 141, 7, 14, # 190 - 199\n", 523 | " 28, 56, 112, 224, 221, 167, 83, 166, 81, 162, # 200 - 209\n", 524 | " 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, # 210 - 219\n", 525 | " 172, 69, 138, 9, 18, 36, 72, 144, 61, 122, # 220 - 229\n", 526 | " 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, # 230 - 239\n", 527 | " 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, # 240 - 249\n", 528 | " 108, 216, 173, 71, 142, 1 # 250 - 255\n", 529 | "]\n", 530 | "GF256_LOG = [\n", 531 | " -1, 0, 1, 25, 2, 50, 26, 198, 3, 223, # 0 - 9\n", 532 | " 51, 238, 27, 104, 199, 75, 4, 100, 224, 14, # 10 - 19\n", 533 | " 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, # 20 - 29\n", 534 | " 76, 113, 5, 138, 101, 47, 225, 36, 15, 33, # 30 - 39\n", 535 | " 53, 147, 142, 218, 240, 18, 130, 69, 29, 181, # 40 - 49\n", 536 | " 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, # 50 - 59\n", 537 | " 77, 228, 114, 166, 6, 191, 139, 98, 102, 221, # 60 - 69\n", 538 | " 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, # 70 - 79\n", 539 | " 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, # 80 - 89\n", 540 | " 19, 92, 131, 56, 70, 64, 30, 66, 182, 163, # 90 - 99\n", 541 | " 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, # 100 - 109\n", 542 | " 186, 61, 202, 94, 155, 159, 10, 21, 121, 43, # 110 - 119\n", 543 | " 78, 212, 229, 172, 115, 243, 167, 87, 7, 112, # 120 - 129\n", 544 | " 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, # 130 - 139\n", 545 | " 49, 197, 254, 24, 227, 165, 153, 119, 38, 184, # 140 - 149\n", 546 | " 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, # 150 - 159\n", 547 | " 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, # 160 - 169\n", 548 | " 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, # 170 - 179\n", 549 | " 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, # 180 - 189\n", 550 | " 65, 162, 31, 45, 67, 216, 183, 123, 164, 118, # 190 - 199\n", 551 | " 196, 23, 73, 236, 127, 12, 111, 246, 108, 161, # 200 - 209\n", 552 | " 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, # 210 - 219\n", 553 | " 187, 204, 62, 90, 203, 89, 95, 176, 156, 169, # 220 - 229\n", 554 | " 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, # 230 - 239\n", 555 | " 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, # 240 - 249\n", 556 | " 244, 234, 168, 80, 88, 175 # 250 - 255\n", 557 | "]\n", 558 | "\n", 559 | "def gf256_add(a: int, b: int):\n", 560 | " return a ^ b\n", 561 | "\n", 562 | "def gf256_sub(a: int, b: int):\n", 563 | " return gf256_add(a, b)\n", 564 | "\n", 565 | "def gf256_mul(a: int, b: int):\n", 566 | " return 0 if (a == 0 or b == 0) else GF256_ANTILOG[(GF256_LOG[a] + GF256_LOG[b]) % (GF256_SIZE - 1)]\n", 567 | "\n", 568 | "def gf256_inv(a: int):\n", 569 | " if a == 0:\n", 570 | " raise Exception(\"Zero has no inverse\")\n", 571 | " return GF256_ANTILOG[(GF256_SIZE - 1) - GF256_LOG[a]]\n", 572 | "\n", 573 | "def gf256_div(a: int, b: int):\n", 574 | " if a == 0:\n", 575 | " return 0\n", 576 | " elif b == 0:\n", 577 | " raise Exception(\"div by zero in GF\")\n", 578 | " return gf256_mul(a, gf256_inv(b))" 579 | ], 580 | "outputs": [], 581 | "metadata": {} 582 | }, 583 | { 584 | "cell_type": "code", 585 | "execution_count": null, 586 | "source": [ 587 | "# add polynomial functions\n", 588 | "\n", 589 | "class Polynomial():\n", 590 | "\n", 591 | " def __init__(self, terms: list):\n", 592 | " self.terms = terms\n", 593 | "\n", 594 | " def __str__(self):\n", 595 | " return ' + '.join([f\"{t}x^{len(self.terms) - i - 1}\" for i, t in enumerate(self.terms[::-1]) if t > 0])\n", 596 | "\n", 597 | " # return __str__ in alpha notation\n", 598 | " def str_alpha(self):\n", 599 | " return ' + '.join([f\"α^{GF256_LOG[t]}x^{len(self.terms) - i - 1}\" for i, t in enumerate(self.terms[::-1]) if t > 0])\n", 600 | "\n", 601 | " # return degree of polynomial\n", 602 | " def get_degree(self):\n", 603 | " return len(self.terms) - 1\n", 604 | "\n", 605 | " # determine if two polynomials are equivalent\n", 606 | " def equals(self, other):\n", 607 | " if len(self.terms) > len(other.terms):\n", 608 | " min_poly = other\n", 609 | " max_poly = self\n", 610 | " else:\n", 611 | " min_poly = self\n", 612 | " max_poly = other\n", 613 | " for i in range(len(min_poly.terms)):\n", 614 | " if self.terms[i] != other.terms[i]:\n", 615 | " return False\n", 616 | " for i in range(len(min_poly.terms), len(max_poly.terms)):\n", 617 | " if max_poly.terms[i] != 0:\n", 618 | " return False\n", 619 | " return True\n", 620 | "\n", 621 | "# create new polynomial from a block of words\n", 622 | "# each word becomes the coefficient of an x term\n", 623 | "def block_to_poly(block: list):\n", 624 | " terms = ([int(w, 2) for w in block])[::-1]\n", 625 | " return Polynomial(terms)\n", 626 | "\n", 627 | "# multiply polynomial by alpha value\n", 628 | "def poly_alpha_mul(p: Polynomial, alpha: int):\n", 629 | " for i, t in enumerate(p.terms):\n", 630 | " t_alpha = (GF256_LOG[t] + alpha) % (GF256_SIZE - 1)\n", 631 | " p.terms[i] = GF256_ANTILOG[t_alpha]\n", 632 | " return p\n", 633 | "\n", 634 | "# normalize polynomial\n", 635 | "def poly_normalize(p: Polynomial):\n", 636 | " # print(f\"[poly_norm] normalizing {p} [{len(p.terms)}]\")\n", 637 | " max_nz = len(p.terms) - 1 # max nonzero term\n", 638 | " for i in range(len(p.terms) - 1, 0, -1):\n", 639 | " if p.terms[i] != 0: break\n", 640 | " max_nz = i - 1\n", 641 | " if max_nz < 0:\n", 642 | " # print(f\"[poly_norm] normalized to {p} [{len(p.terms)}] **** ????\")\n", 643 | " return Polynomial([0])\n", 644 | " elif max_nz < (len(p.terms) - 1): \n", 645 | " p.terms = p.terms[0: max_nz + 1]\n", 646 | " # print(f\"[poly_norm] normalized to {p} [{len(p.terms)}]\")\n", 647 | " return p\n", 648 | "\n", 649 | "# add two polynomials\n", 650 | "def poly_add(a: Polynomial, b: Polynomial):\n", 651 | " # print(f\" [poly_add] ({a}) + ({b})\\n [poly_add] a[{len(a.terms)}] ; b[{len(b.terms)}]\")\n", 652 | " term_len = len(a.terms)\n", 653 | " if len(b.terms) > term_len:\n", 654 | " term_len = len(b.terms)\n", 655 | " p = Polynomial([0] * term_len)\n", 656 | " for i in range(term_len):\n", 657 | " if len(a.terms) > i and len(b.terms) > i:\n", 658 | " p.terms[i] = gf256_add(a.terms[i], b.terms[i])\n", 659 | " # print(f\" [poly_add] prdA_poly[{i}] = gf256_add({a.terms[i]}, {b.terms[i]}) = {p.terms[i]}\")\n", 660 | " elif len(a.terms) > i:\n", 661 | " p.terms[i] = a.terms[i]\n", 662 | " # print(f\" [poly_add] prdA_poly[{i}] = a[{i}] = {p.terms[i]}\")\n", 663 | " else:\n", 664 | " p.terms[i] = b.terms[i]\n", 665 | " # print(f\" [poly_add] prdA_poly[{i}] = b[{i}] = {p.terms[i]}\")\n", 666 | " return poly_normalize(p)\n", 667 | "\n", 668 | "# multiply two polynomials\n", 669 | "def poly_mul(a: Polynomial, b: Polynomial):\n", 670 | " # print(f\" [poly_mul] a={a}\\n b={b}\")\n", 671 | " p = Polynomial([0] * (len(a.terms) + len(b.terms)))\n", 672 | " for i in range(len(a.terms)):\n", 673 | " for j in range(len(b.terms)):\n", 674 | " if a.terms[i] != 0 and b.terms[j] != 0:\n", 675 | " monomial = Polynomial([0] * (i + j + 1))\n", 676 | " monomial.terms[i + j] = gf256_mul(a.terms[i], b.terms[j])\n", 677 | " # print(f\" [poly_mul ({i},{j})] prdB_poly = {monomial}\")\n", 678 | " # print(f\" [poly_mul] gf256_mul({a.terms[i]}, {b.terms[j]}) = {monomial.terms[i + j]}\")\n", 679 | " p = poly_add(p, monomial)\n", 680 | " # print(f\" [poly_mul ({i},{j}) done] a + b = prdA_poly = {p}\\n\")\n", 681 | " return poly_normalize(p)\n", 682 | "\n", 683 | "# perform polynomial long division and return remainder polynomial\n", 684 | "def poly_remainder(numerator: Polynomial, denominator: Polynomial):\n", 685 | " if numerator.equals(denominator):\n", 686 | " raise Exception(\"Remainder is zero\")\n", 687 | " remainder = numerator\n", 688 | "\n", 689 | " while len(remainder.terms) >= len(denominator.terms):\n", 690 | " degree = len(remainder.terms) - len(denominator.terms)\n", 691 | " coefficient = gf256_div(remainder.terms[-1], denominator.terms[-1])\n", 692 | " m = new_monomial(coefficient, degree)\n", 693 | " # print(f\"\\n[poly_remainder] denominator = [{len(denominator.terms)}] {denominator}\")\n", 694 | " # print(f\"[poly_remainder] mono = [{len(m.terms)}] {m}\")\n", 695 | " divisor = poly_mul(denominator, m)\n", 696 | " # print(f\"[poly_remainder] = divisor = {divisor}\\n\")\n", 697 | " # print(f\"[poly_remainder] remainder + divisor = ({remainder}) + ({divisor})\")\n", 698 | " remainder = poly_add(remainder, divisor)\n", 699 | " # print(f\"[poly_remainder] = remainder = {remainder}\\n\")\n", 700 | " return poly_normalize(remainder)\n", 701 | "\n", 702 | "# create a monomial (single term polynomial) with given term and degree\n", 703 | "def new_monomial(term: int, degree: int):\n", 704 | " if term == 0:\n", 705 | " return Polynomial([0])\n", 706 | " mono = Polynomial([0] * (degree + 1))\n", 707 | " mono.terms[degree] = term\n", 708 | " return mono\n", 709 | "\n", 710 | "# create a polynomial from bit string\n", 711 | "def bits_to_poly(bits: str):\n", 712 | " return Polynomial([int(bits[i]) for i in range(len(bits))])\n", 713 | "\n", 714 | "# create bit string from polynomial\n", 715 | "def poly_to_bits(p: Polynomial):\n", 716 | " return ''.join(['1' if t > 0 else '0' for t in p.terms])" 717 | ], 718 | "outputs": [], 719 | "metadata": {} 720 | }, 721 | { 722 | "cell_type": "code", 723 | "execution_count": null, 724 | "source": [ 725 | "# calculate error correction blocks with Reed-Solomon \n", 726 | "\n", 727 | "ec_blocks = []\n", 728 | "\n", 729 | "def get_gen_poly(degree: int):\n", 730 | " if degree < 2:\n", 731 | " raise Exception('generator polynomial degree must be greater than 2')\n", 732 | " gp = Polynomial([1])\n", 733 | " for i in range(degree):\n", 734 | " np = Polynomial([GF256_ANTILOG[i], 1])\n", 735 | " # print(f\"\\n========={i}==========\\ngp = {gp}\")\n", 736 | " # print(f\"np = {np}\\ngp = gp * np = ({gp}) * ({np})\")\n", 737 | " gp = poly_mul(gp, np)\n", 738 | " # print(f\"gp = {gp}\")\n", 739 | " # terms = ' '.join([str(t[1]).rjust(3) for t in enumerate(gp.terms)])\n", 740 | " # print(f\"{str(i).zfill(2)}: {str(len(gp.terms)).rjust(3)} {terms}\")\n", 741 | " print(\".............................................\")\n", 742 | " return gp\n", 743 | "\n", 744 | "# group 1 error correction\n", 745 | "for i, block in enumerate(g1_blocks):\n", 746 | " print(f'\\nblock {i}')\n", 747 | " print([int(word, 2) for word in block])\n", 748 | "\n", 749 | " # translate block of data to message polynomial\n", 750 | " msg_poly = block_to_poly(block)\n", 751 | " print(f\"\\nmsg = {msg_poly}\\n\")\n", 752 | "\n", 753 | " # build generator polynomial\n", 754 | " gen_poly = get_gen_poly(ecw_per_block)\n", 755 | " print(f\"gen = {gen_poly}\\n\")\n", 756 | "\n", 757 | " # ensure lead term doesn't become too small during division\n", 758 | " mono = new_monomial(1, ecw_per_block)\n", 759 | " # print(f\"msg_poly = [{len(msg_poly.terms)}] {msg_poly}\\nmono = [{len(mono.terms)}] {mono}\")\n", 760 | " msg_poly = poly_mul(msg_poly, mono)\n", 761 | " # print(f\"msg_poly * mono = {msg_poly}\\n\")\n", 762 | "\n", 763 | " # find error correction words via polynomial long division\n", 764 | " print(f\"******************\")\n", 765 | " rem_poly = poly_remainder(msg_poly, gen_poly)\n", 766 | " print(f\"remainder of msg / gen = {rem_poly}\\n\")\n", 767 | " ec_block = [int_to_bits(word, 8) for word in rem_poly.terms[::-1]]\n", 768 | "\n", 769 | " print(f\"{len(ec_block)} error correction words:\\n{[hex(int(word, 2)) for word in ec_block]}\")\n", 770 | " print(f\"{[int(word, 2) for word in ec_block]}\")\n", 771 | " assert len(ec_block) == ecw_per_block\n", 772 | " ec_blocks.append(ec_block)" 773 | ], 774 | "outputs": [], 775 | "metadata": {} 776 | }, 777 | { 778 | "cell_type": "code", 779 | "execution_count": null, 780 | "source": [ 781 | "# interleave payload data and error correction data\n", 782 | "data = []\n", 783 | "for i in range(g1_data_block_size):\n", 784 | " for j in range(len(g1_blocks)):\n", 785 | " data.append(g1_blocks[j][i])\n", 786 | "for i in range(ecw_per_block):\n", 787 | " for j in range(len(ec_blocks)):\n", 788 | " data.append(ec_blocks[j][i])\n", 789 | "\n", 790 | "print(f\"g1_data_block_size = {g1_data_block_size}\")\n", 791 | "print(f\"ecw_per_block = {ecw_per_block}\")\n", 792 | "\n", 793 | "print(f\"payload = '{payload}'\\n\")\n", 794 | "print(f\"interleaved data - {len(data)} word(s):\\n{[hex(int(x, 2)) for x in data]}\")\n", 795 | "print(f\"\\n{[int(x, 2) for x in data]}\")" 796 | ], 797 | "outputs": [], 798 | "metadata": {} 799 | }, 800 | { 801 | "cell_type": "code", 802 | "execution_count": null, 803 | "source": [ 804 | "# add remainder bits based on version\n", 805 | "REMAINDER_LOOKUP = [\n", 806 | " 0, # one indexing\n", 807 | " 0, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3,\n", 808 | " 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0\n", 809 | "]\n", 810 | "remainder_bits = REMAINDER_LOOKUP[version]\n", 811 | "print(f\"Adding {remainder_bits} remainder bit(s)\")\n", 812 | "\n", 813 | "print(f\"\\n{[int(x, 2) for x in data]}\")\n", 814 | "\n", 815 | "data = ''.join([x for x in data]) + ('0' * remainder_bits)\n", 816 | "print(f\"data - ({len(data)}) bit(s)\\n\\n{data}\")" 817 | ], 818 | "outputs": [], 819 | "metadata": {} 820 | }, 821 | { 822 | "cell_type": "code", 823 | "execution_count": null, 824 | "source": [ 825 | "# start building QR matrix\n", 826 | "qr_size = ((version - 1) * 4) + 21\n", 827 | "qr_mat = [2] * (qr_size ** 2) # flat list (so ASM port is easier)\n", 828 | "print(f\"QR matrix : {qr_size} x {qr_size} - {len(qr_mat)} module(s)\\n\")\n", 829 | "\n", 830 | "# save matrix to file\n", 831 | "from PIL import Image\n", 832 | "\n", 833 | "def mat_to_file(qr_mat, qr_size, file_name, normalize=False):\n", 834 | " to_print = qr_mat\n", 835 | " if normalize:\n", 836 | " to_print = normalize_matrix(to_print, qr_size)\n", 837 | " img = Image.new(mode='1', size=(qr_size, qr_size))\n", 838 | "\n", 839 | " to_print = [not b for b in to_print] # reverse bits\n", 840 | " for x in range(qr_size):\n", 841 | " for y in range(qr_size):\n", 842 | " pixel = to_print[(y * qr_size) + x]\n", 843 | " img.putpixel((x, y), pixel)\n", 844 | " img.save(file_name)\n", 845 | "\n", 846 | "def normalize_matrix(qr_mat, qr_size):\n", 847 | " n = []\n", 848 | " for x in range(qr_size):\n", 849 | " for y in range(qr_size):\n", 850 | " n.append([0, 1, 0, 0, 1][qr_mat[(y * qr_size) + x]])\n", 851 | " return n\n", 852 | "\n", 853 | "mat_to_file(qr_mat, qr_size, 'qrcode.png', normalize=True)" 854 | ], 855 | "outputs": [], 856 | "metadata": {} 857 | }, 858 | { 859 | "cell_type": "code", 860 | "execution_count": null, 861 | "source": [ 862 | "# add reserved areas\n", 863 | "\n", 864 | "# draw square in qr matrix\n", 865 | "def draw_square(qr_mat, qr_size, x, y, n, c):\n", 866 | " for i in range(n):\n", 867 | " for j in range(n):\n", 868 | " dx = (x + i)\n", 869 | " dy = (y + j)\n", 870 | " if dx < qr_size and dy < qr_size and dx >= 0 and dy >= 0:\n", 871 | " qr_mat[(dy * qr_size) + dx] = c\n", 872 | " return qr_mat\n", 873 | "\n", 874 | "if version > 6:\n", 875 | " raise Exception(\"QR versions greater than 6 are not supported\")\n", 876 | "qr_mat = draw_square(qr_mat, qr_size, 0, 0, 9, 3) # top left\n", 877 | "qr_mat = draw_square(qr_mat, qr_size, (qr_size - 7) - 1, 0, 9, 3) # top right\n", 878 | "qr_mat = draw_square(qr_mat, qr_size, 0, (qr_size - 7), 9, 3) # bottom left\n", 879 | "mat_to_file(qr_mat, qr_size, 'qrcode.png', normalize=True)" 880 | ], 881 | "outputs": [], 882 | "metadata": {} 883 | }, 884 | { 885 | "cell_type": "code", 886 | "execution_count": null, 887 | "source": [ 888 | "# place timing patterns\n", 889 | "\n", 890 | "# horizontal timing\n", 891 | "is_fill = True\n", 892 | "for i in range(qr_size):\n", 893 | " c = 4 if is_fill else 3\n", 894 | " qr_mat[(i) * qr_size + (6)] = c\n", 895 | " is_fill = not is_fill\n", 896 | "\n", 897 | "# vertical timing\n", 898 | "is_fill = True\n", 899 | "for j in range(qr_size):\n", 900 | " c = 4 if is_fill else 3\n", 901 | " qr_mat[(6) * qr_size + (j)] = c\n", 902 | " is_fill = not is_fill\n", 903 | "\n", 904 | "mat_to_file(qr_mat, qr_size, 'qrcode.png', normalize=True)" 905 | ], 906 | "outputs": [], 907 | "metadata": {} 908 | }, 909 | { 910 | "cell_type": "code", 911 | "execution_count": null, 912 | "source": [ 913 | "# place finder pattern in QR code matrix with left corner at (x,y)\n", 914 | "def place_finder(qr_mat, qr_size, x, y):\n", 915 | " qr_mat = draw_square(qr_mat, qr_size, x - 1, y - 1, 9, 3) # separator\n", 916 | " qr_mat = draw_square(qr_mat, qr_size, x, y, 7, 4) # outer\n", 917 | " qr_mat = draw_square(qr_mat, qr_size, x + 1, y + 1, 5, 3) # inner\n", 918 | " qr_mat = draw_square(qr_mat, qr_size, x + 2, y + 2, 3, 4) # center\n", 919 | " return qr_mat\n", 920 | "\n", 921 | "qr_mat = place_finder(qr_mat, qr_size, 0, 0) # top left\n", 922 | "qr_mat = place_finder(qr_mat, qr_size, 0, (qr_size - 7)) # top right\n", 923 | "qr_mat = place_finder(qr_mat, qr_size, (qr_size - 7), 0) # bottom left\n", 924 | "\n", 925 | "mat_to_file(qr_mat, qr_size, 'qrcode.png', normalize=True)" 926 | ], 927 | "outputs": [], 928 | "metadata": {} 929 | }, 930 | { 931 | "cell_type": "code", 932 | "execution_count": null, 933 | "source": [ 934 | "# add misc reserved areas\n", 935 | "ALIGNMENT_PATTERN_LOOK = [\n", 936 | " [], # one indexing\n", 937 | " [], # version 1 has no alignment\n", 938 | " [6, 18, 0, 0, 0, 0, 0], [6, 22, 0, 0, 0, 0, 0], # 2, 3\n", 939 | " [6, 26, 0, 0, 0, 0, 0], [6, 30, 0, 0, 0, 0, 0], # 4, 5\n", 940 | " [6, 34, 0, 0, 0, 0, 0], [6, 22, 38, 0, 0, 0, 0], # 6, 7\n", 941 | " # and so on to 40...\n", 942 | "]\n", 943 | "\n", 944 | "# place alignment pattern\n", 945 | "if version > 1:\n", 946 | " pat = ALIGNMENT_PATTERN_LOOK[version]\n", 947 | " qr_mat = draw_square(qr_mat, qr_size, pat[1] - 2, pat[1] - 2, 5, 4)\n", 948 | " qr_mat = draw_square(qr_mat, qr_size, pat[1] - 1, pat[1] - 1, 3, 3)\n", 949 | " qr_mat = draw_square(qr_mat, qr_size, pat[1], pat[1], 1, 4)\n", 950 | "\n", 951 | "# place dark module\n", 952 | "qr_mat[((4 * version) + 9) * qr_size + (8)] = 4\n", 953 | "mat_to_file(qr_mat, qr_size, 'qrcode.png', normalize=True)" 954 | ], 955 | "outputs": [], 956 | "metadata": {} 957 | }, 958 | { 959 | "cell_type": "code", 960 | "execution_count": null, 961 | "source": [ 962 | "# \"zigzag\" the data\n", 963 | "def zigzag_data(qr_mat, qr_size, data):\n", 964 | " x = qr_size - 1\n", 965 | " y = qr_size - 1\n", 966 | " data_idx = 0\n", 967 | " zig = True\n", 968 | " up = True\n", 969 | "\n", 970 | " while data_idx < len(data):\n", 971 | " # reached edge, bounce back\n", 972 | " if y == qr_size:\n", 973 | " up = not up\n", 974 | " x -= 2\n", 975 | " zig = True\n", 976 | " y = qr_size - 1\n", 977 | " elif y < 0:\n", 978 | " up = not up\n", 979 | " x -= 2\n", 980 | " zig = True\n", 981 | " y = 0\n", 982 | " next_mod = qr_mat[(y * qr_size) + x]\n", 983 | "\n", 984 | " # zig zag past existing structure\n", 985 | " if next_mod == 2:\n", 986 | " qr_mat[(y * qr_size) + x] = int(data[data_idx])\n", 987 | " data_idx += 1\n", 988 | "\n", 989 | " # zig or zag\n", 990 | " if zig:\n", 991 | " x -= 1\n", 992 | " else:\n", 993 | " y += 1 if not up else -1\n", 994 | " x += 1\n", 995 | " zig = not zig\n", 996 | "\n", 997 | " # skip over timing patterns\n", 998 | " if x == 6:\n", 999 | " y -= 1\n", 1000 | " x -= 1\n", 1001 | " return qr_mat\n", 1002 | "\n", 1003 | "qr_mat = zigzag_data(qr_mat, qr_size, data)\n", 1004 | "mat_to_file(qr_mat, qr_size, 'qrcode.png', normalize=True)" 1005 | ], 1006 | "outputs": [], 1007 | "metadata": {} 1008 | }, 1009 | { 1010 | "cell_type": "code", 1011 | "execution_count": null, 1012 | "source": [ 1013 | "# generate masks\n", 1014 | "def get_masks(qr_size):\n", 1015 | " masks = []\n", 1016 | "\n", 1017 | " # mask 0\n", 1018 | " mask = [0] * (qr_size ** 2)\n", 1019 | " for y in range(qr_size):\n", 1020 | " for x in range(qr_size):\n", 1021 | " mask[(y * qr_size) + x] = 1 if ((x + y) % 2) == 0 else 0\n", 1022 | " masks.append(mask)\n", 1023 | "\n", 1024 | " # mask 1\n", 1025 | " mask = [0] * (qr_size ** 2)\n", 1026 | " for y in range(qr_size):\n", 1027 | " for x in range(qr_size):\n", 1028 | " mask[(y * qr_size) + x] = 1 if (y % 2) == 0 else 0\n", 1029 | " masks.append(mask)\n", 1030 | "\n", 1031 | " # mask 2\n", 1032 | " mask = [0] * (qr_size ** 2)\n", 1033 | " for y in range(qr_size):\n", 1034 | " for x in range(qr_size):\n", 1035 | " mask[(y * qr_size) + x] = 1 if (x % 3) == 0 else 0\n", 1036 | " masks.append(mask)\n", 1037 | "\n", 1038 | " # mask 3\n", 1039 | " mask = [0] * (qr_size ** 2)\n", 1040 | " for y in range(qr_size):\n", 1041 | " for x in range(qr_size):\n", 1042 | " mask[(y * qr_size) + x] = 1 if ((x + y) % 3) == 0 else 0\n", 1043 | " masks.append(mask)\n", 1044 | "\n", 1045 | " # mask 4\n", 1046 | " mask = [0] * (qr_size ** 2)\n", 1047 | " for y in range(qr_size):\n", 1048 | " for x in range(qr_size):\n", 1049 | " mask[(y * qr_size) + x] = 1 if ((x // 3 + y // 2) % 2) == 0 else 0\n", 1050 | " masks.append(mask)\n", 1051 | "\n", 1052 | " # mask 5\n", 1053 | " mask = [0] * (qr_size ** 2)\n", 1054 | " for y in range(qr_size):\n", 1055 | " for x in range(qr_size):\n", 1056 | " mask[(y * qr_size) + x] = 1 if ((x * y % 2) + (x * y % 3)) == 0 else 0\n", 1057 | " masks.append(mask)\n", 1058 | "\n", 1059 | " # mask 6\n", 1060 | " mask = [0] * (qr_size ** 2)\n", 1061 | " for y in range(qr_size):\n", 1062 | " for x in range(qr_size):\n", 1063 | " mask[(y * qr_size) + x] = 1 if (((x * y) % 2 + x * y % 3) % 2) == 0 else 0\n", 1064 | " masks.append(mask)\n", 1065 | "\n", 1066 | " # mask 7\n", 1067 | " mask = [0] * (qr_size ** 2)\n", 1068 | " for y in range(qr_size):\n", 1069 | " for x in range(qr_size):\n", 1070 | " mask[(y * qr_size) + x] = 1 if (((x + y) % 2 + x * y % 3) % 2) == 0 else 0\n", 1071 | " masks.append(mask)\n", 1072 | "\n", 1073 | " return masks\n", 1074 | "\n", 1075 | "masks = get_masks(qr_size)\n", 1076 | "for i,mask in enumerate(masks):\n", 1077 | " mat_to_file(mask, qr_size, f'mask-{i}.png')" 1078 | ], 1079 | "outputs": [], 1080 | "metadata": {} 1081 | }, 1082 | { 1083 | "cell_type": "code", 1084 | "execution_count": null, 1085 | "source": [ 1086 | "# determine best mask\n", 1087 | "FMT_INFO_LOOKUP = [\n", 1088 | " [ # L\n", 1089 | " '111011111000100', '111001011110011', '111110110101010', '111100010011101',\n", 1090 | " '110011000101111', '110001100011000', '110110001000001', '110100101110110'\n", 1091 | " ],\n", 1092 | " [ # M\n", 1093 | " '101010000010010', '101000100100101', '101111001111100', '101101101001011',\n", 1094 | " '100010111111001', '100000011001110', '100111110010111', '100101010100000'\n", 1095 | " ],\n", 1096 | " [\n", 1097 | " # Q\n", 1098 | " '011010101011111', '011000001101000', '011111100110001', '011101000000110',\n", 1099 | " '010010010110100', '010000110000011', '010111011011010', '010101111101101'\n", 1100 | " ],\n", 1101 | " [ # H\n", 1102 | " '001011010001001', '001001110111110', '001110011100111', '001100111010000',\n", 1103 | " '000011101100010', '000001001010101', '000110100001100', '000100000111011'\n", 1104 | " ]\n", 1105 | "]\n", 1106 | "\n", 1107 | "# calculate format bits\n", 1108 | "def calc_fmt_bits(err_lvl, mask_idx):\n", 1109 | " fmt_bits = int_to_bits(err_lvl, 2) + int_to_bits(mask_idx, 3)\n", 1110 | " err_bits = (fmt_bits + ('0' * 10)).lstrip('0')\n", 1111 | "\n", 1112 | " # calculate error correction bits\n", 1113 | " while len(err_bits) >= 11:\n", 1114 | " # build generator polynomial\n", 1115 | " res = ''\n", 1116 | " gen_bits = '10100110111' # $x^{10}+x^8+x^5+x^4+x^2+x+1$\n", 1117 | "\n", 1118 | " # pad generator polynomial to match length of format bits\n", 1119 | " while len(gen_bits) != len(err_bits):\n", 1120 | " gen_bits += '0'\n", 1121 | " # XOR generator bits with format string\n", 1122 | " for i in range(len(gen_bits)):\n", 1123 | " res += str(int(gen_bits[i]) ^ int(err_bits[i]))\n", 1124 | " err_bits = res.lstrip('0')\n", 1125 | "\n", 1126 | " # repad to 10-bits\n", 1127 | " while len(err_bits) < 10:\n", 1128 | " err_bits = '0' + err_bits\n", 1129 | "\n", 1130 | " # combine format and error correction bits\n", 1131 | " fmt_bits += err_bits\n", 1132 | " final_fmt_bits = ''\n", 1133 | " for i in range(len(fmt_bits)):\n", 1134 | " final_fmt_bits += str(int(fmt_bits[i]) ^ int('101010000010010'[i]))\n", 1135 | "\n", 1136 | " lookup_fmt = FMT_INFO_LOOKUP[ERROR_IDX_TO_LOOKUP[err_lvl]][mask_idx]\n", 1137 | " assert final_fmt_bits == lookup_fmt\n", 1138 | " return final_fmt_bits\n", 1139 | "\n", 1140 | "# add format bits adjacent to finders\n", 1141 | "def add_format_bits(qr_mat, qr_size, fmt_bits):\n", 1142 | " # break up format bits to place near finder patterns\n", 1143 | " high_bits = fmt_bits[0:7] # MSB=0\n", 1144 | " low_bits = fmt_bits[8:15] # LSB=14\n", 1145 | "\n", 1146 | " # top left format bits\n", 1147 | " x = 0\n", 1148 | " y = 8\n", 1149 | " for i in range(len(high_bits)):\n", 1150 | " if i == 6:\n", 1151 | " x += 1 # skip vertical timing\n", 1152 | " qr_mat[(y * qr_size) + x] = int(high_bits[i])\n", 1153 | " x += 1\n", 1154 | " x = 8\n", 1155 | " y = 7\n", 1156 | " for j in range(len(low_bits)):\n", 1157 | " if j == 1:\n", 1158 | " y -= 1 # skip horizontal timing\n", 1159 | " qr_mat[(y * qr_size) + x] = int(low_bits[j])\n", 1160 | " y -= 1\n", 1161 | "\n", 1162 | " # top right format bits\n", 1163 | " x = qr_size - 7\n", 1164 | " y = 8\n", 1165 | " for i in range(len(low_bits)):\n", 1166 | " qr_mat[(y * qr_size) + x + i] = int(low_bits[i])\n", 1167 | "\n", 1168 | " # bottom left format bits\n", 1169 | " x = 8\n", 1170 | " y = qr_size - 1\n", 1171 | " for i in range(len(low_bits)):\n", 1172 | " qr_mat[((y - i) * qr_size) + x] = int(high_bits[i])\n", 1173 | "\n", 1174 | " return qr_mat\n", 1175 | "\n", 1176 | "# Evaluate penalty for rule 1: group of 5 or more same-colored modules in a row or col\n", 1177 | "def eval_rule_1(masked, qr_size):\n", 1178 | " row_count = 0\n", 1179 | " col_count = 0\n", 1180 | " prev_row = 0\n", 1181 | " prev_col = 0\n", 1182 | " penalty_horizontal = 0\n", 1183 | " penalty_vertical = 0\n", 1184 | " module = -1\n", 1185 | "\n", 1186 | " for y in range(qr_size):\n", 1187 | " if module == prev_col:\n", 1188 | " col_count += 1\n", 1189 | " else:\n", 1190 | " col_count = 0\n", 1191 | "\n", 1192 | " if col_count == 5:\n", 1193 | " penalty_vertical += 3\n", 1194 | " elif col_count > 5:\n", 1195 | " penalty_vertical += 1\n", 1196 | "\n", 1197 | " for x in range(qr_size):\n", 1198 | " module = masked[(y * qr_size) + x]\n", 1199 | " if module == prev_row:\n", 1200 | " row_count += 1\n", 1201 | " else:\n", 1202 | " row_count = 0\n", 1203 | "\n", 1204 | " if row_count == 5:\n", 1205 | " penalty_horizontal += 3\n", 1206 | " elif row_count > 5:\n", 1207 | " penalty_horizontal += 1\n", 1208 | " prev_row = module\n", 1209 | " row_count = 0\n", 1210 | " prev_col = 0\n", 1211 | " return penalty_horizontal + penalty_vertical\n", 1212 | "\n", 1213 | "# Evaluate penalty for rule 2: 2x2 area of same colored modules\n", 1214 | "def eval_rule_2(masked, qr_size):\n", 1215 | " penalty = 0\n", 1216 | " for x in range(qr_size):\n", 1217 | " for y in range(qr_size):\n", 1218 | " idx = (y * qr_size) + x\n", 1219 | " if (x < qr_size - 1) and (y < qr_size - 1) and (y > 0):\n", 1220 | " is_square = True\n", 1221 | " test = masked[idx] # top left\n", 1222 | "\n", 1223 | " if test != masked[idx + 1]:\n", 1224 | " is_square = False # top right\n", 1225 | " elif test != masked[idx + qr_size]:\n", 1226 | " is_square = False # bottom left\n", 1227 | " elif test != masked[idx + qr_size + 1]:\n", 1228 | " is_square = False # bottom right\n", 1229 | "\n", 1230 | " if is_square:\n", 1231 | " penalty += 3\n", 1232 | " return penalty\n", 1233 | " \n", 1234 | "# Evaluate penalty for rule 3: occurrences of 10111010000 and 00001011101 in rows/cols\n", 1235 | "def eval_rule_3(masked, qr_size):\n", 1236 | " return 0 # skipping this...could not get it working for some reason...\n", 1237 | "\n", 1238 | "# Evaluate penalty for rule 4: ratio of light to dark modules\n", 1239 | "def eval_rule_4(masked, qr_size):\n", 1240 | " white = 0\n", 1241 | " black = 0\n", 1242 | " for x in range(qr_size):\n", 1243 | " for y in range(qr_size):\n", 1244 | " idx = (y * qr_size) + x\n", 1245 | " if masked[idx] == 1:\n", 1246 | " black += 1\n", 1247 | " else:\n", 1248 | " white += 1\n", 1249 | " total = white + black\n", 1250 | " return ((abs(black * 20 - total * 10) + total - 1) // (total - 1)) * 10\n", 1251 | "\n", 1252 | "# apply mask to QR matrix (not affecting non-function modules)\n", 1253 | "def apply_mask(mask, qr_mat, qr_size):\n", 1254 | " masked = [0] * (qr_size ** 2)\n", 1255 | " for y in range(qr_size):\n", 1256 | " for x in range(qr_size):\n", 1257 | " idx = (y * qr_size) + x\n", 1258 | " module = qr_mat[idx]\n", 1259 | "\n", 1260 | " # 3-4 are reserved\n", 1261 | " if module < 2:\n", 1262 | " masked[idx] = module ^ mask[idx]\n", 1263 | " elif module == 3:\n", 1264 | " masked[idx] = 0 # swap out reserved '0'\n", 1265 | " elif module == 4:\n", 1266 | " masked[idx] = 1 # swap out reserved '1'\n", 1267 | " return masked\n", 1268 | "\n", 1269 | "# apply each mask and use penalty to determine most ideal\n", 1270 | "def apply_ideal_mask(qr_mat, qr_size, err_lvl):\n", 1271 | " masks = get_masks(qr_size)\n", 1272 | " min_penalty = 99999999\n", 1273 | " ideal_mask_idx = -1\n", 1274 | "\n", 1275 | " for mask_idx, mask in enumerate(masks):\n", 1276 | " penalty = 0\n", 1277 | " fmt_bits = calc_fmt_bits(err_lvl, mask_idx)\n", 1278 | " masked = add_format_bits(qr_mat, qr_size, fmt_bits)\n", 1279 | " masked = apply_mask(mask, masked, qr_size)\n", 1280 | "\n", 1281 | " penalty += eval_rule_1(masked, qr_size)\n", 1282 | " penalty += eval_rule_2(masked, qr_size)\n", 1283 | " penalty += eval_rule_3(masked, qr_size)\n", 1284 | " penalty += eval_rule_4(masked, qr_size)\n", 1285 | "\n", 1286 | " if penalty < min_penalty:\n", 1287 | " min_penalty = penalty\n", 1288 | " ideal_mask_idx = mask_idx\n", 1289 | " print(f\"mask {mask_idx} has penalty {penalty}\")\n", 1290 | " print(f\"ideal mask is mask {ideal_mask_idx}\")\n", 1291 | "\n", 1292 | " # apply ideal mask\n", 1293 | " fmt_bits = calc_fmt_bits(err_lvl, ideal_mask_idx)\n", 1294 | " masked = apply_mask(masks[ideal_mask_idx], qr_mat, qr_size)\n", 1295 | " final_mat = add_format_bits(masked, qr_size, fmt_bits)\n", 1296 | " return final_mat\n", 1297 | "\n", 1298 | "qr_mat = apply_ideal_mask(qr_mat, qr_size, err_lvl)\n", 1299 | "mat_to_file(qr_mat, qr_size, 'qrcode.png')" 1300 | ], 1301 | "outputs": [], 1302 | "metadata": {} 1303 | }, 1304 | { 1305 | "cell_type": "code", 1306 | "execution_count": null, 1307 | "source": [ 1308 | "# add 4 module wide area of light modules (quiet zone)\n", 1309 | "def add_quiet_zone(qr_mat, qr_size):\n", 1310 | " quieted = [0] * ((qr_size + 8) ** 2)\n", 1311 | " for x in range(0, qr_size):\n", 1312 | " for y in range(0, qr_size):\n", 1313 | " module = qr_mat[(y * qr_size) + x]\n", 1314 | " quieted[((y + 4) * (qr_size + 8)) + (x + 4)] = module\n", 1315 | " return quieted\n", 1316 | "\n", 1317 | "qr_mat = add_quiet_zone(qr_mat, qr_size)\n", 1318 | "mat_to_file(qr_mat, qr_size + 8, 'qrcode.png')" 1319 | ], 1320 | "outputs": [], 1321 | "metadata": {} 1322 | } 1323 | ], 1324 | "metadata": { 1325 | "interpreter": { 1326 | "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" 1327 | }, 1328 | "kernelspec": { 1329 | "name": "python3", 1330 | "display_name": "Python 3.8.10 64-bit" 1331 | }, 1332 | "language_info": { 1333 | "codemirror_mode": { 1334 | "name": "ipython", 1335 | "version": 3 1336 | }, 1337 | "file_extension": ".py", 1338 | "mimetype": "text/x-python", 1339 | "name": "python", 1340 | "nbconvert_exporter": "python", 1341 | "pygments_lexer": "ipython3", 1342 | "version": "3.8.10" 1343 | } 1344 | }, 1345 | "nbformat": 4, 1346 | "nbformat_minor": 2 1347 | } --------------------------------------------------------------------------------