Thu Feb 5 16:25:57 2009

Asterisk developer's documentation


app_test.c File Reference

Applications to test connection and produce report in text file. More...

#include "asterisk.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "asterisk/channel.h"
#include "asterisk/options.h"
#include "asterisk/module.h"
#include "asterisk/logger.h"
#include "asterisk/lock.h"
#include "asterisk/app.h"
#include "asterisk/pbx.h"
#include "asterisk/utils.h"

Go to the source code of this file.

Functions

static void __reg_module (void)
static void __unreg_module (void)
static int load_module (void)
static int measurenoise (struct ast_channel *chan, int ms, char *who)
static int sendnoise (struct ast_channel *chan, int ms)
static int testclient_exec (struct ast_channel *chan, void *data)
static int testserver_exec (struct ast_channel *chan, void *data)
static int unload_module (void)

Variables

static struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_DEFAULT | AST_MODFLAG_BUILDSUM, .description = "Interface Test Application" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = "f450f61f60e761b3aa089ebed76ca8a5" , .load = load_module, .unload = unload_module, }
static const struct ast_module_infoast_module_info = &__mod_info
static char * testc_app = "TestClient"
static char * testc_descrip
static char * testc_synopsis = "Execute Interface Test Client"
static char * tests_app = "TestServer"
static char * tests_descrip
static char * tests_synopsis = "Execute Interface Test Server"


Detailed Description

Applications to test connection and produce report in text file.

Author:
Mark Spencer <markster@digium.com>

Russell Bryant <russelb@clemson.edu>

Definition in file app_test.c.


Function Documentation

static void __reg_module ( void   )  [static]

Definition at line 512 of file app_test.c.

static void __unreg_module ( void   )  [static]

Definition at line 512 of file app_test.c.

static int load_module ( void   )  [static]

Definition at line 502 of file app_test.c.

References ast_register_application(), testclient_exec(), and testserver_exec().

00503 {
00504    int res;
00505 
00506    res = ast_register_application(testc_app, testclient_exec, testc_synopsis, testc_descrip);
00507    res |= ast_register_application(tests_app, testserver_exec, tests_synopsis, tests_descrip);
00508 
00509    return res;
00510 }

static int measurenoise ( struct ast_channel chan,
int  ms,
char *  who 
) [static]

Definition at line 64 of file app_test.c.

References AST_FORMAT_SLINEAR, AST_FRAME_VOICE, ast_frfree, ast_log(), ast_read(), ast_set_read_format(), ast_tvdiff_ms(), ast_tvnow(), ast_waitfor(), f, LOG_DEBUG, LOG_NOTICE, and ast_channel::readformat.

Referenced by testclient_exec(), and testserver_exec().

00065 {
00066    int res=0;
00067    int mssofar;
00068    int noise=0;
00069    int samples=0;
00070    int x;
00071    short *foo;
00072    struct timeval start;
00073    struct ast_frame *f;
00074    int rformat;
00075    rformat = chan->readformat;
00076    if (ast_set_read_format(chan, AST_FORMAT_SLINEAR)) {
00077       ast_log(LOG_NOTICE, "Unable to set to linear mode!\n");
00078       return -1;
00079    }
00080    start = ast_tvnow();
00081    for(;;) {
00082       mssofar = ast_tvdiff_ms(ast_tvnow(), start);
00083       if (mssofar > ms)
00084          break;
00085       res = ast_waitfor(chan, ms - mssofar);
00086       if (res < 1)
00087          break;
00088       f = ast_read(chan);
00089       if (!f) {
00090          res = -1;
00091          break;
00092       }
00093       if ((f->frametype == AST_FRAME_VOICE) && (f->subclass == AST_FORMAT_SLINEAR)) {
00094          foo = (short *)f->data;
00095          for (x=0;x<f->samples;x++) {
00096             noise += abs(foo[x]);
00097             samples++;
00098          }
00099       }
00100       ast_frfree(f);
00101    }
00102 
00103    if (rformat) {
00104       if (ast_set_read_format(chan, rformat)) {
00105          ast_log(LOG_NOTICE, "Unable to restore original format!\n");
00106          return -1;
00107       }
00108    }
00109    if (res < 0)
00110       return res;
00111    if (!samples) {
00112       ast_log(LOG_NOTICE, "No samples were received from the other side!\n");
00113       return -1;
00114    }
00115    ast_log(LOG_DEBUG, "%s: Noise: %d, samples: %d, avg: %d\n", who, noise, samples, noise / samples);
00116    return (noise / samples);
00117 }

