diff --git a/.gitignore b/.gitignore index c8b45049b..5b9e36d62 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ node_modules/* *.o *.pm.tdy *.bs +.idea/ diff --git a/Dockerfile_with_OPL b/Dockerfile_with_OPL index f21408c68..fbaebb9ac 100644 --- a/Dockerfile_with_OPL +++ b/Dockerfile_with_OPL @@ -10,12 +10,18 @@ RUN apt-get update \ apt-utils \ git \ gcc \ + npm \ make \ curl \ + nodejs \ dvipng \ + openssl \ libc-dev \ cpanminus \ + libssl-dev \ libgd-perl \ + zlib1g-dev \ + imagemagick \ libdbi-perl \ libjson-perl \ libcgi-pm-perl \ @@ -25,6 +31,10 @@ RUN apt-get update \ libdatetime-perl \ libuuid-tiny-perl \ libtie-ixhash-perl \ + libhttp-async-perl \ + libnet-ssleay-perl \ + libarchive-zip-perl \ + libcrypt-ssleay-perl \ libclass-accessor-perl \ libstring-shellquote-perl \ libextutils-cbuilder-perl \ @@ -32,15 +42,10 @@ RUN apt-get update \ libmath-random-secure-perl \ libdata-structure-util-perl \ liblocale-maketext-lexicon-perl \ - openssl \ - libssl-dev \ - libnet-ssleay-perl \ - libcrypt-ssleay-perl \ - zlib1g-dev \ && apt-get clean \ && rm -fr /var/lib/apt/lists/* /tmp/* -RUN cpanm install Mojo::Base Statistics::R::IO::Rserve Date::Format Future::AsyncAwait Crypt::JWT IO::Socket::SSL \ +RUN cpanm install Mojo::Base Statistics::R::IO::Rserve Date::Format Future::AsyncAwait Crypt::JWT IO::Socket::SSL CGI::Cookie \ && rm -fr ./cpanm /root/.cpanm /tmp/* ENV MOJO_MODE=production @@ -49,16 +54,17 @@ ENV MOJO_MODE=production RUN curl -sOL "https://github.com/openwebwork/webwork-open-problem-library/archive/refs/heads/master.tar.gz" RUN tar -zxf master.tar.gz RUN mkdir webwork-open-problem-library -RUN mv webwork-open-problem-library-master/Contrib/ webwork-open-problem-library/Contrib/ -RUN mv webwork-open-problem-library-master/OpenProblemLibrary/ webwork-open-problem-library/OpenProblemLibrary/ RUN rm master.tar.gz +RUN mv webwork-open-problem-library-master/OpenProblemLibrary/ webwork-open-problem-library/OpenProblemLibrary/ +RUN mv webwork-open-problem-library-master/Contrib/ webwork-open-problem-library/Contrib/ RUN rm -r webwork-open-problem-library-master/ - COPY . . RUN cp render_app.conf.dist render_app.conf +RUN cd lib/WeBWorK/htdocs && npm install && cd ../../.. + EXPOSE 3000 HEALTHCHECK CMD curl -I localhost:3000/health diff --git a/lib/RenderApp.pm b/lib/RenderApp.pm index d57c3e8b8..a4772dd42 100644 --- a/lib/RenderApp.pm +++ b/lib/RenderApp.pm @@ -46,7 +46,6 @@ sub startup { $ENV{baseURL} //= $self->config('baseURL'); $ENV{formURL} //= $self->config('formURL'); $ENV{SITE_HOST} //= $self->config('SITE_HOST'); - $ENV{JWTanswerURL} //= $self->config('JWTanswerURL'); # validate configration urls if($ENV{baseURL} eq '/'){ diff --git a/lib/RenderApp/Controller/Render.pm b/lib/RenderApp/Controller/Render.pm index 482ca87fa..d68499541 100644 --- a/lib/RenderApp/Controller/Render.pm +++ b/lib/RenderApp/Controller/Render.pm @@ -89,12 +89,12 @@ async sub problem { await $inputs_ref->{problemSource} : $inputs_ref->{problemSource}; - my $problem = $c->newProblem({log => $c->log, read_path => $file_path, random_seed => $random_seed, problem_contents => $problem_contents}); - return $c->render(json => $problem->errport(), status => $problem->{status}) unless $problem->success(); - $inputs_ref->{baseURL} ||= $ENV{baseURL}; $inputs_ref->{formURL} ||= $ENV{formURL}; + my $problem = $c->newProblem({ log => $c->log, read_path => $file_path, random_seed => $random_seed, problem_contents => $problem_contents }); + return $c->render(json => $problem->errport(), status => $problem->{status}) unless $problem->success(); + $inputs_ref->{sourceFilePath} = $problem->{read_path}; # in case the path was updated... my @input_errs = checkInputs($inputs_ref); @@ -126,15 +126,48 @@ async sub problem { $ww_return_hash->{debug}->{render_warn} = [@input_errs, @output_errs]; # if answers are submitted and there is a provided answerURL... - if ( defined($inputs_ref->{JWTanswerURL}) && $inputs_ref->{submitAnswers} ) { + + + if ($inputs_ref->{JWTanswerURL} && $ww_return_hash->{answerJWT} && $inputs_ref->{submitAnswers}) { + my $answerJWTresponse = { + iss => $ENV{SITE_HOST}, + subject => "webwork.result", + status => 502, + message => "initial message" + }; my $reqBody = { - Accept => 'application/json', - answerJWT => $ww_return_hash->{answerJWT}, - Host => $ENV{SITE_HOST}, + Origin => $ENV{SITE_HOST}, + "Content-Type" => 'text/plain', }; - await $c->ua->post_p($ENV{JWTanswerURL}, $reqBody)-> - then(sub {$c->log->info(shift)})-> - catch(sub {$c->log->error(shift)}); + + $c->log->info("sending answerJWT to " . $inputs_ref->{JWTanswerURL}); + await $c->ua->request_timeout(7)->post_p($inputs_ref->{JWTanswerURL}, $reqBody, $ww_return_hash->{answerJWT})-> + then(sub { + my $response = shift->result; + + $answerJWTresponse->{status} = int($response->code); + if ($response->is_success) { + $answerJWTresponse->{message} = $response->body; + } + elsif ($response->is_error) {$answerJWTresponse->{message} = $response->message} + + $answerJWTresponse->{message} =~ s/"/\\"/g; + $answerJWTresponse->{message} =~ s/'/\'/g; + + })-> + catch(sub { + my $response = shift; + $c->log->error($response); + + $answerJWTresponse->{status} = 500; + $answerJWTresponse->{message} = $response; + }); + $answerJWTresponse = encode_json($answerJWTresponse); + $c->log->info("answerJWT response ".$answerJWTresponse); + + $ww_return_hash->{renderedHTML} =~ s/JWTanswerURLstatus/$answerJWTresponse/g; + }else{ + $ww_return_hash->{renderedHTML} =~ s/JWTanswerURLstatus//; } $c->respond_to( diff --git a/lib/RenderApp/Controller/RenderProblem.pm b/lib/RenderApp/Controller/RenderProblem.pm index 2dd807d97..545b27520 100644 --- a/lib/RenderApp/Controller/RenderProblem.pm +++ b/lib/RenderApp/Controller/RenderProblem.pm @@ -250,6 +250,7 @@ sub process_problem { $return_object->{raw_metadata_text} = $raw_metadata_text; # generate sessionJWT to store session data and answerJWT to update grade store + # only occurs if problemJWT exists! my ($sessionJWT, $answerJWT) = generateJWTs($return_object, $inputs_ref); $return_object->{sessionJWT} = $sessionJWT // ''; $return_object->{answerJWT} = $answerJWT // ''; @@ -470,13 +471,19 @@ sub generateJWTs { # store the current answer/response state for each entry foreach my $ans (keys %{$pg->{answers}}) { - # TODO: Fix foreach. Currently only keeps last entry + # TODO: Anything else we want to add to sessionHash? $sessionHash->{$ans} = $inputs_ref->{$ans}; $sessionHash->{ 'previous_' . $ans } = $inputs_ref->{$ans}; $sessionHash->{ 'MaThQuIlL_' . $ans } = $inputs_ref->{ 'MaThQuIlL_' . $ans }; - $scoreHash->{$ans} = unbless($pg->{answers}{$ans}); + # $scoreHash->{ans_id} = $ans; + # $scoreHash->{answer} = unbless($pg->{answers}{$ans}) // {}, + # $scoreHash->{score} = $pg->{answers}{$ans}{score} // 0, + + # TODO see why this key is causing JWT corruption in PHP + delete( $pg->{answers}{$ans}{student_ans}); } + $scoreHash->{answers} = unbless($pg->{answers}); # keep track of the number of correct/incorrect submissions $sessionHash->{numCorrect} = $pg->{problem_state}{num_of_correct_ans}; @@ -486,14 +493,20 @@ sub generateJWTs { $scoreHash->{result} = $pg->{problem_result}{score}; # create and return the session JWT + # TODO swap to alg => 'PBES2-HS512+A256KW', enc => 'A256GCM' my $sessionJWT = encode_jwt(payload => $sessionHash, auto_iat => 1, alg => 'HS256', key => $ENV{sessionJWTsecret}); # form answerJWT my $responseHash = { - score => $scoreHash, - problemJWT => $inputs_ref->{problemJWT}, - sessionJWT => $sessionJWT, + iss => $ENV{SITE_HOST}, + aud => $inputs_ref->{JWTanswerURL}, + score => $scoreHash, + problemJWT => $inputs_ref->{problemJWT}, + sessionJWT => $sessionJWT, + platform => 'standaloneRenderer' }; + + # Can instead use alg => 'PBES2-HS512+A256KW', enc => 'A256GCM' for JWE my $answerJWT = encode_jwt(payload=>$responseHash, alg => 'HS256', key => $ENV{problemJWTsecret}, auto_iat => 1); return ($sessionJWT, $answerJWT); diff --git a/lib/WeBWorK/htdocs/package.json b/lib/WeBWorK/htdocs/package.json index 4d8b2dcd0..3e3e61789 100644 --- a/lib/WeBWorK/htdocs/package.json +++ b/lib/WeBWorK/htdocs/package.json @@ -2,6 +2,7 @@ "name": "webwork.javascript_package_manager", "description": "Third party javascript for WeBWorK", "license": "GPL-2.0+", + "version": "1.0.0", "repository": { "type": "git", "url": "https://github.com/openwebwork/webwork2" diff --git a/lib/WeBWorK/lib/WebworkClient/jwe_secure_format.pl b/lib/WeBWorK/lib/WebworkClient/jwe_secure_format.pl index ae6df6fb4..1be707b8e 100644 --- a/lib/WeBWorK/lib/WebworkClient/jwe_secure_format.pl +++ b/lib/WeBWorK/lib/WebworkClient/jwe_secure_format.pl @@ -67,6 +67,12 @@ + ENDPROBLEMTEMPLATE diff --git a/logs/standalone_results.log b/logs/standalone_results.log old mode 100755 new mode 100644 index 63ee2e95a..e69de29bb --- a/logs/standalone_results.log +++ b/logs/standalone_results.log @@ -1,42 +0,0 @@ -[Fri Aug 07 12:56:16 2020] 1249 1596819376.29178 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/CollegeAlgebra_Trig/IntegerExponents/negative1-exponents.pg; duration: 0.528 sec; memory: 29816 bytes;}] -[Fri Aug 07 12:57:33 2020] 1685 1596819453.97726 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/CollegeAlgebra_Trig/IntegerExponents/multiply-divide-easy.pg; duration: 0.292 sec; memory: 29624 bytes;}] -[Fri Aug 07 12:57:37 2020] 1685 1596819457.68604 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/CollegeAlgebra_Trig/IntegerExponents/negative1-exponents.pg; duration: 0.098 sec; memory: 6420 bytes;}] -[Fri Aug 07 12:58:41 2020] 1685 1596819521.31447 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/CollegeAlgebra_Trig/IntegerExponents/multiply-divide-easy.pg; duration: 0.090 sec; memory: 5456 bytes;}] -[Fri Aug 07 12:58:47 2020] 1685 1596819527.4705 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/CollegeAlgebra_Trig/IntegerExponents/multiply-medium.pg; duration: 0.093 sec; memory: 5248 bytes;}] -[Fri Aug 07 12:58:49 2020] 1685 1596819529.31727 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/CollegeAlgebra_Trig/IntegerExponents/exponents-mixed-hard.pg; duration: 0.116 sec; memory: 5256 bytes;}] -[Fri Aug 07 12:58:51 2020] 1685 1596819531.05913 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/CollegeAlgebra_Trig/IntegerExponents/powers-exponents-basic.pg; duration: 0.094 sec; memory: 5724 bytes;}] -[Fri Aug 07 14:21:25 2020] 1685 1596824485.39972 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/CollegeAlgebra_Trig/IntegerExponents/negative1-exponents.pg; duration: 0.094 sec; memory: 5840 bytes;}] -[Fri Aug 07 15:32:36 2020] 2547 1596828756.36428 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/CollegeAlgebra_Trig/IntegerExponents/negative1-exponents.pg; duration: 0.292 sec; memory: 29752 bytes;}] -[Fri Aug 07 15:32:36 2020] 2547 1596828756.53859 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/CollegeAlgebra_Trig/IntegerExponents/exponents-mixed-hard.pg; duration: 0.107 sec; memory: 7692 bytes;}] -[Fri Aug 07 15:34:52 2020] 2578 1596828892.3089 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/CollegeAlgebra_Trig/IntegerExponents/negative1-exponents.pg; duration: 0.270 sec; memory: 30328 bytes;}] -[Fri Aug 07 15:34:54 2020] 2578 1596828894.20459 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/CollegeAlgebra_Trig/IntegerExponents/multiply-divide-easy.pg; duration: 0.087 sec; memory: 6060 bytes;}] -[Fri Aug 07 15:34:55 2020] 2578 1596828895.14978 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/CollegeAlgebra_Trig/IntegerExponents/multiply-medium.pg; duration: 0.089 sec; memory: 5412 bytes;}] -[Fri Aug 07 15:36:42 2020] 2593 1596829002.51717 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/CollegeAlgebra_Trig/IntegerExponents/exponents-fraction-positive.pg; duration: 0.279 sec; memory: 29256 bytes;}] -[Sun Aug 09 11:19:01 2020] 2593 1596986341.87443 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.178 sec; memory: 9100 bytes;}] -[Sun Aug 09 15:41:02 2020] 17452 1597002062.60926 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.348 sec; memory: 33196 bytes;}] -[Mon Aug 10 11:05:51 2020] 3696 1597071951.21439 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.417 sec; memory: 33896 bytes;}] -[Mon Aug 10 11:08:34 2020] 3729 1597072114.08543 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.396 sec; memory: 33416 bytes;}] -[Mon Aug 10 11:09:00 2020] 3738 1597072140.85916 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.397 sec; memory: 33360 bytes;}] -[Mon Aug 10 11:10:47 2020] 3747 1597072247.28362 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.399 sec; memory: 33260 bytes;}] -[Mon Aug 10 11:14:44 2020] 3756 1597072484.75483 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.396 sec; memory: 33380 bytes;}] -[Mon Aug 10 11:18:53 2020] 3770 1597072733.46263 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.398 sec; memory: 32908 bytes;}] -[Mon Aug 10 11:21:46 2020] 3796 1597072906.82124 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.411 sec; memory: 32880 bytes;}] -[Mon Aug 10 11:43:52 2020] 3796 1597074232.62612 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.194 sec; memory: 7948 bytes;}] -[Mon Aug 10 12:34:47 2020] 3796 1597077287.0625 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.182 sec; memory: 8532 bytes;}] -[Mon Aug 10 12:35:17 2020] 3796 1597077317.86973 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.186 sec; memory: 9844 bytes;}] -[Mon Aug 10 12:35:21 2020] 3796 1597077321.6543 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.175 sec; memory: 6068 bytes;}] -[Mon Aug 10 12:35:24 2020] 3796 1597077324.06133 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.186 sec; memory: 6192 bytes;}] -[Mon Aug 10 12:35:26 2020] 3796 1597077326.91569 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.184 sec; memory: 7328 bytes;}] -[Mon Aug 10 12:36:39 2020] 3796 1597077399.43439 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.188 sec; memory: 8812 bytes;}] -[Mon Aug 10 13:04:44 2020] 3796 1597079084.67435 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.182 sec; memory: 5932 bytes;}] -[Mon Aug 10 14:54:01 2020] 4781 1597085641.74943 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.363 sec; memory: 29864 bytes;}] -[Mon Aug 10 15:14:16 2020] 4880 1597086856.12465 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.364 sec; memory: 28052 bytes;}] -[Mon Aug 10 15:14:19 2020] 4880 1597086859.37185 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.170 sec; memory: 8472 bytes;}] -[Mon Aug 10 15:14:25 2020] 4880 1597086865.16237 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.168 sec; memory: 8744 bytes;}] -[Mon Aug 10 15:50:59 2020] 5020 1597089059.46028 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.353 sec; memory: 31944 bytes;}] -[Mon Aug 10 15:51:13 2020] 5020 1597089073.76601 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.173 sec; memory: 8500 bytes;}] -[Mon Aug 10 15:51:17 2020] 5020 1597089077.79333 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.169 sec; memory: 9292 bytes;}] -[Mon Aug 10 15:52:17 2020] 5020 1597089137.94167 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.164 sec; memory: 9120 bytes;}] -[Mon Aug 10 15:52:21 2020] 5020 1597089141.52815 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.172 sec; memory: 8976 bytes;}] -[Mon Aug 10 15:52:26 2020] 5020 1597089146.31309 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.171 sec; memory: 9936 bytes;}] -[Mon Aug 10 15:56:07 2020] 5020 1597089367.11208 - [{script:rederlyPGproblemRenderer; file:webwork-open-problem-library/Contrib/CUNY/CityTech/Calculus/setLimits_-_Analytic/polynomial.pg; duration: 0.167 sec; memory: 5796 bytes;}] diff --git a/render_app.conf.dist b/render_app.conf.dist index 4a2af95b9..b74040344 100644 --- a/render_app.conf.dist +++ b/render_app.conf.dist @@ -5,7 +5,6 @@ problemJWTsecret => "shared", webworkJWTsecret => "private", SITE_HOST => "http://localhost:3000", - JWTanswerURL => "http://localhost:3000/request", hypnotoad => { listen => ['http://*:3000'], accepts => 400,