Pass a pointer to an array value from Cython to C - python

I have a structure in C that looks like this:
typedef struct {
uint32_t id;
uint8_t *buf; // pointer to message data
} msg_t;
and some function that receives that a pointer to such structure and modifies it.
void recv_msg( msg_t *msg ) {
// Stuff happens to message here
return;
}
With cytpes I tried something like this:
from cytpes import CDLL, Structure, POINTER, c_ubyte, c_uint32
class Msg(Structure):
_fields_ = [("id", c_uint32), ("buf", POINTER(c_ubyte * 10))]
lib = CDLL("this_example.so")
get_msg = lib.recv_msg
get_msg.argtypes = [POINTER(Msg)]
get_msg.restype = None
sample_data_array = POINTER(c_ubyte * 10)()
data = sample_data_array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
sample_msg = Msg(1, data)
get_msg(sample_msg)
print sample_msg.id, sample_msg.buf[0] # Should change data
I'm getting a TypeError('expected LP_c_ubyte_Array_10 instance, got list',)
I've also tried a similar approach using Cython:
from libc.stdint cimport uint8_t, uint32_t
cdef extern from "this_example.h":
ctypedef struct msg_t:
uint32_t id;
uint8_t *buf;
void get_msg (msg_t *)
def recv_msg():
# How I would do this I don't know
print msg.id, msg.buf[0]
I should also add, I don't want use numpy (but would reluctantly if I have to). Also, the length of the data array can vary, but I also have a length variable in the msg structure, so I can initialise it to the right length for sending, and just set it to default values and max length for receiving.
Any clues? Thanks!

I'm open to accepting other answers, but for now I thought I would post my solution in the hopes it might help someone else.
I solved it using cytpes and not Cython. I would really like to see a Cython solution though.
from ctypes import CDLL, Structure, POINTER, c_uint8, c_uint32, byref
class Msg(Structure):
_fields_ = [("id", c_uint32), ("buf", POINTER(c_uint8))]
lib = CDLL('this_example.so')
get_msg = lib.recv_msg
get_msg.argtypes = [POINTER(Msg)]
get_msg.restype = None
data = (c_uint8 * 8)()
sample_msg = Msg(1, data)
get_msg(byref(sample_msg))
print sample_msg.id, sample_msg.buf[0] # Should see changed data

Related

Loading c DLL with python, problems with pointer