static int sendnoise ( struct ast_channel chan,
int  ms 
) [static]

Definition at line 119 of file app_test.c.

References ast_tonepair_start(), ast_tonepair_stop(), and ast_waitfordigit().

Referenced by testclient_exec(), and testserver_exec().

00120 {
00121    int res;
00122    res = ast_tonepair_start(chan, 1537, 2195, ms, 8192);
00123    if (!res) {
00124       res = ast_waitfordigit(chan, ms);
00125       ast_tonepair_stop(chan);
00126    }
00127    return res; 
00128 }

static int testclient_exec ( struct ast_channel chan,
void *  data 
) [static]

Definition at line 130 of file app_test.c.

References ast_channel::_state, ast_answer(), ast_app_getdata(), ast_config_AST_LOG_DIR, ast_dtmf_stream(), ast_log(), ast_module_user_add, ast_module_user_remove, ast_safe_sleep(), AST_STATE_UP, ast_strlen_zero(), ast_waitfordigit(), ast_module_user::chan, f, LOG_DEBUG, LOG_NOTICE, LOG_WARNING, measurenoise(), ast_channel::name, option_debug, and sendnoise().

Referenced by load_module().

00131 {
00132    struct ast_module_user *u;
00133    int res = 0;
00134    char *testid=data;
00135    char fn[80];
00136    char serverver[80];
00137    FILE *f;
00138    
00139    /* Check for test id */
00140    if (ast_strlen_zero(testid)) {
00141       ast_log(LOG_WARNING, "TestClient requires an argument - the test id\n");
00142       return -1;
00143    }
00144    
00145    u = ast_module_user_add(chan);
00146 
00147    if (chan->_state != AST_STATE_UP)
00148       res = ast_answer(chan);
00149    
00150    /* Wait a few just to be sure things get started */
00151    res = ast_safe_sleep(chan, 3000);
00152    /* Transmit client version */
00153    if (!res)
00154       res = ast_dtmf_stream(chan, NULL, "8378*1#", 0);
00155    if (option_debug)
00156       ast_log(LOG_DEBUG, "Transmit client version\n");
00157    
00158    /* Read server version */
00159    if (option_debug)
00160       ast_log(LOG_DEBUG, "Read server version\n");
00161    if (!res) 
00162       res = ast_app_getdata(chan, NULL, serverver, sizeof(serverver) - 1, 0);
00163    if (res > 0)
00164       res = 0;
00165    if (option_debug)
00166       ast_log(LOG_DEBUG, "server version: %s\n", serverver);
00167       
00168    if (res > 0)
00169       res = 0;
00170 
00171    if (!res)
00172       res = ast_safe_sleep(chan, 1000);
00173    /* Send test id */
00174    if (!res) 
00175       res = ast_dtmf_stream(chan, NULL, testid, 0);      
00176    if (!res) 
00177       res = ast_dtmf_stream(chan, NULL, "#", 0);      
00178    if (option_debug)
00179       ast_log(LOG_DEBUG, "send test identifier: %s\n", testid);
00180 
00181    if ((res >=0) && (!ast_strlen_zero(testid))) {
00182       /* Make the directory to hold the test results in case it's not there */
00183       snprintf(fn, sizeof(fn), "%s/testresults", ast_config_AST_LOG_DIR);
00184       mkdir(fn, 0777);
00185       snprintf(fn, sizeof(fn), "%s/testresults/%s-client.txt", ast_config_AST_LOG_DIR, testid);
00186       if ((f = fopen(fn, "w+"))) {
00187          setlinebuf(f);
00188          fprintf(f, "CLIENTCHAN:    %s\n", chan->name);
00189          fprintf(f, "CLIENTTEST ID: %s\n", testid);
00190          fprintf(f, "ANSWER:        PASS\n");
00191          res = 0;
00192          
00193          if (!res) {
00194             /* Step 1: Wait for "1" */
00195             if (option_debug)
00196                ast_log(LOG_DEBUG, "TestClient: 2.  Wait DTMF 1\n");
00197             res = ast_waitfordigit(chan, 3000);
00198             fprintf(f, "WAIT DTMF 1:   %s\n", (res != '1') ? "FAIL" : "PASS");
00199             if (res == '1')
00200                res = 0;
00201             else
00202                res = -1;
00203          }
00204          if (!res)
00205             res = ast_safe_sleep(chan, 1000);
00206          if (!res) {
00207             /* Step 2: Send "2" */
00208             if (option_debug)
00209                ast_log(LOG_DEBUG, "TestClient: 2.  Send DTMF 2\n");
00210             res = ast_dtmf_stream(chan, NULL, "2", 0);
00211             fprintf(f, "SEND DTMF 2:   %s\n", (res < 0) ? "FAIL" : "PASS");
00212             if (res > 0)
00213                res = 0;
00214          }
00215          if (!res) {
00216             /* Step 3: Wait one second */
00217             if (option_debug)
00218                ast_log(LOG_DEBUG, "TestClient: 3.  Wait one second\n");
00219             res = ast_safe_sleep(chan, 1000);
00220             fprintf(f, "WAIT 1 SEC:    %s\n", (res < 0) ? "FAIL" : "PASS");
00221             if (res > 0)
00222                res = 0;
00223          }        
00224          if (!res) {
00225             /* Step 4: Measure noise */
00226             if (option_debug)
00227                ast_log(LOG_DEBUG, "TestClient: 4.  Measure noise\n");
00228             res = measurenoise(chan, 5000, "TestClient");
00229             fprintf(f, "MEASURENOISE:  %s (%d)\n", (res < 0) ? "FAIL" : "PASS", res);
00230             if (res > 0)
00231                res = 0;
00232          }
00233          if (!res) {
00234             /* Step 5: Wait for "4" */
00235             if (option_debug)
00236                ast_log(LOG_DEBUG, "TestClient: 5.  Wait DTMF 4\n");
00237             res = ast_waitfordigit(chan, 3000);
00238             fprintf(f, "WAIT DTMF 4:   %s\n", (res != '4') ? "FAIL" : "PASS");
00239             if (res == '4')
00240                res = 0;
00241             else
00242                res = -1;
00243          }
00244          if (!res) {
00245             /* Step 6: Transmit tone noise */
00246             if (option_debug)
00247                ast_log(LOG_DEBUG, "TestClient: 6.  Transmit tone\n");
00248             res = sendnoise(chan, 6000);
00249             fprintf(f, "SENDTONE:      %s\n", (res < 0) ? "FAIL" : "PASS");
00250          }
00251          if (!res || (res == '5')) {
00252             /* Step 7: Wait for "5" */
00253             if (option_debug)
00254                ast_log(LOG_DEBUG, "TestClient: 7.  Wait DTMF 5\n");
00255             if (!res)
00256                res = ast_waitfordigit(chan, 3000);
00257             fprintf(f, "WAIT DTMF 5:   %s\n", (res != '5') ? "FAIL" : "PASS");
00258             if (res == '5')
00259                res = 0;
00260             else
00261                res = -1;
00262          }
00263          if (!res) {
00264             /* Step 8: Wait one second */
00265             if (option_debug)
00266                ast_log(LOG_DEBUG, "TestClient: 8.  Wait one second\n");
00267             res = ast_safe_sleep(chan, 1000);
00268             fprintf(f, "WAIT 1 SEC:    %s\n", (res < 0) ? "FAIL" : "PASS");
00269             if (res > 0)
00270                res = 0;
00271          }
00272          if (!res) {
00273             /* Step 9: Measure noise */
00274             if (option_debug)
00275                ast_log(LOG_DEBUG, "TestClient: 6.  Measure tone\n");
00276             res = measurenoise(chan, 4000, "TestClient");
00277             fprintf(f, "MEASURETONE:   %s (%d)\n", (res < 0) ? "FAIL" : "PASS", res);
00278             if (res > 0)
00279                res = 0;
00280          }
00281          if (!res) {
00282             /* Step 10: Send "7" */
00283             if (option_debug)
00284                ast_log(LOG_DEBUG, "TestClient: 7.  Send DTMF 7\n");
00285             res = ast_dtmf_stream(chan, NULL, "7", 0);
00286             fprintf(f, "SEND DTMF 7:   %s\n", (res < 0) ? "FAIL" : "PASS");
00287             if (res > 0)
00288                res =0;
00289          }
00290          if (!res) {
00291             /* Step 11: Wait for "8" */
00292             if (option_debug)
00293                ast_log(LOG_DEBUG, "TestClient: 11.  Wait DTMF 8\n");
00294             res = ast_waitfordigit(chan, 3000);
00295             fprintf(f, "WAIT DTMF 8:   %s\n", (res != '8') ? "FAIL" : "PASS");
00296             if (res == '8')
00297                res = 0;
00298             else
00299                res = -1;
00300          }
00301          if (option_debug && !res ) {
00302             /* Step 12: Hangup! */
00303             ast_log(LOG_DEBUG, "TestClient: 12.  Hangup\n");
00304          }
00305 
00306          if (option_debug)
00307             ast_log(LOG_DEBUG, "-- TEST COMPLETE--\n");
00308          fprintf(f, "-- END TEST--\n");
00309          fclose(f);
00310          res = -1;
00311       } else
00312          res = -1;
00313    } else {
00314       ast_log(LOG_NOTICE, "Did not read a test ID on '%s'\n", chan->name);
00315       res = -1;
00316    }
00317    ast_module_user_remove(u);
00318    return res;
00319 }

