1/**
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <v8.h>
18#include "ril.h"
19
20#include "ctrl_server.h"
21#include "logging.h"
22#include "node_buffer.h"
23#include "node_object_wrap.h"
24#include "node_util.h"
25#include "protobuf_v8.h"
26#include "responses.h"
27#include "status.h"
28#include "util.h"
29#include "worker.h"
30#include "worker_v8.h"
31
32#include "js_support.h"
33
34//#define JS_SUPPORT_DEBUG
35#ifdef  JS_SUPPORT_DEBUG
36
37#define DBG(...) ALOGD(__VA_ARGS__)
38
39#else
40
41#define DBG(...)
42
43#endif
44
45/**
46 * Current Radio state
47 */
48RIL_RadioState gRadioState = RADIO_STATE_UNAVAILABLE;
49
50v8::Handle<v8::Value> RadioStateGetter(v8::Local<v8::String> property,
51        const v8::AccessorInfo& info) {
52    return v8::Integer::New((int)gRadioState);
53}
54
55void RadioStateSetter(v8::Local<v8::String> property,
56        v8::Local<v8::Value> value, const v8::AccessorInfo& info) {
57    gRadioState = RIL_RadioState(value->Int32Value());
58}
59
60// A javascript sleep for a number of milli-seconds
61v8::Handle<v8::Value> MsSleep(const v8::Arguments& args) {
62    if (args.Length() != 1) {
63        DBG("MsSleep: expecting milli-seconds to sleep");
64    } else {
65        v8::Handle<v8::Value> v8MsValue(args[0]->ToObject());
66        int ms = int(v8MsValue->NumberValue());
67        v8::Unlocker unlocker;
68        usleep(ms * 1000);
69        v8::Locker locker;
70    }
71    return v8::Undefined();
72}
73
74// A javascript print function
75v8::Handle<v8::Value> Print(const v8::Arguments& args) {
76    bool first = true;
77    const int str_size = 1000;
78    char* str = new char[str_size];
79    int offset = 0;
80    for (int i = 0; i < args.Length(); i++) {
81        v8::HandleScope handle_scope;
82        if (first) {
83            first = false;
84        } else {
85            offset += snprintf(&str[offset], str_size, " ");
86        }
87        v8::String::Utf8Value strUtf8(args[i]);
88        const char* cstr = ToCString(strUtf8);
89        offset += snprintf(&str[offset], str_size, "%s", cstr);
90    }
91    ALOGD("%s", str);
92    delete [] str;
93    return v8::Undefined();
94}
95
96int ReadFile(const char *fileName, char** data, size_t *length) {
97    int status;
98    char* buffer = NULL;
99    size_t fileLength = 0;
100    FILE *f;
101
102    DBG("ReadFile E fileName=%s", fileName);
103
104    f = fopen(fileName, "rb");
105    if (f == NULL) {
106        DBG("Could not fopen '%s'", fileName);
107        status = STATUS_COULD_NOT_OPEN_FILE;
108    } else {
109        // Determine the length of the file
110        fseek(f, 0, SEEK_END);
111        fileLength = ftell(f);
112        DBG("fileLength=%d", fileLength);
113        rewind(f);
114
115        // Read file into a buffer
116        buffer = new char[fileLength+1];
117        size_t readLength = fread(buffer, 1, fileLength, f);
118        if (readLength != fileLength) {
119            DBG("Couldn't read entire file");
120            delete [] buffer;
121            buffer = NULL;
122            status = STATUS_COULD_NOT_READ_FILE;
123        } else {
124            DBG("File read");
125            buffer[fileLength] = 0;
126            status = STATUS_OK;
127        }
128        fclose(f);
129    }
130
131    if (length != NULL) {
132        *length = fileLength;
133    }
134    *data = buffer;
135    DBG("ReadFile X status=%d", status);
136    return status;
137}
138
139char *CreateFileName(const v8::Arguments& args) {
140    v8::String::Utf8Value fileNameUtf8Value(args[0]);
141    const char* fileName = ToCString(fileNameUtf8Value);
142    const char* directory = "/sdcard/data/";
143
144    int fullPathLength = strlen(directory) + strlen(fileName) + 1;
145    char * fullPath = new char[fullPathLength];
146    strncpy(fullPath, directory, fullPathLength);
147    strncat(fullPath, fileName, fullPathLength);
148    return fullPath;
149}
150
151// A javascript read file function arg[0] = filename
152v8::Handle<v8::Value> ReadFileToString(const v8::Arguments& args) {
153    DBG("ReadFileToString E");
154    v8::HandleScope handle_scope;
155    v8::Handle<v8::Value> retValue;
156
157    if (args.Length() < 1) {
158        // No file name return Undefined
159        DBG("ReadFile X no argumens");
160        return v8::Undefined();
161    } else {
162        char *fileName = CreateFileName(args);
163
164        char *buffer;
165        int status = ReadFile(fileName, &buffer);
166        if (status == 0) {
167            retValue = v8::String::New(buffer);
168        } else {
169            retValue = v8::Undefined();
170        }
171    }
172    DBG("ReadFileToString X");
173    return retValue;
174}
175
176// A javascript read file function arg[0] = filename
177v8::Handle<v8::Value> ReadFileToBuffer(const v8::Arguments& args) {
178    DBG("ReadFileToBuffer E");
179    v8::HandleScope handle_scope;
180    v8::Handle<v8::Value> retValue;
181
182    if (args.Length() < 1) {
183        // No file name return Undefined
184        DBG("ReadFileToBuffer X no argumens");
185        return v8::Undefined();
186    } else {
187        char *fileName = CreateFileName(args);
188
189        char *buffer;
190        size_t length;
191        int status = ReadFile(fileName, &buffer, &length);
192        if (status == 0) {
193            Buffer *buf = Buffer::New(length);
194            memmove(buf->data(), buffer, length);
195            retValue = buf->handle_;
196        } else {
197            retValue = v8::Undefined();
198        }
199    }
200    DBG("ReadFileToBuffer X");
201    return retValue;
202}
203
204void ErrorCallback(v8::Handle<v8::Message> message,
205        v8::Handle<v8::Value> data) {
206    LogErrorMessage(message, "");
207}
208
209// Read, compile and run a javascript file
210v8::Handle<v8::Value> Include(const v8::Arguments& args) {
211    DBG("Include E");
212    v8::HandleScope handle_scope;
213    v8::Handle<v8::Value> retValue;
214    v8::TryCatch try_catch;
215    try_catch.SetVerbose(true);
216
217    if (args.Length() < 1) {
218        // No file name return Undefined
219        DBG("Include X no argumens");
220        return v8::Undefined();
221    } else {
222        char *fileName = CreateFileName(args);
223
224        char *buffer;
225        int status = ReadFile(fileName, &buffer);
226        if (status == 0) {
227            runJs(v8::Context::GetCurrent(), &try_catch, fileName, buffer);
228        } else {
229            retValue = v8::Undefined();
230        }
231    }
232    DBG("Include X");
233    return retValue;
234}
235
236
237/**
238 * Create a JsContext, must be called within a HandleScope?
239 */
240v8::Persistent<v8::Context> makeJsContext() {
241    v8::HandleScope handle_scope;
242    v8::TryCatch try_catch;
243
244    // Add a Message listner to Catch errors as they occur
245    v8::V8::AddMessageListener(ErrorCallback);
246
247    // Create a template for the global object and
248    // add the function template for print to it.
249    v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();
250    global->SetAccessor(v8::String::New("gRadioState"),
251            RadioStateGetter, RadioStateSetter);
252    global->Set(v8::String::New("msSleep"), v8::FunctionTemplate::New(MsSleep));
253    global->Set(v8::String::New("print"), v8::FunctionTemplate::New(Print));
254    global->Set(v8::String::New("readFileToBuffer"),
255            v8::FunctionTemplate::New(ReadFileToBuffer));
256    global->Set(v8::String::New("readFileToString"),
257            v8::FunctionTemplate::New(ReadFileToString));
258    global->Set(v8::String::New("sendRilRequestComplete"),
259            v8::FunctionTemplate::New(SendRilRequestComplete));
260    global->Set(v8::String::New("sendRilUnsolicitedResponse"),
261            v8::FunctionTemplate::New(SendRilUnsolicitedResponse));
262    global->Set(v8::String::New("sendCtrlRequestComplete"),
263            v8::FunctionTemplate::New(SendCtrlRequestComplete));
264    global->Set(v8::String::New("include"), v8::FunctionTemplate::New(Include));
265    WorkerV8ObjectTemplateInit(global);
266    SchemaObjectTemplateInit(global);
267    Buffer::InitializeObjectTemplate(global);
268
269    // Create context with our globals and make it the current scope
270    v8::Persistent<v8::Context> context = v8::Context::New(NULL, global);
271
272
273    if (try_catch.HasCaught()) {
274        DBG("makeJsContext: Exception making the context");
275        ReportException(&try_catch);
276        try_catch.ReThrow();
277    }
278
279    return context;
280}
281
282/**
283 * Run some javascript code.
284 */
285void runJs(v8::Handle<v8::Context> context, v8::TryCatch *try_catch,
286        const char *fileName, const char *code) {
287    v8::HandleScope handle_scope;
288
289    // Compile the source
290    v8::Handle<v8::Script> script = v8::Script::Compile(
291                v8::String::New(code), v8::String::New(fileName));
292    if (try_catch->HasCaught()) {
293        ALOGE("-- Compiling the source failed");
294    } else {
295        // Run the resulting script
296        v8::Handle<v8::Value> result = script->Run();
297        if (try_catch->HasCaught()) {
298            ALOGE("-- Running the script failed");
299        }
300    }
301}
302
303void testRadioState(v8::Handle<v8::Context> context) {
304    ALOGD("testRadioState E:");
305    v8::HandleScope handle_scope;
306
307    v8::TryCatch try_catch;
308    try_catch.SetVerbose(true);
309
310    runJs(context, &try_catch, "local-string",
311        "for(i = 0; i < 10; i++) {\n"
312        "  gRadioState = i;\n"
313        "  print('gRadioState=' + gRadioState);\n"
314        "}\n"
315        "gRadioState = 1;\n"
316        "print('last gRadioState=' + gRadioState);\n");
317    ALOGD("testRadioState X:");
318}
319
320void testMsSleep(v8::Handle<v8::Context> context) {
321    ALOGD("testMsSleep E:");
322    v8::HandleScope handle_scope;
323
324    v8::TryCatch try_catch;
325    try_catch.SetVerbose(true);
326
327    runJs(context, &try_catch, "local-string",
328        "for(i = 0; i < 10; i++) {\n"
329        "  sleeptime = i * 200\n"
330        "  print('msSleep ' + sleeptime);\n"
331        "  msSleep(sleeptime);\n"
332        "}\n");
333    ALOGD("testMsSleep X:");
334}
335
336void testPrint(v8::Handle<v8::Context> context) {
337    ALOGD("testPrint E:");
338    v8::HandleScope handle_scope;
339
340    v8::TryCatch try_catch;
341    try_catch.SetVerbose(true);
342
343    runJs(context, &try_catch, "local-string", "print(\"Hello\")");
344    ALOGD("testPrint X:");
345}
346
347void testCompileError(v8::Handle<v8::Context> context) {
348    ALOGD("testCompileError E:");
349    v8::HandleScope handle_scope;
350
351    v8::TryCatch try_catch;
352    try_catch.SetVerbose(true);
353
354    // +++ generate a compile time error
355    runJs(context, &try_catch, "local-string", "+++");
356    ALOGD("testCompileError X:");
357}
358
359void testRuntimeError(v8::Handle<v8::Context> context) {
360    ALOGD("testRuntimeError E:");
361    v8::HandleScope handle_scope;
362
363    v8::TryCatch try_catch;
364    try_catch.SetVerbose(true);
365
366    // Runtime error
367    runJs(context, &try_catch, "local-string",
368        "function hello() {\n"
369        "  print(\"Hi there\");\n"
370        "}\n"
371        "helloo()");
372    ALOGD("testRuntimeError X:");
373}
374
375void testReadFile() {
376    char *buffer;
377    size_t length;
378    int status;
379
380    ALOGD("testReadFile E:");
381
382    status = ReadFile("/sdcard/data/no-file", &buffer, &length);
383    ALOGD("testReadFile expect status != 0, status=%d, buffer=%p, length=%d",
384            status, buffer, length);
385
386    ALOGD("testReadFile X:");
387}
388
389
390void testReadFileToStringBuffer(v8::Handle<v8::Context> context) {
391    ALOGD("testReadFileToStringBuffer E:");
392    v8::HandleScope handle_scope;
393
394    v8::TryCatch try_catch;
395    try_catch.SetVerbose(true);
396
397    runJs(context, &try_catch, "local-string",
398        "fileContents = readFileToString(\"mock_ril.js\");\n"
399        "print(\"fileContents:\\n\" + fileContents);\n"
400        "buffer = readFileToBuffer(\"ril.desc\");\n"
401        "print(\"buffer.length=\" + buffer.length);\n");
402    ALOGD("testReadFileToStringBuffer X:");
403}
404
405void testJsSupport(v8::Handle<v8::Context> context) {
406    ALOGD("testJsSupport E: ********");
407    testRadioState(context);
408    testMsSleep(context);
409    testPrint(context);
410    testCompileError(context);
411    testRuntimeError(context);
412    testReadFile();
413    testReadFileToStringBuffer(context);
414    ALOGD("testJsSupport X: ********\n");
415}
416