Currently I have C++ loading DLL. I need replace C++ code with python. My problems are:
in callback function device_ID1_callback, all values seems empty, i'm guessing i did not use pointer correctly.
after call device_get_info, all values are 0, I suppose get some values none zero back.
I have tried anything I can think of for weeks but with very little luck.
To simplified the problem, here's partial of my code. Thanks for your time and help!!
in my lib.h file, i have
typedef unsigned int DeviceHandler;
typedef struct {
unsigned int fpga_version;
}DeviceInfo_t;
typedef struct {
unsigned int check_id;
float distance[256];
}MeasureResult_t;
DLLEPXORT int EXCALL device_add(DeviceHandler* outHandler, char* device_ip, MeasureModeCallback callback);
DLLEPXORT void EXCALL device_get_info(DeviceHandler handler, DeviceInfo_t* p_device_info);
in sample C++ file:
"""
void device_ID1_callback(const void *out,unsigned int out_num){
MeasureResult_t *ptr = (MeasureResult_t *)out;
printf("[ChechID:0x%x] %d pack's data\n",ptr[0].check_id,out_num);
}
void demo_callback_mode(){
int ret;
DeviceHandler device_handler;
DeviceInfo_t device_info;
ret = device_add(&device_handler,"192.168.1.2",&device_ID1_callback);
device_get_info(device_handler,&device_info);
printf("[FPGA] version : %d\n", device_info.fpga_version);
}
"""
*end of c++ *
Here's my python code:
"""
import ctypes as c
class MeasureResult_t(c.Structure):
_fields_ = [
('check_id', c.c_int),
('distance[256]', c.c_float)]
class DeviceInfo_t(c.Structure):
_fields_ = [
('fpga_version', c.c_int)
]
def device_ID1_callback(out, out_num):
print("---enter device call back function---")
print(dir(out))
print("out: ",out.contents)
#print(dir(out))
print("out_num:",out_num)
print("---exit device call back function---\n\n")
return 0
_dev = c.CDLL("./OPSensor/osp_lidar")
T_device_handler = c.c_int
T_device_handler_ptr = c.POINTER(T_device_handler)
_dev.device_add.argtypes = [T_device_handler_ptr, c.c_char_p]
_dev.device_add.restype = c.c_int
device_handler = c.c_int()
ip_val = c.c_char_p("192.168.1.2".encode('utf-8'))
out = MeasureResult_t()
out_num = c.c_int()
CMPFUNC_t = c.CFUNCTYPE(None, c.POINTER(MeasureResult_t), c.c_int)
MeasureModeCallback = CMPFUNC_t(device_ID1_callback)
ret = _dev.device_add(c.byref(device_handler), (ip_val), MeasureModeCallback(c.byref(out), out_num))
_dev.device_get_info.argtypes = [T_device_handler_ptr, c.POINTER(DeviceInfo_t)]
_dev.device_get_info.restype = c.c_void_p # assume it returns C int
p_device_info = DeviceInfo_t()
#_dev.device_get_info(c.byref(device_handler), c.byref(p_device_info)) # does not work
_dev.device_get_info((device_handler), c.byref(p_device_info)) #does not work either
print(device_handler) # I have correct device_handler value
print(p_device_info.fpga_version) # the value i got is 0, does seem right
"""
Here's my attempt at a minimal reproducible example. I implemented dummy functions that demonstrate the callback you described:
// lib.c
#define DLLEXPORT __declspec(dllexport)
#define EXCALL
typedef unsigned int DeviceHandler;
typedef struct {
unsigned int fpga_version;
} DeviceInfo_t;
typedef struct {
unsigned int check_id;
float distance[256];
} MeasureResult_t;
typedef void (*MeasureModeCallback)(MeasureResult_t*, unsigned int);
DLLEXPORT int EXCALL device_add(DeviceHandler* outHandler, char* device_ip, MeasureModeCallback callback) {
*outHandler = 123; // dummy device ID
MeasureResult_t m; // some fake measurement results
m.check_id = 456;
for(int i = 0; i < 256; ++i)
m.distance[i] = (float)(i * .25);
callback(&m, 789); // call the callback
return 1;
}
DLLEXPORT void EXCALL device_get_info(DeviceHandler handler, DeviceInfo_t* p_device_info) {
p_device_info->fpga_version = handler * 2; // fake fpga version
}
To create a callback in Python, assign the CFUNCTYPE prototype to the callback type, decorate the callback function with that type, and use that type in the callback argument definition, and the actual function name when passing it as an argument.
Also note the that float distance[256] is declared in Python as c.c_float * 256 to create an array and the differences in the .argtypes/.restype attributes for the functions. device_get_info takes a DeviceHandler, not a c.POINTER(DeviceHandler) for example.
# test.py
import ctypes as c
class MeasureResult_t(c.Structure):
_fields_ = (('check_id', c.c_uint),
('distance', c.c_float * 256))
def __repr__(self): # defines how to display this class
return f'MeasureResult_t(check_id={self.check_id}, distance=[{self.distance[0]}, ..., {self.distance[255]}])'
class DeviceInfo_t(c.Structure):
_fields_ = ('fpga_version', c.c_uint),
def __repr__(self): # defines how to display this class
return f'DeviceInfo_t(fpga_version={self.fpga_version})'
# Declare the callback type
MeasureModeCallback = c.CFUNCTYPE(None, c.POINTER(MeasureResult_t), c.c_uint)
DeviceHandler = c.c_uint
# apply the decorator so this function can be called from C
#MeasureModeCallback
def device_ID1_callback(out, out_num):
print('---enter device call back function---')
print('out: ',out.contents)
print('out_num:',out_num)
print('---exit device call back function---')
_dev = c.CDLL('./lib')
# Use the argument type
_dev.device_add.argtypes = c.POINTER(DeviceHandler), c.c_char_p, MeasureModeCallback
_dev.device_add.restype = c.c_int
_dev.device_get_info.argtypes = DeviceHandler, c.POINTER(DeviceInfo_t)
_dev.device_get_info.restype = None
device_handler = DeviceHandler()
ip_val = b'192.168.1.2'
# Use the callback function name when calling the function
ret = _dev.device_add(c.byref(device_handler), ip_val, device_ID1_callback)
device_info = DeviceInfo_t()
_dev.device_get_info(device_handler, c.byref(device_info))
print(f'{device_handler.value=}')
print(f'{device_info=}')
Output. Note the classes know how to display themselves and the fake data agrees with my implementation:
---enter device call back function---
out: MeasureResult_t(check_id=456, distance=[0.0, ..., 63.75])
out_num: 789
---exit device call back function---
device_handler.value=123
device_info=DeviceInfo_t(fpga_version=246)