static int testserver_exec ( struct ast_channel chan,
void *  data 
) [static]

Definition at line 321 of file app_test.c.

References ast_channel::_state, ast_answer(), ast_app_getdata(), ast_config_AST_LOG_DIR, ast_dtmf_stream(), ast_log(), ast_module_user_add, ast_module_user_remove, ast_safe_sleep(), AST_STATE_UP, ast_strlen_zero(), ast_waitfordigit(), ast_module_user::chan, f, LOG_DEBUG, LOG_NOTICE, measurenoise(), ast_channel::name, option_debug, and sendnoise().

Referenced by load_module().

00322 {
00323    struct ast_module_user *u;
00324    int res = 0;
00325    char testid[80]="";
00326    char fn[80];
00327    FILE *f;
00328    u = ast_module_user_add(chan);
00329    if (chan->_state != AST_STATE_UP)
00330       res = ast_answer(chan);
00331    /* Read version */
00332    if (option_debug)
00333       ast_log(LOG_DEBUG, "Read client version\n");
00334    if (!res) 
00335       res = ast_app_getdata(chan, NULL, testid, sizeof(testid) - 1, 0);
00336    if (res > 0)
00337       res = 0;
00338    if (option_debug) {
00339       ast_log(LOG_DEBUG, "client version: %s\n", testid);
00340       ast_log(LOG_DEBUG, "Transmit server version\n");
00341    }
00342    res = ast_safe_sleep(chan, 1000);
00343    if (!res)
00344       res = ast_dtmf_stream(chan, NULL, "8378*1#", 0);
00345    if (res > 0)
00346       res = 0;
00347 
00348    if (!res) 
00349       res = ast_app_getdata(chan, NULL, testid, sizeof(testid) - 1, 0);    
00350    if (option_debug) 
00351       ast_log(LOG_DEBUG, "read test identifier: %s\n", testid);
00352    /* Check for sneakyness */
00353    if (strchr(testid, '/'))
00354       res = -1;
00355    if ((res >=0) && (!ast_strlen_zero(testid))) {
00356       /* Got a Test ID!  Whoo hoo! */
00357       /* Make the directory to hold the test results in case it's not there */
00358       snprintf(fn, sizeof(fn), "%s/testresults", ast_config_AST_LOG_DIR);
00359       mkdir(fn, 0777);
00360       snprintf(fn, sizeof(fn), "%s/testresults/%s-server.txt", ast_config_AST_LOG_DIR, testid);
00361       if ((f = fopen(fn, "w+"))) {
00362          setlinebuf(f);
00363          fprintf(f, "SERVERCHAN:    %s\n", chan->name);
00364          fprintf(f, "SERVERTEST ID: %s\n", testid);
00365          fprintf(f, "ANSWER:        PASS\n");
00366          ast_log(LOG_DEBUG, "Processing Test ID '%s'\n", testid);
00367          res = ast_safe_sleep(chan, 1000);
00368          if (!res) {
00369             /* Step 1: Send "1" */
00370             if (option_debug) 
00371                ast_log(LOG_DEBUG, "TestServer: 1.  Send DTMF 1\n");
00372             res = ast_dtmf_stream(chan, NULL, "1", 0);
00373             fprintf(f, "SEND DTMF 1:   %s\n", (res < 0) ? "FAIL" : "PASS");
00374             if (res > 0)
00375                res = 0;
00376          }
00377          if (!res) {
00378             /* Step 2: Wait for "2" */
00379             if (option_debug) 
00380                ast_log(LOG_DEBUG, "TestServer: 2.  Wait DTMF 2\n");
00381             res = ast_waitfordigit(chan, 3000);
00382             fprintf(f, "WAIT DTMF 2:   %s\n", (res != '2') ? "FAIL" : "PASS");
00383             if (res == '2')
00384                res = 0;
00385             else
00386                res = -1;
00387          }
00388          if (!res) {
00389             /* Step 3: Measure noise */
00390             if (option_debug) 
00391                ast_log(LOG_DEBUG, "TestServer: 3.  Measure noise\n");
00392             res = measurenoise(chan, 6000, "TestServer");
00393             fprintf(f, "MEASURENOISE:  %s (%d)\n", (res < 0) ? "FAIL" : "PASS", res);
00394             if (res > 0)
00395                res = 0;
00396          }
00397          if (!res) {
00398             /* Step 4: Send "4" */
00399             if (option_debug) 
00400                ast_log(LOG_DEBUG, "TestServer: 4.  Send DTMF 4\n");
00401             res = ast_dtmf_stream(chan, NULL, "4", 0);
00402             fprintf(f, "SEND DTMF 4:   %s\n", (res < 0) ? "FAIL" : "PASS");
00403             if (res > 0)
00404                res = 0;
00405          }
00406       
00407          if (!res) {
00408             /* Step 5: Wait one second */
00409             if (option_debug) 
00410                ast_log(LOG_DEBUG, "TestServer: 5.  Wait one second\n");
00411             res = ast_safe_sleep(chan, 1000);
00412             fprintf(f, "WAIT 1 SEC:    %s\n", (res < 0) ? "FAIL" : "PASS");
00413             if (res > 0)
00414                res = 0;
00415          }
00416       
00417          if (!res) {
00418             /* Step 6: Measure noise */
00419             if (option_debug) 
00420                ast_log(LOG_DEBUG, "TestServer: 6.  Measure tone\n");
00421             res = measurenoise(chan, 4000, "TestServer");
00422             fprintf(f, "MEASURETONE:   %s (%d)\n", (res < 0) ? "FAIL" : "PASS", res);
00423             if (res > 0)
00424                res = 0;
00425          }
00426 
00427          if (!res) {
00428             /* Step 7: Send "5" */
00429             if (option_debug) 
00430                ast_log(LOG_DEBUG, "TestServer: 7.  Send DTMF 5\n");
00431             res = ast_dtmf_stream(chan, NULL, "5", 0);
00432             fprintf(f, "SEND DTMF 5:   %s\n", (res < 0) ? "FAIL" : "PASS");
00433             if (res > 0)
00434                res = 0;
00435          }
00436 
00437          if (!res) {
00438             /* Step 8: Transmit tone noise */
00439             if (option_debug) 
00440                ast_log(LOG_DEBUG, "TestServer: 8.  Transmit tone\n");
00441             res = sendnoise(chan, 6000);
00442             fprintf(f, "SENDTONE:      %s\n", (res < 0) ? "FAIL" : "PASS");
00443          }
00444       
00445          if (!res || (res == '7')) {
00446             /* Step 9: Wait for "7" */
00447             if (option_debug) 
00448                ast_log(LOG_DEBUG, "TestServer: 9.  Wait DTMF 7\n");
00449             if (!res)
00450                res = ast_waitfordigit(chan, 3000);
00451             fprintf(f, "WAIT DTMF 7:   %s\n", (res != '7') ? "FAIL" : "PASS");
00452             if (res == '7')
00453                res = 0;
00454             else
00455                res = -1;
00456          }
00457          if (!res)
00458             res = ast_safe_sleep(chan, 1000);
00459          if (!res) {
00460             /* Step 10: Send "8" */
00461             if (option_debug) 
00462                ast_log(LOG_DEBUG, "TestServer: 10.  Send DTMF 8\n");
00463             res = ast_dtmf_stream(chan, NULL, "8", 0);
00464             fprintf(f, "SEND DTMF 8:   %s\n", (res < 0) ? "FAIL" : "PASS");
00465             if (res > 0)
00466                res = 0;
00467          }
00468          if (!res) {
00469             /* Step 11: Wait for hangup to arrive! */
00470             if (option_debug) 
00471                ast_log(LOG_DEBUG, "TestServer: 11.  Waiting for hangup\n");
00472             res = ast_safe_sleep(chan, 10000);
00473             fprintf(f, "WAIT HANGUP:   %s\n", (res < 0) ? "PASS" : "FAIL");
00474          }
00475 
00476          ast_log(LOG_NOTICE, "-- TEST COMPLETE--\n");
00477          fprintf(f, "-- END TEST--\n");
00478          fclose(f);
00479          res = -1;
00480       } else
00481          res = -1;
00482    } else {
00483       ast_log(LOG_NOTICE, "Did not read a test ID on '%s'\n", chan->name);
00484       res = -1;
00485    }
00486    ast_module_user_remove(u);
00487    return res;
00488 }

