|
|
1.1 root 1: /* D.L.Buck and Associates, Inc. - 8/27/84
2: *
3: * COPYRIGHT NOTICE:
4: * Copyright c 8/27/84 - An unpublished work by
5: * D.L.Buck and Associates, Inc.
6: *
7: * PROPRIETARY RIGHTS NOTICE:
8: * All rights reserved. This document and program contains
9: * proprietary information of D.L.Buck and Associates,Inc.
10: * of San Jose, California, U.S.A., embodying confidential
11: * information, ideas, and expressions, no part of which
12: * may be reproduced, or transmitted in any form or by any
13: * means, electronic, mechanical, or otherwise, without
14: * the written permission of D.L.Buck and Associates, Inc.
15: *
16: * NAME
17: * bsctrace - BISYNC Monitor Information Display Utility
18: *
19: * SYNOPSIS
20: * bsctrace [-data=tttt] [-verbose] [file]
21: *
22: * DESCRIPTION
23: * BSCTRACE interprets trace information as recorded by
24: * BSCMON. The -data option is used to cause printing
25: * of the actual data, in addition to the state trace.
26: *
27: * ALGORITHM
28: * I. Initialize:
29: * A. trace messages
30: * B. ASCII character set table
31: * C. trmat (trace matrix -- index to all bsc state
32: * transitions and their meanings)
33: * D. trace buffer
34: * E. command line options:
35: * 1. verbose
36: * 2. data display format (hex, ASCII, EBCDIC)
37: * F. if data or verbose options set, set up trace
38: * report header
39: * G. open trace device
40: * II. Process trace data:
41: * A. Read a trace buffer full of stuff from the trace
42: * device
43: * B. for-loop: keep extracting trace packets until the
44: * end of the trace buffer is reached, and:
45: * 1. if data or verbose set and new page,
46: * print trace report header
47: * 2. switch on trace type:
48: * a. send, receive,decision,error, or
49: * function types:
50: * i. get time packet was received
51: * ii. if data and verbose not set:
52: * print only time rcv'd,
53: * state #, and state type
54: * else
55: * pull transition meaning
56: * from trmat and print it
57: * with time rcv'd, state # * and state type
58: * iii. get another packet from the
59: * trace buffer
60: * iv. if it's not complete, set
61: * trace buffer pointer to end
62: * of buffer to end loop and
63: * force another read beginning * at next trace packet.
64: * v. break switch
65: * b. class trace type:
66: * i. get time packet was received
67: * ii. if data and verbose not set:
68: * print only time rcv'd, state
69: * #, and state type
70: * else pull class message
71: * and print it, too.
72: * iii. get another packet from the
73: * trace buffer
74: * iv. if it's not complete, set
75: * trace buffer pointer to end
76: * of buffer to end loop and
77: * force another read beginning
78: * at next trace packet.
79: * v. break switch
80: * c. overruns trace type:
81: * i. i-v as above, except that
82: * if data or verbose set,
83: * print number of overruns.
84: * overrun count is kept in
85: * the trace state postion of
86: * trace packets.
87: * d. data packet sent/received type:
88: * i. i-v as above, except that
89: * if data or verbose set,
90: * display data one character
91: * at a time, using the ASCII
92: * character set table to
93: * display in hex, ASCII, or
94: * EBCDIC as user specified.
95: * C. close trace device
96: */
97:
98: #include <stdio.h>
99: /*
100: #include <sys/types.h>
101: */
102: #include <bsc/bsctr.h>
103: #include "local.h"
104: #if defined BSD42
105: #include <sys/time.h>
106: #else
107: #include <time.h>
108: #endif
109:
110: extern int errno;
111: extern char *sys_errlist[];
112:
113: #define HEX 1
114: #define ASCII 2
115: #define EBCDIC 3
116: #define PAGELEN 60 /* for date and verbose mode reports */
117:
118: #ifndef lint
119: static char sccsid[] = "@(#)bsctrace.c 1.15 REL";
120: #endif
121:
122: static long trmat[55][55]; /* index to trace text file;
123: trace text file contains all possible bsc
124: state transitions and their meanings */
125:
126: #define MAX_C 11 /* # of packet types */
127: char *c_info[] = {
128: "Receive timeout",
129: "Enquiry message",
130: "End of transmission, or disconnect",
131: "Negative acknowledgement",
132: "Text block - last of series",
133: "Text block - not last of series",
134: "Wait acknowledgement",
135: "Reverse interrupt",
136: "Even acknowledgement",
137: "Odd acknowledgement",
138: "Temporary transmit delay"
139: };
140:
141: /* ASCII character set */
142: char *tbl1[] = {
143: "00 NU NU ",
144: "01 SH SH ",
145: "02 SX SX ",
146: "03 EX EX ",
147: "04 04 ET ",
148: "05 HT EQ ",
149: "06 06 AK ",
150: "07 RB BL ",
151: "08 08 BS ",
152: "09 09 HT ",
153: "0A 0A LF ",
154: "0B VT VT ",
155: "0C FF FF ",
156: "0D CR CR ",
157: "0E SO SO ",
158: "0F SI SI ",
159: "10 DL DL ",
160: "11 D1 D1 ",
161: "12 D2 D2 ",
162: "13 D3 D3 ",
163: "14 14 D4 ",
164: "15 15 NK ",
165: "16 BS SY ",
166: "17 17 EB ",
167: "18 CN CN ",
168: "19 EM EM ",
169: "1A 1A SB ",
170: "1B 1B EC ",
171: "1C 1C FS ",
172: "1D GS GS ",
173: "1E 1E RS ",
174: "1F US US ",
175: "20 20 SP ",
176: "21 21 ! ",
177: "22 FS \" ",
178: "23 23 # ",
179: "24 24 $ ",
180: "25 LF % ",
181: "26 EB & ",
182: "27 EC ' ",
183: "28 28 ( ",
184: "29 29 ) ",
185: "2A 2A * ",
186: "2B 2B + ",
187: "2C 2C , ",
188: "2D EQ - ",
189: "2E AK . ",
190: "2F BL / ",
191: "30 30 0 ",
192: "31 31 1 ",
193: "32 SY 2 ",
194: "33 33 3 ",
195: "34 34 4 ",
196: "35 RS 5 ",
197: "36 36 6 ",
198: "37 ET 7 ",
199: "38 38 8 ",
200: "39 39 9 ",
201: "3A 3A : ",
202: "3B 3B ; ",
203: "3C D4 < ",
204: "3D NK = ",
205: "3E 3E > ",
206: "3F SB ? ",
207: "40 SP @ ",
208: "41 41 A ",
209: "42 42 B ",
210: "43 43 C ",
211: "44 44 D ",
212: "45 45 E ",
213: "46 46 F ",
214: "47 47 G ",
215: "48 48 H ",
216: "49 49 I ",
217: "4A 4A J ",
218: "4B . K ",
219: "4C < L ",
220: "4D ( M ",
221: "4E + N ",
222: "4F 4F O ",
223: "50 & P ",
224: "51 51 Q ",
225: "52 52 R ",
226: "53 53 S ",
227: "54 54 T ",
228: "55 55 U ",
229: "56 56 V ",
230: "57 57 W ",
231: "58 58 X ",
232: "59 59 Y ",
233: "5A ! Z ",
234: "5B $ [ ",
235: "5C * \\ ",
236: "5D ) ] ",
237: "5E ; ^ ",
238: "5F ^ _ ",
239: "60 - `",
240: "61 / a",
241: "62 62 b",
242: "63 63 c",
243: "64 64 d",
244: "65 65 e",
245: "66 66 f",
246: "67 67 g",
247: "68 68 h",
248: "69 69 i",
249: "6A | j",
250: "6B , k",
251: "6C % l",
252: "6D _ m",
253: "6E > n",
254: "6F ? o",
255: "70 70 p",
256: "71 71 q",
257: "72 72 r",
258: "73 73 s",
259: "74 74 t",
260: "75 75 u",
261: "76 76 v",
262: "77 77 w",
263: "78 78 x",
264: "79 ` y",
265: "7A : z",
266: "7B # {",
267: "7C @ |",
268: "7D ' }",
269: "7E = ~",
270: "7F \" RB" };
271: char *tbl2[] = {
272: "80 80 ",
273: "81 a ",
274: "82 b ",
275: "83 c ",
276: "84 d ",
277: "85 e ",
278: "86 f ",
279: "87 g ",
280: "88 h ",
281: "89 i ",
282: "8A 8A ",
283: "8B 8B ",
284: "8C 8C ",
285: "8D 8D ",
286: "8E 8E ",
287: "8F 8F ",
288: "90 90 ",
289: "91 j ",
290: "92 k ",
291: "93 l ",
292: "94 m ",
293: "95 n ",
294: "96 o ",
295: "97 p ",
296: "98 q ",
297: "99 r ",
298: "9A 9A ",
299: "9B 9B ",
300: "9C 9C ",
301: "9D 9D ",
302: "9E 9E ",
303: "9F 9F ",
304: "A0 A0 ",
305: "A1 ~ ",
306: "A2 s ",
307: "A3 t ",
308: "A4 u ",
309: "A5 v ",
310: "A6 w ",
311: "A7 x ",
312: "A8 y ",
313: "A9 z ",
314: "AA AA ",
315: "AB AB ",
316: "AC AC ",
317: "AD [ ",
318: "AE AE ",
319: "AF AF ",
320: "B0 B0 ",
321: "B1 B1 ",
322: "B2 B2 ",
323: "B3 B3 ",
324: "B4 B4 ",
325: "B5 B5 ",
326: "B6 B6 ",
327: "B7 B7 ",
328: "B8 B8 ",
329: "B9 B9 ",
330: "BA BA ",
331: "BB BB ",
332: "BC BC ",
333: "BD ] ",
334: "BE BE ",
335: "BF BF ",
336: "C0 { ",
337: "C1 A ",
338: "C2 B ",
339: "C3 C ",
340: "C4 D ",
341: "C5 E ",
342: "C6 F ",
343: "C7 G ",
344: "C8 H ",
345: "C9 I ",
346: "CA CA ",
347: "CB CB ",
348: "CC CC ",
349: "CD CD ",
350: "CE CE ",
351: "CF CF ",
352: "D0 } ",
353: "D1 J ",
354: "D2 K ",
355: "D3 L ",
356: "D4 M ",
357: "D5 N ",
358: "D6 O ",
359: "D7 P ",
360: "D8 Q ",
361: "D9 R ",
362: "DA DA ",
363: "DB DB ",
364: "DC DC ",
365: "DD DD ",
366: "DE DE ",
367: "DF DF ",
368: "E0 \\",
369: "E1 E1",
370: "E2 S",
371: "E3 T",
372: "E4 U",
373: "E5 V",
374: "E6 W",
375: "E7 X",
376: "E8 Y",
377: "E9 Z",
378: "EA EA",
379: "EB EB",
380: "EC EC",
381: "ED ED",
382: "EE EE",
383: "EF EF",
384: "F0 0",
385: "F1 1",
386: "F2 2",
387: "F3 3",
388: "F4 4",
389: "F5 5",
390: "F6 6",
391: "F7 7",
392: "F8 8",
393: "F9 9",
394: "FA FA",
395: "FB FB",
396: "FC FC ",
397: "FD FD ",
398: "FE FE ",
399: "FF FF"
400: };
401: char tracebuf[TBUFSIZE]; /* trace information buffer */
402:
403: int xy_ctr; /* count # of data chars on a line */
404: char first[81]; /* 1st 4 bytes of characters for data display
405: go here */
406: char second[81]; /* 2nd 4 bytes of characters for data display
407: go here */
408: char *header[] = { "\f\n",
409: "\t\t\tBSC State Trace Report\n",
410: "\t\t\t\tmm/dd/yy\n",
411: "---------------------------------------------------------------------------\n",
412: " Time\t |State\t| Type\t| Message\n",
413: "---------------------------------------------------------------------------\n"
414: };
415: char *fptr; /* first pointer */
416: char *sptr; /* second pointer */
417: int counter; /* report line count for verbose mode */
418: void exit();
419:
420: main(argc,argv)
421: int argc;
422: char * argv[];
423: {
424: struct tm *localtime();
425: register int len; /* length of data just read from trfd*/
426: register int left = 0; /* # of chars left in the tracebuf */
427: int data = 0; /* data display indicator */
428: register int idx = 0; /* convenient index */
429: int trfd = 0; /* trace information file descriptor */
430: register struct bsctr *trb; /* ptr to current trace packet */
431: char *tptr; /* ptr to ascii char set table */
432: register int chr_count;
433: int verbose = 0;
434: FILE *fopen();
435: FILE *trtxt;
436: long ftell(); /* function returns current value of offset
437: relative to beginning of file */
438: long whence; /* contains value returned by ftell */
439: long time();
440: long currtime;
441:
442: char line[81];
443:
444: char *lptr;
445: register char *pos; /* trb->tr_data pointer */
446: register char last_type = ' '; /* last packet type */
447: register int from; /* bsc state this packet started from */
448: register int to; /* bsc state this packet went to */
449: register int last_state, save_state;
450: register struct tm *t;
451: long xtime;
452: #ifdef ZEROTIME
453: struct tm tm;
454: t = &tm;
455: #endif
456:
457: lptr = &line[0];
458:
459: /* get options from command line */
460: for (counter = 1; counter < argc; ++counter) {
461: if (strcmp(argv[counter],"-verbose") == 0 ||
462: strncmp(argv[counter],"-data",5) == 0) {
463:
464: /* set up trmat */
465: if ((trtxt = fopen("trace.text","r")) == NULL &&
466: (trtxt=fopen("/usr/lib/bsc/trace.text","r"))== NULL)
467: { fprintf(stderr,
468: "bsctrace: can't open trace text file\n");
469: exit(1); }
470:
471: /* fill the trace text matrix (trmat) -- open trace.text file, which contains
472: a list of state transitions and explanations for each one, e.g.
473: 1 0 Start
474: 8 9 Correct ACK
475: .
476: .
477: .
478: save the offset at which the explanation occurs in "whence".
479: trmat[from][to] becomes the value in whence. this saves much
480: file manipulation later when the trace report is prepared. */
481:
482: whence = ftell(trtxt);
483: while (fgets(line,sizeof line,trtxt) != NULL) {
484: /* "from" state is line[0] */
485: from = atoi(lptr);
486: /* "to" state is line[2] */
487: lptr += 2;
488: to = atoi(lptr);
489: whence += 6L; /* offset in tracetext file where
490: description of state transition
491: [from][to] is kept */
492: trmat[from][to] = whence;
493: lptr = &line[0];
494: whence = ftell(trtxt);
495: }
496: if (strcmp(argv[counter],"-data=hex") == 0) {
497: data = HEX;
498: continue;
499: }
500: if (strcmp(argv[counter],"-data=ebcdic") == 0) {
501: data = EBCDIC;
502: continue;
503: }
504: if (strcmp(argv[counter],"-data=ascii") == 0) {
505: data = ASCII;
506: continue;
507: }
508: if (strcmp(argv[counter],"-verbose") == 0) {
509: ++verbose;
510: continue;
511: }
512: }
513: if (argv[counter][0] == '-' || counter != argc-1) {
514: fprintf(stderr,"bsctrace: invalid parameter (%s)\n",
515: argv[counter]);
516: exit(1);
517: }
518:
519:
520: trfd = open(argv[counter],0); /* open the trace device */
521: if (trfd < 0) {
522: fprintf(stderr,
523: "bsctrace: can't read trace information file %s: %s\n",
524: argv[counter],sys_errlist[errno]);
525: exit(1);
526: }
527: }
528:
529: if (data || verbose) { /* set up trace report header */
530: currtime = time((long*)0);
531: t = localtime(&currtime);
532: sprintf(header[2],"\t\t\t\t%02d/%02d/%02d\n",
533: t->tm_mon+1,t->tm_mday,t->tm_year);
534: }
535:
536: /* begin reading trace information */
537: last_state = 0;
538: counter = PAGELEN; /* force headers to display first */
539:
540: again:
541: while ((len = left + read(trfd,&tracebuf[left],sizeof tracebuf - left)) > 0)
542:
543: for(trb = (struct bsctr *)tracebuf;
544: trb < (struct bsctr *) &tracebuf[len]; ) {
545: if ((data || verbose) && counter >= PAGELEN)
546: disp_header();
547:
548: switch (trb->tr_type) {
549: case 'd': /* decision */
550: case 'e': /* error */
551: case 'f': /* function */
552: case 'r': /* receive */
553: case 's': /* send */
554:
555: /* if ZEROTIME is defined in local.h, use time as is. Otherwise do localtime
556: to get the actual time. */
557: xtime = btol(&trb->tr_time);
558: #ifndef ZEROTIME
559: t = localtime(&xtime);
560: #else
561: t->tm_hour = (xtime/60)/60;
562: t->tm_min = (xtime/60)%60;
563: t->tm_sec = xtime%60;
564: #endif
565: if (!verbose && !data)
566: printf("%02d:%02d:%02d %2d %c\n",
567: t->tm_hour,t->tm_min,t->tm_sec,
568: trb->tr_state,trb->tr_type);
569: else {
570: if (last_type >= 'x')
571: disp_data();
572:
573: save_state = last_state;
574: whence = trmat[last_state][trb->tr_state];
575: last_state = trb->tr_state;
576: if (whence) {
577: fseek(trtxt,whence,0);
578: if (fgets(line,sizeof line, trtxt) == NULL) {
579: fprintf(stderr,
580: "bsctrace: last_state %d, whernce %d\n",
581: save_state,whence);
582: fprintf(stderr,
583: "bsctrace: can't read trace text file\n");
584: exit(1); }
585:
586: line[strlen(line)-1] = '\0';
587: }
588: else
589: line[0] = '\0';
590:
591: printf("%02d:%02d:%02d | %2d\t| %c\t| ",
592: t->tm_hour,t->tm_min,t->tm_sec,
593: trb->tr_state,trb->tr_type);
594: chr_count = strlen(line);
595: if (chr_count < 50 || chr_count == 0)
596: printf("%s\n",line);
597: else {
598: bcopy(first,line,50);
599: lptr = &line[50];
600: printf("%s\n",first);
601: printf("\t |\t|\t| %s\n",lptr);
602: if (++counter>=PAGELEN)
603: disp_header();
604: }
605: ++counter;
606: }
607:
608: last_type = trb->tr_type;
609: trb = (struct bsctr *)&trb->tr_val;
610: left=chk_left(trb,len);
611: break;
612:
613: case 'c': /* class */
614: xtime = btol(&trb->tr_time);
615: #ifndef ZEROTIME
616: t = localtime(&xtime);
617: #else
618: t->tm_hour = (xtime/60)/60;
619: t->tm_min = (xtime/60)%60;
620: t->tm_sec = xtime%60;
621: #endif
622: if (!verbose && !data)
623: printf("%02d:%02d:%02d %2d %c\n",
624: t->tm_hour,t->tm_min,t->tm_sec,
625: trb->tr_state,trb->tr_type);
626: else {
627: if (last_type >= 'x')
628: disp_data();
629:
630: printf("%02d:%02d:%02d | %2d\t| %c\t|",
631: t->tm_hour,t->tm_min,t->tm_sec,
632: trb->tr_state,trb->tr_type);
633: if (trb->tr_state + 1 < MAX_C)
634: printf(" %s\n",c_info[trb->tr_state+1]);
635: else
636: fputs("\n",stdout);
637: ++counter;
638: }
639: last_type = trb->tr_type;
640: trb = (struct bsctr *)&trb->tr_val;
641: left = chk_left (trb, len);
642: break;
643:
644: case 'o': /* overruns */
645: xtime = btol(&trb->tr_time);
646: #ifndef ZEROTIME
647: t = localtime(&xtime);
648: #else
649: t->tm_hour = (xtime/60)/60;
650: t->tm_min = (xtime/60)%60;
651: t->tm_sec = xtime%60;
652: #endif
653: if (!verbose && !data)
654: printf("%02d:%02d:%02d %2d %c\n",
655: t->tm_hour,t->tm_min,t->tm_sec,
656: trb->tr_state,trb->tr_type);
657: else {
658: if (last_type >= 'x')
659: disp_data();
660:
661: printf ("%02d:%02d:%02d | %2d\t| %c\t| %s %d\n",
662: t->tm_hour,t->tm_min,t->tm_sec,trb->tr_state,
663: trb->tr_type,"Trace overruns:",trb->tr_state);
664:
665: ++counter;
666: }
667: last_type = trb->tr_type;
668: trb = (struct bsctr *)&trb->tr_val;
669: left = chk_left (trb, len);
670: break;
671:
672: case 'y':
673: case 'x': /* data being sent (x) or rcv'ed (y) */
674: xtime = btol(&trb->tr_time);
675: #ifndef ZEROTIME
676: t = localtime(&xtime);
677: #else
678: t->tm_hour = (xtime/60)/60;
679: t->tm_min = (xtime/60)%60;
680: t->tm_sec = xtime%60;
681: #endif
682: if (!data && !verbose) {
683: if (last_type != trb->tr_type)
684: printf("%02d:%02d:%02d %2d %s\n",
685: t->tm_hour,t->tm_min,t->tm_sec,
686: trb->tr_state,
687: (trb->tr_type == 'x')?"TD":"RD");
688: }
689: else {
690: if (last_type>='x' && last_type!=trb->tr_type)
691: disp_data();
692: if (last_type != trb->tr_type) {
693: printf("%02d:%02d:%02d | %2d\t| %s\t| ",
694: t->tm_hour,t->tm_min,t->tm_sec,
695: trb->tr_state,
696: (trb->tr_type == 'x')?"TD":"RD");
697:
698: xy_ctr = 0;
699: fptr = first;
700: sptr = second;
701: }
702:
703: if (data && trb->tr_val) {
704: pos = &trb->tr_data[0];
705:
706: while (pos != &trb->tr_data[trb->tr_val]){
707: idx = *pos & 0xff;
708: tptr=((idx< 0x80)?tbl1[idx]:tbl2[idx-0x80]);
709: pos++;
710: if (data == EBCDIC) {
711: *fptr++ = tptr[3];
712: *sptr++ = tptr[4];
713: }
714: if (data == ASCII) {
715: *fptr++ = tptr[6];
716: *sptr++ = tptr[7];
717: }
718: if (data == HEX) {
719: *fptr++ = tptr[0];
720: *sptr++ = tptr[1];
721: }
722: ++xy_ctr;
723: if (xy_ctr == 50) {
724: *fptr = *sptr = '\0';
725: printf("%s\n",first);
726: if (++counter>=PAGELEN)
727: disp_header();
728: printf("\t |\t|\t| %s\n",
729: second);
730: xy_ctr = 0;
731: fptr = first;
732: sptr = second;
733: if (++counter >= PAGELEN)
734: disp_header();
735: printf("\t |\t|\t| ");
736: }
737: continue;
738: }
739: }
740: }
741: if (trb->tr_val & 1) /* round to word */
742: ++trb->tr_val;
743: last_type = trb->tr_type;
744: trb = (struct bsctr *)&trb->tr_data[trb->tr_val];
745: left = chk_left (trb, len);
746: break;
747:
748: default:
749: printf("*** Unknown trace type: %c (0x%x) 0x%X 0x%x\n",
750: trb->tr_type,trb->tr_type,trb->tr_time,
751: trb->tr_state);
752: goto again;
753: /* abort() */
754: }
755: if (left) break; /* need to read more */
756: }
757: close(trfd);
758: }
759:
760: /* chk_left - check whether or not there are enough characters left in the
761: trace buffer for a complete packet */
762: chk_left(trb,len)
763: register struct bsctr *trb;
764: register int len;
765: {
766: register int left;
767:
768: left = &tracebuf[len] - (char *)trb;
769: if (left < 6 || (trb->tr_type >= 'x' && left < 8) ||
770: (trb->tr_type >= 'x' && left < (8 + trb->tr_val))) {
771: bcopy (tracebuf, (char *)trb, left);
772: return(left);
773: }
774: else return(0);
775: }
776: /* disp_data - display x and/or y packet data */
777: disp_data()
778: {
779: if (xy_ctr) {
780: *fptr = *sptr = '\0';
781: puts(first);
782: if (++counter >= PAGELEN)
783: disp_header();
784: printf("\t |\t|\t| %s\n",second);
785: xy_ctr = 0;
786: ++counter;
787: }
788: else {
789: putchar('\n');
790: ++counter;
791: }
792:
793: if (counter >= PAGELEN)
794: disp_header();
795: }
796:
797: /* disp_header - display verbose/data mode report header */
798: disp_header()
799: {
800: for (counter = 0; counter < 6; ++counter)
801: fputs(header[counter],stdout);
802: }
803:
804: btol(bptr)
805: char *bptr;
806: { register long lx;
807: lx = (bptr[0] << 24) | (bptr[1] << 16) | (bptr[2] << 8) | (bptr[3]);
808: return(lx);
809: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.