How to access the value of a ctypes.LP_c_char pointer?

I have defined a struct :
class FILE_HANDLE(Structure):
_fields_ = [
("handle_bytes", c_uint),
("handle_type", c_int),
("f_handle", POINTER(c_char))
]
The struct is initialised :
buf = create_string_buffer(f_handle.handle_bytes)
fh = FILE_HANDLE(c_uint(8), c_int(0), buf)
I am passing it by reference to a function that populates it.
ret = libc.name_to_handle_at(dirfd, pathname, byref(fh), byref(mount_id), flags)
I can check with strace that the call works, but I have not been able to figure out how to access the value of fh.f_handle
fh.f_handle type is <ctypes.LP_c_char object at 0x7f1a7ca17560>
fh.f_handle.contents type is <ctypes.LP_c_char object at 0x7f1a7ca17560> but I get a SIGSEGV if I try to access its value.
How could I get 8 bytes from f_handle into a string or array ?
Everything actually looks right for what you've shown, but without seeing the explicit C definition of the structure and function you are calling it is difficult to see the problem.
Here's an example that works with what you have shown. I inferred what the C definitions should be from what you have declared in Python, but most likely your definition is different if you get a segfault.
C Code (Windows)
struct FILE_HANDLE
{
unsigned int handle_bytes;
int handle_type;
char* f_handle;
};
__declspec(dllexport) int name_to_handle_at(int dirfd, char* pathname, struct FILE_HANDLE* fh, int* mount_id, int flags)
{
unsigned int i;
printf("dirfd=%d pathname=%s fh->handle_bytes=%u fh->handle_type=%d flags=%d\n", dirfd, pathname, fh->handle_bytes, fh->handle_type, flags);
for(i = 0; i < fh->handle_bytes; ++i)
fh->f_handle[i] = 'A' + i;
*mount_id = 123;
return 1;
}
Python code (Works in Python 2 and 3):
from __future__ import print_function
from ctypes import *
class FILE_HANDLE(Structure):
_fields_ = [("handle_bytes", c_uint),
("handle_type", c_int),
("f_handle", POINTER(c_char))]
buf = create_string_buffer(8);
fh = FILE_HANDLE(8,0,buf)
libc = CDLL('test.dll')
mount_id = c_int(0)
ret = libc.name_to_handle_at(1,b'abc',byref(fh),byref(mount_id),7)
print('mount_id =',mount_id.value)
print('fh.f_handle =',fh.f_handle[:fh.handle_bytes])
Output
dirfd=1 pathname=abc fh->handle_bytes=8 fh->handle_type=0 flags=7
mount_id = 123
fh.f_handle = b'ABCDEFGH'
Note that since the structure is declared as a pointer to a single character, printing fh.f_handle.contents would only print b'A'. Using slicing, I've instructed Python to index the pointer up to the length allocated.
If this doesn't work for you, provide a Minimal, Complete, and Verifiable example (as I have) to reproduce your error exactly.
fh.f_handle is shown as LP_c_char because you defined the struct that way.
buf = create_string_buffer(8)
print type(buf)
fh = FILE_HANDLE(c_uint(8), c_int(0), buf)
print type(fh.f_handle)
Will output
<class 'ctypes.c_char_Array_8'>
<class 'ctypes.LP_c_char'>
You have defined your struct to accept a pointer to a c_char. So when you try to access fh.f_handle it will expect the value to be a memory address containing the address to the actual single c_char.
But by trying to input a c_char * 8 from the string buffer it will convert the first part of your buffer to a pointer.
Python tries to dereference your char[0] which means that it will look for a memory address with the value of the character you have defined in char[0]. That memory address is not valid, so your interpreter will signal a SIGSEGV.
Now to create a class which properly handles a variable length buffer is quite difficult. An easier option is to pass the buffer as an opaque handle, to access it afterwards you need to cast it back to a char array.
Example:
class FILE_HANDLE(Structure):
_fields_ = [
("handle_bytes", c_uint),
("handle_type", c_int),
("f_handle", c_void_p)
]
buf = create_string_buffer(8)
buf = cast(buf, c_void_p)
fh = FILE_HANDLE(c_uint(8), c_int(0), buf)
f_handle_value = (c_char * fh.handle_bytes).from_address(fh.f_handle)

