Skip to main content

z3950_rs/
pdu.rs

1use rasn::types::{Any, BitString, GeneralizedTime, Integer, ObjectIdentifier, OctetString, Utf8String, VisibleString};
2use rasn::{AsnType, Decode, Decoder, Encode, Encoder};
3
4use crate::error::{Error, Result};
5
6#[derive(AsnType, Encode, Decode, Debug)]
7#[rasn(choice)]
8pub enum DatabaseName {
9    #[rasn(tag(context, 105))] // C've qu'utilise yaz-client (9F 69)
10    General(VisibleString),
11}
12
13/// Simple credentials container for Init authentication.
14#[derive(Debug, Clone)]
15pub struct Credentials {
16    pub username: String,
17    pub password: String,
18}
19
20impl Credentials {
21    pub fn new(username: impl Into<String>, password: impl Into<String>) -> Self {
22        Self {
23            username: username.into(),
24            password: password.into(),
25        }
26    }
27}
28
29#[derive(Debug, AsnType, Encode, Decode)]
30#[rasn(choice)]
31pub enum Apdu {
32    #[rasn(tag(context, 20))]
33    InitRequest(InitRequest),
34    #[rasn(tag(context, 21))]
35    InitResponse(InitResponse),
36    #[rasn(tag(context, 22))]
37    SearchRequest(SearchRequest),
38    #[rasn(tag(context, 23))]
39    SearchResponse(SearchResponse),
40    #[rasn(tag(context, 24))]
41    PresentRequest(PresentRequest),
42    #[rasn(tag(context, 25))]
43    PresentResponse(PresentResponse),
44    #[rasn(tag(context, 26))]
45    DeleteResultSetRequest(DeleteResultSetRequest),
46    #[rasn(tag(context, 27))]
47    DeleteResultSetResponse(DeleteResultSetResponse),
48    #[rasn(tag(context, 28))]
49    AccessControlRequest(AccessControlRequest),
50    #[rasn(tag(context, 29))]
51    AccessControlResponse(AccessControlResponse),
52    #[rasn(tag(context, 30))]
53    ResourceControlRequest(ResourceControlRequest),
54    #[rasn(tag(context, 31))]
55    ResourceControlResponse(ResourceControlResponse),
56    #[rasn(tag(context, 32))]
57    TriggerResourceControlRequest(TriggerResourceControlRequest),
58    #[rasn(tag(context, 33))]
59    ResourceReportRequest(ResourceReportRequest),
60    #[rasn(tag(context, 34))]
61    ResourceReportResponse(ResourceReportResponse),
62    #[rasn(tag(context, 35))]
63    ScanRequest(ScanRequest),
64    #[rasn(tag(context, 36))]
65    ScanResponse(ScanResponse),
66    #[rasn(tag(context, 43))]
67    SortRequest(SortRequest),
68    #[rasn(tag(context, 44))]
69    SortResponse(SortResponse),
70    #[rasn(tag(context, 45))]
71    Segment(Segment),
72    #[rasn(tag(context, 46))]
73    ExtendedServicesRequest(ExtendedServicesRequest),
74    #[rasn(tag(context, 47))]
75    ExtendedServicesResponse(ExtendedServicesResponse),
76    #[rasn(tag(context, 48))]
77    Close(Close),
78    #[rasn(tag(context, 49))]
79    DuplicateDetectionRequest(DuplicateDetectionRequest),
80    #[rasn(tag(context, 50))]
81    DuplicateDetectionResponse(DuplicateDetectionResponse),
82}
83
84#[derive(Debug, AsnType, Encode, Decode)]
85#[rasn(tag(20))]
86pub struct InitRequest {
87    // 1. Toujours Optionnel
88    #[rasn(tag(2))]
89    pub reference_id: Option<OctetString>,
90
91    // 2. OBLIGATOIRE (Tag 3)
92    #[rasn(tag(3))]
93    pub protocol_version: BitString,
94
95    // 3. OBLIGATOIRE (Tag 4)
96    #[rasn(tag(4))]
97    pub options: BitString,
98
99    // 4. OBLIGATOIRE (Tag 5)
100    #[rasn(tag(5))]
101    pub preferred_message_size: Integer,
102
103    // 5. OBLIGATOIRE (Tag 6)
104    #[rasn(tag(6))]
105    pub exceptional_record_size: Integer,
106
107    // 6. OPTIONNEL (Tag 7) - DOIT VENIR AVANT 110+
108    #[rasn(tag(7))]
109    pub id_authentication: Option<IdAuthentication>,
110
111    // 7. OPTIONNELS (110+)
112    #[rasn(tag(110))]
113    pub implementation_id: Option<Utf8String>,
114    #[rasn(tag(111))]
115    pub implementation_name: Option<Utf8String>,
116    #[rasn(tag(112))]
117    pub implementation_version: Option<Utf8String>,
118
119    #[rasn(tag(11))]
120    pub user_information_field: Option<OctetString>,
121    pub other_info: Option<OctetString>,
122}
123
124#[derive(Debug, AsnType, Encode, Decode)]
125#[rasn(tag(21))]
126pub struct InitResponse {
127    #[rasn(tag(2))]
128    pub reference_id: Option<OctetString>,
129    #[rasn(tag(3))]
130    pub protocol_version: Option<BitString>,
131    #[rasn(tag(4))]
132    pub options: Option<BitString>,
133    #[rasn(tag(5))]
134    pub preferred_message_size: Option<Integer>,
135    #[rasn(tag(6))]
136    pub exceptional_record_size: Option<Integer>,
137    #[rasn(tag(12))]
138    pub result: bool,
139    #[rasn(tag(110))]
140    pub implementation_id: Option<Utf8String>,
141    #[rasn(tag(111))]
142    pub implementation_name: Option<Utf8String>,
143    #[rasn(tag(112))]
144    pub implementation_version: Option<Utf8String>,
145    #[rasn(tag(11))]
146    pub user_information_field: Option<OctetString>,
147    pub other_info: Option<OctetString>,
148}
149
150#[derive(Debug, AsnType, Encode, Decode)]
151#[rasn(tag(22))] // Produira 0xB6
152pub struct SearchRequest {
153    #[rasn(tag(2))]
154    pub reference_id: Option<OctetString>,
155
156    #[rasn(tag(13))]
157    pub small_set_upper_bound: Integer,
158
159    #[rasn(tag(14))]
160    pub large_set_lower_bound: Integer,
161
162    #[rasn(tag(15))]
163    pub medium_set_present_number: Integer,
164
165    #[rasn(tag(16))]
166    pub replace_indicator: bool,
167
168    #[rasn(tag(17))]
169    pub result_set_name: Utf8String,
170
171    #[rasn(tag(18))]
172    pub database_names: Vec<DatabaseName>,
173
174    #[rasn(tag(104))]
175    pub preferred_record_syntax: Option<ObjectIdentifier>,
176
177    // Voici la syntaxe exacte pour l'EXPLICIT dans rasn :
178    #[rasn(tag(explicit(21)))]
179    pub query: Query,
180}
181
182#[derive(Debug, AsnType, Encode, Decode)]
183#[rasn(tag(23))]
184pub struct SearchResponse {
185    #[rasn(tag(context, 2))]
186    pub reference_id: Option<OctetString>,
187    #[rasn(tag(context, 23))]
188    pub result_count: Integer,
189    #[rasn(tag(context, 24))]
190    pub number_of_records_returned: Integer,
191    #[rasn(tag(context, 25))]
192    pub next_result_set_position: Integer,
193    #[rasn(tag(context, 22))]
194    pub search_status: bool,
195    #[rasn(tag(context, 26))]
196    pub result_set_status: Option<Integer>,
197    #[rasn(tag(context, 27))]
198    pub present_status: Option<PresentStatus>,
199    pub records: Option<Records>,
200    #[rasn(tag(context, 203))]
201    pub additional_search_info: Option<OctetString>,
202    pub other_info: Option<OctetString>,
203}
204
205#[derive(Debug, AsnType, Encode, Decode)]
206pub struct PresentRequest {
207    #[rasn(tag(context, 2))]
208    pub reference_id: Option<OctetString>,
209    #[rasn(tag(context, 31))]
210    pub result_set_id: Utf8String,
211    #[rasn(tag(context, 30))]
212    pub result_set_start_point: Integer,
213    #[rasn(tag(context, 29))]
214    pub number_of_records_requested: Integer,
215    #[rasn(tag(context, 212))]
216    pub additional_ranges: Option<Vec<Range>>,
217    pub record_composition: Option<RecordComposition>,
218    #[rasn(tag(context, 104))]
219    pub preferred_record_syntax: Option<ObjectIdentifier>,
220    #[rasn(tag(context, 204))]
221    pub max_segment_count: Option<Integer>,
222    #[rasn(tag(context, 206))]
223    pub max_record_size: Option<Integer>,
224    #[rasn(tag(context, 207))]
225    pub max_segment_size: Option<Integer>,
226    #[rasn(tag(context, 210))]
227    pub other_info: Option<OtherInformation>,
228}
229
230#[derive(Debug, AsnType, Encode, Decode)]
231pub struct OtherInformation {
232    #[rasn(tag(context, 1))]
233    pub category: Option<InfoCategory>,
234
235    pub information: OtherInformationChoice,
236}
237
238#[derive(AsnType, Encode, Decode, Debug)]
239pub struct InfoCategory {
240    #[rasn(tag(context, 1))]
241    pub category_type_id: Option<ObjectIdentifier>,
242
243    #[rasn(tag(context, 2))]
244    pub category_value: Integer,
245}
246
247#[derive(AsnType, Encode, Decode, Debug)]
248#[rasn(choice)]
249pub enum OtherInformationChoice {
250    #[rasn(tag(context, 2))]
251    CharacterInfo(Utf8String),
252
253    #[rasn(tag(context, 3))]
254    BinaryInfo(OctetString),
255
256    #[rasn(tag(context, 4))]
257    ExternallyDefinedInfo(External),
258
259    #[rasn(tag(context, 5))]
260    Oid(ObjectIdentifier),
261}
262
263/// RecordComposition is a CHOICE used in PresentRequest.
264/// Since it's optional and has implicit context tags that may conflict with
265/// other fields during decoding, we wrap it in explicit tags.
266#[derive(Debug, AsnType, Encode, Decode)]
267#[rasn(choice)]
268pub enum RecordComposition {
269    #[rasn(tag(explicit(context, 19)))]
270    Simple(ElementSetNames),
271    #[rasn(tag(explicit(context, 209)))]
272    Complex(CompSpec),
273}
274
275#[derive(Debug, AsnType, Encode, Decode)]
276#[rasn(choice)]
277pub enum ElementSetNames {
278    #[rasn(tag(context, 0))]
279    GenericElementSetName(Utf8String),
280    #[rasn(tag(context, 1))]
281    DatabaseSpecific(Vec<DbElementSetName>),
282}
283
284#[derive(Debug, AsnType, Encode, Decode)]
285pub struct DbElementSetName {
286    pub db_name: DatabaseName,
287    pub element_set_name: Utf8String,
288}
289
290#[derive(Debug, AsnType, Encode, Decode)]
291pub struct CompSpec {
292    #[rasn(tag(context, 1))]
293    pub select_alternative_syntax: bool,
294    #[rasn(tag(context, 2))]
295    pub generic: Option<Specification>,
296    #[rasn(tag(context, 3))]
297    pub db_specific: Option<Vec<DbSpecificSpec>>,
298    #[rasn(tag(context, 4))]
299    pub record_syntax: Option<Vec<ObjectIdentifier>>,
300}
301
302#[derive(Debug, AsnType, Encode, Decode)]
303pub struct DbSpecificSpec {
304    pub db: DatabaseName,
305    pub spec: Specification,
306}
307#[derive(Debug, AsnType, Encode, Decode)]
308pub struct Range {
309    #[rasn(tag(context, 0))]
310    pub starting_position: Integer,
311    #[rasn(tag(context, 1))]
312    pub number_of_records: Integer,
313}
314
315#[derive(Debug, AsnType, Encode, Decode)]
316#[rasn(tag(context, 25))]
317pub struct PresentResponse {
318    #[rasn(tag(context, 2))]
319    pub reference_id: Option<OctetString>,
320    #[rasn(tag(context, 24))]
321    pub number_of_records_returned: Integer, // Obligatoire selon la norme
322    #[rasn(tag(context, 25))]
323    pub next_result_set_position: Integer, // Obligatoire
324
325    #[rasn(tag(context, 27))]
326    pub present_status: Option<PresentStatus>,
327    pub records: Option<Records>,
328    // Le champ other_info est complexe, souvent taggué 201
329    #[rasn(tag(context, 201))]
330    pub other_info: Option<OtherInformation>,
331}
332
333// ============================================================================
334// Delete Result Set (tag 26/27)
335// ============================================================================
336
337#[derive(Debug, AsnType, Encode, Decode)]
338pub struct DeleteResultSetRequest {
339    #[rasn(tag(context, 2))]
340    pub reference_id: Option<OctetString>,
341    #[rasn(tag(context, 32))]
342    pub delete_function: DeleteFunction,
343    pub result_set_list: Option<Vec<ResultSetId>>,
344    #[rasn(tag(context, 201))]
345    pub other_info: Option<OtherInformation>,
346}
347
348#[derive(Debug, AsnType, Encode, Decode, Clone, Copy, PartialEq, Eq)]
349#[rasn(enumerated)]
350pub enum DeleteFunction {
351    List = 0,
352    All = 1,
353}
354
355pub type ResultSetId = Utf8String;
356
357#[derive(Debug, AsnType, Encode, Decode)]
358#[rasn(tag(context, 27))]
359pub struct DeleteResultSetResponse {
360    #[rasn(tag(context, 2))]
361    pub reference_id: Option<OctetString>,
362    #[rasn(tag(context, 0))]
363    pub delete_operation_status: DeleteOperationStatus,
364    #[rasn(tag(context, 1))]
365    pub delete_list_statuses: Option<Vec<ListStatus>>,
366    #[rasn(tag(context, 34))]
367    pub number_not_deleted: Option<Integer>,
368    #[rasn(tag(context, 35))]
369    pub bulk_statuses: Option<Vec<ListStatus>>,
370    #[rasn(tag(context, 36))]
371    pub delete_message: Option<Utf8String>,
372    #[rasn(tag(context, 201))]
373    pub other_info: Option<OtherInformation>,
374}
375
376#[derive(Debug, AsnType, Encode, Decode, Clone, Copy, PartialEq, Eq)]
377#[rasn(enumerated)]
378pub enum DeleteOperationStatus {
379    Success = 0,
380    ResultSetDidNotExist = 1,
381    PreviouslyDeletedByTarget = 2,
382    SystemProblemAtTarget = 3,
383    AccessNotAllowed = 4,
384    ResourceControlAtOrigin = 5,
385    ResourceControlAtTarget = 6,
386    BulkDeleteNotSupported = 7,
387    NotAllRsltSetsDeletedOnBulkDlte = 8,
388    NotAllRequestedResultSetsDeleted = 9,
389    ResultSetInUse = 10,
390}
391
392#[derive(Debug, AsnType, Encode, Decode)]
393pub struct ListStatus {
394    pub id: ResultSetId,
395    pub status: DeleteOperationStatus,
396}
397
398// ============================================================================
399// Access Control (tag 28/29)
400// ============================================================================
401
402#[derive(Debug, AsnType, Encode, Decode)]
403pub struct AccessControlRequest {
404    #[rasn(tag(context, 2))]
405    pub reference_id: Option<OctetString>,
406    pub security_challenge: AccessControlSecurityChallenge,
407    #[rasn(tag(context, 201))]
408    pub other_info: Option<OtherInformation>,
409}
410
411#[derive(Debug, AsnType, Encode, Decode)]
412#[rasn(choice)]
413pub enum AccessControlSecurityChallenge {
414    #[rasn(tag(context, 37))]
415    SimpleForm(OctetString),
416    #[rasn(tag(context, 0))]
417    ExternallyDefined(External),
418}
419
420#[derive(Debug, AsnType, Encode, Decode)]
421pub struct AccessControlResponse {
422    #[rasn(tag(context, 2))]
423    pub reference_id: Option<OctetString>,
424    pub security_challenge_response: Option<AccessControlSecurityChallengeResponse>,
425    #[rasn(tag(context, 223))]
426    pub diagnostic: Option<DiagRec>,
427    #[rasn(tag(context, 201))]
428    pub other_info: Option<OtherInformation>,
429}
430
431#[derive(Debug, AsnType, Encode, Decode)]
432#[rasn(choice)]
433pub enum AccessControlSecurityChallengeResponse {
434    #[rasn(tag(context, 38))]
435    SimpleForm(OctetString),
436    #[rasn(tag(context, 0))]
437    ExternallyDefined(External),
438}
439
440// ============================================================================
441// Resource Control (tag 30/31/32)
442// ============================================================================
443
444#[derive(Debug, AsnType, Encode, Decode)]
445pub struct ResourceControlRequest {
446    #[rasn(tag(context, 2))]
447    pub reference_id: Option<OctetString>,
448    #[rasn(tag(context, 39))]
449    pub suspended_flag: Option<bool>,
450    #[rasn(tag(context, 40))]
451    pub resource_report: Option<ResourceReport>,
452    #[rasn(tag(context, 41))]
453    pub partial_results_available: Option<PartialResultsAvailable>,
454    #[rasn(tag(context, 46))]
455    pub response_required: bool,
456    #[rasn(tag(context, 47))]
457    pub triggered_request_flag: Option<bool>,
458    #[rasn(tag(context, 201))]
459    pub other_info: Option<OtherInformation>,
460}
461
462#[derive(Debug, AsnType, Encode, Decode, Clone, Copy, PartialEq, Eq)]
463#[rasn(enumerated)]
464pub enum PartialResultsAvailable {
465    Subset = 1,
466    Interim = 2,
467    None = 3,
468}
469
470pub type ResourceReport = External;
471
472#[derive(Debug, AsnType, Encode, Decode)]
473pub struct ResourceControlResponse {
474    #[rasn(tag(context, 2))]
475    pub reference_id: Option<OctetString>,
476    #[rasn(tag(context, 44))]
477    pub continue_flag: bool,
478    #[rasn(tag(context, 45))]
479    pub result_set_wanted: Option<bool>,
480    #[rasn(tag(context, 201))]
481    pub other_info: Option<OtherInformation>,
482}
483
484#[derive(Debug, AsnType, Encode, Decode)]
485pub struct TriggerResourceControlRequest {
486    #[rasn(tag(context, 2))]
487    pub reference_id: Option<OctetString>,
488    #[rasn(tag(context, 42))]
489    pub requested_action: TriggerRequestedAction,
490    #[rasn(tag(context, 43))]
491    pub preferred_resource_report_format: Option<ObjectIdentifier>,
492    #[rasn(tag(context, 48))]
493    pub result_set_wanted: Option<bool>,
494    #[rasn(tag(context, 201))]
495    pub other_info: Option<OtherInformation>,
496}
497
498#[derive(Debug, AsnType, Encode, Decode, Clone, Copy, PartialEq, Eq)]
499#[rasn(enumerated)]
500pub enum TriggerRequestedAction {
501    ResourceReport = 1,
502    ResourceControl = 2,
503    Cancel = 3,
504}
505
506// ============================================================================
507// Resource Report (tag 33/34)
508// ============================================================================
509
510#[derive(Debug, AsnType, Encode, Decode)]
511pub struct ResourceReportRequest {
512    #[rasn(tag(context, 2))]
513    pub reference_id: Option<OctetString>,
514    #[rasn(tag(context, 49))]
515    pub op_id: Option<ReferenceId>,
516    #[rasn(tag(context, 43))]
517    pub preferred_resource_report_format: Option<ObjectIdentifier>,
518    #[rasn(tag(context, 201))]
519    pub other_info: Option<OtherInformation>,
520}
521
522pub type ReferenceId = OctetString;
523
524#[derive(Debug, AsnType, Encode, Decode)]
525pub struct ResourceReportResponse {
526    #[rasn(tag(context, 2))]
527    pub reference_id: Option<OctetString>,
528    #[rasn(tag(context, 50))]
529    pub resource_report_status: ResourceReportStatus,
530    #[rasn(tag(context, 40))]
531    pub resource_report: Option<ResourceReport>,
532    #[rasn(tag(context, 201))]
533    pub other_info: Option<OtherInformation>,
534}
535
536#[derive(Debug, AsnType, Encode, Decode, Clone, Copy, PartialEq, Eq)]
537#[rasn(enumerated)]
538pub enum ResourceReportStatus {
539    Success = 0,
540    Partial = 1,
541    FailureNoReport = 2,
542    FailureNoReportNoEstimate = 3,
543    FailureNoReportCannotDetermine = 4,
544    FailureNoReportDueToCondition = 5,
545    FailureReportNotSupported = 6,
546    FailureReportFormatNotSupported = 7,
547}
548
549// ============================================================================
550// Scan (tag 35/36)
551// ============================================================================
552
553#[derive(Debug, AsnType, Encode, Decode)]
554#[rasn(tag(35))]
555pub struct ScanRequest {
556    #[rasn(tag(context, 2))]
557    pub reference_id: Option<OctetString>,
558    #[rasn(tag(context, 3))]
559    pub database_names: Vec<DatabaseName>,
560    pub attribute_set: Option<ObjectIdentifier>,
561    pub terms_list_and_start_point: AttributesPlusTerm,
562    #[rasn(tag(context, 5))]
563    pub step_size: Option<Integer>,
564    #[rasn(tag(context, 6))]
565    pub number_of_terms_requested: Integer,
566    #[rasn(tag(context, 7))]
567    pub preferred_position_in_response: Option<Integer>,
568    #[rasn(tag(context, 201))]
569    pub other_info: Option<OtherInformation>,
570}
571
572#[derive(Debug, AsnType, Encode, Decode)]
573pub struct ScanResponse {
574    #[rasn(tag(context, 2))]
575    pub reference_id: Option<OctetString>,
576    #[rasn(tag(context, 3))]
577    pub step_size: Option<Integer>,
578    #[rasn(tag(context, 4))]
579    pub scan_status: ScanStatus,
580    #[rasn(tag(context, 5))]
581    pub number_of_entries_returned: Integer,
582    #[rasn(tag(context, 6))]
583    pub position_of_term: Option<Integer>,
584    #[rasn(tag(context, 7))]
585    pub entries: Option<ListEntries>,
586    #[rasn(tag(context, 8))]
587    pub attribute_set: Option<ObjectIdentifier>,
588    #[rasn(tag(context, 201))]
589    pub other_info: Option<OtherInformation>,
590}
591
592#[derive(Debug, AsnType, Encode, Decode, Clone, Copy, PartialEq, Eq)]
593#[rasn(enumerated)]
594pub enum ScanStatus {
595    Success = 0,
596    PartialBeginning = 1,
597    PartialEnd = 2,
598    PartialBoth = 3,
599    PartialEmpty = 4,
600    PartialEstimate = 5,
601    Failure = 6,
602}
603
604#[derive(Debug, AsnType, Encode, Decode)]
605pub struct ListEntries {
606    pub entries: Option<Vec<Entry>>,
607    #[rasn(tag(context, 2))]
608    pub nonsurrogate_diagnostics: Option<Vec<DiagRec>>,
609}
610
611#[derive(Debug, AsnType, Encode, Decode)]
612#[rasn(choice)]
613pub enum Entry {
614    #[rasn(tag(context, 1))]
615    TermInfo(TermInfo),
616    #[rasn(tag(context, 2))]
617    SurrogateDiagnostic(DiagRec),
618}
619
620#[derive(Debug, AsnType, Encode, Decode)]
621pub struct TermInfo {
622    pub term: Term,
623    #[rasn(tag(context, 1))]
624    pub display_term: Option<Utf8String>,
625    pub suggested_attributes: Option<AttributeList>,
626    #[rasn(tag(context, 2))]
627    pub alternative_term: Option<Vec<AttributesPlusTerm>>,
628    #[rasn(tag(context, 3))]
629    pub global_occurrences: Option<Integer>,
630    #[rasn(tag(context, 4))]
631    pub by_attributes: Option<OccurrenceByAttributes>,
632    #[rasn(tag(context, 201))]
633    pub other_term_info: Option<OtherInformation>,
634}
635
636#[derive(Debug, AsnType, Encode, Decode)]
637pub struct OccurrenceByAttributes {
638    pub occurrences: Vec<OccurrenceByAttributesElem>,
639}
640
641#[derive(Debug, AsnType, Encode, Decode)]
642pub struct OccurrenceByAttributesElem {
643    pub attributes: AttributeList,
644    pub occurrences: Option<OccurrencesValue>,
645}
646
647#[derive(Debug, AsnType, Encode, Decode)]
648#[rasn(choice)]
649pub enum OccurrencesValue {
650    #[rasn(tag(context, 2))]
651    Global(Integer),
652    #[rasn(tag(context, 3))]
653    ByDatabase(Vec<OccurrenceByDatabase>),
654}
655
656#[derive(Debug, AsnType, Encode, Decode)]
657pub struct OccurrenceByDatabase {
658    pub db: DatabaseName,
659    pub num: Option<Integer>,
660    #[rasn(tag(context, 201))]
661    pub other_db_info: Option<OtherInformation>,
662}
663
664// ============================================================================
665// Sort (tag 43/44)
666// ============================================================================
667
668#[derive(Debug, AsnType, Encode, Decode)]
669pub struct SortRequest {
670    #[rasn(tag(context, 2))]
671    pub reference_id: Option<OctetString>,
672    #[rasn(tag(context, 3))]
673    pub input_result_set_names: Vec<Utf8String>,
674    #[rasn(tag(context, 4))]
675    pub sorted_result_set_name: Utf8String,
676    #[rasn(tag(context, 5))]
677    pub sort_sequence: Vec<SortKeySpec>,
678    #[rasn(tag(context, 201))]
679    pub other_info: Option<OtherInformation>,
680}
681
682#[derive(Debug, AsnType, Encode, Decode)]
683pub struct SortKeySpec {
684    pub sort_element: SortElement,
685    #[rasn(tag(context, 1))]
686    pub sort_relation: SortRelation,
687    #[rasn(tag(context, 2))]
688    pub case_sensitivity: CaseSensitivity,
689    #[rasn(tag(context, 3))]
690    pub missing_value_action: Option<MissingValueAction>,
691}
692
693#[derive(Debug, AsnType, Encode, Decode)]
694#[rasn(choice)]
695pub enum SortElement {
696    #[rasn(tag(context, 1))]
697    Generic(SortKey),
698    #[rasn(tag(context, 2))]
699    DataBaseSpecific(Vec<SortDbSpecific>),
700}
701
702#[derive(Debug, AsnType, Encode, Decode)]
703pub struct SortDbSpecific {
704    pub database_name: DatabaseName,
705    pub db_sort: SortKey,
706}
707
708#[derive(Debug, AsnType, Encode, Decode)]
709#[rasn(choice)]
710pub enum SortKey {
711    #[rasn(tag(context, 0))]
712    SortField(Utf8String),
713    #[rasn(tag(context, 1))]
714    ElementSpec(Specification),
715    #[rasn(tag(context, 2))]
716    SortAttributes(SortAttributes),
717}
718
719#[derive(Debug, AsnType, Encode, Decode)]
720pub struct SortAttributes {
721    pub id: ObjectIdentifier,
722    pub list: AttributeList,
723}
724
725#[derive(Debug, AsnType, Encode, Decode)]
726pub struct Specification {
727    #[rasn(tag(context, 1))]
728    pub schema: Option<ObjectIdentifier>,
729    #[rasn(tag(context, 2))]
730    pub element_spec: Option<ElementSpec>,
731}
732
733#[derive(Debug, AsnType, Encode, Decode)]
734#[rasn(choice)]
735pub enum ElementSpec {
736    #[rasn(tag(context, 1))]
737    ElementSetName(Utf8String),
738    #[rasn(tag(context, 2))]
739    ExternalEspec(External),
740}
741
742#[derive(Debug, AsnType, Encode, Decode, Clone, Copy, PartialEq, Eq)]
743#[rasn(enumerated)]
744pub enum SortRelation {
745    Ascending = 0,
746    Descending = 1,
747    AscendingByFrequency = 3,
748    DescendingByFrequency = 4,
749}
750
751#[derive(Debug, AsnType, Encode, Decode, Clone, Copy, PartialEq, Eq)]
752#[rasn(enumerated)]
753pub enum CaseSensitivity {
754    CaseSensitive = 0,
755    CaseInsensitive = 1,
756}
757
758#[derive(Debug, AsnType, Encode, Decode)]
759#[rasn(choice)]
760pub enum MissingValueAction {
761    #[rasn(tag(context, 1))]
762    Abort(()),
763    #[rasn(tag(context, 2))]
764    Null(()),
765    #[rasn(tag(context, 3))]
766    MissingValueData(OctetString),
767}
768
769#[derive(Debug, AsnType, Encode, Decode)]
770pub struct SortResponse {
771    #[rasn(tag(context, 2))]
772    pub reference_id: Option<OctetString>,
773    #[rasn(tag(context, 3))]
774    pub sort_status: SortStatus,
775    #[rasn(tag(context, 4))]
776    pub result_set_status: Option<SortResultSetStatus>,
777    #[rasn(tag(context, 5))]
778    pub diagnostics: Option<Vec<DiagRec>>,
779    #[rasn(tag(context, 6))]
780    pub result_count: Option<Integer>,
781    #[rasn(tag(context, 201))]
782    pub other_info: Option<OtherInformation>,
783}
784
785#[derive(Debug, AsnType, Encode, Decode, Clone, Copy, PartialEq, Eq)]
786#[rasn(enumerated)]
787pub enum SortStatus {
788    Success = 0,
789    PartialResultsAvailable = 1,
790    Failure = 2,
791}
792
793#[derive(Debug, AsnType, Encode, Decode, Clone, Copy, PartialEq, Eq)]
794#[rasn(enumerated)]
795pub enum SortResultSetStatus {
796    Empty = 1,
797    Interim = 2,
798    Unchanged = 3,
799    None = 4,
800}
801
802// ============================================================================
803// Segment (tag 45)
804// ============================================================================
805
806#[derive(Debug, AsnType, Encode, Decode)]
807pub struct Segment {
808    #[rasn(tag(context, 2))]
809    pub reference_id: Option<OctetString>,
810    #[rasn(tag(context, 24))]
811    pub number_of_records_returned: Integer,
812    #[rasn(tag(context, 0))]
813    pub segment_records: Vec<NamePlusRecord>,
814    #[rasn(tag(context, 201))]
815    pub other_info: Option<OtherInformation>,
816}
817
818// ============================================================================
819// Extended Services (tag 46/47)
820// ============================================================================
821
822#[derive(Debug, AsnType, Encode, Decode)]
823pub struct ExtendedServicesRequest {
824    #[rasn(tag(context, 2))]
825    pub reference_id: Option<OctetString>,
826    #[rasn(tag(context, 3))]
827    pub function: ExtendedServicesFunction,
828    #[rasn(tag(context, 4))]
829    pub package_type: ObjectIdentifier,
830    #[rasn(tag(context, 5))]
831    pub package_name: Option<Utf8String>,
832    #[rasn(tag(context, 6))]
833    pub user_id: Option<Utf8String>,
834    #[rasn(tag(context, 7))]
835    pub retention_time: Option<IntUnit>,
836    #[rasn(tag(context, 8))]
837    pub permissions: Option<Permissions>,
838    #[rasn(tag(context, 9))]
839    pub description: Option<Utf8String>,
840    #[rasn(tag(context, 10))]
841    pub task_specific_parameters: Option<External>,
842    #[rasn(tag(context, 11))]
843    pub wait_action: WaitAction,
844    #[rasn(tag(context, 103))]
845    pub elements: Option<ElementSetName>,
846    #[rasn(tag(context, 201))]
847    pub other_info: Option<OtherInformation>,
848}
849
850#[derive(Debug, AsnType, Encode, Decode, Clone, Copy, PartialEq, Eq)]
851#[rasn(enumerated)]
852pub enum ExtendedServicesFunction {
853    Create = 1,
854    Delete = 2,
855    Modify = 3,
856}
857
858#[derive(Debug, AsnType, Encode, Decode, Clone, Copy, PartialEq, Eq)]
859#[rasn(enumerated)]
860pub enum WaitAction {
861    Wait = 1,
862    WaitIfPossible = 2,
863    DontWait = 3,
864    DontReturnPackage = 4,
865}
866
867pub type ElementSetName = Utf8String;
868
869#[derive(Debug, AsnType, Encode, Decode)]
870pub struct Permissions {
871    pub permissions: Vec<PermissionsElem>,
872}
873
874#[derive(Debug, AsnType, Encode, Decode)]
875pub struct PermissionsElem {
876    #[rasn(tag(context, 1))]
877    pub user_id: Utf8String,
878    #[rasn(tag(context, 2))]
879    pub allowed_functions: Vec<AllowedFunction>,
880}
881
882#[derive(Debug, AsnType, Encode, Decode, Clone, Copy, PartialEq, Eq)]
883#[rasn(enumerated)]
884pub enum AllowedFunction {
885    Delete = 1,
886    ModifyContents = 2,
887    ModifyPermissions = 3,
888    Present = 4,
889    Invoke = 5,
890}
891
892#[derive(Debug, AsnType, Encode, Decode)]
893pub struct ExtendedServicesResponse {
894    #[rasn(tag(context, 2))]
895    pub reference_id: Option<OctetString>,
896    #[rasn(tag(context, 3))]
897    pub operation_status: ExtendedServicesStatus,
898    #[rasn(tag(context, 4))]
899    pub diagnostics: Option<Vec<DiagRec>>,
900    #[rasn(tag(context, 5))]
901    pub task_package: Option<External>,
902    #[rasn(tag(context, 201))]
903    pub other_info: Option<OtherInformation>,
904}
905
906#[derive(Debug, AsnType, Encode, Decode, Clone, Copy, PartialEq, Eq)]
907#[rasn(enumerated)]
908pub enum ExtendedServicesStatus {
909    Done = 1,
910    Accepted = 2,
911    Failure = 3,
912}
913
914// ============================================================================
915// Close (tag 48)
916// ============================================================================
917
918#[derive(Debug, AsnType, Encode, Decode)]
919pub struct Close {
920    #[rasn(tag(context, 2))]
921    pub reference_id: Option<OctetString>,
922    #[rasn(tag(context, 211))]
923    pub close_reason: CloseReason,
924    #[rasn(tag(context, 3))]
925    pub diagnostic_information: Option<Utf8String>,
926    #[rasn(tag(context, 4))]
927    pub resource_report_format: Option<ObjectIdentifier>,
928    #[rasn(tag(context, 5))]
929    pub resource_report: Option<ResourceReport>,
930    #[rasn(tag(context, 201))]
931    pub other_info: Option<OtherInformation>,
932}
933
934#[derive(Debug, AsnType, Encode, Decode, Clone, Copy, PartialEq, Eq)]
935#[rasn(enumerated)]
936pub enum CloseReason {
937    Finished = 0,
938    Shutdown = 1,
939    SystemProblem = 2,
940    CostLimit = 3,
941    Resources = 4,
942    SecurityViolation = 5,
943    ProtocolError = 6,
944    LackOfActivity = 7,
945    PeerAbort = 8,
946    Unspecified = 9,
947}
948
949// ============================================================================
950// Duplicate Detection (tag 49/50)
951// ============================================================================
952
953#[derive(Debug, AsnType, Encode, Decode)]
954pub struct DuplicateDetectionRequest {
955    #[rasn(tag(context, 2))]
956    pub reference_id: Option<OctetString>,
957    #[rasn(tag(context, 3))]
958    pub input_result_set_ids: Vec<Utf8String>,
959    #[rasn(tag(context, 4))]
960    pub output_result_set_name: Utf8String,
961    #[rasn(tag(context, 5))]
962    pub applicable_portion: Option<ApplicablePortion>,
963    #[rasn(tag(context, 6))]
964    pub duplicate_detection_criteria: Option<Vec<DuplicateDetectionCriterion>>,
965    #[rasn(tag(context, 7))]
966    pub clustering: Option<bool>,
967    #[rasn(tag(context, 8))]
968    pub retention_criteria: Option<Vec<RetentionCriterion>>,
969    #[rasn(tag(context, 9))]
970    pub sorting_criteria: Option<Vec<SortCriterion>>,
971    #[rasn(tag(context, 201))]
972    pub other_info: Option<OtherInformation>,
973}
974
975#[derive(Debug, AsnType, Encode, Decode)]
976#[rasn(choice)]
977pub enum ApplicablePortion {
978    #[rasn(tag(context, 2))]
979    Full(()),
980    #[rasn(tag(context, 3))]
981    Fields(Vec<Utf8String>),
982}
983
984#[derive(Debug, AsnType, Encode, Decode)]
985#[rasn(choice)]
986pub enum DuplicateDetectionCriterion {
987    #[rasn(tag(context, 1))]
988    LevelOfMatch(Integer),
989    #[rasn(tag(context, 2))]
990    CaseSensitive(bool),
991    #[rasn(tag(context, 3))]
992    PunctuationSensitive(bool),
993    #[rasn(tag(context, 4))]
994    RegularExpression(External),
995    #[rasn(tag(context, 5))]
996    RsDuplicates(External),
997}
998
999#[derive(Debug, AsnType, Encode, Decode)]
1000#[rasn(choice)]
1001pub enum RetentionCriterion {
1002    #[rasn(tag(context, 1))]
1003    NumberOfEntries(Integer),
1004    #[rasn(tag(context, 2))]
1005    PercentOfEntries(Integer),
1006    #[rasn(tag(context, 3))]
1007    DuplicatesOnly(()),
1008    #[rasn(tag(context, 4))]
1009    DiscardRsDuplicates(()),
1010}
1011
1012#[derive(Debug, AsnType, Encode, Decode)]
1013#[rasn(choice)]
1014pub enum SortCriterion {
1015    #[rasn(tag(context, 1))]
1016    MostComprehensive(()),
1017    #[rasn(tag(context, 2))]
1018    LeastComprehensive(()),
1019    #[rasn(tag(context, 3))]
1020    MostRecent(()),
1021    #[rasn(tag(context, 4))]
1022    Oldest(()),
1023    #[rasn(tag(context, 5))]
1024    LeastCostPerRecord(()),
1025    #[rasn(tag(context, 6))]
1026    PreferredDatabases(Vec<DatabaseName>),
1027}
1028
1029#[derive(Debug, AsnType, Encode, Decode)]
1030pub struct DuplicateDetectionResponse {
1031    #[rasn(tag(context, 2))]
1032    pub reference_id: Option<OctetString>,
1033    #[rasn(tag(context, 3))]
1034    pub status: DuplicateDetectionStatus,
1035    #[rasn(tag(context, 4))]
1036    pub result_set_count: Option<Integer>,
1037    #[rasn(tag(context, 5))]
1038    pub diagnostics: Option<Vec<DiagRec>>,
1039    #[rasn(tag(context, 201))]
1040    pub other_info: Option<OtherInformation>,
1041}
1042
1043#[derive(Debug, AsnType, Encode, Decode, Clone, Copy, PartialEq, Eq)]
1044#[rasn(enumerated)]
1045pub enum DuplicateDetectionStatus {
1046    Success = 0,
1047    PartialSomeInputNotProcessed = 1,
1048    PartialSomeCriteriaNotApplied = 2,
1049    PartialBothProblems = 3,
1050    Failure = 4,
1051}
1052
1053#[derive(Debug, AsnType, Encode, Decode)]
1054#[rasn(choice)]
1055pub enum Records {
1056    #[rasn(tag(context, 28))]
1057    ResponseRecords(Vec<NamePlusRecord>),
1058    #[rasn(tag(context, 130))]
1059    NonSurrogateDiagnostic(DefaultDiagFormat),
1060    #[rasn(tag(context, 205))]
1061    MultipeNonSurDiagnostic(Vec<DiagRec>),
1062}
1063
1064#[derive(AsnType, Encode, Decode, Debug)]
1065#[rasn(choice)]
1066pub enum DiagRec {
1067    DefaultFormat(DefaultDiagFormat),
1068    ExternallDefined(External),
1069}
1070
1071#[derive(AsnType, Encode, Decode, Debug)]
1072pub struct DefaultDiagFormat {
1073    pub diagnostic_set_id: ObjectIdentifier,
1074    pub condition: Integer,
1075    pub addinfo: AddInfo,
1076}
1077
1078#[derive(AsnType, Encode, Decode, Debug)]
1079#[rasn(choice)]
1080pub enum AddInfo {
1081    V2Addinfo(VisibleString),
1082    V3Addinfo(Utf8String),
1083}
1084
1085#[derive(Debug, AsnType, Encode, Decode)]
1086pub struct NamePlusRecord {
1087    #[rasn(tag(context, 0))]
1088    pub name: Option<VisibleString>,
1089
1090    #[rasn(tag(explicit(context, 1)))] // Premier 161
1091    pub record: Record,
1092}
1093
1094#[derive(Debug, AsnType, Encode, Decode)]
1095#[rasn(choice)]
1096pub enum Record {
1097    #[rasn(tag(explicit(context, 1)))]
1098    RetrievalRecord(External),
1099    #[rasn(tag(context, 2))]
1100    SurrogateDiagnostic(DiagRec),
1101    #[rasn(tag(context, 3))]
1102    StartingFragment(FragmentSyntax),
1103    #[rasn(tag(context, 4))]
1104    IntermediateFragment(FragmentSyntax),
1105    #[rasn(tag(context, 5))]
1106    FinalFragment(FragmentSyntax),
1107}
1108
1109#[derive(Debug, AsnType, Encode, Decode, Clone)]
1110#[rasn(choice)]
1111pub enum FragmentSyntax {
1112    ExternallyTagged(External),
1113    NotExternallyTagged(OctetString),
1114}
1115
1116#[derive(Debug, AsnType, Encode, Decode, Clone)]
1117#[rasn(tag(universal, 8))]
1118pub struct External {
1119    pub direct_reference: Option<ObjectIdentifier>,
1120    pub indirect_reference: Option<Integer>,
1121    pub data_value_descriptor: Option<Utf8String>,
1122    pub encoding: ExternalEncoding,
1123}
1124
1125#[derive(Debug, AsnType, Encode, Decode, Clone)]
1126#[rasn(choice)]
1127pub enum ExternalEncoding {
1128    #[rasn(tag(context, 0))]
1129    SingleASN1Type(Any),
1130    #[rasn(tag(context, 1))]
1131    OctetAligned(OctetString),
1132    #[rasn(tag(context, 2))]
1133    Arbitrary(BitString),
1134}
1135
1136#[derive(Debug, AsnType, Encode, Decode, Clone, Copy, PartialEq, Eq)]
1137#[rasn(enumerated)]
1138pub enum PresentStatus {
1139    Success = 0,
1140    Partial1 = 1,
1141    Partial2 = 2,
1142    Partial3 = 3,
1143    Partial4 = 4,
1144    Failure = 5,
1145}
1146
1147#[derive(Debug, AsnType, Encode, Decode)]
1148#[rasn(choice)]
1149pub enum IdAuthentication {
1150    #[rasn(tag(universal, 26))]
1151    Open(VisibleString),
1152    #[rasn(tag(context, 1))]
1153    IdPass(IdPass),
1154}
1155
1156#[derive(Debug, AsnType, Encode, Decode)]
1157pub struct IdPass {
1158    #[rasn(tag(context, 0))]
1159    pub id: Utf8String,
1160    #[rasn(tag(context, 1))]
1161    pub password: Utf8String,
1162}
1163
1164#[derive(Debug, AsnType, Encode, Decode)]
1165#[rasn(choice)]
1166pub enum Query {
1167    // [1] EXPLICIT RPNQuery (Type-1 standard souvent utilisé)
1168    #[rasn(tag(context, 1))]
1169    Type1(RpnQuery),
1170
1171    #[rasn(tag(context, 2))]
1172    Type2(RpnQuery),
1173
1174    // type-100 [100] OCTET STRING
1175    #[rasn(tag(context, 100))]
1176    Type100(OctetString),
1177
1178    // type-101 [101] IMPLICIT RPNQuery
1179    // Note: rasn utilise l'implicite par défaut, donc tag(context, 101) suffit
1180    #[rasn(tag(context, 101))]
1181    Type101(RpnQuery),
1182
1183    // type-102 [102] OCTET STRING
1184    #[rasn(tag(context, 102))]
1185    Type102(OctetString),
1186}
1187
1188#[derive(Debug, AsnType, Encode, Decode)]
1189pub struct RpnQuery {
1190    pub attribute_set: ObjectIdentifier,
1191    pub rpn: RpnStructure,
1192}
1193
1194#[derive(Debug, AsnType, Encode, Decode, Clone)]
1195#[rasn(choice)]
1196pub enum RpnStructure {
1197    #[rasn(tag(context, 0))]
1198    Op(Operand),
1199    #[rasn(tag(context, 1))]
1200    RpnRpnOperator(RpnRpnOperator),
1201}
1202
1203#[derive(Debug, AsnType, Encode, Decode, Clone)]
1204pub struct RpnRpnOperator {
1205    pub rpn1: Box<RpnStructure>, // Box est nécessaire pour la récursion
1206    pub rpn2: Box<RpnStructure>,
1207    pub op: Operator,
1208}
1209
1210#[derive(Debug, AsnType, Encode, Decode, Clone)]
1211#[rasn(choice)]
1212pub enum Operator {
1213    #[rasn(tag(context, 0))]
1214    And(()),
1215    #[rasn(tag(context, 1))]
1216    Or(()),
1217    #[rasn(tag(context, 2))]
1218    AndNot(()),
1219}
1220
1221#[derive(Debug, AsnType, Encode, Decode, Clone)]
1222#[rasn(choice)]
1223pub enum Operand {
1224    #[rasn(tag(context, 102))]
1225    AttributesPlusTerm(AttributesPlusTerm),
1226    #[rasn(tag(context, 33))]
1227    ResultSet(Utf8String),
1228    #[rasn(tag(context, 214))]
1229    ResultAttr(ResultSetPlusAttributes),
1230}
1231
1232#[derive(Debug, AsnType, Encode, Decode, Clone)]
1233pub struct ResultSetPlusAttributes {
1234    pub result_set: Utf8String,
1235    pub attributes: AttributeList,
1236}
1237
1238#[derive(Debug, AsnType, Encode, Decode, Clone)]
1239#[rasn(tag(context, 102))] // ← le plus important !
1240pub struct AttributesPlusTerm {
1241    #[rasn(tag(context, 44))]
1242    pub attributes: Vec<AttributeElement>,
1243    pub term: Term,
1244}
1245
1246#[derive(Debug, AsnType, Encode, Decode, Clone)]
1247pub struct AttributeList {
1248    pub elements: Vec<AttributeElement>,
1249}
1250
1251#[derive(Debug, AsnType, Encode, Decode, Clone)]
1252pub struct AttributeElement {
1253    #[rasn(tag(context, 1))]
1254    pub attribute_set: Option<ObjectIdentifier>,
1255    #[rasn(tag(context, 120))]
1256    pub attribute_type: Integer,
1257    pub attribute_value: AttributeValue,
1258}
1259
1260#[derive(Debug, AsnType, Encode, Decode, Clone)]
1261#[rasn(choice)]
1262pub enum AttributeValue {
1263    #[rasn(tag(context, 121))]
1264    Numeric(Integer),
1265}
1266
1267#[derive(Debug, AsnType, Encode, Decode, Clone)]
1268#[rasn(choice)]
1269pub enum Term {
1270    #[rasn(tag(45))]
1271    General(OctetString),
1272
1273    #[rasn(tag(context, 215))]
1274    Numeric(Integer),
1275    #[rasn(tag(context, 216))]
1276    CharacterString(Utf8String),
1277    #[rasn(tag(context, 217))]
1278    Oid(ObjectIdentifier),
1279    #[rasn(tag(context, 218))]
1280    DateTime(GeneralizedTime),
1281    #[rasn(tag(context, 219))]
1282    External(External),
1283    #[rasn(tag(context, 220))]
1284    IntegerAndUnit(IntUnit),
1285    #[rasn(tag(context, 221))]
1286    Null(()),
1287}
1288
1289#[derive(Debug, AsnType, Encode, Decode, Clone)]
1290#[rasn(choice)]
1291pub enum IntUnit {
1292    #[rasn(tag(context, 1))]
1293    Value(Integer),
1294    #[rasn(tag(context, 2))]
1295    Unit(Unit),
1296}
1297
1298#[derive(Debug, AsnType, Encode, Decode, Clone)]
1299
1300pub struct Unit {
1301    #[rasn(tag(context, 1))]
1302    pub unit_system: Option<Utf8String>,
1303    #[rasn(tag(context, 2))]
1304    pub unit_type: Option<StringOrNumeric>,
1305    #[rasn(tag(context, 3))]
1306    pub unit: Option<StringOrNumeric>,
1307}
1308
1309#[derive(Debug, AsnType, Encode, Decode, Clone)]
1310#[rasn(choice)]
1311pub enum StringOrNumeric {
1312    #[rasn(tag(context, 1))]
1313    String(Utf8String),
1314    #[rasn(tag(context, 2))]
1315    Numeric(Integer),
1316}
1317
1318/// BIB-1 attribute set OID (1.2.840.10003.3.1).
1319pub fn bib1_attribute_set() -> Result<ObjectIdentifier> {
1320    ObjectIdentifier::new(vec![1, 2, 840, 10003, 3, 1]).ok_or_else(|| Error::InvalidOid("failed to construct BIB-1 attribute set OID".into()))
1321}
1322
1323/// USMARC record syntax OID (1.2.840.10003.5.10).
1324pub fn record_syntax_usmarc() -> Result<ObjectIdentifier> {
1325    ObjectIdentifier::new(vec![1, 2, 840, 10003, 5, 10]).ok_or_else(|| Error::InvalidOid("failed to construct USMARC record syntax OID".into()))
1326}
1327
1328/// Builds a minimal InitRequest (Z39.50 Init APDU).
1329///
1330/// This function is intentionally small and strict:
1331/// - It only emits standards-compliant fields
1332/// - It validates that strings fit the ASN.1 types we encode (e.g. VisibleString)
1333pub fn make_init_request(auth: Option<&Credentials>) -> Result<InitRequest> {
1334    // Protocol version bitstring: bits 0..=2 are set to support v1/v2/v3.
1335    let mut protocol_version = BitString::with_capacity(16);
1336    protocol_version.push(true); // bit 0: v1
1337    protocol_version.push(true); // bit 1: v2
1338    protocol_version.push(true); // bit 2: v3
1339    for _ in 3..16 {
1340        protocol_version.push(false);
1341    }
1342
1343    // Options bitstring: bits 0..=1 enable Search and Present.
1344    let mut options = BitString::with_capacity(32);
1345    options.push(true); // bit 0: search
1346    options.push(true); // bit 1: present
1347    for _ in 2..32 {
1348        options.push(false);
1349    }
1350
1351    let id_authentication = match auth {
1352        None => None,
1353        Some(c) => {
1354            // Common "Open" authentication convention: `username/password`.
1355            let combined = format!("{}/{}", c.username, c.password);
1356            let vs = VisibleString::from_iso646_bytes(combined.as_bytes()).map_err(|e| Error::InvalidVisibleString(e.to_string()))?;
1357            Some(IdAuthentication::Open(vs))
1358        }
1359    };
1360
1361    Ok(InitRequest {
1362        reference_id: None,
1363        protocol_version,
1364        options,
1365        preferred_message_size: 0x04000000i64.into(),
1366        exceptional_record_size: 0x04000000i64.into(),
1367        id_authentication,
1368        implementation_id: Some(Utf8String::from("81")),
1369        implementation_name: Some(Utf8String::from("YAZ")),
1370        implementation_version: Some(Utf8String::from("5.34.4 b42e25e840666ea3422c3bd5cb566b07f78a99cd")),
1371        user_information_field: None,
1372        other_info: None,
1373    })
1374}
1375
1376/// Builds a Type-1 query (RPN query with BIB-1 attributes).
1377pub fn make_type1_query(attribute_type: i64, term: &str) -> Result<Query> {
1378    let attr = AttributeElement {
1379        attribute_set: Some(bib1_attribute_set()?),
1380        attribute_type: attribute_type.into(),
1381        attribute_value: AttributeValue::Numeric(1.into()),
1382    };
1383
1384    let rpn = RpnQuery {
1385        attribute_set: bib1_attribute_set()?,
1386        rpn: RpnStructure::Op(Operand::AttributesPlusTerm(AttributesPlusTerm {
1387            attributes: vec![attr],
1388
1389            term: Term::General(OctetString::from(term.as_bytes().to_vec())),
1390        })),
1391    };
1392
1393    Ok(Query::Type1(rpn))
1394}
1395
1396/// Builds a basic SearchRequest using the provided database names and result set name.
1397pub fn make_search_request(databases: &[String], result_set: &str, query: Query) -> Result<SearchRequest> {
1398    let database_names = databases
1399        .iter()
1400        .cloned()
1401        .map(|s| {
1402            let vs = VisibleString::from_iso646_bytes(s.as_bytes()).map_err(|e| Error::InvalidVisibleString(e.to_string()))?;
1403            Ok(DatabaseName::General(vs))
1404        })
1405        .collect::<Result<Vec<_>>>()?;
1406
1407    Ok(SearchRequest {
1408        reference_id: None,
1409        database_names,
1410        small_set_upper_bound: 0.into(),
1411        large_set_lower_bound: 1.into(),
1412        medium_set_present_number: 0.into(),
1413        replace_indicator: true,
1414        result_set_name: Utf8String::from(result_set),
1415        preferred_record_syntax: None,
1416        query,
1417    })
1418}
1419
1420/// Builds a PresentRequest for the current result set.
1421pub fn make_present_request(result_set: &str, start: i64, count: i64) -> Result<PresentRequest> {
1422    Ok(PresentRequest {
1423        reference_id: None,
1424        result_set_id: Utf8String::from(result_set),
1425        result_set_start_point: start.into(),
1426        number_of_records_requested: count.into(),
1427        preferred_record_syntax: Some(record_syntax_usmarc()?),
1428        other_info: None,
1429        additional_ranges: None,
1430        record_composition: None,
1431        max_segment_count: None,
1432        max_record_size: None,
1433        max_segment_size: None,
1434    })
1435}
1436
1437/// Builds a DeleteResultSetRequest to delete specific result sets.
1438pub fn make_delete_result_set_request(result_sets: &[&str]) -> DeleteResultSetRequest {
1439    DeleteResultSetRequest {
1440        reference_id: None,
1441        delete_function: DeleteFunction::List,
1442        result_set_list: Some(result_sets.iter().map(|s| Utf8String::from(*s)).collect()),
1443        other_info: None,
1444    }
1445}
1446
1447/// Builds a DeleteResultSetRequest to delete all result sets.
1448pub fn make_delete_all_result_sets_request() -> DeleteResultSetRequest {
1449    DeleteResultSetRequest {
1450        reference_id: None,
1451        delete_function: DeleteFunction::All,
1452        result_set_list: None,
1453        other_info: None,
1454    }
1455}
1456
1457/// Builds a ScanRequest to browse an index.
1458pub fn make_scan_request(databases: &[String], term: &str, attribute_type: i64, step_size: Option<i64>, number_of_terms: i64, preferred_position: Option<i64>) -> Result<ScanRequest> {
1459    let database_names = databases
1460        .iter()
1461        .cloned()
1462        .map(|s| {
1463            let vs = VisibleString::from_iso646_bytes(s.as_bytes()).map_err(|e| Error::InvalidVisibleString(e.to_string()))?;
1464            Ok(DatabaseName::General(vs))
1465        })
1466        .collect::<Result<Vec<_>>>()?;
1467
1468    let attr = AttributeElement {
1469        attribute_set: Some(bib1_attribute_set()?),
1470        attribute_type: attribute_type.into(),
1471        attribute_value: AttributeValue::Numeric(1.into()),
1472    };
1473
1474    Ok(ScanRequest {
1475        reference_id: None,
1476        database_names,
1477        attribute_set: Some(bib1_attribute_set()?),
1478        terms_list_and_start_point: AttributesPlusTerm {
1479            attributes: vec![attr],
1480            term: Term::General(OctetString::from(term.as_bytes().to_vec())),
1481        },
1482        step_size: step_size.map(|s| s.into()),
1483        number_of_terms_requested: number_of_terms.into(),
1484        preferred_position_in_response: preferred_position.map(|p| p.into()),
1485        other_info: None,
1486    })
1487}
1488
1489/// Builds a SortRequest to sort result sets.
1490pub fn make_sort_request(input_result_sets: &[&str], output_result_set: &str, sort_keys: Vec<SortKeySpec>) -> SortRequest {
1491    SortRequest {
1492        reference_id: None,
1493        input_result_set_names: input_result_sets.iter().map(|s| Utf8String::from(*s)).collect(),
1494        sorted_result_set_name: Utf8String::from(output_result_set),
1495        sort_sequence: sort_keys,
1496        other_info: None,
1497    }
1498}
1499
1500/// Builds a simple SortKeySpec for sorting by a field name.
1501pub fn make_sort_key_by_field(field_name: &str, ascending: bool, case_sensitive: bool) -> SortKeySpec {
1502    SortKeySpec {
1503        sort_element: SortElement::Generic(SortKey::SortField(Utf8String::from(field_name))),
1504        sort_relation: if ascending { SortRelation::Ascending } else { SortRelation::Descending },
1505        case_sensitivity: if case_sensitive { CaseSensitivity::CaseSensitive } else { CaseSensitivity::CaseInsensitive },
1506        missing_value_action: None,
1507    }
1508}
1509
1510/// Builds a Close request.
1511pub fn make_close_request(reason: CloseReason, diagnostic_info: Option<&str>) -> Close {
1512    Close {
1513        reference_id: None,
1514        close_reason: reason,
1515        diagnostic_information: diagnostic_info.map(|s| Utf8String::from(s)),
1516        resource_report_format: None,
1517        resource_report: None,
1518        other_info: None,
1519    }
1520}
1521
1522/// Builds an ExtendedServicesRequest.
1523pub fn make_extended_services_request(
1524    function: ExtendedServicesFunction,
1525    package_type: ObjectIdentifier,
1526    package_name: Option<&str>,
1527    task_specific_parameters: Option<External>,
1528    wait_action: WaitAction,
1529) -> ExtendedServicesRequest {
1530    ExtendedServicesRequest {
1531        reference_id: None,
1532        function,
1533        package_type,
1534        package_name: package_name.map(|s| Utf8String::from(s)),
1535        user_id: None,
1536        retention_time: None,
1537        permissions: None,
1538        description: None,
1539        task_specific_parameters,
1540        wait_action,
1541        elements: None,
1542        other_info: None,
1543    }
1544}
1545
1546/// Builds a DuplicateDetectionRequest.
1547pub fn make_duplicate_detection_request(input_result_sets: &[&str], output_result_set: &str, clustering: bool) -> DuplicateDetectionRequest {
1548    DuplicateDetectionRequest {
1549        reference_id: None,
1550        input_result_set_ids: input_result_sets.iter().map(|s| Utf8String::from(*s)).collect(),
1551        output_result_set_name: Utf8String::from(output_result_set),
1552        applicable_portion: None,
1553        duplicate_detection_criteria: None,
1554        clustering: Some(clustering),
1555        retention_criteria: None,
1556        sorting_criteria: None,
1557        other_info: None,
1558    }
1559}
1560
1561/// Builds a ResourceControlResponse.
1562pub fn make_resource_control_response(continue_flag: bool, result_set_wanted: Option<bool>) -> ResourceControlResponse {
1563    ResourceControlResponse {
1564        reference_id: None,
1565        continue_flag,
1566        result_set_wanted,
1567        other_info: None,
1568    }
1569}
1570
1571/// Builds an AccessControlResponse with simple form.
1572pub fn make_access_control_response(response: &[u8]) -> AccessControlResponse {
1573    AccessControlResponse {
1574        reference_id: None,
1575        security_challenge_response: Some(AccessControlSecurityChallengeResponse::SimpleForm(OctetString::from(response.to_vec()))),
1576        diagnostic: None,
1577        other_info: None,
1578    }
1579}
1580
1581/// Builds a TriggerResourceControlRequest.
1582pub fn make_trigger_resource_control_request(action: TriggerRequestedAction, result_set_wanted: Option<bool>) -> TriggerResourceControlRequest {
1583    TriggerResourceControlRequest {
1584        reference_id: None,
1585        requested_action: action,
1586        preferred_resource_report_format: None,
1587        result_set_wanted,
1588        other_info: None,
1589    }
1590}
1591
1592/// Builds a ResourceReportRequest.
1593pub fn make_resource_report_request(op_id: Option<&[u8]>, preferred_format: Option<ObjectIdentifier>) -> ResourceReportRequest {
1594    ResourceReportRequest {
1595        reference_id: None,
1596        op_id: op_id.map(|b| OctetString::from(b.to_vec())),
1597        preferred_resource_report_format: preferred_format,
1598        other_info: None,
1599    }
1600}
1601
1602/// Extracts raw MARC records from a PresentResponse.
1603///
1604/// Returns an error if the response carries diagnostics instead of records,
1605/// or if the record encoding is not supported.
1606pub fn extract_marc_records(resp: &PresentResponse) -> Result<Vec<Vec<u8>>> {
1607    let mut out = Vec::new();
1608
1609    if let Some(Records::ResponseRecords(records)) = &resp.records {
1610        for rec in records {
1611            match &rec.record {
1612                Record::RetrievalRecord(external) => match &external.encoding {
1613                    ExternalEncoding::OctetAligned(bytes) => {
1614                        out.push(bytes.to_vec());
1615                    }
1616                    ExternalEncoding::SingleASN1Type(any) => {
1617                        out.push(any.as_bytes().to_vec());
1618                    }
1619                    other => {
1620                        return Err(Error::Protocol(format!("unsupported record encoding: {other:?}")));
1621                    }
1622                },
1623                Record::SurrogateDiagnostic(diag) => {
1624                    return Err(Error::Protocol(format!("surrogate diagnostic in record: {diag:?}")));
1625                }
1626                Record::StartingFragment(f) => {
1627                    return Err(Error::Protocol(format!("fragmented record not supported (starting fragment): {f:?}")));
1628                }
1629                Record::IntermediateFragment(f) => {
1630                    return Err(Error::Protocol(format!("fragmented record not supported (intermediate fragment): {f:?}")));
1631                }
1632                Record::FinalFragment(f) => {
1633                    return Err(Error::Protocol(format!("fragmented record not supported (final fragment): {f:?}")));
1634                }
1635            }
1636        }
1637        return Ok(out);
1638    }
1639
1640    match &resp.records {
1641        None => Err(Error::Protocol("present response contains no records".into())),
1642        Some(Records::NonSurrogateDiagnostic(diag)) => Err(Error::Protocol(format!("present response diagnostic: {diag:?}"))),
1643        Some(Records::MultipeNonSurDiagnostic(diags)) => Err(Error::Protocol(format!("present response diagnostics: {diags:?}"))),
1644        Some(other) => Err(Error::Protocol(format!("unexpected records variant in present response: {other:?}"))),
1645    }
1646}