request.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. /*
  2. * Copyright (C) 2019 Intel Corporation. All rights reserved.
  3. * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  4. */
  5. import * as console from './console'
  6. import * as timer from './timer'
  7. @external("env", "wasm_response_send")
  8. declare function wasm_response_send(buffer: ArrayBuffer, size: i32): void;
  9. @external("env", "wasm_register_resource")
  10. declare function wasm_register_resource(url: ArrayBuffer): void;
  11. @external("env", "wasm_post_request")
  12. declare function wasm_post_request(buffer: ArrayBuffer, size: i32): void;
  13. @external("env", "wasm_sub_event")
  14. declare function wasm_sub_event(url: ArrayBuffer): void;
  15. var COAP_GET = 1;
  16. var COAP_POST = 2;
  17. var COAP_PUT = 3;
  18. var COAP_DELETE = 4;
  19. var COAP_EVENT = COAP_DELETE + 2;
  20. /* CoAP response codes */
  21. export enum CoAP_Status {
  22. NO_ERROR = 0,
  23. CREATED_2_01 = 65, /* CREATED */
  24. DELETED_2_02 = 66, /* DELETED */
  25. VALID_2_03 = 67, /* NOT_MODIFIED */
  26. CHANGED_2_04 = 68, /* CHANGED */
  27. CONTENT_2_05 = 69, /* OK */
  28. CONTINUE_2_31 = 95, /* CONTINUE */
  29. BAD_REQUEST_4_00 = 128, /* BAD_REQUEST */
  30. UNAUTHORIZED_4_01 = 129, /* UNAUTHORIZED */
  31. BAD_OPTION_4_02 = 130, /* BAD_OPTION */
  32. FORBIDDEN_4_03 = 131, /* FORBIDDEN */
  33. NOT_FOUND_4_04 = 132, /* NOT_FOUND */
  34. METHOD_NOT_ALLOWED_4_05 = 133, /* METHOD_NOT_ALLOWED */
  35. NOT_ACCEPTABLE_4_06 = 134, /* NOT_ACCEPTABLE */
  36. PRECONDITION_FAILED_4_12 = 140, /* BAD_REQUEST */
  37. REQUEST_ENTITY_TOO_LARGE_4_13 = 141, /* REQUEST_ENTITY_TOO_LARGE */
  38. UNSUPPORTED_MEDIA_TYPE_4_15 = 143, /* UNSUPPORTED_MEDIA_TYPE */
  39. INTERNAL_SERVER_ERROR_5_00 = 160, /* INTERNAL_SERVER_ERROR */
  40. NOT_IMPLEMENTED_5_01 = 161, /* NOT_IMPLEMENTED */
  41. BAD_GATEWAY_5_02 = 162, /* BAD_GATEWAY */
  42. SERVICE_UNAVAILABLE_5_03 = 163, /* SERVICE_UNAVAILABLE */
  43. GATEWAY_TIMEOUT_5_04 = 164, /* GATEWAY_TIMEOUT */
  44. PROXYING_NOT_SUPPORTED_5_05 = 165, /* PROXYING_NOT_SUPPORTED */
  45. /* Erbium errors */
  46. MEMORY_ALLOCATION_ERROR = 192, PACKET_SERIALIZATION_ERROR,
  47. /* Erbium hooks */
  48. MANUAL_RESPONSE, PING_RESPONSE
  49. };
  50. var g_mid: i32 = 0;
  51. class wamr_request {
  52. mid: i32 = 0;
  53. url: string = "";
  54. action: i32 = 0;
  55. fmt: i32 = 0;
  56. payload: ArrayBuffer;
  57. payload_len: i32 = 0;
  58. sender: i32 = 0;
  59. constructor(mid: i32, url: string, action: i32, fmt: i32,
  60. payload: ArrayBuffer, payload_len: number) {
  61. this.mid = mid;
  62. this.url = url;
  63. this.action = action;
  64. this.fmt = fmt;
  65. this.payload = payload;
  66. this.payload_len = i32(payload_len);
  67. }
  68. }
  69. class wamr_response {
  70. mid: i32 = 0;
  71. status: i32 = 0;
  72. fmt: i32 = 0;
  73. payload: ArrayBuffer | null;
  74. payload_len: i32 = 0;
  75. receiver: i32 = 0;
  76. constructor(mid: i32, status: i32, fmt: i32,
  77. payload: ArrayBuffer | null, payload_len: i32) {
  78. this.mid = mid;
  79. this.status = status;
  80. this.fmt = fmt;
  81. this.payload = payload;
  82. this.payload_len = payload_len;
  83. }
  84. set_status(status: number): void {
  85. this.status = i32(status);
  86. }
  87. set_payload(payload: ArrayBuffer, payload_len: number): void {
  88. this.payload = payload;
  89. this.payload_len = i32(payload_len);
  90. }
  91. }
  92. class wamr_resource {
  93. url: string;
  94. type: number;
  95. cb: request_handler_f;
  96. constructor(url: string, type: number, cb: request_handler_f) {
  97. this.url = url;
  98. this.type = type;
  99. this.cb = cb;
  100. }
  101. }
  102. function is_expire(trans: wamr_transaction, index: i32, array: Array<wamr_transaction>): bool {
  103. var now = timer.now();
  104. var elapsed_ms = (now < trans.time) ?
  105. (now + (0xFFFFFFFF - trans.time) + 1) : (now - trans.time);
  106. return elapsed_ms >= TRANSACTION_TIMEOUT_MS;
  107. }
  108. function not_expire(trans: wamr_transaction, index: i32, array: Array<wamr_transaction>): bool {
  109. var now = timer.now();
  110. var elapsed_ms = (now < trans.time) ?
  111. (now + (0xFFFFFFFF - trans.time) + 1) : (now - trans.time);
  112. return elapsed_ms < TRANSACTION_TIMEOUT_MS;
  113. }
  114. function transaction_timeout_handler(): void {
  115. var now = timer.now();
  116. var expired = transaction_list.filter(is_expire);
  117. transaction_list = transaction_list.filter(not_expire);
  118. expired.forEach(item => {
  119. item.cb(null);
  120. transaction_remove(item);
  121. })
  122. if (transaction_list.length > 0) {
  123. var elpased_ms: number, ms_to_expiry: number;
  124. now = timer.now();
  125. if (now < transaction_list[0].time) {
  126. elpased_ms = now + (0xFFFFFFFF - transaction_list[0].time) + 1;
  127. } else {
  128. elpased_ms = now - transaction_list[0].time;
  129. }
  130. ms_to_expiry = TRANSACTION_TIMEOUT_MS - elpased_ms;
  131. timer.timer_restart(g_trans_timer, ms_to_expiry);
  132. } else {
  133. timer.timer_cancel(g_trans_timer);
  134. }
  135. }
  136. function transaction_find(mid: number): wamr_transaction | null {
  137. for (let i = 0; i < transaction_list.length; i++) {
  138. if (transaction_list[i].mid == mid)
  139. return transaction_list[i];
  140. }
  141. return null;
  142. }
  143. function transaction_add(trans: wamr_transaction): void {
  144. transaction_list.push(trans);
  145. if (transaction_list.length == 1) {
  146. g_trans_timer = timer.setTimeout(
  147. transaction_timeout_handler,
  148. TRANSACTION_TIMEOUT_MS
  149. );
  150. }
  151. }
  152. function transaction_remove(trans: wamr_transaction): void {
  153. var index = transaction_list.indexOf(trans);
  154. transaction_list.splice(index, 1);
  155. }
  156. var transaction_list = new Array<wamr_transaction>();
  157. class wamr_transaction {
  158. mid: number;
  159. time: number;
  160. cb: (resp: wamr_response | null) => void;
  161. constructor(mid: number, time: number, cb: (resp: wamr_response) => void) {
  162. this.mid = mid;
  163. this.time = time;
  164. this.cb = cb;
  165. }
  166. }
  167. var REQUEST_PACKET_FIX_PART_LEN = 18;
  168. var RESPONSE_PACKET_FIX_PART_LEN = 16;
  169. var TRANSACTION_TIMEOUT_MS = 5000;
  170. var g_trans_timer: timer.user_timer;
  171. var Reg_Event = 0;
  172. var Reg_Request = 1;
  173. function pack_request(req: wamr_request): DataView {
  174. var url_len = req.url.length + 1;
  175. var len = REQUEST_PACKET_FIX_PART_LEN + url_len + req.payload_len
  176. var buf = new ArrayBuffer(len);
  177. var dataview = new DataView(buf, 0, len);
  178. dataview.setUint8(0, 1);
  179. dataview.setUint8(1, u8(req.action));
  180. dataview.setUint16(2, u16(req.fmt));
  181. dataview.setUint32(4, req.mid);
  182. dataview.setUint32(8, req.sender);
  183. dataview.setUint16(12, u16(url_len))
  184. dataview.setUint32(14, req.payload_len);
  185. var i = 0;
  186. for (i = 0; i < url_len - 1; i++) {
  187. dataview.setUint8(i + 18, u8(req.url.codePointAt(i)));
  188. }
  189. dataview.setUint8(i + 18, 0);
  190. var payload_view = new DataView(req.payload);
  191. for (i = 0; i < req.payload_len; i++) {
  192. dataview.setUint8(i + 18 + url_len, u8(payload_view.getUint8(i)));
  193. }
  194. return dataview;
  195. }
  196. function unpack_request(packet: ArrayBuffer, size: i32): wamr_request {
  197. var dataview = new DataView(packet, 0, size);
  198. if (dataview.getUint8(0) != 1)
  199. throw new Error("packet version mismatch");
  200. if (size < REQUEST_PACKET_FIX_PART_LEN)
  201. throw new Error("packet size error");
  202. var url_len = dataview.getUint16(12);
  203. var payload_len = dataview.getUint32(14);
  204. if (size != (REQUEST_PACKET_FIX_PART_LEN + url_len + payload_len))
  205. throw new Error("packet size error");
  206. var action = dataview.getUint8(1);
  207. var fmt = dataview.getUint16(2);
  208. var mid = dataview.getUint32(4);
  209. var sender = dataview.getUint32(8);
  210. var url = packet.slice(REQUEST_PACKET_FIX_PART_LEN, REQUEST_PACKET_FIX_PART_LEN + url_len - 1);
  211. var payload = packet.slice(REQUEST_PACKET_FIX_PART_LEN + url_len, REQUEST_PACKET_FIX_PART_LEN + url_len + payload_len);
  212. var req = new wamr_request(mid, String.UTF8.decode(url), action, fmt, payload, payload_len);
  213. req.sender = sender;
  214. return req;
  215. }
  216. function pack_response(resp: wamr_response): DataView {
  217. var len = RESPONSE_PACKET_FIX_PART_LEN + resp.payload_len
  218. var buf = new ArrayBuffer(len);
  219. var dataview = new DataView(buf, 0, len);
  220. dataview.setUint8(0, 1);
  221. dataview.setUint8(1, u8(resp.status));
  222. dataview.setUint16(2, u16(resp.fmt));
  223. dataview.setUint32(4, resp.mid);
  224. dataview.setUint32(8, resp.receiver);
  225. dataview.setUint32(12, resp.payload_len)
  226. if (resp.payload != null) {
  227. var payload_view = new DataView(resp.payload!);
  228. for (let i = 0; i < resp.payload_len; i++) {
  229. dataview.setUint8(i + 16, payload_view.getUint8(i));
  230. }
  231. }
  232. return dataview;
  233. }
  234. function unpack_response(packet: ArrayBuffer, size: i32): wamr_response {
  235. var dataview = new DataView(packet, 0, size);
  236. if (dataview.getUint8(0) != 1)
  237. throw new Error("packet version mismatch");
  238. if (size < RESPONSE_PACKET_FIX_PART_LEN)
  239. throw new Error("packet size error");
  240. var payload_len = dataview.getUint32(12);
  241. if (size != RESPONSE_PACKET_FIX_PART_LEN + payload_len)
  242. throw new Error("packet size error");
  243. var status = dataview.getUint8(1);
  244. var fmt = dataview.getUint16(2);
  245. var mid = dataview.getUint32(4);
  246. var receiver = dataview.getUint32(8);
  247. var payload = packet.slice(RESPONSE_PACKET_FIX_PART_LEN);
  248. var resp = new wamr_response(mid, status, fmt, payload, payload_len);
  249. resp.receiver = receiver;
  250. return resp;
  251. }
  252. function do_request(req: wamr_request, cb: (resp: wamr_response) => void): void {
  253. var trans = new wamr_transaction(req.mid, timer.now(), cb);
  254. var msg = pack_request(req);
  255. transaction_add(trans);
  256. wasm_post_request(msg.buffer, msg.byteLength);
  257. }
  258. function do_response(resp: wamr_response): void {
  259. var msg = pack_response(resp);
  260. wasm_response_send(msg.buffer, msg.byteLength);
  261. }
  262. var resource_list = new Array<wamr_resource>();
  263. type request_handler_f = (req: wamr_request) => void;
  264. function registe_url_handler(url: string, cb: request_handler_f, type: number): void {
  265. for (let i = 0; i < resource_list.length; i++) {
  266. if (resource_list[i].type == type && resource_list[i].url == url) {
  267. resource_list[i].cb = cb;
  268. return;
  269. }
  270. }
  271. var res = new wamr_resource(url, type, cb);
  272. resource_list.push(res);
  273. if (type == Reg_Request)
  274. wasm_register_resource(String.UTF8.encode(url));
  275. else
  276. wasm_sub_event(String.UTF8.encode(url));
  277. }
  278. function is_event_type(req: wamr_request): bool {
  279. return req.action == COAP_EVENT;
  280. }
  281. function check_url_start(url: string, leading_str: string): bool {
  282. return url.split('/')[0] == leading_str.split('/')[0];
  283. }
  284. /* User APIs below */
  285. export function post(url: string, payload: ArrayBuffer, payload_len: number, tag: string,
  286. cb: (resp: wamr_response) => void): void {
  287. var req = new wamr_request(g_mid++, url, COAP_POST, 0, payload, payload_len);
  288. do_request(req, cb);
  289. }
  290. export function get(url: string, tag: string,
  291. cb: (resp: wamr_response) => void): void {
  292. var req = new wamr_request(g_mid++, url, COAP_GET, 0, new ArrayBuffer(0), 0);
  293. do_request(req, cb);
  294. }
  295. export function put(url: string, payload: ArrayBuffer, payload_len: number, tag: string,
  296. cb: (resp: wamr_response) => void): void {
  297. var req = new wamr_request(g_mid++, url, COAP_PUT, 0, payload, payload_len);
  298. do_request(req, cb);
  299. }
  300. export function del(url: string, tag: string,
  301. cb: (resp: wamr_response) => void): void {
  302. var req = new wamr_request(g_mid++, url, COAP_DELETE, 0, new ArrayBuffer(0), 0);
  303. do_request(req, cb);
  304. }
  305. export function make_response_for_request(req: wamr_request): wamr_response {
  306. var resp = new wamr_response(req.mid, CoAP_Status.CONTENT_2_05, 0, null, 0);
  307. resp.receiver = req.sender;
  308. return resp;
  309. }
  310. export function api_response_send(resp: wamr_response): void {
  311. do_response(resp);
  312. }
  313. export function register_resource_handler(url: string,
  314. request_handle: request_handler_f): void {
  315. registe_url_handler(url, request_handle, Reg_Request);
  316. }
  317. export function publish_event(url: string, fmt: number,
  318. payload: ArrayBuffer, payload_len: number): void {
  319. var req = new wamr_request(g_mid++, url, COAP_EVENT, i32(fmt), payload, payload_len);
  320. var msg = pack_request(req);
  321. wasm_post_request(msg.buffer, msg.byteLength);
  322. }
  323. export function subscribe_event(url: string, cb: request_handler_f): void {
  324. registe_url_handler(url, cb, Reg_Event);
  325. }
  326. /* These two APIs are required by wamr runtime,
  327. use a wrapper to export them in the entry file
  328. e.g:
  329. import * as request from '.wamr_app_lib/request'
  330. // Your code here ...
  331. export function _on_request(buffer_offset: i32, size: i32): void {
  332. on_request(buffer_offset, size);
  333. }
  334. export function _on_response(buffer_offset: i32, size: i32): void {
  335. on_response(buffer_offset, size);
  336. }
  337. */
  338. export function on_request(buffer_offset: i32, size: i32): void {
  339. var buffer = new ArrayBuffer(size);
  340. var dataview = new DataView(buffer);
  341. for (let i = 0; i < size; i++) {
  342. dataview.setUint8(i, load<i8>(buffer_offset + i, 0, 1));
  343. }
  344. var req = unpack_request(buffer, size);
  345. var is_event = is_event_type(req);
  346. for (let i = 0; i < resource_list.length; i++) {
  347. if ((is_event && resource_list[i].type == Reg_Event)
  348. || (!is_event && resource_list[i].type == Reg_Request)) {
  349. if (check_url_start(req.url, resource_list[i].url)) {
  350. resource_list[i].cb(req);
  351. return;
  352. }
  353. }
  354. }
  355. console.log("on_request: exit. no service handler.");
  356. }
  357. export function on_response(buffer_offset: i32, size: i32): void {
  358. var buffer = new ArrayBuffer(size);
  359. var dataview = new DataView(buffer);
  360. for (let i = 0; i < size; i++) {
  361. dataview.setUint8(i, load<i8>(buffer_offset + i, 0, 1));
  362. }
  363. var resp = unpack_response(buffer, size);
  364. var trans = transaction_find(resp.mid);
  365. if (trans != null) {
  366. if (transaction_list.indexOf(trans) == 0) {
  367. if (transaction_list.length >= 2) {
  368. var elpased_ms: number, ms_to_expiry: number;
  369. var now = timer.now();
  370. if (now < transaction_list[1].time) {
  371. elpased_ms = now + (0xFFFFFFFF - transaction_list[1].time) + 1;
  372. } else {
  373. elpased_ms = now - transaction_list[1].time;
  374. }
  375. ms_to_expiry = TRANSACTION_TIMEOUT_MS - elpased_ms;
  376. timer.timer_restart(g_trans_timer, ms_to_expiry);
  377. } else {
  378. timer.timer_cancel(g_trans_timer);
  379. }
  380. }
  381. trans.cb(resp);
  382. }
  383. }