Python ctypes mutable string in struct

I have the following C struct that contains a mutable C-style string.
typedef struct XMLCFG_PARAMS {
unsigned long ulMedia; // Save/load to file or buffer
unsigned long ulCfgFlags; // Flags say what type of info to save/load
char* pszBufOrFilename; // The buffer or the filename
unsigned long ulBufLen; // How long is the buffer (in bytes)
} XMLCFG_PARAMS;
I've defined the structure in Python like so:
class XMLCFG_PARAMS(ctypes.Structure):
_fields_ = [
("ulMedia", ctypes.c_ulong),
("ulCfgFlags", ctypes.c_ulong),
("pszBufOrFilename", ctypes.c_char_p),
("ulBufLen", ctypes.c_ulong),
]
This structure is then instantiated:
xml_cfg = XMLCFG_PARAMS()
xml_cfg.ulMedia = XMLCFG_BUFFER
xml_cfg.ulCfgFlags = config_flags
xml_buffer = ctypes.create_string_buffer(xml_text, size=1024 * 1024)
xml_cfg.pszBufOrFilename = ctypes.c_char_p(ctypes.addressof(xml_buffer))
xml_cfg.ulBufLen = len(xml_buffer)
The C call returns with the error WindowsError: exception: access violation reading 0x6E6F697B.
I suspect that I'm doing something wrong with the way I'm instantiating theXMLCFG_PARAMS struct, specifically the pszBufOrFilename field as this is the first time I've encountered a mutable C-style string in a struct.
C Function Signature
BOOL Control(unsigned long ulEngId,
unsigned long cmd_id,
unsigned long *ulParam0 = 0,
unsigned long *ulParam1 = 0,
unsigned long *ulParam2 = 0,
unsigned long *ulParam3 = 0);
Python ctypes call
dll_handle.Control(engine_id, cmd_id, ctypes.pointer(xml_cfg), None, None, None)
Output of hex(ctypes.addressof(xml_buffer)) is 0x3940020 while the exception keeps referring to 0x6E6F697B. This is on Win XP, so I guess this memory address doesn't change because the OS lacks ASLR.

Python equivalent of C struct (porting app form C to python)

I'm porting a simple bluetooth app, which sends "magic" packet on L2Cap protocol to bluetooth device..
I have a problem with converting struct object in C to python equivalent..
In c:
/* command types */
#define CFGBT_TYPE_SETREQ 0x00
#define CFGBT_TYPE_SETRES 0x01
#define CFGBT_TYPE_GETREQ 0x02
#define CFGBT_TYPE_GETRES 0x03
/* varid types */
#define CFG_VARIDT_UINT16 0x0000
#define CFG_VARIDT_UINT32 0x1000
#define CFG_VARIDT_STRING16 0x2000
typedef struct {
uint8_t type, status;
uint16_t varid;
uint8_t value[16];
} __attribute__((packed)) CFGBTFRAME;
static CFGBTFRAME c;
and then in app it's used like that:
/* set up */
c.type = CFGBT_TYPE_GETREQ;
c.varid = strtol(argv[3], NULL, 0);
c.status = 0;
memset(c.value, 0, 16);
/* send frame */
write(s, &c, sizeof(c));
Can you point me out how to construct same packet/stuct-like structure using python?
I know I will probably need to use ctypes and create "empty" class, but how to get all this together?
You can go about it with the struct module to pack values into a byte string, for example:
>>> import struct
>>> type, status, varid, value = 1, 0, 16, b'Hello'
>>> buffer = struct.pack('>BBH16s', type, status, varid, value)
>>> buffer
b'\x01\x00\x00\x10Hello\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
Alternatively, you can use ctypes.Structure to define a class that will be representing your structure. It has the advantage of being easier to use with Python code, but you have to take into account alignment and padding issues and resolve them yourself (perhaps with struct).
If your goal is to group a set of key/value in an object, you may use dict or namedtuple.
a dict would be:
CFGBTFRAME = {
'type' : myType,
'status' : ...
}
access: CFGBTFRAME['type']
with namedtuple:
from collections import namedtuple
CFGBTFRAME = namedtuple('CFGBTFRAME', ['type', 'status', ...])
c = CFGBTFRAME()
c.type = myType
see http://docs.python.org/library/collections.html#collections.namedtuple for more information about namedtuple.

