master
go 788 lines 23.4 KB
Raw
1 //go:build windows
2
3 // Windows SHM transport — shared memory data plane with spin + kernel event
4 // synchronization. Wire-compatible with the C and Rust implementations.
5 //
6 // Pure Go — no cgo. Works with CGO_ENABLED=0.
7
8 package windows
9
10 import (
11 "encoding/binary"
12 "errors"
13 "fmt"
14 "sync/atomic"
15 "syscall"
16 "unsafe"
17 )
18
19 // ---------------------------------------------------------------------------
20 // Constants
21 // ---------------------------------------------------------------------------
22
23 const (
24 winShmMagic uint32 = 0x4e535748 // "NSWH"
25 winShmVersion uint32 = 3
26 winShmHeaderLen uint32 = 128
27 winShmCachelineSize uint32 = 64
28 winShmDefaultSpin uint32 = 1024
29
30 WinShmProfileHybrid uint32 = 0x02
31 WinShmProfileBusywait uint32 = 0x04
32
33 // Header field offsets
34 wshOFFMagic = 0
35 wshOFFVersion = 4
36 wshOFFHeaderLen = 8
37 wshOFFProfile = 12
38 wshOFFReqOffset = 16
39 wshOFFReqCapacity = 20
40 wshOFFRespOffset = 24
41 wshOFFRespCapacity = 28
42 wshOFFSpinTries = 32
43 wshOFFReqLen = 36
44 wshOFFRespLen = 40
45 wshOFFReqClientClosed = 44
46 wshOFFReqServerWaiting = 48
47 wshOFFRespServerClosed = 52
48 wshOFFRespClientWaiting = 56
49 wshOFFReqSeq = 64
50 wshOFFRespSeq = 72
51 )
52
53 // ---------------------------------------------------------------------------
54 // Errors
55 // ---------------------------------------------------------------------------
56
57 var (
58 ErrWinShmBadParam = errors.New("invalid Windows SHM argument")
59 ErrWinShmCreateMapping = errors.New("CreateFileMappingW failed")
60 ErrWinShmOpenMapping = errors.New("OpenFileMappingW failed")
61 ErrWinShmMapView = errors.New("MapViewOfFile failed")
62 ErrWinShmCreateEvent = errors.New("CreateEventW failed")
63 ErrWinShmOpenEvent = errors.New("OpenEventW failed")
64 ErrWinShmAddrInUse = errors.New("Windows SHM object name already in use by live server")
65 ErrWinShmBadMagic = errors.New("Windows SHM header magic mismatch")
66 ErrWinShmBadVersion = errors.New("Windows SHM header version mismatch")
67 ErrWinShmBadHeader = errors.New("Windows SHM header_len mismatch")
68 ErrWinShmBadProfile = errors.New("Windows SHM profile mismatch")
69 ErrWinShmMsgTooLarge = errors.New("message exceeds Windows SHM area capacity")
70 ErrWinShmTimeout = errors.New("Windows SHM wait timed out")
71 ErrWinShmDisconnected = errors.New("Windows SHM peer closed")
72 )
73
74 // ---------------------------------------------------------------------------
75 // Win32 syscall imports
76 // ---------------------------------------------------------------------------
77
78 var (
79 procCreateFileMappingW = modkernel32.NewProc("CreateFileMappingW")
80 procOpenFileMappingW = modkernel32.NewProc("OpenFileMappingW")
81 procMapViewOfFile = modkernel32.NewProc("MapViewOfFile")
82 procUnmapViewOfFile = modkernel32.NewProc("UnmapViewOfFile")
83 procCreateEventW = modkernel32.NewProc("CreateEventW")
84 procOpenEventW = modkernel32.NewProc("OpenEventW")
85 procSetEvent = modkernel32.NewProc("SetEvent")
86 procWaitForSingleObj = modkernel32.NewProc("WaitForSingleObject")
87 procGetTickCount64 = modkernel32.NewProc("GetTickCount64")
88 )
89
90 type winShmProcCall func(a ...uintptr) (uintptr, uintptr, error)
91
92 func callCreateFileMappingW(a ...uintptr) (uintptr, uintptr, error) {
93 return procCreateFileMappingW.Call(a...)
94 }
95 func callOpenFileMappingW(a ...uintptr) (uintptr, uintptr, error) {
96 return procOpenFileMappingW.Call(a...)
97 }
98 func callMapViewOfFile(a ...uintptr) (uintptr, uintptr, error) { return procMapViewOfFile.Call(a...) }
99 func callCreateEventW(a ...uintptr) (uintptr, uintptr, error) { return procCreateEventW.Call(a...) }
100 func callOpenEventW(a ...uintptr) (uintptr, uintptr, error) { return procOpenEventW.Call(a...) }
101
102 var (
103 winShmCreateFileMappingW winShmProcCall = callCreateFileMappingW
104 winShmOpenFileMappingW winShmProcCall = callOpenFileMappingW
105 winShmMapViewOfFile winShmProcCall = callMapViewOfFile
106 winShmCreateEventW winShmProcCall = callCreateEventW
107 winShmOpenEventW winShmProcCall = callOpenEventW
108 )
109
110 const (
111 _PAGE_READWRITE = 0x04
112 _FILE_MAP_ALL_ACCESS = 0x000F001F
113 _EVENT_MODIFY_STATE = 0x0002
114 _SYNCHRONIZE = 0x00100000
115 _INFINITE = 0xFFFFFFFF
116 _WAIT_TIMEOUT = 0x00000102
117 _ERROR_ALREADY_EXISTS = 183
118 )
119
120 func isWindowsErrno(err error, want syscall.Errno) bool {
121 errno, ok := err.(syscall.Errno)
122 return ok && errno == want
123 }
124
125 // ---------------------------------------------------------------------------
126 // Role
127 // ---------------------------------------------------------------------------
128
129 // WinShmRole distinguishes server vs client.
130 type WinShmRole int
131
132 const (
133 WinShmRoleServer WinShmRole = 1
134 WinShmRoleClient WinShmRole = 2
135 )
136
137 // ---------------------------------------------------------------------------
138 // Context
139 // ---------------------------------------------------------------------------
140
141 // WinShmContext is a handle to a Windows SHM region.
142 type WinShmContext struct {
143 role WinShmRole
144 mapping syscall.Handle
145 base uintptr
146 size uintptr
147
148 reqEvent syscall.Handle
149 respEvent syscall.Handle
150
151 profile uint32
152 requestOffset uint32
153 requestCapacity uint32
154 responseOffset uint32
155 responseCapacity uint32
156 SpinTries uint32
157
158 localReqSeq int64
159 localRespSeq int64
160 }
161
162 // Role returns the context role.
163 func (c *WinShmContext) Role() WinShmRole { return c.role }
164
165 // GetRole returns the context role.
166 // Deprecated: use Role.
167 func (c *WinShmContext) GetRole() WinShmRole { return c.Role() }
168
169 // ---------------------------------------------------------------------------
170 // Server API
171 // ---------------------------------------------------------------------------
172
173 // WinShmServerCreate creates a per-session Windows SHM region.
174 func WinShmServerCreate(runDir, serviceName string, authToken, sessionID uint64,
175 profile, reqCapacity, respCapacity uint32) (*WinShmContext, error) {
176
177 if err := validateServiceName(serviceName); err != nil {
178 return nil, err
179 }
180 if err := validateWinShmProfile(profile); err != nil {
181 return nil, err
182 }
183
184 hash := computeShmHash(runDir, serviceName, authToken)
185 mappingName, err := buildWinShmObjectName(hash, serviceName, profile, sessionID, "mapping")
186 if err != nil {
187 return nil, err
188 }
189
190 reqCap := winShmAlignCacheline(reqCapacity)
191 respCap := winShmAlignCacheline(respCapacity)
192 reqOff := winShmAlignCacheline(winShmHeaderLen)
193 respOff := winShmAlignCacheline(reqOff + reqCap)
194 regionSize := uintptr(respOff + respCap)
195
196 // Create page-file backed mapping
197 r, _, callErr := winShmCreateFileMappingW(
198 uintptr(syscall.InvalidHandle), // page file
199 0, // NULL security
200 uintptr(_PAGE_READWRITE),
201 uintptr(regionSize>>32),
202 uintptr(regionSize&0xFFFFFFFF),
203 uintptr(unsafe.Pointer(&mappingName[0])),
204 )
205 mapping := syscall.Handle(r)
206 if mapping == 0 {
207 return nil, fmt.Errorf("%w: %v", ErrWinShmCreateMapping, callErr)
208 }
209 if isWindowsErrno(callErr, syscall.Errno(_ERROR_ALREADY_EXISTS)) {
210 syscall.CloseHandle(mapping)
211 return nil, ErrWinShmAddrInUse
212 }
213
214 // Map view
215 base, _, callErr := winShmMapViewOfFile(
216 uintptr(mapping),
217 uintptr(_FILE_MAP_ALL_ACCESS),
218 0, 0,
219 regionSize,
220 )
221 if base == 0 {
222 syscall.CloseHandle(mapping)
223 return nil, fmt.Errorf("%w: %v", ErrWinShmMapView, callErr)
224 }
225
226 // Zero region
227 data := unsafe.Slice((*byte)(unsafe.Pointer(base)), regionSize)
228 for i := range data {
229 data[i] = 0
230 }
231
232 // Write header
233 binary.NativeEndian.PutUint32(data[wshOFFMagic:], winShmMagic)
234 binary.NativeEndian.PutUint32(data[wshOFFVersion:], winShmVersion)
235 binary.NativeEndian.PutUint32(data[wshOFFHeaderLen:], winShmHeaderLen)
236 binary.NativeEndian.PutUint32(data[wshOFFProfile:], profile)
237 binary.NativeEndian.PutUint32(data[wshOFFReqOffset:], reqOff)
238 binary.NativeEndian.PutUint32(data[wshOFFReqCapacity:], reqCap)
239 binary.NativeEndian.PutUint32(data[wshOFFRespOffset:], respOff)
240 binary.NativeEndian.PutUint32(data[wshOFFRespCapacity:], respCap)
241 binary.NativeEndian.PutUint32(data[wshOFFSpinTries:], winShmDefaultSpin)
242
243 // Release fence
244 atomic.StoreInt32((*int32)(unsafe.Pointer(&data[wshOFFReqLen])), 0)
245
246 // Create events for HYBRID
247 var reqEvent, respEvent syscall.Handle
248 reqEvent = syscall.InvalidHandle
249 respEvent = syscall.InvalidHandle
250
251 if profile == WinShmProfileHybrid {
252 reqEventName, err := buildWinShmObjectName(hash, serviceName, profile, sessionID, "req_event")
253 if err != nil {
254 procUnmapViewOfFile.Call(base)
255 syscall.CloseHandle(mapping)
256 return nil, err
257 }
258
259 r, _, callErr := winShmCreateEventW(0, 0, 0,
260 uintptr(unsafe.Pointer(&reqEventName[0])))
261 if r == 0 {
262 procUnmapViewOfFile.Call(base)
263 syscall.CloseHandle(mapping)
264 return nil, fmt.Errorf("%w: req_event: %v", ErrWinShmCreateEvent, callErr)
265 }
266 reqEvent = syscall.Handle(r)
267 if isWindowsErrno(callErr, syscall.Errno(_ERROR_ALREADY_EXISTS)) {
268 syscall.CloseHandle(reqEvent)
269 procUnmapViewOfFile.Call(base)
270 syscall.CloseHandle(mapping)
271 return nil, ErrWinShmAddrInUse
272 }
273
274 respEventName, err := buildWinShmObjectName(hash, serviceName, profile, sessionID, "resp_event")
275 if err != nil {
276 syscall.CloseHandle(reqEvent)
277 procUnmapViewOfFile.Call(base)
278 syscall.CloseHandle(mapping)
279 return nil, err
280 }
281
282 r, _, callErr = winShmCreateEventW(0, 0, 0,
283 uintptr(unsafe.Pointer(&respEventName[0])))
284 if r == 0 {
285 syscall.CloseHandle(reqEvent)
286 procUnmapViewOfFile.Call(base)
287 syscall.CloseHandle(mapping)
288 return nil, fmt.Errorf("%w: resp_event: %v", ErrWinShmCreateEvent, callErr)
289 }
290 respEvent = syscall.Handle(r)
291 if isWindowsErrno(callErr, syscall.Errno(_ERROR_ALREADY_EXISTS)) {
292 syscall.CloseHandle(respEvent)
293 syscall.CloseHandle(reqEvent)
294 procUnmapViewOfFile.Call(base)
295 syscall.CloseHandle(mapping)
296 return nil, ErrWinShmAddrInUse
297 }
298 }
299
300 return &WinShmContext{
301 role: WinShmRoleServer,
302 mapping: mapping,
303 base: base,
304 size: regionSize,
305 reqEvent: reqEvent,
306 respEvent: respEvent,
307 profile: profile,
308 requestOffset: reqOff,
309 requestCapacity: reqCap,
310 responseOffset: respOff,
311 responseCapacity: respCap,
312 SpinTries: winShmDefaultSpin,
313 localReqSeq: 0,
314 localRespSeq: 0,
315 }, nil
316 }
317
318 // WinShmDestroy destroys a server SHM region.
319 func (c *WinShmContext) WinShmDestroy() {
320 if c.base != 0 {
321 data := unsafe.Slice((*byte)(unsafe.Pointer(c.base)), c.size)
322 atomic.StoreInt32((*int32)(unsafe.Pointer(&data[wshOFFRespServerClosed])), 1)
323 }
324
325 if c.profile == WinShmProfileHybrid && c.respEvent != syscall.InvalidHandle {
326 procSetEvent.Call(uintptr(c.respEvent))
327 }
328
329 c.cleanupHandles()
330 }
331
332 // ---------------------------------------------------------------------------
333 // Client API
334 // ---------------------------------------------------------------------------
335
336 // WinShmClientAttach attaches to an existing per-session Windows SHM region.
337 func WinShmClientAttach(runDir, serviceName string, authToken, sessionID uint64,
338 profile uint32) (*WinShmContext, error) {
339
340 if err := validateServiceName(serviceName); err != nil {
341 return nil, err
342 }
343 if err := validateWinShmProfile(profile); err != nil {
344 return nil, err
345 }
346
347 hash := computeShmHash(runDir, serviceName, authToken)
348 mappingName, err := buildWinShmObjectName(hash, serviceName, profile, sessionID, "mapping")
349 if err != nil {
350 return nil, err
351 }
352
353 r, _, callErr := winShmOpenFileMappingW(
354 uintptr(_FILE_MAP_ALL_ACCESS),
355 0,
356 uintptr(unsafe.Pointer(&mappingName[0])),
357 )
358 mapping := syscall.Handle(r)
359 if mapping == 0 {
360 return nil, fmt.Errorf("%w: %v", ErrWinShmOpenMapping, callErr)
361 }
362
363 base, _, callErr := winShmMapViewOfFile(
364 uintptr(mapping),
365 uintptr(_FILE_MAP_ALL_ACCESS),
366 0, 0, 0,
367 )
368 if base == 0 {
369 syscall.CloseHandle(mapping)
370 return nil, fmt.Errorf("%w: %v", ErrWinShmMapView, callErr)
371 }
372
373 // We need at least header_len to validate
374 data := unsafe.Slice((*byte)(unsafe.Pointer(base)), winShmHeaderLen)
375
376 // Acquire fence
377 atomic.LoadInt32((*int32)(unsafe.Pointer(&data[wshOFFReqLen])))
378
379 // Validate header
380 magic := binary.NativeEndian.Uint32(data[wshOFFMagic:])
381 if magic != winShmMagic {
382 procUnmapViewOfFile.Call(base)
383 syscall.CloseHandle(mapping)
384 return nil, ErrWinShmBadMagic
385 }
386 version := binary.NativeEndian.Uint32(data[wshOFFVersion:])
387 if version != winShmVersion {
388 procUnmapViewOfFile.Call(base)
389 syscall.CloseHandle(mapping)
390 return nil, ErrWinShmBadVersion
391 }
392 hdrLen := binary.NativeEndian.Uint32(data[wshOFFHeaderLen:])
393 if hdrLen != winShmHeaderLen {
394 procUnmapViewOfFile.Call(base)
395 syscall.CloseHandle(mapping)
396 return nil, ErrWinShmBadHeader
397 }
398 hdrProfile := binary.NativeEndian.Uint32(data[wshOFFProfile:])
399 if hdrProfile != profile {
400 procUnmapViewOfFile.Call(base)
401 syscall.CloseHandle(mapping)
402 return nil, ErrWinShmBadProfile
403 }
404
405 reqOff := binary.NativeEndian.Uint32(data[wshOFFReqOffset:])
406 reqCap := binary.NativeEndian.Uint32(data[wshOFFReqCapacity:])
407 respOff := binary.NativeEndian.Uint32(data[wshOFFRespOffset:])
408 respCap := binary.NativeEndian.Uint32(data[wshOFFRespCapacity:])
409 spin := binary.NativeEndian.Uint32(data[wshOFFSpinTries:])
410 regionSize := uintptr(respOff + respCap)
411
412 // Now reslice to full region
413 fullData := unsafe.Slice((*byte)(unsafe.Pointer(base)), regionSize)
414
415 // Read current sequence numbers via atomic
416 curReqSeq := atomic.LoadInt64((*int64)(unsafe.Pointer(&fullData[wshOFFReqSeq])))
417 curRespSeq := atomic.LoadInt64((*int64)(unsafe.Pointer(&fullData[wshOFFRespSeq])))
418
419 // Open events for HYBRID
420 var reqEvent, respEvent syscall.Handle
421 reqEvent = syscall.InvalidHandle
422 respEvent = syscall.InvalidHandle
423
424 if profile == WinShmProfileHybrid {
425 reqEventName, err := buildWinShmObjectName(hash, serviceName, profile, sessionID, "req_event")
426 if err != nil {
427 procUnmapViewOfFile.Call(base)
428 syscall.CloseHandle(mapping)
429 return nil, err
430 }
431
432 r, _, callErr := winShmOpenEventW(
433 uintptr(_EVENT_MODIFY_STATE|_SYNCHRONIZE),
434 0,
435 uintptr(unsafe.Pointer(&reqEventName[0])),
436 )
437 if r == 0 {
438 procUnmapViewOfFile.Call(base)
439 syscall.CloseHandle(mapping)
440 return nil, fmt.Errorf("%w: req_event: %v", ErrWinShmOpenEvent, callErr)
441 }
442 reqEvent = syscall.Handle(r)
443
444 respEventName, err := buildWinShmObjectName(hash, serviceName, profile, sessionID, "resp_event")
445 if err != nil {
446 syscall.CloseHandle(reqEvent)
447 procUnmapViewOfFile.Call(base)
448 syscall.CloseHandle(mapping)
449 return nil, err
450 }
451
452 r, _, callErr = winShmOpenEventW(
453 uintptr(_EVENT_MODIFY_STATE|_SYNCHRONIZE),
454 0,
455 uintptr(unsafe.Pointer(&respEventName[0])),
456 )
457 if r == 0 {
458 syscall.CloseHandle(reqEvent)
459 procUnmapViewOfFile.Call(base)
460 syscall.CloseHandle(mapping)
461 return nil, fmt.Errorf("%w: resp_event: %v", ErrWinShmOpenEvent, callErr)
462 }
463 respEvent = syscall.Handle(r)
464 }
465
466 return &WinShmContext{
467 role: WinShmRoleClient,
468 mapping: mapping,
469 base: base,
470 size: regionSize,
471 reqEvent: reqEvent,
472 respEvent: respEvent,
473 profile: profile,
474 requestOffset: reqOff,
475 requestCapacity: reqCap,
476 responseOffset: respOff,
477 responseCapacity: respCap,
478 SpinTries: spin,
479 localReqSeq: curReqSeq,
480 localRespSeq: curRespSeq,
481 }, nil
482 }
483
484 // WinShmClose closes a client SHM context.
485 func (c *WinShmContext) WinShmClose() {
486 if c.base != 0 {
487 data := unsafe.Slice((*byte)(unsafe.Pointer(c.base)), c.size)
488 atomic.StoreInt32((*int32)(unsafe.Pointer(&data[wshOFFReqClientClosed])), 1)
489 }
490
491 if c.profile == WinShmProfileHybrid && c.reqEvent != syscall.InvalidHandle {
492 procSetEvent.Call(uintptr(c.reqEvent))
493 }
494
495 c.cleanupHandles()
496 }
497
498 func (c *WinShmContext) cleanupHandles() {
499 if c.base != 0 {
500 procUnmapViewOfFile.Call(c.base)
501 c.base = 0
502 }
503 if c.mapping != syscall.InvalidHandle && c.mapping != 0 {
504 syscall.CloseHandle(c.mapping)
505 c.mapping = syscall.InvalidHandle
506 }
507 if c.reqEvent != syscall.InvalidHandle {
508 syscall.CloseHandle(c.reqEvent)
509 c.reqEvent = syscall.InvalidHandle
510 }
511 if c.respEvent != syscall.InvalidHandle {
512 syscall.CloseHandle(c.respEvent)
513 c.respEvent = syscall.InvalidHandle
514 }
515 c.size = 0
516 }
517
518 // ---------------------------------------------------------------------------
519 // Data plane
520 // ---------------------------------------------------------------------------
521
522 // WinShmSend publishes a message. The message must include the 32-byte
523 // outer header + payload, exactly as sent over Named Pipe.
524 func (c *WinShmContext) WinShmSend(msg []byte) error {
525 if c.base == 0 || len(msg) == 0 {
526 return fmt.Errorf("%w: null context or empty message", ErrWinShmBadParam)
527 }
528
529 var areaOff, areaCap uint32
530 var lenOff, seqOff, peerWaitingOff int
531 var peerEvent syscall.Handle
532
533 if c.role == WinShmRoleClient {
534 areaOff = c.requestOffset
535 areaCap = c.requestCapacity
536 lenOff = wshOFFReqLen
537 seqOff = wshOFFReqSeq
538 peerWaitingOff = wshOFFReqServerWaiting
539 peerEvent = c.reqEvent
540 } else {
541 areaOff = c.responseOffset
542 areaCap = c.responseCapacity
543 lenOff = wshOFFRespLen
544 seqOff = wshOFFRespSeq
545 peerWaitingOff = wshOFFRespClientWaiting
546 peerEvent = c.respEvent
547 }
548
549 if uint32(len(msg)) > areaCap {
550 return ErrWinShmMsgTooLarge
551 }
552
553 data := unsafe.Slice((*byte)(unsafe.Pointer(c.base)), c.size)
554
555 // 1. Write message data
556 copy(data[areaOff:], msg)
557
558 // 2. Store message length (atomic)
559 atomic.StoreInt32((*int32)(unsafe.Pointer(&data[lenOff])), int32(len(msg)))
560
561 // 3. Increment sequence number (atomic)
562 atomic.AddInt64((*int64)(unsafe.Pointer(&data[seqOff])), 1)
563
564 // 4. If HYBRID and peer waiting, signal event
565 if c.profile == WinShmProfileHybrid {
566 if atomic.LoadInt32((*int32)(unsafe.Pointer(&data[peerWaitingOff]))) != 0 {
567 procSetEvent.Call(uintptr(peerEvent))
568 }
569 }
570
571 if c.role == WinShmRoleClient {
572 c.localReqSeq++
573 } else {
574 c.localRespSeq++
575 }
576
577 return nil
578 }
579
580 // WinShmReceive receives a message into the caller-provided buffer.
581 func (c *WinShmContext) WinShmReceive(buf []byte, timeoutMs uint32) (int, error) {
582 if c.base == 0 {
583 return 0, fmt.Errorf("%w: null context", ErrWinShmBadParam)
584 }
585 if len(buf) == 0 {
586 return 0, fmt.Errorf("%w: empty buffer", ErrWinShmBadParam)
587 }
588
589 var areaOff, areaCap uint32
590 var lenOff, seqOff, selfWaitingOff, peerClosedOff int
591 var waitEvent syscall.Handle
592 var expectedSeq int64
593
594 if c.role == WinShmRoleServer {
595 areaOff = c.requestOffset
596 areaCap = c.requestCapacity
597 lenOff = wshOFFReqLen
598 seqOff = wshOFFReqSeq
599 selfWaitingOff = wshOFFReqServerWaiting
600 peerClosedOff = wshOFFReqClientClosed
601 waitEvent = c.reqEvent
602 expectedSeq = c.localReqSeq + 1
603 } else {
604 areaOff = c.responseOffset
605 areaCap = c.responseCapacity
606 lenOff = wshOFFRespLen
607 seqOff = wshOFFRespSeq
608 selfWaitingOff = wshOFFRespClientWaiting
609 peerClosedOff = wshOFFRespServerClosed
610 waitEvent = c.respEvent
611 expectedSeq = c.localRespSeq + 1
612 }
613
614 // The copy ceiling is the smaller of the caller buffer and the
615 // SHM area capacity. Prevents out-of-bounds reads on forged lengths.
616 maxCopy := len(buf)
617 if int(areaCap) < maxCopy {
618 maxCopy = int(areaCap)
619 }
620
621 data := unsafe.Slice((*byte)(unsafe.Pointer(c.base)), c.size)
622
623 // Phase 1: spin
624 observed := false
625 var mlen int32
626 for i := uint32(0); i < c.SpinTries; i++ {
627 cur := atomic.LoadInt64((*int64)(unsafe.Pointer(&data[seqOff])))
628 if cur >= expectedSeq {
629 mlen = atomic.LoadInt32((*int32)(unsafe.Pointer(&data[lenOff])))
630 if mlen > 0 && int(mlen) <= maxCopy {
631 copy(buf[:mlen], data[areaOff:areaOff+uint32(mlen)])
632 }
633 observed = true
634 break
635 }
636 spinPause()
637 }
638
639 // Phase 2: kernel wait or busy-wait
640 if !observed {
641 if c.profile == WinShmProfileHybrid {
642 deadlineMs := uint32(_INFINITE)
643 if timeoutMs > 0 {
644 deadlineMs = timeoutMs
645 }
646 start, _, _ := procGetTickCount64.Call()
647
648 for {
649 atomic.StoreInt32((*int32)(unsafe.Pointer(&data[selfWaitingOff])), 1)
650 atomic.LoadInt32((*int32)(unsafe.Pointer(&data[selfWaitingOff])))
651
652 cur := atomic.LoadInt64((*int64)(unsafe.Pointer(&data[seqOff])))
653 if cur >= expectedSeq {
654 atomic.StoreInt32((*int32)(unsafe.Pointer(&data[selfWaitingOff])), 0)
655 break
656 }
657
658 waitMs := uintptr(_INFINITE)
659 if deadlineMs != _INFINITE {
660 now, _, _ := procGetTickCount64.Call()
661 elapsed := uint32(now - start)
662 if elapsed >= deadlineMs {
663 atomic.StoreInt32((*int32)(unsafe.Pointer(&data[selfWaitingOff])), 0)
664 return 0, ErrWinShmTimeout
665 }
666 waitMs = uintptr(deadlineMs - elapsed)
667 }
668
669 ret, _, _ := procWaitForSingleObj.Call(uintptr(waitEvent), waitMs)
670 atomic.StoreInt32((*int32)(unsafe.Pointer(&data[selfWaitingOff])), 0)
671
672 cur = atomic.LoadInt64((*int64)(unsafe.Pointer(&data[seqOff])))
673 if cur >= expectedSeq {
674 break
675 }
676
677 if atomic.LoadInt32((*int32)(unsafe.Pointer(&data[peerClosedOff]))) != 0 {
678 cur = atomic.LoadInt64((*int64)(unsafe.Pointer(&data[seqOff])))
679 if cur >= expectedSeq {
680 break
681 }
682 c.advanceSeq(expectedSeq)
683 return 0, ErrWinShmDisconnected
684 }
685
686 if ret == _WAIT_TIMEOUT {
687 return 0, ErrWinShmTimeout
688 }
689 }
690
691 mlen = atomic.LoadInt32((*int32)(unsafe.Pointer(&data[lenOff])))
692 if mlen > 0 && int(mlen) <= maxCopy {
693 copy(buf[:mlen], data[areaOff:areaOff+uint32(mlen)])
694 }
695 } else {
696 // BUSYWAIT
697 start, _, _ := procGetTickCount64.Call()
698 for {
699 cur := atomic.LoadInt64((*int64)(unsafe.Pointer(&data[seqOff])))
700 if cur >= expectedSeq {
701 mlen = atomic.LoadInt32((*int32)(unsafe.Pointer(&data[lenOff])))
702 if mlen > 0 && int(mlen) <= maxCopy {
703 copy(buf[:mlen], data[areaOff:areaOff+uint32(mlen)])
704 }
705 break
706 }
707
708 if timeoutMs > 0 {
709 now, _, _ := procGetTickCount64.Call()
710 elapsed := uint64(now - start)
711 if elapsed >= uint64(timeoutMs) {
712 return 0, ErrWinShmTimeout
713 }
714 }
715
716 if atomic.LoadInt32((*int32)(unsafe.Pointer(&data[peerClosedOff]))) != 0 {
717 cur := atomic.LoadInt64((*int64)(unsafe.Pointer(&data[seqOff])))
718 if cur >= expectedSeq {
719 mlen = atomic.LoadInt32((*int32)(unsafe.Pointer(&data[lenOff])))
720 if mlen > 0 && int(mlen) <= maxCopy {
721 copy(buf[:mlen], data[areaOff:areaOff+uint32(mlen)])
722 }
723 break
724 }
725 c.advanceSeq(expectedSeq)
726 return 0, ErrWinShmDisconnected
727 }
728
729 spinPause()
730 }
731 }
732 }
733
734 c.advanceSeq(expectedSeq)
735
736 if int(mlen) > maxCopy {
737 return int(mlen), ErrWinShmMsgTooLarge
738 }
739
740 return int(mlen), nil
741 }
742
743 func (c *WinShmContext) advanceSeq(expectedSeq int64) {
744 if c.role == WinShmRoleServer {
745 c.localReqSeq = expectedSeq
746 } else {
747 c.localRespSeq = expectedSeq
748 }
749 }
750
751 // ---------------------------------------------------------------------------
752 // Internal helpers
753 // ---------------------------------------------------------------------------
754
755 func winShmAlignCacheline(v uint32) uint32 {
756 return (v + (winShmCachelineSize - 1)) & ^(winShmCachelineSize - 1)
757 }
758
759 func validateWinShmProfile(profile uint32) error {
760 if profile != WinShmProfileHybrid && profile != WinShmProfileBusywait {
761 return fmt.Errorf("%w: invalid profile %d", ErrWinShmBadParam, profile)
762 }
763 return nil
764 }
765
766 func computeShmHash(runDir, serviceName string, authToken uint64) uint64 {
767 input := fmt.Sprintf("%s\n%s\n%d", runDir, serviceName, authToken)
768 return FNV1a64([]byte(input))
769 }
770
771 func buildWinShmObjectName(hash uint64, serviceName string,
772 profile uint32, sessionID uint64, suffix string) ([]uint16, error) {
773
774 narrow := fmt.Sprintf(`Local\netipc-%016x-%s-p%d-s%016x-%s`,
775 hash, serviceName, profile, sessionID, suffix)
776 if len(narrow) >= 256 {
777 return nil, fmt.Errorf("%w: object name too long", ErrWinShmBadParam)
778 }
779
780 // Convert to NUL-terminated UTF-16
781 runes := []rune(narrow)
782 runes = append(runes, 0)
783 utf16 := make([]uint16, len(runes))
784 for i, r := range runes {
785 utf16[i] = uint16(r)
786 }
787 return utf16, nil
788 }