ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
keymask.c
Go to the documentation of this file.
1
2/* Storage capacities in elements; text buffers include the terminating NUL. */
3#define KEYMASK_INTERIOR_ROWS (KEYMASK_ROWS - 2)
4#define KEYMASK_INTERIOR_COLUMNS (KEYMASK_WIDTH - 2)
5
8#include <sodium.h>
9#include <string.h>
10
13 if (!public_key || !digest)
14 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing public key or fingerprint output");
15 if (crypto_hash_sha256(digest, public_key, ED25519_PUBLIC_KEY_SIZE) != 0)
16 return SET_ERRNO(ERROR_CRYPTO, "Cannot compute public-key fingerprint");
17 return ASCIICHAT_OK;
18}
19
20// Independent feature hashes keep each choice stable as other features evolve.
21static void feature_seed(const uint8_t digest[KEYMASK_DIGEST_SIZE], const char *feature,
23 static const char domain[] = "ascii-chat/keymask/v2";
24 crypto_hash_sha256_state state;
25 crypto_hash_sha256_init(&state);
26 crypto_hash_sha256_update(&state, (const unsigned char *)domain, sizeof(domain));
27 crypto_hash_sha256_update(&state, digest, KEYMASK_DIGEST_SIZE);
28 crypto_hash_sha256_update(&state, (const unsigned char *)feature, strlen(feature));
29 crypto_hash_sha256_final(&state, seed);
30}
31
32static void stamp(char cells[KEYMASK_INTERIOR_ROWS][KEYMASK_INTERIOR_COLUMNS], unsigned row, unsigned col,
33 const char *text) {
34 for (; *text && col < KEYMASK_INTERIOR_COLUMNS; ++col, ++text)
35 cells[row][col] = *text;
36}
37
38static void centered(char cells[KEYMASK_INTERIOR_ROWS][KEYMASK_INTERIOR_COLUMNS], unsigned row, const char *text) {
39 stamp(cells, row, (unsigned)(KEYMASK_INTERIOR_COLUMNS - strlen(text)) / 2, text);
40}
41
42asciichat_error_t keymask_render(const uint8_t digest[KEYMASK_DIGEST_SIZE], bool unicode, char *out, size_t size) {
43 if (!out || !size)
44 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing Keymask output buffer");
45 out[0] = '\0';
46 if (!digest)
47 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing Keymask digest");
48
50 memset(cells, ' ', sizeof(cells));
53 feature_seed(digest, "outline", outline);
54 feature_seed(digest, "crown", crown);
55 feature_seed(digest, "eyes", eyes);
56 feature_seed(digest, "center", center);
57 feature_seed(digest, "mouth", mouth);
58 feature_seed(digest, "markings", marks);
59
60 // Half-widths from forehead to chin. Each family has a distinct silhouette.
61 static const unsigned widths[8][12] = {
62 {8, 10, 12, 12, 12, 11, 10, 9, 8, 6, 4, 2}, {6, 7, 8, 8, 8, 8, 8, 8, 7, 6, 5, 4},
63 {11, 13, 14, 14, 13, 12, 11, 10, 9, 7, 5, 3}, {9, 10, 11, 12, 13, 14, 13, 11, 9, 6, 3, 1},
64 {12, 12, 12, 12, 12, 12, 12, 12, 12, 10, 8, 6}, {5, 7, 9, 11, 12, 12, 11, 10, 8, 6, 4, 2},
65 {10, 11, 12, 11, 10, 9, 10, 11, 10, 8, 6, 4}, {7, 9, 11, 13, 14, 13, 12, 11, 10, 9, 8, 7},
66 };
67 const unsigned *profile = widths[outline[0] % 8];
68 for (unsigned r = 0; r < 12; ++r) {
69 unsigned left = 16 - profile[r], right = 15 + profile[r];
70 cells[r + 3][left] = '#';
71 cells[r + 3][right] = '#';
72 if ((outline[1] & 1) && r > 0 && r < 10) {
73 cells[r + 3][left + 1] = '#';
74 cells[r + 3][right - 1] = '#';
75 }
76 if (r == 0 || r == 11)
77 for (unsigned c = left; c <= right; ++c)
78 cells[r + 3][c] = '#';
79 }
80 static const char *crowns[8][3] = {
81 {"/\\ /\\", "/ \\ / \\", "\\ \\________/ /"},
82 {" /\\ ", " /##\\ ", "____/####\\____"},
83 {"o o", "| |", "/\\____________/\\"},
84 {" [] [] [] ", " || || || ", "__||__||__||__"},
85 {" ________ ", " /========\\ ", " /__________\\ "},
86 {"\\\\ //", " \\\\ // ", " \\\\______// "},
87 {" /||\\ ", " /||||\\ ", "___/||||||\\___"},
88 {" /\\ /\\ ", "< > < >", " \\/________\\/ "},
89 };
90 for (unsigned r = 0; r < 3; ++r)
91 centered(cells, r, crowns[crown[0] % 8][r]);
92
93 static const char *eye_rows[8][2] = {
94 {"[###] [###]", " V V "}, {" /--\\ /--\\ ", " \\__/ \\__/ "}, {" \\##\\ /##/ ", " \\_/ \\_/ "},
95 {"============", "[--O----O--]"}, {" .--. .--. ", " (XX) (XX) "}, {" /^^\\ /^^\\ ", " |..| |..| "},
96 {" <> <> ", " /__\\ /__\\ "}, {" [::] [::] ", " -- -- "},
97 };
98 centered(cells, 5, eye_rows[eyes[0] % 8][0]);
99 centered(cells, 6, eye_rows[eyes[0] % 8][1]);
100 static const char *noses[6][2] = {{"/\\", "\\/"}, {"||", "||"}, {"##", "VV"},
101 {"/\\", "/__\\"}, {"::", "[]"}, {"\\/", "||"}};
102 centered(cells, 8, noses[center[0] % 6][0]);
103 centered(cells, 9, noses[center[0] % 6][1]);
104 static const char *mouths[8][2] = {
105 {"[||||||]", " ------ "}, {"\\ VV VV /", " \\____/ "}, {" ====== ", " "}, {" /----\\ ", " \\____/ "},
106 {" [++++] ", " ---- "}, {" \\____/ ", " V V "}, {" /\\/\\ ", " \\/\\/ "}, {" [####] ", " |||| "},
107 };
108 centered(cells, 11, mouths[mouth[0] % 8][0]);
109 centered(cells, 12, mouths[mouth[0] % 8][1]);
110 // Cheek ornaments stay outside the facial features and inside the outline.
111 static const char *ornaments[] = {"/", "=", ":", "+", "<", "*", "o", "-"};
112 for (unsigned r = 7; r <= 10; ++r) {
113 unsigned col = 18 - profile[r - 3];
114 char mark = ornaments[marks[r] % 8][0];
115 cells[r][col] = mark;
116 cells[r][31 - col] = mark == '<' ? '>' : mark == '/' ? '\\' : mark;
117 }
118 // One deliberate asymmetric cheek stripe, on a hash-selected side.
119 unsigned accent_row = 7 + marks[0] % 3;
120 unsigned accent_col = 19 - profile[accent_row - 3];
121 if (marks[1] & 1)
122 accent_col = 31 - accent_col;
123 cells[accent_row][accent_col] = '!';
124 cells[accent_row + 1][accent_col] = '!';
125
126 size_t required = KEYMASK_ROWS * (KEYMASK_WIDTH + 1) + 1;
127 for (unsigned r = 0; r < KEYMASK_INTERIOR_ROWS; ++r)
128 for (unsigned c = 0; c < KEYMASK_INTERIOR_COLUMNS; ++c)
129 if (unicode && cells[r][c] == '#')
130 required += 2;
131 if (size < required)
132 return SET_ERRNO(ERROR_INVALID_PARAM, "Keymask output needs %zu bytes", required);
133 char *p = out;
134 const char border[] = "+--------------------------------+\n";
135 memcpy(p, border, sizeof(border) - 1);
136 p += sizeof(border) - 1;
137 for (unsigned r = 0; r < KEYMASK_INTERIOR_ROWS; ++r) {
138 *p++ = '|';
139 for (unsigned c = 0; c < KEYMASK_INTERIOR_COLUMNS; ++c) {
140 if (unicode && cells[r][c] == '#') {
141 memcpy(p, "\xe2\x96\x91", 3);
142 p += 3;
143 } else {
144 *p++ = cells[r][c];
145 }
146 }
147 *p++ = '|';
148 *p++ = '\n';
149 }
150 memcpy(p, border, sizeof(border));
151 return ASCIICHAT_OK;
152}
#define SET_ERRNO(code,...)
char cells[(512 *256 *2)]
Definition fft.c:21
unsigned char uint8_t
Definition common.h:60
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_CRYPTO
Definition error_codes.h:93
@ ERROR_INVALID_PARAM
asciichat_error_t key_fingerprint_digest(const uint8_t public_key[ED25519_PUBLIC_KEY_SIZE], uint8_t digest[KEYMASK_DIGEST_SIZE])
Definition keymask.c:11
#define KEYMASK_INTERIOR_ROWS
Definition keymask.c:3
asciichat_error_t keymask_render(const uint8_t digest[KEYMASK_DIGEST_SIZE], bool unicode, char *out, size_t size)
Definition keymask.c:42
#define KEYMASK_INTERIOR_COLUMNS
Definition keymask.c:4
#define KEYMASK_WIDTH
Definition keymask.h:10
#define KEYMASK_ROWS
Definition keymask.h:11
#define KEYMASK_DIGEST_SIZE
Definition keymask.h:9
static bool size_t gotel_query_t * out
Definition queries.h:8
#define ED25519_PUBLIC_KEY_SIZE
Ed25519 public key size in bytes.
Definition sizes.h:33
🔤 String Manipulation and Shell Escaping Utilities