Python & Ctypes: Passing a struct to a function as a pointer to get back data

I've looked through other answers but can't seem to get this to work. I'm trying to call a function within a DLL for communicating with SMBus devices. This function takes a pointer to a struct, which has an array as one of it's fields. so...
In C:
typedef struct _SMB_REQUEST
{
unsigned char Address;
unsigned char Command;
unsigned char BlockLength;
unsigned char Data[SMB_MAX_DATA_SIZE];
} SMB_REQUEST;
I think I have to set values for the Address, Command and BlockLength while the DLL fills the Data array.
The function that requires this struct takes it as a pointer
SMBUS_API int SmBusReadByte( SMBUS_HANDLE handle, SMB_REQUEST *request );
So I've set up the struct in Python like so:
class SMB_REQUEST(ctypes.Structure):
_fields_ = [("Address", c_char),
("Command", c_char),
("BlockLength", c_char),
("Data", type(create_string_buffer(SMB_MAX_DATA_SIZE))]
*Note: I've also tried ctypes.c_char*SMB_MAX_DATA_SIZE for the data type*
To pass a pointer to a struct of this type to the function I have tried to initialise it first as follows:
data = create_string_buffer(SMB_MAX_DATA_SIZE)
smb_request = SMB_REQUEST('\x53', \x00', 1, data)
This responds with:
TypeError: expected string or Unicode object, c_char_Array_32 found
If I try leaving out the data array, like so:
smb_request = SMB_REQUEST('\x53', \x00', 1)
No, error.
However, then when I try to pass this to the function:
int_response = smbus_read_byte(smbus_handle, smb_request))
I get:
ArgumentError: argument 2: <type 'exceptions.TypeError'>: expected LP_SMB_REQUES
T instance instead of SMB_REQUEST
I've tried passing it as a pointer:
int_response = smbus_read_byte(smbus_handle, ctypes.POINTER(smb_request))
and I get:
----> 1
2
3
4
5
TypeError: must be a ctypes type
Here's how I've set up the art types:
smbus_read_byte.argtypes = (ctypes.c_void_p, ctypes.POINTER(SMB_REQUEST))
I've tried casting but still no go. Can anyone shed some light on this for me?
Update:
If I first initialise the struct like so:
smb_request = SMB_REQUEST('\xA6', '\x00', chr(1), 'a test string')
and then bass by reference:
int_response = smbus_receive_byte(smbus_handle, ctypes.byref(smb_request))
I get no error. However, the function returns -1 when it should return '0' for success and non-zero for a fail. Checking the value of smb_request.Data gives back 'a test string' so no change there.
Any suggestions as to what might be going on here would be greatly appreciated.
Thanks
UPDATE:
Since I've gotten a couple of enquiries about whether my handle is correct, here's how I'm using it. The header file for the DLL declares the following:
typedef void *SMBUS_HANDLE;
//
// This function call initializes the SMBus, opens the driver and
// allocates the resources associated with the SMBus.
// All SMBus API calls are valid
// after making this call except to re-open the SMBus.
//
SMBUS_API SMBUS_HANDLE OpenSmbus(void);
So here's how I'm doing this in python:
smbus_handle = c_void_p() # NOTE: I have also tried it without this line but same result
open_smbus = CDLL('smbus.dll').OpenSmbus
smbus_handle = open_smbus()
print 'SMBUS_API SMBUS_HANDLE OpenSmbus(void): ' + str(smbus_handle)
I call this before making the call to smbus_read_byte(). I have tried to set open_smbus.restype = c_void_p() but I get an error: TypeError: restype must be a type, a callable, or None
Here's a working example. It looks like you are passing the wrong type to the function.
Test DLL Code ("cl /W4 /LD x.c" on Windows)
#include <stdio.h>
#define SMBUS_API __declspec(dllexport)
#define SMB_MAX_DATA_SIZE 5
typedef void* SMBUS_HANDLE;
typedef struct _SMB_REQUEST
{
unsigned char Address;
unsigned char Command;
unsigned char BlockLength;
unsigned char Data[SMB_MAX_DATA_SIZE];
} SMB_REQUEST;
SMBUS_API int SmBusReadByte(SMBUS_HANDLE handle,SMB_REQUEST *request)
{
unsigned char i;
for(i = 0; i < request->BlockLength; i++)
request->Data[i] = i;
return request->BlockLength;
}
SMBUS_API SMBUS_HANDLE OpenSmbus(void)
{
return (void*)0x12345678;
}
Python code
from ctypes import *
SMB_MAX_DATA_SIZE = 5
ARRAY5 = c_ubyte * SMB_MAX_DATA_SIZE
class SMB_REQUEST(Structure):
_fields_ = [
("Address", c_ubyte),
("Command", c_ubyte),
("BlockLength", c_ubyte),
("Data", ARRAY5)]
smbus_read_byte = CDLL('x').SmBusReadByte
smbus_read_byte.argtypes = [c_void_p,POINTER(SMB_REQUEST)]
smbus_read_byte.restype = c_int
open_smbus = CDLL('x').OpenSmbus
open_smbus.argtypes = []
open_smbus.restype = c_void_p
handle = open_smbus()
print 'handle = %08Xh' % handle
smb_request = SMB_REQUEST(1,2,5)
print 'returned =',smbus_read_byte(handle,byref(smb_request))
print 'Address =',smb_request.Address
print 'Command =',smb_request.Command
print 'BlockLength =',smb_request.BlockLength
for i,b in enumerate(smb_request.Data):
print 'Data[%d] = %02Xh' % (i,b)
Output
handle = 12345678h
returned = 5
Address = 1
Command = 2
BlockLength = 5
Data[0] = 00h
Data[1] = 01h
Data[2] = 02h
Data[3] = 03h
Data[4] = 04h
You're almost there. You should use c_char * SMB_MAX_DATA_SIZE as the type for the definition of Data. This works for me on Mac OS X:
Shared library:
$ cat test.c
#include <stdio.h>
#define SMB_MAX_DATA_SIZE 16
typedef struct _SMB_REQUEST
{
unsigned char Address;
unsigned char Command;
unsigned char BlockLength;
unsigned char Data[SMB_MAX_DATA_SIZE];
} SMB_REQUEST;
int SmBusReadByte(void *handle, SMB_REQUEST *request)
{
printf("SmBusReadByte: handle=%p request=[%d %d %d %s]\n", handle,
request->Address, request->Command, request->BlockLength, request->Data);
return 13;
}
$ gcc test.c -fPIC -shared -o libtest.dylib
Python driver:
$ cat test.py
import ctypes
SMB_MAX_DATA_SIZE = 16
class SMB_REQUEST(ctypes.Structure):
_fields_ = [("Address", ctypes.c_ubyte),
("Command", ctypes.c_ubyte),
("BlockLength", ctypes.c_ubyte),
("Data", ctypes.c_char * SMB_MAX_DATA_SIZE)]
libtest = ctypes.cdll.LoadLibrary('libtest.dylib')
req = SMB_REQUEST(1, 2, 3, 'test')
result = libtest.SmBusReadByte(ctypes.c_voidp(0x12345678), ctypes.byref(req))
print 'result: %d' % result
$ python test.py
SmBusReadByte: handle=0x12345678 request=[1 2 3 test]
result: 13
UPDATE
You're having problems because you need to set the result type of open_smbus to void*. By default, ctypes assumes that functions return ints. You need to say this:
open_smbus.restype = ctypes.c_void_p
You were getting an error because you were using c_void_p() (note the extra parentheses). There's an important distinction between c_void_p and c_void_p(). The former is a type, and the latter is an instance of a type. c_void_p represents the C type void*, whereas c_void_p() represents an actual pointer instance (with a default value of 0).
Try changing
("Data", type(create_string_buffer(SMB_MAX_DATA_SIZE))
to
("Data", (c_char * SMB_MAX_DATA_SIZE)]

Categories