mybb.c

最終更新:2009/12/16

mybb.c

001: /* mybb.c
002:  * Bucket Brigade のサンプル
003:  * (1)ストリームでのデータ操作関数
004:  * ・ストリーム間のバイト単位での移動
005:  * ・ストリームからの行単位での切り出し
006:  * ・ストリームからファイルへの書き出し
007:  * (2)チャンク形式のデコーダ関数
008:  */
009: 
010: #include <stdlib.h>
011:  
012: #include "apr_general.h"
013: #include "apr_buckets.h"
014: #include "apr_file_io.h"
015: #include "apr_strings.h"
016: #include "mybb.h"
017: 
018: #if 0
019: /*
020:  * ストリーム bb1 から size バイト分の buckets をバッファに移す。
021:  * 返り値は実際に移したバイト数。
022:  * size<=0 の時は何もしない。0が返る。
023:  * ストリーム bb1 が空の時も何もしない。0が返る。
024:  * 終了後は、ストリーム bb1 の先頭 size バイト分がなくなる。
025:  */
026: 
027: int move_bytes_from_bb_to_buf (
028:   apr_bucket_brigade *bb1
029:   , apr_size_t size
030:   , char *buf
031: ) {
032:   int ret=0;
033:   apr_size_t remain=size;
034:   apr_bucket *b=NULL;
035:   
036:   if (size <=0) {
037:     return 0;
038:   }
039:   
040:   if (APR_BRIGADE_EMPTY(bb1)) {
041:     return 0;
042:   }
043: 
044: if(0)puts("#move_bytes_from_bb(1)");
045: /*  for (b = APR_BRIGADE_FIRST(bb1); b != APR_BRIGADE_SENTINEL(bb1); b = APR_BUCKET_NEXT(b)) {
046:     バケット b をストリーム bb1 から削除し、bb2 に挿入した場合に、APR_BUCKET_NEXT(b) は bb2 での
047:     位置関係に依存したものとなるため、ループの書き方はいつもと少し違う。毎回先頭のバケットを
048:     チェックする。
049: */
050:   for (b = APR_BRIGADE_FIRST(bb1); b != APR_BRIGADE_SENTINEL(bb1); b = APR_BRIGADE_FIRST(bb1)) {
051: if(0)printf("(1)remain=[%d], b->length=[%d], metadata=[%d]\n", remain, b->length, APR_BUCKET_IS_METADATA(b));
052:     if (remain <= 0) {
053:       break;
054:     }
055: 
056: if(0)puts("#move_bytes_from_bb(2)");
057:     if (b->length == 0) {
058:       /* 削除しておく */
059:       APR_BUCKET_REMOVE(b);
060:       continue;
061:     }
062: 
063: if(0)puts("#move_bytes_from_bb(3)");
064:     if (APR_BUCKET_IS_SOCKET(b)) {
065:       char *buf = NULL;
066:       apr_size_t len=0;
067:       apr_bucket_read(b, &buf, &len, APR_BLOCK_READ); /* 読み飛ばすだけ。 */
068:       
069:       /* バケットがソケットの場合には read しないとストリームにデータが積まれないらしい。b->length も UNKNOWN(-1)。 */
070:     }
071: 
072: if(0)puts("#move_bytes_from_bb(4)");
073: if(0)printf("(2)remain=[%d], b->length=[%d], metadata=[%d]\n", remain, b->length, APR_BUCKET_IS_METADATA(b));
074: 
075: 
076:     if (b->length > remain) {
077: if(0)printf("(3)remain=[%d], b->length=[%ld], metadata=[%d]\n", remain, b->length, APR_BUCKET_IS_METADATA(b));
078:       /* b を remain バイト分と b->length-remain バイト分に分割 */
079:       apr_bucket_split(b, remain);
080:       /* remain バイト分を bb1 から削除 */
081:       APR_BUCKET_REMOVE(b);
082:       /* remain バイト分をバッファコピー */
083:       apr_bucket_read(b, buf+ret, remain, 1);
084:       /* 移動した分をカウント */
085:       ret += remain;
086:       /* ループを抜ける */
087:       break;
088:     }
089: 
090: if(0)puts("#move_bytes_from_bb(8)");
091:     /* b を bb1 から削除 */
092:     APR_BUCKET_REMOVE(b);
093: if(0)puts("#move_bytes_from_bb(9)");
094:     /* b を bb2 に追加 */
095:     apr_bucket_read(b, buf+ret, b->length, 1);
096: if(0)puts("#move_bytes_from_bb(10)");
097:     /* 移動した分をカウント */
098:     ret += b->length;
099:     remain -= b->length;
100: if(0)printf("remain=[%d]\n", remain);
101:   }
102:   
103: if(0)puts("#move_bytes_from_bb(11)");
104:   return ret;
105: }
106: #endif
107: 
108: /*
109:  * ストリーム bb1 から size バイト分の buckets をストリーム bb2 に移す。
110:  * 返り値は実際に移したバイト数。
111:  * size<=0 の時は何もしない。0が返る。
112:  * ストリーム bb1 が空の時も何もしない。0が返る。
113:  * 終了後は、ストリーム bb1 の先頭 size バイト分がなくなる。ストリーム bb2 は後尾に size バイト分増えている。
114:  */
115: 
116: int move_bytes_from_bb (apr_bucket_brigade *bb1, apr_size_t size, apr_bucket_brigade *bb2) {
117:   int ret=0;
118:   apr_size_t remain=size;
119:   apr_bucket *b=NULL;
120:   
121:   if (size <=0) {
122:     return 0;
123:   }
124:   
125:   if (APR_BRIGADE_EMPTY(bb1)) {
126:     return 0;
127:   }
128: 
129: if(0)puts("#move_bytes_from_bb(1)");
130: /*  for (b = APR_BRIGADE_FIRST(bb1); b != APR_BRIGADE_SENTINEL(bb1); b = APR_BUCKET_NEXT(b)) {
131:     バケット b をストリーム bb1 から削除し、bb2 に挿入した場合に、APR_BUCKET_NEXT(b) は bb2 での
132:     位置関係に依存したものとなるため、ループの書き方はいつもと少し違う。毎回先頭のバケットを
133:     チェックする。
134: */
135:   for (b = APR_BRIGADE_FIRST(bb1); b != APR_BRIGADE_SENTINEL(bb1); b = APR_BRIGADE_FIRST(bb1)) {
136: if(0)printf("(1)remain=[%d], b->length=[%d], metadata=[%d]\n", remain, b->length, APR_BUCKET_IS_METADATA(b));
137:     if (remain <= 0) {
138:       break;
139:     }
140: 
141: if(0)puts("#move_bytes_from_bb(2)");
142:     if (b->length == 0) {
143:       /* 削除しておく */
144:       APR_BUCKET_REMOVE(b);
145:       continue;
146:     }
147: 
148: if(0)puts("#move_bytes_from_bb(3)");
149:     if (APR_BUCKET_IS_SOCKET(b)) {
150:       const char *buf = NULL;
151:       apr_size_t len=0;
152:       apr_bucket_read(b, &buf, &len, APR_BLOCK_READ); /* 読み飛ばすだけ。 */
153:       
154:       /* バケットがソケットの場合には read しないとストリームにデータが積まれないらしい。b->length も UNKNOWN(-1)。 */
155:     }
156: 
157: if(0)puts("#move_bytes_from_bb(4)");
158: if(0)printf("(2)remain=[%d], b->length=[%d], metadata=[%d]\n", remain, b->length, APR_BUCKET_IS_METADATA(b));
159: 
160: 
161:     if (b->length > remain) {
162: if(0)printf("(3)remain=[%d], b->length=[%d], metadata=[%d]\n", remain, b->length, APR_BUCKET_IS_METADATA(b));
163:       /* b を remain バイト分と b->length-remain バイト分に分割 */
164:       apr_bucket_split(b, remain);
165: if(0)puts("#move_bytes_from_bb(5)");
166:       /* remain バイト分を bb1 から削除 */
167:       APR_BUCKET_REMOVE(b);
168: if(0)puts("#move_bytes_from_bb(6)");
169:       /* remain バイト分を bb2 に追加 */
170:       APR_BRIGADE_INSERT_TAIL(bb2, b);
171: if(0)puts("#move_bytes_from_bb(7)");
172:       /* 移動した分をカウント */
173:       ret += remain;
174:       /* ループを抜ける */
175:       break;
176:     }
177: 
178: if(0)puts("#move_bytes_from_bb(8)");
179:     /* b を bb1 から削除 */
180:     APR_BUCKET_REMOVE(b);
181: if(0)puts("#move_bytes_from_bb(9)");
182:     /* b を bb2 に追加 */
183:     APR_BRIGADE_INSERT_TAIL(bb2, b);
184: if(0)puts("#move_bytes_from_bb(10)");
185:     /* 移動した分をカウント */
186:     ret += b->length;
187:     remain -= b->length;
188: if(0)printf("remain=[%d]\n", remain);
189:   }
190:   
191: if(0)puts("#move_bytes_from_bb(11)");
192:   return ret;
193: }
194: 
195: 
196: /*
197:  * LF で終わる一行分の文字列をストリーム bb から切り出す。
198:  * 最後のLF もしくは CRLF はヌル文字で置き換えられる。
199:  * 返り値の文字列は必ずヌル文字で終わる
200:  * 終了後は、ストリーム bb から先頭の LF で終わる一行分のデータがなくなる。
201:  * apr_brigade_split_line でエラーになった場合やサイズが大きすぎる場合(MAX_LINE_SIZE 以上)は NULL が返る。
202:  * 空行の場合は、"" が返る。
203:  */
204: 
205: char *get_line_from_bb (apr_bucket_brigade *bb)
206: {
207:   apr_bucket *b = NULL;
208:   char *ret;
209:   char *p = NULL;
210:   apr_size_t len=0;
211:   apr_bucket_brigade *tmp_bb=apr_brigade_create(bb->p, bb->bucket_alloc);
212:   
213:   if (APR_SUCCESS != apr_brigade_split_line(tmp_bb, bb, APR_BLOCK_READ, MAX_LINE_SIZE)) {
214:     return NULL;
215:   }
216: 
217:   
218:   if (APR_BRIGADE_EMPTY(tmp_bb)) { /* こういうケースはある? */
219:     return NULL;
220:   }
221: 
222:   p = ret = apr_palloc(bb->p, MAX_LINE_SIZE);
223: 
224:   for (b = APR_BRIGADE_FIRST(tmp_bb); b != APR_BRIGADE_SENTINEL(tmp_bb); b = APR_BUCKET_NEXT(b)) {
225:     const char *buf = NULL;
226:     apr_size_t buf_len;
227:     
228: 
229:     if (0 == b->length) {
230:       continue;
231:     }
232:     
233:     apr_bucket_read(b, &buf, &buf_len, APR_BLOCK_READ);
234:     if (p-ret+buf_len >= MAX_LINE_SIZE) {
235:       return NULL; /* 長すぎる時も NULL を返す。が、apr_brigade_split_line で成功した場合はこういうケースはないのかもしれない */
236:     }
237:     
238:     memcpy(p, buf, buf_len);
239:     p += buf_len;
240:   }
241:   
242:   *p = '\0'; /* 必ずヌル文字で終端 */
243: 
244:   len = p-ret;
245: 
246:   if (len > 0 && *(p-1) == '\n') { /* 行末の LF はヌル文字に */
247:       *(p-1) = '\0';
248:   }
249:   if (len > 1 && *(p-2) == '\r') { /* 行末の CR はヌル文字に */
250:     *(p-2) = '\0';
251:   }
252: 
253:   apr_brigade_destroy(tmp_bb); /* 一応デストロイしておく。*/
254: 
255:   return ret;
256: }
257: 
258: /*
259:  * 概要:ストリームの中身をファイルに書き込む。バイト数指定なし。
260:  * ストリームの中身は変更なし。
261:  */
262: 
263: void write_from_bb_to_fp (apr_bucket_brigade *bb, apr_file_t *fp) {
264:   apr_bucket *b = NULL;
265:   const char *buf = NULL;
266:   apr_size_t len=0;
267:   for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) {
268:     if (0 == b->length) {
269:       continue;
270:     }
271:     apr_bucket_read(b, &buf, &len, APR_BLOCK_READ);
272: if(0)printf("len=[%d], b->length=[%d], metadata=[%d]\n", len, b->length, APR_BUCKET_IS_METADATA(b));
273: 
274:     apr_file_write(fp, buf, &len);
275:   }
276: }
277: 
278: /*
279:  * ストリーム bb1 から size バイト分の buckets をファイルに書き込む。
280:  * 返り値は実際に移したバイト数。
281:  * size<=0 の時は何もしない。0が返る。
282:  * ストリーム bb1 が空の時も何もしない。0が返る。
283:  * 終了後は、ストリーム bb1 の先頭 size バイト分がなくなる。
284:  */
285: 
286: int move_bytes_from_bb_to_fp (apr_bucket_brigade *bb1, apr_size_t size, apr_file_t *fp) {
287:   int ret=0;
288:   apr_size_t remain=size;
289:   apr_bucket *b=NULL;
290: 
291:   if (size <=0) {
292:     return 0;
293:   }
294:   
295:   if (APR_BRIGADE_EMPTY(bb1)) {
296:     return 0;
297:   }
298: 
299: 
300: /*  for (b = APR_BRIGADE_FIRST(bb1); b != APR_BRIGADE_SENTINEL(bb1); b = APR_BUCKET_NEXT(b)) {
301:     バケット b をストリーム bb1 から削除し、bb2 に挿入した場合に、APR_BUCKET_NEXT(b) は bb2 での
302:     位置関係に依存したものとなるため、ループの書き方はいつもと少し違う。毎回先頭のバケットを
303:     チェックする。
304: */
305:   for (b = APR_BRIGADE_FIRST(bb1); b != APR_BRIGADE_SENTINEL(bb1); b = APR_BRIGADE_FIRST(bb1)) {
306: 
307:     const char *buf = NULL;
308:     apr_size_t len=0;
309: 
310: 
311: if(0)printf("(1)remain=[%d], b->length=[%d], metadata=[%d]\n", remain, b->length, APR_BUCKET_IS_METADATA(b));
312:     if (remain <= 0) {
313:       break;
314:     }
315: 
316: if(0)printf("(1.1)remain=[%d], b->length=[%d], metadata=[%d]\n", remain, b->length, APR_BUCKET_IS_METADATA(b));
317:     if (b->length == 0) {
318:       /* 削除しておく */
319:       APR_BUCKET_REMOVE(b);
320:       continue;
321:     }
322: 
323: if(0)printf("(1.2)remain=[%d], b->length=[%d], metadata=[%d]\n", remain, b->length, APR_BUCKET_IS_METADATA(b));
324:     if (APR_BUCKET_IS_SOCKET(b)) {
325:       apr_bucket_read(b, &buf, &len, APR_BLOCK_READ); /* 読み飛ばすだけ。 */
326:       /* バケットがソケットの場合にはサイズが不明(b->length == (-1))。
327:        * read しないと実際のデータが積まれず、サイズが確定しない。
328:        */
329:     }
330: 
331: if(0)printf("(2)remain=[%d], b->length=[%d], metadata=[%d]\n", remain, b->length, APR_BUCKET_IS_METADATA(b));
332: 
333: 
334:     if (b->length > remain) {
335: if(0)printf("(3)remain=[%d], b->length=[%d], metadata=[%d]\n", remain, b->length, APR_BUCKET_IS_METADATA(b));
336:       /* b を remain バイト分と b->length-remain バイト分に分割 */
337:       apr_bucket_split(b, remain);
338:     }
339: 
340:     /* remain バイト分を bb1 から削除 */
341:     APR_BUCKET_REMOVE(b);
342:     /* remain バイト分をファイルに書き出し */
343:     apr_bucket_read(b, &buf, &len, APR_BLOCK_READ);
344:     apr_file_write(fp, buf, &len);
345:     
346:     /* 書き出した分をカウント */
347:     ret += len;
348:     remain -= len;
349: 
350: if(0)printf("remain=[%d]\n", remain);
351:   }
352:   
353:   return ret;
354: }
355: 
356: /*
357:  * 以下、チャンク形式のデコーダ関数
358:  */
359: 
360: /* -- MEMO --
361:  * From RFC 2616 Section 3.6.1
362:  * Chunked-Body   = *chunk
363:  *                  last-chunk
364:  *                  trailer
365:  *                  CRLF
366:  *
367:  * chunk          = chunk-size [ chunk-extension ] CRLF
368:  *                  chunk-data CRLF
369:  * chunk-size     = 1*HEX
370:  * last-chunk     = 1*("0") [ chunk-extension ] CRLF
371:  *
372:  * chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
373:  * chunk-ext-name = token
374:  * chunk-ext-val  = token | quoted-string
375:  * chunk-data     = chunk-size(OCTET)
376:  * trailer        = *(entity-header CRLF)
377:  *
378:  * From RFC 2616 Section 19.4.6
379:  * length := 0
380:  * read chunk-size, chunk-extension (if any) and CRLF
381:  * while (chunk-size > 0) {
382:  *    read chunk-data and CRLF
383:  *    append chunk-data to entity-body
384:  *    length := length + chunk-size
385:  *    read chunk-size and CRLF
386:  * }
387:  * read entity-header
388:  * while (entity-header not empty) {
389:  *    append entity-header to existing header fields
390:  *    read entity-header
391:  * }
392:  * Content-Length := length
393:  * Remove "chunked" from Transfer-Encoding
394:  *
395:  */
396: 
397: /* 概要:bb1 からの、chunk-size と CRLF の読み出し
398:  * 返り値:
399:  *   成功時:移動した chunk-size (0以上)
400:  *   失敗時:-1
401:  * メモ:chunk-extension は無視する。
402:  */
403: 
404: apr_size_t read_chunk_size_CRLF (apr_bucket_brigade *bb) {
405:   apr_size_t chunk_size = 0;
406: 
407:   char *line = get_line_from_bb(bb);
408: 
409:   if (! line)
410:     return -1;
411: 
412:   if (line) {
413:     char *endptr=NULL;
414:     errno = 0;
415:     /* 16進数の文字列をパースする。
416:      * ・strtol では、"0x" で始まる場合も許容されることに注意
417:      * ・strtol では、16進数の文字の後に続く文字は無視されることにも注意。
418:      *   従って、chunk-extension が存在しても無視される。
419:      *
420:      * XXX
421:      *  long int から apr_size_t への変換は危険かもしれない。
422:      */
423:     chunk_size = (apr_size_t)strtol(line, &endptr, 16); 
424:     if (errno) {
425:       return -1;
426:     }
427:   }
428: 
429:   return chunk_size;
430: }
431: 
432: /* 概要:bb1 からの、chunk-data の bb2 への移動と CRLF の読み出し
433:  * 返り値:
434:  *   成功時:移動したバイト数(0以上)。
435:  *   失敗時:-1
436:  */
437:  
438: int read_chunk_data_CRLF(apr_bucket_brigade *bb1, apr_size_t size, apr_bucket_brigade *bb2) {
439:   int length = 0;
440:   char *line = NULL;
441:   if ((length = move_bytes_from_bb(bb1, size, bb2)) < 0) {
442:     return -1;
443:   }
444:   if (size != length) {
445:     return -1;
446:   }
447:   if ((line = get_line_from_bb(bb1)) == NULL) {
448:     return -1;
449:   }
450:   if (strcmp(line, "")) {
451:     return -1;
452:   }
453:   return length;
454: }
455: 
456: /* 概要:bb1 からの、entity-header と CRLF の読み出し
457:  * 返り値:
458:  *   成功時:1
459:  *   失敗時:0 最後の CRLF を見つけられなかった時
460:  * メモ:読み出した entity-header は無視する。
461:  */
462:  
463: int read_entity_header_CRLF(apr_bucket_brigade *bb1) {
464:   char *line=NULL;
465:   while ((line = get_line_from_bb(bb1))!=NULL) {
466:     if (!strcmp(line, "")) {
467:       return 1;
468:     }
469:     /*
470:      * この位置で、見つけた entity-header に関する何らかの処理(パースして key/value をテーブル
471:      * に保存など)を行なう。ここでは無視する。
472:      */
473:   }
474:   return 0;
475: }
476: 
477: #define read_trailer_CRLF read_entity_header_CRLF
478: 
479: /* 概要:チャンク形式データのデコード
480:  * パラメータ
481:  * IN  bb1 : デコード対象のデータを持つストリーム
482:  * OUT bb2 : デコード後のデータを保管するストリーム
483:  * 返り値:コンテンツの長さ(0以上)。デコード失敗時は -1
484:  */
485: 
486: int decode_chunked_body (apr_bucket_brigade *bb1, apr_bucket_brigade *bb2) {
487:   int length = 0;
488:   int chunk_size=0;
489: 
490:   while ((chunk_size = read_chunk_size_CRLF(bb1)) > 0) {
491:     if (read_chunk_data_CRLF(bb1, chunk_size, bb2) < 0) {
492:       return -1;
493:     }
494:     length += chunk_size;
495:   }
496: 
497:   if (chunk_size < 0) { /* last-chunk がなかった */
498:     return -1;
499:   }
500: 
501:   if (read_trailer_CRLF(bb1) == 0) { /* trailer と最後の CRLF の読み出し */
502:     return -1;
503:   }
504: 
505:   return length;
506: }
507: 
508: /*
509:  * ストリーム bb1 から size バイト分の buckets をソケットに書き込む。
510:  * 返り値は実際に移したバイト数。
511:  * size<=0 の時は何もしない。0が返る。
512:  * ストリーム bb1 が空の時も何もしない。0が返る。
513:  * 終了後は、ストリーム bb1 の先頭 size バイト分がなくなる。
514:  */
515: 
516: int move_bytes_from_bb_to_s (apr_bucket_brigade *bb1, apr_size_t size, apr_socket_t *s) {
517:   int ret=0;
518:   apr_size_t remain=size;
519:   apr_bucket *b=NULL;
520: 
521:   if (size <=0) {
522:     return 0;
523:   }
524:   
525:   if (APR_BRIGADE_EMPTY(bb1)) {
526:     return 0;
527:   }
528: 
529: 
530: /*  for (b = APR_BRIGADE_FIRST(bb1); b != APR_BRIGADE_SENTINEL(bb1); b = APR_BUCKET_NEXT(b)) {
531:     バケット b をストリーム bb1 から削除し、bb2 に挿入した場合に、APR_BUCKET_NEXT(b) は bb2 での
532:     位置関係に依存したものとなるため、ループの書き方はいつもと少し違う。毎回先頭のバケットを
533:     チェックする。
534: */
535:   for (b = APR_BRIGADE_FIRST(bb1); b != APR_BRIGADE_SENTINEL(bb1); b = APR_BRIGADE_FIRST(bb1)) {
536: 
537:     const char *buf = NULL;
538:     apr_size_t len=0;
539: 
540: 
541: if(0)printf("(1)remain=[%d], b->length=[%d], metadata=[%d]\n", remain, b->length, APR_BUCKET_IS_METADATA(b));
542:     if (remain <= 0) {
543:       break;
544:     }
545: 
546:     if (b->length == 0) {
547:       /* 削除しておく */
548:       APR_BUCKET_REMOVE(b);
549:       continue;
550:     }
551: 
552:     if (APR_BUCKET_IS_SOCKET(b)) {
553:       apr_bucket_read(b, &buf, &len, APR_BLOCK_READ); /* 読み飛ばすだけ。 */
554:       /* バケットがソケットの場合にはサイズが不明(b->length == (-1))。
555:        * read しないと実際のデータが積まれず、サイズが確定しない。
556:        */
557:     }
558: 
559: if(0)printf("(2)remain=[%d], b->length=[%d], metadata=[%d]\n", remain, b->length, APR_BUCKET_IS_METADATA(b));
560: 
561: 
562:     if (b->length > remain) {
563: if(0)printf("(3)remain=[%d], b->length=[%d], metadata=[%d]\n", remain, b->length, APR_BUCKET_IS_METADATA(b));
564:       /* b を remain バイト分と b->length-remain バイト分に分割 */
565:       apr_bucket_split(b, remain);
566:     }
567: 
568:     /* remain バイト分を bb1 から削除 */
569:     APR_BUCKET_REMOVE(b);
570:     /* remain バイト分をファイルに書き出し */
571:     apr_bucket_read(b, &buf, &len, APR_BLOCK_READ);
572: if(0)fwrite(buf, len, 1, stdout);
573:     apr_socket_send(s, buf, &len);
574:     
575:     /* 書き出した分をカウント */
576:     ret += len;
577:     remain -= len;
578: 
579: if(0)printf("remain=[%d]\n", remain);
580:   }
581:   
582:   return ret;
583: }
Copyright (C) KAKU PROJECT (2009)KAKU PROJECT (2009)