/ src / codec / pemdec.t0
pemdec.t0
  1  \ Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
  2  \
  3  \ Permission is hereby granted, free of charge, to any person obtaining 
  4  \ a copy of this software and associated documentation files (the
  5  \ "Software"), to deal in the Software without restriction, including
  6  \ without limitation the rights to use, copy, modify, merge, publish,
  7  \ distribute, sublicense, and/or sell copies of the Software, and to
  8  \ permit persons to whom the Software is furnished to do so, subject to
  9  \ the following conditions:
 10  \
 11  \ The above copyright notice and this permission notice shall be 
 12  \ included in all copies or substantial portions of the Software.
 13  \
 14  \ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 15  \ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 16  \ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 17  \ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 18  \ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 19  \ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 20  \ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 21  \ SOFTWARE.
 22  
 23  preamble {
 24  
 25  #include "inner.h"
 26  
 27  #define CTX   ((br_pem_decoder_context *)(void *)((unsigned char *)t0ctx - offsetof(br_pem_decoder_context, cpu)))
 28  
 29  /* see bearssl_pem.h */
 30  void
 31  br_pem_decoder_init(br_pem_decoder_context *ctx)
 32  {
 33  	memset(ctx, 0, sizeof *ctx);
 34  	ctx->cpu.dp = &ctx->dp_stack[0];
 35  	ctx->cpu.rp = &ctx->rp_stack[0];
 36  	br_pem_decoder_init_main(&ctx->cpu);
 37  	br_pem_decoder_run(&ctx->cpu);
 38  }
 39  
 40  /* see bearssl_pem.h */
 41  size_t
 42  br_pem_decoder_push(br_pem_decoder_context *ctx,
 43  	const void *data, size_t len)
 44  {
 45  	if (ctx->event) {
 46  		return 0;
 47  	}
 48  	ctx->hbuf = data;
 49  	ctx->hlen = len;
 50  	br_pem_decoder_run(&ctx->cpu);
 51  	return len - ctx->hlen;
 52  }
 53  
 54  /* see bearssl_pem.h */
 55  int
 56  br_pem_decoder_event(br_pem_decoder_context *ctx)
 57  {
 58  	int event;
 59  
 60  	event = ctx->event;
 61  	ctx->event = 0;
 62  	return event;
 63  }
 64  
 65  }
 66  
 67  \ Define a word that evaluates to the address of a field within the
 68  \ decoder context.
 69  : addr:
 70  	next-word { field }
 71  	"addr-" field + 0 1 define-word
 72  	0 8191 "offsetof(br_pem_decoder_context, " field + ")" + make-CX
 73  	postpone literal postpone ; ;
 74  
 75  addr: event
 76  addr: name
 77  addr: buf
 78  addr: ptr
 79  
 80  \ Set a byte at a specific address (offset within the context).
 81  cc: set8 ( value addr -- ) {
 82  	size_t addr = T0_POP();
 83  	unsigned x = T0_POP();
 84  	*((unsigned char *)CTX + addr) = x;
 85  }
 86  
 87  \ Get a byte at a specific address (offset within the context).
 88  cc: get8 ( addr -- value ) {
 89  	size_t addr = T0_POP();
 90  	T0_PUSH(*((unsigned char *)CTX + addr));
 91  }
 92  
 93  \ Send an event.
 94  : send-event ( event -- )
 95  	addr-event set8 co ;
 96  
 97  \ Low-level function to read a single byte. Returned value is the byte
 98  \ (0 to 255), or -1 if there is no available data.
 99  cc: read8-native ( -- x ) {
100  	if (CTX->hlen > 0) {
101  		T0_PUSH(*CTX->hbuf ++);
102  		CTX->hlen --;
103  	} else {
104  		T0_PUSHi(-1);
105  	}
106  }
107  
108  \ Read next byte. Block until the next byte is available.
109  : read8 ( -- x )
110  	begin read8-native dup 0< ifnot ret then drop co again ;
111  
112  \ Read bytes until next end-of-line.
113  : skip-newline ( -- )
114  	begin read8 `\n <> while repeat ;
115  
116  \ Read bytes until next end-of-line; verify that they are all whitespace.
117  \ This returns -1 if they were all whitespace, 0 otherwise.
118  : skip-newline-ws ( -- bool )
119  	-1 { r }
120  	begin read8 dup `\n <> while ws? ifnot 0 >r then repeat
121  	drop r ;
122  
123  \ Normalise a byte to uppercase (ASCII only).
124  : norm-upper ( x -- x )
125  	dup dup `a >= swap `z <= and if 32 - then ;
126  
127  \ Read bytes and compare with the provided string. On mismatch, the
128  \ rest of the line is consumed. Matching is not case sensitive.
129  : match-string ( str -- bool )
130  	begin
131  		dup data-get8 norm-upper dup ifnot 2drop -1 ret then
132  		read8 norm-upper dup `\n = if drop 2drop 0 ret then
133  		= ifnot drop skip-newline 0 ret then
134  		1+
135  	again ;
136  
137  \ Read bytes into the provided buffer, but no more than the provided
138  \ count. Reading stops when end-of-line is reached. Returned value
139  \ is the count of bytes written to the buffer, or 0 if the buffer size
140  \ was exceeded. All bytes are normalised to uppercase (ASCII only).
141  : read-bytes ( addr len -- len )
142  	dup { orig-len }
143  	swap
144  	begin
145  		over ifnot 2drop skip-newline 0 ret then
146  		read8 dup `\n = if 2drop orig-len swap - ret then
147  		dup `\r = if drop else norm-upper over set8 then
148  		1+ swap 1- swap
149  	again ;
150  
151  \ Remove trailing dashes from the name buffer.
152  : trim-dashes ( len -- )
153  	begin dup while
154  		1-
155  		dup addr-name + get8 `- <> if
156  			addr-name + 1+ 0 swap set8 ret
157  		then
158  	repeat
159  	addr-name set8 ;
160  
161  \ Scan input for next "begin" banner.
162  : next-banner-begin ( -- )
163  	begin
164  		"-----BEGIN " match-string if
165  			addr-name 127 read-bytes
166  			dup if trim-dashes ret then
167  			drop
168  		then
169  	again ;
170  
171  \ Convert a Base64 character to its numerical value. Returned value is
172  \ 0 to 63 for Base64 characters, -1 for '=', and -2 for all other characters.
173  cc: from-base64 ( char -- x ) {
174  	uint32_t c = T0_POP();
175  	uint32_t p, q, r, z;
176  	p = c - 0x41;
177  	q = c - 0x61;
178  	r = c - 0x30;
179  
180  	z = ((p + 2) & -LT(p, 26))
181  		| ((q + 28) & -LT(q, 26))
182  		| ((r + 54) & -LT(r, 10))
183  		| (64 & -EQ(c, 0x2B))
184  		| (65 & -EQ(c, 0x2F))
185  		| EQ(c, 0x3D);
186  	T0_PUSHi((int32_t)z - 2);
187  }
188  
189  \ Test whether a character is whitespace (but not a newline).
190  : ws? ( x -- bool )
191  	dup `\n <> swap 32 <= and ;
192  
193  \ Read next character, skipping whitespace (except newline).
194  : next-nonws ( -- x )
195  	begin
196  		read8 dup ws? ifnot ret then
197  		drop
198  	again ;
199  
200  \ Write one byte in the output buffer.
201  cc: write8 ( x -- ) {
202  	unsigned char x = (unsigned char)T0_POP();
203  	CTX->buf[CTX->ptr ++] = x;
204  	if (CTX->ptr == sizeof CTX->buf) {
205  		if (CTX->dest) {
206  			CTX->dest(CTX->dest_ctx, CTX->buf, sizeof CTX->buf);
207  		}
208  		CTX->ptr = 0;
209  	}
210  }
211  
212  \ Flush the output buffer.
213  cc: flush-buf ( -- ) {
214  	if (CTX->ptr > 0) {
215  		if (CTX->dest) {
216  			CTX->dest(CTX->dest_ctx, CTX->buf, CTX->ptr);
217  		}
218  		CTX->ptr = 0;
219  	}
220  }
221  
222  \ Decode the four next Base64 characters. Returned value is:
223  \    0   quartet processed, three bytes produced.
224  \   -1   dash encountered as first character (no leading whitespace).
225  \    1   quartet processed, one or two bytes produced, terminator reached.
226  \    2   end-of-line reached.
227  \    3   error.
228  \ For all positive return values, the remaining of the current line has been
229  \ consumed.
230  : decode-next-quartet ( -- r )
231  	\ Process first character. It may be a dash.
232  	read8 dup `- = if drop -1 ret then
233  	dup ws? if drop next-nonws then
234  	dup `\n = if drop 2 ret then
235  	from-base64 dup 0< if drop skip-newline 3 ret then
236  	{ acc }
237  
238  	\ Second character.
239  	next-nonws dup `\n = if drop 3 ret then
240  	from-base64 dup 0< if drop skip-newline 3 ret then
241  	acc 6 << + >acc
242  
243  	\ Third character: may be an equal sign.
244  	next-nonws dup `\n = if drop 3 ret then
245  	dup `= = if
246  		\ Fourth character must be an equal sign.
247  		drop
248  		next-nonws dup `\n = if drop 3 ret then
249  		skip-newline-ws ifnot drop 3 ret then
250  		`= <> if 3 ret then
251  		acc 0x0F and if 3 ret then
252  		acc 4 >> write8
253  		1 ret
254  	then
255  	from-base64 dup 0< if drop skip-newline 3 ret then
256  	acc 6 << + >acc
257  
258  	\ Fourth character: may be an equal sign.
259  	next-nonws dup `\n = if drop 3 ret then
260  	dup `= = if
261  		drop skip-newline-ws ifnot 3 ret then
262  		acc 0x03 and if 3 ret then
263  		acc 10 >> write8
264  		acc 2 >> write8
265  		1 ret
266  	then
267  	from-base64 dup 0< if drop skip-newline 3 ret then
268  	acc 6 << + >acc
269  	acc 16 >> write8
270  	acc 8 >> write8
271  	acc write8
272  	0 ;
273  
274  \ Check trailer line (possibly, the leading dash has been read). This
275  \ sends the appropriate event.
276  : check-trailer ( bool -- )
277  	ifnot
278  		begin read8 dup `\n = while drop repeat
279  		`- <> if skip-newline 3 send-event ret then
280  	then
281  	"----END " match-string ifnot 3 send-event ret then
282  	flush-buf
283  	skip-newline 2 send-event ;
284  
285  \ Decode one line worth of characters. Returned value is 0 if the end of the
286  \ object is reached, -1 otherwise. The end of object or error event is sent.
287  : decode-line ( -- bool )
288  	-1 { first }
289  	begin
290  		decode-next-quartet
291  		case
292  			0 of endof
293  			-1 of
294  				first ifnot
295  					skip-newline 3 send-event
296  				else
297  					-1 check-trailer
298  				then
299  				0 ret
300  			endof
301  			1 of 0 check-trailer 0 ret endof
302  			2 of -1 ret endof
303  
304  			\ On decoding error
305  			drop 3 send-event 0 ret
306  		endcase
307  		0 >first
308  	again ;
309  
310  : main ( -- ! )
311  	begin
312  		next-banner-begin 1 send-event
313  		begin decode-line while repeat
314  	again ;