xref: /haiku/src/add-ons/kernel/debugger/qrencode/module.cpp (revision 25a7b01d15612846f332751841da3579db313082)
1 /*
2  * Copyright 2012, Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Michael Lotz <mmlr@mlotz.ch>
7  */
8 
9 
10 #include <debug.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 
15 extern "C" {
16 #include "qrencode.h"
17 #include "qrspec.h"
18 }
19 
20 
21 extern "C" {
22 extern void abort_debugger_command();
23 }
24 
25 
26 static const char* kWebPostBaseURL = "http://mlotz.ch/q";
27 
28 static char sStringBuffer[16 * 1024];
29 static char sEncodeBuffer[3 * 1024];
30 static int sBufferPosition = 0;
31 static int sQRCodeVersion = 19;
32 static QRecLevel sQRCodeLevel = QR_ECLEVEL_L;
33 static char sWebPostId[64];
34 static int sWebPostCounter = 0;
35 
36 
37 static bool
qrcode_bit(QRcode * qrCode,int x,int y)38 qrcode_bit(QRcode* qrCode, int x, int y)
39 {
40 	if (x >= qrCode->width || y >= qrCode->width)
41 		return false;
42 
43 	return (qrCode->data[y * qrCode->width + x] & 0x01) == 1;
44 }
45 
46 
47 static void
move_to_and_clear_line(int line)48 move_to_and_clear_line(int line)
49 {
50 	kprintf(" \x1b[%dd\x1b[G\x1b[K", line + 1);
51 }
52 
53 
54 static bool
print_qrcode(QRcode * qrCode,bool waitForKey)55 print_qrcode(QRcode* qrCode, bool waitForKey)
56 {
57 	move_to_and_clear_line(0);
58 	for (int y = 0; y < qrCode->width; y += 2) {
59 		move_to_and_clear_line(y / 2 + 1);
60 		kputs("  ");
61 
62 		for (int x = 0; x < qrCode->width; x++) {
63 			bool upper = qrcode_bit(qrCode, x, y);
64 			bool lower = qrcode_bit(qrCode, x, y + 1);
65 			if (upper == lower)
66 				kputs(upper ? "\x11" : " ");
67 			else
68 				kputs(upper ? "\x12" : "\x13");
69 		}
70 	}
71 
72 	move_to_and_clear_line(qrCode->width / 2 + 2);
73 	move_to_and_clear_line(qrCode->width / 2 + 3);
74 
75 	if (waitForKey) {
76 		kputs("press q to abort or any other key to continue...\x1b[1B\x1b[G");
77 		if (kgetc() == 'q')
78 			return false;
79 	}
80 
81 	return true;
82 }
83 
84 
85 static int
encode_url(const char * query,const char * data,int encodeLength,int inputLength)86 encode_url(const char* query, const char* data, int encodeLength,
87 	int inputLength)
88 {
89 	sEncodeBuffer[0] = 0;
90 	strlcat(sEncodeBuffer, kWebPostBaseURL, encodeLength + 1);
91 	strlcat(sEncodeBuffer, "?i=", encodeLength + 1);
92 	strlcat(sEncodeBuffer, sWebPostId, encodeLength + 1);
93 	strlcat(sEncodeBuffer, "&", encodeLength + 1);
94 	strlcat(sEncodeBuffer, query, encodeLength + 1);
95 	int position = strlcat(sEncodeBuffer, "=", encodeLength + 1);
96 	if (position > encodeLength)
97 		return -1;
98 
99 	int copyCount = 0;
100 	while (inputLength > 0 && position < encodeLength) {
101 		char character = data[copyCount];
102 		if ((character >= 'a' && character <= 'z')
103 			|| (character >= 'A' && character <= 'Z')
104 			|| (character >= '0' && character <= '9')
105 			|| character == '.' || character == '-' || character == '_'
106 			|| character == '~'
107 			// These aren't strictly valid, but seem to work.
108 			|| character == '/' || character == '(' || character == ')'
109 			|| character == '=' || character == '^' || character == '?'
110 			|| character == '|' || character == '*' || character == '@'
111 			|| character == ';' || character == ':' || character == ','
112 			|| character == '{' || character == '}' || character == '['
113 			|| character == ']' || character == '<' || character == '>'
114 			|| character == '!' || character == '\\') {
115 			sEncodeBuffer[position++] = character;
116 			sEncodeBuffer[position] = 0;
117 		} else if (character == ' ') {
118 			// Encode spaces as '+' as that's shorter than %20.
119 			sEncodeBuffer[position++] = '+';
120 			sEncodeBuffer[position] = 0;
121 		} else {
122 			// Encode to a %xx escape.
123 			if (encodeLength - position < 3) {
124 				// Doesn't fit anymore, we're done.
125 				break;
126 			}
127 
128 			char escaped[4];
129 			sprintf(escaped, "%%%.2x", character);
130 			position = strlcat(sEncodeBuffer, escaped, encodeLength + 1);
131 		}
132 
133 		inputLength--;
134 		copyCount++;
135 	}
136 
137 	return copyCount;
138 }
139 
140 
141 static int
qrencode(int argc,char * argv[])142 qrencode(int argc, char* argv[])
143 {
144 	if (argc > 1 && strcmp(argv[1], "--help") == 0) {
145 		kprintf("%s [<string>]\n", argv[0]);
146 		kprintf("If an argument is given, encodes that string as a QR code.\n"
147 			"Otherwise the current QR buffer is encoded as QR codes.\n"
148 			"When encoding from the QR buffer, the buffer is left intact.\n"
149 			"use qrclear to clear the QR buffer or qrflush to encode/clear.\n");
150 		return 1;
151 	}
152 
153 	const char* source = NULL;
154 	if (argc < 2) {
155 		sStringBuffer[sBufferPosition] = 0;
156 		source = sStringBuffer;
157 	} else
158 		source = argv[1];
159 
160 	int inputLength = strlen(source);
161 	int encodeLength = QRspec_getDataLength(sQRCodeVersion, sQRCodeLevel) - 3;
162 	while (inputLength > 0) {
163 		int copyCount = 0;
164 		if (sWebPostId[0] != 0) {
165 			copyCount = encode_url(sWebPostCounter++ == 0 ? "clear" : "d",
166 				source, encodeLength, inputLength);
167 			if (copyCount < 0) {
168 				kprintf("Failed to URL encode buffer.\n");
169 				QRcode_clearCache();
170 				return 1;
171 			}
172 		} else {
173 			copyCount = inputLength < encodeLength ? inputLength : encodeLength;
174 			memcpy(sEncodeBuffer, source, copyCount);
175 			sEncodeBuffer[copyCount] = 0;
176 		}
177 
178 		QRcode* qrCode = QRcode_encodeString8bit(sEncodeBuffer, sQRCodeVersion,
179 			sQRCodeLevel);
180 		if (qrCode == NULL) {
181 			kprintf("Failed to encode buffer into qr code.\n");
182 			QRcode_clearCache();
183 			return 1;
184 		}
185 
186 		source += copyCount;
187 		inputLength -= copyCount;
188 
189 		bool doContinue = print_qrcode(qrCode, inputLength > 0);
190 		QRcode_free(qrCode);
191 
192 		if (!doContinue) {
193 			QRcode_clearCache();
194 			abort_debugger_command();
195 				// Does not return.
196 		}
197 	}
198 
199 	QRcode_clearCache();
200 	return 0;
201 }
202 
203 
204 static int
qrclear(int argc,char * argv[])205 qrclear(int argc, char* argv[])
206 {
207 	if (argc > 1 && strcmp(argv[1], "--help") == 0) {
208 		kprintf("Clears the current QR buffer.\n");
209 		return 0;
210 	}
211 
212 	sBufferPosition = 0;
213 	sStringBuffer[0] = 0;
214 	return 0;
215 }
216 
217 
218 static int
qrflush(int argc,char * argv[])219 qrflush(int argc, char* argv[])
220 {
221 	if (argc > 1 && strcmp(argv[1], "--help") == 0) {
222 		kprintf("Flushes the current QR buffer by encoding QR codes from\n"
223 			"the data and then clears the QR buffer.\n");
224 		return 1;
225 	}
226 
227 	qrencode(0, NULL);
228 	qrclear(0, NULL);
229 	return 0;
230 }
231 
232 
233 static int
qrappend(int argc,char * argv[])234 qrappend(int argc, char* argv[])
235 {
236 	if (argc < 2 || (argc > 1 && strcmp(argv[1], "--help") == 0)) {
237 		kprintf("%s <string>\n", argv[0]);
238 		kprintf("Appends the given string to the QR buffer. Can be used as\n"
239 			"the target of a pipe command to accumulate the output of other\n"
240 			"commands in the QR buffer.\n"
241 			"Note that this command will flush the QR buffer when it runs\n"
242 			"full to make room for the new string. This will cause QR codes\n"
243 			"to be generated while the append command still runs. As these\n"
244 			"implicit flushes only happen to make room in the buffer, the\n"
245 			"strings are afterwards appended aren't flushed, so make sure to\n"
246 			"execute the qrflush command to generate codes for these as\n"
247 			"well.\n");
248 		return 1;
249 	}
250 
251 	const char* source = argv[1];
252 	int length = strlen(source) + 1;
253 
254 	while (length > 0) {
255 		int copyCount = sizeof(sStringBuffer) - sBufferPosition - 1;
256 		if (copyCount == 0) {
257 			// The buffer is full, we need to flush it.
258 			qrflush(0, NULL);
259 		}
260 
261 		if (length < copyCount)
262 			copyCount = length;
263 
264 		memcpy(sStringBuffer + sBufferPosition, source, copyCount);
265 		sBufferPosition += copyCount;
266 		source += copyCount;
267 		length -= copyCount;
268 	}
269 
270 	// Overwrite the 0 byte that was copied extra with a newline.
271 	if (sBufferPosition > 0)
272 		sStringBuffer[sBufferPosition - 1] = '\n';
273 
274 	return 0;
275 }
276 
277 
278 static int
qrconfig(int argc,char * argv[])279 qrconfig(int argc, char* argv[])
280 {
281 	if (argc < 2 || (argc > 1 && strcmp(argv[1], "--help") == 0)) {
282 		kprintf("%s <version>\n", argv[0]);
283 		kprintf("Sets the QR version to be used. Valid versions range from 1\n"
284 			"to 40 where each version determines the size of the QR code.\n"
285 			"Version 1 is 21x21 in size and each version increments that size\n"
286 			"by 4x4 up to 177x177 for version 40.\n"
287 			"Bigger QR codes can hold more data, so ideally you use a version\n"
288 			"that makes the QR code as big as possible while still fitting on\n"
289 			"your screen.\n"
290 			"You can test the new config by running qrencode\n"
291 			"with a test string.\n"
292 			"Note that due to memory constraints in the kernel debugger, some\n"
293 			"of the higher versions may actually not work. The kernel\n"
294 			"debugger heap size can be adjusted using the KDEBUG_HEAP define\n"
295 			"in the kernel_debug_config.h header\n");
296 		return 1;
297 	}
298 
299 	int newVersion = atoi(argv[1]);
300 	if (newVersion <= 0 || newVersion > 40) {
301 		kprintf("Invalid QR code version supplied, "
302 			"must be between 1 and 40.\n");
303 		return 1;
304 	}
305 
306 	sQRCodeVersion = newVersion;
307 	return 0;
308 }
309 
310 
311 static int
qrwebpost(int argc,char * argv[])312 qrwebpost(int argc, char* argv[])
313 {
314 	if (argc >= 3 && strcmp(argv[1], "start") == 0) {
315 		strlcpy(sWebPostId, argv[2], sizeof(sWebPostId));
316 		sWebPostCounter = 0;
317 
318 		// Generate the clear code.
319 		const char* args[2] = { "", "yes" };
320 		qrencode(2, (char**)args);
321 	} else if (argc >= 2 && strcmp(argv[1], "stop") == 0) {
322 		sWebPostId[0] = 0;
323 	} else {
324 		kprintf("%s start <id>\n", argv[0]);
325 		kprintf("%s stop\n", argv[0]);
326 		kprintf("Causes the QR codes to be rendered as URLs that resolve to\n"
327 			"a service that allows appending the data of multiple QR codes\n"
328 			"for easier handling.\n"
329 			"An initial QR code is generated that invokes a clear operation\n"
330 			"to prepare the output file on the server.\n"
331 			"Note that there is no logic behind the service, if you and\n"
332 			"someone else use the same id at the same time, they will\n"
333 			"overwrite eachother. Therefore use a reasonably unique name.\n");
334 		return 1;
335 	}
336 
337 	return 0;
338 }
339 
340 
341 static status_t
std_ops(int32 op,...)342 std_ops(int32 op, ...)
343 {
344 	switch (op) {
345 		case B_MODULE_INIT:
346 			add_debugger_command("qrencode", qrencode,
347 				"encodes a string / the current QR buffer");
348 			add_debugger_command("qrappend", qrappend,
349 				"appends a string to the QR buffer");
350 			add_debugger_command("qrclear", qrclear,
351 				"clears the current QR buffer");
352 			add_debugger_command("qrflush", qrflush,
353 				"encodes the current QR buffer and clears it");
354 			add_debugger_command("qrconfig", qrconfig,
355 				"sets the QR code version to be used");
356 			add_debugger_command("qrwebpost", qrwebpost,
357 				"sets up URL encoding for QR codes");
358 			return B_OK;
359 		case B_MODULE_UNINIT:
360 			remove_debugger_command("qrencode", qrencode);
361 			remove_debugger_command("qrappend", qrappend);
362 			remove_debugger_command("qrclear", qrclear);
363 			remove_debugger_command("qrflush", qrflush);
364 			remove_debugger_command("qrconfig", qrconfig);
365 			remove_debugger_command("qrwebpost", qrwebpost);
366 			return B_OK;
367 	}
368 
369 	return B_BAD_VALUE;
370 }
371 
372 
373 static struct debugger_module_info sModuleInfo = {
374 	{
375 		"debugger/qrencode/v1",
376 		0,
377 		&std_ops
378 	},
379 	NULL,
380 	NULL,
381 	NULL,
382 	NULL
383 };
384 
385 module_info *modules[] = {
386 	(module_info *)&sModuleInfo,
387 	NULL
388 };
389 
390