rust_ethernet_ip/
ffi.rs

1#![allow(clippy::missing_safety_doc)]
2
3use crate::EipClient;
4use crate::PlcValue;
5use crate::RUNTIME;
6use lazy_static::lazy_static;
7use std::collections::HashMap;
8use std::ffi::{CStr, CString};
9use std::os::raw::{c_char, c_int};
10use std::ptr;
11use std::sync::Mutex;
12
13// FFI-specific client manager using synchronous mutex
14lazy_static! {
15    static ref FFI_CLIENTS: Mutex<HashMap<i32, EipClient>> = Mutex::new(HashMap::new());
16    static ref FFI_NEXT_ID: Mutex<i32> = Mutex::new(1);
17}
18
19/// Connect to a PLC and return a client ID
20///
21/// # Safety
22///
23/// This function is unsafe because:
24/// - `ip_address` must be a valid null-terminated C string pointer
25/// - The caller must ensure the pointer remains valid for the duration of the call
26/// - The string must contain a valid IP address format
27#[no_mangle]
28pub unsafe extern "C" fn eip_connect(ip_address: *const c_char) -> c_int {
29    if ip_address.is_null() {
30        return -1;
31    }
32
33    let ip_str = match unsafe { CStr::from_ptr(ip_address) }.to_str() {
34        Ok(s) => s,
35        Err(_) => return -1,
36    };
37
38    let client = match RUNTIME.block_on(EipClient::new(ip_str)) {
39        Ok(client) => client,
40        Err(_) => return -1,
41    };
42
43    let client_id = {
44        let mut next_id = FFI_NEXT_ID.lock().unwrap();
45        let id = *next_id;
46        *next_id += 1;
47        id
48    };
49
50    {
51        let mut clients = FFI_CLIENTS.lock().unwrap();
52        clients.insert(client_id, client);
53    }
54
55    client_id
56}
57
58/// Disconnect from a PLC
59///
60/// # Safety
61///
62/// This function is unsafe because:
63/// - `client_id` must be a valid client ID returned from `eip_connect`
64/// - The caller must not use the client_id after this call
65#[no_mangle]
66pub unsafe extern "C" fn eip_disconnect(client_id: c_int) -> c_int {
67    let mut clients = FFI_CLIENTS.lock().unwrap();
68    match clients.remove(&client_id) {
69        Some(_) => 0,
70        None => -1,
71    }
72}
73
74/// Read a boolean tag
75///
76/// # Safety
77///
78/// This function is unsafe because:
79/// - `tag_name` must be a valid null-terminated C string pointer
80/// - `result` must be a valid mutable pointer to a c_int
81/// - The caller must ensure both pointers remain valid for the duration of the call
82/// - `client_id` must be a valid client ID returned from `eip_connect`
83#[no_mangle]
84pub unsafe extern "C" fn eip_read_bool(
85    client_id: c_int,
86    tag_name: *const c_char,
87    result: *mut c_int,
88) -> c_int {
89    if tag_name.is_null() || result.is_null() {
90        return -1;
91    }
92
93    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
94        Ok(s) => s,
95        Err(_) => return -1,
96    };
97
98    let mut clients = FFI_CLIENTS.lock().unwrap();
99    match clients.get_mut(&client_id) {
100        Some(client) => match RUNTIME.block_on(client.read_tag(tag_name_str)) {
101            Ok(PlcValue::Bool(value)) => {
102                unsafe {
103                    *result = if value { 1 } else { 0 };
104                }
105                0
106            }
107            _ => -1,
108        },
109        None => -1,
110    }
111}
112
113/// Write a boolean tag
114///
115/// # Safety
116///
117/// This function is unsafe because:
118/// - `tag_name` must be a valid null-terminated C string pointer
119/// - The caller must ensure the pointer remains valid for the duration of the call
120/// - `client_id` must be a valid client ID returned from `eip_connect`
121/// - The tag name must be a valid PLC tag identifier
122#[no_mangle]
123pub unsafe extern "C" fn eip_write_bool(
124    client_id: c_int,
125    tag_name: *const c_char,
126    value: c_int,
127) -> c_int {
128    if tag_name.is_null() {
129        return -1;
130    }
131
132    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
133        Ok(s) => s,
134        Err(_) => return -1,
135    };
136
137    let mut clients = FFI_CLIENTS.lock().unwrap();
138    match clients.get_mut(&client_id) {
139        Some(client) => {
140            let bool_value = value != 0;
141            match RUNTIME.block_on(client.write_tag(tag_name_str, PlcValue::Bool(bool_value))) {
142                Ok(_) => 0,
143                Err(_) => -1,
144            }
145        }
146        None => -1,
147    }
148}
149
150// SINT (8-bit signed integer) operations
151/// Read a signed 8-bit integer tag
152///
153/// # Safety
154///
155/// This function is unsafe because:
156/// - `tag_name` must be a valid null-terminated C string pointer
157/// - `result` must be a valid mutable pointer to an i8
158/// - The caller must ensure both pointers remain valid for the duration of the call
159/// - `client_id` must be a valid client ID returned from `eip_connect`
160#[no_mangle]
161pub unsafe extern "C" fn eip_read_sint(
162    client_id: c_int,
163    tag_name: *const c_char,
164    result: *mut i8,
165) -> c_int {
166    if tag_name.is_null() || result.is_null() {
167        return -1;
168    }
169
170    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
171        Ok(s) => s,
172        Err(_) => return -1,
173    };
174
175    let mut clients = FFI_CLIENTS.lock().unwrap();
176    match clients.get_mut(&client_id) {
177        Some(client) => match RUNTIME.block_on(client.read_tag(tag_name_str)) {
178            Ok(PlcValue::Sint(value)) => {
179                unsafe {
180                    *result = value;
181                }
182                0
183            }
184            _ => -1,
185        },
186        None => -1,
187    }
188}
189
190/// Write a signed 8-bit integer tag
191///
192/// # Safety
193///
194/// This function is unsafe because:
195/// - `tag_name` must be a valid null-terminated C string pointer
196/// - The caller must ensure the pointer remains valid for the duration of the call
197/// - `client_id` must be a valid client ID returned from `eip_connect`
198#[no_mangle]
199pub unsafe extern "C" fn eip_write_sint(
200    client_id: c_int,
201    tag_name: *const c_char,
202    value: i8,
203) -> c_int {
204    if tag_name.is_null() {
205        return -1;
206    }
207
208    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
209        Ok(s) => s,
210        Err(_) => return -1,
211    };
212
213    let mut clients = FFI_CLIENTS.lock().unwrap();
214    match clients.get_mut(&client_id) {
215        Some(client) => {
216            match RUNTIME.block_on(client.write_tag(tag_name_str, PlcValue::Sint(value))) {
217                Ok(_) => 0,
218                Err(_) => -1,
219            }
220        }
221        None => -1,
222    }
223}
224
225// INT (16-bit signed integer) operations
226#[no_mangle]
227pub unsafe extern "C" fn eip_read_int(
228    client_id: c_int,
229    tag_name: *const c_char,
230    result: *mut i16,
231) -> c_int {
232    if tag_name.is_null() || result.is_null() {
233        return -1;
234    }
235
236    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
237        Ok(s) => s,
238        Err(_) => return -1,
239    };
240
241    let mut clients = FFI_CLIENTS.lock().unwrap();
242    match clients.get_mut(&client_id) {
243        Some(client) => match RUNTIME.block_on(client.read_tag(tag_name_str)) {
244            Ok(PlcValue::Int(value)) => {
245                unsafe {
246                    *result = value;
247                }
248                0
249            }
250            _ => -1,
251        },
252        None => -1,
253    }
254}
255
256#[no_mangle]
257pub unsafe extern "C" fn eip_write_int(
258    client_id: c_int,
259    tag_name: *const c_char,
260    value: i16,
261) -> c_int {
262    if tag_name.is_null() {
263        return -1;
264    }
265
266    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
267        Ok(s) => s,
268        Err(_) => return -1,
269    };
270
271    let mut clients = FFI_CLIENTS.lock().unwrap();
272    match clients.get_mut(&client_id) {
273        Some(client) => {
274            match RUNTIME.block_on(client.write_tag(tag_name_str, PlcValue::Int(value))) {
275                Ok(_) => 0,
276                Err(_) => -1,
277            }
278        }
279        None => -1,
280    }
281}
282
283/// Read a DINT tag
284#[no_mangle]
285pub unsafe extern "C" fn eip_read_dint(
286    client_id: c_int,
287    tag_name: *const c_char,
288    result: *mut c_int,
289) -> c_int {
290    if tag_name.is_null() || result.is_null() {
291        return -1;
292    }
293
294    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
295        Ok(s) => s,
296        Err(_) => return -1,
297    };
298
299    let mut clients = FFI_CLIENTS.lock().unwrap();
300    match clients.get_mut(&client_id) {
301        Some(client) => match RUNTIME.block_on(client.read_tag(tag_name_str)) {
302            Ok(PlcValue::Dint(value)) => {
303                unsafe {
304                    *result = value;
305                }
306                0
307            }
308            _ => -1,
309        },
310        None => -1,
311    }
312}
313
314/// Write a DINT tag
315#[no_mangle]
316pub unsafe extern "C" fn eip_write_dint(
317    client_id: c_int,
318    tag_name: *const c_char,
319    value: c_int,
320) -> c_int {
321    if tag_name.is_null() {
322        return -1;
323    }
324
325    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
326        Ok(s) => s,
327        Err(_) => return -1,
328    };
329
330    let mut clients = FFI_CLIENTS.lock().unwrap();
331    match clients.get_mut(&client_id) {
332        Some(client) => {
333            match RUNTIME.block_on(client.write_tag(tag_name_str, PlcValue::Dint(value))) {
334                Ok(_) => 0,
335                Err(_) => -1,
336            }
337        }
338        None => -1,
339    }
340}
341
342// LINT (64-bit signed integer) operations
343#[no_mangle]
344pub unsafe extern "C" fn eip_read_lint(
345    client_id: c_int,
346    tag_name: *const c_char,
347    result: *mut i64,
348) -> c_int {
349    if tag_name.is_null() || result.is_null() {
350        return -1;
351    }
352
353    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
354        Ok(s) => s,
355        Err(_) => return -1,
356    };
357
358    let mut clients = FFI_CLIENTS.lock().unwrap();
359    match clients.get_mut(&client_id) {
360        Some(client) => match RUNTIME.block_on(client.read_tag(tag_name_str)) {
361            Ok(PlcValue::Lint(value)) => {
362                unsafe {
363                    *result = value;
364                }
365                0
366            }
367            _ => -1,
368        },
369        None => -1,
370    }
371}
372
373#[no_mangle]
374pub unsafe extern "C" fn eip_write_lint(
375    client_id: c_int,
376    tag_name: *const c_char,
377    value: i64,
378) -> c_int {
379    if tag_name.is_null() {
380        return -1;
381    }
382
383    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
384        Ok(s) => s,
385        Err(_) => return -1,
386    };
387
388    let mut clients = FFI_CLIENTS.lock().unwrap();
389    match clients.get_mut(&client_id) {
390        Some(client) => {
391            match RUNTIME.block_on(client.write_tag(tag_name_str, PlcValue::Lint(value))) {
392                Ok(_) => 0,
393                Err(_) => -1,
394            }
395        }
396        None => -1,
397    }
398}
399
400// USINT (8-bit unsigned integer) operations
401#[no_mangle]
402pub unsafe extern "C" fn eip_read_usint(
403    client_id: c_int,
404    tag_name: *const c_char,
405    result: *mut u8,
406) -> c_int {
407    if tag_name.is_null() || result.is_null() {
408        return -1;
409    }
410
411    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
412        Ok(s) => s,
413        Err(_) => return -1,
414    };
415
416    let mut clients = FFI_CLIENTS.lock().unwrap();
417    match clients.get_mut(&client_id) {
418        Some(client) => match RUNTIME.block_on(client.read_tag(tag_name_str)) {
419            Ok(PlcValue::Usint(value)) => {
420                unsafe {
421                    *result = value;
422                }
423                0
424            }
425            _ => -1,
426        },
427        None => -1,
428    }
429}
430
431#[no_mangle]
432pub unsafe extern "C" fn eip_write_usint(
433    client_id: c_int,
434    tag_name: *const c_char,
435    value: u8,
436) -> c_int {
437    if tag_name.is_null() {
438        return -1;
439    }
440
441    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
442        Ok(s) => s,
443        Err(_) => return -1,
444    };
445
446    let mut clients = FFI_CLIENTS.lock().unwrap();
447    match clients.get_mut(&client_id) {
448        Some(client) => {
449            match RUNTIME.block_on(client.write_tag(tag_name_str, PlcValue::Usint(value))) {
450                Ok(_) => 0,
451                Err(_) => -1,
452            }
453        }
454        None => -1,
455    }
456}
457
458// UINT (16-bit unsigned integer) operations
459#[no_mangle]
460pub unsafe extern "C" fn eip_read_uint(
461    client_id: c_int,
462    tag_name: *const c_char,
463    result: *mut u16,
464) -> c_int {
465    if tag_name.is_null() || result.is_null() {
466        return -1;
467    }
468
469    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
470        Ok(s) => s,
471        Err(_) => return -1,
472    };
473
474    let mut clients = FFI_CLIENTS.lock().unwrap();
475    match clients.get_mut(&client_id) {
476        Some(client) => match RUNTIME.block_on(client.read_tag(tag_name_str)) {
477            Ok(PlcValue::Uint(value)) => {
478                unsafe {
479                    *result = value;
480                }
481                0
482            }
483            _ => -1,
484        },
485        None => -1,
486    }
487}
488
489#[no_mangle]
490pub unsafe extern "C" fn eip_write_uint(
491    client_id: c_int,
492    tag_name: *const c_char,
493    value: u16,
494) -> c_int {
495    if tag_name.is_null() {
496        return -1;
497    }
498
499    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
500        Ok(s) => s,
501        Err(_) => return -1,
502    };
503
504    let mut clients = FFI_CLIENTS.lock().unwrap();
505    match clients.get_mut(&client_id) {
506        Some(client) => {
507            match RUNTIME.block_on(client.write_tag(tag_name_str, PlcValue::Uint(value))) {
508                Ok(_) => 0,
509                Err(_) => -1,
510            }
511        }
512        None => -1,
513    }
514}
515
516// UDINT (32-bit unsigned integer) operations
517#[no_mangle]
518pub unsafe extern "C" fn eip_read_udint(
519    client_id: c_int,
520    tag_name: *const c_char,
521    result: *mut u32,
522) -> c_int {
523    if tag_name.is_null() || result.is_null() {
524        return -1;
525    }
526
527    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
528        Ok(s) => s,
529        Err(_) => return -1,
530    };
531
532    let mut clients = FFI_CLIENTS.lock().unwrap();
533    match clients.get_mut(&client_id) {
534        Some(client) => match RUNTIME.block_on(client.read_tag(tag_name_str)) {
535            Ok(PlcValue::Udint(value)) => {
536                unsafe {
537                    *result = value;
538                }
539                0
540            }
541            _ => -1,
542        },
543        None => -1,
544    }
545}
546
547#[no_mangle]
548pub unsafe extern "C" fn eip_write_udint(
549    client_id: c_int,
550    tag_name: *const c_char,
551    value: u32,
552) -> c_int {
553    if tag_name.is_null() {
554        return -1;
555    }
556
557    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
558        Ok(s) => s,
559        Err(_) => return -1,
560    };
561
562    let mut clients = FFI_CLIENTS.lock().unwrap();
563    match clients.get_mut(&client_id) {
564        Some(client) => {
565            match RUNTIME.block_on(client.write_tag(tag_name_str, PlcValue::Udint(value))) {
566                Ok(_) => 0,
567                Err(_) => -1,
568            }
569        }
570        None => -1,
571    }
572}
573
574// ULINT (64-bit unsigned integer) operations
575#[no_mangle]
576pub unsafe extern "C" fn eip_read_ulint(
577    client_id: c_int,
578    tag_name: *const c_char,
579    result: *mut u64,
580) -> c_int {
581    if tag_name.is_null() || result.is_null() {
582        return -1;
583    }
584
585    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
586        Ok(s) => s,
587        Err(_) => return -1,
588    };
589
590    let mut clients = FFI_CLIENTS.lock().unwrap();
591    match clients.get_mut(&client_id) {
592        Some(client) => match RUNTIME.block_on(client.read_tag(tag_name_str)) {
593            Ok(PlcValue::Ulint(value)) => {
594                unsafe {
595                    *result = value;
596                }
597                0
598            }
599            _ => -1,
600        },
601        None => -1,
602    }
603}
604
605#[no_mangle]
606pub unsafe extern "C" fn eip_write_ulint(
607    client_id: c_int,
608    tag_name: *const c_char,
609    value: u64,
610) -> c_int {
611    if tag_name.is_null() {
612        return -1;
613    }
614
615    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
616        Ok(s) => s,
617        Err(_) => return -1,
618    };
619
620    let mut clients = FFI_CLIENTS.lock().unwrap();
621    match clients.get_mut(&client_id) {
622        Some(client) => {
623            match RUNTIME.block_on(client.write_tag(tag_name_str, PlcValue::Ulint(value))) {
624                Ok(_) => 0,
625                Err(_) => -1,
626            }
627        }
628        None => -1,
629    }
630}
631
632/// Read a REAL tag
633#[no_mangle]
634pub unsafe extern "C" fn eip_read_real(
635    client_id: c_int,
636    tag_name: *const c_char,
637    result: *mut f64,
638) -> c_int {
639    if tag_name.is_null() || result.is_null() {
640        return -1;
641    }
642
643    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
644        Ok(s) => s,
645        Err(_) => return -1,
646    };
647
648    let mut clients = FFI_CLIENTS.lock().unwrap();
649    match clients.get_mut(&client_id) {
650        Some(client) => match RUNTIME.block_on(client.read_tag(tag_name_str)) {
651            Ok(PlcValue::Real(value)) => {
652                unsafe {
653                    *result = value as f64;
654                }
655                0
656            }
657            _ => -1,
658        },
659        None => -1,
660    }
661}
662
663/// Write a REAL tag
664#[no_mangle]
665pub unsafe extern "C" fn eip_write_real(
666    client_id: c_int,
667    tag_name: *const c_char,
668    value: f64,
669) -> c_int {
670    if tag_name.is_null() {
671        return -1;
672    }
673
674    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
675        Ok(s) => s,
676        Err(_) => return -1,
677    };
678
679    let mut clients = FFI_CLIENTS.lock().unwrap();
680    match clients.get_mut(&client_id) {
681        Some(client) => {
682            match RUNTIME.block_on(client.write_tag(tag_name_str, PlcValue::Real(value as f32))) {
683                Ok(_) => 0,
684                Err(_) => -1,
685            }
686        }
687        None => -1,
688    }
689}
690
691// LREAL (64-bit double precision) operations
692#[no_mangle]
693pub unsafe extern "C" fn eip_read_lreal(
694    client_id: c_int,
695    tag_name: *const c_char,
696    result: *mut f64,
697) -> c_int {
698    if tag_name.is_null() || result.is_null() {
699        return -1;
700    }
701
702    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
703        Ok(s) => s,
704        Err(_) => return -1,
705    };
706
707    let mut clients = FFI_CLIENTS.lock().unwrap();
708    match clients.get_mut(&client_id) {
709        Some(client) => match RUNTIME.block_on(client.read_tag(tag_name_str)) {
710            Ok(PlcValue::Lreal(value)) => {
711                unsafe {
712                    *result = value;
713                }
714                0
715            }
716            _ => -1,
717        },
718        None => -1,
719    }
720}
721
722#[no_mangle]
723pub unsafe extern "C" fn eip_write_lreal(
724    client_id: c_int,
725    tag_name: *const c_char,
726    value: f64,
727) -> c_int {
728    if tag_name.is_null() {
729        return -1;
730    }
731
732    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
733        Ok(s) => s,
734        Err(_) => return -1,
735    };
736
737    let mut clients = FFI_CLIENTS.lock().unwrap();
738    match clients.get_mut(&client_id) {
739        Some(client) => {
740            match RUNTIME.block_on(client.write_tag(tag_name_str, PlcValue::Lreal(value))) {
741                Ok(_) => 0,
742                Err(_) => -1,
743            }
744        }
745        None => -1,
746    }
747}
748
749/// Read a STRING tag
750///
751/// # Safety
752///
753/// This function is unsafe because:
754/// - `tag_name` must be a valid null-terminated C string pointer
755/// - `result` must be a valid mutable pointer to a buffer of at least `max_length` bytes
756/// - The caller must ensure both pointers remain valid for the duration of the call
757/// - `client_id` must be a valid client ID returned from `eip_connect`
758/// - `max_length` must be positive and represent the actual buffer size
759#[no_mangle]
760pub unsafe extern "C" fn eip_read_string(
761    client_id: c_int,
762    tag_name: *const c_char,
763    result: *mut c_char,
764    max_length: c_int,
765) -> c_int {
766    if tag_name.is_null() || result.is_null() || max_length <= 0 {
767        return -1;
768    }
769
770    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
771        Ok(s) => s,
772        Err(_) => return -1,
773    };
774
775    let mut clients = FFI_CLIENTS.lock().unwrap();
776    let client = match clients.get_mut(&client_id) {
777        Some(client) => client,
778        None => return -1,
779    };
780
781    let value = match RUNTIME.block_on(client.read_tag(tag_name_str)) {
782        Ok(PlcValue::String(value)) => value,
783        Ok(_) => return -1,  // Wrong data type
784        Err(_) => return -1, // Error reading tag
785    };
786
787    let c_string = match CString::new(value) {
788        Ok(s) => s,
789        Err(_) => return -1,
790    };
791
792    let bytes = c_string.as_bytes_with_nul();
793    if bytes.len() > max_length as usize {
794        return -1; // String too long
795    }
796
797    unsafe {
798        ptr::copy_nonoverlapping(bytes.as_ptr(), result as *mut u8, bytes.len());
799    }
800    0
801}
802
803/// Write a STRING tag
804#[no_mangle]
805pub unsafe extern "C" fn eip_write_string(
806    client_id: c_int,
807    tag_name: *const c_char,
808    value: *const c_char,
809) -> c_int {
810    if tag_name.is_null() || value.is_null() {
811        return -1;
812    }
813
814    let tag_name_str = match unsafe { CStr::from_ptr(tag_name) }.to_str() {
815        Ok(s) => s,
816        Err(_) => return -1,
817    };
818
819    let value_str = match unsafe { CStr::from_ptr(value) }.to_str() {
820        Ok(s) => s,
821        Err(_) => return -1,
822    };
823
824    let mut clients = FFI_CLIENTS.lock().unwrap();
825    let client = match clients.get_mut(&client_id) {
826        Some(client) => client,
827        None => return -1,
828    };
829
830    match RUNTIME.block_on(client.write_tag(tag_name_str, PlcValue::String(value_str.to_string())))
831    {
832        Ok(_) => 0,
833        Err(_) => -1,
834    }
835}
836
837// UDT operations
838#[no_mangle]
839pub unsafe extern "C" fn eip_read_udt(
840    _client_id: c_int,
841    _tag_name: *const c_char,
842    _result: *mut c_char,
843    _max_size: c_int,
844) -> c_int {
845    // For now, return error - UDT support can be added later
846    -1
847}
848
849#[no_mangle]
850pub unsafe extern "C" fn eip_write_udt(
851    _client_id: c_int,
852    _tag_name: *const c_char,
853    _value: *const c_char,
854    _size: c_int,
855) -> c_int {
856    // For now, return error - UDT support can be added later
857    -1
858}
859
860// Tag discovery and metadata
861#[no_mangle]
862pub unsafe extern "C" fn eip_discover_tags(_client_id: c_int) -> c_int {
863    // Return success for now - can implement tag discovery later
864    0
865}
866
867#[no_mangle]
868pub unsafe extern "C" fn eip_get_tag_metadata(
869    _client_id: c_int,
870    _tag_name: *const c_char,
871    _metadata: *mut u8,
872) -> c_int {
873    // For now, return error - metadata support can be added later
874    -1
875}
876
877// Configuration
878#[no_mangle]
879pub unsafe extern "C" fn eip_set_max_packet_size(_client_id: c_int, _size: c_int) -> c_int {
880    // Return success for now - packet size configuration can be added later
881    0
882}
883
884// Health checks
885#[no_mangle]
886pub unsafe extern "C" fn eip_check_health(client_id: c_int, is_healthy: *mut c_int) -> c_int {
887    if is_healthy.is_null() {
888        return -1;
889    }
890
891    let clients = FFI_CLIENTS.lock().unwrap();
892    match clients.get(&client_id) {
893        Some(_) => {
894            unsafe {
895                *is_healthy = 1;
896            }
897            0
898        }
899        None => {
900            unsafe {
901                *is_healthy = 0;
902            }
903            -1
904        }
905    }
906}
907
908#[no_mangle]
909pub unsafe extern "C" fn eip_check_health_detailed(
910    client_id: c_int,
911    is_healthy: *mut c_int,
912) -> c_int {
913    // Use the same logic as basic health check for now
914    eip_check_health(client_id, is_healthy)
915}
916
917// Batch operations implementation
918#[no_mangle]
919pub unsafe extern "C" fn eip_read_tags_batch(
920    client_id: c_int,
921    tag_names: *mut *const c_char,
922    tag_count: c_int,
923    results: *mut c_char,
924    results_capacity: c_int,
925) -> c_int {
926    if tag_names.is_null() || results.is_null() || tag_count <= 0 {
927        return -1;
928    }
929
930    let mut clients = FFI_CLIENTS.lock().unwrap();
931    let client = match clients.get_mut(&client_id) {
932        Some(client) => client,
933        None => return -1,
934    };
935
936    // Convert C strings to Rust strings
937    let mut tag_name_strs = Vec::new();
938    unsafe {
939        for i in 0..tag_count {
940            let tag_name_ptr = *tag_names.offset(i as isize);
941            if tag_name_ptr.is_null() {
942                return -1;
943            }
944            let tag_name = match CStr::from_ptr(tag_name_ptr).to_str() {
945                Ok(s) => s,
946                Err(_) => return -1,
947            };
948            tag_name_strs.push(tag_name);
949        }
950    }
951
952    // Execute batch read
953    let batch_results = RUNTIME.block_on(async { client.read_tags_batch(&tag_name_strs).await });
954
955    let results_data = match batch_results {
956        Ok(results) => {
957            // Simple format: "tag1:value1;tag2:value2;..."
958            let mut formatted = String::new();
959            for (i, (tag_name, result)) in results.iter().enumerate() {
960                if i > 0 {
961                    formatted.push(';');
962                }
963                formatted.push_str(tag_name);
964                formatted.push(':');
965                match result {
966                    Ok(value) => formatted.push_str(&format!("{:?}", value)),
967                    Err(e) => formatted.push_str(&format!("ERROR:{}", e)),
968                }
969            }
970            formatted
971        }
972        Err(_) => return -1,
973    };
974
975    // Copy results to output buffer
976    let results_bytes = results_data.as_bytes();
977    if results_bytes.len() >= results_capacity as usize {
978        return -1;
979    }
980
981    unsafe {
982        std::ptr::copy_nonoverlapping(
983            results_bytes.as_ptr(),
984            results as *mut u8,
985            results_bytes.len(),
986        );
987        *results.add(results_bytes.len()) = 0; // Null terminate
988    }
989
990    0
991}
992
993#[no_mangle]
994pub unsafe extern "C" fn eip_write_tags_batch(
995    client_id: c_int,
996    tag_values: *const c_char,
997    tag_count: c_int,
998    results: *mut c_char,
999    results_capacity: c_int,
1000) -> c_int {
1001    if tag_values.is_null() || results.is_null() || tag_count <= 0 {
1002        return -1;
1003    }
1004
1005    let mut clients = FFI_CLIENTS.lock().unwrap();
1006    let _client = match clients.get_mut(&client_id) {
1007        Some(client) => client,
1008        None => return -1,
1009    };
1010
1011    // Parse input (simplified implementation)
1012    let _input_str = unsafe {
1013        match CStr::from_ptr(tag_values).to_str() {
1014            Ok(s) => s,
1015            Err(_) => return -1,
1016        }
1017    };
1018
1019    // For now, return not implemented
1020    // TODO: Parse input and execute batch write
1021    let results_data = "ERROR:Batch write not yet implemented in FFI";
1022    let results_bytes = results_data.as_bytes();
1023
1024    if results_bytes.len() >= results_capacity as usize {
1025        return -1;
1026    }
1027
1028    unsafe {
1029        std::ptr::copy_nonoverlapping(
1030            results_bytes.as_ptr(),
1031            results as *mut u8,
1032            results_bytes.len(),
1033        );
1034        *results.add(results_bytes.len()) = 0; // Null terminate
1035    }
1036
1037    0
1038}
1039
1040#[no_mangle]
1041pub unsafe extern "C" fn eip_execute_batch(
1042    client_id: c_int,
1043    operations: *const c_char,
1044    operation_count: c_int,
1045    results: *mut c_char,
1046    results_capacity: c_int,
1047) -> c_int {
1048    if operations.is_null() || results.is_null() || operation_count <= 0 {
1049        return -1;
1050    }
1051
1052    let mut clients = FFI_CLIENTS.lock().unwrap();
1053    let _client = match clients.get_mut(&client_id) {
1054        Some(client) => client,
1055        None => return -1,
1056    };
1057
1058    // Parse input (simplified implementation)
1059    let _input_str = unsafe {
1060        match CStr::from_ptr(operations).to_str() {
1061            Ok(s) => s,
1062            Err(_) => return -1,
1063        }
1064    };
1065
1066    // For now, return not implemented
1067    // TODO: Parse input and execute mixed batch operations
1068    let results_data = "ERROR:Mixed batch operations not yet implemented in FFI";
1069    let results_bytes = results_data.as_bytes();
1070
1071    if results_bytes.len() >= results_capacity as usize {
1072        return -1;
1073    }
1074
1075    unsafe {
1076        std::ptr::copy_nonoverlapping(
1077            results_bytes.as_ptr(),
1078            results as *mut u8,
1079            results_bytes.len(),
1080        );
1081        *results.add(results_bytes.len()) = 0; // Null terminate
1082    }
1083
1084    0
1085}
1086
1087#[no_mangle]
1088pub unsafe extern "C" fn eip_configure_batch_operations(
1089    _client_id: c_int,
1090    _config: *const u8,
1091) -> c_int {
1092    0 // Return success for now
1093}
1094
1095#[no_mangle]
1096pub unsafe extern "C" fn eip_get_batch_config(_client_id: c_int, _config: *mut u8) -> c_int {
1097    -1 // Not implemented yet
1098}