Dependents:   mbed_TANK_Kinect ftusbClass

Committer:
networker
Date:
Sun Jun 19 13:26:53 2011 +0000
Revision:
4:1c90839a1e70
Parent:
3:a90b1a8fd184
added rfcommmanager instance

Who changed what in which revision?

UserRevisionLine numberNew contents of line
networker 0:8d7b47ceb9f5 1 #ifndef RFCOMM_H
networker 0:8d7b47ceb9f5 2 #define RFCOMM_H
networker 0:8d7b47ceb9f5 3 #include "USBHost.h"
networker 0:8d7b47ceb9f5 4 #include "hci.h"
networker 1:9f3821db3048 5 #include "Utils.h"
networker 0:8d7b47ceb9f5 6
networker 0:8d7b47ceb9f5 7 #define MAX_RFCOMM_SCKTS 4
networker 3:a90b1a8fd184 8 //#define MAX_FRAME_SIZE 350 //ACL buffer - some headroom
networker 3:a90b1a8fd184 9 #define MAX_FRAME_SIZE 127 //ft size
networker 3:a90b1a8fd184 10
networker 0:8d7b47ceb9f5 11 /*
networker 1:9f3821db3048 12 template <class T>
networker 1:9f3821db3048 13 T min(T a, T b) {
networker 0:8d7b47ceb9f5 14 return a<b ? a : b;
networker 0:8d7b47ceb9f5 15 }
networker 0:8d7b47ceb9f5 16 */
networker 0:8d7b47ceb9f5 17
networker 1:9f3821db3048 18 #define MASK_BITRATE 0X0001
networker 1:9f3821db3048 19 #define MASK_DATABITS 0X0002
networker 1:9f3821db3048 20 #define MASK_STOPBITS 0X0004
networker 1:9f3821db3048 21 #define MASK_PARITYBITS 0X0008
networker 1:9f3821db3048 22 #define MASK_PARITYTYPE 0X0010
networker 1:9f3821db3048 23 #define MASK_XONCHAR 0X0020
networker 1:9f3821db3048 24 #define MASK_XOFFCHAR 0X0040
networker 1:9f3821db3048 25 #define MASK_XONXOFFIN 0X0100
networker 1:9f3821db3048 26 #define MASK_XONXOFFOUT 0X0200
networker 1:9f3821db3048 27 #define MASK_RTRIN 0X0400
networker 1:9f3821db3048 28 #define MASK_RTROUT 0X0800
networker 1:9f3821db3048 29 #define MASK_RTCIN 0X1000
networker 1:9f3821db3048 30 #define MASK_RTCOUT 0X2000
networker 1:9f3821db3048 31
networker 1:9f3821db3048 32 struct port_settings {
networker 1:9f3821db3048 33 unsigned char baud;
networker 1:9f3821db3048 34 unsigned char bits:
networker 1:9f3821db3048 35 2;
networker 1:9f3821db3048 36 unsigned char stop:
networker 1:9f3821db3048 37 1;
networker 1:9f3821db3048 38 unsigned char par:
networker 1:9f3821db3048 39 1;
networker 1:9f3821db3048 40 unsigned char par_t:
networker 1:9f3821db3048 41 2;
networker 1:9f3821db3048 42 unsigned char :
networker 1:9f3821db3048 43 2;
networker 1:9f3821db3048 44 unsigned char flow:
networker 1:9f3821db3048 45 6;
networker 1:9f3821db3048 46 unsigned char :
networker 1:9f3821db3048 47 2;
networker 1:9f3821db3048 48 unsigned char xon;
networker 1:9f3821db3048 49 unsigned char xoff;
networker 1:9f3821db3048 50 unsigned short mask;
networker 1:9f3821db3048 51 };
networker 1:9f3821db3048 52
networker 0:8d7b47ceb9f5 53 class rfcomm;
networker 0:8d7b47ceb9f5 54 class RFCOMMManager;
networker 0:8d7b47ceb9f5 55 #define MAX_RFCOMM_DEVICES 8 //physical devices
networker 0:8d7b47ceb9f5 56
networker 0:8d7b47ceb9f5 57 class RFCOMMSocket: public SocketInternal {//this object must not be larger than SocketInternalPad (socketinternal + 8 bytes)
networker 0:8d7b47ceb9f5 58 public:
networker 0:8d7b47ceb9f5 59 rfcomm* serdevice;
networker 0:8d7b47ceb9f5 60 u8 dlci; //channel + D bit, D bit is inverted initiator bit
networker 0:8d7b47ceb9f5 61 u8 my_credits, peer_credits;
networker 0:8d7b47ceb9f5 62 };
networker 0:8d7b47ceb9f5 63
networker 0:8d7b47ceb9f5 64 class rfcomm: public SocketHandler {
networker 0:8d7b47ceb9f5 65 int _l2cap; //socket to the l2cap layer
networker 0:8d7b47ceb9f5 66 int _devClass;
networker 0:8d7b47ceb9f5 67 BD_ADDR _addr;
networker 1:9f3821db3048 68 u8 initiator;
networker 0:8d7b47ceb9f5 69 u8 _pad[0]; // Struct align
networker 0:8d7b47ceb9f5 70 char sckts[MAX_RFCOMM_SCKTS];
networker 3:a90b1a8fd184 71 //static
networker 3:a90b1a8fd184 72 unsigned maxframesize;
networker 0:8d7b47ceb9f5 73 int find_slot(unsigned ch);
networker 0:8d7b47ceb9f5 74 RFCOMMSocket* find_socket(unsigned dlci);
networker 0:8d7b47ceb9f5 75 void initChannels(int socket);
networker 0:8d7b47ceb9f5 76 unsigned release_channel(unsigned dlci);
networker 0:8d7b47ceb9f5 77 static void OnRfCommControl(int socket, SocketState state, const u8* data, int len, void* userData);//I guess this is called when data comes out of the socket
networker 0:8d7b47ceb9f5 78 int Disconnect(RFCOMMSocket*);
networker 0:8d7b47ceb9f5 79 public:
networker 0:8d7b47ceb9f5 80 rfcomm() : _l2cap(0), _devClass(0) {
networker 0:8d7b47ceb9f5 81 for (int i = 0; i < MAX_RFCOMM_SCKTS; i++) sckts[i] = 0;
networker 3:a90b1a8fd184 82 maxframesize = MAX_FRAME_SIZE;
networker 0:8d7b47ceb9f5 83 }
networker 0:8d7b47ceb9f5 84
networker 0:8d7b47ceb9f5 85 bool InUse() {
networker 0:8d7b47ceb9f5 86 return _l2cap != 0;
networker 0:8d7b47ceb9f5 87 }
networker 0:8d7b47ceb9f5 88 virtual int Open(SocketInternal* sock, SocketAddrHdr* addr);
networker 0:8d7b47ceb9f5 89 virtual int Send(SocketInternal* sock, const u8* data, int len);//wrap data in RFCOMM frame and dispatch
networker 3:a90b1a8fd184 90 virtual int SetOpt(SocketInternal *sock, int so, int* data, int len);
networker 3:a90b1a8fd184 91 virtual int GetOpt(SocketInternal *sock, int so, int* data, int len);
networker 0:8d7b47ceb9f5 92 virtual int Close(SocketInternal* sock);
networker 1:9f3821db3048 93 virtual char* Name() {
networker 1:9f3821db3048 94 return "rfcomm SocketHandler";
networker 1:9f3821db3048 95 }
networker 0:8d7b47ceb9f5 96 void Recv(const u8* data, int len) {
networker 0:8d7b47ceb9f5 97 printf("rfcomm::Recv was called\n");
networker 0:8d7b47ceb9f5 98 }
networker 1:9f3821db3048 99 #if 0
networker 1:9f3821db3048 100 int Listen(unsigned char ch=0) {//passive open, similar semantics to 'Open' but connection is only made at request of the peer
networker 1:9f3821db3048 101 RFCOMMSocket *s = 0;//this entity may or may not be bound to a remote entity
networker 1:9f3821db3048 102 if (ch>0) {//specific channel
networker 1:9f3821db3048 103 s = find_socket(ch<<1);
networker 1:9f3821db3048 104 if (s) { //socket for this channel already in use
networker 1:9f3821db3048 105 printf("rfcomm::Listen: channel %d already in use\n", ch);
networker 1:9f3821db3048 106 return 0;
networker 1:9f3821db3048 107 } //else s==0, no socket for channel ch
networker 1:9f3821db3048 108 }//else listen to any channel
networker 1:9f3821db3048 109 int sn = find_slot(ch);
networker 1:9f3821db3048 110 if (sn<0) {
networker 1:9f3821db3048 111 printf("No socket could be found for channel %d\n", ch);
networker 1:9f3821db3048 112 return 0;
networker 1:9f3821db3048 113 } //else use slot 'sn' for the new rfcomm socket
networker 1:9f3821db3048 114 int sock = Socket_Create(SOCKET_RFCOM, OnRfCommControl, this);//allocate an rfcomm socket
networker 1:9f3821db3048 115 sckts[sn] = sock; //claim the socket
networker 1:9f3821db3048 116 RFCOMMSocket *rs = (RFCOMMSocket*)GetSocketInternal(sock);
networker 1:9f3821db3048 117 rs->serdevice = this;
networker 1:9f3821db3048 118 rs->dlci = (ch<<1)|1;//server socket
networker 1:9f3821db3048 119 //initiator = 0; what to do if already connected actively on different channel???
networker 1:9f3821db3048 120 /*l2cap is already present or is created when accepting the connection
networker 1:9f3821db3048 121 if (_l2cap == 0) { //no rfcomm -> l2cap connection yet
networker 1:9f3821db3048 122 printf("Need to open L2CAP channel first before opening RFCOMM channel %d\n", rs->dlci);
networker 1:9f3821db3048 123 ((L2CAPAddr*)addr)->psm = L2CAP_PSM_RFCOMM;//open the l2cap channel and the rfcomm_ch channel
networker 1:9f3821db3048 124 initiator = 0;
networker 1:9f3821db3048 125 _l2cap = Socket_Create(SOCKET_L2CAP, addr, OnRfCommControl, this);//this is the socket between the RFCOMM and the L2CAP layer
networker 1:9f3821db3048 126 if (_l2cap)
networker 1:9f3821db3048 127 printf("Successfully opened L2CAP channel on socket %d\n", _l2cap);
networker 1:9f3821db3048 128 else {
networker 1:9f3821db3048 129 printf("Opening L2CAP channel failed\n");
networker 1:9f3821db3048 130 return 0;
networker 1:9f3821db3048 131 }
networker 1:9f3821db3048 132 }
networker 1:9f3821db3048 133 */
networker 1:9f3821db3048 134 return sock;
networker 1:9f3821db3048 135 }
networker 1:9f3821db3048 136 #endif
networker 1:9f3821db3048 137 //int Open(BD_ADDR* bdAddr, inquiry_info* info);
networker 1:9f3821db3048 138 int set_remote_port_parameters(unsigned char dlci, port_settings *p);
networker 1:9f3821db3048 139 friend RFCOMMManager;
networker 0:8d7b47ceb9f5 140 };
networker 0:8d7b47ceb9f5 141
networker 0:8d7b47ceb9f5 142 class RFCOMMManager: public SocketHandler {
networker 0:8d7b47ceb9f5 143 rfcomm _devs[MAX_RFCOMM_DEVICES];
networker 1:9f3821db3048 144 int serverSock;
networker 1:9f3821db3048 145 char sckts[MAX_RFCOMM_SCKTS];//listening sockets
networker 0:8d7b47ceb9f5 146 public:
networker 0:8d7b47ceb9f5 147 virtual int Open(SocketInternal* sock, SocketAddrHdr* addr) {
networker 0:8d7b47ceb9f5 148 L2CAPAddr* ad = (L2CAPAddr*)addr;
networker 0:8d7b47ceb9f5 149 BD_ADDR* a = &ad->bdaddr;
networker 0:8d7b47ceb9f5 150 rfcomm *r = FindRfCommDevice(a);
networker 0:8d7b47ceb9f5 151 if (r==0)
networker 0:8d7b47ceb9f5 152 r = NewRfCommDevice();
networker 1:9f3821db3048 153 if (r==0)
networker 1:9f3821db3048 154 return 0;
networker 0:8d7b47ceb9f5 155 return r->Open(sock, addr);
networker 0:8d7b47ceb9f5 156 }
networker 1:9f3821db3048 157
networker 1:9f3821db3048 158 int FindSocket(BTDevice* dev) {//finds the l2cap socket for a certain rfcomm-btdevice connection
networker 1:9f3821db3048 159 for (int i = 0; i < MAX_RFCOMM_DEVICES; i++) {
networker 1:9f3821db3048 160 rfcomm *r = _devs+i;
networker 1:9f3821db3048 161 int l2cap = r->_l2cap;
networker 1:9f3821db3048 162 if (l2cap) {
networker 1:9f3821db3048 163 L2CAPSocket *p = (L2CAPSocket*)GetSocketInternal(l2cap);
networker 1:9f3821db3048 164 if (p->btdevice == dev)
networker 1:9f3821db3048 165 return l2cap;
networker 1:9f3821db3048 166 }
networker 1:9f3821db3048 167 }
networker 1:9f3821db3048 168 return 0;
networker 1:9f3821db3048 169 }
networker 1:9f3821db3048 170
networker 1:9f3821db3048 171 rfcomm* FindDev(BTDevice* dev) {//finds the rfcomm entity for a certain rfcomm-btdevice connection
networker 1:9f3821db3048 172 for (int i = 0; i < MAX_RFCOMM_DEVICES; i++) {
networker 1:9f3821db3048 173 rfcomm *r = _devs+i;
networker 1:9f3821db3048 174 int l2cap = r->_l2cap;
networker 1:9f3821db3048 175 if (l2cap) {
networker 1:9f3821db3048 176 L2CAPSocket *p = (L2CAPSocket*)GetSocketInternal(l2cap);
networker 1:9f3821db3048 177 if (p->btdevice == dev)
networker 1:9f3821db3048 178 return r;
networker 1:9f3821db3048 179 }
networker 1:9f3821db3048 180 }
networker 1:9f3821db3048 181 return 0;
networker 1:9f3821db3048 182 }
networker 1:9f3821db3048 183
networker 1:9f3821db3048 184 int BindSocket(int s) {
networker 1:9f3821db3048 185 L2CAPSocket *ls = (L2CAPSocket*)GetSocketInternal(s);
networker 1:9f3821db3048 186 printf("Binding l2cap socket %d to a new rfcomm server entity\n", s);
networker 1:9f3821db3048 187 rfcomm *r = NewRfCommDevice();
networker 1:9f3821db3048 188 r->_l2cap = s;
networker 1:9f3821db3048 189 r->initiator = 0;//we are server
networker 1:9f3821db3048 190 ls->si.userData = r;//was BTDevice, make rfcomm
networker 1:9f3821db3048 191 return 0;
networker 1:9f3821db3048 192 }
networker 1:9f3821db3048 193
networker 1:9f3821db3048 194 int new_slot(unsigned ch) {
networker 1:9f3821db3048 195 for (int i = 0; i < MAX_RFCOMM_SCKTS; i++) {
networker 1:9f3821db3048 196 if (sckts[i] != 0) { //socket is in use
networker 1:9f3821db3048 197 RFCOMMSocket *s = (RFCOMMSocket*)GetSocketInternal(sckts[i]);
networker 1:9f3821db3048 198 if (s==0) {
networker 1:9f3821db3048 199 printf("find_slot: socket %d not found\n", sckts[i]);
networker 1:9f3821db3048 200 continue;
networker 1:9f3821db3048 201 }
networker 1:9f3821db3048 202 if ((s->dlci >> 1) == ch) {
networker 1:9f3821db3048 203 printf("Channel %d is already in use on socket %d\n", ch, sckts[i]);
networker 1:9f3821db3048 204 return -1;
networker 1:9f3821db3048 205 }
networker 1:9f3821db3048 206 } else //slot is free
networker 1:9f3821db3048 207 return i;
networker 1:9f3821db3048 208 }
networker 1:9f3821db3048 209 return -2; //no free slots
networker 1:9f3821db3048 210 }
networker 1:9f3821db3048 211
networker 1:9f3821db3048 212 int find_socket(unsigned ch) {
networker 1:9f3821db3048 213 for (int i = 0; i < MAX_RFCOMM_SCKTS; i++) {
networker 1:9f3821db3048 214 if (sckts[i] != 0) { //socket is in use
networker 1:9f3821db3048 215 RFCOMMSocket *s = (RFCOMMSocket*)GetSocketInternal(sckts[i]);
networker 1:9f3821db3048 216 if (s==0) {
networker 1:9f3821db3048 217 printf("find_slot: socket %d not found\n", sckts[i]);
networker 1:9f3821db3048 218 continue;
networker 1:9f3821db3048 219 }
networker 1:9f3821db3048 220 if ((s->dlci >> 1) == ch) {
networker 1:9f3821db3048 221 printf("Found Channel %d on socket %d\n", ch, sckts[i]);
networker 1:9f3821db3048 222 return sckts[i];
networker 1:9f3821db3048 223 }
networker 1:9f3821db3048 224 else
networker 1:9f3821db3048 225 printf("slot %d has socket %d has dlci %d\n", i, sckts[i], s->dlci);
networker 1:9f3821db3048 226 }
networker 1:9f3821db3048 227 else
networker 1:9f3821db3048 228 printf("Slot %d is free\n", i);
networker 1:9f3821db3048 229 }
networker 1:9f3821db3048 230 printf("channel %d not found\n", ch);
networker 1:9f3821db3048 231 return 0; //channel not found
networker 1:9f3821db3048 232 }
networker 1:9f3821db3048 233
networker 1:9f3821db3048 234 int remove_socket(int sock) {
networker 1:9f3821db3048 235 for (int i = 0; i < MAX_RFCOMM_SCKTS; i++) {
networker 1:9f3821db3048 236 if (sckts[i] == sock) {
networker 1:9f3821db3048 237 sckts[i] = 0;
networker 1:9f3821db3048 238 return 0;
networker 1:9f3821db3048 239 }
networker 1:9f3821db3048 240 }
networker 1:9f3821db3048 241 return -1;
networker 1:9f3821db3048 242 }
networker 1:9f3821db3048 243
networker 1:9f3821db3048 244 virtual int Listen(SocketInternal* sock, int ch) {//called by Socket_Listen(SOCKET_RFCOM, channel, callback, userData)
networker 1:9f3821db3048 245 int slot = new_slot(ch);
networker 1:9f3821db3048 246 switch (slot) {
networker 1:9f3821db3048 247 case -1:
networker 1:9f3821db3048 248 printf("There is already someone listening on ch %d\n", ch);
networker 1:9f3821db3048 249 return ERR_SOCKET_CANT_LISTEN;//channel is occupied
networker 1:9f3821db3048 250 case -2:
networker 1:9f3821db3048 251 printf("All listener sockets are in use\n");
networker 1:9f3821db3048 252 return ERR_SOCKET_NONE_LEFT;
networker 1:9f3821db3048 253 }
networker 1:9f3821db3048 254 RFCOMMSocket *rs = (RFCOMMSocket*)sock;
networker 1:9f3821db3048 255 const char dir = 0;
networker 1:9f3821db3048 256 rs->dlci = (ch<<1)|dir;
networker 1:9f3821db3048 257 rs->State = SocketState_Listen;
networker 1:9f3821db3048 258 rs->serdevice = 0;//don't know yet
networker 1:9f3821db3048 259 sckts[slot] = rs->ID;
networker 1:9f3821db3048 260 printf("RFCOMM listener socket %d for ch %d (dlci 0x%02X) is assigned to slot %d\n", rs->ID, ch, rs->dlci, slot);
networker 1:9f3821db3048 261 return rs->ID;
networker 1:9f3821db3048 262 }
networker 1:9f3821db3048 263 /*
networker 1:9f3821db3048 264 virtual int Accept(SocketInternal *sock, int scid, int rxid) { //called indirectly from BTDevice::Control
networker 1:9f3821db3048 265 //sock is registered as an RFCOMM sock but we use it as an L2CAP sock
networker 1:9f3821db3048 266 //sock->type=RFCOM, meaning open/close/send/accept go to RFCOMMManager first
networker 1:9f3821db3048 267 //sock->userData = BTDevice, necessary to make the call back to BTDevice::Accept
networker 1:9f3821db3048 268 //Internal = L2CAPSocket, for scid, dcid
networker 1:9f3821db3048 269 BTDevice *l2cap = (BTDevice*)sock->userData;
networker 1:9f3821db3048 270 //sock->si.dcid = scid
networker 1:9f3821db3048 271 //sock->si.scid = something based on sock->ID
networker 1:9f3821db3048 272 serverSock = sock->ID;
networker 1:9f3821db3048 273 printf("Invoking accept on %p (%s) for sock %d and scid=%d\n", l2cap, l2cap->Name(), sock->ID, scid);
networker 1:9f3821db3048 274 return l2cap->Accept(sock, scid, rxid); //connect 'serverSock' to the remote RFCOMM client
networker 1:9f3821db3048 275 }
networker 1:9f3821db3048 276 */
networker 0:8d7b47ceb9f5 277 virtual int Send(SocketInternal* sock, const u8* data, int len) {
networker 0:8d7b47ceb9f5 278 RFCOMMSocket *s = (RFCOMMSocket*)sock;
networker 0:8d7b47ceb9f5 279 return s->serdevice->Send(sock, data, len);
networker 0:8d7b47ceb9f5 280 }
networker 1:9f3821db3048 281
networker 0:8d7b47ceb9f5 282 virtual int Close(SocketInternal* sock) {
networker 0:8d7b47ceb9f5 283 RFCOMMSocket *s = (RFCOMMSocket*)sock;
networker 0:8d7b47ceb9f5 284 return s->serdevice->Close(sock);
networker 0:8d7b47ceb9f5 285 }
networker 1:9f3821db3048 286
networker 3:a90b1a8fd184 287 virtual int SetOpt(SocketInternal* sock, int so, int* data, int len) {
networker 3:a90b1a8fd184 288 RFCOMMSocket *s = (RFCOMMSocket*)sock;
networker 3:a90b1a8fd184 289 return s->serdevice->SetOpt(sock, so, data, len);
networker 3:a90b1a8fd184 290 }
networker 3:a90b1a8fd184 291
networker 3:a90b1a8fd184 292 virtual int GetOpt(SocketInternal* sock, int so, int* data, int len) {
networker 3:a90b1a8fd184 293 RFCOMMSocket *s = (RFCOMMSocket*)sock;
networker 3:a90b1a8fd184 294 return s->serdevice->GetOpt(sock, so, data, len);
networker 3:a90b1a8fd184 295 }
networker 3:a90b1a8fd184 296
networker 0:8d7b47ceb9f5 297 virtual char* Name() {
networker 0:8d7b47ceb9f5 298 return "RFCOMMManager SocketHandler";
networker 0:8d7b47ceb9f5 299 }
networker 0:8d7b47ceb9f5 300
networker 0:8d7b47ceb9f5 301 rfcomm* NewRfCommDevice() {//allocate a new RFCOMM device from the pool
networker 0:8d7b47ceb9f5 302 for (int i = 0; i < MAX_RFCOMM_DEVICES; i++)
networker 0:8d7b47ceb9f5 303 if (!_devs[i].InUse())
networker 0:8d7b47ceb9f5 304 return _devs+i;
networker 0:8d7b47ceb9f5 305 return 0;
networker 0:8d7b47ceb9f5 306 }
networker 0:8d7b47ceb9f5 307
networker 0:8d7b47ceb9f5 308 rfcomm* FindRfCommDevice(BD_ADDR* ad) {//get a specific RFCOMM device from the pool
networker 0:8d7b47ceb9f5 309 for (int i = 0; i < MAX_RFCOMM_DEVICES; i++)
networker 0:8d7b47ceb9f5 310 if (_devs[i].InUse() && memcmp(ad, &_devs[i]._addr, 6)==0)
networker 0:8d7b47ceb9f5 311 return _devs+i;
networker 0:8d7b47ceb9f5 312 return 0;
networker 0:8d7b47ceb9f5 313 }
networker 1:9f3821db3048 314
networker 1:9f3821db3048 315 static void SerServer(int socket, SocketState state, const u8* data, int len, void* userData) {
networker 1:9f3821db3048 316 printfBytes("SerServer: ", data, len);
networker 1:9f3821db3048 317 //userData is the rfcomm
networker 1:9f3821db3048 318 rfcomm::OnRfCommControl(socket, state, data, len, userData);
networker 1:9f3821db3048 319 }
networker 1:9f3821db3048 320 //friend rfcomm;
networker 0:8d7b47ceb9f5 321 };
networker 0:8d7b47ceb9f5 322
networker 1:9f3821db3048 323 int set_remote_port_parameters(int socket, port_settings *p);
networker 1:9f3821db3048 324 extern RFCOMMManager rfcomm_manager;
networker 0:8d7b47ceb9f5 325
networker 0:8d7b47ceb9f5 326 #endif