2010-09-28 18:15:59 +02:00
/*-
2010-11-17 15:27:11 +01:00
* Public platform independent Near Field Communication ( NFC ) library examples
2012-05-29 02:33:22 +02:00
*
2012-10-21 16:09:16 +02:00
* Copyright ( C ) 2010 Roel Verdult
* Copyright ( C ) 2011 Romain Tartière
* Copyright ( C ) 2010 , 2011 Romuald Conty
2012-05-29 02:33:22 +02:00
*
2010-11-17 15:27:11 +01:00
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions are met :
* 1 ) Redistributions of source code must retain the above copyright notice ,
2012-05-29 02:33:22 +02:00
* this list of conditions and the following disclaimer .
2010-11-17 15:27:11 +01:00
* 2 ) Redistributions in binary form must reproduce the above copyright
* notice , this list of conditions and the following disclaimer in the
* documentation and / or other materials provided with the distribution .
2010-09-28 18:15:59 +02:00
*
2010-11-17 15:27:11 +01:00
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS " AS IS "
* AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR
* CONSEQUENTIAL DAMAGES ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS
* INTERRUPTION ) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN
* CONTRACT , STRICT LIABILITY , OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE )
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE , EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE .
2012-05-29 02:33:22 +02:00
*
2010-11-17 15:27:11 +01:00
* Note that this license only applies on the examples , NFC library itself is under LGPL
2010-09-28 18:15:59 +02:00
*
*/
/**
2010-10-08 17:37:21 +02:00
* @ file nfc - emulate - forum - tag4 . c
2012-05-15 00:13:19 +02:00
* @ brief Emulates a NFC Forum Tag Type 4 v2 .0 ( or v1 .0 ) with a NDEF message
2010-09-28 18:15:59 +02:00
*/
2011-06-30 16:24:47 +02:00
/*
* This implementation was written based on information provided by the
* following documents :
*
* NFC Forum Type 4 Tag Operation
* Technical Specification
* NFCForum - TS - Type - 4 - Tag_1 .0 - 2007 - 03 - 13
2012-05-15 00:13:19 +02:00
* NFCForum - TS - Type - 4 - Tag_2 .0 - 2010 - 11 - 18
2011-06-30 16:24:47 +02:00
*/
2010-10-06 23:04:52 +02:00
// Notes & differences with nfc-emulate-tag:
// - This example only works with PN532 because it relies on
// its internal handling of ISO14443-4 specificities.
// - Thanks to this internal handling & injection of WTX frames,
// this example works on readers very strict on timing
2010-09-28 18:15:59 +02:00
# ifdef HAVE_CONFIG_H
# include "config.h"
# endif // HAVE_CONFIG_H
2011-03-11 20:01:28 +01:00
# include <sys/types.h>
# include <sys/stat.h>
2011-03-09 19:17:05 +01:00
# include <errno.h>
2011-03-09 14:31:12 +01:00
# include <signal.h>
2010-09-28 18:15:59 +02:00
# include <stdio.h>
# include <stdlib.h>
# include <stddef.h>
# include <stdint.h>
# include <string.h>
# include <nfc/nfc.h>
2011-03-13 15:31:06 +01:00
# include <nfc/nfc-emulation.h>
2010-09-28 18:15:59 +02:00
# include "nfc-utils.h"
2011-11-23 16:55:40 +01:00
static nfc_device * pnd ;
2010-09-28 18:15:59 +02:00
static bool quiet_output = false ;
2012-05-15 00:13:19 +02:00
// Version of the emulated type4 tag:
static int type4v = 2 ;
2010-09-28 18:15:59 +02:00
# define SYMBOL_PARAM_fISO14443_4_PICC 0x20
2011-03-09 19:17:05 +01:00
typedef enum { NONE , CC_FILE , NDEF_FILE } file ;
2011-03-13 15:40:36 +01:00
struct nfcforum_tag4_ndef_data {
2011-03-09 19:17:05 +01:00
uint8_t * ndef_file ;
size_t ndef_file_len ;
2011-03-13 15:40:36 +01:00
} ;
struct nfcforum_tag4_state_machine_data {
2011-03-09 19:17:05 +01:00
file current_file ;
} ;
uint8_t nfcforum_capability_container [ ] = {
0x00 , 0x0F , /* CCLEN 15 bytes */
2012-05-15 00:13:19 +02:00
0x20 , /* Mapping version 2.0, use option -1 to force v1.0 */
2011-04-22 19:32:07 +02:00
0x00 , 0x54 , /* MLe Maximum R-ADPU data size */
2012-05-29 02:33:22 +02:00
// Notes:
2011-04-22 19:32:07 +02:00
// - I (Romuald) don't know why Nokia 6212 Classic refuses the NDEF message if MLe is more than 0xFD (any suggests are welcome);
// - ARYGON devices doesn't support extended frame sending, consequently these devices can't sent more than 0xFE bytes as APDU, so 0xFB APDU data bytes.
2012-05-13 15:10:15 +02:00
// - I (Romuald) don't know why ARYGON device doesn't ACK when MLe > 0x54 (ARYGON frame length = 0xC2 (192 bytes))
2011-04-22 19:32:07 +02:00
0x00 , 0xFF , /* MLc Maximum C-ADPU data size */
2011-03-09 19:17:05 +01:00
0x04 , /* T field of the NDEF File-Control TLV */
0x06 , /* L field of the NDEF File-Control TLV */
2012-05-29 17:52:51 +02:00
/* V field of the NDEF File-Control TLV */
2011-03-09 19:17:05 +01:00
0xE1 , 0x04 , /* File identifier */
2011-03-12 16:01:04 +01:00
0xFF , 0xFE , /* Maximum NDEF Size */
2011-03-09 19:17:05 +01:00
0x00 , /* NDEF file read access condition */
2011-03-12 16:01:04 +01:00
0x00 , /* NDEF file write access condition */
2011-03-09 19:17:05 +01:00
} ;
/* C-ADPU offsets */
# define CLA 0
# define INS 1
# define P1 2
# define P2 3
# define LC 4
# define DATA 5
# define ISO144434A_RATS 0xE0
2012-05-13 14:57:49 +02:00
static int
2012-05-29 17:54:36 +02:00
nfcforum_tag4_io ( struct nfc_emulator * emulator , const uint8_t * data_in , const size_t data_in_len , uint8_t * data_out , const size_t data_out_len )
2011-03-09 19:17:05 +01:00
{
int res = 0 ;
2011-03-13 15:40:36 +01:00
struct nfcforum_tag4_ndef_data * ndef_data = ( struct nfcforum_tag4_ndef_data * ) ( emulator - > user_data ) ;
struct nfcforum_tag4_state_machine_data * state_machine_data = ( struct nfcforum_tag4_state_machine_data * ) ( emulator - > state_machine - > data ) ;
2011-03-09 19:17:05 +01:00
2011-06-27 11:14:19 +02:00
if ( data_in_len = = 0 ) {
// No input data, nothing to do
return res ;
}
2011-03-09 19:17:05 +01:00
// Show transmitted command
if ( ! quiet_output ) {
2012-05-29 17:54:36 +02:00
printf ( " In: " ) ;
print_hex ( data_in , data_in_len ) ;
2011-03-09 19:17:05 +01:00
}
2012-05-29 17:54:36 +02:00
if ( data_in_len > = 4 ) {
2011-03-09 19:17:05 +01:00
if ( data_in [ CLA ] ! = 0x00 )
return - ENOTSUP ;
# define ISO7816_SELECT 0xA4
# define ISO7816_READ_BINARY 0xB0
# define ISO7816_UPDATE_BINARY 0xD6
2012-05-29 17:54:36 +02:00
switch ( data_in [ INS ] ) {
2012-05-29 17:52:51 +02:00
case ISO7816_SELECT :
switch ( data_in [ P1 ] ) {
case 0x00 : /* Select by ID */
if ( ( data_in [ P2 ] | 0x0C ) ! = 0x0C )
return - ENOTSUP ;
const uint8_t ndef_capability_container [ ] = { 0xE1 , 0x03 } ;
const uint8_t ndef_file [ ] = { 0xE1 , 0x04 } ;
2012-05-29 17:54:36 +02:00
if ( ( data_in [ LC ] = = sizeof ( ndef_capability_container ) ) & & ( 0 = = memcmp ( ndef_capability_container , data_in + DATA , data_in [ LC ] ) ) ) {
memcpy ( data_out , " \x90 \x00 " , res = 2 ) ;
2012-05-29 17:52:51 +02:00
state_machine_data - > current_file = CC_FILE ;
2012-05-29 17:54:36 +02:00
} else if ( ( data_in [ LC ] = = sizeof ( ndef_file ) ) & & ( 0 = = memcmp ( ndef_file , data_in + DATA , data_in [ LC ] ) ) ) {
memcpy ( data_out , " \x90 \x00 " , res = 2 ) ;
2012-05-29 17:52:51 +02:00
state_machine_data - > current_file = NDEF_FILE ;
} else {
2012-05-29 17:54:36 +02:00
memcpy ( data_out , " \x6a \x00 " , res = 2 ) ;
2012-05-29 17:52:51 +02:00
state_machine_data - > current_file = NONE ;
}
break ;
case 0x04 : /* Select by name */
if ( data_in [ P2 ] ! = 0x00 )
return - ENOTSUP ;
const uint8_t ndef_tag_application_name_v1 [ ] = { 0xD2 , 0x76 , 0x00 , 0x00 , 0x85 , 0x01 , 0x00 } ;
const uint8_t ndef_tag_application_name_v2 [ ] = { 0xD2 , 0x76 , 0x00 , 0x00 , 0x85 , 0x01 , 0x01 } ;
2012-05-29 17:54:36 +02:00
if ( ( type4v = = 1 ) & & ( data_in [ LC ] = = sizeof ( ndef_tag_application_name_v1 ) ) & & ( 0 = = memcmp ( ndef_tag_application_name_v1 , data_in + DATA , data_in [ LC ] ) ) )
memcpy ( data_out , " \x90 \x00 " , res = 2 ) ;
else if ( ( type4v = = 2 ) & & ( data_in [ LC ] = = sizeof ( ndef_tag_application_name_v2 ) ) & & ( 0 = = memcmp ( ndef_tag_application_name_v2 , data_in + DATA , data_in [ LC ] ) ) )
memcpy ( data_out , " \x90 \x00 " , res = 2 ) ;
2012-05-29 17:52:51 +02:00
else
2012-05-29 17:54:36 +02:00
memcpy ( data_out , " \x6a \x82 " , res = 2 ) ;
2012-05-29 17:52:51 +02:00
break ;
default :
return - ENOTSUP ;
2011-03-09 19:17:05 +01:00
}
break ;
2012-05-29 17:52:51 +02:00
case ISO7816_READ_BINARY :
if ( ( size_t ) ( data_in [ LC ] + 2 ) > data_out_len ) {
return - ENOSPC ;
}
switch ( state_machine_data - > current_file ) {
case NONE :
2012-05-29 17:54:36 +02:00
memcpy ( data_out , " \x6a \x82 " , res = 2 ) ;
2012-05-29 17:52:51 +02:00
break ;
case CC_FILE :
2012-05-29 17:54:36 +02:00
memcpy ( data_out , nfcforum_capability_container + ( data_in [ P1 ] < < 8 ) + data_in [ P2 ] , data_in [ LC ] ) ;
memcpy ( data_out + data_in [ LC ] , " \x90 \x00 " , 2 ) ;
2012-05-29 17:52:51 +02:00
res = data_in [ LC ] + 2 ;
break ;
case NDEF_FILE :
2012-05-29 17:54:36 +02:00
memcpy ( data_out , ndef_data - > ndef_file + ( data_in [ P1 ] < < 8 ) + data_in [ P2 ] , data_in [ LC ] ) ;
memcpy ( data_out + data_in [ LC ] , " \x90 \x00 " , 2 ) ;
2012-05-29 17:52:51 +02:00
res = data_in [ LC ] + 2 ;
break ;
}
2011-03-09 19:17:05 +01:00
break ;
2012-05-29 17:52:51 +02:00
case ISO7816_UPDATE_BINARY :
2012-05-29 17:54:36 +02:00
memcpy ( ndef_data - > ndef_file + ( data_in [ P1 ] < < 8 ) + data_in [ P2 ] , data_in + DATA , data_in [ LC ] ) ;
2012-05-29 17:52:51 +02:00
if ( ( data_in [ P1 ] < < 8 ) + data_in [ P2 ] = = 0 ) {
ndef_data - > ndef_file_len = ( ndef_data - > ndef_file [ 0 ] < < 8 ) + ndef_data - > ndef_file [ 1 ] + 2 ;
}
2012-05-29 17:54:36 +02:00
memcpy ( data_out , " \x90 \x00 " , res = 2 ) ;
2011-03-09 19:17:05 +01:00
break ;
2012-05-29 17:52:51 +02:00
default : // Unknown
if ( ! quiet_output ) {
printf ( " Unknown frame, emulated target abort. \n " ) ;
}
res = - ENOTSUP ;
2011-03-09 19:17:05 +01:00
}
} else {
res = - ENOTSUP ;
}
// Show transmitted command
if ( ! quiet_output ) {
2011-06-27 11:14:19 +02:00
if ( res < 0 ) {
2012-05-29 17:54:36 +02:00
ERR ( " %s (%d) " , strerror ( - res ) , - res ) ;
2011-06-27 11:14:19 +02:00
} else {
2012-05-29 17:54:36 +02:00
printf ( " Out: " ) ;
print_hex ( data_out , res ) ;
2011-06-27 11:14:19 +02:00
}
2011-03-09 19:17:05 +01:00
}
return res ;
}
2012-05-29 17:54:36 +02:00
static void stop_emulation ( int sig )
2011-03-09 14:31:12 +01:00
{
( void ) sig ;
if ( pnd )
2012-05-29 17:54:36 +02:00
nfc_abort_command ( pnd ) ;
2011-03-09 14:31:12 +01:00
else
2012-05-29 17:54:36 +02:00
exit ( EXIT_FAILURE ) ;
2011-03-09 14:31:12 +01:00
}
2012-05-13 14:57:49 +02:00
static size_t
2012-05-29 17:54:36 +02:00
ndef_message_load ( char * filename , struct nfcforum_tag4_ndef_data * tag_data )
2010-09-28 18:15:59 +02:00
{
2011-03-11 20:01:28 +01:00
struct stat sb ;
2012-05-29 17:54:36 +02:00
if ( stat ( filename , & sb ) < 0 )
2011-03-11 20:01:28 +01:00
return 0 ;
2010-09-28 18:15:59 +02:00
2011-03-11 20:01:28 +01:00
/* Check file size */
if ( sb . st_size > 0xFFFF ) {
2012-05-29 17:54:36 +02:00
errx ( EXIT_FAILURE , " file size too large '%s' " , filename ) ;
2010-09-28 18:15:59 +02:00
}
2011-03-11 20:01:28 +01:00
tag_data - > ndef_file_len = sb . st_size + 2 ;
tag_data - > ndef_file [ 0 ] = ( uint8_t ) ( sb . st_size > > 8 ) ;
tag_data - > ndef_file [ 1 ] = ( uint8_t ) ( sb . st_size ) ;
FILE * F ;
2012-05-29 17:54:36 +02:00
if ( ! ( F = fopen ( filename , " r " ) ) )
err ( EXIT_FAILURE , " fopen (%s, \" r \" ) " , filename ) ;
2011-03-11 20:01:28 +01:00
2012-05-29 17:54:36 +02:00
if ( 1 ! = fread ( tag_data - > ndef_file + 2 , sb . st_size , 1 , F ) )
err ( EXIT_FAILURE , " Can't read from %s " , filename ) ;
2011-03-11 20:01:28 +01:00
2012-05-29 17:54:36 +02:00
fclose ( F ) ;
2011-03-11 20:01:28 +01:00
return sb . st_size ;
}
2012-05-13 14:57:49 +02:00
static size_t
2012-05-29 17:54:36 +02:00
ndef_message_save ( char * filename , struct nfcforum_tag4_ndef_data * tag_data )
2011-03-11 20:01:28 +01:00
{
FILE * F ;
2012-05-29 17:54:36 +02:00
if ( ! ( F = fopen ( filename , " w " ) ) )
err ( EXIT_FAILURE , " fopen (%s, w) " , filename ) ;
2011-03-11 20:01:28 +01:00
2012-05-29 17:54:36 +02:00
if ( 1 ! = fwrite ( tag_data - > ndef_file + 2 , tag_data - > ndef_file_len - 2 , 1 , F ) ) {
err ( EXIT_FAILURE , " fwrite (%d) " , ( int ) tag_data - > ndef_file_len - 2 ) ;
2011-03-11 20:01:28 +01:00
}
2012-05-29 17:54:36 +02:00
fclose ( F ) ;
2011-03-11 20:01:28 +01:00
return tag_data - > ndef_file_len - 2 ;
}
2010-10-04 14:46:03 +02:00
2012-05-13 14:57:49 +02:00
static void
2012-05-29 17:54:36 +02:00
usage ( char * progname )
2011-03-12 16:01:04 +01:00
{
2012-05-29 17:54:36 +02:00
fprintf ( stderr , " usage: %s [-1] [infile [outfile]] \n " , progname ) ;
fprintf ( stderr , " -1: force Tag Type 4 v1.0 (default is v2.0) \n " ) ;
2011-03-12 16:01:04 +01:00
}
2011-03-11 20:01:28 +01:00
int
2012-05-29 17:54:36 +02:00
main ( int argc , char * argv [ ] )
2011-03-11 20:01:28 +01:00
{
2012-05-15 00:13:19 +02:00
int options = 0 ;
2011-11-23 16:55:40 +01:00
nfc_target nt = {
2011-02-28 10:47:31 +01:00
. nm = {
. nmt = NMT_ISO14443A ,
. nbr = NBR_UNDEFINED , // Will be updated by nfc_target_init()
} ,
. nti = {
. nai = {
. abtAtqa = { 0x00 , 0x04 } ,
. abtUid = { 0x08 , 0x00 , 0xb0 , 0x0b } ,
. szUidLen = 4 ,
2011-03-09 14:31:12 +01:00
. btSak = 0x20 ,
2011-03-09 19:17:05 +01:00
. abtAts = { 0x75 , 0x33 , 0x92 , 0x03 } , /* Not used by PN532 */
2011-03-09 14:31:12 +01:00
. szAtsLen = 4 ,
2011-02-28 10:47:31 +01:00
} ,
} ,
2010-10-04 14:46:03 +02:00
} ;
2011-03-12 16:01:04 +01:00
uint8_t ndef_file [ 0xfffe ] = {
2011-03-09 19:17:05 +01:00
0x00 , 33 ,
0xd1 , 0x02 , 0x1c , 0x53 , 0x70 , 0x91 , 0x01 , 0x09 , 0x54 , 0x02 ,
0x65 , 0x6e , 0x4c , 0x69 , 0x62 , 0x6e , 0x66 , 0x63 , 0x51 , 0x01 ,
0x0b , 0x55 , 0x03 , 0x6c , 0x69 , 0x62 , 0x6e , 0x66 , 0x63 , 0x2e ,
0x6f , 0x72 , 0x67
} ;
2010-09-28 18:15:59 +02:00
2011-03-13 15:40:36 +01:00
struct nfcforum_tag4_ndef_data nfcforum_tag4_data = {
2011-03-09 19:17:05 +01:00
. ndef_file = ndef_file ,
2011-03-12 16:01:04 +01:00
. ndef_file_len = ndef_file [ 1 ] + 2 ,
2011-03-13 15:40:36 +01:00
} ;
struct nfcforum_tag4_state_machine_data state_machine_data = {
2011-03-09 19:17:05 +01:00
. current_file = NONE ,
} ;
2011-03-13 15:31:06 +01:00
struct nfc_emulation_state_machine state_machine = {
. io = nfcforum_tag4_io ,
2011-03-13 15:40:36 +01:00
. data = & state_machine_data ,
2011-03-13 15:31:06 +01:00
} ;
2011-03-09 19:17:05 +01:00
struct nfc_emulator emulator = {
. target = & nt ,
2011-03-13 15:31:06 +01:00
. state_machine = & state_machine ,
. user_data = & nfcforum_tag4_data ,
2011-03-09 19:17:05 +01:00
} ;
2010-09-30 18:02:02 +02:00
2012-05-29 17:54:36 +02:00
if ( ( argc > ( 1 + options ) ) & & ( 0 = = strcmp ( " -h " , argv [ 1 + options ] ) ) ) {
usage ( argv [ 0 ] ) ;
exit ( EXIT_SUCCESS ) ;
2012-05-15 00:13:19 +02:00
}
2012-05-29 17:54:36 +02:00
if ( ( argc > ( 1 + options ) ) & & ( 0 = = strcmp ( " -1 " , argv [ 1 + options ] ) ) ) {
2012-05-15 00:13:19 +02:00
type4v = 1 ;
nfcforum_capability_container [ 2 ] = 0x10 ;
options + = 1 ;
}
if ( argc > ( 3 + options ) ) {
2012-05-29 17:54:36 +02:00
usage ( argv [ 0 ] ) ;
exit ( EXIT_FAILURE ) ;
2011-03-12 16:01:04 +01:00
}
2011-03-11 20:01:28 +01:00
// If some file is provided load it
2012-05-15 00:13:19 +02:00
if ( argc > = ( 2 + options ) ) {
2012-05-29 17:54:36 +02:00
if ( ! ndef_message_load ( argv [ 1 + options ] , & nfcforum_tag4_data ) ) {
err ( EXIT_FAILURE , " Can't load NDEF file '%s' " , argv [ 1 + options ] ) ;
2011-03-11 20:01:28 +01:00
}
}
2012-05-29 02:33:22 +02:00
2012-12-04 19:29:57 +01:00
nfc_context * context ;
nfc_init ( & context ) ;
2011-03-11 20:01:28 +01:00
// Try to open the NFC reader
2012-12-04 19:29:57 +01:00
pnd = nfc_open ( context , NULL ) ;
2011-03-11 20:01:28 +01:00
if ( pnd = = NULL ) {
2012-01-17 16:21:56 +01:00
ERR ( " Unable to open NFC device " ) ;
2012-05-29 17:54:36 +02:00
exit ( EXIT_FAILURE ) ;
2011-03-11 20:01:28 +01:00
}
2012-05-29 17:54:36 +02:00
signal ( SIGINT , stop_emulation ) ;
2011-03-11 20:01:28 +01:00
2012-05-29 17:54:36 +02:00
printf ( " NFC device: %s opened \n " , nfc_device_get_name ( pnd ) ) ;
printf ( " Emulating NDEF tag now, please touch it with a second NFC device \n " ) ;
2011-03-11 20:01:28 +01:00
2012-05-29 17:54:36 +02:00
if ( 0 ! = nfc_emulate_target ( pnd , & emulator ) ) { // contains already nfc_target_init() call
nfc_perror ( pnd , " nfc_emulate_target " ) ;
2011-05-24 19:10:13 +02:00
}
2010-09-28 18:15:59 +02:00
2012-01-17 15:52:39 +01:00
nfc_close ( pnd ) ;
2011-03-11 20:01:28 +01:00
2012-05-15 00:13:19 +02:00
if ( argc = = ( 3 + options ) ) {
2012-05-29 17:54:36 +02:00
if ( ! ( ndef_message_save ( argv [ 2 + options ] , & nfcforum_tag4_data ) ) ) {
err ( EXIT_FAILURE , " Can't save NDEF file '%s' " , argv [ 2 + options ] ) ;
2011-03-11 20:01:28 +01:00
}
}
2012-05-29 02:33:22 +02:00
2012-12-04 19:29:57 +01:00
nfc_exit ( context ) ;
2012-05-29 17:54:36 +02:00
exit ( EXIT_SUCCESS ) ;
2010-09-28 18:15:59 +02:00
}