Files
Bridge-and-Join-s/internal/nsdadapter/sender.go
zuevav 9737c787f9 feat: живой цикл M2M с НРД + мастер установки ключа на флешку
Инфраструктура M2M (живой обмен с НРД через ИШ):
- обработка M2MTransferResponse: ERROR(M2Mxx) → заявка Отклонена, сохранение
  ответа; INFO → ждём Decision; идемпотентность поллера
- fallback-корреляция ответов с нулевым GUID (M2M14/M2M17) по FIFO
- сырой XML ответа НРД в карточке заявки (для пересылки в ТП)
- тестовый пакет роботу приведён к эталону m2m_robot_samples (CostInfo=Yes,
  4 бумаги, IsolationStatus, DocumentSeries=сценарий); override паспорта
- редирект из теста сразу в карточку заявки

Мастер установки ключа Валидаты на флешку (admin/setup/keywizard):
- пошаговый: загрузка .7z+пароль → выбор флешки → запись → справочник
  сертификатов (CRL) → перезапуск+проверка ИШ → готово
- привилегированный воркер (bj-keymedia) в host-namespace через файл-обмен,
  bj-server остаётся в песочнице
- сохранение структуры профиля архива (spr<N>), перечисление съёмных USB

Прочее:
- пакет-доказательство для ТП НРД + форма регистрации участника M2M
- эталонные образцы робота (DOC/m2m_robot_samples)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 00:03:21 +03:00

94 lines
3.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package nsdadapter
import (
"context"
"errors"
"fmt"
"time"
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2m"
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdadapter/igw"
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdxml"
)
// IGWClient — узкий интерфейс из igw.Client, нужный Sender'у. Введён
// ради тестируемости (mock в тестах) и независимости от ИШ-реализации.
type IGWClient interface {
SendPackage(ctx context.Context, channel, packageType string, body []byte) (string, error)
ListIncoming(ctx context.Context, channel string, since time.Time, packageType string) ([]igw.Package, error)
}
// Sender — реализация m2mcore.NSDSender поверх REST ИШ НРД.
type Sender struct {
profile Profile
client IGWClient
}
// NewSender собирает Sender для указанного профиля.
func NewSender(profile Profile, client IGWClient) *Sender {
return &Sender{profile: profile, client: client}
}
// Send сериализует M2MTransferRequest в windows-1251, маршрутизирует к
// типу пакета #M2MTR и отправляет в ИШ. M2MTransferResponse в этом
// канале возвращается асинхронно через ListIncoming, поэтому Send
// возвращает nil-response — реальный ответ забирает поллер.
func (s *Sender) Send(ctx context.Context, req *m2m.M2MTransferRequest) (*m2m.M2MTransferResponse, error) {
if req == nil {
return nil, errors.New("nsdadapter: Send: req=nil")
}
if err := req.Validate(); err != nil {
return nil, fmt.Errorf("nsdadapter: req.Validate: %w", err)
}
pkgType, err := RouteToPackageType(KindTransferRequest)
if err != nil {
return nil, err
}
// ИШ ждёт ZIP-архив (Type=ARCHIVE): doc.xml + config.xml с типом пакета.
// Сырой XML ИШ отвергает ("Bad signature" при распаковке). PackRequest
// собирает корректный ZIP с config.xml (#M2MTR).
body, err := igw.PackRequest(req, "M2MTransferRequest.xml")
if err != nil {
return nil, fmt.Errorf("nsdadapter: PackRequest: %w", err)
}
// ИШ резолвит канал по СОСТАВНОМУ коду: <код канала>+<код депонента>
// (так его формирует ИШ при создании канала: напр. TEST3+MC0413600000).
channel := s.profile.Channel + string(req.Header.SenderCode)
pkgID, err := s.client.SendPackage(ctx, channel, string(pkgType), body)
if err != nil {
return nil, fmt.Errorf("nsdadapter: SendPackage: %w", err)
}
// Возвращаем псевдо-Response с GUID-ом для трассировки. Реальный
// M2MTransferResponse от НРД придёт через входящие пакеты, его
// обработает поллер cmd/nsd-adapter.
_ = pkgID
return nil, nil
}
// SendDecision сериализует и отправляет M2MTransferDecision.
func (s *Sender) SendDecision(ctx context.Context, decision *m2m.M2MTransferDecision) error {
if decision == nil {
return errors.New("nsdadapter: SendDecision: decision=nil")
}
if err := decision.Validate(); err != nil {
return fmt.Errorf("nsdadapter: decision.Validate: %w", err)
}
xmlBytes, err := nsdxml.Marshal(decision)
if err != nil {
return fmt.Errorf("nsdadapter: marshal Decision: %w", err)
}
pkgType, err := RouteToPackageType(KindTransferDecision)
if err != nil {
return err
}
body, err := igw.PackXML(xmlBytes, "M2MTransferDecision.xml", string(pkgType))
if err != nil {
return fmt.Errorf("nsdadapter: PackXML: %w", err)
}
channel := s.profile.Channel + string(decision.Header.SenderCode)
if _, err := s.client.SendPackage(ctx, channel, string(pkgType), body); err != nil {
return fmt.Errorf("nsdadapter: SendPackage: %w", err)
}
return nil
}