Files
Aaru/Aaru.Core/Devices/Report/Scsi.cs

1078 lines
41 KiB
C#
Raw Normal View History

// /***************************************************************************
2020-02-27 12:31:25 +00:00
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : General.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Core algorithms.
//
// --[ Description ] ----------------------------------------------------------
//
// Creates reports from SCSI devices.
//
// --[ License ] --------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
2022-02-18 10:02:53 +00:00
// Copyright © 2011-2022 Natalia Portillo
// ****************************************************************************/
2022-03-07 07:36:44 +00:00
namespace Aaru.Core.Devices.Report;
using System;
using System.Collections.Generic;
using System.Linq;
2020-02-27 00:33:26 +00:00
using Aaru.CommonTypes.Metadata;
using Aaru.CommonTypes.Structs.Devices.SCSI;
using Aaru.Console;
using Aaru.Decoders.SCSI;
using Aaru.Devices;
2020-07-20 15:43:52 +01:00
using Aaru.Helpers;
2022-03-07 07:36:44 +00:00
using global::Spectre.Console;
2020-02-27 00:33:26 +00:00
using Inquiry = Aaru.CommonTypes.Structs.Devices.SCSI.Inquiry;
2022-03-06 13:29:38 +00:00
public sealed partial class DeviceReport
{
2022-03-06 13:29:38 +00:00
/// <summary>Creates a report for the SCSI INQUIRY response</summary>
/// <returns>SCSI report</returns>
public Scsi ReportScsiInquiry()
{
2022-03-07 07:36:44 +00:00
var sense = true;
2022-03-06 13:29:38 +00:00
byte[] buffer = Array.Empty<byte>();
Spectre.ProgressSingleSpinner(ctx =>
{
2022-03-06 13:29:38 +00:00
ctx.AddTask("Querying SCSI INQUIRY...").IsIndeterminate();
sense = _dev.ScsiInquiry(out buffer, out _);
});
2021-09-13 18:08:44 +01:00
2022-03-06 13:29:38 +00:00
var report = new Scsi();
2022-03-06 13:29:38 +00:00
if(sense)
return null;
2022-03-06 13:29:38 +00:00
Inquiry? decodedNullable = Inquiry.Decode(buffer);
2018-12-25 14:46:02 +00:00
2022-03-06 13:29:38 +00:00
if(!decodedNullable.HasValue)
return null;
2018-12-25 14:46:02 +00:00
2022-03-06 13:29:38 +00:00
report.InquiryData = ClearInquiry(buffer);
2018-12-25 14:46:02 +00:00
2022-03-06 13:29:38 +00:00
return report;
}
2022-03-06 13:29:38 +00:00
internal static byte[] ClearInquiry(byte[] inquiry)
{
Inquiry? decodedNullable = Inquiry.Decode(inquiry);
2022-03-06 13:29:38 +00:00
if(!decodedNullable.HasValue)
return inquiry;
2022-03-06 13:29:38 +00:00
Inquiry decoded = decodedNullable.Value;
2022-03-06 13:29:38 +00:00
if(!decoded.SeagatePresent ||
StringHandlers.CToString(decoded.VendorIdentification)?.Trim().ToLowerInvariant() != "seagate")
return inquiry;
2018-12-25 14:46:02 +00:00
2022-03-06 13:29:38 +00:00
// Clear Seagate serial number
2022-03-07 07:36:44 +00:00
for(var i = 36; i <= 43; i++)
2022-03-06 13:29:38 +00:00
inquiry[i] = 0;
2020-07-22 13:20:25 +01:00
2022-03-06 13:29:38 +00:00
return inquiry;
}
2022-03-06 13:29:38 +00:00
/// <summary>Returns a list of decoded SCSI EVPD pages</summary>
/// <param name="vendor">Decoded SCSI vendor identification</param>
/// <returns>List of decoded SCSI EVPD pages</returns>
public List<ScsiPage> ReportEvpdPages(string vendor)
{
2022-03-07 07:36:44 +00:00
var sense = false;
2022-03-06 13:29:38 +00:00
byte[] buffer = Array.Empty<byte>();
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
2022-03-06 13:29:38 +00:00
ctx.AddTask("Querying list of SCSI EVPDs...").IsIndeterminate();
sense = _dev.ScsiInquiry(out buffer, out _, 0x00);
});
2021-09-13 18:08:44 +01:00
2022-03-06 13:29:38 +00:00
if(sense)
return null;
2022-03-06 13:29:38 +00:00
byte[] evpdPages = EVPD.DecodePage00(buffer);
2017-12-19 20:33:03 +00:00
2022-03-06 13:29:38 +00:00
if(evpdPages == null ||
evpdPages.Length <= 0)
return null;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
List<ScsiPage> evpds = new();
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ProgressTask task = ctx.
2022-03-07 07:36:44 +00:00
AddTask("Querying SCSI EVPD pages...", maxValue: evpdPages.Count(page => page != 0x80)).
IsIndeterminate();
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
foreach(byte page in evpdPages.Where(page => page != 0x80))
{
2022-03-06 13:29:38 +00:00
task.Description = $"Querying SCSI EVPD {page:X2}h...";
task.Increment(1);
sense = _dev.ScsiInquiry(out buffer, out _, page);
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
if(sense)
continue;
2022-03-06 13:29:38 +00:00
byte[] empty;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
switch(page)
{
case 0x83:
buffer = ClearPage83(buffer);
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
break;
case 0x80:
2022-03-07 07:36:44 +00:00
var identify = new byte[512];
2022-03-06 13:29:38 +00:00
Array.Copy(buffer, 60, identify, 0, 512);
identify = ClearIdentify(identify);
Array.Copy(identify, 0, buffer, 60, 512);
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
break;
case 0xB1:
case 0xB3:
empty = new byte[buffer.Length - 4];
Array.Copy(empty, 0, buffer, 4, buffer.Length - 4);
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
break;
case 0xC1 when vendor == "ibm":
empty = new byte[12];
Array.Copy(empty, 0, buffer, 4, 12);
Array.Copy(empty, 0, buffer, 16, 12);
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
break;
case 0xC2 when vendor == "certance":
case 0xC3 when vendor == "certance":
case 0xC4 when vendor == "certance":
case 0xC5 when vendor == "certance":
case 0xC6 when vendor == "certance":
Array.Copy(new byte[12], 0, buffer, 4, 12);
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
break;
}
2018-12-25 14:46:02 +00:00
2022-03-06 13:29:38 +00:00
var evpd = new ScsiPage
{
page = page,
value = buffer
};
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
evpds.Add(evpd);
}
});
2021-09-13 18:08:44 +01:00
2022-03-06 13:29:38 +00:00
return evpds.Count > 0 ? evpds : null;
}
2022-03-06 13:29:38 +00:00
byte[] ClearPage83(byte[] pageResponse)
{
if(pageResponse?[1] != 0x83)
return null;
2022-03-06 13:29:38 +00:00
if(pageResponse[3] + 4 != pageResponse.Length)
return null;
2018-12-25 14:46:02 +00:00
2022-03-06 13:29:38 +00:00
if(pageResponse.Length < 6)
return null;
2018-12-25 14:46:02 +00:00
2022-03-07 07:36:44 +00:00
var position = 4;
2018-12-25 14:46:02 +00:00
2022-03-06 13:29:38 +00:00
while(position < pageResponse.Length)
{
byte length = pageResponse[position + 3];
2018-12-25 14:46:02 +00:00
2022-03-06 13:29:38 +00:00
if(length + position + 4 >= pageResponse.Length)
length = (byte)(pageResponse.Length - position - 4);
2019-12-27 18:00:03 +00:00
2022-03-07 07:36:44 +00:00
var empty = new byte[length];
2022-03-06 13:29:38 +00:00
Array.Copy(empty, 0, pageResponse, position + 4, length);
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
position += 4 + length;
}
2018-12-25 14:46:02 +00:00
2022-03-06 13:29:38 +00:00
return pageResponse;
}
2018-12-25 14:46:02 +00:00
2022-03-06 13:29:38 +00:00
/// <summary>Adds reports for the decoded SCSI MODE SENSE pages to a device report</summary>
/// <param name="report">Device report</param>
/// <param name="cdromMode">Returns raw MODE SENSE page 2Ah, aka CD-ROM page</param>
/// <param name="mediumType">Returns decoded list of supported media types response</param>
public void ReportScsiModes(ref DeviceReportV2 report, out byte[] cdromMode, out MediumTypes mediumType)
{
Modes.DecodedMode? decMode = null;
PeripheralDeviceTypes devType = _dev.ScsiType;
byte[] mode10Buffer;
byte[] mode6Buffer;
bool sense;
mediumType = 0;
2018-12-25 14:46:02 +00:00
2022-03-06 13:29:38 +00:00
DeviceReportV2 v2 = report;
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask("Querying all mode pages and subpages using SCSI MODE SENSE (10)...").IsIndeterminate();
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
foreach(ScsiModeSensePageControl pageControl in new[]
{
ScsiModeSensePageControl.Default, ScsiModeSensePageControl.Current,
ScsiModeSensePageControl.Changeable
})
{
2022-03-07 07:36:44 +00:00
var saveBuffer = false;
2019-12-27 18:00:03 +00:00
2022-03-07 07:36:44 +00:00
sense = _dev.ModeSense10(out mode10Buffer, out _, false, true, pageControl, 0x3F, 0xFF, _dev.Timeout,
out _);
2021-09-13 18:08:44 +01:00
2022-03-06 13:29:38 +00:00
if(sense || _dev.Error)
{
sense = _dev.ModeSense10(out mode10Buffer, out _, false, false, pageControl, 0x3F, 0xFF,
_dev.Timeout, out _);
if(sense || _dev.Error)
{
2022-03-06 13:29:38 +00:00
sense = _dev.ModeSense10(out mode10Buffer, out _, false, true, pageControl, 0x3F, 0x00,
_dev.Timeout, out _);
if(sense || _dev.Error)
{
2022-03-06 13:29:38 +00:00
sense = _dev.ModeSense10(out mode10Buffer, out _, false, false, pageControl, 0x3F, 0x00,
_dev.Timeout, out _);
2022-03-06 13:29:38 +00:00
if(!sense &&
!_dev.Error)
{
2021-09-13 18:08:44 +01:00
v2.SCSI.SupportsModeSense10 = true;
decMode ??= Modes.DecodeMode10(mode10Buffer, devType);
saveBuffer = true;
}
}
else
{
2022-03-06 13:29:38 +00:00
v2.SCSI.SupportsModeSense10 = true;
decMode ??= Modes.DecodeMode10(mode10Buffer, devType);
saveBuffer = true;
}
}
else
{
2021-09-13 18:08:44 +01:00
v2.SCSI.SupportsModeSense10 = true;
v2.SCSI.SupportsModeSubpages = true;
decMode ??= Modes.DecodeMode10(mode10Buffer, devType);
saveBuffer = true;
}
2022-03-06 13:29:38 +00:00
}
else
{
v2.SCSI.SupportsModeSense10 = true;
v2.SCSI.SupportsModeSubpages = true;
decMode ??= Modes.DecodeMode10(mode10Buffer, devType);
saveBuffer = true;
}
2022-03-06 13:29:38 +00:00
if(!saveBuffer)
continue;
2022-03-06 13:29:38 +00:00
switch(pageControl)
{
case ScsiModeSensePageControl.Default:
v2.SCSI.ModeSense10Data = mode10Buffer;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
break;
case ScsiModeSensePageControl.Changeable:
v2.SCSI.ModeSense10ChangeableData = mode10Buffer;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
break;
case ScsiModeSensePageControl.Current:
v2.SCSI.ModeSense10CurrentData = mode10Buffer;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
break;
}
2022-03-06 13:29:38 +00:00
}
});
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask("Querying all mode pages and subpages using SCSI MODE SENSE (6)...").IsIndeterminate();
2022-03-06 13:29:38 +00:00
foreach(ScsiModeSensePageControl pageControl in new[]
{
ScsiModeSensePageControl.Default, ScsiModeSensePageControl.Current,
ScsiModeSensePageControl.Changeable
})
{
2022-03-07 07:36:44 +00:00
var saveBuffer = false;
2022-03-06 13:29:38 +00:00
sense = _dev.ModeSense6(out mode6Buffer, out _, true, pageControl, 0x3F, 0xFF, _dev.Timeout, out _);
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
if(sense || _dev.Error)
2021-09-13 18:08:44 +01:00
{
2022-03-06 13:29:38 +00:00
sense = _dev.ModeSense6(out mode6Buffer, out _, false, pageControl, 0x3F, 0xFF, _dev.Timeout,
out _);
if(sense || _dev.Error)
{
2022-03-06 13:29:38 +00:00
sense = _dev.ModeSense6(out mode6Buffer, out _, true, pageControl, 0x3F, 0x00, _dev.Timeout,
out _);
if(sense || _dev.Error)
{
2022-03-06 13:29:38 +00:00
sense = _dev.ModeSense6(out mode6Buffer, out _, false, pageControl, 0x3F, 0x00,
_dev.Timeout, out _);
if(sense || _dev.Error)
{
2022-03-06 13:29:38 +00:00
sense = _dev.ModeSense6(out mode6Buffer, out _, true, pageControl, 0x00, 0x00,
_dev.Timeout, out _);
if(sense || _dev.Error)
{
2022-03-06 13:29:38 +00:00
sense = _dev.ModeSense6(out mode6Buffer, out _, false, pageControl, 0x00, 0x00,
_dev.Timeout, out _);
2022-03-06 13:29:38 +00:00
if(!sense &&
!_dev.Error)
{
2021-09-13 18:08:44 +01:00
v2.SCSI.SupportsModeSense6 = true;
decMode ??= Modes.DecodeMode6(mode6Buffer, devType);
saveBuffer = true;
}
}
else
{
2021-09-13 18:08:44 +01:00
v2.SCSI.SupportsModeSense6 = true;
decMode ??= Modes.DecodeMode6(mode6Buffer, devType);
saveBuffer = true;
}
}
else
{
2021-09-13 18:08:44 +01:00
v2.SCSI.SupportsModeSense6 = true;
decMode ??= Modes.DecodeMode6(mode6Buffer, devType);
saveBuffer = true;
}
}
else
{
2022-03-06 13:29:38 +00:00
v2.SCSI.SupportsModeSense6 = true;
decMode ??= Modes.DecodeMode6(mode6Buffer, devType);
saveBuffer = true;
}
}
else
{
2022-03-06 13:29:38 +00:00
v2.SCSI.SupportsModeSense10 = true;
2021-09-13 18:08:44 +01:00
v2.SCSI.SupportsModeSubpages = true;
decMode ??= Modes.DecodeMode6(mode6Buffer, devType);
saveBuffer = true;
}
2022-03-06 13:29:38 +00:00
}
else
{
v2.SCSI.SupportsModeSense6 = true;
v2.SCSI.SupportsModeSubpages = true;
decMode ??= Modes.DecodeMode6(mode6Buffer, devType);
saveBuffer = true;
}
2022-03-06 13:29:38 +00:00
if(!saveBuffer)
continue;
2022-03-06 13:29:38 +00:00
switch(pageControl)
{
case ScsiModeSensePageControl.Default:
v2.SCSI.ModeSense6Data = mode6Buffer;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
break;
case ScsiModeSensePageControl.Changeable:
v2.SCSI.ModeSense6ChangeableData = mode6Buffer;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
break;
case ScsiModeSensePageControl.Current:
v2.SCSI.ModeSense6CurrentData = mode6Buffer;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
break;
}
2022-03-06 13:29:38 +00:00
}
});
report = v2;
cdromMode = null;
2022-03-18 01:32:22 +00:00
if(decMode == null)
2022-03-06 13:29:38 +00:00
return;
2021-09-13 18:08:44 +01:00
2022-03-06 13:29:38 +00:00
mediumType = decMode.Value.Header.MediumType;
2022-03-06 13:29:38 +00:00
report.SCSI.ModeSense = new ScsiMode
{
BlankCheckEnabled = decMode.Value.Header.EBC,
DPOandFUA = decMode.Value.Header.DPOFUA,
WriteProtected = decMode.Value.Header.WriteProtected
};
if(decMode.Value.Header.BufferedMode > 0)
report.SCSI.ModeSense.BufferedMode = decMode.Value.Header.BufferedMode;
if(decMode.Value.Header.Speed > 0)
report.SCSI.ModeSense.Speed = decMode.Value.Header.Speed;
2022-03-06 13:29:38 +00:00
if(decMode.Value.Pages == null)
return;
2022-03-06 13:29:38 +00:00
List<ScsiPage> modePages = new();
2022-03-06 13:29:38 +00:00
foreach(Modes.ModePage page in decMode.Value.Pages)
{
var modePage = new ScsiPage
{
2022-03-06 13:29:38 +00:00
page = page.Page,
subpage = page.Subpage,
value = page.PageResponse
};
2022-03-06 13:29:38 +00:00
modePages.Add(modePage);
2022-03-06 13:29:38 +00:00
if(modePage.page == 0x2A &&
modePage.subpage == 0x00)
cdromMode = page.PageResponse;
}
2022-03-06 13:29:38 +00:00
if(modePages.Count > 0)
report.SCSI.ModeSense.ModePages = modePages;
}
2022-03-06 13:29:38 +00:00
/// <summary>Creates a report for media inserted into a SCSI device</summary>
/// <returns>Media report</returns>
public TestedMedia ReportScsiMedia()
{
var mediaTest = new TestedMedia();
2022-03-07 07:36:44 +00:00
var sense = true;
2022-03-06 13:29:38 +00:00
byte[] buffer = Array.Empty<byte>();
byte[] senseBuffer = Array.Empty<byte>();
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask("Querying SCSI READ CAPACITY...").IsIndeterminate();
sense = _dev.ReadCapacity(out buffer, out senseBuffer, _dev.Timeout, out _);
});
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
if(!sense &&
!_dev.Error)
{
mediaTest.SupportsReadCapacity = true;
2022-03-06 13:29:38 +00:00
mediaTest.Blocks = ((ulong)((buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[3]) &
0xFFFFFFFF) + 1;
2022-03-06 13:29:38 +00:00
mediaTest.BlockSize = (uint)((buffer[4] << 24) + (buffer[5] << 16) + (buffer[6] << 8) + buffer[7]);
}
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
2022-03-06 13:29:38 +00:00
ctx.AddTask("Querying SCSI READ CAPACITY (16)...").IsIndeterminate();
sense = _dev.ReadCapacity16(out buffer, out buffer, _dev.Timeout, out _);
});
2021-09-13 18:08:44 +01:00
2022-03-06 13:29:38 +00:00
if(!sense &&
!_dev.Error)
{
mediaTest.SupportsReadCapacity16 = true;
2022-03-07 07:36:44 +00:00
var temp = new byte[8];
2022-03-06 13:29:38 +00:00
Array.Copy(buffer, 0, temp, 0, 8);
Array.Reverse(temp);
mediaTest.Blocks = BitConverter.ToUInt64(temp, 0) + 1;
mediaTest.BlockSize = (uint)((buffer[8] << 24) + (buffer[9] << 16) + (buffer[10] << 8) + buffer[11]);
}
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
Modes.DecodedMode? decMode = null;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask("Querying SCSI MODE SENSE (10)...").IsIndeterminate();
2019-12-27 18:00:03 +00:00
2022-03-07 07:36:44 +00:00
sense = _dev.ModeSense10(out buffer, out senseBuffer, false, true, ScsiModeSensePageControl.Current, 0x3F,
0x00, _dev.Timeout, out _);
2022-03-06 13:29:38 +00:00
});
2022-03-06 13:29:38 +00:00
if(!sense &&
!_dev.Error)
{
decMode = Modes.DecodeMode10(buffer, _dev.ScsiType);
mediaTest.ModeSense10Data = buffer;
}
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask("Querying SCSI MODE SENSE...").IsIndeterminate();
sense = _dev.ModeSense(out buffer, out senseBuffer, _dev.Timeout, out _);
});
2022-03-06 13:29:38 +00:00
if(!sense &&
!_dev.Error)
{
decMode ??= Modes.DecodeMode6(buffer, _dev.ScsiType);
2022-03-06 13:29:38 +00:00
mediaTest.ModeSense6Data = buffer;
}
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
if(decMode.HasValue)
{
mediaTest.MediumType = (byte)decMode.Value.Header.MediumType;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
if(decMode.Value.Header.BlockDescriptors?.Length > 0)
mediaTest.Density = (byte)decMode.Value.Header.BlockDescriptors[0].Density;
}
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask("Trying SCSI READ (6)...").IsIndeterminate();
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
mediaTest.SupportsRead6 = !_dev.Read6(out buffer, out senseBuffer, 0, mediaTest.BlockSize ?? 512,
_dev.Timeout, out _);
});
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
AaruConsole.DebugWriteLine("SCSI Report", "Sense = {0}", !mediaTest.SupportsRead6);
mediaTest.Read6Data = buffer;
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask("Trying SCSI READ (10)...").IsIndeterminate();
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
mediaTest.SupportsRead10 = !_dev.Read10(out buffer, out senseBuffer, 0, false, false, false, false, 0,
mediaTest.BlockSize ?? 512, 0, 1, _dev.Timeout, out _);
});
2022-03-06 13:29:38 +00:00
AaruConsole.DebugWriteLine("SCSI Report", "Sense = {0}", !mediaTest.SupportsRead10);
mediaTest.Read10Data = buffer;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask("Trying SCSI READ (12)...").IsIndeterminate();
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
mediaTest.SupportsRead12 = !_dev.Read12(out buffer, out senseBuffer, 0, false, false, false, false, 0,
mediaTest.BlockSize ?? 512, 0, 1, false, _dev.Timeout, out _);
});
2022-03-06 13:29:38 +00:00
AaruConsole.DebugWriteLine("SCSI Report", "Sense = {0}", !mediaTest.SupportsRead12);
mediaTest.Read12Data = buffer;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask("Trying SCSI READ (16)...").IsIndeterminate();
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
mediaTest.SupportsRead16 = !_dev.Read16(out buffer, out senseBuffer, 0, false, false, false, 0,
mediaTest.BlockSize ?? 512, 0, 1, false, _dev.Timeout, out _);
});
2022-03-06 13:29:38 +00:00
AaruConsole.DebugWriteLine("SCSI Report", "Sense = {0}", !mediaTest.SupportsRead16);
mediaTest.Read16Data = buffer;
mediaTest.LongBlockSize = mediaTest.BlockSize;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask("Trying SCSI READ LONG (10)...").IsIndeterminate();
sense = _dev.ReadLong10(out buffer, out senseBuffer, false, false, 0, 0xFFFF, _dev.Timeout, out _);
});
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
if(sense && !_dev.Error)
{
DecodedSense? decSense = Sense.Decode(senseBuffer);
if(decSense is { SenseKey: SenseKeys.IllegalRequest, ASC: 0x24, ASCQ: 0x00 })
2021-09-13 18:08:44 +01:00
{
2022-03-06 13:29:38 +00:00
mediaTest.SupportsReadLong = true;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
bool valid = decSense.Value.Fixed?.InformationValid == true;
bool ili = decSense.Value.Fixed?.ILI == true;
uint information = decSense.Value.Fixed?.Information ?? 0;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
if(decSense.Value.Descriptor.HasValue &&
decSense.Value.Descriptor.Value.Descriptors.TryGetValue(0, out byte[] desc00))
{
valid = true;
ili = true;
information = (uint)Sense.DecodeDescriptor00(desc00);
}
2022-03-06 13:29:38 +00:00
if(valid && ili)
mediaTest.LongBlockSize = 0xFFFF - (information & 0xFFFF);
}
}
2021-09-13 18:08:44 +01:00
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask("Trying SCSI READ LONG (16)...").IsIndeterminate();
sense = _dev.ReadLong16(out buffer, out senseBuffer, false, 0, 0xFFFF, _dev.Timeout, out _);
2019-12-27 18:00:03 +00:00
if(sense && !_dev.Error)
{
DecodedSense? decSense = Sense.Decode(senseBuffer);
2019-12-27 18:00:03 +00:00
if(decSense is { SenseKey: SenseKeys.IllegalRequest, ASC: 0x24, ASCQ: 0x00 })
2020-07-22 13:20:25 +01:00
{
2022-03-06 13:29:38 +00:00
mediaTest.SupportsReadLong16 = true;
2019-12-27 18:00:03 +00:00
bool valid = decSense.Value.Fixed?.InformationValid == true;
bool ili = decSense.Value.Fixed?.ILI == true;
uint information = decSense.Value.Fixed?.Information ?? 0;
if(decSense.Value.Descriptor.HasValue &&
decSense.Value.Descriptor.Value.Descriptors.TryGetValue(0, out byte[] desc00))
{
valid = true;
ili = true;
information = (uint)Sense.DecodeDescriptor00(desc00);
}
if(valid && ili)
mediaTest.LongBlockSize = 0xFFFF - (information & 0xFFFF);
2020-07-22 13:20:25 +01:00
}
}
2022-03-06 13:29:38 +00:00
if((mediaTest.SupportsReadLong == true || mediaTest.SupportsReadLong16 == true) &&
mediaTest.LongBlockSize == mediaTest.BlockSize)
switch(mediaTest.BlockSize)
{
2022-03-06 13:29:38 +00:00
case 512:
{
2022-03-06 13:29:38 +00:00
foreach(ushort testSize in new ushort[]
{
// Long sector sizes for floppies
514,
2021-09-13 18:08:44 +01:00
2022-03-06 13:29:38 +00:00
// Long sector sizes for SuperDisk
536, 558,
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
// Long sector sizes for 512-byte magneto-opticals
600, 610, 630
})
{
2022-03-06 13:29:38 +00:00
sense = mediaTest.SupportsReadLong16 == true
2022-03-07 07:36:44 +00:00
? _dev.ReadLong16(out buffer, out senseBuffer, false, 0, testSize, _dev.Timeout,
out _) : _dev.ReadLong10(out buffer, out senseBuffer, false,
false, 0, testSize, _dev.Timeout, out _);
2022-03-06 13:29:38 +00:00
if(sense || _dev.Error)
continue;
2021-09-13 18:08:44 +01:00
2022-03-06 13:29:38 +00:00
mediaTest.LongBlockSize = testSize;
break;
}
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
break;
}
case 1024:
{
foreach(ushort testSize in new ushort[]
{
// Long sector sizes for floppies
1026,
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
// Long sector sizes for 1024-byte magneto-opticals
1200
})
{
sense = mediaTest.SupportsReadLong16 == true
2022-03-07 07:36:44 +00:00
? _dev.ReadLong16(out buffer, out senseBuffer, false, 0, testSize, _dev.Timeout,
out _) : _dev.ReadLong10(out buffer, out senseBuffer, false,
false, 0, testSize, _dev.Timeout, out _);
2022-03-06 13:29:38 +00:00
if(sense || _dev.Error)
continue;
2021-09-13 18:08:44 +01:00
2022-03-06 13:29:38 +00:00
mediaTest.LongBlockSize = testSize;
break;
}
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
break;
}
case 2048:
{
sense = mediaTest.SupportsReadLong16 == true
2022-03-07 07:36:44 +00:00
? _dev.ReadLong16(out buffer, out senseBuffer, false, 0, 2380, _dev.Timeout, out _)
: _dev.ReadLong10(out buffer, out senseBuffer, false, false, 0, 2380, _dev.Timeout,
out _);
2022-03-06 13:29:38 +00:00
if(!sense &&
!_dev.Error)
mediaTest.LongBlockSize = 2380;
2022-03-06 13:29:38 +00:00
break;
}
case 4096:
{
sense = mediaTest.SupportsReadLong16 == true
2022-03-07 07:36:44 +00:00
? _dev.ReadLong16(out buffer, out senseBuffer, false, 0, 4760, _dev.Timeout, out _)
: _dev.ReadLong10(out buffer, out senseBuffer, false, false, 0, 4760, _dev.Timeout,
out _);
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
if(!sense &&
!_dev.Error)
mediaTest.LongBlockSize = 4760;
2022-03-06 13:29:38 +00:00
break;
}
case 8192:
{
sense = mediaTest.SupportsReadLong16 == true
2022-03-07 07:36:44 +00:00
? _dev.ReadLong16(out buffer, out senseBuffer, false, 0, 9424, _dev.Timeout, out _)
: _dev.ReadLong10(out buffer, out senseBuffer, false, false, 0, 9424, _dev.Timeout,
out _);
2022-03-06 13:29:38 +00:00
if(!sense &&
!_dev.Error)
mediaTest.LongBlockSize = 9424;
break;
}
2022-03-06 13:29:38 +00:00
}
});
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask("Trying SCSI READ MEDIA SERIAL NUMBER...").IsIndeterminate();
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
mediaTest.CanReadMediaSerial =
!_dev.ReadMediaSerialNumber(out buffer, out senseBuffer, _dev.Timeout, out _);
});
2022-03-06 13:29:38 +00:00
return mediaTest;
}
/// <summary>Creates a media report for a non-removable SCSI device</summary>
/// <returns>Media report</returns>
public TestedMedia ReportScsi()
{
2022-03-07 07:36:44 +00:00
var sense = true;
2022-03-06 13:29:38 +00:00
byte[] buffer = Array.Empty<byte>();
byte[] senseBuffer = Array.Empty<byte>();
2022-03-06 13:29:38 +00:00
var capabilities = new TestedMedia
{
2022-03-06 13:29:38 +00:00
MediaIsRecognized = true
};
2021-09-13 18:08:44 +01:00
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask("Querying SCSI READ CAPACITY...").IsIndeterminate();
sense = _dev.ReadCapacity(out buffer, out senseBuffer, _dev.Timeout, out _);
});
2022-03-06 13:29:38 +00:00
if(!sense &&
!_dev.Error)
{
capabilities.SupportsReadCapacity = true;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
capabilities.Blocks = ((ulong)((buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[3]) &
0xFFFFFFFF) + 1;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
capabilities.BlockSize = (uint)((buffer[4] << 24) + (buffer[5] << 16) + (buffer[6] << 8) + buffer[7]);
}
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask("Querying SCSI READ CAPACITY (16)...").IsIndeterminate();
sense = _dev.ReadCapacity16(out buffer, out buffer, _dev.Timeout, out _);
});
2022-03-06 13:29:38 +00:00
if(!sense &&
!_dev.Error)
{
capabilities.SupportsReadCapacity16 = true;
2022-03-07 07:36:44 +00:00
var temp = new byte[8];
2022-03-06 13:29:38 +00:00
Array.Copy(buffer, 0, temp, 0, 8);
Array.Reverse(temp);
capabilities.Blocks = BitConverter.ToUInt64(temp, 0) + 1;
capabilities.BlockSize = (uint)((buffer[8] << 24) + (buffer[9] << 16) + (buffer[10] << 8) + buffer[11]);
}
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
Modes.DecodedMode? decMode = null;
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask("Querying SCSI MODE SENSE (10)...").IsIndeterminate();
2022-03-07 07:36:44 +00:00
sense = _dev.ModeSense10(out buffer, out senseBuffer, false, true, ScsiModeSensePageControl.Current, 0x3F,
0x00, _dev.Timeout, out _);
2022-03-06 13:29:38 +00:00
});
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
if(!sense &&
!_dev.Error)
{
decMode = Modes.DecodeMode10(buffer, _dev.ScsiType);
capabilities.ModeSense10Data = buffer;
}
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask("Querying SCSI MODE SENSE...").IsIndeterminate();
sense = _dev.ModeSense(out buffer, out senseBuffer, _dev.Timeout, out _);
});
2022-03-06 13:29:38 +00:00
if(!sense &&
!_dev.Error)
{
decMode ??= Modes.DecodeMode6(buffer, _dev.ScsiType);
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
capabilities.ModeSense6Data = buffer;
}
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
if(decMode.HasValue)
{
capabilities.MediumType = (byte)decMode.Value.Header.MediumType;
2022-03-06 13:29:38 +00:00
if(decMode.Value.Header.BlockDescriptors?.Length > 0)
capabilities.Density = (byte)decMode.Value.Header.BlockDescriptors[0].Density;
}
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask("Trying SCSI READ (6)...").IsIndeterminate();
2022-03-06 13:29:38 +00:00
capabilities.SupportsRead6 = !_dev.Read6(out buffer, out senseBuffer, 0, capabilities.BlockSize ?? 512,
_dev.Timeout, out _);
});
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
AaruConsole.DebugWriteLine("SCSI Report", "Sense = {0}", !capabilities.SupportsRead6);
capabilities.Read6Data = buffer;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask("Trying SCSI READ (10)...").IsIndeterminate();
2022-03-07 07:36:44 +00:00
capabilities.SupportsRead10 = !_dev.Read10(out buffer, out senseBuffer, 0, false, false, false, false, 0,
capabilities.BlockSize ?? 512, 0, 1, _dev.Timeout, out _);
2022-03-06 13:29:38 +00:00
});
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
AaruConsole.DebugWriteLine("SCSI Report", "Sense = {0}", !capabilities.SupportsRead10);
capabilities.Read10Data = buffer;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask("Trying SCSI READ (12)...").IsIndeterminate();
2022-03-07 07:36:44 +00:00
capabilities.SupportsRead12 = !_dev.Read12(out buffer, out senseBuffer, 0, false, false, false, false, 0,
capabilities.BlockSize ?? 512, 0, 1, false, _dev.Timeout, out _);
2022-03-06 13:29:38 +00:00
});
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
AaruConsole.DebugWriteLine("SCSI Report", "Sense = {0}", !capabilities.SupportsRead12);
capabilities.Read12Data = buffer;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask("Trying SCSI READ (16)...").IsIndeterminate();
2022-03-06 13:29:38 +00:00
capabilities.SupportsRead16 = !_dev.Read16(out buffer, out senseBuffer, 0, false, false, false, 0,
2022-03-07 07:36:44 +00:00
capabilities.BlockSize ?? 512, 0, 1, false, _dev.Timeout, out _);
2022-03-06 13:29:38 +00:00
});
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
AaruConsole.DebugWriteLine("SCSI Report", "Sense = {0}", !capabilities.SupportsRead16);
capabilities.Read16Data = buffer;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
capabilities.LongBlockSize = capabilities.BlockSize;
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask("Trying SCSI READ LONG (10)...").IsIndeterminate();
sense = _dev.ReadLong10(out buffer, out senseBuffer, false, false, 0, 0xFFFF, _dev.Timeout, out _);
});
2021-09-13 18:08:44 +01:00
2022-03-06 13:29:38 +00:00
if(sense && !_dev.Error)
{
DecodedSense? decSense = Sense.Decode(senseBuffer);
2019-12-27 18:00:03 +00:00
if(decSense is { SenseKey: SenseKeys.IllegalRequest, ASC: 0x24, ASCQ: 0x00 })
{
2022-03-06 13:29:38 +00:00
capabilities.SupportsReadLong = true;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
bool valid = decSense.Value.Fixed?.InformationValid == true;
bool ili = decSense.Value.Fixed?.ILI == true;
uint information = decSense.Value.Fixed?.Information ?? 0;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
if(decSense.Value.Descriptor.HasValue &&
decSense.Value.Descriptor.Value.Descriptors.TryGetValue(0, out byte[] desc00))
{
valid = true;
ili = true;
information = (uint)Sense.DecodeDescriptor00(desc00);
2020-07-22 13:20:25 +01:00
}
2022-03-06 13:29:38 +00:00
if(valid && ili)
capabilities.LongBlockSize = 0xFFFF - (information & 0xFFFF);
}
2022-03-06 13:29:38 +00:00
}
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask("Trying SCSI READ LONG (16)...").IsIndeterminate();
sense = _dev.ReadLong16(out buffer, out senseBuffer, false, 0, 0xFFFF, _dev.Timeout, out _);
});
2022-03-06 13:29:38 +00:00
if(sense && !_dev.Error)
{
capabilities.SupportsReadLong16 = true;
DecodedSense? decSense = Sense.Decode(senseBuffer);
if(decSense is { SenseKey: SenseKeys.IllegalRequest, ASC: 0x24, ASCQ: 0x00 })
{
capabilities.SupportsReadLong16 = true;
2022-03-06 13:29:38 +00:00
bool valid = decSense.Value.Fixed?.InformationValid == true;
bool ili = decSense.Value.Fixed?.ILI == true;
uint information = decSense.Value.Fixed?.Information ?? 0;
2022-03-06 13:29:38 +00:00
if(decSense.Value.Descriptor.HasValue &&
decSense.Value.Descriptor.Value.Descriptors.TryGetValue(0, out byte[] desc00))
{
valid = true;
ili = true;
information = (uint)Sense.DecodeDescriptor00(desc00);
}
2022-03-06 13:29:38 +00:00
if(valid && ili)
capabilities.LongBlockSize = 0xFFFF - (information & 0xFFFF);
}
2022-03-06 13:29:38 +00:00
}
2019-12-27 18:00:03 +00:00
2022-03-07 07:36:44 +00:00
if(capabilities.SupportsReadLong != true && capabilities.SupportsReadLong16 != true ||
2022-03-06 13:29:38 +00:00
capabilities.LongBlockSize != capabilities.BlockSize)
return capabilities;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask(capabilities.SupportsReadLong16 == true ? "Trying SCSI READ LONG (16)..."
: "Trying SCSI READ LONG (10)...").IsIndeterminate();
2021-09-13 18:08:44 +01:00
2022-03-06 13:29:38 +00:00
switch(capabilities.BlockSize)
{
case 512:
{
2022-03-06 13:29:38 +00:00
foreach(ushort testSize in new ushort[]
{
// Long sector sizes for floppies
514,
2022-03-06 13:29:38 +00:00
// Long sector sizes for SuperDisk
536, 558,
2022-03-06 13:29:38 +00:00
// Long sector sizes for 512-byte magneto-opticals
600, 610, 630
})
{
sense = capabilities.SupportsReadLong16 == true
? _dev.ReadLong16(out buffer, out senseBuffer, false, 0, testSize, _dev.Timeout,
out _) : _dev.ReadLong10(out buffer, out senseBuffer, false,
false, 0, testSize, _dev.Timeout, out _);
2022-03-06 13:29:38 +00:00
if(sense || _dev.Error)
continue;
2021-09-13 18:08:44 +01:00
2022-03-06 13:29:38 +00:00
capabilities.SupportsReadLong = true;
capabilities.LongBlockSize = testSize;
break;
}
2022-03-06 13:29:38 +00:00
break;
}
case 1024:
{
foreach(ushort testSize in new ushort[]
{
// Long sector sizes for floppies
1026,
2021-09-13 18:08:44 +01:00
2022-03-06 13:29:38 +00:00
// Long sector sizes for 1024-byte magneto-opticals
1200
})
{
sense = capabilities.SupportsReadLong16 == true
2022-03-06 13:29:38 +00:00
? _dev.ReadLong16(out buffer, out senseBuffer, false, 0, testSize, _dev.Timeout,
out _) : _dev.ReadLong10(out buffer, out senseBuffer, false,
false, 0, testSize, _dev.Timeout, out _);
if(sense || _dev.Error)
2022-03-06 13:29:38 +00:00
continue;
capabilities.SupportsReadLong = true;
2022-03-06 13:29:38 +00:00
capabilities.LongBlockSize = testSize;
2019-12-27 18:00:03 +00:00
break;
}
2022-03-06 13:29:38 +00:00
break;
}
case 2048:
{
sense = capabilities.SupportsReadLong16 == true
? _dev.ReadLong16(out buffer, out senseBuffer, false, 0, 2380, _dev.Timeout, out _)
: _dev.ReadLong10(out buffer, out senseBuffer, false, false, 0, 2380, _dev.Timeout,
out _);
2022-03-06 13:29:38 +00:00
if(sense || _dev.Error)
return;
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
capabilities.SupportsReadLong = true;
capabilities.LongBlockSize = 2380;
2022-03-06 13:29:38 +00:00
break;
}
case 4096:
{
sense = capabilities.SupportsReadLong16 == true
? _dev.ReadLong16(out buffer, out senseBuffer, false, 0, 4760, _dev.Timeout, out _)
: _dev.ReadLong10(out buffer, out senseBuffer, false, false, 0, 4760, _dev.Timeout,
out _);
2019-12-27 18:00:03 +00:00
2022-03-06 13:29:38 +00:00
if(sense || _dev.Error)
return;
2022-03-06 13:29:38 +00:00
capabilities.SupportsReadLong = true;
capabilities.LongBlockSize = 4760;
break;
}
2022-03-06 13:29:38 +00:00
case 8192:
{
sense = capabilities.SupportsReadLong16 == true
? _dev.ReadLong16(out buffer, out senseBuffer, false, 0, 9424, _dev.Timeout, out _)
: _dev.ReadLong10(out buffer, out senseBuffer, false, false, 0, 9424, _dev.Timeout,
out _);
2022-03-06 13:29:38 +00:00
if(sense || _dev.Error)
return;
capabilities.SupportsReadLong = true;
capabilities.LongBlockSize = 9424;
break;
}
}
});
return capabilities;
}
2017-12-19 20:33:03 +00:00
}