static int unload_module ( void   )  [static]

Definition at line 490 of file app_test.c.

References ast_module_user_hangup_all, and ast_unregister_application().

00491 {
00492    int res;
00493 
00494    res = ast_unregister_application(testc_app);
00495    res |= ast_unregister_application(tests_app);
00496 
00497    ast_module_user_hangup_all();
00498 
00499    return res; 
00500 }


Variable Documentation

struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_DEFAULT | AST_MODFLAG_BUILDSUM, .description = "Interface Test Application" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = "f450f61f60e761b3aa089ebed76ca8a5" , .load = load_module, .unload = unload_module, } [static]

Definition at line 512 of file app_test.c.

const struct ast_module_info* ast_module_info = &__mod_info [static]

Definition at line 512 of file app_test.c.

char* testc_app = "TestClient" [static]

Definition at line 61 of file app_test.c.

char* testc_descrip [static]

Initial value:

 
    "TestClient(testid): Executes test client with given testid.\n"
    "Results stored in /var/log/asterisk/testreports/<testid>-client.txt"

Definition at line 57 of file app_test.c.

char* testc_synopsis = "Execute Interface Test Client" [static]

Definition at line 62 of file app_test.c.

char* tests_app = "TestServer" [static]

Definition at line 54 of file app_test.c.

char* tests_descrip [static]

Initial value:

 
    "TestServer(): Perform test server function and write call report.\n"
    "Results stored in /var/log/asterisk/testreports/<testid>-server.txt"

Definition at line 51 of file app_test.c.

char* tests_synopsis = "Execute Interface Test Server" [static]

Definition at line 55 of file app_test.c.


Generated on Thu Feb 5 16:25:57 2009 for Asterisk - the Open Source PBX by  doxygen 1.4.7