1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958
//! # Purpose //! //! This module contains the functions exported for use by other languages. //! //! If you're writing bindings, you're in the right place. Don't use imageflow_core::ffi, that's //! *not* correct - those are unstable and will disappear as C is replaced with Rust. //! //! Don't call functions against the same context from multiple threads. You can create contexts //! from as many threads as you like, but you are responsible for synchronizing API calls //! on a per-context basis if you want to use one context from multiple threads. No use //! case for multithreaded Context access has been presented, so it is out of scope for API design. //! //! //! //! //! # Memory Lifetimes //! //! In order to prevent dangling pointers, we must be correct about memory lifetimes. //! //! ## ... when allocated by Imageflow, assume the lifetime of the `context` //! //! **In Imageflow, by default, all things created with a context will be destroyed when the //! context is destroyed.** Don't try to access ANYTHING imageflow has provided after the context is gone. //! //! This is very nice, as it means that a client's failure to clean up //! will have limited impact on the process as a whole - as long as the client at minimum //! calls `flow_context_destroy` at the end of all possible code paths. //! //! However, waiting to free memory and run destructors until the context is destroyed is not ideal; //! it increases our peak memory usage/needs and may cause operations //! to fail that would otherwise succeed. //! //! There are two ways to mitigate this. //! //! 1. When creating an I/O object, request it be cleaned up when the first job it is assigned to is cleaned up (instead of the context). //! 2. Manually invoke the corresponding destroy function when you're done with the thing. //! //! ### Destroying things //! //! * An `imageflow_context` should ALWAYS be destroyed with `imageflow_context_destroy` //! * JsonResponse structures should be released with `imageflow_json_response_destroy` //! * An `imageflow_job` can be destroyed early with `imageflow_job_destroy` //! //! ## ... when allocated by the client, Imageflow only borrows it for the `invocation` //! //! **Imageflow assumes that, at minimum, all pointers that you provide to it will, at minimum, //! remain valid for the duration of the API call.** We'll call this 'borrowing'. Imageflow is //! just borrowing it for a bit; not taking ownership of the thing. //! //! This may seem obvious, but it is not, in fact, guaranteed by garbage-collected languages. They //! are often oblivious to pointers, and cannot track what data is and is not referenced. //! Therefore, we suggest that you ensure every allocation made (and handed to Imageflow) is //! referenced *after* the imageflow API call, preferably in a way that will not be optimized away //! at runtime. Many languages and FFI libraries offer a utility method just for this purpose. //! //! ## ... although Imageflow may borrow some strings for the life of the context, yet not own them. //! //! This happens for strings that are usually static constants, and unlikely to be allocated on //! the heap anyway. //! //! * When an Imageflow API asks for a filename, function name, or error message, it will //! assume that those strings are pointers that (a) Imageflow is not //! responsible for freeing, and (b) will (at least) outlive the `context`. //! //! ## ... and it should be very clear when Imageflow is taking ownership of something you created! //! //! When Imageflow needs continued access to data that is NOT highly likely to be static, it //! will be documented. //! //! * If you give Imageflow a buffer to read an image from, it will need to access that buffer //! much longer than the initial io_create call. //! //! ## What if I need something to outlive the `context`? //! //! Copy it before the context is destroyed. //! //! //! //! # Data types //! //! Reference for those creating bindings in other languages //! //! Two types are platform-specific - use the corresponding pointer or size type that varies with //! your platform. //! //! * libc::c_void (or anything *mut or *const): Platform-sized pointer. 32 or 64 bits. //! * The above includes *mut Context, *mut Job, *mut JobIo, etc. //! * libc::size_t (or usize): Unsigned integer, platform-sized. 32 or 64 bits. //! //! //! Treat *mut Context, *mut Job, *mut JobIo, *mut JsonResponse ALL as opaque pointers. //! //! ## Strings //! //! ASCII is a safe subset of UTF-8; therefore wherever Imageflow asks for UTF-8 encoded bytes, you may provide ASCII instead. //! //! You will provide Imageflow with strings in one of 3 ways: //! * UTF-8 null-terminated. You'll see something like `libc::char`, but no length parameter. Short and likely static strings are usually transmitted this way. //! * Operating system null-terminated. Only applicable to `imageflow_io_create_for_file`. //! * UTF-8 buffer with length. You'll usually see *const u8 and a length parameter. This is common for buffers of UTF-8 encoded json. //! //! filename: *const libc::c_char //! function_name: *const libc::c_char //! //! Fixed size //! //! * u8 (1 unsigned byte) //! * bool (C99 style, 1 byte, value 0 or 1) //! * The rest seem self-explanatory. //! * `i` prefixes signed ints //! * `u` prefixes unsigned ints. //! * `f` prefixes floating point //! //! Structs //! //! Consider all structures to be opaque. Do not attempt to access fields by offsets; rather, //! use the accessor functions provided. //! //! //! ## Failure behavior //! //! If you provide a null pointer for `imageflow_context`, then the process *should* terminate. //! This "fail fast" behavior offers the best opportunity for a useful stacktrace, and it's not a //! recoverable error. //! //! //! #![crate_type = "cdylib"] #![feature(alloc_system)] extern crate imageflow_core as c; extern crate alloc_system; extern crate libc; use c::ffi; pub use c::{Context, Job}; pub use c::ffi::ImageflowJobIo as JobIo; pub use c::ffi::ImageflowJsonResponse as JsonResponse; use std::ptr; use std::io::Write; use std::ffi::CStr; #[cfg(test)] use std::str; /// /// What is possible with the IO object #[repr(C)] pub enum IoMode { None = 0, ReadSequential = 1, WriteSequential = 2, ReadSeekable = 5, // 1 | 4, WriteSeekable = 6, // 2 | 4, ReadWriteSeekable = 15, // 1 | 2 | 4 | 8 } /// /// Input or output? #[repr(C)] #[derive(Copy,Clone)] pub enum Direction { Out = 8, In = 4, } /// /// When a resource should be closed/freed/cleaned up /// #[repr(C)] #[derive(Clone, Copy, PartialEq, Debug)] pub enum CleanupWith{ /// When the context is destroyed Context = 0, /// When the first job that the item is associated with is destroyed. (Not yet implemented) FirstJob = 1 } /// /// How long the provided pointer/buffer will remain valid. /// Callers must prevent the memory from being freed or moved until this contract expires. /// #[repr(C)] #[derive(Clone, Copy, PartialEq, Debug)] pub enum Lifetime{ /// Pointer will outlive function call. If the host language has a garbage collector, call the appropriate method to ensure the object pointed to will not be collected or moved until the call returns. You may think host languages do this automatically in their FFI system. Most do not. OutlivesFunctionCall = 0, /// Pointer will outlive context. If the host language has a GC, ensure that you are using a data type guaranteed to neither be moved or collected automatically. OutlivesContext = 1, } /// Creates a static, null-terminated Rust string, and /// returns a ` *const libc::c_char` pointer to it. /// /// Useful for API invocations that require a static C string #[macro_export] macro_rules! static_char { ($lit:expr) => { concat!($lit, "\0").as_ptr() as *const libc::c_char } } unsafe fn uw(outer: *mut Context) -> *mut c::ffi::ImageflowContext{ if outer.is_null(){ ptr::null_mut() }else { (&mut *outer).flow_c() } } /// Creates and returns an imageflow context. /// An imageflow context is required for all other imageflow API calls. /// /// An imageflow context tracks /// * error state /// * error messages /// * stack traces for errors (in C land, at least) /// * context-managed memory allocations /// * performance profiling information /// /// **Contexts are not thread-safe!** Once you create a context, *you* are responsible for ensuring that it is never involved in two overlapping API calls. /// /// Returns a null pointer if allocation fails. #[no_mangle] pub unsafe extern "C" fn imageflow_context_create() -> *mut Context { Context::abi_create_boxed().map(|b| Box::into_raw(b)).unwrap_or(std::ptr::null_mut()) } /// Begins the process of destroying the context, yet leaves error information intact /// so that any errors in the tear-down process can be /// debugged with imageflow_context_error_and_stacktrace. /// /// Returns true if no errors occurred. Returns false if there were tear-down issues. /// /// *Behavior is undefined if context is a null or invalid ptr.* #[no_mangle] pub unsafe extern "C" fn imageflow_context_begin_terminate(context: *mut Context) -> bool { if context.is_null() { //Is this the best solution? Failing early is best for detecting bugs... Or we could just let null access do its thing. //but it may wait until farther up the call stack to actually try to dereference data... :( let _ = std::io::stderr().write_all(b"A null pointer was provided to imageflow_context_begin_terminate. Terminating process.\n"); std::process::exit(70); }else{ (&mut *context).abi_begin_terminate() } } /// Destroys the imageflow context and frees the context object. /// Only use this with contexts created using imageflow_context_create /// /// Behavior is undefined if context is a null or invalid ptr; may segfault on free(NULL); #[no_mangle] pub unsafe extern "C" fn imageflow_context_destroy(context: *mut Context) { let _ = Box::from_raw(context); } #[test] fn test_create_destroy() { exercise_create_destroy(); } pub fn exercise_create_destroy() { unsafe { let c = imageflow_context_create(); assert!(!c.is_null()); assert!(imageflow_context_begin_terminate(c)); imageflow_context_destroy(c); } } /// Returns true if the context is in an error state. You must immediately deal with the error, /// as subsequent API calls will fail or cause undefined behavior until the error state is cleared /// /// Behavior is undefined if `context` is a null or invalid ptr; segfault likely. #[no_mangle] pub unsafe extern "C" fn imageflow_context_has_error(context: *mut Context) -> bool { (&*context).error().abi_has_error() } /// Clear the error state. This assumes that you know which API call failed and the problem has /// been resolved. Don't use this unless you're sure you've accounted for all possible /// inconsistent state (and fully understand the code paths that led to the error). /// /// Behavior is undefined if `context` is a null or invalid ptr; segfault likely. #[no_mangle] pub unsafe extern "C" fn imageflow_context_clear_error(context: *mut Context) { (&mut *context).error_mut().abi_clear_error() } /// Prints the error messages and stacktrace to the given buffer in UTF-8 form; writes a null /// character to terminate the string, and *ALSO* returns the number of bytes written. /// /// /// Happy(ish) path: Returns the length of the error message written to the buffer. /// Sad path: Returns -1 if buffer_length was too small or buffer was nullptr. /// full_file_path, if true, will display the directory associated with the files in each stack frame. /// /// Please be accurate with the buffer length, or a buffer overflow will occur. /// /// Behavior is undefined if `context` is a null or invalid ptr; segfault likely. #[no_mangle] pub unsafe extern "C" fn imageflow_context_error_and_stacktrace(context: *mut Context, buffer: *mut libc::c_char, buffer_length: libc::size_t, full_file_path: bool) -> i64 { ffi::flow_context_error_and_stacktrace(uw(context), buffer as *mut u8, buffer_length, full_file_path) } /// Returns the numeric code associated with the error. /// /// ## Error codes /// /// * 0 - No error condition. /// * 10 - Out Of Memory condition (malloc/calloc/realloc failed). /// * 20 - I/O error /// * 30 - Invalid internal state (assertion failed; you found a bug). /// * 40 - Error: Not implemented. (Feature not implemented). /// * 50 - Invalid argument provided /// * 51 - Null argument provided /// * 52 - Invalid dimensions /// * 53 - Unsupported pixel format /// * 54 - Item does not exist /// * 60 - Image decoding failed /// * 61 - Image encoding failed /// * 70 - Graph invalid /// * 71 - Graph is cyclic /// * 72 - Invalid inputs to node /// * 73 - Maximum graph passes exceeded /// * 1024 - Other error; something else happened /// * 1025 through 2147483647 are reserved for user-defined errors /// /// /// Behavior is undefined if `context` is a null or invalid ptr; segfault likely. #[no_mangle] pub unsafe extern "C" fn imageflow_context_error_code(context: *mut Context) -> i32 { (&mut *context).error_mut().abi_error_code() } /// Prints the error to stderr and exits the process if an error has been raised on the context. /// If no error is present, the function returns false. /// /// Behavior is undefined if `context` is a null or invalid ptr; segfault likely. /// /// THIS PRINTS DIRECTLY TO STDERR! Do not use in any kind of service! Command-line usage only! #[no_mangle] pub unsafe extern "C" fn imageflow_context_print_and_exit_if_error(context: *mut Context) -> bool { (&mut *context).error_mut().abi_abort_and_print_on_error() } /// /// Raises an error on the context. /// /// Returns `true` on success, `false` if an error was already present. /// /// Designed to be safe(ish) for use in out-of-memory scenarios; no additional allocations are made. /// /// See `imageflow_context_error_code` for a list of error codes. /// /// /// # Expectations /// /// * Strings `message` and `function_name`, and `filename` should be null-terminated UTF-8 strings. /// * The lifetime of `message` is expected to exceed the duration of this function call. /// * The lifetime of `filename` and `function_name` (if provided), is expected to match or exceed the lifetime of `context`. /// * You may provide a null value for `filename` or `function_name`, but for the love of puppies, /// don't provide a dangling or invalid pointer, that will segfault... a long time later. /// /// # Caveats /// /// * You cannot raise a second error until the first has been cleared with /// `imageflow_context_clear_error`. You'll be ignored, as will future /// `imageflow_add_to_callstack` invocations. /// * Behavior is undefined if `context` is a null or invalid ptr; segfault likely. /// * Behavior is undefined if `message` is an invalid ptr; immediate segfault likely. /// * If you provide an error code of zero (why?!), a different error code will be provided. #[no_mangle] pub unsafe extern "C" fn imageflow_context_raise_error(context: *mut Context, error_code: i32, message: *const libc::c_char, filename: *const libc::c_char, line: i32, function_name: *const libc::c_char) -> bool { let message_cstr = if message.is_null() { None } else { Some(CStr::from_ptr(message))}; let filename_cstr = if filename.is_null() { None } else { Some(CStr::from_ptr(filename))}; let function_name_cstr = if function_name.is_null() { None } else { Some(CStr::from_ptr(function_name))}; let line_optional = if line >= 0 { Some(line)} else { None }; (&mut *context).error_mut().abi_raise_error_c_style(error_code, message_cstr, filename_cstr, line_optional, function_name_cstr) } /// /// Adds the given filename, line number, and function name to the call stack. /// Strings `function_name`, and `filename` should be null-terminated UTF-8 strings who will outlive `context` /// /// Returns `true` if add was successful. /// /// # Will fail and return false if... /// /// * You haven't previously called `imageflow_context_raise_error` /// * You tried to raise a second error without clearing the first one. Call will be ignored. /// * You've exceeded the capacity of the call stack (which, at one point, was 14). But this /// category of failure is acceptable. /// /// /// # Expectations /// /// * An error has been raised. /// * You may provide a null value for `filename` or `function_name`, but for the love of puppies, /// don't provide a dangling or invalid pointer, that will segfault... a long time later. /// * The lifetime of `file` and `function_name` (if provided), is expected to match /// or exceed the lifetime of `context`. /// * All strings must be null-terminated, C-style, valid UTF-8. /// /// # Caveats /// /// * Behavior is undefined if `context` is a null or invalid ptr; segfault likely. /// #[no_mangle] pub unsafe extern "C" fn imageflow_context_add_to_callstack(context: *mut Context, filename: *const libc::c_char, line: i32, function_name: *const libc::c_char) -> bool { let filename_cstr = if filename.is_null() { None } else { Some(CStr::from_ptr(filename))}; let function_name_cstr = if function_name.is_null() { None } else { Some(CStr::from_ptr(function_name))}; let line_optional = if line >= 0 { Some(line)} else { None }; (&mut *context).error_mut().abi_add_to_callstack_c_style(filename_cstr, line_optional, function_name_cstr) } #[test] pub fn test_error_handling() { exercise_error_handling(); } pub fn exercise_error_handling() { unsafe { let c = imageflow_context_create(); assert!(!c.is_null()); assert!(!imageflow_context_has_error(c)); assert_eq!(imageflow_context_error_code(c), 0); // While the strings are static, the CStrings are not. // But they will persist until the end of the block, which is all we need let message = static_char!("Test message"); let filename = static_char!(file!()); let function_name = static_char!("test_error_handling"); // Let's raise a nice error assert!(imageflow_context_raise_error(c, 1025, message, filename, 335, function_name)); // Check it registered assert!(imageflow_context_has_error(c)); assert_eq!(imageflow_context_error_code(c), 1025); // Add a backtrace assert!(imageflow_context_add_to_callstack(c, filename, 342, ptr::null())); assert!(imageflow_context_add_to_callstack(c, filename, 20, ptr::null())); assert!(imageflow_context_add_to_callstack(c, ptr::null(), 0, ptr::null())); // Let's see how it handles a insufficient buffer let mut tiny_buf: [i8; 30] = [0; 30]; assert_eq!(-1, imageflow_context_error_and_stacktrace(c, &mut tiny_buf[0], 30, true)); // And check the output looks right let mut buf: [u8; 2048] = [0; 2048]; let buf_used = imageflow_context_error_and_stacktrace(c, std::mem::transmute(&mut buf[0]), 2048, true); assert!(buf_used >= 0); let actual_string = ::std::str::from_utf8(&buf[0..buf_used as usize] ).unwrap(); // let expected_string = "User defined error : Test message\nsrc/lib.rs:335: in function test_error_handling\nsrc/lib.rs:342: in function (unknown)\nsrc/lib.rs:20: in function (unknown)\n(unknown):0: in function (unknown)\n"; assert!(actual_string.starts_with("User defined error : Test message\nsrc")); // raising a second error should fail assert!(!imageflow_context_raise_error(c, 1025, message, filename, line!() as i32, ptr::null())); // as should trying to add to the call stack assert!(!imageflow_context_add_to_callstack(c, filename, line!() as i32, ptr::null())); assert!(!imageflow_context_add_to_callstack(c, filename, line!() as i32, ptr::null())); // Clearing the error should work imageflow_context_clear_error(c); assert!(!imageflow_context_has_error(c)); assert_eq!(imageflow_context_error_code(c), 0); // And it should be possible to report another error assert!(imageflow_context_raise_error(c, 1025, message, ptr::null(), 1, ptr::null())); imageflow_context_clear_error(c); // And cleanup should go smoothly assert!(imageflow_context_begin_terminate(c)); imageflow_context_destroy(c); } } /// /// Writes fields from the given imageflow_json_response to the locations referenced. /// The buffer pointer sent out will be a UTF-8 byte array of the given length (not null-terminated). It will /// also become invalid if the JsonResponse associated is freed, or if the context is destroyed. /// #[no_mangle] pub unsafe extern fn imageflow_json_response_read(context: *mut Context, response_in: *const JsonResponse, status_code_out: *mut i64, buffer_utf8_no_nulls_out: *mut *const u8, buffer_size_out: *mut libc::size_t) -> bool { if context.is_null() { return false; } if response_in.is_null() { imageflow_context_raise_error(context, 51, static_char!("response_in is null"), static_char!(file!()), line!() as i32, ptr::null()); return false; } if !status_code_out.is_null() { *status_code_out = (*response_in).status_code; } if !buffer_utf8_no_nulls_out.is_null() { *buffer_utf8_no_nulls_out = (*response_in).buffer_utf8_no_nulls; } if !buffer_size_out.is_null() { *buffer_size_out = (*response_in).buffer_size; } return true; } /// Frees memory associated with the given object (and owned objects) after /// running any owned or attached destructors. Returns false if something went wrong during tear-down. /// /// Returns true if the object to destroy is a null pointer, or if tear-down was successful. /// /// Behavior is undefined if the pointer is dangling or not a valid memory reference. /// Although certain implementations catch /// some kinds of invalid pointers, a segfault is likely in future revisions). /// /// Behavior is undefined if the context provided does not match the context with which the /// object was created. /// /// Behavior is undefined if `context` is a null or invalid ptr; segfault likely. /// #[no_mangle] pub unsafe extern "C" fn imageflow_json_response_destroy(context: *mut Context, response: *mut JsonResponse) -> bool { ffi::flow_destroy(uw(context), response as *mut libc::c_void, ptr::null(), 0) } /// /// Sends a JSON message to the imageflow_context /// /// The context is provided `method`, which determines which code path will be used to /// process the provided JSON data and compose a response. /// /// * `method` and `json_buffer` are only borrowed for the duration of the function call. You are /// responsible for their cleanup (if necessary - static strings are handy for things like /// `method`). /// * `method` should be a UTF-8 null-terminated string. /// `json_buffer` should be a UTF-8 encoded buffer (not null terminated) of length json_buffer_size. /// /// The function will return NULL if a JSON response could not be allocated (or if some other /// bug occurred). If a null pointer is returned, consult the standard error methods of `context` /// for more detail. /// /// The response can be cleaned up with `imageflow_json_response_destroy` /// /// /// Behavior is undefined if `context` is a null or invalid ptr; segfault likely. #[no_mangle] #[allow(unused_variables)] pub unsafe extern "C" fn imageflow_context_send_json(context: *mut Context, method: *const libc::c_char, json_buffer: *const u8, json_buffer_size: libc::size_t) -> *const JsonResponse { imageflow_send_json(context, ptr::null_mut(), ptr::null_mut(), method, json_buffer, json_buffer_size) } /// /// Sends a JSON message to the imageflow_job /// /// The recipient is provided `method`, which determines which code path will be used to /// process the provided JSON data and compose a response. /// /// * `method` and `json_buffer` are only borrowed for the duration of the function call. You are /// responsible for their cleanup (if necessary - static strings are handy for things like /// `method`). /// * `method` should be a UTF-8 null-terminated string. /// `json_buffer` should be a UTF-8 encoded buffer (not null terminated) of length json_buffer_size. /// /// The function will return NULL if a JSON response could not be allocated (or if some other /// bug occurred). If a null pointer is returned, consult the standard error methods of `context` /// for more detail. /// /// The response can be cleaned up with `imageflow_json_response_destroy` /// /// Behavior is undefined if `context` is a null or invalid ptr; segfault likely. #[no_mangle] #[allow(unused_variables)] pub unsafe extern "C" fn imageflow_job_send_json(context: *mut Context, job: *mut Job, method: *const libc::c_char, json_buffer: *const libc::uint8_t, json_buffer_size: libc::size_t) -> *const JsonResponse { imageflow_send_json(context, job, ptr::null_mut(), method, json_buffer, json_buffer_size) } /// /// Sends a JSON message to one of 3 recipients. /// /// 1. `imageflow_context`, If both `job` and `io` are both null. Required. /// 2. `imageflow_job`, if only `io` is null. /// 3. `imageflow_io`, if `io` is not null. `job` is ignored. /// /// The recipient is then provided `method`, which determines which code path will be used to /// process the provided JSON data and compose a response. /// /// * `method` and `json_buffer` are only borrowed for the duration of the function call. You are /// responsible for their cleanup (if necessary - static strings are handy for things like /// `method`). /// /// The function will return NULL if a JSON response could not be allocated (or if some other /// bug occurred). If a null pointer is returned, consult the standard error methods of `context` /// for more detail. /// /// The response can be cleaned up with `imageflow_json_response_destroy` /// /// Behavior is undefined if `context` is a null or invalid ptr; segfault likely. #[allow(unused_variables)] unsafe fn imageflow_send_json(context: *mut Context, job: *mut Job, io: *mut JobIo, method: *const i8, json_buffer: *const libc::uint8_t, json_buffer_size: libc::size_t) -> *const JsonResponse { //TODO: check for nulls in json_BUFFER AND METHOD // It doesn't appear that this allocates anything // But I'm curious how that Rust knows not to deallocate the string itself // Oh. It's borrowed. CStr is borrowed from the pointer. Pointers don't have lifetimes. &str is borrowed from CStr let method_str = ::std::ffi::CStr::from_ptr(method as *const i8).to_str().unwrap(); //TODO, throw exception if not UTF-8, argument error let json_bytes = std::slice::from_raw_parts(json_buffer, json_buffer_size); // TODO: possibly iterate access to force segfaults earlier? let mut ctx = &mut *context; let response; if job == ptr::null_mut() && io == ptr::null_mut() { response = ctx.message(method_str, json_bytes).unwrap(); //Unwrap for invalid context } else if io == ptr::null_mut() { //TODO: perform lookup instead of dereferencing! response =(&mut *job).message(method_str, json_bytes).unwrap(); } else{ panic!("Messaging JobIo not implemented"); } let ptr = ctx.create_abi_json_response(response.response_json, response.status_code); //TODO: if ptr == null, //Handle alloc failure ptr } #[test] fn test_message() { exercise_json_message(); } pub fn exercise_json_message() { unsafe { let c = imageflow_context_create(); assert!(!c.is_null()); let method_in = static_char!("brew_coffee"); let json_in = "{}"; let expected_response = c::JsonResponse::teapot(); let expected_json_out = ::std::str::from_utf8( expected_response.response_json.as_ref()).unwrap(); let expected_reponse_status = expected_response.status_code; let response = imageflow_send_json(c, ptr::null_mut(), ptr::null_mut(), method_in, json_in.as_ptr(), json_in.len()); assert!(response != ptr::null()); let mut json_out_ptr: *const u8 = ptr::null_mut(); let mut json_out_size: usize = 0; let mut json_status_code: i64 = 0; assert!(imageflow_json_response_read(c, response, &mut json_status_code, &mut json_out_ptr, &mut json_out_size)); let json_out_str = ::std::str::from_utf8(std::slice::from_raw_parts(json_out_ptr, json_out_size)).unwrap(); assert_eq!(json_out_str, expected_json_out); assert_eq!(json_status_code, expected_reponse_status); imageflow_context_destroy(c); } } /// /// Creates an imageflow_io object to wrap a filename. /// /// The filename should be a null-terminated string. It should be written in codepage used by your operating system for handling `fopen` calls. /// https://msdn.microsoft.com/en-us/library/yeby3zcb.aspx /// /// If the filename is fopen compatible, you're probably OK. /// /// As always, `mode` is not enforced except for the file open flags. /// #[no_mangle] #[allow(unused_variables)] pub unsafe extern "C" fn imageflow_io_create_for_file(context: *mut Context, mode: IoMode, filename: *const libc::c_char, cleanup: CleanupWith) -> *mut JobIo { // TODO: validate that 'owner' is capable of being an owner ffi::flow_io_create_for_file(uw(context), std::mem::transmute(mode), filename, uw(context) as *const libc::c_void) } /// /// Creates an imageflow_io structure for reading from the provided buffer. /// You are ALWAYS responsible for freeing the memory provided in accordance with the Lifetime value. /// If you specify OutlivesFunctionCall, then the buffer will be copied. /// /// #[no_mangle] #[allow(unused_variables)] pub unsafe extern "C" fn imageflow_io_create_from_buffer(context: *mut Context, buffer: *const u8, buffer_byte_count: libc::size_t, lifetime: Lifetime, cleanup: CleanupWith) -> *mut JobIo { let mut final_buffer = buffer; if lifetime == Lifetime::OutlivesFunctionCall { let buf : *mut u8 = c::ffi::flow_context_calloc(uw(context), 1, buffer_byte_count, ptr::null(), uw(context) as *const libc::c_void, ptr::null(), 0) as *mut u8 ; if buf.is_null() { //TODO: raise OOM return ptr::null_mut(); } ptr::copy_nonoverlapping(buffer, buf, buffer_byte_count); final_buffer = buf; } ffi::flow_io_create_from_memory(uw(context), std::mem::transmute(IoMode::ReadSeekable), final_buffer, buffer_byte_count, uw(context) as *mut libc::c_void, ptr::null()) } /// /// Creates an imageflow_io structure for writing to an expanding memory buffer. /// /// Reads/seeks, are, in theory, supported, but unless you've written, there will be nothing to read. /// /// The I/O structure and buffer will be freed with the context. /// /// /// Returns null if allocation failed; check the context for error details. #[no_mangle] #[allow(unused_variables)] pub unsafe extern "C" fn imageflow_io_create_for_output_buffer(context: *mut Context) -> *mut JobIo { // The current implementation of output buffer only sheds its actual buffer with the context. // No need for the shell to have an earlier lifetime for mem reasons. ffi::flow_io_create_for_output_buffer(uw(context), uw(context) as *mut libc::c_void) } // Returns false if the flow_io struct is disposed or not an output buffer type (or for any other error) // /// /// Provides access to the underlying buffer for the given imageflow_io object. /// /// Ensure your length variable always holds 64-bits. /// #[no_mangle] pub unsafe extern "C" fn imageflow_io_get_output_buffer(context: *mut Context, io: *mut JobIo, result_buffer: *mut *const u8, result_buffer_length: *mut libc::size_t) -> bool { let mut result_len: usize = 0; let b = ffi::flow_io_get_output_buffer(uw(context), io, result_buffer, &mut result_len); (* result_buffer_length) = result_len; b } /// /// Provides access to the underlying buffer for the given imageflow_io object. /// /// Ensure your length variable always holds 64-bits /// #[no_mangle] pub unsafe extern "C" fn imageflow_job_get_output_buffer_by_id(context: *mut Context, job: *mut Job, io_id: i32, result_buffer: *mut *const u8, result_buffer_length: *mut libc::size_t) -> bool { (&*job).get_io(io_id).map(|io| imageflow_io_get_output_buffer(context, io, result_buffer, result_buffer_length) ).unwrap_or_else(|e| { e.write_to_context_ptr(context); false }) } /// /// Creates an imageflow_job, which permits the association of imageflow_io instances with /// numeric identifiers and provides a 'sub-context' for job execution /// #[no_mangle] pub unsafe extern "C" fn imageflow_job_create(context: *mut Context) -> *mut Job { &mut *(&*context).create_job() } /// /// Looks up the imageflow_io pointer from the provided io_id /// #[no_mangle] pub unsafe extern "C" fn imageflow_job_get_io(context: *mut Context, job: *mut Job, io_id: i32) -> *mut JobIo { (&*job).get_io(io_id).unwrap_or(ptr::null_mut()) } /// /// Associates the imageflow_io object with the job and the assigned io_id. /// /// The io_id will correspond with io_id in the graph /// /// direction is in or out. #[no_mangle] pub unsafe extern "C" fn imageflow_job_add_io(context: *mut Context, job: *mut Job, io: *mut JobIo, io_id: i32, direction: Direction) -> bool { (&mut *job).add_io(io, io_id, std::mem::transmute(direction)) .map(|_| true) .unwrap_or_else(|e| { e.write_to_context_ptr(context); false }) } /// /// Destroys the provided imageflow_job /// #[no_mangle] pub unsafe extern "C" fn imageflow_job_destroy(context: *mut Context, job: *mut Job) -> bool { (&*context).abi_try_remove_job(job) } /// /// Allocates zeroed memory that will be freed with the context. /// /// * filename/line may be used for debugging purposes. They are optional. Provide null/-1 to skip. /// * `filename` should be an null-terminated UTF-8 or ASCII string which will outlive the context. /// /// Returns null(0) on failure. /// #[no_mangle] pub unsafe extern "C" fn imageflow_context_memory_allocate(context: *mut Context, bytes: libc::size_t, filename: *const libc::c_char, line: i32) -> *mut libc::c_void { ffi::flow_context_calloc(uw(context), 1, bytes, ptr::null(), uw(context) as *const libc::c_void, filename, line) } /// /// Frees memory allocated with imageflow_context_memory_allocate early. /// /// * filename/line may be used for debugging purposes. They are optional. Provide null/-1 to skip. /// * `filename` should be an null-terminated UTF-8 or ASCII string which will outlive the context. /// /// Returns false on failure. /// #[no_mangle] pub unsafe extern "C" fn imageflow_context_memory_free(context: *mut Context, pointer: *mut libc::c_void, filename: *const libc::c_char, line: i32) -> bool { ffi::flow_destroy(uw(context), pointer, filename, line) } #[test] fn test_allocate_free() { unsafe{ let c = imageflow_context_create(); let bytes = 100; let ptr = imageflow_context_memory_allocate(c, bytes, static_char!(file!()), line!() as i32) as *mut u8; assert!(ptr != ptr::null_mut()); for x in 0..bytes{ assert_eq!(*ptr.offset(x as isize), 0); } assert!(imageflow_context_memory_free(c, ptr as *mut libc::c_void, static_char!(file!()), line!() as i32)); imageflow_context_destroy(c); //imageflow_context_destroy(c); } }