1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: sw=2 ts=4 et :
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #ifndef DOM_PLUGINS_PLUGINMESSAGEUTILS_H
8 #define DOM_PLUGINS_PLUGINMESSAGEUTILS_H
9
10 #include "ipc/IPCMessageUtils.h"
11 #include "base/message_loop.h"
12 #include "base/shared_memory.h"
13
14 #include "mozilla/ipc/CrossProcessMutex.h"
15 #include "mozilla/ipc/MessageChannel.h"
16 #include "mozilla/ipc/ProtocolUtils.h"
17 #include "mozilla/UniquePtr.h"
18 #include "gfxipc/ShadowLayerUtils.h"
19
20 #include "npapi.h"
21 #include "npruntime.h"
22 #include "npfunctions.h"
23 #include "nsString.h"
24 #include "nsTArray.h"
25 #include "mozilla/Logging.h"
26 #include "nsHashKeys.h"
27
28 #ifdef XP_MACOSX
29 # include "PluginInterposeOSX.h"
30 #else
31 namespace mac_plugin_interposing {
32 class NSCursorInfo {};
33 } // namespace mac_plugin_interposing
34 #endif
35 using mac_plugin_interposing::NSCursorInfo;
36
37 namespace mozilla {
38 namespace plugins {
39
40 using layers::SurfaceDescriptorX11;
41
42 enum ScriptableObjectType { LocalObject, Proxy };
43
44 mozilla::ipc::RacyInterruptPolicy MediateRace(
45 const mozilla::ipc::MessageChannel::MessageInfo& parent,
46 const mozilla::ipc::MessageChannel::MessageInfo& child);
47
48 std::string MungePluginDsoPath(const std::string& path);
49 std::string UnmungePluginDsoPath(const std::string& munged);
50
51 extern mozilla::LogModule* GetPluginLog();
52
53 #if defined(_MSC_VER)
54 # define FULLFUNCTION __FUNCSIG__
55 #elif defined(__GNUC__)
56 # define FULLFUNCTION __PRETTY_FUNCTION__
57 #else
58 # define FULLFUNCTION __FUNCTION__
59 #endif
60
61 #define PLUGIN_LOG_DEBUG(args) \
62 MOZ_LOG(GetPluginLog(), mozilla::LogLevel::Debug, args)
63 #define PLUGIN_LOG_DEBUG_FUNCTION \
64 MOZ_LOG(GetPluginLog(), mozilla::LogLevel::Debug, ("%s", FULLFUNCTION))
65 #define PLUGIN_LOG_DEBUG_METHOD \
66 MOZ_LOG(GetPluginLog(), mozilla::LogLevel::Debug, \
67 ("%s [%p]", FULLFUNCTION, (void*)this))
68
69 /**
70 * This is NPByteRange without the linked list.
71 */
72 struct IPCByteRange {
73 int32_t offset;
74 uint32_t length;
75 };
76
77 typedef nsTArray<IPCByteRange> IPCByteRanges;
78
79 typedef nsCString Buffer;
80
81 struct NPRemoteWindow {
82 NPRemoteWindow();
83 uint64_t window;
84 int32_t x;
85 int32_t y;
86 uint32_t width;
87 uint32_t height;
88 NPRect clipRect;
89 NPWindowType type;
90 #if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
91 VisualID visualID;
92 Colormap colormap;
93 #endif /* XP_UNIX */
94 #if defined(XP_MACOSX) || defined(XP_WIN)
95 double contentsScaleFactor;
96 #endif
97 };
98
99 // This struct is like NPAudioDeviceChangeDetails, only it uses a
100 // std::wstring instead of a const wchar_t* for the defaultDevice.
101 // This gives us the necessary memory-ownership semantics without
102 // requiring C++ objects in npapi.h.
103 struct NPAudioDeviceChangeDetailsIPC {
104 int32_t flow;
105 int32_t role;
106 std::wstring defaultDevice;
107 };
108
109 struct NPAudioDeviceStateChangedIPC {
110 std::wstring device;
111 uint32_t state;
112 };
113
114 #ifdef XP_WIN
115 typedef HWND NativeWindowHandle;
116 #elif defined(MOZ_X11)
117 typedef XID NativeWindowHandle;
118 #elif defined(XP_DARWIN) || defined(ANDROID)
119 typedef intptr_t NativeWindowHandle; // never actually used, will always be 0
120 #else
121 # error Need NativeWindowHandle for this platform
122 #endif
123
124 #ifdef XP_WIN
125 typedef base::SharedMemoryHandle WindowsSharedMemoryHandle;
126 typedef HANDLE DXGISharedSurfaceHandle;
127 #else // XP_WIN
128 typedef mozilla::null_t WindowsSharedMemoryHandle;
129 typedef mozilla::null_t DXGISharedSurfaceHandle;
130 #endif
131
132 // XXX maybe not the best place for these. better one?
133
134 #define VARSTR(v_) \
135 case v_: \
136 return #v_
NPPVariableToString(NPPVariable aVar)137 inline const char* NPPVariableToString(NPPVariable aVar) {
138 switch (aVar) {
139 VARSTR(NPPVpluginNameString);
140 VARSTR(NPPVpluginDescriptionString);
141 VARSTR(NPPVpluginWindowBool);
142 VARSTR(NPPVpluginTransparentBool);
143 VARSTR(NPPVjavaClass);
144 VARSTR(NPPVpluginWindowSize);
145 VARSTR(NPPVpluginTimerInterval);
146
147 VARSTR(NPPVpluginScriptableInstance);
148 VARSTR(NPPVpluginScriptableIID);
149
150 VARSTR(NPPVjavascriptPushCallerBool);
151
152 VARSTR(NPPVpluginKeepLibraryInMemory);
153 VARSTR(NPPVpluginNeedsXEmbed);
154
155 VARSTR(NPPVpluginScriptableNPObject);
156
157 VARSTR(NPPVformValue);
158
159 VARSTR(NPPVpluginUrlRequestsDisplayedBool);
160
161 VARSTR(NPPVpluginWantsAllNetworkStreams);
162
163 #ifdef XP_MACOSX
164 VARSTR(NPPVpluginDrawingModel);
165 VARSTR(NPPVpluginEventModel);
166 #endif
167
168 #ifdef XP_WIN
169 VARSTR(NPPVpluginRequiresAudioDeviceChanges);
170 #endif
171
172 default:
173 return "???";
174 }
175 }
176
NPNVariableToString(NPNVariable aVar)177 inline const char* NPNVariableToString(NPNVariable aVar) {
178 switch (aVar) {
179 VARSTR(NPNVxDisplay);
180 VARSTR(NPNVxtAppContext);
181 VARSTR(NPNVnetscapeWindow);
182 VARSTR(NPNVjavascriptEnabledBool);
183 VARSTR(NPNVasdEnabledBool);
184 VARSTR(NPNVisOfflineBool);
185
186 VARSTR(NPNVserviceManager);
187 VARSTR(NPNVDOMElement);
188 VARSTR(NPNVDOMWindow);
189 VARSTR(NPNVToolkit);
190 VARSTR(NPNVSupportsXEmbedBool);
191
192 VARSTR(NPNVWindowNPObject);
193
194 VARSTR(NPNVPluginElementNPObject);
195
196 VARSTR(NPNVSupportsWindowless);
197
198 VARSTR(NPNVprivateModeBool);
199 VARSTR(NPNVdocumentOrigin);
200
201 #ifdef XP_WIN
202 VARSTR(NPNVaudioDeviceChangeDetails);
203 #endif
204
205 default:
206 return "???";
207 }
208 }
209 #undef VARSTR
210
IsPluginThread()211 inline bool IsPluginThread() {
212 MessageLoop* loop = MessageLoop::current();
213 if (!loop) return false;
214 return (loop->type() == MessageLoop::TYPE_UI);
215 }
216
AssertPluginThread()217 inline void AssertPluginThread() {
218 MOZ_RELEASE_ASSERT(IsPluginThread(),
219 "Should be on the plugin's main thread!");
220 }
221
222 #define ENSURE_PLUGIN_THREAD(retval) \
223 PR_BEGIN_MACRO \
224 if (!IsPluginThread()) { \
225 NS_WARNING("Not running on the plugin's main thread!"); \
226 return (retval); \
227 } \
228 PR_END_MACRO
229
230 #define ENSURE_PLUGIN_THREAD_VOID() \
231 PR_BEGIN_MACRO \
232 if (!IsPluginThread()) { \
233 NS_WARNING("Not running on the plugin's main thread!"); \
234 return; \
235 } \
236 PR_END_MACRO
237
238 void DeferNPObjectLastRelease(const NPNetscapeFuncs* f, NPObject* o);
239 void DeferNPVariantLastRelease(const NPNetscapeFuncs* f, NPVariant* v);
240
IsDrawingModelDirect(int16_t aModel)241 inline bool IsDrawingModelDirect(int16_t aModel) {
242 return aModel == NPDrawingModelAsyncBitmapSurface
243 #if defined(XP_WIN)
244 || aModel == NPDrawingModelAsyncWindowsDXGISurface
245 #endif
246 ;
247 }
248
249 // in NPAPI, char* == nullptr is sometimes meaningful. the following is
250 // helper code for dealing with nullable nsCString's
NullableString(const char * aString)251 inline nsCString NullableString(const char* aString) {
252 if (!aString) {
253 return VoidCString();
254 }
255 return nsCString(aString);
256 }
257
NullableStringGet(const nsCString & str)258 inline const char* NullableStringGet(const nsCString& str) {
259 if (str.IsVoid()) return nullptr;
260
261 return str.get();
262 }
263
264 struct DeletingObjectEntry : public nsPtrHashKey<NPObject> {
DeletingObjectEntryDeletingObjectEntry265 explicit DeletingObjectEntry(const NPObject* key)
266 : nsPtrHashKey<NPObject>(key), mDeleted(false) {}
267
268 bool mDeleted;
269 };
270
271 } /* namespace plugins */
272
273 } /* namespace mozilla */
274
275 namespace IPC {
276
277 template <>
278 struct ParamTraits<NPRect> {
279 typedef NPRect paramType;
280
281 static void Write(Message* aMsg, const paramType& aParam) {
282 WriteParam(aMsg, aParam.top);
283 WriteParam(aMsg, aParam.left);
284 WriteParam(aMsg, aParam.bottom);
285 WriteParam(aMsg, aParam.right);
286 }
287
288 static bool Read(const Message* aMsg, PickleIterator* aIter,
289 paramType* aResult) {
290 uint16_t top, left, bottom, right;
291 if (ReadParam(aMsg, aIter, &top) && ReadParam(aMsg, aIter, &left) &&
292 ReadParam(aMsg, aIter, &bottom) && ReadParam(aMsg, aIter, &right)) {
293 aResult->top = top;
294 aResult->left = left;
295 aResult->bottom = bottom;
296 aResult->right = right;
297 return true;
298 }
299 return false;
300 }
301
302 static void Log(const paramType& aParam, std::wstring* aLog) {
303 aLog->append(StringPrintf(L"[%u, %u, %u, %u]", aParam.top, aParam.left,
304 aParam.bottom, aParam.right));
305 }
306 };
307
308 template <>
309 struct ParamTraits<NPWindowType>
310 : public ContiguousEnumSerializerInclusive<
311 NPWindowType, NPWindowType::NPWindowTypeWindow,
312 NPWindowType::NPWindowTypeDrawable> {};
313
314 template <>
315 struct ParamTraits<mozilla::plugins::NPRemoteWindow> {
316 typedef mozilla::plugins::NPRemoteWindow paramType;
317
318 static void Write(Message* aMsg, const paramType& aParam) {
319 aMsg->WriteUInt64(aParam.window);
320 WriteParam(aMsg, aParam.x);
321 WriteParam(aMsg, aParam.y);
322 WriteParam(aMsg, aParam.width);
323 WriteParam(aMsg, aParam.height);
324 WriteParam(aMsg, aParam.clipRect);
325 WriteParam(aMsg, aParam.type);
326 #if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
327 aMsg->WriteULong(aParam.visualID);
328 aMsg->WriteULong(aParam.colormap);
329 #endif
330 #if defined(XP_MACOSX) || defined(XP_WIN)
331 aMsg->WriteDouble(aParam.contentsScaleFactor);
332 #endif
333 }
334
335 static bool Read(const Message* aMsg, PickleIterator* aIter,
336 paramType* aResult) {
337 uint64_t window;
338 int32_t x, y;
339 uint32_t width, height;
340 NPRect clipRect;
341 NPWindowType type;
342 if (!(aMsg->ReadUInt64(aIter, &window) && ReadParam(aMsg, aIter, &x) &&
343 ReadParam(aMsg, aIter, &y) && ReadParam(aMsg, aIter, &width) &&
344 ReadParam(aMsg, aIter, &height) &&
345 ReadParam(aMsg, aIter, &clipRect) && ReadParam(aMsg, aIter, &type)))
346 return false;
347
348 #if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
349 unsigned long visualID;
350 unsigned long colormap;
351 if (!(aMsg->ReadULong(aIter, &visualID) &&
352 aMsg->ReadULong(aIter, &colormap)))
353 return false;
354 #endif
355
356 #if defined(XP_MACOSX) || defined(XP_WIN)
357 double contentsScaleFactor;
358 if (!aMsg->ReadDouble(aIter, &contentsScaleFactor)) return false;
359 #endif
360
361 aResult->window = window;
362 aResult->x = x;
363 aResult->y = y;
364 aResult->width = width;
365 aResult->height = height;
366 aResult->clipRect = clipRect;
367 aResult->type = type;
368 #if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
369 aResult->visualID = visualID;
370 aResult->colormap = colormap;
371 #endif
372 #if defined(XP_MACOSX) || defined(XP_WIN)
373 aResult->contentsScaleFactor = contentsScaleFactor;
374 #endif
375 return true;
376 }
377
378 static void Log(const paramType& aParam, std::wstring* aLog) {
379 aLog->append(StringPrintf(L"[%u, %d, %d, %u, %u, %d",
380 (unsigned long)aParam.window, aParam.x, aParam.y,
381 aParam.width, aParam.height, (long)aParam.type));
382 }
383 };
384
385 #ifdef XP_MACOSX
386 template <>
387 struct ParamTraits<NPNSString*> {
388 // Empty string writes a length of 0 and no buffer.
389 // We don't write a nullptr terminating character in buffers.
390 static void Write(Message* aMsg, NPNSString* aParam) {
391 CFStringRef cfString = (CFStringRef)aParam;
392
393 // Write true if we have a string, false represents nullptr.
394 aMsg->WriteBool(!!cfString);
395 if (!cfString) {
396 return;
397 }
398
399 long length = ::CFStringGetLength(cfString);
400 WriteParam(aMsg, length);
401 if (length == 0) {
402 return;
403 }
404
405 // Attempt to get characters without any allocation/conversion.
406 if (::CFStringGetCharactersPtr(cfString)) {
407 aMsg->WriteBytes(::CFStringGetCharactersPtr(cfString),
408 length * sizeof(UniChar));
409 } else {
410 UniChar* buffer = (UniChar*)moz_xmalloc(length * sizeof(UniChar));
411 ::CFStringGetCharacters(cfString, ::CFRangeMake(0, length), buffer);
412 aMsg->WriteBytes(buffer, length * sizeof(UniChar));
413 free(buffer);
414 }
415 }
416
417 static bool Read(const Message* aMsg, PickleIterator* aIter,
418 NPNSString** aResult) {
419 bool haveString = false;
420 if (!aMsg->ReadBool(aIter, &haveString)) {
421 return false;
422 }
423 if (!haveString) {
424 *aResult = nullptr;
425 return true;
426 }
427
428 long length;
429 if (!ReadParam(aMsg, aIter, &length)) {
430 return false;
431 }
432
433 // Avoid integer multiplication overflow.
434 if (length > INT_MAX / static_cast<long>(sizeof(UniChar))) {
435 return false;
436 }
437
438 auto chars = mozilla::MakeUnique<UniChar[]>(length);
439 if (length != 0) {
440 if (!aMsg->ReadBytesInto(aIter, chars.get(), length * sizeof(UniChar))) {
441 return false;
442 }
443 }
444
445 *aResult = (NPNSString*)::CFStringCreateWithBytes(
446 kCFAllocatorDefault, (UInt8*)chars.get(), length * sizeof(UniChar),
447 kCFStringEncodingUTF16, false);
448 if (!*aResult) {
449 return false;
450 }
451
452 return true;
453 }
454 };
455 #endif
456
457 #ifdef XP_MACOSX
458 template <>
459 struct ParamTraits<NSCursorInfo> {
460 typedef NSCursorInfo paramType;
461
462 static void Write(Message* aMsg, const paramType& aParam) {
463 NSCursorInfo::Type type = aParam.GetType();
464
465 aMsg->WriteInt(type);
466
467 nsPoint hotSpot = aParam.GetHotSpot();
468 WriteParam(aMsg, hotSpot.x);
469 WriteParam(aMsg, hotSpot.y);
470
471 uint32_t dataLength = aParam.GetCustomImageDataLength();
472 WriteParam(aMsg, dataLength);
473 if (dataLength == 0) {
474 return;
475 }
476
477 uint8_t* buffer = (uint8_t*)moz_xmalloc(dataLength);
478 memcpy(buffer, aParam.GetCustomImageData(), dataLength);
479 aMsg->WriteBytes(buffer, dataLength);
480 free(buffer);
481 }
482
483 static bool Read(const Message* aMsg, PickleIterator* aIter,
484 paramType* aResult) {
485 NSCursorInfo::Type type;
486 if (!aMsg->ReadInt(aIter, (int*)&type)) {
487 return false;
488 }
489
490 nscoord hotSpotX, hotSpotY;
491 if (!ReadParam(aMsg, aIter, &hotSpotX) ||
492 !ReadParam(aMsg, aIter, &hotSpotY)) {
493 return false;
494 }
495
496 uint32_t dataLength;
497 if (!ReadParam(aMsg, aIter, &dataLength)) {
498 return false;
499 }
500
501 auto data = mozilla::MakeUnique<uint8_t[]>(dataLength);
502 if (dataLength != 0) {
503 if (!aMsg->ReadBytesInto(aIter, data.get(), dataLength)) {
504 return false;
505 }
506 }
507
508 aResult->SetType(type);
509 aResult->SetHotSpot(nsPoint(hotSpotX, hotSpotY));
510 aResult->SetCustomImageData(data.get(), dataLength);
511
512 return true;
513 }
514
515 static void Log(const paramType& aParam, std::wstring* aLog) {
516 const char* typeName = aParam.GetTypeName();
517 nsPoint hotSpot = aParam.GetHotSpot();
518 int hotSpotX, hotSpotY;
519 # ifdef NS_COORD_IS_FLOAT
520 hotSpotX = rint(hotSpot.x);
521 hotSpotY = rint(hotSpot.y);
522 # else
523 hotSpotX = hotSpot.x;
524 hotSpotY = hotSpot.y;
525 # endif
526 uint32_t dataLength = aParam.GetCustomImageDataLength();
527 uint8_t* data = aParam.GetCustomImageData();
528
529 aLog->append(StringPrintf(L"[%s, (%i %i), %u, %p]", typeName, hotSpotX,
530 hotSpotY, dataLength, data));
531 }
532 };
533 #else
534 template <>
535 struct ParamTraits<NSCursorInfo> {
536 typedef NSCursorInfo paramType;
537 static void Write(Message* aMsg, const paramType& aParam) {
538 MOZ_CRASH("NSCursorInfo isn't meaningful on this platform");
539 }
540 static bool Read(const Message* aMsg, PickleIterator* aIter,
541 paramType* aResult) {
542 MOZ_CRASH("NSCursorInfo isn't meaningful on this platform");
543 return false;
544 }
545 };
546 #endif // #ifdef XP_MACOSX
547
548 template <>
549 struct ParamTraits<mozilla::plugins::IPCByteRange> {
550 typedef mozilla::plugins::IPCByteRange paramType;
551
552 static void Write(Message* aMsg, const paramType& aParam) {
553 WriteParam(aMsg, aParam.offset);
554 WriteParam(aMsg, aParam.length);
555 }
556
557 static bool Read(const Message* aMsg, PickleIterator* aIter,
558 paramType* aResult) {
559 paramType p;
560 if (ReadParam(aMsg, aIter, &p.offset) &&
561 ReadParam(aMsg, aIter, &p.length)) {
562 *aResult = p;
563 return true;
564 }
565 return false;
566 }
567 };
568
569 template <>
570 struct ParamTraits<NPNVariable>
571 : public ContiguousEnumSerializer<NPNVariable, NPNVariable::NPNVxDisplay,
572 NPNVariable::NPNVLast> {};
573
574 // The only accepted value is NPNURLVariable::NPNURLVProxy
575 template <>
576 struct ParamTraits<NPNURLVariable>
577 : public ContiguousEnumSerializerInclusive<NPNURLVariable,
578 NPNURLVariable::NPNURLVProxy,
579 NPNURLVariable::NPNURLVProxy> {};
580
581 template <>
582 struct ParamTraits<NPCoordinateSpace>
583 : public ContiguousEnumSerializerInclusive<
584 NPCoordinateSpace, NPCoordinateSpace::NPCoordinateSpacePlugin,
585 NPCoordinateSpace::NPCoordinateSpaceFlippedScreen> {};
586
587 template <>
588 struct ParamTraits<mozilla::plugins::NPAudioDeviceChangeDetailsIPC> {
589 typedef mozilla::plugins::NPAudioDeviceChangeDetailsIPC paramType;
590
591 static void Write(Message* aMsg, const paramType& aParam) {
592 WriteParam(aMsg, aParam.flow);
593 WriteParam(aMsg, aParam.role);
594 WriteParam(aMsg, aParam.defaultDevice);
595 }
596
597 static bool Read(const Message* aMsg, PickleIterator* aIter,
598 paramType* aResult) {
599 int32_t flow, role;
600 std::wstring defaultDevice;
601 if (ReadParam(aMsg, aIter, &flow) && ReadParam(aMsg, aIter, &role) &&
602 ReadParam(aMsg, aIter, &defaultDevice)) {
603 aResult->flow = flow;
604 aResult->role = role;
605 aResult->defaultDevice = defaultDevice;
606 return true;
607 }
608 return false;
609 }
610
611 static void Log(const paramType& aParam, std::wstring* aLog) {
612 aLog->append(StringPrintf(L"[%d, %d, %S]", aParam.flow, aParam.role,
613 aParam.defaultDevice.c_str()));
614 }
615 };
616
617 template <>
618 struct ParamTraits<mozilla::plugins::NPAudioDeviceStateChangedIPC> {
619 typedef mozilla::plugins::NPAudioDeviceStateChangedIPC paramType;
620
621 static void Write(Message* aMsg, const paramType& aParam) {
622 WriteParam(aMsg, aParam.device);
623 WriteParam(aMsg, aParam.state);
624 }
625
626 static bool Read(const Message* aMsg, PickleIterator* aIter,
627 paramType* aResult) {
628 int32_t state;
629 std::wstring device;
630 if (ReadParam(aMsg, aIter, &device) && ReadParam(aMsg, aIter, &state)) {
631 aResult->device = device;
632 aResult->state = state;
633 return true;
634 }
635 return false;
636 }
637
638 static void Log(const paramType& aParam, std::wstring* aLog) {
639 aLog->append(StringPrintf(L"[%S,%d]", aParam.device.c_str(), aParam.state));
640 }
641 };
642 } /* namespace IPC */
643
644 // Serializing NPEvents is completely platform-specific and can be rather
645 // intricate depending on the platform. So for readability we split it
646 // into separate files and have the only macro crud live here.
647 //
648 // NB: these guards are based on those where struct NPEvent is defined
649 // in npapi.h. They should be kept in sync.
650 #if defined(XP_MACOSX)
651 # include "mozilla/plugins/NPEventOSX.h"
652 #elif defined(XP_WIN)
653 # include "mozilla/plugins/NPEventWindows.h"
654 #elif defined(ANDROID)
655 # include "mozilla/plugins/NPEventAndroid.h"
656 #elif defined(XP_UNIX)
657 # include "mozilla/plugins/NPEventUnix.h"
658 #else
659 # error Unsupported platform
660 #endif
661
662 #endif /* DOM_PLUGINS_PLUGINMESSAGEUTILS_H */
663